Compare commits

...

159 Commits

Author SHA1 Message Date
2dust
cdb050b6c5 up 1.8.13 2024-01-08 09:13:42 +08:00
2dust
21f47b536d Bug fix 2024-01-07 17:15:09 +08:00
2dust
f14484c986 Merge pull request #2733 from solokot/master
Russian translation
2024-01-07 16:31:38 +08:00
solokot
358dd78dc3 Russian translation
Small cosmetic fixes
2024-01-07 06:58:48 +00:00
2dust
d79e072316 Merge pull request #2732 from admarty/master
Improve Vietnamese translation
2024-01-07 09:25:27 +08:00
2dust
c24dee567a Modify background color 2024-01-07 09:24:48 +08:00
admarty
9f75bafcea Update strings.xml 2024-01-06 21:11:34 +07:00
admarty
2150452629 Update strings.xml 2024-01-06 20:50:50 +07:00
admarty
507a32a08e Improve Vietnamese translation 2024-01-06 20:22:52 +07:00
2dust
44dee8e850 Add main interface filter cache 2024-01-06 16:56:19 +08:00
2dust
5e4d9246c2 Merge pull request #2721 from mosayeb-a/fix-bug
fix a bug(ConcurrentModificationException)
2024-01-05 12:12:22 +08:00
mosayeb
9fe7419467 fix a bug(ConcurrentModificationException) 2024-01-03 11:15:00 -08:00
2dust
41b2251dfe Merge pull request #2718 from yuhan6665/wireguard
Wireguard can configure tun address
2024-01-02 12:06:08 +08:00
yuhan6665
73fad43573 Handle IPv6 address correctly 2024-01-01 22:16:07 -05:00
yuhan6665
4676717582 Wireguard can configure tun address
local tun address is also used by Wireguard server to check against its "allowedIPs" setting
2024-01-01 21:49:54 -05:00
2dust
834766e6e7 Merge pull request #2704 from yuhan6665/wireguard
Add Wireguard maunal config
2023-12-24 16:32:26 +08:00
yuhan6665
e304dce347 Add Wireguard maunal config
No support for export and import via links yet
2023-12-23 23:51:09 -05:00
2dust
61654aefeb Merge pull request #2690 from user09283/patch-1
Update strings.xml
2023-12-18 15:49:06 +08:00
user09283
1664aaa25b Update strings.xml
Update Vietnamese language
2023-12-18 00:41:47 +07:00
2dust
68f1f64f3d Merge pull request #2674 from mmrabbani/patch-1
Add HTTP3 to ALPN options
2023-12-14 09:20:11 +08:00
2dust
7bad57ca52 Merge pull request #2671 from solokot/master
Update Russian translation
2023-12-14 09:18:57 +08:00
MMR
1c8e1f0993 Add HTTP3 to ALPN options 2023-12-13 16:01:12 +03:30
solokot
6037ae6fc4 Update Russian translation 2023-12-09 18:59:33 +03:00
2dust
dc1c5400b8 Add mux concurency setting 2023-12-07 17:49:09 +08:00
2dust
9ae4688171 Update build.yml 2023-12-05 17:52:45 +08:00
2dust
e3f39234b2 Bug fix 2023-12-05 16:51:31 +08:00
2dust
0df0b2d6ac Merge pull request #2662 from solokot/master
Update Russian
2023-12-05 16:43:00 +08:00
2dust
9ce96d0591 up 1.8.12 2023-12-04 17:13:03 +08:00
2dust
a7ef0618ba Adjust FOREGROUND_SERVICE_SPECIAL_USE 2023-12-04 17:11:53 +08:00
solokot
cc9b083e5d Update Russian 2023-12-04 10:00:31 +03:00
2dust
5af322552c Merge pull request #2660 from yuhan6665/xudp
Use Android ID as XUDP basekey
2023-12-04 12:14:38 +08:00
yuhan6665
fc852281dd Use Android ID as XUDP basekey
Since Xray 1.8.1, XUDP pass basekey as the global ID. It can maintain the same UDP port on the proxy server outbound.
To enable maximum UDP connectivity, client should pass the device unique ID in the environment variable.
2023-12-03 22:06:06 -05:00
2dust
0b2f036a22 Up targetSdkVersion 34 2023-12-04 10:55:26 +08:00
2dust
cf2becb5e9 Bug fix 2023-12-04 08:52:26 +08:00
2dust
82d8eba1b9 Merge pull request #2636 from hadi-norouzi/feature/update_sub
Fix minimum update interval for subscription auto update
2023-11-21 16:47:17 +08:00
Hadi Norouzi
286ad34d94 fix minimum subscription update interval 2023-11-20 21:25:35 +03:30
Hadi Norouzi
ff0bc6594d Merge branch 'master' into feature/update_sub 2023-11-20 21:23:38 +03:30
Hadi Norouzi
7b8113aef1 Merge remote-tracking branch 'origin/feature/update_sub' into feature/update_sub
# Conflicts:
#	V2rayNG/app/build.gradle
2023-11-20 21:21:24 +03:30
2dust
81d2ef5db5 Merge pull request #2421 from Fangliding/master
自己写了个小破自动编译action
2023-11-18 18:56:13 +08:00
2dust
bdea3ef88c up 1.8.11 2023-11-18 17:24:37 +08:00
2dust
c62c86fc29 Adjust UI 2023-11-18 16:39:04 +08:00
2dust
59f698f755 Merge pull request #2614 from solokot/master
Update Russian translation
2023-11-18 16:32:06 +08:00
solokot
9ac979006e Update Russian translation 2023-11-17 20:44:48 +03:00
2dust
da6291a965 Add menu for privacy policy 2023-11-17 16:29:32 +08:00
2dust
bf6555e57c Update CR.md 2023-11-17 14:15:29 +08:00
2dust
35b114220e Update CR.md 2023-11-17 10:15:10 +08:00
Hadi Norouzi
800bb6a4e9 Merge branch 'master' of https://github.com/2dust/v2rayNG 2023-11-15 23:51:10 +03:30
2dust
2c80521f5b Merge pull request #2605 from solokot/master
Update Russian translation
2023-11-15 19:40:00 +08:00
2dust
6351ce5991 up 1.8.10 2023-11-15 19:35:16 +08:00
2dust
683362f0ee Fix mux enabled 2023-11-15 19:17:02 +08:00
2dust
84fc909339 Adjust UI 2023-11-15 17:22:16 +08:00
solokot
74171e26db Update Russian translation
Automatic update subscriptions
2023-11-15 11:20:55 +03:00
2dust
f25c0cc890 Fix Automatic update subscriptions 2023-11-15 15:27:53 +08:00
2dust
29848053a4 Revert "Merge pull request #2603 from maskedeken/fix-mux"
This reverts commit 0d9856919e, reversing
changes made to 3dde6b0ca3.
2023-11-15 14:17:21 +08:00
2dust
0d9856919e Merge pull request #2603 from maskedeken/fix-mux
fix mux not working
2023-11-15 14:12:33 +08:00
Eken Chan
0ae7f2f7b3 fix mux not working 2023-11-14 15:42:00 +08:00
2dust
3dde6b0ca3 Fix Automatic update subscriptions 2023-11-14 09:32:05 +08:00
2dust
6b28208044 Fix Automatic update subscriptions 2023-11-13 18:00:00 +08:00
2dust
13f855e3c4 remove android.intent.category.LEANBACK_LAUNCHER 2023-11-13 12:42:57 +08:00
2dust
c870595e98 Merge pull request #2596 from solokot/master
Russian translation
2023-11-13 12:39:24 +08:00
2dust
ae19a3f68d Merge pull request #2471 from hadi-norouzi/feature/update_sub
add auto update subscriptions with interval
2023-11-13 12:38:54 +08:00
2dust
545afc41b3 Merge branch 'master' into feature/update_sub 2023-11-13 12:37:59 +08:00
solokot
7177d88144 Russian translation
Fix Google translate
2023-11-10 19:48:26 +03:00
2dust
294ed50afd Merge pull request #2592 from hossinasaadi/master
fix ServerActivity.kt toast
2023-11-10 15:19:28 +08:00
2dust
0105fe48f7 Merge pull request #2590 from yuhan6665/xudp
Add menu options for Mux
2023-11-10 15:18:44 +08:00
Hossin Asaadi
c7ff23e3d5 Update ServerActivity.kt
fix trojan, shadowsocks password toast
2023-11-08 16:30:04 +04:00
yuhan6665
23e9d7fde5 Add menu options for Mux 2023-11-05 23:16:01 -05:00
Hadi Norouzi
c93edd8875 Merge branch '2dust:master' into master 2023-10-05 20:13:50 +03:30
2dust
bde37e38a7 up 1.8.9 2023-09-30 16:29:37 +08:00
2dust
c401d63d2f Merge pull request #2543 from yuhan6665/rprx-ui
Unified base activity for status bar icon color
2023-09-30 14:26:28 +08:00
yuhan6665
52416dd43d Unified base activity for status bar icon color 2023-09-29 19:59:06 -04:00
2dust
59bd7128ae Merge pull request #2540 from MrIbrahem/patch-1
Update strings.xml
2023-09-29 13:58:58 +08:00
2dust
87f16467bb Merge pull request #2539 from MrIbrahem/update
Update strings.xml
2023-09-29 13:58:46 +08:00
ibrahem Qasim
bed0fd00bd Update strings.xml
update arabic string
2023-09-28 00:32:18 +03:00
ibrahem Qasim
0672af98f8 Update strings.xml 2023-09-28 00:23:40 +03:00
2dust
2341eceb65 up 1.8.8 2023-09-25 11:32:57 +08:00
2dust
ec5f7245bf up 1.8.7 2023-09-25 11:20:21 +08:00
Hadi Norouzi
caa2edcf05 bump version 2023-09-12 22:32:41 +03:30
Hadi Norouzi
dbe78d0aa5 Merge remote-tracking branch 'origin/master' into feature/update_sub 2023-09-12 22:28:15 +03:30
Hadi Norouzi
71f2f590a7 build a version from auto update subscription 2023-09-12 22:20:28 +03:30
Hadi Norouzi
ed26120581 Merge branch '2dust:master' into master 2023-09-12 22:08:12 +03:30
风扇滑翔翼
71bd684b46 Update build.yml 2023-09-08 23:51:26 +08:00
风扇滑翔翼
02ae19f0c7 Merge remote-tracking branch 'upstream/master' 2023-09-08 15:25:46 +00:00
2dust
a46b0d58eb Merge pull request #2500 from MrIbrahem/master
Update arrays.xml to support Arabic language
2023-09-06 07:21:10 +08:00
ibrahem Qasim
d1262d169b Update arrays.xml 2023-09-01 17:39:16 +03:00
2dust
4a85c95b02 Merge pull request #2485 from y67h41/fix-/0-CIDR
Make /0 CIDR parsed as IP
2023-09-01 22:33:13 +08:00
2dust
a368927b9f Merge pull request #2475 from user09283/master-1
Update strings.xml
2023-09-01 22:31:51 +08:00
2dust
80feb69af5 Merge pull request #2460 from MrIbrahem/master
Create arabic translations
2023-09-01 22:31:38 +08:00
2dust
c09f0d5787 Merge pull request #2478 from yuhan6665/rprx-ui
A freshing new UI
2023-09-01 22:31:13 +08:00
y67h41
ff667546b8 Update ExampleUnitTest.kt 2023-08-24 17:55:12 +08:00
y67h41
6a255cdfa4 Make /0 CIDR be parsed as IP 2023-08-24 17:36:49 +08:00
yuhan6665
2b5784df6f Update UI: status bar icon color 2023-08-20 22:09:30 -04:00
user09283
a9665dd2d8 Update strings.xml 2023-08-19 13:11:14 +07:00
user09283
1e52877e93 Update strings.xml
Update Vietnamese language
2023-08-19 13:10:08 +07:00
Hadi Norouzi
062c0d8ddb add auto update subscriptions with interval 2023-08-17 21:06:22 +03:30
yuhan6665
a7a70b448f Update UI: tweak fab color 2023-08-15 20:14:43 -04:00
yuhan6665
3e52aeb804 Update UI: custom font 2023-08-13 23:10:20 -04:00
yuhan6665
4b4f5d145b Update UI: a concept for launching icon 2023-08-13 15:01:07 -04:00
风扇滑翔翼
775fa5ea62 update and remove unnecessary step 2023-08-12 00:54:19 +08:00
ibrahem Qasim
34d8329c8a Delete plurals.xml 2023-08-10 21:12:02 +03:00
ibrahem Qasim
2e7ae732aa Update strings.xml 2023-08-10 21:10:10 +03:00
ibrahem Qasim
424287e258 Rename s.xml to strings.xml 2023-08-10 13:26:25 +03:00
ibrahem Qasim
40d03bbb96 Delete strings.xml 2023-08-10 13:26:05 +03:00
ibrahem Qasim
d768694445 Update s.xml 2023-08-10 13:18:58 +03:00
ibrahem Qasim
56c7f2ef69 Update s.xml 2023-08-10 13:13:15 +03:00
ibrahem Qasim
3839b0c59c Update s.xml 2023-08-10 13:07:36 +03:00
ibrahem Qasim
77cab14ae8 Create s.xml 2023-08-10 12:58:37 +03:00
ibrahem Qasim
b0dbd4c7ca Create plurals.xml 2023-08-10 12:38:18 +03:00
ibrahem Qasim
50ca2e0e69 Create strings.xml 2023-08-10 12:36:43 +03:00
yuhan6665
0bf8beda94 Update UI: tweak after review 2023-08-09 21:38:20 -04:00
yuhan6665
4d334929c9 Update UI: complete change for dark mode 2023-08-07 14:32:41 -04:00
yuhan6665
ccfdf096f9 Update UI dark mode 2023-08-06 22:44:57 -04:00
yuhan6665
b4f2af2778 Update UI: header image 2023-08-06 21:53:14 -04:00
yuhan6665
32b9e4855c Update UI: clean up theme 2023-08-06 18:41:36 -04:00
yuhan6665
bdbce5147e Update UI: menu icons 2023-08-06 17:37:24 -04:00
yuhan6665
a4833506ae Update UI: launching icon 2023-08-06 16:55:29 -04:00
yuhan6665
d4e8072248 Update UI for white theme 2023-08-06 16:09:14 -04:00
yuhan6665
207ca93735 Fix lint by adding missing translation 2023-08-06 10:31:37 -04:00
2dust
99307ab8f0 up 1.8.6 2023-07-30 11:05:31 +08:00
风扇滑翔翼
a5bb39ac8a 允许手动指定编译时的Xray core版本
and update sth
2023-07-23 11:16:06 +08:00
风扇滑翔翼
073c7c0410 新增触发条件 2023-07-23 03:29:07 +08:00
风扇滑翔翼
7e88e3ba4f 新建Build action 2023-07-23 03:23:02 +08:00
2dust
1ec23a7b39 Merge pull request #2302 from dep4/fix/package-name
AppConfig: use current package name to receive broadcasts
2023-05-23 06:15:07 +08:00
Bruce Mills
685f9e220c AppConfig: use current package name to receive broadcasts 2023-05-21 15:54:25 -04:00
2dust
e77b7eb52e Merge pull request #2290 from CUMOON/master
Correction of Persian translation
2023-05-21 08:35:38 +08:00
2dust
29014704e0 Merge pull request #2288 from hadi-norouzi/feature/tv
feature: add leanback for android tv compatiblity
2023-05-21 08:35:13 +08:00
Moon
b3570d9c0b Correction of Persian translation 2023-05-17 12:49:41 +03:30
Hadi Norouzi
6e427cee82 feature: add leanback for android tv compatiblity 2023-05-16 11:33:29 +03:30
2dust
0a1b6d00d9 Merge pull request #2284 from solokot/master
RU: Improvements for google translation
2023-05-15 20:24:19 +08:00
solokot
00ffe66f36 RU: Improvements for google translation 2023-05-15 09:13:11 +03:00
2dust
63661fbdaa Merge pull request #2273 from hadi-norouzi/master
[Feature] separate package names for pre release and dev builds
2023-05-13 19:25:53 +08:00
Hadi Norouzi
4d4a4543c5 feature: import subscription from v2rayng://install-sub deeplink 2023-05-11 20:42:15 +03:30
Hadi Norouzi
2349805968 feature: separate package names for pre release and dev builds 2023-05-10 20:16:11 +03:30
2dust
bdbd0b154e up 1.8.5 2023-05-07 15:35:42 +08:00
2dust
653137ec58 bug fixes 2023-05-07 15:34:11 +08:00
2dust
39f65850be Merge pull request #2258 from Amir-yazdanmanesh/master
Fix persian translation
2023-05-07 15:19:33 +08:00
2dust
2d8db97417 Merge branch 'master' into master 2023-05-07 15:19:25 +08:00
2dust
17a6f80798 Merge pull request #2255 from hadi-norouzi/feature/share_sub
Feature/share sub
2023-05-07 15:18:28 +08:00
2dust
381fe859ff Merge pull request #2254 from hadi-norouzi/master
Fix status bar and navigation bar, remove FooterView TouchHelper
2023-05-07 15:14:04 +08:00
am.yazdanmanesh
e2bdf17b82 Fix persian translation 2023-05-05 21:54:18 +03:30
Hadi Norouzi
d20afc6801 Share subscription implemented 2023-05-04 18:03:50 +03:30
Hadi Norouzi
54046a27e6 Fix fa strings and test layout direction 2023-05-04 17:04:35 +03:30
Hadi Norouzi
36c000d18a Fix status bar and navigation bar, remove FooterView TouchHelper 2023-05-04 16:34:41 +03:30
2dust
2f3c2cf4d5 up 1.8.4 2023-04-29 20:46:28 +08:00
2dust
892358d5d8 Merge pull request #2227 from AlirezaIvaz/master
Add themed icon support
2023-04-27 09:40:08 +08:00
Alireza Ivaz
6dced903cd Add themed icon support 2023-04-26 17:05:11 +03:30
2dust
f8a98a426e Merge pull request #2213 from solokot/master
Updated Russian translation
2023-04-22 14:40:57 +08:00
solokot
011506e99f Updated Russian translation 2023-04-21 21:33:22 +03:00
2dust
966151d3fe bug fixes 2023-04-19 19:45:11 +08:00
2dust
247f4db77e up 1.8.3 2023-04-18 09:51:49 +08:00
2dust
9a661bc401 Use Google code scanner 2023-04-17 10:28:34 +08:00
2dust
f5f1b3816c Refactor createQRCode 2023-04-16 19:58:51 +08:00
2dust
3971c9badc use Exception 2023-04-16 19:56:44 +08:00
2dust
390fbf046b Merge pull request #2130 from pouriaksrvi/master
[New Feature] UX enhancement - Reversed the order of server list
2023-04-06 08:28:54 +08:00
Programmer
ec7ba59528 Bug Fix - Fixed the order of subscription import
Subscription server list should not be reversed when it is being imported. Fixed this problem.
2023-04-03 04:12:01 +03:30
Programmer
838941c6a4 Merge branch '2dust:master' into master 2023-04-03 04:00:52 +03:30
2dust
a0ec764b64 Merge pull request #2142 from solokot/master
Updated Russian translation
2023-04-02 09:32:29 +08:00
solokot
4cfe3394b1 Updated Russian translation 2023-03-29 09:35:28 +03:00
Programmer
ae9aa75ba0 New Feature
Fixed incorrect default order of server list as described in the issue https://github.com/2dust/v2rayNG/issues/2116
The order was problematic for users who add new servers more frequently
2023-03-26 06:27:52 +00:00
151 changed files with 2288 additions and 577 deletions

64
.github/workflows/build.yml vendored Normal file
View File

@@ -0,0 +1,64 @@
name: Build APK
on:
push:
workflow_dispatch:
inputs:
XRAY_CORE_VERSION:
description: 'Xray core version or commit hash'
required: false
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Setup Java
uses: actions/setup-java@v3
with:
distribution: 'temurin'
java-version: '17'
- name: Setup Golang
uses: actions/setup-go@v4
with:
go-version: '1.21.4'
- name: Install gomobile
run: |
go install golang.org/x/mobile/cmd/gomobile@latest
echo "$(go env GOPATH)/bin" >> $GITHUB_PATH
- name: Setup Android environment
uses: android-actions/setup-android@v2
- name: Build dependencies
run: |
mkdir ${{ github.workspace }}/build
cd ${{ github.workspace }}/build
git clone --depth=1 -b main https://github.com/2dust/AndroidLibXrayLite.git
cd AndroidLibXrayLite
go get github.com/xtls/xray-core@${{ github.event.inputs.XRAY_CORE_VERSION }} || true
gomobile init
go mod tidy -v
gomobile bind -v -androidapi 19 -ldflags='-s -w' ./
cp *.aar ${{ github.workspace }}/V2rayNG/app/libs/
- name: Build APK
run: |
cd ${{ github.workspace }}/V2rayNG
chmod 777 *
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
- name: Upload APK
uses: actions/upload-artifact@v3
with:
name: apk
path: ${{ github.workspace }}/V2rayNG/app/build/outputs/apk/debug/

41
CR.md
View File

@@ -1,29 +1,40 @@
v2rayNG 隐私条款 **v2rayNG 隐私权政策**
最后更新 2017-11-22 本政策自2023年11月17日起施行
v2rayNG 尊重并保护所有用户的个人隐私权,为此我们向大众公开这份隐私条款。**您使用 v2rayNG 即代表您以阅读并同意了这份条款,如果您不同意这份条款请立即停止使用并卸载 v2rayNG。** 2dust 将 v2rayNG 应用程序构建为开源应用程序。 本服务由 2dust 免费提供,并且旨在按原样使用。
1. 信息收集 v2rayNG 尊重并保护所有用户的个人隐私权,为此我们向大众公开这份隐私权政策。**您使用 v2rayNG 即代表您以阅读并同意了这份条款,如果您不同意这份条款请立即停止使用并卸载 v2rayNG。**
v2rayNG 软件自身不会发送任何信息到开发者,但是您下载软件的应用市场(如 Google Play可能会收集关于应用运行状态的相关信息并提供给 v2rayNG 开发者。有关这些信息,请阅读您使用的应用市场所提供的隐私条款。 **信息收集**
v2rayNG 软件中可能包含需要通过 IAP 支付解锁的功能,您的支付信息将由相关的 IAP 渠道进行处理,而我们对支付信息没有访问权 v2rayNG 软件自身不会发送任何信息到开发者,但是您下载软件的应用市场(如 Google Play可能会收集关于应用运行状态的相关信息并提供给 v2rayNG 开发者。有关这些信息,请阅读您使用的应用市场所提供的隐私权政策
当您向 v2rayNG 开发者反馈软件运行中的错误时,开发者可能会要求您提供软件以及系统的日志以帮助确认问题的原因。因日志中可能包括敏感信息,此类信息只能由您自己操作发送。**我们不对任何传输服务的安全性和隐私性做任何明示或暗示的担保,请您在传送相关信息时选择可以您自身可以接受的方式。** v2rayNG 软件中可能包含需要通过 IAP 支付解锁的功能,您的支付信息将由相关的 IAP 渠道进行处理,而我们对支付信息没有访问权。
2. 信息共享 当您向 v2rayNG 开发者反馈软件运行中的错误时,开发者可能会要求您提供软件以及系统的日志以帮助确认问题的原因。因日志中可能包括敏感信息,此类信息只能由您自己操作发送。**我们不对任何传输服务的安全性和隐私性做任何明示或暗示的担保,请您在传送相关信息时选择可以您自身可以接受的方式。**
我们不会向任何第三方出售收集到的用户数据。我们可能向外部开发者提供信息以协助软件的开发,但是在提供信息之前我们会传达相关保密义务并确定其可以遵守。 **信息共享**
3. 信息存留 我们不会向任何第三方出售收集到的用户数据。我们可能向外部开发者提供信息以协助软件的开发,但是在提供信息之前我们会传达相关保密义务并确定其可以遵守。
除非有相关法律规定,我们会在 30 天内清除不需要继续使用的用户数据,或将统计数据整合为无法识别单个用户的综合报告。 **信息存留**
4. 信息泄露 除非有相关法律规定,我们会在 30 天内清除不需要继续使用的用户数据,或将统计数据整合为无法识别单个用户的综合报告。
我们会使用合理的技术和安全手段尽力保护用户的数据,但是无法保证数据的绝对安全。如果我们确认数据发生了泄露,我们会在 7 天内通过可用的渠道通知用户。**您同意不向我们追责任何因不可抗力而造成的损失。** **信息泄露**
5. 条款修改 我们会使用合理的技术和安全手段尽力保护用户的数据,但是无法保证数据的绝对安全。如果我们确认数据发生了泄露,我们会在 7 天内通过可用的渠道通知用户。**您同意不向我们追责任何因不可抗力而造成的损失。**
**儿童隐私**
这些服务不针对 13 岁以下的任何人。我不会故意收集 13 岁以下儿童的个人身份信息。 如果我发现 13 岁以下的儿童向我提供了个人信息,我会立即从我们的服务器中删除该信息。 如果您是父母或监护人,并且您知道您的孩子向我们提供了个人信息,请与我联系,以便我能够采取必要的行动。
**条款修改**
我们保留修改这份隐私权政策的权利,但是会确保在更新条款前至少 30 天通过我们的可用渠道和应用内提示来通知用户。**在新条款生效后继续使用软件即表示您同意修改后的隐私权政策。**
**联系我们**
如果您对我的隐私政策有任何疑问或建议,请随时通过 CaptainIronng@protonmail.com 与我联系。
我们保留修改这份隐私条款的权利,但是会确保在更新条款前至少 30 天通过我们的可用渠道和应用内提示来通知用户。**在新条款生效后继续使用软件即表示您同意修改后的隐私条款。**

View File

@@ -8,8 +8,8 @@ android {
buildToolsVersion "$buildToolsVer" buildToolsVersion "$buildToolsVer"
compileOptions { compileOptions {
targetCompatibility = "8" sourceCompatibility = JavaVersion.VERSION_17
sourceCompatibility = "8" targetCompatibility = JavaVersion.VERSION_17
} }
defaultConfig { defaultConfig {
@@ -17,8 +17,8 @@ android {
minSdkVersion 21 minSdkVersion 21
targetSdkVersion Integer.parseInt("$targetSdkVer") targetSdkVersion Integer.parseInt("$targetSdkVer")
multiDexEnabled true multiDexEnabled true
versionCode 510 versionCode 538
versionName "1.8.2" versionName "1.8.13"
} }
buildTypes { buildTypes {
@@ -36,6 +36,21 @@ android {
} }
} }
// flavorDimensions "versions"
//
// productFlavors {
// dev {
// applicationIdSuffix = ".dev"
// versionNameSuffix = "-dev"
// }
// pre_release {
// applicationIdSuffix = ".pre"
// versionNameSuffix = "-pre-release"
// }
// prod {
// }
// }
sourceSets { sourceSets {
main { main {
jniLibs.srcDirs = ['libs'] jniLibs.srcDirs = ['libs']
@@ -72,6 +87,7 @@ android {
buildFeatures { buildFeatures {
viewBinding true viewBinding true
buildConfig true
} }
namespace 'com.v2ray.ang' namespace 'com.v2ray.ang'
testNamespace 'com.v2ray.angTest' testNamespace 'com.v2ray.angTest'
@@ -85,16 +101,16 @@ dependencies {
implementation 'androidx.constraintlayout:constraintlayout:2.1.4' implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
implementation 'androidx.legacy:legacy-support-v4:1.0.0' implementation 'androidx.legacy:legacy-support-v4:1.0.0'
implementation 'androidx.appcompat:appcompat:1.6.1' implementation 'androidx.appcompat:appcompat:1.6.1'
implementation 'com.google.android.material:material:1.8.0' implementation 'com.google.android.material:material:1.9.0'
implementation 'androidx.cardview:cardview:1.0.0' implementation 'androidx.cardview:cardview:1.0.0'
implementation 'androidx.preference:preference-ktx:1.2.0' implementation 'androidx.preference:preference-ktx:1.2.0'
implementation 'androidx.recyclerview:recyclerview:1.3.0' implementation 'androidx.recyclerview:recyclerview:1.3.0'
implementation 'androidx.fragment:fragment-ktx:1.5.6' implementation 'androidx.fragment:fragment-ktx:1.5.7'
implementation 'androidx.multidex:multidex:2.0.1' implementation 'androidx.multidex:multidex:2.0.1'
implementation 'androidx.viewpager2:viewpager2:1.1.0-beta01' implementation 'androidx.viewpager2:viewpager2:1.1.0-beta01'
// Androidx ktx // Androidx ktx
implementation 'androidx.activity:activity-ktx:1.7.0' implementation 'androidx.activity:activity-ktx:1.7.1'
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.1' implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.1'
implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.6.1' implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.6.1'
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.6.1' implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.6.1'
@@ -109,11 +125,16 @@ dependencies {
implementation 'io.reactivex:rxjava:1.3.8' implementation 'io.reactivex:rxjava:1.3.8'
implementation 'io.reactivex:rxandroid:1.2.1' implementation 'io.reactivex:rxandroid:1.2.1'
implementation 'com.tbruyelle.rxpermissions:rxpermissions:0.9.4@aar' implementation 'com.tbruyelle.rxpermissions:rxpermissions:0.9.4@aar'
implementation 'me.dm7.barcodescanner:core:1.9.8'
implementation 'me.dm7.barcodescanner:zxing:1.9.8'
implementation 'com.github.jorgecastilloprz:fabprogresscircle:1.01@aar' implementation 'com.github.jorgecastilloprz:fabprogresscircle:1.01@aar'
implementation 'me.drakeet.support:toastcompat:1.1.0' implementation 'me.drakeet.support:toastcompat:1.1.0'
implementation 'com.blacksquircle.ui:editorkit:2.1.1' implementation 'com.blacksquircle.ui:editorkit:2.8.0'
implementation 'com.blacksquircle.ui:language-base:2.1.1' implementation 'com.blacksquircle.ui:language-base:2.8.0'
implementation 'com.blacksquircle.ui:language-json:2.1.1' implementation 'com.blacksquircle.ui:language-json:2.8.0'
implementation 'io.github.g00fy2.quickie:quickie-bundled:1.6.0'
implementation 'com.google.zxing:core:3.5.1'
def work_version = "2.8.1"
implementation "androidx.work:work-runtime-ktx:$work_version"
implementation "androidx.work:work-multiprocess:$work_version"
} }

View File

@@ -1,13 +0,0 @@
package com.v2ray.ang;
import android.app.Application;
import android.test.ApplicationTestCase;
/**
* <a href="http://d.android.com/tools/testing/testing_android.html">Testing Fundamentals</a>
*/
public class ApplicationTest extends ApplicationTestCase<Application> {
public ApplicationTest() {
super(Application.class);
}
}

View File

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<item name="app_name" type="string">v2rayNG (DEV)</item>
</resources>

View File

@@ -1,6 +1,7 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"> xmlns:tools="http://schemas.android.com/tools"
tools:ignore="MissingLeanbackLauncher">
<supports-screens <supports-screens
android:anyDensity="true" android:anyDensity="true"
@@ -9,8 +10,12 @@
android:largeScreens="true" android:largeScreens="true"
android:xlargeScreens="true"/> android:xlargeScreens="true"/>
<uses-sdk android:minSdkVersion="21" tools:overrideLibrary="com.blacksquircle.ui.editorkit"/>
<uses-feature android:name="android.hardware.camera" android:required="false"/> <uses-feature android:name="android.hardware.camera" android:required="false"/>
<uses-feature android:name="android.hardware.camera.autofocus" android:required="false"/> <uses-feature android:name="android.hardware.camera.autofocus" android:required="false"/>
<uses-feature android:name="android.software.leanback" android:required="false" />
<uses-feature android:name="android.hardware.touchscreen" android:required="false" />
<!-- https://developer.android.com/about/versions/11/privacy/package-visibility --> <!-- https://developer.android.com/about/versions/11/privacy/package-visibility -->
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"
@@ -22,6 +27,9 @@
<!-- <useapplications-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" /> --> <!-- <useapplications-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" /> -->
<uses-permission android:name="android.permission.CAMERA" /> <uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" /> <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission
android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE"
android:minSdkVersion="34" />
<!-- <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> --> <!-- <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> -->
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/> <uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" /> <uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
@@ -31,6 +39,7 @@
android:name=".AngApplication" android:name=".AngApplication"
android:allowBackup="true" android:allowBackup="true"
android:icon="@mipmap/ic_launcher" android:icon="@mipmap/ic_launcher"
android:banner="@mipmap/ic_banner"
android:label="@string/app_name" android:label="@string/app_name"
android:supportsRtl="true" android:supportsRtl="true"
android:theme="@style/AppThemeDayNight" android:theme="@style/AppThemeDayNight"
@@ -45,6 +54,7 @@
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" /> <category android:name="android.intent.category.LAUNCHER" />
<!-- <category android:name="android.intent.category.LEANBACK_LAUNCHER"/>-->
</intent-filter> </intent-filter>
<intent-filter> <intent-filter>
<action android:name="android.service.quicksettings.action.QS_TILE_PREFERENCES" /> <action android:name="android.service.quicksettings.action.QS_TILE_PREFERENCES" />
@@ -109,8 +119,9 @@
<action android:name="android.intent.action.VIEW"/> <action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.BROWSABLE"/> <category android:name="android.intent.category.BROWSABLE"/>
<category android:name="android.intent.category.DEFAULT"/> <category android:name="android.intent.category.DEFAULT"/>
<data android:scheme="v2rayng" <data android:scheme="v2rayng"/>
android:host="install-config" /> <data android:host="install-config"/>
<data android:host="install-sub"/>
</intent-filter> </intent-filter>
</activity> </activity>
@@ -120,6 +131,7 @@
android:exported="false" android:exported="false"
android:label="@string/app_name" android:label="@string/app_name"
android:permission="android.permission.BIND_VPN_SERVICE" android:permission="android.permission.BIND_VPN_SERVICE"
android:foregroundServiceType="specialUse"
android:process=":RunSoLibV2RayDaemon"> android:process=":RunSoLibV2RayDaemon">
<intent-filter> <intent-filter>
<action android:name="android.net.VpnService" /> <action android:name="android.net.VpnService" />
@@ -127,12 +139,19 @@
<meta-data <meta-data
android:name="android.net.VpnService.SUPPORTS_ALWAYS_ON" android:name="android.net.VpnService.SUPPORTS_ALWAYS_ON"
android:value="true" /> android:value="true" />
<property
android:name="android.app.PROPERTY_SPECIAL_USE_FGS_SUBTYPE"
android:value="vpn" />
</service> </service>
<service android:name=".service.V2RayProxyOnlyService" <service android:name=".service.V2RayProxyOnlyService"
android:exported="false" android:exported="false"
android:label="@string/app_name" android:label="@string/app_name"
android:foregroundServiceType="specialUse"
android:process=":RunSoLibV2RayDaemon"> android:process=":RunSoLibV2RayDaemon">
<property
android:name="android.app.PROPERTY_SPECIAL_USE_FGS_SUBTYPE"
android:value="proxy" />
</service> </service>
<service android:name=".service.V2RayTestService" <service android:name=".service.V2RayTestService"
@@ -159,11 +178,15 @@
android:name=".service.QSTileService" android:name=".service.QSTileService"
android:icon="@drawable/ic_stat_name" android:icon="@drawable/ic_stat_name"
android:label="@string/app_tile_name" android:label="@string/app_tile_name"
android:foregroundServiceType="specialUse"
android:permission="android.permission.BIND_QUICK_SETTINGS_TILE" android:permission="android.permission.BIND_QUICK_SETTINGS_TILE"
android:process=":RunSoLibV2RayDaemon"> android:process=":RunSoLibV2RayDaemon">
<intent-filter> <intent-filter>
<action android:name="android.service.quicksettings.action.QS_TILE" /> <action android:name="android.service.quicksettings.action.QS_TILE" />
</intent-filter> </intent-filter>
<property
android:name="android.app.PROPERTY_SPECIAL_USE_FGS_SUBTYPE"
android:value="tile" />
</service> </service>
<!-- =====================Tasker===================== --> <!-- =====================Tasker===================== -->
<activity <activity
@@ -185,6 +208,18 @@
</intent-filter> </intent-filter>
</receiver> </receiver>
<!-- =====================Tasker===================== --> <!-- =====================Tasker===================== -->
<provider
android:name="androidx.startup.InitializationProvider"
android:authorities="${applicationId}.androidx-startup"
android:exported="false"
tools:node="merge">
<meta-data
android:name="androidx.work.WorkManagerInitializer"
android:value="androidx.startup"
tools:node="remove" />
</provider>
</application> </application>

View File

@@ -1,12 +1,20 @@
package com.v2ray.ang package com.v2ray.ang
import android.content.Context
import androidx.multidex.MultiDexApplication import androidx.multidex.MultiDexApplication
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
import androidx.work.Configuration
import com.tencent.mmkv.MMKV import com.tencent.mmkv.MMKV
class AngApplication : MultiDexApplication() { 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
}
override fun attachBaseContext(base: Context?) {
super.attachBaseContext(base)
application = this
} }
var firstRun = false var firstRun = false
@@ -25,4 +33,10 @@ class AngApplication : MultiDexApplication() {
//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)
} }
override fun getWorkManagerConfiguration(): Configuration {
return Configuration.Builder()
.setDefaultProcessName("${BuildConfig.APPLICATION_ID}:bg")
.build()
}
} }

View File

@@ -5,7 +5,7 @@ package com.v2ray.ang
* App Config Const * App Config Const
*/ */
object AppConfig { object AppConfig {
const val ANG_PACKAGE = "com.v2ray.ang" const val ANG_PACKAGE = BuildConfig.APPLICATION_ID
const val DIR_ASSETS = "assets" const val DIR_ASSETS = "assets"
// legacy // legacy
@@ -39,6 +39,11 @@ object AppConfig {
const val PREF_PER_APP_PROXY_SET = "pref_per_app_proxy_set" const val PREF_PER_APP_PROXY_SET = "pref_per_app_proxy_set"
const val PREF_BYPASS_APPS = "pref_bypass_apps" const val PREF_BYPASS_APPS = "pref_bypass_apps"
const val PREF_CONFIRM_REMOVE = "pref_confirm_remove" const val PREF_CONFIRM_REMOVE = "pref_confirm_remove"
const val PREF_START_SCAN_IMMEDIATE = "pref_start_scan_immediate"
const val PREF_MUX_ENABLED = "pref_mux_enabled"
const val PREF_MUX_CONCURRENCY = "pref_mux_concurency"
const val PREF_MUX_XUDP_CONCURRENCY = "pref_mux_xudp_concurency"
const val PREF_MUX_XUDP_QUIC = "pref_mux_xudp_quic"
const val HTTP_PROTOCOL: String = "http://" const val HTTP_PROTOCOL: String = "http://"
const val HTTPS_PROTOCOL: String = "https://" const val HTTPS_PROTOCOL: String = "https://"
@@ -61,8 +66,9 @@ object AppConfig {
const val v2rayCustomRoutingListUrl = "https://raw.githubusercontent.com/2dust/v2rayCustomRoutingList/master/" const val v2rayCustomRoutingListUrl = "https://raw.githubusercontent.com/2dust/v2rayCustomRoutingList/master/"
const val v2rayNGIssues = "https://github.com/2dust/v2rayNG/issues" const val v2rayNGIssues = "https://github.com/2dust/v2rayNG/issues"
const val v2rayNGWikiMode = "https://github.com/2dust/v2rayNG/wiki/Mode" const val v2rayNGWikiMode = "https://github.com/2dust/v2rayNG/wiki/Mode"
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 DNS_AGENT = "1.1.1.1" const val DNS_AGENT = "1.1.1.1"
const val DNS_DIRECT = "223.5.5.5" const val DNS_DIRECT = "223.5.5.5"
@@ -86,4 +92,13 @@ 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

@@ -19,23 +19,32 @@ data class ServerConfig(
when(configType) { when(configType) {
EConfigType.VMESS, EConfigType.VLESS -> EConfigType.VMESS, EConfigType.VLESS ->
return ServerConfig( return ServerConfig(
configType = configType, configType = configType,
outboundBean = V2rayConfig.OutboundBean( outboundBean = V2rayConfig.OutboundBean(
protocol = configType.name.lowercase(), protocol = configType.name.lowercase(),
settings = V2rayConfig.OutboundBean.OutSettingsBean( settings = V2rayConfig.OutboundBean.OutSettingsBean(
vnext = listOf(V2rayConfig.OutboundBean.OutSettingsBean.VnextBean( vnext = listOf(V2rayConfig.OutboundBean.OutSettingsBean.VnextBean(
users = listOf(V2rayConfig.OutboundBean.OutSettingsBean.VnextBean.UsersBean())))), users = listOf(V2rayConfig.OutboundBean.OutSettingsBean.VnextBean.UsersBean())))),
streamSettings = V2rayConfig.OutboundBean.StreamSettingsBean())) streamSettings = V2rayConfig.OutboundBean.StreamSettingsBean()))
EConfigType.CUSTOM, EConfigType.WIREGUARD -> EConfigType.CUSTOM ->
return ServerConfig(configType = configType) return ServerConfig(configType = configType)
EConfigType.SHADOWSOCKS, EConfigType.SOCKS, EConfigType.TROJAN -> EConfigType.SHADOWSOCKS, EConfigType.SOCKS, EConfigType.TROJAN ->
return ServerConfig( return ServerConfig(
configType = configType, configType = configType,
outboundBean = V2rayConfig.OutboundBean( outboundBean = V2rayConfig.OutboundBean(
protocol = configType.name.lowercase(), protocol = configType.name.lowercase(),
settings = V2rayConfig.OutboundBean.OutSettingsBean( settings = V2rayConfig.OutboundBean.OutSettingsBean(
servers = listOf(V2rayConfig.OutboundBean.OutSettingsBean.ServersBean())), servers = listOf(V2rayConfig.OutboundBean.OutSettingsBean.ServersBean())),
streamSettings = V2rayConfig.OutboundBean.StreamSettingsBean())) streamSettings = V2rayConfig.OutboundBean.StreamSettingsBean()))
EConfigType.WIREGUARD ->
return ServerConfig(
configType = configType,
outboundBean = V2rayConfig.OutboundBean(
protocol = configType.name.lowercase(),
settings = V2rayConfig.OutboundBean.OutSettingsBean(
secretKey = "",
peers = listOf(V2rayConfig.OutboundBean.OutSettingsBean.WireGuardBean())
)))
} }
} }
} }
@@ -60,10 +69,6 @@ data class ServerConfig(
fun getV2rayPointDomainAndPort(): String { fun getV2rayPointDomainAndPort(): String {
val address = getProxyOutbound()?.getServerAddress().orEmpty() val address = getProxyOutbound()?.getServerAddress().orEmpty()
val port = getProxyOutbound()?.getServerPort() val port = getProxyOutbound()?.getServerPort()
return if (Utils.isIpv6Address(address)) { return Utils.getIpv6Address(address) + ":" + port
String.format("[%s]:%s", address, port)
} else {
String.format("%s:%s", address, port)
}
} }
} }

View File

@@ -1,8 +1,11 @@
package com.v2ray.ang.dto package com.v2ray.ang.dto
data class SubscriptionItem( data class SubscriptionItem(
var remarks: String = "", var remarks: String = "",
var url: String = "", var url: String = "",
var enabled: Boolean = true, var enabled: Boolean = true,
val addedTime: Long = System.currentTimeMillis()) { val addedTime: Long = System.currentTimeMillis(),
} var lastUpdated: Long = -1,
var autoUpdate: Boolean = false,
val updateInterval: Int? = null,
)

View File

@@ -74,7 +74,7 @@ data class V2rayConfig(
var response: Response? = null, var response: Response? = null,
/*DNS*/ /*DNS*/
val network: String? = null, val network: String? = null,
val address: Any? = null, var address: Any? = null,
val port: Int? = null, val port: Int? = null,
/*Freedom*/ /*Freedom*/
var domainStrategy: String? = null, var domainStrategy: String? = null,
@@ -83,8 +83,9 @@ data class V2rayConfig(
/*Loopback*/ /*Loopback*/
val inboundTag: String? = null, val inboundTag: String? = null,
/*Wireguard*/ /*Wireguard*/
val secretKey: String? = null, var secretKey: String? = null,
val peers: List<WireGuardBean>? = null, val peers: List<WireGuardBean>? = null,
var reserved: List<Int>? = null,
) { ) {
data class VnextBean(var address: String = "", data class VnextBean(var address: String = "",
@@ -292,7 +293,10 @@ data class V2rayConfig(
} }
} }
data class MuxBean(var enabled: Boolean, var concurrency: Int = 8) data class MuxBean(var enabled: Boolean,
var concurrency: Int = 8,
var xudpConcurrency: Int = 8,
var xudpProxyUDP443: String = "",)
fun getServerAddress(): String? { fun getServerAddress(): String? {
if (protocol.equals(EConfigType.VMESS.name, true) if (protocol.equals(EConfigType.VMESS.name, true)

View File

@@ -4,7 +4,6 @@ import android.content.BroadcastReceiver
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.text.TextUtils import android.text.TextUtils
import com.google.zxing.WriterException
import com.tencent.mmkv.MMKV import com.tencent.mmkv.MMKV
import com.v2ray.ang.AppConfig import com.v2ray.ang.AppConfig
import com.v2ray.ang.service.V2RayServiceManager import com.v2ray.ang.service.V2RayServiceManager
@@ -34,7 +33,7 @@ class TaskerReceiver : BroadcastReceiver() {
} else { } else {
Utils.stopVService(context) Utils.stopVService(context)
} }
} catch (e: WriterException) { } catch (e: Exception) {
e.printStackTrace() e.printStackTrace()
} }
} }

View File

@@ -38,12 +38,18 @@ class WidgetProvider : AppWidgetProvider() {
}) })
remoteViews.setOnClickPendingIntent(R.id.layout_switch, pendingIntent) remoteViews.setOnClickPendingIntent(R.id.layout_switch, pendingIntent)
if (isRunning) { if (isRunning) {
if (!Utils.getDarkModeStatus(context)) {
remoteViews.setInt(R.id.image_switch, "setImageResource", R.drawable.ic_stat_name)
}
remoteViews.setInt( remoteViews.setInt(
R.id.layout_switch, R.id.layout_switch,
"setBackgroundResource", "setBackgroundResource",
R.drawable.ic_rounded_corner_theme R.drawable.ic_rounded_corner_active
) )
} else { } else {
if (!Utils.getDarkModeStatus(context)) {
remoteViews.setInt(R.id.image_switch, "setImageResource", R.drawable.ic_stat_name_black)
}
remoteViews.setInt( remoteViews.setInt(
R.id.layout_switch, R.id.layout_switch,
"setBackgroundResource", "setBackgroundResource",

View File

@@ -36,7 +36,12 @@ class QSTileService : TileService() {
super.onStartListening() super.onStartListening()
setState(Tile.STATE_INACTIVE) setState(Tile.STATE_INACTIVE)
mMsgReceive = ReceiveMessageHandler(this) mMsgReceive = ReceiveMessageHandler(this)
registerReceiver(mMsgReceive, IntentFilter(AppConfig.BROADCAST_ACTION_ACTIVITY)) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
registerReceiver(mMsgReceive, IntentFilter(AppConfig.BROADCAST_ACTION_ACTIVITY), Context.RECEIVER_EXPORTED)
} else {
registerReceiver(mMsgReceive, IntentFilter(AppConfig.BROADCAST_ACTION_ACTIVITY))
}
MessageUtil.sendMsg2Service(this, AppConfig.MSG_REGISTER_CLIENT, "") MessageUtil.sendMsg2Service(this, AppConfig.MSG_REGISTER_CLIENT, "")
} }

View File

@@ -0,0 +1,77 @@
package com.v2ray.ang.service
import android.annotation.SuppressLint
import android.app.NotificationChannel
import android.app.NotificationManager
import android.content.Context
import android.os.Build
import android.util.Log
import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat
import androidx.work.CoroutineWorker
import androidx.work.WorkerParameters
import com.v2ray.ang.AppConfig
import com.v2ray.ang.R
import com.v2ray.ang.util.AngConfigManager
import com.v2ray.ang.util.MmkvManager
import com.v2ray.ang.util.Utils
object SubscriptionUpdater {
const val notificationChannel = "subscription_update_channel"
class UpdateTask(context: Context, params: WorkerParameters) :
CoroutineWorker(context, params) {
private val notificationManager = NotificationManagerCompat.from(applicationContext)
private val notification =
NotificationCompat.Builder(applicationContext, notificationChannel)
.setWhen(0)
.setTicker("Update")
.setContentTitle(context.getString(R.string.title_pref_auto_update_subscription))
.setSmallIcon(R.drawable.ic_stat_name)
.setCategory(NotificationCompat.CATEGORY_SERVICE)
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
@SuppressLint("MissingPermission")
override suspend fun doWork(): Result {
Log.d(AppConfig.ANG_PACKAGE, "subscription automatic update starting")
val subs = MmkvManager.decodeSubscriptions().filter { it.second.autoUpdate }
for (i in subs) {
val subscription = i.second
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
notification.setChannelId(notificationChannel)
val channel =
NotificationChannel(
notificationChannel,
"Subscription Update Service",
NotificationManager.IMPORTANCE_MIN
)
notificationManager.createNotificationChannel(channel)
}
notificationManager.notify(3, notification.build())
Log.d(
AppConfig.ANG_PACKAGE,
"subscription automatic update: ---${subscription.remarks}"
)
val configs = Utils.getUrlContentWithCustomUserAgent(subscription.url)
importBatchConfig(configs, i.first)
notification.setContentText("Updating ${subscription.remarks}")
}
notificationManager.cancel(3)
return Result.success()
}
}
fun importBatchConfig(server: String?, subid: String = "") {
val append = subid.isEmpty()
val count = AngConfigManager.importBatchConfig(server, subid, append)
if (count <= 0) {
AngConfigManager.importBatchConfig(Utils.decode(server!!), subid, append)
}
}
}

View File

@@ -50,7 +50,7 @@ object V2RayServiceManager {
set(value) { set(value) {
field = value field = value
Seq.setContext(value?.get()?.getService()?.applicationContext) Seq.setContext(value?.get()?.getService()?.applicationContext)
Libv2ray.initV2Env(Utils.userAssetPath(value?.get()?.getService())) Libv2ray.initV2Env(Utils.userAssetPath(value?.get()?.getService()), Utils.getDeviceIdForXUDPBaseKey())
} }
var currentConfig: ServerConfig? = null var currentConfig: ServerConfig? = null
@@ -133,7 +133,11 @@ object V2RayServiceManager {
mFilter.addAction(Intent.ACTION_SCREEN_ON) mFilter.addAction(Intent.ACTION_SCREEN_ON)
mFilter.addAction(Intent.ACTION_SCREEN_OFF) mFilter.addAction(Intent.ACTION_SCREEN_OFF)
mFilter.addAction(Intent.ACTION_USER_PRESENT) mFilter.addAction(Intent.ACTION_USER_PRESENT)
service.registerReceiver(mMsgReceive, mFilter) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
service.registerReceiver(mMsgReceive, mFilter, Context.RECEIVER_EXPORTED)
} else {
service.registerReceiver(mMsgReceive, mFilter)
}
} catch (e: Exception) { } catch (e: Exception) {
Log.d(ANG_PACKAGE, e.toString()) Log.d(ANG_PACKAGE, e.toString())
} }

View File

@@ -20,7 +20,7 @@ class V2RayTestService : Service() {
override fun onCreate() { override fun onCreate() {
super.onCreate() super.onCreate()
Seq.setContext(this) Seq.setContext(this)
Libv2ray.initV2Env(Utils.userAssetPath(this)) Libv2ray.initV2Env(Utils.userAssetPath(this), Utils.getDeviceIdForXUDPBaseKey())
} }
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {

View File

@@ -2,13 +2,25 @@ package com.v2ray.ang.ui
import android.content.Context import android.content.Context
import android.os.Build import android.os.Build
import android.os.Bundle
import android.view.MenuItem import android.view.MenuItem
import androidx.annotation.RequiresApi import androidx.annotation.RequiresApi
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.WindowCompat
import com.v2ray.ang.util.MyContextWrapper import com.v2ray.ang.util.MyContextWrapper
import com.v2ray.ang.util.Utils import com.v2ray.ang.util.Utils
abstract class BaseActivity : AppCompatActivity() { abstract class BaseActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
supportActionBar?.setDisplayHomeAsUpEnabled(true)
if (!Utils.getDarkModeStatus(this)) {
WindowCompat.getInsetsController(window, window.decorView).apply {
isAppearanceLightStatusBars = true
}
}
}
override fun onOptionsItemSelected(item: MenuItem) = when (item.itemId) { override fun onOptionsItemSelected(item: MenuItem) = when (item.itemId) {
android.R.id.home -> { android.R.id.home -> {
onBackPressed() onBackPressed()

View File

@@ -25,13 +25,12 @@ class LogcatActivity : BaseActivity() {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
binding = ActivityLogcatBinding.inflate(layoutInflater) binding = ActivityLogcatBinding.inflate(layoutInflater)
val view = binding.root val view = binding.root
setContentView(view) setContentView(view)
title = getString(R.string.title_logcat) title = getString(R.string.title_logcat)
supportActionBar?.setDisplayHomeAsUpEnabled(true)
logcat(false) logcat(false)
} }

View File

@@ -103,7 +103,7 @@ 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)
binding.version.text = "v${BuildConfig.VERSION_NAME} (${SpeedtestUtil.getLibVersion()})" "v${BuildConfig.VERSION_NAME} (${SpeedtestUtil.getLibVersion()})".also { binding.version.text = it }
setupViewModel() setupViewModel()
copyAssets() copyAssets()
@@ -131,11 +131,17 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
mainViewModel.isRunning.observe(this) { isRunning -> mainViewModel.isRunning.observe(this) { isRunning ->
adapter.isRunning = isRunning adapter.isRunning = isRunning
if (isRunning) { if (isRunning) {
binding.fab.backgroundTintList = ColorStateList.valueOf(ContextCompat.getColor(this, R.color.colorSelected)) if (!Utils.getDarkModeStatus(this)) {
binding.fab.setImageResource(R.drawable.ic_stat_name)
}
binding.fab.backgroundTintList = ColorStateList.valueOf(ContextCompat.getColor(this, R.color.color_fab_orange))
setTestState(getString(R.string.connection_connected)) setTestState(getString(R.string.connection_connected))
binding.layoutTest.isFocusable = true binding.layoutTest.isFocusable = true
} else { } else {
binding.fab.backgroundTintList = ColorStateList.valueOf(ContextCompat.getColor(this, R.color.colorUnselected)) if (!Utils.getDarkModeStatus(this)) {
binding.fab.setImageResource(R.drawable.ic_stat_name)
}
binding.fab.backgroundTintList = ColorStateList.valueOf(ContextCompat.getColor(this, R.color.color_fab_grey))
setTestState(getString(R.string.connection_not_connected)) setTestState(getString(R.string.connection_not_connected))
binding.layoutTest.isFocusable = false binding.layoutTest.isFocusable = false
} }
@@ -247,6 +253,10 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
importManually(EConfigType.TROJAN.value) importManually(EConfigType.TROJAN.value)
true true
} }
R.id.import_manually_wireguard -> {
importManually(EConfigType.WIREGUARD.value)
true
}
R.id.import_config_custom_clipboard -> { R.id.import_config_custom_clipboard -> {
importConfigCustomClipboard() importConfigCustomClipboard()
true true
@@ -558,8 +568,13 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
* read content from uri * read content from uri
*/ */
private fun readContentFromUri(uri: Uri) { private fun readContentFromUri(uri: Uri) {
val permission = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
Manifest.permission.READ_MEDIA_IMAGES
} else {
Manifest.permission.READ_EXTERNAL_STORAGE
}
RxPermissions(this) RxPermissions(this)
.request(Manifest.permission.READ_EXTERNAL_STORAGE) .request(permission)
.subscribe { .subscribe {
if (it) { if (it) {
try { try {
@@ -608,13 +623,14 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
// } // }
override fun onKeyDown(keyCode: Int, event: KeyEvent): Boolean { override fun onKeyDown(keyCode: Int, event: KeyEvent): Boolean {
if (keyCode == KeyEvent.KEYCODE_BACK) { if (keyCode == KeyEvent.KEYCODE_BACK || keyCode == KeyEvent.KEYCODE_BUTTON_B) {
moveTaskToBack(false) moveTaskToBack(false)
return true return true
} }
return super.onKeyDown(keyCode, event) return super.onKeyDown(keyCode, event)
} }
fun showCircle() { fun showCircle() {
binding.fabProgressCircle.show() binding.fabProgressCircle.show()
} }
@@ -670,6 +686,9 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
R.id.logcat -> { R.id.logcat -> {
startActivity(Intent(this, LogcatActivity::class.java)) startActivity(Intent(this, LogcatActivity::class.java))
} }
R.id.privacy_policy-> {
Utils.openUri(this, AppConfig.v2rayNGPrivacyPolicy)
}
} }
binding.drawerLayout.closeDrawer(GravityCompat.START) binding.drawerLayout.closeDrawer(GravityCompat.START)
return true return true

View File

@@ -96,7 +96,8 @@ class MainRecyclerAdapter(val activity: MainActivity) : RecyclerView.Adapter<Mai
holder.itemMainBinding.tvType.text = config.configType.name.lowercase() holder.itemMainBinding.tvType.text = config.configType.name.lowercase()
} }
} }
holder.itemMainBinding.tvStatistics.text = "${outbound?.getServerAddress()} : ${outbound?.getServerPort()}" val strState = "${outbound?.getServerAddress()?.dropLast(3)}*** : ${outbound?.getServerPort()}"
holder.itemMainBinding.tvStatistics.text = strState
holder.itemMainBinding.layoutShare.setOnClickListener { holder.itemMainBinding.layoutShare.setOnClickListener {
AlertDialog.Builder(mActivity).setItems(shareOptions.toTypedArray()) { _, i -> AlertDialog.Builder(mActivity).setItems(shareOptions.toTypedArray()) { _, i ->
@@ -228,7 +229,7 @@ class MainRecyclerAdapter(val activity: MainActivity) : RecyclerView.Adapter<Mai
BaseViewHolder(itemMainBinding.root), ItemTouchHelperViewHolder BaseViewHolder(itemMainBinding.root), ItemTouchHelperViewHolder
class FooterViewHolder(val itemFooterBinding: ItemRecyclerFooterBinding) : class FooterViewHolder(val itemFooterBinding: ItemRecyclerFooterBinding) :
BaseViewHolder(itemFooterBinding.root), ItemTouchHelperViewHolder BaseViewHolder(itemFooterBinding.root)
override fun onItemDismiss(position: Int) { override fun onItemDismiss(position: Int) {
val guid = mActivity.mainViewModel.serversCache.getOrNull(position)?.guid ?: return val guid = mActivity.mainViewModel.serversCache.getOrNull(position)?.guid ?: return

View File

@@ -41,8 +41,6 @@ class PerAppProxyActivity : BaseActivity() {
val view = binding.root val view = binding.root
setContentView(view) setContentView(view)
supportActionBar?.setDisplayHomeAsUpEnabled(true)
val dividerItemDecoration = DividerItemDecoration(this, LinearLayoutManager.VERTICAL) val dividerItemDecoration = DividerItemDecoration(this, LinearLayoutManager.VERTICAL)
binding.recyclerView.addItemDecoration(dividerItemDecoration) binding.recyclerView.addItemDecoration(dividerItemDecoration)

View File

@@ -21,7 +21,6 @@ class RoutingSettingsActivity : BaseActivity() {
setContentView(view) setContentView(view)
title = getString(R.string.title_pref_routing_custom) title = getString(R.string.title_pref_routing_custom)
supportActionBar?.setDisplayHomeAsUpEnabled(true)
val fragments = ArrayList<Fragment>() val fragments = ArrayList<Fragment>()
fragments.add(RoutingSettingsFragment().newInstance(AppConfig.PREF_V2RAY_ROUTING_AGENT)) fragments.add(RoutingSettingsFragment().newInstance(AppConfig.PREF_V2RAY_ROUTING_AGENT))

View File

@@ -3,58 +3,52 @@ package com.v2ray.ang.ui
import android.Manifest import android.Manifest
import android.app.Activity import android.app.Activity
import android.os.Bundle import android.os.Bundle
import com.google.zxing.Result
import me.dm7.barcodescanner.zxing.ZXingScannerView
import android.content.Intent import android.content.Intent
import android.graphics.BitmapFactory import android.graphics.BitmapFactory
import android.os.Build import android.os.Build
import android.view.Menu import android.view.Menu
import android.view.MenuItem import android.view.MenuItem
import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.result.contract.ActivityResultContracts
import com.google.zxing.BarcodeFormat
import com.tbruyelle.rxpermissions.RxPermissions import com.tbruyelle.rxpermissions.RxPermissions
import com.tencent.mmkv.MMKV
import com.v2ray.ang.AppConfig
import com.v2ray.ang.R import com.v2ray.ang.R
import com.v2ray.ang.extension.toast import com.v2ray.ang.extension.toast
import com.v2ray.ang.util.MmkvManager
import com.v2ray.ang.util.QRCodeDecoder import com.v2ray.ang.util.QRCodeDecoder
import io.github.g00fy2.quickie.QRResult
import io.github.g00fy2.quickie.ScanCustomCode
import io.github.g00fy2.quickie.config.ScannerConfig
class ScannerActivity : BaseActivity(), ZXingScannerView.ResultHandler { class ScannerActivity : BaseActivity(){
private var mScannerView: ZXingScannerView? = null private val scanQrCode = registerForActivityResult(ScanCustomCode(), ::handleResult)
private val settingsStorage by lazy { MMKV.mmkvWithID(MmkvManager.ID_SETTING, MMKV.MULTI_PROCESS_MODE) }
public override fun onCreate(savedInstanceState: Bundle?) { public override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
mScannerView = ZXingScannerView(this) // Programmatically initialize the scanner view
mScannerView?.setAutoFocus(true) if (settingsStorage?.decodeBool(AppConfig.PREF_START_SCAN_IMMEDIATE) == true) {
val formats = ArrayList<BarcodeFormat>() launchScan()
formats.add(BarcodeFormat.QR_CODE) }
mScannerView?.setFormats(formats)
setContentView(mScannerView) // Set the scanner view as the content view
supportActionBar?.setDisplayHomeAsUpEnabled(true)
} }
public override fun onResume() { private fun launchScan(){
super.onResume() scanQrCode.launch(
mScannerView!!.setResultHandler(this) // Register ourselves as a handler for scan results. ScannerConfig.build {
mScannerView!!.startCamera() // Start camera on resume setHapticSuccessFeedback(true) // enable (default) or disable haptic feedback when a barcode was detected
setShowTorchToggle(true) // show or hide (default) torch/flashlight toggle button
setShowCloseButton(true) // show or hide (default) close button
}
)
} }
public override fun onPause() { private fun handleResult(result: QRResult) {
super.onPause() if (result is QRResult.QRSuccess ) {
mScannerView!!.stopCamera() // Stop camera on pause finished(result.content.rawValue)
} } else {
finish()
override fun handleResult(rawResult: Result) { }
// Do something with the result here
// Log.v(FragmentActivity.TAG, rawResult.text) // Prints scan results
// Log.v(FragmentActivity.TAG, rawResult.barcodeFormat.toString()) // Prints the scan format (qrcode, pdf417 etc.)
finished(rawResult.text)
// If you would like to resume scanning, call this method below:
// mScannerView!!.resumeCameraPreview(this)
} }
private fun finished(text: String) { private fun finished(text: String) {
@@ -70,6 +64,10 @@ class ScannerActivity : BaseActivity(), ZXingScannerView.ResultHandler {
} }
override fun onOptionsItemSelected(item: MenuItem) = when (item.itemId) { override fun onOptionsItemSelected(item: MenuItem) = when (item.itemId) {
R.id.scan_code -> {
launchScan()
true
}
R.id.select_photo -> { R.id.select_photo -> {
val permission = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { val permission = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
Manifest.permission.READ_MEDIA_IMAGES Manifest.permission.READ_MEDIA_IMAGES

View File

@@ -21,6 +21,7 @@ import com.v2ray.ang.util.MmkvManager
import com.v2ray.ang.util.MmkvManager.ID_MAIN import com.v2ray.ang.util.MmkvManager.ID_MAIN
import com.v2ray.ang.util.MmkvManager.KEY_SELECTED_SERVER import com.v2ray.ang.util.MmkvManager.KEY_SELECTED_SERVER
import com.v2ray.ang.util.Utils import com.v2ray.ang.util.Utils
import com.v2ray.ang.util.Utils.getIpv6Address
class ServerActivity : BaseActivity() { class ServerActivity : BaseActivity() {
@@ -103,6 +104,12 @@ class ServerActivity : BaseActivity() {
private val container_short_id: LinearLayout? by lazy { findViewById(R.id.l7) } private val container_short_id: LinearLayout? by lazy { findViewById(R.id.l7) }
private val et_spider_x: EditText? by lazy { findViewById(R.id.et_spider_x) } private val et_spider_x: EditText? by lazy { findViewById(R.id.et_spider_x) }
private val container_spider_x: LinearLayout? by lazy { findViewById(R.id.l8) } private val container_spider_x: LinearLayout? by lazy { findViewById(R.id.l8) }
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_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_v6_address: EditText? by lazy { findViewById(R.id.et_local_v6_address) }
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
@@ -116,7 +123,7 @@ class ServerActivity : BaseActivity() {
EConfigType.SOCKS -> setContentView(R.layout.activity_server_socks) EConfigType.SOCKS -> setContentView(R.layout.activity_server_socks)
EConfigType.VLESS -> setContentView(R.layout.activity_server_vless) EConfigType.VLESS -> setContentView(R.layout.activity_server_vless)
EConfigType.TROJAN -> setContentView(R.layout.activity_server_trojan) EConfigType.TROJAN -> setContentView(R.layout.activity_server_trojan)
else -> setContentView(R.layout.activity_server_vmess) 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) {
@@ -175,7 +182,6 @@ class ServerActivity : BaseActivity() {
} else { } else {
clearServer() clearServer()
} }
supportActionBar?.setDisplayHomeAsUpEnabled(true)
} }
/** /**
@@ -183,7 +189,6 @@ class ServerActivity : BaseActivity() {
*/ */
private fun bindingServer(config: ServerConfig): Boolean { private fun bindingServer(config: ServerConfig): Boolean {
val outbound = config.getProxyOutbound() ?: return false val outbound = config.getProxyOutbound() ?: return false
val streamSetting = config.outboundBean?.streamSettings ?: 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())
@@ -198,10 +203,24 @@ class ServerActivity : BaseActivity() {
if (flow >= 0) { if (flow >= 0) {
sp_flow?.setSelection(flow) sp_flow?.setSelection(flow)
} }
} else if (config.configType == EConfigType.TROJAN) { } else if (config.configType == EConfigType.WIREGUARD) {
val flow = Utils.arrayFind(flows, outbound.settings?.servers?.get(0)?.flow.orEmpty()) et_public_key?.text = Utils.getEditable(outbound.settings?.peers?.get(0)?.publicKey.orEmpty())
if (flow >= 0) { if (outbound.settings?.reserved == null) {
sp_flow?.setSelection(flow) et_reserved1?.text = Utils.getEditable("0")
et_reserved2?.text = Utils.getEditable("0")
et_reserved3?.text = Utils.getEditable("0")
} else {
et_reserved1?.text = Utils.getEditable(outbound.settings?.reserved?.get(0).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) {
et_local_v4_address?.text = Utils.getEditable("172.16.0.2/32")
et_local_v6_address?.text = Utils.getEditable("2606:4700:110:8f81:d551:a0:532e:a2b3/128")
} else {
val list = outbound.settings?.address as List<*>
et_local_v4_address?.text = Utils.getEditable(list.get(0).toString())
et_local_v6_address?.text = Utils.getEditable(list.get(1).toString())
} }
} }
val securityEncryptions = if (config.configType == EConfigType.SHADOWSOCKS) shadowsocksSecuritys else securitys val securityEncryptions = if (config.configType == EConfigType.SHADOWSOCKS) shadowsocksSecuritys else securitys
@@ -210,6 +229,7 @@ class ServerActivity : BaseActivity() {
sp_security?.setSelection(security) sp_security?.setSelection(security)
} }
val streamSetting = config.outboundBean?.streamSettings ?: return true
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)
@@ -283,6 +303,12 @@ class ServerActivity : BaseActivity() {
//et_security.text = null //et_security.text = null
sp_flow?.setSelection(0) sp_flow?.setSelection(0)
et_public_key?.text = null
et_reserved1?.text = Utils.getEditable("0")
et_reserved2?.text = Utils.getEditable("0")
et_reserved3?.text = Utils.getEditable("0")
et_local_v4_address?.text = Utils.getEditable("172.16.0.2/32")
et_local_v6_address?.text = Utils.getEditable("2606:4700:110:8f81:d551:a0:532e:a2b3/128")
return true return true
} }
@@ -305,7 +331,11 @@ class ServerActivity : BaseActivity() {
} }
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())) {
toast(R.string.server_lab_id) if (config.configType == EConfigType.TROJAN || config.configType == EConfigType.SHADOWSOCKS) {
toast(R.string.server_lab_id3)
}else{
toast(R.string.server_lab_id)
}
return false return false
} }
sp_stream_security?.let { sp_stream_security?.let {
@@ -329,6 +359,10 @@ class ServerActivity : BaseActivity() {
config.outboundBean?.settings?.servers?.get(0)?.let { server -> config.outboundBean?.settings?.servers?.get(0)?.let { server ->
saveServers(server, port, config) saveServers(server, port, config)
} }
val wireguard = config.outboundBean?.settings
wireguard?.peers?.get(0)?.let { _ ->
savePeer(wireguard, port)
}
config.outboundBean?.streamSettings?.let { config.outboundBean?.streamSettings?.let {
saveStreamSettings(it) saveStreamSettings(it)
} }
@@ -375,6 +409,22 @@ class ServerActivity : BaseActivity() {
} }
} }
private fun savePeer(wireguard: V2rayConfig.OutboundBean.OutSettingsBean, port: Int) {
wireguard.secretKey = et_id.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
val reserved1 = Utils.parseInt(et_reserved1?.text.toString())
val reserved2 = Utils.parseInt(et_reserved2?.text.toString())
val reserved3 = Utils.parseInt(et_reserved3?.text.toString())
if (reserved1 > 0 || reserved2 > 0 || reserved3 > 0) {
wireguard.reserved = listOf(reserved1, reserved2, reserved3)
}else {
wireguard.reserved = null
}
wireguard.address = listOf(et_local_v4_address?.text.toString().trim(),
et_local_v6_address?.text.toString().trim())
}
private fun saveStreamSettings(streamSetting: V2rayConfig.OutboundBean.StreamSettingsBean) { private fun saveStreamSettings(streamSetting: V2rayConfig.OutboundBean.StreamSettingsBean) {
val network = sp_network?.selectedItemPosition ?: return val network = sp_network?.selectedItemPosition ?: return
val type = sp_header_type?.selectedItemPosition ?: return val type = sp_header_type?.selectedItemPosition ?: return

View File

@@ -6,6 +6,7 @@ import android.view.Menu
import android.view.MenuItem import android.view.MenuItem
import android.widget.Toast import android.widget.Toast
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import com.blacksquircle.ui.editorkit.utils.EditorTheme
import com.blacksquircle.ui.language.json.JsonLanguage import com.blacksquircle.ui.language.json.JsonLanguage
import com.google.gson.* import com.google.gson.*
import com.tencent.mmkv.MMKV import com.tencent.mmkv.MMKV
@@ -38,6 +39,9 @@ class ServerCustomConfigActivity : BaseActivity() {
setContentView(view) setContentView(view)
title = getString(R.string.title_server) title = getString(R.string.title_server)
if (!Utils.getDarkModeStatus(this)) {
binding.editor.colorScheme = EditorTheme.INTELLIJ_LIGHT
}
binding.editor.language = JsonLanguage() binding.editor.language = JsonLanguage()
val config = MmkvManager.decodeServerConfig(editGuid) val config = MmkvManager.decodeServerConfig(editGuid)
if (config != null) { if (config != null) {
@@ -45,7 +49,6 @@ class ServerCustomConfigActivity : BaseActivity() {
} else { } else {
clearServer() clearServer()
} }
supportActionBar?.setDisplayHomeAsUpEnabled(true)
} }
/** /**

View File

@@ -6,10 +6,16 @@ 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.*
import androidx.work.ExistingPeriodicWorkPolicy
import androidx.work.PeriodicWorkRequest
import androidx.work.multiprocess.RemoteWorkManager
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.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
class SettingsActivity : BaseActivity() { class SettingsActivity : BaseActivity() {
private val settingsViewModel: SettingsViewModel by viewModels() private val settingsViewModel: SettingsViewModel by viewModels()
@@ -20,8 +26,6 @@ class SettingsActivity : BaseActivity() {
title = getString(R.string.title_settings) title = getString(R.string.title_settings)
supportActionBar?.setDisplayHomeAsUpEnabled(true)
settingsViewModel.startListenPreferenceChange() settingsViewModel.startListenPreferenceChange()
} }
@@ -31,12 +35,21 @@ class SettingsActivity : BaseActivity() {
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 mux by lazy { findPreference<CheckBoxPreference>(AppConfig.PREF_MUX_ENABLED) }
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 muxXudpQuic by lazy { findPreference<ListPreference>(AppConfig.PREF_MUX_XUDP_QUIC) }
// val autoRestart by lazy { findPreference(PREF_AUTO_RESTART) as CheckBoxPreference } // val autoRestart by lazy { findPreference(PREF_AUTO_RESTART) as CheckBoxPreference }
private val remoteDns by lazy { findPreference<EditTextPreference>(AppConfig.PREF_REMOTE_DNS) } private val remoteDns by lazy { findPreference<EditTextPreference>(AppConfig.PREF_REMOTE_DNS) }
private val domesticDns by lazy { findPreference<EditTextPreference>(AppConfig.PREF_DOMESTIC_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 socksPort by lazy { findPreference<EditTextPreference>(AppConfig.PREF_SOCKS_PORT) }
private val httpPort by lazy { findPreference<EditTextPreference>(AppConfig.PREF_HTTP_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 routingCustom by lazy { findPreference<Preference>(AppConfig.PREF_ROUTING_CUSTOM) }
private val autoUpdateCheck by lazy { findPreference<CheckBoxPreference>(AppConfig.SUBSCRIPTION_AUTO_UPDATE) }
private val autoUpdateInterval by lazy { findPreference<EditTextPreference>(AppConfig.SUBSCRIPTION_AUTO_UPDATE_INTERVAL) }
// val licenses: Preference by lazy { findPreference(PREF_LICENSES) } // val licenses: Preference by lazy { findPreference(PREF_LICENSES) }
// val feedback: Preference by lazy { findPreference(PREF_FEEDBACK) } // val feedback: Preference by lazy { findPreference(PREF_FEEDBACK) }
// val tgGroup: Preference by lazy { findPreference(PREF_TG_GROUP) } // val tgGroup: Preference by lazy { findPreference(PREF_TG_GROUP) }
@@ -51,6 +64,27 @@ class SettingsActivity : BaseActivity() {
false 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 { // licenses.onClick {
// val fragment = LicensesDialogFragment.Builder(act) // val fragment = LicensesDialogFragment.Builder(act)
// .setNotices(R.raw.licenses) // .setNotices(R.raw.licenses)
@@ -92,13 +126,14 @@ class SettingsActivity : BaseActivity() {
true true
} }
localDns?.setOnPreferenceChangeListener{ _, any -> localDns?.setOnPreferenceChangeListener { _, any ->
updateLocalDns(any as Boolean) updateLocalDns(any as Boolean)
true true
} }
localDnsPort?.setOnPreferenceChangeListener { _, any -> localDnsPort?.setOnPreferenceChangeListener { _, any ->
val nval = any as String val nval = any as String
localDnsPort?.summary = if (TextUtils.isEmpty(nval)) AppConfig.PORT_LOCAL_DNS else nval localDnsPort?.summary =
if (TextUtils.isEmpty(nval)) AppConfig.PORT_LOCAL_DNS else nval
true true
} }
vpnDns?.setOnPreferenceChangeListener { _, any -> vpnDns?.setOnPreferenceChangeListener { _, any ->
@@ -121,18 +156,36 @@ class SettingsActivity : BaseActivity() {
} }
mode?.dialogLayoutResource = R.layout.preference_with_help_link mode?.dialogLayoutResource = R.layout.preference_with_help_link
//loglevel.summary = "LogLevel" //loglevel.summary = "LogLevel"
mux?.setOnPreferenceChangeListener { _, newValue ->
updateMux(newValue as Boolean)
true
}
muxConcurrency?.setOnPreferenceChangeListener { _, newValue ->
updateMuxConcurrency(newValue as String)
true
}
muxXudpConcurrency?.setOnPreferenceChangeListener { _, newValue ->
updateMuxXudpConcurrency(newValue as String)
true
}
} }
override fun onStart() { override fun onStart() {
super.onStart() super.onStart()
val defaultSharedPreferences = PreferenceManager.getDefaultSharedPreferences(requireActivity()) val defaultSharedPreferences =
PreferenceManager.getDefaultSharedPreferences(requireActivity())
updateMode(defaultSharedPreferences.getString(AppConfig.PREF_MODE, "VPN")) updateMode(defaultSharedPreferences.getString(AppConfig.PREF_MODE, "VPN"))
var remoteDnsString = defaultSharedPreferences.getString(AppConfig.PREF_REMOTE_DNS, "") var remoteDnsString = defaultSharedPreferences.getString(AppConfig.PREF_REMOTE_DNS, "")
domesticDns?.summary = defaultSharedPreferences.getString(AppConfig.PREF_DOMESTIC_DNS, "")
domesticDns?.summary = defaultSharedPreferences.getString(AppConfig.PREF_DOMESTIC_DNS, "")
localDnsPort?.summary = defaultSharedPreferences.getString(AppConfig.PREF_LOCAL_DNS_PORT, AppConfig.PORT_LOCAL_DNS) localDnsPort?.summary = defaultSharedPreferences.getString(AppConfig.PREF_LOCAL_DNS_PORT, AppConfig.PORT_LOCAL_DNS)
socksPort?.summary = defaultSharedPreferences.getString(AppConfig.PREF_SOCKS_PORT, AppConfig.PORT_SOCKS) socksPort?.summary = defaultSharedPreferences.getString(AppConfig.PREF_SOCKS_PORT, AppConfig.PORT_SOCKS)
httpPort?.summary = defaultSharedPreferences.getString(AppConfig.PREF_HTTP_PORT, AppConfig.PORT_HTTP) httpPort?.summary = defaultSharedPreferences.getString(AppConfig.PREF_HTTP_PORT, AppConfig.PORT_HTTP)
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)) { if (TextUtils.isEmpty(remoteDnsString)) {
remoteDnsString = AppConfig.DNS_AGENT remoteDnsString = AppConfig.DNS_AGENT
@@ -141,7 +194,8 @@ class SettingsActivity : BaseActivity() {
domesticDns?.summary = AppConfig.DNS_DIRECT domesticDns?.summary = AppConfig.DNS_DIRECT
} }
remoteDns?.summary = remoteDnsString remoteDns?.summary = remoteDnsString
vpnDns?.summary = defaultSharedPreferences.getString(AppConfig.PREF_VPN_DNS, remoteDnsString) vpnDns?.summary =
defaultSharedPreferences.getString(AppConfig.PREF_VPN_DNS, remoteDnsString)
if (TextUtils.isEmpty(localDnsPort?.summary)) { if (TextUtils.isEmpty(localDnsPort?.summary)) {
localDnsPort?.summary = AppConfig.PORT_LOCAL_DNS localDnsPort?.summary = AppConfig.PORT_LOCAL_DNS
@@ -155,17 +209,24 @@ class SettingsActivity : BaseActivity() {
} }
private fun updateMode(mode: String?) { private fun updateMode(mode: String?) {
val defaultSharedPreferences = PreferenceManager.getDefaultSharedPreferences(requireActivity()) val defaultSharedPreferences =
PreferenceManager.getDefaultSharedPreferences(requireActivity())
val vpn = mode == "VPN" val vpn = mode == "VPN"
perAppProxy?.isEnabled = vpn perAppProxy?.isEnabled = vpn
perAppProxy?.isChecked = PreferenceManager.getDefaultSharedPreferences(requireActivity()) perAppProxy?.isChecked =
PreferenceManager.getDefaultSharedPreferences(requireActivity())
.getBoolean(AppConfig.PREF_PER_APP_PROXY, false) .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(defaultSharedPreferences.getBoolean(AppConfig.PREF_LOCAL_DNS_ENABLED, false)) updateLocalDns(
defaultSharedPreferences.getBoolean(
AppConfig.PREF_LOCAL_DNS_ENABLED,
false
)
)
} }
} }
@@ -174,6 +235,58 @@ class SettingsActivity : BaseActivity() {
localDnsPort?.isEnabled = enabled localDnsPort?.isEnabled = enabled
vpnDns?.isEnabled = !enabled vpnDns?.isEnabled = !enabled
} }
private fun configureUpdateTask(interval: Long) {
val rw = RemoteWorkManager.getInstance(AngApplication.application)
rw.cancelUniqueWork(AppConfig.SUBSCRIPTION_UPDATE_TASK_NAME)
rw.enqueueUniquePeriodicWork(
AppConfig.SUBSCRIPTION_UPDATE_TASK_NAME,
ExistingPeriodicWorkPolicy.UPDATE,
PeriodicWorkRequest.Builder(
SubscriptionUpdater.UpdateTask::class.java,
interval,
TimeUnit.MINUTES
)
.apply {
setInitialDelay(interval, TimeUnit.MINUTES)
}
.build()
)
}
private fun cancelUpdateTask() {
val rw = RemoteWorkManager.getInstance(AngApplication.application)
rw.cancelUniqueWork(AppConfig.SUBSCRIPTION_UPDATE_TASK_NAME)
}
private fun updateMux(enabled: Boolean) {
val defaultSharedPreferences = PreferenceManager.getDefaultSharedPreferences(requireActivity())
muxConcurrency?.isEnabled = enabled
muxXudpConcurrency?.isEnabled = enabled
muxXudpQuic?.isEnabled = enabled
if (enabled) {
updateMuxConcurrency(defaultSharedPreferences.getString(AppConfig.PREF_MUX_CONCURRENCY, "8"))
updateMuxXudpConcurrency(defaultSharedPreferences.getString(AppConfig.PREF_MUX_XUDP_CONCURRENCY, "8"))
}
}
private fun updateMuxConcurrency(value: String?) {
if (value == null) {
} else {
val concurrency = value.toIntOrNull() ?: 8
muxConcurrency?.summary = concurrency.toString()
}
}
private fun updateMuxXudpConcurrency(value: String?) {
if (value == null) {
muxXudpQuic?.isEnabled = true
} else {
val concurrency = value.toIntOrNull() ?: 8
muxXudpConcurrency?.summary = concurrency.toString()
muxXudpQuic?.isEnabled = concurrency >= 0
}
}
} }
fun onModeHelpClicked(view: View) { fun onModeHelpClicked(view: View) {

View File

@@ -5,14 +5,22 @@ import android.text.TextUtils
import android.view.Menu import android.view.Menu
import android.view.MenuItem import android.view.MenuItem
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import androidx.work.Constraints
import androidx.work.ExistingPeriodicWorkPolicy
import androidx.work.NetworkType
import androidx.work.PeriodicWorkRequest
import androidx.work.multiprocess.RemoteWorkManager
import com.google.gson.Gson import com.google.gson.Gson
import com.tencent.mmkv.MMKV import com.tencent.mmkv.MMKV
import com.v2ray.ang.AngApplication
import com.v2ray.ang.R import com.v2ray.ang.R
import com.v2ray.ang.databinding.ActivitySubEditBinding import com.v2ray.ang.databinding.ActivitySubEditBinding
import com.v2ray.ang.dto.SubscriptionItem import com.v2ray.ang.dto.SubscriptionItem
import com.v2ray.ang.extension.toast import com.v2ray.ang.extension.toast
import com.v2ray.ang.service.SubscriptionUpdater
import com.v2ray.ang.util.MmkvManager import com.v2ray.ang.util.MmkvManager
import com.v2ray.ang.util.Utils import com.v2ray.ang.util.Utils
import java.util.concurrent.TimeUnit
class SubEditActivity : BaseActivity() { class SubEditActivity : BaseActivity() {
private lateinit var binding: ActivitySubEditBinding private lateinit var binding: ActivitySubEditBinding
@@ -36,7 +44,6 @@ class SubEditActivity : BaseActivity() {
} else { } else {
clearServer() clearServer()
} }
supportActionBar?.setDisplayHomeAsUpEnabled(true)
} }
/** /**
@@ -46,6 +53,7 @@ class SubEditActivity : BaseActivity() {
binding.etRemarks.text = Utils.getEditable(subItem.remarks) binding.etRemarks.text = Utils.getEditable(subItem.remarks)
binding.etUrl.text = Utils.getEditable(subItem.url) binding.etUrl.text = Utils.getEditable(subItem.url)
binding.chkEnable.isChecked = subItem.enabled binding.chkEnable.isChecked = subItem.enabled
binding.autoUpdateCheck.isChecked = subItem.autoUpdate
return true return true
} }
@@ -76,6 +84,7 @@ class SubEditActivity : BaseActivity() {
subItem.remarks = binding.etRemarks.text.toString() subItem.remarks = binding.etRemarks.text.toString()
subItem.url = binding.etUrl.text.toString() subItem.url = binding.etUrl.text.toString()
subItem.enabled = binding.chkEnable.isChecked subItem.enabled = binding.chkEnable.isChecked
subItem.autoUpdate = binding.autoUpdateCheck.isChecked
if (TextUtils.isEmpty(subItem.remarks)) { if (TextUtils.isEmpty(subItem.remarks)) {
toast(R.string.sub_setting_remarks) toast(R.string.sub_setting_remarks)
@@ -130,4 +139,5 @@ class SubEditActivity : BaseActivity() {
} }
else -> super.onOptionsItemSelected(item) else -> super.onOptionsItemSelected(item)
} }
} }

View File

@@ -27,8 +27,6 @@ class SubSettingActivity : BaseActivity() {
binding.recyclerView.setHasFixedSize(true) binding.recyclerView.setHasFixedSize(true)
binding.recyclerView.layoutManager = LinearLayoutManager(this) binding.recyclerView.layoutManager = LinearLayoutManager(this)
binding.recyclerView.adapter = adapter binding.recyclerView.adapter = adapter
supportActionBar?.setDisplayHomeAsUpEnabled(true)
} }
override fun onResume() { override fun onResume() {

View File

@@ -2,20 +2,34 @@ package com.v2ray.ang.ui
import android.content.Intent import android.content.Intent
import android.graphics.Color import android.graphics.Color
import android.text.TextUtils
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import android.view.ViewGroup import android.view.ViewGroup
import androidx.appcompat.app.AlertDialog
import com.google.gson.Gson import com.google.gson.Gson
import com.tencent.mmkv.MMKV import com.tencent.mmkv.MMKV
import com.v2ray.ang.R import com.v2ray.ang.R
import com.v2ray.ang.databinding.ItemQrcodeBinding
import com.v2ray.ang.databinding.ItemRecyclerSubSettingBinding import com.v2ray.ang.databinding.ItemRecyclerSubSettingBinding
import com.v2ray.ang.dto.EConfigType
import com.v2ray.ang.extension.toast
import com.v2ray.ang.util.AngConfigManager
import com.v2ray.ang.util.MmkvManager import com.v2ray.ang.util.MmkvManager
import com.v2ray.ang.util.QRCodeDecoder
import com.v2ray.ang.util.Utils
class SubSettingRecyclerAdapter(val activity: SubSettingActivity) : RecyclerView.Adapter<SubSettingRecyclerAdapter.MainViewHolder>() { class SubSettingRecyclerAdapter(val activity: SubSettingActivity) :
RecyclerView.Adapter<SubSettingRecyclerAdapter.MainViewHolder>() {
private var mActivity: SubSettingActivity = activity private var mActivity: SubSettingActivity = activity
private val subStorage by lazy { MMKV.mmkvWithID(MmkvManager.ID_SUB, MMKV.MULTI_PROCESS_MODE) } private val subStorage by lazy { MMKV.mmkvWithID(MmkvManager.ID_SUB, MMKV.MULTI_PROCESS_MODE) }
private val share_method: Array<out String> by lazy {
mActivity.resources.getStringArray(R.array.share_sub_method)
}
override fun getItemCount() = mActivity.subscriptions.size override fun getItemCount() = mActivity.subscriptions.size
override fun onBindViewHolder(holder: MainViewHolder, position: Int) { override fun onBindViewHolder(holder: MainViewHolder, position: Int) {
@@ -31,7 +45,8 @@ class SubSettingRecyclerAdapter(val activity: SubSettingActivity) : RecyclerView
holder.itemView.setBackgroundColor(Color.TRANSPARENT) holder.itemView.setBackgroundColor(Color.TRANSPARENT)
holder.itemSubSettingBinding.layoutEdit.setOnClickListener { holder.itemSubSettingBinding.layoutEdit.setOnClickListener {
mActivity.startActivity(Intent(mActivity, SubEditActivity::class.java) mActivity.startActivity(
Intent(mActivity, SubEditActivity::class.java)
.putExtra("subId", subId) .putExtra("subId", subId)
) )
} }
@@ -40,11 +55,51 @@ class SubSettingRecyclerAdapter(val activity: SubSettingActivity) : RecyclerView
subStorage?.encode(subId, Gson().toJson(subItem)) subStorage?.encode(subId, Gson().toJson(subItem))
notifyItemChanged(position) notifyItemChanged(position)
} }
if (TextUtils.isEmpty(subItem.url)) {
holder.itemSubSettingBinding.layoutShare.visibility = View.INVISIBLE
} else {
holder.itemSubSettingBinding.layoutShare.setOnClickListener {
AlertDialog.Builder(mActivity)
.setItems(share_method.asList().toTypedArray()) { _, i ->
try {
when (i) {
0 -> {
val ivBinding =
ItemQrcodeBinding.inflate(LayoutInflater.from(mActivity))
ivBinding.ivQcode.setImageBitmap(
QRCodeDecoder.createQRCode(
subItem.url
)
)
AlertDialog.Builder(mActivity).setView(ivBinding.root).show()
}
1 -> {
Utils.setClipboard(mActivity, subItem.url)
}
else -> mActivity.toast("else")
}
} catch (e: Exception) {
e.printStackTrace()
}
}.show()
}
}
} }
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MainViewHolder { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MainViewHolder {
return MainViewHolder(ItemRecyclerSubSettingBinding.inflate(LayoutInflater.from(parent.context), parent, false)) return MainViewHolder(
ItemRecyclerSubSettingBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false
)
)
} }
class MainViewHolder(val itemSubSettingBinding: ItemRecyclerSubSettingBinding) : RecyclerView.ViewHolder(itemSubSettingBinding.root) class MainViewHolder(val itemSubSettingBinding: ItemRecyclerSubSettingBinding) :
RecyclerView.ViewHolder(itemSubSettingBinding.root)
} }

View File

@@ -11,7 +11,6 @@ import android.content.Intent
import android.text.TextUtils import android.text.TextUtils
import android.view.Menu import android.view.Menu
import android.view.MenuItem import android.view.MenuItem
import com.google.zxing.WriterException
import com.tencent.mmkv.MMKV import com.tencent.mmkv.MMKV
import com.v2ray.ang.AppConfig import com.v2ray.ang.AppConfig
import com.v2ray.ang.databinding.ActivityTaskerBinding import com.v2ray.ang.databinding.ActivityTaskerBinding
@@ -65,7 +64,7 @@ class TaskerActivity : BaseActivity() {
listview?.setItemChecked(pos, true) listview?.setItemChecked(pos, true)
} }
} }
} catch (e: WriterException) { } catch (e: Exception) {
e.printStackTrace() e.printStackTrace()
} }

View File

@@ -3,11 +3,12 @@ package com.v2ray.ang.ui
import android.content.Intent import android.content.Intent
import android.net.Uri import android.net.Uri
import android.os.Bundle import android.os.Bundle
import com.google.zxing.WriterException import com.v2ray.ang.AppConfig
import com.v2ray.ang.R import com.v2ray.ang.R
import com.v2ray.ang.databinding.ActivityLogcatBinding import com.v2ray.ang.databinding.ActivityLogcatBinding
import com.v2ray.ang.extension.toast import com.v2ray.ang.extension.toast
import com.v2ray.ang.util.AngConfigManager import com.v2ray.ang.util.AngConfigManager
import java.net.URLDecoder
class UrlSchemeActivity : BaseActivity() { class UrlSchemeActivity : BaseActivity() {
private lateinit var binding: ActivityLogcatBinding private lateinit var binding: ActivityLogcatBinding
@@ -18,34 +19,68 @@ class UrlSchemeActivity : BaseActivity() {
val view = binding.root val view = binding.root
setContentView(view) setContentView(view)
var shareUrl: String = ""
try { try {
intent?.apply { intent.apply {
when (action) { if (action == Intent.ACTION_SEND) {
Intent.ACTION_SEND -> { if ("text/plain" == type) {
if ("text/plain" == type) { intent.getStringExtra(Intent.EXTRA_TEXT)?.let {
intent.getStringExtra(Intent.EXTRA_TEXT)?.let { val uri = Uri.parse(it)
shareUrl = it if (uri.scheme?.startsWith(AppConfig.HTTPS_PROTOCOL) == true || uri.scheme?.startsWith(
AppConfig.HTTP_PROTOCOL
) == true
) {
val name = uri.getQueryParameter("name") ?: "Subscription"
importSubscription(it, name)
} else {
importConfig(it)
} }
} }
} }
Intent.ACTION_VIEW -> { } else if (action == Intent.ACTION_VIEW) {
val uri: Uri? = intent.data when (data?.host) {
shareUrl = uri?.getQueryParameter("url")!! "install-config" -> {
val uri: Uri? = intent.data
val shareUrl: String = uri?.getQueryParameter("url")!!
toast(shareUrl)
importConfig(shareUrl)
}
"install-sub" -> {
val uri: Uri? = intent.data
val url = uri?.getQueryParameter("url")!!
val name = uri.getQueryParameter("name") ?: "Subscription"
importSubscription(url, name)
}
else -> {
toast(R.string.toast_failure)
}
} }
} }
} }
toast(shareUrl)
val count = AngConfigManager.importBatchConfig(shareUrl, "", false)
if (count > 0) {
toast(R.string.toast_success)
} else {
toast(R.string.toast_failure)
}
startActivity(Intent(this, MainActivity::class.java)) startActivity(Intent(this, MainActivity::class.java))
finish() finish()
} catch (e: WriterException) { } catch (e: Exception) {
e.printStackTrace() e.printStackTrace()
} }
} }
private fun importSubscription(url: String, name: String) {
val decodedUrl = URLDecoder.decode(url, "UTF-8")
val check = AngConfigManager.importSubscription(name, decodedUrl)
if (check) toast(R.string.import_subscription_success) else toast(R.string.import_subscription_failure)
}
private fun importConfig(shareUrl: String) {
val count = AngConfigManager.importBatchConfig(shareUrl, "", false)
if (count > 0) {
toast(R.string.toast_success)
} else {
toast(R.string.toast_failure)
}
}
} }

View File

@@ -4,6 +4,7 @@ import android.Manifest
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.content.Intent import android.content.Intent
import android.net.Uri import android.net.Uri
import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.provider.OpenableColumns import android.provider.OpenableColumns
import android.util.Log import android.util.Log
@@ -48,7 +49,6 @@ class UserAssetActivity : BaseActivity() {
val view = binding.root val view = binding.root
setContentView(view) setContentView(view)
title = getString(R.string.title_user_asset_setting) title = getString(R.string.title_user_asset_setting)
supportActionBar?.setDisplayHomeAsUpEnabled(true)
binding.recyclerView.setHasFixedSize(true) binding.recyclerView.setHasFixedSize(true)
binding.recyclerView.layoutManager = LinearLayoutManager(this) binding.recyclerView.layoutManager = LinearLayoutManager(this)
@@ -75,7 +75,14 @@ class UserAssetActivity : BaseActivity() {
} }
private fun showFileChooser() { private fun showFileChooser() {
RxPermissions(this).request(Manifest.permission.READ_EXTERNAL_STORAGE).subscribe { val permission = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
Manifest.permission.READ_MEDIA_IMAGES
} else {
Manifest.permission.READ_EXTERNAL_STORAGE
}
RxPermissions(this)
.request(permission)
.subscribe {
if (it) { if (it) {
val intent = Intent(Intent.ACTION_GET_CONTENT) val intent = Intent(Intent.ACTION_GET_CONTENT)
intent.type = "*/*" intent.type = "*/*"
@@ -91,7 +98,8 @@ class UserAssetActivity : BaseActivity() {
} catch (ex: android.content.ActivityNotFoundException) { } catch (ex: android.content.ActivityNotFoundException) {
toast(R.string.toast_require_file_manager) toast(R.string.toast_require_file_manager)
} }
} } else
toast(R.string.toast_permission_denied)
} }
} }

View File

@@ -20,11 +20,27 @@ import com.v2ray.ang.util.MmkvManager.KEY_SELECTED_SERVER
import java.net.URI import java.net.URI
import java.util.* import java.util.*
import com.v2ray.ang.extension.idnHost import com.v2ray.ang.extension.idnHost
import com.v2ray.ang.extension.toast
object AngConfigManager { object AngConfigManager {
private val mainStorage by lazy { MMKV.mmkvWithID(MmkvManager.ID_MAIN, MMKV.MULTI_PROCESS_MODE) } private val mainStorage by lazy {
private val serverRawStorage by lazy { MMKV.mmkvWithID(MmkvManager.ID_SERVER_RAW, MMKV.MULTI_PROCESS_MODE) } MMKV.mmkvWithID(
private val settingsStorage by lazy { MMKV.mmkvWithID(MmkvManager.ID_SETTING, MMKV.MULTI_PROCESS_MODE) } MmkvManager.ID_MAIN,
MMKV.MULTI_PROCESS_MODE
)
}
private val serverRawStorage by lazy {
MMKV.mmkvWithID(
MmkvManager.ID_SERVER_RAW,
MMKV.MULTI_PROCESS_MODE
)
}
private val settingsStorage by lazy {
MMKV.mmkvWithID(
MmkvManager.ID_SETTING,
MMKV.MULTI_PROCESS_MODE
)
}
private val subStorage by lazy { MMKV.mmkvWithID(MmkvManager.ID_SUB, MMKV.MULTI_PROCESS_MODE) } private val subStorage by lazy { MMKV.mmkvWithID(MmkvManager.ID_SUB, MMKV.MULTI_PROCESS_MODE) }
/** /**
@@ -82,8 +98,14 @@ object AngConfigManager {
).forEach { key -> ).forEach { key ->
settingsStorage?.encode(key, sharedPreferences.getBoolean(key, false)) settingsStorage?.encode(key, sharedPreferences.getBoolean(key, false))
} }
settingsStorage?.encode(AppConfig.PREF_SNIFFING_ENABLED, sharedPreferences.getBoolean(AppConfig.PREF_SNIFFING_ENABLED, true)) settingsStorage?.encode(
settingsStorage?.encode(AppConfig.PREF_PER_APP_PROXY_SET, sharedPreferences.getStringSet(AppConfig.PREF_PER_APP_PROXY_SET, setOf())) AppConfig.PREF_SNIFFING_ENABLED,
sharedPreferences.getBoolean(AppConfig.PREF_SNIFFING_ENABLED, true)
)
settingsStorage?.encode(
AppConfig.PREF_PER_APP_PROXY_SET,
sharedPreferences.getStringSet(AppConfig.PREF_PER_APP_PROXY_SET, setOf())
)
} }
private fun migrateVmessBean(angConfig: AngConfig, sharedPreferences: SharedPreferences) { private fun migrateVmessBean(angConfig: AngConfig, sharedPreferences: SharedPreferences) {
@@ -125,7 +147,8 @@ object AngConfigManager {
if (TextUtils.isEmpty(vmessBean.security) && TextUtils.isEmpty(vmessBean.id)) { if (TextUtils.isEmpty(vmessBean.security) && TextUtils.isEmpty(vmessBean.id)) {
server.users = null server.users = null
} else { } else {
val socksUsersBean = V2rayConfig.OutboundBean.OutSettingsBean.ServersBean.SocksUsersBean() val socksUsersBean =
V2rayConfig.OutboundBean.OutSettingsBean.ServersBean.SocksUsersBean()
socksUsersBean.user = vmessBean.security socksUsersBean.user = vmessBean.security
socksUsersBean.pass = vmessBean.id socksUsersBean.pass = vmessBean.id
server.users = listOf(socksUsersBean) server.users = listOf(socksUsersBean)
@@ -135,17 +158,27 @@ object AngConfigManager {
} }
} }
config.outboundBean?.streamSettings?.let { streamSetting -> config.outboundBean?.streamSettings?.let { streamSetting ->
val sni = streamSetting.populateTransportSettings(vmessBean.network, vmessBean.headerType, val sni = streamSetting.populateTransportSettings(
vmessBean.requestHost, vmessBean.path, vmessBean.path, vmessBean.requestHost, vmessBean.path, vmessBean.network,
vmessBean.headerType, vmessBean.path) vmessBean.headerType,
vmessBean.requestHost,
vmessBean.path,
vmessBean.path,
vmessBean.requestHost,
vmessBean.path,
vmessBean.headerType,
vmessBean.path
)
val allowInsecure = if (vmessBean.allowInsecure.isBlank()) { val allowInsecure = if (vmessBean.allowInsecure.isBlank()) {
settingsStorage?.decodeBool(AppConfig.PREF_ALLOW_INSECURE) ?: false settingsStorage?.decodeBool(AppConfig.PREF_ALLOW_INSECURE) ?: false
} else { } else {
vmessBean.allowInsecure.toBoolean() vmessBean.allowInsecure.toBoolean()
} }
var fingerprint = streamSetting.tlsSettings?.fingerprint var fingerprint = streamSetting.tlsSettings?.fingerprint
streamSetting.populateTlsSettings(vmessBean.streamSecurity, allowInsecure, streamSetting.populateTlsSettings(
vmessBean.sni.ifBlank { sni }, fingerprint, null, null, null, null) vmessBean.streamSecurity, allowInsecure,
vmessBean.sni.ifBlank { sni }, fingerprint, null, null, null, null
)
} }
} }
val key = MmkvManager.encodeServerConfig(vmessBean.guid, config) val key = MmkvManager.encodeServerConfig(vmessBean.guid, config)
@@ -168,14 +201,21 @@ object AngConfigManager {
/** /**
* import config form qrcode or... * import config form qrcode or...
*/ */
private fun importConfig(str: String?, subid: String, removedSelectedServer: ServerConfig?): Int { private fun importConfig(
str: String?,
subid: String,
removedSelectedServer: ServerConfig?
): Int {
try { try {
if (str == null || TextUtils.isEmpty(str)) { if (str == null || TextUtils.isEmpty(str)) {
return R.string.toast_none_data return R.string.toast_none_data
} }
//maybe sub //maybe sub
if (TextUtils.isEmpty(subid) && (str.startsWith(HTTP_PROTOCOL) || str.startsWith(HTTPS_PROTOCOL))) { if (TextUtils.isEmpty(subid) && (str.startsWith(HTTP_PROTOCOL) || str.startsWith(
HTTPS_PROTOCOL
))
) {
MmkvManager.importUrlAsSubscription(str) MmkvManager.importUrlAsSubscription(str)
return 0 return 0
} }
@@ -201,9 +241,9 @@ object AngConfigManager {
val vmessQRCode = Gson().fromJson(result, VmessQRCode::class.java) val vmessQRCode = Gson().fromJson(result, VmessQRCode::class.java)
// Although VmessQRCode fields are non null, looks like Gson may still create null fields // Although VmessQRCode fields are non null, looks like Gson may still create null fields
if (TextUtils.isEmpty(vmessQRCode.add) if (TextUtils.isEmpty(vmessQRCode.add)
|| TextUtils.isEmpty(vmessQRCode.port) || TextUtils.isEmpty(vmessQRCode.port)
|| TextUtils.isEmpty(vmessQRCode.id) || TextUtils.isEmpty(vmessQRCode.id)
|| TextUtils.isEmpty(vmessQRCode.net) || TextUtils.isEmpty(vmessQRCode.net)
) { ) {
return R.string.toast_incorrect_protocol return R.string.toast_incorrect_protocol
} }
@@ -213,16 +253,28 @@ object AngConfigManager {
vnext.address = vmessQRCode.add vnext.address = vmessQRCode.add
vnext.port = Utils.parseInt(vmessQRCode.port) vnext.port = Utils.parseInt(vmessQRCode.port)
vnext.users[0].id = vmessQRCode.id vnext.users[0].id = vmessQRCode.id
vnext.users[0].security = if (TextUtils.isEmpty(vmessQRCode.scy)) DEFAULT_SECURITY else vmessQRCode.scy vnext.users[0].security =
if (TextUtils.isEmpty(vmessQRCode.scy)) DEFAULT_SECURITY else vmessQRCode.scy
vnext.users[0].alterId = Utils.parseInt(vmessQRCode.aid) vnext.users[0].alterId = Utils.parseInt(vmessQRCode.aid)
} }
val sni = streamSetting.populateTransportSettings(vmessQRCode.net, vmessQRCode.type, vmessQRCode.host, val sni = streamSetting.populateTransportSettings(
vmessQRCode.path, vmessQRCode.path, vmessQRCode.host, vmessQRCode.path, vmessQRCode.type, vmessQRCode.path) vmessQRCode.net,
vmessQRCode.type,
vmessQRCode.host,
vmessQRCode.path,
vmessQRCode.path,
vmessQRCode.host,
vmessQRCode.path,
vmessQRCode.type,
vmessQRCode.path
)
val fingerprint = vmessQRCode.fp ?: streamSetting.tlsSettings?.fingerprint val fingerprint = vmessQRCode.fp ?: streamSetting.tlsSettings?.fingerprint
streamSetting.populateTlsSettings(vmessQRCode.tls, allowInsecure, streamSetting.populateTlsSettings(
if (TextUtils.isEmpty(vmessQRCode.sni)) sni else vmessQRCode.sni, vmessQRCode.tls, allowInsecure,
fingerprint, vmessQRCode.alpn, null, null, null) if (TextUtils.isEmpty(vmessQRCode.sni)) sni else vmessQRCode.sni,
fingerprint, vmessQRCode.alpn, null, null, null
)
} }
} }
} else if (str.startsWith(EConfigType.SHADOWSOCKS.protocolScheme)) { } else if (str.startsWith(EConfigType.SHADOWSOCKS.protocolScheme)) {
@@ -232,7 +284,8 @@ object AngConfigManager {
val indexSplit = result.indexOf("#") val indexSplit = result.indexOf("#")
if (indexSplit > 0) { if (indexSplit > 0) {
try { try {
config.remarks = Utils.urlDecode(result.substring(indexSplit + 1, result.length)) config.remarks =
Utils.urlDecode(result.substring(indexSplit + 1, result.length))
} catch (e: Exception) { } catch (e: Exception) {
e.printStackTrace() e.printStackTrace()
} }
@@ -243,13 +296,17 @@ object AngConfigManager {
//part decode //part decode
val indexS = result.indexOf("@") val indexS = result.indexOf("@")
result = if (indexS > 0) { result = if (indexS > 0) {
Utils.decode(result.substring(0, indexS)) + result.substring(indexS, result.length) Utils.decode(result.substring(0, indexS)) + result.substring(
indexS,
result.length
)
} else { } else {
Utils.decode(result) Utils.decode(result)
} }
val legacyPattern = "^(.+?):(.*)@(.+?):(\\d+?)/?$".toRegex() val legacyPattern = "^(.+?):(.*)@(.+?):(\\d+?)/?$".toRegex()
val match = legacyPattern.matchEntire(result) ?: return R.string.toast_incorrect_protocol val match = legacyPattern.matchEntire(result)
?: return R.string.toast_incorrect_protocol
config.outboundBean?.settings?.servers?.get(0)?.let { server -> config.outboundBean?.settings?.servers?.get(0)?.let { server ->
server.address = match.groupValues[3].removeSurrounding("[", "]") server.address = match.groupValues[3].removeSurrounding("[", "]")
@@ -264,7 +321,8 @@ object AngConfigManager {
config = ServerConfig.create(EConfigType.SOCKS) config = ServerConfig.create(EConfigType.SOCKS)
if (indexSplit > 0) { if (indexSplit > 0) {
try { try {
config.remarks = Utils.urlDecode(result.substring(indexSplit + 1, result.length)) config.remarks =
Utils.urlDecode(result.substring(indexSplit + 1, result.length))
} catch (e: Exception) { } catch (e: Exception) {
e.printStackTrace() e.printStackTrace()
} }
@@ -275,18 +333,23 @@ object AngConfigManager {
//part decode //part decode
val indexS = result.indexOf("@") val indexS = result.indexOf("@")
if (indexS > 0) { if (indexS > 0) {
result = Utils.decode(result.substring(0, indexS)) + result.substring(indexS, result.length) result = Utils.decode(result.substring(0, indexS)) + result.substring(
indexS,
result.length
)
} else { } else {
result = Utils.decode(result) result = Utils.decode(result)
} }
val legacyPattern = "^(.*):(.*)@(.+?):(\\d+?)$".toRegex() val legacyPattern = "^(.*):(.*)@(.+?):(\\d+?)$".toRegex()
val match = legacyPattern.matchEntire(result) ?: return R.string.toast_incorrect_protocol val match =
legacyPattern.matchEntire(result) ?: return R.string.toast_incorrect_protocol
config.outboundBean?.settings?.servers?.get(0)?.let { server -> config.outboundBean?.settings?.servers?.get(0)?.let { server ->
server.address = match.groupValues[3].removeSurrounding("[", "]") server.address = match.groupValues[3].removeSurrounding("[", "]")
server.port = match.groupValues[4].toInt() server.port = match.groupValues[4].toInt()
val socksUsersBean = V2rayConfig.OutboundBean.OutSettingsBean.ServersBean.SocksUsersBean() val socksUsersBean =
V2rayConfig.OutboundBean.OutSettingsBean.ServersBean.SocksUsersBean()
socksUsersBean.user = match.groupValues[1].lowercase() socksUsersBean.user = match.groupValues[1].lowercase()
socksUsersBean.pass = match.groupValues[2] socksUsersBean.pass = match.groupValues[2]
server.users = listOf(socksUsersBean) server.users = listOf(socksUsersBean)
@@ -302,17 +365,29 @@ object AngConfigManager {
val queryParam = uri.rawQuery.split("&") val queryParam = uri.rawQuery.split("&")
.associate { it.split("=").let { (k, v) -> k to Utils.urlDecode(v) } } .associate { it.split("=").let { (k, v) -> k to Utils.urlDecode(v) } }
val sni = config.outboundBean?.streamSettings?.populateTransportSettings(queryParam["type"] ?: "tcp", queryParam["headerType"], val sni = config.outboundBean?.streamSettings?.populateTransportSettings(
queryParam["host"], queryParam["path"], queryParam["seed"], queryParam["quicSecurity"], queryParam["key"], queryParam["type"] ?: "tcp",
queryParam["mode"], queryParam["serviceName"]) queryParam["headerType"],
queryParam["host"],
queryParam["path"],
queryParam["seed"],
queryParam["quicSecurity"],
queryParam["key"],
queryParam["mode"],
queryParam["serviceName"]
)
fingerprint = queryParam["fp"] ?: "" fingerprint = queryParam["fp"] ?: ""
config.outboundBean?.streamSettings?.populateTlsSettings(queryParam["security"] ?: TLS, config.outboundBean?.streamSettings?.populateTlsSettings(
allowInsecure, queryParam["sni"] ?: sni!!, fingerprint, queryParam["alpn"], queryParam["security"] ?: TLS,
null, null, null) allowInsecure, queryParam["sni"] ?: sni!!, fingerprint, queryParam["alpn"],
null, null, null
)
flow = queryParam["flow"] ?: "" flow = queryParam["flow"] ?: ""
} else { } else {
config.outboundBean?.streamSettings?.populateTlsSettings(TLS, allowInsecure, "", config.outboundBean?.streamSettings?.populateTlsSettings(
fingerprint, null, null, null, null) TLS, allowInsecure, "",
fingerprint, null, null, null, null
)
} }
config.outboundBean?.settings?.servers?.get(0)?.let { server -> config.outboundBean?.settings?.servers?.get(0)?.let { server ->
@@ -335,27 +410,41 @@ object AngConfigManager {
vnext.port = uri.port vnext.port = uri.port
vnext.users[0].id = uri.userInfo vnext.users[0].id = uri.userInfo
vnext.users[0].encryption = queryParam["encryption"] ?: "none" vnext.users[0].encryption = queryParam["encryption"] ?: "none"
vnext.users[0].flow =queryParam["flow"] ?: "" vnext.users[0].flow = queryParam["flow"] ?: ""
} }
val sni = streamSetting.populateTransportSettings(queryParam["type"] ?: "tcp", queryParam["headerType"], val sni = streamSetting.populateTransportSettings(
queryParam["host"], queryParam["path"], queryParam["seed"], queryParam["quicSecurity"], queryParam["key"], queryParam["type"] ?: "tcp",
queryParam["mode"], queryParam["serviceName"]) queryParam["headerType"],
queryParam["host"],
queryParam["path"],
queryParam["seed"],
queryParam["quicSecurity"],
queryParam["key"],
queryParam["mode"],
queryParam["serviceName"]
)
fingerprint = queryParam["fp"] ?: "" fingerprint = queryParam["fp"] ?: ""
val pbk = queryParam["pbk"] ?: "" val pbk = queryParam["pbk"] ?: ""
val sid = queryParam["sid"] ?: "" val sid = queryParam["sid"] ?: ""
val spx = Utils.urlDecode(queryParam["spx"] ?: "") val spx = Utils.urlDecode(queryParam["spx"] ?: "")
streamSetting.populateTlsSettings(queryParam["security"] ?: "", allowInsecure, streamSetting.populateTlsSettings(
queryParam["sni"] ?: sni, fingerprint, queryParam["alpn"], pbk, sid, spx) queryParam["security"] ?: "", allowInsecure,
queryParam["sni"] ?: sni, fingerprint, queryParam["alpn"], pbk, sid, spx
)
} }
if (config == null){ if (config == null) {
return R.string.toast_incorrect_protocol return R.string.toast_incorrect_protocol
} }
config.subscriptionId = subid config.subscriptionId = subid
val guid = MmkvManager.encodeServerConfig("", config) val guid = MmkvManager.encodeServerConfig("", config)
if (removedSelectedServer != null && if (removedSelectedServer != null &&
config.getProxyOutbound()?.getServerAddress() == removedSelectedServer.getProxyOutbound()?.getServerAddress() && config.getProxyOutbound()
config.getProxyOutbound()?.getServerPort() == removedSelectedServer.getProxyOutbound()?.getServerPort()) { ?.getServerAddress() == removedSelectedServer.getProxyOutbound()
?.getServerAddress() &&
config.getProxyOutbound()
?.getServerPort() == removedSelectedServer.getProxyOutbound()?.getServerPort()
) {
mainStorage?.encode(KEY_SELECTED_SERVER, guid) mainStorage?.encode(KEY_SELECTED_SERVER, guid)
} }
} catch (e: Exception) { } catch (e: Exception) {
@@ -365,14 +454,18 @@ object AngConfigManager {
return 0 return 0
} }
private fun tryParseNewVmess(uriString: String, config: ServerConfig, allowInsecure: Boolean): Boolean { private fun tryParseNewVmess(
uriString: String,
config: ServerConfig,
allowInsecure: Boolean
): Boolean {
return runCatching { return runCatching {
val uri = URI(uriString) val uri = URI(uriString)
check(uri.scheme == "vmess") check(uri.scheme == "vmess")
val (_, protocol, tlsStr, uuid, alterId) = val (_, protocol, tlsStr, uuid, alterId) =
Regex("(tcp|http|ws|kcp|quic|grpc)(\\+tls)?:([0-9a-z]{8}-[0-9a-z]{4}-[0-9a-z]{4}-[0-9a-z]{4}-[0-9a-z]{12})") Regex("(tcp|http|ws|kcp|quic|grpc)(\\+tls)?:([0-9a-z]{8}-[0-9a-z]{4}-[0-9a-z]{4}-[0-9a-z]{4}-[0-9a-z]{12})")
.matchEntire(uri.userInfo)?.groupValues .matchEntire(uri.userInfo)?.groupValues
?: error("parse user info fail.") ?: error("parse user info fail.")
val tls = tlsStr.isNotBlank() val tls = tlsStr.isNotBlank()
val queryParam = uri.rawQuery.split("&") val queryParam = uri.rawQuery.split("&")
.associate { it.split("=").let { (k, v) -> k to Utils.urlDecode(v) } } .associate { it.split("=").let { (k, v) -> k to Utils.urlDecode(v) } }
@@ -387,12 +480,19 @@ object AngConfigManager {
vnext.users[0].alterId = alterId.toInt() vnext.users[0].alterId = alterId.toInt()
} }
var fingerprint = streamSetting.tlsSettings?.fingerprint var fingerprint = streamSetting.tlsSettings?.fingerprint
val sni = streamSetting.populateTransportSettings(protocol, queryParam["type"], val sni = streamSetting.populateTransportSettings(protocol,
queryParam["host"]?.split("|")?.get(0) ?: "", queryParam["type"],
queryParam["path"]?.takeIf { it.trim() != "/" } ?: "", queryParam["seed"], queryParam["security"], queryParam["host"]?.split("|")?.get(0) ?: "",
queryParam["key"], queryParam["mode"], queryParam["serviceName"]) queryParam["path"]?.takeIf { it.trim() != "/" } ?: "",
streamSetting.populateTlsSettings(if (tls) TLS else "", allowInsecure, sni, fingerprint, null, queryParam["seed"],
null, null, null) queryParam["security"],
queryParam["key"],
queryParam["mode"],
queryParam["serviceName"])
streamSetting.populateTlsSettings(
if (tls) TLS else "", allowInsecure, sni, fingerprint, null,
null, null, null
)
true true
}.getOrElse { false } }.getOrElse { false }
} }
@@ -480,12 +580,16 @@ object AngConfigManager {
vmessQRCode.add = outbound.getServerAddress().orEmpty() vmessQRCode.add = outbound.getServerAddress().orEmpty()
vmessQRCode.port = outbound.getServerPort().toString() vmessQRCode.port = outbound.getServerPort().toString()
vmessQRCode.id = outbound.getPassword().orEmpty() vmessQRCode.id = outbound.getPassword().orEmpty()
vmessQRCode.aid = outbound.settings?.vnext?.get(0)?.users?.get(0)?.alterId.toString() vmessQRCode.aid =
vmessQRCode.scy = outbound.settings?.vnext?.get(0)?.users?.get(0)?.security.toString() outbound.settings?.vnext?.get(0)?.users?.get(0)?.alterId.toString()
vmessQRCode.scy =
outbound.settings?.vnext?.get(0)?.users?.get(0)?.security.toString()
vmessQRCode.net = streamSetting.network vmessQRCode.net = streamSetting.network
vmessQRCode.tls = streamSetting.security vmessQRCode.tls = streamSetting.security
vmessQRCode.sni = streamSetting.tlsSettings?.serverName.orEmpty() vmessQRCode.sni = streamSetting.tlsSettings?.serverName.orEmpty()
vmessQRCode.alpn = Utils.removeWhiteSpace(streamSetting.tlsSettings?.alpn?.joinToString()).orEmpty() vmessQRCode.alpn =
Utils.removeWhiteSpace(streamSetting.tlsSettings?.alpn?.joinToString())
.orEmpty()
vmessQRCode.fp = streamSetting.tlsSettings?.fingerprint.orEmpty() vmessQRCode.fp = streamSetting.tlsSettings?.fingerprint.orEmpty()
outbound.getTransportSettingDetails()?.let { transportDetails -> outbound.getTransportSettingDetails()?.let { transportDetails ->
vmessQRCode.type = transportDetails[0] vmessQRCode.type = transportDetails[0]
@@ -495,25 +599,37 @@ object AngConfigManager {
val json = Gson().toJson(vmessQRCode) val json = Gson().toJson(vmessQRCode)
Utils.encode(json) Utils.encode(json)
} }
EConfigType.CUSTOM, EConfigType.WIREGUARD -> "" EConfigType.CUSTOM, EConfigType.WIREGUARD -> ""
EConfigType.SHADOWSOCKS -> { EConfigType.SHADOWSOCKS -> {
val remark = "#" + Utils.urlEncode(config.remarks) val remark = "#" + Utils.urlEncode(config.remarks)
val pw = Utils.encode("${outbound.getSecurityEncryption()}:${outbound.getPassword()}") val pw =
val url = String.format("%s@%s:%s", Utils.encode("${outbound.getSecurityEncryption()}:${outbound.getPassword()}")
pw, val url = String.format(
Utils.getIpv6Address(outbound.getServerAddress()!!), "%s@%s:%s",
outbound.getServerPort())
url + remark
}
EConfigType.SOCKS -> {
val remark = "#" + Utils.urlEncode(config.remarks)
val pw = Utils.encode("${outbound.settings?.servers?.get(0)?.users?.get(0)?.user}:${outbound.getPassword()}")
val url = String.format("%s@%s:%s",
pw, pw,
Utils.getIpv6Address(outbound.getServerAddress()!!), Utils.getIpv6Address(outbound.getServerAddress()!!),
outbound.getServerPort()) outbound.getServerPort()
)
url + remark url + remark
} }
EConfigType.SOCKS -> {
val remark = "#" + Utils.urlEncode(config.remarks)
val pw =
if (outbound.settings?.servers?.get(0)?.users?.get(0)?.user != null)
"${outbound.settings?.servers?.get(0)?.users?.get(0)?.user}:${outbound.getPassword()}"
else
":"
val url = String.format(
"%s@%s:%s",
Utils.encode(pw),
Utils.getIpv6Address(outbound.getServerAddress()!!),
outbound.getServerPort()
)
url + remark
}
EConfigType.VLESS, EConfigType.VLESS,
EConfigType.TROJAN -> { EConfigType.TROJAN -> {
val remark = "#" + Utils.urlEncode(config.remarks) val remark = "#" + Utils.urlEncode(config.remarks)
@@ -537,12 +653,14 @@ object AngConfigManager {
} }
dicQuery["security"] = streamSetting.security.ifEmpty { "none" } dicQuery["security"] = streamSetting.security.ifEmpty { "none" }
(streamSetting.tlsSettings?: streamSetting.realitySettings)?.let { tlsSetting -> (streamSetting.tlsSettings
?: streamSetting.realitySettings)?.let { tlsSetting ->
if (!TextUtils.isEmpty(tlsSetting.serverName)) { if (!TextUtils.isEmpty(tlsSetting.serverName)) {
dicQuery["sni"] = tlsSetting.serverName dicQuery["sni"] = tlsSetting.serverName
} }
if (!tlsSetting.alpn.isNullOrEmpty() && tlsSetting.alpn.isNotEmpty()) { if (!tlsSetting.alpn.isNullOrEmpty() && tlsSetting.alpn.isNotEmpty()) {
dicQuery["alpn"] = Utils.removeWhiteSpace(tlsSetting.alpn.joinToString()).orEmpty() dicQuery["alpn"] =
Utils.removeWhiteSpace(tlsSetting.alpn.joinToString()).orEmpty()
} }
if (!TextUtils.isEmpty(tlsSetting.fingerprint)) { if (!TextUtils.isEmpty(tlsSetting.fingerprint)) {
dicQuery["fp"] = tlsSetting.fingerprint!! dicQuery["fp"] = tlsSetting.fingerprint!!
@@ -567,12 +685,14 @@ object AngConfigManager {
dicQuery["host"] = Utils.urlEncode(transportDetails[1]) dicQuery["host"] = Utils.urlEncode(transportDetails[1])
} }
} }
"kcp" -> { "kcp" -> {
dicQuery["headerType"] = transportDetails[0].ifEmpty { "none" } dicQuery["headerType"] = transportDetails[0].ifEmpty { "none" }
if (!TextUtils.isEmpty(transportDetails[2])) { if (!TextUtils.isEmpty(transportDetails[2])) {
dicQuery["seed"] = Utils.urlEncode(transportDetails[2]) dicQuery["seed"] = Utils.urlEncode(transportDetails[2])
} }
} }
"ws" -> { "ws" -> {
if (!TextUtils.isEmpty(transportDetails[1])) { if (!TextUtils.isEmpty(transportDetails[1])) {
dicQuery["host"] = Utils.urlEncode(transportDetails[1]) dicQuery["host"] = Utils.urlEncode(transportDetails[1])
@@ -581,6 +701,7 @@ object AngConfigManager {
dicQuery["path"] = Utils.urlEncode(transportDetails[2]) dicQuery["path"] = Utils.urlEncode(transportDetails[2])
} }
} }
"http", "h2" -> { "http", "h2" -> {
dicQuery["type"] = "http" dicQuery["type"] = "http"
if (!TextUtils.isEmpty(transportDetails[1])) { if (!TextUtils.isEmpty(transportDetails[1])) {
@@ -590,11 +711,13 @@ object AngConfigManager {
dicQuery["path"] = Utils.urlEncode(transportDetails[2]) dicQuery["path"] = Utils.urlEncode(transportDetails[2])
} }
} }
"quic" -> { "quic" -> {
dicQuery["headerType"] = transportDetails[0].ifEmpty { "none" } dicQuery["headerType"] = transportDetails[0].ifEmpty { "none" }
dicQuery["quicSecurity"] = Utils.urlEncode(transportDetails[1]) dicQuery["quicSecurity"] = Utils.urlEncode(transportDetails[1])
dicQuery["key"] = Utils.urlEncode(transportDetails[2]) dicQuery["key"] = Utils.urlEncode(transportDetails[2])
} }
"grpc" -> { "grpc" -> {
dicQuery["mode"] = transportDetails[0] dicQuery["mode"] = transportDetails[0]
dicQuery["serviceName"] = transportDetails[2] dicQuery["serviceName"] = transportDetails[2]
@@ -602,13 +725,15 @@ object AngConfigManager {
} }
} }
val query = "?" + dicQuery.toList().joinToString( val query = "?" + dicQuery.toList().joinToString(
separator = "&", separator = "&",
transform = { it.first + "=" + it.second }) transform = { it.first + "=" + it.second })
val url = String.format("%s@%s:%s", val url = String.format(
outbound.getPassword(), "%s@%s:%s",
Utils.getIpv6Address(outbound.getServerAddress()!!), outbound.getPassword(),
outbound.getServerPort()) Utils.getIpv6Address(outbound.getServerAddress()!!),
outbound.getServerPort()
)
url + query + remark url + query + remark
} }
} }
@@ -670,7 +795,7 @@ object AngConfigManager {
if (TextUtils.isEmpty(conf)) { if (TextUtils.isEmpty(conf)) {
return null return null
} }
return Utils.createQRCode(conf) return QRCodeDecoder.createQRCode(conf)
} catch (e: Exception) { } catch (e: Exception) {
e.printStackTrace() e.printStackTrace()
@@ -756,16 +881,34 @@ object AngConfigManager {
var count = 0 var count = 0
servers.lines() servers.lines()
.forEach { .reversed()
val resId = importConfig(it, subid, removedSelectedServer) .forEach {
if (resId == 0) { val resId = importConfig(it, subid, removedSelectedServer)
count++ if (resId == 0) {
} count++
} }
}
return count return count
} catch (e: Exception) { } catch (e: Exception) {
e.printStackTrace() e.printStackTrace()
} }
return 0 return 0
} }
fun importSubscription(remark: String, url: String, enabled: Boolean = true): Boolean {
val subId = Utils.getUuid()
val subItem = SubscriptionItem()
subItem.remarks = remark
subItem.url = url
subItem.enabled = enabled
if (TextUtils.isEmpty(subItem.remarks) || TextUtils.isEmpty(subItem.url)) {
return false
}
subStorage?.encode(subId, Gson().toJson(subItem))
return true
}
} }

View File

@@ -46,7 +46,7 @@ object MmkvManager {
serverStorage?.encode(key, Gson().toJson(config)) serverStorage?.encode(key, Gson().toJson(config))
val serverList = decodeServerList() val serverList = decodeServerList()
if (!serverList.contains(key)) { if (!serverList.contains(key)) {
serverList.add(key) serverList.add(0, key)
mainStorage?.encode(KEY_ANG_CONFIGS, Gson().toJson(serverList)) mainStorage?.encode(KEY_ANG_CONFIGS, Gson().toJson(serverList))
if (mainStorage?.decodeString(KEY_SELECTED_SERVER).isNullOrBlank()) { if (mainStorage?.decodeString(KEY_SELECTED_SERVER).isNullOrBlank()) {
mainStorage?.encode(KEY_SELECTED_SERVER, key) mainStorage?.encode(KEY_SELECTED_SERVER, key)

View File

@@ -5,6 +5,7 @@ import android.graphics.BitmapFactory
import com.google.zxing.* import com.google.zxing.*
import com.google.zxing.common.GlobalHistogramBinarizer import com.google.zxing.common.GlobalHistogramBinarizer
import com.google.zxing.common.HybridBinarizer import com.google.zxing.common.HybridBinarizer
import com.google.zxing.qrcode.QRCodeWriter
import java.util.* import java.util.*
/** /**
@@ -13,6 +14,36 @@ import java.util.*
object QRCodeDecoder { object QRCodeDecoder {
val HINTS: MutableMap<DecodeHintType, Any?> = EnumMap(DecodeHintType::class.java) val HINTS: MutableMap<DecodeHintType, Any?> = EnumMap(DecodeHintType::class.java)
/**
* create qrcode using zxing
*/
fun createQRCode(text: String, size: Int = 800): Bitmap? {
try {
val hints = HashMap<EncodeHintType, String>()
hints[EncodeHintType.CHARACTER_SET] = "utf-8"
val bitMatrix = QRCodeWriter().encode(text,
BarcodeFormat.QR_CODE, size, size, hints)
val pixels = IntArray(size * size)
for (y in 0 until size) {
for (x in 0 until size) {
if (bitMatrix.get(x, y)) {
pixels[y * size + x] = 0xff000000.toInt()
} else {
pixels[y * size + x] = 0xffffffff.toInt()
}
}
}
val bitmap = Bitmap.createBitmap(size, size,
Bitmap.Config.ARGB_8888)
bitmap.setPixels(pixels, 0, size, 0, 0, size, size)
return bitmap
} catch (e: Exception) {
e.printStackTrace()
return null
}
}
/** /**
* 同步解析本地图片二维码。该方法是耗时操作,请在子线程中调用。 * 同步解析本地图片二维码。该方法是耗时操作,请在子线程中调用。
* *

View File

@@ -4,20 +4,16 @@ import android.content.ClipboardManager
import android.content.Context import android.content.Context
import android.text.Editable import android.text.Editable
import android.util.Base64 import android.util.Base64
import com.google.zxing.WriterException
import android.graphics.Bitmap
import com.google.zxing.BarcodeFormat
import com.google.zxing.qrcode.QRCodeWriter
import com.google.zxing.EncodeHintType
import java.util.* import java.util.*
import kotlin.collections.HashMap
import android.content.ClipData import android.content.ClipData
import android.content.Intent import android.content.Intent
import android.content.pm.PackageManager
import android.content.res.Configuration.UI_MODE_NIGHT_MASK import android.content.res.Configuration.UI_MODE_NIGHT_MASK
import android.content.res.Configuration.UI_MODE_NIGHT_YES import android.content.res.Configuration.UI_MODE_NIGHT_NO
import android.net.Uri import android.net.Uri
import android.os.Build import android.os.Build
import android.os.LocaleList import android.os.LocaleList
import android.provider.Settings
import android.util.Log import android.util.Log
import android.util.Patterns import android.util.Patterns
import android.webkit.URLUtil import android.webkit.URLUtil
@@ -171,36 +167,6 @@ object Utils {
return ret return ret
} }
/**
* create qrcode using zxing
*/
fun createQRCode(text: String, size: Int = 800): Bitmap? {
try {
val hints = HashMap<EncodeHintType, String>()
hints[EncodeHintType.CHARACTER_SET] = "utf-8"
val bitMatrix = QRCodeWriter().encode(text,
BarcodeFormat.QR_CODE, size, size, hints)
val pixels = IntArray(size * size)
for (y in 0 until size) {
for (x in 0 until size) {
if (bitMatrix.get(x, y)) {
pixels[y * size + x] = 0xff000000.toInt()
} else {
pixels[y * size + x] = 0xffffffff.toInt()
}
}
}
val bitmap = Bitmap.createBitmap(size, size,
Bitmap.Config.ARGB_8888)
bitmap.setPixels(pixels, 0, size, 0, 0, size, size)
return bitmap
} catch (e: WriterException) {
e.printStackTrace()
return null
}
}
/** /**
* is ip address * is ip address
*/ */
@@ -213,7 +179,7 @@ object Utils {
//CIDR //CIDR
if (addr.indexOf("/") > 0) { if (addr.indexOf("/") > 0) {
val arr = addr.split("/") val arr = addr.split("/")
if (arr.count() == 2 && Integer.parseInt(arr[1]) > 0) { if (arr.count() == 2 && Integer.parseInt(arr[1]) > -1) {
addr = arr[0] addr = arr[0]
} }
} }
@@ -274,7 +240,7 @@ object Utils {
if (value != null && Patterns.WEB_URL.matcher(value).matches() || URLUtil.isValidUrl(value)) { if (value != null && Patterns.WEB_URL.matcher(value).matches() || URLUtil.isValidUrl(value)) {
return true return true
} }
} catch (e: WriterException) { } catch (e: Exception) {
e.printStackTrace() e.printStackTrace()
return false return false
} }
@@ -352,6 +318,11 @@ object Utils {
return extDir.absolutePath return extDir.absolutePath
} }
fun getDeviceIdForXUDPBaseKey(): String {
val androidId = Settings.Secure.ANDROID_ID.toByteArray(charset("UTF-8"))
return Base64.encodeToString(androidId.copyOf(32), Base64.NO_PADDING.or(Base64.URL_SAFE))
}
fun getUrlContext(url: String, timeout: Int): String { fun getUrlContext(url: String, timeout: Int): String {
var result: String var result: String
var conn: HttpURLConnection? = null var conn: HttpURLConnection? = null
@@ -391,11 +362,11 @@ object Utils {
fun getDarkModeStatus(context: Context): Boolean { fun getDarkModeStatus(context: Context): Boolean {
val mode = context.resources.configuration.uiMode and UI_MODE_NIGHT_MASK val mode = context.resources.configuration.uiMode and UI_MODE_NIGHT_MASK
return mode == UI_MODE_NIGHT_YES return mode != UI_MODE_NIGHT_NO
} }
fun getIpv6Address(address: String): String { fun getIpv6Address(address: String): String {
return if (isIpv6Address(address)) { return if (isIpv6Address(address) && !address.contains('[') && !address.contains(']')) {
String.format("[%s]", address) String.format("[%s]", address)
} else { } else {
address address
@@ -435,5 +406,9 @@ object Utils {
return URL(url.protocol, IDN.toASCII(url.host, IDN.ALLOW_UNASSIGNED), url.port, url.file) return URL(url.protocol, IDN.toASCII(url.host, IDN.ALLOW_UNASSIGNED), url.port, url.file)
.toExternalForm() .toExternalForm()
} }
fun isTv(context: Context): Boolean =
context.packageManager.hasSystemFeature(PackageManager.FEATURE_LEANBACK)
} }

View File

@@ -64,7 +64,7 @@ object V2rayConfigUtil {
inbounds(v2rayConfig) inbounds(v2rayConfig)
httpRequestObject(outbound) updateOutboundWithGlobalSettings(outbound)
v2rayConfig.outbounds[0] = outbound v2rayConfig.outbounds[0] = outbound
@@ -399,10 +399,48 @@ object V2rayConfigUtil {
return true return true
} }
private fun httpRequestObject(outbound: V2rayConfig.OutboundBean): Boolean { private fun updateOutboundWithGlobalSettings(outbound: V2rayConfig.OutboundBean): Boolean {
try { try {
var muxEnabled = settingsStorage?.decodeBool(AppConfig.PREF_MUX_ENABLED, false)
val protocol = outbound.protocol
if (protocol.equals(EConfigType.SHADOWSOCKS.name, true)
|| protocol.equals(EConfigType.SOCKS.name, true)
|| protocol.equals(EConfigType.TROJAN.name, true)
) {
muxEnabled = false
} else if (protocol.equals(EConfigType.VLESS.name, true)
&& outbound.settings?.vnext?.get(0)?.users?.get(0)?.flow?.isNotEmpty() == true
) {
muxEnabled = false
}
if (muxEnabled == true) {
outbound.mux?.enabled = true
outbound.mux?.concurrency =
settingsStorage?.decodeInt(AppConfig.PREF_MUX_CONCURRENCY) ?: 8
outbound.mux?.xudpConcurrency =
settingsStorage?.decodeInt(AppConfig.PREF_MUX_XUDP_CONCURRENCY) ?: 8
outbound.mux?.xudpProxyUDP443 =
settingsStorage?.decodeString(AppConfig.PREF_MUX_XUDP_QUIC) ?: "reject"
} else {
outbound.mux?.enabled = false
outbound.mux?.concurrency = -1
}
if (protocol.equals(EConfigType.WIREGUARD.name, true)) {
var localTunAddr = if (outbound.settings?.address == null) {
listOf("172.16.0.2/32", "2606:4700:110:8f81:d551:a0:532e:a2b3/128")
} else {
outbound.settings?.address as List<*>
}
if (settingsStorage?.decodeBool(AppConfig.PREF_PREFER_IPV6) != true) {
localTunAddr = listOf(localTunAddr.first())
}
outbound.settings?.address = localTunAddr
}
if (outbound.streamSettings?.network == DEFAULT_NETWORK if (outbound.streamSettings?.network == DEFAULT_NETWORK
&& outbound.streamSettings?.tcpSettings?.header?.type == HTTP) { && outbound.streamSettings?.tcpSettings?.header?.type == HTTP
) {
val path = outbound.streamSettings?.tcpSettings?.header?.request?.path val path = outbound.streamSettings?.tcpSettings?.header?.request?.path
val host = outbound.streamSettings?.tcpSettings?.header?.request?.headers?.Host val host = outbound.streamSettings?.tcpSettings?.header?.request?.headers?.Host

View File

@@ -2,6 +2,7 @@ package com.v2ray.ang.viewmodel
import android.app.Application import android.app.Application
import android.content.* import android.content.*
import android.os.Build
import android.util.Log import android.util.Log
import android.view.LayoutInflater import android.view.LayoutInflater
import android.widget.ArrayAdapter import android.widget.ArrayAdapter
@@ -24,12 +25,28 @@ import kotlinx.coroutines.*
import java.util.* import java.util.*
class MainViewModel(application: Application) : AndroidViewModel(application) { class MainViewModel(application: Application) : AndroidViewModel(application) {
private val mainStorage by lazy { MMKV.mmkvWithID(MmkvManager.ID_MAIN, MMKV.MULTI_PROCESS_MODE) } private val mainStorage by lazy {
private val serverRawStorage by lazy { MMKV.mmkvWithID(MmkvManager.ID_SERVER_RAW, MMKV.MULTI_PROCESS_MODE) } MMKV.mmkvWithID(
MmkvManager.ID_MAIN,
MMKV.MULTI_PROCESS_MODE
)
}
private val serverRawStorage by lazy {
MMKV.mmkvWithID(
MmkvManager.ID_SERVER_RAW,
MMKV.MULTI_PROCESS_MODE
)
}
private val settingsStorage by lazy {
MMKV.mmkvWithID(
MmkvManager.ID_SETTING,
MMKV.MULTI_PROCESS_MODE
)
}
var serverList = MmkvManager.decodeServerList() var serverList = MmkvManager.decodeServerList()
var subscriptionId: String = "" var subscriptionId: String = settingsStorage.decodeString(AppConfig.CACHE_SUBSCRIPTION_ID, "")!!
var keywordFilter: String = "" var keywordFilter: String = settingsStorage.decodeString(AppConfig.CACHE_KEYWORD_FILTER, "")!!
private set private set
val serversCache = mutableListOf<ServersCache>() val serversCache = mutableListOf<ServersCache>()
val isRunning by lazy { MutableLiveData<Boolean>() } val isRunning by lazy { MutableLiveData<Boolean>() }
@@ -40,7 +57,18 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
fun startListenBroadcast() { fun startListenBroadcast() {
isRunning.value = false isRunning.value = false
getApplication<AngApplication>().registerReceiver(mMsgReceiver, IntentFilter(AppConfig.BROADCAST_ACTION_ACTIVITY)) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
getApplication<AngApplication>().registerReceiver(
mMsgReceiver,
IntentFilter(AppConfig.BROADCAST_ACTION_ACTIVITY),
Context.RECEIVER_EXPORTED
)
} else {
getApplication<AngApplication>().registerReceiver(
mMsgReceiver,
IntentFilter(AppConfig.BROADCAST_ACTION_ACTIVITY)
)
}
MessageUtil.sendMsg2Service(getApplication(), AppConfig.MSG_REGISTER_CLIENT, "") MessageUtil.sendMsg2Service(getApplication(), AppConfig.MSG_REGISTER_CLIENT, "")
} }
@@ -62,7 +90,7 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
serverList.remove(guid) serverList.remove(guid)
MmkvManager.removeServer(guid) MmkvManager.removeServer(guid)
val index = getPosition(guid) val index = getPosition(guid)
if(index >= 0){ if (index >= 0) {
serversCache.removeAt(index) serversCache.removeAt(index)
} }
} }
@@ -74,8 +102,8 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
config.fullConfig = Gson().fromJson(server, V2rayConfig::class.java) config.fullConfig = Gson().fromJson(server, V2rayConfig::class.java)
val key = MmkvManager.encodeServerConfig("", config) val key = MmkvManager.encodeServerConfig("", config)
serverRawStorage?.encode(key, server) serverRawStorage?.encode(key, server)
serverList.add(key) serverList.add(0, key)
serversCache.add(ServersCache(key,config)) serversCache.add(0, ServersCache(key, config))
} }
fun swapServer(fromPosition: Int, toPosition: Int) { fun swapServer(fromPosition: Int, toPosition: Int) {
@@ -115,7 +143,7 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
val testResult = SpeedtestUtil.tcping(serverAddress, serverPort) val testResult = SpeedtestUtil.tcping(serverAddress, serverPort)
launch(Dispatchers.Main) { launch(Dispatchers.Main) {
MmkvManager.encodeServerTestDelayMillis(item.guid, testResult) MmkvManager.encodeServerTestDelayMillis(item.guid, testResult)
updateListAction.value = getPosition(item.guid) updateListAction.value = getPosition(item.guid)
} }
} }
} }
@@ -128,12 +156,18 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
MmkvManager.clearAllTestDelayResults() MmkvManager.clearAllTestDelayResults()
updateListAction.value = -1 // update all updateListAction.value = -1 // update all
val serversCopy = serversCache.toList() // Create a copy of the list
getApplication<AngApplication>().toast(R.string.connection_test_testing) getApplication<AngApplication>().toast(R.string.connection_test_testing)
viewModelScope.launch(Dispatchers.Default) { // without Dispatchers.Default viewModelScope will launch in main thread viewModelScope.launch(Dispatchers.Default) { // without Dispatchers.Default viewModelScope will launch in main thread
for (item in serversCache) { for (item in serversCopy) {
val config = V2rayConfigUtil.getV2rayConfig(getApplication(), item.guid) val config = V2rayConfigUtil.getV2rayConfig(getApplication(), item.guid)
if (config.status) { if (config.status) {
MessageUtil.sendMsg2TestService(getApplication(), AppConfig.MSG_MEASURE_CONFIG, Pair(item.guid, config.content)) MessageUtil.sendMsg2TestService(
getApplication(),
AppConfig.MSG_MEASURE_CONFIG,
Pair(item.guid, config.content)
)
} }
} }
} }
@@ -143,7 +177,7 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
MessageUtil.sendMsg2Service(getApplication(), AppConfig.MSG_MEASURE_DELAY, "") MessageUtil.sendMsg2Service(getApplication(), AppConfig.MSG_MEASURE_DELAY, "")
} }
fun filterConfig(context :Context) { fun filterConfig(context: Context) {
val subscriptions = MmkvManager.decodeSubscriptions() val subscriptions = MmkvManager.decodeSubscriptions()
val listId = subscriptions.map { it.first }.toList().toMutableList() val listId = subscriptions.map { it.first }.toList().toMutableList()
val listRemarks = subscriptions.map { it.second.remarks }.toList().toMutableList() val listRemarks = subscriptions.map { it.second.remarks }.toList().toMutableList()
@@ -155,7 +189,11 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
} }
val ivBinding = DialogConfigFilterBinding.inflate(LayoutInflater.from(context)) val ivBinding = DialogConfigFilterBinding.inflate(LayoutInflater.from(context))
ivBinding.spSubscriptionId.adapter = ArrayAdapter<String>( context, android.R.layout.simple_spinner_dropdown_item, listRemarks) ivBinding.spSubscriptionId.adapter = ArrayAdapter<String>(
context,
android.R.layout.simple_spinner_dropdown_item,
listRemarks
)
ivBinding.spSubscriptionId.setSelection(checkedItem) ivBinding.spSubscriptionId.setSelection(checkedItem)
ivBinding.etKeyword.text = Utils.getEditable(keywordFilter) ivBinding.etKeyword.text = Utils.getEditable(keywordFilter)
val builder = AlertDialog.Builder(context).setView(ivBinding.root) val builder = AlertDialog.Builder(context).setView(ivBinding.root)
@@ -169,6 +207,8 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
subscriptions[position].first subscriptions[position].first
} }
keywordFilter = ivBinding.etKeyword.text.toString() keywordFilter = ivBinding.etKeyword.text.toString()
settingsStorage?.encode(AppConfig.CACHE_SUBSCRIPTION_ID, subscriptionId)
settingsStorage?.encode(AppConfig.CACHE_KEYWORD_FILTER, keywordFilter)
reloadServerList() reloadServerList()
dialogInterface?.dismiss() dialogInterface?.dismiss()
@@ -193,7 +233,7 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
// }.show() // }.show()
} }
fun getPosition(guid: String) : Int { fun getPosition(guid: String): Int {
serversCache.forEachIndexed { index, it -> serversCache.forEachIndexed { index, it ->
if (it.guid == guid) if (it.guid == guid)
return index return index
@@ -206,20 +246,24 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
serversCache.forEachIndexed { index, it -> serversCache.forEachIndexed { index, it ->
val outbound = it.config.getProxyOutbound() val outbound = it.config.getProxyOutbound()
serversCache.forEachIndexed { index2, it2 -> serversCache.forEachIndexed { index2, it2 ->
if(index2 > index){ if (index2 > index) {
val outbound2 = it2.config.getProxyOutbound() val outbound2 = it2.config.getProxyOutbound()
if( outbound == outbound2 && !deleteServer.contains(it2.guid)) if (outbound == outbound2 && !deleteServer.contains(it2.guid)) {
{
deleteServer.add(it2.guid) deleteServer.add(it2.guid)
} }
} }
} }
} }
for(it in deleteServer){ for (it in deleteServer) {
MmkvManager.removeServer(it) MmkvManager.removeServer(it)
} }
reloadServerList() reloadServerList()
getApplication<AngApplication>().toast(getApplication<AngApplication>().getString(R.string.title_del_duplicate_config_count, deleteServer.count())) getApplication<AngApplication>().toast(
getApplication<AngApplication>().getString(
R.string.title_del_duplicate_config_count,
deleteServer.count()
)
)
} }
private val mMsgReceiver = object : BroadcastReceiver() { private val mMsgReceiver = object : BroadcastReceiver() {
@@ -228,23 +272,29 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
AppConfig.MSG_STATE_RUNNING -> { AppConfig.MSG_STATE_RUNNING -> {
isRunning.value = true isRunning.value = true
} }
AppConfig.MSG_STATE_NOT_RUNNING -> { AppConfig.MSG_STATE_NOT_RUNNING -> {
isRunning.value = false isRunning.value = false
} }
AppConfig.MSG_STATE_START_SUCCESS -> { AppConfig.MSG_STATE_START_SUCCESS -> {
getApplication<AngApplication>().toast(R.string.toast_services_success) getApplication<AngApplication>().toast(R.string.toast_services_success)
isRunning.value = true isRunning.value = true
} }
AppConfig.MSG_STATE_START_FAILURE -> { AppConfig.MSG_STATE_START_FAILURE -> {
getApplication<AngApplication>().toast(R.string.toast_services_failure) getApplication<AngApplication>().toast(R.string.toast_services_failure)
isRunning.value = false isRunning.value = false
} }
AppConfig.MSG_STATE_STOP_SUCCESS -> { AppConfig.MSG_STATE_STOP_SUCCESS -> {
isRunning.value = false isRunning.value = false
} }
AppConfig.MSG_MEASURE_DELAY_SUCCESS -> { AppConfig.MSG_MEASURE_DELAY_SUCCESS -> {
updateTestResultAction.value = intent.getStringExtra("content") updateTestResultAction.value = intent.getStringExtra("content")
} }
AppConfig.MSG_MEASURE_CONFIG_SUCCESS -> { AppConfig.MSG_MEASURE_CONFIG_SUCCESS -> {
val resultPair = intent.getSerializableExtra("content") as Pair<String, Long> val resultPair = intent.getSerializableExtra("content") as Pair<String, Long>
MmkvManager.encodeServerTestDelayMillis(resultPair.first, resultPair.second) MmkvManager.encodeServerTestDelayMillis(resultPair.first, resultPair.second)

View File

@@ -39,7 +39,9 @@ class SettingsViewModel(application: Application) : AndroidViewModel(application
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.PREF_MUX_XUDP_QUIC, -> {
settingsStorage?.encode(key, sharedPreferences.getString(key, "")) settingsStorage?.encode(key, sharedPreferences.getString(key, ""))
} }
AppConfig.PREF_SPEED_ENABLED, AppConfig.PREF_SPEED_ENABLED,
@@ -50,12 +52,19 @@ class SettingsViewModel(application: Application) : AndroidViewModel(application
AppConfig.PREF_PREFER_IPV6, AppConfig.PREF_PREFER_IPV6,
AppConfig.PREF_PER_APP_PROXY, AppConfig.PREF_PER_APP_PROXY,
AppConfig.PREF_BYPASS_APPS, AppConfig.PREF_BYPASS_APPS,
AppConfig.PREF_CONFIRM_REMOVE, -> { AppConfig.PREF_CONFIRM_REMOVE,
AppConfig.PREF_START_SCAN_IMMEDIATE,
AppConfig.SUBSCRIPTION_AUTO_UPDATE,
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_XUDP_CONCURRENCY -> {
settingsStorage?.encode(key, sharedPreferences.getString(key, "8")?.toIntOrNull() ?: 8)
}
AppConfig.PREF_PER_APP_PROXY_SET -> { AppConfig.PREF_PER_APP_PROXY_SET -> {
settingsStorage?.encode(key, sharedPreferences.getStringSet(key, setOf())) settingsStorage?.encode(key, sharedPreferences.getStringSet(key, setOf()))
} }

Binary file not shown.

After

Width:  |  Height:  |  Size: 526 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 651 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 386 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 476 B

View File

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FFFFFFFF"
android:pathData="M9,16.2L4.8,12l-1.4,1.4L9,19 21,7l-1.4,-1.4L9,16.2z"/>
</vector>

View File

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FFFFFFFF"
android:pathData="M3,17.25V21h3.75L17.81,9.94l-3.75,-3.75L3,17.25zM20.71,7.04c0.39,-0.39 0.39,-1.02 0,-1.41l-2.34,-2.34c-0.39,-0.39 -1.02,-0.39 -1.41,0l-1.83,1.83 3.75,3.75 1.83,-1.83z"/>
</vector>

View File

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FFF"
android:pathData="M9,16.17L4.83,12l-1.42,1.41L9,19 21,7l-1.41,-1.41z" />
</vector>

View File

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#fff"
android:pathData="M21,19V5c0,-1.1 -0.9,-2 -2,-2H5c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2zM8.5,13.5l2.5,3.01L14.5,12l4.5,6H5l3.5,-4.5z"/>
</vector>

View File

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportHeight="24.0"
android:viewportWidth="24.0">
<path
android:fillColor="#FFFFFFFF"
android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zm1,15h-2v-6h2v6zm0,-8h-2V7h2v2z" />
</vector>

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 679 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 855 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 984 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@@ -4,6 +4,6 @@
android:viewportWidth="24.0" android:viewportWidth="24.0"
android:viewportHeight="24.0"> android:viewportHeight="24.0">
<path <path
android:fillColor="#FFFFFFFF" android:fillColor="#FF000000"
android:pathData="M9,16.2L4.8,12l-1.4,1.4L9,19 21,7l-1.4,-1.4L9,16.2z"/> android:pathData="M9,16.2L4.8,12l-1.4,1.4L9,19 21,7l-1.4,-1.4L9,16.2z"/>
</vector> </vector>

View File

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FF000000"
android:pathData="M19,13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"/>
</vector>

View File

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FF000000"
android:pathData="M19.35,10.04C18.67,6.59 15.64,4 12,4 9.11,4 6.6,5.64 5.35,8.04 2.34,8.36 0,10.91 0,14c0,3.31 2.69,6 6,6h13c2.76,0 5,-2.24 5,-5 0,-2.64 -2.05,-4.78 -4.65,-4.96zM17,13l-5,5 -5,-5h3V9h4v4h3z" />
</vector>

View File

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FF000000"
android:pathData="M16,1L4,1c-1.1,0 -2,0.9 -2,2v14h2L4,3h12L16,1zM19,5L8,5c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h11c1.1,0 2,-0.9 2,-2L21,7c0,-1.1 -0.9,-2 -2,-2zM19,21L8,21L8,7h11v14z"/>
</vector>

View File

@@ -4,6 +4,6 @@
android:viewportWidth="24.0" android:viewportWidth="24.0"
android:viewportHeight="24.0"> android:viewportHeight="24.0">
<path <path
android:fillColor="#FFF" android:fillColor="#000"
android:pathData="M9,16.17L4.83,12l-1.42,1.41L9,19 21,7l-1.41,-1.41z" /> android:pathData="M9,16.17L4.83,12l-1.42,1.41L9,19 21,7l-1.41,-1.41z" />
</vector> </vector>

View File

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FF000000"
android:pathData="M20,2L4,2c-1.1,0 -1.99,0.9 -1.99,2L2,22l4,-4h14c1.1,0 2,-0.9 2,-2L22,4c0,-1.1 -0.9,-2 -2,-2zM13,14h-2v-2h2v2zM13,10h-2L11,6h2v4z"/>
</vector>

View File

@@ -0,0 +1,34 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M11,23h8a3,3 0,0 0,3 -3V8L15,1H7A3,3 0,0 0,4 4V9"
android:strokeLineJoin="round"
android:strokeWidth="2"
android:fillColor="#00000000"
android:strokeColor="#000"
android:strokeLineCap="round"/>
<path
android:pathData="M15,5a3,3 0,0 0,3 3h4L15,1Z"
android:strokeLineJoin="round"
android:strokeWidth="2"
android:fillColor="#00000000"
android:strokeColor="#000"
android:strokeLineCap="round"/>
<path
android:pathData="M2,17L10,17"
android:strokeLineJoin="round"
android:strokeWidth="2"
android:fillColor="#00000000"
android:strokeColor="#000"
android:strokeLineCap="round"/>
<path
android:pathData="M6,13L6,21"
android:strokeLineJoin="round"
android:strokeWidth="2"
android:fillColor="#00000000"
android:strokeColor="#000"
android:strokeLineCap="round"/>
</vector>

View File

@@ -4,6 +4,6 @@
android:viewportWidth="24.0" android:viewportWidth="24.0"
android:viewportHeight="24.0"> android:viewportHeight="24.0">
<path <path
android:fillColor="#fff" android:fillColor="#000"
android:pathData="M21,19V5c0,-1.1 -0.9,-2 -2,-2H5c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2zM8.5,13.5l2.5,3.01L14.5,12l4.5,6H5l3.5,-4.5z"/> android:pathData="M21,19V5c0,-1.1 -0.9,-2 -2,-2H5c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2zM8.5,13.5l2.5,3.01L14.5,12l4.5,6H5l3.5,-4.5z"/>
</vector> </vector>

View File

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FF000000"
android:pathData="M11,15h2v2h-2zM11,7h2v6h-2zM11.99,2C6.47,2 2,6.48 2,12s4.47,10 9.99,10C17.52,22 22,17.52 22,12S17.52,2 11.99,2zM12,20c-4.42,0 -8,-3.58 -8,-8s3.58,-8 8,-8 8,3.58 8,8 -3.58,8 -8,8z"/>
</vector>

View File

@@ -0,0 +1,5 @@
<vector android:height="24dp" android:tint="#000000"
android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M7,6h10l-5.01,6.3L7,6zM4.25,5.61C6.27,8.2 10,13 10,13v6c0,0.55 0.45,1 1,1h2c0.55,0 1,-0.45 1,-1v-6c0,0 3.72,-4.8 5.74,-7.39C20.25,4.95 19.78,4 18.95,4H5.04C4.21,4 3.74,4.95 4.25,5.61z"/>
</vector>

View File

@@ -0,0 +1,19 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="48"
android:viewportHeight="48">
<path
android:fillColor="@color/colorBg"
android:pathData="M24,24m-22,0a22,22 0,1 1,44 0a22,22 0,1 1,-44 0Z" />
<path
android:fillColor="@color/colorText"
android:pathData="M24,21.8 C25.7673,21.8,27.2,23.2327,27.2,25 C27.2,26.7673,25.7673,28.2,24,28.2
C22.2327,28.2,20.8,26.7673,20.8,25 C20.8,23.2327,22.2327,21.8,24,21.8 Z" />
<path
android:fillColor="@color/colorText"
android:pathData="M21,15 L19.17,17 L16,17 A2,2,0,0,0,14,19 L14,31 A2,2,0,0,0,16,33 L32,33
A2,2,0,0,0,34,31 L34,19 A2,2,0,0,0,32,17 L28.83,17 L27,15 Z M24,30
A5,5,0,1,1,29,25 A5,5,0,0,1,24,30 Z" />
</vector>

View File

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:drawable="@drawable/ic_shortcut_background"
android:left="2dp"
android:top="2dp"
android:right="2dp"
android:bottom="2dp" />
<item
android:drawable="@drawable/ic_stat_name_black"
android:left="12dp"
android:top="12dp"
android:right="12dp"
android:bottom="12dp" />
</layer-list>

View File

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

View File

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

View File

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FF000000"
android:pathData="M17,3L5,3c-1.11,0 -2,0.9 -2,2v14c0,1.1 0.89,2 2,2h14c1.1,0 2,-0.9 2,-2L21,7l-4,-4zM12,19c-1.66,0 -3,-1.34 -3,-3s1.34,-3 3,-3 3,1.34 3,3 -1.34,3 -3,3zM15,9L5,9L5,5h10v4z"/>
</vector>

View File

@@ -5,14 +5,14 @@
android:viewportHeight="48"> android:viewportHeight="48">
<path <path
android:fillColor="#999999" android:fillColor="#050505"
android:pathData="M24,24m-22,0a22,22 0,1 1,44 0a22,22 0,1 1,-44 0Z" /> android:pathData="M24,24m-22,0a22,22 0,1 1,44 0a22,22 0,1 1,-44 0Z" />
<path <path
android:fillColor="#ffffff" android:fillColor="#FFFFFF"
android:pathData="M24,21.8 C25.7673,21.8,27.2,23.2327,27.2,25 C27.2,26.7673,25.7673,28.2,24,28.2 android:pathData="M24,21.8 C25.7673,21.8,27.2,23.2327,27.2,25 C27.2,26.7673,25.7673,28.2,24,28.2
C22.2327,28.2,20.8,26.7673,20.8,25 C20.8,23.2327,22.2327,21.8,24,21.8 Z" /> C22.2327,28.2,20.8,26.7673,20.8,25 C20.8,23.2327,22.2327,21.8,24,21.8 Z" />
<path <path
android:fillColor="#ffffff" android:fillColor="#FFFFFF"
android:pathData="M21,15 L19.17,17 L16,17 A2,2,0,0,0,14,19 L14,31 A2,2,0,0,0,16,33 L32,33 android:pathData="M21,15 L19.17,17 L16,17 A2,2,0,0,0,14,19 L14,31 A2,2,0,0,0,16,33 L32,33
A2,2,0,0,0,34,31 L34,19 A2,2,0,0,0,32,17 L28.83,17 L27,15 Z M24,30 A2,2,0,0,0,34,31 L34,19 A2,2,0,0,0,32,17 L28.83,17 L27,15 Z M24,30
A5,5,0,1,1,29,25 A5,5,0,0,1,24,30 Z" /> A5,5,0,1,1,29,25 A5,5,0,0,1,24,30 Z" />

View File

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

View File

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FF000000"
android:pathData="M19.43,12.98c0.04,-0.32 0.07,-0.64 0.07,-0.98s-0.03,-0.66 -0.07,-0.98l2.11,-1.65c0.19,-0.15 0.24,-0.42 0.12,-0.64l-2,-3.46c-0.12,-0.22 -0.39,-0.3 -0.61,-0.22l-2.49,1c-0.52,-0.4 -1.08,-0.73 -1.69,-0.98l-0.38,-2.65C14.46,2.18 14.25,2 14,2h-4c-0.25,0 -0.46,0.18 -0.49,0.42l-0.38,2.65c-0.61,0.25 -1.17,0.59 -1.69,0.98l-2.49,-1c-0.23,-0.09 -0.49,0 -0.61,0.22l-2,3.46c-0.13,0.22 -0.07,0.49 0.12,0.64l2.11,1.65c-0.04,0.32 -0.07,0.65 -0.07,0.98s0.03,0.66 0.07,0.98l-2.11,1.65c-0.19,0.15 -0.24,0.42 -0.12,0.64l2,3.46c0.12,0.22 0.39,0.3 0.61,0.22l2.49,-1c0.52,0.4 1.08,0.73 1.69,0.98l0.38,2.65c0.03,0.24 0.24,0.42 0.49,0.42h4c0.25,0 0.46,-0.18 0.49,-0.42l0.38,-2.65c0.61,-0.25 1.17,-0.59 1.69,-0.98l2.49,1c0.23,0.09 0.49,0 0.61,-0.22l2,-3.46c0.12,-0.22 0.07,-0.49 -0.12,-0.64l-2.11,-1.65zM12,15.5c-1.93,0 -3.5,-1.57 -3.5,-3.5s1.57,-3.5 3.5,-3.5 3.5,1.57 3.5,3.5 -1.57,3.5 -3.5,3.5z"/>
</vector>

View File

@@ -2,6 +2,6 @@
<shape <shape
xmlns:android="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval"> android:shape="oval">
<solid android:color="@color/colorUnselected" /> <solid android:color="@color/colorBg" />
<size android:width="44dp" android:height="44dp" /> <size android:width="44dp" android:height="44dp" />
</shape> </shape>

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