Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| abd23b3227 | |||
| 7d3e002140 | |||
| 1adb22944a | |||
| f48253371d | |||
| 74f701096e | |||
| 8eca5fe8af | |||
| ff40b53e2f | |||
| 5080d94bb7 |
+1
-1
Submodule AndroidLibXrayLite updated: 1f10bdfa7f...d783dc8ea7
@@ -12,8 +12,8 @@ android {
|
|||||||
applicationId = "com.v2ray.ang"
|
applicationId = "com.v2ray.ang"
|
||||||
minSdk = 24
|
minSdk = 24
|
||||||
targetSdk = 36
|
targetSdk = 36
|
||||||
versionCode = 707
|
versionCode = 708
|
||||||
versionName = "2.0.7"
|
versionName = "2.0.8"
|
||||||
multiDexEnabled = true
|
multiDexEnabled = true
|
||||||
|
|
||||||
val abiFilterList = (properties["ABI_FILTERS"] as? String)?.split(';')
|
val abiFilterList = (properties["ABI_FILTERS"] as? String)?.split(';')
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ data class ProfileItem(
|
|||||||
var addedTime: Long = System.currentTimeMillis(),
|
var addedTime: Long = System.currentTimeMillis(),
|
||||||
|
|
||||||
var remarks: String = "",
|
var remarks: String = "",
|
||||||
|
var description: String? = null,
|
||||||
var server: String? = null,
|
var server: String? = null,
|
||||||
var serverPort: String? = null,
|
var serverPort: String? = null,
|
||||||
|
|
||||||
|
|||||||
@@ -162,7 +162,7 @@ data class V2rayConfig(
|
|||||||
var realitySettings: TlsSettingsBean? = null,
|
var realitySettings: TlsSettingsBean? = null,
|
||||||
var grpcSettings: GrpcSettingsBean? = null,
|
var grpcSettings: GrpcSettingsBean? = null,
|
||||||
var hysteriaSettings: HysteriaSettingsBean? = null,
|
var hysteriaSettings: HysteriaSettingsBean? = null,
|
||||||
var udpmasks: List<UdpMasksBean>? = null,
|
var finalmask: FinalMaskBean? = null,
|
||||||
val dsSettings: Any? = null,
|
val dsSettings: Any? = null,
|
||||||
var sockopt: SockoptBean? = null
|
var sockopt: SockoptBean? = null
|
||||||
) {
|
) {
|
||||||
@@ -202,15 +202,8 @@ data class V2rayConfig(
|
|||||||
var downlinkCapacity: Int = 100,
|
var downlinkCapacity: Int = 100,
|
||||||
var congestion: Boolean = false,
|
var congestion: Boolean = false,
|
||||||
var readBufferSize: Int = 1,
|
var readBufferSize: Int = 1,
|
||||||
var writeBufferSize: Int = 1,
|
var writeBufferSize: Int = 1
|
||||||
var header: HeaderBean = HeaderBean(),
|
)
|
||||||
var seed: String? = null
|
|
||||||
) {
|
|
||||||
data class HeaderBean(
|
|
||||||
var type: String = "none",
|
|
||||||
var domain: String? = null
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
data class WsSettingsBean(
|
data class WsSettingsBean(
|
||||||
var path: String? = null,
|
var path: String? = null,
|
||||||
@@ -310,13 +303,19 @@ data class V2rayConfig(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
data class UdpMasksBean(
|
data class FinalMaskBean(
|
||||||
var type: String,
|
var tcp: List<MaskBean>? = null,
|
||||||
var settings: UdpMasksSettingsBean? = null
|
var udp: List<MaskBean>? = null
|
||||||
) {
|
) {
|
||||||
data class UdpMasksSettingsBean(
|
data class MaskBean(
|
||||||
var password: String? = null
|
var type: String,
|
||||||
)
|
var settings: MaskSettingsBean? = null
|
||||||
|
) {
|
||||||
|
data class MaskSettingsBean(
|
||||||
|
var password: String? = null,
|
||||||
|
var domain: String? = null
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -22,17 +22,17 @@ open class FmtBase {
|
|||||||
val query = if (dicQuery != null)
|
val query = if (dicQuery != null)
|
||||||
"?" + dicQuery.toList().joinToString(
|
"?" + dicQuery.toList().joinToString(
|
||||||
separator = "&",
|
separator = "&",
|
||||||
transform = { it.first + "=" + Utils.urlEncode(it.second) })
|
transform = { it.first + "=" + Utils.encodeURIComponent(it.second) })
|
||||||
else ""
|
else ""
|
||||||
|
|
||||||
val url = String.format(
|
val url = String.format(
|
||||||
"%s@%s:%s",
|
"%s@%s:%s",
|
||||||
Utils.urlEncode(userInfo ?: ""),
|
Utils.encodeURIComponent(userInfo ?: ""),
|
||||||
Utils.getIpv6Address(HttpUtil.toIdnDomain(config.server.orEmpty())),
|
Utils.getIpv6Address(HttpUtil.toIdnDomain(config.server.orEmpty())),
|
||||||
config.serverPort
|
config.serverPort
|
||||||
)
|
)
|
||||||
|
|
||||||
return "${url}${query}#${Utils.urlEncode(config.remarks)}"
|
return "${url}${query}#${Utils.encodeURIComponent(config.remarks)}"
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -43,7 +43,7 @@ open class FmtBase {
|
|||||||
*/
|
*/
|
||||||
fun getQueryParam(uri: URI): Map<String, String> {
|
fun getQueryParam(uri: URI): Map<String, String> {
|
||||||
return uri.rawQuery.split("&")
|
return uri.rawQuery.split("&")
|
||||||
.associate { it.split("=").let { (k, v) -> k to Utils.urlDecode(v) } }
|
.associate { it.split("=").let { (k, v) -> k to Utils.decodeURIComponent(v) } }
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -5,8 +5,7 @@ import com.v2ray.ang.enums.EConfigType
|
|||||||
import com.v2ray.ang.enums.NetworkType
|
import com.v2ray.ang.enums.NetworkType
|
||||||
import com.v2ray.ang.dto.ProfileItem
|
import com.v2ray.ang.dto.ProfileItem
|
||||||
import com.v2ray.ang.dto.V2rayConfig.OutboundBean
|
import com.v2ray.ang.dto.V2rayConfig.OutboundBean
|
||||||
import com.v2ray.ang.dto.V2rayConfig.OutboundBean.StreamSettingsBean
|
import com.v2ray.ang.dto.V2rayConfig.OutboundBean.StreamSettingsBean.FinalMaskBean
|
||||||
import com.v2ray.ang.dto.V2rayConfig.OutboundBean.StreamSettingsBean.UdpMasksBean.UdpMasksSettingsBean
|
|
||||||
import com.v2ray.ang.extension.idnHost
|
import com.v2ray.ang.extension.idnHost
|
||||||
import com.v2ray.ang.extension.isNotNullEmpty
|
import com.v2ray.ang.extension.isNotNullEmpty
|
||||||
import com.v2ray.ang.extension.nullIfBlank
|
import com.v2ray.ang.extension.nullIfBlank
|
||||||
@@ -27,7 +26,7 @@ object Hysteria2Fmt : FmtBase() {
|
|||||||
val config = ProfileItem.create(EConfigType.HYSTERIA2)
|
val config = ProfileItem.create(EConfigType.HYSTERIA2)
|
||||||
|
|
||||||
val uri = URI(Utils.fixIllegalUrl(str))
|
val uri = URI(Utils.fixIllegalUrl(str))
|
||||||
config.remarks = Utils.urlDecode(uri.fragment.orEmpty()).let { it.ifEmpty { "none" } }
|
config.remarks = Utils.decodeURIComponent(uri.fragment.orEmpty()).let { it.ifEmpty { "none" } }
|
||||||
config.server = uri.idnHost
|
config.server = uri.idnHost
|
||||||
config.serverPort = uri.port.toString()
|
config.serverPort = uri.port.toString()
|
||||||
config.password = uri.userInfo
|
config.password = uri.userInfo
|
||||||
@@ -109,11 +108,13 @@ object Hysteria2Fmt : FmtBase() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (profileItem.obfsPassword.isNotNullEmpty()) {
|
if (profileItem.obfsPassword.isNotNullEmpty()) {
|
||||||
outboundBean.streamSettings?.udpmasks = mutableListOf(
|
outboundBean.streamSettings?.finalmask = FinalMaskBean(
|
||||||
StreamSettingsBean.UdpMasksBean(
|
udp = listOf(
|
||||||
type = "salamander",
|
FinalMaskBean.MaskBean(
|
||||||
settings = UdpMasksSettingsBean(
|
type = "salamander",
|
||||||
password = profileItem.obfsPassword
|
settings = FinalMaskBean.MaskBean.MaskSettingsBean(
|
||||||
|
password = profileItem.obfsPassword
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ object ShadowsocksFmt : FmtBase() {
|
|||||||
if (uri.port <= 0) return null
|
if (uri.port <= 0) return null
|
||||||
if (uri.userInfo.isNullOrEmpty()) return null
|
if (uri.userInfo.isNullOrEmpty()) return null
|
||||||
|
|
||||||
config.remarks = Utils.urlDecode(uri.fragment.orEmpty()).let { it.ifEmpty { "none" } }
|
config.remarks = Utils.decodeURIComponent(uri.fragment.orEmpty()).let { it.ifEmpty { "none" } }
|
||||||
config.server = uri.idnHost
|
config.server = uri.idnHost
|
||||||
config.serverPort = uri.port.toString()
|
config.serverPort = uri.port.toString()
|
||||||
|
|
||||||
@@ -83,7 +83,7 @@ object ShadowsocksFmt : FmtBase() {
|
|||||||
if (indexSplit > 0) {
|
if (indexSplit > 0) {
|
||||||
try {
|
try {
|
||||||
config.remarks =
|
config.remarks =
|
||||||
Utils.urlDecode(result.substring(indexSplit + 1, result.length))
|
Utils.decodeURIComponent(result.substring(indexSplit + 1, result.length))
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e(AppConfig.TAG, "Failed to decode remarks in SS legacy URL", e)
|
Log.e(AppConfig.TAG, "Failed to decode remarks in SS legacy URL", e)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ object SocksFmt : FmtBase() {
|
|||||||
if (uri.idnHost.isEmpty()) return null
|
if (uri.idnHost.isEmpty()) return null
|
||||||
if (uri.port <= 0) return null
|
if (uri.port <= 0) return null
|
||||||
|
|
||||||
config.remarks = Utils.urlDecode(uri.fragment.orEmpty()).let { it.ifEmpty { "none" } }
|
config.remarks = Utils.decodeURIComponent(uri.fragment.orEmpty()).let { it.ifEmpty { "none" } }
|
||||||
config.server = uri.idnHost
|
config.server = uri.idnHost
|
||||||
config.serverPort = uri.port.toString()
|
config.serverPort = uri.port.toString()
|
||||||
|
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ object TrojanFmt : FmtBase() {
|
|||||||
val config = ProfileItem.create(EConfigType.TROJAN)
|
val config = ProfileItem.create(EConfigType.TROJAN)
|
||||||
|
|
||||||
val uri = URI(Utils.fixIllegalUrl(str))
|
val uri = URI(Utils.fixIllegalUrl(str))
|
||||||
config.remarks = Utils.urlDecode(uri.fragment.orEmpty()).let { it.ifEmpty { "none" } }
|
config.remarks = Utils.decodeURIComponent(uri.fragment.orEmpty()).let { it.ifEmpty { "none" } }
|
||||||
config.server = uri.idnHost
|
config.server = uri.idnHost
|
||||||
config.serverPort = uri.port.toString()
|
config.serverPort = uri.port.toString()
|
||||||
config.password = uri.userInfo
|
config.password = uri.userInfo
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ object VlessFmt : FmtBase() {
|
|||||||
if (uri.rawQuery.isNullOrEmpty()) return null
|
if (uri.rawQuery.isNullOrEmpty()) return null
|
||||||
val queryParam = getQueryParam(uri)
|
val queryParam = getQueryParam(uri)
|
||||||
|
|
||||||
config.remarks = Utils.urlDecode(uri.fragment.orEmpty()).let { it.ifEmpty { "none" } }
|
config.remarks = Utils.decodeURIComponent(uri.fragment.orEmpty()).let { it.ifEmpty { "none" } }
|
||||||
config.server = uri.idnHost
|
config.server = uri.idnHost
|
||||||
config.serverPort = uri.port.toString()
|
config.serverPort = uri.port.toString()
|
||||||
config.password = uri.userInfo
|
config.password = uri.userInfo
|
||||||
|
|||||||
@@ -163,7 +163,7 @@ object VmessFmt : FmtBase() {
|
|||||||
if (uri.rawQuery.isNullOrEmpty()) return null
|
if (uri.rawQuery.isNullOrEmpty()) return null
|
||||||
val queryParam = getQueryParam(uri)
|
val queryParam = getQueryParam(uri)
|
||||||
|
|
||||||
config.remarks = Utils.urlDecode(uri.fragment.orEmpty()).let { it.ifEmpty { "none" } }
|
config.remarks = Utils.decodeURIComponent(uri.fragment.orEmpty()).let { it.ifEmpty { "none" } }
|
||||||
config.server = uri.idnHost
|
config.server = uri.idnHost
|
||||||
config.serverPort = uri.port.toString()
|
config.serverPort = uri.port.toString()
|
||||||
config.password = uri.userInfo
|
config.password = uri.userInfo
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ object WireguardFmt : FmtBase() {
|
|||||||
if (uri.rawQuery.isNullOrEmpty()) return null
|
if (uri.rawQuery.isNullOrEmpty()) return null
|
||||||
val queryParam = getQueryParam(uri)
|
val queryParam = getQueryParam(uri)
|
||||||
|
|
||||||
config.remarks = Utils.urlDecode(uri.fragment.orEmpty()).let { it.ifEmpty { "none" } }
|
config.remarks = Utils.decodeURIComponent(uri.fragment.orEmpty()).let { it.ifEmpty { "none" } }
|
||||||
config.server = uri.idnHost
|
config.server = uri.idnHost
|
||||||
config.serverPort = uri.port.toString()
|
config.serverPort = uri.port.toString()
|
||||||
|
|
||||||
|
|||||||
@@ -271,13 +271,14 @@ object AngConfigManager {
|
|||||||
) {
|
) {
|
||||||
try {
|
try {
|
||||||
val serverList: Array<Any> =
|
val serverList: Array<Any> =
|
||||||
JsonUtil.fromJson(server, Array<Any>::class.java)?: arrayOf()
|
JsonUtil.fromJson(server, Array<Any>::class.java) ?: arrayOf()
|
||||||
|
|
||||||
if (serverList.isNotEmpty()) {
|
if (serverList.isNotEmpty()) {
|
||||||
var count = 0
|
var count = 0
|
||||||
for (srv in serverList.reversed()) {
|
for (srv in serverList.reversed()) {
|
||||||
val config = CustomFmt.parse(JsonUtil.toJson(srv)) ?: continue
|
val config = CustomFmt.parse(JsonUtil.toJson(srv)) ?: continue
|
||||||
config.subscriptionId = subid
|
config.subscriptionId = subid
|
||||||
|
config.description = generateDescription(config)
|
||||||
val key = MmkvManager.encodeServerConfig("", config)
|
val key = MmkvManager.encodeServerConfig("", config)
|
||||||
MmkvManager.encodeServerRaw(key, JsonUtil.toJsonPretty(srv) ?: "")
|
MmkvManager.encodeServerRaw(key, JsonUtil.toJsonPretty(srv) ?: "")
|
||||||
count += 1
|
count += 1
|
||||||
@@ -292,6 +293,7 @@ object AngConfigManager {
|
|||||||
// For compatibility
|
// For compatibility
|
||||||
val config = CustomFmt.parse(server) ?: return 0
|
val config = CustomFmt.parse(server) ?: return 0
|
||||||
config.subscriptionId = subid
|
config.subscriptionId = subid
|
||||||
|
config.description = generateDescription(config)
|
||||||
val key = MmkvManager.encodeServerConfig("", config)
|
val key = MmkvManager.encodeServerConfig("", config)
|
||||||
MmkvManager.encodeServerRaw(key, server)
|
MmkvManager.encodeServerRaw(key, server)
|
||||||
return 1
|
return 1
|
||||||
@@ -302,6 +304,7 @@ object AngConfigManager {
|
|||||||
} else if (server.startsWith("[Interface]") && server.contains("[Peer]")) {
|
} else if (server.startsWith("[Interface]") && server.contains("[Peer]")) {
|
||||||
try {
|
try {
|
||||||
val config = WireguardFmt.parseWireguardConfFile(server) ?: return R.string.toast_incorrect_protocol
|
val config = WireguardFmt.parseWireguardConfFile(server) ?: return R.string.toast_incorrect_protocol
|
||||||
|
config.description = generateDescription(config)
|
||||||
val key = MmkvManager.encodeServerConfig("", config)
|
val key = MmkvManager.encodeServerConfig("", config)
|
||||||
MmkvManager.encodeServerRaw(key, server)
|
MmkvManager.encodeServerRaw(key, server)
|
||||||
return 1
|
return 1
|
||||||
@@ -363,6 +366,7 @@ object AngConfigManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
config.subscriptionId = subid
|
config.subscriptionId = subid
|
||||||
|
config.description = generateDescription(config)
|
||||||
val guid = MmkvManager.encodeServerConfig("", config)
|
val guid = MmkvManager.encodeServerConfig("", config)
|
||||||
if (removedSelectedServer != null &&
|
if (removedSelectedServer != null &&
|
||||||
config.server == removedSelectedServer.server && config.serverPort == removedSelectedServer.serverPort
|
config.server == removedSelectedServer.server && config.serverPort == removedSelectedServer.serverPort
|
||||||
@@ -493,4 +497,25 @@ object AngConfigManager {
|
|||||||
MmkvManager.encodeSubscription("", subItem)
|
MmkvManager.encodeSubscription("", subItem)
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Generates a description for the profile.
|
||||||
|
*
|
||||||
|
* @param profile The profile item.
|
||||||
|
* @return The generated description.
|
||||||
|
*/
|
||||||
|
fun generateDescription(profile: ProfileItem): String {
|
||||||
|
// Hide xxx:xxx:***/xxx.xxx.xxx.***
|
||||||
|
val server = profile.server
|
||||||
|
val port = profile.serverPort
|
||||||
|
if (server.isNullOrBlank() && port.isNullOrBlank()) return ""
|
||||||
|
|
||||||
|
val addrPart = server?.let {
|
||||||
|
if (it.contains(":"))
|
||||||
|
it.split(":").take(2).joinToString(":", postfix = ":***")
|
||||||
|
else
|
||||||
|
it.split('.').dropLast(1).joinToString(".", postfix = ".***")
|
||||||
|
} ?: ""
|
||||||
|
|
||||||
|
return "$addrPart : ${port ?: ""}"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1183,19 +1183,39 @@ object V2rayConfigManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
NetworkType.KCP.type -> {
|
NetworkType.KCP.type -> {
|
||||||
val kcpsetting = StreamSettingsBean.KcpSettingsBean()
|
streamSettings.kcpSettings = StreamSettingsBean.KcpSettingsBean()
|
||||||
kcpsetting.header.type = headerType ?: "none"
|
val udpMaskList = mutableListOf<StreamSettingsBean.FinalMaskBean.MaskBean>()
|
||||||
|
if (!headerType.isNullOrEmpty() && headerType != "none") {
|
||||||
|
val kcpHeaderType = when {
|
||||||
|
headerType == "wechat-video" -> "header-wechat"
|
||||||
|
else -> "header-$headerType"
|
||||||
|
}
|
||||||
|
udpMaskList.add(StreamSettingsBean.FinalMaskBean.MaskBean(
|
||||||
|
type = kcpHeaderType,
|
||||||
|
settings = if (headerType == "dns" && !host.isNullOrEmpty()) {
|
||||||
|
StreamSettingsBean.FinalMaskBean.MaskBean.MaskSettingsBean(
|
||||||
|
domain = host
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
))
|
||||||
|
}
|
||||||
if (seed.isNullOrEmpty()) {
|
if (seed.isNullOrEmpty()) {
|
||||||
kcpsetting.seed = null
|
udpMaskList.add(StreamSettingsBean.FinalMaskBean.MaskBean(
|
||||||
|
type = "mkcp-original"
|
||||||
|
))
|
||||||
} else {
|
} else {
|
||||||
kcpsetting.seed = seed
|
udpMaskList.add(StreamSettingsBean.FinalMaskBean.MaskBean(
|
||||||
|
type = "mkcp-aes128gcm",
|
||||||
|
settings = StreamSettingsBean.FinalMaskBean.MaskBean.MaskSettingsBean(
|
||||||
|
password = seed
|
||||||
|
)
|
||||||
|
))
|
||||||
}
|
}
|
||||||
if (host.isNullOrEmpty()) {
|
streamSettings.finalmask = StreamSettingsBean.FinalMaskBean(
|
||||||
kcpsetting.header.domain = null
|
udp = udpMaskList.toList()
|
||||||
} else {
|
)
|
||||||
kcpsetting.header.domain = host
|
|
||||||
}
|
|
||||||
streamSettings.kcpSettings = kcpsetting
|
|
||||||
}
|
}
|
||||||
|
|
||||||
NetworkType.WS.type -> {
|
NetworkType.WS.type -> {
|
||||||
|
|||||||
@@ -14,6 +14,8 @@ import com.v2ray.ang.databinding.ItemRecyclerFooterBinding
|
|||||||
import com.v2ray.ang.databinding.ItemRecyclerMainBinding
|
import com.v2ray.ang.databinding.ItemRecyclerMainBinding
|
||||||
import com.v2ray.ang.dto.ProfileItem
|
import com.v2ray.ang.dto.ProfileItem
|
||||||
import com.v2ray.ang.dto.ServersCache
|
import com.v2ray.ang.dto.ServersCache
|
||||||
|
import com.v2ray.ang.extension.nullIfBlank
|
||||||
|
import com.v2ray.ang.handler.AngConfigManager
|
||||||
import com.v2ray.ang.handler.MmkvManager
|
import com.v2ray.ang.handler.MmkvManager
|
||||||
import com.v2ray.ang.helper.ItemTouchHelperAdapter
|
import com.v2ray.ang.helper.ItemTouchHelperAdapter
|
||||||
import com.v2ray.ang.helper.ItemTouchHelperViewHolder
|
import com.v2ray.ang.helper.ItemTouchHelperViewHolder
|
||||||
@@ -121,19 +123,7 @@ class MainRecyclerAdapter(
|
|||||||
* @return Formatted address string
|
* @return Formatted address string
|
||||||
*/
|
*/
|
||||||
private fun getAddress(profile: ProfileItem): String {
|
private fun getAddress(profile: ProfileItem): String {
|
||||||
// Hide xxx:xxx:***/xxx.xxx.xxx.***
|
return profile.description.nullIfBlank() ?: AngConfigManager.generateDescription(profile)
|
||||||
val server = profile.server
|
|
||||||
val port = profile.serverPort
|
|
||||||
if (server.isNullOrBlank() && port.isNullOrBlank()) return ""
|
|
||||||
|
|
||||||
val addrPart = server?.let {
|
|
||||||
if (it.contains(":"))
|
|
||||||
it.split(":").take(2).joinToString(":", postfix = ":***")
|
|
||||||
else
|
|
||||||
it.split('.').dropLast(1).joinToString(".", postfix = ".***")
|
|
||||||
} ?: ""
|
|
||||||
|
|
||||||
return "$addrPart : ${port ?: ""}"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ import com.v2ray.ang.dto.ProfileItem
|
|||||||
import com.v2ray.ang.extension.isNotNullEmpty
|
import com.v2ray.ang.extension.isNotNullEmpty
|
||||||
import com.v2ray.ang.extension.toast
|
import com.v2ray.ang.extension.toast
|
||||||
import com.v2ray.ang.extension.toastSuccess
|
import com.v2ray.ang.extension.toastSuccess
|
||||||
|
import com.v2ray.ang.handler.AngConfigManager
|
||||||
import com.v2ray.ang.handler.MmkvManager
|
import com.v2ray.ang.handler.MmkvManager
|
||||||
import com.v2ray.ang.util.JsonUtil
|
import com.v2ray.ang.util.JsonUtil
|
||||||
import com.v2ray.ang.util.Utils
|
import com.v2ray.ang.util.Utils
|
||||||
@@ -494,6 +495,8 @@ class ServerActivity : BaseActivity() {
|
|||||||
saveStreamSettings(config)
|
saveStreamSettings(config)
|
||||||
saveTls(config)
|
saveTls(config)
|
||||||
|
|
||||||
|
config.description = AngConfigManager.generateDescription(config)
|
||||||
|
|
||||||
if (config.subscriptionId.isEmpty() && !subscriptionId.isNullOrEmpty()) {
|
if (config.subscriptionId.isEmpty() && !subscriptionId.isNullOrEmpty()) {
|
||||||
config.subscriptionId = subscriptionId.orEmpty()
|
config.subscriptionId = subscriptionId.orEmpty()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import com.v2ray.ang.dto.ProfileItem
|
|||||||
import com.v2ray.ang.extension.toast
|
import com.v2ray.ang.extension.toast
|
||||||
import com.v2ray.ang.extension.toastSuccess
|
import com.v2ray.ang.extension.toastSuccess
|
||||||
import com.v2ray.ang.fmt.CustomFmt
|
import com.v2ray.ang.fmt.CustomFmt
|
||||||
|
import com.v2ray.ang.handler.AngConfigManager
|
||||||
import com.v2ray.ang.handler.MmkvManager
|
import com.v2ray.ang.handler.MmkvManager
|
||||||
import com.v2ray.ang.util.Utils
|
import com.v2ray.ang.util.Utils
|
||||||
|
|
||||||
@@ -89,6 +90,7 @@ class ServerCustomConfigActivity : BaseActivity() {
|
|||||||
}
|
}
|
||||||
config.server = profileItem?.server
|
config.server = profileItem?.server
|
||||||
config.serverPort = profileItem?.serverPort
|
config.serverPort = profileItem?.serverPort
|
||||||
|
config.description = AngConfigManager.generateDescription(config)
|
||||||
|
|
||||||
MmkvManager.encodeServerConfig(editGuid, config)
|
MmkvManager.encodeServerConfig(editGuid, config)
|
||||||
MmkvManager.encodeServerRaw(editGuid, binding.editor.text.toString())
|
MmkvManager.encodeServerRaw(editGuid, binding.editor.text.toString())
|
||||||
|
|||||||
@@ -10,8 +10,10 @@ import com.v2ray.ang.R
|
|||||||
import com.v2ray.ang.databinding.ActivityServerGroupBinding
|
import com.v2ray.ang.databinding.ActivityServerGroupBinding
|
||||||
import com.v2ray.ang.enums.EConfigType
|
import com.v2ray.ang.enums.EConfigType
|
||||||
import com.v2ray.ang.dto.ProfileItem
|
import com.v2ray.ang.dto.ProfileItem
|
||||||
|
import com.v2ray.ang.extension.isNotNullEmpty
|
||||||
import com.v2ray.ang.extension.toast
|
import com.v2ray.ang.extension.toast
|
||||||
import com.v2ray.ang.extension.toastSuccess
|
import com.v2ray.ang.extension.toastSuccess
|
||||||
|
import com.v2ray.ang.handler.AngConfigManager
|
||||||
import com.v2ray.ang.handler.MmkvManager
|
import com.v2ray.ang.handler.MmkvManager
|
||||||
import com.v2ray.ang.util.Utils
|
import com.v2ray.ang.util.Utils
|
||||||
|
|
||||||
@@ -66,6 +68,11 @@ class ServerGroupActivity : BaseActivity() {
|
|||||||
private fun clearServer(): Boolean {
|
private fun clearServer(): Boolean {
|
||||||
binding.etRemarks.text = null
|
binding.etRemarks.text = null
|
||||||
binding.etPolicyGroupFilter.text = null
|
binding.etPolicyGroupFilter.text = null
|
||||||
|
|
||||||
|
if (subscriptionId.isNotNullEmpty()) {
|
||||||
|
val pos = subIds.indexOf(subscriptionId).let { if (it >= 0) it else 0 }
|
||||||
|
binding.spPolicyGroupSubId.setSelection(pos)
|
||||||
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -90,6 +97,9 @@ class ServerGroupActivity : BaseActivity() {
|
|||||||
if (config.subscriptionId.isEmpty() && !subscriptionId.isNullOrEmpty()) {
|
if (config.subscriptionId.isEmpty() && !subscriptionId.isNullOrEmpty()) {
|
||||||
config.subscriptionId = subscriptionId.orEmpty()
|
config.subscriptionId = subscriptionId.orEmpty()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
config.description = "${binding.spPolicyGroupType.selectedItem} - ${binding.spPolicyGroupSubId.selectedItem} - ${config.policyGroupFilter}"
|
||||||
|
|
||||||
MmkvManager.encodeServerConfig(editGuid, config)
|
MmkvManager.encodeServerConfig(editGuid, config)
|
||||||
toastSuccess(R.string.toast_success)
|
toastSuccess(R.string.toast_success)
|
||||||
finish()
|
finish()
|
||||||
|
|||||||
@@ -328,13 +328,48 @@ object Utils {
|
|||||||
*/
|
*/
|
||||||
fun urlEncode(url: String): String {
|
fun urlEncode(url: String): String {
|
||||||
return try {
|
return try {
|
||||||
URLEncoder.encode(url, Charsets.UTF_8.toString()).replace("+", "%20")
|
URLEncoder.encode(url, Charsets.UTF_8.toString())
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e(AppConfig.TAG, "Failed to encode URL", e)
|
Log.e(AppConfig.TAG, "Failed to encode URL", e)
|
||||||
url
|
url
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decode a "encodeURIComponent" string.
|
||||||
|
*
|
||||||
|
* @param url The "encodeURIComponent" string.
|
||||||
|
* @return The decoded string, or the original string if decoding fails.
|
||||||
|
*/
|
||||||
|
fun decodeURIComponent(url: String): String {
|
||||||
|
return try {
|
||||||
|
// Decode strictly according to RFC 3986 / encodeURIComponent semantics.
|
||||||
|
// '+' is a literal plus and MUST NOT be interpreted as space.
|
||||||
|
// Inputs using '+' for spaces are non-conforming and rejected deliberately
|
||||||
|
// to avoid cross-language interoperability issues.
|
||||||
|
URLDecoder.decode(url.replace("+", "%2B"), Charsets.UTF_8.toString())
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e(AppConfig.TAG, "Failed to decode encodeURIComponent", e)
|
||||||
|
url
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encode a string to "encodeURIComponent" format.
|
||||||
|
*
|
||||||
|
* @param url The string to encode.
|
||||||
|
* @return The "encodeURIComponent" encoded string, or the original string if encoding fails.
|
||||||
|
*/
|
||||||
|
fun encodeURIComponent(url: String): String {
|
||||||
|
return try {
|
||||||
|
// Replace '+' with '%20' to conform to encodeURIComponent semantics.
|
||||||
|
URLEncoder.encode(url, Charsets.UTF_8.toString()).replace("+", "%20")
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e(AppConfig.TAG, "Failed to encode encodeURIComponent", e)
|
||||||
|
url
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Read text from an asset file.
|
* Read text from an asset file.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -29,6 +29,7 @@
|
|||||||
<ImageView
|
<ImageView
|
||||||
android:layout_width="@dimen/image_size_dp24"
|
android:layout_width="@dimen/image_size_dp24"
|
||||||
android:layout_height="@dimen/image_size_dp24"
|
android:layout_height="@dimen/image_size_dp24"
|
||||||
|
android:importantForAccessibility="no"
|
||||||
app:srcCompat="@drawable/ic_source_code_24dp" />
|
app:srcCompat="@drawable/ic_source_code_24dp" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
@@ -53,6 +54,7 @@
|
|||||||
<ImageView
|
<ImageView
|
||||||
android:layout_width="@dimen/image_size_dp24"
|
android:layout_width="@dimen/image_size_dp24"
|
||||||
android:layout_height="@dimen/image_size_dp24"
|
android:layout_height="@dimen/image_size_dp24"
|
||||||
|
android:importantForAccessibility="no"
|
||||||
app:srcCompat="@drawable/license_24px" />
|
app:srcCompat="@drawable/license_24px" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
@@ -77,6 +79,7 @@
|
|||||||
<ImageView
|
<ImageView
|
||||||
android:layout_width="@dimen/image_size_dp24"
|
android:layout_width="@dimen/image_size_dp24"
|
||||||
android:layout_height="@dimen/image_size_dp24"
|
android:layout_height="@dimen/image_size_dp24"
|
||||||
|
android:importantForAccessibility="no"
|
||||||
app:srcCompat="@drawable/ic_feedback_24dp" />
|
app:srcCompat="@drawable/ic_feedback_24dp" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
@@ -102,6 +105,7 @@
|
|||||||
<ImageView
|
<ImageView
|
||||||
android:layout_width="@dimen/image_size_dp24"
|
android:layout_width="@dimen/image_size_dp24"
|
||||||
android:layout_height="@dimen/image_size_dp24"
|
android:layout_height="@dimen/image_size_dp24"
|
||||||
|
android:importantForAccessibility="no"
|
||||||
app:srcCompat="@drawable/ic_telegram_24dp" />
|
app:srcCompat="@drawable/ic_telegram_24dp" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
@@ -126,6 +130,7 @@
|
|||||||
<ImageView
|
<ImageView
|
||||||
android:layout_width="@dimen/image_size_dp24"
|
android:layout_width="@dimen/image_size_dp24"
|
||||||
android:layout_height="@dimen/image_size_dp24"
|
android:layout_height="@dimen/image_size_dp24"
|
||||||
|
android:importantForAccessibility="no"
|
||||||
app:srcCompat="@drawable/ic_privacy_24dp" />
|
app:srcCompat="@drawable/ic_privacy_24dp" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
|
|||||||
@@ -115,7 +115,7 @@
|
|||||||
<string name="server_lab_xhttp_extra">Необработанный JSON XHTTP Extra, формат: { XHTTPObject }</string>
|
<string name="server_lab_xhttp_extra">Необработанный JSON XHTTP Extra, формат: { XHTTPObject }</string>
|
||||||
<string name="server_lab_ech_config_list">EchConfigList</string>
|
<string name="server_lab_ech_config_list">EchConfigList</string>
|
||||||
<string name="server_lab_ech_force_query">EchForceQuery</string>
|
<string name="server_lab_ech_force_query">EchForceQuery</string>
|
||||||
<string name="server_lab_pinned_ca256">Certificate fingerprint (SHA-256)</string>
|
<string name="server_lab_pinned_ca256">Отпечаток сертификата (SHA-256)</string>
|
||||||
|
|
||||||
<!-- UserAssetActivity -->
|
<!-- UserAssetActivity -->
|
||||||
<string name="toast_asset_copy_failed">Невозможно скопировать файл, используйте файловый менеджер</string>
|
<string name="toast_asset_copy_failed">Невозможно скопировать файл, используйте файловый менеджер</string>
|
||||||
@@ -415,7 +415,7 @@
|
|||||||
<string-array name="policy_group_type">
|
<string-array name="policy_group_type">
|
||||||
<item>Наименьшая задержка</item>
|
<item>Наименьшая задержка</item>
|
||||||
<item>Наименьшая нагрузка</item>
|
<item>Наименьшая нагрузка</item>
|
||||||
<item>Случайный</item>
|
<item>Случайно</item>
|
||||||
<item>По очереди</item>
|
<item>По очереди</item>
|
||||||
</string-array>
|
</string-array>
|
||||||
|
|
||||||
|
|||||||
+1
-1
Submodule hev-socks5-tunnel updated: da33382c72...4d6c334dbf
Reference in New Issue
Block a user