Compare commits
78 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b8939763d4 | ||
|
|
51adca8568 | ||
|
|
f646eff048 | ||
|
|
fee0a016d8 | ||
|
|
f040fa5c08 | ||
|
|
7f3c6b4665 | ||
|
|
8b806fe0be | ||
|
|
b37d8c2369 | ||
|
|
b5451e9d3d | ||
|
|
b2235d4c38 | ||
|
|
c8ba5d727e | ||
|
|
a3de44cd0a | ||
|
|
214d9e1c53 | ||
|
|
92900c3f74 | ||
|
|
e17e566daa | ||
|
|
df4e232087 | ||
|
|
828a39331b | ||
|
|
a0223a3eee | ||
|
|
7f6a526b25 | ||
|
|
9983ea25d2 | ||
|
|
47166b937f | ||
|
|
0a09966e81 | ||
|
|
7ec34e934e | ||
|
|
f2b03e7492 | ||
|
|
5208bd62c5 | ||
|
|
d9f0854c27 | ||
|
|
6be125b5cb | ||
|
|
c884c098fd | ||
|
|
f77fe05c92 | ||
|
|
f3bfa8ceba | ||
|
|
ae7d9d87d2 | ||
|
|
1652040c1c | ||
|
|
818b7cdff4 | ||
|
|
48ce359d2d | ||
|
|
e115bf0c6d | ||
|
|
0415b60ba5 | ||
|
|
4570fdb05f | ||
|
|
9d109e7ca9 | ||
|
|
2574553180 | ||
|
|
66e77d50bd | ||
|
|
253bd793d7 | ||
|
|
be30de6728 | ||
|
|
a1455bbb1c | ||
|
|
1ac19ae3e9 | ||
|
|
6e6ca209df | ||
|
|
52699967cd | ||
|
|
146d20ce86 | ||
|
|
b6959b5990 | ||
|
|
06649df8b1 | ||
|
|
0174ed9082 | ||
|
|
adabb281b1 | ||
|
|
63e710d1ab | ||
|
|
509a568446 | ||
|
|
6fd94b53f0 | ||
|
|
164412fa34 | ||
|
|
514ca0810e | ||
|
|
7582f86482 | ||
|
|
4b4c46e5ae | ||
|
|
162e156b33 | ||
|
|
bc7d1971ef | ||
|
|
bbdee92f37 | ||
|
|
cf9e830cc7 | ||
|
|
804e425a87 | ||
|
|
cc21383928 | ||
|
|
413f4efd69 | ||
|
|
de69605eff | ||
|
|
9338ba3525 | ||
|
|
322b6ec615 | ||
|
|
304232d029 | ||
|
|
f80c3bfe07 | ||
|
|
bb0a62fc8b | ||
|
|
5bfdca6cd9 | ||
|
|
447e712a9d | ||
|
|
8bb03f189d | ||
|
|
3b0554cd9b | ||
|
|
858101b0d9 | ||
|
|
a7cf8bee28 | ||
|
|
af1ec7bea9 |
2
.github/workflows/build.yml
vendored
2
.github/workflows/build.yml
vendored
@@ -47,7 +47,7 @@ jobs:
|
||||
go get github.com/xtls/xray-core@${{ github.event.inputs.XRAY_CORE_VERSION }} || true
|
||||
gomobile init
|
||||
go mod tidy -v
|
||||
gomobile bind -v -androidapi 19 -ldflags='-s -w' ./
|
||||
gomobile bind -v -androidapi 21 -ldflags='-s -w' ./
|
||||
cp *.aar ${{ github.workspace }}/V2rayNG/app/libs/
|
||||
|
||||
- name: Build APK
|
||||
|
||||
@@ -11,20 +11,24 @@ android {
|
||||
applicationId = "com.v2ray.ang"
|
||||
minSdk = 21
|
||||
targetSdk = 34
|
||||
versionCode = 571
|
||||
versionName = "1.8.28"
|
||||
versionCode = 583
|
||||
versionName = "1.8.38"
|
||||
multiDexEnabled = true
|
||||
splits.abi {
|
||||
reset()
|
||||
splits {
|
||||
abi {
|
||||
isEnable = true
|
||||
include(
|
||||
"arm64-v8a",
|
||||
"armeabi-v7a",
|
||||
"x86_64",
|
||||
"x86"
|
||||
)
|
||||
isUniversalApk = true
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_1_8
|
||||
targetCompatibility = JavaVersion.VERSION_1_8
|
||||
@@ -50,13 +54,6 @@ android {
|
||||
jvmTarget = JavaVersion.VERSION_1_8.toString()
|
||||
}
|
||||
|
||||
splits {
|
||||
abi {
|
||||
isEnable = true
|
||||
isUniversalApk = true
|
||||
}
|
||||
}
|
||||
|
||||
applicationVariants.all {
|
||||
val variant = this
|
||||
val versionCodes =
|
||||
@@ -71,12 +68,9 @@ android {
|
||||
"universal"
|
||||
|
||||
output.outputFileName = "v2rayNG_${variant.versionName}_${abi}.apk"
|
||||
if(versionCodes.containsKey(abi))
|
||||
{
|
||||
if (versionCodes.containsKey(abi)) {
|
||||
output.versionCodeOverride = (1000000 * versionCodes[abi]!!).plus(variant.versionCode)
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
return@forEach
|
||||
}
|
||||
}
|
||||
@@ -87,7 +81,7 @@ android {
|
||||
buildConfig = true
|
||||
}
|
||||
|
||||
packagingOptions {
|
||||
packaging {
|
||||
jniLibs {
|
||||
useLegacyPackaging = true
|
||||
}
|
||||
@@ -96,44 +90,43 @@ android {
|
||||
|
||||
dependencies {
|
||||
implementation(fileTree(mapOf("dir" to "libs", "include" to listOf("*.aar", "*.jar"))))
|
||||
testImplementation("junit:junit:4.13.2")
|
||||
testImplementation(libs.junit)
|
||||
|
||||
implementation("com.google.android.flexbox:flexbox:3.0.0")
|
||||
implementation(libs.flexbox)
|
||||
// Androidx
|
||||
implementation("androidx.constraintlayout:constraintlayout:2.1.4")
|
||||
implementation("androidx.legacy:legacy-support-v4:1.0.0")
|
||||
implementation("androidx.appcompat:appcompat:1.7.0")
|
||||
implementation("com.google.android.material:material:1.12.0")
|
||||
implementation("androidx.cardview:cardview:1.0.0")
|
||||
implementation("androidx.preference:preference-ktx:1.2.1")
|
||||
implementation("androidx.recyclerview:recyclerview:1.3.2")
|
||||
implementation("androidx.fragment:fragment-ktx:1.8.1")
|
||||
implementation("androidx.multidex:multidex:2.0.1")
|
||||
implementation("androidx.viewpager2:viewpager2:1.1.0")
|
||||
implementation(libs.constraintlayout)
|
||||
implementation(libs.legacy.support.v4)
|
||||
implementation(libs.appcompat)
|
||||
implementation(libs.material)
|
||||
implementation(libs.cardview)
|
||||
implementation(libs.preference.ktx)
|
||||
implementation(libs.recyclerview)
|
||||
implementation(libs.fragment.ktx)
|
||||
implementation(libs.multidex)
|
||||
implementation(libs.viewpager2)
|
||||
|
||||
// Androidx ktx
|
||||
implementation("androidx.activity:activity-ktx:1.9.0")
|
||||
implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.8.3")
|
||||
implementation("androidx.lifecycle:lifecycle-livedata-ktx:2.8.3")
|
||||
implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.8.3")
|
||||
implementation(libs.activity.ktx)
|
||||
implementation(libs.lifecycle.viewmodel.ktx)
|
||||
implementation(libs.lifecycle.livedata.ktx)
|
||||
implementation(libs.lifecycle.runtime.ktx)
|
||||
|
||||
//kotlin
|
||||
implementation("org.jetbrains.kotlin:kotlin-reflect:1.9.23")
|
||||
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.8.1")
|
||||
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.8.1")
|
||||
implementation(libs.kotlin.reflect)
|
||||
implementation(libs.kotlinx.coroutines.core)
|
||||
implementation(libs.kotlinx.coroutines.android)
|
||||
|
||||
implementation("com.tencent:mmkv-static:1.3.4")
|
||||
implementation("com.google.code.gson:gson:2.11.0")
|
||||
implementation("io.reactivex:rxjava:1.3.8")
|
||||
implementation("io.reactivex:rxandroid:1.2.1")
|
||||
implementation("com.tbruyelle.rxpermissions:rxpermissions:0.9.4@aar")
|
||||
implementation("me.drakeet.support:toastcompat:1.1.0")
|
||||
implementation("com.blacksquircle.ui:editorkit:2.9.0")
|
||||
implementation("com.blacksquircle.ui:language-base:2.9.0")
|
||||
implementation("com.blacksquircle.ui:language-json:2.9.0")
|
||||
implementation("io.github.g00fy2.quickie:quickie-bundled:1.9.0")
|
||||
implementation("com.google.zxing:core:3.5.3")
|
||||
|
||||
implementation("androidx.work:work-runtime-ktx:2.8.1")
|
||||
implementation("androidx.work:work-multiprocess:2.8.1")
|
||||
implementation(libs.mmkv.static)
|
||||
implementation(libs.gson)
|
||||
implementation(libs.rxjava)
|
||||
implementation(libs.rxandroid)
|
||||
implementation(libs.rxpermissions)
|
||||
implementation(libs.toastcompat)
|
||||
implementation(libs.editorkit)
|
||||
implementation(libs.language.base)
|
||||
implementation(libs.language.json)
|
||||
implementation(libs.quickie.bundled)
|
||||
implementation(libs.core)
|
||||
implementation(libs.work.runtime.ktx)
|
||||
implementation(libs.work.multiprocess)
|
||||
}
|
||||
@@ -10,15 +10,26 @@
|
||||
android:largeScreens="true"
|
||||
android:xlargeScreens="true" />
|
||||
|
||||
<uses-sdk android:minSdkVersion="21" tools:overrideLibrary="com.blacksquircle.ui.editorkit"/>
|
||||
<uses-sdk
|
||||
android:minSdkVersion="21"
|
||||
tools:overrideLibrary="com.blacksquircle.ui.editorkit" />
|
||||
|
||||
<uses-feature android:name="android.hardware.camera" android:required="false"/>
|
||||
<uses-feature android:name="android.hardware.camera.autofocus" android:required="false"/>
|
||||
<uses-feature android:name="android.software.leanback" android:required="false" />
|
||||
<uses-feature android:name="android.hardware.touchscreen" android:required="false" />
|
||||
<uses-feature
|
||||
android:name="android.hardware.camera"
|
||||
android:required="false" />
|
||||
<uses-feature
|
||||
android:name="android.hardware.camera.autofocus"
|
||||
android:required="false" />
|
||||
<uses-feature
|
||||
android:name="android.software.leanback"
|
||||
android:required="false" />
|
||||
<uses-feature
|
||||
android:name="android.hardware.touchscreen"
|
||||
android:required="false" />
|
||||
|
||||
<!-- https://developer.android.com/about/versions/11/privacy/package-visibility -->
|
||||
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"
|
||||
<uses-permission
|
||||
android:name="android.permission.QUERY_ALL_PACKAGES"
|
||||
tools:ignore="QueryAllPackagesPermission" />
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
|
||||
@@ -53,12 +64,14 @@
|
||||
android:theme="@style/AppThemeDayNight.NoActionBar">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
<category android:name="android.intent.category.LEANBACK_LAUNCHER" />
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="android.service.quicksettings.action.QS_TILE_PREFERENCES" />
|
||||
</intent-filter>
|
||||
|
||||
<meta-data
|
||||
android:name="android.app.shortcuts"
|
||||
android:resource="@xml/shortcuts" />
|
||||
@@ -120,8 +133,10 @@
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
|
||||
<data android:scheme="v2rayng" />
|
||||
<data android:host="install-config" />
|
||||
<data android:host="install-sub" />
|
||||
@@ -150,7 +165,8 @@
|
||||
android:value="vpn" />
|
||||
</service>
|
||||
|
||||
<service android:name=".service.V2RayProxyOnlyService"
|
||||
<service
|
||||
android:name=".service.V2RayProxyOnlyService"
|
||||
android:exported="false"
|
||||
android:label="@string/app_name"
|
||||
android:foregroundServiceType="specialUse"
|
||||
@@ -160,10 +176,10 @@
|
||||
android:value="proxy" />
|
||||
</service>
|
||||
|
||||
<service android:name=".service.V2RayTestService"
|
||||
<service
|
||||
android:name=".service.V2RayTestService"
|
||||
android:exported="false"
|
||||
android:process=":RunSoLibV2RayDaemon">
|
||||
</service>
|
||||
android:process=":RunSoLibV2RayDaemon"></service>
|
||||
|
||||
<receiver
|
||||
android:exported="true"
|
||||
|
||||
@@ -1,2 +1 @@
|
||||
geosite:cn,
|
||||
geosite:geolocation-cn
|
||||
geosite:cn
|
||||
@@ -81,7 +81,9 @@
|
||||
},
|
||||
{
|
||||
"protocol": "freedom",
|
||||
"settings": {},
|
||||
"settings": {
|
||||
"domainStrategy": "UseIP"
|
||||
},
|
||||
"tag": "direct"
|
||||
},
|
||||
{
|
||||
|
||||
@@ -16,8 +16,8 @@
|
||||
|
||||
package com.v2ray.ang.helper;
|
||||
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import androidx.recyclerview.widget.ItemTouchHelper;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
/**
|
||||
* Interface to listen for a move or dismissal event from a {@link ItemTouchHelper.Callback}.
|
||||
@@ -36,7 +36,6 @@ public interface ItemTouchHelperAdapter {
|
||||
* @param fromPosition The start position of the moved item.
|
||||
* @param toPosition Then resolved position of the moved item.
|
||||
* @return True if the item was moved to the new adapter position.
|
||||
*
|
||||
* @see RecyclerView#getAdapterPositionFor(RecyclerView.ViewHolder)
|
||||
* @see RecyclerView.ViewHolder#getAdapterPosition()
|
||||
*/
|
||||
@@ -52,7 +51,6 @@ public interface ItemTouchHelperAdapter {
|
||||
* adjusting the underlying data to reflect this removal.
|
||||
*
|
||||
* @param position The position of the item dismissed.
|
||||
*
|
||||
* @see RecyclerView#getAdapterPositionFor(RecyclerView.ViewHolder)
|
||||
* @see RecyclerView.ViewHolder#getAdapterPosition()
|
||||
*/
|
||||
|
||||
@@ -17,9 +17,10 @@
|
||||
package com.v2ray.ang.helper;
|
||||
|
||||
import android.graphics.Canvas;
|
||||
|
||||
import androidx.recyclerview.widget.GridLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import androidx.recyclerview.widget.ItemTouchHelper;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
|
||||
@@ -3,10 +3,11 @@ package com.v2ray.ang
|
||||
import android.content.Context
|
||||
import androidx.multidex.MultiDexApplication
|
||||
import androidx.work.Configuration
|
||||
import androidx.work.WorkManager
|
||||
import com.tencent.mmkv.MMKV
|
||||
import com.v2ray.ang.util.Utils
|
||||
|
||||
class AngApplication : MultiDexApplication(), Configuration.Provider {
|
||||
class AngApplication : MultiDexApplication() {
|
||||
companion object {
|
||||
//const val PREF_LAST_VERSION = "pref_last_version"
|
||||
lateinit var application: AngApplication
|
||||
@@ -17,8 +18,9 @@ class AngApplication : MultiDexApplication(), Configuration.Provider {
|
||||
application = this
|
||||
}
|
||||
|
||||
//var firstRun = false
|
||||
// private set
|
||||
private val workManagerConfiguration: Configuration = Configuration.Builder()
|
||||
.setDefaultProcessName("${BuildConfig.APPLICATION_ID}:bg")
|
||||
.build()
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
@@ -34,11 +36,7 @@ class AngApplication : MultiDexApplication(), Configuration.Provider {
|
||||
MMKV.initialize(this)
|
||||
|
||||
Utils.setNightMode(application)
|
||||
}
|
||||
|
||||
override fun getWorkManagerConfiguration(): Configuration {
|
||||
return Configuration.Builder()
|
||||
.setDefaultProcessName("${BuildConfig.APPLICATION_ID}:bg")
|
||||
.build()
|
||||
// Initialize WorkManager with the custom configuration
|
||||
WorkManager.initialize(this, workManagerConfiguration)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,22 +1,22 @@
|
||||
package com.v2ray.ang
|
||||
|
||||
/**
|
||||
*
|
||||
* App Config Const
|
||||
*/
|
||||
|
||||
object AppConfig {
|
||||
|
||||
/** The application's package name. */
|
||||
const val ANG_PACKAGE = BuildConfig.APPLICATION_ID
|
||||
|
||||
/** Directory names used in the app's file system. */
|
||||
const val DIR_ASSETS = "assets"
|
||||
const val DIR_BACKUPS = "backups"
|
||||
|
||||
// legacy
|
||||
/** Legacy configuration keys. */
|
||||
const val ANG_CONFIG = "ang_config"
|
||||
const val PREF_INAPP_BUY_IS_PREMIUM = "pref_inapp_buy_is_premium"
|
||||
|
||||
// Preferences mapped to MMKV
|
||||
/** Preferences mapped to MMKV storage. */
|
||||
const val PREF_SNIFFING_ENABLED = "pref_sniffing_enabled"
|
||||
const val PREF_ROUTE_ONLY_ENABLED = "pref_route_only_enabled"
|
||||
|
||||
const val PREF_PER_APP_PROXY = "pref_per_app_proxy"
|
||||
const val PREF_PER_APP_PROXY_SET = "pref_per_app_proxy_set"
|
||||
const val PREF_BYPASS_APPS = "pref_bypass_apps"
|
||||
@@ -24,70 +24,73 @@ object AppConfig {
|
||||
const val PREF_FAKE_DNS_ENABLED = "pref_fake_dns_enabled"
|
||||
const val PREF_LOCAL_DNS_PORT = "pref_local_dns_port"
|
||||
const val PREF_VPN_DNS = "pref_vpn_dns"
|
||||
|
||||
const val PREF_ROUTING_DOMAIN_STRATEGY = "pref_routing_domain_strategy"
|
||||
const val PREF_ROUTING_MODE = "pref_routing_mode"
|
||||
const val PREF_V2RAY_ROUTING_AGENT = "pref_v2ray_routing_agent"
|
||||
const val PREF_V2RAY_ROUTING_DIRECT = "pref_v2ray_routing_direct"
|
||||
const val PREF_V2RAY_ROUTING_BLOCKED = "pref_v2ray_routing_blocked"
|
||||
const val PREF_ROUTING_CUSTOM = "pref_routing_custom"
|
||||
|
||||
const val PREF_MUX_ENABLED = "pref_mux_enabled"
|
||||
const val PREF_MUX_CONCURRENCY = "pref_mux_concurency"
|
||||
const val PREF_MUX_XUDP_CONCURRENCY = "pref_mux_xudp_concurency"
|
||||
const val PREF_MUX_CONCURRENCY = "pref_mux_concurrency"
|
||||
const val PREF_MUX_XUDP_CONCURRENCY = "pref_mux_xudp_concurrency"
|
||||
const val PREF_MUX_XUDP_QUIC = "pref_mux_xudp_quic"
|
||||
|
||||
const val PREF_FRAGMENT_ENABLED = "pref_fragment_enabled"
|
||||
const val PREF_FRAGMENT_PACKETS = "pref_fragment_packets"
|
||||
const val PREF_FRAGMENT_LENGTH = "pref_fragment_length"
|
||||
const val PREF_FRAGMENT_INTERVAL = "pref_fragment_interval"
|
||||
|
||||
const val SUBSCRIPTION_AUTO_UPDATE = "pref_auto_update_subscription"
|
||||
const val SUBSCRIPTION_AUTO_UPDATE_INTERVAL = "pref_auto_update_interval"
|
||||
const val SUBSCRIPTION_DEFAULT_UPDATE_INTERVAL = "1440" // 24 hours
|
||||
const val SUBSCRIPTION_DEFAULT_UPDATE_INTERVAL = "1440" // Default is 24 hours
|
||||
const val SUBSCRIPTION_UPDATE_TASK_NAME = "subscription_updater"
|
||||
|
||||
const val PREF_SPEED_ENABLED = "pref_speed_enabled"
|
||||
const val PREF_CONFIRM_REMOVE = "pref_confirm_remove"
|
||||
const val PREF_START_SCAN_IMMEDIATE = "pref_start_scan_immediate"
|
||||
const val PREF_LANGUAGE = "pref_language"
|
||||
const val PREF_UI_MODE_NIGHT = "pref_ui_mode_night"
|
||||
|
||||
const val PREF_PREFER_IPV6 = "pref_prefer_ipv6"
|
||||
const val PREF_PROXY_SHARING = "pref_proxy_sharing_enabled"
|
||||
const val PREF_ALLOW_INSECURE = "pref_allow_insecure"
|
||||
const val PREF_SOCKS_PORT = "pref_socks_port"
|
||||
const val PREF_HTTP_PORT = "pref_http_port"
|
||||
|
||||
const val PREF_REMOTE_DNS = "pref_remote_dns"
|
||||
const val PREF_DOMESTIC_DNS = "pref_domestic_dns"
|
||||
const val PREF_DELAY_TEST_URL = "pref_delay_test_url"
|
||||
const val PREF_LOGLEVEL = "pref_core_loglevel"
|
||||
const val PREF_MODE = "pref_mode"
|
||||
|
||||
/** Cache keys. */
|
||||
const val CACHE_SUBSCRIPTION_ID = "cache_subscription_id"
|
||||
const val CACHE_KEYWORD_FILTER = "cache_keyword_filter"
|
||||
|
||||
//Preferences mapped to MMKV End
|
||||
|
||||
/** Protocol identifiers. */
|
||||
const val PROTOCOL_HTTP: String = "http://"
|
||||
const val PROTOCOL_HTTPS: String = "https://"
|
||||
const val PROTOCOL_FREEDOM: String = "freedom"
|
||||
|
||||
/** Broadcast actions. */
|
||||
const val BROADCAST_ACTION_SERVICE = "com.v2ray.ang.action.service"
|
||||
const val BROADCAST_ACTION_ACTIVITY = "com.v2ray.ang.action.activity"
|
||||
const val BROADCAST_ACTION_WIDGET_CLICK = "com.v2ray.ang.action.widget.click"
|
||||
|
||||
/** Tasker extras. */
|
||||
const val TASKER_EXTRA_BUNDLE = "com.twofortyfouram.locale.intent.extra.BUNDLE"
|
||||
const val TASKER_EXTRA_STRING_BLURB = "com.twofortyfouram.locale.intent.extra.BLURB"
|
||||
const val TASKER_EXTRA_BUNDLE_SWITCH = "tasker_extra_bundle_switch"
|
||||
const val TASKER_EXTRA_BUNDLE_GUID = "tasker_extra_bundle_guid"
|
||||
const val TASKER_DEFAULT_GUID = "Default"
|
||||
|
||||
/** Tags for different proxy modes. */
|
||||
const val TAG_PROXY = "proxy"
|
||||
const val TAG_DIRECT = "direct"
|
||||
const val TAG_BLOCKED = "block"
|
||||
const val TAG_FRAGMENT = "fragment"
|
||||
|
||||
/** Network-related constants. */
|
||||
const val UPLINK = "uplink"
|
||||
const val DOWNLINK = "downlink"
|
||||
|
||||
/** URLs for various resources. */
|
||||
const val androidpackagenamelistUrl =
|
||||
"https://raw.githubusercontent.com/2dust/androidpackagenamelist/master/proxy.txt"
|
||||
const val v2rayCustomRoutingListUrl =
|
||||
@@ -102,10 +105,12 @@ object AppConfig {
|
||||
const val DelayTestUrl = "https://www.gstatic.com/generate_204"
|
||||
const val DelayTestUrl2 = "https://www.google.com/generate_204"
|
||||
|
||||
/** DNS server addresses. */
|
||||
const val DNS_PROXY = "1.1.1.1"
|
||||
const val DNS_DIRECT = "223.5.5.5"
|
||||
const val DNS_VPN = "1.1.1.1"
|
||||
|
||||
/** Ports and addresses for various services. */
|
||||
const val PORT_LOCAL_DNS = "10853"
|
||||
const val PORT_SOCKS = "10808"
|
||||
const val PORT_HTTP = "10809"
|
||||
@@ -113,6 +118,7 @@ object AppConfig {
|
||||
const val WIREGUARD_LOCAL_ADDRESS_V6 = "2606:4700:110:8f81:d551:a0:532e:a2b3/128"
|
||||
const val WIREGUARD_LOCAL_MTU = "1420"
|
||||
|
||||
/** Message constants for communication. */
|
||||
const val MSG_REGISTER_CLIENT = 1
|
||||
const val MSG_STATE_RUNNING = 11
|
||||
const val MSG_STATE_NOT_RUNNING = 12
|
||||
@@ -128,4 +134,19 @@ object AppConfig {
|
||||
const val MSG_MEASURE_CONFIG = 7
|
||||
const val MSG_MEASURE_CONFIG_SUCCESS = 71
|
||||
const val MSG_MEASURE_CONFIG_CANCEL = 72
|
||||
|
||||
/** Notification channel IDs and names. */
|
||||
const val RAY_NG_CHANNEL_ID = "RAY_NG_M_CH_ID"
|
||||
const val RAY_NG_CHANNEL_NAME = "V2rayNG Background Service"
|
||||
const val SUBSCRIPTION_UPDATE_CHANNEL = "subscription_update_channel"
|
||||
const val SUBSCRIPTION_UPDATE_CHANNEL_NAME = "Subscription Update Service"
|
||||
|
||||
/** Protocols Scheme **/
|
||||
const val VMESS = "vmess://"
|
||||
const val CUSTOM = ""
|
||||
const val SHADOWSOCKS = "ss://"
|
||||
const val SOCKS = "socks://"
|
||||
const val VLESS = "vless://"
|
||||
const val TROJAN = "trojan://"
|
||||
const val WIREGUARD = "wireguard://"
|
||||
}
|
||||
|
||||
@@ -2,8 +2,10 @@ package com.v2ray.ang.dto
|
||||
|
||||
import android.graphics.drawable.Drawable
|
||||
|
||||
data class AppInfo(val appName: String,
|
||||
data class AppInfo(
|
||||
val appName: String,
|
||||
val packageName: String,
|
||||
val appIcon: Drawable,
|
||||
val isSystemApp: Boolean,
|
||||
var isSelected: Int)
|
||||
var isSelected: Int
|
||||
)
|
||||
@@ -1,13 +1,16 @@
|
||||
package com.v2ray.ang.dto
|
||||
|
||||
import com.v2ray.ang.AppConfig
|
||||
|
||||
|
||||
enum class EConfigType(val value: Int, val protocolScheme: String) {
|
||||
VMESS(1, "vmess://"),
|
||||
CUSTOM(2, ""),
|
||||
SHADOWSOCKS(3, "ss://"),
|
||||
SOCKS(4, "socks://"),
|
||||
VLESS(5, "vless://"),
|
||||
TROJAN(6, "trojan://"),
|
||||
WIREGUARD(7, "wireguard://");
|
||||
VMESS(1, AppConfig.VMESS),
|
||||
CUSTOM(2, AppConfig.CUSTOM),
|
||||
SHADOWSOCKS(3, AppConfig.SHADOWSOCKS),
|
||||
SOCKS(4, AppConfig.SOCKS),
|
||||
VLESS(5, AppConfig.VLESS),
|
||||
TROJAN(6, AppConfig.TROJAN),
|
||||
WIREGUARD(7, AppConfig.WIREGUARD);
|
||||
|
||||
companion object {
|
||||
fun fromInt(value: Int) = values().firstOrNull { it.value == value }
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
package com.v2ray.ang.dto
|
||||
|
||||
data class ProfileItem(
|
||||
val configType: EConfigType,
|
||||
var subscriptionId: String = "",
|
||||
var remarks: String = "",
|
||||
var server: String?,
|
||||
var serverPort: Int?,
|
||||
)
|
||||
@@ -1,8 +1,8 @@
|
||||
package com.v2ray.ang.dto
|
||||
|
||||
import com.v2ray.ang.AppConfig.TAG_PROXY
|
||||
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(
|
||||
@@ -23,19 +23,31 @@ data class ServerConfig(
|
||||
outboundBean = V2rayConfig.OutboundBean(
|
||||
protocol = configType.name.lowercase(),
|
||||
settings = V2rayConfig.OutboundBean.OutSettingsBean(
|
||||
vnext = listOf(V2rayConfig.OutboundBean.OutSettingsBean.VnextBean(
|
||||
users = listOf(V2rayConfig.OutboundBean.OutSettingsBean.VnextBean.UsersBean())))),
|
||||
streamSettings = V2rayConfig.OutboundBean.StreamSettingsBean()))
|
||||
vnext = listOf(
|
||||
V2rayConfig.OutboundBean.OutSettingsBean.VnextBean(
|
||||
users = listOf(V2rayConfig.OutboundBean.OutSettingsBean.VnextBean.UsersBean())
|
||||
)
|
||||
)
|
||||
),
|
||||
streamSettings = V2rayConfig.OutboundBean.StreamSettingsBean()
|
||||
)
|
||||
)
|
||||
|
||||
EConfigType.CUSTOM ->
|
||||
return ServerConfig(configType = configType)
|
||||
|
||||
EConfigType.SHADOWSOCKS, EConfigType.SOCKS, EConfigType.TROJAN ->
|
||||
return ServerConfig(
|
||||
configType = configType,
|
||||
outboundBean = V2rayConfig.OutboundBean(
|
||||
protocol = configType.name.lowercase(),
|
||||
settings = V2rayConfig.OutboundBean.OutSettingsBean(
|
||||
servers = listOf(V2rayConfig.OutboundBean.OutSettingsBean.ServersBean())),
|
||||
streamSettings = V2rayConfig.OutboundBean.StreamSettingsBean()))
|
||||
servers = listOf(V2rayConfig.OutboundBean.OutSettingsBean.ServersBean())
|
||||
),
|
||||
streamSettings = V2rayConfig.OutboundBean.StreamSettingsBean()
|
||||
)
|
||||
)
|
||||
|
||||
EConfigType.WIREGUARD ->
|
||||
return ServerConfig(
|
||||
configType = configType,
|
||||
@@ -44,7 +56,9 @@ data class ServerConfig(
|
||||
settings = V2rayConfig.OutboundBean.OutSettingsBean(
|
||||
secretKey = "",
|
||||
peers = listOf(V2rayConfig.OutboundBean.OutSettingsBean.WireGuardBean())
|
||||
)))
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
package com.v2ray.ang.dto
|
||||
|
||||
data class ServersCache(val guid: String,
|
||||
val config: ServerConfig)
|
||||
data class ServersCache(
|
||||
val guid: String,
|
||||
val profile: ProfileItem
|
||||
)
|
||||
@@ -24,7 +24,8 @@ data class V2rayConfig(
|
||||
var fakedns: Any? = null,
|
||||
val browserForwarder: Any? = null,
|
||||
var observatory: Any? = null,
|
||||
var burstObservatory: Any? = null) {
|
||||
var burstObservatory: Any? = null
|
||||
) {
|
||||
companion object {
|
||||
const val DEFAULT_PORT = 443
|
||||
const val DEFAULT_SECURITY = "auto"
|
||||
@@ -36,10 +37,12 @@ data class V2rayConfig(
|
||||
const val HTTP = "http"
|
||||
}
|
||||
|
||||
data class LogBean(val access: String,
|
||||
data class LogBean(
|
||||
val access: String,
|
||||
val error: String,
|
||||
var loglevel: String?,
|
||||
val dnsLog: Boolean? = null)
|
||||
val dnsLog: Boolean? = null
|
||||
)
|
||||
|
||||
data class InboundBean(
|
||||
var tag: String,
|
||||
@@ -49,30 +52,38 @@ data class V2rayConfig(
|
||||
val settings: Any? = null,
|
||||
val sniffing: SniffingBean?,
|
||||
val streamSettings: Any? = null,
|
||||
val allocate: Any? = null) {
|
||||
val allocate: Any? = null
|
||||
) {
|
||||
|
||||
data class InSettingsBean(val auth: String? = null,
|
||||
data class InSettingsBean(
|
||||
val auth: String? = null,
|
||||
val udp: Boolean? = null,
|
||||
val userLevel: Int? = null,
|
||||
val address: String? = null,
|
||||
val port: Int? = null,
|
||||
val network: String? = null)
|
||||
val network: String? = null
|
||||
)
|
||||
|
||||
data class SniffingBean(var enabled: Boolean,
|
||||
data class SniffingBean(
|
||||
var enabled: Boolean,
|
||||
val destOverride: ArrayList<String>,
|
||||
val metadataOnly: Boolean? = null,
|
||||
var routeOnly: Boolean? = null)
|
||||
var routeOnly: Boolean? = null
|
||||
)
|
||||
}
|
||||
|
||||
data class OutboundBean(var tag: String = "proxy",
|
||||
data class OutboundBean(
|
||||
var tag: String = "proxy",
|
||||
var protocol: String,
|
||||
var settings: OutSettingsBean? = null,
|
||||
var streamSettings: StreamSettingsBean? = null,
|
||||
val proxySettings: Any? = null,
|
||||
val sendThrough: String? = null,
|
||||
var mux: MuxBean? = MuxBean(false)) {
|
||||
var mux: MuxBean? = MuxBean(false)
|
||||
) {
|
||||
|
||||
data class OutSettingsBean(var vnext: List<VnextBean>? = null,
|
||||
data class OutSettingsBean(
|
||||
var vnext: List<VnextBean>? = null,
|
||||
var fragment: FragmentBean? = null,
|
||||
var servers: List<ServersBean>? = null,
|
||||
/*Blackhole*/
|
||||
@@ -94,23 +105,30 @@ data class V2rayConfig(
|
||||
var mtu: Int? = null
|
||||
) {
|
||||
|
||||
data class VnextBean(var address: String = "",
|
||||
data class VnextBean(
|
||||
var address: String = "",
|
||||
var port: Int = DEFAULT_PORT,
|
||||
var users: List<UsersBean>) {
|
||||
var users: List<UsersBean>
|
||||
) {
|
||||
|
||||
data class UsersBean(var id: String = "",
|
||||
data class UsersBean(
|
||||
var id: String = "",
|
||||
var alterId: Int? = null,
|
||||
var security: String = DEFAULT_SECURITY,
|
||||
var level: Int = DEFAULT_LEVEL,
|
||||
var encryption: String = "",
|
||||
var flow: String = "")
|
||||
var flow: String = ""
|
||||
)
|
||||
}
|
||||
|
||||
data class FragmentBean(var packets: String? = null,
|
||||
data class FragmentBean(
|
||||
var packets: String? = null,
|
||||
var length: String? = null,
|
||||
var interval: String? = null)
|
||||
var interval: String? = null
|
||||
)
|
||||
|
||||
data class ServersBean(var address: String = "",
|
||||
data class ServersBean(
|
||||
var address: String = "",
|
||||
var method: String = "chacha20-poly1305",
|
||||
var ota: Boolean = false,
|
||||
var password: String = "",
|
||||
@@ -119,21 +137,27 @@ data class V2rayConfig(
|
||||
val email: String? = null,
|
||||
var flow: String? = null,
|
||||
val ivCheck: Boolean? = null,
|
||||
var users: List<SocksUsersBean>? = null) {
|
||||
var users: List<SocksUsersBean>? = null
|
||||
) {
|
||||
|
||||
|
||||
data class SocksUsersBean(var user: String = "",
|
||||
data class SocksUsersBean(
|
||||
var user: String = "",
|
||||
var pass: String = "",
|
||||
var level: Int = DEFAULT_LEVEL)
|
||||
var level: Int = DEFAULT_LEVEL
|
||||
)
|
||||
}
|
||||
|
||||
data class Response(var type: String)
|
||||
|
||||
data class WireGuardBean(var publicKey: String = "",
|
||||
var endpoint: String = "")
|
||||
data class WireGuardBean(
|
||||
var publicKey: String = "",
|
||||
var endpoint: String = ""
|
||||
)
|
||||
}
|
||||
|
||||
data class StreamSettingsBean(var network: String = DEFAULT_NETWORK,
|
||||
data class StreamSettingsBean(
|
||||
var network: String = DEFAULT_NETWORK,
|
||||
var security: String = "",
|
||||
var tcpSettings: TcpSettingsBean? = null,
|
||||
var kcpSettings: KcpSettingsBean? = null,
|
||||
@@ -149,27 +173,36 @@ data class V2rayConfig(
|
||||
var sockopt: SockoptBean? = null
|
||||
) {
|
||||
|
||||
data class TcpSettingsBean(var header: HeaderBean = HeaderBean(),
|
||||
val acceptProxyProtocol: Boolean? = null) {
|
||||
data class HeaderBean(var type: String = "none",
|
||||
data class TcpSettingsBean(
|
||||
var header: HeaderBean = HeaderBean(),
|
||||
val acceptProxyProtocol: Boolean? = null
|
||||
) {
|
||||
data class HeaderBean(
|
||||
var type: String = "none",
|
||||
var request: RequestBean? = null,
|
||||
var response: Any? = null) {
|
||||
data class RequestBean(var path: List<String> = ArrayList(),
|
||||
var response: Any? = null
|
||||
) {
|
||||
data class RequestBean(
|
||||
var path: List<String> = ArrayList(),
|
||||
var headers: HeadersBean = HeadersBean(),
|
||||
val version: String? = null,
|
||||
val method: String? = null) {
|
||||
data class HeadersBean(var Host: List<String>? = ArrayList(),
|
||||
val method: String? = null
|
||||
) {
|
||||
data class HeadersBean(
|
||||
var Host: List<String>? = ArrayList(),
|
||||
@SerializedName("User-Agent")
|
||||
val userAgent: List<String>? = null,
|
||||
@SerializedName("Accept-Encoding")
|
||||
val acceptEncoding: List<String>? = null,
|
||||
val Connection: List<String>? = null,
|
||||
val Pragma: String? = null)
|
||||
val Pragma: String? = null
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data class KcpSettingsBean(var mtu: Int = 1350,
|
||||
data class KcpSettingsBean(
|
||||
var mtu: Int = 1350,
|
||||
var tti: Int = 50,
|
||||
var uplinkCapacity: Int = 12,
|
||||
var downlinkCapacity: Int = 100,
|
||||
@@ -177,37 +210,50 @@ data class V2rayConfig(
|
||||
var readBufferSize: Int = 1,
|
||||
var writeBufferSize: Int = 1,
|
||||
var header: HeaderBean = HeaderBean(),
|
||||
var seed: String? = null) {
|
||||
var seed: String? = null
|
||||
) {
|
||||
data class HeaderBean(var type: String = "none")
|
||||
}
|
||||
|
||||
data class WsSettingsBean(var path: String = "",
|
||||
data class WsSettingsBean(
|
||||
var path: String = "",
|
||||
var headers: HeadersBean = HeadersBean(),
|
||||
val maxEarlyData: Int? = null,
|
||||
val useBrowserForwarding: Boolean? = null,
|
||||
val acceptProxyProtocol: Boolean? = null) {
|
||||
val acceptProxyProtocol: Boolean? = null
|
||||
) {
|
||||
data class HeadersBean(var Host: String = "")
|
||||
}
|
||||
|
||||
data class HttpupgradeSettingsBean(var path: String = "",
|
||||
data class HttpupgradeSettingsBean(
|
||||
var path: String = "",
|
||||
var host: String = "",
|
||||
val acceptProxyProtocol: Boolean? = null)
|
||||
val acceptProxyProtocol: Boolean? = null
|
||||
)
|
||||
|
||||
data class SplithttpSettingsBean(var path: String = "",
|
||||
data class SplithttpSettingsBean(
|
||||
var path: String = "",
|
||||
var host: String = "",
|
||||
val maxUploadSize: Int? = null,
|
||||
val maxConcurrentUploads: Int? = null)
|
||||
data class HttpSettingsBean(var host: List<String> = ArrayList(),
|
||||
var path: String = "")
|
||||
val maxConcurrentUploads: Int? = null
|
||||
)
|
||||
|
||||
data class SockoptBean(var TcpNoDelay: Boolean? = null,
|
||||
data class HttpSettingsBean(
|
||||
var host: List<String> = ArrayList(),
|
||||
var path: String = ""
|
||||
)
|
||||
|
||||
data class SockoptBean(
|
||||
var TcpNoDelay: Boolean? = null,
|
||||
var tcpKeepAliveIdle: Int? = null,
|
||||
var tcpFastOpen: Boolean? = null,
|
||||
var tproxy: String? = null,
|
||||
var mark: Int? = null,
|
||||
var dialerProxy: String? = null)
|
||||
var dialerProxy: String? = null
|
||||
)
|
||||
|
||||
data class TlsSettingsBean(var allowInsecure: Boolean = false,
|
||||
data class TlsSettingsBean(
|
||||
var allowInsecure: Boolean = false,
|
||||
var serverName: String = "",
|
||||
val alpn: List<String>? = null,
|
||||
val minVersion: String? = null,
|
||||
@@ -222,24 +268,30 @@ data class V2rayConfig(
|
||||
val show: Boolean = false,
|
||||
var publicKey: String? = null,
|
||||
var shortId: String? = null,
|
||||
var spiderX: String? = null)
|
||||
var spiderX: String? = null
|
||||
)
|
||||
|
||||
data class QuicSettingBean(var security: String = "none",
|
||||
data class QuicSettingBean(
|
||||
var security: String = "none",
|
||||
var key: String = "",
|
||||
var header: HeaderBean = HeaderBean()) {
|
||||
var header: HeaderBean = HeaderBean()
|
||||
) {
|
||||
data class HeaderBean(var type: String = "none")
|
||||
}
|
||||
|
||||
data class GrpcSettingsBean(var serviceName: String = "",
|
||||
data class GrpcSettingsBean(
|
||||
var serviceName: String = "",
|
||||
var authority: String? = null,
|
||||
var multiMode: Boolean? = null,
|
||||
var idle_timeout: Int? = null,
|
||||
var health_check_timeout: Int? = null
|
||||
)
|
||||
|
||||
fun populateTransportSettings(transport: String, headerType: String?, host: String?, path: String?, seed: String?,
|
||||
fun populateTransportSettings(
|
||||
transport: String, headerType: String?, host: String?, path: String?, seed: String?,
|
||||
quicSecurity: String?, key: String?, mode: String?, serviceName: String?,
|
||||
authority: String?): String {
|
||||
authority: String?
|
||||
): String {
|
||||
var sni = ""
|
||||
network = transport
|
||||
when (network) {
|
||||
@@ -249,17 +301,18 @@ data class V2rayConfig(
|
||||
tcpSetting.header.type = HTTP
|
||||
if (!TextUtils.isEmpty(host) || !TextUtils.isEmpty(path)) {
|
||||
val requestObj = TcpSettingsBean.HeaderBean.RequestBean()
|
||||
requestObj.headers.Host = (host ?: "").split(",").map { it.trim() }.filter { it.isNotEmpty() }
|
||||
requestObj.path = (path ?: "").split(",").map { it.trim() }.filter { it.isNotEmpty() }
|
||||
requestObj.headers.Host = (host.orEmpty()).split(",").map { it.trim() }.filter { it.isNotEmpty() }
|
||||
requestObj.path = (path.orEmpty()).split(",").map { it.trim() }.filter { it.isNotEmpty() }
|
||||
tcpSetting.header.request = requestObj
|
||||
sni = requestObj.headers.Host?.getOrNull(0) ?: sni
|
||||
}
|
||||
} else {
|
||||
tcpSetting.header.type = "none"
|
||||
sni = host ?: ""
|
||||
sni = host.orEmpty()
|
||||
}
|
||||
tcpSettings = tcpSetting
|
||||
}
|
||||
|
||||
"kcp" -> {
|
||||
val kcpsetting = KcpSettingsBean()
|
||||
kcpsetting.header.type = headerType ?: "none"
|
||||
@@ -270,58 +323,66 @@ data class V2rayConfig(
|
||||
}
|
||||
kcpSettings = kcpsetting
|
||||
}
|
||||
|
||||
"ws" -> {
|
||||
val wssetting = WsSettingsBean()
|
||||
wssetting.headers.Host = host ?: ""
|
||||
wssetting.headers.Host = host.orEmpty()
|
||||
sni = wssetting.headers.Host
|
||||
wssetting.path = path ?: "/"
|
||||
wsSettings = wssetting
|
||||
}
|
||||
|
||||
"httpupgrade" -> {
|
||||
val httpupgradeSetting = HttpupgradeSettingsBean()
|
||||
httpupgradeSetting.host = host ?: ""
|
||||
httpupgradeSetting.host = host.orEmpty()
|
||||
sni = httpupgradeSetting.host
|
||||
httpupgradeSetting.path = path ?: "/"
|
||||
httpupgradeSettings = httpupgradeSetting
|
||||
}
|
||||
|
||||
"splithttp" -> {
|
||||
val splithttpSetting = SplithttpSettingsBean()
|
||||
splithttpSetting.host = host ?: ""
|
||||
splithttpSetting.host = host.orEmpty()
|
||||
sni = splithttpSetting.host
|
||||
splithttpSetting.path = path ?: "/"
|
||||
splithttpSettings = splithttpSetting
|
||||
}
|
||||
|
||||
"h2", "http" -> {
|
||||
network = "h2"
|
||||
val h2Setting = HttpSettingsBean()
|
||||
h2Setting.host = (host ?: "").split(",").map { it.trim() }.filter { it.isNotEmpty() }
|
||||
h2Setting.host = (host.orEmpty()).split(",").map { it.trim() }.filter { it.isNotEmpty() }
|
||||
sni = h2Setting.host.getOrNull(0) ?: sni
|
||||
h2Setting.path = path ?: "/"
|
||||
httpSettings = h2Setting
|
||||
}
|
||||
|
||||
"quic" -> {
|
||||
val quicsetting = QuicSettingBean()
|
||||
quicsetting.security = quicSecurity ?: "none"
|
||||
quicsetting.key = key ?: ""
|
||||
quicsetting.key = key.orEmpty()
|
||||
quicsetting.header.type = headerType ?: "none"
|
||||
quicSettings = quicsetting
|
||||
}
|
||||
|
||||
"grpc" -> {
|
||||
val grpcSetting = GrpcSettingsBean()
|
||||
grpcSetting.multiMode = mode == "multi"
|
||||
grpcSetting.serviceName = serviceName ?: ""
|
||||
grpcSetting.authority = authority ?: ""
|
||||
grpcSetting.serviceName = serviceName.orEmpty()
|
||||
grpcSetting.authority = authority.orEmpty()
|
||||
grpcSetting.idle_timeout = 60
|
||||
grpcSetting.health_check_timeout = 20
|
||||
sni = authority ?: ""
|
||||
sni = authority.orEmpty()
|
||||
grpcSettings = grpcSetting
|
||||
}
|
||||
}
|
||||
return sni
|
||||
}
|
||||
|
||||
fun populateTlsSettings(streamSecurity: String, allowInsecure: Boolean, sni: String, fingerprint: String?, alpns: String?,
|
||||
publicKey: String?, shortId: String?, spiderX: String?) {
|
||||
fun populateTlsSettings(
|
||||
streamSecurity: String, allowInsecure: Boolean, sni: String, fingerprint: String?, alpns: String?,
|
||||
publicKey: String?, shortId: String?, spiderX: String?
|
||||
) {
|
||||
security = streamSecurity
|
||||
val tlsSetting = TlsSettingsBean(
|
||||
allowInsecure = allowInsecure,
|
||||
@@ -342,18 +403,22 @@ data class V2rayConfig(
|
||||
}
|
||||
}
|
||||
|
||||
data class MuxBean(var enabled: Boolean,
|
||||
data class MuxBean(
|
||||
var enabled: Boolean,
|
||||
var concurrency: Int = 8,
|
||||
var xudpConcurrency: Int = 8,
|
||||
var xudpProxyUDP443: String = "",)
|
||||
var xudpProxyUDP443: String = "",
|
||||
)
|
||||
|
||||
fun getServerAddress(): String? {
|
||||
if (protocol.equals(EConfigType.VMESS.name, true)
|
||||
|| protocol.equals(EConfigType.VLESS.name, true)) {
|
||||
|| protocol.equals(EConfigType.VLESS.name, true)
|
||||
) {
|
||||
return settings?.vnext?.get(0)?.address
|
||||
} else if (protocol.equals(EConfigType.SHADOWSOCKS.name, true)
|
||||
|| protocol.equals(EConfigType.SOCKS.name, true)
|
||||
|| protocol.equals(EConfigType.TROJAN.name, true)) {
|
||||
|| protocol.equals(EConfigType.TROJAN.name, true)
|
||||
) {
|
||||
return settings?.servers?.get(0)?.address
|
||||
} else if (protocol.equals(EConfigType.WIREGUARD.name, true)) {
|
||||
return settings?.peers?.get(0)?.endpoint?.substringBeforeLast(":")
|
||||
@@ -363,11 +428,13 @@ data class V2rayConfig(
|
||||
|
||||
fun getServerPort(): Int? {
|
||||
if (protocol.equals(EConfigType.VMESS.name, true)
|
||||
|| protocol.equals(EConfigType.VLESS.name, true)) {
|
||||
|| protocol.equals(EConfigType.VLESS.name, true)
|
||||
) {
|
||||
return settings?.vnext?.get(0)?.port
|
||||
} else if (protocol.equals(EConfigType.SHADOWSOCKS.name, true)
|
||||
|| protocol.equals(EConfigType.SOCKS.name, true)
|
||||
|| protocol.equals(EConfigType.TROJAN.name, true)) {
|
||||
|| protocol.equals(EConfigType.TROJAN.name, true)
|
||||
) {
|
||||
return settings?.servers?.get(0)?.port
|
||||
} else if (protocol.equals(EConfigType.WIREGUARD.name, true)) {
|
||||
return settings?.peers?.get(0)?.endpoint?.substringAfterLast(":")?.toInt()
|
||||
@@ -377,10 +444,12 @@ data class V2rayConfig(
|
||||
|
||||
fun getPassword(): String? {
|
||||
if (protocol.equals(EConfigType.VMESS.name, true)
|
||||
|| protocol.equals(EConfigType.VLESS.name, true)) {
|
||||
|| protocol.equals(EConfigType.VLESS.name, true)
|
||||
) {
|
||||
return settings?.vnext?.get(0)?.users?.get(0)?.id
|
||||
} else if (protocol.equals(EConfigType.SHADOWSOCKS.name, true)
|
||||
|| protocol.equals(EConfigType.TROJAN.name, true)) {
|
||||
|| protocol.equals(EConfigType.TROJAN.name, true)
|
||||
) {
|
||||
return settings?.servers?.get(0)?.password
|
||||
} else if (protocol.equals(EConfigType.SOCKS.name, true)) {
|
||||
return settings?.servers?.get(0)?.users?.get(0)?.pass
|
||||
@@ -403,57 +472,82 @@ data class V2rayConfig(
|
||||
if (protocol.equals(EConfigType.VMESS.name, true)
|
||||
|| protocol.equals(EConfigType.VLESS.name, true)
|
||||
|| protocol.equals(EConfigType.TROJAN.name, true)
|
||||
|| protocol.equals(EConfigType.SHADOWSOCKS.name, true)) {
|
||||
|| protocol.equals(EConfigType.SHADOWSOCKS.name, true)
|
||||
) {
|
||||
val transport = streamSettings?.network ?: return null
|
||||
return when (transport) {
|
||||
"tcp" -> {
|
||||
val tcpSetting = streamSettings?.tcpSettings ?: return null
|
||||
listOf(tcpSetting.header.type,
|
||||
listOf(
|
||||
tcpSetting.header.type,
|
||||
tcpSetting.header.request?.headers?.Host?.joinToString().orEmpty(),
|
||||
tcpSetting.header.request?.path?.joinToString().orEmpty())
|
||||
tcpSetting.header.request?.path?.joinToString().orEmpty()
|
||||
)
|
||||
}
|
||||
|
||||
"kcp" -> {
|
||||
val kcpSetting = streamSettings?.kcpSettings ?: return null
|
||||
listOf(kcpSetting.header.type,
|
||||
listOf(
|
||||
kcpSetting.header.type,
|
||||
"",
|
||||
kcpSetting.seed.orEmpty())
|
||||
kcpSetting.seed.orEmpty()
|
||||
)
|
||||
}
|
||||
|
||||
"ws" -> {
|
||||
val wsSetting = streamSettings?.wsSettings ?: return null
|
||||
listOf("",
|
||||
listOf(
|
||||
"",
|
||||
wsSetting.headers.Host,
|
||||
wsSetting.path)
|
||||
wsSetting.path
|
||||
)
|
||||
}
|
||||
|
||||
"httpupgrade" -> {
|
||||
val httpupgradeSetting = streamSettings?.httpupgradeSettings ?: return null
|
||||
listOf("",
|
||||
listOf(
|
||||
"",
|
||||
httpupgradeSetting.host,
|
||||
httpupgradeSetting.path)
|
||||
httpupgradeSetting.path
|
||||
)
|
||||
}
|
||||
|
||||
"splithttp" -> {
|
||||
val splithttpSetting = streamSettings?.splithttpSettings ?: return null
|
||||
listOf("",
|
||||
listOf(
|
||||
"",
|
||||
splithttpSetting.host,
|
||||
splithttpSetting.path)
|
||||
splithttpSetting.path
|
||||
)
|
||||
}
|
||||
|
||||
"h2" -> {
|
||||
val h2Setting = streamSettings?.httpSettings ?: return null
|
||||
listOf("",
|
||||
listOf(
|
||||
"",
|
||||
h2Setting.host.joinToString(),
|
||||
h2Setting.path)
|
||||
h2Setting.path
|
||||
)
|
||||
}
|
||||
|
||||
"quic" -> {
|
||||
val quicSetting = streamSettings?.quicSettings ?: return null
|
||||
listOf(quicSetting.header.type,
|
||||
listOf(
|
||||
quicSetting.header.type,
|
||||
quicSetting.security,
|
||||
quicSetting.key)
|
||||
quicSetting.key
|
||||
)
|
||||
}
|
||||
|
||||
"grpc" -> {
|
||||
val grpcSetting = streamSettings?.grpcSettings ?: return null
|
||||
listOf(if (grpcSetting.multiMode == true) "multi" else "gun",
|
||||
grpcSetting.authority ?: "",
|
||||
grpcSetting.serviceName)
|
||||
listOf(
|
||||
if (grpcSetting.multiMode == true) "multi" else "gun",
|
||||
grpcSetting.authority.orEmpty(),
|
||||
grpcSetting.serviceName
|
||||
)
|
||||
}
|
||||
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
@@ -461,24 +555,29 @@ data class V2rayConfig(
|
||||
}
|
||||
}
|
||||
|
||||
data class DnsBean(var servers: ArrayList<Any>? = null,
|
||||
data class DnsBean(
|
||||
var servers: ArrayList<Any>? = null,
|
||||
var hosts: Map<String, Any>? = null,
|
||||
val clientIp: String? = null,
|
||||
val disableCache: Boolean? = null,
|
||||
val queryStrategy: String? = null,
|
||||
val tag: String? = null
|
||||
) {
|
||||
data class ServersBean(var address: String = "",
|
||||
data class ServersBean(
|
||||
var address: String = "",
|
||||
var port: Int? = null,
|
||||
var domains: List<String>? = null,
|
||||
var expectIPs: List<String>? = null,
|
||||
val clientIp: String? = null)
|
||||
val clientIp: String? = null
|
||||
)
|
||||
}
|
||||
|
||||
data class RoutingBean(var domainStrategy: String,
|
||||
data class RoutingBean(
|
||||
var domainStrategy: String,
|
||||
var domainMatcher: String? = null,
|
||||
var rules: ArrayList<RulesBean>,
|
||||
val balancers: List<Any>? = null) {
|
||||
val balancers: List<Any>? = null
|
||||
) {
|
||||
|
||||
data class RulesBean(
|
||||
var ip: ArrayList<String>? = null,
|
||||
@@ -497,8 +596,10 @@ data class V2rayConfig(
|
||||
)
|
||||
}
|
||||
|
||||
data class PolicyBean(var levels: Map<String, LevelBean>,
|
||||
var system: Any? = null) {
|
||||
data class PolicyBean(
|
||||
var levels: Map<String, LevelBean>,
|
||||
var system: Any? = null
|
||||
) {
|
||||
data class LevelBean(
|
||||
var handshake: Int? = null,
|
||||
var connIdle: Int? = null,
|
||||
@@ -506,14 +607,17 @@ data class V2rayConfig(
|
||||
var downlinkOnly: Int? = null,
|
||||
val statsUserUplink: Boolean? = null,
|
||||
val statsUserDownlink: Boolean? = null,
|
||||
var bufferSize: Int? = null)
|
||||
var bufferSize: Int? = null
|
||||
)
|
||||
}
|
||||
|
||||
data class FakednsBean(var ipPool: String = "198.18.0.0/15",
|
||||
var poolSize: Int = 10000) // roughly 10 times smaller than total ip pool
|
||||
data class FakednsBean(
|
||||
var ipPool: String = "198.18.0.0/15",
|
||||
var poolSize: Int = 10000
|
||||
) // roughly 10 times smaller than total ip pool
|
||||
|
||||
fun getProxyOutbound(): OutboundBean? {
|
||||
outbounds?.forEach { outbound ->
|
||||
outbounds.forEach { outbound ->
|
||||
EConfigType.entries.forEach {
|
||||
if (outbound.protocol.equals(it.name, true)) {
|
||||
return outbound
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.v2ray.ang.dto
|
||||
|
||||
data class VmessQRCode(var v: String = "",
|
||||
data class VmessQRCode(
|
||||
var v: String = "",
|
||||
var ps: String = "",
|
||||
var add: String = "",
|
||||
var port: String = "",
|
||||
@@ -14,4 +15,5 @@ data class VmessQRCode(var v: String = "",
|
||||
var tls: String = "",
|
||||
var sni: String = "",
|
||||
var alpn: String = "",
|
||||
var fp: String = "")
|
||||
var fp: String = ""
|
||||
)
|
||||
@@ -9,15 +9,15 @@ import org.json.JSONObject
|
||||
import java.net.URI
|
||||
import java.net.URLConnection
|
||||
|
||||
val Context.v2RayApplication: AngApplication
|
||||
get() = applicationContext as AngApplication
|
||||
val Context.v2RayApplication: AngApplication?
|
||||
get() = applicationContext as? AngApplication
|
||||
|
||||
fun Context.toast(message: Int) {
|
||||
ToastCompat.makeText(this, message, Toast.LENGTH_SHORT).apply { show() }
|
||||
ToastCompat.makeText(this, message, Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
|
||||
fun Context.toast(message: CharSequence) {
|
||||
ToastCompat.makeText(this, message, Toast.LENGTH_SHORT).apply { show() }
|
||||
ToastCompat.makeText(this, message, Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
|
||||
fun JSONObject.putOpt(pair: Pair<String, Any?>) {
|
||||
@@ -34,26 +34,14 @@ const val DIVISOR = 1024.0
|
||||
fun Long.toSpeedString(): String = this.toTrafficString() + "/s"
|
||||
|
||||
fun Long.toTrafficString(): String {
|
||||
if (this < THRESHOLD) {
|
||||
return "$this B"
|
||||
val units = arrayOf("B", "KB", "MB", "GB", "TB", "PB")
|
||||
var size = this.toDouble()
|
||||
var unitIndex = 0
|
||||
while (size >= THRESHOLD && unitIndex < units.size - 1) {
|
||||
size /= DIVISOR
|
||||
unitIndex++
|
||||
}
|
||||
val kb = this / DIVISOR
|
||||
if (kb < THRESHOLD) {
|
||||
return "${String.format("%.1f KB", kb)}"
|
||||
}
|
||||
val mb = kb / DIVISOR
|
||||
if (mb < THRESHOLD) {
|
||||
return "${String.format("%.1f MB", mb)}"
|
||||
}
|
||||
val gb = mb / DIVISOR
|
||||
if (gb < THRESHOLD) {
|
||||
return "${String.format("%.1f GB", gb)}"
|
||||
}
|
||||
val tb = gb / DIVISOR
|
||||
if (tb < THRESHOLD) {
|
||||
return "${String.format("%.1f TB", tb)}"
|
||||
}
|
||||
return String.format("%.1f PB", tb / DIVISOR)
|
||||
return String.format("%.1f %s", size, units[unitIndex])
|
||||
}
|
||||
|
||||
val URLConnection.responseLength: Long
|
||||
@@ -64,6 +52,6 @@ val URLConnection.responseLength: Long
|
||||
}
|
||||
|
||||
val URI.idnHost: String
|
||||
get() = host?.replace("[", "")?.replace("]", "") ?: ""
|
||||
get() = host?.replace("[", "")?.replace("]", "").orEmpty()
|
||||
|
||||
fun String.removeWhiteSpace(): String = replace("\\s+".toRegex(), "")
|
||||
@@ -35,7 +35,8 @@ class WidgetProvider : AppWidgetProvider() {
|
||||
PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
|
||||
} else {
|
||||
PendingIntent.FLAG_UPDATE_CURRENT
|
||||
})
|
||||
}
|
||||
)
|
||||
remoteViews.setOnClickPendingIntent(R.id.layout_switch, pendingIntent)
|
||||
if (isRunning) {
|
||||
remoteViews.setInt(R.id.image_switch, "setImageResource", R.drawable.ic_stop_24dp)
|
||||
@@ -65,12 +66,17 @@ class WidgetProvider : AppWidgetProvider() {
|
||||
AppWidgetManager.getInstance(context)?.let { manager ->
|
||||
when (intent.getIntExtra("key", 0)) {
|
||||
AppConfig.MSG_STATE_RUNNING, AppConfig.MSG_STATE_START_SUCCESS -> {
|
||||
updateWidgetBackground(context, manager, manager.getAppWidgetIds(ComponentName(context, WidgetProvider::class.java)),
|
||||
true)
|
||||
updateWidgetBackground(
|
||||
context, manager, manager.getAppWidgetIds(ComponentName(context, WidgetProvider::class.java)),
|
||||
true
|
||||
)
|
||||
}
|
||||
|
||||
AppConfig.MSG_STATE_NOT_RUNNING, AppConfig.MSG_STATE_START_FAILURE, AppConfig.MSG_STATE_STOP_SUCCESS -> {
|
||||
updateWidgetBackground(context, manager, manager.getAppWidgetIds(ComponentName(context, WidgetProvider::class.java)),
|
||||
false)
|
||||
updateWidgetBackground(
|
||||
context, manager, manager.getAppWidgetIds(ComponentName(context, WidgetProvider::class.java)),
|
||||
false
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,6 +58,7 @@ class QSTileService : TileService() {
|
||||
Tile.STATE_INACTIVE -> {
|
||||
Utils.startVServiceFromToggle(this)
|
||||
}
|
||||
|
||||
Tile.STATE_ACTIVE -> {
|
||||
Utils.stopVService(this)
|
||||
}
|
||||
@@ -67,22 +68,26 @@ class QSTileService : TileService() {
|
||||
private var mMsgReceive: BroadcastReceiver? = null
|
||||
|
||||
private class ReceiveMessageHandler(context: QSTileService) : BroadcastReceiver() {
|
||||
internal var mReference: SoftReference<QSTileService> = SoftReference(context)
|
||||
var mReference: SoftReference<QSTileService> = SoftReference(context)
|
||||
override fun onReceive(ctx: Context?, intent: Intent?) {
|
||||
val context = mReference.get()
|
||||
when (intent?.getIntExtra("key", 0)) {
|
||||
AppConfig.MSG_STATE_RUNNING -> {
|
||||
context?.setState(Tile.STATE_ACTIVE)
|
||||
}
|
||||
|
||||
AppConfig.MSG_STATE_NOT_RUNNING -> {
|
||||
context?.setState(Tile.STATE_INACTIVE)
|
||||
}
|
||||
|
||||
AppConfig.MSG_STATE_START_SUCCESS -> {
|
||||
context?.setState(Tile.STATE_ACTIVE)
|
||||
}
|
||||
|
||||
AppConfig.MSG_STATE_START_FAILURE -> {
|
||||
context?.setState(Tile.STATE_INACTIVE)
|
||||
}
|
||||
|
||||
AppConfig.MSG_STATE_STOP_SUCCESS -> {
|
||||
context?.setState(Tile.STATE_INACTIVE)
|
||||
}
|
||||
|
||||
@@ -11,6 +11,8 @@ import androidx.core.app.NotificationManagerCompat
|
||||
import androidx.work.CoroutineWorker
|
||||
import androidx.work.WorkerParameters
|
||||
import com.v2ray.ang.AppConfig
|
||||
import com.v2ray.ang.AppConfig.SUBSCRIPTION_UPDATE_CHANNEL
|
||||
import com.v2ray.ang.AppConfig.SUBSCRIPTION_UPDATE_CHANNEL_NAME
|
||||
import com.v2ray.ang.R
|
||||
import com.v2ray.ang.util.AngConfigManager
|
||||
import com.v2ray.ang.util.MmkvManager
|
||||
@@ -18,14 +20,13 @@ import com.v2ray.ang.util.Utils
|
||||
|
||||
object SubscriptionUpdater {
|
||||
|
||||
const val notificationChannel = "subscription_update_channel"
|
||||
|
||||
class UpdateTask(context: Context, params: WorkerParameters) :
|
||||
CoroutineWorker(context, params) {
|
||||
|
||||
private val notificationManager = NotificationManagerCompat.from(applicationContext)
|
||||
private val notification =
|
||||
NotificationCompat.Builder(applicationContext, notificationChannel)
|
||||
NotificationCompat.Builder(applicationContext, SUBSCRIPTION_UPDATE_CHANNEL)
|
||||
.setWhen(0)
|
||||
.setTicker("Update")
|
||||
.setContentTitle(context.getString(R.string.title_pref_auto_update_subscription))
|
||||
@@ -43,11 +44,11 @@ object SubscriptionUpdater {
|
||||
val subscription = i.second
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
notification.setChannelId(notificationChannel)
|
||||
notification.setChannelId(SUBSCRIPTION_UPDATE_CHANNEL)
|
||||
val channel =
|
||||
NotificationChannel(
|
||||
notificationChannel,
|
||||
"Subscription Update Service",
|
||||
SUBSCRIPTION_UPDATE_CHANNEL,
|
||||
SUBSCRIPTION_UPDATE_CHANNEL_NAME,
|
||||
NotificationManager.IMPORTANCE_MIN
|
||||
)
|
||||
notificationManager.createNotificationChannel(channel)
|
||||
|
||||
@@ -49,7 +49,7 @@ class V2RayProxyOnlyService : Service(), ServiceControl {
|
||||
@RequiresApi(Build.VERSION_CODES.N)
|
||||
override fun attachBaseContext(newBase: Context?) {
|
||||
val context = newBase?.let {
|
||||
MyContextWrapper.wrap(newBase, Utils.getLocale(newBase))
|
||||
MyContextWrapper.wrap(newBase, Utils.getLocale())
|
||||
}
|
||||
super.attachBaseContext(context)
|
||||
}
|
||||
|
||||
@@ -27,14 +27,14 @@ import com.v2ray.ang.util.MmkvManager
|
||||
import com.v2ray.ang.util.Utils
|
||||
import com.v2ray.ang.util.V2rayConfigUtil
|
||||
import go.Seq
|
||||
import io.reactivex.rxjava3.core.Observable
|
||||
import io.reactivex.rxjava3.disposables.Disposable
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.launch
|
||||
import libv2ray.Libv2ray
|
||||
import libv2ray.V2RayPoint
|
||||
import libv2ray.V2RayVPNServiceSupportsSet
|
||||
import rx.Observable
|
||||
import rx.Subscription
|
||||
import java.lang.ref.SoftReference
|
||||
import kotlin.math.min
|
||||
|
||||
@@ -59,7 +59,7 @@ object V2RayServiceManager {
|
||||
|
||||
private var lastQueryTime = 0L
|
||||
private var mBuilder: NotificationCompat.Builder? = null
|
||||
private var mSubscription: Subscription? = null
|
||||
private var mDisposable: Disposable? = null
|
||||
private var mNotificationManager: NotificationManager? = null
|
||||
|
||||
fun startV2Ray(context: Context) {
|
||||
@@ -73,7 +73,7 @@ object V2RayServiceManager {
|
||||
} else {
|
||||
context.toast(R.string.toast_services_start)
|
||||
}
|
||||
val intent = if (settingsStorage?.decodeString(AppConfig.PREF_MODE) ?: "VPN" == "VPN") {
|
||||
val intent = if ((settingsStorage?.decodeString(AppConfig.PREF_MODE) ?: "VPN") == "VPN") {
|
||||
Intent(context.applicationContext, V2RayVpnService::class.java)
|
||||
} else {
|
||||
Intent(context.applicationContext, V2RayProxyOnlyService::class.java)
|
||||
@@ -175,7 +175,7 @@ object V2RayServiceManager {
|
||||
val service = serviceControl?.get()?.getService() ?: return
|
||||
|
||||
if (v2rayPoint.isRunning) {
|
||||
GlobalScope.launch(Dispatchers.Default) {
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
try {
|
||||
v2rayPoint.stopLoop()
|
||||
} catch (e: Exception) {
|
||||
@@ -206,18 +206,23 @@ object V2RayServiceManager {
|
||||
MessageUtil.sendMsg2UI(serviceControl.getService(), AppConfig.MSG_STATE_NOT_RUNNING, "")
|
||||
}
|
||||
}
|
||||
|
||||
AppConfig.MSG_UNREGISTER_CLIENT -> {
|
||||
// nothing to do
|
||||
}
|
||||
|
||||
AppConfig.MSG_STATE_START -> {
|
||||
// nothing to do
|
||||
}
|
||||
|
||||
AppConfig.MSG_STATE_STOP -> {
|
||||
serviceControl.stopService()
|
||||
}
|
||||
|
||||
AppConfig.MSG_STATE_RESTART -> {
|
||||
startV2rayPoint()
|
||||
}
|
||||
|
||||
AppConfig.MSG_MEASURE_DELAY -> {
|
||||
measureV2rayDelay()
|
||||
}
|
||||
@@ -228,6 +233,7 @@ object V2RayServiceManager {
|
||||
Log.d(ANG_PACKAGE, "SCREEN_OFF, stop querying stats")
|
||||
stopSpeedNotification()
|
||||
}
|
||||
|
||||
Intent.ACTION_SCREEN_ON -> {
|
||||
Log.d(ANG_PACKAGE, "SCREEN_ON, start querying stats")
|
||||
startSpeedNotification()
|
||||
@@ -237,7 +243,7 @@ object V2RayServiceManager {
|
||||
}
|
||||
|
||||
private fun measureV2rayDelay() {
|
||||
GlobalScope.launch(Dispatchers.IO) {
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
val service = serviceControl?.get()?.getService() ?: return@launch
|
||||
var time = -1L
|
||||
var errstr = ""
|
||||
@@ -270,25 +276,29 @@ object V2RayServiceManager {
|
||||
private fun showNotification() {
|
||||
val service = serviceControl?.get()?.getService() ?: return
|
||||
val startMainIntent = Intent(service, MainActivity::class.java)
|
||||
val contentPendingIntent = PendingIntent.getActivity(service,
|
||||
val contentPendingIntent = PendingIntent.getActivity(
|
||||
service,
|
||||
NOTIFICATION_PENDING_INTENT_CONTENT, startMainIntent,
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
|
||||
} else {
|
||||
PendingIntent.FLAG_UPDATE_CURRENT
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
val stopV2RayIntent = Intent(AppConfig.BROADCAST_ACTION_SERVICE)
|
||||
stopV2RayIntent.`package` = ANG_PACKAGE
|
||||
stopV2RayIntent.putExtra("key", AppConfig.MSG_STATE_STOP)
|
||||
|
||||
val stopV2RayPendingIntent = PendingIntent.getBroadcast(service,
|
||||
val stopV2RayPendingIntent = PendingIntent.getBroadcast(
|
||||
service,
|
||||
NOTIFICATION_PENDING_INTENT_STOP_V2RAY, stopV2RayIntent,
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
|
||||
} else {
|
||||
PendingIntent.FLAG_UPDATE_CURRENT
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
val channelId =
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
@@ -307,9 +317,11 @@ object V2RayServiceManager {
|
||||
.setShowWhen(false)
|
||||
.setOnlyAlertOnce(true)
|
||||
.setContentIntent(contentPendingIntent)
|
||||
.addAction(R.drawable.ic_delete_24dp,
|
||||
.addAction(
|
||||
R.drawable.ic_delete_24dp,
|
||||
service.getString(R.string.notification_action_stop_v2ray),
|
||||
stopV2RayPendingIntent)
|
||||
stopV2RayPendingIntent
|
||||
)
|
||||
//.build()
|
||||
|
||||
//mBuilder?.setDefaults(NotificationCompat.FLAG_ONLY_ALERT_ONCE) //取消震动,铃声其他都不好使
|
||||
@@ -319,10 +331,12 @@ object V2RayServiceManager {
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.O)
|
||||
private fun createNotificationChannel(): String {
|
||||
val channelId = "RAY_NG_M_CH_ID"
|
||||
val channelName = "V2rayNG Background Service"
|
||||
val chan = NotificationChannel(channelId,
|
||||
channelName, NotificationManager.IMPORTANCE_HIGH)
|
||||
val channelId = AppConfig.RAY_NG_CHANNEL_ID
|
||||
val channelName = AppConfig.RAY_NG_CHANNEL_NAME
|
||||
val chan = NotificationChannel(
|
||||
channelId,
|
||||
channelName, NotificationManager.IMPORTANCE_HIGH
|
||||
)
|
||||
chan.lightColor = Color.DKGRAY
|
||||
chan.importance = NotificationManager.IMPORTANCE_NONE
|
||||
chan.lockscreenVisibility = Notification.VISIBILITY_PRIVATE
|
||||
@@ -334,8 +348,8 @@ object V2RayServiceManager {
|
||||
val service = serviceControl?.get()?.getService() ?: return
|
||||
service.stopForeground(true)
|
||||
mBuilder = null
|
||||
mSubscription?.unsubscribe()
|
||||
mSubscription = null
|
||||
mDisposable?.dispose()
|
||||
mDisposable = null
|
||||
}
|
||||
|
||||
private fun updateNotification(contentText: String?, proxyTraffic: Long, directTraffic: Long) {
|
||||
@@ -362,36 +376,39 @@ object V2RayServiceManager {
|
||||
}
|
||||
|
||||
private fun startSpeedNotification() {
|
||||
if (mSubscription == null &&
|
||||
if (mDisposable == null &&
|
||||
v2rayPoint.isRunning &&
|
||||
settingsStorage?.decodeBool(AppConfig.PREF_SPEED_ENABLED) == true) {
|
||||
settingsStorage?.decodeBool(AppConfig.PREF_SPEED_ENABLED) == true
|
||||
) {
|
||||
var lastZeroSpeed = false
|
||||
val outboundTags = currentConfig?.getAllOutboundTags()
|
||||
outboundTags?.remove(TAG_DIRECT)
|
||||
|
||||
mSubscription = Observable.interval(3, java.util.concurrent.TimeUnit.SECONDS)
|
||||
mDisposable = Observable.interval(3, java.util.concurrent.TimeUnit.SECONDS)
|
||||
.subscribe {
|
||||
val queryTime = System.currentTimeMillis()
|
||||
val sinceLastQueryInSeconds = (queryTime - lastQueryTime) / 1000.0
|
||||
var proxyTotal = 0L
|
||||
val text = StringBuilder()
|
||||
outboundTags?.forEach {
|
||||
val up = v2rayPoint.queryStats(it, "uplink")
|
||||
val down = v2rayPoint.queryStats(it, "downlink")
|
||||
val up = v2rayPoint.queryStats(it, AppConfig.UPLINK)
|
||||
val down = v2rayPoint.queryStats(it, AppConfig.DOWNLINK)
|
||||
if (up + down > 0) {
|
||||
appendSpeedString(text, it, up / sinceLastQueryInSeconds, down / sinceLastQueryInSeconds)
|
||||
proxyTotal += up + down
|
||||
}
|
||||
}
|
||||
val directUplink = v2rayPoint.queryStats(TAG_DIRECT, "uplink")
|
||||
val directDownlink = v2rayPoint.queryStats(TAG_DIRECT, "downlink")
|
||||
val directUplink = v2rayPoint.queryStats(TAG_DIRECT, AppConfig.UPLINK)
|
||||
val directDownlink = v2rayPoint.queryStats(TAG_DIRECT, AppConfig.DOWNLINK)
|
||||
val zeroSpeed = proxyTotal == 0L && directUplink == 0L && directDownlink == 0L
|
||||
if (!zeroSpeed || !lastZeroSpeed) {
|
||||
if (proxyTotal == 0L) {
|
||||
appendSpeedString(text, outboundTags?.firstOrNull(), 0.0, 0.0)
|
||||
}
|
||||
appendSpeedString(text, TAG_DIRECT, directUplink / sinceLastQueryInSeconds,
|
||||
directDownlink / sinceLastQueryInSeconds)
|
||||
appendSpeedString(
|
||||
text, TAG_DIRECT, directUplink / sinceLastQueryInSeconds,
|
||||
directDownlink / sinceLastQueryInSeconds
|
||||
)
|
||||
updateNotification(text.toString(), proxyTotal, directDownlink + directUplink)
|
||||
}
|
||||
lastZeroSpeed = zeroSpeed
|
||||
@@ -411,9 +428,9 @@ object V2RayServiceManager {
|
||||
}
|
||||
|
||||
private fun stopSpeedNotification() {
|
||||
if (mSubscription != null) {
|
||||
mSubscription?.unsubscribe() //stop queryStats
|
||||
mSubscription = null
|
||||
if (mDisposable != null) {
|
||||
mDisposable?.dispose() //stop queryStats
|
||||
mDisposable = null
|
||||
updateNotification(currentConfig?.remarks, 0, 0)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,11 @@ import com.v2ray.ang.util.MessageUtil
|
||||
import com.v2ray.ang.util.SpeedtestUtil
|
||||
import com.v2ray.ang.util.Utils
|
||||
import go.Seq
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.asCoroutineDispatcher
|
||||
import kotlinx.coroutines.cancelChildren
|
||||
import kotlinx.coroutines.launch
|
||||
import libv2ray.Libv2ray
|
||||
import java.util.concurrent.Executors
|
||||
|
||||
@@ -32,6 +36,7 @@ class V2RayTestService : Service() {
|
||||
MessageUtil.sendMsg2UI(this@V2RayTestService, MSG_MEASURE_CONFIG_SUCCESS, Pair(contentPair.first, result))
|
||||
}
|
||||
}
|
||||
|
||||
MSG_MEASURE_CONFIG_CANCEL -> {
|
||||
realTestScope.coroutineContext[Job]?.cancelChildren()
|
||||
}
|
||||
|
||||
@@ -4,7 +4,13 @@ import android.app.Service
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.pm.PackageManager
|
||||
import android.net.*
|
||||
import android.net.ConnectivityManager
|
||||
import android.net.LocalSocket
|
||||
import android.net.LocalSocketAddress
|
||||
import android.net.Network
|
||||
import android.net.NetworkCapabilities
|
||||
import android.net.NetworkRequest
|
||||
import android.net.VpnService
|
||||
import android.os.Build
|
||||
import android.os.ParcelFileDescriptor
|
||||
import android.os.StrictMode
|
||||
@@ -17,8 +23,8 @@ import com.v2ray.ang.dto.ERoutingMode
|
||||
import com.v2ray.ang.util.MmkvManager
|
||||
import com.v2ray.ang.util.MyContextWrapper
|
||||
import com.v2ray.ang.util.Utils
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.launch
|
||||
import java.io.File
|
||||
import java.lang.ref.SoftReference
|
||||
@@ -196,14 +202,16 @@ class V2RayVpnService : VpnService(), ServiceControl {
|
||||
|
||||
private fun runTun2socks() {
|
||||
val socksPort = Utils.parseInt(settingsStorage?.decodeString(AppConfig.PREF_SOCKS_PORT), AppConfig.PORT_SOCKS.toInt())
|
||||
val cmd = arrayListOf(File(applicationContext.applicationInfo.nativeLibraryDir, TUN2SOCKS).absolutePath,
|
||||
val cmd = arrayListOf(
|
||||
File(applicationContext.applicationInfo.nativeLibraryDir, TUN2SOCKS).absolutePath,
|
||||
"--netif-ipaddr", PRIVATE_VLAN4_ROUTER,
|
||||
"--netif-netmask", "255.255.255.252",
|
||||
"--socks-server-addr", "127.0.0.1:${socksPort}",
|
||||
"--tunmtu", VPN_MTU.toString(),
|
||||
"--sock-path", "sock_path",//File(applicationContext.filesDir, "sock_path").absolutePath,
|
||||
"--enable-udprelay",
|
||||
"--loglevel", "notice")
|
||||
"--loglevel", "notice"
|
||||
)
|
||||
|
||||
if (settingsStorage?.decodeBool(AppConfig.PREF_PREFER_IPV6) == true) {
|
||||
cmd.add("--netif-ip6addr")
|
||||
@@ -244,7 +252,7 @@ class V2RayVpnService : VpnService(), ServiceControl {
|
||||
val path = File(applicationContext.filesDir, "sock_path").absolutePath
|
||||
Log.d(packageName, path)
|
||||
|
||||
GlobalScope.launch(Dispatchers.IO) {
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
var tries = 0
|
||||
while (true) try {
|
||||
Thread.sleep(50L shl tries)
|
||||
@@ -274,7 +282,7 @@ class V2RayVpnService : VpnService(), ServiceControl {
|
||||
// val emptyInfo = VpnNetworkInfo()
|
||||
// val info = loadVpnNetworkInfo(configName, emptyInfo)!! + (lastNetworkInfo ?: emptyInfo)
|
||||
// saveVpnNetworkInfo(configName, info)
|
||||
isRunning = false;
|
||||
isRunning = false
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
||||
try {
|
||||
connectivity.unregisterNetworkCallback(defaultNetworkCallback)
|
||||
@@ -327,7 +335,7 @@ class V2RayVpnService : VpnService(), ServiceControl {
|
||||
@RequiresApi(Build.VERSION_CODES.N)
|
||||
override fun attachBaseContext(newBase: Context?) {
|
||||
val context = newBase?.let {
|
||||
MyContextWrapper.wrap(newBase, Utils.getLocale(newBase))
|
||||
MyContextWrapper.wrap(newBase, Utils.getLocale())
|
||||
}
|
||||
super.attachBaseContext(context)
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ import android.os.Build
|
||||
import android.os.Bundle
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.core.content.FileProvider
|
||||
import com.tbruyelle.rxpermissions.RxPermissions
|
||||
import com.tbruyelle.rxpermissions3.RxPermissions
|
||||
import com.tencent.mmkv.MMKV
|
||||
import com.v2ray.ang.AppConfig
|
||||
import com.v2ray.ang.BuildConfig
|
||||
@@ -21,14 +21,12 @@ import java.text.SimpleDateFormat
|
||||
import java.util.Locale
|
||||
|
||||
class AboutActivity : BaseActivity() {
|
||||
private lateinit var binding: ActivityAboutBinding
|
||||
private val binding by lazy { ActivityAboutBinding.inflate(layoutInflater) }
|
||||
private val extDir by lazy { File(Utils.backupPath(this)) }
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
binding = ActivityAboutBinding.inflate(layoutInflater)
|
||||
val view = binding.root
|
||||
setContentView(view)
|
||||
setContentView(binding.root)
|
||||
|
||||
title = getString(R.string.title_about)
|
||||
|
||||
|
||||
@@ -23,16 +23,19 @@ abstract class BaseActivity : AppCompatActivity() {
|
||||
|
||||
override fun onOptionsItemSelected(item: MenuItem) = when (item.itemId) {
|
||||
android.R.id.home -> {
|
||||
onBackPressed()
|
||||
// Handles the home button press by delegating to the onBackPressedDispatcher.
|
||||
// This ensures consistent back navigation behavior.
|
||||
onBackPressedDispatcher.onBackPressed()
|
||||
true
|
||||
}
|
||||
|
||||
else -> super.onOptionsItemSelected(item)
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.N)
|
||||
override fun attachBaseContext(newBase: Context?) {
|
||||
val context = newBase?.let {
|
||||
MyContextWrapper.wrap(newBase, Utils.getLocale(newBase))
|
||||
MyContextWrapper.wrap(newBase, Utils.getLocale())
|
||||
}
|
||||
super.attachBaseContext(context)
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
package com.v2ray.ang.ui
|
||||
|
||||
import android.os.Bundle
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import android.os.Bundle
|
||||
import android.text.method.ScrollingMovementMethod
|
||||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
@@ -14,20 +14,18 @@ import com.v2ray.ang.databinding.ActivityLogcatBinding
|
||||
import com.v2ray.ang.extension.toast
|
||||
import com.v2ray.ang.util.Utils
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.io.IOException
|
||||
import java.util.LinkedHashSet
|
||||
|
||||
class LogcatActivity : BaseActivity() {
|
||||
private lateinit var binding: ActivityLogcatBinding
|
||||
private val binding by lazy {
|
||||
ActivityLogcatBinding.inflate(layoutInflater)
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
binding = ActivityLogcatBinding.inflate(layoutInflater)
|
||||
val view = binding.root
|
||||
setContentView(view)
|
||||
setContentView(binding.root)
|
||||
|
||||
title = getString(R.string.title_logcat)
|
||||
|
||||
@@ -44,9 +42,11 @@ class LogcatActivity : BaseActivity() {
|
||||
val lst = LinkedHashSet<String>()
|
||||
lst.add("logcat")
|
||||
lst.add("-c")
|
||||
withContext(Dispatchers.IO) {
|
||||
val process = Runtime.getRuntime().exec(lst.toTypedArray())
|
||||
process.waitFor()
|
||||
}
|
||||
}
|
||||
val lst = LinkedHashSet<String>()
|
||||
lst.add("logcat")
|
||||
lst.add("-d")
|
||||
@@ -54,7 +54,9 @@ class LogcatActivity : BaseActivity() {
|
||||
lst.add("time")
|
||||
lst.add("-s")
|
||||
lst.add("GoLog,tun2socks,${ANG_PACKAGE},AndroidRuntime,System.err")
|
||||
val process = Runtime.getRuntime().exec(lst.toTypedArray())
|
||||
val process = withContext(Dispatchers.IO) {
|
||||
Runtime.getRuntime().exec(lst.toTypedArray())
|
||||
}
|
||||
// val bufferedReader = BufferedReader(
|
||||
// InputStreamReader(process.inputStream))
|
||||
// val allText = bufferedReader.use(BufferedReader::readText)
|
||||
@@ -82,10 +84,12 @@ class LogcatActivity : BaseActivity() {
|
||||
toast(R.string.toast_success)
|
||||
true
|
||||
}
|
||||
|
||||
R.id.clear_all -> {
|
||||
logcat(true)
|
||||
true
|
||||
}
|
||||
|
||||
else -> super.onOptionsItemSelected(item)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,18 +18,19 @@ import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.activity.viewModels
|
||||
import androidx.appcompat.app.ActionBarDrawerToggle
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.appcompat.widget.SearchView
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.view.GravityCompat
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.recyclerview.widget.ItemTouchHelper
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import com.google.android.material.navigation.NavigationView
|
||||
import com.tbruyelle.rxpermissions.RxPermissions
|
||||
import com.tencent.mmkv.MMKV
|
||||
import com.google.android.material.tabs.TabLayout
|
||||
import com.tbruyelle.rxpermissions3.RxPermissions
|
||||
import com.v2ray.ang.AppConfig
|
||||
import com.v2ray.ang.R
|
||||
import com.v2ray.ang.databinding.ActivityMainBinding
|
||||
import com.v2ray.ang.databinding.LayoutProgressBinding
|
||||
import com.v2ray.ang.dto.EConfigType
|
||||
import com.v2ray.ang.extension.toast
|
||||
import com.v2ray.ang.helper.SimpleItemTouchHelperCallback
|
||||
@@ -38,40 +39,55 @@ import com.v2ray.ang.util.AngConfigManager
|
||||
import com.v2ray.ang.util.MmkvManager
|
||||
import com.v2ray.ang.util.Utils
|
||||
import com.v2ray.ang.viewmodel.MainViewModel
|
||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
|
||||
import io.reactivex.rxjava3.core.Observable
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import me.drakeet.support.toast.ToastCompat
|
||||
import rx.Observable
|
||||
import rx.android.schedulers.AndroidSchedulers
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedListener {
|
||||
private lateinit var binding: ActivityMainBinding
|
||||
private val binding by lazy {
|
||||
ActivityMainBinding.inflate(layoutInflater)
|
||||
}
|
||||
|
||||
private val adapter by lazy { MainRecyclerAdapter(this) }
|
||||
private val mainStorage by lazy { MMKV.mmkvWithID(MmkvManager.ID_MAIN, MMKV.MULTI_PROCESS_MODE) }
|
||||
private val settingsStorage by lazy { MMKV.mmkvWithID(MmkvManager.ID_SETTING, MMKV.MULTI_PROCESS_MODE) }
|
||||
private val requestVpnPermission = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
|
||||
if (it.resultCode == RESULT_OK) {
|
||||
startV2Ray()
|
||||
}
|
||||
}
|
||||
private val requestSubSettingActivity = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
|
||||
initGroupTab()
|
||||
}
|
||||
private val tabGroupListener = object : TabLayout.OnTabSelectedListener {
|
||||
override fun onTabSelected(tab: TabLayout.Tab?) {
|
||||
val selectId = tab?.tag.toString()
|
||||
if (selectId != mainViewModel.subscriptionId) {
|
||||
mainViewModel.subscriptionIdChanged(selectId)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onTabUnselected(tab: TabLayout.Tab?) {
|
||||
}
|
||||
|
||||
override fun onTabReselected(tab: TabLayout.Tab?) {
|
||||
}
|
||||
}
|
||||
private var mItemTouchHelper: ItemTouchHelper? = null
|
||||
val mainViewModel: MainViewModel by viewModels()
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
binding = ActivityMainBinding.inflate(layoutInflater)
|
||||
val view = binding.root
|
||||
setContentView(view)
|
||||
setContentView(binding.root)
|
||||
title = getString(R.string.title_server)
|
||||
setSupportActionBar(binding.toolbar)
|
||||
|
||||
binding.fab.setOnClickListener {
|
||||
if (mainViewModel.isRunning.value == true) {
|
||||
Utils.stopVService(this)
|
||||
} else if ((settingsStorage?.decodeString(AppConfig.PREF_MODE) ?: "VPN") == "VPN") {
|
||||
} else if ((MmkvManager.settingsStorage?.decodeString(AppConfig.PREF_MODE) ?: "VPN") == "VPN") {
|
||||
val intent = VpnService.prepare(this)
|
||||
if (intent == null) {
|
||||
startV2Ray()
|
||||
@@ -101,14 +117,14 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
|
||||
|
||||
|
||||
val toggle = ActionBarDrawerToggle(
|
||||
this, binding.drawerLayout, binding.toolbar, R.string.navigation_drawer_open, R.string.navigation_drawer_close)
|
||||
this, binding.drawerLayout, binding.toolbar, R.string.navigation_drawer_open, R.string.navigation_drawer_close
|
||||
)
|
||||
binding.drawerLayout.addDrawerListener(toggle)
|
||||
toggle.syncState()
|
||||
binding.navView.setNavigationItemSelectedListener(this)
|
||||
|
||||
|
||||
initGroupTab()
|
||||
setupViewModel()
|
||||
mainViewModel.copyAssets(assets)
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
RxPermissions(this)
|
||||
@@ -155,10 +171,34 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
|
||||
}
|
||||
}
|
||||
mainViewModel.startListenBroadcast()
|
||||
mainViewModel.copyAssets(assets)
|
||||
}
|
||||
|
||||
private fun initGroupTab() {
|
||||
binding.tabGroup.removeOnTabSelectedListener(tabGroupListener)
|
||||
binding.tabGroup.removeAllTabs()
|
||||
binding.tabGroup.isVisible = false
|
||||
|
||||
val (listId, listRemarks) = mainViewModel.getSubscriptions(this)
|
||||
if (listId == null || listRemarks == null) {
|
||||
return
|
||||
}
|
||||
|
||||
for (it in listRemarks.indices) {
|
||||
val tab = binding.tabGroup.newTab()
|
||||
tab.text = listRemarks[it]
|
||||
tab.tag = listId[it]
|
||||
binding.tabGroup.addTab(tab)
|
||||
}
|
||||
val selectIndex =
|
||||
listId.indexOf(mainViewModel.subscriptionId).takeIf { it >= 0 } ?: (listId.count() - 1)
|
||||
binding.tabGroup.selectTab(binding.tabGroup.getTabAt(selectIndex))
|
||||
binding.tabGroup.addOnTabSelectedListener(tabGroupListener)
|
||||
binding.tabGroup.isVisible = true
|
||||
}
|
||||
|
||||
fun startV2Ray() {
|
||||
if (mainStorage?.decodeString(MmkvManager.KEY_SELECTED_SERVER).isNullOrEmpty()) {
|
||||
if (MmkvManager.mainStorage?.decodeString(MmkvManager.KEY_SELECTED_SERVER).isNullOrEmpty()) {
|
||||
return
|
||||
}
|
||||
V2RayServiceManager.startV2Ray(this)
|
||||
@@ -186,7 +226,25 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
|
||||
|
||||
override fun onCreateOptionsMenu(menu: Menu): Boolean {
|
||||
menuInflater.inflate(R.menu.menu_main, menu)
|
||||
return true
|
||||
|
||||
val searchItem = menu.findItem(R.id.search_view)
|
||||
if (searchItem != null) {
|
||||
val searchView = searchItem.actionView as SearchView
|
||||
searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
|
||||
override fun onQueryTextSubmit(query: String?): Boolean = false
|
||||
|
||||
override fun onQueryTextChange(newText: String?): Boolean {
|
||||
mainViewModel.filterConfig(newText.orEmpty())
|
||||
return false
|
||||
}
|
||||
})
|
||||
|
||||
searchView.setOnCloseListener {
|
||||
mainViewModel.filterConfig("")
|
||||
false
|
||||
}
|
||||
}
|
||||
return super.onCreateOptionsMenu(menu)
|
||||
}
|
||||
|
||||
override fun onOptionsItemSelected(item: MenuItem) = when (item.itemId) {
|
||||
@@ -194,46 +252,57 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
|
||||
importQRcode(true)
|
||||
true
|
||||
}
|
||||
|
||||
R.id.import_clipboard -> {
|
||||
importClipboard()
|
||||
true
|
||||
}
|
||||
|
||||
R.id.import_manually_vmess -> {
|
||||
importManually(EConfigType.VMESS.value)
|
||||
true
|
||||
}
|
||||
|
||||
R.id.import_manually_vless -> {
|
||||
importManually(EConfigType.VLESS.value)
|
||||
true
|
||||
}
|
||||
|
||||
R.id.import_manually_ss -> {
|
||||
importManually(EConfigType.SHADOWSOCKS.value)
|
||||
true
|
||||
}
|
||||
|
||||
R.id.import_manually_socks -> {
|
||||
importManually(EConfigType.SOCKS.value)
|
||||
true
|
||||
}
|
||||
|
||||
R.id.import_manually_trojan -> {
|
||||
importManually(EConfigType.TROJAN.value)
|
||||
true
|
||||
}
|
||||
|
||||
R.id.import_manually_wireguard -> {
|
||||
importManually(EConfigType.WIREGUARD.value)
|
||||
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
|
||||
@@ -245,11 +314,18 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
|
||||
}
|
||||
|
||||
R.id.export_all -> {
|
||||
if (AngConfigManager.shareNonCustomConfigsToClipboard(this, mainViewModel.serverList) == 0) {
|
||||
binding.pbWaiting.show()
|
||||
lifecycleScope.launch(Dispatchers.IO) {
|
||||
val ret = mainViewModel.exportAllServer()
|
||||
launch(Dispatchers.Main) {
|
||||
if (ret == 0)
|
||||
toast(R.string.toast_success)
|
||||
} else {
|
||||
else
|
||||
toast(R.string.toast_failure)
|
||||
binding.pbWaiting.hide()
|
||||
}
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
@@ -271,8 +347,14 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
|
||||
R.id.del_all_config -> {
|
||||
AlertDialog.Builder(this).setMessage(R.string.del_config_comfirm)
|
||||
.setPositiveButton(android.R.string.ok) { _, _ ->
|
||||
MmkvManager.removeAllServer()
|
||||
binding.pbWaiting.show()
|
||||
lifecycleScope.launch(Dispatchers.IO) {
|
||||
mainViewModel.removeAllServer()
|
||||
launch(Dispatchers.Main) {
|
||||
mainViewModel.reloadServerList()
|
||||
binding.pbWaiting.hide()
|
||||
}
|
||||
}
|
||||
}
|
||||
.setNegativeButton(android.R.string.no) { _, _ ->
|
||||
//do noting
|
||||
@@ -280,11 +362,19 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
|
||||
.show()
|
||||
true
|
||||
}
|
||||
|
||||
R.id.del_duplicate_config -> {
|
||||
AlertDialog.Builder(this).setMessage(R.string.del_config_comfirm)
|
||||
.setPositiveButton(android.R.string.ok) { _, _ ->
|
||||
mainViewModel.removeDuplicateServer()
|
||||
binding.pbWaiting.show()
|
||||
lifecycleScope.launch(Dispatchers.IO) {
|
||||
val ret = mainViewModel.removeDuplicateServer()
|
||||
launch(Dispatchers.Main) {
|
||||
mainViewModel.reloadServerList()
|
||||
toast(getString(R.string.title_del_duplicate_config_count, ret))
|
||||
binding.pbWaiting.hide()
|
||||
}
|
||||
}
|
||||
}
|
||||
.setNegativeButton(android.R.string.no) { _, _ ->
|
||||
//do noting
|
||||
@@ -292,11 +382,18 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
|
||||
.show()
|
||||
true
|
||||
}
|
||||
|
||||
R.id.del_invalid_config -> {
|
||||
AlertDialog.Builder(this).setMessage(R.string.del_config_comfirm)
|
||||
AlertDialog.Builder(this).setMessage(R.string.del_invalid_config_comfirm)
|
||||
.setPositiveButton(android.R.string.ok) { _, _ ->
|
||||
MmkvManager.removeInvalidServer()
|
||||
binding.pbWaiting.show()
|
||||
lifecycleScope.launch(Dispatchers.IO) {
|
||||
mainViewModel.removeInvalidServer()
|
||||
launch(Dispatchers.Main) {
|
||||
mainViewModel.reloadServerList()
|
||||
binding.pbWaiting.hide()
|
||||
}
|
||||
}
|
||||
}
|
||||
.setNegativeButton(android.R.string.no) { _, _ ->
|
||||
//do noting
|
||||
@@ -304,13 +401,16 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
|
||||
.show()
|
||||
true
|
||||
}
|
||||
|
||||
R.id.sort_by_test_results -> {
|
||||
MmkvManager.sortByTestResults()
|
||||
binding.pbWaiting.show()
|
||||
lifecycleScope.launch(Dispatchers.IO) {
|
||||
mainViewModel.sortByTestResults()
|
||||
launch(Dispatchers.Main) {
|
||||
mainViewModel.reloadServerList()
|
||||
true
|
||||
binding.pbWaiting.hide()
|
||||
}
|
||||
}
|
||||
R.id.filter_config -> {
|
||||
mainViewModel.filterConfig(this)
|
||||
true
|
||||
}
|
||||
|
||||
@@ -378,22 +478,26 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
|
||||
}
|
||||
|
||||
private fun importBatchConfig(server: String?) {
|
||||
val dialog = AlertDialog.Builder(this)
|
||||
.setView(LayoutProgressBinding.inflate(layoutInflater).root)
|
||||
.setCancelable(false)
|
||||
.show()
|
||||
// val dialog = AlertDialog.Builder(this)
|
||||
// .setView(LayoutProgressBinding.inflate(layoutInflater).root)
|
||||
// .setCancelable(false)
|
||||
// .show()
|
||||
binding.pbWaiting.show()
|
||||
|
||||
lifecycleScope.launch(Dispatchers.IO) {
|
||||
val count = AngConfigManager.importBatchConfig(server, mainViewModel.subscriptionId, true)
|
||||
val (count, countSub) = AngConfigManager.importBatchConfig(server, mainViewModel.subscriptionId, true)
|
||||
delay(500L)
|
||||
launch(Dispatchers.Main) {
|
||||
if (count > 0) {
|
||||
toast(R.string.toast_success)
|
||||
mainViewModel.reloadServerList()
|
||||
} else if (countSub > 0) {
|
||||
initGroupTab()
|
||||
} else {
|
||||
toast(R.string.toast_failure)
|
||||
}
|
||||
dialog.dismiss()
|
||||
//dialog.dismiss()
|
||||
binding.pbWaiting.hide()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -473,13 +577,14 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
|
||||
* import config from sub
|
||||
*/
|
||||
private fun importConfigViaSub(): Boolean {
|
||||
val dialog = AlertDialog.Builder(this)
|
||||
.setView(LayoutProgressBinding.inflate(layoutInflater).root)
|
||||
.setCancelable(false)
|
||||
.show()
|
||||
// val dialog = AlertDialog.Builder(this)
|
||||
// .setView(LayoutProgressBinding.inflate(layoutInflater).root)
|
||||
// .setCancelable(false)
|
||||
// .show()
|
||||
binding.pbWaiting.show()
|
||||
|
||||
lifecycleScope.launch(Dispatchers.IO) {
|
||||
val count = AngConfigManager.updateConfigViaSubAll()
|
||||
val count = mainViewModel.updateConfigViaSubAll()
|
||||
delay(500L)
|
||||
launch(Dispatchers.Main) {
|
||||
if (count > 0) {
|
||||
@@ -488,7 +593,8 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
|
||||
} else {
|
||||
toast(R.string.toast_failure)
|
||||
}
|
||||
dialog.dismiss()
|
||||
//dialog.dismiss()
|
||||
binding.pbWaiting.hide()
|
||||
}
|
||||
}
|
||||
return true
|
||||
@@ -585,26 +691,33 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
|
||||
return super.onKeyDown(keyCode, event)
|
||||
}
|
||||
|
||||
|
||||
override fun onNavigationItemSelected(item: MenuItem): Boolean {
|
||||
// Handle navigation view item clicks here.
|
||||
when (item.itemId) {
|
||||
//R.id.server_profile -> activityClass = MainActivity::class.java
|
||||
R.id.sub_setting -> {
|
||||
startActivity(Intent(this, SubSettingActivity::class.java))
|
||||
requestSubSettingActivity.launch(Intent(this, SubSettingActivity::class.java))
|
||||
}
|
||||
|
||||
R.id.settings -> {
|
||||
startActivity(Intent(this, SettingsActivity::class.java)
|
||||
.putExtra("isRunning", mainViewModel.isRunning.value == true))
|
||||
startActivity(
|
||||
Intent(this, SettingsActivity::class.java)
|
||||
.putExtra("isRunning", mainViewModel.isRunning.value == true)
|
||||
)
|
||||
}
|
||||
|
||||
R.id.user_asset_setting -> {
|
||||
startActivity(Intent(this, UserAssetActivity::class.java))
|
||||
}
|
||||
|
||||
R.id.promotion -> {
|
||||
Utils.openUri(this, "${Utils.decode(AppConfig.PromotionUrl)}?t=${System.currentTimeMillis()}")
|
||||
}
|
||||
|
||||
R.id.logcat -> {
|
||||
startActivity(Intent(this, LogcatActivity::class.java))
|
||||
}
|
||||
|
||||
R.id.about -> {
|
||||
startActivity(Intent(this, AboutActivity::class.java))
|
||||
}
|
||||
|
||||
@@ -10,7 +10,6 @@ import androidx.appcompat.app.AlertDialog
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.google.gson.Gson
|
||||
import com.tencent.mmkv.MMKV
|
||||
import com.v2ray.ang.AngApplication.Companion.application
|
||||
import com.v2ray.ang.AppConfig
|
||||
import com.v2ray.ang.R
|
||||
@@ -26,21 +25,17 @@ import com.v2ray.ang.service.V2RayServiceManager
|
||||
import com.v2ray.ang.util.AngConfigManager
|
||||
import com.v2ray.ang.util.MmkvManager
|
||||
import com.v2ray.ang.util.Utils
|
||||
import rx.Observable
|
||||
import rx.android.schedulers.AndroidSchedulers
|
||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
|
||||
import io.reactivex.rxjava3.core.Observable
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
class MainRecyclerAdapter(val activity: MainActivity) : RecyclerView.Adapter<MainRecyclerAdapter.BaseViewHolder>()
|
||||
, ItemTouchHelperAdapter {
|
||||
class MainRecyclerAdapter(val activity: MainActivity) : RecyclerView.Adapter<MainRecyclerAdapter.BaseViewHolder>(), ItemTouchHelperAdapter {
|
||||
companion object {
|
||||
private const val VIEW_TYPE_ITEM = 1
|
||||
private const val VIEW_TYPE_FOOTER = 2
|
||||
}
|
||||
|
||||
private var mActivity: MainActivity = activity
|
||||
private val mainStorage by lazy { MMKV.mmkvWithID(MmkvManager.ID_MAIN, MMKV.MULTI_PROCESS_MODE) }
|
||||
private val subStorage by lazy { MMKV.mmkvWithID(MmkvManager.ID_SUB, MMKV.MULTI_PROCESS_MODE) }
|
||||
private val settingsStorage by lazy { MMKV.mmkvWithID(MmkvManager.ID_SETTING, MMKV.MULTI_PROCESS_MODE) }
|
||||
private val share_method: Array<out String> by lazy {
|
||||
mActivity.resources.getStringArray(R.array.share_method)
|
||||
}
|
||||
@@ -51,7 +46,7 @@ class MainRecyclerAdapter(val activity: MainActivity) : RecyclerView.Adapter<Mai
|
||||
override fun onBindViewHolder(holder: BaseViewHolder, position: Int) {
|
||||
if (holder is MainViewHolder) {
|
||||
val guid = mActivity.mainViewModel.serversCache[position].guid
|
||||
val config = mActivity.mainViewModel.serversCache[position].config
|
||||
val profile = mActivity.mainViewModel.serversCache[position].profile
|
||||
// //filter
|
||||
// if (mActivity.mainViewModel.subscriptionId.isNotEmpty()
|
||||
// && mActivity.mainViewModel.subscriptionId != config.subscriptionId
|
||||
@@ -61,48 +56,45 @@ class MainRecyclerAdapter(val activity: MainActivity) : RecyclerView.Adapter<Mai
|
||||
// holder.itemMainBinding.cardView.visibility = View.VISIBLE
|
||||
// }
|
||||
|
||||
val outbound = config.getProxyOutbound()
|
||||
val aff = MmkvManager.decodeServerAffiliationInfo(guid)
|
||||
|
||||
holder.itemMainBinding.tvName.text = config.remarks
|
||||
holder.itemMainBinding.tvName.text = profile.remarks
|
||||
holder.itemView.setBackgroundColor(Color.TRANSPARENT)
|
||||
holder.itemMainBinding.tvTestResult.text = aff?.getTestDelayString() ?: ""
|
||||
holder.itemMainBinding.tvTestResult.text = aff?.getTestDelayString().orEmpty()
|
||||
if ((aff?.testDelayMillis ?: 0L) < 0L) {
|
||||
holder.itemMainBinding.tvTestResult.setTextColor(ContextCompat.getColor(mActivity, R.color.colorPingRed))
|
||||
} else {
|
||||
holder.itemMainBinding.tvTestResult.setTextColor(ContextCompat.getColor(mActivity, R.color.colorPing))
|
||||
}
|
||||
if (guid == mainStorage?.decodeString(MmkvManager.KEY_SELECTED_SERVER)) {
|
||||
if (guid == MmkvManager.mainStorage?.decodeString(MmkvManager.KEY_SELECTED_SERVER)) {
|
||||
holder.itemMainBinding.layoutIndicator.setBackgroundResource(R.color.colorAccent)
|
||||
} else {
|
||||
holder.itemMainBinding.layoutIndicator.setBackgroundResource(0)
|
||||
}
|
||||
holder.itemMainBinding.tvSubscription.text = ""
|
||||
val json = subStorage?.decodeString(config.subscriptionId)
|
||||
val json = MmkvManager.subStorage?.decodeString(profile.subscriptionId)
|
||||
if (!json.isNullOrBlank()) {
|
||||
val sub = Gson().fromJson(json, SubscriptionItem::class.java)
|
||||
holder.itemMainBinding.tvSubscription.text = sub.remarks
|
||||
}
|
||||
|
||||
var shareOptions = share_method.asList()
|
||||
when (config.configType) {
|
||||
when (profile.configType) {
|
||||
EConfigType.CUSTOM -> {
|
||||
holder.itemMainBinding.tvType.text = mActivity.getString(R.string.server_customize_config)
|
||||
shareOptions = shareOptions.takeLast(1)
|
||||
}
|
||||
|
||||
EConfigType.VLESS -> {
|
||||
holder.itemMainBinding.tvType.text = config.configType.name
|
||||
holder.itemMainBinding.tvType.text = profile.configType.name
|
||||
}
|
||||
|
||||
else -> {
|
||||
holder.itemMainBinding.tvType.text = config.configType.name.lowercase()
|
||||
holder.itemMainBinding.tvType.text = profile.configType.name.lowercase()
|
||||
}
|
||||
}
|
||||
|
||||
val strState = try{
|
||||
"${outbound?.getServerAddress()?.dropLast(3)}*** : ${outbound?.getServerPort()}"
|
||||
}catch(e: Exception){
|
||||
""
|
||||
}
|
||||
val strState = "${profile.server?.dropLast(3)}*** : ${profile.serverPort}"
|
||||
|
||||
holder.itemMainBinding.tvStatistics.text = strState
|
||||
|
||||
@@ -111,7 +103,7 @@ class MainRecyclerAdapter(val activity: MainActivity) : RecyclerView.Adapter<Mai
|
||||
try {
|
||||
when (i) {
|
||||
0 -> {
|
||||
if (config.configType == EConfigType.CUSTOM) {
|
||||
if (profile.configType == EConfigType.CUSTOM) {
|
||||
shareFullContent(guid)
|
||||
} else {
|
||||
val ivBinding = ItemQrcodeBinding.inflate(LayoutInflater.from(mActivity))
|
||||
@@ -119,6 +111,7 @@ class MainRecyclerAdapter(val activity: MainActivity) : RecyclerView.Adapter<Mai
|
||||
AlertDialog.Builder(mActivity).setView(ivBinding.root).show()
|
||||
}
|
||||
}
|
||||
|
||||
1 -> {
|
||||
if (AngConfigManager.share2Clipboard(mActivity, guid) == 0) {
|
||||
mActivity.toast(R.string.toast_success)
|
||||
@@ -126,6 +119,7 @@ class MainRecyclerAdapter(val activity: MainActivity) : RecyclerView.Adapter<Mai
|
||||
mActivity.toast(R.string.toast_failure)
|
||||
}
|
||||
}
|
||||
|
||||
2 -> shareFullContent(guid)
|
||||
else -> mActivity.toast("else")
|
||||
}
|
||||
@@ -138,15 +132,15 @@ class MainRecyclerAdapter(val activity: MainActivity) : RecyclerView.Adapter<Mai
|
||||
holder.itemMainBinding.layoutEdit.setOnClickListener {
|
||||
val intent = Intent().putExtra("guid", guid)
|
||||
.putExtra("isRunning", isRunning)
|
||||
if (config.configType == EConfigType.CUSTOM) {
|
||||
if (profile.configType == EConfigType.CUSTOM) {
|
||||
mActivity.startActivity(intent.setClass(mActivity, ServerCustomConfigActivity::class.java))
|
||||
} else {
|
||||
mActivity.startActivity(intent.setClass(mActivity, ServerActivity::class.java))
|
||||
}
|
||||
}
|
||||
holder.itemMainBinding.layoutRemove.setOnClickListener {
|
||||
if (guid != mainStorage?.decodeString(MmkvManager.KEY_SELECTED_SERVER)) {
|
||||
if (settingsStorage?.decodeBool(AppConfig.PREF_CONFIRM_REMOVE) == true) {
|
||||
if (guid != MmkvManager.mainStorage?.decodeString(MmkvManager.KEY_SELECTED_SERVER)) {
|
||||
if (MmkvManager.settingsStorage?.decodeBool(AppConfig.PREF_CONFIRM_REMOVE) == true) {
|
||||
AlertDialog.Builder(mActivity).setMessage(R.string.del_config_comfirm)
|
||||
.setPositiveButton(android.R.string.ok) { _, _ ->
|
||||
removeServer(guid, position)
|
||||
@@ -164,11 +158,11 @@ class MainRecyclerAdapter(val activity: MainActivity) : RecyclerView.Adapter<Mai
|
||||
}
|
||||
|
||||
holder.itemMainBinding.infoContainer.setOnClickListener {
|
||||
val selected = mainStorage?.decodeString(MmkvManager.KEY_SELECTED_SERVER)
|
||||
val selected = MmkvManager.mainStorage?.decodeString(MmkvManager.KEY_SELECTED_SERVER)
|
||||
if (guid != selected) {
|
||||
mainStorage?.encode(MmkvManager.KEY_SELECTED_SERVER, guid)
|
||||
MmkvManager.mainStorage?.encode(MmkvManager.KEY_SELECTED_SERVER, guid)
|
||||
if (!TextUtils.isEmpty(selected)) {
|
||||
notifyItemChanged(mActivity.mainViewModel.getPosition(selected?:""))
|
||||
notifyItemChanged(mActivity.mainViewModel.getPosition(selected.orEmpty()))
|
||||
}
|
||||
notifyItemChanged(mActivity.mainViewModel.getPosition(guid))
|
||||
if (isRunning) {
|
||||
@@ -212,6 +206,7 @@ class MainRecyclerAdapter(val activity: MainActivity) : RecyclerView.Adapter<Mai
|
||||
return when (viewType) {
|
||||
VIEW_TYPE_ITEM ->
|
||||
MainViewHolder(ItemRecyclerMainBinding.inflate(LayoutInflater.from(parent.context), parent, false))
|
||||
|
||||
else ->
|
||||
FooterViewHolder(ItemRecyclerFooterBinding.inflate(LayoutInflater.from(parent.context), parent, false))
|
||||
}
|
||||
@@ -243,7 +238,7 @@ class MainRecyclerAdapter(val activity: MainActivity) : RecyclerView.Adapter<Mai
|
||||
|
||||
override fun onItemDismiss(position: Int) {
|
||||
val guid = mActivity.mainViewModel.serversCache.getOrNull(position)?.guid ?: return
|
||||
if (guid != mainStorage?.decodeString(MmkvManager.KEY_SELECTED_SERVER)) {
|
||||
if (guid != MmkvManager.mainStorage?.decodeString(MmkvManager.KEY_SELECTED_SERVER)) {
|
||||
// mActivity.alert(R.string.del_config_comfirm) {
|
||||
// positiveButton(android.R.string.ok) {
|
||||
mActivity.mainViewModel.removeServer(guid)
|
||||
|
||||
@@ -21,14 +21,16 @@ import com.v2ray.ang.extension.v2RayApplication
|
||||
import com.v2ray.ang.util.AppManagerUtil
|
||||
import com.v2ray.ang.util.MmkvManager
|
||||
import com.v2ray.ang.util.Utils
|
||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
|
||||
import io.reactivex.rxjava3.schedulers.Schedulers
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import rx.android.schedulers.AndroidSchedulers
|
||||
import rx.schedulers.Schedulers
|
||||
import java.text.Collator
|
||||
|
||||
class PerAppProxyActivity : BaseActivity() {
|
||||
private lateinit var binding: ActivityBypassListBinding
|
||||
private val binding by lazy {
|
||||
ActivityBypassListBinding.inflate(layoutInflater)
|
||||
}
|
||||
|
||||
private var adapter: PerAppProxyAdapter? = null
|
||||
private var appsAll: List<AppInfo>? = null
|
||||
@@ -36,9 +38,7 @@ class PerAppProxyActivity : BaseActivity() {
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
binding = ActivityBypassListBinding.inflate(layoutInflater)
|
||||
val view = binding.root
|
||||
setContentView(view)
|
||||
setContentView(binding.root)
|
||||
|
||||
val dividerItemDecoration = DividerItemDecoration(this, LinearLayoutManager.VERTICAL)
|
||||
binding.recyclerView.addItemDecoration(dividerItemDecoration)
|
||||
@@ -188,12 +188,10 @@ class PerAppProxyActivity : BaseActivity() {
|
||||
if (searchItem != null) {
|
||||
val searchView = searchItem.actionView as SearchView
|
||||
searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
|
||||
override fun onQueryTextSubmit(query: String?): Boolean {
|
||||
return false
|
||||
}
|
||||
override fun onQueryTextSubmit(query: String?): Boolean = false
|
||||
|
||||
override fun onQueryTextChange(newText: String?): Boolean {
|
||||
filterProxyApp(newText?:"")
|
||||
filterProxyApp(newText.orEmpty())
|
||||
return false
|
||||
}
|
||||
})
|
||||
@@ -220,18 +218,22 @@ class PerAppProxyActivity : BaseActivity() {
|
||||
it.notifyDataSetChanged()
|
||||
true
|
||||
} ?: false
|
||||
|
||||
R.id.select_proxy_app -> {
|
||||
selectProxyApp()
|
||||
true
|
||||
}
|
||||
|
||||
R.id.import_proxy_app -> {
|
||||
importProxyApp()
|
||||
true
|
||||
}
|
||||
|
||||
R.id.export_proxy_app -> {
|
||||
exportProxyApp()
|
||||
true
|
||||
}
|
||||
|
||||
else -> super.onOptionsItemSelected(item)
|
||||
}
|
||||
|
||||
@@ -250,9 +252,7 @@ class PerAppProxyActivity : BaseActivity() {
|
||||
|
||||
private fun importProxyApp() {
|
||||
val content = Utils.getClipboard(applicationContext)
|
||||
if (TextUtils.isEmpty(content)) {
|
||||
return
|
||||
}
|
||||
if (TextUtils.isEmpty(content)) return
|
||||
selectProxyApp(content, false)
|
||||
toast(R.string.toast_success)
|
||||
}
|
||||
@@ -274,9 +274,7 @@ class PerAppProxyActivity : BaseActivity() {
|
||||
} else {
|
||||
content
|
||||
}
|
||||
if (TextUtils.isEmpty(proxyApps)) {
|
||||
return false
|
||||
}
|
||||
if (TextUtils.isEmpty(proxyApps)) return false
|
||||
|
||||
adapter?.blacklist?.clear()
|
||||
|
||||
@@ -316,12 +314,8 @@ class PerAppProxyActivity : BaseActivity() {
|
||||
|
||||
private fun inProxyApps(proxyApps: String, packageName: String, force: Boolean): Boolean {
|
||||
if (force) {
|
||||
if (packageName == "com.google.android.webview") {
|
||||
return false
|
||||
}
|
||||
if (packageName.startsWith("com.google")) {
|
||||
return true
|
||||
}
|
||||
if (packageName == "com.google.android.webview") return false
|
||||
if (packageName.startsWith("com.google")) return true
|
||||
}
|
||||
|
||||
return proxyApps.indexOf(packageName) >= 0
|
||||
@@ -334,7 +328,8 @@ class PerAppProxyActivity : BaseActivity() {
|
||||
if (key.isNotEmpty()) {
|
||||
appsAll?.forEach {
|
||||
if (it.appName.uppercase().indexOf(key) >= 0
|
||||
|| it.packageName.uppercase().indexOf(key) >= 0) {
|
||||
|| it.packageName.uppercase().indexOf(key) >= 0
|
||||
) {
|
||||
apps.add(it)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
package com.v2ray.ang.ui
|
||||
|
||||
import android.view.LayoutInflater
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.v2ray.ang.R
|
||||
import com.v2ray.ang.databinding.ItemRecyclerBypassListBinding
|
||||
import com.v2ray.ang.dto.AppInfo
|
||||
import java.util.*
|
||||
|
||||
class PerAppProxyAdapter(val activity: BaseActivity, val apps: List<AppInfo>, blacklist: MutableSet<String>?) :
|
||||
RecyclerView.Adapter<PerAppProxyAdapter.BaseViewHolder>() {
|
||||
@@ -34,8 +33,10 @@ class PerAppProxyAdapter(val activity: BaseActivity, val apps: List<AppInfo>, bl
|
||||
return when (viewType) {
|
||||
VIEW_TYPE_HEADER -> {
|
||||
val view = View(ctx)
|
||||
view.layoutParams = ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
|
||||
ctx.resources.getDimensionPixelSize(R.dimen.bypass_list_header_height) * 0)
|
||||
view.layoutParams = ViewGroup.LayoutParams(
|
||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||
ctx.resources.getDimensionPixelSize(R.dimen.bypass_list_header_height) * 0
|
||||
)
|
||||
BaseViewHolder(view)
|
||||
}
|
||||
// VIEW_TYPE_ITEM -> AppViewHolder(ctx.layoutInflater
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
package com.v2ray.ang.ui
|
||||
|
||||
import android.os.Bundle
|
||||
import com.v2ray.ang.R
|
||||
import androidx.fragment.app.Fragment
|
||||
import com.google.android.material.tabs.TabLayoutMediator
|
||||
import com.v2ray.ang.AppConfig
|
||||
import com.v2ray.ang.R
|
||||
import com.v2ray.ang.databinding.ActivityRoutingSettingsBinding
|
||||
|
||||
class RoutingSettingsActivity : BaseActivity() {
|
||||
private lateinit var binding: ActivityRoutingSettingsBinding
|
||||
private val binding by lazy { ActivityRoutingSettingsBinding.inflate(layoutInflater) }
|
||||
|
||||
private val titles: Array<out String> by lazy {
|
||||
resources.getStringArray(R.array.routing_tag)
|
||||
@@ -16,9 +16,7 @@ class RoutingSettingsActivity : BaseActivity() {
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
binding = ActivityRoutingSettingsBinding.inflate(layoutInflater)
|
||||
val view = binding.root
|
||||
setContentView(view)
|
||||
setContentView(binding.root)
|
||||
|
||||
title = getString(R.string.title_pref_routing_custom)
|
||||
|
||||
|
||||
@@ -1,16 +1,20 @@
|
||||
package com.v2ray.ang.ui
|
||||
|
||||
import android.Manifest
|
||||
import androidx.appcompat.app.AppCompatActivity.RESULT_OK
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.text.TextUtils
|
||||
import android.view.*
|
||||
import android.view.LayoutInflater
|
||||
import android.view.Menu
|
||||
import android.view.MenuInflater
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.appcompat.app.AppCompatActivity.RESULT_OK
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.preference.PreferenceManager
|
||||
import com.tbruyelle.rxpermissions.RxPermissions
|
||||
import com.tbruyelle.rxpermissions3.RxPermissions
|
||||
import com.tencent.mmkv.MMKV
|
||||
import com.v2ray.ang.AppConfig
|
||||
import com.v2ray.ang.R
|
||||
@@ -23,17 +27,19 @@ import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class RoutingSettingsFragment : Fragment() {
|
||||
private lateinit var binding: FragmentRoutingSettingsBinding
|
||||
private val binding by lazy { FragmentRoutingSettingsBinding.inflate(layoutInflater) }
|
||||
|
||||
companion object {
|
||||
private const val routing_arg = "routing_arg"
|
||||
}
|
||||
|
||||
private val settingsStorage by lazy { MMKV.mmkvWithID(MmkvManager.ID_SETTING, MMKV.MULTI_PROCESS_MODE) }
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
|
||||
savedInstanceState: Bundle?): View? {
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater, container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View {
|
||||
// Inflate the layout for this fragment
|
||||
binding = FragmentRoutingSettingsBinding.inflate(layoutInflater)
|
||||
return binding.root// inflater.inflate(R.layout.fragment_routing_settings, container, false)
|
||||
}
|
||||
|
||||
@@ -64,22 +70,27 @@ class RoutingSettingsFragment : Fragment() {
|
||||
saveRouting()
|
||||
true
|
||||
}
|
||||
|
||||
R.id.del_routing -> {
|
||||
binding.etRoutingContent.text = null
|
||||
true
|
||||
}
|
||||
|
||||
R.id.scan_replace -> {
|
||||
scanQRcode(true)
|
||||
true
|
||||
}
|
||||
|
||||
R.id.scan_append -> {
|
||||
scanQRcode(false)
|
||||
true
|
||||
}
|
||||
|
||||
R.id.default_rules -> {
|
||||
setDefaultRules()
|
||||
true
|
||||
}
|
||||
|
||||
else -> super.onOptionsItemSelected(item)
|
||||
}
|
||||
|
||||
@@ -131,9 +142,11 @@ class RoutingSettingsFragment : Fragment() {
|
||||
AppConfig.PREF_V2RAY_ROUTING_AGENT -> {
|
||||
tag = AppConfig.TAG_PROXY
|
||||
}
|
||||
|
||||
AppConfig.PREF_V2RAY_ROUTING_DIRECT -> {
|
||||
tag = AppConfig.TAG_DIRECT
|
||||
}
|
||||
|
||||
AppConfig.PREF_V2RAY_ROUTING_BLOCKED -> {
|
||||
tag = AppConfig.TAG_BLOCKED
|
||||
}
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
package com.v2ray.ang.ui
|
||||
|
||||
import android.Manifest
|
||||
import android.content.*
|
||||
import com.tbruyelle.rxpermissions.RxPermissions
|
||||
import com.v2ray.ang.R
|
||||
import com.v2ray.ang.util.AngConfigManager
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import com.tbruyelle.rxpermissions3.RxPermissions
|
||||
import com.v2ray.ang.R
|
||||
import com.v2ray.ang.extension.toast
|
||||
import com.v2ray.ang.util.AngConfigManager
|
||||
|
||||
class ScScannerActivity : BaseActivity() {
|
||||
|
||||
@@ -32,8 +32,8 @@ class ScScannerActivity : BaseActivity() {
|
||||
|
||||
private val scanQRCode = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
|
||||
if (it.resultCode == RESULT_OK) {
|
||||
val count = AngConfigManager.importBatchConfig(it.data?.getStringExtra("SCAN_RESULT"), "", false)
|
||||
if (count > 0) {
|
||||
val (count, countSub) = AngConfigManager.importBatchConfig(it.data?.getStringExtra("SCAN_RESULT"), "", false)
|
||||
if (count + countSub > 0) {
|
||||
toast(R.string.toast_success)
|
||||
} else {
|
||||
toast(R.string.toast_failure)
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
package com.v2ray.ang.ui
|
||||
|
||||
import com.v2ray.ang.R
|
||||
import com.v2ray.ang.util.Utils
|
||||
import android.os.Bundle
|
||||
import com.v2ray.ang.R
|
||||
import com.v2ray.ang.service.V2RayServiceManager
|
||||
import com.v2ray.ang.util.Utils
|
||||
|
||||
class ScSwitchActivity : BaseActivity() {
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
|
||||
@@ -1,15 +1,14 @@
|
||||
package com.v2ray.ang.ui
|
||||
|
||||
import android.Manifest
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import android.os.Bundle
|
||||
import android.content.Intent
|
||||
import android.graphics.BitmapFactory
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import com.tbruyelle.rxpermissions.RxPermissions
|
||||
import com.tbruyelle.rxpermissions3.RxPermissions
|
||||
import com.tencent.mmkv.MMKV
|
||||
import com.v2ray.ang.AppConfig
|
||||
import com.v2ray.ang.R
|
||||
@@ -45,7 +44,7 @@ class ScannerActivity : BaseActivity(){
|
||||
|
||||
private fun handleResult(result: QRResult) {
|
||||
if (result is QRResult.QRSuccess) {
|
||||
finished(result.content.rawValue?:"")
|
||||
finished(result.content.rawValue.orEmpty())
|
||||
} else {
|
||||
finish()
|
||||
}
|
||||
@@ -54,7 +53,7 @@ class ScannerActivity : BaseActivity(){
|
||||
private fun finished(text: String) {
|
||||
val intent = Intent()
|
||||
intent.putExtra("SCAN_RESULT", text)
|
||||
setResult(AppCompatActivity.RESULT_OK, intent)
|
||||
setResult(RESULT_OK, intent)
|
||||
finish()
|
||||
}
|
||||
|
||||
@@ -68,6 +67,7 @@ class ScannerActivity : BaseActivity(){
|
||||
launchScan()
|
||||
true
|
||||
}
|
||||
|
||||
R.id.select_photo -> {
|
||||
val permission = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
Manifest.permission.READ_MEDIA_IMAGES
|
||||
@@ -88,6 +88,7 @@ class ScannerActivity : BaseActivity(){
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
else -> super.onOptionsItemSelected(item)
|
||||
}
|
||||
|
||||
@@ -110,7 +111,7 @@ class ScannerActivity : BaseActivity(){
|
||||
try {
|
||||
val bitmap = BitmapFactory.decodeStream(contentResolver.openInputStream(uri))
|
||||
val text = QRCodeDecoder.syncDecodeQRCode(bitmap)
|
||||
finished(text?:"")
|
||||
finished(text.orEmpty())
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
toast(e.message.toString())
|
||||
|
||||
@@ -322,7 +322,7 @@ class ServerActivity : BaseActivity() {
|
||||
tlsSetting.alpn?.let {
|
||||
val alpnIndex = Utils.arrayFind(
|
||||
alpns,
|
||||
Utils.removeWhiteSpace(tlsSetting.alpn.joinToString())?:""
|
||||
Utils.removeWhiteSpace(tlsSetting.alpn.joinToString()).orEmpty()
|
||||
)
|
||||
sp_stream_alpn?.setSelection(alpnIndex)
|
||||
}
|
||||
@@ -450,7 +450,7 @@ class ServerActivity : BaseActivity() {
|
||||
saveStreamSettings(it)
|
||||
}
|
||||
if (config.subscriptionId.isEmpty() && !subscriptionId.isNullOrEmpty()) {
|
||||
config.subscriptionId = subscriptionId?:""
|
||||
config.subscriptionId = subscriptionId.orEmpty()
|
||||
}
|
||||
|
||||
MmkvManager.encodeServerConfig(editGuid, config)
|
||||
|
||||
@@ -8,7 +8,7 @@ import android.widget.Toast
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import com.blacksquircle.ui.editorkit.utils.EditorTheme
|
||||
import com.blacksquircle.ui.language.json.JsonLanguage
|
||||
import com.google.gson.*
|
||||
import com.google.gson.Gson
|
||||
import com.tencent.mmkv.MMKV
|
||||
import com.v2ray.ang.R
|
||||
import com.v2ray.ang.databinding.ActivityServerCustomConfigBinding
|
||||
@@ -21,7 +21,7 @@ import com.v2ray.ang.util.Utils
|
||||
import me.drakeet.support.toast.ToastCompat
|
||||
|
||||
class ServerCustomConfigActivity : BaseActivity() {
|
||||
private lateinit var binding: ActivityServerCustomConfigBinding
|
||||
private val binding by lazy { ActivityServerCustomConfigBinding.inflate(layoutInflater) }
|
||||
|
||||
private val mainStorage by lazy { MMKV.mmkvWithID(MmkvManager.ID_MAIN, MMKV.MULTI_PROCESS_MODE) }
|
||||
private val serverRawStorage by lazy { MMKV.mmkvWithID(MmkvManager.ID_SERVER_RAW, MMKV.MULTI_PROCESS_MODE) }
|
||||
@@ -34,9 +34,7 @@ class ServerCustomConfigActivity : BaseActivity() {
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
binding = ActivityServerCustomConfigBinding.inflate(layoutInflater)
|
||||
val view = binding.root
|
||||
setContentView(view)
|
||||
setContentView(binding.root)
|
||||
title = getString(R.string.title_server)
|
||||
|
||||
if (!Utils.getDarkModeStatus(this)) {
|
||||
@@ -91,7 +89,7 @@ class ServerCustomConfigActivity : BaseActivity() {
|
||||
}
|
||||
|
||||
val config = MmkvManager.decodeServerConfig(editGuid) ?: ServerConfig.create(EConfigType.CUSTOM)
|
||||
config.remarks = v2rayConfig.remarks ?: binding.etRemarks.text.toString().trim()
|
||||
config.remarks = if (binding.etRemarks.text.isNullOrEmpty()) v2rayConfig.remarks.orEmpty() else binding.etRemarks.text.toString()
|
||||
config.fullConfig = v2rayConfig
|
||||
|
||||
MmkvManager.encodeServerConfig(editGuid, config)
|
||||
@@ -141,10 +139,12 @@ class ServerCustomConfigActivity : BaseActivity() {
|
||||
deleteServer()
|
||||
true
|
||||
}
|
||||
|
||||
R.id.save_config -> {
|
||||
saveServer()
|
||||
true
|
||||
}
|
||||
|
||||
else -> super.onOptionsItemSelected(item)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -198,7 +198,8 @@ class SettingsActivity : BaseActivity() {
|
||||
fragmentInterval?.summary = settingsStorage.decodeString(AppConfig.PREF_FRAGMENT_INTERVAL, "10-20")
|
||||
|
||||
autoUpdateCheck?.isChecked = settingsStorage.getBoolean(AppConfig.SUBSCRIPTION_AUTO_UPDATE, false)
|
||||
autoUpdateInterval?.summary = settingsStorage.decodeString(AppConfig.SUBSCRIPTION_AUTO_UPDATE_INTERVAL,AppConfig.SUBSCRIPTION_DEFAULT_UPDATE_INTERVAL)
|
||||
autoUpdateInterval?.summary =
|
||||
settingsStorage.decodeString(AppConfig.SUBSCRIPTION_AUTO_UPDATE_INTERVAL, AppConfig.SUBSCRIPTION_DEFAULT_UPDATE_INTERVAL)
|
||||
autoUpdateInterval?.isEnabled = settingsStorage.getBoolean(AppConfig.SUBSCRIPTION_AUTO_UPDATE, false)
|
||||
|
||||
socksPort?.summary = settingsStorage.decodeString(AppConfig.PREF_SOCKS_PORT, AppConfig.PORT_SOCKS)
|
||||
@@ -350,12 +351,15 @@ class SettingsActivity : BaseActivity() {
|
||||
updateFragmentInterval(settingsStorage.decodeString(AppConfig.PREF_FRAGMENT_INTERVAL, "10-20"))
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateFragmentPackets(value: String?) {
|
||||
fragmentPackets?.summary = value.toString()
|
||||
}
|
||||
|
||||
private fun updateFragmentLength(value: String?) {
|
||||
fragmentLength?.summary = value.toString()
|
||||
}
|
||||
|
||||
private fun updateFragmentInterval(value: String?) {
|
||||
fragmentInterval?.summary = value.toString()
|
||||
}
|
||||
|
||||
@@ -5,25 +5,20 @@ import android.text.TextUtils
|
||||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.work.Constraints
|
||||
import androidx.work.ExistingPeriodicWorkPolicy
|
||||
import androidx.work.NetworkType
|
||||
import androidx.work.PeriodicWorkRequest
|
||||
import androidx.work.multiprocess.RemoteWorkManager
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.google.gson.Gson
|
||||
import com.tencent.mmkv.MMKV
|
||||
import com.v2ray.ang.AngApplication
|
||||
import com.v2ray.ang.R
|
||||
import com.v2ray.ang.databinding.ActivitySubEditBinding
|
||||
import com.v2ray.ang.dto.SubscriptionItem
|
||||
import com.v2ray.ang.extension.toast
|
||||
import com.v2ray.ang.service.SubscriptionUpdater
|
||||
import com.v2ray.ang.util.MmkvManager
|
||||
import com.v2ray.ang.util.Utils
|
||||
import java.util.concurrent.TimeUnit
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class SubEditActivity : BaseActivity() {
|
||||
private lateinit var binding: ActivitySubEditBinding
|
||||
private val binding by lazy { ActivitySubEditBinding.inflate(layoutInflater) }
|
||||
|
||||
var del_config: MenuItem? = null
|
||||
var save_config: MenuItem? = null
|
||||
@@ -33,9 +28,7 @@ class SubEditActivity : BaseActivity() {
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
binding = ActivitySubEditBinding.inflate(layoutInflater)
|
||||
val view = binding.root
|
||||
setContentView(view)
|
||||
setContentView(binding.root)
|
||||
title = getString(R.string.title_sub_setting)
|
||||
|
||||
val json = subStorage?.decodeString(editSubId)
|
||||
@@ -108,9 +101,13 @@ class SubEditActivity : BaseActivity() {
|
||||
if (editSubId.isNotEmpty()) {
|
||||
AlertDialog.Builder(this).setMessage(R.string.del_config_comfirm)
|
||||
.setPositiveButton(android.R.string.ok) { _, _ ->
|
||||
lifecycleScope.launch(Dispatchers.IO) {
|
||||
MmkvManager.removeSubscription(editSubId)
|
||||
launch(Dispatchers.Main) {
|
||||
finish()
|
||||
}
|
||||
}
|
||||
}
|
||||
.setNegativeButton(android.R.string.no) { _, _ ->
|
||||
// do nothing
|
||||
}
|
||||
@@ -136,10 +133,12 @@ class SubEditActivity : BaseActivity() {
|
||||
deleteServer()
|
||||
true
|
||||
}
|
||||
|
||||
R.id.save_config -> {
|
||||
saveServer()
|
||||
true
|
||||
}
|
||||
|
||||
else -> super.onOptionsItemSelected(item)
|
||||
}
|
||||
|
||||
|
||||
@@ -19,16 +19,14 @@ import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class SubSettingActivity : BaseActivity() {
|
||||
private lateinit var binding: ActivitySubSettingBinding
|
||||
private val binding by lazy { ActivitySubSettingBinding.inflate(layoutInflater) }
|
||||
|
||||
var subscriptions: List<Pair<String, SubscriptionItem>> = listOf()
|
||||
private val adapter by lazy { SubSettingRecyclerAdapter(this) }
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
binding = ActivitySubSettingBinding.inflate(layoutInflater)
|
||||
val view = binding.root
|
||||
setContentView(view)
|
||||
setContentView(binding.root)
|
||||
|
||||
title = getString(R.string.title_sub_setting)
|
||||
|
||||
|
||||
@@ -1,23 +1,21 @@
|
||||
package com.v2ray.ang.ui
|
||||
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import android.widget.ArrayAdapter
|
||||
import android.widget.ListView
|
||||
import java.util.ArrayList
|
||||
import com.v2ray.ang.R
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.text.TextUtils
|
||||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import android.widget.ArrayAdapter
|
||||
import android.widget.ListView
|
||||
import com.tencent.mmkv.MMKV
|
||||
import com.v2ray.ang.AppConfig
|
||||
import com.v2ray.ang.R
|
||||
import com.v2ray.ang.databinding.ActivityTaskerBinding
|
||||
import com.v2ray.ang.util.MmkvManager
|
||||
|
||||
class TaskerActivity : BaseActivity() {
|
||||
private lateinit var binding: ActivityTaskerBinding
|
||||
private val binding by lazy { ActivityTaskerBinding.inflate(layoutInflater) }
|
||||
|
||||
private var listview: ListView? = null
|
||||
private var lstData: ArrayList<String> = ArrayList()
|
||||
@@ -27,9 +25,7 @@ class TaskerActivity : BaseActivity() {
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
binding = ActivityTaskerBinding.inflate(layoutInflater)
|
||||
val view = binding.root
|
||||
setContentView(view)
|
||||
setContentView(binding.root)
|
||||
|
||||
//add def value
|
||||
lstData.add("Default")
|
||||
@@ -41,8 +37,10 @@ class TaskerActivity : BaseActivity() {
|
||||
lstGuid.add(key)
|
||||
}
|
||||
}
|
||||
val adapter = ArrayAdapter(this,
|
||||
android.R.layout.simple_list_item_single_choice, lstData)
|
||||
val adapter = ArrayAdapter(
|
||||
this,
|
||||
android.R.layout.simple_list_item_single_choice, lstData
|
||||
)
|
||||
listview = findViewById<View>(R.id.listview) as ListView
|
||||
listview?.adapter = adapter
|
||||
|
||||
@@ -90,7 +88,7 @@ class TaskerActivity : BaseActivity() {
|
||||
|
||||
intent.putExtra(AppConfig.TASKER_EXTRA_BUNDLE, extraBundle)
|
||||
intent.putExtra(AppConfig.TASKER_EXTRA_STRING_BLURB, blurb)
|
||||
setResult(AppCompatActivity.RESULT_OK, intent)
|
||||
setResult(RESULT_OK, intent)
|
||||
finish()
|
||||
}
|
||||
|
||||
@@ -105,10 +103,12 @@ class TaskerActivity : BaseActivity() {
|
||||
R.id.del_config -> {
|
||||
true
|
||||
}
|
||||
|
||||
R.id.save_config -> {
|
||||
confirmFinish()
|
||||
true
|
||||
}
|
||||
|
||||
else -> super.onOptionsItemSelected(item)
|
||||
}
|
||||
|
||||
|
||||
@@ -11,34 +11,32 @@ import com.v2ray.ang.util.AngConfigManager
|
||||
import java.net.URLDecoder
|
||||
|
||||
class UrlSchemeActivity : BaseActivity() {
|
||||
private lateinit var binding: ActivityLogcatBinding
|
||||
private val binding by lazy { ActivityLogcatBinding.inflate(layoutInflater) }
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
binding = ActivityLogcatBinding.inflate(layoutInflater)
|
||||
val view = binding.root
|
||||
setContentView(view)
|
||||
setContentView(binding.root)
|
||||
|
||||
try {
|
||||
intent.apply {
|
||||
if (action == Intent.ACTION_SEND) {
|
||||
if ("text/plain" == type) {
|
||||
intent.getStringExtra(Intent.EXTRA_TEXT)?.let {
|
||||
parseUri(it)
|
||||
parseUri(it, null)
|
||||
}
|
||||
}
|
||||
} else if (action == Intent.ACTION_VIEW) {
|
||||
when (data?.host) {
|
||||
"install-config" -> {
|
||||
val uri: Uri? = intent.data
|
||||
val shareUrl = uri?.getQueryParameter("url") ?: ""
|
||||
parseUri(shareUrl)
|
||||
val shareUrl = uri?.getQueryParameter("url").orEmpty()
|
||||
parseUri(shareUrl, uri?.fragment)
|
||||
}
|
||||
|
||||
"install-sub" -> {
|
||||
val uri: Uri? = intent.data
|
||||
val shareUrl = uri?.getQueryParameter("url") ?: ""
|
||||
parseUri(shareUrl)
|
||||
val shareUrl = uri?.getQueryParameter("url").orEmpty()
|
||||
parseUri(shareUrl, uri?.fragment)
|
||||
}
|
||||
|
||||
else -> {
|
||||
@@ -55,17 +53,21 @@ class UrlSchemeActivity : BaseActivity() {
|
||||
}
|
||||
}
|
||||
|
||||
private fun parseUri(uriString: String?) {
|
||||
private fun parseUri(uriString: String?, fragment: String?) {
|
||||
if (uriString.isNullOrEmpty()) {
|
||||
return
|
||||
}
|
||||
Log.d("UrlScheme", uriString)
|
||||
|
||||
val decodedUrl = URLDecoder.decode(uriString, "UTF-8")
|
||||
var decodedUrl = URLDecoder.decode(uriString, "UTF-8")
|
||||
val uri = Uri.parse(decodedUrl)
|
||||
if (uri != null) {
|
||||
val count = AngConfigManager.importBatchConfig(decodedUrl, "", false)
|
||||
if (count > 0) {
|
||||
if (uri.fragment.isNullOrEmpty() && !fragment.isNullOrEmpty()) {
|
||||
decodedUrl += "#${fragment}"
|
||||
}
|
||||
Log.d("UrlScheme-decodedUrl", decodedUrl)
|
||||
val (count, countSub) = AngConfigManager.importBatchConfig(decodedUrl, "", false)
|
||||
if (count + countSub > 0) {
|
||||
toast(R.string.import_subscription_success)
|
||||
} else {
|
||||
toast(R.string.import_subscription_failure)
|
||||
|
||||
@@ -2,26 +2,31 @@ package com.v2ray.ang.ui
|
||||
|
||||
import android.Manifest
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.AlertDialog
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.provider.OpenableColumns
|
||||
import android.util.Log
|
||||
import android.view.*
|
||||
import android.view.LayoutInflater
|
||||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import android.view.View.GONE
|
||||
import android.view.View.VISIBLE
|
||||
import android.view.ViewGroup
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.google.gson.Gson
|
||||
import com.tbruyelle.rxpermissions.RxPermissions
|
||||
import com.tbruyelle.rxpermissions3.RxPermissions
|
||||
import com.tencent.mmkv.MMKV
|
||||
import com.v2ray.ang.AppConfig
|
||||
import com.v2ray.ang.R
|
||||
import com.v2ray.ang.databinding.ActivitySubSettingBinding
|
||||
import com.v2ray.ang.databinding.ItemRecyclerUserAssetBinding
|
||||
import com.v2ray.ang.databinding.LayoutProgressBinding
|
||||
import com.v2ray.ang.dto.AssetUrlItem
|
||||
import com.v2ray.ang.extension.toTrafficString
|
||||
import com.v2ray.ang.extension.toast
|
||||
@@ -36,10 +41,10 @@ import java.net.InetSocketAddress
|
||||
import java.net.Proxy
|
||||
import java.net.URL
|
||||
import java.text.DateFormat
|
||||
import java.util.*
|
||||
import java.util.Date
|
||||
|
||||
class UserAssetActivity : BaseActivity() {
|
||||
private lateinit var binding: ActivitySubSettingBinding
|
||||
private val binding by lazy { ActivitySubSettingBinding.inflate(layoutInflater) }
|
||||
private val settingsStorage by lazy { MMKV.mmkvWithID(MmkvManager.ID_SETTING, MMKV.MULTI_PROCESS_MODE) }
|
||||
private val assetStorage by lazy { MMKV.mmkvWithID(MmkvManager.ID_ASSET, MMKV.MULTI_PROCESS_MODE) }
|
||||
|
||||
@@ -49,9 +54,7 @@ class UserAssetActivity : BaseActivity() {
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
binding = ActivitySubSettingBinding.inflate(layoutInflater)
|
||||
val view = binding.root
|
||||
setContentView(view)
|
||||
setContentView(binding.root)
|
||||
title = getString(R.string.title_user_asset_setting)
|
||||
|
||||
binding.recyclerView.setHasFixedSize(true)
|
||||
@@ -80,6 +83,7 @@ class UserAssetActivity : BaseActivity() {
|
||||
startActivity(intent)
|
||||
true
|
||||
}
|
||||
|
||||
R.id.download_file -> {
|
||||
downloadGeoFiles()
|
||||
true
|
||||
@@ -168,9 +172,13 @@ class UserAssetActivity : BaseActivity() {
|
||||
}
|
||||
|
||||
private fun downloadGeoFiles() {
|
||||
val httpPort = Utils.parseInt(settingsStorage?.decodeString(AppConfig.PREF_HTTP_PORT), AppConfig.PORT_HTTP.toInt())
|
||||
|
||||
val dialog = AlertDialog.Builder(this)
|
||||
.setView(LayoutProgressBinding.inflate(layoutInflater).root)
|
||||
.setCancelable(false)
|
||||
.show()
|
||||
toast(R.string.msg_downloading_content)
|
||||
|
||||
val httpPort = Utils.parseInt(settingsStorage?.decodeString(AppConfig.PREF_HTTP_PORT), AppConfig.PORT_HTTP.toInt())
|
||||
var assets = MmkvManager.decodeAssetUrls()
|
||||
assets = addBuiltInGeoItems(assets)
|
||||
|
||||
@@ -188,6 +196,7 @@ class UserAssetActivity : BaseActivity() {
|
||||
} else {
|
||||
toast(getString(R.string.toast_failure) + " " + it.second.remarks)
|
||||
}
|
||||
dialog.dismiss()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -229,15 +238,18 @@ class UserAssetActivity : BaseActivity() {
|
||||
conn?.disconnect()
|
||||
}
|
||||
}
|
||||
|
||||
private fun addBuiltInGeoItems(assets: List<Pair<String, AssetUrlItem>>): List<Pair<String, AssetUrlItem>> {
|
||||
val list = mutableListOf<Pair<String, AssetUrlItem>>()
|
||||
builtInGeoFiles
|
||||
.filter { geoFile -> assets.none { it.second.remarks == geoFile } }
|
||||
.forEach {
|
||||
list.add(Utils.getUuid() to AssetUrlItem(
|
||||
list.add(
|
||||
Utils.getUuid() to AssetUrlItem(
|
||||
it,
|
||||
AppConfig.GeoUrl + it
|
||||
))
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
return list + assets
|
||||
@@ -249,14 +261,15 @@ class UserAssetActivity : BaseActivity() {
|
||||
ItemRecyclerUserAssetBinding.inflate(
|
||||
LayoutInflater.from(parent.context),
|
||||
parent,
|
||||
false)
|
||||
false
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@SuppressLint("SetTextI18n")
|
||||
override fun onBindViewHolder(holder: UserAssetViewHolder, position: Int) {
|
||||
var assets = MmkvManager.decodeAssetUrls();
|
||||
assets = addBuiltInGeoItems(assets);
|
||||
var assets = MmkvManager.decodeAssetUrls()
|
||||
assets = addBuiltInGeoItems(assets)
|
||||
val item = assets.getOrNull(position) ?: return
|
||||
// file with name == item.second.remarks
|
||||
val file = extDir.listFiles()?.find { it.name == item.second.remarks }
|
||||
@@ -292,8 +305,8 @@ class UserAssetActivity : BaseActivity() {
|
||||
}
|
||||
|
||||
override fun getItemCount(): Int {
|
||||
var assets = MmkvManager.decodeAssetUrls();
|
||||
assets = addBuiltInGeoItems(assets);
|
||||
var assets = MmkvManager.decodeAssetUrls()
|
||||
assets = addBuiltInGeoItems(assets)
|
||||
return assets.size
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ import com.v2ray.ang.util.Utils
|
||||
import java.io.File
|
||||
|
||||
class UserAssetUrlActivity : BaseActivity() {
|
||||
private lateinit var binding: ActivityUserAssetUrlBinding
|
||||
private val binding by lazy { ActivityUserAssetUrlBinding.inflate(layoutInflater) }
|
||||
|
||||
var del_config: MenuItem? = null
|
||||
var save_config: MenuItem? = null
|
||||
@@ -27,9 +27,7 @@ class UserAssetUrlActivity : BaseActivity() {
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
binding = ActivityUserAssetUrlBinding.inflate(layoutInflater)
|
||||
val view = binding.root
|
||||
setContentView(view)
|
||||
setContentView(binding.root)
|
||||
title = getString(R.string.title_user_asset_add_url)
|
||||
|
||||
val json = assetStorage?.decodeString(editAssetId)
|
||||
@@ -139,10 +137,12 @@ class UserAssetUrlActivity : BaseActivity() {
|
||||
deleteServer()
|
||||
true
|
||||
}
|
||||
|
||||
R.id.save_config -> {
|
||||
saveServer()
|
||||
true
|
||||
}
|
||||
|
||||
else -> super.onOptionsItemSelected(item)
|
||||
}
|
||||
}
|
||||
@@ -386,7 +386,7 @@ object AngConfigManager {
|
||||
// }
|
||||
// }
|
||||
|
||||
fun importBatchConfig(server: String?, subid: String, append: Boolean): Int {
|
||||
fun importBatchConfig(server: String?, subid: String, append: Boolean): Pair<Int, Int> {
|
||||
var count = parseBatchConfig(Utils.decode(server), subid, append)
|
||||
if (count <= 0) {
|
||||
count = parseBatchConfig(server, subid, append)
|
||||
@@ -395,14 +395,18 @@ object AngConfigManager {
|
||||
count = parseCustomConfigServer(server, subid)
|
||||
}
|
||||
|
||||
if (parseBatchSubscription(server, subid) > 0) {
|
||||
updateConfigViaSubAll()
|
||||
return 1
|
||||
var countSub = parseBatchSubscription(server)
|
||||
if (countSub <= 0) {
|
||||
countSub = parseBatchSubscription(Utils.decode(server))
|
||||
}
|
||||
return count
|
||||
if (countSub > 0) {
|
||||
updateConfigViaSubAll()
|
||||
}
|
||||
|
||||
fun parseBatchSubscription(servers: String?, subid: String): Int {
|
||||
return count to countSub
|
||||
}
|
||||
|
||||
fun parseBatchSubscription(servers: String?): Int {
|
||||
try {
|
||||
if (servers == null) {
|
||||
return 0
|
||||
@@ -410,7 +414,6 @@ object AngConfigManager {
|
||||
|
||||
var count = 0
|
||||
servers.lines()
|
||||
.reversed()
|
||||
.forEach { str ->
|
||||
if (str.startsWith(AppConfig.PROTOCOL_HTTP) || str.startsWith(AppConfig.PROTOCOL_HTTPS)) {
|
||||
count += MmkvManager.importUrlAsSubscription(str)
|
||||
@@ -431,7 +434,7 @@ object AngConfigManager {
|
||||
val removedSelectedServer =
|
||||
if (!TextUtils.isEmpty(subid) && !append) {
|
||||
MmkvManager.decodeServerConfig(
|
||||
mainStorage?.decodeString(KEY_SELECTED_SERVER) ?: ""
|
||||
mainStorage?.decodeString(KEY_SELECTED_SERVER).orEmpty()
|
||||
)?.let {
|
||||
if (it.subscriptionId == subid) {
|
||||
return@let it
|
||||
@@ -488,7 +491,7 @@ object AngConfigManager {
|
||||
|
||||
if (serverList.isNotEmpty()) {
|
||||
var count = 0
|
||||
for (srv in serverList) {
|
||||
for (srv in serverList.reversed()) {
|
||||
val config = ServerConfig.create(EConfigType.CUSTOM)
|
||||
config.fullConfig =
|
||||
Gson().fromJson(Gson().toJson(srv), V2rayConfig::class.java)
|
||||
@@ -532,7 +535,7 @@ object AngConfigManager {
|
||||
return count
|
||||
}
|
||||
|
||||
private fun updateConfigViaSub(it: Pair<String, SubscriptionItem>): Int {
|
||||
fun updateConfigViaSub(it: Pair<String, SubscriptionItem>): Int {
|
||||
try {
|
||||
if (TextUtils.isEmpty(it.first)
|
||||
|| TextUtils.isEmpty(it.second.remarks)
|
||||
@@ -560,7 +563,7 @@ object AngConfigManager {
|
||||
settingsStorage?.decodeString(AppConfig.PREF_HTTP_PORT),
|
||||
AppConfig.PORT_HTTP.toInt()
|
||||
)
|
||||
Utils.getUrlContentWithCustomUserAgent(url, httpPort)
|
||||
Utils.getUrlContentWithCustomUserAgent(url, 30000, httpPort)
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
""
|
||||
|
||||
@@ -6,8 +6,7 @@ import android.content.pm.ApplicationInfo
|
||||
import android.content.pm.PackageInfo
|
||||
import android.content.pm.PackageManager
|
||||
import com.v2ray.ang.dto.AppInfo
|
||||
import rx.Observable
|
||||
import java.util.*
|
||||
import io.reactivex.rxjava3.core.Observable
|
||||
|
||||
object AppManagerUtil {
|
||||
fun loadNetworkAppList(ctx: Context): ArrayList<AppInfo> {
|
||||
@@ -31,7 +30,8 @@ object AppManagerUtil {
|
||||
return apps
|
||||
}
|
||||
|
||||
fun rxLoadNetworkAppList(ctx: Context): Observable<ArrayList<AppInfo>> = Observable.unsafeCreate {
|
||||
fun rxLoadNetworkAppList(ctx: Context): Observable<ArrayList<AppInfo>> =
|
||||
Observable.unsafeCreate {
|
||||
it.onNext(loadNetworkAppList(ctx))
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ package com.v2ray.ang.util
|
||||
import com.google.gson.Gson
|
||||
import com.tencent.mmkv.MMKV
|
||||
import com.v2ray.ang.dto.AssetUrlItem
|
||||
import com.v2ray.ang.dto.ProfileItem
|
||||
import com.v2ray.ang.dto.ServerAffiliationInfo
|
||||
import com.v2ray.ang.dto.ServerConfig
|
||||
import com.v2ray.ang.dto.SubscriptionItem
|
||||
@@ -11,6 +12,7 @@ import java.net.URI
|
||||
object MmkvManager {
|
||||
const val ID_MAIN = "MAIN"
|
||||
const val ID_SERVER_CONFIG = "SERVER_CONFIG"
|
||||
const val ID_PROFILE_CONFIG = "PROFILE_CONFIG"
|
||||
const val ID_SERVER_RAW = "SERVER_RAW"
|
||||
const val ID_SERVER_AFF = "SERVER_AFF"
|
||||
const val ID_SUB = "SUB"
|
||||
@@ -19,11 +21,14 @@ object MmkvManager {
|
||||
const val KEY_SELECTED_SERVER = "SELECTED_SERVER"
|
||||
const val KEY_ANG_CONFIGS = "ANG_CONFIGS"
|
||||
|
||||
private val mainStorage by lazy { MMKV.mmkvWithID(ID_MAIN, MMKV.MULTI_PROCESS_MODE) }
|
||||
val mainStorage by lazy { MMKV.mmkvWithID(ID_MAIN, MMKV.MULTI_PROCESS_MODE) }
|
||||
val settingsStorage by lazy { MMKV.mmkvWithID(ID_SETTING, MMKV.MULTI_PROCESS_MODE) }
|
||||
private val serverStorage by lazy { MMKV.mmkvWithID(ID_SERVER_CONFIG, MMKV.MULTI_PROCESS_MODE) }
|
||||
private val profileStorage by lazy { MMKV.mmkvWithID(ID_PROFILE_CONFIG, MMKV.MULTI_PROCESS_MODE) }
|
||||
private val serverAffStorage by lazy { MMKV.mmkvWithID(ID_SERVER_AFF, MMKV.MULTI_PROCESS_MODE) }
|
||||
private val subStorage by lazy { MMKV.mmkvWithID(ID_SUB, MMKV.MULTI_PROCESS_MODE) }
|
||||
val subStorage by lazy { MMKV.mmkvWithID(ID_SUB, MMKV.MULTI_PROCESS_MODE) }
|
||||
private val assetStorage by lazy { MMKV.mmkvWithID(ID_ASSET, MMKV.MULTI_PROCESS_MODE) }
|
||||
val serverRawStorage by lazy { MMKV.mmkvWithID(ID_SERVER_RAW, MMKV.MULTI_PROCESS_MODE) }
|
||||
|
||||
fun decodeServerList(): MutableList<String> {
|
||||
val json = mainStorage?.decodeString(KEY_ANG_CONFIGS)
|
||||
@@ -45,6 +50,17 @@ object MmkvManager {
|
||||
return Gson().fromJson(json, ServerConfig::class.java)
|
||||
}
|
||||
|
||||
fun decodeProfileConfig(guid: String): ProfileItem? {
|
||||
if (guid.isBlank()) {
|
||||
return null
|
||||
}
|
||||
val json = profileStorage?.decodeString(guid)
|
||||
if (json.isNullOrBlank()) {
|
||||
return null
|
||||
}
|
||||
return Gson().fromJson(json, ProfileItem::class.java)
|
||||
}
|
||||
|
||||
fun encodeServerConfig(guid: String, config: ServerConfig): String {
|
||||
val key = guid.ifBlank { Utils.getUuid() }
|
||||
serverStorage?.encode(key, Gson().toJson(config))
|
||||
@@ -56,6 +72,14 @@ object MmkvManager {
|
||||
mainStorage?.encode(KEY_SELECTED_SERVER, key)
|
||||
}
|
||||
}
|
||||
val profile = ProfileItem(
|
||||
configType = config.configType,
|
||||
subscriptionId = config.subscriptionId,
|
||||
remarks = config.remarks,
|
||||
server = config.getProxyOutbound()?.getServerAddress(),
|
||||
serverPort = config.getProxyOutbound()?.getServerPort(),
|
||||
)
|
||||
profileStorage?.encode(key, Gson().toJson(profile))
|
||||
return key
|
||||
}
|
||||
|
||||
@@ -70,6 +94,7 @@ object MmkvManager {
|
||||
serverList.remove(guid)
|
||||
mainStorage?.encode(KEY_ANG_CONFIGS, Gson().toJson(serverList))
|
||||
serverStorage?.remove(guid)
|
||||
profileStorage?.remove(guid)
|
||||
serverAffStorage?.remove(guid)
|
||||
}
|
||||
|
||||
@@ -124,7 +149,7 @@ object MmkvManager {
|
||||
}
|
||||
val uri = URI(Utils.fixIllegalUrl(url))
|
||||
val subItem = SubscriptionItem()
|
||||
subItem.remarks = Utils.urlDecode(uri.fragment ?: "import sub")
|
||||
subItem.remarks = uri.fragment ?: "import sub"
|
||||
subItem.url = url
|
||||
subStorage?.encode(Utils.getUuid(), Gson().toJson(subItem))
|
||||
return 1
|
||||
@@ -138,8 +163,7 @@ object MmkvManager {
|
||||
subscriptions.add(Pair(key, Gson().fromJson(json, SubscriptionItem::class.java)))
|
||||
}
|
||||
}
|
||||
subscriptions.sortedBy { (_, value) -> value.addedTime }
|
||||
return subscriptions
|
||||
return subscriptions.sortedBy { (_, value) -> value.addedTime }
|
||||
}
|
||||
|
||||
fun removeSubscription(subid: String) {
|
||||
@@ -155,8 +179,7 @@ object MmkvManager {
|
||||
assetUrlItems.add(Pair(key, Gson().fromJson(json, AssetUrlItem::class.java)))
|
||||
}
|
||||
}
|
||||
assetUrlItems.sortedBy { (_, value) -> value.addedTime }
|
||||
return assetUrlItems
|
||||
return assetUrlItems.sortedBy { (_, value) -> value.addedTime }
|
||||
}
|
||||
|
||||
fun removeAssetUrl(assetid: String) {
|
||||
@@ -166,10 +189,18 @@ object MmkvManager {
|
||||
fun removeAllServer() {
|
||||
mainStorage?.clearAll()
|
||||
serverStorage?.clearAll()
|
||||
profileStorage?.clearAll()
|
||||
serverAffStorage?.clearAll()
|
||||
}
|
||||
|
||||
fun removeInvalidServer() {
|
||||
fun removeInvalidServer(guid: String) {
|
||||
if (guid.isNotEmpty()) {
|
||||
decodeServerAffiliationInfo(guid)?.let { aff ->
|
||||
if (aff.testDelayMillis < 0L) {
|
||||
removeServer(guid)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
serverAffStorage?.allKeys()?.forEach { key ->
|
||||
decodeServerAffiliationInfo(key)?.let { aff ->
|
||||
if (aff.testDelayMillis < 0L) {
|
||||
@@ -178,6 +209,7 @@ object MmkvManager {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun sortByTestResults() {
|
||||
data class ServerDelay(var guid: String, var testDelayMillis: Long)
|
||||
|
||||
@@ -7,7 +7,7 @@ import android.content.res.Resources
|
||||
import android.os.Build
|
||||
import android.os.LocaleList
|
||||
import androidx.annotation.RequiresApi
|
||||
import java.util.*
|
||||
import java.util.Locale
|
||||
|
||||
open class MyContextWrapper(base: Context?) : ContextWrapper(base) {
|
||||
companion object {
|
||||
|
||||
@@ -52,7 +52,8 @@ object SpeedtestUtil {
|
||||
val allText = process.inputStream.bufferedReader().use { it.readText() }
|
||||
if (!TextUtils.isEmpty(allText)) {
|
||||
val tempInfo = allText.substring(allText.indexOf("min/avg/max/mdev") + 19)
|
||||
val temps = tempInfo.split("/".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
|
||||
val temps =
|
||||
tempInfo.split("/".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
|
||||
if (temps.count() > 0 && temps[0].length < 10) {
|
||||
return temps[0].toFloat().toInt().toString() + "ms"
|
||||
}
|
||||
@@ -105,8 +106,11 @@ object SpeedtestUtil {
|
||||
val url = URL(Utils.getDelayTestUrl())
|
||||
|
||||
conn = url.openConnection(
|
||||
Proxy(Proxy.Type.HTTP,
|
||||
InetSocketAddress("127.0.0.1", port))) as HttpURLConnection
|
||||
Proxy(
|
||||
Proxy.Type.HTTP,
|
||||
InetSocketAddress("127.0.0.1", port)
|
||||
)
|
||||
) as HttpURLConnection
|
||||
conn.connectTimeout = 30000
|
||||
conn.readTimeout = 30000
|
||||
conn.setRequestProperty("Connection", "close")
|
||||
@@ -120,11 +124,19 @@ object SpeedtestUtil {
|
||||
if (code == 204 || code == 200 && conn.responseLength == 0L) {
|
||||
result = context.getString(R.string.connection_test_available, elapsed)
|
||||
} else {
|
||||
throw IOException(context.getString(R.string.connection_test_error_status_code, code))
|
||||
throw IOException(
|
||||
context.getString(
|
||||
R.string.connection_test_error_status_code,
|
||||
code
|
||||
)
|
||||
)
|
||||
}
|
||||
} catch (e: IOException) {
|
||||
// network exception
|
||||
Log.d(AppConfig.ANG_PACKAGE, "testConnection IOException: " + Log.getStackTraceString(e))
|
||||
Log.d(
|
||||
AppConfig.ANG_PACKAGE,
|
||||
"testConnection IOException: " + Log.getStackTraceString(e)
|
||||
)
|
||||
result = context.getString(R.string.connection_test_error, e.message)
|
||||
} catch (e: Exception) {
|
||||
// library exception, eg sumsung
|
||||
|
||||
@@ -40,7 +40,7 @@ object Utils {
|
||||
* @return
|
||||
*/
|
||||
fun getEditable(text: String?): Editable {
|
||||
return Editable.Factory.getInstance().newEditable(text?:"")
|
||||
return Editable.Factory.getInstance().newEditable(text.orEmpty())
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -63,15 +63,10 @@ object Utils {
|
||||
}
|
||||
|
||||
fun parseInt(str: String?, default: Int): Int {
|
||||
str ?: return default
|
||||
return try {
|
||||
Integer.parseInt(str)
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
default
|
||||
}
|
||||
return str?.toIntOrNull() ?: default
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* get text from clipboard
|
||||
*/
|
||||
@@ -102,22 +97,18 @@ object Utils {
|
||||
* base64 decode
|
||||
*/
|
||||
fun decode(text: String?): String {
|
||||
tryDecodeBase64(text)?.let { return it }
|
||||
if (text?.endsWith('=')==true) {
|
||||
// try again for some loosely formatted base64
|
||||
tryDecodeBase64(text.trimEnd('='))?.let { return it }
|
||||
}
|
||||
return ""
|
||||
return tryDecodeBase64(text) ?: text?.trimEnd('=')?.let { tryDecodeBase64(it) }.orEmpty()
|
||||
}
|
||||
|
||||
|
||||
fun tryDecodeBase64(text: String?): String? {
|
||||
try {
|
||||
return Base64.decode(text, Base64.NO_WRAP).toString(charset("UTF-8"))
|
||||
return Base64.decode(text, Base64.NO_WRAP).toString(Charsets.UTF_8)
|
||||
} catch (e: Exception) {
|
||||
Log.i(ANG_PACKAGE, "Parse base64 standard failed $e")
|
||||
}
|
||||
try {
|
||||
return Base64.decode(text, Base64.NO_WRAP.or(Base64.URL_SAFE)).toString(charset("UTF-8"))
|
||||
return Base64.decode(text, Base64.NO_WRAP.or(Base64.URL_SAFE)).toString(Charsets.UTF_8)
|
||||
} catch (e: Exception) {
|
||||
Log.i(ANG_PACKAGE, "Parse base64 url safe failed $e")
|
||||
}
|
||||
@@ -129,7 +120,7 @@ object Utils {
|
||||
*/
|
||||
fun encode(text: String): String {
|
||||
return try {
|
||||
Base64.encodeToString(text.toByteArray(charset("UTF-8")), Base64.NO_WRAP)
|
||||
Base64.encodeToString(text.toByteArray(Charsets.UTF_8), Base64.NO_WRAP)
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
""
|
||||
@@ -213,7 +204,8 @@ object Utils {
|
||||
}
|
||||
|
||||
fun isIpv4Address(value: String): Boolean {
|
||||
val regV4 = Regex("^([01]?[0-9]?[0-9]|2[0-4][0-9]|25[0-5])\\.([01]?[0-9]?[0-9]|2[0-4][0-9]|25[0-5])\\.([01]?[0-9]?[0-9]|2[0-4][0-9]|25[0-5])\\.([01]?[0-9]?[0-9]|2[0-4][0-9]|25[0-5])$")
|
||||
val regV4 =
|
||||
Regex("^([01]?[0-9]?[0-9]|2[0-4][0-9]|25[0-5])\\.([01]?[0-9]?[0-9]|2[0-4][0-9]|25[0-5])\\.([01]?[0-9]?[0-9]|2[0-4][0-9]|25[0-5])\\.([01]?[0-9]?[0-9]|2[0-4][0-9]|25[0-5])$")
|
||||
return regV4.matches(value)
|
||||
}
|
||||
|
||||
@@ -223,12 +215,13 @@ object Utils {
|
||||
addr = addr.drop(1)
|
||||
addr = addr.dropLast(addr.count() - addr.lastIndexOf("]"))
|
||||
}
|
||||
val regV6 = Regex("^((?:[0-9A-Fa-f]{1,4}))?((?::[0-9A-Fa-f]{1,4}))*::((?:[0-9A-Fa-f]{1,4}))?((?::[0-9A-Fa-f]{1,4}))*|((?:[0-9A-Fa-f]{1,4}))((?::[0-9A-Fa-f]{1,4})){7}$")
|
||||
val regV6 =
|
||||
Regex("^((?:[0-9A-Fa-f]{1,4}))?((?::[0-9A-Fa-f]{1,4}))*::((?:[0-9A-Fa-f]{1,4}))?((?::[0-9A-Fa-f]{1,4}))*|((?:[0-9A-Fa-f]{1,4}))((?::[0-9A-Fa-f]{1,4})){7}$")
|
||||
return regV6.matches(addr)
|
||||
}
|
||||
|
||||
private fun isCoreDNSAddress(s: String): Boolean {
|
||||
return s.startsWith("https") || s.startsWith("tcp") || s.startsWith("quic")
|
||||
return s.startsWith("https") || s.startsWith("tcp") || s.startsWith("quic") || s == "localhost"
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -236,7 +229,13 @@ object Utils {
|
||||
*/
|
||||
fun isValidUrl(value: String?): Boolean {
|
||||
try {
|
||||
if (value != null && Patterns.WEB_URL.matcher(value).matches() || URLUtil.isValidUrl(value)) {
|
||||
if (value.isNullOrEmpty()) {
|
||||
return false
|
||||
}
|
||||
if (Patterns.WEB_URL.matcher(value).matches()
|
||||
|| Patterns.DOMAIN_NAME.matcher(value).matches()
|
||||
|| URLUtil.isValidUrl(value)
|
||||
) {
|
||||
return true
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
@@ -282,7 +281,7 @@ object Utils {
|
||||
|
||||
fun urlDecode(url: String): String {
|
||||
return try {
|
||||
URLDecoder.decode(url, "UTF-8")
|
||||
URLDecoder.decode(url, Charsets.UTF_8.toString())
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
url
|
||||
@@ -291,7 +290,7 @@ object Utils {
|
||||
|
||||
fun urlEncode(url: String): String {
|
||||
return try {
|
||||
URLEncoder.encode(url, "UTF-8")
|
||||
URLEncoder.encode(url, Charsets.UTF_8.toString())
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
url
|
||||
@@ -303,8 +302,7 @@ object Utils {
|
||||
* readTextFromAssets
|
||||
*/
|
||||
fun readTextFromAssets(context: Context?, fileName: String): String {
|
||||
if(context == null)
|
||||
{
|
||||
if (context == null) {
|
||||
return ""
|
||||
}
|
||||
val content = context.assets.open(fileName).bufferedReader().use {
|
||||
@@ -330,7 +328,7 @@ object Utils {
|
||||
}
|
||||
|
||||
fun getDeviceIdForXUDPBaseKey(): String {
|
||||
val androidId = Settings.Secure.ANDROID_ID.toByteArray(charset("UTF-8"))
|
||||
val androidId = Settings.Secure.ANDROID_ID.toByteArray(Charsets.UTF_8)
|
||||
return Base64.encodeToString(androidId.copyOf(32), Base64.NO_PADDING.or(Base64.URL_SAFE))
|
||||
}
|
||||
|
||||
@@ -373,8 +371,10 @@ object Utils {
|
||||
conn.setRequestProperty("Connection", "close")
|
||||
conn.setRequestProperty("User-agent", "v2rayNG/${BuildConfig.VERSION_NAME}")
|
||||
url.userInfo?.let {
|
||||
conn.setRequestProperty("Authorization",
|
||||
"Basic ${encode(urlDecode(it))}")
|
||||
conn.setRequestProperty(
|
||||
"Authorization",
|
||||
"Basic ${encode(urlDecode(it))}"
|
||||
)
|
||||
}
|
||||
conn.useCaches = false
|
||||
return conn.inputStream.use {
|
||||
@@ -383,10 +383,10 @@ object Utils {
|
||||
}
|
||||
|
||||
fun getDarkModeStatus(context: Context): Boolean {
|
||||
val mode = context.resources.configuration.uiMode and UI_MODE_NIGHT_MASK
|
||||
return mode != UI_MODE_NIGHT_NO
|
||||
return context.resources.configuration.uiMode and UI_MODE_NIGHT_MASK != UI_MODE_NIGHT_NO
|
||||
}
|
||||
|
||||
|
||||
fun setNightMode(context: Context) {
|
||||
when (settingsStorage?.decodeString(AppConfig.PREF_UI_MODE_NIGHT, "0")) {
|
||||
"0" -> AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM)
|
||||
@@ -406,17 +406,21 @@ object Utils {
|
||||
}
|
||||
}
|
||||
|
||||
fun getLocale(context: Context): Locale =
|
||||
when (settingsStorage?.decodeString(AppConfig.PREF_LANGUAGE) ?: "auto") {
|
||||
fun getLocale(): Locale {
|
||||
val lang = settingsStorage?.decodeString(AppConfig.PREF_LANGUAGE) ?: "auto"
|
||||
return when (lang) {
|
||||
"auto" -> getSysLocale()
|
||||
"en" -> Locale("en")
|
||||
"zh-rCN" -> Locale("zh", "CN")
|
||||
"zh-rTW" -> Locale("zh", "TW")
|
||||
"en" -> Locale.ENGLISH
|
||||
"zh-rCN" -> Locale.CHINA
|
||||
"zh-rTW" -> Locale.TRADITIONAL_CHINESE
|
||||
"vi" -> Locale("vi")
|
||||
"ru" -> Locale("ru")
|
||||
"fa" -> Locale("fa")
|
||||
"bn" -> Locale("bn")
|
||||
else -> getSysLocale()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private fun getSysLocale(): Locale = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||
LocaleList.getDefault()[0]
|
||||
@@ -443,18 +447,14 @@ object Utils {
|
||||
fun isTv(context: Context): Boolean =
|
||||
context.packageManager.hasSystemFeature(PackageManager.FEATURE_LEANBACK)
|
||||
|
||||
fun getDelayTestUrl(): String {
|
||||
val url = settingsStorage.decodeString(AppConfig.PREF_DELAY_TEST_URL)
|
||||
return if (url.isNullOrEmpty()) AppConfig.DelayTestUrl else url
|
||||
}
|
||||
|
||||
fun getDelayTestUrl(second: Boolean = false): String {
|
||||
return if (second) {
|
||||
AppConfig.DelayTestUrl2
|
||||
} else {
|
||||
val url = settingsStorage.decodeString(AppConfig.PREF_DELAY_TEST_URL)
|
||||
if (url.isNullOrEmpty()) AppConfig.DelayTestUrl else url
|
||||
}
|
||||
settingsStorage.decodeString(AppConfig.PREF_DELAY_TEST_URL) ?: AppConfig.DelayTestUrl
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -8,8 +8,10 @@ import com.tencent.mmkv.MMKV
|
||||
import com.v2ray.ang.AppConfig
|
||||
import com.v2ray.ang.AppConfig.ANG_PACKAGE
|
||||
import com.v2ray.ang.AppConfig.PROTOCOL_FREEDOM
|
||||
import com.v2ray.ang.AppConfig.TAG_BLOCKED
|
||||
import com.v2ray.ang.AppConfig.TAG_DIRECT
|
||||
import com.v2ray.ang.AppConfig.TAG_FRAGMENT
|
||||
import com.v2ray.ang.AppConfig.TAG_PROXY
|
||||
import com.v2ray.ang.AppConfig.WIREGUARD_LOCAL_ADDRESS_V4
|
||||
import com.v2ray.ang.AppConfig.WIREGUARD_LOCAL_ADDRESS_V6
|
||||
import com.v2ray.ang.dto.EConfigType
|
||||
@@ -52,10 +54,12 @@ object V2rayConfigUtil {
|
||||
}
|
||||
val outbound = config.getProxyOutbound() ?: return Result(false, "")
|
||||
val address = outbound.getServerAddress() ?: return Result(false, "")
|
||||
if (!Utils.isIpAddress(address) && !Utils.isValidUrl(address)) {
|
||||
if (!Utils.isIpAddress(address)) {
|
||||
if (!Utils.isValidUrl(address)) {
|
||||
Log.d(ANG_PACKAGE, "$address is an invalid ip or domain")
|
||||
return Result(false, "")
|
||||
}
|
||||
}
|
||||
|
||||
val result = getV2rayNonCustomConfig(context, outbound, config.remarks)
|
||||
//Log.d(ANG_PACKAGE, result.content)
|
||||
@@ -142,7 +146,8 @@ object V2rayConfigUtil {
|
||||
settingsStorage?.decodeBool(AppConfig.PREF_SNIFFING_ENABLED, true)
|
||||
?: true
|
||||
v2rayConfig.inbounds[0].sniffing?.enabled = fakedns || sniffAllTlsAndHttp
|
||||
v2rayConfig.inbounds[0].sniffing?.routeOnly = settingsStorage?.decodeBool(AppConfig.PREF_ROUTE_ONLY_ENABLED, false)
|
||||
v2rayConfig.inbounds[0].sniffing?.routeOnly =
|
||||
settingsStorage?.decodeBool(AppConfig.PREF_ROUTE_ONLY_ENABLED, false)
|
||||
if (!sniffAllTlsAndHttp) {
|
||||
v2rayConfig.inbounds[0].sniffing?.destOverride?.clear()
|
||||
}
|
||||
@@ -170,10 +175,6 @@ object V2rayConfigUtil {
|
||||
&& settingsStorage?.decodeBool(AppConfig.PREF_FAKE_DNS_ENABLED) == true
|
||||
) {
|
||||
v2rayConfig.fakedns = listOf(V2rayConfig.FakednsBean())
|
||||
v2rayConfig.outbounds.filter { it.protocol == PROTOCOL_FREEDOM && it.tag == TAG_DIRECT }
|
||||
.forEach {
|
||||
it.settings?.domainStrategy = "UseIP"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -182,65 +183,82 @@ object V2rayConfigUtil {
|
||||
*/
|
||||
private fun routing(v2rayConfig: V2rayConfig): Boolean {
|
||||
try {
|
||||
val routingMode = settingsStorage?.decodeString(AppConfig.PREF_ROUTING_MODE)
|
||||
?: ERoutingMode.BYPASS_LAN_MAINLAND.value
|
||||
|
||||
routingUserRule(
|
||||
settingsStorage?.decodeString(AppConfig.PREF_V2RAY_ROUTING_BLOCKED)
|
||||
.orEmpty(), TAG_BLOCKED, v2rayConfig
|
||||
)
|
||||
if (routingMode == ERoutingMode.GLOBAL_DIRECT.value) {
|
||||
routingUserRule(
|
||||
settingsStorage?.decodeString(AppConfig.PREF_V2RAY_ROUTING_DIRECT)
|
||||
.orEmpty(), TAG_DIRECT, v2rayConfig
|
||||
)
|
||||
routingUserRule(
|
||||
settingsStorage?.decodeString(AppConfig.PREF_V2RAY_ROUTING_AGENT)
|
||||
?: "", AppConfig.TAG_PROXY, v2rayConfig
|
||||
.orEmpty(), TAG_PROXY, v2rayConfig
|
||||
)
|
||||
} else {
|
||||
routingUserRule(
|
||||
settingsStorage?.decodeString(AppConfig.PREF_V2RAY_ROUTING_AGENT)
|
||||
.orEmpty(), TAG_PROXY, v2rayConfig
|
||||
)
|
||||
routingUserRule(
|
||||
settingsStorage?.decodeString(AppConfig.PREF_V2RAY_ROUTING_DIRECT)
|
||||
?: "", AppConfig.TAG_DIRECT, v2rayConfig
|
||||
)
|
||||
routingUserRule(
|
||||
settingsStorage?.decodeString(AppConfig.PREF_V2RAY_ROUTING_BLOCKED)
|
||||
?: "", AppConfig.TAG_BLOCKED, v2rayConfig
|
||||
.orEmpty(), TAG_DIRECT, v2rayConfig
|
||||
)
|
||||
}
|
||||
|
||||
v2rayConfig.routing.domainStrategy =
|
||||
settingsStorage?.decodeString(AppConfig.PREF_ROUTING_DOMAIN_STRATEGY)
|
||||
?: "IPIfNonMatch"
|
||||
// v2rayConfig.routing.domainMatcher = "mph"
|
||||
val routingMode = settingsStorage?.decodeString(AppConfig.PREF_ROUTING_MODE)
|
||||
?: ERoutingMode.BYPASS_LAN_MAINLAND.value
|
||||
|
||||
// Hardcode googleapis.cn gstatic.com
|
||||
val googleapisRoute = V2rayConfig.RoutingBean.RulesBean(
|
||||
outboundTag = AppConfig.TAG_PROXY,
|
||||
outboundTag = TAG_PROXY,
|
||||
domain = arrayListOf("domain:googleapis.cn", "domain:gstatic.com")
|
||||
)
|
||||
|
||||
when (routingMode) {
|
||||
ERoutingMode.BYPASS_LAN.value -> {
|
||||
routingGeo("ip", "private", AppConfig.TAG_DIRECT, v2rayConfig)
|
||||
routingGeo("", "private", TAG_DIRECT, v2rayConfig)
|
||||
}
|
||||
|
||||
ERoutingMode.BYPASS_MAINLAND.value -> {
|
||||
routingGeo("", "cn", AppConfig.TAG_DIRECT, v2rayConfig)
|
||||
routingGeo("domain", "geolocation-cn", AppConfig.TAG_DIRECT, v2rayConfig)
|
||||
routingGeo("", "cn", TAG_DIRECT, v2rayConfig)
|
||||
v2rayConfig.routing.rules.add(0, googleapisRoute)
|
||||
}
|
||||
|
||||
ERoutingMode.BYPASS_LAN_MAINLAND.value -> {
|
||||
routingGeo("ip", "private", AppConfig.TAG_DIRECT, v2rayConfig)
|
||||
routingGeo("", "cn", AppConfig.TAG_DIRECT, v2rayConfig)
|
||||
routingGeo("domain", "geolocation-cn", AppConfig.TAG_DIRECT, v2rayConfig)
|
||||
routingGeo("", "private", TAG_DIRECT, v2rayConfig)
|
||||
routingGeo("", "cn", TAG_DIRECT, v2rayConfig)
|
||||
v2rayConfig.routing.rules.add(0, googleapisRoute)
|
||||
}
|
||||
|
||||
ERoutingMode.GLOBAL_DIRECT.value -> {
|
||||
val globalDirect = V2rayConfig.RoutingBean.RulesBean(
|
||||
outboundTag = AppConfig.TAG_DIRECT,
|
||||
port = "0-65535"
|
||||
outboundTag = TAG_DIRECT,
|
||||
)
|
||||
if (v2rayConfig.routing.domainStrategy != "IPIfNonMatch") {
|
||||
globalDirect.port = "0-65535"
|
||||
} else {
|
||||
globalDirect.ip = arrayListOf("0.0.0.0/0", "::/0")
|
||||
}
|
||||
v2rayConfig.routing.rules.add(globalDirect)
|
||||
}
|
||||
}
|
||||
|
||||
if (routingMode != ERoutingMode.GLOBAL_DIRECT.value) {
|
||||
v2rayConfig.routing.rules.add(
|
||||
V2rayConfig.RoutingBean.RulesBean(
|
||||
outboundTag = AppConfig.TAG_PROXY,
|
||||
port = "0-65535"
|
||||
))
|
||||
val globalProxy = V2rayConfig.RoutingBean.RulesBean(
|
||||
outboundTag = TAG_PROXY,
|
||||
)
|
||||
if (v2rayConfig.routing.domainStrategy != "IPIfNonMatch") {
|
||||
globalProxy.port = "0-65535"
|
||||
} else {
|
||||
globalProxy.ip = arrayListOf("0.0.0.0/0", "::/0")
|
||||
}
|
||||
v2rayConfig.routing.rules.add(globalProxy)
|
||||
}
|
||||
|
||||
} catch (e: Exception) {
|
||||
@@ -315,7 +333,7 @@ object V2rayConfigUtil {
|
||||
}
|
||||
}
|
||||
|
||||
private fun userRule2Domian(userRule: String): ArrayList<String> {
|
||||
private fun userRule2Domain(userRule: String): ArrayList<String> {
|
||||
val domain = ArrayList<String>()
|
||||
userRule.split(",").map { it.trim() }.forEach {
|
||||
if (it.startsWith("geosite:") || it.startsWith("domain:")) {
|
||||
@@ -332,13 +350,13 @@ object V2rayConfigUtil {
|
||||
try {
|
||||
if (settingsStorage?.decodeBool(AppConfig.PREF_FAKE_DNS_ENABLED) == true) {
|
||||
val geositeCn = arrayListOf("geosite:cn")
|
||||
val proxyDomain = userRule2Domian(
|
||||
val proxyDomain = userRule2Domain(
|
||||
settingsStorage?.decodeString(AppConfig.PREF_V2RAY_ROUTING_AGENT)
|
||||
?: ""
|
||||
.orEmpty()
|
||||
)
|
||||
val directDomain = userRule2Domian(
|
||||
val directDomain = userRule2Domain(
|
||||
settingsStorage?.decodeString(AppConfig.PREF_V2RAY_ROUTING_DIRECT)
|
||||
?: ""
|
||||
.orEmpty()
|
||||
)
|
||||
// fakedns with all domains to make it always top priority
|
||||
v2rayConfig.dns.servers?.add(
|
||||
@@ -405,14 +423,14 @@ object V2rayConfigUtil {
|
||||
|
||||
private fun dns(v2rayConfig: V2rayConfig): Boolean {
|
||||
try {
|
||||
val hosts = mutableMapOf<String, String>()
|
||||
val hosts = mutableMapOf<String, Any>()
|
||||
val servers = ArrayList<Any>()
|
||||
|
||||
//remote Dns
|
||||
val remoteDns = Utils.getRemoteDnsServers()
|
||||
val proxyDomain = userRule2Domian(
|
||||
val proxyDomain = userRule2Domain(
|
||||
settingsStorage?.decodeString(AppConfig.PREF_V2RAY_ROUTING_AGENT)
|
||||
?: ""
|
||||
.orEmpty()
|
||||
)
|
||||
remoteDns.forEach {
|
||||
servers.add(it)
|
||||
@@ -430,9 +448,9 @@ object V2rayConfigUtil {
|
||||
|
||||
// domestic DNS
|
||||
val domesticDns = Utils.getDomesticDnsServers()
|
||||
val directDomain = userRule2Domian(
|
||||
val directDomain = userRule2Domain(
|
||||
settingsStorage?.decodeString(AppConfig.PREF_V2RAY_ROUTING_DIRECT)
|
||||
?: ""
|
||||
.orEmpty()
|
||||
)
|
||||
val routingMode = settingsStorage?.decodeString(AppConfig.PREF_ROUTING_MODE)
|
||||
?: ERoutingMode.BYPASS_LAN_MAINLAND.value
|
||||
@@ -451,7 +469,7 @@ object V2rayConfigUtil {
|
||||
)
|
||||
}
|
||||
if (isCnRoutingMode) {
|
||||
val geositeCn = arrayListOf("geosite:cn", "geosite:geolocation-cn")
|
||||
val geositeCn = arrayListOf("geosite:cn")
|
||||
servers.add(
|
||||
V2rayConfig.DnsBean.ServersBean(
|
||||
domesticDns.first(),
|
||||
@@ -465,7 +483,7 @@ object V2rayConfigUtil {
|
||||
if (Utils.isPureIpAddress(domesticDns.first())) {
|
||||
v2rayConfig.routing.rules.add(
|
||||
0, V2rayConfig.RoutingBean.RulesBean(
|
||||
outboundTag = AppConfig.TAG_DIRECT,
|
||||
outboundTag = TAG_DIRECT,
|
||||
port = "53",
|
||||
ip = arrayListOf(domesticDns.first()),
|
||||
domain = null
|
||||
@@ -474,9 +492,9 @@ object V2rayConfigUtil {
|
||||
}
|
||||
|
||||
//block dns
|
||||
val blkDomain = userRule2Domian(
|
||||
val blkDomain = userRule2Domain(
|
||||
settingsStorage?.decodeString(AppConfig.PREF_V2RAY_ROUTING_BLOCKED)
|
||||
?: ""
|
||||
.orEmpty()
|
||||
)
|
||||
if (blkDomain.size > 0) {
|
||||
hosts.putAll(blkDomain.map { it to "127.0.0.1" })
|
||||
@@ -485,6 +503,12 @@ object V2rayConfigUtil {
|
||||
// hardcode googleapi rule to fix play store problems
|
||||
hosts["domain:googleapis.cn"] = "googleapis.com"
|
||||
|
||||
// hardcode popular Android Private DNS rule to fix localhost DNS problem
|
||||
hosts["dns.pub"] = arrayListOf("1.12.12.12", "120.53.53.53")
|
||||
hosts["dns.alidns.com"] = arrayListOf("223.5.5.5", "223.6.6.6", "2400:3200::1", "2400:3200:baba::1")
|
||||
hosts["one.one.one.one"] = arrayListOf("1.1.1.1", "1.0.0.1", "2606:4700:4700::1111", "2606:4700:4700::1001")
|
||||
hosts["dns.google"] = arrayListOf("8.8.8.8", "8.8.4.4", "2001:4860:4860::8888", "2001:4860:4860::8844")
|
||||
|
||||
// DNS dns对象
|
||||
v2rayConfig.dns = V2rayConfig.DnsBean(
|
||||
servers = servers,
|
||||
@@ -495,7 +519,7 @@ object V2rayConfigUtil {
|
||||
if (Utils.isPureIpAddress(remoteDns.first())) {
|
||||
v2rayConfig.routing.rules.add(
|
||||
0, V2rayConfig.RoutingBean.RulesBean(
|
||||
outboundTag = AppConfig.TAG_PROXY,
|
||||
outboundTag = TAG_PROXY,
|
||||
port = "53",
|
||||
ip = arrayListOf(remoteDns.first()),
|
||||
domain = null
|
||||
@@ -597,7 +621,8 @@ object V2rayConfigUtil {
|
||||
mux = null
|
||||
)
|
||||
|
||||
var packets = settingsStorage?.decodeString(AppConfig.PREF_FRAGMENT_PACKETS) ?: "tlshello"
|
||||
var packets =
|
||||
settingsStorage?.decodeString(AppConfig.PREF_FRAGMENT_PACKETS) ?: "tlshello"
|
||||
if (v2rayConfig.outbounds[0].streamSettings?.security == V2rayConfig.REALITY
|
||||
&& packets == "tlshello"
|
||||
) {
|
||||
|
||||
@@ -67,7 +67,7 @@ object ShadowsocksFmt {
|
||||
private fun tryResolveResolveSip002(str: String, config: ServerConfig): Boolean {
|
||||
try {
|
||||
val uri = URI(Utils.fixIllegalUrl(str))
|
||||
config.remarks = Utils.urlDecode(uri.fragment ?: "")
|
||||
config.remarks = Utils.urlDecode(uri.fragment.orEmpty())
|
||||
|
||||
val method: String
|
||||
val password: String
|
||||
@@ -88,7 +88,7 @@ object ShadowsocksFmt {
|
||||
password = base64Decode.substringAfter(":")
|
||||
}
|
||||
|
||||
val query = Utils.urlDecode(uri.query ?: "")
|
||||
val query = Utils.urlDecode(uri.query.orEmpty())
|
||||
if (query != "") {
|
||||
val queryPairs = HashMap<String, String>()
|
||||
val pairs = query.split(";")
|
||||
@@ -96,7 +96,7 @@ object ShadowsocksFmt {
|
||||
for (pair in pairs) {
|
||||
val idx = pair.indexOf("=")
|
||||
if (idx == -1) {
|
||||
queryPairs[Utils.urlDecode(pair)] = "";
|
||||
queryPairs[Utils.urlDecode(pair)] = ""
|
||||
} else {
|
||||
queryPairs[Utils.urlDecode(pair.substring(0, idx))] =
|
||||
Utils.urlDecode(pair.substring(idx + 1))
|
||||
@@ -118,9 +118,9 @@ object ShadowsocksFmt {
|
||||
null
|
||||
)
|
||||
} else if (queryPairs["plugin"] == "v2ray-plugin") {
|
||||
var network = "ws";
|
||||
var network = "ws"
|
||||
if (queryPairs["mode"] == "quic") {
|
||||
network = "quic";
|
||||
network = "quic"
|
||||
}
|
||||
sni = config.outboundBean?.streamSettings?.populateTransportSettings(
|
||||
network,
|
||||
@@ -137,7 +137,7 @@ object ShadowsocksFmt {
|
||||
}
|
||||
if ("tls" in queryPairs) {
|
||||
config.outboundBean?.streamSettings?.populateTlsSettings(
|
||||
"tls", false, sni ?: "", null, null, null, null, null
|
||||
"tls", false, sni.orEmpty(), null, null, null, null, null
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -19,19 +19,25 @@ object TrojanFmt {
|
||||
)
|
||||
}
|
||||
|
||||
fun parseTrojan(str: String): ServerConfig? {
|
||||
val allowInsecure = settingsStorage?.decodeBool(AppConfig.PREF_ALLOW_INSECURE) ?: false
|
||||
fun parseTrojan(str: String): ServerConfig {
|
||||
var allowInsecure = settingsStorage?.decodeBool(AppConfig.PREF_ALLOW_INSECURE) ?: false
|
||||
val config = ServerConfig.create(EConfigType.TROJAN)
|
||||
|
||||
val uri = URI(Utils.fixIllegalUrl(str))
|
||||
config.remarks = Utils.urlDecode(uri.fragment ?: "")
|
||||
config.remarks = Utils.urlDecode(uri.fragment.orEmpty())
|
||||
|
||||
var flow = ""
|
||||
var fingerprint = config.outboundBean?.streamSettings?.tlsSettings?.fingerprint
|
||||
if (uri.rawQuery.isNullOrEmpty()) {
|
||||
config.outboundBean?.streamSettings?.populateTlsSettings(
|
||||
V2rayConfig.TLS, allowInsecure, "",
|
||||
fingerprint, null, null, null, null
|
||||
V2rayConfig.TLS,
|
||||
allowInsecure,
|
||||
"",
|
||||
fingerprint,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null
|
||||
)
|
||||
} else {
|
||||
val queryParam = uri.rawQuery.split("&")
|
||||
@@ -49,15 +55,20 @@ object TrojanFmt {
|
||||
queryParam["serviceName"],
|
||||
queryParam["authority"]
|
||||
)
|
||||
fingerprint = queryParam["fp"] ?: ""
|
||||
fingerprint = queryParam["fp"].orEmpty()
|
||||
allowInsecure = if ((queryParam["allowInsecure"].orEmpty()) == "1") true else allowInsecure
|
||||
config.outboundBean?.streamSettings?.populateTlsSettings(
|
||||
queryParam["security"] ?: V2rayConfig.TLS,
|
||||
allowInsecure, queryParam["sni"] ?: sni?:"", fingerprint, queryParam["alpn"],
|
||||
null, null, null
|
||||
allowInsecure,
|
||||
queryParam["sni"] ?: sni.orEmpty(),
|
||||
fingerprint,
|
||||
queryParam["alpn"],
|
||||
null,
|
||||
null,
|
||||
null
|
||||
)
|
||||
flow = queryParam["flow"] ?: ""
|
||||
flow = queryParam["flow"].orEmpty()
|
||||
}
|
||||
|
||||
config.outboundBean?.settings?.servers?.get(0)?.let { server ->
|
||||
server.address = uri.idnHost
|
||||
server.port = uri.port
|
||||
@@ -78,7 +89,6 @@ object TrojanFmt {
|
||||
if (!TextUtils.isEmpty(it)) {
|
||||
dicQuery["flow"] = it
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
dicQuery["security"] = streamSetting.security.ifEmpty { "none" }
|
||||
@@ -92,16 +102,16 @@ object TrojanFmt {
|
||||
Utils.removeWhiteSpace(tlsSetting.alpn.joinToString()).orEmpty()
|
||||
}
|
||||
if (!TextUtils.isEmpty(tlsSetting.fingerprint)) {
|
||||
dicQuery["fp"] = tlsSetting.fingerprint?:""
|
||||
dicQuery["fp"] = tlsSetting.fingerprint.orEmpty()
|
||||
}
|
||||
if (!TextUtils.isEmpty(tlsSetting.publicKey)) {
|
||||
dicQuery["pbk"] = tlsSetting.publicKey?:""
|
||||
dicQuery["pbk"] = tlsSetting.publicKey.orEmpty()
|
||||
}
|
||||
if (!TextUtils.isEmpty(tlsSetting.shortId)) {
|
||||
dicQuery["sid"] = tlsSetting.shortId?:""
|
||||
dicQuery["sid"] = tlsSetting.shortId.orEmpty()
|
||||
}
|
||||
if (!TextUtils.isEmpty(tlsSetting.spiderX)) {
|
||||
dicQuery["spx"] = Utils.urlEncode(tlsSetting.spiderX?:"")
|
||||
dicQuery["spx"] = Utils.urlEncode(tlsSetting.spiderX.orEmpty())
|
||||
}
|
||||
}
|
||||
dicQuery["type"] =
|
||||
|
||||
@@ -20,7 +20,7 @@ object VlessFmt {
|
||||
}
|
||||
|
||||
fun parseVless(str: String): ServerConfig? {
|
||||
val allowInsecure = settingsStorage?.decodeBool(AppConfig.PREF_ALLOW_INSECURE) ?: false
|
||||
var allowInsecure = settingsStorage?.decodeBool(AppConfig.PREF_ALLOW_INSECURE) ?: false
|
||||
val config = ServerConfig.create(EConfigType.VLESS)
|
||||
|
||||
val uri = URI(Utils.fixIllegalUrl(str))
|
||||
@@ -30,13 +30,13 @@ object VlessFmt {
|
||||
|
||||
val streamSetting = config.outboundBean?.streamSettings ?: return null
|
||||
|
||||
config.remarks = Utils.urlDecode(uri.fragment ?: "")
|
||||
config.outboundBean?.settings?.vnext?.get(0)?.let { vnext ->
|
||||
config.remarks = Utils.urlDecode(uri.fragment.orEmpty())
|
||||
config.outboundBean.settings?.vnext?.get(0)?.let { vnext ->
|
||||
vnext.address = uri.idnHost
|
||||
vnext.port = uri.port
|
||||
vnext.users[0].id = uri.userInfo
|
||||
vnext.users[0].encryption = queryParam["encryption"] ?: "none"
|
||||
vnext.users[0].flow = queryParam["flow"] ?: ""
|
||||
vnext.users[0].flow = queryParam["flow"].orEmpty()
|
||||
}
|
||||
|
||||
val sni = streamSetting.populateTransportSettings(
|
||||
@@ -51,15 +51,16 @@ object VlessFmt {
|
||||
queryParam["serviceName"],
|
||||
queryParam["authority"]
|
||||
)
|
||||
allowInsecure = if ((queryParam["allowInsecure"].orEmpty()) == "1") true else allowInsecure
|
||||
streamSetting.populateTlsSettings(
|
||||
queryParam["security"] ?: "",
|
||||
queryParam["security"].orEmpty(),
|
||||
allowInsecure,
|
||||
queryParam["sni"] ?: sni,
|
||||
queryParam["fp"] ?: "",
|
||||
queryParam["fp"].orEmpty(),
|
||||
queryParam["alpn"],
|
||||
queryParam["pbk"] ?: "",
|
||||
queryParam["sid"] ?: "",
|
||||
queryParam["spx"] ?: ""
|
||||
queryParam["pbk"].orEmpty(),
|
||||
queryParam["sid"].orEmpty(),
|
||||
queryParam["spx"].orEmpty()
|
||||
)
|
||||
|
||||
return config
|
||||
@@ -92,16 +93,16 @@ object VlessFmt {
|
||||
Utils.removeWhiteSpace(tlsSetting.alpn.joinToString()).orEmpty()
|
||||
}
|
||||
if (!TextUtils.isEmpty(tlsSetting.fingerprint)) {
|
||||
dicQuery["fp"] = tlsSetting.fingerprint?:""
|
||||
dicQuery["fp"] = tlsSetting.fingerprint.orEmpty()
|
||||
}
|
||||
if (!TextUtils.isEmpty(tlsSetting.publicKey)) {
|
||||
dicQuery["pbk"] = tlsSetting.publicKey?:""
|
||||
dicQuery["pbk"] = tlsSetting.publicKey.orEmpty()
|
||||
}
|
||||
if (!TextUtils.isEmpty(tlsSetting.shortId)) {
|
||||
dicQuery["sid"] = tlsSetting.shortId?:""
|
||||
dicQuery["sid"] = tlsSetting.shortId.orEmpty()
|
||||
}
|
||||
if (!TextUtils.isEmpty(tlsSetting.spiderX)) {
|
||||
dicQuery["spx"] = Utils.urlEncode(tlsSetting.spiderX?:"")
|
||||
dicQuery["spx"] = Utils.urlEncode(tlsSetting.spiderX.orEmpty())
|
||||
}
|
||||
}
|
||||
dicQuery["type"] =
|
||||
|
||||
@@ -22,17 +22,13 @@ object VmessFmt {
|
||||
}
|
||||
|
||||
fun parseVmess(str: String): ServerConfig? {
|
||||
if (str.indexOf('?') > 0 && str.indexOf('&') > 0) {
|
||||
return parseVmessStd(str)
|
||||
}
|
||||
|
||||
val allowInsecure = settingsStorage?.decodeBool(AppConfig.PREF_ALLOW_INSECURE) ?: false
|
||||
val config = ServerConfig.create(EConfigType.VMESS)
|
||||
val streamSetting = config.outboundBean?.streamSettings ?: return null
|
||||
|
||||
if (!tryParseNewVmess(str, config, allowInsecure)) {
|
||||
if (str.indexOf("?") > 0) {
|
||||
if (!tryResolveVmess4Kitsunebi(str, config)) {
|
||||
Log.d(AppConfig.ANG_PACKAGE, "R.string.toast_incorrect_protocol")
|
||||
return null
|
||||
}
|
||||
} else {
|
||||
var result = str.replace(EConfigType.VMESS.protocolScheme, "")
|
||||
result = Utils.decode(result)
|
||||
if (TextUtils.isEmpty(result)) {
|
||||
@@ -41,16 +37,17 @@ object VmessFmt {
|
||||
}
|
||||
val vmessQRCode = Gson().fromJson(result, VmessQRCode::class.java)
|
||||
// Although VmessQRCode fields are non null, looks like Gson may still create null fields
|
||||
if (TextUtils.isEmpty(vmessQRCode.add) || TextUtils.isEmpty(vmessQRCode.port) || TextUtils.isEmpty(
|
||||
vmessQRCode.id
|
||||
) || TextUtils.isEmpty(vmessQRCode.net)
|
||||
if (TextUtils.isEmpty(vmessQRCode.add)
|
||||
|| TextUtils.isEmpty(vmessQRCode.port)
|
||||
|| TextUtils.isEmpty(vmessQRCode.id)
|
||||
|| TextUtils.isEmpty(vmessQRCode.net)
|
||||
) {
|
||||
Log.d(AppConfig.ANG_PACKAGE, "R.string.toast_incorrect_protocol")
|
||||
return null
|
||||
}
|
||||
|
||||
config.remarks = vmessQRCode.ps
|
||||
config.outboundBean?.settings?.vnext?.get(0)?.let { vnext ->
|
||||
config.outboundBean.settings?.vnext?.get(0)?.let { vnext ->
|
||||
vnext.address = vmessQRCode.add
|
||||
vnext.port = Utils.parseInt(vmessQRCode.port)
|
||||
vnext.users[0].id = vmessQRCode.id
|
||||
@@ -71,7 +68,7 @@ object VmessFmt {
|
||||
vmessQRCode.host
|
||||
)
|
||||
|
||||
val fingerprint = vmessQRCode.fp ?: streamSetting.tlsSettings?.fingerprint
|
||||
val fingerprint = vmessQRCode.fp
|
||||
streamSetting.populateTlsSettings(
|
||||
vmessQRCode.tls,
|
||||
allowInsecure,
|
||||
@@ -82,8 +79,7 @@ object VmessFmt {
|
||||
null,
|
||||
null
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return config
|
||||
}
|
||||
|
||||
@@ -114,80 +110,51 @@ object VmessFmt {
|
||||
return Utils.encode(json)
|
||||
}
|
||||
|
||||
private fun tryParseNewVmess(
|
||||
uriString: String, config: ServerConfig, allowInsecure: Boolean
|
||||
): Boolean {
|
||||
return runCatching {
|
||||
val uri = URI(Utils.fixIllegalUrl(uriString))
|
||||
check(uri.scheme == "vmess")
|
||||
val (_, protocol, tlsStr, uuid, alterId) = Regex("(tcp|http|ws|kcp|quic|grpc)(\\+tls)?:([0-9a-z]{8}-[0-9a-z]{4}-[0-9a-z]{4}-[0-9a-z]{4}-[0-9a-z]{12})").matchEntire(
|
||||
uri.userInfo
|
||||
)?.groupValues ?: error("parse user info fail.")
|
||||
val tls = tlsStr.isNotBlank()
|
||||
fun parseVmessStd(str: String): ServerConfig? {
|
||||
var allowInsecure = settingsStorage?.decodeBool(AppConfig.PREF_ALLOW_INSECURE) ?: false
|
||||
val config = ServerConfig.create(EConfigType.VMESS)
|
||||
|
||||
val uri = URI(Utils.fixIllegalUrl(str))
|
||||
if (uri.rawQuery.isNullOrEmpty()) return null
|
||||
val queryParam = uri.rawQuery.split("&")
|
||||
.associate { it.split("=").let { (k, v) -> k to Utils.urlDecode(v) } }
|
||||
|
||||
val streamSetting = config.outboundBean?.streamSettings ?: return false
|
||||
config.remarks = Utils.urlDecode(uri.fragment ?: "")
|
||||
val streamSetting = config.outboundBean?.streamSettings ?: return null
|
||||
|
||||
config.remarks = Utils.urlDecode(uri.fragment.orEmpty())
|
||||
config.outboundBean.settings?.vnext?.get(0)?.let { vnext ->
|
||||
vnext.address = uri.idnHost
|
||||
vnext.port = uri.port
|
||||
vnext.users[0].id = uuid
|
||||
vnext.users[0].id = uri.userInfo
|
||||
vnext.users[0].security = V2rayConfig.DEFAULT_SECURITY
|
||||
vnext.users[0].alterId = alterId.toInt()
|
||||
vnext.users[0].alterId = 0
|
||||
}
|
||||
var fingerprint = streamSetting.tlsSettings?.fingerprint
|
||||
val sni = streamSetting.populateTransportSettings(protocol,
|
||||
queryParam["type"],
|
||||
queryParam["host"]?.split("|")?.get(0) ?: "",
|
||||
queryParam["path"]?.takeIf { it.trim() != "/" } ?: "",
|
||||
|
||||
val sni = streamSetting.populateTransportSettings(
|
||||
queryParam["type"] ?: "tcp",
|
||||
queryParam["headerType"],
|
||||
queryParam["host"],
|
||||
queryParam["path"],
|
||||
queryParam["seed"],
|
||||
queryParam["security"],
|
||||
queryParam["quicSecurity"],
|
||||
queryParam["key"],
|
||||
queryParam["mode"],
|
||||
queryParam["serviceName"],
|
||||
queryParam["authority"])
|
||||
queryParam["authority"]
|
||||
)
|
||||
|
||||
allowInsecure = if ((queryParam["allowInsecure"].orEmpty()) == "1") true else allowInsecure
|
||||
streamSetting.populateTlsSettings(
|
||||
if (tls) V2rayConfig.TLS else "",
|
||||
queryParam["security"].orEmpty(),
|
||||
allowInsecure,
|
||||
sni,
|
||||
fingerprint,
|
||||
null,
|
||||
queryParam["sni"] ?: sni,
|
||||
queryParam["fp"].orEmpty(),
|
||||
queryParam["alpn"],
|
||||
null,
|
||||
null,
|
||||
null
|
||||
)
|
||||
true
|
||||
}.getOrElse { false }
|
||||
}
|
||||
|
||||
private fun tryResolveVmess4Kitsunebi(server: String, config: ServerConfig): Boolean {
|
||||
|
||||
var result = server.replace(EConfigType.VMESS.protocolScheme, "")
|
||||
val indexSplit = result.indexOf("?")
|
||||
if (indexSplit > 0) {
|
||||
result = result.substring(0, indexSplit)
|
||||
}
|
||||
result = Utils.decode(result)
|
||||
|
||||
val arr1 = result.split('@')
|
||||
if (arr1.count() != 2) {
|
||||
return false
|
||||
}
|
||||
val arr21 = arr1[0].split(':')
|
||||
val arr22 = arr1[1].split(':')
|
||||
if (arr21.count() != 2) {
|
||||
return false
|
||||
}
|
||||
|
||||
config.remarks = "Alien"
|
||||
config.outboundBean?.settings?.vnext?.get(0)?.let { vnext ->
|
||||
vnext.address = arr22[0]
|
||||
vnext.port = Utils.parseInt(arr22[1])
|
||||
vnext.users[0].id = arr21[1]
|
||||
vnext.users[0].security = arr21[0]
|
||||
vnext.users[0].alterId = 0
|
||||
}
|
||||
return true
|
||||
return config
|
||||
}
|
||||
}
|
||||
@@ -13,7 +13,7 @@ object WireguardFmt {
|
||||
val uri = URI(Utils.fixIllegalUrl(str))
|
||||
if (uri.rawQuery != null) {
|
||||
val config = ServerConfig.create(EConfigType.WIREGUARD)
|
||||
config.remarks = Utils.urlDecode(uri.fragment ?: "")
|
||||
config.remarks = Utils.urlDecode(uri.fragment.orEmpty())
|
||||
|
||||
val queryParam = uri.rawQuery.split("&")
|
||||
.associate { it.split("=").let { (k, v) -> k to Utils.urlDecode(v) } }
|
||||
@@ -24,8 +24,9 @@ object WireguardFmt {
|
||||
(queryParam["address"]
|
||||
?: AppConfig.WIREGUARD_LOCAL_ADDRESS_V4).removeWhiteSpace()
|
||||
.split(",")
|
||||
wireguard.peers?.get(0)?.publicKey = queryParam["publickey"] ?: ""
|
||||
wireguard.peers?.get(0)?.endpoint = Utils.getIpv6Address(uri.idnHost) + ":${uri.port}"
|
||||
wireguard.peers?.get(0)?.publicKey = queryParam["publickey"].orEmpty()
|
||||
wireguard.peers?.get(0)?.endpoint =
|
||||
Utils.getIpv6Address(uri.idnHost) + ":${uri.port}"
|
||||
wireguard.mtu = Utils.parseInt(queryParam["mtu"] ?: AppConfig.WIREGUARD_LOCAL_MTU)
|
||||
wireguard.reserved =
|
||||
(queryParam["reserved"] ?: "0,0,0").removeWhiteSpace().split(",")
|
||||
|
||||
@@ -3,33 +3,32 @@ package com.v2ray.ang.viewmodel
|
||||
import android.app.Application
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.DialogInterface
|
||||
import android.content.Intent
|
||||
import android.content.IntentFilter
|
||||
import android.content.res.AssetManager
|
||||
import android.os.Build
|
||||
import android.util.Log
|
||||
import android.view.LayoutInflater
|
||||
import android.widget.ArrayAdapter
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.lifecycle.AndroidViewModel
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.google.gson.Gson
|
||||
import com.tencent.mmkv.MMKV
|
||||
import com.v2ray.ang.AngApplication
|
||||
import com.v2ray.ang.AppConfig
|
||||
import com.v2ray.ang.AppConfig.ANG_PACKAGE
|
||||
import com.v2ray.ang.R
|
||||
import com.v2ray.ang.databinding.DialogConfigFilterBinding
|
||||
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
|
||||
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
|
||||
@@ -43,34 +42,15 @@ import java.io.FileOutputStream
|
||||
import java.util.Collections
|
||||
|
||||
class MainViewModel(application: Application) : AndroidViewModel(application) {
|
||||
private val mainStorage by lazy {
|
||||
MMKV.mmkvWithID(
|
||||
MmkvManager.ID_MAIN,
|
||||
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
|
||||
)
|
||||
}
|
||||
private var serverList = MmkvManager.decodeServerList()
|
||||
var subscriptionId: String = MmkvManager.settingsStorage.decodeString(AppConfig.CACHE_SUBSCRIPTION_ID, "").orEmpty()
|
||||
|
||||
var serverList = MmkvManager.decodeServerList()
|
||||
var subscriptionId: String = settingsStorage.decodeString(AppConfig.CACHE_SUBSCRIPTION_ID, "")?:""
|
||||
var keywordFilter: String = settingsStorage.decodeString(AppConfig.CACHE_KEYWORD_FILTER, "")?:""
|
||||
private set
|
||||
//var keywordFilter: String = MmkvManager.settingsStorage.decodeString(AppConfig.CACHE_KEYWORD_FILTER, "")?:""
|
||||
var keywordFilter = ""
|
||||
val serversCache = mutableListOf<ServersCache>()
|
||||
val isRunning by lazy { MutableLiveData<Boolean>() }
|
||||
val updateListAction by lazy { MutableLiveData<Int>() }
|
||||
val updateTestResultAction by lazy { MutableLiveData<String>() }
|
||||
|
||||
private val tcpingTestScope by lazy { CoroutineScope(Dispatchers.IO) }
|
||||
|
||||
fun startListenBroadcast() {
|
||||
@@ -124,9 +104,16 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
|
||||
config.fullConfig = Gson().fromJson(server, V2rayConfig::class.java)
|
||||
config.remarks = config.fullConfig?.remarks ?: System.currentTimeMillis().toString()
|
||||
val key = MmkvManager.encodeServerConfig("", config)
|
||||
serverRawStorage?.encode(key, server)
|
||||
MmkvManager.serverRawStorage?.encode(key, server)
|
||||
serverList.add(0, key)
|
||||
serversCache.add(0, ServersCache(key, config))
|
||||
val profile = ProfileItem(
|
||||
configType = config.configType,
|
||||
subscriptionId = config.subscriptionId,
|
||||
remarks = config.remarks,
|
||||
server = config.getProxyOutbound()?.getServerAddress(),
|
||||
serverPort = config.getProxyOutbound()?.getServerPort(),
|
||||
)
|
||||
serversCache.add(0, ServersCache(key, profile))
|
||||
return true
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
@@ -138,24 +125,65 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
|
||||
fun swapServer(fromPosition: Int, toPosition: Int) {
|
||||
Collections.swap(serverList, fromPosition, toPosition)
|
||||
Collections.swap(serversCache, fromPosition, toPosition)
|
||||
mainStorage?.encode(KEY_ANG_CONFIGS, Gson().toJson(serverList))
|
||||
MmkvManager.mainStorage?.encode(KEY_ANG_CONFIGS, Gson().toJson(serverList))
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
fun updateCache() {
|
||||
serversCache.clear()
|
||||
for (guid in serverList) {
|
||||
var profile = MmkvManager.decodeProfileConfig(guid)
|
||||
if (profile == null) {
|
||||
val config = MmkvManager.decodeServerConfig(guid) ?: continue
|
||||
if (subscriptionId.isNotEmpty() && subscriptionId != config.subscriptionId) {
|
||||
profile = ProfileItem(
|
||||
configType = config.configType,
|
||||
subscriptionId = config.subscriptionId,
|
||||
remarks = config.remarks,
|
||||
server = config.getProxyOutbound()?.getServerAddress(),
|
||||
serverPort = config.getProxyOutbound()?.getServerPort(),
|
||||
)
|
||||
MmkvManager.encodeServerConfig(guid, config)
|
||||
}
|
||||
|
||||
if (subscriptionId.isNotEmpty() && subscriptionId != profile.subscriptionId) {
|
||||
continue
|
||||
}
|
||||
|
||||
if (keywordFilter.isEmpty() || config.remarks.contains(keywordFilter)) {
|
||||
serversCache.add(ServersCache(guid, config))
|
||||
if (keywordFilter.isEmpty() || profile.remarks.contains(keywordFilter)) {
|
||||
serversCache.add(ServersCache(guid, profile))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun updateConfigViaSubAll(): Int {
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun exportAllServer(): Int {
|
||||
val serverListCopy =
|
||||
if (subscriptionId.isNullOrEmpty() && keywordFilter.isNullOrEmpty()) {
|
||||
serverList
|
||||
} else {
|
||||
serversCache.map { it.guid }.toList()
|
||||
}
|
||||
|
||||
val ret = AngConfigManager.shareNonCustomConfigsToClipboard(
|
||||
getApplication<AngApplication>(),
|
||||
serverListCopy
|
||||
)
|
||||
return ret
|
||||
}
|
||||
|
||||
|
||||
fun testAllTcping() {
|
||||
tcpingTestScope.coroutineContext[Job]?.cancelChildren()
|
||||
SpeedtestUtil.closeAllTcpSockets()
|
||||
@@ -164,9 +192,9 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
|
||||
|
||||
getApplication<AngApplication>().toast(R.string.connection_test_testing)
|
||||
for (item in serversCache) {
|
||||
item.config.getProxyOutbound()?.let { outbound ->
|
||||
val serverAddress = outbound.getServerAddress()
|
||||
val serverPort = outbound.getServerPort()
|
||||
item.profile.let { outbound ->
|
||||
val serverAddress = outbound.server
|
||||
val serverPort = outbound.serverPort
|
||||
if (serverAddress != null && serverPort != null) {
|
||||
tcpingTestScope.launch {
|
||||
val testResult = SpeedtestUtil.tcping(serverAddress, serverPort)
|
||||
@@ -206,60 +234,30 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
|
||||
MessageUtil.sendMsg2Service(getApplication(), AppConfig.MSG_MEASURE_DELAY, "")
|
||||
}
|
||||
|
||||
fun filterConfig(context: Context) {
|
||||
val subscriptions = MmkvManager.decodeSubscriptions()
|
||||
val listId = subscriptions.map { it.first }.toList().toMutableList()
|
||||
val listRemarks = subscriptions.map { it.second.remarks }.toList().toMutableList()
|
||||
listRemarks += context.getString(R.string.filter_config_all)
|
||||
val checkedItem = if (subscriptionId.isNotEmpty()) {
|
||||
listId.indexOf(subscriptionId)
|
||||
} else {
|
||||
listRemarks.count() - 1
|
||||
}
|
||||
|
||||
val ivBinding = DialogConfigFilterBinding.inflate(LayoutInflater.from(context))
|
||||
ivBinding.spSubscriptionId.adapter = ArrayAdapter<String>(
|
||||
context,
|
||||
android.R.layout.simple_spinner_dropdown_item,
|
||||
listRemarks
|
||||
)
|
||||
ivBinding.spSubscriptionId.setSelection(checkedItem)
|
||||
ivBinding.etKeyword.text = Utils.getEditable(keywordFilter)
|
||||
val builder = AlertDialog.Builder(context).setView(ivBinding.root)
|
||||
builder.setTitle(R.string.title_filter_config)
|
||||
builder.setPositiveButton(R.string.tasker_setting_confirm) { dialogInterface: DialogInterface?, _: Int ->
|
||||
try {
|
||||
val position = ivBinding.spSubscriptionId.selectedItemPosition
|
||||
subscriptionId = if (listRemarks.count() - 1 == position) {
|
||||
""
|
||||
} else {
|
||||
subscriptions[position].first
|
||||
}
|
||||
keywordFilter = ivBinding.etKeyword.text.toString()
|
||||
settingsStorage?.encode(AppConfig.CACHE_SUBSCRIPTION_ID, subscriptionId)
|
||||
settingsStorage?.encode(AppConfig.CACHE_KEYWORD_FILTER, keywordFilter)
|
||||
fun subscriptionIdChanged(id: String) {
|
||||
if (subscriptionId != id) {
|
||||
subscriptionId = id
|
||||
MmkvManager.settingsStorage.encode(AppConfig.CACHE_SUBSCRIPTION_ID, subscriptionId)
|
||||
reloadServerList()
|
||||
}
|
||||
}
|
||||
|
||||
dialogInterface?.dismiss()
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
fun getSubscriptions(context: Context): Pair<MutableList<String>?, MutableList<String>?> {
|
||||
val subscriptions = MmkvManager.decodeSubscriptions()
|
||||
if (subscriptionId.isNotEmpty()
|
||||
&& !subscriptions.map { it.first }.contains(subscriptionId)
|
||||
) {
|
||||
subscriptionIdChanged("")
|
||||
}
|
||||
if (subscriptions.isEmpty()) {
|
||||
return null to null
|
||||
}
|
||||
builder.show()
|
||||
// AlertDialog.Builder(context)
|
||||
// .setSingleChoiceItems(listRemarks.toTypedArray(), checkedItem) { dialog, i ->
|
||||
// try {
|
||||
// subscriptionId = if (listRemarks.count() - 1 == i) {
|
||||
// ""
|
||||
// } else {
|
||||
// subscriptions[i].first
|
||||
// }
|
||||
// reloadServerList()
|
||||
// dialog.dismiss()
|
||||
// } catch (e: Exception) {
|
||||
// e.printStackTrace()
|
||||
// }
|
||||
// }.show()
|
||||
val listId = subscriptions.map { it.first }.toMutableList()
|
||||
listId.add(0, "")
|
||||
val listRemarks = subscriptions.map { it.second.remarks }.toMutableList()
|
||||
listRemarks.add(0, context.getString(R.string.filter_config_all))
|
||||
|
||||
return listId to listRemarks
|
||||
}
|
||||
|
||||
fun getPosition(guid: String): Int {
|
||||
@@ -270,15 +268,21 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
|
||||
return -1
|
||||
}
|
||||
|
||||
fun removeDuplicateServer() {
|
||||
fun removeDuplicateServer(): Int {
|
||||
val serversCacheCopy = mutableListOf<Pair<String, ServerConfig>>()
|
||||
for (it in serversCache) {
|
||||
val config = MmkvManager.decodeServerConfig(it.guid) ?: continue
|
||||
serversCacheCopy.add(Pair(it.guid, config))
|
||||
}
|
||||
|
||||
val deleteServer = mutableListOf<String>()
|
||||
serversCache.forEachIndexed { index, it ->
|
||||
val outbound = it.config.getProxyOutbound()
|
||||
serversCache.forEachIndexed { index2, it2 ->
|
||||
serversCacheCopy.forEachIndexed { index, it ->
|
||||
val outbound = it.second.getProxyOutbound()
|
||||
serversCacheCopy.forEachIndexed { index2, it2 ->
|
||||
if (index2 > index) {
|
||||
val outbound2 = it2.config.getProxyOutbound()
|
||||
if (outbound == outbound2 && !deleteServer.contains(it2.guid)) {
|
||||
deleteServer.add(it2.guid)
|
||||
val outbound2 = it2.second.getProxyOutbound()
|
||||
if (outbound == outbound2 && !deleteServer.contains(it2.first)) {
|
||||
deleteServer.add(it2.first)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -286,14 +290,37 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
|
||||
for (it in deleteServer) {
|
||||
MmkvManager.removeServer(it)
|
||||
}
|
||||
getApplication<AngApplication>().toast(
|
||||
getApplication<AngApplication>().getString(
|
||||
R.string.title_del_duplicate_config_count,
|
||||
deleteServer.count()
|
||||
)
|
||||
)
|
||||
|
||||
return deleteServer.count()
|
||||
}
|
||||
|
||||
fun removeAllServer() {
|
||||
if (subscriptionId.isNullOrEmpty() && keywordFilter.isNullOrEmpty()) {
|
||||
MmkvManager.removeAllServer()
|
||||
} else {
|
||||
val serversCopy = serversCache.toList()
|
||||
for (item in serversCopy) {
|
||||
MmkvManager.removeServer(item.guid)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun removeInvalidServer() {
|
||||
if (subscriptionId.isNullOrEmpty() && keywordFilter.isNullOrEmpty()) {
|
||||
MmkvManager.removeInvalidServer("")
|
||||
} else {
|
||||
val serversCopy = serversCache.toList()
|
||||
for (item in serversCopy) {
|
||||
MmkvManager.removeInvalidServer(item.guid)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun sortByTestResults() {
|
||||
MmkvManager.sortByTestResults()
|
||||
}
|
||||
|
||||
|
||||
fun copyAssets(assets: AssetManager) {
|
||||
val extFolder = Utils.userAssetPath(getApplication<AngApplication>())
|
||||
viewModelScope.launch(Dispatchers.Default) {
|
||||
@@ -320,6 +347,15 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
|
||||
}
|
||||
}
|
||||
|
||||
fun filterConfig(keyword: String) {
|
||||
if (keyword == keywordFilter) {
|
||||
return
|
||||
}
|
||||
keywordFilter = keyword
|
||||
MmkvManager.settingsStorage.encode(AppConfig.CACHE_KEYWORD_FILTER, keywordFilter)
|
||||
reloadServerList()
|
||||
}
|
||||
|
||||
private val mMsgReceiver = object : BroadcastReceiver() {
|
||||
override fun onReceive(ctx: Context?, intent: Intent?) {
|
||||
when (intent?.getIntExtra("key", 0)) {
|
||||
@@ -350,7 +386,11 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
|
||||
}
|
||||
|
||||
AppConfig.MSG_MEASURE_CONFIG_SUCCESS -> {
|
||||
val resultPair = intent.getSerializableExtra("content") as Pair<String, Long>
|
||||
val resultPair: Pair<String, Long> = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
intent.getSerializableExtra("content", Pair::class.java) as Pair<String, Long>
|
||||
} else {
|
||||
intent.getSerializableExtra("content") as Pair<String, Long>
|
||||
}
|
||||
MmkvManager.encodeServerTestDelayMillis(resultPair.first, resultPair.second)
|
||||
updateListAction.value = getPosition(resultPair.first)
|
||||
}
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
<vector android:height="24dp" android:tint="#FFFFFF"
|
||||
android:viewportHeight="24" android:viewportWidth="24"
|
||||
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="@android:color/white" android:pathData="M7,6h10l-5.01,6.3L7,6zM4.25,5.61C6.27,8.2 10,13 10,13v6c0,0.55 0.45,1 1,1h2c0.55,0 1,-0.45 1,-1v-6c0,0 3.72,-4.8 5.74,-7.39C20.25,4.95 19.78,4 18.95,4H5.04C4.21,4 3.74,4.95 4.25,5.61z"/>
|
||||
<vector android:height="24dp"
|
||||
android:tint="#FFFFFF"
|
||||
android:viewportHeight="24"
|
||||
android:viewportWidth="24"
|
||||
android:width="24dp"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M7,6h10l-5.01,6.3L7,6zM4.25,5.61C6.27,8.2 10,13 10,13v6c0,0.55 0.45,1 1,1h2c0.55,0 1,-0.45 1,-1v-6c0,0 3.72,-4.8 5.74,-7.39C20.25,4.95 19.78,4 18.95,4H5.04C4.21,4 3.74,4.95 4.25,5.61z" />
|
||||
</vector>
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
<vector android:height="24dp" android:tint="#000000"
|
||||
android:viewportHeight="24" android:viewportWidth="24"
|
||||
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="@android:color/white" android:pathData="M7,6h10l-5.01,6.3L7,6zM4.25,5.61C6.27,8.2 10,13 10,13v6c0,0.55 0.45,1 1,1h2c0.55,0 1,-0.45 1,-1v-6c0,0 3.72,-4.8 5.74,-7.39C20.25,4.95 19.78,4 18.95,4H5.04C4.21,4 3.74,4.95 4.25,5.61z"/>
|
||||
<vector android:height="24dp"
|
||||
android:tint="#000000"
|
||||
android:viewportHeight="24"
|
||||
android:viewportWidth="24"
|
||||
android:width="24dp"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M7,6h10l-5.01,6.3L7,6zM4.25,5.61C6.27,8.2 10,13 10,13v6c0,0.55 0.45,1 1,1h2c0.55,0 1,-0.45 1,-1v-6c0,0 3.72,-4.8 5.74,-7.39C20.25,4.95 19.78,4 18.95,4H5.04C4.21,4 3.74,4.95 4.25,5.61z" />
|
||||
</vector>
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
<vector android:height="24dp" android:viewportHeight="1024"
|
||||
android:viewportWidth="1024" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="#FF000000" android:pathData="M469.2,802.1l-81.7,-24.6L554.8,221.9l81.7,24.6L469.2,802.1zM362.7,654.5l-124.7,-141.7 124.8,-143.5 -64.4,-56 -173.8,199.8 174,197.7 64.1,-56.3zM899.4,513.1l-173.8,-199.8 -64.4,56 124.8,143.5 -124.7,141.7 64.1,56.4 174,-197.7z"/>
|
||||
<vector android:height="24dp"
|
||||
android:viewportHeight="1024"
|
||||
android:viewportWidth="1024"
|
||||
android:width="24dp"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M469.2,802.1l-81.7,-24.6L554.8,221.9l81.7,24.6L469.2,802.1zM362.7,654.5l-124.7,-141.7 124.8,-143.5 -64.4,-56 -173.8,199.8 174,197.7 64.1,-56.3zM899.4,513.1l-173.8,-199.8 -64.4,56 124.8,143.5 -124.7,141.7 64.1,56.4 174,-197.7z" />
|
||||
</vector>
|
||||
|
||||
@@ -37,6 +37,22 @@
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
|
||||
<com.google.android.material.progressindicator.LinearProgressIndicator
|
||||
android:id="@+id/pb_waiting"
|
||||
app:indicatorColor="@color/color_fab_active"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:indeterminate="true"
|
||||
android:visibility="invisible" />
|
||||
|
||||
<com.google.android.material.tabs.TabLayout
|
||||
android:id="@+id/tab_group"
|
||||
app:tabMode="scrollable"
|
||||
app:tabTextAppearance="@style/TabLayoutTextStyle"
|
||||
app:tabIndicatorFullWidth="false"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="match_parent" />
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/recycler_view"
|
||||
android:layout_width="match_parent"
|
||||
@@ -68,7 +84,6 @@
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/fabProgressCircle"
|
||||
android:layout_width="wrap_content"
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
</RelativeLayout>
|
||||
android:layout_height="match_parent"></RelativeLayout>
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:orientation="vertical">
|
||||
|
||||
<com.google.android.material.tabs.TabLayout
|
||||
|
||||
@@ -133,16 +133,19 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<EditText
|
||||
android:id="@+id/et_reserved1"
|
||||
android:layout_width="60dp"
|
||||
android:layout_height="@dimen/edit_height"
|
||||
android:inputType="number" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/et_reserved2"
|
||||
android:layout_width="60dp"
|
||||
android:layout_height="@dimen/edit_height"
|
||||
android:inputType="number" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/et_reserved3"
|
||||
android:layout_width="60dp"
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
<?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"
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/nav_header_height"
|
||||
android:background="@drawable/nav_header_bg"
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/activity_horizontal_margin"
|
||||
@@ -27,6 +26,7 @@
|
||||
android:nextFocusDown="@+id/et_sni" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/l2"
|
||||
android:layout_width="match_parent"
|
||||
@@ -47,6 +47,7 @@
|
||||
android:nextFocusDown="@+id/sp_stream_fingerprint" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/l3"
|
||||
android:layout_width="match_parent"
|
||||
@@ -67,6 +68,7 @@
|
||||
android:nextFocusDown="@+id/sp_stream_alpn" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/l4"
|
||||
android:layout_width="match_parent"
|
||||
@@ -87,6 +89,7 @@
|
||||
android:nextFocusDown="@+id/sp_allow_insecure" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/l5"
|
||||
android:layout_width="match_parent"
|
||||
@@ -104,6 +107,7 @@
|
||||
android:layout_height="@dimen/edit_height"
|
||||
android:entries="@array/allowinsecures" />
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/l6"
|
||||
android:layout_width="match_parent"
|
||||
@@ -124,6 +128,7 @@
|
||||
android:nextFocusDown="@+id/sp_stream_fingerprint" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/l7"
|
||||
android:layout_width="match_parent"
|
||||
@@ -144,6 +149,7 @@
|
||||
android:nextFocusDown="@+id/sp_stream_fingerprint" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/l8"
|
||||
android:layout_width="match_parent"
|
||||
|
||||
@@ -3,8 +3,7 @@
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
tools:showIn="navigation_view">
|
||||
|
||||
<group
|
||||
android:id="@+id/group_main">
|
||||
<group android:id="@+id/group_main">
|
||||
<item
|
||||
android:id="@+id/sub_setting"
|
||||
android:icon="@drawable/ic_subscriptions_24dp"
|
||||
|
||||
@@ -1,6 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
<item
|
||||
android:id="@+id/search_view"
|
||||
android:icon="@drawable/ic_outline_filter_alt_24"
|
||||
android:title="@string/menu_item_search"
|
||||
app:actionViewClass="androidx.appcompat.widget.SearchView"
|
||||
app:showAsAction="always|collapseActionView" />
|
||||
<item
|
||||
android:icon="@drawable/ic_add_24dp"
|
||||
android:title="@string/menu_item_add_config"
|
||||
@@ -63,11 +69,6 @@
|
||||
</item>
|
||||
</menu>
|
||||
</item>
|
||||
<item
|
||||
android:id="@+id/filter_config"
|
||||
android:icon="@drawable/ic_outline_filter_alt_24"
|
||||
android:title="@string/title_filter_config"
|
||||
app:showAsAction="ifRoom" />
|
||||
<item
|
||||
android:id="@+id/service_restart"
|
||||
android:title="@string/title_service_restart"
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@color/ic_banner_background" />
|
||||
<foreground>
|
||||
<inset android:drawable="@mipmap/ic_banner_foreground" android:inset="10%"/>
|
||||
<inset
|
||||
android:drawable="@mipmap/ic_banner_foreground"
|
||||
android:inset="10%" />
|
||||
</foreground>
|
||||
</adaptive-icon>
|
||||
@@ -37,6 +37,7 @@
|
||||
<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>
|
||||
<string name="server_lab_address">العنوان</string>
|
||||
<string name="server_lab_port">المنفذ</string>
|
||||
|
||||
307
V2rayNG/app/src/main/res/values-bn/strings.xml
Normal file
307
V2rayNG/app/src/main/res/values-bn/strings.xml
Normal file
@@ -0,0 +1,307 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="app_name" translatable="false">v2rayNG</string>
|
||||
<string name="app_widget_name">সুইচ</string>
|
||||
<string name="app_tile_name">সুইচ</string>
|
||||
<string name="app_tile_first_use">এই ফিচারটি প্রথম ব্যবহার করা হচ্ছে, সার্ভার যোগ করতে অ্যাপটি ব্যবহার করুন</string>
|
||||
<string name="navigation_drawer_open">নেভিগেশন ড্রয়ার খোলুন</string>
|
||||
<string name="navigation_drawer_close">নেভিগেশন ড্রয়ার বন্ধ করুন</string>
|
||||
<string name="migration_success">ডেটা স্থানান্তর সফল!</string>
|
||||
<string name="migration_fail">ডেটা স্থানান্তর ব্যর্থ!</string>
|
||||
|
||||
<!-- Notifications -->
|
||||
<string name="notification_action_stop_v2ray">বন্ধ করুন</string>
|
||||
<string name="toast_permission_denied">অনুমতি পাওয়া যাচ্ছে না</string>
|
||||
<string name="notification_action_more">আরও দেখতে ক্লিক করুন</string>
|
||||
<string name="toast_services_start">সার্ভিস শুরু করুন</string>
|
||||
<string name="toast_services_stop">সার্ভিস বন্ধ করুন</string>
|
||||
<string name="toast_services_success">সার্ভিস সফলভাবে শুরু হয়েছে</string>
|
||||
<string name="toast_services_failure">সার্ভিস শুরু হতে ব্যর্থ হয়েছে</string>
|
||||
|
||||
<!--ServerActivity-->
|
||||
<string name="title_server">কনফিগারেশন ফাইল</string>
|
||||
<string name="menu_item_add_config">কনফিগারেশন যোগ করুন</string>
|
||||
<string name="menu_item_save_config">কনফিগারেশন সংরক্ষণ করুন</string>
|
||||
<string name="menu_item_del_config">কনফিগারেশন মুছুন</string>
|
||||
<string name="menu_item_import_config_qrcode">QR কোড থেকে কনফিগারেশন আমদানি করুন</string>
|
||||
<string name="menu_item_import_config_clipboard">ক্লিপবোর্ড থেকে কনফিগারেশন আমদানি করুন</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>
|
||||
<string name="menu_item_import_config_manually_socks">ম্যানুয়ালি টাইপ করুন [Socks]</string>
|
||||
<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_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>
|
||||
<string name="server_lab_address">ঠিকানা</string>
|
||||
<string name="server_lab_port">পোর্ট</string>
|
||||
<string name="server_lab_id">আইডি</string>
|
||||
<string name="server_lab_alterid">অলটারআইডি</string>
|
||||
<string name="server_lab_security">নিরাপত্তা</string>
|
||||
<string name="server_lab_network">নেটওয়ার্ক</string>
|
||||
<string name="server_lab_more_function">ট্রান্সপোর্ট</string>
|
||||
<string name="server_lab_head_type">হেড টাইপ</string>
|
||||
<string name="server_lab_mode_type">gRPC মোড</string>
|
||||
<string name="server_lab_request_host">হোস্ট</string>
|
||||
<string name="server_lab_request_host_http">http হোস্ট</string>
|
||||
<string name="server_lab_request_host_ws">ws হোস্ট</string>
|
||||
<string name="server_lab_request_host_httpupgrade">httpupgrade হোস্ট</string>
|
||||
<string name="server_lab_request_host_splithttp">splithttp হোস্ট</string>
|
||||
<string name="server_lab_request_host_h2">h2 হোস্ট</string>
|
||||
<string name="server_lab_request_host_quic">QUIC নিরাপত্তা</string>
|
||||
<string name="server_lab_request_host_grpc">gRPC কর্তৃপক্ষ</string>
|
||||
<string name="server_lab_path">পথ</string>
|
||||
<string name="server_lab_path_ws">ws পথ</string>
|
||||
<string name="server_lab_path_httpupgrade">httpupgrade পথ</string>
|
||||
<string name="server_lab_path_splithttp">splithttp পথ</string>
|
||||
<string name="server_lab_path_h2">h2 পথ</string>
|
||||
<string name="server_lab_path_quic">QUIC কী</string>
|
||||
<string name="server_lab_path_kcp">kcp বীজ</string>
|
||||
<string name="server_lab_path_grpc">gRPC সেবার নাম</string>
|
||||
<string name="server_lab_stream_security">TLS</string>
|
||||
<string name="server_lab_stream_fingerprint" translatable="false">ফিঙ্গারপ্রিন্ট</string>
|
||||
<string name="server_lab_stream_alpn" translatable="false">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_port3">পোর্ট</string>
|
||||
<string name="server_lab_id3">পাসওয়ার্ড</string>
|
||||
<string name="server_lab_security3">নিরাপত্তা</string>
|
||||
<string name="server_lab_id4">পাসওয়ার্ড (ঐচ্ছিক)</string>
|
||||
<string name="server_lab_security4">ব্যবহারকারী (ঐচ্ছিক)</string>
|
||||
<string name="server_lab_encryption">এনক্রিপশন</string>
|
||||
<string name="server_lab_flow">ফ্লো</string>
|
||||
<string name="server_lab_public_key" translatable="false">পাবলিক কী</string>
|
||||
<string name="server_lab_short_id" translatable="false">শর্ট আইডি</string>
|
||||
<string name="server_lab_spider_x" translatable="false">SpiderX</string>
|
||||
<string name="server_lab_secret_key" translatable="false">সিক্রেট কী</string>
|
||||
<string name="server_lab_reserved">সংরক্ষিত (ঐচ্ছিক)</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>
|
||||
<string name="toast_none_data">কোনও তথ্য নেই</string>
|
||||
<string name="toast_incorrect_protocol">ভুল প্রোটোকল</string>
|
||||
<string name="toast_decoding_failed">ডিকোডিং ব্যর্থ</string>
|
||||
<string name="title_file_chooser">একটি কনফিগারেশন ফাইল নির্বাচন করুন</string>
|
||||
<string name="toast_require_file_manager">অনুগ্রহ করে একটি ফাইল ম্যানেজার ইনস্টল করুন।</string>
|
||||
<string name="server_customize_config">কাস্টমাইজ কনফিগারেশন</string>
|
||||
<string name="toast_config_file_invalid">অবৈধ কনফিগারেশন</string>
|
||||
<string name="server_lab_content">কনটেন্ট</string>
|
||||
<string name="toast_none_data_clipboard">ক্লিপবোর্ডে কোনও তথ্য নেই</string>
|
||||
<string name="toast_invalid_url">অবৈধ URL</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_asset_copy_failed">ফাইল কপি ব্যর্থ, অনুগ্রহ করে ফাইল ম্যানেজার ব্যবহার করুন</string>
|
||||
<string name="menu_item_add_asset">অ্যাসেট যোগ করুন</string>
|
||||
<string name="menu_item_add_file">ফাইল যোগ করুন</string>
|
||||
<string name="menu_item_add_url">URL যোগ করুন</string>
|
||||
<string name="title_url" translatable="false">URL</string>
|
||||
<string name="menu_item_download_file">ফাইল ডাউনলোড করুন</string>
|
||||
<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="toast_action_not_allowed">অ্যাকশন অনুমোদিত নয়</string>
|
||||
<!-- PerAppProxyActivity -->
|
||||
<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="switch_bypass_apps_mode">বাইপাস মোড</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>
|
||||
|
||||
<!-- Preferences -->
|
||||
<string name="title_settings">সেটিংস</string>
|
||||
<string name="title_advanced">এডভান্সড সেটিংস</string>
|
||||
<string name="title_vpn_settings">VPN সেটিংস</string>
|
||||
<string name="title_pref_per_app_proxy">প্রতি-অ্যাপ প্রক্সি</string>
|
||||
<string name="summary_pref_per_app_proxy">সাধারণ: চেকড অ্যাপ প্রক্সি, আনচেকড সরাসরি সংযোগ; \nবাইপাস মোড: চেকড অ্যাপ সরাসরি সংযুক্ত, আনচেকড প্রক্সি। \nমেনুতে প্রক্সি অ্যাপ্লিকেশন স্বয়ংক্রিয়ভাবে নির্বাচন করার বিকল্প</string>
|
||||
|
||||
<string name="title_mux_settings">Mux সেটিংস</string>
|
||||
<string name="title_pref_mux_enabled">Mux সক্রিয় করুন</string>
|
||||
<string name="summary_pref_mux_enabled">দ্রুত, তবে এটি অস্থিতিশীল সংযোগ সৃষ্টি করতে পারে\nTCP, 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">Mux টানেলে QUIC পরিচালনা</string>
|
||||
<string-array name="mux_xudp_quic_entries">
|
||||
<item>অস্বীকার করুন</item>
|
||||
<item>অনুমতি দিন</item>
|
||||
<item>এড়িয়ে যান</item>
|
||||
</string-array>
|
||||
|
||||
<string name="title_pref_speed_enabled">গতি প্রদর্শন সক্রিয় করুন</string>
|
||||
<string name="summary_pref_speed_enabled">বিজ্ঞপ্তিতে বর্তমান গতি প্রদর্শন করুন।\nব্যবহারের উপর ভিত্তি করে বিজ্ঞপ্তি আইকন পরিবর্তিত হবে।</string>
|
||||
|
||||
<string name="title_pref_sniffing_enabled">স্নিফিং সক্রিয় করুন</string>
|
||||
<string name="summary_pref_sniffing_enabled">প্যাকেট থেকে ডোমেইন স্নিফ করার চেষ্টা করুন (ডিফল্টভাবে চালু)</string>
|
||||
|
||||
<string name="title_pref_route_only_enabled">রুটঅনলি সক্রিয় করুন</string>
|
||||
<string name="summary_pref_route_only_enabled">রুটিংয়ের জন্য শুধুমাত্র স্নিফড ডোমেইন নাম ব্যবহার করুন, এবং লক্ষ্য ঠিকানা হিসেবে আইপি ঠিকানা রাখুন।</string>
|
||||
|
||||
<string name="title_pref_local_dns_enabled">স্থানীয় DNS সক্রিয় করুন</string>
|
||||
<string name="summary_pref_local_dns_enabled">DNS মূল DNS মডিউল দ্বারা প্রক্রিয়া করা হয় (প্রস্তাবিত, যদি LAN এবং মূলভূমি ঠিকানার বাইপাসিং প্রয়োজন হয়)</string>
|
||||
|
||||
<string name="title_pref_fake_dns_enabled">ভুয়া DNS সক্রিয় করুন</string>
|
||||
<string name="summary_pref_fake_dns_enabled">স্থানীয় DNS মিথ্যা আইপি ঠিকানা ফেরত দেয় (দ্রুত, তবে এটি কিছু অ্যাপের জন্য কাজ নাও করতে পারে)</string>
|
||||
|
||||
<string name="title_pref_prefer_ipv6">IPv6 অগ্রাধিকার দিন</string>
|
||||
<string name="summary_pref_prefer_ipv6">IPv6 ঠিকানা এবং রুটকে অগ্রাধিকার দিন</string>
|
||||
|
||||
<string name="title_pref_routing">রাউটিং</string>
|
||||
<string name="title_pref_routing_domain_strategy">ডোমেইন কৌশল</string>
|
||||
<string name="title_pref_routing_mode">পূর্বনির্ধারিত নিয়ম</string>
|
||||
<string name="title_pref_routing_custom">কাস্টম নিয়ম</string>
|
||||
|
||||
<string name="title_pref_remote_dns">রিমোট DNS (udp/tcp/https/quic)(ঐচ্ছিক)</string>
|
||||
<string name="summary_pref_remote_dns">DNS</string>
|
||||
|
||||
<string name="title_pref_vpn_dns">VPN DNS (শুধুমাত্র IPv4/v6)</string>
|
||||
|
||||
<string name="title_pref_domestic_dns">ঘরোয়া DNS (ঐচ্ছিক)</string>
|
||||
<string name="summary_pref_domestic_dns">DNS</string>
|
||||
|
||||
<string name="title_pref_delay_test_url">সঠিক বিলম্ব পরীক্ষা ইউআরএল (http/https)</string>
|
||||
<string name="summary_pref_delay_test_url">ইউআরএল</string>
|
||||
|
||||
<string name="title_pref_proxy_sharing_enabled">LAN থেকে সংযোগ অনুমোদন করুন</string>
|
||||
<string name="summary_pref_proxy_sharing_enabled">অন্যান্য ডিভাইসগুলি আপনার আইপি ঠিকানা ব্যবহার করে প্রক্সিতে সংযুক্ত হতে পারে, শুধুমাত্র বিশ্বস্ত নেটওয়ার্কে সক্রিয় করুন যাতে অনুমোদিত সংযোগ এড়ানো যায়</string>
|
||||
<string name="toast_warning_pref_proxysharing_short">LAN থেকে সংযোগ অনুমোদন করুন, নিশ্চিত করুন যে আপনি একটি বিশ্বস্ত নেটওয়ার্কে আছেন</string>
|
||||
|
||||
<string name="title_pref_allow_insecure">allowInsecure</string>
|
||||
<string name="summary_pref_allow_insecure">যখন TLS, ডিফল্টভাবে allowInsecure</string>
|
||||
|
||||
<string name="title_pref_socks_port">SOCKS5 প্রক্সি পোর্ট</string>
|
||||
<string name="summary_pref_socks_port">SOCKS5 প্রক্সি পোর্ট</string>
|
||||
|
||||
<string name="title_pref_http_port">HTTP প্রক্সি পোর্ট</string>
|
||||
<string name="summary_pref_http_port">HTTP প্রক্সি পোর্ট</string>
|
||||
|
||||
<string name="title_pref_local_dns_port">স্থানীয় DNS পোর্ট</string>
|
||||
<string name="summary_pref_local_dns_port">স্থানীয় DNS পোর্ট</string>
|
||||
|
||||
<string name="title_pref_confirm_remove">কনফিগারেশন ফাইল মুছে ফেলার নিশ্চিতকরণ</string>
|
||||
<string name="summary_pref_confirm_remove">কনফিগারেশন ফাইল মুছে ফেলার জন্য ব্যবহারকারীর দ্বিতীয় নিশ্চিতকরণের প্রয়োজন</string>
|
||||
|
||||
<string name="title_pref_start_scan_immediate">তাত্ক্ষণিক স্ক্যান শুরু করুন</string>
|
||||
<string name="summary_pref_start_scan_immediate">শুরুতে তাত্ক্ষণিকভাবে স্ক্যান করতে ক্যামেরা খুলুন, অন্যথায় আপনি কোড স্ক্যান বা টুলবারে একটি ছবি নির্বাচন করতে পারেন</string>
|
||||
<string name="title_pref_feedback">মতামত</string>
|
||||
<string name="summary_pref_feedback">মতামত উন্নয়ন বা বাগগুলি GitHub-এ পাঠান</string>
|
||||
<string name="summary_pref_tg_group">টেলিগ্রাম গ্রুপে যোগদান করুন</string>
|
||||
<string name="toast_tg_app_not_found">টেলিগ্রাম অ্যাপ পাওয়া যায়নি</string>
|
||||
<string name="title_privacy_policy">গোপনীয়তা নীতি</string>
|
||||
<string name="title_about">সম্পর্কিত</string>
|
||||
<string name="title_source_code">সোর্স কোড</string>
|
||||
<string name="title_tg_channel">টেলিগ্রাম চ্যানেল</string>
|
||||
<string name="title_configuration_backup">কনফিগারেশন ব্যাকআপ</string>
|
||||
<string name="summary_configuration_backup">স্টোরেজ অবস্থান: [%s], অ্যাপ আনইনস্টল বা স্টোরেজ ক্লিয়ার করার পরে ব্যাকআপ মুছে যাবে</string>
|
||||
<string name="title_configuration_restore">কনফিগারেশন পুনরুদ্ধার</string>
|
||||
<string name="title_configuration_share">কনফিগারেশন শেয়ার করুন</string>
|
||||
|
||||
<string name="title_pref_promotion">প্রচার</string>
|
||||
<string name="summary_pref_promotion">প্রচার, বিস্তারিত জানার জন্য ক্লিক করুন (ডোনেশন মুছে ফেলা যেতে পারে)</string>
|
||||
|
||||
<string name="title_pref_auto_update_subscription">স্বয়ংক্রিয় আপডেট সাবস্ক্রিপশন</string>
|
||||
<string name="summary_pref_auto_update_subscription">পটভূমিতে একটি নির্দিষ্ট সময় পর পর আপনার সাবস্ক্রিপশন স্বয়ংক্রিয়ভাবে আপডেট করুন। ডিভাইসের উপর নির্ভর করে, এই বৈশিষ্ট্যটি সবসময় কাজ নাও করতে পারে</string>
|
||||
<string name="title_pref_auto_update_interval">অটো আপডেট ইন্টারভ্যাল (মিনিট, সর্বনিম্ন মান ১৫)</string>
|
||||
|
||||
<string name="title_core_loglevel">লগ স্তর</string>
|
||||
<string name="title_mode">মোড</string>
|
||||
<string name="title_mode_help">আরো সাহায্যের জন্য ক্লিক করুন</string>
|
||||
<string name="title_language">ভাষা</string>
|
||||
<string name="title_ui_settings">ইউআই সেটিংস</string>
|
||||
<string name="title_pref_ui_mode_night">ইউআই মোড সেটিংস</string>
|
||||
|
||||
<string name="title_logcat">লগক্যাট</string>
|
||||
<string name="logcat_copy">কপি করুন</string>
|
||||
<string name="logcat_clear">স্পষ্ট করুন</string>
|
||||
<string name="title_service_restart">সার্ভিস পুনরায় চালু করুন</string>
|
||||
<string name="title_del_all_config">সব কনফিগারেশন মুছে ফেলুন</string>
|
||||
<string name="title_del_duplicate_config">ডুপ্লিকেট কনফিগারেশন মুছে ফেলুন</string>
|
||||
<string name="title_del_invalid_config">অবৈধ কনফিগারেশন মুছে ফেলুন (প্রথমে পরীক্ষা করুন)</string>
|
||||
<string name="title_export_all">কাস্টম না করা কনফিগারেশনগুলি ক্লিপবোর্ডে রপ্তানি করুন</string>
|
||||
<string name="title_sub_setting">সাবস্ক্রিপশন গ্রুপ সেটিং</string>
|
||||
<string name="sub_setting_remarks">মন্তব্য</string>
|
||||
<string name="sub_setting_url">ঐচ্ছিক URL</string>
|
||||
<string name="sub_setting_enable">আপডেট সক্রিয় করুন</string>
|
||||
<string name="sub_auto_update">স্বয়ংক্রিয় আপডেট সক্রিয় করুন</string>
|
||||
<string name="title_sub_update">সাবস্ক্রিপশন আপডেট</string>
|
||||
<string name="title_ping_all_server">সব কনফিগারেশন TCPing</string>
|
||||
<string name="title_real_ping_all_server">সব কনফিগারেশন প্রকৃত বিলম্ব</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="title_del_duplicate_config_count">%d ডুপ্লিকেট কনফিগারেশন মুছে ফেলুন</string>
|
||||
<string name="tasker_start_service">সার্ভিস শুরু করুন</string>
|
||||
<string name="tasker_setting_confirm">নিশ্চিত করুন</string>
|
||||
|
||||
<string name="routing_settings_title">রাউটিং সেটিংস</string>
|
||||
<string name="routing_settings_tips">কমা (,) দ্বারা আলাদা করুন, মনে রাখবেন সেভ করতে</string>
|
||||
<string name="routing_settings_save">সেভ করুন</string>
|
||||
<string name="routing_settings_delete">মুছে ফেলুন</string>
|
||||
<string name="routing_settings_scan_replace">স্ক্যান করুন এবং প্রতিস্থাপন করুন</string>
|
||||
<string name="routing_settings_scan_append">স্ক্যান করুন এবং যোগ করুন</string>
|
||||
<string name="routing_settings_default_rules"> ডিফল্ট রাউটিং নিয়ম সেট করুন</string>
|
||||
|
||||
<string name="connection_test_pending">সংযোগ পরীক্ষা করুন</string>
|
||||
<string name="connection_test_testing">পরীক্ষা চলছে…</string>
|
||||
<string name="connection_test_available">সফল: HTTP সংযোগ নিয়েছে %dms</string>
|
||||
<string name="connection_test_error">ইন্টারনেট সংযোগ সনাক্ত করতে ব্যর্থ: %s</string>
|
||||
<string name="connection_test_fail">ইন্টারনেট উপলব্ধ নয়</string>
|
||||
<string name="connection_test_error_status_code">ত্রুটি কোড: #%d</string>
|
||||
<string name="connection_connected">সংযুক্ত, সংযোগ পরীক্ষা করতে ট্যাপ করুন</string>
|
||||
<string name="connection_not_connected">সংযুক্ত নয়</string>
|
||||
|
||||
<string name="import_subscription_success">সাবস্ক্রিপশন সফলভাবে আমদানি করা হয়েছে</string>
|
||||
<string name="import_subscription_failure">সাবস্ক্রিপশন আমদানি ব্যর্থ</string>
|
||||
<string name="title_fragment_settings">ফ্র্যাগমেন্ট সেটিংস</string>
|
||||
<string name="title_pref_fragment_packets">ফ্র্যাগমেন্ট প্যাকেটস</string>
|
||||
<string name="title_pref_fragment_length">ফ্র্যাগমেন্ট দৈর্ঘ্য (ন্যূনতম-সর্বাধিক)</string>
|
||||
<string name="title_pref_fragment_interval">ফ্র্যাগমেন্ট ইন্টারভ্যাল (ন্যূনতম-সর্বাধিক)</string>
|
||||
<string name="title_pref_fragment_enabled">ফ্র্যাগমেন্ট সক্রিয় করুন</string>
|
||||
<string-array name="share_method">
|
||||
<item>QR কোড</item>
|
||||
<item>ক্লিপবোর্ডে রপ্তানি করুন</item>
|
||||
<item>পূর্ণ কনফিগারেশন ক্লিপবোর্ডে রপ্তানি করুন</item>
|
||||
</string-array>
|
||||
|
||||
<string-array name="share_sub_method">
|
||||
<item>QR কোড</item>
|
||||
<item>ক্লিপবোর্ডে রপ্তানি করুন</item>
|
||||
</string-array>
|
||||
|
||||
<string-array name="routing_tag">
|
||||
<item>প্রক্সি URL বা IP</item>
|
||||
<item>ডাইরেক্ট URL বা IP</item>
|
||||
<item>ব্লকড URL বা IP</item>
|
||||
</string-array>
|
||||
|
||||
<string-array name="routing_mode">
|
||||
<item>গ্লোবাল প্রোক্সি</item>
|
||||
<item>LAN ঠিকানা বাইপাস করে তারপর প্রোক্সি</item>
|
||||
<item>মেইনল্যান্ড ঠিকানা বাইপাস করে তারপর প্রোক্সি</item>
|
||||
<item>LAN এবং মেইনল্যান্ড ঠিকানা বাইপাস করে তারপর প্রোক্সি</item>
|
||||
<item>গ্লোবাল ডাইরেক্ট</item>
|
||||
</string-array>
|
||||
|
||||
<string-array name="mode_entries">
|
||||
<item>VPN</item>
|
||||
<item>শুধুমাত্র প্রোক্সি</item>
|
||||
</string-array>
|
||||
|
||||
<string-array name="ui_mode_night">
|
||||
<item>সিস্টেম অনুসরণ করুন</item>
|
||||
<item>লাইট</item>
|
||||
<item>ডার্ক</item>
|
||||
</string-array>
|
||||
</resources>
|
||||
@@ -36,6 +36,7 @@
|
||||
<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">Please test before deleting! Confirm delete ?</string>
|
||||
<string name="server_lab_remarks">ملاحظات</string>
|
||||
<string name="server_lab_address">نشانی</string>
|
||||
<string name="server_lab_port">پورت</string>
|
||||
@@ -63,6 +64,8 @@
|
||||
<string name="server_lab_path_kcp">kcp seed</string>
|
||||
<string name="server_lab_path_grpc">gRPC serviceName</string>
|
||||
<string name="server_lab_stream_security">TLS</string>
|
||||
<string name="server_lab_stream_fingerprint">اثرانگشت</string>
|
||||
<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>
|
||||
@@ -73,10 +76,14 @@
|
||||
<string name="server_lab_security4">نامکاربری (اختیاری)</string>
|
||||
<string name="server_lab_encryption">رمزنگاری</string>
|
||||
<string name="server_lab_flow">جریان</string>
|
||||
<string name="server_lab_public_key">PublicKey</string>
|
||||
<string name="server_lab_short_id">ShortId</string>
|
||||
<string name="server_lab_spider_x">SpiderX</string>
|
||||
<string name="server_lab_secret_key">SecretKey</string>
|
||||
<string name="server_lab_reserved">Reserved (اختیاری)</string>
|
||||
<string name="server_lab_local_address">آدرس محلی IPv4(اختیاری)</string>
|
||||
<string name="server_lab_local_mtu">Mtu(optional, default 1420)</string>
|
||||
<string name="toast_success">موفقیت</string>
|
||||
<string name="toast_success">با موفقیت انجام شد</string>
|
||||
<string name="toast_failure">شکست</string>
|
||||
<string name="toast_none_data">چیزی نیست</string>
|
||||
<string name="toast_incorrect_protocol">پروتکل نادرست</string>
|
||||
@@ -93,6 +100,7 @@
|
||||
<string name="server_lab_request_host6">میزبان (SNI) (اختیاری)</string>
|
||||
<string name="toast_asset_copy_failed">کپی فایل انجام نشد، لطفا از برنامه مدیریت فایل استفاده کنید</string>
|
||||
<string name="menu_item_add_file">افزودن فایلها</string>
|
||||
<string name="title_url">URL</string>
|
||||
<string name="menu_item_download_file">دانلود فایلها</string>
|
||||
<string name="toast_action_not_allowed">این عمل ممنوع است</string>
|
||||
|
||||
@@ -191,7 +199,7 @@
|
||||
<string name="toast_tg_app_not_found">برنامه تلگرام پیدا نشد</string>
|
||||
|
||||
<string name="title_privacy_policy">حریم خصوصی</string>
|
||||
<string name="title_about">About</string>
|
||||
<string name="title_about">درباره</string>
|
||||
<string name="title_source_code">Source code</string>
|
||||
<string name="title_tg_channel">Telegram channel</string>
|
||||
<string name="title_configuration_backup">Backup configuration</string>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="app_widget_name">Переключить</string>
|
||||
<string name="app_tile_name">Переключить</string>
|
||||
<string name="app_widget_name">v2rayNG</string>
|
||||
<string name="app_tile_name">v2rayNG</string>
|
||||
<string name="app_tile_first_use">Первое использование этой функции, пожалуйста, используйте приложение, чтобы добавить сервер</string>
|
||||
<string name="navigation_drawer_open">Открыть панель навигации</string>
|
||||
<string name="navigation_drawer_close">Закрыть панель навигации</string>
|
||||
@@ -36,6 +36,7 @@
|
||||
<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>
|
||||
<string name="server_lab_address">Адрес</string>
|
||||
<string name="server_lab_port">Порт</string>
|
||||
@@ -63,6 +64,8 @@
|
||||
<string name="server_lab_path_kcp">Сид KCP</string>
|
||||
<string name="server_lab_path_grpc">Служба gRPC</string>
|
||||
<string name="server_lab_stream_security">TLS</string>
|
||||
<string name="server_lab_stream_fingerprint">Отпечаток</string>
|
||||
<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>
|
||||
@@ -73,6 +76,10 @@
|
||||
<string name="server_lab_security4">Пользователь (необязательно)</string>
|
||||
<string name="server_lab_encryption">Шифрование</string>
|
||||
<string name="server_lab_flow">Поток</string>
|
||||
<string name="server_lab_public_key">PublicKey</string>
|
||||
<string name="server_lab_short_id">ShortID</string>
|
||||
<string name="server_lab_spider_x">SpiderX</string>
|
||||
<string name="server_lab_secret_key">SecretKey</string>
|
||||
<string name="server_lab_reserved">Reserved (необязательно)</string>
|
||||
<string name="server_lab_local_address">Локальный адрес (необязательно, IPv4/IPv6 через запятую)</string>
|
||||
<string name="server_lab_local_mtu">MTU (необязательно, по умолчанию 1420)</string>
|
||||
@@ -95,6 +102,7 @@
|
||||
<string name="menu_item_add_asset">Добавить ресурс</string>
|
||||
<string name="menu_item_add_file">Добавить файлы</string>
|
||||
<string name="menu_item_add_url">Добавить URL</string>
|
||||
<string name="title_url">URL</string>
|
||||
<string name="menu_item_download_file">Загрузить файлы</string>
|
||||
<string name="title_user_asset_add_url">Добавить URL ресурса</string>
|
||||
<string name="msg_file_not_found">Файл не найден</string>
|
||||
@@ -219,18 +227,18 @@
|
||||
<string name="logcat_copy">Копировать</string>
|
||||
<string name="logcat_clear">Очистить</string>
|
||||
<string name="title_service_restart">Перезапуск службы</string>
|
||||
<string name="title_del_all_config">Удалить все профили</string>
|
||||
<string name="title_del_duplicate_config">Удалить дубликаты профилей</string>
|
||||
<string name="title_del_invalid_config">Удалить нерабочие профили (после проверки)</string>
|
||||
<string name="title_export_all">Экспорт всех профилей в буфер обмена</string>
|
||||
<string name="title_del_all_config">Удалить профили группы</string>
|
||||
<string name="title_del_duplicate_config">Удалить дубликаты профилей группы</string>
|
||||
<string name="title_del_invalid_config">Удалить нерабочие профили группы</string>
|
||||
<string name="title_export_all">Экспорт профилей группы в буфер обмена</string>
|
||||
<string name="title_sub_setting">Группы</string>
|
||||
<string name="sub_setting_remarks">Название</string>
|
||||
<string name="sub_setting_url">URL (необязательно)</string>
|
||||
<string name="sub_setting_enable">Использовать обновление</string>
|
||||
<string name="sub_auto_update">Использовать автоматическое обновление</string>
|
||||
<string name="title_sub_update">Обновить подписку</string>
|
||||
<string name="title_ping_all_server">Проверка доступности профилей</string>
|
||||
<string name="title_real_ping_all_server">Время отклика профилей</string>
|
||||
<string name="title_sub_update">Обновить подписку группы</string>
|
||||
<string name="title_ping_all_server">Проверка профилей группы</string>
|
||||
<string name="title_real_ping_all_server">Время отклика профилей группы</string>
|
||||
<string name="title_user_asset_setting">Файлы георесурсов</string>
|
||||
<string name="title_sort_by_test_results">Сортировка по результатам теста</string>
|
||||
<string name="title_filter_config">Фильтр групп</string>
|
||||
|
||||
@@ -36,6 +36,7 @@
|
||||
<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>
|
||||
<string name="server_lab_address">Địa chỉ</string>
|
||||
<string name="server_lab_port">Cổng</string>
|
||||
@@ -63,6 +64,8 @@
|
||||
<string name="server_lab_path_kcp">kcp seed</string>
|
||||
<string name="server_lab_path_grpc">gRPC serviceName</string>
|
||||
<string name="server_lab_stream_security">TLS</string>
|
||||
<string name="server_lab_stream_fingerprint">Fingerprint</string>
|
||||
<string name="server_lab_stream_alpn">Alpn</string>
|
||||
<string name="server_lab_allow_insecure">Bỏ qua xác minh chứng chỉ</string>
|
||||
<string name="server_lab_sni">SNI</string>
|
||||
<string name="server_lab_address3">Địa chỉ</string>
|
||||
@@ -73,6 +76,10 @@
|
||||
<string name="server_lab_security4">Tên người dùng (Không bắt buộc)</string>
|
||||
<string name="server_lab_encryption">Mã hóa</string>
|
||||
<string name="server_lab_flow">Kiểm soát lưu lượng (Flow)</string>
|
||||
<string name="server_lab_public_key">PublicKey</string>
|
||||
<string name="server_lab_short_id">ShortId</string>
|
||||
<string name="server_lab_spider_x">SpiderX</string>
|
||||
<string name="server_lab_secret_key">SecretKey</string>
|
||||
<string name="server_lab_reserved">Reserved (Không bắt buộc)</string>
|
||||
<string name="server_lab_local_address">Địa chỉ cục bộ (IPv4 / IPv6, phân cách bằng dấu phẩy)</string>
|
||||
<string name="server_lab_local_mtu">MTU (Không bắt buộc, mặc định là 1420)</string>
|
||||
@@ -93,6 +100,7 @@
|
||||
<string name="server_lab_request_host6">Host (SNI) (Không bắt buộc)</string>
|
||||
<string name="toast_asset_copy_failed">Không thể sao chép tệp tin, hãy dùng trình quản lý tệp!</string>
|
||||
<string name="menu_item_add_file">Thêm tệp</string>
|
||||
<string name="title_url">URL</string>
|
||||
<string name="menu_item_download_file">Tải xuống tệp tin</string>
|
||||
<string name="toast_action_not_allowed">Hành động này bị cấm!</string>
|
||||
|
||||
@@ -296,5 +304,10 @@
|
||||
<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>
|
||||
|
||||
</resources>
|
||||
|
||||
@@ -36,6 +36,7 @@
|
||||
<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>
|
||||
<string name="server_lab_address">地址(address)</string>
|
||||
<string name="server_lab_port">端口(port)</string>
|
||||
@@ -63,6 +64,8 @@
|
||||
<string name="server_lab_path_kcp">kcp seed</string>
|
||||
<string name="server_lab_path_grpc">gRPC serviceName</string>
|
||||
<string name="server_lab_stream_security">传输层安全(TLS)</string>
|
||||
<string name="server_lab_stream_fingerprint">Fingerprint</string>
|
||||
<string name="server_lab_stream_alpn">Alpn</string>
|
||||
<string name="server_lab_allow_insecure">跳过证书验证(allowInsecure)</string>
|
||||
<string name="server_lab_sni">SNI</string>
|
||||
<string name="server_lab_address3">服务器地址</string>
|
||||
@@ -73,6 +76,10 @@
|
||||
<string name="server_lab_security4">用户名(可选)</string>
|
||||
<string name="server_lab_encryption">加密方式(encryption)</string>
|
||||
<string name="server_lab_flow">流控(flow)</string>
|
||||
<string name="server_lab_public_key">PublicKey</string>
|
||||
<string name="server_lab_short_id">ShortId</string>
|
||||
<string name="server_lab_spider_x">SpiderX</string>
|
||||
<string name="server_lab_secret_key">SecretKey</string>
|
||||
<string name="server_lab_reserved">Reserved(可选)</string>
|
||||
<string name="server_lab_local_address">本地地址(可选IPv4/IPv6,逗号隔开)</string>
|
||||
<string name="server_lab_local_mtu">Mtu(可选, 默认1420)</string>
|
||||
@@ -93,6 +100,7 @@
|
||||
<string name="server_lab_request_host6">Host(SNI)(可选)</string>
|
||||
<string name="toast_asset_copy_failed">失败, 请使用文件管理器</string>
|
||||
<string name="menu_item_add_file">添加文件</string>
|
||||
<string name="title_url">URL</string>
|
||||
<string name="menu_item_download_file">下载文件</string>
|
||||
<string name="toast_action_not_allowed">禁止此项操作</string>
|
||||
|
||||
@@ -216,22 +224,22 @@
|
||||
<string name="logcat_copy">复制</string>
|
||||
<string name="logcat_clear">清除</string>
|
||||
<string name="title_service_restart">服务重启</string>
|
||||
<string name="title_del_all_config">删除全部配置</string>
|
||||
<string name="title_del_duplicate_config">删除重复配置</string>
|
||||
<string name="title_del_invalid_config">删除无效配置(先测试)</string>
|
||||
<string name="title_export_all">导出全部(非自定义)配置至剪贴板</string>
|
||||
<string name="title_del_all_config">删除当前组配置</string>
|
||||
<string name="title_del_duplicate_config">删除当前组重复配置</string>
|
||||
<string name="title_del_invalid_config">删除当前组无效配置</string>
|
||||
<string name="title_export_all">导出当前组配置至剪贴板</string>
|
||||
<string name="title_sub_setting">订阅分组设置</string>
|
||||
<string name="sub_setting_remarks">备注</string>
|
||||
<string name="sub_setting_url">可选地址(url)</string>
|
||||
<string name="sub_setting_enable">启用更新</string>
|
||||
<string name="sub_auto_update">启用自动更新</string>
|
||||
<string name="title_sub_update">更新订阅</string>
|
||||
<string name="title_ping_all_server">测试全部配置Tcping</string>
|
||||
<string name="title_real_ping_all_server">测试全部配置真连接</string>
|
||||
<string name="title_sub_update">更新当前组订阅</string>
|
||||
<string name="title_ping_all_server">测试当前组配置Tcping</string>
|
||||
<string name="title_real_ping_all_server">测试当前组配置真连接</string>
|
||||
<string name="title_user_asset_setting">Geo 资源文件</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="tasker_start_service">启动服务</string>
|
||||
|
||||
@@ -36,6 +36,7 @@
|
||||
<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>
|
||||
<string name="server_lab_address">位址</string>
|
||||
<string name="server_lab_port">埠</string>
|
||||
@@ -63,6 +64,8 @@
|
||||
<string name="server_lab_path_kcp">kcp seed</string>
|
||||
<string name="server_lab_path_grpc">gRPC serviceName</string>
|
||||
<string name="server_lab_stream_security">傳輸層安全 (TLS)</string>
|
||||
<string name="server_lab_stream_fingerprint">Fingerprint</string>
|
||||
<string name="server_lab_stream_alpn">Alpn</string>
|
||||
<string name="server_lab_allow_insecure">跳過憑證驗證 (allowInsecure)</string>
|
||||
<string name="server_lab_sni">SNI</string>
|
||||
<string name="server_lab_address3">伺服器位址</string>
|
||||
@@ -73,6 +76,10 @@
|
||||
<string name="server_lab_security4">使用者名稱 (可選)</string>
|
||||
<string name="server_lab_encryption">加密 (encryption)</string>
|
||||
<string name="server_lab_flow">流程 (flow)</string>
|
||||
<string name="server_lab_public_key">PublicKey</string>
|
||||
<string name="server_lab_short_id">ShortId</string>
|
||||
<string name="server_lab_spider_x">SpiderX</string>
|
||||
<string name="server_lab_secret_key">SecretKey</string>
|
||||
<string name="server_lab_reserved">Reserved (可選)</string>
|
||||
<string name="server_lab_local_address">本機位址(可選IPv4/IPv6,逗號隔開)</string>
|
||||
<string name="server_lab_local_mtu">MTU(可選, 預設1420)</string>
|
||||
@@ -93,6 +100,7 @@
|
||||
<string name="server_lab_request_host6">Host(SNI)(可選)</string>
|
||||
<string name="toast_asset_copy_failed">失敗,請使用檔案總管</string>
|
||||
<string name="menu_item_add_file">新增檔案</string>
|
||||
<string name="title_url">URL</string>
|
||||
<string name="menu_item_download_file">下載檔案</string>
|
||||
<string name="toast_action_not_allowed">禁止此項操作</string>
|
||||
|
||||
@@ -217,22 +225,22 @@
|
||||
<string name="logcat_copy">複製</string>
|
||||
<string name="logcat_clear">清除</string>
|
||||
<string name="title_service_restart">重啟服務</string>
|
||||
<string name="title_del_all_config">刪除全部配置</string>
|
||||
<string name="title_del_duplicate_config">刪除重複配置</string>
|
||||
<string name="title_del_invalid_config">刪除無效配置 (先偵測)</string>
|
||||
<string name="title_export_all">匯出全部 (非自訂) 配置至剪貼簿</string>
|
||||
<string name="title_del_all_config">刪除目前群組配置</string>
|
||||
<string name="title_del_duplicate_config">刪除目前群組重複配置</string>
|
||||
<string name="title_del_invalid_config">刪除目前群組無效配置</string>
|
||||
<string name="title_export_all">匯出目前群組配置至剪貼簿</string>
|
||||
<string name="title_sub_setting">訂閱分組設定</string>
|
||||
<string name="sub_setting_remarks">備註</string>
|
||||
<string name="sub_setting_url">Optional URL</string>
|
||||
<string name="sub_setting_enable">啟用更新</string>
|
||||
<string name="sub_auto_update">啟用自動更新</string>
|
||||
<string name="title_sub_update">更新訂閱</string>
|
||||
<string name="title_ping_all_server">偵測所有配置 Tcping</string>
|
||||
<string name="title_real_ping_all_server">偵測所有配置真延遲</string>
|
||||
<string name="title_sub_update">更新目前群組訂閱</string>
|
||||
<string name="title_ping_all_server">偵測目前群組配置 Tcping</string>
|
||||
<string name="title_real_ping_all_server">偵測目前群組配置真延遲</string>
|
||||
<string name="title_user_asset_setting">Geo 資源檔案</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">Delete %d duplicate configurations</string>
|
||||
|
||||
<string name="tasker_start_service">啟動服務</string>
|
||||
|
||||
@@ -178,6 +178,7 @@
|
||||
<item>Русский</item>
|
||||
<item>فارسی</item>
|
||||
<item>عربي</item>
|
||||
<item>বাংলা</item>
|
||||
</string-array>
|
||||
|
||||
|
||||
@@ -190,6 +191,7 @@
|
||||
<item>ru</item>
|
||||
<item>fa</item>
|
||||
<item>ar</item>
|
||||
<item>bn</item>
|
||||
</string-array>
|
||||
|
||||
<string-array name="mux_xudp_quic_value" translatable="false">
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
|
||||
<style name="TabLayoutTextStyle" parent="TextAppearance.Design.Tab">
|
||||
<item name="textAllCaps">false</item>
|
||||
</style>
|
||||
</resources>
|
||||
@@ -36,7 +36,8 @@
|
||||
<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_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>
|
||||
<string name="server_lab_address">address</string>
|
||||
<string name="server_lab_port">port</string>
|
||||
@@ -65,7 +66,7 @@
|
||||
<string name="server_lab_path_grpc">gRPC serviceName</string>
|
||||
<string name="server_lab_stream_security">TLS</string>
|
||||
<string name="server_lab_stream_fingerprint" translatable="false">Fingerprint</string>
|
||||
<string name="server_lab_stream_alpn" translatable="false">Alpn</string>
|
||||
<string name="server_lab_stream_alpn">Alpn</string>
|
||||
<string name="server_lab_allow_insecure">allowInsecure</string>
|
||||
<string name="server_lab_sni">SNI</string>
|
||||
<string name="server_lab_address3">address</string>
|
||||
@@ -76,10 +77,10 @@
|
||||
<string name="server_lab_security4">User(Optional)</string>
|
||||
<string name="server_lab_encryption">encryption</string>
|
||||
<string name="server_lab_flow">flow</string>
|
||||
<string name="server_lab_public_key" translatable="false">PublicKey</string>
|
||||
<string name="server_lab_short_id" translatable="false">ShortId</string>
|
||||
<string name="server_lab_spider_x" translatable="false">SpiderX</string>
|
||||
<string name="server_lab_secret_key" translatable="false">SecretKey</string>
|
||||
<string name="server_lab_public_key">PublicKey</string>
|
||||
<string name="server_lab_short_id">ShortId</string>
|
||||
<string name="server_lab_spider_x">SpiderX</string>
|
||||
<string name="server_lab_secret_key">SecretKey</string>
|
||||
<string name="server_lab_reserved">Reserved(Optional)</string>
|
||||
<string name="server_lab_local_address">Local address (optional IPv4/IPv6, separated by commas)</string>
|
||||
<string name="server_lab_local_mtu">Mtu(optional, default 1420)</string>
|
||||
@@ -102,7 +103,7 @@
|
||||
<string name="menu_item_add_asset">Add asset</string>
|
||||
<string name="menu_item_add_file">Add files</string>
|
||||
<string name="menu_item_add_url">Add URL</string>
|
||||
<string name="title_url" translatable="false">URL</string>
|
||||
<string name="title_url">URL</string>
|
||||
<string name="menu_item_download_file">Download files</string>
|
||||
<string name="title_user_asset_add_url">Add asset URL</string>
|
||||
<string name="msg_file_not_found">File not found</string>
|
||||
@@ -229,22 +230,22 @@
|
||||
<string name="logcat_copy">Copy</string>
|
||||
<string name="logcat_clear">Clear</string>
|
||||
<string name="title_service_restart">Service restart</string>
|
||||
<string name="title_del_all_config">Delete all config</string>
|
||||
<string name="title_del_duplicate_config">Delete duplicate config</string>
|
||||
<string name="title_del_invalid_config">Delete invalid config(Test first)</string>
|
||||
<string name="title_export_all">Export non-custom configs to clipboard</string>
|
||||
<string name="title_del_all_config">Delete current group configuration</string>
|
||||
<string name="title_del_duplicate_config">Delete current group duplicate configuration</string>
|
||||
<string name="title_del_invalid_config">Delete current group invalid configuration</string>
|
||||
<string name="title_export_all">Export current group non-custom configs to clipboard</string>
|
||||
<string name="title_sub_setting">Subscription group setting</string>
|
||||
<string name="sub_setting_remarks">remarks</string>
|
||||
<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="title_sub_update">Update subscription</string>
|
||||
<string name="title_ping_all_server">Tcping all configuration</string>
|
||||
<string name="title_real_ping_all_server">Real delay all configuration</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>
|
||||
<string name="title_user_asset_setting">Geo asset files</string>
|
||||
<string name="title_sort_by_test_results">Sorting by test results</string>
|
||||
<string name="title_filter_config">Filter configuration file</string>
|
||||
<string name="filter_config_all">All subscription groups</string>
|
||||
<string name="filter_config_all">All groups</string>
|
||||
<string name="title_del_duplicate_config_count">Delete %d duplicate configurations</string>
|
||||
|
||||
<string name="tasker_start_service">Start Service</string>
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<paths>
|
||||
<cache-path name="cache" path="/"/>
|
||||
<cache-path
|
||||
name="cache"
|
||||
path="/" />
|
||||
</paths>
|
||||
|
||||
@@ -109,13 +109,13 @@
|
||||
|
||||
<EditTextPreference
|
||||
android:inputType="number"
|
||||
android:key="pref_mux_concurency"
|
||||
android:key="pref_mux_concurrency"
|
||||
android:summary="8"
|
||||
android:title="@string/title_pref_mux_concurency" />
|
||||
|
||||
<EditTextPreference
|
||||
android:inputType="number"
|
||||
android:key="pref_mux_xudp_concurency"
|
||||
android:key="pref_mux_xudp_concurrency"
|
||||
android:summary="8"
|
||||
android:title="@string/title_pref_mux_xudp_concurency" />
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
package com.v2ray.ang;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
/**
|
||||
* To work on unit tests, switch the Test Artifact in the Build Variants view.
|
||||
*/
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import org.junit.Assert.*
|
||||
import org.junit.Test
|
||||
import com.v2ray.ang.util.Utils
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertFalse
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Test
|
||||
|
||||
class UtilTest {
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||
plugins {
|
||||
id("com.android.application") version "8.2.2" apply false
|
||||
id("com.android.library") version "8.2.2" apply false
|
||||
id("com.android.application") version "8.4.2" apply false
|
||||
id("com.android.library") version "8.4.2" apply false
|
||||
id("org.jetbrains.kotlin.android") version "1.9.23" apply false
|
||||
}
|
||||
|
||||
63
V2rayNG/gradle/libs.versions.toml
Normal file
63
V2rayNG/gradle/libs.versions.toml
Normal file
@@ -0,0 +1,63 @@
|
||||
[versions]
|
||||
activityKtx = "1.9.1"
|
||||
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"
|
||||
gson = "2.11.0"
|
||||
junit = "4.13.2"
|
||||
kotlinReflect = "2.0.0"
|
||||
kotlinxCoroutinesCore = "1.8.1"
|
||||
legacySupportV4 = "1.0.0"
|
||||
lifecycleViewmodelKtx = "2.8.4"
|
||||
material = "1.12.0"
|
||||
mmkvStatic = "1.3.4"
|
||||
multidex = "2.0.1"
|
||||
preferenceKtx = "1.2.1"
|
||||
quickieBundled = "1.9.0"
|
||||
recyclerview = "1.3.2"
|
||||
rxandroid = "3.0.2"
|
||||
rxjava = "3.1.8"
|
||||
rxpermissions = "0.12"
|
||||
toastcompat = "1.1.0"
|
||||
viewpager2 = "1.1.0"
|
||||
workRuntimeKtx = "2.9.1"
|
||||
|
||||
[libraries]
|
||||
activity-ktx = { module = "androidx.activity:activity-ktx", version.ref = "activityKtx" }
|
||||
appcompat = { module = "androidx.appcompat:appcompat", version.ref = "appcompat" }
|
||||
cardview = { module = "androidx.cardview:cardview", version.ref = "cardview" }
|
||||
constraintlayout = { module = "androidx.constraintlayout:constraintlayout", version.ref = "constraintlayout" }
|
||||
core = { module = "com.google.zxing:core", version.ref = "core" }
|
||||
editorkit = { module = "com.blacksquircle.ui:editorkit", version.ref = "editorkit" }
|
||||
flexbox = { module = "com.google.android.flexbox:flexbox", version.ref = "flexbox" }
|
||||
fragment-ktx = { module = "androidx.fragment:fragment-ktx", version.ref = "fragmentKtx" }
|
||||
gson = { module = "com.google.code.gson:gson", version.ref = "gson" }
|
||||
junit = { module = "junit:junit", version.ref = "junit" }
|
||||
kotlin-reflect = { module = "org.jetbrains.kotlin:kotlin-reflect", version.ref = "kotlinReflect" }
|
||||
kotlinx-coroutines-android = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-android", version.ref = "kotlinxCoroutinesCore" }
|
||||
kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlinxCoroutinesCore" }
|
||||
language-base = { module = "com.blacksquircle.ui:language-base", version.ref = "editorkit" }
|
||||
language-json = { module = "com.blacksquircle.ui:language-json", version.ref = "editorkit" }
|
||||
legacy-support-v4 = { module = "androidx.legacy:legacy-support-v4", version.ref = "legacySupportV4" }
|
||||
lifecycle-livedata-ktx = { module = "androidx.lifecycle:lifecycle-livedata-ktx", version.ref = "lifecycleViewmodelKtx" }
|
||||
lifecycle-runtime-ktx = { module = "androidx.lifecycle:lifecycle-runtime-ktx", version.ref = "lifecycleViewmodelKtx" }
|
||||
lifecycle-viewmodel-ktx = { module = "androidx.lifecycle:lifecycle-viewmodel-ktx", version.ref = "lifecycleViewmodelKtx" }
|
||||
material = { module = "com.google.android.material:material", version.ref = "material" }
|
||||
mmkv-static = { module = "com.tencent:mmkv-static", version.ref = "mmkvStatic" }
|
||||
multidex = { module = "androidx.multidex:multidex", version.ref = "multidex" }
|
||||
quickie-bundled = { module = "io.github.g00fy2.quickie:quickie-bundled", version.ref = "quickieBundled" }
|
||||
recyclerview = { module = "androidx.recyclerview:recyclerview", version.ref = "recyclerview" }
|
||||
preference-ktx = { module = "androidx.preference:preference-ktx", version.ref = "preferenceKtx" }
|
||||
rxandroid = { module = "io.reactivex.rxjava3:rxandroid", version.ref = "rxandroid" }
|
||||
rxjava = { module = "io.reactivex.rxjava3:rxjava", version.ref = "rxjava" }
|
||||
rxpermissions = { module = "com.github.tbruyelle:rxpermissions", version.ref = "rxpermissions" }
|
||||
toastcompat = { module = "me.drakeet.support:toastcompat", version.ref = "toastcompat" }
|
||||
viewpager2 = { module = "androidx.viewpager2:viewpager2", version.ref = "viewpager2" }
|
||||
work-multiprocess = { module = "androidx.work:work-multiprocess", version.ref = "workRuntimeKtx" }
|
||||
work-runtime-ktx = { module = "androidx.work:work-runtime-ktx", version.ref = "workRuntimeKtx" }
|
||||
|
||||
[plugins]
|
||||
@@ -1,6 +1,6 @@
|
||||
#Sun Jun 21 12:33:19 CST 2020
|
||||
#Sun Jul 28 13:40:50 CST 2024
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip
|
||||
|
||||
@@ -11,6 +11,7 @@ dependencyResolutionManagement {
|
||||
google()
|
||||
mavenCentral()
|
||||
jcenter()
|
||||
maven { url = uri("https://jitpack.io") }
|
||||
}
|
||||
}
|
||||
rootProject.name = "V2rayNG"
|
||||
|
||||
Reference in New Issue
Block a user