Compare commits

...

61 Commits

Author SHA1 Message Date
2dust
214d9e1c53 up 1.8.37 2024-08-15 09:58:44 +08:00
2dust
92900c3f74 Optimize text descriptions 2024-08-15 09:54:42 +08:00
2dust
e17e566daa Update subscriptions based on grouping
https://github.com/2dust/v2rayNG/issues/3445
2024-08-14 20:16:26 +08:00
2dust
df4e232087 Code Optimization 2024-08-14 19:58:32 +08:00
2dust
828a39331b Add a progress bar 2024-08-14 17:55:41 +08:00
2dust
a0223a3eee logic optimization
https://github.com/2dust/v2rayNG/issues/3470
2024-08-14 15:06:21 +08:00
2dust
7f6a526b25 Add profile filtering
https://github.com/2dust/v2rayNG/issues/3471
2024-08-14 10:46:42 +08:00
2dust
9983ea25d2 Performance optimization 2024-08-13 21:17:27 +08:00
2dust
47166b937f Main interface list performance optimization 2024-08-13 17:07:07 +08:00
2dust
0a09966e81 Update UserAssetActivity.kt 2024-08-13 16:09:53 +08:00
2dust
7ec34e934e Bug fix 2024-08-13 15:11:53 +08:00
2dust
f2b03e7492 Merge branch 'master' of https://github.com/2dust/v2rayNG 2024-08-13 15:10:34 +08:00
2dust
5208bd62c5 Optimized UI
com.google.android.material.progressindicator.LinearProgressIndicator
2024-08-13 14:39:45 +08:00
AmirMohammad Yazdanmanesh
d9f0854c27 Fix some Persian word translations (#3469)
* Translate fingerprint

* The word 'موفقیت' is a noun and does not mean 'success' in this context

* Translate 'about'
2024-08-12 19:35:50 +08:00
Tamim Hossain
6be125b5cb Replace deprecated packagingOptions with packaging for jniLibs configuration (#3464)
Replaced deprecated packagingOptions with packaging for jniLibs configuration.

Updated build.gradle file to use the new packaging configuration method as the previous
packagingOptions method is deprecated. This change ensures compatibility with the latest
Gradle plugin versions and avoids any potential issues with outdated configuration.

Changes:
- Updated from `packagingOptions { jniLibs { useLegacyPackaging = true } }`
  to `packaging { jniLibs { useLegacyPackaging = true } }`
2024-08-12 09:20:23 +08:00
2dust
c884c098fd Bug fix
https://github.com/2dust/v2rayNG/issues/3426
2024-08-10 20:11:34 +08:00
AnGgIt86
f77fe05c92 Update RxPermissions and rxjava3 (#3463) 2024-08-10 19:56:37 +08:00
Tamim Hossain
f3bfa8ceba Update null safety handling with orEmpty() (#3461)
Replaced ?: "" with orEmpty() for improved null safety

Updated all instances of the null-coalescing operator (?: "") with orEmpty() to enhance null safety across the codebase. This change ensures that an empty string is used when a nullable value is null, improving consistency and reducing potential null-related issues.
2024-08-10 19:54:45 +08:00
Tamim Hossain
ae7d9d87d2 Inline return statements for cleaner code (#3460)
Refactored conditional return statements to be inline.
- Simplified code readability by inlining return statements.
- Maintained functionality while improving code clarity.
2024-08-10 19:53:35 +08:00
Tamim Hossain
1652040c1c Simplify search logic using SearchView (#3459)
Refactor the search functionality to use SearchView for better handling of search queries.
- Removed redundant code and replaced it with SearchView's onQueryTextChange listener.
- Improved readability and maintainability of the search logic.
2024-08-10 19:51:45 +08:00
Tamim Hossain
818b7cdff4 Replace global scope with CoroutineScope for socket operations (#3454)
Replaced the use of global scope with CoroutineScope(Dispatchers.IO) in the socket operation function. This change improves coroutine management and ensures proper use of dispatchers for background tasks. The code now properly handles retries with exponential backoff and logs attempts and errors.
2024-08-10 19:48:53 +08:00
Tamim Hossain
48ce359d2d Fix: Refactor to use lazy initialization for binding (#3453)
Refactor binding initialization to use lazy delegation

Replaced direct initialization of binding with lazy initialization to improve performance and resource management. This change defers the creation of the binding object until it is actually needed, which can reduce memory usage and improve efficiency.

- Updated binding initialization to use lazy
- Ensured proper access and lifecycle handling

This update ensures that the binding is initialized only when required, leading to a more efficient use of resources.
2024-08-10 19:41:37 +08:00
Tamim Hossain
e115bf0c6d Refactor EConfigType to use constants from AppConfig (#3451)
Updated the EConfigType enum to use protocol scheme constants from AppConfig. This change improves maintainability by centralizing protocol scheme definitions in a single location.
2024-08-10 19:34:24 +08:00
Tamim Hossain
0415b60ba5 Fix ABI split configuration for updated Gradle plugin API (#3449)
* Fix ABI split configuration for updated Gradle plugin API

Updated the ABI split configuration in the build.gradle file to use the new syntax.
Replaced deprecated `reset()` method with the updated `splits.abi` configuration block and added `universalApk = true` to include a universal APK.

* Unresolved reference: universalApk

Unresolved reference: universalApk
2024-08-10 19:32:32 +08:00
AmirMohammad Yazdanmanesh
4570fdb05f Implement a check to start V2Ray if the network connected (#3439)
* Implement isNetworkConnected functionality

* Implement a check to start V2Ray if the network connected
2024-08-10 19:23:52 +08:00
mayampi01
9d109e7ca9 Hardcode popular Android Private DNS rule to fix localhost DNS problem (#3433)
* hardcode popular Android Private DNS rule to fix localhost DNS problem

* V2rayConfigUtil.kt: Fix hosts type

* hardcode popular Android Private DNS: Add dns.pub
2024-08-10 19:21:47 +08:00
2dust
2574553180 up 1.8.36 2024-08-05 20:28:36 +08:00
2dust
66e77d50bd Optimized UI 2024-08-04 19:18:16 +08:00
mayampi01
253bd793d7 Add geosite:private and make it work (#3418)
* Add geosite:private and make it work

* Update AppConfig.kt

---------

Co-authored-by: 2dust <31833384+2dust@users.noreply.github.com>
2024-08-04 10:15:55 +08:00
2dust
be30de6728 Update build.yml 2024-08-03 10:44:12 +08:00
Tamim Hossain
a1455bbb1c Updated getLocale function to use appropriate Locale constants (#3413)
Updated getLocale function to use appropriate Locale constants
2024-08-03 10:38:00 +08:00
Tamim Hossain
1ac19ae3e9 Fix getDarkModeStatus to handle configuration changes more accurately (#3412)
Updated getDarkModeStatus function to improve detection of dark mode status.
2024-08-03 10:36:21 +08:00
Tamim Hossain
6e6ca209df Refactor base64 decode function to simplify handling (#3411)
Refactored the decode function to simplify base64 decoding and improve handling of various base64 formats. Removed redundant code and improved clarity.
2024-08-03 10:35:15 +08:00
Tamim Hossain
52699967cd Simplify parseInt function (#3410)
Refactored the parseInt function to use toIntOrNull() for more concise and idiomatic error handling. This change simplifies the code and avoids the need for manual exception handling
2024-08-03 10:33:28 +08:00
Tamim Hossain
146d20ce86 Optimize strState Handling by Removing Redundant try-catch (#3409)
Refactored the code for handling the `strState` string to remove the redundant `try-catch` block. Updated to use safe call with the Elvis operator for improved readability and efficiency. This change simplifies the code and reduces the need for exception handling in this context.
2024-08-03 10:30:38 +08:00
Tamim Hossain
b6959b5990 Refactor filterConfig Function for Simplified Code and Reusability (#3408)
Refactored the `filterConfig` function in `MainViewModel` to simplify the code and improve reusability. The updated implementation reduces redundancy, enhances readability, and streamlines the configuration filtering process, making the code more efficient and maintainable.
2024-08-03 10:20:28 +08:00
Tamim Hossain
06649df8b1 Add Documentation Comments to AppConfig Constants (#3406)
This branch adds comprehensive documentation comments to the AppConfig object in the codebase
2024-08-03 09:52:00 +08:00
Tamim Hossain
0174ed9082 Remove redundant access modifier (#3405)
Removed unnecessary access modifier
2024-08-03 09:49:44 +08:00
2dust
adabb281b1 Update string 2024-08-02 10:11:49 +08:00
2dust
63e710d1ab Simplifying the filter configuration file 2024-08-02 10:05:40 +08:00
Tamim Hossain
509a568446 Added Bangla Translation (#3399)
Added Bangla translations for all resources in the project. The translations were provided by a native Bangla speaker from Bangladesh—myself! This update includes translations for all project elements to support localization for Bangla-speaking users.
2024-08-01 20:18:51 +08:00
Tamim Hossain
6fd94b53f0 Refactor charset usage and consolidate function implementation (#3398)
- Updated charset usage from string literal "UTF_8" to standard `Charsets.UTF_8`.
- Consolidated the implementation of the `getDelayTestUrl()` function.
- Improved code readability and consistency with these changes.
2024-08-01 20:17:02 +08:00
Tamim Hossain
164412fa34 Refactor GlobalScope Usage to Prevent Memory Leaks (#3396)
Replaced usage of GlobalScope with a specific coroutine scope tied to the lifecycle of the service . This change helps to prevent potential memory leaks and unintended behavior by ensuring coroutines are properly managed and tied to the appropriate lifecycle.
2024-08-01 20:07:30 +08:00
Tamim Hossain
514ca0810e Refactor V2RayServiceManager to Use Constants for Configuration Values (#3395)
Refactor V2RayServiceManager to Use Constants for Configuration Values.
- Replaced hardcoded string literals for 'uplink' and 'downlink' with constants from AppConfig.
- Introduced constants for notification channel ID and name.
2024-08-01 20:05:09 +08:00
Tamim Hossain
7582f86482 Refactor extension functions for better performance and clarity (#3394)
Refactor Kotlin extension functions for clarity and performance

- Updated `v2RayApplication` extension to handle potential ClassCastException.
- Simplified `toast` functions for better readability.
- Refactored `toTrafficString` function to reduce redundancy and improve performance.
- Minor improvements to JSON and URL connection handling.
2024-08-01 19:44:07 +08:00
mayampi01
4b4c46e5ae Drop geolocation-cn (cn contains geolocation-cn) (#3393) 2024-08-01 19:38:08 +08:00
2dust
162e156b33 Bug fix
https://github.com/2dust/v2rayNG/pull/3390
2024-08-01 09:47:33 +08:00
mayampi01
bc7d1971ef Add localhost DNS support (#3384) 2024-08-01 09:27:46 +08:00
2dust
bbdee92f37 Determine custom rule order based on preset rules
https://github.com/2dust/v2rayNG/pull/3388
2024-07-31 21:01:16 +08:00
Tamim Hossain
cf9e830cc7 Replace deprecated onBackPressed with onBackPressedDispatcher (#3389)
Replaced deprecated `onBackPressed` with `onBackPressedDispatcher`. The `onBackPressedDispatcher` handles the home button press by delegating to the appropriate back navigation method.
2024-07-31 20:36:52 +08:00
mayampi01
804e425a87 Make IPIfNonMatch actually work (#3387) 2024-07-31 20:18:40 +08:00
mayampi01
cc21383928 Fix typo: s/Domian/Domain/g (#3383) 2024-07-31 20:15:06 +08:00
2dust
413f4efd69 up 1.8.35 2024-07-29 19:12:58 +08:00
Tamim Hossain
de69605eff Introduced version catalog for streamlined dependency management (#3377)
This pull request integrates a version catalog (libs.versions.toml) to centralize dependency management within the project. By utilizing this approach, we enhance dependency consistency, reduce maintenance overhead, and facilitate version updates across multiple modules. This change provides a more organized and efficient way to manage project dependencies.
2024-07-28 09:27:58 +08:00
mayampi01
9338ba3525 Set UseIP in Direct to break the Android Private DNS dead loop (#3362)
* Set UseIP in Direct to break the Android Private DNS dead loop

* fakedns: No need to set UseIP again
2024-07-25 19:50:23 +08:00
2dust
322b6ec615 Bug fix 2024-07-25 19:46:39 +08:00
2dust
304232d029 up 1.8.34 2024-07-22 11:34:44 +08:00
2dust
f80c3bfe07 up 1.8.33 2024-07-20 15:05:45 +08:00
2dust
bb0a62fc8b up 1.8.32 2024-07-18 10:45:05 +08:00
2dust
5bfdca6cd9 Bug fix 2024-07-18 10:15:58 +08:00
2dust
447e712a9d up 1.8.31 2024-07-16 14:05:34 +08:00
57 changed files with 1304 additions and 636 deletions

View File

@@ -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

View File

@@ -11,18 +11,22 @@ android {
applicationId = "com.v2ray.ang"
minSdk = 21
targetSdk = 34
versionCode = 574
versionName = "1.8.30"
versionCode = 582
versionName = "1.8.37"
multiDexEnabled = true
splits.abi {
reset()
include(
"arm64-v8a",
"armeabi-v7a",
"x86_64",
"x86"
)
splits {
abi {
isEnable = true
include(
"arm64-v8a",
"armeabi-v7a",
"x86_64",
"x86"
)
isUniversalApk = true
}
}
}
compileOptions {
@@ -50,13 +54,6 @@ android {
jvmTarget = JavaVersion.VERSION_1_8.toString()
}
splits {
abi {
isEnable = true
isUniversalApk = true
}
}
applicationVariants.all {
val variant = this
val versionCodes =
@@ -87,7 +84,7 @@ android {
buildConfig = true
}
packagingOptions {
packaging {
jniLibs {
useLegacyPackaging = true
}
@@ -96,44 +93,44 @@ 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)
// Updating these 2 dependencies may cause some errors. Be careful.
implementation(libs.work.runtime.ktx)
implementation(libs.work.multiprocess)
}

View File

@@ -1,2 +1 @@
geosite:cn,
geosite:geolocation-cn
geosite:cn

View File

@@ -81,7 +81,9 @@
},
{
"protocol": "freedom",
"settings": {},
"settings": {
"domainStrategy": "UseIP"
},
"tag": "direct"
},
{

View File

@@ -17,9 +17,6 @@ class AngApplication : MultiDexApplication(), Configuration.Provider {
application = this
}
//var firstRun = false
// private set
override fun onCreate() {
super.onCreate()

View File

@@ -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,18 @@ 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://"
}

View File

@@ -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 }

View File

@@ -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?,
)

View File

@@ -1,4 +1,4 @@
package com.v2ray.ang.dto
data class ServersCache(val guid: String,
val config: ServerConfig)
val profile: ProfileItem)

View File

@@ -1,6 +1,8 @@
package com.v2ray.ang.extension
import android.content.Context
import android.net.ConnectivityManager
import android.net.NetworkCapabilities
import android.os.Build
import android.widget.Toast
import com.v2ray.ang.AngApplication
@@ -9,15 +11,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 +36,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
@@ -67,3 +57,19 @@ val URI.idnHost: String
get() = host?.replace("[", "")?.replace("]", "") ?: ""
fun String.removeWhiteSpace(): String = replace("\\s+".toRegex(), "")
val Context.isNetworkConnected: Boolean
get() {
val manager = getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M)
manager.getNetworkCapabilities(manager.activeNetwork)?.let {
it.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) ||
it.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) ||
it.hasTransport(NetworkCapabilities.TRANSPORT_BLUETOOTH) ||
it.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET) ||
it.hasTransport(NetworkCapabilities.TRANSPORT_VPN)
} ?: false
else
@Suppress("DEPRECATION")
manager.activeNetworkInfo?.isConnectedOrConnecting == true
}

View File

@@ -67,7 +67,7 @@ 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)) {

View File

@@ -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,14 @@ 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 +45,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)

View File

@@ -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)
}

View File

@@ -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 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 io.reactivex.rxjava3.core.Observable
import io.reactivex.rxjava3.disposables.Disposable
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) {
@@ -237,7 +237,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 = ""
@@ -319,8 +319,8 @@ object V2RayServiceManager {
@RequiresApi(Build.VERSION_CODES.O)
private fun createNotificationChannel(): String {
val channelId = "RAY_NG_M_CH_ID"
val channelName = "V2rayNG Background Service"
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
@@ -334,8 +334,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,29 +362,29 @@ object V2RayServiceManager {
}
private fun startSpeedNotification() {
if (mSubscription == null &&
if (mDisposable == null &&
v2rayPoint.isRunning &&
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) {
@@ -411,9 +411,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)
}
}

View File

@@ -17,6 +17,7 @@ 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
@@ -244,7 +245,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)
@@ -327,7 +328,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)
}

View File

@@ -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)

View File

@@ -23,7 +23,9 @@ 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)
@@ -32,7 +34,7 @@ abstract class BaseActivity : AppCompatActivity() {
@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)
}

View File

@@ -21,13 +21,13 @@ 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)

View File

@@ -18,19 +18,21 @@ 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.isNetworkConnected
import com.v2ray.ang.extension.toast
import com.v2ray.ang.helper.SimpleItemTouchHelperCallback
import com.v2ray.ang.service.V2RayServiceManager
@@ -38,40 +40,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 +118,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,13 +172,41 @@ 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()) {
return
if (isNetworkConnected) {
if (MmkvManager.mainStorage?.decodeString(MmkvManager.KEY_SELECTED_SERVER).isNullOrEmpty()) {
return
}
V2RayServiceManager.startV2Ray(this)
} else {
ToastCompat.makeText(this, getString(R.string.connection_test_fail), Toast.LENGTH_LONG).show()
}
V2RayServiceManager.startV2Ray(this)
}
fun restartV2Ray() {
@@ -169,10 +214,10 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
Utils.stopVService(this)
}
Observable.timer(500, TimeUnit.MILLISECONDS)
.observeOn(AndroidSchedulers.mainThread())
.subscribe {
startV2Ray()
}
.observeOn(AndroidSchedulers.mainThread())
.subscribe {
startV2Ray()
}
}
public override fun onResume() {
@@ -186,7 +231,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 {
mainViewModel.filterConfig(query.orEmpty())
return false
}
override fun onQueryTextChange(newText: String?): Boolean = false
})
searchView.setOnCloseListener {
mainViewModel.filterConfig("")
false
}
}
return super.onCreateOptionsMenu(menu)
}
override fun onOptionsItemSelected(item: MenuItem) = when (item.itemId) {
@@ -194,46 +257,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 +319,18 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
}
R.id.export_all -> {
if (AngConfigManager.shareNonCustomConfigsToClipboard(this, mainViewModel.serverList) == 0) {
toast(R.string.toast_success)
} else {
toast(R.string.toast_failure)
binding.pbWaiting.show()
lifecycleScope.launch(Dispatchers.IO) {
val ret = mainViewModel.exportAllServer()
launch(Dispatchers.Main) {
if (ret == 0)
toast(R.string.toast_success)
else
toast(R.string.toast_failure)
binding.pbWaiting.hide()
}
}
true
}
@@ -269,55 +350,79 @@ 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()
mainViewModel.reloadServerList()
}
.setNegativeButton(android.R.string.no) {_, _ ->
//do noting
}
.show()
true
}
R.id.del_duplicate_config-> {
AlertDialog.Builder(this).setMessage(R.string.del_config_comfirm)
.setPositiveButton(android.R.string.ok) { _, _ ->
mainViewModel.removeDuplicateServer()
mainViewModel.reloadServerList()
binding.pbWaiting.show()
lifecycleScope.launch(Dispatchers.IO) {
mainViewModel.removeAllServer()
launch(Dispatchers.Main) {
mainViewModel.reloadServerList()
binding.pbWaiting.hide()
}
}
}
.setNegativeButton(android.R.string.no) {_, _ ->
.setNegativeButton(android.R.string.no) { _, _ ->
//do noting
}
.show()
true
}
R.id.del_duplicate_config -> {
AlertDialog.Builder(this).setMessage(R.string.del_config_comfirm)
.setPositiveButton(android.R.string.ok) { _, _ ->
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
}
.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()
mainViewModel.reloadServerList()
binding.pbWaiting.show()
lifecycleScope.launch(Dispatchers.IO) {
mainViewModel.removeInvalidServer()
launch(Dispatchers.Main) {
mainViewModel.reloadServerList()
binding.pbWaiting.hide()
}
}
}
.setNegativeButton(android.R.string.no) {_, _ ->
.setNegativeButton(android.R.string.no) { _, _ ->
//do noting
}
.show()
true
}
R.id.sort_by_test_results -> {
MmkvManager.sortByTestResults()
mainViewModel.reloadServerList()
true
}
R.id.filter_config -> {
mainViewModel.filterConfig(this)
binding.pbWaiting.show()
lifecycleScope.launch(Dispatchers.IO) {
mainViewModel.sortByTestResults()
launch(Dispatchers.Main) {
mainViewModel.reloadServerList()
binding.pbWaiting.hide()
}
}
true
}
else -> super.onOptionsItemSelected(item)
}
private fun importManually(createConfigType : Int) {
private fun importManually(createConfigType: Int) {
startActivity(
Intent()
.putExtra("createConfigType", createConfigType)
@@ -336,16 +441,16 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
// .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP), requestCode)
// } catch (e: Exception) {
RxPermissions(this)
.request(Manifest.permission.CAMERA)
.subscribe {
if (it)
if (forConfig)
scanQRCodeForConfig.launch(Intent(this, ScannerActivity::class.java))
else
scanQRCodeForUrlToCustomConfig.launch(Intent(this, ScannerActivity::class.java))
.request(Manifest.permission.CAMERA)
.subscribe {
if (it)
if (forConfig)
scanQRCodeForConfig.launch(Intent(this, ScannerActivity::class.java))
else
toast(R.string.toast_permission_denied)
}
scanQRCodeForUrlToCustomConfig.launch(Intent(this, ScannerActivity::class.java))
else
toast(R.string.toast_permission_denied)
}
// }
return true
}
@@ -378,22 +483,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()
}
}
}
@@ -472,14 +581,15 @@ 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()
private fun importConfigViaSub(): Boolean {
// 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 +598,8 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
} else {
toast(R.string.toast_failure)
}
dialog.dismiss()
//dialog.dismiss()
binding.pbWaiting.hide()
}
}
return true
@@ -526,19 +637,19 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
Manifest.permission.READ_EXTERNAL_STORAGE
}
RxPermissions(this)
.request(permission)
.subscribe {
if (it) {
try {
contentResolver.openInputStream(uri).use { input ->
importCustomizeConfig(input?.bufferedReader()?.readText())
}
} catch (e: Exception) {
e.printStackTrace()
.request(permission)
.subscribe {
if (it) {
try {
contentResolver.openInputStream(uri).use { input ->
importCustomizeConfig(input?.bufferedReader()?.readText())
}
} else
toast(R.string.toast_permission_denied)
}
} catch (e: Exception) {
e.printStackTrace()
}
} else
toast(R.string.toast_permission_denied)
}
}
/**
@@ -585,27 +696,34 @@ 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-> {
R.id.about -> {
startActivity(Intent(this, AboutActivity::class.java))
}
}

View File

@@ -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,8 +25,8 @@ 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>()
@@ -38,9 +37,6 @@ class MainRecyclerAdapter(val activity: MainActivity) : RecyclerView.Adapter<Mai
}
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 +47,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,10 +57,9 @@ 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() ?: ""
if ((aff?.testDelayMillis ?: 0L) < 0L) {
@@ -72,37 +67,35 @@ class MainRecyclerAdapter(val activity: MainActivity) : RecyclerView.Adapter<Mai
} 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 +104,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 +112,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 +120,7 @@ class MainRecyclerAdapter(val activity: MainActivity) : RecyclerView.Adapter<Mai
mActivity.toast(R.string.toast_failure)
}
}
2 -> shareFullContent(guid)
else -> mActivity.toast("else")
}
@@ -137,21 +132,21 @@ 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) {
.putExtra("isRunning", isRunning)
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)
}
.setNegativeButton(android.R.string.no) {_, _ ->
.setNegativeButton(android.R.string.no) { _, _ ->
//do noting
}
.show()
@@ -164,20 +159,20 @@ 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) {
Utils.stopVService(mActivity)
Observable.timer(500, TimeUnit.MILLISECONDS)
.observeOn(AndroidSchedulers.mainThread())
.subscribe {
V2RayServiceManager.startV2Ray(mActivity)
}
.observeOn(AndroidSchedulers.mainThread())
.subscribe {
V2RayServiceManager.startV2Ray(mActivity)
}
}
}
}
@@ -202,7 +197,7 @@ class MainRecyclerAdapter(val activity: MainActivity) : RecyclerView.Adapter<Mai
}
}
private fun removeServer(guid: String,position:Int) {
private fun removeServer(guid: String, position: Int) {
mActivity.mainViewModel.removeServer(guid)
notifyItemRemoved(position)
notifyItemRangeChanged(position, mActivity.mainViewModel.serversCache.size)
@@ -212,6 +207,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))
}
@@ -236,14 +232,14 @@ class MainRecyclerAdapter(val activity: MainActivity) : RecyclerView.Adapter<Mai
}
class MainViewHolder(val itemMainBinding: ItemRecyclerMainBinding) :
BaseViewHolder(itemMainBinding.root), ItemTouchHelperViewHolder
BaseViewHolder(itemMainBinding.root), ItemTouchHelperViewHolder
class FooterViewHolder(val itemFooterBinding: ItemRecyclerFooterBinding) :
BaseViewHolder(itemFooterBinding.root)
BaseViewHolder(itemFooterBinding.root)
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)

View File

@@ -23,12 +23,14 @@ import com.v2ray.ang.util.MmkvManager
import com.v2ray.ang.util.Utils
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import rx.android.schedulers.AndroidSchedulers
import rx.schedulers.Schedulers
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
import io.reactivex.rxjava3.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
}
})
@@ -250,9 +248,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 +270,7 @@ class PerAppProxyActivity : BaseActivity() {
} else {
content
}
if (TextUtils.isEmpty(proxyApps)) {
return false
}
if (TextUtils.isEmpty(proxyApps)) return false
adapter?.blacklist?.clear()
@@ -316,12 +310,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

View File

@@ -8,7 +8,7 @@ import com.v2ray.ang.AppConfig
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)

View File

@@ -10,7 +10,7 @@ import androidx.activity.result.contract.ActivityResultContracts
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,7 +23,7 @@ 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"
}
@@ -33,7 +33,6 @@ class RoutingSettingsFragment : Fragment() {
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)
}

View File

@@ -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)

View File

@@ -9,7 +9,7 @@ import android.os.Build
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 +45,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()
}
@@ -110,7 +110,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())

View File

@@ -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)

View File

@@ -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)) {

View File

@@ -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,8 +101,12 @@ class SubEditActivity : BaseActivity() {
if (editSubId.isNotEmpty()) {
AlertDialog.Builder(this).setMessage(R.string.del_config_comfirm)
.setPositiveButton(android.R.string.ok) { _, _ ->
MmkvManager.removeSubscription(editSubId)
finish()
lifecycleScope.launch(Dispatchers.IO) {
MmkvManager.removeSubscription(editSubId)
launch(Dispatchers.Main) {
finish()
}
}
}
.setNegativeButton(android.R.string.no) {_, _ ->
// do nothing

View File

@@ -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)

View File

@@ -17,7 +17,7 @@ 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 +27,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")

View File

@@ -11,20 +11,18 @@ 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) {
@@ -32,13 +30,13 @@ class UrlSchemeActivity : BaseActivity() {
"install-config" -> {
val uri: Uri? = intent.data
val shareUrl = uri?.getQueryParameter("url") ?: ""
parseUri(shareUrl)
parseUri(shareUrl, uri?.fragment)
}
"install-sub" -> {
val uri: Uri? = intent.data
val shareUrl = uri?.getQueryParameter("url") ?: ""
parseUri(shareUrl)
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)

View File

@@ -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)
@@ -168,9 +171,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 +195,7 @@ class UserAssetActivity : BaseActivity() {
} else {
toast(getString(R.string.toast_failure) + " " + it.second.remarks)
}
dialog.dismiss()
}
}
}

View File

@@ -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)

View File

@@ -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)
@@ -403,7 +403,7 @@ object AngConfigManager {
updateConfigViaSubAll()
}
return count + countSub
return count to countSub
}
fun parseBatchSubscription(servers: String?): Int {
@@ -535,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)

View File

@@ -6,7 +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 io.reactivex.rxjava3.core.Observable
object AppManagerUtil {
fun loadNetworkAppList(ctx: Context): ArrayList<AppInfo> {

View File

@@ -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,14 +189,23 @@ object MmkvManager {
fun removeAllServer() {
mainStorage?.clearAll()
serverStorage?.clearAll()
profileStorage?.clearAll()
serverAffStorage?.clearAll()
}
fun removeInvalidServer() {
serverAffStorage?.allKeys()?.forEach { key ->
decodeServerAffiliationInfo(key)?.let { aff ->
fun removeInvalidServer(guid: String) {
if (guid.isNotEmpty()) {
decodeServerAffiliationInfo(guid)?.let { aff ->
if (aff.testDelayMillis < 0L) {
removeServer(key)
removeServer(guid)
}
}
} else {
serverAffStorage?.allKeys()?.forEach { key ->
decodeServerAffiliationInfo(key)?.let { aff ->
if (aff.testDelayMillis < 0L) {
removeServer(key)
}
}
}
}

View File

@@ -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) } ?: ""
}
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()
""
@@ -228,7 +219,7 @@ object Utils {
}
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 +227,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 +279,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 +288,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
@@ -330,7 +327,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))
}
@@ -383,10 +380,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 +403,21 @@ object Utils {
}
}
fun getLocale(context: Context): Locale =
when (settingsStorage?.decodeString(AppConfig.PREF_LANGUAGE) ?: "auto") {
"auto" -> getSysLocale()
"en" -> Locale("en")
"zh-rCN" -> Locale("zh", "CN")
"zh-rTW" -> Locale("zh", "TW")
fun getLocale(): Locale {
val lang = settingsStorage?.decodeString(AppConfig.PREF_LANGUAGE) ?: "auto"
return when (lang) {
"auto" -> getSysLocale()
"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 +444,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
}
}
}

View File

@@ -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,9 +54,11 @@ object V2rayConfigUtil {
}
val outbound = config.getProxyOutbound() ?: return Result(false, "")
val address = outbound.getServerAddress() ?: return Result(false, "")
if (!Utils.isIpAddress(address) && !Utils.isValidUrl(address)) {
Log.d(ANG_PACKAGE, "$address is an invalid ip or domain")
return Result(false, "")
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)
@@ -171,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"
}
}
}
@@ -183,66 +183,82 @@ object V2rayConfigUtil {
*/
private fun routing(v2rayConfig: V2rayConfig): Boolean {
try {
routingUserRule(
settingsStorage?.decodeString(AppConfig.PREF_V2RAY_ROUTING_AGENT)
?: "", AppConfig.TAG_PROXY, v2rayConfig
)
routingUserRule(
settingsStorage?.decodeString(AppConfig.PREF_V2RAY_ROUTING_DIRECT)
?: "", TAG_DIRECT, v2rayConfig
)
val routingMode = settingsStorage?.decodeString(AppConfig.PREF_ROUTING_MODE)
?: ERoutingMode.BYPASS_LAN_MAINLAND.value
routingUserRule(
settingsStorage?.decodeString(AppConfig.PREF_V2RAY_ROUTING_BLOCKED)
?: "", AppConfig.TAG_BLOCKED, v2rayConfig
?: "", TAG_BLOCKED, v2rayConfig
)
if (routingMode == ERoutingMode.GLOBAL_DIRECT.value) {
routingUserRule(
settingsStorage?.decodeString(AppConfig.PREF_V2RAY_ROUTING_DIRECT)
?: "", TAG_DIRECT, v2rayConfig
)
routingUserRule(
settingsStorage?.decodeString(AppConfig.PREF_V2RAY_ROUTING_AGENT)
?: "", TAG_PROXY, v2rayConfig
)
} else {
routingUserRule(
settingsStorage?.decodeString(AppConfig.PREF_V2RAY_ROUTING_AGENT)
?: "", TAG_PROXY, v2rayConfig
)
routingUserRule(
settingsStorage?.decodeString(AppConfig.PREF_V2RAY_ROUTING_DIRECT)
?: "", 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", TAG_DIRECT, v2rayConfig)
routingGeo("", "private", TAG_DIRECT, v2rayConfig)
}
ERoutingMode.BYPASS_MAINLAND.value -> {
routingGeo("", "cn", TAG_DIRECT, v2rayConfig)
routingGeo("domain", "geolocation-cn", TAG_DIRECT, v2rayConfig)
v2rayConfig.routing.rules.add(0, googleapisRoute)
}
ERoutingMode.BYPASS_LAN_MAINLAND.value -> {
routingGeo("ip", "private", TAG_DIRECT, v2rayConfig)
routingGeo("", "private", TAG_DIRECT, v2rayConfig)
routingGeo("", "cn", TAG_DIRECT, v2rayConfig)
routingGeo("domain", "geolocation-cn", TAG_DIRECT, v2rayConfig)
v2rayConfig.routing.rules.add(0, googleapisRoute)
}
ERoutingMode.GLOBAL_DIRECT.value -> {
val globalDirect = V2rayConfig.RoutingBean.RulesBean(
outboundTag = TAG_DIRECT,
port = "0-65535"
)
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) {
@@ -317,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:")) {
@@ -334,11 +350,11 @@ 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)
?: ""
)
val directDomain = userRule2Domian(
val directDomain = userRule2Domain(
settingsStorage?.decodeString(AppConfig.PREF_V2RAY_ROUTING_DIRECT)
?: ""
)
@@ -407,12 +423,12 @@ 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)
?: ""
)
@@ -432,7 +448,7 @@ object V2rayConfigUtil {
// domestic DNS
val domesticDns = Utils.getDomesticDnsServers()
val directDomain = userRule2Domian(
val directDomain = userRule2Domain(
settingsStorage?.decodeString(AppConfig.PREF_V2RAY_ROUTING_DIRECT)
?: ""
)
@@ -453,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(),
@@ -476,7 +492,7 @@ object V2rayConfigUtil {
}
//block dns
val blkDomain = userRule2Domian(
val blkDomain = userRule2Domain(
settingsStorage?.decodeString(AppConfig.PREF_V2RAY_ROUTING_BLOCKED)
?: ""
)
@@ -487,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,
@@ -497,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

View File

@@ -28,38 +28,47 @@ object TrojanFmt {
var flow = ""
var fingerprint = config.outboundBean?.streamSettings?.tlsSettings?.fingerprint
if (uri.rawQuery.isNullOrEmpty()) return null
val queryParam = uri.rawQuery.split("&")
.associate { it.split("=").let { (k, v) -> k to Utils.urlDecode(v) } }
val sni = config.outboundBean?.streamSettings?.populateTransportSettings(
queryParam["type"] ?: "tcp",
queryParam["headerType"],
queryParam["host"],
queryParam["path"],
queryParam["seed"],
queryParam["quicSecurity"],
queryParam["key"],
queryParam["mode"],
queryParam["serviceName"],
queryParam["authority"]
)
fingerprint = queryParam["fp"] ?: ""
allowInsecure = if ((queryParam["allowInsecure"] ?: "") == "1") true else allowInsecure
config.outboundBean?.streamSettings?.populateTlsSettings(
queryParam["security"] ?: V2rayConfig.TLS,
allowInsecure,
queryParam["sni"] ?: sni ?: "",
fingerprint,
queryParam["alpn"],
null,
null,
null
)
flow = queryParam["flow"] ?: ""
if (uri.rawQuery.isNullOrEmpty()) {
config.outboundBean?.streamSettings?.populateTlsSettings(
V2rayConfig.TLS,
allowInsecure,
"",
fingerprint,
null,
null,
null,
null
)
} else {
val queryParam = uri.rawQuery.split("&")
.associate { it.split("=").let { (k, v) -> k to Utils.urlDecode(v) } }
val sni = config.outboundBean?.streamSettings?.populateTransportSettings(
queryParam["type"] ?: "tcp",
queryParam["headerType"],
queryParam["host"],
queryParam["path"],
queryParam["seed"],
queryParam["quicSecurity"],
queryParam["key"],
queryParam["mode"],
queryParam["serviceName"],
queryParam["authority"]
)
fingerprint = queryParam["fp"] ?: ""
allowInsecure = if ((queryParam["allowInsecure"] ?: "") == "1") true else allowInsecure
config.outboundBean?.streamSettings?.populateTlsSettings(
queryParam["security"] ?: V2rayConfig.TLS,
allowInsecure,
queryParam["sni"] ?: sni ?: "",
fingerprint,
queryParam["alpn"],
null,
null,
null
)
flow = queryParam["flow"] ?: ""
}
config.outboundBean?.settings?.servers?.get(0)?.let { server ->
server.address = uri.idnHost
server.port = uri.port

View File

@@ -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, "") ?: ""
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, "")?:""
private 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) {
val config = MmkvManager.decodeServerConfig(guid) ?: continue
if (subscriptionId.isNotEmpty() && subscriptionId != config.subscriptionId) {
var profile = MmkvManager.decodeProfileConfig(guid)
if (profile == null) {
val config = MmkvManager.decodeServerConfig(guid) ?: continue
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()) {
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) {
fun subscriptionIdChanged(id: String) {
if (subscriptionId != id) {
subscriptionId = id
MmkvManager.settingsStorage.encode(AppConfig.CACHE_SUBSCRIPTION_ID, subscriptionId)
reloadServerList()
}
}
fun getSubscriptions(context: Context): Pair<MutableList<String>?, MutableList<String>?> {
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
if (subscriptionId.isNotEmpty()
&& !subscriptions.map { it.first }.contains(subscriptionId)
) {
subscriptionIdChanged("")
}
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)
reloadServerList()
dialogInterface?.dismiss()
} catch (e: Exception) {
e.printStackTrace()
}
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()) {
MmkvManager.removeAllServer()
} else {
val serversCopy = serversCache.toList()
for (item in serversCopy) {
MmkvManager.removeServer(item.guid)
}
}
}
fun removeInvalidServer() {
if (subscriptionId.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,12 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
}
}
fun filterConfig(keyword: String) {
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)) {
@@ -357,4 +390,4 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
}
}
}
}
}

View File

@@ -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"

View File

@@ -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_description_24dp"
android:title="@string/menu_item_search"
app:actionViewClass="androidx.appcompat.widget.SearchView"
app:showAsAction="always" />
<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"

View File

@@ -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>

View 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>

View File

@@ -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>

View File

@@ -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">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</string>
<string name="server_lab_path_grpc">Служба gRPC</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">Разрешать небезопасные</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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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">

View File

@@ -1,3 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="TabLayoutTextStyle" parent="TextAppearance.Design.Tab">
<item name="textAllCaps">false</item>
</style>
</resources>

View File

@@ -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>

View File

@@ -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" />

View File

@@ -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
}

View 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.8.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]

View File

@@ -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

View File

@@ -11,6 +11,7 @@ dependencyResolutionManagement {
google()
mavenCentral()
jcenter()
maven { url = uri("https://jitpack.io") }
}
}
rootProject.name = "V2rayNG"