Compare commits

..

111 Commits

Author SHA1 Message Date
2dust ec5f7245bf up 1.8.7 2023-09-25 11:20:21 +08:00
2dust a46b0d58eb Merge pull request #2500 from MrIbrahem/master
Update arrays.xml to support Arabic language
2023-09-06 07:21:10 +08:00
ibrahem Qasim d1262d169b Update arrays.xml 2023-09-01 17:39:16 +03:00
2dust 4a85c95b02 Merge pull request #2485 from y67h41/fix-/0-CIDR
Make /0 CIDR parsed as IP
2023-09-01 22:33:13 +08:00
2dust a368927b9f Merge pull request #2475 from user09283/master-1
Update strings.xml
2023-09-01 22:31:51 +08:00
2dust 80feb69af5 Merge pull request #2460 from MrIbrahem/master
Create arabic translations
2023-09-01 22:31:38 +08:00
2dust c09f0d5787 Merge pull request #2478 from yuhan6665/rprx-ui
A freshing new UI
2023-09-01 22:31:13 +08:00
y67h41 ff667546b8 Update ExampleUnitTest.kt 2023-08-24 17:55:12 +08:00
y67h41 6a255cdfa4 Make /0 CIDR be parsed as IP 2023-08-24 17:36:49 +08:00
yuhan6665 2b5784df6f Update UI: status bar icon color 2023-08-20 22:09:30 -04:00
user09283 a9665dd2d8 Update strings.xml 2023-08-19 13:11:14 +07:00
user09283 1e52877e93 Update strings.xml
Update Vietnamese language
2023-08-19 13:10:08 +07:00
yuhan6665 a7a70b448f Update UI: tweak fab color 2023-08-15 20:14:43 -04:00
yuhan6665 3e52aeb804 Update UI: custom font 2023-08-13 23:10:20 -04:00
yuhan6665 4b4f5d145b Update UI: a concept for launching icon 2023-08-13 15:01:07 -04:00
ibrahem Qasim 34d8329c8a Delete plurals.xml 2023-08-10 21:12:02 +03:00
ibrahem Qasim 2e7ae732aa Update strings.xml 2023-08-10 21:10:10 +03:00
ibrahem Qasim 424287e258 Rename s.xml to strings.xml 2023-08-10 13:26:25 +03:00
ibrahem Qasim 40d03bbb96 Delete strings.xml 2023-08-10 13:26:05 +03:00
ibrahem Qasim d768694445 Update s.xml 2023-08-10 13:18:58 +03:00
ibrahem Qasim 56c7f2ef69 Update s.xml 2023-08-10 13:13:15 +03:00
ibrahem Qasim 3839b0c59c Update s.xml 2023-08-10 13:07:36 +03:00
ibrahem Qasim 77cab14ae8 Create s.xml 2023-08-10 12:58:37 +03:00
ibrahem Qasim b0dbd4c7ca Create plurals.xml 2023-08-10 12:38:18 +03:00
ibrahem Qasim 50ca2e0e69 Create strings.xml 2023-08-10 12:36:43 +03:00
yuhan6665 0bf8beda94 Update UI: tweak after review 2023-08-09 21:38:20 -04:00
yuhan6665 4d334929c9 Update UI: complete change for dark mode 2023-08-07 14:32:41 -04:00
yuhan6665 ccfdf096f9 Update UI dark mode 2023-08-06 22:44:57 -04:00
yuhan6665 b4f2af2778 Update UI: header image 2023-08-06 21:53:14 -04:00
yuhan6665 32b9e4855c Update UI: clean up theme 2023-08-06 18:41:36 -04:00
yuhan6665 bdbce5147e Update UI: menu icons 2023-08-06 17:37:24 -04:00
yuhan6665 a4833506ae Update UI: launching icon 2023-08-06 16:55:29 -04:00
yuhan6665 d4e8072248 Update UI for white theme 2023-08-06 16:09:14 -04:00
yuhan6665 207ca93735 Fix lint by adding missing translation 2023-08-06 10:31:37 -04:00
2dust 99307ab8f0 up 1.8.6 2023-07-30 11:05:31 +08:00
2dust 1ec23a7b39 Merge pull request #2302 from dep4/fix/package-name
AppConfig: use current package name to receive broadcasts
2023-05-23 06:15:07 +08:00
Bruce Mills 685f9e220c AppConfig: use current package name to receive broadcasts 2023-05-21 15:54:25 -04:00
2dust e77b7eb52e Merge pull request #2290 from CUMOON/master
Correction of Persian translation
2023-05-21 08:35:38 +08:00
2dust 29014704e0 Merge pull request #2288 from hadi-norouzi/feature/tv
feature: add leanback for android tv compatiblity
2023-05-21 08:35:13 +08:00
Moon b3570d9c0b Correction of Persian translation 2023-05-17 12:49:41 +03:30
Hadi Norouzi 6e427cee82 feature: add leanback for android tv compatiblity 2023-05-16 11:33:29 +03:30
2dust 0a1b6d00d9 Merge pull request #2284 from solokot/master
RU: Improvements for google translation
2023-05-15 20:24:19 +08:00
solokot 00ffe66f36 RU: Improvements for google translation 2023-05-15 09:13:11 +03:00
2dust 63661fbdaa Merge pull request #2273 from hadi-norouzi/master
[Feature] separate package names for pre release and dev builds
2023-05-13 19:25:53 +08:00
Hadi Norouzi 4d4a4543c5 feature: import subscription from v2rayng://install-sub deeplink 2023-05-11 20:42:15 +03:30
Hadi Norouzi 2349805968 feature: separate package names for pre release and dev builds 2023-05-10 20:16:11 +03:30
2dust bdbd0b154e up 1.8.5 2023-05-07 15:35:42 +08:00
2dust 653137ec58 bug fixes 2023-05-07 15:34:11 +08:00
2dust 39f65850be Merge pull request #2258 from Amir-yazdanmanesh/master
Fix persian translation
2023-05-07 15:19:33 +08:00
2dust 2d8db97417 Merge branch 'master' into master 2023-05-07 15:19:25 +08:00
2dust 17a6f80798 Merge pull request #2255 from hadi-norouzi/feature/share_sub
Feature/share sub
2023-05-07 15:18:28 +08:00
2dust 381fe859ff Merge pull request #2254 from hadi-norouzi/master
Fix status bar and navigation bar, remove FooterView TouchHelper
2023-05-07 15:14:04 +08:00
am.yazdanmanesh e2bdf17b82 Fix persian translation 2023-05-05 21:54:18 +03:30
Hadi Norouzi d20afc6801 Share subscription implemented 2023-05-04 18:03:50 +03:30
Hadi Norouzi 54046a27e6 Fix fa strings and test layout direction 2023-05-04 17:04:35 +03:30
Hadi Norouzi 36c000d18a Fix status bar and navigation bar, remove FooterView TouchHelper 2023-05-04 16:34:41 +03:30
2dust 2f3c2cf4d5 up 1.8.4 2023-04-29 20:46:28 +08:00
2dust 892358d5d8 Merge pull request #2227 from AlirezaIvaz/master
Add themed icon support
2023-04-27 09:40:08 +08:00
Alireza Ivaz 6dced903cd Add themed icon support 2023-04-26 17:05:11 +03:30
2dust f8a98a426e Merge pull request #2213 from solokot/master
Updated Russian translation
2023-04-22 14:40:57 +08:00
solokot 011506e99f Updated Russian translation 2023-04-21 21:33:22 +03:00
2dust 966151d3fe bug fixes 2023-04-19 19:45:11 +08:00
2dust 247f4db77e up 1.8.3 2023-04-18 09:51:49 +08:00
2dust 9a661bc401 Use Google code scanner 2023-04-17 10:28:34 +08:00
2dust f5f1b3816c Refactor createQRCode 2023-04-16 19:58:51 +08:00
2dust 3971c9badc use Exception 2023-04-16 19:56:44 +08:00
2dust 390fbf046b Merge pull request #2130 from pouriaksrvi/master
[New Feature] UX enhancement - Reversed the order of server list
2023-04-06 08:28:54 +08:00
Programmer ec7ba59528 Bug Fix - Fixed the order of subscription import
Subscription server list should not be reversed when it is being imported. Fixed this problem.
2023-04-03 04:12:01 +03:30
Programmer 838941c6a4 Merge branch '2dust:master' into master 2023-04-03 04:00:52 +03:30
2dust a0ec764b64 Merge pull request #2142 from solokot/master
Updated Russian translation
2023-04-02 09:32:29 +08:00
2dust 26f1f7099b up 1.8.2 2023-04-01 18:08:11 +08:00
2dust 613e9ac8bf Add permission POST_NOTIFICATIONS and READ_MEDIA_IMAGES 2023-03-31 13:48:40 +08:00
2dust ea979f02b5 update gradle 2023-03-30 20:45:37 +08:00
2dust 383f55f809 update org.jetbrains.kotlin.android to 1.8.0 2023-03-30 13:21:36 +08:00
2dust ce61f177dc Adjust build gradle 2023-03-30 11:54:15 +08:00
2dust 6a64157537 update kotlinx-coroutines to 1.6.4 2023-03-29 16:15:43 +08:00
2dust de83302c8a update buildtoolsversion & targetSdkversion to 33 2023-03-29 15:54:35 +08:00
2dust 68553d3807 update kotlin version to 1.7.20 2023-03-29 15:25:54 +08:00
solokot 4cfe3394b1 Updated Russian translation 2023-03-29 09:35:28 +03:00
Programmer ae9aa75ba0 New Feature
Fixed incorrect default order of server list as described in the issue https://github.com/2dust/v2rayNG/issues/2116
The order was problematic for users who add new servers more frequently
2023-03-26 06:27:52 +00:00
2dust 3d7ed12d4b Added the function of deleting duplicate configurations 2023-03-25 21:16:21 +08:00
2dust f70be5bce9 routing domainMatcher is set to null 2023-03-25 18:10:29 +08:00
2dust 405667697e REALITY show false 2023-03-25 18:09:53 +08:00
2dust 3aeda7de81 Merge pull request #2122 from justlovediaodiao/master
fix a crash when adding custom config
2023-03-24 09:15:01 +08:00
yangxing 8a775d662a fix a crash when adding empty custom config 2023-03-23 22:25:07 +08:00
2dust ec0ccbca76 up 1.8.1 2023-03-21 17:17:51 +08:00
2dust 6cdcbb0096 Add REALITY share link 2023-03-21 10:04:03 +08:00
2dust 79e3881704 hide alpn in reality 2023-03-21 09:19:21 +08:00
2dust 4cf2d429f0 Adjust string 2023-03-21 09:18:46 +08:00
2dust fa51952bfa up 1.8.0 2023-03-10 11:17:25 +08:00
2dust b2c4c0a67e Adjust string 2023-03-10 10:00:34 +08:00
2dust cf2c58637b Merge pull request #2036 from yuhan6665/reality
Add reality and remove legacy xtls settings
2023-02-21 20:26:58 +08:00
yuhan6665 a2c262441e Add reality and remove legacy xtls settings
No sharing support for Reality yet
2023-02-20 21:25:33 -05:00
2dust 237be79680 up 1.7.38 2023-02-08 20:07:30 +08:00
2dust 441a64b8c6 add more fingerprint 2023-02-08 20:07:12 +08:00
2dust 2128488658 up 1.7.37 (rollback core to 1.7.2) 2023-02-03 12:04:39 +08:00
2dust d9dc8317f5 up 1.7.36 2023-02-03 10:47:28 +08:00
2dust 6eda2180e4 Update strings.xml 2023-02-03 10:42:24 +08:00
2dust 57a2f0bebf share link to add fingerprint 2023-02-03 10:11:40 +08:00
2dust 1fd3ccaa0c Merge pull request #1959 from user09283/patch-2
Update strings.xml
2023-01-29 20:23:23 +08:00
user09283 528597d0c7 Update strings.xml 2023-01-29 16:22:31 +07:00
user09283 37411e3e6b Update strings.xml 2023-01-29 16:13:38 +07:00
2dust 6d681b92aa up 1.7.35 2023-01-28 15:52:25 +08:00
2dust 73fae63905 Merge pull request #1942 from user09283/patch-1
Update strings.xml (vi)
2023-01-28 15:38:04 +08:00
2dust 216710974a Merge pull request #1920 from ysy950803/master
Replace colors hard code with values-night.
2023-01-28 15:37:53 +08:00
user09283 f3bbef7412 Update strings.xml 2023-01-26 14:03:47 +07:00
user09283 fa74d9c598 Update strings.xml 2023-01-24 21:00:51 +07:00
user09283 113364af26 Update strings.xml 2023-01-24 20:51:43 +07:00
Sylvester Yao 2990db305e Replace colors hard code with values-night. 2023-01-13 12:00:59 +08:00
2dust db8942fe5a up 1.7.34 2023-01-12 15:42:47 +08:00
2dust 402d63bd9a . 2023-01-12 14:35:38 +08:00
141 changed files with 1585 additions and 651 deletions
+46 -62
View File
@@ -1,16 +1,15 @@
apply plugin: 'com.android.application' plugins {
apply plugin: 'kotlin-android' id 'com.android.application'
id 'org.jetbrains.kotlin.android'
Properties props = new Properties() }
props.load(new FileInputStream(new File('local.properties')))
android { android {
compileSdkVersion Integer.parseInt("$compileSdkVer") compileSdkVersion Integer.parseInt("$compileSdkVer")
buildToolsVersion "$buildToolsVer" buildToolsVersion "$buildToolsVer"
compileOptions { compileOptions {
targetCompatibility = "8" sourceCompatibility = JavaVersion.VERSION_17
sourceCompatibility = "8" targetCompatibility = JavaVersion.VERSION_17
} }
defaultConfig { defaultConfig {
@@ -18,25 +17,8 @@ android {
minSdkVersion 21 minSdkVersion 21
targetSdkVersion Integer.parseInt("$targetSdkVer") targetSdkVersion Integer.parseInt("$targetSdkVer")
multiDexEnabled true multiDexEnabled true
versionCode 492 versionCode 521
versionName "1.7.31" versionName "1.8.7"
}
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'
}
}
} }
buildTypes { buildTypes {
@@ -44,23 +26,31 @@ android {
minifyEnabled false minifyEnabled false
zipAlignEnabled false zipAlignEnabled false
shrinkResources false shrinkResources false
if (props["sign"]) {
signingConfig signingConfigs.release
}
ndk.abiFilters 'x86', 'x86_64', 'armeabi-v7a', 'arm64-v8a' ndk.abiFilters 'x86', 'x86_64', 'armeabi-v7a', 'arm64-v8a'
// proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
} }
debug { debug {
minifyEnabled false minifyEnabled false
zipAlignEnabled false zipAlignEnabled false
shrinkResources false shrinkResources false
if (props["sign"]) {
signingConfig signingConfigs.release
}
ndk.abiFilters 'x86', 'x86_64', 'armeabi-v7a', 'arm64-v8a' 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 { sourceSets {
main { main {
jniLibs.srcDirs = ['libs'] jniLibs.srcDirs = ['libs']
@@ -97,7 +87,10 @@ android {
buildFeatures { buildFeatures {
viewBinding true viewBinding true
buildConfig true
} }
namespace 'com.v2ray.ang'
testNamespace 'com.v2ray.angTest'
} }
dependencies { dependencies {
@@ -107,45 +100,36 @@ dependencies {
// Androidx // Androidx
implementation 'androidx.constraintlayout:constraintlayout:2.1.4' implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
implementation 'androidx.legacy:legacy-support-v4:1.0.0' implementation 'androidx.legacy:legacy-support-v4:1.0.0'
implementation 'androidx.appcompat:appcompat:1.4.2' implementation 'androidx.appcompat:appcompat:1.6.1'
implementation 'com.google.android.material:material:1.6.1' implementation 'com.google.android.material:material:1.9.0'
implementation 'androidx.cardview:cardview:1.0.0' implementation 'androidx.cardview:cardview:1.0.0'
implementation 'androidx.preference:preference-ktx:1.2.0' implementation 'androidx.preference:preference-ktx:1.2.0'
implementation 'androidx.recyclerview:recyclerview:1.2.1' implementation 'androidx.recyclerview:recyclerview:1.3.0'
implementation 'androidx.fragment:fragment-ktx:1.5.2' implementation 'androidx.fragment:fragment-ktx:1.5.7'
implementation 'androidx.multidex:multidex:2.0.1' implementation 'androidx.multidex:multidex:2.0.1'
implementation 'androidx.viewpager2:viewpager2:1.1.0-beta01' implementation 'androidx.viewpager2:viewpager2:1.1.0-beta01'
// Androidx ktx // Androidx ktx
implementation 'androidx.activity:activity-ktx:1.5.1' implementation 'androidx.activity:activity-ktx:1.7.1'
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.1' implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.1'
implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.5.1' implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.6.1'
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.5.1' implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.6.1'
//kotlin //kotlin
implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlinVersion" implementation "org.jetbrains.kotlin:kotlin-reflect:1.8.0"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.2" implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.2" implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4"
implementation 'com.tencent:mmkv-static:1.2.12' implementation 'com.tencent:mmkv-static:1.2.15'
implementation 'com.google.code.gson:gson:2.8.9' implementation 'com.google.code.gson:gson:2.10.1'
implementation 'io.reactivex:rxjava:1.3.4' implementation 'io.reactivex:rxjava:1.3.8'
implementation 'io.reactivex:rxandroid:1.2.1' implementation 'io.reactivex:rxandroid:1.2.1'
implementation 'com.tbruyelle.rxpermissions:rxpermissions:0.9.4@aar' 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 'com.github.jorgecastilloprz:fabprogresscircle:1.01@aar'
implementation 'me.drakeet.support:toastcompat:1.1.0' implementation 'me.drakeet.support:toastcompat:1.1.0'
implementation 'com.blacksquircle.ui:editorkit:2.1.1' implementation 'com.blacksquircle.ui:editorkit:2.8.0'
implementation 'com.blacksquircle.ui:language-base:2.1.1' implementation 'com.blacksquircle.ui:language-base:2.8.0'
implementation 'com.blacksquircle.ui:language-json:2.1.1' implementation 'com.blacksquircle.ui:language-json:2.8.0'
} implementation 'io.github.g00fy2.quickie:quickie-bundled:1.6.0'
implementation 'com.google.zxing:core:3.5.1'
//buildscript { }
// repositories {
// google()
// mavenCentral()
// maven { url 'https://maven.google.com' }
// maven { url 'https://jitpack.io' }
// }
//}
Binary file not shown.
@@ -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);
}
}
@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<item name="app_name" type="string">v2rayNG (DEV)</item>
</resources>
+18 -7
View File
@@ -1,7 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools">
package="com.v2ray.ang">
<supports-screens <supports-screens
android:anyDensity="true" android:anyDensity="true"
@@ -10,8 +9,12 @@
android:largeScreens="true" android:largeScreens="true"
android:xlargeScreens="true"/> android:xlargeScreens="true"/>
<uses-sdk android:minSdkVersion="21" tools:overrideLibrary="com.blacksquircle.ui.editorkit"/>
<uses-feature android:name="android.hardware.camera" android:required="false"/> <uses-feature android:name="android.hardware.camera" android:required="false"/>
<uses-feature android:name="android.hardware.camera.autofocus" 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 --> <!-- https://developer.android.com/about/versions/11/privacy/package-visibility -->
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"
@@ -24,23 +27,30 @@
<uses-permission android:name="android.permission.CAMERA" /> <uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" /> <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<!-- <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> --> <!-- <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 <application
android:name=".AngApplication" android:name=".AngApplication"
android:allowBackup="true" android:allowBackup="true"
android:icon="@mipmap/ic_launcher" android:icon="@mipmap/ic_launcher"
android:banner="@mipmap/ic_banner"
android:label="@string/app_name" android:label="@string/app_name"
android:supportsRtl="true" android:supportsRtl="true"
android:theme="@style/AppThemeLight" android:theme="@style/AppThemeDayNight"
android:usesCleartextTraffic="true" android:usesCleartextTraffic="true"
tools:targetApi="m"> tools:targetApi="m">
<activity <activity
android:exported="true"
android:name=".ui.MainActivity" android:name=".ui.MainActivity"
android:launchMode="singleTask"> android:exported="true"
android:launchMode="singleTask"
android:theme="@style/AppThemeDayNight.NoActionBar">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" /> <category android:name="android.intent.category.LAUNCHER" />
<category android:name="android.intent.category.LEANBACK_LAUNCHER"/>
</intent-filter> </intent-filter>
<intent-filter> <intent-filter>
<action android:name="android.service.quicksettings.action.QS_TILE_PREFERENCES" /> <action android:name="android.service.quicksettings.action.QS_TILE_PREFERENCES" />
@@ -105,8 +115,9 @@
<action android:name="android.intent.action.VIEW"/> <action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.BROWSABLE"/> <category android:name="android.intent.category.BROWSABLE"/>
<category android:name="android.intent.category.DEFAULT"/> <category android:name="android.intent.category.DEFAULT"/>
<data android:scheme="v2rayng" <data android:scheme="v2rayng"/>
android:host="install-config" /> <data android:host="install-config"/>
<data android:host="install-sub"/>
</intent-filter> </intent-filter>
</activity> </activity>
@@ -5,7 +5,7 @@ package com.v2ray.ang
* App Config Const * App Config Const
*/ */
object AppConfig { object AppConfig {
const val ANG_PACKAGE = "com.v2ray.ang" const val ANG_PACKAGE = BuildConfig.APPLICATION_ID
const val DIR_ASSETS = "assets" const val DIR_ASSETS = "assets"
// legacy // legacy
@@ -39,6 +39,7 @@ object AppConfig {
const val PREF_PER_APP_PROXY_SET = "pref_per_app_proxy_set" const val PREF_PER_APP_PROXY_SET = "pref_per_app_proxy_set"
const val PREF_BYPASS_APPS = "pref_bypass_apps" const val PREF_BYPASS_APPS = "pref_bypass_apps"
const val PREF_CONFIRM_REMOVE = "pref_confirm_remove" 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 HTTP_PROTOCOL: String = "http://"
const val HTTPS_PROTOCOL: String = "https://" const val HTTPS_PROTOCOL: String = "https://"
@@ -29,7 +29,7 @@ data class V2rayConfig(
const val DEFAULT_NETWORK = "tcp" const val DEFAULT_NETWORK = "tcp"
const val TLS = "tls" const val TLS = "tls"
const val XTLS = "xtls" const val REALITY = "reality"
const val HTTP = "http" const val HTTP = "http"
} }
@@ -130,7 +130,7 @@ data class V2rayConfig(
var httpSettings: HttpSettingsBean? = null, var httpSettings: HttpSettingsBean? = null,
var tlsSettings: TlsSettingsBean? = null, var tlsSettings: TlsSettingsBean? = null,
var quicSettings: QuicSettingBean? = null, var quicSettings: QuicSettingBean? = null,
var xtlsSettings: TlsSettingsBean? = null, var realitySettings: TlsSettingsBean? = null,
var grpcSettings: GrpcSettingsBean? = null, var grpcSettings: GrpcSettingsBean? = null,
val dsSettings: Any? = null, val dsSettings: Any? = null,
val sockopt: Any? = null val sockopt: Any? = null
@@ -189,7 +189,12 @@ data class V2rayConfig(
val fingerprint: String? = null, val fingerprint: String? = null,
val certificates: List<Any>? = null, val certificates: List<Any>? = null,
val disableSystemRoot: Boolean? = 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", data class QuicSettingBean(var security: String = "none",
var key: String = "", var key: String = "",
@@ -265,20 +270,24 @@ data class V2rayConfig(
return sni 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 security = streamSecurity
val tlsSetting = TlsSettingsBean( val tlsSetting = TlsSettingsBean(
allowInsecure = allowInsecure, allowInsecure = allowInsecure,
serverName = sni, serverName = sni,
fingerprint = fingerprint, fingerprint = fingerprint,
alpn = if (alpns.isNullOrEmpty()) null else alpns.split(",").map { it.trim() }.filter { it.isNotEmpty() } alpn = if (alpns.isNullOrEmpty()) null else alpns.split(",").map { it.trim() }.filter { it.isNotEmpty() },
publicKey = publicKey,
shortId = shortId,
spiderX = spiderX
) )
if (security == TLS) { if (security == TLS) {
tlsSettings = tlsSetting tlsSettings = tlsSetting
xtlsSettings = null realitySettings = null
} else if (security == XTLS) { } else if (security == REALITY) {
tlsSettings = null 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 var poolSize: Int = 10000) // roughly 10 times smaller than total ip pool
fun getProxyOutbound(): OutboundBean? { fun getProxyOutbound(): OutboundBean? {
outbounds.forEach { outbound -> outbounds?.forEach { outbound ->
EConfigType.values().forEach { EConfigType.values().forEach {
if (outbound.protocol.equals(it.name, true)) { if (outbound.protocol.equals(it.name, true)) {
return outbound return outbound
@@ -13,4 +13,5 @@ data class VmessQRCode(var v: String = "",
var path: String = "", var path: String = "",
var tls: String = "", var tls: String = "",
var sni: 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.Context
import android.content.Intent import android.content.Intent
import android.text.TextUtils import android.text.TextUtils
import com.google.zxing.WriterException
import com.tencent.mmkv.MMKV import com.tencent.mmkv.MMKV
import com.v2ray.ang.AppConfig import com.v2ray.ang.AppConfig
import com.v2ray.ang.service.V2RayServiceManager import com.v2ray.ang.service.V2RayServiceManager
@@ -34,7 +33,7 @@ class TaskerReceiver : BroadcastReceiver() {
} else { } else {
Utils.stopVService(context) Utils.stopVService(context)
} }
} catch (e: WriterException) { } catch (e: Exception) {
e.printStackTrace() e.printStackTrace()
} }
} }
@@ -38,12 +38,18 @@ class WidgetProvider : AppWidgetProvider() {
}) })
remoteViews.setOnClickPendingIntent(R.id.layout_switch, pendingIntent) remoteViews.setOnClickPendingIntent(R.id.layout_switch, pendingIntent)
if (isRunning) { if (isRunning) {
if (!Utils.getDarkModeStatus(context)) {
remoteViews.setInt(R.id.image_switch, "setImageResource", R.drawable.ic_stat_name)
}
remoteViews.setInt( remoteViews.setInt(
R.id.layout_switch, R.id.layout_switch,
"setBackgroundResource", "setBackgroundResource",
R.drawable.ic_rounded_corner_theme R.drawable.ic_rounded_corner_active
) )
} else { } else {
if (!Utils.getDarkModeStatus(context)) {
remoteViews.setInt(R.id.image_switch, "setImageResource", R.drawable.ic_stat_name_black)
}
remoteViews.setInt( remoteViews.setInt(
R.id.layout_switch, R.id.layout_switch,
"setBackgroundResource", "setBackgroundResource",
@@ -2,12 +2,10 @@ package com.v2ray.ang.ui
import android.content.Context import android.content.Context
import android.os.Build import android.os.Build
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import android.view.MenuItem import android.view.MenuItem
import androidx.annotation.RequiresApi import androidx.annotation.RequiresApi
import androidx.appcompat.app.AppCompatActivity
import com.v2ray.ang.util.MyContextWrapper import com.v2ray.ang.util.MyContextWrapper
import com.v2ray.ang.R
import com.v2ray.ang.util.Utils import com.v2ray.ang.util.Utils
abstract class BaseActivity : AppCompatActivity() { abstract class BaseActivity : AppCompatActivity() {
@@ -19,27 +17,6 @@ abstract class BaseActivity : AppCompatActivity() {
else -> super.onOptionsItemSelected(item) else -> super.onOptionsItemSelected(item)
} }
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
checkDarkMode()
}
private fun checkDarkMode() {
if (Utils.getDarkModeStatus(this)) {
if (this.javaClass.simpleName == "MainActivity") {
setTheme(R.style.AppThemeDark_NoActionBar)
} else {
setTheme(R.style.AppThemeDark)
}
} else {
if (this.javaClass.simpleName == "MainActivity") {
setTheme(R.style.AppThemeLight_NoActionBar)
} else {
setTheme(R.style.AppThemeLight)
}
}
}
@RequiresApi(Build.VERSION_CODES.N) @RequiresApi(Build.VERSION_CODES.N)
override fun attachBaseContext(newBase: Context?) { override fun attachBaseContext(newBase: Context?) {
val context = newBase?.let { val context = newBase?.let {
@@ -47,7 +24,4 @@ abstract class BaseActivity : AppCompatActivity() {
} }
super.attachBaseContext(context) super.attachBaseContext(context)
} }
}
}
@@ -14,6 +14,7 @@ import android.text.TextUtils
import android.view.KeyEvent import android.view.KeyEvent
import com.v2ray.ang.AppConfig import com.v2ray.ang.AppConfig
import android.content.res.ColorStateList import android.content.res.ColorStateList
import android.os.Build
import com.google.android.material.navigation.NavigationView import com.google.android.material.navigation.NavigationView
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.core.view.GravityCompat import androidx.core.view.GravityCompat
@@ -24,6 +25,7 @@ import android.widget.Toast
import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.result.contract.ActivityResultContracts
import androidx.activity.viewModels import androidx.activity.viewModels
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import androidx.core.view.WindowCompat
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import com.tencent.mmkv.MMKV import com.tencent.mmkv.MMKV
import com.v2ray.ang.AppConfig.ANG_PACKAGE import com.v2ray.ang.AppConfig.ANG_PACKAGE
@@ -64,6 +66,11 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
setContentView(view) setContentView(view)
title = getString(R.string.title_server) title = getString(R.string.title_server)
setSupportActionBar(binding.toolbar) setSupportActionBar(binding.toolbar)
if (!Utils.getDarkModeStatus(this)) {
WindowCompat.getInsetsController(window, window.decorView).apply {
isAppearanceLightStatusBars = true
}
}
binding.fab.setOnClickListener { binding.fab.setOnClickListener {
if (mainViewModel.isRunning.value == true) { if (mainViewModel.isRunning.value == true) {
@@ -107,6 +114,15 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
setupViewModel() setupViewModel()
copyAssets() copyAssets()
migrateLegacy() 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() { private fun setupViewModel() {
@@ -121,11 +137,17 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
mainViewModel.isRunning.observe(this) { isRunning -> mainViewModel.isRunning.observe(this) { isRunning ->
adapter.isRunning = isRunning adapter.isRunning = isRunning
if (isRunning) { if (isRunning) {
binding.fab.backgroundTintList = ColorStateList.valueOf(ContextCompat.getColor(this, R.color.colorSelected)) if (!Utils.getDarkModeStatus(this)) {
binding.fab.setImageResource(R.drawable.ic_stat_name)
}
binding.fab.backgroundTintList = ColorStateList.valueOf(ContextCompat.getColor(this, R.color.color_fab_orange))
setTestState(getString(R.string.connection_connected)) setTestState(getString(R.string.connection_connected))
binding.layoutTest.isFocusable = true binding.layoutTest.isFocusable = true
} else { } else {
binding.fab.backgroundTintList = ColorStateList.valueOf(ContextCompat.getColor(this, R.color.colorUnselected)) if (!Utils.getDarkModeStatus(this)) {
binding.fab.setImageResource(R.drawable.ic_stat_name)
}
binding.fab.backgroundTintList = ColorStateList.valueOf(ContextCompat.getColor(this, R.color.color_fab_grey))
setTestState(getString(R.string.connection_not_connected)) setTestState(getString(R.string.connection_not_connected))
binding.layoutTest.isFocusable = false binding.layoutTest.isFocusable = false
} }
@@ -297,7 +319,14 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
.show() .show()
true 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 -> { R.id.del_invalid_config -> {
AlertDialog.Builder(this).setMessage(R.string.del_config_comfirm) AlertDialog.Builder(this).setMessage(R.string.del_config_comfirm)
.setPositiveButton(android.R.string.ok) { _, _ -> .setPositiveButton(android.R.string.ok) { _, _ ->
@@ -541,8 +570,13 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
* read content from uri * read content from uri
*/ */
private fun readContentFromUri(uri: 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) RxPermissions(this)
.request(Manifest.permission.READ_EXTERNAL_STORAGE) .request(permission)
.subscribe { .subscribe {
if (it) { if (it) {
try { try {
@@ -591,13 +625,14 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
// } // }
override fun onKeyDown(keyCode: Int, event: KeyEvent): Boolean { 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) moveTaskToBack(false)
return true return true
} }
return super.onKeyDown(keyCode, event) return super.onKeyDown(keyCode, event)
} }
fun showCircle() { fun showCircle() {
binding.fabProgressCircle.show() binding.fabProgressCircle.show()
} }
@@ -620,11 +655,13 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
} }
} }
@Deprecated("Deprecated in Java")
override fun onBackPressed() { override fun onBackPressed() {
if (binding.drawerLayout.isDrawerOpen(GravityCompat.START)) { if (binding.drawerLayout.isDrawerOpen(GravityCompat.START)) {
binding.drawerLayout.closeDrawer(GravityCompat.START) binding.drawerLayout.closeDrawer(GravityCompat.START)
} else { } else {
super.onBackPressed() //super.onBackPressed()
onBackPressedDispatcher.onBackPressed()
} }
} }
@@ -228,7 +228,7 @@ class MainRecyclerAdapter(val activity: MainActivity) : RecyclerView.Adapter<Mai
BaseViewHolder(itemMainBinding.root), ItemTouchHelperViewHolder BaseViewHolder(itemMainBinding.root), ItemTouchHelperViewHolder
class FooterViewHolder(val itemFooterBinding: ItemRecyclerFooterBinding) : class FooterViewHolder(val itemFooterBinding: ItemRecyclerFooterBinding) :
BaseViewHolder(itemFooterBinding.root), ItemTouchHelperViewHolder BaseViewHolder(itemFooterBinding.root)
override fun onItemDismiss(position: Int) { override fun onItemDismiss(position: Int) {
val guid = mActivity.mainViewModel.serversCache.getOrNull(position)?.guid ?: return val guid = mActivity.mainViewModel.serversCache.getOrNull(position)?.guid ?: return
@@ -3,57 +3,62 @@ package com.v2ray.ang.ui
import android.Manifest import android.Manifest
import android.app.Activity import android.app.Activity
import android.os.Bundle import android.os.Bundle
import com.google.zxing.Result
import me.dm7.barcodescanner.zxing.ZXingScannerView
import android.content.Intent import android.content.Intent
import android.graphics.BitmapFactory import android.graphics.BitmapFactory
import android.os.Build
import android.view.Menu import android.view.Menu
import android.view.MenuItem import android.view.MenuItem
import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.result.contract.ActivityResultContracts
import com.google.zxing.BarcodeFormat
import com.tbruyelle.rxpermissions.RxPermissions import com.tbruyelle.rxpermissions.RxPermissions
import com.tencent.mmkv.MMKV
import com.v2ray.ang.AppConfig
import com.v2ray.ang.R import com.v2ray.ang.R
import com.v2ray.ang.extension.toast import com.v2ray.ang.extension.toast
import com.v2ray.ang.util.MmkvManager
import com.v2ray.ang.util.QRCodeDecoder 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?) { public override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
mScannerView = ZXingScannerView(this) // Programmatically initialize the scanner view
mScannerView?.setAutoFocus(true) if (settingsStorage?.decodeBool(AppConfig.PREF_START_SCAN_IMMEDIATE) == true) {
val formats = ArrayList<BarcodeFormat>() launchScan()
formats.add(BarcodeFormat.QR_CODE) }
mScannerView?.setFormats(formats)
setContentView(mScannerView) // Set the scanner view as the content view
supportActionBar?.setDisplayHomeAsUpEnabled(true) supportActionBar?.setDisplayHomeAsUpEnabled(true)
} }
public override fun onResume() { public override fun onResume() {
super.onResume() super.onResume()
mScannerView!!.setResultHandler(this) // Register ourselves as a handler for scan results.
mScannerView!!.startCamera() // Start camera on resume
} }
public override fun onPause() { public override fun onPause() {
super.onPause() super.onPause()
mScannerView!!.stopCamera() // Stop camera on pause
} }
override fun handleResult(rawResult: Result) { private fun launchScan(){
// Do something with the result here scanQrCode.launch(
// Log.v(FragmentActivity.TAG, rawResult.text) // Prints scan results ScannerConfig.build {
// Log.v(FragmentActivity.TAG, rawResult.barcodeFormat.toString()) // Prints the scan format (qrcode, pdf417 etc.) 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) private fun handleResult(result: QRResult) {
if (result is QRResult.QRSuccess ) {
// If you would like to resume scanning, call this method below: finished(result.content.rawValue)
// mScannerView!!.resumeCameraPreview(this) } else {
finish()
}
} }
private fun finished(text: String) { private fun finished(text: String) {
@@ -69,19 +74,28 @@ class ScannerActivity : BaseActivity(), ZXingScannerView.ResultHandler {
} }
override fun onOptionsItemSelected(item: MenuItem) = when (item.itemId) { override fun onOptionsItemSelected(item: MenuItem) = when (item.itemId) {
R.id.scan_code -> {
launchScan()
true
}
R.id.select_photo -> { 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) RxPermissions(this)
.request(Manifest.permission.READ_EXTERNAL_STORAGE) .request(permission)
.subscribe { .subscribe {
if (it) { if (it) {
try { try {
showFileChooser() showFileChooser()
} catch (e: Exception) { } catch (e: Exception) {
e.printStackTrace() e.printStackTrace()
} }
} else } else
toast(R.string.toast_permission_denied) toast(R.string.toast_permission_denied)
} }
true true
} }
else -> super.onOptionsItemSelected(item) 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.ServerConfig
import com.v2ray.ang.dto.V2rayConfig import com.v2ray.ang.dto.V2rayConfig
import com.v2ray.ang.dto.V2rayConfig.Companion.DEFAULT_PORT 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.extension.toast
import com.v2ray.ang.util.MmkvManager import com.v2ray.ang.util.MmkvManager
import com.v2ray.ang.util.MmkvManager.ID_MAIN 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_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_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 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 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 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_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: 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 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_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 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 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?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
@@ -105,6 +116,7 @@ class ServerActivity : BaseActivity() {
EConfigType.SOCKS -> setContentView(R.layout.activity_server_socks) EConfigType.SOCKS -> setContentView(R.layout.activity_server_socks)
EConfigType.VLESS -> setContentView(R.layout.activity_server_vless) EConfigType.VLESS -> setContentView(R.layout.activity_server_vless)
EConfigType.TROJAN -> setContentView(R.layout.activity_server_trojan) EConfigType.TROJAN -> setContentView(R.layout.activity_server_trojan)
else -> setContentView(R.layout.activity_server_vmess)
} }
sp_network?.onItemSelectedListener = object : AdapterView.OnItemSelectedListener { sp_network?.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) { override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
@@ -126,6 +138,38 @@ class ServerActivity : BaseActivity() {
// do nothing // 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) { if (config != null) {
bindingServer(config) bindingServer(config)
} else { } else {
@@ -169,13 +213,11 @@ class ServerActivity : BaseActivity() {
val streamSecurity = Utils.arrayFind(streamSecuritys, streamSetting.security) val streamSecurity = Utils.arrayFind(streamSecuritys, streamSetting.security)
if (streamSecurity >= 0) { if (streamSecurity >= 0) {
sp_stream_security?.setSelection(streamSecurity) sp_stream_security?.setSelection(streamSecurity)
(streamSetting.tlsSettings?: streamSetting.xtlsSettings)?.let { tlsSetting -> (streamSetting.tlsSettings?: streamSetting.realitySettings)?.let { tlsSetting ->
val allowinsecure = Utils.arrayFind(allowinsecures, tlsSetting.allowInsecure.toString()) container_sni?.visibility = View.VISIBLE
if (allowinsecure >= 0) { container_fingerprint?.visibility = View.VISIBLE
sp_allow_insecure?.setSelection(allowinsecure) container_alpn?.visibility = View.VISIBLE
}
et_sni?.text = Utils.getEditable(tlsSetting.serverName) et_sni?.text = Utils.getEditable(tlsSetting.serverName)
tlsSetting.fingerprint?.let { tlsSetting.fingerprint?.let {
val utlsIndex = Utils.arrayFind(uTlsItems, tlsSetting.fingerprint) val utlsIndex = Utils.arrayFind(uTlsItems, tlsSetting.fingerprint)
sp_stream_fingerprint?.setSelection(utlsIndex) sp_stream_fingerprint?.setSelection(utlsIndex)
@@ -184,7 +226,33 @@ class ServerActivity : BaseActivity() {
val alpnIndex = Utils.arrayFind(alpns, Utils.removeWhiteSpace(tlsSetting.alpn.joinToString())!!) val alpnIndex = Utils.arrayFind(alpns, Utils.removeWhiteSpace(tlsSetting.alpn.joinToString())!!)
sp_stream_alpn?.setSelection(alpnIndex) 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) val network = Utils.arrayFind(networks, streamSetting.network)
@@ -304,12 +372,6 @@ class ServerActivity : BaseActivity() {
} }
} else if (config.configType == EConfigType.TROJAN) { } else if (config.configType == EConfigType.TROJAN) {
server.password = et_id.text.toString().trim() 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 sniField = et_sni?.text?.toString()?.trim() ?: return
val allowInsecureField = sp_allow_insecure?.selectedItemPosition ?: return val allowInsecureField = sp_allow_insecure?.selectedItemPosition ?: return
val streamSecurity = sp_stream_security?.selectedItemPosition ?: return val streamSecurity = sp_stream_security?.selectedItemPosition ?: return
var utlsIndex = sp_stream_fingerprint?.selectedItemPosition ?: return val utlsIndex = sp_stream_fingerprint?.selectedItemPosition ?: return
var alpnIndex = sp_stream_alpn?.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( var sni = streamSetting.populateTransportSettings(
transport = networks[network], transport = networks[network],
@@ -344,18 +409,32 @@ class ServerActivity : BaseActivity() {
allowinsecures[allowInsecureField].toBoolean() 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> { private fun transportTypes(network: String?): Array<out String> {
return if (network == "tcp") { return when (network) {
tcpTypes "tcp" -> {
} else if (network == "kcp" || network == "quic") { tcpTypes
kcpAndQuicTypes }
} else if (network == "grpc") { "kcp", "quic" -> {
grpcModes kcpAndQuicTypes
} else { }
arrayOf("---") "grpc" -> {
grpcModes
}
else -> {
arrayOf("---")
}
} }
} }
@@ -364,7 +443,7 @@ class ServerActivity : BaseActivity() {
*/ */
private fun deleteServer(): Boolean { private fun deleteServer(): Boolean {
if (editGuid.isNotEmpty()) { 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) { if (settingsStorage?.decodeBool(AppConfig.PREF_CONFIRM_REMOVE) == true) {
AlertDialog.Builder(this).setMessage(R.string.del_config_comfirm) AlertDialog.Builder(this).setMessage(R.string.del_config_comfirm)
.setPositiveButton(android.R.string.ok) { _, _ -> .setPositiveButton(android.R.string.ok) { _, _ ->
@@ -6,6 +6,7 @@ import android.view.Menu
import android.view.MenuItem import android.view.MenuItem
import android.widget.Toast import android.widget.Toast
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import com.blacksquircle.ui.editorkit.utils.EditorTheme
import com.blacksquircle.ui.language.json.JsonLanguage import com.blacksquircle.ui.language.json.JsonLanguage
import com.google.gson.* import com.google.gson.*
import com.tencent.mmkv.MMKV import com.tencent.mmkv.MMKV
@@ -38,6 +39,9 @@ class ServerCustomConfigActivity : BaseActivity() {
setContentView(view) setContentView(view)
title = getString(R.string.title_server) title = getString(R.string.title_server)
if (!Utils.getDarkModeStatus(this)) {
binding.editor.colorScheme = EditorTheme.INTELLIJ_LIGHT
}
binding.editor.language = JsonLanguage() binding.editor.language = JsonLanguage()
val config = MmkvManager.decodeServerConfig(editGuid) val config = MmkvManager.decodeServerConfig(editGuid)
if (config != null) { if (config != null) {
@@ -2,20 +2,34 @@ package com.v2ray.ang.ui
import android.content.Intent import android.content.Intent
import android.graphics.Color import android.graphics.Color
import android.text.TextUtils
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import android.view.ViewGroup import android.view.ViewGroup
import androidx.appcompat.app.AlertDialog
import com.google.gson.Gson import com.google.gson.Gson
import com.tencent.mmkv.MMKV import com.tencent.mmkv.MMKV
import com.v2ray.ang.R import com.v2ray.ang.R
import com.v2ray.ang.databinding.ItemQrcodeBinding
import com.v2ray.ang.databinding.ItemRecyclerSubSettingBinding 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.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 var mActivity: SubSettingActivity = activity
private val subStorage by lazy { MMKV.mmkvWithID(MmkvManager.ID_SUB, MMKV.MULTI_PROCESS_MODE) } 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 getItemCount() = mActivity.subscriptions.size
override fun onBindViewHolder(holder: MainViewHolder, position: Int) { override fun onBindViewHolder(holder: MainViewHolder, position: Int) {
@@ -31,7 +45,8 @@ class SubSettingRecyclerAdapter(val activity: SubSettingActivity) : RecyclerView
holder.itemView.setBackgroundColor(Color.TRANSPARENT) holder.itemView.setBackgroundColor(Color.TRANSPARENT)
holder.itemSubSettingBinding.layoutEdit.setOnClickListener { holder.itemSubSettingBinding.layoutEdit.setOnClickListener {
mActivity.startActivity(Intent(mActivity, SubEditActivity::class.java) mActivity.startActivity(
Intent(mActivity, SubEditActivity::class.java)
.putExtra("subId", subId) .putExtra("subId", subId)
) )
} }
@@ -40,11 +55,51 @@ class SubSettingRecyclerAdapter(val activity: SubSettingActivity) : RecyclerView
subStorage?.encode(subId, Gson().toJson(subItem)) subStorage?.encode(subId, Gson().toJson(subItem))
notifyItemChanged(position) 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 { 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.text.TextUtils
import android.view.Menu import android.view.Menu
import android.view.MenuItem import android.view.MenuItem
import com.google.zxing.WriterException
import com.tencent.mmkv.MMKV import com.tencent.mmkv.MMKV
import com.v2ray.ang.AppConfig import com.v2ray.ang.AppConfig
import com.v2ray.ang.databinding.ActivityTaskerBinding import com.v2ray.ang.databinding.ActivityTaskerBinding
@@ -65,7 +64,7 @@ class TaskerActivity : BaseActivity() {
listview?.setItemChecked(pos, true) listview?.setItemChecked(pos, true)
} }
} }
} catch (e: WriterException) { } catch (e: Exception) {
e.printStackTrace() e.printStackTrace()
} }
@@ -3,11 +3,12 @@ package com.v2ray.ang.ui
import android.content.Intent import android.content.Intent
import android.net.Uri import android.net.Uri
import android.os.Bundle import android.os.Bundle
import com.google.zxing.WriterException import com.v2ray.ang.AppConfig
import com.v2ray.ang.R import com.v2ray.ang.R
import com.v2ray.ang.databinding.ActivityLogcatBinding import com.v2ray.ang.databinding.ActivityLogcatBinding
import com.v2ray.ang.extension.toast import com.v2ray.ang.extension.toast
import com.v2ray.ang.util.AngConfigManager import com.v2ray.ang.util.AngConfigManager
import java.net.URLDecoder
class UrlSchemeActivity : BaseActivity() { class UrlSchemeActivity : BaseActivity() {
private lateinit var binding: ActivityLogcatBinding private lateinit var binding: ActivityLogcatBinding
@@ -18,34 +19,68 @@ class UrlSchemeActivity : BaseActivity() {
val view = binding.root val view = binding.root
setContentView(view) setContentView(view)
var shareUrl: String = ""
try { try {
intent?.apply { intent.apply {
when (action) { if (action == Intent.ACTION_SEND) {
Intent.ACTION_SEND -> { if ("text/plain" == type) {
if ("text/plain" == type) { intent.getStringExtra(Intent.EXTRA_TEXT)?.let {
intent.getStringExtra(Intent.EXTRA_TEXT)?.let { val uri = Uri.parse(it)
shareUrl = 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 -> { } else if (action == Intent.ACTION_VIEW) {
val uri: Uri? = intent.data when (data?.host) {
shareUrl = uri?.getQueryParameter("url")!! "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)) startActivity(Intent(this, MainActivity::class.java))
finish() finish()
} catch (e: WriterException) { } catch (e: Exception) {
e.printStackTrace() 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.annotation.SuppressLint
import android.content.Intent import android.content.Intent
import android.net.Uri import android.net.Uri
import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.provider.OpenableColumns import android.provider.OpenableColumns
import android.util.Log import android.util.Log
@@ -75,7 +76,14 @@ class UserAssetActivity : BaseActivity() {
} }
private fun showFileChooser() { 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) { if (it) {
val intent = Intent(Intent.ACTION_GET_CONTENT) val intent = Intent(Intent.ACTION_GET_CONTENT)
intent.type = "*/*" intent.type = "*/*"
@@ -91,7 +99,8 @@ class UserAssetActivity : BaseActivity() {
} catch (ex: android.content.ActivityNotFoundException) { } catch (ex: android.content.ActivityNotFoundException) {
toast(R.string.toast_require_file_manager) 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.net.URI
import java.util.* import java.util.*
import com.v2ray.ang.extension.idnHost import com.v2ray.ang.extension.idnHost
import com.v2ray.ang.extension.toast
object AngConfigManager { object AngConfigManager {
private val mainStorage by lazy { MMKV.mmkvWithID(MmkvManager.ID_MAIN, MMKV.MULTI_PROCESS_MODE) } private val mainStorage by lazy {
private val serverRawStorage by lazy { MMKV.mmkvWithID(MmkvManager.ID_SERVER_RAW, MMKV.MULTI_PROCESS_MODE) } MMKV.mmkvWithID(
private val settingsStorage by lazy { MMKV.mmkvWithID(MmkvManager.ID_SETTING, MMKV.MULTI_PROCESS_MODE) } 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) } private val subStorage by lazy { MMKV.mmkvWithID(MmkvManager.ID_SUB, MMKV.MULTI_PROCESS_MODE) }
/** /**
@@ -82,8 +98,14 @@ object AngConfigManager {
).forEach { key -> ).forEach { key ->
settingsStorage?.encode(key, sharedPreferences.getBoolean(key, false)) settingsStorage?.encode(key, sharedPreferences.getBoolean(key, false))
} }
settingsStorage?.encode(AppConfig.PREF_SNIFFING_ENABLED, sharedPreferences.getBoolean(AppConfig.PREF_SNIFFING_ENABLED, true)) settingsStorage?.encode(
settingsStorage?.encode(AppConfig.PREF_PER_APP_PROXY_SET, sharedPreferences.getStringSet(AppConfig.PREF_PER_APP_PROXY_SET, setOf())) 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) { private fun migrateVmessBean(angConfig: AngConfig, sharedPreferences: SharedPreferences) {
@@ -125,7 +147,8 @@ object AngConfigManager {
if (TextUtils.isEmpty(vmessBean.security) && TextUtils.isEmpty(vmessBean.id)) { if (TextUtils.isEmpty(vmessBean.security) && TextUtils.isEmpty(vmessBean.id)) {
server.users = null server.users = null
} else { } else {
val socksUsersBean = V2rayConfig.OutboundBean.OutSettingsBean.ServersBean.SocksUsersBean() val socksUsersBean =
V2rayConfig.OutboundBean.OutSettingsBean.ServersBean.SocksUsersBean()
socksUsersBean.user = vmessBean.security socksUsersBean.user = vmessBean.security
socksUsersBean.pass = vmessBean.id socksUsersBean.pass = vmessBean.id
server.users = listOf(socksUsersBean) server.users = listOf(socksUsersBean)
@@ -135,17 +158,27 @@ object AngConfigManager {
} }
} }
config.outboundBean?.streamSettings?.let { streamSetting -> config.outboundBean?.streamSettings?.let { streamSetting ->
val sni = streamSetting.populateTransportSettings(vmessBean.network, vmessBean.headerType, val sni = streamSetting.populateTransportSettings(
vmessBean.requestHost, vmessBean.path, vmessBean.path, vmessBean.requestHost, vmessBean.path, vmessBean.network,
vmessBean.headerType, vmessBean.path) vmessBean.headerType,
vmessBean.requestHost,
vmessBean.path,
vmessBean.path,
vmessBean.requestHost,
vmessBean.path,
vmessBean.headerType,
vmessBean.path
)
val allowInsecure = if (vmessBean.allowInsecure.isBlank()) { val allowInsecure = if (vmessBean.allowInsecure.isBlank()) {
settingsStorage?.decodeBool(AppConfig.PREF_ALLOW_INSECURE) ?: false settingsStorage?.decodeBool(AppConfig.PREF_ALLOW_INSECURE) ?: false
} else { } else {
vmessBean.allowInsecure.toBoolean() vmessBean.allowInsecure.toBoolean()
} }
var fingerprint = streamSetting.tlsSettings?.fingerprint var fingerprint = streamSetting.tlsSettings?.fingerprint
streamSetting.populateTlsSettings(vmessBean.streamSecurity, allowInsecure, streamSetting.populateTlsSettings(
vmessBean.sni.ifBlank { sni }, fingerprint, null) vmessBean.streamSecurity, allowInsecure,
vmessBean.sni.ifBlank { sni }, fingerprint, null, null, null, null
)
} }
} }
val key = MmkvManager.encodeServerConfig(vmessBean.guid, config) val key = MmkvManager.encodeServerConfig(vmessBean.guid, config)
@@ -168,14 +201,21 @@ object AngConfigManager {
/** /**
* import config form qrcode or... * 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 { try {
if (str == null || TextUtils.isEmpty(str)) { if (str == null || TextUtils.isEmpty(str)) {
return R.string.toast_none_data return R.string.toast_none_data
} }
//maybe sub //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) MmkvManager.importUrlAsSubscription(str)
return 0 return 0
} }
@@ -186,8 +226,6 @@ object AngConfigManager {
config = ServerConfig.create(EConfigType.VMESS) config = ServerConfig.create(EConfigType.VMESS)
val streamSetting = config.outboundBean?.streamSettings ?: return -1 val streamSetting = config.outboundBean?.streamSettings ?: return -1
var fingerprint = streamSetting.tlsSettings?.fingerprint
if (!tryParseNewVmess(str, config, allowInsecure)) { if (!tryParseNewVmess(str, config, allowInsecure)) {
if (str.indexOf("?") > 0) { if (str.indexOf("?") > 0) {
@@ -203,9 +241,9 @@ object AngConfigManager {
val vmessQRCode = Gson().fromJson(result, VmessQRCode::class.java) val vmessQRCode = Gson().fromJson(result, VmessQRCode::class.java)
// Although VmessQRCode fields are non null, looks like Gson may still create null fields // Although VmessQRCode fields are non null, looks like Gson may still create null fields
if (TextUtils.isEmpty(vmessQRCode.add) if (TextUtils.isEmpty(vmessQRCode.add)
|| TextUtils.isEmpty(vmessQRCode.port) || TextUtils.isEmpty(vmessQRCode.port)
|| TextUtils.isEmpty(vmessQRCode.id) || TextUtils.isEmpty(vmessQRCode.id)
|| TextUtils.isEmpty(vmessQRCode.net) || TextUtils.isEmpty(vmessQRCode.net)
) { ) {
return R.string.toast_incorrect_protocol return R.string.toast_incorrect_protocol
} }
@@ -215,15 +253,28 @@ object AngConfigManager {
vnext.address = vmessQRCode.add vnext.address = vmessQRCode.add
vnext.port = Utils.parseInt(vmessQRCode.port) vnext.port = Utils.parseInt(vmessQRCode.port)
vnext.users[0].id = vmessQRCode.id 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) vnext.users[0].alterId = Utils.parseInt(vmessQRCode.aid)
} }
val sni = streamSetting.populateTransportSettings(vmessQRCode.net, vmessQRCode.type, vmessQRCode.host, val sni = streamSetting.populateTransportSettings(
vmessQRCode.path, vmessQRCode.path, vmessQRCode.host, vmessQRCode.path, vmessQRCode.type, vmessQRCode.path) vmessQRCode.net,
vmessQRCode.type,
vmessQRCode.host,
vmessQRCode.path,
vmessQRCode.path,
vmessQRCode.host,
vmessQRCode.path,
vmessQRCode.type,
vmessQRCode.path
)
val fingerprint = vmessQRCode.fp ?: streamSetting.tlsSettings?.fingerprint
streamSetting.populateTlsSettings(vmessQRCode.tls, allowInsecure, streamSetting.populateTlsSettings(
if (TextUtils.isEmpty(vmessQRCode.sni)) sni else vmessQRCode.sni, fingerprint, vmessQRCode.alpn) 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)) { } else if (str.startsWith(EConfigType.SHADOWSOCKS.protocolScheme)) {
@@ -233,7 +284,8 @@ object AngConfigManager {
val indexSplit = result.indexOf("#") val indexSplit = result.indexOf("#")
if (indexSplit > 0) { if (indexSplit > 0) {
try { try {
config.remarks = Utils.urlDecode(result.substring(indexSplit + 1, result.length)) config.remarks =
Utils.urlDecode(result.substring(indexSplit + 1, result.length))
} catch (e: Exception) { } catch (e: Exception) {
e.printStackTrace() e.printStackTrace()
} }
@@ -244,13 +296,17 @@ object AngConfigManager {
//part decode //part decode
val indexS = result.indexOf("@") val indexS = result.indexOf("@")
result = if (indexS > 0) { 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 { } else {
Utils.decode(result) Utils.decode(result)
} }
val legacyPattern = "^(.+?):(.*)@(.+?):(\\d+?)/?$".toRegex() 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 -> config.outboundBean?.settings?.servers?.get(0)?.let { server ->
server.address = match.groupValues[3].removeSurrounding("[", "]") server.address = match.groupValues[3].removeSurrounding("[", "]")
@@ -265,7 +321,8 @@ object AngConfigManager {
config = ServerConfig.create(EConfigType.SOCKS) config = ServerConfig.create(EConfigType.SOCKS)
if (indexSplit > 0) { if (indexSplit > 0) {
try { try {
config.remarks = Utils.urlDecode(result.substring(indexSplit + 1, result.length)) config.remarks =
Utils.urlDecode(result.substring(indexSplit + 1, result.length))
} catch (e: Exception) { } catch (e: Exception) {
e.printStackTrace() e.printStackTrace()
} }
@@ -276,18 +333,23 @@ object AngConfigManager {
//part decode //part decode
val indexS = result.indexOf("@") val indexS = result.indexOf("@")
if (indexS > 0) { 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 { } else {
result = Utils.decode(result) result = Utils.decode(result)
} }
val legacyPattern = "^(.*):(.*)@(.+?):(\\d+?)$".toRegex() 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 -> config.outboundBean?.settings?.servers?.get(0)?.let { server ->
server.address = match.groupValues[3].removeSurrounding("[", "]") server.address = match.groupValues[3].removeSurrounding("[", "]")
server.port = match.groupValues[4].toInt() 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.user = match.groupValues[1].lowercase()
socksUsersBean.pass = match.groupValues[2] socksUsersBean.pass = match.groupValues[2]
server.users = listOf(socksUsersBean) server.users = listOf(socksUsersBean)
@@ -303,14 +365,29 @@ object AngConfigManager {
val queryParam = uri.rawQuery.split("&") val queryParam = uri.rawQuery.split("&")
.associate { it.split("=").let { (k, v) -> k to Utils.urlDecode(v) } } .associate { it.split("=").let { (k, v) -> k to Utils.urlDecode(v) } }
val sni = config.outboundBean?.streamSettings?.populateTransportSettings(queryParam["type"] ?: "tcp", queryParam["headerType"], val sni = config.outboundBean?.streamSettings?.populateTransportSettings(
queryParam["host"], queryParam["path"], queryParam["seed"], queryParam["quicSecurity"], queryParam["key"], queryParam["type"] ?: "tcp",
queryParam["mode"], queryParam["serviceName"]) queryParam["headerType"],
config.outboundBean?.streamSettings?.populateTlsSettings(queryParam["security"] ?: TLS, allowInsecure, queryParam["sni"] ?: sni!!, fingerprint, queryParam["alpn"]) 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"] ?: "" flow = queryParam["flow"] ?: ""
} else { } else {
config.outboundBean?.streamSettings?.populateTlsSettings(
config.outboundBean?.streamSettings?.populateTlsSettings(TLS, allowInsecure, "", fingerprint, null) TLS, allowInsecure, "",
fingerprint, null, null, null, null
)
} }
config.outboundBean?.settings?.servers?.get(0)?.let { server -> config.outboundBean?.settings?.servers?.get(0)?.let { server ->
@@ -333,22 +410,41 @@ object AngConfigManager {
vnext.port = uri.port vnext.port = uri.port
vnext.users[0].id = uri.userInfo vnext.users[0].id = uri.userInfo
vnext.users[0].encryption = queryParam["encryption"] ?: "none" 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"], val sni = streamSetting.populateTransportSettings(
queryParam["host"], queryParam["path"], queryParam["seed"], queryParam["quicSecurity"], queryParam["key"], queryParam["type"] ?: "tcp",
queryParam["mode"], queryParam["serviceName"]) queryParam["headerType"],
streamSetting.populateTlsSettings(queryParam["security"] ?: "", allowInsecure, queryParam["sni"] ?: sni, fingerprint, queryParam["alpn"]) 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 return R.string.toast_incorrect_protocol
} }
config.subscriptionId = subid config.subscriptionId = subid
val guid = MmkvManager.encodeServerConfig("", config) val guid = MmkvManager.encodeServerConfig("", config)
if (removedSelectedServer != null && if (removedSelectedServer != null &&
config.getProxyOutbound()?.getServerAddress() == removedSelectedServer.getProxyOutbound()?.getServerAddress() && config.getProxyOutbound()
config.getProxyOutbound()?.getServerPort() == removedSelectedServer.getProxyOutbound()?.getServerPort()) { ?.getServerAddress() == removedSelectedServer.getProxyOutbound()
?.getServerAddress() &&
config.getProxyOutbound()
?.getServerPort() == removedSelectedServer.getProxyOutbound()?.getServerPort()
) {
mainStorage?.encode(KEY_SELECTED_SERVER, guid) mainStorage?.encode(KEY_SELECTED_SERVER, guid)
} }
} catch (e: Exception) { } catch (e: Exception) {
@@ -358,14 +454,18 @@ object AngConfigManager {
return 0 return 0
} }
private fun tryParseNewVmess(uriString: String, config: ServerConfig, allowInsecure: Boolean): Boolean { private fun tryParseNewVmess(
uriString: String,
config: ServerConfig,
allowInsecure: Boolean
): Boolean {
return runCatching { return runCatching {
val uri = URI(uriString) val uri = URI(uriString)
check(uri.scheme == "vmess") check(uri.scheme == "vmess")
val (_, protocol, tlsStr, uuid, alterId) = 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})") 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 .matchEntire(uri.userInfo)?.groupValues
?: error("parse user info fail.") ?: error("parse user info fail.")
val tls = tlsStr.isNotBlank() val tls = tlsStr.isNotBlank()
val queryParam = uri.rawQuery.split("&") val queryParam = uri.rawQuery.split("&")
.associate { it.split("=").let { (k, v) -> k to Utils.urlDecode(v) } } .associate { it.split("=").let { (k, v) -> k to Utils.urlDecode(v) } }
@@ -380,11 +480,19 @@ object AngConfigManager {
vnext.users[0].alterId = alterId.toInt() vnext.users[0].alterId = alterId.toInt()
} }
var fingerprint = streamSetting.tlsSettings?.fingerprint var fingerprint = streamSetting.tlsSettings?.fingerprint
val sni = streamSetting.populateTransportSettings(protocol, queryParam["type"], val sni = streamSetting.populateTransportSettings(protocol,
queryParam["host"]?.split("|")?.get(0) ?: "", queryParam["type"],
queryParam["path"]?.takeIf { it.trim() != "/" } ?: "", queryParam["seed"], queryParam["security"], queryParam["host"]?.split("|")?.get(0) ?: "",
queryParam["key"], queryParam["mode"], queryParam["serviceName"]) queryParam["path"]?.takeIf { it.trim() != "/" } ?: "",
streamSetting.populateTlsSettings(if (tls) TLS else "", allowInsecure, sni, fingerprint, null) queryParam["seed"],
queryParam["security"],
queryParam["key"],
queryParam["mode"],
queryParam["serviceName"])
streamSetting.populateTlsSettings(
if (tls) TLS else "", allowInsecure, sni, fingerprint, null,
null, null, null
)
true true
}.getOrElse { false } }.getOrElse { false }
} }
@@ -472,12 +580,17 @@ object AngConfigManager {
vmessQRCode.add = outbound.getServerAddress().orEmpty() vmessQRCode.add = outbound.getServerAddress().orEmpty()
vmessQRCode.port = outbound.getServerPort().toString() vmessQRCode.port = outbound.getServerPort().toString()
vmessQRCode.id = outbound.getPassword().orEmpty() vmessQRCode.id = outbound.getPassword().orEmpty()
vmessQRCode.aid = outbound.settings?.vnext?.get(0)?.users?.get(0)?.alterId.toString() vmessQRCode.aid =
vmessQRCode.scy = outbound.settings?.vnext?.get(0)?.users?.get(0)?.security.toString() 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.net = streamSetting.network
vmessQRCode.tls = streamSetting.security vmessQRCode.tls = streamSetting.security
vmessQRCode.sni = streamSetting.tlsSettings?.serverName.orEmpty() 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 -> outbound.getTransportSettingDetails()?.let { transportDetails ->
vmessQRCode.type = transportDetails[0] vmessQRCode.type = transportDetails[0]
vmessQRCode.host = transportDetails[1] vmessQRCode.host = transportDetails[1]
@@ -486,25 +599,34 @@ object AngConfigManager {
val json = Gson().toJson(vmessQRCode) val json = Gson().toJson(vmessQRCode)
Utils.encode(json) Utils.encode(json)
} }
EConfigType.CUSTOM, EConfigType.WIREGUARD -> "" EConfigType.CUSTOM, EConfigType.WIREGUARD -> ""
EConfigType.SHADOWSOCKS -> { EConfigType.SHADOWSOCKS -> {
val remark = "#" + Utils.urlEncode(config.remarks) val remark = "#" + Utils.urlEncode(config.remarks)
val pw = Utils.encode("${outbound.getSecurityEncryption()}:${outbound.getPassword()}") val pw =
val url = String.format("%s@%s:%s", Utils.encode("${outbound.getSecurityEncryption()}:${outbound.getPassword()}")
pw, val url = String.format(
Utils.getIpv6Address(outbound.getServerAddress()!!), "%s@%s:%s",
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, pw,
Utils.getIpv6Address(outbound.getServerAddress()!!), Utils.getIpv6Address(outbound.getServerAddress()!!),
outbound.getServerPort()) outbound.getServerPort()
)
url + remark 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.VLESS,
EConfigType.TROJAN -> { EConfigType.TROJAN -> {
val remark = "#" + Utils.urlEncode(config.remarks) val remark = "#" + Utils.urlEncode(config.remarks)
@@ -528,12 +650,26 @@ object AngConfigManager {
} }
dicQuery["security"] = streamSetting.security.ifEmpty { "none" } dicQuery["security"] = streamSetting.security.ifEmpty { "none" }
(streamSetting.tlsSettings?: streamSetting.xtlsSettings)?.let { tlsSetting -> (streamSetting.tlsSettings
?: streamSetting.realitySettings)?.let { tlsSetting ->
if (!TextUtils.isEmpty(tlsSetting.serverName)) { if (!TextUtils.isEmpty(tlsSetting.serverName)) {
dicQuery["sni"] = tlsSetting.serverName dicQuery["sni"] = tlsSetting.serverName
} }
if (!tlsSetting.alpn.isNullOrEmpty() && tlsSetting.alpn.isNotEmpty()) { 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 } dicQuery["type"] = streamSetting.network.ifEmpty { V2rayConfig.DEFAULT_NETWORK }
@@ -546,12 +682,14 @@ object AngConfigManager {
dicQuery["host"] = Utils.urlEncode(transportDetails[1]) dicQuery["host"] = Utils.urlEncode(transportDetails[1])
} }
} }
"kcp" -> { "kcp" -> {
dicQuery["headerType"] = transportDetails[0].ifEmpty { "none" } dicQuery["headerType"] = transportDetails[0].ifEmpty { "none" }
if (!TextUtils.isEmpty(transportDetails[2])) { if (!TextUtils.isEmpty(transportDetails[2])) {
dicQuery["seed"] = Utils.urlEncode(transportDetails[2]) dicQuery["seed"] = Utils.urlEncode(transportDetails[2])
} }
} }
"ws" -> { "ws" -> {
if (!TextUtils.isEmpty(transportDetails[1])) { if (!TextUtils.isEmpty(transportDetails[1])) {
dicQuery["host"] = Utils.urlEncode(transportDetails[1]) dicQuery["host"] = Utils.urlEncode(transportDetails[1])
@@ -560,6 +698,7 @@ object AngConfigManager {
dicQuery["path"] = Utils.urlEncode(transportDetails[2]) dicQuery["path"] = Utils.urlEncode(transportDetails[2])
} }
} }
"http", "h2" -> { "http", "h2" -> {
dicQuery["type"] = "http" dicQuery["type"] = "http"
if (!TextUtils.isEmpty(transportDetails[1])) { if (!TextUtils.isEmpty(transportDetails[1])) {
@@ -569,11 +708,13 @@ object AngConfigManager {
dicQuery["path"] = Utils.urlEncode(transportDetails[2]) dicQuery["path"] = Utils.urlEncode(transportDetails[2])
} }
} }
"quic" -> { "quic" -> {
dicQuery["headerType"] = transportDetails[0].ifEmpty { "none" } dicQuery["headerType"] = transportDetails[0].ifEmpty { "none" }
dicQuery["quicSecurity"] = Utils.urlEncode(transportDetails[1]) dicQuery["quicSecurity"] = Utils.urlEncode(transportDetails[1])
dicQuery["key"] = Utils.urlEncode(transportDetails[2]) dicQuery["key"] = Utils.urlEncode(transportDetails[2])
} }
"grpc" -> { "grpc" -> {
dicQuery["mode"] = transportDetails[0] dicQuery["mode"] = transportDetails[0]
dicQuery["serviceName"] = transportDetails[2] dicQuery["serviceName"] = transportDetails[2]
@@ -581,13 +722,15 @@ object AngConfigManager {
} }
} }
val query = "?" + dicQuery.toList().joinToString( val query = "?" + dicQuery.toList().joinToString(
separator = "&", separator = "&",
transform = { it.first + "=" + it.second }) transform = { it.first + "=" + it.second })
val url = String.format("%s@%s:%s", val url = String.format(
outbound.getPassword(), "%s@%s:%s",
Utils.getIpv6Address(outbound.getServerAddress()!!), outbound.getPassword(),
outbound.getServerPort()) Utils.getIpv6Address(outbound.getServerAddress()!!),
outbound.getServerPort()
)
url + query + remark url + query + remark
} }
} }
@@ -649,7 +792,7 @@ object AngConfigManager {
if (TextUtils.isEmpty(conf)) { if (TextUtils.isEmpty(conf)) {
return null return null
} }
return Utils.createQRCode(conf) return QRCodeDecoder.createQRCode(conf)
} catch (e: Exception) { } catch (e: Exception) {
e.printStackTrace() e.printStackTrace()
@@ -735,16 +878,34 @@ object AngConfigManager {
var count = 0 var count = 0
servers.lines() servers.lines()
.forEach { .reversed()
val resId = importConfig(it, subid, removedSelectedServer) .forEach {
if (resId == 0) { val resId = importConfig(it, subid, removedSelectedServer)
count++ if (resId == 0) {
} count++
} }
}
return count return count
} catch (e: Exception) { } catch (e: Exception) {
e.printStackTrace() e.printStackTrace()
} }
return 0 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)) serverStorage?.encode(key, Gson().toJson(config))
val serverList = decodeServerList() val serverList = decodeServerList()
if (!serverList.contains(key)) { if (!serverList.contains(key)) {
serverList.add(key) serverList.add(0, key)
mainStorage?.encode(KEY_ANG_CONFIGS, Gson().toJson(serverList)) mainStorage?.encode(KEY_ANG_CONFIGS, Gson().toJson(serverList))
if (mainStorage?.decodeString(KEY_SELECTED_SERVER).isNullOrBlank()) { if (mainStorage?.decodeString(KEY_SELECTED_SERVER).isNullOrBlank()) {
mainStorage?.encode(KEY_SELECTED_SERVER, key) mainStorage?.encode(KEY_SELECTED_SERVER, key)
@@ -5,6 +5,7 @@ import android.graphics.BitmapFactory
import com.google.zxing.* import com.google.zxing.*
import com.google.zxing.common.GlobalHistogramBinarizer import com.google.zxing.common.GlobalHistogramBinarizer
import com.google.zxing.common.HybridBinarizer import com.google.zxing.common.HybridBinarizer
import com.google.zxing.qrcode.QRCodeWriter
import java.util.* import java.util.*
/** /**
@@ -13,6 +14,36 @@ import java.util.*
object QRCodeDecoder { object QRCodeDecoder {
val HINTS: MutableMap<DecodeHintType, Any?> = EnumMap(DecodeHintType::class.java) 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,17 +4,12 @@ import android.content.ClipboardManager
import android.content.Context import android.content.Context
import android.text.Editable import android.text.Editable
import android.util.Base64 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 java.util.*
import kotlin.collections.HashMap
import android.content.ClipData import android.content.ClipData
import android.content.Intent 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_MASK
import android.content.res.Configuration.UI_MODE_NIGHT_YES import android.content.res.Configuration.UI_MODE_NIGHT_NO
import android.net.Uri import android.net.Uri
import android.os.Build import android.os.Build
import android.os.LocaleList import android.os.LocaleList
@@ -171,36 +166,6 @@ object Utils {
return ret 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 * is ip address
*/ */
@@ -213,7 +178,7 @@ object Utils {
//CIDR //CIDR
if (addr.indexOf("/") > 0) { if (addr.indexOf("/") > 0) {
val arr = addr.split("/") val arr = addr.split("/")
if (arr.count() == 2 && Integer.parseInt(arr[1]) > 0) { if (arr.count() == 2 && Integer.parseInt(arr[1]) > -1) {
addr = arr[0] addr = arr[0]
} }
} }
@@ -274,7 +239,7 @@ object Utils {
if (value != null && Patterns.WEB_URL.matcher(value).matches() || URLUtil.isValidUrl(value)) { if (value != null && Patterns.WEB_URL.matcher(value).matches() || URLUtil.isValidUrl(value)) {
return true return true
} }
} catch (e: WriterException) { } catch (e: Exception) {
e.printStackTrace() e.printStackTrace()
return false return false
} }
@@ -391,7 +356,7 @@ object Utils {
fun getDarkModeStatus(context: Context): Boolean { fun getDarkModeStatus(context: Context): Boolean {
val mode = context.resources.configuration.uiMode and UI_MODE_NIGHT_MASK val mode = context.resources.configuration.uiMode and UI_MODE_NIGHT_MASK
return mode == UI_MODE_NIGHT_YES return mode != UI_MODE_NIGHT_NO
} }
fun getIpv6Address(address: String): String { fun getIpv6Address(address: String): String {
@@ -435,5 +400,9 @@ object Utils {
return URL(url.protocol, IDN.toASCII(url.host, IDN.ALLOW_UNASSIGNED), url.port, url.file) return URL(url.protocol, IDN.toASCII(url.host, IDN.ALLOW_UNASSIGNED), url.port, url.file)
.toExternalForm() .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) v2rayConfig.routing.domainStrategy = settingsStorage?.decodeString(AppConfig.PREF_ROUTING_DOMAIN_STRATEGY)
?: "IPIfNonMatch" ?: "IPIfNonMatch"
v2rayConfig.routing.domainMatcher = "mph" // v2rayConfig.routing.domainMatcher = "mph"
val routingMode = settingsStorage?.decodeString(AppConfig.PREF_ROUTING_MODE) ?: ERoutingMode.GLOBAL_PROXY.value val routingMode = settingsStorage?.decodeString(AppConfig.PREF_ROUTING_MODE) ?: ERoutingMode.GLOBAL_PROXY.value
// Hardcode googleapis.cn // Hardcode googleapis.cn
@@ -74,8 +74,8 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
config.fullConfig = Gson().fromJson(server, V2rayConfig::class.java) config.fullConfig = Gson().fromJson(server, V2rayConfig::class.java)
val key = MmkvManager.encodeServerConfig("", config) val key = MmkvManager.encodeServerConfig("", config)
serverRawStorage?.encode(key, server) serverRawStorage?.encode(key, server)
serverList.add(key) serverList.add(0, key)
serversCache.add(ServersCache(key,config)) serversCache.add(0, ServersCache(key,config))
} }
fun swapServer(fromPosition: Int, toPosition: Int) { fun swapServer(fromPosition: Int, toPosition: Int) {
@@ -201,6 +201,27 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
return -1 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() { private val mMsgReceiver = object : BroadcastReceiver() {
override fun onReceive(ctx: Context?, intent: Intent?) { override fun onReceive(ctx: Context?, intent: Intent?) {
when (intent?.getIntExtra("key", 0)) { when (intent?.getIntExtra("key", 0)) {
@@ -50,7 +50,8 @@ class SettingsViewModel(application: Application) : AndroidViewModel(application
AppConfig.PREF_PREFER_IPV6, AppConfig.PREF_PREFER_IPV6,
AppConfig.PREF_PER_APP_PROXY, AppConfig.PREF_PER_APP_PROXY,
AppConfig.PREF_BYPASS_APPS, AppConfig.PREF_BYPASS_APPS,
AppConfig.PREF_CONFIRM_REMOVE, -> { AppConfig.PREF_CONFIRM_REMOVE,
AppConfig.PREF_START_SCAN_IMMEDIATE, -> {
settingsStorage?.encode(key, sharedPreferences.getBoolean(key, false)) settingsStorage?.encode(key, sharedPreferences.getBoolean(key, false))
} }
AppConfig.PREF_SNIFFING_ENABLED -> { AppConfig.PREF_SNIFFING_ENABLED -> {
Binary file not shown.

After

Width:  |  Height:  |  Size: 526 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 651 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 386 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 476 B

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FFFFFFFF"
android:pathData="M9,16.2L4.8,12l-1.4,1.4L9,19 21,7l-1.4,-1.4L9,16.2z"/>
</vector>
@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FFFFFFFF"
android:pathData="M3,17.25V21h3.75L17.81,9.94l-3.75,-3.75L3,17.25zM20.71,7.04c0.39,-0.39 0.39,-1.02 0,-1.41l-2.34,-2.34c-0.39,-0.39 -1.02,-0.39 -1.41,0l-1.83,1.83 3.75,3.75 1.83,-1.83z"/>
</vector>
@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FFF"
android:pathData="M9,16.17L4.83,12l-1.42,1.41L9,19 21,7l-1.41,-1.41z" />
</vector>
@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#fff"
android:pathData="M21,19V5c0,-1.1 -0.9,-2 -2,-2H5c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2zM8.5,13.5l2.5,3.01L14.5,12l4.5,6H5l3.5,-4.5z"/>
</vector>
@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportHeight="24.0"
android:viewportWidth="24.0">
<path
android:fillColor="#FFFFFFFF"
android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zm1,15h-2v-6h2v6zm0,-8h-2V7h2v2z" />
</vector>
Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 679 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 855 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 984 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

@@ -4,6 +4,6 @@
android:viewportWidth="24.0" android:viewportWidth="24.0"
android:viewportHeight="24.0"> android:viewportHeight="24.0">
<path <path
android:fillColor="#FFFFFFFF" android:fillColor="#FF000000"
android:pathData="M9,16.2L4.8,12l-1.4,1.4L9,19 21,7l-1.4,-1.4L9,16.2z"/> android:pathData="M9,16.2L4.8,12l-1.4,1.4L9,19 21,7l-1.4,-1.4L9,16.2z"/>
</vector> </vector>
@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FF000000"
android:pathData="M19,13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"/>
</vector>
@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FF000000"
android:pathData="M19.35,10.04C18.67,6.59 15.64,4 12,4 9.11,4 6.6,5.64 5.35,8.04 2.34,8.36 0,10.91 0,14c0,3.31 2.69,6 6,6h13c2.76,0 5,-2.24 5,-5 0,-2.64 -2.05,-4.78 -4.65,-4.96zM17,13l-5,5 -5,-5h3V9h4v4h3z" />
</vector>
@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FF000000"
android:pathData="M16,1L4,1c-1.1,0 -2,0.9 -2,2v14h2L4,3h12L16,1zM19,5L8,5c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h11c1.1,0 2,-0.9 2,-2L21,7c0,-1.1 -0.9,-2 -2,-2zM19,21L8,21L8,7h11v14z"/>
</vector>
@@ -4,6 +4,6 @@
android:viewportWidth="24.0" android:viewportWidth="24.0"
android:viewportHeight="24.0"> android:viewportHeight="24.0">
<path <path
android:fillColor="#FFF" android:fillColor="#000"
android:pathData="M9,16.17L4.83,12l-1.42,1.41L9,19 21,7l-1.41,-1.41z" /> android:pathData="M9,16.17L4.83,12l-1.42,1.41L9,19 21,7l-1.41,-1.41z" />
</vector> </vector>
@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FF000000"
android:pathData="M20,2L4,2c-1.1,0 -1.99,0.9 -1.99,2L2,22l4,-4h14c1.1,0 2,-0.9 2,-2L22,4c0,-1.1 -0.9,-2 -2,-2zM13,14h-2v-2h2v2zM13,10h-2L11,6h2v4z"/>
</vector>
@@ -0,0 +1,34 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M11,23h8a3,3 0,0 0,3 -3V8L15,1H7A3,3 0,0 0,4 4V9"
android:strokeLineJoin="round"
android:strokeWidth="2"
android:fillColor="#00000000"
android:strokeColor="#000"
android:strokeLineCap="round"/>
<path
android:pathData="M15,5a3,3 0,0 0,3 3h4L15,1Z"
android:strokeLineJoin="round"
android:strokeWidth="2"
android:fillColor="#00000000"
android:strokeColor="#000"
android:strokeLineCap="round"/>
<path
android:pathData="M2,17L10,17"
android:strokeLineJoin="round"
android:strokeWidth="2"
android:fillColor="#00000000"
android:strokeColor="#000"
android:strokeLineCap="round"/>
<path
android:pathData="M6,13L6,21"
android:strokeLineJoin="round"
android:strokeWidth="2"
android:fillColor="#00000000"
android:strokeColor="#000"
android:strokeLineCap="round"/>
</vector>
@@ -4,6 +4,6 @@
android:viewportWidth="24.0" android:viewportWidth="24.0"
android:viewportHeight="24.0"> android:viewportHeight="24.0">
<path <path
android:fillColor="#fff" android:fillColor="#000"
android:pathData="M21,19V5c0,-1.1 -0.9,-2 -2,-2H5c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2zM8.5,13.5l2.5,3.01L14.5,12l4.5,6H5l3.5,-4.5z"/> android:pathData="M21,19V5c0,-1.1 -0.9,-2 -2,-2H5c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2zM8.5,13.5l2.5,3.01L14.5,12l4.5,6H5l3.5,-4.5z"/>
</vector> </vector>
@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FF000000"
android:pathData="M11,15h2v2h-2zM11,7h2v6h-2zM11.99,2C6.47,2 2,6.48 2,12s4.47,10 9.99,10C17.52,22 22,17.52 22,12S17.52,2 11.99,2zM12,20c-4.42,0 -8,-3.58 -8,-8s3.58,-8 8,-8 8,3.58 8,8 -3.58,8 -8,8z"/>
</vector>
@@ -0,0 +1,5 @@
<vector android:height="24dp" android:tint="#000000"
android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M7,6h10l-5.01,6.3L7,6zM4.25,5.61C6.27,8.2 10,13 10,13v6c0,0.55 0.45,1 1,1h2c0.55,0 1,-0.45 1,-1v-6c0,0 3.72,-4.8 5.74,-7.39C20.25,4.95 19.78,4 18.95,4H5.04C4.21,4 3.74,4.95 4.25,5.61z"/>
</vector>
@@ -0,0 +1,19 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="48"
android:viewportHeight="48">
<path
android:fillColor="@color/colorBg"
android:pathData="M24,24m-22,0a22,22 0,1 1,44 0a22,22 0,1 1,-44 0Z" />
<path
android:fillColor="@color/colorText"
android:pathData="M24,21.8 C25.7673,21.8,27.2,23.2327,27.2,25 C27.2,26.7673,25.7673,28.2,24,28.2
C22.2327,28.2,20.8,26.7673,20.8,25 C20.8,23.2327,22.2327,21.8,24,21.8 Z" />
<path
android:fillColor="@color/colorText"
android:pathData="M21,15 L19.17,17 L16,17 A2,2,0,0,0,14,19 L14,31 A2,2,0,0,0,16,33 L32,33
A2,2,0,0,0,34,31 L34,19 A2,2,0,0,0,32,17 L28.83,17 L27,15 Z M24,30
A5,5,0,1,1,29,25 A5,5,0,0,1,24,30 Z" />
</vector>
@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:drawable="@drawable/ic_shortcut_background"
android:left="2dp"
android:top="2dp"
android:right="2dp"
android:bottom="2dp" />
<item
android:drawable="@drawable/ic_stat_name_black"
android:left="12dp"
android:top="12dp"
android:right="12dp"
android:bottom="12dp" />
</layer-list>
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"> <shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="#009963"/> <solid android:color="@color/color_fab_orange"/>
<corners android:radius="20dp"/> <corners android:radius="20dp"/>
<padding android:left="0dp" android:top="0dp" android:right="0dp" android:bottom="0dp" /> <padding android:left="0dp" android:top="0dp" android:right="0dp" android:bottom="0dp" />
</shape> </shape>
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"> <shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="@color/colorUnselected"/> <solid android:color="@color/color_switch_fab_grey"/>
<corners android:radius="20dp"/> <corners android:radius="20dp"/>
<padding android:left="0dp" android:top="0dp" android:right="0dp" android:bottom="0dp" /> <padding android:left="0dp" android:top="0dp" android:right="0dp" android:bottom="0dp" />
</shape> </shape>
@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FF000000"
android:pathData="M17,3L5,3c-1.11,0 -2,0.9 -2,2v14c0,1.1 0.89,2 2,2h14c1.1,0 2,-0.9 2,-2L21,7l-4,-4zM12,19c-1.66,0 -3,-1.34 -3,-3s1.34,-3 3,-3 3,1.34 3,3 -1.34,3 -3,3zM15,9L5,9L5,5h10v4z"/>
</vector>
@@ -5,14 +5,14 @@
android:viewportHeight="48"> android:viewportHeight="48">
<path <path
android:fillColor="#999999" android:fillColor="#050505"
android:pathData="M24,24m-22,0a22,22 0,1 1,44 0a22,22 0,1 1,-44 0Z" /> android:pathData="M24,24m-22,0a22,22 0,1 1,44 0a22,22 0,1 1,-44 0Z" />
<path <path
android:fillColor="#ffffff" android:fillColor="#FFFFFF"
android:pathData="M24,21.8 C25.7673,21.8,27.2,23.2327,27.2,25 C27.2,26.7673,25.7673,28.2,24,28.2 android:pathData="M24,21.8 C25.7673,21.8,27.2,23.2327,27.2,25 C27.2,26.7673,25.7673,28.2,24,28.2
C22.2327,28.2,20.8,26.7673,20.8,25 C20.8,23.2327,22.2327,21.8,24,21.8 Z" /> C22.2327,28.2,20.8,26.7673,20.8,25 C20.8,23.2327,22.2327,21.8,24,21.8 Z" />
<path <path
android:fillColor="#ffffff" android:fillColor="#FFFFFF"
android:pathData="M21,15 L19.17,17 L16,17 A2,2,0,0,0,14,19 L14,31 A2,2,0,0,0,16,33 L32,33 android:pathData="M21,15 L19.17,17 L16,17 A2,2,0,0,0,14,19 L14,31 A2,2,0,0,0,16,33 L32,33
A2,2,0,0,0,34,31 L34,19 A2,2,0,0,0,32,17 L28.83,17 L27,15 Z M24,30 A2,2,0,0,0,34,31 L34,19 A2,2,0,0,0,32,17 L28.83,17 L27,15 Z M24,30
A5,5,0,1,1,29,25 A5,5,0,0,1,24,30 Z" /> A5,5,0,1,1,29,25 A5,5,0,0,1,24,30 Z" />
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:height="24dp"
android:viewportHeight="24.0"
android:viewportWidth="24.0"
android:width="24dp">
<path
android:fillColor="#000000"
android:pathData="M3,5h2L5,3c-1.1,0 -2,0.9 -2,2zM3,13h2v-2L3,11v2zM7,21h2v-2L7,19v2zM3,9h2L5,7L3,7v2zM13,3h-2v2h2L13,3zM19,3v2h2c0,-1.1 -0.9,-2 -2,-2zM5,21v-2L3,19c0,1.1 0.9,2 2,2zM3,17h2v-2L3,15v2zM9,3L7,3v2h2L9,3zM11,21h2v-2h-2v2zM19,13h2v-2h-2v2zM19,21c1.1,0 2,-0.9 2,-2h-2v2zM19,9h2L21,7h-2v2zM19,17h2v-2h-2v2zM15,21h2v-2h-2v2zM15,5h2L17,3h-2v2zM7,17h10L17,7L7,7v10zM9,9h6v6L9,15L9,9z" />
</vector>
@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FF000000"
android:pathData="M19.43,12.98c0.04,-0.32 0.07,-0.64 0.07,-0.98s-0.03,-0.66 -0.07,-0.98l2.11,-1.65c0.19,-0.15 0.24,-0.42 0.12,-0.64l-2,-3.46c-0.12,-0.22 -0.39,-0.3 -0.61,-0.22l-2.49,1c-0.52,-0.4 -1.08,-0.73 -1.69,-0.98l-0.38,-2.65C14.46,2.18 14.25,2 14,2h-4c-0.25,0 -0.46,0.18 -0.49,0.42l-0.38,2.65c-0.61,0.25 -1.17,0.59 -1.69,0.98l-2.49,-1c-0.23,-0.09 -0.49,0 -0.61,0.22l-2,3.46c-0.13,0.22 -0.07,0.49 0.12,0.64l2.11,1.65c-0.04,0.32 -0.07,0.65 -0.07,0.98s0.03,0.66 0.07,0.98l-2.11,1.65c-0.19,0.15 -0.24,0.42 -0.12,0.64l2,3.46c0.12,0.22 0.39,0.3 0.61,0.22l2.49,-1c0.52,0.4 1.08,0.73 1.69,0.98l0.38,2.65c0.03,0.24 0.24,0.42 0.49,0.42h4c0.25,0 0.46,-0.18 0.49,-0.42l0.38,-2.65c0.61,-0.25 1.17,-0.59 1.69,-0.98l2.49,1c0.23,0.09 0.49,0 0.61,-0.22l2,-3.46c0.12,-0.22 0.07,-0.49 -0.12,-0.64l-2.11,-1.65zM12,15.5c-1.93,0 -3.5,-1.57 -3.5,-3.5s1.57,-3.5 3.5,-3.5 3.5,1.57 3.5,3.5 -1.57,3.5 -3.5,3.5z"/>
</vector>
@@ -2,6 +2,6 @@
<shape <shape
xmlns:android="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval"> android:shape="oval">
<solid android:color="@color/colorUnselected" /> <solid android:color="@color/colorBg" />
<size android:width="44dp" android:height="44dp" /> <size android:width="44dp" android:height="44dp" />
</shape> </shape>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.
@@ -3,7 +3,8 @@
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:padding="10dp" android:paddingStart="10dp"
android:paddingEnd="10dp"
tools:context=".ui.MainActivity"> tools:context=".ui.MainActivity">
<ProgressBar <ProgressBar
@@ -13,8 +13,7 @@
<com.google.android.material.appbar.AppBarLayout <com.google.android.material.appbar.AppBarLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content">
android:theme="@style/AppTheme.AppBarOverlay">
<androidx.appcompat.widget.Toolbar <androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar" android:id="@+id/toolbar"
@@ -50,7 +49,7 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="@dimen/connection_test_height" android:layout_height="@dimen/connection_test_height"
android:background="?attr/colorPrimary" android:background="?attr/colorPrimary"
android:gravity="center|left" android:gravity="center|start"
android:nextFocusRight="@+id/fab" android:nextFocusRight="@+id/fab"
android:clickable="true" android:clickable="true"
android:focusable="true" android:focusable="true"
@@ -65,7 +64,7 @@
android:paddingStart="16dp" android:paddingStart="16dp"
android:text="@string/connection_test_pending" android:text="@string/connection_test_pending"
android:textAppearance="@style/TextAppearance.AppCompat.Small" android:textAppearance="@style/TextAppearance.AppCompat.Small"
android:textColor="@color/colorWhite" /> android:textColor="@color/colorText" />
</LinearLayout> </LinearLayout>
</LinearLayout> </LinearLayout>
@@ -106,6 +105,7 @@
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_gravity="start" android:layout_gravity="start"
app:headerLayout="@layout/nav_header" app:headerLayout="@layout/nav_header"
app:itemIconTint="@color/colorAccent"
app:menu="@menu/menu_drawer" > app:menu="@menu/menu_drawer" >
<LinearLayout <LinearLayout
@@ -21,7 +21,7 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="@string/server_lab_need_inbound" android:text="@string/server_lab_need_inbound"
android:textAppearance="@style/TextAppearance.AppCompat.Tooltip" android:textAppearance="@style/TextAppearance.AppCompat.Tooltip"
android:textColor="@color/fab_orange_dark" /> android:textColor="@color/color_secondary" />
</LinearLayout> </LinearLayout>
<LinearLayout <LinearLayout
@@ -98,24 +98,6 @@
</LinearLayout> </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 <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
@@ -16,7 +16,8 @@
<androidx.recyclerview.widget.RecyclerView <androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler_view" android:id="@+id/recycler_view"
android:layout_width="match_parent" 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> </androidx.coordinatorlayout.widget.CoordinatorLayout>
</RelativeLayout> </RelativeLayout>
@@ -19,7 +19,7 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="@string/routing_settings_tips" android:text="@string/routing_settings_tips"
android:textColor="@color/fab_orange_dark" /> android:textColor="@color/color_secondary" />
<EditText <EditText
android:id="@+id/et_routing_content" android:id="@+id/et_routing_content"
@@ -3,7 +3,8 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:gravity="center_vertical" android:gravity="center_vertical"
android:background="?android:attr/selectableItemBackground" android:background="@color/colorPrimary"
android:foreground="?android:attr/selectableItemBackground"
android:clickable="true" android:clickable="true"
android:focusable="true"> android:focusable="true">
@@ -26,7 +26,7 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="@string/summary_pref_promotion" android:text="@string/summary_pref_promotion"
android:textAppearance="@style/TextAppearance.AppCompat.Tooltip" android:textAppearance="@style/TextAppearance.AppCompat.Tooltip"
android:textColor="@color/fab_orange_dark" /> android:textColor="@color/color_secondary" />
</LinearLayout> </LinearLayout>
</LinearLayout> </LinearLayout>
</LinearLayout> </LinearLayout>
@@ -19,7 +19,8 @@
android:clickable="true" android:clickable="true"
android:focusable="true" android:focusable="true"
android:nextFocusRight="@+id/layout_share" android:nextFocusRight="@+id/layout_share"
android:background="?android:attr/selectableItemBackground" android:background="@color/colorPrimary"
android:foreground="?android:attr/selectableItemBackground"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="@dimen/server_height" android:layout_height="@dimen/server_height"
android:layout_gravity="center" android:layout_gravity="center"
@@ -28,7 +29,7 @@
<LinearLayout <LinearLayout
android:id="@+id/layout_indicator" android:id="@+id/layout_indicator"
android:layout_width="10dp" android:layout_width="6dp"
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_gravity="center" android:layout_gravity="center"
android:orientation="vertical" /> android:orientation="vertical" />
@@ -38,7 +39,7 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_weight="1" android:layout_weight="1"
android:orientation="vertical" android:orientation="vertical"
android:paddingStart="5dp"> android:paddingStart="9dp">
<TextView <TextView
android:id="@+id/tv_name" android:id="@+id/tv_name"
@@ -84,7 +85,7 @@
android:layout_weight="1" android:layout_weight="1"
android:lines="1" android:lines="1"
android:textAppearance="@style/TextAppearance.AppCompat.Small" android:textAppearance="@style/TextAppearance.AppCompat.Small"
android:textColor="@color/colorSubscription" android:textColor="@color/color_secondary"
android:textSize="10sp" android:textSize="10sp"
tools:text="Sub" /> tools:text="Sub" />
@@ -121,7 +122,7 @@
<ImageView <ImageView
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="@dimen/png_height" android:layout_height="@dimen/png_height"
android:src="@drawable/ic_share_black_24dp" android:src="@drawable/ic_share_24dp"
app:tint="?attr/colorMainText" /> app:tint="?attr/colorMainText" />
</LinearLayout> </LinearLayout>
@@ -140,7 +141,7 @@
<ImageView <ImageView
android:layout_width="@dimen/png_height" android:layout_width="@dimen/png_height"
android:layout_height="@dimen/png_height" android:layout_height="@dimen/png_height"
android:src="@drawable/ic_edit_black_24dp" android:src="@drawable/ic_edit_24dp"
app:tint="?attr/colorMainText" /> app:tint="?attr/colorMainText" />
</LinearLayout> </LinearLayout>
@@ -159,7 +160,7 @@
<ImageView <ImageView
android:layout_width="@dimen/png_height" android:layout_width="@dimen/png_height"
android:layout_height="@dimen/png_height" android:layout_height="@dimen/png_height"
android:src="@drawable/ic_delete_black_24dp" android:src="@drawable/ic_delete_24dp"
app:tint="?attr/colorMainText" /> app:tint="?attr/colorMainText" />
</LinearLayout> </LinearLayout>
@@ -178,7 +179,7 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:lines="1" android:lines="1"
android:textAppearance="@style/TextAppearance.AppCompat.Small" android:textAppearance="@style/TextAppearance.AppCompat.Small"
android:textColor="@color/fab_orange_dark" android:textColor="@color/color_fab_orange"
android:textSize="10sp" /> android:textSize="10sp" />
</LinearLayout> </LinearLayout>

Some files were not shown because too many files have changed in this diff Show More