Compare commits
60 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fd9c5040bf | ||
|
|
aa328f0add | ||
|
|
9743d7b87b | ||
|
|
98fb0c433e | ||
|
|
7c9fcd9f43 | ||
|
|
54c76d9968 | ||
|
|
40b3f0fedc | ||
|
|
dcfcf83430 | ||
|
|
e46b354643 | ||
|
|
f497e4e301 | ||
|
|
b65e4b3819 | ||
|
|
d166b036fc | ||
|
|
ddf5f22037 | ||
|
|
7d8a9f2b6d | ||
|
|
0a1695e3d7 | ||
|
|
4a653d4935 | ||
|
|
2bc31a10c5 | ||
|
|
e8d2c6214b | ||
|
|
3a0f2687e9 | ||
|
|
04c98326b2 | ||
|
|
eb22c7f303 | ||
|
|
d51a4d7a7e | ||
|
|
0fb705e1e2 | ||
|
|
10b849ef09 | ||
|
|
d7d3b23cea | ||
|
|
c3786d434e | ||
|
|
9e3b92014a | ||
|
|
f4e088131b | ||
|
|
e55e069fe3 | ||
|
|
d8d3767798 | ||
|
|
7e99b1ac78 | ||
|
|
6ff3a73bf2 | ||
|
|
2a43b52344 | ||
|
|
abff80ec23 | ||
|
|
a4edf86195 | ||
|
|
0d0da6bfec | ||
|
|
e0c8ece9b5 | ||
|
|
4d875bc3d4 | ||
|
|
3a6e23bcef | ||
|
|
efd0716707 | ||
|
|
c94a5fb743 | ||
|
|
047011f60b | ||
|
|
a54ed3a51a | ||
|
|
c37f09bfcd | ||
|
|
1c7042463d | ||
|
|
dcb003f9ab | ||
|
|
7dbda3cee7 | ||
|
|
26bee229a1 | ||
|
|
5bf2beb179 | ||
|
|
4a5c551678 | ||
|
|
277894215d | ||
|
|
684e08a3a1 | ||
|
|
19dbc2f9b9 | ||
|
|
833a1e06f0 | ||
|
|
daca0831a4 | ||
|
|
337889c5f1 | ||
|
|
244d2d3866 | ||
|
|
c0fed0ba4f | ||
|
|
affb107b9d | ||
|
|
f96073af99 |
113
.github/workflows/build.yml
vendored
113
.github/workflows/build.yml
vendored
@@ -17,40 +17,42 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
- name: Prepare build dir
|
submodules: 'recursive'
|
||||||
run: |
|
fetch-depth: '0'
|
||||||
mkdir ${{ github.workspace }}/build
|
|
||||||
|
|
||||||
- name: Fetch AndroidLibV2rayLite
|
|
||||||
run: |
|
|
||||||
cd ${{ github.workspace }}/build
|
|
||||||
git clone --depth=1 -b master https://github.com/2dust/AndroidLibV2rayLite.git
|
|
||||||
cd AndroidLibV2rayLite
|
|
||||||
git submodule update --init
|
|
||||||
|
|
||||||
- name: Restore cached libtun2socks
|
- name: Restore cached libtun2socks
|
||||||
id: cache-libtun2socks-restore
|
id: cache-libtun2socks-restore
|
||||||
uses: actions/cache/restore@v4
|
uses: actions/cache/restore@v4
|
||||||
with:
|
with:
|
||||||
path: ${{ github.workspace }}/build/AndroidLibV2rayLite/libs
|
path: ${{ github.workspace }}/libs
|
||||||
key: libtun2socks-${{ runner.os }}-${{ hashFiles('build/AndroidLibV2rayLite/.git/refs/heads/master') }}-${{ hashFiles('build/AndroidLibV2rayLite/.git/modules/badvpn/HEAD') }}-${{ hashFiles('build/AndroidLibV2rayLite/.git/modules/libancillary/HEAD') }}
|
key: libtun2socks-${{ runner.os }}-${{ hashFiles('.git/modules/badvpn/HEAD') }}-${{ hashFiles('.git/modules/libancillary/HEAD') }}
|
||||||
|
|
||||||
- name: Setup Android NDK
|
- name: Setup Android NDK
|
||||||
if: steps.cache-libtun2socks-restore.outputs.cache-hit != 'true'
|
|
||||||
uses: nttld/setup-ndk@v1
|
uses: nttld/setup-ndk@v1
|
||||||
id: setup-ndk
|
id: setup-ndk
|
||||||
# Same version as https://gitlab.com/fdroid/fdroiddata/metadata/com.v2ray.ang.yml
|
# Same version as https://gitlab.com/fdroid/fdroiddata/metadata/com.v2ray.ang.yml
|
||||||
with:
|
with:
|
||||||
ndk-version: r27
|
ndk-version: r27
|
||||||
|
add-to-path: true
|
||||||
|
link-to-sdk: true
|
||||||
|
local-cache: true
|
||||||
|
|
||||||
|
- name: Restore Android Symlinks
|
||||||
|
run: |
|
||||||
|
directory="${{ steps.setup-ndk.outputs.ndk-path }}/toolchains/llvm/prebuilt/linux-x86_64/bin"
|
||||||
|
find "$directory" -type l | while read link; do
|
||||||
|
current_target=$(readlink "$link")
|
||||||
|
new_target="$directory/$(basename "$current_target")"
|
||||||
|
ln -sf "$new_target" "$link"
|
||||||
|
echo "Changed $(basename "$link") from $current_target to $new_target"
|
||||||
|
done
|
||||||
|
|
||||||
- name: Build libtun2socks
|
- name: Build libtun2socks
|
||||||
if: steps.cache-libtun2socks-restore.outputs.cache-hit != 'true'
|
if: steps.cache-libtun2socks-restore.outputs.cache-hit != 'true'
|
||||||
run: |
|
run: |
|
||||||
cd ${{ github.workspace }}/build/AndroidLibV2rayLite
|
|
||||||
bash compile-tun2socks.sh
|
bash compile-tun2socks.sh
|
||||||
tar -xvzf libtun2socks.so.tgz
|
tar -xvzf libtun2socks.so.tgz
|
||||||
cp -r libs/* ${{ github.workspace }}/V2rayNG/app/libs/
|
|
||||||
env:
|
env:
|
||||||
NDK_HOME: ${{ steps.setup-ndk.outputs.ndk-path }}
|
NDK_HOME: ${{ steps.setup-ndk.outputs.ndk-path }}
|
||||||
|
|
||||||
@@ -58,63 +60,59 @@ jobs:
|
|||||||
if: steps.cache-libtun2socks-restore.outputs.cache-hit != 'true'
|
if: steps.cache-libtun2socks-restore.outputs.cache-hit != 'true'
|
||||||
uses: actions/cache/save@v4
|
uses: actions/cache/save@v4
|
||||||
with:
|
with:
|
||||||
path: ${{ github.workspace }}/build/AndroidLibV2rayLite/libs
|
path: ${{ github.workspace }}/libs
|
||||||
key: libtun2socks-${{ runner.os }}-${{ hashFiles('build/AndroidLibV2rayLite/.git/refs/heads/master') }}-${{ hashFiles('build/AndroidLibV2rayLite/.git/modules/badvpn/HEAD') }}-${{ hashFiles('build/AndroidLibV2rayLite/.git/modules/libancillary/HEAD') }}
|
key: libtun2socks-${{ runner.os }}-${{ hashFiles('.git/modules/badvpn/HEAD') }}-${{ hashFiles('.git/modules/libancillary/HEAD') }}
|
||||||
|
|
||||||
- name: Copy libtun2socks
|
- name: Copy libtun2socks
|
||||||
run: |
|
run: |
|
||||||
cp -r ${{ github.workspace }}/build/AndroidLibV2rayLite/libs/* ${{ github.workspace }}/V2rayNG/app/libs/
|
cp -r ${{ github.workspace }}/libs ${{ github.workspace }}/V2rayNG/app
|
||||||
|
|
||||||
- name: Fetch AndroidLibXrayLite
|
- name: Fetch AndroidLibXrayLite tag
|
||||||
run: |
|
run: |
|
||||||
cd ${{ github.workspace }}/build
|
pushd AndroidLibXrayLite
|
||||||
git clone --depth=1 -b main https://github.com/2dust/AndroidLibXrayLite.git
|
CURRENT_TAG=$(git describe --tags --abbrev=0)
|
||||||
|
echo "Current tag in this repo: $CURRENT_TAG"
|
||||||
|
echo "CURRENT_TAG=$CURRENT_TAG" >> $GITHUB_ENV
|
||||||
|
popd
|
||||||
|
|
||||||
- name: Restore cached libv2ray
|
- name: Download libv2ray
|
||||||
id: cache-libv2ray-restore
|
uses: robinraju/release-downloader@v1
|
||||||
|
with:
|
||||||
|
repository: '2dust/AndroidLibXrayLite'
|
||||||
|
tag: ${{ env.CURRENT_TAG }}
|
||||||
|
fileName: 'libv2ray.aar'
|
||||||
|
out-file-path: V2rayNG/app/libs/
|
||||||
|
|
||||||
|
- name: Restore cached libhysteria2
|
||||||
|
id: cache-libhysteria2-restore
|
||||||
uses: actions/cache/restore@v4
|
uses: actions/cache/restore@v4
|
||||||
with:
|
with:
|
||||||
path: ${{ github.workspace }}/build/AndroidLibXrayLite/
|
path: ${{ github.workspace }}/hysteria/libs
|
||||||
key: libv2ray-${{ runner.os }}-${{ hashFiles('build/AndroidLibXrayLite/.git/refs/heads/main') }}
|
key: libhysteria2-${{ runner.os }}-${{ hashFiles('.git/modules/hysteria/HEAD') }}-${{ hashFiles('libhysteria2.sh') }}
|
||||||
|
|
||||||
- name: Setup Golang
|
- name: Setup Golang
|
||||||
if: steps.cache-libv2ray-restore.outputs.cache-hit != 'true'
|
if: steps.cache-libhysteria2-restore.outputs.cache-hit != 'true'
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: '1.23.4'
|
go-version-file: 'AndroidLibXrayLite/go.mod'
|
||||||
cache: false
|
|
||||||
|
|
||||||
- name: Patch Go use 600296
|
- name: Build libhysteria2
|
||||||
if: steps.cache-libv2ray-restore.outputs.cache-hit != 'true'
|
if: steps.cache-libhysteria2-restore.outputs.cache-hit != 'true'
|
||||||
#https://go-review.googlesource.com/c/go/+/600296
|
|
||||||
run: |
|
run: |
|
||||||
cd "$(go env GOROOT)"
|
bash libhysteria2.sh
|
||||||
curl "https://go-review.googlesource.com/changes/go~600296/revisions/5/patch" | base64 -d | patch --verbose -p 1
|
env:
|
||||||
|
ANDROID_NDK_HOME: ${{ steps.setup-ndk.outputs.ndk-path }}
|
||||||
|
|
||||||
- name: Install gomobile
|
- name: Save libhysteria2
|
||||||
if: steps.cache-libv2ray-restore.outputs.cache-hit != 'true'
|
if: steps.cache-libhysteria2-restore.outputs.cache-hit != 'true'
|
||||||
run: |
|
|
||||||
go install golang.org/x/mobile/cmd/gomobile@v0.0.0-20240806205939-81131f6468ab
|
|
||||||
echo "$(go env GOPATH)/bin" >> $GITHUB_PATH
|
|
||||||
|
|
||||||
- name: Build libv2ray
|
|
||||||
if: steps.cache-libv2ray-restore.outputs.cache-hit != 'true'
|
|
||||||
run: |
|
|
||||||
cd ${{ github.workspace }}/build/AndroidLibXrayLite
|
|
||||||
gomobile init
|
|
||||||
go mod tidy -v
|
|
||||||
gomobile bind -v -androidapi 21 -ldflags='-s -w' ./
|
|
||||||
|
|
||||||
- name: Save libv2ray
|
|
||||||
if: steps.cache-libv2ray-restore.outputs.cache-hit != 'true'
|
|
||||||
uses: actions/cache/save@v4
|
uses: actions/cache/save@v4
|
||||||
with:
|
with:
|
||||||
path: ${{ github.workspace }}/build/AndroidLibXrayLite/
|
path: ${{ github.workspace }}/hysteria/libs
|
||||||
key: libv2ray-${{ runner.os }}-${{ hashFiles('build/AndroidLibXrayLite/.git/refs/heads/main') }}
|
key: libhysteria2-${{ runner.os }}-${{ hashFiles('.git/modules/hysteria/HEAD') }}-${{ hashFiles('libhysteria2.sh') }}
|
||||||
|
|
||||||
- name: Copy libv2ray
|
- name: Copy libhysteria2
|
||||||
run: |
|
run: |
|
||||||
cp -r ${{ github.workspace }}/build/AndroidLibXrayLite/*.aar ${{ github.workspace }}/V2rayNG/app/libs/
|
cp -r ${{ github.workspace }}/hysteria/libs ${{ github.workspace }}/V2rayNG/app
|
||||||
|
|
||||||
- name: Setup Java
|
- name: Setup Java
|
||||||
uses: actions/setup-java@v4
|
uses: actions/setup-java@v4
|
||||||
@@ -138,6 +136,8 @@ jobs:
|
|||||||
chmod 755 gradlew
|
chmod 755 gradlew
|
||||||
./gradlew licenseFdroidReleaseReport
|
./gradlew licenseFdroidReleaseReport
|
||||||
./gradlew assembleRelease -Pandroid.injected.signing.store.file=${{ steps.android_keystore.outputs.filePath }} -Pandroid.injected.signing.store.password=${{ secrets.APP_KEYSTORE_PASSWORD }} -Pandroid.injected.signing.key.alias=${{ secrets.APP_KEYSTORE_ALIAS }} -Pandroid.injected.signing.key.password=${{ secrets.APP_KEY_PASSWORD }}
|
./gradlew assembleRelease -Pandroid.injected.signing.store.file=${{ steps.android_keystore.outputs.filePath }} -Pandroid.injected.signing.store.password=${{ secrets.APP_KEYSTORE_PASSWORD }} -Pandroid.injected.signing.key.alias=${{ secrets.APP_KEYSTORE_ALIAS }} -Pandroid.injected.signing.key.password=${{ secrets.APP_KEY_PASSWORD }}
|
||||||
|
env:
|
||||||
|
ANDROID_NDK_HOME: ${{ steps.setup-ndk.outputs.ndk-path }}
|
||||||
|
|
||||||
- name: Upload arm64-v8a APK
|
- name: Upload arm64-v8a APK
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
@@ -160,10 +160,11 @@ jobs:
|
|||||||
name: x86-apk
|
name: x86-apk
|
||||||
path: ${{ github.workspace }}/V2rayNG/app/build/outputs/apk/*/release/*x86*.apk
|
path: ${{ github.workspace }}/V2rayNG/app/build/outputs/apk/*/release/*x86*.apk
|
||||||
|
|
||||||
- name: Upload AndroidLibXrayLite to release
|
- name: Upload to release
|
||||||
uses: svenstaro/upload-release-action@v2
|
uses: svenstaro/upload-release-action@v2
|
||||||
if: github.event.inputs.release_tag != ''
|
if: github.event.inputs.release_tag != ''
|
||||||
with:
|
with:
|
||||||
file: ${{ github.workspace }}/V2rayNG/app/build/outputs/apk/*playstore*/release/*.apk
|
file: ${{ github.workspace }}/V2rayNG/app/build/outputs/apk/*playstore*/release/*.apk
|
||||||
tag: ${{ github.event.inputs.release_tag }}
|
tag: ${{ github.event.inputs.release_tag }}
|
||||||
file_glob: true
|
file_glob: true
|
||||||
|
prerelease: true
|
||||||
|
|||||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -3,5 +3,4 @@
|
|||||||
V2rayNG/app/release/output.json
|
V2rayNG/app/release/output.json
|
||||||
.idea/
|
.idea/
|
||||||
.gradle/
|
.gradle/
|
||||||
libtun2socks.so
|
*.so
|
||||||
libhysteria2.so
|
|
||||||
|
|||||||
12
.gitmodules
vendored
Normal file
12
.gitmodules
vendored
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
[submodule "hysteria"]
|
||||||
|
path = hysteria
|
||||||
|
url = https://github.com/apernet/hysteria
|
||||||
|
[submodule "AndroidLibXrayLite"]
|
||||||
|
path = AndroidLibXrayLite
|
||||||
|
url = https://github.com/2dust/AndroidLibXrayLite
|
||||||
|
[submodule "badvpn"]
|
||||||
|
path = badvpn
|
||||||
|
url = https://github.com/XTLS/badvpn
|
||||||
|
[submodule "libancillary"]
|
||||||
|
path = libancillary
|
||||||
|
url = https://github.com/shadowsocks/libancillary
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
# AndroidLibV2rayLite
|
|
||||||
|
|
||||||
### Preparation
|
|
||||||
- latest Ubuntu environment
|
|
||||||
- At lease 30G free space
|
|
||||||
- Get Repo [AndroidLibV2rayLite](https://github.com/2dust/AndroidLibV2rayLite) or [AndroidLibXrayLite](https://github.com/2dust/AndroidLibXrayLite)
|
|
||||||
### Prepare Go
|
|
||||||
- Go to https://golang.org/doc/install and install latest go
|
|
||||||
- Make sure `go version` works as expected
|
|
||||||
### Prepare gomobile
|
|
||||||
- Go to https://pkg.go.dev/golang.org/x/mobile/cmd/gomobile and install gomobile
|
|
||||||
- export PATH=$PATH:~/go/bin
|
|
||||||
- Make sure `gomobile init` works as expected
|
|
||||||
### Prepare NDK
|
|
||||||
- Go to https://developer.android.com/ndk/downloads and install latest NDK
|
|
||||||
- export PATH=$PATH:<wherever you ndk is located>
|
|
||||||
- Make sure `ndk-build -v` works as expected
|
|
||||||
### Make
|
|
||||||
- sudo apt install make
|
|
||||||
- Read and understand [build script](https://github.com/2dust/AndroidLibV2rayLite/blob/master/Makefile)
|
|
||||||
1
AndroidLibXrayLite
Submodule
1
AndroidLibXrayLite
Submodule
Submodule AndroidLibXrayLite added at 36f046e27b
@@ -3,7 +3,7 @@
|
|||||||
A V2Ray client for Android, support [Xray core](https://github.com/XTLS/Xray-core) and [v2fly core](https://github.com/v2fly/v2ray-core)
|
A V2Ray client for Android, support [Xray core](https://github.com/XTLS/Xray-core) and [v2fly core](https://github.com/v2fly/v2ray-core)
|
||||||
|
|
||||||
[](https://developer.android.com/about/versions/lollipop)
|
[](https://developer.android.com/about/versions/lollipop)
|
||||||
[](https://kotlinlang.org)
|
[](https://kotlinlang.org)
|
||||||
[](https://github.com/2dust/v2rayNG/commits/master)
|
[](https://github.com/2dust/v2rayNG/commits/master)
|
||||||
[](https://www.codefactor.io/repository/github/2dust/v2rayng)
|
[](https://www.codefactor.io/repository/github/2dust/v2rayng)
|
||||||
[](https://github.com/2dust/v2rayNG/releases)
|
[](https://github.com/2dust/v2rayNG/releases)
|
||||||
|
|||||||
@@ -12,8 +12,8 @@ android {
|
|||||||
applicationId = "com.v2ray.ang"
|
applicationId = "com.v2ray.ang"
|
||||||
minSdk = 21
|
minSdk = 21
|
||||||
targetSdk = 35
|
targetSdk = 35
|
||||||
versionCode = 623
|
versionCode = 635
|
||||||
versionName = "1.9.27"
|
versionName = "1.9.38"
|
||||||
multiDexEnabled = true
|
multiDexEnabled = true
|
||||||
|
|
||||||
val abiFilterList = (properties["ABI_FILTERS"] as? String)?.split(';')
|
val abiFilterList = (properties["ABI_FILTERS"] as? String)?.split(';')
|
||||||
@@ -144,6 +144,7 @@ dependencies {
|
|||||||
implementation(libs.androidx.constraintlayout)
|
implementation(libs.androidx.constraintlayout)
|
||||||
implementation(libs.preference.ktx)
|
implementation(libs.preference.ktx)
|
||||||
implementation(libs.recyclerview)
|
implementation(libs.recyclerview)
|
||||||
|
implementation(libs.androidx.swiperefreshlayout)
|
||||||
|
|
||||||
// UI Libraries
|
// UI Libraries
|
||||||
implementation(libs.material)
|
implementation(libs.material)
|
||||||
@@ -156,9 +157,8 @@ dependencies {
|
|||||||
implementation(libs.gson)
|
implementation(libs.gson)
|
||||||
|
|
||||||
// Reactive and Utility Libraries
|
// Reactive and Utility Libraries
|
||||||
implementation(libs.rxjava)
|
implementation(libs.kotlinx.coroutines.android)
|
||||||
implementation(libs.rxandroid)
|
implementation(libs.kotlinx.coroutines.core)
|
||||||
implementation(libs.rxpermissions)
|
|
||||||
|
|
||||||
// Language and Processing Libraries
|
// Language and Processing Libraries
|
||||||
implementation(libs.language.base)
|
implementation(libs.language.base)
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -99,10 +99,5 @@
|
|||||||
"domain": [
|
"domain": [
|
||||||
"geosite:cn"
|
"geosite:cn"
|
||||||
]
|
]
|
||||||
},
|
|
||||||
{
|
|
||||||
"remarks": "最终代理",
|
|
||||||
"port": "0-65535",
|
|
||||||
"outboundTag": "proxy"
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -40,10 +40,5 @@
|
|||||||
"ip": [
|
"ip": [
|
||||||
"geoip:ir"
|
"geoip:ir"
|
||||||
]
|
]
|
||||||
},
|
|
||||||
{
|
|
||||||
"remarks": "Final Agent",
|
|
||||||
"port": "0-65535",
|
|
||||||
"outboundTag": "proxy"
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ object AppConfig {
|
|||||||
const val PREF_APPEND_HTTP_PROXY = "pref_append_http_proxy"
|
const val PREF_APPEND_HTTP_PROXY = "pref_append_http_proxy"
|
||||||
const val PREF_LOCAL_DNS_PORT = "pref_local_dns_port"
|
const val PREF_LOCAL_DNS_PORT = "pref_local_dns_port"
|
||||||
const val PREF_VPN_DNS = "pref_vpn_dns"
|
const val PREF_VPN_DNS = "pref_vpn_dns"
|
||||||
|
const val PREF_VPN_BYPASS_LAN = "pref_vpn_bypass_lan"
|
||||||
const val PREF_ROUTING_DOMAIN_STRATEGY = "pref_routing_domain_strategy"
|
const val PREF_ROUTING_DOMAIN_STRATEGY = "pref_routing_domain_strategy"
|
||||||
const val PREF_ROUTING_RULESET = "pref_routing_ruleset"
|
const val PREF_ROUTING_RULESET = "pref_routing_ruleset"
|
||||||
const val PREF_MUX_ENABLED = "pref_mux_enabled"
|
const val PREF_MUX_ENABLED = "pref_mux_enabled"
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ data class Hysteria2Bean(
|
|||||||
val http: Socks5Bean? = null,
|
val http: Socks5Bean? = null,
|
||||||
val tls: TlsBean? = null,
|
val tls: TlsBean? = null,
|
||||||
val transport: TransportBean? = null,
|
val transport: TransportBean? = null,
|
||||||
|
val bandwidth: BandwidthBean? = null,
|
||||||
) {
|
) {
|
||||||
data class ObfsBean(
|
data class ObfsBean(
|
||||||
val type: String?,
|
val type: String?,
|
||||||
@@ -37,4 +38,9 @@ data class Hysteria2Bean(
|
|||||||
val hopInterval: String?,
|
val hopInterval: String?,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
data class BandwidthBean(
|
||||||
|
val down: String?,
|
||||||
|
val up: String?,
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
package com.v2ray.ang.dto
|
package com.v2ray.ang.dto
|
||||||
|
|
||||||
|
import com.v2ray.ang.AppConfig.LOOPBACK
|
||||||
|
import com.v2ray.ang.AppConfig.PORT_SOCKS
|
||||||
import com.v2ray.ang.AppConfig.TAG_BLOCKED
|
import com.v2ray.ang.AppConfig.TAG_BLOCKED
|
||||||
import com.v2ray.ang.AppConfig.TAG_DIRECT
|
import com.v2ray.ang.AppConfig.TAG_DIRECT
|
||||||
import com.v2ray.ang.AppConfig.TAG_PROXY
|
import com.v2ray.ang.AppConfig.TAG_PROXY
|
||||||
@@ -53,6 +55,8 @@ data class ProfileItem(
|
|||||||
var portHopping: String? = null,
|
var portHopping: String? = null,
|
||||||
var portHoppingInterval: String? = null,
|
var portHoppingInterval: String? = null,
|
||||||
var pinSHA256: String? = null,
|
var pinSHA256: String? = null,
|
||||||
|
var bandwidthDown: String? = null,
|
||||||
|
var bandwidthUp: String? = null,
|
||||||
|
|
||||||
) {
|
) {
|
||||||
companion object {
|
companion object {
|
||||||
@@ -66,6 +70,9 @@ data class ProfileItem(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun getServerAddressAndPort(): String {
|
fun getServerAddressAndPort(): String {
|
||||||
|
if (server.isNullOrEmpty() && configType == EConfigType.CUSTOM) {
|
||||||
|
return "$LOOPBACK:$PORT_SOCKS"
|
||||||
|
}
|
||||||
return Utils.getIpv6Address(server) + ":" + serverPort
|
return Utils.getIpv6Address(server) + ":" + serverPort
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -476,9 +476,9 @@ data class V2rayConfig(
|
|||||||
|
|
||||||
data class MuxBean(
|
data class MuxBean(
|
||||||
var enabled: Boolean,
|
var enabled: Boolean,
|
||||||
var concurrency: Int = 8,
|
var concurrency: Int? = null,
|
||||||
var xudpConcurrency: Int = 8,
|
var xudpConcurrency: Int? = null,
|
||||||
var xudpProxyUDP443: String = "",
|
var xudpProxyUDP443: String? = null,
|
||||||
)
|
)
|
||||||
|
|
||||||
fun getServerAddress(): String? {
|
fun getServerAddress(): String? {
|
||||||
|
|||||||
@@ -85,6 +85,12 @@ object Hysteria2Fmt : FmtBase() {
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
val bandwidth = if (config.bandwidthDown.isNullOrEmpty() || config.bandwidthUp.isNullOrEmpty()) null else
|
||||||
|
Hysteria2Bean.BandwidthBean(
|
||||||
|
down = config.bandwidthDown,
|
||||||
|
up = config.bandwidthUp,
|
||||||
|
)
|
||||||
|
|
||||||
val server =
|
val server =
|
||||||
if (config.portHopping.isNullOrEmpty())
|
if (config.portHopping.isNullOrEmpty())
|
||||||
config.getServerAddressAndPort()
|
config.getServerAddressAndPort()
|
||||||
@@ -96,6 +102,7 @@ object Hysteria2Fmt : FmtBase() {
|
|||||||
auth = config.password,
|
auth = config.password,
|
||||||
obfs = obfs,
|
obfs = obfs,
|
||||||
transport = transport,
|
transport = transport,
|
||||||
|
bandwidth = bandwidth,
|
||||||
socks5 = Hysteria2Bean.Socks5Bean(
|
socks5 = Hysteria2Bean.Socks5Bean(
|
||||||
listen = "$LOOPBACK:${socksPort}",
|
listen = "$LOOPBACK:${socksPort}",
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -349,10 +349,6 @@ object MmkvManager {
|
|||||||
return settingsStorage.decodeBool(key, defaultValue)
|
return settingsStorage.decodeBool(key, defaultValue)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun decodeSettingsInt(key: String, defaultValue: Int): Int {
|
|
||||||
return settingsStorage.decodeInt(key, defaultValue)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun decodeSettingsStringSet(key: String): MutableSet<String>? {
|
fun decodeSettingsStringSet(key: String): MutableSet<String>? {
|
||||||
return settingsStorage.decodeStringSet(key)
|
return settingsStorage.decodeStringSet(key)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,9 +9,11 @@ import com.v2ray.ang.AppConfig.ANG_PACKAGE
|
|||||||
import com.v2ray.ang.AppConfig.GEOIP_PRIVATE
|
import com.v2ray.ang.AppConfig.GEOIP_PRIVATE
|
||||||
import com.v2ray.ang.AppConfig.GEOSITE_PRIVATE
|
import com.v2ray.ang.AppConfig.GEOSITE_PRIVATE
|
||||||
import com.v2ray.ang.AppConfig.TAG_DIRECT
|
import com.v2ray.ang.AppConfig.TAG_DIRECT
|
||||||
|
import com.v2ray.ang.dto.EConfigType
|
||||||
import com.v2ray.ang.dto.ProfileItem
|
import com.v2ray.ang.dto.ProfileItem
|
||||||
import com.v2ray.ang.dto.RoutingType
|
import com.v2ray.ang.dto.RoutingType
|
||||||
import com.v2ray.ang.dto.RulesetItem
|
import com.v2ray.ang.dto.RulesetItem
|
||||||
|
import com.v2ray.ang.dto.V2rayConfig
|
||||||
import com.v2ray.ang.handler.MmkvManager.decodeServerConfig
|
import com.v2ray.ang.handler.MmkvManager.decodeServerConfig
|
||||||
import com.v2ray.ang.handler.MmkvManager.decodeServerList
|
import com.v2ray.ang.handler.MmkvManager.decodeServerList
|
||||||
import com.v2ray.ang.util.JsonUtil
|
import com.v2ray.ang.util.JsonUtil
|
||||||
@@ -91,8 +93,10 @@ object SettingsManager {
|
|||||||
fun saveRoutingRuleset(index: Int, ruleset: RulesetItem?) {
|
fun saveRoutingRuleset(index: Int, ruleset: RulesetItem?) {
|
||||||
if (ruleset == null) return
|
if (ruleset == null) return
|
||||||
|
|
||||||
val rulesetList = MmkvManager.decodeRoutingRulesets()
|
var rulesetList = MmkvManager.decodeRoutingRulesets()
|
||||||
if (rulesetList.isNullOrEmpty()) return
|
if (rulesetList.isNullOrEmpty()) {
|
||||||
|
rulesetList = mutableListOf()
|
||||||
|
}
|
||||||
|
|
||||||
if (index < 0 || index >= rulesetList.count()) {
|
if (index < 0 || index >= rulesetList.count()) {
|
||||||
rulesetList.add(0, ruleset)
|
rulesetList.add(0, ruleset)
|
||||||
@@ -113,6 +117,25 @@ object SettingsManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun routingRulesetsBypassLan(): Boolean {
|
fun routingRulesetsBypassLan(): Boolean {
|
||||||
|
val vpnBypassLan = MmkvManager.decodeSettingsString(AppConfig.PREF_VPN_BYPASS_LAN) ?: "0"
|
||||||
|
if (vpnBypassLan == "1") {
|
||||||
|
return true
|
||||||
|
} else if (vpnBypassLan == "2") {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
//Follow config
|
||||||
|
val guid = MmkvManager.getSelectServer() ?: return false
|
||||||
|
val config = MmkvManager.decodeServerConfig(guid) ?: return false
|
||||||
|
if (config.configType == EConfigType.CUSTOM) {
|
||||||
|
val raw = MmkvManager.decodeServerRaw(guid) ?: return false
|
||||||
|
val v2rayConfig = JsonUtil.fromJson(raw, V2rayConfig::class.java)
|
||||||
|
val exist = v2rayConfig.routing.rules.filter { it.outboundTag == TAG_DIRECT }?.any {
|
||||||
|
it.domain?.contains(GEOSITE_PRIVATE) == true || it.ip?.contains(GEOIP_PRIVATE) == true
|
||||||
|
}
|
||||||
|
return exist == true
|
||||||
|
}
|
||||||
|
|
||||||
val rulesetItems = MmkvManager.decodeRoutingRulesets()
|
val rulesetItems = MmkvManager.decodeRoutingRulesets()
|
||||||
val exist = rulesetItems?.filter { it.enabled && it.outboundTag == TAG_DIRECT }?.any {
|
val exist = rulesetItems?.filter { it.enabled && it.outboundTag == TAG_DIRECT }?.any {
|
||||||
it.domain?.contains(GEOSITE_PRIVATE) == true || it.ip?.contains(GEOIP_PRIVATE) == true
|
it.domain?.contains(GEOSITE_PRIVATE) == true || it.ip?.contains(GEOIP_PRIVATE) == true
|
||||||
|
|||||||
@@ -438,21 +438,18 @@ object V2rayConfigManager {
|
|||||||
|| protocol.equals(EConfigType.HYSTERIA2.name, true)
|
|| protocol.equals(EConfigType.HYSTERIA2.name, true)
|
||||||
) {
|
) {
|
||||||
muxEnabled = false
|
muxEnabled = false
|
||||||
} else if (protocol.equals(EConfigType.VLESS.name, true)
|
|
||||||
&& outbound.settings?.vnext?.first()?.users?.first()?.flow?.isNotEmpty() == true
|
|
||||||
) {
|
|
||||||
muxEnabled = false
|
|
||||||
} else if (outbound.streamSettings?.network == NetworkType.XHTTP.type) {
|
} else if (outbound.streamSettings?.network == NetworkType.XHTTP.type) {
|
||||||
muxEnabled = false
|
muxEnabled = false
|
||||||
}
|
}
|
||||||
|
|
||||||
if (muxEnabled == true) {
|
if (muxEnabled == true) {
|
||||||
outbound.mux?.enabled = true
|
outbound.mux?.enabled = true
|
||||||
outbound.mux?.concurrency =
|
outbound.mux?.concurrency = MmkvManager.decodeSettingsString(AppConfig.PREF_MUX_CONCURRENCY, "8").orEmpty().toInt()
|
||||||
MmkvManager.decodeSettingsInt(AppConfig.PREF_MUX_CONCURRENCY, 8)
|
outbound.mux?.xudpConcurrency = MmkvManager.decodeSettingsString(AppConfig.PREF_MUX_XUDP_CONCURRENCY, "16").orEmpty().toInt()
|
||||||
outbound.mux?.xudpConcurrency =
|
outbound.mux?.xudpProxyUDP443 = MmkvManager.decodeSettingsString(AppConfig.PREF_MUX_XUDP_QUIC,"reject")
|
||||||
MmkvManager.decodeSettingsInt(AppConfig.PREF_MUX_XUDP_CONCURRENCY, 16)
|
if (protocol.equals(EConfigType.VLESS.name, true) && outbound.settings?.vnext?.first()?.users?.first()?.flow?.isNotEmpty() == true) {
|
||||||
outbound.mux?.xudpProxyUDP443 =
|
outbound.mux?.concurrency = -1
|
||||||
MmkvManager.decodeSettingsString(AppConfig.PREF_MUX_XUDP_QUIC) ?: "reject"
|
}
|
||||||
} else {
|
} else {
|
||||||
outbound.mux?.enabled = false
|
outbound.mux?.enabled = false
|
||||||
outbound.mux?.concurrency = -1
|
outbound.mux?.concurrency = -1
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import com.v2ray.ang.AppConfig.ANG_PACKAGE
|
|||||||
import com.v2ray.ang.AppConfig.TAG_DIRECT
|
import com.v2ray.ang.AppConfig.TAG_DIRECT
|
||||||
import com.v2ray.ang.AppConfig.VPN
|
import com.v2ray.ang.AppConfig.VPN
|
||||||
import com.v2ray.ang.R
|
import com.v2ray.ang.R
|
||||||
|
import com.v2ray.ang.dto.EConfigType
|
||||||
import com.v2ray.ang.dto.ProfileItem
|
import com.v2ray.ang.dto.ProfileItem
|
||||||
import com.v2ray.ang.extension.toSpeedString
|
import com.v2ray.ang.extension.toSpeedString
|
||||||
import com.v2ray.ang.extension.toast
|
import com.v2ray.ang.extension.toast
|
||||||
@@ -30,10 +31,11 @@ import com.v2ray.ang.util.MessageUtil
|
|||||||
import com.v2ray.ang.util.PluginUtil
|
import com.v2ray.ang.util.PluginUtil
|
||||||
import com.v2ray.ang.util.Utils
|
import com.v2ray.ang.util.Utils
|
||||||
import go.Seq
|
import go.Seq
|
||||||
import io.reactivex.rxjava3.core.Observable
|
|
||||||
import io.reactivex.rxjava3.disposables.Disposable
|
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.Job
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
|
import kotlinx.coroutines.isActive
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import libv2ray.Libv2ray
|
import libv2ray.Libv2ray
|
||||||
import libv2ray.V2RayPoint
|
import libv2ray.V2RayPoint
|
||||||
@@ -61,14 +63,17 @@ object V2RayServiceManager {
|
|||||||
|
|
||||||
private var lastQueryTime = 0L
|
private var lastQueryTime = 0L
|
||||||
private var mBuilder: NotificationCompat.Builder? = null
|
private var mBuilder: NotificationCompat.Builder? = null
|
||||||
private var mDisposable: Disposable? = null
|
private var speedNotificationJob: Job? = null
|
||||||
private var mNotificationManager: NotificationManager? = null
|
private var mNotificationManager: NotificationManager? = null
|
||||||
|
|
||||||
fun startV2Ray(context: Context) {
|
fun startV2Ray(context: Context) {
|
||||||
if (v2rayPoint.isRunning) return
|
if (v2rayPoint.isRunning) return
|
||||||
val guid = MmkvManager.getSelectServer() ?: return
|
val guid = MmkvManager.getSelectServer() ?: return
|
||||||
val config = MmkvManager.decodeServerConfig(guid) ?: return
|
val config = MmkvManager.decodeServerConfig(guid) ?: return
|
||||||
if (!Utils.isValidUrl(config.server) && !Utils.isIpAddress(config.server)) return
|
if (config.configType != EConfigType.CUSTOM
|
||||||
|
&& !Utils.isValidUrl(config.server)
|
||||||
|
&& !Utils.isIpAddress(config.server)
|
||||||
|
) return
|
||||||
// val result = V2rayConfigUtil.getV2rayConfig(context, guid)
|
// val result = V2rayConfigUtil.getV2rayConfig(context, guid)
|
||||||
// if (!result.status) return
|
// if (!result.status) return
|
||||||
|
|
||||||
@@ -361,8 +366,8 @@ object V2RayServiceManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
mBuilder = null
|
mBuilder = null
|
||||||
mDisposable?.dispose()
|
speedNotificationJob?.cancel()
|
||||||
mDisposable = null
|
speedNotificationJob = null
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun updateNotification(contentText: String?, proxyTraffic: Long, directTraffic: Long) {
|
private fun updateNotification(contentText: String?, proxyTraffic: Long, directTraffic: Long) {
|
||||||
@@ -389,7 +394,7 @@ object V2RayServiceManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun startSpeedNotification() {
|
private fun startSpeedNotification() {
|
||||||
if (mDisposable == null &&
|
if (speedNotificationJob == null &&
|
||||||
v2rayPoint.isRunning &&
|
v2rayPoint.isRunning &&
|
||||||
MmkvManager.decodeSettingsBool(AppConfig.PREF_SPEED_ENABLED) == true
|
MmkvManager.decodeSettingsBool(AppConfig.PREF_SPEED_ENABLED) == true
|
||||||
) {
|
) {
|
||||||
@@ -397,8 +402,8 @@ object V2RayServiceManager {
|
|||||||
val outboundTags = currentConfig?.getAllOutboundTags()
|
val outboundTags = currentConfig?.getAllOutboundTags()
|
||||||
outboundTags?.remove(TAG_DIRECT)
|
outboundTags?.remove(TAG_DIRECT)
|
||||||
|
|
||||||
mDisposable = Observable.interval(3, java.util.concurrent.TimeUnit.SECONDS)
|
speedNotificationJob = CoroutineScope(Dispatchers.IO).launch {
|
||||||
.subscribe {
|
while (isActive) {
|
||||||
val queryTime = System.currentTimeMillis()
|
val queryTime = System.currentTimeMillis()
|
||||||
val sinceLastQueryInSeconds = (queryTime - lastQueryTime) / 1000.0
|
val sinceLastQueryInSeconds = (queryTime - lastQueryTime) / 1000.0
|
||||||
var proxyTotal = 0L
|
var proxyTotal = 0L
|
||||||
@@ -426,7 +431,9 @@ object V2RayServiceManager {
|
|||||||
}
|
}
|
||||||
lastZeroSpeed = zeroSpeed
|
lastZeroSpeed = zeroSpeed
|
||||||
lastQueryTime = queryTime
|
lastQueryTime = queryTime
|
||||||
|
delay(3000)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -441,11 +448,10 @@ object V2RayServiceManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun stopSpeedNotification() {
|
private fun stopSpeedNotification() {
|
||||||
mDisposable?.let {
|
speedNotificationJob?.let {
|
||||||
it.dispose() //stop queryStats
|
it.cancel()
|
||||||
mDisposable = null
|
speedNotificationJob = null
|
||||||
updateNotification(currentConfig?.remarks, 0, 0)
|
updateNotification(currentConfig?.remarks, 0, 0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,15 +5,16 @@ import android.content.Intent
|
|||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
|
import android.widget.Toast
|
||||||
import androidx.activity.result.contract.ActivityResultContracts
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
|
import androidx.core.app.ActivityCompat
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.core.content.FileProvider
|
import androidx.core.content.FileProvider
|
||||||
import com.tbruyelle.rxpermissions3.RxPermissions
|
|
||||||
import com.tencent.mmkv.MMKV
|
import com.tencent.mmkv.MMKV
|
||||||
import com.v2ray.ang.AppConfig
|
import com.v2ray.ang.AppConfig
|
||||||
import com.v2ray.ang.BuildConfig
|
import com.v2ray.ang.BuildConfig
|
||||||
import com.v2ray.ang.R
|
import com.v2ray.ang.R
|
||||||
import com.v2ray.ang.databinding.ActivityAboutBinding
|
import com.v2ray.ang.databinding.ActivityAboutBinding
|
||||||
import com.v2ray.ang.extension.toast
|
|
||||||
import com.v2ray.ang.util.SpeedtestUtil
|
import com.v2ray.ang.util.SpeedtestUtil
|
||||||
import com.v2ray.ang.util.Utils
|
import com.v2ray.ang.util.Utils
|
||||||
import com.v2ray.ang.util.ZipUtil
|
import com.v2ray.ang.util.ZipUtil
|
||||||
@@ -21,11 +22,24 @@ import java.io.File
|
|||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
|
|
||||||
|
|
||||||
class AboutActivity : BaseActivity() {
|
class AboutActivity : BaseActivity() {
|
||||||
|
|
||||||
private val binding by lazy { ActivityAboutBinding.inflate(layoutInflater) }
|
private val binding by lazy { ActivityAboutBinding.inflate(layoutInflater) }
|
||||||
private val extDir by lazy { File(Utils.backupPath(this)) }
|
private val extDir by lazy { File(Utils.backupPath(this)) }
|
||||||
|
|
||||||
|
private val requestPermissionLauncher =
|
||||||
|
registerForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted ->
|
||||||
|
if (isGranted) {
|
||||||
|
try {
|
||||||
|
showFileChooser()
|
||||||
|
} catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
toast(R.string.toast_permission_denied)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
setContentView(binding.root)
|
setContentView(binding.root)
|
||||||
@@ -33,6 +47,7 @@ class AboutActivity : BaseActivity() {
|
|||||||
title = getString(R.string.title_about)
|
title = getString(R.string.title_about)
|
||||||
|
|
||||||
binding.tvBackupSummary.text = this.getString(R.string.summary_configuration_backup, extDir)
|
binding.tvBackupSummary.text = this.getString(R.string.summary_configuration_backup, extDir)
|
||||||
|
|
||||||
binding.layoutBackup.setOnClickListener {
|
binding.layoutBackup.setOnClickListener {
|
||||||
val ret = backupConfiguration(extDir.absolutePath)
|
val ret = backupConfiguration(extDir.absolutePath)
|
||||||
if (ret.first) {
|
if (ret.first) {
|
||||||
@@ -50,7 +65,8 @@ class AboutActivity : BaseActivity() {
|
|||||||
Intent(Intent.ACTION_SEND).setType("application/zip")
|
Intent(Intent.ACTION_SEND).setType("application/zip")
|
||||||
.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||||
.putExtra(
|
.putExtra(
|
||||||
Intent.EXTRA_STREAM, FileProvider.getUriForFile(
|
Intent.EXTRA_STREAM,
|
||||||
|
FileProvider.getUriForFile(
|
||||||
this, BuildConfig.APPLICATION_ID + ".cache", File(ret.second)
|
this, BuildConfig.APPLICATION_ID + ".cache", File(ret.second)
|
||||||
)
|
)
|
||||||
), getString(R.string.title_configuration_share)
|
), getString(R.string.title_configuration_share)
|
||||||
@@ -62,23 +78,22 @@ class AboutActivity : BaseActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
binding.layoutRestore.setOnClickListener {
|
binding.layoutRestore.setOnClickListener {
|
||||||
val permission = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
val permission =
|
||||||
Manifest.permission.READ_MEDIA_IMAGES
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||||
} else {
|
Manifest.permission.READ_MEDIA_IMAGES
|
||||||
Manifest.permission.READ_EXTERNAL_STORAGE
|
} else {
|
||||||
}
|
Manifest.permission.READ_EXTERNAL_STORAGE
|
||||||
RxPermissions(this)
|
|
||||||
.request(permission)
|
|
||||||
.subscribe {
|
|
||||||
if (it) {
|
|
||||||
try {
|
|
||||||
showFileChooser()
|
|
||||||
} catch (e: Exception) {
|
|
||||||
e.printStackTrace()
|
|
||||||
}
|
|
||||||
} else
|
|
||||||
toast(R.string.toast_permission_denied)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (ContextCompat.checkSelfPermission(this, permission) == android.content.pm.PackageManager.PERMISSION_GRANTED) {
|
||||||
|
try {
|
||||||
|
showFileChooser()
|
||||||
|
} catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
requestPermissionLauncher.launch(permission)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
binding.layoutSoureCcode.setOnClickListener {
|
binding.layoutSoureCcode.setOnClickListener {
|
||||||
@@ -88,13 +103,15 @@ class AboutActivity : BaseActivity() {
|
|||||||
binding.layoutFeedback.setOnClickListener {
|
binding.layoutFeedback.setOnClickListener {
|
||||||
Utils.openUri(this, AppConfig.v2rayNGIssues)
|
Utils.openUri(this, AppConfig.v2rayNGIssues)
|
||||||
}
|
}
|
||||||
binding.layoutOssLicenses.setOnClickListener{
|
|
||||||
|
binding.layoutOssLicenses.setOnClickListener {
|
||||||
val webView = android.webkit.WebView(this);
|
val webView = android.webkit.WebView(this);
|
||||||
webView.loadUrl("file:///android_asset/open_source_licenses.html");
|
webView.loadUrl("file:///android_asset/open_source_licenses.html")
|
||||||
android.app.AlertDialog.Builder(this)
|
android.app.AlertDialog.Builder(this)
|
||||||
.setTitle("Open source licenses")
|
.setTitle("Open source licenses")
|
||||||
.setView(webView)
|
.setView(webView)
|
||||||
.setPositiveButton("OK", android.content.DialogInterface.OnClickListener { dialog, whichButton -> dialog.dismiss() }).show()
|
.setPositiveButton("OK") { dialog, _ -> dialog.dismiss() }
|
||||||
|
.show()
|
||||||
}
|
}
|
||||||
|
|
||||||
binding.layoutTgChannel.setOnClickListener {
|
binding.layoutTgChannel.setOnClickListener {
|
||||||
@@ -157,9 +174,9 @@ class AboutActivity : BaseActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private val chooseFile =
|
private val chooseFile =
|
||||||
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
|
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
|
||||||
val uri = it.data?.data
|
val uri = result.data?.data
|
||||||
if (it.resultCode == RESULT_OK && uri != null) {
|
if (result.resultCode == RESULT_OK && uri != null) {
|
||||||
try {
|
try {
|
||||||
val targetFile =
|
val targetFile =
|
||||||
File(this.cacheDir.absolutePath, "${System.currentTimeMillis()}.zip")
|
File(this.cacheDir.absolutePath, "${System.currentTimeMillis()}.zip")
|
||||||
@@ -180,4 +197,7 @@ class AboutActivity : BaseActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun toast(messageResId: Int) {
|
||||||
|
Toast.makeText(this, getString(messageResId), Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,13 +1,13 @@
|
|||||||
package com.v2ray.ang.ui
|
package com.v2ray.ang.ui
|
||||||
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.os.Handler
|
|
||||||
import android.os.Looper
|
|
||||||
import android.text.method.ScrollingMovementMethod
|
|
||||||
import android.view.Menu
|
import android.view.Menu
|
||||||
import android.view.MenuItem
|
import android.view.MenuItem
|
||||||
import android.view.View
|
import androidx.appcompat.widget.SearchView
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
|
import androidx.recyclerview.widget.DividerItemDecoration
|
||||||
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
|
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
||||||
import com.v2ray.ang.AppConfig.ANG_PACKAGE
|
import com.v2ray.ang.AppConfig.ANG_PACKAGE
|
||||||
import com.v2ray.ang.R
|
import com.v2ray.ang.R
|
||||||
import com.v2ray.ang.databinding.ActivityLogcatBinding
|
import com.v2ray.ang.databinding.ActivityLogcatBinding
|
||||||
@@ -18,129 +18,132 @@ import kotlinx.coroutines.launch
|
|||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
|
|
||||||
class LogcatActivity : BaseActivity() {
|
|
||||||
|
class LogcatActivity : BaseActivity(), SwipeRefreshLayout.OnRefreshListener {
|
||||||
private val binding by lazy { ActivityLogcatBinding.inflate(layoutInflater) }
|
private val binding by lazy { ActivityLogcatBinding.inflate(layoutInflater) }
|
||||||
private val throttleManager = ThrottleManager()
|
|
||||||
|
var logsetsAll: MutableList<String> = mutableListOf()
|
||||||
|
var logsets: MutableList<String> = mutableListOf()
|
||||||
|
private val adapter by lazy { LogcatRecyclerAdapter(this) }
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
setContentView(binding.root)
|
setContentView(binding.root)
|
||||||
|
|
||||||
title = getString(R.string.title_logcat)
|
title = getString(R.string.title_logcat)
|
||||||
logcat(false)
|
|
||||||
|
binding.recyclerView.setHasFixedSize(true)
|
||||||
|
binding.recyclerView.layoutManager = LinearLayoutManager(this)
|
||||||
|
binding.recyclerView.adapter = adapter
|
||||||
|
binding.recyclerView.addItemDecoration(DividerItemDecoration(this, DividerItemDecoration.VERTICAL))
|
||||||
|
|
||||||
|
binding.refreshLayout.setOnRefreshListener(this)
|
||||||
|
|
||||||
|
logsets.add(getString(R.string.pull_down_to_refresh))
|
||||||
}
|
}
|
||||||
|
|
||||||
class ThrottleManager {
|
private fun getLogcat() {
|
||||||
private val throttleMap = mutableMapOf<String, Long>()
|
|
||||||
|
|
||||||
companion object {
|
try {
|
||||||
private const val THROTTLE_DURATION = 1000L
|
binding.refreshLayout.isRefreshing = true
|
||||||
}
|
|
||||||
|
|
||||||
@Synchronized
|
|
||||||
fun shouldProcess(key: String): Boolean {
|
|
||||||
val currentTime = System.currentTimeMillis()
|
|
||||||
val lastProcessTime = throttleMap[key] ?: 0L
|
|
||||||
|
|
||||||
return if (currentTime - lastProcessTime > THROTTLE_DURATION) {
|
|
||||||
throttleMap[key] = currentTime
|
|
||||||
true
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Synchronized
|
|
||||||
fun reset(key: String) {
|
|
||||||
throttleMap.remove(key)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun logcat(shouldFlushLog: Boolean) {
|
|
||||||
binding.pbWaiting.visibility = View.VISIBLE
|
|
||||||
|
|
||||||
lifecycleScope.launch(Dispatchers.Default) {
|
|
||||||
try {
|
|
||||||
if (shouldFlushLog) {
|
|
||||||
val lst = linkedSetOf("logcat", "-c")
|
|
||||||
withContext(Dispatchers.IO) {
|
|
||||||
val process = Runtime.getRuntime().exec(lst.toTypedArray())
|
|
||||||
process.waitFor()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val lst = linkedSetOf(
|
|
||||||
"logcat", "-d", "-v", "time", "-s",
|
|
||||||
"GoLog,tun2socks,$ANG_PACKAGE,AndroidRuntime,System.err"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
lifecycleScope.launch(Dispatchers.Default) {
|
||||||
|
val lst = LinkedHashSet<String>()
|
||||||
|
lst.add("logcat")
|
||||||
|
lst.add("-d")
|
||||||
|
lst.add("-v")
|
||||||
|
lst.add("time")
|
||||||
|
lst.add("-s")
|
||||||
|
lst.add("GoLog,tun2socks,${ANG_PACKAGE},AndroidRuntime,System.err")
|
||||||
val process = withContext(Dispatchers.IO) {
|
val process = withContext(Dispatchers.IO) {
|
||||||
Runtime.getRuntime().exec(lst.toTypedArray())
|
Runtime.getRuntime().exec(lst.toTypedArray())
|
||||||
}
|
}
|
||||||
|
|
||||||
val allLogs = process.inputStream.bufferedReader().use { it.readLines() }
|
val allText = process.inputStream.bufferedReader().use { it.readLines() }.reversed()
|
||||||
val filteredLogs = processLogs(allLogs)
|
launch(Dispatchers.Main) {
|
||||||
|
logsetsAll = allText.toMutableList()
|
||||||
withContext(Dispatchers.Main) {
|
logsets = allText.toMutableList()
|
||||||
updateLogDisplay(filteredLogs)
|
adapter.notifyDataSetChanged()
|
||||||
|
binding.refreshLayout.isRefreshing = false
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (e: IOException) {
|
|
||||||
withContext(Dispatchers.Main) {
|
|
||||||
binding.pbWaiting.visibility = View.GONE
|
|
||||||
toast(R.string.toast_failure)
|
|
||||||
}
|
|
||||||
e.printStackTrace()
|
|
||||||
}
|
}
|
||||||
|
} catch (e: IOException) {
|
||||||
|
e.printStackTrace()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun processLogs(logs: List<String>): List<String> {
|
private fun clearLogcat() {
|
||||||
val processedLogs = mutableListOf<String>()
|
try {
|
||||||
var isNotMatch = false
|
lifecycleScope.launch(Dispatchers.Default) {
|
||||||
|
val lst = LinkedHashSet<String>()
|
||||||
for (line in logs) {
|
lst.add("logcat")
|
||||||
when {
|
lst.add("-c")
|
||||||
line.contains("zxing.NotFoundException", ignoreCase = true) -> {
|
withContext(Dispatchers.IO) {
|
||||||
if (!isNotMatch) {
|
val process = Runtime.getRuntime().exec(lst.toTypedArray())
|
||||||
if (throttleManager.shouldProcess("NotFoundException")) {
|
process.waitFor()
|
||||||
processedLogs.add(line)
|
}
|
||||||
isNotMatch = true
|
launch(Dispatchers.Main) {
|
||||||
}
|
logsetsAll.clear()
|
||||||
}
|
logsets.clear()
|
||||||
|
adapter.notifyDataSetChanged()
|
||||||
}
|
}
|
||||||
else -> processedLogs.add(line)
|
|
||||||
}
|
}
|
||||||
}
|
} catch (e: IOException) {
|
||||||
|
e.printStackTrace()
|
||||||
return processedLogs.take(500)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun updateLogDisplay(logs: List<String>) {
|
|
||||||
binding.tvLogcat.text = logs.joinToString("\n")
|
|
||||||
binding.tvLogcat.movementMethod = ScrollingMovementMethod()
|
|
||||||
binding.pbWaiting.visibility = View.GONE
|
|
||||||
|
|
||||||
Handler(Looper.getMainLooper()).post {
|
|
||||||
binding.svLogcat.fullScroll(View.FOCUS_DOWN)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreateOptionsMenu(menu: Menu): Boolean {
|
override fun onCreateOptionsMenu(menu: Menu): Boolean {
|
||||||
menuInflater.inflate(R.menu.menu_logcat, menu)
|
menuInflater.inflate(R.menu.menu_logcat, menu)
|
||||||
|
|
||||||
|
val searchItem = menu.findItem(R.id.search_view)
|
||||||
|
if (searchItem != null) {
|
||||||
|
val searchView = searchItem.actionView as SearchView
|
||||||
|
searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
|
||||||
|
override fun onQueryTextSubmit(query: String?): Boolean = false
|
||||||
|
|
||||||
|
override fun onQueryTextChange(newText: String?): Boolean {
|
||||||
|
filterLogs(newText)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
searchView.setOnCloseListener {
|
||||||
|
filterLogs("")
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return super.onCreateOptionsMenu(menu)
|
return super.onCreateOptionsMenu(menu)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onOptionsItemSelected(item: MenuItem) = when (item.itemId) {
|
override fun onOptionsItemSelected(item: MenuItem) = when (item.itemId) {
|
||||||
R.id.copy_all -> {
|
R.id.copy_all -> {
|
||||||
Utils.setClipboard(this, binding.tvLogcat.text.toString())
|
Utils.setClipboard(this, logsets.joinToString("\n"))
|
||||||
toast(R.string.toast_success)
|
toast(R.string.toast_success)
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
R.id.clear_all -> {
|
R.id.clear_all -> {
|
||||||
throttleManager.reset("zxing.NotFoundException")
|
clearLogcat()
|
||||||
logcat(true)
|
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> super.onOptionsItemSelected(item)
|
else -> super.onOptionsItemSelected(item)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
private fun filterLogs(content: String?): Boolean {
|
||||||
|
val key = content?.trim()
|
||||||
|
logsets = if (key.isNullOrEmpty()) {
|
||||||
|
logsetsAll.toMutableList()
|
||||||
|
} else {
|
||||||
|
logsetsAll.filter { it.contains(key) }.toMutableList()
|
||||||
|
}
|
||||||
|
|
||||||
|
adapter?.notifyDataSetChanged()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onRefresh() {
|
||||||
|
getLogcat()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
package com.v2ray.ang.ui
|
||||||
|
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import com.v2ray.ang.databinding.ItemRecyclerLogcatBinding
|
||||||
|
|
||||||
|
class LogcatRecyclerAdapter(val activity: LogcatActivity) : RecyclerView.Adapter<LogcatRecyclerAdapter.MainViewHolder>() {
|
||||||
|
private var mActivity: LogcatActivity = activity
|
||||||
|
|
||||||
|
override fun getItemCount() = mActivity.logsets.size
|
||||||
|
|
||||||
|
override fun onBindViewHolder(holder: MainViewHolder, position: Int) {
|
||||||
|
val content = mActivity.logsets[position]
|
||||||
|
holder.itemSubSettingBinding.logContent.text = content
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MainViewHolder {
|
||||||
|
return MainViewHolder(
|
||||||
|
ItemRecyclerLogcatBinding.inflate(
|
||||||
|
LayoutInflater.from(parent.context),
|
||||||
|
parent,
|
||||||
|
false
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
class MainViewHolder(val itemSubSettingBinding: ItemRecyclerLogcatBinding) : RecyclerView.ViewHolder(itemSubSettingBinding.root)
|
||||||
|
|
||||||
|
}
|
||||||
@@ -3,6 +3,7 @@ package com.v2ray.ang.ui
|
|||||||
import android.Manifest
|
import android.Manifest
|
||||||
import android.content.ActivityNotFoundException
|
import android.content.ActivityNotFoundException
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
|
import android.content.pm.PackageManager
|
||||||
import android.content.res.ColorStateList
|
import android.content.res.ColorStateList
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.net.VpnService
|
import android.net.VpnService
|
||||||
@@ -27,7 +28,6 @@ import androidx.recyclerview.widget.ItemTouchHelper
|
|||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import com.google.android.material.navigation.NavigationView
|
import com.google.android.material.navigation.NavigationView
|
||||||
import com.google.android.material.tabs.TabLayout
|
import com.google.android.material.tabs.TabLayout
|
||||||
import com.tbruyelle.rxpermissions3.RxPermissions
|
|
||||||
import com.v2ray.ang.AppConfig
|
import com.v2ray.ang.AppConfig
|
||||||
import com.v2ray.ang.AppConfig.VPN
|
import com.v2ray.ang.AppConfig.VPN
|
||||||
import com.v2ray.ang.R
|
import com.v2ray.ang.R
|
||||||
@@ -41,8 +41,6 @@ import com.v2ray.ang.helper.SimpleItemTouchHelperCallback
|
|||||||
import com.v2ray.ang.service.V2RayServiceManager
|
import com.v2ray.ang.service.V2RayServiceManager
|
||||||
import com.v2ray.ang.util.Utils
|
import com.v2ray.ang.util.Utils
|
||||||
import com.v2ray.ang.viewmodel.MainViewModel
|
import com.v2ray.ang.viewmodel.MainViewModel
|
||||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
|
|
||||||
import io.reactivex.rxjava3.core.Observable
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
@@ -81,6 +79,60 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
|
|||||||
private var mItemTouchHelper: ItemTouchHelper? = null
|
private var mItemTouchHelper: ItemTouchHelper? = null
|
||||||
val mainViewModel: MainViewModel by viewModels()
|
val mainViewModel: MainViewModel by viewModels()
|
||||||
|
|
||||||
|
// register activity result for requesting permission
|
||||||
|
private val requestPermissionLauncher =
|
||||||
|
registerForActivityResult(
|
||||||
|
ActivityResultContracts.RequestPermission()
|
||||||
|
) { isGranted: Boolean ->
|
||||||
|
if (isGranted) {
|
||||||
|
when (pendingAction) {
|
||||||
|
Action.IMPORT_QR_CODE_CONFIG ->
|
||||||
|
scanQRCodeForConfig.launch(Intent(this, ScannerActivity::class.java))
|
||||||
|
Action.IMPORT_QR_CODE_URL ->
|
||||||
|
scanQRCodeForUrlToCustomConfig.launch(Intent(this, ScannerActivity::class.java))
|
||||||
|
Action.READ_CONTENT_FROM_URI ->
|
||||||
|
chooseFileForCustomConfig.launch(Intent.createChooser(Intent(Intent.ACTION_GET_CONTENT).apply {
|
||||||
|
type = "*/*"
|
||||||
|
addCategory(Intent.CATEGORY_OPENABLE)
|
||||||
|
}, getString(R.string.title_file_chooser)))
|
||||||
|
Action.POST_NOTIFICATIONS -> {}
|
||||||
|
else -> {}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
toast(R.string.toast_permission_denied)
|
||||||
|
}
|
||||||
|
pendingAction = Action.NONE
|
||||||
|
}
|
||||||
|
|
||||||
|
private var pendingAction: Action = Action.NONE
|
||||||
|
|
||||||
|
enum class Action {
|
||||||
|
NONE,
|
||||||
|
IMPORT_QR_CODE_CONFIG,
|
||||||
|
IMPORT_QR_CODE_URL,
|
||||||
|
READ_CONTENT_FROM_URI,
|
||||||
|
POST_NOTIFICATIONS
|
||||||
|
}
|
||||||
|
|
||||||
|
private val chooseFileForCustomConfig = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
|
||||||
|
val uri = it.data?.data
|
||||||
|
if (it.resultCode == RESULT_OK && uri != null) {
|
||||||
|
readContentFromUri(uri)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private val scanQRCodeForConfig = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
|
||||||
|
if (it.resultCode == RESULT_OK) {
|
||||||
|
importBatchConfig(it.data?.getStringExtra("SCAN_RESULT"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private val scanQRCodeForUrlToCustomConfig = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
|
||||||
|
if (it.resultCode == RESULT_OK) {
|
||||||
|
importConfigCustomUrl(it.data?.getStringExtra("SCAN_RESULT"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
setContentView(binding.root)
|
setContentView(binding.root)
|
||||||
@@ -129,12 +181,10 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
|
|||||||
migrateLegacy()
|
migrateLegacy()
|
||||||
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||||
RxPermissions(this)
|
if (ContextCompat.checkSelfPermission(this, Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED) {
|
||||||
.request(Manifest.permission.POST_NOTIFICATIONS)
|
pendingAction = Action.POST_NOTIFICATIONS
|
||||||
.subscribe {
|
requestPermissionLauncher.launch(Manifest.permission.POST_NOTIFICATIONS)
|
||||||
if (!it)
|
}
|
||||||
toast(R.string.toast_permission_denied_notification)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onBackPressedDispatcher.addCallback(this, object : OnBackPressedCallback(true) {
|
onBackPressedDispatcher.addCallback(this, object : OnBackPressedCallback(true) {
|
||||||
@@ -226,11 +276,10 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
|
|||||||
if (mainViewModel.isRunning.value == true) {
|
if (mainViewModel.isRunning.value == true) {
|
||||||
Utils.stopVService(this)
|
Utils.stopVService(this)
|
||||||
}
|
}
|
||||||
Observable.timer(500, TimeUnit.MILLISECONDS)
|
lifecycleScope.launch {
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
delay(500)
|
||||||
.subscribe {
|
startV2Ray()
|
||||||
startV2Ray()
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override fun onResume() {
|
public override fun onResume() {
|
||||||
@@ -462,38 +511,20 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
|
|||||||
* import config from qrcode
|
* import config from qrcode
|
||||||
*/
|
*/
|
||||||
private fun importQRcode(forConfig: Boolean): Boolean {
|
private fun importQRcode(forConfig: Boolean): Boolean {
|
||||||
// try {
|
val permission = Manifest.permission.CAMERA
|
||||||
// startActivityForResult(Intent("com.google.zxing.client.android.SCAN")
|
if (ContextCompat.checkSelfPermission(this, permission) == PackageManager.PERMISSION_GRANTED) {
|
||||||
// .addCategory(Intent.CATEGORY_DEFAULT)
|
if (forConfig) {
|
||||||
// .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP), requestCode)
|
scanQRCodeForConfig.launch(Intent(this, ScannerActivity::class.java))
|
||||||
// } catch (e: Exception) {
|
} else {
|
||||||
RxPermissions(this)
|
scanQRCodeForUrlToCustomConfig.launch(Intent(this, ScannerActivity::class.java))
|
||||||
.request(Manifest.permission.CAMERA)
|
|
||||||
.subscribe {
|
|
||||||
if (it)
|
|
||||||
if (forConfig)
|
|
||||||
scanQRCodeForConfig.launch(Intent(this, ScannerActivity::class.java))
|
|
||||||
else
|
|
||||||
scanQRCodeForUrlToCustomConfig.launch(Intent(this, ScannerActivity::class.java))
|
|
||||||
else
|
|
||||||
toast(R.string.toast_permission_denied)
|
|
||||||
}
|
}
|
||||||
// }
|
} else {
|
||||||
|
pendingAction = if (forConfig) Action.IMPORT_QR_CODE_CONFIG else Action.IMPORT_QR_CODE_URL
|
||||||
|
requestPermissionLauncher.launch(permission)
|
||||||
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
private val scanQRCodeForConfig = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
|
|
||||||
if (it.resultCode == RESULT_OK) {
|
|
||||||
importBatchConfig(it.data?.getStringExtra("SCAN_RESULT"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private val scanQRCodeForUrlToCustomConfig = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
|
|
||||||
if (it.resultCode == RESULT_OK) {
|
|
||||||
importConfigCustomUrl(it.data?.getStringExtra("SCAN_RESULT"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* import config from clipboard
|
* import config from clipboard
|
||||||
*/
|
*/
|
||||||
@@ -614,10 +645,6 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
|
|||||||
* import config from sub
|
* import config from sub
|
||||||
*/
|
*/
|
||||||
private fun importConfigViaSub(): Boolean {
|
private fun importConfigViaSub(): Boolean {
|
||||||
// val dialog = AlertDialog.Builder(this)
|
|
||||||
// .setView(LayoutProgressBinding.inflate(layoutInflater).root)
|
|
||||||
// .setCancelable(false)
|
|
||||||
// .show()
|
|
||||||
binding.pbWaiting.show()
|
binding.pbWaiting.show()
|
||||||
|
|
||||||
lifecycleScope.launch(Dispatchers.IO) {
|
lifecycleScope.launch(Dispatchers.IO) {
|
||||||
@@ -630,7 +657,6 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
|
|||||||
} else {
|
} else {
|
||||||
toast(R.string.toast_failure)
|
toast(R.string.toast_failure)
|
||||||
}
|
}
|
||||||
//dialog.dismiss()
|
|
||||||
binding.pbWaiting.hide()
|
binding.pbWaiting.hide()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -645,17 +671,17 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
|
|||||||
intent.type = "*/*"
|
intent.type = "*/*"
|
||||||
intent.addCategory(Intent.CATEGORY_OPENABLE)
|
intent.addCategory(Intent.CATEGORY_OPENABLE)
|
||||||
|
|
||||||
try {
|
val permission = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||||
chooseFileForCustomConfig.launch(Intent.createChooser(intent, getString(R.string.title_file_chooser)))
|
Manifest.permission.READ_MEDIA_IMAGES
|
||||||
} catch (ex: ActivityNotFoundException) {
|
} else {
|
||||||
toast(R.string.toast_require_file_manager)
|
Manifest.permission.READ_EXTERNAL_STORAGE
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private val chooseFileForCustomConfig = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
|
if (ContextCompat.checkSelfPermission(this, permission) == PackageManager.PERMISSION_GRANTED) {
|
||||||
val uri = it.data?.data
|
pendingAction = Action.READ_CONTENT_FROM_URI
|
||||||
if (it.resultCode == RESULT_OK && uri != null) {
|
chooseFileForCustomConfig.launch(Intent.createChooser(intent, getString(R.string.title_file_chooser)))
|
||||||
readContentFromUri(uri)
|
} else {
|
||||||
|
requestPermissionLauncher.launch(permission)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -668,20 +694,18 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
|
|||||||
} else {
|
} else {
|
||||||
Manifest.permission.READ_EXTERNAL_STORAGE
|
Manifest.permission.READ_EXTERNAL_STORAGE
|
||||||
}
|
}
|
||||||
RxPermissions(this)
|
|
||||||
.request(permission)
|
if (ContextCompat.checkSelfPermission(this, permission) == PackageManager.PERMISSION_GRANTED) {
|
||||||
.subscribe {
|
try {
|
||||||
if (it) {
|
contentResolver.openInputStream(uri).use { input ->
|
||||||
try {
|
importCustomizeConfig(input?.bufferedReader()?.readText())
|
||||||
contentResolver.openInputStream(uri).use { input ->
|
}
|
||||||
importCustomizeConfig(input?.bufferedReader()?.readText())
|
} catch (e: Exception) {
|
||||||
}
|
e.printStackTrace()
|
||||||
} catch (e: Exception) {
|
|
||||||
e.printStackTrace()
|
|
||||||
}
|
|
||||||
} else
|
|
||||||
toast(R.string.toast_permission_denied)
|
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
requestPermissionLauncher.launch(permission)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -763,4 +787,4 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
|
|||||||
binding.drawerLayout.closeDrawer(GravityCompat.START)
|
binding.drawerLayout.closeDrawer(GravityCompat.START)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -8,6 +8,7 @@ import android.view.View
|
|||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import androidx.appcompat.app.AlertDialog
|
import androidx.appcompat.app.AlertDialog
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import com.v2ray.ang.AngApplication.Companion.application
|
import com.v2ray.ang.AngApplication.Companion.application
|
||||||
import com.v2ray.ang.AppConfig
|
import com.v2ray.ang.AppConfig
|
||||||
@@ -23,9 +24,9 @@ import com.v2ray.ang.helper.ItemTouchHelperAdapter
|
|||||||
import com.v2ray.ang.helper.ItemTouchHelperViewHolder
|
import com.v2ray.ang.helper.ItemTouchHelperViewHolder
|
||||||
import com.v2ray.ang.service.V2RayServiceManager
|
import com.v2ray.ang.service.V2RayServiceManager
|
||||||
import com.v2ray.ang.util.Utils
|
import com.v2ray.ang.util.Utils
|
||||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
|
import kotlinx.coroutines.delay
|
||||||
import io.reactivex.rxjava3.core.Observable
|
import kotlinx.coroutines.launch
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.*
|
||||||
|
|
||||||
class MainRecyclerAdapter(val activity: MainActivity) : RecyclerView.Adapter<MainRecyclerAdapter.BaseViewHolder>(), ItemTouchHelperAdapter {
|
class MainRecyclerAdapter(val activity: MainActivity) : RecyclerView.Adapter<MainRecyclerAdapter.BaseViewHolder>(), ItemTouchHelperAdapter {
|
||||||
companion object {
|
companion object {
|
||||||
@@ -165,11 +166,14 @@ class MainRecyclerAdapter(val activity: MainActivity) : RecyclerView.Adapter<Mai
|
|||||||
notifyItemChanged(mActivity.mainViewModel.getPosition(guid))
|
notifyItemChanged(mActivity.mainViewModel.getPosition(guid))
|
||||||
if (isRunning) {
|
if (isRunning) {
|
||||||
Utils.stopVService(mActivity)
|
Utils.stopVService(mActivity)
|
||||||
Observable.timer(500, TimeUnit.MILLISECONDS)
|
mActivity.lifecycleScope.launch {
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
try {
|
||||||
.subscribe {
|
delay(500)
|
||||||
V2RayServiceManager.startV2Ray(mActivity)
|
V2RayServiceManager.startV2Ray(mActivity)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -246,4 +250,4 @@ class MainRecyclerAdapter(val activity: MainActivity) : RecyclerView.Adapter<Mai
|
|||||||
|
|
||||||
override fun onItemDismiss(position: Int) {
|
override fun onItemDismiss(position: Int) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -20,10 +20,9 @@ import com.v2ray.ang.extension.v2RayApplication
|
|||||||
import com.v2ray.ang.handler.MmkvManager
|
import com.v2ray.ang.handler.MmkvManager
|
||||||
import com.v2ray.ang.util.AppManagerUtil
|
import com.v2ray.ang.util.AppManagerUtil
|
||||||
import com.v2ray.ang.util.Utils
|
import com.v2ray.ang.util.Utils
|
||||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
|
|
||||||
import io.reactivex.rxjava3.schedulers.Schedulers
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
import java.text.Collator
|
import java.text.Collator
|
||||||
|
|
||||||
class PerAppProxyActivity : BaseActivity() {
|
class PerAppProxyActivity : BaseActivity() {
|
||||||
@@ -43,93 +42,39 @@ class PerAppProxyActivity : BaseActivity() {
|
|||||||
|
|
||||||
val blacklist = MmkvManager.decodeSettingsStringSet(AppConfig.PREF_PER_APP_PROXY_SET)
|
val blacklist = MmkvManager.decodeSettingsStringSet(AppConfig.PREF_PER_APP_PROXY_SET)
|
||||||
|
|
||||||
AppManagerUtil.rxLoadNetworkAppList(this)
|
lifecycleScope.launch {
|
||||||
.subscribeOn(Schedulers.io())
|
try {
|
||||||
.map {
|
binding.pbWaiting.visibility = View.VISIBLE
|
||||||
if (blacklist != null) {
|
val blacklist = MmkvManager.decodeSettingsStringSet(AppConfig.PREF_PER_APP_PROXY_SET)
|
||||||
it.forEach { one ->
|
val apps = withContext(Dispatchers.IO) {
|
||||||
if (blacklist.contains(one.packageName)) {
|
val appsList = AppManagerUtil.loadNetworkAppList(this@PerAppProxyActivity)
|
||||||
one.isSelected = 1
|
|
||||||
} else {
|
if (blacklist != null) {
|
||||||
one.isSelected = 0
|
appsList.forEach { app ->
|
||||||
|
app.isSelected = if (blacklist.contains(app.packageName)) 1 else 0
|
||||||
}
|
}
|
||||||
}
|
appsList.sortedWith(Comparator { p1, p2 ->
|
||||||
val comparator = Comparator<AppInfo> { p1, p2 ->
|
when {
|
||||||
when {
|
p1.isSelected > p2.isSelected -> -1
|
||||||
p1.isSelected > p2.isSelected -> -1
|
p1.isSelected == p2.isSelected -> 0
|
||||||
p1.isSelected == p2.isSelected -> 0
|
else -> 1
|
||||||
else -> 1
|
}
|
||||||
}
|
})
|
||||||
}
|
} else {
|
||||||
it.sortedWith(comparator)
|
|
||||||
} else {
|
|
||||||
val comparator = object : Comparator<AppInfo> {
|
|
||||||
val collator = Collator.getInstance()
|
val collator = Collator.getInstance()
|
||||||
override fun compare(o1: AppInfo, o2: AppInfo) = collator.compare(o1.appName, o2.appName)
|
appsList.sortedWith(compareBy(collator) { it.appName })
|
||||||
}
|
}
|
||||||
it.sortedWith(comparator)
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
// .map {
|
appsAll = apps
|
||||||
// val comparator = object : Comparator<AppInfo> {
|
adapter = PerAppProxyAdapter(this@PerAppProxyActivity, apps, blacklist)
|
||||||
// val collator = Collator.getInstance()
|
|
||||||
// override fun compare(o1: AppInfo, o2: AppInfo) = collator.compare(o1.appName, o2.appName)
|
|
||||||
// }
|
|
||||||
// it.sortedWith(comparator)
|
|
||||||
// }
|
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
|
||||||
.subscribe {
|
|
||||||
appsAll = it
|
|
||||||
adapter = PerAppProxyAdapter(this, it, blacklist)
|
|
||||||
binding.recyclerView.adapter = adapter
|
binding.recyclerView.adapter = adapter
|
||||||
binding.pbWaiting.visibility = View.GONE
|
binding.pbWaiting.visibility = View.GONE
|
||||||
|
} catch (e: Exception) {
|
||||||
|
binding.pbWaiting.visibility = View.GONE
|
||||||
|
Log.e(ANG_PACKAGE, "Error loading apps", e)
|
||||||
}
|
}
|
||||||
/***
|
|
||||||
recycler_view.addOnScrollListener(object : RecyclerView.OnScrollListener() {
|
|
||||||
var dst = 0
|
|
||||||
val threshold = resources.getDimensionPixelSize(R.dimen.bypass_list_header_height) * 2
|
|
||||||
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
|
|
||||||
dst += dy
|
|
||||||
if (dst > threshold) {
|
|
||||||
header_view.hide()
|
|
||||||
dst = 0
|
|
||||||
} else if (dst < -20) {
|
|
||||||
header_view.show()
|
|
||||||
dst = 0
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
var hiding = false
|
|
||||||
fun View.hide() {
|
|
||||||
val target = -height.toFloat()
|
|
||||||
if (hiding || translationY == target) return
|
|
||||||
animate()
|
|
||||||
.translationY(target)
|
|
||||||
.setInterpolator(AccelerateInterpolator(2F))
|
|
||||||
.setListener(object : AnimatorListenerAdapter() {
|
|
||||||
override fun onAnimationEnd(animation: Animator?) {
|
|
||||||
hiding = false
|
|
||||||
}
|
|
||||||
})
|
|
||||||
hiding = true
|
|
||||||
}
|
|
||||||
|
|
||||||
var showing = false
|
|
||||||
fun View.show() {
|
|
||||||
val target = 0f
|
|
||||||
if (showing || translationY == target) return
|
|
||||||
animate()
|
|
||||||
.translationY(target)
|
|
||||||
.setInterpolator(DecelerateInterpolator(2F))
|
|
||||||
.setListener(object : AnimatorListenerAdapter() {
|
|
||||||
override fun onAnimationEnd(animation: Animator?) {
|
|
||||||
showing = false
|
|
||||||
}
|
|
||||||
})
|
|
||||||
showing = true
|
|
||||||
}
|
|
||||||
})
|
|
||||||
***/
|
|
||||||
|
|
||||||
binding.switchPerAppProxy.setOnCheckedChangeListener { _, isChecked ->
|
binding.switchPerAppProxy.setOnCheckedChangeListener { _, isChecked ->
|
||||||
MmkvManager.encodeSettings(AppConfig.PREF_PER_APP_PROXY, isChecked)
|
MmkvManager.encodeSettings(AppConfig.PREF_PER_APP_PROXY, isChecked)
|
||||||
@@ -140,36 +85,6 @@ class PerAppProxyActivity : BaseActivity() {
|
|||||||
MmkvManager.encodeSettings(AppConfig.PREF_BYPASS_APPS, isChecked)
|
MmkvManager.encodeSettings(AppConfig.PREF_BYPASS_APPS, isChecked)
|
||||||
}
|
}
|
||||||
binding.switchBypassApps.isChecked = MmkvManager.decodeSettingsBool(AppConfig.PREF_BYPASS_APPS, false)
|
binding.switchBypassApps.isChecked = MmkvManager.decodeSettingsBool(AppConfig.PREF_BYPASS_APPS, false)
|
||||||
|
|
||||||
/***
|
|
||||||
et_search.setOnEditorActionListener { v, actionId, event ->
|
|
||||||
if (actionId == EditorInfo.IME_ACTION_SEARCH) {
|
|
||||||
//hide
|
|
||||||
var imm: InputMethodManager = v.context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
|
|
||||||
imm.toggleSoftInput(0, InputMethodManager.HIDE_NOT_ALWAYS)
|
|
||||||
|
|
||||||
val key = v.text.toString().toUpperCase()
|
|
||||||
val apps = ArrayList<AppInfo>()
|
|
||||||
if (TextUtils.isEmpty(key)) {
|
|
||||||
appsAll?.forEach {
|
|
||||||
apps.add(it)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
appsAll?.forEach {
|
|
||||||
if (it.appName.toUpperCase().indexOf(key) >= 0) {
|
|
||||||
apps.add(it)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
adapter = PerAppProxyAdapter(this, apps, adapter?.blacklist)
|
|
||||||
recycler_view.adapter = adapter
|
|
||||||
adapter?.notifyDataSetChanged()
|
|
||||||
true
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
***/
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onPause() {
|
override fun onPause() {
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ import androidx.appcompat.app.AlertDialog
|
|||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import androidx.recyclerview.widget.ItemTouchHelper
|
import androidx.recyclerview.widget.ItemTouchHelper
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import com.tbruyelle.rxpermissions3.RxPermissions
|
|
||||||
import com.v2ray.ang.AppConfig
|
import com.v2ray.ang.AppConfig
|
||||||
import com.v2ray.ang.R
|
import com.v2ray.ang.R
|
||||||
import com.v2ray.ang.databinding.ActivityRoutingSettingBinding
|
import com.v2ray.ang.databinding.ActivityRoutingSettingBinding
|
||||||
@@ -39,6 +38,16 @@ class RoutingSettingActivity : BaseActivity() {
|
|||||||
private val preset_rulesets: Array<out String> by lazy {
|
private val preset_rulesets: Array<out String> by lazy {
|
||||||
resources.getStringArray(R.array.preset_rulesets)
|
resources.getStringArray(R.array.preset_rulesets)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private val requestCameraPermissionLauncher = registerForActivityResult(
|
||||||
|
ActivityResultContracts.RequestPermission()
|
||||||
|
) { isGranted: Boolean ->
|
||||||
|
if (isGranted) {
|
||||||
|
scanQRcodeForRulesets.launch(Intent(this, ScannerActivity::class.java))
|
||||||
|
} else {
|
||||||
|
toast(R.string.toast_permission_denied)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
@@ -102,11 +111,9 @@ class RoutingSettingActivity : BaseActivity() {
|
|||||||
e.printStackTrace()
|
e.printStackTrace()
|
||||||
}
|
}
|
||||||
}.show()
|
}.show()
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
.setNegativeButton(android.R.string.cancel) { _, _ ->
|
.setNegativeButton(android.R.string.cancel) { _, _ ->
|
||||||
//do noting
|
//do nothing
|
||||||
}
|
}
|
||||||
.show()
|
.show()
|
||||||
true
|
true
|
||||||
@@ -142,18 +149,10 @@ class RoutingSettingActivity : BaseActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
R.id.import_rulesets_from_qrcode -> {
|
R.id.import_rulesets_from_qrcode -> {
|
||||||
RxPermissions(this)
|
requestCameraPermissionLauncher.launch(Manifest.permission.CAMERA)
|
||||||
.request(Manifest.permission.CAMERA)
|
|
||||||
.subscribe {
|
|
||||||
if (it)
|
|
||||||
scanQRcodeForRulesets.launch(Intent(this, ScannerActivity::class.java))
|
|
||||||
else
|
|
||||||
toast(R.string.toast_permission_denied)
|
|
||||||
}
|
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
R.id.export_rulesets_to_clipboard -> {
|
R.id.export_rulesets_to_clipboard -> {
|
||||||
val rulesetList = MmkvManager.decodeRoutingRulesets()
|
val rulesetList = MmkvManager.decodeRoutingRulesets()
|
||||||
if (rulesetList.isNullOrEmpty()) {
|
if (rulesetList.isNullOrEmpty()) {
|
||||||
@@ -201,5 +200,4 @@ class RoutingSettingActivity : BaseActivity() {
|
|||||||
rulesets.addAll(MmkvManager.decodeRoutingRulesets() ?: mutableListOf())
|
rulesets.addAll(MmkvManager.decodeRoutingRulesets() ?: mutableListOf())
|
||||||
adapter.notifyDataSetChanged()
|
adapter.notifyDataSetChanged()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
|
||||||
@@ -4,13 +4,23 @@ import android.Manifest
|
|||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import androidx.activity.result.contract.ActivityResultContracts
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
import com.tbruyelle.rxpermissions3.RxPermissions
|
|
||||||
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.handler.AngConfigManager
|
import com.v2ray.ang.handler.AngConfigManager
|
||||||
|
|
||||||
class ScScannerActivity : BaseActivity() {
|
class ScScannerActivity : BaseActivity() {
|
||||||
|
|
||||||
|
private val requestCameraPermissionLauncher = registerForActivityResult(
|
||||||
|
ActivityResultContracts.RequestPermission()
|
||||||
|
) { isGranted: Boolean ->
|
||||||
|
if (isGranted) {
|
||||||
|
scanQRCode.launch(Intent(this, ScannerActivity::class.java))
|
||||||
|
} else {
|
||||||
|
toast(R.string.toast_permission_denied)
|
||||||
|
finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
setContentView(R.layout.activity_none)
|
setContentView(R.layout.activity_none)
|
||||||
@@ -18,19 +28,10 @@ class ScScannerActivity : BaseActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun importQRcode(): Boolean {
|
fun importQRcode(): Boolean {
|
||||||
RxPermissions(this)
|
requestCameraPermissionLauncher.launch(Manifest.permission.CAMERA)
|
||||||
.request(Manifest.permission.CAMERA)
|
|
||||||
.subscribe { granted ->
|
|
||||||
if (granted) {
|
|
||||||
scanQRCode.launch(Intent(this, ScannerActivity::class.java))
|
|
||||||
} else {
|
|
||||||
toast(R.string.toast_permission_denied)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private val scanQRCode = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
|
private val scanQRCode = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
|
||||||
if (it.resultCode == RESULT_OK) {
|
if (it.resultCode == RESULT_OK) {
|
||||||
val scanResult = it.data?.getStringExtra("SCAN_RESULT").orEmpty()
|
val scanResult = it.data?.getStringExtra("SCAN_RESULT").orEmpty()
|
||||||
@@ -46,5 +47,4 @@ class ScScannerActivity : BaseActivity() {
|
|||||||
}
|
}
|
||||||
finish()
|
finish()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
|
||||||
@@ -2,13 +2,15 @@ package com.v2ray.ang.ui
|
|||||||
|
|
||||||
import android.Manifest
|
import android.Manifest
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
|
import android.content.pm.PackageManager
|
||||||
import android.graphics.BitmapFactory
|
import android.graphics.BitmapFactory
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
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.tbruyelle.rxpermissions3.RxPermissions
|
import androidx.core.app.ActivityCompat
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
import com.v2ray.ang.AppConfig
|
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
|
||||||
@@ -21,6 +23,37 @@ import io.github.g00fy2.quickie.config.ScannerConfig
|
|||||||
class ScannerActivity : BaseActivity() {
|
class ScannerActivity : BaseActivity() {
|
||||||
|
|
||||||
private val scanQrCode = registerForActivityResult(ScanCustomCode(), ::handleResult)
|
private val scanQrCode = registerForActivityResult(ScanCustomCode(), ::handleResult)
|
||||||
|
private val chooseFile = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
|
||||||
|
val uri = it.data?.data
|
||||||
|
if (it.resultCode == RESULT_OK && uri != null) {
|
||||||
|
try {
|
||||||
|
val inputStream = contentResolver.openInputStream(uri)
|
||||||
|
val bitmap = BitmapFactory.decodeStream(inputStream)
|
||||||
|
inputStream?.close()
|
||||||
|
|
||||||
|
val text = QRCodeDecoder.syncDecodeQRCode(bitmap)
|
||||||
|
if (text.isNullOrEmpty()) {
|
||||||
|
toast(R.string.toast_decoding_failed)
|
||||||
|
} else {
|
||||||
|
finished(text)
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
toast(R.string.toast_decoding_failed)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private val requestPermissionLauncher =
|
||||||
|
registerForActivityResult(
|
||||||
|
ActivityResultContracts.RequestPermission()
|
||||||
|
) { isGranted: Boolean ->
|
||||||
|
if (isGranted) {
|
||||||
|
showFileChooser()
|
||||||
|
} else {
|
||||||
|
toast(R.string.toast_permission_denied)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public override fun onCreate(savedInstanceState: Bundle?) {
|
public override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
@@ -72,15 +105,12 @@ class ScannerActivity : BaseActivity() {
|
|||||||
} else {
|
} else {
|
||||||
Manifest.permission.READ_EXTERNAL_STORAGE
|
Manifest.permission.READ_EXTERNAL_STORAGE
|
||||||
}
|
}
|
||||||
RxPermissions(this)
|
|
||||||
.request(permission)
|
if (ContextCompat.checkSelfPermission(this, permission) == PackageManager.PERMISSION_GRANTED) {
|
||||||
.subscribe { granted ->
|
showFileChooser()
|
||||||
if (granted) {
|
} else {
|
||||||
showFileChooser()
|
requestPermissionLauncher.launch(permission)
|
||||||
} else {
|
}
|
||||||
toast(R.string.toast_permission_denied)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -100,26 +130,4 @@ class ScannerActivity : BaseActivity() {
|
|||||||
toast(R.string.toast_require_file_manager)
|
toast(R.string.toast_require_file_manager)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
private val chooseFile = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
|
|
||||||
val uri = it.data?.data
|
|
||||||
if (it.resultCode == RESULT_OK && uri != null) {
|
|
||||||
try {
|
|
||||||
val inputStream = contentResolver.openInputStream(uri)
|
|
||||||
val bitmap = BitmapFactory.decodeStream(inputStream)
|
|
||||||
inputStream?.close()
|
|
||||||
|
|
||||||
val text = QRCodeDecoder.syncDecodeQRCode(bitmap)
|
|
||||||
if (text.isNullOrEmpty()) {
|
|
||||||
toast(R.string.toast_decoding_failed)
|
|
||||||
} else {
|
|
||||||
finished(text)
|
|
||||||
}
|
|
||||||
} catch (e: Exception) {
|
|
||||||
e.printStackTrace()
|
|
||||||
toast(R.string.toast_decoding_failed)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -126,6 +126,8 @@ class ServerActivity : BaseActivity() {
|
|||||||
private val et_port_hop: EditText? by lazy { findViewById(R.id.et_port_hop) }
|
private val et_port_hop: EditText? by lazy { findViewById(R.id.et_port_hop) }
|
||||||
private val et_port_hop_interval: EditText? by lazy { findViewById(R.id.et_port_hop_interval) }
|
private val et_port_hop_interval: EditText? by lazy { findViewById(R.id.et_port_hop_interval) }
|
||||||
private val et_pinsha256: EditText? by lazy { findViewById(R.id.et_pinsha256) }
|
private val et_pinsha256: EditText? by lazy { findViewById(R.id.et_pinsha256) }
|
||||||
|
private val et_bandwidth_down: EditText? by lazy { findViewById(R.id.et_bandwidth_down) }
|
||||||
|
private val et_bandwidth_up: EditText? by lazy { findViewById(R.id.et_bandwidth_up) }
|
||||||
private val et_extra: EditText? by lazy { findViewById(R.id.et_extra) }
|
private val et_extra: EditText? by lazy { findViewById(R.id.et_extra) }
|
||||||
private val layout_extra: LinearLayout? by lazy { findViewById(R.id.layout_extra) }
|
private val layout_extra: LinearLayout? by lazy { findViewById(R.id.layout_extra) }
|
||||||
|
|
||||||
@@ -334,6 +336,8 @@ class ServerActivity : BaseActivity() {
|
|||||||
et_port_hop?.text = Utils.getEditable(config.portHopping)
|
et_port_hop?.text = Utils.getEditable(config.portHopping)
|
||||||
et_port_hop_interval?.text = Utils.getEditable(config.portHoppingInterval)
|
et_port_hop_interval?.text = Utils.getEditable(config.portHoppingInterval)
|
||||||
et_pinsha256?.text = Utils.getEditable(config.pinSHA256)
|
et_pinsha256?.text = Utils.getEditable(config.pinSHA256)
|
||||||
|
et_bandwidth_down?.text = Utils.getEditable(config.bandwidthDown)
|
||||||
|
et_bandwidth_up?.text = Utils.getEditable(config.bandwidthUp)
|
||||||
}
|
}
|
||||||
val securityEncryptions =
|
val securityEncryptions =
|
||||||
if (config.configType == EConfigType.SHADOWSOCKS) shadowsocksSecuritys else securitys
|
if (config.configType == EConfigType.SHADOWSOCKS) shadowsocksSecuritys else securitys
|
||||||
@@ -352,11 +356,11 @@ class ServerActivity : BaseActivity() {
|
|||||||
et_sni?.text = Utils.getEditable(config.sni)
|
et_sni?.text = Utils.getEditable(config.sni)
|
||||||
config.fingerPrint?.let {
|
config.fingerPrint?.let {
|
||||||
val utlsIndex = Utils.arrayFind(uTlsItems, it)
|
val utlsIndex = Utils.arrayFind(uTlsItems, it)
|
||||||
sp_stream_fingerprint?.setSelection(utlsIndex)
|
utlsIndex.let { sp_stream_fingerprint?.setSelection(if (it >= 0) it else 0) }
|
||||||
}
|
}
|
||||||
config.alpn?.let {
|
config.alpn?.let {
|
||||||
val alpnIndex = Utils.arrayFind(alpns, it)
|
val alpnIndex = Utils.arrayFind(alpns, it)
|
||||||
sp_stream_alpn?.setSelection(alpnIndex)
|
alpnIndex.let { sp_stream_alpn?.setSelection(if (it >= 0) it else 0) }
|
||||||
}
|
}
|
||||||
if (config.security == TLS) {
|
if (config.security == TLS) {
|
||||||
container_allow_insecure?.visibility = View.VISIBLE
|
container_allow_insecure?.visibility = View.VISIBLE
|
||||||
@@ -513,6 +517,8 @@ class ServerActivity : BaseActivity() {
|
|||||||
config.portHopping = et_port_hop?.text?.toString()
|
config.portHopping = et_port_hop?.text?.toString()
|
||||||
config.portHoppingInterval = et_port_hop_interval?.text?.toString()
|
config.portHoppingInterval = et_port_hop_interval?.text?.toString()
|
||||||
config.pinSHA256 = et_pinsha256?.text?.toString()
|
config.pinSHA256 = et_pinsha256?.text?.toString()
|
||||||
|
config.bandwidthDown = et_bandwidth_down?.text?.toString()
|
||||||
|
config.bandwidthUp = et_bandwidth_up?.text?.toString()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ class SettingsActivity : BaseActivity() {
|
|||||||
private val appendHttpProxy by lazy { findPreference<CheckBoxPreference>(AppConfig.PREF_APPEND_HTTP_PROXY) }
|
private val appendHttpProxy by lazy { findPreference<CheckBoxPreference>(AppConfig.PREF_APPEND_HTTP_PROXY) }
|
||||||
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 vpnBypassLan by lazy { findPreference<ListPreference>(AppConfig.PREF_VPN_BYPASS_LAN) }
|
||||||
|
|
||||||
private val mux by lazy { findPreference<CheckBoxPreference>(AppConfig.PREF_MUX_ENABLED) }
|
private val mux by lazy { findPreference<CheckBoxPreference>(AppConfig.PREF_MUX_ENABLED) }
|
||||||
private val muxConcurrency by lazy { findPreference<EditTextPreference>(AppConfig.PREF_MUX_CONCURRENCY) }
|
private val muxConcurrency by lazy { findPreference<EditTextPreference>(AppConfig.PREF_MUX_CONCURRENCY) }
|
||||||
@@ -246,6 +247,7 @@ class SettingsActivity : BaseActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
listOf(
|
listOf(
|
||||||
|
AppConfig.PREF_VPN_BYPASS_LAN,
|
||||||
AppConfig.PREF_ROUTING_DOMAIN_STRATEGY,
|
AppConfig.PREF_ROUTING_DOMAIN_STRATEGY,
|
||||||
AppConfig.PREF_MUX_XUDP_QUIC,
|
AppConfig.PREF_MUX_XUDP_QUIC,
|
||||||
AppConfig.PREF_FRAGMENT_PACKETS,
|
AppConfig.PREF_FRAGMENT_PACKETS,
|
||||||
@@ -269,6 +271,8 @@ class SettingsActivity : BaseActivity() {
|
|||||||
appendHttpProxy?.isEnabled = vpn
|
appendHttpProxy?.isEnabled = vpn
|
||||||
localDnsPort?.isEnabled = vpn
|
localDnsPort?.isEnabled = vpn
|
||||||
vpnDns?.isEnabled = vpn
|
vpnDns?.isEnabled = vpn
|
||||||
|
vpnBypassLan?.isEnabled = vpn
|
||||||
|
vpn
|
||||||
if (vpn) {
|
if (vpn) {
|
||||||
updateLocalDns(
|
updateLocalDns(
|
||||||
MmkvManager.decodeSettingsBool(
|
MmkvManager.decodeSettingsBool(
|
||||||
|
|||||||
@@ -4,10 +4,15 @@ import android.content.Intent
|
|||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
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.handler.AngConfigManager
|
import com.v2ray.ang.handler.AngConfigManager
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
import java.net.URLDecoder
|
import java.net.URLDecoder
|
||||||
|
|
||||||
class UrlSchemeActivity : BaseActivity() {
|
class UrlSchemeActivity : BaseActivity() {
|
||||||
@@ -66,11 +71,15 @@ class UrlSchemeActivity : BaseActivity() {
|
|||||||
decodedUrl += "#${fragment}"
|
decodedUrl += "#${fragment}"
|
||||||
}
|
}
|
||||||
Log.d("UrlScheme-decodedUrl", decodedUrl)
|
Log.d("UrlScheme-decodedUrl", decodedUrl)
|
||||||
val (count, countSub) = AngConfigManager.importBatchConfig(decodedUrl, "", false)
|
lifecycleScope.launch(Dispatchers.IO) {
|
||||||
if (count + countSub > 0) {
|
val (count, countSub) = AngConfigManager.importBatchConfig(decodedUrl, "", false)
|
||||||
toast(R.string.import_subscription_success)
|
withContext(Dispatchers.Main) {
|
||||||
} else {
|
if (count + countSub > 0) {
|
||||||
toast(R.string.import_subscription_failure)
|
toast(R.string.import_subscription_success)
|
||||||
|
} else {
|
||||||
|
toast(R.string.import_subscription_failure)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ import androidx.activity.result.contract.ActivityResultContracts
|
|||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import com.tbruyelle.rxpermissions3.RxPermissions
|
|
||||||
import com.v2ray.ang.AppConfig
|
import com.v2ray.ang.AppConfig
|
||||||
import com.v2ray.ang.AppConfig.LOOPBACK
|
import com.v2ray.ang.AppConfig.LOOPBACK
|
||||||
import com.v2ray.ang.R
|
import com.v2ray.ang.R
|
||||||
@@ -50,6 +49,38 @@ class UserAssetActivity : BaseActivity() {
|
|||||||
val extDir by lazy { File(Utils.userAssetPath(this)) }
|
val extDir by lazy { File(Utils.userAssetPath(this)) }
|
||||||
val builtInGeoFiles = arrayOf("geosite.dat", "geoip.dat")
|
val builtInGeoFiles = arrayOf("geosite.dat", "geoip.dat")
|
||||||
|
|
||||||
|
private val requestStoragePermissionLauncher = registerForActivityResult(
|
||||||
|
ActivityResultContracts.RequestPermission()
|
||||||
|
) { isGranted: Boolean ->
|
||||||
|
if (isGranted) {
|
||||||
|
val intent = Intent(Intent.ACTION_GET_CONTENT)
|
||||||
|
intent.type = "*/*"
|
||||||
|
intent.addCategory(Intent.CATEGORY_OPENABLE)
|
||||||
|
|
||||||
|
try {
|
||||||
|
chooseFile.launch(
|
||||||
|
Intent.createChooser(
|
||||||
|
intent,
|
||||||
|
getString(R.string.title_file_chooser)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
} catch (ex: android.content.ActivityNotFoundException) {
|
||||||
|
toast(R.string.toast_require_file_manager)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
toast(R.string.toast_permission_denied)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private val requestCameraPermissionLauncher = registerForActivityResult(
|
||||||
|
ActivityResultContracts.RequestPermission()
|
||||||
|
) { isGranted: Boolean ->
|
||||||
|
if (isGranted) {
|
||||||
|
scanQRCodeForAssetURL.launch(Intent(this, ScannerActivity::class.java))
|
||||||
|
} else {
|
||||||
|
toast(R.string.toast_permission_denied)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
@@ -86,27 +117,7 @@ class UserAssetActivity : BaseActivity() {
|
|||||||
} else {
|
} else {
|
||||||
Manifest.permission.READ_EXTERNAL_STORAGE
|
Manifest.permission.READ_EXTERNAL_STORAGE
|
||||||
}
|
}
|
||||||
RxPermissions(this)
|
requestStoragePermissionLauncher.launch(permission)
|
||||||
.request(permission)
|
|
||||||
.subscribe {
|
|
||||||
if (it) {
|
|
||||||
val intent = Intent(Intent.ACTION_GET_CONTENT)
|
|
||||||
intent.type = "*/*"
|
|
||||||
intent.addCategory(Intent.CATEGORY_OPENABLE)
|
|
||||||
|
|
||||||
try {
|
|
||||||
chooseFile.launch(
|
|
||||||
Intent.createChooser(
|
|
||||||
intent,
|
|
||||||
getString(R.string.title_file_chooser)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
} catch (ex: android.content.ActivityNotFoundException) {
|
|
||||||
toast(R.string.toast_require_file_manager)
|
|
||||||
}
|
|
||||||
} else
|
|
||||||
toast(R.string.toast_permission_denied)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val chooseFile = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
|
val chooseFile = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
|
||||||
@@ -158,14 +169,7 @@ class UserAssetActivity : BaseActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun importAssetFromQRcode(): Boolean {
|
private fun importAssetFromQRcode(): Boolean {
|
||||||
RxPermissions(this)
|
requestCameraPermissionLauncher.launch(Manifest.permission.CAMERA)
|
||||||
.request(Manifest.permission.CAMERA)
|
|
||||||
.subscribe {
|
|
||||||
if (it)
|
|
||||||
scanQRCodeForAssetURL.launch(Intent(this, ScannerActivity::class.java))
|
|
||||||
else
|
|
||||||
toast(R.string.toast_permission_denied)
|
|
||||||
}
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -349,4 +353,4 @@ class UserAssetActivity : BaseActivity() {
|
|||||||
|
|
||||||
class UserAssetViewHolder(val itemUserAssetBinding: ItemRecyclerUserAssetBinding) :
|
class UserAssetViewHolder(val itemUserAssetBinding: ItemRecyclerUserAssetBinding) :
|
||||||
RecyclerView.ViewHolder(itemUserAssetBinding.root)
|
RecyclerView.ViewHolder(itemUserAssetBinding.root)
|
||||||
}
|
}
|
||||||
@@ -4,36 +4,27 @@ import android.content.Context
|
|||||||
import android.content.pm.ApplicationInfo
|
import android.content.pm.ApplicationInfo
|
||||||
import android.content.pm.PackageManager
|
import android.content.pm.PackageManager
|
||||||
import com.v2ray.ang.dto.AppInfo
|
import com.v2ray.ang.dto.AppInfo
|
||||||
import io.reactivex.rxjava3.core.Observable
|
import kotlinx.coroutines.withContext
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
|
||||||
object AppManagerUtil {
|
object AppManagerUtil {
|
||||||
private fun loadNetworkAppList(ctx: Context): ArrayList<AppInfo> {
|
suspend fun loadNetworkAppList(context: Context): ArrayList<AppInfo> =
|
||||||
val packageManager = ctx.packageManager
|
withContext(Dispatchers.IO) {
|
||||||
val packages = packageManager.getInstalledPackages(PackageManager.GET_PERMISSIONS)
|
val packageManager = context.packageManager
|
||||||
val apps = ArrayList<AppInfo>()
|
val packages = packageManager.getInstalledPackages(PackageManager.GET_PERMISSIONS)
|
||||||
|
val apps = ArrayList<AppInfo>()
|
||||||
|
|
||||||
for (pkg in packages) {
|
for (pkg in packages) {
|
||||||
val applicationInfo = pkg.applicationInfo ?: continue
|
val applicationInfo = pkg.applicationInfo ?: continue
|
||||||
|
|
||||||
val appName = applicationInfo.loadLabel(packageManager).toString()
|
val appName = applicationInfo.loadLabel(packageManager).toString()
|
||||||
val appIcon = applicationInfo.loadIcon(packageManager) ?: continue
|
val appIcon = applicationInfo.loadIcon(packageManager) ?: continue
|
||||||
val isSystemApp = (applicationInfo.flags and ApplicationInfo.FLAG_SYSTEM) > 0
|
val isSystemApp = (applicationInfo.flags and ApplicationInfo.FLAG_SYSTEM) > 0
|
||||||
|
|
||||||
val appInfo = AppInfo(appName, pkg.packageName, appIcon, isSystemApp, 0)
|
val appInfo = AppInfo(appName, pkg.packageName, appIcon, isSystemApp, 0)
|
||||||
apps.add(appInfo)
|
apps.add(appInfo)
|
||||||
|
}
|
||||||
|
|
||||||
|
return@withContext apps
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return apps
|
|
||||||
}
|
|
||||||
|
|
||||||
fun rxLoadNetworkAppList(ctx: Context): Observable<ArrayList<AppInfo>> =
|
|
||||||
Observable.unsafeCreate {
|
|
||||||
it.onNext(loadNetworkAppList(ctx))
|
|
||||||
}
|
|
||||||
|
|
||||||
// val PackageInfo.hasInternetPermission: Boolean
|
|
||||||
// get() {
|
|
||||||
// val permissions = requestedPermissions
|
|
||||||
// return permissions?.any { it == Manifest.permission.INTERNET } ?: false
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
@@ -29,6 +29,7 @@ class SettingsViewModel(application: Application) : AndroidViewModel(application
|
|||||||
when (key) {
|
when (key) {
|
||||||
AppConfig.PREF_MODE,
|
AppConfig.PREF_MODE,
|
||||||
AppConfig.PREF_VPN_DNS,
|
AppConfig.PREF_VPN_DNS,
|
||||||
|
AppConfig.PREF_VPN_BYPASS_LAN,
|
||||||
AppConfig.PREF_REMOTE_DNS,
|
AppConfig.PREF_REMOTE_DNS,
|
||||||
AppConfig.PREF_DOMESTIC_DNS,
|
AppConfig.PREF_DOMESTIC_DNS,
|
||||||
AppConfig.PREF_DNS_HOSTS,
|
AppConfig.PREF_DNS_HOSTS,
|
||||||
|
|||||||
@@ -77,6 +77,7 @@
|
|||||||
|
|
||||||
<androidx.recyclerview.widget.RecyclerView
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
android:id="@+id/recycler_view"
|
android:id="@+id/recycler_view"
|
||||||
|
android:scrollbars="vertical"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
||||||
|
|||||||
@@ -1,40 +1,26 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:fitsSystemWindows="true"
|
android:fitsSystemWindows="true"
|
||||||
android:paddingStart="@dimen/padding_start"
|
|
||||||
android:paddingEnd="@dimen/padding_end"
|
|
||||||
tools:context=".ui.LogcatActivity">
|
tools:context=".ui.LogcatActivity">
|
||||||
|
|
||||||
<ProgressBar
|
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
||||||
android:id="@+id/pb_waiting"
|
android:id="@+id/refreshLayout"
|
||||||
style="@android:style/Widget.DeviceDefault.ProgressBar"
|
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="match_parent">
|
||||||
android:layout_centerHorizontal="true"
|
|
||||||
android:layout_centerVertical="true" />
|
|
||||||
|
|
||||||
<ScrollView
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
android:id="@+id/sv_logcat"
|
android:id="@+id/recycler_view"
|
||||||
android:layout_width="match_parent"
|
android:scrollbars="vertical"
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_alignParentStart="true"
|
|
||||||
android:layout_alignParentTop="true"
|
|
||||||
android:layout_alignParentEnd="true"
|
|
||||||
android:layout_alignParentBottom="true"
|
|
||||||
android:fillViewport="false"
|
|
||||||
android:foregroundGravity="bottom">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/tv_logcat"
|
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="match_parent"
|
||||||
android:gravity="bottom"
|
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" />
|
||||||
android:maxLines="65536"
|
|
||||||
android:textAppearance="?android:attr/textAppearanceSmall" />
|
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
|
||||||
</ScrollView>
|
|
||||||
</RelativeLayout>
|
</RelativeLayout>
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -54,6 +54,7 @@
|
|||||||
|
|
||||||
<androidx.recyclerview.widget.RecyclerView
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
android:id="@+id/recycler_view"
|
android:id="@+id/recycler_view"
|
||||||
|
android:scrollbars="vertical"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="0dp"
|
android:layout_height="0dp"
|
||||||
android:layout_weight="1"
|
android:layout_weight="1"
|
||||||
|
|||||||
@@ -90,6 +90,44 @@
|
|||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="@dimen/layout_margin_top_height"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/server_lab_bandwidth_down" />
|
||||||
|
|
||||||
|
<EditText
|
||||||
|
android:id="@+id/et_bandwidth_down"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="@dimen/edit_height"
|
||||||
|
android:inputType="text" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="@dimen/layout_margin_top_height"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/server_lab_bandwidth_up" />
|
||||||
|
|
||||||
|
<EditText
|
||||||
|
android:id="@+id/et_bandwidth_up"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="@dimen/edit_height"
|
||||||
|
android:inputType="text" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
<include layout="@layout/layout_tls_hysteria2" />
|
<include layout="@layout/layout_tls_hysteria2" />
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
@@ -16,18 +17,16 @@
|
|||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_alignParentEnd="true"
|
android:layout_alignParentEnd="true"
|
||||||
android:layout_alignParentRight="true"
|
|
||||||
android:layout_centerVertical="true"
|
android:layout_centerVertical="true"
|
||||||
android:checked="true" />
|
android:checked="true"
|
||||||
|
app:theme="@style/BrandedSwitch" />
|
||||||
|
|
||||||
<androidx.appcompat.widget.AppCompatTextView
|
<androidx.appcompat.widget.AppCompatTextView
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_alignParentStart="true"
|
android:layout_alignParentStart="true"
|
||||||
android:layout_alignParentLeft="true"
|
|
||||||
android:layout_centerVertical="true"
|
android:layout_centerVertical="true"
|
||||||
android:layout_toStartOf="@id/switch_start_service"
|
android:layout_toStartOf="@id/switch_start_service"
|
||||||
android:layout_toLeftOf="@id/switch_start_service"
|
|
||||||
android:text="@string/tasker_start_service"
|
android:text="@string/tasker_start_service"
|
||||||
android:textAppearance="@style/TextAppearance.AppCompat.Medium" />
|
android:textAppearance="@style/TextAppearance.AppCompat.Medium" />
|
||||||
|
|
||||||
|
|||||||
24
V2rayNG/app/src/main/res/layout/item_recycler_logcat.xml
Normal file
24
V2rayNG/app/src/main/res/layout/item_recycler_logcat.xml
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:background="?attr/selectableItemBackground"
|
||||||
|
android:clickable="true"
|
||||||
|
android:focusable="true"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_margin="@dimen/layout_margin_spacing">
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.AppCompatTextView
|
||||||
|
android:id="@+id/log_content"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textAppearance="@style/TextAppearance.AppCompat.Small" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
@@ -44,70 +44,76 @@
|
|||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginTop="@dimen/layout_margin_spacing"
|
android:layout_marginTop="@dimen/layout_margin_spacing"
|
||||||
android:lines="1"
|
android:lines="2"
|
||||||
android:textAppearance="@style/TextAppearance.AppCompat.Small" />
|
android:textAppearance="@style/TextAppearance.AppCompat.Small" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="@dimen/sub_height"
|
||||||
|
android:gravity="center"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:paddingStart="@dimen/padding_start"
|
||||||
|
android:paddingEnd="@dimen/padding_end">
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginTop="@dimen/layout_margin_spacing"
|
android:gravity="center"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/layout_share"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:background="?attr/selectableItemBackgroundBorderless"
|
||||||
|
android:clickable="true"
|
||||||
|
android:focusable="true"
|
||||||
|
android:gravity="center"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:padding="@dimen/layout_margin_spacing">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:layout_width="@dimen/png_height"
|
||||||
|
android:layout_height="@dimen/png_height"
|
||||||
|
app:srcCompat="@drawable/ic_share_24dp" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/layout_edit"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:background="?attr/selectableItemBackgroundBorderless"
|
||||||
|
android:clickable="true"
|
||||||
|
android:focusable="true"
|
||||||
|
android:gravity="center"
|
||||||
|
android:nextFocusLeft="@+id/info_container"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:padding="@dimen/layout_margin_spacing">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:layout_width="@dimen/png_height"
|
||||||
|
android:layout_height="@dimen/png_height"
|
||||||
|
app:srcCompat="@drawable/ic_edit_24dp" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="@dimen/layout_margin_top_height"
|
||||||
android:orientation="horizontal">
|
android:orientation="horizontal">
|
||||||
|
|
||||||
<androidx.appcompat.widget.SwitchCompat
|
<androidx.appcompat.widget.SwitchCompat
|
||||||
android:id="@+id/chk_enable"
|
android:id="@+id/chk_enable"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:text="@string/sub_setting_enable"
|
|
||||||
app:theme="@style/BrandedSwitch" />
|
app:theme="@style/BrandedSwitch" />
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="@dimen/sub_height"
|
|
||||||
android:gravity="center"
|
|
||||||
android:orientation="horizontal">
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:id="@+id/layout_share"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:background="?attr/selectableItemBackgroundBorderless"
|
|
||||||
android:clickable="true"
|
|
||||||
android:focusable="true"
|
|
||||||
android:gravity="center"
|
|
||||||
android:orientation="vertical"
|
|
||||||
android:padding="@dimen/layout_margin_spacing">
|
|
||||||
|
|
||||||
<ImageView
|
|
||||||
android:layout_width="@dimen/png_height"
|
|
||||||
android:layout_height="@dimen/png_height"
|
|
||||||
app:srcCompat="@drawable/ic_share_24dp" />
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:id="@+id/layout_edit"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:background="?attr/selectableItemBackgroundBorderless"
|
|
||||||
android:clickable="true"
|
|
||||||
android:focusable="true"
|
|
||||||
android:gravity="center"
|
|
||||||
android:nextFocusLeft="@+id/info_container"
|
|
||||||
android:orientation="vertical"
|
|
||||||
android:padding="@dimen/layout_margin_spacing">
|
|
||||||
|
|
||||||
<ImageView
|
|
||||||
android:layout_width="@dimen/png_height"
|
|
||||||
android:layout_height="@dimen/png_height"
|
|
||||||
app:srcCompat="@drawable/ic_edit_24dp" />
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
</androidx.cardview.widget.CardView>
|
</androidx.cardview.widget.CardView>
|
||||||
|
|||||||
@@ -5,10 +5,10 @@
|
|||||||
android:id="@+id/del_config"
|
android:id="@+id/del_config"
|
||||||
android:icon="@drawable/ic_delete_24dp"
|
android:icon="@drawable/ic_delete_24dp"
|
||||||
android:title="@string/menu_item_del_config"
|
android:title="@string/menu_item_del_config"
|
||||||
app:showAsAction="always" />
|
app:showAsAction="ifRoom" />
|
||||||
<item
|
<item
|
||||||
android:id="@+id/save_config"
|
android:id="@+id/save_config"
|
||||||
android:icon="@drawable/ic_action_done"
|
android:icon="@drawable/ic_action_done"
|
||||||
android:title="@string/menu_item_save_config"
|
android:title="@string/menu_item_save_config"
|
||||||
app:showAsAction="always" />
|
app:showAsAction="ifRoom" />
|
||||||
</menu>
|
</menu>
|
||||||
@@ -5,10 +5,10 @@
|
|||||||
android:id="@+id/add_config"
|
android:id="@+id/add_config"
|
||||||
android:icon="@drawable/ic_add_24dp"
|
android:icon="@drawable/ic_add_24dp"
|
||||||
android:title="@string/menu_item_add_config"
|
android:title="@string/menu_item_add_config"
|
||||||
app:showAsAction="always" />
|
app:showAsAction="ifRoom" />
|
||||||
<item
|
<item
|
||||||
android:id="@+id/sub_update"
|
android:id="@+id/sub_update"
|
||||||
android:icon="@drawable/ic_restore_24dp"
|
android:icon="@drawable/ic_restore_24dp"
|
||||||
android:title="@string/title_sub_update"
|
android:title="@string/title_sub_update"
|
||||||
app:showAsAction="always" />
|
app:showAsAction="ifRoom" />
|
||||||
</menu>
|
</menu>
|
||||||
@@ -24,5 +24,5 @@
|
|||||||
android:id="@+id/download_file"
|
android:id="@+id/download_file"
|
||||||
android:icon="@drawable/ic_cloud_download_24dp"
|
android:icon="@drawable/ic_cloud_download_24dp"
|
||||||
android:title="@string/menu_item_download_file"
|
android:title="@string/menu_item_download_file"
|
||||||
app:showAsAction="always" />
|
app:showAsAction="ifRoom" />
|
||||||
</menu>
|
</menu>
|
||||||
@@ -6,7 +6,7 @@
|
|||||||
android:icon="@drawable/ic_description_24dp"
|
android:icon="@drawable/ic_description_24dp"
|
||||||
android:title="@string/menu_item_search"
|
android:title="@string/menu_item_search"
|
||||||
app:actionViewClass="androidx.appcompat.widget.SearchView"
|
app:actionViewClass="androidx.appcompat.widget.SearchView"
|
||||||
app:showAsAction="always" />
|
app:showAsAction="ifRoom" />
|
||||||
<item
|
<item
|
||||||
android:id="@+id/select_all"
|
android:id="@+id/select_all"
|
||||||
android:icon="@drawable/ic_select_all_24dp"
|
android:icon="@drawable/ic_select_all_24dp"
|
||||||
|
|||||||
@@ -1,14 +1,20 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||||
|
<item
|
||||||
|
android:id="@+id/search_view"
|
||||||
|
android:icon="@drawable/ic_outline_filter_alt_24"
|
||||||
|
android:title="@string/menu_item_search"
|
||||||
|
app:actionViewClass="androidx.appcompat.widget.SearchView"
|
||||||
|
app:showAsAction="always|collapseActionView" />
|
||||||
<item
|
<item
|
||||||
android:id="@+id/clear_all"
|
android:id="@+id/clear_all"
|
||||||
android:icon="@drawable/ic_delete_24dp"
|
android:icon="@drawable/ic_delete_24dp"
|
||||||
android:title="@string/logcat_clear"
|
android:title="@string/logcat_clear"
|
||||||
app:showAsAction="always" />
|
app:showAsAction="ifRoom" />
|
||||||
<item
|
<item
|
||||||
android:id="@+id/copy_all"
|
android:id="@+id/copy_all"
|
||||||
android:icon="@drawable/ic_copy"
|
android:icon="@drawable/ic_copy"
|
||||||
android:title="@string/logcat_copy"
|
android:title="@string/logcat_copy"
|
||||||
app:showAsAction="always" />
|
app:showAsAction="ifRoom" />
|
||||||
</menu>
|
</menu>
|
||||||
@@ -55,7 +55,7 @@
|
|||||||
|
|
||||||
<item
|
<item
|
||||||
android:title="@string/menu_item_import_config_custom"
|
android:title="@string/menu_item_import_config_custom"
|
||||||
app:showAsAction="always">
|
app:showAsAction="ifRoom">
|
||||||
<menu>
|
<menu>
|
||||||
<item
|
<item
|
||||||
android:id="@+id/import_config_custom_clipboard"
|
android:id="@+id/import_config_custom_clipboard"
|
||||||
|
|||||||
@@ -5,10 +5,10 @@
|
|||||||
android:id="@+id/scan_code"
|
android:id="@+id/scan_code"
|
||||||
android:icon="@drawable/ic_scan_24dp"
|
android:icon="@drawable/ic_scan_24dp"
|
||||||
android:title=""
|
android:title=""
|
||||||
app:showAsAction="always" />
|
app:showAsAction="ifRoom" />
|
||||||
<item
|
<item
|
||||||
android:id="@+id/select_photo"
|
android:id="@+id/select_photo"
|
||||||
android:icon="@drawable/ic_image_24dp"
|
android:icon="@drawable/ic_image_24dp"
|
||||||
android:title=""
|
android:title=""
|
||||||
app:showAsAction="always" />
|
app:showAsAction="ifRoom" />
|
||||||
</menu>
|
</menu>
|
||||||
@@ -8,6 +8,7 @@
|
|||||||
<string name="navigation_drawer_close">إغلاق درج التنقل</string>
|
<string name="navigation_drawer_close">إغلاق درج التنقل</string>
|
||||||
<string name="migration_success">نجحت عملية ترحيل البيانات!</string>
|
<string name="migration_success">نجحت عملية ترحيل البيانات!</string>
|
||||||
<string name="migration_fail">فشلت عملية ترحيل البيانات!</string>
|
<string name="migration_fail">فشلت عملية ترحيل البيانات!</string>
|
||||||
|
<string name="pull_down_to_refresh">Please pull down to refresh!</string>
|
||||||
|
|
||||||
<!-- Notifications -->
|
<!-- Notifications -->
|
||||||
<string name="notification_action_stop_v2ray">إيقاف</string>
|
<string name="notification_action_stop_v2ray">إيقاف</string>
|
||||||
@@ -120,6 +121,8 @@
|
|||||||
<string name="server_lab_port_hop">Port Hopping</string>
|
<string name="server_lab_port_hop">Port Hopping</string>
|
||||||
<string name="server_lab_port_hop_interval">Port Hopping Interval</string>
|
<string name="server_lab_port_hop_interval">Port Hopping Interval</string>
|
||||||
<string name="server_lab_stream_pinsha256">pinSHA256</string>
|
<string name="server_lab_stream_pinsha256">pinSHA256</string>
|
||||||
|
<string name="server_lab_bandwidth_down">Bandwidth down (units)</string>
|
||||||
|
<string name="server_lab_bandwidth_up">Bandwidth up (units)</string>
|
||||||
<string name="server_lab_xhttp_mode">XHTTP Mode</string>
|
<string name="server_lab_xhttp_mode">XHTTP Mode</string>
|
||||||
<string name="server_lab_xhttp_extra">XHTTP Extra raw JSON, format: { XHTTPObject }</string>
|
<string name="server_lab_xhttp_extra">XHTTP Extra raw JSON, format: { XHTTPObject }</string>
|
||||||
|
|
||||||
@@ -176,6 +179,7 @@
|
|||||||
<string name="summary_pref_remote_dns">DNS</string>
|
<string name="summary_pref_remote_dns">DNS</string>
|
||||||
|
|
||||||
<string name="title_pref_vpn_dns">VPN DNS (IPv4/v6 فقط)</string>
|
<string name="title_pref_vpn_dns">VPN DNS (IPv4/v6 فقط)</string>
|
||||||
|
<string name="title_pref_vpn_bypass_lan">Does VPN bypass LAN</string>
|
||||||
|
|
||||||
<string name="title_pref_domestic_dns">DNS المحلي (اختياري)</string>
|
<string name="title_pref_domestic_dns">DNS المحلي (اختياري)</string>
|
||||||
<string name="summary_pref_domestic_dns">DNS</string>
|
<string name="summary_pref_domestic_dns">DNS</string>
|
||||||
@@ -323,4 +327,10 @@
|
|||||||
<item>داكن</item>
|
<item>داكن</item>
|
||||||
</string-array>
|
</string-array>
|
||||||
|
|
||||||
|
<string-array name="vpn_bypass_lan">
|
||||||
|
<item>Follow config</item>
|
||||||
|
<item>Bypass</item>
|
||||||
|
<item>Not Bypass</item>
|
||||||
|
</string-array>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
<string name="navigation_drawer_close">নেভিগেশন ড্রয়ার বন্ধ করুন</string>
|
<string name="navigation_drawer_close">নেভিগেশন ড্রয়ার বন্ধ করুন</string>
|
||||||
<string name="migration_success">ডেটা স্থানান্তর সফল!</string>
|
<string name="migration_success">ডেটা স্থানান্তর সফল!</string>
|
||||||
<string name="migration_fail">ডেটা স্থানান্তর ব্যর্থ!</string>
|
<string name="migration_fail">ডেটা স্থানান্তর ব্যর্থ!</string>
|
||||||
|
<string name="pull_down_to_refresh">Please pull down to refresh!</string>
|
||||||
|
|
||||||
<!-- Notifications -->
|
<!-- Notifications -->
|
||||||
<string name="notification_action_stop_v2ray">বন্ধ করুন</string>
|
<string name="notification_action_stop_v2ray">বন্ধ করুন</string>
|
||||||
@@ -119,6 +120,8 @@
|
|||||||
<string name="server_lab_port_hop">Port Hopping</string>
|
<string name="server_lab_port_hop">Port Hopping</string>
|
||||||
<string name="server_lab_port_hop_interval">Port Hopping Interval</string>
|
<string name="server_lab_port_hop_interval">Port Hopping Interval</string>
|
||||||
<string name="server_lab_stream_pinsha256">pinSHA256</string>
|
<string name="server_lab_stream_pinsha256">pinSHA256</string>
|
||||||
|
<string name="server_lab_bandwidth_down">Bandwidth down (units)</string>
|
||||||
|
<string name="server_lab_bandwidth_up">Bandwidth up (units)</string>
|
||||||
<string name="server_lab_xhttp_mode">XHTTP Mode</string>
|
<string name="server_lab_xhttp_mode">XHTTP Mode</string>
|
||||||
<string name="server_lab_xhttp_extra">XHTTP Extra raw JSON, format: { XHTTPObject }</string>
|
<string name="server_lab_xhttp_extra">XHTTP Extra raw JSON, format: { XHTTPObject }</string>
|
||||||
|
|
||||||
@@ -176,6 +179,7 @@
|
|||||||
<string name="summary_pref_remote_dns">DNS</string>
|
<string name="summary_pref_remote_dns">DNS</string>
|
||||||
|
|
||||||
<string name="title_pref_vpn_dns">VPN DNS (শুধুমাত্র IPv4/v6)</string>
|
<string name="title_pref_vpn_dns">VPN DNS (শুধুমাত্র IPv4/v6)</string>
|
||||||
|
<string name="title_pref_vpn_bypass_lan">Does VPN bypass LAN</string>
|
||||||
|
|
||||||
<string name="title_pref_domestic_dns">ঘরোয়া DNS (ঐচ্ছিক)</string>
|
<string name="title_pref_domestic_dns">ঘরোয়া DNS (ঐচ্ছিক)</string>
|
||||||
<string name="summary_pref_domestic_dns">DNS</string>
|
<string name="summary_pref_domestic_dns">DNS</string>
|
||||||
@@ -327,5 +331,10 @@
|
|||||||
<item>ইরান হোয়াইটলিস্ট</item>
|
<item>ইরান হোয়াইটলিস্ট</item>
|
||||||
</string-array>
|
</string-array>
|
||||||
|
|
||||||
|
<string-array name="vpn_bypass_lan">
|
||||||
|
<item>Follow config</item>
|
||||||
|
<item>Bypass</item>
|
||||||
|
<item>Not Bypass</item>
|
||||||
|
</string-array>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
@@ -8,6 +8,7 @@
|
|||||||
<string name="navigation_drawer_close">بستن نومگه کشاری</string>
|
<string name="navigation_drawer_close">بستن نومگه کشاری</string>
|
||||||
<string name="migration_success">مووفقیت من جاگورویی داده</string>
|
<string name="migration_success">مووفقیت من جاگورویی داده</string>
|
||||||
<string name="migration_fail">جاگورویی داده ٱنجوم نگرؽڌ</string>
|
<string name="migration_fail">جاگورویی داده ٱنجوم نگرؽڌ</string>
|
||||||
|
<string name="pull_down_to_refresh">Please pull down to refresh!</string>
|
||||||
|
|
||||||
<!-- Notifications -->
|
<!-- Notifications -->
|
||||||
<string name="notification_action_stop_v2ray">واڌاشتن</string>
|
<string name="notification_action_stop_v2ray">واڌاشتن</string>
|
||||||
@@ -40,16 +41,16 @@
|
|||||||
<string name="menu_item_import_config_custom_url">کانفیگ سفارشین ز آدرس اینترنتی و من بیار</string>
|
<string name="menu_item_import_config_custom_url">کانفیگ سفارشین ز آدرس اینترنتی و من بیار</string>
|
||||||
<string name="menu_item_import_config_custom_url_scan">نشۊوی اینترنتی اسکن کانفیگ سفارشین بزݩ</string>
|
<string name="menu_item_import_config_custom_url_scan">نشۊوی اینترنتی اسکن کانفیگ سفارشین بزݩ</string>
|
||||||
<string name="del_config_comfirm">پاک بۊ؟</string>
|
<string name="del_config_comfirm">پاک بۊ؟</string>
|
||||||
<string name="del_invalid_config_comfirm">پؽش ز پاک کردن کانفیگ نا موئتبر قوۊل کوݩ! پاک کردن کانفیگن قوۊل اکۊنی؟</string>
|
<string name="del_invalid_config_comfirm">پؽش ز پاک کردن کانفیگ نا موئتبر واجۊری کوݩ! پاک کردن کانفیگن قوۊل اکۊنی؟</string>
|
||||||
<string name="server_lab_remarks">نیشتنا</string>
|
<string name="server_lab_remarks">نیشتنا</string>
|
||||||
<string name="server_lab_address">آدرس</string>
|
<string name="server_lab_address">آدرس</string>
|
||||||
<string name="server_lab_port">پورت</string>
|
<string name="server_lab_port">پورت</string>
|
||||||
<string name="server_lab_id">نوم من توری</string>
|
<string name="server_lab_id">نوم من توری</string>
|
||||||
<string name="server_lab_alterid">alterId</string>
|
<string name="server_lab_alterid">شناسه جایگۊزین</string>
|
||||||
<string name="server_lab_security">ٱمنیت</string>
|
<string name="server_lab_security">ٱمنیت</string>
|
||||||
<string name="server_lab_network">شبکه</string>
|
<string name="server_lab_network">شبکه</string>
|
||||||
<string name="server_lab_more_function">جاگورو</string>
|
<string name="server_lab_more_function">جاگورو</string>
|
||||||
<string name="server_lab_head_type">نوء head</string>
|
<string name="server_lab_head_type">نوء سر بلگ</string>
|
||||||
<string name="server_lab_mode_type">هالت gRPC</string>
|
<string name="server_lab_mode_type">هالت gRPC</string>
|
||||||
<string name="server_lab_request_host">هاست</string>
|
<string name="server_lab_request_host">هاست</string>
|
||||||
<string name="server_lab_request_host_http">هاست http</string>
|
<string name="server_lab_request_host_http">هاست http</string>
|
||||||
@@ -58,14 +59,14 @@
|
|||||||
<string name="server_lab_request_host_xhttp">هاست xhttp</string>
|
<string name="server_lab_request_host_xhttp">هاست xhttp</string>
|
||||||
<string name="server_lab_request_host_h2">هاست h2</string>
|
<string name="server_lab_request_host_h2">هاست h2</string>
|
||||||
<string name="server_lab_request_host_quic">ٱمنیت QUIC</string>
|
<string name="server_lab_request_host_quic">ٱمنیت QUIC</string>
|
||||||
<string name="server_lab_request_host_grpc">اختیار gRPC</string>
|
<string name="server_lab_request_host_grpc">Authority gRPC</string>
|
||||||
<string name="server_lab_path">تور</string>
|
<string name="server_lab_path">تور</string>
|
||||||
<string name="server_lab_path_ws">تور ws</string>
|
<string name="server_lab_path_ws">تور WS</string>
|
||||||
<string name="server_lab_path_httpupgrade">تور httpupgrade</string>
|
<string name="server_lab_path_httpupgrade">تور HTTPUpgrade</string>
|
||||||
<string name="server_lab_path_xhttp">تور xhttp</string>
|
<string name="server_lab_path_xhttp">تور XHTTP</string>
|
||||||
<string name="server_lab_path_h2">تور h2</string>
|
<string name="server_lab_path_h2">تور H2</string>
|
||||||
<string name="server_lab_path_quic">کیلیت QUIC</string>
|
<string name="server_lab_path_quic">تور QUIC</string>
|
||||||
<string name="server_lab_path_kcp">سید kcp</string>
|
<string name="server_lab_path_kcp">KCP seed</string>
|
||||||
<string name="server_lab_path_grpc">نوم خدمات gRPC</string>
|
<string name="server_lab_path_grpc">نوم خدمات gRPC</string>
|
||||||
<string name="server_lab_stream_security">TLS</string>
|
<string name="server_lab_stream_security">TLS</string>
|
||||||
<string name="server_lab_stream_fingerprint" translatable="false">Fingerprint</string>
|
<string name="server_lab_stream_fingerprint" translatable="false">Fingerprint</string>
|
||||||
@@ -82,12 +83,12 @@
|
|||||||
<string name="server_lab_flow">جریان</string>
|
<string name="server_lab_flow">جریان</string>
|
||||||
<string name="server_lab_public_key">کیلیت پوی وولاتی</string>
|
<string name="server_lab_public_key">کیلیت پوی وولاتی</string>
|
||||||
<string name="server_lab_preshared_key">کیلیت رمز ناهاڌن ازاف (اختیاری)</string>
|
<string name="server_lab_preshared_key">کیلیت رمز ناهاڌن ازاف (اختیاری)</string>
|
||||||
<string name="server_lab_short_id">ShortId</string>
|
<string name="server_lab_short_id">ShortID</string>
|
||||||
<string name="server_lab_spider_x">SpiderX</string>
|
<string name="server_lab_spider_x">SpiderX</string>
|
||||||
<string name="server_lab_secret_key">کیلیت سیخومی</string>
|
<string name="server_lab_secret_key">کیلیت سیخومی</string>
|
||||||
<string name="server_lab_reserved">Reserved(اختیاری، وا کاما ز یک جوڌا ابۊن)</string>
|
<string name="server_lab_reserved">Reserved(اختیاری، وا کاما ز یک جوڌا ابۊن)</string>
|
||||||
<string name="server_lab_local_address">آدرس مهلی (اختیاری IPv4/IPv6، وا کاما ز یک جوڌا ابۊن)</string>
|
<string name="server_lab_local_address">آدرس مهلی (اختیاری IPv4/IPv6، وا کاما ز یک جوڌا ابۊن)</string>
|
||||||
<string name="server_lab_local_mtu">Mtu(اختیاری، خوتکار 1420)</string>
|
<string name="server_lab_local_mtu">Mtu(اختیاری، پؽش فرز 1420)</string>
|
||||||
<string name="toast_success">وا مووفقیت ٱنجوم وابی</string>
|
<string name="toast_success">وا مووفقیت ٱنجوم وابی</string>
|
||||||
<string name="toast_failure">شکست خرد</string>
|
<string name="toast_failure">شکست خرد</string>
|
||||||
<string name="toast_none_data">هیچ داده ای وۊجۊڌ نڌاره</string>
|
<string name="toast_none_data">هیچ داده ای وۊجۊڌ نڌاره</string>
|
||||||
@@ -107,7 +108,7 @@
|
|||||||
<string name="toast_asset_copy_failed">لف گیری فایل ٱنجوم نوابی، ز ی برنومه دؽوۉداری فایل هیاری بگرین</string>
|
<string name="toast_asset_copy_failed">لف گیری فایل ٱنجوم نوابی، ز ی برنومه دؽوۉداری فایل هیاری بگرین</string>
|
||||||
<string name="menu_item_add_asset">ازاف کردن دارایی</string>
|
<string name="menu_item_add_asset">ازاف کردن دارایی</string>
|
||||||
<string name="menu_item_add_file">ازاف کردن فایل</string>
|
<string name="menu_item_add_file">ازاف کردن فایل</string>
|
||||||
<string name="menu_item_add_url">ازاف کردن آدرس اینترنتی</string>
|
<string name="menu_item_add_url">ازاف کردن لینگ</string>
|
||||||
<string name="menu_item_scan_qrcode">اسکن QRcode</string>
|
<string name="menu_item_scan_qrcode">اسکن QRcode</string>
|
||||||
<string name="title_url">آدرس اینترنتی</string>
|
<string name="title_url">آدرس اینترنتی</string>
|
||||||
<string name="menu_item_download_file">دانلود فایلا</string>
|
<string name="menu_item_download_file">دانلود فایلا</string>
|
||||||
@@ -115,10 +116,12 @@
|
|||||||
<string name="msg_file_not_found">فایلن نجوست</string>
|
<string name="msg_file_not_found">فایلن نجوست</string>
|
||||||
<string name="msg_remark_is_duplicate">ائزارات ز زیتر بیڌسۉݩ</string>
|
<string name="msg_remark_is_duplicate">ائزارات ز زیتر بیڌسۉݩ</string>
|
||||||
<string name="toast_action_not_allowed">ای کار ممنۊ هڌ</string>
|
<string name="toast_action_not_allowed">ای کار ممنۊ هڌ</string>
|
||||||
<string name="server_obfs_password">رزم Obfs</string>
|
<string name="server_obfs_password">رزم obfs</string>
|
||||||
<string name="server_lab_port_hop">پورت گوم (درگا سرورن ز نۊ هؽل اکونه)</string>
|
<string name="server_lab_port_hop">پورت گوم (درگا سرورن ز نۊ هؽل اکونه)</string>
|
||||||
<string name="server_lab_port_hop_interval">فاسله پورت گوم (سانیه)</string>
|
<string name="server_lab_port_hop_interval">فاسله پورت گوم (سانیه)</string>
|
||||||
<string name="server_lab_stream_pinsha256">pinSHA256</string>
|
<string name="server_lab_stream_pinsha256">pinSHA256</string>
|
||||||
|
<string name="server_lab_bandwidth_down">ب لم ٱووڌن پئنا باند (واهڌ)</string>
|
||||||
|
<string name="server_lab_bandwidth_up">وا روء رئڌن پئنا باند (واهڌ)</string>
|
||||||
<string name="server_lab_xhttp_mode">هالت XHTTP</string>
|
<string name="server_lab_xhttp_mode">هالت XHTTP</string>
|
||||||
<string name="server_lab_xhttp_extra">XHTTP Extra خام JSON، قالوو: { XHTTPObject }</string>
|
<string name="server_lab_xhttp_extra">XHTTP Extra خام JSON، قالوو: { XHTTPObject }</string>
|
||||||
|
|
||||||
@@ -176,6 +179,7 @@
|
|||||||
<string name="summary_pref_remote_dns">DNS</string>
|
<string name="summary_pref_remote_dns">DNS</string>
|
||||||
|
|
||||||
<string name="title_pref_vpn_dns">VPN DNS (تینا IPv4/v6)</string>
|
<string name="title_pref_vpn_dns">VPN DNS (تینا IPv4/v6)</string>
|
||||||
|
<string name="title_pref_vpn_bypass_lan">VPN ز شبکه مهلی اگوڌرته؟</string>
|
||||||
|
|
||||||
<string name="title_pref_domestic_dns">DNS منی (اختیاری)</string>
|
<string name="title_pref_domestic_dns">DNS منی (اختیاری)</string>
|
||||||
<string name="summary_pref_domestic_dns">DNS</string>
|
<string name="summary_pref_domestic_dns">DNS</string>
|
||||||
@@ -186,12 +190,12 @@
|
|||||||
<string name="title_pref_delay_test_url">آدرس اینترنتی آزمایش تئخیر واقعی (http/https)</string>
|
<string name="title_pref_delay_test_url">آدرس اینترنتی آزمایش تئخیر واقعی (http/https)</string>
|
||||||
<string name="summary_pref_delay_test_url">نشۊوی اینترنتی</string>
|
<string name="summary_pref_delay_test_url">نشۊوی اینترنتی</string>
|
||||||
|
|
||||||
<string name="title_pref_proxy_sharing_enabled">هشتن منپیزا ز LAN</string>
|
<string name="title_pref_proxy_sharing_enabled">هشتن منپیزا ز شبکه مهلی</string>
|
||||||
<string name="summary_pref_proxy_sharing_enabled">پوی دسگایل ترن وا آدرس IP ایسا، ز ر socks/http و پروکسی منپیز بۊن، تینا من شبکه قابل اعتماد فعال بۊ تا ز منپیز ؛یر موجاز جلو گری بۊ.</string>
|
<string name="summary_pref_proxy_sharing_enabled">پوی دسگایل ترن وا آدرس IP ایسا، ز ر socks/http و پروکسی منپیز بۊن، تینا من شبکه قابل اعتماد فعال بۊ تا ز منپیز غیر موجاز جلو گری بۊ.</string>
|
||||||
<string name="toast_warning_pref_proxysharing_short">منپیزا ز LAN ن موجار کۊنین، موتمعن بۊین ک من ی شبکه قابل ائتماڌ هڌین.</string>
|
<string name="toast_warning_pref_proxysharing_short">منپیزا ز شبکه مهلی ن موجار کۊنین، موتمعن بۊین ک من ی شبکه قابل ائتماڌ هڌین.</string>
|
||||||
|
|
||||||
<string name="title_pref_allow_insecure">اجازه نا ٱمن</string>
|
<string name="title_pref_allow_insecure">اجازه نا ٱمن</string>
|
||||||
<string name="summary_pref_allow_insecure">مجال و کار بردن TLS ب تۉر پؽش فرز، موجوز نا ٱمن فعال هڌ.</string>
|
<string name="summary_pref_allow_insecure">مجال و کار بوردن TLS ب تۉر پؽش فرز، موجوز نا ٱمن فعال هڌ.</string>
|
||||||
|
|
||||||
<string name="title_pref_socks_port">پورت پروکسی مهلی</string>
|
<string name="title_pref_socks_port">پورت پروکسی مهلی</string>
|
||||||
<string name="summary_pref_socks_port">پورت پروکسی مهلی</string>
|
<string name="summary_pref_socks_port">پورت پروکسی مهلی</string>
|
||||||
@@ -231,7 +235,7 @@
|
|||||||
|
|
||||||
<string name="title_core_loglevel">سئت داسووا</string>
|
<string name="title_core_loglevel">سئت داسووا</string>
|
||||||
<string name="title_mode">هالت</string>
|
<string name="title_mode">هالت</string>
|
||||||
<string name="title_mode_help">سی هیاری بیشتر ری ای هؽل بزݩ</string>
|
<string name="title_mode_help">سی دووسمندیا وو هیاری بیشتر، ری ای هؽل بزݩ</string>
|
||||||
<string name="title_language">زۉݩ</string>
|
<string name="title_language">زۉݩ</string>
|
||||||
<string name="title_ui_settings">سامووا رابت منتوری</string>
|
<string name="title_ui_settings">سامووا رابت منتوری</string>
|
||||||
<string name="title_pref_ui_mode_night">سامووا هالت رابت منتوری</string>
|
<string name="title_pref_ui_mode_night">سامووا هالت رابت منتوری</string>
|
||||||
@@ -247,12 +251,12 @@
|
|||||||
<string name="title_sub_setting">سامووا جرگه اشتراک</string>
|
<string name="title_sub_setting">سامووا جرگه اشتراک</string>
|
||||||
<string name="sub_setting_remarks">نیشتنا</string>
|
<string name="sub_setting_remarks">نیشتنا</string>
|
||||||
<string name="sub_setting_url">نشۊوی اینترنتی اختیاری</string>
|
<string name="sub_setting_url">نشۊوی اینترنتی اختیاری</string>
|
||||||
<string name="sub_setting_filter">نیشتنا فیلتر مئمۊلی</string>
|
<string name="sub_setting_filter">نوم موستعار فیلتر</string>
|
||||||
<string name="sub_setting_enable">فعال بیڌن ورۊ کردن</string>
|
<string name="sub_setting_enable">فعال بیڌن ورۊ کردن</string>
|
||||||
<string name="sub_auto_update">فعال بیڌن ورۊ کردن خوتکار</string>
|
<string name="sub_auto_update">فعال بیڌن ورۊ کردن خوتکار</string>
|
||||||
<string name="sub_setting_pre_profile">نیشتنا پروکسی پؽشی</string>
|
<string name="sub_setting_pre_profile">نوم موستعار پروکسی دیندایی</string>
|
||||||
<string name="sub_setting_next_profile">نیشتنا پروکسی نیایی</string>
|
<string name="sub_setting_next_profile">نوم موستعار پروکسی نیایی</string>
|
||||||
<string name="sub_setting_pre_profile_tip">نیشتنا هڌسۉݩ وو هرف نارن</string>
|
<string name="sub_setting_pre_profile_tip">موتمعن بۊ ک نوم موستعار هڌس وو جۊرس نی</string>
|
||||||
<string name="title_sub_update">ورۊ کردن اشتراک جرگه سکویی</string>
|
<string name="title_sub_update">ورۊ کردن اشتراک جرگه سکویی</string>
|
||||||
<string name="title_ping_all_server">Tcping کانفیگا جرگه سکویی</string>
|
<string name="title_ping_all_server">Tcping کانفیگا جرگه سکویی</string>
|
||||||
<string name="title_real_ping_all_server">تئخیر واقعی کانفیگا جرگه سکویی</string>
|
<string name="title_real_ping_all_server">تئخیر واقعی کانفیگا جرگه سکویی</string>
|
||||||
@@ -338,4 +342,10 @@
|
|||||||
<item>نومگه اسبؽڌ ایران</item>
|
<item>نومگه اسبؽڌ ایران</item>
|
||||||
</string-array>
|
</string-array>
|
||||||
|
|
||||||
|
<string-array name="vpn_bypass_lan">
|
||||||
|
<item>پؽش فرز کانفیگ</item>
|
||||||
|
<item>دور زیڌه بۊ</item>
|
||||||
|
<item>دور زیڌه نبۊ</item>
|
||||||
|
</string-array>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
<string name="navigation_drawer_close">بستن منو کشویی</string>
|
<string name="navigation_drawer_close">بستن منو کشویی</string>
|
||||||
<string name="migration_success">موفقیت در انتقال داده</string>
|
<string name="migration_success">موفقیت در انتقال داده</string>
|
||||||
<string name="migration_fail">انتقال داده انجام نشد!</string>
|
<string name="migration_fail">انتقال داده انجام نشد!</string>
|
||||||
|
<string name="pull_down_to_refresh">لطفاً برای تازه کردن، پایین بکشید!</string>
|
||||||
|
|
||||||
<!-- Notifications -->
|
<!-- Notifications -->
|
||||||
<string name="notification_action_stop_v2ray">توقف</string>
|
<string name="notification_action_stop_v2ray">توقف</string>
|
||||||
@@ -40,36 +41,36 @@
|
|||||||
<string name="menu_item_import_config_custom_url">کانفیگ سفارشی را از طریق نشانی اینترنتی وارد کنید</string>
|
<string name="menu_item_import_config_custom_url">کانفیگ سفارشی را از طریق نشانی اینترنتی وارد کنید</string>
|
||||||
<string name="menu_item_import_config_custom_url_scan">نشانی اینترنتی اسکن کانفیگ سفارشی را وارد کنید</string>
|
<string name="menu_item_import_config_custom_url_scan">نشانی اینترنتی اسکن کانفیگ سفارشی را وارد کنید</string>
|
||||||
<string name="del_config_comfirm">حذف شود؟</string>
|
<string name="del_config_comfirm">حذف شود؟</string>
|
||||||
<string name="del_invalid_config_comfirm">لطفا قبل از حذف کانفیگ نامعتبر تایید کنید! حذف کانفیگ را تایید می کنید؟</string>
|
<string name="del_invalid_config_comfirm">لطفا قبل از حذف کانفیگ نامعتبر بررسی کنید! حذف کانفیگ را تایید می کنید؟</string>
|
||||||
<string name="server_lab_remarks">ملاحظات</string>
|
<string name="server_lab_remarks">ملاحظات</string>
|
||||||
<string name="server_lab_address">نشانی</string>
|
<string name="server_lab_address">نشانی</string>
|
||||||
<string name="server_lab_port">پورت</string>
|
<string name="server_lab_port">پورت</string>
|
||||||
<string name="server_lab_id">شناسه</string>
|
<string name="server_lab_id">شناسه</string>
|
||||||
<string name="server_lab_alterid">alterId</string>
|
<string name="server_lab_alterid">شناسه جایگزین</string>
|
||||||
<string name="server_lab_security">امنیت</string>
|
<string name="server_lab_security">امنیت</string>
|
||||||
<string name="server_lab_network">شبکه</string>
|
<string name="server_lab_network">شبکه</string>
|
||||||
<string name="server_lab_more_function">انتقال</string>
|
<string name="server_lab_more_function">انتقال</string>
|
||||||
<string name="server_lab_head_type">نوع HEAD</string>
|
<string name="server_lab_head_type">نوع سربرگ</string>
|
||||||
<string name="server_lab_mode_type">حالت GRPC</string>
|
<string name="server_lab_mode_type">حالت gRPC</string>
|
||||||
<string name="server_lab_request_host">HOST</string>
|
<string name="server_lab_request_host">هاست</string>
|
||||||
<string name="server_lab_request_host_http">HTTP HOST</string>
|
<string name="server_lab_request_host_http">هاست HTTP</string>
|
||||||
<string name="server_lab_request_host_ws">WS HOST</string>
|
<string name="server_lab_request_host_ws">هاست WS</string>
|
||||||
<string name="server_lab_request_host_httpupgrade">HTTPUPGRADE HOST</string>
|
<string name="server_lab_request_host_httpupgrade">هاست HTTPUpgrade</string>
|
||||||
<string name="server_lab_request_host_xhttp">XHTTP HOST</string>
|
<string name="server_lab_request_host_xhttp">هاست XHTTP</string>
|
||||||
<string name="server_lab_request_host_h2">H2 HOST</string>
|
<string name="server_lab_request_host_h2">هاست H2</string>
|
||||||
<string name="server_lab_request_host_quic">QUIC security</string>
|
<string name="server_lab_request_host_quic">QUIC security</string>
|
||||||
<string name="server_lab_request_host_grpc">GRPC Authority</string>
|
<string name="server_lab_request_host_grpc">gRPC Authority</string>
|
||||||
<string name="server_lab_path">PATH</string>
|
<string name="server_lab_path">مسیر</string>
|
||||||
<string name="server_lab_path_ws">WS PATH</string>
|
<string name="server_lab_path_ws">مسیر WS</string>
|
||||||
<string name="server_lab_path_httpupgrade">HTTPUPGRADE PATH</string>
|
<string name="server_lab_path_httpupgrade">مسیر HTTPUpgrade</string>
|
||||||
<string name="server_lab_path_xhttp">XHTTP PATH</string>
|
<string name="server_lab_path_xhttp">مسیر XHTTP</string>
|
||||||
<string name="server_lab_path_h2">H2 PATH</string>
|
<string name="server_lab_path_h2">مسیر H2</string>
|
||||||
<string name="server_lab_path_quic">QUIC key</string>
|
<string name="server_lab_path_quic">مسیر QUIC</string>
|
||||||
<string name="server_lab_path_kcp">KCP SEED</string>
|
<string name="server_lab_path_kcp">KCP seed</string>
|
||||||
<string name="server_lab_path_grpc">GRPC SERVICENAME</string>
|
<string name="server_lab_path_grpc">gRPC ServiceName</string>
|
||||||
<string name="server_lab_stream_security">TLS</string>
|
<string name="server_lab_stream_security">TLS</string>
|
||||||
<string name="server_lab_stream_fingerprint">اثرانگشت</string>
|
<string name="server_lab_stream_fingerprint">اثرانگشت</string>
|
||||||
<string name="server_lab_stream_alpn">AlPN</string>
|
<string name="server_lab_stream_alpn">Alpn</string>
|
||||||
<string name="server_lab_allow_insecure">اعطای مجوز ناامن</string>
|
<string name="server_lab_allow_insecure">اعطای مجوز ناامن</string>
|
||||||
<string name="server_lab_sni">SNI</string>
|
<string name="server_lab_sni">SNI</string>
|
||||||
<string name="server_lab_address3">نشانی</string>
|
<string name="server_lab_address3">نشانی</string>
|
||||||
@@ -82,12 +83,12 @@
|
|||||||
<string name="server_lab_flow">جریان</string>
|
<string name="server_lab_flow">جریان</string>
|
||||||
<string name="server_lab_public_key">کلید عمومی</string>
|
<string name="server_lab_public_key">کلید عمومی</string>
|
||||||
<string name="server_lab_preshared_key">کلید رمزگذاری اضافی (اختیاری)</string>
|
<string name="server_lab_preshared_key">کلید رمزگذاری اضافی (اختیاری)</string>
|
||||||
<string name="server_lab_short_id">SHORTID</string>
|
<string name="server_lab_short_id">ShortID</string>
|
||||||
<string name="server_lab_spider_x">SPIDERX</string>
|
<string name="server_lab_spider_x">SpiderX</string>
|
||||||
<string name="server_lab_secret_key">کلید خصوصی</string>
|
<string name="server_lab_secret_key">کلید خصوصی</string>
|
||||||
<string name="server_lab_reserved">Reserved (اختیاری)</string>
|
<string name="server_lab_reserved">Reserved (اختیاری، جدا شده با کاما)</string>
|
||||||
<string name="server_lab_local_address">آدرس محلی IPV4(اختیاری)</string>
|
<string name="server_lab_local_address">آدرس محلی IPV4 (اختیاری)</string>
|
||||||
<string name="server_lab_local_mtu">MTU (اختیاری، پیش فرض: 1420)</string>
|
<string name="server_lab_local_mtu">MTU (اختیاری، پیشفرض 1420)</string>
|
||||||
<string name="toast_success">با موفقیت انجام شد</string>
|
<string name="toast_success">با موفقیت انجام شد</string>
|
||||||
<string name="toast_failure">شکست</string>
|
<string name="toast_failure">شکست</string>
|
||||||
<string name="toast_none_data">هیچ داده ای وجود ندارد</string>
|
<string name="toast_none_data">هیچ داده ای وجود ندارد</string>
|
||||||
@@ -103,17 +104,19 @@
|
|||||||
<string name="toast_insecure_url_protocol">لطفاً از آدرس اشتراک پروتکل HTTP ناامن استفاده نکنید</string>
|
<string name="toast_insecure_url_protocol">لطفاً از آدرس اشتراک پروتکل HTTP ناامن استفاده نکنید</string>
|
||||||
<string name="server_lab_need_inbound">اطمینان حاصل کنید که پورت ورودی با تنظیمات مطابقت دارد</string>
|
<string name="server_lab_need_inbound">اطمینان حاصل کنید که پورت ورودی با تنظیمات مطابقت دارد</string>
|
||||||
<string name="toast_malformed_josn">کانفیگ درست نیست</string>
|
<string name="toast_malformed_josn">کانفیگ درست نیست</string>
|
||||||
<string name="server_lab_request_host6">میزبان (SNI) (اختیاری)</string>
|
<string name="server_lab_request_host6">هاست (SNI) (اختیاری)</string>
|
||||||
<string name="toast_asset_copy_failed">کپی فایل انجام نشد، لطفا از برنامه مدیریت فایل استفاده کنید</string>
|
<string name="toast_asset_copy_failed">کپی فایل انجام نشد، لطفا از برنامه مدیریت فایل استفاده کنید</string>
|
||||||
<string name="menu_item_add_file">افزودن فایل ها</string>
|
<string name="menu_item_add_file">افزودن فایل ها</string>
|
||||||
<string name="menu_item_scan_qrcode">اسکن QRcode</string>
|
<string name="menu_item_scan_qrcode">اسکن QRcode</string>
|
||||||
<string name="title_url">URL</string>
|
<string name="title_url">URL</string>
|
||||||
<string name="menu_item_download_file">دانلود فایل ها</string>
|
<string name="menu_item_download_file">دانلود فایل ها</string>
|
||||||
<string name="toast_action_not_allowed">این عمل ممنوع است</string>
|
<string name="toast_action_not_allowed">این عمل ممنوع است</string>
|
||||||
<string name="server_obfs_password">رمز عبور OBFS</string>
|
<string name="server_obfs_password">رمز عبور obfs</string>
|
||||||
<string name="server_lab_port_hop">پورت پرش (درگاه سرور را بازنویسی می کند)</string>
|
<string name="server_lab_port_hop">پورت پرش (درگاه سرور را بازنویسی می کند)</string>
|
||||||
<string name="server_lab_port_hop_interval">فاصله پورت پرش (ثانیه)</string>
|
<string name="server_lab_port_hop_interval">فاصله پورت پرش (ثانیه)</string>
|
||||||
<string name="server_lab_stream_pinsha256">PINSHA256</string>
|
<string name="server_lab_stream_pinsha256">pinSHA256</string>
|
||||||
|
<string name="server_lab_bandwidth_down">کاهش پهنای باند (واحد)</string>
|
||||||
|
<string name="server_lab_bandwidth_up">افزایش پهنای باند (واحد)</string>
|
||||||
<string name="server_lab_xhttp_mode">حالت XHTTP</string>
|
<string name="server_lab_xhttp_mode">حالت XHTTP</string>
|
||||||
<string name="server_lab_xhttp_extra">خام JSON XHTTP Extra، قالب: { XHTTPObject }</string>
|
<string name="server_lab_xhttp_extra">خام JSON XHTTP Extra، قالب: { XHTTPObject }</string>
|
||||||
|
|
||||||
@@ -125,7 +128,7 @@
|
|||||||
<string name="menu_item_search">جستجو</string>
|
<string name="menu_item_search">جستجو</string>
|
||||||
<string name="menu_item_select_all">انتخاب همه</string>
|
<string name="menu_item_select_all">انتخاب همه</string>
|
||||||
<string name="msg_enter_keywords">کلیدواژه ها را وارد کنید</string>
|
<string name="msg_enter_keywords">کلیدواژه ها را وارد کنید</string>
|
||||||
<string name="switch_bypass_apps_mode">حالت Bypass</string>
|
<string name="switch_bypass_apps_mode">حالت دور زدن</string>
|
||||||
<string name="menu_item_select_proxy_app">انتخاب خودکار پروکسی برنامه</string>
|
<string name="menu_item_select_proxy_app">انتخاب خودکار پروکسی برنامه</string>
|
||||||
<string name="msg_downloading_content">در حال دانلود محتوا</string>
|
<string name="msg_downloading_content">در حال دانلود محتوا</string>
|
||||||
<string name="menu_item_export_proxy_app">خروجی گرفتن در کلیپ بورد</string>
|
<string name="menu_item_export_proxy_app">خروجی گرفتن در کلیپ بورد</string>
|
||||||
@@ -136,7 +139,7 @@
|
|||||||
<string name="title_advanced">تنظیمات پیشرفته</string>
|
<string name="title_advanced">تنظیمات پیشرفته</string>
|
||||||
<string name="title_vpn_settings">تنظیمات VPN</string>
|
<string name="title_vpn_settings">تنظیمات VPN</string>
|
||||||
<string name="title_pref_per_app_proxy">پروکسی به تفکیک برنامه</string>
|
<string name="title_pref_per_app_proxy">پروکسی به تفکیک برنامه</string>
|
||||||
<string name="summary_pref_per_app_proxy">عمومی: برنامه بررسی شده پروکسی است، اتصال مستقیم بدون بررسی است. \nحالت bypass: برنامه بررسی شده مستقیما متصل است، پراکسی بررسی نشده است. \nگزینهای برای انتخاب خودکار پروکسی برنامه در منو است.</string>
|
<string name="summary_pref_per_app_proxy">عمومی: برنامه بررسی شده پروکسی است، اتصال مستقیم بدون بررسی است. \nحالت دور زدن: برنامه بررسی شده مستقیما متصل است، پراکسی بررسی نشده است. \nگزینهای برای انتخاب خودکار پروکسی برنامه در منو است.</string>
|
||||||
<string name="title_pref_is_booted">اتصال خودکار هنگام راه اندازی</string>
|
<string name="title_pref_is_booted">اتصال خودکار هنگام راه اندازی</string>
|
||||||
<string name="summary_pref_is_booted">هنگام راه اندازی به طور خودکار به سرور انتخابی متصل می شود که ممکن است ناموفق باشد.</string>
|
<string name="summary_pref_is_booted">هنگام راه اندازی به طور خودکار به سرور انتخابی متصل می شود که ممکن است ناموفق باشد.</string>
|
||||||
|
|
||||||
@@ -153,12 +156,12 @@
|
|||||||
</string-array>
|
</string-array>
|
||||||
|
|
||||||
<string name="title_pref_speed_enabled">فعال کردن نمایش سرعت</string>
|
<string name="title_pref_speed_enabled">فعال کردن نمایش سرعت</string>
|
||||||
<string name="summary_pref_speed_enabled">نمایش سرعت فعلی در قسمت آگاهسازی. \nآیکون آگاهسازی بر اساس استفاده تغییر میکند.</string>
|
<string name="summary_pref_speed_enabled">نمایش سرعت فعلی در قسمت اعلان. \nآیکون اعلان بر اساس استفاده تغییر میکند.</string>
|
||||||
|
|
||||||
<string name="title_pref_sniffing_enabled">فعال کردن SNIFFING</string>
|
<string name="title_pref_sniffing_enabled">فعال کردن تجزیه و تحلیل بسته ها (Sniffing)</string>
|
||||||
<string name="summary_pref_sniffing_enabled">دامنه SNIFF را از بسته امتحان کنید (پیشفرض روشن)</string>
|
<string name="summary_pref_sniffing_enabled">استفاده از تشخیص نام دامنه (Sniff) در بسته ها (به طور پیش فرض فعال است)</string>
|
||||||
<string name="title_pref_route_only_enabled">فعال کردن ROUTEONLY</string>
|
<string name="title_pref_route_only_enabled">فعال کردن دامنه فقط مسیر یابی (RouteOnly)</string>
|
||||||
<string name="summary_pref_route_only_enabled">از نام دامنه SNIFFED فقط برای مسیریابی استفاده کنید و آدرس مورد نظر را به عنوان آدرس IP نگه دارید.</string>
|
<string name="summary_pref_route_only_enabled">از نام دامنه (Snnifed) فقط برای مسیریابی استفاده کنید و آدرس مقصد را به عنوان IP ذخیره کنید.</string>
|
||||||
|
|
||||||
|
|
||||||
<string name="title_pref_local_dns_enabled">فعال کردن DNS محلی</string>
|
<string name="title_pref_local_dns_enabled">فعال کردن DNS محلی</string>
|
||||||
@@ -174,19 +177,20 @@
|
|||||||
<string name="summary_pref_remote_dns">DNS</string>
|
<string name="summary_pref_remote_dns">DNS</string>
|
||||||
|
|
||||||
<string name="title_pref_vpn_dns">VPN DNS (فقط IPv4/v6)</string>
|
<string name="title_pref_vpn_dns">VPN DNS (فقط IPv4/v6)</string>
|
||||||
|
<string name="title_pref_vpn_bypass_lan">آیا VPN از شبکه محلی عبور می کند؟</string>
|
||||||
|
|
||||||
<string name="title_pref_domestic_dns">DNS داخلی (اختیاری)</string>
|
<string name="title_pref_domestic_dns">DNS داخلی (اختیاری)</string>
|
||||||
<string name="summary_pref_domestic_dns">DNS</string>
|
<string name="summary_pref_domestic_dns">DNS</string>
|
||||||
|
|
||||||
<string name="title_pref_dns_hosts">DNS مستقیم هاست(فرمت: دامنه: آدرس،…)</string>
|
<string name="title_pref_dns_hosts">DNS مستقیم هاست (فرمت: دامنه:آدرس،…)</string>
|
||||||
<string name="summary_pref_dns_hosts">دامنه: آدرس، …</string>
|
<string name="summary_pref_dns_hosts">دامنه:آدرس،…</string>
|
||||||
|
|
||||||
<string name="title_pref_delay_test_url">آدرس اینترنتی آزمایش تاخیر واقعی کانفیگ ها (HTTP/HTTPS)</string>
|
<string name="title_pref_delay_test_url">آدرس اینترنتی آزمایش تاخیر واقعی کانفیگ ها (HTTP/HTTPS)</string>
|
||||||
<string name="summary_pref_delay_test_url">URL</string>
|
<string name="summary_pref_delay_test_url">URL</string>
|
||||||
|
|
||||||
<string name="title_pref_proxy_sharing_enabled">اجازه اتصالات از طریق LAN</string>
|
<string name="title_pref_proxy_sharing_enabled">اجازه اتصالات از طریق شبکه محلی</string>
|
||||||
<string name="summary_pref_proxy_sharing_enabled">سایر دستگاهها میتوانند با آدرس آیپی شما از طریق پراکسی محلی به پروکسی متصل شوند، فقط در شبکه قابل اعتماد فعال شود تا از اتصال غیرمجاز جلوگیری شود.</string>
|
<string name="summary_pref_proxy_sharing_enabled">سایر دستگاه ها می توانند با استفاده از آدرس آیپی شما برای استفاده از یک پروکسی محلی متصل شوند. فقط در یک شبکه قابل اعتماد برای جلوگیری از اتصالات غیرمجاز استفاده کنید.</string>
|
||||||
<string name="toast_warning_pref_proxysharing_short">اتصالات از طریق LAN را مجاز کنید، مطمئن شوید که در یک شبکه قابل اعتماد هستید</string>
|
<string name="toast_warning_pref_proxysharing_short">اتصالات از طریق شبکه محلی را مجاز کنید، مطمئن شوید که در یک شبکه قابل اعتماد هستید.</string>
|
||||||
|
|
||||||
<string name="title_pref_allow_insecure">اعطای مجوز ناامن</string>
|
<string name="title_pref_allow_insecure">اعطای مجوز ناامن</string>
|
||||||
<string name="summary_pref_allow_insecure">هنگام استفاده از TLS، به طور پیش فرض مجوز ناامن فعال است.</string>
|
<string name="summary_pref_allow_insecure">هنگام استفاده از TLS، به طور پیش فرض مجوز ناامن فعال است.</string>
|
||||||
@@ -228,7 +232,7 @@
|
|||||||
<string name="title_pref_auto_update_interval">فاصله به روزرسانی خودکار ( حداقل مقدار ، 15 دقیقه )</string>
|
<string name="title_pref_auto_update_interval">فاصله به روزرسانی خودکار ( حداقل مقدار ، 15 دقیقه )</string>
|
||||||
<string name="title_core_loglevel">سطح گزارشات</string>
|
<string name="title_core_loglevel">سطح گزارشات</string>
|
||||||
<string name="title_mode">حالت</string>
|
<string name="title_mode">حالت</string>
|
||||||
<string name="title_mode_help">برای راهنمایی بیشتر روی این متن، کلیک کنید</string>
|
<string name="title_mode_help">برای اطلاعات و راهنمایی بیشتر، روی این متن کلیک کنید</string>
|
||||||
<string name="title_language">زبان</string>
|
<string name="title_language">زبان</string>
|
||||||
<string name="title_ui_settings">تنظیمات رابط کاربری</string>
|
<string name="title_ui_settings">تنظیمات رابط کاربری</string>
|
||||||
<string name="title_pref_ui_mode_night">تنظیمات حالت رابط کاربری</string>
|
<string name="title_pref_ui_mode_night">تنظیمات حالت رابط کاربری</string>
|
||||||
@@ -244,12 +248,12 @@
|
|||||||
<string name="title_sub_setting">تنظیمات گروه اشتراک</string>
|
<string name="title_sub_setting">تنظیمات گروه اشتراک</string>
|
||||||
<string name="sub_setting_remarks">ملاحظات</string>
|
<string name="sub_setting_remarks">ملاحظات</string>
|
||||||
<string name="sub_setting_url">نشانی اینترنتی اختیاری</string>
|
<string name="sub_setting_url">نشانی اینترنتی اختیاری</string>
|
||||||
<string name="sub_setting_filter">REMARKS REGULAR FILTER</string>
|
<string name="sub_setting_filter">نام مستعار فیلتر</string>
|
||||||
<string name="sub_setting_enable">فعال کردن بهروزرسانی</string>
|
<string name="sub_setting_enable">فعال کردن بهروزرسانی</string>
|
||||||
<string name="sub_auto_update">فعال سازی بهروزرسانی خودکار</string>
|
<string name="sub_auto_update">فعال سازی بهروزرسانی خودکار</string>
|
||||||
<string name="sub_setting_pre_profile">Previous proxy remarks</string>
|
<string name="sub_setting_pre_profile">نام مستعار پروکسی قبلی</string>
|
||||||
<string name="sub_setting_next_profile">Next proxy remarks</string>
|
<string name="sub_setting_next_profile">نام مستعار پروکسی بعدی</string>
|
||||||
<string name="sub_setting_pre_profile_tip">The remarks exists and is unique</string>
|
<string name="sub_setting_pre_profile_tip">لطفاً مطمئن شوید که نام مستعار وجود دارد و منحصر به فرد است</string>
|
||||||
<string name="title_sub_update">بهروزرسانی گروه فعلی اشتراک</string>
|
<string name="title_sub_update">بهروزرسانی گروه فعلی اشتراک</string>
|
||||||
<string name="title_ping_all_server">TCPING کانفیگ های گروه فعلی</string>
|
<string name="title_ping_all_server">TCPING کانفیگ های گروه فعلی</string>
|
||||||
<string name="title_real_ping_all_server">تاخیر واقعی کانفیگ های گروه فعلی</string>
|
<string name="title_real_ping_all_server">تاخیر واقعی کانفیگ های گروه فعلی</string>
|
||||||
@@ -336,4 +340,10 @@
|
|||||||
<item>ایران</item>
|
<item>ایران</item>
|
||||||
</string-array>
|
</string-array>
|
||||||
|
|
||||||
|
<string-array name="vpn_bypass_lan">
|
||||||
|
<item>پیش فرض کانفیگ</item>
|
||||||
|
<item>دور زده شود</item>
|
||||||
|
<item>دور زده نشود</item>
|
||||||
|
</string-array>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
<string name="navigation_drawer_close">Закрыть панель навигации</string>
|
<string name="navigation_drawer_close">Закрыть панель навигации</string>
|
||||||
<string name="migration_success">Успешный перенос данных!</string>
|
<string name="migration_success">Успешный перенос данных!</string>
|
||||||
<string name="migration_fail">Перенос данных не выполнен!</string>
|
<string name="migration_fail">Перенос данных не выполнен!</string>
|
||||||
|
<string name="pull_down_to_refresh">Потяните вниз для обновления!</string>
|
||||||
|
|
||||||
<!-- Notifications -->
|
<!-- Notifications -->
|
||||||
<string name="notification_action_stop_v2ray">Остановить</string>
|
<string name="notification_action_stop_v2ray">Остановить</string>
|
||||||
@@ -118,6 +119,8 @@
|
|||||||
<string name="server_lab_port_hop">Смена портов (переопределяет порт)</string>
|
<string name="server_lab_port_hop">Смена портов (переопределяет порт)</string>
|
||||||
<string name="server_lab_port_hop_interval">Интервал смены портов</string>
|
<string name="server_lab_port_hop_interval">Интервал смены портов</string>
|
||||||
<string name="server_lab_stream_pinsha256">pinSHA256</string>
|
<string name="server_lab_stream_pinsha256">pinSHA256</string>
|
||||||
|
<string name="server_lab_bandwidth_down">Входящая пропускная способность (единицы)</string>
|
||||||
|
<string name="server_lab_bandwidth_up">Исходящая пропускная способность (единицы)</string>
|
||||||
<string name="server_lab_xhttp_mode">Режим XHTTP</string>
|
<string name="server_lab_xhttp_mode">Режим XHTTP</string>
|
||||||
<string name="server_lab_xhttp_extra">Необработанный JSON XHTTP Extra, формат: { XHTTPObject }</string>
|
<string name="server_lab_xhttp_extra">Необработанный JSON XHTTP Extra, формат: { XHTTPObject }</string>
|
||||||
|
|
||||||
@@ -175,6 +178,7 @@
|
|||||||
<string name="summary_pref_remote_dns">DNS</string>
|
<string name="summary_pref_remote_dns">DNS</string>
|
||||||
|
|
||||||
<string name="title_pref_vpn_dns">VPN DNS (только IPv4/v6)</string>
|
<string name="title_pref_vpn_dns">VPN DNS (только IPv4/v6)</string>
|
||||||
|
<string name="title_pref_vpn_bypass_lan">VPN пропускает LAN</string>
|
||||||
|
|
||||||
<string name="title_pref_domestic_dns">Внутренняя DNS (необязательно)</string>
|
<string name="title_pref_domestic_dns">Внутренняя DNS (необязательно)</string>
|
||||||
<string name="summary_pref_domestic_dns">DNS</string>
|
<string name="summary_pref_domestic_dns">DNS</string>
|
||||||
@@ -337,4 +341,10 @@
|
|||||||
<item>Белый список Ирана</item>
|
<item>Белый список Ирана</item>
|
||||||
</string-array>
|
</string-array>
|
||||||
|
|
||||||
|
<string-array name="vpn_bypass_lan">
|
||||||
|
<item>Как в профиле</item>
|
||||||
|
<item>Пропускает</item>
|
||||||
|
<item>Не пропускает</item>
|
||||||
|
</string-array>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
<string name="navigation_drawer_close">Đóng Menu ứng dụng</string>
|
<string name="navigation_drawer_close">Đóng Menu ứng dụng</string>
|
||||||
<string name="migration_success">Đã chuyển dữ liệu!</string>
|
<string name="migration_success">Đã chuyển dữ liệu!</string>
|
||||||
<string name="migration_fail">Không thể chuyển dữ liệu!</string>
|
<string name="migration_fail">Không thể chuyển dữ liệu!</string>
|
||||||
|
<string name="pull_down_to_refresh">Please pull down to refresh!</string>
|
||||||
|
|
||||||
<!-- Notifications -->
|
<!-- Notifications -->
|
||||||
<string name="notification_action_stop_v2ray">Ngắt kết nối v2rayNG</string>
|
<string name="notification_action_stop_v2ray">Ngắt kết nối v2rayNG</string>
|
||||||
@@ -113,6 +114,8 @@
|
|||||||
<string name="server_lab_port_hop">Port Hopping</string>
|
<string name="server_lab_port_hop">Port Hopping</string>
|
||||||
<string name="server_lab_port_hop_interval">Port Hopping Interval</string>
|
<string name="server_lab_port_hop_interval">Port Hopping Interval</string>
|
||||||
<string name="server_lab_stream_pinsha256">pinSHA256</string>
|
<string name="server_lab_stream_pinsha256">pinSHA256</string>
|
||||||
|
<string name="server_lab_bandwidth_down">Bandwidth down (units)</string>
|
||||||
|
<string name="server_lab_bandwidth_up">Bandwidth up (units)</string>
|
||||||
<string name="server_lab_xhttp_mode">XHTTP Mode</string>
|
<string name="server_lab_xhttp_mode">XHTTP Mode</string>
|
||||||
<string name="server_lab_xhttp_extra">XHTTP Extra raw JSON, format: { XHTTPObject }</string>
|
<string name="server_lab_xhttp_extra">XHTTP Extra raw JSON, format: { XHTTPObject }</string>
|
||||||
|
|
||||||
@@ -175,6 +178,7 @@
|
|||||||
<string name="summary_pref_remote_dns">DNS</string>
|
<string name="summary_pref_remote_dns">DNS</string>
|
||||||
|
|
||||||
<string name="title_pref_vpn_dns">VPN DNS (Chỉ IPv4 / IPv6)</string>
|
<string name="title_pref_vpn_dns">VPN DNS (Chỉ IPv4 / IPv6)</string>
|
||||||
|
<string name="title_pref_vpn_bypass_lan">Does VPN bypass LAN</string>
|
||||||
|
|
||||||
<string name="title_pref_domestic_dns">DNS nội địa (Không bắt buộc)</string>
|
<string name="title_pref_domestic_dns">DNS nội địa (Không bắt buộc)</string>
|
||||||
<string name="summary_pref_domestic_dns">DNS</string>
|
<string name="summary_pref_domestic_dns">DNS</string>
|
||||||
@@ -324,4 +328,10 @@
|
|||||||
<string name="title_pref_fragment_interval">Fragment Interval (min-max)</string>
|
<string name="title_pref_fragment_interval">Fragment Interval (min-max)</string>
|
||||||
<string name="title_pref_fragment_enabled">Enable Fragment</string>
|
<string name="title_pref_fragment_enabled">Enable Fragment</string>
|
||||||
|
|
||||||
|
<string-array name="vpn_bypass_lan">
|
||||||
|
<item>Follow config</item>
|
||||||
|
<item>Bypass</item>
|
||||||
|
<item>Not Bypass</item>
|
||||||
|
</string-array>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
<string name="navigation_drawer_close">Close navigation drawer</string>
|
<string name="navigation_drawer_close">Close navigation drawer</string>
|
||||||
<string name="migration_success">数据迁移成功!</string>
|
<string name="migration_success">数据迁移成功!</string>
|
||||||
<string name="migration_fail">数据迁移失败啦!</string>
|
<string name="migration_fail">数据迁移失败啦!</string>
|
||||||
|
<string name="pull_down_to_refresh">请下拉刷新!</string>
|
||||||
|
|
||||||
<!-- Notifications -->
|
<!-- Notifications -->
|
||||||
<string name="notification_action_stop_v2ray">停止</string>
|
<string name="notification_action_stop_v2ray">停止</string>
|
||||||
@@ -113,6 +114,8 @@
|
|||||||
<string name="server_lab_port_hop">跳跃端口(会覆盖服务器端口)</string>
|
<string name="server_lab_port_hop">跳跃端口(会覆盖服务器端口)</string>
|
||||||
<string name="server_lab_port_hop_interval">端口跳跃间隔(秒)</string>
|
<string name="server_lab_port_hop_interval">端口跳跃间隔(秒)</string>
|
||||||
<string name="server_lab_stream_pinsha256">SHA256证书指纹</string>
|
<string name="server_lab_stream_pinsha256">SHA256证书指纹</string>
|
||||||
|
<string name="server_lab_bandwidth_down">带宽下行 (单位)</string>
|
||||||
|
<string name="server_lab_bandwidth_up">带宽上行 (单位)</string>
|
||||||
<string name="server_lab_xhttp_mode">XHTTP 模式</string>
|
<string name="server_lab_xhttp_mode">XHTTP 模式</string>
|
||||||
<string name="server_lab_xhttp_extra">XHTTP Extra 原始 JSON,格式: { XHTTPObject }</string>
|
<string name="server_lab_xhttp_extra">XHTTP Extra 原始 JSON,格式: { XHTTPObject }</string>
|
||||||
|
|
||||||
@@ -172,6 +175,7 @@
|
|||||||
<string name="summary_pref_remote_dns">DNS</string>
|
<string name="summary_pref_remote_dns">DNS</string>
|
||||||
|
|
||||||
<string name="title_pref_vpn_dns">VPN DNS (仅支持 IPv4/v6)</string>
|
<string name="title_pref_vpn_dns">VPN DNS (仅支持 IPv4/v6)</string>
|
||||||
|
<string name="title_pref_vpn_bypass_lan">VPN是否绕过局域网</string>
|
||||||
|
|
||||||
<string name="title_pref_domestic_dns">境内DNS (可选)</string>
|
<string name="title_pref_domestic_dns">境内DNS (可选)</string>
|
||||||
<string name="summary_pref_domestic_dns">DNS</string>
|
<string name="summary_pref_domestic_dns">DNS</string>
|
||||||
@@ -328,4 +332,10 @@
|
|||||||
<item>伊朗(Iran)</item>
|
<item>伊朗(Iran)</item>
|
||||||
</string-array>
|
</string-array>
|
||||||
|
|
||||||
|
<string-array name="vpn_bypass_lan">
|
||||||
|
<item>跟随配置文件</item>
|
||||||
|
<item>绕过</item>
|
||||||
|
<item>不绕过</item>
|
||||||
|
</string-array>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
<string name="navigation_drawer_close">關閉導覽匣</string>
|
<string name="navigation_drawer_close">關閉導覽匣</string>
|
||||||
<string name="migration_success">資料遷移成功!</string>
|
<string name="migration_success">資料遷移成功!</string>
|
||||||
<string name="migration_fail">資料遷移失敗!</string>
|
<string name="migration_fail">資料遷移失敗!</string>
|
||||||
|
<string name="pull_down_to_refresh">請下拉刷新!</string>
|
||||||
|
|
||||||
<!-- Notifications -->
|
<!-- Notifications -->
|
||||||
<string name="notification_action_stop_v2ray">停止</string>
|
<string name="notification_action_stop_v2ray">停止</string>
|
||||||
@@ -113,6 +114,8 @@
|
|||||||
<string name="server_lab_port_hop">跳躍連接埠(會覆蓋伺服器連接埠)</string>
|
<string name="server_lab_port_hop">跳躍連接埠(會覆蓋伺服器連接埠)</string>
|
||||||
<string name="server_lab_port_hop_interval">連接埠跳躍間隔(秒)</string>
|
<string name="server_lab_port_hop_interval">連接埠跳躍間隔(秒)</string>
|
||||||
<string name="server_lab_stream_pinsha256">SHA256憑證指紋</string>
|
<string name="server_lab_stream_pinsha256">SHA256憑證指紋</string>
|
||||||
|
<string name="server_lab_bandwidth_down">頻寬下行 (單位)</string>
|
||||||
|
<string name="server_lab_bandwidth_up">頻寬上行 (單位)</string>
|
||||||
<string name="server_lab_xhttp_mode">XHTTP 模式</string>
|
<string name="server_lab_xhttp_mode">XHTTP 模式</string>
|
||||||
<string name="server_lab_xhttp_extra">XHTTP Extra 原始 JSON,格式: { XHTTPObject }</string>
|
<string name="server_lab_xhttp_extra">XHTTP Extra 原始 JSON,格式: { XHTTPObject }</string>
|
||||||
|
|
||||||
@@ -174,6 +177,7 @@
|
|||||||
<string name="summary_pref_remote_dns">DNS</string>
|
<string name="summary_pref_remote_dns">DNS</string>
|
||||||
|
|
||||||
<string name="title_pref_vpn_dns">VPN DNS (僅支援 IPv4/v6)</string>
|
<string name="title_pref_vpn_dns">VPN DNS (僅支援 IPv4/v6)</string>
|
||||||
|
<string name="title_pref_vpn_bypass_lan">VPN是否繞過區域網</string>
|
||||||
|
|
||||||
<string name="title_pref_dns_hosts">DNS hosts (格式: 網域:位址,…)</string>
|
<string name="title_pref_dns_hosts">DNS hosts (格式: 網域:位址,…)</string>
|
||||||
<string name="summary_pref_dns_hosts">domain:address,…</string>
|
<string name="summary_pref_dns_hosts">domain:address,…</string>
|
||||||
@@ -327,4 +331,10 @@
|
|||||||
<item>伊朗(Iran)</item>
|
<item>伊朗(Iran)</item>
|
||||||
</string-array>
|
</string-array>
|
||||||
|
|
||||||
|
<string-array name="vpn_bypass_lan">
|
||||||
|
<item>跟隨設定檔</item>
|
||||||
|
<item>繞過</item>
|
||||||
|
<item>不繞過</item>
|
||||||
|
</string-array>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -211,4 +211,11 @@
|
|||||||
<item>stream-up</item>
|
<item>stream-up</item>
|
||||||
<item>stream-one</item>
|
<item>stream-one</item>
|
||||||
</string-array>
|
</string-array>
|
||||||
|
|
||||||
|
<string-array name="vpn_bypass_lan_value" translatable="false">
|
||||||
|
<item>0</item>
|
||||||
|
<item>1</item>
|
||||||
|
<item>2</item>
|
||||||
|
</string-array>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
@@ -8,6 +8,7 @@
|
|||||||
<string name="navigation_drawer_close">Close navigation drawer</string>
|
<string name="navigation_drawer_close">Close navigation drawer</string>
|
||||||
<string name="migration_success">Data migration success!</string>
|
<string name="migration_success">Data migration success!</string>
|
||||||
<string name="migration_fail">Data migration failed!</string>
|
<string name="migration_fail">Data migration failed!</string>
|
||||||
|
<string name="pull_down_to_refresh">Please pull down to refresh!</string>
|
||||||
|
|
||||||
<!-- Notifications -->
|
<!-- Notifications -->
|
||||||
<string name="notification_action_stop_v2ray">Stop</string>
|
<string name="notification_action_stop_v2ray">Stop</string>
|
||||||
@@ -119,6 +120,8 @@
|
|||||||
<string name="server_lab_port_hop">Port Hopping(will override the port)</string>
|
<string name="server_lab_port_hop">Port Hopping(will override the port)</string>
|
||||||
<string name="server_lab_port_hop_interval">Port Hopping Interval</string>
|
<string name="server_lab_port_hop_interval">Port Hopping Interval</string>
|
||||||
<string name="server_lab_stream_pinsha256">pinSHA256</string>
|
<string name="server_lab_stream_pinsha256">pinSHA256</string>
|
||||||
|
<string name="server_lab_bandwidth_down">Bandwidth down (units)</string>
|
||||||
|
<string name="server_lab_bandwidth_up">Bandwidth up (units)</string>
|
||||||
<string name="server_lab_xhttp_mode">XHTTP Mode</string>
|
<string name="server_lab_xhttp_mode">XHTTP Mode</string>
|
||||||
<string name="server_lab_xhttp_extra">XHTTP Extra raw JSON, format: { XHTTPObject }</string>
|
<string name="server_lab_xhttp_extra">XHTTP Extra raw JSON, format: { XHTTPObject }</string>
|
||||||
|
|
||||||
@@ -178,6 +181,7 @@
|
|||||||
<string name="summary_pref_remote_dns">DNS</string>
|
<string name="summary_pref_remote_dns">DNS</string>
|
||||||
|
|
||||||
<string name="title_pref_vpn_dns">VPN DNS (only IPv4/v6)</string>
|
<string name="title_pref_vpn_dns">VPN DNS (only IPv4/v6)</string>
|
||||||
|
<string name="title_pref_vpn_bypass_lan">Does VPN bypass LAN</string>
|
||||||
|
|
||||||
<string name="title_pref_domestic_dns">Domestic DNS (Optional)</string>
|
<string name="title_pref_domestic_dns">Domestic DNS (Optional)</string>
|
||||||
<string name="summary_pref_domestic_dns">DNS</string>
|
<string name="summary_pref_domestic_dns">DNS</string>
|
||||||
@@ -340,4 +344,10 @@
|
|||||||
<item>Iran Whitelist</item>
|
<item>Iran Whitelist</item>
|
||||||
</string-array>
|
</string-array>
|
||||||
|
|
||||||
|
<string-array name="vpn_bypass_lan">
|
||||||
|
<item>Follow config</item>
|
||||||
|
<item>Bypass</item>
|
||||||
|
<item>Not Bypass</item>
|
||||||
|
</string-array>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -49,6 +49,14 @@
|
|||||||
android:key="pref_vpn_dns"
|
android:key="pref_vpn_dns"
|
||||||
android:summary="@string/summary_pref_remote_dns"
|
android:summary="@string/summary_pref_remote_dns"
|
||||||
android:title="@string/title_pref_vpn_dns" />
|
android:title="@string/title_pref_vpn_dns" />
|
||||||
|
|
||||||
|
<ListPreference
|
||||||
|
android:defaultValue="0"
|
||||||
|
android:entries="@array/vpn_bypass_lan"
|
||||||
|
android:entryValues="@array/vpn_bypass_lan_value"
|
||||||
|
android:key="pref_vpn_bypass_lan"
|
||||||
|
android:summary="%s"
|
||||||
|
android:title="@string/title_pref_vpn_bypass_lan" />
|
||||||
</PreferenceCategory>
|
</PreferenceCategory>
|
||||||
|
|
||||||
<PreferenceCategory android:title="@string/title_ui_settings">
|
<PreferenceCategory android:title="@string/title_ui_settings">
|
||||||
|
|||||||
@@ -1,22 +1,22 @@
|
|||||||
[versions]
|
[versions]
|
||||||
agp = "8.7.2"
|
agp = "8.8.2"
|
||||||
desugar_jdk_libs = "2.1.4"
|
desugar_jdk_libs = "2.1.5"
|
||||||
gradleLicensePlugin = "0.9.8"
|
gradleLicensePlugin = "0.9.8"
|
||||||
kotlin = "2.1.0"
|
kotlin = "2.1.10"
|
||||||
coreKtx = "1.15.0"
|
coreKtx = "1.15.0"
|
||||||
junit = "4.13.2"
|
junit = "4.13.2"
|
||||||
junitVersion = "1.2.1"
|
junitVersion = "1.2.1"
|
||||||
espressoCore = "3.6.1"
|
espressoCore = "3.6.1"
|
||||||
appcompat = "1.7.0"
|
appcompat = "1.7.0"
|
||||||
material = "1.12.0"
|
material = "1.12.0"
|
||||||
activity = "1.9.3"
|
activity = "1.10.1"
|
||||||
constraintlayout = "2.2.0"
|
constraintlayout = "2.2.1"
|
||||||
mmkvStatic = "1.3.11"
|
mmkvStatic = "1.3.12"
|
||||||
gson = "2.11.0"
|
gson = "2.11.0"
|
||||||
quickieFoss = "1.13.1"
|
quickieFoss = "1.13.1"
|
||||||
rxjava = "3.1.9"
|
kotlinx-coroutines-android = "1.10.1"
|
||||||
rxandroid = "3.0.2"
|
kotlinx-coroutines-core = "1.10.1"
|
||||||
rxpermissions = "0.12"
|
swiperefreshlayout = "1.1.0"
|
||||||
toastcompat = "1.1.0"
|
toastcompat = "1.1.0"
|
||||||
editorkit = "2.9.0"
|
editorkit = "2.9.0"
|
||||||
core = "3.5.3"
|
core = "3.5.3"
|
||||||
@@ -26,9 +26,10 @@ multidex = "2.0.1"
|
|||||||
mockitoMockitoInline = "4.0.0"
|
mockitoMockitoInline = "4.0.0"
|
||||||
flexbox = "3.0.0"
|
flexbox = "3.0.0"
|
||||||
preferenceKtx = "1.2.1"
|
preferenceKtx = "1.2.1"
|
||||||
recyclerview = "1.3.2"
|
recyclerview = "1.4.0"
|
||||||
[libraries]
|
[libraries]
|
||||||
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
|
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
|
||||||
|
androidx-swiperefreshlayout = { module = "androidx.swiperefreshlayout:swiperefreshlayout", version.ref = "swiperefreshlayout" }
|
||||||
desugar_jdk_libs = { module = "com.android.tools:desugar_jdk_libs", version.ref = "desugar_jdk_libs" }
|
desugar_jdk_libs = { module = "com.android.tools:desugar_jdk_libs", version.ref = "desugar_jdk_libs" }
|
||||||
gradle-license-plugin = { module = "com.jaredsburrows:gradle-license-plugin", version.ref = "gradleLicensePlugin" }
|
gradle-license-plugin = { module = "com.jaredsburrows:gradle-license-plugin", version.ref = "gradleLicensePlugin" }
|
||||||
junit = { group = "junit", name = "junit", version.ref = "junit" }
|
junit = { group = "junit", name = "junit", version.ref = "junit" }
|
||||||
@@ -41,9 +42,8 @@ androidx-constraintlayout = { group = "androidx.constraintlayout", name = "const
|
|||||||
mmkv-static = { module = "com.tencent:mmkv-static", version.ref = "mmkvStatic" }
|
mmkv-static = { module = "com.tencent:mmkv-static", version.ref = "mmkvStatic" }
|
||||||
gson = { module = "com.google.code.gson:gson", version.ref = "gson" }
|
gson = { module = "com.google.code.gson:gson", version.ref = "gson" }
|
||||||
quickie-foss = { module = "com.github.T8RIN.QuickieExtended:quickie-foss", version.ref = "quickieFoss" }
|
quickie-foss = { module = "com.github.T8RIN.QuickieExtended:quickie-foss", version.ref = "quickieFoss" }
|
||||||
rxjava = { module = "io.reactivex.rxjava3:rxjava", version.ref = "rxjava" }
|
kotlinx-coroutines-android = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-android", version = "kotlinx-coroutines-android" }
|
||||||
rxandroid = { module = "io.reactivex.rxjava3:rxandroid", version.ref = "rxandroid" }
|
kotlinx-coroutines-core = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-core", version = "kotlinx-coroutines-core" }
|
||||||
rxpermissions = { module = "com.github.tbruyelle:rxpermissions", version.ref = "rxpermissions" }
|
|
||||||
toastcompat = { module = "me.drakeet.support:toastcompat", version.ref = "toastcompat" }
|
toastcompat = { module = "me.drakeet.support:toastcompat", version.ref = "toastcompat" }
|
||||||
editorkit = { module = "com.blacksquircle.ui:editorkit", version.ref = "editorkit" }
|
editorkit = { module = "com.blacksquircle.ui:editorkit", version.ref = "editorkit" }
|
||||||
language-base = { module = "com.blacksquircle.ui:language-base", version.ref = "editorkit" }
|
language-base = { module = "com.blacksquircle.ui:language-base", version.ref = "editorkit" }
|
||||||
@@ -64,4 +64,3 @@ preference-ktx = { module = "androidx.preference:preference-ktx", version.ref =
|
|||||||
android-application = { id = "com.android.application", version.ref = "agp" }
|
android-application = { id = "com.android.application", version.ref = "agp" }
|
||||||
kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
|
kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
|
||||||
android-library = { id = "com.android.library", version.ref = "agp" }
|
android-library = { id = "com.android.library", version.ref = "agp" }
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
#Thu Nov 14 12:42:51 BDT 2024
|
#Thu Nov 14 12:42:51 BDT 2024
|
||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.11-bin.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-bin.zip
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
|
|||||||
1
badvpn
Submodule
1
badvpn
Submodule
Submodule badvpn added at e68480088a
33
compile-tun2socks.sh
Normal file
33
compile-tun2socks.sh
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
set -o errexit
|
||||||
|
set -o pipefail
|
||||||
|
set -o nounset
|
||||||
|
# Set magic variables for current file & dir
|
||||||
|
__dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
__file="${__dir}/$(basename "${BASH_SOURCE[0]}")"
|
||||||
|
__base="$(basename ${__file} .sh)"
|
||||||
|
if [[ ! -d $NDK_HOME ]]; then
|
||||||
|
echo "Android NDK: NDK_HOME not found. please set env \$NDK_HOME"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
TMPDIR=$(mktemp -d)
|
||||||
|
clear_tmp () {
|
||||||
|
rm -rf $TMPDIR
|
||||||
|
}
|
||||||
|
trap 'echo -e "Aborted, error $? in command: $BASH_COMMAND"; trap ERR; clear_tmp; exit 1' ERR INT
|
||||||
|
install -m644 $__dir/tun2socks.mk $TMPDIR/
|
||||||
|
pushd $TMPDIR
|
||||||
|
ln -s $__dir/badvpn badvpn
|
||||||
|
ln -s $__dir/libancillary libancillary
|
||||||
|
$NDK_HOME/ndk-build \
|
||||||
|
NDK_PROJECT_PATH=. \
|
||||||
|
APP_BUILD_SCRIPT=./tun2socks.mk \
|
||||||
|
APP_ABI=all \
|
||||||
|
APP_PLATFORM=android-19 \
|
||||||
|
NDK_LIBS_OUT=$TMPDIR/libs \
|
||||||
|
NDK_OUT=$TMPDIR/tmp \
|
||||||
|
APP_SHORT_COMMANDS=false LOCAL_SHORT_COMMANDS=false -B -j4 \
|
||||||
|
LOCAL_LDFLAGS=-Wl,--build-id=none
|
||||||
|
tar cvfz $__dir/libtun2socks.so.tgz libs
|
||||||
|
popd
|
||||||
|
rm -rf $TMPDIR
|
||||||
1
hysteria
Submodule
1
hysteria
Submodule
Submodule hysteria added at 401ed5245d
1
libancillary
Submodule
1
libancillary
Submodule
Submodule libancillary added at 232d69a5eb
20
libhysteria2.sh
Normal file
20
libhysteria2.sh
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
targets=(
|
||||||
|
"aarch64-linux-android21 arm64 arm64-v8a"
|
||||||
|
"armv7a-linux-androideabi21 arm armeabi-v7a"
|
||||||
|
"x86_64-linux-android21 amd64 x86_64"
|
||||||
|
"i686-linux-android21 386 x86"
|
||||||
|
)
|
||||||
|
|
||||||
|
cd "hysteria" || exit
|
||||||
|
|
||||||
|
for target in "${targets[@]}"; do
|
||||||
|
IFS=' ' read -r ndk_target goarch abi <<< "$target"
|
||||||
|
|
||||||
|
echo "Building for ${abi} with ${ndk_target} (${goarch})"
|
||||||
|
|
||||||
|
CC="${ANDROID_NDK_HOME}/toolchains/llvm/prebuilt/linux-x86_64/bin/${ndk_target}-clang" CGO_ENABLED=1 CGO_LDFLAGS="-Wl,-z,max-page-size=16384" GOOS=android GOARCH=$goarch go build -o libs/$abi/libhysteria2.so -trimpath -ldflags "-s -w -buildid=" -buildvcs=false ./app
|
||||||
|
|
||||||
|
echo "Built libhysteria2.so for ${abi}"
|
||||||
|
done
|
||||||
124
tun2socks.mk
Normal file
124
tun2socks.mk
Normal file
@@ -0,0 +1,124 @@
|
|||||||
|
# Copyright (C) 2009 The Android Open Source Project
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
#
|
||||||
|
#
|
||||||
|
LOCAL_PATH := $(call my-dir)
|
||||||
|
ROOT_PATH := $(LOCAL_PATH)
|
||||||
|
########################################################
|
||||||
|
## libancillary
|
||||||
|
########################################################
|
||||||
|
include $(CLEAR_VARS)
|
||||||
|
ANCILLARY_SOURCE := fd_recv.c fd_send.c
|
||||||
|
LOCAL_MODULE := libancillary
|
||||||
|
#LOCAL_CFLAGS += -I$(LOCAL_PATH)/libancillary
|
||||||
|
LOCAL_C_INCLUDES := $(LOCAL_PATH)/libancillary
|
||||||
|
LOCAL_SRC_FILES := $(addprefix libancillary/, $(ANCILLARY_SOURCE))
|
||||||
|
include $(BUILD_STATIC_LIBRARY)
|
||||||
|
########################################################
|
||||||
|
## tun2socks
|
||||||
|
########################################################
|
||||||
|
include $(CLEAR_VARS)
|
||||||
|
LOCAL_CFLAGS := -std=gnu99
|
||||||
|
LOCAL_CFLAGS += -DBADVPN_THREADWORK_USE_PTHREAD -DBADVPN_LINUX -DBADVPN_BREACTOR_BADVPN -D_GNU_SOURCE
|
||||||
|
LOCAL_CFLAGS += -DBADVPN_USE_SIGNALFD -DBADVPN_USE_EPOLL
|
||||||
|
LOCAL_CFLAGS += -DBADVPN_LITTLE_ENDIAN -DBADVPN_THREAD_SAFE
|
||||||
|
LOCAL_CFLAGS += -DNDEBUG -DANDROID
|
||||||
|
LOCAL_CFLAGS += -I
|
||||||
|
LOCAL_STATIC_LIBRARIES := libancillary
|
||||||
|
LOCAL_C_INCLUDES := \
|
||||||
|
$(LOCAL_PATH)/badvpn/libancillary \
|
||||||
|
$(LOCAL_PATH)/badvpn/lwip/src/include/ipv4 \
|
||||||
|
$(LOCAL_PATH)/badvpn/lwip/src/include/ipv6 \
|
||||||
|
$(LOCAL_PATH)/badvpn/lwip/src/include \
|
||||||
|
$(LOCAL_PATH)/badvpn/lwip/custom \
|
||||||
|
$(LOCAL_PATH)/badvpn \
|
||||||
|
$(LOCAL_PATH)/libancillary
|
||||||
|
TUN2SOCKS_SOURCES := \
|
||||||
|
base/BLog_syslog.c \
|
||||||
|
system/BReactor_badvpn.c \
|
||||||
|
system/BSignal.c \
|
||||||
|
system/BConnection_common.c \
|
||||||
|
system/BConnection_unix.c \
|
||||||
|
system/BTime.c \
|
||||||
|
system/BUnixSignal.c \
|
||||||
|
system/BNetwork.c \
|
||||||
|
system/BDatagram_common.c \
|
||||||
|
system/BDatagram_unix.c \
|
||||||
|
flow/StreamRecvInterface.c \
|
||||||
|
flow/PacketRecvInterface.c \
|
||||||
|
flow/PacketPassInterface.c \
|
||||||
|
flow/StreamPassInterface.c \
|
||||||
|
flow/SinglePacketBuffer.c \
|
||||||
|
flow/BufferWriter.c \
|
||||||
|
flow/PacketBuffer.c \
|
||||||
|
flow/PacketStreamSender.c \
|
||||||
|
flow/PacketPassConnector.c \
|
||||||
|
flow/PacketProtoFlow.c \
|
||||||
|
flow/PacketPassFairQueue.c \
|
||||||
|
flow/PacketProtoEncoder.c \
|
||||||
|
flow/PacketProtoDecoder.c \
|
||||||
|
socksclient/BSocksClient.c \
|
||||||
|
tuntap/BTap.c \
|
||||||
|
lwip/src/core/udp.c \
|
||||||
|
lwip/src/core/memp.c \
|
||||||
|
lwip/src/core/init.c \
|
||||||
|
lwip/src/core/pbuf.c \
|
||||||
|
lwip/src/core/tcp.c \
|
||||||
|
lwip/src/core/tcp_out.c \
|
||||||
|
lwip/src/core/netif.c \
|
||||||
|
lwip/src/core/def.c \
|
||||||
|
lwip/src/core/ip.c \
|
||||||
|
lwip/src/core/mem.c \
|
||||||
|
lwip/src/core/tcp_in.c \
|
||||||
|
lwip/src/core/stats.c \
|
||||||
|
lwip/src/core/inet_chksum.c \
|
||||||
|
lwip/src/core/timeouts.c \
|
||||||
|
lwip/src/core/ipv4/icmp.c \
|
||||||
|
lwip/src/core/ipv4/igmp.c \
|
||||||
|
lwip/src/core/ipv4/ip4_addr.c \
|
||||||
|
lwip/src/core/ipv4/ip4_frag.c \
|
||||||
|
lwip/src/core/ipv4/ip4.c \
|
||||||
|
lwip/src/core/ipv4/autoip.c \
|
||||||
|
lwip/src/core/ipv6/ethip6.c \
|
||||||
|
lwip/src/core/ipv6/inet6.c \
|
||||||
|
lwip/src/core/ipv6/ip6_addr.c \
|
||||||
|
lwip/src/core/ipv6/mld6.c \
|
||||||
|
lwip/src/core/ipv6/dhcp6.c \
|
||||||
|
lwip/src/core/ipv6/icmp6.c \
|
||||||
|
lwip/src/core/ipv6/ip6.c \
|
||||||
|
lwip/src/core/ipv6/ip6_frag.c \
|
||||||
|
lwip/src/core/ipv6/nd6.c \
|
||||||
|
lwip/custom/sys.c \
|
||||||
|
tun2socks/tun2socks.c \
|
||||||
|
base/DebugObject.c \
|
||||||
|
base/BLog.c \
|
||||||
|
base/BPending.c \
|
||||||
|
flowextra/PacketPassInactivityMonitor.c \
|
||||||
|
tun2socks/SocksUdpGwClient.c \
|
||||||
|
udpgw_client/UdpGwClient.c \
|
||||||
|
socks_udp_client/SocksUdpClient.c
|
||||||
|
LOCAL_MODULE := tun2socks
|
||||||
|
LOCAL_LDLIBS := -ldl -llog
|
||||||
|
LOCAL_SRC_FILES := $(addprefix badvpn/, $(TUN2SOCKS_SOURCES))
|
||||||
|
LOCAL_BUILD_SCRIPT := BUILD_EXECUTABLE
|
||||||
|
LOCAL_MAKEFILE := $(local-makefile)
|
||||||
|
$(call check-defined-LOCAL_MODULE,$(LOCAL_BUILD_SCRIPT))
|
||||||
|
$(call check-LOCAL_MODULE,$(LOCAL_MAKEFILE))
|
||||||
|
$(call check-LOCAL_MODULE_FILENAME)
|
||||||
|
# we are building target objects
|
||||||
|
my := TARGET_
|
||||||
|
$(call handle-module-filename,lib,$(TARGET_SONAME_EXTENSION))
|
||||||
|
$(call handle-module-built)
|
||||||
|
LOCAL_MODULE_CLASS := EXECUTABLE
|
||||||
|
include $(BUILD_SYSTEM)/build-module.mk
|
||||||
Reference in New Issue
Block a user