Compare commits

...

8 Commits

Author SHA1 Message Date
2dust
c0fda6fcba up 1.9.1 2024-09-19 14:54:17 +08:00
2dust
75c90e3c45 Resolving pre-proxy domain issues 2024-09-19 13:45:28 +08:00
2dust
9960f49698 Adding pre-proxy to group 2024-09-19 09:11:42 +08:00
2dust
1a2c4cc9a1 Adding pre-proxy setting to group 2024-09-19 09:09:04 +08:00
2dust
ee4f05b07d up 1.9.0 2024-09-17 13:47:21 +08:00
2dust
17ef476ede Bug fix 2024-09-17 13:39:02 +08:00
2dust
141b98631c Update libs.versions.toml 2024-09-17 11:08:49 +08:00
2dust
845562bca3 adapter noises 2024-09-17 11:02:13 +08:00
22 changed files with 240 additions and 118 deletions

View File

@@ -11,8 +11,8 @@ android {
applicationId = "com.v2ray.ang"
minSdk = 21
targetSdk = 34
versionCode = 586
versionName = "1.8.40"
versionCode = 592
versionName = "1.9.1"
multiDexEnabled = true
splits {
abi {

View File

@@ -3,7 +3,6 @@ package com.v2ray.ang.dto
import com.v2ray.ang.AppConfig.TAG_BLOCKED
import com.v2ray.ang.AppConfig.TAG_DIRECT
import com.v2ray.ang.AppConfig.TAG_PROXY
import com.v2ray.ang.util.Utils
data class ServerConfig(
val configVersion: Int = 3,
@@ -79,10 +78,4 @@ data class ServerConfig(
}
return mutableListOf()
}
fun getV2rayPointDomainAndPort(): String {
val address = getProxyOutbound()?.getServerAddress().orEmpty()
val port = getProxyOutbound()?.getServerPort()
return Utils.getIpv6Address(address) + ":" + port
}
}

View File

@@ -8,5 +8,7 @@ data class SubscriptionItem(
var lastUpdated: Long = -1,
var autoUpdate: Boolean = false,
val updateInterval: Int? = null,
var prevProfile: String? = null,
var nextProfile: String? = null,
)

View File

@@ -7,6 +7,7 @@ import com.google.gson.JsonSerializationContext
import com.google.gson.JsonSerializer
import com.google.gson.annotations.SerializedName
import com.google.gson.reflect.TypeToken
import com.v2ray.ang.util.Utils
import java.lang.reflect.Type
data class V2rayConfig(
@@ -85,7 +86,7 @@ data class V2rayConfig(
data class OutSettingsBean(
var vnext: List<VnextBean>? = null,
var fragment: FragmentBean? = null,
var noise: NoiseBean? = null,
var noises: List<NoiseBean>? = null,
var servers: List<ServersBean>? = null,
/*Blackhole*/
var response: Response? = null,
@@ -129,6 +130,7 @@ data class V2rayConfig(
)
data class NoiseBean(
var type: String? = null,
var packet: String? = null,
var delay: String? = null
)
@@ -448,6 +450,12 @@ data class V2rayConfig(
return null
}
fun getServerAddressAndPort(): String {
val address = getServerAddress().orEmpty()
val port = getServerPort()
return Utils.getIpv6Address(address) + ":" + port
}
fun getPassword(): String? {
if (protocol.equals(EConfigType.VMESS.name, true)
|| protocol.equals(EConfigType.VLESS.name, true)

View File

@@ -54,4 +54,6 @@ val URLConnection.responseLength: Long
val URI.idnHost: String
get() = host?.replace("[", "")?.replace("]", "").orEmpty()
fun String.removeWhiteSpace(): String = replace("\\s+".toRegex(), "")
fun String.removeWhiteSpace(): String = replace("\\s+".toRegex(), "")
fun String.toLongEx(): Long = toLongOrNull() ?: 0

View File

@@ -153,7 +153,7 @@ object V2RayServiceManager {
}
v2rayPoint.configureFileContent = result.content
v2rayPoint.domainName = config.getV2rayPointDomainAndPort()
v2rayPoint.domainName = result.domainPort
currentConfig = config
try {

View File

@@ -9,7 +9,6 @@ import android.view.ViewGroup
import androidx.appcompat.app.AlertDialog
import androidx.core.content.ContextCompat
import androidx.recyclerview.widget.RecyclerView
import com.google.gson.Gson
import com.v2ray.ang.AngApplication.Companion.application
import com.v2ray.ang.AppConfig
import com.v2ray.ang.R
@@ -17,7 +16,6 @@ import com.v2ray.ang.databinding.ItemQrcodeBinding
import com.v2ray.ang.databinding.ItemRecyclerFooterBinding
import com.v2ray.ang.databinding.ItemRecyclerMainBinding
import com.v2ray.ang.dto.EConfigType
import com.v2ray.ang.dto.SubscriptionItem
import com.v2ray.ang.extension.toast
import com.v2ray.ang.helper.ItemTouchHelperAdapter
import com.v2ray.ang.helper.ItemTouchHelperViewHolder
@@ -71,12 +69,7 @@ class MainRecyclerAdapter(val activity: MainActivity) : RecyclerView.Adapter<Mai
} else {
holder.itemMainBinding.layoutIndicator.setBackgroundResource(0)
}
holder.itemMainBinding.tvSubscription.text = ""
val json = MmkvManager.subStorage?.decodeString(profile.subscriptionId)
if (!json.isNullOrBlank()) {
val sub = Gson().fromJson(json, SubscriptionItem::class.java)
holder.itemMainBinding.tvSubscription.text = sub.remarks
}
holder.itemMainBinding.tvSubscription.text = MmkvManager.decodeSubscription(profile.subscriptionId)?.remarks ?: ""
var shareOptions = share_method.asList()
when (profile.configType) {

View File

@@ -17,6 +17,7 @@ import com.tencent.mmkv.MMKV
import com.v2ray.ang.AngApplication
import com.v2ray.ang.AppConfig
import com.v2ray.ang.R
import com.v2ray.ang.extension.toLongEx
import com.v2ray.ang.service.SubscriptionUpdater
import com.v2ray.ang.util.MmkvManager
import com.v2ray.ang.util.Utils
@@ -128,7 +129,7 @@ class SettingsActivity : BaseActivity() {
val value = newValue as Boolean
autoUpdateCheck?.isChecked = value
autoUpdateInterval?.isEnabled = value
autoUpdateInterval?.text?.toLong()?.let {
autoUpdateInterval?.text?.toLongEx()?.let {
if (newValue) configureUpdateTask(it) else cancelUpdateTask()
}
true
@@ -138,9 +139,9 @@ class SettingsActivity : BaseActivity() {
// It must be greater than 15 minutes because WorkManager couldn't run tasks under 15 minutes intervals
nval =
if (TextUtils.isEmpty(nval) || nval.toLong() < 15) AppConfig.SUBSCRIPTION_DEFAULT_UPDATE_INTERVAL else nval
if (TextUtils.isEmpty(nval) || nval.toLongEx() < 15) AppConfig.SUBSCRIPTION_DEFAULT_UPDATE_INTERVAL else nval
autoUpdateInterval?.summary = nval
configureUpdateTask(nval.toLong())
configureUpdateTask(nval.toLongEx())
true
}

View File

@@ -47,6 +47,8 @@ class SubEditActivity : BaseActivity() {
binding.etUrl.text = Utils.getEditable(subItem.url)
binding.chkEnable.isChecked = subItem.enabled
binding.autoUpdateCheck.isChecked = subItem.autoUpdate
binding.etPreProfile.text = Utils.getEditable(subItem.prevProfile)
binding.etNextProfile.text = Utils.getEditable(subItem.nextProfile)
return true
}
@@ -57,6 +59,8 @@ class SubEditActivity : BaseActivity() {
binding.etRemarks.text = null
binding.etUrl.text = null
binding.chkEnable.isChecked = true
binding.etPreProfile.text = null
binding.etNextProfile.text = null
return true
}
@@ -78,6 +82,8 @@ class SubEditActivity : BaseActivity() {
subItem.url = binding.etUrl.text.toString()
subItem.enabled = binding.chkEnable.isChecked
subItem.autoUpdate = binding.autoUpdateCheck.isChecked
subItem.prevProfile = binding.etPreProfile.text.toString()
subItem.nextProfile = binding.etNextProfile.text.toString()
if (TextUtils.isEmpty(subItem.remarks)) {
toast(R.string.sub_setting_remarks)

View File

@@ -171,6 +171,11 @@ object MmkvManager {
removeServerViaSubid(subid)
}
fun decodeSubscription(subscriptionId: String): SubscriptionItem? {
val json = subStorage.decodeString(subscriptionId) ?: return null
return Gson().fromJson(json, SubscriptionItem::class.java)
}
fun decodeAssetUrls(): List<Pair<String, AssetUrlItem>> {
val assetUrlItems = mutableListOf<Pair<String, AssetUrlItem>>()
assetStorage?.allKeys()?.forEach { key ->
@@ -229,4 +234,18 @@ object MmkvManager {
mainStorage?.encode(KEY_ANG_CONFIGS, Gson().toJson(serverList))
}
fun getServerViaRemarks(remarks: String?): ServerConfig? {
if (remarks == null) {
return null
}
val serverList = decodeServerList()
for (guid in serverList) {
val profile = decodeProfileConfig(guid)
if (profile != null && profile.remarks == remarks) {
return decodeServerConfig(guid)
}
}
return null
}
}

View File

@@ -16,87 +16,66 @@ import com.v2ray.ang.AppConfig.WIREGUARD_LOCAL_ADDRESS_V4
import com.v2ray.ang.AppConfig.WIREGUARD_LOCAL_ADDRESS_V6
import com.v2ray.ang.dto.EConfigType
import com.v2ray.ang.dto.ERoutingMode
import com.v2ray.ang.dto.ServerConfig
import com.v2ray.ang.dto.V2rayConfig
import com.v2ray.ang.dto.V2rayConfig.Companion.DEFAULT_NETWORK
import com.v2ray.ang.dto.V2rayConfig.Companion.HTTP
object V2rayConfigUtil {
private val serverRawStorage by lazy {
MMKV.mmkvWithID(
MmkvManager.ID_SERVER_RAW,
MMKV.MULTI_PROCESS_MODE
)
}
private val settingsStorage by lazy {
MMKV.mmkvWithID(
MmkvManager.ID_SETTING,
MMKV.MULTI_PROCESS_MODE
)
}
private val serverRawStorage by lazy { MMKV.mmkvWithID(MmkvManager.ID_SERVER_RAW, MMKV.MULTI_PROCESS_MODE) }
private val settingsStorage by lazy { MMKV.mmkvWithID(MmkvManager.ID_SETTING, MMKV.MULTI_PROCESS_MODE) }
data class Result(var status: Boolean, var content: String)
data class Result(var status: Boolean, var content: String = "", var domainPort: String? = null)
/**
* 生成v2ray的客户端配置文件
*/
fun getV2rayConfig(context: Context, guid: String): Result {
try {
val config = MmkvManager.decodeServerConfig(guid) ?: return Result(false, "")
val config = MmkvManager.decodeServerConfig(guid) ?: return Result(false)
if (config.configType == EConfigType.CUSTOM) {
val raw = serverRawStorage?.decodeString(guid)
val customConfig = if (raw.isNullOrBlank()) {
config.fullConfig?.toPrettyPrinting() ?: return Result(false, "")
config.fullConfig?.toPrettyPrinting() ?: return Result(false)
} else {
raw
}
//Log.d(ANG_PACKAGE, customConfig)
return Result(true, customConfig)
}
val outbound = config.getProxyOutbound() ?: return Result(false, "")
val address = outbound.getServerAddress() ?: return Result(false, "")
if (!Utils.isIpAddress(address)) {
if (!Utils.isValidUrl(address)) {
Log.d(ANG_PACKAGE, "$address is an invalid ip or domain")
return Result(false, "")
}
val domainPort = config.getProxyOutbound()?.getServerAddressAndPort()
return Result(true, customConfig, domainPort)
}
val result = getV2rayNonCustomConfig(context, outbound, config.remarks)
val result = getV2rayNonCustomConfig(context, config)
//Log.d(ANG_PACKAGE, result.content)
return result
} catch (e: Exception) {
e.printStackTrace()
return Result(false, "")
return Result(false)
}
}
/**
* 生成v2ray的客户端配置文件
*/
private fun getV2rayNonCustomConfig(
context: Context,
outbound: V2rayConfig.OutboundBean,
remarks: String,
): Result {
val result = Result(false, "")
private fun getV2rayNonCustomConfig(context: Context, config: ServerConfig): Result {
val result = Result(false)
val outbound = config.getProxyOutbound() ?: return result
val address = outbound.getServerAddress() ?: return result
if (!Utils.isIpAddress(address)) {
if (!Utils.isValidUrl(address)) {
Log.d(ANG_PACKAGE, "$address is an invalid ip or domain")
return result
}
}
//取得默认配置
val assets = Utils.readTextFromAssets(context, "v2ray_config.json")
if (TextUtils.isEmpty(assets)) {
return result
}
//转成Json
val v2rayConfig = Gson().fromJson(assets, V2rayConfig::class.java) ?: return result
v2rayConfig.log.loglevel = settingsStorage?.decodeString(AppConfig.PREF_LOGLEVEL)
?: "warning"
v2rayConfig.log.loglevel = settingsStorage?.decodeString(AppConfig.PREF_LOGLEVEL) ?: "warning"
v2rayConfig.remarks = config.remarks
inbounds(v2rayConfig)
updateOutboundWithGlobalSettings(outbound)
v2rayConfig.outbounds[0] = outbound
outbounds(v2rayConfig, outbound)
updateOutboundFragment(v2rayConfig)
val retMore = moreOutbounds(v2rayConfig, config.subscriptionId)
routing(v2rayConfig)
@@ -112,16 +91,12 @@ object V2rayConfigUtil {
v2rayConfig.policy = null
}
v2rayConfig.remarks = remarks
result.status = true
result.content = v2rayConfig.toPrettyPrinting()
result.domainPort = if (retMore.first) retMore.second else outbound.getServerAddressAndPort()
return result
}
/**
*
*/
private fun inbounds(v2rayConfig: V2rayConfig): Boolean {
try {
val socksPort = Utils.parseInt(
@@ -170,6 +145,20 @@ object V2rayConfigUtil {
return true
}
private fun outbounds(v2rayConfig: V2rayConfig, outbound: V2rayConfig.OutboundBean): Boolean {
val ret = updateOutboundWithGlobalSettings(outbound)
if (!ret) return false
if (v2rayConfig.outbounds.isNotEmpty()) {
v2rayConfig.outbounds[0] = outbound
} else {
v2rayConfig.outbounds.add(outbound)
}
updateOutboundFragment(v2rayConfig)
return true
}
private fun fakedns(v2rayConfig: V2rayConfig) {
if (settingsStorage?.decodeBool(AppConfig.PREF_LOCAL_DNS_ENABLED) == true
&& settingsStorage?.decodeBool(AppConfig.PREF_FAKE_DNS_ENABLED) == true
@@ -178,9 +167,6 @@ object V2rayConfigUtil {
}
}
/**
* routing
*/
private fun routing(v2rayConfig: V2rayConfig): Boolean {
try {
val routingMode = settingsStorage?.decodeString(AppConfig.PREF_ROUTING_MODE)
@@ -268,12 +254,7 @@ object V2rayConfigUtil {
return true
}
private fun routingGeo(
ipOrDomain: String,
code: String,
tag: String,
v2rayConfig: V2rayConfig
) {
private fun routingGeo(ipOrDomain: String, code: String, tag: String, v2rayConfig: V2rayConfig) {
try {
if (!TextUtils.isEmpty(code)) {
//IP
@@ -343,9 +324,6 @@ object V2rayConfigUtil {
return domain
}
/**
* Custom Dns
*/
private fun customLocalDns(v2rayConfig: V2rayConfig): Boolean {
try {
if (settingsStorage?.decodeBool(AppConfig.PREF_FAKE_DNS_ENABLED) == true) {
@@ -641,9 +619,12 @@ object V2rayConfigUtil {
interval = settingsStorage?.decodeString(AppConfig.PREF_FRAGMENT_INTERVAL)
?: "10-20"
),
noise = V2rayConfig.OutboundBean.OutSettingsBean.NoiseBean(
packet = "rand:100-200",
delay = "10-20",
noises = listOf(
V2rayConfig.OutboundBean.OutSettingsBean.NoiseBean(
type = "rand",
packet = "100-200",
delay = "10-20",
)
),
)
fragmentOutbound.streamSettings = V2rayConfig.OutboundBean.StreamSettingsBean(
@@ -665,4 +646,64 @@ object V2rayConfigUtil {
}
return true
}
}
private fun moreOutbounds(v2rayConfig: V2rayConfig, subscriptionId: String): Pair<Boolean, String> {
val returnPair = Pair(false, "")
var domainPort: String = ""
//fragment proxy
if (settingsStorage?.decodeBool(AppConfig.PREF_FRAGMENT_ENABLED, false) == true) {
return returnPair
}
if (subscriptionId.isNullOrEmpty()) {
return returnPair
}
try {
val subItem = MmkvManager.decodeSubscription(subscriptionId) ?: return returnPair
//current proxy
val outbound = v2rayConfig.outbounds[0]
//Previous proxy
val prevNode = MmkvManager.getServerViaRemarks(subItem.prevProfile)
if (prevNode != null) {
val prevOutbound = prevNode.getProxyOutbound()
if (prevOutbound != null) {
updateOutboundWithGlobalSettings(prevOutbound)
prevOutbound.tag = TAG_PROXY + "2"
v2rayConfig.outbounds.add(prevOutbound)
outbound.streamSettings?.sockopt =
V2rayConfig.OutboundBean.StreamSettingsBean.SockoptBean(
dialerProxy = prevOutbound.tag
)
domainPort = prevOutbound.getServerAddressAndPort()
}
}
//Next proxy
val nextNode = MmkvManager.getServerViaRemarks(subItem.nextProfile)
if (nextNode != null) {
val nextOutbound = nextNode.getProxyOutbound()
if (nextOutbound != null) {
updateOutboundWithGlobalSettings(nextOutbound)
nextOutbound.tag = TAG_PROXY
v2rayConfig.outbounds.add(0, nextOutbound)
outbound.tag = TAG_PROXY + "1"
nextOutbound.streamSettings?.sockopt =
V2rayConfig.OutboundBean.StreamSettingsBean.SockoptBean(
dialerProxy = outbound.tag
)
}
}
} catch (e: Exception) {
e.printStackTrace()
return returnPair
}
if (domainPort.isNotEmpty()) {
return Pair(true, domainPort)
}
return returnPair
}
}

View File

@@ -20,7 +20,6 @@ import com.v2ray.ang.dto.EConfigType
import com.v2ray.ang.dto.ProfileItem
import com.v2ray.ang.dto.ServerConfig
import com.v2ray.ang.dto.ServersCache
import com.v2ray.ang.dto.SubscriptionItem
import com.v2ray.ang.dto.V2rayConfig
import com.v2ray.ang.extension.toast
import com.v2ray.ang.util.AngConfigManager
@@ -28,7 +27,6 @@ import com.v2ray.ang.util.AngConfigManager.updateConfigViaSub
import com.v2ray.ang.util.MessageUtil
import com.v2ray.ang.util.MmkvManager
import com.v2ray.ang.util.MmkvManager.KEY_ANG_CONFIGS
import com.v2ray.ang.util.MmkvManager.subStorage
import com.v2ray.ang.util.SpeedtestUtil
import com.v2ray.ang.util.Utils
import com.v2ray.ang.util.V2rayConfigUtil
@@ -159,12 +157,8 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
if (subscriptionId.isNullOrEmpty()) {
return AngConfigManager.updateConfigViaSubAll()
} else {
val json = subStorage?.decodeString(subscriptionId)
if (!json.isNullOrBlank()) {
return updateConfigViaSub(Pair(subscriptionId, Gson().fromJson(json, SubscriptionItem::class.java)))
} else {
return 0
}
val subItem = MmkvManager.decodeSubscription(subscriptionId) ?: return 0
return updateConfigViaSub(Pair(subscriptionId, subItem))
}
}

View File

@@ -47,6 +47,32 @@
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/layout_margin_top_height"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/sub_setting_url" />
<EditText
android:id="@+id/et_url"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:gravity="top"
android:inputType="textMultiLine"
android:maxLines="10"
android:minLines="2"
android:scrollbars="vertical"
tools:ignore="TextFields" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
@@ -98,25 +124,38 @@
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/sub_setting_url" />
android:text="@string/sub_setting_pre_profile" />
<EditText
android:id="@+id/et_url"
android:id="@+id/et_pre_profile"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:gravity="top"
android:inputType="textMultiLine"
android:maxLines="10"
android:minLines="5"
android:scrollbars="vertical" />
android:layout_height="@dimen/edit_height"
android:inputType="text"
android:hint="@string/sub_setting_pre_profile_tip" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/layout_margin_top_height"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/sub_setting_next_profile" />
<EditText
android:id="@+id/et_next_profile"
android:layout_width="match_parent"
android:layout_height="@dimen/edit_height"
android:inputType="text"
android:hint="@string/sub_setting_pre_profile_tip" />
</LinearLayout>
</LinearLayout>
<LinearLayout

View File

@@ -236,6 +236,9 @@
<string name="sub_setting_url">عنوان URL اختياري</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="title_sub_update">تحديث الاشتراك (أول خطوة)</string>
<string name="title_ping_all_server">Tcping لجميع الإعدادات</string>
<string name="title_real_ping_all_server"> اختبر جميع الإعدادات (3)</string>

View File

@@ -234,6 +234,9 @@
<string name="sub_setting_url">ঐচ্ছিক URL</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="title_sub_update">সাবস্ক্রিপশন আপডেট</string>
<string name="title_ping_all_server">সব কনফিগারেশন TCPing</string>
<string name="title_real_ping_all_server">সব কনফিগারেশন প্রকৃত বিলম্ব</string>

View File

@@ -232,6 +232,9 @@
<string name="sub_setting_url">نشانی اینترنتی اختیاری</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="title_sub_update">به‌روزرسانی اشتراک</string>
<string name="title_ping_all_server">Tcping همه کانفیگ</string>
<string name="title_real_ping_all_server">تاخیر واقعی همه کانفیگ</string>

View File

@@ -236,6 +236,9 @@
<string name="sub_setting_url">URL (необязательно)</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="title_sub_update">Обновить подписку группы</string>
<string name="title_ping_all_server">Проверка профилей группы</string>
<string name="title_real_ping_all_server">Время отклика профилей группы</string>

View File

@@ -235,6 +235,9 @@
<string name="sub_setting_url">URL gói đăng ký</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="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>

View File

@@ -233,6 +233,9 @@
<string name="sub_setting_url">可选地址(url)</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="title_sub_update">更新当前组订阅</string>
<string name="title_ping_all_server">测试当前组配置Tcping</string>
<string name="title_real_ping_all_server">测试当前组配置真连接</string>

View File

@@ -234,6 +234,9 @@
<string name="sub_setting_url">Optional URL</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="title_sub_update">更新目前群組訂閱</string>
<string name="title_ping_all_server">偵測目前群組配置 Tcping</string>
<string name="title_real_ping_all_server">偵測目前群組配置真延遲</string>

View File

@@ -239,6 +239,9 @@
<string name="sub_setting_url">Optional URL</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="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>

View File

@@ -1,26 +1,26 @@
[versions]
activityKtx = "1.9.1"
activityKtx = "1.9.2"
appcompat = "1.7.0"
cardview = "1.0.0"
constraintlayout = "2.1.4"
core = "3.5.3"
editorkit = "2.9.0"
flexbox = "3.0.0"
fragmentKtx = "1.8.2"
fragmentKtx = "1.8.3"
gson = "2.11.0"
junit = "4.13.2"
kotlinReflect = "2.0.0"
kotlinxCoroutinesCore = "1.8.1"
kotlinReflect = "2.0.20"
kotlinxCoroutinesCore = "1.9.0"
legacySupportV4 = "1.0.0"
lifecycleViewmodelKtx = "2.8.4"
lifecycleViewmodelKtx = "2.8.5"
material = "1.12.0"
mmkvStatic = "1.3.4"
mmkvStatic = "1.3.9"
multidex = "2.0.1"
preferenceKtx = "1.2.1"
quickieBundled = "1.9.0"
quickieBundled = "1.10.0"
recyclerview = "1.3.2"
rxandroid = "3.0.2"
rxjava = "3.1.8"
rxjava = "3.1.9"
rxpermissions = "0.12"
toastcompat = "1.1.0"
viewpager2 = "1.1.0"