Compare commits

...

113 Commits

Author SHA1 Message Date
2dust
413f4efd69 up 1.8.35 2024-07-29 19:12:58 +08:00
Tamim Hossain
de69605eff Introduced version catalog for streamlined dependency management (#3377)
This pull request integrates a version catalog (libs.versions.toml) to centralize dependency management within the project. By utilizing this approach, we enhance dependency consistency, reduce maintenance overhead, and facilitate version updates across multiple modules. This change provides a more organized and efficient way to manage project dependencies.
2024-07-28 09:27:58 +08:00
mayampi01
9338ba3525 Set UseIP in Direct to break the Android Private DNS dead loop (#3362)
* Set UseIP in Direct to break the Android Private DNS dead loop

* fakedns: No need to set UseIP again
2024-07-25 19:50:23 +08:00
2dust
322b6ec615 Bug fix 2024-07-25 19:46:39 +08:00
2dust
304232d029 up 1.8.34 2024-07-22 11:34:44 +08:00
2dust
f80c3bfe07 up 1.8.33 2024-07-20 15:05:45 +08:00
2dust
bb0a62fc8b up 1.8.32 2024-07-18 10:45:05 +08:00
2dust
5bfdca6cd9 Bug fix 2024-07-18 10:15:58 +08:00
2dust
447e712a9d up 1.8.31 2024-07-16 14:05:34 +08:00
2dust
8bb03f189d up 1.8.30 2024-07-13 10:43:46 +08:00
2dust
3b0554cd9b up 1.8.29 2024-07-12 13:30:38 +08:00
2dust
858101b0d9 Bug fix
https://github.com/2dust/v2rayNG/issues/3234
https://github.com/2dust/v2rayNG/issues/3291
2024-07-11 16:41:38 +08:00
2dust
a7cf8bee28 Code clean
Add allowInsecure to Share
2024-07-11 16:40:25 +08:00
2dust
af1ec7bea9 Bug fix 2024-07-09 12:29:04 +08:00
2dust
002bf7ef22 up 1.8.28 2024-07-07 18:14:36 +08:00
2dust
6919e2336d Bug fix
https://github.com/2dust/v2rayNG/issues/3278
2024-07-01 18:09:57 +08:00
2dust
5a5bd22073 Bug fix
https://github.com/2dust/v2rayNG/issues/3232
2024-07-01 10:34:45 +08:00
2dust
a726f00f35 Bug fix 2024-06-29 16:47:06 +08:00
2dust
79297f8a42 up 1.8.27
App bundle support
2024-06-29 15:32:01 +08:00
2dust
e3f70ac253 Add LEANBACK_LAUNCHER 2024-06-29 15:29:20 +08:00
GFW-knocker
838b346fcc fix wireguard issue (#3260)
fix crash when invalid wireguard config imported
2024-06-28 09:41:55 +08:00
solokot
eba9545ccf Update Russian translation to 1.8.26 (#3238) 2024-06-24 09:09:35 +08:00
2dust
890ade9495 up 1.8.26 2024-06-21 20:33:34 +08:00
2dust
518ef1e0ec Add splithttp transport for xray 2024-06-21 20:18:04 +08:00
2dust
bdcecfca72 Hardcode gstatic.com taking detour [proxy] 2024-06-21 16:48:30 +08:00
2dust
1363846ac4 Fix view 2024-06-08 20:24:23 +08:00
2dust
30347546a2 Optimized code 2024-06-08 10:17:54 +08:00
2dust
ac7eb28e91 Auto update subscriptions when adding a url 2024-06-07 21:06:27 +08:00
2dust
748405473b Fix
https://github.com/2dust/v2rayNG/issues/3175
2024-06-07 10:51:13 +08:00
2dust
1080390bed Adding a second test when a delayed test fails
https://www.google.com/generate_204
2024-06-07 10:25:23 +08:00
2dust
c48725c7dd Bug fix
https://github.com/2dust/v2rayNG/issues/3193
2024-06-06 19:48:50 +08:00
2dust
a5287dbadc Optimized code 2024-06-02 20:50:27 +08:00
solokot
ee5a3b0dd9 Update Russian translation (#3172) 2024-05-30 20:06:16 +08:00
2dust
3d001541e5 Refactor the parser 2024-05-30 20:04:43 +08:00
2dust
b376b229b9 Add progress bar when subscription is updated 2024-05-30 16:07:11 +08:00
2dust
33b6203978 Bug fix 2024-05-29 20:30:10 +08:00
2dust
2d803e009c Bug fix
https://github.com/2dust/v2rayNG/issues/3168
2024-05-29 17:41:58 +08:00
2dust
2ddbe38781 Merge branch 'master' of https://github.com/2dust/v2rayNG 2024-05-25 20:32:37 +08:00
user09283
96181a2b8d Update strings.xml (#3152)
Update Vietnamese language!
2024-05-24 20:42:30 +08:00
2dust
8308b8eaf2 Bug fix
android.app.RemoteServiceException$ForegroundServiceDidNotStartInTimeException: Context.startForegroundService() did not then call Service.startForeground(): ServiceRecord{8f445a6 u0 com.v2ray.ang/.service.V2RayVpnService}
2024-05-24 10:35:46 +08:00
2dust
00f26ff529 up 1.8.25 2024-05-23 10:54:47 +08:00
2dust
49dcdf3ae5 used resources 2024-05-22 17:41:55 +08:00
2dust
409b431d1c unused resources 2024-05-20 17:12:54 +08:00
2dust
6da988e3db up 1.8.24 2024-05-18 13:45:44 +08:00
2dust
fd8f8306ee up dependency 2024-05-18 13:44:43 +08:00
2dust
74b342f5c6 Bug fix
https://github.com/2dust/v2rayNG/issues/3126
2024-05-17 14:04:15 +08:00
2dust
2504ec79ee Change delayed test URL
https://github.com/2dust/v2rayNG/issues/3110
2024-05-14 10:53:15 +08:00
2dust
3e7b211b17 Bug fix
https://github.com/2dust/v2rayNG/issues/3106
2024-05-12 16:47:27 +08:00
solokot
13d5514a4c Update Russian translation (#3098) 2024-05-12 11:55:57 +08:00
2dust
f0f9da0f1b Add delayed test URL 2024-05-12 11:55:10 +08:00
2dust
f6d2c5f473 up 1.8.23 2024-05-08 17:18:17 +08:00
2dust
6e8dd5b250 Update strings.xml 2024-05-08 15:34:14 +08:00
2dust
f4779bc50c Update strings.xml 2024-05-08 15:33:00 +08:00
ibrahem Qasim
432baf262d Update Arabic translation (#3089)
* Update strings.xml

* Update strings.xml

* Update strings.xml

* Update strings.xml

* Update strings.xml
2024-05-07 09:42:15 +08:00
2dust
a1d68fcde3 Added attempt to update subscription via proxy
https://github.com/2dust/v2rayNG/issues/2760
2024-05-05 17:08:05 +08:00
2dust
e053db3dff Add routeOnly 2024-05-02 10:51:54 +08:00
solokot
f624bd651e Update Russian translation (#3070) 2024-05-01 19:37:03 +08:00
2dust
84964c7f91 Add attributes to grpc
https://github.com/2dust/v2rayNG/issues/3041
2024-05-01 14:01:34 +08:00
2dust
96f56b468e Adjust DNS hardcoded geoip:cn issue
https://github.com/2dust/v2rayNG/issues/3061
2024-05-01 14:00:35 +08:00
2dust
bdc3212f38 Rule start with ext and contains geoip added to the ips
https://github.com/2dust/v2rayNG/issues/3060
2024-05-01 10:43:47 +08:00
2dust
508ddf6df2 Share configuration 2024-04-28 19:47:54 +08:00
2dust
17af24d179 up 1.8.22 2024-04-26 13:45:59 +08:00
2dust
c260e447ea Adjust UI 2024-04-25 17:52:34 +08:00
2dust
0eb40ae993 Adjust UI 2024-04-25 10:34:15 +08:00
ibrahem Qasim
18f3e39346 Update Arabic translation (#3042)
* Create android.yml

* Create gradle-publish.yml

* Update strings.xml

* Delete .github/workflows/android.yml

* Delete .github/workflows/gradle-publish.yml

* Update strings.xml

* Update strings.xml

* Update strings.xml

* Update strings.xml
2024-04-25 08:17:58 +08:00
2dust
311af02726 up 1.8.21 2024-04-23 17:32:20 +08:00
2dust
b799969de8 Adjust UI 2024-04-23 13:45:50 +08:00
2dust
93541883bb Adjust UI 2024-04-22 10:35:16 +08:00
2dust
33430bff8d Adjust UI 2024-04-21 16:31:34 +08:00
2dust
3bc2081540 Adjust UI 2024-04-21 12:05:42 +08:00
2dust
a3b1feabff Bug fix
https://github.com/2dust/v2rayNG/issues/3024
2024-04-20 15:38:55 +08:00
2dust
7af8d3843e Merge pull request #3025 from solokot/master
Update Russian translation
2024-04-20 15:11:02 +08:00
solokot
2235805800 Update Russian translation 2024-04-19 18:40:09 +03:00
2dust
364b521ec3 up 1.8.20 2024-04-19 21:07:38 +08:00
2dust
3cdaf4a8ee Bug fix
https://github.com/2dust/v2rayNG/issues/3010
2024-04-19 14:36:48 +08:00
2dust
b2fc6dcdd0 Add switching theme dark or light 2024-04-19 13:41:52 +08:00
2dust
70e9320463 Merge pull request #3020 from FranzKafkaYu/optimize-ui-logic
style:optimize UI logic
2024-04-18 10:42:51 +08:00
FranzKafkayu
304c7ed068 style:optimize UI logic
1.add button for cancelling delete server config
2.toast when delete current using config
2024-04-17 18:37:17 +08:00
2dust
ddb908f937 Bug fix 2024-04-17 16:39:53 +08:00
2dust
accd17afd4 Bug fix 2024-04-17 15:49:07 +08:00
2dust
9f598b77b4 Merge pull request #3016 from solokot/master
Update Russian translation
2024-04-17 14:11:28 +08:00
solokot
d82fa974b1 Update Russian translation 2024-04-16 17:13:22 +03:00
2dust
fcbd4a0d48 Optimized storage of settings for SharedPreference 2024-04-16 20:23:58 +08:00
2dust
5cd2b8845e Adjust about style 2024-04-16 10:39:56 +08:00
2dust
723ab70170 Remove function migrateLegacy() 2024-04-16 10:29:07 +08:00
2dust
a26bf3eeda Add backup and restore configuration functionality 2024-04-15 17:30:00 +08:00
2dust
c33b6463c6 Merge pull request #3005 from solokot/master
Update Russian translation
2024-04-13 20:23:06 +08:00
solokot
df995a3ab2 Update Russian translation 2024-04-13 10:39:33 +03:00
2dust
817f844212 Add about activity 2024-04-13 13:12:35 +08:00
2dust
fa8113b8d7 Merge pull request #3002 from NetworkKeeper/patch-1
Ability to override built in `geosite.dat` and `geoip.dat`
2024-04-12 07:47:44 +08:00
2dust
99b95d8369 Merge pull request #3001 from NetworkKeeper/patch-2
Retry downloading geo assets without proxy
2024-04-12 07:47:19 +08:00
2dust
fc2e4ff210 Optimize routing 2024-04-12 07:45:26 +08:00
NetworkKeeper
1063bf71d6 Retry downloading geo assets without proxy 2024-04-11 19:37:20 +03:00
NetworkKeeper
f2af5c45e9 Ability to override built in geosite.dat and geoip.dat 2024-04-11 19:30:32 +03:00
2dust
b132b0d2f0 Remove the type of routing rule 2024-04-11 11:00:46 +08:00
2dust
7869f99fc8 Adjust routing default rules 2024-04-11 09:21:57 +08:00
2dust
122f2eb400 Merge pull request #2995 from oXIIIo/master
fix(deps): Update Go version to 1.22.2 for Xray compatibility (1.8.10)
2024-04-11 07:47:36 +08:00
XIII
3c0f6eeb21 fix(deps): Update Go version to 1.22.2 for Xray compatibility (1.8.10) 2024-04-10 17:36:39 +03:30
2dust
19a109355b Merge pull request #2986 from kimsuelim/improve_build
Remove -XX:MaxPermSize option
2024-04-08 17:12:15 +08:00
Surim Kim
703965a0dd Remove -XX:MaxPermSize option.
Android Gradle requires JDK 17, and this option was removed between Java 11 and Java 17.
2024-04-08 10:05:28 +09:00
2dust
66f92c6c60 Bug fix
https://github.com/2dust/v2rayNG/issues/2972
2024-04-03 10:19:03 +08:00
2dust
a1cdf6b7a5 Merge pull request #2966 from Malus-risus/master
Update dependency
2024-04-03 10:02:45 +08:00
Το μοχθηρό ^_^
554c7b5687 Update build.gradle.kts 2024-04-02 09:34:43 +08:00
Το μοχθηρό ^_^
2d987313a7 Delete renovate.json 2024-04-01 14:02:42 +08:00
Το μοχθηρό ^_^
9eebe32bdf Merge pull request #31 from Malus-risus/renovate/com.android.library-8.x
Update plugin com.android.library to v8.3.1
2024-04-01 13:24:07 +08:00
renovate[bot]
8e9da0ad6f Update plugin com.android.library to v8.3.1 2024-04-01 05:15:22 +00:00
Το μοχθηρό ^_^
2f20dea611 Merge pull request #30 from Malus-risus/renovate/com.android.application-8.x
Update plugin com.android.application to v8.3.1
2024-04-01 13:14:51 +08:00
renovate[bot]
167baf64a9 Update plugin com.android.application to v8.3.1 2024-04-01 04:47:28 +00:00
Το μοχθηρό ^_^
bd7a214f7f Merge pull request #29 from Malus-risus/renovate/gradle-8.x
Update dependency gradle to v8.7
2024-04-01 12:17:52 +08:00
Το μοχθηρό ^_^
f16d2d9a74 Merge pull request #28 from Malus-risus/renovate/com.tencent-mmkv-static-1.x
Update dependency com.tencent:mmkv-static to v1.3.4
2024-04-01 12:17:42 +08:00
renovate[bot]
e984d2c274 Update dependency gradle to v8.7 2024-04-01 04:09:58 +00:00
renovate[bot]
8720d087ea Update dependency com.tencent:mmkv-static to v1.3.4 2024-04-01 04:09:24 +00:00
Το μοχθηρό ^_^
a1e19b9fcd Create renovate.json 2024-04-01 12:08:53 +08:00
151 changed files with 3919 additions and 2651 deletions

View File

@@ -12,11 +12,11 @@ on:
jobs: jobs:
build: build:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@v4 uses: actions/checkout@v4
- name: Setup Java - name: Setup Java
uses: actions/setup-java@v4 uses: actions/setup-java@v4
with: with:
@@ -26,7 +26,7 @@ jobs:
- name: Setup Golang - name: Setup Golang
uses: actions/setup-go@v5 uses: actions/setup-go@v5
with: with:
go-version: '1.21.4' go-version: '1.22.2'
- name: Install gomobile - name: Install gomobile
run: | run: |
@@ -36,7 +36,7 @@ jobs:
- name: Setup Android environment - name: Setup Android environment
uses: android-actions/setup-android@v3 uses: android-actions/setup-android@v3
- name: Build dependencies - name: Build dependencies
run: | run: |
@@ -53,8 +53,7 @@ jobs:
- name: Build APK - name: Build APK
run: | run: |
cd ${{ github.workspace }}/V2rayNG cd ${{ github.workspace }}/V2rayNG
chmod 777 * chmod 755 gradlew
sed -i 's/org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8/org.gradle.jvmargs=-Xmx2048m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8/' ${{ github.workspace }}/V2rayNG/gradle.properties
./gradlew assembleDebug ./gradlew assembleDebug
- name: Upload APK - name: Upload APK

View File

@@ -11,9 +11,18 @@ android {
applicationId = "com.v2ray.ang" applicationId = "com.v2ray.ang"
minSdk = 21 minSdk = 21
targetSdk = 34 targetSdk = 34
versionCode = 552 versionCode = 579
versionName = "1.8.19" versionName = "1.8.35"
multiDexEnabled = true multiDexEnabled = true
splits.abi {
reset()
include(
"arm64-v8a",
"armeabi-v7a",
"x86_64",
"x86"
)
}
} }
compileOptions { compileOptions {
@@ -51,7 +60,7 @@ android {
applicationVariants.all { applicationVariants.all {
val variant = this val variant = this
val versionCodes = val versionCodes =
mapOf("armeabi-v7a" to 1, "arm64-v8a" to 2, "x86" to 3, "x86_64" to 4) mapOf("armeabi-v7a" to 4, "arm64-v8a" to 4, "x86" to 4, "x86_64" to 4, "universal" to 4)
variant.outputs variant.outputs
.map { it as com.android.build.gradle.internal.api.ApkVariantOutputImpl } .map { it as com.android.build.gradle.internal.api.ApkVariantOutputImpl }
@@ -59,7 +68,7 @@ android {
val abi = if (output.getFilter("ABI") != null) val abi = if (output.getFilter("ABI") != null)
output.getFilter("ABI") output.getFilter("ABI")
else else
"all" "universal"
output.outputFileName = "v2rayNG_${variant.versionName}_${abi}.apk" output.outputFileName = "v2rayNG_${variant.versionName}_${abi}.apk"
if(versionCodes.containsKey(abi)) if(versionCodes.containsKey(abi))
@@ -77,48 +86,54 @@ android {
viewBinding = true viewBinding = true
buildConfig = true buildConfig = true
} }
packagingOptions {
jniLibs {
useLegacyPackaging = true
}
}
} }
dependencies { dependencies {
implementation(fileTree(mapOf("dir" to "libs", "include" to listOf("*.aar","*.jar")))) implementation(fileTree(mapOf("dir" to "libs", "include" to listOf("*.aar","*.jar"))))
testImplementation("junit:junit:4.13.2") testImplementation(libs.junit)
implementation(libs.flexbox)
// Androidx // Androidx
implementation("androidx.constraintlayout:constraintlayout:2.1.4") implementation(libs.constraintlayout)
implementation("androidx.legacy:legacy-support-v4:1.0.0") implementation(libs.legacy.support.v4)
implementation("androidx.appcompat:appcompat:1.6.1") implementation(libs.appcompat)
implementation("com.google.android.material:material:1.11.0") implementation(libs.material)
implementation("androidx.cardview:cardview:1.0.0") implementation(libs.cardview)
implementation("androidx.preference:preference-ktx:1.2.1") implementation(libs.preference.ktx)
implementation("androidx.recyclerview:recyclerview:1.3.2") implementation(libs.recyclerview)
implementation("androidx.fragment:fragment-ktx:1.6.2") implementation(libs.fragment.ktx)
implementation("androidx.multidex:multidex:2.0.1") implementation(libs.multidex)
implementation("androidx.viewpager2:viewpager2:1.1.0-beta02") implementation(libs.viewpager2)
// Androidx ktx // Androidx ktx
implementation("androidx.activity:activity-ktx:1.8.2") implementation(libs.activity.ktx)
implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.7.0") implementation(libs.lifecycle.viewmodel.ktx)
implementation("androidx.lifecycle:lifecycle-livedata-ktx:2.7.0") implementation(libs.lifecycle.livedata.ktx)
implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.7.0") implementation(libs.lifecycle.runtime.ktx)
//kotlin //kotlin
implementation("org.jetbrains.kotlin:kotlin-reflect:1.9.23") implementation(libs.kotlin.reflect)
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.8.0") implementation(libs.kotlinx.coroutines.core)
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.8.0") implementation(libs.kotlinx.coroutines.android)
implementation("com.tencent:mmkv-static:1.3.3") implementation(libs.mmkv.static)
implementation("com.google.code.gson:gson:2.10.1") implementation(libs.gson)
implementation("io.reactivex:rxjava:1.3.8") implementation(libs.rxjava)
implementation("io.reactivex:rxandroid:1.2.1") implementation(libs.rxandroid)
implementation("com.tbruyelle.rxpermissions:rxpermissions:0.9.4@aar") implementation(libs.rxpermissions)
implementation("com.github.jorgecastilloprz:fabprogresscircle:1.01@aar") implementation(libs.toastcompat)
implementation("me.drakeet.support:toastcompat:1.1.0") implementation(libs.editorkit)
implementation("com.blacksquircle.ui:editorkit:2.9.0") implementation(libs.language.base)
implementation("com.blacksquircle.ui:language-base:2.9.0") implementation(libs.language.json)
implementation("com.blacksquircle.ui:language-json:2.9.0") implementation(libs.quickie.bundled)
implementation("io.github.g00fy2.quickie:quickie-bundled:1.9.0") implementation(libs.core)
implementation("com.google.zxing:core:3.5.3")
implementation("androidx.work:work-runtime-ktx:2.8.1") implementation(libs.work.runtime.ktx)
implementation("androidx.work:work-multiprocess:2.8.1") implementation(libs.work.multiprocess)
} }

View File

@@ -54,7 +54,7 @@
<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"/>--> <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" />
@@ -127,6 +127,9 @@
<data android:host="install-sub"/> <data android:host="install-sub"/>
</intent-filter> </intent-filter>
</activity> </activity>
<activity
android:exported="false"
android:name=".ui.AboutActivity" />
<service <service
android:name=".service.V2RayVpnService" android:name=".service.V2RayVpnService"
@@ -224,6 +227,16 @@
</provider> </provider>
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.cache"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/cache_paths"/>
</provider>
</application> </application>
</manifest> </manifest>

View File

@@ -1,132 +1,2 @@
domain:12306.com, geosite:cn,
domain:51ym.me, geosite:geolocation-cn
domain:52pojie.cn,
domain:8686c.com,
domain:abercrombie.com,
domain:adobesc.com,
domain:air-matters.com,
domain:air-matters.io,
domain:airtable.com,
domain:akadns.net,
domain:apache.org,
domain:api.crisp.chat,
domain:api.termius.com,
domain:appshike.com,
domain:appstore.com,
domain:aweme.snssdk.com,
domain:bababian.com,
domain:battle.net,
domain:beatsbydre.com,
domain:bet365.com,
domain:bilibili.cn,
domain:ccgslb.com,
domain:ccgslb.net,
domain:chunbo.com,
domain:chunboimg.com,
domain:clashroyaleapp.com,
domain:cloudsigma.com,
domain:cloudxns.net,
domain:cmfu.com,
domain:culturedcode.com,
domain:dct-cloud.com,
domain:didialift.com,
domain:douyutv.com,
domain:duokan.com,
domain:dytt8.net,
domain:easou.com,
domain:ecitic.net,
domain:eclipse.org,
domain:eudic.net,
domain:ewqcxz.com,
domain:fir.im,
domain:frdic.com,
domain:fresh-ideas.cc,
domain:godic.net,
domain:goodread.com,
domain:haibian.com,
domain:hdslb.net,
domain:hollisterco.com,
domain:hongxiu.com,
domain:hxcdn.net,
domain:images.unsplash.com,
domain:img4me.com,
domain:ipify.org,
domain:ixdzs.com,
domain:jd.hk,
domain:jianshuapi.com,
domain:jomodns.com,
domain:jsboxbbs.com,
domain:knewone.com,
domain:kuaidi100.com,
domain:lemicp.com,
domain:letvcloud.com,
domain:lizhi.io,
domain:localizecdn.com,
domain:lucifr.com,
domain:luoo.net,
domain:mai.tn,
domain:maven.org,
domain:miwifi.com,
domain:moji.com,
domain:moke.com,
domain:mtalk.google.com,
domain:mxhichina.com,
domain:myqcloud.com,
domain:myunlu.com,
domain:netease.com,
domain:nfoservers.com,
domain:nssurge.com,
domain:nuomi.com,
domain:ourdvs.com,
domain:overcast.fm,
domain:paypal.com,
domain:paypalobjects.com,
domain:pgyer.com,
domain:qdaily.com,
domain:qdmm.com,
domain:qin.io,
domain:qingmang.me,
domain:qingmang.mobi,
domain:qqurl.com,
domain:rarbg.to,
domain:rrmj.tv,
domain:ruguoapp.com,
domain:sm.ms,
domain:snwx.com,
domain:soku.com,
domain:startssl.com,
domain:store.steampowered.com,
domain:symcd.com,
domain:teamviewer.com,
domain:tmzvps.com,
domain:trello.com,
domain:trellocdn.com,
domain:ttmeiju.com,
domain:udache.com,
domain:uxengine.net,
domain:weather.bjango.com,
domain:weather.com,
domain:webqxs.com,
domain:weico.cc,
domain:wenku8.net,
domain:werewolf.53site.com,
domain:windowsupdate.com,
domain:wkcdn.com,
domain:workflowy.com,
domain:xdrig.com,
domain:xiaojukeji.com,
domain:xiaomi.net,
domain:xiaomicp.com,
domain:ximalaya.com,
domain:xitek.com,
domain:xmcdn.com,
domain:xslb.net,
domain:xteko.com,
domain:yach.me,
domain:yixia.com,
domain:yunjiasu-cdn.net,
domain:zealer.com,
domain:zgslb.net,
domain:zimuzu.tv,
domain:zmz002.com,
domain:samsungdm.com,

View File

@@ -1,33 +1 @@
geosite:google, geosite:geolocation-!cn
geosite:github,
geosite:netflix,
geosite:steam,
geosite:telegram,
geosite:tumblr,
geosite:speedtest,
geosite:bbc,
domain:gvt1.com,
domain:textnow.com,
domain:twitch.tv,
domain:wikileaks.org,
domain:naver.com,
91.108.4.0/22,
91.108.8.0/22,
91.108.12.0/22,
91.108.20.0/22,
91.108.36.0/23,
91.108.38.0/23,
91.108.56.0/22,
149.154.160.0/20,
149.154.164.0/22,
149.154.172.0/22,
74.125.0.0/16,
173.194.0.0/16,
172.217.0.0/16,
216.58.200.0/24,
216.58.220.0/24,
91.108.56.116,
91.108.56.0/24,
109.239.140.0/24,
149.154.167.0/24,
149.154.175.0/24,

View File

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

View File

@@ -2,13 +2,13 @@ package com.v2ray.ang
import android.content.Context import android.content.Context
import androidx.multidex.MultiDexApplication import androidx.multidex.MultiDexApplication
import androidx.preference.PreferenceManager
import androidx.work.Configuration import androidx.work.Configuration
import com.tencent.mmkv.MMKV import com.tencent.mmkv.MMKV
import com.v2ray.ang.util.Utils
class AngApplication : MultiDexApplication(), Configuration.Provider { class AngApplication : MultiDexApplication(), Configuration.Provider {
companion object { companion object {
const val PREF_LAST_VERSION = "pref_last_version" //const val PREF_LAST_VERSION = "pref_last_version"
lateinit var application: AngApplication lateinit var application: AngApplication
} }
@@ -17,21 +17,23 @@ class AngApplication : MultiDexApplication(), Configuration.Provider {
application = this application = this
} }
var firstRun = false //var firstRun = false
private set // private set
override fun onCreate() { override fun onCreate() {
super.onCreate() super.onCreate()
// LeakCanary.install(this) // LeakCanary.install(this)
val defaultSharedPreferences = PreferenceManager.getDefaultSharedPreferences(this) // val defaultSharedPreferences = PreferenceManager.getDefaultSharedPreferences(this)
firstRun = defaultSharedPreferences.getInt(PREF_LAST_VERSION, 0) != BuildConfig.VERSION_CODE // firstRun = defaultSharedPreferences.getInt(PREF_LAST_VERSION, 0) != BuildConfig.VERSION_CODE
if (firstRun) // if (firstRun)
defaultSharedPreferences.edit().putInt(PREF_LAST_VERSION, BuildConfig.VERSION_CODE).apply() // defaultSharedPreferences.edit().putInt(PREF_LAST_VERSION, BuildConfig.VERSION_CODE).apply()
//Logger.init().logLevel(if (BuildConfig.DEBUG) LogLevel.FULL else LogLevel.NONE) //Logger.init().logLevel(if (BuildConfig.DEBUG) LogLevel.FULL else LogLevel.NONE)
MMKV.initialize(this) MMKV.initialize(this)
Utils.setNightMode(application)
} }
override fun getWorkManagerConfiguration(): Configuration { override fun getWorkManagerConfiguration(): Configuration {

View File

@@ -7,48 +7,68 @@ package com.v2ray.ang
object AppConfig { object AppConfig {
const val ANG_PACKAGE = BuildConfig.APPLICATION_ID const val ANG_PACKAGE = BuildConfig.APPLICATION_ID
const val DIR_ASSETS = "assets" const val DIR_ASSETS = "assets"
const val DIR_BACKUPS = "backups"
// legacy // legacy
const val ANG_CONFIG = "ang_config" const val ANG_CONFIG = "ang_config"
const val PREF_INAPP_BUY_IS_PREMIUM = "pref_inapp_buy_is_premium" const val PREF_INAPP_BUY_IS_PREMIUM = "pref_inapp_buy_is_premium"
const val PREF_ROUTING_CUSTOM = "pref_routing_custom"
// Preferences mapped to MMKV // Preferences mapped to MMKV
const val PREF_MODE = "pref_mode"
const val PREF_SPEED_ENABLED = "pref_speed_enabled"
const val PREF_SNIFFING_ENABLED = "pref_sniffing_enabled" const val PREF_SNIFFING_ENABLED = "pref_sniffing_enabled"
const val PREF_PROXY_SHARING = "pref_proxy_sharing_enabled" const val PREF_ROUTE_ONLY_ENABLED = "pref_route_only_enabled"
const val PREF_PER_APP_PROXY = "pref_per_app_proxy"
const val PREF_PER_APP_PROXY_SET = "pref_per_app_proxy_set"
const val PREF_BYPASS_APPS = "pref_bypass_apps"
const val PREF_LOCAL_DNS_ENABLED = "pref_local_dns_enabled" const val PREF_LOCAL_DNS_ENABLED = "pref_local_dns_enabled"
const val PREF_FAKE_DNS_ENABLED = "pref_fake_dns_enabled" const val PREF_FAKE_DNS_ENABLED = "pref_fake_dns_enabled"
const val PREF_VPN_DNS = "pref_vpn_dns"
const val PREF_REMOTE_DNS = "pref_remote_dns"
const val PREF_DOMESTIC_DNS = "pref_domestic_dns"
const val PREF_LOCAL_DNS_PORT = "pref_local_dns_port" const val PREF_LOCAL_DNS_PORT = "pref_local_dns_port"
const val PREF_ALLOW_INSECURE = "pref_allow_insecure" const val PREF_VPN_DNS = "pref_vpn_dns"
const val PREF_SOCKS_PORT = "pref_socks_port"
const val PREF_HTTP_PORT = "pref_http_port"
const val PREF_LOGLEVEL = "pref_core_loglevel"
const val PREF_LANGUAGE = "pref_language"
const val PREF_PREFER_IPV6 = "pref_prefer_ipv6"
const val PREF_ROUTING_DOMAIN_STRATEGY = "pref_routing_domain_strategy" const val PREF_ROUTING_DOMAIN_STRATEGY = "pref_routing_domain_strategy"
const val PREF_ROUTING_MODE = "pref_routing_mode" const val PREF_ROUTING_MODE = "pref_routing_mode"
const val PREF_V2RAY_ROUTING_AGENT = "pref_v2ray_routing_agent" const val PREF_V2RAY_ROUTING_AGENT = "pref_v2ray_routing_agent"
const val PREF_V2RAY_ROUTING_DIRECT = "pref_v2ray_routing_direct" const val PREF_V2RAY_ROUTING_DIRECT = "pref_v2ray_routing_direct"
const val PREF_V2RAY_ROUTING_BLOCKED = "pref_v2ray_routing_blocked" const val PREF_V2RAY_ROUTING_BLOCKED = "pref_v2ray_routing_blocked"
const val PREF_PER_APP_PROXY = "pref_per_app_proxy" const val PREF_ROUTING_CUSTOM = "pref_routing_custom"
const val PREF_PER_APP_PROXY_SET = "pref_per_app_proxy_set"
const val PREF_BYPASS_APPS = "pref_bypass_apps"
const val PREF_CONFIRM_REMOVE = "pref_confirm_remove"
const val PREF_START_SCAN_IMMEDIATE = "pref_start_scan_immediate"
const val PREF_MUX_ENABLED = "pref_mux_enabled" const val PREF_MUX_ENABLED = "pref_mux_enabled"
const val PREF_MUX_CONCURRENCY = "pref_mux_concurency" const val PREF_MUX_CONCURRENCY = "pref_mux_concurency"
const val PREF_MUX_XUDP_CONCURRENCY = "pref_mux_xudp_concurency" const val PREF_MUX_XUDP_CONCURRENCY = "pref_mux_xudp_concurency"
const val PREF_MUX_XUDP_QUIC = "pref_mux_xudp_quic" const val PREF_MUX_XUDP_QUIC = "pref_mux_xudp_quic"
const val PREF_FRAGMENT_ENABLED = "pref_fragment_enabled" const val PREF_FRAGMENT_ENABLED = "pref_fragment_enabled"
const val PREF_FRAGMENT_PACKETS = "pref_fragment_packets" const val PREF_FRAGMENT_PACKETS = "pref_fragment_packets"
const val PREF_FRAGMENT_LENGTH = "pref_fragment_length" const val PREF_FRAGMENT_LENGTH = "pref_fragment_length"
const val PREF_FRAGMENT_INTERVAL = "pref_fragment_interval" const val PREF_FRAGMENT_INTERVAL = "pref_fragment_interval"
const val SUBSCRIPTION_AUTO_UPDATE = "pref_auto_update_subscription"
const val SUBSCRIPTION_AUTO_UPDATE_INTERVAL = "pref_auto_update_interval"
const val SUBSCRIPTION_DEFAULT_UPDATE_INTERVAL = "1440" // 24 hours
const val SUBSCRIPTION_UPDATE_TASK_NAME = "subscription_updater"
const val PREF_SPEED_ENABLED = "pref_speed_enabled"
const val PREF_CONFIRM_REMOVE = "pref_confirm_remove"
const val PREF_START_SCAN_IMMEDIATE = "pref_start_scan_immediate"
const val PREF_LANGUAGE = "pref_language"
const val PREF_UI_MODE_NIGHT = "pref_ui_mode_night"
const val PREF_PREFER_IPV6 = "pref_prefer_ipv6"
const val PREF_PROXY_SHARING = "pref_proxy_sharing_enabled"
const val PREF_ALLOW_INSECURE = "pref_allow_insecure"
const val PREF_SOCKS_PORT = "pref_socks_port"
const val PREF_HTTP_PORT = "pref_http_port"
const val PREF_REMOTE_DNS = "pref_remote_dns"
const val PREF_DOMESTIC_DNS = "pref_domestic_dns"
const val PREF_DELAY_TEST_URL = "pref_delay_test_url"
const val PREF_LOGLEVEL = "pref_core_loglevel"
const val PREF_MODE = "pref_mode"
const val CACHE_SUBSCRIPTION_ID = "cache_subscription_id"
const val CACHE_KEYWORD_FILTER = "cache_keyword_filter"
//Preferences mapped to MMKV End
const val PROTOCOL_HTTP: String = "http://" const val PROTOCOL_HTTP: String = "http://"
const val PROTOCOL_HTTPS: String = "https://" const val PROTOCOL_HTTPS: String = "https://"
const val PROTOCOL_FREEDOM: String = "freedom" const val PROTOCOL_FREEDOM: String = "freedom"
@@ -63,7 +83,7 @@ object AppConfig {
const val TASKER_EXTRA_BUNDLE_GUID = "tasker_extra_bundle_guid" const val TASKER_EXTRA_BUNDLE_GUID = "tasker_extra_bundle_guid"
const val TASKER_DEFAULT_GUID = "Default" const val TASKER_DEFAULT_GUID = "Default"
const val TAG_AGENT = "proxy" const val TAG_PROXY = "proxy"
const val TAG_DIRECT = "direct" const val TAG_DIRECT = "direct"
const val TAG_BLOCKED = "block" const val TAG_BLOCKED = "block"
const val TAG_FRAGMENT = "fragment" const val TAG_FRAGMENT = "fragment"
@@ -72,14 +92,19 @@ object AppConfig {
"https://raw.githubusercontent.com/2dust/androidpackagenamelist/master/proxy.txt" "https://raw.githubusercontent.com/2dust/androidpackagenamelist/master/proxy.txt"
const val v2rayCustomRoutingListUrl = const val v2rayCustomRoutingListUrl =
"https://raw.githubusercontent.com/2dust/v2rayCustomRoutingList/master/" "https://raw.githubusercontent.com/2dust/v2rayCustomRoutingList/master/"
const val v2rayNGIssues = "https://github.com/2dust/v2rayNG/issues" const val v2rayNGUrl = "https://github.com/2dust/v2rayNG"
const val v2rayNGWikiMode = "https://github.com/2dust/v2rayNG/wiki/Mode" const val v2rayNGIssues = "$v2rayNGUrl/issues"
const val v2rayNGWikiMode = "$v2rayNGUrl/wiki/Mode"
const val v2rayNGPrivacyPolicy = "https://raw.githubusercontent.com/2dust/v2rayNG/master/CR.md" const val v2rayNGPrivacyPolicy = "https://raw.githubusercontent.com/2dust/v2rayNG/master/CR.md"
const val promotionUrl = "aHR0cHM6Ly85LjIzNDQ1Ni54eXovYWJjLmh0bWw=" const val PromotionUrl = "aHR0cHM6Ly85LjIzNDQ1Ni54eXovYWJjLmh0bWw="
const val geoUrl = "https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/" const val GeoUrl = "https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/"
const val TgChannelUrl = "https://t.me/github_2dust"
const val DelayTestUrl = "https://www.gstatic.com/generate_204"
const val DelayTestUrl2 = "https://www.google.com/generate_204"
const val DNS_AGENT = "1.1.1.1" const val DNS_PROXY = "1.1.1.1"
const val DNS_DIRECT = "223.5.5.5" const val DNS_DIRECT = "223.5.5.5"
const val DNS_VPN = "1.1.1.1"
const val PORT_LOCAL_DNS = "10853" const val PORT_LOCAL_DNS = "10853"
const val PORT_SOCKS = "10808" const val PORT_SOCKS = "10808"
@@ -103,13 +128,4 @@ object AppConfig {
const val MSG_MEASURE_CONFIG = 7 const val MSG_MEASURE_CONFIG = 7
const val MSG_MEASURE_CONFIG_SUCCESS = 71 const val MSG_MEASURE_CONFIG_SUCCESS = 71
const val MSG_MEASURE_CONFIG_CANCEL = 72 const val MSG_MEASURE_CONFIG_CANCEL = 72
// subscription settings
const val SUBSCRIPTION_AUTO_UPDATE = "pref_auto_update_subscription"
const val SUBSCRIPTION_AUTO_UPDATE_INTERVAL = "pref_auto_update_interval"
const val SUBSCRIPTION_DEFAULT_UPDATE_INTERVAL = "1440" // 24 hours
const val SUBSCRIPTION_UPDATE_TASK_NAME = "subscription_updater"
const val CACHE_SUBSCRIPTION_ID = "cache_subscription_id"
const val CACHE_KEYWORD_FILTER = "cache_keyword_filter"
} }

View File

@@ -1,32 +0,0 @@
package com.v2ray.ang.dto
data class AngConfig(
var index: Int,
var vmess: ArrayList<VmessBean>,
var subItem: ArrayList<SubItemBean>
) {
data class VmessBean(var guid: String = "123456",
var address: String = "v2ray.cool",
var port: Int = 10086,
var id: String = "a3482e88-686a-4a58-8126-99c9df64b7bf",
var alterId: Int = 64,
var security: String = "aes-128-cfb",
var network: String = "tcp",
var remarks: String = "def",
var headerType: String = "",
var requestHost: String = "",
var path: String = "",
var streamSecurity: String = "",
var allowInsecure: String = "",
var configType: Int = 1,
var configVersion: Int = 1,
var testResult: String = "",
var subid: String = "",
var flow: String = "",
var sni: String = "")
data class SubItemBean(var id: String = "",
var remarks: String = "",
var url: String = "",
var enabled: Boolean = true)
}

View File

@@ -1,6 +1,6 @@
package com.v2ray.ang.dto package com.v2ray.ang.dto
import com.v2ray.ang.AppConfig.TAG_AGENT import com.v2ray.ang.AppConfig.TAG_PROXY
import com.v2ray.ang.AppConfig.TAG_BLOCKED import com.v2ray.ang.AppConfig.TAG_BLOCKED
import com.v2ray.ang.AppConfig.TAG_DIRECT import com.v2ray.ang.AppConfig.TAG_DIRECT
import com.v2ray.ang.util.Utils import com.v2ray.ang.util.Utils
@@ -58,7 +58,7 @@ data class ServerConfig(
fun getAllOutboundTags(): MutableList<String> { fun getAllOutboundTags(): MutableList<String> {
if (configType != EConfigType.CUSTOM) { if (configType != EConfigType.CUSTOM) {
return mutableListOf(TAG_AGENT, TAG_DIRECT, TAG_BLOCKED) return mutableListOf(TAG_PROXY, TAG_DIRECT, TAG_BLOCKED)
} }
fullConfig?.let { config -> fullConfig?.let { config ->
return config.outbounds.map { it.tag }.toMutableList() return config.outbounds.map { it.tag }.toMutableList()

View File

@@ -60,7 +60,8 @@ data class V2rayConfig(
data class SniffingBean(var enabled: Boolean, data class SniffingBean(var enabled: Boolean,
val destOverride: ArrayList<String>, val destOverride: ArrayList<String>,
val metadataOnly: Boolean? = null) val metadataOnly: Boolean? = null,
var routeOnly: Boolean? = null)
} }
data class OutboundBean(var tag: String = "proxy", data class OutboundBean(var tag: String = "proxy",
@@ -138,6 +139,7 @@ data class V2rayConfig(
var kcpSettings: KcpSettingsBean? = null, var kcpSettings: KcpSettingsBean? = null,
var wsSettings: WsSettingsBean? = null, var wsSettings: WsSettingsBean? = null,
var httpupgradeSettings: HttpupgradeSettingsBean? = null, var httpupgradeSettings: HttpupgradeSettingsBean? = null,
var splithttpSettings: SplithttpSettingsBean? = null,
var httpSettings: HttpSettingsBean? = null, var httpSettings: HttpSettingsBean? = null,
var tlsSettings: TlsSettingsBean? = null, var tlsSettings: TlsSettingsBean? = null,
var quicSettings: QuicSettingBean? = null, var quicSettings: QuicSettingBean? = null,
@@ -156,7 +158,7 @@ data class V2rayConfig(
var headers: HeadersBean = HeadersBean(), var headers: HeadersBean = HeadersBean(),
val version: String? = null, val version: String? = null,
val method: String? = null) { val method: String? = null) {
data class HeadersBean(var Host: List<String> = ArrayList(), data class HeadersBean(var Host: List<String>? = ArrayList(),
@SerializedName("User-Agent") @SerializedName("User-Agent")
val userAgent: List<String>? = null, val userAgent: List<String>? = null,
@SerializedName("Accept-Encoding") @SerializedName("Accept-Encoding")
@@ -191,6 +193,10 @@ data class V2rayConfig(
var host: String = "", var host: String = "",
val acceptProxyProtocol: Boolean? = null) val acceptProxyProtocol: Boolean? = null)
data class SplithttpSettingsBean(var path: String = "",
var host: String = "",
val maxUploadSize: Int? = null,
val maxConcurrentUploads: Int? = null)
data class HttpSettingsBean(var host: List<String> = ArrayList(), data class HttpSettingsBean(var host: List<String> = ArrayList(),
var path: String = "") var path: String = "")
@@ -226,7 +232,10 @@ data class V2rayConfig(
data class GrpcSettingsBean(var serviceName: String = "", data class GrpcSettingsBean(var serviceName: String = "",
var authority: String? = null, var authority: String? = null,
var multiMode: Boolean? = null) var multiMode: Boolean? = null,
var idle_timeout: Int? = null,
var health_check_timeout: Int? = null
)
fun populateTransportSettings(transport: String, headerType: String?, host: String?, path: String?, seed: String?, fun populateTransportSettings(transport: String, headerType: String?, host: String?, path: String?, seed: String?,
quicSecurity: String?, key: String?, mode: String?, serviceName: String?, quicSecurity: String?, key: String?, mode: String?, serviceName: String?,
@@ -243,7 +252,7 @@ data class V2rayConfig(
requestObj.headers.Host = (host ?: "").split(",").map { it.trim() }.filter { it.isNotEmpty() } requestObj.headers.Host = (host ?: "").split(",").map { it.trim() }.filter { it.isNotEmpty() }
requestObj.path = (path ?: "").split(",").map { it.trim() }.filter { it.isNotEmpty() } requestObj.path = (path ?: "").split(",").map { it.trim() }.filter { it.isNotEmpty() }
tcpSetting.header.request = requestObj tcpSetting.header.request = requestObj
sni = requestObj.headers.Host.getOrNull(0) ?: sni sni = requestObj.headers.Host?.getOrNull(0) ?: sni
} }
} else { } else {
tcpSetting.header.type = "none" tcpSetting.header.type = "none"
@@ -275,6 +284,13 @@ data class V2rayConfig(
httpupgradeSetting.path = path ?: "/" httpupgradeSetting.path = path ?: "/"
httpupgradeSettings = httpupgradeSetting httpupgradeSettings = httpupgradeSetting
} }
"splithttp" -> {
val splithttpSetting = SplithttpSettingsBean()
splithttpSetting.host = host ?: ""
sni = splithttpSetting.host
splithttpSetting.path = path ?: "/"
splithttpSettings = splithttpSetting
}
"h2", "http" -> { "h2", "http" -> {
network = "h2" network = "h2"
val h2Setting = HttpSettingsBean() val h2Setting = HttpSettingsBean()
@@ -295,6 +311,8 @@ data class V2rayConfig(
grpcSetting.multiMode = mode == "multi" grpcSetting.multiMode = mode == "multi"
grpcSetting.serviceName = serviceName ?: "" grpcSetting.serviceName = serviceName ?: ""
grpcSetting.authority = authority ?: "" grpcSetting.authority = authority ?: ""
grpcSetting.idle_timeout = 60
grpcSetting.health_check_timeout = 20
sni = authority ?: "" sni = authority ?: ""
grpcSettings = grpcSetting grpcSettings = grpcSetting
} }
@@ -412,6 +430,12 @@ data class V2rayConfig(
httpupgradeSetting.host, httpupgradeSetting.host,
httpupgradeSetting.path) httpupgradeSetting.path)
} }
"splithttp" -> {
val splithttpSetting = streamSettings?.splithttpSettings ?: return null
listOf("",
splithttpSetting.host,
splithttpSetting.path)
}
"h2" -> { "h2" -> {
val h2Setting = streamSettings?.httpSettings ?: return null val h2Setting = streamSettings?.httpSettings ?: return null
listOf("", listOf("",
@@ -456,7 +480,7 @@ data class V2rayConfig(
var rules: ArrayList<RulesBean>, var rules: ArrayList<RulesBean>,
val balancers: List<Any>? = null) { val balancers: List<Any>? = null) {
data class RulesBean(var type: String = "", data class RulesBean(
var ip: ArrayList<String>? = null, var ip: ArrayList<String>? = null,
var domain: ArrayList<String>? = null, var domain: ArrayList<String>? = null,
var outboundTag: String = "", var outboundTag: String = "",
@@ -490,7 +514,7 @@ data class V2rayConfig(
fun getProxyOutbound(): OutboundBean? { fun getProxyOutbound(): OutboundBean? {
outbounds?.forEach { outbound -> outbounds?.forEach { outbound ->
EConfigType.values().forEach { EConfigType.entries.forEach {
if (outbound.protocol.equals(it.name, true)) { if (outbound.protocol.equals(it.name, true)) {
return outbound return outbound
} }

View File

@@ -8,8 +8,8 @@ import android.content.Context
import android.content.Intent import android.content.Intent
import android.os.Build import android.os.Build
import android.widget.RemoteViews import android.widget.RemoteViews
import com.v2ray.ang.R
import com.v2ray.ang.AppConfig import com.v2ray.ang.AppConfig
import com.v2ray.ang.R
import com.v2ray.ang.service.V2RayServiceManager import com.v2ray.ang.service.V2RayServiceManager
import com.v2ray.ang.util.Utils import com.v2ray.ang.util.Utils
@@ -38,23 +38,11 @@ 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_stop_24dp)
remoteViews.setInt(R.id.image_switch, "setImageResource", R.drawable.ic_stat_name) remoteViews.setInt(R.id.layout_background, "setBackgroundResource", R.drawable.ic_rounded_corner_active)
}
remoteViews.setInt(
R.id.layout_switch,
"setBackgroundResource",
R.drawable.ic_rounded_corner_active
)
} else { } else {
if (!Utils.getDarkModeStatus(context)) { remoteViews.setInt(R.id.image_switch, "setImageResource", R.drawable.ic_play_24dp)
remoteViews.setInt(R.id.image_switch, "setImageResource", R.drawable.ic_stat_name_black) remoteViews.setInt(R.id.layout_background, "setBackgroundResource", R.drawable.ic_rounded_corner_inactive)
}
remoteViews.setInt(
R.id.layout_switch,
"setBackgroundResource",
R.drawable.ic_rounded_corner_grey
)
} }
for (appWidgetId in appWidgetIds) { for (appWidgetId in appWidgetIds) {

View File

@@ -58,23 +58,11 @@ object SubscriptionUpdater {
"subscription automatic update: ---${subscription.remarks}" "subscription automatic update: ---${subscription.remarks}"
) )
val configs = Utils.getUrlContentWithCustomUserAgent(subscription.url) val configs = Utils.getUrlContentWithCustomUserAgent(subscription.url)
importBatchConfig(configs, i.first) AngConfigManager.importBatchConfig(configs, i.first, false)
notification.setContentText("Updating ${subscription.remarks}") notification.setContentText("Updating ${subscription.remarks}")
} }
notificationManager.cancel(3) notificationManager.cancel(3)
return Result.success() return Result.success()
} }
} }
fun importBatchConfig(server: String?, subid: String = "") {
val append = subid.isEmpty()
val count = AngConfigManager.importBatchConfig(server, subid, append)
if (count <= 0) {
AngConfigManager.importBatchConfig(Utils.decode(server!!), subid, append)
}
if (count <= 0) {
AngConfigManager.appendCustomConfigServer(server, subid)
}
}
} }

View File

@@ -1,6 +1,9 @@
package com.v2ray.ang.service package com.v2ray.ang.service
import android.app.* import android.app.Notification
import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.PendingIntent
import android.content.BroadcastReceiver import android.content.BroadcastReceiver
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
@@ -60,12 +63,17 @@ object V2RayServiceManager {
private var mNotificationManager: NotificationManager? = null private var mNotificationManager: NotificationManager? = null
fun startV2Ray(context: Context) { fun startV2Ray(context: Context) {
if (v2rayPoint.isRunning) return
val guid = mainStorage?.decodeString(MmkvManager.KEY_SELECTED_SERVER) ?: return
val result = V2rayConfigUtil.getV2rayConfig(context, guid)
if (!result.status) return
if (settingsStorage?.decodeBool(AppConfig.PREF_PROXY_SHARING) == true) { if (settingsStorage?.decodeBool(AppConfig.PREF_PROXY_SHARING) == true) {
context.toast(R.string.toast_warning_pref_proxysharing_short) context.toast(R.string.toast_warning_pref_proxysharing_short)
} else { } else {
context.toast(R.string.toast_services_start) context.toast(R.string.toast_services_start)
} }
val intent = if (settingsStorage?.decodeString(AppConfig.PREF_MODE) ?: "VPN" == "VPN") { val intent = if ((settingsStorage?.decodeString(AppConfig.PREF_MODE) ?: "VPN") == "VPN") {
Intent(context.applicationContext, V2RayVpnService::class.java) Intent(context.applicationContext, V2RayVpnService::class.java)
} else { } else {
Intent(context.applicationContext, V2RayProxyOnlyService::class.java) Intent(context.applicationContext, V2RayProxyOnlyService::class.java)
@@ -123,42 +131,43 @@ object V2RayServiceManager {
val service = serviceControl?.get()?.getService() ?: return val service = serviceControl?.get()?.getService() ?: return
val guid = mainStorage?.decodeString(MmkvManager.KEY_SELECTED_SERVER) ?: return val guid = mainStorage?.decodeString(MmkvManager.KEY_SELECTED_SERVER) ?: return
val config = MmkvManager.decodeServerConfig(guid) ?: return val config = MmkvManager.decodeServerConfig(guid) ?: return
if (!v2rayPoint.isRunning) { if (v2rayPoint.isRunning) {
val result = V2rayConfigUtil.getV2rayConfig(service, guid) return
if (!result.status) }
return val result = V2rayConfigUtil.getV2rayConfig(service, guid)
if (!result.status)
return
try { try {
val mFilter = IntentFilter(AppConfig.BROADCAST_ACTION_SERVICE) val mFilter = IntentFilter(AppConfig.BROADCAST_ACTION_SERVICE)
mFilter.addAction(Intent.ACTION_SCREEN_ON) mFilter.addAction(Intent.ACTION_SCREEN_ON)
mFilter.addAction(Intent.ACTION_SCREEN_OFF) mFilter.addAction(Intent.ACTION_SCREEN_OFF)
mFilter.addAction(Intent.ACTION_USER_PRESENT) mFilter.addAction(Intent.ACTION_USER_PRESENT)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
service.registerReceiver(mMsgReceive, mFilter, Context.RECEIVER_EXPORTED) service.registerReceiver(mMsgReceive, mFilter, Context.RECEIVER_EXPORTED)
} else {
service.registerReceiver(mMsgReceive, mFilter)
}
} catch (e: Exception) {
Log.d(ANG_PACKAGE, e.toString())
}
v2rayPoint.configureFileContent = result.content
v2rayPoint.domainName = config.getV2rayPointDomainAndPort()
currentConfig = config
try {
v2rayPoint.runLoop(settingsStorage?.decodeBool(AppConfig.PREF_PREFER_IPV6) ?: false)
} catch (e: Exception) {
Log.d(ANG_PACKAGE, e.toString())
}
if (v2rayPoint.isRunning) {
MessageUtil.sendMsg2UI(service, AppConfig.MSG_STATE_START_SUCCESS, "")
showNotification()
} else { } else {
MessageUtil.sendMsg2UI(service, AppConfig.MSG_STATE_START_FAILURE, "") service.registerReceiver(mMsgReceive, mFilter)
cancelNotification()
} }
} catch (e: Exception) {
Log.d(ANG_PACKAGE, e.toString())
}
v2rayPoint.configureFileContent = result.content
v2rayPoint.domainName = config.getV2rayPointDomainAndPort()
currentConfig = config
try {
v2rayPoint.runLoop(settingsStorage?.decodeBool(AppConfig.PREF_PREFER_IPV6) ?: false)
} catch (e: Exception) {
Log.d(ANG_PACKAGE, e.toString())
}
if (v2rayPoint.isRunning) {
MessageUtil.sendMsg2UI(service, AppConfig.MSG_STATE_START_SUCCESS, "")
showNotification()
} else {
MessageUtil.sendMsg2UI(service, AppConfig.MSG_STATE_START_FAILURE, "")
cancelNotification()
} }
} }
@@ -234,11 +243,19 @@ object V2RayServiceManager {
var errstr = "" var errstr = ""
if (v2rayPoint.isRunning) { if (v2rayPoint.isRunning) {
try { try {
time = v2rayPoint.measureDelay() time = v2rayPoint.measureDelay(Utils.getDelayTestUrl())
} catch (e: Exception) { } catch (e: Exception) {
Log.d(ANG_PACKAGE, "measureV2rayDelay: $e") Log.d(ANG_PACKAGE, "measureV2rayDelay: $e")
errstr = e.message?.substringAfter("\":") ?: "empty message" errstr = e.message?.substringAfter("\":") ?: "empty message"
} }
if (time == -1L) {
try {
time = v2rayPoint.measureDelay(Utils.getDelayTestUrl(true))
} catch (e: Exception) {
Log.d(ANG_PACKAGE, "measureV2rayDelay: $e")
errstr = e.message?.substringAfter("\":") ?: "empty message"
}
}
} }
val result = if (time == -1L) { val result = if (time == -1L) {
service.getString(R.string.connection_test_error, errstr) service.getString(R.string.connection_test_error, errstr)
@@ -290,7 +307,7 @@ object V2RayServiceManager {
.setShowWhen(false) .setShowWhen(false)
.setOnlyAlertOnce(true) .setOnlyAlertOnce(true)
.setContentIntent(contentPendingIntent) .setContentIntent(contentPendingIntent)
.addAction(R.drawable.ic_close_grey_800_24dp, .addAction(R.drawable.ic_delete_24dp,
service.getString(R.string.notification_action_stop_v2ray), service.getString(R.string.notification_action_stop_v2ray),
stopV2RayPendingIntent) stopV2RayPendingIntent)
//.build() //.build()

View File

@@ -111,7 +111,8 @@ class V2RayVpnService : VpnService(), ServiceControl {
val builder = Builder() val builder = Builder()
//val enableLocalDns = defaultDPreference.getPrefBoolean(AppConfig.PREF_LOCAL_DNS_ENABLED, false) //val enableLocalDns = defaultDPreference.getPrefBoolean(AppConfig.PREF_LOCAL_DNS_ENABLED, false)
val routingMode = settingsStorage?.decodeString(AppConfig.PREF_ROUTING_MODE) ?: ERoutingMode.GLOBAL_PROXY.value val routingMode = settingsStorage?.decodeString(AppConfig.PREF_ROUTING_MODE)
?: ERoutingMode.BYPASS_LAN_MAINLAND.value
builder.setMtu(VPN_MTU) builder.setMtu(VPN_MTU)
builder.addAddress(PRIVATE_VLAN4_CLIENT, 30) builder.addAddress(PRIVATE_VLAN4_CLIENT, 30)

View File

@@ -0,0 +1,177 @@
package com.v2ray.ang.ui
import android.Manifest
import android.content.Intent
import android.os.Build
import android.os.Bundle
import androidx.activity.result.contract.ActivityResultContracts
import androidx.core.content.FileProvider
import com.tbruyelle.rxpermissions.RxPermissions
import com.tencent.mmkv.MMKV
import com.v2ray.ang.AppConfig
import com.v2ray.ang.BuildConfig
import com.v2ray.ang.R
import com.v2ray.ang.databinding.ActivityAboutBinding
import com.v2ray.ang.extension.toast
import com.v2ray.ang.util.SpeedtestUtil
import com.v2ray.ang.util.Utils
import com.v2ray.ang.util.ZipUtil
import java.io.File
import java.text.SimpleDateFormat
import java.util.Locale
class AboutActivity : BaseActivity() {
private lateinit var binding: ActivityAboutBinding
private val extDir by lazy { File(Utils.backupPath(this)) }
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityAboutBinding.inflate(layoutInflater)
val view = binding.root
setContentView(view)
title = getString(R.string.title_about)
binding.tvBackupSummary.text = this.getString(R.string.summary_configuration_backup, extDir)
binding.layoutBackup.setOnClickListener {
val ret = backupConfiguration(extDir.absolutePath)
if (ret.first) {
toast(R.string.toast_success)
} else {
toast(R.string.toast_failure)
}
}
binding.layoutShare.setOnClickListener {
val ret = backupConfiguration(cacheDir.absolutePath)
if (ret.first) {
startActivity(
Intent.createChooser(
Intent(Intent.ACTION_SEND).setType("application/zip")
.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
.putExtra(
Intent.EXTRA_STREAM, FileProvider.getUriForFile(
this, BuildConfig.APPLICATION_ID + ".cache", File(ret.second)
)
), getString(R.string.title_configuration_share)
)
)
} else {
toast(R.string.toast_failure)
}
}
binding.layoutRestore.setOnClickListener {
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) {
try {
showFileChooser()
} catch (e: Exception) {
e.printStackTrace()
}
} else
toast(R.string.toast_permission_denied)
}
}
binding.layoutSoureCcode.setOnClickListener {
Utils.openUri(this, AppConfig.v2rayNGUrl)
}
binding.layoutFeedback.setOnClickListener {
Utils.openUri(this, AppConfig.v2rayNGIssues)
}
binding.layoutTgChannel.setOnClickListener {
Utils.openUri(this, AppConfig.TgChannelUrl)
}
binding.layoutPrivacyPolicy.setOnClickListener {
Utils.openUri(this, AppConfig.v2rayNGPrivacyPolicy)
}
"v${BuildConfig.VERSION_NAME} (${SpeedtestUtil.getLibVersion()})".also {
binding.tvVersion.text = it
}
}
fun backupConfiguration(outputZipFilePos: String): Pair<Boolean, String> {
val dateFormated = SimpleDateFormat(
"yyyy-MM-dd-HH-mm-ss",
Locale.getDefault()
).format(System.currentTimeMillis())
val folderName = "${getString(R.string.app_name)}_${dateFormated}"
val backupDir = this.cacheDir.absolutePath + "/$folderName"
val outputZipFilePath = "$outputZipFilePos/$folderName.zip"
val count = MMKV.backupAllToDirectory(backupDir)
if (count <= 0) {
return Pair(false, "")
}
if (ZipUtil.zipFromFolder(backupDir, outputZipFilePath)) {
return Pair(true, outputZipFilePath)
} else {
return Pair(false, "")
}
}
fun restoreConfiguration(zipFile: File): Boolean {
val backupDir = this.cacheDir.absolutePath + "/${System.currentTimeMillis()}"
if (!ZipUtil.unzipToFolder(zipFile, backupDir)) {
return false
}
val count = MMKV.restoreAllFromDirectory(backupDir)
return count > 0
}
private fun showFileChooser() {
val intent = Intent(Intent.ACTION_GET_CONTENT)
intent.type = "*/*"
intent.addCategory(Intent.CATEGORY_OPENABLE)
try {
chooseFile.launch(Intent.createChooser(intent, getString(R.string.title_file_chooser)))
} catch (ex: android.content.ActivityNotFoundException) {
toast(R.string.toast_require_file_manager)
}
}
private val chooseFile =
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
val uri = it.data?.data
if (it.resultCode == RESULT_OK && uri != null) {
try {
try {
val targetFile =
File(this.cacheDir.absolutePath, "${System.currentTimeMillis()}.zip")
contentResolver.openInputStream(uri).use { input ->
targetFile.outputStream().use { fileOut ->
input?.copyTo(fileOut)
}
}
if (restoreConfiguration(targetFile)) {
toast(R.string.toast_success)
} else {
toast(R.string.toast_failure)
}
} catch (e: Exception) {
e.printStackTrace()
}
} catch (e: Exception) {
e.printStackTrace()
toast(e.message.toString())
}
}
}
}

View File

@@ -1,49 +1,50 @@
package com.v2ray.ang.ui package com.v2ray.ang.ui
import android.Manifest import android.Manifest
import android.content.* import android.content.ActivityNotFoundException
import android.content.Intent
import android.content.res.ColorStateList
import android.net.Uri import android.net.Uri
import android.net.VpnService import android.net.VpnService
import androidx.recyclerview.widget.LinearLayoutManager import android.os.Build
import android.view.Menu
import android.view.MenuItem
import com.tbruyelle.rxpermissions.RxPermissions
import com.v2ray.ang.R
import android.os.Bundle import android.os.Bundle
import android.text.TextUtils import android.text.TextUtils
import android.view.KeyEvent import android.view.KeyEvent
import com.v2ray.ang.AppConfig import android.view.Menu
import android.content.res.ColorStateList import android.view.MenuItem
import android.os.Build
import com.google.android.material.navigation.NavigationView
import androidx.core.content.ContextCompat
import androidx.core.view.GravityCompat
import androidx.appcompat.app.ActionBarDrawerToggle
import androidx.recyclerview.widget.ItemTouchHelper
import android.util.Log
import android.widget.Toast import android.widget.Toast
import androidx.activity.OnBackPressedCallback import androidx.activity.OnBackPressedCallback
import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.result.contract.ActivityResultContracts
import androidx.activity.viewModels import androidx.activity.viewModels
import androidx.appcompat.app.ActionBarDrawerToggle
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import androidx.core.content.ContextCompat
import androidx.core.view.GravityCompat
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.LinearLayoutManager
import com.google.android.material.navigation.NavigationView
import com.tbruyelle.rxpermissions.RxPermissions
import com.tencent.mmkv.MMKV import com.tencent.mmkv.MMKV
import com.v2ray.ang.AppConfig.ANG_PACKAGE import com.v2ray.ang.AppConfig
import com.v2ray.ang.BuildConfig import com.v2ray.ang.R
import com.v2ray.ang.databinding.ActivityMainBinding import com.v2ray.ang.databinding.ActivityMainBinding
import com.v2ray.ang.databinding.LayoutProgressBinding
import com.v2ray.ang.dto.EConfigType import com.v2ray.ang.dto.EConfigType
import com.v2ray.ang.extension.toast import com.v2ray.ang.extension.toast
import com.v2ray.ang.helper.SimpleItemTouchHelperCallback
import com.v2ray.ang.service.V2RayServiceManager
import com.v2ray.ang.util.AngConfigManager
import com.v2ray.ang.util.MmkvManager
import com.v2ray.ang.util.Utils
import com.v2ray.ang.viewmodel.MainViewModel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import me.drakeet.support.toast.ToastCompat
import rx.Observable import rx.Observable
import rx.android.schedulers.AndroidSchedulers import rx.android.schedulers.AndroidSchedulers
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
import com.v2ray.ang.helper.SimpleItemTouchHelperCallback
import com.v2ray.ang.service.V2RayServiceManager
import com.v2ray.ang.util.*
import com.v2ray.ang.viewmodel.MainViewModel
import kotlinx.coroutines.*
import me.drakeet.support.toast.ToastCompat
import java.io.File
import java.io.FileOutputStream
class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedListener { class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedListener {
private lateinit var binding: ActivityMainBinding private lateinit var binding: ActivityMainBinding
@@ -104,11 +105,10 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
binding.drawerLayout.addDrawerListener(toggle) binding.drawerLayout.addDrawerListener(toggle)
toggle.syncState() toggle.syncState()
binding.navView.setNavigationItemSelectedListener(this) binding.navView.setNavigationItemSelectedListener(this)
"v${BuildConfig.VERSION_NAME} (${SpeedtestUtil.getLibVersion()})".also { binding.version.text = it }
setupViewModel() setupViewModel()
copyAssets() mainViewModel.copyAssets(assets)
migrateLegacy()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
RxPermissions(this) RxPermissions(this)
@@ -143,72 +143,25 @@ 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) {
if (!Utils.getDarkModeStatus(this)) { binding.fab.setImageResource(R.drawable.ic_stop_24dp)
binding.fab.setImageResource(R.drawable.ic_stat_name) binding.fab.backgroundTintList = ColorStateList.valueOf(ContextCompat.getColor(this, R.color.color_fab_active))
}
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 {
if (!Utils.getDarkModeStatus(this)) { binding.fab.setImageResource(R.drawable.ic_play_24dp)
binding.fab.setImageResource(R.drawable.ic_stat_name) binding.fab.backgroundTintList = ColorStateList.valueOf(ContextCompat.getColor(this, R.color.color_fab_inactive))
}
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
} }
hideCircle()
} }
mainViewModel.startListenBroadcast() mainViewModel.startListenBroadcast()
} }
private fun copyAssets() {
val extFolder = Utils.userAssetPath(this)
lifecycleScope.launch(Dispatchers.IO) {
try {
val geo = arrayOf("geosite.dat", "geoip.dat")
assets.list("")
?.filter { geo.contains(it) }
?.filter { !File(extFolder, it).exists() }
?.forEach {
val target = File(extFolder, it)
assets.open(it).use { input ->
FileOutputStream(target).use { output ->
input.copyTo(output)
}
}
Log.i(ANG_PACKAGE, "Copied from apk assets folder to ${target.absolutePath}")
}
} catch (e: Exception) {
Log.e(ANG_PACKAGE, "asset copy failed", e)
}
}
}
private fun migrateLegacy() {
lifecycleScope.launch(Dispatchers.IO) {
val result = AngConfigManager.migrateLegacyConfig(this@MainActivity)
if (result != null) {
launch(Dispatchers.Main) {
if (result) {
toast(getString(R.string.migration_success))
mainViewModel.reloadServerList()
} else {
toast(getString(R.string.migration_fail))
}
}
}
}
}
fun startV2Ray() { fun startV2Ray() {
if (mainStorage?.decodeString(MmkvManager.KEY_SELECTED_SERVER).isNullOrEmpty()) { if (mainStorage?.decodeString(MmkvManager.KEY_SELECTED_SERVER).isNullOrEmpty()) {
return return
} }
showCircle()
// toast(R.string.toast_services_start)
V2RayServiceManager.startV2Ray(this) V2RayServiceManager.startV2Ray(this)
hideCircle()
} }
fun restartV2Ray() { fun restartV2Ray() {
@@ -286,11 +239,6 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
true true
} }
// R.id.sub_setting -> {
// startActivity<SubSettingActivity>()
// true
// }
R.id.sub_update -> { R.id.sub_update -> {
importConfigViaSub() importConfigViaSub()
true true
@@ -326,6 +274,9 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
MmkvManager.removeAllServer() MmkvManager.removeAllServer()
mainViewModel.reloadServerList() mainViewModel.reloadServerList()
} }
.setNegativeButton(android.R.string.no) {_, _ ->
//do noting
}
.show() .show()
true true
} }
@@ -333,6 +284,10 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
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) { _, _ ->
mainViewModel.removeDuplicateServer() mainViewModel.removeDuplicateServer()
mainViewModel.reloadServerList()
}
.setNegativeButton(android.R.string.no) {_, _ ->
//do noting
} }
.show() .show()
true true
@@ -343,6 +298,9 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
MmkvManager.removeInvalidServer() MmkvManager.removeInvalidServer()
mainViewModel.reloadServerList() mainViewModel.reloadServerList()
} }
.setNegativeButton(android.R.string.no) {_, _ ->
//do noting
}
.show() .show()
true true
} }
@@ -371,7 +329,7 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
/** /**
* import config from qrcode * import config from qrcode
*/ */
fun importQRcode(forConfig: Boolean): Boolean { private fun importQRcode(forConfig: Boolean): Boolean {
// try { // try {
// startActivityForResult(Intent("com.google.zxing.client.android.SCAN") // startActivityForResult(Intent("com.google.zxing.client.android.SCAN")
// .addCategory(Intent.CATEGORY_DEFAULT) // .addCategory(Intent.CATEGORY_DEFAULT)
@@ -407,7 +365,7 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
/** /**
* import config from clipboard * import config from clipboard
*/ */
fun importClipboard() private fun importClipboard()
: Boolean { : Boolean {
try { try {
val clipboard = Utils.getClipboard(this) val clipboard = Utils.getClipboard(this)
@@ -419,30 +377,28 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
return true return true
} }
fun importBatchConfig(server: String?, subid: String = "") { private fun importBatchConfig(server: String?) {
val subid2 = if(subid.isNullOrEmpty()){ val dialog = AlertDialog.Builder(this)
mainViewModel.subscriptionId .setView(LayoutProgressBinding.inflate(layoutInflater).root)
}else{ .setCancelable(false)
subid .show()
}
val append = subid.isNullOrEmpty()
var count = AngConfigManager.importBatchConfig(server, subid2, append) lifecycleScope.launch(Dispatchers.IO) {
if (count <= 0) { val count = AngConfigManager.importBatchConfig(server, mainViewModel.subscriptionId, true)
count = AngConfigManager.importBatchConfig(Utils.decode(server!!), subid2, append) delay(500L)
} launch(Dispatchers.Main) {
if (count <= 0) { if (count > 0) {
count = AngConfigManager.appendCustomConfigServer(server, subid2) toast(R.string.toast_success)
} mainViewModel.reloadServerList()
if (count > 0) { } else {
toast(R.string.toast_success) toast(R.string.toast_failure)
mainViewModel.reloadServerList() }
} else { dialog.dismiss()
toast(R.string.toast_failure) }
} }
} }
fun importConfigCustomClipboard() private fun importConfigCustomClipboard()
: Boolean { : Boolean {
try { try {
val configText = Utils.getClipboard(this) val configText = Utils.getClipboard(this)
@@ -461,7 +417,7 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
/** /**
* import config from local config file * import config from local config file
*/ */
fun importConfigCustomLocal(): Boolean { private fun importConfigCustomLocal(): Boolean {
try { try {
showFileChooser() showFileChooser()
} catch (e: Exception) { } catch (e: Exception) {
@@ -471,7 +427,7 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
return true return true
} }
fun importConfigCustomUrlClipboard() private fun importConfigCustomUrlClipboard()
: Boolean { : Boolean {
try { try {
val url = Utils.getClipboard(this) val url = Utils.getClipboard(this)
@@ -489,7 +445,7 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
/** /**
* import config from url * import config from url
*/ */
fun importConfigCustomUrl(url: String?): Boolean { private fun importConfigCustomUrl(url: String?): Boolean {
try { try {
if (!Utils.isValidUrl(url)) { if (!Utils.isValidUrl(url)) {
toast(R.string.toast_invalid_url) toast(R.string.toast_invalid_url)
@@ -516,43 +472,24 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
/** /**
* import config from sub * import config from sub
*/ */
fun importConfigViaSub() private fun importConfigViaSub() : Boolean {
: Boolean { val dialog = AlertDialog.Builder(this)
try { .setView(LayoutProgressBinding.inflate(layoutInflater).root)
toast(R.string.title_sub_update) .setCancelable(false)
MmkvManager.decodeSubscriptions().forEach { .show()
if (TextUtils.isEmpty(it.first)
|| TextUtils.isEmpty(it.second.remarks) lifecycleScope.launch(Dispatchers.IO) {
|| TextUtils.isEmpty(it.second.url) val count = AngConfigManager.updateConfigViaSubAll()
) { delay(500L)
return@forEach launch(Dispatchers.Main) {
} if (count > 0) {
if (!it.second.enabled) { toast(R.string.toast_success)
return@forEach mainViewModel.reloadServerList()
} } else {
val url = Utils.idnToASCII(it.second.url) toast(R.string.toast_failure)
if (!Utils.isValidUrl(url)) {
return@forEach
}
Log.d(ANG_PACKAGE, url)
lifecycleScope.launch(Dispatchers.IO) {
val configText = try {
Utils.getUrlContentWithCustomUserAgent(url)
} catch (e: Exception) {
e.printStackTrace()
launch(Dispatchers.Main) {
toast("\"" + it.second.remarks + "\" " + getString(R.string.toast_failure))
}
return@launch
}
launch(Dispatchers.Main) {
importBatchConfig(configText, it.first)
}
} }
dialog.dismiss()
} }
} catch (e: Exception) {
e.printStackTrace()
return false
} }
return true return true
} }
@@ -607,15 +544,18 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
/** /**
* import customize config * import customize config
*/ */
fun importCustomizeConfig(server: String?) { private fun importCustomizeConfig(server: String?) {
try { try {
if (server == null || TextUtils.isEmpty(server)) { if (server == null || TextUtils.isEmpty(server)) {
toast(R.string.toast_none_data) toast(R.string.toast_none_data)
return return
} }
mainViewModel.appendCustomConfigServer(server) if (mainViewModel.appendCustomConfigServer(server)) {
mainViewModel.reloadServerList() mainViewModel.reloadServerList()
toast(R.string.toast_success) toast(R.string.toast_success)
} else {
toast(R.string.toast_failure)
}
//adapter.notifyItemInserted(mainViewModel.serverList.lastIndex) //adapter.notifyItemInserted(mainViewModel.serverList.lastIndex)
} catch (e: Exception) { } catch (e: Exception) {
ToastCompat.makeText(this, "${getString(R.string.toast_malformed_josn)} ${e.cause?.message}", Toast.LENGTH_LONG).show() ToastCompat.makeText(this, "${getString(R.string.toast_malformed_josn)} ${e.cause?.message}", Toast.LENGTH_LONG).show()
@@ -624,7 +564,7 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
} }
} }
fun setTestState(content: String?) { private fun setTestState(content: String?) {
binding.tvTestState.text = content binding.tvTestState.text = content
} }
@@ -645,29 +585,6 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
return super.onKeyDown(keyCode, event) return super.onKeyDown(keyCode, event)
} }
fun showCircle() {
binding.fabProgressCircle.show()
}
fun hideCircle() {
try {
Observable.timer(300, TimeUnit.MILLISECONDS)
.observeOn(AndroidSchedulers.mainThread())
.subscribe {
try {
if (binding.fabProgressCircle.isShown) {
binding.fabProgressCircle.hide()
}
} catch (e: Exception) {
Log.w(ANG_PACKAGE, e)
}
}
} catch (e: Exception) {
Log.d(ANG_PACKAGE, e.toString())
}
}
override fun onNavigationItemSelected(item: MenuItem): Boolean { override fun onNavigationItemSelected(item: MenuItem): Boolean {
// Handle navigation view item clicks here. // Handle navigation view item clicks here.
when (item.itemId) { when (item.itemId) {
@@ -682,17 +599,14 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
R.id.user_asset_setting -> { R.id.user_asset_setting -> {
startActivity(Intent(this, UserAssetActivity::class.java)) startActivity(Intent(this, UserAssetActivity::class.java))
} }
R.id.feedback -> {
Utils.openUri(this, AppConfig.v2rayNGIssues)
}
R.id.promotion -> { R.id.promotion -> {
Utils.openUri(this, "${Utils.decode(AppConfig.promotionUrl)}?t=${System.currentTimeMillis()}") Utils.openUri(this, "${Utils.decode(AppConfig.PromotionUrl)}?t=${System.currentTimeMillis()}")
} }
R.id.logcat -> { R.id.logcat -> {
startActivity(Intent(this, LogcatActivity::class.java)) startActivity(Intent(this, LogcatActivity::class.java))
} }
R.id.privacy_policy-> { R.id.about-> {
Utils.openUri(this, AppConfig.v2rayNGPrivacyPolicy) startActivity(Intent(this, AboutActivity::class.java))
} }
} }
binding.drawerLayout.closeDrawer(GravityCompat.START) binding.drawerLayout.closeDrawer(GravityCompat.START)

View File

@@ -3,14 +3,15 @@ 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.text.TextUtils
import androidx.core.content.ContextCompat
import androidx.recyclerview.widget.RecyclerView
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import androidx.core.content.ContextCompat
import androidx.recyclerview.widget.RecyclerView
import com.google.gson.Gson import com.google.gson.Gson
import com.tencent.mmkv.MMKV import com.tencent.mmkv.MMKV
import com.v2ray.ang.AngApplication.Companion.application
import com.v2ray.ang.AppConfig import com.v2ray.ang.AppConfig
import com.v2ray.ang.R import com.v2ray.ang.R
import com.v2ray.ang.databinding.ItemQrcodeBinding import com.v2ray.ang.databinding.ItemQrcodeBinding
@@ -66,15 +67,15 @@ class MainRecyclerAdapter(val activity: MainActivity) : RecyclerView.Adapter<Mai
holder.itemMainBinding.tvName.text = config.remarks holder.itemMainBinding.tvName.text = config.remarks
holder.itemView.setBackgroundColor(Color.TRANSPARENT) holder.itemView.setBackgroundColor(Color.TRANSPARENT)
holder.itemMainBinding.tvTestResult.text = aff?.getTestDelayString() ?: "" holder.itemMainBinding.tvTestResult.text = aff?.getTestDelayString() ?: ""
if (aff?.testDelayMillis ?: 0L < 0L) { if ((aff?.testDelayMillis ?: 0L) < 0L) {
holder.itemMainBinding.tvTestResult.setTextColor(ContextCompat.getColor(mActivity, R.color.colorPingRed)) holder.itemMainBinding.tvTestResult.setTextColor(ContextCompat.getColor(mActivity, R.color.colorPingRed))
} else { } else {
holder.itemMainBinding.tvTestResult.setTextColor(ContextCompat.getColor(mActivity, R.color.colorPing)) holder.itemMainBinding.tvTestResult.setTextColor(ContextCompat.getColor(mActivity, R.color.colorPing))
} }
if (guid == mainStorage?.decodeString(MmkvManager.KEY_SELECTED_SERVER)) { if (guid == mainStorage?.decodeString(MmkvManager.KEY_SELECTED_SERVER)) {
holder.itemMainBinding.layoutIndicator.setBackgroundResource(R.color.colorSelected) holder.itemMainBinding.layoutIndicator.setBackgroundResource(R.color.colorAccent)
} else { } else {
holder.itemMainBinding.layoutIndicator.setBackgroundResource(R.color.colorUnselected) holder.itemMainBinding.layoutIndicator.setBackgroundResource(0)
} }
holder.itemMainBinding.tvSubscription.text = "" holder.itemMainBinding.tvSubscription.text = ""
val json = subStorage?.decodeString(config.subscriptionId) val json = subStorage?.decodeString(config.subscriptionId)
@@ -96,7 +97,13 @@ class MainRecyclerAdapter(val activity: MainActivity) : RecyclerView.Adapter<Mai
holder.itemMainBinding.tvType.text = config.configType.name.lowercase() holder.itemMainBinding.tvType.text = config.configType.name.lowercase()
} }
} }
val strState = "${outbound?.getServerAddress()?.dropLast(3)}*** : ${outbound?.getServerPort()}"
val strState = try{
"${outbound?.getServerAddress()?.dropLast(3)}*** : ${outbound?.getServerPort()}"
}catch(e: Exception){
""
}
holder.itemMainBinding.tvStatistics.text = strState holder.itemMainBinding.tvStatistics.text = strState
holder.itemMainBinding.layoutShare.setOnClickListener { holder.itemMainBinding.layoutShare.setOnClickListener {
@@ -144,10 +151,15 @@ class MainRecyclerAdapter(val activity: MainActivity) : RecyclerView.Adapter<Mai
.setPositiveButton(android.R.string.ok) { _, _ -> .setPositiveButton(android.R.string.ok) { _, _ ->
removeServer(guid, position) removeServer(guid, position)
} }
.setNegativeButton(android.R.string.no) {_, _ ->
//do noting
}
.show() .show()
} else { } else {
removeServer(guid, position) removeServer(guid, position)
} }
} else {
application.toast(R.string.toast_action_not_allowed)
} }
} }
@@ -156,17 +168,15 @@ class MainRecyclerAdapter(val activity: MainActivity) : RecyclerView.Adapter<Mai
if (guid != selected) { if (guid != selected) {
mainStorage?.encode(MmkvManager.KEY_SELECTED_SERVER, guid) mainStorage?.encode(MmkvManager.KEY_SELECTED_SERVER, guid)
if (!TextUtils.isEmpty(selected)) { if (!TextUtils.isEmpty(selected)) {
notifyItemChanged(mActivity.mainViewModel.getPosition(selected!!)) notifyItemChanged(mActivity.mainViewModel.getPosition(selected?:""))
} }
notifyItemChanged(mActivity.mainViewModel.getPosition(guid)) notifyItemChanged(mActivity.mainViewModel.getPosition(guid))
if (isRunning) { if (isRunning) {
mActivity.showCircle()
Utils.stopVService(mActivity) Utils.stopVService(mActivity)
Observable.timer(500, TimeUnit.MILLISECONDS) Observable.timer(500, TimeUnit.MILLISECONDS)
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.subscribe { .subscribe {
V2RayServiceManager.startV2Ray(mActivity) V2RayServiceManager.startV2Ray(mActivity)
mActivity.hideCircle()
} }
} }
} }
@@ -178,7 +188,7 @@ class MainRecyclerAdapter(val activity: MainActivity) : RecyclerView.Adapter<Mai
holder.itemFooterBinding.layoutEdit.visibility = View.INVISIBLE holder.itemFooterBinding.layoutEdit.visibility = View.INVISIBLE
} else { } else {
holder.itemFooterBinding.layoutEdit.setOnClickListener { holder.itemFooterBinding.layoutEdit.setOnClickListener {
Utils.openUri(mActivity, "${Utils.decode(AppConfig.promotionUrl)}?t=${System.currentTimeMillis()}") Utils.openUri(mActivity, "${Utils.decode(AppConfig.PromotionUrl)}?t=${System.currentTimeMillis()}")
} }
} }
} }

View File

@@ -8,9 +8,9 @@ import android.view.MenuItem
import android.view.View import android.view.View
import androidx.appcompat.widget.SearchView import androidx.appcompat.widget.SearchView
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.preference.PreferenceManager
import androidx.recyclerview.widget.DividerItemDecoration import androidx.recyclerview.widget.DividerItemDecoration
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import com.tencent.mmkv.MMKV
import com.v2ray.ang.AppConfig import com.v2ray.ang.AppConfig
import com.v2ray.ang.AppConfig.ANG_PACKAGE import com.v2ray.ang.AppConfig.ANG_PACKAGE
import com.v2ray.ang.R import com.v2ray.ang.R
@@ -19,20 +19,20 @@ import com.v2ray.ang.dto.AppInfo
import com.v2ray.ang.extension.toast import com.v2ray.ang.extension.toast
import com.v2ray.ang.extension.v2RayApplication import com.v2ray.ang.extension.v2RayApplication
import com.v2ray.ang.util.AppManagerUtil import com.v2ray.ang.util.AppManagerUtil
import com.v2ray.ang.util.MmkvManager
import com.v2ray.ang.util.Utils import com.v2ray.ang.util.Utils
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import rx.android.schedulers.AndroidSchedulers import rx.android.schedulers.AndroidSchedulers
import rx.schedulers.Schedulers import rx.schedulers.Schedulers
import java.text.Collator import java.text.Collator
import java.util.*
class PerAppProxyActivity : BaseActivity() { class PerAppProxyActivity : BaseActivity() {
private lateinit var binding: ActivityBypassListBinding private lateinit var binding: ActivityBypassListBinding
private var adapter: PerAppProxyAdapter? = null private var adapter: PerAppProxyAdapter? = null
private var appsAll: List<AppInfo>? = null private var appsAll: List<AppInfo>? = null
private val defaultSharedPreferences by lazy { PreferenceManager.getDefaultSharedPreferences(this) } private val settingsStorage by lazy { MMKV.mmkvWithID(MmkvManager.ID_SETTING, MMKV.MULTI_PROCESS_MODE) }
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
@@ -43,7 +43,7 @@ class PerAppProxyActivity : BaseActivity() {
val dividerItemDecoration = DividerItemDecoration(this, LinearLayoutManager.VERTICAL) val dividerItemDecoration = DividerItemDecoration(this, LinearLayoutManager.VERTICAL)
binding.recyclerView.addItemDecoration(dividerItemDecoration) binding.recyclerView.addItemDecoration(dividerItemDecoration)
val blacklist = defaultSharedPreferences.getStringSet(AppConfig.PREF_PER_APP_PROXY_SET, null) val blacklist = settingsStorage?.decodeStringSet(AppConfig.PREF_PER_APP_PROXY_SET)
AppManagerUtil.rxLoadNetworkAppList(this) AppManagerUtil.rxLoadNetworkAppList(this)
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
@@ -134,14 +134,14 @@ class PerAppProxyActivity : BaseActivity() {
***/ ***/
binding.switchPerAppProxy.setOnCheckedChangeListener { _, isChecked -> binding.switchPerAppProxy.setOnCheckedChangeListener { _, isChecked ->
defaultSharedPreferences.edit().putBoolean(AppConfig.PREF_PER_APP_PROXY, isChecked).apply() settingsStorage.encode(AppConfig.PREF_PER_APP_PROXY, isChecked)
} }
binding.switchPerAppProxy.isChecked = defaultSharedPreferences.getBoolean(AppConfig.PREF_PER_APP_PROXY, false) binding.switchPerAppProxy.isChecked = settingsStorage.getBoolean(AppConfig.PREF_PER_APP_PROXY, false)
binding.switchBypassApps.setOnCheckedChangeListener { _, isChecked -> binding.switchBypassApps.setOnCheckedChangeListener { _, isChecked ->
defaultSharedPreferences.edit().putBoolean(AppConfig.PREF_BYPASS_APPS, isChecked).apply() settingsStorage.encode(AppConfig.PREF_BYPASS_APPS, isChecked)
} }
binding.switchBypassApps.isChecked = defaultSharedPreferences.getBoolean(AppConfig.PREF_BYPASS_APPS, false) binding.switchBypassApps.isChecked = settingsStorage.getBoolean(AppConfig.PREF_BYPASS_APPS, false)
/*** /***
et_search.setOnEditorActionListener { v, actionId, event -> et_search.setOnEditorActionListener { v, actionId, event ->
@@ -177,7 +177,7 @@ class PerAppProxyActivity : BaseActivity() {
override fun onPause() { override fun onPause() {
super.onPause() super.onPause()
adapter?.let { adapter?.let {
defaultSharedPreferences.edit().putStringSet(AppConfig.PREF_PER_APP_PROXY_SET, it.blacklist).apply() settingsStorage.encode(AppConfig.PREF_PER_APP_PROXY_SET, it.blacklist)
} }
} }
@@ -193,7 +193,7 @@ class PerAppProxyActivity : BaseActivity() {
} }
override fun onQueryTextChange(newText: String?): Boolean { override fun onQueryTextChange(newText: String?): Boolean {
filterProxyApp(newText!!) filterProxyApp(newText?:"")
return false return false
} }
}) })
@@ -209,12 +209,12 @@ class PerAppProxyActivity : BaseActivity() {
if (it.blacklist.containsAll(pkgNames)) { if (it.blacklist.containsAll(pkgNames)) {
it.apps.forEach { it.apps.forEach {
val packageName = it.packageName val packageName = it.packageName
adapter?.blacklist!!.remove(packageName) adapter?.blacklist?.remove(packageName)
} }
} else { } else {
it.apps.forEach { it.apps.forEach {
val packageName = it.packageName val packageName = it.packageName
adapter?.blacklist!!.add(packageName) adapter?.blacklist?.add(packageName)
} }
} }
it.notifyDataSetChanged() it.notifyDataSetChanged()
@@ -278,7 +278,7 @@ class PerAppProxyActivity : BaseActivity() {
return false return false
} }
adapter?.blacklist!!.clear() adapter?.blacklist?.clear()
if (binding.switchBypassApps.isChecked) { if (binding.switchBypassApps.isChecked) {
adapter?.let { adapter?.let {
@@ -286,7 +286,7 @@ class PerAppProxyActivity : BaseActivity() {
val packageName = it.packageName val packageName = it.packageName
Log.d(ANG_PACKAGE, packageName) Log.d(ANG_PACKAGE, packageName)
if (!inProxyApps(proxyApps, packageName, force)) { if (!inProxyApps(proxyApps, packageName, force)) {
adapter?.blacklist!!.add(packageName) adapter?.blacklist?.add(packageName)
println(packageName) println(packageName)
return@block return@block
} }
@@ -299,7 +299,7 @@ class PerAppProxyActivity : BaseActivity() {
val packageName = it.packageName val packageName = it.packageName
Log.d(ANG_PACKAGE, packageName) Log.d(ANG_PACKAGE, packageName)
if (inProxyApps(proxyApps, packageName, force)) { if (inProxyApps(proxyApps, packageName, force)) {
adapter?.blacklist!!.add(packageName) adapter?.blacklist?.add(packageName)
println(packageName) println(packageName)
return@block return@block
} }

View File

@@ -11,11 +11,13 @@ import androidx.fragment.app.Fragment
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
import com.tbruyelle.rxpermissions.RxPermissions import com.tbruyelle.rxpermissions.RxPermissions
import com.tencent.mmkv.MMKV
import com.v2ray.ang.AppConfig import com.v2ray.ang.AppConfig
import com.v2ray.ang.R import com.v2ray.ang.R
import com.v2ray.ang.databinding.FragmentRoutingSettingsBinding import com.v2ray.ang.databinding.FragmentRoutingSettingsBinding
import com.v2ray.ang.extension.toast import com.v2ray.ang.extension.toast
import com.v2ray.ang.extension.v2RayApplication import com.v2ray.ang.extension.v2RayApplication
import com.v2ray.ang.util.MmkvManager
import com.v2ray.ang.util.Utils import com.v2ray.ang.util.Utils
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@@ -26,7 +28,7 @@ class RoutingSettingsFragment : Fragment() {
private const val routing_arg = "routing_arg" private const val routing_arg = "routing_arg"
} }
val defaultSharedPreferences by lazy { PreferenceManager.getDefaultSharedPreferences(requireContext()) } private val settingsStorage by lazy { MMKV.mmkvWithID(MmkvManager.ID_SETTING, MMKV.MULTI_PROCESS_MODE) }
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View? { savedInstanceState: Bundle?): View? {
@@ -46,8 +48,8 @@ class RoutingSettingsFragment : Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
val content = defaultSharedPreferences.getString(requireArguments().getString(routing_arg), "") val content = settingsStorage?.getString(requireArguments().getString(routing_arg), "")
binding.etRoutingContent.text = Utils.getEditable(content!!) binding.etRoutingContent.text = Utils.getEditable(content)
setHasOptionsMenu(true) setHasOptionsMenu(true)
} }
@@ -83,7 +85,7 @@ class RoutingSettingsFragment : Fragment() {
private fun saveRouting() { private fun saveRouting() {
val content = binding.etRoutingContent.text.toString() val content = binding.etRoutingContent.text.toString()
defaultSharedPreferences.edit().putString(requireArguments().getString(routing_arg), content).apply() settingsStorage?.encode(requireArguments().getString(routing_arg), content)
activity?.toast(R.string.toast_success) activity?.toast(R.string.toast_success)
} }
@@ -111,7 +113,7 @@ class RoutingSettingsFragment : Fragment() {
private val scanQRCodeForReplace = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { private val scanQRCodeForReplace = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
if (it.resultCode == RESULT_OK) { if (it.resultCode == RESULT_OK) {
val content = it.data?.getStringExtra("SCAN_RESULT") val content = it.data?.getStringExtra("SCAN_RESULT")
binding.etRoutingContent.text = Utils.getEditable(content!!) binding.etRoutingContent.text = Utils.getEditable(content)
} }
} }
@@ -127,7 +129,7 @@ class RoutingSettingsFragment : Fragment() {
var tag = "" var tag = ""
when (requireArguments().getString(routing_arg)) { when (requireArguments().getString(routing_arg)) {
AppConfig.PREF_V2RAY_ROUTING_AGENT -> { AppConfig.PREF_V2RAY_ROUTING_AGENT -> {
tag = AppConfig.TAG_AGENT tag = AppConfig.TAG_PROXY
} }
AppConfig.PREF_V2RAY_ROUTING_DIRECT -> { AppConfig.PREF_V2RAY_ROUTING_DIRECT -> {
tag = AppConfig.TAG_DIRECT tag = AppConfig.TAG_DIRECT
@@ -143,7 +145,7 @@ class RoutingSettingsFragment : Fragment() {
val content = Utils.getUrlContext(url, 5000) val content = Utils.getUrlContext(url, 5000)
launch(Dispatchers.Main) { launch(Dispatchers.Main) {
val routingList = if (TextUtils.isEmpty(content)) { val routingList = if (TextUtils.isEmpty(content)) {
Utils.readTextFromAssets(activity?.v2RayApplication!!, "custom_routing_$tag") Utils.readTextFromAssets(activity?.v2RayApplication, "custom_routing_$tag")
} else { } else {
content content
} }

View File

@@ -45,7 +45,7 @@ class ScannerActivity : BaseActivity(){
private fun handleResult(result: QRResult) { private fun handleResult(result: QRResult) {
if (result is QRResult.QRSuccess ) { if (result is QRResult.QRSuccess ) {
finished(result.content.rawValue!!) finished(result.content.rawValue?:"")
} else { } else {
finish() finish()
} }
@@ -110,7 +110,7 @@ class ScannerActivity : BaseActivity(){
try { try {
val bitmap = BitmapFactory.decodeStream(contentResolver.openInputStream(uri)) val bitmap = BitmapFactory.decodeStream(contentResolver.openInputStream(uri))
val text = QRCodeDecoder.syncDecodeQRCode(bitmap) val text = QRCodeDecoder.syncDecodeQRCode(bitmap)
finished(text!!) finished(text?:"")
} catch (e: Exception) { } catch (e: Exception) {
e.printStackTrace() e.printStackTrace()
toast(e.message.toString()) toast(e.message.toString())

View File

@@ -5,7 +5,12 @@ import android.text.TextUtils
import android.view.Menu import android.view.Menu
import android.view.MenuItem import android.view.MenuItem
import android.view.View import android.view.View
import android.widget.* import android.widget.AdapterView
import android.widget.ArrayAdapter
import android.widget.EditText
import android.widget.LinearLayout
import android.widget.Spinner
import android.widget.TextView
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import com.tencent.mmkv.MMKV import com.tencent.mmkv.MMKV
import com.v2ray.ang.AppConfig import com.v2ray.ang.AppConfig
@@ -105,7 +110,9 @@ class ServerActivity : BaseActivity() {
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 tv_request_host: TextView? by lazy { findViewById(R.id.tv_request_host) }
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 tv_path: TextView? by lazy { findViewById(R.id.tv_path) }
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 container_alpn: LinearLayout? by lazy { findViewById(R.id.l4) }
@@ -157,6 +164,36 @@ class ServerActivity : BaseActivity() {
et_request_host?.text = Utils.getEditable(transportDetails[1]) et_request_host?.text = Utils.getEditable(transportDetails[1])
et_path?.text = Utils.getEditable(transportDetails[2]) et_path?.text = Utils.getEditable(transportDetails[2])
} }
tv_request_host?.text = Utils.getEditable(
getString(
when (networks[position]) {
"tcp" -> R.string.server_lab_request_host_http
"ws" -> R.string.server_lab_request_host_ws
"httpupgrade" -> R.string.server_lab_request_host_httpupgrade
"splithttp" -> R.string.server_lab_request_host_splithttp
"h2" -> R.string.server_lab_request_host_h2
"quic" -> R.string.server_lab_request_host_quic
"grpc" -> R.string.server_lab_request_host_grpc
else -> R.string.server_lab_request_host
}
)
)
tv_path?.text = Utils.getEditable(
getString(
when (networks[position]) {
"kcp" -> R.string.server_lab_path_kcp
"ws" -> R.string.server_lab_path_ws
"httpupgrade" -> R.string.server_lab_path_httpupgrade
"splithttp" -> R.string.server_lab_path_splithttp
"h2" -> R.string.server_lab_path_h2
"quic" -> R.string.server_lab_path_quic
"grpc" -> R.string.server_lab_path_grpc
else -> R.string.server_lab_path
}
)
)
} }
override fun onNothingSelected(parent: AdapterView<*>?) { override fun onNothingSelected(parent: AdapterView<*>?) {
@@ -209,7 +246,7 @@ class ServerActivity : BaseActivity() {
} }
/** /**
* bingding seleced server config * binding selected server config
*/ */
private fun bindingServer(config: ServerConfig): Boolean { private fun bindingServer(config: ServerConfig): Boolean {
val outbound = config.getProxyOutbound() ?: return false val outbound = config.getProxyOutbound() ?: return false
@@ -285,7 +322,7 @@ class ServerActivity : BaseActivity() {
tlsSetting.alpn?.let { tlsSetting.alpn?.let {
val alpnIndex = Utils.arrayFind( val alpnIndex = Utils.arrayFind(
alpns, alpns,
Utils.removeWhiteSpace(tlsSetting.alpn.joinToString())!! Utils.removeWhiteSpace(tlsSetting.alpn.joinToString())?:""
) )
sp_stream_alpn?.setSelection(alpnIndex) sp_stream_alpn?.setSelection(alpnIndex)
} }
@@ -413,7 +450,7 @@ class ServerActivity : BaseActivity() {
saveStreamSettings(it) saveStreamSettings(it)
} }
if (config.subscriptionId.isEmpty() && !subscriptionId.isNullOrEmpty()) { if (config.subscriptionId.isEmpty() && !subscriptionId.isNullOrEmpty()) {
config.subscriptionId = subscriptionId!! config.subscriptionId = subscriptionId?:""
} }
MmkvManager.encodeServerConfig(editGuid, config) MmkvManager.encodeServerConfig(editGuid, config)
@@ -560,11 +597,16 @@ class ServerActivity : BaseActivity() {
MmkvManager.removeServer(editGuid) MmkvManager.removeServer(editGuid)
finish() finish()
} }
.setNegativeButton(android.R.string.no) { _, _ ->
// do nothing
}
.show() .show()
} else { } else {
MmkvManager.removeServer(editGuid) MmkvManager.removeServer(editGuid)
finish() finish()
} }
} else {
application.toast(R.string.toast_action_not_allowed)
} }
} }
return true return true

View File

@@ -111,6 +111,9 @@ class ServerCustomConfigActivity : BaseActivity() {
MmkvManager.removeServer(editGuid) MmkvManager.removeServer(editGuid)
finish() finish()
} }
.setNegativeButton(android.R.string.no) {_, _ ->
// do nothing
}
.show() .show()
} }
return true return true

View File

@@ -5,14 +5,20 @@ import android.os.Bundle
import android.text.TextUtils import android.text.TextUtils
import android.view.View import android.view.View
import androidx.activity.viewModels import androidx.activity.viewModels
import androidx.preference.* import androidx.preference.CheckBoxPreference
import androidx.preference.EditTextPreference
import androidx.preference.ListPreference
import androidx.preference.Preference
import androidx.preference.PreferenceFragmentCompat
import androidx.work.ExistingPeriodicWorkPolicy import androidx.work.ExistingPeriodicWorkPolicy
import androidx.work.PeriodicWorkRequest import androidx.work.PeriodicWorkRequest
import androidx.work.multiprocess.RemoteWorkManager import androidx.work.multiprocess.RemoteWorkManager
import com.tencent.mmkv.MMKV
import com.v2ray.ang.AngApplication import com.v2ray.ang.AngApplication
import com.v2ray.ang.AppConfig import com.v2ray.ang.AppConfig
import com.v2ray.ang.R import com.v2ray.ang.R
import com.v2ray.ang.service.SubscriptionUpdater import com.v2ray.ang.service.SubscriptionUpdater
import com.v2ray.ang.util.MmkvManager
import com.v2ray.ang.util.Utils import com.v2ray.ang.util.Utils
import com.v2ray.ang.viewmodel.SettingsViewModel import com.v2ray.ang.viewmodel.SettingsViewModel
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
@@ -30,12 +36,16 @@ class SettingsActivity : BaseActivity() {
} }
class SettingsFragment : PreferenceFragmentCompat() { class SettingsFragment : PreferenceFragmentCompat() {
private val settingsStorage by lazy { MMKV.mmkvWithID(MmkvManager.ID_SETTING, MMKV.MULTI_PROCESS_MODE) }
private val perAppProxy by lazy { findPreference<CheckBoxPreference>(AppConfig.PREF_PER_APP_PROXY) } private val perAppProxy by lazy { findPreference<CheckBoxPreference>(AppConfig.PREF_PER_APP_PROXY) }
private val localDns by lazy { findPreference<CheckBoxPreference>(AppConfig.PREF_LOCAL_DNS_ENABLED) } private val localDns by lazy { findPreference<CheckBoxPreference>(AppConfig.PREF_LOCAL_DNS_ENABLED) }
private val fakeDns by lazy { findPreference<CheckBoxPreference>(AppConfig.PREF_FAKE_DNS_ENABLED) } private val fakeDns by lazy { findPreference<CheckBoxPreference>(AppConfig.PREF_FAKE_DNS_ENABLED) }
private val localDnsPort by lazy { findPreference<EditTextPreference>(AppConfig.PREF_LOCAL_DNS_PORT) } private val localDnsPort by lazy { findPreference<EditTextPreference>(AppConfig.PREF_LOCAL_DNS_PORT) }
private val vpnDns by lazy { findPreference<EditTextPreference>(AppConfig.PREF_VPN_DNS) } private val vpnDns by lazy { findPreference<EditTextPreference>(AppConfig.PREF_VPN_DNS) }
private val routingCustom by lazy { findPreference<Preference>(AppConfig.PREF_ROUTING_CUSTOM) }
private val mux by lazy { findPreference<CheckBoxPreference>(AppConfig.PREF_MUX_ENABLED) } private val mux by lazy { findPreference<CheckBoxPreference>(AppConfig.PREF_MUX_ENABLED) }
private val muxConcurrency by lazy { findPreference<EditTextPreference>(AppConfig.PREF_MUX_CONCURRENCY) } private val muxConcurrency by lazy { findPreference<EditTextPreference>(AppConfig.PREF_MUX_CONCURRENCY) }
private val muxXudpConcurrency by lazy { findPreference<EditTextPreference>(AppConfig.PREF_MUX_XUDP_CONCURRENCY) } private val muxXudpConcurrency by lazy { findPreference<EditTextPreference>(AppConfig.PREF_MUX_XUDP_CONCURRENCY) }
@@ -46,90 +56,24 @@ class SettingsActivity : BaseActivity() {
private val fragmentLength by lazy { findPreference<EditTextPreference>(AppConfig.PREF_FRAGMENT_LENGTH) } private val fragmentLength by lazy { findPreference<EditTextPreference>(AppConfig.PREF_FRAGMENT_LENGTH) }
private val fragmentInterval by lazy { findPreference<EditTextPreference>(AppConfig.PREF_FRAGMENT_INTERVAL) } private val fragmentInterval by lazy { findPreference<EditTextPreference>(AppConfig.PREF_FRAGMENT_INTERVAL) }
// val autoRestart by lazy { findPreference(PREF_AUTO_RESTART) as CheckBoxPreference }
private val remoteDns by lazy { findPreference<EditTextPreference>(AppConfig.PREF_REMOTE_DNS) }
private val domesticDns by lazy { findPreference<EditTextPreference>(AppConfig.PREF_DOMESTIC_DNS) }
private val socksPort by lazy { findPreference<EditTextPreference>(AppConfig.PREF_SOCKS_PORT) }
private val httpPort by lazy { findPreference<EditTextPreference>(AppConfig.PREF_HTTP_PORT) }
private val routingCustom by lazy { findPreference<Preference>(AppConfig.PREF_ROUTING_CUSTOM) }
private val autoUpdateCheck by lazy { findPreference<CheckBoxPreference>(AppConfig.SUBSCRIPTION_AUTO_UPDATE) } private val autoUpdateCheck by lazy { findPreference<CheckBoxPreference>(AppConfig.SUBSCRIPTION_AUTO_UPDATE) }
private val autoUpdateInterval by lazy { findPreference<EditTextPreference>(AppConfig.SUBSCRIPTION_AUTO_UPDATE_INTERVAL) } private val autoUpdateInterval by lazy { findPreference<EditTextPreference>(AppConfig.SUBSCRIPTION_AUTO_UPDATE_INTERVAL) }
// val licenses: Preference by lazy { findPreference(PREF_LICENSES) }
// val feedback: Preference by lazy { findPreference(PREF_FEEDBACK) }
// val tgGroup: Preference by lazy { findPreference(PREF_TG_GROUP) }
private val socksPort by lazy { findPreference<EditTextPreference>(AppConfig.PREF_SOCKS_PORT) }
private val httpPort by lazy { findPreference<EditTextPreference>(AppConfig.PREF_HTTP_PORT) }
private val remoteDns by lazy { findPreference<EditTextPreference>(AppConfig.PREF_REMOTE_DNS) }
private val domesticDns by lazy { findPreference<EditTextPreference>(AppConfig.PREF_DOMESTIC_DNS) }
private val delayTestUrl by lazy { findPreference<EditTextPreference>(AppConfig.PREF_DELAY_TEST_URL) }
private val mode by lazy { findPreference<ListPreference>(AppConfig.PREF_MODE) } private val mode by lazy { findPreference<ListPreference>(AppConfig.PREF_MODE) }
override fun onCreatePreferences(bundle: Bundle?, s: String?) { override fun onCreatePreferences(bundle: Bundle?, s: String?) {
addPreferencesFromResource(R.xml.pref_settings) addPreferencesFromResource(R.xml.pref_settings)
routingCustom?.setOnPreferenceClickListener {
startActivity(Intent(activity, RoutingSettingsActivity::class.java))
false
}
autoUpdateCheck?.setOnPreferenceChangeListener { _, newValue ->
val value = newValue as Boolean
autoUpdateCheck?.isChecked = value
autoUpdateInterval?.isEnabled = value
autoUpdateInterval?.text?.toLong()?.let {
if (newValue) configureUpdateTask(it) else cancelUpdateTask()
}
true
}
autoUpdateInterval?.setOnPreferenceChangeListener { _, any ->
var nval = any as String
// It must be greater than 15 minutes because WorkManager couldn't run tasks under 15 minutes intervals
nval =
if (TextUtils.isEmpty(nval) || nval.toLong() < 15) AppConfig.SUBSCRIPTION_DEFAULT_UPDATE_INTERVAL else nval
autoUpdateInterval?.summary = nval
configureUpdateTask(nval.toLong())
true
}
// licenses.onClick {
// val fragment = LicensesDialogFragment.Builder(act)
// .setNotices(R.raw.licenses)
// .setIncludeOwnLicense(false)
// .build()
// fragment.show((act as AppCompatActivity).supportFragmentManager, null)
// }
//
// feedback.onClick {
// Utils.openUri(activity, "https://github.com/2dust/v2rayNG/issues")
// }
// tgGroup.onClick {
// // Utils.openUri(activity, "https://t.me/v2rayN")
// val intent = Intent(Intent.ACTION_VIEW, Uri.parse("tg:resolve?domain=v2rayN"))
// try {
// startActivity(intent)
// } catch (e: Exception) {
// e.printStackTrace()
// toast(R.string.toast_tg_app_not_found)
// }
// }
perAppProxy?.setOnPreferenceClickListener { perAppProxy?.setOnPreferenceClickListener {
startActivity(Intent(activity, PerAppProxyActivity::class.java)) startActivity(Intent(activity, PerAppProxyActivity::class.java))
perAppProxy?.isChecked = true perAppProxy?.isChecked = true
false false
} }
remoteDns?.setOnPreferenceChangeListener { _, any ->
// remoteDns.summary = any as String
val nval = any as String
remoteDns?.summary = if (nval == "") AppConfig.DNS_AGENT else nval
true
}
domesticDns?.setOnPreferenceChangeListener { _, any ->
// domesticDns.summary = any as String
val nval = any as String
domesticDns?.summary = if (nval == "") AppConfig.DNS_DIRECT else nval
true
}
localDns?.setOnPreferenceChangeListener { _, any -> localDns?.setOnPreferenceChangeListener { _, any ->
updateLocalDns(any as Boolean) updateLocalDns(any as Boolean)
true true
@@ -144,22 +88,12 @@ class SettingsActivity : BaseActivity() {
vpnDns?.summary = any as String vpnDns?.summary = any as String
true true
} }
socksPort?.setOnPreferenceChangeListener { _, any ->
val nval = any as String routingCustom?.setOnPreferenceClickListener {
socksPort?.summary = if (TextUtils.isEmpty(nval)) AppConfig.PORT_SOCKS else nval startActivity(Intent(activity, RoutingSettingsActivity::class.java))
true false
} }
httpPort?.setOnPreferenceChangeListener { _, any ->
val nval = any as String
httpPort?.summary = if (TextUtils.isEmpty(nval)) AppConfig.PORT_HTTP else nval
true
}
mode?.setOnPreferenceChangeListener { _, newValue ->
updateMode(newValue.toString())
true
}
mode?.dialogLayoutResource = R.layout.preference_with_help_link
//loglevel.summary = "LogLevel"
mux?.setOnPreferenceChangeListener { _, newValue -> mux?.setOnPreferenceChangeListener { _, newValue ->
updateMux(newValue as Boolean) updateMux(newValue as Boolean)
true true
@@ -189,65 +123,159 @@ class SettingsActivity : BaseActivity() {
updateFragmentInterval(newValue as String) updateFragmentInterval(newValue as String)
true true
} }
autoUpdateCheck?.setOnPreferenceChangeListener { _, newValue ->
val value = newValue as Boolean
autoUpdateCheck?.isChecked = value
autoUpdateInterval?.isEnabled = value
autoUpdateInterval?.text?.toLong()?.let {
if (newValue) configureUpdateTask(it) else cancelUpdateTask()
}
true
}
autoUpdateInterval?.setOnPreferenceChangeListener { _, any ->
var nval = any as String
// It must be greater than 15 minutes because WorkManager couldn't run tasks under 15 minutes intervals
nval =
if (TextUtils.isEmpty(nval) || nval.toLong() < 15) AppConfig.SUBSCRIPTION_DEFAULT_UPDATE_INTERVAL else nval
autoUpdateInterval?.summary = nval
configureUpdateTask(nval.toLong())
true
}
socksPort?.setOnPreferenceChangeListener { _, any ->
val nval = any as String
socksPort?.summary = if (TextUtils.isEmpty(nval)) AppConfig.PORT_SOCKS else nval
true
}
httpPort?.setOnPreferenceChangeListener { _, any ->
val nval = any as String
httpPort?.summary = if (TextUtils.isEmpty(nval)) AppConfig.PORT_HTTP else nval
true
}
remoteDns?.setOnPreferenceChangeListener { _, any ->
val nval = any as String
remoteDns?.summary = if (nval == "") AppConfig.DNS_PROXY else nval
true
}
domesticDns?.setOnPreferenceChangeListener { _, any ->
val nval = any as String
domesticDns?.summary = if (nval == "") AppConfig.DNS_DIRECT else nval
true
}
delayTestUrl?.setOnPreferenceChangeListener { _, any ->
val nval = any as String
delayTestUrl?.summary = if (nval == "") AppConfig.DelayTestUrl else nval
true
}
mode?.setOnPreferenceChangeListener { _, newValue ->
updateMode(newValue.toString())
true
}
mode?.dialogLayoutResource = R.layout.preference_with_help_link
//loglevel.summary = "LogLevel"
} }
override fun onStart() { override fun onStart() {
super.onStart() super.onStart()
val defaultSharedPreferences = updateMode(settingsStorage.decodeString(AppConfig.PREF_MODE, "VPN"))
PreferenceManager.getDefaultSharedPreferences(requireActivity()) localDns?.isChecked = settingsStorage.getBoolean(AppConfig.PREF_LOCAL_DNS_ENABLED, false)
updateMode(defaultSharedPreferences.getString(AppConfig.PREF_MODE, "VPN")) fakeDns?.isChecked = settingsStorage.getBoolean(AppConfig.PREF_FAKE_DNS_ENABLED, false)
var remoteDnsString = defaultSharedPreferences.getString(AppConfig.PREF_REMOTE_DNS, "") localDnsPort?.summary = settingsStorage.decodeString(AppConfig.PREF_LOCAL_DNS_PORT, AppConfig.PORT_LOCAL_DNS)
vpnDns?.summary = settingsStorage.decodeString(AppConfig.PREF_VPN_DNS, AppConfig.DNS_VPN)
domesticDns?.summary = defaultSharedPreferences.getString(AppConfig.PREF_DOMESTIC_DNS, "") updateMux(settingsStorage.getBoolean(AppConfig.PREF_MUX_ENABLED, false))
localDnsPort?.summary = defaultSharedPreferences.getString(AppConfig.PREF_LOCAL_DNS_PORT, AppConfig.PORT_LOCAL_DNS) mux?.isChecked = settingsStorage.getBoolean(AppConfig.PREF_MUX_ENABLED, false)
socksPort?.summary = defaultSharedPreferences.getString(AppConfig.PREF_SOCKS_PORT, AppConfig.PORT_SOCKS) muxConcurrency?.summary = settingsStorage.decodeString(AppConfig.PREF_MUX_CONCURRENCY, "8")
httpPort?.summary = defaultSharedPreferences.getString(AppConfig.PREF_HTTP_PORT, AppConfig.PORT_HTTP) muxXudpConcurrency?.summary = settingsStorage.decodeString(AppConfig.PREF_MUX_XUDP_CONCURRENCY, "8")
updateMux(defaultSharedPreferences.getBoolean(AppConfig.PREF_MUX_ENABLED, false))
muxConcurrency?.summary = defaultSharedPreferences.getString(AppConfig.PREF_MUX_CONCURRENCY, "8")
muxXudpConcurrency?.summary = defaultSharedPreferences.getString(AppConfig.PREF_MUX_XUDP_CONCURRENCY, "8")
updateFragment(defaultSharedPreferences.getBoolean(AppConfig.PREF_FRAGMENT_ENABLED, false))
fragmentPackets?.summary = defaultSharedPreferences.getString(AppConfig.PREF_FRAGMENT_PACKETS, "tlshello")
fragmentLength?.summary = defaultSharedPreferences.getString(AppConfig.PREF_FRAGMENT_LENGTH, "50-100")
fragmentInterval?.summary = defaultSharedPreferences.getString(AppConfig.PREF_FRAGMENT_INTERVAL, "10-20")
autoUpdateInterval?.summary = defaultSharedPreferences.getString(AppConfig.SUBSCRIPTION_AUTO_UPDATE_INTERVAL,AppConfig.SUBSCRIPTION_DEFAULT_UPDATE_INTERVAL)
autoUpdateInterval?.isEnabled = defaultSharedPreferences.getBoolean(AppConfig.SUBSCRIPTION_AUTO_UPDATE, false)
if (TextUtils.isEmpty(remoteDnsString)) { updateFragment(settingsStorage.getBoolean(AppConfig.PREF_FRAGMENT_ENABLED, false))
remoteDnsString = AppConfig.DNS_AGENT fragment?.isChecked = settingsStorage.getBoolean(AppConfig.PREF_FRAGMENT_ENABLED, false)
} fragmentPackets?.summary = settingsStorage.decodeString(AppConfig.PREF_FRAGMENT_PACKETS, "tlshello")
if (TextUtils.isEmpty(domesticDns?.summary)) { fragmentLength?.summary = settingsStorage.decodeString(AppConfig.PREF_FRAGMENT_LENGTH, "50-100")
domesticDns?.summary = AppConfig.DNS_DIRECT fragmentInterval?.summary = settingsStorage.decodeString(AppConfig.PREF_FRAGMENT_INTERVAL, "10-20")
}
remoteDns?.summary = remoteDnsString
vpnDns?.summary =
defaultSharedPreferences.getString(AppConfig.PREF_VPN_DNS, remoteDnsString)
if (TextUtils.isEmpty(localDnsPort?.summary)) { autoUpdateCheck?.isChecked = settingsStorage.getBoolean(AppConfig.SUBSCRIPTION_AUTO_UPDATE, false)
localDnsPort?.summary = AppConfig.PORT_LOCAL_DNS autoUpdateInterval?.summary = settingsStorage.decodeString(AppConfig.SUBSCRIPTION_AUTO_UPDATE_INTERVAL,AppConfig.SUBSCRIPTION_DEFAULT_UPDATE_INTERVAL)
autoUpdateInterval?.isEnabled = settingsStorage.getBoolean(AppConfig.SUBSCRIPTION_AUTO_UPDATE, false)
socksPort?.summary = settingsStorage.decodeString(AppConfig.PREF_SOCKS_PORT, AppConfig.PORT_SOCKS)
httpPort?.summary = settingsStorage.decodeString(AppConfig.PREF_HTTP_PORT, AppConfig.PORT_HTTP)
remoteDns?.summary = settingsStorage.decodeString(AppConfig.PREF_REMOTE_DNS, AppConfig.DNS_PROXY)
domesticDns?.summary = settingsStorage.decodeString(AppConfig.PREF_DOMESTIC_DNS, AppConfig.DNS_DIRECT)
delayTestUrl?.summary = settingsStorage.decodeString(AppConfig.PREF_DELAY_TEST_URL, AppConfig.DelayTestUrl)
initSharedPreference()
}
private fun initSharedPreference() {
listOf(
localDnsPort,
vpnDns,
muxConcurrency,
muxXudpConcurrency,
fragmentLength,
fragmentInterval,
autoUpdateInterval,
socksPort,
httpPort,
remoteDns,
domesticDns,
delayTestUrl
).forEach { key ->
key?.text = key?.summary.toString()
} }
if (TextUtils.isEmpty(socksPort?.summary)) {
socksPort?.summary = AppConfig.PORT_SOCKS listOf(
AppConfig.PREF_SNIFFING_ENABLED,
).forEach { key ->
findPreference<CheckBoxPreference>(key)?.isChecked =
settingsStorage.decodeBool(key, true)
} }
if (TextUtils.isEmpty(httpPort?.summary)) {
httpPort?.summary = AppConfig.PORT_HTTP listOf(
AppConfig.PREF_ROUTE_ONLY_ENABLED,
AppConfig.PREF_BYPASS_APPS,
AppConfig.PREF_SPEED_ENABLED,
AppConfig.PREF_CONFIRM_REMOVE,
AppConfig.PREF_START_SCAN_IMMEDIATE,
AppConfig.PREF_PREFER_IPV6,
AppConfig.PREF_PROXY_SHARING,
AppConfig.PREF_ALLOW_INSECURE
).forEach { key ->
findPreference<CheckBoxPreference>(key)?.isChecked =
settingsStorage.decodeBool(key, false)
}
listOf(
AppConfig.PREF_ROUTING_DOMAIN_STRATEGY,
AppConfig.PREF_ROUTING_MODE,
AppConfig.PREF_MUX_XUDP_QUIC,
AppConfig.PREF_FRAGMENT_PACKETS,
AppConfig.PREF_LANGUAGE,
AppConfig.PREF_UI_MODE_NIGHT,
AppConfig.PREF_LOGLEVEL,
AppConfig.PREF_MODE
).forEach { key ->
if (settingsStorage.decodeString(key) != null) {
findPreference<ListPreference>(key)?.value = settingsStorage.decodeString(key)
}
} }
} }
private fun updateMode(mode: String?) { private fun updateMode(mode: String?) {
val defaultSharedPreferences =
PreferenceManager.getDefaultSharedPreferences(requireActivity())
val vpn = mode == "VPN" val vpn = mode == "VPN"
perAppProxy?.isEnabled = vpn perAppProxy?.isEnabled = vpn
perAppProxy?.isChecked = perAppProxy?.isChecked = settingsStorage.getBoolean(AppConfig.PREF_PER_APP_PROXY, false)
PreferenceManager.getDefaultSharedPreferences(requireActivity())
.getBoolean(AppConfig.PREF_PER_APP_PROXY, false)
localDns?.isEnabled = vpn localDns?.isEnabled = vpn
fakeDns?.isEnabled = vpn fakeDns?.isEnabled = vpn
localDnsPort?.isEnabled = vpn localDnsPort?.isEnabled = vpn
vpnDns?.isEnabled = vpn vpnDns?.isEnabled = vpn
if (vpn) { if (vpn) {
updateLocalDns( updateLocalDns(
defaultSharedPreferences.getBoolean( settingsStorage.getBoolean(
AppConfig.PREF_LOCAL_DNS_ENABLED, AppConfig.PREF_LOCAL_DNS_ENABLED,
false false
) )
@@ -283,15 +311,14 @@ class SettingsActivity : BaseActivity() {
val rw = RemoteWorkManager.getInstance(AngApplication.application) val rw = RemoteWorkManager.getInstance(AngApplication.application)
rw.cancelUniqueWork(AppConfig.SUBSCRIPTION_UPDATE_TASK_NAME) rw.cancelUniqueWork(AppConfig.SUBSCRIPTION_UPDATE_TASK_NAME)
} }
private fun updateMux(enabled: Boolean) { private fun updateMux(enabled: Boolean) {
val defaultSharedPreferences = PreferenceManager.getDefaultSharedPreferences(requireActivity())
muxConcurrency?.isEnabled = enabled muxConcurrency?.isEnabled = enabled
muxXudpConcurrency?.isEnabled = enabled muxXudpConcurrency?.isEnabled = enabled
muxXudpQuic?.isEnabled = enabled muxXudpQuic?.isEnabled = enabled
if (enabled) { if (enabled) {
updateMuxConcurrency(defaultSharedPreferences.getString(AppConfig.PREF_MUX_CONCURRENCY, "8")) updateMuxConcurrency(settingsStorage.decodeString(AppConfig.PREF_MUX_CONCURRENCY, "8"))
updateMuxXudpConcurrency(defaultSharedPreferences.getString(AppConfig.PREF_MUX_XUDP_CONCURRENCY, "8")) updateMuxXudpConcurrency(settingsStorage.decodeString(AppConfig.PREF_MUX_XUDP_CONCURRENCY, "8"))
} }
} }
@@ -314,14 +341,13 @@ class SettingsActivity : BaseActivity() {
} }
private fun updateFragment(enabled: Boolean) { private fun updateFragment(enabled: Boolean) {
val defaultSharedPreferences = PreferenceManager.getDefaultSharedPreferences(requireActivity())
fragmentPackets?.isEnabled = enabled fragmentPackets?.isEnabled = enabled
fragmentLength?.isEnabled = enabled fragmentLength?.isEnabled = enabled
fragmentInterval?.isEnabled = enabled fragmentInterval?.isEnabled = enabled
if (enabled) { if (enabled) {
updateFragmentPackets(defaultSharedPreferences.getString(AppConfig.PREF_FRAGMENT_PACKETS, "tlshello")) updateFragmentPackets(settingsStorage.decodeString(AppConfig.PREF_FRAGMENT_PACKETS, "tlshello"))
updateFragmentLength(defaultSharedPreferences.getString(AppConfig.PREF_FRAGMENT_LENGTH, "50-100")) updateFragmentLength(settingsStorage.decodeString(AppConfig.PREF_FRAGMENT_LENGTH, "50-100"))
updateFragmentInterval(defaultSharedPreferences.getString(AppConfig.PREF_FRAGMENT_INTERVAL, "10-20")) updateFragmentInterval(settingsStorage.decodeString(AppConfig.PREF_FRAGMENT_INTERVAL, "10-20"))
} }
} }
private fun updateFragmentPackets(value: String?) { private fun updateFragmentPackets(value: String?) {

View File

@@ -111,6 +111,9 @@ class SubEditActivity : BaseActivity() {
MmkvManager.removeSubscription(editSubId) MmkvManager.removeSubscription(editSubId)
finish() finish()
} }
.setNegativeButton(android.R.string.no) {_, _ ->
// do nothing
}
.show() .show()
} }
return true return true

View File

@@ -1,19 +1,27 @@
package com.v2ray.ang.ui package com.v2ray.ang.ui
import android.content.Intent import android.content.Intent
import androidx.recyclerview.widget.LinearLayoutManager import android.os.Bundle
import android.view.Menu import android.view.Menu
import android.view.MenuItem import android.view.MenuItem
import androidx.appcompat.app.AlertDialog
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.LinearLayoutManager
import com.v2ray.ang.R import com.v2ray.ang.R
import android.os.Bundle
import com.v2ray.ang.databinding.ActivitySubSettingBinding import com.v2ray.ang.databinding.ActivitySubSettingBinding
import com.v2ray.ang.databinding.LayoutProgressBinding
import com.v2ray.ang.dto.SubscriptionItem import com.v2ray.ang.dto.SubscriptionItem
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 kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
class SubSettingActivity : BaseActivity() { class SubSettingActivity : BaseActivity() {
private lateinit var binding: ActivitySubSettingBinding private lateinit var binding: ActivitySubSettingBinding
var subscriptions:List<Pair<String, SubscriptionItem>> = listOf() var subscriptions: List<Pair<String, SubscriptionItem>> = listOf()
private val adapter by lazy { SubSettingRecyclerAdapter(this) } private val adapter by lazy { SubSettingRecyclerAdapter(this) }
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
@@ -37,9 +45,6 @@ class SubSettingActivity : BaseActivity() {
override fun onCreateOptionsMenu(menu: Menu): Boolean { override fun onCreateOptionsMenu(menu: Menu): Boolean {
menuInflater.inflate(R.menu.action_sub_setting, menu) menuInflater.inflate(R.menu.action_sub_setting, menu)
menu.findItem(R.id.del_config)?.isVisible = false
menu.findItem(R.id.save_config)?.isVisible = false
return super.onCreateOptionsMenu(menu) return super.onCreateOptionsMenu(menu)
} }
@@ -48,6 +53,30 @@ class SubSettingActivity : BaseActivity() {
startActivity(Intent(this, SubEditActivity::class.java)) startActivity(Intent(this, SubEditActivity::class.java))
true true
} }
R.id.sub_update -> {
val dialog = AlertDialog.Builder(this)
.setView(LayoutProgressBinding.inflate(layoutInflater).root)
.setCancelable(false)
.show()
lifecycleScope.launch(Dispatchers.IO) {
val count = AngConfigManager.updateConfigViaSubAll()
delay(500L)
launch(Dispatchers.Main) {
if (count > 0) {
toast(R.string.toast_success)
} else {
toast(R.string.toast_failure)
}
dialog.dismiss()
}
}
true
}
else -> super.onOptionsItemSelected(item) else -> super.onOptionsItemSelected(item)
} }
} }

View File

@@ -5,17 +5,15 @@ import android.graphics.Color
import android.text.TextUtils import android.text.TextUtils
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import androidx.recyclerview.widget.RecyclerView
import android.view.ViewGroup import android.view.ViewGroup
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import androidx.recyclerview.widget.RecyclerView
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.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.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.QRCodeDecoder
import com.v2ray.ang.util.Utils import com.v2ray.ang.util.Utils
@@ -38,9 +36,9 @@ class SubSettingRecyclerAdapter(val activity: SubSettingActivity) :
holder.itemSubSettingBinding.tvName.text = subItem.remarks holder.itemSubSettingBinding.tvName.text = subItem.remarks
holder.itemSubSettingBinding.tvUrl.text = subItem.url holder.itemSubSettingBinding.tvUrl.text = subItem.url
if (subItem.enabled) { if (subItem.enabled) {
holder.itemSubSettingBinding.chkEnable.setBackgroundResource(R.color.colorSelected) holder.itemSubSettingBinding.chkEnable.setBackgroundResource(R.color.colorAccent)
} else { } else {
holder.itemSubSettingBinding.chkEnable.setBackgroundResource(R.color.colorUnselected) holder.itemSubSettingBinding.chkEnable.setBackgroundResource(0)
} }
holder.itemView.setBackgroundColor(Color.TRANSPARENT) holder.itemView.setBackgroundColor(Color.TRANSPARENT)

View File

@@ -44,7 +44,7 @@ class TaskerActivity : BaseActivity() {
val adapter = ArrayAdapter(this, val adapter = ArrayAdapter(this,
android.R.layout.simple_list_item_single_choice, lstData) android.R.layout.simple_list_item_single_choice, lstData)
listview = findViewById<View>(R.id.listview) as ListView listview = findViewById<View>(R.id.listview) as ListView
listview!!.adapter = adapter listview?.adapter = adapter
init() init()
} }

View File

@@ -3,7 +3,7 @@ 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.v2ray.ang.AppConfig import android.util.Log
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
@@ -24,32 +24,21 @@ class UrlSchemeActivity : BaseActivity() {
if (action == Intent.ACTION_SEND) { if (action == 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) parseUri(it)
if (uri.scheme?.startsWith(AppConfig.PROTOCOL_HTTPS) == true || uri.scheme?.startsWith(
AppConfig.PROTOCOL_HTTP
) == true
) {
val name = uri.getQueryParameter("name") ?: "Subscription"
importSubscription(it, name)
} else {
importConfig(it)
}
} }
} }
} else if (action == Intent.ACTION_VIEW) { } else if (action == Intent.ACTION_VIEW) {
when (data?.host) { when (data?.host) {
"install-config" -> { "install-config" -> {
val uri: Uri? = intent.data val uri: Uri? = intent.data
val shareUrl: String = uri?.getQueryParameter("url")!! val shareUrl = uri?.getQueryParameter("url") ?: ""
toast(shareUrl) parseUri(shareUrl)
importConfig(shareUrl)
} }
"install-sub" -> { "install-sub" -> {
val uri: Uri? = intent.data val uri: Uri? = intent.data
val url = uri?.getQueryParameter("url")!! val shareUrl = uri?.getQueryParameter("url") ?: ""
val name = uri.getQueryParameter("name") ?: "Subscription" parseUri(shareUrl)
importSubscription(url, name)
} }
else -> { else -> {
@@ -57,10 +46,8 @@ class UrlSchemeActivity : BaseActivity() {
} }
} }
} }
} }
startActivity(Intent(this, MainActivity::class.java)) startActivity(Intent(this, MainActivity::class.java))
finish() finish()
} catch (e: Exception) { } catch (e: Exception) {
@@ -68,19 +55,21 @@ class UrlSchemeActivity : BaseActivity() {
} }
} }
private fun importSubscription(url: String, name: String) { private fun parseUri(uriString: String?) {
val decodedUrl = URLDecoder.decode(url, "UTF-8") if (uriString.isNullOrEmpty()) {
return
}
Log.d("UrlScheme", uriString)
val check = AngConfigManager.importSubscription(name, decodedUrl) val decodedUrl = URLDecoder.decode(uriString, "UTF-8")
if (check) toast(R.string.import_subscription_success) else toast(R.string.import_subscription_failure) val uri = Uri.parse(decodedUrl)
} if (uri != null) {
val count = AngConfigManager.importBatchConfig(decodedUrl, "", false)
private fun importConfig(shareUrl: String) { if (count > 0) {
val count = AngConfigManager.importBatchConfig(shareUrl, "", false) toast(R.string.import_subscription_success)
if (count > 0) { } else {
toast(R.string.toast_success) toast(R.string.import_subscription_failure)
} else { }
toast(R.string.toast_failure)
} }
} }
} }

View File

@@ -177,7 +177,10 @@ class UserAssetActivity : BaseActivity() {
assets.forEach { assets.forEach {
//toast(getString(R.string.msg_downloading_content) + it) //toast(getString(R.string.msg_downloading_content) + it)
lifecycleScope.launch(Dispatchers.IO) { lifecycleScope.launch(Dispatchers.IO) {
val result = downloadGeo(it.second, 60000, httpPort) var result = downloadGeo(it.second, 60000, httpPort)
if (!result) {
result = downloadGeo(it.second, 60000, 0)
}
launch(Dispatchers.Main) { launch(Dispatchers.Main) {
if (result) { if (result) {
toast(getString(R.string.toast_success) + " " + it.second.remarks) toast(getString(R.string.toast_success) + " " + it.second.remarks)
@@ -197,12 +200,16 @@ class UserAssetActivity : BaseActivity() {
//Log.d(AppConfig.ANG_PACKAGE, url) //Log.d(AppConfig.ANG_PACKAGE, url)
try { try {
conn = URL(item.url).openConnection( conn = if (httpPort == 0) {
Proxy( URL(item.url).openConnection() as HttpURLConnection
Proxy.Type.HTTP, } else {
InetSocketAddress("127.0.0.1", httpPort) URL(item.url).openConnection(
) Proxy(
) as HttpURLConnection Proxy.Type.HTTP,
InetSocketAddress("127.0.0.1", httpPort)
)
) as HttpURLConnection
}
conn.connectTimeout = timeout conn.connectTimeout = timeout
conn.readTimeout = timeout conn.readTimeout = timeout
val inputStream = conn.inputStream val inputStream = conn.inputStream
@@ -224,13 +231,14 @@ class UserAssetActivity : BaseActivity() {
} }
private fun addBuiltInGeoItems(assets: List<Pair<String, AssetUrlItem>>): List<Pair<String, AssetUrlItem>> { private fun addBuiltInGeoItems(assets: List<Pair<String, AssetUrlItem>>): List<Pair<String, AssetUrlItem>> {
val list = mutableListOf<Pair<String, AssetUrlItem>>() val list = mutableListOf<Pair<String, AssetUrlItem>>()
builtInGeoFiles.forEach { builtInGeoFiles
list.add(Utils.getUuid() to AssetUrlItem( .filter { geoFile -> assets.none { it.second.remarks == geoFile } }
it, .forEach {
AppConfig.geoUrl + it list.add(Utils.getUuid() to AssetUrlItem(
) it,
) AppConfig.GeoUrl + it
} ))
}
return list + assets return list + assets
} }
@@ -263,7 +271,7 @@ class UserAssetActivity : BaseActivity() {
holder.itemUserAssetBinding.assetProperties.text = getString(R.string.msg_file_not_found) holder.itemUserAssetBinding.assetProperties.text = getString(R.string.msg_file_not_found)
} }
if (item.second.remarks in builtInGeoFiles) { if (item.second.remarks in builtInGeoFiles && item.second.url == AppConfig.GeoUrl + item.second.remarks) {
holder.itemUserAssetBinding.layoutEdit.visibility = GONE holder.itemUserAssetBinding.layoutEdit.visibility = GONE
holder.itemUserAssetBinding.layoutRemove.visibility = GONE holder.itemUserAssetBinding.layoutRemove.visibility = GONE
} else { } else {

View File

@@ -114,6 +114,9 @@ class UserAssetUrlActivity : BaseActivity() {
MmkvManager.removeAssetUrl(editAssetId) MmkvManager.removeAssetUrl(editAssetId)
finish() finish()
} }
.setNegativeButton(android.R.string.no) {_, _ ->
// do nothing
}
.show() .show()
} }
return true return true

View File

@@ -7,7 +7,6 @@ import android.content.pm.PackageInfo
import android.content.pm.PackageManager import android.content.pm.PackageManager
import com.v2ray.ang.dto.AppInfo import com.v2ray.ang.dto.AppInfo
import rx.Observable import rx.Observable
import java.util.*
object AppManagerUtil { object AppManagerUtil {
fun loadNetworkAppList(ctx: Context): ArrayList<AppInfo> { fun loadNetworkAppList(ctx: Context): ArrayList<AppInfo> {
@@ -31,9 +30,10 @@ object AppManagerUtil {
return apps return apps
} }
fun rxLoadNetworkAppList(ctx: Context): Observable<ArrayList<AppInfo>> = Observable.unsafeCreate { fun rxLoadNetworkAppList(ctx: Context): Observable<ArrayList<AppInfo>> =
it.onNext(loadNetworkAppList(ctx)) Observable.unsafeCreate {
} it.onNext(loadNetworkAppList(ctx))
}
val PackageInfo.hasInternetPermission: Boolean val PackageInfo.hasInternetPermission: Boolean
get() { get() {

View File

@@ -106,8 +106,8 @@ object MmkvManager {
serverAffStorage?.encode(guid, Gson().toJson(aff)) serverAffStorage?.encode(guid, Gson().toJson(aff))
} }
fun clearAllTestDelayResults() { fun clearAllTestDelayResults(keys: List<String>?) {
serverAffStorage?.allKeys()?.forEach { key -> keys?.forEach { key ->
decodeServerAffiliationInfo(key)?.let { aff -> decodeServerAffiliationInfo(key)?.let { aff ->
aff.testDelayMillis = 0 aff.testDelayMillis = 0
serverAffStorage?.encode(key, Gson().toJson(aff)) serverAffStorage?.encode(key, Gson().toJson(aff))
@@ -172,14 +172,14 @@ object MmkvManager {
fun removeInvalidServer() { fun removeInvalidServer() {
serverAffStorage?.allKeys()?.forEach { key -> serverAffStorage?.allKeys()?.forEach { key ->
decodeServerAffiliationInfo(key)?.let { aff -> decodeServerAffiliationInfo(key)?.let { aff ->
if (aff.testDelayMillis <= 0L) { if (aff.testDelayMillis < 0L) {
removeServer(key) removeServer(key)
} }
} }
} }
} }
fun sortByTestResults( ) { fun sortByTestResults() {
data class ServerDelay(var guid: String, var testDelayMillis: Long) data class ServerDelay(var guid: String, var testDelayMillis: Long)
val serverDelays = mutableListOf<ServerDelay>() val serverDelays = mutableListOf<ServerDelay>()

View File

@@ -7,7 +7,7 @@ import android.content.res.Resources
import android.os.Build import android.os.Build
import android.os.LocaleList import android.os.LocaleList
import androidx.annotation.RequiresApi import androidx.annotation.RequiresApi
import java.util.* import java.util.Locale
open class MyContextWrapper(base: Context?) : ContextWrapper(base) { open class MyContextWrapper(base: Context?) : ContextWrapper(base) {
companion object { companion object {

View File

@@ -2,11 +2,16 @@ package com.v2ray.ang.util
import android.graphics.Bitmap import android.graphics.Bitmap
import android.graphics.BitmapFactory import android.graphics.BitmapFactory
import com.google.zxing.* import com.google.zxing.BarcodeFormat
import com.google.zxing.BinaryBitmap
import com.google.zxing.DecodeHintType
import com.google.zxing.EncodeHintType
import com.google.zxing.NotFoundException
import com.google.zxing.RGBLuminanceSource
import com.google.zxing.common.GlobalHistogramBinarizer import com.google.zxing.common.GlobalHistogramBinarizer
import com.google.zxing.common.HybridBinarizer import com.google.zxing.qrcode.QRCodeReader
import com.google.zxing.qrcode.QRCodeWriter import com.google.zxing.qrcode.QRCodeWriter
import java.util.* import java.util.EnumMap
/** /**
* 描述:解析二维码图片 * 描述:解析二维码图片
@@ -21,8 +26,10 @@ object QRCodeDecoder {
try { try {
val hints = HashMap<EncodeHintType, String>() val hints = HashMap<EncodeHintType, String>()
hints[EncodeHintType.CHARACTER_SET] = "utf-8" hints[EncodeHintType.CHARACTER_SET] = "utf-8"
val bitMatrix = QRCodeWriter().encode(text, val bitMatrix = QRCodeWriter().encode(
BarcodeFormat.QR_CODE, size, size, hints) text,
BarcodeFormat.QR_CODE, size, size, hints
)
val pixels = IntArray(size * size) val pixels = IntArray(size * size)
for (y in 0 until size) { for (y in 0 until size) {
for (x in 0 until size) { for (x in 0 until size) {
@@ -34,8 +41,10 @@ object QRCodeDecoder {
} }
} }
val bitmap = Bitmap.createBitmap(size, size, val bitmap = Bitmap.createBitmap(
Bitmap.Config.ARGB_8888) size, size,
Bitmap.Config.ARGB_8888
)
bitmap.setPixels(pixels, 0, size, 0, 0, size, size) bitmap.setPixels(pixels, 0, size, 0, 0, size, size)
return bitmap return bitmap
} catch (e: Exception) { } catch (e: Exception) {
@@ -61,24 +70,37 @@ object QRCodeDecoder {
* @return 返回二维码图片里的内容 或 null * @return 返回二维码图片里的内容 或 null
*/ */
fun syncDecodeQRCode(bitmap: Bitmap?): String? { fun syncDecodeQRCode(bitmap: Bitmap?): String? {
if (bitmap == null) {
return null
}
var source: RGBLuminanceSource? = null var source: RGBLuminanceSource? = null
try { try {
val width = bitmap!!.width val width = bitmap.width
val height = bitmap.height val height = bitmap.height
val pixels = IntArray(width * height) val pixels = IntArray(width * height)
bitmap.getPixels(pixels, 0, width, 0, 0, width, height) bitmap.getPixels(pixels, 0, width, 0, 0, width, height)
source = RGBLuminanceSource(width, height, pixels) source = RGBLuminanceSource(width, height, pixels)
return MultiFormatReader().decode(BinaryBitmap(HybridBinarizer(source)), HINTS).text val qrReader = QRCodeReader()
try {
val result = try {
qrReader.decode(
BinaryBitmap(GlobalHistogramBinarizer(source)),
mapOf(DecodeHintType.TRY_HARDER to true)
)
} catch (e: NotFoundException) {
qrReader.decode(
BinaryBitmap(GlobalHistogramBinarizer(source.invert())),
mapOf(DecodeHintType.TRY_HARDER to true)
)
}
return result.text
} catch (e: Exception) {
e.printStackTrace()
}
} catch (e: Exception) { } catch (e: Exception) {
e.printStackTrace() e.printStackTrace()
} }
if (source != null) {
try {
return MultiFormatReader().decode(BinaryBitmap(GlobalHistogramBinarizer(source)), HINTS).text
} catch (e2: Throwable) {
e2.printStackTrace()
}
}
return null return null
} }
@@ -107,23 +129,24 @@ object QRCodeDecoder {
init { init {
val allFormats: List<BarcodeFormat> = arrayListOf( val allFormats: List<BarcodeFormat> = arrayListOf(
BarcodeFormat.AZTEC BarcodeFormat.AZTEC,
,BarcodeFormat.CODABAR BarcodeFormat.CODABAR,
,BarcodeFormat.CODE_39 BarcodeFormat.CODE_39,
,BarcodeFormat.CODE_93 BarcodeFormat.CODE_93,
,BarcodeFormat.CODE_128 BarcodeFormat.CODE_128,
,BarcodeFormat.DATA_MATRIX BarcodeFormat.DATA_MATRIX,
,BarcodeFormat.EAN_8 BarcodeFormat.EAN_8,
,BarcodeFormat.EAN_13 BarcodeFormat.EAN_13,
,BarcodeFormat.ITF BarcodeFormat.ITF,
,BarcodeFormat.MAXICODE BarcodeFormat.MAXICODE,
,BarcodeFormat.PDF_417 BarcodeFormat.PDF_417,
,BarcodeFormat.QR_CODE BarcodeFormat.QR_CODE,
,BarcodeFormat.RSS_14 BarcodeFormat.RSS_14,
,BarcodeFormat.RSS_EXPANDED BarcodeFormat.RSS_EXPANDED,
,BarcodeFormat.UPC_A BarcodeFormat.UPC_A,
,BarcodeFormat.UPC_E BarcodeFormat.UPC_E,
,BarcodeFormat.UPC_EAN_EXTENSION) BarcodeFormat.UPC_EAN_EXTENSION
)
HINTS[DecodeHintType.TRY_HARDER] = BarcodeFormat.QR_CODE HINTS[DecodeHintType.TRY_HARDER] = BarcodeFormat.QR_CODE
HINTS[DecodeHintType.POSSIBLE_FORMATS] = allFormats HINTS[DecodeHintType.POSSIBLE_FORMATS] = allFormats
HINTS[DecodeHintType.CHARACTER_SET] = "utf-8" HINTS[DecodeHintType.CHARACTER_SET] = "utf-8"

View File

@@ -10,8 +10,12 @@ import com.v2ray.ang.extension.responseLength
import kotlinx.coroutines.isActive import kotlinx.coroutines.isActive
import libv2ray.Libv2ray import libv2ray.Libv2ray
import java.io.IOException import java.io.IOException
import java.net.* import java.net.HttpURLConnection
import java.util.* import java.net.InetSocketAddress
import java.net.Proxy
import java.net.Socket
import java.net.URL
import java.net.UnknownHostException
import kotlin.coroutines.coroutineContext import kotlin.coroutines.coroutineContext
object SpeedtestUtil { object SpeedtestUtil {
@@ -34,7 +38,7 @@ object SpeedtestUtil {
fun realPing(config: String): Long { fun realPing(config: String): Long {
return try { return try {
Libv2ray.measureOutboundDelay(config) Libv2ray.measureOutboundDelay(config, Utils.getDelayTestUrl())
} catch (e: Exception) { } catch (e: Exception) {
Log.d(AppConfig.ANG_PACKAGE, "realPing: $e") Log.d(AppConfig.ANG_PACKAGE, "realPing: $e")
-1L -1L
@@ -48,7 +52,8 @@ object SpeedtestUtil {
val allText = process.inputStream.bufferedReader().use { it.readText() } val allText = process.inputStream.bufferedReader().use { it.readText() }
if (!TextUtils.isEmpty(allText)) { if (!TextUtils.isEmpty(allText)) {
val tempInfo = allText.substring(allText.indexOf("min/avg/max/mdev") + 19) val tempInfo = allText.substring(allText.indexOf("min/avg/max/mdev") + 19)
val temps = tempInfo.split("/".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray() val temps =
tempInfo.split("/".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
if (temps.count() > 0 && temps[0].length < 10) { if (temps.count() > 0 && temps[0].length < 10) {
return temps[0].toFloat().toInt().toString() + "ms" return temps[0].toFloat().toInt().toString() + "ms"
} }
@@ -66,7 +71,7 @@ object SpeedtestUtil {
tcpTestingSockets.add(socket) tcpTestingSockets.add(socket)
} }
val start = System.currentTimeMillis() val start = System.currentTimeMillis()
socket.connect(InetSocketAddress(url, port),3000) socket.connect(InetSocketAddress(url, port), 3000)
val time = System.currentTimeMillis() - start val time = System.currentTimeMillis() - start
synchronized(this) { synchronized(this) {
tcpTestingSockets.remove(socket) tcpTestingSockets.remove(socket)
@@ -98,13 +103,14 @@ object SpeedtestUtil {
var conn: HttpURLConnection? = null var conn: HttpURLConnection? = null
try { try {
val url = URL("https", val url = URL(Utils.getDelayTestUrl())
"www.google.com",
"/generate_204")
conn = url.openConnection( conn = url.openConnection(
Proxy(Proxy.Type.HTTP, Proxy(
InetSocketAddress("127.0.0.1", port))) as HttpURLConnection Proxy.Type.HTTP,
InetSocketAddress("127.0.0.1", port)
)
) as HttpURLConnection
conn.connectTimeout = 30000 conn.connectTimeout = 30000
conn.readTimeout = 30000 conn.readTimeout = 30000
conn.setRequestProperty("Connection", "close") conn.setRequestProperty("Connection", "close")
@@ -118,11 +124,19 @@ object SpeedtestUtil {
if (code == 204 || code == 200 && conn.responseLength == 0L) { if (code == 204 || code == 200 && conn.responseLength == 0L) {
result = context.getString(R.string.connection_test_available, elapsed) result = context.getString(R.string.connection_test_available, elapsed)
} else { } else {
throw IOException(context.getString(R.string.connection_test_error_status_code, code)) throw IOException(
context.getString(
R.string.connection_test_error_status_code,
code
)
)
} }
} catch (e: IOException) { } catch (e: IOException) {
// network exception // network exception
Log.d(AppConfig.ANG_PACKAGE, "testConnection IOException: " + Log.getStackTraceString(e)) Log.d(
AppConfig.ANG_PACKAGE,
"testConnection IOException: " + Log.getStackTraceString(e)
)
result = context.getString(R.string.connection_test_error, e.message) result = context.getString(R.string.connection_test_error, e.message)
} catch (e: Exception) { } catch (e: Exception) {
// library exception, eg sumsung // library exception, eg sumsung

View File

@@ -1,11 +1,8 @@
package com.v2ray.ang.util package com.v2ray.ang.util
import android.content.ClipData
import android.content.ClipboardManager import android.content.ClipboardManager
import android.content.Context import android.content.Context
import android.text.Editable
import android.util.Base64
import java.util.*
import android.content.ClipData
import android.content.Intent import android.content.Intent
import android.content.pm.PackageManager import android.content.pm.PackageManager
import android.content.res.Configuration.UI_MODE_NIGHT_MASK import android.content.res.Configuration.UI_MODE_NIGHT_MASK
@@ -14,18 +11,22 @@ import android.net.Uri
import android.os.Build import android.os.Build
import android.os.LocaleList import android.os.LocaleList
import android.provider.Settings import android.provider.Settings
import android.text.Editable
import android.util.Base64
import android.util.Log import android.util.Log
import android.util.Patterns import android.util.Patterns
import android.webkit.URLUtil import android.webkit.URLUtil
import androidx.appcompat.app.AppCompatDelegate
import com.tencent.mmkv.MMKV import com.tencent.mmkv.MMKV
import com.v2ray.ang.AppConfig import com.v2ray.ang.AppConfig
import com.v2ray.ang.AppConfig.ANG_PACKAGE import com.v2ray.ang.AppConfig.ANG_PACKAGE
import com.v2ray.ang.BuildConfig import com.v2ray.ang.BuildConfig
import com.v2ray.ang.R import com.v2ray.ang.R
import com.v2ray.ang.extension.toast import com.v2ray.ang.extension.toast
import java.net.*
import com.v2ray.ang.service.V2RayServiceManager import com.v2ray.ang.service.V2RayServiceManager
import java.io.IOException import java.io.IOException
import java.net.*
import java.util.*
object Utils { object Utils {
@@ -38,8 +39,8 @@ object Utils {
* @param text * @param text
* @return * @return
*/ */
fun getEditable(text: String): Editable { fun getEditable(text: String?): Editable {
return Editable.Factory.getInstance().newEditable(text) return Editable.Factory.getInstance().newEditable(text?:"")
} }
/** /**
@@ -100,16 +101,16 @@ object Utils {
/** /**
* base64 decode * base64 decode
*/ */
fun decode(text: String): String { fun decode(text: String?): String {
tryDecodeBase64(text)?.let { return it } tryDecodeBase64(text)?.let { return it }
if (text.endsWith('=')) { if (text?.endsWith('=')==true) {
// try again for some loosely formatted base64 // try again for some loosely formatted base64
tryDecodeBase64(text.trimEnd('='))?.let { return it } tryDecodeBase64(text.trimEnd('='))?.let { return it }
} }
return "" return ""
} }
fun tryDecodeBase64(text: String): String? { fun tryDecodeBase64(text: String?): String? {
try { try {
return Base64.decode(text, Base64.NO_WRAP).toString(charset("UTF-8")) return Base64.decode(text, Base64.NO_WRAP).toString(charset("UTF-8"))
} catch (e: Exception) { } catch (e: Exception) {
@@ -139,18 +140,16 @@ object Utils {
* get remote dns servers from preference * get remote dns servers from preference
*/ */
fun getRemoteDnsServers(): List<String> { fun getRemoteDnsServers(): List<String> {
val remoteDns = settingsStorage?.decodeString(AppConfig.PREF_REMOTE_DNS) ?: AppConfig.DNS_AGENT val remoteDns = settingsStorage?.decodeString(AppConfig.PREF_REMOTE_DNS) ?: AppConfig.DNS_PROXY
val ret = remoteDns.split(",").filter { isPureIpAddress(it) || isCoreDNSAddress(it) } val ret = remoteDns.split(",").filter { isPureIpAddress(it) || isCoreDNSAddress(it) }
if (ret.isEmpty()) { if (ret.isEmpty()) {
return listOf(AppConfig.DNS_AGENT) return listOf(AppConfig.DNS_PROXY)
} }
return ret return ret
} }
fun getVpnDnsServers(): List<String> { fun getVpnDnsServers(): List<String> {
val vpnDns = settingsStorage?.decodeString(AppConfig.PREF_VPN_DNS) val vpnDns = settingsStorage?.decodeString(AppConfig.PREF_VPN_DNS)?:AppConfig.DNS_VPN
?: settingsStorage?.decodeString(AppConfig.PREF_REMOTE_DNS)
?: AppConfig.DNS_AGENT
return vpnDns.split(",").filter { isPureIpAddress(it) } return vpnDns.split(",").filter { isPureIpAddress(it) }
// allow empty, in that case dns will use system default // allow empty, in that case dns will use system default
} }
@@ -237,7 +236,13 @@ object Utils {
*/ */
fun isValidUrl(value: String?): Boolean { fun isValidUrl(value: String?): Boolean {
try { try {
if (value != null && Patterns.WEB_URL.matcher(value).matches() || URLUtil.isValidUrl(value)) { if (value.isNullOrEmpty()) {
return false
}
if (Patterns.WEB_URL.matcher(value).matches()
|| Patterns.DOMAIN_NAME.matcher(value).matches()
|| URLUtil.isValidUrl(value)
) {
return true return true
} }
} catch (e: Exception) { } catch (e: Exception) {
@@ -303,7 +308,11 @@ object Utils {
/** /**
* readTextFromAssets * readTextFromAssets
*/ */
fun readTextFromAssets(context: Context, fileName: String): String { fun readTextFromAssets(context: Context?, fileName: String): String {
if(context == null)
{
return ""
}
val content = context.assets.open(fileName).bufferedReader().use { val content = context.assets.open(fileName).bufferedReader().use {
it.readText() it.readText()
} }
@@ -318,6 +327,14 @@ object Utils {
return extDir.absolutePath return extDir.absolutePath
} }
fun backupPath(context: Context?): String {
if (context == null)
return ""
val extDir = context.getExternalFilesDir(AppConfig.DIR_BACKUPS)
?: return context.getDir(AppConfig.DIR_BACKUPS, 0).absolutePath
return extDir.absolutePath
}
fun getDeviceIdForXUDPBaseKey(): String { fun getDeviceIdForXUDPBaseKey(): String {
val androidId = Settings.Secure.ANDROID_ID.toByteArray(charset("UTF-8")) val androidId = Settings.Secure.ANDROID_ID.toByteArray(charset("UTF-8"))
return Base64.encodeToString(androidId.copyOf(32), Base64.NO_PADDING.or(Base64.URL_SAFE)) return Base64.encodeToString(androidId.copyOf(32), Base64.NO_PADDING.or(Base64.URL_SAFE))
@@ -345,9 +362,20 @@ object Utils {
} }
@Throws(IOException::class) @Throws(IOException::class)
fun getUrlContentWithCustomUserAgent(urlStr: String?): String { fun getUrlContentWithCustomUserAgent(urlStr: String?, timeout: Int = 30000, httpPort: Int = 0): String {
val url = URL(urlStr) val url = URL(urlStr)
val conn = url.openConnection() val conn = if (httpPort == 0) {
url.openConnection()
} else {
url.openConnection(
Proxy(
Proxy.Type.HTTP,
InetSocketAddress("127.0.0.1", httpPort)
)
)
}
conn.connectTimeout = timeout
conn.readTimeout = timeout
conn.setRequestProperty("Connection", "close") conn.setRequestProperty("Connection", "close")
conn.setRequestProperty("User-agent", "v2rayNG/${BuildConfig.VERSION_NAME}") conn.setRequestProperty("User-agent", "v2rayNG/${BuildConfig.VERSION_NAME}")
url.userInfo?.let { url.userInfo?.let {
@@ -365,7 +393,18 @@ object Utils {
return mode != UI_MODE_NIGHT_NO return mode != UI_MODE_NIGHT_NO
} }
fun getIpv6Address(address: String): String { fun setNightMode(context: Context) {
when (settingsStorage?.decodeString(AppConfig.PREF_UI_MODE_NIGHT, "0")) {
"0" -> AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM)
"1" -> AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO)
"2" -> AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES)
}
}
fun getIpv6Address(address: String?): String {
if(address == null){
return ""
}
return if (isIpv6Address(address) && !address.contains('[') && !address.contains(']')) { return if (isIpv6Address(address) && !address.contains('[') && !address.contains(']')) {
String.format("[%s]", address) String.format("[%s]", address)
} else { } else {
@@ -410,5 +449,18 @@ object Utils {
fun isTv(context: Context): Boolean = fun isTv(context: Context): Boolean =
context.packageManager.hasSystemFeature(PackageManager.FEATURE_LEANBACK) context.packageManager.hasSystemFeature(PackageManager.FEATURE_LEANBACK)
fun getDelayTestUrl(): String {
val url = settingsStorage.decodeString(AppConfig.PREF_DELAY_TEST_URL)
return if (url.isNullOrEmpty()) AppConfig.DelayTestUrl else url
}
fun getDelayTestUrl(second: Boolean = false): String {
return if (second) {
AppConfig.DelayTestUrl2
} else {
val url = settingsStorage.decodeString(AppConfig.PREF_DELAY_TEST_URL)
if (url.isNullOrEmpty()) AppConfig.DelayTestUrl else url
}
}
} }

View File

@@ -2,17 +2,19 @@ package com.v2ray.ang.util
import android.content.Context import android.content.Context
import android.text.TextUtils import android.text.TextUtils
import com.google.gson.* import android.util.Log
import com.google.gson.Gson
import com.tencent.mmkv.MMKV import com.tencent.mmkv.MMKV
import com.v2ray.ang.AppConfig import com.v2ray.ang.AppConfig
import com.v2ray.ang.AppConfig.ANG_PACKAGE
import com.v2ray.ang.AppConfig.PROTOCOL_FREEDOM import com.v2ray.ang.AppConfig.PROTOCOL_FREEDOM
import com.v2ray.ang.AppConfig.TAG_DIRECT import com.v2ray.ang.AppConfig.TAG_DIRECT
import com.v2ray.ang.AppConfig.TAG_FRAGMENT import com.v2ray.ang.AppConfig.TAG_FRAGMENT
import com.v2ray.ang.AppConfig.WIREGUARD_LOCAL_ADDRESS_V4 import com.v2ray.ang.AppConfig.WIREGUARD_LOCAL_ADDRESS_V4
import com.v2ray.ang.AppConfig.WIREGUARD_LOCAL_ADDRESS_V6 import com.v2ray.ang.AppConfig.WIREGUARD_LOCAL_ADDRESS_V6
import com.v2ray.ang.dto.V2rayConfig
import com.v2ray.ang.dto.EConfigType import com.v2ray.ang.dto.EConfigType
import com.v2ray.ang.dto.ERoutingMode import com.v2ray.ang.dto.ERoutingMode
import com.v2ray.ang.dto.V2rayConfig
import com.v2ray.ang.dto.V2rayConfig.Companion.DEFAULT_NETWORK import com.v2ray.ang.dto.V2rayConfig.Companion.DEFAULT_NETWORK
import com.v2ray.ang.dto.V2rayConfig.Companion.HTTP import com.v2ray.ang.dto.V2rayConfig.Companion.HTTP
@@ -49,6 +51,14 @@ object V2rayConfigUtil {
return Result(true, customConfig) return Result(true, customConfig)
} }
val outbound = config.getProxyOutbound() ?: return Result(false, "") val outbound = config.getProxyOutbound() ?: return Result(false, "")
val address = outbound.getServerAddress() ?: return Result(false, "")
if (!Utils.isIpAddress(address)) {
if (!Utils.isValidUrl(address)) {
Log.d(ANG_PACKAGE, "$address is an invalid ip or domain")
return Result(false, "")
}
}
val result = getV2rayNonCustomConfig(context, outbound, config.remarks) val result = getV2rayNonCustomConfig(context, outbound, config.remarks)
//Log.d(ANG_PACKAGE, result.content) //Log.d(ANG_PACKAGE, result.content)
return result return result
@@ -134,6 +144,8 @@ object V2rayConfigUtil {
settingsStorage?.decodeBool(AppConfig.PREF_SNIFFING_ENABLED, true) settingsStorage?.decodeBool(AppConfig.PREF_SNIFFING_ENABLED, true)
?: true ?: true
v2rayConfig.inbounds[0].sniffing?.enabled = fakedns || sniffAllTlsAndHttp v2rayConfig.inbounds[0].sniffing?.enabled = fakedns || sniffAllTlsAndHttp
v2rayConfig.inbounds[0].sniffing?.routeOnly =
settingsStorage?.decodeBool(AppConfig.PREF_ROUTE_ONLY_ENABLED, false)
if (!sniffAllTlsAndHttp) { if (!sniffAllTlsAndHttp) {
v2rayConfig.inbounds[0].sniffing?.destOverride?.clear() v2rayConfig.inbounds[0].sniffing?.destOverride?.clear()
} }
@@ -158,13 +170,9 @@ object V2rayConfigUtil {
private fun fakedns(v2rayConfig: V2rayConfig) { private fun fakedns(v2rayConfig: V2rayConfig) {
if (settingsStorage?.decodeBool(AppConfig.PREF_LOCAL_DNS_ENABLED) == true if (settingsStorage?.decodeBool(AppConfig.PREF_LOCAL_DNS_ENABLED) == true
|| settingsStorage?.decodeBool(AppConfig.PREF_FAKE_DNS_ENABLED) == true && settingsStorage?.decodeBool(AppConfig.PREF_FAKE_DNS_ENABLED) == true
) { ) {
v2rayConfig.fakedns = listOf(V2rayConfig.FakednsBean()) v2rayConfig.fakedns = listOf(V2rayConfig.FakednsBean())
v2rayConfig.outbounds.filter { it.protocol == PROTOCOL_FREEDOM && it.tag == TAG_DIRECT }
.forEach {
it.settings?.domainStrategy = "UseIP"
}
} }
} }
@@ -175,11 +183,11 @@ object V2rayConfigUtil {
try { try {
routingUserRule( routingUserRule(
settingsStorage?.decodeString(AppConfig.PREF_V2RAY_ROUTING_AGENT) settingsStorage?.decodeString(AppConfig.PREF_V2RAY_ROUTING_AGENT)
?: "", AppConfig.TAG_AGENT, v2rayConfig ?: "", AppConfig.TAG_PROXY, v2rayConfig
) )
routingUserRule( routingUserRule(
settingsStorage?.decodeString(AppConfig.PREF_V2RAY_ROUTING_DIRECT) settingsStorage?.decodeString(AppConfig.PREF_V2RAY_ROUTING_DIRECT)
?: "", AppConfig.TAG_DIRECT, v2rayConfig ?: "", TAG_DIRECT, v2rayConfig
) )
routingUserRule( routingUserRule(
settingsStorage?.decodeString(AppConfig.PREF_V2RAY_ROUTING_BLOCKED) settingsStorage?.decodeString(AppConfig.PREF_V2RAY_ROUTING_BLOCKED)
@@ -191,40 +199,50 @@ object V2rayConfigUtil {
?: "IPIfNonMatch" ?: "IPIfNonMatch"
// v2rayConfig.routing.domainMatcher = "mph" // v2rayConfig.routing.domainMatcher = "mph"
val routingMode = settingsStorage?.decodeString(AppConfig.PREF_ROUTING_MODE) val routingMode = settingsStorage?.decodeString(AppConfig.PREF_ROUTING_MODE)
?: ERoutingMode.GLOBAL_PROXY.value ?: ERoutingMode.BYPASS_LAN_MAINLAND.value
// Hardcode googleapis.cn // Hardcode googleapis.cn gstatic.com
val googleapisRoute = V2rayConfig.RoutingBean.RulesBean( val googleapisRoute = V2rayConfig.RoutingBean.RulesBean(
type = "field", outboundTag = AppConfig.TAG_PROXY,
outboundTag = AppConfig.TAG_AGENT, domain = arrayListOf("domain:googleapis.cn", "domain:gstatic.com")
domain = arrayListOf("domain:googleapis.cn")
) )
when (routingMode) { when (routingMode) {
ERoutingMode.BYPASS_LAN.value -> { ERoutingMode.BYPASS_LAN.value -> {
routingGeo("ip", "private", AppConfig.TAG_DIRECT, v2rayConfig) routingGeo("ip", "private", TAG_DIRECT, v2rayConfig)
} }
ERoutingMode.BYPASS_MAINLAND.value -> { ERoutingMode.BYPASS_MAINLAND.value -> {
routingGeo("", "cn", AppConfig.TAG_DIRECT, v2rayConfig) routingGeo("", "cn", TAG_DIRECT, v2rayConfig)
routingGeo("domain", "geolocation-cn", TAG_DIRECT, v2rayConfig)
v2rayConfig.routing.rules.add(0, googleapisRoute) v2rayConfig.routing.rules.add(0, googleapisRoute)
} }
ERoutingMode.BYPASS_LAN_MAINLAND.value -> { ERoutingMode.BYPASS_LAN_MAINLAND.value -> {
routingGeo("ip", "private", AppConfig.TAG_DIRECT, v2rayConfig) routingGeo("ip", "private", TAG_DIRECT, v2rayConfig)
routingGeo("", "cn", AppConfig.TAG_DIRECT, v2rayConfig) routingGeo("", "cn", TAG_DIRECT, v2rayConfig)
routingGeo("domain", "geolocation-cn", TAG_DIRECT, v2rayConfig)
v2rayConfig.routing.rules.add(0, googleapisRoute) v2rayConfig.routing.rules.add(0, googleapisRoute)
} }
ERoutingMode.GLOBAL_DIRECT.value -> { ERoutingMode.GLOBAL_DIRECT.value -> {
val globalDirect = V2rayConfig.RoutingBean.RulesBean( val globalDirect = V2rayConfig.RoutingBean.RulesBean(
type = "field", outboundTag = TAG_DIRECT,
outboundTag = AppConfig.TAG_DIRECT,
port = "0-65535" port = "0-65535"
) )
v2rayConfig.routing.rules.add(globalDirect) v2rayConfig.routing.rules.add(globalDirect)
} }
} }
if (routingMode != ERoutingMode.GLOBAL_DIRECT.value) {
v2rayConfig.routing.rules.add(
V2rayConfig.RoutingBean.RulesBean(
outboundTag = AppConfig.TAG_PROXY,
port = "0-65535"
)
)
}
} catch (e: Exception) { } catch (e: Exception) {
e.printStackTrace() e.printStackTrace()
return false return false
@@ -243,7 +261,6 @@ object V2rayConfigUtil {
//IP //IP
if (ipOrDomain == "ip" || ipOrDomain == "") { if (ipOrDomain == "ip" || ipOrDomain == "") {
val rulesIP = V2rayConfig.RoutingBean.RulesBean() val rulesIP = V2rayConfig.RoutingBean.RulesBean()
rulesIP.type = "field"
rulesIP.outboundTag = tag rulesIP.outboundTag = tag
rulesIP.ip = ArrayList() rulesIP.ip = ArrayList()
rulesIP.ip?.add("geoip:$code") rulesIP.ip?.add("geoip:$code")
@@ -253,7 +270,6 @@ object V2rayConfigUtil {
if (ipOrDomain == "domain" || ipOrDomain == "") { if (ipOrDomain == "domain" || ipOrDomain == "") {
//Domain //Domain
val rulesDomain = V2rayConfig.RoutingBean.RulesBean() val rulesDomain = V2rayConfig.RoutingBean.RulesBean()
rulesDomain.type = "field"
rulesDomain.outboundTag = tag rulesDomain.outboundTag = tag
rulesDomain.domain = ArrayList() rulesDomain.domain = ArrayList()
rulesDomain.domain?.add("geosite:$code") rulesDomain.domain?.add("geosite:$code")
@@ -270,33 +286,27 @@ object V2rayConfigUtil {
if (!TextUtils.isEmpty(userRule)) { if (!TextUtils.isEmpty(userRule)) {
//Domain //Domain
val rulesDomain = V2rayConfig.RoutingBean.RulesBean() val rulesDomain = V2rayConfig.RoutingBean.RulesBean()
rulesDomain.type = "field"
rulesDomain.outboundTag = tag rulesDomain.outboundTag = tag
rulesDomain.domain = ArrayList() rulesDomain.domain = ArrayList()
//IP //IP
val rulesIP = V2rayConfig.RoutingBean.RulesBean() val rulesIP = V2rayConfig.RoutingBean.RulesBean()
rulesIP.type = "field"
rulesIP.outboundTag = tag rulesIP.outboundTag = tag
rulesIP.ip = ArrayList() rulesIP.ip = ArrayList()
userRule.split(",").map { it.trim() }.forEach { userRule.split(",").map { it.trim() }.forEach {
if (Utils.isIpAddress(it) || it.startsWith("geoip:")) { if (it.startsWith("ext:") && it.contains("geoip")) {
rulesIP.ip?.add(it) rulesIP.ip?.add(it)
} else if (it.isNotEmpty()) } else if (Utils.isIpAddress(it) || it.startsWith("geoip:")) {
// if (Utils.isValidUrl(it) rulesIP.ip?.add(it)
// || it.startsWith("geosite:") } else if (it.isNotEmpty()) {
// || it.startsWith("regexp:")
// || it.startsWith("domain:")
// || it.startsWith("full:"))
{
rulesDomain.domain?.add(it) rulesDomain.domain?.add(it)
} }
} }
if (rulesDomain.domain?.size!! > 0) { if ((rulesDomain.domain?.size ?: 0) > 0) {
v2rayConfig.routing.rules.add(rulesDomain) v2rayConfig.routing.rules.add(rulesDomain)
} }
if (rulesIP.ip?.size!! > 0) { if ((rulesIP.ip?.size ?: 0) > 0) {
v2rayConfig.routing.rules.add(rulesIP) v2rayConfig.routing.rules.add(rulesIP)
} }
} }
@@ -344,7 +354,7 @@ object V2rayConfigUtil {
val remoteDns = Utils.getRemoteDnsServers() val remoteDns = Utils.getRemoteDnsServers()
if (v2rayConfig.inbounds.none { e -> e.protocol == "dokodemo-door" && e.tag == "dns-in" }) { if (v2rayConfig.inbounds.none { e -> e.protocol == "dokodemo-door" && e.tag == "dns-in" }) {
val dnsInboundSettings = V2rayConfig.InboundBean.InSettingsBean( val dnsInboundSettings = V2rayConfig.InboundBean.InSettingsBean(
address = if (Utils.isPureIpAddress(remoteDns.first())) remoteDns.first() else "1.1.1.1", address = if (Utils.isPureIpAddress(remoteDns.first())) remoteDns.first() else AppConfig.DNS_PROXY,
port = 53, port = 53,
network = "tcp,udp" network = "tcp,udp"
) )
@@ -381,7 +391,6 @@ object V2rayConfigUtil {
// DNS routing tag // DNS routing tag
v2rayConfig.routing.rules.add( v2rayConfig.routing.rules.add(
0, V2rayConfig.RoutingBean.RulesBean( 0, V2rayConfig.RoutingBean.RulesBean(
type = "field",
inboundTag = arrayListOf("dns-in"), inboundTag = arrayListOf("dns-in"),
outboundTag = "dns-out", outboundTag = "dns-out",
domain = null domain = null
@@ -398,12 +407,13 @@ object V2rayConfigUtil {
try { try {
val hosts = mutableMapOf<String, String>() val hosts = mutableMapOf<String, String>()
val servers = ArrayList<Any>() val servers = ArrayList<Any>()
//remote Dns
val remoteDns = Utils.getRemoteDnsServers() val remoteDns = Utils.getRemoteDnsServers()
val proxyDomain = userRule2Domian( val proxyDomain = userRule2Domian(
settingsStorage?.decodeString(AppConfig.PREF_V2RAY_ROUTING_AGENT) settingsStorage?.decodeString(AppConfig.PREF_V2RAY_ROUTING_AGENT)
?: "" ?: ""
) )
remoteDns.forEach { remoteDns.forEach {
servers.add(it) servers.add(it)
} }
@@ -419,49 +429,51 @@ object V2rayConfigUtil {
} }
// domestic DNS // domestic DNS
val domesticDns = Utils.getDomesticDnsServers()
val directDomain = userRule2Domian( val directDomain = userRule2Domian(
settingsStorage?.decodeString(AppConfig.PREF_V2RAY_ROUTING_DIRECT) settingsStorage?.decodeString(AppConfig.PREF_V2RAY_ROUTING_DIRECT)
?: "" ?: ""
) )
val routingMode = settingsStorage?.decodeString(AppConfig.PREF_ROUTING_MODE) val routingMode = settingsStorage?.decodeString(AppConfig.PREF_ROUTING_MODE)
?: ERoutingMode.GLOBAL_PROXY.value ?: ERoutingMode.BYPASS_LAN_MAINLAND.value
if (directDomain.size > 0 || routingMode == ERoutingMode.BYPASS_MAINLAND.value || routingMode == ERoutingMode.BYPASS_LAN_MAINLAND.value) { val isCnRoutingMode =
val domesticDns = Utils.getDomesticDnsServers() (routingMode == ERoutingMode.BYPASS_MAINLAND.value || routingMode == ERoutingMode.BYPASS_LAN_MAINLAND.value)
val geositeCn = arrayListOf("geosite:cn") val geoipCn = arrayListOf("geoip:cn")
val geoipCn = arrayListOf("geoip:cn")
if (directDomain.size > 0) { if (directDomain.size > 0) {
servers.add( servers.add(
V2rayConfig.DnsBean.ServersBean( V2rayConfig.DnsBean.ServersBean(
domesticDns.first(), domesticDns.first(),
53, 53,
directDomain, directDomain,
geoipCn if (isCnRoutingMode) geoipCn else null
)
) )
} )
if (routingMode == ERoutingMode.BYPASS_MAINLAND.value || routingMode == ERoutingMode.BYPASS_LAN_MAINLAND.value) { }
servers.add( if (isCnRoutingMode) {
V2rayConfig.DnsBean.ServersBean( val geositeCn = arrayListOf("geosite:cn", "geosite:geolocation-cn")
domesticDns.first(), servers.add(
53, V2rayConfig.DnsBean.ServersBean(
geositeCn, domesticDns.first(),
geoipCn 53,
) geositeCn,
geoipCn
) )
} )
if (Utils.isPureIpAddress(domesticDns.first())) {
v2rayConfig.routing.rules.add(
0, V2rayConfig.RoutingBean.RulesBean(
type = "field",
outboundTag = AppConfig.TAG_DIRECT,
port = "53",
ip = arrayListOf(domesticDns.first()),
domain = null
)
)
}
} }
if (Utils.isPureIpAddress(domesticDns.first())) {
v2rayConfig.routing.rules.add(
0, V2rayConfig.RoutingBean.RulesBean(
outboundTag = TAG_DIRECT,
port = "53",
ip = arrayListOf(domesticDns.first()),
domain = null
)
)
}
//block dns
val blkDomain = userRule2Domian( val blkDomain = userRule2Domian(
settingsStorage?.decodeString(AppConfig.PREF_V2RAY_ROUTING_BLOCKED) settingsStorage?.decodeString(AppConfig.PREF_V2RAY_ROUTING_BLOCKED)
?: "" ?: ""
@@ -483,8 +495,7 @@ object V2rayConfigUtil {
if (Utils.isPureIpAddress(remoteDns.first())) { if (Utils.isPureIpAddress(remoteDns.first())) {
v2rayConfig.routing.rules.add( v2rayConfig.routing.rules.add(
0, V2rayConfig.RoutingBean.RulesBean( 0, V2rayConfig.RoutingBean.RulesBean(
type = "field", outboundTag = AppConfig.TAG_PROXY,
outboundTag = AppConfig.TAG_AGENT,
port = "53", port = "53",
ip = arrayListOf(remoteDns.first()), ip = arrayListOf(remoteDns.first()),
domain = null domain = null
@@ -557,7 +568,7 @@ object V2rayConfigUtil {
} else { } else {
path path
} }
outbound.streamSettings?.tcpSettings?.header?.request?.headers?.Host = host!! outbound.streamSettings?.tcpSettings?.header?.request?.headers?.Host = host
} }
@@ -586,7 +597,8 @@ object V2rayConfigUtil {
mux = null mux = null
) )
var packets = settingsStorage?.decodeString(AppConfig.PREF_FRAGMENT_PACKETS) ?: "tlshello" var packets =
settingsStorage?.decodeString(AppConfig.PREF_FRAGMENT_PACKETS) ?: "tlshello"
if (v2rayConfig.outbounds[0].streamSettings?.security == V2rayConfig.REALITY if (v2rayConfig.outbounds[0].streamSettings?.security == V2rayConfig.REALITY
&& packets == "tlshello" && packets == "tlshello"
) { ) {

View File

@@ -0,0 +1,102 @@
package com.v2ray.ang.util
import java.io.BufferedOutputStream
import java.io.File
import java.io.FileInputStream
import java.io.FileOutputStream
import java.io.IOException
import java.io.InputStream
import java.util.zip.ZipEntry
import java.util.zip.ZipFile
import java.util.zip.ZipOutputStream
object ZipUtil {
private const val BUFFER_SIZE = 4096
@Throws(IOException::class)
fun zipFromFolder(folderPath: String, outputZipFilePath: String): Boolean {
val buffer = ByteArray(BUFFER_SIZE)
try {
if (folderPath.isEmpty() || outputZipFilePath.isEmpty()) {
return false
}
val filesToCompress = ArrayList<String>()
val directory = File(folderPath)
if (directory.isDirectory) {
directory.listFiles()?.forEach {
if (it.isFile) {
filesToCompress.add(it.absolutePath)
}
}
}
if (filesToCompress.isEmpty()) {
return false
}
val zos = ZipOutputStream(FileOutputStream(outputZipFilePath))
filesToCompress.forEach { file ->
val ze = ZipEntry(File(file).name)
zos.putNextEntry(ze)
val inputStream = FileInputStream(file)
while (true) {
val len = inputStream.read(buffer)
if (len <= 0) break
zos.write(buffer, 0, len)
}
inputStream.close()
}
zos.closeEntry()
zos.close()
} catch (e: Exception) {
e.printStackTrace()
return false
}
return true
}
@Throws(IOException::class)
fun unzipToFolder(zipFile: File, destDirectory: String): Boolean {
File(destDirectory).run {
if (!exists()) {
mkdirs()
}
}
try {
ZipFile(zipFile).use { zip ->
zip.entries().asSequence().forEach { entry ->
zip.getInputStream(entry).use { input ->
val filePath = destDirectory + File.separator + entry.name
if (!entry.isDirectory) {
// if the entry is a file, extracts it
extractFile(input, filePath)
} else {
// if the entry is a directory, make the directory
val dir = File(filePath)
dir.mkdir()
}
}
}
}
} catch (e: Exception) {
e.printStackTrace()
return false
}
return true
}
@Throws(IOException::class)
private fun extractFile(inputStream: InputStream, destFilePath: String) {
val bos = BufferedOutputStream(FileOutputStream(destFilePath))
val bytesIn = ByteArray(BUFFER_SIZE)
var read: Int
while (inputStream.read(bytesIn).also { read = it } != -1) {
bos.write(bytesIn, 0, read)
}
bos.close()
}
}

View File

@@ -0,0 +1,159 @@
package com.v2ray.ang.util.fmt
import android.util.Log
import com.v2ray.ang.AppConfig
import com.v2ray.ang.dto.EConfigType
import com.v2ray.ang.dto.ServerConfig
import com.v2ray.ang.extension.idnHost
import com.v2ray.ang.util.Utils
import java.net.URI
object ShadowsocksFmt {
fun parseShadowsocks(str: String): ServerConfig? {
val config = ServerConfig.create(EConfigType.SHADOWSOCKS)
if (!tryResolveResolveSip002(str, config)) {
var result = str.replace(EConfigType.SHADOWSOCKS.protocolScheme, "")
val indexSplit = result.indexOf("#")
if (indexSplit > 0) {
try {
config.remarks =
Utils.urlDecode(result.substring(indexSplit + 1, result.length))
} catch (e: Exception) {
e.printStackTrace()
}
result = result.substring(0, indexSplit)
}
//part decode
val indexS = result.indexOf("@")
result = if (indexS > 0) {
Utils.decode(result.substring(0, indexS)) + result.substring(
indexS,
result.length
)
} else {
Utils.decode(result)
}
val legacyPattern = "^(.+?):(.*)@(.+?):(\\d+?)/?$".toRegex()
val match = legacyPattern.matchEntire(result)
?: return null
config.outboundBean?.settings?.servers?.get(0)?.let { server ->
server.address = match.groupValues[3].removeSurrounding("[", "]")
server.port = match.groupValues[4].toInt()
server.password = match.groupValues[2]
server.method = match.groupValues[1].lowercase()
}
}
return config
}
fun toUri(config: ServerConfig): String {
val outbound = config.getProxyOutbound() ?: return ""
val remark = "#" + Utils.urlEncode(config.remarks)
val pw =
Utils.encode("${outbound.getSecurityEncryption()}:${outbound.getPassword()}")
val url = String.format(
"%s@%s:%s",
pw,
Utils.getIpv6Address(outbound.getServerAddress()),
outbound.getServerPort()
)
return url + remark
}
private fun tryResolveResolveSip002(str: String, config: ServerConfig): Boolean {
try {
val uri = URI(Utils.fixIllegalUrl(str))
config.remarks = Utils.urlDecode(uri.fragment ?: "")
val method: String
val password: String
if (uri.userInfo.contains(":")) {
val arrUserInfo = uri.userInfo.split(":").map { it.trim() }
if (arrUserInfo.count() != 2) {
return false
}
method = arrUserInfo[0]
password = Utils.urlDecode(arrUserInfo[1])
} else {
val base64Decode = Utils.decode(uri.userInfo)
val arrUserInfo = base64Decode.split(":").map { it.trim() }
if (arrUserInfo.count() < 2) {
return false
}
method = arrUserInfo[0]
password = base64Decode.substringAfter(":")
}
val query = Utils.urlDecode(uri.query ?: "")
if (query != "") {
val queryPairs = HashMap<String, String>()
val pairs = query.split(";")
Log.d(AppConfig.ANG_PACKAGE, pairs.toString())
for (pair in pairs) {
val idx = pair.indexOf("=")
if (idx == -1) {
queryPairs[Utils.urlDecode(pair)] = ""
} else {
queryPairs[Utils.urlDecode(pair.substring(0, idx))] =
Utils.urlDecode(pair.substring(idx + 1))
}
}
Log.d(AppConfig.ANG_PACKAGE, queryPairs.toString())
var sni: String? = ""
if (queryPairs["plugin"] == "obfs-local" && queryPairs["obfs"] == "http") {
sni = config.outboundBean?.streamSettings?.populateTransportSettings(
"tcp",
"http",
queryPairs["obfs-host"],
queryPairs["path"],
null,
null,
null,
null,
null,
null
)
} else if (queryPairs["plugin"] == "v2ray-plugin") {
var network = "ws"
if (queryPairs["mode"] == "quic") {
network = "quic"
}
sni = config.outboundBean?.streamSettings?.populateTransportSettings(
network,
null,
queryPairs["host"],
queryPairs["path"],
null,
null,
null,
null,
null,
null
)
}
if ("tls" in queryPairs) {
config.outboundBean?.streamSettings?.populateTlsSettings(
"tls", false, sni ?: "", null, null, null, null, null
)
}
}
config.outboundBean?.settings?.servers?.get(0)?.let { server ->
server.address = uri.idnHost
server.port = uri.port
server.password = password
server.method = method
}
return true
} catch (e: Exception) {
Log.d(AppConfig.ANG_PACKAGE, e.toString())
return false
}
}
}

View File

@@ -0,0 +1,69 @@
package com.v2ray.ang.util.fmt
import com.v2ray.ang.dto.EConfigType
import com.v2ray.ang.dto.ServerConfig
import com.v2ray.ang.dto.V2rayConfig
import com.v2ray.ang.util.Utils
object SocksFmt {
fun parseSocks(str: String): ServerConfig? {
val config = ServerConfig.create(EConfigType.SOCKS)
var result = str.replace(EConfigType.SOCKS.protocolScheme, "")
val indexSplit = result.indexOf("#")
if (indexSplit > 0) {
try {
config.remarks =
Utils.urlDecode(result.substring(indexSplit + 1, result.length))
} catch (e: Exception) {
e.printStackTrace()
}
result = result.substring(0, indexSplit)
}
//part decode
val indexS = result.indexOf("@")
if (indexS > 0) {
result = Utils.decode(result.substring(0, indexS)) + result.substring(
indexS,
result.length
)
} else {
result = Utils.decode(result)
}
val legacyPattern = "^(.*):(.*)@(.+?):(\\d+?)$".toRegex()
val match =
legacyPattern.matchEntire(result) ?: return null
config.outboundBean?.settings?.servers?.get(0)?.let { server ->
server.address = match.groupValues[3].removeSurrounding("[", "]")
server.port = match.groupValues[4].toInt()
val socksUsersBean =
V2rayConfig.OutboundBean.OutSettingsBean.ServersBean.SocksUsersBean()
socksUsersBean.user = match.groupValues[1]
socksUsersBean.pass = match.groupValues[2]
server.users = listOf(socksUsersBean)
}
return config
}
fun toUri(config: ServerConfig): String {
val outbound = config.getProxyOutbound() ?: return ""
val remark = "#" + Utils.urlEncode(config.remarks)
val pw =
if (outbound.settings?.servers?.get(0)?.users?.get(0)?.user != null)
"${outbound.settings?.servers?.get(0)?.users?.get(0)?.user}:${outbound.getPassword()}"
else
":"
val url = String.format(
"%s@%s:%s",
Utils.encode(pw),
Utils.getIpv6Address(outbound.getServerAddress()),
outbound.getServerPort()
)
return url + remark
}
}

View File

@@ -0,0 +1,180 @@
package com.v2ray.ang.util.fmt
import android.text.TextUtils
import com.tencent.mmkv.MMKV
import com.v2ray.ang.AppConfig
import com.v2ray.ang.dto.EConfigType
import com.v2ray.ang.dto.ServerConfig
import com.v2ray.ang.dto.V2rayConfig
import com.v2ray.ang.extension.idnHost
import com.v2ray.ang.util.MmkvManager
import com.v2ray.ang.util.Utils
import java.net.URI
object TrojanFmt {
private val settingsStorage by lazy {
MMKV.mmkvWithID(
MmkvManager.ID_SETTING,
MMKV.MULTI_PROCESS_MODE
)
}
fun parseTrojan(str: String): ServerConfig? {
var allowInsecure = settingsStorage?.decodeBool(AppConfig.PREF_ALLOW_INSECURE) ?: false
val config = ServerConfig.create(EConfigType.TROJAN)
val uri = URI(Utils.fixIllegalUrl(str))
config.remarks = Utils.urlDecode(uri.fragment ?: "")
var flow = ""
var fingerprint = config.outboundBean?.streamSettings?.tlsSettings?.fingerprint
if (uri.rawQuery.isNullOrEmpty()) {
config.outboundBean?.streamSettings?.populateTlsSettings(
V2rayConfig.TLS,
allowInsecure,
"",
fingerprint,
null,
null,
null,
null
)
} else {
val queryParam = uri.rawQuery.split("&")
.associate { it.split("=").let { (k, v) -> k to Utils.urlDecode(v) } }
val sni = config.outboundBean?.streamSettings?.populateTransportSettings(
queryParam["type"] ?: "tcp",
queryParam["headerType"],
queryParam["host"],
queryParam["path"],
queryParam["seed"],
queryParam["quicSecurity"],
queryParam["key"],
queryParam["mode"],
queryParam["serviceName"],
queryParam["authority"]
)
fingerprint = queryParam["fp"] ?: ""
allowInsecure = if ((queryParam["allowInsecure"] ?: "") == "1") true else allowInsecure
config.outboundBean?.streamSettings?.populateTlsSettings(
queryParam["security"] ?: V2rayConfig.TLS,
allowInsecure,
queryParam["sni"] ?: sni ?: "",
fingerprint,
queryParam["alpn"],
null,
null,
null
)
flow = queryParam["flow"] ?: ""
}
config.outboundBean?.settings?.servers?.get(0)?.let { server ->
server.address = uri.idnHost
server.port = uri.port
server.password = uri.userInfo
server.flow = flow
}
return config
}
fun toUri(config: ServerConfig): String {
val outbound = config.getProxyOutbound() ?: return ""
val streamSetting = outbound.streamSettings ?: V2rayConfig.OutboundBean.StreamSettingsBean()
val remark = "#" + Utils.urlEncode(config.remarks)
val dicQuery = HashMap<String, String>()
config.outboundBean?.settings?.servers?.get(0)?.flow?.let {
if (!TextUtils.isEmpty(it)) {
dicQuery["flow"] = it
}
}
dicQuery["security"] = streamSetting.security.ifEmpty { "none" }
(streamSetting.tlsSettings
?: streamSetting.realitySettings)?.let { tlsSetting ->
if (!TextUtils.isEmpty(tlsSetting.serverName)) {
dicQuery["sni"] = tlsSetting.serverName
}
if (!tlsSetting.alpn.isNullOrEmpty() && tlsSetting.alpn.isNotEmpty()) {
dicQuery["alpn"] =
Utils.removeWhiteSpace(tlsSetting.alpn.joinToString()).orEmpty()
}
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 }
outbound.getTransportSettingDetails()?.let { transportDetails ->
when (streamSetting.network) {
"tcp" -> {
dicQuery["headerType"] = transportDetails[0].ifEmpty { "none" }
if (!TextUtils.isEmpty(transportDetails[1])) {
dicQuery["host"] = Utils.urlEncode(transportDetails[1])
}
}
"kcp" -> {
dicQuery["headerType"] = transportDetails[0].ifEmpty { "none" }
if (!TextUtils.isEmpty(transportDetails[2])) {
dicQuery["seed"] = Utils.urlEncode(transportDetails[2])
}
}
"ws", "httpupgrade", "splithttp" -> {
if (!TextUtils.isEmpty(transportDetails[1])) {
dicQuery["host"] = Utils.urlEncode(transportDetails[1])
}
if (!TextUtils.isEmpty(transportDetails[2])) {
dicQuery["path"] = Utils.urlEncode(transportDetails[2])
}
}
"http", "h2" -> {
dicQuery["type"] = "http"
if (!TextUtils.isEmpty(transportDetails[1])) {
dicQuery["host"] = Utils.urlEncode(transportDetails[1])
}
if (!TextUtils.isEmpty(transportDetails[2])) {
dicQuery["path"] = Utils.urlEncode(transportDetails[2])
}
}
"quic" -> {
dicQuery["headerType"] = transportDetails[0].ifEmpty { "none" }
dicQuery["quicSecurity"] = Utils.urlEncode(transportDetails[1])
dicQuery["key"] = Utils.urlEncode(transportDetails[2])
}
"grpc" -> {
dicQuery["mode"] = transportDetails[0]
dicQuery["authority"] = Utils.urlEncode(transportDetails[1])
dicQuery["serviceName"] = Utils.urlEncode(transportDetails[2])
}
}
}
val query = "?" + dicQuery.toList().joinToString(
separator = "&",
transform = { it.first + "=" + it.second })
val url = String.format(
"%s@%s:%s",
outbound.getPassword(),
Utils.getIpv6Address(outbound.getServerAddress()),
outbound.getServerPort()
)
return url + query + remark
}
}

View File

@@ -0,0 +1,171 @@
package com.v2ray.ang.util.fmt
import android.text.TextUtils
import com.tencent.mmkv.MMKV
import com.v2ray.ang.AppConfig
import com.v2ray.ang.dto.EConfigType
import com.v2ray.ang.dto.ServerConfig
import com.v2ray.ang.dto.V2rayConfig
import com.v2ray.ang.extension.idnHost
import com.v2ray.ang.util.MmkvManager
import com.v2ray.ang.util.Utils
import java.net.URI
object VlessFmt {
private val settingsStorage by lazy {
MMKV.mmkvWithID(
MmkvManager.ID_SETTING,
MMKV.MULTI_PROCESS_MODE
)
}
fun parseVless(str: String): ServerConfig? {
var allowInsecure = settingsStorage?.decodeBool(AppConfig.PREF_ALLOW_INSECURE) ?: false
val config = ServerConfig.create(EConfigType.VLESS)
val uri = URI(Utils.fixIllegalUrl(str))
if (uri.rawQuery.isNullOrEmpty()) return null
val queryParam = uri.rawQuery.split("&")
.associate { it.split("=").let { (k, v) -> k to Utils.urlDecode(v) } }
val streamSetting = config.outboundBean?.streamSettings ?: return null
config.remarks = Utils.urlDecode(uri.fragment ?: "")
config.outboundBean.settings?.vnext?.get(0)?.let { vnext ->
vnext.address = uri.idnHost
vnext.port = uri.port
vnext.users[0].id = uri.userInfo
vnext.users[0].encryption = queryParam["encryption"] ?: "none"
vnext.users[0].flow = queryParam["flow"] ?: ""
}
val sni = streamSetting.populateTransportSettings(
queryParam["type"] ?: "tcp",
queryParam["headerType"],
queryParam["host"],
queryParam["path"],
queryParam["seed"],
queryParam["quicSecurity"],
queryParam["key"],
queryParam["mode"],
queryParam["serviceName"],
queryParam["authority"]
)
allowInsecure = if ((queryParam["allowInsecure"] ?: "") == "1") true else allowInsecure
streamSetting.populateTlsSettings(
queryParam["security"] ?: "",
allowInsecure,
queryParam["sni"] ?: sni,
queryParam["fp"] ?: "",
queryParam["alpn"],
queryParam["pbk"] ?: "",
queryParam["sid"] ?: "",
queryParam["spx"] ?: ""
)
return config
}
fun toUri(config: ServerConfig): String {
val outbound = config.getProxyOutbound() ?: return ""
val streamSetting = outbound.streamSettings ?: V2rayConfig.OutboundBean.StreamSettingsBean()
val remark = "#" + Utils.urlEncode(config.remarks)
val dicQuery = HashMap<String, String>()
outbound.settings?.vnext?.get(0)?.users?.get(0)?.flow?.let {
if (!TextUtils.isEmpty(it)) {
dicQuery["flow"] = it
}
}
dicQuery["encryption"] =
if (outbound.getSecurityEncryption().isNullOrEmpty()) "none"
else outbound.getSecurityEncryption().orEmpty()
dicQuery["security"] = streamSetting.security.ifEmpty { "none" }
(streamSetting.tlsSettings
?: streamSetting.realitySettings)?.let { tlsSetting ->
if (!TextUtils.isEmpty(tlsSetting.serverName)) {
dicQuery["sni"] = tlsSetting.serverName
}
if (!tlsSetting.alpn.isNullOrEmpty() && tlsSetting.alpn.isNotEmpty()) {
dicQuery["alpn"] =
Utils.removeWhiteSpace(tlsSetting.alpn.joinToString()).orEmpty()
}
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 }
outbound.getTransportSettingDetails()?.let { transportDetails ->
when (streamSetting.network) {
"tcp" -> {
dicQuery["headerType"] = transportDetails[0].ifEmpty { "none" }
if (!TextUtils.isEmpty(transportDetails[1])) {
dicQuery["host"] = Utils.urlEncode(transportDetails[1])
}
}
"kcp" -> {
dicQuery["headerType"] = transportDetails[0].ifEmpty { "none" }
if (!TextUtils.isEmpty(transportDetails[2])) {
dicQuery["seed"] = Utils.urlEncode(transportDetails[2])
}
}
"ws", "httpupgrade", "splithttp" -> {
if (!TextUtils.isEmpty(transportDetails[1])) {
dicQuery["host"] = Utils.urlEncode(transportDetails[1])
}
if (!TextUtils.isEmpty(transportDetails[2])) {
dicQuery["path"] = Utils.urlEncode(transportDetails[2])
}
}
"http", "h2" -> {
dicQuery["type"] = "http"
if (!TextUtils.isEmpty(transportDetails[1])) {
dicQuery["host"] = Utils.urlEncode(transportDetails[1])
}
if (!TextUtils.isEmpty(transportDetails[2])) {
dicQuery["path"] = Utils.urlEncode(transportDetails[2])
}
}
"quic" -> {
dicQuery["headerType"] = transportDetails[0].ifEmpty { "none" }
dicQuery["quicSecurity"] = Utils.urlEncode(transportDetails[1])
dicQuery["key"] = Utils.urlEncode(transportDetails[2])
}
"grpc" -> {
dicQuery["mode"] = transportDetails[0]
dicQuery["authority"] = Utils.urlEncode(transportDetails[1])
dicQuery["serviceName"] = Utils.urlEncode(transportDetails[2])
}
}
}
val query = "?" + dicQuery.toList().joinToString(
separator = "&",
transform = { it.first + "=" + it.second })
val url = String.format(
"%s@%s:%s",
outbound.getPassword(),
Utils.getIpv6Address(outbound.getServerAddress()),
outbound.getServerPort()
)
return url + query + remark
}
}

View File

@@ -0,0 +1,160 @@
package com.v2ray.ang.util.fmt
import android.text.TextUtils
import android.util.Log
import com.google.gson.Gson
import com.tencent.mmkv.MMKV
import com.v2ray.ang.AppConfig
import com.v2ray.ang.dto.EConfigType
import com.v2ray.ang.dto.ServerConfig
import com.v2ray.ang.dto.V2rayConfig
import com.v2ray.ang.dto.VmessQRCode
import com.v2ray.ang.extension.idnHost
import com.v2ray.ang.util.MmkvManager
import com.v2ray.ang.util.Utils
import java.net.URI
object VmessFmt {
private val settingsStorage by lazy {
MMKV.mmkvWithID(
MmkvManager.ID_SETTING, MMKV.MULTI_PROCESS_MODE
)
}
fun parseVmess(str: String): ServerConfig? {
if (str.indexOf('?') > 0 && str.indexOf('&') > 0) {
return parseVmessStd(str)
}
val allowInsecure = settingsStorage?.decodeBool(AppConfig.PREF_ALLOW_INSECURE) ?: false
val config = ServerConfig.create(EConfigType.VMESS)
val streamSetting = config.outboundBean?.streamSettings ?: return null
var result = str.replace(EConfigType.VMESS.protocolScheme, "")
result = Utils.decode(result)
if (TextUtils.isEmpty(result)) {
Log.d(AppConfig.ANG_PACKAGE, "R.string.toast_decoding_failed")
return null
}
val vmessQRCode = Gson().fromJson(result, VmessQRCode::class.java)
// Although VmessQRCode fields are non null, looks like Gson may still create null fields
if (TextUtils.isEmpty(vmessQRCode.add)
|| TextUtils.isEmpty(vmessQRCode.port)
|| TextUtils.isEmpty(vmessQRCode.id)
|| TextUtils.isEmpty(vmessQRCode.net)
) {
Log.d(AppConfig.ANG_PACKAGE, "R.string.toast_incorrect_protocol")
return null
}
config.remarks = vmessQRCode.ps
config.outboundBean.settings?.vnext?.get(0)?.let { vnext ->
vnext.address = vmessQRCode.add
vnext.port = Utils.parseInt(vmessQRCode.port)
vnext.users[0].id = vmessQRCode.id
vnext.users[0].security =
if (TextUtils.isEmpty(vmessQRCode.scy)) V2rayConfig.DEFAULT_SECURITY else vmessQRCode.scy
vnext.users[0].alterId = Utils.parseInt(vmessQRCode.aid)
}
val sni = streamSetting.populateTransportSettings(
vmessQRCode.net,
vmessQRCode.type,
vmessQRCode.host,
vmessQRCode.path,
vmessQRCode.path,
vmessQRCode.host,
vmessQRCode.path,
vmessQRCode.type,
vmessQRCode.path,
vmessQRCode.host
)
val fingerprint = vmessQRCode.fp
streamSetting.populateTlsSettings(
vmessQRCode.tls,
allowInsecure,
if (TextUtils.isEmpty(vmessQRCode.sni)) sni else vmessQRCode.sni,
fingerprint,
vmessQRCode.alpn,
null,
null,
null
)
return config
}
fun toUri(config: ServerConfig): String {
val outbound = config.getProxyOutbound() ?: return ""
val streamSetting = outbound.streamSettings ?: V2rayConfig.OutboundBean.StreamSettingsBean()
val vmessQRCode = VmessQRCode()
vmessQRCode.v = "2"
vmessQRCode.ps = config.remarks
vmessQRCode.add = outbound.getServerAddress().orEmpty()
vmessQRCode.port = outbound.getServerPort().toString()
vmessQRCode.id = outbound.getPassword().orEmpty()
vmessQRCode.aid = outbound.settings?.vnext?.get(0)?.users?.get(0)?.alterId.toString()
vmessQRCode.scy = outbound.settings?.vnext?.get(0)?.users?.get(0)?.security.toString()
vmessQRCode.net = streamSetting.network
vmessQRCode.tls = streamSetting.security
vmessQRCode.sni = streamSetting.tlsSettings?.serverName.orEmpty()
vmessQRCode.alpn =
Utils.removeWhiteSpace(streamSetting.tlsSettings?.alpn?.joinToString()).orEmpty()
vmessQRCode.fp = streamSetting.tlsSettings?.fingerprint.orEmpty()
outbound.getTransportSettingDetails()?.let { transportDetails ->
vmessQRCode.type = transportDetails[0]
vmessQRCode.host = transportDetails[1]
vmessQRCode.path = transportDetails[2]
}
val json = Gson().toJson(vmessQRCode)
return Utils.encode(json)
}
fun parseVmessStd(str: String): ServerConfig? {
var allowInsecure = settingsStorage?.decodeBool(AppConfig.PREF_ALLOW_INSECURE) ?: false
val config = ServerConfig.create(EConfigType.VMESS)
val uri = URI(Utils.fixIllegalUrl(str))
if (uri.rawQuery.isNullOrEmpty()) return null
val queryParam = uri.rawQuery.split("&")
.associate { it.split("=").let { (k, v) -> k to Utils.urlDecode(v) } }
val streamSetting = config.outboundBean?.streamSettings ?: return null
config.remarks = Utils.urlDecode(uri.fragment ?: "")
config.outboundBean.settings?.vnext?.get(0)?.let { vnext ->
vnext.address = uri.idnHost
vnext.port = uri.port
vnext.users[0].id = uri.userInfo
vnext.users[0].security = V2rayConfig.DEFAULT_SECURITY
vnext.users[0].alterId = 0
}
val sni = streamSetting.populateTransportSettings(
queryParam["type"] ?: "tcp",
queryParam["headerType"],
queryParam["host"],
queryParam["path"],
queryParam["seed"],
queryParam["quicSecurity"],
queryParam["key"],
queryParam["mode"],
queryParam["serviceName"],
queryParam["authority"]
)
allowInsecure = if ((queryParam["allowInsecure"] ?: "") == "1") true else allowInsecure
streamSetting.populateTlsSettings(
queryParam["security"] ?: "",
allowInsecure,
queryParam["sni"] ?: sni,
queryParam["fp"] ?: "",
queryParam["alpn"],
null,
null,
null
)
return config
}
}

View File

@@ -0,0 +1,73 @@
package com.v2ray.ang.util.fmt
import com.v2ray.ang.AppConfig
import com.v2ray.ang.dto.EConfigType
import com.v2ray.ang.dto.ServerConfig
import com.v2ray.ang.extension.idnHost
import com.v2ray.ang.extension.removeWhiteSpace
import com.v2ray.ang.util.Utils
import java.net.URI
object WireguardFmt {
fun parseWireguard(str: String): ServerConfig? {
val uri = URI(Utils.fixIllegalUrl(str))
if (uri.rawQuery != null) {
val config = ServerConfig.create(EConfigType.WIREGUARD)
config.remarks = Utils.urlDecode(uri.fragment ?: "")
val queryParam = uri.rawQuery.split("&")
.associate { it.split("=").let { (k, v) -> k to Utils.urlDecode(v) } }
config.outboundBean?.settings?.let { wireguard ->
wireguard.secretKey = uri.userInfo
wireguard.address =
(queryParam["address"]
?: AppConfig.WIREGUARD_LOCAL_ADDRESS_V4).removeWhiteSpace()
.split(",")
wireguard.peers?.get(0)?.publicKey = queryParam["publickey"] ?: ""
wireguard.peers?.get(0)?.endpoint =
Utils.getIpv6Address(uri.idnHost) + ":${uri.port}"
wireguard.mtu = Utils.parseInt(queryParam["mtu"] ?: AppConfig.WIREGUARD_LOCAL_MTU)
wireguard.reserved =
(queryParam["reserved"] ?: "0,0,0").removeWhiteSpace().split(",")
.map { it.toInt() }
}
return config
} else {
return null
}
}
fun toUri(config: ServerConfig): String {
val outbound = config.getProxyOutbound() ?: return ""
val remark = "#" + Utils.urlEncode(config.remarks)
val dicQuery = HashMap<String, String>()
dicQuery["publickey"] =
Utils.urlEncode(outbound.settings?.peers?.get(0)?.publicKey.toString())
if (outbound.settings?.reserved != null) {
dicQuery["reserved"] = Utils.urlEncode(
Utils.removeWhiteSpace(outbound.settings?.reserved?.joinToString())
.toString()
)
}
dicQuery["address"] = Utils.urlEncode(
Utils.removeWhiteSpace((outbound.settings?.address as List<*>).joinToString())
.toString()
)
if (outbound.settings?.mtu != null) {
dicQuery["mtu"] = outbound.settings?.mtu.toString()
}
val query = "?" + dicQuery.toList().joinToString(
separator = "&",
transform = { it.first + "=" + it.second })
val url = String.format(
"%s@%s:%s",
Utils.urlEncode(outbound.getPassword().toString()),
Utils.getIpv6Address(outbound.getServerAddress()),
outbound.getServerPort()
)
return url + query + remark
}
}

View File

@@ -1,7 +1,12 @@
package com.v2ray.ang.viewmodel package com.v2ray.ang.viewmodel
import android.app.Application import android.app.Application
import android.content.* import android.content.BroadcastReceiver
import android.content.Context
import android.content.DialogInterface
import android.content.Intent
import android.content.IntentFilter
import android.content.res.AssetManager
import android.os.Build import android.os.Build
import android.util.Log import android.util.Log
import android.view.LayoutInflater import android.view.LayoutInflater
@@ -17,12 +22,25 @@ import com.v2ray.ang.AppConfig
import com.v2ray.ang.AppConfig.ANG_PACKAGE import com.v2ray.ang.AppConfig.ANG_PACKAGE
import com.v2ray.ang.R import com.v2ray.ang.R
import com.v2ray.ang.databinding.DialogConfigFilterBinding import com.v2ray.ang.databinding.DialogConfigFilterBinding
import com.v2ray.ang.dto.* import com.v2ray.ang.dto.EConfigType
import com.v2ray.ang.dto.ServerConfig
import com.v2ray.ang.dto.ServersCache
import com.v2ray.ang.dto.V2rayConfig
import com.v2ray.ang.extension.toast import com.v2ray.ang.extension.toast
import com.v2ray.ang.util.* import com.v2ray.ang.util.MessageUtil
import com.v2ray.ang.util.MmkvManager
import com.v2ray.ang.util.MmkvManager.KEY_ANG_CONFIGS import com.v2ray.ang.util.MmkvManager.KEY_ANG_CONFIGS
import kotlinx.coroutines.* import com.v2ray.ang.util.SpeedtestUtil
import java.util.* import com.v2ray.ang.util.Utils
import com.v2ray.ang.util.V2rayConfigUtil
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.cancelChildren
import kotlinx.coroutines.launch
import java.io.File
import java.io.FileOutputStream
import java.util.Collections
class MainViewModel(application: Application) : AndroidViewModel(application) { class MainViewModel(application: Application) : AndroidViewModel(application) {
private val mainStorage by lazy { private val mainStorage by lazy {
@@ -45,8 +63,8 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
} }
var serverList = MmkvManager.decodeServerList() var serverList = MmkvManager.decodeServerList()
var subscriptionId: String = settingsStorage.decodeString(AppConfig.CACHE_SUBSCRIPTION_ID, "")!! var subscriptionId: String = settingsStorage.decodeString(AppConfig.CACHE_SUBSCRIPTION_ID, "")?:""
var keywordFilter: String = settingsStorage.decodeString(AppConfig.CACHE_KEYWORD_FILTER, "")!! var keywordFilter: String = settingsStorage.decodeString(AppConfig.CACHE_KEYWORD_FILTER, "")?:""
private set private set
val serversCache = mutableListOf<ServersCache>() val serversCache = mutableListOf<ServersCache>()
val isRunning by lazy { MutableLiveData<Boolean>() } val isRunning by lazy { MutableLiveData<Boolean>() }
@@ -95,15 +113,26 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
} }
} }
fun appendCustomConfigServer(server: String) { fun appendCustomConfigServer(server: String): Boolean {
val config = ServerConfig.create(EConfigType.CUSTOM) if (server.contains("inbounds")
config.subscriptionId = subscriptionId && server.contains("outbounds")
config.fullConfig = Gson().fromJson(server, V2rayConfig::class.java) && server.contains("routing")
config.remarks = config.fullConfig?.remarks ?: System.currentTimeMillis().toString() ) {
val key = MmkvManager.encodeServerConfig("", config) try {
serverRawStorage?.encode(key, server) val config = ServerConfig.create(EConfigType.CUSTOM)
serverList.add(0, key) config.subscriptionId = subscriptionId
serversCache.add(0, ServersCache(key, config)) config.fullConfig = Gson().fromJson(server, V2rayConfig::class.java)
config.remarks = config.fullConfig?.remarks ?: System.currentTimeMillis().toString()
val key = MmkvManager.encodeServerConfig("", config)
serverRawStorage?.encode(key, server)
serverList.add(0, key)
serversCache.add(0, ServersCache(key, config))
return true
} catch (e: Exception) {
e.printStackTrace()
}
}
return false
} }
fun swapServer(fromPosition: Int, toPosition: Int) { fun swapServer(fromPosition: Int, toPosition: Int) {
@@ -130,7 +159,7 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
fun testAllTcping() { fun testAllTcping() {
tcpingTestScope.coroutineContext[Job]?.cancelChildren() tcpingTestScope.coroutineContext[Job]?.cancelChildren()
SpeedtestUtil.closeAllTcpSockets() SpeedtestUtil.closeAllTcpSockets()
MmkvManager.clearAllTestDelayResults() MmkvManager.clearAllTestDelayResults(serversCache.map { it.guid }.toList())
updateListAction.value = -1 // update all updateListAction.value = -1 // update all
getApplication<AngApplication>().toast(R.string.connection_test_testing) getApplication<AngApplication>().toast(R.string.connection_test_testing)
@@ -153,7 +182,7 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
fun testAllRealPing() { fun testAllRealPing() {
MessageUtil.sendMsg2TestService(getApplication(), AppConfig.MSG_MEASURE_CONFIG_CANCEL, "") MessageUtil.sendMsg2TestService(getApplication(), AppConfig.MSG_MEASURE_CONFIG_CANCEL, "")
MmkvManager.clearAllTestDelayResults() MmkvManager.clearAllTestDelayResults(serversCache.map { it.guid }.toList())
updateListAction.value = -1 // update all updateListAction.value = -1 // update all
val serversCopy = serversCache.toList() // Create a copy of the list val serversCopy = serversCache.toList() // Create a copy of the list
@@ -257,7 +286,6 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
for (it in deleteServer) { for (it in deleteServer) {
MmkvManager.removeServer(it) MmkvManager.removeServer(it)
} }
reloadServerList()
getApplication<AngApplication>().toast( getApplication<AngApplication>().toast(
getApplication<AngApplication>().getString( getApplication<AngApplication>().getString(
R.string.title_del_duplicate_config_count, R.string.title_del_duplicate_config_count,
@@ -266,6 +294,32 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
) )
} }
fun copyAssets(assets: AssetManager) {
val extFolder = Utils.userAssetPath(getApplication<AngApplication>())
viewModelScope.launch(Dispatchers.Default) {
try {
val geo = arrayOf("geosite.dat", "geoip.dat")
assets.list("")
?.filter { geo.contains(it) }
?.filter { !File(extFolder, it).exists() }
?.forEach {
val target = File(extFolder, it)
assets.open(it).use { input ->
FileOutputStream(target).use { output ->
input.copyTo(output)
}
}
Log.i(
ANG_PACKAGE,
"Copied from apk assets folder to ${target.absolutePath}"
)
}
} catch (e: Exception) {
Log.e(ANG_PACKAGE, "asset copy failed", e)
}
}
}
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)) {

View File

@@ -8,33 +8,44 @@ import androidx.preference.PreferenceManager
import com.tencent.mmkv.MMKV import com.tencent.mmkv.MMKV
import com.v2ray.ang.AppConfig import com.v2ray.ang.AppConfig
import com.v2ray.ang.util.MmkvManager import com.v2ray.ang.util.MmkvManager
import com.v2ray.ang.util.Utils
class SettingsViewModel(application: Application) : AndroidViewModel(application), SharedPreferences.OnSharedPreferenceChangeListener { class SettingsViewModel(application: Application) : AndroidViewModel(application),
SharedPreferences.OnSharedPreferenceChangeListener {
private val settingsStorage by lazy { MMKV.mmkvWithID(MmkvManager.ID_SETTING, MMKV.MULTI_PROCESS_MODE) } private val settingsStorage by lazy {
MMKV.mmkvWithID(
MmkvManager.ID_SETTING,
MMKV.MULTI_PROCESS_MODE
)
}
fun startListenPreferenceChange() { fun startListenPreferenceChange() {
PreferenceManager.getDefaultSharedPreferences(getApplication()).registerOnSharedPreferenceChangeListener(this) PreferenceManager.getDefaultSharedPreferences(getApplication())
.registerOnSharedPreferenceChangeListener(this)
} }
override fun onCleared() { override fun onCleared() {
PreferenceManager.getDefaultSharedPreferences(getApplication()).unregisterOnSharedPreferenceChangeListener(this) PreferenceManager.getDefaultSharedPreferences(getApplication())
.unregisterOnSharedPreferenceChangeListener(this)
Log.i(AppConfig.ANG_PACKAGE, "Settings ViewModel is cleared") Log.i(AppConfig.ANG_PACKAGE, "Settings ViewModel is cleared")
super.onCleared() super.onCleared()
} }
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences, key: String?) { override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences, key: String?) {
Log.d(AppConfig.ANG_PACKAGE, "Observe settings changed: $key") Log.d(AppConfig.ANG_PACKAGE, "Observe settings changed: $key")
when(key) { when (key) {
AppConfig.PREF_MODE, AppConfig.PREF_MODE,
AppConfig.PREF_VPN_DNS, AppConfig.PREF_VPN_DNS,
AppConfig.PREF_REMOTE_DNS, AppConfig.PREF_REMOTE_DNS,
AppConfig.PREF_DOMESTIC_DNS, AppConfig.PREF_DOMESTIC_DNS,
AppConfig.PREF_DELAY_TEST_URL,
AppConfig.PREF_LOCAL_DNS_PORT, AppConfig.PREF_LOCAL_DNS_PORT,
AppConfig.PREF_SOCKS_PORT, AppConfig.PREF_SOCKS_PORT,
AppConfig.PREF_HTTP_PORT, AppConfig.PREF_HTTP_PORT,
AppConfig.PREF_LOGLEVEL, AppConfig.PREF_LOGLEVEL,
AppConfig.PREF_LANGUAGE, AppConfig.PREF_LANGUAGE,
AppConfig.PREF_UI_MODE_NIGHT,
AppConfig.PREF_ROUTING_DOMAIN_STRATEGY, AppConfig.PREF_ROUTING_DOMAIN_STRATEGY,
AppConfig.PREF_ROUTING_MODE, AppConfig.PREF_ROUTING_MODE,
AppConfig.PREF_V2RAY_ROUTING_AGENT, AppConfig.PREF_V2RAY_ROUTING_AGENT,
@@ -44,9 +55,12 @@ class SettingsViewModel(application: Application) : AndroidViewModel(application
AppConfig.PREF_FRAGMENT_PACKETS, AppConfig.PREF_FRAGMENT_PACKETS,
AppConfig.PREF_FRAGMENT_LENGTH, AppConfig.PREF_FRAGMENT_LENGTH,
AppConfig.PREF_FRAGMENT_INTERVAL, AppConfig.PREF_FRAGMENT_INTERVAL,
AppConfig.PREF_MUX_XUDP_QUIC, -> { AppConfig.PREF_MUX_XUDP_QUIC,
-> {
settingsStorage?.encode(key, sharedPreferences.getString(key, "")) settingsStorage?.encode(key, sharedPreferences.getString(key, ""))
} }
AppConfig.PREF_ROUTE_ONLY_ENABLED,
AppConfig.PREF_SPEED_ENABLED, AppConfig.PREF_SPEED_ENABLED,
AppConfig.PREF_PROXY_SHARING, AppConfig.PREF_PROXY_SHARING,
AppConfig.PREF_LOCAL_DNS_ENABLED, AppConfig.PREF_LOCAL_DNS_ENABLED,
@@ -59,19 +73,26 @@ class SettingsViewModel(application: Application) : AndroidViewModel(application
AppConfig.PREF_START_SCAN_IMMEDIATE, AppConfig.PREF_START_SCAN_IMMEDIATE,
AppConfig.SUBSCRIPTION_AUTO_UPDATE, AppConfig.SUBSCRIPTION_AUTO_UPDATE,
AppConfig.PREF_FRAGMENT_ENABLED, AppConfig.PREF_FRAGMENT_ENABLED,
AppConfig.PREF_MUX_ENABLED, -> { AppConfig.PREF_MUX_ENABLED,
-> {
settingsStorage?.encode(key, sharedPreferences.getBoolean(key, false)) settingsStorage?.encode(key, sharedPreferences.getBoolean(key, false))
} }
AppConfig.PREF_SNIFFING_ENABLED -> { AppConfig.PREF_SNIFFING_ENABLED -> {
settingsStorage?.encode(key, sharedPreferences.getBoolean(key, true)) settingsStorage?.encode(key, sharedPreferences.getBoolean(key, true))
} }
AppConfig.PREF_MUX_CONCURRENCY, AppConfig.PREF_MUX_CONCURRENCY,
AppConfig.PREF_MUX_XUDP_CONCURRENCY -> { AppConfig.PREF_MUX_XUDP_CONCURRENCY -> {
settingsStorage?.encode(key, sharedPreferences.getString(key, "8")?.toIntOrNull() ?: 8) settingsStorage?.encode(key, sharedPreferences.getString(key, "8"))
}
AppConfig.PREF_PER_APP_PROXY_SET -> {
settingsStorage?.encode(key, sharedPreferences.getStringSet(key, setOf()))
} }
// AppConfig.PREF_PER_APP_PROXY_SET -> {
// settingsStorage?.encode(key, sharedPreferences.getStringSet(key, setOf()))
// }
}
if (key == AppConfig.PREF_UI_MODE_NIGHT) {
Utils.setNightMode(getApplication())
} }
} }
} }

View File

@@ -1,6 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<alpha xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="@android:integer/config_shortAnimTime"
android:fromAlpha="0.0"
android:interpolator="@android:interpolator/decelerate_quad"
android:toAlpha="1.0" />

View File

@@ -1,6 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<alpha xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="@android:integer/config_shortAnimTime"
android:fromAlpha="1.0"
android:interpolator="@android:interpolator/accelerate_quad"
android:toAlpha="0.0" />

Binary file not shown.

Before

Width:  |  Height:  |  Size: 526 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 651 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 476 B

View File

@@ -0,0 +1,12 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="1024"
android:viewportHeight="1024">
<path
android:fillColor="#FFFFFFFF"
android:pathData="M810.7,206.3A426.7,426.7 0,1 0,512 938.7h5.5A426.7,426.7 0,0 0,810.7 206.3zM771.8,765A360.1,360.1 0,0 1,517.1 874.7L512,874.7a362.7,362.7 0,0 1,-4.9 -725.3h3.4a362.7,362.7 0,0 1,261.3 615.7z" />
<path
android:fillColor="#FFFFFFFF"
android:pathData="M547.2,390a57.4,57.4 0,0 0,62.3 -55.3,39.3 39.3,0 0,0 -45,-42.7 58.9,58.9 0,0 0,-64 54.6c-1.7,26.9 13.9,43.3 46.7,43.3zM548.3,663.5c-5.5,0 -7.9,-7.3 -2.3,-28.2l31.1,-118c11.7,-42.7 7.9,-71.3 -15.8,-71.3 -28.4,0 -94.7,28.4 -152.5,76.4l11.7,19.4a181.3,181.3 0,0 1,56.1 -24.7c5.5,0 4.7,7.3 0,25.2l-27.3,112.2c-16.6,64 0,77.7 24.5,77.7s85.3,-21.3 141,-77.7l-13.4,-17.9a122.7,122.7 0,0 1,-53.1 26.9z" />
</vector>

View File

@@ -1,9 +0,0 @@
<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="M11.8,10.9c-2.27,-0.59 -3,-1.2 -3,-2.15 0,-1.09 1.01,-1.85 2.7,-1.85 1.78,0 2.44,0.85 2.5,2.1h2.21c-0.07,-1.72 -1.12,-3.3 -3.21,-3.81V3h-3v2.16c-1.94,0.42 -3.5,1.68 -3.5,3.61 0,2.31 1.91,3.46 4.7,4.13 2.5,0.6 3,1.48 3,2.41 0,0.69 -0.49,1.79 -2.7,1.79 -2.06,0 -2.87,-0.92 -2.98,-2.1h-2.2c0.12,2.19 1.76,3.42 3.68,3.83V21h3v-2.15c1.95,-0.37 3.5,-1.5 3.5,-3.55 0,-2.84 -2.43,-3.81 -4.7,-4.4z"/>
</vector>

View File

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="1024"
android:viewportHeight="1024">
<path
android:fillColor="#FFFFFFFF"
android:pathData="M864,192L704,192L704,96c0,-17.7 -14.3,-32 -32,-32L352,64c-9,0 -17.2,3.7 -22.9,9.7L137.7,265.1c-6,5.8 -9.7,14 -9.7,22.9v512c0,17.7 14.3,32 32,32h160v96c0,17.7 14.3,32 32,32h512c17.7,0 32,-14.3 32,-32L896,224c0,-17.7 -14.3,-32 -32,-32zM320,173.2L320,256h-82.8l82.8,-82.8zM192,768L192,320h160c17.7,0 32,-14.3 32,-32L384,128h256v64h-96c-9,0 -17.2,3.7 -22.9,9.7L329.7,393.1c-6,5.8 -9.7,14 -9.7,22.9v352L192,768zM512,301.2L512,384h-82.8l82.8,-82.8zM832,896L384,896L384,448h160c17.7,0 32,-14.3 32,-32L576,256h256v640z" />
</vector>

View File

@@ -1,9 +1,12 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" <vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp" android:width="24dp"
android:height="24dp" android:height="24dp"
android:viewportWidth="24.0" android:viewportWidth="1024"
android:viewportHeight="24.0"> android:viewportHeight="1024">
<path <path
android:fillColor="#FFFFFFFF" android:fillColor="#FFFFFFFF"
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"/> android:pathData="M512,192.7v42.7a21.3,21.3 0,0 1,-21.3 21.3H213.5V770.6l552.9,-2.2v-233.4a21.3,21.3 0,0 1,21.3 -21.3h42.7a21.3,21.3 0,0 1,21.3 21.3v256.1c0,32.6 -24.9,59.3 -56.6,62.4l-6.1,0.3 -598.2,2.1c-32.6,0 -59.3,-24.8 -62.4,-56.6l-0.3,-6V234c0,-32.6 24.8,-59.3 56.6,-62.4l6,-0.3H490.7a21.3,21.3 0,0 1,21.3 21.3z" />
<path
android:fillColor="#FFFFFFFF"
android:pathData="M848.7,238.2l-250.8,250.8a21.3,21.3 0,0 1,-13.1 6.1c-30.8,2.9 -47.9,2.6 -51.2,-0.8 -3.4,-3.4 -4,-20.8 -2,-52.3a21.3,21.3 0,0 1,6.2 -13.7l250.5,-250.6a21.3,21.3 0,0 1,30.2 0l30.2,30.2a21.3,21.3 0,0 1,0 30.2z" />
</vector> </vector>

View File

@@ -1,34 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" <vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp" android:width="24dp"
android:height="24dp" android:height="24dp"
android:viewportWidth="24" android:viewportWidth="1024"
android:viewportHeight="24"> android:viewportHeight="1024">
<path <path
android:pathData="M11,23h8a3,3 0,0 0,3 -3V8L15,1H7A3,3 0,0 0,4 4V9" android:pathData="M537,85.3A85.3,85.3 0,0 1,597.3 110.3L828.3,341.3A85.3,85.3 0,0 1,853.3 401.7L853.3,810.7a128,128 0,0 1,-128 128L298.7,938.7a128,128 0,0 1,-128 -128L170.7,213.3a128,128 0,0 1,128 -128zM537,170.7L298.7,170.7a42.7,42.7 0,0 0,-42.7 42.7v597.3a42.7,42.7 0,0 0,42.7 42.7h426.7a42.7,42.7 0,0 0,42.7 -42.7L768,401.7L537,170.7zM512,384a42.7,42.7 0,0 1,42.4 37.7L554.7,426.7v85.3h85.3a42.7,42.7 0,0 1,5 85L640,597.3h-85.3v85.3a42.7,42.7 0,0 1,-85 5L469.3,682.7v-85.3L384,597.3a42.7,42.7 0,0 1,-5 -85L384,512h85.3v-85.3a42.7,42.7 0,0 1,42.7 -42.7z"
android:strokeLineJoin="round" android:fillColor="#FFFFFFFF"/>
android:strokeWidth="2"
android:fillColor="#00000000"
android:strokeColor="#FFF"
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="#FFF"
android:strokeLineCap="round"/>
<path
android:pathData="M2,17L10,17"
android:strokeLineJoin="round"
android:strokeWidth="2"
android:fillColor="#00000000"
android:strokeColor="#FFF"
android:strokeLineCap="round"/>
<path
android:pathData="M6,13L6,21"
android:strokeLineJoin="round"
android:strokeWidth="2"
android:fillColor="#00000000"
android:strokeColor="#FFF"
android:strokeLineCap="round"/>
</vector> </vector>

View File

@@ -0,0 +1,12 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="1024"
android:viewportHeight="1024">
<path
android:fillColor="#FFFFFFFF"
android:pathData="M784,112L240,112c-88,0 -160,72 -160,160v480c0,88 72,160 160,160h544c88,0 160,-72 160,-160L944,272c0,-88 -72,-160 -160,-160zM880,752c0,52.8 -43.2,96 -96,96L240,848c-52.8,0 -96,-43.2 -96,-96L144,272c0,-52.8 43.2,-96 96,-96h544c52.8,0 96,43.2 96,96v480z" />
<path
android:fillColor="#FFFFFFFF"
android:pathData="M352,480c52.8,0 96,-43.2 96,-96s-43.2,-96 -96,-96 -96,43.2 -96,96 43.2,96 96,96zM352,352c17.6,0 32,14.4 32,32s-14.4,32 -32,32 -32,-14.4 -32,-32 14.4,-32 32,-32zM814.4,731.2l-3.2,-3.2 -177.6,-177.6c-25.6,-25.6 -65.6,-25.6 -91.2,0l-80,80 -36.8,-36.8c-25.6,-25.6 -65.6,-25.6 -91.2,0L200,728c-4.8,6.4 -8,14.4 -8,24 0,17.6 14.4,32 32,32 9.6,0 16,-3.2 22.4,-9.6L380.8,640l134.4,134.4c6.4,6.4 14.4,9.6 24,9.6 17.6,0 32,-14.4 32,-32 0,-9.6 -4.8,-17.6 -9.6,-24l-52.8,-52.8 80,-80L769.6,776c6.4,4.8 12.8,8 20.8,8 17.6,0 32,-14.4 32,-32 0,-8 -3.2,-16 -8,-20.8z" />
</vector>

View File

@@ -1,9 +0,0 @@
<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>

View File

@@ -1,9 +0,0 @@
<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>

View File

@@ -1,9 +1,15 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" <vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp" android:width="24dp"
android:height="24dp" android:height="24dp"
android:viewportWidth="24.0" android:viewportWidth="1024"
android:viewportHeight="24.0"> android:viewportHeight="1024">
<path <path
android:fillColor="#FFFFFFFF" android:pathData="M273.2,212.8h583.7L856.9,906.2h-583.7L273.2,212.8zM344.9,284.5L344.9,834.6h440.3L785.2,284.5h-440.3z"
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"/> android:fillColor="#FFFFFFFF"/>
<path
android:pathData="M167.1,117.8h554.4v123.6h-71.7V189.4H238.8v498.8h73.9v71.7H167.1V117.8z"
android:fillColor="#FFFFFFFF"/>
<path
android:pathData="M674.8,504H455.3v-71.7h219.4v71.7zM674.8,650.2H455.3v-71.7h219.4v71.7z"
android:fillColor="#FFFFFFFF"/>
</vector> </vector>

View File

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="1024"
android:viewportHeight="1024">
<path
android:fillColor="#FFFFFFFF"
android:pathData="M213.333333 789.461333V234.538667C213.333333 168.533333 285.013333 127.530667 341.930667 160.981333l471.68 277.461334c56.106667 32.981333 56.106667 114.133333 0 147.114666L341.930667 863.018667C285.056 896.469333 213.333333 855.466667 213.333333 789.461333z" />
</vector>

View File

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="1024"
android:viewportHeight="1024">
<path
android:fillColor="#FFFFFFFF"
android:pathData="M471.5,77.5a128,128 0,0 1,72.1 -2.6l8.9,2.6 256,85.3a128,128 0,0 1,87.3 113.6l0.3,7.8L896,512c0,101.6 -44.5,186 -103.2,253.1l-8.1,9 -12.1,12.8a618.2,618.2 0,0 1,-25 24.2l-12.8,11.4 -13,11a839.3,839.3 0,0 1,-127.7 86.5l-19.5,10.5 -17.5,9a101.4,101.4 0,0 1,-90.5 0l-18.9,-9.6c-40.1,-21.1 -93.9,-53.2 -145.9,-96.3l-12.9,-11 -12.8,-11.4a618.2,618.2 0,0 1,-24.9 -24.2l-12.1,-12.8 -8.1,-9c-55.9,-63.9 -98.9,-143.4 -102.9,-238.6L128,512L128,284.2A128,128 0,0 1,208.2 165.5l7.3,-2.7 256,-85.3zM512,661.3c-80.2,0 -151.1,40.3 -193.4,101.7 62,57.2 132.2,97.2 176.6,119a37.4,37.4 0,0 0,33.7 0c44.4,-21.9 114.6,-61.9 176.6,-119A234.4,234.4 0,0 0,512 661.3zM491.8,138.2l-256,85.3A64,64 0,0 0,192 284.2L192,512c0,78.5 32.9,146.4 81.5,204.2A298.2,298.2 0,0 1,512 597.3a298.2,298.2 0,0 1,238.5 118.8C799.1,658.4 832,590.5 832,512L832,284.2a64,64 0,0 0,-43.8 -60.7l-256,-85.3a64,64 0,0 0,-40.4 0zM512,298.7a128,128 0,1 1,0 256,128 128,0 0,1 0,-256zM512,362.7a64,64 0,1 0,0 128,64 64,0 0,0 0,-128z" />
</vector>

View File

@@ -0,0 +1,12 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="1024"
android:viewportHeight="1024">
<path
android:pathData="M723.9,312c-13.2,-13.2 -34.7,-13.2 -47.9,0 -6.6,6.6 -9.9,15.3 -9.9,23.9 0,8.7 3.3,17.3 9.9,23.9 65.9,65.9 80.9,165.5 37.3,247.8 -4.9,9.2 -10.5,18.2 -16.9,26.8 -6.4,8.7 -12.9,16.4 -20,23.3 -13.4,13.1 -13.7,34.5 -0.6,47.9 13.1,13.4 34.5,13.7 47.9,0.6 9.7,-9.5 18.9,-20.2 27.3,-31.6 8.3,-11.2 15.7,-23 22.2,-35.2 57.5,-108.7 37.7,-240.3 -49.3,-327.4zM507.6,470c-0.2,-0.2 -0.4,-0.3 -0.7,-0.5 -0.8,-0.3 -1.7,-0.1 -2.3,0.5 -0.2,0.2 -0.3,0.4 -0.5,0.7 -0.1,0.3 -0.2,0.5 -0.2,0.8 0,0.6 0.3,1.1 0.6,1.5 0.2,0.2 0.4,0.3 0.7,0.5 0.3,0.1 0.5,0.2 0.8,0.2 0.6,0 1.1,-0.3 1.5,-0.6 0.4,-0.4 0.6,-1 0.6,-1.5 0,-0.3 -0.1,-0.5 -0.2,-0.8 0,-0.4 -0.2,-0.6 -0.3,-0.8z"
android:fillColor="#FFFFFFFF"/>
<path
android:pathData="M833.7,209.6c-13.2,-13.2 -34.5,-13.2 -47.6,0 -13.2,13.2 -13.2,34.5 0,47.6 122.3,122.3 140,314.4 42.1,456.6 -12.5,18.1 -26.5,35 -42.1,50.5 -6.6,6.6 -9.9,15.2 -9.9,23.8 0,8.6 3.3,17.2 9.9,23.8 13.2,13.2 34.5,13.2 47.6,0 18.4,-18.4 35.1,-38.5 49.9,-60C1000,583.1 979,355 833.7,209.6zM515.1,166.8c-48,-22.3 -102.9,-15.1 -143.4,19L176.6,349.9h-50.2c-31,0 -63.3,25.2 -63.3,56.3v209.4c0,31 32.2,56.3 63.3,56.3h50.2L371.7,836c24.9,21 55.4,31.8 86.2,31.8 19.3,0 38.7,-4.2 57.2,-12.8 48,-22.3 77.8,-69.1 77.8,-122L592.9,288.8c0,-52.9 -29.8,-99.6 -77.8,-122zM531.1,732.9c0,31.8 -17.2,52.9 -46.1,66.3 -28.9,13.4 -56.7,6.2 -81,-14.3L206.2,623.5c-4.9,-4.2 -11.2,-6.4 -17.6,-6.4h-60.2c-0.8,0 -1.5,-0.7 -1.5,-1.5L126.9,406.2c0,-0.8 0.7,-1.5 1.5,-1.5h60.2c6.5,0 12.7,-2.3 17.6,-6.4L412,237.7c24.4,-20.5 51.2,-24.7 80,-11.3 28.9,13.4 39.1,30.5 39.1,62.3v444.2z"
android:fillColor="#FFFFFFFF"/>
</vector>

View File

@@ -1,18 +0,0 @@
<?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"
android:left="12dp"
android:top="12dp"
android:right="12dp"
android:bottom="12dp" />
</layer-list>

View File

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="1024"
android:viewportHeight="1024">
<path
android:fillColor="#FFFFFFFF"
android:pathData="M938.7,512a384,384 0,0 1,-384 384,379.3 379.3,0 0,1 -220.2,-69.5 21.8,21.8 0,0 1,-9 -15.8,21.3 21.3,0 0,1 6,-16.6l30.7,-31.1a21.3,21.3 0,0 1,26.9 -2.6A294.8,294.8 0,0 0,554.7 810.7a298.7,298.7 0,1 0,-298.7 -298.7h100.7a20.9,20.9 0,0 1,15.4 6.4l8.5,8.5a21.3,21.3 0,0 1,0 30.3L230,708.3a21.8,21.8 0,0 1,-30.3 0l-150.6,-151a21.3,21.3 0,0 1,0 -30.3l8.5,-8.5a20.9,20.9 0,0 1,15.4 -6.4H170.7a384,384 0,0 1,768 0z" />
</vector>

View File

@@ -1,19 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" <vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp" android:width="24dp"
android:height="24dp" android:height="24dp"
android:viewportWidth="48" android:viewportWidth="1024"
android:viewportHeight="48"> android:viewportHeight="1024">
<path <path
android:fillColor="#f5f5f5" android:fillColor="#FFFFFFFF"
android:pathData="M24,24m-22,0a22,22 0,1 1,44 0a22,22 0,1 1,-44 0Z" /> android:pathData="M181.333333 384a32 32 0 0 1-64 0v-111.146667a155.52 155.52 0 0 1 155.52-155.52H384a32 32 0 0 1 0 64h-111.146667a91.52 91.52 0 0 0-91.52 91.52V384zM640 181.333333a32 32 0 0 1 0-64h111.146667a155.52 155.52 0 0 1 155.52 155.52V384a32 32 0 0 1-64 0v-111.146667a91.52 91.52 0 0 0-91.52-91.52H640zM842.666667 640a32 32 0 0 1 64 0v111.146667a155.52 155.52 0 0 1-155.52 155.52H640a32 32 0 0 1 0-64h111.146667a91.52 91.52 0 0 0 91.52-91.52V640zM384 842.666667a32 32 0 0 1 0 64h-111.146667a155.52 155.52 0 0 1-155.52-155.52V640a32 32 0 0 1 64 0v111.146667a91.52 91.52 0 0 0 91.52 91.52H384z m-192-298.666667a32 32 0 0 1 0-64h640a32 32 0 0 1 0 64H192z" />
<path
android:fillColor="#000000"
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="#000000"
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> </vector>

View File

@@ -1,9 +1,12 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" <vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp" android:width="24dp"
android:height="24dp" android:height="24dp"
android:viewportWidth="24.0" android:viewportWidth="1024"
android:viewportHeight="24.0"> android:viewportHeight="1024">
<path <path
android:fillColor="#FFFFFFFF" android:fillColor="#FFFFFFFF"
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"/> android:pathData="M924.8,625.7l-65.5,-56c3.1,-19 4.7,-38.4 4.7,-57.8s-1.6,-38.8 -4.7,-57.8l65.5,-56c10.1,-8.6 13.8,-22.6 9.3,-35.2l-0.9,-2.6c-18.1,-50.5 -44.9,-96.9 -79.7,-137.9l-1.8,-2.1c-8.6,-10.1 -22.5,-13.9 -35.1,-9.5l-81.3,28.9c-30,-24.6 -63.5,-44 -99.7,-57.6l-15.7,-85c-2.4,-13.1 -12.7,-23.3 -25.8,-25.7l-2.7,-0.5c-52.1,-9.4 -106.9,-9.4 -159,0l-2.7,0.5c-13.1,2.4 -23.4,12.6 -25.8,25.7l-15.8,85.4c-35.9,13.6 -69.2,32.9 -99,57.4l-81.9,-29.1c-12.5,-4.4 -26.5,-0.7 -35.1,9.5l-1.8,2.1c-34.8,41.1 -61.6,87.5 -79.7,137.9l-0.9,2.6c-4.5,12.5 -0.8,26.5 9.3,35.2l66.3,56.6c-3.1,18.8 -4.6,38 -4.6,57.1 0,19.2 1.5,38.4 4.6,57.1L99,625.5c-10.1,8.6 -13.8,22.6 -9.3,35.2l0.9,2.6c18.1,50.4 44.9,96.9 79.7,137.9l1.8,2.1c8.6,10.1 22.5,13.9 35.1,9.5l81.9,-29.1c29.8,24.5 63.1,43.9 99,57.4l15.8,85.4c2.4,13.1 12.7,23.3 25.8,25.7l2.7,0.5c26.1,4.7 52.8,7.1 79.5,7.1 26.7,0 53.5,-2.4 79.5,-7.1l2.7,-0.5c13.1,-2.4 23.4,-12.6 25.8,-25.7l15.7,-85c36.2,-13.6 69.7,-32.9 99.7,-57.6l81.3,28.9c12.5,4.4 26.5,0.7 35.1,-9.5l1.8,-2.1c34.8,-41.1 61.6,-87.5 79.7,-137.9l0.9,-2.6c4.5,-12.3 0.8,-26.3 -9.3,-35zM788.3,465.9c2.5,15.1 3.8,30.6 3.8,46.1s-1.3,31 -3.8,46.1l-6.6,40.1 74.7,63.9c-11.3,26.1 -25.6,50.7 -42.6,73.6L721,702.8l-31.4,25.8c-23.9,19.6 -50.5,35 -79.3,45.8l-38.1,14.3 -17.9,97c-28.1,3.2 -56.8,3.2 -85,0l-17.9,-97.2 -37.8,-14.5c-28.5,-10.8 -55,-26.2 -78.7,-45.7l-31.4,-25.9 -93.4,33.2c-17,-22.9 -31.2,-47.6 -42.6,-73.6l75.5,-64.5 -6.5,-40c-2.4,-14.9 -3.7,-30.3 -3.7,-45.5 0,-15.3 1.2,-30.6 3.7,-45.5l6.5,-40 -75.5,-64.5c11.3,-26.1 25.6,-50.7 42.6,-73.6l93.4,33.2 31.4,-25.9c23.7,-19.5 50.2,-34.9 78.7,-45.7l37.9,-14.3 17.9,-97.2c28.1,-3.2 56.8,-3.2 85,0l17.9,97 38.1,14.3c28.7,10.8 55.4,26.2 79.3,45.8l31.4,25.8 92.8,-32.9c17,22.9 31.2,47.6 42.6,73.6L781.8,426l6.5,39.9z" />
<path
android:fillColor="#FFFFFFFF"
android:pathData="M512,326c-97.2,0 -176,78.8 -176,176s78.8,176 176,176 176,-78.8 176,-176 -78.8,-176 -176,-176zM591.2,581.2C570,602.3 541.9,614 512,614c-29.9,0 -58,-11.7 -79.2,-32.8C411.7,560 400,531.9 400,502c0,-29.9 11.7,-58 32.8,-79.2C454,401.6 482.1,390 512,390c29.9,0 58,11.6 79.2,32.8C612.3,444 624,472.1 624,502c0,29.9 -11.7,58 -32.8,79.2z" />
</vector> </vector>

View File

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="1024"
android:viewportHeight="1024">
<path
android:fillColor="#FFFFFFFF"
android:pathData="M469.2,802.1l-81.7,-24.6L554.8,221.9l81.7,24.6L469.2,802.1zM362.7,654.5l-124.7,-141.7 124.8,-143.5 -64.4,-56 -173.8,199.8 174,197.7 64.1,-56.3zM899.4,513.1l-173.8,-199.8 -64.4,56 124.8,143.5 -124.7,141.7 64.1,56.4 174,-197.7z" />
</vector>

View File

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="1024"
android:viewportHeight="1024">
<path
android:fillColor="#FFFFFFFF"
android:pathData="M256 192h512c35.392 0 64 28.608 64 64v512c0 35.392-28.608 64-64 64H256c-35.392 0-64-28.608-64-64V256c0-35.392 28.608-64 64-64z" />
</vector>

View File

@@ -1,9 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" <vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp" android:width="24dp"
android:height="24dp" android:height="24dp"
android:viewportWidth="24.0" android:viewportWidth="1024"
android:viewportHeight="24.0"> android:viewportHeight="1024">
<path <path
android:fillColor="#FFFFFFFF" android:fillColor="#FFFFFFFF"
android:pathData="M20,8L4,8L4,6h16v2zM18,2L6,2v2h12L18,2zM22,12v8c0,1.1 -0.9,2 -2,2L4,22c-1.1,0 -2,-0.9 -2,-2v-8c0,-1.1 0.9,-2 2,-2h16c1.1,0 2,0.9 2,2zM16,16l-6,-3.27v6.53L16,16z"/> android:pathData="M170.6,256h682.7v85.3L170.6,341.3L170.6,256zM256,85.3h512v85.4L256,170.7L256,85.3zM853.3,426.7L170.6,426.7c-46.9,0 -85.3,38.4 -85.3,85.3v341.3c0,47 38.4,85.4 85.3,85.4h682.7c46.9,0 85.3,-38.4 85.3,-85.4L938.6,512c0,-46.9 -38.4,-85.3 -85.3,-85.3zM853.3,853.3L170.6,853.3L170.6,512h682.7v341.3z" />
</vector> </vector>

View File

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="1024"
android:viewportHeight="1024">
<path
android:fillColor="#FFFFFFFF"
android:pathData="M912.2,154A85.3,85.3 0,0 0,853.3 128a85.3,85.3 0,0 0,-33.3 6.8l-708.3,306.8a42.7,42.7 0,0 0,-26.5 42.7V512a42.7,42.7 0,0 0,29.4 42.7L298.7,616.1l55.5,187.3a75.9,75.9 0,0 0,56.3 53.3,62.7 62.7,0 0,0 15.4,0 73.8,73.8 0,0 0,50.8 -20.5l67.8,-64 131.4,103.7a85.3,85.3 0,0 0,90 9.4l14.1,-7.3a88.3,88.3 0,0 0,46.5 -62.7L938.7,235.1a90,90 0,0 0,-26.5 -81.1zM763.7,805.1a25.2,25.2 0,0 1,-12.8 17.5l-14.1,7.3a19.6,19.6 0,0 1,-9 2.1,19.6 19.6,0 0,1 -12.4,-4.7l-160.4,-128a20.9,20.9 0,0 0,-27.7 0l-94.7,89.2a11.1,11.1 0,0 1,-6 2.1V640a21.8,21.8 0,0 1,6.8 -15.8c136.1,-128 217.6,-199.7 266.2,-240.6a15.8,15.8 0,0 0,5.1 -11.1,13.7 13.7,0 0,0 -4.3,-11.1 14.9,14.9 0,0 0,-17.9 -4.3l-322.6,203.5a21.3,21.3 0,0 1,-18.3 0L149.3,494.9l694.2,-301.2a16.6,16.6 0,0 1,7.7 0,22.2 22.2,0 0,1 16.2,7.7 26.9,26.9 0,0 1,6.8 23.5z" />
</vector>

View File

@@ -1,9 +0,0 @@
<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="M13.5,0.67s0.74,2.65 0.74,4.8c0,2.06 -1.35,3.73 -3.41,3.73 -2.07,0 -3.63,-1.67 -3.63,-3.73l0.03,-0.36C5.21,7.51 4,10.62 4,14c0,4.42 3.58,8 8,8s8,-3.58 8,-8C20,8.61 17.41,3.8 13.5,0.67zM11.71,19c-1.78,0 -3.22,-1.4 -3.22,-3.14 0,-1.62 1.05,-2.76 2.81,-3.12 1.77,-0.36 3.6,-1.21 4.62,-2.58 0.39,1.29 0.59,2.65 0.59,4.04 0,2.65 -2.15,4.8 -4.8,4.8z"/>
</vector>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 855 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -1,9 +0,0 @@
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<gradient
android:angle="135"
android:centerColor="#009688"
android:endColor="#00695C"
android:startColor="#4DB6AC"
android:type="linear" />
</shape>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

View File

@@ -0,0 +1,12 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="1024"
android:viewportHeight="1024">
<path
android:fillColor="#FF000000"
android:pathData="M810.7,206.3A426.7,426.7 0,1 0,512 938.7h5.5A426.7,426.7 0,0 0,810.7 206.3zM771.8,765A360.1,360.1 0,0 1,517.1 874.7L512,874.7a362.7,362.7 0,0 1,-4.9 -725.3h3.4a362.7,362.7 0,0 1,261.3 615.7z" />
<path
android:fillColor="#FF000000"
android:pathData="M547.2,390a57.4,57.4 0,0 0,62.3 -55.3,39.3 39.3,0 0,0 -45,-42.7 58.9,58.9 0,0 0,-64 54.6c-1.7,26.9 13.9,43.3 46.7,43.3zM548.3,663.5c-5.5,0 -7.9,-7.3 -2.3,-28.2l31.1,-118c11.7,-42.7 7.9,-71.3 -15.8,-71.3 -28.4,0 -94.7,28.4 -152.5,76.4l11.7,19.4a181.3,181.3 0,0 1,56.1 -24.7c5.5,0 4.7,7.3 0,25.2l-27.3,112.2c-16.6,64 0,77.7 24.5,77.7s85.3,-21.3 141,-77.7l-13.4,-17.9a122.7,122.7 0,0 1,-53.1 26.9z" />
</vector>

View File

@@ -1,9 +0,0 @@
<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.8,10.9c-2.27,-0.59 -3,-1.2 -3,-2.15 0,-1.09 1.01,-1.85 2.7,-1.85 1.78,0 2.44,0.85 2.5,2.1h2.21c-0.07,-1.72 -1.12,-3.3 -3.21,-3.81V3h-3v2.16c-1.94,0.42 -3.5,1.68 -3.5,3.61 0,2.31 1.91,3.46 4.7,4.13 2.5,0.6 3,1.48 3,2.41 0,0.69 -0.49,1.79 -2.7,1.79 -2.06,0 -2.87,-0.92 -2.98,-2.1h-2.2c0.12,2.19 1.76,3.42 3.68,3.83V21h3v-2.15c1.95,-0.37 3.5,-1.5 3.5,-3.55 0,-2.84 -2.43,-3.81 -4.7,-4.4z"/>
</vector>

View File

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="1024"
android:viewportHeight="1024">
<path
android:fillColor="#FF000000"
android:pathData="M864,192L704,192L704,96c0,-17.7 -14.3,-32 -32,-32L352,64c-9,0 -17.2,3.7 -22.9,9.7L137.7,265.1c-6,5.8 -9.7,14 -9.7,22.9v512c0,17.7 14.3,32 32,32h160v96c0,17.7 14.3,32 32,32h512c17.7,0 32,-14.3 32,-32L896,224c0,-17.7 -14.3,-32 -32,-32zM320,173.2L320,256h-82.8l82.8,-82.8zM192,768L192,320h160c17.7,0 32,-14.3 32,-32L384,128h256v64h-96c-9,0 -17.2,3.7 -22.9,9.7L329.7,393.1c-6,5.8 -9.7,14 -9.7,22.9v352L192,768zM512,301.2L512,384h-82.8l82.8,-82.8zM832,896L384,896L384,448h160c17.7,0 32,-14.3 32,-32L576,256h256v640z" />
</vector>

View File

@@ -1,10 +0,0 @@
<?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="#424242"
android:pathData="M19,6.41L17.59,5 12,10.59 6.41,5 5,6.41 10.59,12 5,17.59 6.41,19 12,13.41 17.59,19 19,17.59 13.41,12z" />
</vector>

View File

@@ -1,9 +1,12 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" <vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp" android:width="24dp"
android:height="24dp" android:height="24dp"
android:viewportWidth="24.0" android:viewportWidth="1024"
android:viewportHeight="24.0"> android:viewportHeight="1024">
<path <path
android:fillColor="#FF000000" 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"/> android:pathData="M512,192.7v42.7a21.3,21.3 0,0 1,-21.3 21.3H213.5V770.6l552.9,-2.2v-233.4a21.3,21.3 0,0 1,21.3 -21.3h42.7a21.3,21.3 0,0 1,21.3 21.3v256.1c0,32.6 -24.9,59.3 -56.6,62.4l-6.1,0.3 -598.2,2.1c-32.6,0 -59.3,-24.8 -62.4,-56.6l-0.3,-6V234c0,-32.6 24.8,-59.3 56.6,-62.4l6,-0.3H490.7a21.3,21.3 0,0 1,21.3 21.3z" />
<path
android:fillColor="#FF000000"
android:pathData="M848.7,238.2l-250.8,250.8a21.3,21.3 0,0 1,-13.1 6.1c-30.8,2.9 -47.9,2.6 -51.2,-0.8 -3.4,-3.4 -4,-20.8 -2,-52.3a21.3,21.3 0,0 1,6.2 -13.7l250.5,-250.6a21.3,21.3 0,0 1,30.2 0l30.2,30.2a21.3,21.3 0,0 1,0 30.2z" />
</vector> </vector>

View File

@@ -1,34 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" <vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp" android:width="24dp"
android:height="24dp" android:height="24dp"
android:viewportWidth="24" android:viewportWidth="1024"
android:viewportHeight="24"> android:viewportHeight="1024">
<path <path
android:pathData="M11,23h8a3,3 0,0 0,3 -3V8L15,1H7A3,3 0,0 0,4 4V9" android:pathData="M537,85.3A85.3,85.3 0,0 1,597.3 110.3L828.3,341.3A85.3,85.3 0,0 1,853.3 401.7L853.3,810.7a128,128 0,0 1,-128 128L298.7,938.7a128,128 0,0 1,-128 -128L170.7,213.3a128,128 0,0 1,128 -128zM537,170.7L298.7,170.7a42.7,42.7 0,0 0,-42.7 42.7v597.3a42.7,42.7 0,0 0,42.7 42.7h426.7a42.7,42.7 0,0 0,42.7 -42.7L768,401.7L537,170.7zM512,384a42.7,42.7 0,0 1,42.4 37.7L554.7,426.7v85.3h85.3a42.7,42.7 0,0 1,5 85L640,597.3h-85.3v85.3a42.7,42.7 0,0 1,-85 5L469.3,682.7v-85.3L384,597.3a42.7,42.7 0,0 1,-5 -85L384,512h85.3v-85.3a42.7,42.7 0,0 1,42.7 -42.7z"
android:strokeLineJoin="round" android:fillColor="#FF000000"/>
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> </vector>

View File

@@ -0,0 +1,12 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="1024"
android:viewportHeight="1024">
<path
android:fillColor="#FF000000"
android:pathData="M784,112L240,112c-88,0 -160,72 -160,160v480c0,88 72,160 160,160h544c88,0 160,-72 160,-160L944,272c0,-88 -72,-160 -160,-160zM880,752c0,52.8 -43.2,96 -96,96L240,848c-52.8,0 -96,-43.2 -96,-96L144,272c0,-52.8 43.2,-96 96,-96h544c52.8,0 96,43.2 96,96v480z" />
<path
android:fillColor="#FF000000"
android:pathData="M352,480c52.8,0 96,-43.2 96,-96s-43.2,-96 -96,-96 -96,43.2 -96,96 43.2,96 96,96zM352,352c17.6,0 32,14.4 32,32s-14.4,32 -32,32 -32,-14.4 -32,-32 14.4,-32 32,-32zM814.4,731.2l-3.2,-3.2 -177.6,-177.6c-25.6,-25.6 -65.6,-25.6 -91.2,0l-80,80 -36.8,-36.8c-25.6,-25.6 -65.6,-25.6 -91.2,0L200,728c-4.8,6.4 -8,14.4 -8,24 0,17.6 14.4,32 32,32 9.6,0 16,-3.2 22.4,-9.6L380.8,640l134.4,134.4c6.4,6.4 14.4,9.6 24,9.6 17.6,0 32,-14.4 32,-32 0,-9.6 -4.8,-17.6 -9.6,-24l-52.8,-52.8 80,-80L769.6,776c6.4,4.8 12.8,8 20.8,8 17.6,0 32,-14.4 32,-32 0,-8 -3.2,-16 -8,-20.8z" />
</vector>

View File

@@ -1,9 +0,0 @@
<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="#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"/>
</vector>

View File

@@ -1,9 +0,0 @@
<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="#FF000000"
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>

View File

@@ -1,9 +1,15 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" <vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp" android:width="24dp"
android:height="24dp" android:height="24dp"
android:viewportWidth="24.0" android:viewportWidth="1024"
android:viewportHeight="24.0"> android:viewportHeight="1024">
<path <path
android:fillColor="#FF000000" android:pathData="M273.2,212.8h583.7L856.9,906.2h-583.7L273.2,212.8zM344.9,284.5L344.9,834.6h440.3L785.2,284.5h-440.3z"
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"/> android:fillColor="#FF000000"/>
<path
android:pathData="M167.1,117.8h554.4v123.6h-71.7V189.4H238.8v498.8h73.9v71.7H167.1V117.8z"
android:fillColor="#FF000000"/>
<path
android:pathData="M674.8,504H455.3v-71.7h219.4v71.7zM674.8,650.2H455.3v-71.7h219.4v71.7z"
android:fillColor="#FF000000"/>
</vector> </vector>

View File

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="1024"
android:viewportHeight="1024">
<path
android:fillColor="#FFFFFFFF"
android:pathData="M213.333333 789.461333V234.538667C213.333333 168.533333 285.013333 127.530667 341.930667 160.981333l471.68 277.461334c56.106667 32.981333 56.106667 114.133333 0 147.114666L341.930667 863.018667C285.056 896.469333 213.333333 855.466667 213.333333 789.461333z" />
</vector>

View File

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="1024"
android:viewportHeight="1024">
<path
android:fillColor="#FF000000"
android:pathData="M471.5,77.5a128,128 0,0 1,72.1 -2.6l8.9,2.6 256,85.3a128,128 0,0 1,87.3 113.6l0.3,7.8L896,512c0,101.6 -44.5,186 -103.2,253.1l-8.1,9 -12.1,12.8a618.2,618.2 0,0 1,-25 24.2l-12.8,11.4 -13,11a839.3,839.3 0,0 1,-127.7 86.5l-19.5,10.5 -17.5,9a101.4,101.4 0,0 1,-90.5 0l-18.9,-9.6c-40.1,-21.1 -93.9,-53.2 -145.9,-96.3l-12.9,-11 -12.8,-11.4a618.2,618.2 0,0 1,-24.9 -24.2l-12.1,-12.8 -8.1,-9c-55.9,-63.9 -98.9,-143.4 -102.9,-238.6L128,512L128,284.2A128,128 0,0 1,208.2 165.5l7.3,-2.7 256,-85.3zM512,661.3c-80.2,0 -151.1,40.3 -193.4,101.7 62,57.2 132.2,97.2 176.6,119a37.4,37.4 0,0 0,33.7 0c44.4,-21.9 114.6,-61.9 176.6,-119A234.4,234.4 0,0 0,512 661.3zM491.8,138.2l-256,85.3A64,64 0,0 0,192 284.2L192,512c0,78.5 32.9,146.4 81.5,204.2A298.2,298.2 0,0 1,512 597.3a298.2,298.2 0,0 1,238.5 118.8C799.1,658.4 832,590.5 832,512L832,284.2a64,64 0,0 0,-43.8 -60.7l-256,-85.3a64,64 0,0 0,-40.4 0zM512,298.7a128,128 0,1 1,0 256,128 128,0 0,1 0,-256zM512,362.7a64,64 0,1 0,0 128,64 64,0 0,0 0,-128z" />
</vector>

View File

@@ -0,0 +1,12 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="1024"
android:viewportHeight="1024">
<path
android:pathData="M723.9,312c-13.2,-13.2 -34.7,-13.2 -47.9,0 -6.6,6.6 -9.9,15.3 -9.9,23.9 0,8.7 3.3,17.3 9.9,23.9 65.9,65.9 80.9,165.5 37.3,247.8 -4.9,9.2 -10.5,18.2 -16.9,26.8 -6.4,8.7 -12.9,16.4 -20,23.3 -13.4,13.1 -13.7,34.5 -0.6,47.9 13.1,13.4 34.5,13.7 47.9,0.6 9.7,-9.5 18.9,-20.2 27.3,-31.6 8.3,-11.2 15.7,-23 22.2,-35.2 57.5,-108.7 37.7,-240.3 -49.3,-327.4zM507.6,470c-0.2,-0.2 -0.4,-0.3 -0.7,-0.5 -0.8,-0.3 -1.7,-0.1 -2.3,0.5 -0.2,0.2 -0.3,0.4 -0.5,0.7 -0.1,0.3 -0.2,0.5 -0.2,0.8 0,0.6 0.3,1.1 0.6,1.5 0.2,0.2 0.4,0.3 0.7,0.5 0.3,0.1 0.5,0.2 0.8,0.2 0.6,0 1.1,-0.3 1.5,-0.6 0.4,-0.4 0.6,-1 0.6,-1.5 0,-0.3 -0.1,-0.5 -0.2,-0.8 0,-0.4 -0.2,-0.6 -0.3,-0.8z"
android:fillColor="#FF000000"/>
<path
android:pathData="M833.7,209.6c-13.2,-13.2 -34.5,-13.2 -47.6,0 -13.2,13.2 -13.2,34.5 0,47.6 122.3,122.3 140,314.4 42.1,456.6 -12.5,18.1 -26.5,35 -42.1,50.5 -6.6,6.6 -9.9,15.2 -9.9,23.8 0,8.6 3.3,17.2 9.9,23.8 13.2,13.2 34.5,13.2 47.6,0 18.4,-18.4 35.1,-38.5 49.9,-60C1000,583.1 979,355 833.7,209.6zM515.1,166.8c-48,-22.3 -102.9,-15.1 -143.4,19L176.6,349.9h-50.2c-31,0 -63.3,25.2 -63.3,56.3v209.4c0,31 32.2,56.3 63.3,56.3h50.2L371.7,836c24.9,21 55.4,31.8 86.2,31.8 19.3,0 38.7,-4.2 57.2,-12.8 48,-22.3 77.8,-69.1 77.8,-122L592.9,288.8c0,-52.9 -29.8,-99.6 -77.8,-122zM531.1,732.9c0,31.8 -17.2,52.9 -46.1,66.3 -28.9,13.4 -56.7,6.2 -81,-14.3L206.2,623.5c-4.9,-4.2 -11.2,-6.4 -17.6,-6.4h-60.2c-0.8,0 -1.5,-0.7 -1.5,-1.5L126.9,406.2c0,-0.8 0.7,-1.5 1.5,-1.5h60.2c6.5,0 12.7,-2.3 17.6,-6.4L412,237.7c24.4,-20.5 51.2,-24.7 80,-11.3 28.9,13.4 39.1,30.5 39.1,62.3v444.2z"
android:fillColor="#FF000000"/>
</vector>

View File

@@ -1,19 +1,13 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" <vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp" android:width="24dp"
android:height="24dp" android:height="24dp"
android:viewportWidth="48" android:viewportWidth="1024"
android:viewportHeight="48"> android:viewportHeight="1024">
<path
android:fillColor="#FFFFFFFF"
android:pathData="M0,512C0,229.23 229.81,0 512,0 794.77,0 1024,229.81 1024,512 1024,794.77 794.19,1024 512,1024 229.23,1024 0,794.19 0,512Z" />
<path <path
android:fillColor="@color/colorBg" android:fillColor="#FF000000"
android:pathData="M24,24m-22,0a22,22 0,1 1,44 0a22,22 0,1 1,-44 0Z" /> android:pathData="M780.16 875.52H652.8c-17.92 0-32-14.08-32-32s14.08-32 32-32h127.36c16 0 29.44-13.44 29.44-29.44v-127.36c0-17.92 14.08-32 32-32s32 14.08 32 32v127.36c-0.64 51.2-42.24 93.44-93.44 93.44zM192.64 415.36c-17.92 0-32-14.08-32-32V256c0-51.2 41.6-93.44 93.44-93.44h127.36c17.92 0 32 14.08 32 32s-14.08 32-32 32h-128c-16 0-29.44 13.44-29.44 29.44v127.36c0.64 17.92-14.08 32-31.36 32zM840.96 415.36c-17.92 0-32-14.08-32-32V256c0-16-13.44-29.44-29.44-29.44h-127.36c-17.92 0-32-14.08-32-32s14.08-32 32-32h127.36c51.2 0 93.44 41.6 93.44 93.44v127.36c0 17.92-14.08 32-32 32zM381.44 875.52h-128c-51.2 0-93.44-41.6-93.44-93.44v-127.36c0-17.92 14.08-32 32-32s32 14.08 32 32v127.36c0 16 13.44 29.44 29.44 29.44h127.36c17.92 0 32 14.08 32 32s-14.08 32-31.36 32zM759.04 553.6H274.56c-17.92 0-32-14.08-32-32s14.08-32 32-32h484.48c17.92 0 32 14.08 32 32s-14.72 32-32 32z" />
<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> </vector>

File diff suppressed because one or more lines are too long

View File

@@ -1,18 +1,13 @@
<?xml version="1.0" encoding="utf-8"?> <vector xmlns:android="http://schemas.android.com/apk/res/android"
<layer-list xmlns:android="http://schemas.android.com/apk/res/android"> android:width="24dp"
android:height="24dp"
android:viewportHeight="1024"
android:viewportWidth="1024">
<path
android:fillColor="#FFFFFFFF"
android:pathData="M0,512C0,229.23 229.81,0 512,0 794.77,0 1024,229.81 1024,512 1024,794.77 794.19,1024 512,1024 229.23,1024 0,794.19 0,512Z" />
<item <path
android:drawable="@drawable/ic_shortcut_background" android:fillColor="#FF000000"
android:left="2dp" android:pathData="M742.4 691.2c-12.8-12.8-25.6-12.8-38.4 0-51.2 51.2-115.2 83.2-185.6 83.2-128 0-230.4-89.6-256-204.8l44.8 0C320 569.6 332.8 550.4 320 537.6L243.2 428.8c-6.4-12.8-25.6-12.8-32 0L134.4 537.6c-12.8 12.8 0 32 19.2 32l51.2 0C224 716.8 358.4 832 512 832c89.6 0 166.4-32 230.4-96C748.8 723.2 748.8 704 742.4 691.2L742.4 691.2zM281.6 332.8c12.8 12.8 25.6 12.8 38.4 0C371.2 275.2 441.6 249.6 512 249.6c128 0 230.4 89.6 256 204.8l-44.8 0c-19.2 0-25.6 19.2-19.2 32l76.8 108.8c6.4 12.8 25.6 12.8 32 0l76.8-108.8c12.8-12.8 0-32-19.2-32l-51.2 0C800 307.2 665.6 192 512 192 422.4 192 345.6 230.4 281.6 288 275.2 300.8 275.2 320 281.6 332.8L281.6 332.8zM627.2 556.8c0 25.6-25.6 51.2-51.2 51.2l-128 0c-25.6 0-51.2-25.6-51.2-51.2L396.8 473.6c0-25.6 25.6-51.2 51.2-51.2l128 0c25.6 0 51.2 25.6 51.2 51.2L627.2 556.8 627.2 556.8zM627.2 556.8" />
android:top="2dp" </vector>
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>

View File

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="1024"
android:viewportHeight="1024">
<path
android:fillColor="#FF000000"
android:pathData="M938.7,512a384,384 0,0 1,-384 384,379.3 379.3,0 0,1 -220.2,-69.5 21.8,21.8 0,0 1,-9 -15.8,21.3 21.3,0 0,1 6,-16.6l30.7,-31.1a21.3,21.3 0,0 1,26.9 -2.6A294.8,294.8 0,0 0,554.7 810.7a298.7,298.7 0,1 0,-298.7 -298.7h100.7a20.9,20.9 0,0 1,15.4 6.4l8.5,8.5a21.3,21.3 0,0 1,0 30.3L230,708.3a21.8,21.8 0,0 1,-30.3 0l-150.6,-151a21.3,21.3 0,0 1,0 -30.3l8.5,-8.5a20.9,20.9 0,0 1,15.4 -6.4H170.7a384,384 0,0 1,768 0z" />
</vector>

View File

@@ -1,6 +1,10 @@
<?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/color_fab_orange"/> <corners android:radius="20dp" />
<corners android:radius="20dp"/> <padding
<padding android:left="0dp" android:top="0dp" android:right="0dp" android:bottom="0dp" /> android:bottom="0dp"
android:left="0dp"
android:right="0dp"
android:top="0dp" />
<solid android:color="@color/color_fab_active" />
</shape> </shape>

View File

@@ -1,6 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="@color/color_switch_fab_grey"/>
<corners android:radius="20dp"/>
<padding android:left="0dp" android:top="0dp" android:right="0dp" android:bottom="0dp" />
</shape>

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<corners android:radius="20dp" />
<padding
android:bottom="0dp"
android:left="0dp"
android:right="0dp"
android:top="0dp" />
<solid android:color="@color/color_fab_inactive" />
</shape>

View File

@@ -1,19 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" <vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp" android:width="24dp"
android:height="24dp" android:height="24dp"
android:viewportWidth="48" android:viewportWidth="1024"
android:viewportHeight="48"> android:viewportHeight="1024">
<path <path
android:fillColor="#050505" android:fillColor="#FF000000"
android:pathData="M24,24m-22,0a22,22 0,1 1,44 0a22,22 0,1 1,-44 0Z" /> android:pathData="M181.333333 384a32 32 0 0 1-64 0v-111.146667a155.52 155.52 0 0 1 155.52-155.52H384a32 32 0 0 1 0 64h-111.146667a91.52 91.52 0 0 0-91.52 91.52V384zM640 181.333333a32 32 0 0 1 0-64h111.146667a155.52 155.52 0 0 1 155.52 155.52V384a32 32 0 0 1-64 0v-111.146667a91.52 91.52 0 0 0-91.52-91.52H640zM842.666667 640a32 32 0 0 1 64 0v111.146667a155.52 155.52 0 0 1-155.52 155.52H640a32 32 0 0 1 0-64h111.146667a91.52 91.52 0 0 0 91.52-91.52V640zM384 842.666667a32 32 0 0 1 0 64h-111.146667a155.52 155.52 0 0 1-155.52-155.52V640a32 32 0 0 1 64 0v111.146667a91.52 91.52 0 0 0 91.52 91.52H384z m-192-298.666667a32 32 0 0 1 0-64h640a32 32 0 0 1 0 64H192z" />
<path
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
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="#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
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> </vector>

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