Compare commits
68 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
99307ab8f0 | ||
|
|
1ec23a7b39 | ||
|
|
685f9e220c | ||
|
|
e77b7eb52e | ||
|
|
29014704e0 | ||
|
|
b3570d9c0b | ||
|
|
6e427cee82 | ||
|
|
0a1b6d00d9 | ||
|
|
00ffe66f36 | ||
|
|
63661fbdaa | ||
|
|
4d4a4543c5 | ||
|
|
2349805968 | ||
|
|
bdbd0b154e | ||
|
|
653137ec58 | ||
|
|
39f65850be | ||
|
|
2d8db97417 | ||
|
|
17a6f80798 | ||
|
|
381fe859ff | ||
|
|
e2bdf17b82 | ||
|
|
d20afc6801 | ||
|
|
54046a27e6 | ||
|
|
36c000d18a | ||
|
|
2f3c2cf4d5 | ||
|
|
892358d5d8 | ||
|
|
6dced903cd | ||
|
|
f8a98a426e | ||
|
|
011506e99f | ||
|
|
966151d3fe | ||
|
|
247f4db77e | ||
|
|
9a661bc401 | ||
|
|
f5f1b3816c | ||
|
|
3971c9badc | ||
|
|
390fbf046b | ||
|
|
ec7ba59528 | ||
|
|
838941c6a4 | ||
|
|
a0ec764b64 | ||
|
|
26f1f7099b | ||
|
|
613e9ac8bf | ||
|
|
ea979f02b5 | ||
|
|
383f55f809 | ||
|
|
ce61f177dc | ||
|
|
6a64157537 | ||
|
|
de83302c8a | ||
|
|
68553d3807 | ||
|
|
4cfe3394b1 | ||
|
|
ae9aa75ba0 | ||
|
|
3d7ed12d4b | ||
|
|
f70be5bce9 | ||
|
|
405667697e | ||
|
|
3aeda7de81 | ||
|
|
8a775d662a | ||
|
|
ec0ccbca76 | ||
|
|
6cdcbb0096 | ||
|
|
79e3881704 | ||
|
|
4cf2d429f0 | ||
|
|
fa51952bfa | ||
|
|
b2c4c0a67e | ||
|
|
cf2c58637b | ||
|
|
a2c262441e | ||
|
|
237be79680 | ||
|
|
441a64b8c6 | ||
|
|
2128488658 | ||
|
|
d9dc8317f5 | ||
|
|
6eda2180e4 | ||
|
|
57a2f0bebf | ||
|
|
1fd3ccaa0c | ||
|
|
528597d0c7 | ||
|
|
37411e3e6b |
@@ -1,8 +1,7 @@
|
||||
apply plugin: 'com.android.application'
|
||||
apply plugin: 'kotlin-android'
|
||||
|
||||
Properties props = new Properties()
|
||||
props.load(new FileInputStream(new File('local.properties')))
|
||||
plugins {
|
||||
id 'com.android.application'
|
||||
id 'org.jetbrains.kotlin.android'
|
||||
}
|
||||
|
||||
android {
|
||||
compileSdkVersion Integer.parseInt("$compileSdkVer")
|
||||
@@ -18,25 +17,8 @@ android {
|
||||
minSdkVersion 21
|
||||
targetSdkVersion Integer.parseInt("$targetSdkVer")
|
||||
multiDexEnabled true
|
||||
versionCode 498
|
||||
versionName "1.7.35"
|
||||
}
|
||||
|
||||
if (props["sign"]) {
|
||||
signingConfigs {
|
||||
release {
|
||||
storeFile file("../key.jks")
|
||||
keyAlias 'ang'
|
||||
keyPassword '123456'
|
||||
storePassword '123456'
|
||||
}
|
||||
debug {
|
||||
storeFile file("../key.jks")
|
||||
keyAlias 'ang'
|
||||
keyPassword '123456'
|
||||
storePassword '123456'
|
||||
}
|
||||
}
|
||||
versionCode 518
|
||||
versionName "1.8.6"
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
@@ -44,23 +26,31 @@ android {
|
||||
minifyEnabled false
|
||||
zipAlignEnabled false
|
||||
shrinkResources false
|
||||
if (props["sign"]) {
|
||||
signingConfig signingConfigs.release
|
||||
}
|
||||
ndk.abiFilters 'x86', 'x86_64', 'armeabi-v7a', 'arm64-v8a'
|
||||
// proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
debug {
|
||||
minifyEnabled false
|
||||
zipAlignEnabled false
|
||||
shrinkResources false
|
||||
if (props["sign"]) {
|
||||
signingConfig signingConfigs.release
|
||||
}
|
||||
ndk.abiFilters 'x86', 'x86_64', 'armeabi-v7a', 'arm64-v8a'
|
||||
}
|
||||
}
|
||||
|
||||
// flavorDimensions "versions"
|
||||
//
|
||||
// productFlavors {
|
||||
// dev {
|
||||
// applicationIdSuffix = ".dev"
|
||||
// versionNameSuffix = "-dev"
|
||||
// }
|
||||
// pre_release {
|
||||
// applicationIdSuffix = ".pre"
|
||||
// versionNameSuffix = "-pre-release"
|
||||
// }
|
||||
// prod {
|
||||
// }
|
||||
// }
|
||||
|
||||
sourceSets {
|
||||
main {
|
||||
jniLibs.srcDirs = ['libs']
|
||||
@@ -97,7 +87,10 @@ android {
|
||||
|
||||
buildFeatures {
|
||||
viewBinding true
|
||||
buildConfig true
|
||||
}
|
||||
namespace 'com.v2ray.ang'
|
||||
testNamespace 'com.v2ray.angTest'
|
||||
}
|
||||
|
||||
dependencies {
|
||||
@@ -107,45 +100,36 @@ dependencies {
|
||||
// Androidx
|
||||
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
|
||||
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
|
||||
implementation 'androidx.appcompat:appcompat:1.4.2'
|
||||
implementation 'com.google.android.material:material:1.6.1'
|
||||
implementation 'androidx.appcompat:appcompat:1.6.1'
|
||||
implementation 'com.google.android.material:material:1.9.0'
|
||||
implementation 'androidx.cardview:cardview:1.0.0'
|
||||
implementation 'androidx.preference:preference-ktx:1.2.0'
|
||||
implementation 'androidx.recyclerview:recyclerview:1.2.1'
|
||||
implementation 'androidx.fragment:fragment-ktx:1.5.2'
|
||||
implementation 'androidx.recyclerview:recyclerview:1.3.0'
|
||||
implementation 'androidx.fragment:fragment-ktx:1.5.7'
|
||||
implementation 'androidx.multidex:multidex:2.0.1'
|
||||
implementation 'androidx.viewpager2:viewpager2:1.1.0-beta01'
|
||||
|
||||
// Androidx ktx
|
||||
implementation 'androidx.activity:activity-ktx:1.5.1'
|
||||
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.1'
|
||||
implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.5.1'
|
||||
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.5.1'
|
||||
implementation 'androidx.activity:activity-ktx:1.7.1'
|
||||
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.1'
|
||||
implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.6.1'
|
||||
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.6.1'
|
||||
|
||||
//kotlin
|
||||
implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlinVersion"
|
||||
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.2"
|
||||
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.2"
|
||||
implementation "org.jetbrains.kotlin:kotlin-reflect:1.8.0"
|
||||
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4"
|
||||
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4"
|
||||
|
||||
implementation 'com.tencent:mmkv-static:1.2.12'
|
||||
implementation 'com.google.code.gson:gson:2.8.9'
|
||||
implementation 'io.reactivex:rxjava:1.3.4'
|
||||
implementation 'com.tencent:mmkv-static:1.2.15'
|
||||
implementation 'com.google.code.gson:gson:2.10.1'
|
||||
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.dm7.barcodescanner:core:1.9.8'
|
||||
implementation 'me.dm7.barcodescanner:zxing:1.9.8'
|
||||
implementation 'com.github.jorgecastilloprz:fabprogresscircle:1.01@aar'
|
||||
implementation 'me.drakeet.support:toastcompat:1.1.0'
|
||||
implementation 'com.blacksquircle.ui:editorkit:2.1.1'
|
||||
implementation 'com.blacksquircle.ui:language-base:2.1.1'
|
||||
implementation 'com.blacksquircle.ui:language-json:2.1.1'
|
||||
}
|
||||
|
||||
//buildscript {
|
||||
// repositories {
|
||||
// google()
|
||||
// mavenCentral()
|
||||
// maven { url 'https://maven.google.com' }
|
||||
// maven { url 'https://jitpack.io' }
|
||||
// }
|
||||
//}
|
||||
implementation 'io.github.g00fy2.quickie:quickie-bundled:1.6.0'
|
||||
implementation 'com.google.zxing:core:3.5.1'
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
package com.v2ray.ang;
|
||||
|
||||
import android.app.Application;
|
||||
import android.test.ApplicationTestCase;
|
||||
|
||||
/**
|
||||
* <a href="http://d.android.com/tools/testing/testing_android.html">Testing Fundamentals</a>
|
||||
*/
|
||||
public class ApplicationTest extends ApplicationTestCase<Application> {
|
||||
public ApplicationTest() {
|
||||
super(Application.class);
|
||||
}
|
||||
}
|
||||
4
V2rayNG/app/src/dev/res/values/strings.xml
Normal file
4
V2rayNG/app/src/dev/res/values/strings.xml
Normal file
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<item name="app_name" type="string">v2rayNG (DEV)</item>
|
||||
</resources>
|
||||
@@ -1,7 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
package="com.v2ray.ang">
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<supports-screens
|
||||
android:anyDensity="true"
|
||||
@@ -12,6 +11,8 @@
|
||||
|
||||
<uses-feature android:name="android.hardware.camera" android:required="false"/>
|
||||
<uses-feature android:name="android.hardware.camera.autofocus" android:required="false"/>
|
||||
<uses-feature android:name="android.software.leanback" android:required="false" />
|
||||
<uses-feature android:name="android.hardware.touchscreen" android:required="false" />
|
||||
|
||||
<!-- https://developer.android.com/about/versions/11/privacy/package-visibility -->
|
||||
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"
|
||||
@@ -24,11 +25,15 @@
|
||||
<uses-permission android:name="android.permission.CAMERA" />
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||
<!-- <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> -->
|
||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
|
||||
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
|
||||
|
||||
|
||||
<application
|
||||
android:name=".AngApplication"
|
||||
android:allowBackup="true"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:banner="@mipmap/ic_banner"
|
||||
android:label="@string/app_name"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/AppThemeDayNight"
|
||||
@@ -43,6 +48,7 @@
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
<category android:name="android.intent.category.LEANBACK_LAUNCHER"/>
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="android.service.quicksettings.action.QS_TILE_PREFERENCES" />
|
||||
@@ -107,8 +113,9 @@
|
||||
<action android:name="android.intent.action.VIEW"/>
|
||||
<category android:name="android.intent.category.BROWSABLE"/>
|
||||
<category android:name="android.intent.category.DEFAULT"/>
|
||||
<data android:scheme="v2rayng"
|
||||
android:host="install-config" />
|
||||
<data android:scheme="v2rayng"/>
|
||||
<data android:host="install-config"/>
|
||||
<data android:host="install-sub"/>
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ package com.v2ray.ang
|
||||
* App Config Const
|
||||
*/
|
||||
object AppConfig {
|
||||
const val ANG_PACKAGE = "com.v2ray.ang"
|
||||
const val ANG_PACKAGE = BuildConfig.APPLICATION_ID
|
||||
const val DIR_ASSETS = "assets"
|
||||
|
||||
// legacy
|
||||
@@ -39,6 +39,7 @@ object AppConfig {
|
||||
const val PREF_PER_APP_PROXY_SET = "pref_per_app_proxy_set"
|
||||
const val PREF_BYPASS_APPS = "pref_bypass_apps"
|
||||
const val PREF_CONFIRM_REMOVE = "pref_confirm_remove"
|
||||
const val PREF_START_SCAN_IMMEDIATE = "pref_start_scan_immediate"
|
||||
|
||||
const val HTTP_PROTOCOL: String = "http://"
|
||||
const val HTTPS_PROTOCOL: String = "https://"
|
||||
|
||||
@@ -29,7 +29,7 @@ data class V2rayConfig(
|
||||
const val DEFAULT_NETWORK = "tcp"
|
||||
|
||||
const val TLS = "tls"
|
||||
const val XTLS = "xtls"
|
||||
const val REALITY = "reality"
|
||||
const val HTTP = "http"
|
||||
}
|
||||
|
||||
@@ -130,7 +130,7 @@ data class V2rayConfig(
|
||||
var httpSettings: HttpSettingsBean? = null,
|
||||
var tlsSettings: TlsSettingsBean? = null,
|
||||
var quicSettings: QuicSettingBean? = null,
|
||||
var xtlsSettings: TlsSettingsBean? = null,
|
||||
var realitySettings: TlsSettingsBean? = null,
|
||||
var grpcSettings: GrpcSettingsBean? = null,
|
||||
val dsSettings: Any? = null,
|
||||
val sockopt: Any? = null
|
||||
@@ -189,7 +189,12 @@ data class V2rayConfig(
|
||||
val fingerprint: String? = null,
|
||||
val certificates: List<Any>? = null,
|
||||
val disableSystemRoot: Boolean? = null,
|
||||
val enableSessionResumption: Boolean? = null)
|
||||
val enableSessionResumption: Boolean? = null,
|
||||
// REALITY settings
|
||||
val show: Boolean = false,
|
||||
var publicKey: String? = null,
|
||||
var shortId: String? = null,
|
||||
var spiderX: String? = null)
|
||||
|
||||
data class QuicSettingBean(var security: String = "none",
|
||||
var key: String = "",
|
||||
@@ -265,20 +270,24 @@ data class V2rayConfig(
|
||||
return sni
|
||||
}
|
||||
|
||||
fun populateTlsSettings(streamSecurity: String, allowInsecure: Boolean, sni: String, fingerprint: String?, alpns: String?) {
|
||||
fun populateTlsSettings(streamSecurity: String, allowInsecure: Boolean, sni: String, fingerprint: String?, alpns: String?,
|
||||
publicKey: String?, shortId: String?, spiderX: String?) {
|
||||
security = streamSecurity
|
||||
val tlsSetting = TlsSettingsBean(
|
||||
allowInsecure = allowInsecure,
|
||||
serverName = sni,
|
||||
fingerprint = fingerprint,
|
||||
alpn = if (alpns.isNullOrEmpty()) null else alpns.split(",").map { it.trim() }.filter { it.isNotEmpty() }
|
||||
allowInsecure = allowInsecure,
|
||||
serverName = sni,
|
||||
fingerprint = fingerprint,
|
||||
alpn = if (alpns.isNullOrEmpty()) null else alpns.split(",").map { it.trim() }.filter { it.isNotEmpty() },
|
||||
publicKey = publicKey,
|
||||
shortId = shortId,
|
||||
spiderX = spiderX
|
||||
)
|
||||
if (security == TLS) {
|
||||
tlsSettings = tlsSetting
|
||||
xtlsSettings = null
|
||||
} else if (security == XTLS) {
|
||||
realitySettings = null
|
||||
} else if (security == REALITY) {
|
||||
tlsSettings = null
|
||||
xtlsSettings = tlsSetting
|
||||
realitySettings = tlsSetting
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -438,7 +447,7 @@ data class V2rayConfig(
|
||||
var poolSize: Int = 10000) // roughly 10 times smaller than total ip pool
|
||||
|
||||
fun getProxyOutbound(): OutboundBean? {
|
||||
outbounds.forEach { outbound ->
|
||||
outbounds?.forEach { outbound ->
|
||||
EConfigType.values().forEach {
|
||||
if (outbound.protocol.equals(it.name, true)) {
|
||||
return outbound
|
||||
|
||||
@@ -13,4 +13,5 @@ data class VmessQRCode(var v: String = "",
|
||||
var path: String = "",
|
||||
var tls: String = "",
|
||||
var sni: String = "",
|
||||
var alpn: String = "")
|
||||
var alpn: String = "",
|
||||
var fp: String = "")
|
||||
@@ -4,7 +4,6 @@ import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.text.TextUtils
|
||||
import com.google.zxing.WriterException
|
||||
import com.tencent.mmkv.MMKV
|
||||
import com.v2ray.ang.AppConfig
|
||||
import com.v2ray.ang.service.V2RayServiceManager
|
||||
@@ -34,7 +33,7 @@ class TaskerReceiver : BroadcastReceiver() {
|
||||
} else {
|
||||
Utils.stopVService(context)
|
||||
}
|
||||
} catch (e: WriterException) {
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ import android.text.TextUtils
|
||||
import android.view.KeyEvent
|
||||
import com.v2ray.ang.AppConfig
|
||||
import android.content.res.ColorStateList
|
||||
import android.os.Build
|
||||
import com.google.android.material.navigation.NavigationView
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.view.GravityCompat
|
||||
@@ -107,6 +108,15 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
|
||||
setupViewModel()
|
||||
copyAssets()
|
||||
migrateLegacy()
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
RxPermissions(this)
|
||||
.request(Manifest.permission.POST_NOTIFICATIONS)
|
||||
.subscribe {
|
||||
if (!it)
|
||||
toast(R.string.toast_permission_denied)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupViewModel() {
|
||||
@@ -297,7 +307,14 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
|
||||
.show()
|
||||
true
|
||||
}
|
||||
|
||||
R.id.del_duplicate_config-> {
|
||||
AlertDialog.Builder(this).setMessage(R.string.del_config_comfirm)
|
||||
.setPositiveButton(android.R.string.ok) { _, _ ->
|
||||
mainViewModel.removeDuplicateServer()
|
||||
}
|
||||
.show()
|
||||
true
|
||||
}
|
||||
R.id.del_invalid_config -> {
|
||||
AlertDialog.Builder(this).setMessage(R.string.del_config_comfirm)
|
||||
.setPositiveButton(android.R.string.ok) { _, _ ->
|
||||
@@ -541,8 +558,13 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
|
||||
* read content from uri
|
||||
*/
|
||||
private fun readContentFromUri(uri: Uri) {
|
||||
val permission = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
Manifest.permission.READ_MEDIA_IMAGES
|
||||
} else {
|
||||
Manifest.permission.READ_EXTERNAL_STORAGE
|
||||
}
|
||||
RxPermissions(this)
|
||||
.request(Manifest.permission.READ_EXTERNAL_STORAGE)
|
||||
.request(permission)
|
||||
.subscribe {
|
||||
if (it) {
|
||||
try {
|
||||
@@ -591,13 +613,14 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
|
||||
// }
|
||||
|
||||
override fun onKeyDown(keyCode: Int, event: KeyEvent): Boolean {
|
||||
if (keyCode == KeyEvent.KEYCODE_BACK) {
|
||||
if (keyCode == KeyEvent.KEYCODE_BACK || keyCode == KeyEvent.KEYCODE_BUTTON_B) {
|
||||
moveTaskToBack(false)
|
||||
return true
|
||||
}
|
||||
return super.onKeyDown(keyCode, event)
|
||||
}
|
||||
|
||||
|
||||
fun showCircle() {
|
||||
binding.fabProgressCircle.show()
|
||||
}
|
||||
@@ -620,11 +643,13 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
|
||||
}
|
||||
}
|
||||
|
||||
@Deprecated("Deprecated in Java")
|
||||
override fun onBackPressed() {
|
||||
if (binding.drawerLayout.isDrawerOpen(GravityCompat.START)) {
|
||||
binding.drawerLayout.closeDrawer(GravityCompat.START)
|
||||
} else {
|
||||
super.onBackPressed()
|
||||
//super.onBackPressed()
|
||||
onBackPressedDispatcher.onBackPressed()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -228,7 +228,7 @@ class MainRecyclerAdapter(val activity: MainActivity) : RecyclerView.Adapter<Mai
|
||||
BaseViewHolder(itemMainBinding.root), ItemTouchHelperViewHolder
|
||||
|
||||
class FooterViewHolder(val itemFooterBinding: ItemRecyclerFooterBinding) :
|
||||
BaseViewHolder(itemFooterBinding.root), ItemTouchHelperViewHolder
|
||||
BaseViewHolder(itemFooterBinding.root)
|
||||
|
||||
override fun onItemDismiss(position: Int) {
|
||||
val guid = mActivity.mainViewModel.serversCache.getOrNull(position)?.guid ?: return
|
||||
|
||||
@@ -3,57 +3,62 @@ package com.v2ray.ang.ui
|
||||
import android.Manifest
|
||||
import android.app.Activity
|
||||
import android.os.Bundle
|
||||
import com.google.zxing.Result
|
||||
import me.dm7.barcodescanner.zxing.ZXingScannerView
|
||||
import android.content.Intent
|
||||
import android.graphics.BitmapFactory
|
||||
import android.os.Build
|
||||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import com.google.zxing.BarcodeFormat
|
||||
import com.tbruyelle.rxpermissions.RxPermissions
|
||||
import com.tencent.mmkv.MMKV
|
||||
import com.v2ray.ang.AppConfig
|
||||
import com.v2ray.ang.R
|
||||
import com.v2ray.ang.extension.toast
|
||||
import com.v2ray.ang.util.MmkvManager
|
||||
import com.v2ray.ang.util.QRCodeDecoder
|
||||
import io.github.g00fy2.quickie.QRResult
|
||||
import io.github.g00fy2.quickie.ScanCustomCode
|
||||
import io.github.g00fy2.quickie.config.ScannerConfig
|
||||
|
||||
class ScannerActivity : BaseActivity(), ZXingScannerView.ResultHandler {
|
||||
class ScannerActivity : BaseActivity(){
|
||||
|
||||
private var mScannerView: ZXingScannerView? = null
|
||||
private val scanQrCode = registerForActivityResult(ScanCustomCode(), ::handleResult)
|
||||
private val settingsStorage by lazy { MMKV.mmkvWithID(MmkvManager.ID_SETTING, MMKV.MULTI_PROCESS_MODE) }
|
||||
|
||||
public override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
mScannerView = ZXingScannerView(this) // Programmatically initialize the scanner view
|
||||
|
||||
mScannerView?.setAutoFocus(true)
|
||||
val formats = ArrayList<BarcodeFormat>()
|
||||
formats.add(BarcodeFormat.QR_CODE)
|
||||
mScannerView?.setFormats(formats)
|
||||
|
||||
setContentView(mScannerView) // Set the scanner view as the content view
|
||||
if (settingsStorage?.decodeBool(AppConfig.PREF_START_SCAN_IMMEDIATE) == true) {
|
||||
launchScan()
|
||||
}
|
||||
|
||||
supportActionBar?.setDisplayHomeAsUpEnabled(true)
|
||||
}
|
||||
|
||||
public override fun onResume() {
|
||||
super.onResume()
|
||||
mScannerView!!.setResultHandler(this) // Register ourselves as a handler for scan results.
|
||||
mScannerView!!.startCamera() // Start camera on resume
|
||||
}
|
||||
|
||||
public override fun onPause() {
|
||||
super.onPause()
|
||||
mScannerView!!.stopCamera() // Stop camera on pause
|
||||
}
|
||||
|
||||
override fun handleResult(rawResult: Result) {
|
||||
// Do something with the result here
|
||||
// Log.v(FragmentActivity.TAG, rawResult.text) // Prints scan results
|
||||
// Log.v(FragmentActivity.TAG, rawResult.barcodeFormat.toString()) // Prints the scan format (qrcode, pdf417 etc.)
|
||||
private fun launchScan(){
|
||||
scanQrCode.launch(
|
||||
ScannerConfig.build {
|
||||
setHapticSuccessFeedback(true) // enable (default) or disable haptic feedback when a barcode was detected
|
||||
setShowTorchToggle(true) // show or hide (default) torch/flashlight toggle button
|
||||
setShowCloseButton(true) // show or hide (default) close button
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
finished(rawResult.text)
|
||||
|
||||
// If you would like to resume scanning, call this method below:
|
||||
// mScannerView!!.resumeCameraPreview(this)
|
||||
private fun handleResult(result: QRResult) {
|
||||
if (result is QRResult.QRSuccess ) {
|
||||
finished(result.content.rawValue)
|
||||
} else {
|
||||
finish()
|
||||
}
|
||||
}
|
||||
|
||||
private fun finished(text: String) {
|
||||
@@ -69,19 +74,28 @@ class ScannerActivity : BaseActivity(), ZXingScannerView.ResultHandler {
|
||||
}
|
||||
|
||||
override fun onOptionsItemSelected(item: MenuItem) = when (item.itemId) {
|
||||
R.id.scan_code -> {
|
||||
launchScan()
|
||||
true
|
||||
}
|
||||
R.id.select_photo -> {
|
||||
val permission = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
Manifest.permission.READ_MEDIA_IMAGES
|
||||
} else {
|
||||
Manifest.permission.READ_EXTERNAL_STORAGE
|
||||
}
|
||||
RxPermissions(this)
|
||||
.request(Manifest.permission.READ_EXTERNAL_STORAGE)
|
||||
.subscribe {
|
||||
if (it) {
|
||||
try {
|
||||
showFileChooser()
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
} else
|
||||
toast(R.string.toast_permission_denied)
|
||||
}
|
||||
.request(permission)
|
||||
.subscribe {
|
||||
if (it) {
|
||||
try {
|
||||
showFileChooser()
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
} else
|
||||
toast(R.string.toast_permission_denied)
|
||||
}
|
||||
true
|
||||
}
|
||||
else -> super.onOptionsItemSelected(item)
|
||||
|
||||
@@ -15,6 +15,7 @@ import com.v2ray.ang.dto.EConfigType
|
||||
import com.v2ray.ang.dto.ServerConfig
|
||||
import com.v2ray.ang.dto.V2rayConfig
|
||||
import com.v2ray.ang.dto.V2rayConfig.Companion.DEFAULT_PORT
|
||||
import com.v2ray.ang.dto.V2rayConfig.Companion.TLS
|
||||
import com.v2ray.ang.extension.toast
|
||||
import com.v2ray.ang.util.MmkvManager
|
||||
import com.v2ray.ang.util.MmkvManager.ID_MAIN
|
||||
@@ -84,14 +85,24 @@ class ServerActivity : BaseActivity() {
|
||||
private val sp_security: Spinner? by lazy { findViewById(R.id.sp_security) }
|
||||
private val sp_stream_security: Spinner? by lazy { findViewById(R.id.sp_stream_security) }
|
||||
private val sp_allow_insecure: Spinner? by lazy { findViewById(R.id.sp_allow_insecure) }
|
||||
private val container_allow_insecure: LinearLayout? by lazy { findViewById(R.id.l5) }
|
||||
private val et_sni: EditText? by lazy { findViewById(R.id.et_sni) }
|
||||
private val container_sni: LinearLayout? by lazy { findViewById(R.id.l2) }
|
||||
private val sp_stream_fingerprint: Spinner? by lazy { findViewById(R.id.sp_stream_fingerprint) } //uTLS
|
||||
private val container_fingerprint: LinearLayout? by lazy { findViewById(R.id.l3) }
|
||||
private val sp_network: Spinner? by lazy { findViewById(R.id.sp_network) }
|
||||
private val sp_header_type: Spinner? by lazy { findViewById(R.id.sp_header_type) }
|
||||
private val sp_header_type_title: TextView? by lazy { findViewById(R.id.sp_header_type_title) }
|
||||
private val et_request_host: EditText? by lazy { findViewById(R.id.et_request_host) }
|
||||
private val et_path: EditText? by lazy { findViewById(R.id.et_path) }
|
||||
private val sp_stream_alpn: Spinner? by lazy { findViewById(R.id.sp_stream_alpn) } //uTLS
|
||||
private val container_alpn: LinearLayout? by lazy { findViewById(R.id.l4) }
|
||||
private val et_public_key: EditText? by lazy { findViewById(R.id.et_public_key) }
|
||||
private val container_public_key: LinearLayout? by lazy { findViewById(R.id.l6) }
|
||||
private val et_short_id: EditText? by lazy { findViewById(R.id.et_short_id) }
|
||||
private val container_short_id: LinearLayout? by lazy { findViewById(R.id.l7) }
|
||||
private val et_spider_x: EditText? by lazy { findViewById(R.id.et_spider_x) }
|
||||
private val container_spider_x: LinearLayout? by lazy { findViewById(R.id.l8) }
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
@@ -105,6 +116,7 @@ class ServerActivity : BaseActivity() {
|
||||
EConfigType.SOCKS -> setContentView(R.layout.activity_server_socks)
|
||||
EConfigType.VLESS -> setContentView(R.layout.activity_server_vless)
|
||||
EConfigType.TROJAN -> setContentView(R.layout.activity_server_trojan)
|
||||
else -> setContentView(R.layout.activity_server_vmess)
|
||||
}
|
||||
sp_network?.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
|
||||
override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
|
||||
@@ -126,6 +138,38 @@ class ServerActivity : BaseActivity() {
|
||||
// do nothing
|
||||
}
|
||||
}
|
||||
sp_stream_security?.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
|
||||
override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
|
||||
if (streamSecuritys[position].isBlank()) {
|
||||
container_sni?.visibility = View.GONE
|
||||
container_fingerprint?.visibility = View.GONE
|
||||
container_alpn?.visibility = View.GONE
|
||||
container_allow_insecure?.visibility = View.GONE
|
||||
container_public_key?.visibility = View.GONE
|
||||
container_short_id?.visibility = View.GONE
|
||||
container_spider_x?.visibility = View.GONE
|
||||
} else {
|
||||
container_sni?.visibility = View.VISIBLE
|
||||
container_fingerprint?.visibility = View.VISIBLE
|
||||
container_alpn?.visibility = View.VISIBLE
|
||||
if (streamSecuritys[position] == TLS) {
|
||||
container_allow_insecure?.visibility = View.VISIBLE
|
||||
container_public_key?.visibility = View.GONE
|
||||
container_short_id?.visibility = View.GONE
|
||||
container_spider_x?.visibility = View.GONE
|
||||
} else {
|
||||
container_allow_insecure?.visibility = View.GONE
|
||||
container_alpn?.visibility = View.GONE
|
||||
container_public_key?.visibility = View.VISIBLE
|
||||
container_short_id?.visibility = View.VISIBLE
|
||||
container_spider_x?.visibility = View.VISIBLE
|
||||
}
|
||||
}
|
||||
}
|
||||
override fun onNothingSelected(p0: AdapterView<*>?) {
|
||||
// do nothing
|
||||
}
|
||||
}
|
||||
if (config != null) {
|
||||
bindingServer(config)
|
||||
} else {
|
||||
@@ -169,13 +213,11 @@ class ServerActivity : BaseActivity() {
|
||||
val streamSecurity = Utils.arrayFind(streamSecuritys, streamSetting.security)
|
||||
if (streamSecurity >= 0) {
|
||||
sp_stream_security?.setSelection(streamSecurity)
|
||||
(streamSetting.tlsSettings?: streamSetting.xtlsSettings)?.let { tlsSetting ->
|
||||
val allowinsecure = Utils.arrayFind(allowinsecures, tlsSetting.allowInsecure.toString())
|
||||
if (allowinsecure >= 0) {
|
||||
sp_allow_insecure?.setSelection(allowinsecure)
|
||||
}
|
||||
(streamSetting.tlsSettings?: streamSetting.realitySettings)?.let { tlsSetting ->
|
||||
container_sni?.visibility = View.VISIBLE
|
||||
container_fingerprint?.visibility = View.VISIBLE
|
||||
container_alpn?.visibility = View.VISIBLE
|
||||
et_sni?.text = Utils.getEditable(tlsSetting.serverName)
|
||||
|
||||
tlsSetting.fingerprint?.let {
|
||||
val utlsIndex = Utils.arrayFind(uTlsItems, tlsSetting.fingerprint)
|
||||
sp_stream_fingerprint?.setSelection(utlsIndex)
|
||||
@@ -184,7 +226,33 @@ class ServerActivity : BaseActivity() {
|
||||
val alpnIndex = Utils.arrayFind(alpns, Utils.removeWhiteSpace(tlsSetting.alpn.joinToString())!!)
|
||||
sp_stream_alpn?.setSelection(alpnIndex)
|
||||
}
|
||||
|
||||
if (streamSetting.tlsSettings != null) {
|
||||
container_allow_insecure?.visibility = View.VISIBLE
|
||||
val allowinsecure = Utils.arrayFind(allowinsecures, tlsSetting.allowInsecure.toString())
|
||||
if (allowinsecure >= 0) {
|
||||
sp_allow_insecure?.setSelection(allowinsecure)
|
||||
}
|
||||
container_public_key?.visibility = View.GONE
|
||||
container_short_id?.visibility = View.GONE
|
||||
container_spider_x?.visibility = View.GONE
|
||||
} else { // reality settings
|
||||
container_public_key?.visibility = View.VISIBLE
|
||||
et_public_key?.text = Utils.getEditable(tlsSetting.publicKey.orEmpty())
|
||||
container_short_id?.visibility = View.VISIBLE
|
||||
et_short_id?.text = Utils.getEditable(tlsSetting.shortId.orEmpty())
|
||||
container_spider_x?.visibility = View.VISIBLE
|
||||
et_spider_x?.text = Utils.getEditable(tlsSetting.spiderX.orEmpty())
|
||||
container_allow_insecure?.visibility = View.GONE
|
||||
}
|
||||
}
|
||||
if (streamSetting.tlsSettings == null && streamSetting.realitySettings == null) {
|
||||
container_sni?.visibility = View.GONE
|
||||
container_fingerprint?.visibility = View.GONE
|
||||
container_alpn?.visibility = View.GONE
|
||||
container_allow_insecure?.visibility = View.GONE
|
||||
container_public_key?.visibility = View.GONE
|
||||
container_short_id?.visibility = View.GONE
|
||||
container_spider_x?.visibility = View.GONE
|
||||
}
|
||||
}
|
||||
val network = Utils.arrayFind(networks, streamSetting.network)
|
||||
@@ -304,12 +372,6 @@ class ServerActivity : BaseActivity() {
|
||||
}
|
||||
} else if (config.configType == EConfigType.TROJAN) {
|
||||
server.password = et_id.text.toString().trim()
|
||||
server.flow =
|
||||
if (streamSecuritys[sp_stream_security?.selectedItemPosition ?: 0] == V2rayConfig.XTLS) {
|
||||
flows[sp_flow?.selectedItemPosition ?: 0]
|
||||
} else {
|
||||
""
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -321,8 +383,11 @@ class ServerActivity : BaseActivity() {
|
||||
val sniField = et_sni?.text?.toString()?.trim() ?: return
|
||||
val allowInsecureField = sp_allow_insecure?.selectedItemPosition ?: return
|
||||
val streamSecurity = sp_stream_security?.selectedItemPosition ?: return
|
||||
var utlsIndex = sp_stream_fingerprint?.selectedItemPosition ?: return
|
||||
var alpnIndex = sp_stream_alpn?.selectedItemPosition ?: return
|
||||
val utlsIndex = sp_stream_fingerprint?.selectedItemPosition ?: return
|
||||
val alpnIndex = sp_stream_alpn?.selectedItemPosition ?: return
|
||||
val publicKey = et_public_key?.text?.toString()?.trim() ?: return
|
||||
val shortId = et_short_id?.text?.toString()?.trim() ?: return
|
||||
val spiderX = et_spider_x?.text?.toString()?.trim() ?: return
|
||||
|
||||
var sni = streamSetting.populateTransportSettings(
|
||||
transport = networks[network],
|
||||
@@ -344,18 +409,32 @@ class ServerActivity : BaseActivity() {
|
||||
allowinsecures[allowInsecureField].toBoolean()
|
||||
}
|
||||
|
||||
streamSetting.populateTlsSettings(streamSecuritys[streamSecurity], allowInsecure, sni, uTlsItems[utlsIndex], alpns[alpnIndex])
|
||||
streamSetting.populateTlsSettings(
|
||||
streamSecurity = streamSecuritys[streamSecurity],
|
||||
allowInsecure = allowInsecure,
|
||||
sni = sni,
|
||||
fingerprint = uTlsItems[utlsIndex],
|
||||
alpns = alpns[alpnIndex],
|
||||
publicKey = publicKey,
|
||||
shortId = shortId,
|
||||
spiderX = spiderX
|
||||
)
|
||||
}
|
||||
|
||||
private fun transportTypes(network: String?): Array<out String> {
|
||||
return if (network == "tcp") {
|
||||
tcpTypes
|
||||
} else if (network == "kcp" || network == "quic") {
|
||||
kcpAndQuicTypes
|
||||
} else if (network == "grpc") {
|
||||
grpcModes
|
||||
} else {
|
||||
arrayOf("---")
|
||||
return when (network) {
|
||||
"tcp" -> {
|
||||
tcpTypes
|
||||
}
|
||||
"kcp", "quic" -> {
|
||||
kcpAndQuicTypes
|
||||
}
|
||||
"grpc" -> {
|
||||
grpcModes
|
||||
}
|
||||
else -> {
|
||||
arrayOf("---")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -364,7 +443,7 @@ class ServerActivity : BaseActivity() {
|
||||
*/
|
||||
private fun deleteServer(): Boolean {
|
||||
if (editGuid.isNotEmpty()) {
|
||||
if (editGuid != mainStorage?.decodeString(MmkvManager.KEY_SELECTED_SERVER)) {
|
||||
if (editGuid != mainStorage?.decodeString(KEY_SELECTED_SERVER)) {
|
||||
if (settingsStorage?.decodeBool(AppConfig.PREF_CONFIRM_REMOVE) == true) {
|
||||
AlertDialog.Builder(this).setMessage(R.string.del_config_comfirm)
|
||||
.setPositiveButton(android.R.string.ok) { _, _ ->
|
||||
|
||||
@@ -2,20 +2,34 @@ package com.v2ray.ang.ui
|
||||
|
||||
import android.content.Intent
|
||||
import android.graphics.Color
|
||||
import android.text.TextUtils
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import android.view.ViewGroup
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import com.google.gson.Gson
|
||||
import com.tencent.mmkv.MMKV
|
||||
import com.v2ray.ang.R
|
||||
import com.v2ray.ang.databinding.ItemQrcodeBinding
|
||||
import com.v2ray.ang.databinding.ItemRecyclerSubSettingBinding
|
||||
import com.v2ray.ang.dto.EConfigType
|
||||
import com.v2ray.ang.extension.toast
|
||||
import com.v2ray.ang.util.AngConfigManager
|
||||
import com.v2ray.ang.util.MmkvManager
|
||||
import com.v2ray.ang.util.QRCodeDecoder
|
||||
import com.v2ray.ang.util.Utils
|
||||
|
||||
class SubSettingRecyclerAdapter(val activity: SubSettingActivity) : RecyclerView.Adapter<SubSettingRecyclerAdapter.MainViewHolder>() {
|
||||
class SubSettingRecyclerAdapter(val activity: SubSettingActivity) :
|
||||
RecyclerView.Adapter<SubSettingRecyclerAdapter.MainViewHolder>() {
|
||||
|
||||
private var mActivity: SubSettingActivity = activity
|
||||
private val subStorage by lazy { MMKV.mmkvWithID(MmkvManager.ID_SUB, MMKV.MULTI_PROCESS_MODE) }
|
||||
|
||||
private val share_method: Array<out String> by lazy {
|
||||
mActivity.resources.getStringArray(R.array.share_sub_method)
|
||||
}
|
||||
|
||||
override fun getItemCount() = mActivity.subscriptions.size
|
||||
|
||||
override fun onBindViewHolder(holder: MainViewHolder, position: Int) {
|
||||
@@ -31,7 +45,8 @@ class SubSettingRecyclerAdapter(val activity: SubSettingActivity) : RecyclerView
|
||||
holder.itemView.setBackgroundColor(Color.TRANSPARENT)
|
||||
|
||||
holder.itemSubSettingBinding.layoutEdit.setOnClickListener {
|
||||
mActivity.startActivity(Intent(mActivity, SubEditActivity::class.java)
|
||||
mActivity.startActivity(
|
||||
Intent(mActivity, SubEditActivity::class.java)
|
||||
.putExtra("subId", subId)
|
||||
)
|
||||
}
|
||||
@@ -40,11 +55,51 @@ class SubSettingRecyclerAdapter(val activity: SubSettingActivity) : RecyclerView
|
||||
subStorage?.encode(subId, Gson().toJson(subItem))
|
||||
notifyItemChanged(position)
|
||||
}
|
||||
|
||||
if (TextUtils.isEmpty(subItem.url)) {
|
||||
holder.itemSubSettingBinding.layoutShare.visibility = View.INVISIBLE
|
||||
} else {
|
||||
holder.itemSubSettingBinding.layoutShare.setOnClickListener {
|
||||
AlertDialog.Builder(mActivity)
|
||||
.setItems(share_method.asList().toTypedArray()) { _, i ->
|
||||
try {
|
||||
when (i) {
|
||||
0 -> {
|
||||
val ivBinding =
|
||||
ItemQrcodeBinding.inflate(LayoutInflater.from(mActivity))
|
||||
ivBinding.ivQcode.setImageBitmap(
|
||||
QRCodeDecoder.createQRCode(
|
||||
subItem.url
|
||||
|
||||
)
|
||||
)
|
||||
AlertDialog.Builder(mActivity).setView(ivBinding.root).show()
|
||||
}
|
||||
|
||||
1 -> {
|
||||
Utils.setClipboard(mActivity, subItem.url)
|
||||
}
|
||||
|
||||
else -> mActivity.toast("else")
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}.show()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MainViewHolder {
|
||||
return MainViewHolder(ItemRecyclerSubSettingBinding.inflate(LayoutInflater.from(parent.context), parent, false))
|
||||
return MainViewHolder(
|
||||
ItemRecyclerSubSettingBinding.inflate(
|
||||
LayoutInflater.from(parent.context),
|
||||
parent,
|
||||
false
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
class MainViewHolder(val itemSubSettingBinding: ItemRecyclerSubSettingBinding) : RecyclerView.ViewHolder(itemSubSettingBinding.root)
|
||||
class MainViewHolder(val itemSubSettingBinding: ItemRecyclerSubSettingBinding) :
|
||||
RecyclerView.ViewHolder(itemSubSettingBinding.root)
|
||||
}
|
||||
|
||||
@@ -11,7 +11,6 @@ import android.content.Intent
|
||||
import android.text.TextUtils
|
||||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import com.google.zxing.WriterException
|
||||
import com.tencent.mmkv.MMKV
|
||||
import com.v2ray.ang.AppConfig
|
||||
import com.v2ray.ang.databinding.ActivityTaskerBinding
|
||||
@@ -65,7 +64,7 @@ class TaskerActivity : BaseActivity() {
|
||||
listview?.setItemChecked(pos, true)
|
||||
}
|
||||
}
|
||||
} catch (e: WriterException) {
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
|
||||
}
|
||||
|
||||
@@ -3,11 +3,12 @@ package com.v2ray.ang.ui
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import com.google.zxing.WriterException
|
||||
import com.v2ray.ang.AppConfig
|
||||
import com.v2ray.ang.R
|
||||
import com.v2ray.ang.databinding.ActivityLogcatBinding
|
||||
import com.v2ray.ang.extension.toast
|
||||
import com.v2ray.ang.util.AngConfigManager
|
||||
import java.net.URLDecoder
|
||||
|
||||
class UrlSchemeActivity : BaseActivity() {
|
||||
private lateinit var binding: ActivityLogcatBinding
|
||||
@@ -18,34 +19,68 @@ class UrlSchemeActivity : BaseActivity() {
|
||||
val view = binding.root
|
||||
setContentView(view)
|
||||
|
||||
var shareUrl: String = ""
|
||||
try {
|
||||
intent?.apply {
|
||||
when (action) {
|
||||
Intent.ACTION_SEND -> {
|
||||
if ("text/plain" == type) {
|
||||
intent.getStringExtra(Intent.EXTRA_TEXT)?.let {
|
||||
shareUrl = it
|
||||
intent.apply {
|
||||
if (action == Intent.ACTION_SEND) {
|
||||
if ("text/plain" == type) {
|
||||
intent.getStringExtra(Intent.EXTRA_TEXT)?.let {
|
||||
val uri = Uri.parse(it)
|
||||
if (uri.scheme?.startsWith(AppConfig.HTTPS_PROTOCOL) == true || uri.scheme?.startsWith(
|
||||
AppConfig.HTTP_PROTOCOL
|
||||
) == true
|
||||
) {
|
||||
val name = uri.getQueryParameter("name") ?: "Subscription"
|
||||
importSubscription(it, name)
|
||||
} else {
|
||||
importConfig(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
Intent.ACTION_VIEW -> {
|
||||
val uri: Uri? = intent.data
|
||||
shareUrl = uri?.getQueryParameter("url")!!
|
||||
} else if (action == Intent.ACTION_VIEW) {
|
||||
when (data?.host) {
|
||||
"install-config" -> {
|
||||
val uri: Uri? = intent.data
|
||||
val shareUrl: String = uri?.getQueryParameter("url")!!
|
||||
toast(shareUrl)
|
||||
importConfig(shareUrl)
|
||||
}
|
||||
|
||||
"install-sub" -> {
|
||||
val uri: Uri? = intent.data
|
||||
val url = uri?.getQueryParameter("url")!!
|
||||
val name = uri.getQueryParameter("name") ?: "Subscription"
|
||||
importSubscription(url, name)
|
||||
}
|
||||
|
||||
else -> {
|
||||
toast(R.string.toast_failure)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
toast(shareUrl)
|
||||
val count = AngConfigManager.importBatchConfig(shareUrl, "", false)
|
||||
if (count > 0) {
|
||||
toast(R.string.toast_success)
|
||||
} else {
|
||||
toast(R.string.toast_failure)
|
||||
}
|
||||
|
||||
|
||||
startActivity(Intent(this, MainActivity::class.java))
|
||||
finish()
|
||||
} catch (e: WriterException) {
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
|
||||
private fun importSubscription(url: String, name: String) {
|
||||
val decodedUrl = URLDecoder.decode(url, "UTF-8")
|
||||
|
||||
val check = AngConfigManager.importSubscription(name, decodedUrl)
|
||||
if (check) toast(R.string.import_subscription_success) else toast(R.string.import_subscription_failure)
|
||||
}
|
||||
|
||||
private fun importConfig(shareUrl: String) {
|
||||
val count = AngConfigManager.importBatchConfig(shareUrl, "", false)
|
||||
if (count > 0) {
|
||||
toast(R.string.toast_success)
|
||||
} else {
|
||||
toast(R.string.toast_failure)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import android.Manifest
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.provider.OpenableColumns
|
||||
import android.util.Log
|
||||
@@ -75,7 +76,14 @@ class UserAssetActivity : BaseActivity() {
|
||||
}
|
||||
|
||||
private fun showFileChooser() {
|
||||
RxPermissions(this).request(Manifest.permission.READ_EXTERNAL_STORAGE).subscribe {
|
||||
val permission = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
Manifest.permission.READ_MEDIA_IMAGES
|
||||
} else {
|
||||
Manifest.permission.READ_EXTERNAL_STORAGE
|
||||
}
|
||||
RxPermissions(this)
|
||||
.request(permission)
|
||||
.subscribe {
|
||||
if (it) {
|
||||
val intent = Intent(Intent.ACTION_GET_CONTENT)
|
||||
intent.type = "*/*"
|
||||
@@ -91,7 +99,8 @@ class UserAssetActivity : BaseActivity() {
|
||||
} catch (ex: android.content.ActivityNotFoundException) {
|
||||
toast(R.string.toast_require_file_manager)
|
||||
}
|
||||
}
|
||||
} else
|
||||
toast(R.string.toast_permission_denied)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -20,11 +20,27 @@ import com.v2ray.ang.util.MmkvManager.KEY_SELECTED_SERVER
|
||||
import java.net.URI
|
||||
import java.util.*
|
||||
import com.v2ray.ang.extension.idnHost
|
||||
import com.v2ray.ang.extension.toast
|
||||
|
||||
object AngConfigManager {
|
||||
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 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 val subStorage by lazy { MMKV.mmkvWithID(MmkvManager.ID_SUB, MMKV.MULTI_PROCESS_MODE) }
|
||||
|
||||
/**
|
||||
@@ -82,8 +98,14 @@ object AngConfigManager {
|
||||
).forEach { key ->
|
||||
settingsStorage?.encode(key, sharedPreferences.getBoolean(key, false))
|
||||
}
|
||||
settingsStorage?.encode(AppConfig.PREF_SNIFFING_ENABLED, sharedPreferences.getBoolean(AppConfig.PREF_SNIFFING_ENABLED, true))
|
||||
settingsStorage?.encode(AppConfig.PREF_PER_APP_PROXY_SET, sharedPreferences.getStringSet(AppConfig.PREF_PER_APP_PROXY_SET, setOf()))
|
||||
settingsStorage?.encode(
|
||||
AppConfig.PREF_SNIFFING_ENABLED,
|
||||
sharedPreferences.getBoolean(AppConfig.PREF_SNIFFING_ENABLED, true)
|
||||
)
|
||||
settingsStorage?.encode(
|
||||
AppConfig.PREF_PER_APP_PROXY_SET,
|
||||
sharedPreferences.getStringSet(AppConfig.PREF_PER_APP_PROXY_SET, setOf())
|
||||
)
|
||||
}
|
||||
|
||||
private fun migrateVmessBean(angConfig: AngConfig, sharedPreferences: SharedPreferences) {
|
||||
@@ -125,7 +147,8 @@ object AngConfigManager {
|
||||
if (TextUtils.isEmpty(vmessBean.security) && TextUtils.isEmpty(vmessBean.id)) {
|
||||
server.users = null
|
||||
} else {
|
||||
val socksUsersBean = V2rayConfig.OutboundBean.OutSettingsBean.ServersBean.SocksUsersBean()
|
||||
val socksUsersBean =
|
||||
V2rayConfig.OutboundBean.OutSettingsBean.ServersBean.SocksUsersBean()
|
||||
socksUsersBean.user = vmessBean.security
|
||||
socksUsersBean.pass = vmessBean.id
|
||||
server.users = listOf(socksUsersBean)
|
||||
@@ -135,17 +158,27 @@ object AngConfigManager {
|
||||
}
|
||||
}
|
||||
config.outboundBean?.streamSettings?.let { streamSetting ->
|
||||
val sni = streamSetting.populateTransportSettings(vmessBean.network, vmessBean.headerType,
|
||||
vmessBean.requestHost, vmessBean.path, vmessBean.path, vmessBean.requestHost, vmessBean.path,
|
||||
vmessBean.headerType, vmessBean.path)
|
||||
val sni = streamSetting.populateTransportSettings(
|
||||
vmessBean.network,
|
||||
vmessBean.headerType,
|
||||
vmessBean.requestHost,
|
||||
vmessBean.path,
|
||||
vmessBean.path,
|
||||
vmessBean.requestHost,
|
||||
vmessBean.path,
|
||||
vmessBean.headerType,
|
||||
vmessBean.path
|
||||
)
|
||||
val allowInsecure = if (vmessBean.allowInsecure.isBlank()) {
|
||||
settingsStorage?.decodeBool(AppConfig.PREF_ALLOW_INSECURE) ?: false
|
||||
} else {
|
||||
vmessBean.allowInsecure.toBoolean()
|
||||
}
|
||||
var fingerprint = streamSetting.tlsSettings?.fingerprint
|
||||
streamSetting.populateTlsSettings(vmessBean.streamSecurity, allowInsecure,
|
||||
vmessBean.sni.ifBlank { sni }, fingerprint, null)
|
||||
streamSetting.populateTlsSettings(
|
||||
vmessBean.streamSecurity, allowInsecure,
|
||||
vmessBean.sni.ifBlank { sni }, fingerprint, null, null, null, null
|
||||
)
|
||||
}
|
||||
}
|
||||
val key = MmkvManager.encodeServerConfig(vmessBean.guid, config)
|
||||
@@ -168,14 +201,21 @@ object AngConfigManager {
|
||||
/**
|
||||
* import config form qrcode or...
|
||||
*/
|
||||
private fun importConfig(str: String?, subid: String, removedSelectedServer: ServerConfig?): Int {
|
||||
private fun importConfig(
|
||||
str: String?,
|
||||
subid: String,
|
||||
removedSelectedServer: ServerConfig?
|
||||
): Int {
|
||||
try {
|
||||
if (str == null || TextUtils.isEmpty(str)) {
|
||||
return R.string.toast_none_data
|
||||
}
|
||||
|
||||
//maybe sub
|
||||
if (TextUtils.isEmpty(subid) && (str.startsWith(HTTP_PROTOCOL) || str.startsWith(HTTPS_PROTOCOL))) {
|
||||
if (TextUtils.isEmpty(subid) && (str.startsWith(HTTP_PROTOCOL) || str.startsWith(
|
||||
HTTPS_PROTOCOL
|
||||
))
|
||||
) {
|
||||
MmkvManager.importUrlAsSubscription(str)
|
||||
return 0
|
||||
}
|
||||
@@ -186,8 +226,6 @@ object AngConfigManager {
|
||||
config = ServerConfig.create(EConfigType.VMESS)
|
||||
val streamSetting = config.outboundBean?.streamSettings ?: return -1
|
||||
|
||||
var fingerprint = streamSetting.tlsSettings?.fingerprint
|
||||
|
||||
|
||||
if (!tryParseNewVmess(str, config, allowInsecure)) {
|
||||
if (str.indexOf("?") > 0) {
|
||||
@@ -203,9 +241,9 @@ object AngConfigManager {
|
||||
val vmessQRCode = Gson().fromJson(result, VmessQRCode::class.java)
|
||||
// Although VmessQRCode fields are non null, looks like Gson may still create null fields
|
||||
if (TextUtils.isEmpty(vmessQRCode.add)
|
||||
|| TextUtils.isEmpty(vmessQRCode.port)
|
||||
|| TextUtils.isEmpty(vmessQRCode.id)
|
||||
|| TextUtils.isEmpty(vmessQRCode.net)
|
||||
|| TextUtils.isEmpty(vmessQRCode.port)
|
||||
|| TextUtils.isEmpty(vmessQRCode.id)
|
||||
|| TextUtils.isEmpty(vmessQRCode.net)
|
||||
) {
|
||||
return R.string.toast_incorrect_protocol
|
||||
}
|
||||
@@ -215,15 +253,28 @@ object AngConfigManager {
|
||||
vnext.address = vmessQRCode.add
|
||||
vnext.port = Utils.parseInt(vmessQRCode.port)
|
||||
vnext.users[0].id = vmessQRCode.id
|
||||
vnext.users[0].security = if (TextUtils.isEmpty(vmessQRCode.scy)) DEFAULT_SECURITY else vmessQRCode.scy
|
||||
vnext.users[0].security =
|
||||
if (TextUtils.isEmpty(vmessQRCode.scy)) DEFAULT_SECURITY else vmessQRCode.scy
|
||||
vnext.users[0].alterId = Utils.parseInt(vmessQRCode.aid)
|
||||
}
|
||||
val sni = streamSetting.populateTransportSettings(vmessQRCode.net, vmessQRCode.type, vmessQRCode.host,
|
||||
vmessQRCode.path, vmessQRCode.path, vmessQRCode.host, vmessQRCode.path, vmessQRCode.type, vmessQRCode.path)
|
||||
val sni = streamSetting.populateTransportSettings(
|
||||
vmessQRCode.net,
|
||||
vmessQRCode.type,
|
||||
vmessQRCode.host,
|
||||
vmessQRCode.path,
|
||||
vmessQRCode.path,
|
||||
vmessQRCode.host,
|
||||
vmessQRCode.path,
|
||||
vmessQRCode.type,
|
||||
vmessQRCode.path
|
||||
)
|
||||
|
||||
|
||||
streamSetting.populateTlsSettings(vmessQRCode.tls, allowInsecure,
|
||||
if (TextUtils.isEmpty(vmessQRCode.sni)) sni else vmessQRCode.sni, fingerprint, vmessQRCode.alpn)
|
||||
val fingerprint = vmessQRCode.fp ?: streamSetting.tlsSettings?.fingerprint
|
||||
streamSetting.populateTlsSettings(
|
||||
vmessQRCode.tls, allowInsecure,
|
||||
if (TextUtils.isEmpty(vmessQRCode.sni)) sni else vmessQRCode.sni,
|
||||
fingerprint, vmessQRCode.alpn, null, null, null
|
||||
)
|
||||
}
|
||||
}
|
||||
} else if (str.startsWith(EConfigType.SHADOWSOCKS.protocolScheme)) {
|
||||
@@ -233,7 +284,8 @@ object AngConfigManager {
|
||||
val indexSplit = result.indexOf("#")
|
||||
if (indexSplit > 0) {
|
||||
try {
|
||||
config.remarks = Utils.urlDecode(result.substring(indexSplit + 1, result.length))
|
||||
config.remarks =
|
||||
Utils.urlDecode(result.substring(indexSplit + 1, result.length))
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
@@ -244,13 +296,17 @@ object AngConfigManager {
|
||||
//part decode
|
||||
val indexS = result.indexOf("@")
|
||||
result = if (indexS > 0) {
|
||||
Utils.decode(result.substring(0, indexS)) + result.substring(indexS, result.length)
|
||||
Utils.decode(result.substring(0, indexS)) + result.substring(
|
||||
indexS,
|
||||
result.length
|
||||
)
|
||||
} else {
|
||||
Utils.decode(result)
|
||||
}
|
||||
|
||||
val legacyPattern = "^(.+?):(.*)@(.+?):(\\d+?)/?$".toRegex()
|
||||
val match = legacyPattern.matchEntire(result) ?: return R.string.toast_incorrect_protocol
|
||||
val match = legacyPattern.matchEntire(result)
|
||||
?: return R.string.toast_incorrect_protocol
|
||||
|
||||
config.outboundBean?.settings?.servers?.get(0)?.let { server ->
|
||||
server.address = match.groupValues[3].removeSurrounding("[", "]")
|
||||
@@ -265,7 +321,8 @@ object AngConfigManager {
|
||||
config = ServerConfig.create(EConfigType.SOCKS)
|
||||
if (indexSplit > 0) {
|
||||
try {
|
||||
config.remarks = Utils.urlDecode(result.substring(indexSplit + 1, result.length))
|
||||
config.remarks =
|
||||
Utils.urlDecode(result.substring(indexSplit + 1, result.length))
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
@@ -276,18 +333,23 @@ object AngConfigManager {
|
||||
//part decode
|
||||
val indexS = result.indexOf("@")
|
||||
if (indexS > 0) {
|
||||
result = Utils.decode(result.substring(0, indexS)) + result.substring(indexS, result.length)
|
||||
result = Utils.decode(result.substring(0, indexS)) + result.substring(
|
||||
indexS,
|
||||
result.length
|
||||
)
|
||||
} else {
|
||||
result = Utils.decode(result)
|
||||
}
|
||||
|
||||
val legacyPattern = "^(.*):(.*)@(.+?):(\\d+?)$".toRegex()
|
||||
val match = legacyPattern.matchEntire(result) ?: return R.string.toast_incorrect_protocol
|
||||
val match =
|
||||
legacyPattern.matchEntire(result) ?: return R.string.toast_incorrect_protocol
|
||||
|
||||
config.outboundBean?.settings?.servers?.get(0)?.let { server ->
|
||||
server.address = match.groupValues[3].removeSurrounding("[", "]")
|
||||
server.port = match.groupValues[4].toInt()
|
||||
val socksUsersBean = V2rayConfig.OutboundBean.OutSettingsBean.ServersBean.SocksUsersBean()
|
||||
val socksUsersBean =
|
||||
V2rayConfig.OutboundBean.OutSettingsBean.ServersBean.SocksUsersBean()
|
||||
socksUsersBean.user = match.groupValues[1].lowercase()
|
||||
socksUsersBean.pass = match.groupValues[2]
|
||||
server.users = listOf(socksUsersBean)
|
||||
@@ -303,14 +365,29 @@ object AngConfigManager {
|
||||
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"])
|
||||
config.outboundBean?.streamSettings?.populateTlsSettings(queryParam["security"] ?: TLS, allowInsecure, queryParam["sni"] ?: sni!!, fingerprint, queryParam["alpn"])
|
||||
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"]
|
||||
)
|
||||
fingerprint = queryParam["fp"] ?: ""
|
||||
config.outboundBean?.streamSettings?.populateTlsSettings(
|
||||
queryParam["security"] ?: TLS,
|
||||
allowInsecure, queryParam["sni"] ?: sni!!, fingerprint, queryParam["alpn"],
|
||||
null, null, null
|
||||
)
|
||||
flow = queryParam["flow"] ?: ""
|
||||
} else {
|
||||
|
||||
config.outboundBean?.streamSettings?.populateTlsSettings(TLS, allowInsecure, "", fingerprint, null)
|
||||
config.outboundBean?.streamSettings?.populateTlsSettings(
|
||||
TLS, allowInsecure, "",
|
||||
fingerprint, null, null, null, null
|
||||
)
|
||||
}
|
||||
|
||||
config.outboundBean?.settings?.servers?.get(0)?.let { server ->
|
||||
@@ -333,22 +410,41 @@ object AngConfigManager {
|
||||
vnext.port = uri.port
|
||||
vnext.users[0].id = uri.userInfo
|
||||
vnext.users[0].encryption = queryParam["encryption"] ?: "none"
|
||||
vnext.users[0].flow =queryParam["flow"] ?: ""
|
||||
vnext.users[0].flow = queryParam["flow"] ?: ""
|
||||
}
|
||||
|
||||
val sni = streamSetting.populateTransportSettings(queryParam["type"] ?: "tcp", queryParam["headerType"],
|
||||
queryParam["host"], queryParam["path"], queryParam["seed"], queryParam["quicSecurity"], queryParam["key"],
|
||||
queryParam["mode"], queryParam["serviceName"])
|
||||
streamSetting.populateTlsSettings(queryParam["security"] ?: "", allowInsecure, queryParam["sni"] ?: sni, fingerprint, queryParam["alpn"])
|
||||
val sni = streamSetting.populateTransportSettings(
|
||||
queryParam["type"] ?: "tcp",
|
||||
queryParam["headerType"],
|
||||
queryParam["host"],
|
||||
queryParam["path"],
|
||||
queryParam["seed"],
|
||||
queryParam["quicSecurity"],
|
||||
queryParam["key"],
|
||||
queryParam["mode"],
|
||||
queryParam["serviceName"]
|
||||
)
|
||||
fingerprint = queryParam["fp"] ?: ""
|
||||
val pbk = queryParam["pbk"] ?: ""
|
||||
val sid = queryParam["sid"] ?: ""
|
||||
val spx = Utils.urlDecode(queryParam["spx"] ?: "")
|
||||
streamSetting.populateTlsSettings(
|
||||
queryParam["security"] ?: "", allowInsecure,
|
||||
queryParam["sni"] ?: sni, fingerprint, queryParam["alpn"], pbk, sid, spx
|
||||
)
|
||||
}
|
||||
if (config == null){
|
||||
if (config == null) {
|
||||
return R.string.toast_incorrect_protocol
|
||||
}
|
||||
config.subscriptionId = subid
|
||||
val guid = MmkvManager.encodeServerConfig("", config)
|
||||
if (removedSelectedServer != null &&
|
||||
config.getProxyOutbound()?.getServerAddress() == removedSelectedServer.getProxyOutbound()?.getServerAddress() &&
|
||||
config.getProxyOutbound()?.getServerPort() == removedSelectedServer.getProxyOutbound()?.getServerPort()) {
|
||||
config.getProxyOutbound()
|
||||
?.getServerAddress() == removedSelectedServer.getProxyOutbound()
|
||||
?.getServerAddress() &&
|
||||
config.getProxyOutbound()
|
||||
?.getServerPort() == removedSelectedServer.getProxyOutbound()?.getServerPort()
|
||||
) {
|
||||
mainStorage?.encode(KEY_SELECTED_SERVER, guid)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
@@ -358,14 +454,18 @@ object AngConfigManager {
|
||||
return 0
|
||||
}
|
||||
|
||||
private fun tryParseNewVmess(uriString: String, config: ServerConfig, allowInsecure: Boolean): Boolean {
|
||||
private fun tryParseNewVmess(
|
||||
uriString: String,
|
||||
config: ServerConfig,
|
||||
allowInsecure: Boolean
|
||||
): Boolean {
|
||||
return runCatching {
|
||||
val uri = URI(uriString)
|
||||
check(uri.scheme == "vmess")
|
||||
val (_, protocol, tlsStr, uuid, alterId) =
|
||||
Regex("(tcp|http|ws|kcp|quic|grpc)(\\+tls)?:([0-9a-z]{8}-[0-9a-z]{4}-[0-9a-z]{4}-[0-9a-z]{4}-[0-9a-z]{12})")
|
||||
.matchEntire(uri.userInfo)?.groupValues
|
||||
?: error("parse user info fail.")
|
||||
Regex("(tcp|http|ws|kcp|quic|grpc)(\\+tls)?:([0-9a-z]{8}-[0-9a-z]{4}-[0-9a-z]{4}-[0-9a-z]{4}-[0-9a-z]{12})")
|
||||
.matchEntire(uri.userInfo)?.groupValues
|
||||
?: error("parse user info fail.")
|
||||
val tls = tlsStr.isNotBlank()
|
||||
val queryParam = uri.rawQuery.split("&")
|
||||
.associate { it.split("=").let { (k, v) -> k to Utils.urlDecode(v) } }
|
||||
@@ -380,11 +480,19 @@ object AngConfigManager {
|
||||
vnext.users[0].alterId = alterId.toInt()
|
||||
}
|
||||
var fingerprint = streamSetting.tlsSettings?.fingerprint
|
||||
val sni = streamSetting.populateTransportSettings(protocol, queryParam["type"],
|
||||
queryParam["host"]?.split("|")?.get(0) ?: "",
|
||||
queryParam["path"]?.takeIf { it.trim() != "/" } ?: "", queryParam["seed"], queryParam["security"],
|
||||
queryParam["key"], queryParam["mode"], queryParam["serviceName"])
|
||||
streamSetting.populateTlsSettings(if (tls) TLS else "", allowInsecure, sni, fingerprint, null)
|
||||
val sni = streamSetting.populateTransportSettings(protocol,
|
||||
queryParam["type"],
|
||||
queryParam["host"]?.split("|")?.get(0) ?: "",
|
||||
queryParam["path"]?.takeIf { it.trim() != "/" } ?: "",
|
||||
queryParam["seed"],
|
||||
queryParam["security"],
|
||||
queryParam["key"],
|
||||
queryParam["mode"],
|
||||
queryParam["serviceName"])
|
||||
streamSetting.populateTlsSettings(
|
||||
if (tls) TLS else "", allowInsecure, sni, fingerprint, null,
|
||||
null, null, null
|
||||
)
|
||||
true
|
||||
}.getOrElse { false }
|
||||
}
|
||||
@@ -472,12 +580,17 @@ object AngConfigManager {
|
||||
vmessQRCode.add = outbound.getServerAddress().orEmpty()
|
||||
vmessQRCode.port = outbound.getServerPort().toString()
|
||||
vmessQRCode.id = outbound.getPassword().orEmpty()
|
||||
vmessQRCode.aid = outbound.settings?.vnext?.get(0)?.users?.get(0)?.alterId.toString()
|
||||
vmessQRCode.scy = outbound.settings?.vnext?.get(0)?.users?.get(0)?.security.toString()
|
||||
vmessQRCode.aid =
|
||||
outbound.settings?.vnext?.get(0)?.users?.get(0)?.alterId.toString()
|
||||
vmessQRCode.scy =
|
||||
outbound.settings?.vnext?.get(0)?.users?.get(0)?.security.toString()
|
||||
vmessQRCode.net = streamSetting.network
|
||||
vmessQRCode.tls = streamSetting.security
|
||||
vmessQRCode.sni = streamSetting.tlsSettings?.serverName.orEmpty()
|
||||
vmessQRCode.alpn = Utils.removeWhiteSpace(streamSetting.tlsSettings?.alpn?.joinToString()).orEmpty()
|
||||
vmessQRCode.alpn =
|
||||
Utils.removeWhiteSpace(streamSetting.tlsSettings?.alpn?.joinToString())
|
||||
.orEmpty()
|
||||
vmessQRCode.fp = streamSetting.tlsSettings?.fingerprint.orEmpty()
|
||||
outbound.getTransportSettingDetails()?.let { transportDetails ->
|
||||
vmessQRCode.type = transportDetails[0]
|
||||
vmessQRCode.host = transportDetails[1]
|
||||
@@ -486,25 +599,34 @@ object AngConfigManager {
|
||||
val json = Gson().toJson(vmessQRCode)
|
||||
Utils.encode(json)
|
||||
}
|
||||
|
||||
EConfigType.CUSTOM, EConfigType.WIREGUARD -> ""
|
||||
EConfigType.SHADOWSOCKS -> {
|
||||
val remark = "#" + Utils.urlEncode(config.remarks)
|
||||
val pw = Utils.encode("${outbound.getSecurityEncryption()}:${outbound.getPassword()}")
|
||||
val url = String.format("%s@%s:%s",
|
||||
pw,
|
||||
Utils.getIpv6Address(outbound.getServerAddress()!!),
|
||||
outbound.getServerPort())
|
||||
url + remark
|
||||
}
|
||||
EConfigType.SOCKS -> {
|
||||
val remark = "#" + Utils.urlEncode(config.remarks)
|
||||
val pw = Utils.encode("${outbound.settings?.servers?.get(0)?.users?.get(0)?.user}:${outbound.getPassword()}")
|
||||
val url = String.format("%s@%s:%s",
|
||||
val pw =
|
||||
Utils.encode("${outbound.getSecurityEncryption()}:${outbound.getPassword()}")
|
||||
val url = String.format(
|
||||
"%s@%s:%s",
|
||||
pw,
|
||||
Utils.getIpv6Address(outbound.getServerAddress()!!),
|
||||
outbound.getServerPort())
|
||||
outbound.getServerPort()
|
||||
)
|
||||
url + remark
|
||||
}
|
||||
|
||||
EConfigType.SOCKS -> {
|
||||
val remark = "#" + Utils.urlEncode(config.remarks)
|
||||
val pw =
|
||||
Utils.encode("${outbound.settings?.servers?.get(0)?.users?.get(0)?.user}:${outbound.getPassword()}")
|
||||
val url = String.format(
|
||||
"%s@%s:%s",
|
||||
pw,
|
||||
Utils.getIpv6Address(outbound.getServerAddress()!!),
|
||||
outbound.getServerPort()
|
||||
)
|
||||
url + remark
|
||||
}
|
||||
|
||||
EConfigType.VLESS,
|
||||
EConfigType.TROJAN -> {
|
||||
val remark = "#" + Utils.urlEncode(config.remarks)
|
||||
@@ -528,12 +650,26 @@ object AngConfigManager {
|
||||
}
|
||||
|
||||
dicQuery["security"] = streamSetting.security.ifEmpty { "none" }
|
||||
(streamSetting.tlsSettings?: streamSetting.xtlsSettings)?.let { tlsSetting ->
|
||||
(streamSetting.tlsSettings
|
||||
?: streamSetting.realitySettings)?.let { tlsSetting ->
|
||||
if (!TextUtils.isEmpty(tlsSetting.serverName)) {
|
||||
dicQuery["sni"] = tlsSetting.serverName
|
||||
}
|
||||
if (!tlsSetting.alpn.isNullOrEmpty() && tlsSetting.alpn.isNotEmpty()) {
|
||||
dicQuery["alpn"] = Utils.removeWhiteSpace(tlsSetting.alpn.joinToString()).orEmpty()
|
||||
dicQuery["alpn"] =
|
||||
Utils.removeWhiteSpace(tlsSetting.alpn.joinToString()).orEmpty()
|
||||
}
|
||||
if (!TextUtils.isEmpty(tlsSetting.fingerprint)) {
|
||||
dicQuery["fp"] = tlsSetting.fingerprint!!
|
||||
}
|
||||
if (!TextUtils.isEmpty(tlsSetting.publicKey)) {
|
||||
dicQuery["pbk"] = tlsSetting.publicKey!!
|
||||
}
|
||||
if (!TextUtils.isEmpty(tlsSetting.shortId)) {
|
||||
dicQuery["sid"] = tlsSetting.shortId!!
|
||||
}
|
||||
if (!TextUtils.isEmpty(tlsSetting.spiderX)) {
|
||||
dicQuery["spx"] = Utils.urlEncode(tlsSetting.spiderX!!)
|
||||
}
|
||||
}
|
||||
dicQuery["type"] = streamSetting.network.ifEmpty { V2rayConfig.DEFAULT_NETWORK }
|
||||
@@ -546,12 +682,14 @@ object AngConfigManager {
|
||||
dicQuery["host"] = Utils.urlEncode(transportDetails[1])
|
||||
}
|
||||
}
|
||||
|
||||
"kcp" -> {
|
||||
dicQuery["headerType"] = transportDetails[0].ifEmpty { "none" }
|
||||
if (!TextUtils.isEmpty(transportDetails[2])) {
|
||||
dicQuery["seed"] = Utils.urlEncode(transportDetails[2])
|
||||
}
|
||||
}
|
||||
|
||||
"ws" -> {
|
||||
if (!TextUtils.isEmpty(transportDetails[1])) {
|
||||
dicQuery["host"] = Utils.urlEncode(transportDetails[1])
|
||||
@@ -560,6 +698,7 @@ object AngConfigManager {
|
||||
dicQuery["path"] = Utils.urlEncode(transportDetails[2])
|
||||
}
|
||||
}
|
||||
|
||||
"http", "h2" -> {
|
||||
dicQuery["type"] = "http"
|
||||
if (!TextUtils.isEmpty(transportDetails[1])) {
|
||||
@@ -569,11 +708,13 @@ object AngConfigManager {
|
||||
dicQuery["path"] = Utils.urlEncode(transportDetails[2])
|
||||
}
|
||||
}
|
||||
|
||||
"quic" -> {
|
||||
dicQuery["headerType"] = transportDetails[0].ifEmpty { "none" }
|
||||
dicQuery["quicSecurity"] = Utils.urlEncode(transportDetails[1])
|
||||
dicQuery["key"] = Utils.urlEncode(transportDetails[2])
|
||||
}
|
||||
|
||||
"grpc" -> {
|
||||
dicQuery["mode"] = transportDetails[0]
|
||||
dicQuery["serviceName"] = transportDetails[2]
|
||||
@@ -581,13 +722,15 @@ object AngConfigManager {
|
||||
}
|
||||
}
|
||||
val query = "?" + dicQuery.toList().joinToString(
|
||||
separator = "&",
|
||||
transform = { it.first + "=" + it.second })
|
||||
separator = "&",
|
||||
transform = { it.first + "=" + it.second })
|
||||
|
||||
val url = String.format("%s@%s:%s",
|
||||
outbound.getPassword(),
|
||||
Utils.getIpv6Address(outbound.getServerAddress()!!),
|
||||
outbound.getServerPort())
|
||||
val url = String.format(
|
||||
"%s@%s:%s",
|
||||
outbound.getPassword(),
|
||||
Utils.getIpv6Address(outbound.getServerAddress()!!),
|
||||
outbound.getServerPort()
|
||||
)
|
||||
url + query + remark
|
||||
}
|
||||
}
|
||||
@@ -649,7 +792,7 @@ object AngConfigManager {
|
||||
if (TextUtils.isEmpty(conf)) {
|
||||
return null
|
||||
}
|
||||
return Utils.createQRCode(conf)
|
||||
return QRCodeDecoder.createQRCode(conf)
|
||||
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
@@ -735,16 +878,34 @@ object AngConfigManager {
|
||||
|
||||
var count = 0
|
||||
servers.lines()
|
||||
.forEach {
|
||||
val resId = importConfig(it, subid, removedSelectedServer)
|
||||
if (resId == 0) {
|
||||
count++
|
||||
}
|
||||
.reversed()
|
||||
.forEach {
|
||||
val resId = importConfig(it, subid, removedSelectedServer)
|
||||
if (resId == 0) {
|
||||
count++
|
||||
}
|
||||
}
|
||||
return count
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
fun importSubscription(remark: String, url: String, enabled: Boolean = true): Boolean {
|
||||
val subId = Utils.getUuid()
|
||||
val subItem = SubscriptionItem()
|
||||
|
||||
|
||||
subItem.remarks = remark
|
||||
subItem.url = url
|
||||
subItem.enabled = enabled
|
||||
|
||||
if (TextUtils.isEmpty(subItem.remarks) || TextUtils.isEmpty(subItem.url)) {
|
||||
return false
|
||||
}
|
||||
subStorage?.encode(subId, Gson().toJson(subItem))
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,7 +46,7 @@ object MmkvManager {
|
||||
serverStorage?.encode(key, Gson().toJson(config))
|
||||
val serverList = decodeServerList()
|
||||
if (!serverList.contains(key)) {
|
||||
serverList.add(key)
|
||||
serverList.add(0, key)
|
||||
mainStorage?.encode(KEY_ANG_CONFIGS, Gson().toJson(serverList))
|
||||
if (mainStorage?.decodeString(KEY_SELECTED_SERVER).isNullOrBlank()) {
|
||||
mainStorage?.encode(KEY_SELECTED_SERVER, key)
|
||||
|
||||
@@ -5,6 +5,7 @@ import android.graphics.BitmapFactory
|
||||
import com.google.zxing.*
|
||||
import com.google.zxing.common.GlobalHistogramBinarizer
|
||||
import com.google.zxing.common.HybridBinarizer
|
||||
import com.google.zxing.qrcode.QRCodeWriter
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
@@ -13,6 +14,36 @@ import java.util.*
|
||||
object QRCodeDecoder {
|
||||
val HINTS: MutableMap<DecodeHintType, Any?> = EnumMap(DecodeHintType::class.java)
|
||||
|
||||
/**
|
||||
* create qrcode using zxing
|
||||
*/
|
||||
fun createQRCode(text: String, size: Int = 800): Bitmap? {
|
||||
try {
|
||||
val hints = HashMap<EncodeHintType, String>()
|
||||
hints[EncodeHintType.CHARACTER_SET] = "utf-8"
|
||||
val bitMatrix = QRCodeWriter().encode(text,
|
||||
BarcodeFormat.QR_CODE, size, size, hints)
|
||||
val pixels = IntArray(size * size)
|
||||
for (y in 0 until size) {
|
||||
for (x in 0 until size) {
|
||||
if (bitMatrix.get(x, y)) {
|
||||
pixels[y * size + x] = 0xff000000.toInt()
|
||||
} else {
|
||||
pixels[y * size + x] = 0xffffffff.toInt()
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
val bitmap = Bitmap.createBitmap(size, size,
|
||||
Bitmap.Config.ARGB_8888)
|
||||
bitmap.setPixels(pixels, 0, size, 0, 0, size, size)
|
||||
return bitmap
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 同步解析本地图片二维码。该方法是耗时操作,请在子线程中调用。
|
||||
*
|
||||
|
||||
@@ -4,15 +4,10 @@ import android.content.ClipboardManager
|
||||
import android.content.Context
|
||||
import android.text.Editable
|
||||
import android.util.Base64
|
||||
import com.google.zxing.WriterException
|
||||
import android.graphics.Bitmap
|
||||
import com.google.zxing.BarcodeFormat
|
||||
import com.google.zxing.qrcode.QRCodeWriter
|
||||
import com.google.zxing.EncodeHintType
|
||||
import java.util.*
|
||||
import kotlin.collections.HashMap
|
||||
import android.content.ClipData
|
||||
import android.content.Intent
|
||||
import android.content.pm.PackageManager
|
||||
import android.content.res.Configuration.UI_MODE_NIGHT_MASK
|
||||
import android.content.res.Configuration.UI_MODE_NIGHT_YES
|
||||
import android.net.Uri
|
||||
@@ -171,36 +166,6 @@ object Utils {
|
||||
return ret
|
||||
}
|
||||
|
||||
/**
|
||||
* create qrcode using zxing
|
||||
*/
|
||||
fun createQRCode(text: String, size: Int = 800): Bitmap? {
|
||||
try {
|
||||
val hints = HashMap<EncodeHintType, String>()
|
||||
hints[EncodeHintType.CHARACTER_SET] = "utf-8"
|
||||
val bitMatrix = QRCodeWriter().encode(text,
|
||||
BarcodeFormat.QR_CODE, size, size, hints)
|
||||
val pixels = IntArray(size * size)
|
||||
for (y in 0 until size) {
|
||||
for (x in 0 until size) {
|
||||
if (bitMatrix.get(x, y)) {
|
||||
pixels[y * size + x] = 0xff000000.toInt()
|
||||
} else {
|
||||
pixels[y * size + x] = 0xffffffff.toInt()
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
val bitmap = Bitmap.createBitmap(size, size,
|
||||
Bitmap.Config.ARGB_8888)
|
||||
bitmap.setPixels(pixels, 0, size, 0, 0, size, size)
|
||||
return bitmap
|
||||
} catch (e: WriterException) {
|
||||
e.printStackTrace()
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* is ip address
|
||||
*/
|
||||
@@ -274,7 +239,7 @@ object Utils {
|
||||
if (value != null && Patterns.WEB_URL.matcher(value).matches() || URLUtil.isValidUrl(value)) {
|
||||
return true
|
||||
}
|
||||
} catch (e: WriterException) {
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
return false
|
||||
}
|
||||
@@ -435,5 +400,9 @@ object Utils {
|
||||
return URL(url.protocol, IDN.toASCII(url.host, IDN.ALLOW_UNASSIGNED), url.port, url.file)
|
||||
.toExternalForm()
|
||||
}
|
||||
|
||||
fun isTv(context: Context): Boolean =
|
||||
context.packageManager.hasSystemFeature(PackageManager.FEATURE_LEANBACK)
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -151,7 +151,7 @@ object V2rayConfigUtil {
|
||||
|
||||
v2rayConfig.routing.domainStrategy = settingsStorage?.decodeString(AppConfig.PREF_ROUTING_DOMAIN_STRATEGY)
|
||||
?: "IPIfNonMatch"
|
||||
v2rayConfig.routing.domainMatcher = "mph"
|
||||
// v2rayConfig.routing.domainMatcher = "mph"
|
||||
val routingMode = settingsStorage?.decodeString(AppConfig.PREF_ROUTING_MODE) ?: ERoutingMode.GLOBAL_PROXY.value
|
||||
|
||||
// Hardcode googleapis.cn
|
||||
|
||||
@@ -74,8 +74,8 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
|
||||
config.fullConfig = Gson().fromJson(server, V2rayConfig::class.java)
|
||||
val key = MmkvManager.encodeServerConfig("", config)
|
||||
serverRawStorage?.encode(key, server)
|
||||
serverList.add(key)
|
||||
serversCache.add(ServersCache(key,config))
|
||||
serverList.add(0, key)
|
||||
serversCache.add(0, ServersCache(key,config))
|
||||
}
|
||||
|
||||
fun swapServer(fromPosition: Int, toPosition: Int) {
|
||||
@@ -201,6 +201,27 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
|
||||
return -1
|
||||
}
|
||||
|
||||
fun removeDuplicateServer() {
|
||||
val deleteServer = mutableListOf<String>()
|
||||
serversCache.forEachIndexed { index, it ->
|
||||
val outbound = it.config.getProxyOutbound()
|
||||
serversCache.forEachIndexed { index2, it2 ->
|
||||
if(index2 > index){
|
||||
val outbound2 = it2.config.getProxyOutbound()
|
||||
if( outbound == outbound2 && !deleteServer.contains(it2.guid))
|
||||
{
|
||||
deleteServer.add(it2.guid)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for(it in deleteServer){
|
||||
MmkvManager.removeServer(it)
|
||||
}
|
||||
reloadServerList()
|
||||
getApplication<AngApplication>().toast(getApplication<AngApplication>().getString(R.string.title_del_duplicate_config_count, deleteServer.count()))
|
||||
}
|
||||
|
||||
private val mMsgReceiver = object : BroadcastReceiver() {
|
||||
override fun onReceive(ctx: Context?, intent: Intent?) {
|
||||
when (intent?.getIntExtra("key", 0)) {
|
||||
|
||||
@@ -50,7 +50,8 @@ class SettingsViewModel(application: Application) : AndroidViewModel(application
|
||||
AppConfig.PREF_PREFER_IPV6,
|
||||
AppConfig.PREF_PER_APP_PROXY,
|
||||
AppConfig.PREF_BYPASS_APPS,
|
||||
AppConfig.PREF_CONFIRM_REMOVE, -> {
|
||||
AppConfig.PREF_CONFIRM_REMOVE,
|
||||
AppConfig.PREF_START_SCAN_IMMEDIATE, -> {
|
||||
settingsStorage?.encode(key, sharedPreferences.getBoolean(key, false))
|
||||
}
|
||||
AppConfig.PREF_SNIFFING_ENABLED -> {
|
||||
|
||||
@@ -50,7 +50,7 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/connection_test_height"
|
||||
android:background="?attr/colorPrimary"
|
||||
android:gravity="center|left"
|
||||
android:gravity="center|start"
|
||||
android:nextFocusRight="@+id/fab"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
|
||||
@@ -98,24 +98,6 @@
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/layout_margin_top_height"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/server_lab_flow" />
|
||||
|
||||
<Spinner
|
||||
android:id="@+id/sp_flow"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/edit_height"
|
||||
android:entries="@array/flows" />
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
|
||||
@@ -16,7 +16,8 @@
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/recycler_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" />
|
||||
android:layout_height="match_parent"
|
||||
tools:listitem="@layout/item_recycler_sub_setting"/>
|
||||
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
</RelativeLayout>
|
||||
@@ -73,12 +73,13 @@
|
||||
android:gravity="center"
|
||||
android:orientation="vertical"
|
||||
android:padding="@dimen/layout_margin_spacing"
|
||||
android:visibility="invisible">
|
||||
>
|
||||
|
||||
<ImageView
|
||||
android:layout_width="@dimen/png_height"
|
||||
android:layout_height="@dimen/png_height"
|
||||
android:src="@drawable/ic_share_black_24dp" />
|
||||
android:src="@drawable/ic_share_black_24dp"
|
||||
app:tint="?attr/colorMainText"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
@@ -104,5 +104,64 @@
|
||||
android:layout_height="@dimen/edit_height"
|
||||
android:entries="@array/allowinsecures" />
|
||||
</LinearLayout>
|
||||
<LinearLayout
|
||||
android:id="@+id/l6"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="@dimen/activity_horizontal_margin"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/server_lab_public_key" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/et_public_key"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/edit_height"
|
||||
android:inputType="text"
|
||||
android:nextFocusDown="@+id/sp_stream_fingerprint" />
|
||||
|
||||
</LinearLayout>
|
||||
<LinearLayout
|
||||
android:id="@+id/l7"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="@dimen/activity_horizontal_margin"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/server_lab_short_id" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/et_short_id"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/edit_height"
|
||||
android:inputType="text"
|
||||
android:nextFocusDown="@+id/sp_stream_fingerprint" />
|
||||
|
||||
</LinearLayout>
|
||||
<LinearLayout
|
||||
android:id="@+id/l8"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="@dimen/activity_horizontal_margin"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/server_lab_spider_x" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/et_spider_x"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/edit_height"
|
||||
android:inputType="text"
|
||||
android:nextFocusDown="@+id/sp_stream_fingerprint" />
|
||||
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
@@ -73,6 +73,11 @@
|
||||
android:icon="@drawable/ic_delete_white_24dp"
|
||||
android:title="@string/title_del_all_config"
|
||||
app:showAsAction="never" />
|
||||
<item
|
||||
android:id="@+id/del_duplicate_config"
|
||||
android:icon="@drawable/ic_delete_white_24dp"
|
||||
android:title="@string/title_del_duplicate_config"
|
||||
app:showAsAction="never" />
|
||||
<item
|
||||
android:id="@+id/del_invalid_config"
|
||||
android:icon="@drawable/ic_delete_white_24dp"
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
<?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/scan_code"
|
||||
android:icon="@drawable/ic_scan_black_24dp"
|
||||
android:title=""
|
||||
app:showAsAction="always" />
|
||||
<item
|
||||
android:id="@+id/select_photo"
|
||||
android:icon="@drawable/ic_image_photo"
|
||||
|
||||
7
V2rayNG/app/src/main/res/mipmap-anydpi-v26/ic_banner.xml
Normal file
7
V2rayNG/app/src/main/res/mipmap-anydpi-v26/ic_banner.xml
Normal file
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@color/ic_banner_background"/>
|
||||
<foreground>
|
||||
<inset android:drawable="@mipmap/ic_banner_foreground" android:inset="10%"/>
|
||||
</foreground>
|
||||
</adaptive-icon>
|
||||
@@ -2,4 +2,5 @@
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@color/ic_launcher_background"/>
|
||||
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
|
||||
<monochrome android:drawable="@mipmap/ic_launcher_foreground" />
|
||||
</adaptive-icon>
|
||||
BIN
V2rayNG/app/src/main/res/mipmap-xhdpi/ic_banner.png
Normal file
BIN
V2rayNG/app/src/main/res/mipmap-xhdpi/ic_banner.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.8 KiB |
BIN
V2rayNG/app/src/main/res/mipmap-xhdpi/ic_banner_foreground.png
Normal file
BIN
V2rayNG/app/src/main/res/mipmap-xhdpi/ic_banner_foreground.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.3 KiB |
@@ -10,7 +10,7 @@
|
||||
<string name="migration_fail">انتقال داده انجام نشد!</string>
|
||||
|
||||
<!-- Notifications -->
|
||||
<string name="notification_action_stop_v2ray">متوقف</string>
|
||||
<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>
|
||||
@@ -35,7 +35,7 @@
|
||||
<string name="menu_item_import_config_custom_local">پیکربندی سفارشی را به صورت محلی وارد کنید</string>
|
||||
<string name="menu_item_import_config_custom_url">پیکربندی سفارشی را از طریق نشانی اینترنتی وارد کنید</string>
|
||||
<string name="menu_item_import_config_custom_url_scan">نشانی اینترنتی اسکن پیکربندی سفارشی را وارد کنید</string>
|
||||
<string name="del_config_comfirm">تایید حذف؟</string>
|
||||
<string name="del_config_comfirm">حذف شود؟</string>
|
||||
<string name="server_lab_remarks">ملاحظات</string>
|
||||
<string name="server_lab_address">نشانی</string>
|
||||
<string name="server_lab_port">پورت</string>
|
||||
@@ -48,9 +48,7 @@
|
||||
<string name="server_lab_mode_type">حالت gRPC</string>
|
||||
<string name="server_lab_request_host">درخواست میزبان (میزبان/میزبان ws/ میزبان h2)/امنیت QUIC</string>
|
||||
<string name="server_lab_path">مسیر (مسیر ws/ مسیر h2) کلید QUIC/دانه kcp/نامخدمات gRPC</string>
|
||||
<string name="server_lab_stream_security">tls</string>
|
||||
<string name="server_lab_stream_fingerprint" translatable="false">uTLS</string>
|
||||
<string name="server_lab_stream_alpn" translatable="false">alpn</string>
|
||||
<string name="server_lab_stream_security">TLS</string>
|
||||
<string name="server_lab_allow_insecure">allowInsecure</string>
|
||||
<string name="server_lab_sni">SNI</string>
|
||||
<string name="server_lab_address3">نشانی</string>
|
||||
@@ -114,8 +112,8 @@
|
||||
<string name="title_pref_fake_dns_enabled">فعال کردن DNS جعلی</string>
|
||||
<string name="summary_pref_fake_dns_enabled">DNS محلی آدرس IP جعلی را برمیگرداند (سریعتر میباشد، اما ممکن است برای برخی از برنامهها کار نکند)</string>
|
||||
|
||||
<string name="title_pref_prefer_ipv6">IPv6 را ترجیح دهید</string>
|
||||
<string name="summary_pref_prefer_ipv6">نشانی و مسیرهای IPv6 را ترجیح دهید</string>
|
||||
<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>
|
||||
@@ -135,7 +133,7 @@
|
||||
<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="summary_pref_allow_insecure">هنگام استفاده از TLS، به طور پیشفرض allowInsecure فعال است</string>
|
||||
|
||||
<string name="title_pref_socks_port">پورت پروکسی SOCKS5</string>
|
||||
<string name="summary_pref_socks_port">پورت پروکسی SOCKS5</string>
|
||||
@@ -149,24 +147,29 @@
|
||||
<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">بهبودهای بازخورد یا اشکالات در گیتهاب</string>
|
||||
<string name="summary_pref_feedback">بازخورد یا گزارش اشکالات در گیتهاب</string>
|
||||
<string name="summary_pref_tg_group">عضویت در گروه تلگرام</string>
|
||||
<string name="toast_tg_app_not_found">برنامه تلگرام پیدا نشد</string>
|
||||
|
||||
<string name="title_pref_promotion">تبلیغات،</string>
|
||||
<string name="title_pref_promotion">تبلیغات</string>
|
||||
<string name="summary_pref_promotion">تبلیغات، برای جزئیات بیشتر کلیک کنید (کمک مالی کنید تا حذف شود)</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_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>
|
||||
@@ -178,8 +181,9 @@
|
||||
<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="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>
|
||||
@@ -207,6 +211,11 @@
|
||||
<item>خروجی گرفتن پیکربندی کامل در کلیپبورد</item>
|
||||
</string-array>
|
||||
|
||||
<string-array name="share_sub_method">
|
||||
<item>QRcode</item>
|
||||
<item>خروجی گرفتن در کلیپبورد</item>
|
||||
</string-array>
|
||||
|
||||
<string-array name="routing_tag">
|
||||
<item>نشانی اینترنتی یا آیپی پروکسی</item>
|
||||
<item>نشانی اینترنتی یا آیپی مستقیم</item>
|
||||
@@ -225,5 +234,7 @@
|
||||
<item>VPN</item>
|
||||
<item>فقط پروکسی</item>
|
||||
</string-array>
|
||||
<string name="import_subscription_success">اشتراک با موفقیت ذخیره شد</string>
|
||||
<string name="import_subscription_failure">ذخیره اشتراک ناموفق بود</string>
|
||||
|
||||
</resources>
|
||||
|
||||
15
V2rayNG/app/src/main/res/values-night/styles.xml
Normal file
15
V2rayNG/app/src/main/res/values-night/styles.xml
Normal file
@@ -0,0 +1,15 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
|
||||
<style name="AppThemeDayNight" parent="Theme.AppCompat.DayNight.DarkActionBar">
|
||||
|
||||
<item name="colorPrimary">@color/colorPrimary</item>
|
||||
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
|
||||
<item name="colorAccent">@color/colorAccent</item>
|
||||
<item name="colorMainBg">@color/colorBg</item>
|
||||
<item name="colorMainText">@color/colorText</item>
|
||||
<item name="android:statusBarColor">@color/colorPrimary</item>
|
||||
<item name="android:navigationBarColor">@color/colorPrimary</item>
|
||||
</style>
|
||||
|
||||
</resources>
|
||||
@@ -49,8 +49,8 @@
|
||||
<string name="server_lab_request_host">Запрос узла (WS/H2) / Шифрование QUIC</string>
|
||||
<string name="server_lab_path">Путь (WS/H2) / Ключ QUIC / Сид KCP / Сервис gRPC</string>
|
||||
<string name="server_lab_stream_security">TLS</string>
|
||||
<string name="server_lab_stream_fingerprint">uTLS</string>
|
||||
<string name="server_lab_stream_alpn" translatable="false">alpn</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_allow_insecure">Разрешать небезопасные</string>
|
||||
<string name="server_lab_sni">SNI</string>
|
||||
<string name="server_lab_address3">Адрес</string>
|
||||
@@ -61,6 +61,9 @@
|
||||
<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">PublicKey</string>
|
||||
<string name="server_lab_short_id" translatable="false">ShortId</string>
|
||||
<string name="server_lab_spider_x" translatable="false">SpiderX</string>
|
||||
<string name="toast_success">Успешно</string>
|
||||
<string name="toast_failure">Ошибка</string>
|
||||
<string name="toast_none_data">Ничего нет</string>
|
||||
@@ -149,6 +152,9 @@
|
||||
<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">Присоединиться к группе в Telegram</string>
|
||||
@@ -161,12 +167,14 @@
|
||||
<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_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>
|
||||
@@ -180,6 +188,7 @@
|
||||
<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>
|
||||
@@ -207,6 +216,11 @@
|
||||
<item>Экспорт всего профиля в буфер обмена</item>
|
||||
</string-array>
|
||||
|
||||
<string-array name="share_sub_method">
|
||||
<item>QR-код</item>
|
||||
<item>Экспорт в буфер обмена</item>
|
||||
</string-array>
|
||||
|
||||
<string-array name="routing_tag">
|
||||
<item>Проксируемые</item>
|
||||
<item>Прямые</item>
|
||||
@@ -225,5 +239,7 @@
|
||||
<item>VPN</item>
|
||||
<item>Только прокси</item>
|
||||
</string-array>
|
||||
<string name="import_subscription_success">Подписка импортирована</string>
|
||||
<string name="import_subscription_failure">Подписка не импортирована</string>
|
||||
|
||||
</resources>
|
||||
|
||||
@@ -94,10 +94,10 @@
|
||||
<string name="title_advanced">Cài đặt nâng cao</string>
|
||||
<string name="title_vpn_settings">Cài đặt VPN</string>
|
||||
<string name="title_pref_per_app_proxy">Proxy ứng dụng</string>
|
||||
<string name="summary_pref_per_app_proxy">Chung: Ứng dụng đã chọn sẽ kết nối Proxy, chưa lựa chọn sẽ kết nối trực tiếp; \nBỏ qua kết nối: Ứng dụng được chọn sẽ trực tiếp kết nối, không lựa chọn sẽ kết nối qua Proxy. \nLựa chọn để tự động chọn ứng dụng Proxy trong Menu.</string>
|
||||
<string name="summary_pref_per_app_proxy">- Chung: Ứng dụng đã chọn sẽ kết nối Proxy, chưa lựa chọn sẽ kết nối trực tiếp. \n- Bỏ qua kết nối: Ứng dụng được chọn sẽ trực tiếp kết nối, không lựa chọn sẽ kết nối qua Proxy. \n- Lựa chọn để tự động chọn ứng dụng Proxy trong Menu.</string>
|
||||
|
||||
<string name="title_pref_mux_enabled">Cho phép Mux</string>
|
||||
<string name="summary_pref_mux_enabled">Bật lên có thể làm tăng tốc độ mạng và chuyển mạng nhanh hơn!</string>
|
||||
<string name="summary_pref_mux_enabled">Bật lên có thể làm tăng tốc độ mạng và chuyển mạng nhanh hơn.</string>
|
||||
|
||||
<string name="title_pref_speed_enabled">Cho phép hiển thị tốc độ mạng</string>
|
||||
<string name="summary_pref_speed_enabled">Hiển thị tốc độ mạng hiện tại trên thanh thông báo.\nBiểu tượng trên thanh trạng thái có thể thay đổi tùy vào mức sử dụng.</string>
|
||||
@@ -129,10 +129,10 @@
|
||||
|
||||
<string name="title_pref_proxy_sharing_enabled">Cho phép kết nối từ mạng LAN</string>
|
||||
<string name="summary_pref_proxy_sharing_enabled">Các thiết bị khác có thể kết nối đến Proxy bởi địa chỉ IP thông qua Socks/HTTP, chỉ bật khi bạn tin tưởng kết nối đó.</string>
|
||||
<string name="toast_warning_pref_proxysharing_short">Cho phép kết nối từ mạng LAN, Đảm bảo rằng bạn tin tưởng kết nối hiện tại.</string>
|
||||
<string name="toast_warning_pref_proxysharing_short">Cho phép kết nối từ mạng LAN, đảm bảo rằng bạn tin tưởng kết nối hiện tại!</string>
|
||||
|
||||
<string name="title_pref_allow_insecure">Cho phép đặt lại Bỏ qua xác minh chứng chỉ</string>
|
||||
<string name="summary_pref_allow_insecure">Khi kết nối TLS, đặt cài đặt Bỏ qua xác minh chứng chỉ thành mặc định</string>
|
||||
<string name="summary_pref_allow_insecure">Khi kết nối TLS, đặt cài đặt Bỏ qua xác minh chứng chỉ thành mặc định.</string>
|
||||
|
||||
<string name="title_pref_socks_port">Cổng Proxy SOCKS5</string>
|
||||
<string name="summary_pref_socks_port">Cổng Proxy SOCKS5</string>
|
||||
@@ -144,7 +144,10 @@
|
||||
<string name="summary_pref_local_dns_port">Cổng DNS cục bộ</string>
|
||||
|
||||
<string name="title_pref_confirm_remove">Hiển thị thông báo xác nhận xoá cấu hình</string>
|
||||
<string name="summary_pref_confirm_remove">Hiển thị thông báo xác nhận xoá cấu hình khi bạn xoá một cấu hình!</string>
|
||||
<string name="summary_pref_confirm_remove">Hiển thị thông báo xác nhận xoá cấu hình khi bạn xoá một cấu hình.</string>
|
||||
|
||||
<string name="title_pref_start_scan_immediate">Start scanning immediately</string>
|
||||
<string name="summary_pref_start_scan_immediate">Open the camera to scan immediately at startup, otherwise you can choose to scan the code or select a photo in the toolbar</string>
|
||||
|
||||
<string name="title_pref_feedback">Phản hồi lỗi</string>
|
||||
<string name="summary_pref_feedback">Phản hồi cải tiến hoặc lỗi lên GitHub</string>
|
||||
@@ -158,12 +161,14 @@
|
||||
<string name="title_mode">Chế độ kết nối</string>
|
||||
<string name="title_mode_help">Nhấn vào đây nếu bạn cần trợ giúp!</string>
|
||||
<string name="title_language">Ngôn ngữ</string>
|
||||
<string name="title_ui_settings">UI settings</string>
|
||||
|
||||
<string name="title_logcat">Nhật ký hoạt động</string>
|
||||
<string name="logcat_copy">Sao chép nhật ký</string>
|
||||
<string name="logcat_clear">Xoá nhật ký</string>
|
||||
<string name="title_service_restart">Kết nối lại v2rayNG</string>
|
||||
<string name="title_del_all_config">Xoá tất cả cấu hình</string>
|
||||
<string name="title_del_duplicate_config">Delete duplicate config</string>
|
||||
<string name="title_del_invalid_config">Xoá cấu hình lỗi (Kiểm tra trước)</string>
|
||||
<string name="title_export_all">Xuất và sao chép tất cả cấu hình</string>
|
||||
<string name="title_sub_setting">Các gói đăng ký</string>
|
||||
@@ -177,6 +182,7 @@
|
||||
<string name="title_sort_by_test_results">Sắp xếp lại theo lần kiểm tra cuối cùng</string>
|
||||
<string name="title_filter_config">Lọc cấu hình theo các gói đăng ký</string>
|
||||
<string name="filter_config_all">Hiển thị tất cả các gói đăng ký</string>
|
||||
<string name="title_del_duplicate_config_count">Delete %d duplicate configurations</string>
|
||||
|
||||
<string name="tasker_start_service">Bắt đầu v2rayNG</string>
|
||||
<string name="tasker_setting_confirm">Xác nhận</string>
|
||||
@@ -191,12 +197,12 @@
|
||||
|
||||
<string name="connection_test_pending">Kiểm tra kết nối</string>
|
||||
<string name="connection_test_testing">Đang kiểm tra kết nối mạng...</string>
|
||||
<string name="connection_test_available">kiểm tra kết nối mạng thành công! Ping hiện tại là %d</string>
|
||||
<string name="connection_test_available">Kiểm tra kết nối mạng thành công! Ping hiện tại là %d</string>
|
||||
<string name="connection_test_error">Lỗi kết nối mạng, hãy thử đổi cấu hình hoặc kiểm tra lại! Mã lỗi: %s</string>
|
||||
<string name="connection_test_fail">Không có kết nối mạng!</string>
|
||||
<string name="connection_test_error_status_code">Mã lỗi: #%d</string>
|
||||
<string name="connection_connected">Đã kết nối, hãy nhấn vào đây để kiểm tra kết nối mạng!</string>
|
||||
<string name="connection_not_connected">Chưa kết nối, hãy thêm một cấu hình để kết nối!</string>
|
||||
<string name="connection_not_connected">Chưa kết nối, hãy chọn một cấu hình để kết nối!</string>
|
||||
|
||||
<string-array name="share_method">
|
||||
<item>Xuất ra mã QR (Chụp màn hình để lưu)</item>
|
||||
@@ -204,6 +210,11 @@
|
||||
<item>Sao chép thành cấu hình tùy chỉnh</item>
|
||||
</string-array>
|
||||
|
||||
<string-array name="share_sub_method">
|
||||
<item>Xuất ra mã QR (Chụp màn hình để lưu)</item>
|
||||
<item>Sao chép cấu hình này</item>
|
||||
</string-array>
|
||||
|
||||
<string-array name="routing_tag">
|
||||
<item>Proxy URL hoặc IP</item>
|
||||
<item>Direct URL hoặc IP</item>
|
||||
|
||||
@@ -44,10 +44,10 @@
|
||||
<string name="server_lab_network">传输协议(network)</string>
|
||||
<string name="server_lab_more_function">底层传输方式(transport)</string>
|
||||
<string name="server_lab_head_type">伪装类型(type)</string>
|
||||
<string name="server_lab_mode_type">gRPC 传输模式 (mode)</string>
|
||||
<string name="server_lab_mode_type">gRPC 传输模式(mode)</string>
|
||||
<string name="server_lab_request_host">伪装域名(host)(host/ws host/h2 host)/QUIC 加密方式</string>
|
||||
<string name="server_lab_path">path(ws path/h2 path)/QUIC 加密密钥/kcp seed/gRPC serviceName</string>
|
||||
<string name="server_lab_stream_security">传输层安全(tls)</string>
|
||||
<string name="server_lab_stream_security">传输层安全(TLS)</string>
|
||||
<string name="server_lab_allow_insecure">跳过证书验证(allowInsecure)</string>
|
||||
<string name="server_lab_sni">SNI</string>
|
||||
<string name="server_lab_address3">服务器地址</string>
|
||||
@@ -56,7 +56,7 @@
|
||||
<string name="server_lab_security3">加密方式</string>
|
||||
<string name="server_lab_id4">密码(可选)</string>
|
||||
<string name="server_lab_security4">用户名(可选)</string>
|
||||
<string name="server_lab_encryption">加密(encryption)</string>
|
||||
<string name="server_lab_encryption">加密方式(encryption)</string>
|
||||
<string name="server_lab_flow">流控(flow)</string>
|
||||
<string name="toast_success">成功</string>
|
||||
<string name="toast_failure">失败</string>
|
||||
@@ -146,6 +146,9 @@
|
||||
<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">加入Telegram Group</string>
|
||||
@@ -158,12 +161,14 @@
|
||||
<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_logcat">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>
|
||||
@@ -177,6 +182,7 @@
|
||||
<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>
|
||||
@@ -203,6 +209,12 @@
|
||||
<item>导出至剪贴板</item>
|
||||
<item>导出完整配置至剪贴板</item>
|
||||
</string-array>
|
||||
|
||||
<string-array name="share_sub_method">
|
||||
<item>二维码</item>
|
||||
<item>导出至剪贴板</item>
|
||||
</string-array>
|
||||
|
||||
share_method
|
||||
<string-array name="routing_tag">
|
||||
<item>代理的网址或IP</item>
|
||||
@@ -222,5 +234,7 @@
|
||||
<item>VPN</item>
|
||||
<item>仅代理</item>
|
||||
</string-array>
|
||||
<string name="import_subscription_success">订阅导入成功</string>
|
||||
<string name="import_subscription_failure">导入订阅失败</string>
|
||||
|
||||
</resources>
|
||||
|
||||
@@ -47,7 +47,7 @@
|
||||
<string name="server_lab_mode_type">gRPC 傳輸模式 (mode)</string>
|
||||
<string name="server_lab_request_host">要求主機 (host)(host/ws host/h2 host)/QUIC 加密方式</string>
|
||||
<string name="server_lab_path">path(ws path/h2 path)/QUIC 加密金鑰/kcp seed/gRPC serviceName</string>
|
||||
<string name="server_lab_stream_security">傳輸層安全 (tls)</string>
|
||||
<string name="server_lab_stream_security">傳輸層安全 (TLS)</string>
|
||||
<string name="server_lab_allow_insecure">跳過憑證驗證 (allowInsecure)</string>
|
||||
<string name="server_lab_sni">SNI</string>
|
||||
<string name="server_lab_address3">伺服器位址</string>
|
||||
@@ -146,6 +146,9 @@
|
||||
<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">加入 Telegram 群組</string>
|
||||
@@ -158,12 +161,14 @@
|
||||
<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_logcat">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>
|
||||
@@ -177,6 +182,7 @@
|
||||
<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">Delete %d duplicate configurations</string>
|
||||
|
||||
<string name="tasker_start_service">啟動服務</string>
|
||||
<string name="tasker_setting_confirm">確定</string>
|
||||
@@ -204,6 +210,11 @@
|
||||
<item>匯出完整組態至剪貼簿</item>
|
||||
</string-array>
|
||||
|
||||
<string-array name="share_sub_method">
|
||||
<item>QR Code</item>
|
||||
<item>匯出至剪貼簿</item>
|
||||
</string-array>
|
||||
|
||||
<string-array name="routing_tag">
|
||||
<item>Proxy URL 或 IP</item>
|
||||
<item>直接連線 URL 或 IP</item>
|
||||
|
||||
@@ -57,7 +57,7 @@
|
||||
<string-array name="streamsecurityxs" translatable="false">
|
||||
<item></item>
|
||||
<item>tls</item>
|
||||
<item>xtls</item>
|
||||
<item>reality</item>
|
||||
</string-array>
|
||||
|
||||
<string-array name="streamsecurity_utls" translatable="false">
|
||||
@@ -65,6 +65,12 @@
|
||||
<item>chrome</item>
|
||||
<item>firefox</item>
|
||||
<item>safari</item>
|
||||
<item>ios</item>
|
||||
<item>android</item>
|
||||
<item>edge</item>
|
||||
<item>360</item>
|
||||
<item>qq</item>
|
||||
<item>random</item>
|
||||
<item>randomized</item>
|
||||
</string-array>
|
||||
|
||||
@@ -110,12 +116,6 @@
|
||||
|
||||
<string-array name="flows" translatable="false">
|
||||
<item></item>
|
||||
<item>xtls-rprx-origin</item>
|
||||
<item>xtls-rprx-origin-udp443</item>
|
||||
<item>xtls-rprx-direct</item>
|
||||
<item>xtls-rprx-direct-udp443</item>
|
||||
<item>xtls-rprx-splice</item>
|
||||
<item>xtls-rprx-splice-udp443</item>
|
||||
<item>xtls-rprx-vision</item>
|
||||
<item>xtls-rprx-vision-udp443</item>
|
||||
</string-array>
|
||||
|
||||
4
V2rayNG/app/src/main/res/values/ic_banner_background.xml
Normal file
4
V2rayNG/app/src/main/res/values/ic_banner_background.xml
Normal file
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="ic_banner_background">#FFFFFF</color>
|
||||
</resources>
|
||||
@@ -30,7 +30,7 @@
|
||||
<string name="menu_item_import_config_manually_ss">Type manually[Shadowsocks]</string>
|
||||
<string name="menu_item_import_config_manually_socks">Type manually[Socks]</string>
|
||||
<string name="menu_item_import_config_manually_trojan">Type manually[Trojan]</string>
|
||||
<string name="menu_item_import_config_custom">custom config</string>
|
||||
<string name="menu_item_import_config_custom">Custom config</string>
|
||||
<string name="menu_item_import_config_custom_clipboard">Import custom config from Clipboard</string>
|
||||
<string name="menu_item_import_config_custom_local">Import custom config from locally</string>
|
||||
<string name="menu_item_import_config_custom_url">Import custom config from URL</string>
|
||||
@@ -48,9 +48,9 @@
|
||||
<string name="server_lab_mode_type">gRPC mode</string>
|
||||
<string name="server_lab_request_host">request host(host/ws host/h2 host)/QUIC security</string>
|
||||
<string name="server_lab_path">path(ws path/h2 path)/QUIC key/kcp seed/gRPC serviceName</string>
|
||||
<string name="server_lab_stream_security">tls</string>
|
||||
<string name="server_lab_stream_fingerprint" translatable="false">uTLS</string>
|
||||
<string name="server_lab_stream_alpn" translatable="false">alpn</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_allow_insecure">allowInsecure</string>
|
||||
<string name="server_lab_sni">SNI</string>
|
||||
<string name="server_lab_address3">address</string>
|
||||
@@ -61,6 +61,9 @@
|
||||
<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="toast_success">Success</string>
|
||||
<string name="toast_failure">Failure</string>
|
||||
<string name="toast_none_data">There is nothing</string>
|
||||
@@ -151,6 +154,9 @@
|
||||
<string name="title_pref_confirm_remove">Delete configuration file confirmation</string>
|
||||
<string name="summary_pref_confirm_remove">Whether to delete the configuration file requires a second confirmation by the user</string>
|
||||
|
||||
<string name="title_pref_start_scan_immediate">Start scanning immediately</string>
|
||||
<string name="summary_pref_start_scan_immediate">Open the camera to scan immediately at startup, otherwise you can choose to scan the code or select a photo in the toolbar</string>
|
||||
|
||||
<string name="title_pref_feedback">Feedback</string>
|
||||
<string name="summary_pref_feedback">Feedback enhancements or bugs to GitHub</string>
|
||||
<string name="summary_pref_tg_group">Join Telegram Group</string>
|
||||
@@ -163,12 +169,14 @@
|
||||
<string name="title_mode">Mode</string>
|
||||
<string name="title_mode_help">Click me for more help</string>
|
||||
<string name="title_language">Language</string>
|
||||
<string name="title_ui_settings">UI settings</string>
|
||||
|
||||
<string name="title_logcat">Logcat</string>
|
||||
<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_sub_setting">Subscription group setting</string>
|
||||
@@ -182,6 +190,7 @@
|
||||
<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="title_del_duplicate_config_count">Delete %d duplicate configurations</string>
|
||||
|
||||
<string name="tasker_start_service">Start Service</string>
|
||||
<string name="tasker_setting_confirm">Confirm</string>
|
||||
@@ -203,12 +212,20 @@
|
||||
<string name="connection_connected">Connected, tap to check connection</string>
|
||||
<string name="connection_not_connected">Not connected</string>
|
||||
|
||||
<string name="import_subscription_success">Subscription imported Successfully</string>
|
||||
<string name="import_subscription_failure">Import subscription failed</string>
|
||||
|
||||
<string-array name="share_method">
|
||||
<item>QRcode</item>
|
||||
<item>Export to clipboard</item>
|
||||
<item>Export full configuration to clipboard</item>
|
||||
</string-array>
|
||||
|
||||
<string-array name="share_sub_method">
|
||||
<item>QRcode</item>
|
||||
<item>Export to clipboard</item>
|
||||
</string-array>
|
||||
|
||||
<string-array name="routing_tag">
|
||||
<item>proxy URL or IP</item>
|
||||
<item>direct URL or IP</item>
|
||||
|
||||
@@ -6,6 +6,8 @@
|
||||
<item name="colorAccent">@color/colorAccent</item>
|
||||
<item name="colorMainBg">@color/colorBg</item>
|
||||
<item name="colorMainText">@color/colorText</item>
|
||||
<item name="android:statusBarColor">@color/colorPrimary</item>
|
||||
<item name="android:navigationBarColor">@color/colorPrimary</item>
|
||||
</style>
|
||||
|
||||
<style name="AppThemeDayNight.NoActionBar" parent="AppThemeDayNight">
|
||||
|
||||
@@ -124,6 +124,20 @@
|
||||
android:summary="%s"
|
||||
android:title="@string/title_mode" />
|
||||
|
||||
</PreferenceCategory>
|
||||
|
||||
<PreferenceCategory android:title="@string/title_ui_settings">
|
||||
|
||||
<CheckBoxPreference
|
||||
android:key="pref_confirm_remove"
|
||||
android:summary="@string/summary_pref_confirm_remove"
|
||||
android:title="@string/title_pref_confirm_remove" />
|
||||
|
||||
<CheckBoxPreference
|
||||
android:key="pref_start_scan_immediate"
|
||||
android:summary="@string/summary_pref_start_scan_immediate"
|
||||
android:title="@string/title_pref_start_scan_immediate" />
|
||||
|
||||
<ListPreference
|
||||
android:defaultValue="auto"
|
||||
android:entries="@array/language_select"
|
||||
@@ -132,10 +146,5 @@
|
||||
android:summary="%s"
|
||||
android:title="@string/title_language" />
|
||||
|
||||
<CheckBoxPreference
|
||||
android:key="pref_confirm_remove"
|
||||
android:summary="@string/summary_pref_confirm_remove"
|
||||
android:title="@string/title_pref_confirm_remove" />
|
||||
|
||||
</PreferenceCategory>
|
||||
</PreferenceScreen>
|
||||
|
||||
4
V2rayNG/app/src/pre_release/res/values/strings.xml
Normal file
4
V2rayNG/app/src/pre_release/res/values/strings.xml
Normal file
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="app_name" translatable="false">v2rayNG (PR)</string>
|
||||
</resources>
|
||||
@@ -1,31 +1,6 @@
|
||||
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||
|
||||
buildscript {
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
maven { url 'https://maven.google.com' }
|
||||
maven { url 'https://jitpack.io' }
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:7.2.1'
|
||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion"
|
||||
|
||||
// NOTE: Do not place your application dependencies here; they belong
|
||||
// in the individual module build.gradle files
|
||||
}
|
||||
}
|
||||
|
||||
allprojects {
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
maven { url 'https://maven.google.com' }
|
||||
maven { url 'https://jitpack.io' }
|
||||
jcenter()
|
||||
}
|
||||
}
|
||||
|
||||
task clean(type: Delete) {
|
||||
delete rootProject.buildDir
|
||||
}
|
||||
plugins {
|
||||
id 'com.android.application' version '7.4.2' apply false
|
||||
id 'com.android.library' version '7.4.2' apply false
|
||||
id 'org.jetbrains.kotlin.android' version '1.8.0' apply false
|
||||
}
|
||||
@@ -1,22 +1,10 @@
|
||||
## Project-wide Gradle settings.
|
||||
#
|
||||
# For more details on how to configure your build environment visit
|
||||
# http://www.gradle.org/docs/current/userguide/build_environment.html
|
||||
#
|
||||
# Specifies the JVM arguments used for the daemon process.
|
||||
# The setting is particularly useful for tweaking memory settings.
|
||||
# Default value: -Xmx1024m -XX:MaxPermSize=256m
|
||||
org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
|
||||
#
|
||||
# When configured, Gradle will run in incubating parallel mode.
|
||||
# This option should only be used with decoupled projects. More details, visit
|
||||
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
|
||||
# org.gradle.parallel=true
|
||||
#Fri Jun 02 14:08:42 CST 2017
|
||||
kotlinVersion=1.6.21
|
||||
buildToolsVer=31.0.0
|
||||
compileSdkVer=31
|
||||
targetSdkVer=31
|
||||
|
||||
buildToolsVer=33.0.2
|
||||
compileSdkVer=33
|
||||
targetSdkVer=33
|
||||
kotlin.incremental=true
|
||||
android.useAndroidX=true
|
||||
android.enableJetifier=true
|
||||
kotlin.code.style=official
|
||||
android.nonTransitiveRClass=true
|
||||
org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
|
||||
|
||||
@@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-all.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-all.zip
|
||||
|
||||
@@ -1 +1,17 @@
|
||||
pluginManagement {
|
||||
repositories {
|
||||
gradlePluginPortal()
|
||||
google()
|
||||
mavenCentral()
|
||||
}
|
||||
}
|
||||
dependencyResolutionManagement {
|
||||
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
jcenter()
|
||||
}
|
||||
}
|
||||
rootProject.name = "V2rayNG"
|
||||
include ':app'
|
||||
|
||||
Reference in New Issue
Block a user