Compare commits

...

233 Commits

Author SHA1 Message Date
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
2dust
6b9728dc84 Update 1.8.19 2024-03-30 17:25:08 +08:00
2dust
1ce9b7c0c8 Merge pull request #2956 from xfree-man/master
Add observatory to V2rayConfig, to enable load balancing features in a custom config
2024-03-27 07:53:57 +08:00
xfree-man
ff75d3fdc2 Add observatory to V2rayConfig 2024-03-26 13:31:04 +04:00
2dust
8154812570 Merge pull request #2948 from Amir-yazdanmanesh/master
Fix deprecated codes
2024-03-22 09:58:44 +08:00
amir
1dcd2478fc Expression should use clarifying parentheses 2024-03-20 12:37:53 +03:30
amir
0515806e92 Remove deprecated codes in MainActivity.kt 2024-03-20 12:37:32 +03:30
2dust
f286506ba4 Bug fix 2024-03-16 17:11:38 +08:00
2dust
48be736275 Merge pull request #2933 from Utaea/pr
Add support to transport for shadowsocks
2024-03-16 16:58:20 +08:00
2dust
af0faeab16 Merge pull request #2928 from yuhan6665/authority
Add authority for gRPC
2024-03-16 16:31:12 +08:00
2dust
fb6edee842 Merge branch 'master' into authority 2024-03-16 16:30:28 +08:00
2dust
1209c4b92a Merge pull request #2921 from solokot/master
Update & improve Russian translation
2024-03-16 16:26:31 +08:00
Utaea
e4e668b492 Add support to transport for shadowsocks 2024-03-15 03:51:49 +00:00
yuhan6665
92d3136a23 Add authority for gRPC 2024-03-13 12:57:08 -04:00
solokot
f6e74ddb45 Update & improve Russian translation 2024-03-12 10:22:16 +03:00
2dust
c34f332771 Update 1.8.18 2024-03-12 10:49:52 +08:00
2dust
eb1d71bda6 Add httpupgrade 2024-03-12 10:49:32 +08:00
2dust
3cdd5822cb Merge pull request #2915 from Malus-risus/master
Update dependency
2024-03-10 10:25:07 +08:00
Το μοχθηρό ^_^
8f924b5ce1 Delete renovate.json 2024-03-09 17:33:08 +08:00
Το μοχθηρό ^_^
2ae645dce5 Merge pull request #24 from Malus-risus/renovate/com.android.library-8.x
Update plugin com.android.library to v8.3.0
2024-03-09 17:20:13 +08:00
renovate[bot]
f848a7119f Update plugin com.android.library to v8.3.0 2024-03-09 09:12:10 +00:00
Το μοχθηρό ^_^
2fa41db32f Merge pull request #23 from Malus-risus/renovate/com.android.application-8.x
Update plugin com.android.application to v8.3.0
2024-03-09 17:11:24 +08:00
Το μοχθηρό ^_^
9af4516761 Merge pull request #22 from Malus-risus/renovate/kotlin-ksp
Update Kotlin, KSP to v1.9.23
2024-03-09 17:11:03 +08:00
renovate[bot]
189b07124d Update plugin com.android.application to v8.3.0 2024-03-09 08:19:13 +00:00
renovate[bot]
e6e5cdcdcd Update Kotlin, KSP to v1.9.23 2024-03-09 08:19:07 +00:00
Το μοχθηρό ^_^
a31e4dab85 Create renovate.json 2024-03-09 16:18:14 +08:00
2dust
b2a9397dc7 Revert "Merge pull request #2903 from ssszmath/master"
This reverts commit a536df1a02, reversing
changes made to 2dc82b7e83.
2024-03-07 08:22:28 +08:00
2dust
a536df1a02 Merge pull request #2903 from ssszmath/master
Fixing the problem of connecting to the Internet after publishing the application on Google Play in aab format
2024-03-07 08:06:04 +08:00
Sajad Sajad
1a2751563d add NativeLibs configuration to manifest and gradle.properties 2024-03-05 19:12:04 +03:30
2dust
2dc82b7e83 Merge pull request #2892 from vfarid/add_remark_to_custom_configs
Added remarks to all custom configs
2024-03-03 16:59:03 +08:00
2dust
b02d81ae53 Merge pull request #2889 from vfarid/more_fragment_options
More fragment options + Fix for reality configs
2024-03-03 16:53:22 +08:00
Vahid Farid
773bcc5658 Set V2rayConfig remarks based on ServerConfig in V2rayConfigUtil.kt 2024-03-02 18:30:05 +03:30
Vahid Farid
9142b9cfb4 Update AngConfigManager.kt
Added remark for single config subscription
Removed unnecessary code in multiple config subscription update
2024-03-02 18:27:59 +03:30
Vahid Farid
376882d975 Add remark to custom-config for export in ServerCustomConfigActivity.kt 2024-03-02 18:26:00 +03:30
Vahid Farid
f330c32ccc Get config name from custom-config's remark during import in MainViewModel.kt 2024-03-02 18:25:01 +03:30
Vahid Farid
58dbb71b53 Fix fragment for reality configs 2024-03-02 16:54:17 +03:30
Vahid Farid
ebd0257f6e Added new fragment packets for reality 2024-03-02 16:53:34 +03:30
2dust
945c584fc6 Merge pull request #2886 from MrMalekfar/master
Addressing issue #2880
2024-03-01 08:07:55 +08:00
MrMalekfar
19cd24c37a Update AngConfigManager.kt 2024-03-01 02:35:56 +03:30
MrMalekfar
3994810c4e Update AngConfigManager.kt 2024-03-01 02:06:36 +03:30
MrMalekfar
d86e68c77a Update AngConfigManager.kt 2024-03-01 01:51:08 +03:30
MrMalekfar
cc7bdefe54 Update AngConfigManager.kt 2024-03-01 01:27:59 +03:30
MrMalekfar
51b32a030a Update AngConfigManager.kt 2024-02-29 14:15:05 +03:30
MrMalekfar
6c76ddd145 Update AngConfigManager.kt 2024-02-29 14:12:19 +03:30
MrMalekfar
0d12cc5dc8 Update AngConfigManager.kt 2024-02-29 13:55:52 +03:30
MrMalekfar
cfb756723c Update AngConfigManager.kt 2024-02-29 11:07:39 +03:30
MrMalekfar
0e9f198341 Update AngConfigManager.kt 2024-02-29 09:59:58 +03:30
MrMalekfar
1fa1325630 Update AngConfigManager.kt 2024-02-29 09:50:31 +03:30
MrMalekfar
dc79d3a897 Update AngConfigManager.kt 2024-02-29 09:25:06 +03:30
MrMalekfar
486f3ffc96 Update AngConfigManager.kt 2024-02-29 09:23:46 +03:30
2dust
bf030e12f5 Merge pull request #2883 from admarty/master
Update vi translation
2024-02-29 08:58:12 +08:00
MrMalekfar
e80cce9696 Update AngConfigManager.kt 2024-02-29 02:04:42 +03:30
admarty
5bb9ecce47 Update vi translation 2024-02-28 22:32:26 +07:00
MrMalekfar
5eb09aa54e Update AngConfigManager.kt 2024-02-28 16:31:30 +03:30
MrMalekfar
ff261d9939 Update AngConfigManager.kt 2024-02-28 15:22:57 +03:30
MrMalekfar
1624ec87b2 Update AngConfigManager.kt 2024-02-28 15:06:37 +03:30
MrMalekfar
f1062f2f45 Update AngConfigManager.kt 2024-02-28 14:51:54 +03:30
2dust
39341c27bc up 1.8.17 2024-02-26 08:54:51 +08:00
2dust
31a90cec2b Merge pull request #2869 from Malus-risus/master
Refactor: Simplify Extensions and Improve Compatibility
2024-02-26 08:11:39 +08:00
Το μοχθηρό ^_^
617fc63393 Delete renovate.json 2024-02-25 19:59:53 +08:00
Το μοχθηρό ^_^
802f2cf3eb Update _Ext.kt 2024-02-25 19:05:48 +08:00
Το μοχθηρό ^_^
c7efcde868 Update _Ext.kt 2024-02-25 18:42:39 +08:00
Το μοχθηρό ^_^
e858179204 Update _Ext.kt 2024-02-25 18:31:42 +08:00
Το μοχθηρό ^_^
6871b0b950 Merge branch '2dust:master' into master 2024-02-25 10:42:48 +08:00
2dust
e9a27a1585 Merge pull request #2868 from NagisaEfi/master
Update translation
2024-02-25 09:28:36 +08:00
Το μοχθηρό ^_^
976b765629 Create renovate.json 2024-02-24 01:09:56 +08:00
NagisaEfi
126b9b6516 Update translation 2024-02-23 23:23:39 +08:00
2dust
94dab02b54 Merge pull request #2856 from Malus-risus/master
Remove unnecessary parentheses
2024-02-20 13:02:35 +08:00
Το μοχθηρό ^_^
a893b87730 Update MainRecyclerAdapter.kt 2024-02-19 14:36:28 +08:00
Το μοχθηρό ^_^
7db2ddd1f7 Update V2RayServiceManager.kt 2024-02-19 14:34:50 +08:00
Το μοχθηρό ^_^
748980aa1a Update PerAppProxyActivity.kt 2024-02-19 14:33:12 +08:00
Το μοχθηρό ^_^
96d416066e Update AppManagerUtil.kt 2024-02-19 14:31:47 +08:00
Το μοχθηρό ^_^
3955bb16bc Update _Ext.kt 2024-02-19 14:29:35 +08:00
Το μοχθηρό ^_^
de9bbf842f Update Utils.kt 2024-02-19 12:42:56 +08:00
Το μοχθηρό ^_^
336b673746 Update AngConfigManager.kt 2024-02-19 12:38:24 +08:00
Το μοχθηρό ^_^
f1b6b1e871 Update SettingsActivity.kt 2024-02-19 12:07:13 +08:00
2dust
fba4c03bb5 Bug fix 2024-02-17 09:55:26 +08:00
2dust
a32ae5b53f Merge pull request #2848 from NotDubious/master
Update Farsi translation
2024-02-17 09:23:11 +08:00
Mahyar
589e0f38fd Update Farsi translation 2024-02-16 11:07:31 +03:30
2dust
e21116680e Up 1.8.16 2024-02-16 13:48:41 +08:00
2dust
e1960f5aff Write remarks when pasting subscription link
https://github.com/2dust/v2rayNG/issues/2845
2024-02-16 09:52:10 +08:00
2dust
327ba57088 Improve 2024-02-16 09:39:39 +08:00
2dust
04e1b024e2 Merge pull request #2846 from Malus-risus/master
Update dependency
2024-02-16 09:20:25 +08:00
2dust
b59fe9b57b Delete renovate.json 2024-02-16 09:19:29 +08:00
2dust
fab0b756de Merge pull request #2844 from solokot/master
Update Russian translation
2024-02-16 09:12:59 +08:00
Το μοχθηρό ^_^
aa2727d0d0 Merge pull request #10 from Malus-risus/renovate/org.jetbrains.kotlinx-kotlinx-coroutines-android-1.x
Update dependency org.jetbrains.kotlinx:kotlinx-coroutines-android to v1.8.0
2024-02-16 00:58:49 +08:00
Το μοχθηρό ^_^
ce3dd73a81 Merge branch '2dust:master' into master 2024-02-16 00:54:06 +08:00
renovate[bot]
2eab209fea Update dependency org.jetbrains.kotlinx:kotlinx-coroutines-android to v1.8.0 2024-02-15 16:52:16 +00:00
Το μοχθηρό ^_^
02476657bf Merge pull request #11 from Malus-risus/renovate/org.jetbrains.kotlinx-kotlinx-coroutines-core-1.x
Update dependency org.jetbrains.kotlinx:kotlinx-coroutines-core to v1.8.0
2024-02-16 00:51:46 +08:00
renovate[bot]
35602120e8 Update dependency org.jetbrains.kotlinx:kotlinx-coroutines-core to v1.8.0 2024-02-15 15:36:59 +00:00
solokot
9e800e08ab Update Russian translation 2024-02-15 08:25:30 +03:00
2dust
09fc20794e Improve and translate 2024-02-14 19:40:51 +08:00
2dust
3cd95fbfdb Improve
https://github.com/2dust/v2rayNG/pull/2839
2024-02-14 18:52:05 +08:00
2dust
c243fadacf Merge pull request #2839 from vfarid/add_fragment_preferences
Added fragment to the preferences settings
2024-02-14 17:31:48 +08:00
Vahid Farid
9c06412ceb Update V2rayConfig.kt 2024-02-14 11:20:25 +03:30
Vahid Farid
8d1f0d5df9 Update ServerConfig.kt 2024-02-14 11:20:20 +03:30
Το μοχθηρό ^_^
a035f42008 Merge pull request #9 from Malus-risus/renovate/android-actions-setup-android-3.x
Update android-actions/setup-android action to v3
2024-02-14 15:40:03 +08:00
Το μοχθηρό ^_^
838fb041aa Merge pull request #8 from Malus-risus/renovate/actions-upload-artifact-4.x
Update actions/upload-artifact action to v4
2024-02-14 15:39:54 +08:00
Το μοχθηρό ^_^
7fbab63227 Merge pull request #7 from Malus-risus/renovate/actions-setup-java-4.x
Update actions/setup-java action to v4
2024-02-14 15:39:43 +08:00
Το μοχθηρό ^_^
ad871723fe Merge pull request #6 from Malus-risus/renovate/actions-setup-go-5.x
Update actions/setup-go action to v5
2024-02-14 15:39:33 +08:00
Το μοχθηρό ^_^
8887b44bf7 Merge pull request #5 from Malus-risus/renovate/actions-checkout-4.x
Update actions/checkout action to v4
2024-02-14 15:39:18 +08:00
Το μοχθηρό ^_^
176beced3a Merge pull request #4 from Malus-risus/renovate/gradle-8.x
Update dependency gradle to v8.6
2024-02-14 15:39:01 +08:00
Vahid Farid
a3561ddc6c Fixed and turened off fragmentOutbound function for future use 2024-02-14 11:06:41 +03:30
renovate[bot]
097bd06021 Update android-actions/setup-android action to v3 2024-02-14 07:30:38 +00:00
renovate[bot]
dfdd1efcc8 Update actions/upload-artifact action to v4 2024-02-14 07:30:35 +00:00
renovate[bot]
1ac5a410d4 Update actions/setup-java action to v4 2024-02-14 07:30:32 +00:00
renovate[bot]
8c44e849c9 Update actions/setup-go action to v5 2024-02-14 07:30:29 +00:00
renovate[bot]
2684bd2af4 Update actions/checkout action to v4 2024-02-14 07:30:26 +00:00
renovate[bot]
01a860aab5 Update dependency gradle to v8.6 2024-02-14 07:30:22 +00:00
Το μοχθηρό ^_^
e1faf4e54e Merge pull request #1 from Malus-risus/renovate/configure
Configure Renovate
2024-02-14 15:13:34 +08:00
renovate[bot]
14dd4d6b99 Add renovate.json 2024-02-14 07:12:20 +00:00
Vahid Farid
bab21bc8a5 Implemented fragment in V2rayConfigUtil.kt 2024-02-13 18:37:48 +03:30
Vahid Farid
8393b3ce86 Added relevant codes to handle fragment settings in SettingsActivity.kt 2024-02-13 18:36:40 +03:30
Vahid Farid
e15eec9cff Added fragment fields to pref_settings.xml 2024-02-13 18:35:46 +03:30
Vahid Farid
32741ed7ab Added PREF_FRAGMENT_* to SettingsViewModel.kt 2024-02-13 16:44:17 +03:30
Vahid Farid
0d77a65bbb Make tag, mux & sockopt writable in V2rayConfig.kt 2024-02-13 16:43:29 +03:30
Vahid Farid
6eaac2d7e9 Added getFragmentOutbound function to ServerConfig.kt 2024-02-13 16:38:27 +03:30
Vahid Farid
496a0ec92c Added PREF_FRAGMENT_* to AppConfig.kt 2024-02-13 16:37:51 +03:30
Vahid Farid
826329e996 Added title_pref_fragment_* to strings.xml 2024-02-13 16:29:39 +03:30
Vahid Farid
4f57da4a38 Added fragment_packets to arrays.xml 2024-02-13 16:28:46 +03:30
2dust
b3f49d0a34 Merge pull request #2836 from vfarid/fix_fragmentBean_add_dialerProxy
fix for SockoptBean & FragmentBean
2024-02-13 10:18:41 +08:00
2dust
b78b370408 Merge pull request #2834 from hamidrezahy/thirdparty_asset_update
Automatic update of third party dat files - fix #2821
2024-02-13 10:09:49 +08:00
Vahid Farid
82afcdddd0 Added dialerProxy to SockoptBean and fixed default values if FragmentBean 2024-02-12 17:51:27 +03:30
hamidreza hematyar
a33a698f38 handle upload file, improve asset ui, fix messages 2024-02-12 15:25:05 +03:30
hamidreza hematyar
3aff1800cd fix download assets functionality, fix strings.xml 2024-02-12 03:16:57 +03:30
hamidreza hematyar
ea816ca981 add asset by url menu, ui and save logic 2024-02-12 00:37:07 +03:30
hamidreza hematyar
a2bace4ede update strings.xml 2024-02-12 00:34:49 +03:30
2dust
5589d1058b Merge pull request #2831 from hamidrezahy/fix_translations
Add missing translations
2024-02-11 09:00:57 +08:00
hamidreza hematyar
a12fc32ff0 Add missing translations 2024-02-10 21:57:21 +03:30
2dust
ce2d1c5e0d Bug fix 2024-02-10 11:59:00 +08:00
2dust
eb75666c85 Merge pull request #2827 from vfarid/master
added support for multiple custom configs in subscriptions + remarks
2024-02-10 09:54:47 +08:00
2dust
4b970cedcc Up build.gradle.kts 2024-02-10 09:51:29 +08:00
Vahid Farid
fbd9d92f5e Update strings.xml 2024-02-10 01:22:07 +03:30
Vahid Farid
cdaff4da06 added support for multiple custom configs in subscriptions + remarks
Accepting both single config and multiple configs i.e:
{customConfig} or [{customConfig1}, {customConfig2} ...]

Furtheremore added an optional 'remarks' field to v2rayConfig class to keep remote customConfig name.
2024-02-09 20:25:51 +03:30
2dust
fbb17390f2 Up build.gradle.kts 2024-02-09 17:38:03 +08:00
2dust
52273db482 build.gradle 2 build.gradle.kts 2024-02-05 19:47:30 +08:00
2dust
a6af25ae88 up 1.8.15 2024-02-04 16:49:12 +08:00
2dust
3ac89d68fb Up implementation 2024-02-04 16:46:01 +08:00
2dust
267a43fd97 Refactor 2024-02-04 12:28:58 +08:00
2dust
6d0384b6f1 Bug fix 2024-02-02 16:47:22 +08:00
2dust
7ae4be402f Merge pull request #2785 from maskedeken/fix-wg
MUX should be disabled when using wireguard
2024-01-25 16:26:41 +08:00
2dust
46f0b7be5b Merge pull request #2783 from admarty/master
Update Vietnamese translation.
2024-01-25 16:26:19 +08:00
Eken Chan
b253f2d947 MUX should be disabled when using wireguard 2024-01-24 16:15:44 +08:00
admarty
444ade8afe Update Vietnamese translation 2024-01-23 19:27:46 +07:00
admarty
522dbdd170 Update Vietnamese translation. 2024-01-23 19:00:37 +07:00
2dust
d89238e1aa up 1.8.14 2024-01-19 18:56:44 +08:00
2dust
52710020ea update subscription 4 custom configuration 2024-01-19 17:44:35 +08:00
2dust
a18b8f4f2b Bug fix 2024-01-19 10:29:43 +08:00
2dust
01e0a6570d Merge pull request #2768 from solokot/master
Update Russian translation
2024-01-19 08:36:22 +08:00
solokot
cd6ef8f062 Update Russian translation
MTU menu
2024-01-18 08:38:45 +03:00
2dust
ea383c43bd Add share 4 Wireguard 2024-01-18 11:56:03 +08:00
2dust
68e08e3866 Merge pull request #2763 from admarty/master
Update vi translation
2024-01-18 09:57:21 +08:00
admarty
329ed26e85 Update vi translation 2024-01-17 12:56:39 +07:00
admarty
c830a4cea4 Revert "Updated vi translation"
This reverts commit e7e088ec83.
2024-01-17 12:53:53 +07:00
admarty
e7e088ec83 Updated vi translation 2024-01-17 12:46:48 +07:00
2dust
cf6c814eb4 Add share 4 Wireguard 2024-01-16 18:49:00 +08:00
2dust
8f62e42ae8 Add Wireguard Mtu 2024-01-16 17:48:04 +08:00
2dust
89b4060ba2 Merge pull request #2748 from admarty/master
Improve vietnamese translation
2024-01-11 17:53:51 +08:00
admarty
e4d4b329a0 fix typo 2024-01-11 13:03:53 +07:00
admarty
09c5f41995 Improve Vietnamese translation 2024-01-11 12:04:57 +07:00
admarty
02b25787c1 Improve Vietnamese translation 2024-01-11 00:48:28 +07:00
145 changed files with 3890 additions and 2141 deletions

View File

@@ -12,21 +12,21 @@ on:
jobs: jobs:
build: build:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@v3 uses: actions/checkout@v4
- name: Setup Java - name: Setup Java
uses: actions/setup-java@v3 uses: actions/setup-java@v4
with: with:
distribution: 'temurin' distribution: 'temurin'
java-version: '17' java-version: '17'
- name: Setup Golang - name: Setup Golang
uses: actions/setup-go@v4 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: |
@@ -35,8 +35,8 @@ jobs:
- name: Setup Android environment - name: Setup Android environment
uses: android-actions/setup-android@v2 uses: android-actions/setup-android@v3
- name: Build dependencies - name: Build dependencies
run: | run: |
@@ -53,12 +53,11 @@ 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
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v4
with: with:
name: apk name: apk
path: ${{ github.workspace }}/V2rayNG/app/build/outputs/apk/debug/ path: ${{ github.workspace }}/V2rayNG/app/build/outputs/apk/debug/

View File

@@ -1,140 +0,0 @@
plugins {
id 'com.android.application'
id 'org.jetbrains.kotlin.android'
}
android {
compileSdkVersion Integer.parseInt("$compileSdkVer")
buildToolsVersion "$buildToolsVer"
compileOptions {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
defaultConfig {
applicationId "com.v2ray.ang"
minSdkVersion 21
targetSdkVersion Integer.parseInt("$targetSdkVer")
multiDexEnabled true
versionCode 538
versionName "1.8.13"
}
buildTypes {
release {
minifyEnabled false
zipAlignEnabled false
shrinkResources false
ndk.abiFilters 'x86', 'x86_64', 'armeabi-v7a', 'arm64-v8a'
}
debug {
minifyEnabled false
zipAlignEnabled false
shrinkResources false
ndk.abiFilters 'x86', 'x86_64', 'armeabi-v7a', 'arm64-v8a'
}
}
// flavorDimensions "versions"
//
// productFlavors {
// dev {
// applicationIdSuffix = ".dev"
// versionNameSuffix = "-dev"
// }
// pre_release {
// applicationIdSuffix = ".pre"
// versionNameSuffix = "-pre-release"
// }
// prod {
// }
// }
sourceSets {
main {
jniLibs.srcDirs = ['libs']
java.srcDirs += 'src/main/kotlin'
}
}
kotlinOptions {
jvmTarget = JavaVersion.VERSION_1_8
}
splits {
abi {
enable true
reset()
include 'x86', 'x86_64', 'armeabi-v7a', 'arm64-v8a' //select ABIs to build APKs for
universalApk true //generate an additional APK that contains all the ABIs
}
}
// map for the version code
project.ext.versionCodes = ['armeabi-v7a': 1, 'arm64-v8a': 2, 'x86': 3, 'x86_64': 4]
android.applicationVariants.all { variant ->
// assign different version code for each output
variant.outputs.each { output ->
output.outputFileName = "v2rayNG_" + variant.versionName + "_" + output.getFilter(com.android.build.OutputFile.ABI) + ".apk"
output.versionCodeOverride =
project.ext.versionCodes.get(output.getFilter(com.android.build.OutputFile.ABI), 0) *
1000000 + android.defaultConfig.versionCode
}
}
buildFeatures {
viewBinding true
buildConfig true
}
namespace 'com.v2ray.ang'
testNamespace 'com.v2ray.angTest'
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.aar', '*.jar'], exclude: [])
testImplementation 'junit:junit:4.13.2'
// Androidx
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
implementation 'androidx.appcompat:appcompat:1.6.1'
implementation 'com.google.android.material:material:1.9.0'
implementation 'androidx.cardview:cardview:1.0.0'
implementation 'androidx.preference:preference-ktx:1.2.0'
implementation 'androidx.recyclerview:recyclerview:1.3.0'
implementation 'androidx.fragment:fragment-ktx:1.5.7'
implementation 'androidx.multidex:multidex:2.0.1'
implementation 'androidx.viewpager2:viewpager2:1.1.0-beta01'
// Androidx ktx
implementation 'androidx.activity:activity-ktx:1.7.1'
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.1'
implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.6.1'
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.6.1'
//kotlin
implementation "org.jetbrains.kotlin:kotlin-reflect:1.8.0"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4"
implementation 'com.tencent:mmkv-static:1.2.15'
implementation 'com.google.code.gson:gson:2.10.1'
implementation 'io.reactivex:rxjava:1.3.8'
implementation 'io.reactivex:rxandroid:1.2.1'
implementation 'com.tbruyelle.rxpermissions:rxpermissions:0.9.4@aar'
implementation 'com.github.jorgecastilloprz:fabprogresscircle:1.01@aar'
implementation 'me.drakeet.support:toastcompat:1.1.0'
implementation 'com.blacksquircle.ui:editorkit:2.8.0'
implementation 'com.blacksquircle.ui:language-base:2.8.0'
implementation 'com.blacksquircle.ui:language-json:2.8.0'
implementation 'io.github.g00fy2.quickie:quickie-bundled:1.6.0'
implementation 'com.google.zxing:core:3.5.1'
def work_version = "2.8.1"
implementation "androidx.work:work-runtime-ktx:$work_version"
implementation "androidx.work:work-multiprocess:$work_version"
}

View File

@@ -0,0 +1,132 @@
plugins {
id("com.android.application")
id("org.jetbrains.kotlin.android")
}
android {
namespace = "com.v2ray.ang"
compileSdk = 34
defaultConfig {
applicationId = "com.v2ray.ang"
minSdk = 21
targetSdk = 34
versionCode = 562
versionName = "1.8.25"
multiDexEnabled = true
splits.abi {
reset()
include(
"arm64-v8a",
"armeabi-v7a",
"x86_64",
"x86"
)
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
buildTypes {
release {
isMinifyEnabled = false
}
debug {
isMinifyEnabled = false
}
}
sourceSets {
getByName("main") {
jniLibs.srcDirs("libs")
}
}
kotlinOptions {
jvmTarget = JavaVersion.VERSION_1_8.toString()
}
splits {
abi {
isEnable = true
isUniversalApk = false
}
}
applicationVariants.all {
val variant = this
val versionCodes =
mapOf("armeabi-v7a" to 1, "arm64-v8a" to 2, "x86" to 3, "x86_64" to 4)
variant.outputs
.map { it as com.android.build.gradle.internal.api.ApkVariantOutputImpl }
.forEach { output ->
val abi = if (output.getFilter("ABI") != null)
output.getFilter("ABI")
else
"all"
output.outputFileName = "v2rayNG_${variant.versionName}_${abi}.apk"
if(versionCodes.containsKey(abi))
{
output.versionCodeOverride = (1000000 * versionCodes[abi]!!).plus(variant.versionCode)
}
else
{
return@forEach
}
}
}
buildFeatures {
viewBinding = true
buildConfig = true
}
}
dependencies {
implementation(fileTree(mapOf("dir" to "libs", "include" to listOf("*.aar","*.jar"))))
testImplementation("junit:junit:4.13.2")
// Androidx
implementation("androidx.constraintlayout:constraintlayout:2.1.4")
implementation("androidx.legacy:legacy-support-v4:1.0.0")
implementation("androidx.appcompat:appcompat:1.6.1")
implementation("com.google.android.material:material:1.12.0")
implementation("androidx.cardview:cardview:1.0.0")
implementation("androidx.preference:preference-ktx:1.2.1")
implementation("androidx.recyclerview:recyclerview:1.3.2")
implementation("androidx.fragment:fragment-ktx:1.7.1")
implementation("androidx.multidex:multidex:2.0.1")
implementation("androidx.viewpager2:viewpager2:1.1.0")
// Androidx ktx
implementation("androidx.activity:activity-ktx:1.9.0")
implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.8.0")
implementation("androidx.lifecycle:lifecycle-livedata-ktx:2.8.0")
implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.8.0")
//kotlin
implementation("org.jetbrains.kotlin:kotlin-reflect:1.9.23")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.8.0")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.8.0")
implementation("com.tencent:mmkv-static:1.3.4")
implementation("com.google.code.gson:gson:2.10.1")
implementation("io.reactivex:rxjava:1.3.8")
implementation("io.reactivex:rxandroid:1.2.1")
implementation("com.tbruyelle.rxpermissions:rxpermissions:0.9.4@aar")
implementation("me.drakeet.support:toastcompat:1.1.0")
implementation("com.blacksquircle.ui:editorkit:2.9.0")
implementation("com.blacksquircle.ui:language-base:2.9.0")
implementation("com.blacksquircle.ui:language-json:2.9.0")
implementation("io.github.g00fy2.quickie:quickie-bundled:1.9.0")
implementation("com.google.zxing:core:3.5.3")
implementation("androidx.work:work-runtime-ktx:2.8.1")
implementation("androidx.work:work-multiprocess:2.8.1")
}

View File

@@ -93,6 +93,9 @@
<activity <activity
android:exported="false" android:exported="false"
android:name=".ui.UserAssetActivity" /> android:name=".ui.UserAssetActivity" />
<activity
android:exported="false"
android:name=".ui.UserAssetUrlActivity" />
<activity <activity
android:exported="false" android:exported="false"
@@ -124,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"
@@ -221,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

@@ -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,46 +7,71 @@ 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 HTTP_PROTOCOL: String = "http://" const val PREF_FRAGMENT_ENABLED = "pref_fragment_enabled"
const val HTTPS_PROTOCOL: String = "https://" const val PREF_FRAGMENT_PACKETS = "pref_fragment_packets"
const val PREF_FRAGMENT_LENGTH = "pref_fragment_length"
const val PREF_FRAGMENT_INTERVAL = "pref_fragment_interval"
const val SUBSCRIPTION_AUTO_UPDATE = "pref_auto_update_subscription"
const val SUBSCRIPTION_AUTO_UPDATE_INTERVAL = "pref_auto_update_interval"
const val SUBSCRIPTION_DEFAULT_UPDATE_INTERVAL = "1440" // 24 hours
const val SUBSCRIPTION_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_HTTPS: String = "https://"
const val PROTOCOL_FREEDOM: String = "freedom"
const val BROADCAST_ACTION_SERVICE = "com.v2ray.ang.action.service" const val BROADCAST_ACTION_SERVICE = "com.v2ray.ang.action.service"
const val BROADCAST_ACTION_ACTIVITY = "com.v2ray.ang.action.activity" const val BROADCAST_ACTION_ACTIVITY = "com.v2ray.ang.action.activity"
@@ -58,24 +83,34 @@ 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 androidpackagenamelistUrl = "https://raw.githubusercontent.com/2dust/androidpackagenamelist/master/proxy.txt" const val androidpackagenamelistUrl =
const val v2rayCustomRoutingListUrl = "https://raw.githubusercontent.com/2dust/v2rayCustomRoutingList/master/" "https://raw.githubusercontent.com/2dust/androidpackagenamelist/master/proxy.txt"
const val v2rayNGIssues = "https://github.com/2dust/v2rayNG/issues" const val v2rayCustomRoutingListUrl =
const val v2rayNGWikiMode = "https://github.com/2dust/v2rayNG/wiki/Mode" "https://raw.githubusercontent.com/2dust/v2rayCustomRoutingList/master/"
const val v2rayNGUrl = "https://github.com/2dust/v2rayNG"
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 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"
const val PORT_HTTP = "10809" const val PORT_HTTP = "10809"
const val WIREGUARD_LOCAL_ADDRESS_V4 = "172.16.0.2/32"
const val WIREGUARD_LOCAL_ADDRESS_V6 = "2606:4700:110:8f81:d551:a0:532e:a2b3/128"
const val WIREGUARD_LOCAL_MTU = "1420"
const val MSG_REGISTER_CLIENT = 1 const val MSG_REGISTER_CLIENT = 1
const val MSG_STATE_RUNNING = 11 const val MSG_STATE_RUNNING = 11
@@ -92,13 +127,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

@@ -0,0 +1,8 @@
package com.v2ray.ang.dto
data class AssetUrlItem(
var remarks: String = "",
var url: String = "",
val addedTime: Long = System.currentTimeMillis(),
var lastUpdated: Long = -1
)

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

@@ -8,4 +8,5 @@ data class SubscriptionItem(
var lastUpdated: Long = -1, var lastUpdated: Long = -1,
var autoUpdate: Boolean = false, var autoUpdate: Boolean = false,
val updateInterval: Int? = null, val updateInterval: Int? = null,
) )

View File

@@ -10,6 +10,7 @@ import com.google.gson.reflect.TypeToken
import java.lang.reflect.Type import java.lang.reflect.Type
data class V2rayConfig( data class V2rayConfig(
var remarks: String? = null,
var stats: Any? = null, var stats: Any? = null,
val log: LogBean, val log: LogBean,
var policy: PolicyBean?, var policy: PolicyBean?,
@@ -21,7 +22,9 @@ data class V2rayConfig(
val transport: Any? = null, val transport: Any? = null,
val reverse: Any? = null, val reverse: Any? = null,
var fakedns: Any? = null, var fakedns: Any? = null,
val browserForwarder: Any? = null) { val browserForwarder: Any? = null,
var observatory: Any? = null,
var burstObservatory: Any? = null) {
companion object { companion object {
const val DEFAULT_PORT = 443 const val DEFAULT_PORT = 443
const val DEFAULT_SECURITY = "auto" const val DEFAULT_SECURITY = "auto"
@@ -57,18 +60,20 @@ 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(val tag: String = "proxy", data class OutboundBean(var tag: String = "proxy",
var protocol: String, var protocol: String,
var settings: OutSettingsBean? = null, var settings: OutSettingsBean? = null,
var streamSettings: StreamSettingsBean? = null, var streamSettings: StreamSettingsBean? = null,
val proxySettings: Any? = null, val proxySettings: Any? = null,
val sendThrough: String? = null, val sendThrough: String? = null,
val mux: MuxBean? = MuxBean(false)) { var mux: MuxBean? = MuxBean(false)) {
data class OutSettingsBean(var vnext: List<VnextBean>? = null, data class OutSettingsBean(var vnext: List<VnextBean>? = null,
var fragment: FragmentBean? = null,
var servers: List<ServersBean>? = null, var servers: List<ServersBean>? = null,
/*Blackhole*/ /*Blackhole*/
var response: Response? = null, var response: Response? = null,
@@ -86,6 +91,7 @@ data class V2rayConfig(
var secretKey: String? = null, var secretKey: String? = null,
val peers: List<WireGuardBean>? = null, val peers: List<WireGuardBean>? = null,
var reserved: List<Int>? = null, var reserved: List<Int>? = null,
var mtu :Int? = null
) { ) {
data class VnextBean(var address: String = "", data class VnextBean(var address: String = "",
@@ -100,6 +106,10 @@ data class V2rayConfig(
var flow: String = "") var flow: String = "")
} }
data class FragmentBean(var packets: String? = null,
var length: String? = null,
var interval: String? = null)
data class ServersBean(var address: String = "", data class ServersBean(var address: String = "",
var method: String = "chacha20-poly1305", var method: String = "chacha20-poly1305",
var ota: Boolean = false, var ota: Boolean = false,
@@ -128,13 +138,14 @@ data class V2rayConfig(
var tcpSettings: TcpSettingsBean? = null, var tcpSettings: TcpSettingsBean? = null,
var kcpSettings: KcpSettingsBean? = null, var kcpSettings: KcpSettingsBean? = null,
var wsSettings: WsSettingsBean? = null, var wsSettings: WsSettingsBean? = null,
var httpupgradeSettings: HttpupgradeSettingsBean? = 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,
var realitySettings: TlsSettingsBean? = null, var realitySettings: TlsSettingsBean? = null,
var grpcSettings: GrpcSettingsBean? = null, var grpcSettings: GrpcSettingsBean? = null,
val dsSettings: Any? = null, val dsSettings: Any? = null,
val sockopt: Any? = null var sockopt: SockoptBean? = null
) { ) {
data class TcpSettingsBean(var header: HeaderBean = HeaderBean(), data class TcpSettingsBean(var header: HeaderBean = HeaderBean(),
@@ -177,9 +188,20 @@ data class V2rayConfig(
data class HeadersBean(var Host: String = "") data class HeadersBean(var Host: String = "")
} }
data class HttpupgradeSettingsBean(var path: String = "",
var host: String = "",
val acceptProxyProtocol: Boolean? = null)
data class HttpSettingsBean(var host: List<String> = ArrayList(), data class HttpSettingsBean(var host: List<String> = ArrayList(),
var path: String = "") var path: String = "")
data class SockoptBean(var TcpNoDelay: Boolean? = null,
var tcpKeepAliveIdle: Int? = null,
var tcpFastOpen: Boolean? = null,
var tproxy: String? = null,
var mark: Int? = null,
var dialerProxy: String? = null)
data class TlsSettingsBean(var allowInsecure: Boolean = false, data class TlsSettingsBean(var allowInsecure: Boolean = false,
var serverName: String = "", var serverName: String = "",
val alpn: List<String>? = null, val alpn: List<String>? = null,
@@ -204,10 +226,15 @@ data class V2rayConfig(
} }
data class GrpcSettingsBean(var serviceName: String = "", data class GrpcSettingsBean(var serviceName: String = "",
var multiMode: Boolean? = null) var authority: String? = null,
var multiMode: Boolean? = null,
var idle_timeout: Int? = null,
var health_check_timeout: Int? = null
)
fun populateTransportSettings(transport: String, headerType: String?, host: String?, path: String?, seed: String?, fun populateTransportSettings(transport: String, headerType: String?, host: String?, path: String?, seed: String?,
quicSecurity: String?, key: String?, mode: String?, serviceName: String?): String { quicSecurity: String?, key: String?, mode: String?, serviceName: String?,
authority: String?): String {
var sni = "" var sni = ""
network = transport network = transport
when (network) { when (network) {
@@ -245,6 +272,13 @@ data class V2rayConfig(
wssetting.path = path ?: "/" wssetting.path = path ?: "/"
wsSettings = wssetting wsSettings = wssetting
} }
"httpupgrade" -> {
val httpupgradeSetting = HttpupgradeSettingsBean()
httpupgradeSetting.host = host ?: ""
sni = httpupgradeSetting.host
httpupgradeSetting.path = path ?: "/"
httpupgradeSettings = httpupgradeSetting
}
"h2", "http" -> { "h2", "http" -> {
network = "h2" network = "h2"
val h2Setting = HttpSettingsBean() val h2Setting = HttpSettingsBean()
@@ -264,7 +298,10 @@ data class V2rayConfig(
val grpcSetting = GrpcSettingsBean() val grpcSetting = GrpcSettingsBean()
grpcSetting.multiMode = mode == "multi" grpcSetting.multiMode = mode == "multi"
grpcSetting.serviceName = serviceName ?: "" grpcSetting.serviceName = serviceName ?: ""
sni = host ?: "" grpcSetting.authority = authority ?: ""
grpcSetting.idle_timeout = 60
grpcSetting.health_check_timeout = 20
sni = authority ?: ""
grpcSettings = grpcSetting grpcSettings = grpcSetting
} }
} }
@@ -353,7 +390,8 @@ data class V2rayConfig(
fun getTransportSettingDetails(): List<String>? { fun getTransportSettingDetails(): List<String>? {
if (protocol.equals(EConfigType.VMESS.name, true) if (protocol.equals(EConfigType.VMESS.name, true)
|| protocol.equals(EConfigType.VLESS.name, true) || protocol.equals(EConfigType.VLESS.name, true)
|| protocol.equals(EConfigType.TROJAN.name, true)) { || protocol.equals(EConfigType.TROJAN.name, true)
|| protocol.equals(EConfigType.SHADOWSOCKS.name, true)) {
val transport = streamSettings?.network ?: return null val transport = streamSettings?.network ?: return null
return when (transport) { return when (transport) {
"tcp" -> { "tcp" -> {
@@ -374,6 +412,12 @@ data class V2rayConfig(
wsSetting.headers.Host, wsSetting.headers.Host,
wsSetting.path) wsSetting.path)
} }
"httpupgrade" -> {
val httpupgradeSetting = streamSettings?.httpupgradeSettings ?: return null
listOf("",
httpupgradeSetting.host,
httpupgradeSetting.path)
}
"h2" -> { "h2" -> {
val h2Setting = streamSettings?.httpSettings ?: return null val h2Setting = streamSettings?.httpSettings ?: return null
listOf("", listOf("",
@@ -389,7 +433,7 @@ data class V2rayConfig(
"grpc" -> { "grpc" -> {
val grpcSetting = streamSettings?.grpcSettings ?: return null val grpcSetting = streamSettings?.grpcSettings ?: return null
listOf(if (grpcSetting.multiMode == true) "multi" else "gun", listOf(if (grpcSetting.multiMode == true) "multi" else "gun",
"", grpcSetting.authority ?: "",
grpcSetting.serviceName) grpcSetting.serviceName)
} }
else -> null else -> null
@@ -418,7 +462,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 = "",
@@ -451,8 +495,8 @@ data class V2rayConfig(
var poolSize: Int = 10000) // roughly 10 times smaller than total ip pool var poolSize: Int = 10000) // roughly 10 times smaller than total ip pool
fun getProxyOutbound(): OutboundBean? { fun getProxyOutbound(): OutboundBean? {
outbounds?.forEach { outbound -> outbounds.forEach { outbound ->
EConfigType.values().forEach { EConfigType.entries.forEach {
if (outbound.protocol.equals(it.name, true)) { if (outbound.protocol.equals(it.name, true)) {
return outbound return outbound
} }

View File

@@ -9,72 +9,61 @@ import org.json.JSONObject
import java.net.URI import java.net.URI
import java.net.URLConnection import java.net.URLConnection
/**
* Some extensions
*/
val Context.v2RayApplication: AngApplication val Context.v2RayApplication: AngApplication
get() = applicationContext as AngApplication get() = applicationContext as AngApplication
fun Context.toast(message: Int): Toast = ToastCompat fun Context.toast(message: Int) {
.makeText(this, message, Toast.LENGTH_SHORT) ToastCompat.makeText(this, message, Toast.LENGTH_SHORT).apply { show() }
.apply {
show()
}
fun Context.toast(message: CharSequence): Toast = ToastCompat
.makeText(this, message, Toast.LENGTH_SHORT)
.apply {
show()
}
fun JSONObject.putOpt(pair: Pair<String, Any>) = putOpt(pair.first, pair.second)
fun JSONObject.putOpt(pairs: Map<String, Any>) = pairs.forEach { putOpt(it.key to it.value) }
const val threshold = 1000
const val divisor = 1024F
fun Long.toSpeedString() = toTrafficString() + "/s"
fun Long.toTrafficString(): String {
if (this == 0L)
return "\t\t\t0\t B"
if (this < threshold)
return "${this.toFloat().toShortString()}\t B"
val kib = this / divisor
if (kib < threshold)
return "${kib.toShortString()}\t KB"
val mib = kib / divisor
if (mib < threshold)
return "${mib.toShortString()}\t MB"
val gib = mib / divisor
if (gib < threshold)
return "${gib.toShortString()}\t GB"
val tib = gib / divisor
if (tib < threshold)
return "${tib.toShortString()}\t TB"
val pib = tib / divisor
if (pib < threshold)
return "${pib.toShortString()}\t PB"
return ""
} }
private fun Float.toShortString(): String { fun Context.toast(message: CharSequence) {
val s = "%.2f".format(this) ToastCompat.makeText(this, message, Toast.LENGTH_SHORT).apply { show() }
if (s.length <= 4) }
return s
return s.substring(0, 4).removeSuffix(".") fun JSONObject.putOpt(pair: Pair<String, Any?>) {
put(pair.first, pair.second)
}
fun JSONObject.putOpt(pairs: Map<String, Any?>) {
pairs.forEach { put(it.key, it.value) }
}
const val THRESHOLD = 1000L
const val DIVISOR = 1024.0
fun Long.toSpeedString(): String = this.toTrafficString() + "/s"
fun Long.toTrafficString(): String {
if (this < THRESHOLD) {
return "$this B"
}
val kb = this / DIVISOR
if (kb < THRESHOLD) {
return "${String.format("%.1f KB", kb)}"
}
val mb = kb / DIVISOR
if (mb < THRESHOLD) {
return "${String.format("%.1f MB", mb)}"
}
val gb = mb / DIVISOR
if (gb < THRESHOLD) {
return "${String.format("%.1f GB", gb)}"
}
val tb = gb / DIVISOR
if (tb < THRESHOLD) {
return "${String.format("%.1f TB", tb)}"
}
return String.format("%.1f PB", tb / DIVISOR)
} }
val URLConnection.responseLength: Long val URLConnection.responseLength: Long
get() = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) contentLengthLong else contentLength.toLong() get() = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
contentLengthLong
} else {
contentLength.toLong()
}
val URI.idnHost: String val URI.idnHost: String
get() = (host!!).replace("[", "").replace("]", "") get() = host?.replace("[", "")?.replace("]", "") ?: ""
fun String.removeWhiteSpace(): String = replace("\\s+".toRegex(), "")

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

@@ -73,5 +73,8 @@ object SubscriptionUpdater {
if (count <= 0) { if (count <= 0) {
AngConfigManager.importBatchConfig(Utils.decode(server!!), subid, append) 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
@@ -234,7 +237,7 @@ 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"
@@ -290,7 +293,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()
@@ -368,7 +371,7 @@ object V2RayServiceManager {
} }
val directUplink = v2rayPoint.queryStats(TAG_DIRECT, "uplink") val directUplink = v2rayPoint.queryStats(TAG_DIRECT, "uplink")
val directDownlink = v2rayPoint.queryStats(TAG_DIRECT, "downlink") val directDownlink = v2rayPoint.queryStats(TAG_DIRECT, "downlink")
val zeroSpeed = (proxyTotal == 0L && directUplink == 0L && directDownlink == 0L) val zeroSpeed = proxyTotal == 0L && directUplink == 0L && directDownlink == 0L
if (!zeroSpeed || !lastZeroSpeed) { if (!zeroSpeed || !lastZeroSpeed) {
if (proxyTotal == 0L) { if (proxyTotal == 0L) {
appendSpeedString(text, outboundTags?.firstOrNull(), 0.0, 0.0) appendSpeedString(text, outboundTags?.firstOrNull(), 0.0, 0.0)

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,48 +1,52 @@
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 com.v2ray.ang.AppConfig
import android.content.res.ColorStateList
import android.os.Build
import com.google.android.material.navigation.NavigationView
import androidx.core.content.ContextCompat
import androidx.core.view.GravityCompat
import androidx.appcompat.app.ActionBarDrawerToggle
import androidx.recyclerview.widget.ItemTouchHelper
import android.util.Log import android.util.Log
import android.view.KeyEvent
import android.view.Menu
import android.view.MenuItem
import android.widget.Toast import android.widget.Toast
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
import com.v2ray.ang.AppConfig.ANG_PACKAGE import com.v2ray.ang.AppConfig.ANG_PACKAGE
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.dto.EConfigType import com.v2ray.ang.dto.EConfigType
import com.v2ray.ang.extension.toast import com.v2ray.ang.extension.toast
import rx.Observable
import rx.android.schedulers.AndroidSchedulers
import java.util.concurrent.TimeUnit
import com.v2ray.ang.helper.SimpleItemTouchHelperCallback import com.v2ray.ang.helper.SimpleItemTouchHelperCallback
import com.v2ray.ang.service.V2RayServiceManager import com.v2ray.ang.service.V2RayServiceManager
import com.v2ray.ang.util.* 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 com.v2ray.ang.viewmodel.MainViewModel
import kotlinx.coroutines.* import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import me.drakeet.support.toast.ToastCompat import me.drakeet.support.toast.ToastCompat
import rx.Observable
import rx.android.schedulers.AndroidSchedulers
import java.io.File import java.io.File
import java.io.FileOutputStream import java.io.FileOutputStream
import java.util.concurrent.TimeUnit
class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedListener { class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedListener {
private lateinit var binding: ActivityMainBinding private lateinit var binding: ActivityMainBinding
@@ -69,7 +73,7 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
binding.fab.setOnClickListener { binding.fab.setOnClickListener {
if (mainViewModel.isRunning.value == true) { if (mainViewModel.isRunning.value == true) {
Utils.stopVService(this) Utils.stopVService(this)
} else if (settingsStorage?.decodeString(AppConfig.PREF_MODE) ?: "VPN" == "VPN") { } else if ((settingsStorage?.decodeString(AppConfig.PREF_MODE) ?: "VPN") == "VPN") {
val intent = VpnService.prepare(this) val intent = VpnService.prepare(this)
if (intent == null) { if (intent == null) {
startV2Ray() startV2Ray()
@@ -103,11 +107,11 @@ 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() copyAssets()
migrateLegacy() //migrateLegacy()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
RxPermissions(this) RxPermissions(this)
@@ -117,6 +121,17 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
toast(R.string.toast_permission_denied) toast(R.string.toast_permission_denied)
} }
} }
onBackPressedDispatcher.addCallback(this, object : OnBackPressedCallback(true) {
override fun handleOnBackPressed() {
if (binding.drawerLayout.isDrawerOpen(GravityCompat.START)) {
binding.drawerLayout.closeDrawer(GravityCompat.START)
} else {
//super.onBackPressed()
onBackPressedDispatcher.onBackPressed()
}
}
})
} }
private fun setupViewModel() { private fun setupViewModel() {
@@ -131,21 +146,16 @@ 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()
} }
@@ -173,30 +183,27 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
} }
} }
private fun migrateLegacy() { // private fun migrateLegacy() {
lifecycleScope.launch(Dispatchers.IO) { // lifecycleScope.launch(Dispatchers.IO) {
val result = AngConfigManager.migrateLegacyConfig(this@MainActivity) // val result = AngConfigManager.migrateLegacyConfig(this@MainActivity)
if (result != null) { // if (result != null) {
launch(Dispatchers.Main) { // launch(Dispatchers.Main) {
if (result) { // if (result) {
toast(getString(R.string.migration_success)) // toast(getString(R.string.migration_success))
mainViewModel.reloadServerList() // mainViewModel.reloadServerList()
} else { // } else {
toast(getString(R.string.migration_fail)) // 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() {
@@ -314,6 +321,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
} }
@@ -322,6 +332,9 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
.setPositiveButton(android.R.string.ok) { _, _ -> .setPositiveButton(android.R.string.ok) { _, _ ->
mainViewModel.removeDuplicateServer() mainViewModel.removeDuplicateServer()
} }
.setNegativeButton(android.R.string.no) {_, _ ->
//do noting
}
.show() .show()
true true
} }
@@ -331,6 +344,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
} }
@@ -419,6 +435,9 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
if (count <= 0) { if (count <= 0) {
count = AngConfigManager.importBatchConfig(Utils.decode(server!!), subid2, append) count = AngConfigManager.importBatchConfig(Utils.decode(server!!), subid2, append)
} }
if (count <= 0) {
count = AngConfigManager.appendCustomConfigServer(server, subid2)
}
if (count > 0) { if (count > 0) {
toast(R.string.toast_success) toast(R.string.toast_success)
mainViewModel.reloadServerList() mainViewModel.reloadServerList()
@@ -521,10 +540,22 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
} }
Log.d(ANG_PACKAGE, url) Log.d(ANG_PACKAGE, url)
lifecycleScope.launch(Dispatchers.IO) { lifecycleScope.launch(Dispatchers.IO) {
val configText = try { var configText = try {
Utils.getUrlContentWithCustomUserAgent(url) Utils.getUrlContentWithCustomUserAgent(url)
} catch (e: Exception) { } catch (e: Exception) {
e.printStackTrace() e.printStackTrace()
""
}
if(configText.isEmpty()) {
configText = try {
val httpPort = Utils.parseInt(settingsStorage?.decodeString(AppConfig.PREF_HTTP_PORT), AppConfig.PORT_HTTP.toInt())
Utils.getUrlContentWithCustomUserAgent(url, httpPort)
} catch (e: Exception) {
e.printStackTrace()
""
}
}
if(configText.isEmpty()) {
launch(Dispatchers.Main) { launch(Dispatchers.Main) {
toast("\"" + it.second.remarks + "\" " + getString(R.string.toast_failure)) toast("\"" + it.second.remarks + "\" " + getString(R.string.toast_failure))
} }
@@ -630,39 +661,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())
}
}
@Deprecated("Deprecated in Java")
override fun onBackPressed() {
if (binding.drawerLayout.isDrawerOpen(GravityCompat.START)) {
binding.drawerLayout.closeDrawer(GravityCompat.START)
} else {
//super.onBackPressed()
onBackPressedDispatcher.onBackPressed()
}
}
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) {
@@ -677,17 +675,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
@@ -72,9 +73,9 @@ class MainRecyclerAdapter(val activity: MainActivity) : RecyclerView.Adapter<Mai
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)
@@ -144,10 +145,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)
} }
} }
@@ -160,13 +166,11 @@ class MainRecyclerAdapter(val activity: MainActivity) : RecyclerView.Adapter<Mai
} }
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 +182,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,21 +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.GlobalScope
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)
@@ -44,14 +43,14 @@ 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())
.map { .map {
if (blacklist != null) { if (blacklist != null) {
it.forEach { one -> it.forEach { one ->
if ((blacklist.contains(one.packageName))) { if (blacklist.contains(one.packageName)) {
one.isSelected = 1 one.isSelected = 1
} else { } else {
one.isSelected = 0 one.isSelected = 0
@@ -135,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 ->
@@ -178,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)
} }
} }

View File

@@ -1,7 +1,7 @@
package com.v2ray.ang.ui package com.v2ray.ang.ui
import android.Manifest import android.Manifest
import android.app.Activity.RESULT_OK import androidx.appcompat.app.AppCompatActivity.RESULT_OK
import android.content.Intent import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.text.TextUtils import android.text.TextUtils
@@ -11,14 +11,15 @@ 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.GlobalScope
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
class RoutingSettingsFragment : Fragment() { class RoutingSettingsFragment : Fragment() {
@@ -27,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? {
@@ -47,7 +48,7 @@ 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)
@@ -84,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)
} }
@@ -128,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

View File

@@ -1,7 +1,7 @@
package com.v2ray.ang.ui package com.v2ray.ang.ui
import android.Manifest import android.Manifest
import android.app.Activity import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle import android.os.Bundle
import android.content.Intent import android.content.Intent
import android.graphics.BitmapFactory import android.graphics.BitmapFactory
@@ -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()
} }
@@ -54,7 +54,7 @@ class ScannerActivity : BaseActivity(){
private fun finished(text: String) { private fun finished(text: String) {
val intent = Intent() val intent = Intent()
intent.putExtra("SCAN_RESULT", text) intent.putExtra("SCAN_RESULT", text)
setResult(Activity.RESULT_OK, intent) setResult(AppCompatActivity.RESULT_OK, intent)
finish() finish()
} }

View File

@@ -8,14 +8,19 @@ import android.view.View
import android.widget.* import android.widget.*
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import com.tencent.mmkv.MMKV import com.tencent.mmkv.MMKV
import com.v2ray.ang.AngApplication
import com.v2ray.ang.AppConfig import com.v2ray.ang.AppConfig
import com.v2ray.ang.AppConfig.PREF_ALLOW_INSECURE import com.v2ray.ang.AppConfig.PREF_ALLOW_INSECURE
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_MTU
import com.v2ray.ang.R import com.v2ray.ang.R
import com.v2ray.ang.dto.EConfigType import com.v2ray.ang.dto.EConfigType
import com.v2ray.ang.dto.ServerConfig import com.v2ray.ang.dto.ServerConfig
import com.v2ray.ang.dto.V2rayConfig import com.v2ray.ang.dto.V2rayConfig
import com.v2ray.ang.dto.V2rayConfig.Companion.DEFAULT_PORT import com.v2ray.ang.dto.V2rayConfig.Companion.DEFAULT_PORT
import com.v2ray.ang.dto.V2rayConfig.Companion.TLS import com.v2ray.ang.dto.V2rayConfig.Companion.TLS
import com.v2ray.ang.extension.removeWhiteSpace
import com.v2ray.ang.extension.toast import com.v2ray.ang.extension.toast
import com.v2ray.ang.util.MmkvManager import com.v2ray.ang.util.MmkvManager
import com.v2ray.ang.util.MmkvManager.ID_MAIN import com.v2ray.ang.util.MmkvManager.ID_MAIN
@@ -26,7 +31,12 @@ import com.v2ray.ang.util.Utils.getIpv6Address
class ServerActivity : BaseActivity() { class ServerActivity : BaseActivity() {
private val mainStorage by lazy { MMKV.mmkvWithID(ID_MAIN, MMKV.MULTI_PROCESS_MODE) } private val mainStorage by lazy { MMKV.mmkvWithID(ID_MAIN, MMKV.MULTI_PROCESS_MODE) }
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
)
}
private val editGuid by lazy { intent.getStringExtra("guid").orEmpty() } private val editGuid by lazy { intent.getStringExtra("guid").orEmpty() }
private val isRunning by lazy { private val isRunning by lazy {
intent.getBooleanExtra("isRunning", false) intent.getBooleanExtra("isRunning", false)
@@ -34,7 +44,8 @@ class ServerActivity : BaseActivity() {
&& editGuid == mainStorage?.decodeString(KEY_SELECTED_SERVER) && editGuid == mainStorage?.decodeString(KEY_SELECTED_SERVER)
} }
private val createConfigType by lazy { private val createConfigType by lazy {
EConfigType.fromInt(intent.getIntExtra("createConfigType", EConfigType.VMESS.value)) ?: EConfigType.VMESS EConfigType.fromInt(intent.getIntExtra("createConfigType", EConfigType.VMESS.value))
?: EConfigType.VMESS
} }
private val subscriptionId by lazy { private val subscriptionId by lazy {
intent.getStringExtra("subscriptionId") intent.getStringExtra("subscriptionId")
@@ -67,12 +78,13 @@ class ServerActivity : BaseActivity() {
private val allowinsecures: Array<out String> by lazy { private val allowinsecures: Array<out String> by lazy {
resources.getStringArray(R.array.allowinsecures) resources.getStringArray(R.array.allowinsecures)
} }
private val uTlsItems: Array<out String> by lazy { private val uTlsItems: Array<out String> by lazy {
resources.getStringArray(R.array.streamsecurity_utls) resources.getStringArray(R.array.streamsecurity_utls)
} }
private val alpns: Array<out String> by lazy { private val alpns: Array<out String> by lazy {
resources.getStringArray(R.array.streamsecurity_alpn) resources.getStringArray(R.array.streamsecurity_alpn)
} }
// Kotlin synthetics was used, but since it is removed in 1.8. We switch to old manual approach. // Kotlin synthetics was used, but since it is removed in 1.8. We switch to old manual approach.
// We don't use AndroidViewBinding because, it is better to share similar logics for different // We don't use AndroidViewBinding because, it is better to share similar logics for different
// protocols. Use findViewById manually ensures the xml are de-coupled with the activity logic. // protocols. Use findViewById manually ensures the xml are de-coupled with the activity logic.
@@ -107,8 +119,8 @@ class ServerActivity : BaseActivity() {
private val et_reserved1: EditText? by lazy { findViewById(R.id.et_reserved1) } private val et_reserved1: EditText? by lazy { findViewById(R.id.et_reserved1) }
private val et_reserved2: EditText? by lazy { findViewById(R.id.et_reserved2) } private val et_reserved2: EditText? by lazy { findViewById(R.id.et_reserved2) }
private val et_reserved3: EditText? by lazy { findViewById(R.id.et_reserved3) } private val et_reserved3: EditText? by lazy { findViewById(R.id.et_reserved3) }
private val et_local_v4_address: EditText? by lazy { findViewById(R.id.et_local_v4_address) } private val et_local_address: EditText? by lazy { findViewById(R.id.et_local_address) }
private val et_local_v6_address: EditText? by lazy { findViewById(R.id.et_local_v6_address) } private val et_local_mtu: EditText? by lazy { findViewById(R.id.et_local_mtu) }
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
@@ -116,7 +128,7 @@ class ServerActivity : BaseActivity() {
title = getString(R.string.title_server) title = getString(R.string.title_server)
val config = MmkvManager.decodeServerConfig(editGuid) val config = MmkvManager.decodeServerConfig(editGuid)
when(config?.configType ?: createConfigType) { when (config?.configType ?: createConfigType) {
EConfigType.VMESS -> setContentView(R.layout.activity_server_vmess) EConfigType.VMESS -> setContentView(R.layout.activity_server_vmess)
EConfigType.CUSTOM -> return EConfigType.CUSTOM -> return
EConfigType.SHADOWSOCKS -> setContentView(R.layout.activity_server_shadowsocks) EConfigType.SHADOWSOCKS -> setContentView(R.layout.activity_server_shadowsocks)
@@ -126,10 +138,16 @@ class ServerActivity : BaseActivity() {
EConfigType.WIREGUARD -> setContentView(R.layout.activity_server_wireguard) EConfigType.WIREGUARD -> setContentView(R.layout.activity_server_wireguard)
} }
sp_network?.onItemSelectedListener = object : AdapterView.OnItemSelectedListener { sp_network?.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) { override fun onItemSelected(
parent: AdapterView<*>?,
view: View?,
position: Int,
id: Long
) {
val types = transportTypes(networks[position]) val types = transportTypes(networks[position])
sp_header_type?.isEnabled = types.size > 1 sp_header_type?.isEnabled = types.size > 1
val adapter = ArrayAdapter(this@ServerActivity, android.R.layout.simple_spinner_item, types) val adapter =
ArrayAdapter(this@ServerActivity, android.R.layout.simple_spinner_item, types)
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item) adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
sp_header_type?.adapter = adapter sp_header_type?.adapter = adapter
sp_header_type_title?.text = if (networks[position] == "grpc") sp_header_type_title?.text = if (networks[position] == "grpc")
@@ -141,12 +159,18 @@ class ServerActivity : BaseActivity() {
et_path?.text = Utils.getEditable(transportDetails[2]) et_path?.text = Utils.getEditable(transportDetails[2])
} }
} }
override fun onNothingSelected(parent: AdapterView<*>?) { override fun onNothingSelected(parent: AdapterView<*>?) {
// do nothing // do nothing
} }
} }
sp_stream_security?.onItemSelectedListener = object : AdapterView.OnItemSelectedListener { sp_stream_security?.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) { override fun onItemSelected(
parent: AdapterView<*>?,
view: View?,
position: Int,
id: Long
) {
if (streamSecuritys[position].isBlank()) { if (streamSecuritys[position].isBlank()) {
container_sni?.visibility = View.GONE container_sni?.visibility = View.GONE
container_fingerprint?.visibility = View.GONE container_fingerprint?.visibility = View.GONE
@@ -173,6 +197,7 @@ class ServerActivity : BaseActivity() {
} }
} }
} }
override fun onNothingSelected(p0: AdapterView<*>?) { override fun onNothingSelected(p0: AdapterView<*>?) {
// do nothing // do nothing
} }
@@ -185,46 +210,62 @@ 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
et_remarks.text = Utils.getEditable(config.remarks) et_remarks.text = Utils.getEditable(config.remarks)
et_address.text = Utils.getEditable(outbound.getServerAddress().orEmpty()) et_address.text = Utils.getEditable(outbound.getServerAddress().orEmpty())
et_port.text = Utils.getEditable(outbound.getServerPort()?.toString() ?: DEFAULT_PORT.toString()) et_port.text =
Utils.getEditable(outbound.getServerPort()?.toString() ?: DEFAULT_PORT.toString())
et_id.text = Utils.getEditable(outbound.getPassword().orEmpty()) et_id.text = Utils.getEditable(outbound.getPassword().orEmpty())
et_alterId?.text = Utils.getEditable(outbound.settings?.vnext?.get(0)?.users?.get(0)?.alterId.toString()) et_alterId?.text =
Utils.getEditable(outbound.settings?.vnext?.get(0)?.users?.get(0)?.alterId.toString())
if (config.configType == EConfigType.SOCKS) { if (config.configType == EConfigType.SOCKS) {
et_security?.text = Utils.getEditable(outbound.settings?.servers?.get(0)?.users?.get(0)?.user.orEmpty()) et_security?.text =
Utils.getEditable(outbound.settings?.servers?.get(0)?.users?.get(0)?.user.orEmpty())
} else if (config.configType == EConfigType.VLESS) { } else if (config.configType == EConfigType.VLESS) {
et_security?.text = Utils.getEditable(outbound.getSecurityEncryption().orEmpty()) et_security?.text = Utils.getEditable(outbound.getSecurityEncryption().orEmpty())
val flow = Utils.arrayFind(flows, outbound.settings?.vnext?.get(0)?.users?.get(0)?.flow.orEmpty()) val flow = Utils.arrayFind(
flows,
outbound.settings?.vnext?.get(0)?.users?.get(0)?.flow.orEmpty()
)
if (flow >= 0) { if (flow >= 0) {
sp_flow?.setSelection(flow) sp_flow?.setSelection(flow)
} }
} else if (config.configType == EConfigType.WIREGUARD) { } else if (config.configType == EConfigType.WIREGUARD) {
et_public_key?.text = Utils.getEditable(outbound.settings?.peers?.get(0)?.publicKey.orEmpty()) et_public_key?.text =
Utils.getEditable(outbound.settings?.peers?.get(0)?.publicKey.orEmpty())
if (outbound.settings?.reserved == null) { if (outbound.settings?.reserved == null) {
et_reserved1?.text = Utils.getEditable("0") et_reserved1?.text = Utils.getEditable("0")
et_reserved2?.text = Utils.getEditable("0") et_reserved2?.text = Utils.getEditable("0")
et_reserved3?.text = Utils.getEditable("0") et_reserved3?.text = Utils.getEditable("0")
} else { } else {
et_reserved1?.text = Utils.getEditable(outbound.settings?.reserved?.get(0).toString()) et_reserved1?.text =
et_reserved2?.text = Utils.getEditable(outbound.settings?.reserved?.get(1).toString()) Utils.getEditable(outbound.settings?.reserved?.get(0).toString())
et_reserved3?.text = Utils.getEditable(outbound.settings?.reserved?.get(2).toString()) et_reserved2?.text =
Utils.getEditable(outbound.settings?.reserved?.get(1).toString())
et_reserved3?.text =
Utils.getEditable(outbound.settings?.reserved?.get(2).toString())
} }
if (outbound.settings?.address == null) { if (outbound.settings?.address == null) {
et_local_v4_address?.text = Utils.getEditable("172.16.0.2/32") et_local_address?.text =
et_local_v6_address?.text = Utils.getEditable("2606:4700:110:8f81:d551:a0:532e:a2b3/128") Utils.getEditable("${WIREGUARD_LOCAL_ADDRESS_V4},${WIREGUARD_LOCAL_ADDRESS_V6}")
} else { } else {
val list = outbound.settings?.address as List<*> val list = outbound.settings?.address as List<*>
et_local_v4_address?.text = Utils.getEditable(list.get(0).toString()) et_local_address?.text = Utils.getEditable(list.joinToString())
et_local_v6_address?.text = Utils.getEditable(list.get(1).toString()) }
if (outbound.settings?.mtu == null) {
et_local_mtu?.text = Utils.getEditable(WIREGUARD_LOCAL_MTU)
} else {
et_local_mtu?.text = Utils.getEditable(outbound.settings?.mtu.toString())
} }
} }
val securityEncryptions = if (config.configType == EConfigType.SHADOWSOCKS) shadowsocksSecuritys else securitys val securityEncryptions =
val security = Utils.arrayFind(securityEncryptions, outbound.getSecurityEncryption().orEmpty()) if (config.configType == EConfigType.SHADOWSOCKS) shadowsocksSecuritys else securitys
val security =
Utils.arrayFind(securityEncryptions, outbound.getSecurityEncryption().orEmpty())
if (security >= 0) { if (security >= 0) {
sp_security?.setSelection(security) sp_security?.setSelection(security)
} }
@@ -233,7 +274,7 @@ class ServerActivity : BaseActivity() {
val streamSecurity = Utils.arrayFind(streamSecuritys, streamSetting.security) val streamSecurity = Utils.arrayFind(streamSecuritys, streamSetting.security)
if (streamSecurity >= 0) { if (streamSecurity >= 0) {
sp_stream_security?.setSelection(streamSecurity) sp_stream_security?.setSelection(streamSecurity)
(streamSetting.tlsSettings?: streamSetting.realitySettings)?.let { tlsSetting -> (streamSetting.tlsSettings ?: streamSetting.realitySettings)?.let { tlsSetting ->
container_sni?.visibility = View.VISIBLE container_sni?.visibility = View.VISIBLE
container_fingerprint?.visibility = View.VISIBLE container_fingerprint?.visibility = View.VISIBLE
container_alpn?.visibility = View.VISIBLE container_alpn?.visibility = View.VISIBLE
@@ -243,12 +284,16 @@ class ServerActivity : BaseActivity() {
sp_stream_fingerprint?.setSelection(utlsIndex) sp_stream_fingerprint?.setSelection(utlsIndex)
} }
tlsSetting.alpn?.let { tlsSetting.alpn?.let {
val alpnIndex = Utils.arrayFind(alpns, Utils.removeWhiteSpace(tlsSetting.alpn.joinToString())!!) val alpnIndex = Utils.arrayFind(
alpns,
Utils.removeWhiteSpace(tlsSetting.alpn.joinToString())!!
)
sp_stream_alpn?.setSelection(alpnIndex) sp_stream_alpn?.setSelection(alpnIndex)
} }
if (streamSetting.tlsSettings != null) { if (streamSetting.tlsSettings != null) {
container_allow_insecure?.visibility = View.VISIBLE container_allow_insecure?.visibility = View.VISIBLE
val allowinsecure = Utils.arrayFind(allowinsecures, tlsSetting.allowInsecure.toString()) val allowinsecure =
Utils.arrayFind(allowinsecures, tlsSetting.allowInsecure.toString())
if (allowinsecure >= 0) { if (allowinsecure >= 0) {
sp_allow_insecure?.setSelection(allowinsecure) sp_allow_insecure?.setSelection(allowinsecure)
} }
@@ -307,8 +352,9 @@ class ServerActivity : BaseActivity() {
et_reserved1?.text = Utils.getEditable("0") et_reserved1?.text = Utils.getEditable("0")
et_reserved2?.text = Utils.getEditable("0") et_reserved2?.text = Utils.getEditable("0")
et_reserved3?.text = Utils.getEditable("0") et_reserved3?.text = Utils.getEditable("0")
et_local_v4_address?.text = Utils.getEditable("172.16.0.2/32") et_local_address?.text =
et_local_v6_address?.text = Utils.getEditable("2606:4700:110:8f81:d551:a0:532e:a2b3/128") Utils.getEditable("${WIREGUARD_LOCAL_ADDRESS_V4},${WIREGUARD_LOCAL_ADDRESS_V6}")
et_local_mtu?.text = Utils.getEditable(WIREGUARD_LOCAL_MTU)
return true return true
} }
@@ -329,11 +375,12 @@ class ServerActivity : BaseActivity() {
toast(R.string.server_lab_port) toast(R.string.server_lab_port)
return false return false
} }
val config = MmkvManager.decodeServerConfig(editGuid) ?: ServerConfig.create(createConfigType) val config =
MmkvManager.decodeServerConfig(editGuid) ?: ServerConfig.create(createConfigType)
if (config.configType != EConfigType.SOCKS && TextUtils.isEmpty(et_id.text.toString())) { if (config.configType != EConfigType.SOCKS && TextUtils.isEmpty(et_id.text.toString())) {
if (config.configType == EConfigType.TROJAN || config.configType == EConfigType.SHADOWSOCKS) { if (config.configType == EConfigType.TROJAN || config.configType == EConfigType.SHADOWSOCKS) {
toast(R.string.server_lab_id3) toast(R.string.server_lab_id3)
}else{ } else {
toast(R.string.server_lab_id) toast(R.string.server_lab_id)
} }
return false return false
@@ -366,7 +413,7 @@ class ServerActivity : BaseActivity() {
config.outboundBean?.streamSettings?.let { config.outboundBean?.streamSettings?.let {
saveStreamSettings(it) saveStreamSettings(it)
} }
if(config.subscriptionId.isEmpty() && !subscriptionId.isNullOrEmpty()) { if (config.subscriptionId.isEmpty() && !subscriptionId.isNullOrEmpty()) {
config.subscriptionId = subscriptionId!! config.subscriptionId = subscriptionId!!
} }
@@ -376,7 +423,11 @@ class ServerActivity : BaseActivity() {
return true return true
} }
private fun saveVnext(vnext: V2rayConfig.OutboundBean.OutSettingsBean.VnextBean, port: Int, config: ServerConfig) { private fun saveVnext(
vnext: V2rayConfig.OutboundBean.OutSettingsBean.VnextBean,
port: Int,
config: ServerConfig
) {
vnext.address = et_address.text.toString().trim() vnext.address = et_address.text.toString().trim()
vnext.port = port vnext.port = port
vnext.users[0].id = et_id.text.toString().trim() vnext.users[0].id = et_id.text.toString().trim()
@@ -389,7 +440,11 @@ class ServerActivity : BaseActivity() {
} }
} }
private fun saveServers(server: V2rayConfig.OutboundBean.OutSettingsBean.ServersBean, port: Int, config: ServerConfig) { private fun saveServers(
server: V2rayConfig.OutboundBean.OutSettingsBean.ServersBean,
port: Int,
config: ServerConfig
) {
server.address = et_address.text.toString().trim() server.address = et_address.text.toString().trim()
server.port = port server.port = port
if (config.configType == EConfigType.SHADOWSOCKS) { if (config.configType == EConfigType.SHADOWSOCKS) {
@@ -399,7 +454,8 @@ class ServerActivity : BaseActivity() {
if (TextUtils.isEmpty(et_security?.text) && TextUtils.isEmpty(et_id.text)) { if (TextUtils.isEmpty(et_security?.text) && TextUtils.isEmpty(et_id.text)) {
server.users = null server.users = null
} else { } else {
val socksUsersBean = V2rayConfig.OutboundBean.OutSettingsBean.ServersBean.SocksUsersBean() val socksUsersBean =
V2rayConfig.OutboundBean.OutSettingsBean.ServersBean.SocksUsersBean()
socksUsersBean.user = et_security?.text.toString().trim() socksUsersBean.user = et_security?.text.toString().trim()
socksUsersBean.pass = et_id.text.toString().trim() socksUsersBean.pass = et_id.text.toString().trim()
server.users = listOf(socksUsersBean) server.users = listOf(socksUsersBean)
@@ -412,17 +468,18 @@ class ServerActivity : BaseActivity() {
private fun savePeer(wireguard: V2rayConfig.OutboundBean.OutSettingsBean, port: Int) { private fun savePeer(wireguard: V2rayConfig.OutboundBean.OutSettingsBean, port: Int) {
wireguard.secretKey = et_id.text.toString().trim() wireguard.secretKey = et_id.text.toString().trim()
wireguard.peers?.get(0)?.publicKey = et_public_key?.text.toString().trim() wireguard.peers?.get(0)?.publicKey = et_public_key?.text.toString().trim()
wireguard.peers?.get(0)?.endpoint = getIpv6Address(et_address.text.toString().trim()) + ":" + port wireguard.peers?.get(0)?.endpoint =
getIpv6Address(et_address.text.toString().trim()) + ":" + port
val reserved1 = Utils.parseInt(et_reserved1?.text.toString()) val reserved1 = Utils.parseInt(et_reserved1?.text.toString())
val reserved2 = Utils.parseInt(et_reserved2?.text.toString()) val reserved2 = Utils.parseInt(et_reserved2?.text.toString())
val reserved3 = Utils.parseInt(et_reserved3?.text.toString()) val reserved3 = Utils.parseInt(et_reserved3?.text.toString())
if (reserved1 > 0 || reserved2 > 0 || reserved3 > 0) { if (reserved1 > 0 || reserved2 > 0 || reserved3 > 0) {
wireguard.reserved = listOf(reserved1, reserved2, reserved3) wireguard.reserved = listOf(reserved1, reserved2, reserved3)
}else { } else {
wireguard.reserved = null wireguard.reserved = null
} }
wireguard.address = listOf(et_local_v4_address?.text.toString().trim(), wireguard.address = et_local_address?.text.toString().removeWhiteSpace().split(",")
et_local_v6_address?.text.toString().trim()) wireguard.mtu = Utils.parseInt(et_local_mtu?.text.toString())
} }
private fun saveStreamSettings(streamSetting: V2rayConfig.OutboundBean.StreamSettingsBean) { private fun saveStreamSettings(streamSetting: V2rayConfig.OutboundBean.StreamSettingsBean) {
@@ -448,7 +505,8 @@ class ServerActivity : BaseActivity() {
quicSecurity = requestHost, quicSecurity = requestHost,
key = path, key = path,
mode = transportTypes(networks[network])[type], mode = transportTypes(networks[network])[type],
serviceName = path serviceName = path,
authority = requestHost,
) )
if (sniField.isNotBlank()) { if (sniField.isNotBlank()) {
sni = sniField sni = sniField
@@ -460,14 +518,14 @@ class ServerActivity : BaseActivity() {
} }
streamSetting.populateTlsSettings( streamSetting.populateTlsSettings(
streamSecurity = streamSecuritys[streamSecurity], streamSecurity = streamSecuritys[streamSecurity],
allowInsecure = allowInsecure, allowInsecure = allowInsecure,
sni = sni, sni = sni,
fingerprint = uTlsItems[utlsIndex], fingerprint = uTlsItems[utlsIndex],
alpns = alpns[alpnIndex], alpns = alpns[alpnIndex],
publicKey = publicKey, publicKey = publicKey,
shortId = shortId, shortId = shortId,
spiderX = spiderX spiderX = spiderX
) )
} }
@@ -476,12 +534,15 @@ class ServerActivity : BaseActivity() {
"tcp" -> { "tcp" -> {
tcpTypes tcpTypes
} }
"kcp", "quic" -> { "kcp", "quic" -> {
kcpAndQuicTypes kcpAndQuicTypes
} }
"grpc" -> { "grpc" -> {
grpcModes grpcModes
} }
else -> { else -> {
arrayOf("---") arrayOf("---")
} }
@@ -500,11 +561,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
@@ -532,10 +598,12 @@ class ServerActivity : BaseActivity() {
deleteServer() deleteServer()
true true
} }
R.id.save_config -> { R.id.save_config -> {
saveServer() saveServer()
true true
} }
else -> super.onOptionsItemSelected(item) else -> super.onOptionsItemSelected(item)
} }
} }

View File

@@ -91,7 +91,7 @@ class ServerCustomConfigActivity : BaseActivity() {
} }
val config = MmkvManager.decodeServerConfig(editGuid) ?: ServerConfig.create(EConfigType.CUSTOM) val config = MmkvManager.decodeServerConfig(editGuid) ?: ServerConfig.create(EConfigType.CUSTOM)
config.remarks = binding.etRemarks.text.toString().trim() config.remarks = v2rayConfig.remarks ?: binding.etRemarks.text.toString().trim()
config.fullConfig = v2rayConfig config.fullConfig = v2rayConfig
MmkvManager.encodeServerConfig(editGuid, config) MmkvManager.encodeServerConfig(editGuid, config)
@@ -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,102 +36,44 @@ 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) }
private val muxXudpQuic by lazy { findPreference<ListPreference>(AppConfig.PREF_MUX_XUDP_QUIC) } private val muxXudpQuic by lazy { findPreference<ListPreference>(AppConfig.PREF_MUX_XUDP_QUIC) }
private val fragment by lazy { findPreference<CheckBoxPreference>(AppConfig.PREF_FRAGMENT_ENABLED) }
private val fragmentPackets by lazy { findPreference<ListPreference>(AppConfig.PREF_FRAGMENT_PACKETS) }
private val fragmentLength by lazy { findPreference<EditTextPreference>(AppConfig.PREF_FRAGMENT_LENGTH) }
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
autoUpdateInterval?.summary = nval
// It must be greater than 15 minutes because WorkManager couldn't run tasks under 15 minutes intervals
nval =
if (TextUtils.isEmpty(nval) or (nval.toLong() < 15)) AppConfig.SUBSCRIPTION_DEFAULT_UPDATE_INTERVAL else 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
@@ -140,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
@@ -168,61 +106,176 @@ class SettingsActivity : BaseActivity() {
updateMuxXudpConcurrency(newValue as String) updateMuxXudpConcurrency(newValue as String)
true true
} }
fragment?.setOnPreferenceChangeListener { _, newValue ->
updateFragment(newValue as Boolean)
true
}
fragmentPackets?.setOnPreferenceChangeListener { _, newValue ->
updateFragmentPackets(newValue as String)
true
}
fragmentLength?.setOnPreferenceChangeListener { _, newValue ->
updateFragmentLength(newValue as String)
true
}
fragmentInterval?.setOnPreferenceChangeListener { _, newValue ->
updateFragmentInterval(newValue as String)
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")
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
) )
@@ -258,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"))
} }
} }
@@ -287,6 +339,26 @@ class SettingsActivity : BaseActivity() {
muxXudpQuic?.isEnabled = concurrency >= 0 muxXudpQuic?.isEnabled = concurrency >= 0
} }
} }
private fun updateFragment(enabled: Boolean) {
fragmentPackets?.isEnabled = enabled
fragmentLength?.isEnabled = enabled
fragmentInterval?.isEnabled = enabled
if (enabled) {
updateFragmentPackets(settingsStorage.decodeString(AppConfig.PREF_FRAGMENT_PACKETS, "tlshello"))
updateFragmentLength(settingsStorage.decodeString(AppConfig.PREF_FRAGMENT_LENGTH, "50-100"))
updateFragmentInterval(settingsStorage.decodeString(AppConfig.PREF_FRAGMENT_INTERVAL, "10-20"))
}
}
private fun updateFragmentPackets(value: String?) {
fragmentPackets?.summary = value.toString()
}
private fun updateFragmentLength(value: String?) {
fragmentLength?.summary = value.toString()
}
private fun updateFragmentInterval(value: String?) {
fragmentInterval?.summary = value.toString()
}
} }
fun onModeHelpClicked(view: View) { fun onModeHelpClicked(view: View) {

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

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

@@ -1,6 +1,6 @@
package com.v2ray.ang.ui package com.v2ray.ang.ui
import android.app.Activity import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle import android.os.Bundle
import android.view.View import android.view.View
import android.widget.ArrayAdapter import android.widget.ArrayAdapter
@@ -90,7 +90,7 @@ class TaskerActivity : BaseActivity() {
intent.putExtra(AppConfig.TASKER_EXTRA_BUNDLE, extraBundle) intent.putExtra(AppConfig.TASKER_EXTRA_BUNDLE, extraBundle)
intent.putExtra(AppConfig.TASKER_EXTRA_STRING_BLURB, blurb) intent.putExtra(AppConfig.TASKER_EXTRA_STRING_BLURB, blurb)
setResult(Activity.RESULT_OK, intent) setResult(AppCompatActivity.RESULT_OK, intent)
finish() finish()
} }

View File

@@ -25,8 +25,8 @@ class UrlSchemeActivity : BaseActivity() {
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) val uri = Uri.parse(it)
if (uri.scheme?.startsWith(AppConfig.HTTPS_PROTOCOL) == true || uri.scheme?.startsWith( if (uri.scheme?.startsWith(AppConfig.PROTOCOL_HTTPS) == true || uri.scheme?.startsWith(
AppConfig.HTTP_PROTOCOL AppConfig.PROTOCOL_HTTP
) == true ) == true
) { ) {
val name = uri.getQueryParameter("name") ?: "Subscription" val name = uri.getQueryParameter("name") ?: "Subscription"

View File

@@ -15,12 +15,14 @@ import androidx.activity.result.contract.ActivityResultContracts
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.google.gson.Gson
import com.tbruyelle.rxpermissions.RxPermissions import com.tbruyelle.rxpermissions.RxPermissions
import com.tencent.mmkv.MMKV 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.ActivitySubSettingBinding import com.v2ray.ang.databinding.ActivitySubSettingBinding
import com.v2ray.ang.databinding.ItemRecyclerUserAssetBinding import com.v2ray.ang.databinding.ItemRecyclerUserAssetBinding
import com.v2ray.ang.dto.AssetUrlItem
import com.v2ray.ang.extension.toTrafficString import com.v2ray.ang.extension.toTrafficString
import com.v2ray.ang.extension.toast import com.v2ray.ang.extension.toast
import com.v2ray.ang.util.MmkvManager import com.v2ray.ang.util.MmkvManager
@@ -39,9 +41,11 @@ import java.util.*
class UserAssetActivity : BaseActivity() { class UserAssetActivity : BaseActivity() {
private lateinit var binding: ActivitySubSettingBinding private lateinit var binding: ActivitySubSettingBinding
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) }
private val assetStorage by lazy { MMKV.mmkvWithID(MmkvManager.ID_ASSET, MMKV.MULTI_PROCESS_MODE) }
val extDir by lazy { File(Utils.userAssetPath(this)) } val extDir by lazy { File(Utils.userAssetPath(this)) }
val geofiles = arrayOf("geosite.dat", "geoip.dat") val builtInGeoFiles = arrayOf("geosite.dat", "geoip.dat")
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
@@ -55,6 +59,11 @@ class UserAssetActivity : BaseActivity() {
binding.recyclerView.adapter = UserAssetAdapter() binding.recyclerView.adapter = UserAssetAdapter()
} }
override fun onResume() {
super.onResume()
binding.recyclerView.adapter?.notifyDataSetChanged()
}
override fun onCreateOptionsMenu(menu: Menu): Boolean { override fun onCreateOptionsMenu(menu: Menu): Boolean {
menuInflater.inflate(R.menu.menu_asset, menu) menuInflater.inflate(R.menu.menu_asset, menu)
return super.onCreateOptionsMenu(menu) return super.onCreateOptionsMenu(menu)
@@ -66,6 +75,11 @@ class UserAssetActivity : BaseActivity() {
true true
} }
R.id.add_url -> {
val intent = Intent(this, UserAssetUrlActivity::class.java)
startActivity(intent)
true
}
R.id.download_file -> { R.id.download_file -> {
downloadGeoFiles() downloadGeoFiles()
true true
@@ -104,13 +118,27 @@ class UserAssetActivity : BaseActivity() {
} }
private val chooseFile = private val chooseFile =
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { it ->
val uri = it.data?.data val uri = it.data?.data
if (it.resultCode == RESULT_OK && uri != null) { if (it.resultCode == RESULT_OK && uri != null) {
val assetId = Utils.getUuid()
try { try {
val assetItem = AssetUrlItem(
getCursorName(uri) ?: uri.toString(),
"file"
)
// check remarks unique
val assetList = MmkvManager.decodeAssetUrls()
if (assetList.any { it.second.remarks == assetItem.remarks && it.first != assetId }) {
toast(R.string.msg_remark_is_duplicate)
return@registerForActivityResult
}
assetStorage?.encode(assetId, Gson().toJson(assetItem))
copyFile(uri) copyFile(uri)
} catch (e: Exception) { } catch (e: Exception) {
toast(R.string.toast_asset_copy_failed) toast(R.string.toast_asset_copy_failed)
MmkvManager.removeAssetUrl(assetId)
} }
} }
} }
@@ -143,36 +171,45 @@ class UserAssetActivity : BaseActivity() {
val httpPort = Utils.parseInt(settingsStorage?.decodeString(AppConfig.PREF_HTTP_PORT), AppConfig.PORT_HTTP.toInt()) val httpPort = Utils.parseInt(settingsStorage?.decodeString(AppConfig.PREF_HTTP_PORT), AppConfig.PORT_HTTP.toInt())
toast(R.string.msg_downloading_content) toast(R.string.msg_downloading_content)
geofiles.forEach { var assets = MmkvManager.decodeAssetUrls()
assets = addBuiltInGeoItems(assets)
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, 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) toast(getString(R.string.toast_success) + " " + it.second.remarks)
binding.recyclerView.adapter?.notifyDataSetChanged() binding.recyclerView.adapter?.notifyDataSetChanged()
} else { } else {
toast(getString(R.string.toast_failure) + " " + it) toast(getString(R.string.toast_failure) + " " + it.second.remarks)
} }
} }
} }
} }
} }
private fun downloadGeo(name: String, timeout: Int, httpPort: Int): Boolean { private fun downloadGeo(item: AssetUrlItem, timeout: Int, httpPort: Int): Boolean {
val url = AppConfig.geoUrl + name val targetTemp = File(extDir, item.remarks + "_temp")
val targetTemp = File(extDir, name + "_temp") val target = File(extDir, item.remarks)
val target = File(extDir, name)
var conn: HttpURLConnection? = null var conn: HttpURLConnection? = null
//Log.d(AppConfig.ANG_PACKAGE, url) //Log.d(AppConfig.ANG_PACKAGE, url)
try { try {
conn = URL(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
@@ -192,33 +229,75 @@ class UserAssetActivity : BaseActivity() {
conn?.disconnect() conn?.disconnect()
} }
} }
private fun addBuiltInGeoItems(assets: List<Pair<String, AssetUrlItem>>): List<Pair<String, AssetUrlItem>> {
val list = mutableListOf<Pair<String, AssetUrlItem>>()
builtInGeoFiles
.filter { geoFile -> assets.none { it.second.remarks == geoFile } }
.forEach {
list.add(Utils.getUuid() to AssetUrlItem(
it,
AppConfig.GeoUrl + it
))
}
return list + assets
}
inner class UserAssetAdapter : RecyclerView.Adapter<UserAssetViewHolder>() { inner class UserAssetAdapter : RecyclerView.Adapter<UserAssetViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): UserAssetViewHolder { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): UserAssetViewHolder {
return UserAssetViewHolder(ItemRecyclerUserAssetBinding.inflate(LayoutInflater.from(parent.context), parent, false)) return UserAssetViewHolder(
ItemRecyclerUserAssetBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false)
)
} }
@SuppressLint("SetTextI18n") @SuppressLint("SetTextI18n")
override fun onBindViewHolder(holder: UserAssetViewHolder, position: Int) { override fun onBindViewHolder(holder: UserAssetViewHolder, position: Int) {
val file = extDir.listFiles()?.getOrNull(position) ?: return var assets = MmkvManager.decodeAssetUrls();
holder.itemUserAssetBinding.assetName.text = file.name assets = addBuiltInGeoItems(assets);
val dateFormat = DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.MEDIUM) val item = assets.getOrNull(position) ?: return
holder.itemUserAssetBinding.assetProperties.text = "${file.length().toTrafficString()}${dateFormat.format(Date(file.lastModified()))}" // file with name == item.second.remarks
if (file.name in geofiles) { val file = extDir.listFiles()?.find { it.name == item.second.remarks }
holder.itemUserAssetBinding.assetName.text = item.second.remarks
if (file != null) {
val dateFormat = DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.MEDIUM)
holder.itemUserAssetBinding.assetProperties.text =
"${file.length().toTrafficString()}${dateFormat.format(Date(file.lastModified()))}"
} else {
holder.itemUserAssetBinding.assetProperties.text = getString(R.string.msg_file_not_found)
}
if (item.second.remarks in builtInGeoFiles && item.second.url == AppConfig.GeoUrl + item.second.remarks) {
holder.itemUserAssetBinding.layoutEdit.visibility = GONE
holder.itemUserAssetBinding.layoutRemove.visibility = GONE holder.itemUserAssetBinding.layoutRemove.visibility = GONE
} else { } else {
holder.itemUserAssetBinding.layoutEdit.visibility = item.second.url.let { if (it == "file") GONE else VISIBLE }
holder.itemUserAssetBinding.layoutRemove.visibility = VISIBLE holder.itemUserAssetBinding.layoutRemove.visibility = VISIBLE
} }
holder.itemUserAssetBinding.layoutEdit.setOnClickListener {
val intent = Intent(this@UserAssetActivity, UserAssetUrlActivity::class.java)
intent.putExtra("assetId", item.first)
startActivity(intent)
}
holder.itemUserAssetBinding.layoutRemove.setOnClickListener { holder.itemUserAssetBinding.layoutRemove.setOnClickListener {
file.delete() file?.delete()
MmkvManager.removeAssetUrl(item.first)
binding.recyclerView.adapter?.notifyItemRemoved(position) binding.recyclerView.adapter?.notifyItemRemoved(position)
} }
} }
override fun getItemCount(): Int { override fun getItemCount(): Int {
return extDir.listFiles()?.size ?: 0 var assets = MmkvManager.decodeAssetUrls();
assets = addBuiltInGeoItems(assets);
return assets.size
} }
} }
class UserAssetViewHolder(val itemUserAssetBinding: ItemRecyclerUserAssetBinding) : RecyclerView.ViewHolder(itemUserAssetBinding.root) class UserAssetViewHolder(val itemUserAssetBinding: ItemRecyclerUserAssetBinding) :
RecyclerView.ViewHolder(itemUserAssetBinding.root)
} }

View File

@@ -0,0 +1,148 @@
package com.v2ray.ang.ui
import android.os.Bundle
import android.text.TextUtils
import android.view.Menu
import android.view.MenuItem
import androidx.appcompat.app.AlertDialog
import com.google.gson.Gson
import com.tencent.mmkv.MMKV
import com.v2ray.ang.R
import com.v2ray.ang.databinding.ActivityUserAssetUrlBinding
import com.v2ray.ang.dto.AssetUrlItem
import com.v2ray.ang.extension.toast
import com.v2ray.ang.util.MmkvManager
import com.v2ray.ang.util.Utils
import java.io.File
class UserAssetUrlActivity : BaseActivity() {
private lateinit var binding: ActivityUserAssetUrlBinding
var del_config: MenuItem? = null
var save_config: MenuItem? = null
val extDir by lazy { File(Utils.userAssetPath(this)) }
private val assetStorage by lazy { MMKV.mmkvWithID(MmkvManager.ID_ASSET, MMKV.MULTI_PROCESS_MODE) }
private val editAssetId by lazy { intent.getStringExtra("assetId").orEmpty() }
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityUserAssetUrlBinding.inflate(layoutInflater)
val view = binding.root
setContentView(view)
title = getString(R.string.title_user_asset_add_url)
val json = assetStorage?.decodeString(editAssetId)
if (!json.isNullOrBlank()) {
bindingAsset(Gson().fromJson(json, AssetUrlItem::class.java))
} else {
clearAsset()
}
}
/**
* bingding seleced asset config
*/
private fun bindingAsset(assetItem: AssetUrlItem): Boolean {
binding.etRemarks.text = Utils.getEditable(assetItem.remarks)
binding.etUrl.text = Utils.getEditable(assetItem.url)
return true
}
/**
* clear or init asset config
*/
private fun clearAsset(): Boolean {
binding.etRemarks.text = null
binding.etUrl.text = null
return true
}
/**
* save asset config
*/
private fun saveServer(): Boolean {
val assetItem: AssetUrlItem
val json = assetStorage?.decodeString(editAssetId)
var assetId = editAssetId
if (!json.isNullOrBlank()) {
assetItem = Gson().fromJson(json, AssetUrlItem::class.java)
// remove file associated with the asset
val file = extDir.resolve(assetItem.remarks)
if (file.exists()) {
file.delete()
}
} else {
assetId = Utils.getUuid()
assetItem = AssetUrlItem()
}
assetItem.remarks = binding.etRemarks.text.toString()
assetItem.url = binding.etUrl.text.toString()
// check remarks unique
val assetList = MmkvManager.decodeAssetUrls()
if (assetList.any { it.second.remarks == assetItem.remarks && it.first != assetId }) {
toast(R.string.msg_remark_is_duplicate)
return false
}
if (TextUtils.isEmpty(assetItem.remarks)) {
toast(R.string.sub_setting_remarks)
return false
}
if (TextUtils.isEmpty(assetItem.url)) {
toast(R.string.title_url)
return false
}
assetStorage?.encode(assetId, Gson().toJson(assetItem))
toast(R.string.toast_success)
finish()
return true
}
/**
* save server config
*/
private fun deleteServer(): Boolean {
if (editAssetId.isNotEmpty()) {
AlertDialog.Builder(this).setMessage(R.string.del_config_comfirm)
.setPositiveButton(android.R.string.ok) { _, _ ->
MmkvManager.removeAssetUrl(editAssetId)
finish()
}
.setNegativeButton(android.R.string.no) {_, _ ->
// do nothing
}
.show()
}
return true
}
override fun onCreateOptionsMenu(menu: Menu): Boolean {
menuInflater.inflate(R.menu.action_server, menu)
del_config = menu.findItem(R.id.del_config)
save_config = menu.findItem(R.id.save_config)
if (editAssetId.isEmpty()) {
del_config?.isVisible = false
}
return super.onCreateOptionsMenu(menu)
}
override fun onOptionsItemSelected(item: MenuItem) = when (item.itemId) {
R.id.del_config -> {
deleteServer()
true
}
R.id.save_config -> {
saveServer()
true
}
else -> super.onOptionsItemSelected(item)
}
}

View File

@@ -1,26 +1,31 @@
package com.v2ray.ang.util package com.v2ray.ang.util
import android.content.Context import android.content.Context
import android.content.SharedPreferences
import android.graphics.Bitmap import android.graphics.Bitmap
import android.text.TextUtils import android.text.TextUtils
import android.util.Log import android.util.Log
import androidx.preference.PreferenceManager
import com.google.gson.Gson import com.google.gson.Gson
import com.google.gson.GsonBuilder
import com.google.gson.JsonPrimitive
import com.google.gson.JsonSerializationContext
import com.google.gson.JsonSerializer
import com.google.gson.reflect.TypeToken
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_CONFIG import com.v2ray.ang.AppConfig.PROTOCOL_HTTP
import com.v2ray.ang.AppConfig.HTTPS_PROTOCOL import com.v2ray.ang.AppConfig.PROTOCOL_HTTPS
import com.v2ray.ang.AppConfig.HTTP_PROTOCOL import com.v2ray.ang.AppConfig.WIREGUARD_LOCAL_ADDRESS_V4
import com.v2ray.ang.AppConfig.WIREGUARD_LOCAL_MTU
import com.v2ray.ang.R import com.v2ray.ang.R
import com.v2ray.ang.dto.* import com.v2ray.ang.dto.*
import com.v2ray.ang.dto.V2rayConfig.Companion.DEFAULT_SECURITY import com.v2ray.ang.dto.V2rayConfig.Companion.DEFAULT_SECURITY
import com.v2ray.ang.dto.V2rayConfig.Companion.TLS import com.v2ray.ang.dto.V2rayConfig.Companion.TLS
import com.v2ray.ang.extension.idnHost
import com.v2ray.ang.extension.removeWhiteSpace
import com.v2ray.ang.util.MmkvManager.KEY_SELECTED_SERVER import com.v2ray.ang.util.MmkvManager.KEY_SELECTED_SERVER
import java.lang.reflect.Type
import java.net.URI import java.net.URI
import java.util.* import java.util.*
import com.v2ray.ang.extension.idnHost
import com.v2ray.ang.extension.toast
object AngConfigManager { object AngConfigManager {
private val mainStorage by lazy { private val mainStorage by lazy {
@@ -46,157 +51,158 @@ object AngConfigManager {
/** /**
* Legacy loading config * Legacy loading config
*/ */
fun migrateLegacyConfig(c: Context): Boolean? { // fun migrateLegacyConfig(c: Context): Boolean? {
try { // try {
val defaultSharedPreferences = PreferenceManager.getDefaultSharedPreferences(c) // val defaultSharedPreferences = PreferenceManager.getDefaultSharedPreferences(c)
val context = defaultSharedPreferences.getString(ANG_CONFIG, "") // val context = defaultSharedPreferences.getString(ANG_CONFIG, "")
if (context.isNullOrBlank()) { // if (context.isNullOrBlank()) {
return null // return null
} // }
val angConfig = Gson().fromJson(context, AngConfig::class.java) // val angConfig = Gson().fromJson(context, AngConfig::class.java)
for (i in angConfig.vmess.indices) { // for (i in angConfig.vmess.indices) {
upgradeServerVersion(angConfig.vmess[i]) // upgradeServerVersion(angConfig.vmess[i])
} // }
//
copyLegacySettings(defaultSharedPreferences) // copyLegacySettings(defaultSharedPreferences)
migrateVmessBean(angConfig, defaultSharedPreferences) // migrateVmessBean(angConfig, defaultSharedPreferences)
migrateSubItemBean(angConfig) // migrateSubItemBean(angConfig)
//
defaultSharedPreferences.edit().remove(ANG_CONFIG).apply() // defaultSharedPreferences.edit().remove(ANG_CONFIG).apply()
return true // return true
} catch (e: Exception) { // } catch (e: Exception) {
e.printStackTrace() // e.printStackTrace()
} // }
return false // return false
} // }
//
private fun copyLegacySettings(sharedPreferences: SharedPreferences) { // private fun copyLegacySettings(sharedPreferences: SharedPreferences) {
listOf( // listOf(
AppConfig.PREF_MODE, // AppConfig.PREF_MODE,
AppConfig.PREF_REMOTE_DNS, // AppConfig.PREF_REMOTE_DNS,
AppConfig.PREF_DOMESTIC_DNS, // AppConfig.PREF_DOMESTIC_DNS,
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_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,
AppConfig.PREF_V2RAY_ROUTING_BLOCKED, // AppConfig.PREF_V2RAY_ROUTING_BLOCKED,
AppConfig.PREF_V2RAY_ROUTING_DIRECT, // AppConfig.PREF_V2RAY_ROUTING_DIRECT,
).forEach { key -> // ).forEach { key ->
settingsStorage?.encode(key, sharedPreferences.getString(key, null)) // settingsStorage?.encode(key, sharedPreferences.getString(key, null))
} // }
listOf( // listOf(
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,
AppConfig.PREF_ALLOW_INSECURE, // AppConfig.PREF_ALLOW_INSECURE,
AppConfig.PREF_PREFER_IPV6, // AppConfig.PREF_PREFER_IPV6,
AppConfig.PREF_PER_APP_PROXY, // AppConfig.PREF_PER_APP_PROXY,
AppConfig.PREF_BYPASS_APPS, // AppConfig.PREF_BYPASS_APPS,
).forEach { key -> // ).forEach { key ->
settingsStorage?.encode(key, sharedPreferences.getBoolean(key, false)) // settingsStorage?.encode(key, sharedPreferences.getBoolean(key, false))
} // }
settingsStorage?.encode( // settingsStorage?.encode(
AppConfig.PREF_SNIFFING_ENABLED, // AppConfig.PREF_SNIFFING_ENABLED,
sharedPreferences.getBoolean(AppConfig.PREF_SNIFFING_ENABLED, true) // sharedPreferences.getBoolean(AppConfig.PREF_SNIFFING_ENABLED, true)
) // )
settingsStorage?.encode( // settingsStorage?.encode(
AppConfig.PREF_PER_APP_PROXY_SET, // AppConfig.PREF_PER_APP_PROXY_SET,
sharedPreferences.getStringSet(AppConfig.PREF_PER_APP_PROXY_SET, setOf()) // sharedPreferences.getStringSet(AppConfig.PREF_PER_APP_PROXY_SET, setOf())
) // )
} // }
//
private fun migrateVmessBean(angConfig: AngConfig, sharedPreferences: SharedPreferences) { // private fun migrateVmessBean(angConfig: AngConfig, sharedPreferences: SharedPreferences) {
angConfig.vmess.forEachIndexed { index, vmessBean -> // angConfig.vmess.forEachIndexed { index, vmessBean ->
val type = EConfigType.fromInt(vmessBean.configType) ?: return@forEachIndexed // val type = EConfigType.fromInt(vmessBean.configType) ?: return@forEachIndexed
val config = ServerConfig.create(type) // val config = ServerConfig.create(type)
config.remarks = vmessBean.remarks // config.remarks = vmessBean.remarks
config.subscriptionId = vmessBean.subid // config.subscriptionId = vmessBean.subid
if (type == EConfigType.CUSTOM) { // if (type == EConfigType.CUSTOM) {
val jsonConfig = sharedPreferences.getString(ANG_CONFIG + vmessBean.guid, "") // val jsonConfig = sharedPreferences.getString(ANG_CONFIG + vmessBean.guid, "")
val v2rayConfig = try { // val v2rayConfig = try {
Gson().fromJson(jsonConfig, V2rayConfig::class.java) // Gson().fromJson(jsonConfig, V2rayConfig::class.java)
} catch (e: Exception) { // } catch (e: Exception) {
e.printStackTrace() // e.printStackTrace()
return@forEachIndexed // return@forEachIndexed
} // }
config.fullConfig = v2rayConfig // config.fullConfig = v2rayConfig
serverRawStorage?.encode(vmessBean.guid, jsonConfig) // serverRawStorage?.encode(vmessBean.guid, jsonConfig)
} else { // } else {
config.outboundBean?.settings?.vnext?.get(0)?.let { vnext -> // config.outboundBean?.settings?.vnext?.get(0)?.let { vnext ->
vnext.address = vmessBean.address // vnext.address = vmessBean.address
vnext.port = vmessBean.port // vnext.port = vmessBean.port
vnext.users[0].id = vmessBean.id // vnext.users[0].id = vmessBean.id
if (config.configType == EConfigType.VMESS) { // if (config.configType == EConfigType.VMESS) {
vnext.users[0].alterId = vmessBean.alterId // vnext.users[0].alterId = vmessBean.alterId
vnext.users[0].security = vmessBean.security // vnext.users[0].security = vmessBean.security
} else if (config.configType == EConfigType.VLESS) { // } else if (config.configType == EConfigType.VLESS) {
vnext.users[0].encryption = vmessBean.security // vnext.users[0].encryption = vmessBean.security
vnext.users[0].flow = vmessBean.flow // vnext.users[0].flow = vmessBean.flow
} // }
} // }
config.outboundBean?.settings?.servers?.get(0)?.let { server -> // config.outboundBean?.settings?.servers?.get(0)?.let { server ->
server.address = vmessBean.address // server.address = vmessBean.address
server.port = vmessBean.port // server.port = vmessBean.port
if (config.configType == EConfigType.SHADOWSOCKS) { // if (config.configType == EConfigType.SHADOWSOCKS) {
server.password = vmessBean.id // server.password = vmessBean.id
server.method = vmessBean.security // server.method = vmessBean.security
} else if (config.configType == EConfigType.SOCKS) { // } else if (config.configType == EConfigType.SOCKS) {
if (TextUtils.isEmpty(vmessBean.security) && TextUtils.isEmpty(vmessBean.id)) { // if (TextUtils.isEmpty(vmessBean.security) && TextUtils.isEmpty(vmessBean.id)) {
server.users = null // server.users = null
} else { // } else {
val socksUsersBean = // val socksUsersBean =
V2rayConfig.OutboundBean.OutSettingsBean.ServersBean.SocksUsersBean() // V2rayConfig.OutboundBean.OutSettingsBean.ServersBean.SocksUsersBean()
socksUsersBean.user = vmessBean.security // socksUsersBean.user = vmessBean.security
socksUsersBean.pass = vmessBean.id // socksUsersBean.pass = vmessBean.id
server.users = listOf(socksUsersBean) // server.users = listOf(socksUsersBean)
} // }
} else if (config.configType == EConfigType.TROJAN) { // } else if (config.configType == EConfigType.TROJAN) {
server.password = vmessBean.id // server.password = vmessBean.id
} // }
} // }
config.outboundBean?.streamSettings?.let { streamSetting -> // config.outboundBean?.streamSettings?.let { streamSetting ->
val sni = streamSetting.populateTransportSettings( // val sni = streamSetting.populateTransportSettings(
vmessBean.network, // vmessBean.network,
vmessBean.headerType, // vmessBean.headerType,
vmessBean.requestHost, // vmessBean.requestHost,
vmessBean.path, // vmessBean.path,
vmessBean.path, // vmessBean.path,
vmessBean.requestHost, // vmessBean.requestHost,
vmessBean.path, // vmessBean.path,
vmessBean.headerType, // vmessBean.headerType,
vmessBean.path // vmessBean.path,
) // vmessBean.requestHost,
val allowInsecure = if (vmessBean.allowInsecure.isBlank()) { // )
settingsStorage?.decodeBool(AppConfig.PREF_ALLOW_INSECURE) ?: false // val allowInsecure = if (vmessBean.allowInsecure.isBlank()) {
} else { // settingsStorage?.decodeBool(AppConfig.PREF_ALLOW_INSECURE) ?: false
vmessBean.allowInsecure.toBoolean() // } else {
} // vmessBean.allowInsecure.toBoolean()
var fingerprint = streamSetting.tlsSettings?.fingerprint // }
streamSetting.populateTlsSettings( // var fingerprint = streamSetting.tlsSettings?.fingerprint
vmessBean.streamSecurity, allowInsecure, // streamSetting.populateTlsSettings(
vmessBean.sni.ifBlank { sni }, fingerprint, null, null, null, null // vmessBean.streamSecurity, allowInsecure,
) // vmessBean.sni.ifBlank { sni }, fingerprint, null, null, null, null
} // )
} // }
val key = MmkvManager.encodeServerConfig(vmessBean.guid, config) // }
if (index == angConfig.index) { // val key = MmkvManager.encodeServerConfig(vmessBean.guid, config)
mainStorage?.encode(KEY_SELECTED_SERVER, key) // if (index == angConfig.index) {
} // mainStorage?.encode(KEY_SELECTED_SERVER, key)
} // }
} // }
// }
private fun migrateSubItemBean(angConfig: AngConfig) { //
angConfig.subItem.forEach { // private fun migrateSubItemBean(angConfig: AngConfig) {
val subItem = SubscriptionItem() // angConfig.subItem.forEach {
subItem.remarks = it.remarks // val subItem = SubscriptionItem()
subItem.url = it.url // subItem.remarks = it.remarks
subItem.enabled = it.enabled // subItem.url = it.url
subStorage?.encode(it.id, Gson().toJson(subItem)) // subItem.enabled = it.enabled
} // subStorage?.encode(it.id, Gson().toJson(subItem))
} // }
// }
/** /**
* import config form qrcode or... * import config form qrcode or...
@@ -212,8 +218,8 @@ object AngConfigManager {
} }
//maybe sub //maybe sub
if (TextUtils.isEmpty(subid) && (str.startsWith(HTTP_PROTOCOL) || str.startsWith( if (TextUtils.isEmpty(subid) && (str.startsWith(PROTOCOL_HTTP) || str.startsWith(
HTTPS_PROTOCOL PROTOCOL_HTTPS
)) ))
) { ) {
MmkvManager.importUrlAsSubscription(str) MmkvManager.importUrlAsSubscription(str)
@@ -266,7 +272,8 @@ object AngConfigManager {
vmessQRCode.host, vmessQRCode.host,
vmessQRCode.path, vmessQRCode.path,
vmessQRCode.type, vmessQRCode.type,
vmessQRCode.path vmessQRCode.path,
vmessQRCode.host
) )
val fingerprint = vmessQRCode.fp ?: streamSetting.tlsSettings?.fingerprint val fingerprint = vmessQRCode.fp ?: streamSetting.tlsSettings?.fingerprint
@@ -350,7 +357,7 @@ object AngConfigManager {
server.port = match.groupValues[4].toInt() server.port = match.groupValues[4].toInt()
val socksUsersBean = val socksUsersBean =
V2rayConfig.OutboundBean.OutSettingsBean.ServersBean.SocksUsersBean() V2rayConfig.OutboundBean.OutSettingsBean.ServersBean.SocksUsersBean()
socksUsersBean.user = match.groupValues[1].lowercase() socksUsersBean.user = match.groupValues[1]
socksUsersBean.pass = match.groupValues[2] socksUsersBean.pass = match.groupValues[2]
server.users = listOf(socksUsersBean) server.users = listOf(socksUsersBean)
} }
@@ -374,7 +381,8 @@ object AngConfigManager {
queryParam["quicSecurity"], queryParam["quicSecurity"],
queryParam["key"], queryParam["key"],
queryParam["mode"], queryParam["mode"],
queryParam["serviceName"] queryParam["serviceName"],
queryParam["authority"]
) )
fingerprint = queryParam["fp"] ?: "" fingerprint = queryParam["fp"] ?: ""
config.outboundBean?.streamSettings?.populateTlsSettings( config.outboundBean?.streamSettings?.populateTlsSettings(
@@ -402,7 +410,6 @@ object AngConfigManager {
.associate { it.split("=").let { (k, v) -> k to Utils.urlDecode(v) } } .associate { it.split("=").let { (k, v) -> k to Utils.urlDecode(v) } }
config = ServerConfig.create(EConfigType.VLESS) config = ServerConfig.create(EConfigType.VLESS)
val streamSetting = config.outboundBean?.streamSettings ?: return -1 val streamSetting = config.outboundBean?.streamSettings ?: return -1
var fingerprint = streamSetting.tlsSettings?.fingerprint
config.remarks = Utils.urlDecode(uri.fragment ?: "") config.remarks = Utils.urlDecode(uri.fragment ?: "")
config.outboundBean?.settings?.vnext?.get(0)?.let { vnext -> config.outboundBean?.settings?.vnext?.get(0)?.let { vnext ->
@@ -422,16 +429,41 @@ object AngConfigManager {
queryParam["quicSecurity"], queryParam["quicSecurity"],
queryParam["key"], queryParam["key"],
queryParam["mode"], queryParam["mode"],
queryParam["serviceName"] queryParam["serviceName"],
queryParam["authority"]
) )
fingerprint = queryParam["fp"] ?: ""
val pbk = queryParam["pbk"] ?: ""
val sid = queryParam["sid"] ?: ""
val spx = Utils.urlDecode(queryParam["spx"] ?: "")
streamSetting.populateTlsSettings( streamSetting.populateTlsSettings(
queryParam["security"] ?: "", allowInsecure, queryParam["security"] ?: "",
queryParam["sni"] ?: sni, fingerprint, queryParam["alpn"], pbk, sid, spx allowInsecure,
queryParam["sni"] ?: sni,
queryParam["fp"] ?: "",
queryParam["alpn"],
queryParam["pbk"] ?: "",
queryParam["sid"] ?: "",
queryParam["spx"] ?: ""
) )
} else if (str.startsWith(EConfigType.WIREGUARD.protocolScheme)) {
val uri = URI(Utils.fixIllegalUrl(str))
config = ServerConfig.create(EConfigType.WIREGUARD)
config.remarks = Utils.urlDecode(uri.fragment ?: "")
if (uri.rawQuery != null) {
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"] ?: WIREGUARD_LOCAL_ADDRESS_V4).removeWhiteSpace()
.split(",")
wireguard.peers?.get(0)?.publicKey = queryParam["publickey"] ?: ""
wireguard.peers?.get(0)?.endpoint = "${uri.idnHost}:${uri.port}"
wireguard.mtu = Utils.parseInt(queryParam["mtu"] ?: WIREGUARD_LOCAL_MTU)
wireguard.reserved =
(queryParam["reserved"] ?: "0,0,0").removeWhiteSpace().split(",")
.map { it.toInt() }
}
}
} }
if (config == null) { if (config == null) {
return R.string.toast_incorrect_protocol return R.string.toast_incorrect_protocol
@@ -443,7 +475,8 @@ object AngConfigManager {
?.getServerAddress() == removedSelectedServer.getProxyOutbound() ?.getServerAddress() == removedSelectedServer.getProxyOutbound()
?.getServerAddress() && ?.getServerAddress() &&
config.getProxyOutbound() config.getProxyOutbound()
?.getServerPort() == removedSelectedServer.getProxyOutbound()?.getServerPort() ?.getServerPort() == removedSelectedServer.getProxyOutbound()
?.getServerPort()
) { ) {
mainStorage?.encode(KEY_SELECTED_SERVER, guid) mainStorage?.encode(KEY_SELECTED_SERVER, guid)
} }
@@ -460,7 +493,7 @@ object AngConfigManager {
allowInsecure: Boolean allowInsecure: Boolean
): Boolean { ): Boolean {
return runCatching { return runCatching {
val uri = URI(uriString) val uri = URI(Utils.fixIllegalUrl(uriString))
check(uri.scheme == "vmess") check(uri.scheme == "vmess")
val (_, protocol, tlsStr, uuid, alterId) = val (_, protocol, tlsStr, uuid, alterId) =
Regex("(tcp|http|ws|kcp|quic|grpc)(\\+tls)?:([0-9a-z]{8}-[0-9a-z]{4}-[0-9a-z]{4}-[0-9a-z]{4}-[0-9a-z]{12})") Regex("(tcp|http|ws|kcp|quic|grpc)(\\+tls)?:([0-9a-z]{8}-[0-9a-z]{4}-[0-9a-z]{4}-[0-9a-z]{4}-[0-9a-z]{12})")
@@ -488,7 +521,8 @@ object AngConfigManager {
queryParam["security"], queryParam["security"],
queryParam["key"], queryParam["key"],
queryParam["mode"], queryParam["mode"],
queryParam["serviceName"]) queryParam["serviceName"],
queryParam["authority"])
streamSetting.populateTlsSettings( streamSetting.populateTlsSettings(
if (tls) TLS else "", allowInsecure, sni, fingerprint, null, if (tls) TLS else "", allowInsecure, sni, fingerprint, null,
null, null, null null, null, null
@@ -551,6 +585,61 @@ object AngConfigManager {
password = base64Decode.substringAfter(":") 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 -> config.outboundBean?.settings?.servers?.get(0)?.let { server ->
server.address = uri.idnHost server.address = uri.idnHost
server.port = uri.port server.port = uri.port
@@ -571,7 +660,11 @@ object AngConfigManager {
try { try {
val config = MmkvManager.decodeServerConfig(guid) ?: return "" val config = MmkvManager.decodeServerConfig(guid) ?: return ""
val outbound = config.getProxyOutbound() ?: return "" val outbound = config.getProxyOutbound() ?: return ""
val streamSetting = outbound.streamSettings ?: return "" val streamSetting =
outbound.streamSettings ?: V2rayConfig.OutboundBean.StreamSettingsBean()
if (config.configType != EConfigType.WIREGUARD) {
if (outbound.streamSettings == null) return ""
}
return config.configType.protocolScheme + when (config.configType) { return config.configType.protocolScheme + when (config.configType) {
EConfigType.VMESS -> { EConfigType.VMESS -> {
val vmessQRCode = VmessQRCode() val vmessQRCode = VmessQRCode()
@@ -600,7 +693,8 @@ object AngConfigManager {
Utils.encode(json) Utils.encode(json)
} }
EConfigType.CUSTOM, EConfigType.WIREGUARD -> "" EConfigType.CUSTOM -> ""
EConfigType.SHADOWSOCKS -> { EConfigType.SHADOWSOCKS -> {
val remark = "#" + Utils.urlEncode(config.remarks) val remark = "#" + Utils.urlEncode(config.remarks)
val pw = val pw =
@@ -675,7 +769,8 @@ object AngConfigManager {
dicQuery["spx"] = Utils.urlEncode(tlsSetting.spiderX!!) dicQuery["spx"] = Utils.urlEncode(tlsSetting.spiderX!!)
} }
} }
dicQuery["type"] = streamSetting.network.ifEmpty { V2rayConfig.DEFAULT_NETWORK } dicQuery["type"] =
streamSetting.network.ifEmpty { V2rayConfig.DEFAULT_NETWORK }
outbound.getTransportSettingDetails()?.let { transportDetails -> outbound.getTransportSettingDetails()?.let { transportDetails ->
when (streamSetting.network) { when (streamSetting.network) {
@@ -693,7 +788,7 @@ object AngConfigManager {
} }
} }
"ws" -> { "ws", "httpupgrade" -> {
if (!TextUtils.isEmpty(transportDetails[1])) { if (!TextUtils.isEmpty(transportDetails[1])) {
dicQuery["host"] = Utils.urlEncode(transportDetails[1]) dicQuery["host"] = Utils.urlEncode(transportDetails[1])
} }
@@ -720,7 +815,8 @@ object AngConfigManager {
"grpc" -> { "grpc" -> {
dicQuery["mode"] = transportDetails[0] dicQuery["mode"] = transportDetails[0]
dicQuery["serviceName"] = transportDetails[2] dicQuery["authority"] = Utils.urlEncode(transportDetails[1])
dicQuery["serviceName"] = Utils.urlEncode(transportDetails[2])
} }
} }
} }
@@ -736,6 +832,38 @@ object AngConfigManager {
) )
url + query + remark url + query + remark
} }
EConfigType.WIREGUARD -> {
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()
)
url + query + remark
}
} }
} catch (e: Exception) { } catch (e: Exception) {
e.printStackTrace() e.printStackTrace()
@@ -822,38 +950,38 @@ object AngConfigManager {
return 0 return 0
} }
/** // /**
* upgrade // * upgrade
*/ // */
private fun upgradeServerVersion(vmess: AngConfig.VmessBean): Int { // private fun upgradeServerVersion(vmess: AngConfig.VmessBean): Int {
try { // try {
if (vmess.configVersion == 2) { // if (vmess.configVersion == 2) {
return 0 // return 0
} // }
//
when (vmess.network) { // when (vmess.network) {
"ws", "h2" -> { // "ws", "h2" -> {
var path = "" // var path = ""
var host = "" // var host = ""
val lstParameter = vmess.requestHost.split(";") // val lstParameter = vmess.requestHost.split(";")
if (lstParameter.isNotEmpty()) { // if (lstParameter.isNotEmpty()) {
path = lstParameter[0].trim() // path = lstParameter[0].trim()
} // }
if (lstParameter.size > 1) { // if (lstParameter.size > 1) {
path = lstParameter[0].trim() // path = lstParameter[0].trim()
host = lstParameter[1].trim() // host = lstParameter[1].trim()
} // }
vmess.path = path // vmess.path = path
vmess.requestHost = host // vmess.requestHost = host
} // }
} // }
vmess.configVersion = 2 // vmess.configVersion = 2
return 0 // return 0
} catch (e: Exception) { // } catch (e: Exception) {
e.printStackTrace() // e.printStackTrace()
return -1 // return -1
} // }
} // }
fun importBatchConfig(servers: String?, subid: String, append: Boolean): Int { fun importBatchConfig(servers: String?, subid: String, append: Boolean): Int {
try { try {
@@ -861,17 +989,19 @@ object AngConfigManager {
return 0 return 0
} }
val removedSelectedServer = val removedSelectedServer =
if (!TextUtils.isEmpty(subid) && !append) { if (!TextUtils.isEmpty(subid) && !append) {
MmkvManager.decodeServerConfig(mainStorage?.decodeString(KEY_SELECTED_SERVER) ?: "")?.let { MmkvManager.decodeServerConfig(
if (it.subscriptionId == subid) { mainStorage?.decodeString(KEY_SELECTED_SERVER) ?: ""
return@let it )?.let {
} if (it.subscriptionId == subid) {
return@let null return@let it
} }
} else { return@let null
null
} }
if(!append) { } else {
null
}
if (!append) {
MmkvManager.removeServerViaSubid(subid) MmkvManager.removeServerViaSubid(subid)
} }
// var servers = server // var servers = server
@@ -911,4 +1041,61 @@ object AngConfigManager {
return true return true
} }
fun appendCustomConfigServer(server: String?, subid: String): Int {
if (server == null) {
return 0
}
if (server.contains("inbounds")
&& server.contains("outbounds")
&& server.contains("routing")
) {
try {
//val gson = GsonBuilder().setPrettyPrinting().create()
val gson = GsonBuilder()
.setPrettyPrinting()
.disableHtmlEscaping()
.registerTypeAdapter( // custom serialiser is needed here since JSON by default parse number as Double, core will fail to start
object : TypeToken<Double>() {}.type,
JsonSerializer { src: Double?, _: Type?, _: JsonSerializationContext? ->
JsonPrimitive(
src?.toInt()
)
}
)
.create()
val serverList: Array<Any> =
Gson().fromJson(server, Array<Any>::class.java)
if (serverList.isNotEmpty()) {
var count = 0
for (srv in serverList) {
val config = ServerConfig.create(EConfigType.CUSTOM)
config.fullConfig = Gson().fromJson(Gson().toJson(srv), V2rayConfig::class.java)
config.remarks = config.fullConfig?.remarks
?: ("%04d-".format(count + 1) + System.currentTimeMillis()
.toString())
config.subscriptionId = subid
val key = MmkvManager.encodeServerConfig("", config)
serverRawStorage?.encode(key, gson.toJson(srv))
count += 1
}
return count
}
} catch (e: Exception) {
e.printStackTrace()
}
// For compatibility
val config = ServerConfig.create(EConfigType.CUSTOM)
config.subscriptionId = subid
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)
return 1
} else {
return 0
}
}
} }

View File

@@ -22,7 +22,7 @@ object AppManagerUtil {
val appName = applicationInfo.loadLabel(packageManager).toString() val appName = applicationInfo.loadLabel(packageManager).toString()
val appIcon = applicationInfo.loadIcon(packageManager) val appIcon = applicationInfo.loadIcon(packageManager)
val isSystemApp = (applicationInfo.flags and ApplicationInfo.FLAG_SYSTEM) > 0 val isSystemApp = applicationInfo.flags and ApplicationInfo.FLAG_SYSTEM > 0
val appInfo = AppInfo(appName, pkg.packageName, appIcon, isSystemApp, 0) val appInfo = AppInfo(appName, pkg.packageName, appIcon, isSystemApp, 0)
apps.add(appInfo) apps.add(appInfo)

View File

@@ -2,9 +2,11 @@ package com.v2ray.ang.util
import com.google.gson.Gson import com.google.gson.Gson
import com.tencent.mmkv.MMKV import com.tencent.mmkv.MMKV
import com.v2ray.ang.dto.AssetUrlItem
import com.v2ray.ang.dto.ServerAffiliationInfo import com.v2ray.ang.dto.ServerAffiliationInfo
import com.v2ray.ang.dto.ServerConfig import com.v2ray.ang.dto.ServerConfig
import com.v2ray.ang.dto.SubscriptionItem import com.v2ray.ang.dto.SubscriptionItem
import java.net.URI
object MmkvManager { object MmkvManager {
const val ID_MAIN = "MAIN" const val ID_MAIN = "MAIN"
@@ -12,6 +14,7 @@ object MmkvManager {
const val ID_SERVER_RAW = "SERVER_RAW" const val ID_SERVER_RAW = "SERVER_RAW"
const val ID_SERVER_AFF = "SERVER_AFF" const val ID_SERVER_AFF = "SERVER_AFF"
const val ID_SUB = "SUB" const val ID_SUB = "SUB"
const val ID_ASSET = "ASSET"
const val ID_SETTING = "SETTING" const val ID_SETTING = "SETTING"
const val KEY_SELECTED_SERVER = "SELECTED_SERVER" const val KEY_SELECTED_SERVER = "SELECTED_SERVER"
const val KEY_ANG_CONFIGS = "ANG_CONFIGS" const val KEY_ANG_CONFIGS = "ANG_CONFIGS"
@@ -20,6 +23,7 @@ object MmkvManager {
private val serverStorage by lazy { MMKV.mmkvWithID(ID_SERVER_CONFIG, MMKV.MULTI_PROCESS_MODE) } private val serverStorage by lazy { MMKV.mmkvWithID(ID_SERVER_CONFIG, MMKV.MULTI_PROCESS_MODE) }
private val serverAffStorage by lazy { MMKV.mmkvWithID(ID_SERVER_AFF, MMKV.MULTI_PROCESS_MODE) } private val serverAffStorage by lazy { MMKV.mmkvWithID(ID_SERVER_AFF, MMKV.MULTI_PROCESS_MODE) }
private val subStorage by lazy { MMKV.mmkvWithID(ID_SUB, MMKV.MULTI_PROCESS_MODE) } private val subStorage by lazy { MMKV.mmkvWithID(ID_SUB, MMKV.MULTI_PROCESS_MODE) }
private val assetStorage by lazy { MMKV.mmkvWithID(ID_ASSET, MMKV.MULTI_PROCESS_MODE) }
fun decodeServerList(): MutableList<String> { fun decodeServerList(): MutableList<String> {
val json = mainStorage?.decodeString(KEY_ANG_CONFIGS) val json = mainStorage?.decodeString(KEY_ANG_CONFIGS)
@@ -102,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))
@@ -118,8 +122,9 @@ object MmkvManager {
return 0 return 0
} }
} }
val uri = URI(Utils.fixIllegalUrl(url))
val subItem = SubscriptionItem() val subItem = SubscriptionItem()
subItem.remarks = "import sub" subItem.remarks = Utils.urlDecode(uri.fragment ?: "import sub")
subItem.url = url subItem.url = url
subStorage?.encode(Utils.getUuid(), Gson().toJson(subItem)) subStorage?.encode(Utils.getUuid(), Gson().toJson(subItem))
return 1 return 1
@@ -142,6 +147,22 @@ object MmkvManager {
removeServerViaSubid(subid) removeServerViaSubid(subid)
} }
fun decodeAssetUrls(): List<Pair<String, AssetUrlItem>> {
val assetUrlItems = mutableListOf<Pair<String, AssetUrlItem>>()
assetStorage?.allKeys()?.forEach { key ->
val json = assetStorage?.decodeString(key)
if (!json.isNullOrBlank()) {
assetUrlItems.add(Pair(key, Gson().fromJson(json, AssetUrlItem::class.java)))
}
}
assetUrlItems.sortedBy { (_, value) -> value.addedTime }
return assetUrlItems
}
fun removeAssetUrl(assetid: String) {
assetStorage?.remove(assetid)
}
fun removeAllServer() { fun removeAllServer() {
mainStorage?.clearAll() mainStorage?.clearAll()
serverStorage?.clearAll() serverStorage?.clearAll()
@@ -151,7 +172,7 @@ 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)
} }
} }

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
@@ -98,9 +102,7 @@ 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(Proxy.Type.HTTP,

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 {
@@ -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
} }
@@ -210,7 +209,7 @@ object Utils {
} }
fun isPureIpAddress(value: String): Boolean { fun isPureIpAddress(value: String): Boolean {
return (isIpv4Address(value) || isIpv6Address(value)) return isIpv4Address(value) || isIpv6Address(value)
} }
fun isIpv4Address(value: String): Boolean { fun isIpv4Address(value: String): Boolean {
@@ -283,7 +282,7 @@ object Utils {
fun urlDecode(url: String): String { fun urlDecode(url: String): String {
return try { return try {
URLDecoder.decode(URLDecoder.decode(url), "utf-8") URLDecoder.decode(url, "UTF-8")
} catch (e: Exception) { } catch (e: Exception) {
e.printStackTrace() e.printStackTrace()
url url
@@ -318,6 +317,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 +352,18 @@ object Utils {
} }
@Throws(IOException::class) @Throws(IOException::class)
fun getUrlContentWithCustomUserAgent(urlStr: String?): String { fun getUrlContentWithCustomUserAgent(urlStr: String?, 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.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,6 +381,14 @@ object Utils {
return mode != UI_MODE_NIGHT_NO return mode != UI_MODE_NIGHT_NO
} }
fun setNightMode(context: Context) {
when (settingsStorage?.decodeString(AppConfig.PREF_UI_MODE_NIGHT, "0")) {
"0" -> AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM)
"1" -> AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO)
"2" -> AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES)
}
}
fun getIpv6Address(address: String): String { fun getIpv6Address(address: String): String {
return if (isIpv6Address(address) && !address.contains('[') && !address.contains(']')) { return if (isIpv6Address(address) && !address.contains('[') && !address.contains(']')) {
String.format("[%s]", address) String.format("[%s]", address)
@@ -410,5 +434,9 @@ 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
}
} }

View File

@@ -2,20 +2,33 @@ package com.v2ray.ang.util
import android.content.Context import android.content.Context
import android.text.TextUtils import android.text.TextUtils
import android.util.Log import com.google.gson.Gson
import com.google.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.dto.V2rayConfig import com.v2ray.ang.AppConfig.TAG_DIRECT
import com.v2ray.ang.AppConfig.TAG_FRAGMENT
import com.v2ray.ang.AppConfig.WIREGUARD_LOCAL_ADDRESS_V4
import com.v2ray.ang.AppConfig.WIREGUARD_LOCAL_ADDRESS_V6
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
object V2rayConfigUtil { object V2rayConfigUtil {
private val serverRawStorage by lazy { MMKV.mmkvWithID(MmkvManager.ID_SERVER_RAW, MMKV.MULTI_PROCESS_MODE) } private val serverRawStorage by lazy {
private val settingsStorage by lazy { MMKV.mmkvWithID(MmkvManager.ID_SETTING, MMKV.MULTI_PROCESS_MODE) } MMKV.mmkvWithID(
MmkvManager.ID_SERVER_RAW,
MMKV.MULTI_PROCESS_MODE
)
}
private val settingsStorage by lazy {
MMKV.mmkvWithID(
MmkvManager.ID_SETTING,
MMKV.MULTI_PROCESS_MODE
)
}
data class Result(var status: Boolean, var content: String) data class Result(var status: Boolean, var content: String)
@@ -32,12 +45,12 @@ object V2rayConfigUtil {
} else { } else {
raw raw
} }
Log.d(ANG_PACKAGE, customConfig) //Log.d(ANG_PACKAGE, customConfig)
return Result(true, customConfig) return Result(true, customConfig)
} }
val outbound = config.getProxyOutbound() ?: return Result(false, "") val outbound = config.getProxyOutbound() ?: return Result(false, "")
val result = getV2rayNonCustomConfig(context, outbound) val result = getV2rayNonCustomConfig(context, outbound, config.remarks)
Log.d(ANG_PACKAGE, result.content) //Log.d(ANG_PACKAGE, result.content)
return result return result
} catch (e: Exception) { } catch (e: Exception) {
e.printStackTrace() e.printStackTrace()
@@ -48,7 +61,11 @@ object V2rayConfigUtil {
/** /**
* 生成v2ray的客户端配置文件 * 生成v2ray的客户端配置文件
*/ */
private fun getV2rayNonCustomConfig(context: Context, outbound: V2rayConfig.OutboundBean): Result { private fun getV2rayNonCustomConfig(
context: Context,
outbound: V2rayConfig.OutboundBean,
remarks: String,
): Result {
val result = Result(false, "") val result = Result(false, "")
//取得默认配置 //取得默认配置
val assets = Utils.readTextFromAssets(context, "v2ray_config.json") val assets = Utils.readTextFromAssets(context, "v2ray_config.json")
@@ -60,14 +77,15 @@ object V2rayConfigUtil {
val v2rayConfig = Gson().fromJson(assets, V2rayConfig::class.java) ?: return result val v2rayConfig = Gson().fromJson(assets, V2rayConfig::class.java) ?: return result
v2rayConfig.log.loglevel = settingsStorage?.decodeString(AppConfig.PREF_LOGLEVEL) v2rayConfig.log.loglevel = settingsStorage?.decodeString(AppConfig.PREF_LOGLEVEL)
?: "warning" ?: "warning"
inbounds(v2rayConfig) inbounds(v2rayConfig)
updateOutboundWithGlobalSettings(outbound) updateOutboundWithGlobalSettings(outbound)
v2rayConfig.outbounds[0] = outbound v2rayConfig.outbounds[0] = outbound
updateOutboundFragment(v2rayConfig)
routing(v2rayConfig) routing(v2rayConfig)
fakedns(v2rayConfig) fakedns(v2rayConfig)
@@ -81,6 +99,9 @@ object V2rayConfigUtil {
v2rayConfig.stats = null v2rayConfig.stats = null
v2rayConfig.policy = null v2rayConfig.policy = null
} }
v2rayConfig.remarks = remarks
result.status = true result.status = true
result.content = v2rayConfig.toPrettyPrinting() result.content = v2rayConfig.toPrettyPrinting()
return result return result
@@ -91,8 +112,14 @@ object V2rayConfigUtil {
*/ */
private fun inbounds(v2rayConfig: V2rayConfig): Boolean { private fun inbounds(v2rayConfig: V2rayConfig): Boolean {
try { try {
val socksPort = Utils.parseInt(settingsStorage?.decodeString(AppConfig.PREF_SOCKS_PORT), AppConfig.PORT_SOCKS.toInt()) val socksPort = Utils.parseInt(
val httpPort = Utils.parseInt(settingsStorage?.decodeString(AppConfig.PREF_HTTP_PORT), AppConfig.PORT_HTTP.toInt()) settingsStorage?.decodeString(AppConfig.PREF_SOCKS_PORT),
AppConfig.PORT_SOCKS.toInt()
)
val httpPort = Utils.parseInt(
settingsStorage?.decodeString(AppConfig.PREF_HTTP_PORT),
AppConfig.PORT_HTTP.toInt()
)
v2rayConfig.inbounds.forEach { curInbound -> v2rayConfig.inbounds.forEach { curInbound ->
if (settingsStorage?.decodeBool(AppConfig.PREF_PROXY_SHARING) != true) { if (settingsStorage?.decodeBool(AppConfig.PREF_PROXY_SHARING) != true) {
@@ -102,10 +129,12 @@ object V2rayConfigUtil {
} }
v2rayConfig.inbounds[0].port = socksPort v2rayConfig.inbounds[0].port = socksPort
val fakedns = settingsStorage?.decodeBool(AppConfig.PREF_FAKE_DNS_ENABLED) val fakedns = settingsStorage?.decodeBool(AppConfig.PREF_FAKE_DNS_ENABLED)
?: false ?: false
val sniffAllTlsAndHttp = settingsStorage?.decodeBool(AppConfig.PREF_SNIFFING_ENABLED, true) val sniffAllTlsAndHttp =
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()
} }
@@ -129,11 +158,14 @@ object V2rayConfigUtil {
} }
private fun fakedns(v2rayConfig: V2rayConfig) { private fun fakedns(v2rayConfig: V2rayConfig) {
if (settingsStorage?.decodeBool(AppConfig.PREF_FAKE_DNS_ENABLED) == true) { if (settingsStorage?.decodeBool(AppConfig.PREF_LOCAL_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 == "freedom" }.forEach { v2rayConfig.outbounds.filter { it.protocol == PROTOCOL_FREEDOM && it.tag == TAG_DIRECT }
it.settings?.domainStrategy = "UseIP" .forEach {
} it.settings?.domainStrategy = "UseIP"
}
} }
} }
@@ -142,47 +174,67 @@ object V2rayConfigUtil {
*/ */
private fun routing(v2rayConfig: V2rayConfig): Boolean { private fun routing(v2rayConfig: V2rayConfig): Boolean {
try { try {
routingUserRule(settingsStorage?.decodeString(AppConfig.PREF_V2RAY_ROUTING_AGENT) routingUserRule(
?: "", AppConfig.TAG_AGENT, v2rayConfig) settingsStorage?.decodeString(AppConfig.PREF_V2RAY_ROUTING_AGENT)
routingUserRule(settingsStorage?.decodeString(AppConfig.PREF_V2RAY_ROUTING_DIRECT) ?: "", AppConfig.TAG_PROXY, v2rayConfig
?: "", AppConfig.TAG_DIRECT, v2rayConfig) )
routingUserRule(settingsStorage?.decodeString(AppConfig.PREF_V2RAY_ROUTING_BLOCKED) routingUserRule(
?: "", AppConfig.TAG_BLOCKED, v2rayConfig) settingsStorage?.decodeString(AppConfig.PREF_V2RAY_ROUTING_DIRECT)
?: "", AppConfig.TAG_DIRECT, v2rayConfig
)
routingUserRule(
settingsStorage?.decodeString(AppConfig.PREF_V2RAY_ROUTING_BLOCKED)
?: "", AppConfig.TAG_BLOCKED, v2rayConfig
)
v2rayConfig.routing.domainStrategy = settingsStorage?.decodeString(AppConfig.PREF_ROUTING_DOMAIN_STRATEGY) v2rayConfig.routing.domainStrategy =
settingsStorage?.decodeString(AppConfig.PREF_ROUTING_DOMAIN_STRATEGY)
?: "IPIfNonMatch" ?: "IPIfNonMatch"
// v2rayConfig.routing.domainMatcher = "mph" // v2rayConfig.routing.domainMatcher = "mph"
val routingMode = settingsStorage?.decodeString(AppConfig.PREF_ROUTING_MODE) ?: ERoutingMode.GLOBAL_PROXY.value val routingMode = settingsStorage?.decodeString(AppConfig.PREF_ROUTING_MODE)
?: ERoutingMode.BYPASS_LAN_MAINLAND.value
// Hardcode googleapis.cn // Hardcode googleapis.cn
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 = 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", AppConfig.TAG_DIRECT, v2rayConfig)
} }
ERoutingMode.BYPASS_MAINLAND.value -> { ERoutingMode.BYPASS_MAINLAND.value -> {
routingGeo("", "cn", AppConfig.TAG_DIRECT, v2rayConfig) routingGeo("", "cn", AppConfig.TAG_DIRECT, v2rayConfig)
routingGeo("domain", "geolocation-cn", AppConfig.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", AppConfig.TAG_DIRECT, v2rayConfig)
routingGeo("", "cn", AppConfig.TAG_DIRECT, v2rayConfig) routingGeo("", "cn", AppConfig.TAG_DIRECT, v2rayConfig)
routingGeo("domain", "geolocation-cn", AppConfig.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 = AppConfig.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
@@ -190,13 +242,17 @@ object V2rayConfigUtil {
return true return true
} }
private fun routingGeo(ipOrDomain: String, code: String, tag: String, v2rayConfig: V2rayConfig) { private fun routingGeo(
ipOrDomain: String,
code: String,
tag: String,
v2rayConfig: V2rayConfig
) {
try { try {
if (!TextUtils.isEmpty(code)) { if (!TextUtils.isEmpty(code)) {
//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")
@@ -206,7 +262,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")
@@ -223,26 +278,20 @@ 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)
} }
} }
@@ -275,51 +324,69 @@ object V2rayConfigUtil {
try { try {
if (settingsStorage?.decodeBool(AppConfig.PREF_FAKE_DNS_ENABLED) == true) { if (settingsStorage?.decodeBool(AppConfig.PREF_FAKE_DNS_ENABLED) == true) {
val geositeCn = arrayListOf("geosite:cn") val geositeCn = arrayListOf("geosite:cn")
val proxyDomain = userRule2Domian(settingsStorage?.decodeString(AppConfig.PREF_V2RAY_ROUTING_AGENT) val proxyDomain = userRule2Domian(
?: "") settingsStorage?.decodeString(AppConfig.PREF_V2RAY_ROUTING_AGENT)
val directDomain = userRule2Domian(settingsStorage?.decodeString(AppConfig.PREF_V2RAY_ROUTING_DIRECT) ?: ""
?: "") )
val directDomain = userRule2Domian(
settingsStorage?.decodeString(AppConfig.PREF_V2RAY_ROUTING_DIRECT)
?: ""
)
// fakedns with all domains to make it always top priority // fakedns with all domains to make it always top priority
v2rayConfig.dns.servers?.add(0, v2rayConfig.dns.servers?.add(
V2rayConfig.DnsBean.ServersBean(address = "fakedns", domains = geositeCn.plus(proxyDomain).plus(directDomain))) 0,
V2rayConfig.DnsBean.ServersBean(
address = "fakedns",
domains = geositeCn.plus(proxyDomain).plus(directDomain)
)
)
} }
// DNS inbound对象 // DNS inbound对象
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"
)
val localDnsPort = Utils.parseInt(settingsStorage?.decodeString(AppConfig.PREF_LOCAL_DNS_PORT), AppConfig.PORT_LOCAL_DNS.toInt()) val localDnsPort = Utils.parseInt(
settingsStorage?.decodeString(AppConfig.PREF_LOCAL_DNS_PORT),
AppConfig.PORT_LOCAL_DNS.toInt()
)
v2rayConfig.inbounds.add( v2rayConfig.inbounds.add(
V2rayConfig.InboundBean( V2rayConfig.InboundBean(
tag = "dns-in", tag = "dns-in",
port = localDnsPort, port = localDnsPort,
listen = "127.0.0.1", listen = "127.0.0.1",
protocol = "dokodemo-door", protocol = "dokodemo-door",
settings = dnsInboundSettings, settings = dnsInboundSettings,
sniffing = null)) sniffing = null
)
)
} }
// DNS outbound对象 // DNS outbound对象
if (v2rayConfig.outbounds.none { e -> e.protocol == "dns" && e.tag == "dns-out" }) { if (v2rayConfig.outbounds.none { e -> e.protocol == "dns" && e.tag == "dns-out" }) {
v2rayConfig.outbounds.add( v2rayConfig.outbounds.add(
V2rayConfig.OutboundBean( V2rayConfig.OutboundBean(
protocol = "dns", protocol = "dns",
tag = "dns-out", tag = "dns-out",
settings = null, settings = null,
streamSettings = null, streamSettings = null,
mux = null)) mux = null
)
)
} }
// DNS routing tag // DNS routing tag
v2rayConfig.routing.rules.add(0, V2rayConfig.RoutingBean.RulesBean( v2rayConfig.routing.rules.add(
type = "field", 0, V2rayConfig.RoutingBean.RulesBean(
inboundTag = arrayListOf("dns-in"), inboundTag = arrayListOf("dns-in"),
outboundTag = "dns-out", outboundTag = "dns-out",
domain = null) domain = null
)
) )
} catch (e: Exception) { } catch (e: Exception) {
e.printStackTrace() e.printStackTrace()
@@ -332,44 +399,77 @@ object V2rayConfigUtil {
try { try {
val hosts = mutableMapOf<String, String>() val hosts = mutableMapOf<String, String>()
val servers = ArrayList<Any>() val servers = ArrayList<Any>()
val remoteDns = Utils.getRemoteDnsServers()
val proxyDomain = userRule2Domian(settingsStorage?.decodeString(AppConfig.PREF_V2RAY_ROUTING_AGENT)
?: "")
//remote Dns
val remoteDns = Utils.getRemoteDnsServers()
val proxyDomain = userRule2Domian(
settingsStorage?.decodeString(AppConfig.PREF_V2RAY_ROUTING_AGENT)
?: ""
)
remoteDns.forEach { remoteDns.forEach {
servers.add(it) servers.add(it)
} }
if (proxyDomain.size > 0) { if (proxyDomain.size > 0) {
servers.add(V2rayConfig.DnsBean.ServersBean(remoteDns.first(), 53, proxyDomain, null)) servers.add(
V2rayConfig.DnsBean.ServersBean(
remoteDns.first(),
53,
proxyDomain,
null
)
)
} }
// domestic DNS // domestic DNS
val directDomain = userRule2Domian(settingsStorage?.decodeString(AppConfig.PREF_V2RAY_ROUTING_DIRECT) val domesticDns = Utils.getDomesticDnsServers()
?: "") val directDomain = userRule2Domian(
val routingMode = settingsStorage?.decodeString(AppConfig.PREF_ROUTING_MODE) ?: ERoutingMode.GLOBAL_PROXY.value settingsStorage?.decodeString(AppConfig.PREF_V2RAY_ROUTING_DIRECT)
if (directDomain.size > 0 || routingMode == ERoutingMode.BYPASS_MAINLAND.value || routingMode == ERoutingMode.BYPASS_LAN_MAINLAND.value) { ?: ""
val domesticDns = Utils.getDomesticDnsServers() )
val geositeCn = arrayListOf("geosite:cn") val routingMode = settingsStorage?.decodeString(AppConfig.PREF_ROUTING_MODE)
val geoipCn = arrayListOf("geoip:cn") ?: ERoutingMode.BYPASS_LAN_MAINLAND.value
if (directDomain.size > 0) { val isCnRoutingMode =
servers.add(V2rayConfig.DnsBean.ServersBean(domesticDns.first(), 53, directDomain, geoipCn)) (routingMode == ERoutingMode.BYPASS_MAINLAND.value || routingMode == ERoutingMode.BYPASS_LAN_MAINLAND.value)
} val geoipCn = arrayListOf("geoip:cn")
if (routingMode == ERoutingMode.BYPASS_MAINLAND.value || routingMode == ERoutingMode.BYPASS_LAN_MAINLAND.value) {
servers.add(V2rayConfig.DnsBean.ServersBean(domesticDns.first(), 53, geositeCn, geoipCn)) if (directDomain.size > 0) {
} servers.add(
if (Utils.isPureIpAddress(domesticDns.first())) { V2rayConfig.DnsBean.ServersBean(
v2rayConfig.routing.rules.add(0, V2rayConfig.RoutingBean.RulesBean( domesticDns.first(),
type = "field", 53,
outboundTag = AppConfig.TAG_DIRECT, directDomain,
port = "53", if (isCnRoutingMode) geoipCn else null
ip = arrayListOf(domesticDns.first()),
domain = null)
) )
} )
}
if (isCnRoutingMode) {
val geositeCn = arrayListOf("geosite:cn", "geosite:geolocation-cn")
servers.add(
V2rayConfig.DnsBean.ServersBean(
domesticDns.first(),
53,
geositeCn,
geoipCn
)
)
} }
val blkDomain = userRule2Domian(settingsStorage?.decodeString(AppConfig.PREF_V2RAY_ROUTING_BLOCKED) if (Utils.isPureIpAddress(domesticDns.first())) {
?: "") v2rayConfig.routing.rules.add(
0, V2rayConfig.RoutingBean.RulesBean(
outboundTag = AppConfig.TAG_DIRECT,
port = "53",
ip = arrayListOf(domesticDns.first()),
domain = null
)
)
}
//block dns
val blkDomain = userRule2Domian(
settingsStorage?.decodeString(AppConfig.PREF_V2RAY_ROUTING_BLOCKED)
?: ""
)
if (blkDomain.size > 0) { if (blkDomain.size > 0) {
hosts.putAll(blkDomain.map { it to "127.0.0.1" }) hosts.putAll(blkDomain.map { it to "127.0.0.1" })
} }
@@ -379,17 +479,19 @@ object V2rayConfigUtil {
// DNS dns对象 // DNS dns对象
v2rayConfig.dns = V2rayConfig.DnsBean( v2rayConfig.dns = V2rayConfig.DnsBean(
servers = servers, servers = servers,
hosts = hosts) hosts = hosts
)
// DNS routing // DNS routing
if (Utils.isPureIpAddress(remoteDns.first())) { if (Utils.isPureIpAddress(remoteDns.first())) {
v2rayConfig.routing.rules.add(0, V2rayConfig.RoutingBean.RulesBean( v2rayConfig.routing.rules.add(
type = "field", 0, V2rayConfig.RoutingBean.RulesBean(
outboundTag = AppConfig.TAG_AGENT, outboundTag = AppConfig.TAG_PROXY,
port = "53", port = "53",
ip = arrayListOf(remoteDns.first()), ip = arrayListOf(remoteDns.first()),
domain = null) domain = null
)
) )
} }
} catch (e: Exception) { } catch (e: Exception) {
@@ -406,6 +508,7 @@ object V2rayConfigUtil {
if (protocol.equals(EConfigType.SHADOWSOCKS.name, true) if (protocol.equals(EConfigType.SHADOWSOCKS.name, true)
|| protocol.equals(EConfigType.SOCKS.name, true) || protocol.equals(EConfigType.SOCKS.name, true)
|| protocol.equals(EConfigType.TROJAN.name, true) || protocol.equals(EConfigType.TROJAN.name, true)
|| protocol.equals(EConfigType.WIREGUARD.name, true)
) { ) {
muxEnabled = false muxEnabled = false
} else if (protocol.equals(EConfigType.VLESS.name, true) } else if (protocol.equals(EConfigType.VLESS.name, true)
@@ -428,7 +531,7 @@ object V2rayConfigUtil {
if (protocol.equals(EConfigType.WIREGUARD.name, true)) { if (protocol.equals(EConfigType.WIREGUARD.name, true)) {
var localTunAddr = if (outbound.settings?.address == null) { var localTunAddr = if (outbound.settings?.address == null) {
listOf("172.16.0.2/32", "2606:4700:110:8f81:d551:a0:532e:a2b3/128") listOf(WIREGUARD_LOCAL_ADDRESS_V4, WIREGUARD_LOCAL_ADDRESS_V6)
} else { } else {
outbound.settings?.address as List<*> outbound.settings?.address as List<*>
} }
@@ -460,6 +563,7 @@ object V2rayConfigUtil {
outbound.streamSettings?.tcpSettings?.header?.request?.headers?.Host = host!! outbound.streamSettings?.tcpSettings?.header?.request?.headers?.Host = host!!
} }
} catch (e: Exception) { } catch (e: Exception) {
e.printStackTrace() e.printStackTrace()
return false return false
@@ -467,4 +571,61 @@ object V2rayConfigUtil {
return true return true
} }
private fun updateOutboundFragment(v2rayConfig: V2rayConfig): Boolean {
try {
if (settingsStorage?.decodeBool(AppConfig.PREF_FRAGMENT_ENABLED, false) == false) {
return true
}
if (v2rayConfig.outbounds[0].streamSettings?.security != V2rayConfig.TLS
&& v2rayConfig.outbounds[0].streamSettings?.security != V2rayConfig.REALITY
) {
return true
}
val fragmentOutbound =
V2rayConfig.OutboundBean(
protocol = PROTOCOL_FREEDOM,
tag = TAG_FRAGMENT,
mux = null
)
var packets = settingsStorage?.decodeString(AppConfig.PREF_FRAGMENT_PACKETS) ?: "tlshello"
if (v2rayConfig.outbounds[0].streamSettings?.security == V2rayConfig.REALITY
&& packets == "tlshello"
) {
packets = "1-3"
} else if (v2rayConfig.outbounds[0].streamSettings?.security == V2rayConfig.TLS
&& packets != "tlshello"
) {
packets = "tlshello"
}
fragmentOutbound.settings = V2rayConfig.OutboundBean.OutSettingsBean(
fragment = V2rayConfig.OutboundBean.OutSettingsBean.FragmentBean(
packets = packets,
length = settingsStorage?.decodeString(AppConfig.PREF_FRAGMENT_LENGTH)
?: "50-100",
interval = settingsStorage?.decodeString(AppConfig.PREF_FRAGMENT_INTERVAL)
?: "10-20"
)
)
fragmentOutbound.streamSettings = V2rayConfig.OutboundBean.StreamSettingsBean(
sockopt = V2rayConfig.OutboundBean.StreamSettingsBean.SockoptBean(
TcpNoDelay = true,
mark = 255
)
)
v2rayConfig.outbounds.add(fragmentOutbound)
//proxy chain
v2rayConfig.outbounds[0].streamSettings?.sockopt =
V2rayConfig.OutboundBean.StreamSettingsBean.SockoptBean(
dialerProxy = TAG_FRAGMENT
)
} catch (e: Exception) {
e.printStackTrace()
return false
}
return true
}
} }

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

@@ -1,7 +1,11 @@
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.os.Build import android.os.Build
import android.util.Log import android.util.Log
import android.view.LayoutInflater import android.view.LayoutInflater
@@ -17,12 +21,23 @@ 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.util.Collections
class MainViewModel(application: Application) : AndroidViewModel(application) { class MainViewModel(application: Application) : AndroidViewModel(application) {
private val mainStorage by lazy { private val mainStorage by lazy {
@@ -97,9 +112,9 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
fun appendCustomConfigServer(server: String) { fun appendCustomConfigServer(server: String) {
val config = ServerConfig.create(EConfigType.CUSTOM) val config = ServerConfig.create(EConfigType.CUSTOM)
config.remarks = System.currentTimeMillis().toString()
config.subscriptionId = subscriptionId config.subscriptionId = subscriptionId
config.fullConfig = Gson().fromJson(server, V2rayConfig::class.java) config.fullConfig = Gson().fromJson(server, V2rayConfig::class.java)
config.remarks = config.fullConfig?.remarks ?: System.currentTimeMillis().toString()
val key = MmkvManager.encodeServerConfig("", config) val key = MmkvManager.encodeServerConfig("", config)
serverRawStorage?.encode(key, server) serverRawStorage?.encode(key, server)
serverList.add(0, key) serverList.add(0, key)
@@ -130,7 +145,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 +168,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

View File

@@ -8,42 +8,59 @@ 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,
AppConfig.PREF_V2RAY_ROUTING_BLOCKED, AppConfig.PREF_V2RAY_ROUTING_BLOCKED,
AppConfig.PREF_V2RAY_ROUTING_DIRECT, AppConfig.PREF_V2RAY_ROUTING_DIRECT,
AppConfig.SUBSCRIPTION_AUTO_UPDATE_INTERVAL, AppConfig.SUBSCRIPTION_AUTO_UPDATE_INTERVAL,
AppConfig.PREF_MUX_XUDP_QUIC, -> { AppConfig.PREF_FRAGMENT_PACKETS,
AppConfig.PREF_FRAGMENT_LENGTH,
AppConfig.PREF_FRAGMENT_INTERVAL,
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,
@@ -55,19 +72,27 @@ class SettingsViewModel(application: Application) : AndroidViewModel(application
AppConfig.PREF_CONFIRM_REMOVE, AppConfig.PREF_CONFIRM_REMOVE,
AppConfig.PREF_START_SCAN_IMMEDIATE, AppConfig.PREF_START_SCAN_IMMEDIATE,
AppConfig.SUBSCRIPTION_AUTO_UPDATE, AppConfig.SUBSCRIPTION_AUTO_UPDATE,
AppConfig.PREF_MUX_ENABLED, -> { AppConfig.PREF_FRAGMENT_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>

View File

@@ -1,9 +1,9 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?> <?xml version="1.0" encoding="UTF-8" standalone="no"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android" <vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp" android:height="24dp"
android:viewportHeight="24.0"
android:viewportWidth="24.0" android:viewportWidth="24.0"
android:width="24dp"> android:viewportHeight="24.0">
<path <path
android:fillColor="#000000" android:fillColor="#000000"
android:pathData="M3,5h2L5,3c-1.1,0 -2,0.9 -2,2zM3,13h2v-2L3,11v2zM7,21h2v-2L7,19v2zM3,9h2L5,7L3,7v2zM13,3h-2v2h2L13,3zM19,3v2h2c0,-1.1 -0.9,-2 -2,-2zM5,21v-2L3,19c0,1.1 0.9,2 2,2zM3,17h2v-2L3,15v2zM9,3L7,3v2h2L9,3zM11,21h2v-2h-2v2zM19,13h2v-2h-2v2zM19,21c1.1,0 2,-0.9 2,-2h-2v2zM19,9h2L21,7h-2v2zM19,17h2v-2h-2v2zM15,21h2v-2h-2v2zM15,5h2L17,3h-2v2zM7,17h10L17,7L7,7v10zM9,9h6v6L9,15L9,9z" /> android:pathData="M3,5h2L5,3c-1.1,0 -2,0.9 -2,2zM3,13h2v-2L3,11v2zM7,21h2v-2L7,19v2zM3,9h2L5,7L3,7v2zM13,3h-2v2h2L13,3zM19,3v2h2c0,-1.1 -0.9,-2 -2,-2zM5,21v-2L3,19c0,1.1 0.9,2 2,2zM3,17h2v-2L3,15v2zM9,3L7,3v2h2L9,3zM11,21h2v-2h-2v2zM19,13h2v-2h-2v2zM19,21c1.1,0 2,-0.9 2,-2h-2v2zM19,9h2L21,7h-2v2zM19,17h2v-2h-2v2zM15,21h2v-2h-2v2zM15,5h2L17,3h-2v2zM7,17h10L17,7L7,7v10zM9,9h6v6L9,15L9,9z" />

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="#000000"
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="#000000"
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

@@ -1,7 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<shape
xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<solid android:color="@color/colorBg" />
<size android:width="44dp" android:height="44dp" />
</shape>

View File

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

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="#FF000000" android:fillColor="#FF000000"
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>

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