mirror of
https://github.com/wgtunnel/android.git
synced 2026-07-03 14:07:49 +02:00
Compare commits
47 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 7c15943a81 | |||
| 05adf7539f | |||
| a9a49e3421 | |||
| 4dd8241fa1 | |||
| 431b2f9061 | |||
| f822292584 | |||
| 4ffc5d4069 | |||
| 680fbed28c | |||
| 737524831b | |||
| b8c36ac192 | |||
| 547686069f | |||
| ac18ac8274 | |||
| cb983da990 | |||
| cfe64dcb61 | |||
| 2db521d510 | |||
| ff6c763b7b | |||
| ebf7521fa1 | |||
| 7a2d96fcd7 | |||
| c6c8047982 | |||
| 9cfb7250de | |||
| 79b5b039b0 | |||
| 29616f8325 | |||
| 8bbe81d294 | |||
| 571fb1b12c | |||
| 02f6f97aa1 | |||
| 1d74d0984e | |||
| 6448386f76 | |||
| d09e85ba45 | |||
| a9bc1cc7f0 | |||
| 54d9653f04 | |||
| efc66821a6 | |||
| 3af7adc45b | |||
| 5754f2183c | |||
| f7f7f1bd9d | |||
| 57bb3f5e74 | |||
| 49196e7c7b | |||
| 894b63e668 | |||
| e16d44ff20 | |||
| b8b3f3001b | |||
| d142ecea6e | |||
| b793984ede | |||
| ae2532afe5 | |||
| 2720a3b35e | |||
| 2350364543 | |||
| f4172cb1fc | |||
| 90c482ae4f | |||
| 1eb8ad62e0 |
@@ -1,124 +0,0 @@
|
|||||||
name: Android CI Tag Deployment (Pre-release)
|
|
||||||
|
|
||||||
on:
|
|
||||||
workflow_dispatch:
|
|
||||||
push:
|
|
||||||
tags:
|
|
||||||
- '*.*.*-**'
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
name: Build Signed APK
|
|
||||||
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
|
|
||||||
env:
|
|
||||||
SIGNING_KEY_ALIAS: ${{ secrets.SIGNING_KEY_ALIAS }}
|
|
||||||
SIGNING_KEY_PASSWORD: ${{ secrets.SIGNING_KEY_PASSWORD }}
|
|
||||||
SIGNING_STORE_PASSWORD: ${{ secrets.SIGNING_STORE_PASSWORD }}
|
|
||||||
KEY_STORE_FILE: 'android_keystore.jks'
|
|
||||||
KEY_STORE_LOCATION: ${{ github.workspace }}/app/keystore/
|
|
||||||
GH_USER: ${{ secrets.GH_USER }}
|
|
||||||
GH_TOKEN: ${{ secrets.GH_TOKEN }}
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
- name: Set up JDK 17
|
|
||||||
uses: actions/setup-java@v4
|
|
||||||
with:
|
|
||||||
distribution: 'temurin'
|
|
||||||
java-version: '17'
|
|
||||||
cache: gradle
|
|
||||||
- name: Grant execute permission for gradlew
|
|
||||||
run: chmod +x gradlew
|
|
||||||
|
|
||||||
# Here we need to decode keystore.jks from base64 string and place it
|
|
||||||
# in the folder specified in the release signing configuration
|
|
||||||
- name: Decode Keystore
|
|
||||||
id: decode_keystore
|
|
||||||
uses: timheuer/base64-to-file@v1.2
|
|
||||||
with:
|
|
||||||
fileName: ${{ env.KEY_STORE_FILE }}
|
|
||||||
fileDir: ${{ env.KEY_STORE_LOCATION }}
|
|
||||||
encodedString: ${{ secrets.KEYSTORE }}
|
|
||||||
|
|
||||||
# create keystore path for gradle to read
|
|
||||||
- name: Create keystore path env var
|
|
||||||
run: |
|
|
||||||
store_path=${{ env.KEY_STORE_LOCATION }}${{ env.KEY_STORE_FILE }}
|
|
||||||
echo "KEY_STORE_PATH=$store_path" >> $GITHUB_ENV
|
|
||||||
|
|
||||||
- name: Create service_account.json
|
|
||||||
id: createServiceAccount
|
|
||||||
run: echo '${{ secrets.SERVICE_ACCOUNT_JSON }}' > service_account.json
|
|
||||||
|
|
||||||
# Build and sign APK ("-x test" argument is used to skip tests)
|
|
||||||
# add fdroid flavor for apk upload
|
|
||||||
- name: Build Fdroid Release APK
|
|
||||||
run: ./gradlew :app:assembleFdroidRelease -x test
|
|
||||||
|
|
||||||
# get fdroid flavor release apk path
|
|
||||||
- name: Get apk path
|
|
||||||
id: apk-path
|
|
||||||
run: echo "path=$(find . -regex '^.*/build/outputs/apk/fdroid/release/.*\.apk$' -type f | head -1)" >> $GITHUB_OUTPUT
|
|
||||||
- name: Get version code
|
|
||||||
run: |
|
|
||||||
version_code=$(grep "VERSION_CODE" buildSrc/src/main/kotlin/Constants.kt | awk '{print $5}' | tr -d '\n')
|
|
||||||
echo "VERSION_CODE=$version_code" >> $GITHUB_ENV
|
|
||||||
# Save the APK after the Build job is complete to publish it as a Github release in the next job
|
|
||||||
- name: Upload APK
|
|
||||||
uses: actions/upload-artifact@v4.3.3
|
|
||||||
with:
|
|
||||||
name: wgtunnel
|
|
||||||
path: ${{ steps.apk-path.outputs.path }}
|
|
||||||
- name: Download APK from build
|
|
||||||
uses: actions/download-artifact@v4
|
|
||||||
with:
|
|
||||||
name: wgtunnel
|
|
||||||
- name: Create Release with Fastlane changelog notes
|
|
||||||
id: create_release
|
|
||||||
uses: softprops/action-gh-release@v2
|
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
with:
|
|
||||||
# fix hardcode changelog file name
|
|
||||||
body_path: ${{ github.workspace }}/fastlane/metadata/android/en-US/changelogs/${{ env.VERSION_CODE }}.txt
|
|
||||||
tag_name: ${{ github.ref_name }}
|
|
||||||
name: ${{ github.ref_name }}
|
|
||||||
draft: false
|
|
||||||
prerelease: true
|
|
||||||
files: ${{ github.workspace }}/${{ steps.apk-path.outputs.path }}
|
|
||||||
|
|
||||||
- name: Install apksigner
|
|
||||||
run: |
|
|
||||||
sudo apt-get update
|
|
||||||
sudo apt-get install -y apksigner
|
|
||||||
|
|
||||||
- name: Get checksum
|
|
||||||
id: checksum
|
|
||||||
run: echo "checksum=$(apksigner verify -print-certs ${{ steps.apk-path.outputs.path }} | grep -Po "(?<=SHA-256 digest:) .*" | tr -d "[:blank:]")" >> $GITHUB_OUTPUT
|
|
||||||
|
|
||||||
- name: Append checksum
|
|
||||||
id: append_checksum
|
|
||||||
uses: softprops/action-gh-release@v2
|
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
with:
|
|
||||||
body: |
|
|
||||||
|
|
||||||
SHA256 fingerprint:
|
|
||||||
```${{ steps.checksum.outputs.checksum }}```
|
|
||||||
tag_name: ${{ github.ref_name }}
|
|
||||||
name: ${{ github.ref_name }}
|
|
||||||
draft: false
|
|
||||||
prerelease: true
|
|
||||||
append_body: true
|
|
||||||
|
|
||||||
- name: Deploy with fastlane
|
|
||||||
uses: ruby/setup-ruby@v1
|
|
||||||
with:
|
|
||||||
ruby-version: '3.2' # Not needed with a .ruby-version file
|
|
||||||
bundler-cache: true
|
|
||||||
- name: Distribute app to Beta track 🚀
|
|
||||||
run: (cd ${{ github.workspace }} && bundle install && bundle exec fastlane beta)
|
|
||||||
|
|
||||||
+121
-41
@@ -1,17 +1,40 @@
|
|||||||
# name of the workflow
|
name: release-android
|
||||||
name: Android CI Tag Deployment (Release)
|
|
||||||
|
|
||||||
on:
|
on:
|
||||||
|
schedule:
|
||||||
|
- cron: "4 3 * * *"
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
push:
|
inputs:
|
||||||
tags:
|
track:
|
||||||
- '*.*.*'
|
type: choice
|
||||||
- '!*.*.*-**'
|
description: "Google play release track"
|
||||||
|
options:
|
||||||
|
- none
|
||||||
|
- internal
|
||||||
|
- alpha
|
||||||
|
- beta
|
||||||
|
- production
|
||||||
|
default: alpha
|
||||||
|
required: true
|
||||||
|
release_type:
|
||||||
|
type: choice
|
||||||
|
description: "GitHub release type"
|
||||||
|
options:
|
||||||
|
- none
|
||||||
|
- prerelease
|
||||||
|
- nightly
|
||||||
|
- release
|
||||||
|
default: release
|
||||||
|
required: true
|
||||||
|
tag_name:
|
||||||
|
description: "Tag name for release"
|
||||||
|
required: false
|
||||||
|
default: nightly
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
name: Build Signed APK
|
name: Build Signed APK
|
||||||
|
if: ${{ inputs.release_type != 'none' }}
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
env:
|
env:
|
||||||
@@ -21,7 +44,9 @@ jobs:
|
|||||||
KEY_STORE_FILE: 'android_keystore.jks'
|
KEY_STORE_FILE: 'android_keystore.jks'
|
||||||
KEY_STORE_LOCATION: ${{ github.workspace }}/app/keystore/
|
KEY_STORE_LOCATION: ${{ github.workspace }}/app/keystore/
|
||||||
GH_USER: ${{ secrets.GH_USER }}
|
GH_USER: ${{ secrets.GH_USER }}
|
||||||
|
# GH needed for gh cli
|
||||||
GH_TOKEN: ${{ secrets.GH_TOKEN }}
|
GH_TOKEN: ${{ secrets.GH_TOKEN }}
|
||||||
|
GH_REPO: ${{ github.repository }}
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
@@ -34,6 +59,10 @@ jobs:
|
|||||||
- name: Grant execute permission for gradlew
|
- name: Grant execute permission for gradlew
|
||||||
run: chmod +x gradlew
|
run: chmod +x gradlew
|
||||||
|
|
||||||
|
- name: Install system dependencies
|
||||||
|
run: |
|
||||||
|
sudo apt update && sudo apt install -y gh apksigner
|
||||||
|
|
||||||
# Here we need to decode keystore.jks from base64 string and place it
|
# Here we need to decode keystore.jks from base64 string and place it
|
||||||
# in the folder specified in the release signing configuration
|
# in the folder specified in the release signing configuration
|
||||||
- name: Decode Keystore
|
- name: Decode Keystore
|
||||||
@@ -57,75 +86,126 @@ jobs:
|
|||||||
# Build and sign APK ("-x test" argument is used to skip tests)
|
# Build and sign APK ("-x test" argument is used to skip tests)
|
||||||
# add fdroid flavor for apk upload
|
# add fdroid flavor for apk upload
|
||||||
- name: Build Fdroid Release APK
|
- name: Build Fdroid Release APK
|
||||||
|
if: ${{ inputs.release_type != '' && inputs.release_type != 'nightly' }}
|
||||||
run: ./gradlew :app:assembleFdroidRelease -x test
|
run: ./gradlew :app:assembleFdroidRelease -x test
|
||||||
|
|
||||||
# get fdroid flavor release apk path
|
|
||||||
- name: Get apk path
|
- name: Build Fdroid Nightly APK
|
||||||
id: apk-path
|
if: ${{ inputs.release_type == '' || inputs.release_type == 'nightly' }}
|
||||||
run: echo "path=$(find . -regex '^.*/build/outputs/apk/fdroid/release/.*\.apk$' -type f | head -1)" >> $GITHUB_OUTPUT
|
run: ./gradlew :app:assembleFdroidNightly -x test
|
||||||
|
|
||||||
|
- if: ${{ inputs.release_type == '' || inputs.release_type == 'nightly' }}
|
||||||
|
run: echo "APK_PATH=$(find . -regex '^.*/build/outputs/apk/fdroid/nightly/.*\.apk$' -type f | head -1)" >> $GITHUB_ENV
|
||||||
|
- if: ${{ inputs.release_type != '' && inputs.release_type != 'nightly' }}
|
||||||
|
run: echo "APK_PATH=$(find . -regex '^.*/build/outputs/apk/fdroid/release/.*\.apk$' -type f | head -1)" >> $GITHUB_ENV
|
||||||
|
|
||||||
- name: Get version code
|
- name: Get version code
|
||||||
|
if: ${{ inputs.release_type == 'release' || inputs.release_type == 'prerelease' }}
|
||||||
run: |
|
run: |
|
||||||
version_code=$(grep "VERSION_CODE" buildSrc/src/main/kotlin/Constants.kt | awk '{print $5}' | tr -d '\n')
|
version_code=$(grep "VERSION_CODE" buildSrc/src/main/kotlin/Constants.kt | awk '{print $5}' | tr -d '\n')
|
||||||
echo "VERSION_CODE=$version_code" >> $GITHUB_ENV
|
echo "VERSION_CODE=$version_code" >> $GITHUB_ENV
|
||||||
|
|
||||||
# Save the APK after the Build job is complete to publish it as a Github release in the next job
|
# Save the APK after the Build job is complete to publish it as a Github release in the next job
|
||||||
- name: Upload APK
|
- name: Upload APK
|
||||||
uses: actions/upload-artifact@v4.3.3
|
uses: actions/upload-artifact@v4.3.4
|
||||||
with:
|
with:
|
||||||
name: wgtunnel
|
name: wgtunnel
|
||||||
path: ${{ steps.apk-path.outputs.path }}
|
path: ${{ env.APK_PATH }}
|
||||||
|
|
||||||
- name: Download APK from build
|
- name: Download APK from build
|
||||||
uses: actions/download-artifact@v4
|
uses: actions/download-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: wgtunnel
|
name: wgtunnel
|
||||||
|
|
||||||
- name: Repository Dispatch for my F-Droid repo
|
- name: Repository Dispatch for my F-Droid repo
|
||||||
uses: peter-evans/repository-dispatch@v3
|
uses: peter-evans/repository-dispatch@v3
|
||||||
|
if: ${{ inputs.release_type == 'release' }}
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.PAT }}
|
token: ${{ secrets.PAT }}
|
||||||
repository: zaneschepke/fdroid
|
repository: zaneschepke/fdroid
|
||||||
event-type: fdroid-update
|
event-type: fdroid-update
|
||||||
|
|
||||||
|
- name: Set version release notes
|
||||||
|
if: ${{ inputs.release_type == 'release' || inputs.release_type == 'prerelease' }}
|
||||||
|
run: |
|
||||||
|
RELEASE_NOTES="$(cat ${{ github.workspace }}/fastlane/metadata/android/en-US/changelogs/${{ env.VERSION_CODE }}.txt)"
|
||||||
|
echo "RELEASE_NOTES<<EOF" >> $GITHUB_ENV
|
||||||
|
echo "$RELEASE_NOTES" >> $GITHUB_ENV
|
||||||
|
echo "EOF" >> $GITHUB_ENV
|
||||||
|
|
||||||
|
- name: On nightly release
|
||||||
|
if: ${{ contains(env.TAG_NAME, 'nightly') }}
|
||||||
|
run: |
|
||||||
|
echo "RELEASE_NOTES=Nightly build for the latest development version of the app." >> $GITHUB_ENV
|
||||||
|
gh release delete nightly --yes || true
|
||||||
|
|
||||||
|
# Setup TAG_NAME, which is used as a general "name"
|
||||||
|
- if: github.event_name == 'workflow_dispatch'
|
||||||
|
run: echo "TAG_NAME=${{ github.event.inputs.tag_name }}" >> $GITHUB_ENV
|
||||||
|
- if: github.event_name == 'schedule'
|
||||||
|
run: echo "TAG_NAME=nightly" >> $GITHUB_ENV
|
||||||
|
|
||||||
|
- name: On nightly release
|
||||||
|
if: ${{ contains(env.TAG_NAME, 'nightly') }}
|
||||||
|
run: |
|
||||||
|
echo "RELEASE_NOTES=Nightly build of the latest development version of the android client." >> $GITHUB_ENV
|
||||||
|
gh release delete nightly --yes || true
|
||||||
|
|
||||||
|
- name: Get checksum
|
||||||
|
id: checksum
|
||||||
|
run: echo "checksum=$(apksigner verify -print-certs ${{ env.APK_PATH }} | grep -Po "(?<=SHA-256 digest:) .*" | tr -d "[:blank:]")" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
|
||||||
- name: Create Release with Fastlane changelog notes
|
- name: Create Release with Fastlane changelog notes
|
||||||
id: create_release
|
id: create_release
|
||||||
uses: softprops/action-gh-release@v2
|
uses: softprops/action-gh-release@v2
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
with:
|
|
||||||
body_path: ${{ github.workspace }}/fastlane/metadata/android/en-US/changelogs/${{ env.VERSION_CODE }}.txt
|
|
||||||
tag_name: ${{ github.ref_name }}
|
|
||||||
name: ${{ github.ref_name }}
|
|
||||||
draft: false
|
|
||||||
prerelease: false
|
|
||||||
files: ${{ github.workspace }}/${{ steps.apk-path.outputs.path }}
|
|
||||||
|
|
||||||
- name: Install apksigner
|
|
||||||
run: |
|
|
||||||
sudo apt-get update
|
|
||||||
sudo apt-get install -y apksigner
|
|
||||||
|
|
||||||
- name: Get checksum
|
|
||||||
id: checksum
|
|
||||||
run: echo "checksum=$(apksigner verify -print-certs ${{ steps.apk-path.outputs.path }} | grep -Po "(?<=SHA-256 digest:) .*" | tr -d "[:blank:]")" >> $GITHUB_OUTPUT
|
|
||||||
|
|
||||||
- name: Append checksum
|
|
||||||
id: append_checksum
|
|
||||||
uses: softprops/action-gh-release@v2
|
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
with:
|
with:
|
||||||
body: |
|
body: |
|
||||||
|
${{ env.RELEASE_NOTES }}
|
||||||
|
|
||||||
SHA256 fingerprint:
|
SHA256 fingerprint:
|
||||||
```${{ steps.checksum.outputs.checksum }}```
|
```${{ steps.checksum.outputs.checksum }}```
|
||||||
tag_name: ${{ github.ref_name }}
|
tag_name: ${{ env.TAG_NAME }}
|
||||||
name: ${{ github.ref_name }}
|
name: ${{ env.TAG_NAME }}
|
||||||
draft: false
|
draft: false
|
||||||
prerelease: false
|
prerelease: ${{ inputs.release_type == 'prerelease' || inputs.release_type == '' || inputs.release_type == 'nightly' }}
|
||||||
append_body: true
|
make_latest: ${{ inputs.release_type == 'release' }}
|
||||||
|
files: ${{ github.workspace }}/${{ env.APK_PATH }}
|
||||||
|
|
||||||
|
publish-play:
|
||||||
|
if: ${{ inputs.track != 'none' && inputs.track != '' }}
|
||||||
|
name: Publish to Google Play
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
env:
|
||||||
|
SIGNING_KEY_ALIAS: ${{ secrets.SIGNING_KEY_ALIAS }}
|
||||||
|
SIGNING_KEY_PASSWORD: ${{ secrets.SIGNING_KEY_PASSWORD }}
|
||||||
|
SIGNING_STORE_PASSWORD: ${{ secrets.SIGNING_STORE_PASSWORD }}
|
||||||
|
KEY_STORE_FILE: 'android_keystore.jks'
|
||||||
|
KEY_STORE_LOCATION: ${{ github.workspace }}/app/keystore/
|
||||||
|
GH_USER: ${{ secrets.GH_USER }}
|
||||||
|
GH_TOKEN: ${{ secrets.GH_TOKEN }}
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- name: Set up JDK 17
|
||||||
|
uses: actions/setup-java@v4
|
||||||
|
with:
|
||||||
|
distribution: 'temurin'
|
||||||
|
java-version: '17'
|
||||||
|
cache: gradle
|
||||||
|
|
||||||
|
- name: Grant execute permission for gradlew
|
||||||
|
run: chmod +x gradlew
|
||||||
|
|
||||||
- name: Deploy with fastlane
|
- name: Deploy with fastlane
|
||||||
uses: ruby/setup-ruby@v1
|
uses: ruby/setup-ruby@v1
|
||||||
with:
|
with:
|
||||||
ruby-version: '3.2' # Not needed with a .ruby-version file
|
ruby-version: '3.2' # Not needed with a .ruby-version file
|
||||||
bundler-cache: true
|
bundler-cache: true
|
||||||
- name: Distribute app to Prod track 🚀
|
|
||||||
run: (cd ${{ github.workspace }} && bundle install && bundle exec fastlane production)
|
- name: Distribute app to Prod track 🚀
|
||||||
|
run: (cd ${{ github.workspace }} && bundle install && bundle exec fastlane ${{ inputs.track }})
|
||||||
|
|
||||||
|
|||||||
@@ -22,7 +22,8 @@ WG Tunnel
|
|||||||
|
|
||||||
<div align="left">
|
<div align="left">
|
||||||
|
|
||||||
This is an alternative Android Application for [WireGuard](https://www.wireguard.com/) and [AmneziaWG](https://docs.amnezia.org/documentation/amnezia-wg/) with added
|
This is an alternative Android Application for [WireGuard](https://www.wireguard.com/)
|
||||||
|
and [AmneziaWG](https://docs.amnezia.org/documentation/amnezia-wg/) with added
|
||||||
features. Built using the [wireguard-android](https://github.com/WireGuard/wireguard-android)
|
features. Built using the [wireguard-android](https://github.com/WireGuard/wireguard-android)
|
||||||
library and [Jetpack Compose](https://developer.android.com/jetpack/compose), this application was
|
library and [Jetpack Compose](https://developer.android.com/jetpack/compose), this application was
|
||||||
inspired by the official [WireGuard Android](https://github.com/WireGuard/wireguard-android) app.
|
inspired by the official [WireGuard Android](https://github.com/WireGuard/wireguard-android) app.
|
||||||
@@ -64,27 +65,28 @@ and on while on different networks. This app was created to offer a free solutio
|
|||||||
* Battery preservation measures
|
* Battery preservation measures
|
||||||
* Restart tunnel on ping failure (beta)
|
* Restart tunnel on ping failure (beta)
|
||||||
|
|
||||||
|
## Fdroid
|
||||||
|
|
||||||
|
Want updates faster?
|
||||||
|
|
||||||
|
Check out my personal [fdroid repository](https://github.com/zaneschepke/fdroid) to get updates the
|
||||||
|
moment they are released.
|
||||||
|
|
||||||
## Docs
|
## Docs
|
||||||
|
|
||||||
Basic documentation of the feature and behaviors of this app can be
|
Information about features, behaviors, and answers to common questions can be found in the
|
||||||
found [here](https://zaneschepke.com/wgtunnel-docs/overview.html).
|
app [documentation](https://zaneschepke.com/wgtunnel-docs/overview.html).
|
||||||
|
|
||||||
The repository for these docs can be found [here](https://github.com/zaneschepke/wgtunnel-docs).
|
The repository for these docs can be found [here](https://github.com/zaneschepke/wgtunnel-docs).
|
||||||
|
|
||||||
## Contributing
|
|
||||||
|
|
||||||
Any contributions in the form of feedback, issues, code, or translations are welcome and much appreciated!
|
|
||||||
|
|
||||||
Please read the [code of conduct](https://github.com/zaneschepke/wgtunnel?tab=coc-ov-file#contributor-code-of-conduct) before contributing.
|
|
||||||
|
|
||||||
## Translation
|
## Translation
|
||||||
|
|
||||||
This app is using [Weblate](https://weblate.org) to assist with translations.
|
This app is using [Weblate](https://weblate.org) to assist with translations.
|
||||||
|
|
||||||
Help translate WG Tunnel into your language at [Hosted Weblate](https://hosted.weblate.org/engage/wg-tunnel/).\
|
Help translate WG Tunnel into your language
|
||||||
|
at [Hosted Weblate](https://hosted.weblate.org/engage/wg-tunnel/).\
|
||||||
[](https://hosted.weblate.org/engage/wg-tunnel/)
|
[](https://hosted.weblate.org/engage/wg-tunnel/)
|
||||||
|
|
||||||
|
|
||||||
## Building
|
## Building
|
||||||
|
|
||||||
```
|
```
|
||||||
@@ -98,4 +100,11 @@ And then build the app:
|
|||||||
$ ./gradlew assembleDebug
|
$ ./gradlew assembleDebug
|
||||||
```
|
```
|
||||||
|
|
||||||
</span>
|
## Contributing
|
||||||
|
|
||||||
|
Any contributions in the form of feedback, issues, code, or translations are welcome and much
|
||||||
|
appreciated!
|
||||||
|
|
||||||
|
Please read
|
||||||
|
the [code of conduct](https://github.com/zaneschepke/wgtunnel?tab=coc-ov-file#contributor-code-of-conduct)
|
||||||
|
before contributing.
|
||||||
|
|||||||
+24
-45
@@ -1,16 +1,17 @@
|
|||||||
import java.util.Properties
|
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
alias(libs.plugins.android.application)
|
alias(libs.plugins.android.application)
|
||||||
alias(libs.plugins.kotlin.android)
|
alias(libs.plugins.kotlin.android)
|
||||||
alias(libs.plugins.hilt.android)
|
alias(libs.plugins.hilt.android)
|
||||||
alias(libs.plugins.kotlinxSerialization)
|
alias(libs.plugins.kotlinxSerialization)
|
||||||
alias(libs.plugins.ksp)
|
alias(libs.plugins.ksp)
|
||||||
|
alias(libs.plugins.compose.compiler)
|
||||||
|
alias(libs.plugins.grgit)
|
||||||
}
|
}
|
||||||
|
|
||||||
android {
|
android {
|
||||||
namespace = Constants.APP_ID
|
namespace = Constants.APP_ID
|
||||||
compileSdk = Constants.TARGET_SDK
|
compileSdk = Constants.TARGET_SDK
|
||||||
|
compileSdkPreview = "VanillaIceCream"
|
||||||
|
|
||||||
androidResources {
|
androidResources {
|
||||||
generateLocaleConfig = true
|
generateLocaleConfig = true
|
||||||
@@ -35,44 +36,10 @@ android {
|
|||||||
|
|
||||||
signingConfigs {
|
signingConfigs {
|
||||||
create(Constants.RELEASE) {
|
create(Constants.RELEASE) {
|
||||||
val properties =
|
storeFile = getStoreFile()
|
||||||
Properties().apply {
|
storePassword = getSigningProperty(Constants.STORE_PASS_VAR)
|
||||||
// created local file for signing details
|
keyAlias = getSigningProperty(Constants.KEY_ALIAS_VAR)
|
||||||
try {
|
keyPassword = getSigningProperty(Constants.KEY_PASS_VAR)
|
||||||
load(file("signing.properties").reader())
|
|
||||||
} catch (_: Exception) {
|
|
||||||
load(file("signing_template.properties").reader())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// try to get secrets from env first for pipeline build, then properties file for local
|
|
||||||
// build
|
|
||||||
storeFile =
|
|
||||||
file(
|
|
||||||
System.getenv()
|
|
||||||
.getOrDefault(
|
|
||||||
Constants.KEY_STORE_PATH_VAR,
|
|
||||||
properties.getProperty(Constants.KEY_STORE_PATH_VAR),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
storePassword =
|
|
||||||
System.getenv()
|
|
||||||
.getOrDefault(
|
|
||||||
Constants.STORE_PASS_VAR,
|
|
||||||
properties.getProperty(Constants.STORE_PASS_VAR),
|
|
||||||
)
|
|
||||||
keyAlias =
|
|
||||||
System.getenv()
|
|
||||||
.getOrDefault(
|
|
||||||
Constants.KEY_ALIAS_VAR,
|
|
||||||
properties.getProperty(Constants.KEY_ALIAS_VAR),
|
|
||||||
)
|
|
||||||
keyPassword =
|
|
||||||
System.getenv()
|
|
||||||
.getOrDefault(
|
|
||||||
Constants.KEY_PASS_VAR,
|
|
||||||
properties.getProperty(Constants.KEY_PASS_VAR),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -103,6 +70,12 @@ android {
|
|||||||
signingConfig = signingConfigs.getByName(Constants.RELEASE)
|
signingConfig = signingConfigs.getByName(Constants.RELEASE)
|
||||||
}
|
}
|
||||||
debug { isDebuggable = true }
|
debug { isDebuggable = true }
|
||||||
|
|
||||||
|
create(Constants.NIGHTLY) {
|
||||||
|
initWith(getByName("release"))
|
||||||
|
defaultConfig.versionName = nightlyVersionName()
|
||||||
|
defaultConfig.versionCode = nightlyVersionCode()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
flavorDimensions.add(Constants.TYPE)
|
flavorDimensions.add(Constants.TYPE)
|
||||||
productFlavors {
|
productFlavors {
|
||||||
@@ -112,9 +85,6 @@ android {
|
|||||||
}
|
}
|
||||||
create("general") {
|
create("general") {
|
||||||
dimension = Constants.TYPE
|
dimension = Constants.TYPE
|
||||||
if (BuildHelper.isReleaseBuild(gradle) && BuildHelper.isGeneralFlavor(gradle)) {
|
|
||||||
//any plugins general specific
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
compileOptions {
|
compileOptions {
|
||||||
@@ -127,7 +97,6 @@ android {
|
|||||||
compose = true
|
compose = true
|
||||||
buildConfig = true
|
buildConfig = true
|
||||||
}
|
}
|
||||||
composeOptions { kotlinCompilerExtensionVersion = Constants.COMPOSE_COMPILER_EXTENSION_VERSION }
|
|
||||||
packaging { resources { excludes += "/META-INF/{AL2.0,LGPL2.1}" } }
|
packaging { resources { excludes += "/META-INF/{AL2.0,LGPL2.1}" } }
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -203,7 +172,6 @@ dependencies {
|
|||||||
|
|
||||||
// barcode scanning
|
// barcode scanning
|
||||||
implementation(libs.zxing.android.embedded)
|
implementation(libs.zxing.android.embedded)
|
||||||
implementation(libs.zxing.core)
|
|
||||||
|
|
||||||
// bio
|
// bio
|
||||||
implementation(libs.androidx.biometric.ktx)
|
implementation(libs.androidx.biometric.ktx)
|
||||||
@@ -212,4 +180,15 @@ dependencies {
|
|||||||
// shortcuts
|
// shortcuts
|
||||||
implementation(libs.androidx.core)
|
implementation(libs.androidx.core)
|
||||||
implementation(libs.androidx.core.google.shortcuts)
|
implementation(libs.androidx.core.google.shortcuts)
|
||||||
|
|
||||||
|
// splash
|
||||||
|
implementation(libs.androidx.core.splashscreen)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun nightlyVersionCode() : Int {
|
||||||
|
return Constants.VERSION_CODE + Constants.NIGHTLY_CODE
|
||||||
|
}
|
||||||
|
|
||||||
|
fun nightlyVersionName() : String {
|
||||||
|
return Constants.VERSION_NAME + "-${grgitService.service.get().grgit.head().abbreviatedId}"
|
||||||
}
|
}
|
||||||
|
|||||||
Vendored
-2
@@ -22,5 +22,3 @@
|
|||||||
-keepclassmembers class * extends androidx.datastore.preferences.protobuf.GeneratedMessageLite {
|
-keepclassmembers class * extends androidx.datastore.preferences.protobuf.GeneratedMessageLite {
|
||||||
<fields>;
|
<fields>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -51,7 +51,7 @@
|
|||||||
</queries>
|
</queries>
|
||||||
<application
|
<application
|
||||||
android:name=".WireGuardAutoTunnel"
|
android:name=".WireGuardAutoTunnel"
|
||||||
android:allowBackup="true"
|
android:allowBackup="false"
|
||||||
android:banner="@drawable/ic_banner"
|
android:banner="@drawable/ic_banner"
|
||||||
android:dataExtractionRules="@xml/data_extraction_rules"
|
android:dataExtractionRules="@xml/data_extraction_rules"
|
||||||
android:enableOnBackInvokedCallback="true"
|
android:enableOnBackInvokedCallback="true"
|
||||||
@@ -60,31 +60,35 @@
|
|||||||
android:label="@string/app_name"
|
android:label="@string/app_name"
|
||||||
android:roundIcon="@mipmap/ic_launcher_round"
|
android:roundIcon="@mipmap/ic_launcher_round"
|
||||||
android:supportsRtl="true"
|
android:supportsRtl="true"
|
||||||
android:theme="@style/Theme.WireguardAutoTunnel"
|
android:theme="@style/Theme.AppSplashScreen"
|
||||||
tools:targetApi="tiramisu">
|
tools:targetApi="tiramisu">
|
||||||
<activity
|
<activity
|
||||||
android:name=".ui.MainActivity"
|
android:name=".ui.SplashActivity"
|
||||||
android:exported="true"
|
android:exported="true"
|
||||||
android:theme="@style/Theme.WireguardAutoTunnel">
|
android:theme="@style/Theme.AppSplashScreen">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.MAIN" />
|
<action android:name="android.intent.action.MAIN" />
|
||||||
|
|
||||||
<category android:name="android.intent.category.LAUNCHER" />
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
<category android:name="android.intent.category.LEANBACK_LAUNCHER" />
|
<category android:name="android.intent.category.LEANBACK_LAUNCHER" />
|
||||||
|
|
||||||
<action android:name="android.service.quicksettings.action.QS_TILE_PREFERENCES" />
|
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
<meta-data
|
<meta-data
|
||||||
android:name="android.app.shortcuts"
|
android:name="android.app.shortcuts"
|
||||||
android:resource="@xml/shortcuts" />
|
android:resource="@xml/shortcuts" />
|
||||||
</activity>
|
</activity>
|
||||||
<activity
|
<activity
|
||||||
android:name=".ui.CaptureActivityPortrait"
|
android:name=".ui.MainActivity"
|
||||||
android:screenOrientation="fullSensor"
|
android:exported="true"
|
||||||
android:stateNotNeeded="true"
|
android:theme="@style/Theme.WireguardAutoTunnel">
|
||||||
android:theme="@style/zxing_CaptureTheme"
|
<intent-filter>
|
||||||
android:windowSoftInputMode="stateAlwaysHidden"
|
<action android:name="android.service.quicksettings.action.QS_TILE_PREFERENCES" />
|
||||||
tools:ignore="DiscouragedApi" />
|
</intent-filter>
|
||||||
|
</activity>
|
||||||
|
<activity
|
||||||
|
android:name="com.journeyapps.barcodescanner.CaptureActivity"
|
||||||
|
android:screenOrientation="portrait"
|
||||||
|
tools:replace="screenOrientation" />
|
||||||
<activity
|
<activity
|
||||||
android:name=".service.shortcut.ShortcutsActivity"
|
android:name=".service.shortcut.ShortcutsActivity"
|
||||||
android:enabled="true"
|
android:enabled="true"
|
||||||
|
|||||||
@@ -3,35 +3,36 @@ package com.zaneschepke.wireguardautotunnel
|
|||||||
import android.app.Application
|
import android.app.Application
|
||||||
import android.content.ComponentName
|
import android.content.ComponentName
|
||||||
import android.content.pm.PackageManager
|
import android.content.pm.PackageManager
|
||||||
|
import android.os.StrictMode
|
||||||
|
import android.os.StrictMode.ThreadPolicy
|
||||||
import android.service.quicksettings.TileService
|
import android.service.quicksettings.TileService
|
||||||
import com.zaneschepke.wireguardautotunnel.service.tile.AutoTunnelControlTile
|
import com.zaneschepke.wireguardautotunnel.service.tile.AutoTunnelControlTile
|
||||||
import com.zaneschepke.wireguardautotunnel.service.tile.TunnelControlTile
|
import com.zaneschepke.wireguardautotunnel.service.tile.TunnelControlTile
|
||||||
import com.zaneschepke.wireguardautotunnel.util.ReleaseTree
|
import com.zaneschepke.wireguardautotunnel.util.ReleaseTree
|
||||||
import dagger.hilt.android.HiltAndroidApp
|
import dagger.hilt.android.HiltAndroidApp
|
||||||
import kotlinx.coroutines.MainScope
|
|
||||||
import kotlinx.coroutines.cancel
|
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import xyz.teamgravity.pin_lock_compose.PinManager
|
|
||||||
|
|
||||||
@HiltAndroidApp
|
@HiltAndroidApp
|
||||||
class WireGuardAutoTunnel : Application() {
|
class WireGuardAutoTunnel : Application() {
|
||||||
|
|
||||||
override fun onCreate() {
|
override fun onCreate() {
|
||||||
super.onCreate()
|
super.onCreate()
|
||||||
instance = this
|
instance = this
|
||||||
if (BuildConfig.DEBUG) Timber.plant(Timber.DebugTree()) else Timber.plant(ReleaseTree())
|
if (BuildConfig.DEBUG) {
|
||||||
PinManager.initialize(this)
|
Timber.plant(Timber.DebugTree())
|
||||||
}
|
StrictMode.setThreadPolicy(
|
||||||
|
ThreadPolicy.Builder()
|
||||||
override fun onLowMemory() {
|
.detectDiskReads()
|
||||||
super.onLowMemory()
|
.detectDiskWrites()
|
||||||
applicationScope.cancel("onLowMemory() called by system")
|
.detectNetwork()
|
||||||
applicationScope = MainScope()
|
.penaltyLog()
|
||||||
|
.build(),
|
||||||
|
)
|
||||||
|
} else Timber.plant(ReleaseTree())
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
||||||
var applicationScope = MainScope()
|
|
||||||
|
|
||||||
lateinit var instance: WireGuardAutoTunnel
|
lateinit var instance: WireGuardAutoTunnel
|
||||||
private set
|
private set
|
||||||
|
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ import com.zaneschepke.wireguardautotunnel.data.domain.TunnelConfig
|
|||||||
to = 7,
|
to = 7,
|
||||||
spec = RemoveLegacySettingColumnsMigration::class,
|
spec = RemoveLegacySettingColumnsMigration::class,
|
||||||
),
|
),
|
||||||
AutoMigration(7, 8)
|
AutoMigration(7, 8),
|
||||||
],
|
],
|
||||||
exportSchema = true,
|
exportSchema = true,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -36,10 +36,10 @@ interface TunnelConfigDao {
|
|||||||
suspend fun findByTunnelNetworkName(name: String): TunnelConfigs
|
suspend fun findByTunnelNetworkName(name: String): TunnelConfigs
|
||||||
|
|
||||||
@Query("UPDATE TunnelConfig SET is_primary_tunnel = 0 WHERE is_primary_tunnel =1")
|
@Query("UPDATE TunnelConfig SET is_primary_tunnel = 0 WHERE is_primary_tunnel =1")
|
||||||
fun resetPrimaryTunnel()
|
suspend fun resetPrimaryTunnel()
|
||||||
|
|
||||||
@Query("UPDATE TunnelConfig SET is_mobile_data_tunnel = 0 WHERE is_mobile_data_tunnel =1")
|
@Query("UPDATE TunnelConfig SET is_mobile_data_tunnel = 0 WHERE is_mobile_data_tunnel =1")
|
||||||
fun resetMobileDataTunnel()
|
suspend fun resetMobileDataTunnel()
|
||||||
|
|
||||||
@Query("SELECT * FROM TUNNELCONFIG WHERE is_primary_tunnel=1")
|
@Query("SELECT * FROM TUNNELCONFIG WHERE is_primary_tunnel=1")
|
||||||
suspend fun findByPrimary(): TunnelConfigs
|
suspend fun findByPrimary(): TunnelConfigs
|
||||||
|
|||||||
+15
-2
@@ -7,14 +7,20 @@ import androidx.datastore.preferences.core.edit
|
|||||||
import androidx.datastore.preferences.core.intPreferencesKey
|
import androidx.datastore.preferences.core.intPreferencesKey
|
||||||
import androidx.datastore.preferences.core.stringPreferencesKey
|
import androidx.datastore.preferences.core.stringPreferencesKey
|
||||||
import androidx.datastore.preferences.preferencesDataStore
|
import androidx.datastore.preferences.preferencesDataStore
|
||||||
|
import com.zaneschepke.wireguardautotunnel.module.IoDispatcher
|
||||||
|
import kotlinx.coroutines.CoroutineDispatcher
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.first
|
import kotlinx.coroutines.flow.first
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
|
|
||||||
class DataStoreManager(private val context: Context) {
|
class DataStoreManager(
|
||||||
|
private val context: Context,
|
||||||
|
@IoDispatcher private val ioDispatcher: CoroutineDispatcher
|
||||||
|
) {
|
||||||
companion object {
|
companion object {
|
||||||
val LOCATION_DISCLOSURE_SHOWN = booleanPreferencesKey("LOCATION_DISCLOSURE_SHOWN")
|
val LOCATION_DISCLOSURE_SHOWN = booleanPreferencesKey("LOCATION_DISCLOSURE_SHOWN")
|
||||||
val BATTERY_OPTIMIZE_DISABLE_SHOWN = booleanPreferencesKey("BATTERY_OPTIMIZE_DISABLE_SHOWN")
|
val BATTERY_OPTIMIZE_DISABLE_SHOWN = booleanPreferencesKey("BATTERY_OPTIMIZE_DISABLE_SHOWN")
|
||||||
@@ -22,6 +28,7 @@ class DataStoreManager(private val context: Context) {
|
|||||||
booleanPreferencesKey("TUNNEL_RUNNING_FROM_MANUAL_START")
|
booleanPreferencesKey("TUNNEL_RUNNING_FROM_MANUAL_START")
|
||||||
val ACTIVE_TUNNEL = intPreferencesKey("ACTIVE_TUNNEL")
|
val ACTIVE_TUNNEL = intPreferencesKey("ACTIVE_TUNNEL")
|
||||||
val CURRENT_SSID = stringPreferencesKey("CURRENT_SSID")
|
val CURRENT_SSID = stringPreferencesKey("CURRENT_SSID")
|
||||||
|
val IS_PIN_LOCK_ENABLED = booleanPreferencesKey("PIN_LOCK_ENABLED")
|
||||||
}
|
}
|
||||||
|
|
||||||
// preferences
|
// preferences
|
||||||
@@ -32,14 +39,17 @@ class DataStoreManager(private val context: Context) {
|
|||||||
)
|
)
|
||||||
|
|
||||||
suspend fun init() {
|
suspend fun init() {
|
||||||
|
withContext(ioDispatcher) {
|
||||||
try {
|
try {
|
||||||
context.dataStore.data.first()
|
context.dataStore.data.first()
|
||||||
} catch (e: IOException) {
|
} catch (e: IOException) {
|
||||||
Timber.e(e)
|
Timber.e(e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
suspend fun <T> saveToDataStore(key: Preferences.Key<T>, value: T) {
|
suspend fun <T> saveToDataStore(key: Preferences.Key<T>, value: T) {
|
||||||
|
withContext(ioDispatcher) {
|
||||||
try {
|
try {
|
||||||
context.dataStore.edit { it[key] = value }
|
context.dataStore.edit { it[key] = value }
|
||||||
} catch (e: IOException) {
|
} catch (e: IOException) {
|
||||||
@@ -48,18 +58,21 @@ class DataStoreManager(private val context: Context) {
|
|||||||
Timber.e(e)
|
Timber.e(e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
fun <T> getFromStoreFlow(key: Preferences.Key<T>) = context.dataStore.data.map { it[key] }
|
fun <T> getFromStoreFlow(key: Preferences.Key<T>) = context.dataStore.data.map { it[key] }
|
||||||
|
|
||||||
suspend fun <T> getFromStore(key: Preferences.Key<T>): T? {
|
suspend fun <T> getFromStore(key: Preferences.Key<T>): T? {
|
||||||
return try {
|
return withContext(ioDispatcher) {
|
||||||
|
try {
|
||||||
context.dataStore.data.map { it[key] }.first()
|
context.dataStore.data.map { it[key] }.first()
|
||||||
} catch (e: IOException) {
|
} catch (e: IOException) {
|
||||||
Timber.e(e)
|
Timber.e(e)
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
fun <T> getFromStoreBlocking(key: Preferences.Key<T>) = runBlocking {
|
fun <T> getFromStoreBlocking(key: Preferences.Key<T>) = runBlocking {
|
||||||
|
|||||||
@@ -1,14 +1,16 @@
|
|||||||
package com.zaneschepke.wireguardautotunnel.data.domain
|
package com.zaneschepke.wireguardautotunnel.data.domain
|
||||||
|
|
||||||
data class GeneralState(
|
data class GeneralState(
|
||||||
val locationDisclosureShown: Boolean = LOCATION_DISCLOSURE_SHOWN_DEFAULT,
|
val isLocationDisclosureShown: Boolean = LOCATION_DISCLOSURE_SHOWN_DEFAULT,
|
||||||
val batteryOptimizationDisableShown: Boolean = BATTERY_OPTIMIZATION_DISABLE_SHOWN_DEFAULT,
|
val isBatteryOptimizationDisableShown: Boolean = BATTERY_OPTIMIZATION_DISABLE_SHOWN_DEFAULT,
|
||||||
val tunnelRunningFromManualStart: Boolean = TUNNELING_RUNNING_FROM_MANUAL_START_DEFAULT,
|
val isTunnelRunningFromManualStart: Boolean = TUNNELING_RUNNING_FROM_MANUAL_START_DEFAULT,
|
||||||
|
val isPinLockEnabled: Boolean = PIN_LOCK_ENABLED_DEFAULT,
|
||||||
val activeTunnelId: Int? = null
|
val activeTunnelId: Int? = null
|
||||||
) {
|
) {
|
||||||
companion object {
|
companion object {
|
||||||
const val LOCATION_DISCLOSURE_SHOWN_DEFAULT = false
|
const val LOCATION_DISCLOSURE_SHOWN_DEFAULT = false
|
||||||
const val BATTERY_OPTIMIZATION_DISABLE_SHOWN_DEFAULT = false
|
const val BATTERY_OPTIMIZATION_DISABLE_SHOWN_DEFAULT = false
|
||||||
const val TUNNELING_RUNNING_FROM_MANUAL_START_DEFAULT = false
|
const val TUNNELING_RUNNING_FROM_MANUAL_START_DEFAULT = false
|
||||||
|
const val PIN_LOCK_ENABLED_DEFAULT = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,12 +40,14 @@ data class TunnelConfig(
|
|||||||
Config.parse(it)
|
Config.parse(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun configFromAmQuick(amQuick: String): org.amnezia.awg.config.Config {
|
fun configFromAmQuick(amQuick: String): org.amnezia.awg.config.Config {
|
||||||
val inputStream: InputStream = amQuick.byteInputStream()
|
val inputStream: InputStream = amQuick.byteInputStream()
|
||||||
return inputStream.bufferedReader(Charsets.UTF_8).use {
|
return inputStream.bufferedReader(Charsets.UTF_8).use {
|
||||||
org.amnezia.awg.config.Config.parse(it)
|
org.amnezia.awg.config.Config.parse(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const val AM_QUICK_DEFAULT = ""
|
const val AM_QUICK_DEFAULT = ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+3
@@ -7,6 +7,9 @@ interface AppStateRepository {
|
|||||||
suspend fun isLocationDisclosureShown(): Boolean
|
suspend fun isLocationDisclosureShown(): Boolean
|
||||||
suspend fun setLocationDisclosureShown(shown: Boolean)
|
suspend fun setLocationDisclosureShown(shown: Boolean)
|
||||||
|
|
||||||
|
suspend fun isPinLockEnabled(): Boolean
|
||||||
|
suspend fun setPinLockEnabled(enabled: Boolean)
|
||||||
|
|
||||||
suspend fun isBatteryOptimizationDisableShown(): Boolean
|
suspend fun isBatteryOptimizationDisableShown(): Boolean
|
||||||
suspend fun setBatteryOptimizationDisableShown(shown: Boolean)
|
suspend fun setBatteryOptimizationDisableShown(shown: Boolean)
|
||||||
|
|
||||||
|
|||||||
+14
-3
@@ -17,6 +17,15 @@ class DataStoreAppStateRepository(private val dataStoreManager: DataStoreManager
|
|||||||
dataStoreManager.saveToDataStore(DataStoreManager.LOCATION_DISCLOSURE_SHOWN, shown)
|
dataStoreManager.saveToDataStore(DataStoreManager.LOCATION_DISCLOSURE_SHOWN, shown)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override suspend fun isPinLockEnabled(): Boolean {
|
||||||
|
return dataStoreManager.getFromStore(DataStoreManager.IS_PIN_LOCK_ENABLED)
|
||||||
|
?: GeneralState.PIN_LOCK_ENABLED_DEFAULT
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun setPinLockEnabled(enabled: Boolean) {
|
||||||
|
dataStoreManager.saveToDataStore(DataStoreManager.IS_PIN_LOCK_ENABLED, enabled)
|
||||||
|
}
|
||||||
|
|
||||||
override suspend fun isBatteryOptimizationDisableShown(): Boolean {
|
override suspend fun isBatteryOptimizationDisableShown(): Boolean {
|
||||||
return dataStoreManager.getFromStore(DataStoreManager.BATTERY_OPTIMIZE_DISABLE_SHOWN)
|
return dataStoreManager.getFromStore(DataStoreManager.BATTERY_OPTIMIZE_DISABLE_SHOWN)
|
||||||
?: GeneralState.BATTERY_OPTIMIZATION_DISABLE_SHOWN_DEFAULT
|
?: GeneralState.BATTERY_OPTIMIZATION_DISABLE_SHOWN_DEFAULT
|
||||||
@@ -65,11 +74,13 @@ class DataStoreAppStateRepository(private val dataStoreManager: DataStoreManager
|
|||||||
prefs?.let { pref ->
|
prefs?.let { pref ->
|
||||||
try {
|
try {
|
||||||
GeneralState(
|
GeneralState(
|
||||||
locationDisclosureShown = pref[DataStoreManager.LOCATION_DISCLOSURE_SHOWN]
|
isLocationDisclosureShown = pref[DataStoreManager.LOCATION_DISCLOSURE_SHOWN]
|
||||||
?: GeneralState.LOCATION_DISCLOSURE_SHOWN_DEFAULT,
|
?: GeneralState.LOCATION_DISCLOSURE_SHOWN_DEFAULT,
|
||||||
batteryOptimizationDisableShown = pref[DataStoreManager.BATTERY_OPTIMIZE_DISABLE_SHOWN]
|
isBatteryOptimizationDisableShown = pref[DataStoreManager.BATTERY_OPTIMIZE_DISABLE_SHOWN]
|
||||||
?: GeneralState.BATTERY_OPTIMIZATION_DISABLE_SHOWN_DEFAULT,
|
?: GeneralState.BATTERY_OPTIMIZATION_DISABLE_SHOWN_DEFAULT,
|
||||||
tunnelRunningFromManualStart = pref[DataStoreManager.TUNNEL_RUNNING_FROM_MANUAL_START]
|
isTunnelRunningFromManualStart = pref[DataStoreManager.TUNNEL_RUNNING_FROM_MANUAL_START]
|
||||||
|
?: GeneralState.TUNNELING_RUNNING_FROM_MANUAL_START_DEFAULT,
|
||||||
|
isPinLockEnabled = pref[DataStoreManager.IS_PIN_LOCK_ENABLED]
|
||||||
?: GeneralState.TUNNELING_RUNNING_FROM_MANUAL_START_DEFAULT,
|
?: GeneralState.TUNNELING_RUNNING_FROM_MANUAL_START_DEFAULT,
|
||||||
)
|
)
|
||||||
} catch (e: IllegalArgumentException) {
|
} catch (e: IllegalArgumentException) {
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
package com.zaneschepke.wireguardautotunnel.module
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import com.zaneschepke.logcatter.LocalLogCollector
|
||||||
|
import com.zaneschepke.logcatter.LogcatHelper
|
||||||
|
import dagger.Module
|
||||||
|
import dagger.Provides
|
||||||
|
import dagger.hilt.InstallIn
|
||||||
|
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||||
|
import dagger.hilt.components.SingletonComponent
|
||||||
|
import kotlinx.coroutines.CoroutineDispatcher
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.SupervisorJob
|
||||||
|
import javax.inject.Singleton
|
||||||
|
|
||||||
|
@Module
|
||||||
|
@InstallIn(SingletonComponent::class)
|
||||||
|
class AppModule {
|
||||||
|
@Singleton
|
||||||
|
@ApplicationScope
|
||||||
|
@Provides
|
||||||
|
fun providesApplicationScope(@DefaultDispatcher defaultDispatcher: CoroutineDispatcher): CoroutineScope =
|
||||||
|
CoroutineScope(SupervisorJob() + defaultDispatcher)
|
||||||
|
|
||||||
|
@Singleton
|
||||||
|
@Provides
|
||||||
|
fun provideLogCollect(@ApplicationContext context: Context): LocalLogCollector {
|
||||||
|
return LogcatHelper.init(context = context)
|
||||||
|
}
|
||||||
|
}
|
||||||
+4
@@ -2,6 +2,10 @@ package com.zaneschepke.wireguardautotunnel.module
|
|||||||
|
|
||||||
import javax.inject.Qualifier
|
import javax.inject.Qualifier
|
||||||
|
|
||||||
|
@Qualifier
|
||||||
|
@Retention(AnnotationRetention.BINARY)
|
||||||
|
annotation class Kernel
|
||||||
|
|
||||||
@Qualifier
|
@Qualifier
|
||||||
@Retention(AnnotationRetention.BINARY)
|
@Retention(AnnotationRetention.BINARY)
|
||||||
annotation class Userspace
|
annotation class Userspace
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
package com.zaneschepke.wireguardautotunnel.module
|
||||||
|
|
||||||
|
import javax.inject.Qualifier
|
||||||
|
|
||||||
|
@Retention(AnnotationRetention.RUNTIME)
|
||||||
|
@Qualifier
|
||||||
|
annotation class DefaultDispatcher
|
||||||
|
|
||||||
|
@Retention(AnnotationRetention.RUNTIME)
|
||||||
|
@Qualifier
|
||||||
|
annotation class IoDispatcher
|
||||||
|
|
||||||
|
@Retention(AnnotationRetention.RUNTIME)
|
||||||
|
@Qualifier
|
||||||
|
annotation class MainDispatcher
|
||||||
|
|
||||||
|
@Retention(AnnotationRetention.BINARY)
|
||||||
|
@Qualifier
|
||||||
|
annotation class MainImmediateDispatcher
|
||||||
|
|
||||||
|
@Retention(AnnotationRetention.RUNTIME)
|
||||||
|
@Qualifier
|
||||||
|
annotation class ApplicationScope
|
||||||
|
|
||||||
|
@Retention(AnnotationRetention.RUNTIME)
|
||||||
|
@Qualifier
|
||||||
|
annotation class ServiceScope
|
||||||
+28
@@ -0,0 +1,28 @@
|
|||||||
|
package com.zaneschepke.wireguardautotunnel.module
|
||||||
|
|
||||||
|
import dagger.Module
|
||||||
|
import dagger.Provides
|
||||||
|
import dagger.hilt.InstallIn
|
||||||
|
import dagger.hilt.components.SingletonComponent
|
||||||
|
import kotlinx.coroutines.CoroutineDispatcher
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
|
||||||
|
@Module
|
||||||
|
@InstallIn(SingletonComponent::class)
|
||||||
|
object CoroutinesDispatchersModule {
|
||||||
|
@DefaultDispatcher
|
||||||
|
@Provides
|
||||||
|
fun providesDefaultDispatcher(): CoroutineDispatcher = Dispatchers.Default
|
||||||
|
|
||||||
|
@IoDispatcher
|
||||||
|
@Provides
|
||||||
|
fun providesIoDispatcher(): CoroutineDispatcher = Dispatchers.IO
|
||||||
|
|
||||||
|
@MainDispatcher
|
||||||
|
@Provides
|
||||||
|
fun providesMainDispatcher(): CoroutineDispatcher = Dispatchers.Main
|
||||||
|
|
||||||
|
@MainImmediateDispatcher
|
||||||
|
@Provides
|
||||||
|
fun providesMainImmediateDispatcher(): CoroutineDispatcher = Dispatchers.Main.immediate
|
||||||
|
}
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
package com.zaneschepke.wireguardautotunnel.module
|
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import androidx.room.Room
|
|
||||||
import com.zaneschepke.wireguardautotunnel.R
|
|
||||||
import com.zaneschepke.wireguardautotunnel.data.AppDatabase
|
|
||||||
import com.zaneschepke.wireguardautotunnel.data.DatabaseCallback
|
|
||||||
import dagger.Module
|
|
||||||
import dagger.Provides
|
|
||||||
import dagger.hilt.InstallIn
|
|
||||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
|
||||||
import dagger.hilt.components.SingletonComponent
|
|
||||||
import javax.inject.Singleton
|
|
||||||
|
|
||||||
@Module
|
|
||||||
@InstallIn(SingletonComponent::class)
|
|
||||||
class DatabaseModule {
|
|
||||||
@Provides
|
|
||||||
@Singleton
|
|
||||||
fun provideDatabase(@ApplicationContext context: Context): AppDatabase {
|
|
||||||
return Room.databaseBuilder(
|
|
||||||
context,
|
|
||||||
AppDatabase::class.java,
|
|
||||||
context.getString(R.string.db_name),
|
|
||||||
)
|
|
||||||
.fallbackToDestructiveMigration()
|
|
||||||
.addCallback(DatabaseCallback())
|
|
||||||
.build()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
package com.zaneschepke.wireguardautotunnel.module
|
|
||||||
|
|
||||||
import javax.inject.Qualifier
|
|
||||||
|
|
||||||
@Qualifier
|
|
||||||
@Retention(AnnotationRetention.BINARY)
|
|
||||||
annotation class Kernel
|
|
||||||
@@ -1,7 +1,10 @@
|
|||||||
package com.zaneschepke.wireguardautotunnel.module
|
package com.zaneschepke.wireguardautotunnel.module
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import androidx.room.Room
|
||||||
|
import com.zaneschepke.wireguardautotunnel.R
|
||||||
import com.zaneschepke.wireguardautotunnel.data.AppDatabase
|
import com.zaneschepke.wireguardautotunnel.data.AppDatabase
|
||||||
|
import com.zaneschepke.wireguardautotunnel.data.DatabaseCallback
|
||||||
import com.zaneschepke.wireguardautotunnel.data.SettingsDao
|
import com.zaneschepke.wireguardautotunnel.data.SettingsDao
|
||||||
import com.zaneschepke.wireguardautotunnel.data.TunnelConfigDao
|
import com.zaneschepke.wireguardautotunnel.data.TunnelConfigDao
|
||||||
import com.zaneschepke.wireguardautotunnel.data.datastore.DataStoreManager
|
import com.zaneschepke.wireguardautotunnel.data.datastore.DataStoreManager
|
||||||
@@ -18,11 +21,25 @@ import dagger.Provides
|
|||||||
import dagger.hilt.InstallIn
|
import dagger.hilt.InstallIn
|
||||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||||
import dagger.hilt.components.SingletonComponent
|
import dagger.hilt.components.SingletonComponent
|
||||||
|
import kotlinx.coroutines.CoroutineDispatcher
|
||||||
import javax.inject.Singleton
|
import javax.inject.Singleton
|
||||||
|
|
||||||
@Module
|
@Module
|
||||||
@InstallIn(SingletonComponent::class)
|
@InstallIn(SingletonComponent::class)
|
||||||
class RepositoryModule {
|
class RepositoryModule {
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
fun provideDatabase(@ApplicationContext context: Context): AppDatabase {
|
||||||
|
return Room.databaseBuilder(
|
||||||
|
context,
|
||||||
|
AppDatabase::class.java,
|
||||||
|
context.getString(R.string.db_name),
|
||||||
|
)
|
||||||
|
.fallbackToDestructiveMigration()
|
||||||
|
.addCallback(DatabaseCallback())
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
|
||||||
@Singleton
|
@Singleton
|
||||||
@Provides
|
@Provides
|
||||||
fun provideSettingsDoa(appDatabase: AppDatabase): SettingsDao {
|
fun provideSettingsDoa(appDatabase: AppDatabase): SettingsDao {
|
||||||
@@ -49,8 +66,11 @@ class RepositoryModule {
|
|||||||
|
|
||||||
@Singleton
|
@Singleton
|
||||||
@Provides
|
@Provides
|
||||||
fun providePreferencesDataStore(@ApplicationContext context: Context): DataStoreManager {
|
fun providePreferencesDataStore(
|
||||||
return DataStoreManager(context)
|
@ApplicationContext context: Context,
|
||||||
|
@IoDispatcher ioDispatcher: CoroutineDispatcher
|
||||||
|
): DataStoreManager {
|
||||||
|
return DataStoreManager(context, ioDispatcher)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
|
|||||||
@@ -15,6 +15,9 @@ import dagger.Provides
|
|||||||
import dagger.hilt.InstallIn
|
import dagger.hilt.InstallIn
|
||||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||||
import dagger.hilt.components.SingletonComponent
|
import dagger.hilt.components.SingletonComponent
|
||||||
|
import kotlinx.coroutines.CoroutineDispatcher
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import javax.inject.Provider
|
||||||
import javax.inject.Singleton
|
import javax.inject.Singleton
|
||||||
|
|
||||||
@Module
|
@Module
|
||||||
@@ -49,17 +52,29 @@ class TunnelModule {
|
|||||||
@Provides
|
@Provides
|
||||||
@Singleton
|
@Singleton
|
||||||
fun provideVpnService(
|
fun provideVpnService(
|
||||||
amneziaBackend: org.amnezia.awg.backend.Backend,
|
amneziaBackend: Provider<org.amnezia.awg.backend.Backend>,
|
||||||
@Userspace userspaceBackend: Backend,
|
@Userspace userspaceBackend: Provider<Backend>,
|
||||||
@Kernel kernelBackend: Backend,
|
@Kernel kernelBackend: Provider<Backend>,
|
||||||
appDataRepository: AppDataRepository
|
appDataRepository: AppDataRepository,
|
||||||
|
@ApplicationScope applicationScope: CoroutineScope,
|
||||||
|
@IoDispatcher ioDispatcher: CoroutineDispatcher
|
||||||
): VpnService {
|
): VpnService {
|
||||||
return WireGuardTunnel(amneziaBackend,userspaceBackend, kernelBackend, appDataRepository)
|
return WireGuardTunnel(
|
||||||
|
amneziaBackend,
|
||||||
|
userspaceBackend,
|
||||||
|
kernelBackend,
|
||||||
|
appDataRepository,
|
||||||
|
applicationScope,
|
||||||
|
ioDispatcher,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@Singleton
|
@Singleton
|
||||||
fun provideServiceManager(appDataRepository: AppDataRepository): ServiceManager {
|
fun provideServiceManager(
|
||||||
return ServiceManager(appDataRepository)
|
appDataRepository: AppDataRepository,
|
||||||
|
@IoDispatcher ioDispatcher: CoroutineDispatcher
|
||||||
|
): ServiceManager {
|
||||||
|
return ServiceManager(appDataRepository, ioDispatcher)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
package com.zaneschepke.wireguardautotunnel.module
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import com.zaneschepke.wireguardautotunnel.util.FileUtils
|
||||||
|
import dagger.Module
|
||||||
|
import dagger.Provides
|
||||||
|
import dagger.hilt.InstallIn
|
||||||
|
import dagger.hilt.android.components.ViewModelComponent
|
||||||
|
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||||
|
import dagger.hilt.android.scopes.ViewModelScoped
|
||||||
|
import kotlinx.coroutines.CoroutineDispatcher
|
||||||
|
|
||||||
|
@Module
|
||||||
|
@InstallIn(ViewModelComponent::class)
|
||||||
|
class ViewModelModule {
|
||||||
|
|
||||||
|
@ViewModelScoped
|
||||||
|
@Provides
|
||||||
|
fun provideFileUtils(
|
||||||
|
@ApplicationContext context: Context,
|
||||||
|
@IoDispatcher ioDispatcher: CoroutineDispatcher
|
||||||
|
): FileUtils {
|
||||||
|
return FileUtils(context, ioDispatcher)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,9 +4,11 @@ import android.content.BroadcastReceiver
|
|||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import com.zaneschepke.wireguardautotunnel.data.repository.AppDataRepository
|
import com.zaneschepke.wireguardautotunnel.data.repository.AppDataRepository
|
||||||
|
import com.zaneschepke.wireguardautotunnel.module.ApplicationScope
|
||||||
import com.zaneschepke.wireguardautotunnel.service.foreground.ServiceManager
|
import com.zaneschepke.wireguardautotunnel.service.foreground.ServiceManager
|
||||||
import com.zaneschepke.wireguardautotunnel.util.goAsync
|
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@@ -19,10 +21,16 @@ class BootReceiver : BroadcastReceiver() {
|
|||||||
@Inject
|
@Inject
|
||||||
lateinit var serviceManager: ServiceManager
|
lateinit var serviceManager: ServiceManager
|
||||||
|
|
||||||
override fun onReceive(context: Context?, intent: Intent?) = goAsync {
|
@Inject
|
||||||
if (Intent.ACTION_BOOT_COMPLETED != intent?.action) return@goAsync
|
@ApplicationScope
|
||||||
|
lateinit var applicationScope: CoroutineScope
|
||||||
|
|
||||||
|
override fun onReceive(context: Context?, intent: Intent?) {
|
||||||
|
if (Intent.ACTION_BOOT_COMPLETED != intent?.action) return
|
||||||
context?.run {
|
context?.run {
|
||||||
|
applicationScope.launch {
|
||||||
val settings = appDataRepository.settings.getSettings()
|
val settings = appDataRepository.settings.getSettings()
|
||||||
|
if(settings.isRestoreOnBootEnabled) {
|
||||||
if (settings.isAutoTunnelEnabled) {
|
if (settings.isAutoTunnelEnabled) {
|
||||||
Timber.i("Starting watcher service from boot")
|
Timber.i("Starting watcher service from boot")
|
||||||
serviceManager.startWatcherServiceForeground(context)
|
serviceManager.startWatcherServiceForeground(context)
|
||||||
@@ -34,6 +42,7 @@ class BootReceiver : BroadcastReceiver() {
|
|||||||
context,
|
context,
|
||||||
appDataRepository.tunnels.getById(it)?.id,
|
appDataRepository.tunnels.getById(it)?.id,
|
||||||
)
|
)
|
||||||
|
return@launch
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (settings.isAlwaysOnVpnEnabled) {
|
if (settings.isAlwaysOnVpnEnabled) {
|
||||||
@@ -43,3 +52,5 @@ class BootReceiver : BroadcastReceiver() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
+10
-2
@@ -4,12 +4,14 @@ import android.content.BroadcastReceiver
|
|||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import com.zaneschepke.wireguardautotunnel.data.repository.SettingsRepository
|
import com.zaneschepke.wireguardautotunnel.data.repository.SettingsRepository
|
||||||
|
import com.zaneschepke.wireguardautotunnel.module.ApplicationScope
|
||||||
import com.zaneschepke.wireguardautotunnel.service.foreground.ServiceManager
|
import com.zaneschepke.wireguardautotunnel.service.foreground.ServiceManager
|
||||||
import com.zaneschepke.wireguardautotunnel.util.Constants
|
import com.zaneschepke.wireguardautotunnel.util.Constants
|
||||||
import com.zaneschepke.wireguardautotunnel.util.goAsync
|
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.cancel
|
import kotlinx.coroutines.cancel
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@@ -21,7 +23,12 @@ class NotificationActionReceiver : BroadcastReceiver() {
|
|||||||
@Inject
|
@Inject
|
||||||
lateinit var serviceManager: ServiceManager
|
lateinit var serviceManager: ServiceManager
|
||||||
|
|
||||||
override fun onReceive(context: Context, intent: Intent?) = goAsync {
|
@Inject
|
||||||
|
@ApplicationScope
|
||||||
|
lateinit var applicationScope: CoroutineScope
|
||||||
|
|
||||||
|
override fun onReceive(context: Context, intent: Intent?) {
|
||||||
|
applicationScope.launch {
|
||||||
try {
|
try {
|
||||||
//TODO fix for manual start changes when enabled
|
//TODO fix for manual start changes when enabled
|
||||||
serviceManager.stopVpnServiceForeground(context)
|
serviceManager.stopVpnServiceForeground(context)
|
||||||
@@ -34,3 +41,4 @@ class NotificationActionReceiver : BroadcastReceiver() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|||||||
+1
@@ -24,6 +24,7 @@ open class ForegroundService : LifecycleService() {
|
|||||||
when (action) {
|
when (action) {
|
||||||
Action.START.name,
|
Action.START.name,
|
||||||
Action.START_FOREGROUND.name -> startService(intent.extras)
|
Action.START_FOREGROUND.name -> startService(intent.extras)
|
||||||
|
|
||||||
Action.STOP.name, Action.STOP_FOREGROUND.name -> stopService()
|
Action.STOP.name, Action.STOP_FOREGROUND.name -> stopService()
|
||||||
Constants.ALWAYS_ON_VPN_ACTION -> {
|
Constants.ALWAYS_ON_VPN_ACTION -> {
|
||||||
Timber.i("Always-on VPN starting service")
|
Timber.i("Always-on VPN starting service")
|
||||||
|
|||||||
+17
-2
@@ -4,10 +4,16 @@ import android.app.Service
|
|||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import com.zaneschepke.wireguardautotunnel.data.repository.AppDataRepository
|
import com.zaneschepke.wireguardautotunnel.data.repository.AppDataRepository
|
||||||
|
import com.zaneschepke.wireguardautotunnel.module.IoDispatcher
|
||||||
import com.zaneschepke.wireguardautotunnel.util.Constants
|
import com.zaneschepke.wireguardautotunnel.util.Constants
|
||||||
|
import kotlinx.coroutines.CoroutineDispatcher
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
|
||||||
class ServiceManager(private val appDataRepository: AppDataRepository) {
|
class ServiceManager(
|
||||||
|
private val appDataRepository: AppDataRepository,
|
||||||
|
@IoDispatcher private val ioDispatcher: CoroutineDispatcher
|
||||||
|
) {
|
||||||
|
|
||||||
private fun <T : Service> actionOnService(
|
private fun <T : Service> actionOnService(
|
||||||
action: Action,
|
action: Action,
|
||||||
@@ -23,7 +29,10 @@ class ServiceManager(private val appDataRepository: AppDataRepository) {
|
|||||||
intent.component?.javaClass
|
intent.component?.javaClass
|
||||||
try {
|
try {
|
||||||
when (action) {
|
when (action) {
|
||||||
Action.START_FOREGROUND, Action.STOP_FOREGROUND -> context.startForegroundService(intent)
|
Action.START_FOREGROUND, Action.STOP_FOREGROUND -> context.startForegroundService(
|
||||||
|
intent,
|
||||||
|
)
|
||||||
|
|
||||||
Action.START, Action.STOP -> context.startService(intent)
|
Action.START, Action.STOP -> context.startService(intent)
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
@@ -46,6 +55,7 @@ class ServiceManager(private val appDataRepository: AppDataRepository) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
suspend fun stopVpnServiceForeground(context: Context, isManualStop: Boolean = false) {
|
suspend fun stopVpnServiceForeground(context: Context, isManualStop: Boolean = false) {
|
||||||
|
withContext(ioDispatcher) {
|
||||||
if (isManualStop) onManualStop()
|
if (isManualStop) onManualStop()
|
||||||
Timber.i("Stopping vpn service")
|
Timber.i("Stopping vpn service")
|
||||||
actionOnService(
|
actionOnService(
|
||||||
@@ -54,8 +64,10 @@ class ServiceManager(private val appDataRepository: AppDataRepository) {
|
|||||||
WireGuardTunnelService::class.java,
|
WireGuardTunnelService::class.java,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
suspend fun stopVpnService(context: Context, isManualStop: Boolean = false) {
|
suspend fun stopVpnService(context: Context, isManualStop: Boolean = false) {
|
||||||
|
withContext(ioDispatcher) {
|
||||||
if (isManualStop) onManualStop()
|
if (isManualStop) onManualStop()
|
||||||
Timber.i("Stopping vpn service")
|
Timber.i("Stopping vpn service")
|
||||||
actionOnService(
|
actionOnService(
|
||||||
@@ -64,6 +76,7 @@ class ServiceManager(private val appDataRepository: AppDataRepository) {
|
|||||||
WireGuardTunnelService::class.java,
|
WireGuardTunnelService::class.java,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private suspend fun onManualStop() {
|
private suspend fun onManualStop() {
|
||||||
appDataRepository.appState.setManualStop()
|
appDataRepository.appState.setManualStop()
|
||||||
@@ -80,6 +93,7 @@ class ServiceManager(private val appDataRepository: AppDataRepository) {
|
|||||||
tunnelId: Int? = null,
|
tunnelId: Int? = null,
|
||||||
isManualStart: Boolean = false
|
isManualStart: Boolean = false
|
||||||
) {
|
) {
|
||||||
|
withContext(ioDispatcher) {
|
||||||
if (isManualStart) onManualStart(tunnelId)
|
if (isManualStart) onManualStart(tunnelId)
|
||||||
actionOnService(
|
actionOnService(
|
||||||
Action.START_FOREGROUND,
|
Action.START_FOREGROUND,
|
||||||
@@ -88,6 +102,7 @@ class ServiceManager(private val appDataRepository: AppDataRepository) {
|
|||||||
tunnelId?.let { mapOf(Constants.TUNNEL_EXTRA_KEY to it) },
|
tunnelId?.let { mapOf(Constants.TUNNEL_EXTRA_KEY to it) },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun startWatcherServiceForeground(
|
fun startWatcherServiceForeground(
|
||||||
context: Context,
|
context: Context,
|
||||||
|
|||||||
-7
@@ -21,13 +21,6 @@ data class WatcherState(
|
|||||||
isMobileDataConnected)
|
isMobileDataConnected)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun isTunnelOnMobileDataPreferredConditionMet(): Boolean {
|
|
||||||
return (!isEthernetConnected &&
|
|
||||||
settings.isTunnelOnMobileDataEnabled &&
|
|
||||||
!isWifiConnected &&
|
|
||||||
isMobileDataConnected)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun isTunnelOffOnMobileDataConditionMet(): Boolean {
|
fun isTunnelOffOnMobileDataConditionMet(): Boolean {
|
||||||
return (!isEthernetConnected &&
|
return (!isEthernetConnected &&
|
||||||
!settings.isTunnelOnMobileDataEnabled &&
|
!settings.isTunnelOnMobileDataEnabled &&
|
||||||
|
|||||||
+53
-26
@@ -8,6 +8,8 @@ import androidx.lifecycle.lifecycleScope
|
|||||||
import com.zaneschepke.wireguardautotunnel.R
|
import com.zaneschepke.wireguardautotunnel.R
|
||||||
import com.zaneschepke.wireguardautotunnel.data.domain.TunnelConfig
|
import com.zaneschepke.wireguardautotunnel.data.domain.TunnelConfig
|
||||||
import com.zaneschepke.wireguardautotunnel.data.repository.AppDataRepository
|
import com.zaneschepke.wireguardautotunnel.data.repository.AppDataRepository
|
||||||
|
import com.zaneschepke.wireguardautotunnel.module.IoDispatcher
|
||||||
|
import com.zaneschepke.wireguardautotunnel.module.MainImmediateDispatcher
|
||||||
import com.zaneschepke.wireguardautotunnel.service.network.EthernetService
|
import com.zaneschepke.wireguardautotunnel.service.network.EthernetService
|
||||||
import com.zaneschepke.wireguardautotunnel.service.network.MobileDataService
|
import com.zaneschepke.wireguardautotunnel.service.network.MobileDataService
|
||||||
import com.zaneschepke.wireguardautotunnel.service.network.NetworkService
|
import com.zaneschepke.wireguardautotunnel.service.network.NetworkService
|
||||||
@@ -19,13 +21,14 @@ import com.zaneschepke.wireguardautotunnel.service.tunnel.VpnService
|
|||||||
import com.zaneschepke.wireguardautotunnel.util.Constants
|
import com.zaneschepke.wireguardautotunnel.util.Constants
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
import kotlinx.coroutines.CancellationException
|
import kotlinx.coroutines.CancellationException
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.CoroutineDispatcher
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.collectLatest
|
import kotlinx.coroutines.flow.collectLatest
|
||||||
import kotlinx.coroutines.flow.update
|
import kotlinx.coroutines.flow.update
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import java.net.InetAddress
|
import java.net.InetAddress
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
@@ -56,6 +59,14 @@ class WireGuardConnectivityWatcherService : ForegroundService() {
|
|||||||
@Inject
|
@Inject
|
||||||
lateinit var serviceManager: ServiceManager
|
lateinit var serviceManager: ServiceManager
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
@IoDispatcher
|
||||||
|
lateinit var ioDispatcher: CoroutineDispatcher
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
@MainImmediateDispatcher
|
||||||
|
lateinit var mainImmediateDispatcher: CoroutineDispatcher
|
||||||
|
|
||||||
private val networkEventsFlow = MutableStateFlow(WatcherState())
|
private val networkEventsFlow = MutableStateFlow(WatcherState())
|
||||||
|
|
||||||
private var watcherJob: Job? = null
|
private var watcherJob: Job? = null
|
||||||
@@ -65,7 +76,7 @@ class WireGuardConnectivityWatcherService : ForegroundService() {
|
|||||||
|
|
||||||
override fun onCreate() {
|
override fun onCreate() {
|
||||||
super.onCreate()
|
super.onCreate()
|
||||||
lifecycleScope.launch(Dispatchers.Main) {
|
lifecycleScope.launch(mainImmediateDispatcher) {
|
||||||
try {
|
try {
|
||||||
if (appDataRepository.settings.getSettings().isAutoTunnelPaused) {
|
if (appDataRepository.settings.getSettings().isAutoTunnelPaused) {
|
||||||
launchWatcherPausedNotification()
|
launchWatcherPausedNotification()
|
||||||
@@ -145,7 +156,7 @@ class WireGuardConnectivityWatcherService : ForegroundService() {
|
|||||||
|
|
||||||
private fun startWatcherJob() {
|
private fun startWatcherJob() {
|
||||||
watcherJob =
|
watcherJob =
|
||||||
lifecycleScope.launch(Dispatchers.IO) {
|
lifecycleScope.launch {
|
||||||
val setting = appDataRepository.settings.getSettings()
|
val setting = appDataRepository.settings.getSettings()
|
||||||
launch {
|
launch {
|
||||||
Timber.i("Starting wifi watcher")
|
Timber.i("Starting wifi watcher")
|
||||||
@@ -182,6 +193,7 @@ class WireGuardConnectivityWatcherService : ForegroundService() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun watchForMobileDataConnectivityChanges() {
|
private suspend fun watchForMobileDataConnectivityChanges() {
|
||||||
|
withContext(ioDispatcher) {
|
||||||
mobileDataService.networkStatus.collect { status ->
|
mobileDataService.networkStatus.collect { status ->
|
||||||
when (status) {
|
when (status) {
|
||||||
is NetworkStatus.Available -> {
|
is NetworkStatus.Available -> {
|
||||||
@@ -213,8 +225,11 @@ class WireGuardConnectivityWatcherService : ForegroundService() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private suspend fun watchForPingFailure() {
|
private suspend fun watchForPingFailure() {
|
||||||
|
val context = this
|
||||||
|
withContext(ioDispatcher) {
|
||||||
try {
|
try {
|
||||||
do {
|
do {
|
||||||
if (vpnService.vpnState.value.status == TunnelState.UP) {
|
if (vpnService.vpnState.value.status == TunnelState.UP) {
|
||||||
@@ -234,9 +249,9 @@ class WireGuardConnectivityWatcherService : ForegroundService() {
|
|||||||
}
|
}
|
||||||
if (results.contains(false)) {
|
if (results.contains(false)) {
|
||||||
Timber.i("Restarting VPN for ping failure")
|
Timber.i("Restarting VPN for ping failure")
|
||||||
serviceManager.stopVpnServiceForeground(this)
|
serviceManager.stopVpnServiceForeground(context)
|
||||||
delay(Constants.VPN_RESTART_DELAY)
|
delay(Constants.VPN_RESTART_DELAY)
|
||||||
serviceManager.startVpnServiceForeground(this, it.id)
|
serviceManager.startVpnServiceForeground(context, it.id)
|
||||||
delay(Constants.PING_COOLDOWN)
|
delay(Constants.PING_COOLDOWN)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -247,6 +262,7 @@ class WireGuardConnectivityWatcherService : ForegroundService() {
|
|||||||
Timber.e(e)
|
Timber.e(e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private suspend fun watchForSettingsChanges() {
|
private suspend fun watchForSettingsChanges() {
|
||||||
appDataRepository.settings.getSettingsFlow().collect { settings ->
|
appDataRepository.settings.getSettingsFlow().collect { settings ->
|
||||||
@@ -265,6 +281,7 @@ class WireGuardConnectivityWatcherService : ForegroundService() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun watchForEthernetConnectivityChanges() {
|
private suspend fun watchForEthernetConnectivityChanges() {
|
||||||
|
withContext(ioDispatcher) {
|
||||||
ethernetService.networkStatus.collect { status ->
|
ethernetService.networkStatus.collect { status ->
|
||||||
when (status) {
|
when (status) {
|
||||||
is NetworkStatus.Available -> {
|
is NetworkStatus.Available -> {
|
||||||
@@ -296,8 +313,10 @@ class WireGuardConnectivityWatcherService : ForegroundService() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private suspend fun watchForWifiConnectivityChanges() {
|
private suspend fun watchForWifiConnectivityChanges() {
|
||||||
|
withContext(ioDispatcher) {
|
||||||
wifiService.networkStatus.collect { status ->
|
wifiService.networkStatus.collect { status ->
|
||||||
when (status) {
|
when (status) {
|
||||||
is NetworkStatus.Available -> {
|
is NetworkStatus.Available -> {
|
||||||
@@ -308,6 +327,7 @@ class WireGuardConnectivityWatcherService : ForegroundService() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
is NetworkStatus.CapabilitiesChanged -> {
|
is NetworkStatus.CapabilitiesChanged -> {
|
||||||
Timber.i("Wifi capabilities changed")
|
Timber.i("Wifi capabilities changed")
|
||||||
networkEventsFlow.update {
|
networkEventsFlow.update {
|
||||||
@@ -340,6 +360,7 @@ class WireGuardConnectivityWatcherService : ForegroundService() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private suspend fun getMobileDataTunnel(): TunnelConfig? {
|
private suspend fun getMobileDataTunnel(): TunnelConfig? {
|
||||||
return appDataRepository.tunnels.findByMobileDataTunnel().firstOrNull()
|
return appDataRepository.tunnels.findByMobileDataTunnel().firstOrNull()
|
||||||
@@ -354,6 +375,8 @@ class WireGuardConnectivityWatcherService : ForegroundService() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun manageVpn() {
|
private suspend fun manageVpn() {
|
||||||
|
val context = this
|
||||||
|
withContext(ioDispatcher) {
|
||||||
networkEventsFlow.collectLatest { watcherState ->
|
networkEventsFlow.collectLatest { watcherState ->
|
||||||
val autoTunnel = "Auto-tunnel watcher"
|
val autoTunnel = "Auto-tunnel watcher"
|
||||||
if (!watcherState.settings.isAutoTunnelPaused) {
|
if (!watcherState.settings.isAutoTunnelPaused) {
|
||||||
@@ -363,29 +386,25 @@ class WireGuardConnectivityWatcherService : ForegroundService() {
|
|||||||
when {
|
when {
|
||||||
watcherState.isEthernetConditionMet() -> {
|
watcherState.isEthernetConditionMet() -> {
|
||||||
Timber.i("$autoTunnel - tunnel on on ethernet condition met")
|
Timber.i("$autoTunnel - tunnel on on ethernet condition met")
|
||||||
if(isTunnelDown()) serviceManager.startVpnServiceForeground(this)
|
if (isTunnelDown()) serviceManager.startVpnServiceForeground(context)
|
||||||
}
|
}
|
||||||
|
|
||||||
watcherState.isMobileDataConditionMet() -> {
|
watcherState.isMobileDataConditionMet() -> {
|
||||||
Timber.i("$autoTunnel - tunnel on on mobile data condition met")
|
Timber.i("$autoTunnel - tunnel on mobile data condition met")
|
||||||
if(isTunnelDown()) serviceManager.startVpnServiceForeground(this, getMobileDataTunnel()?.id)
|
val mobileDataTunnel = getMobileDataTunnel()
|
||||||
}
|
val tunnel =
|
||||||
|
mobileDataTunnel ?: appDataRepository.getPrimaryOrFirstTunnel()
|
||||||
watcherState.isTunnelOnMobileDataPreferredConditionMet() -> {
|
if (isTunnelDown() || tunnelConfig?.isMobileDataTunnel == false) {
|
||||||
if(tunnelConfig?.isMobileDataTunnel == false) {
|
serviceManager.startVpnServiceForeground(
|
||||||
getMobileDataTunnel()?.let {
|
context,
|
||||||
Timber.i("$autoTunnel - tunnel connected on mobile data is not preferred condition met, switching to preferred")
|
tunnel?.id,
|
||||||
if(isTunnelDown()) serviceManager.startVpnServiceForeground(
|
|
||||||
this,
|
|
||||||
getMobileDataTunnel()?.id,
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
watcherState.isTunnelOffOnMobileDataConditionMet() -> {
|
watcherState.isTunnelOffOnMobileDataConditionMet() -> {
|
||||||
Timber.i("$autoTunnel - tunnel off on mobile data met, turning vpn off")
|
Timber.i("$autoTunnel - tunnel off on mobile data met, turning vpn off")
|
||||||
if(!isTunnelDown()) serviceManager.stopVpnServiceForeground(this)
|
if (!isTunnelDown()) serviceManager.stopVpnServiceForeground(context)
|
||||||
}
|
}
|
||||||
|
|
||||||
watcherState.isUntrustedWifiConditionMet() -> {
|
watcherState.isUntrustedWifiConditionMet() -> {
|
||||||
@@ -393,12 +412,19 @@ class WireGuardConnectivityWatcherService : ForegroundService() {
|
|||||||
tunnelConfig == null) {
|
tunnelConfig == null) {
|
||||||
Timber.i("$autoTunnel - tunnel on ssid not associated with current tunnel condition met")
|
Timber.i("$autoTunnel - tunnel on ssid not associated with current tunnel condition met")
|
||||||
getSsidTunnel(watcherState.currentNetworkSSID)?.let {
|
getSsidTunnel(watcherState.currentNetworkSSID)?.let {
|
||||||
Timber.i("Found tunnel associated with this SSID, bringing tunnel up")
|
Timber.i("Found tunnel associated with this SSID, bringing tunnel up: ${it.name}")
|
||||||
if(isTunnelDown()) serviceManager.startVpnServiceForeground(this, it.id)
|
if (isTunnelDown() || tunnelConfig?.id != it.id) serviceManager.startVpnServiceForeground(
|
||||||
|
context,
|
||||||
|
it.id,
|
||||||
|
)
|
||||||
} ?: suspend {
|
} ?: suspend {
|
||||||
Timber.i("No tunnel associated with this SSID, using defaults")
|
Timber.i("No tunnel associated with this SSID, using defaults")
|
||||||
if (appDataRepository.getPrimaryOrFirstTunnel()?.name != vpnService.name) {
|
val default = appDataRepository.getPrimaryOrFirstTunnel()
|
||||||
if(isTunnelDown()) serviceManager.startVpnServiceForeground(this)
|
if (default?.name != vpnService.name) {
|
||||||
|
default?.let {
|
||||||
|
serviceManager.startVpnServiceForeground(context, it.id)
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}.invoke()
|
}.invoke()
|
||||||
}
|
}
|
||||||
@@ -406,17 +432,17 @@ class WireGuardConnectivityWatcherService : ForegroundService() {
|
|||||||
|
|
||||||
watcherState.isTrustedWifiConditionMet() -> {
|
watcherState.isTrustedWifiConditionMet() -> {
|
||||||
Timber.i("$autoTunnel - tunnel off on trusted wifi condition met, turning vpn off")
|
Timber.i("$autoTunnel - tunnel off on trusted wifi condition met, turning vpn off")
|
||||||
if(!isTunnelDown()) serviceManager.stopVpnServiceForeground(this)
|
if (!isTunnelDown()) serviceManager.stopVpnServiceForeground(context)
|
||||||
}
|
}
|
||||||
|
|
||||||
watcherState.isTunnelOffOnWifiConditionMet() -> {
|
watcherState.isTunnelOffOnWifiConditionMet() -> {
|
||||||
Timber.i("$autoTunnel - tunnel off on wifi condition met, turning vpn off")
|
Timber.i("$autoTunnel - tunnel off on wifi condition met, turning vpn off")
|
||||||
if(!isTunnelDown()) serviceManager.stopVpnServiceForeground(this)
|
if (!isTunnelDown()) serviceManager.stopVpnServiceForeground(context)
|
||||||
}
|
}
|
||||||
|
|
||||||
watcherState.isTunnelOffOnNoConnectivityMet() -> {
|
watcherState.isTunnelOffOnNoConnectivityMet() -> {
|
||||||
Timber.i("$autoTunnel - tunnel off on no connectivity met, turning vpn off")
|
Timber.i("$autoTunnel - tunnel off on no connectivity met, turning vpn off")
|
||||||
if(!isTunnelDown()) serviceManager.stopVpnServiceForeground(this)
|
if (!isTunnelDown()) serviceManager.stopVpnServiceForeground(context)
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> {
|
else -> {
|
||||||
@@ -427,3 +453,4 @@ class WireGuardConnectivityWatcherService : ForegroundService() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|||||||
+17
-4
@@ -7,6 +7,8 @@ import androidx.core.app.ServiceCompat
|
|||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import com.zaneschepke.wireguardautotunnel.R
|
import com.zaneschepke.wireguardautotunnel.R
|
||||||
import com.zaneschepke.wireguardautotunnel.data.repository.AppDataRepository
|
import com.zaneschepke.wireguardautotunnel.data.repository.AppDataRepository
|
||||||
|
import com.zaneschepke.wireguardautotunnel.module.IoDispatcher
|
||||||
|
import com.zaneschepke.wireguardautotunnel.module.MainImmediateDispatcher
|
||||||
import com.zaneschepke.wireguardautotunnel.receiver.NotificationActionReceiver
|
import com.zaneschepke.wireguardautotunnel.receiver.NotificationActionReceiver
|
||||||
import com.zaneschepke.wireguardautotunnel.service.notification.NotificationService
|
import com.zaneschepke.wireguardautotunnel.service.notification.NotificationService
|
||||||
import com.zaneschepke.wireguardautotunnel.service.tunnel.HandshakeStatus
|
import com.zaneschepke.wireguardautotunnel.service.tunnel.HandshakeStatus
|
||||||
@@ -17,10 +19,11 @@ import com.zaneschepke.wireguardautotunnel.util.handshakeStatus
|
|||||||
import com.zaneschepke.wireguardautotunnel.util.mapPeerStats
|
import com.zaneschepke.wireguardautotunnel.util.mapPeerStats
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
import kotlinx.coroutines.CancellationException
|
import kotlinx.coroutines.CancellationException
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.CoroutineDispatcher
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@@ -37,13 +40,21 @@ class WireGuardTunnelService : ForegroundService() {
|
|||||||
@Inject
|
@Inject
|
||||||
lateinit var notificationService: NotificationService
|
lateinit var notificationService: NotificationService
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
@MainImmediateDispatcher
|
||||||
|
lateinit var mainImmediateDispatcher: CoroutineDispatcher
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
@IoDispatcher
|
||||||
|
lateinit var ioDispatcher: CoroutineDispatcher
|
||||||
|
|
||||||
private var job: Job? = null
|
private var job: Job? = null
|
||||||
|
|
||||||
private var didShowConnected = false
|
private var didShowConnected = false
|
||||||
|
|
||||||
override fun onCreate() {
|
override fun onCreate() {
|
||||||
super.onCreate()
|
super.onCreate()
|
||||||
lifecycleScope.launch(Dispatchers.Main) {
|
lifecycleScope.launch(mainImmediateDispatcher) {
|
||||||
//TODO fix this to not launch if AOVPN
|
//TODO fix this to not launch if AOVPN
|
||||||
if (appDataRepository.tunnels.count() != 0) {
|
if (appDataRepository.tunnels.count() != 0) {
|
||||||
launchVpnNotification()
|
launchVpnNotification()
|
||||||
@@ -55,7 +66,7 @@ class WireGuardTunnelService : ForegroundService() {
|
|||||||
super.startService(extras)
|
super.startService(extras)
|
||||||
cancelJob()
|
cancelJob()
|
||||||
job =
|
job =
|
||||||
lifecycleScope.launch(Dispatchers.IO) {
|
lifecycleScope.launch {
|
||||||
launch {
|
launch {
|
||||||
val tunnelId = extras?.getInt(Constants.TUNNEL_EXTRA_KEY)
|
val tunnelId = extras?.getInt(Constants.TUNNEL_EXTRA_KEY)
|
||||||
if (vpnService.getState() == TunnelState.UP) {
|
if (vpnService.getState() == TunnelState.UP) {
|
||||||
@@ -75,6 +86,7 @@ class WireGuardTunnelService : ForegroundService() {
|
|||||||
|
|
||||||
//TODO improve tunnel notifications
|
//TODO improve tunnel notifications
|
||||||
private suspend fun handshakeNotifications() {
|
private suspend fun handshakeNotifications() {
|
||||||
|
withContext(ioDispatcher) {
|
||||||
var tunnelName: String? = null
|
var tunnelName: String? = null
|
||||||
vpnService.vpnState.collect { state ->
|
vpnService.vpnState.collect { state ->
|
||||||
state.statistics
|
state.statistics
|
||||||
@@ -111,6 +123,7 @@ class WireGuardTunnelService : ForegroundService() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun launchAlwaysOnDisabledNotification() {
|
private fun launchAlwaysOnDisabledNotification() {
|
||||||
launchVpnNotification(
|
launchVpnNotification(
|
||||||
@@ -121,7 +134,7 @@ class WireGuardTunnelService : ForegroundService() {
|
|||||||
|
|
||||||
override fun stopService() {
|
override fun stopService() {
|
||||||
super.stopService()
|
super.stopService()
|
||||||
lifecycleScope.launch(Dispatchers.IO) {
|
lifecycleScope.launch {
|
||||||
vpnService.stopTunnel()
|
vpnService.stopTunnel()
|
||||||
didShowConnected = false
|
didShowConnected = false
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-1
@@ -10,6 +10,7 @@ import android.graphics.Color
|
|||||||
import androidx.core.app.NotificationCompat
|
import androidx.core.app.NotificationCompat
|
||||||
import com.zaneschepke.wireguardautotunnel.R
|
import com.zaneschepke.wireguardautotunnel.R
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.MainActivity
|
import com.zaneschepke.wireguardautotunnel.ui.MainActivity
|
||||||
|
import com.zaneschepke.wireguardautotunnel.ui.SplashActivity
|
||||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@@ -59,7 +60,7 @@ class WireGuardNotification @Inject constructor(@ApplicationContext private val
|
|||||||
}
|
}
|
||||||
notificationManager.createNotificationChannel(channel)
|
notificationManager.createNotificationChannel(channel)
|
||||||
val pendingIntent: PendingIntent =
|
val pendingIntent: PendingIntent =
|
||||||
Intent(context, MainActivity::class.java).let { notificationIntent ->
|
Intent(context, SplashActivity::class.java).let { notificationIntent ->
|
||||||
PendingIntent.getActivity(
|
PendingIntent.getActivity(
|
||||||
context,
|
context,
|
||||||
0,
|
0,
|
||||||
|
|||||||
+7
-3
@@ -2,14 +2,14 @@ package com.zaneschepke.wireguardautotunnel.service.shortcut
|
|||||||
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import androidx.activity.ComponentActivity
|
import androidx.activity.ComponentActivity
|
||||||
import com.zaneschepke.wireguardautotunnel.WireGuardAutoTunnel
|
|
||||||
import com.zaneschepke.wireguardautotunnel.data.repository.AppDataRepository
|
import com.zaneschepke.wireguardautotunnel.data.repository.AppDataRepository
|
||||||
|
import com.zaneschepke.wireguardautotunnel.module.ApplicationScope
|
||||||
import com.zaneschepke.wireguardautotunnel.service.foreground.Action
|
import com.zaneschepke.wireguardautotunnel.service.foreground.Action
|
||||||
import com.zaneschepke.wireguardautotunnel.service.foreground.ServiceManager
|
import com.zaneschepke.wireguardautotunnel.service.foreground.ServiceManager
|
||||||
import com.zaneschepke.wireguardautotunnel.service.foreground.WireGuardConnectivityWatcherService
|
import com.zaneschepke.wireguardautotunnel.service.foreground.WireGuardConnectivityWatcherService
|
||||||
import com.zaneschepke.wireguardautotunnel.service.foreground.WireGuardTunnelService
|
import com.zaneschepke.wireguardautotunnel.service.foreground.WireGuardTunnelService
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@@ -22,9 +22,13 @@ class ShortcutsActivity : ComponentActivity() {
|
|||||||
@Inject
|
@Inject
|
||||||
lateinit var serviceManager: ServiceManager
|
lateinit var serviceManager: ServiceManager
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
@ApplicationScope
|
||||||
|
lateinit var applicationScope: CoroutineScope
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
WireGuardAutoTunnel.applicationScope.launch(Dispatchers.IO) {
|
applicationScope.launch {
|
||||||
val settings = appDataRepository.settings.getSettings()
|
val settings = appDataRepository.settings.getSettings()
|
||||||
if (settings.isShortcutsEnabled) {
|
if (settings.isShortcutsEnabled) {
|
||||||
when (intent.getStringExtra(CLASS_NAME_EXTRA_KEY)) {
|
when (intent.getStringExtra(CLASS_NAME_EXTRA_KEY)) {
|
||||||
|
|||||||
+17
-21
@@ -3,21 +3,25 @@ package com.zaneschepke.wireguardautotunnel.service.tile
|
|||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.service.quicksettings.Tile
|
import android.service.quicksettings.Tile
|
||||||
import android.service.quicksettings.TileService
|
import android.service.quicksettings.TileService
|
||||||
|
import androidx.lifecycle.Lifecycle
|
||||||
|
import androidx.lifecycle.LifecycleOwner
|
||||||
|
import androidx.lifecycle.ServiceLifecycleDispatcher
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
import com.zaneschepke.wireguardautotunnel.R
|
import com.zaneschepke.wireguardautotunnel.R
|
||||||
import com.zaneschepke.wireguardautotunnel.data.domain.TunnelConfig
|
import com.zaneschepke.wireguardautotunnel.data.domain.TunnelConfig
|
||||||
import com.zaneschepke.wireguardautotunnel.data.repository.AppDataRepository
|
import com.zaneschepke.wireguardautotunnel.data.repository.AppDataRepository
|
||||||
|
import com.zaneschepke.wireguardautotunnel.module.ApplicationScope
|
||||||
|
import com.zaneschepke.wireguardautotunnel.module.ServiceScope
|
||||||
import com.zaneschepke.wireguardautotunnel.service.foreground.ServiceManager
|
import com.zaneschepke.wireguardautotunnel.service.foreground.ServiceManager
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
import kotlinx.coroutines.cancel
|
import kotlinx.coroutines.cancel
|
||||||
import kotlinx.coroutines.flow.collectLatest
|
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@AndroidEntryPoint
|
@AndroidEntryPoint
|
||||||
class AutoTunnelControlTile : TileService() {
|
class AutoTunnelControlTile : TileService(), LifecycleOwner {
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
lateinit var appDataRepository: AppDataRepository
|
lateinit var appDataRepository: AppDataRepository
|
||||||
@@ -25,17 +29,17 @@ class AutoTunnelControlTile : TileService() {
|
|||||||
@Inject
|
@Inject
|
||||||
lateinit var serviceManager: ServiceManager
|
lateinit var serviceManager: ServiceManager
|
||||||
|
|
||||||
private val scope = CoroutineScope(Dispatchers.IO)
|
private val dispatcher = ServiceLifecycleDispatcher(this)
|
||||||
|
|
||||||
private var manualStartConfig: TunnelConfig? = null
|
private var manualStartConfig: TunnelConfig? = null
|
||||||
|
|
||||||
override fun onStartListening() {
|
override fun onStartListening() {
|
||||||
super.onStartListening()
|
super.onStartListening()
|
||||||
scope.launch {
|
lifecycleScope.launch {
|
||||||
appDataRepository.settings.getSettingsFlow().collectLatest {
|
val settings = appDataRepository.settings.getSettings()
|
||||||
when (it.isAutoTunnelEnabled) {
|
when (settings.isAutoTunnelEnabled) {
|
||||||
true -> {
|
true -> {
|
||||||
if (it.isAutoTunnelPaused) {
|
if (settings.isAutoTunnelPaused) {
|
||||||
setInactive()
|
setInactive()
|
||||||
setTileDescription(this@AutoTunnelControlTile.getString(R.string.paused))
|
setTileDescription(this@AutoTunnelControlTile.getString(R.string.paused))
|
||||||
} else {
|
} else {
|
||||||
@@ -43,7 +47,6 @@ class AutoTunnelControlTile : TileService() {
|
|||||||
setTileDescription(this@AutoTunnelControlTile.getString(R.string.active))
|
setTileDescription(this@AutoTunnelControlTile.getString(R.string.active))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
false -> {
|
false -> {
|
||||||
setTileDescription(this@AutoTunnelControlTile.getString(R.string.disabled))
|
setTileDescription(this@AutoTunnelControlTile.getString(R.string.disabled))
|
||||||
setUnavailable()
|
setUnavailable()
|
||||||
@@ -51,29 +54,19 @@ class AutoTunnelControlTile : TileService() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
override fun onTileAdded() {
|
override fun onTileAdded() {
|
||||||
super.onTileAdded()
|
super.onTileAdded()
|
||||||
onStartListening()
|
onStartListening()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDestroy() {
|
|
||||||
super.onDestroy()
|
|
||||||
scope.cancel()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onTileRemoved() {
|
|
||||||
super.onTileRemoved()
|
|
||||||
scope.cancel()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onClick() {
|
override fun onClick() {
|
||||||
super.onClick()
|
super.onClick()
|
||||||
unlockAndRun {
|
unlockAndRun {
|
||||||
scope.launch {
|
lifecycleScope.launch {
|
||||||
try {
|
try {
|
||||||
appDataRepository.toggleWatcherServicePause()
|
appDataRepository.toggleWatcherServicePause()
|
||||||
|
onStartListening()
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Timber.e(e.message)
|
Timber.e(e.message)
|
||||||
} finally {
|
} finally {
|
||||||
@@ -108,4 +101,7 @@ class AutoTunnelControlTile : TileService() {
|
|||||||
}
|
}
|
||||||
qsTile.updateTile()
|
qsTile.updateTile()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override val lifecycle: Lifecycle
|
||||||
|
get() = dispatcher.lifecycle
|
||||||
}
|
}
|
||||||
|
|||||||
+16
-19
@@ -3,21 +3,26 @@ package com.zaneschepke.wireguardautotunnel.service.tile
|
|||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.service.quicksettings.Tile
|
import android.service.quicksettings.Tile
|
||||||
import android.service.quicksettings.TileService
|
import android.service.quicksettings.TileService
|
||||||
|
import androidx.lifecycle.Lifecycle
|
||||||
|
import androidx.lifecycle.LifecycleOwner
|
||||||
|
import androidx.lifecycle.LifecycleService
|
||||||
|
import androidx.lifecycle.ServiceLifecycleDispatcher
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
import com.zaneschepke.wireguardautotunnel.data.domain.TunnelConfig
|
import com.zaneschepke.wireguardautotunnel.data.domain.TunnelConfig
|
||||||
import com.zaneschepke.wireguardautotunnel.data.repository.AppDataRepository
|
import com.zaneschepke.wireguardautotunnel.data.repository.AppDataRepository
|
||||||
|
import com.zaneschepke.wireguardautotunnel.module.ApplicationScope
|
||||||
import com.zaneschepke.wireguardautotunnel.service.foreground.ServiceManager
|
import com.zaneschepke.wireguardautotunnel.service.foreground.ServiceManager
|
||||||
import com.zaneschepke.wireguardautotunnel.service.tunnel.TunnelState
|
import com.zaneschepke.wireguardautotunnel.service.tunnel.TunnelState
|
||||||
import com.zaneschepke.wireguardautotunnel.service.tunnel.VpnService
|
import com.zaneschepke.wireguardautotunnel.service.tunnel.VpnService
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
import kotlinx.coroutines.cancel
|
import kotlinx.coroutines.cancel
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@AndroidEntryPoint
|
@AndroidEntryPoint
|
||||||
class TunnelControlTile : TileService() {
|
class TunnelControlTile : TileService(), LifecycleOwner {
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
lateinit var appDataRepository: AppDataRepository
|
lateinit var appDataRepository: AppDataRepository
|
||||||
@@ -28,19 +33,18 @@ class TunnelControlTile : TileService() {
|
|||||||
@Inject
|
@Inject
|
||||||
lateinit var serviceManager: ServiceManager
|
lateinit var serviceManager: ServiceManager
|
||||||
|
|
||||||
private val scope = CoroutineScope(Dispatchers.IO)
|
private val dispatcher = ServiceLifecycleDispatcher(this)
|
||||||
|
|
||||||
private var manualStartConfig: TunnelConfig? = null
|
private var manualStartConfig: TunnelConfig? = null
|
||||||
|
|
||||||
override fun onStartListening() {
|
override fun onStartListening() {
|
||||||
super.onStartListening()
|
super.onStartListening()
|
||||||
Timber.d("On start listening called")
|
Timber.d("On start listening called")
|
||||||
scope.launch {
|
lifecycleScope.launch {
|
||||||
vpnService.vpnState.collect { it ->
|
when (vpnService.getState()) {
|
||||||
when (it.status) {
|
|
||||||
TunnelState.UP -> {
|
TunnelState.UP -> {
|
||||||
setActive()
|
setActive()
|
||||||
it.tunnelConfig?.name?.let { name -> setTileDescription(name) }
|
setTileDescription(vpnService.name)
|
||||||
}
|
}
|
||||||
|
|
||||||
TunnelState.DOWN -> {
|
TunnelState.DOWN -> {
|
||||||
@@ -52,21 +56,11 @@ class TunnelControlTile : TileService() {
|
|||||||
setTileDescription(it.name)
|
setTileDescription(it.name)
|
||||||
} ?: setUnavailable()
|
} ?: setUnavailable()
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> setInactive()
|
else -> setInactive()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
override fun onDestroy() {
|
|
||||||
super.onDestroy()
|
|
||||||
scope.cancel()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onTileRemoved() {
|
|
||||||
super.onTileRemoved()
|
|
||||||
scope.cancel()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onTileAdded() {
|
override fun onTileAdded() {
|
||||||
super.onTileAdded()
|
super.onTileAdded()
|
||||||
@@ -76,7 +70,7 @@ class TunnelControlTile : TileService() {
|
|||||||
override fun onClick() {
|
override fun onClick() {
|
||||||
super.onClick()
|
super.onClick()
|
||||||
unlockAndRun {
|
unlockAndRun {
|
||||||
scope.launch {
|
lifecycleScope.launch {
|
||||||
try {
|
try {
|
||||||
if (vpnService.getState() == TunnelState.UP) {
|
if (vpnService.getState() == TunnelState.UP) {
|
||||||
serviceManager.stopVpnServiceForeground(
|
serviceManager.stopVpnServiceForeground(
|
||||||
@@ -122,4 +116,7 @@ class TunnelControlTile : TileService() {
|
|||||||
}
|
}
|
||||||
qsTile.updateTile()
|
qsTile.updateTile()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override val lifecycle: Lifecycle
|
||||||
|
get() = dispatcher.lifecycle
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ enum class TunnelState {
|
|||||||
Tunnel.State.UP -> UP
|
Tunnel.State.UP -> UP
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun from(state: org.amnezia.awg.backend.Tunnel.State): TunnelState {
|
fun from(state: org.amnezia.awg.backend.Tunnel.State): TunnelState {
|
||||||
return when (state) {
|
return when (state) {
|
||||||
org.amnezia.awg.backend.Tunnel.State.DOWN -> DOWN
|
org.amnezia.awg.backend.Tunnel.State.DOWN -> DOWN
|
||||||
|
|||||||
+59
-30
@@ -6,6 +6,8 @@ import com.wireguard.android.backend.Tunnel.State
|
|||||||
import com.zaneschepke.wireguardautotunnel.WireGuardAutoTunnel
|
import com.zaneschepke.wireguardautotunnel.WireGuardAutoTunnel
|
||||||
import com.zaneschepke.wireguardautotunnel.data.domain.TunnelConfig
|
import com.zaneschepke.wireguardautotunnel.data.domain.TunnelConfig
|
||||||
import com.zaneschepke.wireguardautotunnel.data.repository.AppDataRepository
|
import com.zaneschepke.wireguardautotunnel.data.repository.AppDataRepository
|
||||||
|
import com.zaneschepke.wireguardautotunnel.module.ApplicationScope
|
||||||
|
import com.zaneschepke.wireguardautotunnel.module.IoDispatcher
|
||||||
import com.zaneschepke.wireguardautotunnel.module.Kernel
|
import com.zaneschepke.wireguardautotunnel.module.Kernel
|
||||||
import com.zaneschepke.wireguardautotunnel.module.Userspace
|
import com.zaneschepke.wireguardautotunnel.module.Userspace
|
||||||
import com.zaneschepke.wireguardautotunnel.service.tunnel.statistics.AmneziaStatistics
|
import com.zaneschepke.wireguardautotunnel.service.tunnel.statistics.AmneziaStatistics
|
||||||
@@ -13,54 +15,53 @@ import com.zaneschepke.wireguardautotunnel.service.tunnel.statistics.TunnelStati
|
|||||||
import com.zaneschepke.wireguardautotunnel.service.tunnel.statistics.WireGuardStatistics
|
import com.zaneschepke.wireguardautotunnel.service.tunnel.statistics.WireGuardStatistics
|
||||||
import com.zaneschepke.wireguardautotunnel.util.Constants
|
import com.zaneschepke.wireguardautotunnel.util.Constants
|
||||||
import kotlinx.coroutines.CancellationException
|
import kotlinx.coroutines.CancellationException
|
||||||
|
import kotlinx.coroutines.CoroutineDispatcher
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
import kotlinx.coroutines.flow.asStateFlow
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
import org.amnezia.awg.backend.Tunnel
|
import org.amnezia.awg.backend.Tunnel
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
import javax.inject.Provider
|
||||||
|
|
||||||
class WireGuardTunnel
|
class WireGuardTunnel
|
||||||
@Inject
|
@Inject
|
||||||
constructor(
|
constructor(
|
||||||
private val userspaceAmneziaBackend : org.amnezia.awg.backend.Backend,
|
private val userspaceAmneziaBackend: Provider<org.amnezia.awg.backend.Backend>,
|
||||||
@Userspace private val userspaceBackend: Backend,
|
@Userspace private val userspaceBackend: Provider<Backend>,
|
||||||
@Kernel private val kernelBackend: Backend,
|
@Kernel private val kernelBackend: Provider<Backend>,
|
||||||
private val appDataRepository: AppDataRepository,
|
private val appDataRepository: AppDataRepository,
|
||||||
|
@ApplicationScope private val applicationScope: CoroutineScope,
|
||||||
|
@IoDispatcher private val ioDispatcher: CoroutineDispatcher
|
||||||
) : VpnService {
|
) : VpnService {
|
||||||
private val _vpnState = MutableStateFlow(VpnState())
|
private val _vpnState = MutableStateFlow(VpnState())
|
||||||
override val vpnState: StateFlow<VpnState> = _vpnState.asStateFlow()
|
override val vpnState: StateFlow<VpnState> = _vpnState.asStateFlow()
|
||||||
|
|
||||||
private val scope = CoroutineScope(Dispatchers.IO)
|
|
||||||
|
|
||||||
private var statsJob: Job? = null
|
private var statsJob: Job? = null
|
||||||
|
|
||||||
private var backend: Backend = userspaceBackend
|
|
||||||
|
|
||||||
private var backendIsWgUserspace = true
|
private var backendIsWgUserspace = true
|
||||||
|
|
||||||
private var backendIsAmneziaUserspace = false
|
private var backendIsAmneziaUserspace = false
|
||||||
|
|
||||||
init {
|
init {
|
||||||
scope.launch {
|
applicationScope.launch(ioDispatcher) {
|
||||||
appDataRepository.settings.getSettingsFlow().collect {
|
appDataRepository.settings.getSettingsFlow().collect {
|
||||||
if (it.isKernelEnabled && (backendIsWgUserspace || backendIsAmneziaUserspace)) {
|
if (it.isKernelEnabled && (backendIsWgUserspace || backendIsAmneziaUserspace)) {
|
||||||
Timber.d("Setting kernel backend")
|
Timber.i("Setting kernel backend")
|
||||||
backend = kernelBackend
|
|
||||||
backendIsWgUserspace = false
|
backendIsWgUserspace = false
|
||||||
backendIsAmneziaUserspace = false
|
backendIsAmneziaUserspace = false
|
||||||
} else if (!it.isKernelEnabled && !it.isAmneziaEnabled && !backendIsWgUserspace) {
|
} else if (!it.isKernelEnabled && !it.isAmneziaEnabled && !backendIsWgUserspace) {
|
||||||
Timber.d("Setting WireGuard userspace backend")
|
Timber.i("Setting WireGuard userspace backend")
|
||||||
backend = userspaceBackend
|
|
||||||
backendIsWgUserspace = true
|
backendIsWgUserspace = true
|
||||||
backendIsAmneziaUserspace = false
|
backendIsAmneziaUserspace = false
|
||||||
} else if (it.isAmneziaEnabled && !backendIsAmneziaUserspace) {
|
} else if (it.isAmneziaEnabled && !backendIsAmneziaUserspace) {
|
||||||
Timber.d("Setting Amnezia userspace backend")
|
Timber.i("Setting Amnezia userspace backend")
|
||||||
backendIsAmneziaUserspace = true
|
backendIsAmneziaUserspace = true
|
||||||
backendIsWgUserspace = false
|
backendIsWgUserspace = false
|
||||||
}
|
}
|
||||||
@@ -77,12 +78,13 @@ constructor(
|
|||||||
TunnelConfig.configFromAmQuick(it.wgQuick)
|
TunnelConfig.configFromAmQuick(it.wgQuick)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val state = userspaceAmneziaBackend.setState(this, tunnelState.toAmState(), config)
|
val state =
|
||||||
|
userspaceAmneziaBackend.get().setState(this, tunnelState.toAmState(), config)
|
||||||
TunnelState.from(state)
|
TunnelState.from(state)
|
||||||
} else {
|
} else {
|
||||||
Timber.i("Using Wg backend")
|
Timber.i("Using Wg backend")
|
||||||
val wgConfig = tunnelConfig?.let { TunnelConfig.configFromWgQuick(it.wgQuick) }
|
val wgConfig = tunnelConfig?.let { TunnelConfig.configFromWgQuick(it.wgQuick) }
|
||||||
val state = backend.setState(
|
val state = backend().setState(
|
||||||
this,
|
this,
|
||||||
tunnelState.toWgState(),
|
tunnelState.toWgState(),
|
||||||
wgConfig,
|
wgConfig,
|
||||||
@@ -92,8 +94,10 @@ constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun startTunnel(tunnelConfig: TunnelConfig?): TunnelState {
|
override suspend fun startTunnel(tunnelConfig: TunnelConfig?): TunnelState {
|
||||||
return try {
|
return withContext(ioDispatcher) {
|
||||||
|
try {
|
||||||
//TODO we need better error handling here
|
//TODO we need better error handling here
|
||||||
|
// need to bubble up these errors to the UI
|
||||||
val config = tunnelConfig ?: appDataRepository.getPrimaryOrFirstTunnel()
|
val config = tunnelConfig ?: appDataRepository.getPrimaryOrFirstTunnel()
|
||||||
if (config != null) {
|
if (config != null) {
|
||||||
emitTunnelConfig(config)
|
emitTunnelConfig(config)
|
||||||
@@ -104,6 +108,23 @@ constructor(
|
|||||||
TunnelState.from(State.DOWN)
|
TunnelState.from(State.DOWN)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun backend(): Backend {
|
||||||
|
return when {
|
||||||
|
backendIsWgUserspace -> {
|
||||||
|
userspaceBackend.get()
|
||||||
|
}
|
||||||
|
|
||||||
|
!backendIsWgUserspace && !backendIsAmneziaUserspace -> {
|
||||||
|
kernelBackend.get()
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> {
|
||||||
|
userspaceBackend.get()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun emitTunnelState(state: TunnelState) {
|
private fun emitTunnelState(state: TunnelState) {
|
||||||
_vpnState.tryEmit(
|
_vpnState.tryEmit(
|
||||||
@@ -134,6 +155,7 @@ constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun stopTunnel() {
|
override suspend fun stopTunnel() {
|
||||||
|
withContext(ioDispatcher) {
|
||||||
try {
|
try {
|
||||||
if (getState() == TunnelState.UP) {
|
if (getState() == TunnelState.UP) {
|
||||||
val state = setState(null, TunnelState.DOWN)
|
val state = setState(null, TunnelState.DOWN)
|
||||||
@@ -146,10 +168,13 @@ constructor(
|
|||||||
Timber.e("Failed to stop amnezia tunnel with error: ${e.message}")
|
Timber.e("Failed to stop amnezia tunnel with error: ${e.message}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun getState(): TunnelState {
|
override fun getState(): TunnelState {
|
||||||
return if(backendIsAmneziaUserspace) TunnelState.from(userspaceAmneziaBackend.getState(this))
|
return if (backendIsAmneziaUserspace) TunnelState.from(
|
||||||
else TunnelState.from(backend.getState(this))
|
userspaceAmneziaBackend.get().getState(this),
|
||||||
|
)
|
||||||
|
else TunnelState.from(backend().getState(this))
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getName(): String {
|
override fun getName(): String {
|
||||||
@@ -162,21 +187,10 @@ constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun handleStateChange(state: TunnelState) {
|
private fun handleStateChange(state: TunnelState) {
|
||||||
val tunnel = this
|
|
||||||
emitTunnelState(state)
|
emitTunnelState(state)
|
||||||
WireGuardAutoTunnel.requestTunnelTileServiceStateUpdate()
|
WireGuardAutoTunnel.requestTunnelTileServiceStateUpdate()
|
||||||
if (state == TunnelState.UP) {
|
if (state == TunnelState.UP) {
|
||||||
statsJob =
|
statsJob = startTunnelStatisticsJob()
|
||||||
scope.launch {
|
|
||||||
while (true) {
|
|
||||||
if(backendIsAmneziaUserspace) {
|
|
||||||
emitBackendStatistics(AmneziaStatistics(userspaceAmneziaBackend.getStatistics(tunnel)))
|
|
||||||
} else {
|
|
||||||
emitBackendStatistics(WireGuardStatistics(backend.getStatistics(tunnel)))
|
|
||||||
}
|
|
||||||
delay(Constants.VPN_STATISTIC_CHECK_INTERVAL)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (state == TunnelState.DOWN) {
|
if (state == TunnelState.DOWN) {
|
||||||
try {
|
try {
|
||||||
@@ -187,6 +201,21 @@ constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun startTunnelStatisticsJob() = applicationScope.launch(ioDispatcher) {
|
||||||
|
while (true) {
|
||||||
|
if (backendIsAmneziaUserspace) {
|
||||||
|
emitBackendStatistics(
|
||||||
|
AmneziaStatistics(
|
||||||
|
userspaceAmneziaBackend.get().getStatistics(this@WireGuardTunnel),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
emitBackendStatistics(WireGuardStatistics(backend().getStatistics(this@WireGuardTunnel)))
|
||||||
|
}
|
||||||
|
delay(Constants.VPN_STATISTIC_CHECK_INTERVAL)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun onStateChange(state: State) {
|
override fun onStateChange(state: State) {
|
||||||
handleStateChange(TunnelState.from(state))
|
handleStateChange(TunnelState.from(state))
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -11,7 +11,7 @@ class AmneziaStatistics(private val statistics: Statistics) : TunnelStatistics()
|
|||||||
PeerStats(
|
PeerStats(
|
||||||
rxBytes = stats.rxBytes,
|
rxBytes = stats.rxBytes,
|
||||||
txBytes = stats.txBytes,
|
txBytes = stats.txBytes,
|
||||||
latestHandshakeEpochMillis = stats.latestHandshakeEpochMillis
|
latestHandshakeEpochMillis = stats.latestHandshakeEpochMillis,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -11,7 +11,7 @@ class WireGuardStatistics(private val statistics: Statistics) : TunnelStatistics
|
|||||||
PeerStats(
|
PeerStats(
|
||||||
txBytes = peerStats.txBytes,
|
txBytes = peerStats.txBytes,
|
||||||
rxBytes = peerStats.rxBytes,
|
rxBytes = peerStats.rxBytes,
|
||||||
latestHandshakeEpochMillis = peerStats.latestHandshakeEpochMillis
|
latestHandshakeEpochMillis = peerStats.latestHandshakeEpochMillis,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,25 +4,16 @@ import android.content.ActivityNotFoundException
|
|||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.widget.Toast
|
|
||||||
import androidx.compose.runtime.mutableStateListOf
|
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.viewModelScope
|
|
||||||
import com.wireguard.android.backend.GoBackend
|
import com.wireguard.android.backend.GoBackend
|
||||||
import com.zaneschepke.logcatter.Logcatter
|
|
||||||
import com.zaneschepke.logcatter.model.LogMessage
|
|
||||||
import com.zaneschepke.wireguardautotunnel.R
|
import com.zaneschepke.wireguardautotunnel.R
|
||||||
import com.zaneschepke.wireguardautotunnel.WireGuardAutoTunnel
|
import com.zaneschepke.wireguardautotunnel.WireGuardAutoTunnel
|
||||||
import com.zaneschepke.wireguardautotunnel.util.Constants
|
import com.zaneschepke.wireguardautotunnel.util.Constants
|
||||||
import com.zaneschepke.wireguardautotunnel.util.FileUtils
|
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.asStateFlow
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
import kotlinx.coroutines.flow.update
|
import kotlinx.coroutines.flow.update
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import java.time.Instant
|
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@HiltViewModel
|
@HiltViewModel
|
||||||
@@ -50,7 +41,7 @@ constructor() : ViewModel() {
|
|||||||
private fun requestPermissions() {
|
private fun requestPermissions() {
|
||||||
_appUiState.update {
|
_appUiState.update {
|
||||||
it.copy(
|
it.copy(
|
||||||
requestPermissions = true
|
requestPermissions = true,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -58,7 +49,7 @@ constructor() : ViewModel() {
|
|||||||
fun permissionsRequested() {
|
fun permissionsRequested() {
|
||||||
_appUiState.update {
|
_appUiState.update {
|
||||||
it.copy(
|
it.copy(
|
||||||
requestPermissions = false
|
requestPermissions = false,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -79,7 +70,7 @@ constructor() : ViewModel() {
|
|||||||
fun onVpnPermissionAccepted() {
|
fun onVpnPermissionAccepted() {
|
||||||
_appUiState.update {
|
_appUiState.update {
|
||||||
it.copy(
|
it.copy(
|
||||||
vpnPermissionAccepted = true
|
vpnPermissionAccepted = true,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -122,33 +113,6 @@ constructor() : ViewModel() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val logs = mutableStateListOf<LogMessage>()
|
|
||||||
|
|
||||||
fun readLogCatOutput() =
|
|
||||||
viewModelScope.launch(viewModelScope.coroutineContext + Dispatchers.IO) {
|
|
||||||
launch {
|
|
||||||
Logcatter.logs(callback = {
|
|
||||||
logs.add(it)
|
|
||||||
if (logs.size > Constants.LOG_BUFFER_SIZE) {
|
|
||||||
logs.removeRange(0, (logs.size - Constants.LOG_BUFFER_SIZE).toInt())
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun clearLogs() {
|
|
||||||
logs.clear()
|
|
||||||
Logcatter.clear()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun saveLogsToFile(context: Context) {
|
|
||||||
val fileName = "${Constants.BASE_LOG_FILE_NAME}-${Instant.now().epochSecond}.txt"
|
|
||||||
val content = logs.joinToString(separator = "\n")
|
|
||||||
FileUtils.saveFileToDownloads(context.applicationContext, content, fileName)
|
|
||||||
Toast.makeText(context, context.getString(R.string.logs_saved), Toast.LENGTH_SHORT)
|
|
||||||
.show()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun setNotificationPermissionAccepted(accepted: Boolean) {
|
fun setNotificationPermissionAccepted(accepted: Boolean) {
|
||||||
_appUiState.update {
|
_appUiState.update {
|
||||||
it.copy(
|
it.copy(
|
||||||
|
|||||||
@@ -1,5 +0,0 @@
|
|||||||
package com.zaneschepke.wireguardautotunnel.ui
|
|
||||||
|
|
||||||
import com.journeyapps.barcodescanner.CaptureActivity
|
|
||||||
|
|
||||||
class CaptureActivityPortrait : CaptureActivity()
|
|
||||||
@@ -43,7 +43,7 @@ import com.google.accompanist.permissions.isGranted
|
|||||||
import com.google.accompanist.permissions.rememberPermissionState
|
import com.google.accompanist.permissions.rememberPermissionState
|
||||||
import com.zaneschepke.wireguardautotunnel.R
|
import com.zaneschepke.wireguardautotunnel.R
|
||||||
import com.zaneschepke.wireguardautotunnel.WireGuardAutoTunnel
|
import com.zaneschepke.wireguardautotunnel.WireGuardAutoTunnel
|
||||||
import com.zaneschepke.wireguardautotunnel.data.datastore.DataStoreManager
|
import com.zaneschepke.wireguardautotunnel.data.repository.AppStateRepository
|
||||||
import com.zaneschepke.wireguardautotunnel.data.repository.SettingsRepository
|
import com.zaneschepke.wireguardautotunnel.data.repository.SettingsRepository
|
||||||
import com.zaneschepke.wireguardautotunnel.service.foreground.ServiceManager
|
import com.zaneschepke.wireguardautotunnel.service.foreground.ServiceManager
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.common.navigation.BottomNavBar
|
import com.zaneschepke.wireguardautotunnel.ui.common.navigation.BottomNavBar
|
||||||
@@ -61,14 +61,13 @@ import com.zaneschepke.wireguardautotunnel.util.StringValue
|
|||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import xyz.teamgravity.pin_lock_compose.PinManager
|
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@AndroidEntryPoint
|
@AndroidEntryPoint
|
||||||
class MainActivity : AppCompatActivity() {
|
class MainActivity : AppCompatActivity() {
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
lateinit var dataStoreManager: DataStoreManager
|
lateinit var appStateRepository: AppStateRepository
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
lateinit var settingsRepository: SettingsRepository
|
lateinit var settingsRepository: SettingsRepository
|
||||||
@@ -82,17 +81,18 @@ class MainActivity : AppCompatActivity() {
|
|||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
|
val isPinLockEnabled = intent.extras?.getBoolean(SplashActivity.IS_PIN_LOCK_ENABLED_KEY)
|
||||||
|
|
||||||
enableEdgeToEdge(navigationBarStyle = SystemBarStyle.dark(Color.Transparent.toArgb()))
|
enableEdgeToEdge(navigationBarStyle = SystemBarStyle.dark(Color.Transparent.toArgb()))
|
||||||
|
|
||||||
// load preferences into memory and init data
|
|
||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
dataStoreManager.init()
|
|
||||||
WireGuardAutoTunnel.requestTunnelTileServiceStateUpdate()
|
WireGuardAutoTunnel.requestTunnelTileServiceStateUpdate()
|
||||||
val settings = settingsRepository.getSettings()
|
val settings = settingsRepository.getSettings()
|
||||||
if (settings.isAutoTunnelEnabled) {
|
if (settings.isAutoTunnelEnabled) {
|
||||||
serviceManager.startWatcherService(application.applicationContext)
|
serviceManager.startWatcherService(application.applicationContext)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
setContent {
|
setContent {
|
||||||
val appViewModel = hiltViewModel<AppViewModel>()
|
val appViewModel = hiltViewModel<AppViewModel>()
|
||||||
val appUiState by appViewModel.appUiState.collectAsStateWithLifecycle()
|
val appUiState by appViewModel.appUiState.collectAsStateWithLifecycle()
|
||||||
@@ -143,7 +143,7 @@ class MainActivity : AppCompatActivity() {
|
|||||||
if (!appUiState.vpnPermissionAccepted) {
|
if (!appUiState.vpnPermissionAccepted) {
|
||||||
return@LaunchedEffect appViewModel.vpnIntent?.let {
|
return@LaunchedEffect appViewModel.vpnIntent?.let {
|
||||||
vpnActivityResultState.launch(
|
vpnActivityResultState.launch(
|
||||||
it
|
it,
|
||||||
)
|
)
|
||||||
}!!
|
}!!
|
||||||
}
|
}
|
||||||
@@ -155,7 +155,6 @@ class MainActivity : AppCompatActivity() {
|
|||||||
appViewModel.setNotificationPermissionAccepted(
|
appViewModel.setNotificationPermissionAccepted(
|
||||||
notificationPermissionState?.status?.isGranted ?: true,
|
notificationPermissionState?.status?.isGranted ?: true,
|
||||||
)
|
)
|
||||||
if (!WireGuardAutoTunnel.isRunningOnAndroidTv()) appViewModel.readLogCatOutput()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
LaunchedEffect(appUiState.snackbarMessageConsumed) {
|
LaunchedEffect(appUiState.snackbarMessageConsumed) {
|
||||||
@@ -202,10 +201,8 @@ class MainActivity : AppCompatActivity() {
|
|||||||
) { padding ->
|
) { padding ->
|
||||||
NavHost(
|
NavHost(
|
||||||
navController,
|
navController,
|
||||||
startDestination =
|
startDestination = (if (isPinLockEnabled == true) Screen.Lock.route else Screen.Main.route),
|
||||||
(if (PinManager.pinExists()) Screen.Lock.route else Screen.Main.route),
|
modifier = Modifier
|
||||||
modifier =
|
|
||||||
Modifier
|
|
||||||
.padding(padding)
|
.padding(padding)
|
||||||
.fillMaxSize(),
|
.fillMaxSize(),
|
||||||
) {
|
) {
|
||||||
@@ -237,10 +234,11 @@ class MainActivity : AppCompatActivity() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
composable(Screen.Support.Logs.route) {
|
composable(Screen.Support.Logs.route) {
|
||||||
LogsScreen(appViewModel)
|
LogsScreen()
|
||||||
}
|
}
|
||||||
//TODO fix navigation for amnezia
|
composable(
|
||||||
composable("${Screen.Config.route}/{id}?configType={configType}", arguments =
|
"${Screen.Config.route}/{id}?configType={configType}",
|
||||||
|
arguments =
|
||||||
listOf(
|
listOf(
|
||||||
navArgument("id") {
|
navArgument("id") {
|
||||||
type = NavType.StringType
|
type = NavType.StringType
|
||||||
@@ -249,18 +247,20 @@ class MainActivity : AppCompatActivity() {
|
|||||||
navArgument("configType") {
|
navArgument("configType") {
|
||||||
type = NavType.StringType
|
type = NavType.StringType
|
||||||
defaultValue = ConfigType.WIREGUARD.name
|
defaultValue = ConfigType.WIREGUARD.name
|
||||||
}
|
},
|
||||||
)
|
),
|
||||||
) {
|
) {
|
||||||
val id = it.arguments?.getString("id")
|
val id = it.arguments?.getString("id")
|
||||||
val configType = ConfigType.valueOf( it.arguments?.getString("configType") ?: ConfigType.WIREGUARD.name)
|
val configType = ConfigType.valueOf(
|
||||||
|
it.arguments?.getString("configType") ?: ConfigType.WIREGUARD.name,
|
||||||
|
)
|
||||||
if (!id.isNullOrBlank()) {
|
if (!id.isNullOrBlank()) {
|
||||||
ConfigScreen(
|
ConfigScreen(
|
||||||
navController = navController,
|
navController = navController,
|
||||||
tunnelId = id,
|
tunnelId = id,
|
||||||
appViewModel = appViewModel,
|
appViewModel = appViewModel,
|
||||||
focusRequester = focusRequester,
|
focusRequester = focusRequester,
|
||||||
configType = configType
|
configType = configType,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,68 @@
|
|||||||
|
package com.zaneschepke.wireguardautotunnel.ui
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import android.content.Intent
|
||||||
|
import android.os.Build
|
||||||
|
import android.os.Bundle
|
||||||
|
import androidx.activity.ComponentActivity
|
||||||
|
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
|
||||||
|
import androidx.lifecycle.Lifecycle
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
|
import androidx.lifecycle.repeatOnLifecycle
|
||||||
|
import com.zaneschepke.logcatter.LocalLogCollector
|
||||||
|
import com.zaneschepke.wireguardautotunnel.WireGuardAutoTunnel
|
||||||
|
import com.zaneschepke.wireguardautotunnel.WireGuardAutoTunnel.Companion.isRunningOnAndroidTv
|
||||||
|
import com.zaneschepke.wireguardautotunnel.data.repository.AppStateRepository
|
||||||
|
import com.zaneschepke.wireguardautotunnel.module.ApplicationScope
|
||||||
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import xyz.teamgravity.pin_lock_compose.PinManager
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@SuppressLint("CustomSplashScreen")
|
||||||
|
@AndroidEntryPoint
|
||||||
|
class SplashActivity : ComponentActivity() {
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
lateinit var appStateRepository: AppStateRepository
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
lateinit var localLogCollector: LocalLogCollector
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
@ApplicationScope
|
||||||
|
lateinit var applicationScope: CoroutineScope
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||||
|
val splashScreen = installSplashScreen()
|
||||||
|
splashScreen.setKeepOnScreenCondition { true }
|
||||||
|
}
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
|
applicationScope.launch {
|
||||||
|
if (!isRunningOnAndroidTv()) localLogCollector.start()
|
||||||
|
}
|
||||||
|
|
||||||
|
lifecycleScope.launch {
|
||||||
|
repeatOnLifecycle(Lifecycle.State.CREATED) {
|
||||||
|
val pinLockEnabled = appStateRepository.isPinLockEnabled()
|
||||||
|
if (pinLockEnabled) {
|
||||||
|
PinManager.initialize(WireGuardAutoTunnel.instance)
|
||||||
|
}
|
||||||
|
|
||||||
|
val intent = Intent(this@SplashActivity, MainActivity::class.java).apply {
|
||||||
|
putExtra(IS_PIN_LOCK_ENABLED_KEY, pinLockEnabled)
|
||||||
|
}
|
||||||
|
startActivity(intent)
|
||||||
|
finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val IS_PIN_LOCK_ENABLED_KEY = "is_pin_lock_enabled"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -15,6 +15,9 @@ import androidx.compose.runtime.Composable
|
|||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.clip
|
import androidx.compose.ui.draw.clip
|
||||||
|
import androidx.compose.ui.focus.FocusRequester
|
||||||
|
import androidx.compose.ui.focus.focusRequester
|
||||||
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
import com.zaneschepke.wireguardautotunnel.service.tunnel.statistics.TunnelStatistics
|
import com.zaneschepke.wireguardautotunnel.service.tunnel.statistics.TunnelStatistics
|
||||||
@@ -30,11 +33,12 @@ fun RowListItem(
|
|||||||
onClick: () -> Unit,
|
onClick: () -> Unit,
|
||||||
rowButton: @Composable () -> Unit,
|
rowButton: @Composable () -> Unit,
|
||||||
expanded: Boolean,
|
expanded: Boolean,
|
||||||
statistics: TunnelStatistics?
|
statistics: TunnelStatistics?,
|
||||||
|
focusRequester: FocusRequester,
|
||||||
) {
|
) {
|
||||||
Box(
|
Box(
|
||||||
modifier =
|
modifier =
|
||||||
Modifier
|
Modifier.focusRequester(focusRequester)
|
||||||
.animateContentSize()
|
.animateContentSize()
|
||||||
.clip(RoundedCornerShape(30.dp))
|
.clip(RoundedCornerShape(30.dp))
|
||||||
.combinedClickable(
|
.combinedClickable(
|
||||||
@@ -52,9 +56,10 @@ fun RowListItem(
|
|||||||
) {
|
) {
|
||||||
Row(
|
Row(
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
modifier = Modifier.fillMaxWidth(13 / 20f),
|
||||||
) {
|
) {
|
||||||
icon()
|
icon()
|
||||||
Text(text)
|
Text(text, maxLines = 1, overflow = TextOverflow.Ellipsis)
|
||||||
}
|
}
|
||||||
rowButton()
|
rowButton()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,19 +44,21 @@ fun SearchBar(onQuery: (queryString: String) -> Unit) {
|
|||||||
onQuery(onQueryChanged)
|
onQuery(onQueryChanged)
|
||||||
},
|
},
|
||||||
leadingIcon = {
|
leadingIcon = {
|
||||||
|
val icon = Icons.Rounded.Search
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.Rounded.Search,
|
imageVector = icon,
|
||||||
tint = MaterialTheme.colorScheme.onBackground,
|
tint = MaterialTheme.colorScheme.onBackground,
|
||||||
contentDescription = stringResource(id = R.string.search_icon),
|
contentDescription = icon.name,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
trailingIcon = {
|
trailingIcon = {
|
||||||
if (showClearIcon) {
|
if (showClearIcon) {
|
||||||
IconButton(onClick = { query = "" }) {
|
IconButton(onClick = { query = "" }) {
|
||||||
|
val icon = Icons.Rounded.Clear
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.Rounded.Clear,
|
imageVector = icon,
|
||||||
tint = MaterialTheme.colorScheme.onBackground,
|
tint = MaterialTheme.colorScheme.onBackground,
|
||||||
contentDescription = stringResource(id = R.string.clear_icon),
|
contentDescription = icon.name,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+5
-2
@@ -28,12 +28,15 @@ fun ConfigurationToggle(
|
|||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
horizontalArrangement = Arrangement.SpaceBetween,
|
horizontalArrangement = Arrangement.SpaceBetween,
|
||||||
) {
|
) {
|
||||||
Text(label, textAlign = TextAlign.Start, modifier = Modifier
|
Text(
|
||||||
|
label, textAlign = TextAlign.Start,
|
||||||
|
modifier = Modifier
|
||||||
.weight(
|
.weight(
|
||||||
weight = 1.0f,
|
weight = 1.0f,
|
||||||
fill = false,
|
fill = false,
|
||||||
),
|
),
|
||||||
softWrap = true)
|
softWrap = true,
|
||||||
|
)
|
||||||
Switch(
|
Switch(
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
enabled = enabled,
|
enabled = enabled,
|
||||||
|
|||||||
+30
-6
@@ -496,7 +496,11 @@ fun ConfigScreen(
|
|||||||
)
|
)
|
||||||
ConfigurationTextBox(
|
ConfigurationTextBox(
|
||||||
value = uiState.interfaceProxy.junkPacketMinSize,
|
value = uiState.interfaceProxy.junkPacketMinSize,
|
||||||
onValueChange = { value -> viewModel.onJunkPacketMinSizeChanged(value) },
|
onValueChange = { value ->
|
||||||
|
viewModel.onJunkPacketMinSizeChanged(
|
||||||
|
value,
|
||||||
|
)
|
||||||
|
},
|
||||||
keyboardActions = keyboardActions,
|
keyboardActions = keyboardActions,
|
||||||
label = stringResource(R.string.junk_packet_minimum_size),
|
label = stringResource(R.string.junk_packet_minimum_size),
|
||||||
hint = stringResource(R.string.junk_packet_minimum_size).lowercase(),
|
hint = stringResource(R.string.junk_packet_minimum_size).lowercase(),
|
||||||
@@ -506,7 +510,11 @@ fun ConfigScreen(
|
|||||||
)
|
)
|
||||||
ConfigurationTextBox(
|
ConfigurationTextBox(
|
||||||
value = uiState.interfaceProxy.junkPacketMaxSize,
|
value = uiState.interfaceProxy.junkPacketMaxSize,
|
||||||
onValueChange = { value -> viewModel.onJunkPacketMaxSizeChanged(value) },
|
onValueChange = { value ->
|
||||||
|
viewModel.onJunkPacketMaxSizeChanged(
|
||||||
|
value,
|
||||||
|
)
|
||||||
|
},
|
||||||
keyboardActions = keyboardActions,
|
keyboardActions = keyboardActions,
|
||||||
label = stringResource(R.string.junk_packet_maximum_size),
|
label = stringResource(R.string.junk_packet_maximum_size),
|
||||||
hint = stringResource(R.string.junk_packet_maximum_size).lowercase(),
|
hint = stringResource(R.string.junk_packet_maximum_size).lowercase(),
|
||||||
@@ -516,7 +524,11 @@ fun ConfigScreen(
|
|||||||
)
|
)
|
||||||
ConfigurationTextBox(
|
ConfigurationTextBox(
|
||||||
value = uiState.interfaceProxy.initPacketJunkSize,
|
value = uiState.interfaceProxy.initPacketJunkSize,
|
||||||
onValueChange = { value -> viewModel.onInitPacketJunkSizeChanged(value) },
|
onValueChange = { value ->
|
||||||
|
viewModel.onInitPacketJunkSizeChanged(
|
||||||
|
value,
|
||||||
|
)
|
||||||
|
},
|
||||||
keyboardActions = keyboardActions,
|
keyboardActions = keyboardActions,
|
||||||
label = stringResource(R.string.init_packet_junk_size),
|
label = stringResource(R.string.init_packet_junk_size),
|
||||||
hint = stringResource(R.string.init_packet_junk_size).lowercase(),
|
hint = stringResource(R.string.init_packet_junk_size).lowercase(),
|
||||||
@@ -546,7 +558,11 @@ fun ConfigScreen(
|
|||||||
)
|
)
|
||||||
ConfigurationTextBox(
|
ConfigurationTextBox(
|
||||||
value = uiState.interfaceProxy.responsePacketMagicHeader,
|
value = uiState.interfaceProxy.responsePacketMagicHeader,
|
||||||
onValueChange = { value -> viewModel.onResponsePacketMagicHeader(value) },
|
onValueChange = { value ->
|
||||||
|
viewModel.onResponsePacketMagicHeader(
|
||||||
|
value,
|
||||||
|
)
|
||||||
|
},
|
||||||
keyboardActions = keyboardActions,
|
keyboardActions = keyboardActions,
|
||||||
label = stringResource(R.string.response_packet_magic_header),
|
label = stringResource(R.string.response_packet_magic_header),
|
||||||
hint = stringResource(R.string.response_packet_magic_header).lowercase(),
|
hint = stringResource(R.string.response_packet_magic_header).lowercase(),
|
||||||
@@ -556,7 +572,11 @@ fun ConfigScreen(
|
|||||||
)
|
)
|
||||||
ConfigurationTextBox(
|
ConfigurationTextBox(
|
||||||
value = uiState.interfaceProxy.underloadPacketMagicHeader,
|
value = uiState.interfaceProxy.underloadPacketMagicHeader,
|
||||||
onValueChange = { value -> viewModel.onUnderloadPacketMagicHeader(value) },
|
onValueChange = { value ->
|
||||||
|
viewModel.onUnderloadPacketMagicHeader(
|
||||||
|
value,
|
||||||
|
)
|
||||||
|
},
|
||||||
keyboardActions = keyboardActions,
|
keyboardActions = keyboardActions,
|
||||||
label = stringResource(R.string.underload_packet_magic_header),
|
label = stringResource(R.string.underload_packet_magic_header),
|
||||||
hint = stringResource(R.string.underload_packet_magic_header).lowercase(),
|
hint = stringResource(R.string.underload_packet_magic_header).lowercase(),
|
||||||
@@ -566,7 +586,11 @@ fun ConfigScreen(
|
|||||||
)
|
)
|
||||||
ConfigurationTextBox(
|
ConfigurationTextBox(
|
||||||
value = uiState.interfaceProxy.transportPacketMagicHeader,
|
value = uiState.interfaceProxy.transportPacketMagicHeader,
|
||||||
onValueChange = { value -> viewModel.onTransportPacketMagicHeader(value) },
|
onValueChange = { value ->
|
||||||
|
viewModel.onTransportPacketMagicHeader(
|
||||||
|
value,
|
||||||
|
)
|
||||||
|
},
|
||||||
keyboardActions = keyboardActions,
|
keyboardActions = keyboardActions,
|
||||||
label = stringResource(R.string.transport_packet_magic_header),
|
label = stringResource(R.string.transport_packet_magic_header),
|
||||||
hint = stringResource(R.string.transport_packet_magic_header).lowercase(),
|
hint = stringResource(R.string.transport_packet_magic_header).lowercase(),
|
||||||
|
|||||||
+1
@@ -43,6 +43,7 @@ data class ConfigUiState(
|
|||||||
isAllApplicationsEnabled,
|
isAllApplicationsEnabled,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun from(config: org.amnezia.awg.config.Config): ConfigUiState {
|
fun from(config: org.amnezia.awg.config.Config): ConfigUiState {
|
||||||
//TODO update with new values
|
//TODO update with new values
|
||||||
val proxyPeers = config.peers.map { PeerProxy.from(it) }
|
val proxyPeers = config.peers.map { PeerProxy.from(it) }
|
||||||
|
|||||||
+47
-25
@@ -16,6 +16,7 @@ import com.zaneschepke.wireguardautotunnel.WireGuardAutoTunnel
|
|||||||
import com.zaneschepke.wireguardautotunnel.data.domain.TunnelConfig
|
import com.zaneschepke.wireguardautotunnel.data.domain.TunnelConfig
|
||||||
import com.zaneschepke.wireguardautotunnel.data.repository.AppDataRepository
|
import com.zaneschepke.wireguardautotunnel.data.repository.AppDataRepository
|
||||||
import com.zaneschepke.wireguardautotunnel.data.repository.SettingsRepository
|
import com.zaneschepke.wireguardautotunnel.data.repository.SettingsRepository
|
||||||
|
import com.zaneschepke.wireguardautotunnel.module.IoDispatcher
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.screens.config.model.PeerProxy
|
import com.zaneschepke.wireguardautotunnel.ui.screens.config.model.PeerProxy
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.screens.main.ConfigType
|
import com.zaneschepke.wireguardautotunnel.ui.screens.main.ConfigType
|
||||||
import com.zaneschepke.wireguardautotunnel.util.Constants
|
import com.zaneschepke.wireguardautotunnel.util.Constants
|
||||||
@@ -25,7 +26,7 @@ import com.zaneschepke.wireguardautotunnel.util.WgTunnelExceptions
|
|||||||
import com.zaneschepke.wireguardautotunnel.util.removeAt
|
import com.zaneschepke.wireguardautotunnel.util.removeAt
|
||||||
import com.zaneschepke.wireguardautotunnel.util.update
|
import com.zaneschepke.wireguardautotunnel.util.update
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.CoroutineDispatcher
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.asStateFlow
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
import kotlinx.coroutines.flow.update
|
import kotlinx.coroutines.flow.update
|
||||||
@@ -38,7 +39,8 @@ class ConfigViewModel
|
|||||||
@Inject
|
@Inject
|
||||||
constructor(
|
constructor(
|
||||||
private val settingsRepository: SettingsRepository,
|
private val settingsRepository: SettingsRepository,
|
||||||
private val appDataRepository: AppDataRepository
|
private val appDataRepository: AppDataRepository,
|
||||||
|
@IoDispatcher private val ioDispatcher: CoroutineDispatcher
|
||||||
) : ViewModel() {
|
) : ViewModel() {
|
||||||
|
|
||||||
private val packageManager = WireGuardAutoTunnel.instance.packageManager
|
private val packageManager = WireGuardAutoTunnel.instance.packageManager
|
||||||
@@ -47,7 +49,7 @@ constructor(
|
|||||||
val uiState = _uiState.asStateFlow()
|
val uiState = _uiState.asStateFlow()
|
||||||
|
|
||||||
fun init(tunnelId: String) =
|
fun init(tunnelId: String) =
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
viewModelScope.launch(ioDispatcher) {
|
||||||
val packages = getQueriedPackages("")
|
val packages = getQueriedPackages("")
|
||||||
val state =
|
val state =
|
||||||
if (tunnelId != Constants.MANUAL_TUNNEL_CONFIG_ID) {
|
if (tunnelId != Constants.MANUAL_TUNNEL_CONFIG_ID) {
|
||||||
@@ -57,14 +59,15 @@ constructor(
|
|||||||
val isAmneziaEnabled = settingsRepository.getSettings().isAmneziaEnabled
|
val isAmneziaEnabled = settingsRepository.getSettings().isAmneziaEnabled
|
||||||
if (tunnelConfig != null) {
|
if (tunnelConfig != null) {
|
||||||
(if (isAmneziaEnabled) {
|
(if (isAmneziaEnabled) {
|
||||||
val amConfig = if(tunnelConfig.amQuick == "") tunnelConfig.wgQuick else tunnelConfig.amQuick
|
val amConfig =
|
||||||
|
if (tunnelConfig.amQuick == "") tunnelConfig.wgQuick else tunnelConfig.amQuick
|
||||||
ConfigUiState.from(TunnelConfig.configFromAmQuick(amConfig))
|
ConfigUiState.from(TunnelConfig.configFromAmQuick(amConfig))
|
||||||
} else ConfigUiState.from(TunnelConfig.configFromWgQuick(tunnelConfig.wgQuick))).copy(
|
} else ConfigUiState.from(TunnelConfig.configFromWgQuick(tunnelConfig.wgQuick))).copy(
|
||||||
packages = packages,
|
packages = packages,
|
||||||
loading = false,
|
loading = false,
|
||||||
tunnel = tunnelConfig,
|
tunnel = tunnelConfig,
|
||||||
tunnelName = tunnelConfig.name,
|
tunnelName = tunnelConfig.name,
|
||||||
isAmneziaEnabled = isAmneziaEnabled
|
isAmneziaEnabled = isAmneziaEnabled,
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
ConfigUiState(loading = false, packages = packages)
|
ConfigUiState(loading = false, packages = packages)
|
||||||
@@ -210,28 +213,44 @@ constructor(
|
|||||||
builder.setJunkPacketCount(_uiState.value.interfaceProxy.junkPacketCount.trim().toInt())
|
builder.setJunkPacketCount(_uiState.value.interfaceProxy.junkPacketCount.trim().toInt())
|
||||||
}
|
}
|
||||||
if (_uiState.value.interfaceProxy.junkPacketMinSize.isNotEmpty()) {
|
if (_uiState.value.interfaceProxy.junkPacketMinSize.isNotEmpty()) {
|
||||||
builder.setJunkPacketMinSize(_uiState.value.interfaceProxy.junkPacketMinSize.trim().toInt())
|
builder.setJunkPacketMinSize(
|
||||||
|
_uiState.value.interfaceProxy.junkPacketMinSize.trim().toInt(),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
if (_uiState.value.interfaceProxy.junkPacketMaxSize.isNotEmpty()) {
|
if (_uiState.value.interfaceProxy.junkPacketMaxSize.isNotEmpty()) {
|
||||||
builder.setJunkPacketMaxSize(_uiState.value.interfaceProxy.junkPacketMaxSize.trim().toInt())
|
builder.setJunkPacketMaxSize(
|
||||||
|
_uiState.value.interfaceProxy.junkPacketMaxSize.trim().toInt(),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
if (_uiState.value.interfaceProxy.initPacketJunkSize.isNotEmpty()) {
|
if (_uiState.value.interfaceProxy.initPacketJunkSize.isNotEmpty()) {
|
||||||
builder.setInitPacketJunkSize(_uiState.value.interfaceProxy.initPacketJunkSize.trim().toInt())
|
builder.setInitPacketJunkSize(
|
||||||
|
_uiState.value.interfaceProxy.initPacketJunkSize.trim().toInt(),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
if (_uiState.value.interfaceProxy.responsePacketJunkSize.isNotEmpty()) {
|
if (_uiState.value.interfaceProxy.responsePacketJunkSize.isNotEmpty()) {
|
||||||
builder.setResponsePacketJunkSize(_uiState.value.interfaceProxy.responsePacketJunkSize.trim().toInt())
|
builder.setResponsePacketJunkSize(
|
||||||
|
_uiState.value.interfaceProxy.responsePacketJunkSize.trim().toInt(),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
if (_uiState.value.interfaceProxy.initPacketMagicHeader.isNotEmpty()) {
|
if (_uiState.value.interfaceProxy.initPacketMagicHeader.isNotEmpty()) {
|
||||||
builder.setInitPacketMagicHeader(_uiState.value.interfaceProxy.initPacketMagicHeader.trim().toLong())
|
builder.setInitPacketMagicHeader(
|
||||||
|
_uiState.value.interfaceProxy.initPacketMagicHeader.trim().toLong(),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
if (_uiState.value.interfaceProxy.responsePacketMagicHeader.isNotEmpty()) {
|
if (_uiState.value.interfaceProxy.responsePacketMagicHeader.isNotEmpty()) {
|
||||||
builder.setResponsePacketMagicHeader(_uiState.value.interfaceProxy.responsePacketMagicHeader.trim().toLong())
|
builder.setResponsePacketMagicHeader(
|
||||||
|
_uiState.value.interfaceProxy.responsePacketMagicHeader.trim().toLong(),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
if (_uiState.value.interfaceProxy.transportPacketMagicHeader.isNotEmpty()) {
|
if (_uiState.value.interfaceProxy.transportPacketMagicHeader.isNotEmpty()) {
|
||||||
builder.setTransportPacketMagicHeader(_uiState.value.interfaceProxy.transportPacketMagicHeader.trim().toLong())
|
builder.setTransportPacketMagicHeader(
|
||||||
|
_uiState.value.interfaceProxy.transportPacketMagicHeader.trim().toLong(),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
if (_uiState.value.interfaceProxy.underloadPacketMagicHeader.isNotEmpty()) {
|
if (_uiState.value.interfaceProxy.underloadPacketMagicHeader.isNotEmpty()) {
|
||||||
builder.setUnderloadPacketMagicHeader(_uiState.value.interfaceProxy.underloadPacketMagicHeader.trim().toLong())
|
builder.setUnderloadPacketMagicHeader(
|
||||||
|
_uiState.value.interfaceProxy.underloadPacketMagicHeader.trim().toLong(),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
return builder.build()
|
return builder.build()
|
||||||
}
|
}
|
||||||
@@ -245,7 +264,8 @@ constructor(
|
|||||||
private fun buildAmConfig(): org.amnezia.awg.config.Config {
|
private fun buildAmConfig(): org.amnezia.awg.config.Config {
|
||||||
val peerList = buildAmPeerListFromProxyPeers()
|
val peerList = buildAmPeerListFromProxyPeers()
|
||||||
val amInterface = buildAmInterfaceListFromProxyInterface()
|
val amInterface = buildAmInterfaceListFromProxyInterface()
|
||||||
return org.amnezia.awg.config.Config.Builder().addPeers(peerList).setInterface(amInterface).build()
|
return org.amnezia.awg.config.Config.Builder().addPeers(peerList).setInterface(amInterface)
|
||||||
|
.build()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onSaveAllChanges(configType: ConfigType): Result<Unit> {
|
fun onSaveAllChanges(configType: ConfigType): Result<Unit> {
|
||||||
@@ -258,12 +278,13 @@ constructor(
|
|||||||
null -> TunnelConfig(
|
null -> TunnelConfig(
|
||||||
name = _uiState.value.tunnelName,
|
name = _uiState.value.tunnelName,
|
||||||
wgQuick = wgQuick,
|
wgQuick = wgQuick,
|
||||||
amQuick = amQuick
|
amQuick = amQuick,
|
||||||
)
|
)
|
||||||
|
|
||||||
else -> uiState.value.tunnel!!.copy(
|
else -> uiState.value.tunnel!!.copy(
|
||||||
name = _uiState.value.tunnelName,
|
name = _uiState.value.tunnelName,
|
||||||
wgQuick = wgQuick,
|
wgQuick = wgQuick,
|
||||||
amQuick = amQuick
|
amQuick = amQuick,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
updateTunnelConfig(tunnelConfig)
|
updateTunnelConfig(tunnelConfig)
|
||||||
@@ -430,14 +451,15 @@ constructor(
|
|||||||
fun onJunkPacketCountChanged(value: String) {
|
fun onJunkPacketCountChanged(value: String) {
|
||||||
_uiState.update {
|
_uiState.update {
|
||||||
it.copy(
|
it.copy(
|
||||||
interfaceProxy = _uiState.value.interfaceProxy.copy(junkPacketCount = value)
|
interfaceProxy = _uiState.value.interfaceProxy.copy(junkPacketCount = value),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onJunkPacketMinSizeChanged(value: String) {
|
fun onJunkPacketMinSizeChanged(value: String) {
|
||||||
_uiState.update {
|
_uiState.update {
|
||||||
it.copy(
|
it.copy(
|
||||||
interfaceProxy = _uiState.value.interfaceProxy.copy(junkPacketMinSize = value)
|
interfaceProxy = _uiState.value.interfaceProxy.copy(junkPacketMinSize = value),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -445,7 +467,7 @@ constructor(
|
|||||||
fun onJunkPacketMaxSizeChanged(value: String) {
|
fun onJunkPacketMaxSizeChanged(value: String) {
|
||||||
_uiState.update {
|
_uiState.update {
|
||||||
it.copy(
|
it.copy(
|
||||||
interfaceProxy = _uiState.value.interfaceProxy.copy(junkPacketMaxSize = value)
|
interfaceProxy = _uiState.value.interfaceProxy.copy(junkPacketMaxSize = value),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -453,7 +475,7 @@ constructor(
|
|||||||
fun onInitPacketJunkSizeChanged(value: String) {
|
fun onInitPacketJunkSizeChanged(value: String) {
|
||||||
_uiState.update {
|
_uiState.update {
|
||||||
it.copy(
|
it.copy(
|
||||||
interfaceProxy = _uiState.value.interfaceProxy.copy(initPacketJunkSize = value)
|
interfaceProxy = _uiState.value.interfaceProxy.copy(initPacketJunkSize = value),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -461,7 +483,7 @@ constructor(
|
|||||||
fun onResponsePacketJunkSize(value: String) {
|
fun onResponsePacketJunkSize(value: String) {
|
||||||
_uiState.update {
|
_uiState.update {
|
||||||
it.copy(
|
it.copy(
|
||||||
interfaceProxy = _uiState.value.interfaceProxy.copy(responsePacketJunkSize = value)
|
interfaceProxy = _uiState.value.interfaceProxy.copy(responsePacketJunkSize = value),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -469,7 +491,7 @@ constructor(
|
|||||||
fun onInitPacketMagicHeader(value: String) {
|
fun onInitPacketMagicHeader(value: String) {
|
||||||
_uiState.update {
|
_uiState.update {
|
||||||
it.copy(
|
it.copy(
|
||||||
interfaceProxy = _uiState.value.interfaceProxy.copy(initPacketMagicHeader = value)
|
interfaceProxy = _uiState.value.interfaceProxy.copy(initPacketMagicHeader = value),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -477,7 +499,7 @@ constructor(
|
|||||||
fun onResponsePacketMagicHeader(value: String) {
|
fun onResponsePacketMagicHeader(value: String) {
|
||||||
_uiState.update {
|
_uiState.update {
|
||||||
it.copy(
|
it.copy(
|
||||||
interfaceProxy = _uiState.value.interfaceProxy.copy(responsePacketMagicHeader = value)
|
interfaceProxy = _uiState.value.interfaceProxy.copy(responsePacketMagicHeader = value),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -485,7 +507,7 @@ constructor(
|
|||||||
fun onTransportPacketMagicHeader(value: String) {
|
fun onTransportPacketMagicHeader(value: String) {
|
||||||
_uiState.update {
|
_uiState.update {
|
||||||
it.copy(
|
it.copy(
|
||||||
interfaceProxy = _uiState.value.interfaceProxy.copy(transportPacketMagicHeader = value)
|
interfaceProxy = _uiState.value.interfaceProxy.copy(transportPacketMagicHeader = value),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -493,7 +515,7 @@ constructor(
|
|||||||
fun onUnderloadPacketMagicHeader(value: String) {
|
fun onUnderloadPacketMagicHeader(value: String) {
|
||||||
_uiState.update {
|
_uiState.update {
|
||||||
it.copy(
|
it.copy(
|
||||||
interfaceProxy = _uiState.value.interfaceProxy.copy(underloadPacketMagicHeader = value)
|
interfaceProxy = _uiState.value.interfaceProxy.copy(underloadPacketMagicHeader = value),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+19
-9
@@ -35,6 +35,7 @@ data class InterfaceProxy(
|
|||||||
mtu = if (i.mtu.isPresent) i.mtu.get().toString().trim() else "",
|
mtu = if (i.mtu.isPresent) i.mtu.get().toString().trim() else "",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun from(i: org.amnezia.awg.config.Interface): InterfaceProxy {
|
fun from(i: org.amnezia.awg.config.Interface): InterfaceProxy {
|
||||||
return InterfaceProxy(
|
return InterfaceProxy(
|
||||||
publicKey = i.keyPair.publicKey.toBase64().trim(),
|
publicKey = i.keyPair.publicKey.toBase64().trim(),
|
||||||
@@ -48,15 +49,24 @@ data class InterfaceProxy(
|
|||||||
""
|
""
|
||||||
},
|
},
|
||||||
mtu = if (i.mtu.isPresent) i.mtu.get().toString().trim() else "",
|
mtu = if (i.mtu.isPresent) i.mtu.get().toString().trim() else "",
|
||||||
junkPacketCount = if(i.junkPacketCount.isPresent) i.junkPacketCount.get().toString() else "",
|
junkPacketCount = if (i.junkPacketCount.isPresent) i.junkPacketCount.get()
|
||||||
junkPacketMinSize = if(i.junkPacketMinSize.isPresent) i.junkPacketMinSize.get().toString() else "",
|
.toString() else "",
|
||||||
junkPacketMaxSize = if(i.junkPacketMaxSize.isPresent) i.junkPacketMaxSize.get().toString() else "",
|
junkPacketMinSize = if (i.junkPacketMinSize.isPresent) i.junkPacketMinSize.get()
|
||||||
initPacketJunkSize = if(i.initPacketJunkSize.isPresent) i.initPacketJunkSize.get().toString() else "",
|
.toString() else "",
|
||||||
responsePacketJunkSize = if(i.responsePacketJunkSize.isPresent) i.responsePacketJunkSize.get().toString() else "",
|
junkPacketMaxSize = if (i.junkPacketMaxSize.isPresent) i.junkPacketMaxSize.get()
|
||||||
initPacketMagicHeader = if(i.initPacketMagicHeader.isPresent) i.initPacketMagicHeader.get().toString() else "",
|
.toString() else "",
|
||||||
responsePacketMagicHeader = if(i.responsePacketMagicHeader.isPresent) i.responsePacketMagicHeader.get().toString() else "",
|
initPacketJunkSize = if (i.initPacketJunkSize.isPresent) i.initPacketJunkSize.get()
|
||||||
transportPacketMagicHeader = if(i.transportPacketMagicHeader.isPresent) i.transportPacketMagicHeader.get().toString() else "",
|
.toString() else "",
|
||||||
underloadPacketMagicHeader = if(i.underloadPacketMagicHeader.isPresent) i.underloadPacketMagicHeader.get().toString() else "",
|
responsePacketJunkSize = if (i.responsePacketJunkSize.isPresent) i.responsePacketJunkSize.get()
|
||||||
|
.toString() else "",
|
||||||
|
initPacketMagicHeader = if (i.initPacketMagicHeader.isPresent) i.initPacketMagicHeader.get()
|
||||||
|
.toString() else "",
|
||||||
|
responsePacketMagicHeader = if (i.responsePacketMagicHeader.isPresent) i.responsePacketMagicHeader.get()
|
||||||
|
.toString() else "",
|
||||||
|
transportPacketMagicHeader = if (i.transportPacketMagicHeader.isPresent) i.transportPacketMagicHeader.get()
|
||||||
|
.toString() else "",
|
||||||
|
underloadPacketMagicHeader = if (i.underloadPacketMagicHeader.isPresent) i.underloadPacketMagicHeader.get()
|
||||||
|
.toString() else "",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+81
-48
@@ -14,6 +14,7 @@ import androidx.compose.animation.slideInVertically
|
|||||||
import androidx.compose.animation.slideOutVertically
|
import androidx.compose.animation.slideOutVertically
|
||||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.focusGroup
|
||||||
import androidx.compose.foundation.focusable
|
import androidx.compose.foundation.focusable
|
||||||
import androidx.compose.foundation.gestures.ScrollableDefaults
|
import androidx.compose.foundation.gestures.ScrollableDefaults
|
||||||
import androidx.compose.foundation.gestures.detectTapGestures
|
import androidx.compose.foundation.gestures.detectTapGestures
|
||||||
@@ -69,7 +70,6 @@ import androidx.compose.ui.Alignment
|
|||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.focus.FocusRequester
|
import androidx.compose.ui.focus.FocusRequester
|
||||||
import androidx.compose.ui.focus.focusRequester
|
import androidx.compose.ui.focus.focusRequester
|
||||||
import androidx.compose.ui.focus.onFocusChanged
|
|
||||||
import androidx.compose.ui.geometry.Offset
|
import androidx.compose.ui.geometry.Offset
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
|
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
|
||||||
@@ -101,7 +101,6 @@ import com.zaneschepke.wireguardautotunnel.data.domain.TunnelConfig
|
|||||||
import com.zaneschepke.wireguardautotunnel.service.tunnel.HandshakeStatus
|
import com.zaneschepke.wireguardautotunnel.service.tunnel.HandshakeStatus
|
||||||
import com.zaneschepke.wireguardautotunnel.service.tunnel.TunnelState
|
import com.zaneschepke.wireguardautotunnel.service.tunnel.TunnelState
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.AppViewModel
|
import com.zaneschepke.wireguardautotunnel.ui.AppViewModel
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.CaptureActivityPortrait
|
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.Screen
|
import com.zaneschepke.wireguardautotunnel.ui.Screen
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.common.RowListItem
|
import com.zaneschepke.wireguardautotunnel.ui.common.RowListItem
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.common.screen.LoadingScreen
|
import com.zaneschepke.wireguardautotunnel.ui.common.screen.LoadingScreen
|
||||||
@@ -111,8 +110,6 @@ import com.zaneschepke.wireguardautotunnel.util.Constants
|
|||||||
import com.zaneschepke.wireguardautotunnel.util.getMessage
|
import com.zaneschepke.wireguardautotunnel.util.getMessage
|
||||||
import com.zaneschepke.wireguardautotunnel.util.handshakeStatus
|
import com.zaneschepke.wireguardautotunnel.util.handshakeStatus
|
||||||
import com.zaneschepke.wireguardautotunnel.util.mapPeerStats
|
import com.zaneschepke.wireguardautotunnel.util.mapPeerStats
|
||||||
import com.zaneschepke.wireguardautotunnel.util.truncateWithEllipsis
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
@@ -128,7 +125,7 @@ fun MainScreen(
|
|||||||
val haptic = LocalHapticFeedback.current
|
val haptic = LocalHapticFeedback.current
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
val isVisible = rememberSaveable { mutableStateOf(true) }
|
val isVisible = rememberSaveable { mutableStateOf(true) }
|
||||||
val scope = rememberCoroutineScope { Dispatchers.IO }
|
val scope = rememberCoroutineScope()
|
||||||
|
|
||||||
val sheetState = rememberModalBottomSheetState()
|
val sheetState = rememberModalBottomSheetState()
|
||||||
var showBottomSheet by remember { mutableStateOf(false) }
|
var showBottomSheet by remember { mutableStateOf(false) }
|
||||||
@@ -212,8 +209,8 @@ fun MainScreen(
|
|||||||
onResult = {
|
onResult = {
|
||||||
if (it.contents != null) {
|
if (it.contents != null) {
|
||||||
scope.launch {
|
scope.launch {
|
||||||
viewModel.onTunnelQrResult(it.contents, configType).onFailure {
|
viewModel.onTunnelQrResult(it.contents, configType).onFailure { error ->
|
||||||
appViewModel.showSnackbarMessage(it.getMessage(context))
|
appViewModel.showSnackbarMessage(error.getMessage(context))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -246,7 +243,9 @@ fun MainScreen(
|
|||||||
|
|
||||||
fun onTunnelToggle(checked: Boolean, tunnel: TunnelConfig) {
|
fun onTunnelToggle(checked: Boolean, tunnel: TunnelConfig) {
|
||||||
if (appViewModel.isRequiredPermissionGranted()) {
|
if (appViewModel.isRequiredPermissionGranted()) {
|
||||||
if (checked) viewModel.onTunnelStart(tunnel, context) else viewModel.onTunnelStop(context)
|
if (checked) viewModel.onTunnelStart(tunnel, context) else viewModel.onTunnelStop(
|
||||||
|
context,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -262,8 +261,6 @@ fun MainScreen(
|
|||||||
context.getString(R.string.scanning_qr),
|
context.getString(R.string.scanning_qr),
|
||||||
)
|
)
|
||||||
scanOptions.setBeepEnabled(false)
|
scanOptions.setBeepEnabled(false)
|
||||||
scanOptions.captureActivity =
|
|
||||||
CaptureActivityPortrait::class.java
|
|
||||||
scanLauncher.launch(scanOptions)
|
scanLauncher.launch(scanOptions)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -285,31 +282,25 @@ fun MainScreen(
|
|||||||
visible = isVisible.value,
|
visible = isVisible.value,
|
||||||
enter = slideInVertically(initialOffsetY = { it * 2 }),
|
enter = slideInVertically(initialOffsetY = { it * 2 }),
|
||||||
exit = slideOutVertically(targetOffsetY = { it * 2 }),
|
exit = slideOutVertically(targetOffsetY = { it * 2 }),
|
||||||
|
modifier = Modifier
|
||||||
|
.focusRequester(focusRequester)
|
||||||
|
.focusGroup(),
|
||||||
) {
|
) {
|
||||||
val secondaryColor = MaterialTheme.colorScheme.secondary
|
val secondaryColor = MaterialTheme.colorScheme.secondary
|
||||||
val hoverColor = MaterialTheme.colorScheme.surfaceColorAtElevation(2.dp)
|
val tvFobColor = MaterialTheme.colorScheme.surfaceColorAtElevation(2.dp)
|
||||||
var fobColor by remember { mutableStateOf(secondaryColor) }
|
val fobColor =
|
||||||
|
if (WireGuardAutoTunnel.isRunningOnAndroidTv()) tvFobColor else secondaryColor
|
||||||
|
val fobIconColor =
|
||||||
|
if (WireGuardAutoTunnel.isRunningOnAndroidTv()) Color.White else MaterialTheme.colorScheme.background
|
||||||
MultiFloatingActionButton(
|
MultiFloatingActionButton(
|
||||||
modifier =
|
|
||||||
(if (
|
|
||||||
WireGuardAutoTunnel.isRunningOnAndroidTv() &&
|
|
||||||
uiState.tunnels.isEmpty()
|
|
||||||
)
|
|
||||||
Modifier.focusRequester(focusRequester)
|
|
||||||
else Modifier)
|
|
||||||
.onFocusChanged {
|
|
||||||
if (WireGuardAutoTunnel.isRunningOnAndroidTv()) {
|
|
||||||
fobColor = if (it.isFocused) hoverColor else secondaryColor
|
|
||||||
}
|
|
||||||
},
|
|
||||||
fabIcon = FabIcon(
|
fabIcon = FabIcon(
|
||||||
iconRes = R.drawable.add,
|
iconRes = R.drawable.add,
|
||||||
iconResAfterRotate = R.drawable.close,
|
iconResAfterRotate = R.drawable.close,
|
||||||
iconRotate = 180f
|
iconRotate = 180f,
|
||||||
),
|
),
|
||||||
fabOption = FabOption(
|
fabOption = FabOption(
|
||||||
iconTint = MaterialTheme.colorScheme.background,
|
iconTint = fobIconColor,
|
||||||
backgroundTint = MaterialTheme.colorScheme.primary,
|
backgroundTint = fobColor,
|
||||||
),
|
),
|
||||||
itemsMultiFab = listOf(
|
itemsMultiFab = listOf(
|
||||||
MultiFabItem(
|
MultiFabItem(
|
||||||
@@ -318,18 +309,33 @@ fun MainScreen(
|
|||||||
stringResource(id = R.string.amnezia),
|
stringResource(id = R.string.amnezia),
|
||||||
color = Color.White,
|
color = Color.White,
|
||||||
textAlign = TextAlign.Center,
|
textAlign = TextAlign.Center,
|
||||||
modifier = Modifier.padding(end = 10.dp)
|
modifier = Modifier.padding(end = 10.dp),
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
modifier = Modifier
|
||||||
|
.size(40.dp),
|
||||||
icon = R.drawable.add,
|
icon = R.drawable.add,
|
||||||
value = ConfigType.AMNEZIA.name,
|
value = ConfigType.AMNEZIA.name,
|
||||||
|
miniFabOption = FabOption(
|
||||||
|
backgroundTint = fobColor,
|
||||||
|
fobIconColor,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
MultiFabItem(
|
MultiFabItem(
|
||||||
label = {
|
label = {
|
||||||
Text(stringResource(id = R.string.wireguard), color = Color.White, textAlign = TextAlign.Center, modifier = Modifier.padding(end = 10.dp))
|
Text(
|
||||||
|
stringResource(id = R.string.wireguard),
|
||||||
|
color = Color.White,
|
||||||
|
textAlign = TextAlign.Center,
|
||||||
|
modifier = Modifier.padding(end = 10.dp),
|
||||||
|
)
|
||||||
},
|
},
|
||||||
icon = R.drawable.add,
|
icon = R.drawable.add,
|
||||||
value = ConfigType.WIREGUARD.name
|
value = ConfigType.WIREGUARD.name,
|
||||||
|
miniFabOption = FabOption(
|
||||||
|
backgroundTint = fobColor,
|
||||||
|
fobIconColor,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
onFabItemClicked = {
|
onFabItemClicked = {
|
||||||
@@ -343,7 +349,10 @@ fun MainScreen(
|
|||||||
) {
|
) {
|
||||||
if (showBottomSheet) {
|
if (showBottomSheet) {
|
||||||
ModalBottomSheet(
|
ModalBottomSheet(
|
||||||
onDismissRequest = { showBottomSheet = false },
|
onDismissRequest = {
|
||||||
|
showBottomSheet = false
|
||||||
|
|
||||||
|
},
|
||||||
sheetState = sheetState,
|
sheetState = sheetState,
|
||||||
) {
|
) {
|
||||||
// Sheet content
|
// Sheet content
|
||||||
@@ -432,10 +441,23 @@ fun MainScreen(
|
|||||||
flingBehavior = ScrollableDefaults.flingBehavior(),
|
flingBehavior = ScrollableDefaults.flingBehavior(),
|
||||||
) {
|
) {
|
||||||
item {
|
item {
|
||||||
|
AnimatedVisibility(
|
||||||
|
uiState.tunnels.isEmpty(), exit = fadeOut(), enter = fadeIn(),
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
|
verticalArrangement = Arrangement.Center,
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(top = 100.dp)
|
||||||
|
.fillMaxSize(),
|
||||||
|
) {
|
||||||
val gettingStarted = buildAnnotatedString {
|
val gettingStarted = buildAnnotatedString {
|
||||||
append(stringResource(id = R.string.see_the))
|
append(stringResource(id = R.string.see_the))
|
||||||
append(" ")
|
append(" ")
|
||||||
pushStringAnnotation(tag = "gettingStarted", annotation = stringResource(id = R.string.getting_started_url))
|
pushStringAnnotation(
|
||||||
|
tag = "gettingStarted",
|
||||||
|
annotation = stringResource(id = R.string.getting_started_url),
|
||||||
|
)
|
||||||
withStyle(style = SpanStyle(color = MaterialTheme.colorScheme.primary)) {
|
withStyle(style = SpanStyle(color = MaterialTheme.colorScheme.primary)) {
|
||||||
append(stringResource(id = R.string.getting_started_guide))
|
append(stringResource(id = R.string.getting_started_guide))
|
||||||
}
|
}
|
||||||
@@ -444,20 +466,21 @@ fun MainScreen(
|
|||||||
append(stringResource(R.string.unsure_how))
|
append(stringResource(R.string.unsure_how))
|
||||||
append(".")
|
append(".")
|
||||||
}
|
}
|
||||||
AnimatedVisibility(
|
Text(
|
||||||
uiState.tunnels.isEmpty(), exit = fadeOut(), enter = fadeIn()) {
|
text = stringResource(R.string.no_tunnels),
|
||||||
Column(
|
fontStyle = FontStyle.Italic,
|
||||||
horizontalAlignment = Alignment.CenterHorizontally,
|
)
|
||||||
verticalArrangement = Arrangement.Center,
|
|
||||||
modifier = Modifier.padding(top = 100.dp)
|
|
||||||
) {
|
|
||||||
Text(text = stringResource(R.string.no_tunnels), fontStyle = FontStyle.Italic)
|
|
||||||
ClickableText(
|
ClickableText(
|
||||||
modifier = Modifier.padding(vertical = 10.dp, horizontal = 24.dp),
|
modifier = Modifier
|
||||||
|
.padding(vertical = 10.dp, horizontal = 24.dp),
|
||||||
text = gettingStarted,
|
text = gettingStarted,
|
||||||
style = MaterialTheme.typography.bodyMedium.copy(color = MaterialTheme.colorScheme.onSurfaceVariant, textAlign = TextAlign.Center),
|
style = MaterialTheme.typography.bodyMedium.copy(
|
||||||
|
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
|
textAlign = TextAlign.Center,
|
||||||
|
),
|
||||||
) {
|
) {
|
||||||
gettingStarted.getStringAnnotations(tag = "gettingStarted", it, it).firstOrNull()?.let { annotation ->
|
gettingStarted.getStringAnnotations(tag = "gettingStarted", it, it)
|
||||||
|
.firstOrNull()?.let { annotation ->
|
||||||
appViewModel.openWebPage(annotation.item, context)
|
appViewModel.openWebPage(annotation.item, context)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -466,6 +489,7 @@ fun MainScreen(
|
|||||||
}
|
}
|
||||||
item {
|
item {
|
||||||
if (uiState.settings.isAutoTunnelEnabled) {
|
if (uiState.settings.isAutoTunnelEnabled) {
|
||||||
|
val itemFocusRequester = remember { FocusRequester() }
|
||||||
val autoTunnelingLabel = buildAnnotatedString {
|
val autoTunnelingLabel = buildAnnotatedString {
|
||||||
append(stringResource(id = R.string.auto_tunneling))
|
append(stringResource(id = R.string.auto_tunneling))
|
||||||
append(": ")
|
append(": ")
|
||||||
@@ -493,22 +517,29 @@ fun MainScreen(
|
|||||||
rowButton = {
|
rowButton = {
|
||||||
if (uiState.settings.isAutoTunnelPaused) {
|
if (uiState.settings.isAutoTunnelPaused) {
|
||||||
TextButton(
|
TextButton(
|
||||||
|
modifier = Modifier.focusRequester(itemFocusRequester),
|
||||||
onClick = { viewModel.resumeAutoTunneling() },
|
onClick = { viewModel.resumeAutoTunneling() },
|
||||||
) {
|
) {
|
||||||
Text(stringResource(id = R.string.resume))
|
Text(stringResource(id = R.string.resume))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
TextButton(
|
TextButton(
|
||||||
|
modifier = Modifier.focusRequester(itemFocusRequester),
|
||||||
onClick = { viewModel.pauseAutoTunneling() },
|
onClick = { viewModel.pauseAutoTunneling() },
|
||||||
) {
|
) {
|
||||||
Text(stringResource(id = R.string.pause))
|
Text(stringResource(id = R.string.pause))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onClick = {},
|
onClick = {
|
||||||
|
if (WireGuardAutoTunnel.isRunningOnAndroidTv()) {
|
||||||
|
itemFocusRequester.requestFocus()
|
||||||
|
}
|
||||||
|
},
|
||||||
onHold = {},
|
onHold = {},
|
||||||
expanded = false,
|
expanded = false,
|
||||||
statistics = null,
|
statistics = null,
|
||||||
|
focusRequester = focusRequester
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -539,6 +570,7 @@ fun MainScreen(
|
|||||||
} else {
|
} else {
|
||||||
Color.Gray
|
Color.Gray
|
||||||
})
|
})
|
||||||
|
val itemFocusRequester = remember { FocusRequester() }
|
||||||
val expanded = remember { mutableStateOf(false) }
|
val expanded = remember { mutableStateOf(false) }
|
||||||
RowListItem(
|
RowListItem(
|
||||||
icon = {
|
icon = {
|
||||||
@@ -562,7 +594,7 @@ fun MainScreen(
|
|||||||
.size(if (icon == circleIcon) 15.dp else 20.dp),
|
.size(if (icon == circleIcon) 15.dp else 20.dp),
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
text = tunnel.name.truncateWithEllipsis(Constants.ALLOWED_DISPLAY_NAME_LENGTH),
|
text = tunnel.name,
|
||||||
onHold = {
|
onHold = {
|
||||||
if (
|
if (
|
||||||
(uiState.vpnState.status == TunnelState.UP) &&
|
(uiState.vpnState.status == TunnelState.UP) &&
|
||||||
@@ -584,11 +616,12 @@ fun MainScreen(
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
selectedTunnel = tunnel
|
selectedTunnel = tunnel
|
||||||
focusRequester.requestFocus()
|
itemFocusRequester.requestFocus()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
statistics = uiState.vpnState.statistics,
|
statistics = uiState.vpnState.statistics,
|
||||||
expanded = expanded.value,
|
expanded = expanded.value,
|
||||||
|
focusRequester = focusRequester,
|
||||||
rowButton = {
|
rowButton = {
|
||||||
if (
|
if (
|
||||||
tunnel.id == selectedTunnel?.id &&
|
tunnel.id == selectedTunnel?.id &&
|
||||||
@@ -644,7 +677,7 @@ fun MainScreen(
|
|||||||
@Composable
|
@Composable
|
||||||
fun TunnelSwitch() =
|
fun TunnelSwitch() =
|
||||||
Switch(
|
Switch(
|
||||||
modifier = Modifier.focusRequester(focusRequester),
|
modifier = Modifier.focusRequester(itemFocusRequester),
|
||||||
checked = checked,
|
checked = checked,
|
||||||
onCheckedChange = { checked ->
|
onCheckedChange = { checked ->
|
||||||
if (!checked) expanded.value = false
|
if (!checked) expanded.value = false
|
||||||
@@ -655,7 +688,7 @@ fun MainScreen(
|
|||||||
Row {
|
Row {
|
||||||
IconButton(
|
IconButton(
|
||||||
onClick = {
|
onClick = {
|
||||||
if (uiState.settings.isAutoTunnelEnabled) {
|
if (uiState.settings.isAutoTunnelEnabled && !uiState.settings.isAutoTunnelPaused) {
|
||||||
appViewModel.showSnackbarMessage(
|
appViewModel.showSnackbarMessage(
|
||||||
context.getString(R.string.turn_off_auto),
|
context.getString(R.string.turn_off_auto),
|
||||||
)
|
)
|
||||||
|
|||||||
+84
-26
@@ -11,6 +11,7 @@ import com.zaneschepke.wireguardautotunnel.WireGuardAutoTunnel
|
|||||||
import com.zaneschepke.wireguardautotunnel.data.domain.Settings
|
import com.zaneschepke.wireguardautotunnel.data.domain.Settings
|
||||||
import com.zaneschepke.wireguardautotunnel.data.domain.TunnelConfig
|
import com.zaneschepke.wireguardautotunnel.data.domain.TunnelConfig
|
||||||
import com.zaneschepke.wireguardautotunnel.data.repository.AppDataRepository
|
import com.zaneschepke.wireguardautotunnel.data.repository.AppDataRepository
|
||||||
|
import com.zaneschepke.wireguardautotunnel.module.IoDispatcher
|
||||||
import com.zaneschepke.wireguardautotunnel.service.foreground.ServiceManager
|
import com.zaneschepke.wireguardautotunnel.service.foreground.ServiceManager
|
||||||
import com.zaneschepke.wireguardautotunnel.service.tunnel.VpnService
|
import com.zaneschepke.wireguardautotunnel.service.tunnel.VpnService
|
||||||
import com.zaneschepke.wireguardautotunnel.util.Constants
|
import com.zaneschepke.wireguardautotunnel.util.Constants
|
||||||
@@ -18,7 +19,7 @@ import com.zaneschepke.wireguardautotunnel.util.NumberUtils
|
|||||||
import com.zaneschepke.wireguardautotunnel.util.WgTunnelExceptions
|
import com.zaneschepke.wireguardautotunnel.util.WgTunnelExceptions
|
||||||
import com.zaneschepke.wireguardautotunnel.util.toWgQuickString
|
import com.zaneschepke.wireguardautotunnel.util.toWgQuickString
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.CoroutineDispatcher
|
||||||
import kotlinx.coroutines.flow.SharingStarted
|
import kotlinx.coroutines.flow.SharingStarted
|
||||||
import kotlinx.coroutines.flow.combine
|
import kotlinx.coroutines.flow.combine
|
||||||
import kotlinx.coroutines.flow.stateIn
|
import kotlinx.coroutines.flow.stateIn
|
||||||
@@ -35,7 +36,8 @@ class MainViewModel
|
|||||||
constructor(
|
constructor(
|
||||||
private val appDataRepository: AppDataRepository,
|
private val appDataRepository: AppDataRepository,
|
||||||
private val serviceManager: ServiceManager,
|
private val serviceManager: ServiceManager,
|
||||||
val vpnService: VpnService
|
val vpnService: VpnService,
|
||||||
|
@IoDispatcher private val ioDispatcher: CoroutineDispatcher
|
||||||
) : ViewModel() {
|
) : ViewModel() {
|
||||||
|
|
||||||
val uiState =
|
val uiState =
|
||||||
@@ -52,13 +54,12 @@ constructor(
|
|||||||
MainUiState(),
|
MainUiState(),
|
||||||
)
|
)
|
||||||
|
|
||||||
private fun stopWatcherService(context: Context) =
|
private fun stopWatcherService(context: Context) {
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
|
||||||
serviceManager.stopWatcherService(context)
|
serviceManager.stopWatcherService(context)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onDelete(tunnel: TunnelConfig, context: Context) {
|
fun onDelete(tunnel: TunnelConfig, context: Context) {
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
viewModelScope.launch {
|
||||||
val settings = appDataRepository.settings.getSettings()
|
val settings = appDataRepository.settings.getSettings()
|
||||||
val isPrimary = tunnel.isPrimaryTunnel
|
val isPrimary = tunnel.isPrimaryTunnel
|
||||||
if (appDataRepository.tunnels.count() == 1 || isPrimary) {
|
if (appDataRepository.tunnels.count() == 1 || isPrimary) {
|
||||||
@@ -80,7 +81,7 @@ constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun onTunnelStart(tunnelConfig: TunnelConfig, context: Context) =
|
fun onTunnelStart(tunnelConfig: TunnelConfig, context: Context) =
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
viewModelScope.launch {
|
||||||
Timber.d("On start called!")
|
Timber.d("On start called!")
|
||||||
serviceManager.startVpnService(
|
serviceManager.startVpnService(
|
||||||
context,
|
context,
|
||||||
@@ -91,7 +92,7 @@ constructor(
|
|||||||
|
|
||||||
|
|
||||||
fun onTunnelStop(context: Context) =
|
fun onTunnelStop(context: Context) =
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
viewModelScope.launch {
|
||||||
Timber.i("Stopping active tunnel")
|
Timber.i("Stopping active tunnel")
|
||||||
serviceManager.stopVpnService(context, isManualStop = true)
|
serviceManager.stopVpnService(context, isManualStop = true)
|
||||||
}
|
}
|
||||||
@@ -109,6 +110,7 @@ constructor(
|
|||||||
ConfigType.AMNEZIA -> {
|
ConfigType.AMNEZIA -> {
|
||||||
TunnelConfig.configFromAmQuick(config).peers[0].endpoint.get().host
|
TunnelConfig.configFromAmQuick(config).peers[0].endpoint.get().host
|
||||||
}
|
}
|
||||||
|
|
||||||
ConfigType.WIREGUARD -> {
|
ConfigType.WIREGUARD -> {
|
||||||
TunnelConfig.configFromWgQuick(config).peers[0].endpoint.get().host
|
TunnelConfig.configFromWgQuick(config).peers[0].endpoint.get().host
|
||||||
}
|
}
|
||||||
@@ -134,14 +136,18 @@ constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
suspend fun onTunnelQrResult(result: String, configType: ConfigType): Result<Unit> {
|
suspend fun onTunnelQrResult(result: String, configType: ConfigType): Result<Unit> {
|
||||||
return try {
|
return withContext(ioDispatcher) {
|
||||||
|
try {
|
||||||
validateConfigString(result, configType)
|
validateConfigString(result, configType)
|
||||||
val tunnelName = makeTunnelNameUnique(generateQrCodeTunnelName(result, configType))
|
val tunnelName = makeTunnelNameUnique(generateQrCodeTunnelName(result, configType))
|
||||||
val tunnelConfig = when (configType) {
|
val tunnelConfig = when (configType) {
|
||||||
ConfigType.AMNEZIA -> {
|
ConfigType.AMNEZIA -> {
|
||||||
TunnelConfig(name = tunnelName, amQuick = result,
|
TunnelConfig(
|
||||||
wgQuick = TunnelConfig.configFromAmQuick(result).toWgQuickString())
|
name = tunnelName, amQuick = result,
|
||||||
|
wgQuick = TunnelConfig.configFromAmQuick(result).toWgQuickString(),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
ConfigType.WIREGUARD -> TunnelConfig(name = tunnelName, wgQuick = result)
|
ConfigType.WIREGUARD -> TunnelConfig(name = tunnelName, wgQuick = result)
|
||||||
}
|
}
|
||||||
addTunnel(tunnelConfig)
|
addTunnel(tunnelConfig)
|
||||||
@@ -151,8 +157,10 @@ constructor(
|
|||||||
Result.failure(WgTunnelExceptions.InvalidQrCode())
|
Result.failure(WgTunnelExceptions.InvalidQrCode())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private suspend fun makeTunnelNameUnique(name: String): String {
|
private suspend fun makeTunnelNameUnique(name: String): String {
|
||||||
|
return withContext(ioDispatcher) {
|
||||||
val tunnels = appDataRepository.tunnels.getAll()
|
val tunnels = appDataRepository.tunnels.getAll()
|
||||||
var tunnelName = name
|
var tunnelName = name
|
||||||
var num = 1
|
var num = 1
|
||||||
@@ -160,10 +168,15 @@ constructor(
|
|||||||
tunnelName = name + "(${num})"
|
tunnelName = name + "(${num})"
|
||||||
num++
|
num++
|
||||||
}
|
}
|
||||||
return tunnelName
|
tunnelName
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun saveTunnelConfigFromStream(stream: InputStream, fileName: String, type: ConfigType) {
|
private fun saveTunnelConfigFromStream(
|
||||||
|
stream: InputStream,
|
||||||
|
fileName: String,
|
||||||
|
type: ConfigType
|
||||||
|
) {
|
||||||
var amQuick: String? = null
|
var amQuick: String? = null
|
||||||
val wgQuick = stream.use {
|
val wgQuick = stream.use {
|
||||||
when (type) {
|
when (type) {
|
||||||
@@ -172,27 +185,47 @@ constructor(
|
|||||||
amQuick = config.toAwgQuickString()
|
amQuick = config.toAwgQuickString()
|
||||||
config.toWgQuickString()
|
config.toWgQuickString()
|
||||||
}
|
}
|
||||||
|
|
||||||
ConfigType.WIREGUARD -> {
|
ConfigType.WIREGUARD -> {
|
||||||
Config.parse(it).toWgQuickString()
|
Config.parse(it).toWgQuickString()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
viewModelScope.launch {
|
||||||
val tunnelName = makeTunnelNameUnique(getNameFromFileName(fileName))
|
val tunnelName = makeTunnelNameUnique(getNameFromFileName(fileName))
|
||||||
addTunnel(TunnelConfig(name = tunnelName, wgQuick = wgQuick, amQuick = amQuick ?: TunnelConfig.AM_QUICK_DEFAULT))
|
addTunnel(
|
||||||
|
TunnelConfig(
|
||||||
|
name = tunnelName,
|
||||||
|
wgQuick = wgQuick,
|
||||||
|
amQuick = amQuick ?: TunnelConfig.AM_QUICK_DEFAULT,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getInputStreamFromUri(uri: Uri, context: Context): InputStream? {
|
private fun getInputStreamFromUri(uri: Uri, context: Context): InputStream? {
|
||||||
return context.applicationContext.contentResolver.openInputStream(uri)
|
return context.applicationContext.contentResolver.openInputStream(uri)
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun onTunnelFileSelected(uri: Uri, configType: ConfigType, context: Context): Result<Unit> {
|
suspend fun onTunnelFileSelected(
|
||||||
return try {
|
uri: Uri,
|
||||||
|
configType: ConfigType,
|
||||||
|
context: Context
|
||||||
|
): Result<Unit> {
|
||||||
|
return withContext(ioDispatcher) {
|
||||||
|
try {
|
||||||
if (isValidUriContentScheme(uri)) {
|
if (isValidUriContentScheme(uri)) {
|
||||||
val fileName = getFileName(context, uri)
|
val fileName = getFileName(context, uri)
|
||||||
return when (getFileExtensionFromFileName(fileName)) {
|
return@withContext when (getFileExtensionFromFileName(fileName)) {
|
||||||
Constants.CONF_FILE_EXTENSION ->
|
Constants.CONF_FILE_EXTENSION ->
|
||||||
saveTunnelFromConfUri(fileName, uri, configType, context)
|
saveTunnelFromConfUri(fileName, uri, configType, context)
|
||||||
Constants.ZIP_FILE_EXTENSION -> saveTunnelsFromZipUri(uri, configType, context)
|
|
||||||
|
Constants.ZIP_FILE_EXTENSION -> saveTunnelsFromZipUri(
|
||||||
|
uri,
|
||||||
|
configType,
|
||||||
|
context,
|
||||||
|
)
|
||||||
|
|
||||||
else -> Result.failure(WgTunnelExceptions.InvalidFileExtension())
|
else -> Result.failure(WgTunnelExceptions.InvalidFileExtension())
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -203,9 +236,15 @@ constructor(
|
|||||||
Result.failure(WgTunnelExceptions.FileReadFailed())
|
Result.failure(WgTunnelExceptions.FileReadFailed())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private suspend fun saveTunnelsFromZipUri(uri: Uri, configType: ConfigType, context: Context) : Result<Unit> {
|
private suspend fun saveTunnelsFromZipUri(
|
||||||
return ZipInputStream(getInputStreamFromUri(uri, context)).use { zip ->
|
uri: Uri,
|
||||||
|
configType: ConfigType,
|
||||||
|
context: Context
|
||||||
|
): Result<Unit> {
|
||||||
|
return withContext(ioDispatcher) {
|
||||||
|
ZipInputStream(getInputStreamFromUri(uri, context)).use { zip ->
|
||||||
generateSequence { zip.nextEntry }
|
generateSequence { zip.nextEntry }
|
||||||
.filterNot {
|
.filterNot {
|
||||||
it.isDirectory ||
|
it.isDirectory ||
|
||||||
@@ -213,7 +252,7 @@ constructor(
|
|||||||
}
|
}
|
||||||
.forEach {
|
.forEach {
|
||||||
val name = getNameFromFileName(it.name)
|
val name = getNameFromFileName(it.name)
|
||||||
withContext(viewModelScope.coroutineContext + Dispatchers.IO) {
|
withContext(viewModelScope.coroutineContext) {
|
||||||
try {
|
try {
|
||||||
var amQuick: String? = null
|
var amQuick: String? = null
|
||||||
val wgQuick =
|
val wgQuick =
|
||||||
@@ -223,11 +262,18 @@ constructor(
|
|||||||
amQuick = config.toAwgQuickString()
|
amQuick = config.toAwgQuickString()
|
||||||
config.toWgQuickString()
|
config.toWgQuickString()
|
||||||
}
|
}
|
||||||
|
|
||||||
ConfigType.WIREGUARD -> {
|
ConfigType.WIREGUARD -> {
|
||||||
Config.parse(zip).toWgQuickString()
|
Config.parse(zip).toWgQuickString()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
addTunnel(TunnelConfig(name = makeTunnelNameUnique(name), wgQuick = wgQuick, amQuick = amQuick ?: TunnelConfig.AM_QUICK_DEFAULT))
|
addTunnel(
|
||||||
|
TunnelConfig(
|
||||||
|
name = makeTunnelNameUnique(name),
|
||||||
|
wgQuick = wgQuick,
|
||||||
|
amQuick = amQuick ?: TunnelConfig.AM_QUICK_DEFAULT,
|
||||||
|
),
|
||||||
|
)
|
||||||
Result.success(Unit)
|
Result.success(Unit)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Result.failure(WgTunnelExceptions.FileReadFailed())
|
Result.failure(WgTunnelExceptions.FileReadFailed())
|
||||||
@@ -237,18 +283,30 @@ constructor(
|
|||||||
Result.success(Unit)
|
Result.success(Unit)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private suspend fun saveTunnelFromConfUri(name: String, uri: Uri, configType: ConfigType, context: Context): Result<Unit> {
|
private suspend fun saveTunnelFromConfUri(
|
||||||
|
name: String,
|
||||||
|
uri: Uri,
|
||||||
|
configType: ConfigType,
|
||||||
|
context: Context
|
||||||
|
): Result<Unit> {
|
||||||
|
return withContext(ioDispatcher) {
|
||||||
val stream = getInputStreamFromUri(uri, context)
|
val stream = getInputStreamFromUri(uri, context)
|
||||||
return if (stream != null) {
|
return@withContext if (stream != null) {
|
||||||
|
try {
|
||||||
saveTunnelConfigFromStream(stream, name, configType)
|
saveTunnelConfigFromStream(stream, name, configType)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
return@withContext Result.failure(WgTunnelExceptions.ConfigParseError())
|
||||||
|
}
|
||||||
Result.success(Unit)
|
Result.success(Unit)
|
||||||
} else {
|
} else {
|
||||||
Result.failure(WgTunnelExceptions.FileReadFailed())
|
Result.failure(WgTunnelExceptions.FileReadFailed())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private suspend fun addTunnel(tunnelConfig: TunnelConfig) {
|
private fun addTunnel(tunnelConfig: TunnelConfig) = viewModelScope.launch {
|
||||||
val firstTunnel = appDataRepository.tunnels.count() == 0
|
val firstTunnel = appDataRepository.tunnels.count() == 0
|
||||||
saveTunnel(tunnelConfig)
|
saveTunnel(tunnelConfig)
|
||||||
if (firstTunnel) WireGuardAutoTunnel.requestTunnelTileServiceStateUpdate()
|
if (firstTunnel) WireGuardAutoTunnel.requestTunnelTileServiceStateUpdate()
|
||||||
@@ -266,7 +324,7 @@ constructor(
|
|||||||
WireGuardAutoTunnel.requestAutoTunnelTileServiceUpdate()
|
WireGuardAutoTunnel.requestAutoTunnelTileServiceUpdate()
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun saveTunnel(tunnelConfig: TunnelConfig) {
|
private fun saveTunnel(tunnelConfig: TunnelConfig) = viewModelScope.launch {
|
||||||
appDataRepository.tunnels.save(tunnelConfig)
|
appDataRepository.tunnels.save(tunnelConfig)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -317,7 +375,7 @@ constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun saveSettings(settings: Settings) =
|
private fun saveSettings(settings: Settings) =
|
||||||
viewModelScope.launch(Dispatchers.IO) { appDataRepository.settings.save(settings) }
|
viewModelScope.launch { appDataRepository.settings.save(settings) }
|
||||||
|
|
||||||
|
|
||||||
fun onCopyTunnel(tunnel: TunnelConfig?) = viewModelScope.launch {
|
fun onCopyTunnel(tunnel: TunnelConfig?) = viewModelScope.launch {
|
||||||
|
|||||||
+40
-20
@@ -1,7 +1,11 @@
|
|||||||
package com.zaneschepke.wireguardautotunnel.ui.screens.options
|
package com.zaneschepke.wireguardautotunnel.ui.screens.options
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
|
import androidx.compose.animation.AnimatedVisibility
|
||||||
|
import androidx.compose.animation.slideInVertically
|
||||||
|
import androidx.compose.animation.slideOutVertically
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.focusGroup
|
||||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
@@ -12,6 +16,7 @@ import androidx.compose.foundation.layout.fillMaxSize
|
|||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.height
|
import androidx.compose.foundation.layout.height
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
import androidx.compose.foundation.rememberScrollState
|
import androidx.compose.foundation.rememberScrollState
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.foundation.text.KeyboardActions
|
import androidx.compose.foundation.text.KeyboardActions
|
||||||
@@ -39,7 +44,6 @@ import androidx.compose.ui.Alignment
|
|||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.focus.FocusRequester
|
import androidx.compose.ui.focus.FocusRequester
|
||||||
import androidx.compose.ui.focus.focusRequester
|
import androidx.compose.ui.focus.focusRequester
|
||||||
import androidx.compose.ui.focus.onFocusChanged
|
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.platform.LocalFocusManager
|
import androidx.compose.ui.platform.LocalFocusManager
|
||||||
@@ -114,28 +118,28 @@ fun OptionsScreen(
|
|||||||
Scaffold(
|
Scaffold(
|
||||||
floatingActionButton = {
|
floatingActionButton = {
|
||||||
val secondaryColor = MaterialTheme.colorScheme.secondary
|
val secondaryColor = MaterialTheme.colorScheme.secondary
|
||||||
val hoverColor = MaterialTheme.colorScheme.surfaceColorAtElevation(2.dp)
|
val tvFobColor = MaterialTheme.colorScheme.surfaceColorAtElevation(2.dp)
|
||||||
var fobColor by remember { mutableStateOf(secondaryColor) }
|
val fobColor =
|
||||||
|
if (WireGuardAutoTunnel.isRunningOnAndroidTv()) tvFobColor else secondaryColor
|
||||||
|
val fobIconColor =
|
||||||
|
if (WireGuardAutoTunnel.isRunningOnAndroidTv()) Color.White else MaterialTheme.colorScheme.background
|
||||||
|
AnimatedVisibility(
|
||||||
|
visible = true,
|
||||||
|
enter = slideInVertically(initialOffsetY = { it * 2 }),
|
||||||
|
exit = slideOutVertically(targetOffsetY = { it * 2 }),
|
||||||
|
modifier = Modifier
|
||||||
|
.focusRequester(focusRequester)
|
||||||
|
.focusGroup(),
|
||||||
|
) {
|
||||||
MultiFloatingActionButton(
|
MultiFloatingActionButton(
|
||||||
modifier =
|
|
||||||
(if (
|
|
||||||
WireGuardAutoTunnel.isRunningOnAndroidTv()
|
|
||||||
)
|
|
||||||
Modifier.focusRequester(focusRequester)
|
|
||||||
else Modifier)
|
|
||||||
.onFocusChanged {
|
|
||||||
if (WireGuardAutoTunnel.isRunningOnAndroidTv()) {
|
|
||||||
fobColor = if (it.isFocused) hoverColor else secondaryColor
|
|
||||||
}
|
|
||||||
},
|
|
||||||
fabIcon = FabIcon(
|
fabIcon = FabIcon(
|
||||||
iconRes = R.drawable.edit,
|
iconRes = R.drawable.edit,
|
||||||
iconResAfterRotate = R.drawable.close,
|
iconResAfterRotate = R.drawable.close,
|
||||||
iconRotate = 180f
|
iconRotate = 180f,
|
||||||
),
|
),
|
||||||
fabOption = FabOption(
|
fabOption = FabOption(
|
||||||
iconTint = MaterialTheme.colorScheme.background,
|
iconTint = fobIconColor,
|
||||||
backgroundTint = MaterialTheme.colorScheme.primary,
|
backgroundTint = fobColor,
|
||||||
),
|
),
|
||||||
itemsMultiFab = listOf(
|
itemsMultiFab = listOf(
|
||||||
MultiFabItem(
|
MultiFabItem(
|
||||||
@@ -144,18 +148,33 @@ fun OptionsScreen(
|
|||||||
stringResource(id = R.string.amnezia),
|
stringResource(id = R.string.amnezia),
|
||||||
color = Color.White,
|
color = Color.White,
|
||||||
textAlign = TextAlign.Center,
|
textAlign = TextAlign.Center,
|
||||||
modifier = Modifier.padding(end = 10.dp)
|
modifier = Modifier.padding(end = 10.dp),
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
modifier = Modifier
|
||||||
|
.size(40.dp),
|
||||||
icon = R.drawable.edit,
|
icon = R.drawable.edit,
|
||||||
value = ConfigType.AMNEZIA.name,
|
value = ConfigType.AMNEZIA.name,
|
||||||
|
miniFabOption = FabOption(
|
||||||
|
backgroundTint = fobColor,
|
||||||
|
fobIconColor,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
MultiFabItem(
|
MultiFabItem(
|
||||||
label = {
|
label = {
|
||||||
Text(stringResource(id = R.string.wireguard), color = Color.White, textAlign = TextAlign.Center, modifier = Modifier.padding(end = 10.dp))
|
Text(
|
||||||
|
stringResource(id = R.string.wireguard),
|
||||||
|
color = Color.White,
|
||||||
|
textAlign = TextAlign.Center,
|
||||||
|
modifier = Modifier.padding(end = 10.dp),
|
||||||
|
)
|
||||||
},
|
},
|
||||||
icon = R.drawable.edit,
|
icon = R.drawable.edit,
|
||||||
value = ConfigType.WIREGUARD.name
|
value = ConfigType.WIREGUARD.name,
|
||||||
|
miniFabOption = FabOption(
|
||||||
|
backgroundTint = fobColor,
|
||||||
|
fobIconColor,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
onFabItemClicked = {
|
onFabItemClicked = {
|
||||||
@@ -167,6 +186,7 @@ fun OptionsScreen(
|
|||||||
shape = RoundedCornerShape(16.dp),
|
shape = RoundedCornerShape(16.dp),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
},
|
||||||
) {
|
) {
|
||||||
Column(
|
Column(
|
||||||
horizontalAlignment = Alignment.CenterHorizontally,
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
|
|||||||
+5
-6
@@ -9,7 +9,6 @@ import com.zaneschepke.wireguardautotunnel.data.repository.AppDataRepository
|
|||||||
import com.zaneschepke.wireguardautotunnel.util.Constants
|
import com.zaneschepke.wireguardautotunnel.util.Constants
|
||||||
import com.zaneschepke.wireguardautotunnel.util.WgTunnelExceptions
|
import com.zaneschepke.wireguardautotunnel.util.WgTunnelExceptions
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.SharingStarted
|
import kotlinx.coroutines.flow.SharingStarted
|
||||||
import kotlinx.coroutines.flow.combine
|
import kotlinx.coroutines.flow.combine
|
||||||
@@ -45,12 +44,12 @@ constructor(
|
|||||||
fun init(tunnelId: String) {
|
fun init(tunnelId: String) {
|
||||||
_optionState.update {
|
_optionState.update {
|
||||||
it.copy(
|
it.copy(
|
||||||
id = tunnelId
|
id = tunnelId,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onDeleteRunSSID(ssid: String) = viewModelScope.launch(Dispatchers.IO) {
|
fun onDeleteRunSSID(ssid: String) = viewModelScope.launch {
|
||||||
uiState.value.tunnel?.let {
|
uiState.value.tunnel?.let {
|
||||||
appDataRepository.tunnels.save(
|
appDataRepository.tunnels.save(
|
||||||
tunnelConfig = it.copy(
|
tunnelConfig = it.copy(
|
||||||
@@ -60,7 +59,7 @@ constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun saveTunnel(tunnelConfig: TunnelConfig?) = viewModelScope.launch(Dispatchers.IO) {
|
private fun saveTunnel(tunnelConfig: TunnelConfig?) = viewModelScope.launch {
|
||||||
tunnelConfig?.let {
|
tunnelConfig?.let {
|
||||||
appDataRepository.tunnels.save(it)
|
appDataRepository.tunnels.save(it)
|
||||||
}
|
}
|
||||||
@@ -81,7 +80,7 @@ constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onToggleIsMobileDataTunnel() = viewModelScope.launch(Dispatchers.IO) {
|
fun onToggleIsMobileDataTunnel() = viewModelScope.launch {
|
||||||
uiState.value.tunnel?.let {
|
uiState.value.tunnel?.let {
|
||||||
if (it.isMobileDataTunnel) {
|
if (it.isMobileDataTunnel) {
|
||||||
appDataRepository.tunnels.updateMobileDataTunnel(null)
|
appDataRepository.tunnels.updateMobileDataTunnel(null)
|
||||||
@@ -89,7 +88,7 @@ constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onTogglePrimaryTunnel() = viewModelScope.launch(Dispatchers.IO) {
|
fun onTogglePrimaryTunnel() = viewModelScope.launch {
|
||||||
if (uiState.value.tunnel != null) {
|
if (uiState.value.tunnel != null) {
|
||||||
appDataRepository.tunnels.updatePrimaryTunnel(
|
appDataRepository.tunnels.updatePrimaryTunnel(
|
||||||
when (uiState.value.isDefaultTunnel) {
|
when (uiState.value.isDefaultTunnel) {
|
||||||
|
|||||||
+31
-17
@@ -44,6 +44,7 @@ import androidx.compose.material3.Surface
|
|||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.material3.TextButton
|
import androidx.compose.material3.TextButton
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.SideEffect
|
import androidx.compose.runtime.SideEffect
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
@@ -70,7 +71,6 @@ import androidx.navigation.NavController
|
|||||||
import com.google.accompanist.permissions.ExperimentalPermissionsApi
|
import com.google.accompanist.permissions.ExperimentalPermissionsApi
|
||||||
import com.google.accompanist.permissions.isGranted
|
import com.google.accompanist.permissions.isGranted
|
||||||
import com.google.accompanist.permissions.rememberPermissionState
|
import com.google.accompanist.permissions.rememberPermissionState
|
||||||
import com.wireguard.android.backend.WgQuickBackend
|
|
||||||
import com.zaneschepke.wireguardautotunnel.R
|
import com.zaneschepke.wireguardautotunnel.R
|
||||||
import com.zaneschepke.wireguardautotunnel.WireGuardAutoTunnel
|
import com.zaneschepke.wireguardautotunnel.WireGuardAutoTunnel
|
||||||
import com.zaneschepke.wireguardautotunnel.data.domain.TunnelConfig
|
import com.zaneschepke.wireguardautotunnel.data.domain.TunnelConfig
|
||||||
@@ -81,12 +81,9 @@ import com.zaneschepke.wireguardautotunnel.ui.common.ClickableIconButton
|
|||||||
import com.zaneschepke.wireguardautotunnel.ui.common.config.ConfigurationToggle
|
import com.zaneschepke.wireguardautotunnel.ui.common.config.ConfigurationToggle
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.common.prompt.AuthorizationPrompt
|
import com.zaneschepke.wireguardautotunnel.ui.common.prompt.AuthorizationPrompt
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.common.text.SectionTitle
|
import com.zaneschepke.wireguardautotunnel.ui.common.text.SectionTitle
|
||||||
import com.zaneschepke.wireguardautotunnel.util.FileUtils
|
|
||||||
import com.zaneschepke.wireguardautotunnel.util.getMessage
|
import com.zaneschepke.wireguardautotunnel.util.getMessage
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import xyz.teamgravity.pin_lock_compose.PinManager
|
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
@OptIn(
|
@OptIn(
|
||||||
@@ -100,14 +97,14 @@ fun SettingsScreen(
|
|||||||
navController: NavController,
|
navController: NavController,
|
||||||
focusRequester: FocusRequester,
|
focusRequester: FocusRequester,
|
||||||
) {
|
) {
|
||||||
val scope = rememberCoroutineScope { Dispatchers.IO }
|
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
val focusManager = LocalFocusManager.current
|
val focusManager = LocalFocusManager.current
|
||||||
|
val scope = rememberCoroutineScope()
|
||||||
val scrollState = rememberScrollState()
|
val scrollState = rememberScrollState()
|
||||||
val interactionSource = remember { MutableInteractionSource() }
|
val interactionSource = remember { MutableInteractionSource() }
|
||||||
val pinExists = remember { mutableStateOf(PinManager.pinExists()) }
|
|
||||||
|
|
||||||
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
|
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
|
||||||
|
val kernelSupport by viewModel.kernelSupport.collectAsStateWithLifecycle()
|
||||||
|
|
||||||
val fineLocationState = rememberPermissionState(Manifest.permission.ACCESS_FINE_LOCATION)
|
val fineLocationState = rememberPermissionState(Manifest.permission.ACCESS_FINE_LOCATION)
|
||||||
var currentText by remember { mutableStateOf("") }
|
var currentText by remember { mutableStateOf("") }
|
||||||
@@ -119,6 +116,10 @@ fun SettingsScreen(
|
|||||||
val screenPadding = 5.dp
|
val screenPadding = 5.dp
|
||||||
val fillMaxWidth = .85f
|
val fillMaxWidth = .85f
|
||||||
|
|
||||||
|
LaunchedEffect(Unit) {
|
||||||
|
viewModel.checkKernelSupport()
|
||||||
|
}
|
||||||
|
|
||||||
val startForResult =
|
val startForResult =
|
||||||
rememberLauncherForActivityResult(ActivityResultContracts.StartActivityForResult()) { result: ActivityResult ->
|
rememberLauncherForActivityResult(ActivityResultContracts.StartActivityForResult()) { result: ActivityResult ->
|
||||||
if (result.resultCode == Activity.RESULT_OK) {
|
if (result.resultCode == Activity.RESULT_OK) {
|
||||||
@@ -137,19 +138,23 @@ fun SettingsScreen(
|
|||||||
}
|
}
|
||||||
file
|
file
|
||||||
}
|
}
|
||||||
val amFiles = uiState.tunnels.mapNotNull { config -> if(config.amQuick != TunnelConfig.AM_QUICK_DEFAULT) {
|
val amFiles = uiState.tunnels.mapNotNull { config ->
|
||||||
|
if (config.amQuick != TunnelConfig.AM_QUICK_DEFAULT) {
|
||||||
val file = File(context.cacheDir, "${config.name}-am.conf")
|
val file = File(context.cacheDir, "${config.name}-am.conf")
|
||||||
file.outputStream().use {
|
file.outputStream().use {
|
||||||
it.write(config.amQuick.toByteArray())
|
it.write(config.amQuick.toByteArray())
|
||||||
}
|
}
|
||||||
file
|
file
|
||||||
} else null }
|
} else null
|
||||||
FileUtils.saveFilesToZip(context, wgFiles + amFiles).onFailure {
|
}
|
||||||
|
scope.launch {
|
||||||
|
viewModel.onExportTunnels(wgFiles + amFiles).onFailure {
|
||||||
appViewModel.showSnackbarMessage(it.getMessage(context))
|
appViewModel.showSnackbarMessage(it.getMessage(context))
|
||||||
}.onSuccess {
|
}.onSuccess {
|
||||||
didExportFiles = true
|
didExportFiles = true
|
||||||
appViewModel.showSnackbarMessage(context.getString(R.string.exported_configs_message))
|
appViewModel.showSnackbarMessage(context.getString(R.string.exported_configs_message))
|
||||||
}
|
}
|
||||||
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Timber.e(e)
|
Timber.e(e)
|
||||||
}
|
}
|
||||||
@@ -190,12 +195,10 @@ fun SettingsScreen(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun openSettings() {
|
fun openSettings() {
|
||||||
scope.launch {
|
|
||||||
val intentSettings = Intent(ACTION_APPLICATION_DETAILS_SETTINGS)
|
val intentSettings = Intent(ACTION_APPLICATION_DETAILS_SETTINGS)
|
||||||
intentSettings.data = Uri.fromParts("package", context.packageName, null)
|
intentSettings.data = Uri.fromParts("package", context.packageName, null)
|
||||||
context.startActivity(intentSettings)
|
context.startActivity(intentSettings)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
fun checkFineLocationGranted() {
|
fun checkFineLocationGranted() {
|
||||||
isBackgroundLocationGranted =
|
isBackgroundLocationGranted =
|
||||||
@@ -591,7 +594,7 @@ fun SettingsScreen(
|
|||||||
viewModel.onToggleAmnezia()
|
viewModel.onToggleAmnezia()
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
if (WgQuickBackend.hasKernelSupport()) {
|
if (kernelSupport) {
|
||||||
ConfigurationToggle(
|
ConfigurationToggle(
|
||||||
stringResource(R.string.use_kernel),
|
stringResource(R.string.use_kernel),
|
||||||
enabled =
|
enabled =
|
||||||
@@ -601,9 +604,11 @@ fun SettingsScreen(
|
|||||||
checked = uiState.settings.isKernelEnabled,
|
checked = uiState.settings.isKernelEnabled,
|
||||||
padding = screenPadding,
|
padding = screenPadding,
|
||||||
onCheckChanged = {
|
onCheckChanged = {
|
||||||
|
scope.launch {
|
||||||
viewModel.onToggleKernelMode().onFailure {
|
viewModel.onToggleKernelMode().onFailure {
|
||||||
appViewModel.showSnackbarMessage(it.getMessage(context))
|
appViewModel.showSnackbarMessage(it.getMessage(context))
|
||||||
}
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -646,15 +651,24 @@ fun SettingsScreen(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
ConfigurationToggle(
|
ConfigurationToggle(
|
||||||
stringResource(R.string.enable_app_lock),
|
stringResource(R.string.restart_at_boot),
|
||||||
enabled = true,
|
enabled = true,
|
||||||
checked = pinExists.value,
|
checked = uiState.settings.isRestoreOnBootEnabled,
|
||||||
padding = screenPadding,
|
padding = screenPadding,
|
||||||
onCheckChanged = {
|
onCheckChanged = {
|
||||||
if (pinExists.value) {
|
viewModel.onToggleRestartAtBoot()
|
||||||
PinManager.clearPin()
|
},
|
||||||
pinExists.value = PinManager.pinExists()
|
)
|
||||||
|
ConfigurationToggle(
|
||||||
|
stringResource(R.string.enable_app_lock),
|
||||||
|
enabled = true,
|
||||||
|
checked = uiState.isPinLockEnabled,
|
||||||
|
padding = screenPadding,
|
||||||
|
onCheckChanged = {
|
||||||
|
if (uiState.isPinLockEnabled) {
|
||||||
|
viewModel.onPinLockDisabled()
|
||||||
} else {
|
} else {
|
||||||
|
viewModel.onPinLockEnabled()
|
||||||
navController.navigate(Screen.Lock.route)
|
navController.navigate(Screen.Lock.route)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
+2
-1
@@ -9,5 +9,6 @@ data class SettingsUiState(
|
|||||||
val tunnels: List<TunnelConfig> = emptyList(),
|
val tunnels: List<TunnelConfig> = emptyList(),
|
||||||
val vpnState: VpnState = VpnState(),
|
val vpnState: VpnState = VpnState(),
|
||||||
val isLocationDisclosureShown: Boolean = true,
|
val isLocationDisclosureShown: Boolean = true,
|
||||||
val isBatteryOptimizeDisableShown: Boolean = false
|
val isBatteryOptimizeDisableShown: Boolean = false,
|
||||||
|
val isPinLockEnabled: Boolean = false
|
||||||
)
|
)
|
||||||
|
|||||||
+59
-9
@@ -5,21 +5,32 @@ import android.location.LocationManager
|
|||||||
import androidx.core.location.LocationManagerCompat
|
import androidx.core.location.LocationManagerCompat
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import com.wireguard.android.backend.WgQuickBackend
|
||||||
import com.wireguard.android.util.RootShell
|
import com.wireguard.android.util.RootShell
|
||||||
import com.zaneschepke.wireguardautotunnel.WireGuardAutoTunnel
|
import com.zaneschepke.wireguardautotunnel.WireGuardAutoTunnel
|
||||||
import com.zaneschepke.wireguardautotunnel.data.domain.Settings
|
import com.zaneschepke.wireguardautotunnel.data.domain.Settings
|
||||||
import com.zaneschepke.wireguardautotunnel.data.repository.AppDataRepository
|
import com.zaneschepke.wireguardautotunnel.data.repository.AppDataRepository
|
||||||
|
import com.zaneschepke.wireguardautotunnel.module.IoDispatcher
|
||||||
import com.zaneschepke.wireguardautotunnel.service.foreground.ServiceManager
|
import com.zaneschepke.wireguardautotunnel.service.foreground.ServiceManager
|
||||||
import com.zaneschepke.wireguardautotunnel.service.tunnel.VpnService
|
import com.zaneschepke.wireguardautotunnel.service.tunnel.VpnService
|
||||||
import com.zaneschepke.wireguardautotunnel.util.Constants
|
import com.zaneschepke.wireguardautotunnel.util.Constants
|
||||||
|
import com.zaneschepke.wireguardautotunnel.util.FileUtils
|
||||||
import com.zaneschepke.wireguardautotunnel.util.WgTunnelExceptions
|
import com.zaneschepke.wireguardautotunnel.util.WgTunnelExceptions
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
|
import kotlinx.coroutines.CoroutineDispatcher
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.SharingStarted
|
import kotlinx.coroutines.flow.SharingStarted
|
||||||
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
import kotlinx.coroutines.flow.combine
|
import kotlinx.coroutines.flow.combine
|
||||||
import kotlinx.coroutines.flow.stateIn
|
import kotlinx.coroutines.flow.stateIn
|
||||||
|
import kotlinx.coroutines.flow.update
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
import xyz.teamgravity.pin_lock_compose.PinManager
|
||||||
|
import java.io.File
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
import javax.inject.Provider
|
||||||
|
|
||||||
@HiltViewModel
|
@HiltViewModel
|
||||||
class SettingsViewModel
|
class SettingsViewModel
|
||||||
@@ -27,10 +38,15 @@ class SettingsViewModel
|
|||||||
constructor(
|
constructor(
|
||||||
private val appDataRepository: AppDataRepository,
|
private val appDataRepository: AppDataRepository,
|
||||||
private val serviceManager: ServiceManager,
|
private val serviceManager: ServiceManager,
|
||||||
private val rootShell: RootShell,
|
private val rootShell: Provider<RootShell>,
|
||||||
|
private val fileUtils: FileUtils,
|
||||||
|
@IoDispatcher private val ioDispatcher: CoroutineDispatcher,
|
||||||
vpnService: VpnService
|
vpnService: VpnService
|
||||||
) : ViewModel() {
|
) : ViewModel() {
|
||||||
|
|
||||||
|
private val _kernelSupport = MutableStateFlow(false)
|
||||||
|
val kernelSupport = _kernelSupport.asStateFlow()
|
||||||
|
|
||||||
val uiState =
|
val uiState =
|
||||||
combine(
|
combine(
|
||||||
appDataRepository.settings.getSettingsFlow(),
|
appDataRepository.settings.getSettingsFlow(),
|
||||||
@@ -42,8 +58,9 @@ constructor(
|
|||||||
settings,
|
settings,
|
||||||
tunnels,
|
tunnels,
|
||||||
tunnelState,
|
tunnelState,
|
||||||
generalState.locationDisclosureShown,
|
generalState.isLocationDisclosureShown,
|
||||||
generalState.batteryOptimizationDisableShown,
|
generalState.isBatteryOptimizationDisableShown,
|
||||||
|
generalState.isPinLockEnabled,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
.stateIn(
|
.stateIn(
|
||||||
@@ -90,6 +107,10 @@ constructor(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
suspend fun onExportTunnels(files: List<File>): Result<Unit> {
|
||||||
|
return fileUtils.saveFilesToZip(files)
|
||||||
|
}
|
||||||
|
|
||||||
fun onToggleAutoTunnel(context: Context) =
|
fun onToggleAutoTunnel(context: Context) =
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
val isAutoTunnelEnabled = uiState.value.settings.isAutoTunnelEnabled
|
val isAutoTunnelEnabled = uiState.value.settings.isAutoTunnelEnabled
|
||||||
@@ -169,15 +190,16 @@ constructor(
|
|||||||
private fun saveAmneziaMode(on: Boolean) {
|
private fun saveAmneziaMode(on: Boolean) {
|
||||||
saveSettings(
|
saveSettings(
|
||||||
uiState.value.settings.copy(
|
uiState.value.settings.copy(
|
||||||
isAmneziaEnabled = on
|
isAmneziaEnabled = on,
|
||||||
)
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onToggleKernelMode(): Result<Unit> {
|
suspend fun onToggleKernelMode(): Result<Unit> {
|
||||||
|
return withContext(ioDispatcher) {
|
||||||
if (!uiState.value.settings.isKernelEnabled) {
|
if (!uiState.value.settings.isKernelEnabled) {
|
||||||
try {
|
try {
|
||||||
rootShell.start()
|
rootShell.get().start()
|
||||||
Timber.i("Root shell accepted!")
|
Timber.i("Root shell accepted!")
|
||||||
saveSettings(
|
saveSettings(
|
||||||
uiState.value.settings.copy(
|
uiState.value.settings.copy(
|
||||||
@@ -189,12 +211,13 @@ constructor(
|
|||||||
} catch (e: RootShell.RootShellException) {
|
} catch (e: RootShell.RootShellException) {
|
||||||
Timber.e(e)
|
Timber.e(e)
|
||||||
saveKernelMode(on = false)
|
saveKernelMode(on = false)
|
||||||
return Result.failure(WgTunnelExceptions.RootDenied())
|
return@withContext Result.failure(WgTunnelExceptions.RootDenied())
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
saveKernelMode(on = false)
|
saveKernelMode(on = false)
|
||||||
}
|
}
|
||||||
return Result.success(Unit)
|
Result.success(Unit)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onToggleRestartOnPing() = viewModelScope.launch {
|
fun onToggleRestartOnPing() = viewModelScope.launch {
|
||||||
@@ -204,4 +227,31 @@ constructor(
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun checkKernelSupport() = viewModelScope.launch {
|
||||||
|
val kernelSupport = withContext(ioDispatcher) {
|
||||||
|
WgQuickBackend.hasKernelSupport()
|
||||||
|
}
|
||||||
|
_kernelSupport.update {
|
||||||
|
kernelSupport
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onPinLockDisabled() = viewModelScope.launch {
|
||||||
|
PinManager.clearPin()
|
||||||
|
appDataRepository.appState.setPinLockEnabled(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onPinLockEnabled() = viewModelScope.launch {
|
||||||
|
PinManager.initialize(WireGuardAutoTunnel.instance)
|
||||||
|
appDataRepository.appState.setPinLockEnabled(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onToggleRestartAtBoot() = viewModelScope.launch {
|
||||||
|
saveSettings(
|
||||||
|
uiState.value.settings.copy(
|
||||||
|
isRestoreOnBootEnabled = !uiState.value.settings.isRestoreOnBootEnabled
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+23
-5
@@ -107,7 +107,12 @@ fun SupportScreen(
|
|||||||
modifier = Modifier.padding(bottom = 20.dp),
|
modifier = Modifier.padding(bottom = 20.dp),
|
||||||
)
|
)
|
||||||
TextButton(
|
TextButton(
|
||||||
onClick = { appViewModel.openWebPage(context.resources.getString(R.string.docs_url), context) },
|
onClick = {
|
||||||
|
appViewModel.openWebPage(
|
||||||
|
context.resources.getString(R.string.docs_url),
|
||||||
|
context,
|
||||||
|
)
|
||||||
|
},
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.padding(vertical = 5.dp)
|
.padding(vertical = 5.dp)
|
||||||
.focusRequester(focusRequester),
|
.focusRequester(focusRequester),
|
||||||
@@ -129,7 +134,7 @@ fun SupportScreen(
|
|||||||
weight = 1.0f,
|
weight = 1.0f,
|
||||||
fill = false,
|
fill = false,
|
||||||
),
|
),
|
||||||
softWrap = true
|
softWrap = true,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
Icon(
|
Icon(
|
||||||
@@ -143,7 +148,12 @@ fun SupportScreen(
|
|||||||
color = MaterialTheme.colorScheme.onBackground,
|
color = MaterialTheme.colorScheme.onBackground,
|
||||||
)
|
)
|
||||||
TextButton(
|
TextButton(
|
||||||
onClick = { appViewModel.openWebPage(context.resources.getString(R.string.telegram_url), context) },
|
onClick = {
|
||||||
|
appViewModel.openWebPage(
|
||||||
|
context.resources.getString(R.string.telegram_url),
|
||||||
|
context,
|
||||||
|
)
|
||||||
|
},
|
||||||
modifier = Modifier.padding(vertical = 5.dp),
|
modifier = Modifier.padding(vertical = 5.dp),
|
||||||
) {
|
) {
|
||||||
Row(
|
Row(
|
||||||
@@ -175,7 +185,12 @@ fun SupportScreen(
|
|||||||
color = MaterialTheme.colorScheme.onBackground,
|
color = MaterialTheme.colorScheme.onBackground,
|
||||||
)
|
)
|
||||||
TextButton(
|
TextButton(
|
||||||
onClick = { appViewModel.openWebPage(context.resources.getString(R.string.github_url), context) },
|
onClick = {
|
||||||
|
appViewModel.openWebPage(
|
||||||
|
context.resources.getString(R.string.github_url),
|
||||||
|
context,
|
||||||
|
)
|
||||||
|
},
|
||||||
modifier = Modifier.padding(vertical = 5.dp),
|
modifier = Modifier.padding(vertical = 5.dp),
|
||||||
) {
|
) {
|
||||||
Row(
|
Row(
|
||||||
@@ -269,7 +284,10 @@ fun SupportScreen(
|
|||||||
fontSize = 16.sp,
|
fontSize = 16.sp,
|
||||||
modifier =
|
modifier =
|
||||||
Modifier.clickable {
|
Modifier.clickable {
|
||||||
appViewModel.openWebPage(context.resources.getString(R.string.privacy_policy_url), context)
|
appViewModel.openWebPage(
|
||||||
|
context.resources.getString(R.string.privacy_policy_url),
|
||||||
|
context,
|
||||||
|
)
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
Row(
|
Row(
|
||||||
|
|||||||
+21
-8
@@ -1,6 +1,7 @@
|
|||||||
package com.zaneschepke.wireguardautotunnel.ui.screens.support.logs
|
package com.zaneschepke.wireguardautotunnel.ui.screens.support.logs
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
|
import android.widget.Toast
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
@@ -8,7 +9,7 @@ import androidx.compose.foundation.layout.Row
|
|||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.lazy.items
|
import androidx.compose.foundation.lazy.itemsIndexed
|
||||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
@@ -32,17 +33,17 @@ import androidx.compose.ui.text.AnnotatedString
|
|||||||
import androidx.compose.ui.text.style.TextAlign
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.AppViewModel
|
import androidx.hilt.navigation.compose.hiltViewModel
|
||||||
|
import com.zaneschepke.logcatter.model.LogMessage
|
||||||
|
import com.zaneschepke.wireguardautotunnel.R
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.common.text.LogTypeLabel
|
import com.zaneschepke.wireguardautotunnel.ui.common.text.LogTypeLabel
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
@SuppressLint("UnusedMaterial3ScaffoldPaddingParameter")
|
@SuppressLint("UnusedMaterial3ScaffoldPaddingParameter")
|
||||||
@Composable
|
@Composable
|
||||||
fun LogsScreen(appViewModel: AppViewModel) {
|
fun LogsScreen(viewModel: LogsViewModel = hiltViewModel()) {
|
||||||
|
|
||||||
val logs = remember {
|
val logs = viewModel.logs
|
||||||
appViewModel.logs
|
|
||||||
}
|
|
||||||
|
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
|
|
||||||
@@ -60,7 +61,15 @@ fun LogsScreen(appViewModel: AppViewModel) {
|
|||||||
floatingActionButton = {
|
floatingActionButton = {
|
||||||
FloatingActionButton(
|
FloatingActionButton(
|
||||||
onClick = {
|
onClick = {
|
||||||
appViewModel.saveLogsToFile(context)
|
scope.launch {
|
||||||
|
viewModel.saveLogsToFile().onSuccess {
|
||||||
|
Toast.makeText(
|
||||||
|
context,
|
||||||
|
context.getString(R.string.logs_saved),
|
||||||
|
Toast.LENGTH_SHORT,
|
||||||
|
).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
shape = RoundedCornerShape(16.dp),
|
shape = RoundedCornerShape(16.dp),
|
||||||
containerColor = MaterialTheme.colorScheme.primary,
|
containerColor = MaterialTheme.colorScheme.primary,
|
||||||
@@ -82,7 +91,11 @@ fun LogsScreen(appViewModel: AppViewModel) {
|
|||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.padding(horizontal = 24.dp),
|
.padding(horizontal = 24.dp),
|
||||||
) {
|
) {
|
||||||
items(logs) {
|
itemsIndexed(
|
||||||
|
logs,
|
||||||
|
key = { index, _ -> index },
|
||||||
|
contentType = { _: Int, _: LogMessage -> null },
|
||||||
|
) { _, it ->
|
||||||
Row(
|
Row(
|
||||||
horizontalArrangement = Arrangement.spacedBy(5.dp, Alignment.Start),
|
horizontalArrangement = Arrangement.spacedBy(5.dp, Alignment.Start),
|
||||||
verticalAlignment = Alignment.Top,
|
verticalAlignment = Alignment.Top,
|
||||||
|
|||||||
+55
@@ -0,0 +1,55 @@
|
|||||||
|
package com.zaneschepke.wireguardautotunnel.ui.screens.support.logs
|
||||||
|
|
||||||
|
import androidx.compose.runtime.mutableStateListOf
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import com.zaneschepke.logcatter.LocalLogCollector
|
||||||
|
import com.zaneschepke.logcatter.model.LogMessage
|
||||||
|
import com.zaneschepke.wireguardautotunnel.module.IoDispatcher
|
||||||
|
import com.zaneschepke.wireguardautotunnel.module.MainDispatcher
|
||||||
|
import com.zaneschepke.wireguardautotunnel.util.Constants
|
||||||
|
import com.zaneschepke.wireguardautotunnel.util.FileUtils
|
||||||
|
import com.zaneschepke.wireguardautotunnel.util.chunked
|
||||||
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
|
import kotlinx.coroutines.CoroutineDispatcher
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
import java.time.Duration
|
||||||
|
import java.time.Instant
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@HiltViewModel
|
||||||
|
class LogsViewModel
|
||||||
|
@Inject constructor(
|
||||||
|
private val localLogCollector: LocalLogCollector,
|
||||||
|
private val fileUtils: FileUtils,
|
||||||
|
@IoDispatcher private val ioDispatcher: CoroutineDispatcher,
|
||||||
|
@MainDispatcher private val mainDispatcher: CoroutineDispatcher
|
||||||
|
) : ViewModel() {
|
||||||
|
|
||||||
|
val logs = mutableStateListOf<LogMessage>()
|
||||||
|
|
||||||
|
init {
|
||||||
|
viewModelScope.launch(ioDispatcher) {
|
||||||
|
localLogCollector.bufferedLogs.chunked(500, Duration.ofSeconds(1)).collect {
|
||||||
|
withContext(mainDispatcher) {
|
||||||
|
logs.addAll(it)
|
||||||
|
}
|
||||||
|
if (logs.size > Constants.LOG_BUFFER_SIZE) {
|
||||||
|
withContext(mainDispatcher) {
|
||||||
|
logs.removeRange(0, (logs.size - Constants.LOG_BUFFER_SIZE).toInt())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun saveLogsToFile(): Result<Unit> {
|
||||||
|
val file = localLogCollector.getLogFile().getOrElse {
|
||||||
|
return Result.failure(it)
|
||||||
|
}
|
||||||
|
val fileContent = fileUtils.readBytesFromFile(file)
|
||||||
|
val fileName = "${Constants.BASE_LOG_FILE_NAME}-${Instant.now().epochSecond}.txt"
|
||||||
|
return fileUtils.saveByteArrayToDownloads(fileContent, fileName)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -16,10 +16,11 @@ object Constants {
|
|||||||
const val URI_CONTENT_SCHEME = "content"
|
const val URI_CONTENT_SCHEME = "content"
|
||||||
const val ALLOWED_FILE_TYPES = "*/*"
|
const val ALLOWED_FILE_TYPES = "*/*"
|
||||||
const val TEXT_MIME_TYPE = "text/plain"
|
const val TEXT_MIME_TYPE = "text/plain"
|
||||||
|
const val ZIP_FILE_MIME_TYPE = "application/zip"
|
||||||
const val GOOGLE_TV_EXPLORER_STUB = "com.google.android.tv.frameworkpackagestubs"
|
const val GOOGLE_TV_EXPLORER_STUB = "com.google.android.tv.frameworkpackagestubs"
|
||||||
const val ANDROID_TV_EXPLORER_STUB = "com.android.tv.frameworkpackagestubs"
|
const val ANDROID_TV_EXPLORER_STUB = "com.android.tv.frameworkpackagestubs"
|
||||||
const val ALWAYS_ON_VPN_ACTION = "android.net.VpnService"
|
const val ALWAYS_ON_VPN_ACTION = "android.net.VpnService"
|
||||||
const val EMAIL_MIME_TYPE = "message/rfc822"
|
const val EMAIL_MIME_TYPE = "plain/text"
|
||||||
const val SYSTEM_EXEMPT_SERVICE_TYPE_ID = 1024
|
const val SYSTEM_EXEMPT_SERVICE_TYPE_ID = 1024
|
||||||
|
|
||||||
const val SUBSCRIPTION_TIMEOUT = 5_000L
|
const val SUBSCRIPTION_TIMEOUT = 5_000L
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
package com.zaneschepke.wireguardautotunnel.util
|
package com.zaneschepke.wireguardautotunnel.util
|
||||||
|
|
||||||
import android.content.BroadcastReceiver
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.pm.PackageInfo
|
import android.content.pm.PackageInfo
|
||||||
import com.zaneschepke.wireguardautotunnel.R
|
import com.zaneschepke.wireguardautotunnel.R
|
||||||
@@ -9,34 +8,23 @@ import com.zaneschepke.wireguardautotunnel.service.tunnel.HandshakeStatus
|
|||||||
import com.zaneschepke.wireguardautotunnel.service.tunnel.statistics.TunnelStatistics
|
import com.zaneschepke.wireguardautotunnel.service.tunnel.statistics.TunnelStatistics
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.DelicateCoroutinesApi
|
import kotlinx.coroutines.DelicateCoroutinesApi
|
||||||
import kotlinx.coroutines.GlobalScope
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.ObsoleteCoroutinesApi
|
||||||
|
import kotlinx.coroutines.channels.ClosedReceiveChannelException
|
||||||
|
import kotlinx.coroutines.channels.ReceiveChannel
|
||||||
|
import kotlinx.coroutines.channels.produce
|
||||||
|
import kotlinx.coroutines.channels.ticker
|
||||||
|
import kotlinx.coroutines.coroutineScope
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.channelFlow
|
||||||
|
import kotlinx.coroutines.selects.whileSelect
|
||||||
import org.amnezia.awg.config.Config
|
import org.amnezia.awg.config.Config
|
||||||
|
import timber.log.Timber
|
||||||
import java.math.BigDecimal
|
import java.math.BigDecimal
|
||||||
import java.text.DecimalFormat
|
import java.text.DecimalFormat
|
||||||
import kotlin.coroutines.CoroutineContext
|
import java.time.Duration
|
||||||
import kotlin.coroutines.EmptyCoroutineContext
|
import java.util.concurrent.ConcurrentLinkedQueue
|
||||||
|
import kotlin.coroutines.cancellation.CancellationException
|
||||||
fun BroadcastReceiver.goAsync(
|
|
||||||
context: CoroutineContext = EmptyCoroutineContext,
|
|
||||||
block: suspend CoroutineScope.() -> Unit
|
|
||||||
) {
|
|
||||||
val pendingResult = goAsync()
|
|
||||||
@OptIn(DelicateCoroutinesApi::class) // Must run globally; there's no teardown callback.
|
|
||||||
GlobalScope.launch(context) {
|
|
||||||
try {
|
|
||||||
block()
|
|
||||||
} finally {
|
|
||||||
pendingResult.finish()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun String.truncateWithEllipsis(allowedLength: Int): String {
|
|
||||||
return if (this.length > allowedLength + 3) {
|
|
||||||
this.substring(0, allowedLength) + "***"
|
|
||||||
} else this
|
|
||||||
}
|
|
||||||
|
|
||||||
fun BigDecimal.toThreeDecimalPlaceString(): String {
|
fun BigDecimal.toThreeDecimalPlaceString(): String {
|
||||||
val df = DecimalFormat("#.###")
|
val df = DecimalFormat("#.###")
|
||||||
@@ -94,3 +82,67 @@ fun Throwable.getMessage(context: Context) : String {
|
|||||||
else -> this.message ?: StringValue.StringResource(R.string.unknown_error).asString(context)
|
else -> this.message ?: StringValue.StringResource(R.string.unknown_error).asString(context)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Chunks based on a time or size threshold.
|
||||||
|
*
|
||||||
|
* Borrowed from this [Stack Overflow question](https://stackoverflow.com/questions/51022533/kotlin-chunk-sequence-based-on-size-and-time).
|
||||||
|
*/
|
||||||
|
@OptIn(ObsoleteCoroutinesApi::class, ExperimentalCoroutinesApi::class)
|
||||||
|
fun <T> ReceiveChannel<T>.chunked(scope: CoroutineScope, size: Int, time: Duration) =
|
||||||
|
scope.produce<List<T>> {
|
||||||
|
while (true) { // this loop goes over each chunk
|
||||||
|
val chunk = ConcurrentLinkedQueue<T>() // current chunk
|
||||||
|
val ticker = ticker(time.toMillis()) // time-limit for this chunk
|
||||||
|
try {
|
||||||
|
whileSelect {
|
||||||
|
ticker.onReceive {
|
||||||
|
false // done with chunk when timer ticks, takes priority over received elements
|
||||||
|
}
|
||||||
|
this@chunked.onReceive {
|
||||||
|
chunk += it
|
||||||
|
chunk.size < size // continue whileSelect if chunk is not full
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e: ClosedReceiveChannelException) {
|
||||||
|
Timber.e(e)
|
||||||
|
return@produce
|
||||||
|
} finally {
|
||||||
|
ticker.cancel()
|
||||||
|
if (chunk.isNotEmpty()) {
|
||||||
|
send(chunk.toList())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@OptIn(DelicateCoroutinesApi::class, ExperimentalCoroutinesApi::class)
|
||||||
|
fun <T> Flow<T>.chunked(size: Int, time: Duration) = channelFlow {
|
||||||
|
coroutineScope {
|
||||||
|
val channel = asChannel(this@chunked).chunked(this, size, time)
|
||||||
|
try {
|
||||||
|
while (!channel.isClosedForReceive) {
|
||||||
|
send(channel.receive())
|
||||||
|
}
|
||||||
|
} catch (e: ClosedReceiveChannelException) {
|
||||||
|
// Channel was closed by the flow completing, nothing to do
|
||||||
|
Timber.w(e)
|
||||||
|
} catch (e: CancellationException) {
|
||||||
|
channel.cancel(e)
|
||||||
|
throw e
|
||||||
|
} catch (e: Exception) {
|
||||||
|
channel.cancel(CancellationException("Closing channel due to flow exception", e))
|
||||||
|
throw e
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@ExperimentalCoroutinesApi
|
||||||
|
fun <T> CoroutineScope.asChannel(flow: Flow<T>): ReceiveChannel<T> = produce {
|
||||||
|
flow.collect { value ->
|
||||||
|
channel.send(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -6,20 +6,102 @@ import android.os.Build
|
|||||||
import android.os.Environment
|
import android.os.Environment
|
||||||
import android.provider.MediaStore
|
import android.provider.MediaStore
|
||||||
import android.provider.MediaStore.MediaColumns
|
import android.provider.MediaStore.MediaColumns
|
||||||
|
import kotlinx.coroutines.CoroutineDispatcher
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
import java.io.FileInputStream
|
||||||
import java.io.FileOutputStream
|
import java.io.FileOutputStream
|
||||||
import java.io.OutputStream
|
import java.io.OutputStream
|
||||||
import java.time.Instant
|
import java.time.Instant
|
||||||
import java.util.zip.ZipEntry
|
import java.util.zip.ZipEntry
|
||||||
import java.util.zip.ZipOutputStream
|
import java.util.zip.ZipOutputStream
|
||||||
|
|
||||||
object FileUtils {
|
class FileUtils(
|
||||||
private const val ZIP_FILE_MIME_TYPE = "application/zip"
|
private val context: Context,
|
||||||
|
private val ioDispatcher: CoroutineDispatcher,
|
||||||
|
) {
|
||||||
|
|
||||||
|
suspend fun readBytesFromFile(file: File): ByteArray {
|
||||||
|
return withContext(ioDispatcher) {
|
||||||
|
FileInputStream(file).use {
|
||||||
|
it.readBytes()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun readTextFromFileName(fileName: String): String {
|
||||||
|
return withContext(ioDispatcher) {
|
||||||
|
context.assets.open(fileName).use { stream ->
|
||||||
|
stream.bufferedReader(Charsets.UTF_8).use {
|
||||||
|
it.readText()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun saveByteArrayToDownloads(content: ByteArray, fileName: String): Result<Unit> {
|
||||||
|
return withContext(ioDispatcher) {
|
||||||
|
try {
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||||
|
val contentValues =
|
||||||
|
ContentValues().apply {
|
||||||
|
put(MediaColumns.DISPLAY_NAME, fileName)
|
||||||
|
put(MediaColumns.MIME_TYPE, Constants.TEXT_MIME_TYPE)
|
||||||
|
put(MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_DOWNLOADS)
|
||||||
|
}
|
||||||
|
val resolver = context.contentResolver
|
||||||
|
val uri =
|
||||||
|
resolver.insert(MediaStore.Downloads.EXTERNAL_CONTENT_URI, contentValues)
|
||||||
|
if (uri != null) {
|
||||||
|
resolver.openOutputStream(uri).use { output ->
|
||||||
|
output?.write(content)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
val target =
|
||||||
|
File(
|
||||||
|
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS),
|
||||||
|
fileName,
|
||||||
|
)
|
||||||
|
FileOutputStream(target).use { output ->
|
||||||
|
output.write(content)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Result.success(Unit)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Result.failure(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun saveFilesToZip(files: List<File>): Result<Unit> {
|
||||||
|
return withContext(ioDispatcher) {
|
||||||
|
try {
|
||||||
|
val zipOutputStream =
|
||||||
|
createDownloadsFileOutputStream(
|
||||||
|
"wg-export_${Instant.now().epochSecond}.zip",
|
||||||
|
Constants.ZIP_FILE_MIME_TYPE,
|
||||||
|
)
|
||||||
|
ZipOutputStream(zipOutputStream).use { zos ->
|
||||||
|
files.forEach { file ->
|
||||||
|
val entry = ZipEntry(file.name)
|
||||||
|
zos.putNextEntry(entry)
|
||||||
|
if (file.isFile) {
|
||||||
|
file.inputStream().use { fis -> fis.copyTo(zos) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return@withContext Result.success(Unit)
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Timber.e(e)
|
||||||
|
Result.failure(WgTunnelExceptions.ConfigExportFailed())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
//TODO issue with android 9
|
//TODO issue with android 9
|
||||||
private fun createDownloadsFileOutputStream(
|
private fun createDownloadsFileOutputStream(
|
||||||
context: Context,
|
|
||||||
fileName: String,
|
fileName: String,
|
||||||
mimeType: String = Constants.ALLOWED_FILE_TYPES
|
mimeType: String = Constants.ALLOWED_FILE_TYPES
|
||||||
): OutputStream? {
|
): OutputStream? {
|
||||||
@@ -45,53 +127,4 @@ object FileUtils {
|
|||||||
}
|
}
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
fun saveFileToDownloads(context: Context, content: String, fileName: String) {
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
|
||||||
val contentValues = ContentValues().apply {
|
|
||||||
put(MediaColumns.DISPLAY_NAME, fileName)
|
|
||||||
put(MediaColumns.MIME_TYPE, Constants.TEXT_MIME_TYPE)
|
|
||||||
put(MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_DOWNLOADS)
|
|
||||||
}
|
|
||||||
val resolver = context.contentResolver
|
|
||||||
val uri = resolver.insert(MediaStore.Downloads.EXTERNAL_CONTENT_URI, contentValues)
|
|
||||||
if (uri != null) {
|
|
||||||
resolver.openOutputStream(uri).use { output ->
|
|
||||||
output?.write(content.toByteArray())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
val target = File(
|
|
||||||
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS),
|
|
||||||
fileName,
|
|
||||||
)
|
|
||||||
FileOutputStream(target).use { output ->
|
|
||||||
output.write(content.toByteArray())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun saveFilesToZip(context: Context, files: List<File>) : Result<Unit> {
|
|
||||||
return try {
|
|
||||||
val zipOutputStream =
|
|
||||||
createDownloadsFileOutputStream(
|
|
||||||
context,
|
|
||||||
"wg-export_${Instant.now().epochSecond}.zip",
|
|
||||||
ZIP_FILE_MIME_TYPE,
|
|
||||||
)
|
|
||||||
ZipOutputStream(zipOutputStream).use { zos ->
|
|
||||||
files.forEach { file ->
|
|
||||||
val entry = ZipEntry(file.name)
|
|
||||||
zos.putNextEntry(entry)
|
|
||||||
if (file.isFile) {
|
|
||||||
file.inputStream().use { fis -> fis.copyTo(zos) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return Result.success(Unit)
|
|
||||||
}
|
|
||||||
} catch (e : Exception) {
|
|
||||||
Timber.e(e)
|
|
||||||
Result.failure(WgTunnelExceptions.ConfigExportFailed())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,114 +11,120 @@ sealed class WgTunnelExceptions : Exception() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
data class SsidConflict(private val userMessage : StringValue = StringValue.StringResource(R.string.error_ssid_exists)) : WgTunnelExceptions() {
|
data class SsidConflict(private val userMessage: StringValue = StringValue.StringResource(R.string.error_ssid_exists)) :
|
||||||
|
WgTunnelExceptions() {
|
||||||
override fun getMessage(context: Context): String {
|
override fun getMessage(context: Context): String {
|
||||||
return userMessage.asString(context)
|
return userMessage.asString(context)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
data class ConfigExportFailed(private val userMessage : StringValue = StringValue.StringResource(R.string.export_configs_failed)) : WgTunnelExceptions() {
|
data class ConfigExportFailed(
|
||||||
|
private val userMessage: StringValue = StringValue.StringResource(
|
||||||
|
R.string.export_configs_failed,
|
||||||
|
)
|
||||||
|
) : WgTunnelExceptions() {
|
||||||
override fun getMessage(context: Context): String {
|
override fun getMessage(context: Context): String {
|
||||||
return userMessage.asString(context)
|
return userMessage.asString(context)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
data class ConfigParseError(private val appendMessage : StringValue = StringValue.Empty) : WgTunnelExceptions() {
|
data class ConfigParseError(private val appendMessage: StringValue = StringValue.Empty) :
|
||||||
|
WgTunnelExceptions() {
|
||||||
override fun getMessage(context: Context): String {
|
override fun getMessage(context: Context): String {
|
||||||
return StringValue.StringResource(R.string.config_parse_error).asString(context) + (
|
return StringValue.StringResource(R.string.config_parse_error).asString(context) + (
|
||||||
if (appendMessage != StringValue.Empty) ": ${appendMessage.asString(context)}" else "")
|
if (appendMessage != StringValue.Empty) ": ${appendMessage.asString(context)}" else "")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
data class RootDenied(private val userMessage : StringValue = StringValue.StringResource(R.string.error_root_denied)) : WgTunnelExceptions() {
|
data class RootDenied(private val userMessage: StringValue = StringValue.StringResource(R.string.error_root_denied)) :
|
||||||
|
WgTunnelExceptions() {
|
||||||
override fun getMessage(context: Context): String {
|
override fun getMessage(context: Context): String {
|
||||||
return userMessage.asString(context)
|
return userMessage.asString(context)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
data class InvalidQrCode(private val userMessage : StringValue = StringValue.StringResource(R.string.error_invalid_code)) : WgTunnelExceptions() {
|
data class InvalidQrCode(private val userMessage: StringValue = StringValue.StringResource(R.string.error_invalid_code)) :
|
||||||
|
WgTunnelExceptions() {
|
||||||
override fun getMessage(context: Context): String {
|
override fun getMessage(context: Context): String {
|
||||||
return userMessage.asString(context)
|
return userMessage.asString(context)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
data class InvalidFileExtension(private val userMessage : StringValue = StringValue.StringResource(R.string.error_file_extension)) : WgTunnelExceptions() {
|
data class InvalidFileExtension(
|
||||||
|
private val userMessage: StringValue = StringValue.StringResource(
|
||||||
|
R.string.error_file_extension,
|
||||||
|
)
|
||||||
|
) : WgTunnelExceptions() {
|
||||||
override fun getMessage(context: Context): String {
|
override fun getMessage(context: Context): String {
|
||||||
return userMessage.asString(context)
|
return userMessage.asString(context)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
data class FileReadFailed(private val userMessage : StringValue = StringValue.StringResource(R.string.error_file_format)) : WgTunnelExceptions() {
|
data class FileReadFailed(private val userMessage: StringValue = StringValue.StringResource(R.string.error_file_format)) :
|
||||||
|
WgTunnelExceptions() {
|
||||||
override fun getMessage(context: Context): String {
|
override fun getMessage(context: Context): String {
|
||||||
return userMessage.asString(context)
|
return userMessage.asString(context)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
data class AuthenticationFailed(private val userMessage : StringValue = StringValue.StringResource(R.string.error_authentication_failed)) : WgTunnelExceptions() {
|
data class AuthenticationFailed(
|
||||||
|
private val userMessage: StringValue = StringValue.StringResource(
|
||||||
|
R.string.error_authentication_failed,
|
||||||
|
)
|
||||||
|
) : WgTunnelExceptions() {
|
||||||
override fun getMessage(context: Context): String {
|
override fun getMessage(context: Context): String {
|
||||||
return userMessage.asString(context)
|
return userMessage.asString(context)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
data class AuthorizationFailed(private val userMessage : StringValue = StringValue.StringResource(R.string.error_authorization_failed)) : WgTunnelExceptions() {
|
data class AuthorizationFailed(
|
||||||
|
private val userMessage: StringValue = StringValue.StringResource(
|
||||||
|
R.string.error_authorization_failed,
|
||||||
|
)
|
||||||
|
) : WgTunnelExceptions() {
|
||||||
override fun getMessage(context: Context): String {
|
override fun getMessage(context: Context): String {
|
||||||
return userMessage.asString(context)
|
return userMessage.asString(context)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
data class BackgroundLocationRequired(private val userMessage : StringValue = StringValue.StringResource(R.string.background_location_required)) : WgTunnelExceptions() {
|
data class BackgroundLocationRequired(
|
||||||
|
private val userMessage: StringValue = StringValue.StringResource(
|
||||||
|
R.string.background_location_required,
|
||||||
|
)
|
||||||
|
) : WgTunnelExceptions() {
|
||||||
override fun getMessage(context: Context): String {
|
override fun getMessage(context: Context): String {
|
||||||
return userMessage.asString(context)
|
return userMessage.asString(context)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
data class LocationServicesRequired(private val userMessage : StringValue = StringValue.StringResource(R.string.location_services_required)) : WgTunnelExceptions() {
|
data class LocationServicesRequired(
|
||||||
|
private val userMessage: StringValue = StringValue.StringResource(
|
||||||
|
R.string.location_services_required,
|
||||||
|
)
|
||||||
|
) : WgTunnelExceptions() {
|
||||||
override fun getMessage(context: Context): String {
|
override fun getMessage(context: Context): String {
|
||||||
return userMessage.asString(context)
|
return userMessage.asString(context)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
data class PreciseLocationRequired(private val userMessage : StringValue = StringValue.StringResource(R.string.precise_location_required)) : WgTunnelExceptions() {
|
data class PreciseLocationRequired(
|
||||||
|
private val userMessage: StringValue = StringValue.StringResource(
|
||||||
|
R.string.precise_location_required,
|
||||||
|
)
|
||||||
|
) : WgTunnelExceptions() {
|
||||||
override fun getMessage(context: Context): String {
|
override fun getMessage(context: Context): String {
|
||||||
return userMessage.asString(context)
|
return userMessage.asString(context)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
data class FileExplorerRequired (private val userMessage : StringValue = StringValue.StringResource(R.string.error_no_file_explorer)) : WgTunnelExceptions() {
|
data class FileExplorerRequired(
|
||||||
|
private val userMessage: StringValue = StringValue.StringResource(
|
||||||
|
R.string.error_no_file_explorer,
|
||||||
|
)
|
||||||
|
) : WgTunnelExceptions() {
|
||||||
override fun getMessage(context: Context): String {
|
override fun getMessage(context: Context): String {
|
||||||
return userMessage.asString(context)
|
return userMessage.asString(context)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// sealed class Message : Event() {
|
|
||||||
// data object ConfigSaved : Message() {
|
|
||||||
// override val message: String
|
|
||||||
// get() = WireGuardAutoTunnel.instance.getString(R.string.config_changes_saved)
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// data object ConfigsExported : Message() {
|
|
||||||
// override val message: String
|
|
||||||
// get() = WireGuardAutoTunnel.instance.getString(R.string.exported_configs_message)
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// data object TunnelOffAction : Message() {
|
|
||||||
// override val message: String
|
|
||||||
// get() = WireGuardAutoTunnel.instance.getString(R.string.turn_off_tunnel)
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// data object TunnelOnAction : Message() {
|
|
||||||
// override val message: String
|
|
||||||
// get() = WireGuardAutoTunnel.instance.getString(R.string.turn_on_tunnel)
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// data object AutoTunnelOffAction : Message() {
|
|
||||||
// override val message: String
|
|
||||||
// get() = WireGuardAutoTunnel.instance.getString(R.string.turn_off_auto)
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,6 @@
|
|||||||
android:viewportWidth="960"
|
android:viewportWidth="960"
|
||||||
android:viewportHeight="960">
|
android:viewportHeight="960">
|
||||||
<path
|
<path
|
||||||
android:pathData="M440,520L200,520v-80h240v-240h80v240h240v80L520,520v240h-80v-240Z"
|
android:fillColor="#e8eaed"
|
||||||
android:fillColor="#e8eaed"/>
|
android:pathData="M440,520L200,520v-80h240v-240h80v240h240v80L520,520v240h-80v-240Z" />
|
||||||
</vector>
|
</vector>
|
||||||
|
|||||||
@@ -4,6 +4,6 @@
|
|||||||
android:viewportWidth="960"
|
android:viewportWidth="960"
|
||||||
android:viewportHeight="960">
|
android:viewportHeight="960">
|
||||||
<path
|
<path
|
||||||
android:pathData="m256,760 l-56,-56 224,-224 -224,-224 56,-56 224,224 224,-224 56,56 -224,224 224,224 -56,56 -224,-224 -224,224Z"
|
android:fillColor="#e8eaed"
|
||||||
android:fillColor="#e8eaed"/>
|
android:pathData="m256,760 l-56,-56 224,-224 -224,-224 56,-56 224,224 224,-224 56,56 -224,224 224,224 -56,56 -224,-224 -224,224Z" />
|
||||||
</vector>
|
</vector>
|
||||||
|
|||||||
@@ -4,6 +4,6 @@
|
|||||||
android:viewportWidth="960"
|
android:viewportWidth="960"
|
||||||
android:viewportHeight="960">
|
android:viewportHeight="960">
|
||||||
<path
|
<path
|
||||||
android:pathData="M200,760h57l391,-391 -57,-57 -391,391v57ZM120,840v-170l528,-527q12,-11 26.5,-17t30.5,-6q16,0 31,6t26,18l55,56q12,11 17.5,26t5.5,30q0,16 -5.5,30.5T817,313L290,840L120,840ZM760,256 L704,200 760,256ZM619,341 L591,312 648,369 619,341Z"
|
android:fillColor="#e8eaed"
|
||||||
android:fillColor="#e8eaed"/>
|
android:pathData="M200,760h57l391,-391 -57,-57 -391,391v57ZM120,840v-170l528,-527q12,-11 26.5,-17t30.5,-6q16,0 31,6t26,18l55,56q12,11 17.5,26t5.5,30q0,16 -5.5,30.5T817,313L290,840L120,840ZM760,256 L704,200 760,256ZM619,341 L591,312 648,369 619,341Z" />
|
||||||
</vector>
|
</vector>
|
||||||
|
|||||||
@@ -0,0 +1,154 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<string name="no_tunnels">Žádné tunely ještě nebyly přidány!</string>
|
||||||
|
<string name="watcher_notification_text_paused">Monitorování změn ve stavu sítě: pozastaveno</string>
|
||||||
|
<string name="notification_permission_required">Je vyžadováno oprávnění pro oznámení.</string>
|
||||||
|
<string name="add_trusted_ssid">Přidat název důvěryhodné Wi-Fi</string>
|
||||||
|
<string name="enable_auto_tunnel">Spustit automatické tunelování</string>
|
||||||
|
<string name="tunnel_mobile_data">Tunelování na mobilních datech</string>
|
||||||
|
<string name="one_tunnel_required">Alespoň jeden tunel je vyžadován pro použití této funkce</string>
|
||||||
|
<string name="privacy_policy">Otevřít zásady soukromí</string>
|
||||||
|
<string name="okay">OK</string>
|
||||||
|
<string name="thank_you">Děkujeme za používání WG Tunnel!</string>
|
||||||
|
<string name="add_tunnels_text">Přidat ze souboru nebo zipu</string>
|
||||||
|
<string name="open_file">Otevřít soubor</string>
|
||||||
|
<string name="add_from_qr">Přidat z QR kódu</string>
|
||||||
|
<string name="qr_scan">QR skenování</string>
|
||||||
|
<string name="tunnel_name">Název tunelu</string>
|
||||||
|
<string name="add_tunnel">Přidat tunel</string>
|
||||||
|
<string name="exclude">Vyloučit</string>
|
||||||
|
<string name="include">Zahrnout</string>
|
||||||
|
<string name="save_changes">Uložit</string>
|
||||||
|
<string name="endpoint">Koncový bod</string>
|
||||||
|
<string name="vpn_connection_failed">Připojení selhalo</string>
|
||||||
|
<string name="always_on_vpn_support">Povolit trvalé připojení VPN</string>
|
||||||
|
<string name="location_services_not_detected">Služby polohy nebyly detekovány</string>
|
||||||
|
<string name="hint_search_packages">Hledat balíčky</string>
|
||||||
|
<string name="attempt_connection">Pokus o připojení...</string>
|
||||||
|
<string name="vpn_starting">VPN se spouští</string>
|
||||||
|
<string name="vpn_on">VPN zapnuto</string>
|
||||||
|
<string name="vpn_off">VPN vypnuto</string>
|
||||||
|
<string name="default_vpn_on">Hlavní VPN zapnuto</string>
|
||||||
|
<string name="default_vpn_off">Hlavní VPN vypnuto</string>
|
||||||
|
<string name="create_import">Vytvořit od základu</string>
|
||||||
|
<string name="turn_off_auto">Akce vyžaduje, aby bylo automatické tunelování vypnuté či pozastavené</string>
|
||||||
|
<string name="add_peer">Přidat peer</string>
|
||||||
|
<string name="comma_separated_list">seznam oddělený čárkami</string>
|
||||||
|
<string name="random">(náhodné)</string>
|
||||||
|
<string name="optional">(volitelné)</string>
|
||||||
|
<string name="optional_no_recommend">(volitelné, nedoporučeno)</string>
|
||||||
|
<string name="preshared_key">Předsdílený klíč</string>
|
||||||
|
<string name="seconds">vteřin(y)</string>
|
||||||
|
<string name="cancel">Zrušit</string>
|
||||||
|
<string name="error_authentication_failed">Autentizace selhala</string>
|
||||||
|
<string name="error_authorization_failed">Nepovedlo se autorizovat</string>
|
||||||
|
<string name="enabled_app_shortcuts">Zapnout zkratky</string>
|
||||||
|
<string name="export_configs">Exportovat konfigurace</string>
|
||||||
|
<string name="export_configs_failed">Nepovedlo se exportovat konfigurace</string>
|
||||||
|
<string name="location_services_required">Služby polohy vyžadovány</string>
|
||||||
|
<string name="background_location_required">Oprávnění pro polohu na pozadí vyžadováno</string>
|
||||||
|
<string name="precise_location_required">Oprávnění pro přesnou polohu vyžadováno</string>
|
||||||
|
<string name="unknown_error">Došlo k neznámé chybě</string>
|
||||||
|
<string name="exported_configs_message">Konfigurace exportovány do stažených souborů</string>
|
||||||
|
<string name="tunnel_on_wifi">Tunelovat na nedůvěryhodné Wi-Fi</string>
|
||||||
|
<string name="email_subject">WG Tunnel podpora</string>
|
||||||
|
<string name="email_chooser">Poslat email…</string>
|
||||||
|
<string name="use_kernel">Použít kernel modul</string>
|
||||||
|
<string name="error_ssid_exists">SSID již existuje</string>
|
||||||
|
<string name="error_root_denied">Oprávnění root zamítnuto</string>
|
||||||
|
<string name="error_no_file_explorer">Žádný průzkumník souborů není nainstalován</string>
|
||||||
|
<string name="error_invalid_code">Neplatný QR kód</string>
|
||||||
|
<string name="error_none">Žádná chyba</string>
|
||||||
|
<string name="auto_tunnel_title">Služba automatického tunelování</string>
|
||||||
|
<string name="resume">Obnovit</string>
|
||||||
|
<string name="active">aktivní</string>
|
||||||
|
<string name="open_issue">Otevřít případ</string>
|
||||||
|
<string name="incorrect_pin">PIN je nesprávný</string>
|
||||||
|
<string name="create_pin">Vytvořte PIN</string>
|
||||||
|
<string name="set_primary_tunnel">Nastavit jako hlavní tunel</string>
|
||||||
|
<string name="use_tunnel_on_wifi_name">Použít tunel pro Wi-Fi</string>
|
||||||
|
<string name="edit_tunnel">Upravit tunel</string>
|
||||||
|
<string name="disabled">vypnuto</string>
|
||||||
|
<string name="auto_tun_on">Obnovit automatické tunelování</string>
|
||||||
|
<string name="auto_tun_off">Pozastavit automatické tunelování</string>
|
||||||
|
<string name="version">Verze</string>
|
||||||
|
<string name="mode">Mód</string>
|
||||||
|
<string name="use_amnezia">"Použít Amnezia userspace "</string>
|
||||||
|
<string name="junk_packet_count">Junk packet počet</string>
|
||||||
|
<string name="junk_packet_minimum_size">Junk packet minimální velikost</string>
|
||||||
|
<string name="junk_packet_maximum_size">Junk packet maximální velikost</string>
|
||||||
|
<string name="init_packet_junk_size">Init packet junk velikost</string>
|
||||||
|
<string name="response_packet_junk_size">Response packet junk velikost</string>
|
||||||
|
<string name="unsure_how">pokud si nejste jisti, jak postupovat</string>
|
||||||
|
<string name="see_the">Podívejte se na</string>
|
||||||
|
<string name="getting_started_guide">začátečnickou příručku</string>
|
||||||
|
<string name="error_file_format">Neplatný formát konfigurace</string>
|
||||||
|
<string name="error_file_extension">Soubor není ve formátu .conf nebo .zip</string>
|
||||||
|
<string name="turn_off_tunnel">Akce vyžaduje vypnutí tunelu</string>
|
||||||
|
<string name="watcher_notification_text_active">Monitorování změn ve stavu sítě: aktivní</string>
|
||||||
|
<string name="tunnel_on_ethernet">Tunelovat na ethernetu</string>
|
||||||
|
<string name="tunnel_start_title">VPN připojeno</string>
|
||||||
|
<string name="prominent_background_location_message">Tato funkce vyžaduje oprávnění pro přístup k poloze na pozadí pro zapnutí monitorování Wi-Fi SSID, i když je aplikace zavřená. Pro více detailů, podívejte se prosím na zásady soukromí umístěné v kategorii Podpora.</string>
|
||||||
|
<string name="tunnel_start_text">Připojeno k tunelu</string>
|
||||||
|
<string name="tunnels">Tunely</string>
|
||||||
|
<string name="disable_auto_tunnel">Zastavit automatické tunelování</string>
|
||||||
|
<string name="tunnel_all">Tunelovat všechny aplikace</string>
|
||||||
|
<string name="config_changes_saved">Změny v konfiguraci uloženy.</string>
|
||||||
|
<string name="icon">Ikona</string>
|
||||||
|
<string name="no_thanks">Ne, děkuji</string>
|
||||||
|
<string name="turn_on">Zapnout</string>
|
||||||
|
<string name="map">Mapa</string>
|
||||||
|
<string name="public_key">Veřejný klíč</string>
|
||||||
|
<string name="addresses">Adresy</string>
|
||||||
|
<string name="dns_servers">DNS servery</string>
|
||||||
|
<string name="allowed_ips">Povolené IP adresy</string>
|
||||||
|
<string name="name">Název</string>
|
||||||
|
<string name="restart">Restartovat tunel</string>
|
||||||
|
<string name="scanning_qr">Skenování QR</string>
|
||||||
|
<string name="none">Žádné názvy důvěryhodných Wi-Fi</string>
|
||||||
|
<string name="other">Ostatní</string>
|
||||||
|
<string name="auto_tunneling">Automatické tunelování</string>
|
||||||
|
<string name="turn_on_tunnel">Akce vyžaduje aktivní tunel</string>
|
||||||
|
<string name="interface_">Rozhraní</string>
|
||||||
|
<string name="done">Hotovo</string>
|
||||||
|
<string name="rotate_keys">Rotovat klíče</string>
|
||||||
|
<string name="private_key">Soukromý klíč</string>
|
||||||
|
<string name="copy_public_key">Kopírovat veřejný klíč</string>
|
||||||
|
<string name="base64_key">base64 klíč</string>
|
||||||
|
<string name="docs_description">Přečíst si dokumentaci</string>
|
||||||
|
<string name="discord_description">Přidat se ke komunitě</string>
|
||||||
|
<string name="email_description">Poslat mi email</string>
|
||||||
|
<string name="support_help_text">Pokud máte potíže, nápady pro zlepšení, nebo se chcete jen zapojit, následující prostředky jsou k dispozici:</string>
|
||||||
|
<string name="location_services_missing_message">Aplikace nenašla žádné služby polohy zapnuté na Vašem zařízení. Dle Vašeho zařízení, tohle může způsobit, že funkce nedůvěryhodné Wi-Fi nedokáže přečíst jméno připojené Wi-Fi. Chcete i přesto pokračovat?</string>
|
||||||
|
<string name="delete_tunnel">Smazat tunel</string>
|
||||||
|
<string name="delete_tunnel_message">Jste si jisti, že chcete smazat tento tunel?</string>
|
||||||
|
<string name="yes">Ano</string>
|
||||||
|
<string name="pause">Pozastavit</string>
|
||||||
|
<string name="paused">pozastaveno</string>
|
||||||
|
<string name="tunneling_apps">Tunelování aplikací</string>
|
||||||
|
<string name="all">vše</string>
|
||||||
|
<string name="included">zahrnuto</string>
|
||||||
|
<string name="excluded">vyloučeno</string>
|
||||||
|
<string name="always_on_disabled">Trvalé VPN připojení se pokusilo spustit tunel, ale tato funkce je vypnutá v nastavení.</string>
|
||||||
|
<string name="no_email_detected">Žádná emailová aplikace nebyla nalezena</string>
|
||||||
|
<string name="no_browser_detected">Žádný prohlížeč nebyl nalezen</string>
|
||||||
|
<string name="logs_saved">Logy uloženy do stažených souborů</string>
|
||||||
|
<string name="read_logs">Přečíst si logy</string>
|
||||||
|
<string name="config_parse_error">Nepovedlo se vložit konfiguraci</string>
|
||||||
|
<string name="pin_created">PIN úspěšně vytvořen</string>
|
||||||
|
<string name="enter_pin">Vložte Váš PIN</string>
|
||||||
|
<string name="no_wifi_names_configured">Žádné názvy Wi-Fi nebyly nastaveny pro tento tunel</string>
|
||||||
|
<string name="enable_app_lock">Zapnout zámek aplikace</string>
|
||||||
|
<string name="restart_on_ping">Restartovat při selhání pingu</string>
|
||||||
|
<string name="mobile_data_tunnel">Nastavit jako tunel pro mobilní data</string>
|
||||||
|
<string name="general">Obecné</string>
|
||||||
|
<string name="settings">Nastavení</string>
|
||||||
|
<string name="support">Podpora</string>
|
||||||
|
<string name="app_name">WG Tunnel</string>
|
||||||
|
<string name="mtu">MTU</string>
|
||||||
|
<string name="db_name">wg-tunnel-db</string>
|
||||||
|
<string name="listen_port">Naslouchací port</string>
|
||||||
|
<string name="auto">(automaticky)</string>
|
||||||
|
<string name="kernel">Kernel</string>
|
||||||
|
<string name="backend">Backend</string>
|
||||||
|
</resources>
|
||||||
@@ -1,42 +1,41 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<resources>
|
||||||
<string name="app_name">WG Tunnel</string>
|
<string name="app_name">WG Tunnel</string>
|
||||||
<string name="error_file_extension">Dies ist keine .conf oder .zip Datei</string>
|
<string name="error_file_extension">Datei ist keine .conf oder .zip</string>
|
||||||
<string name="no_tunnels">Noch keine Tunnel hinzugefügt!</string>
|
<string name="no_tunnels">Noch keine Tunnel hinzugefügt!</string>
|
||||||
<string name="watcher_notification_text_active">Überwachung der Netzwerkänderungen: aktiv</string>
|
<string name="watcher_notification_text_active">Überwachung der Netzwerkänderungen: aktiv</string>
|
||||||
<string name="tunnels">Tunnel</string>
|
<string name="tunnels">Tunnel</string>
|
||||||
<string name="enable_auto_tunnel">Starte automatisches Verbinden</string>
|
<string name="enable_auto_tunnel">Auto-Tunneln starten</string>
|
||||||
<string name="tunnel_mobile_data">Verbinden im Mobilnetzwerk</string>
|
<string name="tunnel_mobile_data">Tunnel für mobile Daten</string>
|
||||||
<string name="one_tunnel_required">Mindestens eine Verbindung wird für diese Funktion benötigt</string>
|
<string name="one_tunnel_required">Mindestens ein Tunnel wird für diese Funktion benötigt</string>
|
||||||
<string name="privacy_policy">Siehe Privacy Policy</string>
|
<string name="privacy_policy">Datenschutzbestimmungen anzeigen</string>
|
||||||
<string name="disable_auto_tunnel">Stoppe automatisches Verbinden</string>
|
<string name="disable_auto_tunnel">Auto-Tunneln stoppen</string>
|
||||||
<string name="okay">Okay</string>
|
<string name="okay">Ok</string>
|
||||||
<string name="tunnel_on_ethernet">Verbindung über Ethernet</string>
|
<string name="tunnel_on_ethernet">Tunnel für Ethernet</string>
|
||||||
<string name="search_icon">Suchsymbol</string>
|
<string name="attempt_connection">Verbindungsversuch..</string>
|
||||||
<string name="attempt_connection">Versuche zu verbinden..</string>
|
<string name="auto_tunneling">Auto-Tunneln</string>
|
||||||
<string name="auto_tunneling">Automatisches verbinden</string>
|
|
||||||
<string name="default_vpn_off">Primärer VPN aus</string>
|
<string name="default_vpn_off">Primärer VPN aus</string>
|
||||||
<string name="turn_on_tunnel">Für diese Altion muss eine Verbindung bestehen</string>
|
<string name="turn_on_tunnel">Für diese Aktion muss ein aktiver Tunnel bestehen</string>
|
||||||
<string name="watcher_notification_text_paused">Überwachung der Netzwerkänderungen: pausiert</string>
|
<string name="watcher_notification_text_paused">Überwachung der Netzwerkänderungen: pausiert</string>
|
||||||
<string name="tunnel_start_title">VPN verbunden</string>
|
<string name="tunnel_start_title">VPN verbunden</string>
|
||||||
<string name="tunnel_start_text">Mit Tunnel verbunden</string>
|
<string name="tunnel_start_text">Mit Tunnel verbunden</string>
|
||||||
<string name="notification_permission_required">Berechtigung für die Benachrichtigung benötigt.</string>
|
<string name="notification_permission_required">Benachrichtigungsberechtigung benötigt.</string>
|
||||||
<string name="add_trusted_ssid">Vertrauenswürdiger WiFi-Name hinzugügen</string>
|
<string name="add_trusted_ssid">Vertrauenswürdigen WLAN-Namen hinzufügen</string>
|
||||||
<string name="prominent_background_location_message">Diese Funktion benötigt Standortberechtigung im Hintergrund um die WiFi SSIDs auch wenn die Applikation geschlossen ist zu überwachen. Für mehr Infos, siehe Privacy Policy auf der Hilfeseite.</string>
|
<string name="prominent_background_location_message">Diese Funktion erfordert die Erlaubnis zur Standortbestimmung im Hintergrund, um die Überwachung der WLAN SSID zu ermöglichen, auch wenn die Anwendung geschlossen ist. Weitere Einzelheiten in den Datenschutzbestimmungen, die auf dem Support-Bildschirm verlinkt sind.</string>
|
||||||
<string name="prominent_background_location_title">Vereinbarung der Standortberechtigung im Hintergrund</string>
|
<string name="prominent_background_location_title">Vereinbarung der Standortberechtigung im Hintergrund</string>
|
||||||
<string name="thank_you">Danke fürs benutzen von WG Tunnel!</string>
|
<string name="thank_you">Danke fürs Benutzen von WG Tunnel!</string>
|
||||||
<string name="trusted_ssid_empty_description">SSID eingeben</string>
|
<string name="trusted_ssid_empty_description">SSID eingeben</string>
|
||||||
<string name="trusted_ssid_value_description">SSID bestätigen</string>
|
<string name="trusted_ssid_value_description">SSID bestätigen</string>
|
||||||
<string name="add_tunnels_text">Von Datei oder ZIP hinzufügen</string>
|
<string name="add_tunnels_text">Von Datei oder ZIP hinzufügen</string>
|
||||||
<string name="open_file">Datei geöffnet</string>
|
<string name="open_file">Datei öffnen</string>
|
||||||
<string name="add_from_qr">Von QR-Code hinzufügen</string>
|
<string name="add_from_qr">Über QR-Code hinzufügen</string>
|
||||||
<string name="qr_scan">Scanne QR</string>
|
<string name="qr_scan">Scanne QR</string>
|
||||||
<string name="tunnel_name">Verbindungsname</string>
|
<string name="tunnel_name">Tunnel Name</string>
|
||||||
<string name="add_tunnel">Verbindung hinzufügen</string>
|
<string name="add_tunnel">Tunnel hinzufügen</string>
|
||||||
<string name="exclude">Ausgenommen</string>
|
<string name="exclude">Ausschließen</string>
|
||||||
<string name="include">Eingeschlossen</string>
|
<string name="include">Einschließen</string>
|
||||||
<string name="tunnel_all">Verbinde alle Apps</string>
|
<string name="tunnel_all">Alle Apps tunneln</string>
|
||||||
<string name="config_changes_saved">Änderungen der Konfiguration gespeichert.</string>
|
<string name="config_changes_saved">Konfigurationsänderungen gespeichert.</string>
|
||||||
<string name="save_changes">Speichern</string>
|
<string name="save_changes">Speichern</string>
|
||||||
<string name="icon">Symbol</string>
|
<string name="icon">Symbol</string>
|
||||||
<string name="no_thanks">Nein danke</string>
|
<string name="no_thanks">Nein danke</string>
|
||||||
@@ -50,52 +49,51 @@
|
|||||||
<string name="allowed_ips">Erlaubte IPs</string>
|
<string name="allowed_ips">Erlaubte IPs</string>
|
||||||
<string name="endpoint">Endpunkt</string>
|
<string name="endpoint">Endpunkt</string>
|
||||||
<string name="name">Name</string>
|
<string name="name">Name</string>
|
||||||
<string name="restart">Verbindung neustarten</string>
|
<string name="restart">Tunnel neustarten</string>
|
||||||
<string name="vpn_connection_failed">Verbindung fehlgeschlagen</string>
|
<string name="vpn_connection_failed">Verbindung fehlgeschlagen</string>
|
||||||
<string name="always_on_vpn_support">Erlaube Always-On VPN</string>
|
<string name="always_on_vpn_support">Always-On VPN erlauben</string>
|
||||||
<string name="location_services_not_detected">Standortdienste Nicht Erkannt</string>
|
<string name="location_services_not_detected">Standortdienste nicht erkannt</string>
|
||||||
<string name="hint_search_packages">Suche packete</string>
|
<string name="hint_search_packages">Pakete suchen</string>
|
||||||
<string name="clear_icon">Lösche Symbol</string>
|
|
||||||
<string name="vpn_starting">VPN startet</string>
|
<string name="vpn_starting">VPN startet</string>
|
||||||
<string name="db_name">wg-tunnel-db</string>
|
<string name="db_name">wg-tunnel-db</string>
|
||||||
<string name="scanning_qr">Scanne nach QR</string>
|
<string name="scanning_qr">Scanne nach QR</string>
|
||||||
<string name="none">Keine vertrauenswürdigen WiFi Namen</string>
|
<string name="none">Keine vertrauenswürdigen WLAN Namen</string>
|
||||||
<string name="other">Andere</string>
|
<string name="other">Sonstige</string>
|
||||||
<string name="vpn_on">VPN an</string>
|
<string name="vpn_on">VPN an</string>
|
||||||
<string name="vpn_off">VPN aus</string>
|
<string name="vpn_off">VPN aus</string>
|
||||||
<string name="default_vpn_on">Primärer VPN an</string>
|
<string name="default_vpn_on">Primärer VPN an</string>
|
||||||
<string name="create_import">Starte von Grund auf neu</string>
|
<string name="create_import">Von Grund auf neu erstellen</string>
|
||||||
<string name="turn_off_auto">Für diese Aktion muss automatisches Verbinden ausgeschaltet oder pausiert sein</string>
|
<string name="turn_off_auto">Für diese Aktion muss Auto-Tunneln ausgeschaltet oder pausiert sein</string>
|
||||||
<string name="add_peer">Peer hinzufügen</string>
|
<string name="add_peer">Peer hinzufügen</string>
|
||||||
<string name="done">Erledigt</string>
|
<string name="done">Erledigt</string>
|
||||||
<string name="rotate_keys">Schlüssel ändern</string>
|
<string name="rotate_keys">Schlüssel rotieren</string>
|
||||||
<string name="private_key">Privater Schlüssel</string>
|
<string name="private_key">Privater Schlüssel</string>
|
||||||
<string name="copy_public_key">Öffentlicher Schlüssel kopieren</string>
|
<string name="copy_public_key">Öffentlichen Schlüssel kopieren</string>
|
||||||
<string name="base64_key">base64-Schlüssel</string>
|
<string name="base64_key">base64-Schlüssel</string>
|
||||||
<string name="comma_separated_list">Kommaseparierte Liste</string>
|
<string name="comma_separated_list">Kommaseparierte Liste</string>
|
||||||
<string name="delete_tunnel">Tunnel löschen</string>
|
<string name="delete_tunnel">Tunnel löschen</string>
|
||||||
<string name="persistent_keepalive">Dauerhaftes Keepalive</string>
|
<string name="persistent_keepalive">Dauerhaftes Keepalive</string>
|
||||||
<string name="background_location_required">Hintergrund Standortdienste erforderlich</string>
|
<string name="background_location_required">Hintergrund Standortdienste erforderlich</string>
|
||||||
<string name="enable_app_lock">App Sperre aktiviert</string>
|
<string name="enable_app_lock">App-Sperre aktivieren</string>
|
||||||
<string name="discord_description">Tritt der Community bei</string>
|
<string name="discord_description">Tritt der Community bei</string>
|
||||||
<string name="interface_">Schnittstelle</string>
|
<string name="interface_">Schnittstelle</string>
|
||||||
<string name="listen_port">Eingehender Port</string>
|
<string name="listen_port">Eingehender Port</string>
|
||||||
<string name="random">(zufällig)</string>
|
<string name="random">(zufällig)</string>
|
||||||
<string name="optional">(nicht erforderlich)</string>
|
<string name="optional">(optional)</string>
|
||||||
<string name="optional_no_recommend">(nicht erforderlich, aber empfohlen)</string>
|
<string name="optional_no_recommend">(Optional, nicht empfohlen)</string>
|
||||||
<string name="seconds">Sekunden</string>
|
<string name="seconds">Sekunden</string>
|
||||||
<string name="cancel">Abbrechen</string>
|
<string name="cancel">Abbrechen</string>
|
||||||
<string name="preshared_key">Geteilter Schlüssel</string>
|
<string name="preshared_key">Geteilter Schlüssel</string>
|
||||||
<string name="enabled_app_shortcuts">Aktiviere App Verknüpfungen</string>
|
<string name="enabled_app_shortcuts">App-Verknüpfungen aktivieren</string>
|
||||||
<string name="exported_configs_message">Konfigurationen in den Download Ordner exportiert</string>
|
<string name="exported_configs_message">Konfigurationen in Download Ordner exportiert</string>
|
||||||
<string name="tunnel_on_wifi">Tunnel bei nicht vertrauenswürdigem Wifi</string>
|
<string name="tunnel_on_wifi">Tunnel bei nicht vertrauenswürdigem WLAN</string>
|
||||||
<string name="email_subject">WG Tunnel Unterstützung</string>
|
<string name="email_subject">WG Tunnel Unterstützung</string>
|
||||||
<string name="docs_description">Lese die Dokumentation</string>
|
<string name="docs_description">Dokumentation lesen</string>
|
||||||
<string name="email_description">Sende mir eine E-Mail</string>
|
<string name="email_description">Sende mir eine E-Mail</string>
|
||||||
<string name="support_help_text">Bei Fehlern oder Verbesserungsvorschlägen stehen folgende Ressourcen zur Verfügung:</string>
|
<string name="support_help_text">Bei Fehlern oder Verbesserungsvorschlägen stehen folgende Ressourcen zur Verfügung:</string>
|
||||||
<string name="error_root_denied">Root Shell verboten</string>
|
<string name="error_root_denied">Root Shell verweigert</string>
|
||||||
<string name="error_no_file_explorer">Kein Datei-Explorer installiert</string>
|
<string name="error_no_file_explorer">Kein Datei-Explorer installiert</string>
|
||||||
<string name="location_services_missing_message">Die App konnte keine aktivierten Standortdienste auf deinem Gerät erkennen. Dies kann auf manchen Geräten ein Auslesen des aktuellen WIFI-Names für die \"Nicht vertrauenswürdiges WIFI\" Funktion verhindern. Möchtest du trotzdem fortfahren ?</string>
|
<string name="location_services_missing_message">Die App erkennt keine auf deinem Gerät aktivierten Standortdienste. Je nach Gerät kann dies dazu führen, dass die Funktion \"Nicht vertrauenswürdiges WLAN\" den WLAN-Namen nicht lesen kann. Möchtest du trotzdem fortfahren?</string>
|
||||||
<string name="auto_tunnel_title">Auto-Tunnel Service</string>
|
<string name="auto_tunnel_title">Auto-Tunnel Service</string>
|
||||||
<string name="delete_tunnel_message">Bist du sicher, dass du den Tunnel löschen möchtest?</string>
|
<string name="delete_tunnel_message">Bist du sicher, dass du den Tunnel löschen möchtest?</string>
|
||||||
<string name="yes">Ja</string>
|
<string name="yes">Ja</string>
|
||||||
@@ -103,62 +101,71 @@
|
|||||||
<string name="pause">Pausieren</string>
|
<string name="pause">Pausieren</string>
|
||||||
<string name="paused">Pausiert</string>
|
<string name="paused">Pausiert</string>
|
||||||
<string name="active">Aktiv</string>
|
<string name="active">Aktiv</string>
|
||||||
<string name="go">Gehe</string>
|
<string name="go">Los</string>
|
||||||
<string name="excluded">Ausgeschlossen</string>
|
<string name="excluded">ausgeschlossen</string>
|
||||||
<string name="all">Alle</string>
|
<string name="all">Alle</string>
|
||||||
<string name="always_on_disabled">Always-on VPN wollte eine Tunnel starten, aber dieses Feature ist in den Einstellungen deaktiviert.</string>
|
<string name="always_on_disabled">Always-on VPN wollte einen Tunnel starten, aber dieses Feature ist in den Einstellungen deaktiviert.</string>
|
||||||
<string name="no_browser_detected">Kein Browser erkannt</string>
|
<string name="no_browser_detected">Keinen Browser erkannt</string>
|
||||||
<string name="open_issue">Öffne ein Issue</string>
|
<string name="open_issue">Issue öffnen</string>
|
||||||
<string name="read_logs">Lese die Logs</string>
|
<string name="read_logs">Logs lesen</string>
|
||||||
<string name="auto">(automatisch)</string>
|
<string name="auto">(automatisch)</string>
|
||||||
<string name="config_parse_error">Fehler beim lesen der Konfiguration</string>
|
<string name="config_parse_error">Fehler beim Lesen der Konfiguration</string>
|
||||||
<string name="incorrect_pin">PIN is nicht korrekt</string>
|
<string name="incorrect_pin">PIN nicht korrekt</string>
|
||||||
<string name="pin_created">PIN erfolgreich angelegt</string>
|
<string name="pin_created">PIN erfolgreich erstellt</string>
|
||||||
<string name="enter_pin">Gib deine PIN ein</string>
|
<string name="enter_pin">Deine PIN eingeben</string>
|
||||||
<string name="auto_off">Auto-Tunnel pausieren</string>
|
<string name="auto_off">Auto-Tunneln pausieren</string>
|
||||||
<string name="auto_tun_on">Auto-Tunnel fortsetzen</string>
|
<string name="auto_tun_on">Auto-Tunneln fortsetzen</string>
|
||||||
<string name="auto_tun_off">Auto-Tunnel pausieren</string>
|
<string name="auto_tun_off">Auto-Tunneln pausieren</string>
|
||||||
<string name="version">Version</string>
|
<string name="version">Version</string>
|
||||||
<string name="mode">Modus</string>
|
<string name="mode">Modus</string>
|
||||||
<string name="userspace">Userspace</string>
|
<string name="userspace">Benutzerfläche</string>
|
||||||
<string name="settings">Einstellungen</string>
|
<string name="settings">Einstellungen</string>
|
||||||
<string name="support">Unterstützung</string>
|
<string name="support">Unterstützung</string>
|
||||||
<string name="watcher_channel_id">Beobachter Kanal</string>
|
<string name="watcher_channel_id">Wächterkanal</string>
|
||||||
<string name="error_authentication_failed">Anmeldung fehlgeschlagen</string>
|
<string name="error_authentication_failed">Anmeldung fehlgeschlagen</string>
|
||||||
<string name="export_configs">Exportiere Konfigurationen</string>
|
<string name="export_configs">Konfigurationen exportieren</string>
|
||||||
<string name="unknown_error">Ein unbekannter Fehler ist aufgetreten</string>
|
<string name="unknown_error">Unbekannter Fehler aufgetreten</string>
|
||||||
<string name="email_chooser">Sende eine E-Mail…</string>
|
<string name="email_chooser">Sende eine E-Mail…</string>
|
||||||
<string name="error_authorization_failed">Fehler bei der Authorisierung</string>
|
<string name="error_authorization_failed">Autorisierung fehlgeschlagen</string>
|
||||||
<string name="location_services_required">Standortdienste erforderlich</string>
|
<string name="location_services_required">Standortdienste erforderlich</string>
|
||||||
<string name="precise_location_required">Genauer Standort erforderlich</string>
|
<string name="precise_location_required">Genauer Standort erforderlich</string>
|
||||||
<string name="error_invalid_code">Fehlerhafter QR code</string>
|
<string name="error_invalid_code">Ungültiger QR Code</string>
|
||||||
<string name="error_none">Kein Fehler</string>
|
<string name="error_none">Kein Fehler</string>
|
||||||
<string name="tunneling_apps">Tunnel Anwendungen</string>
|
<string name="tunneling_apps">Getunnelte Apps</string>
|
||||||
<string name="included">Hinzugefügt</string>
|
<string name="included">eingeschlossen</string>
|
||||||
<string name="no_email_detected">Keine E-Mail Anwendung erkannt</string>
|
<string name="no_email_detected">Keine E-Mail-App erkannt</string>
|
||||||
<string name="logs_saved">Logs im Download Ordner gespeichert</string>
|
<string name="logs_saved">Logs im Download Ordner gespeichert</string>
|
||||||
<string name="create_pin">Erstelle eine PIN</string>
|
<string name="create_pin">PIN erstellen</string>
|
||||||
<string name="use_tunnel_on_wifi_name">Tunnel in Wifi-Namen verwenden</string>
|
<string name="use_tunnel_on_wifi_name">Tunnel für WLAN-Namen verwenden</string>
|
||||||
<string name="no_wifi_names_configured">Keine Wifi-Namen für diesen Tunnel konfiguriert</string>
|
<string name="no_wifi_names_configured">Keine WLAN-Namen für diesen Tunnel konfiguriert</string>
|
||||||
<string name="disabled">Deaktiviert</string>
|
<string name="disabled">Deaktiviert</string>
|
||||||
<string name="mobile_data_tunnel">Als Tunnel für Mobile Daten setzen</string>
|
<string name="mobile_data_tunnel">Als Tunnel für Mobile Daten setzen</string>
|
||||||
<string name="general">Allgemein</string>
|
<string name="general">Allgemein</string>
|
||||||
<string name="restart_on_ping">Neustart bei PING Fehler (Beta)</string>
|
<string name="restart_on_ping">Neustart bei PING Fehler (Beta)</string>
|
||||||
<string name="edit_tunnel">Tunnel bearbeiten</string>
|
<string name="edit_tunnel">Tunnel bearbeiten</string>
|
||||||
<string name="set_primary_tunnel">Als Primären Tunnel setzen</string>
|
<string name="set_primary_tunnel">Als Primären Tunnel setzen</string>
|
||||||
<string name="auto_on">Auto-Tunnel fortsetzen</string>
|
<string name="auto_on">Auto-Tunneln fortsetzen</string>
|
||||||
<string name="vpn_channel_id">VPN Kanal</string>
|
<string name="vpn_channel_id">VPN Kanal</string>
|
||||||
<string name="vpn_channel_name">VPN Benachrichtigungskanal</string>
|
<string name="vpn_channel_name">VPN Benachrichtigungskanal</string>
|
||||||
<string name="watcher_channel_name">Beobachter Benachrichtigungskanal</string>
|
<string name="watcher_channel_name">Wächterbenachrichtigungskanal</string>
|
||||||
<string name="turn_off_tunnel">Aktion erfordert deaktivierten Tunnel</string>
|
<string name="turn_off_tunnel">Aktion erfordert deaktivierten Tunnel</string>
|
||||||
<string name="kernel">Kernel</string>
|
<string name="kernel">Kernel</string>
|
||||||
<string name="use_kernel">Verwende das Kernel Modul</string>
|
<string name="use_kernel">Kernelmodul verwenden</string>
|
||||||
<string name="error_ssid_exists">SSID existiert bereits</string>
|
<string name="error_ssid_exists">SSID existiert bereits</string>
|
||||||
<string name="use_amnezia">"Benutze Amnezia Benuzterumgebung "</string>
|
<string name="use_amnezia">"Amnezia Benutzerumgebung benutzen "</string>
|
||||||
<string name="junk_packet_count">Müll Packet Anzahk</string>
|
<string name="junk_packet_count">Junk-Paket Anzahl</string>
|
||||||
<string name="junk_packet_maximum_size">Müll Packet maximale Grösse</string>
|
<string name="junk_packet_maximum_size">Junk-Paket maximale Grösse</string>
|
||||||
<string name="init_packet_junk_size">Erstes Packet Müllgrösse</string>
|
<string name="init_packet_junk_size">Initial Junk-Paketgröße</string>
|
||||||
<string name="backend">Backend</string>
|
<string name="backend">Backend</string>
|
||||||
<string name="junk_packet_minimum_size">Müll Packet minimale Grösse</string>
|
<string name="junk_packet_minimum_size">Junk-Paket minimale Grösse</string>
|
||||||
<string name="response_packet_junk_size">Antwortpaket Müllgrösse</string>
|
<string name="response_packet_junk_size">Antwort Junk-Paketgröße</string>
|
||||||
|
<string name="init_packet_magic_header">Initialpaket magic header</string>
|
||||||
|
<string name="getting_started_guide">Startanleitung erhalten</string>
|
||||||
|
<string name="transport_packet_magic_header">Transportpaket magic header</string>
|
||||||
|
<string name="underload_packet_magic_header">Unterlastpaket magic header</string>
|
||||||
|
<string name="see_the">Schaue das</string>
|
||||||
|
<string name="unsure_how">Wenn du nicht sicher bist, wie du weiterverfahren sollst</string>
|
||||||
|
<string name="export_configs_failed">Konfigurationsexport fehlgeschlagen</string>
|
||||||
|
<string name="error_file_format">Ungültige Konfiguration Tunnel-Format</string>
|
||||||
|
<string name="response_packet_magic_header">Antwortpaket magic header</string>
|
||||||
</resources>
|
</resources>
|
||||||
@@ -72,7 +72,6 @@
|
|||||||
<string name="always_on_vpn_support">Permitir VPN siempre-activada</string>
|
<string name="always_on_vpn_support">Permitir VPN siempre-activada</string>
|
||||||
<string name="location_services_not_detected">Servicios de Ubicación No Detectados</string>
|
<string name="location_services_not_detected">Servicios de Ubicación No Detectados</string>
|
||||||
<string name="hint_search_packages">Buscar paquetes</string>
|
<string name="hint_search_packages">Buscar paquetes</string>
|
||||||
<string name="clear_icon">Icono claro</string>
|
|
||||||
<string name="attempt_connection">Intentando conexión...</string>
|
<string name="attempt_connection">Intentando conexión...</string>
|
||||||
<string name="vpn_starting">Iniciando VPN</string>
|
<string name="vpn_starting">Iniciando VPN</string>
|
||||||
<string name="db_name">wg-tunnel-db</string>
|
<string name="db_name">wg-tunnel-db</string>
|
||||||
@@ -106,7 +105,6 @@
|
|||||||
<string name="listen_port">Puerto de escucha</string>
|
<string name="listen_port">Puerto de escucha</string>
|
||||||
<string name="preshared_key">Clave previamente compartida</string>
|
<string name="preshared_key">Clave previamente compartida</string>
|
||||||
<string name="persistent_keepalive">Keepalive persistente</string>
|
<string name="persistent_keepalive">Keepalive persistente</string>
|
||||||
<string name="search_icon">Icono de búsqueda</string>
|
|
||||||
<string name="docs_description">Leer documentación</string>
|
<string name="docs_description">Leer documentación</string>
|
||||||
<string name="email_description">Envíame un email</string>
|
<string name="email_description">Envíame un email</string>
|
||||||
<string name="support_help_text">Si tienes problemas, ideas para mejoras, o simlemente comprometerte, tienes disponibles los siguientes recursos:</string>
|
<string name="support_help_text">Si tienes problemas, ideas para mejoras, o simlemente comprometerte, tienes disponibles los siguientes recursos:</string>
|
||||||
@@ -137,7 +135,7 @@
|
|||||||
<string name="pin_created">Pin creado con éxito</string>
|
<string name="pin_created">Pin creado con éxito</string>
|
||||||
<string name="enter_pin">Introduce tu pin</string>
|
<string name="enter_pin">Introduce tu pin</string>
|
||||||
<string name="create_pin">Crear pin</string>
|
<string name="create_pin">Crear pin</string>
|
||||||
<string name="enable_app_lock">Bloqueo de app activado</string>
|
<string name="enable_app_lock">Activar el bloqueo de aplicaciones</string>
|
||||||
<string name="restart_on_ping">Reiniciar al fallar ping (beta)</string>
|
<string name="restart_on_ping">Reiniciar al fallar ping (beta)</string>
|
||||||
<string name="set_primary_tunnel">Establecer como túnel Principal</string>
|
<string name="set_primary_tunnel">Establecer como túnel Principal</string>
|
||||||
<string name="no_wifi_names_configured">No hay nombres Wi-Fi configurados para este túnel</string>
|
<string name="no_wifi_names_configured">No hay nombres Wi-Fi configurados para este túnel</string>
|
||||||
@@ -154,4 +152,9 @@
|
|||||||
<string name="watcher_channel_id">Canal del obvervador</string>
|
<string name="watcher_channel_id">Canal del obvervador</string>
|
||||||
<string name="watcher_channel_name">Canal de notificación del obvervador</string>
|
<string name="watcher_channel_name">Canal de notificación del obvervador</string>
|
||||||
<string name="prominent_background_location_message">La monitorización SSID Wi-Fi necesita de permiso de ubicación en segundo plano incluso si la app está cerrada. Mira el enlace a la Política de Privacidad en la pantalla de ayuda para más detalles.</string>
|
<string name="prominent_background_location_message">La monitorización SSID Wi-Fi necesita de permiso de ubicación en segundo plano incluso si la app está cerrada. Mira el enlace a la Política de Privacidad en la pantalla de ayuda para más detalles.</string>
|
||||||
|
<string name="export_configs_failed">Error al exportar la configuración</string>
|
||||||
|
<string name="use_amnezia">"Utilizar el entorno de usuario de Amnezia "</string>
|
||||||
|
<string name="junk_packet_count">Recuento de paquetes basura</string>
|
||||||
|
<string name="backend">Backend</string>
|
||||||
|
<string name="junk_packet_minimum_size">Tamaño mínimo del paquete basura</string>
|
||||||
</resources>
|
</resources>
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources></resources>
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<string name="tunnels">Tunele</string>
|
||||||
|
<string name="app_name">WG Tunnel</string>
|
||||||
|
</resources>
|
||||||
@@ -0,0 +1,162 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<string name="watcher_notification_text_active">Monitoramento de mudanças do estado da rede: Ativo</string>
|
||||||
|
<string name="watcher_notification_text_paused">Monitoramento de mudanças do estado da rede: pausado</string>
|
||||||
|
<string name="tunnel_start_title">VPN conectada</string>
|
||||||
|
<string name="tunnel_on_ethernet">Túnel na ethernet</string>
|
||||||
|
<string name="save_changes">Salvar</string>
|
||||||
|
<string name="public_key">Chave pública</string>
|
||||||
|
<string name="addresses">Endereços</string>
|
||||||
|
<string name="dns_servers">Servidores DNS</string>
|
||||||
|
<string name="endpoint">Endpoint</string>
|
||||||
|
<string name="name">Nome</string>
|
||||||
|
<string name="restart">Reiniciar Túnel</string>
|
||||||
|
<string name="create_import">Criar do zero</string>
|
||||||
|
<string name="turn_off_auto">Esta ação precisa do auto-túnel desativado ou pausado</string>
|
||||||
|
<string name="rotate_keys">Revezar chaves</string>
|
||||||
|
<string name="private_key">Chave privada</string>
|
||||||
|
<string name="base64_key">Chave base64</string>
|
||||||
|
<string name="optional_no_recommend">(opcional, não recomendado)</string>
|
||||||
|
<string name="preshared_key">Chave pré-compartilhada</string>
|
||||||
|
<string name="seconds">segundos</string>
|
||||||
|
<string name="export_configs">Exportar configurações</string>
|
||||||
|
<string name="export_configs_failed">Falhou ao exportar configurações</string>
|
||||||
|
<string name="go">ir</string>
|
||||||
|
<string name="error_no_file_explorer">Nenhum explorador de arquivos instalado</string>
|
||||||
|
<string name="error_invalid_code">Código QR inválido</string>
|
||||||
|
<string name="auto_tunnel_title">Serviço de Auto-túnel</string>
|
||||||
|
<string name="excluded">excluído</string>
|
||||||
|
<string name="all">todos</string>
|
||||||
|
<string name="enter_pin">Digite seu pin</string>
|
||||||
|
<string name="use_tunnel_on_wifi_name">Usar túnel em wifi com nome</string>
|
||||||
|
<string name="auto_tun_on">Continuar auto-túnel</string>
|
||||||
|
<string name="auto_tun_off">Pausar auto-túnel</string>
|
||||||
|
<string name="version">Versão</string>
|
||||||
|
<string name="mode">Modo</string>
|
||||||
|
<string name="use_amnezia">"Usar Amnezia em modo usuário "</string>
|
||||||
|
<string name="junk_packet_count">Número de pacotes-lixo</string>
|
||||||
|
<string name="junk_packet_minimum_size">Tamanho mínimo de pacote-lixo</string>
|
||||||
|
<string name="junk_packet_maximum_size">Tamanho máximo de pacote-lixo</string>
|
||||||
|
<string name="init_packet_junk_size">Tamanho de pacote-lixo inicial</string>
|
||||||
|
<string name="response_packet_junk_size">Tamanho de resposta de pacote-lixo</string>
|
||||||
|
<string name="app_name">WG Tunnel</string>
|
||||||
|
<string name="no_tunnels">Nenhum túnel foi adicionado!</string>
|
||||||
|
<string name="error_file_extension">O arquivo não é .conf ou .zip</string>
|
||||||
|
<string name="prominent_background_location_message">Este recurso precisa de permissões de localização em segundo plano para habilitar o monitoramento do SSID da rede Wi-Fi mesmo quando o aplicativo está fechado. Para mais detalhes, por favor veja a Política de Privacidade na tela de Suporte.</string>
|
||||||
|
<string name="turn_off_tunnel">Esta ação só é possível com o túnel inativo</string>
|
||||||
|
<string name="enabled_app_shortcuts">Habilitar atalhos do aplicativo</string>
|
||||||
|
<string name="notification_permission_required">Necessita permissões de notificação.</string>
|
||||||
|
<string name="add_trusted_ssid">Adicionar nome de Wi-Fi confiável</string>
|
||||||
|
<string name="enable_auto_tunnel">Iniciar auto-túnel</string>
|
||||||
|
<string name="tunnels">Túneis</string>
|
||||||
|
<string name="disable_auto_tunnel">Parar auto-túnel</string>
|
||||||
|
<string name="tunnel_start_text">Conectado ao túnel</string>
|
||||||
|
<string name="trusted_ssid_empty_description">Digite o SSID</string>
|
||||||
|
<string name="no_thanks">Não, obrigado</string>
|
||||||
|
<string name="privacy_policy">Ver a Política de Privacidade</string>
|
||||||
|
<string name="okay">OK</string>
|
||||||
|
<string name="tunnel_mobile_data">Túnel em dados móveis</string>
|
||||||
|
<string name="one_tunnel_required">Pelo menos um túnel é necessário para usar este recurso</string>
|
||||||
|
<string name="prominent_background_location_title">Revelar a localização em segundo plano</string>
|
||||||
|
<string name="thank_you">Obrigado por usar o WG Tunnel!</string>
|
||||||
|
<string name="trusted_ssid_value_description">Envie o SSID</string>
|
||||||
|
<string name="open_file">Abrir Arquivo</string>
|
||||||
|
<string name="add_from_qr">Adicionar a partir de código QR</string>
|
||||||
|
<string name="add_tunnels_text">Adicionar a partir de arquivo ou zip</string>
|
||||||
|
<string name="tunnel_all">Todos os aplicativos pelo túnel</string>
|
||||||
|
<string name="icon">Ícone</string>
|
||||||
|
<string name="turn_on">Ligar</string>
|
||||||
|
<string name="qr_scan">Escanear o código QR</string>
|
||||||
|
<string name="tunnel_name">Nome do Túnel</string>
|
||||||
|
<string name="add_tunnel">Adicionar Túnel</string>
|
||||||
|
<string name="config_changes_saved">Mudanças nas configurações salvas.</string>
|
||||||
|
<string name="exclude">Excluir</string>
|
||||||
|
<string name="include">Incluir</string>
|
||||||
|
<string name="map">Mapa</string>
|
||||||
|
<string name="vpn_connection_failed">Falha na conexão</string>
|
||||||
|
<string name="mtu">MTU</string>
|
||||||
|
<string name="always_on_vpn_support">Permitir VPN sempre ligada</string>
|
||||||
|
<string name="allowed_ips">IPs Permitidos</string>
|
||||||
|
<string name="attempt_connection">Tentando conexão..</string>
|
||||||
|
<string name="peer">Par</string>
|
||||||
|
<string name="location_services_not_detected">Serviço de localização não foi detectado</string>
|
||||||
|
<string name="hint_search_packages">Procurar pacotes</string>
|
||||||
|
<string name="vpn_starting">Iniciando VPN</string>
|
||||||
|
<string name="other">Outro</string>
|
||||||
|
<string name="scanning_qr">Escaneando código QR</string>
|
||||||
|
<string name="none">Nenhum nome de Wi-Fi confiável</string>
|
||||||
|
<string name="auto_tunneling">Auto-túnel</string>
|
||||||
|
<string name="default_vpn_on">VPN Principal ligada</string>
|
||||||
|
<string name="vpn_on">VPN ligada</string>
|
||||||
|
<string name="vpn_off">VPN desligada</string>
|
||||||
|
<string name="listen_port">Porta de escuta</string>
|
||||||
|
<string name="default_vpn_off">VPN Principal desligada</string>
|
||||||
|
<string name="turn_on_tunnel">Esta ação precisa um túnel ativo</string>
|
||||||
|
<string name="done">Feito</string>
|
||||||
|
<string name="add_peer">Adicionar par</string>
|
||||||
|
<string name="interface_">Interface</string>
|
||||||
|
<string name="copy_public_key">Copiar chave pública</string>
|
||||||
|
<string name="comma_separated_list">Lista separada por vírgulas</string>
|
||||||
|
<string name="optional">(opcional)</string>
|
||||||
|
<string name="random">(aleatório)</string>
|
||||||
|
<string name="persistent_keepalive">Manter a conexão persistente (keepalive)</string>
|
||||||
|
<string name="cancel">Cancelar</string>
|
||||||
|
<string name="error_authentication_failed">Autenticação falhou</string>
|
||||||
|
<string name="error_authorization_failed">Autorização falhou</string>
|
||||||
|
<string name="restart_on_ping">Reiniciar em falha de ping (beta)</string>
|
||||||
|
<string name="background_location_required">Necessita a localização em segundo plano</string>
|
||||||
|
<string name="location_services_required">Necessita dos serviços de localização</string>
|
||||||
|
<string name="email_description">Me envie um email</string>
|
||||||
|
<string name="error_ssid_exists">SSID já existe</string>
|
||||||
|
<string name="delete_tunnel_message">Tem certeza que você quer apagar este túnel?</string>
|
||||||
|
<string name="yes">Sim</string>
|
||||||
|
<string name="precise_location_required">Necessita da localização precisa</string>
|
||||||
|
<string name="exported_configs_message">Configurações exportadas para downloads</string>
|
||||||
|
<string name="unknown_error">Ocorreu um erro desconhecido</string>
|
||||||
|
<string name="email_subject">Suporte para o WG Tunnel</string>
|
||||||
|
<string name="tunnel_on_wifi">Túnel em Wi-Fi não confiável</string>
|
||||||
|
<string name="error_none">Nenhum erro</string>
|
||||||
|
<string name="delete_tunnel">Apagar túnel</string>
|
||||||
|
<string name="email_chooser">Enviar um email…</string>
|
||||||
|
<string name="use_kernel">Usar o módulo do kernel</string>
|
||||||
|
<string name="discord_description">Juntar-se à comunidade</string>
|
||||||
|
<string name="docs_description">Ler a documentação</string>
|
||||||
|
<string name="support_help_text">Se você enfrentar problemas, tiver ideias para melhorias ou apenas quiser participar, os seguintes recursos estão disponíveis:</string>
|
||||||
|
<string name="error_root_denied">Shell Root negado</string>
|
||||||
|
<string name="location_services_missing_message">O aplicativo não detectou o serviço de localização habilitado no seu dispositivo. Dependendo do dispositivo, isto pode causar que a função de Wi-Fi não confiável falhe em ler o nome do Wi-Fi. Quer continuar mesmo assim?</string>
|
||||||
|
<string name="paused">pausado</string>
|
||||||
|
<string name="included">incluso</string>
|
||||||
|
<string name="resume">Continuar</string>
|
||||||
|
<string name="pause">Pausar</string>
|
||||||
|
<string name="active">ativo</string>
|
||||||
|
<string name="always_on_disabled">VPN sempre ligada tentou iniciar um túnel, mas este recurso está desligado nas configurações.</string>
|
||||||
|
<string name="open_issue">Abrir um problema</string>
|
||||||
|
<string name="tunneling_apps">Aplicativos em túnel</string>
|
||||||
|
<string name="no_email_detected">Nenhum aplicativo de email detectado</string>
|
||||||
|
<string name="no_browser_detected">Nenhum navegador detectado</string>
|
||||||
|
<string name="logs_saved">Registros salvos em downloads</string>
|
||||||
|
<string name="incorrect_pin">O Pin está errado</string>
|
||||||
|
<string name="auto">(automático)</string>
|
||||||
|
<string name="disabled">desligado</string>
|
||||||
|
<string name="read_logs">Ler os registros</string>
|
||||||
|
<string name="config_parse_error">Falha na interpretação das configurações</string>
|
||||||
|
<string name="pin_created">Pin criado com sucesso</string>
|
||||||
|
<string name="auto_on">Continuar auto-túnel</string>
|
||||||
|
<string name="create_pin">Criar um pin</string>
|
||||||
|
<string name="enable_app_lock">Ligar trava de aplicativo</string>
|
||||||
|
<string name="no_wifi_names_configured">Nenhum Wi-Fi configurado para este túnel</string>
|
||||||
|
<string name="edit_tunnel">Editar túnel</string>
|
||||||
|
<string name="mobile_data_tunnel">Selecionar como túnel em dados móveis</string>
|
||||||
|
<string name="set_primary_tunnel">Selecionar como túnel principal</string>
|
||||||
|
<string name="general">Geral</string>
|
||||||
|
<string name="userspace">Modo usuário</string>
|
||||||
|
<string name="support">Suporte</string>
|
||||||
|
<string name="kernel">Kernel</string>
|
||||||
|
<string name="auto_off">Pausar auto-túnel</string>
|
||||||
|
<string name="backend">Backend</string>
|
||||||
|
<string name="settings">Configurações</string>
|
||||||
|
<string name="unsure_how">se não tiver certeza em como continuar</string>
|
||||||
|
<string name="see_the">Veja o</string>
|
||||||
|
<string name="getting_started_guide">guia de início rápido</string>
|
||||||
|
<string name="error_file_format">Formato de arquivo de configuração inválido</string>
|
||||||
|
</resources>
|
||||||
@@ -11,9 +11,9 @@
|
|||||||
<string name="icon">Иконка</string>
|
<string name="icon">Иконка</string>
|
||||||
<string name="turn_on">Включить</string>
|
<string name="turn_on">Включить</string>
|
||||||
<string name="add_from_qr">Добавить из QR</string>
|
<string name="add_from_qr">Добавить из QR</string>
|
||||||
<string name="qr_scan">Сканер QR</string>
|
<string name="qr_scan">Сканировать QR</string>
|
||||||
<string name="auto_tunneling">Авто-туннелирование</string>
|
<string name="auto_tunneling">Авто-туннелирование</string>
|
||||||
<string name="no_tunnels">Туннели еще не добавлены!</string>
|
<string name="no_tunnels">Туннели ещё не добавлены!</string>
|
||||||
<string name="open_file">Открыть файл</string>
|
<string name="open_file">Открыть файл</string>
|
||||||
<string name="exclude">Исключить</string>
|
<string name="exclude">Исключить</string>
|
||||||
<string name="include">Включить</string>
|
<string name="include">Включить</string>
|
||||||
@@ -23,10 +23,10 @@
|
|||||||
<string name="no_thanks">Нет, спасибо</string>
|
<string name="no_thanks">Нет, спасибо</string>
|
||||||
<string name="map">Карта</string>
|
<string name="map">Карта</string>
|
||||||
<string name="addresses">Адреса</string>
|
<string name="addresses">Адреса</string>
|
||||||
<string name="dns_servers">DNS сервера</string>
|
<string name="dns_servers">DNS-серверы</string>
|
||||||
<string name="allowed_ips">Разрешенные IP</string>
|
<string name="allowed_ips">Разрешённые IP</string>
|
||||||
<string name="endpoint">Конечная точка</string>
|
<string name="endpoint">Конечная точка</string>
|
||||||
<string name="restart">Перезагрузить туннель</string>
|
<string name="restart">Перезапустить туннель</string>
|
||||||
<string name="vpn_connection_failed">Ошибка соединения</string>
|
<string name="vpn_connection_failed">Ошибка соединения</string>
|
||||||
<string name="always_on_vpn_support">Разрешить постоянный VPN</string>
|
<string name="always_on_vpn_support">Разрешить постоянный VPN</string>
|
||||||
<string name="hint_search_packages">Поиск приложений</string>
|
<string name="hint_search_packages">Поиск приложений</string>
|
||||||
@@ -36,4 +36,136 @@
|
|||||||
<string name="interface_">Интерфейс</string>
|
<string name="interface_">Интерфейс</string>
|
||||||
<string name="optional">(необязательно)</string>
|
<string name="optional">(необязательно)</string>
|
||||||
<string name="optional_no_recommend">(необязательно, не рекомендуется)</string>
|
<string name="optional_no_recommend">(необязательно, не рекомендуется)</string>
|
||||||
|
<string name="resume">Восстановить</string>
|
||||||
|
<string name="tunneling_apps">Туннелируемые приложения</string>
|
||||||
|
<string name="paused">приостановлено</string>
|
||||||
|
<string name="active">активно</string>
|
||||||
|
<string name="excluded">исключено</string>
|
||||||
|
<string name="all">все</string>
|
||||||
|
<string name="no_email_detected">Приложение для отправки почты не обнаружено</string>
|
||||||
|
<string name="enter_pin">Введите свой PIN-код</string>
|
||||||
|
<string name="enable_app_lock">Включить блокировку приложения</string>
|
||||||
|
<string name="restart_on_ping">Перезапуск при сбое пинга (бета)</string>
|
||||||
|
<string name="settings">Настройки</string>
|
||||||
|
<string name="support">Поддержка</string>
|
||||||
|
<string name="backend">Модуль</string>
|
||||||
|
<string name="use_amnezia">"Использовать модуль Amnezia режима пользователя "</string>
|
||||||
|
<string name="init_packet_junk_size">Начальный размер «мусорного» пакета</string>
|
||||||
|
<string name="response_packet_junk_size">Размер ответного «мусорного» пакета</string>
|
||||||
|
<string name="init_packet_magic_header">Заголовок пакета инициализации</string>
|
||||||
|
<string name="see_the">Смотрите</string>
|
||||||
|
<string name="tunnel_mobile_data">Туннелировать через мобильный интернет</string>
|
||||||
|
<string name="tunnel_on_ethernet">Туннелировать через Ethernet</string>
|
||||||
|
<string name="cancel">Отмена</string>
|
||||||
|
<string name="docs_description">Посмотреть документацию</string>
|
||||||
|
<string name="discord_description">Присоединиться к сообществу</string>
|
||||||
|
<string name="email_chooser">Отправить письмо…</string>
|
||||||
|
<string name="add_trusted_ssid">Добавить доверенное имя сети Wi-Fi</string>
|
||||||
|
<string name="included">включено</string>
|
||||||
|
<string name="vpn_starting">Идёт запуск VPN</string>
|
||||||
|
<string name="auto_tunnel_title">Сервис авто-туннелирования</string>
|
||||||
|
<string name="create_import">Создать с нуля</string>
|
||||||
|
<string name="private_key">Закрытый ключ</string>
|
||||||
|
<string name="pause">Приостановить</string>
|
||||||
|
<string name="logs_saved">Журнал сохранён в Загрузки</string>
|
||||||
|
<string name="db_name">wg-tunnel-db</string>
|
||||||
|
<string name="base64_key">Ключ в base64</string>
|
||||||
|
<string name="error_no_file_explorer">Не найден файловый менеджер</string>
|
||||||
|
<string name="delete_tunnel_message">Вы действительно хотите удалить этот туннель?</string>
|
||||||
|
<string name="version">Версия</string>
|
||||||
|
<string name="unknown_error">Неизвестная ошибка</string>
|
||||||
|
<string name="tunnel_on_wifi">Туннель в недоверенных сетях Wi-Fi</string>
|
||||||
|
<string name="error_file_extension">Файл не имеет расширение .conf или .zip</string>
|
||||||
|
<string name="random">(случайно)</string>
|
||||||
|
<string name="tunnel_start_text">Подключено к туннелю</string>
|
||||||
|
<string name="app_name">WG Tunnel</string>
|
||||||
|
<string name="vpn_channel_id">Канал VPN</string>
|
||||||
|
<string name="vpn_channel_name">Канал уведомлений VPN</string>
|
||||||
|
<string name="watcher_channel_id">Канал наблюдателя</string>
|
||||||
|
<string name="watcher_channel_name">Канал уведомлений наблюдателя</string>
|
||||||
|
<string name="watcher_notification_text_active">Отслеживание состояния сети: активно</string>
|
||||||
|
<string name="notification_permission_required">Требуется разрешение на отображение уведомлений.</string>
|
||||||
|
<string name="tunnels">Туннели</string>
|
||||||
|
<string name="enable_auto_tunnel">Запустить авто-туннель</string>
|
||||||
|
<string name="disable_auto_tunnel">Остановить авто-туннель</string>
|
||||||
|
<string name="one_tunnel_required">Для использования этой функции нужно настроить хотя бы один туннель</string>
|
||||||
|
<string name="okay">Хорошо</string>
|
||||||
|
<string name="prominent_background_location_title">Фоновая передача местоположения</string>
|
||||||
|
<string name="thank_you">Благодарим Вас за использование WG Tunnel!</string>
|
||||||
|
<string name="trusted_ssid_value_description">Отправить SSID</string>
|
||||||
|
<string name="trusted_ssid_empty_description">Введите SSID</string>
|
||||||
|
<string name="add_tunnels_text">Добавить из файла или архива</string>
|
||||||
|
<string name="location_services_not_detected">Сервисы местоположения не обнаружены</string>
|
||||||
|
<string name="attempt_connection">Попытка соединения…</string>
|
||||||
|
<string name="scanning_qr">Поиск QR-кода</string>
|
||||||
|
<string name="none">Нет доверенных имён Wi-Fi</string>
|
||||||
|
<string name="default_vpn_on">Основной VPN вкл.</string>
|
||||||
|
<string name="default_vpn_off">Основной VPN выкл.</string>
|
||||||
|
<string name="turn_off_auto">Необходимо приостановить или отключить авто-туннелирование</string>
|
||||||
|
<string name="turn_on_tunnel">Действие требует наличие активного туннеля</string>
|
||||||
|
<string name="add_peer">Добавить пира</string>
|
||||||
|
<string name="rotate_keys">Обновить ключи</string>
|
||||||
|
<string name="comma_separated_list">разделённый запятыми список</string>
|
||||||
|
<string name="listen_port">Порт прослушивания</string>
|
||||||
|
<string name="preshared_key">Общий ключ</string>
|
||||||
|
<string name="seconds">секунд</string>
|
||||||
|
<string name="persistent_keepalive">Поддержание работы туннеля (keepalive)</string>
|
||||||
|
<string name="error_authentication_failed">Сбой аутентификации</string>
|
||||||
|
<string name="export_configs">Экспорт конфигурации</string>
|
||||||
|
<string name="location_services_required">Необходимо наличие служб местоположения</string>
|
||||||
|
<string name="error_invalid_code">Некорректный QR-код</string>
|
||||||
|
<string name="location_services_missing_message">Приложение не обнаружило сервис местоположения на вашем устройстве. На некоторых устройствах это может привести к невозможности определения имени сети Wi-Fi и сбою функции недоверенной сети. Всё равно продолжить?</string>
|
||||||
|
<string name="error_ssid_exists">SSID уже существует</string>
|
||||||
|
<string name="error_root_denied">Root-доступ запрещён</string>
|
||||||
|
<string name="error_none">Нет ошибки</string>
|
||||||
|
<string name="delete_tunnel">Удалить туннель</string>
|
||||||
|
<string name="yes">Да</string>
|
||||||
|
<string name="use_tunnel_on_wifi_name">Использовать туннель в сети Wi-Fi</string>
|
||||||
|
<string name="disabled">отключено</string>
|
||||||
|
<string name="auto_off">Приостановить авто-туннель</string>
|
||||||
|
<string name="auto_tun_on">Восстановить авто-туннель</string>
|
||||||
|
<string name="watcher_notification_text_paused">Отслеживание состояния сети: приостановлено</string>
|
||||||
|
<string name="tunnel_start_title">VPN подключен</string>
|
||||||
|
<string name="prominent_background_location_message">Эта функция требует фоновый доступ к местоположению для отслеживания имён сетей Wi-Fi, даже когда приложение закрыто. Для получения дополнительной информации, прочтите политику конфиденциальности на экране поддержки.</string>
|
||||||
|
<string name="done">Готово</string>
|
||||||
|
<string name="copy_public_key">Копировать открытый ключ</string>
|
||||||
|
<string name="error_authorization_failed">Не удалось пройти аутентификацию</string>
|
||||||
|
<string name="enabled_app_shortcuts">Включить ярлыки приложения</string>
|
||||||
|
<string name="export_configs_failed">Не удалось экспортировать конфигурацию</string>
|
||||||
|
<string name="precise_location_required">Необходимо точное местоположение</string>
|
||||||
|
<string name="open_issue">Сообщить о проблеме</string>
|
||||||
|
<string name="config_parse_error">Не удалось разобрать файл конфигурации</string>
|
||||||
|
<string name="incorrect_pin">Некорректный PIN-код</string>
|
||||||
|
<string name="pin_created">PIN создан успешно</string>
|
||||||
|
<string name="create_pin">Создать PIN-код</string>
|
||||||
|
<string name="mobile_data_tunnel">Назначить как туннель для мобильного интернета</string>
|
||||||
|
<string name="edit_tunnel">Редактировать туннель</string>
|
||||||
|
<string name="auto_on">Восстановить авто-туннель</string>
|
||||||
|
<string name="auto_tun_off">Приостановить авто-туннель</string>
|
||||||
|
<string name="junk_packet_count">Количество «мусорных» пакетов</string>
|
||||||
|
<string name="junk_packet_maximum_size">Максимальный размер «мусорного» пакета</string>
|
||||||
|
<string name="error_file_format">некорректный формат конфигурации туннеля</string>
|
||||||
|
<string name="background_location_required">Необходим фоновый доступ к местоположению</string>
|
||||||
|
<string name="exported_configs_message">Экспорт конфигурации в Загрузки</string>
|
||||||
|
<string name="email_subject">Поддержка WG Tunnel</string>
|
||||||
|
<string name="go">вперёд</string>
|
||||||
|
<string name="email_description">Отправить письмо автору</string>
|
||||||
|
<string name="support_help_text">Если у Вас возникли проблемы, или появилась идея по улучшению, или Вы просто хотите пообщаться, используйте следующие ресурсы:</string>
|
||||||
|
<string name="use_kernel">Использовать модуль режима ядра</string>
|
||||||
|
<string name="always_on_disabled">Функция всегда доступного VPN попыталась запустить туннель, но эта функция отключена в настройках.</string>
|
||||||
|
<string name="no_browser_detected">Веб-браузер не обнаружен</string>
|
||||||
|
<string name="read_logs">Посмотреть журнал</string>
|
||||||
|
<string name="auto">(авто)</string>
|
||||||
|
<string name="set_primary_tunnel">Назначить как главный туннель</string>
|
||||||
|
<string name="no_wifi_names_configured">Имена сетей Wi-Fi не назначены этому туннелю</string>
|
||||||
|
<string name="general">Общее</string>
|
||||||
|
<string name="mode">Режим</string>
|
||||||
|
<string name="userspace">Пользователя</string>
|
||||||
|
<string name="kernel">Модуль ядра</string>
|
||||||
|
<string name="junk_packet_minimum_size">Минимальный размер «мусорного» пакета</string>
|
||||||
|
<string name="response_packet_magic_header">Заголовок пакета ответа</string>
|
||||||
|
<string name="transport_packet_magic_header">Заголовок транспортного пакета</string>
|
||||||
|
<string name="getting_started_guide">руководство по началу работы</string>
|
||||||
|
<string name="unsure_how">, если не уверены, что делать дальше</string>
|
||||||
|
<string name="underload_packet_magic_header">Заголовок пакета под нагрузкой</string>
|
||||||
</resources>
|
</resources>
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<resources>
|
<resources>
|
||||||
<string name="app_name">WG Tunnel</string>
|
<string name="app_name">WG Tunnel</string>
|
||||||
<string name="vpn_channel_id">VPN Kanalı</string>
|
<string name="vpn_channel_id">VPN Kanalı</string>
|
||||||
@@ -8,38 +7,38 @@
|
|||||||
<string name="github_url" translatable="false">https://github.com/zaneschepke/wgtunnel/issues</string>
|
<string name="github_url" translatable="false">https://github.com/zaneschepke/wgtunnel/issues</string>
|
||||||
<string name="docs_url" translatable="false">https://zaneschepke.com/wgtunnel-docs/overview.html</string>
|
<string name="docs_url" translatable="false">https://zaneschepke.com/wgtunnel-docs/overview.html</string>
|
||||||
<string name="privacy_policy_url" translatable="false">https://zaneschepke.com/wgtunnel-docs/privacypolicy.html</string>
|
<string name="privacy_policy_url" translatable="false">https://zaneschepke.com/wgtunnel-docs/privacypolicy.html</string>
|
||||||
<string name="error_file_extension">Dosya bir .conf veya .zip değil</string>
|
<string name="error_file_extension">Dosya .conf veya .zip değil</string>
|
||||||
<string name="turn_off_tunnel">Eylem tünelin kapalı olmasını gerektiriyor</string>
|
<string name="turn_off_tunnel">İşlem için tünelin kapalı olması gerekiyor</string>
|
||||||
<string name="no_tunnels">Henüz bir tünel ekli değil!</string>
|
<string name="no_tunnels">Henüz tünel eklenmedi!</string>
|
||||||
<string name="discord_url" translatable="false">https://discord.gg/rbRRNh6H7V</string>
|
<string name="discord_url" translatable="false">https://discord.gg/rbRRNh6H7V</string>
|
||||||
<string name="watcher_notification_text_active">Ağ durumu değişikliklerini izleme: etkin</string>
|
<string name="watcher_notification_text_active">Ağ durum değişiklikleri izleniyor: aktif</string>
|
||||||
<string name="watcher_notification_text_paused">Ağ durumu değişikliklerini izleme: duraklatıldı</string>
|
<string name="watcher_notification_text_paused">Ağ durum değişiklikleri izleniyor: duraklatıldı</string>
|
||||||
<string name="tunnel_start_title">VPN bağlandı</string>
|
<string name="tunnel_start_title">VPN bağlandı</string>
|
||||||
<string name="tunnel_start_text">Tünele bağlı</string>
|
<string name="tunnel_start_text">Tünele bağlandı</string>
|
||||||
<string name="notification_permission_required">Bildirim izni gerekli.</string>
|
<string name="notification_permission_required">Bildirim izni gerekli.</string>
|
||||||
<string name="add_trusted_ssid">Güvenilir Wi-Fi adı ekle</string>
|
<string name="add_trusted_ssid">Güvenilir wifi adı ekle</string>
|
||||||
<string name="tunnels">Tüneller</string>
|
<string name="tunnels">Tüneller</string>
|
||||||
<string name="enable_auto_tunnel">Otomatik tünellemeyi başlat</string>
|
<string name="enable_auto_tunnel">Otomatik tünellemeyi başlat</string>
|
||||||
<string name="disable_auto_tunnel">Otomatik tünellemeyi durdur</string>
|
<string name="disable_auto_tunnel">Otomatik tünellemeyi durdur</string>
|
||||||
<string name="tunnel_mobile_data">Mobil veri üzerinde tünel</string>
|
<string name="tunnel_mobile_data">Mobil veride tünel</string>
|
||||||
<string name="one_tunnel_required">Bu özelliği kullanabilmek için en az bir tünel gereklidir.</string>
|
<string name="one_tunnel_required">Bu özelliği kullanmak için en az bir tünel gerekli</string>
|
||||||
<string name="privacy_policy">Gizlilik Politikası</string>
|
<string name="privacy_policy">Gizlilik Politikasını Görüntüle</string>
|
||||||
<string name="okay">Tamam</string>
|
<string name="okay">Tamam</string>
|
||||||
<string name="tunnel_on_ethernet">Ethernet üzerinde tünel</string>
|
<string name="tunnel_on_ethernet">Ethernet\'te tünel</string>
|
||||||
<string name="prominent_background_location_message">Bu özellik, uygulama kapalıyken bile Wi-Fi SSID izlemesini etkinleştirmek için arka plan konumu izni gerektirir. Daha fazla bilgi için lütfen Destek ekranında bağlantısı verilen Gizlilik Politikasına bakın.</string>
|
<string name="prominent_background_location_message">Bu özellik, uygulama kapalıyken bile Wi-Fi SSID izlemesini etkinleştirmek için arka plan konum iznine ihtiyaç duyar. Daha fazla ayrıntı için lütfen Destek ekranında bağlantısı verilen Gizlilik Politikasına bakın.</string>
|
||||||
<string name="prominent_background_location_title">Arka Plan Konum Açıklaması</string>
|
<string name="prominent_background_location_title">Arka Plan Konum Açıklaması</string>
|
||||||
<string name="thank_you">WG Tunnel\'i kullandığınız için teşekkür ederiz!</string>
|
<string name="thank_you">WG Tunnel\'ı kullandığınız için teşekkürler!</string>
|
||||||
<string name="trusted_ssid_empty_description">SSID\'yi gir</string>
|
<string name="trusted_ssid_empty_description">SSID girin</string>
|
||||||
<string name="trusted_ssid_value_description">SSID\'yi gönder</string>
|
<string name="trusted_ssid_value_description">SSID\'yi gönder</string>
|
||||||
<string name="add_tunnels_text">Dosyadan veya ZIP\'ten ekle</string>
|
<string name="add_tunnels_text">Dosyadan veya zip\'ten ekle</string>
|
||||||
<string name="open_file">Dosya Aç</string>
|
<string name="open_file">Dosya Aç</string>
|
||||||
<string name="add_from_qr">QR kodundan ekle</string>
|
<string name="add_from_qr">QR kodundan ekle</string>
|
||||||
<string name="qr_scan">QR kodu tarat</string>
|
<string name="qr_scan">QR Tarama</string>
|
||||||
<string name="tunnel_name">Tünel adı</string>
|
<string name="tunnel_name">Tünel Adı</string>
|
||||||
<string name="add_tunnel">Tünel ekle</string>
|
<string name="add_tunnel">Tünel Ekle</string>
|
||||||
<string name="exclude">Hariç tut</string>
|
<string name="exclude">Hariç tut</string>
|
||||||
<string name="include">Dahil et</string>
|
<string name="include">Dahil et</string>
|
||||||
<string name="tunnel_all">Tüm uygulamaları dahil et</string>
|
<string name="tunnel_all">Tüm uygulamaları tünelle</string>
|
||||||
<string name="config_changes_saved">Yapılandırma değişiklikleri kaydedildi.</string>
|
<string name="config_changes_saved">Yapılandırma değişiklikleri kaydedildi.</string>
|
||||||
<string name="save_changes">Kaydet</string>
|
<string name="save_changes">Kaydet</string>
|
||||||
<string name="icon">Simge</string>
|
<string name="icon">Simge</string>
|
||||||
@@ -55,17 +54,15 @@
|
|||||||
<string name="endpoint">Uç nokta (endpoint)</string>
|
<string name="endpoint">Uç nokta (endpoint)</string>
|
||||||
<string name="name">Ad</string>
|
<string name="name">Ad</string>
|
||||||
<string name="restart">Tüneli Yeniden Başlat</string>
|
<string name="restart">Tüneli Yeniden Başlat</string>
|
||||||
<string name="vpn_connection_failed">Bağlantı kurulamadı</string>
|
<string name="vpn_connection_failed">Bağlantı başarısız</string>
|
||||||
<string name="always_on_vpn_support">Her Zaman Açık VPN\'e İzin Ver</string>
|
<string name="always_on_vpn_support">Her Zaman Açık VPN\'e İzin Ver</string>
|
||||||
<string name="location_services_not_detected">Konum Hizmetleri Algılanmadı</string>
|
<string name="location_services_not_detected">Konum Hizmetleri Algılanmadı</string>
|
||||||
<string name="hint_search_packages">Uygulama arayın</string>
|
<string name="hint_search_packages">Paketlerde ara</string>
|
||||||
<string name="clear_icon">Simgeyi Temizle</string>
|
<string name="attempt_connection">Bağlantı deneniyor..</string>
|
||||||
<string name="search_icon">Simge Ara</string>
|
|
||||||
<string name="attempt_connection">Bağlantı kurulmaya çalışılıyor..</string>
|
|
||||||
<string name="vpn_starting">VPN başlatılıyor</string>
|
<string name="vpn_starting">VPN başlatılıyor</string>
|
||||||
<string name="db_name">wg-tunnel-db</string>
|
<string name="db_name">wg-tunnel-db</string>
|
||||||
<string name="scanning_qr">QR taranıyor</string>
|
<string name="scanning_qr">QR için taranıyor</string>
|
||||||
<string name="none">Güvenilir Wi-Fi adı yok</string>
|
<string name="none">Güvenilir wifi adı yok</string>
|
||||||
<string name="other">Diğer</string>
|
<string name="other">Diğer</string>
|
||||||
<string name="auto_tunneling">Otomatik tünelleme</string>
|
<string name="auto_tunneling">Otomatik tünelleme</string>
|
||||||
<string name="vpn_on">VPN açık</string>
|
<string name="vpn_on">VPN açık</string>
|
||||||
@@ -73,50 +70,50 @@
|
|||||||
<string name="default_vpn_on">Birincil VPN açık</string>
|
<string name="default_vpn_on">Birincil VPN açık</string>
|
||||||
<string name="default_vpn_off">Birincil VPN kapalı</string>
|
<string name="default_vpn_off">Birincil VPN kapalı</string>
|
||||||
<string name="create_import">Sıfırdan oluştur</string>
|
<string name="create_import">Sıfırdan oluştur</string>
|
||||||
<string name="turn_off_auto">Eylem, otomatik tünelin devre dışı bırakılmasını veya duraklatılmasını gerektirir</string>
|
<string name="turn_off_auto">İşlem için otomatik tünelin devre dışı veya duraklatılmış olması gerekiyor</string>
|
||||||
<string name="turn_on_tunnel">Eylem aktif tünel gerektirir</string>
|
<string name="turn_on_tunnel">İşlem için aktif tünel gerekiyor</string>
|
||||||
<string name="add_peer">Eş (peer) ekle</string>
|
<string name="add_peer">Eş ekle</string>
|
||||||
<string name="done">Tamam</string>
|
<string name="done">Tamam</string>
|
||||||
<string name="interface_">Arayüz</string>
|
<string name="interface_">Arayüz</string>
|
||||||
<string name="rotate_keys">Tuşları döndür</string>
|
<string name="rotate_keys">Anahtarları döndür</string>
|
||||||
<string name="private_key">Özel anahtar</string>
|
<string name="private_key">Özel anahtar</string>
|
||||||
<string name="copy_public_key">Genel anahtarı kopyala</string>
|
<string name="copy_public_key">Genel anahtarı kopyala</string>
|
||||||
<string name="base64_key">Base64 anahtarı</string>
|
<string name="base64_key">base64 anahtar</string>
|
||||||
<string name="comma_separated_list">Virgül ile ayrılmış liste ekleyin</string>
|
<string name="comma_separated_list">virgülle ayrılmış liste</string>
|
||||||
<string name="listen_port">Bağlantı noktası</string>
|
<string name="listen_port">Dinleme portu</string>
|
||||||
<string name="random">(Rastgele)</string>
|
<string name="random">(rastgele)</string>
|
||||||
<string name="optional">(İsteğe bağlı)</string>
|
<string name="optional">(isteğe bağlı)</string>
|
||||||
<string name="optional_no_recommend">(İsteğe bağlı, önerilmez)</string>
|
<string name="optional_no_recommend">(isteğe bağlı, önerilmez)</string>
|
||||||
<string name="preshared_key">Ön paylaşımlı anahtar</string>
|
<string name="preshared_key">Önceden paylaşılmış anahtar</string>
|
||||||
<string name="seconds">saniye</string>
|
<string name="seconds">saniye</string>
|
||||||
<string name="persistent_keepalive">Kalıcı olarak canlı tutma</string>
|
<string name="persistent_keepalive">Kalıcı canlı tutma</string>
|
||||||
<string name="cancel">İptal</string>
|
<string name="cancel">İptal</string>
|
||||||
<string name="error_authentication_failed">Kimlik doğrulama başarısız oldu</string>
|
<string name="error_authentication_failed">Kimlik doğrulama başarısız oldu</string>
|
||||||
<string name="error_authorization_failed">Yetkilendirilemedi</string>
|
<string name="error_authorization_failed">Yetkilendirme başarısız oldu</string>
|
||||||
<string name="enabled_app_shortcuts">Uygulama kısayollarını etkinleştir</string>
|
<string name="enabled_app_shortcuts">Uygulama kısayollarını etkinleştir</string>
|
||||||
<string name="export_configs">Yapılandırmaları dışa aktar</string>
|
<string name="export_configs">Yapılandırmaları dışa aktar</string>
|
||||||
<string name="location_services_required">Gerekli konum hizmetleri</string>
|
<string name="export_configs_failed">Yapılandırmaları dışa aktarma başarısız oldu</string>
|
||||||
|
<string name="location_services_required">Konum hizmetleri gerekli</string>
|
||||||
<string name="background_location_required">Arka plan konumu gerekli</string>
|
<string name="background_location_required">Arka plan konumu gerekli</string>
|
||||||
<string name="precise_location_required">Kesin konum gerekli</string>
|
<string name="precise_location_required">Hassas konum gerekli</string>
|
||||||
<string name="unknown_error">Bilinmeyen bir hata oluştu</string>
|
<string name="unknown_error">Bilinmeyen bir hata oluştu</string>
|
||||||
<string name="exported_configs_message">Yapılandırmalar indirilenler\'e aktarıldı</string>
|
<string name="exported_configs_message">Yapılandırmalar indirilenler klasörüne aktarıldı</string>
|
||||||
<string name="tunnel_on_wifi">Güvenilmeyen kablosuz internet bağlantısında tünel</string>
|
<string name="tunnel_on_wifi">Güvenilmeyen wifi\'da tünel</string>
|
||||||
<string name="my_email" translatable="false">support@zaneschepke.com</string>
|
<string name="my_email" translatable="false">support@zaneschepke.com</string>
|
||||||
<string name="email_subject">WG Tunnel Desteği</string>
|
<string name="email_subject">WG Tunnel Desteği</string>
|
||||||
<string name="email_chooser">Bir e-posta gönderin…</string>
|
<string name="email_chooser">E-posta gönder…</string>
|
||||||
<string name="go">git</string>
|
<string name="go">git</string>
|
||||||
<string name="docs_description">Belgeleri oku</string>
|
<string name="docs_description">Belgeleri oku</string>
|
||||||
<string name="discord_description">Topluluğa katıl</string>
|
<string name="discord_description">Topluluğa katıl</string>
|
||||||
<string name="email_description">Bana e-posta gönder</string>
|
<string name="email_description">Bana e-posta gönder</string>
|
||||||
<string name="support_help_text">Sorun yaşıyorsanız, iyileştirme fikirleriniz varsa veya sadece etkileşimde bulunmak istiyorsanız, aşağıdaki kaynaklar mevcuttur:</string>
|
<string name="support_help_text">Sorun yaşıyorsanız, iyileştirme fikirleriniz varsa veya sadece iletişime geçmek istiyorsanız, aşağıdaki kaynaklar mevcuttur:</string>
|
||||||
<string name="kernel">Çekirdek</string>
|
<string name="use_kernel">Kernel modülünü kullan</string>
|
||||||
<string name="use_kernel">Çekirdek modülünü kullan</string>
|
<string name="error_ssid_exists">SSID zaten mevcut</string>
|
||||||
<string name="error_ssid_exists">SSID zaten var</string>
|
<string name="error_root_denied">Root kabuğu reddedildi</string>
|
||||||
<string name="error_root_denied">Kök kabuğu reddedildi</string>
|
<string name="error_no_file_explorer">Dosya gezgini yüklü değil</string>
|
||||||
<string name="error_no_file_explorer">Yüklü dosya gezgini yok</string>
|
|
||||||
<string name="error_invalid_code">Geçersiz QR kodu</string>
|
<string name="error_invalid_code">Geçersiz QR kodu</string>
|
||||||
<string name="error_none">Hata yok</string>
|
<string name="error_none">Hata yok</string>
|
||||||
<string name="location_services_missing_message">Uygulama, cihazınızda etkinleştirilen herhangi bir konum hizmeti algılamıyor. Cihaza bağlı olarak bu, güvenilmeyen Wi-Fi özelliğinin Wi-Fi adını okuyamamasına neden olabilir. Yine de devam etmek ister misiniz?</string>
|
<string name="location_services_missing_message">Uygulama, cihazınızda etkinleştirilmiş herhangi bir konum hizmeti algılamıyor. Cihaza bağlı olarak, bu durum güvenilmeyen wifi özelliğinin wifi adını okumasını engelleyebilir. Yine de devam etmek istiyor musunuz?</string>
|
||||||
<string name="auto_tunnel_title">Otomatik Tünel Hizmeti</string>
|
<string name="auto_tunnel_title">Otomatik Tünel Hizmeti</string>
|
||||||
<string name="delete_tunnel">Tüneli sil</string>
|
<string name="delete_tunnel">Tüneli sil</string>
|
||||||
<string name="delete_tunnel_message">Bu tüneli silmek istediğinizden emin misiniz?</string>
|
<string name="delete_tunnel_message">Bu tüneli silmek istediğinizden emin misiniz?</string>
|
||||||
@@ -126,37 +123,58 @@
|
|||||||
<string name="paused">duraklatıldı</string>
|
<string name="paused">duraklatıldı</string>
|
||||||
<string name="active">aktif</string>
|
<string name="active">aktif</string>
|
||||||
<string name="tunneling_apps">Tünellenen uygulamalar</string>
|
<string name="tunneling_apps">Tünellenen uygulamalar</string>
|
||||||
<string name="included">dahil edildi</string>
|
<string name="included">dahil</string>
|
||||||
<string name="excluded">hariç tutuldu</string>
|
<string name="excluded">hariç</string>
|
||||||
<string name="all">tümü</string>
|
<string name="all">tümü</string>
|
||||||
<string name="always_on_disabled">Her zaman açık VPN bir tünel başlatmaya çalıştı, ancak bu özellik ayarlarda devre dışı bırakıldı.</string>
|
<string name="always_on_disabled">Her Zaman Açık VPN bir tüneli başlatmaya çalıştı, ancak bu özellik ayarlarda devre dışı bırakılmış.</string>
|
||||||
<string name="no_email_detected">E-posta uygulaması algılanmadı</string>
|
<string name="no_email_detected">E-posta uygulaması algılanmadı</string>
|
||||||
<string name="no_browser_detected">Tarayıcı algılanmadı</string>
|
<string name="no_browser_detected">Tarayıcı algılanmadı</string>
|
||||||
<string name="logs_saved">İndirilenlere kaydedilen günlükler</string>
|
<string name="logs_saved">Günlükler indirilenler klasörüne kaydedildi</string>
|
||||||
<string name="open_issue">Bir sorun bildir</string>
|
<string name="open_issue">Sorun bildir</string>
|
||||||
<string name="read_logs">Günlükleri oku</string>
|
<string name="read_logs">Günlükleri oku</string>
|
||||||
<string name="auto">(Otomatik)</string>
|
<string name="auto">(otomatik)</string>
|
||||||
<string name="config_parse_error">Yapılandırma kaydedilemedi</string>
|
<string name="config_parse_error">Yapılandırma ayrıştırılamadı</string>
|
||||||
<string name="incorrect_pin">PIN kodu yanlış</string>
|
<string name="incorrect_pin">PIN yanlış</string>
|
||||||
<string name="pin_created">Pin başarıyla oluşturuldu</string>
|
<string name="pin_created">PIN başarıyla oluşturuldu</string>
|
||||||
<string name="enter_pin">PIN kodunuzu girin</string>
|
<string name="enter_pin">PIN\'inizi girin</string>
|
||||||
<string name="create_pin">PIN kodu oluştur</string>
|
<string name="create_pin">PIN oluştur</string>
|
||||||
<string name="enable_app_lock">Uygulama kilidini etkinleştir</string>
|
<string name="enable_app_lock">Uygulama kilidini etkinleştir</string>
|
||||||
<string name="restart_on_ping">Ping başarısız olduğunda yeniden başlat (beta)</string>
|
<string name="restart_on_ping">Ping başarısız olduğunda yeniden başlat (beta)</string>
|
||||||
<string name="mobile_data_tunnel">Mobil veri tüneli olarak ayarla</string>
|
<string name="mobile_data_tunnel">Mobil veri tüneli olarak ayarla</string>
|
||||||
<string name="set_primary_tunnel">Birincil tünel olarak ayarla</string>
|
<string name="set_primary_tunnel">Birincil tünel olarak ayarla</string>
|
||||||
<string name="use_tunnel_on_wifi_name">Wi-Fi adında tünel kullan</string>
|
<string name="use_tunnel_on_wifi_name">Wifi adında tünel kullan</string>
|
||||||
<string name="no_wifi_names_configured">Bu tünel için yapılandırılmış Wi-Fi adı yok</string>
|
<string name="no_wifi_names_configured">Bu tünel için yapılandırılmış wifi adı yok</string>
|
||||||
<string name="general">Genel</string>
|
<string name="general">Genel</string>
|
||||||
<string name="edit_tunnel">Tüneli düzenle</string>
|
<string name="edit_tunnel">Tüneli düzenle</string>
|
||||||
<string name="disabled">Devre dışı</string>
|
<string name="disabled">devre dışı</string>
|
||||||
<string name="auto_on">Otomatik ayarlamayı sürdür</string>
|
<string name="auto_on">Otomatik tüneli devam ettir</string>
|
||||||
<string name="auto_off">Otomatik ayarlamayı duraklat</string>
|
<string name="auto_off">Otomatik tüneli duraklat</string>
|
||||||
<string name="auto_tun_on">Otomatik tüneli sürdür</string>
|
<string name="auto_tun_on">Otomatik tüneli devam ettir</string>
|
||||||
<string name="auto_tun_off">Otomatik tüneli duraklat</string>
|
<string name="auto_tun_off">Otomatik tüneli duraklat</string>
|
||||||
<string name="version">Sürüm</string>
|
<string name="version">Sürüm</string>
|
||||||
<string name="mode">Mod</string>
|
<string name="mode">Mod</string>
|
||||||
<string name="userspace">Kullanıcı alanı</string>
|
<string name="userspace">Kullanıcı alanı</string>
|
||||||
<string name="settings">Ayarlar</string>
|
<string name="settings">Ayarlar</string>
|
||||||
<string name="support">Destek</string>
|
<string name="support">Destek</string>
|
||||||
|
<string name="backend">Arka uç</string>
|
||||||
|
<string name="kernel">Çekirdek</string>
|
||||||
|
<string name="use_amnezia">"Amnezia kullanıcı alanını kullan "</string>
|
||||||
|
<string name="junk_packet_count">Gereksiz paket sayısı</string>
|
||||||
|
<string name="junk_packet_minimum_size">Gereksiz paket minimum boyutu</string>
|
||||||
|
<string name="junk_packet_maximum_size">Gereksiz paket maksimum boyutu</string>
|
||||||
|
<string name="init_packet_junk_size">Başlatma paketi gereksiz boyutu</string>
|
||||||
|
<string name="response_packet_junk_size">Yanıt paketi gereksiz boyutu</string>
|
||||||
|
<string name="init_packet_magic_header">Başlatma paketi sihirli başlığı</string>
|
||||||
|
<string name="response_packet_magic_header">Yanıt paketi sihirli başlığı</string>
|
||||||
|
<string name="transport_packet_magic_header">Taşıma paketi sihirli başlığı</string>
|
||||||
|
<string name="underload_packet_magic_header">Düşük yük paketi sihirli başlığı</string>
|
||||||
|
<string name="telegram_url" translatable="false">https://t.me/wgtunnel</string>
|
||||||
|
<string name="unsure_how">nasıl devam edeceğinizden emin değilseniz</string>
|
||||||
|
<string name="see_the">Bakın:</string>
|
||||||
|
<string name="getting_started_url" translatable="false">https://zaneschepke.com/wgtunnel-docs/getting-started.html</string>
|
||||||
|
<string name="getting_started_guide">başlangıç kılavuzu</string>
|
||||||
|
<string name="amnezia" translatable="false">Amnezia</string>
|
||||||
|
<string name="wireguard" translatable="false">WireGuard</string>
|
||||||
|
<string name="error_file_format">Geçersiz tünel yapılandırma formatı</string>
|
||||||
|
<string name="restart_at_boot">Önyüklemede yeniden başlat</string>
|
||||||
</resources>
|
</resources>
|
||||||
@@ -0,0 +1,169 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<string name="comma_separated_list">список розділений комами</string>
|
||||||
|
<string name="no_tunnels">Тунелі ще не додані!</string>
|
||||||
|
<string name="app_name">WG Tunnel</string>
|
||||||
|
<string name="add_trusted_ssid">Додати ім\'я довіреної мережі Wi-Fi</string>
|
||||||
|
<string name="tunnels">Тунелі</string>
|
||||||
|
<string name="enable_auto_tunnel">Запустити авто-тунелі</string>
|
||||||
|
<string name="disable_auto_tunnel">Зупинити авто-тунелі</string>
|
||||||
|
<string name="okay">ОК</string>
|
||||||
|
<string name="tunnel_on_ethernet">Тунелювати Ethernet</string>
|
||||||
|
<string name="prominent_background_location_title">Фонова передача місцезнаходження</string>
|
||||||
|
<string name="trusted_ssid_value_description">Підтвердити SSID</string>
|
||||||
|
<string name="qr_scan">Сканувати QR</string>
|
||||||
|
<string name="tunnel_name">Ім\'я тунелю</string>
|
||||||
|
<string name="add_tunnel">Додати тунель</string>
|
||||||
|
<string name="include">Включити</string>
|
||||||
|
<string name="tunnel_all">Тунель для всіх додатків</string>
|
||||||
|
<string name="config_changes_saved">Зміни налаштувань збережено.</string>
|
||||||
|
<string name="icon">Іконка</string>
|
||||||
|
<string name="no_thanks">Ні, дякую</string>
|
||||||
|
<string name="default_vpn_off">Основний VPN вимк.</string>
|
||||||
|
<string name="turn_on_tunnel">Дія потребує активного тунелю</string>
|
||||||
|
<string name="rotate_keys">Оновити ключі</string>
|
||||||
|
<string name="private_key">Закритий ключ</string>
|
||||||
|
<string name="base64_key">Ключ в base64</string>
|
||||||
|
<string name="random">(випадково)</string>
|
||||||
|
<string name="optional">(необов\'язково)</string>
|
||||||
|
<string name="optional_no_recommend">(необов\'язково, не рекомендується)</string>
|
||||||
|
<string name="cancel">Скасувати</string>
|
||||||
|
<string name="export_configs_failed">Помилка експорту конфігурації</string>
|
||||||
|
<string name="location_services_required">Необхідно сервіси місцезнаходження</string>
|
||||||
|
<string name="precise_location_required">Необхідно доступ до точного місцезнаходження</string>
|
||||||
|
<string name="exported_configs_message">Експорт конфігурації в Завантаження</string>
|
||||||
|
<string name="email_chooser">Надіслати E-Mail…</string>
|
||||||
|
<string name="error_root_denied">Root доступ заборонено</string>
|
||||||
|
<string name="error_invalid_code">Некоректний QR-код</string>
|
||||||
|
<string name="error_none">Нема помилок</string>
|
||||||
|
<string name="logs_saved">Логи збережено в Завантаженнях</string>
|
||||||
|
<string name="config_parse_error">Помилка аналізу файлу конфігурації</string>
|
||||||
|
<string name="incorrect_pin">Невірний PIN-код</string>
|
||||||
|
<string name="use_tunnel_on_wifi_name">Використовувати тунель в мережі Wi-Fi</string>
|
||||||
|
<string name="disabled">відключено</string>
|
||||||
|
<string name="version">Версія</string>
|
||||||
|
<string name="mode">Режим</string>
|
||||||
|
<string name="transport_packet_magic_header">Заголовок транспортного пакету</string>
|
||||||
|
<string name="getting_started_guide">інструкція щодо початку роботи</string>
|
||||||
|
<string name="error_file_format">некоректний формат конфігурації тунелю</string>
|
||||||
|
<string name="watcher_notification_text_active">Моніторинг стану мережі: активний</string>
|
||||||
|
<string name="vpn_channel_name">Канал сповіщення VPN</string>
|
||||||
|
<string name="vpn_channel_id">Канал VPN</string>
|
||||||
|
<string name="error_file_extension">Файл не є .conf або .zip файлом</string>
|
||||||
|
<string name="turn_off_tunnel">Дія потребує вимкнення тунелю</string>
|
||||||
|
<string name="watcher_notification_text_paused">Моніторинг стану мережі: призупинено</string>
|
||||||
|
<string name="tunnel_start_text">Підключення до тунелю</string>
|
||||||
|
<string name="tunnel_start_title">VPN підключено</string>
|
||||||
|
<string name="notification_permission_required">Потрібен дозвіл на відображення сповіщень.</string>
|
||||||
|
<string name="tunnel_mobile_data">Тунелювати мобільні дані</string>
|
||||||
|
<string name="one_tunnel_required">Для використання даної функції потрібно налаштувати мінімум один тунель</string>
|
||||||
|
<string name="privacy_policy">Переглянути політику конфіденційності</string>
|
||||||
|
<string name="thank_you">Спасибі за використання WG Tunnel!</string>
|
||||||
|
<string name="prominent_background_location_message">Дана функція потребує фоновий доступ до служби місцезнаходження для моніторингу назви мереж Wi-Fi навіть коли додаток закрито. Для отримання додаткової інформації прочитайте політику приватності на екрані Підтримки.</string>
|
||||||
|
<string name="trusted_ssid_empty_description">Введіть SSID</string>
|
||||||
|
<string name="add_tunnels_text">Додати з файлу або архіву</string>
|
||||||
|
<string name="open_file">Відкрити файл</string>
|
||||||
|
<string name="exclude">Виключити</string>
|
||||||
|
<string name="add_from_qr">Додати з QR коду</string>
|
||||||
|
<string name="save_changes">Зберегти</string>
|
||||||
|
<string name="turn_on">Увімкнути</string>
|
||||||
|
<string name="map">Карта</string>
|
||||||
|
<string name="public_key">Публічний ключ</string>
|
||||||
|
<string name="addresses">Адреса</string>
|
||||||
|
<string name="allowed_ips">Дозволені IP</string>
|
||||||
|
<string name="dns_servers">DNS-сервери</string>
|
||||||
|
<string name="mtu">MTU</string>
|
||||||
|
<string name="peer">Пір</string>
|
||||||
|
<string name="endpoint">Кінцева точка</string>
|
||||||
|
<string name="hint_search_packages">Пошук програм</string>
|
||||||
|
<string name="name">Ім\'я</string>
|
||||||
|
<string name="vpn_connection_failed">Помилка з\'єднання</string>
|
||||||
|
<string name="restart">Перезапустити тунель</string>
|
||||||
|
<string name="always_on_vpn_support">Дозволили Always-ON VPN</string>
|
||||||
|
<string name="location_services_not_detected">Сервіси місце знаходження не знайдено</string>
|
||||||
|
<string name="db_name">wg-tunnel-db</string>
|
||||||
|
<string name="scanning_qr">Сканування QR коду</string>
|
||||||
|
<string name="attempt_connection">Спроба з\'єднання...</string>
|
||||||
|
<string name="other">Інше</string>
|
||||||
|
<string name="vpn_starting">Запуск VPN</string>
|
||||||
|
<string name="auto_tunneling">Авто-тунелювання</string>
|
||||||
|
<string name="none">Нема довірених мереж Wi-Fi</string>
|
||||||
|
<string name="vpn_on">VPN увімк.</string>
|
||||||
|
<string name="vpn_off">VPN вимк.</string>
|
||||||
|
<string name="default_vpn_on">Основний VPN увімк.</string>
|
||||||
|
<string name="create_import">Створити з нуля</string>
|
||||||
|
<string name="turn_off_auto">Необхідно вимкнути або призупинити авто-тунелювання</string>
|
||||||
|
<string name="add_peer">Додати peer</string>
|
||||||
|
<string name="done">Готово</string>
|
||||||
|
<string name="interface_">Інтерфейс</string>
|
||||||
|
<string name="copy_public_key">Копіювати відкритий ключ</string>
|
||||||
|
<string name="listen_port">Слухати порт</string>
|
||||||
|
<string name="preshared_key">Pre-shared key</string>
|
||||||
|
<string name="seconds">секунд</string>
|
||||||
|
<string name="persistent_keepalive">Persistent keepalive</string>
|
||||||
|
<string name="error_authorization_failed">Не вдалося авторизуватися</string>
|
||||||
|
<string name="enabled_app_shortcuts">Дозволити ярлики</string>
|
||||||
|
<string name="error_authentication_failed">Помилка автентифікації</string>
|
||||||
|
<string name="export_configs">Експорт конфігурації</string>
|
||||||
|
<string name="background_location_required">Необхідний фоновий доступ до місцезнаходження</string>
|
||||||
|
<string name="unknown_error">Невідома помилка</string>
|
||||||
|
<string name="tunnel_on_wifi">Тунелювати недовірені мережі Wi-Fi</string>
|
||||||
|
<string name="email_subject">Підтримка WG-Tunnel</string>
|
||||||
|
<string name="go">вперед</string>
|
||||||
|
<string name="docs_description">Переглянути документацію</string>
|
||||||
|
<string name="email_description">Відправити email автору</string>
|
||||||
|
<string name="discord_description">Приєднатися до спільноти</string>
|
||||||
|
<string name="support_help_text">Якщо у вас виникли проблеми, є ідеї щодо покращення, чи бажання долучитися, скористайтесь наступними ресурсами:</string>
|
||||||
|
<string name="use_kernel">Використовувати модуль режиму ядра</string>
|
||||||
|
<string name="error_ssid_exists">SSID вже існує</string>
|
||||||
|
<string name="error_no_file_explorer">Не знайдено файловий менеджер</string>
|
||||||
|
<string name="auto_tunnel_title">Сервіс авто-тунелювання</string>
|
||||||
|
<string name="delete_tunnel">Видалити тунель</string>
|
||||||
|
<string name="location_services_missing_message">Додаток не знайшов служб місце знаходження на вашому пристрої. На деяких пристроях це може привести до неможливості визначення назви мережі Wi-Fi і помилок функції недовірених Wi-Fi мереж. Все рівно хочете продовжити?</string>
|
||||||
|
<string name="included">включено</string>
|
||||||
|
<string name="delete_tunnel_message">Ви дійсно хочете видалити цей тунель?</string>
|
||||||
|
<string name="yes">Так</string>
|
||||||
|
<string name="active">активно</string>
|
||||||
|
<string name="resume">Відновити</string>
|
||||||
|
<string name="pause">Призупинити</string>
|
||||||
|
<string name="paused">призупинено</string>
|
||||||
|
<string name="tunneling_apps">Тунельовані додатки</string>
|
||||||
|
<string name="excluded">виключено</string>
|
||||||
|
<string name="always_on_disabled">Функція Always-on VPN спробувала запустити тунель, але функція вимкнена в налаштуваннях.</string>
|
||||||
|
<string name="auto">(авто)</string>
|
||||||
|
<string name="all">всі</string>
|
||||||
|
<string name="no_email_detected">Програми для надсилання email не знайдено</string>
|
||||||
|
<string name="open_issue">Повідомити про проблему</string>
|
||||||
|
<string name="read_logs">Переглянути логи</string>
|
||||||
|
<string name="no_browser_detected">Веб браузер не знайдено</string>
|
||||||
|
<string name="pin_created">PIN-код створено успішно</string>
|
||||||
|
<string name="enter_pin">Введіть PIN-код</string>
|
||||||
|
<string name="create_pin">Створити PINhкод</string>
|
||||||
|
<string name="enable_app_lock">Увімкнути блокування додатку</string>
|
||||||
|
<string name="edit_tunnel">Редагувати тунель</string>
|
||||||
|
<string name="auto_on">Відновити авто-тунель</string>
|
||||||
|
<string name="restart_on_ping">Перезапуск при помилці ping (бета)</string>
|
||||||
|
<string name="mobile_data_tunnel">Встановити як тунель для мобільних даних</string>
|
||||||
|
<string name="set_primary_tunnel">Встановити як основний тунель</string>
|
||||||
|
<string name="no_wifi_names_configured">Імена мереж Wi-Fi не налаштовано для цього тунелю</string>
|
||||||
|
<string name="general">Загальне</string>
|
||||||
|
<string name="auto_tun_off">Призупинити авто-тунель</string>
|
||||||
|
<string name="auto_off">Призупинити авто-тунель</string>
|
||||||
|
<string name="auto_tun_on">Відновити авто-тунель</string>
|
||||||
|
<string name="userspace">Користувача</string>
|
||||||
|
<string name="support">Підтримка</string>
|
||||||
|
<string name="settings">Налаштування</string>
|
||||||
|
<string name="use_amnezia">"Використовувати модуль Amnezia режиму користувача "</string>
|
||||||
|
<string name="backend">Модуль</string>
|
||||||
|
<string name="kernel">Модуль ядра</string>
|
||||||
|
<string name="junk_packet_count">Кількість «сміттєвих» пакетів</string>
|
||||||
|
<string name="junk_packet_minimum_size">Мінімальний розмір «сміттєвого» пакету</string>
|
||||||
|
<string name="junk_packet_maximum_size">Максимальний розмір «сміттєвого» пакету</string>
|
||||||
|
<string name="init_packet_junk_size">Початковий розмір «сміттєвого» пакету</string>
|
||||||
|
<string name="response_packet_junk_size">Розмір відповіді «сміттєвого» пакету</string>
|
||||||
|
<string name="init_packet_magic_header">Заголовок пакету ініціалізації</string>
|
||||||
|
<string name="underload_packet_magic_header">Заголовок пакету під навантаженням</string>
|
||||||
|
<string name="response_packet_magic_header">Заголовок пакету відповіді</string>
|
||||||
|
<string name="unsure_how">, якщо не впевнені що робити далі</string>
|
||||||
|
<string name="see_the">Дивіться</string>
|
||||||
|
</resources>
|
||||||
@@ -58,8 +58,6 @@
|
|||||||
<string name="always_on_vpn_support">Allow Always-On VPN </string>
|
<string name="always_on_vpn_support">Allow Always-On VPN </string>
|
||||||
<string name="location_services_not_detected">Location Services Not Detected</string>
|
<string name="location_services_not_detected">Location Services Not Detected</string>
|
||||||
<string name="hint_search_packages">Search packages</string>
|
<string name="hint_search_packages">Search packages</string>
|
||||||
<string name="clear_icon">Clear Icon</string>
|
|
||||||
<string name="search_icon">Search Icon</string>
|
|
||||||
<string name="attempt_connection">Attempting connection..</string>
|
<string name="attempt_connection">Attempting connection..</string>
|
||||||
<string name="vpn_starting">VPN starting</string>
|
<string name="vpn_starting">VPN starting</string>
|
||||||
<string name="db_name">wg-tunnel-db</string>
|
<string name="db_name">wg-tunnel-db</string>
|
||||||
@@ -178,4 +176,5 @@
|
|||||||
<string name="amnezia" translatable="false">Amnezia</string>
|
<string name="amnezia" translatable="false">Amnezia</string>
|
||||||
<string name="wireguard" translatable="false">WireGuard</string>
|
<string name="wireguard" translatable="false">WireGuard</string>
|
||||||
<string name="error_file_format">Invalid tunnel config format</string>
|
<string name="error_file_format">Invalid tunnel config format</string>
|
||||||
|
<string name="restart_at_boot">Restart on boot</string>
|
||||||
</resources>
|
</resources>
|
||||||
@@ -4,4 +4,12 @@
|
|||||||
<style name="Theme.WireguardAutoTunnel" parent="@style/Theme.AppCompat.NoActionBar">
|
<style name="Theme.WireguardAutoTunnel" parent="@style/Theme.AppCompat.NoActionBar">
|
||||||
<item name="android:windowBackground">@color/black_background</item>
|
<item name="android:windowBackground">@color/black_background</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
<style name="Theme.AppSplashScreen" parent="Theme.SplashScreen">
|
||||||
|
<!--<item name="windowSplashScreenBackground">@color/white</item>-->
|
||||||
|
<!-- icon has to be a circle -->
|
||||||
|
<item name="windowSplashScreenAnimatedIcon">@mipmap/ic_launcher</item>
|
||||||
|
<item name="windowSplashScreenAnimationDuration">500</item>
|
||||||
|
<item name="postSplashScreenTheme">@style/Theme.WireguardAutoTunnel</item>
|
||||||
|
</style>
|
||||||
</resources>
|
</resources>
|
||||||
@@ -5,4 +5,5 @@ plugins {
|
|||||||
alias(libs.plugins.kotlinxSerialization) apply false
|
alias(libs.plugins.kotlinxSerialization) apply false
|
||||||
alias(libs.plugins.ksp) apply false
|
alias(libs.plugins.ksp) apply false
|
||||||
alias(libs.plugins.androidLibrary) apply false
|
alias(libs.plugins.androidLibrary) apply false
|
||||||
|
alias(libs.plugins.compose.compiler) apply false
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,47 +0,0 @@
|
|||||||
import org.gradle.api.invocation.Gradle
|
|
||||||
import java.io.File
|
|
||||||
|
|
||||||
object BuildHelper {
|
|
||||||
private fun getCurrentFlavor(gradle: Gradle): String {
|
|
||||||
val taskRequestsStr = gradle.startParameter.taskRequests.toString()
|
|
||||||
val pattern: java.util.regex.Pattern =
|
|
||||||
if (taskRequestsStr.contains("assemble")) {
|
|
||||||
java.util.regex.Pattern.compile("assemble(\\w+)(Release|Debug)")
|
|
||||||
} else {
|
|
||||||
java.util.regex.Pattern.compile("bundle(\\w+)(Release|Debug)")
|
|
||||||
}
|
|
||||||
|
|
||||||
val matcher = pattern.matcher(taskRequestsStr)
|
|
||||||
val flavor =
|
|
||||||
if (matcher.find()) {
|
|
||||||
matcher.group(1).lowercase()
|
|
||||||
} else {
|
|
||||||
print("NO FLAVOR FOUND")
|
|
||||||
""
|
|
||||||
}
|
|
||||||
return flavor
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getLocalProperty(key: String, file: String = "local.properties"): String? {
|
|
||||||
val properties = java.util.Properties()
|
|
||||||
val localProperties = File(file)
|
|
||||||
if (localProperties.isFile) {
|
|
||||||
java.io.InputStreamReader(java.io.FileInputStream(localProperties), Charsets.UTF_8)
|
|
||||||
.use { reader ->
|
|
||||||
properties.load(reader)
|
|
||||||
}
|
|
||||||
} else return null
|
|
||||||
return properties.getProperty(key)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun isGeneralFlavor(gradle: Gradle): Boolean {
|
|
||||||
return getCurrentFlavor(gradle) == "general"
|
|
||||||
}
|
|
||||||
|
|
||||||
fun isReleaseBuild(gradle: Gradle): Boolean {
|
|
||||||
return (gradle.startParameter.taskNames.size > 0 &&
|
|
||||||
gradle.startParameter.taskNames[0].contains(
|
|
||||||
"Release",
|
|
||||||
))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,13 +1,11 @@
|
|||||||
object Constants {
|
object Constants {
|
||||||
const val VERSION_NAME = "3.4.4"
|
const val VERSION_NAME = "3.4.8"
|
||||||
const val JVM_TARGET = "17"
|
const val JVM_TARGET = "17"
|
||||||
const val VERSION_CODE = 34400
|
const val VERSION_CODE = 34800
|
||||||
const val TARGET_SDK = 34
|
const val TARGET_SDK = 34
|
||||||
const val MIN_SDK = 26
|
const val MIN_SDK = 26
|
||||||
const val APP_ID = "com.zaneschepke.wireguardautotunnel"
|
const val APP_ID = "com.zaneschepke.wireguardautotunnel"
|
||||||
const val APP_NAME = "wgtunnel"
|
const val APP_NAME = "wgtunnel"
|
||||||
const val COMPOSE_COMPILER_EXTENSION_VERSION = "1.5.11"
|
|
||||||
|
|
||||||
|
|
||||||
const val STORE_PASS_VAR = "SIGNING_STORE_PASSWORD"
|
const val STORE_PASS_VAR = "SIGNING_STORE_PASSWORD"
|
||||||
const val KEY_ALIAS_VAR = "SIGNING_KEY_ALIAS"
|
const val KEY_ALIAS_VAR = "SIGNING_KEY_ALIAS"
|
||||||
@@ -15,5 +13,9 @@ object Constants {
|
|||||||
const val KEY_STORE_PATH_VAR = "KEY_STORE_PATH"
|
const val KEY_STORE_PATH_VAR = "KEY_STORE_PATH"
|
||||||
|
|
||||||
const val RELEASE = "release"
|
const val RELEASE = "release"
|
||||||
|
const val NIGHTLY = "nightly"
|
||||||
|
const val DEBUG = "debug"
|
||||||
const val TYPE = "type"
|
const val TYPE = "type"
|
||||||
|
|
||||||
|
const val NIGHTLY_CODE = 42
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,75 @@
|
|||||||
|
import org.gradle.api.Project
|
||||||
|
import org.gradle.api.invocation.Gradle
|
||||||
|
import java.io.File
|
||||||
|
import java.util.Properties
|
||||||
|
|
||||||
|
private fun getCurrentFlavor(gradle: Gradle): String {
|
||||||
|
val taskRequestsStr = gradle.startParameter.taskRequests.toString()
|
||||||
|
val pattern: java.util.regex.Pattern =
|
||||||
|
if (taskRequestsStr.contains("assemble")) {
|
||||||
|
java.util.regex.Pattern.compile("assemble(\\w+)(Release|Debug)")
|
||||||
|
} else {
|
||||||
|
java.util.regex.Pattern.compile("bundle(\\w+)(Release|Debug)")
|
||||||
|
}
|
||||||
|
|
||||||
|
val matcher = pattern.matcher(taskRequestsStr)
|
||||||
|
val flavor =
|
||||||
|
if (matcher.find()) {
|
||||||
|
matcher.group(1).lowercase()
|
||||||
|
} else {
|
||||||
|
print("NO FLAVOR FOUND")
|
||||||
|
""
|
||||||
|
}
|
||||||
|
return flavor
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getLocalProperty(key: String, file: String = "local.properties"): String? {
|
||||||
|
val properties = java.util.Properties()
|
||||||
|
val localProperties = File(file)
|
||||||
|
if (localProperties.isFile) {
|
||||||
|
java.io.InputStreamReader(java.io.FileInputStream(localProperties), Charsets.UTF_8)
|
||||||
|
.use { reader ->
|
||||||
|
properties.load(reader)
|
||||||
|
}
|
||||||
|
} else return null
|
||||||
|
return properties.getProperty(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Project.isGeneralFlavor(gradle: Gradle): Boolean {
|
||||||
|
return getCurrentFlavor(gradle) == "general"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fun Project.getSigningProperties() : Properties {
|
||||||
|
return Properties().apply {
|
||||||
|
// created local file for signing details
|
||||||
|
try {
|
||||||
|
load(file("signing.properties").reader())
|
||||||
|
} catch (_: Exception) {
|
||||||
|
load(file("signing_template.properties").reader())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Project.getStoreFile() : File {
|
||||||
|
return file(
|
||||||
|
System.getenv()
|
||||||
|
.getOrDefault(
|
||||||
|
Constants.KEY_STORE_PATH_VAR,
|
||||||
|
getSigningProperties().getProperty(Constants.KEY_STORE_PATH_VAR),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Project.getSigningProperty(property: String) : String {
|
||||||
|
// try to get secrets from env first for pipeline build, then properties file for local
|
||||||
|
return System.getenv()
|
||||||
|
.getOrDefault(
|
||||||
|
property,
|
||||||
|
getSigningProperties().getProperty(property),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
Funkce
|
||||||
|
|
||||||
|
- Přidání tunelů pomocí .conf souboru, zipu, ručního zadávání nebo QR kódu
|
||||||
|
- Automatické připojení k VPN dle Wi-Fi SSID, ethernetu či mobilních dat
|
||||||
|
- Rozdělené tunelování dle aplikací s vyhledáváním
|
||||||
|
- WireGuard podpora pro kernel a userspace módy
|
||||||
|
- Amnezia podpora pro userspace mód pro ochranu před cenzurou
|
||||||
|
- Always-On VPN podpora
|
||||||
|
- Exportování Amnezia a WireGuard tunelů do zipu
|
||||||
|
- Podpora pro přepínání VPN pomocí rychlých dlaždic
|
||||||
|
- Podpora statických zkratek u hlavního tunelu pro automatickou integraci
|
||||||
|
- Podpora Intent automatizace pro všechny tunely
|
||||||
|
- Automatický restart služby po restartu systému
|
||||||
|
- Opatření pro úsporu baterie
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
Alternativní VPN klientská aplikace pro WireGuard s přidanými funkcemi
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
WG Tunnel
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
Verbesserungen:
|
Verbesserungen:
|
||||||
- Unterstützung für Tunnelung nur bei Verwendung von Mobilen Daten
|
- Unterstützung für Auto-Tunneln nur bei Verwendung von Mobilen Daten
|
||||||
- Verbesserungen der Support Oberfläche
|
- Verbesserungen der Support Oberfläche
|
||||||
- Aktualisierung der Ressourcenlinks
|
- Aktualisierung der Ressourcenlinks
|
||||||
- Verschiedene andere Fehlerbehebungen
|
- Verschiedene andere Fehlerbehebungen
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
Was ist neu:
|
||||||
|
- Offizielle Unterstützung für AmneziaWG
|
||||||
|
- Import/Export von Amnezia Konfigurationen
|
||||||
|
- Auto-Tunnel, um nur einmal pro Netzwerkänderung umzuschalten
|
||||||
|
- Unterstützung für zusätzliche Sprachen
|
||||||
|
- Weitere Fehlerbehebungen und Verbesserungen
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
Was ist neu:
|
||||||
|
- Verbesserung der Benennung von Tunnel-Importen
|
||||||
|
- Fehler des Anfangszustand beim automatischen Tunneln behoben
|
||||||
|
- Verbesserte Fehlerhandhabung
|
||||||
|
- Fehler beim Import von Amnezia zip behoben
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
Was ist neu?
|
||||||
|
- Zusätzliche Sprachunterstützung
|
||||||
|
- Fehler beim automatischen Tunneln von mobilen Daten behoben
|
||||||
|
- AndroidTV-Schaltfläche für schwebende Aktionen behoben
|
||||||
|
- Weitere Optimierungen und Erweiterungen
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
Was ist neu?
|
||||||
|
- Behebt Auto-Tunneling-Fehler
|
||||||
|
- Behebt Android-Backup-Fehler
|
||||||
|
- Versionen erhöhen
|
||||||
@@ -1,13 +1,14 @@
|
|||||||
Funktionen
|
Funktionen
|
||||||
|
|
||||||
- Hinzufügen von Tunneln über .conf Dateien, Zip, Manuelle Eingabe oder QR Codes
|
- Hinzufügen von Tunneln über .conf Dateien, Zip, Manuelle Eingabe oder QR Codes
|
||||||
- Automatische Verbindung zum VPN basierend auf der Wifi-SSID, Ethernet und mobilen Daten
|
- Automatische Verbindung zum VPN basierend auf der WLAN-SSID, Ethernet und mobilen Daten
|
||||||
- Geteilter Tunnel für Anwendungen mit Suche
|
- Geteilter Tunnel für Anwendungen mit Suche
|
||||||
- Unterstützung für Wireguard Userspace- und Kernel-modus
|
- Unterstützung für Wireguard Userspace- und Kernel-modus
|
||||||
|
- Amnezia Unterstützung für Benutzeroberflächen-Modus zur DPI/Zensurschutz
|
||||||
- Always-On VPN Unterstützung
|
- Always-On VPN Unterstützung
|
||||||
- Export von Tunneln ins Zip Format
|
- Export von Amnezia- und WireGuard-Tunnel ins Zip Format
|
||||||
- Quicktiles zum aktivieren/deaktivieren der VPN Verbindung
|
- Quicktiles zum aktivieren/deaktivieren der VPN Verbindung
|
||||||
- Feste Shortcuts für den Haupttunnel für automatische Integration
|
- Feste Shortcuts für den Haupttunnel für automatische Integration
|
||||||
- Intent basierten Automationssupport für alle Tunnel
|
- Absichtlicher Automationssupport für alle Tunnel
|
||||||
- Automatischer Servicestart nach Geräteneustart
|
- Automatischer Servicestart nach Geräteneustart
|
||||||
- Akkuerhaltungsfunktionen
|
- Akkuerhaltungsfunktionen
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
What's new:
|
||||||
|
- Additional language support
|
||||||
|
- Auto-tunneling mobile data bug fix
|
||||||
|
- AndroidTV floating action button fix
|
||||||
|
- Other optimizations and enhancements
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
What's new:
|
||||||
|
- Fixes auto tunneling bugs
|
||||||
|
- Fixes android backup bug
|
||||||
|
- Bump versions
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
What's new:
|
||||||
|
- Fix crashing issues
|
||||||
|
- Improve tile performance
|
||||||
|
- Re-enable pin lock
|
||||||
|
- Make restart on boot a setting
|
||||||
|
- Various performance and bug fixes
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
What's new:
|
||||||
|
- Fixes for AndroidTV UI tunnel control
|
||||||
|
- Fixes portrait lock bug
|
||||||
|
- Fixes pin lock bypass bug
|
||||||
|
- Fixes auto tunnel tile bug
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
Novedades:
|
||||||
|
- Config editar corrección de errores de interfaz de usuario
|
||||||
|
- Añadir GrapheneOS primer lanzamiento AOVPN notificación
|
||||||
|
- Versiones Bump
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
Novedades:
|
||||||
|
- Añadir pantalla de registros
|
||||||
|
- Añadir bloqueo local de aplicaciones
|
||||||
|
- Añadir reiniciar vpn en ping fallido
|
||||||
|
- Varios errores corregidos
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
Novedades:
|
||||||
|
- Selección automática de túnel por nombre de WiFi
|
||||||
|
- Control automático de túneles a través de mosaicos y atajos.
|
||||||
|
- Reinicio automático del túnel manual después de un reinicio del sistema.
|
||||||
|
- Varias correcciones de errores y mejoras de rendimiento
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
Novedades:
|
||||||
|
- Mejora de la fiabilidad del túnel automático
|
||||||
|
- Mejora de la sincronización de azulejos
|
||||||
|
- Añadidos activos AndroidTV
|
||||||
|
- Añadida huella digital al apk
|
||||||
@@ -1,13 +1,14 @@
|
|||||||
Características:
|
Características
|
||||||
|
|
||||||
- Añadir túneles vía .conf file, zip, manualmente, o código QR
|
- Añade túneles a través de un archivo .conf, zip, entrada manual o código QR
|
||||||
- Conexión automática a la VPN basado en el SSID Wi-Fi, ethernet, o datos móviles
|
- Conexión automática a VPN basada en Wi-Fi SSID, ethernet o datos móviles
|
||||||
- División de túnel por aplicacióno con búsqueda
|
- Túneles divididos por aplicación con búsqueda
|
||||||
- Compatibilidad WireGuard para modos kernel y userspace
|
- Soporte de WireGuard para los modos kernel y espacio de usuario
|
||||||
- Compatibilidad con VPN Siempre-Activada
|
- Soporte de Amnezia para el modo de espacio de usuario para protección DPI/censura
|
||||||
- Exportar túmeles como zip
|
- Compatible con VPN siempre activa
|
||||||
- Añadido interruptor VPN en el Centro de Control
|
- Exportación de túneles Amnezia y WireGuard a zip
|
||||||
- Accesos directos estáticos para túnel principal para intergración con apps de automatización
|
- Compatibilidad con Quick Tile para alternar entre VPN
|
||||||
- Intents de apps de automatización para todos los túneles
|
- Soporte de accesos directos estáticos para el túnel principal para la integración de automatización.
|
||||||
- Inicio automático del servicio tras reinicio
|
- Soporte de automatización de intenciones para todos los túneles
|
||||||
- Medidas para ahorro de batería
|
- Reinicio automático del servicio tras reiniciar
|
||||||
|
- Medidas de conservación de la batería
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
Funkcje
|
||||||
|
|
||||||
|
-Dodawanie tuneli za pomocą pliku .conf, zip, ręcznego wprowadzania lub kodu QR
|
||||||
|
-Automatyczne łączenie się z VPN na podstawie SSID Wi-Fi, sieci Ethernet lub danych mobilnych
|
||||||
|
-Dzielenie tuneli w aplikacji z wyszukiwaniem
|
||||||
|
-
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user