Compare commits
51 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
002bf7ef22 | ||
|
|
6919e2336d | ||
|
|
5a5bd22073 | ||
|
|
a726f00f35 | ||
|
|
79297f8a42 | ||
|
|
e3f70ac253 | ||
|
|
838b346fcc | ||
|
|
eba9545ccf | ||
|
|
890ade9495 | ||
|
|
518ef1e0ec | ||
|
|
bdcecfca72 | ||
|
|
1363846ac4 | ||
|
|
30347546a2 | ||
|
|
ac7eb28e91 | ||
|
|
748405473b | ||
|
|
1080390bed | ||
|
|
c48725c7dd | ||
|
|
a5287dbadc | ||
|
|
ee5a3b0dd9 | ||
|
|
3d001541e5 | ||
|
|
b376b229b9 | ||
|
|
33b6203978 | ||
|
|
2d803e009c | ||
|
|
2ddbe38781 | ||
|
|
96181a2b8d | ||
|
|
8308b8eaf2 | ||
|
|
00f26ff529 | ||
|
|
49dcdf3ae5 | ||
|
|
409b431d1c | ||
|
|
6da988e3db | ||
|
|
fd8f8306ee | ||
|
|
74b342f5c6 | ||
|
|
2504ec79ee | ||
|
|
3e7b211b17 | ||
|
|
13d5514a4c | ||
|
|
f0f9da0f1b | ||
|
|
f6d2c5f473 | ||
|
|
6e8dd5b250 | ||
|
|
f4779bc50c | ||
|
|
432baf262d | ||
|
|
a1d68fcde3 | ||
|
|
e053db3dff | ||
|
|
f624bd651e | ||
|
|
84964c7f91 | ||
|
|
96f56b468e | ||
|
|
bdc3212f38 | ||
|
|
508ddf6df2 | ||
|
|
17af24d179 | ||
|
|
c260e447ea | ||
|
|
0eb40ae993 | ||
|
|
18f3e39346 |
@@ -11,9 +11,18 @@ android {
|
|||||||
applicationId = "com.v2ray.ang"
|
applicationId = "com.v2ray.ang"
|
||||||
minSdk = 21
|
minSdk = 21
|
||||||
targetSdk = 34
|
targetSdk = 34
|
||||||
versionCode = 557
|
versionCode = 571
|
||||||
versionName = "1.8.21"
|
versionName = "1.8.28"
|
||||||
multiDexEnabled = true
|
multiDexEnabled = true
|
||||||
|
splits.abi {
|
||||||
|
reset()
|
||||||
|
include(
|
||||||
|
"arm64-v8a",
|
||||||
|
"armeabi-v7a",
|
||||||
|
"x86_64",
|
||||||
|
"x86"
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
compileOptions {
|
compileOptions {
|
||||||
@@ -51,7 +60,7 @@ android {
|
|||||||
applicationVariants.all {
|
applicationVariants.all {
|
||||||
val variant = this
|
val variant = this
|
||||||
val versionCodes =
|
val versionCodes =
|
||||||
mapOf("armeabi-v7a" to 1, "arm64-v8a" to 2, "x86" to 3, "x86_64" to 4)
|
mapOf("armeabi-v7a" to 4, "arm64-v8a" to 4, "x86" to 4, "x86_64" to 4, "universal" to 4)
|
||||||
|
|
||||||
variant.outputs
|
variant.outputs
|
||||||
.map { it as com.android.build.gradle.internal.api.ApkVariantOutputImpl }
|
.map { it as com.android.build.gradle.internal.api.ApkVariantOutputImpl }
|
||||||
@@ -59,7 +68,7 @@ android {
|
|||||||
val abi = if (output.getFilter("ABI") != null)
|
val abi = if (output.getFilter("ABI") != null)
|
||||||
output.getFilter("ABI")
|
output.getFilter("ABI")
|
||||||
else
|
else
|
||||||
"all"
|
"universal"
|
||||||
|
|
||||||
output.outputFileName = "v2rayNG_${variant.versionName}_${abi}.apk"
|
output.outputFileName = "v2rayNG_${variant.versionName}_${abi}.apk"
|
||||||
if(versionCodes.containsKey(abi))
|
if(versionCodes.containsKey(abi))
|
||||||
@@ -77,37 +86,44 @@ android {
|
|||||||
viewBinding = true
|
viewBinding = true
|
||||||
buildConfig = true
|
buildConfig = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
packagingOptions {
|
||||||
|
jniLibs {
|
||||||
|
useLegacyPackaging = true
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation(fileTree(mapOf("dir" to "libs", "include" to listOf("*.aar","*.jar"))))
|
implementation(fileTree(mapOf("dir" to "libs", "include" to listOf("*.aar","*.jar"))))
|
||||||
testImplementation("junit:junit:4.13.2")
|
testImplementation("junit:junit:4.13.2")
|
||||||
|
|
||||||
|
implementation("com.google.android.flexbox:flexbox:3.0.0")
|
||||||
// Androidx
|
// Androidx
|
||||||
implementation("androidx.constraintlayout:constraintlayout:2.1.4")
|
implementation("androidx.constraintlayout:constraintlayout:2.1.4")
|
||||||
implementation("androidx.legacy:legacy-support-v4:1.0.0")
|
implementation("androidx.legacy:legacy-support-v4:1.0.0")
|
||||||
implementation("androidx.appcompat:appcompat:1.6.1")
|
implementation("androidx.appcompat:appcompat:1.7.0")
|
||||||
implementation("com.google.android.material:material:1.11.0")
|
implementation("com.google.android.material:material:1.12.0")
|
||||||
implementation("androidx.cardview:cardview:1.0.0")
|
implementation("androidx.cardview:cardview:1.0.0")
|
||||||
implementation("androidx.preference:preference-ktx:1.2.1")
|
implementation("androidx.preference:preference-ktx:1.2.1")
|
||||||
implementation("androidx.recyclerview:recyclerview:1.3.2")
|
implementation("androidx.recyclerview:recyclerview:1.3.2")
|
||||||
implementation("androidx.fragment:fragment-ktx:1.6.2")
|
implementation("androidx.fragment:fragment-ktx:1.8.1")
|
||||||
implementation("androidx.multidex:multidex:2.0.1")
|
implementation("androidx.multidex:multidex:2.0.1")
|
||||||
implementation("androidx.viewpager2:viewpager2:1.1.0-beta02")
|
implementation("androidx.viewpager2:viewpager2:1.1.0")
|
||||||
|
|
||||||
// Androidx ktx
|
// Androidx ktx
|
||||||
implementation("androidx.activity:activity-ktx:1.9.0")
|
implementation("androidx.activity:activity-ktx:1.9.0")
|
||||||
implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.7.0")
|
implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.8.3")
|
||||||
implementation("androidx.lifecycle:lifecycle-livedata-ktx:2.7.0")
|
implementation("androidx.lifecycle:lifecycle-livedata-ktx:2.8.3")
|
||||||
implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.7.0")
|
implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.8.3")
|
||||||
|
|
||||||
//kotlin
|
//kotlin
|
||||||
implementation("org.jetbrains.kotlin:kotlin-reflect:1.9.23")
|
implementation("org.jetbrains.kotlin:kotlin-reflect:1.9.23")
|
||||||
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.8.0")
|
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.8.1")
|
||||||
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.8.0")
|
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.8.1")
|
||||||
|
|
||||||
implementation("com.tencent:mmkv-static:1.3.4")
|
implementation("com.tencent:mmkv-static:1.3.4")
|
||||||
implementation("com.google.code.gson:gson:2.10.1")
|
implementation("com.google.code.gson:gson:2.11.0")
|
||||||
implementation("io.reactivex:rxjava:1.3.8")
|
implementation("io.reactivex:rxjava:1.3.8")
|
||||||
implementation("io.reactivex:rxandroid:1.2.1")
|
implementation("io.reactivex:rxandroid:1.2.1")
|
||||||
implementation("com.tbruyelle.rxpermissions:rxpermissions:0.9.4@aar")
|
implementation("com.tbruyelle.rxpermissions:rxpermissions:0.9.4@aar")
|
||||||
|
|||||||
@@ -54,7 +54,7 @@
|
|||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.MAIN" />
|
<action android:name="android.intent.action.MAIN" />
|
||||||
<category android:name="android.intent.category.LAUNCHER" />
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
<!-- <category android:name="android.intent.category.LEANBACK_LAUNCHER"/>-->
|
<category android:name="android.intent.category.LEANBACK_LAUNCHER"/>
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.service.quicksettings.action.QS_TILE_PREFERENCES" />
|
<action android:name="android.service.quicksettings.action.QS_TILE_PREFERENCES" />
|
||||||
@@ -227,6 +227,16 @@
|
|||||||
|
|
||||||
</provider>
|
</provider>
|
||||||
|
|
||||||
|
<provider
|
||||||
|
android:name="androidx.core.content.FileProvider"
|
||||||
|
android:authorities="${applicationId}.cache"
|
||||||
|
android:exported="false"
|
||||||
|
android:grantUriPermissions="true">
|
||||||
|
<meta-data
|
||||||
|
android:name="android.support.FILE_PROVIDER_PATHS"
|
||||||
|
android:resource="@xml/cache_paths"/>
|
||||||
|
</provider>
|
||||||
|
|
||||||
</application>
|
</application>
|
||||||
|
|
||||||
</manifest>
|
</manifest>
|
||||||
|
|||||||
@@ -15,6 +15,8 @@ object AppConfig {
|
|||||||
|
|
||||||
// Preferences mapped to MMKV
|
// Preferences mapped to MMKV
|
||||||
const val PREF_SNIFFING_ENABLED = "pref_sniffing_enabled"
|
const val PREF_SNIFFING_ENABLED = "pref_sniffing_enabled"
|
||||||
|
const val PREF_ROUTE_ONLY_ENABLED = "pref_route_only_enabled"
|
||||||
|
|
||||||
const val PREF_PER_APP_PROXY = "pref_per_app_proxy"
|
const val PREF_PER_APP_PROXY = "pref_per_app_proxy"
|
||||||
const val PREF_PER_APP_PROXY_SET = "pref_per_app_proxy_set"
|
const val PREF_PER_APP_PROXY_SET = "pref_per_app_proxy_set"
|
||||||
const val PREF_BYPASS_APPS = "pref_bypass_apps"
|
const val PREF_BYPASS_APPS = "pref_bypass_apps"
|
||||||
@@ -58,6 +60,7 @@ object AppConfig {
|
|||||||
const val PREF_HTTP_PORT = "pref_http_port"
|
const val PREF_HTTP_PORT = "pref_http_port"
|
||||||
const val PREF_REMOTE_DNS = "pref_remote_dns"
|
const val PREF_REMOTE_DNS = "pref_remote_dns"
|
||||||
const val PREF_DOMESTIC_DNS = "pref_domestic_dns"
|
const val PREF_DOMESTIC_DNS = "pref_domestic_dns"
|
||||||
|
const val PREF_DELAY_TEST_URL = "pref_delay_test_url"
|
||||||
const val PREF_LOGLEVEL = "pref_core_loglevel"
|
const val PREF_LOGLEVEL = "pref_core_loglevel"
|
||||||
const val PREF_MODE = "pref_mode"
|
const val PREF_MODE = "pref_mode"
|
||||||
|
|
||||||
@@ -96,6 +99,8 @@ object AppConfig {
|
|||||||
const val PromotionUrl = "aHR0cHM6Ly85LjIzNDQ1Ni54eXovYWJjLmh0bWw="
|
const val PromotionUrl = "aHR0cHM6Ly85LjIzNDQ1Ni54eXovYWJjLmh0bWw="
|
||||||
const val GeoUrl = "https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/"
|
const val GeoUrl = "https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/"
|
||||||
const val TgChannelUrl = "https://t.me/github_2dust"
|
const val TgChannelUrl = "https://t.me/github_2dust"
|
||||||
|
const val DelayTestUrl = "https://www.gstatic.com/generate_204"
|
||||||
|
const val DelayTestUrl2 = "https://www.google.com/generate_204"
|
||||||
|
|
||||||
const val DNS_PROXY = "1.1.1.1"
|
const val DNS_PROXY = "1.1.1.1"
|
||||||
const val DNS_DIRECT = "223.5.5.5"
|
const val DNS_DIRECT = "223.5.5.5"
|
||||||
|
|||||||
@@ -1,32 +0,0 @@
|
|||||||
package com.v2ray.ang.dto
|
|
||||||
|
|
||||||
data class AngConfig(
|
|
||||||
var index: Int,
|
|
||||||
var vmess: ArrayList<VmessBean>,
|
|
||||||
var subItem: ArrayList<SubItemBean>
|
|
||||||
) {
|
|
||||||
data class VmessBean(var guid: String = "123456",
|
|
||||||
var address: String = "v2ray.cool",
|
|
||||||
var port: Int = 10086,
|
|
||||||
var id: String = "a3482e88-686a-4a58-8126-99c9df64b7bf",
|
|
||||||
var alterId: Int = 64,
|
|
||||||
var security: String = "aes-128-cfb",
|
|
||||||
var network: String = "tcp",
|
|
||||||
var remarks: String = "def",
|
|
||||||
var headerType: String = "",
|
|
||||||
var requestHost: String = "",
|
|
||||||
var path: String = "",
|
|
||||||
var streamSecurity: String = "",
|
|
||||||
var allowInsecure: String = "",
|
|
||||||
var configType: Int = 1,
|
|
||||||
var configVersion: Int = 1,
|
|
||||||
var testResult: String = "",
|
|
||||||
var subid: String = "",
|
|
||||||
var flow: String = "",
|
|
||||||
var sni: String = "")
|
|
||||||
|
|
||||||
data class SubItemBean(var id: String = "",
|
|
||||||
var remarks: String = "",
|
|
||||||
var url: String = "",
|
|
||||||
var enabled: Boolean = true)
|
|
||||||
}
|
|
||||||
@@ -60,7 +60,8 @@ data class V2rayConfig(
|
|||||||
|
|
||||||
data class SniffingBean(var enabled: Boolean,
|
data class SniffingBean(var enabled: Boolean,
|
||||||
val destOverride: ArrayList<String>,
|
val destOverride: ArrayList<String>,
|
||||||
val metadataOnly: Boolean? = null)
|
val metadataOnly: Boolean? = null,
|
||||||
|
var routeOnly: Boolean? = null)
|
||||||
}
|
}
|
||||||
|
|
||||||
data class OutboundBean(var tag: String = "proxy",
|
data class OutboundBean(var tag: String = "proxy",
|
||||||
@@ -138,6 +139,7 @@ data class V2rayConfig(
|
|||||||
var kcpSettings: KcpSettingsBean? = null,
|
var kcpSettings: KcpSettingsBean? = null,
|
||||||
var wsSettings: WsSettingsBean? = null,
|
var wsSettings: WsSettingsBean? = null,
|
||||||
var httpupgradeSettings: HttpupgradeSettingsBean? = null,
|
var httpupgradeSettings: HttpupgradeSettingsBean? = null,
|
||||||
|
var splithttpSettings: SplithttpSettingsBean? = null,
|
||||||
var httpSettings: HttpSettingsBean? = null,
|
var httpSettings: HttpSettingsBean? = null,
|
||||||
var tlsSettings: TlsSettingsBean? = null,
|
var tlsSettings: TlsSettingsBean? = null,
|
||||||
var quicSettings: QuicSettingBean? = null,
|
var quicSettings: QuicSettingBean? = null,
|
||||||
@@ -156,7 +158,7 @@ data class V2rayConfig(
|
|||||||
var headers: HeadersBean = HeadersBean(),
|
var headers: HeadersBean = HeadersBean(),
|
||||||
val version: String? = null,
|
val version: String? = null,
|
||||||
val method: String? = null) {
|
val method: String? = null) {
|
||||||
data class HeadersBean(var Host: List<String> = ArrayList(),
|
data class HeadersBean(var Host: List<String>? = ArrayList(),
|
||||||
@SerializedName("User-Agent")
|
@SerializedName("User-Agent")
|
||||||
val userAgent: List<String>? = null,
|
val userAgent: List<String>? = null,
|
||||||
@SerializedName("Accept-Encoding")
|
@SerializedName("Accept-Encoding")
|
||||||
@@ -191,6 +193,10 @@ data class V2rayConfig(
|
|||||||
var host: String = "",
|
var host: String = "",
|
||||||
val acceptProxyProtocol: Boolean? = null)
|
val acceptProxyProtocol: Boolean? = null)
|
||||||
|
|
||||||
|
data class SplithttpSettingsBean(var path: String = "",
|
||||||
|
var host: String = "",
|
||||||
|
val maxUploadSize: Int? = null,
|
||||||
|
val maxConcurrentUploads: Int? = null)
|
||||||
data class HttpSettingsBean(var host: List<String> = ArrayList(),
|
data class HttpSettingsBean(var host: List<String> = ArrayList(),
|
||||||
var path: String = "")
|
var path: String = "")
|
||||||
|
|
||||||
@@ -226,7 +232,10 @@ data class V2rayConfig(
|
|||||||
|
|
||||||
data class GrpcSettingsBean(var serviceName: String = "",
|
data class GrpcSettingsBean(var serviceName: String = "",
|
||||||
var authority: String? = null,
|
var authority: String? = null,
|
||||||
var multiMode: Boolean? = null)
|
var multiMode: Boolean? = null,
|
||||||
|
var idle_timeout: Int? = null,
|
||||||
|
var health_check_timeout: Int? = null
|
||||||
|
)
|
||||||
|
|
||||||
fun populateTransportSettings(transport: String, headerType: String?, host: String?, path: String?, seed: String?,
|
fun populateTransportSettings(transport: String, headerType: String?, host: String?, path: String?, seed: String?,
|
||||||
quicSecurity: String?, key: String?, mode: String?, serviceName: String?,
|
quicSecurity: String?, key: String?, mode: String?, serviceName: String?,
|
||||||
@@ -243,7 +252,7 @@ data class V2rayConfig(
|
|||||||
requestObj.headers.Host = (host ?: "").split(",").map { it.trim() }.filter { it.isNotEmpty() }
|
requestObj.headers.Host = (host ?: "").split(",").map { it.trim() }.filter { it.isNotEmpty() }
|
||||||
requestObj.path = (path ?: "").split(",").map { it.trim() }.filter { it.isNotEmpty() }
|
requestObj.path = (path ?: "").split(",").map { it.trim() }.filter { it.isNotEmpty() }
|
||||||
tcpSetting.header.request = requestObj
|
tcpSetting.header.request = requestObj
|
||||||
sni = requestObj.headers.Host.getOrNull(0) ?: sni
|
sni = requestObj.headers.Host?.getOrNull(0) ?: sni
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
tcpSetting.header.type = "none"
|
tcpSetting.header.type = "none"
|
||||||
@@ -275,6 +284,13 @@ data class V2rayConfig(
|
|||||||
httpupgradeSetting.path = path ?: "/"
|
httpupgradeSetting.path = path ?: "/"
|
||||||
httpupgradeSettings = httpupgradeSetting
|
httpupgradeSettings = httpupgradeSetting
|
||||||
}
|
}
|
||||||
|
"splithttp" -> {
|
||||||
|
val splithttpSetting = SplithttpSettingsBean()
|
||||||
|
splithttpSetting.host = host ?: ""
|
||||||
|
sni = splithttpSetting.host
|
||||||
|
splithttpSetting.path = path ?: "/"
|
||||||
|
splithttpSettings = splithttpSetting
|
||||||
|
}
|
||||||
"h2", "http" -> {
|
"h2", "http" -> {
|
||||||
network = "h2"
|
network = "h2"
|
||||||
val h2Setting = HttpSettingsBean()
|
val h2Setting = HttpSettingsBean()
|
||||||
@@ -295,6 +311,8 @@ data class V2rayConfig(
|
|||||||
grpcSetting.multiMode = mode == "multi"
|
grpcSetting.multiMode = mode == "multi"
|
||||||
grpcSetting.serviceName = serviceName ?: ""
|
grpcSetting.serviceName = serviceName ?: ""
|
||||||
grpcSetting.authority = authority ?: ""
|
grpcSetting.authority = authority ?: ""
|
||||||
|
grpcSetting.idle_timeout = 60
|
||||||
|
grpcSetting.health_check_timeout = 20
|
||||||
sni = authority ?: ""
|
sni = authority ?: ""
|
||||||
grpcSettings = grpcSetting
|
grpcSettings = grpcSetting
|
||||||
}
|
}
|
||||||
@@ -412,6 +430,12 @@ data class V2rayConfig(
|
|||||||
httpupgradeSetting.host,
|
httpupgradeSetting.host,
|
||||||
httpupgradeSetting.path)
|
httpupgradeSetting.path)
|
||||||
}
|
}
|
||||||
|
"splithttp" -> {
|
||||||
|
val splithttpSetting = streamSettings?.splithttpSettings ?: return null
|
||||||
|
listOf("",
|
||||||
|
splithttpSetting.host,
|
||||||
|
splithttpSetting.path)
|
||||||
|
}
|
||||||
"h2" -> {
|
"h2" -> {
|
||||||
val h2Setting = streamSettings?.httpSettings ?: return null
|
val h2Setting = streamSettings?.httpSettings ?: return null
|
||||||
listOf("",
|
listOf("",
|
||||||
@@ -489,7 +513,7 @@ data class V2rayConfig(
|
|||||||
var poolSize: Int = 10000) // roughly 10 times smaller than total ip pool
|
var poolSize: Int = 10000) // roughly 10 times smaller than total ip pool
|
||||||
|
|
||||||
fun getProxyOutbound(): OutboundBean? {
|
fun getProxyOutbound(): OutboundBean? {
|
||||||
outbounds.forEach { outbound ->
|
outbounds?.forEach { outbound ->
|
||||||
EConfigType.entries.forEach {
|
EConfigType.entries.forEach {
|
||||||
if (outbound.protocol.equals(it.name, true)) {
|
if (outbound.protocol.equals(it.name, true)) {
|
||||||
return outbound
|
return outbound
|
||||||
|
|||||||
@@ -58,23 +58,11 @@ object SubscriptionUpdater {
|
|||||||
"subscription automatic update: ---${subscription.remarks}"
|
"subscription automatic update: ---${subscription.remarks}"
|
||||||
)
|
)
|
||||||
val configs = Utils.getUrlContentWithCustomUserAgent(subscription.url)
|
val configs = Utils.getUrlContentWithCustomUserAgent(subscription.url)
|
||||||
importBatchConfig(configs, i.first)
|
AngConfigManager.importBatchConfig(configs, i.first, false)
|
||||||
notification.setContentText("Updating ${subscription.remarks}")
|
notification.setContentText("Updating ${subscription.remarks}")
|
||||||
}
|
}
|
||||||
notificationManager.cancel(3)
|
notificationManager.cancel(3)
|
||||||
return Result.success()
|
return Result.success()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun importBatchConfig(server: String?, subid: String = "") {
|
|
||||||
val append = subid.isEmpty()
|
|
||||||
|
|
||||||
val count = AngConfigManager.importBatchConfig(server, subid, append)
|
|
||||||
if (count <= 0) {
|
|
||||||
AngConfigManager.importBatchConfig(Utils.decode(server!!), subid, append)
|
|
||||||
}
|
|
||||||
if (count <= 0) {
|
|
||||||
AngConfigManager.appendCustomConfigServer(server, subid)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -63,6 +63,11 @@ object V2RayServiceManager {
|
|||||||
private var mNotificationManager: NotificationManager? = null
|
private var mNotificationManager: NotificationManager? = null
|
||||||
|
|
||||||
fun startV2Ray(context: Context) {
|
fun startV2Ray(context: Context) {
|
||||||
|
if (v2rayPoint.isRunning) return
|
||||||
|
val guid = mainStorage?.decodeString(MmkvManager.KEY_SELECTED_SERVER) ?: return
|
||||||
|
val result = V2rayConfigUtil.getV2rayConfig(context, guid)
|
||||||
|
if (!result.status) return
|
||||||
|
|
||||||
if (settingsStorage?.decodeBool(AppConfig.PREF_PROXY_SHARING) == true) {
|
if (settingsStorage?.decodeBool(AppConfig.PREF_PROXY_SHARING) == true) {
|
||||||
context.toast(R.string.toast_warning_pref_proxysharing_short)
|
context.toast(R.string.toast_warning_pref_proxysharing_short)
|
||||||
} else {
|
} else {
|
||||||
@@ -126,42 +131,43 @@ object V2RayServiceManager {
|
|||||||
val service = serviceControl?.get()?.getService() ?: return
|
val service = serviceControl?.get()?.getService() ?: return
|
||||||
val guid = mainStorage?.decodeString(MmkvManager.KEY_SELECTED_SERVER) ?: return
|
val guid = mainStorage?.decodeString(MmkvManager.KEY_SELECTED_SERVER) ?: return
|
||||||
val config = MmkvManager.decodeServerConfig(guid) ?: return
|
val config = MmkvManager.decodeServerConfig(guid) ?: return
|
||||||
if (!v2rayPoint.isRunning) {
|
if (v2rayPoint.isRunning) {
|
||||||
val result = V2rayConfigUtil.getV2rayConfig(service, guid)
|
return
|
||||||
if (!result.status)
|
}
|
||||||
return
|
val result = V2rayConfigUtil.getV2rayConfig(service, guid)
|
||||||
|
if (!result.status)
|
||||||
|
return
|
||||||
|
|
||||||
try {
|
try {
|
||||||
val mFilter = IntentFilter(AppConfig.BROADCAST_ACTION_SERVICE)
|
val mFilter = IntentFilter(AppConfig.BROADCAST_ACTION_SERVICE)
|
||||||
mFilter.addAction(Intent.ACTION_SCREEN_ON)
|
mFilter.addAction(Intent.ACTION_SCREEN_ON)
|
||||||
mFilter.addAction(Intent.ACTION_SCREEN_OFF)
|
mFilter.addAction(Intent.ACTION_SCREEN_OFF)
|
||||||
mFilter.addAction(Intent.ACTION_USER_PRESENT)
|
mFilter.addAction(Intent.ACTION_USER_PRESENT)
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||||
service.registerReceiver(mMsgReceive, mFilter, Context.RECEIVER_EXPORTED)
|
service.registerReceiver(mMsgReceive, mFilter, Context.RECEIVER_EXPORTED)
|
||||||
} else {
|
|
||||||
service.registerReceiver(mMsgReceive, mFilter)
|
|
||||||
}
|
|
||||||
} catch (e: Exception) {
|
|
||||||
Log.d(ANG_PACKAGE, e.toString())
|
|
||||||
}
|
|
||||||
|
|
||||||
v2rayPoint.configureFileContent = result.content
|
|
||||||
v2rayPoint.domainName = config.getV2rayPointDomainAndPort()
|
|
||||||
currentConfig = config
|
|
||||||
|
|
||||||
try {
|
|
||||||
v2rayPoint.runLoop(settingsStorage?.decodeBool(AppConfig.PREF_PREFER_IPV6) ?: false)
|
|
||||||
} catch (e: Exception) {
|
|
||||||
Log.d(ANG_PACKAGE, e.toString())
|
|
||||||
}
|
|
||||||
|
|
||||||
if (v2rayPoint.isRunning) {
|
|
||||||
MessageUtil.sendMsg2UI(service, AppConfig.MSG_STATE_START_SUCCESS, "")
|
|
||||||
showNotification()
|
|
||||||
} else {
|
} else {
|
||||||
MessageUtil.sendMsg2UI(service, AppConfig.MSG_STATE_START_FAILURE, "")
|
service.registerReceiver(mMsgReceive, mFilter)
|
||||||
cancelNotification()
|
|
||||||
}
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.d(ANG_PACKAGE, e.toString())
|
||||||
|
}
|
||||||
|
|
||||||
|
v2rayPoint.configureFileContent = result.content
|
||||||
|
v2rayPoint.domainName = config.getV2rayPointDomainAndPort()
|
||||||
|
currentConfig = config
|
||||||
|
|
||||||
|
try {
|
||||||
|
v2rayPoint.runLoop(settingsStorage?.decodeBool(AppConfig.PREF_PREFER_IPV6) ?: false)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.d(ANG_PACKAGE, e.toString())
|
||||||
|
}
|
||||||
|
|
||||||
|
if (v2rayPoint.isRunning) {
|
||||||
|
MessageUtil.sendMsg2UI(service, AppConfig.MSG_STATE_START_SUCCESS, "")
|
||||||
|
showNotification()
|
||||||
|
} else {
|
||||||
|
MessageUtil.sendMsg2UI(service, AppConfig.MSG_STATE_START_FAILURE, "")
|
||||||
|
cancelNotification()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -237,11 +243,19 @@ object V2RayServiceManager {
|
|||||||
var errstr = ""
|
var errstr = ""
|
||||||
if (v2rayPoint.isRunning) {
|
if (v2rayPoint.isRunning) {
|
||||||
try {
|
try {
|
||||||
time = v2rayPoint.measureDelay()
|
time = v2rayPoint.measureDelay(Utils.getDelayTestUrl())
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.d(ANG_PACKAGE, "measureV2rayDelay: $e")
|
Log.d(ANG_PACKAGE, "measureV2rayDelay: $e")
|
||||||
errstr = e.message?.substringAfter("\":") ?: "empty message"
|
errstr = e.message?.substringAfter("\":") ?: "empty message"
|
||||||
}
|
}
|
||||||
|
if (time == -1L) {
|
||||||
|
try {
|
||||||
|
time = v2rayPoint.measureDelay(Utils.getDelayTestUrl(true))
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.d(ANG_PACKAGE, "measureV2rayDelay: $e")
|
||||||
|
errstr = e.message?.substringAfter("\":") ?: "empty message"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
val result = if (time == -1L) {
|
val result = if (time == -1L) {
|
||||||
service.getString(R.string.connection_test_error, errstr)
|
service.getString(R.string.connection_test_error, errstr)
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import android.content.Intent
|
|||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import androidx.activity.result.contract.ActivityResultContracts
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
|
import androidx.core.content.FileProvider
|
||||||
import com.tbruyelle.rxpermissions.RxPermissions
|
import com.tbruyelle.rxpermissions.RxPermissions
|
||||||
import com.tencent.mmkv.MMKV
|
import com.tencent.mmkv.MMKV
|
||||||
import com.v2ray.ang.AppConfig
|
import com.v2ray.ang.AppConfig
|
||||||
@@ -33,7 +34,31 @@ class AboutActivity : BaseActivity() {
|
|||||||
|
|
||||||
binding.tvBackupSummary.text = this.getString(R.string.summary_configuration_backup, extDir)
|
binding.tvBackupSummary.text = this.getString(R.string.summary_configuration_backup, extDir)
|
||||||
binding.layoutBackup.setOnClickListener {
|
binding.layoutBackup.setOnClickListener {
|
||||||
backupMMKV()
|
val ret = backupConfiguration(extDir.absolutePath)
|
||||||
|
if (ret.first) {
|
||||||
|
toast(R.string.toast_success)
|
||||||
|
} else {
|
||||||
|
toast(R.string.toast_failure)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.layoutShare.setOnClickListener {
|
||||||
|
val ret = backupConfiguration(cacheDir.absolutePath)
|
||||||
|
if (ret.first) {
|
||||||
|
startActivity(
|
||||||
|
Intent.createChooser(
|
||||||
|
Intent(Intent.ACTION_SEND).setType("application/zip")
|
||||||
|
.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||||
|
.putExtra(
|
||||||
|
Intent.EXTRA_STREAM, FileProvider.getUriForFile(
|
||||||
|
this, BuildConfig.APPLICATION_ID + ".cache", File(ret.second)
|
||||||
|
)
|
||||||
|
), getString(R.string.title_configuration_share)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
toast(R.string.toast_failure)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
binding.layoutRestore.setOnClickListener {
|
binding.layoutRestore.setOnClickListener {
|
||||||
@@ -77,40 +102,36 @@ class AboutActivity : BaseActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun backupMMKV() {
|
fun backupConfiguration(outputZipFilePos: String): Pair<Boolean, String> {
|
||||||
val dateFormated = SimpleDateFormat(
|
val dateFormated = SimpleDateFormat(
|
||||||
"yyyy-MM-dd-HH-mm-ss",
|
"yyyy-MM-dd-HH-mm-ss",
|
||||||
Locale.getDefault()
|
Locale.getDefault()
|
||||||
).format(System.currentTimeMillis())
|
).format(System.currentTimeMillis())
|
||||||
val folderName = "${getString(R.string.app_name)}_${dateFormated}"
|
val folderName = "${getString(R.string.app_name)}_${dateFormated}"
|
||||||
val backupDir = this.cacheDir.absolutePath + "/$folderName"
|
val backupDir = this.cacheDir.absolutePath + "/$folderName"
|
||||||
val outputZipFilePath = extDir.absolutePath + "/$folderName.zip"
|
val outputZipFilePath = "$outputZipFilePos/$folderName.zip"
|
||||||
|
|
||||||
val count = MMKV.backupAllToDirectory(backupDir)
|
val count = MMKV.backupAllToDirectory(backupDir)
|
||||||
if (count <= 0) {
|
if (count <= 0) {
|
||||||
toast(R.string.toast_failure)
|
return Pair(false, "")
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ZipUtil.zipFromFolder(backupDir, outputZipFilePath)) {
|
if (ZipUtil.zipFromFolder(backupDir, outputZipFilePath)) {
|
||||||
toast(R.string.toast_success)
|
return Pair(true, outputZipFilePath)
|
||||||
} else {
|
} else {
|
||||||
toast(R.string.toast_failure)
|
return Pair(false, "")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun restoreMMKV(zipFile: File) {
|
fun restoreConfiguration(zipFile: File): Boolean {
|
||||||
val backupDir = this.cacheDir.absolutePath + "/${System.currentTimeMillis()}"
|
val backupDir = this.cacheDir.absolutePath + "/${System.currentTimeMillis()}"
|
||||||
|
|
||||||
if (!ZipUtil.unzipToFolder(zipFile, backupDir)) {
|
if (!ZipUtil.unzipToFolder(zipFile, backupDir)) {
|
||||||
toast(R.string.toast_failure)
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
val count = MMKV.restoreAllFromDirectory(backupDir)
|
val count = MMKV.restoreAllFromDirectory(backupDir)
|
||||||
if (count > 0) {
|
return count > 0
|
||||||
toast(R.string.toast_success)
|
|
||||||
} else {
|
|
||||||
toast(R.string.toast_failure)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun showFileChooser() {
|
private fun showFileChooser() {
|
||||||
@@ -138,8 +159,11 @@ class AboutActivity : BaseActivity() {
|
|||||||
input?.copyTo(fileOut)
|
input?.copyTo(fileOut)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (restoreConfiguration(targetFile)) {
|
||||||
restoreMMKV(targetFile)
|
toast(R.string.toast_success)
|
||||||
|
} else {
|
||||||
|
toast(R.string.toast_failure)
|
||||||
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
e.printStackTrace()
|
e.printStackTrace()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import android.net.VpnService
|
|||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.text.TextUtils
|
import android.text.TextUtils
|
||||||
import android.util.Log
|
|
||||||
import android.view.KeyEvent
|
import android.view.KeyEvent
|
||||||
import android.view.Menu
|
import android.view.Menu
|
||||||
import android.view.MenuItem
|
import android.view.MenuItem
|
||||||
@@ -28,9 +27,9 @@ import com.google.android.material.navigation.NavigationView
|
|||||||
import com.tbruyelle.rxpermissions.RxPermissions
|
import com.tbruyelle.rxpermissions.RxPermissions
|
||||||
import com.tencent.mmkv.MMKV
|
import com.tencent.mmkv.MMKV
|
||||||
import com.v2ray.ang.AppConfig
|
import com.v2ray.ang.AppConfig
|
||||||
import com.v2ray.ang.AppConfig.ANG_PACKAGE
|
|
||||||
import com.v2ray.ang.R
|
import com.v2ray.ang.R
|
||||||
import com.v2ray.ang.databinding.ActivityMainBinding
|
import com.v2ray.ang.databinding.ActivityMainBinding
|
||||||
|
import com.v2ray.ang.databinding.LayoutProgressBinding
|
||||||
import com.v2ray.ang.dto.EConfigType
|
import com.v2ray.ang.dto.EConfigType
|
||||||
import com.v2ray.ang.extension.toast
|
import com.v2ray.ang.extension.toast
|
||||||
import com.v2ray.ang.helper.SimpleItemTouchHelperCallback
|
import com.v2ray.ang.helper.SimpleItemTouchHelperCallback
|
||||||
@@ -40,12 +39,11 @@ import com.v2ray.ang.util.MmkvManager
|
|||||||
import com.v2ray.ang.util.Utils
|
import com.v2ray.ang.util.Utils
|
||||||
import com.v2ray.ang.viewmodel.MainViewModel
|
import com.v2ray.ang.viewmodel.MainViewModel
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import me.drakeet.support.toast.ToastCompat
|
import me.drakeet.support.toast.ToastCompat
|
||||||
import rx.Observable
|
import rx.Observable
|
||||||
import rx.android.schedulers.AndroidSchedulers
|
import rx.android.schedulers.AndroidSchedulers
|
||||||
import java.io.File
|
|
||||||
import java.io.FileOutputStream
|
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedListener {
|
class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedListener {
|
||||||
@@ -110,8 +108,7 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
|
|||||||
|
|
||||||
|
|
||||||
setupViewModel()
|
setupViewModel()
|
||||||
copyAssets()
|
mainViewModel.copyAssets(assets)
|
||||||
//migrateLegacy()
|
|
||||||
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||||
RxPermissions(this)
|
RxPermissions(this)
|
||||||
@@ -160,45 +157,6 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
|
|||||||
mainViewModel.startListenBroadcast()
|
mainViewModel.startListenBroadcast()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun copyAssets() {
|
|
||||||
val extFolder = Utils.userAssetPath(this)
|
|
||||||
lifecycleScope.launch(Dispatchers.IO) {
|
|
||||||
try {
|
|
||||||
val geo = arrayOf("geosite.dat", "geoip.dat")
|
|
||||||
assets.list("")
|
|
||||||
?.filter { geo.contains(it) }
|
|
||||||
?.filter { !File(extFolder, it).exists() }
|
|
||||||
?.forEach {
|
|
||||||
val target = File(extFolder, it)
|
|
||||||
assets.open(it).use { input ->
|
|
||||||
FileOutputStream(target).use { output ->
|
|
||||||
input.copyTo(output)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Log.i(ANG_PACKAGE, "Copied from apk assets folder to ${target.absolutePath}")
|
|
||||||
}
|
|
||||||
} catch (e: Exception) {
|
|
||||||
Log.e(ANG_PACKAGE, "asset copy failed", e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// private fun migrateLegacy() {
|
|
||||||
// lifecycleScope.launch(Dispatchers.IO) {
|
|
||||||
// val result = AngConfigManager.migrateLegacyConfig(this@MainActivity)
|
|
||||||
// if (result != null) {
|
|
||||||
// launch(Dispatchers.Main) {
|
|
||||||
// if (result) {
|
|
||||||
// toast(getString(R.string.migration_success))
|
|
||||||
// mainViewModel.reloadServerList()
|
|
||||||
// } else {
|
|
||||||
// toast(getString(R.string.migration_fail))
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
fun startV2Ray() {
|
fun startV2Ray() {
|
||||||
if (mainStorage?.decodeString(MmkvManager.KEY_SELECTED_SERVER).isNullOrEmpty()) {
|
if (mainStorage?.decodeString(MmkvManager.KEY_SELECTED_SERVER).isNullOrEmpty()) {
|
||||||
return
|
return
|
||||||
@@ -281,11 +239,6 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
|
|||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
// R.id.sub_setting -> {
|
|
||||||
// startActivity<SubSettingActivity>()
|
|
||||||
// true
|
|
||||||
// }
|
|
||||||
|
|
||||||
R.id.sub_update -> {
|
R.id.sub_update -> {
|
||||||
importConfigViaSub()
|
importConfigViaSub()
|
||||||
true
|
true
|
||||||
@@ -331,6 +284,7 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
|
|||||||
AlertDialog.Builder(this).setMessage(R.string.del_config_comfirm)
|
AlertDialog.Builder(this).setMessage(R.string.del_config_comfirm)
|
||||||
.setPositiveButton(android.R.string.ok) { _, _ ->
|
.setPositiveButton(android.R.string.ok) { _, _ ->
|
||||||
mainViewModel.removeDuplicateServer()
|
mainViewModel.removeDuplicateServer()
|
||||||
|
mainViewModel.reloadServerList()
|
||||||
}
|
}
|
||||||
.setNegativeButton(android.R.string.no) {_, _ ->
|
.setNegativeButton(android.R.string.no) {_, _ ->
|
||||||
//do noting
|
//do noting
|
||||||
@@ -375,7 +329,7 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
|
|||||||
/**
|
/**
|
||||||
* import config from qrcode
|
* import config from qrcode
|
||||||
*/
|
*/
|
||||||
fun importQRcode(forConfig: Boolean): Boolean {
|
private fun importQRcode(forConfig: Boolean): Boolean {
|
||||||
// try {
|
// try {
|
||||||
// startActivityForResult(Intent("com.google.zxing.client.android.SCAN")
|
// startActivityForResult(Intent("com.google.zxing.client.android.SCAN")
|
||||||
// .addCategory(Intent.CATEGORY_DEFAULT)
|
// .addCategory(Intent.CATEGORY_DEFAULT)
|
||||||
@@ -411,7 +365,7 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
|
|||||||
/**
|
/**
|
||||||
* import config from clipboard
|
* import config from clipboard
|
||||||
*/
|
*/
|
||||||
fun importClipboard()
|
private fun importClipboard()
|
||||||
: Boolean {
|
: Boolean {
|
||||||
try {
|
try {
|
||||||
val clipboard = Utils.getClipboard(this)
|
val clipboard = Utils.getClipboard(this)
|
||||||
@@ -423,30 +377,28 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
fun importBatchConfig(server: String?, subid: String = "") {
|
private fun importBatchConfig(server: String?) {
|
||||||
val subid2 = if(subid.isNullOrEmpty()){
|
val dialog = AlertDialog.Builder(this)
|
||||||
mainViewModel.subscriptionId
|
.setView(LayoutProgressBinding.inflate(layoutInflater).root)
|
||||||
}else{
|
.setCancelable(false)
|
||||||
subid
|
.show()
|
||||||
}
|
|
||||||
val append = subid.isNullOrEmpty()
|
|
||||||
|
|
||||||
var count = AngConfigManager.importBatchConfig(server, subid2, append)
|
lifecycleScope.launch(Dispatchers.IO) {
|
||||||
if (count <= 0) {
|
val count = AngConfigManager.importBatchConfig(server, mainViewModel.subscriptionId, true)
|
||||||
count = AngConfigManager.importBatchConfig(Utils.decode(server!!), subid2, append)
|
delay(500L)
|
||||||
}
|
launch(Dispatchers.Main) {
|
||||||
if (count <= 0) {
|
if (count > 0) {
|
||||||
count = AngConfigManager.appendCustomConfigServer(server, subid2)
|
toast(R.string.toast_success)
|
||||||
}
|
mainViewModel.reloadServerList()
|
||||||
if (count > 0) {
|
} else {
|
||||||
toast(R.string.toast_success)
|
toast(R.string.toast_failure)
|
||||||
mainViewModel.reloadServerList()
|
}
|
||||||
} else {
|
dialog.dismiss()
|
||||||
toast(R.string.toast_failure)
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun importConfigCustomClipboard()
|
private fun importConfigCustomClipboard()
|
||||||
: Boolean {
|
: Boolean {
|
||||||
try {
|
try {
|
||||||
val configText = Utils.getClipboard(this)
|
val configText = Utils.getClipboard(this)
|
||||||
@@ -465,7 +417,7 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
|
|||||||
/**
|
/**
|
||||||
* import config from local config file
|
* import config from local config file
|
||||||
*/
|
*/
|
||||||
fun importConfigCustomLocal(): Boolean {
|
private fun importConfigCustomLocal(): Boolean {
|
||||||
try {
|
try {
|
||||||
showFileChooser()
|
showFileChooser()
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
@@ -475,7 +427,7 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
fun importConfigCustomUrlClipboard()
|
private fun importConfigCustomUrlClipboard()
|
||||||
: Boolean {
|
: Boolean {
|
||||||
try {
|
try {
|
||||||
val url = Utils.getClipboard(this)
|
val url = Utils.getClipboard(this)
|
||||||
@@ -493,7 +445,7 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
|
|||||||
/**
|
/**
|
||||||
* import config from url
|
* import config from url
|
||||||
*/
|
*/
|
||||||
fun importConfigCustomUrl(url: String?): Boolean {
|
private fun importConfigCustomUrl(url: String?): Boolean {
|
||||||
try {
|
try {
|
||||||
if (!Utils.isValidUrl(url)) {
|
if (!Utils.isValidUrl(url)) {
|
||||||
toast(R.string.toast_invalid_url)
|
toast(R.string.toast_invalid_url)
|
||||||
@@ -520,43 +472,24 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
|
|||||||
/**
|
/**
|
||||||
* import config from sub
|
* import config from sub
|
||||||
*/
|
*/
|
||||||
fun importConfigViaSub()
|
private fun importConfigViaSub() : Boolean {
|
||||||
: Boolean {
|
val dialog = AlertDialog.Builder(this)
|
||||||
try {
|
.setView(LayoutProgressBinding.inflate(layoutInflater).root)
|
||||||
toast(R.string.title_sub_update)
|
.setCancelable(false)
|
||||||
MmkvManager.decodeSubscriptions().forEach {
|
.show()
|
||||||
if (TextUtils.isEmpty(it.first)
|
|
||||||
|| TextUtils.isEmpty(it.second.remarks)
|
lifecycleScope.launch(Dispatchers.IO) {
|
||||||
|| TextUtils.isEmpty(it.second.url)
|
val count = AngConfigManager.updateConfigViaSubAll()
|
||||||
) {
|
delay(500L)
|
||||||
return@forEach
|
launch(Dispatchers.Main) {
|
||||||
}
|
if (count > 0) {
|
||||||
if (!it.second.enabled) {
|
toast(R.string.toast_success)
|
||||||
return@forEach
|
mainViewModel.reloadServerList()
|
||||||
}
|
} else {
|
||||||
val url = Utils.idnToASCII(it.second.url)
|
toast(R.string.toast_failure)
|
||||||
if (!Utils.isValidUrl(url)) {
|
|
||||||
return@forEach
|
|
||||||
}
|
|
||||||
Log.d(ANG_PACKAGE, url)
|
|
||||||
lifecycleScope.launch(Dispatchers.IO) {
|
|
||||||
val configText = try {
|
|
||||||
Utils.getUrlContentWithCustomUserAgent(url)
|
|
||||||
} catch (e: Exception) {
|
|
||||||
e.printStackTrace()
|
|
||||||
launch(Dispatchers.Main) {
|
|
||||||
toast("\"" + it.second.remarks + "\" " + getString(R.string.toast_failure))
|
|
||||||
}
|
|
||||||
return@launch
|
|
||||||
}
|
|
||||||
launch(Dispatchers.Main) {
|
|
||||||
importBatchConfig(configText, it.first)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
dialog.dismiss()
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
|
||||||
e.printStackTrace()
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@@ -611,15 +544,18 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
|
|||||||
/**
|
/**
|
||||||
* import customize config
|
* import customize config
|
||||||
*/
|
*/
|
||||||
fun importCustomizeConfig(server: String?) {
|
private fun importCustomizeConfig(server: String?) {
|
||||||
try {
|
try {
|
||||||
if (server == null || TextUtils.isEmpty(server)) {
|
if (server == null || TextUtils.isEmpty(server)) {
|
||||||
toast(R.string.toast_none_data)
|
toast(R.string.toast_none_data)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
mainViewModel.appendCustomConfigServer(server)
|
if (mainViewModel.appendCustomConfigServer(server)) {
|
||||||
mainViewModel.reloadServerList()
|
mainViewModel.reloadServerList()
|
||||||
toast(R.string.toast_success)
|
toast(R.string.toast_success)
|
||||||
|
} else {
|
||||||
|
toast(R.string.toast_failure)
|
||||||
|
}
|
||||||
//adapter.notifyItemInserted(mainViewModel.serverList.lastIndex)
|
//adapter.notifyItemInserted(mainViewModel.serverList.lastIndex)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
ToastCompat.makeText(this, "${getString(R.string.toast_malformed_josn)} ${e.cause?.message}", Toast.LENGTH_LONG).show()
|
ToastCompat.makeText(this, "${getString(R.string.toast_malformed_josn)} ${e.cause?.message}", Toast.LENGTH_LONG).show()
|
||||||
@@ -628,7 +564,7 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setTestState(content: String?) {
|
private fun setTestState(content: String?) {
|
||||||
binding.tvTestState.text = content
|
binding.tvTestState.text = content
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -67,15 +67,15 @@ class MainRecyclerAdapter(val activity: MainActivity) : RecyclerView.Adapter<Mai
|
|||||||
holder.itemMainBinding.tvName.text = config.remarks
|
holder.itemMainBinding.tvName.text = config.remarks
|
||||||
holder.itemView.setBackgroundColor(Color.TRANSPARENT)
|
holder.itemView.setBackgroundColor(Color.TRANSPARENT)
|
||||||
holder.itemMainBinding.tvTestResult.text = aff?.getTestDelayString() ?: ""
|
holder.itemMainBinding.tvTestResult.text = aff?.getTestDelayString() ?: ""
|
||||||
if (aff?.testDelayMillis ?: 0L < 0L) {
|
if ((aff?.testDelayMillis ?: 0L) < 0L) {
|
||||||
holder.itemMainBinding.tvTestResult.setTextColor(ContextCompat.getColor(mActivity, R.color.colorPingRed))
|
holder.itemMainBinding.tvTestResult.setTextColor(ContextCompat.getColor(mActivity, R.color.colorPingRed))
|
||||||
} else {
|
} else {
|
||||||
holder.itemMainBinding.tvTestResult.setTextColor(ContextCompat.getColor(mActivity, R.color.colorPing))
|
holder.itemMainBinding.tvTestResult.setTextColor(ContextCompat.getColor(mActivity, R.color.colorPing))
|
||||||
}
|
}
|
||||||
if (guid == mainStorage?.decodeString(MmkvManager.KEY_SELECTED_SERVER)) {
|
if (guid == mainStorage?.decodeString(MmkvManager.KEY_SELECTED_SERVER)) {
|
||||||
holder.itemMainBinding.layoutIndicator.setBackgroundResource(R.color.colorSelected)
|
holder.itemMainBinding.layoutIndicator.setBackgroundResource(R.color.colorAccent)
|
||||||
} else {
|
} else {
|
||||||
holder.itemMainBinding.layoutIndicator.setBackgroundResource(R.color.colorUnselected)
|
holder.itemMainBinding.layoutIndicator.setBackgroundResource(0)
|
||||||
}
|
}
|
||||||
holder.itemMainBinding.tvSubscription.text = ""
|
holder.itemMainBinding.tvSubscription.text = ""
|
||||||
val json = subStorage?.decodeString(config.subscriptionId)
|
val json = subStorage?.decodeString(config.subscriptionId)
|
||||||
@@ -97,7 +97,13 @@ class MainRecyclerAdapter(val activity: MainActivity) : RecyclerView.Adapter<Mai
|
|||||||
holder.itemMainBinding.tvType.text = config.configType.name.lowercase()
|
holder.itemMainBinding.tvType.text = config.configType.name.lowercase()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val strState = "${outbound?.getServerAddress()?.dropLast(3)}*** : ${outbound?.getServerPort()}"
|
|
||||||
|
val strState = try{
|
||||||
|
"${outbound?.getServerAddress()?.dropLast(3)}*** : ${outbound?.getServerPort()}"
|
||||||
|
}catch(e: Exception){
|
||||||
|
""
|
||||||
|
}
|
||||||
|
|
||||||
holder.itemMainBinding.tvStatistics.text = strState
|
holder.itemMainBinding.tvStatistics.text = strState
|
||||||
|
|
||||||
holder.itemMainBinding.layoutShare.setOnClickListener {
|
holder.itemMainBinding.layoutShare.setOnClickListener {
|
||||||
@@ -162,7 +168,7 @@ class MainRecyclerAdapter(val activity: MainActivity) : RecyclerView.Adapter<Mai
|
|||||||
if (guid != selected) {
|
if (guid != selected) {
|
||||||
mainStorage?.encode(MmkvManager.KEY_SELECTED_SERVER, guid)
|
mainStorage?.encode(MmkvManager.KEY_SELECTED_SERVER, guid)
|
||||||
if (!TextUtils.isEmpty(selected)) {
|
if (!TextUtils.isEmpty(selected)) {
|
||||||
notifyItemChanged(mActivity.mainViewModel.getPosition(selected!!))
|
notifyItemChanged(mActivity.mainViewModel.getPosition(selected?:""))
|
||||||
}
|
}
|
||||||
notifyItemChanged(mActivity.mainViewModel.getPosition(guid))
|
notifyItemChanged(mActivity.mainViewModel.getPosition(guid))
|
||||||
if (isRunning) {
|
if (isRunning) {
|
||||||
|
|||||||
@@ -193,7 +193,7 @@ class PerAppProxyActivity : BaseActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onQueryTextChange(newText: String?): Boolean {
|
override fun onQueryTextChange(newText: String?): Boolean {
|
||||||
filterProxyApp(newText!!)
|
filterProxyApp(newText?:"")
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -209,12 +209,12 @@ class PerAppProxyActivity : BaseActivity() {
|
|||||||
if (it.blacklist.containsAll(pkgNames)) {
|
if (it.blacklist.containsAll(pkgNames)) {
|
||||||
it.apps.forEach {
|
it.apps.forEach {
|
||||||
val packageName = it.packageName
|
val packageName = it.packageName
|
||||||
adapter?.blacklist!!.remove(packageName)
|
adapter?.blacklist?.remove(packageName)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
it.apps.forEach {
|
it.apps.forEach {
|
||||||
val packageName = it.packageName
|
val packageName = it.packageName
|
||||||
adapter?.blacklist!!.add(packageName)
|
adapter?.blacklist?.add(packageName)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
it.notifyDataSetChanged()
|
it.notifyDataSetChanged()
|
||||||
@@ -278,7 +278,7 @@ class PerAppProxyActivity : BaseActivity() {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
adapter?.blacklist!!.clear()
|
adapter?.blacklist?.clear()
|
||||||
|
|
||||||
if (binding.switchBypassApps.isChecked) {
|
if (binding.switchBypassApps.isChecked) {
|
||||||
adapter?.let {
|
adapter?.let {
|
||||||
@@ -286,7 +286,7 @@ class PerAppProxyActivity : BaseActivity() {
|
|||||||
val packageName = it.packageName
|
val packageName = it.packageName
|
||||||
Log.d(ANG_PACKAGE, packageName)
|
Log.d(ANG_PACKAGE, packageName)
|
||||||
if (!inProxyApps(proxyApps, packageName, force)) {
|
if (!inProxyApps(proxyApps, packageName, force)) {
|
||||||
adapter?.blacklist!!.add(packageName)
|
adapter?.blacklist?.add(packageName)
|
||||||
println(packageName)
|
println(packageName)
|
||||||
return@block
|
return@block
|
||||||
}
|
}
|
||||||
@@ -299,7 +299,7 @@ class PerAppProxyActivity : BaseActivity() {
|
|||||||
val packageName = it.packageName
|
val packageName = it.packageName
|
||||||
Log.d(ANG_PACKAGE, packageName)
|
Log.d(ANG_PACKAGE, packageName)
|
||||||
if (inProxyApps(proxyApps, packageName, force)) {
|
if (inProxyApps(proxyApps, packageName, force)) {
|
||||||
adapter?.blacklist!!.add(packageName)
|
adapter?.blacklist?.add(packageName)
|
||||||
println(packageName)
|
println(packageName)
|
||||||
return@block
|
return@block
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ class RoutingSettingsFragment : Fragment() {
|
|||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
|
||||||
val content = settingsStorage?.getString(requireArguments().getString(routing_arg), "")
|
val content = settingsStorage?.getString(requireArguments().getString(routing_arg), "")
|
||||||
binding.etRoutingContent.text = Utils.getEditable(content!!)
|
binding.etRoutingContent.text = Utils.getEditable(content)
|
||||||
|
|
||||||
setHasOptionsMenu(true)
|
setHasOptionsMenu(true)
|
||||||
}
|
}
|
||||||
@@ -113,7 +113,7 @@ class RoutingSettingsFragment : Fragment() {
|
|||||||
private val scanQRCodeForReplace = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
|
private val scanQRCodeForReplace = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
|
||||||
if (it.resultCode == RESULT_OK) {
|
if (it.resultCode == RESULT_OK) {
|
||||||
val content = it.data?.getStringExtra("SCAN_RESULT")
|
val content = it.data?.getStringExtra("SCAN_RESULT")
|
||||||
binding.etRoutingContent.text = Utils.getEditable(content!!)
|
binding.etRoutingContent.text = Utils.getEditable(content)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -145,7 +145,7 @@ class RoutingSettingsFragment : Fragment() {
|
|||||||
val content = Utils.getUrlContext(url, 5000)
|
val content = Utils.getUrlContext(url, 5000)
|
||||||
launch(Dispatchers.Main) {
|
launch(Dispatchers.Main) {
|
||||||
val routingList = if (TextUtils.isEmpty(content)) {
|
val routingList = if (TextUtils.isEmpty(content)) {
|
||||||
Utils.readTextFromAssets(activity?.v2RayApplication!!, "custom_routing_$tag")
|
Utils.readTextFromAssets(activity?.v2RayApplication, "custom_routing_$tag")
|
||||||
} else {
|
} else {
|
||||||
content
|
content
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ class ScannerActivity : BaseActivity(){
|
|||||||
|
|
||||||
private fun handleResult(result: QRResult) {
|
private fun handleResult(result: QRResult) {
|
||||||
if (result is QRResult.QRSuccess ) {
|
if (result is QRResult.QRSuccess ) {
|
||||||
finished(result.content.rawValue!!)
|
finished(result.content.rawValue?:"")
|
||||||
} else {
|
} else {
|
||||||
finish()
|
finish()
|
||||||
}
|
}
|
||||||
@@ -110,7 +110,7 @@ class ScannerActivity : BaseActivity(){
|
|||||||
try {
|
try {
|
||||||
val bitmap = BitmapFactory.decodeStream(contentResolver.openInputStream(uri))
|
val bitmap = BitmapFactory.decodeStream(contentResolver.openInputStream(uri))
|
||||||
val text = QRCodeDecoder.syncDecodeQRCode(bitmap)
|
val text = QRCodeDecoder.syncDecodeQRCode(bitmap)
|
||||||
finished(text!!)
|
finished(text?:"")
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
e.printStackTrace()
|
e.printStackTrace()
|
||||||
toast(e.message.toString())
|
toast(e.message.toString())
|
||||||
|
|||||||
@@ -5,10 +5,14 @@ import android.text.TextUtils
|
|||||||
import android.view.Menu
|
import android.view.Menu
|
||||||
import android.view.MenuItem
|
import android.view.MenuItem
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.widget.*
|
import android.widget.AdapterView
|
||||||
|
import android.widget.ArrayAdapter
|
||||||
|
import android.widget.EditText
|
||||||
|
import android.widget.LinearLayout
|
||||||
|
import android.widget.Spinner
|
||||||
|
import android.widget.TextView
|
||||||
import androidx.appcompat.app.AlertDialog
|
import androidx.appcompat.app.AlertDialog
|
||||||
import com.tencent.mmkv.MMKV
|
import com.tencent.mmkv.MMKV
|
||||||
import com.v2ray.ang.AngApplication
|
|
||||||
import com.v2ray.ang.AppConfig
|
import com.v2ray.ang.AppConfig
|
||||||
import com.v2ray.ang.AppConfig.PREF_ALLOW_INSECURE
|
import com.v2ray.ang.AppConfig.PREF_ALLOW_INSECURE
|
||||||
import com.v2ray.ang.AppConfig.WIREGUARD_LOCAL_ADDRESS_V4
|
import com.v2ray.ang.AppConfig.WIREGUARD_LOCAL_ADDRESS_V4
|
||||||
@@ -106,7 +110,9 @@ class ServerActivity : BaseActivity() {
|
|||||||
private val sp_network: Spinner? by lazy { findViewById(R.id.sp_network) }
|
private val sp_network: Spinner? by lazy { findViewById(R.id.sp_network) }
|
||||||
private val sp_header_type: Spinner? by lazy { findViewById(R.id.sp_header_type) }
|
private val sp_header_type: Spinner? by lazy { findViewById(R.id.sp_header_type) }
|
||||||
private val sp_header_type_title: TextView? by lazy { findViewById(R.id.sp_header_type_title) }
|
private val sp_header_type_title: TextView? by lazy { findViewById(R.id.sp_header_type_title) }
|
||||||
|
private val tv_request_host: TextView? by lazy { findViewById(R.id.tv_request_host) }
|
||||||
private val et_request_host: EditText? by lazy { findViewById(R.id.et_request_host) }
|
private val et_request_host: EditText? by lazy { findViewById(R.id.et_request_host) }
|
||||||
|
private val tv_path: TextView? by lazy { findViewById(R.id.tv_path) }
|
||||||
private val et_path: EditText? by lazy { findViewById(R.id.et_path) }
|
private val et_path: EditText? by lazy { findViewById(R.id.et_path) }
|
||||||
private val sp_stream_alpn: Spinner? by lazy { findViewById(R.id.sp_stream_alpn) } //uTLS
|
private val sp_stream_alpn: Spinner? by lazy { findViewById(R.id.sp_stream_alpn) } //uTLS
|
||||||
private val container_alpn: LinearLayout? by lazy { findViewById(R.id.l4) }
|
private val container_alpn: LinearLayout? by lazy { findViewById(R.id.l4) }
|
||||||
@@ -158,6 +164,36 @@ class ServerActivity : BaseActivity() {
|
|||||||
et_request_host?.text = Utils.getEditable(transportDetails[1])
|
et_request_host?.text = Utils.getEditable(transportDetails[1])
|
||||||
et_path?.text = Utils.getEditable(transportDetails[2])
|
et_path?.text = Utils.getEditable(transportDetails[2])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
tv_request_host?.text = Utils.getEditable(
|
||||||
|
getString(
|
||||||
|
when (networks[position]) {
|
||||||
|
"tcp" -> R.string.server_lab_request_host_http
|
||||||
|
"ws" -> R.string.server_lab_request_host_ws
|
||||||
|
"httpupgrade" -> R.string.server_lab_request_host_httpupgrade
|
||||||
|
"splithttp" -> R.string.server_lab_request_host_splithttp
|
||||||
|
"h2" -> R.string.server_lab_request_host_h2
|
||||||
|
"quic" -> R.string.server_lab_request_host_quic
|
||||||
|
"grpc" -> R.string.server_lab_request_host_grpc
|
||||||
|
else -> R.string.server_lab_request_host
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
tv_path?.text = Utils.getEditable(
|
||||||
|
getString(
|
||||||
|
when (networks[position]) {
|
||||||
|
"kcp" -> R.string.server_lab_path_kcp
|
||||||
|
"ws" -> R.string.server_lab_path_ws
|
||||||
|
"httpupgrade" -> R.string.server_lab_path_httpupgrade
|
||||||
|
"splithttp" -> R.string.server_lab_path_splithttp
|
||||||
|
"h2" -> R.string.server_lab_path_h2
|
||||||
|
"quic" -> R.string.server_lab_path_quic
|
||||||
|
"grpc" -> R.string.server_lab_path_grpc
|
||||||
|
else -> R.string.server_lab_path
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onNothingSelected(parent: AdapterView<*>?) {
|
override fun onNothingSelected(parent: AdapterView<*>?) {
|
||||||
@@ -286,7 +322,7 @@ class ServerActivity : BaseActivity() {
|
|||||||
tlsSetting.alpn?.let {
|
tlsSetting.alpn?.let {
|
||||||
val alpnIndex = Utils.arrayFind(
|
val alpnIndex = Utils.arrayFind(
|
||||||
alpns,
|
alpns,
|
||||||
Utils.removeWhiteSpace(tlsSetting.alpn.joinToString())!!
|
Utils.removeWhiteSpace(tlsSetting.alpn.joinToString())?:""
|
||||||
)
|
)
|
||||||
sp_stream_alpn?.setSelection(alpnIndex)
|
sp_stream_alpn?.setSelection(alpnIndex)
|
||||||
}
|
}
|
||||||
@@ -414,7 +450,7 @@ class ServerActivity : BaseActivity() {
|
|||||||
saveStreamSettings(it)
|
saveStreamSettings(it)
|
||||||
}
|
}
|
||||||
if (config.subscriptionId.isEmpty() && !subscriptionId.isNullOrEmpty()) {
|
if (config.subscriptionId.isEmpty() && !subscriptionId.isNullOrEmpty()) {
|
||||||
config.subscriptionId = subscriptionId!!
|
config.subscriptionId = subscriptionId?:""
|
||||||
}
|
}
|
||||||
|
|
||||||
MmkvManager.encodeServerConfig(editGuid, config)
|
MmkvManager.encodeServerConfig(editGuid, config)
|
||||||
|
|||||||
@@ -63,6 +63,7 @@ class SettingsActivity : BaseActivity() {
|
|||||||
private val httpPort by lazy { findPreference<EditTextPreference>(AppConfig.PREF_HTTP_PORT) }
|
private val httpPort by lazy { findPreference<EditTextPreference>(AppConfig.PREF_HTTP_PORT) }
|
||||||
private val remoteDns by lazy { findPreference<EditTextPreference>(AppConfig.PREF_REMOTE_DNS) }
|
private val remoteDns by lazy { findPreference<EditTextPreference>(AppConfig.PREF_REMOTE_DNS) }
|
||||||
private val domesticDns by lazy { findPreference<EditTextPreference>(AppConfig.PREF_DOMESTIC_DNS) }
|
private val domesticDns by lazy { findPreference<EditTextPreference>(AppConfig.PREF_DOMESTIC_DNS) }
|
||||||
|
private val delayTestUrl by lazy { findPreference<EditTextPreference>(AppConfig.PREF_DELAY_TEST_URL) }
|
||||||
private val mode by lazy { findPreference<ListPreference>(AppConfig.PREF_MODE) }
|
private val mode by lazy { findPreference<ListPreference>(AppConfig.PREF_MODE) }
|
||||||
|
|
||||||
override fun onCreatePreferences(bundle: Bundle?, s: String?) {
|
override fun onCreatePreferences(bundle: Bundle?, s: String?) {
|
||||||
@@ -143,18 +144,6 @@ class SettingsActivity : BaseActivity() {
|
|||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
remoteDns?.setOnPreferenceChangeListener { _, any ->
|
|
||||||
// remoteDns.summary = any as String
|
|
||||||
val nval = any as String
|
|
||||||
remoteDns?.summary = if (nval == "") AppConfig.DNS_PROXY else nval
|
|
||||||
true
|
|
||||||
}
|
|
||||||
domesticDns?.setOnPreferenceChangeListener { _, any ->
|
|
||||||
// domesticDns.summary = any as String
|
|
||||||
val nval = any as String
|
|
||||||
domesticDns?.summary = if (nval == "") AppConfig.DNS_DIRECT else nval
|
|
||||||
true
|
|
||||||
}
|
|
||||||
socksPort?.setOnPreferenceChangeListener { _, any ->
|
socksPort?.setOnPreferenceChangeListener { _, any ->
|
||||||
val nval = any as String
|
val nval = any as String
|
||||||
socksPort?.summary = if (TextUtils.isEmpty(nval)) AppConfig.PORT_SOCKS else nval
|
socksPort?.summary = if (TextUtils.isEmpty(nval)) AppConfig.PORT_SOCKS else nval
|
||||||
@@ -165,6 +154,21 @@ class SettingsActivity : BaseActivity() {
|
|||||||
httpPort?.summary = if (TextUtils.isEmpty(nval)) AppConfig.PORT_HTTP else nval
|
httpPort?.summary = if (TextUtils.isEmpty(nval)) AppConfig.PORT_HTTP else nval
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
remoteDns?.setOnPreferenceChangeListener { _, any ->
|
||||||
|
val nval = any as String
|
||||||
|
remoteDns?.summary = if (nval == "") AppConfig.DNS_PROXY else nval
|
||||||
|
true
|
||||||
|
}
|
||||||
|
domesticDns?.setOnPreferenceChangeListener { _, any ->
|
||||||
|
val nval = any as String
|
||||||
|
domesticDns?.summary = if (nval == "") AppConfig.DNS_DIRECT else nval
|
||||||
|
true
|
||||||
|
}
|
||||||
|
delayTestUrl?.setOnPreferenceChangeListener { _, any ->
|
||||||
|
val nval = any as String
|
||||||
|
delayTestUrl?.summary = if (nval == "") AppConfig.DelayTestUrl else nval
|
||||||
|
true
|
||||||
|
}
|
||||||
mode?.setOnPreferenceChangeListener { _, newValue ->
|
mode?.setOnPreferenceChangeListener { _, newValue ->
|
||||||
updateMode(newValue.toString())
|
updateMode(newValue.toString())
|
||||||
true
|
true
|
||||||
@@ -201,6 +205,7 @@ class SettingsActivity : BaseActivity() {
|
|||||||
httpPort?.summary = settingsStorage.decodeString(AppConfig.PREF_HTTP_PORT, AppConfig.PORT_HTTP)
|
httpPort?.summary = settingsStorage.decodeString(AppConfig.PREF_HTTP_PORT, AppConfig.PORT_HTTP)
|
||||||
remoteDns?.summary = settingsStorage.decodeString(AppConfig.PREF_REMOTE_DNS, AppConfig.DNS_PROXY)
|
remoteDns?.summary = settingsStorage.decodeString(AppConfig.PREF_REMOTE_DNS, AppConfig.DNS_PROXY)
|
||||||
domesticDns?.summary = settingsStorage.decodeString(AppConfig.PREF_DOMESTIC_DNS, AppConfig.DNS_DIRECT)
|
domesticDns?.summary = settingsStorage.decodeString(AppConfig.PREF_DOMESTIC_DNS, AppConfig.DNS_DIRECT)
|
||||||
|
delayTestUrl?.summary = settingsStorage.decodeString(AppConfig.PREF_DELAY_TEST_URL, AppConfig.DelayTestUrl)
|
||||||
|
|
||||||
initSharedPreference()
|
initSharedPreference()
|
||||||
}
|
}
|
||||||
@@ -217,7 +222,8 @@ class SettingsActivity : BaseActivity() {
|
|||||||
socksPort,
|
socksPort,
|
||||||
httpPort,
|
httpPort,
|
||||||
remoteDns,
|
remoteDns,
|
||||||
domesticDns
|
domesticDns,
|
||||||
|
delayTestUrl
|
||||||
).forEach { key ->
|
).forEach { key ->
|
||||||
key?.text = key?.summary.toString()
|
key?.text = key?.summary.toString()
|
||||||
}
|
}
|
||||||
@@ -230,6 +236,7 @@ class SettingsActivity : BaseActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
listOf(
|
listOf(
|
||||||
|
AppConfig.PREF_ROUTE_ONLY_ENABLED,
|
||||||
AppConfig.PREF_BYPASS_APPS,
|
AppConfig.PREF_BYPASS_APPS,
|
||||||
AppConfig.PREF_SPEED_ENABLED,
|
AppConfig.PREF_SPEED_ENABLED,
|
||||||
AppConfig.PREF_CONFIRM_REMOVE,
|
AppConfig.PREF_CONFIRM_REMOVE,
|
||||||
|
|||||||
@@ -1,19 +1,27 @@
|
|||||||
package com.v2ray.ang.ui
|
package com.v2ray.ang.ui
|
||||||
|
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import android.os.Bundle
|
||||||
import android.view.Menu
|
import android.view.Menu
|
||||||
import android.view.MenuItem
|
import android.view.MenuItem
|
||||||
|
import androidx.appcompat.app.AlertDialog
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import com.v2ray.ang.R
|
import com.v2ray.ang.R
|
||||||
import android.os.Bundle
|
|
||||||
import com.v2ray.ang.databinding.ActivitySubSettingBinding
|
import com.v2ray.ang.databinding.ActivitySubSettingBinding
|
||||||
|
import com.v2ray.ang.databinding.LayoutProgressBinding
|
||||||
import com.v2ray.ang.dto.SubscriptionItem
|
import com.v2ray.ang.dto.SubscriptionItem
|
||||||
|
import com.v2ray.ang.extension.toast
|
||||||
|
import com.v2ray.ang.util.AngConfigManager
|
||||||
import com.v2ray.ang.util.MmkvManager
|
import com.v2ray.ang.util.MmkvManager
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
class SubSettingActivity : BaseActivity() {
|
class SubSettingActivity : BaseActivity() {
|
||||||
private lateinit var binding: ActivitySubSettingBinding
|
private lateinit var binding: ActivitySubSettingBinding
|
||||||
|
|
||||||
var subscriptions:List<Pair<String, SubscriptionItem>> = listOf()
|
var subscriptions: List<Pair<String, SubscriptionItem>> = listOf()
|
||||||
private val adapter by lazy { SubSettingRecyclerAdapter(this) }
|
private val adapter by lazy { SubSettingRecyclerAdapter(this) }
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
@@ -37,9 +45,6 @@ class SubSettingActivity : BaseActivity() {
|
|||||||
|
|
||||||
override fun onCreateOptionsMenu(menu: Menu): Boolean {
|
override fun onCreateOptionsMenu(menu: Menu): Boolean {
|
||||||
menuInflater.inflate(R.menu.action_sub_setting, menu)
|
menuInflater.inflate(R.menu.action_sub_setting, menu)
|
||||||
menu.findItem(R.id.del_config)?.isVisible = false
|
|
||||||
menu.findItem(R.id.save_config)?.isVisible = false
|
|
||||||
|
|
||||||
return super.onCreateOptionsMenu(menu)
|
return super.onCreateOptionsMenu(menu)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -48,6 +53,30 @@ class SubSettingActivity : BaseActivity() {
|
|||||||
startActivity(Intent(this, SubEditActivity::class.java))
|
startActivity(Intent(this, SubEditActivity::class.java))
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
R.id.sub_update -> {
|
||||||
|
val dialog = AlertDialog.Builder(this)
|
||||||
|
.setView(LayoutProgressBinding.inflate(layoutInflater).root)
|
||||||
|
.setCancelable(false)
|
||||||
|
.show()
|
||||||
|
|
||||||
|
lifecycleScope.launch(Dispatchers.IO) {
|
||||||
|
val count = AngConfigManager.updateConfigViaSubAll()
|
||||||
|
delay(500L)
|
||||||
|
launch(Dispatchers.Main) {
|
||||||
|
if (count > 0) {
|
||||||
|
toast(R.string.toast_success)
|
||||||
|
} else {
|
||||||
|
toast(R.string.toast_failure)
|
||||||
|
}
|
||||||
|
dialog.dismiss()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
else -> super.onOptionsItemSelected(item)
|
else -> super.onOptionsItemSelected(item)
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,17 +5,15 @@ import android.graphics.Color
|
|||||||
import android.text.TextUtils
|
import android.text.TextUtils
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import androidx.appcompat.app.AlertDialog
|
import androidx.appcompat.app.AlertDialog
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import com.google.gson.Gson
|
import com.google.gson.Gson
|
||||||
import com.tencent.mmkv.MMKV
|
import com.tencent.mmkv.MMKV
|
||||||
import com.v2ray.ang.R
|
import com.v2ray.ang.R
|
||||||
import com.v2ray.ang.databinding.ItemQrcodeBinding
|
import com.v2ray.ang.databinding.ItemQrcodeBinding
|
||||||
import com.v2ray.ang.databinding.ItemRecyclerSubSettingBinding
|
import com.v2ray.ang.databinding.ItemRecyclerSubSettingBinding
|
||||||
import com.v2ray.ang.dto.EConfigType
|
|
||||||
import com.v2ray.ang.extension.toast
|
import com.v2ray.ang.extension.toast
|
||||||
import com.v2ray.ang.util.AngConfigManager
|
|
||||||
import com.v2ray.ang.util.MmkvManager
|
import com.v2ray.ang.util.MmkvManager
|
||||||
import com.v2ray.ang.util.QRCodeDecoder
|
import com.v2ray.ang.util.QRCodeDecoder
|
||||||
import com.v2ray.ang.util.Utils
|
import com.v2ray.ang.util.Utils
|
||||||
@@ -38,9 +36,9 @@ class SubSettingRecyclerAdapter(val activity: SubSettingActivity) :
|
|||||||
holder.itemSubSettingBinding.tvName.text = subItem.remarks
|
holder.itemSubSettingBinding.tvName.text = subItem.remarks
|
||||||
holder.itemSubSettingBinding.tvUrl.text = subItem.url
|
holder.itemSubSettingBinding.tvUrl.text = subItem.url
|
||||||
if (subItem.enabled) {
|
if (subItem.enabled) {
|
||||||
holder.itemSubSettingBinding.chkEnable.setBackgroundResource(R.color.colorSelected)
|
holder.itemSubSettingBinding.chkEnable.setBackgroundResource(R.color.colorAccent)
|
||||||
} else {
|
} else {
|
||||||
holder.itemSubSettingBinding.chkEnable.setBackgroundResource(R.color.colorUnselected)
|
holder.itemSubSettingBinding.chkEnable.setBackgroundResource(0)
|
||||||
}
|
}
|
||||||
holder.itemView.setBackgroundColor(Color.TRANSPARENT)
|
holder.itemView.setBackgroundColor(Color.TRANSPARENT)
|
||||||
|
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ class TaskerActivity : BaseActivity() {
|
|||||||
val adapter = ArrayAdapter(this,
|
val adapter = ArrayAdapter(this,
|
||||||
android.R.layout.simple_list_item_single_choice, lstData)
|
android.R.layout.simple_list_item_single_choice, lstData)
|
||||||
listview = findViewById<View>(R.id.listview) as ListView
|
listview = findViewById<View>(R.id.listview) as ListView
|
||||||
listview!!.adapter = adapter
|
listview?.adapter = adapter
|
||||||
|
|
||||||
init()
|
init()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ package com.v2ray.ang.ui
|
|||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import com.v2ray.ang.AppConfig
|
import android.util.Log
|
||||||
import com.v2ray.ang.R
|
import com.v2ray.ang.R
|
||||||
import com.v2ray.ang.databinding.ActivityLogcatBinding
|
import com.v2ray.ang.databinding.ActivityLogcatBinding
|
||||||
import com.v2ray.ang.extension.toast
|
import com.v2ray.ang.extension.toast
|
||||||
@@ -24,32 +24,21 @@ class UrlSchemeActivity : BaseActivity() {
|
|||||||
if (action == Intent.ACTION_SEND) {
|
if (action == Intent.ACTION_SEND) {
|
||||||
if ("text/plain" == type) {
|
if ("text/plain" == type) {
|
||||||
intent.getStringExtra(Intent.EXTRA_TEXT)?.let {
|
intent.getStringExtra(Intent.EXTRA_TEXT)?.let {
|
||||||
val uri = Uri.parse(it)
|
parseUri(it)
|
||||||
if (uri.scheme?.startsWith(AppConfig.PROTOCOL_HTTPS) == true || uri.scheme?.startsWith(
|
|
||||||
AppConfig.PROTOCOL_HTTP
|
|
||||||
) == true
|
|
||||||
) {
|
|
||||||
val name = uri.getQueryParameter("name") ?: "Subscription"
|
|
||||||
importSubscription(it, name)
|
|
||||||
} else {
|
|
||||||
importConfig(it)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (action == Intent.ACTION_VIEW) {
|
} else if (action == Intent.ACTION_VIEW) {
|
||||||
when (data?.host) {
|
when (data?.host) {
|
||||||
"install-config" -> {
|
"install-config" -> {
|
||||||
val uri: Uri? = intent.data
|
val uri: Uri? = intent.data
|
||||||
val shareUrl: String = uri?.getQueryParameter("url")!!
|
val shareUrl = uri?.getQueryParameter("url") ?: ""
|
||||||
toast(shareUrl)
|
parseUri(shareUrl)
|
||||||
importConfig(shareUrl)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
"install-sub" -> {
|
"install-sub" -> {
|
||||||
val uri: Uri? = intent.data
|
val uri: Uri? = intent.data
|
||||||
val url = uri?.getQueryParameter("url")!!
|
val shareUrl = uri?.getQueryParameter("url") ?: ""
|
||||||
val name = uri.getQueryParameter("name") ?: "Subscription"
|
parseUri(shareUrl)
|
||||||
importSubscription(url, name)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> {
|
else -> {
|
||||||
@@ -57,10 +46,8 @@ class UrlSchemeActivity : BaseActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
startActivity(Intent(this, MainActivity::class.java))
|
startActivity(Intent(this, MainActivity::class.java))
|
||||||
finish()
|
finish()
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
@@ -68,19 +55,21 @@ class UrlSchemeActivity : BaseActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun importSubscription(url: String, name: String) {
|
private fun parseUri(uriString: String?) {
|
||||||
val decodedUrl = URLDecoder.decode(url, "UTF-8")
|
if (uriString.isNullOrEmpty()) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
Log.d("UrlScheme", uriString)
|
||||||
|
|
||||||
val check = AngConfigManager.importSubscription(name, decodedUrl)
|
val decodedUrl = URLDecoder.decode(uriString, "UTF-8")
|
||||||
if (check) toast(R.string.import_subscription_success) else toast(R.string.import_subscription_failure)
|
val uri = Uri.parse(decodedUrl)
|
||||||
}
|
if (uri != null) {
|
||||||
|
val count = AngConfigManager.importBatchConfig(decodedUrl, "", false)
|
||||||
private fun importConfig(shareUrl: String) {
|
if (count > 0) {
|
||||||
val count = AngConfigManager.importBatchConfig(shareUrl, "", false)
|
toast(R.string.import_subscription_success)
|
||||||
if (count > 0) {
|
} else {
|
||||||
toast(R.string.toast_success)
|
toast(R.string.import_subscription_failure)
|
||||||
} else {
|
}
|
||||||
toast(R.string.toast_failure)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -12,19 +12,16 @@ import com.google.gson.JsonSerializer
|
|||||||
import com.google.gson.reflect.TypeToken
|
import com.google.gson.reflect.TypeToken
|
||||||
import com.tencent.mmkv.MMKV
|
import com.tencent.mmkv.MMKV
|
||||||
import com.v2ray.ang.AppConfig
|
import com.v2ray.ang.AppConfig
|
||||||
import com.v2ray.ang.AppConfig.PROTOCOL_HTTP
|
|
||||||
import com.v2ray.ang.AppConfig.PROTOCOL_HTTPS
|
|
||||||
import com.v2ray.ang.AppConfig.WIREGUARD_LOCAL_ADDRESS_V4
|
|
||||||
import com.v2ray.ang.AppConfig.WIREGUARD_LOCAL_MTU
|
|
||||||
import com.v2ray.ang.R
|
import com.v2ray.ang.R
|
||||||
import com.v2ray.ang.dto.*
|
import com.v2ray.ang.dto.*
|
||||||
import com.v2ray.ang.dto.V2rayConfig.Companion.DEFAULT_SECURITY
|
|
||||||
import com.v2ray.ang.dto.V2rayConfig.Companion.TLS
|
|
||||||
import com.v2ray.ang.extension.idnHost
|
|
||||||
import com.v2ray.ang.extension.removeWhiteSpace
|
|
||||||
import com.v2ray.ang.util.MmkvManager.KEY_SELECTED_SERVER
|
import com.v2ray.ang.util.MmkvManager.KEY_SELECTED_SERVER
|
||||||
|
import com.v2ray.ang.util.fmt.ShadowsocksFmt
|
||||||
|
import com.v2ray.ang.util.fmt.SocksFmt
|
||||||
|
import com.v2ray.ang.util.fmt.TrojanFmt
|
||||||
|
import com.v2ray.ang.util.fmt.VlessFmt
|
||||||
|
import com.v2ray.ang.util.fmt.VmessFmt
|
||||||
|
import com.v2ray.ang.util.fmt.WireguardFmt
|
||||||
import java.lang.reflect.Type
|
import java.lang.reflect.Type
|
||||||
import java.net.URI
|
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
object AngConfigManager {
|
object AngConfigManager {
|
||||||
@@ -205,9 +202,9 @@ object AngConfigManager {
|
|||||||
// }
|
// }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* import config form qrcode or...
|
* parse config form qrcode or...
|
||||||
*/
|
*/
|
||||||
private fun importConfig(
|
private fun parseConfig(
|
||||||
str: String?,
|
str: String?,
|
||||||
subid: String,
|
subid: String,
|
||||||
removedSelectedServer: ServerConfig?
|
removedSelectedServer: ServerConfig?
|
||||||
@@ -217,254 +214,22 @@ object AngConfigManager {
|
|||||||
return R.string.toast_none_data
|
return R.string.toast_none_data
|
||||||
}
|
}
|
||||||
|
|
||||||
//maybe sub
|
val config = if (str.startsWith(EConfigType.VMESS.protocolScheme)) {
|
||||||
if (TextUtils.isEmpty(subid) && (str.startsWith(PROTOCOL_HTTP) || str.startsWith(
|
VmessFmt.parseVmess(str)
|
||||||
PROTOCOL_HTTPS
|
|
||||||
))
|
|
||||||
) {
|
|
||||||
MmkvManager.importUrlAsSubscription(str)
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
var config: ServerConfig? = null
|
|
||||||
val allowInsecure = settingsStorage?.decodeBool(AppConfig.PREF_ALLOW_INSECURE) ?: false
|
|
||||||
if (str.startsWith(EConfigType.VMESS.protocolScheme)) {
|
|
||||||
config = ServerConfig.create(EConfigType.VMESS)
|
|
||||||
val streamSetting = config.outboundBean?.streamSettings ?: return -1
|
|
||||||
|
|
||||||
|
|
||||||
if (!tryParseNewVmess(str, config, allowInsecure)) {
|
|
||||||
if (str.indexOf("?") > 0) {
|
|
||||||
if (!tryResolveVmess4Kitsunebi(str, config)) {
|
|
||||||
return R.string.toast_incorrect_protocol
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
var result = str.replace(EConfigType.VMESS.protocolScheme, "")
|
|
||||||
result = Utils.decode(result)
|
|
||||||
if (TextUtils.isEmpty(result)) {
|
|
||||||
return R.string.toast_decoding_failed
|
|
||||||
}
|
|
||||||
val vmessQRCode = Gson().fromJson(result, VmessQRCode::class.java)
|
|
||||||
// Although VmessQRCode fields are non null, looks like Gson may still create null fields
|
|
||||||
if (TextUtils.isEmpty(vmessQRCode.add)
|
|
||||||
|| TextUtils.isEmpty(vmessQRCode.port)
|
|
||||||
|| TextUtils.isEmpty(vmessQRCode.id)
|
|
||||||
|| TextUtils.isEmpty(vmessQRCode.net)
|
|
||||||
) {
|
|
||||||
return R.string.toast_incorrect_protocol
|
|
||||||
}
|
|
||||||
|
|
||||||
config.remarks = vmessQRCode.ps
|
|
||||||
config.outboundBean?.settings?.vnext?.get(0)?.let { vnext ->
|
|
||||||
vnext.address = vmessQRCode.add
|
|
||||||
vnext.port = Utils.parseInt(vmessQRCode.port)
|
|
||||||
vnext.users[0].id = vmessQRCode.id
|
|
||||||
vnext.users[0].security =
|
|
||||||
if (TextUtils.isEmpty(vmessQRCode.scy)) DEFAULT_SECURITY else vmessQRCode.scy
|
|
||||||
vnext.users[0].alterId = Utils.parseInt(vmessQRCode.aid)
|
|
||||||
}
|
|
||||||
val sni = streamSetting.populateTransportSettings(
|
|
||||||
vmessQRCode.net,
|
|
||||||
vmessQRCode.type,
|
|
||||||
vmessQRCode.host,
|
|
||||||
vmessQRCode.path,
|
|
||||||
vmessQRCode.path,
|
|
||||||
vmessQRCode.host,
|
|
||||||
vmessQRCode.path,
|
|
||||||
vmessQRCode.type,
|
|
||||||
vmessQRCode.path,
|
|
||||||
vmessQRCode.host
|
|
||||||
)
|
|
||||||
|
|
||||||
val fingerprint = vmessQRCode.fp ?: streamSetting.tlsSettings?.fingerprint
|
|
||||||
streamSetting.populateTlsSettings(
|
|
||||||
vmessQRCode.tls, allowInsecure,
|
|
||||||
if (TextUtils.isEmpty(vmessQRCode.sni)) sni else vmessQRCode.sni,
|
|
||||||
fingerprint, vmessQRCode.alpn, null, null, null
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (str.startsWith(EConfigType.SHADOWSOCKS.protocolScheme)) {
|
} else if (str.startsWith(EConfigType.SHADOWSOCKS.protocolScheme)) {
|
||||||
config = ServerConfig.create(EConfigType.SHADOWSOCKS)
|
ShadowsocksFmt.parseShadowsocks(str)
|
||||||
if (!tryResolveResolveSip002(str, config)) {
|
|
||||||
var result = str.replace(EConfigType.SHADOWSOCKS.protocolScheme, "")
|
|
||||||
val indexSplit = result.indexOf("#")
|
|
||||||
if (indexSplit > 0) {
|
|
||||||
try {
|
|
||||||
config.remarks =
|
|
||||||
Utils.urlDecode(result.substring(indexSplit + 1, result.length))
|
|
||||||
} catch (e: Exception) {
|
|
||||||
e.printStackTrace()
|
|
||||||
}
|
|
||||||
|
|
||||||
result = result.substring(0, indexSplit)
|
|
||||||
}
|
|
||||||
|
|
||||||
//part decode
|
|
||||||
val indexS = result.indexOf("@")
|
|
||||||
result = if (indexS > 0) {
|
|
||||||
Utils.decode(result.substring(0, indexS)) + result.substring(
|
|
||||||
indexS,
|
|
||||||
result.length
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
Utils.decode(result)
|
|
||||||
}
|
|
||||||
|
|
||||||
val legacyPattern = "^(.+?):(.*)@(.+?):(\\d+?)/?$".toRegex()
|
|
||||||
val match = legacyPattern.matchEntire(result)
|
|
||||||
?: return R.string.toast_incorrect_protocol
|
|
||||||
|
|
||||||
config.outboundBean?.settings?.servers?.get(0)?.let { server ->
|
|
||||||
server.address = match.groupValues[3].removeSurrounding("[", "]")
|
|
||||||
server.port = match.groupValues[4].toInt()
|
|
||||||
server.password = match.groupValues[2]
|
|
||||||
server.method = match.groupValues[1].lowercase()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (str.startsWith(EConfigType.SOCKS.protocolScheme)) {
|
} else if (str.startsWith(EConfigType.SOCKS.protocolScheme)) {
|
||||||
var result = str.replace(EConfigType.SOCKS.protocolScheme, "")
|
SocksFmt.parseSocks(str)
|
||||||
val indexSplit = result.indexOf("#")
|
|
||||||
config = ServerConfig.create(EConfigType.SOCKS)
|
|
||||||
if (indexSplit > 0) {
|
|
||||||
try {
|
|
||||||
config.remarks =
|
|
||||||
Utils.urlDecode(result.substring(indexSplit + 1, result.length))
|
|
||||||
} catch (e: Exception) {
|
|
||||||
e.printStackTrace()
|
|
||||||
}
|
|
||||||
|
|
||||||
result = result.substring(0, indexSplit)
|
|
||||||
}
|
|
||||||
|
|
||||||
//part decode
|
|
||||||
val indexS = result.indexOf("@")
|
|
||||||
if (indexS > 0) {
|
|
||||||
result = Utils.decode(result.substring(0, indexS)) + result.substring(
|
|
||||||
indexS,
|
|
||||||
result.length
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
result = Utils.decode(result)
|
|
||||||
}
|
|
||||||
|
|
||||||
val legacyPattern = "^(.*):(.*)@(.+?):(\\d+?)$".toRegex()
|
|
||||||
val match =
|
|
||||||
legacyPattern.matchEntire(result) ?: return R.string.toast_incorrect_protocol
|
|
||||||
|
|
||||||
config.outboundBean?.settings?.servers?.get(0)?.let { server ->
|
|
||||||
server.address = match.groupValues[3].removeSurrounding("[", "]")
|
|
||||||
server.port = match.groupValues[4].toInt()
|
|
||||||
val socksUsersBean =
|
|
||||||
V2rayConfig.OutboundBean.OutSettingsBean.ServersBean.SocksUsersBean()
|
|
||||||
socksUsersBean.user = match.groupValues[1]
|
|
||||||
socksUsersBean.pass = match.groupValues[2]
|
|
||||||
server.users = listOf(socksUsersBean)
|
|
||||||
}
|
|
||||||
} else if (str.startsWith(EConfigType.TROJAN.protocolScheme)) {
|
} else if (str.startsWith(EConfigType.TROJAN.protocolScheme)) {
|
||||||
val uri = URI(Utils.fixIllegalUrl(str))
|
TrojanFmt.parseTrojan(str)
|
||||||
config = ServerConfig.create(EConfigType.TROJAN)
|
|
||||||
config.remarks = Utils.urlDecode(uri.fragment ?: "")
|
|
||||||
|
|
||||||
var flow = ""
|
|
||||||
var fingerprint = config.outboundBean?.streamSettings?.tlsSettings?.fingerprint
|
|
||||||
if (uri.rawQuery != null) {
|
|
||||||
val queryParam = uri.rawQuery.split("&")
|
|
||||||
.associate { it.split("=").let { (k, v) -> k to Utils.urlDecode(v) } }
|
|
||||||
|
|
||||||
val sni = config.outboundBean?.streamSettings?.populateTransportSettings(
|
|
||||||
queryParam["type"] ?: "tcp",
|
|
||||||
queryParam["headerType"],
|
|
||||||
queryParam["host"],
|
|
||||||
queryParam["path"],
|
|
||||||
queryParam["seed"],
|
|
||||||
queryParam["quicSecurity"],
|
|
||||||
queryParam["key"],
|
|
||||||
queryParam["mode"],
|
|
||||||
queryParam["serviceName"],
|
|
||||||
queryParam["authority"]
|
|
||||||
)
|
|
||||||
fingerprint = queryParam["fp"] ?: ""
|
|
||||||
config.outboundBean?.streamSettings?.populateTlsSettings(
|
|
||||||
queryParam["security"] ?: TLS,
|
|
||||||
allowInsecure, queryParam["sni"] ?: sni!!, fingerprint, queryParam["alpn"],
|
|
||||||
null, null, null
|
|
||||||
)
|
|
||||||
flow = queryParam["flow"] ?: ""
|
|
||||||
} else {
|
|
||||||
config.outboundBean?.streamSettings?.populateTlsSettings(
|
|
||||||
TLS, allowInsecure, "",
|
|
||||||
fingerprint, null, null, null, null
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
config.outboundBean?.settings?.servers?.get(0)?.let { server ->
|
|
||||||
server.address = uri.idnHost
|
|
||||||
server.port = uri.port
|
|
||||||
server.password = uri.userInfo
|
|
||||||
server.flow = flow
|
|
||||||
}
|
|
||||||
} else if (str.startsWith(EConfigType.VLESS.protocolScheme)) {
|
} else if (str.startsWith(EConfigType.VLESS.protocolScheme)) {
|
||||||
val uri = URI(Utils.fixIllegalUrl(str))
|
VlessFmt.parseVless(str)
|
||||||
val queryParam = uri.rawQuery.split("&")
|
|
||||||
.associate { it.split("=").let { (k, v) -> k to Utils.urlDecode(v) } }
|
|
||||||
config = ServerConfig.create(EConfigType.VLESS)
|
|
||||||
val streamSetting = config.outboundBean?.streamSettings ?: return -1
|
|
||||||
|
|
||||||
config.remarks = Utils.urlDecode(uri.fragment ?: "")
|
|
||||||
config.outboundBean?.settings?.vnext?.get(0)?.let { vnext ->
|
|
||||||
vnext.address = uri.idnHost
|
|
||||||
vnext.port = uri.port
|
|
||||||
vnext.users[0].id = uri.userInfo
|
|
||||||
vnext.users[0].encryption = queryParam["encryption"] ?: "none"
|
|
||||||
vnext.users[0].flow = queryParam["flow"] ?: ""
|
|
||||||
}
|
|
||||||
|
|
||||||
val sni = streamSetting.populateTransportSettings(
|
|
||||||
queryParam["type"] ?: "tcp",
|
|
||||||
queryParam["headerType"],
|
|
||||||
queryParam["host"],
|
|
||||||
queryParam["path"],
|
|
||||||
queryParam["seed"],
|
|
||||||
queryParam["quicSecurity"],
|
|
||||||
queryParam["key"],
|
|
||||||
queryParam["mode"],
|
|
||||||
queryParam["serviceName"],
|
|
||||||
queryParam["authority"]
|
|
||||||
)
|
|
||||||
streamSetting.populateTlsSettings(
|
|
||||||
queryParam["security"] ?: "",
|
|
||||||
allowInsecure,
|
|
||||||
queryParam["sni"] ?: sni,
|
|
||||||
queryParam["fp"] ?: "",
|
|
||||||
queryParam["alpn"],
|
|
||||||
queryParam["pbk"] ?: "",
|
|
||||||
queryParam["sid"] ?: "",
|
|
||||||
queryParam["spx"] ?: ""
|
|
||||||
)
|
|
||||||
} else if (str.startsWith(EConfigType.WIREGUARD.protocolScheme)) {
|
} else if (str.startsWith(EConfigType.WIREGUARD.protocolScheme)) {
|
||||||
val uri = URI(Utils.fixIllegalUrl(str))
|
WireguardFmt.parseWireguard(str)
|
||||||
config = ServerConfig.create(EConfigType.WIREGUARD)
|
} else {
|
||||||
config.remarks = Utils.urlDecode(uri.fragment ?: "")
|
null
|
||||||
|
|
||||||
if (uri.rawQuery != null) {
|
|
||||||
val queryParam = uri.rawQuery.split("&")
|
|
||||||
.associate { it.split("=").let { (k, v) -> k to Utils.urlDecode(v) } }
|
|
||||||
|
|
||||||
config.outboundBean?.settings?.let { wireguard ->
|
|
||||||
wireguard.secretKey = uri.userInfo
|
|
||||||
wireguard.address =
|
|
||||||
(queryParam["address"] ?: WIREGUARD_LOCAL_ADDRESS_V4).removeWhiteSpace()
|
|
||||||
.split(",")
|
|
||||||
wireguard.peers?.get(0)?.publicKey = queryParam["publickey"] ?: ""
|
|
||||||
wireguard.peers?.get(0)?.endpoint = "${uri.idnHost}:${uri.port}"
|
|
||||||
wireguard.mtu = Utils.parseInt(queryParam["mtu"] ?: WIREGUARD_LOCAL_MTU)
|
|
||||||
wireguard.reserved =
|
|
||||||
(queryParam["reserved"] ?: "0,0,0").removeWhiteSpace().split(",")
|
|
||||||
.map { it.toInt() }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (config == null) {
|
if (config == null) {
|
||||||
return R.string.toast_incorrect_protocol
|
return R.string.toast_incorrect_protocol
|
||||||
}
|
}
|
||||||
@@ -487,383 +252,21 @@ object AngConfigManager {
|
|||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun tryParseNewVmess(
|
|
||||||
uriString: String,
|
|
||||||
config: ServerConfig,
|
|
||||||
allowInsecure: Boolean
|
|
||||||
): Boolean {
|
|
||||||
return runCatching {
|
|
||||||
val uri = URI(Utils.fixIllegalUrl(uriString))
|
|
||||||
check(uri.scheme == "vmess")
|
|
||||||
val (_, protocol, tlsStr, uuid, alterId) =
|
|
||||||
Regex("(tcp|http|ws|kcp|quic|grpc)(\\+tls)?:([0-9a-z]{8}-[0-9a-z]{4}-[0-9a-z]{4}-[0-9a-z]{4}-[0-9a-z]{12})")
|
|
||||||
.matchEntire(uri.userInfo)?.groupValues
|
|
||||||
?: error("parse user info fail.")
|
|
||||||
val tls = tlsStr.isNotBlank()
|
|
||||||
val queryParam = uri.rawQuery.split("&")
|
|
||||||
.associate { it.split("=").let { (k, v) -> k to Utils.urlDecode(v) } }
|
|
||||||
|
|
||||||
val streamSetting = config.outboundBean?.streamSettings ?: return false
|
|
||||||
config.remarks = Utils.urlDecode(uri.fragment ?: "")
|
|
||||||
config.outboundBean.settings?.vnext?.get(0)?.let { vnext ->
|
|
||||||
vnext.address = uri.idnHost
|
|
||||||
vnext.port = uri.port
|
|
||||||
vnext.users[0].id = uuid
|
|
||||||
vnext.users[0].security = DEFAULT_SECURITY
|
|
||||||
vnext.users[0].alterId = alterId.toInt()
|
|
||||||
}
|
|
||||||
var fingerprint = streamSetting.tlsSettings?.fingerprint
|
|
||||||
val sni = streamSetting.populateTransportSettings(protocol,
|
|
||||||
queryParam["type"],
|
|
||||||
queryParam["host"]?.split("|")?.get(0) ?: "",
|
|
||||||
queryParam["path"]?.takeIf { it.trim() != "/" } ?: "",
|
|
||||||
queryParam["seed"],
|
|
||||||
queryParam["security"],
|
|
||||||
queryParam["key"],
|
|
||||||
queryParam["mode"],
|
|
||||||
queryParam["serviceName"],
|
|
||||||
queryParam["authority"])
|
|
||||||
streamSetting.populateTlsSettings(
|
|
||||||
if (tls) TLS else "", allowInsecure, sni, fingerprint, null,
|
|
||||||
null, null, null
|
|
||||||
)
|
|
||||||
true
|
|
||||||
}.getOrElse { false }
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun tryResolveVmess4Kitsunebi(server: String, config: ServerConfig): Boolean {
|
|
||||||
|
|
||||||
var result = server.replace(EConfigType.VMESS.protocolScheme, "")
|
|
||||||
val indexSplit = result.indexOf("?")
|
|
||||||
if (indexSplit > 0) {
|
|
||||||
result = result.substring(0, indexSplit)
|
|
||||||
}
|
|
||||||
result = Utils.decode(result)
|
|
||||||
|
|
||||||
val arr1 = result.split('@')
|
|
||||||
if (arr1.count() != 2) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
val arr21 = arr1[0].split(':')
|
|
||||||
val arr22 = arr1[1].split(':')
|
|
||||||
if (arr21.count() != 2) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
config.remarks = "Alien"
|
|
||||||
config.outboundBean?.settings?.vnext?.get(0)?.let { vnext ->
|
|
||||||
vnext.address = arr22[0]
|
|
||||||
vnext.port = Utils.parseInt(arr22[1])
|
|
||||||
vnext.users[0].id = arr21[1]
|
|
||||||
vnext.users[0].security = arr21[0]
|
|
||||||
vnext.users[0].alterId = 0
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun tryResolveResolveSip002(str: String, config: ServerConfig): Boolean {
|
|
||||||
try {
|
|
||||||
val uri = URI(Utils.fixIllegalUrl(str))
|
|
||||||
config.remarks = Utils.urlDecode(uri.fragment ?: "")
|
|
||||||
|
|
||||||
val method: String
|
|
||||||
val password: String
|
|
||||||
if (uri.userInfo.contains(":")) {
|
|
||||||
val arrUserInfo = uri.userInfo.split(":").map { it.trim() }
|
|
||||||
if (arrUserInfo.count() != 2) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
method = arrUserInfo[0]
|
|
||||||
password = Utils.urlDecode(arrUserInfo[1])
|
|
||||||
} else {
|
|
||||||
val base64Decode = Utils.decode(uri.userInfo)
|
|
||||||
val arrUserInfo = base64Decode.split(":").map { it.trim() }
|
|
||||||
if (arrUserInfo.count() < 2) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
method = arrUserInfo[0]
|
|
||||||
password = base64Decode.substringAfter(":")
|
|
||||||
}
|
|
||||||
|
|
||||||
val query = Utils.urlDecode(uri.query ?: "")
|
|
||||||
if (query != "") {
|
|
||||||
val queryPairs = HashMap<String, String>()
|
|
||||||
val pairs = query.split(";")
|
|
||||||
Log.d(AppConfig.ANG_PACKAGE, pairs.toString())
|
|
||||||
for (pair in pairs) {
|
|
||||||
val idx = pair.indexOf("=")
|
|
||||||
if (idx == -1) {
|
|
||||||
queryPairs[Utils.urlDecode(pair)] = "";
|
|
||||||
} else {
|
|
||||||
queryPairs[Utils.urlDecode(pair.substring(0, idx))] =
|
|
||||||
Utils.urlDecode(pair.substring(idx + 1))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Log.d(AppConfig.ANG_PACKAGE, queryPairs.toString())
|
|
||||||
var sni: String? = ""
|
|
||||||
if (queryPairs["plugin"] == "obfs-local" && queryPairs["obfs"] == "http") {
|
|
||||||
sni = config.outboundBean?.streamSettings?.populateTransportSettings(
|
|
||||||
"tcp",
|
|
||||||
"http",
|
|
||||||
queryPairs["obfs-host"],
|
|
||||||
queryPairs["path"],
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null
|
|
||||||
)
|
|
||||||
} else if (queryPairs["plugin"] == "v2ray-plugin") {
|
|
||||||
var network = "ws";
|
|
||||||
if (queryPairs["mode"] == "quic") {
|
|
||||||
network = "quic";
|
|
||||||
}
|
|
||||||
sni = config.outboundBean?.streamSettings?.populateTransportSettings(
|
|
||||||
network,
|
|
||||||
null,
|
|
||||||
queryPairs["host"],
|
|
||||||
queryPairs["path"],
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null
|
|
||||||
)
|
|
||||||
}
|
|
||||||
if ("tls" in queryPairs) {
|
|
||||||
config.outboundBean?.streamSettings?.populateTlsSettings(
|
|
||||||
"tls", false, sni ?: "", null, null, null, null, null
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
config.outboundBean?.settings?.servers?.get(0)?.let { server ->
|
|
||||||
server.address = uri.idnHost
|
|
||||||
server.port = uri.port
|
|
||||||
server.password = password
|
|
||||||
server.method = method
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
} catch (e: Exception) {
|
|
||||||
Log.d(AppConfig.ANG_PACKAGE, e.toString())
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* share config
|
* share config
|
||||||
*/
|
*/
|
||||||
private fun shareConfig(guid: String): String {
|
private fun shareConfig(guid: String): String {
|
||||||
try {
|
try {
|
||||||
val config = MmkvManager.decodeServerConfig(guid) ?: return ""
|
val config = MmkvManager.decodeServerConfig(guid) ?: return ""
|
||||||
val outbound = config.getProxyOutbound() ?: return ""
|
|
||||||
val streamSetting =
|
|
||||||
outbound.streamSettings ?: V2rayConfig.OutboundBean.StreamSettingsBean()
|
|
||||||
if (config.configType != EConfigType.WIREGUARD) {
|
|
||||||
if (outbound.streamSettings == null) return ""
|
|
||||||
}
|
|
||||||
return config.configType.protocolScheme + when (config.configType) {
|
return config.configType.protocolScheme + when (config.configType) {
|
||||||
EConfigType.VMESS -> {
|
EConfigType.VMESS -> VmessFmt.toUri(config)
|
||||||
val vmessQRCode = VmessQRCode()
|
|
||||||
vmessQRCode.v = "2"
|
|
||||||
vmessQRCode.ps = config.remarks
|
|
||||||
vmessQRCode.add = outbound.getServerAddress().orEmpty()
|
|
||||||
vmessQRCode.port = outbound.getServerPort().toString()
|
|
||||||
vmessQRCode.id = outbound.getPassword().orEmpty()
|
|
||||||
vmessQRCode.aid =
|
|
||||||
outbound.settings?.vnext?.get(0)?.users?.get(0)?.alterId.toString()
|
|
||||||
vmessQRCode.scy =
|
|
||||||
outbound.settings?.vnext?.get(0)?.users?.get(0)?.security.toString()
|
|
||||||
vmessQRCode.net = streamSetting.network
|
|
||||||
vmessQRCode.tls = streamSetting.security
|
|
||||||
vmessQRCode.sni = streamSetting.tlsSettings?.serverName.orEmpty()
|
|
||||||
vmessQRCode.alpn =
|
|
||||||
Utils.removeWhiteSpace(streamSetting.tlsSettings?.alpn?.joinToString())
|
|
||||||
.orEmpty()
|
|
||||||
vmessQRCode.fp = streamSetting.tlsSettings?.fingerprint.orEmpty()
|
|
||||||
outbound.getTransportSettingDetails()?.let { transportDetails ->
|
|
||||||
vmessQRCode.type = transportDetails[0]
|
|
||||||
vmessQRCode.host = transportDetails[1]
|
|
||||||
vmessQRCode.path = transportDetails[2]
|
|
||||||
}
|
|
||||||
val json = Gson().toJson(vmessQRCode)
|
|
||||||
Utils.encode(json)
|
|
||||||
}
|
|
||||||
|
|
||||||
EConfigType.CUSTOM -> ""
|
EConfigType.CUSTOM -> ""
|
||||||
|
EConfigType.SHADOWSOCKS -> ShadowsocksFmt.toUri(config)
|
||||||
EConfigType.SHADOWSOCKS -> {
|
EConfigType.SOCKS -> SocksFmt.toUri(config)
|
||||||
val remark = "#" + Utils.urlEncode(config.remarks)
|
EConfigType.VLESS -> VlessFmt.toUri(config)
|
||||||
val pw =
|
EConfigType.TROJAN -> TrojanFmt.toUri(config)
|
||||||
Utils.encode("${outbound.getSecurityEncryption()}:${outbound.getPassword()}")
|
EConfigType.WIREGUARD -> WireguardFmt.toUri(config)
|
||||||
val url = String.format(
|
|
||||||
"%s@%s:%s",
|
|
||||||
pw,
|
|
||||||
Utils.getIpv6Address(outbound.getServerAddress()!!),
|
|
||||||
outbound.getServerPort()
|
|
||||||
)
|
|
||||||
url + remark
|
|
||||||
}
|
|
||||||
|
|
||||||
EConfigType.SOCKS -> {
|
|
||||||
val remark = "#" + Utils.urlEncode(config.remarks)
|
|
||||||
val pw =
|
|
||||||
if (outbound.settings?.servers?.get(0)?.users?.get(0)?.user != null)
|
|
||||||
"${outbound.settings?.servers?.get(0)?.users?.get(0)?.user}:${outbound.getPassword()}"
|
|
||||||
else
|
|
||||||
":"
|
|
||||||
val url = String.format(
|
|
||||||
"%s@%s:%s",
|
|
||||||
Utils.encode(pw),
|
|
||||||
Utils.getIpv6Address(outbound.getServerAddress()!!),
|
|
||||||
outbound.getServerPort()
|
|
||||||
)
|
|
||||||
url + remark
|
|
||||||
}
|
|
||||||
|
|
||||||
EConfigType.VLESS,
|
|
||||||
EConfigType.TROJAN -> {
|
|
||||||
val remark = "#" + Utils.urlEncode(config.remarks)
|
|
||||||
|
|
||||||
val dicQuery = HashMap<String, String>()
|
|
||||||
if (config.configType == EConfigType.VLESS) {
|
|
||||||
outbound.settings?.vnext?.get(0)?.users?.get(0)?.flow?.let {
|
|
||||||
if (!TextUtils.isEmpty(it)) {
|
|
||||||
dicQuery["flow"] = it
|
|
||||||
}
|
|
||||||
}
|
|
||||||
dicQuery["encryption"] =
|
|
||||||
if (outbound.getSecurityEncryption().isNullOrEmpty()) "none"
|
|
||||||
else outbound.getSecurityEncryption().orEmpty()
|
|
||||||
} else if (config.configType == EConfigType.TROJAN) {
|
|
||||||
config.outboundBean?.settings?.servers?.get(0)?.flow?.let {
|
|
||||||
if (!TextUtils.isEmpty(it)) {
|
|
||||||
dicQuery["flow"] = it
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
dicQuery["security"] = streamSetting.security.ifEmpty { "none" }
|
|
||||||
(streamSetting.tlsSettings
|
|
||||||
?: streamSetting.realitySettings)?.let { tlsSetting ->
|
|
||||||
if (!TextUtils.isEmpty(tlsSetting.serverName)) {
|
|
||||||
dicQuery["sni"] = tlsSetting.serverName
|
|
||||||
}
|
|
||||||
if (!tlsSetting.alpn.isNullOrEmpty() && tlsSetting.alpn.isNotEmpty()) {
|
|
||||||
dicQuery["alpn"] =
|
|
||||||
Utils.removeWhiteSpace(tlsSetting.alpn.joinToString()).orEmpty()
|
|
||||||
}
|
|
||||||
if (!TextUtils.isEmpty(tlsSetting.fingerprint)) {
|
|
||||||
dicQuery["fp"] = tlsSetting.fingerprint!!
|
|
||||||
}
|
|
||||||
if (!TextUtils.isEmpty(tlsSetting.publicKey)) {
|
|
||||||
dicQuery["pbk"] = tlsSetting.publicKey!!
|
|
||||||
}
|
|
||||||
if (!TextUtils.isEmpty(tlsSetting.shortId)) {
|
|
||||||
dicQuery["sid"] = tlsSetting.shortId!!
|
|
||||||
}
|
|
||||||
if (!TextUtils.isEmpty(tlsSetting.spiderX)) {
|
|
||||||
dicQuery["spx"] = Utils.urlEncode(tlsSetting.spiderX!!)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
dicQuery["type"] =
|
|
||||||
streamSetting.network.ifEmpty { V2rayConfig.DEFAULT_NETWORK }
|
|
||||||
|
|
||||||
outbound.getTransportSettingDetails()?.let { transportDetails ->
|
|
||||||
when (streamSetting.network) {
|
|
||||||
"tcp" -> {
|
|
||||||
dicQuery["headerType"] = transportDetails[0].ifEmpty { "none" }
|
|
||||||
if (!TextUtils.isEmpty(transportDetails[1])) {
|
|
||||||
dicQuery["host"] = Utils.urlEncode(transportDetails[1])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
"kcp" -> {
|
|
||||||
dicQuery["headerType"] = transportDetails[0].ifEmpty { "none" }
|
|
||||||
if (!TextUtils.isEmpty(transportDetails[2])) {
|
|
||||||
dicQuery["seed"] = Utils.urlEncode(transportDetails[2])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
"ws", "httpupgrade" -> {
|
|
||||||
if (!TextUtils.isEmpty(transportDetails[1])) {
|
|
||||||
dicQuery["host"] = Utils.urlEncode(transportDetails[1])
|
|
||||||
}
|
|
||||||
if (!TextUtils.isEmpty(transportDetails[2])) {
|
|
||||||
dicQuery["path"] = Utils.urlEncode(transportDetails[2])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
"http", "h2" -> {
|
|
||||||
dicQuery["type"] = "http"
|
|
||||||
if (!TextUtils.isEmpty(transportDetails[1])) {
|
|
||||||
dicQuery["host"] = Utils.urlEncode(transportDetails[1])
|
|
||||||
}
|
|
||||||
if (!TextUtils.isEmpty(transportDetails[2])) {
|
|
||||||
dicQuery["path"] = Utils.urlEncode(transportDetails[2])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
"quic" -> {
|
|
||||||
dicQuery["headerType"] = transportDetails[0].ifEmpty { "none" }
|
|
||||||
dicQuery["quicSecurity"] = Utils.urlEncode(transportDetails[1])
|
|
||||||
dicQuery["key"] = Utils.urlEncode(transportDetails[2])
|
|
||||||
}
|
|
||||||
|
|
||||||
"grpc" -> {
|
|
||||||
dicQuery["mode"] = transportDetails[0]
|
|
||||||
dicQuery["authority"] = Utils.urlEncode(transportDetails[1])
|
|
||||||
dicQuery["serviceName"] = Utils.urlEncode(transportDetails[2])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
val query = "?" + dicQuery.toList().joinToString(
|
|
||||||
separator = "&",
|
|
||||||
transform = { it.first + "=" + it.second })
|
|
||||||
|
|
||||||
val url = String.format(
|
|
||||||
"%s@%s:%s",
|
|
||||||
outbound.getPassword(),
|
|
||||||
Utils.getIpv6Address(outbound.getServerAddress()!!),
|
|
||||||
outbound.getServerPort()
|
|
||||||
)
|
|
||||||
url + query + remark
|
|
||||||
}
|
|
||||||
|
|
||||||
EConfigType.WIREGUARD -> {
|
|
||||||
val remark = "#" + Utils.urlEncode(config.remarks)
|
|
||||||
|
|
||||||
val dicQuery = HashMap<String, String>()
|
|
||||||
dicQuery["publickey"] =
|
|
||||||
Utils.urlEncode(outbound.settings?.peers?.get(0)?.publicKey.toString())
|
|
||||||
if (outbound.settings?.reserved != null) {
|
|
||||||
dicQuery["reserved"] = Utils.urlEncode(
|
|
||||||
Utils.removeWhiteSpace(outbound.settings?.reserved?.joinToString())
|
|
||||||
.toString()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
dicQuery["address"] = Utils.urlEncode(
|
|
||||||
Utils.removeWhiteSpace((outbound.settings?.address as List<*>).joinToString())
|
|
||||||
.toString()
|
|
||||||
)
|
|
||||||
if (outbound.settings?.mtu != null) {
|
|
||||||
dicQuery["mtu"] = outbound.settings?.mtu.toString()
|
|
||||||
}
|
|
||||||
val query = "?" + dicQuery.toList().joinToString(
|
|
||||||
separator = "&",
|
|
||||||
transform = { it.first + "=" + it.second })
|
|
||||||
|
|
||||||
val url = String.format(
|
|
||||||
"%s@%s:%s",
|
|
||||||
Utils.urlEncode(outbound.getPassword().toString()),
|
|
||||||
Utils.getIpv6Address(outbound.getServerAddress()!!),
|
|
||||||
outbound.getServerPort()
|
|
||||||
)
|
|
||||||
url + query + remark
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
e.printStackTrace()
|
e.printStackTrace()
|
||||||
@@ -983,7 +386,44 @@ object AngConfigManager {
|
|||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
|
|
||||||
fun importBatchConfig(servers: String?, subid: String, append: Boolean): Int {
|
fun importBatchConfig(server: String?, subid: String, append: Boolean): Int {
|
||||||
|
var count = parseBatchConfig(Utils.decode(server), subid, append)
|
||||||
|
if (count <= 0) {
|
||||||
|
count = parseBatchConfig(server, subid, append)
|
||||||
|
}
|
||||||
|
if (count <= 0) {
|
||||||
|
count = parseCustomConfigServer(server, subid)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parseBatchSubscription(server, subid) > 0) {
|
||||||
|
updateConfigViaSubAll()
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
return count
|
||||||
|
}
|
||||||
|
|
||||||
|
fun parseBatchSubscription(servers: String?, subid: String): Int {
|
||||||
|
try {
|
||||||
|
if (servers == null) {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
var count = 0
|
||||||
|
servers.lines()
|
||||||
|
.reversed()
|
||||||
|
.forEach { str ->
|
||||||
|
if (str.startsWith(AppConfig.PROTOCOL_HTTP) || str.startsWith(AppConfig.PROTOCOL_HTTPS)) {
|
||||||
|
count += MmkvManager.importUrlAsSubscription(str)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return count
|
||||||
|
} catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
fun parseBatchConfig(servers: String?, subid: String, append: Boolean): Int {
|
||||||
try {
|
try {
|
||||||
if (servers == null) {
|
if (servers == null) {
|
||||||
return 0
|
return 0
|
||||||
@@ -1004,16 +444,12 @@ object AngConfigManager {
|
|||||||
if (!append) {
|
if (!append) {
|
||||||
MmkvManager.removeServerViaSubid(subid)
|
MmkvManager.removeServerViaSubid(subid)
|
||||||
}
|
}
|
||||||
// var servers = server
|
|
||||||
// if (server.indexOf("vmess") >= 0 && server.indexOf("vmess") == server.lastIndexOf("vmess")) {
|
|
||||||
// servers = server.replace("\n", "")
|
|
||||||
// }
|
|
||||||
|
|
||||||
var count = 0
|
var count = 0
|
||||||
servers.lines()
|
servers.lines()
|
||||||
.reversed()
|
.reversed()
|
||||||
.forEach {
|
.forEach {
|
||||||
val resId = importConfig(it, subid, removedSelectedServer)
|
val resId = parseConfig(it, subid, removedSelectedServer)
|
||||||
if (resId == 0) {
|
if (resId == 0) {
|
||||||
count++
|
count++
|
||||||
}
|
}
|
||||||
@@ -1025,24 +461,7 @@ object AngConfigManager {
|
|||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
fun importSubscription(remark: String, url: String, enabled: Boolean = true): Boolean {
|
fun parseCustomConfigServer(server: String?, subid: String): Int {
|
||||||
val subId = Utils.getUuid()
|
|
||||||
val subItem = SubscriptionItem()
|
|
||||||
|
|
||||||
|
|
||||||
subItem.remarks = remark
|
|
||||||
subItem.url = url
|
|
||||||
subItem.enabled = enabled
|
|
||||||
|
|
||||||
if (TextUtils.isEmpty(subItem.remarks) || TextUtils.isEmpty(subItem.url)) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
subStorage?.encode(subId, Gson().toJson(subItem))
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
fun appendCustomConfigServer(server: String?, subid: String): Int {
|
|
||||||
if (server == null) {
|
if (server == null) {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
@@ -1071,7 +490,8 @@ object AngConfigManager {
|
|||||||
var count = 0
|
var count = 0
|
||||||
for (srv in serverList) {
|
for (srv in serverList) {
|
||||||
val config = ServerConfig.create(EConfigType.CUSTOM)
|
val config = ServerConfig.create(EConfigType.CUSTOM)
|
||||||
config.fullConfig = Gson().fromJson(Gson().toJson(srv), V2rayConfig::class.java)
|
config.fullConfig =
|
||||||
|
Gson().fromJson(Gson().toJson(srv), V2rayConfig::class.java)
|
||||||
config.remarks = config.fullConfig?.remarks
|
config.remarks = config.fullConfig?.remarks
|
||||||
?: ("%04d-".format(count + 1) + System.currentTimeMillis()
|
?: ("%04d-".format(count + 1) + System.currentTimeMillis()
|
||||||
.toString())
|
.toString())
|
||||||
@@ -1098,4 +518,72 @@ object AngConfigManager {
|
|||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun updateConfigViaSubAll(): Int {
|
||||||
|
var count = 0
|
||||||
|
try {
|
||||||
|
MmkvManager.decodeSubscriptions().forEach {
|
||||||
|
count += updateConfigViaSub(it)
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return count
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun updateConfigViaSub(it: Pair<String, SubscriptionItem>): Int {
|
||||||
|
try {
|
||||||
|
if (TextUtils.isEmpty(it.first)
|
||||||
|
|| TextUtils.isEmpty(it.second.remarks)
|
||||||
|
|| TextUtils.isEmpty(it.second.url)
|
||||||
|
) {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
if (!it.second.enabled) {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
val url = Utils.idnToASCII(it.second.url)
|
||||||
|
if (!Utils.isValidUrl(url)) {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
Log.d(AppConfig.ANG_PACKAGE, url)
|
||||||
|
var configText = try {
|
||||||
|
Utils.getUrlContentWithCustomUserAgent(url)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
""
|
||||||
|
}
|
||||||
|
if (configText.isEmpty()) {
|
||||||
|
configText = try {
|
||||||
|
val httpPort = Utils.parseInt(
|
||||||
|
settingsStorage?.decodeString(AppConfig.PREF_HTTP_PORT),
|
||||||
|
AppConfig.PORT_HTTP.toInt()
|
||||||
|
)
|
||||||
|
Utils.getUrlContentWithCustomUserAgent(url, httpPort)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (configText.isEmpty()) {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return parseConfigViaSub(configText, it.first, false)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun parseConfigViaSub(server: String?, subid: String, append: Boolean): Int {
|
||||||
|
var count = parseBatchConfig(Utils.decode(server), subid, append)
|
||||||
|
if (count <= 0) {
|
||||||
|
count = parseBatchConfig(server, subid, append)
|
||||||
|
}
|
||||||
|
if (count <= 0) {
|
||||||
|
count = parseCustomConfigServer(server, subid)
|
||||||
|
}
|
||||||
|
return count
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -106,8 +106,8 @@ object MmkvManager {
|
|||||||
serverAffStorage?.encode(guid, Gson().toJson(aff))
|
serverAffStorage?.encode(guid, Gson().toJson(aff))
|
||||||
}
|
}
|
||||||
|
|
||||||
fun clearAllTestDelayResults() {
|
fun clearAllTestDelayResults(keys: List<String>?) {
|
||||||
serverAffStorage?.allKeys()?.forEach { key ->
|
keys?.forEach { key ->
|
||||||
decodeServerAffiliationInfo(key)?.let { aff ->
|
decodeServerAffiliationInfo(key)?.let { aff ->
|
||||||
aff.testDelayMillis = 0
|
aff.testDelayMillis = 0
|
||||||
serverAffStorage?.encode(key, Gson().toJson(aff))
|
serverAffStorage?.encode(key, Gson().toJson(aff))
|
||||||
@@ -172,7 +172,7 @@ object MmkvManager {
|
|||||||
fun removeInvalidServer() {
|
fun removeInvalidServer() {
|
||||||
serverAffStorage?.allKeys()?.forEach { key ->
|
serverAffStorage?.allKeys()?.forEach { key ->
|
||||||
decodeServerAffiliationInfo(key)?.let { aff ->
|
decodeServerAffiliationInfo(key)?.let { aff ->
|
||||||
if (aff.testDelayMillis <= 0L) {
|
if (aff.testDelayMillis < 0L) {
|
||||||
removeServer(key)
|
removeServer(key)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,11 +2,16 @@ package com.v2ray.ang.util
|
|||||||
|
|
||||||
import android.graphics.Bitmap
|
import android.graphics.Bitmap
|
||||||
import android.graphics.BitmapFactory
|
import android.graphics.BitmapFactory
|
||||||
import com.google.zxing.*
|
import com.google.zxing.BarcodeFormat
|
||||||
|
import com.google.zxing.BinaryBitmap
|
||||||
|
import com.google.zxing.DecodeHintType
|
||||||
|
import com.google.zxing.EncodeHintType
|
||||||
|
import com.google.zxing.NotFoundException
|
||||||
|
import com.google.zxing.RGBLuminanceSource
|
||||||
import com.google.zxing.common.GlobalHistogramBinarizer
|
import com.google.zxing.common.GlobalHistogramBinarizer
|
||||||
import com.google.zxing.common.HybridBinarizer
|
import com.google.zxing.qrcode.QRCodeReader
|
||||||
import com.google.zxing.qrcode.QRCodeWriter
|
import com.google.zxing.qrcode.QRCodeWriter
|
||||||
import java.util.*
|
import java.util.EnumMap
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 描述:解析二维码图片
|
* 描述:解析二维码图片
|
||||||
@@ -21,8 +26,10 @@ object QRCodeDecoder {
|
|||||||
try {
|
try {
|
||||||
val hints = HashMap<EncodeHintType, String>()
|
val hints = HashMap<EncodeHintType, String>()
|
||||||
hints[EncodeHintType.CHARACTER_SET] = "utf-8"
|
hints[EncodeHintType.CHARACTER_SET] = "utf-8"
|
||||||
val bitMatrix = QRCodeWriter().encode(text,
|
val bitMatrix = QRCodeWriter().encode(
|
||||||
BarcodeFormat.QR_CODE, size, size, hints)
|
text,
|
||||||
|
BarcodeFormat.QR_CODE, size, size, hints
|
||||||
|
)
|
||||||
val pixels = IntArray(size * size)
|
val pixels = IntArray(size * size)
|
||||||
for (y in 0 until size) {
|
for (y in 0 until size) {
|
||||||
for (x in 0 until size) {
|
for (x in 0 until size) {
|
||||||
@@ -34,8 +41,10 @@ object QRCodeDecoder {
|
|||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val bitmap = Bitmap.createBitmap(size, size,
|
val bitmap = Bitmap.createBitmap(
|
||||||
Bitmap.Config.ARGB_8888)
|
size, size,
|
||||||
|
Bitmap.Config.ARGB_8888
|
||||||
|
)
|
||||||
bitmap.setPixels(pixels, 0, size, 0, 0, size, size)
|
bitmap.setPixels(pixels, 0, size, 0, 0, size, size)
|
||||||
return bitmap
|
return bitmap
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
@@ -61,24 +70,37 @@ object QRCodeDecoder {
|
|||||||
* @return 返回二维码图片里的内容 或 null
|
* @return 返回二维码图片里的内容 或 null
|
||||||
*/
|
*/
|
||||||
fun syncDecodeQRCode(bitmap: Bitmap?): String? {
|
fun syncDecodeQRCode(bitmap: Bitmap?): String? {
|
||||||
|
if (bitmap == null) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
var source: RGBLuminanceSource? = null
|
var source: RGBLuminanceSource? = null
|
||||||
try {
|
try {
|
||||||
val width = bitmap!!.width
|
val width = bitmap.width
|
||||||
val height = bitmap.height
|
val height = bitmap.height
|
||||||
val pixels = IntArray(width * height)
|
val pixels = IntArray(width * height)
|
||||||
bitmap.getPixels(pixels, 0, width, 0, 0, width, height)
|
bitmap.getPixels(pixels, 0, width, 0, 0, width, height)
|
||||||
source = RGBLuminanceSource(width, height, pixels)
|
source = RGBLuminanceSource(width, height, pixels)
|
||||||
return MultiFormatReader().decode(BinaryBitmap(HybridBinarizer(source)), HINTS).text
|
val qrReader = QRCodeReader()
|
||||||
|
try {
|
||||||
|
val result = try {
|
||||||
|
qrReader.decode(
|
||||||
|
BinaryBitmap(GlobalHistogramBinarizer(source)),
|
||||||
|
mapOf(DecodeHintType.TRY_HARDER to true)
|
||||||
|
)
|
||||||
|
} catch (e: NotFoundException) {
|
||||||
|
qrReader.decode(
|
||||||
|
BinaryBitmap(GlobalHistogramBinarizer(source.invert())),
|
||||||
|
mapOf(DecodeHintType.TRY_HARDER to true)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return result.text
|
||||||
|
} catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
e.printStackTrace()
|
e.printStackTrace()
|
||||||
}
|
}
|
||||||
if (source != null) {
|
|
||||||
try {
|
|
||||||
return MultiFormatReader().decode(BinaryBitmap(GlobalHistogramBinarizer(source)), HINTS).text
|
|
||||||
} catch (e2: Throwable) {
|
|
||||||
e2.printStackTrace()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -107,23 +129,24 @@ object QRCodeDecoder {
|
|||||||
|
|
||||||
init {
|
init {
|
||||||
val allFormats: List<BarcodeFormat> = arrayListOf(
|
val allFormats: List<BarcodeFormat> = arrayListOf(
|
||||||
BarcodeFormat.AZTEC
|
BarcodeFormat.AZTEC,
|
||||||
,BarcodeFormat.CODABAR
|
BarcodeFormat.CODABAR,
|
||||||
,BarcodeFormat.CODE_39
|
BarcodeFormat.CODE_39,
|
||||||
,BarcodeFormat.CODE_93
|
BarcodeFormat.CODE_93,
|
||||||
,BarcodeFormat.CODE_128
|
BarcodeFormat.CODE_128,
|
||||||
,BarcodeFormat.DATA_MATRIX
|
BarcodeFormat.DATA_MATRIX,
|
||||||
,BarcodeFormat.EAN_8
|
BarcodeFormat.EAN_8,
|
||||||
,BarcodeFormat.EAN_13
|
BarcodeFormat.EAN_13,
|
||||||
,BarcodeFormat.ITF
|
BarcodeFormat.ITF,
|
||||||
,BarcodeFormat.MAXICODE
|
BarcodeFormat.MAXICODE,
|
||||||
,BarcodeFormat.PDF_417
|
BarcodeFormat.PDF_417,
|
||||||
,BarcodeFormat.QR_CODE
|
BarcodeFormat.QR_CODE,
|
||||||
,BarcodeFormat.RSS_14
|
BarcodeFormat.RSS_14,
|
||||||
,BarcodeFormat.RSS_EXPANDED
|
BarcodeFormat.RSS_EXPANDED,
|
||||||
,BarcodeFormat.UPC_A
|
BarcodeFormat.UPC_A,
|
||||||
,BarcodeFormat.UPC_E
|
BarcodeFormat.UPC_E,
|
||||||
,BarcodeFormat.UPC_EAN_EXTENSION)
|
BarcodeFormat.UPC_EAN_EXTENSION
|
||||||
|
)
|
||||||
HINTS[DecodeHintType.TRY_HARDER] = BarcodeFormat.QR_CODE
|
HINTS[DecodeHintType.TRY_HARDER] = BarcodeFormat.QR_CODE
|
||||||
HINTS[DecodeHintType.POSSIBLE_FORMATS] = allFormats
|
HINTS[DecodeHintType.POSSIBLE_FORMATS] = allFormats
|
||||||
HINTS[DecodeHintType.CHARACTER_SET] = "utf-8"
|
HINTS[DecodeHintType.CHARACTER_SET] = "utf-8"
|
||||||
|
|||||||
@@ -10,8 +10,12 @@ import com.v2ray.ang.extension.responseLength
|
|||||||
import kotlinx.coroutines.isActive
|
import kotlinx.coroutines.isActive
|
||||||
import libv2ray.Libv2ray
|
import libv2ray.Libv2ray
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.net.*
|
import java.net.HttpURLConnection
|
||||||
import java.util.*
|
import java.net.InetSocketAddress
|
||||||
|
import java.net.Proxy
|
||||||
|
import java.net.Socket
|
||||||
|
import java.net.URL
|
||||||
|
import java.net.UnknownHostException
|
||||||
import kotlin.coroutines.coroutineContext
|
import kotlin.coroutines.coroutineContext
|
||||||
|
|
||||||
object SpeedtestUtil {
|
object SpeedtestUtil {
|
||||||
@@ -34,7 +38,7 @@ object SpeedtestUtil {
|
|||||||
|
|
||||||
fun realPing(config: String): Long {
|
fun realPing(config: String): Long {
|
||||||
return try {
|
return try {
|
||||||
Libv2ray.measureOutboundDelay(config)
|
Libv2ray.measureOutboundDelay(config, Utils.getDelayTestUrl())
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.d(AppConfig.ANG_PACKAGE, "realPing: $e")
|
Log.d(AppConfig.ANG_PACKAGE, "realPing: $e")
|
||||||
-1L
|
-1L
|
||||||
@@ -98,9 +102,7 @@ object SpeedtestUtil {
|
|||||||
var conn: HttpURLConnection? = null
|
var conn: HttpURLConnection? = null
|
||||||
|
|
||||||
try {
|
try {
|
||||||
val url = URL("https",
|
val url = URL(Utils.getDelayTestUrl())
|
||||||
"www.google.com",
|
|
||||||
"/generate_204")
|
|
||||||
|
|
||||||
conn = url.openConnection(
|
conn = url.openConnection(
|
||||||
Proxy(Proxy.Type.HTTP,
|
Proxy(Proxy.Type.HTTP,
|
||||||
|
|||||||
@@ -39,8 +39,8 @@ object Utils {
|
|||||||
* @param text
|
* @param text
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
fun getEditable(text: String): Editable {
|
fun getEditable(text: String?): Editable {
|
||||||
return Editable.Factory.getInstance().newEditable(text)
|
return Editable.Factory.getInstance().newEditable(text?:"")
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -101,16 +101,16 @@ object Utils {
|
|||||||
/**
|
/**
|
||||||
* base64 decode
|
* base64 decode
|
||||||
*/
|
*/
|
||||||
fun decode(text: String): String {
|
fun decode(text: String?): String {
|
||||||
tryDecodeBase64(text)?.let { return it }
|
tryDecodeBase64(text)?.let { return it }
|
||||||
if (text.endsWith('=')) {
|
if (text?.endsWith('=')==true) {
|
||||||
// try again for some loosely formatted base64
|
// try again for some loosely formatted base64
|
||||||
tryDecodeBase64(text.trimEnd('='))?.let { return it }
|
tryDecodeBase64(text.trimEnd('='))?.let { return it }
|
||||||
}
|
}
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
fun tryDecodeBase64(text: String): String? {
|
fun tryDecodeBase64(text: String?): String? {
|
||||||
try {
|
try {
|
||||||
return Base64.decode(text, Base64.NO_WRAP).toString(charset("UTF-8"))
|
return Base64.decode(text, Base64.NO_WRAP).toString(charset("UTF-8"))
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
@@ -159,7 +159,7 @@ object Utils {
|
|||||||
*/
|
*/
|
||||||
fun getDomesticDnsServers(): List<String> {
|
fun getDomesticDnsServers(): List<String> {
|
||||||
val domesticDns = settingsStorage?.decodeString(AppConfig.PREF_DOMESTIC_DNS) ?: AppConfig.DNS_DIRECT
|
val domesticDns = settingsStorage?.decodeString(AppConfig.PREF_DOMESTIC_DNS) ?: AppConfig.DNS_DIRECT
|
||||||
val ret = domesticDns.split(",").filter { isPureIpAddress(it) }
|
val ret = domesticDns.split(",").filter { isPureIpAddress(it) || isCoreDNSAddress(it) }
|
||||||
if (ret.isEmpty()) {
|
if (ret.isEmpty()) {
|
||||||
return listOf(AppConfig.DNS_DIRECT)
|
return listOf(AppConfig.DNS_DIRECT)
|
||||||
}
|
}
|
||||||
@@ -302,7 +302,11 @@ object Utils {
|
|||||||
/**
|
/**
|
||||||
* readTextFromAssets
|
* readTextFromAssets
|
||||||
*/
|
*/
|
||||||
fun readTextFromAssets(context: Context, fileName: String): String {
|
fun readTextFromAssets(context: Context?, fileName: String): String {
|
||||||
|
if(context == null)
|
||||||
|
{
|
||||||
|
return ""
|
||||||
|
}
|
||||||
val content = context.assets.open(fileName).bufferedReader().use {
|
val content = context.assets.open(fileName).bufferedReader().use {
|
||||||
it.readText()
|
it.readText()
|
||||||
}
|
}
|
||||||
@@ -352,9 +356,20 @@ object Utils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Throws(IOException::class)
|
@Throws(IOException::class)
|
||||||
fun getUrlContentWithCustomUserAgent(urlStr: String?): String {
|
fun getUrlContentWithCustomUserAgent(urlStr: String?, timeout: Int = 30000, httpPort: Int = 0): String {
|
||||||
val url = URL(urlStr)
|
val url = URL(urlStr)
|
||||||
val conn = url.openConnection()
|
val conn = if (httpPort == 0) {
|
||||||
|
url.openConnection()
|
||||||
|
} else {
|
||||||
|
url.openConnection(
|
||||||
|
Proxy(
|
||||||
|
Proxy.Type.HTTP,
|
||||||
|
InetSocketAddress("127.0.0.1", httpPort)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
conn.connectTimeout = timeout
|
||||||
|
conn.readTimeout = timeout
|
||||||
conn.setRequestProperty("Connection", "close")
|
conn.setRequestProperty("Connection", "close")
|
||||||
conn.setRequestProperty("User-agent", "v2rayNG/${BuildConfig.VERSION_NAME}")
|
conn.setRequestProperty("User-agent", "v2rayNG/${BuildConfig.VERSION_NAME}")
|
||||||
url.userInfo?.let {
|
url.userInfo?.let {
|
||||||
@@ -380,7 +395,10 @@ object Utils {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getIpv6Address(address: String): String {
|
fun getIpv6Address(address: String?): String {
|
||||||
|
if(address == null){
|
||||||
|
return ""
|
||||||
|
}
|
||||||
return if (isIpv6Address(address) && !address.contains('[') && !address.contains(']')) {
|
return if (isIpv6Address(address) && !address.contains('[') && !address.contains(']')) {
|
||||||
String.format("[%s]", address)
|
String.format("[%s]", address)
|
||||||
} else {
|
} else {
|
||||||
@@ -425,5 +443,18 @@ object Utils {
|
|||||||
fun isTv(context: Context): Boolean =
|
fun isTv(context: Context): Boolean =
|
||||||
context.packageManager.hasSystemFeature(PackageManager.FEATURE_LEANBACK)
|
context.packageManager.hasSystemFeature(PackageManager.FEATURE_LEANBACK)
|
||||||
|
|
||||||
|
fun getDelayTestUrl(): String {
|
||||||
|
val url = settingsStorage.decodeString(AppConfig.PREF_DELAY_TEST_URL)
|
||||||
|
return if (url.isNullOrEmpty()) AppConfig.DelayTestUrl else url
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getDelayTestUrl(second: Boolean = false): String {
|
||||||
|
return if (second) {
|
||||||
|
AppConfig.DelayTestUrl2
|
||||||
|
} else {
|
||||||
|
val url = settingsStorage.decodeString(AppConfig.PREF_DELAY_TEST_URL)
|
||||||
|
if (url.isNullOrEmpty()) AppConfig.DelayTestUrl else url
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,17 +2,19 @@ package com.v2ray.ang.util
|
|||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.text.TextUtils
|
import android.text.TextUtils
|
||||||
import com.google.gson.*
|
import android.util.Log
|
||||||
|
import com.google.gson.Gson
|
||||||
import com.tencent.mmkv.MMKV
|
import com.tencent.mmkv.MMKV
|
||||||
import com.v2ray.ang.AppConfig
|
import com.v2ray.ang.AppConfig
|
||||||
|
import com.v2ray.ang.AppConfig.ANG_PACKAGE
|
||||||
import com.v2ray.ang.AppConfig.PROTOCOL_FREEDOM
|
import com.v2ray.ang.AppConfig.PROTOCOL_FREEDOM
|
||||||
import com.v2ray.ang.AppConfig.TAG_DIRECT
|
import com.v2ray.ang.AppConfig.TAG_DIRECT
|
||||||
import com.v2ray.ang.AppConfig.TAG_FRAGMENT
|
import com.v2ray.ang.AppConfig.TAG_FRAGMENT
|
||||||
import com.v2ray.ang.AppConfig.WIREGUARD_LOCAL_ADDRESS_V4
|
import com.v2ray.ang.AppConfig.WIREGUARD_LOCAL_ADDRESS_V4
|
||||||
import com.v2ray.ang.AppConfig.WIREGUARD_LOCAL_ADDRESS_V6
|
import com.v2ray.ang.AppConfig.WIREGUARD_LOCAL_ADDRESS_V6
|
||||||
import com.v2ray.ang.dto.V2rayConfig
|
|
||||||
import com.v2ray.ang.dto.EConfigType
|
import com.v2ray.ang.dto.EConfigType
|
||||||
import com.v2ray.ang.dto.ERoutingMode
|
import com.v2ray.ang.dto.ERoutingMode
|
||||||
|
import com.v2ray.ang.dto.V2rayConfig
|
||||||
import com.v2ray.ang.dto.V2rayConfig.Companion.DEFAULT_NETWORK
|
import com.v2ray.ang.dto.V2rayConfig.Companion.DEFAULT_NETWORK
|
||||||
import com.v2ray.ang.dto.V2rayConfig.Companion.HTTP
|
import com.v2ray.ang.dto.V2rayConfig.Companion.HTTP
|
||||||
|
|
||||||
@@ -49,6 +51,12 @@ object V2rayConfigUtil {
|
|||||||
return Result(true, customConfig)
|
return Result(true, customConfig)
|
||||||
}
|
}
|
||||||
val outbound = config.getProxyOutbound() ?: return Result(false, "")
|
val outbound = config.getProxyOutbound() ?: return Result(false, "")
|
||||||
|
val address = outbound.getServerAddress() ?: return Result(false, "")
|
||||||
|
if (!Utils.isIpAddress(address) && !Utils.isValidUrl(address)) {
|
||||||
|
Log.d(ANG_PACKAGE, "$address is an invalid ip or domain")
|
||||||
|
return Result(false, "")
|
||||||
|
}
|
||||||
|
|
||||||
val result = getV2rayNonCustomConfig(context, outbound, config.remarks)
|
val result = getV2rayNonCustomConfig(context, outbound, config.remarks)
|
||||||
//Log.d(ANG_PACKAGE, result.content)
|
//Log.d(ANG_PACKAGE, result.content)
|
||||||
return result
|
return result
|
||||||
@@ -134,6 +142,7 @@ object V2rayConfigUtil {
|
|||||||
settingsStorage?.decodeBool(AppConfig.PREF_SNIFFING_ENABLED, true)
|
settingsStorage?.decodeBool(AppConfig.PREF_SNIFFING_ENABLED, true)
|
||||||
?: true
|
?: true
|
||||||
v2rayConfig.inbounds[0].sniffing?.enabled = fakedns || sniffAllTlsAndHttp
|
v2rayConfig.inbounds[0].sniffing?.enabled = fakedns || sniffAllTlsAndHttp
|
||||||
|
v2rayConfig.inbounds[0].sniffing?.routeOnly = settingsStorage?.decodeBool(AppConfig.PREF_ROUTE_ONLY_ENABLED, false)
|
||||||
if (!sniffAllTlsAndHttp) {
|
if (!sniffAllTlsAndHttp) {
|
||||||
v2rayConfig.inbounds[0].sniffing?.destOverride?.clear()
|
v2rayConfig.inbounds[0].sniffing?.destOverride?.clear()
|
||||||
}
|
}
|
||||||
@@ -158,7 +167,7 @@ object V2rayConfigUtil {
|
|||||||
|
|
||||||
private fun fakedns(v2rayConfig: V2rayConfig) {
|
private fun fakedns(v2rayConfig: V2rayConfig) {
|
||||||
if (settingsStorage?.decodeBool(AppConfig.PREF_LOCAL_DNS_ENABLED) == true
|
if (settingsStorage?.decodeBool(AppConfig.PREF_LOCAL_DNS_ENABLED) == true
|
||||||
|| settingsStorage?.decodeBool(AppConfig.PREF_FAKE_DNS_ENABLED) == true
|
&& settingsStorage?.decodeBool(AppConfig.PREF_FAKE_DNS_ENABLED) == true
|
||||||
) {
|
) {
|
||||||
v2rayConfig.fakedns = listOf(V2rayConfig.FakednsBean())
|
v2rayConfig.fakedns = listOf(V2rayConfig.FakednsBean())
|
||||||
v2rayConfig.outbounds.filter { it.protocol == PROTOCOL_FREEDOM && it.tag == TAG_DIRECT }
|
v2rayConfig.outbounds.filter { it.protocol == PROTOCOL_FREEDOM && it.tag == TAG_DIRECT }
|
||||||
@@ -193,10 +202,10 @@ object V2rayConfigUtil {
|
|||||||
val routingMode = settingsStorage?.decodeString(AppConfig.PREF_ROUTING_MODE)
|
val routingMode = settingsStorage?.decodeString(AppConfig.PREF_ROUTING_MODE)
|
||||||
?: ERoutingMode.BYPASS_LAN_MAINLAND.value
|
?: ERoutingMode.BYPASS_LAN_MAINLAND.value
|
||||||
|
|
||||||
// Hardcode googleapis.cn
|
// Hardcode googleapis.cn gstatic.com
|
||||||
val googleapisRoute = V2rayConfig.RoutingBean.RulesBean(
|
val googleapisRoute = V2rayConfig.RoutingBean.RulesBean(
|
||||||
outboundTag = AppConfig.TAG_PROXY,
|
outboundTag = AppConfig.TAG_PROXY,
|
||||||
domain = arrayListOf("domain:googleapis.cn")
|
domain = arrayListOf("domain:googleapis.cn", "domain:gstatic.com")
|
||||||
)
|
)
|
||||||
|
|
||||||
when (routingMode) {
|
when (routingMode) {
|
||||||
@@ -286,22 +295,18 @@ object V2rayConfigUtil {
|
|||||||
rulesIP.ip = ArrayList()
|
rulesIP.ip = ArrayList()
|
||||||
|
|
||||||
userRule.split(",").map { it.trim() }.forEach {
|
userRule.split(",").map { it.trim() }.forEach {
|
||||||
if (Utils.isIpAddress(it) || it.startsWith("geoip:")) {
|
if (it.startsWith("ext:") && it.contains("geoip")) {
|
||||||
rulesIP.ip?.add(it)
|
rulesIP.ip?.add(it)
|
||||||
} else if (it.isNotEmpty())
|
} else if (Utils.isIpAddress(it) || it.startsWith("geoip:")) {
|
||||||
// if (Utils.isValidUrl(it)
|
rulesIP.ip?.add(it)
|
||||||
// || it.startsWith("geosite:")
|
} else if (it.isNotEmpty()) {
|
||||||
// || it.startsWith("regexp:")
|
|
||||||
// || it.startsWith("domain:")
|
|
||||||
// || it.startsWith("full:"))
|
|
||||||
{
|
|
||||||
rulesDomain.domain?.add(it)
|
rulesDomain.domain?.add(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (rulesDomain.domain?.size!! > 0) {
|
if ((rulesDomain.domain?.size ?: 0) > 0) {
|
||||||
v2rayConfig.routing.rules.add(rulesDomain)
|
v2rayConfig.routing.rules.add(rulesDomain)
|
||||||
}
|
}
|
||||||
if (rulesIP.ip?.size!! > 0) {
|
if ((rulesIP.ip?.size ?: 0) > 0) {
|
||||||
v2rayConfig.routing.rules.add(rulesIP)
|
v2rayConfig.routing.rules.add(rulesIP)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -402,12 +407,13 @@ object V2rayConfigUtil {
|
|||||||
try {
|
try {
|
||||||
val hosts = mutableMapOf<String, String>()
|
val hosts = mutableMapOf<String, String>()
|
||||||
val servers = ArrayList<Any>()
|
val servers = ArrayList<Any>()
|
||||||
|
|
||||||
|
//remote Dns
|
||||||
val remoteDns = Utils.getRemoteDnsServers()
|
val remoteDns = Utils.getRemoteDnsServers()
|
||||||
val proxyDomain = userRule2Domian(
|
val proxyDomain = userRule2Domian(
|
||||||
settingsStorage?.decodeString(AppConfig.PREF_V2RAY_ROUTING_AGENT)
|
settingsStorage?.decodeString(AppConfig.PREF_V2RAY_ROUTING_AGENT)
|
||||||
?: ""
|
?: ""
|
||||||
)
|
)
|
||||||
|
|
||||||
remoteDns.forEach {
|
remoteDns.forEach {
|
||||||
servers.add(it)
|
servers.add(it)
|
||||||
}
|
}
|
||||||
@@ -423,48 +429,51 @@ object V2rayConfigUtil {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// domestic DNS
|
// domestic DNS
|
||||||
|
val domesticDns = Utils.getDomesticDnsServers()
|
||||||
val directDomain = userRule2Domian(
|
val directDomain = userRule2Domian(
|
||||||
settingsStorage?.decodeString(AppConfig.PREF_V2RAY_ROUTING_DIRECT)
|
settingsStorage?.decodeString(AppConfig.PREF_V2RAY_ROUTING_DIRECT)
|
||||||
?: ""
|
?: ""
|
||||||
)
|
)
|
||||||
val routingMode = settingsStorage?.decodeString(AppConfig.PREF_ROUTING_MODE)
|
val routingMode = settingsStorage?.decodeString(AppConfig.PREF_ROUTING_MODE)
|
||||||
?: ERoutingMode.BYPASS_LAN_MAINLAND.value
|
?: ERoutingMode.BYPASS_LAN_MAINLAND.value
|
||||||
if (directDomain.size > 0 || routingMode == ERoutingMode.BYPASS_MAINLAND.value || routingMode == ERoutingMode.BYPASS_LAN_MAINLAND.value) {
|
val isCnRoutingMode =
|
||||||
val domesticDns = Utils.getDomesticDnsServers()
|
(routingMode == ERoutingMode.BYPASS_MAINLAND.value || routingMode == ERoutingMode.BYPASS_LAN_MAINLAND.value)
|
||||||
val geositeCn = arrayListOf("geosite:cn","geosite:geolocation-cn")
|
val geoipCn = arrayListOf("geoip:cn")
|
||||||
val geoipCn = arrayListOf("geoip:cn")
|
|
||||||
if (directDomain.size > 0) {
|
if (directDomain.size > 0) {
|
||||||
servers.add(
|
servers.add(
|
||||||
V2rayConfig.DnsBean.ServersBean(
|
V2rayConfig.DnsBean.ServersBean(
|
||||||
domesticDns.first(),
|
domesticDns.first(),
|
||||||
53,
|
53,
|
||||||
directDomain,
|
directDomain,
|
||||||
geoipCn
|
if (isCnRoutingMode) geoipCn else null
|
||||||
)
|
|
||||||
)
|
)
|
||||||
}
|
)
|
||||||
if (routingMode == ERoutingMode.BYPASS_MAINLAND.value || routingMode == ERoutingMode.BYPASS_LAN_MAINLAND.value) {
|
}
|
||||||
servers.add(
|
if (isCnRoutingMode) {
|
||||||
V2rayConfig.DnsBean.ServersBean(
|
val geositeCn = arrayListOf("geosite:cn", "geosite:geolocation-cn")
|
||||||
domesticDns.first(),
|
servers.add(
|
||||||
53,
|
V2rayConfig.DnsBean.ServersBean(
|
||||||
geositeCn,
|
domesticDns.first(),
|
||||||
geoipCn
|
53,
|
||||||
)
|
geositeCn,
|
||||||
|
geoipCn
|
||||||
)
|
)
|
||||||
}
|
)
|
||||||
if (Utils.isPureIpAddress(domesticDns.first())) {
|
|
||||||
v2rayConfig.routing.rules.add(
|
|
||||||
0, V2rayConfig.RoutingBean.RulesBean(
|
|
||||||
outboundTag = AppConfig.TAG_DIRECT,
|
|
||||||
port = "53",
|
|
||||||
ip = arrayListOf(domesticDns.first()),
|
|
||||||
domain = null
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (Utils.isPureIpAddress(domesticDns.first())) {
|
||||||
|
v2rayConfig.routing.rules.add(
|
||||||
|
0, V2rayConfig.RoutingBean.RulesBean(
|
||||||
|
outboundTag = AppConfig.TAG_DIRECT,
|
||||||
|
port = "53",
|
||||||
|
ip = arrayListOf(domesticDns.first()),
|
||||||
|
domain = null
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
//block dns
|
||||||
val blkDomain = userRule2Domian(
|
val blkDomain = userRule2Domian(
|
||||||
settingsStorage?.decodeString(AppConfig.PREF_V2RAY_ROUTING_BLOCKED)
|
settingsStorage?.decodeString(AppConfig.PREF_V2RAY_ROUTING_BLOCKED)
|
||||||
?: ""
|
?: ""
|
||||||
@@ -559,7 +568,7 @@ object V2rayConfigUtil {
|
|||||||
} else {
|
} else {
|
||||||
path
|
path
|
||||||
}
|
}
|
||||||
outbound.streamSettings?.tcpSettings?.header?.request?.headers?.Host = host!!
|
outbound.streamSettings?.tcpSettings?.header?.request?.headers?.Host = host
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,159 @@
|
|||||||
|
package com.v2ray.ang.util.fmt
|
||||||
|
|
||||||
|
import android.util.Log
|
||||||
|
import com.v2ray.ang.AppConfig
|
||||||
|
import com.v2ray.ang.dto.EConfigType
|
||||||
|
import com.v2ray.ang.dto.ServerConfig
|
||||||
|
import com.v2ray.ang.extension.idnHost
|
||||||
|
import com.v2ray.ang.util.Utils
|
||||||
|
import java.net.URI
|
||||||
|
|
||||||
|
object ShadowsocksFmt {
|
||||||
|
fun parseShadowsocks(str: String): ServerConfig? {
|
||||||
|
val config = ServerConfig.create(EConfigType.SHADOWSOCKS)
|
||||||
|
if (!tryResolveResolveSip002(str, config)) {
|
||||||
|
var result = str.replace(EConfigType.SHADOWSOCKS.protocolScheme, "")
|
||||||
|
val indexSplit = result.indexOf("#")
|
||||||
|
if (indexSplit > 0) {
|
||||||
|
try {
|
||||||
|
config.remarks =
|
||||||
|
Utils.urlDecode(result.substring(indexSplit + 1, result.length))
|
||||||
|
} catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
}
|
||||||
|
|
||||||
|
result = result.substring(0, indexSplit)
|
||||||
|
}
|
||||||
|
|
||||||
|
//part decode
|
||||||
|
val indexS = result.indexOf("@")
|
||||||
|
result = if (indexS > 0) {
|
||||||
|
Utils.decode(result.substring(0, indexS)) + result.substring(
|
||||||
|
indexS,
|
||||||
|
result.length
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
Utils.decode(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
val legacyPattern = "^(.+?):(.*)@(.+?):(\\d+?)/?$".toRegex()
|
||||||
|
val match = legacyPattern.matchEntire(result)
|
||||||
|
?: return null
|
||||||
|
|
||||||
|
config.outboundBean?.settings?.servers?.get(0)?.let { server ->
|
||||||
|
server.address = match.groupValues[3].removeSurrounding("[", "]")
|
||||||
|
server.port = match.groupValues[4].toInt()
|
||||||
|
server.password = match.groupValues[2]
|
||||||
|
server.method = match.groupValues[1].lowercase()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return config
|
||||||
|
}
|
||||||
|
|
||||||
|
fun toUri(config: ServerConfig): String {
|
||||||
|
val outbound = config.getProxyOutbound() ?: return ""
|
||||||
|
val remark = "#" + Utils.urlEncode(config.remarks)
|
||||||
|
val pw =
|
||||||
|
Utils.encode("${outbound.getSecurityEncryption()}:${outbound.getPassword()}")
|
||||||
|
val url = String.format(
|
||||||
|
"%s@%s:%s",
|
||||||
|
pw,
|
||||||
|
Utils.getIpv6Address(outbound.getServerAddress()),
|
||||||
|
outbound.getServerPort()
|
||||||
|
)
|
||||||
|
return url + remark
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun tryResolveResolveSip002(str: String, config: ServerConfig): Boolean {
|
||||||
|
try {
|
||||||
|
val uri = URI(Utils.fixIllegalUrl(str))
|
||||||
|
config.remarks = Utils.urlDecode(uri.fragment ?: "")
|
||||||
|
|
||||||
|
val method: String
|
||||||
|
val password: String
|
||||||
|
if (uri.userInfo.contains(":")) {
|
||||||
|
val arrUserInfo = uri.userInfo.split(":").map { it.trim() }
|
||||||
|
if (arrUserInfo.count() != 2) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
method = arrUserInfo[0]
|
||||||
|
password = Utils.urlDecode(arrUserInfo[1])
|
||||||
|
} else {
|
||||||
|
val base64Decode = Utils.decode(uri.userInfo)
|
||||||
|
val arrUserInfo = base64Decode.split(":").map { it.trim() }
|
||||||
|
if (arrUserInfo.count() < 2) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
method = arrUserInfo[0]
|
||||||
|
password = base64Decode.substringAfter(":")
|
||||||
|
}
|
||||||
|
|
||||||
|
val query = Utils.urlDecode(uri.query ?: "")
|
||||||
|
if (query != "") {
|
||||||
|
val queryPairs = HashMap<String, String>()
|
||||||
|
val pairs = query.split(";")
|
||||||
|
Log.d(AppConfig.ANG_PACKAGE, pairs.toString())
|
||||||
|
for (pair in pairs) {
|
||||||
|
val idx = pair.indexOf("=")
|
||||||
|
if (idx == -1) {
|
||||||
|
queryPairs[Utils.urlDecode(pair)] = "";
|
||||||
|
} else {
|
||||||
|
queryPairs[Utils.urlDecode(pair.substring(0, idx))] =
|
||||||
|
Utils.urlDecode(pair.substring(idx + 1))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Log.d(AppConfig.ANG_PACKAGE, queryPairs.toString())
|
||||||
|
var sni: String? = ""
|
||||||
|
if (queryPairs["plugin"] == "obfs-local" && queryPairs["obfs"] == "http") {
|
||||||
|
sni = config.outboundBean?.streamSettings?.populateTransportSettings(
|
||||||
|
"tcp",
|
||||||
|
"http",
|
||||||
|
queryPairs["obfs-host"],
|
||||||
|
queryPairs["path"],
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null
|
||||||
|
)
|
||||||
|
} else if (queryPairs["plugin"] == "v2ray-plugin") {
|
||||||
|
var network = "ws";
|
||||||
|
if (queryPairs["mode"] == "quic") {
|
||||||
|
network = "quic";
|
||||||
|
}
|
||||||
|
sni = config.outboundBean?.streamSettings?.populateTransportSettings(
|
||||||
|
network,
|
||||||
|
null,
|
||||||
|
queryPairs["host"],
|
||||||
|
queryPairs["path"],
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if ("tls" in queryPairs) {
|
||||||
|
config.outboundBean?.streamSettings?.populateTlsSettings(
|
||||||
|
"tls", false, sni ?: "", null, null, null, null, null
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
config.outboundBean?.settings?.servers?.get(0)?.let { server ->
|
||||||
|
server.address = uri.idnHost
|
||||||
|
server.port = uri.port
|
||||||
|
server.password = password
|
||||||
|
server.method = method
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.d(AppConfig.ANG_PACKAGE, e.toString())
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
package com.v2ray.ang.util.fmt
|
||||||
|
|
||||||
|
import com.v2ray.ang.dto.EConfigType
|
||||||
|
import com.v2ray.ang.dto.ServerConfig
|
||||||
|
import com.v2ray.ang.dto.V2rayConfig
|
||||||
|
import com.v2ray.ang.util.Utils
|
||||||
|
|
||||||
|
object SocksFmt {
|
||||||
|
fun parseSocks(str: String): ServerConfig? {
|
||||||
|
val config = ServerConfig.create(EConfigType.SOCKS)
|
||||||
|
var result = str.replace(EConfigType.SOCKS.protocolScheme, "")
|
||||||
|
val indexSplit = result.indexOf("#")
|
||||||
|
|
||||||
|
if (indexSplit > 0) {
|
||||||
|
try {
|
||||||
|
config.remarks =
|
||||||
|
Utils.urlDecode(result.substring(indexSplit + 1, result.length))
|
||||||
|
} catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
}
|
||||||
|
|
||||||
|
result = result.substring(0, indexSplit)
|
||||||
|
}
|
||||||
|
|
||||||
|
//part decode
|
||||||
|
val indexS = result.indexOf("@")
|
||||||
|
if (indexS > 0) {
|
||||||
|
result = Utils.decode(result.substring(0, indexS)) + result.substring(
|
||||||
|
indexS,
|
||||||
|
result.length
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
result = Utils.decode(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
val legacyPattern = "^(.*):(.*)@(.+?):(\\d+?)$".toRegex()
|
||||||
|
val match =
|
||||||
|
legacyPattern.matchEntire(result) ?: return null
|
||||||
|
|
||||||
|
config.outboundBean?.settings?.servers?.get(0)?.let { server ->
|
||||||
|
server.address = match.groupValues[3].removeSurrounding("[", "]")
|
||||||
|
server.port = match.groupValues[4].toInt()
|
||||||
|
val socksUsersBean =
|
||||||
|
V2rayConfig.OutboundBean.OutSettingsBean.ServersBean.SocksUsersBean()
|
||||||
|
socksUsersBean.user = match.groupValues[1]
|
||||||
|
socksUsersBean.pass = match.groupValues[2]
|
||||||
|
server.users = listOf(socksUsersBean)
|
||||||
|
}
|
||||||
|
|
||||||
|
return config
|
||||||
|
}
|
||||||
|
|
||||||
|
fun toUri(config: ServerConfig): String {
|
||||||
|
val outbound = config.getProxyOutbound() ?: return ""
|
||||||
|
val remark = "#" + Utils.urlEncode(config.remarks)
|
||||||
|
val pw =
|
||||||
|
if (outbound.settings?.servers?.get(0)?.users?.get(0)?.user != null)
|
||||||
|
"${outbound.settings?.servers?.get(0)?.users?.get(0)?.user}:${outbound.getPassword()}"
|
||||||
|
else
|
||||||
|
":"
|
||||||
|
val url = String.format(
|
||||||
|
"%s@%s:%s",
|
||||||
|
Utils.encode(pw),
|
||||||
|
Utils.getIpv6Address(outbound.getServerAddress()),
|
||||||
|
outbound.getServerPort()
|
||||||
|
)
|
||||||
|
return url + remark
|
||||||
|
}
|
||||||
|
}
|
||||||
170
V2rayNG/app/src/main/kotlin/com/v2ray/ang/util/fmt/TrojanFmt.kt
Normal file
170
V2rayNG/app/src/main/kotlin/com/v2ray/ang/util/fmt/TrojanFmt.kt
Normal file
@@ -0,0 +1,170 @@
|
|||||||
|
package com.v2ray.ang.util.fmt
|
||||||
|
|
||||||
|
import android.text.TextUtils
|
||||||
|
import com.tencent.mmkv.MMKV
|
||||||
|
import com.v2ray.ang.AppConfig
|
||||||
|
import com.v2ray.ang.dto.EConfigType
|
||||||
|
import com.v2ray.ang.dto.ServerConfig
|
||||||
|
import com.v2ray.ang.dto.V2rayConfig
|
||||||
|
import com.v2ray.ang.extension.idnHost
|
||||||
|
import com.v2ray.ang.util.MmkvManager
|
||||||
|
import com.v2ray.ang.util.Utils
|
||||||
|
import java.net.URI
|
||||||
|
|
||||||
|
object TrojanFmt {
|
||||||
|
private val settingsStorage by lazy {
|
||||||
|
MMKV.mmkvWithID(
|
||||||
|
MmkvManager.ID_SETTING,
|
||||||
|
MMKV.MULTI_PROCESS_MODE
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun parseTrojan(str: String): ServerConfig? {
|
||||||
|
val allowInsecure = settingsStorage?.decodeBool(AppConfig.PREF_ALLOW_INSECURE) ?: false
|
||||||
|
val config = ServerConfig.create(EConfigType.TROJAN)
|
||||||
|
|
||||||
|
val uri = URI(Utils.fixIllegalUrl(str))
|
||||||
|
config.remarks = Utils.urlDecode(uri.fragment ?: "")
|
||||||
|
|
||||||
|
var flow = ""
|
||||||
|
var fingerprint = config.outboundBean?.streamSettings?.tlsSettings?.fingerprint
|
||||||
|
if (uri.rawQuery.isNullOrEmpty()) {
|
||||||
|
config.outboundBean?.streamSettings?.populateTlsSettings(
|
||||||
|
V2rayConfig.TLS, allowInsecure, "",
|
||||||
|
fingerprint, null, null, null, null
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
val queryParam = uri.rawQuery.split("&")
|
||||||
|
.associate { it.split("=").let { (k, v) -> k to Utils.urlDecode(v) } }
|
||||||
|
|
||||||
|
val sni = config.outboundBean?.streamSettings?.populateTransportSettings(
|
||||||
|
queryParam["type"] ?: "tcp",
|
||||||
|
queryParam["headerType"],
|
||||||
|
queryParam["host"],
|
||||||
|
queryParam["path"],
|
||||||
|
queryParam["seed"],
|
||||||
|
queryParam["quicSecurity"],
|
||||||
|
queryParam["key"],
|
||||||
|
queryParam["mode"],
|
||||||
|
queryParam["serviceName"],
|
||||||
|
queryParam["authority"]
|
||||||
|
)
|
||||||
|
fingerprint = queryParam["fp"] ?: ""
|
||||||
|
config.outboundBean?.streamSettings?.populateTlsSettings(
|
||||||
|
queryParam["security"] ?: V2rayConfig.TLS,
|
||||||
|
allowInsecure, queryParam["sni"] ?: sni?:"", fingerprint, queryParam["alpn"],
|
||||||
|
null, null, null
|
||||||
|
)
|
||||||
|
flow = queryParam["flow"] ?: ""
|
||||||
|
}
|
||||||
|
|
||||||
|
config.outboundBean?.settings?.servers?.get(0)?.let { server ->
|
||||||
|
server.address = uri.idnHost
|
||||||
|
server.port = uri.port
|
||||||
|
server.password = uri.userInfo
|
||||||
|
server.flow = flow
|
||||||
|
}
|
||||||
|
|
||||||
|
return config
|
||||||
|
}
|
||||||
|
|
||||||
|
fun toUri(config: ServerConfig): String {
|
||||||
|
val outbound = config.getProxyOutbound() ?: return ""
|
||||||
|
val streamSetting = outbound.streamSettings ?: V2rayConfig.OutboundBean.StreamSettingsBean()
|
||||||
|
|
||||||
|
val remark = "#" + Utils.urlEncode(config.remarks)
|
||||||
|
val dicQuery = HashMap<String, String>()
|
||||||
|
config.outboundBean?.settings?.servers?.get(0)?.flow?.let {
|
||||||
|
if (!TextUtils.isEmpty(it)) {
|
||||||
|
dicQuery["flow"] = it
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
dicQuery["security"] = streamSetting.security.ifEmpty { "none" }
|
||||||
|
(streamSetting.tlsSettings
|
||||||
|
?: streamSetting.realitySettings)?.let { tlsSetting ->
|
||||||
|
if (!TextUtils.isEmpty(tlsSetting.serverName)) {
|
||||||
|
dicQuery["sni"] = tlsSetting.serverName
|
||||||
|
}
|
||||||
|
if (!tlsSetting.alpn.isNullOrEmpty() && tlsSetting.alpn.isNotEmpty()) {
|
||||||
|
dicQuery["alpn"] =
|
||||||
|
Utils.removeWhiteSpace(tlsSetting.alpn.joinToString()).orEmpty()
|
||||||
|
}
|
||||||
|
if (!TextUtils.isEmpty(tlsSetting.fingerprint)) {
|
||||||
|
dicQuery["fp"] = tlsSetting.fingerprint?:""
|
||||||
|
}
|
||||||
|
if (!TextUtils.isEmpty(tlsSetting.publicKey)) {
|
||||||
|
dicQuery["pbk"] = tlsSetting.publicKey?:""
|
||||||
|
}
|
||||||
|
if (!TextUtils.isEmpty(tlsSetting.shortId)) {
|
||||||
|
dicQuery["sid"] = tlsSetting.shortId?:""
|
||||||
|
}
|
||||||
|
if (!TextUtils.isEmpty(tlsSetting.spiderX)) {
|
||||||
|
dicQuery["spx"] = Utils.urlEncode(tlsSetting.spiderX?:"")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dicQuery["type"] =
|
||||||
|
streamSetting.network.ifEmpty { V2rayConfig.DEFAULT_NETWORK }
|
||||||
|
|
||||||
|
outbound.getTransportSettingDetails()?.let { transportDetails ->
|
||||||
|
when (streamSetting.network) {
|
||||||
|
"tcp" -> {
|
||||||
|
dicQuery["headerType"] = transportDetails[0].ifEmpty { "none" }
|
||||||
|
if (!TextUtils.isEmpty(transportDetails[1])) {
|
||||||
|
dicQuery["host"] = Utils.urlEncode(transportDetails[1])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
"kcp" -> {
|
||||||
|
dicQuery["headerType"] = transportDetails[0].ifEmpty { "none" }
|
||||||
|
if (!TextUtils.isEmpty(transportDetails[2])) {
|
||||||
|
dicQuery["seed"] = Utils.urlEncode(transportDetails[2])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
"ws", "httpupgrade", "splithttp" -> {
|
||||||
|
if (!TextUtils.isEmpty(transportDetails[1])) {
|
||||||
|
dicQuery["host"] = Utils.urlEncode(transportDetails[1])
|
||||||
|
}
|
||||||
|
if (!TextUtils.isEmpty(transportDetails[2])) {
|
||||||
|
dicQuery["path"] = Utils.urlEncode(transportDetails[2])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
"http", "h2" -> {
|
||||||
|
dicQuery["type"] = "http"
|
||||||
|
if (!TextUtils.isEmpty(transportDetails[1])) {
|
||||||
|
dicQuery["host"] = Utils.urlEncode(transportDetails[1])
|
||||||
|
}
|
||||||
|
if (!TextUtils.isEmpty(transportDetails[2])) {
|
||||||
|
dicQuery["path"] = Utils.urlEncode(transportDetails[2])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
"quic" -> {
|
||||||
|
dicQuery["headerType"] = transportDetails[0].ifEmpty { "none" }
|
||||||
|
dicQuery["quicSecurity"] = Utils.urlEncode(transportDetails[1])
|
||||||
|
dicQuery["key"] = Utils.urlEncode(transportDetails[2])
|
||||||
|
}
|
||||||
|
|
||||||
|
"grpc" -> {
|
||||||
|
dicQuery["mode"] = transportDetails[0]
|
||||||
|
dicQuery["authority"] = Utils.urlEncode(transportDetails[1])
|
||||||
|
dicQuery["serviceName"] = Utils.urlEncode(transportDetails[2])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val query = "?" + dicQuery.toList().joinToString(
|
||||||
|
separator = "&",
|
||||||
|
transform = { it.first + "=" + it.second })
|
||||||
|
|
||||||
|
val url = String.format(
|
||||||
|
"%s@%s:%s",
|
||||||
|
outbound.getPassword(),
|
||||||
|
Utils.getIpv6Address(outbound.getServerAddress()),
|
||||||
|
outbound.getServerPort()
|
||||||
|
)
|
||||||
|
return url + query + remark
|
||||||
|
}
|
||||||
|
}
|
||||||
170
V2rayNG/app/src/main/kotlin/com/v2ray/ang/util/fmt/VlessFmt.kt
Normal file
170
V2rayNG/app/src/main/kotlin/com/v2ray/ang/util/fmt/VlessFmt.kt
Normal file
@@ -0,0 +1,170 @@
|
|||||||
|
package com.v2ray.ang.util.fmt
|
||||||
|
|
||||||
|
import android.text.TextUtils
|
||||||
|
import com.tencent.mmkv.MMKV
|
||||||
|
import com.v2ray.ang.AppConfig
|
||||||
|
import com.v2ray.ang.dto.EConfigType
|
||||||
|
import com.v2ray.ang.dto.ServerConfig
|
||||||
|
import com.v2ray.ang.dto.V2rayConfig
|
||||||
|
import com.v2ray.ang.extension.idnHost
|
||||||
|
import com.v2ray.ang.util.MmkvManager
|
||||||
|
import com.v2ray.ang.util.Utils
|
||||||
|
import java.net.URI
|
||||||
|
|
||||||
|
object VlessFmt {
|
||||||
|
private val settingsStorage by lazy {
|
||||||
|
MMKV.mmkvWithID(
|
||||||
|
MmkvManager.ID_SETTING,
|
||||||
|
MMKV.MULTI_PROCESS_MODE
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun parseVless(str: String): ServerConfig? {
|
||||||
|
val allowInsecure = settingsStorage?.decodeBool(AppConfig.PREF_ALLOW_INSECURE) ?: false
|
||||||
|
val config = ServerConfig.create(EConfigType.VLESS)
|
||||||
|
|
||||||
|
val uri = URI(Utils.fixIllegalUrl(str))
|
||||||
|
if (uri.rawQuery.isNullOrEmpty()) return null
|
||||||
|
val queryParam = uri.rawQuery.split("&")
|
||||||
|
.associate { it.split("=").let { (k, v) -> k to Utils.urlDecode(v) } }
|
||||||
|
|
||||||
|
val streamSetting = config.outboundBean?.streamSettings ?: return null
|
||||||
|
|
||||||
|
config.remarks = Utils.urlDecode(uri.fragment ?: "")
|
||||||
|
config.outboundBean?.settings?.vnext?.get(0)?.let { vnext ->
|
||||||
|
vnext.address = uri.idnHost
|
||||||
|
vnext.port = uri.port
|
||||||
|
vnext.users[0].id = uri.userInfo
|
||||||
|
vnext.users[0].encryption = queryParam["encryption"] ?: "none"
|
||||||
|
vnext.users[0].flow = queryParam["flow"] ?: ""
|
||||||
|
}
|
||||||
|
|
||||||
|
val sni = streamSetting.populateTransportSettings(
|
||||||
|
queryParam["type"] ?: "tcp",
|
||||||
|
queryParam["headerType"],
|
||||||
|
queryParam["host"],
|
||||||
|
queryParam["path"],
|
||||||
|
queryParam["seed"],
|
||||||
|
queryParam["quicSecurity"],
|
||||||
|
queryParam["key"],
|
||||||
|
queryParam["mode"],
|
||||||
|
queryParam["serviceName"],
|
||||||
|
queryParam["authority"]
|
||||||
|
)
|
||||||
|
streamSetting.populateTlsSettings(
|
||||||
|
queryParam["security"] ?: "",
|
||||||
|
allowInsecure,
|
||||||
|
queryParam["sni"] ?: sni,
|
||||||
|
queryParam["fp"] ?: "",
|
||||||
|
queryParam["alpn"],
|
||||||
|
queryParam["pbk"] ?: "",
|
||||||
|
queryParam["sid"] ?: "",
|
||||||
|
queryParam["spx"] ?: ""
|
||||||
|
)
|
||||||
|
|
||||||
|
return config
|
||||||
|
}
|
||||||
|
|
||||||
|
fun toUri(config: ServerConfig): String {
|
||||||
|
val outbound = config.getProxyOutbound() ?: return ""
|
||||||
|
val streamSetting = outbound.streamSettings ?: V2rayConfig.OutboundBean.StreamSettingsBean()
|
||||||
|
|
||||||
|
val remark = "#" + Utils.urlEncode(config.remarks)
|
||||||
|
val dicQuery = HashMap<String, String>()
|
||||||
|
outbound.settings?.vnext?.get(0)?.users?.get(0)?.flow?.let {
|
||||||
|
if (!TextUtils.isEmpty(it)) {
|
||||||
|
dicQuery["flow"] = it
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dicQuery["encryption"] =
|
||||||
|
if (outbound.getSecurityEncryption().isNullOrEmpty()) "none"
|
||||||
|
else outbound.getSecurityEncryption().orEmpty()
|
||||||
|
|
||||||
|
|
||||||
|
dicQuery["security"] = streamSetting.security.ifEmpty { "none" }
|
||||||
|
(streamSetting.tlsSettings
|
||||||
|
?: streamSetting.realitySettings)?.let { tlsSetting ->
|
||||||
|
if (!TextUtils.isEmpty(tlsSetting.serverName)) {
|
||||||
|
dicQuery["sni"] = tlsSetting.serverName
|
||||||
|
}
|
||||||
|
if (!tlsSetting.alpn.isNullOrEmpty() && tlsSetting.alpn.isNotEmpty()) {
|
||||||
|
dicQuery["alpn"] =
|
||||||
|
Utils.removeWhiteSpace(tlsSetting.alpn.joinToString()).orEmpty()
|
||||||
|
}
|
||||||
|
if (!TextUtils.isEmpty(tlsSetting.fingerprint)) {
|
||||||
|
dicQuery["fp"] = tlsSetting.fingerprint?:""
|
||||||
|
}
|
||||||
|
if (!TextUtils.isEmpty(tlsSetting.publicKey)) {
|
||||||
|
dicQuery["pbk"] = tlsSetting.publicKey?:""
|
||||||
|
}
|
||||||
|
if (!TextUtils.isEmpty(tlsSetting.shortId)) {
|
||||||
|
dicQuery["sid"] = tlsSetting.shortId?:""
|
||||||
|
}
|
||||||
|
if (!TextUtils.isEmpty(tlsSetting.spiderX)) {
|
||||||
|
dicQuery["spx"] = Utils.urlEncode(tlsSetting.spiderX?:"")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dicQuery["type"] =
|
||||||
|
streamSetting.network.ifEmpty { V2rayConfig.DEFAULT_NETWORK }
|
||||||
|
|
||||||
|
outbound.getTransportSettingDetails()?.let { transportDetails ->
|
||||||
|
when (streamSetting.network) {
|
||||||
|
"tcp" -> {
|
||||||
|
dicQuery["headerType"] = transportDetails[0].ifEmpty { "none" }
|
||||||
|
if (!TextUtils.isEmpty(transportDetails[1])) {
|
||||||
|
dicQuery["host"] = Utils.urlEncode(transportDetails[1])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
"kcp" -> {
|
||||||
|
dicQuery["headerType"] = transportDetails[0].ifEmpty { "none" }
|
||||||
|
if (!TextUtils.isEmpty(transportDetails[2])) {
|
||||||
|
dicQuery["seed"] = Utils.urlEncode(transportDetails[2])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
"ws", "httpupgrade", "splithttp" -> {
|
||||||
|
if (!TextUtils.isEmpty(transportDetails[1])) {
|
||||||
|
dicQuery["host"] = Utils.urlEncode(transportDetails[1])
|
||||||
|
}
|
||||||
|
if (!TextUtils.isEmpty(transportDetails[2])) {
|
||||||
|
dicQuery["path"] = Utils.urlEncode(transportDetails[2])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
"http", "h2" -> {
|
||||||
|
dicQuery["type"] = "http"
|
||||||
|
if (!TextUtils.isEmpty(transportDetails[1])) {
|
||||||
|
dicQuery["host"] = Utils.urlEncode(transportDetails[1])
|
||||||
|
}
|
||||||
|
if (!TextUtils.isEmpty(transportDetails[2])) {
|
||||||
|
dicQuery["path"] = Utils.urlEncode(transportDetails[2])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
"quic" -> {
|
||||||
|
dicQuery["headerType"] = transportDetails[0].ifEmpty { "none" }
|
||||||
|
dicQuery["quicSecurity"] = Utils.urlEncode(transportDetails[1])
|
||||||
|
dicQuery["key"] = Utils.urlEncode(transportDetails[2])
|
||||||
|
}
|
||||||
|
|
||||||
|
"grpc" -> {
|
||||||
|
dicQuery["mode"] = transportDetails[0]
|
||||||
|
dicQuery["authority"] = Utils.urlEncode(transportDetails[1])
|
||||||
|
dicQuery["serviceName"] = Utils.urlEncode(transportDetails[2])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val query = "?" + dicQuery.toList().joinToString(
|
||||||
|
separator = "&",
|
||||||
|
transform = { it.first + "=" + it.second })
|
||||||
|
|
||||||
|
val url = String.format(
|
||||||
|
"%s@%s:%s",
|
||||||
|
outbound.getPassword(),
|
||||||
|
Utils.getIpv6Address(outbound.getServerAddress()),
|
||||||
|
outbound.getServerPort()
|
||||||
|
)
|
||||||
|
return url + query + remark
|
||||||
|
}
|
||||||
|
}
|
||||||
193
V2rayNG/app/src/main/kotlin/com/v2ray/ang/util/fmt/VmessFmt.kt
Normal file
193
V2rayNG/app/src/main/kotlin/com/v2ray/ang/util/fmt/VmessFmt.kt
Normal file
@@ -0,0 +1,193 @@
|
|||||||
|
package com.v2ray.ang.util.fmt
|
||||||
|
|
||||||
|
import android.text.TextUtils
|
||||||
|
import android.util.Log
|
||||||
|
import com.google.gson.Gson
|
||||||
|
import com.tencent.mmkv.MMKV
|
||||||
|
import com.v2ray.ang.AppConfig
|
||||||
|
import com.v2ray.ang.dto.EConfigType
|
||||||
|
import com.v2ray.ang.dto.ServerConfig
|
||||||
|
import com.v2ray.ang.dto.V2rayConfig
|
||||||
|
import com.v2ray.ang.dto.VmessQRCode
|
||||||
|
import com.v2ray.ang.extension.idnHost
|
||||||
|
import com.v2ray.ang.util.MmkvManager
|
||||||
|
import com.v2ray.ang.util.Utils
|
||||||
|
import java.net.URI
|
||||||
|
|
||||||
|
object VmessFmt {
|
||||||
|
private val settingsStorage by lazy {
|
||||||
|
MMKV.mmkvWithID(
|
||||||
|
MmkvManager.ID_SETTING, MMKV.MULTI_PROCESS_MODE
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun parseVmess(str: String): ServerConfig? {
|
||||||
|
val allowInsecure = settingsStorage?.decodeBool(AppConfig.PREF_ALLOW_INSECURE) ?: false
|
||||||
|
val config = ServerConfig.create(EConfigType.VMESS)
|
||||||
|
val streamSetting = config.outboundBean?.streamSettings ?: return null
|
||||||
|
|
||||||
|
if (!tryParseNewVmess(str, config, allowInsecure)) {
|
||||||
|
if (str.indexOf("?") > 0) {
|
||||||
|
if (!tryResolveVmess4Kitsunebi(str, config)) {
|
||||||
|
Log.d(AppConfig.ANG_PACKAGE, "R.string.toast_incorrect_protocol")
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
var result = str.replace(EConfigType.VMESS.protocolScheme, "")
|
||||||
|
result = Utils.decode(result)
|
||||||
|
if (TextUtils.isEmpty(result)) {
|
||||||
|
Log.d(AppConfig.ANG_PACKAGE, "R.string.toast_decoding_failed")
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
val vmessQRCode = Gson().fromJson(result, VmessQRCode::class.java)
|
||||||
|
// Although VmessQRCode fields are non null, looks like Gson may still create null fields
|
||||||
|
if (TextUtils.isEmpty(vmessQRCode.add) || TextUtils.isEmpty(vmessQRCode.port) || TextUtils.isEmpty(
|
||||||
|
vmessQRCode.id
|
||||||
|
) || TextUtils.isEmpty(vmessQRCode.net)
|
||||||
|
) {
|
||||||
|
Log.d(AppConfig.ANG_PACKAGE, "R.string.toast_incorrect_protocol")
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
config.remarks = vmessQRCode.ps
|
||||||
|
config.outboundBean?.settings?.vnext?.get(0)?.let { vnext ->
|
||||||
|
vnext.address = vmessQRCode.add
|
||||||
|
vnext.port = Utils.parseInt(vmessQRCode.port)
|
||||||
|
vnext.users[0].id = vmessQRCode.id
|
||||||
|
vnext.users[0].security =
|
||||||
|
if (TextUtils.isEmpty(vmessQRCode.scy)) V2rayConfig.DEFAULT_SECURITY else vmessQRCode.scy
|
||||||
|
vnext.users[0].alterId = Utils.parseInt(vmessQRCode.aid)
|
||||||
|
}
|
||||||
|
val sni = streamSetting.populateTransportSettings(
|
||||||
|
vmessQRCode.net,
|
||||||
|
vmessQRCode.type,
|
||||||
|
vmessQRCode.host,
|
||||||
|
vmessQRCode.path,
|
||||||
|
vmessQRCode.path,
|
||||||
|
vmessQRCode.host,
|
||||||
|
vmessQRCode.path,
|
||||||
|
vmessQRCode.type,
|
||||||
|
vmessQRCode.path,
|
||||||
|
vmessQRCode.host
|
||||||
|
)
|
||||||
|
|
||||||
|
val fingerprint = vmessQRCode.fp ?: streamSetting.tlsSettings?.fingerprint
|
||||||
|
streamSetting.populateTlsSettings(
|
||||||
|
vmessQRCode.tls,
|
||||||
|
allowInsecure,
|
||||||
|
if (TextUtils.isEmpty(vmessQRCode.sni)) sni else vmessQRCode.sni,
|
||||||
|
fingerprint,
|
||||||
|
vmessQRCode.alpn,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return config
|
||||||
|
}
|
||||||
|
|
||||||
|
fun toUri(config: ServerConfig): String {
|
||||||
|
val outbound = config.getProxyOutbound() ?: return ""
|
||||||
|
val streamSetting = outbound.streamSettings ?: V2rayConfig.OutboundBean.StreamSettingsBean()
|
||||||
|
|
||||||
|
val vmessQRCode = VmessQRCode()
|
||||||
|
vmessQRCode.v = "2"
|
||||||
|
vmessQRCode.ps = config.remarks
|
||||||
|
vmessQRCode.add = outbound.getServerAddress().orEmpty()
|
||||||
|
vmessQRCode.port = outbound.getServerPort().toString()
|
||||||
|
vmessQRCode.id = outbound.getPassword().orEmpty()
|
||||||
|
vmessQRCode.aid = outbound.settings?.vnext?.get(0)?.users?.get(0)?.alterId.toString()
|
||||||
|
vmessQRCode.scy = outbound.settings?.vnext?.get(0)?.users?.get(0)?.security.toString()
|
||||||
|
vmessQRCode.net = streamSetting.network
|
||||||
|
vmessQRCode.tls = streamSetting.security
|
||||||
|
vmessQRCode.sni = streamSetting.tlsSettings?.serverName.orEmpty()
|
||||||
|
vmessQRCode.alpn =
|
||||||
|
Utils.removeWhiteSpace(streamSetting.tlsSettings?.alpn?.joinToString()).orEmpty()
|
||||||
|
vmessQRCode.fp = streamSetting.tlsSettings?.fingerprint.orEmpty()
|
||||||
|
outbound.getTransportSettingDetails()?.let { transportDetails ->
|
||||||
|
vmessQRCode.type = transportDetails[0]
|
||||||
|
vmessQRCode.host = transportDetails[1]
|
||||||
|
vmessQRCode.path = transportDetails[2]
|
||||||
|
}
|
||||||
|
val json = Gson().toJson(vmessQRCode)
|
||||||
|
return Utils.encode(json)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun tryParseNewVmess(
|
||||||
|
uriString: String, config: ServerConfig, allowInsecure: Boolean
|
||||||
|
): Boolean {
|
||||||
|
return runCatching {
|
||||||
|
val uri = URI(Utils.fixIllegalUrl(uriString))
|
||||||
|
check(uri.scheme == "vmess")
|
||||||
|
val (_, protocol, tlsStr, uuid, alterId) = Regex("(tcp|http|ws|kcp|quic|grpc)(\\+tls)?:([0-9a-z]{8}-[0-9a-z]{4}-[0-9a-z]{4}-[0-9a-z]{4}-[0-9a-z]{12})").matchEntire(
|
||||||
|
uri.userInfo
|
||||||
|
)?.groupValues ?: error("parse user info fail.")
|
||||||
|
val tls = tlsStr.isNotBlank()
|
||||||
|
val queryParam = uri.rawQuery.split("&")
|
||||||
|
.associate { it.split("=").let { (k, v) -> k to Utils.urlDecode(v) } }
|
||||||
|
|
||||||
|
val streamSetting = config.outboundBean?.streamSettings ?: return false
|
||||||
|
config.remarks = Utils.urlDecode(uri.fragment ?: "")
|
||||||
|
config.outboundBean.settings?.vnext?.get(0)?.let { vnext ->
|
||||||
|
vnext.address = uri.idnHost
|
||||||
|
vnext.port = uri.port
|
||||||
|
vnext.users[0].id = uuid
|
||||||
|
vnext.users[0].security = V2rayConfig.DEFAULT_SECURITY
|
||||||
|
vnext.users[0].alterId = alterId.toInt()
|
||||||
|
}
|
||||||
|
var fingerprint = streamSetting.tlsSettings?.fingerprint
|
||||||
|
val sni = streamSetting.populateTransportSettings(protocol,
|
||||||
|
queryParam["type"],
|
||||||
|
queryParam["host"]?.split("|")?.get(0) ?: "",
|
||||||
|
queryParam["path"]?.takeIf { it.trim() != "/" } ?: "",
|
||||||
|
queryParam["seed"],
|
||||||
|
queryParam["security"],
|
||||||
|
queryParam["key"],
|
||||||
|
queryParam["mode"],
|
||||||
|
queryParam["serviceName"],
|
||||||
|
queryParam["authority"])
|
||||||
|
streamSetting.populateTlsSettings(
|
||||||
|
if (tls) V2rayConfig.TLS else "",
|
||||||
|
allowInsecure,
|
||||||
|
sni,
|
||||||
|
fingerprint,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null
|
||||||
|
)
|
||||||
|
true
|
||||||
|
}.getOrElse { false }
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun tryResolveVmess4Kitsunebi(server: String, config: ServerConfig): Boolean {
|
||||||
|
|
||||||
|
var result = server.replace(EConfigType.VMESS.protocolScheme, "")
|
||||||
|
val indexSplit = result.indexOf("?")
|
||||||
|
if (indexSplit > 0) {
|
||||||
|
result = result.substring(0, indexSplit)
|
||||||
|
}
|
||||||
|
result = Utils.decode(result)
|
||||||
|
|
||||||
|
val arr1 = result.split('@')
|
||||||
|
if (arr1.count() != 2) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
val arr21 = arr1[0].split(':')
|
||||||
|
val arr22 = arr1[1].split(':')
|
||||||
|
if (arr21.count() != 2) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
config.remarks = "Alien"
|
||||||
|
config.outboundBean?.settings?.vnext?.get(0)?.let { vnext ->
|
||||||
|
vnext.address = arr22[0]
|
||||||
|
vnext.port = Utils.parseInt(arr22[1])
|
||||||
|
vnext.users[0].id = arr21[1]
|
||||||
|
vnext.users[0].security = arr21[0]
|
||||||
|
vnext.users[0].alterId = 0
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
package com.v2ray.ang.util.fmt
|
||||||
|
|
||||||
|
import com.v2ray.ang.AppConfig
|
||||||
|
import com.v2ray.ang.dto.EConfigType
|
||||||
|
import com.v2ray.ang.dto.ServerConfig
|
||||||
|
import com.v2ray.ang.extension.idnHost
|
||||||
|
import com.v2ray.ang.extension.removeWhiteSpace
|
||||||
|
import com.v2ray.ang.util.Utils
|
||||||
|
import java.net.URI
|
||||||
|
|
||||||
|
object WireguardFmt {
|
||||||
|
fun parseWireguard(str: String): ServerConfig? {
|
||||||
|
val uri = URI(Utils.fixIllegalUrl(str))
|
||||||
|
if (uri.rawQuery != null) {
|
||||||
|
val config = ServerConfig.create(EConfigType.WIREGUARD)
|
||||||
|
config.remarks = Utils.urlDecode(uri.fragment ?: "")
|
||||||
|
|
||||||
|
val queryParam = uri.rawQuery.split("&")
|
||||||
|
.associate { it.split("=").let { (k, v) -> k to Utils.urlDecode(v) } }
|
||||||
|
|
||||||
|
config.outboundBean?.settings?.let { wireguard ->
|
||||||
|
wireguard.secretKey = uri.userInfo
|
||||||
|
wireguard.address =
|
||||||
|
(queryParam["address"]
|
||||||
|
?: AppConfig.WIREGUARD_LOCAL_ADDRESS_V4).removeWhiteSpace()
|
||||||
|
.split(",")
|
||||||
|
wireguard.peers?.get(0)?.publicKey = queryParam["publickey"] ?: ""
|
||||||
|
wireguard.peers?.get(0)?.endpoint = Utils.getIpv6Address(uri.idnHost) + ":${uri.port}"
|
||||||
|
wireguard.mtu = Utils.parseInt(queryParam["mtu"] ?: AppConfig.WIREGUARD_LOCAL_MTU)
|
||||||
|
wireguard.reserved =
|
||||||
|
(queryParam["reserved"] ?: "0,0,0").removeWhiteSpace().split(",")
|
||||||
|
.map { it.toInt() }
|
||||||
|
}
|
||||||
|
return config
|
||||||
|
}else {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun toUri(config: ServerConfig): String {
|
||||||
|
val outbound = config.getProxyOutbound() ?: return ""
|
||||||
|
|
||||||
|
val remark = "#" + Utils.urlEncode(config.remarks)
|
||||||
|
val dicQuery = HashMap<String, String>()
|
||||||
|
dicQuery["publickey"] =
|
||||||
|
Utils.urlEncode(outbound.settings?.peers?.get(0)?.publicKey.toString())
|
||||||
|
if (outbound.settings?.reserved != null) {
|
||||||
|
dicQuery["reserved"] = Utils.urlEncode(
|
||||||
|
Utils.removeWhiteSpace(outbound.settings?.reserved?.joinToString())
|
||||||
|
.toString()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
dicQuery["address"] = Utils.urlEncode(
|
||||||
|
Utils.removeWhiteSpace((outbound.settings?.address as List<*>).joinToString())
|
||||||
|
.toString()
|
||||||
|
)
|
||||||
|
if (outbound.settings?.mtu != null) {
|
||||||
|
dicQuery["mtu"] = outbound.settings?.mtu.toString()
|
||||||
|
}
|
||||||
|
val query = "?" + dicQuery.toList().joinToString(
|
||||||
|
separator = "&",
|
||||||
|
transform = { it.first + "=" + it.second })
|
||||||
|
|
||||||
|
val url = String.format(
|
||||||
|
"%s@%s:%s",
|
||||||
|
Utils.urlEncode(outbound.getPassword().toString()),
|
||||||
|
Utils.getIpv6Address(outbound.getServerAddress()),
|
||||||
|
outbound.getServerPort()
|
||||||
|
)
|
||||||
|
return url + query + remark
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,7 +1,12 @@
|
|||||||
package com.v2ray.ang.viewmodel
|
package com.v2ray.ang.viewmodel
|
||||||
|
|
||||||
import android.app.Application
|
import android.app.Application
|
||||||
import android.content.*
|
import android.content.BroadcastReceiver
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.DialogInterface
|
||||||
|
import android.content.Intent
|
||||||
|
import android.content.IntentFilter
|
||||||
|
import android.content.res.AssetManager
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
@@ -17,12 +22,25 @@ import com.v2ray.ang.AppConfig
|
|||||||
import com.v2ray.ang.AppConfig.ANG_PACKAGE
|
import com.v2ray.ang.AppConfig.ANG_PACKAGE
|
||||||
import com.v2ray.ang.R
|
import com.v2ray.ang.R
|
||||||
import com.v2ray.ang.databinding.DialogConfigFilterBinding
|
import com.v2ray.ang.databinding.DialogConfigFilterBinding
|
||||||
import com.v2ray.ang.dto.*
|
import com.v2ray.ang.dto.EConfigType
|
||||||
|
import com.v2ray.ang.dto.ServerConfig
|
||||||
|
import com.v2ray.ang.dto.ServersCache
|
||||||
|
import com.v2ray.ang.dto.V2rayConfig
|
||||||
import com.v2ray.ang.extension.toast
|
import com.v2ray.ang.extension.toast
|
||||||
import com.v2ray.ang.util.*
|
import com.v2ray.ang.util.MessageUtil
|
||||||
|
import com.v2ray.ang.util.MmkvManager
|
||||||
import com.v2ray.ang.util.MmkvManager.KEY_ANG_CONFIGS
|
import com.v2ray.ang.util.MmkvManager.KEY_ANG_CONFIGS
|
||||||
import kotlinx.coroutines.*
|
import com.v2ray.ang.util.SpeedtestUtil
|
||||||
import java.util.*
|
import com.v2ray.ang.util.Utils
|
||||||
|
import com.v2ray.ang.util.V2rayConfigUtil
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.Job
|
||||||
|
import kotlinx.coroutines.cancelChildren
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import java.io.File
|
||||||
|
import java.io.FileOutputStream
|
||||||
|
import java.util.Collections
|
||||||
|
|
||||||
class MainViewModel(application: Application) : AndroidViewModel(application) {
|
class MainViewModel(application: Application) : AndroidViewModel(application) {
|
||||||
private val mainStorage by lazy {
|
private val mainStorage by lazy {
|
||||||
@@ -45,8 +63,8 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var serverList = MmkvManager.decodeServerList()
|
var serverList = MmkvManager.decodeServerList()
|
||||||
var subscriptionId: String = settingsStorage.decodeString(AppConfig.CACHE_SUBSCRIPTION_ID, "")!!
|
var subscriptionId: String = settingsStorage.decodeString(AppConfig.CACHE_SUBSCRIPTION_ID, "")?:""
|
||||||
var keywordFilter: String = settingsStorage.decodeString(AppConfig.CACHE_KEYWORD_FILTER, "")!!
|
var keywordFilter: String = settingsStorage.decodeString(AppConfig.CACHE_KEYWORD_FILTER, "")?:""
|
||||||
private set
|
private set
|
||||||
val serversCache = mutableListOf<ServersCache>()
|
val serversCache = mutableListOf<ServersCache>()
|
||||||
val isRunning by lazy { MutableLiveData<Boolean>() }
|
val isRunning by lazy { MutableLiveData<Boolean>() }
|
||||||
@@ -95,15 +113,26 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun appendCustomConfigServer(server: String) {
|
fun appendCustomConfigServer(server: String): Boolean {
|
||||||
val config = ServerConfig.create(EConfigType.CUSTOM)
|
if (server.contains("inbounds")
|
||||||
config.subscriptionId = subscriptionId
|
&& server.contains("outbounds")
|
||||||
config.fullConfig = Gson().fromJson(server, V2rayConfig::class.java)
|
&& server.contains("routing")
|
||||||
config.remarks = config.fullConfig?.remarks ?: System.currentTimeMillis().toString()
|
) {
|
||||||
val key = MmkvManager.encodeServerConfig("", config)
|
try {
|
||||||
serverRawStorage?.encode(key, server)
|
val config = ServerConfig.create(EConfigType.CUSTOM)
|
||||||
serverList.add(0, key)
|
config.subscriptionId = subscriptionId
|
||||||
serversCache.add(0, ServersCache(key, config))
|
config.fullConfig = Gson().fromJson(server, V2rayConfig::class.java)
|
||||||
|
config.remarks = config.fullConfig?.remarks ?: System.currentTimeMillis().toString()
|
||||||
|
val key = MmkvManager.encodeServerConfig("", config)
|
||||||
|
serverRawStorage?.encode(key, server)
|
||||||
|
serverList.add(0, key)
|
||||||
|
serversCache.add(0, ServersCache(key, config))
|
||||||
|
return true
|
||||||
|
} catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
fun swapServer(fromPosition: Int, toPosition: Int) {
|
fun swapServer(fromPosition: Int, toPosition: Int) {
|
||||||
@@ -130,7 +159,7 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
|
|||||||
fun testAllTcping() {
|
fun testAllTcping() {
|
||||||
tcpingTestScope.coroutineContext[Job]?.cancelChildren()
|
tcpingTestScope.coroutineContext[Job]?.cancelChildren()
|
||||||
SpeedtestUtil.closeAllTcpSockets()
|
SpeedtestUtil.closeAllTcpSockets()
|
||||||
MmkvManager.clearAllTestDelayResults()
|
MmkvManager.clearAllTestDelayResults(serversCache.map { it.guid }.toList())
|
||||||
updateListAction.value = -1 // update all
|
updateListAction.value = -1 // update all
|
||||||
|
|
||||||
getApplication<AngApplication>().toast(R.string.connection_test_testing)
|
getApplication<AngApplication>().toast(R.string.connection_test_testing)
|
||||||
@@ -153,7 +182,7 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
|
|||||||
|
|
||||||
fun testAllRealPing() {
|
fun testAllRealPing() {
|
||||||
MessageUtil.sendMsg2TestService(getApplication(), AppConfig.MSG_MEASURE_CONFIG_CANCEL, "")
|
MessageUtil.sendMsg2TestService(getApplication(), AppConfig.MSG_MEASURE_CONFIG_CANCEL, "")
|
||||||
MmkvManager.clearAllTestDelayResults()
|
MmkvManager.clearAllTestDelayResults(serversCache.map { it.guid }.toList())
|
||||||
updateListAction.value = -1 // update all
|
updateListAction.value = -1 // update all
|
||||||
|
|
||||||
val serversCopy = serversCache.toList() // Create a copy of the list
|
val serversCopy = serversCache.toList() // Create a copy of the list
|
||||||
@@ -257,7 +286,6 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
|
|||||||
for (it in deleteServer) {
|
for (it in deleteServer) {
|
||||||
MmkvManager.removeServer(it)
|
MmkvManager.removeServer(it)
|
||||||
}
|
}
|
||||||
reloadServerList()
|
|
||||||
getApplication<AngApplication>().toast(
|
getApplication<AngApplication>().toast(
|
||||||
getApplication<AngApplication>().getString(
|
getApplication<AngApplication>().getString(
|
||||||
R.string.title_del_duplicate_config_count,
|
R.string.title_del_duplicate_config_count,
|
||||||
@@ -266,6 +294,32 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun copyAssets(assets: AssetManager) {
|
||||||
|
val extFolder = Utils.userAssetPath(getApplication<AngApplication>())
|
||||||
|
viewModelScope.launch(Dispatchers.Default) {
|
||||||
|
try {
|
||||||
|
val geo = arrayOf("geosite.dat", "geoip.dat")
|
||||||
|
assets.list("")
|
||||||
|
?.filter { geo.contains(it) }
|
||||||
|
?.filter { !File(extFolder, it).exists() }
|
||||||
|
?.forEach {
|
||||||
|
val target = File(extFolder, it)
|
||||||
|
assets.open(it).use { input ->
|
||||||
|
FileOutputStream(target).use { output ->
|
||||||
|
input.copyTo(output)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Log.i(
|
||||||
|
ANG_PACKAGE,
|
||||||
|
"Copied from apk assets folder to ${target.absolutePath}"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e(ANG_PACKAGE, "asset copy failed", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private val mMsgReceiver = object : BroadcastReceiver() {
|
private val mMsgReceiver = object : BroadcastReceiver() {
|
||||||
override fun onReceive(ctx: Context?, intent: Intent?) {
|
override fun onReceive(ctx: Context?, intent: Intent?) {
|
||||||
when (intent?.getIntExtra("key", 0)) {
|
when (intent?.getIntExtra("key", 0)) {
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ class SettingsViewModel(application: Application) : AndroidViewModel(application
|
|||||||
AppConfig.PREF_VPN_DNS,
|
AppConfig.PREF_VPN_DNS,
|
||||||
AppConfig.PREF_REMOTE_DNS,
|
AppConfig.PREF_REMOTE_DNS,
|
||||||
AppConfig.PREF_DOMESTIC_DNS,
|
AppConfig.PREF_DOMESTIC_DNS,
|
||||||
|
AppConfig.PREF_DELAY_TEST_URL,
|
||||||
AppConfig.PREF_LOCAL_DNS_PORT,
|
AppConfig.PREF_LOCAL_DNS_PORT,
|
||||||
AppConfig.PREF_SOCKS_PORT,
|
AppConfig.PREF_SOCKS_PORT,
|
||||||
AppConfig.PREF_HTTP_PORT,
|
AppConfig.PREF_HTTP_PORT,
|
||||||
@@ -59,6 +60,7 @@ class SettingsViewModel(application: Application) : AndroidViewModel(application
|
|||||||
settingsStorage?.encode(key, sharedPreferences.getString(key, ""))
|
settingsStorage?.encode(key, sharedPreferences.getString(key, ""))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
AppConfig.PREF_ROUTE_ONLY_ENABLED,
|
||||||
AppConfig.PREF_SPEED_ENABLED,
|
AppConfig.PREF_SPEED_ENABLED,
|
||||||
AppConfig.PREF_PROXY_SHARING,
|
AppConfig.PREF_PROXY_SHARING,
|
||||||
AppConfig.PREF_LOCAL_DNS_ENABLED,
|
AppConfig.PREF_LOCAL_DNS_ENABLED,
|
||||||
|
|||||||
@@ -1,6 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<alpha xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:duration="@android:integer/config_shortAnimTime"
|
|
||||||
android:fromAlpha="0.0"
|
|
||||||
android:interpolator="@android:interpolator/decelerate_quad"
|
|
||||||
android:toAlpha="1.0" />
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<alpha xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:duration="@android:integer/config_shortAnimTime"
|
|
||||||
android:fromAlpha="1.0"
|
|
||||||
android:interpolator="@android:interpolator/accelerate_quad"
|
|
||||||
android:toAlpha="0.0" />
|
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 526 B |
@@ -1,9 +0,0 @@
|
|||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:width="24dp"
|
|
||||||
android:height="24dp"
|
|
||||||
android:viewportWidth="24.0"
|
|
||||||
android:viewportHeight="24.0">
|
|
||||||
<path
|
|
||||||
android:fillColor="#FFFFFFFF"
|
|
||||||
android:pathData="M11.8,10.9c-2.27,-0.59 -3,-1.2 -3,-2.15 0,-1.09 1.01,-1.85 2.7,-1.85 1.78,0 2.44,0.85 2.5,2.1h2.21c-0.07,-1.72 -1.12,-3.3 -3.21,-3.81V3h-3v2.16c-1.94,0.42 -3.5,1.68 -3.5,3.61 0,2.31 1.91,3.46 4.7,4.13 2.5,0.6 3,1.48 3,2.41 0,0.69 -0.49,1.79 -2.7,1.79 -2.06,0 -2.87,-0.92 -2.98,-2.1h-2.2c0.12,2.19 1.76,3.42 3.68,3.83V21h3v-2.15c1.95,-0.37 3.5,-1.5 3.5,-3.55 0,-2.84 -2.43,-3.81 -4.7,-4.4z"/>
|
|
||||||
</vector>
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:width="24dp"
|
|
||||||
android:height="24dp"
|
|
||||||
android:viewportWidth="24.0"
|
|
||||||
android:viewportHeight="24.0">
|
|
||||||
<path
|
|
||||||
android:fillColor="#FF000000"
|
|
||||||
android:pathData="M11.8,10.9c-2.27,-0.59 -3,-1.2 -3,-2.15 0,-1.09 1.01,-1.85 2.7,-1.85 1.78,0 2.44,0.85 2.5,2.1h2.21c-0.07,-1.72 -1.12,-3.3 -3.21,-3.81V3h-3v2.16c-1.94,0.42 -3.5,1.68 -3.5,3.61 0,2.31 1.91,3.46 4.7,4.13 2.5,0.6 3,1.48 3,2.41 0,0.69 -0.49,1.79 -2.7,1.79 -2.06,0 -2.87,-0.92 -2.98,-2.1h-2.2c0.12,2.19 1.76,3.42 3.68,3.83V21h3v-2.15c1.95,-0.37 3.5,-1.5 3.5,-3.55 0,-2.84 -2.43,-3.81 -4.7,-4.4z"/>
|
|
||||||
</vector>
|
|
||||||
@@ -1,214 +1,230 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent">
|
||||||
android:orientation="vertical">
|
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:gravity="top"
|
|
||||||
android:orientation="vertical">
|
android:orientation="vertical">
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:id="@+id/layout_backup"
|
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:background="@color/colorPrimary"
|
android:gravity="top"
|
||||||
android:clickable="true"
|
android:orientation="vertical">
|
||||||
android:focusable="true"
|
|
||||||
android:foreground="?attr/selectableItemBackground"
|
|
||||||
android:gravity="center|start"
|
|
||||||
android:orientation="horizontal"
|
|
||||||
android:padding="16dp">
|
|
||||||
|
|
||||||
<ImageView
|
|
||||||
android:layout_width="@dimen/png_height"
|
|
||||||
android:layout_height="@dimen/png_height"
|
|
||||||
app:srcCompat="@drawable/ic_backup_24dp"
|
|
||||||
app:tint="?attr/colorMainText" />
|
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:layout_width="wrap_content"
|
android:id="@+id/layout_backup"
|
||||||
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:orientation="vertical"
|
android:clickable="true"
|
||||||
android:paddingStart="16dp">
|
android:focusable="true"
|
||||||
|
android:background="?attr/selectableItemBackground"
|
||||||
|
android:gravity="center|start"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:padding="16dp">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:layout_width="@dimen/png_height"
|
||||||
|
android:layout_height="@dimen/png_height"
|
||||||
|
app:srcCompat="@drawable/ic_backup_24dp" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:paddingStart="16dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/title_configuration_backup"
|
||||||
|
android:textAppearance="@style/TextAppearance.AppCompat.Subhead" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tv_backup_summary"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="4dp"
|
||||||
|
android:maxLines="4"
|
||||||
|
android:textAppearance="@style/TextAppearance.AppCompat.Small" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/layout_share"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="@dimen/server_height"
|
||||||
|
android:clickable="true"
|
||||||
|
android:focusable="true"
|
||||||
|
android:background="?attr/selectableItemBackground"
|
||||||
|
android:gravity="center|start"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:padding="16dp">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:layout_width="@dimen/png_height"
|
||||||
|
android:layout_height="@dimen/png_height"
|
||||||
|
app:srcCompat="@drawable/ic_share_24dp" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:text="@string/title_configuration_backup"
|
android:paddingStart="16dp"
|
||||||
|
android:text="@string/title_configuration_share"
|
||||||
android:textAppearance="@style/TextAppearance.AppCompat.Subhead" />
|
android:textAppearance="@style/TextAppearance.AppCompat.Subhead" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/layout_restore"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="@dimen/server_height"
|
||||||
|
android:clickable="true"
|
||||||
|
android:focusable="true"
|
||||||
|
android:background="?attr/selectableItemBackground"
|
||||||
|
android:gravity="center|start"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:padding="16dp">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:layout_width="@dimen/png_height"
|
||||||
|
android:layout_height="@dimen/png_height"
|
||||||
|
app:srcCompat="@drawable/ic_restore_24dp" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/tv_backup_summary"
|
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginTop="4dp"
|
android:paddingStart="16dp"
|
||||||
android:maxLines="4"
|
android:text="@string/title_configuration_restore"
|
||||||
android:textAppearance="@style/TextAppearance.AppCompat.Small" />
|
android:textAppearance="@style/TextAppearance.AppCompat.Subhead" />
|
||||||
|
</LinearLayout>
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:gravity="top"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:paddingTop="16dp">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/layout_soure_ccode"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="@dimen/server_height"
|
||||||
|
android:clickable="true"
|
||||||
|
android:focusable="true"
|
||||||
|
android:background="?attr/selectableItemBackground"
|
||||||
|
android:gravity="center|start"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:padding="16dp">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:layout_width="@dimen/png_height"
|
||||||
|
android:layout_height="@dimen/png_height"
|
||||||
|
app:srcCompat="@drawable/ic_source_code_24dp" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:paddingStart="16dp"
|
||||||
|
android:text="@string/title_source_code"
|
||||||
|
android:textAppearance="@style/TextAppearance.AppCompat.Subhead" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/layout_feedback"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="@dimen/server_height"
|
||||||
|
android:clickable="true"
|
||||||
|
android:focusable="true"
|
||||||
|
android:background="?attr/selectableItemBackground"
|
||||||
|
android:gravity="center|start"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:padding="16dp">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:layout_width="@dimen/png_height"
|
||||||
|
android:layout_height="@dimen/png_height"
|
||||||
|
app:srcCompat="@drawable/ic_feedback_24dp" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:paddingStart="16dp"
|
||||||
|
android:text="@string/title_pref_feedback"
|
||||||
|
android:textAppearance="@style/TextAppearance.AppCompat.Subhead" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/layout_tg_channel"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="@dimen/server_height"
|
||||||
|
android:clickable="true"
|
||||||
|
android:focusable="true"
|
||||||
|
android:background="?attr/selectableItemBackground"
|
||||||
|
android:gravity="center|start"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:padding="16dp">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:layout_width="@dimen/png_height"
|
||||||
|
android:layout_height="@dimen/png_height"
|
||||||
|
app:srcCompat="@drawable/ic_telegram_24dp" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:paddingStart="16dp"
|
||||||
|
android:text="@string/title_tg_channel"
|
||||||
|
android:textAppearance="@style/TextAppearance.AppCompat.Subhead" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/layout_privacy_policy"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="@dimen/server_height"
|
||||||
|
android:clickable="true"
|
||||||
|
android:focusable="true"
|
||||||
|
android:background="?attr/selectableItemBackground"
|
||||||
|
android:gravity="center|start"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:padding="16dp">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:layout_width="@dimen/png_height"
|
||||||
|
android:layout_height="@dimen/png_height"
|
||||||
|
app:srcCompat="@drawable/ic_privacy_24dp" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:paddingStart="16dp"
|
||||||
|
android:text="@string/title_privacy_policy"
|
||||||
|
android:textAppearance="@style/TextAppearance.AppCompat.Subhead" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="@dimen/server_height"
|
||||||
|
android:gravity="center"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:padding="16dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tv_version"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/title_about"
|
||||||
|
android:textAppearance="@style/TextAppearance.AppCompat.Small" />
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:id="@+id/layout_restore"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="@dimen/server_height"
|
|
||||||
android:background="@color/colorPrimary"
|
|
||||||
android:clickable="true"
|
|
||||||
android:focusable="true"
|
|
||||||
android:foreground="?attr/selectableItemBackground"
|
|
||||||
android:gravity="center|start"
|
|
||||||
android:orientation="horizontal"
|
|
||||||
android:padding="16dp">
|
|
||||||
|
|
||||||
<ImageView
|
|
||||||
android:layout_width="@dimen/png_height"
|
|
||||||
android:layout_height="@dimen/png_height"
|
|
||||||
app:srcCompat="@drawable/ic_restore_24dp"
|
|
||||||
app:tint="?attr/colorMainText" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:paddingStart="16dp"
|
|
||||||
android:text="@string/title_configuration_restore"
|
|
||||||
android:textAppearance="@style/TextAppearance.AppCompat.Subhead" />
|
|
||||||
</LinearLayout>
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
<LinearLayout
|
</ScrollView>
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:gravity="top"
|
|
||||||
android:orientation="vertical"
|
|
||||||
android:paddingTop="16dp">
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:id="@+id/layout_soure_ccode"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="@dimen/server_height"
|
|
||||||
android:background="@color/colorPrimary"
|
|
||||||
android:clickable="true"
|
|
||||||
android:focusable="true"
|
|
||||||
android:foreground="?attr/selectableItemBackground"
|
|
||||||
android:gravity="center|start"
|
|
||||||
android:orientation="horizontal"
|
|
||||||
android:padding="16dp">
|
|
||||||
|
|
||||||
<ImageView
|
|
||||||
android:layout_width="@dimen/png_height"
|
|
||||||
android:layout_height="@dimen/png_height"
|
|
||||||
app:srcCompat="@drawable/ic_source_code_24dp"
|
|
||||||
app:tint="?attr/colorMainText" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:paddingStart="16dp"
|
|
||||||
android:text="@string/title_source_code"
|
|
||||||
android:textAppearance="@style/TextAppearance.AppCompat.Subhead" />
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:id="@+id/layout_feedback"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="@dimen/server_height"
|
|
||||||
android:background="@color/colorPrimary"
|
|
||||||
android:clickable="true"
|
|
||||||
android:focusable="true"
|
|
||||||
android:foreground="?attr/selectableItemBackground"
|
|
||||||
android:gravity="center|start"
|
|
||||||
android:orientation="horizontal"
|
|
||||||
android:padding="16dp">
|
|
||||||
|
|
||||||
<ImageView
|
|
||||||
android:layout_width="@dimen/png_height"
|
|
||||||
android:layout_height="@dimen/png_height"
|
|
||||||
app:srcCompat="@drawable/ic_feedback_24dp"
|
|
||||||
app:tint="?attr/colorMainText" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:paddingStart="16dp"
|
|
||||||
android:text="@string/title_pref_feedback"
|
|
||||||
android:textAppearance="@style/TextAppearance.AppCompat.Subhead" />
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:id="@+id/layout_tg_channel"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="@dimen/server_height"
|
|
||||||
android:background="@color/colorPrimary"
|
|
||||||
android:clickable="true"
|
|
||||||
android:focusable="true"
|
|
||||||
android:foreground="?attr/selectableItemBackground"
|
|
||||||
android:gravity="center|start"
|
|
||||||
android:orientation="horizontal"
|
|
||||||
android:padding="16dp">
|
|
||||||
|
|
||||||
<ImageView
|
|
||||||
android:layout_width="@dimen/png_height"
|
|
||||||
android:layout_height="@dimen/png_height"
|
|
||||||
app:srcCompat="@drawable/ic_telegram_24dp"
|
|
||||||
app:tint="?attr/colorMainText" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:paddingStart="16dp"
|
|
||||||
android:text="@string/title_tg_channel"
|
|
||||||
android:textAppearance="@style/TextAppearance.AppCompat.Subhead" />
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:id="@+id/layout_privacy_policy"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="@dimen/server_height"
|
|
||||||
android:background="@color/colorPrimary"
|
|
||||||
android:clickable="true"
|
|
||||||
android:focusable="true"
|
|
||||||
android:foreground="?attr/selectableItemBackground"
|
|
||||||
android:gravity="center|start"
|
|
||||||
android:orientation="horizontal"
|
|
||||||
android:padding="16dp">
|
|
||||||
|
|
||||||
<ImageView
|
|
||||||
android:layout_width="@dimen/png_height"
|
|
||||||
android:layout_height="@dimen/png_height"
|
|
||||||
app:srcCompat="@drawable/ic_privacy_24dp"
|
|
||||||
app:tint="?attr/colorMainText" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:paddingStart="16dp"
|
|
||||||
android:text="@string/title_privacy_policy"
|
|
||||||
android:textAppearance="@style/TextAppearance.AppCompat.Subhead" />
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="@dimen/server_height"
|
|
||||||
android:gravity="center"
|
|
||||||
android:orientation="horizontal"
|
|
||||||
android:padding="16dp">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/tv_version"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="@string/title_about"
|
|
||||||
android:textAppearance="@style/TextAppearance.AppCompat.Small" />
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:paddingStart="10dp"
|
android:paddingStart="10dp"
|
||||||
android:paddingEnd="10dp"
|
android:paddingEnd="10dp"
|
||||||
tools:context=".ui.MainActivity">
|
tools:context=".ui.LogcatActivity">
|
||||||
|
|
||||||
<ProgressBar
|
<ProgressBar
|
||||||
android:id="@+id/pb_waiting"
|
android:id="@+id/pb_waiting"
|
||||||
|
|||||||
@@ -48,7 +48,7 @@
|
|||||||
android:id="@+id/layout_test"
|
android:id="@+id/layout_test"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="@dimen/connection_test_height"
|
android:layout_height="@dimen/connection_test_height"
|
||||||
android:background="@color/colorMainTestBg"
|
android:background="@color/colorPrimary"
|
||||||
android:gravity="center|start"
|
android:gravity="center|start"
|
||||||
android:nextFocusRight="@+id/fab"
|
android:nextFocusRight="@+id/fab"
|
||||||
android:clickable="true"
|
android:clickable="true"
|
||||||
@@ -63,8 +63,7 @@
|
|||||||
android:minLines="1"
|
android:minLines="1"
|
||||||
android:paddingStart="16dp"
|
android:paddingStart="16dp"
|
||||||
android:text="@string/connection_test_pending"
|
android:text="@string/connection_test_pending"
|
||||||
android:textAppearance="@style/TextAppearance.AppCompat.Small"
|
android:textAppearance="@style/TextAppearance.AppCompat.Small"/>
|
||||||
android:textColor="@color/colorText" />
|
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
tools:context=".ui.ServerActivity">
|
tools:context=".ui.ServerCustomConfigActivity">
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
|
|||||||
@@ -173,6 +173,7 @@
|
|||||||
android:orientation="vertical">
|
android:orientation="vertical">
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
|
android:id="@+id/tv_request_host"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:text="@string/server_lab_request_host" />
|
android:text="@string/server_lab_request_host" />
|
||||||
@@ -192,6 +193,7 @@
|
|||||||
android:orientation="vertical">
|
android:orientation="vertical">
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
|
android:id="@+id/tv_path"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:text="@string/server_lab_path" />
|
android:text="@string/server_lab_path" />
|
||||||
|
|||||||
@@ -154,6 +154,7 @@
|
|||||||
android:orientation="vertical">
|
android:orientation="vertical">
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
|
android:id="@+id/tv_request_host"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:text="@string/server_lab_request_host" />
|
android:text="@string/server_lab_request_host" />
|
||||||
@@ -173,6 +174,7 @@
|
|||||||
android:orientation="vertical">
|
android:orientation="vertical">
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
|
android:id="@+id/tv_path"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:text="@string/server_lab_path" />
|
android:text="@string/server_lab_path" />
|
||||||
|
|||||||
@@ -193,6 +193,7 @@
|
|||||||
android:orientation="vertical">
|
android:orientation="vertical">
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
|
android:id="@+id/tv_request_host"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:text="@string/server_lab_request_host" />
|
android:text="@string/server_lab_request_host" />
|
||||||
@@ -212,6 +213,7 @@
|
|||||||
android:orientation="vertical">
|
android:orientation="vertical">
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
|
android:id="@+id/tv_path"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:text="@string/server_lab_path" />
|
android:text="@string/server_lab_path" />
|
||||||
|
|||||||
@@ -192,6 +192,7 @@
|
|||||||
android:orientation="vertical">
|
android:orientation="vertical">
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
|
android:id="@+id/tv_request_host"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:text="@string/server_lab_request_host" />
|
android:text="@string/server_lab_request_host" />
|
||||||
@@ -211,6 +212,7 @@
|
|||||||
android:orientation="vertical">
|
android:orientation="vertical">
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
|
android:id="@+id/tv_path"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:text="@string/server_lab_path" />
|
android:text="@string/server_lab_path" />
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
tools:context=".ui.ServerActivity">
|
tools:context=".ui.SubSettingActivity">
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:fitsSystemWindows="true"
|
android:fitsSystemWindows="true"
|
||||||
tools:context=".ui.MainActivity">
|
tools:context=".ui.SubSettingActivity">
|
||||||
|
|
||||||
<androidx.coordinatorlayout.widget.CoordinatorLayout
|
<androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||||
android:id="@+id/main_content"
|
android:id="@+id/main_content"
|
||||||
|
|||||||
@@ -3,8 +3,7 @@
|
|||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:gravity="center_vertical"
|
android:gravity="center_vertical"
|
||||||
android:background="@color/colorPrimary"
|
android:background="?attr/selectableItemBackground"
|
||||||
android:foreground="?attr/selectableItemBackground"
|
|
||||||
android:clickable="true"
|
android:clickable="true"
|
||||||
android:focusable="true">
|
android:focusable="true">
|
||||||
|
|
||||||
|
|||||||
@@ -1,30 +1,29 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:id="@+id/item_bg"
|
android:id="@+id/item_bg"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:gravity="center_vertical">
|
android:gravity="center_vertical">
|
||||||
|
|
||||||
<androidx.cardview.widget.CardView
|
<androidx.cardview.widget.CardView
|
||||||
android:id="@+id/card_view"
|
android:id="@+id/card_view"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_margin="2dp"
|
android:layout_margin="1dp"
|
||||||
app:cardCornerRadius="5dp">
|
app:cardCornerRadius="5dp">
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:id="@+id/info_container"
|
android:id="@+id/info_container"
|
||||||
android:clickable="true"
|
|
||||||
android:focusable="true"
|
|
||||||
android:nextFocusRight="@+id/layout_share"
|
|
||||||
android:background="@color/colorPrimary"
|
|
||||||
android:foreground="?attr/selectableItemBackground"
|
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="@dimen/server_height"
|
android:layout_height="@dimen/server_height"
|
||||||
android:layout_gravity="center"
|
android:layout_gravity="center"
|
||||||
|
android:clickable="true"
|
||||||
|
android:focusable="true"
|
||||||
|
android:background="?attr/selectableItemBackground"
|
||||||
android:gravity="center"
|
android:gravity="center"
|
||||||
|
android:nextFocusRight="@+id/layout_share"
|
||||||
android:orientation="horizontal">
|
android:orientation="horizontal">
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
@@ -65,7 +64,7 @@
|
|||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:lines="1"
|
android:lines="1"
|
||||||
android:textAppearance="@style/TextAppearance.AppCompat.Small"
|
android:textAppearance="@style/TextAppearance.AppCompat.Small"
|
||||||
android:textSize="12sp"/>
|
android:textSize="12sp" />
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
@@ -78,31 +77,31 @@
|
|||||||
android:orientation="vertical">
|
android:orientation="vertical">
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:paddingEnd="5dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tv_subscription"
|
||||||
|
android:layout_width="0dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:orientation="horizontal"
|
android:layout_weight="1"
|
||||||
android:paddingEnd="5dp">
|
android:lines="1"
|
||||||
|
android:textAppearance="@style/TextAppearance.AppCompat.Small"
|
||||||
|
android:textColor="@color/color_secondary"
|
||||||
|
android:textSize="10sp"
|
||||||
|
tools:text="Sub" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/tv_subscription"
|
android:id="@+id/tv_test_result"
|
||||||
android:layout_width="0dp"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_weight="1"
|
android:lines="1"
|
||||||
android:lines="1"
|
android:textAppearance="@style/TextAppearance.AppCompat.Small"
|
||||||
android:textAppearance="@style/TextAppearance.AppCompat.Small"
|
android:textColor="@color/colorPing"
|
||||||
android:textColor="@color/color_secondary"
|
android:textSize="10sp"
|
||||||
android:textSize="10sp"
|
tools:text="214ms" />
|
||||||
tools:text="Sub" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/tv_test_result"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:lines="1"
|
|
||||||
android:textAppearance="@style/TextAppearance.AppCompat.Small"
|
|
||||||
android:textColor="@color/colorPing"
|
|
||||||
android:textSize="10sp"
|
|
||||||
tools:text="214ms" />
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
@@ -116,7 +115,7 @@
|
|||||||
android:id="@+id/layout_share"
|
android:id="@+id/layout_share"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="@dimen/server_height"
|
android:layout_height="@dimen/server_height"
|
||||||
android:background="?attr/selectableItemBackground"
|
android:background="?attr/selectableItemBackgroundBorderless"
|
||||||
android:clickable="true"
|
android:clickable="true"
|
||||||
android:focusable="true"
|
android:focusable="true"
|
||||||
android:gravity="center"
|
android:gravity="center"
|
||||||
@@ -127,8 +126,7 @@
|
|||||||
<ImageView
|
<ImageView
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="@dimen/png_height"
|
android:layout_height="@dimen/png_height"
|
||||||
app:srcCompat="@drawable/ic_share_24dp"
|
app:srcCompat="@drawable/ic_share_24dp" />
|
||||||
app:tint="?attr/colorMainText" />
|
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
@@ -136,7 +134,7 @@
|
|||||||
android:id="@+id/layout_edit"
|
android:id="@+id/layout_edit"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="@dimen/server_height"
|
android:layout_height="@dimen/server_height"
|
||||||
android:background="?attr/selectableItemBackground"
|
android:background="?attr/selectableItemBackgroundBorderless"
|
||||||
android:clickable="true"
|
android:clickable="true"
|
||||||
android:focusable="true"
|
android:focusable="true"
|
||||||
android:gravity="center"
|
android:gravity="center"
|
||||||
@@ -146,8 +144,7 @@
|
|||||||
<ImageView
|
<ImageView
|
||||||
android:layout_width="@dimen/png_height"
|
android:layout_width="@dimen/png_height"
|
||||||
android:layout_height="@dimen/png_height"
|
android:layout_height="@dimen/png_height"
|
||||||
app:srcCompat="@drawable/ic_edit_24dp"
|
app:srcCompat="@drawable/ic_edit_24dp" />
|
||||||
app:tint="?attr/colorMainText" />
|
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
@@ -155,7 +152,7 @@
|
|||||||
android:id="@+id/layout_remove"
|
android:id="@+id/layout_remove"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="@dimen/server_height"
|
android:layout_height="@dimen/server_height"
|
||||||
android:background="?attr/selectableItemBackground"
|
android:background="?attr/selectableItemBackgroundBorderless"
|
||||||
android:clickable="true"
|
android:clickable="true"
|
||||||
android:focusable="true"
|
android:focusable="true"
|
||||||
android:gravity="center"
|
android:gravity="center"
|
||||||
@@ -165,8 +162,7 @@
|
|||||||
<ImageView
|
<ImageView
|
||||||
android:layout_width="@dimen/png_height"
|
android:layout_width="@dimen/png_height"
|
||||||
android:layout_height="@dimen/png_height"
|
android:layout_height="@dimen/png_height"
|
||||||
app:srcCompat="@drawable/ic_delete_24dp"
|
app:srcCompat="@drawable/ic_delete_24dp" />
|
||||||
app:tint="?attr/colorMainText" />
|
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|||||||
@@ -10,42 +10,41 @@
|
|||||||
android:id="@+id/item_cardview"
|
android:id="@+id/item_cardview"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_margin="2dp"
|
android:layout_margin="1dp"
|
||||||
android:orientation="horizontal"
|
android:orientation="horizontal"
|
||||||
card_view:cardCornerRadius="5dp">
|
card_view:cardCornerRadius="5dp">
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:id="@+id/info_container"
|
android:id="@+id/info_container"
|
||||||
android:clickable="true"
|
|
||||||
android:focusable="true"
|
|
||||||
android:background="@color/colorPrimary"
|
|
||||||
android:foreground="?attr/selectableItemBackground"
|
|
||||||
android:nextFocusRight="@+id/layout_edit"
|
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="@dimen/server_height"
|
android:layout_height="@dimen/server_height"
|
||||||
android:layout_gravity="center"
|
android:layout_gravity="center"
|
||||||
|
android:clickable="true"
|
||||||
|
android:focusable="true"
|
||||||
|
android:background="?attr/selectableItemBackground"
|
||||||
android:gravity="center"
|
android:gravity="center"
|
||||||
|
android:nextFocusRight="@+id/layout_edit"
|
||||||
android:orientation="horizontal">
|
android:orientation="horizontal">
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:id="@+id/chk_enable"
|
android:id="@+id/chk_enable"
|
||||||
android:layout_width="6dp"
|
android:layout_width="6dp"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:layout_gravity="center"
|
android:layout_gravity="center"
|
||||||
android:orientation="vertical" />
|
android:orientation="vertical" />
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_weight="1"
|
android:layout_weight="1"
|
||||||
android:paddingStart="9dp"
|
android:orientation="vertical"
|
||||||
android:orientation="vertical">
|
android:paddingStart="9dp">
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/tv_name"
|
android:id="@+id/tv_name"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:textAppearance="@style/TextAppearance.AppCompat.Subhead" />
|
android:textAppearance="@style/TextAppearance.AppCompat.Subhead" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/tv_url"
|
android:id="@+id/tv_url"
|
||||||
@@ -68,19 +67,17 @@
|
|||||||
android:id="@+id/layout_share"
|
android:id="@+id/layout_share"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:background="?attr/selectableItemBackground"
|
android:background="?attr/selectableItemBackgroundBorderless"
|
||||||
android:clickable="true"
|
android:clickable="true"
|
||||||
android:focusable="true"
|
android:focusable="true"
|
||||||
android:gravity="center"
|
android:gravity="center"
|
||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
android:padding="@dimen/layout_margin_spacing"
|
android:padding="@dimen/layout_margin_spacing">
|
||||||
>
|
|
||||||
|
|
||||||
<ImageView
|
<ImageView
|
||||||
android:layout_width="@dimen/png_height"
|
android:layout_width="@dimen/png_height"
|
||||||
android:layout_height="@dimen/png_height"
|
android:layout_height="@dimen/png_height"
|
||||||
app:srcCompat="@drawable/ic_share_24dp"
|
app:srcCompat="@drawable/ic_share_24dp" />
|
||||||
app:tint="?attr/colorMainText"/>
|
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
@@ -88,7 +85,7 @@
|
|||||||
android:id="@+id/layout_edit"
|
android:id="@+id/layout_edit"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:background="?attr/selectableItemBackground"
|
android:background="?attr/selectableItemBackgroundBorderless"
|
||||||
android:clickable="true"
|
android:clickable="true"
|
||||||
android:focusable="true"
|
android:focusable="true"
|
||||||
android:gravity="center"
|
android:gravity="center"
|
||||||
@@ -99,8 +96,7 @@
|
|||||||
<ImageView
|
<ImageView
|
||||||
android:layout_width="@dimen/png_height"
|
android:layout_width="@dimen/png_height"
|
||||||
android:layout_height="@dimen/png_height"
|
android:layout_height="@dimen/png_height"
|
||||||
app:srcCompat="@drawable/ic_edit_24dp"
|
app:srcCompat="@drawable/ic_edit_24dp" />
|
||||||
app:tint="?attr/colorMainText" />
|
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|||||||
@@ -1,24 +1,24 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content">
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
<androidx.cardview.widget.CardView
|
<androidx.cardview.widget.CardView
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_margin="2dp"
|
android:layout_margin="1dp"
|
||||||
app:cardCornerRadius="5dp">
|
app:cardCornerRadius="5dp">
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:orientation="horizontal"
|
android:background="?attr/selectableItemBackground"
|
||||||
android:gravity="center"
|
android:gravity="center"
|
||||||
android:background="@color/colorPrimary"
|
android:orientation="horizontal"
|
||||||
android:foreground="?attr/selectableItemBackground"
|
android:padding="@dimen/nav_header_vertical_spacing">
|
||||||
android:padding="@dimen/nav_header_vertical_spacing">
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
@@ -64,7 +64,7 @@
|
|||||||
android:id="@+id/layout_edit"
|
android:id="@+id/layout_edit"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:background="?attr/selectableItemBackground"
|
android:background="?attr/selectableItemBackgroundBorderless"
|
||||||
android:clickable="true"
|
android:clickable="true"
|
||||||
android:focusable="true"
|
android:focusable="true"
|
||||||
android:padding="@dimen/nav_header_vertical_spacing">
|
android:padding="@dimen/nav_header_vertical_spacing">
|
||||||
@@ -72,24 +72,22 @@
|
|||||||
<ImageView
|
<ImageView
|
||||||
android:layout_width="@dimen/png_height"
|
android:layout_width="@dimen/png_height"
|
||||||
android:layout_height="@dimen/png_height"
|
android:layout_height="@dimen/png_height"
|
||||||
app:srcCompat="@drawable/ic_edit_24dp"
|
app:srcCompat="@drawable/ic_edit_24dp" />
|
||||||
app:tint="?attr/colorMainText" />
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:id="@+id/layout_remove"
|
android:id="@+id/layout_remove"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:background="?attr/selectableItemBackground"
|
android:background="?attr/selectableItemBackgroundBorderless"
|
||||||
android:clickable="true"
|
android:clickable="true"
|
||||||
android:focusable="true"
|
android:focusable="true"
|
||||||
android:padding="@dimen/nav_header_vertical_spacing">
|
android:padding="@dimen/nav_header_vertical_spacing">
|
||||||
|
|
||||||
<ImageView
|
<ImageView
|
||||||
android:layout_width="@dimen/png_height"
|
android:layout_width="@dimen/png_height"
|
||||||
android:layout_height="@dimen/png_height"
|
android:layout_height="@dimen/png_height"
|
||||||
app:srcCompat="@drawable/ic_delete_24dp"
|
app:srcCompat="@drawable/ic_delete_24dp" />
|
||||||
app:tint="?attr/colorMainText" />
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
</androidx.cardview.widget.CardView>
|
</androidx.cardview.widget.CardView>
|
||||||
|
|||||||
16
V2rayNG/app/src/main/res/layout/layout_progress.xml
Normal file
16
V2rayNG/app/src/main/res/layout/layout_progress.xml
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:padding="16dp">
|
||||||
|
|
||||||
|
<ProgressBar
|
||||||
|
android:id="@+id/pb_waiting"
|
||||||
|
style="@android:style/Widget.DeviceDefault.ProgressBar"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
@@ -6,10 +6,10 @@
|
|||||||
android:background="@drawable/nav_header_bg"
|
android:background="@drawable/nav_header_bg"
|
||||||
android:gravity="center"
|
android:gravity="center"
|
||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
android:paddingBottom="@dimen/activity_vertical_margin"
|
|
||||||
android:paddingLeft="@dimen/activity_horizontal_margin"
|
android:paddingLeft="@dimen/activity_horizontal_margin"
|
||||||
|
android:paddingTop="@dimen/activity_vertical_margin"
|
||||||
android:paddingRight="@dimen/activity_horizontal_margin"
|
android:paddingRight="@dimen/activity_horizontal_margin"
|
||||||
android:paddingTop="@dimen/activity_vertical_margin">
|
android:paddingBottom="@dimen/activity_vertical_margin">
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
@@ -19,8 +19,7 @@
|
|||||||
android:gravity="center"
|
android:gravity="center"
|
||||||
android:paddingTop="@dimen/nav_header_vertical_spacing"
|
android:paddingTop="@dimen/nav_header_vertical_spacing"
|
||||||
android:text="@string/app_name"
|
android:text="@string/app_name"
|
||||||
android:textAppearance="@style/TextAppearance.AppCompat.Display1"
|
android:textAppearance="@style/TextAppearance.AppCompat.Display1" />
|
||||||
android:textColor="@color/colorText" />
|
|
||||||
|
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|||||||
@@ -26,6 +26,6 @@
|
|||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:text="@string/app_name"
|
android:text="@string/app_name"
|
||||||
android:textColor="@color/colorWidgetText"
|
android:textColor="@android:color/white"
|
||||||
android:textSize="10sp" />
|
android:textSize="10sp" />
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|||||||
@@ -7,13 +7,8 @@
|
|||||||
android:title="@string/menu_item_add_config"
|
android:title="@string/menu_item_add_config"
|
||||||
app:showAsAction="always" />
|
app:showAsAction="always" />
|
||||||
<item
|
<item
|
||||||
android:id="@+id/del_config"
|
android:id="@+id/sub_update"
|
||||||
android:icon="@drawable/ic_delete_24dp"
|
android:icon="@drawable/ic_restore_24dp"
|
||||||
android:title="@string/menu_item_del_config"
|
android:title="@string/title_sub_update"
|
||||||
app:showAsAction="always" />
|
|
||||||
<item
|
|
||||||
android:id="@+id/save_config"
|
|
||||||
android:icon="@drawable/ic_action_done"
|
|
||||||
android:title="@string/menu_item_save_config"
|
|
||||||
app:showAsAction="always" />
|
app:showAsAction="always" />
|
||||||
</menu>
|
</menu>
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 2.9 KiB |
@@ -1,240 +1,311 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<resources>
|
||||||
<string name="app_widget_name">التبديل</string>
|
<string name="app_name" translatable="false">v2rayNG</string>
|
||||||
<string name="app_tile_name">التبديل</string>
|
<string name="app_widget_name">التبديل</string>
|
||||||
<string name="app_tile_first_use">أول استخدام لهذه الميزة، يرجى استخدام التطبيق لإضافة خادم</string>
|
<string name="app_tile_name">التبديل</string>
|
||||||
<string name="navigation_drawer_open">افتح درج التنقل</string>
|
<string name="app_tile_first_use">أول استخدام لهذه الميزة، يرجى استخدام التطبيق لإضافة خادم</string>
|
||||||
<string name="navigation_drawer_close">أغلق درج التنقل</string>
|
<string name="navigation_drawer_open">فتح درج التنقل</string>
|
||||||
<string name="migration_success">نجحت عملية ترحيل البيانات!</string>
|
<string name="navigation_drawer_close">إغلاق درج التنقل</string>
|
||||||
<string name="migration_fail">فشلت عملية ترحيل البيانات!</string>
|
<string name="migration_success">نجحت عملية ترحيل البيانات!</string>
|
||||||
<!-- Notifications -->
|
<string name="migration_fail">فشلت عملية ترحيل البيانات!</string>
|
||||||
<string name="notification_action_stop_v2ray">توقف</string>
|
|
||||||
<string name="toast_permission_denied">غير قادر على الحصول على الإذن</string>
|
<!-- Notifications -->
|
||||||
<string name="notification_action_more">اضغط للمزيد</string>
|
<string name="notification_action_stop_v2ray">إيقاف</string>
|
||||||
<string name="toast_services_start">بدء الخدمات</string>
|
<string name="toast_permission_denied">تعذر الحصول على الإذن</string>
|
||||||
<string name="toast_services_stop">توقف الخدمات</string>
|
<string name="notification_action_more">انقر للمزيد</string>
|
||||||
<string name="toast_services_success">نجح بدء الخدمات</string>
|
<string name="toast_services_start">بدء الخدمات</string>
|
||||||
<string name="toast_services_failure">فشل بدء الخدمات</string>
|
<string name="toast_services_stop">إيقاف الخدمات</string>
|
||||||
<!--ServerActivity-->
|
<string name="toast_services_success">نجاح بدء الخدمات</string>
|
||||||
<string name="title_server">ملف التكوين</string>
|
<string name="toast_services_failure">فشل بدء الخدمات</string>
|
||||||
<string name="menu_item_add_config">إضافة تكوين</string>
|
|
||||||
<string name="menu_item_save_config">حفظ التكوين</string>
|
<!--ServerActivity-->
|
||||||
<string name="menu_item_del_config">حذف التكوين</string>
|
<string name="title_server">ملف التكوين</string>
|
||||||
<string name="menu_item_import_config_qrcode">استيراد التكوين من QRcode</string>
|
<string name="menu_item_add_config">إضافة تكوين</string>
|
||||||
<string name="menu_item_import_config_clipboard">استيراد التكوين من الحافظة</string>
|
<string name="menu_item_save_config">حفظ التكوين</string>
|
||||||
<string name="menu_item_import_config_manually_vmess">الكتابة يدويا [Vmess]</string>
|
<string name="menu_item_del_config">حذف التكوين</string>
|
||||||
<string name="menu_item_import_config_manually_vless">الكتابة يدويا [VLESS]</string>
|
<string name="menu_item_import_config_qrcode">استيراد التكوين من رمز الاستجابة السريعة (QRcode)</string>
|
||||||
<string name="menu_item_import_config_manually_ss">الكتابة يدويا [Shadowsocks]</string>
|
<string name="menu_item_import_config_clipboard">استيراد التكوين من الحافظة</string>
|
||||||
<string name="menu_item_import_config_manually_socks">الكتابة يدويا [Socks]</string>
|
<string name="menu_item_import_config_manually_vmess">الكتابة يدويًا [Vmess]</string>
|
||||||
<string name="menu_item_import_config_manually_trojan">الكتابة يدويا [Trojan]</string>
|
<string name="menu_item_import_config_manually_vless">الكتابة يدويًا [VLESS]</string>
|
||||||
<string name="menu_item_import_config_manually_wireguard">[Wireguard] الكتابة يدويا</string>
|
<string name="menu_item_import_config_manually_ss">الكتابة يدويًا [Shadowsocks]</string>
|
||||||
<string name="menu_item_import_config_custom">تكوين مخصص</string>
|
<string name="menu_item_import_config_manually_socks">الكتابة يدويًا [Socks]</string>
|
||||||
<string name="menu_item_import_config_custom_clipboard">استيراد التكوين المخصص من الحافظة</string>
|
<string name="menu_item_import_config_manually_trojan">الكتابة يدويًا [Trojan]</string>
|
||||||
<string name="menu_item_import_config_custom_local">استيراد التكوين المخصص من ملف محلي</string>
|
<string name="menu_item_import_config_manually_wireguard">الكتابة يدويًا [Wireguard]</string>
|
||||||
<string name="menu_item_import_config_custom_url">استيراد التكوين المخصص من URL</string>
|
<string name="menu_item_import_config_custom">تكوين مخصص</string>
|
||||||
<string name="menu_item_import_config_custom_url_scan">استيراد التكوين المخصص من مسح URL</string>
|
<string name="menu_item_import_config_custom_clipboard">استيراد تكوين مخصص من الحافظة</string>
|
||||||
<string name="del_config_comfirm">تأكيد الحذف؟</string>
|
<string name="menu_item_import_config_custom_local">استيراد تكوين مخصص من الجهاز</string>
|
||||||
<string name="server_lab_remarks">ملاحظات</string>
|
<string name="menu_item_import_config_custom_url">استيراد تكوين مخصص من عنوان URL</string>
|
||||||
<string name="server_lab_address">العنوان</string>
|
<string name="menu_item_import_config_custom_url_scan">استيراد تكوين مخصص مسح عنوان URL</string>
|
||||||
<string name="server_lab_port">المنفذ</string>
|
<string name="del_config_comfirm">تأكيد الحذف؟</string>
|
||||||
<string name="server_lab_id">المعرف</string>
|
<string name="server_lab_remarks">ملاحظات</string>
|
||||||
<string name="server_lab_alterid">alterId</string>
|
<string name="server_lab_address">العنوان</string>
|
||||||
<string name="server_lab_security">الأمان</string>
|
<string name="server_lab_port">المنفذ</string>
|
||||||
<string name="server_lab_network">الشبكة</string>
|
<string name="server_lab_id">المعرف</string>
|
||||||
<string name="server_lab_more_function">النقل</string>
|
<string name="server_lab_alterid">AlterId</string>
|
||||||
<string name="server_lab_head_type">نوع الرأس</string>
|
|
||||||
<string name="server_lab_mode_type">وضع gRPC</string>
|
<string name="server_lab_security">الأمان</string>
|
||||||
<string name="server_lab_request_host">طلب الاستضافة (host/ws host/h2 host)/QUIC security/gRPC Authority</string>
|
<string name="server_lab_network">الشبكة</string>
|
||||||
<string name="server_lab_path">المسار (ws path/h2 path)/QUIC key/kcp seed/gRPC serviceName</string>
|
<string name="server_lab_more_function">النقل</string>
|
||||||
<string name="server_lab_stream_security">TLS</string>
|
<string name="server_lab_head_type">نوع الرأس</string>
|
||||||
<string name="server_lab_allow_insecure">allowInsecure</string>
|
<string name="server_lab_mode_type">وضع gRPC</string>
|
||||||
<string name="server_lab_sni">SNI</string>
|
<string name="server_lab_request_host">host</string>
|
||||||
<string name="server_lab_address3">العنوان</string>
|
<string name="server_lab_request_host_http">http host</string>
|
||||||
<string name="server_lab_port3">المنفذ</string>
|
<string name="server_lab_request_host_ws">ws host</string>
|
||||||
<string name="server_lab_id3">كلمة المرور</string>
|
<string name="server_lab_request_host_httpupgrade">httpupgrade host</string>
|
||||||
<string name="server_lab_security3">الأمان</string>
|
<string name="server_lab_request_host_splithttp">splithttp host</string>
|
||||||
<string name="server_lab_id4">كلمة المرور (اختياري)</string>
|
<string name="server_lab_request_host_h2">h2 host</string>
|
||||||
<string name="server_lab_security4">المستخدم (اختياري)</string>
|
<string name="server_lab_request_host_quic">QUIC security</string>
|
||||||
<string name="server_lab_encryption">التشفير</string>
|
<string name="server_lab_request_host_grpc">gRPC Authority</string>
|
||||||
<string name="server_lab_flow">التدفق</string>
|
<string name="server_lab_path">path</string>
|
||||||
<string name="server_lab_reserved">Reserved (اختياري)</string>
|
<string name="server_lab_path_ws">ws path</string>
|
||||||
<string name="server_lab_local_address">العنوان المحلي IPv4(اختياري)</string>
|
<string name="server_lab_path_httpupgrade">httpupgrade path</string>
|
||||||
<string name="server_lab_local_mtu">Mtu(optional, default 1420)</string>
|
<string name="server_lab_path_splithttp">splithttp path</string>
|
||||||
<string name="toast_success">نجاح</string>
|
<string name="server_lab_path_h2">h2 path</string>
|
||||||
<string name="toast_failure">فشل</string>
|
<string name="server_lab_path_quic">QUIC key</string>
|
||||||
<string name="toast_none_data">لا يوجد شيء</string>
|
<string name="server_lab_path_kcp">kcp seed</string>
|
||||||
<string name="toast_incorrect_protocol">بروتوكول غير صحيح</string>
|
<string name="server_lab_path_grpc">gRPC serviceName</string>
|
||||||
<string name="toast_decoding_failed">فشل التشفير</string>
|
<string name="server_lab_stream_security">TLS</string>
|
||||||
<string name="title_file_chooser">اختر ملف التكوين</string>
|
<string name="server_lab_stream_fingerprint" translatable="false">البصمة</string>
|
||||||
<string name="toast_require_file_manager">الرجاء تثبيت مدير الملفات.</string>
|
<string name="server_lab_stream_alpn" translatable="false">بروتوكول الطبقة الآخرى التالية (ALPN)</string>
|
||||||
<string name="server_customize_config">تخصيص التكوين</string>
|
<string name="server_lab_allow_insecure">السماح غير الآمن</string>
|
||||||
<string name="toast_config_file_invalid">تكوين غير صالح</string>
|
<string name="server_lab_sni">SNI</string>
|
||||||
<string name="server_lab_content">المحتوى</string>
|
<string name="server_lab_address3">العنوان</string>
|
||||||
<string name="toast_none_data_clipboard">لا توجد بيانات في الحافظة</string>
|
<string name="server_lab_port3">المنفذ</string>
|
||||||
<string name="toast_invalid_url">URL غير صالح</string>
|
<string name="server_lab_id3">كلمة المرور</string>
|
||||||
<string name="server_lab_need_inbound">تأكد من أن منافذ الدخول متسقة مع الإعدادات</string>
|
<string name="server_lab_security3">الأمان</string>
|
||||||
<string name="toast_malformed_josn">تكوين غير صحيح</string>
|
<string name="server_lab_id4">كلمة المرور (اختياري)</string>
|
||||||
<string name="server_lab_request_host6">الاستضافة (SNI) (اختياري)</string>
|
<string name="server_lab_security4">المستخدم (اختياري)</string>
|
||||||
<string name="toast_asset_copy_failed">فشل نسخ الملف، يرجى استخدام مدير الملفات</string>
|
<string name="server_lab_encryption">التشفير</string>
|
||||||
<string name="menu_item_add_file">إضافة ملفات</string>
|
<string name="server_lab_flow">التدفق</string>
|
||||||
<string name="menu_item_download_file">تحميل الملفات</string>
|
<string name="server_lab_public_key" translatable="false">المفتاح العام</string>
|
||||||
<string name="toast_action_not_allowed">هذا الإجراء محظور</string>
|
<string name="server_lab_short_id" translatable="false">المعرّف القصير</string>
|
||||||
<!-- PerAppProxyActivity -->
|
<string name="server_lab_spider_x" translatable="false">SpiderX</string>
|
||||||
<string name="title_user_asset_add_url">أضف عنوان URL للأصل</string>
|
<string name="server_lab_secret_key" translatable="false">المفتاح السري</string>
|
||||||
<string name="msg_file_not_found">لم يتم العثور على الملف</string>
|
<string name="server_lab_reserved">محجوز (اختياري)</string>
|
||||||
<string name="msg_remark_is_duplicate">الملاحظات موجودة بالفعل</string>
|
<string name="server_lab_local_address">العنوان المحلي (اختياري IPv4/IPv6، مفصولة بفواصل)</string>
|
||||||
<string name="msg_dialog_progress">جار التحميل</string>
|
<string name="server_lab_local_mtu">Mtu (اختياري، الافتراضي 1420)</string>
|
||||||
<string name="menu_item_search">بحث</string>
|
<string name="toast_success">نجاح</string>
|
||||||
<string name="menu_item_select_all">تحديد الكل</string>
|
<string name="toast_failure">فشل</string>
|
||||||
<string name="msg_enter_keywords">أدخل الكلمات الرئيسية</string>
|
<string name="toast_none_data">لا يوجد شيء</string>
|
||||||
<string name="switch_bypass_apps_mode">وضع التجاوز</string>
|
<string name="toast_incorrect_protocol">بروتوكول غير صحيح</string>
|
||||||
<string name="menu_item_select_proxy_app">تحديد التطبيق الوكيل تلقائيا</string>
|
<string name="toast_decoding_failed">فشل فك التشفير</string>
|
||||||
<string name="msg_downloading_content">جار تحميل المحتوى</string>
|
<string name="title_file_chooser">حدد ملف التكوين</string>
|
||||||
<string name="menu_item_export_proxy_app">تصدير إلى الحافظة</string>
|
<string name="toast_require_file_manager">يرجى تثبيت مدير الملفات.</string>
|
||||||
<string name="menu_item_import_proxy_app">استيراد من الحافظة</string>
|
<string name="server_customize_config">تخصيص التكوين</string>
|
||||||
<!-- Preferences -->
|
<string name="toast_config_file_invalid">تكوين غير صالح</string>
|
||||||
<string name="title_settings">الإعدادات</string>
|
<string name="server_lab_content">المحتوى</string>
|
||||||
<string name="title_advanced">إعدادات متقدمة</string>
|
<string name="toast_none_data_clipboard">لا توجد بيانات في الحافظة</string>
|
||||||
<string name="title_vpn_settings">إعدادات VPN</string>
|
<string name="toast_invalid_url">رابط URL غير صالح</string>
|
||||||
<string name="title_pref_per_app_proxy">وكيل لكل تطبيق</string>
|
<string name="server_lab_need_inbound">تأكد من أن منفذ الاتصالات الواردة يتوافق مع الإعدادات</string>
|
||||||
<string name="summary_pref_per_app_proxy">عام: التطبيق المحدد هو الوكيل، الاتصال غير المحدد مباشر؛ \nوضع التجاوز: التطبيق المحدد متصل مباشرة، الوكيل غير المحدد. \nالخيار لتحديد التطبيق الوكيل تلقائيا في القائمة</string>
|
<string name="toast_malformed_josn">تكوين مشوه</string>
|
||||||
<string name="title_pref_mux_enabled">تمكين Mux</string>
|
<string name="server_lab_request_host6">مضيف (SNI) (اختياري)</string>
|
||||||
<string name="summary_pref_mux_enabled">حركة مرور TCP مع 8 اتصالات افتراضية، قم بتخصيص كيفية التعامل مع UDP وQUIC أدناهn\أسرع، لكنه قد يسبب اتصالاً غير مستقر</string>
|
<string name="toast_asset_copy_failed">فشل نسخ الملف، يرجى استخدام مدير الملفات</string>
|
||||||
<string name="title_pref_mux_concurency">اتصالات TCP (النطاق من -1 إلى 1024)</string>
|
<string name="menu_item_add_asset">إضافة أصل</string>
|
||||||
<string name="title_pref_mux_xudp_concurency">اتصالات XUDP (النطاق من -1 إلى 1024)</string>
|
<string name="menu_item_add_file">إضافة ملفات</string>
|
||||||
<string name="title_pref_mux_xudp_quic">التعامل مع QUIC في نفق مكس</string>
|
<string name="menu_item_add_url">إضافة URL</string>
|
||||||
<string-array name="mux_xudp_quic_entries">
|
<string name="title_url" translatable="false">URL</string>
|
||||||
<item>يرفض</item>
|
<string name="menu_item_download_file">تنزيل الملفات</string>
|
||||||
<item>مسموح</item>
|
<string name="title_user_asset_add_url">إضافة عنوان URL للأصل</string>
|
||||||
<item>يتخطى</item>
|
<string name="msg_file_not_found">الملف غير موجود</string>
|
||||||
</string-array>
|
<string name="msg_remark_is_duplicate">الملاحظات موجودة بالفعل</string>
|
||||||
<string name="title_pref_speed_enabled">تمكين عرض السرعة</string>
|
<string name="toast_action_not_allowed">الإجراء غير مسموح به</string>
|
||||||
<string name="summary_pref_speed_enabled">عرض السرعة الحالية في الإشعار.\nسيتغير رمز الإشعار استنادًا إلى الاستخدام.</string>
|
|
||||||
<string name="title_pref_sniffing_enabled">تمكين Sniffing</string>
|
<!-- PerAppProxyActivity -->
|
||||||
<string name="summary_pref_sniffing_enabled">محاولة استخلاص النطاق من الحزمة (الافتراضي هو التشغيل)</string>
|
<string name="msg_dialog_progress">جار التحميل</string>
|
||||||
<string name="title_pref_local_dns_enabled">تمكين DNS المحلي</string>
|
<string name="menu_item_search">بحث</string>
|
||||||
<string name="summary_pref_local_dns_enabled">DNS يتم معالجتها بواسطة وحدة DNS الأساسية (موصى بها، إذا كانت بحاجة إلى توجيه تجاوز الشبكة المحلية والعنوان الرئيسي)</string>
|
<string name="menu_item_select_all">تحديد الكل</string>
|
||||||
<string name="title_pref_fake_dns_enabled">تمكين DNS الوهمي</string>
|
<string name="msg_enter_keywords">أدخل الكلمات الرئيسية</string>
|
||||||
<string name="summary_pref_fake_dns_enabled">DNS المحلي يعود بعنوان IP وهمي (أسرع، ولكن قد لا يعمل لبعض التطبيقات)</string>
|
<string name="switch_bypass_apps_mode">وضع التجاوز</string>
|
||||||
<string name="title_pref_prefer_ipv6">تفضيل IPv6</string>
|
<string name="menu_item_select_proxy_app">تحديد تطبيق الوكيل تلقائيًا</string>
|
||||||
<string name="summary_pref_prefer_ipv6">تفضيل عنوان IPv6 والمسارات</string>
|
<string name="msg_downloading_content">جارٍ تنزيل المحتوى</string>
|
||||||
<string name="title_pref_routing">التوجيه</string>
|
<string name="menu_item_export_proxy_app">تصدير إلى الحافظة</string>
|
||||||
<string name="title_pref_routing_domain_strategy">استراتيجية النطاق</string>
|
<string name="menu_item_import_proxy_app">استيراد من الحافظة</string>
|
||||||
<string name="title_pref_routing_mode">قواعد محددة مسبقا</string>
|
|
||||||
<string name="title_pref_routing_custom">قواعد مخصصة</string>
|
|
||||||
<string name="title_pref_remote_dns">DNS عن بُعد (اختياري)</string>
|
<!-- Preferences -->
|
||||||
<string name="summary_pref_remote_dns">DNS</string>
|
<string name="title_settings">الإعدادات</string>
|
||||||
<string name="title_pref_vpn_dns">DNS VPN (IPv4/v6 فقط)</string>
|
<string name="title_advanced">إعدادات متقدمة</string>
|
||||||
<string name="title_pref_domestic_dns">DNS المحلي (اختياري)</string>
|
<string name="title_vpn_settings">إعدادات VPN</string>
|
||||||
<string name="summary_pref_domestic_dns">DNS</string>
|
<string name="title_pref_per_app_proxy">الوكيل لكل تطبيق</string>
|
||||||
<string name="title_pref_proxy_sharing_enabled">السماح بالاتصالات من الشبكة المحلية</string>
|
<string name="summary_pref_per_app_proxy">عام: التطبيق المحدد هو وكيل، غير المحدد اتصال مباشر؛ \nوضع التجاوز: التطبيق المحدد متصل مباشرة، غير المحدد وكيل. \nخيار تحديد تطبيق الوكيل تلقائيًا في القائمة</string>
|
||||||
<string name="summary_pref_proxy_sharing_enabled">يمكن للأجهزة الأخرى الاتصال بالوكيل عبر عنوان IP الخاص بك من خلال socks/http، فقط تمكين في الشبكة الموثوقة لتجنب الاتصال غير المصرح به</string>
|
|
||||||
<string name="toast_warning_pref_proxysharing_short">السماح بالاتصالات من الشبكة المحلية، تأكد من أنك في شبكة موثوقة</string>
|
<string name="title_mux_settings">إعدادات Mux</string>
|
||||||
<string name="title_pref_allow_insecure">allowInsecure</string>
|
<string name="title_pref_mux_enabled">تمكين Mux</string>
|
||||||
<string name="summary_pref_allow_insecure">عند استخدام TLS، الافتراضي هو allowInsecure</string>
|
<string name="summary_pref_mux_enabled">أسرع، لكن قد يتسبب في اتصال غير مستقر\nتخصيص كيفية التعامل مع TCP و UDP و QUIC أدناه</string>
|
||||||
<string name="title_pref_socks_port">منفذ الوكيل SOCKS5</string>
|
<string name="title_pref_mux_concurency">اتصالات TCP(النطاق من -1 إلى 1024)</string>
|
||||||
<string name="summary_pref_socks_port">منفذ الوكيل SOCKS5</string>
|
<string name="title_pref_mux_xudp_concurency">اتصالات XUDP(النطاق من -1 إلى 1024)</string>
|
||||||
<string name="title_pref_http_port">منفذ الوكيل HTTP</string>
|
<string name="title_pref_mux_xudp_quic">التعامل مع QUIC في نفق mux</string>
|
||||||
<string name="summary_pref_http_port">منفذ الوكيل HTTP</string>
|
<string-array name="mux_xudp_quic_entries">
|
||||||
<string name="title_pref_local_dns_port">منفذ DNS المحلي</string>
|
<item>رفض</item>
|
||||||
<string name="summary_pref_local_dns_port">منفذ DNS المحلي</string>
|
<item>سماح</item>
|
||||||
<string name="title_pref_confirm_remove">تأكيد حذف ملف التكوين</string>
|
<item>تخطي</item>
|
||||||
<string name="summary_pref_confirm_remove">هل يتطلب حذف ملف التكوين تأكيدًا ثانيًا من المستخدم</string>
|
</string-array>
|
||||||
<string name="title_pref_start_scan_immediate">بدء المسح فورا</string>
|
|
||||||
<string name="summary_pref_start_scan_immediate">افتح الكاميرا للمسح فورا عند بدء التشغيل، وإلا يمكنك اختيار المسح الضوئي للرمز أو اختيار صورة في شريط الأدوات</string>
|
<string name="title_pref_speed_enabled">تمكين عرض السرعة</string>
|
||||||
<string name="title_pref_feedback">الملاحظات</string>
|
<string name="summary_pref_speed_enabled">عرض السرعة الحالية في الإشعار.\nستتغير أيقونة الإشعار بناءً على الاستخدام.</string>
|
||||||
<string name="summary_pref_feedback">إرسال ملاحظات عن التحسينات أو الأخطاء إلى GitHub</string>
|
<string name="title_pref_sniffing_enabled">تفعيل استخراج الشبكة</string>
|
||||||
<string name="summary_pref_tg_group">الانضمام إلى مجموعة Telegram</string>
|
<string name="summary_pref_sniffing_enabled">محاولة استخراج النطاق من الحزمة (مفعل افتراضيًا)</string>
|
||||||
<string name="toast_tg_app_not_found">لم يتم العثور على تطبيق Telegram</string>
|
<string name="title_pref_route_only_enabled">تفعيل التوجيه فقط</string>
|
||||||
<string name="title_privacy_policy">حریم خصوصی</string>
|
<string name="summary_pref_route_only_enabled">استخدم اسم النطاق الذي تم استخراجه للتوجيه فقط، واحتفظ بعنوان الوجهة كعنوان IP.</string>
|
||||||
<string name="title_about">About</string>
|
|
||||||
<string name="title_source_code">Source code</string>
|
<string name="title_pref_local_dns_enabled">تفعيل DNS المحلي</string>
|
||||||
<string name="title_tg_channel">Telegram channel</string>
|
<string name="summary_pref_local_dns_enabled">يتم معالجة DNS بواسطة وحدة DNS الأساسية (موصى به، إذا لزم تجاوز التوجيه لشبكة LAN وعنوان البر الرئيسي)</string>
|
||||||
<string name="title_configuration_backup">Backup configuration</string>
|
|
||||||
<string name="summary_configuration_backup">Storage location: [%s], The backup will be cleared after uninstalling the app or clearing the storage</string>
|
<string name="title_pref_fake_dns_enabled">تفعيل DNS وهمي</string>
|
||||||
<string name="title_configuration_restore">Restore configuration</string>
|
<string name="summary_pref_fake_dns_enabled">يعيد DNS المحلي عنوان IP وهمي (أسرع، ولكنه قد لا يعمل مع بعض التطبيقات)</string>
|
||||||
<string name="title_pref_promotion">ترقية</string>
|
|
||||||
<string name="summary_pref_promotion">ترقية، انقر للحصول على التفاصيل (يمكن إزالة التبرع)</string>
|
<string name="title_pref_prefer_ipv6">تفضيل IPv6</string>
|
||||||
<string name="title_pref_auto_update_subscription">اشتراكات التحديث التلقائي</string>
|
<string name="summary_pref_prefer_ipv6">تفضيل عنوان IPv6 وطرق التوجيه</string>
|
||||||
<string name="summary_pref_auto_update_subscription">قم بتحديث اشتراكاتك تلقائيًا بفاصل زمني في الخلفية. اعتمادًا على الجهاز، قد لا تعمل هذه الميزة دائمًا</string>
|
|
||||||
<string name="title_pref_auto_update_interval">الفاصل الزمني للتحديث التلقائي (الدقائق، القيمة الدنيا 15)</string>
|
<string name="title_pref_routing">التوجيه</string>
|
||||||
<string name="title_core_loglevel">مستوى السجل</string>
|
<string name="title_pref_routing_domain_strategy">استراتيجية النطاق</string>
|
||||||
<string name="title_mode">الوضع</string>
|
<string name="title_pref_routing_mode">قواعد محددة مسبقًا</string>
|
||||||
<string name="title_mode_help">انقر علي للمزيد من المساعدة</string>
|
<string name="title_pref_routing_custom">قواعد مخصصة</string>
|
||||||
<string name="title_language">اللغة</string>
|
|
||||||
<string name="title_ui_settings">إعدادات واجهة المستخدم</string>
|
<string name="title_pref_remote_dns">DNS البعيد (udp/tcp/https/quic) (اختياري)</string>
|
||||||
<string name="title_pref_ui_mode_night">UI mode settings</string>
|
<string name="summary_pref_remote_dns">DNS</string>
|
||||||
<string name="title_logcat">Logcat</string>
|
|
||||||
<string name="logcat_copy">نسخ</string>
|
<string name="title_pref_vpn_dns">VPN DNS (IPv4/v6 فقط)</string>
|
||||||
<string name="logcat_clear">مسح</string>
|
|
||||||
<string name="title_service_restart">إعادة تشغيل الخدمة</string>
|
<string name="title_pref_domestic_dns">DNS المحلي (اختياري)</string>
|
||||||
<string name="title_del_all_config">حذف الكل (قبل أول خطوة)</string>
|
<string name="summary_pref_domestic_dns">DNS</string>
|
||||||
<string name="title_del_duplicate_config">حذف المكررات (2)</string>
|
|
||||||
<string name="title_del_invalid_config">حذف العناوين العاطلة (بعد الاختبار؛ 4)</string>
|
<string name="title_pref_delay_test_url">True delay test url (http/https)</string>
|
||||||
<string name="title_export_all">تصدير التكوينات غير المخصصة إلى الحافظة</string>
|
<string name="summary_pref_delay_test_url">Url</string>
|
||||||
<string name="title_sub_setting">إعدادات مجموعة الاشتراك</string>
|
|
||||||
<string name="sub_setting_remarks">ملاحظات</string>
|
<string name="title_pref_proxy_sharing_enabled">السماح بالاتصالات من الشبكة المحلية</string>
|
||||||
<string name="sub_setting_url">URL اختياري</string>
|
<string name="summary_pref_proxy_sharing_enabled">يمكن للأجهزة الأخرى الاتصال بالبروكسي بواسطة عنوان IP الخاص بك من خلال socks/http، يتم التمكين فقط في الشبكة الموثوقة لتجنب الاتصال غير المصرح به</string>
|
||||||
<string name="sub_setting_enable">تمكين التحديث</string>
|
<string name="toast_warning_pref_proxysharing_short">السماح بالاتصالات من الشبكة المحلية، تأكد من أنك في شبكة موثوقة</string>
|
||||||
<string name="sub_auto_update">تمكين التحديث التلقائي</string>
|
|
||||||
<string name="title_sub_update">تحديث الاشتراك (1)</string>
|
<string name="title_pref_allow_insecure">السماح غير الآمن</string>
|
||||||
<string name="title_ping_all_server">Tcping كل التكوين</string>
|
<string name="summary_pref_allow_insecure">عند TLS، الافتراضي هو السماح غير الآمن</string>
|
||||||
<string name="title_real_ping_all_server">اختبر كل العناوين (3)</string>
|
|
||||||
<string name="title_user_asset_setting">ملفات الأصول الجغرافية</string>
|
<string name="title_pref_socks_port">منفذ بروكسي SOCKS5</string>
|
||||||
<string name="title_sort_by_test_results">ترتيب العناوين حسب نتائج الاختبار (5)</string>
|
<string name="summary_pref_socks_port">منفذ بروكسي SOCKS5</string>
|
||||||
<string name="title_filter_config">تصفية ملف التكوين</string>
|
|
||||||
<string name="filter_config_all">جميع مجموعات الاشتراك</string>
|
<string name="title_pref_http_port">منفذ بروكسي HTTP</string>
|
||||||
<string name="title_del_duplicate_config_count">حذف %d من التكوينات المكررة</string>
|
<string name="summary_pref_http_port">منفذ بروكسي HTTP</string>
|
||||||
<string name="tasker_start_service">بدء الخدمة</string>
|
|
||||||
<string name="tasker_setting_confirm">تأكيد</string>
|
<string name="title_pref_local_dns_port">منفذ DNS المحلي</string>
|
||||||
<string name="routing_settings_title">إعدادات التوجيه</string>
|
<string name="summary_pref_local_dns_port">منفذ DNS المحلي</string>
|
||||||
<string name="routing_settings_tips">مفصولة بفواصل (،)، تذكر الحفظ</string>
|
|
||||||
<string name="routing_settings_save">حفظ</string>
|
<string name="title_pref_confirm_remove">تأكيد حذف ملف التكوين</string>
|
||||||
<string name="routing_settings_delete">مسح</string>
|
<string name="summary_pref_confirm_remove">ما إذا كان حذف ملف التكوين يتطلب تأكيدًا ثانيًا من قبل المستخدم</string>
|
||||||
<string name="routing_settings_scan_replace">مسح واستبدال</string>
|
|
||||||
<string name="routing_settings_scan_append">مسح وإضافة</string>
|
<string name="title_pref_start_scan_immediate">بدء المسح الضوئي على الفور</string>
|
||||||
<string name="routing_settings_default_rules">تعيين قواعد التوجيه الافتراضية</string>
|
<string name="summary_pref_start_scan_immediate">افتح الكاميرا لمسح الرمز ضوئيًا على الفور عند بدء التشغيل، وإلا يمكنك اختيار مسح الرمز ضوئيًا أو تحديد صورة في شريط الأدوات</string>
|
||||||
<string name="connection_test_pending">فحص الاتصال</string>
|
|
||||||
<string name="connection_test_testing">جارٍ الفحص...</string>
|
<string name="title_pref_feedback">ملاحظات</string>
|
||||||
<string name="connection_test_available">نجاح: استغرق الاتصال HTTP %dms</string>
|
<string name="summary_pref_feedback">ملاحظات التحسينات أو الأخطاء إلى GitHub</string>
|
||||||
<string name="connection_test_error">فشل في اكتشاف الاتصال بالإنترنت: %s</string>
|
<string name="summary_pref_tg_group">الانضمام إلى مجموعة Telegram</string>
|
||||||
<string name="connection_test_fail">الإنترنت غير متوفر</string>
|
<string name="toast_tg_app_not_found">لم يتم العثور على تطبيق Telegram</string>
|
||||||
<string name="connection_test_error_status_code">رمز الخطأ: #%d</string>
|
<string name="title_privacy_policy">سياسة الخصوصية</string>
|
||||||
<string name="connection_connected">متصل، اضغط لفحص الاتصال</string>
|
<string name="title_about">حول\nترجمة م. ابراهيم قاسم</string>
|
||||||
<string name="connection_not_connected">غير متصل</string>
|
<string name="title_source_code">الكود المصدري</string>
|
||||||
<string name="import_subscription_success">تم استيراد الاشتراك بنجاح</string>
|
<string name="title_tg_channel">قناة Telegram</string>
|
||||||
<string name="import_subscription_failure">فشل استيراد الاشتراك</string>
|
<string name="title_configuration_backup">نسخ التكوين احتياطيًا</string>
|
||||||
<string-array name="share_method">
|
<string name="summary_configuration_backup">موقع التخزين: [%s]، سيتم مسح النسخة الاحتياطية بعد إلغاء تثبيت التطبيق أو مسح التخزين</string>
|
||||||
<item>QRcode</item>
|
<string name="title_configuration_restore">استعادة التكوين</string>
|
||||||
<item>تصدير إلى الحافظة</item>
|
<string name="title_configuration_share">مشاركة التكوين</string>
|
||||||
<item>تصدير التكوين الكامل إلى الحافظة</item>
|
|
||||||
</string-array>
|
<string name="title_pref_promotion">ترويج</string>
|
||||||
<string-array name="share_sub_method">
|
<string name="summary_pref_promotion">ترويج، انقر للحصول على التفاصيل (يمكن إزالة التبرع)</string>
|
||||||
<item>QRcode</item>
|
|
||||||
<item>تصدير إلى الحافظة</item>
|
<string name="title_pref_auto_update_subscription">اشتراكات التحديث التلقائي</string>
|
||||||
</string-array>
|
<string name="summary_pref_auto_update_subscription">قم بتحديث اشتراكاتك تلقائيًا بفترة زمنية في الخلفية. اعتمادًا على الجهاز، قد لا تعمل هذه الميزة دائمًا</string>
|
||||||
<string-array name="routing_tag">
|
<string name="title_pref_auto_update_interval">فاصل التحديث التلقائي (بالدقائق، الحد الأدنى للقيمة 15)</string>
|
||||||
<item>مسار وكيل أو IP</item>
|
|
||||||
<item>مسار مباشر أو IP</item>
|
<string name="title_core_loglevel">مستوى السجل</string>
|
||||||
<item>مسار محظور أو IP</item>
|
<string name="title_mode">الوضع</string>
|
||||||
</string-array>
|
<string name="title_mode_help">انقر هنا للحصول على مزيد من المساعدة</string>
|
||||||
<string-array name="routing_mode">
|
<string name="title_language">اللغة</string>
|
||||||
<item>وكيل عالمي</item>
|
<string name="title_ui_settings">إعدادات واجهة المستخدم</string>
|
||||||
<item>تجاوز عنوان الشبكة المحلية ثم الوكيل</item>
|
<string name="title_pref_ui_mode_night">إعدادات وضع واجهة المستخدم ليلاً</string>
|
||||||
<item>تجاوز عنوان البر الرئيسي ثم الوكيل</item>
|
|
||||||
<item>تجاوز عنوان الشبكة المحلية والبر الرئيسي ثم الوكيل</item>
|
<string name="title_logcat">Logcat</string>
|
||||||
<item>مباشر عالمي</item>
|
<string name="logcat_copy">نسخ</string>
|
||||||
</string-array>
|
<string name="logcat_clear">مسح</string>
|
||||||
<string-array name="mode_entries">
|
<string name="title_service_restart">إعادة تشغيل الخدمة</string>
|
||||||
<item>VPN</item>
|
<string name="title_del_all_config">حذف جميع الإعدادات (قبل أول خطوة)</string>
|
||||||
<item>الوكيل فقط</item>
|
<string name="title_del_duplicate_config">حذف الإعدادات المكررة (2)</string>
|
||||||
</string-array>
|
<string name="title_del_invalid_config">حذف الإعدادات غير الصالحة (بعد الاختبار؛ 4)</string>
|
||||||
<string name="menu_item_add_asset">يضيف</string>
|
<string name="title_export_all">تصدير الإعدادات غير المخصصة إلى الحافظة</string>
|
||||||
<string name="menu_item_add_url">إضافة رابط</string>
|
<string name="title_sub_setting">إعداد مجموعة الاشتراك</string>
|
||||||
|
<string name="sub_setting_remarks">ملاحظات</string>
|
||||||
|
<string name="sub_setting_url">عنوان URL اختياري</string>
|
||||||
|
<string name="sub_setting_enable">تفعيل التحديث</string>
|
||||||
|
<string name="sub_auto_update">تفعيل التحديث التلقائي</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">ملفات أصول جغرافية</string>
|
||||||
|
<string name="title_sort_by_test_results">الفرز حسب نتائج الاختبار (5)</string>
|
||||||
|
<string name="title_filter_config">تصفية ملف التكوين</string>
|
||||||
|
<string name="filter_config_all">جميع مجموعات الاشتراك</string>
|
||||||
|
<string name="title_del_duplicate_config_count">حذف %d من الإعدادات المكررة</string>
|
||||||
|
|
||||||
|
<string name="tasker_start_service">بدء الخدمة</string>
|
||||||
|
<string name="tasker_setting_confirm">تأكيد</string>
|
||||||
|
|
||||||
|
<string name="routing_settings_title">إعدادات التوجيه</string>
|
||||||
|
<string name="routing_settings_tips">مفصولة بفواصل (،)، تذكر الحفظ</string>
|
||||||
|
<string name="routing_settings_save">حفظ</string>
|
||||||
|
<string name="routing_settings_delete">مسح</string>
|
||||||
|
<string name="routing_settings_scan_replace">فحص واستبدال</string>
|
||||||
|
<string name="routing_settings_scan_append">فحص وإضافة</string>
|
||||||
|
<string name="routing_settings_default_rules"> تعيين قواعد توجيه افتراضية</string>
|
||||||
|
|
||||||
|
<string name="connection_test_pending">التحقق من الاتصال</string>
|
||||||
|
<string name="connection_test_testing">يجري الاختبار…</string>
|
||||||
|
<string name="connection_test_available">نجاح: استغرق اتصال HTTP %dms</string>
|
||||||
|
<string name="connection_test_error">فشل اكتشاف اتصال الإنترنت: %s</string>
|
||||||
|
<string name="connection_test_fail">الإنترنت غير متاح</string>
|
||||||
|
<string name="connection_test_error_status_code">رمز الخطأ: #%d</string>
|
||||||
|
<string name="connection_connected">متصل، انقر للتحقق من الاتصال</string>
|
||||||
|
<string name="connection_not_connected">غير متصل</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>
|
||||||
|
<string name="title_pref_fragment_length">طول الجزء (الحد الأدنى - الحد الأقصى)</string>
|
||||||
|
<string name="title_pref_fragment_interval">فاصل الجزء (الحد الأدنى - الحد الأقصى)</string>
|
||||||
|
<string name="title_pref_fragment_enabled">تفعيل الجزء</string>
|
||||||
|
|
||||||
|
<string-array name="share_method">
|
||||||
|
<item>رمز استجابة سريعة (QRcode)</item>
|
||||||
|
<item>تصدير إلى الحافظة</item>
|
||||||
|
<item>تصدير التكوين الكامل إلى الحافظة</item>
|
||||||
|
</string-array>
|
||||||
|
|
||||||
|
<string-array name="share_sub_method">
|
||||||
|
<item>رمز استجابة سريعة (QRcode)</item>
|
||||||
|
<item>تصدير إلى الحافظة</item>
|
||||||
|
</string-array>
|
||||||
|
|
||||||
|
<string-array name="routing_tag">
|
||||||
|
<item>عنوان URL أو IP الوكيل</item>
|
||||||
|
<item>عنوان URL أو IP المباشر</item>
|
||||||
|
<item>عنوان URL أو IP المحظور</item>
|
||||||
|
</string-array>
|
||||||
|
|
||||||
|
<string-array name="routing_mode">
|
||||||
|
<item>وكيل عام</item>
|
||||||
|
<item>تجاوز عنوان الشبكة المحلية ثم الوكيل</item>
|
||||||
|
<item>تجاوز عنوان البر الرئيسي ثم الوكيل</item>
|
||||||
|
<item>تجاوز عنوان الشبكة المحلية والبر الرئيسي ثم الوكيل</item>
|
||||||
|
<item>مباشر عام</item>
|
||||||
|
</string-array>
|
||||||
|
|
||||||
|
<string-array name="mode_entries">
|
||||||
|
<item>VPN</item>
|
||||||
|
<item>بروكسي فقط</item>
|
||||||
|
</string-array>
|
||||||
|
|
||||||
|
<string-array name="ui_mode_night">
|
||||||
|
<item>اتبع النظام</item>
|
||||||
|
<item>فاتح</item>
|
||||||
|
<item>داكن</item>
|
||||||
|
</string-array>
|
||||||
|
|
||||||
<string-array name="ui_mode_night">
|
|
||||||
<item>Follow system</item>
|
|
||||||
<item>Light</item>
|
|
||||||
<item>Dark</item>
|
|
||||||
</string-array>
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -46,8 +46,22 @@
|
|||||||
<string name="server_lab_more_function">انتقال</string>
|
<string name="server_lab_more_function">انتقال</string>
|
||||||
<string name="server_lab_head_type">نوع head</string>
|
<string name="server_lab_head_type">نوع head</string>
|
||||||
<string name="server_lab_mode_type">حالت gRPC</string>
|
<string name="server_lab_mode_type">حالت gRPC</string>
|
||||||
<string name="server_lab_request_host">gRPC Authority/میزبان درخواست (host/host ws/host h2)/امنیت QUIC</string>
|
<string name="server_lab_request_host">host</string>
|
||||||
<string name="server_lab_path">مسیر (مسیر ws/ مسیر h2) کلید QUIC/دانه kcp/نامخدمات gRPC</string>
|
<string name="server_lab_request_host_http">http host</string>
|
||||||
|
<string name="server_lab_request_host_ws">ws host</string>
|
||||||
|
<string name="server_lab_request_host_httpupgrade">httpupgrade host</string>
|
||||||
|
<string name="server_lab_request_host_splithttp">splithttp host</string>
|
||||||
|
<string name="server_lab_request_host_h2">h2 host</string>
|
||||||
|
<string name="server_lab_request_host_quic">QUIC security</string>
|
||||||
|
<string name="server_lab_request_host_grpc">gRPC Authority</string>
|
||||||
|
<string name="server_lab_path">path</string>
|
||||||
|
<string name="server_lab_path_ws">ws path</string>
|
||||||
|
<string name="server_lab_path_httpupgrade">httpupgrade path</string>
|
||||||
|
<string name="server_lab_path_splithttp">splithttp path</string>
|
||||||
|
<string name="server_lab_path_h2">h2 path</string>
|
||||||
|
<string name="server_lab_path_quic">QUIC key</string>
|
||||||
|
<string name="server_lab_path_kcp">kcp seed</string>
|
||||||
|
<string name="server_lab_path_grpc">gRPC serviceName</string>
|
||||||
<string name="server_lab_stream_security">TLS</string>
|
<string name="server_lab_stream_security">TLS</string>
|
||||||
<string name="server_lab_allow_insecure">مجوز ناامن</string>
|
<string name="server_lab_allow_insecure">مجوز ناامن</string>
|
||||||
<string name="server_lab_sni">SNI</string>
|
<string name="server_lab_sni">SNI</string>
|
||||||
@@ -120,6 +134,9 @@
|
|||||||
|
|
||||||
<string name="title_pref_sniffing_enabled">فعال کردن Sniffing</string>
|
<string name="title_pref_sniffing_enabled">فعال کردن Sniffing</string>
|
||||||
<string name="summary_pref_sniffing_enabled">دامنه sniff را از بسته امتحان کنید (پیشفرض روشن)</string>
|
<string name="summary_pref_sniffing_enabled">دامنه sniff را از بسته امتحان کنید (پیشفرض روشن)</string>
|
||||||
|
<string name="title_pref_route_only_enabled">Enable routeOnly</string>
|
||||||
|
<string name="summary_pref_route_only_enabled">Use the sniffed domain name for routing only, and keep the target address as the IP address.</string>
|
||||||
|
|
||||||
|
|
||||||
<string name="title_pref_local_dns_enabled">فعال کردن DNS محلی</string>
|
<string name="title_pref_local_dns_enabled">فعال کردن DNS محلی</string>
|
||||||
<string name="summary_pref_local_dns_enabled">DNS پردازش شده توسط ماژول DNS هسته (توصیه میشود، در صورت نیاز به دور زدن LAN و نشانی mainland)</string>
|
<string name="summary_pref_local_dns_enabled">DNS پردازش شده توسط ماژول DNS هسته (توصیه میشود، در صورت نیاز به دور زدن LAN و نشانی mainland)</string>
|
||||||
@@ -143,6 +160,9 @@
|
|||||||
<string name="title_pref_domestic_dns">DNS داخلی (اختیاری)</string>
|
<string name="title_pref_domestic_dns">DNS داخلی (اختیاری)</string>
|
||||||
<string name="summary_pref_domestic_dns">DNS</string>
|
<string name="summary_pref_domestic_dns">DNS</string>
|
||||||
|
|
||||||
|
<string name="title_pref_delay_test_url">True delay test url (http/https)</string>
|
||||||
|
<string name="summary_pref_delay_test_url">Url</string>
|
||||||
|
|
||||||
<string name="title_pref_proxy_sharing_enabled">اجازه اتصالات از طریق LAN</string>
|
<string name="title_pref_proxy_sharing_enabled">اجازه اتصالات از طریق LAN</string>
|
||||||
<string name="summary_pref_proxy_sharing_enabled">دستگاههای دیگر میتوانند از طریق socks/http به پراکسی توسط نشانی آیپی شما متصل شوند، فقط در شبکه مورد اعتماد فعال میشوند تا از اتصال غیرمجاز جلوگیری کنند</string>
|
<string name="summary_pref_proxy_sharing_enabled">دستگاههای دیگر میتوانند از طریق socks/http به پراکسی توسط نشانی آیپی شما متصل شوند، فقط در شبکه مورد اعتماد فعال میشوند تا از اتصال غیرمجاز جلوگیری کنند</string>
|
||||||
<string name="toast_warning_pref_proxysharing_short">اتصالات از طریق LAN را مجاز کنید، مطمئن شوید که در یک شبکه قابل اعتماد هستید</string>
|
<string name="toast_warning_pref_proxysharing_short">اتصالات از طریق LAN را مجاز کنید، مطمئن شوید که در یک شبکه قابل اعتماد هستید</string>
|
||||||
@@ -177,6 +197,7 @@
|
|||||||
<string name="title_configuration_backup">Backup configuration</string>
|
<string name="title_configuration_backup">Backup configuration</string>
|
||||||
<string name="summary_configuration_backup">Storage location: [%s], The backup will be cleared after uninstalling the app or clearing the storage</string>
|
<string name="summary_configuration_backup">Storage location: [%s], The backup will be cleared after uninstalling the app or clearing the storage</string>
|
||||||
<string name="title_configuration_restore">Restore configuration</string>
|
<string name="title_configuration_restore">Restore configuration</string>
|
||||||
|
<string name="title_configuration_share">Share configuration</string>
|
||||||
<string name="title_pref_promotion">تبلیغات</string>
|
<string name="title_pref_promotion">تبلیغات</string>
|
||||||
<string name="summary_pref_promotion">تبلیغات، برای جزئیات بیشتر کلیک کنید (کمک مالی کنید تا حذف شود)</string>
|
<string name="summary_pref_promotion">تبلیغات، برای جزئیات بیشتر کلیک کنید (کمک مالی کنید تا حذف شود)</string>
|
||||||
|
|
||||||
|
|||||||
@@ -3,13 +3,7 @@
|
|||||||
<color name="color_fab_active">#f97910</color>
|
<color name="color_fab_active">#f97910</color>
|
||||||
<color name="color_fab_inactive">#646464</color>
|
<color name="color_fab_inactive">#646464</color>
|
||||||
<color name="color_secondary">#BDBDBD</color>
|
<color name="color_secondary">#BDBDBD</color>
|
||||||
<color name="colorSelected">#FFFFFF</color>
|
|
||||||
<color name="colorUnselected">#222222</color>
|
|
||||||
|
|
||||||
<color name="colorPrimary">#222222</color>
|
<color name="colorPrimary">#212121</color>
|
||||||
<color name="colorPrimaryDark">#000000</color>
|
|
||||||
<color name="colorAccent">#FFFFFF</color>
|
<color name="colorAccent">#FFFFFF</color>
|
||||||
<color name="colorBg">#000000</color>
|
|
||||||
<color name="colorText">#FFFFFF</color>
|
|
||||||
<color name="colorMainTestBg">#222222</color>
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -3,14 +3,10 @@
|
|||||||
|
|
||||||
<style name="AppThemeDayNight" parent="Theme.AppCompat.DayNight.DarkActionBar">
|
<style name="AppThemeDayNight" parent="Theme.AppCompat.DayNight.DarkActionBar">
|
||||||
<item name="colorPrimary">@color/colorPrimary</item>
|
<item name="colorPrimary">@color/colorPrimary</item>
|
||||||
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
|
|
||||||
<item name="colorAccent">@color/colorAccent</item>
|
<item name="colorAccent">@color/colorAccent</item>
|
||||||
<item name="colorMainBg">@color/colorBg</item>
|
<item name="android:statusBarColor">@color/colorPrimary</item>
|
||||||
<item name="colorMainText">@color/colorText</item>
|
<item name="android:navigationBarColor">@color/colorPrimary</item>
|
||||||
<item name="android:statusBarColor">@color/colorBg</item>
|
|
||||||
<item name="android:navigationBarColor">@color/colorBg</item>
|
|
||||||
<item name="colorControlNormal">@color/colorAccent</item>
|
<item name="colorControlNormal">@color/colorAccent</item>
|
||||||
<item name="android:windowBackground">@color/colorBg</item>
|
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
@@ -46,8 +46,22 @@
|
|||||||
<string name="server_lab_more_function">Другие параметры</string>
|
<string name="server_lab_more_function">Другие параметры</string>
|
||||||
<string name="server_lab_head_type">Тип заголовка</string>
|
<string name="server_lab_head_type">Тип заголовка</string>
|
||||||
<string name="server_lab_mode_type">Режим gRPC</string>
|
<string name="server_lab_mode_type">Режим gRPC</string>
|
||||||
<string name="server_lab_request_host">Запрос узла (WS/H2)/HTTPUpgrade/Шифрование QUIC/Полномочия gRPC</string>
|
<string name="server_lab_request_host">Узел</string>
|
||||||
<string name="server_lab_path">Путь (WS/H2)/HTTPUpgrade/Ключ QUIC/Сид KCP/Сервис gRPC</string>
|
<string name="server_lab_request_host_http">Узел HTTP</string>
|
||||||
|
<string name="server_lab_request_host_ws">Узел WS</string>
|
||||||
|
<string name="server_lab_request_host_httpupgrade">Узел HTTPUpgrade</string>
|
||||||
|
<string name="server_lab_request_host_splithttp">Узел SplitHTTP</string>
|
||||||
|
<string name="server_lab_request_host_h2">Узел H2</string>
|
||||||
|
<string name="server_lab_request_host_quic">Шифрование QUIC</string>
|
||||||
|
<string name="server_lab_request_host_grpc">Полномочия gRPC</string>
|
||||||
|
<string name="server_lab_path">Путь</string>
|
||||||
|
<string name="server_lab_path_ws">Путь WS</string>
|
||||||
|
<string name="server_lab_path_httpupgrade">Путь HTTPUpgrade</string>
|
||||||
|
<string name="server_lab_path_splithttp">Путь SplitHTTP</string>
|
||||||
|
<string name="server_lab_path_h2">Путь H2</string>
|
||||||
|
<string name="server_lab_path_quic">Ключ QUIC</string>
|
||||||
|
<string name="server_lab_path_kcp">Сид KCP</string>
|
||||||
|
<string name="server_lab_path_grpc">Служба gRPC</string>
|
||||||
<string name="server_lab_stream_security">TLS</string>
|
<string name="server_lab_stream_security">TLS</string>
|
||||||
<string name="server_lab_allow_insecure">Разрешать небезопасные</string>
|
<string name="server_lab_allow_insecure">Разрешать небезопасные</string>
|
||||||
<string name="server_lab_sni">SNI</string>
|
<string name="server_lab_sni">SNI</string>
|
||||||
@@ -87,6 +101,7 @@
|
|||||||
<string name="msg_remark_is_duplicate">Описание уже существует</string>
|
<string name="msg_remark_is_duplicate">Описание уже существует</string>
|
||||||
<string name="toast_action_not_allowed">Это действие запрещено</string>
|
<string name="toast_action_not_allowed">Это действие запрещено</string>
|
||||||
|
|
||||||
|
|
||||||
<!-- PerAppProxyActivity -->
|
<!-- PerAppProxyActivity -->
|
||||||
<string name="msg_dialog_progress">Загрузка…</string>
|
<string name="msg_dialog_progress">Загрузка…</string>
|
||||||
<string name="menu_item_search">Поиск</string>
|
<string name="menu_item_search">Поиск</string>
|
||||||
@@ -122,7 +137,9 @@
|
|||||||
<string name="summary_pref_speed_enabled">Показывать текущую скорость в уведомлении.\nЗначок будет меняться в зависимости от использования.</string>
|
<string name="summary_pref_speed_enabled">Показывать текущую скорость в уведомлении.\nЗначок будет меняться в зависимости от использования.</string>
|
||||||
|
|
||||||
<string name="title_pref_sniffing_enabled">Анализ пакетов</string>
|
<string name="title_pref_sniffing_enabled">Анализ пакетов</string>
|
||||||
<string name="summary_pref_sniffing_enabled">Использовать анализ пакетов (по умолчанию включено)</string>
|
<string name="summary_pref_sniffing_enabled">Использовать определение доменных имён в пакетах (по умолчанию включено)</string>
|
||||||
|
<string name="title_pref_route_only_enabled">Домен только для маршрутизации</string>
|
||||||
|
<string name="summary_pref_route_only_enabled">Использовать определённое доменное имя только для маршрутизации и сохранять целевой адрес в виде IP.</string>
|
||||||
|
|
||||||
<string name="title_pref_local_dns_enabled">Использовать локальную DNS</string>
|
<string name="title_pref_local_dns_enabled">Использовать локальную DNS</string>
|
||||||
<string name="summary_pref_local_dns_enabled">Обслуживание выполняется DNS-модулем ядра (в настройках маршрутизации рекомендуется выбрать режим «Все, кроме LAN и Китая»)</string>
|
<string name="summary_pref_local_dns_enabled">Обслуживание выполняется DNS-модулем ядра (в настройках маршрутизации рекомендуется выбрать режим «Все, кроме LAN и Китая»)</string>
|
||||||
@@ -146,6 +163,9 @@
|
|||||||
<string name="title_pref_domestic_dns">Внутренняя DNS (необязательно)</string>
|
<string name="title_pref_domestic_dns">Внутренняя DNS (необязательно)</string>
|
||||||
<string name="summary_pref_domestic_dns">DNS</string>
|
<string name="summary_pref_domestic_dns">DNS</string>
|
||||||
|
|
||||||
|
<string name="title_pref_delay_test_url">Сервис проверки времени отклика (HTTP/HTTPS)</string>
|
||||||
|
<string name="summary_pref_delay_test_url">URL</string>
|
||||||
|
|
||||||
<string name="title_pref_proxy_sharing_enabled">Разрешать подключения из LAN</string>
|
<string name="title_pref_proxy_sharing_enabled">Разрешать подключения из LAN</string>
|
||||||
<string name="summary_pref_proxy_sharing_enabled">Другие устройства могут подключаться, используя ваш IP-адрес, чтобы использовать прокси по протоколам SOCKS/HTTP. Используйте только в доверенной сети, чтобы избежать несанкционированного подключения.</string>
|
<string name="summary_pref_proxy_sharing_enabled">Другие устройства могут подключаться, используя ваш IP-адрес, чтобы использовать прокси по протоколам SOCKS/HTTP. Используйте только в доверенной сети, чтобы избежать несанкционированного подключения.</string>
|
||||||
<string name="toast_warning_pref_proxysharing_short">Доступ из LAN разрешён, убедитесь, что вы находитесь в надёжной сети</string>
|
<string name="toast_warning_pref_proxysharing_short">Доступ из LAN разрешён, убедитесь, что вы находитесь в надёжной сети</string>
|
||||||
@@ -172,13 +192,14 @@
|
|||||||
<string name="summary_pref_feedback">Предложить улучшение или сообщить об ошибке на GitHub</string>
|
<string name="summary_pref_feedback">Предложить улучшение или сообщить об ошибке на GitHub</string>
|
||||||
<string name="summary_pref_tg_group">Присоединиться к группе в Telegram</string>
|
<string name="summary_pref_tg_group">Присоединиться к группе в Telegram</string>
|
||||||
<string name="toast_tg_app_not_found">Приложение Telegram не найдено</string>
|
<string name="toast_tg_app_not_found">Приложение Telegram не найдено</string>
|
||||||
<string name="title_privacy_policy">Конфиденциальность</string>
|
<string name="title_privacy_policy">Политика конфиденциальности</string>
|
||||||
<string name="title_about">О приложении</string>
|
<string name="title_about">О приложении</string>
|
||||||
<string name="title_source_code">Исходный код</string>
|
<string name="title_source_code">Исходный код</string>
|
||||||
<string name="title_tg_channel">Telegram-канал</string>
|
<string name="title_tg_channel">Telegram-канал</string>
|
||||||
<string name="title_configuration_backup">Резервирование</string>
|
<string name="title_configuration_backup">Резервирование конфигурации</string>
|
||||||
<string name="summary_configuration_backup">Путь: [%s]. Резервная копия будет стёрта при удалении приложения или очистке хранилища.</string>
|
<string name="summary_configuration_backup">Путь: [%s]. Резервная копия будет стёрта при удалении приложения или очистке хранилища.</string>
|
||||||
<string name="title_configuration_restore">Восстановление</string>
|
<string name="title_configuration_restore">Восстановление конфигурации</string>
|
||||||
|
<string name="title_configuration_share">Поделиться конфигурацией</string>
|
||||||
|
|
||||||
<string name="title_pref_promotion">Содействие</string>
|
<string name="title_pref_promotion">Содействие</string>
|
||||||
<string name="summary_pref_promotion">Содействие, нажмите для получения подробной информации (пожертвование может быть удалено)</string>
|
<string name="summary_pref_promotion">Содействие, нажмите для получения подробной информации (пожертвование может быть удалено)</string>
|
||||||
|
|||||||
@@ -2,4 +2,4 @@
|
|||||||
<resources xmlns:tools="http://schemas.android.com/tools">
|
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||||
<bool name="config_materialPreferenceIconSpaceReserved" tools:ignore="MissingDefaultResource,PrivateResource">false</bool>
|
<bool name="config_materialPreferenceIconSpaceReserved" tools:ignore="MissingDefaultResource,PrivateResource">false</bool>
|
||||||
<dimen name="preference_category_padding_start" tools:ignore="MissingDefaultResource,PrivateResource">0dp</dimen>
|
<dimen name="preference_category_padding_start" tools:ignore="MissingDefaultResource,PrivateResource">0dp</dimen>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -11,11 +11,11 @@
|
|||||||
<!-- Notifications -->
|
<!-- Notifications -->
|
||||||
<string name="notification_action_stop_v2ray">Ngắt kết nối v2rayNG</string>
|
<string name="notification_action_stop_v2ray">Ngắt kết nối v2rayNG</string>
|
||||||
<string name="toast_permission_denied">Vui lòng cấp quyền cần thiết cho v2rayNG! Bạn đã từ chối các quyền cần thiết như Camera hay Bộ nhớ?</string>
|
<string name="toast_permission_denied">Vui lòng cấp quyền cần thiết cho v2rayNG! Bạn đã từ chối các quyền cần thiết như Camera hay Bộ nhớ?</string>
|
||||||
<string name="notification_action_more">Nhấn để biết thêm</string>
|
<string name="notification_action_more">Nhấn để biết thêm...</string>
|
||||||
<string name="toast_services_start">Đang khởi động v2rayNG...</string>
|
<string name="toast_services_start">Đang khởi động v2rayNG...</string>
|
||||||
<string name="toast_services_stop">Đã dừng v2rayNG!</string>
|
<string name="toast_services_stop">Đã dừng v2rayNG!</string>
|
||||||
<string name="toast_services_success">Đã khởi động v2rayNG!</string>
|
<string name="toast_services_success">Đã khởi động v2rayNG!</string>
|
||||||
<string name="toast_services_failure">Không thể khởi động v2rayNG, kiểm tra lại cấu hình.</string>
|
<string name="toast_services_failure">Không thể khởi động v2rayNG, kiểm tra lại cấu hình!</string>
|
||||||
|
|
||||||
<!--ServerActivity-->
|
<!--ServerActivity-->
|
||||||
<string name="title_server">v2rayNG</string>
|
<string name="title_server">v2rayNG</string>
|
||||||
@@ -24,12 +24,12 @@
|
|||||||
<string name="menu_item_del_config">Xoá cấu hình</string>
|
<string name="menu_item_del_config">Xoá cấu hình</string>
|
||||||
<string name="menu_item_import_config_qrcode">Nhập cấu hình từ mã QR</string>
|
<string name="menu_item_import_config_qrcode">Nhập cấu hình từ mã QR</string>
|
||||||
<string name="menu_item_import_config_clipboard">Nhập cấu hình từ Clipboard</string>
|
<string name="menu_item_import_config_clipboard">Nhập cấu hình từ Clipboard</string>
|
||||||
<string name="menu_item_import_config_manually_vmess">Nhập thủ công [Vmess]</string>
|
<string name="menu_item_import_config_manually_vmess">Nhập thủ công [VMESS]</string>
|
||||||
<string name="menu_item_import_config_manually_vless">Nhập thủ công [VLESS]</string>
|
<string name="menu_item_import_config_manually_vless">Nhập thủ công [VLESS]</string>
|
||||||
<string name="menu_item_import_config_manually_ss">Nhập thủ công [Shadowsocks]</string>
|
<string name="menu_item_import_config_manually_ss">Nhập thủ công [ShadowSocks]</string>
|
||||||
<string name="menu_item_import_config_manually_socks">Nhập thủ công [Socks]</string>
|
<string name="menu_item_import_config_manually_socks">Nhập thủ công [Socks]</string>
|
||||||
<string name="menu_item_import_config_manually_trojan">Nhập thủ công [Trojan]</string>
|
<string name="menu_item_import_config_manually_trojan">Nhập thủ công [Trojan]</string>
|
||||||
<string name="menu_item_import_config_manually_wireguard">Nhập thủ công [Wireguard]</string>
|
<string name="menu_item_import_config_manually_wireguard">Nhập thủ công [WireGuard]</string>
|
||||||
<string name="menu_item_import_config_custom">Nâng cao / Cấu hình tùy chỉnh</string>
|
<string name="menu_item_import_config_custom">Nâng cao / Cấu hình tùy chỉnh</string>
|
||||||
<string name="menu_item_import_config_custom_clipboard">Nhập cấu hình tùy chỉnh từ Clipboard</string>
|
<string name="menu_item_import_config_custom_clipboard">Nhập cấu hình tùy chỉnh từ Clipboard</string>
|
||||||
<string name="menu_item_import_config_custom_local">Nhập cấu hình tùy chỉnh từ Tệp</string>
|
<string name="menu_item_import_config_custom_local">Nhập cấu hình tùy chỉnh từ Tệp</string>
|
||||||
@@ -40,33 +40,47 @@
|
|||||||
<string name="server_lab_address">Địa chỉ</string>
|
<string name="server_lab_address">Địa chỉ</string>
|
||||||
<string name="server_lab_port">Cổng</string>
|
<string name="server_lab_port">Cổng</string>
|
||||||
<string name="server_lab_id">ID</string>
|
<string name="server_lab_id">ID</string>
|
||||||
<string name="server_lab_alterid">alterId</string>
|
<string name="server_lab_alterid">ID bổ sung (AlterID)</string>
|
||||||
<string name="server_lab_security">Thuật toán mã hóa</string>
|
<string name="server_lab_security">Thuật toán mã hóa</string>
|
||||||
<string name="server_lab_network">Giao thức truyền tải (network)</string>
|
<string name="server_lab_network">Giao thức truyền tải (Network)</string>
|
||||||
<string name="server_lab_more_function">Nâng cao</string>
|
<string name="server_lab_more_function">Nâng cao</string>
|
||||||
<string name="server_lab_head_type">Kiểu ngụy trang (type)</string>
|
<string name="server_lab_head_type">Kiểu ngụy trang (Type)</string>
|
||||||
<string name="server_lab_mode_type">Chế độ gRPC</string>
|
<string name="server_lab_mode_type">Chế độ gRPC</string>
|
||||||
<string name="server_lab_request_host">Yêu cầu host(host/ws host/h2 host)/QUIC security/gRPC Authority</string>
|
<string name="server_lab_request_host">host</string>
|
||||||
<string name="server_lab_path">path(ws path/h2 path)/QUIC key/kcp seed/gRPC serviceName</string>
|
<string name="server_lab_request_host_http">http host</string>
|
||||||
|
<string name="server_lab_request_host_ws">ws host</string>
|
||||||
|
<string name="server_lab_request_host_httpupgrade">httpupgrade host</string>
|
||||||
|
<string name="server_lab_request_host_splithttp">splithttp host</string>
|
||||||
|
<string name="server_lab_request_host_h2">h2 host</string>
|
||||||
|
<string name="server_lab_request_host_quic">QUIC security</string>
|
||||||
|
<string name="server_lab_request_host_grpc">gRPC Authority</string>
|
||||||
|
<string name="server_lab_path">path</string>
|
||||||
|
<string name="server_lab_path_ws">ws path</string>
|
||||||
|
<string name="server_lab_path_httpupgrade">httpupgrade path</string>
|
||||||
|
<string name="server_lab_path_splithttp">splithttp path</string>
|
||||||
|
<string name="server_lab_path_h2">h2 path</string>
|
||||||
|
<string name="server_lab_path_quic">QUIC key</string>
|
||||||
|
<string name="server_lab_path_kcp">kcp seed</string>
|
||||||
|
<string name="server_lab_path_grpc">gRPC serviceName</string>
|
||||||
<string name="server_lab_stream_security">TLS</string>
|
<string name="server_lab_stream_security">TLS</string>
|
||||||
<string name="server_lab_allow_insecure">allowInsecure</string>
|
<string name="server_lab_allow_insecure">Bỏ qua xác minh chứng chỉ</string>
|
||||||
<string name="server_lab_sni">SNI</string>
|
<string name="server_lab_sni">SNI</string>
|
||||||
<string name="server_lab_address3">Địa chỉ</string>
|
<string name="server_lab_address3">Địa chỉ</string>
|
||||||
<string name="server_lab_port3">Cổng</string>
|
<string name="server_lab_port3">Cổng</string>
|
||||||
<string name="server_lab_id3">Mật khẩu</string>
|
<string name="server_lab_id3">Mật khẩu</string>
|
||||||
<string name="server_lab_security3">Thuật toán mã hóa</string>
|
<string name="server_lab_security3">Thuật toán mã hóa</string>
|
||||||
<string name="server_lab_id4">Mật khẩu (không bắt buộc)</string>
|
<string name="server_lab_id4">Mật khẩu (Không bắt buộc)</string>
|
||||||
<string name="server_lab_security4">Tên người dùng (không bắt buộc)</string>
|
<string name="server_lab_security4">Tên người dùng (Không bắt buộc)</string>
|
||||||
<string name="server_lab_encryption">Mã hóa</string>
|
<string name="server_lab_encryption">Mã hóa</string>
|
||||||
<string name="server_lab_flow">Kiểm soát lưu lượng (flow)</string>
|
<string name="server_lab_flow">Kiểm soát lưu lượng (Flow)</string>
|
||||||
<string name="server_lab_reserved">Reserved (không bắt buộc)</string>
|
<string name="server_lab_reserved">Reserved (Không bắt buộc)</string>
|
||||||
<string name="server_lab_local_address">Địa chỉ cục bộ (IPv4/IPv6, phân cách bằng dấu phẩy)</string>
|
<string name="server_lab_local_address">Địa chỉ cục bộ (IPv4 / IPv6, phân cách bằng dấu phẩy)</string>
|
||||||
<string name="server_lab_local_mtu">MTU (không bắt buộc, mặc định 1420)</string>
|
<string name="server_lab_local_mtu">MTU (Không bắt buộc, mặc định là 1420)</string>
|
||||||
<string name="toast_success">Thành công!</string>
|
<string name="toast_success">Thành công!</string>
|
||||||
<string name="toast_failure">Đã xảy ra lỗi, vui lòng thử lại!</string>
|
<string name="toast_failure">Đã xảy ra lỗi, vui lòng thử lại!</string>
|
||||||
<string name="toast_none_data">Không có gì ở đây</string>
|
<string name="toast_none_data">Không có gì ở đây!</string>
|
||||||
<string name="toast_incorrect_protocol">Không đúng Protocol</string>
|
<string name="toast_incorrect_protocol">Protocol không đúng!</string>
|
||||||
<string name="toast_decoding_failed">Không thể giải mã</string>
|
<string name="toast_decoding_failed">Không thể giải mã!</string>
|
||||||
<string name="title_file_chooser">Vui lòng chọn tệp cấu hình!</string>
|
<string name="title_file_chooser">Vui lòng chọn tệp cấu hình!</string>
|
||||||
<string name="toast_require_file_manager">Vui lòng cài đặt trình quản lý tệp để tiếp tục!</string>
|
<string name="toast_require_file_manager">Vui lòng cài đặt trình quản lý tệp để tiếp tục!</string>
|
||||||
<string name="server_customize_config">Cấu hình tùy chỉnh</string>
|
<string name="server_customize_config">Cấu hình tùy chỉnh</string>
|
||||||
@@ -76,16 +90,16 @@
|
|||||||
<string name="toast_invalid_url">URL không hợp lệ hoặc trống!</string>
|
<string name="toast_invalid_url">URL không hợp lệ hoặc trống!</string>
|
||||||
<string name="server_lab_need_inbound">Vui lòng đảm bảo cấu hình tùy chỉnh này không bị lỗi trước khi sử dụng!</string>
|
<string name="server_lab_need_inbound">Vui lòng đảm bảo cấu hình tùy chỉnh này không bị lỗi trước khi sử dụng!</string>
|
||||||
<string name="toast_malformed_josn">Cấu hình không hợp lệ!</string>
|
<string name="toast_malformed_josn">Cấu hình không hợp lệ!</string>
|
||||||
<string name="server_lab_request_host6">Host (SNI) (không bắt buộc)</string>
|
<string name="server_lab_request_host6">Host (SNI) (Không bắt buộc)</string>
|
||||||
<string name="toast_asset_copy_failed">Không thể sao chép tệp tin, hãy dùng trình quản lý tệp!</string>
|
<string name="toast_asset_copy_failed">Không thể sao chép tệp tin, hãy dùng trình quản lý tệp!</string>
|
||||||
<string name="menu_item_add_file">Thêm tệp</string>
|
<string name="menu_item_add_file">Thêm tệp</string>
|
||||||
<string name="menu_item_download_file">Tải xuống tệp tin</string>
|
<string name="menu_item_download_file">Tải xuống tệp tin</string>
|
||||||
<string name="toast_action_not_allowed">Hành động này bị cấm</string>
|
<string name="toast_action_not_allowed">Hành động này bị cấm!</string>
|
||||||
|
|
||||||
<!-- PerAppProxyActivity -->
|
<!-- PerAppProxyActivity -->
|
||||||
<string name="title_user_asset_add_url">Thêm URL nội dung</string>
|
<string name="title_user_asset_add_url">Thêm URL nội dung</string>
|
||||||
<string name="msg_file_not_found">Không tìm thấy tập tin</string>
|
<string name="msg_file_not_found">Không tìm thấy tập tin!</string>
|
||||||
<string name="msg_remark_is_duplicate">Nhận xét đã tồn tại</string>
|
<string name="msg_remark_is_duplicate">Nhận xét đã tồn tại!</string>
|
||||||
<string name="msg_dialog_progress">Đang tải...</string>
|
<string name="msg_dialog_progress">Đang tải...</string>
|
||||||
<string name="menu_item_search">Tìm kiếm</string>
|
<string name="menu_item_search">Tìm kiếm</string>
|
||||||
<string name="menu_item_select_all">Chọn tất cả</string>
|
<string name="menu_item_select_all">Chọn tất cả</string>
|
||||||
@@ -93,7 +107,7 @@
|
|||||||
<string name="switch_bypass_apps_mode">Chế độ Bypass</string>
|
<string name="switch_bypass_apps_mode">Chế độ Bypass</string>
|
||||||
<string name="menu_item_select_proxy_app">Tự động chọn ứng dụng Proxy</string>
|
<string name="menu_item_select_proxy_app">Tự động chọn ứng dụng Proxy</string>
|
||||||
<string name="msg_downloading_content">Đang tải xuống nội dung...</string>
|
<string name="msg_downloading_content">Đang tải xuống nội dung...</string>
|
||||||
<string name="menu_item_export_proxy_app">Xuất và sao chép</string>
|
<string name="menu_item_export_proxy_app">Xuất và Sao chép</string>
|
||||||
<string name="menu_item_import_proxy_app">Nhập từ Clipboard</string>
|
<string name="menu_item_import_proxy_app">Nhập từ Clipboard</string>
|
||||||
|
|
||||||
|
|
||||||
@@ -101,15 +115,15 @@
|
|||||||
<string name="title_settings">Cài đặt</string>
|
<string name="title_settings">Cài đặt</string>
|
||||||
<string name="title_advanced">Cài đặt nâng cao</string>
|
<string name="title_advanced">Cài đặt nâng cao</string>
|
||||||
<string name="title_vpn_settings">Cài đặt VPN</string>
|
<string name="title_vpn_settings">Cài đặt VPN</string>
|
||||||
<string name="title_pref_per_app_proxy">Proxy Theo Ứng Dụng</string>
|
<string name="title_pref_per_app_proxy">Proxy theo Ứng dụng</string>
|
||||||
<string name="summary_pref_per_app_proxy">- Bình thường: Ứng dụng đã chọn sẽ kết nối thông qua Proxy, chưa chọn sẽ kết nối trực tiếp. \n- Chế độ Bypass: Ứng dụng đã chọn sẽ kết nối trực tiếp, chưa chọn sẽ kết nối qua Proxy. \n- Nếu bạn đang ở Trung Quốc thì vào Menu, chọn Tự động chọn ứng dụng Proxy.</string>
|
<string name="summary_pref_per_app_proxy">- Bình thường: Ứng dụng đã chọn sẽ kết nối thông qua Proxy, chưa chọn sẽ kết nối trực tiếp. \n- Chế độ Bypass: Ứng dụng đã chọn sẽ kết nối trực tiếp, chưa chọn sẽ kết nối qua Proxy. \n- Nếu bạn đang ở Trung Quốc thì vào Menu, chọn Tự động chọn ứng dụng Proxy.</string>
|
||||||
|
|
||||||
<string name="title_mux_settings">Cài đặt Mux</string>
|
<string name="title_mux_settings">Cài đặt Mux</string>
|
||||||
<string name="title_pref_mux_enabled">Bật Mux</string>
|
<string name="title_pref_mux_enabled">Bật Mux</string>
|
||||||
<string name="summary_pref_mux_enabled">Giảm độ trễ trong bước bắt tay của kết nối TCP. Mux phân phối dữ liệu từ nhiều kết nối TCP trên một kết nối TCP duy nhất. Không nên sử dụng Mux để xem video, download file hoặc chạy speedtest vì thường không hiệu quả.</string>
|
<string name="summary_pref_mux_enabled">Giảm độ trễ nhưng có thể làm gián đoạn luồng, vì vậy không nên kích hoạt nó. \nCác phương pháp xử lý lưu lượng truy cập TCP, UDP và QUIC có sẵn bên dưới.</string>
|
||||||
<string name="title_pref_mux_concurency">TCP connections (từ 1 đến 1024)</string>
|
<string name="title_pref_mux_concurency">Số lượng liên kết con tái sử dụng TCP (-1 đến 1024)</string>
|
||||||
<string name="title_pref_mux_xudp_concurency">XUDP connections (từ 1 đến 1024)</string>
|
<string name="title_pref_mux_xudp_concurency">Số lượng liên kết con tái sử dụng XUDP (-1 đến 1024)</string>
|
||||||
<string name="title_pref_mux_xudp_quic">Handling of QUIC traffic in mux tunnel.</string>
|
<string name="title_pref_mux_xudp_quic">Phương pháp xử lý lưu lượng QUIC</string>
|
||||||
<string-array name="mux_xudp_quic_entries">
|
<string-array name="mux_xudp_quic_entries">
|
||||||
<item>Từ chối</item>
|
<item>Từ chối</item>
|
||||||
<item>Cho phép</item>
|
<item>Cho phép</item>
|
||||||
@@ -117,40 +131,46 @@
|
|||||||
</string-array>
|
</string-array>
|
||||||
|
|
||||||
|
|
||||||
<string name="title_pref_speed_enabled">Bật Hiển thị tốc độ mạng</string>
|
<string name="title_pref_speed_enabled">Hiển thị tốc độ mạng</string>
|
||||||
<string name="summary_pref_speed_enabled">Hiển thị tốc độ mạng hiện tại trên thanh thông báo. \nBiểu tượng trên thanh trạng thái có thể thay đổi tùy vào mức sử dụng.</string>
|
<string name="summary_pref_speed_enabled">Hiển thị tốc độ mạng hiện tại trên thanh thông báo. \nBiểu tượng trên thanh trạng thái có thể thay đổi tùy vào mức sử dụng.</string>
|
||||||
|
|
||||||
<string name="title_pref_sniffing_enabled">Bật Sniffing</string>
|
<string name="title_pref_sniffing_enabled">Bật Sniffing</string>
|
||||||
<string name="summary_pref_sniffing_enabled">Nhận diện tên miền từ gói tin để phục vụ định tuyến. \n(phải tắt để xài Zalo)</string>
|
<string name="summary_pref_sniffing_enabled">Phát hiện tên miền từ lưu lượng truy cập (Được bật theo mặc định). \nỞ Việt Nam: tắt để sử dụng Zalo.</string>
|
||||||
|
<string name="title_pref_route_only_enabled">Bật RouteOnly</string>
|
||||||
|
<string name="summary_pref_route_only_enabled">Chỉ sử dụng tên miền được đánh dấu để định tuyến và giữ địa chỉ đích làm địa chỉ IP.</string>
|
||||||
|
|
||||||
|
|
||||||
<string name="title_pref_local_dns_enabled">Bật Local DNS</string>
|
<string name="title_pref_local_dns_enabled">Bật Local DNS</string>
|
||||||
<string name="summary_pref_local_dns_enabled">DNS được xử lý bởi mô-đun DNS của xray-core (dùng nếu cần định tuyến bypass cho mạng LAN và địa chỉ nội địa)</string>
|
<string name="summary_pref_local_dns_enabled">DNS được xử lý bởi module DNS của Xray-Core (Dùng nếu cần định tuyến Bypass cho mạng LAN và Địa chỉ nội địa).</string>
|
||||||
|
|
||||||
<string name="title_pref_fake_dns_enabled">Bật FakeDNS</string>
|
<string name="title_pref_fake_dns_enabled">Bật FakeDNS</string>
|
||||||
<string name="summary_pref_fake_dns_enabled">FakeDNS lấy tên miền mục tiêu bằng cách giả mạo DNS (nhanh hơn, nhưng có thể không hoạt động cho một số ứng dụng)</string>
|
<string name="summary_pref_fake_dns_enabled">FakeDNS lấy tên miền mục tiêu bằng cách giả mạo DNS (Nhanh hơn, nhưng có thể không hoạt động cho một số ứng dụng).</string>
|
||||||
|
|
||||||
<string name="title_pref_prefer_ipv6">Ưu tiên IPv6</string>
|
<string name="title_pref_prefer_ipv6">Ưu tiên IPv6</string>
|
||||||
<string name="summary_pref_prefer_ipv6">Ưu tiên sử dụng địa chỉ IPv6 cho kết nối và định tuyến.</string>
|
<string name="summary_pref_prefer_ipv6">Ưu tiên sử dụng địa chỉ IPv6 cho kết nối và định tuyến.</string>
|
||||||
|
|
||||||
<string name="title_pref_routing">Định tuyến</string>
|
<string name="title_pref_routing">Định tuyến</string>
|
||||||
<string name="title_pref_routing_domain_strategy">Chiến lược tên miền (domainStrategy)</string>
|
<string name="title_pref_routing_domain_strategy">Chiến lược tên miền (DomainStrategy)</string>
|
||||||
<string name="title_pref_routing_mode">Quy tắc được định nghĩa trước</string>
|
<string name="title_pref_routing_mode">Quy tắc được định nghĩa trước</string>
|
||||||
<string name="title_pref_routing_custom">Quy tắc tùy chỉnh</string>
|
<string name="title_pref_routing_custom">Quy tắc tùy chỉnh</string>
|
||||||
|
|
||||||
<string name="title_pref_remote_dns">DNS ngoại quốc (không bắt buộc)</string>
|
<string name="title_pref_remote_dns">DNS ngoại quốc (UDP / TCP / HTTPS / QUIC) (Không bắt buộc)</string>
|
||||||
<string name="summary_pref_remote_dns">DNS</string>
|
<string name="summary_pref_remote_dns">DNS</string>
|
||||||
|
|
||||||
<string name="title_pref_vpn_dns">VPN DNS (Chỉ IPv4/IPv6)</string>
|
<string name="title_pref_vpn_dns">VPN DNS (Chỉ IPv4 / IPv6)</string>
|
||||||
|
|
||||||
<string name="title_pref_domestic_dns">DNS nội địa (không bắt buộc)</string>
|
<string name="title_pref_domestic_dns">DNS nội địa (Không bắt buộc)</string>
|
||||||
<string name="summary_pref_domestic_dns">DNS</string>
|
<string name="summary_pref_domestic_dns">DNS</string>
|
||||||
|
|
||||||
|
<string name="title_pref_delay_test_url">URL kiểm tra độ trễ thực (HTTP / HTTPS)</string>
|
||||||
|
<string name="summary_pref_delay_test_url">URL</string>
|
||||||
|
|
||||||
<string name="title_pref_proxy_sharing_enabled">Cho phép kết nối từ mạng LAN</string>
|
<string name="title_pref_proxy_sharing_enabled">Cho phép kết nối từ mạng LAN</string>
|
||||||
<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="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="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">allowInsecure</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ỉ (allowInsecure: true).</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 SOCKS5</string>
|
<string name="title_pref_socks_port">Cổng Proxy SOCKS5</string>
|
||||||
<string name="summary_pref_socks_port">Cổng Proxy SOCKS5</string>
|
<string name="summary_pref_socks_port">Cổng Proxy SOCKS5</string>
|
||||||
@@ -170,28 +190,29 @@
|
|||||||
<string name="title_pref_feedback">Phản hồi lỗi</string>
|
<string name="title_pref_feedback">Phản hồi lỗi</string>
|
||||||
<string name="summary_pref_feedback">Phản hồi cải tiến hoặc lỗi lên GitHub</string>
|
<string name="summary_pref_feedback">Phản hồi cải tiến hoặc lỗi lên GitHub</string>
|
||||||
<string name="summary_pref_tg_group">Tham gia nhóm Telegram</string>
|
<string name="summary_pref_tg_group">Tham gia nhóm Telegram</string>
|
||||||
<string name="toast_tg_app_not_found">Không tìm thấy ứng dụng Telegram</string>
|
<string name="toast_tg_app_not_found">Không tìm thấy ứng dụng Telegram!</string>
|
||||||
|
|
||||||
<string name="title_privacy_policy">Chính sách bảo mật</string>
|
<string name="title_privacy_policy">Chính sách bảo mật</string>
|
||||||
<string name="title_about">About</string>
|
<string name="title_about">Giới thiệu</string>
|
||||||
<string name="title_source_code">Source code</string>
|
<string name="title_source_code">Mã nguồn</string>
|
||||||
<string name="title_tg_channel">Telegram channel</string>
|
<string name="title_tg_channel">Kênh Telegram</string>
|
||||||
<string name="title_configuration_backup">Backup configuration</string>
|
<string name="title_configuration_backup">Sao lưu cấu hình</string>
|
||||||
<string name="summary_configuration_backup">Storage location: [%s], The backup will be cleared after uninstalling the app or clearing the storage</string>
|
<string name="summary_configuration_backup">Nơi lưu trữ: [%s], bản backup sẽ được dọn dẹp sau khi xóa ứng dụng hoặc xóa bộ nhớ.</string>
|
||||||
<string name="title_configuration_restore">Restore configuration</string>
|
<string name="title_configuration_restore">Khôi phục cấu hình</string>
|
||||||
|
<string name="title_configuration_share">Chia sẻ cấu hình</string>
|
||||||
<string name="title_pref_promotion">Quảng bá server</string>
|
<string name="title_pref_promotion">Quảng bá server</string>
|
||||||
<string name="summary_pref_promotion">Quảng cáo, nhấn để biết thêm (Ủng hộ có thể được gỡ bỏ)</string>
|
<string name="summary_pref_promotion">Quảng cáo, nhấn để biết thêm (Ủng hộ có thể được gỡ bỏ)</string>
|
||||||
|
|
||||||
<string name="title_pref_auto_update_subscription">Tự động cập nhật các gói đăng ký</string>
|
<string name="title_pref_auto_update_subscription">Tự động cập nhật các gói đăng ký</string>
|
||||||
<string name="summary_pref_auto_update_subscription">Tự động cập nhật các gói đăng ký của bạn ở trong nền với khoảng thời gian cố định. Tùy thiết bị, tính năng này có thể không luôn hoạt động đúng như mong đợi.</string>
|
<string name="summary_pref_auto_update_subscription">Tự động cập nhật các gói đăng ký của bạn ở trong nền với khoảng thời gian cố định. Tùy thiết bị, tính năng này có thể không luôn hoạt động đúng như mong đợi.</string>
|
||||||
<string name="title_pref_auto_update_interval">Thời gian Cập nhật tự động (Phút, Giá trị tối thiểu 15)</string>
|
<string name="title_pref_auto_update_interval">Thời gian cập nhật tự động (Phút, giá trị tối thiểu là 15)</string>
|
||||||
|
|
||||||
<string name="title_core_loglevel">Log level</string>
|
<string name="title_core_loglevel">Cấp độ nhật ký</string>
|
||||||
<string name="title_mode">Chế độ kết nối</string>
|
<string name="title_mode">Chế độ kết nối</string>
|
||||||
<string name="title_mode_help">Nhấn vào đây nếu bạn cần trợ giúp!</string>
|
<string name="title_mode_help">Nhấn vào đây nếu bạn cần trợ giúp!</string>
|
||||||
<string name="title_language">Ngôn ngữ</string>
|
<string name="title_language">Ngôn ngữ</string>
|
||||||
<string name="title_ui_settings">Cài đặt giao diện</string>
|
<string name="title_ui_settings">Cài đặt UI</string>
|
||||||
<string name="title_pref_ui_mode_night">UI mode settings</string>
|
<string name="title_pref_ui_mode_night">Cài đặt chế độ UI</string>
|
||||||
|
|
||||||
<string name="title_logcat">Logcat</string>
|
<string name="title_logcat">Logcat</string>
|
||||||
<string name="logcat_copy">Sao chép</string>
|
<string name="logcat_copy">Sao chép</string>
|
||||||
@@ -200,7 +221,7 @@
|
|||||||
<string name="title_del_all_config">Xoá tất cả cấu hình</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>
|
<string name="title_del_duplicate_config">Xoá cấu hình trùng lặp</string>
|
||||||
<string name="title_del_invalid_config">Xoá cấu hình lỗi</string>
|
<string name="title_del_invalid_config">Xoá cấu hình lỗi</string>
|
||||||
<string name="title_export_all">Xuất và sao chép tất cả cấu hình</string>
|
<string name="title_export_all">Xuất và Sao chép tất cả cấu hình</string>
|
||||||
<string name="title_sub_setting">Các gói đăng ký</string>
|
<string name="title_sub_setting">Các gói đăng ký</string>
|
||||||
<string name="sub_setting_remarks">Tên gói đăng ký</string>
|
<string name="sub_setting_remarks">Tên gói đăng ký</string>
|
||||||
<string name="sub_setting_url">URL gói đăng ký</string>
|
<string name="sub_setting_url">URL gói đăng ký</string>
|
||||||
@@ -208,8 +229,8 @@
|
|||||||
<string name="sub_auto_update">Bật tự động cập nhật</string>
|
<string name="sub_auto_update">Bật tự động cập nhật</string>
|
||||||
<string name="title_sub_update">Cập nhật các gói đăng ký</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_ping_all_server">Ping tất cả máy chủ</string>
|
||||||
<string name="title_real_ping_all_server">Test HTTP 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">Tệp Geo asset</string>
|
<string name="title_user_asset_setting">Tệp Geo Asset</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>
|
<string name="title_sort_by_test_results">Sắp xếp lại theo lần kiểm tra cuối cùng</string>
|
||||||
<string name="title_filter_config">Lọc cấu hình theo các gói đăng ký</string>
|
<string name="title_filter_config">Lọc cấu hình theo các gói đăng ký</string>
|
||||||
<string name="filter_config_all">Hiển thị tất cả các gói đăng ký</string>
|
<string name="filter_config_all">Hiển thị tất cả các gói đăng ký</string>
|
||||||
@@ -219,20 +240,20 @@
|
|||||||
<string name="tasker_setting_confirm">Xác nhận</string>
|
<string name="tasker_setting_confirm">Xác nhận</string>
|
||||||
|
|
||||||
<string name="routing_settings_title">Cài đặt định tuyến</string>
|
<string name="routing_settings_title">Cài đặt định tuyến</string>
|
||||||
<string name="routing_settings_tips">Phân cách bằng dấu phẩy (,). Có thể tải xuống rules mặc định để tham khảo ở menu ba chấm</string>
|
<string name="routing_settings_tips">Phân cách bằng dấu phẩy (,). Có thể tải xuống Rules mặc định để tham khảo ở menu ba chấm.</string>
|
||||||
<string name="routing_settings_save">Lưu lại</string>
|
<string name="routing_settings_save">Lưu lại</string>
|
||||||
<string name="routing_settings_delete">Xoá</string>
|
<string name="routing_settings_delete">Xoá</string>
|
||||||
<string name="routing_settings_scan_replace">Quét QR và thay thế</string>
|
<string name="routing_settings_scan_replace">Quét QR và Thay thế</string>
|
||||||
<string name="routing_settings_scan_append">Quét QR và nối thêm</string>
|
<string name="routing_settings_scan_append">Quét QR và Nối thêm</string>
|
||||||
<string name="routing_settings_default_rules">Tải xuống rules mặc định cho China</string>
|
<string name="routing_settings_default_rules">Tải xuống Rules mặc định cho Trung Quốc</string>
|
||||||
|
|
||||||
<string name="connection_test_pending">Kiểm tra kết nối</string>
|
<string name="connection_test_pending">Kiểm tra kết nối</string>
|
||||||
<string name="connection_test_testing">Đang kiểm tra kết nối mạng...</string>
|
<string name="connection_test_testing">Đang kiểm tra kết nối mạng...</string>
|
||||||
<string name="connection_test_available">Test thành công: Mất %d ms để truy cập Google.com</string>
|
<string name="connection_test_available">Kiểm tra thành công: thời gian truy cập Google là %d ms</string>
|
||||||
<string name="connection_test_error">Lỗi kết nối mạng, hãy thử đổi cấu hình hoặc kiểm tra lại! Mã lỗi: %s</string>
|
<string name="connection_test_error">Lỗi kết nối mạng, hãy thử đổi cấu hình hoặc kiểm tra lại! Mã lỗi: %s</string>
|
||||||
<string name="connection_test_fail">Không có kết nối mạng!</string>
|
<string name="connection_test_fail">Không có kết nối mạng!</string>
|
||||||
<string name="connection_test_error_status_code">Mã lỗi: #%d</string>
|
<string name="connection_test_error_status_code">Mã lỗi: #%d</string>
|
||||||
<string name="connection_connected">Đã kết nối, nhấn vào đây để kiểm tra kết nối mạng!</string>
|
<string name="connection_connected">Đã kết nối, nhấn để kiểm tra kết nối mạng!</string>
|
||||||
<string name="connection_not_connected">Chưa kết nối</string>
|
<string name="connection_not_connected">Chưa kết nối</string>
|
||||||
|
|
||||||
<string name="import_subscription_success">Nhập gói đăng ký thành công!</string>
|
<string name="import_subscription_success">Nhập gói đăng ký thành công!</string>
|
||||||
@@ -240,13 +261,13 @@
|
|||||||
|
|
||||||
<string-array name="share_method">
|
<string-array name="share_method">
|
||||||
<item>Xuất ra mã QR (Chụp màn hình để lưu)</item>
|
<item>Xuất ra mã QR (Chụp màn hình để lưu)</item>
|
||||||
<item>Sao chép vào bảng nhớ tạm</item>
|
<item>Sao chép vào Clipboard</item>
|
||||||
<item>Sao chép thành cấu hình tùy chỉnh</item>
|
<item>Sao chép thành cấu hình tùy chỉnh</item>
|
||||||
</string-array>
|
</string-array>
|
||||||
|
|
||||||
<string-array name="share_sub_method">
|
<string-array name="share_sub_method">
|
||||||
<item>Xuất gói ra mã QR (Chụp màn hình để lưu)</item>
|
<item>Xuất gói ra mã QR (Chụp màn hình để lưu)</item>
|
||||||
<item>Xuất gói vào bảng nhớ tạm</item>
|
<item>Xuất gói vào Clipboard</item>
|
||||||
</string-array>
|
</string-array>
|
||||||
|
|
||||||
<string-array name="routing_tag">
|
<string-array name="routing_tag">
|
||||||
@@ -271,9 +292,9 @@
|
|||||||
<string name="menu_item_add_url">Thêm liên kết</string>
|
<string name="menu_item_add_url">Thêm liên kết</string>
|
||||||
|
|
||||||
<string-array name="ui_mode_night">
|
<string-array name="ui_mode_night">
|
||||||
<item>Follow system</item>
|
<item>Theo hệ thống</item>
|
||||||
<item>Light</item>
|
<item>Sáng</item>
|
||||||
<item>Dark</item>
|
<item>Tối</item>
|
||||||
</string-array>
|
</string-array>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -46,8 +46,22 @@
|
|||||||
<string name="server_lab_more_function">底层传输方式(transport)</string>
|
<string name="server_lab_more_function">底层传输方式(transport)</string>
|
||||||
<string name="server_lab_head_type">伪装类型(type)</string>
|
<string name="server_lab_head_type">伪装类型(type)</string>
|
||||||
<string name="server_lab_mode_type">gRPC 传输模式(mode)</string>
|
<string name="server_lab_mode_type">gRPC 传输模式(mode)</string>
|
||||||
<string name="server_lab_request_host">伪装域名(host)(host/ws host/httpupgrade host/h2 host)/QUIC 加密方式/gRPC Authority</string>
|
<string name="server_lab_request_host">伪装域名(host)</string>
|
||||||
<string name="server_lab_path">path(ws path/httpupgrade path/h2 path)/QUIC 加密密钥/kcp seed/gRPC serviceName</string>
|
<string name="server_lab_request_host_http">http host</string>
|
||||||
|
<string name="server_lab_request_host_ws">ws host</string>
|
||||||
|
<string name="server_lab_request_host_httpupgrade">httpupgrade host</string>
|
||||||
|
<string name="server_lab_request_host_splithttp">splithttp host</string>
|
||||||
|
<string name="server_lab_request_host_h2">h2 host</string>
|
||||||
|
<string name="server_lab_request_host_quic">QUIC 加密方式</string>
|
||||||
|
<string name="server_lab_request_host_grpc">gRPC Authority</string>
|
||||||
|
<string name="server_lab_path">path</string>
|
||||||
|
<string name="server_lab_path_ws">ws path</string>
|
||||||
|
<string name="server_lab_path_httpupgrade">httpupgrade path</string>
|
||||||
|
<string name="server_lab_path_splithttp">splithttp path</string>
|
||||||
|
<string name="server_lab_path_h2">h2 path</string>
|
||||||
|
<string name="server_lab_path_quic">QUIC 加密密钥</string>
|
||||||
|
<string name="server_lab_path_kcp">kcp seed</string>
|
||||||
|
<string name="server_lab_path_grpc">gRPC serviceName</string>
|
||||||
<string name="server_lab_stream_security">传输层安全(TLS)</string>
|
<string name="server_lab_stream_security">传输层安全(TLS)</string>
|
||||||
<string name="server_lab_allow_insecure">跳过证书验证(allowInsecure)</string>
|
<string name="server_lab_allow_insecure">跳过证书验证(allowInsecure)</string>
|
||||||
<string name="server_lab_sni">SNI</string>
|
<string name="server_lab_sni">SNI</string>
|
||||||
@@ -121,6 +135,8 @@
|
|||||||
|
|
||||||
<string name="title_pref_sniffing_enabled">启用流量探测</string>
|
<string name="title_pref_sniffing_enabled">启用流量探测</string>
|
||||||
<string name="summary_pref_sniffing_enabled">从流量中探测域名 (默认启用)</string>
|
<string name="summary_pref_sniffing_enabled">从流量中探测域名 (默认启用)</string>
|
||||||
|
<string name="title_pref_route_only_enabled">启用 routeOnly</string>
|
||||||
|
<string name="summary_pref_route_only_enabled">将嗅探得到的域名仅用于路由,代理目标地址仍为 IP</string>
|
||||||
|
|
||||||
<string name="title_pref_local_dns_enabled">启用本地DNS</string>
|
<string name="title_pref_local_dns_enabled">启用本地DNS</string>
|
||||||
<string name="summary_pref_local_dns_enabled">DNS 请求导入 core 由 DNS 模块处理(推荐启用 如果需要路由绕过局域网及大陆地址)</string>
|
<string name="summary_pref_local_dns_enabled">DNS 请求导入 core 由 DNS 模块处理(推荐启用 如果需要路由绕过局域网及大陆地址)</string>
|
||||||
@@ -144,6 +160,9 @@
|
|||||||
<string name="title_pref_domestic_dns">境内DNS (可选)</string>
|
<string name="title_pref_domestic_dns">境内DNS (可选)</string>
|
||||||
<string name="summary_pref_domestic_dns">DNS</string>
|
<string name="summary_pref_domestic_dns">DNS</string>
|
||||||
|
|
||||||
|
<string name="title_pref_delay_test_url">真连接延迟测试网址 (http/https)</string>
|
||||||
|
<string name="summary_pref_delay_test_url">Url</string>
|
||||||
|
|
||||||
<string name="title_pref_proxy_sharing_enabled">允许来自局域网的连接</string>
|
<string name="title_pref_proxy_sharing_enabled">允许来自局域网的连接</string>
|
||||||
<string name="summary_pref_proxy_sharing_enabled">其他设备可以使用socks/http协议通过您的IP地址连接到代理,仅在受信任的网络中启用以避免未经授权的连接</string>
|
<string name="summary_pref_proxy_sharing_enabled">其他设备可以使用socks/http协议通过您的IP地址连接到代理,仅在受信任的网络中启用以避免未经授权的连接</string>
|
||||||
<string name="toast_warning_pref_proxysharing_short">允许来自局域网的连接,请确保处于受信网络</string>
|
<string name="toast_warning_pref_proxysharing_short">允许来自局域网的连接,请确保处于受信网络</string>
|
||||||
@@ -177,6 +196,7 @@
|
|||||||
<string name="title_configuration_backup">备份配置</string>
|
<string name="title_configuration_backup">备份配置</string>
|
||||||
<string name="summary_configuration_backup">存储位置: [%s], 卸载App或清除存储后备份将被清除</string>
|
<string name="summary_configuration_backup">存储位置: [%s], 卸载App或清除存储后备份将被清除</string>
|
||||||
<string name="title_configuration_restore">还原配置</string>
|
<string name="title_configuration_restore">还原配置</string>
|
||||||
|
<string name="title_configuration_share">分享配置</string>
|
||||||
|
|
||||||
<string name="title_pref_promotion">推广</string>
|
<string name="title_pref_promotion">推广</string>
|
||||||
<string name="summary_pref_promotion">一些推广,点击查看详情(捐赠可去除)</string>
|
<string name="summary_pref_promotion">一些推广,点击查看详情(捐赠可去除)</string>
|
||||||
|
|||||||
@@ -46,8 +46,22 @@
|
|||||||
<string name="server_lab_more_function">底層傳輸方式 (transport)</string>
|
<string name="server_lab_more_function">底層傳輸方式 (transport)</string>
|
||||||
<string name="server_lab_head_type">標頭類型</string>
|
<string name="server_lab_head_type">標頭類型</string>
|
||||||
<string name="server_lab_mode_type">gRPC 傳輸模式 (mode)</string>
|
<string name="server_lab_mode_type">gRPC 傳輸模式 (mode)</string>
|
||||||
<string name="server_lab_request_host">要求主機 (host)(host/ws host/httpupgrade host/h2 host)/QUIC 加密方式/gRPC Authority</string>
|
<string name="server_lab_request_host">要求主機 (host)</string>
|
||||||
<string name="server_lab_path">path(ws path/httpupgrade path/h2 path)/QUIC 加密金鑰/kcp seed/gRPC serviceName</string>
|
<string name="server_lab_request_host_http">http host</string>
|
||||||
|
<string name="server_lab_request_host_ws">ws host</string>
|
||||||
|
<string name="server_lab_request_host_httpupgrade">httpupgrade host</string>
|
||||||
|
<string name="server_lab_request_host_splithttp">splithttp host</string>
|
||||||
|
<string name="server_lab_request_host_h2">h2 host</string>
|
||||||
|
<string name="server_lab_request_host_quic">QUIC 加密方式</string>
|
||||||
|
<string name="server_lab_request_host_grpc">gRPC Authority</string>
|
||||||
|
<string name="server_lab_path">path</string>
|
||||||
|
<string name="server_lab_path_ws">ws path</string>
|
||||||
|
<string name="server_lab_path_httpupgrade">httpupgrade path</string>
|
||||||
|
<string name="server_lab_path_splithttp">splithttp path</string>
|
||||||
|
<string name="server_lab_path_h2">h2 path</string>
|
||||||
|
<string name="server_lab_path_quic">QUIC 加密金鑰</string>
|
||||||
|
<string name="server_lab_path_kcp">kcp seed</string>
|
||||||
|
<string name="server_lab_path_grpc">gRPC serviceName</string>
|
||||||
<string name="server_lab_stream_security">傳輸層安全 (TLS)</string>
|
<string name="server_lab_stream_security">傳輸層安全 (TLS)</string>
|
||||||
<string name="server_lab_allow_insecure">跳過憑證驗證 (allowInsecure)</string>
|
<string name="server_lab_allow_insecure">跳過憑證驗證 (allowInsecure)</string>
|
||||||
<string name="server_lab_sni">SNI</string>
|
<string name="server_lab_sni">SNI</string>
|
||||||
@@ -121,6 +135,9 @@
|
|||||||
|
|
||||||
<string name="title_pref_sniffing_enabled">啟用流量監聽</string>
|
<string name="title_pref_sniffing_enabled">啟用流量監聽</string>
|
||||||
<string name="summary_pref_sniffing_enabled">從流量中監聽網域 (預設啟用)</string>
|
<string name="summary_pref_sniffing_enabled">從流量中監聽網域 (預設啟用)</string>
|
||||||
|
<string name="title_pref_route_only_enabled">啟用 routeOnly</string>
|
||||||
|
<string name="summary_pref_route_only_enabled">將嗅探得到的網域只用於路由,代理目標位址仍為 IP</string>
|
||||||
|
|
||||||
|
|
||||||
<string name="title_pref_local_dns_enabled">啟用本機 DNS</string>
|
<string name="title_pref_local_dns_enabled">啟用本機 DNS</string>
|
||||||
<string name="summary_pref_local_dns_enabled">DNS 請求匯入 core 由 DNS 模塊處理 (建議啟用,如果需要轉送略過區域網路及中國大陸)</string>
|
<string name="summary_pref_local_dns_enabled">DNS 請求匯入 core 由 DNS 模塊處理 (建議啟用,如果需要轉送略過區域網路及中國大陸)</string>
|
||||||
@@ -144,6 +161,9 @@
|
|||||||
<string name="title_pref_domestic_dns">國內 DNS (可選)</string>
|
<string name="title_pref_domestic_dns">國內 DNS (可選)</string>
|
||||||
<string name="summary_pref_domestic_dns">DNS</string>
|
<string name="summary_pref_domestic_dns">DNS</string>
|
||||||
|
|
||||||
|
<string name="title_pref_delay_test_url">真連線延遲測試網址 (http/https)</string>
|
||||||
|
<string name="summary_pref_delay_test_url">Url</string>
|
||||||
|
|
||||||
<string name="title_pref_proxy_sharing_enabled">允許來自區域網路的連線</string>
|
<string name="title_pref_proxy_sharing_enabled">允許來自區域網路的連線</string>
|
||||||
<string name="summary_pref_proxy_sharing_enabled">其他裝置可以使用 socks/http 協定透過您的 IP 位址連線到 Proxy,僅在受信任的網路中啟用以避免未經授權的連線</string>
|
<string name="summary_pref_proxy_sharing_enabled">其他裝置可以使用 socks/http 協定透過您的 IP 位址連線到 Proxy,僅在受信任的網路中啟用以避免未經授權的連線</string>
|
||||||
<string name="toast_warning_pref_proxysharing_short">允許來自區域網路的連線,請確保處於受信網路</string>
|
<string name="toast_warning_pref_proxysharing_short">允許來自區域網路的連線,請確保處於受信網路</string>
|
||||||
@@ -177,6 +197,7 @@
|
|||||||
<string name="title_configuration_backup">備份配置</string>
|
<string name="title_configuration_backup">備份配置</string>
|
||||||
<string name="summary_configuration_backup">儲存位置: [%s], 卸載App或清除儲存後備份將被清除</string>
|
<string name="summary_configuration_backup">儲存位置: [%s], 卸載App或清除儲存後備份將被清除</string>
|
||||||
<string name="title_configuration_restore">還原配置</string>
|
<string name="title_configuration_restore">還原配置</string>
|
||||||
|
<string name="title_configuration_share">分享配置</string>
|
||||||
|
|
||||||
<string name="title_pref_promotion">推廣</string>
|
<string name="title_pref_promotion">推廣</string>
|
||||||
<string name="summary_pref_promotion">一些推廣,輕觸以檢視 (捐贈可去除)</string>
|
<string name="summary_pref_promotion">一些推廣,輕觸以檢視 (捐贈可去除)</string>
|
||||||
|
|||||||
@@ -26,6 +26,7 @@
|
|||||||
<item>kcp</item>
|
<item>kcp</item>
|
||||||
<item>ws</item>
|
<item>ws</item>
|
||||||
<item>httpupgrade</item>
|
<item>httpupgrade</item>
|
||||||
|
<item>splithttp</item>
|
||||||
<item>h2</item>
|
<item>h2</item>
|
||||||
<item>quic</item>
|
<item>quic</item>
|
||||||
<item>grpc</item>
|
<item>grpc</item>
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<resources>
|
||||||
<attr name="colorMainBg" format="color"/>
|
|
||||||
<attr name="colorMainText" format="color"/>
|
|
||||||
</resources>
|
</resources>
|
||||||
@@ -3,18 +3,11 @@
|
|||||||
<color name="colorPing">#009966</color>
|
<color name="colorPing">#009966</color>
|
||||||
<color name="colorPingRed">#FF0099</color>
|
<color name="colorPingRed">#FF0099</color>
|
||||||
<color name="colorConfigType">#f97910</color>
|
<color name="colorConfigType">#f97910</color>
|
||||||
<color name="colorWidgetText">#FFFFFF</color>
|
|
||||||
|
|
||||||
<color name="color_fab_active">#f97910</color>
|
<color name="color_fab_active">#f97910</color>
|
||||||
<color name="color_fab_inactive">#9C9C9C</color>
|
<color name="color_fab_inactive">#9C9C9C</color>
|
||||||
<color name="color_secondary">#727272</color>
|
<color name="color_secondary">#727272</color>
|
||||||
<color name="colorSelected">#000000</color>
|
|
||||||
<color name="colorUnselected">#FFFFFF</color>
|
|
||||||
|
|
||||||
<color name="colorPrimary">#FFFFFF</color>
|
<color name="colorPrimary">#F5F5F5</color>
|
||||||
<color name="colorPrimaryDark">#000000</color>
|
|
||||||
<color name="colorAccent">#000000</color>
|
<color name="colorAccent">#000000</color>
|
||||||
<color name="colorBg">#FFFFFF</color>
|
|
||||||
<color name="colorText">#000000</color>
|
|
||||||
<color name="colorMainTestBg">#F0F1F6</color>
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -47,8 +47,22 @@
|
|||||||
<string name="server_lab_more_function">Transport</string>
|
<string name="server_lab_more_function">Transport</string>
|
||||||
<string name="server_lab_head_type">head type</string>
|
<string name="server_lab_head_type">head type</string>
|
||||||
<string name="server_lab_mode_type">gRPC mode</string>
|
<string name="server_lab_mode_type">gRPC mode</string>
|
||||||
<string name="server_lab_request_host">request host(host/ws host/httpupgrade host/h2 host)/QUIC security/gRPC Authority</string>
|
<string name="server_lab_request_host">host</string>
|
||||||
<string name="server_lab_path">path(ws path/httpupgrade path/h2 path)/QUIC key/kcp seed/gRPC serviceName</string>
|
<string name="server_lab_request_host_http">http host</string>
|
||||||
|
<string name="server_lab_request_host_ws">ws host</string>
|
||||||
|
<string name="server_lab_request_host_httpupgrade">httpupgrade host</string>
|
||||||
|
<string name="server_lab_request_host_splithttp">splithttp host</string>
|
||||||
|
<string name="server_lab_request_host_h2">h2 host</string>
|
||||||
|
<string name="server_lab_request_host_quic">QUIC security</string>
|
||||||
|
<string name="server_lab_request_host_grpc">gRPC Authority</string>
|
||||||
|
<string name="server_lab_path">path</string>
|
||||||
|
<string name="server_lab_path_ws">ws path</string>
|
||||||
|
<string name="server_lab_path_httpupgrade">httpupgrade path</string>
|
||||||
|
<string name="server_lab_path_splithttp">splithttp path</string>
|
||||||
|
<string name="server_lab_path_h2">h2 path</string>
|
||||||
|
<string name="server_lab_path_quic">QUIC key</string>
|
||||||
|
<string name="server_lab_path_kcp">kcp seed</string>
|
||||||
|
<string name="server_lab_path_grpc">gRPC serviceName</string>
|
||||||
<string name="server_lab_stream_security">TLS</string>
|
<string name="server_lab_stream_security">TLS</string>
|
||||||
<string name="server_lab_stream_fingerprint" translatable="false">Fingerprint</string>
|
<string name="server_lab_stream_fingerprint" translatable="false">Fingerprint</string>
|
||||||
<string name="server_lab_stream_alpn" translatable="false">Alpn</string>
|
<string name="server_lab_stream_alpn" translatable="false">Alpn</string>
|
||||||
@@ -133,6 +147,8 @@
|
|||||||
|
|
||||||
<string name="title_pref_sniffing_enabled">Enable Sniffing</string>
|
<string name="title_pref_sniffing_enabled">Enable Sniffing</string>
|
||||||
<string name="summary_pref_sniffing_enabled">Try sniff domain from the packet (default on)</string>
|
<string name="summary_pref_sniffing_enabled">Try sniff domain from the packet (default on)</string>
|
||||||
|
<string name="title_pref_route_only_enabled">Enable routeOnly</string>
|
||||||
|
<string name="summary_pref_route_only_enabled">Use the sniffed domain name for routing only, and keep the target address as the IP address.</string>
|
||||||
|
|
||||||
<string name="title_pref_local_dns_enabled">Enable local DNS</string>
|
<string name="title_pref_local_dns_enabled">Enable local DNS</string>
|
||||||
<string name="summary_pref_local_dns_enabled">DNS processed by core‘s DNS module (Recommended, if need routing Bypassing LAN and
|
<string name="summary_pref_local_dns_enabled">DNS processed by core‘s DNS module (Recommended, if need routing Bypassing LAN and
|
||||||
@@ -157,6 +173,9 @@
|
|||||||
<string name="title_pref_domestic_dns">Domestic DNS (Optional)</string>
|
<string name="title_pref_domestic_dns">Domestic DNS (Optional)</string>
|
||||||
<string name="summary_pref_domestic_dns">DNS</string>
|
<string name="summary_pref_domestic_dns">DNS</string>
|
||||||
|
|
||||||
|
<string name="title_pref_delay_test_url">True delay test url (http/https)</string>
|
||||||
|
<string name="summary_pref_delay_test_url">Url</string>
|
||||||
|
|
||||||
<string name="title_pref_proxy_sharing_enabled">Allow connections from the LAN</string>
|
<string name="title_pref_proxy_sharing_enabled">Allow connections from the LAN</string>
|
||||||
<string name="summary_pref_proxy_sharing_enabled">Other devices can connect to proxy by your ip address through socks/http, Only enable in trusted network to avoid unauthorized connection</string>
|
<string name="summary_pref_proxy_sharing_enabled">Other devices can connect to proxy by your ip address through socks/http, Only enable in trusted network to avoid unauthorized connection</string>
|
||||||
<string name="toast_warning_pref_proxysharing_short">Allow connections from the LAN, Make sure you are in a trusted network</string>
|
<string name="toast_warning_pref_proxysharing_short">Allow connections from the LAN, Make sure you are in a trusted network</string>
|
||||||
@@ -190,6 +209,7 @@
|
|||||||
<string name="title_configuration_backup">Backup configuration</string>
|
<string name="title_configuration_backup">Backup configuration</string>
|
||||||
<string name="summary_configuration_backup">Storage location: [%s], The backup will be cleared after uninstalling the app or clearing the storage</string>
|
<string name="summary_configuration_backup">Storage location: [%s], The backup will be cleared after uninstalling the app or clearing the storage</string>
|
||||||
<string name="title_configuration_restore">Restore configuration</string>
|
<string name="title_configuration_restore">Restore configuration</string>
|
||||||
|
<string name="title_configuration_share">Share configuration</string>
|
||||||
|
|
||||||
<string name="title_pref_promotion">Promotion</string>
|
<string name="title_pref_promotion">Promotion</string>
|
||||||
<string name="summary_pref_promotion">Promotion,click for details(Donation can be removed)</string>
|
<string name="summary_pref_promotion">Promotion,click for details(Donation can be removed)</string>
|
||||||
|
|||||||
@@ -2,10 +2,7 @@
|
|||||||
|
|
||||||
<style name="AppThemeDayNight" parent="Theme.AppCompat.DayNight">
|
<style name="AppThemeDayNight" parent="Theme.AppCompat.DayNight">
|
||||||
<item name="colorPrimary">@color/colorPrimary</item>
|
<item name="colorPrimary">@color/colorPrimary</item>
|
||||||
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
|
|
||||||
<item name="colorAccent">@color/colorAccent</item>
|
<item name="colorAccent">@color/colorAccent</item>
|
||||||
<item name="colorMainBg">@color/colorBg</item>
|
|
||||||
<item name="colorMainText">@color/colorText</item>
|
|
||||||
<item name="android:statusBarColor">@color/colorPrimary</item>
|
<item name="android:statusBarColor">@color/colorPrimary</item>
|
||||||
<item name="android:navigationBarColor">@color/colorPrimary</item>
|
<item name="android:navigationBarColor">@color/colorPrimary</item>
|
||||||
<item name="colorControlNormal">@color/colorAccent</item>
|
<item name="colorControlNormal">@color/colorAccent</item>
|
||||||
@@ -17,12 +14,7 @@
|
|||||||
</style>
|
</style>
|
||||||
|
|
||||||
<!-- Base application theme. -->
|
<!-- Base application theme. -->
|
||||||
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
|
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar"></style>
|
||||||
<!-- Customize your theme here. -->
|
|
||||||
<!--<item name="colorPrimary">@color/colorPrimary</item>-->
|
|
||||||
<!--<item name="colorPrimaryDark">@color/colorPrimaryDark</item>-->
|
|
||||||
<!--<item name="colorAccent">@color/colorAccent</item>-->
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<style name="AppTheme.NoActionBar">
|
<style name="AppTheme.NoActionBar">
|
||||||
<item name="windowActionBar">false</item>
|
<item name="windowActionBar">false</item>
|
||||||
|
|||||||
4
V2rayNG/app/src/main/res/xml/cache_paths.xml
Normal file
4
V2rayNG/app/src/main/res/xml/cache_paths.xml
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<paths>
|
||||||
|
<cache-path name="cache" path="/"/>
|
||||||
|
</paths>
|
||||||
@@ -8,6 +8,12 @@
|
|||||||
android:summary="@string/summary_pref_sniffing_enabled"
|
android:summary="@string/summary_pref_sniffing_enabled"
|
||||||
android:title="@string/title_pref_sniffing_enabled" />
|
android:title="@string/title_pref_sniffing_enabled" />
|
||||||
|
|
||||||
|
<CheckBoxPreference
|
||||||
|
android:defaultValue="false"
|
||||||
|
android:key="pref_route_only_enabled"
|
||||||
|
android:summary="@string/summary_pref_route_only_enabled"
|
||||||
|
android:title="@string/title_pref_route_only_enabled" />
|
||||||
|
|
||||||
<PreferenceCategory android:title="@string/title_vpn_settings">
|
<PreferenceCategory android:title="@string/title_vpn_settings">
|
||||||
<CheckBoxPreference
|
<CheckBoxPreference
|
||||||
android:key="pref_per_app_proxy"
|
android:key="pref_per_app_proxy"
|
||||||
@@ -205,6 +211,11 @@
|
|||||||
android:summary="@string/summary_pref_domestic_dns"
|
android:summary="@string/summary_pref_domestic_dns"
|
||||||
android:title="@string/title_pref_domestic_dns" />
|
android:title="@string/title_pref_domestic_dns" />
|
||||||
|
|
||||||
|
<EditTextPreference
|
||||||
|
android:key="pref_delay_test_url"
|
||||||
|
android:summary="@string/summary_pref_delay_test_url"
|
||||||
|
android:title="@string/title_pref_delay_test_url" />
|
||||||
|
|
||||||
<ListPreference
|
<ListPreference
|
||||||
android:defaultValue="warning"
|
android:defaultValue="warning"
|
||||||
android:entries="@array/core_loglevel"
|
android:entries="@array/core_loglevel"
|
||||||
|
|||||||
Reference in New Issue
Block a user