mirror of
https://github.com/2dust/v2rayNG.git
synced 2026-10-29 14:39:25 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5436d67e47 | ||
|
|
1c3d247071 | ||
|
|
d3e1e7c6fe | ||
|
|
a65c57ea0d | ||
|
|
8586c2550f | ||
|
|
fa6c4807f3 | ||
|
|
6e65f46e3f | ||
|
|
98e7b8e1a1 | ||
|
|
7dd42528db | ||
|
|
e580b76e9e | ||
|
|
c4d02c00ce | ||
|
|
007a4e674f | ||
|
|
46af9d803c | ||
|
|
32a9263e0a | ||
|
|
253e171fec | ||
|
|
31b66a705b | ||
|
|
23c7cd5b95 | ||
|
|
32a37dd626 | ||
|
|
53e84d5fd8 | ||
|
|
0519d02001 | ||
|
|
6dfed5bbc2 | ||
|
|
dcd060e350 | ||
|
|
71f94db929 | ||
|
|
d06b9cb999 | ||
|
|
f53a91241c | ||
|
|
3fb067dc99 | ||
|
|
02103287e5 | ||
|
|
b103e3aec9 | ||
|
|
19fc65d128 | ||
|
|
27cafd1052 | ||
|
|
a155b24c1e | ||
|
|
e805fcf088 | ||
|
|
95e37db42e | ||
|
|
fe812c4456 | ||
|
|
d5af0fe735 | ||
|
|
9126aa9c9e | ||
|
|
4a763d2ec5 | ||
|
|
08d6de4504 | ||
|
|
629d410189 | ||
|
|
39d2513df1 | ||
|
|
3cb4df474b | ||
|
|
b64bb92c8c | ||
|
|
7f0e2f801d | ||
|
|
0f7cee0613 | ||
|
|
b2e10fc60b | ||
|
|
aa3f17fb51 |
No files matched your search
@@ -16,31 +16,31 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v7
|
||||
with:
|
||||
submodules: 'recursive'
|
||||
fetch-depth: '0'
|
||||
|
||||
- name: Setup Android SDK
|
||||
uses: android-actions/setup-android@v4.0.1
|
||||
uses: android-actions/setup-android@v4
|
||||
with:
|
||||
log-accepted-android-sdk-licenses: false
|
||||
cmdline-tools-version: '13114758'
|
||||
packages: 'platforms;android-36.1 build-tools;36.1.0 platform-tools'
|
||||
cmdline-tools-version: '14742923'
|
||||
packages: 'platforms;android-37.0 build-tools;37.0.0 platform-tools'
|
||||
|
||||
- name: Install NDK
|
||||
run: |
|
||||
echo "y" | $ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager \
|
||||
--channel=0 \
|
||||
--install "ndk;28.2.13676358"
|
||||
echo "NDK_HOME=$ANDROID_HOME/ndk/28.2.13676358" >> $GITHUB_ENV
|
||||
--install "ndk;29.0.14206865"
|
||||
echo "NDK_HOME=$ANDROID_HOME/ndk/29.0.14206865" >> $GITHUB_ENV
|
||||
sed -i '10i\
|
||||
\
|
||||
ndkVersion = "28.2.13676358"' ${{ github.workspace }}/V2rayNG/app/build.gradle.kts
|
||||
ndkVersion = "29.0.14206865"' ${{ github.workspace }}/V2rayNG/app/build.gradle.kts
|
||||
|
||||
- name: Restore cached libhevtun
|
||||
id: cache-libhevtun-restore
|
||||
uses: actions/cache/restore@v4
|
||||
uses: actions/cache/restore@v5
|
||||
with:
|
||||
path: ${{ github.workspace }}/libs
|
||||
key: libhevtun-${{ runner.os }}-${{ env.NDK_HOME }}-${{ hashFiles('.git/modules/hev-socks5-tunnel/HEAD') }}-${{ hashFiles('compile-hevtun.sh') }}
|
||||
@@ -52,7 +52,7 @@ jobs:
|
||||
|
||||
- name: Save libhevtun
|
||||
if: steps.cache-libhevtun-restore.outputs.cache-hit != 'true'
|
||||
uses: actions/cache/save@v4
|
||||
uses: actions/cache/save@v5
|
||||
with:
|
||||
path: ${{ github.workspace }}/libs
|
||||
key: libhevtun-${{ runner.os }}-${{ env.NDK_HOME }}-${{ hashFiles('.git/modules/hev-socks5-tunnel/HEAD') }}-${{ hashFiles('compile-hevtun.sh') }}
|
||||
|
||||
@@ -11,6 +11,6 @@ jobs:
|
||||
go:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v7
|
||||
- name: Validate Fastlane Supply Metadata
|
||||
uses: ashutoshgngwr/validate-fastlane-supply-metadata@v2.1.0
|
||||
+1
-1
Submodule AndroidLibXrayLite updated: 4c3a3cd051...85f272f37c.
@@ -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)
|
||||
|
||||
[](https://developer.android.com/about/versions/lollipop)
|
||||
[](https://kotlinlang.org)
|
||||
[](https://kotlinlang.org)
|
||||
[](https://github.com/2dust/v2rayNG/commits/master)
|
||||
[](https://www.codefactor.io/repository/github/2dust/v2rayng)
|
||||
[](https://github.com/2dust/v2rayNG/releases)
|
||||
|
||||
@@ -12,8 +12,8 @@ android {
|
||||
applicationId = "com.v2ray.ang"
|
||||
minSdk = 24
|
||||
targetSdk = 37
|
||||
versionCode = 730
|
||||
versionName = "2.2.0"
|
||||
versionCode = 735
|
||||
versionName = "2.2.5"
|
||||
multiDexEnabled = true
|
||||
|
||||
val abiFilterList = (properties["ABI_FILTERS"] as? String)?.split(';')
|
||||
@@ -21,7 +21,7 @@ android {
|
||||
abi {
|
||||
isEnable = true
|
||||
reset()
|
||||
if (abiFilterList != null && abiFilterList.isNotEmpty()) {
|
||||
if (!abiFilterList.isNullOrEmpty()) {
|
||||
include(*abiFilterList.toTypedArray())
|
||||
} else {
|
||||
include(
|
||||
|
||||
@@ -55,7 +55,6 @@ object AppConfig {
|
||||
const val PREF_IPV6_ENABLED = "pref_ipv6_enabled"
|
||||
const val PREF_PREFER_IPV6 = "pref_prefer_ipv6"
|
||||
const val PREF_PROXY_SHARING = "pref_proxy_sharing_enabled"
|
||||
const val PREF_ALLOW_INSECURE = "pref_allow_insecure"
|
||||
const val PREF_ENABLE_LOCAL_PROXY = "pref_enable_local_proxy"
|
||||
const val PREF_SOCKS_PORT = "pref_socks_port"
|
||||
const val PREF_DYNAMIC_SOCKS_PORT = "pref_dynamic_socks_port"
|
||||
@@ -204,13 +203,19 @@ object AppConfig {
|
||||
const val GOOGLEAPIS_COM_DOMAIN = "googleapis.com"
|
||||
|
||||
// Android Private DNS constants
|
||||
const val DNS_DNSPOD_DOMAIN = "dot.pub"
|
||||
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_ONEDOT_DNS_DOMAIN = "1dot1dot1dot1.cloudflare-dns.com"
|
||||
const val DNS_CLOUDFLARE_DNS_COM_DOMAIN = "dns.cloudflare.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_QUAD9_DOMAIN = "dns.quad9.net"
|
||||
const val DNS_SB_DOMAIN = "dns.sb"
|
||||
const val DNS_YANDEX_DOMAIN = "common.dot.dns.yandex.net"
|
||||
|
||||
const val DEFAULT_PORT = 443
|
||||
@@ -224,12 +229,18 @@ object AppConfig {
|
||||
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_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_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_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_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")
|
||||
|
||||
//minimum list https://serverfault.com/a/304791
|
||||
|
||||
@@ -6,6 +6,7 @@ import com.v2ray.ang.dto.CoreConfigContext
|
||||
import com.v2ray.ang.dto.entities.ProfileItem
|
||||
import com.v2ray.ang.enums.CoreResolvedType
|
||||
import com.v2ray.ang.enums.EConfigType
|
||||
import com.v2ray.ang.extension.isComplexType
|
||||
import com.v2ray.ang.extension.isNotNullEmpty
|
||||
import com.v2ray.ang.handler.MmkvManager
|
||||
import com.v2ray.ang.handler.SettingsManager
|
||||
@@ -162,10 +163,8 @@ object CoreConfigContextBuilder {
|
||||
}
|
||||
}
|
||||
.filter { it.server.isNotNullEmpty() }
|
||||
.filter { !Utils.isPureIpAddress(it.server!!) || Utils.isValidUrl(it.server!!) }
|
||||
.filter { it.configType != EConfigType.CUSTOM }
|
||||
.filter { it.configType != EConfigType.POLICYGROUP }
|
||||
.filter { it.configType != EConfigType.PROXYCHAIN }
|
||||
.filter { Utils.isPureIpAddress(it.server!!) || Utils.isValidUrl(it.server!!) }
|
||||
.filter { !it.configType.isComplexType() }
|
||||
.toList()
|
||||
} catch (e: Exception) {
|
||||
LogUtil.e(AppConfig.TAG, "Failed to resolve policy group profiles for '${config.remarks}'", e)
|
||||
@@ -183,10 +182,8 @@ object CoreConfigContextBuilder {
|
||||
.asSequence()
|
||||
.mapNotNull { remark -> SettingsManager.getServerViaRemarks(remark) }
|
||||
.filter { it.server.isNotNullEmpty() }
|
||||
.filter { !Utils.isPureIpAddress(it.server!!) || Utils.isValidUrl(it.server!!) }
|
||||
.filter { it.configType != EConfigType.CUSTOM }
|
||||
.filter { it.configType != EConfigType.POLICYGROUP }
|
||||
.filter { it.configType != EConfigType.PROXYCHAIN }
|
||||
.filter { Utils.isPureIpAddress(it.server!!) || Utils.isValidUrl(it.server!!) }
|
||||
.filter { !it.configType.isComplexType() }
|
||||
.toList()
|
||||
.reversed()
|
||||
} catch (e: Exception) {
|
||||
|
||||
@@ -710,12 +710,18 @@ object CoreConfigManager {
|
||||
|
||||
// hardcode popular Android Private DNS rule to fix localhost DNS problem
|
||||
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_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_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_QUAD9_DOMAIN] = AppConfig.DNS_QUAD9_ADDRESSES
|
||||
hosts[AppConfig.DNS_SB_DOMAIN] = AppConfig.DNS_SB_ADDRESSES
|
||||
hosts[AppConfig.DNS_YANDEX_DOMAIN] = AppConfig.DNS_YANDEX_ADDRESSES
|
||||
|
||||
//User DNS hosts
|
||||
|
||||
@@ -368,37 +368,40 @@ object CoreOutboundBuilder {
|
||||
profileItem.kcpMtu?.let { kcpSetting.mtu = it }
|
||||
profileItem.kcpTti?.let { kcpSetting.tti = it }
|
||||
streamSettings.kcpSettings = kcpSetting
|
||||
val udpMaskList = mutableListOf<OutboundBean.StreamSettingsBean.FinalMaskBean.MaskBean>()
|
||||
val udpMaskList =
|
||||
mutableListOf<OutboundBean.StreamSettingsBean.FinalMaskBean.MaskBean>()
|
||||
if (!headerType.isNullOrEmpty() && headerType != "none") {
|
||||
val kcpHeaderType = when {
|
||||
headerType == "wechat-video" -> "header-wechat"
|
||||
else -> "header-$headerType"
|
||||
headerType == "wechat-video" -> "wechat"
|
||||
else -> headerType
|
||||
}
|
||||
udpMaskList.add(
|
||||
OutboundBean.StreamSettingsBean.FinalMaskBean.MaskBean(
|
||||
type = kcpHeaderType,
|
||||
settings = if (headerType == "dns" && !host.isNullOrEmpty()) {
|
||||
type = "mkcp-legacy",
|
||||
settings =
|
||||
OutboundBean.StreamSettingsBean.FinalMaskBean.MaskBean.MaskSettingsBean(
|
||||
domain = host
|
||||
header = kcpHeaderType,
|
||||
value = if (headerType == "dns" && !host.isNullOrEmpty()) {
|
||||
host
|
||||
} else {
|
||||
null
|
||||
}
|
||||
)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
if (seed.isNullOrEmpty()) {
|
||||
udpMaskList.add(
|
||||
OutboundBean.StreamSettingsBean.FinalMaskBean.MaskBean(
|
||||
type = "mkcp-original"
|
||||
type = "mkcp-legacy"
|
||||
)
|
||||
)
|
||||
} else {
|
||||
udpMaskList.add(
|
||||
OutboundBean.StreamSettingsBean.FinalMaskBean.MaskBean(
|
||||
type = "mkcp-aes128gcm",
|
||||
type = "mkcp-legacy",
|
||||
settings = OutboundBean.StreamSettingsBean.FinalMaskBean.MaskBean.MaskSettingsBean(
|
||||
password = seed
|
||||
value = seed
|
||||
)
|
||||
)
|
||||
)
|
||||
@@ -410,7 +413,7 @@ object CoreOutboundBuilder {
|
||||
|
||||
NetworkType.WS.type -> {
|
||||
val wssetting = OutboundBean.StreamSettingsBean.WsSettingsBean()
|
||||
wssetting.headers.Host = host.orEmpty()
|
||||
wssetting.host = host.orEmpty()
|
||||
sni = host
|
||||
wssetting.path = path ?: "/"
|
||||
streamSettings.wsSettings = wssetting
|
||||
@@ -545,7 +548,7 @@ object CoreOutboundBuilder {
|
||||
*/
|
||||
fun populateTlsSettings(streamSettings: OutboundBean.StreamSettingsBean, profileItem: ProfileItem, sniExt: String?) {
|
||||
val streamSecurity = profileItem.security.orEmpty()
|
||||
val allowInsecure = profileItem.insecure == true
|
||||
val allowInsecure = profileItem.insecure == true && profileItem.pinnedCA256.isNullOrEmpty()
|
||||
val sni = if (profileItem.sni.isNullOrEmpty()) {
|
||||
when {
|
||||
sniExt.isNotNullEmpty() && Utils.isDomainName(sniExt) -> sniExt
|
||||
@@ -564,6 +567,7 @@ object CoreOutboundBuilder {
|
||||
fingerprint = profileItem.fingerPrint.nullIfBlank(),
|
||||
alpn = profileItem.alpn?.split(",")?.map { it.trim() }?.filter { it.isNotEmpty() }.takeIf { !it.isNullOrEmpty() },
|
||||
echConfigList = profileItem.echConfigList.nullIfBlank(),
|
||||
verifyPeerCertByName = profileItem.verifyPeerCertByName.nullIfBlank(),
|
||||
pinnedPeerCertSha256 = profileItem.pinnedCA256.nullIfBlank(),
|
||||
publicKey = profileItem.publicKey.nullIfBlank(),
|
||||
shortId = profileItem.shortId.nullIfBlank(),
|
||||
@@ -611,10 +615,6 @@ object CoreOutboundBuilder {
|
||||
&& packets == "tlshello"
|
||||
) {
|
||||
packets = "1-3"
|
||||
} else if (streamSettings.security == AppConfig.TLS
|
||||
&& packets != "tlshello"
|
||||
) {
|
||||
packets = "tlshello"
|
||||
}
|
||||
|
||||
val fragmentMask = OutboundBean.StreamSettingsBean.FinalMaskBean.MaskBean(
|
||||
@@ -685,4 +685,4 @@ object CoreOutboundBuilder {
|
||||
}
|
||||
return resolvedIps.first()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -16,7 +16,9 @@ import com.v2ray.ang.contracts.ServiceControl
|
||||
import com.v2ray.ang.dto.OutboundTrafficStat
|
||||
import com.v2ray.ang.dto.entities.ProfileItem
|
||||
import com.v2ray.ang.enums.EConfigType
|
||||
import com.v2ray.ang.extension.isComplexType
|
||||
import com.v2ray.ang.extension.toast
|
||||
import com.v2ray.ang.extension.toastError
|
||||
import com.v2ray.ang.handler.MmkvManager
|
||||
import com.v2ray.ang.handler.NotificationManager
|
||||
import com.v2ray.ang.handler.SettingsManager
|
||||
@@ -145,9 +147,7 @@ object CoreServiceManager {
|
||||
error(context.getString(R.string.toast_config_file_invalid))
|
||||
}
|
||||
|
||||
if (config.configType != EConfigType.CUSTOM
|
||||
&& config.configType != EConfigType.POLICYGROUP
|
||||
&& config.configType != EConfigType.PROXYCHAIN
|
||||
if (!config.configType.isComplexType()
|
||||
&& !Utils.isValidUrl(config.server)
|
||||
&& !Utils.isPureIpAddress(config.server.orEmpty())
|
||||
) {
|
||||
@@ -161,6 +161,11 @@ object CoreServiceManager {
|
||||
// val result = V2rayConfigUtil.getV2rayConfig(context, guid)
|
||||
// if (!result.status) error(result.errorMessage.ifBlank { "Failed to get V2Ray config" })
|
||||
|
||||
if (config.insecure == true) {
|
||||
context.toastError(R.string.toast_allow_insecure_deprecated)
|
||||
context.toastError(R.string.toast_allow_insecure_deprecated)
|
||||
}
|
||||
|
||||
if (MmkvManager.decodeSettingsBool(AppConfig.PREF_PROXY_SHARING)) {
|
||||
context.toast(R.string.toast_warning_pref_proxysharing_short)
|
||||
} else {
|
||||
|
||||
@@ -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 = "",
|
||||
)
|
||||
@@ -197,13 +197,12 @@ data class V2rayConfig(
|
||||
|
||||
data class WsSettingsBean(
|
||||
var path: String? = null,
|
||||
var headers: HeadersBean = HeadersBean(),
|
||||
var host: String? = null,
|
||||
var headers: Map<String, String>? = null,
|
||||
val maxEarlyData: Int? = null,
|
||||
val useBrowserForwarding: Boolean? = null,
|
||||
val acceptProxyProtocol: Boolean? = null
|
||||
) {
|
||||
data class HeadersBean(var Host: String = "")
|
||||
}
|
||||
)
|
||||
|
||||
data class HttpupgradeSettingsBean(
|
||||
var path: String? = null,
|
||||
@@ -254,9 +253,9 @@ data class V2rayConfig(
|
||||
val disableSystemRoot: Boolean? = null,
|
||||
val enableSessionResumption: Boolean? = null,
|
||||
var echConfigList: String? = null,
|
||||
var verifyPeerCertByName: String? = null,
|
||||
var pinnedPeerCertSha256: String? = null,
|
||||
// REALITY settings
|
||||
val show: Boolean = false,
|
||||
var publicKey: String? = null,
|
||||
var shortId: String? = null,
|
||||
var spiderX: String? = null,
|
||||
@@ -296,7 +295,8 @@ data class V2rayConfig(
|
||||
) {
|
||||
data class MaskSettingsBean(
|
||||
val password: String? = null,
|
||||
val domain: String? = null,
|
||||
val header: String? = null,
|
||||
val value: String? = null,
|
||||
// fragment
|
||||
val packets: String? = null,
|
||||
val length: String? = null,
|
||||
|
||||
@@ -16,5 +16,7 @@ data class VmessQRCode(
|
||||
var sni: String = "",
|
||||
var alpn: 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 insecure: Boolean? = null,
|
||||
var echConfigList: String? = null,
|
||||
var verifyPeerCertByName: String? = null,
|
||||
var pinnedCA256: String? = null,
|
||||
|
||||
var publicKey: String? = null,
|
||||
|
||||
@@ -8,6 +8,7 @@ import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.widget.Toast
|
||||
import com.v2ray.ang.AngApplication
|
||||
import com.v2ray.ang.enums.EConfigType
|
||||
import es.dmoral.toasty.Toasty
|
||||
import java.io.Serializable
|
||||
import java.net.URI
|
||||
@@ -200,4 +201,22 @@ fun String.matchesPattern(regex: Regex?, keyword: String?, ignoreCase: Boolean =
|
||||
}
|
||||
return regex?.containsMatchIn(this)
|
||||
?: this.contains(keyword, ignoreCase = ignoreCase)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the config type is a group type (PolicyGroup or ProxyChain).
|
||||
*
|
||||
* @return True if the config type is PolicyGroup or ProxyChain, false otherwise.
|
||||
*/
|
||||
fun EConfigType.isGroupType(): Boolean {
|
||||
return this == EConfigType.POLICYGROUP || this == EConfigType.PROXYCHAIN
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the config type is a complex type (Custom, PolicyGroup, or ProxyChain).
|
||||
*
|
||||
* @return True if the config type is Custom, PolicyGroup, or ProxyChain, false otherwise.
|
||||
*/
|
||||
fun EConfigType.isComplexType(): Boolean {
|
||||
return this == EConfigType.CUSTOM || this == EConfigType.POLICYGROUP || this == EConfigType.PROXYCHAIN
|
||||
}
|
||||
@@ -50,9 +50,8 @@ open class FmtBase {
|
||||
*
|
||||
* @param config the ProfileItem object to populate
|
||||
* @param queryParam the query parameters to use for populating the ProfileItem
|
||||
* @param allowInsecure whether to allow insecure connections
|
||||
*/
|
||||
fun getItemFormQuery(config: ProfileItem, queryParam: Map<String, String>, allowInsecure: Boolean) {
|
||||
fun getItemFormQuery(config: ProfileItem, queryParam: Map<String, String>) {
|
||||
config.network = queryParam["type"] ?: NetworkType.TCP.type
|
||||
config.headerType = queryParam["headerType"]
|
||||
config.host = queryParam["host"]
|
||||
@@ -79,12 +78,13 @@ open class FmtBase {
|
||||
config.insecure = when {
|
||||
allowInsecureKeys.any { queryParam[it] == "1" } -> true
|
||||
allowInsecureKeys.any { queryParam[it] == "0" } -> false
|
||||
else -> allowInsecure
|
||||
else -> false
|
||||
}
|
||||
config.sni = queryParam["sni"]
|
||||
config.fingerPrint = queryParam["fp"]
|
||||
config.alpn = queryParam["alpn"]
|
||||
config.echConfigList = queryParam["ech"]
|
||||
config.verifyPeerCertByName = queryParam["vcn"]
|
||||
config.pinnedCA256 = queryParam["pcs"]
|
||||
config.publicKey = queryParam["pbk"]
|
||||
config.shortId = queryParam["sid"]
|
||||
@@ -105,6 +105,7 @@ open class FmtBase {
|
||||
config.sni?.nullIfBlank()?.let { dicQuery["sni"] = it }
|
||||
config.alpn?.nullIfBlank()?.let { dicQuery["alpn"] = it }
|
||||
config.echConfigList?.nullIfBlank()?.let { dicQuery["ech"] = it }
|
||||
config.verifyPeerCertByName?.nullIfBlank()?.let { dicQuery["vcn"] = it }
|
||||
config.pinnedCA256?.nullIfBlank()?.let { dicQuery["pcs"] = it }
|
||||
config.fingerPrint?.nullIfBlank()?.let { dicQuery["fp"] = it }
|
||||
config.publicKey?.nullIfBlank()?.let { dicQuery["pbk"] = it }
|
||||
|
||||
@@ -19,7 +19,6 @@ object Hysteria2Fmt : FmtBase() {
|
||||
* @return the parsed ProfileItem object, or null if parsing fails
|
||||
*/
|
||||
fun parse(str: String): ProfileItem {
|
||||
var allowInsecure = MmkvManager.decodeSettingsBool(AppConfig.PREF_ALLOW_INSECURE, false)
|
||||
val config = ProfileItem.create(EConfigType.HYSTERIA2)
|
||||
|
||||
val uri = URI(Utils.fixIllegalUrl(str))
|
||||
@@ -33,7 +32,7 @@ object Hysteria2Fmt : FmtBase() {
|
||||
if (!uri.rawQuery.isNullOrEmpty()) {
|
||||
val queryParam = getQueryParam(uri)
|
||||
|
||||
getItemFormQuery(config, queryParam, allowInsecure)
|
||||
getItemFormQuery(config, queryParam)
|
||||
|
||||
config.security = queryParam["security"] ?: AppConfig.TLS
|
||||
config.obfsPassword = queryParam["obfs-password"]
|
||||
|
||||
@@ -17,7 +17,6 @@ object TrojanFmt : FmtBase() {
|
||||
* @return the parsed ProfileItem object, or null if parsing fails
|
||||
*/
|
||||
fun parse(str: String): ProfileItem {
|
||||
var allowInsecure = MmkvManager.decodeSettingsBool(AppConfig.PREF_ALLOW_INSECURE, false)
|
||||
val config = ProfileItem.create(EConfigType.TROJAN)
|
||||
|
||||
val uri = URI(Utils.fixIllegalUrl(str))
|
||||
@@ -29,11 +28,11 @@ object TrojanFmt : FmtBase() {
|
||||
if (uri.rawQuery.isNullOrEmpty()) {
|
||||
config.network = NetworkType.TCP.type
|
||||
config.security = AppConfig.TLS
|
||||
config.insecure = allowInsecure
|
||||
config.insecure = false
|
||||
} else {
|
||||
val queryParam = getQueryParam(uri)
|
||||
|
||||
getItemFormQuery(config, queryParam, allowInsecure)
|
||||
getItemFormQuery(config, queryParam)
|
||||
config.security = queryParam["security"] ?: AppConfig.TLS
|
||||
}
|
||||
|
||||
|
||||
@@ -17,7 +17,6 @@ object VlessFmt : FmtBase() {
|
||||
* @return the parsed ProfileItem object, or null if parsing fails
|
||||
*/
|
||||
fun parse(str: String): ProfileItem? {
|
||||
var allowInsecure = MmkvManager.decodeSettingsBool(AppConfig.PREF_ALLOW_INSECURE, false)
|
||||
val config = ProfileItem.create(EConfigType.VLESS)
|
||||
|
||||
val uri = URI(Utils.fixIllegalUrl(str))
|
||||
@@ -30,7 +29,7 @@ object VlessFmt : FmtBase() {
|
||||
config.password = uri.userInfo
|
||||
config.method = queryParam["encryption"] ?: "none"
|
||||
|
||||
getItemFormQuery(config, queryParam, allowInsecure)
|
||||
getItemFormQuery(config, queryParam)
|
||||
|
||||
return config
|
||||
}
|
||||
|
||||
@@ -26,7 +26,6 @@ object VmessFmt : FmtBase() {
|
||||
return parseVmessStd(str)
|
||||
}
|
||||
|
||||
val allowInsecure = MmkvManager.decodeSettingsBool(AppConfig.PREF_ALLOW_INSECURE, false)
|
||||
val config = ProfileItem.create(EConfigType.VMESS)
|
||||
|
||||
var result = str.replace(EConfigType.VMESS.protocolScheme, "")
|
||||
@@ -87,8 +86,11 @@ object VmessFmt : FmtBase() {
|
||||
config.insecure = when (vmessQRCode.insecure) {
|
||||
"1" -> true
|
||||
"0" -> false
|
||||
else -> allowInsecure
|
||||
else -> false
|
||||
}
|
||||
config.verifyPeerCertByName = vmessQRCode.vcn
|
||||
config.pinnedCA256 = vmessQRCode.pcs
|
||||
|
||||
return config
|
||||
}
|
||||
|
||||
@@ -142,6 +144,8 @@ object VmessFmt : FmtBase() {
|
||||
false -> "0"
|
||||
else -> ""
|
||||
}
|
||||
vmessQRCode.vcn = config.verifyPeerCertByName.orEmpty()
|
||||
vmessQRCode.pcs = config.pinnedCA256.orEmpty()
|
||||
|
||||
val json = JsonUtil.toJson(vmessQRCode)
|
||||
return Utils.encode(json)
|
||||
@@ -154,7 +158,6 @@ object VmessFmt : FmtBase() {
|
||||
* @return the parsed ProfileItem object, or null if parsing fails
|
||||
*/
|
||||
fun parseVmessStd(str: String): ProfileItem? {
|
||||
val allowInsecure = MmkvManager.decodeSettingsBool(AppConfig.PREF_ALLOW_INSECURE, false)
|
||||
val config = ProfileItem.create(EConfigType.VMESS)
|
||||
|
||||
val uri = URI(Utils.fixIllegalUrl(str))
|
||||
@@ -167,7 +170,7 @@ object VmessFmt : FmtBase() {
|
||||
config.password = uri.userInfo
|
||||
config.method = AppConfig.DEFAULT_SECURITY
|
||||
|
||||
getItemFormQuery(config, queryParam, allowInsecure)
|
||||
getItemFormQuery(config, queryParam)
|
||||
|
||||
return config
|
||||
}
|
||||
|
||||
@@ -234,15 +234,8 @@ object AngConfigManager {
|
||||
if (servers == null) {
|
||||
return 0
|
||||
}
|
||||
// Find the currently selected server that matches the subscription ID
|
||||
val removedSelected = if (subid.isNotBlank() && !append) {
|
||||
MmkvManager.getSelectServer()
|
||||
.takeIf { it?.isNotBlank() == true }
|
||||
?.let { MmkvManager.decodeServerConfig(it) }
|
||||
?.takeIf { it.subscriptionId == subid }
|
||||
} else {
|
||||
null
|
||||
}
|
||||
// Find the currently selected server that belongs to the same subscription before replacement.
|
||||
val removedSelected = getRemovedSelectedProfile(subid, append)
|
||||
|
||||
val subItem = MmkvManager.decodeSubscription(subid)
|
||||
|
||||
@@ -288,7 +281,6 @@ object AngConfigManager {
|
||||
|
||||
// Read serverList once
|
||||
val serverList = MmkvManager.decodeServerList(subid)
|
||||
var needSetSelected = MmkvManager.getSelectServer().isNullOrBlank()
|
||||
|
||||
configs.forEach { config ->
|
||||
val key = Utils.getUuid()
|
||||
@@ -297,10 +289,6 @@ object AngConfigManager {
|
||||
|
||||
if (!serverList.contains(key)) {
|
||||
serverList.add(0, key)
|
||||
if (needSetSelected) {
|
||||
MmkvManager.setSelectServer(key)
|
||||
needSetSelected = false
|
||||
}
|
||||
}
|
||||
keyToProfile[key] = config
|
||||
}
|
||||
@@ -323,7 +311,8 @@ object AngConfigManager {
|
||||
* @return Matched key or null
|
||||
*/
|
||||
private fun findMatchedProfileKey(keyToProfile: Map<String, ProfileItem>, target: ProfileItem?): String? {
|
||||
if (keyToProfile.isEmpty() || target == null) return null
|
||||
if (keyToProfile.isEmpty()) return null
|
||||
if (target == null) return null
|
||||
|
||||
// Level 0: Full match (remarks + server + port + password)
|
||||
if (target.remarks.isNotBlank()) {
|
||||
@@ -360,7 +349,20 @@ object AngConfigManager {
|
||||
isSameText(saved.server, target.server)
|
||||
}?.key?.let { return it }
|
||||
|
||||
return null
|
||||
// If old selected node cannot be matched, fall back to the first imported config.
|
||||
return keyToProfile.keys.firstOrNull()
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the currently selected profile if it belongs to the target subscription and will be replaced.
|
||||
*/
|
||||
private fun getRemovedSelectedProfile(subid: String, append: Boolean): ProfileItem? {
|
||||
if (subid.isBlank() || append) return null
|
||||
|
||||
return MmkvManager.getSelectServer()
|
||||
.takeIf { it?.isNotBlank() == true }
|
||||
?.let { MmkvManager.decodeServerConfig(it) }
|
||||
?.takeIf { it.subscriptionId == subid }
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -396,18 +398,25 @@ object AngConfigManager {
|
||||
JsonUtil.fromJson(server, Array<Any>::class.java) ?: arrayOf()
|
||||
|
||||
if (serverList.isNotEmpty()) {
|
||||
val removedSelected = getRemovedSelectedProfile(subid, append)
|
||||
if (!append) {
|
||||
MmkvManager.removeServerViaSubid(subid)
|
||||
}
|
||||
var count = 0
|
||||
val keyToProfile = mutableMapOf<String, ProfileItem>()
|
||||
for (srv in serverList.reversed()) {
|
||||
val config = CustomFmt.parse(JsonUtil.toJson(srv)) ?: continue
|
||||
config.subscriptionId = subid
|
||||
config.description = generateDescription(config)
|
||||
val key = MmkvManager.encodeServerConfig("", config)
|
||||
MmkvManager.encodeServerRaw(key, JsonUtil.toJsonPretty(srv) ?: "")
|
||||
keyToProfile[key] = config
|
||||
count += 1
|
||||
}
|
||||
if (count > 0) {
|
||||
val matchKey = findMatchedProfileKey(keyToProfile, removedSelected)
|
||||
matchKey?.let { MmkvManager.setSelectServer(it) }
|
||||
}
|
||||
return count
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
|
||||
@@ -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) }
|
||||
}
|
||||
}
|
||||
@@ -286,9 +286,13 @@ object MmkvManager {
|
||||
*/
|
||||
fun removeAllServer(): Int {
|
||||
val count = profileFullStorage.allKeys()?.count() ?: 0
|
||||
mainStorage.clearAll()
|
||||
profileFullStorage.clearAll()
|
||||
serverAffStorage.clearAll()
|
||||
serverRawStorage.clearAll()
|
||||
|
||||
decodeSubscriptions().forEach { sub ->
|
||||
encodeServerList(mutableListOf(), sub.guid)
|
||||
}
|
||||
return count
|
||||
}
|
||||
|
||||
|
||||
@@ -105,7 +105,7 @@ object NotificationManager {
|
||||
stopV2RayPendingIntent
|
||||
)
|
||||
.addAction(
|
||||
R.drawable.ic_delete_24dp,
|
||||
R.drawable.ic_restore_24dp,
|
||||
service.getString(R.string.title_service_restart),
|
||||
restartV2RayPendingIntent
|
||||
)
|
||||
|
||||
@@ -6,8 +6,6 @@ import com.v2ray.ang.dto.UrlContentRequest
|
||||
import com.v2ray.ang.util.HttpUtil
|
||||
import com.v2ray.ang.util.JsonUtil
|
||||
import com.v2ray.ang.util.LogUtil
|
||||
import kotlinx.coroutines.currentCoroutineContext
|
||||
import kotlinx.coroutines.isActive
|
||||
import java.io.IOException
|
||||
import java.net.InetSocketAddress
|
||||
import java.net.Socket
|
||||
@@ -15,29 +13,6 @@ import java.net.UnknownHostException
|
||||
|
||||
object SpeedtestManager {
|
||||
|
||||
private val tcpTestingSockets = ArrayList<Socket?>()
|
||||
|
||||
/**
|
||||
* Measures the TCP connection time to a given URL and port.
|
||||
*
|
||||
* @param url The URL to connect to.
|
||||
* @param port The port to connect to.
|
||||
* @return The connection time in milliseconds, or -1 if the connection failed.
|
||||
*/
|
||||
suspend fun tcping(url: String, port: Int): Long {
|
||||
var time = -1L
|
||||
for (k in 0 until 2) {
|
||||
val one = socketConnectTime(url, port)
|
||||
if (!currentCoroutineContext().isActive) {
|
||||
break
|
||||
}
|
||||
if (one != -1L && (time == -1L || one < time)) {
|
||||
time = one
|
||||
}
|
||||
}
|
||||
return time
|
||||
}
|
||||
|
||||
/**
|
||||
* Measures the time taken to establish a TCP connection to a given URL and port.
|
||||
*
|
||||
@@ -45,42 +20,34 @@ object SpeedtestManager {
|
||||
* @param port The port to connect to.
|
||||
* @return The connection time in milliseconds, or -1 if the connection failed.
|
||||
*/
|
||||
fun socketConnectTime(url: String, port: Int): Long {
|
||||
fun socketConnectTime(url: String, port: Int, timeoutMs: Int = 1500): Long {
|
||||
var socket: Socket? = null
|
||||
val start = System.currentTimeMillis()
|
||||
|
||||
try {
|
||||
val socket = Socket()
|
||||
synchronized(this) {
|
||||
tcpTestingSockets.add(socket)
|
||||
}
|
||||
val start = System.currentTimeMillis()
|
||||
socket.connect(InetSocketAddress(url, port), 3000)
|
||||
val time = System.currentTimeMillis() - start
|
||||
synchronized(this) {
|
||||
tcpTestingSockets.remove(socket)
|
||||
}
|
||||
socket.close()
|
||||
return time
|
||||
socket = Socket()
|
||||
socket.connect(InetSocketAddress(url, port), timeoutMs)
|
||||
|
||||
return System.currentTimeMillis() - start
|
||||
} catch (e: UnknownHostException) {
|
||||
LogUtil.e(AppConfig.TAG, "Unknown host: $url", e)
|
||||
} catch (e: IOException) {
|
||||
LogUtil.e(AppConfig.TAG, "socketConnectTime IOException: $e")
|
||||
LogUtil.e(AppConfig.TAG, "socketConnectTime IOException: ${e.message}")
|
||||
} catch (e: Exception) {
|
||||
LogUtil.e(AppConfig.TAG, "Failed to establish socket connection to $url:$port", e)
|
||||
} finally {
|
||||
socket?.let { s ->
|
||||
try {
|
||||
if (!s.isClosed) {
|
||||
s.close()
|
||||
}
|
||||
} catch (closeEx: IOException) {
|
||||
}
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes all TCP sockets that are currently being tested.
|
||||
*/
|
||||
fun closeAllTcpSockets() {
|
||||
synchronized(this) {
|
||||
tcpTestingSockets.forEach {
|
||||
it?.close()
|
||||
}
|
||||
tcpTestingSockets.clear()
|
||||
}
|
||||
}
|
||||
|
||||
fun getRemoteIPInfo(): String? {
|
||||
val url = MmkvManager.decodeSettingsString(AppConfig.PREF_IP_API_URL)
|
||||
.takeIf { !it.isNullOrBlank() } ?: AppConfig.IP_API_URL
|
||||
|
||||
@@ -4,7 +4,12 @@ import android.content.Context
|
||||
import com.v2ray.ang.core.CoreConfigManager
|
||||
import com.v2ray.ang.core.CoreNativeManager
|
||||
import com.v2ray.ang.dto.RealPingEvent
|
||||
import com.v2ray.ang.enums.EConfigType
|
||||
import com.v2ray.ang.extension.isComplexType
|
||||
import com.v2ray.ang.extension.isNotNullEmpty
|
||||
import com.v2ray.ang.handler.MmkvManager
|
||||
import com.v2ray.ang.handler.SettingsManager
|
||||
import com.v2ray.ang.handler.SpeedtestManager
|
||||
import kotlinx.coroutines.CancellationException
|
||||
import kotlinx.coroutines.CoroutineName
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
@@ -76,6 +81,21 @@ class RealPingWorkerService(
|
||||
|
||||
private fun startRealPing(guid: String): Long {
|
||||
val retFailure = -1L
|
||||
|
||||
val config = MmkvManager.decodeServerConfig(guid) ?: return retFailure
|
||||
if (!config.configType.isComplexType()
|
||||
&& config.configType != EConfigType.HYSTERIA2
|
||||
&& config.server.isNotNullEmpty()
|
||||
&& config.serverPort?.toIntOrNull() != null
|
||||
) {
|
||||
val url = config.server.orEmpty()
|
||||
val port = config.serverPort.orEmpty().toInt()
|
||||
val tcpTime = SpeedtestManager.socketConnectTime(url, port, 1000)
|
||||
if (tcpTime <= -1L) {
|
||||
return retFailure
|
||||
}
|
||||
}
|
||||
|
||||
val configResult = CoreConfigManager.getV2rayConfig4Speedtest(context, guid)
|
||||
if (!configResult.status) {
|
||||
return retFailure
|
||||
|
||||
@@ -19,6 +19,7 @@ import com.v2ray.ang.databinding.FragmentGroupServerBinding
|
||||
import com.v2ray.ang.databinding.ItemQrcodeBinding
|
||||
import com.v2ray.ang.dto.entities.ProfileItem
|
||||
import com.v2ray.ang.enums.EConfigType
|
||||
import com.v2ray.ang.extension.isComplexType
|
||||
import com.v2ray.ang.extension.toast
|
||||
import com.v2ray.ang.extension.toastError
|
||||
import com.v2ray.ang.extension.toastSuccess
|
||||
@@ -225,6 +226,7 @@ class GroupServerFragment : BaseFragment<FragmentGroupServerBinding>(),
|
||||
private fun removeServerSub(guid: String, position: Int) {
|
||||
mainViewModel.removeServer(guid)
|
||||
adapter.removeServerSub(guid, position)
|
||||
ownerActivity.refreshGroupTabTitles()
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -269,9 +271,7 @@ class GroupServerFragment : BaseFragment<FragmentGroupServerBinding>(),
|
||||
}
|
||||
|
||||
override fun onShare(guid: String, profile: ProfileItem, position: Int, more: Boolean) {
|
||||
val isCustom = profile.configType == EConfigType.CUSTOM
|
||||
|| profile.configType == EConfigType.POLICYGROUP
|
||||
|| profile.configType == EConfigType.PROXYCHAIN
|
||||
val isCustom = profile.configType.isComplexType()
|
||||
|
||||
val (shareOptions, skip) = if (more) {
|
||||
val options = if (isCustom) share_method_more.asList().takeLast(3) else share_method_more.asList()
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package com.v2ray.ang.ui
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.ClipData
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
@@ -18,6 +20,11 @@ import com.v2ray.ang.viewmodel.LogcatViewModel
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import androidx.core.content.FileProvider
|
||||
import java.io.File
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Date
|
||||
import java.util.Locale
|
||||
|
||||
class LogcatActivity : BaseActivity(), SwipeRefreshLayout.OnRefreshListener {
|
||||
private val binding by lazy { ActivityLogcatBinding.inflate(layoutInflater) }
|
||||
@@ -45,6 +52,55 @@ class LogcatActivity : BaseActivity(), SwipeRefreshLayout.OnRefreshListener {
|
||||
return true
|
||||
}
|
||||
|
||||
private fun shareLogcat() {
|
||||
lifecycleScope.launch(Dispatchers.IO) {
|
||||
val logText = viewModel.getAll().joinToString("\n")
|
||||
|
||||
val result = try {
|
||||
val shareDir = File(cacheDir, "shared_logs").apply {
|
||||
mkdirs()
|
||||
}
|
||||
|
||||
shareDir.listFiles()?.forEach { it.delete() }
|
||||
|
||||
val timestamp = SimpleDateFormat("yyyy-MM-dd_HH-mm-ss", Locale.US).format(Date())
|
||||
val logFile = File(shareDir, "v2rayNG_logcat_$timestamp.txt")
|
||||
logFile.writeText(logText, Charsets.UTF_8)
|
||||
|
||||
val uri = FileProvider.getUriForFile(
|
||||
this@LogcatActivity,
|
||||
"${packageName}.cache",
|
||||
logFile
|
||||
)
|
||||
|
||||
uri to logFile.name
|
||||
} catch (e: Exception) {
|
||||
withContext(Dispatchers.Main) {
|
||||
toast(e.localizedMessage ?: e.toString())
|
||||
}
|
||||
return@launch
|
||||
}
|
||||
|
||||
withContext(Dispatchers.Main) {
|
||||
val shareIntent = Intent(Intent.ACTION_SEND).apply {
|
||||
type = "text/plain"
|
||||
putExtra(Intent.EXTRA_STREAM, result.first)
|
||||
putExtra(Intent.EXTRA_SUBJECT, result.second)
|
||||
putExtra(Intent.EXTRA_TITLE, result.second)
|
||||
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||
clipData = ClipData.newUri(contentResolver, result.second, result.first)
|
||||
}
|
||||
|
||||
startActivity(
|
||||
Intent.createChooser(
|
||||
shareIntent,
|
||||
getString(R.string.logcat_share)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreateOptionsMenu(menu: Menu): Boolean {
|
||||
menuInflater.inflate(R.menu.menu_logcat, menu)
|
||||
|
||||
@@ -78,6 +134,11 @@ class LogcatActivity : BaseActivity(), SwipeRefreshLayout.OnRefreshListener {
|
||||
true
|
||||
}
|
||||
|
||||
R.id.share_all -> {
|
||||
shareLogcat()
|
||||
true
|
||||
}
|
||||
|
||||
R.id.clear_all -> {
|
||||
lifecycleScope.launch(Dispatchers.IO) {
|
||||
viewModel.clearLogcat()
|
||||
|
||||
@@ -76,23 +76,7 @@ class MainActivity : HelperBaseActivity(), NavigationView.OnNavigationItemSelect
|
||||
binding.viewPager.isUserInputEnabled = true
|
||||
|
||||
// setup navigation drawer
|
||||
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
|
||||
}
|
||||
}
|
||||
})
|
||||
setupNavigationDrawer()
|
||||
|
||||
binding.fab.setOnClickListener { handleFabAction() }
|
||||
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() {
|
||||
mainViewModel.updateTestResultAction.observe(this) { setTestState(it) }
|
||||
mainViewModel.isRunning.observe(this) { isRunning ->
|
||||
@@ -131,6 +140,26 @@ class MainActivity : HelperBaseActivity(), NavigationView.OnNavigationItemSelect
|
||||
binding.viewPager.setCurrentItem(targetIndex, false)
|
||||
|
||||
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() {
|
||||
@@ -304,12 +333,6 @@ class MainActivity : HelperBaseActivity(), NavigationView.OnNavigationItemSelect
|
||||
true
|
||||
}
|
||||
|
||||
R.id.ping_all -> {
|
||||
toast(getString(R.string.connection_test_testing_count, mainViewModel.serversCache.count()))
|
||||
mainViewModel.testAllTcping()
|
||||
true
|
||||
}
|
||||
|
||||
R.id.real_ping_all -> {
|
||||
toast(getString(R.string.connection_test_testing_count, mainViewModel.serversCache.count()))
|
||||
mainViewModel.testAllRealPing()
|
||||
@@ -416,6 +439,7 @@ class MainActivity : HelperBaseActivity(), NavigationView.OnNavigationItemSelect
|
||||
count > 0 -> {
|
||||
toast(getString(R.string.title_import_config_count, count))
|
||||
mainViewModel.reloadServerList()
|
||||
refreshGroupTabTitles()
|
||||
}
|
||||
|
||||
countSub > 0 -> setupGroupTab()
|
||||
@@ -471,6 +495,7 @@ class MainActivity : HelperBaseActivity(), NavigationView.OnNavigationItemSelect
|
||||
}
|
||||
if (result.configCount > 0) {
|
||||
mainViewModel.reloadServerList()
|
||||
refreshGroupTabTitles()
|
||||
}
|
||||
hideLoading()
|
||||
}
|
||||
@@ -500,6 +525,7 @@ class MainActivity : HelperBaseActivity(), NavigationView.OnNavigationItemSelect
|
||||
val ret = mainViewModel.removeAllServer()
|
||||
launch(Dispatchers.Main) {
|
||||
mainViewModel.reloadServerList()
|
||||
refreshGroupTabTitles()
|
||||
toast(getString(R.string.title_del_config_count, ret))
|
||||
hideLoading()
|
||||
}
|
||||
@@ -519,6 +545,7 @@ class MainActivity : HelperBaseActivity(), NavigationView.OnNavigationItemSelect
|
||||
val ret = mainViewModel.removeDuplicateServer()
|
||||
launch(Dispatchers.Main) {
|
||||
mainViewModel.reloadServerList()
|
||||
refreshGroupTabTitles()
|
||||
toast(getString(R.string.title_del_duplicate_config_count, ret))
|
||||
hideLoading()
|
||||
}
|
||||
@@ -538,6 +565,7 @@ class MainActivity : HelperBaseActivity(), NavigationView.OnNavigationItemSelect
|
||||
val ret = mainViewModel.removeInvalidServer()
|
||||
launch(Dispatchers.Main) {
|
||||
mainViewModel.reloadServerList()
|
||||
refreshGroupTabTitles()
|
||||
toast(getString(R.string.title_del_config_count, ret))
|
||||
hideLoading()
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
|
||||
package com.v2ray.ang.ui
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
@@ -14,6 +15,7 @@ import com.v2ray.ang.databinding.ItemRecyclerFooterBinding
|
||||
import com.v2ray.ang.databinding.ItemRecyclerMainBinding
|
||||
import com.v2ray.ang.dto.entities.ProfileItem
|
||||
import com.v2ray.ang.dto.entities.ServersCache
|
||||
import com.v2ray.ang.extension.isComplexType
|
||||
import com.v2ray.ang.extension.nullIfBlank
|
||||
import com.v2ray.ang.handler.AngConfigManager
|
||||
import com.v2ray.ang.handler.MmkvManager
|
||||
@@ -58,7 +60,7 @@ class MainRecyclerAdapter(
|
||||
//Name address
|
||||
holder.itemMainBinding.tvName.text = profile.remarks
|
||||
holder.itemMainBinding.tvStatistics.text = getAddress(profile)
|
||||
holder.itemMainBinding.tvType.text = profile.configType.name
|
||||
holder.itemMainBinding.tvType.text = getProtocolDescription(profile)
|
||||
|
||||
//TestResult
|
||||
val aff = MmkvManager.decodeServerAffiliationInfo(guid)
|
||||
@@ -140,6 +142,35 @@ class MainRecyclerAdapter(
|
||||
return subRemarks?.toString() ?: ""
|
||||
}
|
||||
|
||||
private fun getProtocolDescription(profile: ProfileItem): String {
|
||||
if (profile.configType.isComplexType()) {
|
||||
return profile.configType.name
|
||||
}
|
||||
|
||||
val parts = mutableListOf<String>()
|
||||
parts.add(profile.configType.name)
|
||||
|
||||
// Transport: hide tcp or blank
|
||||
profile.network?.let { net ->
|
||||
if (net.isNotBlank() && !net.equals("tcp", ignoreCase = true)) {
|
||||
parts.add(net)
|
||||
}
|
||||
}
|
||||
|
||||
// Security: hide blank or tls
|
||||
profile.security?.let { sec ->
|
||||
if (sec.isNotBlank()) {
|
||||
if (profile.insecure == true && sec.equals("tls", ignoreCase = true)) {
|
||||
parts.add("$sec insecure") // TODO
|
||||
} else {
|
||||
parts.add(sec)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return parts.joinToString(" / ")
|
||||
}
|
||||
|
||||
fun removeServerSub(guid: String, position: Int) {
|
||||
val idx = data.indexOfFirst { it.guid == guid }
|
||||
if (idx >= 0) {
|
||||
@@ -203,4 +234,4 @@ class MainRecyclerAdapter(
|
||||
|
||||
override fun onItemDismiss(position: Int) {
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,14 +7,15 @@ import android.view.MenuItem
|
||||
import android.view.View
|
||||
import android.widget.AdapterView
|
||||
import android.widget.ArrayAdapter
|
||||
import android.widget.Button
|
||||
import android.widget.EditText
|
||||
import android.widget.LinearLayout
|
||||
import android.widget.Spinner
|
||||
import android.widget.TextView
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.v2ray.ang.AppConfig
|
||||
import com.v2ray.ang.AppConfig.DEFAULT_PORT
|
||||
import com.v2ray.ang.AppConfig.PREF_ALLOW_INSECURE
|
||||
import com.v2ray.ang.AppConfig.REALITY
|
||||
import com.v2ray.ang.AppConfig.TLS
|
||||
import com.v2ray.ang.AppConfig.WIREGUARD_LOCAL_ADDRESS_V4
|
||||
@@ -28,10 +29,14 @@ import com.v2ray.ang.extension.nullIfBlank
|
||||
import com.v2ray.ang.extension.toast
|
||||
import com.v2ray.ang.extension.toastSuccess
|
||||
import com.v2ray.ang.handler.AngConfigManager
|
||||
import com.v2ray.ang.handler.CertificateFingerprintManager
|
||||
import com.v2ray.ang.handler.MmkvManager
|
||||
import com.v2ray.ang.handler.SettingsChangeManager
|
||||
import com.v2ray.ang.util.JsonUtil
|
||||
import com.v2ray.ang.util.Utils
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
class ServerActivity : BaseActivity() {
|
||||
|
||||
@@ -141,7 +146,10 @@ class ServerActivity : BaseActivity() {
|
||||
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 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 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 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) }
|
||||
@@ -296,6 +304,7 @@ class ServerActivity : BaseActivity() {
|
||||
container_spider_x,
|
||||
container_mldsa65_verify,
|
||||
container_ech_config_list,
|
||||
container_verify_peer_cert_by_name,
|
||||
container_pinned_ca256
|
||||
).forEach { it?.visibility = View.GONE }
|
||||
}
|
||||
@@ -308,6 +317,7 @@ class ServerActivity : BaseActivity() {
|
||||
container_alpn,
|
||||
container_allow_insecure,
|
||||
container_ech_config_list,
|
||||
container_verify_peer_cert_by_name,
|
||||
container_pinned_ca256
|
||||
).forEach { it?.visibility = View.VISIBLE }
|
||||
listOf(
|
||||
@@ -328,6 +338,7 @@ class ServerActivity : BaseActivity() {
|
||||
container_alpn,
|
||||
container_allow_insecure,
|
||||
container_ech_config_list,
|
||||
container_verify_peer_cert_by_name,
|
||||
container_pinned_ca256
|
||||
).forEach { it?.visibility = View.GONE }
|
||||
listOf(
|
||||
@@ -344,6 +355,9 @@ class ServerActivity : BaseActivity() {
|
||||
// do nothing
|
||||
}
|
||||
}
|
||||
btn_pinned_ca256_action?.setOnClickListener {
|
||||
fetchPinnedCA256ForCurrentConfig()
|
||||
}
|
||||
if (config != null) {
|
||||
bindingServer(config)
|
||||
} else {
|
||||
@@ -411,6 +425,7 @@ class ServerActivity : BaseActivity() {
|
||||
sp_allow_insecure?.setSelection(allowinsecure)
|
||||
}
|
||||
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)
|
||||
} else if (config.security == REALITY) {
|
||||
et_public_key?.text = Utils.getEditable(config.publicKey.orEmpty())
|
||||
@@ -614,11 +629,12 @@ class ServerActivity : BaseActivity() {
|
||||
val spiderX = et_spider_x?.text?.toString()
|
||||
val mldsa65Verify = et_mldsa65_verify?.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 allowInsecure =
|
||||
if (allowInsecureField == null || allowinsecures[allowInsecureField].isBlank()) {
|
||||
MmkvManager.decodeSettingsBool(PREF_ALLOW_INSECURE)
|
||||
false
|
||||
} else {
|
||||
allowinsecures[allowInsecureField].toBoolean()
|
||||
}
|
||||
@@ -633,9 +649,51 @@ class ServerActivity : BaseActivity() {
|
||||
config.spiderX = spiderX
|
||||
config.mldsa65Verify = mldsa65Verify
|
||||
config.echConfigList = echConfigList
|
||||
config.verifyPeerCertByName = verifyPeerCertByName
|
||||
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> {
|
||||
return when (network) {
|
||||
NetworkType.TCP.type -> {
|
||||
|
||||
@@ -13,6 +13,7 @@ import com.v2ray.ang.contracts.BaseAdapterListener
|
||||
import com.v2ray.ang.databinding.ActivityServerProxyChainBinding
|
||||
import com.v2ray.ang.dto.entities.ProfileItem
|
||||
import com.v2ray.ang.enums.EConfigType
|
||||
import com.v2ray.ang.extension.isComplexType
|
||||
import com.v2ray.ang.extension.toast
|
||||
import com.v2ray.ang.extension.toastSuccess
|
||||
import com.v2ray.ang.handler.MmkvManager
|
||||
@@ -109,10 +110,7 @@ class ServerProxyChainActivity : BaseActivity() {
|
||||
|
||||
val invalidMembers = chainMembers.filter { member ->
|
||||
val profile = SettingsManager.getServerViaRemarks(member)
|
||||
profile == null
|
||||
|| profile.configType == EConfigType.CUSTOM
|
||||
|| profile.configType == EConfigType.POLICYGROUP
|
||||
|| profile.configType == EConfigType.PROXYCHAIN
|
||||
profile == null || profile.configType.isComplexType()
|
||||
}
|
||||
if (invalidMembers.isNotEmpty()) {
|
||||
toast(getString(R.string.server_proxy_chain_members_invalid, invalidMembers.joinToString(", ")))
|
||||
|
||||
@@ -18,20 +18,17 @@ import com.v2ray.ang.dto.SubscriptionUpdateResult
|
||||
import com.v2ray.ang.dto.TestServiceMessage
|
||||
import com.v2ray.ang.dto.entities.ServersCache
|
||||
import com.v2ray.ang.dto.entities.SubscriptionCache
|
||||
import com.v2ray.ang.extension.isComplexType
|
||||
import com.v2ray.ang.extension.matchesPattern
|
||||
import com.v2ray.ang.extension.toastError
|
||||
import com.v2ray.ang.extension.toastSuccess
|
||||
import com.v2ray.ang.handler.AngConfigManager
|
||||
import com.v2ray.ang.handler.MmkvManager
|
||||
import com.v2ray.ang.handler.SettingsManager
|
||||
import com.v2ray.ang.handler.SpeedtestManager
|
||||
import com.v2ray.ang.util.LogUtil
|
||||
import com.v2ray.ang.util.MessageUtil
|
||||
import com.v2ray.ang.util.Utils
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.cancelChildren
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.util.Collections
|
||||
@@ -45,7 +42,6 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
|
||||
val isRunning by lazy { MutableLiveData<Boolean>() }
|
||||
val updateListAction by lazy { MutableLiveData<Int>() }
|
||||
val updateTestResultAction by lazy { MutableLiveData<String>() }
|
||||
private val tcpingTestScope by lazy { CoroutineScope(Dispatchers.IO) }
|
||||
|
||||
/**
|
||||
* Refer to the official documentation for [registerReceiver](https://developer.android.com/reference/androidx/core/content/ContextCompat#registerReceiver(android.content.Context,android.content.BroadcastReceiver,android.content.IntentFilter,int):
|
||||
@@ -63,8 +59,6 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
|
||||
*/
|
||||
override fun onCleared() {
|
||||
getApplication<AngApplication>().unregisterReceiver(mMsgReceiver)
|
||||
tcpingTestScope.coroutineContext[Job]?.cancelChildren()
|
||||
SpeedtestManager.closeAllTcpSockets()
|
||||
LogUtil.i(AppConfig.TAG, "Main ViewModel is cleared")
|
||||
super.onCleared()
|
||||
}
|
||||
@@ -177,32 +171,6 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
|
||||
return ret
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the TCP ping for all servers.
|
||||
*/
|
||||
fun testAllTcping() {
|
||||
tcpingTestScope.coroutineContext[Job]?.cancelChildren()
|
||||
SpeedtestManager.closeAllTcpSockets()
|
||||
MmkvManager.clearAllTestDelayResults(serversCache.map { it.guid }.toList())
|
||||
|
||||
val serversCopy = serversCache.toList()
|
||||
for (item in serversCopy) {
|
||||
item.profile.let { outbound ->
|
||||
val serverAddress = outbound.server
|
||||
val serverPort = outbound.serverPort
|
||||
if (serverAddress != null && serverPort != null) {
|
||||
tcpingTestScope.launch {
|
||||
val testResult = SpeedtestManager.tcping(serverAddress, serverPort.toInt())
|
||||
launch(Dispatchers.Main) {
|
||||
MmkvManager.encodeServerTestDelayMillis(item.guid, testResult)
|
||||
updateListAction.value = getPosition(item.guid)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the real ping for all servers.
|
||||
*/
|
||||
@@ -296,16 +264,28 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
|
||||
|
||||
/**
|
||||
* Removes duplicate servers.
|
||||
* Excludes servers with complex types (Custom, PolicyGroup, or ProxyChain) from duplicate comparison.
|
||||
* @return The number of removed servers.
|
||||
*/
|
||||
fun removeDuplicateServer(): Int {
|
||||
val serversCacheCopy = serversCache.toList().toMutableList()
|
||||
val deleteServer = mutableListOf<String>()
|
||||
|
||||
serversCacheCopy.forEachIndexed { index, sc ->
|
||||
val profile = sc.profile
|
||||
// Skip if this profile has a complex config type
|
||||
if (profile.configType.isComplexType()) {
|
||||
return@forEachIndexed
|
||||
}
|
||||
|
||||
serversCacheCopy.forEachIndexed { index2, sc2 ->
|
||||
if (index2 > index) {
|
||||
val profile2 = sc2.profile
|
||||
// Skip if the second profile has a complex config type
|
||||
if (profile2.configType.isComplexType()) {
|
||||
return@forEachIndexed
|
||||
}
|
||||
|
||||
if (profile == profile2 && !deleteServer.contains(sc2.guid)) {
|
||||
deleteServer.add(sc2.guid)
|
||||
}
|
||||
|
||||
@@ -114,7 +114,7 @@
|
||||
android:layout_marginTop="@dimen/padding_spacing_dp8"
|
||||
android:layout_marginBottom="@dimen/padding_spacing_dp8"
|
||||
android:entries="@array/allowinsecures"
|
||||
android:nextFocusDown="@+id/et_ech_config_list"/>
|
||||
android:nextFocusDown="@+id/et_verify_peer_cert_by_name"/>
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
@@ -138,6 +138,27 @@
|
||||
|
||||
</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
|
||||
android:id="@+id/lay_pinned_ca256"
|
||||
android:layout_width="match_parent"
|
||||
@@ -157,6 +178,14 @@
|
||||
android:inputType="text"
|
||||
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
|
||||
|
||||
@@ -88,6 +88,14 @@
|
||||
android:layout_height="wrap_content"
|
||||
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>
|
||||
|
||||
|
||||
@@ -17,4 +17,9 @@
|
||||
android:icon="@drawable/ic_copy"
|
||||
android:title="@string/logcat_copy"
|
||||
app:showAsAction="ifRoom" />
|
||||
<item
|
||||
android:id="@+id/share_all"
|
||||
android:icon="@drawable/ic_share_24dp"
|
||||
android:title="@string/logcat_share"
|
||||
app:showAsAction="ifRoom" />
|
||||
</menu>
|
||||
@@ -90,10 +90,6 @@
|
||||
android:icon="@drawable/ic_share_24dp"
|
||||
android:title="@string/title_export_all"
|
||||
app:showAsAction="never" />
|
||||
<item
|
||||
android:id="@+id/ping_all"
|
||||
android:title="@string/title_ping_all_server"
|
||||
app:showAsAction="never" />
|
||||
<item
|
||||
android:id="@+id/real_ping_all"
|
||||
android:title="@string/title_real_ping_all_server"
|
||||
|
||||
@@ -118,7 +118,11 @@
|
||||
<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_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="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 -->
|
||||
<string name="toast_asset_copy_failed">فشل نسخ الملف، يرجى استخدام مدير الملفات</string>
|
||||
@@ -220,9 +224,6 @@
|
||||
<string name="summary_pref_proxy_sharing_enabled">يمكن للأجهزة الأخرى الاتصال بالبروكسي بواسطة عنوان IP الخاص بك من خلال socks/http، يتم التمكين فقط في الشبكة الموثوقة لتجنب الاتصال غير المصرح به</string>
|
||||
<string name="toast_warning_pref_proxysharing_short">السماح بالاتصالات من الشبكة المحلية، تأكد من أنك في شبكة موثوقة</string>
|
||||
|
||||
<string name="title_pref_allow_insecure">السماح غير الآمن</string>
|
||||
<string name="summary_pref_allow_insecure">عند TLS، الافتراضي هو السماح غير الآمن</string>
|
||||
|
||||
<string name="title_pref_socks_port">منفذ بروكسي Local</string>
|
||||
<string name="title_pref_enable_local_proxy">تفعيل الوكيل المحلي</string>
|
||||
<string name="summary_pref_enable_local_proxy">When disabled, no SOCKS proxy is available, so subscription updates and similar actions cannot use the proxy</string>
|
||||
@@ -286,6 +287,7 @@
|
||||
<string name="title_logcat">Logcat</string>
|
||||
<string name="logcat_copy">نسخ</string>
|
||||
<string name="logcat_clear">مسح</string>
|
||||
<string name="logcat_share">مشاركة السجل</string>
|
||||
<string name="title_service_restart">إعادة تشغيل الخدمة</string>
|
||||
<string name="title_del_all_config">حذف جميع الإعدادات (قبل أول خطوة)</string>
|
||||
<string name="title_del_duplicate_config">حذف الإعدادات المكررة (2)</string>
|
||||
@@ -304,7 +306,6 @@
|
||||
<string name="sub_setting_pre_profile_tip">The configuration remarks exists and is unique</string>
|
||||
<string name="toast_invalid_update_interval">فاصل التحديث غير صالح. الحد الأدنى هو 15 دقيقة.</string>
|
||||
<string name="title_sub_update">تحديث الاشتراك (أول خطوة)</string>
|
||||
<string name="title_ping_all_server">Tcping لجميع الإعدادات</string>
|
||||
<string name="title_real_ping_all_server"> اختبر جميع الإعدادات (3)</string>
|
||||
<string name="title_user_asset_setting">Asset files</string>
|
||||
<string name="title_sort_by_test_results">الفرز حسب نتائج الاختبار (5)</string>
|
||||
|
||||
@@ -117,7 +117,11 @@
|
||||
<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_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="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 -->
|
||||
<string name="toast_asset_copy_failed">ফাইল কপি ব্যর্থ, অনুগ্রহ করে ফাইল ম্যানেজার ব্যবহার করুন</string>
|
||||
@@ -220,9 +224,6 @@
|
||||
<string name="summary_pref_proxy_sharing_enabled">অন্যান্য ডিভাইসগুলি আপনার আইপি ঠিকানা ব্যবহার করে প্রক্সিতে সংযুক্ত হতে পারে, শুধুমাত্র বিশ্বস্ত নেটওয়ার্কে সক্রিয় করুন যাতে অনুমোদিত সংযোগ এড়ানো যায়</string>
|
||||
<string name="toast_warning_pref_proxysharing_short">LAN থেকে সংযোগ অনুমোদন করুন, নিশ্চিত করুন যে আপনি একটি বিশ্বস্ত নেটওয়ার্কে আছেন</string>
|
||||
|
||||
<string name="title_pref_allow_insecure">allowInsecure</string>
|
||||
<string name="summary_pref_allow_insecure">যখন TLS, ডিফল্টভাবে allowInsecure</string>
|
||||
|
||||
<string name="title_pref_socks_port">Local প্রক্সি পোর্ট</string>
|
||||
<string name="title_pref_enable_local_proxy">লোকাল প্রক্সি সক্ষম করুন</string>
|
||||
<string name="summary_pref_enable_local_proxy">When disabled, no SOCKS proxy is available, so subscription updates and similar actions cannot use the proxy</string>
|
||||
@@ -286,6 +287,7 @@
|
||||
<string name="title_logcat">লগক্যাট</string>
|
||||
<string name="logcat_copy">কপি করুন</string>
|
||||
<string name="logcat_clear">স্পষ্ট করুন</string>
|
||||
<string name="logcat_share">লগ শেয়ার করুন</string>
|
||||
<string name="title_service_restart">সার্ভিস পুনরায় চালু করুন</string>
|
||||
<string name="title_del_all_config">সব কনফিগারেশন মুছে ফেলুন</string>
|
||||
<string name="title_del_duplicate_config">ডুপ্লিকেট কনফিগারেশন মুছে ফেলুন</string>
|
||||
@@ -304,7 +306,6 @@
|
||||
<string name="sub_setting_pre_profile_tip">The configuration remarks exists and is unique</string>
|
||||
<string name="toast_invalid_update_interval">আপডেটের বিরতি সঠিক নয়। সর্বনিম্ন ১৫ মিনিট।</string>
|
||||
<string name="title_sub_update">সাবস্ক্রিপশন আপডেট</string>
|
||||
<string name="title_ping_all_server">সব কনফিগারেশন TCPing</string>
|
||||
<string name="title_real_ping_all_server">সব কনফিগারেশন প্রকৃত বিলম্ব</string>
|
||||
<string name="title_user_asset_setting">Asset files</string>
|
||||
<string name="title_sort_by_test_results">টেস্ট ফলাফল দ্বারা সাজানো</string>
|
||||
|
||||
@@ -117,7 +117,11 @@
|
||||
<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_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">گرؽڌن وو پور کردن جا کلک گوواهی</string>
|
||||
<string name="toast_fetch_cert_sha256_success">جا کلک گوواهی وا مووفقیت گرؽڌه وابی</string>
|
||||
<string name="toast_fetch_cert_sha256_failed">گرؽڌن جا کلک گوواهی مووفق نبی</string>
|
||||
|
||||
<!-- UserAssetActivity -->
|
||||
<string name="toast_asset_copy_failed">لف گیری فایل ٱنجوم نوابی، ز ی برنومه دؽوۉداری فایل هیاری بگرین</string>
|
||||
@@ -153,7 +157,7 @@
|
||||
<string name="title_core_settings">سامووا هسته</string>
|
||||
<string name="title_vpn_settings">سامووا VPN</string>
|
||||
<string name="title_pref_per_app_proxy">پروکسی و ری برنومه</string>
|
||||
<string name="summary_pref_per_app_proxy">پوی وولاتی: برنومه پسند وابیڌه وا ی پروکسی منپیز ابۊ، برنومه پسند نوابیڌه موستقیمن منپیز ابۊ. \nحالت دور زیڌن: برنومه پسند وابیڌه موستقیمن منپیز ابۊ، برنومه پسند نوابیڌه وا ی پروکسی منپیز ابۊ. \nپسند خوتکار برنومه یل پروکسی من نومگه امکووݩ پزیر هڌ.</string>
|
||||
<string name="summary_pref_per_app_proxy">پوی وولاتی: برنومه پسند وابیڌه وا ی پروکسی منپیز ابۊ، برنومه پسند نوابیڌه موستقیمن منپیز ابۊ.\nهالت دور زیڌن: برنومه پسند وابیڌه موستقیمن منپیز ابۊ، برنومه پسند نوابیڌه وا ی پروکسی منپیز ابۊ.\nپسند خوتکار برنومه یل پروکسی من نومگه امکووݩ پزیر هڌ.</string>
|
||||
<string name="title_pref_is_booted">منپیز خوتکار مجال ره ونی</string>
|
||||
<string name="summary_pref_is_booted">مجال ره وندن، خوساخوس و سرور پسند بیڌه منپیز ابۊ که گاشڌ نا مووفق بۊ.</string>
|
||||
|
||||
@@ -175,7 +179,7 @@
|
||||
</string-array>
|
||||
|
||||
<string name="title_pref_speed_enabled">ره وندن نشووݩ داڌن سورعت</string>
|
||||
<string name="summary_pref_speed_enabled">نشووݩ داڌن سورعت هیم سکویی من وارسۊویا. نماڌ وارسۊوی و ری و کار گرؽڌن کانفیگ آلشت ابۊ.</string>
|
||||
<string name="summary_pref_speed_enabled">نشووݩ داڌن سورعت هیم سکویی من وارسۊویا.\nنماڌ وارسۊوی و ری و کار گرؽڌن کانفیگ آلشت ابۊ.</string>
|
||||
|
||||
<string name="title_pref_sniffing_enabled">ره وندن تجزیه وو تئلیل کتنا (Sniffing)</string>
|
||||
<string name="summary_pref_sniffing_enabled">و کار گرؽڌن تشخیس نوم دامنه (Sniff) من کتنا (پؽش فرز رۊشن)</string>
|
||||
@@ -186,10 +190,10 @@
|
||||
<string name="summary_pref_local_dns_enabled">درخاستا DNS و هسته و من ایان وو و دست ماژول DNS پردازشت ابۊن (پؽشنهاڌ ابۊ ٱر لنگ تور جوستن سی دور زیڌن نشۊویا LAN وو وولات ٱسلی هڌین ره ونده بۊ)</string>
|
||||
|
||||
<string name="title_pref_fake_dns_enabled">ره وندن DNS جئلی</string>
|
||||
<string name="summary_pref_fake_dns_enabled">DNS مهلی نشۊویا IP جئلی ن وورگنه (زل تر، ٱما گاشڌ من ی قرده ز برنومه یل کار نکونه)</string>
|
||||
<string name="summary_pref_fake_dns_enabled">دی ان اس مهلی نشۊویا آی پی جئلی ن اوورگنه (زل تر، ٱما گاشڌ من ی قرده ز برنومه یل کار نکونه)</string>
|
||||
|
||||
<string name="title_pref_ipv6_enabled">ره وندن IPv6</string>
|
||||
<string name="summary_pref_ipv6_enabled">نشۊویا IPv6 وو تورا IPv6 ن ز رابت VPN ره ونین</string>
|
||||
<string name="summary_pref_ipv6_enabled">نشۊویا وو تورا IPv6 ن و رابت VPN ازاف کۊنین</string>
|
||||
|
||||
<string name="title_pref_prefer_ipv6">ترجی داڌن IPv6</string>
|
||||
<string name="summary_pref_prefer_ipv6">مجال هل وو فسل نوم دامنه، نشۊویا IPv6 ن ترجی بڌین</string>
|
||||
@@ -220,9 +224,6 @@
|
||||
<string name="summary_pref_proxy_sharing_enabled">پوی دسگایل ترن وا نشۊوی IP ایسا، ز ره socks/http و پروکسی منپیز بۊن، تینا من شبکه قابل ائتماد ره ونده بۊ تا ز منپیز قیر موجاز جلو گری بۊ.</string>
|
||||
<string name="toast_warning_pref_proxysharing_short">منپیزا ز شبکه مهلی ن موجار کۊنین، موتمعن بۊین ک من ی شبکه قابل ائتماڌ هڌین.</string>
|
||||
|
||||
<string name="title_pref_allow_insecure">اجازه نا ٱمن</string>
|
||||
<string name="summary_pref_allow_insecure">مجال و کار بوردن TLS ب تۉر پؽش فرز، موجوز نا ٱمن ره ونده هڌ.</string>
|
||||
|
||||
<string name="title_pref_socks_port">پورت پروکسی مهلی</string>
|
||||
<string name="title_pref_enable_local_proxy">ره وندن پروکسی مهلی</string>
|
||||
<string name="summary_pref_enable_local_proxy">هرسا ره نوسته بۊ، هیچ پروکسی SOCKS من دسرس نؽ، سی دل هیمو ورۊ رسۊوی یل اشتراک وو کارا جۊر یو نترن پروکسی ن و کار گرن.</string>
|
||||
@@ -240,7 +241,7 @@
|
||||
<string name="summary_pref_local_dns_port">پورت DNS مهلی</string>
|
||||
|
||||
<string name="title_pref_confirm_remove">قوۊل کردن پاک کردن کانفیگ</string>
|
||||
<string name="summary_pref_confirm_remove">سی پاک وابیڌن فایل کانفیگ نیاز به قوۊل کردن دووارته ز سمت منتور هڌ.</string>
|
||||
<string name="summary_pref_confirm_remove">سی پاک وابیڌن فایل کانفیگ، لنگ قوۊل کردن دووارته ز سمت منتور هڌ.</string>
|
||||
|
||||
<string name="title_pref_start_scan_immediate">زی اسکنن ره ون</string>
|
||||
<string name="summary_pref_start_scan_immediate">شؽواتگرن سی اسکن، زی مجال ره وندن بۊگۊشین، ٱندی ترین کودن اسکن کۊنین یا شؽواتی ن منه نوار ٱوزار پسند کۊنین.</string>
|
||||
@@ -249,7 +250,7 @@
|
||||
<string name="summary_pref_append_http_proxy">پروکسی HTTP ن موسقیمن ز (مۊرۊرگر/ی قرد ز برنومه یل لادراری بیڌه)، بؽ استفاڌه ز دسگا NIC مجازی (Android 10+) استفاڌه ابۊ.</string>
|
||||
|
||||
<string name="title_pref_double_column_display">ره وندن نشووݩ داڌن دو سۊتۊنی</string>
|
||||
<string name="summary_pref_double_column_display">نومگه نمایه یل من دو سۊتۊن نشووݩ داڌه ابۊن وو چینوو ترین موئتوا بیشتری ن سیل کۊنین. سی ره وستن، وا برنومه ن ز نۊ ره ونین.</string>
|
||||
<string name="summary_pref_double_column_display">نومگه نمایه یل من دو سۊتۊن نشووݩ داڌه ابۊن وو چینوو ترین موئتوا بیشتری ن سیل کۊنین.\nسی ره وستن، وا برنومه ن ز نۊ ره ونین.</string>
|
||||
|
||||
<string name="title_pref_group_all_display">نشووݩ داڌن پوی بونکۊ یل ن ره ونین</string>
|
||||
<string name="summary_pref_group_all_display">ی بلگه «پوی بونکۊ یل کانفیگ» ازاف کۊنین</string>
|
||||
@@ -286,6 +287,7 @@
|
||||
<string name="title_logcat">گوزارشا</string>
|
||||
<string name="logcat_copy">لف گیری</string>
|
||||
<string name="logcat_clear">روفتن</string>
|
||||
<string name="logcat_share">یک رسۊوی گوزارش</string>
|
||||
<string name="title_service_restart">ره وندن دووارته خدمات</string>
|
||||
<string name="title_del_all_config">پاک کردن پوی کانفیگا بونکۊ سکویی</string>
|
||||
<string name="title_del_duplicate_config">پاک کردن کانفیگا تکراری بونکۊ سکویی</string>
|
||||
@@ -304,7 +306,6 @@
|
||||
<string name="sub_setting_pre_profile_tip">موتمعن بۊ ک نوم موستعار هڌس وو جۊرس نی</string>
|
||||
<string name="toast_invalid_update_interval">فاسله ورۊ کردن نا موئتبر هڌ. هدقل مقدار 15 دؽقه هڌ.</string>
|
||||
<string name="title_sub_update">ورۊ کردن اشتراک بونکۊ سکویی</string>
|
||||
<string name="title_ping_all_server">Tcping کانفیگا بونکۊ سکویی</string>
|
||||
<string name="title_real_ping_all_server">تئخیر واقعی کانفیگا بونکۊ سکویی</string>
|
||||
<string name="title_user_asset_setting">فایلا بونچک جوقرافیایی</string>
|
||||
<string name="title_sort_by_test_results">ترتیب و ری نتیجه یل آزمایش</string>
|
||||
@@ -359,7 +360,7 @@
|
||||
<string name="connection_not_connected">منپیز نؽڌ</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="title_fragment_settings">سامووا فرگمنت</string>
|
||||
<string name="title_pref_fragment_packets">کتنا فرگمنت</string>
|
||||
@@ -447,7 +448,7 @@
|
||||
</string-array>
|
||||
<string-array name="policy_group_type">
|
||||
<item>کم ترین پینگ</item>
|
||||
<item>کم ترین بار(لود)</item>
|
||||
<item>کم ترین بار (لود)</item>
|
||||
<item>تساڌۊفی</item>
|
||||
<item>گردشی</item>
|
||||
</string-array>
|
||||
|
||||
@@ -117,7 +117,11 @@
|
||||
<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_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="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 -->
|
||||
<string name="toast_asset_copy_failed">کپی فایل انجام نشد، لطفا از برنامه مدیریت فایل استفاده کنید</string>
|
||||
@@ -218,9 +222,6 @@
|
||||
<string name="summary_pref_proxy_sharing_enabled">سایر دستگاه ها می توانند با استفاده از آدرس آیپی شما برای استفاده از یک پروکسی محلی متصل شوند. فقط در یک شبکه قابل اعتماد برای جلوگیری از اتصالات غیرمجاز استفاده کنید.</string>
|
||||
<string name="toast_warning_pref_proxysharing_short">اتصالات از طریق شبکه محلی را مجاز کنید، مطمئن شوید که در یک شبکه قابل اعتماد هستید.</string>
|
||||
|
||||
<string name="title_pref_allow_insecure">اعطای مجوز ناامن</string>
|
||||
<string name="summary_pref_allow_insecure">هنگام استفاده از TLS، به طور پیش فرض مجوز ناامن فعال است.</string>
|
||||
|
||||
<string name="title_pref_socks_port">پورت پروکسی محلی</string>
|
||||
<string name="title_pref_enable_local_proxy">فعالسازی پروکسی محلی</string>
|
||||
<string name="summary_pref_enable_local_proxy">When disabled, no SOCKS proxy is available, so subscription updates and similar actions cannot use the proxy</string>
|
||||
@@ -283,6 +284,7 @@
|
||||
<string name="title_logcat">گزارشات</string>
|
||||
<string name="logcat_copy">کپی</string>
|
||||
<string name="logcat_clear">پاک کردن</string>
|
||||
<string name="logcat_share">اشتراکگذاری گزارش</string>
|
||||
<string name="title_service_restart">راهاندازی مجدد خدمات</string>
|
||||
<string name="title_del_all_config">حذف تمام کانفیگ های گروه فعلی</string>
|
||||
<string name="title_del_duplicate_config">حذف کانفیگ های تکراری گروه فعلی</string>
|
||||
@@ -301,7 +303,6 @@
|
||||
<string name="sub_setting_pre_profile_tip">لطفاً مطمئن شوید که نام مستعار وجود دارد و منحصر به فرد است</string>
|
||||
<string name="toast_invalid_update_interval">بازه بروزرسانی نامعتبر است. حداقل مقدار 15 دقیقه است.</string>
|
||||
<string name="title_sub_update">بهروزرسانی گروه فعلی اشتراک</string>
|
||||
<string name="title_ping_all_server">TCPING کانفیگ های گروه فعلی</string>
|
||||
<string name="title_real_ping_all_server">تاخیر واقعی کانفیگ های گروه فعلی</string>
|
||||
<string name="title_user_asset_setting">فایل های منبع جغرافیایی</string>
|
||||
<string name="title_sort_by_test_results">مرتب سازی بر اساس نتایج آزمایش</string>
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
<string name="menu_item_import_config_qrcode">Импорт из QR-кода</string>
|
||||
<string name="menu_item_import_config_clipboard">Импорт из буфера обмена</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_manually_vmess">Добавить VMess</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_final_mask">Необработанный JSON FinalMask, формат: { FinalMaskObject }</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_browser_dialer">Использовать переадресацию браузера</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">Получить и заполнить отпечаток сертификата</string>
|
||||
<string name="toast_fetch_cert_sha256_success">Отпечаток сертификата успешно получен</string>
|
||||
<string name="toast_fetch_cert_sha256_failed">Отпечаток сертификата не получен</string>
|
||||
|
||||
<!-- UserAssetActivity -->
|
||||
<string name="toast_asset_copy_failed">Невозможно скопировать файл, используйте файловый менеджер</string>
|
||||
@@ -224,9 +229,6 @@
|
||||
<string name="summary_pref_proxy_sharing_enabled">Другие устройства могут подключаться, используя ваш IP-адрес, чтобы использовать локальный прокси. Используйте только в доверенной сети, чтобы избежать несанкционированного подключения.</string>
|
||||
<string name="toast_warning_pref_proxysharing_short">Доступ из LAN разрешён, убедитесь, что вы находитесь в доверенной сети</string>
|
||||
|
||||
<string name="title_pref_allow_insecure">Разрешать небезопасные соединения</string>
|
||||
<string name="summary_pref_allow_insecure">Для TLS по умолчанию разрешены небезопасные соединения</string>
|
||||
|
||||
<string name="title_pref_socks_port">Порт локального прокси</string>
|
||||
<string name="title_pref_enable_local_proxy">Использовать локальный прокси</string>
|
||||
<string name="summary_pref_enable_local_proxy">Если отключено, SOCKS-прокси недоступен, поэтому обновления подписки и подобные действия не смогут использовать прокси</string>
|
||||
@@ -291,6 +293,7 @@
|
||||
<string name="title_logcat">Журнал</string>
|
||||
<string name="logcat_copy">Копировать</string>
|
||||
<string name="logcat_clear">Очистить</string>
|
||||
<string name="logcat_share">Поделиться журналом</string>
|
||||
<string name="title_service_restart">Перезапуск службы</string>
|
||||
<string name="title_del_all_config">Удалить профили</string>
|
||||
<string name="title_del_duplicate_config">Удалить дубликаты профилей</string>
|
||||
@@ -309,7 +312,6 @@
|
||||
<string name="sub_setting_pre_profile_tip">Профиль должен быть уникальным</string>
|
||||
<string name="toast_invalid_update_interval">Неправильный интервал обновления. Минимум 15 минут.</string>
|
||||
<string name="title_sub_update">Обновить подписку</string>
|
||||
<string name="title_ping_all_server">Проверить профили</string>
|
||||
<string name="title_real_ping_all_server">Проверить задержку профилей</string>
|
||||
<string name="title_user_asset_setting">Файлы ресурсов</string>
|
||||
<string name="title_sort_by_test_results">Сортировать по результатам теста</string>
|
||||
@@ -379,7 +381,7 @@
|
||||
<string name="update_check_pre_release">Искать предварительный выпуск</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_filter">Название фильтра</string>
|
||||
|
||||
@@ -426,6 +428,12 @@
|
||||
<item>Прокси</item>
|
||||
</string-array>
|
||||
|
||||
<string-array name="browser_dialer_mode">
|
||||
<item>Отключено</item>
|
||||
<item>OkHttp</item>
|
||||
<item>WebView</item>
|
||||
</string-array>
|
||||
|
||||
<string-array name="ui_mode_night">
|
||||
<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_final_mask">finalMask raw JSON, format: { FinalMaskObject }</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="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 -->
|
||||
<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>
|
||||
@@ -220,9 +224,6 @@
|
||||
<string name="summary_pref_proxy_sharing_enabled">Các thiết bị khác trong cùng mạng LAN có thể kết nối đến SOCKS / HTTP proxy trên thiết bị của bạn. \nChỉ bật tính năng này trong các mạng đáng tin cậy để tránh kết nối trái phép.</string>
|
||||
<string name="toast_warning_pref_proxysharing_short">Đang bật cho phép kết nối từ mạng LAN</string>
|
||||
|
||||
<string name="title_pref_allow_insecure">Bỏ qua xác minh chứng chỉ</string>
|
||||
<string name="summary_pref_allow_insecure">Khi nhập những cấu hình có bảo mật TLS, mặc định sẽ không xác minh chứng chỉ.</string>
|
||||
|
||||
<string name="title_pref_socks_port">Cổng Proxy Local</string>
|
||||
<string name="title_pref_enable_local_proxy">Bật proxy cục bộ</string>
|
||||
<string name="summary_pref_enable_local_proxy">When disabled, no SOCKS proxy is available, so subscription updates and similar actions cannot use the proxy</string>
|
||||
@@ -286,6 +287,7 @@
|
||||
<string name="title_logcat">Logcat</string>
|
||||
<string name="logcat_copy">Sao chép</string>
|
||||
<string name="logcat_clear">Xoá</string>
|
||||
<string name="logcat_share">Chia sẻ nhật ký</string>
|
||||
<string name="title_service_restart">Khởi động lại dịch vụ lõi</string>
|
||||
<string name="title_del_all_config">Xoá tất cả cấu hình</string>
|
||||
<string name="title_del_duplicate_config">Xoá cấu hình trùng lặp</string>
|
||||
@@ -304,7 +306,6 @@
|
||||
<string name="sub_setting_pre_profile_tip">The configuration remarks exists and is unique</string>
|
||||
<string name="toast_invalid_update_interval">Khoảng thời gian cập nhật không hợp lệ. Giá trị tối thiểu là 15 phút.</string>
|
||||
<string name="title_sub_update">Cập nhật các gói đăng ký</string>
|
||||
<string name="title_ping_all_server">Ping tất cả máy chủ</string>
|
||||
<string name="title_real_ping_all_server">Kiểm tra HTTP tất cả máy chủ</string>
|
||||
<string name="title_user_asset_setting">Asset files</string>
|
||||
<string name="title_sort_by_test_results">Sắp xếp lại theo lần kiểm tra cuối cùng</string>
|
||||
|
||||
@@ -117,9 +117,14 @@
|
||||
<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_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_browser_dialer">启用浏览器转发</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="pinned_ca256_action_fetch">获取并填充指纹证书</string>
|
||||
<string name="toast_fetch_cert_sha256_success">获取指纹证书完成</string>
|
||||
<string name="toast_fetch_cert_sha256_failed">获取证书指纹失败</string>
|
||||
|
||||
<!-- UserAssetActivity -->
|
||||
<string name="toast_asset_copy_failed">失败, 请使用文件管理器</string>
|
||||
@@ -219,10 +224,7 @@
|
||||
<string name="summary_pref_proxy_sharing_enabled">其他设备可以使用 socks/http 协议通过您的 IP 地址连接到代理, 仅在受信任的网络中启用以避免未经授权的连接</string>
|
||||
<string name="toast_warning_pref_proxysharing_short">允许来自局域网的连接,请确保处于受信网络</string>
|
||||
|
||||
<string name="title_pref_allow_insecure">跳过证书验证 (allowInsecure)</string>
|
||||
<string name="summary_pref_allow_insecure">传输层安全选 tls 时,默认跳过证书验证 (allowInsecure)</string>
|
||||
|
||||
<string name="title_pref_socks_port">本地代理端口</string>
|
||||
<string name="title_pref_socks_port">本地代理端口</string>
|
||||
<string name="title_pref_enable_local_proxy">启用本地代理</string>
|
||||
<string name="summary_pref_enable_local_proxy">不开启时没有 socks 代理,无法通过代理更新订阅等</string>
|
||||
<string name="title_pref_socks_enable_udp">SOCKS5 UDP</string>
|
||||
@@ -286,6 +288,7 @@
|
||||
<string name="title_logcat">Logcat</string>
|
||||
<string name="logcat_copy">复制</string>
|
||||
<string name="logcat_clear">清除</string>
|
||||
<string name="logcat_share">分享日志</string>
|
||||
<string name="title_service_restart">服务重启</string>
|
||||
<string name="title_del_all_config">删除配置</string>
|
||||
<string name="title_del_duplicate_config">删除重复配置</string>
|
||||
@@ -304,7 +307,6 @@
|
||||
<string name="sub_setting_pre_profile_tip">请确保配置别名存在并唯一</string>
|
||||
<string name="toast_invalid_update_interval">更新间隔无效,最小值为 15 分钟。</string>
|
||||
<string name="title_sub_update">更新订阅</string>
|
||||
<string name="title_ping_all_server">测试配置 Tcping</string>
|
||||
<string name="title_real_ping_all_server">测试配置真连接</string>
|
||||
<string name="title_user_asset_setting">资源文件</string>
|
||||
<string name="title_sort_by_test_results">按测试结果排序</string>
|
||||
|
||||
@@ -117,7 +117,12 @@
|
||||
<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_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="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 -->
|
||||
<string name="toast_asset_copy_failed">失敗,請使用檔案總管</string>
|
||||
@@ -218,9 +223,6 @@
|
||||
<string name="summary_pref_proxy_sharing_enabled">其他裝置可以使用 socks/http 協定透過您的 IP 位址連線到 Proxy,僅在受信任的網路中啟用以避免未經授權的連線</string>
|
||||
<string name="toast_warning_pref_proxysharing_short">允許來自區域網路的連線,請確保處於受信網路</string>
|
||||
|
||||
<string name="title_pref_allow_insecure">跳過憑證驗證 (allowInsecure)</string>
|
||||
<string name="summary_pref_allow_insecure">傳輸層安全選 tls 時,預設跳過憑證驗證 (allowInsecure)</string>
|
||||
|
||||
<string name="title_pref_socks_port">本地代理埠</string>
|
||||
<string name="title_pref_enable_local_proxy">啟用本地代理</string>
|
||||
<string name="summary_pref_enable_local_proxy">未開啟時沒有 socks 代理,無法透過代理更新訂閱等</string>
|
||||
@@ -285,6 +287,7 @@
|
||||
<string name="title_logcat">Logcat</string>
|
||||
<string name="logcat_copy">複製</string>
|
||||
<string name="logcat_clear">清除</string>
|
||||
<string name="logcat_share">分享日誌</string>
|
||||
<string name="title_service_restart">重啟服務</string>
|
||||
<string name="title_del_all_config">刪除設定</string>
|
||||
<string name="title_del_duplicate_config">刪除重複設定</string>
|
||||
@@ -303,7 +306,6 @@
|
||||
<string name="sub_setting_pre_profile_tip">请确保設定檔别名存在并唯一</string>
|
||||
<string name="toast_invalid_update_interval">更新間隔無效,最小值為 15 分鐘。</string>
|
||||
<string name="title_sub_update">更新訂閱</string>
|
||||
<string name="title_ping_all_server">偵測設定 Tcping</string>
|
||||
<string name="title_real_ping_all_server">偵測設定真延遲</string>
|
||||
<string name="title_user_asset_setting">資源檔案</string>
|
||||
<string name="title_sort_by_test_results">依偵測結果排序</string>
|
||||
|
||||
@@ -64,6 +64,7 @@
|
||||
|
||||
<string-array name="fragment_packets" translatable="false">
|
||||
<item>tlshello</item>
|
||||
<item>1-1</item>
|
||||
<item>1-2</item>
|
||||
<item>1-3</item>
|
||||
<item>1-5</item>
|
||||
|
||||
@@ -118,9 +118,14 @@
|
||||
<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_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_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="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 -->
|
||||
<string name="toast_asset_copy_failed">File copy failed, please use File Manager</string>
|
||||
@@ -226,9 +231,6 @@
|
||||
<string name="summary_pref_proxy_sharing_enabled">Other devices can connect to proxy by your IP address through local proxy. Only enable in trusted networks to avoid unauthorized connections</string>
|
||||
<string name="toast_warning_pref_proxysharing_short">Allow connections from the LAN. Make sure you are in a trusted network</string>
|
||||
|
||||
<string name="title_pref_allow_insecure">allowInsecure</string>
|
||||
<string name="summary_pref_allow_insecure">When TLS is selected, allow insecure connections by default</string>
|
||||
|
||||
<string name="title_pref_socks_port">Local proxy port</string>
|
||||
<string name="title_pref_enable_local_proxy">Enable local proxy</string>
|
||||
<string name="summary_pref_enable_local_proxy">When disabled, no SOCKS proxy is available, so subscription updates and similar actions cannot use the proxy</string>
|
||||
@@ -293,6 +295,7 @@
|
||||
<string name="title_logcat">Logcat</string>
|
||||
<string name="logcat_copy">Copy</string>
|
||||
<string name="logcat_clear">Clear</string>
|
||||
<string name="logcat_share">Share log</string>
|
||||
<string name="title_service_restart">Service restart</string>
|
||||
<string name="title_del_all_config">Delete config</string>
|
||||
<string name="title_del_duplicate_config">Delete duplicate config</string>
|
||||
@@ -311,7 +314,6 @@
|
||||
<string name="sub_setting_pre_profile_tip">The config remarks exist and are unique</string>
|
||||
<string name="toast_invalid_update_interval">Invalid update interval. Minimum is 15 minutes.</string>
|
||||
<string name="title_sub_update">Update subscription</string>
|
||||
<string name="title_ping_all_server">Tcping config</string>
|
||||
<string name="title_real_ping_all_server">Real delay config</string>
|
||||
<string name="title_user_asset_setting">Asset files</string>
|
||||
<string name="title_sort_by_test_results">Sorting by test results</string>
|
||||
|
||||
@@ -144,11 +144,6 @@
|
||||
android:summary="@string/summary_pref_route_only_enabled"
|
||||
android:title="@string/title_pref_route_only_enabled" />
|
||||
|
||||
<CheckBoxPreference
|
||||
android:defaultValue="false"
|
||||
android:key="pref_allow_insecure"
|
||||
android:title="@string/title_pref_allow_insecure" />
|
||||
|
||||
<CheckBoxPreference
|
||||
android:defaultValue="true"
|
||||
android:key="pref_enable_local_proxy"
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
agp = "9.2.1"
|
||||
desugarJdkLibs = "2.1.5"
|
||||
gradleLicensePlugin = "0.9.8"
|
||||
kotlin = "2.3.10"
|
||||
kotlin = "2.4.0"
|
||||
coreKtx = "1.17.0"
|
||||
junit = "4.13.2"
|
||||
junitVersion = "1.3.0"
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
#Thu Nov 14 12:42:51 BDT 2024
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-9.4.1-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-9.5.1-bin.zip
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
Reference in new issue
Block a user