Compare commits

...
16 Commits
Author SHA1 Message Date
2dust 007a4e674f up 2.2.4 2026-06-14 11:35:05 +08:00
2dust 46af9d803c Improved insecure display 2026-06-14 11:34:53 +08:00
2dust 32a9263e0a Add vcn and pcs properties to VmessQRCode 2026-06-14 11:18:35 +08:00
2dust 253e171fec Update layout_tls.xml 2026-06-14 11:18:00 +08:00
2dust 31b66a705b Bug fix 2026-06-14 11:05:34 +08:00
2dust 23c7cd5b95 Update AndroidLibXrayLite 2026-06-14 10:57:32 +08:00
2dust 32a37dd626 Update strings.xml 2026-06-14 10:56:57 +08:00
53e84d5fd8 add fetch cert sha256 manual (#5749)
* add fetch cert sha256

* Potential fix for pull request finding

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

* Potential fix for pull request finding

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

* btn_pinned_ca256_action

---------

Co-authored-by: 2dust <31833384+2dust@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-06-14 10:50:50 +08:00
Skh-web6982 0519d02001 Update kotlin version to 2.4.0 (#5768)
* Update kotlin version to 2.4.0

* Update kotlin version to 2.4.0
2026-06-14 09:59:27 +08:00
MrArrowww 6dfed5bbc2 Add Fragment Packet 1-1 option to UI (#5774) 2026-06-14 09:59:07 +08:00
2dust dcd060e350 Use decoded server list when removing servers 2026-06-13 15:32:15 +08:00
2dust 71f94db929 Refresh group tab titles on server changes
https://github.com/2dust/v2rayNG/issues/5765
2026-06-13 15:20:06 +08:00
Hossein Abaspanah d06b9cb999 Update Luri Bakhtiari translation (#5737)
* Update strings.xml

* Update strings.xml
2026-06-06 09:42:35 +08:00
direnquanbuKO f53a91241c Correct & add some DNS Host ips (#5734) 2026-06-04 14:58:30 +08:00
solokot 3fb067dc99 Update Russian translation (#5730)
* Update Russian translation

* Update Russian translation
2026-06-04 14:52:36 +08:00
chlink2025 02103287e5 add verifyPeerCertByName (#5727) 2026-06-03 09:05:36 +08:00
31 changed files with 343 additions and 42 deletions

No files matched your search

+1 -1
View File
@@ -3,7 +3,7 @@
A V2Ray client for Android, support [Xray core](https://github.com/XTLS/Xray-core) and [v2fly core](https://github.com/v2fly/v2ray-core) A V2Ray client for Android, support [Xray core](https://github.com/XTLS/Xray-core) and [v2fly core](https://github.com/v2fly/v2ray-core)
[![API](https://img.shields.io/badge/API-24%2B-yellow.svg?style=flat)](https://developer.android.com/about/versions/lollipop) [![API](https://img.shields.io/badge/API-24%2B-yellow.svg?style=flat)](https://developer.android.com/about/versions/lollipop)
[![Kotlin Version](https://img.shields.io/badge/Kotlin-2.3.21-blue.svg)](https://kotlinlang.org) [![Kotlin Version](https://img.shields.io/badge/Kotlin-2.4.0-blue.svg)](https://kotlinlang.org)
[![GitHub commit activity](https://img.shields.io/github/commit-activity/m/2dust/v2rayNG)](https://github.com/2dust/v2rayNG/commits/master) [![GitHub commit activity](https://img.shields.io/github/commit-activity/m/2dust/v2rayNG)](https://github.com/2dust/v2rayNG/commits/master)
[![CodeFactor](https://www.codefactor.io/repository/github/2dust/v2rayng/badge)](https://www.codefactor.io/repository/github/2dust/v2rayng) [![CodeFactor](https://www.codefactor.io/repository/github/2dust/v2rayng/badge)](https://www.codefactor.io/repository/github/2dust/v2rayng)
[![GitHub Releases](https://img.shields.io/github/downloads/2dust/v2rayNG/latest/total?logo=github)](https://github.com/2dust/v2rayNG/releases) [![GitHub Releases](https://img.shields.io/github/downloads/2dust/v2rayNG/latest/total?logo=github)](https://github.com/2dust/v2rayNG/releases)
+3 -3
View File
@@ -12,8 +12,8 @@ android {
applicationId = "com.v2ray.ang" applicationId = "com.v2ray.ang"
minSdk = 24 minSdk = 24
targetSdk = 37 targetSdk = 37
versionCode = 733 versionCode = 734
versionName = "2.2.3" versionName = "2.2.4"
multiDexEnabled = true multiDexEnabled = true
val abiFilterList = (properties["ABI_FILTERS"] as? String)?.split(';') val abiFilterList = (properties["ABI_FILTERS"] as? String)?.split(';')
@@ -21,7 +21,7 @@ android {
abi { abi {
isEnable = true isEnable = true
reset() reset()
if (abiFilterList != null && abiFilterList.isNotEmpty()) { if (!abiFilterList.isNullOrEmpty()) {
include(*abiFilterList.toTypedArray()) include(*abiFilterList.toTypedArray())
} else { } else {
include( include(
@@ -203,13 +203,19 @@ object AppConfig {
const val GOOGLEAPIS_COM_DOMAIN = "googleapis.com" const val GOOGLEAPIS_COM_DOMAIN = "googleapis.com"
// Android Private DNS constants // Android Private DNS constants
const val DNS_DNSPOD_DOMAIN = "dot.pub"
const val DNS_ALIDNS_DOMAIN = "dns.alidns.com" const val DNS_ALIDNS_DOMAIN = "dns.alidns.com"
const val DNS_CISCO_SSE_DOMAIN = "dns.sse.cisco.com"
const val DNS_CISCO_UMBRELLA_DOMAIN = "dns.umbrella.com"
const val DNS_CLOUDFLARE_ONE_DOMAIN = "one.one.one.one" const val DNS_CLOUDFLARE_ONE_DOMAIN = "one.one.one.one"
const val DNS_CLOUDFLARE_ONEDOT_DNS_DOMAIN = "1dot1dot1dot1.cloudflare-dns.com"
const val DNS_CLOUDFLARE_DNS_COM_DOMAIN = "dns.cloudflare.com" const val DNS_CLOUDFLARE_DNS_COM_DOMAIN = "dns.cloudflare.com"
const val DNS_CLOUDFLARE_DNS_DOMAIN = "cloudflare-dns.com" const val DNS_CLOUDFLARE_DNS_DOMAIN = "cloudflare-dns.com"
const val DNS_CLOUDFLARE_WARP_DOMAIN = "engage.cloudflareclient.com"
const val DNS_DNSPOD_DOH_DOMAIN = "doh.pub"
const val DNS_DNSPOD_DOT_DOMAIN = "dot.pub"
const val DNS_GOOGLE_DOMAIN = "dns.google" const val DNS_GOOGLE_DOMAIN = "dns.google"
const val DNS_QUAD9_DOMAIN = "dns.quad9.net" const val DNS_QUAD9_DOMAIN = "dns.quad9.net"
const val DNS_SB_DOMAIN = "dns.sb"
const val DNS_YANDEX_DOMAIN = "common.dot.dns.yandex.net" const val DNS_YANDEX_DOMAIN = "common.dot.dns.yandex.net"
const val DEFAULT_PORT = 443 const val DEFAULT_PORT = 443
@@ -223,12 +229,18 @@ object AppConfig {
const val UNIDENTIFIED_PACKAGE = "__unknown_app__" const val UNIDENTIFIED_PACKAGE = "__unknown_app__"
val DNS_ALIDNS_ADDRESSES = arrayListOf("223.5.5.5", "223.6.6.6", "2400:3200::1", "2400:3200:baba::1") val DNS_ALIDNS_ADDRESSES = arrayListOf("223.5.5.5", "223.6.6.6", "2400:3200::1", "2400:3200:baba::1")
val DNS_CISCO_SSE_ADDRESSES = arrayListOf("208.67.220.220", "208.67.222.222", "2620:119:35::35", "2620:119:53::53")
val DNS_CISCO_UMBRELLA_ADDRESSES = arrayListOf("208.67.220.220", "208.67.222.222", "2620:119:35::35", "2620:119:53::53")
val DNS_CLOUDFLARE_ONE_ADDRESSES = arrayListOf("1.1.1.1", "1.0.0.1", "2606:4700:4700::1111", "2606:4700:4700::1001") val DNS_CLOUDFLARE_ONE_ADDRESSES = arrayListOf("1.1.1.1", "1.0.0.1", "2606:4700:4700::1111", "2606:4700:4700::1001")
val DNS_CLOUDFLARE_DNS_COM_ADDRESSES = arrayListOf("104.16.132.229", "104.16.133.229", "2606:4700::6810:84e5", "2606:4700::6810:85e5") val DNS_CLOUDFLARE_ONEDOT_DNS_ADDRESSES = arrayListOf("1.1.1.1", "1.0.0.1", "2606:4700:4700::1111", "2606:4700:4700::1001")
val DNS_CLOUDFLARE_DNS_COM_ADDRESSES = arrayListOf("162.159.61.8", "172.64.41.8", "2a06:98c1:52::8", "2803:f800:53::8")
val DNS_CLOUDFLARE_DNS_ADDRESSES = arrayListOf("104.16.248.249", "104.16.249.249", "2606:4700::6810:f8f9", "2606:4700::6810:f9f9") val DNS_CLOUDFLARE_DNS_ADDRESSES = arrayListOf("104.16.248.249", "104.16.249.249", "2606:4700::6810:f8f9", "2606:4700::6810:f9f9")
val DNS_DNSPOD_ADDRESSES = arrayListOf("1.12.12.12", "120.53.53.53") val DNS_CLOUDFLARE_WARP_ADDRESSES = arrayListOf("162.159.192.1", "2606:4700:d0::a29f:c001")
val DNS_DNSPOD_DOH_ADDRESSES = arrayListOf("1.12.12.12", "120.53.53.53")
val DNS_DNSPOD_DOT_ADDRESSES = arrayListOf("1.12.12.12", "120.53.53.53")
val DNS_GOOGLE_ADDRESSES = arrayListOf("8.8.8.8", "8.8.4.4", "2001:4860:4860::8888", "2001:4860:4860::8844") val DNS_GOOGLE_ADDRESSES = arrayListOf("8.8.8.8", "8.8.4.4", "2001:4860:4860::8888", "2001:4860:4860::8844")
val DNS_QUAD9_ADDRESSES = arrayListOf("9.9.9.9", "149.112.112.112", "2620:fe::fe", "2620:fe::9") val DNS_QUAD9_ADDRESSES = arrayListOf("9.9.9.9", "149.112.112.112", "2620:fe::fe", "2620:fe::9")
val DNS_SB_ADDRESSES = arrayListOf("45.11.45.11", "185.222.222.222", "2a09::", "2a11::")
val DNS_YANDEX_ADDRESSES = arrayListOf("77.88.8.8", "77.88.8.1", "2a02:6b8::feed:0ff", "2a02:6b8:0:1::feed:0ff") val DNS_YANDEX_ADDRESSES = arrayListOf("77.88.8.8", "77.88.8.1", "2a02:6b8::feed:0ff", "2a02:6b8:0:1::feed:0ff")
//minimum list https://serverfault.com/a/304791 //minimum list https://serverfault.com/a/304791
@@ -710,12 +710,18 @@ object CoreConfigManager {
// hardcode popular Android Private DNS rule to fix localhost DNS problem // hardcode popular Android Private DNS rule to fix localhost DNS problem
hosts[AppConfig.DNS_ALIDNS_DOMAIN] = AppConfig.DNS_ALIDNS_ADDRESSES hosts[AppConfig.DNS_ALIDNS_DOMAIN] = AppConfig.DNS_ALIDNS_ADDRESSES
hosts[AppConfig.DNS_CISCO_SSE_DOMAIN] = AppConfig.DNS_CISCO_SSE_ADDRESSES
hosts[AppConfig.DNS_CISCO_UMBRELLA_DOMAIN] = AppConfig.DNS_CISCO_UMBRELLA_ADDRESSES
hosts[AppConfig.DNS_CLOUDFLARE_ONE_DOMAIN] = AppConfig.DNS_CLOUDFLARE_ONE_ADDRESSES hosts[AppConfig.DNS_CLOUDFLARE_ONE_DOMAIN] = AppConfig.DNS_CLOUDFLARE_ONE_ADDRESSES
hosts[AppConfig.DNS_CLOUDFLARE_ONEDOT_DNS_DOMAIN] = AppConfig.DNS_CLOUDFLARE_ONEDOT_DNS_ADDRESSES
hosts[AppConfig.DNS_CLOUDFLARE_DNS_COM_DOMAIN] = AppConfig.DNS_CLOUDFLARE_DNS_COM_ADDRESSES hosts[AppConfig.DNS_CLOUDFLARE_DNS_COM_DOMAIN] = AppConfig.DNS_CLOUDFLARE_DNS_COM_ADDRESSES
hosts[AppConfig.DNS_CLOUDFLARE_DNS_DOMAIN] = AppConfig.DNS_CLOUDFLARE_DNS_ADDRESSES hosts[AppConfig.DNS_CLOUDFLARE_DNS_DOMAIN] = AppConfig.DNS_CLOUDFLARE_DNS_ADDRESSES
hosts[AppConfig.DNS_DNSPOD_DOMAIN] = AppConfig.DNS_DNSPOD_ADDRESSES hosts[AppConfig.DNS_CLOUDFLARE_WARP_DOMAIN] = AppConfig.DNS_CLOUDFLARE_WARP_ADDRESSES
hosts[AppConfig.DNS_DNSPOD_DOH_DOMAIN] = AppConfig.DNS_DNSPOD_DOH_ADDRESSES
hosts[AppConfig.DNS_DNSPOD_DOT_DOMAIN] = AppConfig.DNS_DNSPOD_DOT_ADDRESSES
hosts[AppConfig.DNS_GOOGLE_DOMAIN] = AppConfig.DNS_GOOGLE_ADDRESSES hosts[AppConfig.DNS_GOOGLE_DOMAIN] = AppConfig.DNS_GOOGLE_ADDRESSES
hosts[AppConfig.DNS_QUAD9_DOMAIN] = AppConfig.DNS_QUAD9_ADDRESSES hosts[AppConfig.DNS_QUAD9_DOMAIN] = AppConfig.DNS_QUAD9_ADDRESSES
hosts[AppConfig.DNS_SB_DOMAIN] = AppConfig.DNS_SB_ADDRESSES
hosts[AppConfig.DNS_YANDEX_DOMAIN] = AppConfig.DNS_YANDEX_ADDRESSES hosts[AppConfig.DNS_YANDEX_DOMAIN] = AppConfig.DNS_YANDEX_ADDRESSES
//User DNS hosts //User DNS hosts
@@ -564,6 +564,7 @@ object CoreOutboundBuilder {
fingerprint = profileItem.fingerPrint.nullIfBlank(), fingerprint = profileItem.fingerPrint.nullIfBlank(),
alpn = profileItem.alpn?.split(",")?.map { it.trim() }?.filter { it.isNotEmpty() }.takeIf { !it.isNullOrEmpty() }, alpn = profileItem.alpn?.split(",")?.map { it.trim() }?.filter { it.isNotEmpty() }.takeIf { !it.isNullOrEmpty() },
echConfigList = profileItem.echConfigList.nullIfBlank(), echConfigList = profileItem.echConfigList.nullIfBlank(),
verifyPeerCertByName = profileItem.verifyPeerCertByName.nullIfBlank(),
pinnedPeerCertSha256 = profileItem.pinnedCA256.nullIfBlank(), pinnedPeerCertSha256 = profileItem.pinnedCA256.nullIfBlank(),
publicKey = profileItem.publicKey.nullIfBlank(), publicKey = profileItem.publicKey.nullIfBlank(),
shortId = profileItem.shortId.nullIfBlank(), shortId = profileItem.shortId.nullIfBlank(),
@@ -0,0 +1,13 @@
package com.v2ray.ang.dto
data class CertSha256Request(
val address: String = "",
val port: Int = 443,
val serverName: String? = null,
val timeoutMs: Long = 5000L,
)
data class CertSha256Result(
val sha256: String = "",
val error: String = "",
)
@@ -253,6 +253,7 @@ data class V2rayConfig(
val disableSystemRoot: Boolean? = null, val disableSystemRoot: Boolean? = null,
val enableSessionResumption: Boolean? = null, val enableSessionResumption: Boolean? = null,
var echConfigList: String? = null, var echConfigList: String? = null,
var verifyPeerCertByName: String? = null,
var pinnedPeerCertSha256: String? = null, var pinnedPeerCertSha256: String? = null,
// REALITY settings // REALITY settings
val show: Boolean = false, val show: Boolean = false,
@@ -16,5 +16,7 @@ data class VmessQRCode(
var sni: String = "", var sni: String = "",
var alpn: String = "", var alpn: String = "",
var fp: String = "", var fp: String = "",
var insecure: String = "" var insecure: String = "",
var vcn: String = "",
var pcs: String = ""
) )
@@ -42,6 +42,7 @@ data class ProfileItem(
var fingerPrint: String? = null, var fingerPrint: String? = null,
var insecure: Boolean? = null, var insecure: Boolean? = null,
var echConfigList: String? = null, var echConfigList: String? = null,
var verifyPeerCertByName: String? = null,
var pinnedCA256: String? = null, var pinnedCA256: String? = null,
var publicKey: String? = null, var publicKey: String? = null,
@@ -84,6 +84,7 @@ open class FmtBase {
config.fingerPrint = queryParam["fp"] config.fingerPrint = queryParam["fp"]
config.alpn = queryParam["alpn"] config.alpn = queryParam["alpn"]
config.echConfigList = queryParam["ech"] config.echConfigList = queryParam["ech"]
config.verifyPeerCertByName = queryParam["vcn"]
config.pinnedCA256 = queryParam["pcs"] config.pinnedCA256 = queryParam["pcs"]
config.publicKey = queryParam["pbk"] config.publicKey = queryParam["pbk"]
config.shortId = queryParam["sid"] config.shortId = queryParam["sid"]
@@ -104,6 +105,7 @@ open class FmtBase {
config.sni?.nullIfBlank()?.let { dicQuery["sni"] = it } config.sni?.nullIfBlank()?.let { dicQuery["sni"] = it }
config.alpn?.nullIfBlank()?.let { dicQuery["alpn"] = it } config.alpn?.nullIfBlank()?.let { dicQuery["alpn"] = it }
config.echConfigList?.nullIfBlank()?.let { dicQuery["ech"] = it } config.echConfigList?.nullIfBlank()?.let { dicQuery["ech"] = it }
config.verifyPeerCertByName?.nullIfBlank()?.let { dicQuery["vcn"] = it }
config.pinnedCA256?.nullIfBlank()?.let { dicQuery["pcs"] = it } config.pinnedCA256?.nullIfBlank()?.let { dicQuery["pcs"] = it }
config.fingerPrint?.nullIfBlank()?.let { dicQuery["fp"] = it } config.fingerPrint?.nullIfBlank()?.let { dicQuery["fp"] = it }
config.publicKey?.nullIfBlank()?.let { dicQuery["pbk"] = it } config.publicKey?.nullIfBlank()?.let { dicQuery["pbk"] = it }
@@ -88,6 +88,9 @@ object VmessFmt : FmtBase() {
"0" -> false "0" -> false
else -> false else -> false
} }
config.verifyPeerCertByName = vmessQRCode.vcn
config.pinnedCA256 = vmessQRCode.pcs
return config return config
} }
@@ -141,6 +144,8 @@ object VmessFmt : FmtBase() {
false -> "0" false -> "0"
else -> "" else -> ""
} }
vmessQRCode.vcn = config.verifyPeerCertByName.orEmpty()
vmessQRCode.pcs = config.pinnedCA256.orEmpty()
val json = JsonUtil.toJson(vmessQRCode) val json = JsonUtil.toJson(vmessQRCode)
return Utils.encode(json) return Utils.encode(json)
@@ -0,0 +1,79 @@
package com.v2ray.ang.handler
import com.v2ray.ang.AppConfig
import com.v2ray.ang.dto.CertSha256Request
import com.v2ray.ang.dto.CertSha256Result
import com.v2ray.ang.dto.entities.ProfileItem
import com.v2ray.ang.enums.EConfigType
import com.v2ray.ang.util.HttpUtil
import com.v2ray.ang.util.JsonUtil
import com.v2ray.ang.util.LogUtil
import com.v2ray.ang.util.Utils
import libv2ray.Libv2ray
object CertificateFingerprintManager {
private const val TIMEOUT_MS = 5000L
fun fetchForManualFill(profile: ProfileItem): String? {
val request = buildRequest(profile) ?: return null
val result = if (profile.configType == EConfigType.HYSTERIA2) {
fetch("quic", request) { Libv2ray.fetchQuicCertSha256(it) }
} else {
fetch("tls", request) { Libv2ray.fetchTlsCertSha256(it) }
}
return result
?.takeIf { it.error.isBlank() }
?.sha256
?.takeIf { it.isNotBlank() }
}
private fun buildRequest(profile: ProfileItem): CertSha256Request? {
if (!isFetchable(profile)) return null
val server = profile.server?.trim()?.takeIf { it.isNotEmpty() } ?: return null
val port = profile.serverPort?.toIntOrNull()?.takeIf { it > 0 } ?: AppConfig.DEFAULT_PORT
return CertSha256Request(
address = resolveDialAddress(server),
port = port,
serverName = inferServerName(profile),
timeoutMs = TIMEOUT_MS,
)
}
private fun isFetchable(profile: ProfileItem): Boolean {
return profile.configType == EConfigType.HYSTERIA2 || profile.security == AppConfig.TLS
}
private fun fetch(
type: String,
request: CertSha256Request,
fetcher: (String) -> String,
): CertSha256Result? {
return try {
JsonUtil.fromJsonSafe(fetcher(JsonUtil.toJson(request)), CertSha256Result::class.java)
} catch (e: UnsatisfiedLinkError) {
LogUtil.e(AppConfig.TAG, "Fetch $type cert SHA-256 API missing in libv2ray", e)
null
} catch (e: Exception) {
LogUtil.e(AppConfig.TAG, "Fetch $type cert SHA-256 failed", e)
null
}
}
private fun resolveDialAddress(server: String): String {
if (Utils.isPureIpAddress(server) || !Utils.isDomainName(server)) return server
val preferIpv6 = MmkvManager.decodeSettingsBool(AppConfig.PREF_PREFER_IPV6, false)
return HttpUtil.resolveHostToIP(server, preferIpv6)
?.firstOrNull()
?.takeIf { it.isNotBlank() }
?: server
}
private fun inferServerName(profile: ProfileItem): String? {
val sni = profile.sni?.takeIf { it.isNotBlank() }
return sni?.takeUnless { Utils.isPureIpAddress(it) }
}
}
@@ -226,6 +226,7 @@ class GroupServerFragment : BaseFragment<FragmentGroupServerBinding>(),
private fun removeServerSub(guid: String, position: Int) { private fun removeServerSub(guid: String, position: Int) {
mainViewModel.removeServer(guid) mainViewModel.removeServer(guid)
adapter.removeServerSub(guid, position) adapter.removeServerSub(guid, position)
ownerActivity.refreshGroupTabTitles()
} }
/** /**
@@ -76,23 +76,7 @@ class MainActivity : HelperBaseActivity(), NavigationView.OnNavigationItemSelect
binding.viewPager.isUserInputEnabled = true binding.viewPager.isUserInputEnabled = true
// setup navigation drawer // setup navigation drawer
val toggle = ActionBarDrawerToggle( setupNavigationDrawer()
this, binding.drawerLayout, binding.toolbar, R.string.navigation_drawer_open, R.string.navigation_drawer_close
)
binding.drawerLayout.addDrawerListener(toggle)
toggle.syncState()
binding.navView.setNavigationItemSelectedListener(this)
onBackPressedDispatcher.addCallback(this, object : OnBackPressedCallback(true) {
override fun handleOnBackPressed() {
if (binding.drawerLayout.isDrawerOpen(GravityCompat.START)) {
binding.drawerLayout.closeDrawer(GravityCompat.START)
} else {
isEnabled = false
onBackPressedDispatcher.onBackPressed()
isEnabled = true
}
}
})
binding.fab.setOnClickListener { handleFabAction() } binding.fab.setOnClickListener { handleFabAction() }
binding.layoutTest.setOnClickListener { handleLayoutTestClick() } binding.layoutTest.setOnClickListener { handleLayoutTestClick() }
@@ -106,6 +90,31 @@ class MainActivity : HelperBaseActivity(), NavigationView.OnNavigationItemSelect
} }
} }
private fun setupNavigationDrawer() {
val toggle = ActionBarDrawerToggle(
this,
binding.drawerLayout,
binding.toolbar,
R.string.navigation_drawer_open,
R.string.navigation_drawer_close
)
binding.drawerLayout.addDrawerListener(toggle)
toggle.syncState()
binding.navView.setNavigationItemSelectedListener(this)
onBackPressedDispatcher.addCallback(this, object : OnBackPressedCallback(true) {
override fun handleOnBackPressed() {
if (binding.drawerLayout.isDrawerOpen(GravityCompat.START)) {
binding.drawerLayout.closeDrawer(GravityCompat.START)
} else {
isEnabled = false
onBackPressedDispatcher.onBackPressed()
isEnabled = true
}
}
})
}
private fun setupViewModel() { private fun setupViewModel() {
mainViewModel.updateTestResultAction.observe(this) { setTestState(it) } mainViewModel.updateTestResultAction.observe(this) { setTestState(it) }
mainViewModel.isRunning.observe(this) { isRunning -> mainViewModel.isRunning.observe(this) { isRunning ->
@@ -131,6 +140,26 @@ class MainActivity : HelperBaseActivity(), NavigationView.OnNavigationItemSelect
binding.viewPager.setCurrentItem(targetIndex, false) binding.viewPager.setCurrentItem(targetIndex, false)
binding.tabGroup.isVisible = groups.size > 1 binding.tabGroup.isVisible = groups.size > 1
refreshGroupTabTitles(true)
}
fun refreshGroupTabTitles(refreshAll: Boolean = false) {
val groupsToRefresh = if (refreshAll || mainViewModel.subscriptionId.isEmpty()) {
groupPagerAdapter.groups
} else {
groupPagerAdapter.groups.filter { it.id == mainViewModel.subscriptionId }
}
groupsToRefresh.forEach { group ->
if (group.id.isEmpty()) {
return@forEach
}
val tabIndex = groupPagerAdapter.groups.indexOfFirst { it.id == group.id }
if (tabIndex >= 0) {
val count = MmkvManager.decodeServerList(group.id).size
binding.tabGroup.getTabAt(tabIndex)?.text = "${group.remarks} ($count)"
}
}
} }
private fun handleFabAction() { private fun handleFabAction() {
@@ -416,6 +445,7 @@ class MainActivity : HelperBaseActivity(), NavigationView.OnNavigationItemSelect
count > 0 -> { count > 0 -> {
toast(getString(R.string.title_import_config_count, count)) toast(getString(R.string.title_import_config_count, count))
mainViewModel.reloadServerList() mainViewModel.reloadServerList()
refreshGroupTabTitles()
} }
countSub > 0 -> setupGroupTab() countSub > 0 -> setupGroupTab()
@@ -471,6 +501,7 @@ class MainActivity : HelperBaseActivity(), NavigationView.OnNavigationItemSelect
} }
if (result.configCount > 0) { if (result.configCount > 0) {
mainViewModel.reloadServerList() mainViewModel.reloadServerList()
refreshGroupTabTitles()
} }
hideLoading() hideLoading()
} }
@@ -500,6 +531,7 @@ class MainActivity : HelperBaseActivity(), NavigationView.OnNavigationItemSelect
val ret = mainViewModel.removeAllServer() val ret = mainViewModel.removeAllServer()
launch(Dispatchers.Main) { launch(Dispatchers.Main) {
mainViewModel.reloadServerList() mainViewModel.reloadServerList()
refreshGroupTabTitles()
toast(getString(R.string.title_del_config_count, ret)) toast(getString(R.string.title_del_config_count, ret))
hideLoading() hideLoading()
} }
@@ -519,6 +551,7 @@ class MainActivity : HelperBaseActivity(), NavigationView.OnNavigationItemSelect
val ret = mainViewModel.removeDuplicateServer() val ret = mainViewModel.removeDuplicateServer()
launch(Dispatchers.Main) { launch(Dispatchers.Main) {
mainViewModel.reloadServerList() mainViewModel.reloadServerList()
refreshGroupTabTitles()
toast(getString(R.string.title_del_duplicate_config_count, ret)) toast(getString(R.string.title_del_duplicate_config_count, ret))
hideLoading() hideLoading()
} }
@@ -538,6 +571,7 @@ class MainActivity : HelperBaseActivity(), NavigationView.OnNavigationItemSelect
val ret = mainViewModel.removeInvalidServer() val ret = mainViewModel.removeInvalidServer()
launch(Dispatchers.Main) { launch(Dispatchers.Main) {
mainViewModel.reloadServerList() mainViewModel.reloadServerList()
refreshGroupTabTitles()
toast(getString(R.string.title_del_config_count, ret)) toast(getString(R.string.title_del_config_count, ret))
hideLoading() hideLoading()
} }
@@ -159,8 +159,12 @@ class MainRecyclerAdapter(
// Security: hide blank or tls // Security: hide blank or tls
profile.security?.let { sec -> profile.security?.let { sec ->
if (sec.isNotBlank() && !sec.equals("tls", ignoreCase = true)) { if (sec.isNotBlank()) {
parts.add(sec) if (profile.insecure == true && sec.equals("tls", ignoreCase = true)) {
parts.add("$sec insecure") // TODO
} else {
parts.add(sec)
}
} }
} }
@@ -7,11 +7,13 @@ import android.view.MenuItem
import android.view.View import android.view.View
import android.widget.AdapterView import android.widget.AdapterView
import android.widget.ArrayAdapter import android.widget.ArrayAdapter
import android.widget.Button
import android.widget.EditText import android.widget.EditText
import android.widget.LinearLayout import android.widget.LinearLayout
import android.widget.Spinner import android.widget.Spinner
import android.widget.TextView import android.widget.TextView
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import androidx.lifecycle.lifecycleScope
import com.v2ray.ang.AppConfig import com.v2ray.ang.AppConfig
import com.v2ray.ang.AppConfig.DEFAULT_PORT import com.v2ray.ang.AppConfig.DEFAULT_PORT
import com.v2ray.ang.AppConfig.REALITY import com.v2ray.ang.AppConfig.REALITY
@@ -27,10 +29,14 @@ import com.v2ray.ang.extension.nullIfBlank
import com.v2ray.ang.extension.toast import com.v2ray.ang.extension.toast
import com.v2ray.ang.extension.toastSuccess import com.v2ray.ang.extension.toastSuccess
import com.v2ray.ang.handler.AngConfigManager import com.v2ray.ang.handler.AngConfigManager
import com.v2ray.ang.handler.CertificateFingerprintManager
import com.v2ray.ang.handler.MmkvManager import com.v2ray.ang.handler.MmkvManager
import com.v2ray.ang.handler.SettingsChangeManager import com.v2ray.ang.handler.SettingsChangeManager
import com.v2ray.ang.util.JsonUtil import com.v2ray.ang.util.JsonUtil
import com.v2ray.ang.util.Utils import com.v2ray.ang.util.Utils
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
class ServerActivity : BaseActivity() { class ServerActivity : BaseActivity() {
@@ -140,7 +146,10 @@ class ServerActivity : BaseActivity() {
private val layout_extra: LinearLayout? by lazy { findViewById(R.id.layout_extra) } private val layout_extra: LinearLayout? by lazy { findViewById(R.id.layout_extra) }
private val et_ech_config_list: EditText? by lazy { findViewById(R.id.et_ech_config_list) } private val et_ech_config_list: EditText? by lazy { findViewById(R.id.et_ech_config_list) }
private val container_ech_config_list: LinearLayout? by lazy { findViewById(R.id.lay_ech_config_list) } private val container_ech_config_list: LinearLayout? by lazy { findViewById(R.id.lay_ech_config_list) }
private val et_verify_peer_cert_by_name: EditText? by lazy { findViewById(R.id.et_verify_peer_cert_by_name) }
private val container_verify_peer_cert_by_name: LinearLayout? by lazy { findViewById(R.id.lay_verify_peer_cert_by_name) }
private val et_pinned_ca256: EditText? by lazy { findViewById(R.id.et_pinned_ca256) } private val et_pinned_ca256: EditText? by lazy { findViewById(R.id.et_pinned_ca256) }
private val btn_pinned_ca256_action: Button? by lazy { findViewById(R.id.btn_pinned_ca256_action) }
private val container_pinned_ca256: LinearLayout? by lazy { findViewById(R.id.lay_pinned_ca256) } private val container_pinned_ca256: LinearLayout? by lazy { findViewById(R.id.lay_pinned_ca256) }
private val layout_browser_dialer: LinearLayout? by lazy { findViewById(R.id.layout_browser_dialer) } private val layout_browser_dialer: LinearLayout? by lazy { findViewById(R.id.layout_browser_dialer) }
private val sp_browser_dialer_mode: Spinner? by lazy { findViewById(R.id.sp_browser_dialer_mode) } private val sp_browser_dialer_mode: Spinner? by lazy { findViewById(R.id.sp_browser_dialer_mode) }
@@ -295,6 +304,7 @@ class ServerActivity : BaseActivity() {
container_spider_x, container_spider_x,
container_mldsa65_verify, container_mldsa65_verify,
container_ech_config_list, container_ech_config_list,
container_verify_peer_cert_by_name,
container_pinned_ca256 container_pinned_ca256
).forEach { it?.visibility = View.GONE } ).forEach { it?.visibility = View.GONE }
} }
@@ -307,6 +317,7 @@ class ServerActivity : BaseActivity() {
container_alpn, container_alpn,
container_allow_insecure, container_allow_insecure,
container_ech_config_list, container_ech_config_list,
container_verify_peer_cert_by_name,
container_pinned_ca256 container_pinned_ca256
).forEach { it?.visibility = View.VISIBLE } ).forEach { it?.visibility = View.VISIBLE }
listOf( listOf(
@@ -327,6 +338,7 @@ class ServerActivity : BaseActivity() {
container_alpn, container_alpn,
container_allow_insecure, container_allow_insecure,
container_ech_config_list, container_ech_config_list,
container_verify_peer_cert_by_name,
container_pinned_ca256 container_pinned_ca256
).forEach { it?.visibility = View.GONE } ).forEach { it?.visibility = View.GONE }
listOf( listOf(
@@ -343,6 +355,9 @@ class ServerActivity : BaseActivity() {
// do nothing // do nothing
} }
} }
btn_pinned_ca256_action?.setOnClickListener {
fetchPinnedCA256ForCurrentConfig()
}
if (config != null) { if (config != null) {
bindingServer(config) bindingServer(config)
} else { } else {
@@ -410,6 +425,7 @@ class ServerActivity : BaseActivity() {
sp_allow_insecure?.setSelection(allowinsecure) sp_allow_insecure?.setSelection(allowinsecure)
} }
et_ech_config_list?.text = Utils.getEditable(config.echConfigList) et_ech_config_list?.text = Utils.getEditable(config.echConfigList)
et_verify_peer_cert_by_name?.text = Utils.getEditable(config.verifyPeerCertByName)
et_pinned_ca256?.text = Utils.getEditable(config.pinnedCA256) et_pinned_ca256?.text = Utils.getEditable(config.pinnedCA256)
} else if (config.security == REALITY) { } else if (config.security == REALITY) {
et_public_key?.text = Utils.getEditable(config.publicKey.orEmpty()) et_public_key?.text = Utils.getEditable(config.publicKey.orEmpty())
@@ -613,6 +629,7 @@ class ServerActivity : BaseActivity() {
val spiderX = et_spider_x?.text?.toString() val spiderX = et_spider_x?.text?.toString()
val mldsa65Verify = et_mldsa65_verify?.text?.toString() val mldsa65Verify = et_mldsa65_verify?.text?.toString()
val echConfigList = et_ech_config_list?.text?.toString() val echConfigList = et_ech_config_list?.text?.toString()
val verifyPeerCertByName = et_verify_peer_cert_by_name?.text?.toString()
val pinnedCA256 = et_pinned_ca256?.text?.toString() val pinnedCA256 = et_pinned_ca256?.text?.toString()
val allowInsecure = val allowInsecure =
@@ -632,9 +649,51 @@ class ServerActivity : BaseActivity() {
config.spiderX = spiderX config.spiderX = spiderX
config.mldsa65Verify = mldsa65Verify config.mldsa65Verify = mldsa65Verify
config.echConfigList = echConfigList config.echConfigList = echConfigList
config.verifyPeerCertByName = verifyPeerCertByName
config.pinnedCA256 = pinnedCA256 config.pinnedCA256 = pinnedCA256
} }
private fun fetchPinnedCA256ForCurrentConfig() {
val config = buildCurrentProfileForCertificateFetch() ?: return
lifecycleScope.launch {
btn_pinned_ca256_action?.isEnabled = false
try {
val sha256 = withContext(Dispatchers.IO) {
CertificateFingerprintManager.fetchForManualFill(config)
}
if (sha256.isNullOrBlank()) {
toast(R.string.toast_fetch_cert_sha256_failed)
} else {
et_pinned_ca256?.text = Utils.getEditable(sha256)
toastSuccess(R.string.toast_fetch_cert_sha256_success)
}
} finally {
btn_pinned_ca256_action?.isEnabled = true
}
}
}
private fun buildCurrentProfileForCertificateFetch(): ProfileItem? {
if (TextUtils.isEmpty(et_address.text.toString())) {
toast(R.string.server_lab_address)
return null
}
val configType = MmkvManager.decodeServerConfig(editGuid)?.configType ?: createConfigType
if (configType != EConfigType.HYSTERIA2 && Utils.parseInt(et_port.text.toString()) <= 0) {
toast(R.string.server_lab_port)
return null
}
val config = ProfileItem.create(configType)
saveCommon(config)
saveStreamSettings(config)
saveTls(config)
return config
}
private fun transportTypes(network: String?): Array<out String> { private fun transportTypes(network: String?): Array<out String> {
return when (network) { return when (network) {
NetworkType.TCP.type -> { NetworkType.TCP.type -> {
@@ -341,11 +341,11 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
if (subscriptionId.isEmpty() && keywordFilter.isEmpty()) { if (subscriptionId.isEmpty() && keywordFilter.isEmpty()) {
MmkvManager.removeAllServer() MmkvManager.removeAllServer()
} else { } else {
val serversCopy = serversCache.toList() val serverList = MmkvManager.decodeServerList(subscriptionId)
for (item in serversCopy) { for (guid in serverList) {
MmkvManager.removeServer(item.guid) MmkvManager.removeServer(guid)
} }
serversCache.toList().count() serverList.count()
} }
return count return count
} }
+30 -1
View File
@@ -114,7 +114,7 @@
android:layout_marginTop="@dimen/padding_spacing_dp8" android:layout_marginTop="@dimen/padding_spacing_dp8"
android:layout_marginBottom="@dimen/padding_spacing_dp8" android:layout_marginBottom="@dimen/padding_spacing_dp8"
android:entries="@array/allowinsecures" android:entries="@array/allowinsecures"
android:nextFocusDown="@+id/et_ech_config_list"/> android:nextFocusDown="@+id/et_verify_peer_cert_by_name"/>
</LinearLayout> </LinearLayout>
<LinearLayout <LinearLayout
@@ -138,6 +138,27 @@
</LinearLayout> </LinearLayout>
<LinearLayout
android:id="@+id/lay_verify_peer_cert_by_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="@dimen/padding_spacing_dp16"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/server_lab_verify_peer_cert_by_name" />
<EditText
android:id="@+id/et_verify_peer_cert_by_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="text"
android:nextFocusDown="@+id/et_ech_config_list" />
</LinearLayout>
<LinearLayout <LinearLayout
android:id="@+id/lay_pinned_ca256" android:id="@+id/lay_pinned_ca256"
android:layout_width="match_parent" android:layout_width="match_parent"
@@ -157,6 +178,14 @@
android:inputType="text" android:inputType="text"
android:nextFocusDown="@+id/et_public_key" /> android:nextFocusDown="@+id/et_public_key" />
<Button
android:id="@+id/btn_pinned_ca256_action"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/padding_spacing_dp8"
android:layout_marginBottom="@dimen/padding_spacing_dp8"
android:text="@string/pinned_ca256_action_fetch" />
</LinearLayout> </LinearLayout>
<LinearLayout <LinearLayout
@@ -88,6 +88,14 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:inputType="text" /> android:inputType="text" />
<Button
android:id="@+id/btn_pinned_ca256_action"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/padding_spacing_dp8"
android:layout_marginBottom="@dimen/padding_spacing_dp8"
android:text="@string/pinned_ca256_action_fetch" />
</LinearLayout> </LinearLayout>
</LinearLayout> </LinearLayout>
@@ -118,7 +118,11 @@
<string name="server_lab_xhttp_extra">XHTTP Extra raw JSON, format: { XHTTPObject }</string> <string name="server_lab_xhttp_extra">XHTTP Extra raw JSON, format: { XHTTPObject }</string>
<string name="server_lab_final_mask">finalMask raw JSON, format: { FinalMaskObject }</string> <string name="server_lab_final_mask">finalMask raw JSON, format: { FinalMaskObject }</string>
<string name="server_lab_ech_config_list">EchConfigList</string> <string name="server_lab_ech_config_list">EchConfigList</string>
<string name="server_lab_verify_peer_cert_by_name">Verify Peer Cert By Name</string>
<string name="server_lab_pinned_ca256">Certificate fingerprint (SHA-256)</string> <string name="server_lab_pinned_ca256">Certificate fingerprint (SHA-256)</string>
<string name="pinned_ca256_action_fetch">Fetch and fill certificate fingerprint</string>
<string name="toast_fetch_cert_sha256_success">Certificate fingerprint fetched successfully</string>
<string name="toast_fetch_cert_sha256_failed">Failed to fetch certificate fingerprint</string>
<!-- UserAssetActivity --> <!-- UserAssetActivity -->
<string name="toast_asset_copy_failed">فشل نسخ الملف، يرجى استخدام مدير الملفات</string> <string name="toast_asset_copy_failed">فشل نسخ الملف، يرجى استخدام مدير الملفات</string>
@@ -117,7 +117,11 @@
<string name="server_lab_xhttp_extra">XHTTP Extra raw JSON, format: { XHTTPObject }</string> <string name="server_lab_xhttp_extra">XHTTP Extra raw JSON, format: { XHTTPObject }</string>
<string name="server_lab_final_mask">finalMask raw JSON, format: { FinalMaskObject }</string> <string name="server_lab_final_mask">finalMask raw JSON, format: { FinalMaskObject }</string>
<string name="server_lab_ech_config_list">EchConfigList</string> <string name="server_lab_ech_config_list">EchConfigList</string>
<string name="server_lab_verify_peer_cert_by_name">Verify Peer Cert By Name</string>
<string name="server_lab_pinned_ca256">Certificate fingerprint (SHA-256)</string> <string name="server_lab_pinned_ca256">Certificate fingerprint (SHA-256)</string>
<string name="pinned_ca256_action_fetch">Fetch and fill certificate fingerprint</string>
<string name="toast_fetch_cert_sha256_success">Certificate fingerprint fetched successfully</string>
<string name="toast_fetch_cert_sha256_failed">Failed to fetch certificate fingerprint</string>
<!-- UserAssetActivity --> <!-- UserAssetActivity -->
<string name="toast_asset_copy_failed">ফাইল কপি ব্যর্থ, অনুগ্রহ করে ফাইল ম্যানেজার ব্যবহার করুন</string> <string name="toast_asset_copy_failed">ফাইল কপি ব্যর্থ, অনুগ্রহ করে ফাইল ম্যানেজার ব্যবহার করুন</string>
@@ -117,7 +117,11 @@
<string name="server_lab_xhttp_extra">XHTTP Extra خام JSON، قالوو: { XHTTPObject }</string> <string name="server_lab_xhttp_extra">XHTTP Extra خام JSON، قالوو: { XHTTPObject }</string>
<string name="server_lab_final_mask">finalMask خام JSON، قالوو: { FinalMaskObject }</string> <string name="server_lab_final_mask">finalMask خام JSON، قالوو: { FinalMaskObject }</string>
<string name="server_lab_ech_config_list">نومگه کانفیگ Ech</string> <string name="server_lab_ech_config_list">نومگه کانفیگ Ech</string>
<string name="server_lab_pinned_ca256">Certificate fingerprint (SHA-256)</string> <string name="server_lab_verify_peer_cert_by_name">تئیڌ گوواهی هومتا و ری نوم</string>
<string name="server_lab_pinned_ca256">جا کلک گوواهی (SHA-256)</string>
<string name="pinned_ca256_action_fetch">Fetch and fill certificate fingerprint</string>
<string name="toast_fetch_cert_sha256_success">Certificate fingerprint fetched successfully</string>
<string name="toast_fetch_cert_sha256_failed">Failed to fetch certificate fingerprint</string>
<!-- UserAssetActivity --> <!-- UserAssetActivity -->
<string name="toast_asset_copy_failed">لف گیری فایل ٱنجوم نوابی، ز ی برنومه دؽوۉداری فایل هیاری بگرین</string> <string name="toast_asset_copy_failed">لف گیری فایل ٱنجوم نوابی، ز ی برنومه دؽوۉداری فایل هیاری بگرین</string>
@@ -357,7 +361,7 @@
<string name="connection_not_connected">منپیز نؽڌ</string> <string name="connection_not_connected">منپیز نؽڌ</string>
<string name="connection_runing_task_left">تعداد وزیفه یل آزمایشی ک ره اۊفتانه: %s</string> <string name="connection_runing_task_left">تعداد وزیفه یل آزمایشی ک ره اۊفتانه: %s</string>
<string name="import_subscription_success">اشتراک وا موفقیت زفت زابی</string> <string name="import_subscription_success">اشتراک وا مووفقیت زفت وابی</string>
<string name="import_subscription_failure">اشتراک زفت نوابی</string> <string name="import_subscription_failure">اشتراک زفت نوابی</string>
<string name="title_fragment_settings">سامووا فرگمنت</string> <string name="title_fragment_settings">سامووا فرگمنت</string>
<string name="title_pref_fragment_packets">کتنا فرگمنت</string> <string name="title_pref_fragment_packets">کتنا فرگمنت</string>
@@ -445,7 +449,7 @@
</string-array> </string-array>
<string-array name="policy_group_type"> <string-array name="policy_group_type">
<item>کم ترین پینگ</item> <item>کم ترین پینگ</item>
<item>کم ترین بار(لود)</item> <item>کم ترین بار (لود)</item>
<item>تساڌۊفی</item> <item>تساڌۊفی</item>
<item>گردشی</item> <item>گردشی</item>
</string-array> </string-array>
@@ -117,7 +117,11 @@
<string name="server_lab_xhttp_extra">خام JSON XHTTP Extra، قالب: { XHTTPObject }</string> <string name="server_lab_xhttp_extra">خام JSON XHTTP Extra، قالب: { XHTTPObject }</string>
<string name="server_lab_final_mask">finalMask raw JSON, format: { FinalMaskObject }</string> <string name="server_lab_final_mask">finalMask raw JSON, format: { FinalMaskObject }</string>
<string name="server_lab_ech_config_list">لیست کانفیگ Ech</string> <string name="server_lab_ech_config_list">لیست کانفیگ Ech</string>
<string name="server_lab_verify_peer_cert_by_name">Verify Peer Cert By Name</string>
<string name="server_lab_pinned_ca256">Certificate fingerprint (SHA-256)</string> <string name="server_lab_pinned_ca256">Certificate fingerprint (SHA-256)</string>
<string name="pinned_ca256_action_fetch">Fetch and fill certificate fingerprint</string>
<string name="toast_fetch_cert_sha256_success">Certificate fingerprint fetched successfully</string>
<string name="toast_fetch_cert_sha256_failed">Failed to fetch certificate fingerprint</string>
<!-- UserAssetActivity --> <!-- UserAssetActivity -->
<string name="toast_asset_copy_failed">کپی فایل انجام نشد، لطفا از برنامه مدیریت فایل استفاده کنید</string> <string name="toast_asset_copy_failed">کپی فایل انجام نشد، لطفا از برنامه مدیریت فایل استفاده کنید</string>
+14 -3
View File
@@ -29,7 +29,7 @@
<string name="menu_item_import_config_qrcode">Импорт из QR-кода</string> <string name="menu_item_import_config_qrcode">Импорт из QR-кода</string>
<string name="menu_item_import_config_clipboard">Импорт из буфера обмена</string> <string name="menu_item_import_config_clipboard">Импорт из буфера обмена</string>
<string name="menu_item_import_config_local">Импорт из файла</string> <string name="menu_item_import_config_local">Импорт из файла</string>
<string name="menu_item_import_config_policy_group">Добавить группу политик</string> <string name="menu_item_import_config_policy_group">Добавить политику группы</string>
<string name="menu_item_import_config_proxy_chain">Добавить цепочку прокси</string> <string name="menu_item_import_config_proxy_chain">Добавить цепочку прокси</string>
<string name="menu_item_import_config_manually_vmess">Добавить VMess</string> <string name="menu_item_import_config_manually_vmess">Добавить VMess</string>
<string name="menu_item_import_config_manually_vless">Добавить VLESS</string> <string name="menu_item_import_config_manually_vless">Добавить VLESS</string>
@@ -117,9 +117,14 @@
<string name="server_lab_xhttp_extra">Необработанный JSON XHTTP Extra, формат: { XHTTPObject }</string> <string name="server_lab_xhttp_extra">Необработанный JSON XHTTP Extra, формат: { XHTTPObject }</string>
<string name="server_lab_final_mask">Необработанный JSON FinalMask, формат: { FinalMaskObject }</string> <string name="server_lab_final_mask">Необработанный JSON FinalMask, формат: { FinalMaskObject }</string>
<string name="server_lab_ech_config_list">ECHConfigList</string> <string name="server_lab_ech_config_list">ECHConfigList</string>
<string name="server_lab_verify_peer_cert_by_name">Проверять сертификат узла по имени</string>
<string name="server_lab_pinned_ca256">Отпечаток сертификата (SHA-256)</string> <string name="server_lab_pinned_ca256">Отпечаток сертификата (SHA-256)</string>
<string name="server_lab_browser_dialer">Использовать переадресацию браузера</string> <string name="server_lab_browser_dialer">Использовать переадресацию браузера</string>
<string name="server_lab_browser_dialer_tip">Поддерживаются только исходящие соединения XHTTP (packet-up) и WS; настройки, связанные с TLS, могут быть проигнорированы или конфликтовать</string> <string name="server_lab_browser_dialer_tip">Поддерживаются только исходящие соединения XHTTP (packet-up) и WS; настройки, связанные с TLS, могут быть проигнорированы или конфликтовать</string>
<string name="toast_allow_insecure_deprecated">ВНИМАНИЕ: пропуск проверки сертификата («Разрешать небезопасные соединения») будет отключён в августе 2026 года. Пожалуйста, используйте отпечатки сертификатов как можно скорее. Функция будет недоступна после истечения срока действия.</string>
<string name="pinned_ca256_action_fetch">Fetch and fill certificate fingerprint</string>
<string name="toast_fetch_cert_sha256_success">Certificate fingerprint fetched successfully</string>
<string name="toast_fetch_cert_sha256_failed">Failed to fetch certificate fingerprint</string>
<!-- UserAssetActivity --> <!-- UserAssetActivity -->
<string name="toast_asset_copy_failed">Невозможно скопировать файл, используйте файловый менеджер</string> <string name="toast_asset_copy_failed">Невозможно скопировать файл, используйте файловый менеджер</string>
@@ -288,7 +293,7 @@
<string name="title_logcat">Журнал</string> <string name="title_logcat">Журнал</string>
<string name="logcat_copy">Копировать</string> <string name="logcat_copy">Копировать</string>
<string name="logcat_clear">Очистить</string> <string name="logcat_clear">Очистить</string>
<string name="logcat_share">Поделиться логом</string> <string name="logcat_share">Поделиться журналом</string>
<string name="title_service_restart">Перезапуск службы</string> <string name="title_service_restart">Перезапуск службы</string>
<string name="title_del_all_config">Удалить профили</string> <string name="title_del_all_config">Удалить профили</string>
<string name="title_del_duplicate_config">Удалить дубликаты профилей</string> <string name="title_del_duplicate_config">Удалить дубликаты профилей</string>
@@ -377,7 +382,7 @@
<string name="update_check_pre_release">Искать предварительный выпуск</string> <string name="update_check_pre_release">Искать предварительный выпуск</string>
<string name="update_checking_for_update">Проверка обновления…</string> <string name="update_checking_for_update">Проверка обновления…</string>
<string name="title_policy_group_type">Тип группы политик</string> <string name="title_policy_group_type">Тип политики группы</string>
<string name="title_policy_group_subscription_id">Из группы подписки</string> <string name="title_policy_group_subscription_id">Из группы подписки</string>
<string name="title_policy_group_subscription_filter">Название фильтра</string> <string name="title_policy_group_subscription_filter">Название фильтра</string>
@@ -424,6 +429,12 @@
<item>Прокси</item> <item>Прокси</item>
</string-array> </string-array>
<string-array name="browser_dialer_mode">
<item>Отключено</item>
<item>OkHttp</item>
<item>WebView</item>
</string-array>
<string-array name="ui_mode_night"> <string-array name="ui_mode_night">
<item>Как в системе</item> <item>Как в системе</item>
<item>Светлая</item> <item>Светлая</item>
@@ -117,7 +117,11 @@
<string name="server_lab_xhttp_extra">XHTTP Extra raw JSON, format: { XHTTPObject }</string> <string name="server_lab_xhttp_extra">XHTTP Extra raw JSON, format: { XHTTPObject }</string>
<string name="server_lab_final_mask">finalMask raw JSON, format: { FinalMaskObject }</string> <string name="server_lab_final_mask">finalMask raw JSON, format: { FinalMaskObject }</string>
<string name="server_lab_ech_config_list">EchConfigList</string> <string name="server_lab_ech_config_list">EchConfigList</string>
<string name="server_lab_verify_peer_cert_by_name">Verify Peer Cert By Name</string>
<string name="server_lab_pinned_ca256">Certificate fingerprint (SHA-256)</string> <string name="server_lab_pinned_ca256">Certificate fingerprint (SHA-256)</string>
<string name="pinned_ca256_action_fetch">Fetch and fill certificate fingerprint</string>
<string name="toast_fetch_cert_sha256_success">Certificate fingerprint fetched successfully</string>
<string name="toast_fetch_cert_sha256_failed">Failed to fetch certificate fingerprint</string>
<!-- UserAssetActivity --> <!-- UserAssetActivity -->
<string name="toast_asset_copy_failed">Không thể sao chép tệp tin, hãy dùng trình quản lý tệp!</string> <string name="toast_asset_copy_failed">Không thể sao chép tệp tin, hãy dùng trình quản lý tệp!</string>
@@ -117,10 +117,14 @@
<string name="server_lab_xhttp_extra">XHTTP Extra 原始 JSON,格式: { XHTTPObject }</string> <string name="server_lab_xhttp_extra">XHTTP Extra 原始 JSON,格式: { XHTTPObject }</string>
<string name="server_lab_final_mask">FinalMask 原始 JSON 格式: { FinalMaskObject }</string> <string name="server_lab_final_mask">FinalMask 原始 JSON 格式: { FinalMaskObject }</string>
<string name="server_lab_ech_config_list">EchConfigList</string> <string name="server_lab_ech_config_list">EchConfigList</string>
<string name="server_lab_verify_peer_cert_by_name">Verify Peer Cert By Name</string>
<string name="server_lab_pinned_ca256">证书指纹 (SHA-256)</string> <string name="server_lab_pinned_ca256">证书指纹 (SHA-256)</string>
<string name="server_lab_browser_dialer">启用浏览器转发</string> <string name="server_lab_browser_dialer">启用浏览器转发</string>
<string name="server_lab_browser_dialer_tip">仅支持 xhttp (packet-up) 和 ws。与优选域名冲突,utls, alpn, ech 等 TLS 设置将被忽略</string> <string name="server_lab_browser_dialer_tip">仅支持 xhttp (packet-up) 和 ws。与优选域名冲突,utls, alpn, ech 等 TLS 设置将被忽略</string>
<string name="toast_allow_insecure_deprecated">警告:将在 2026.8.1 禁用跳过证书验证 "allowInsecure" ,请尽快改用证书指纹。到期后无法使用。 </string> <string name="toast_allow_insecure_deprecated">警告:将在 2026.8.1 移除跳过证书验证 "allowInsecure" ,请尽快改用证书指纹或修改服务端。否则到期后会无法使用。 </string>
<string name="pinned_ca256_action_fetch">获取并填充指纹证书</string>
<string name="toast_fetch_cert_sha256_success">获取指纹证书完成</string>
<string name="toast_fetch_cert_sha256_failed">获取证书指纹失败</string>
<!-- UserAssetActivity --> <!-- UserAssetActivity -->
<string name="toast_asset_copy_failed">失败, 请使用文件管理器</string> <string name="toast_asset_copy_failed">失败, 请使用文件管理器</string>
@@ -117,8 +117,12 @@
<string name="server_lab_xhttp_extra">XHTTP Extra 原始 JSON,格式: { XHTTPObject }</string> <string name="server_lab_xhttp_extra">XHTTP Extra 原始 JSON,格式: { XHTTPObject }</string>
<string name="server_lab_final_mask">FinalMask 原始 JSON 格式: { FinalMaskObject }</string> <string name="server_lab_final_mask">FinalMask 原始 JSON 格式: { FinalMaskObject }</string>
<string name="server_lab_ech_config_list">EchConfigList</string> <string name="server_lab_ech_config_list">EchConfigList</string>
<string name="server_lab_verify_peer_cert_by_name">Verify Peer Cert By Name</string>
<string name="server_lab_pinned_ca256">證書指紋 (SHA-256)</string> <string name="server_lab_pinned_ca256">證書指紋 (SHA-256)</string>
<string name="toast_allow_insecure_deprecated">警告:將在 2026.8.1 停用跳過憑證驗證 "allowInsecure" ,請盡快改用憑證指紋。到期後無法使用。 </string> <string name="toast_allow_insecure_deprecated">警告:將在 2026.8.1 停用跳過憑證驗證 "allowInsecure" ,請盡快改用憑證指紋。到期後無法使用。 </string>
<string name="pinned_ca256_action_fetch">獲取並填充指紋證書</string>
<string name="toast_fetch_cert_sha256_success">獲取指紋證書完成</string>
<string name="toast_fetch_cert_sha256_failed">獲取證書指紋失敗</string>
<!-- UserAssetActivity --> <!-- UserAssetActivity -->
<string name="toast_asset_copy_failed">失敗,請使用檔案總管</string> <string name="toast_asset_copy_failed">失敗,請使用檔案總管</string>
@@ -64,6 +64,7 @@
<string-array name="fragment_packets" translatable="false"> <string-array name="fragment_packets" translatable="false">
<item>tlshello</item> <item>tlshello</item>
<item>1-1</item>
<item>1-2</item> <item>1-2</item>
<item>1-3</item> <item>1-3</item>
<item>1-5</item> <item>1-5</item>
@@ -118,10 +118,14 @@
<string name="server_lab_xhttp_extra">XHTTP Extra raw JSON, format: { XHTTPObject }</string> <string name="server_lab_xhttp_extra">XHTTP Extra raw JSON, format: { XHTTPObject }</string>
<string name="server_lab_final_mask">finalMask raw JSON, format: { FinalMaskObject }</string> <string name="server_lab_final_mask">finalMask raw JSON, format: { FinalMaskObject }</string>
<string name="server_lab_ech_config_list">EchConfigList</string> <string name="server_lab_ech_config_list">EchConfigList</string>
<string name="server_lab_verify_peer_cert_by_name">Verify Peer Cert By Name</string>
<string name="server_lab_pinned_ca256">Certificate fingerprint (SHA-256)</string> <string name="server_lab_pinned_ca256">Certificate fingerprint (SHA-256)</string>
<string name="server_lab_browser_dialer">Enable Browser Dialer</string> <string name="server_lab_browser_dialer">Enable Browser Dialer</string>
<string name="server_lab_browser_dialer_tip">Only supports xhttp (packet-up) and ws outbound; TLS-related settings may be ignored or conflict</string> <string name="server_lab_browser_dialer_tip">Only supports xhttp (packet-up) and ws outbound; TLS-related settings may be ignored or conflict</string>
<string name="toast_allow_insecure_deprecated">WARNING: Skipping certificate verification "allowInsecure" will be disabled in August 2026. Please use certificate fingerprints as soon as possible. This feature will not be available after its expiration.</string> <string name="toast_allow_insecure_deprecated">WARNING: Skipping certificate verification "allowInsecure" will be disabled in August 2026. Please use certificate fingerprints as soon as possible. This feature will not be available after its expiration.</string>
<string name="pinned_ca256_action_fetch">Fetch and fill certificate fingerprint</string>
<string name="toast_fetch_cert_sha256_success">Certificate fingerprint fetched successfully</string>
<string name="toast_fetch_cert_sha256_failed">Failed to fetch certificate fingerprint</string>
<!-- UserAssetActivity --> <!-- UserAssetActivity -->
<string name="toast_asset_copy_failed">File copy failed, please use File Manager</string> <string name="toast_asset_copy_failed">File copy failed, please use File Manager</string>
+1 -1
View File
@@ -2,7 +2,7 @@
agp = "9.2.1" agp = "9.2.1"
desugarJdkLibs = "2.1.5" desugarJdkLibs = "2.1.5"
gradleLicensePlugin = "0.9.8" gradleLicensePlugin = "0.9.8"
kotlin = "2.3.21" kotlin = "2.4.0"
coreKtx = "1.17.0" coreKtx = "1.17.0"
junit = "4.13.2" junit = "4.13.2"
junitVersion = "1.3.0" junitVersion = "1.3.0"