Compare commits

...

30 Commits

Author SHA1 Message Date
2dust
5b9f24c1f0 up 1.9.46 2025-04-09 14:41:11 +08:00
2dust
c47c2c3666 Code clean 2025-04-09 14:35:05 +08:00
2dust
49f7c3e7d7 Added tips for per app proxy 2025-04-09 14:34:27 +08:00
2dust
423e5de2c6 Fix
https://github.com/2dust/v2rayNG/issues/4473
2025-04-09 11:55:43 +08:00
kore kas nadar
3e3387e63e Update Luri Bakhtiari translation (#4477) 2025-04-09 10:50:47 +08:00
solokot
debddace8b Update Russian translation (#4476) 2025-04-09 10:50:33 +08:00
Pk-web6936
160b412e0a Update Persian translate (#4475) 2025-04-09 10:50:23 +08:00
2dust
0f3e0a0ea2 Optimize and improve RoutingSettingActivity 2025-04-09 10:47:35 +08:00
2dust
c4cf90e807 Optimize and improve UserAssetActivity 2025-04-09 10:46:21 +08:00
2dust
5db46e81b7 Bug fix
https://github.com/2dust/v2rayNG/issues/4474
2025-04-09 10:08:56 +08:00
2dust
1ef80a3a96 Refactor AppConfig const 2025-04-08 21:05:34 +08:00
2dust
a46d9d0d2a Fix tools:context 2025-04-08 21:03:29 +08:00
2dust
7b80536e1e Added GEO files sources settings in asset settings
https://github.com/2dust/v2rayNG/issues/4440
2025-04-08 19:31:26 +08:00
2dust
5733ecf20e Add some unit test 2025-04-07 17:06:29 +08:00
ᡠᠵᡠᡳ ᡠᠵᡠ ᠮᠠᠨᡩ᠋ᠠᠨ
eae33b61cf Restoring some IP ranges removed in b4c833b (#4469) 2025-04-07 16:55:59 +08:00
2dust
9e55b525f1 Bug fix
https://github.com/2dust/v2rayNG/issues/4468
2025-04-07 16:15:06 +08:00
2dust
678b3cb505 Allow private IP to use HTTP protocol
https://github.com/2dust/v2rayNG/issues/4460
2025-04-07 16:00:56 +08:00
2dust
b4c833b039 Refactoring of private IP lists 2025-04-06 19:42:01 +08:00
2dust
597bd021b8 Bug fix 2025-04-06 14:22:47 +08:00
2dust
ba03118a43 Code clean 2025-04-06 14:22:30 +08:00
kore kas nadar
82148408b0 Improved Luri Bakhtiari Translation (#4465)
* Improved Luri Bakhtiari Translation

* Improved Luri Bakhtiari Translation
2025-04-06 14:09:49 +08:00
kore kas nadar
042900e065 Update Luri Bakhtiari translation (#4463) 2025-04-06 10:19:20 +08:00
kore kas nadar
874fccc351 Update Luri Bakhtiari translation (#4455) 2025-04-04 10:35:39 +08:00
2dust
14f36872e7 If it is the Google Play version, the update check will not be displayed within 2 days after update. 2025-04-03 17:30:29 +08:00
solokot
3b6ad3052a Update Russian translation (#4451) 2025-04-03 15:39:55 +08:00
Pk-web6936
194fc6b6ed Update Persian Translation (#4449)
* Update Persian Translation

* Update strings.xml

* Update strings.xml
2025-04-03 15:39:47 +08:00
Pk-web6936
0275ad54ac Delete Unnecessary () around expression (#4447)
* Delete Unnecessary () around expression

Delete Unnecessary () around expression

* Delete Unnecessary () around expression

Delete Unnecessary () around expression

* Delete Unnecessary () around expression

Delete Unnecessary () around expression

* Delete Unnecessary () around expression

Delete Unnecessary () around expression

* Delete Unnecessary () around expression

Delete Unnecessary () around expression

* Delete Unnecessary () around expression

Delete Unnecessary () around expression

* Delete Unnecessary () around expression

Delete Unnecessary () around expression
2025-04-03 10:20:59 +08:00
2dust
7ca4044467 Added check for updates 2025-04-01 17:24:59 +08:00
2dust
1672494ee9 1.9.45 2025-04-01 10:26:59 +08:00
2dust
bbbbc72d22 Update AndroidLibXrayLite 2025-04-01 10:26:45 +08:00
48 changed files with 818 additions and 401 deletions

View File

@@ -12,8 +12,8 @@ android {
applicationId = "com.v2ray.ang"
minSdk = 21
targetSdk = 35
versionCode = 644
versionName = "1.9.44"
versionCode = 646
versionName = "1.9.46"
multiDexEnabled = true
val abiFilterList = (properties["ABI_FILTERS"] as? String)?.split(';')

View File

@@ -39,5 +39,9 @@ class AngApplication : MultiDexApplication() {
WorkManager.initialize(this, workManagerConfiguration)
SettingsManager.initRoutingRulesets(this)
es.dmoral.toasty.Toasty.Config.getInstance()
.setGravity(android.view.Gravity.BOTTOM, 0, 200)
.apply()
}
}

View File

@@ -57,13 +57,15 @@ object AppConfig {
const val PREF_LOGLEVEL = "pref_core_loglevel"
const val PREF_MODE = "pref_mode"
const val PREF_IS_BOOTED = "pref_is_booted"
const val PREF_CHECK_UPDATE_PRE_RELEASE = "pref_check_update_pre_release"
const val PREF_GEO_FILES_SOURCES = "pref_geo_files_sources"
/** Cache keys. */
const val CACHE_SUBSCRIPTION_ID = "cache_subscription_id"
const val CACHE_KEYWORD_FILTER = "cache_keyword_filter"
/** Protocol identifiers. */
const val PROTOCOL_FREEDOM: String = "freedom"
const val PROTOCOL_FREEDOM = "freedom"
/** Broadcast actions. */
const val BROADCAST_ACTION_SERVICE = "com.v2ray.ang.action.service"
@@ -88,19 +90,19 @@ object AppConfig {
const val DOWNLINK = "downlink"
/** URLs for various resources. */
const val androidpackagenamelistUrl =
"https://raw.githubusercontent.com/2dust/androidpackagenamelist/master/proxy.txt"
const val v2rayCustomRoutingListUrl =
"https://raw.githubusercontent.com/2dust/v2rayCustomRoutingList/master/"
const val v2rayNGUrl = "https://github.com/2dust/v2rayNG"
const val v2rayNGIssues = "$v2rayNGUrl/issues"
const val v2rayNGWikiMode = "$v2rayNGUrl/wiki/Mode"
const val v2rayNGPrivacyPolicy = "https://raw.githubusercontent.com/2dust/v2rayNG/master/CR.md"
const val PromotionUrl = "aHR0cHM6Ly85LjIzNDQ1Ni54eXovYWJjLmh0bWw="
const val GeoUrl = "https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/"
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 GITHUB_URL = "https://github.com"
const val GITHUB_RAW_URL = "https://raw.githubusercontent.com"
const val GITHUB_DOWNLOAD_URL = "$GITHUB_URL/%s/releases/latest/download"
const val ANDROID_PACKAGE_NAME_LIST_URL = "$GITHUB_RAW_URL/2dust/androidpackagenamelist/master/proxy.txt"
const val APP_URL = "$GITHUB_URL/2dust/v2rayNG"
const val APP_API_URL = "https://api.github.com/repos/2dust/v2rayNG/releases"
const val APP_ISSUES_URL = "$APP_URL/issues"
const val APP_WIKI_MODE = "$APP_URL/wiki/Mode"
const val APP_PRIVACY_POLICY = "$GITHUB_RAW_URL/2dust/v2rayNG/master/CR.md"
const val APP_PROMOTION_URL = "aHR0cHM6Ly85LjIzNDQ1Ni54eXovYWJjLmh0bWw="
const val TG_CHANNEL_URL = "https://t.me/github_2dust"
const val DELAY_TEST_URL = "https://www.gstatic.com/generate_204"
const val DELAY_TEST_URL2 = "https://www.google.com/generate_204"
/** DNS server addresses. */
const val DNS_PROXY = "1.1.1.1"
@@ -170,14 +172,6 @@ object AppConfig {
const val DNS_QUAD9_DOMAIN = "dns.quad9.net"
const val DNS_YANDEX_DOMAIN = "common.dot.dns.yandex.net"
val DNS_ALIDNS_ADDRESSES = arrayListOf("223.5.5.5", "223.6.6.6", "2400:3200::1", "2400:3200:baba::1")
val DNS_CLOUDFLARE_ADDRESSES = arrayListOf("1.1.1.1", "1.0.0.1", "2606:4700:4700::1111", "2606:4700:4700::1001")
val DNS_DNSPOD_ADDRESSES = arrayListOf("1.12.12.12", "120.53.53.53")
val DNS_GOOGLE_ADDRESSES = arrayListOf("8.8.8.8", "8.8.4.4", "2001:4860:4860::8888", "2001:4860:4860::8844")
val DNS_QUAD9_ADDRESSES = arrayListOf("9.9.9.9", "149.112.112.112", "2620:fe::fe", "2620:fe::9")
val DNS_YANDEX_ADDRESSES = arrayListOf("77.88.8.8", "77.88.8.1", "2a02:6b8::feed:0ff", "2a02:6b8:0:1::feed:0ff")
const val DEFAULT_PORT = 443
const val DEFAULT_SECURITY = "auto"
const val DEFAULT_LEVEL = 8
@@ -186,4 +180,60 @@ object AppConfig {
const val REALITY = "reality"
const val HEADER_TYPE_HTTP = "http"
val DNS_ALIDNS_ADDRESSES = arrayListOf("223.5.5.5", "223.6.6.6", "2400:3200::1", "2400:3200:baba::1")
val DNS_CLOUDFLARE_ADDRESSES = arrayListOf("1.1.1.1", "1.0.0.1", "2606:4700:4700::1111", "2606:4700:4700::1001")
val DNS_DNSPOD_ADDRESSES = arrayListOf("1.12.12.12", "120.53.53.53")
val DNS_GOOGLE_ADDRESSES = arrayListOf("8.8.8.8", "8.8.4.4", "2001:4860:4860::8888", "2001:4860:4860::8844")
val DNS_QUAD9_ADDRESSES = arrayListOf("9.9.9.9", "149.112.112.112", "2620:fe::fe", "2620:fe::9")
val DNS_YANDEX_ADDRESSES = arrayListOf("77.88.8.8", "77.88.8.1", "2a02:6b8::feed:0ff", "2a02:6b8:0:1::feed:0ff")
//minimum list https://serverfault.com/a/304791
val BYPASS_PRIVATE_IP_LIST = arrayListOf(
"0.0.0.0/5",
"8.0.0.0/7",
"11.0.0.0/8",
"12.0.0.0/6",
"16.0.0.0/4",
"32.0.0.0/3",
"64.0.0.0/2",
"128.0.0.0/3",
"160.0.0.0/5",
"168.0.0.0/6",
"172.0.0.0/12",
"172.32.0.0/11",
"172.64.0.0/10",
"172.128.0.0/9",
"173.0.0.0/8",
"174.0.0.0/7",
"176.0.0.0/4",
"192.0.0.0/9",
"192.128.0.0/11",
"192.160.0.0/13",
"192.169.0.0/16",
"192.170.0.0/15",
"192.172.0.0/14",
"192.176.0.0/12",
"192.192.0.0/10",
"193.0.0.0/8",
"194.0.0.0/7",
"196.0.0.0/6",
"200.0.0.0/5",
"208.0.0.0/4",
"240.0.0.0/4"
)
val PRIVATE_IP_LIST = arrayListOf(
"10.0.0.0/8",
"172.16.0.0/12",
"192.168.0.0/16",
"169.254.0.0/16",
"224.0.0.0/4"
)
val GEO_FILES_SOURCES = arrayListOf(
"Loyalsoldier/v2ray-rules-dat",
"runetfreedom/russia-v2ray-rules-dat",
"Chocolate4U/Iran-v2ray-rules"
)
}

View File

@@ -4,5 +4,6 @@ data class AssetUrlItem(
var remarks: String = "",
var url: String = "",
val addedTime: Long = System.currentTimeMillis(),
var lastUpdated: Long = -1
var lastUpdated: Long = -1,
var locked: Boolean? = false,
)

View File

@@ -0,0 +1,10 @@
package com.v2ray.ang.dto
data class CheckUpdateResult(
val hasUpdate: Boolean,
val latestVersion: String? = null,
val releaseNotes: String? = null,
val downloadUrl: String? = null,
val error: String? = null,
val isPreRelease: Boolean = false
)

View File

@@ -0,0 +1,23 @@
package com.v2ray.ang.dto
import com.google.gson.annotations.SerializedName
data class GitHubRelease(
@SerializedName("tag_name")
val tagName: String,
@SerializedName("body")
val body: String,
@SerializedName("assets")
val assets: List<Asset>,
@SerializedName("prerelease")
val prerelease: Boolean = false,
@SerializedName("published_at")
val publishedAt: String = ""
) {
data class Asset(
@SerializedName("name")
val name: String,
@SerializedName("browser_download_url")
val browserDownloadUrl: String
)
}

View File

@@ -196,4 +196,17 @@ inline fun <reified T : Serializable> Intent.serializable(key: String): T? = whe
*
* @return True if the CharSequence is not null and not empty, false otherwise.
*/
fun CharSequence?.isNotNullEmpty(): Boolean = (this != null && this.isNotEmpty())
fun CharSequence?.isNotNullEmpty(): Boolean = this != null && this.isNotEmpty()
fun String.concatUrl(vararg paths: String): String {
val builder = StringBuilder(this.trimEnd('/'))
paths.forEach { path ->
val trimmedPath = path.trim('/')
if (trimmedPath.isNotEmpty()) {
builder.append('/').append(trimmedPath)
}
}
return builder.toString()
}

View File

@@ -18,9 +18,9 @@ open class FmtBase {
*/
fun toUri(config: ProfileItem, userInfo: String?, dicQuery: HashMap<String, String>?): String {
val query = if (dicQuery != null)
("?" + dicQuery.toList().joinToString(
"?" + dicQuery.toList().joinToString(
separator = "&",
transform = { it.first + "=" + Utils.urlEncode(it.second) }))
transform = { it.first + "=" + Utils.urlEncode(it.second) })
else ""
val url = String.format(
@@ -148,4 +148,4 @@ open class FmtBase {
return dicQuery
}
}
}

View File

@@ -29,11 +29,11 @@ object WireguardFmt : FmtBase() {
config.serverPort = uri.port.toString()
config.secretKey = uri.userInfo.orEmpty()
config.localAddress = (queryParam["address"] ?: WIREGUARD_LOCAL_ADDRESS_V4)
config.localAddress = queryParam["address"] ?: WIREGUARD_LOCAL_ADDRESS_V4
config.publicKey = queryParam["publickey"].orEmpty()
config.preSharedKey = queryParam["presharedkey"].orEmpty()
config.mtu = Utils.parseInt(queryParam["mtu"] ?: AppConfig.WIREGUARD_LOCAL_MTU)
config.reserved = (queryParam["reserved"] ?: "0,0,0")
config.reserved = queryParam["reserved"] ?: "0,0,0"
return config
}

View File

@@ -7,7 +7,9 @@ import android.util.Log
import com.v2ray.ang.AppConfig
import com.v2ray.ang.AppConfig.HY2
import com.v2ray.ang.R
import com.v2ray.ang.dto.*
import com.v2ray.ang.dto.EConfigType
import com.v2ray.ang.dto.ProfileItem
import com.v2ray.ang.dto.SubscriptionItem
import com.v2ray.ang.fmt.CustomFmt
import com.v2ray.ang.fmt.Hysteria2Fmt
import com.v2ray.ang.fmt.ShadowsocksFmt
@@ -417,6 +419,9 @@ object AngConfigManager {
if (!Utils.isValidUrl(url)) {
return 0
}
if (!Utils.isValidSubUrl(url)) {
return 0
}
Log.i(AppConfig.TAG, url)
var configText = try {
@@ -430,7 +435,7 @@ object AngConfigManager {
configText = try {
HttpUtil.getUrlContentWithUserAgent(url)
} catch (e: Exception) {
Log.e(AppConfig.TAG, "Failed to get URL content with user agent", e)
Log.e(AppConfig.TAG, "Update subscription: Failed to get URL content with user agent", e)
""
}
}

View File

@@ -242,7 +242,7 @@ object SettingsManager {
* @return The HTTP port.
*/
fun getHttpPort(): Int {
return getSocksPort() + (if (Utils.isXray()) 0 else 1)
return getSocksPort() + if (Utils.isXray()) 0 else 1
}
/**
@@ -316,10 +316,10 @@ object SettingsManager {
*/
fun getDelayTestUrl(second: Boolean = false): String {
return if (second) {
AppConfig.DelayTestUrl2
AppConfig.DELAY_TEST_URL2
} else {
MmkvManager.decodeSettingsString(AppConfig.PREF_DELAY_TEST_URL)
?: AppConfig.DelayTestUrl
?: AppConfig.DELAY_TEST_URL
}
}

View File

@@ -0,0 +1,112 @@
package com.v2ray.ang.handler
import android.content.Context
import android.os.Build
import android.util.Log
import com.v2ray.ang.AppConfig
import com.v2ray.ang.BuildConfig
import com.v2ray.ang.dto.CheckUpdateResult
import com.v2ray.ang.dto.GitHubRelease
import com.v2ray.ang.extension.concatUrl
import com.v2ray.ang.util.HttpUtil
import com.v2ray.ang.util.JsonUtil
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import java.io.File
import java.io.FileOutputStream
object UpdateCheckerManager {
suspend fun checkForUpdate(includePreRelease: Boolean = false): CheckUpdateResult = withContext(Dispatchers.IO) {
try {
val url = if (includePreRelease) {
AppConfig.APP_API_URL
} else {
AppConfig.APP_API_URL.concatUrl("latest")
}
var response = HttpUtil.getUrlContent(url, 5000)
if (response.isNullOrEmpty()) {
val httpPort = SettingsManager.getHttpPort()
response = HttpUtil.getUrlContent(url, 5000, httpPort) ?: throw IllegalStateException("Failed to get response")
}
val latestRelease = if (includePreRelease) {
JsonUtil.fromJson(response, Array<GitHubRelease>::class.java)
.firstOrNull()
?: throw IllegalStateException("No pre-release found")
} else {
JsonUtil.fromJson(response, GitHubRelease::class.java)
}
val latestVersion = latestRelease.tagName.removePrefix("v")
Log.i(AppConfig.TAG, "Found new version: $latestVersion (current: ${BuildConfig.VERSION_NAME})")
return@withContext if (compareVersions(latestVersion, BuildConfig.VERSION_NAME) > 0) {
val downloadUrl = getDownloadUrl(latestRelease, Build.SUPPORTED_ABIS[0])
CheckUpdateResult(
hasUpdate = true,
latestVersion = latestVersion,
releaseNotes = latestRelease.body,
downloadUrl = downloadUrl,
isPreRelease = latestRelease.prerelease
)
} else {
CheckUpdateResult(hasUpdate = false)
}
} catch (e: Exception) {
Log.e(AppConfig.TAG, "Failed to check for updates: ${e.message}")
return@withContext CheckUpdateResult(hasUpdate = false, error = e.message)
}
}
suspend fun downloadApk(context: Context, downloadUrl: String): File? = withContext(Dispatchers.IO) {
try {
val httpPort = SettingsManager.getHttpPort()
val connection = HttpUtil.createProxyConnection(downloadUrl, httpPort, 10000, 10000, true)
?: throw IllegalStateException("Failed to create connection")
try {
val apkFile = File(context.cacheDir, "update.apk")
Log.i(AppConfig.TAG, "Downloading APK to: ${apkFile.absolutePath}")
FileOutputStream(apkFile).use { outputStream ->
connection.inputStream.use { inputStream ->
inputStream.copyTo(outputStream)
}
}
Log.i(AppConfig.TAG, "APK download completed")
return@withContext apkFile
} catch (e: Exception) {
Log.e(AppConfig.TAG, "Failed to download APK: ${e.message}")
return@withContext null
} finally {
try {
connection.disconnect()
} catch (e: Exception) {
Log.e(AppConfig.TAG, "Error closing connection: ${e.message}")
}
}
} catch (e: Exception) {
Log.e(AppConfig.TAG, "Failed to initiate download: ${e.message}")
return@withContext null
}
}
private fun compareVersions(version1: String, version2: String): Int {
val v1 = version1.split(".")
val v2 = version2.split(".")
for (i in 0 until maxOf(v1.size, v2.size)) {
val num1 = if (i < v1.size) v1[i].toInt() else 0
val num2 = if (i < v2.size) v2[i].toInt() else 0
if (num1 != num2) return num1 - num2
}
return 0
}
private fun getDownloadUrl(release: GitHubRelease, abi: String): String {
return release.assets.find { it.name.contains(abi) }?.browserDownloadUrl
?: release.assets.firstOrNull()?.browserDownloadUrl
?: throw IllegalStateException("No compatible APK found")
}
}

View File

@@ -107,7 +107,7 @@ class SimpleItemTouchHelperCallback(private val mAdapter: ItemTouchHelperAdapter
addUpdateListener { animation ->
val value = animation.animatedValue as Float
viewHolder.itemView.translationX = value
viewHolder.itemView.alpha = (1f - abs(value) / (viewHolder.itemView.width * SWIPE_THRESHOLD))
viewHolder.itemView.alpha = 1f - abs(value) / (viewHolder.itemView.width * SWIPE_THRESHOLD)
}
interpolator = DecelerateInterpolator()
duration = ANIMATION_DURATION
@@ -144,4 +144,4 @@ class SimpleItemTouchHelperCallback(private val mAdapter: ItemTouchHelperAdapter
private const val SWIPE_THRESHOLD = 0.25f
private const val ANIMATION_DURATION: Long = 200
}
}
}

View File

@@ -20,7 +20,6 @@ import androidx.annotation.RequiresApi
import com.v2ray.ang.AppConfig
import com.v2ray.ang.AppConfig.LOOPBACK
import com.v2ray.ang.BuildConfig
import com.v2ray.ang.R
import com.v2ray.ang.handler.MmkvManager
import com.v2ray.ang.handler.SettingsManager
import com.v2ray.ang.util.MyContextWrapper
@@ -166,7 +165,7 @@ class V2RayVpnService : VpnService(), ServiceControl {
//builder.addDnsServer(PRIVATE_VLAN4_ROUTER)
val bypassLan = SettingsManager.routingRulesetsBypassLan()
if (bypassLan) {
resources.getStringArray(R.array.bypass_private_ip_address).forEach {
AppConfig.BYPASS_PRIVATE_IP_LIST.forEach {
val addr = it.split('/')
builder.addRoute(addr[0], addr[1].toInt())
}

View File

@@ -5,20 +5,28 @@ import android.content.Intent
import android.os.Build
import android.os.Bundle
import android.util.Log
import android.view.View
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AlertDialog
import androidx.core.content.ContextCompat
import androidx.core.content.FileProvider
import androidx.lifecycle.lifecycleScope
import com.tencent.mmkv.MMKV
import com.v2ray.ang.AppConfig
import com.v2ray.ang.BuildConfig
import com.v2ray.ang.R
import com.v2ray.ang.databinding.ActivityAboutBinding
import com.v2ray.ang.dto.CheckUpdateResult
import com.v2ray.ang.extension.toast
import com.v2ray.ang.extension.toastError
import com.v2ray.ang.extension.toastSuccess
import com.v2ray.ang.handler.MmkvManager
import com.v2ray.ang.handler.SpeedtestManager
import com.v2ray.ang.handler.UpdateCheckerManager
import com.v2ray.ang.util.AppManagerUtil
import com.v2ray.ang.util.Utils
import com.v2ray.ang.util.ZipUtil
import kotlinx.coroutines.launch
import java.io.File
import java.text.SimpleDateFormat
import java.util.Locale
@@ -97,12 +105,29 @@ class AboutActivity : BaseActivity() {
}
}
//If it is the Google Play version, not be displayed within 1 days after update
if (Utils.isGoogleFlavor()) {
val lastUpdateTime = AppManagerUtil.getLastUpdateTime(this)
val currentTime = System.currentTimeMillis()
if ((currentTime - lastUpdateTime) < 1 * 24 * 60 * 60 * 1000L) {
binding.layoutCheckUpdate.visibility = View.GONE
}
}
binding.layoutCheckUpdate.setOnClickListener {
checkForUpdates(binding.checkPreRelease.isChecked)
}
binding.checkPreRelease.setOnCheckedChangeListener { _, isChecked ->
MmkvManager.encodeSettings(AppConfig.PREF_CHECK_UPDATE_PRE_RELEASE, isChecked)
}
binding.checkPreRelease.isChecked = MmkvManager.decodeSettingsBool(AppConfig.PREF_CHECK_UPDATE_PRE_RELEASE, false)
binding.layoutSoureCcode.setOnClickListener {
Utils.openUri(this, AppConfig.v2rayNGUrl)
Utils.openUri(this, AppConfig.APP_URL)
}
binding.layoutFeedback.setOnClickListener {
Utils.openUri(this, AppConfig.v2rayNGIssues)
Utils.openUri(this, AppConfig.APP_ISSUES_URL)
}
binding.layoutOssLicenses.setOnClickListener {
@@ -116,11 +141,11 @@ class AboutActivity : BaseActivity() {
}
binding.layoutTgChannel.setOnClickListener {
Utils.openUri(this, AppConfig.TgChannelUrl)
Utils.openUri(this, AppConfig.TG_CHANNEL_URL)
}
binding.layoutPrivacyPolicy.setOnClickListener {
Utils.openUri(this, AppConfig.v2rayNGPrivacyPolicy)
Utils.openUri(this, AppConfig.APP_PRIVACY_POLICY)
}
"v${BuildConfig.VERSION_NAME} (${SpeedtestManager.getLibVersion()})".also {
@@ -197,4 +222,28 @@ class AboutActivity : BaseActivity() {
}
}
}
private fun checkForUpdates(includePreRelease: Boolean) {
lifecycleScope.launch {
val result = UpdateCheckerManager.checkForUpdate(includePreRelease)
if (result.hasUpdate) {
showUpdateDialog(result)
} else {
toast(R.string.update_already_latest_version)
}
}
}
private fun showUpdateDialog(result: CheckUpdateResult) {
AlertDialog.Builder(this)
.setTitle(getString(R.string.update_new_version_found, result.latestVersion))
.setMessage(result.releaseNotes)
.setPositiveButton(R.string.update_now) { _, _ ->
result.downloadUrl?.let {
Utils.openUri(this, it)
}
}
.setNegativeButton(android.R.string.cancel, null)
.show()
}
}

View File

@@ -87,9 +87,6 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
Action.IMPORT_QR_CODE_CONFIG ->
scanQRCodeForConfig.launch(Intent(this, ScannerActivity::class.java))
// Action.IMPORT_QR_CODE_URL ->
// scanQRCodeForUrlToCustomConfig.launch(Intent(this, ScannerActivity::class.java))
Action.READ_CONTENT_FROM_URI ->
chooseFileForCustomConfig.launch(Intent.createChooser(Intent(Intent.ACTION_GET_CONTENT).apply {
type = "*/*"
@@ -110,8 +107,6 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
enum class Action {
NONE,
IMPORT_QR_CODE_CONFIG,
//IMPORT_QR_CODE_URL,
READ_CONTENT_FROM_URI,
POST_NOTIFICATIONS
}
@@ -129,12 +124,6 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
}
}
// private val scanQRCodeForUrlToCustomConfig = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
// if (it.resultCode == RESULT_OK) {
// importConfigCustomUrl(it.data?.getStringExtra("SCAN_RESULT"))
// }
// }
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(binding.root)
@@ -325,7 +314,7 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
override fun onOptionsItemSelected(item: MenuItem) = when (item.itemId) {
R.id.import_qrcode -> {
importQRcode(true)
importQRcode()
true
}
@@ -379,26 +368,6 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
true
}
// R.id.import_config_custom_clipboard -> {
// importConfigCustomClipboard()
// true
// }
//
// R.id.import_config_custom_local -> {
// importConfigCustomLocal()
// true
// }
//
// R.id.import_config_custom_url -> {
// importConfigCustomUrlClipboard()
// true
// }
//
// R.id.import_config_custom_url_scan -> {
// importQRcode(false)
// true
// }
R.id.export_all -> {
exportAll()
true
@@ -462,16 +431,12 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
/**
* import config from qrcode
*/
private fun importQRcode(forConfig: Boolean): Boolean {
private fun importQRcode(): Boolean {
val permission = Manifest.permission.CAMERA
if (ContextCompat.checkSelfPermission(this, permission) == PackageManager.PERMISSION_GRANTED) {
if (forConfig) {
scanQRCodeForConfig.launch(Intent(this, ScannerActivity::class.java))
} else {
//scanQRCodeForUrlToCustomConfig.launch(Intent(this, ScannerActivity::class.java))
}
scanQRCodeForConfig.launch(Intent(this, ScannerActivity::class.java))
} else {
pendingAction = Action.IMPORT_QR_CODE_CONFIG//if (forConfig) Action.IMPORT_QR_CODE_CONFIG else Action.IMPORT_QR_CODE_URL
pendingAction = Action.IMPORT_QR_CODE_CONFIG
requestPermissionLauncher.launch(permission)
}
return true
@@ -535,77 +500,6 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
}
// private fun importConfigCustomClipboard()
// : Boolean {
// try {
// val configText = Utils.getClipboard(this)
// if (TextUtils.isEmpty(configText)) {
// toast(R.string.toast_none_data_clipboard)
// return false
// }
// importCustomizeConfig(configText)
// return true
// } catch (e: Exception) {
// e.printStackTrace()
// return false
// }
// }
/**
* import config from local config file
*/
// private fun importConfigCustomLocal(): Boolean {
// try {
// showFileChooser()
// } catch (e: Exception) {
// e.printStackTrace()
// return false
// }
// return true
// }
//
// private fun importConfigCustomUrlClipboard()
// : Boolean {
// try {
// val url = Utils.getClipboard(this)
// if (TextUtils.isEmpty(url)) {
// toast(R.string.toast_none_data_clipboard)
// return false
// }
// return importConfigCustomUrl(url)
// } catch (e: Exception) {
// e.printStackTrace()
// return false
// }
// }
/**
* import config from url
*/
// private fun importConfigCustomUrl(url: String?): Boolean {
// try {
// if (!Utils.isValidUrl(url)) {
// toast(R.string.toast_invalid_url)
// return false
// }
// lifecycleScope.launch(Dispatchers.IO) {
// val configText = try {
// HttpUtil.getUrlContentWithUserAgent(url)
// } catch (e: Exception) {
// e.printStackTrace()
// ""
// }
// launch(Dispatchers.Main) {
// importCustomizeConfig(configText)
// }
// }
// } catch (e: Exception) {
// e.printStackTrace()
// return false
// }
// return true
// }
/**
* import config from sub
*/
@@ -755,29 +649,6 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
}
}
// /**
// * import customize config
// */
// private fun importCustomizeConfig(server: String?) {
// try {
// if (server == null || TextUtils.isEmpty(server)) {
// toast(R.string.toast_none_data)
// return
// }
// if (mainViewModel.appendCustomConfigServer(server)) {
// mainViewModel.reloadServerList()
// toastSuccess(R.string.toast_success)
// } else {
// toastError(R.string.toast_failure)
// }
// //adapter.notifyItemInserted(mainViewModel.serverList.lastIndex)
// } catch (e: Exception) {
// ToastCompat.makeText(this, "${getString(R.string.toast_malformed_josn)} ${e.cause?.message}", Toast.LENGTH_LONG).show()
// e.printStackTrace()
// return
// }
// }
private fun setTestState(content: String?) {
binding.tvTestState.text = content
}
@@ -812,7 +683,7 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
.putExtra("isRunning", mainViewModel.isRunning.value == true)
)
R.id.promotion -> Utils.openUri(this, "${Utils.decode(AppConfig.PromotionUrl)}?t=${System.currentTimeMillis()}")
R.id.promotion -> Utils.openUri(this, "${Utils.decode(AppConfig.APP_PROMOTION_URL)}?t=${System.currentTimeMillis()}")
R.id.logcat -> startActivity(Intent(this, LogcatActivity::class.java))
R.id.about -> startActivity(Intent(this, AboutActivity::class.java))
}

View File

@@ -6,6 +6,7 @@ import android.text.TextUtils
import android.util.Log
import android.view.Menu
import android.view.MenuItem
import android.widget.Toast
import androidx.appcompat.widget.SearchView
import androidx.lifecycle.lifecycleScope
import com.v2ray.ang.AppConfig
@@ -21,16 +22,14 @@ import com.v2ray.ang.handler.SettingsManager
import com.v2ray.ang.util.AppManagerUtil
import com.v2ray.ang.util.HttpUtil
import com.v2ray.ang.util.Utils
import es.dmoral.toasty.Toasty
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.text.Collator
class PerAppProxyActivity : BaseActivity() {
private val binding by lazy {
ActivityBypassListBinding.inflate(layoutInflater)
}
private val binding by lazy { ActivityBypassListBinding.inflate(layoutInflater) }
private var adapter: PerAppProxyAdapter? = null
private var appsAll: List<AppInfo>? = null
@@ -86,6 +85,10 @@ class PerAppProxyActivity : BaseActivity() {
MmkvManager.encodeSettings(AppConfig.PREF_BYPASS_APPS, isChecked)
}
binding.switchBypassApps.isChecked = MmkvManager.decodeSettingsBool(AppConfig.PREF_BYPASS_APPS, false)
binding.layoutSwitchBypassAppsTips.setOnClickListener {
Toasty.info(this, R.string.summary_pref_per_app_proxy, Toast.LENGTH_LONG, true).show()
}
}
override fun onPause() {
@@ -157,7 +160,7 @@ class PerAppProxyActivity : BaseActivity() {
toast(R.string.msg_downloading_content)
binding.pbWaiting.show()
val url = AppConfig.androidpackagenamelistUrl
val url = AppConfig.ANDROID_PACKAGE_NAME_LIST_URL
lifecycleScope.launch(Dispatchers.IO) {
var content = HttpUtil.getUrlContent(url, 5000)
if (content.isNullOrEmpty()) {

View File

@@ -7,8 +7,6 @@ import android.os.Bundle
import android.util.Log
import android.view.Menu
import android.view.MenuItem
import android.view.View
import android.widget.AdapterView
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AlertDialog
import androidx.lifecycle.lifecycleScope
@@ -67,15 +65,9 @@ class RoutingSettingActivity : BaseActivity() {
mItemTouchHelper = ItemTouchHelper(SimpleItemTouchHelperCallback(adapter))
mItemTouchHelper?.attachToRecyclerView(binding.recyclerView)
val found = Utils.arrayFind(routing_domain_strategy, MmkvManager.decodeSettingsString(AppConfig.PREF_ROUTING_DOMAIN_STRATEGY) ?: "")
found.let { binding.spDomainStrategy.setSelection(if (it >= 0) it else 0) }
binding.spDomainStrategy.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
override fun onNothingSelected(parent: AdapterView<*>?) {
}
override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
MmkvManager.encodeSettings(AppConfig.PREF_ROUTING_DOMAIN_STRATEGY, routing_domain_strategy[position])
}
binding.tvDomainStrategySummary.text = getDomainStrategy()
binding.layoutDomainStrategy.setOnClickListener {
setDomainStrategy()
}
}
@@ -98,6 +90,22 @@ class RoutingSettingActivity : BaseActivity() {
else -> super.onOptionsItemSelected(item)
}
private fun getDomainStrategy(): String {
return MmkvManager.decodeSettingsString(AppConfig.PREF_ROUTING_DOMAIN_STRATEGY) ?: routing_domain_strategy.first()
}
private fun setDomainStrategy() {
android.app.AlertDialog.Builder(this).setItems(routing_domain_strategy.asList().toTypedArray()) { _, i ->
try {
val value = routing_domain_strategy[i]
MmkvManager.encodeSettings(AppConfig.PREF_ROUTING_DOMAIN_STRATEGY, value)
binding.tvDomainStrategySummary.text = value
} catch (e: Exception) {
Log.e(AppConfig.TAG, "Failed to set domain strategy", e)
}
}.show()
}
private fun importPredefined() {
AlertDialog.Builder(this).setItems(preset_rulesets.asList().toTypedArray()) { _, i ->
AlertDialog.Builder(this).setMessage(R.string.routing_settings_import_rulesets_tip)

View File

@@ -2,7 +2,6 @@ package com.v2ray.ang.ui
import android.os.Bundle
import android.text.TextUtils
import android.util.Log
import android.view.Menu
import android.view.MenuItem
import android.view.View

View File

@@ -161,7 +161,7 @@ class SettingsActivity : BaseActivity() {
}
delayTestUrl?.setOnPreferenceChangeListener { _, any ->
val nval = any as String
delayTestUrl?.summary = if (nval == "") AppConfig.DelayTestUrl else nval
delayTestUrl?.summary = if (nval == "") AppConfig.DELAY_TEST_URL else nval
true
}
mode?.setOnPreferenceChangeListener { _, newValue ->
@@ -202,7 +202,7 @@ class SettingsActivity : BaseActivity() {
remoteDns?.summary = MmkvManager.decodeSettingsString(AppConfig.PREF_REMOTE_DNS, AppConfig.DNS_PROXY)
domesticDns?.summary = MmkvManager.decodeSettingsString(AppConfig.PREF_DOMESTIC_DNS, AppConfig.DNS_DIRECT)
dnsHosts?.summary = MmkvManager.decodeSettingsString(AppConfig.PREF_DNS_HOSTS)
delayTestUrl?.summary = MmkvManager.decodeSettingsString(AppConfig.PREF_DELAY_TEST_URL, AppConfig.DelayTestUrl)
delayTestUrl?.summary = MmkvManager.decodeSettingsString(AppConfig.PREF_DELAY_TEST_URL, AppConfig.DELAY_TEST_URL)
initSharedPreference()
}
@@ -364,6 +364,6 @@ class SettingsActivity : BaseActivity() {
}
fun onModeHelpClicked(view: View) {
Utils.openUri(this, AppConfig.v2rayNGWikiMode)
Utils.openUri(this, AppConfig.APP_WIKI_MODE)
}
}

View File

@@ -90,7 +90,7 @@ class SubEditActivity : BaseActivity() {
if (!Utils.isValidSubUrl(subItem.url)) {
toast(R.string.toast_insecure_url_protocol)
//return false
return false
}
}

View File

@@ -21,9 +21,10 @@ import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.v2ray.ang.AppConfig
import com.v2ray.ang.R
import com.v2ray.ang.databinding.ActivitySubSettingBinding
import com.v2ray.ang.databinding.ActivityUserAssetBinding
import com.v2ray.ang.databinding.ItemRecyclerUserAssetBinding
import com.v2ray.ang.dto.AssetUrlItem
import com.v2ray.ang.extension.concatUrl
import com.v2ray.ang.extension.toTrafficString
import com.v2ray.ang.extension.toast
import com.v2ray.ang.extension.toastError
@@ -42,8 +43,7 @@ import java.text.DateFormat
import java.util.Date
class UserAssetActivity : BaseActivity() {
private val binding by lazy { ActivitySubSettingBinding.inflate(layoutInflater) }
private val binding by lazy { ActivityUserAssetBinding.inflate(layoutInflater) }
val extDir by lazy { File(Utils.userAssetPath(this)) }
val builtInGeoFiles = arrayOf("geosite.dat", "geoip.dat")
@@ -90,6 +90,11 @@ class UserAssetActivity : BaseActivity() {
binding.recyclerView.layoutManager = LinearLayoutManager(this)
addCustomDividerToRecyclerView(binding.recyclerView, this, R.drawable.custom_divider)
binding.recyclerView.adapter = UserAssetAdapter()
binding.tvGeoFilesSourcesSummary.text = getGeoFilesSources()
binding.layoutGeoFilesSources.setOnClickListener {
setGeoFilesSources()
}
}
override fun onResume() {
@@ -111,6 +116,22 @@ class UserAssetActivity : BaseActivity() {
else -> super.onOptionsItemSelected(item)
}
private fun getGeoFilesSources(): String {
return MmkvManager.decodeSettingsString(AppConfig.PREF_GEO_FILES_SOURCES) ?: AppConfig.GEO_FILES_SOURCES.first()
}
private fun setGeoFilesSources() {
AlertDialog.Builder(this).setItems(AppConfig.GEO_FILES_SOURCES.toTypedArray()) { _, i ->
try {
val value = AppConfig.GEO_FILES_SOURCES[i]
MmkvManager.encodeSettings(AppConfig.PREF_GEO_FILES_SOURCES, value)
binding.tvGeoFilesSourcesSummary.text = value
} catch (e: Exception) {
Log.e(AppConfig.TAG, "Failed to set geo files sources", e)
}
}.show()
}
private fun showFileChooser() {
val permission = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
Manifest.permission.READ_MEDIA_IMAGES
@@ -264,7 +285,8 @@ class UserAssetActivity : BaseActivity() {
list.add(
Utils.getUuid() to AssetUrlItem(
it,
AppConfig.GeoUrl + it
String.format(AppConfig.GITHUB_DOWNLOAD_URL, getGeoFilesSources()).concatUrl(it),
locked = true
)
)
}
@@ -315,7 +337,7 @@ class UserAssetActivity : BaseActivity() {
holder.itemUserAssetBinding.assetProperties.text = getString(R.string.msg_file_not_found)
}
if (item.second.remarks in builtInGeoFiles && item.second.url == AppConfig.GeoUrl + item.second.remarks) {
if (item.second.locked == true) {
holder.itemUserAssetBinding.layoutEdit.visibility = GONE
//holder.itemUserAssetBinding.layoutRemove.visibility = GONE
} else {

View File

@@ -25,7 +25,7 @@ object AppManagerUtil {
val appName = applicationInfo.loadLabel(packageManager).toString()
val appIcon = applicationInfo.loadIcon(packageManager) ?: continue
val isSystemApp = (applicationInfo.flags and ApplicationInfo.FLAG_SYSTEM) > 0
val isSystemApp = applicationInfo.flags and ApplicationInfo.FLAG_SYSTEM > 0
val appInfo = AppInfo(appName, pkg.packageName, appIcon, isSystemApp, 0)
apps.add(appInfo)
@@ -33,4 +33,8 @@ object AppManagerUtil {
return@withContext apps
}
}
fun getLastUpdateTime(context: Context): Long =
context.packageManager.getPackageInfo(context.packageName, 0).lastUpdateTime
}

View File

@@ -7,8 +7,11 @@ import com.v2ray.ang.BuildConfig
import com.v2ray.ang.util.Utils.encode
import com.v2ray.ang.util.Utils.urlDecode
import java.io.IOException
import java.net.*
import java.util.*
import java.net.HttpURLConnection
import java.net.IDN
import java.net.InetSocketAddress
import java.net.Proxy
import java.net.URL
object HttpUtil {
@@ -20,7 +23,7 @@ object HttpUtil {
* @return The ASCII representation of the URL.
*/
fun idnToASCII(str: String): String {
val url = URI(str)
val url = URL(str)
val host = url.host
val asciiHost = IDN.toASCII(url.host, IDN.ALLOW_UNASSIGNED)
if (host != asciiHost) {

View File

@@ -17,10 +17,12 @@ import android.webkit.URLUtil
import androidx.core.content.ContextCompat
import androidx.core.net.toUri
import com.v2ray.ang.AppConfig
import com.v2ray.ang.AppConfig.ANG_PACKAGE
import com.v2ray.ang.AppConfig.LOOPBACK
import com.v2ray.ang.BuildConfig
import java.io.IOException
import java.net.InetAddress
import java.net.ServerSocket
import java.net.URI
import java.net.URLDecoder
import java.net.URLEncoder
import java.util.Locale
@@ -461,13 +463,23 @@ object Utils {
fun isValidSubUrl(value: String?): Boolean {
if (value.isNullOrEmpty()) return false
return try {
URLUtil.isHttpsUrl(value) ||
(URLUtil.isHttpUrl(value) && value.contains(LOOPBACK))
try {
if (URLUtil.isHttpsUrl(value)) return true
if (URLUtil.isHttpUrl(value)) {
if (value.contains(LOOPBACK)) return true
//Check private ip address
val uri = URI(fixIllegalUrl(value))
if (isIpAddress(uri.host)) {
AppConfig.PRIVATE_IP_LIST.forEach {
if (isIpInCidr(uri.host, it)) return true
}
}
}
} catch (e: Exception) {
Log.e(AppConfig.TAG, "Failed to validate subscription URL", e)
false
}
return false
}
/**
@@ -486,7 +498,58 @@ object Utils {
*
* @return True if the package is Xray, false otherwise.
*/
fun isXray(): Boolean = ANG_PACKAGE.startsWith("com.v2ray.ang")
fun isXray(): Boolean = BuildConfig.APPLICATION_ID.startsWith("com.v2ray.ang")
/**
* Check if it is the Google Play version.
*
* @return True if the package is Google Play, false otherwise.
*/
fun isGoogleFlavor(): Boolean = BuildConfig.FLAVOR == "playstore"
/**
* Converts an InetAddress to its long representation
*
* @param ip The InetAddress to convert
* @return The long representation of the IP address
*/
private fun inetAddressToLong(ip: InetAddress): Long {
val bytes = ip.address
var result: Long = 0
for (i in bytes.indices) {
result = result shl 8 or (bytes[i].toInt() and 0xff).toLong()
}
return result
}
/**
* Check if an IP address is within a CIDR range
*
* @param ip The IP address to check
* @param cidr The CIDR notation range (e.g., "192.168.1.0/24")
* @return True if the IP is within the CIDR range, false otherwise
*/
fun isIpInCidr(ip: String, cidr: String): Boolean {
try {
if (!isIpAddress(ip)) return false
// Parse CIDR (e.g., "192.168.1.0/24")
val (cidrIp, prefixLen) = cidr.split("/")
val prefixLength = prefixLen.toInt()
// Convert IP and CIDR's IP portion to Long
val ipLong = inetAddressToLong(InetAddress.getByName(ip))
val cidrIpLong = inetAddressToLong(InetAddress.getByName(cidrIp))
// Calculate subnet mask (e.g., /24 → 0xFFFFFF00)
val mask = if (prefixLength == 0) 0L else (-1L shl (32 - prefixLength))
// Check if they're in the same subnet
return (ipLong and mask) == (cidrIpLong and mask)
} catch (e: Exception) {
Log.e(AppConfig.TAG, "Failed to check if IP is in CIDR", e)
return false
}
}
}

View File

@@ -0,0 +1,11 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FFFFFFFF"
android:pathData="M21,10.12h-6.78l2.74,-2.82c-2.73,-2.7 -7.15,-2.8 -9.88,-0.1c-2.73,2.71 -2.73,7.08 0,9.79s7.15,2.71 9.88,0C18.32,15.65 19,14.08 19,12.1h2c0,1.98 -0.88,4.55 -2.64,6.29c-3.51,3.48 -9.21,3.48 -12.72,0c-3.5,-3.47 -3.53,-9.11 -0.02,-12.58s9.14,-3.47 12.65,0L21,3V10.12zM12.5,8v4.25l3.5,2.08l-0.72,1.21L11,13V8H12.5z" />
</vector>

View File

@@ -0,0 +1,11 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FF000000"
android:pathData="M21,10.12h-6.78l2.74,-2.82c-2.73,-2.7 -7.15,-2.8 -9.88,-0.1c-2.73,2.71 -2.73,7.08 0,9.79s7.15,2.71 9.88,0C18.32,15.65 19,14.08 19,12.1h2c0,1.98 -0.88,4.55 -2.64,6.29c-3.51,3.48 -9.21,3.48 -12.72,0c-3.5,-3.47 -3.53,-9.11 -0.02,-12.58s9.14,-3.47 12.65,0L21,3V10.12zM12.5,8v4.25l3.5,2.08l-0.72,1.21L11,13V8H12.5z" />
</vector>

View File

@@ -111,6 +111,49 @@
android:orientation="vertical"
android:paddingTop="@dimen/padding_spacing_dp16">
<LinearLayout
android:id="@+id/layout_check_update"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/selectableItemBackground"
android:clickable="true"
android:focusable="true"
android:gravity="center|start"
android:orientation="horizontal"
android:padding="@dimen/padding_spacing_dp16">
<ImageView
android:layout_width="@dimen/image_size_dp24"
android:layout_height="@dimen/image_size_dp24"
app:srcCompat="@drawable/ic_check_update_24dp" />
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingStart="@dimen/padding_spacing_dp16">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/update_check_for_update"
android:textAppearance="@style/TextAppearance.AppCompat.Subhead" />
<androidx.appcompat.widget.SwitchCompat
android:id="@+id/check_pre_release"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/padding_spacing_dp16"
android:maxLines="1"
android:text="@string/update_check_pre_release"
android:textAppearance="@style/TextAppearance.AppCompat.Small"
android:textColor="@color/colorAccent"
app:theme="@style/BrandedSwitch" />
</LinearLayout>
</LinearLayout>
<LinearLayout
android:id="@+id/layout_soure_ccode"
android:layout_width="match_parent"

View File

@@ -5,7 +5,8 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
android:orientation="vertical">
android:orientation="vertical"
tools:context=".ui.PerAppProxyActivity">
<com.google.android.material.progressindicator.LinearProgressIndicator
android:id="@+id/pb_waiting"
@@ -71,6 +72,23 @@
android:textColor="@color/colorAccent"
app:theme="@style/BrandedSwitch" />
<LinearLayout
android:id="@+id/layout_switch_bypass_apps_tips"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="?attr/selectableItemBackgroundBorderless"
android:clickable="true"
android:focusable="true"
android:gravity="center"
android:padding="@dimen/padding_spacing_dp8">
<ImageView
android:layout_width="@dimen/image_size_dp24"
android:layout_height="@dimen/image_size_dp24"
app:srcCompat="@drawable/ic_about_24dp" />
</LinearLayout>
</LinearLayout>
</LinearLayout>
@@ -83,7 +101,6 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scrollbars="vertical"
app:layoutManager="androidx.recyclerview.widget.GridLayoutManager"
tools:context=".ui.PerAppProxyActivity" />
app:layoutManager="androidx.recyclerview.widget.GridLayoutManager" />
</LinearLayout>

View File

@@ -5,7 +5,7 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
tools:context=".ui.SubSettingActivity">
tools:context=".ui.RoutingEditActivity">
<LinearLayout
android:layout_width="match_parent"

View File

@@ -18,30 +18,35 @@
android:orientation="vertical">
<LinearLayout
android:id="@+id/layout_domain_strategy"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="@dimen/padding_spacing_dp8"
android:orientation="vertical">
android:background="?attr/selectableItemBackground"
android:clickable="true"
android:focusable="true"
android:gravity="center|start"
android:orientation="vertical"
android:padding="@dimen/padding_spacing_dp16">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/routing_settings_domain_strategy" />
android:text="@string/routing_settings_domain_strategy"
android:textAppearance="@style/TextAppearance.AppCompat.Subhead" />
<Spinner
android:id="@+id/sp_domain_strategy"
android:layout_width="match_parent"
<TextView
android:id="@+id/tv_domain_strategy_summary"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/padding_spacing_dp8"
android:layout_marginBottom="@dimen/padding_spacing_dp8"
android:entries="@array/routing_domain_strategy"
android:paddingTop="@dimen/padding_spacing_dp8" />
android:maxLines="1"
android:textAppearance="@style/TextAppearance.AppCompat.Small" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="@dimen/padding_spacing_dp8"
android:layout_margin="@dimen/padding_spacing_dp16"
android:orientation="vertical">
<TextView

View File

@@ -5,7 +5,7 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
tools:context=".ui.SubSettingActivity">
tools:context=".ui.SubEditActivity">
<LinearLayout
android:layout_width="match_parent"
@@ -26,7 +26,7 @@
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/title_server"
android:text="@string/title_sub_setting"
android:textAppearance="@style/TextAppearance.AppCompat.Subhead" />
</LinearLayout>

View File

@@ -0,0 +1,78 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
tools:context=".ui.UserAssetActivity">
<com.google.android.material.progressindicator.LinearProgressIndicator
android:id="@+id/pb_waiting"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:indeterminate="true"
android:visibility="invisible"
app:indicatorColor="@color/color_fab_active" />
<androidx.core.widget.NestedScrollView
android:id="@+id/main_content"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<LinearLayout
android:id="@+id/layout_geo_files_sources"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/selectableItemBackground"
android:clickable="true"
android:focusable="true"
android:gravity="center|start"
android:orientation="vertical"
android:padding="@dimen/padding_spacing_dp16">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/asset_geo_files_sources"
android:textAppearance="@style/TextAppearance.AppCompat.Subhead" />
<TextView
android:id="@+id/tv_geo_files_sources_summary"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/padding_spacing_dp8"
android:maxLines="2"
android:textAppearance="@style/TextAppearance.AppCompat.Small" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="@dimen/padding_spacing_dp16"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/title_user_asset_setting"
android:textAppearance="@style/TextAppearance.AppCompat.Subhead" />
</LinearLayout>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:nestedScrollingEnabled="false"
tools:listitem="@layout/item_recycler_user_asset" />
</LinearLayout>
</androidx.core.widget.NestedScrollView>
</RelativeLayout>

View File

@@ -56,29 +56,6 @@
android:id="@+id/import_manually_hysteria2"
android:title="@string/menu_item_import_config_manually_hysteria2"
app:showAsAction="never" />
<!-- <item-->
<!-- android:title="@string/menu_item_import_config_custom"-->
<!-- app:showAsAction="ifRoom">-->
<!-- <menu>-->
<!-- <item-->
<!-- android:id="@+id/import_config_custom_clipboard"-->
<!-- android:title="@string/menu_item_import_config_custom_clipboard"-->
<!-- app:showAsAction="never" />-->
<!-- <item-->
<!-- android:id="@+id/import_config_custom_local"-->
<!-- android:title="@string/menu_item_import_config_custom_local"-->
<!-- app:showAsAction="never" />-->
<!-- <item-->
<!-- android:id="@+id/import_config_custom_url"-->
<!-- android:title="@string/menu_item_import_config_custom_url"-->
<!-- app:showAsAction="never" />-->
<!-- <item-->
<!-- android:id="@+id/import_config_custom_url_scan"-->
<!-- android:title="@string/menu_item_import_config_custom_url_scan"-->
<!-- app:showAsAction="never" />-->
<!-- </menu>-->
<!-- </item>-->
</menu>
</item>
<item

View File

@@ -35,11 +35,6 @@
<string name="menu_item_import_config_manually_trojan">الكتابة يدويًا [Trojan]</string>
<string name="menu_item_import_config_manually_wireguard">الكتابة يدويًا [Wireguard]</string>
<string name="menu_item_import_config_manually_hysteria2">Type manually[Hysteria2]</string>
<string name="menu_item_import_config_custom">تكوين مخصص</string>
<string name="menu_item_import_config_custom_clipboard">استيراد تكوين مخصص من الحافظة</string>
<string name="menu_item_import_config_custom_local">استيراد تكوين مخصص من الجهاز</string>
<string name="menu_item_import_config_custom_url">استيراد تكوين مخصص من عنوان URL</string>
<string name="menu_item_import_config_custom_url_scan">استيراد تكوين مخصص مسح عنوان URL</string>
<string name="del_config_comfirm">تأكيد الحذف؟</string>
<string name="del_invalid_config_comfirm">Please test before deleting! Confirm delete ?</string>
<string name="server_lab_remarks">ملاحظات</string>
@@ -127,6 +122,7 @@
<string name="title_user_asset_add_url">إضافة عنوان URL للأصل</string>
<string name="msg_file_not_found">الملف غير موجود</string>
<string name="msg_remark_is_duplicate">الملاحظات موجودة بالفعل</string>
<string name="asset_geo_files_sources">Geo files source (optional)</string>
<!-- PerAppProxyActivity -->
<string name="msg_dialog_progress">جار التحميل</string>
@@ -262,9 +258,9 @@
<string name="sub_setting_filter">Remarks regular filter</string>
<string name="sub_setting_enable">تفعيل التحديث</string>
<string name="sub_auto_update">تفعيل التحديث التلقائي</string>
<string name="sub_setting_pre_profile">Previous proxy remarks</string>
<string name="sub_setting_next_profile">Next proxy remarks</string>
<string name="sub_setting_pre_profile_tip">The remarks exists and is unique</string>
<string name="sub_setting_pre_profile">Previous proxy configuration remarks</string>
<string name="sub_setting_next_profile">Next proxy configuration remarks</string>
<string name="sub_setting_pre_profile_tip">The configuration remarks exists and is unique</string>
<string name="title_sub_update">تحديث الاشتراك (أول خطوة)</string>
<string name="title_ping_all_server">Tcping لجميع الإعدادات</string>
<string name="title_real_ping_all_server"> اختبر جميع الإعدادات (3)</string>
@@ -314,6 +310,12 @@
<string name="title_pref_fragment_interval">فاصل الجزء (الحد الأدنى - الحد الأقصى)</string>
<string name="title_pref_fragment_enabled">تفعيل الجزء</string>
<string name="update_check_for_update">Check for update</string>
<string name="update_already_latest_version">Already on the latest version</string>
<string name="update_new_version_found">New version found: %s</string>
<string name="update_now">Update now</string>
<string name="update_check_pre_release">Check Pre-release</string>
<string-array name="share_method">
<item>رمز استجابة سريعة (QRcode)</item>
<item>تصدير إلى الحافظة</item>

View File

@@ -35,11 +35,6 @@
<string name="menu_item_import_config_manually_trojan">ম্যানুয়ালি টাইপ করুন [Trojan]</string>
<string name="menu_item_import_config_manually_wireguard">ম্যানুয়ালি টাইপ করুন [Wireguard]</string>
<string name="menu_item_import_config_manually_hysteria2">Type manually[Hysteria2]</string>
<string name="menu_item_import_config_custom">কাস্টম কনফিগারেশন</string>
<string name="menu_item_import_config_custom_clipboard">ক্লিপবোর্ড থেকে কাস্টম কনফিগারেশন আমদানি করুন</string>
<string name="menu_item_import_config_custom_local">স্থানীয়ভাবে কাস্টম কনফিগারেশন আমদানি করুন</string>
<string name="menu_item_import_config_custom_url">URL থেকে কাস্টম কনফিগারেশন আমদানি করুন</string>
<string name="menu_item_import_config_custom_url_scan">কাস্টম কনফিগারেশন স্ক্যান URL আমদানি করুন</string>
<string name="del_config_comfirm">মুছে ফেলুন নিশ্চিত করুন?</string>
<string name="del_invalid_config_comfirm">Please test before deleting! Confirm delete ?</string>
<string name="server_lab_remarks">মন্তব্য</string>
@@ -126,6 +121,7 @@
<string name="title_user_asset_add_url">অ্যাসেট URL যোগ করুন</string>
<string name="msg_file_not_found">ফাইল খুঁজে পাওয়া যায়নি</string>
<string name="msg_remark_is_duplicate">মন্তব্য ইতিমধ্যে বিদ্যমান</string>
<string name="asset_geo_files_sources">Geo files source (optional)</string>
<!-- PerAppProxyActivity -->
<string name="msg_dialog_progress">লোড হচ্ছে</string>
@@ -262,9 +258,9 @@
<string name="sub_setting_filter">Remarks regular filter</string>
<string name="sub_setting_enable">আপডেট সক্রিয় করুন</string>
<string name="sub_auto_update">স্বয়ংক্রিয় আপডেট সক্রিয় করুন</string>
<string name="sub_setting_pre_profile">Previous proxy remarks</string>
<string name="sub_setting_next_profile">Next proxy remarks</string>
<string name="sub_setting_pre_profile_tip">The remarks exists and is unique</string>
<string name="sub_setting_pre_profile">Previous proxy configuration remarks</string>
<string name="sub_setting_next_profile">Next proxy configuration remarks</string>
<string name="sub_setting_pre_profile_tip">The configuration remarks exists and is unique</string>
<string name="title_sub_update">সাবস্ক্রিপশন আপডেট</string>
<string name="title_ping_all_server">সব কনফিগারেশন TCPing</string>
<string name="title_real_ping_all_server">সব কনফিগারেশন প্রকৃত বিলম্ব</string>
@@ -313,6 +309,12 @@
<string name="title_pref_fragment_interval">ফ্র্যাগমেন্ট ইন্টারভ্যাল (ন্যূনতম-সর্বাধিক)</string>
<string name="title_pref_fragment_enabled">ফ্র্যাগমেন্ট সক্রিয় করুন</string>
<string name="update_check_for_update">Check for update</string>
<string name="update_already_latest_version">Already on the latest version</string>
<string name="update_new_version_found">New version found: %s</string>
<string name="update_now">Update now</string>
<string name="update_check_pre_release">Check Pre-release</string>
<string-array name="share_method">
<item>QR কোড</item>
<item>ক্লিপবোর্ডে রপ্তানি করুন</item>

View File

@@ -7,9 +7,9 @@
<string name="navigation_drawer_close">بستن نومگه کشاری</string>
<string name="migration_success">مووفقیت من جاگورویی داده</string>
<string name="migration_fail">جاگورویی داده ٱنجوم نگرؽڌ</string>
<string name="pull_down_to_refresh">Please pull down to refresh!</string>
<string name="pull_down_to_refresh">سی وانۊ کردن، بکشینس بلم!</string>
<!-- Notifications -->
<!-- Notifications -->
<string name="notification_action_stop_v2ray">واڌاشتن</string>
<string name="toast_permission_denied">گرؽڌن موجوز مومکن نؽڌ</string>
<string name="toast_permission_denied_notification">گرؽڌن موجوز وارسۊوی مومکن نؽڌ</string>
@@ -26,7 +26,7 @@
<string name="menu_item_del_config">پاک کردن کانفیگ</string>
<string name="menu_item_import_config_qrcode">و من ٱووردن کانفیگ ز QRcode</string>
<string name="menu_item_import_config_clipboard">و من ٱووردن کانفیگ ز کلیپ بورد</string>
<string name="menu_item_import_config_local">Import config from locally</string>
<string name="menu_item_import_config_local">و من ٱووردن کانفیگ ز مهلی</string>
<string name="menu_item_import_config_manually_vmess">هؽل دستی[VMess]</string>
<string name="menu_item_import_config_manually_vless">هؽل دستی[VLESS]</string>
<string name="menu_item_import_config_manually_ss">هؽل دستی[Shadowsocks]</string>
@@ -35,15 +35,10 @@
<string name="menu_item_import_config_manually_trojan">هؽل دستی[Trojan]</string>
<string name="menu_item_import_config_manually_wireguard">هؽل دستی[Wireguard]</string>
<string name="menu_item_import_config_manually_hysteria2">هؽل دستی[Hysteria2]</string>
<string name="menu_item_import_config_custom">کانفیگ سفارشی</string>
<string name="menu_item_import_config_custom_clipboard">کانفیگ سفارشین ز کلیپ بورد و من بیار</string>
<string name="menu_item_import_config_custom_local">کانفیگ سفارشین ز مهلی و من بیار</string>
<string name="menu_item_import_config_custom_url">کانفیگ سفارشین ز آدرس اینترنتی و من بیار</string>
<string name="menu_item_import_config_custom_url_scan">نشۊوی اینترنتی اسکن کانفیگ سفارشین بزݩ</string>
<string name="del_config_comfirm">پاک بۊ؟</string>
<string name="del_invalid_config_comfirm">پؽش ز پاک کردن کانفیگ نا موئتبر واجۊری کوݩ! پاک کردن کانفیگن قوۊل اکۊنی؟</string>
<string name="server_lab_remarks">نیشتنا</string>
<string name="server_lab_address">آدرس</string>
<string name="server_lab_address">نشۊوی</string>
<string name="server_lab_port">پورت</string>
<string name="server_lab_id">نوم من توری</string>
<string name="server_lab_alterid">شناسه جایگۊزین</string>
@@ -73,7 +68,7 @@
<string name="server_lab_stream_alpn">Alpn</string>
<string name="server_lab_allow_insecure">اجازه نا ٱمن</string>
<string name="server_lab_sni">SNI</string>
<string name="server_lab_address3">آدرس</string>
<string name="server_lab_address3">نشۊوی</string>
<string name="server_lab_port3">پورت</string>
<string name="server_lab_id3">رزم</string>
<string name="server_lab_security3">ٱمنیت</string>
@@ -82,12 +77,12 @@
<string name="server_lab_encryption">رزم نگاری</string>
<string name="server_lab_flow">جریان</string>
<string name="server_lab_public_key">کیلیت پوی وولاتی</string>
<string name="server_lab_preshared_key">کیلیت رمز ناهاڌن ازاف (اختیاری)</string>
<string name="server_lab_preshared_key">کیلیت رزم ناهاڌن ازاف (اختیاری)</string>
<string name="server_lab_short_id">ShortID</string>
<string name="server_lab_spider_x">SpiderX</string>
<string name="server_lab_secret_key">کیلیت سیخومی</string>
<string name="server_lab_reserved">Reserved(اختیاری، وا کاما ز یک جوڌا ابۊن)</string>
<string name="server_lab_local_address">آدرس مهلی (اختیاری IPv4/IPv6، وا کاما ز یک جوڌا ابۊن)</string>
<string name="server_lab_local_address">نشۊوی مهلی (اختیاری IPv4/IPv6، وا کاما ز یک جوڌا ابۊن)</string>
<string name="server_lab_local_mtu">Mtu(اختیاری، پؽش فرز 1420)</string>
<string name="toast_success">وا مووفقیت ٱنجوم وابی</string>
<string name="toast_failure">شکست خرد</string>
@@ -101,11 +96,11 @@
<string name="server_lab_content">موئتوا</string>
<string name="toast_none_data_clipboard">هیچ داده ای من کلیپ بورد وۊجۊڌ نڌاره</string>
<string name="toast_invalid_url">نشۊوی اینترنتی نا موئتبر هڌ</string>
<string name="toast_insecure_url_protocol">آدرس اشتراک پوروتوکول نا ٱمن HTTP ن و کار مبرین</string>
<string name="toast_insecure_url_protocol">نشۊوی اشتراک پوروتوکول نا ٱمن HTTP ن و کار مبرین</string>
<string name="server_lab_need_inbound">موتمعن بۊین ک پورت وۊرۊڌی وا سامووا ی جۊر هڌ</string>
<string name="toast_malformed_josn">کانفیگ زبال نؽڌ</string>
<string name="server_lab_request_host6">هاست(SNI)(اختیاری)</string>
<string name="toast_action_not_allowed">ای کار ممنۊ هڌ</string>
<string name="toast_action_not_allowed">ای کار ممنۊع هڌ</string>
<string name="server_obfs_password">رزم obfs</string>
<string name="server_lab_port_hop">پورت گوم (درگا سرورن ز نۊ هؽل اکونه)</string>
<string name="server_lab_port_hop_interval">فاسله پورت گوم (سانیه)</string>
@@ -121,24 +116,25 @@
<string name="menu_item_add_file">ازاف کردن فایل</string>
<string name="menu_item_add_url">ازاف کردن لینگ</string>
<string name="menu_item_scan_qrcode">اسکن QRcode</string>
<string name="title_url">آدرس اینترنتی</string>
<string name="title_url">نشۊوی اینترنتی</string>
<string name="menu_item_download_file">دانلود فایلا</string>
<string name="title_user_asset_add_url">آدرس اینترنتی دارایین ازاف کۊنین</string>
<string name="title_user_asset_add_url">نشۊوی اینترنتی دارایین ازاف کۊنین</string>
<string name="msg_file_not_found">فایلن نجوست</string>
<string name="msg_remark_is_duplicate">ائزارات ز زیتر بیڌسۉݩ</string>
<string name="msg_remark_is_duplicate">نوم ز زیتر بیڌس</string>
<string name="asset_geo_files_sources">بونچک فایلا جوقرافیایی (اختیاری)</string>
<!-- PerAppProxyActivity -->
<string name="msg_dialog_progress">هون بار ونی بۊ</string>
<string name="msg_dialog_progress">هونی بار ونی بۊ</string>
<string name="menu_item_search">پیتینیڌن</string>
<string name="menu_item_select_all">پسند پوی</string>
<string name="msg_enter_keywords">رزمان بزنین</string>
<string name="msg_enter_keywords">رزما ن بزنین</string>
<string name="switch_bypass_apps_mode">هالت Bypass</string>
<string name="menu_item_select_proxy_app">پسند خوتکار پروکسی برنومه</string>
<string name="msg_downloading_content">موئتوا هونی دانلود ابۊن</string>
<string name="menu_item_export_proxy_app">و در کشیڌن من کلیپ بورد</string>
<string name="menu_item_import_proxy_app">و من ٱووردن ز کلیپ بورد</string>
<string name="per_app_proxy_settings">Per-app settings</string>
<string name="per_app_proxy_settings_enable">Enable per-app</string>
<string name="per_app_proxy_settings">سامووا ب تفکیک برنومه</string>
<string name="per_app_proxy_settings_enable">ر وندن ب تفکیک برنومه</string>
<!-- Preferences -->
@@ -155,7 +151,7 @@
<string name="summary_pref_mux_enabled">زل تر، ٱما گاشڌ منپیز زی قت بۊ بارت دؽوۉداری، TCP، UDP و QUIC ن ای لم سفارشی کۊنین.</string>
<string name="title_pref_mux_concurency">منپیزا TCP (تلایه منجا 1-1024)</string>
<string name="title_pref_mux_xudp_concurency">منپیزا XUDP (تلایه منجا 1-1024)</string>
<string name="title_pref_mux_xudp_quic">دؽوۉداری QUIC من تونل mux</string>
<string name="title_pref_mux_xudp_quic">دؽوۉداری QUIC من تۊنل mux</string>
<string-array name="mux_xudp_quic_entries">
<item>رڌ کردن</item>
<item>موجاز</item>
@@ -168,13 +164,13 @@
<string name="title_pref_sniffing_enabled">ر وندن Sniffing</string>
<string name="summary_pref_sniffing_enabled">دامنه sniff ن ز کتن امتهۉݩ کۊنین (پؽش فرز رۊشن)</string>
<string name="title_pref_route_only_enabled">ر وندن routeOnly</string>
<string name="summary_pref_route_only_enabled">ز نوم دامنه sniffed تینا سی تور جوستن استفاڌه کۊنین وو آدرس مورد نزرن و عونوان آدرس IP ووردارین.</string>
<string name="summary_pref_route_only_enabled">ز نوم دامنه sniffed تینا سی تور جوستن استفاڌه کۊنین وو نشۊوی مۉرد نزرن و عونوان نشۊوی IP ووردارین.</string>
<string name="title_pref_local_dns_enabled">ر وندن DNS مهلی</string>
<string name="summary_pref_local_dns_enabled">DNS پردازشت وابیڌه و دس هسته ماژول DNS (پؽشنهاڌ ابۊ، ٱر نیاز هڌ ک جوستن تور وو ولات ٱسلین دور زنی)</string>
<string name="title_pref_fake_dns_enabled">ر وندن DNS جئلی</string>
<string name="summary_pref_fake_dns_enabled">DNS مهلی آدرسا IP جئلی ن وورگنه (زل تر، ٱما گاشڌ من یقرد ز برنومیل کار نکونه)</string>
<string name="summary_pref_fake_dns_enabled">DNS مهلی نشۊویا IP جئلی ن وورگنه (زل تر، ٱما گاشڌ من یقرد ز برنومه یل کار نکونه)</string>
<string name="title_pref_prefer_ipv6">ترجی IPv6</string>
<string name="summary_pref_prefer_ipv6">ترجی داڌن نشۊوی وو تورا IPv6</string>
@@ -188,14 +184,14 @@
<string name="title_pref_domestic_dns">DNS منی (اختیاری)</string>
<string name="summary_pref_domestic_dns">DNS</string>
<string name="title_pref_dns_hosts">DNS هاست موستقیم (قالوو: دامنه: آدرس،...)</string>
<string name="summary_pref_dns_hosts">دامنه:آدرس،...</string>
<string name="title_pref_dns_hosts">DNS هاست موستقیم (قالوو: دامنه: نشۊوی،...)</string>
<string name="summary_pref_dns_hosts">دامنه:نشۊوی،...</string>
<string name="title_pref_delay_test_url">آدرس اینترنتی آزمایش تئخیر واقعی (http/https)</string>
<string name="title_pref_delay_test_url">نشۊوی اینترنتی آزمایش تئخیر واقعی (http/https)</string>
<string name="summary_pref_delay_test_url">نشۊوی اینترنتی</string>
<string name="title_pref_proxy_sharing_enabled">هشتن منپیزا ز شبکه مهلی</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">منپیزا ز شبکه مهلی ن موجار کۊنین، موتمعن بۊین ک من ی شبکه قابل ائتماڌ هڌین.</string>
<string name="title_pref_allow_insecure">اجازه نا ٱمن</string>
@@ -214,10 +210,10 @@
<string name="summary_pref_start_scan_immediate">شؽواتگرن سی اسکن، زی مجال ر وندن بۊگۊشین، اندی ترین کودن اسکن کۊنین یا شؽواتی ن منه نوار ٱوزار پسند کۊنین.</string>
<string name="title_pref_append_http_proxy">پروکسی HTTP ن و VPN ازاف کۊنین</string>
<string name="summary_pref_append_http_proxy">پروکسی HTTP ن موسقیمن ز (مۊرۊرگر/ی قرد ز برنومیل لادراری بیڌه)، بؽ استفاڌه ز دسگا NIC مجازی (Android 10+) استفاڌه ابۊ.</string>
<string name="summary_pref_append_http_proxy">پروکسی HTTP ن موسقیمن ز (مۊرۊرگر/ی قرد ز برنومه یل لادراری بیڌه)، بؽ استفاڌه ز دسگا NIC مجازی (Android 10+) استفاڌه ابۊ.</string>
<string name="title_pref_double_column_display">Enable double column display</string>
<string name="summary_pref_double_column_display">The profile list is displayed in double columns, allowing more content to be displayed on the screen. You need to restart the application to take effect.</string>
<string name="title_pref_double_column_display">ر وندن نشۉݩ داڌن دو سۊتۊنی</string>
<string name="summary_pref_double_column_display">نومگه نمایه یل من دو سۊتۊن نشۉݩ داڌه ابۊن وو چینۉ ترین موئتوا بیشتری ن سیل کۊنین. سی ر وستن وا برنومه ن ز نۊ ر ونین.</string>
<!-- AboutActivity -->
<string name="title_pref_feedback">فشناڌن منشڌ</string>
@@ -268,10 +264,10 @@
<string name="title_sub_update">ورۊ کردن اشتراک جرگه سکویی</string>
<string name="title_ping_all_server">Tcping کانفیگا جرگه سکویی</string>
<string name="title_real_ping_all_server">تئخیر واقعی کانفیگا جرگه سکویی</string>
<string name="title_user_asset_setting">Asset files</string>
<string name="title_sort_by_test_results">ترتیب و ری نتیجیل آزمایش</string>
<string name="title_user_asset_setting">فایلا بونچک جوقرافیایی</string>
<string name="title_sort_by_test_results">ترتیب و ری نتیجه یل آزمایش</string>
<string name="title_filter_config">فیلتر کردن کانفیگا</string>
<string name="filter_config_all">پوی جرگیل</string>
<string name="filter_config_all">پوی جرگه یل کانفیگ</string>
<string name="title_del_duplicate_config_count">پاک کردن %d کانفیگ تکراری</string>
<string name="title_del_config_count">پاک کردن %d کانفیگ</string>
@@ -316,11 +312,17 @@
<string name="import_subscription_success">اشتراک وا مووفقیت زفت زابی</string>
<string name="import_subscription_failure">اشتراک زفت نوابی</string>
<string name="title_fragment_settings">سامووا Fragment</string>
<string name="title_pref_fragment_packets">Fragment Packets</string>
<string name="title_pref_fragment_length">Fragment Length (min-max)</string>
<string name="title_pref_fragment_interval">Fragment Interval (min-max)</string>
<string name="title_pref_fragment_enabled">ر وندن Fragment</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 name="update_check_for_update">واجۊری سی ورۊ رسۊوی</string>
<string name="update_already_latest_version">سکو نوسخه دیندایی پۊرنیڌه هڌ</string>
<string name="update_new_version_found">نوسخه نۊ ن جوست: %s</string>
<string name="update_now">سکو ورۊ رسۊوی کۊنین</string>
<string name="update_check_pre_release">نوسخیل پؽش ز تیجنیڌنن واجۊری کۊنین</string>
<string-array name="share_method">
<item>QRcode</item>
@@ -330,10 +332,10 @@
<string-array name="share_method_more">
<item>QRcode</item>
<item>Export to clipboard</item>
<item>Export full configuration to clipboard</item>
<item>Edit</item>
<item>Delete</item>
<item>و در کشیڌن من کلیپ بورد</item>
<item>و در کشیڌن پوی کانفیگ من کلیپ بورد</item>
<item>آلشت</item>
<item>پاک کردن</item>
</string-array>
<string-array name="share_sub_method">

View File

@@ -35,11 +35,6 @@
<string name="menu_item_import_config_manually_trojan">تایپ دستی[TROJAN]</string>
<string name="menu_item_import_config_manually_wireguard">‌تایپ دستی[WIREGUARD]</string>
<string name="menu_item_import_config_manually_hysteria2">تایپ دستی[HYSTERIA2]</string>
<string name="menu_item_import_config_custom">کانفیگ سفارشی</string>
<string name="menu_item_import_config_custom_clipboard">کانفیگ سفارشی را از کلیپ ‌بورد وارد کنید</string>
<string name="menu_item_import_config_custom_local">کانفیگ سفارشی را به صورت محلی وارد کنید</string>
<string name="menu_item_import_config_custom_url">کانفیگ سفارشی را از طریق نشانی اینترنتی وارد کنید</string>
<string name="menu_item_import_config_custom_url_scan">نشانی اینترنتی اسکن کانفیگ سفارشی را وارد کنید</string>
<string name="del_config_comfirm">حذف شود؟</string>
<string name="del_invalid_config_comfirm">لطفا قبل از حذف کانفیگ نامعتبر بررسی کنید! حذف کانفیگ را تایید می کنید؟</string>
<string name="server_lab_remarks">ملاحظات</string>
@@ -124,6 +119,7 @@
<string name="title_user_asset_add_url">آدرس اینترنتی را اضافه کنید</string>
<string name="msg_file_not_found">فایل پیدا نشد</string>
<string name="msg_remark_is_duplicate">نام قبلاً وجود دارد</string>
<string name="asset_geo_files_sources">منبع فایل های جغرافیایی (اختیاری)</string>
<!-- PerAppProxyActivity -->
<string name="msg_dialog_progress">بارگذاری</string>
@@ -319,6 +315,12 @@
<string name="title_pref_fragment_interval">فاصله بین بسته های فرگمنت (حداقل-حداکثر)</string>
<string name="title_pref_fragment_enabled">فعال کردن فرگمنت</string>
<string name="update_check_for_update">بررسی به روز رسانی</string>
<string name="update_already_latest_version">در حال حاضر آخرین نسخه نصب شده است</string>
<string name="update_new_version_found">نسخه جدید پیدا شد: %s</string>
<string name="update_now">اکنون به روز رسانی کنید</string>
<string name="update_check_pre_release">بررسی نسخه پیش از انتشار</string>
<string-array name="share_method">
<item>QRcode</item>
<item>خروجی گرفتن در کلیپ‌ بورد</item>

View File

@@ -35,11 +35,6 @@
<string name="menu_item_import_config_manually_trojan">Ручной ввод Trojan</string>
<string name="menu_item_import_config_manually_wireguard">Ручной ввод WireGuard</string>
<string name="menu_item_import_config_manually_hysteria2">Ручной ввод Hysteria2</string>
<string name="menu_item_import_config_custom">Другой профиль</string>
<string name="menu_item_import_config_custom_clipboard">Импорт из буфера обмена</string>
<string name="menu_item_import_config_custom_local">Импорт из файла</string>
<string name="menu_item_import_config_custom_url">Импорт из URL</string>
<string name="menu_item_import_config_custom_url_scan">Импорт сканированием URL</string>
<string name="del_config_comfirm">Подтверждаете удаление?</string>
<string name="del_invalid_config_comfirm">Выполните проверку перед удалением! Подтверждаете удаление?</string>
<string name="server_lab_remarks">Название</string>
@@ -126,6 +121,7 @@
<string name="title_user_asset_add_url">Добавить URL ресурса</string>
<string name="msg_file_not_found">Файл не найден</string>
<string name="msg_remark_is_duplicate">Название уже существует</string>
<string name="asset_geo_files_sources">Источник геофайлов (необязательно)</string>
<!-- PerAppProxyActivity -->
<string name="msg_dialog_progress">Загрузка…</string>
@@ -321,6 +317,12 @@
<string name="title_pref_fragment_interval">Интервал фрагментов (от - до)</string>
<string name="title_pref_fragment_enabled">Использовать фрагментирование</string>
<string name="update_check_for_update">Проверить обновление</string>
<string name="update_already_latest_version">Установлена последняя версия</string>
<string name="update_new_version_found">Найдена новая версия: %s</string>
<string name="update_now">Обновить</string>
<string name="update_check_pre_release">Проверить предварительный выпуск</string>
<string-array name="share_method">
<item>QR-код</item>
<item>Экспорт в буфер обмена</item>

View File

@@ -35,11 +35,6 @@
<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_hysteria2">Type manually[Hysteria2]</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_local">Nhập cấu hình tùy chỉnh từ Tệp</string>
<string name="menu_item_import_config_custom_url">Nhập cấu hình tùy chỉnh từ URL</string>
<string name="menu_item_import_config_custom_url_scan">Nhập cấu hình tùy chỉnh quét URL</string>
<string name="del_config_comfirm">Xác nhận xóa?</string>
<string name="del_invalid_config_comfirm">Please test before deleting! Confirm delete ?</string>
<string name="server_lab_remarks">Tên cấu hình</string>
@@ -124,6 +119,7 @@
<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_remark_is_duplicate">Nhận xét đã tồn tại!</string>
<string name="asset_geo_files_sources">Geo files source (optional)</string>
<!-- PerAppProxyActivity -->
<string name="msg_dialog_progress">Đang tải...</string>
@@ -262,9 +258,9 @@
<string name="sub_setting_filter">Remarks regular filter</string>
<string name="sub_setting_enable">Sử dụng gói đăng ký này</string>
<string name="sub_auto_update">Bật tự động cập nhật</string>
<string name="sub_setting_pre_profile">Previous proxy remarks</string>
<string name="sub_setting_next_profile">Next proxy remarks</string>
<string name="sub_setting_pre_profile_tip">The remarks exists and is unique</string>
<string name="sub_setting_pre_profile">Previous proxy configuration remarks</string>
<string name="sub_setting_next_profile">Next proxy configuration remarks</string>
<string name="sub_setting_pre_profile_tip">The configuration remarks exists and is unique</string>
<string name="title_sub_update">Cập nhật các gói đăng ký</string>
<string name="title_ping_all_server">Ping tất cả máy chủ</string>
<string name="title_real_ping_all_server">Kiểm tra HTTP tất cả máy chủ</string>
@@ -309,6 +305,18 @@
<string name="import_subscription_success">Nhập gói đăng ký thành công!</string>
<string name="import_subscription_failure">Nhập gói đăng ký không thành công!</string>
<string name="title_fragment_settings">Fragment Settings</string>
<string name="title_pref_fragment_packets">Fragment Packets</string>
<string name="title_pref_fragment_length">Fragment Length (min-max)</string>
<string name="title_pref_fragment_interval">Fragment Interval (min-max)</string>
<string name="title_pref_fragment_enabled">Enable Fragment</string>
<string name="update_check_for_update">Check for update</string>
<string name="update_already_latest_version">Already on the latest version</string>
<string name="update_new_version_found">New version found: %s</string>
<string name="update_now">Update now</string>
<string name="update_check_pre_release">Check Pre-release</string>
<string-array name="share_method">
<item>Xuất ra mã QR (Chụp màn hình để lưu)</item>
<item>Sao chép vào Clipboard</item>
@@ -340,12 +348,6 @@
<item>Sáng</item>
<item>Tối</item>
</string-array>
<string name="title_fragment_settings">Fragment Settings</string>
<string name="title_pref_fragment_packets">Fragment Packets</string>
<string name="title_pref_fragment_length">Fragment Length (min-max)</string>
<string name="title_pref_fragment_interval">Fragment Interval (min-max)</string>
<string name="title_pref_fragment_enabled">Enable Fragment</string>
<string-array name="vpn_bypass_lan">
<item>Follow config</item>
<item>Bypass</item>

View File

@@ -35,11 +35,6 @@
<string name="menu_item_import_config_manually_trojan">手动输入 [Trojan]</string>
<string name="menu_item_import_config_manually_wireguard">手动输入 [Wireguard]</string>
<string name="menu_item_import_config_manually_hysteria2">手动输入 [Hysteria2]</string>
<string name="menu_item_import_config_custom">自定义配置</string>
<string name="menu_item_import_config_custom_clipboard">从剪贴板导入自定义配置</string>
<string name="menu_item_import_config_custom_local">从本地导入自定义配置</string>
<string name="menu_item_import_config_custom_url">剪贴板 URL 导入自定义配置</string>
<string name="menu_item_import_config_custom_url_scan">扫描 URL 导入自定义配置</string>
<string name="del_config_comfirm">确认删除?</string>
<string name="del_invalid_config_comfirm">删除前请先测试!确认删除?</string>
<string name="server_lab_remarks">别名 (remarks)</string>
@@ -124,6 +119,7 @@
<string name="title_user_asset_add_url">添加资产网址</string>
<string name="msg_file_not_found">文件未找到</string>
<string name="msg_remark_is_duplicate">备注已经存在</string>
<string name="asset_geo_files_sources">Geo 文件来源 (可选)</string>
<!-- PerAppProxyActivity -->
<string name="msg_dialog_progress">正在加载</string>
@@ -259,9 +255,9 @@
<string name="sub_setting_filter">别名正则过滤</string>
<string name="sub_setting_enable">启用更新</string>
<string name="sub_auto_update">启用自动更新</string>
<string name="sub_setting_pre_profile">前置代理别名</string>
<string name="sub_setting_next_profile">落地代理別名</string>
<string name="sub_setting_pre_profile_tip">请确保别名存在并唯一</string>
<string name="sub_setting_pre_profile">前置代理配置文件别名</string>
<string name="sub_setting_next_profile">落地代理配置文件別名</string>
<string name="sub_setting_pre_profile_tip">请确保配置文件别名存在并唯一</string>
<string name="title_sub_update">更新当前组订阅</string>
<string name="title_ping_all_server">测试当前组配置 Tcping</string>
<string name="title_real_ping_all_server">测试当前组配置真连接</string>
@@ -313,6 +309,12 @@
<string name="title_pref_fragment_interval">分片间隔(最小 - 最大)</string>
<string name="title_pref_fragment_enabled">启用分片Fragment</string>
<string name="update_check_for_update">检查更新</string>
<string name="update_already_latest_version">目前已是最新版本</string>
<string name="update_new_version_found">发现新版本: %s</string>
<string name="update_now">立即更新</string>
<string name="update_check_pre_release">检查 Pre-release</string>
<string-array name="share_method">
<item>二维码</item>
<item>导出至剪贴板</item>

View File

@@ -35,11 +35,6 @@
<string name="menu_item_import_config_manually_trojan">手動鍵入 [Trojan]</string>
<string name="menu_item_import_config_manually_wireguard">手動鍵入 [Wireguard]</string>
<string name="menu_item_import_config_manually_hysteria2">手動鍵入 [Hysteria2]</string>
<string name="menu_item_import_config_custom">自訂設定</string>
<string name="menu_item_import_config_custom_clipboard">從剪貼簿匯入自訂設定</string>
<string name="menu_item_import_config_custom_local">從本地匯入自訂設定</string>
<string name="menu_item_import_config_custom_url">從 URL 匯入自訂設定</string>
<string name="menu_item_import_config_custom_url_scan">掃描 URL 匯入自訂設定</string>
<string name="del_config_comfirm">確定刪除?</string>
<string name="del_invalid_config_comfirm">刪除前請先測試!確認刪除?</string>
<string name="server_lab_remarks">備註</string>
@@ -124,6 +119,7 @@
<string name="title_user_asset_add_url">新增資產網址</string>
<string name="msg_file_not_found">文件未找到</string>
<string name="msg_remark_is_duplicate">備註已經存在</string>
<string name="asset_geo_files_sources">Geo 檔案來源 (可選)</string>
<!-- PerAppProxyActivity -->
<string name="msg_dialog_progress">載入</string>
@@ -260,9 +256,9 @@
<string name="sub_setting_filter">別名正規過濾</string>
<string name="sub_setting_enable">啟用更新</string>
<string name="sub_auto_update">啟用自動更新</string>
<string name="sub_setting_pre_profile">前置代理别名</string>
<string name="sub_setting_next_profile">落地代理別名</string>
<string name="sub_setting_pre_profile_tip">请确保别名存在并唯一</string>
<string name="sub_setting_pre_profile">前置代理設定檔别名</string>
<string name="sub_setting_next_profile">落地代理設定檔別名</string>
<string name="sub_setting_pre_profile_tip">请确保設定檔别名存在并唯一</string>
<string name="title_sub_update">更新目前群組訂閱</string>
<string name="title_ping_all_server">偵測目前群組設定 Tcping</string>
<string name="title_real_ping_all_server">偵測目前群組設定真延遲</string>
@@ -313,6 +309,12 @@
<string name="title_pref_fragment_interval">分片間隔(最小-最大)</string>
<string name="title_pref_fragment_enabled">啟用分片Fragment</string>
<string name="update_check_for_update">檢查更新</string>
<string name="update_already_latest_version">当前已是最新版本</string>
<string name="update_new_version_found">發現新版本: %s</string>
<string name="update_now">立即更新</string>
<string name="update_check_pre_release">檢查 Pre-release</string>
<string-array name="share_method">
<item>QR Code</item>
<item>匯出至剪貼簿</item>

View File

@@ -124,43 +124,6 @@
<item>xtls-rprx-vision-udp443</item>
</string-array>
<!-- minimum list https://serverfault.com/a/304791 -->
<string-array name="bypass_private_ip_address" translatable="false">
<item>0.0.0.0/5</item>
<item>8.0.0.0/7</item>
<item>11.0.0.0/8</item>
<item>12.0.0.0/6</item>
<item>16.0.0.0/4</item>
<item>32.0.0.0/3</item>
<item>64.0.0.0/2</item>
<item>128.0.0.0/3</item>
<item>160.0.0.0/5</item>
<item>168.0.0.0/6</item>
<item>172.0.0.0/12</item>
<item>172.32.0.0/11</item>
<item>172.64.0.0/10</item>
<item>172.128.0.0/9</item>
<item>173.0.0.0/8</item>
<item>174.0.0.0/7</item>
<item>176.0.0.0/4</item>
<item>192.0.0.0/9</item>
<item>192.128.0.0/11</item>
<item>192.160.0.0/13</item>
<item>192.169.0.0/16</item>
<item>192.170.0.0/15</item>
<item>192.172.0.0/14</item>
<item>192.176.0.0/12</item>
<item>192.192.0.0/10</item>
<item>193.0.0.0/8</item>
<item>194.0.0.0/7</item>
<item>196.0.0.0/6</item>
<item>200.0.0.0/5</item>
<item>208.0.0.0/4</item>
<item>240.0.0.0/4</item>
</string-array>
<string-array name="language_select" translatable="false">
<item>auto</item>
<item>English</item>

View File

@@ -36,11 +36,6 @@
<string name="menu_item_import_config_manually_trojan">Type manually[Trojan]</string>
<string name="menu_item_import_config_manually_wireguard">Type manually[Wireguard]</string>
<string name="menu_item_import_config_manually_hysteria2">Type manually[Hysteria2]</string>
<string name="menu_item_import_config_custom">Custom config</string>
<string name="menu_item_import_config_custom_clipboard">Import custom config from Clipboard</string>
<string name="menu_item_import_config_custom_local">Import custom config from locally</string>
<string name="menu_item_import_config_custom_url">Import custom config from URL</string>
<string name="menu_item_import_config_custom_url_scan">Import custom config scan URL</string>
<string name="del_config_comfirm">Confirm delete ?</string>
<string name="del_invalid_config_comfirm">Please test before deleting! Confirm delete ?</string>
<string name="server_lab_remarks">remarks</string>
@@ -127,6 +122,7 @@
<string name="title_user_asset_add_url">Add asset URL</string>
<string name="msg_file_not_found">File not found</string>
<string name="msg_remark_is_duplicate">The remarks already exists</string>
<string name="asset_geo_files_sources">Geo files source (optional)</string>
<!-- PerAppProxyActivity -->
<string name="msg_dialog_progress">Loading</string>
@@ -263,9 +259,9 @@
<string name="sub_setting_filter">Remarks regular filter</string>
<string name="sub_setting_enable">Enable update</string>
<string name="sub_auto_update">Enable automatic update</string>
<string name="sub_setting_pre_profile">Previous proxy remarks</string>
<string name="sub_setting_next_profile">Next proxy remarks</string>
<string name="sub_setting_pre_profile_tip">The remarks exists and is unique</string>
<string name="sub_setting_pre_profile">Previous proxy configuration remarks</string>
<string name="sub_setting_next_profile">Next proxy configuration remarks</string>
<string name="sub_setting_pre_profile_tip">The configuration remarks exists and is unique</string>
<string name="title_sub_update">Update current group subscription</string>
<string name="title_ping_all_server">Tcping current group configuration</string>
<string name="title_real_ping_all_server">Real delay current group configuration</string>
@@ -323,6 +319,12 @@
<string name="title_pref_fragment_interval">Fragment Interval (min-max)</string>
<string name="title_pref_fragment_enabled">Enable Fragment</string>
<string name="update_check_for_update">Check for update</string>
<string name="update_already_latest_version">Already on the latest version</string>
<string name="update_new_version_found">New version found: %s</string>
<string name="update_now">Update now</string>
<string name="update_check_pre_release">Check Pre-release</string>
<string-array name="share_method">
<item>QRcode</item>
<item>Export to clipboard</item>

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<network-security-config xmlns:tools="http://schemas.android.com/tools">
<base-config>
<base-config cleartextTrafficPermitted="true">
<trust-anchors>
<certificates src="system" />
<certificates

View File

@@ -0,0 +1,41 @@
package com.v2ray.ang
import com.v2ray.ang.util.HttpUtil
import org.junit.Assert.assertEquals
import org.junit.Test
class HttpUtilTest {
@Test
fun testIdnToASCII() {
// Regular URL remains unchanged
val regularUrl = "https://example.com/path"
assertEquals(regularUrl, HttpUtil.idnToASCII(regularUrl))
// Non-ASCII URL converts to ASCII (Punycode)
val nonAsciiUrl = "https://例子.测试/path"
val expectedNonAscii = "https://xn--fsqu00a.xn--0zwm56d/path"
assertEquals(expectedNonAscii, HttpUtil.idnToASCII(nonAsciiUrl))
// Mixed URL only converts the host part
val mixedUrl = "https://例子.com/测试"
val expectedMixed = "https://xn--fsqu00a.com/测试"
assertEquals(expectedMixed, HttpUtil.idnToASCII(mixedUrl))
// URL with Basic Authentication using regular domain
val basicAuthUrl = "https://user:password@example.com/path"
assertEquals(basicAuthUrl, HttpUtil.idnToASCII(basicAuthUrl))
// URL with Basic Authentication using non-ASCII domain
val basicAuthNonAscii = "https://user:password@例子.测试/path"
val expectedBasicAuthNonAscii = "https://user:password@xn--fsqu00a.xn--0zwm56d/path"
assertEquals(expectedBasicAuthNonAscii, HttpUtil.idnToASCII(basicAuthNonAscii))
// URL with non-ASCII username and password
val nonAsciiAuth = "https://用户:密码@example.com/path"
// Basic auth credentials should remain unchanged as they're percent-encoded separately
assertEquals(nonAsciiAuth, HttpUtil.idnToASCII(nonAsciiAuth))
}
}

View File

@@ -11,7 +11,7 @@ import org.junit.Test
*
* See [testing documentation](http://d.android.com/tools/testing).
*/
class AngUnitTest {
class UtilsTest {
@Test
fun test_parseInt() {
@@ -45,4 +45,18 @@ class AngUnitTest {
assertTrue(Utils.isIpAddress("240e:1234:abcd:12::/64"))
}
@Test
fun test_IsIpInCidr() {
assertTrue(Utils.isIpInCidr("192.168.1.1", "192.168.1.0/24"))
assertTrue(Utils.isIpInCidr("192.168.1.254", "192.168.1.0/24"))
assertFalse(Utils.isIpInCidr("192.168.2.1", "192.168.1.0/24"))
assertTrue(Utils.isIpInCidr("10.0.0.0", "10.0.0.0/8"))
assertTrue(Utils.isIpInCidr("10.255.255.255", "10.0.0.0/8"))
assertFalse(Utils.isIpInCidr("11.0.0.0", "10.0.0.0/8"))
assertFalse(Utils.isIpInCidr("invalid-ip", "192.168.1.0/24"))
assertFalse(Utils.isIpInCidr("192.168.1.1", "invalid-cidr"))
}
}