mirror of
https://github.com/wgtunnel/android.git
synced 2026-07-03 14:07:49 +02:00
Compare commits
146 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 1952ff1b02 | |||
| 99f6c0bda5 | |||
| 8b828cca55 | |||
| 28b77d33ca | |||
| b0b070f6aa | |||
| 89b1f314b1 | |||
| a223289949 | |||
| 020263eba6 | |||
| d359215b20 | |||
| a6a0bba569 | |||
| f729e924cd | |||
| 3c1d0f893a | |||
| b184331258 | |||
| 36603ab542 | |||
| d0e21247b5 | |||
| 786a8b54ce | |||
| d762fe7283 | |||
| ed9cab3251 | |||
| 8e1e754a87 | |||
| c6eaaa1f37 | |||
| 5077ec9a46 | |||
| e4a808c6ce | |||
| c8b65fb7fa | |||
| 52efdfa288 | |||
| feec7f0ffc | |||
| b63c6a9b73 | |||
| 46975607c4 | |||
| 0c7bcb5453 | |||
| 599bf9c9e0 | |||
| 03345bdf86 | |||
| b07e604003 | |||
| a4f413e79e | |||
| 71ae3864fc | |||
| 008bc476e0 | |||
| 3947b04e21 | |||
| acef2637c8 | |||
| ba8b09cdc9 | |||
| 79e5ba0cb0 | |||
| c8b3af4857 | |||
| 0a3447c63d | |||
| 7f3297db79 | |||
| 165fda0352 | |||
| c4dfb4d591 | |||
| e80e0b7f94 | |||
| aa33aebd2f | |||
| 53b1d03ca8 | |||
| 53f72850e2 | |||
| b8deb7b644 | |||
| d5a3090782 | |||
| 063cbf3ea6 | |||
| b856fc2230 | |||
| 202ac52e25 | |||
| 2b1a8af998 | |||
| 4a45387efd | |||
| fa064ef3a6 | |||
| 3f8894a566 | |||
| b924845835 | |||
| 0f8d7fed97 | |||
| 20ff172055 | |||
| 42671f616f | |||
| ecca99828f | |||
| 6d77ef878d | |||
| 7f40df9d36 | |||
| 9a497f7892 | |||
| dc5a10ebc9 | |||
| d1a4c7f133 | |||
| 93ebf299e6 | |||
| 1d83da9d44 | |||
| d2abaad5b4 | |||
| 8bf88a89bd | |||
| 96cfd04450 | |||
| c5f39ec906 | |||
| 5cd8f9c2f2 | |||
| 2d11712fdc | |||
| 9528850873 | |||
| a0d5647f23 | |||
| 5ff810a6ef | |||
| 3dc314d79a | |||
| 466a5cecbe | |||
| a13d70227f | |||
| 519981d681 | |||
| 0d17bf28e0 | |||
| fc1d4b22a6 | |||
| d2f5be6e19 | |||
| 27a5b6b9f2 | |||
| 2ae048b1de | |||
| a890c83088 | |||
| 3eedbdccba | |||
| 73b3d03a25 | |||
| b74f84abc2 | |||
| bbb056e9d1 | |||
| 0d953a32be | |||
| 3383bb6b45 | |||
| afcb801806 | |||
| 494835a5a4 | |||
| 2f784f563f | |||
| 11d6fd2ed9 | |||
| 0e8138ba6b | |||
| 0eb3719972 | |||
| 196cd34ef7 | |||
| ed42a2f7df | |||
| 9b944bf51e | |||
| b8c792418d | |||
| 6d8a38c65b | |||
| 09c1c68aad | |||
| 36b8353b07 | |||
| f10ed1269e | |||
| e6e35f01d7 | |||
| 35737bdd84 | |||
| 5a6960bffd | |||
| 10cefe2ab0 | |||
| add53834e7 | |||
| f740ec57ea | |||
| 7eadfe2af2 | |||
| 001a4358e1 | |||
| 31abe64513 | |||
| 900aa86c29 | |||
| 264e17f30b | |||
| fbc3ff3276 | |||
| 151cdd4e03 | |||
| f0deb1294a | |||
| 19efb08cb9 | |||
| 9f4b9ca771 | |||
| a60e480be0 | |||
| f28e21ef79 | |||
| b4ca1e63c4 | |||
| e35f4bbb52 | |||
| f0e575da69 | |||
| a6b7d2091a | |||
| 324a1a66c5 | |||
| a05f11739d | |||
| fe519be5cc | |||
| ab0f67c897 | |||
| a5639cd129 | |||
| babbab051f | |||
| 2e984e8b6f | |||
| 044e6da7f5 | |||
| 77aa2c30d7 | |||
| e773238e6b | |||
| 85316bec3f | |||
| 1935653309 | |||
| e3e24b4a06 | |||
| 7af53dcc18 | |||
| 2eb0ab0f19 | |||
| 07857a53c2 | |||
| 25fd31e252 |
@@ -1,4 +1,6 @@
|
|||||||
name: Build
|
name: build
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
on:
|
on:
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
@@ -20,7 +22,7 @@ on:
|
|||||||
default: fdroid
|
default: fdroid
|
||||||
options:
|
options:
|
||||||
- fdroid
|
- fdroid
|
||||||
- full
|
- standalone
|
||||||
secrets:
|
secrets:
|
||||||
SIGNING_KEY_ALIAS:
|
SIGNING_KEY_ALIAS:
|
||||||
required: false
|
required: false
|
||||||
@@ -94,10 +96,7 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
store_path=${{ env.KEY_STORE_LOCATION }}${{ env.KEY_STORE_FILE }}
|
store_path=${{ env.KEY_STORE_LOCATION }}${{ env.KEY_STORE_FILE }}
|
||||||
echo "KEY_STORE_PATH=$store_path" >> $GITHUB_ENV
|
echo "KEY_STORE_PATH=$store_path" >> $GITHUB_ENV
|
||||||
- name: Create service_account.json
|
|
||||||
if: ${{ inputs.build_type != 'debug' }}
|
|
||||||
id: createServiceAccount
|
|
||||||
run: echo '${{ secrets.SERVICE_ACCOUNT_JSON }}' > service_account.json
|
|
||||||
- name: Build APK
|
- name: Build APK
|
||||||
run: |
|
run: |
|
||||||
flavor=${{ inputs.flavor }}
|
flavor=${{ inputs.flavor }}
|
||||||
@@ -123,6 +122,6 @@ jobs:
|
|||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: android_artifacts_${{ inputs.flavor }}
|
name: android_artifacts_${{ inputs.flavor }}
|
||||||
path: app/build/outputs/apk/${{ inputs.flavor }}/release/wgtunnel-${{ inputs.flavor }}-release-*.apk
|
path: app/build/outputs/apk/${{ inputs.flavor }}/${{ inputs.build_type }}/wgtunnel-${{ inputs.flavor }}${{ inputs.flavor == 'fdroid' && '-release' || '' }}-*.apk
|
||||||
retention-days: 1
|
retention-days: 1
|
||||||
if-no-files-found: warn
|
if-no-files-found: warn
|
||||||
@@ -0,0 +1,127 @@
|
|||||||
|
name: nightly
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
packages: write
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
schedule:
|
||||||
|
- cron: "4 3 * * *"
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
check_commits:
|
||||||
|
name: Check for New Commits
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
outputs:
|
||||||
|
has_new_commits: ${{ steps.check.outputs.new_commits }}
|
||||||
|
steps:
|
||||||
|
- name: Checkout Repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
- name: Check for new commits
|
||||||
|
id: check
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
run: |
|
||||||
|
NEW_COMMITS=$(git rev-list --count --after="$(date -Iseconds -d '23 hours ago')" ${{ github.sha }})
|
||||||
|
echo "new_commits=$NEW_COMMITS" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
build-standalone-nightly:
|
||||||
|
uses: ./.github/workflows/build.yml
|
||||||
|
secrets: inherit
|
||||||
|
with:
|
||||||
|
build_type: "nightly"
|
||||||
|
flavor: standalone
|
||||||
|
|
||||||
|
publish:
|
||||||
|
needs:
|
||||||
|
- check_commits
|
||||||
|
- build-standalone-nightly
|
||||||
|
if: ${{ needs.check_commits.outputs.has_new_commits > 0 && inputs.release_type != 'none' }}
|
||||||
|
name: publish-nightly
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Install system dependencies
|
||||||
|
run: |
|
||||||
|
sudo apt update && sudo apt install -y gh apksigner
|
||||||
|
- name: Set latest tag
|
||||||
|
uses: rickstaa/action-create-tag@v1
|
||||||
|
id: tag_creation
|
||||||
|
with:
|
||||||
|
tag: "latest"
|
||||||
|
message: "Automated tag for HEAD commit"
|
||||||
|
force_push_tag: true
|
||||||
|
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
tag_exists_error: false
|
||||||
|
|
||||||
|
- name: Generate Changelog
|
||||||
|
id: changelog
|
||||||
|
uses: requarks/changelog-action@v1
|
||||||
|
with:
|
||||||
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
toTag: "nightly"
|
||||||
|
fromTag: "latest"
|
||||||
|
writeToFile: false
|
||||||
|
|
||||||
|
- name: Make download dir
|
||||||
|
run: mkdir ${{ github.workspace }}/temp
|
||||||
|
|
||||||
|
- name: Download artifacts
|
||||||
|
uses: actions/download-artifact@v4
|
||||||
|
with:
|
||||||
|
pattern: android_artifacts_*
|
||||||
|
path: ${{ github.workspace }}/temp
|
||||||
|
|
||||||
|
- name: Set release notes
|
||||||
|
run: |
|
||||||
|
echo "RELEASE_NOTES=Nightly build for the latest development version of the app." >> $GITHUB_ENV
|
||||||
|
|
||||||
|
- name: Delete previous nightly version
|
||||||
|
uses: ClementTsang/delete-tag-and-release@v0.4.0
|
||||||
|
with:
|
||||||
|
tag_name: "nightly"
|
||||||
|
delete_release: true
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Get checksum
|
||||||
|
id: checksum
|
||||||
|
run: |
|
||||||
|
file_path=$(find ${{ github.workspace }}/temp -type f -iname "*.apk" | head -n 1)
|
||||||
|
if [ -z "$file_path" ]; then
|
||||||
|
echo "No APK file found"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
checksum=$(apksigner verify --print-certs "$file_path" | grep -Po "(?<=SHA-256 digest:) .*" | tr -d "[:blank:]")
|
||||||
|
echo "checksum=$checksum" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
- name: Create nightly release
|
||||||
|
id: create_release
|
||||||
|
uses: softprops/action-gh-release@v2
|
||||||
|
with:
|
||||||
|
body: |
|
||||||
|
${{ env.RELEASE_NOTES }}
|
||||||
|
|
||||||
|
SHA-256 fingerprints for the 4096-bit signing certificate:
|
||||||
|
```sh
|
||||||
|
${{ steps.checksum.outputs.checksum }}
|
||||||
|
```
|
||||||
|
|
||||||
|
To verify fingerprint:
|
||||||
|
```sh
|
||||||
|
apksigner verify --print-certs [path to APK file] | grep SHA-256
|
||||||
|
```
|
||||||
|
|
||||||
|
### Changelog
|
||||||
|
${{ steps.changelog.outputs.changes }}
|
||||||
|
tag_name: nightly
|
||||||
|
name: nightly
|
||||||
|
draft: false
|
||||||
|
prerelease: true
|
||||||
|
make_latest: false
|
||||||
|
files: |
|
||||||
|
${{ github.workspace }}/temp/**/*.apk
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
@@ -1,4 +1,6 @@
|
|||||||
name: on-pr
|
name: on-pr
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
on:
|
on:
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|||||||
@@ -1,8 +1,12 @@
|
|||||||
name: publish
|
name: publish
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
packages: write
|
||||||
|
|
||||||
on:
|
on:
|
||||||
schedule:
|
push:
|
||||||
- cron: "4 3 * * *"
|
tags:
|
||||||
|
- '[0-9]*.[0-9]*.[0-9]*'
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
inputs:
|
inputs:
|
||||||
track:
|
track:
|
||||||
@@ -22,95 +26,60 @@ on:
|
|||||||
options:
|
options:
|
||||||
- none
|
- none
|
||||||
- prerelease
|
- prerelease
|
||||||
- nightly
|
|
||||||
- release
|
- release
|
||||||
default: release
|
default: release
|
||||||
required: true
|
required: true
|
||||||
tag_name:
|
tag_name:
|
||||||
description: "Tag name for release"
|
description: "Tag name for release"
|
||||||
required: false
|
required: false
|
||||||
default: nightly
|
default: 1.1.1
|
||||||
flavor:
|
flavor:
|
||||||
type: choice
|
type: choice
|
||||||
description: "Product flavor"
|
description: "Product flavor"
|
||||||
required: true
|
required: true
|
||||||
default: full
|
default: standalone
|
||||||
options:
|
options:
|
||||||
- fdroid
|
- fdroid
|
||||||
- full
|
- standalone
|
||||||
workflow_call:
|
workflow_call:
|
||||||
inputs:
|
inputs:
|
||||||
flavor:
|
flavor:
|
||||||
type: string
|
type: string
|
||||||
description: "Product flavor"
|
description: "Product flavor"
|
||||||
required: false
|
required: false
|
||||||
default: full
|
default: standalone
|
||||||
|
|
||||||
env:
|
|
||||||
UPLOAD_DIR_ANDROID: android_artifacts
|
|
||||||
|
|
||||||
permissions:
|
|
||||||
contents: write
|
|
||||||
packages: write
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
check_commits:
|
|
||||||
name: Check for New Commits
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
outputs:
|
|
||||||
has_new_commits: ${{ steps.check.outputs.new_commits }}
|
|
||||||
steps:
|
|
||||||
- name: Checkout Repository
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
- name: Check for new commits
|
|
||||||
id: check
|
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ secrets.PAT }}
|
|
||||||
run: |
|
|
||||||
NEW_COMMITS=$(git rev-list --count --after="$(date -Iseconds -d '23 hours ago')" ${{ github.sha }})
|
|
||||||
echo "new_commits=$NEW_COMMITS" >> $GITHUB_OUTPUT
|
|
||||||
|
|
||||||
build-fdroid:
|
build-fdroid:
|
||||||
if: ${{ inputs.release_type == 'release' || inputs.flavor == 'fdroid' }}
|
if: ${{ github.event_name == 'push' || inputs.release_type == 'release' || inputs.flavor == 'fdroid' }}
|
||||||
uses: ./.github/workflows/build.yml
|
uses: ./.github/workflows/build.yml
|
||||||
secrets: inherit
|
secrets: inherit
|
||||||
with:
|
with:
|
||||||
build_type: ${{ inputs.release_type == '' && 'nightly' || inputs.release_type }}
|
build_type: ${{ github.event_name == 'push' && 'release' || inputs.release_type }}
|
||||||
flavor: fdroid
|
flavor: fdroid
|
||||||
|
|
||||||
build-full:
|
build-standalone:
|
||||||
if: ${{ inputs.release_type == 'release' || inputs.release_type == 'nightly' || inputs.release_type == 'prerelease' || inputs.flavor == 'full' }}
|
if: ${{ github.event_name == 'push' || inputs.release_type == 'release' || inputs.release_type == 'prerelease' || inputs.flavor == 'standalone' }}
|
||||||
uses: ./.github/workflows/build.yml
|
uses: ./.github/workflows/build.yml
|
||||||
secrets: inherit
|
secrets: inherit
|
||||||
with:
|
with:
|
||||||
build_type: ${{ inputs.release_type == '' && 'nightly' || inputs.release_type }}
|
build_type: ${{ github.event_name == 'push' && 'release' || inputs.release_type }}
|
||||||
flavor: full
|
flavor: standalone
|
||||||
|
|
||||||
publish:
|
publish:
|
||||||
needs:
|
needs:
|
||||||
- check_commits
|
- build-standalone
|
||||||
- build-full
|
|
||||||
if: ${{ needs.check_commits.outputs.has_new_commits > 0 && inputs.release_type != 'none' }}
|
|
||||||
name: publish-github
|
name: publish-github
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
ref: ${{ github.event_name == 'push' && github.ref || 'main' }}
|
||||||
ref: main
|
|
||||||
- name: Install system dependencies
|
- name: Install system dependencies
|
||||||
run: |
|
run: |
|
||||||
sudo apt update && sudo apt install -y gh apksigner
|
sudo apt update && sudo apt install -y gh apksigner
|
||||||
- name: Set TAG_NAME
|
|
||||||
run: |
|
|
||||||
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
|
|
||||||
echo "TAG_NAME=${{ github.event.inputs.tag_name }}" >> $GITHUB_ENV
|
|
||||||
elif [ "${{ github.event_name }}" = "schedule" ]; then
|
|
||||||
echo "TAG_NAME=nightly" >> $GITHUB_ENV
|
|
||||||
echo "RELEASE_TYPE=nightly" >> $GITHUB_ENV
|
|
||||||
fi
|
|
||||||
- name: Set latest tag
|
- name: Set latest tag
|
||||||
uses: rickstaa/action-create-tag@v1
|
uses: rickstaa/action-create-tag@v1
|
||||||
id: tag_creation
|
id: tag_creation
|
||||||
@@ -126,24 +95,28 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
token: ${{ secrets.GITHUB_TOKEN }}
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
latest: true
|
latest: true
|
||||||
|
|
||||||
- name: Generate Changelog
|
- name: Generate Changelog
|
||||||
id: changelog
|
id: changelog
|
||||||
uses: requarks/changelog-action@v1
|
uses: requarks/changelog-action@v1
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.GITHUB_TOKEN }}
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
toTag: ${{ github.event_name == 'schedule' && 'nightly' || steps.latest_release.outputs.tag_name }}
|
toTag: ${{ steps.latest_release.outputs.tag_name }}
|
||||||
fromTag: "latest"
|
fromTag: "latest"
|
||||||
writeToFile: false
|
writeToFile: false
|
||||||
|
|
||||||
- name: Make download dir
|
- name: Make download dir
|
||||||
run: mkdir ${{ github.workspace }}/temp
|
run: mkdir ${{ github.workspace }}/temp
|
||||||
|
|
||||||
- name: Download artifacts
|
- name: Download artifacts
|
||||||
uses: actions/download-artifact@v4
|
uses: actions/download-artifact@v4
|
||||||
with:
|
with:
|
||||||
pattern: android_artifacts_*
|
pattern: android_artifacts_*
|
||||||
path: ${{ github.workspace }}/temp
|
path: ${{ github.workspace }}/temp
|
||||||
merge-multiple: true
|
merge-multiple: true
|
||||||
|
|
||||||
- name: Set version release notes
|
- name: Set version release notes
|
||||||
if: ${{ inputs.release_type == 'release' }}
|
if: ${{ github.event_name == 'push' || inputs.release_type == 'release' }}
|
||||||
run: |
|
run: |
|
||||||
VERSION_NAME=$(grep "const val VERSION_NAME" buildSrc/src/main/kotlin/Constants.kt | awk -F'"' '{print $2}')
|
VERSION_NAME=$(grep "const val VERSION_NAME" buildSrc/src/main/kotlin/Constants.kt | awk -F'"' '{print $2}')
|
||||||
RELEASE_NOTES="$(cat ${{ github.workspace }}/fastlane/metadata/android/en-US/changelogs/${VERSION_NAME}.txt || echo "No changelog found for ${VERSION_NAME}")"
|
RELEASE_NOTES="$(cat ${{ github.workspace }}/fastlane/metadata/android/en-US/changelogs/${VERSION_NAME}.txt || echo "No changelog found for ${VERSION_NAME}")"
|
||||||
@@ -151,36 +124,22 @@ jobs:
|
|||||||
echo "$RELEASE_NOTES" >> $GITHUB_ENV
|
echo "$RELEASE_NOTES" >> $GITHUB_ENV
|
||||||
echo "EOF" >> $GITHUB_ENV
|
echo "EOF" >> $GITHUB_ENV
|
||||||
|
|
||||||
- name: On nightly release notes
|
|
||||||
if: ${{ contains(env.TAG_NAME, 'nightly') }}
|
|
||||||
run: |
|
|
||||||
echo "RELEASE_NOTES=Nightly build for the latest development version of the app." >> $GITHUB_ENV
|
|
||||||
|
|
||||||
- name: On prerelease release notes
|
- name: On prerelease release notes
|
||||||
if: ${{ inputs.release_type == 'prerelease' }}
|
if: ${{ github.event_name != 'push' && inputs.release_type == 'prerelease' }}
|
||||||
run: |
|
run: |
|
||||||
echo "RELEASE_NOTES=Testing version of app for specific feature." >> $GITHUB_ENV
|
echo "RELEASE_NOTES=Testing version of app for specific feature." >> $GITHUB_ENV
|
||||||
|
|
||||||
- name: Delete previous release
|
- name: Get checksum
|
||||||
if: ${{ contains(env.TAG_NAME, 'nightly') || inputs.release_type == 'prerelease' }}
|
|
||||||
uses: ClementTsang/delete-tag-and-release@v0.4.0
|
|
||||||
with:
|
|
||||||
tag_name: ${{ env.TAG_NAME }}
|
|
||||||
delete_release: true
|
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
|
|
||||||
- name: Get checksums
|
|
||||||
id: checksum
|
id: checksum
|
||||||
run: |
|
run: |
|
||||||
checksums=""
|
file_path=$(find ${{ github.workspace }}/temp -type f -iname "*.apk" | head -n 1)
|
||||||
for file_path in $(find ${{ github.workspace }}/temp -type f -iname "*.apk"); do
|
if [ -z "$file_path" ]; then
|
||||||
checksum=$(apksigner verify -print-certs $file_path | grep -Po "(?<=SHA-256 digest:) .*" | tr -d "[:blank:]")
|
echo "No APK file found"
|
||||||
checksums="$checksums\n$file_path: $checksum"
|
exit 1
|
||||||
done
|
fi
|
||||||
echo "checksum<<EOF" >> $GITHUB_OUTPUT
|
checksum=$(apksigner verify --print-certs "$file_path" | grep -Po "(?<=SHA-256 digest:) .*" | tr -d "[:blank:]")
|
||||||
echo -e "$checksums" >> $GITHUB_OUTPUT
|
echo "checksum=$checksum" >> $GITHUB_OUTPUT
|
||||||
echo "EOF" >> $GITHUB_OUTPUT
|
|
||||||
- name: Create Release
|
- name: Create Release
|
||||||
id: create_release
|
id: create_release
|
||||||
uses: softprops/action-gh-release@v2
|
uses: softprops/action-gh-release@v2
|
||||||
@@ -200,13 +159,13 @@ jobs:
|
|||||||
|
|
||||||
### Changelog
|
### Changelog
|
||||||
${{ steps.changelog.outputs.changes }}
|
${{ steps.changelog.outputs.changes }}
|
||||||
tag_name: ${{ env.TAG_NAME }}
|
tag_name: ${{ github.event_name == 'push' && github.ref_name || github.event.inputs.tag_name }}
|
||||||
name: ${{ env.TAG_NAME }}
|
name: ${{ github.event_name == 'push' && github.ref_name || github.event.inputs.tag_name }}
|
||||||
draft: false
|
draft: false
|
||||||
prerelease: ${{ inputs.release_type == 'prerelease' || inputs.release_type == '' || inputs.release_type == 'nightly' }}
|
prerelease: ${{ github.event_name != 'push' && inputs.release_type == 'prerelease' }}
|
||||||
make_latest: ${{ inputs.release_type == 'release' }}
|
make_latest: ${{ github.event_name == 'push' || inputs.release_type == 'release' }}
|
||||||
files: |
|
files: |
|
||||||
${{ github.workspace }}/temp/*
|
${{ github.workspace }}/temp/**/*.apk
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
@@ -214,7 +173,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
needs:
|
needs:
|
||||||
- build-fdroid
|
- build-fdroid
|
||||||
if: inputs.release_type == 'release'
|
if: ${{ github.event_name == 'push' || inputs.release_type == 'release' }}
|
||||||
steps:
|
steps:
|
||||||
- name: Dispatch update for fdroid repo
|
- name: Dispatch update for fdroid repo
|
||||||
uses: peter-evans/repository-dispatch@v3
|
uses: peter-evans/repository-dispatch@v3
|
||||||
@@ -224,7 +183,7 @@ jobs:
|
|||||||
event-type: fdroid-update
|
event-type: fdroid-update
|
||||||
|
|
||||||
publish-play:
|
publish-play:
|
||||||
if: ${{ inputs.track != 'none' && inputs.track != '' }}
|
if: ${{ github.event_name == 'push' || inputs.track != 'none' }}
|
||||||
name: Publish to Google Play
|
name: Publish to Google Play
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
@@ -274,4 +233,6 @@ jobs:
|
|||||||
bundler-cache: true
|
bundler-cache: true
|
||||||
|
|
||||||
- name: Distribute app to Prod track 🚀
|
- name: Distribute app to Prod track 🚀
|
||||||
run: (cd ${{ github.workspace }} && bundle install && bundle exec fastlane ${{ inputs.track }})
|
run: |
|
||||||
|
track=${{ github.event_name == 'push' && 'production' || inputs.track }}
|
||||||
|
(cd ${{ github.workspace }} && bundle install && bundle exec fastlane $track)
|
||||||
+19
-5
@@ -103,13 +103,16 @@ android {
|
|||||||
dimension = "type"
|
dimension = "type"
|
||||||
buildConfigField("String", "FLAVOR", "\"google\"")
|
buildConfigField("String", "FLAVOR", "\"google\"")
|
||||||
}
|
}
|
||||||
create("full") { dimension = "type" }
|
create("standalone") {
|
||||||
|
dimension = "type"
|
||||||
|
buildConfigField("String", "FLAVOR", "\"standalone\"")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
compileOptions {
|
compileOptions {
|
||||||
|
isCoreLibraryDesugaringEnabled = true
|
||||||
sourceCompatibility = JavaVersion.VERSION_17
|
sourceCompatibility = JavaVersion.VERSION_17
|
||||||
targetCompatibility = JavaVersion.VERSION_17
|
targetCompatibility = JavaVersion.VERSION_17
|
||||||
isCoreLibraryDesugaringEnabled = true
|
|
||||||
}
|
}
|
||||||
kotlinOptions { jvmTarget = Constants.JVM_TARGET }
|
kotlinOptions { jvmTarget = Constants.JVM_TARGET }
|
||||||
buildFeatures {
|
buildFeatures {
|
||||||
@@ -120,8 +123,7 @@ android {
|
|||||||
|
|
||||||
licensee {
|
licensee {
|
||||||
Constants.allowedLicenses.forEach { allow(it) }
|
Constants.allowedLicenses.forEach { allow(it) }
|
||||||
allowUrl(Constants.XZING_LICENSE_URL)
|
Constants.allowedLicenseUrls.forEach { allowUrl(it) }
|
||||||
allowUrl("https://rafaellins.mit-license.org/2021/")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
applicationVariants.all {
|
applicationVariants.all {
|
||||||
@@ -195,6 +197,7 @@ dependencies {
|
|||||||
implementation(libs.kotlinx.serialization.json)
|
implementation(libs.kotlinx.serialization.json)
|
||||||
|
|
||||||
implementation(libs.zxing.android.embedded)
|
implementation(libs.zxing.android.embedded)
|
||||||
|
|
||||||
implementation(libs.material.icons.extended)
|
implementation(libs.material.icons.extended)
|
||||||
|
|
||||||
implementation(libs.androidx.biometric.ktx)
|
implementation(libs.androidx.biometric.ktx)
|
||||||
@@ -207,7 +210,7 @@ dependencies {
|
|||||||
implementation(libs.androidx.work.runtime)
|
implementation(libs.androidx.work.runtime)
|
||||||
implementation(libs.androidx.hilt.work)
|
implementation(libs.androidx.hilt.work)
|
||||||
|
|
||||||
implementation(libs.qrcode.kotlin)
|
implementation(libs.qrose)
|
||||||
implementation(libs.semver4j)
|
implementation(libs.semver4j)
|
||||||
|
|
||||||
implementation(libs.ktor.client.core)
|
implementation(libs.ktor.client.core)
|
||||||
@@ -216,6 +219,10 @@ dependencies {
|
|||||||
implementation(libs.ktor.client.content.negotiation)
|
implementation(libs.ktor.client.content.negotiation)
|
||||||
implementation(libs.ktor.serialization.kotlinx.json)
|
implementation(libs.ktor.serialization.kotlinx.json)
|
||||||
implementation(libs.slf4j.android)
|
implementation(libs.slf4j.android)
|
||||||
|
|
||||||
|
// shizuku
|
||||||
|
implementation(libs.shizuku.api)
|
||||||
|
implementation(libs.shizuku.provider)
|
||||||
}
|
}
|
||||||
|
|
||||||
tasks.register<Copy>("copyLicenseeJsonToAssets") {
|
tasks.register<Copy>("copyLicenseeJsonToAssets") {
|
||||||
@@ -228,3 +235,10 @@ tasks.register<Copy>("copyLicenseeJsonToAssets") {
|
|||||||
}
|
}
|
||||||
|
|
||||||
tasks.named("preBuild") { dependsOn("copyLicenseeJsonToAssets") }
|
tasks.named("preBuild") { dependsOn("copyLicenseeJsonToAssets") }
|
||||||
|
|
||||||
|
// https://gist.github.com/obfusk/61046e09cee352ae6dd109911534b12e#fix-proposed-by-linsui-disable-baseline-profiles
|
||||||
|
tasks.whenTaskAdded {
|
||||||
|
if (name.contains("ArtProfile")) {
|
||||||
|
enabled = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -155,9 +155,7 @@
|
|||||||
"columnNames": [
|
"columnNames": [
|
||||||
"id"
|
"id"
|
||||||
]
|
]
|
||||||
},
|
}
|
||||||
"indices": [],
|
|
||||||
"foreignKeys": []
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"tableName": "TunnelConfig",
|
"tableName": "TunnelConfig",
|
||||||
@@ -227,21 +225,18 @@
|
|||||||
"fieldPath": "pingInterval",
|
"fieldPath": "pingInterval",
|
||||||
"columnName": "ping_interval",
|
"columnName": "ping_interval",
|
||||||
"affinity": "INTEGER",
|
"affinity": "INTEGER",
|
||||||
"notNull": false,
|
|
||||||
"defaultValue": "null"
|
"defaultValue": "null"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldPath": "pingCooldown",
|
"fieldPath": "pingCooldown",
|
||||||
"columnName": "ping_cooldown",
|
"columnName": "ping_cooldown",
|
||||||
"affinity": "INTEGER",
|
"affinity": "INTEGER",
|
||||||
"notNull": false,
|
|
||||||
"defaultValue": "null"
|
"defaultValue": "null"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldPath": "pingIp",
|
"fieldPath": "pingIp",
|
||||||
"columnName": "ping_ip",
|
"columnName": "ping_ip",
|
||||||
"affinity": "TEXT",
|
"affinity": "TEXT",
|
||||||
"notNull": false,
|
|
||||||
"defaultValue": "null"
|
"defaultValue": "null"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -275,11 +270,9 @@
|
|||||||
"orders": [],
|
"orders": [],
|
||||||
"createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_TunnelConfig_name` ON `${TABLE_NAME}` (`name`)"
|
"createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_TunnelConfig_name` ON `${TABLE_NAME}` (`name`)"
|
||||||
}
|
}
|
||||||
],
|
]
|
||||||
"foreignKeys": []
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"views": [],
|
|
||||||
"setupQueries": [
|
"setupQueries": [
|
||||||
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
|
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
|
||||||
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'ae51793c4d09ea3194ecd26f0606f35c')"
|
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'ae51793c4d09ea3194ecd26f0606f35c')"
|
||||||
|
|||||||
@@ -0,0 +1,295 @@
|
|||||||
|
{
|
||||||
|
"formatVersion": 1,
|
||||||
|
"database": {
|
||||||
|
"version": 17,
|
||||||
|
"identityHash": "380d82359c99933cc9ce783347c4ec31",
|
||||||
|
"entities": [
|
||||||
|
{
|
||||||
|
"tableName": "Settings",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_tunnel_enabled` INTEGER NOT NULL, `is_tunnel_on_mobile_data_enabled` INTEGER NOT NULL, `trusted_network_ssids` TEXT NOT NULL, `is_always_on_vpn_enabled` INTEGER NOT NULL, `is_tunnel_on_ethernet_enabled` INTEGER NOT NULL, `is_shortcuts_enabled` INTEGER NOT NULL DEFAULT false, `is_tunnel_on_wifi_enabled` INTEGER NOT NULL DEFAULT false, `is_kernel_enabled` INTEGER NOT NULL DEFAULT false, `is_restore_on_boot_enabled` INTEGER NOT NULL DEFAULT false, `is_multi_tunnel_enabled` INTEGER NOT NULL DEFAULT false, `is_ping_enabled` INTEGER NOT NULL DEFAULT false, `is_amnezia_enabled` INTEGER NOT NULL DEFAULT false, `is_wildcards_enabled` INTEGER NOT NULL DEFAULT false, `is_stop_on_no_internet_enabled` INTEGER NOT NULL DEFAULT false, `is_vpn_kill_switch_enabled` INTEGER NOT NULL DEFAULT false, `is_kernel_kill_switch_enabled` INTEGER NOT NULL DEFAULT false, `is_lan_on_kill_switch_enabled` INTEGER NOT NULL DEFAULT false, `debounce_delay_seconds` INTEGER NOT NULL DEFAULT 3, `is_disable_kill_switch_on_trusted_enabled` INTEGER NOT NULL DEFAULT false, `is_tunnel_on_unsecure_enabled` INTEGER NOT NULL DEFAULT false, `split_tunnel_apps` TEXT NOT NULL DEFAULT '', `wifi_detection_method` INTEGER NOT NULL DEFAULT 0)",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "id",
|
||||||
|
"columnName": "id",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "isAutoTunnelEnabled",
|
||||||
|
"columnName": "is_tunnel_enabled",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "isTunnelOnMobileDataEnabled",
|
||||||
|
"columnName": "is_tunnel_on_mobile_data_enabled",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "trustedNetworkSSIDs",
|
||||||
|
"columnName": "trusted_network_ssids",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "isAlwaysOnVpnEnabled",
|
||||||
|
"columnName": "is_always_on_vpn_enabled",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "isTunnelOnEthernetEnabled",
|
||||||
|
"columnName": "is_tunnel_on_ethernet_enabled",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "isShortcutsEnabled",
|
||||||
|
"columnName": "is_shortcuts_enabled",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "false"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "isTunnelOnWifiEnabled",
|
||||||
|
"columnName": "is_tunnel_on_wifi_enabled",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "false"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "isKernelEnabled",
|
||||||
|
"columnName": "is_kernel_enabled",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "false"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "isRestoreOnBootEnabled",
|
||||||
|
"columnName": "is_restore_on_boot_enabled",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "false"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "isMultiTunnelEnabled",
|
||||||
|
"columnName": "is_multi_tunnel_enabled",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "false"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "isPingEnabled",
|
||||||
|
"columnName": "is_ping_enabled",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "false"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "isAmneziaEnabled",
|
||||||
|
"columnName": "is_amnezia_enabled",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "false"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "isWildcardsEnabled",
|
||||||
|
"columnName": "is_wildcards_enabled",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "false"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "isStopOnNoInternetEnabled",
|
||||||
|
"columnName": "is_stop_on_no_internet_enabled",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "false"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "isVpnKillSwitchEnabled",
|
||||||
|
"columnName": "is_vpn_kill_switch_enabled",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "false"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "isKernelKillSwitchEnabled",
|
||||||
|
"columnName": "is_kernel_kill_switch_enabled",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "false"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "isLanOnKillSwitchEnabled",
|
||||||
|
"columnName": "is_lan_on_kill_switch_enabled",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "false"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "debounceDelaySeconds",
|
||||||
|
"columnName": "debounce_delay_seconds",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "3"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "isDisableKillSwitchOnTrustedEnabled",
|
||||||
|
"columnName": "is_disable_kill_switch_on_trusted_enabled",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "false"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "isTunnelOnUnsecureEnabled",
|
||||||
|
"columnName": "is_tunnel_on_unsecure_enabled",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "false"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "splitTunnelApps",
|
||||||
|
"columnName": "split_tunnel_apps",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "''"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "wifiDetectionMethod",
|
||||||
|
"columnName": "wifi_detection_method",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": true,
|
||||||
|
"columnNames": [
|
||||||
|
"id"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "TunnelConfig",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NOT NULL, `wg_quick` TEXT NOT NULL, `tunnel_networks` TEXT NOT NULL DEFAULT '', `is_mobile_data_tunnel` INTEGER NOT NULL DEFAULT false, `is_primary_tunnel` INTEGER NOT NULL DEFAULT false, `am_quick` TEXT NOT NULL DEFAULT '', `is_Active` INTEGER NOT NULL DEFAULT false, `is_ping_enabled` INTEGER NOT NULL DEFAULT false, `ping_interval` INTEGER DEFAULT null, `ping_cooldown` INTEGER DEFAULT null, `ping_ip` TEXT DEFAULT null, `is_ethernet_tunnel` INTEGER NOT NULL DEFAULT false, `is_ipv4_preferred` INTEGER NOT NULL DEFAULT true)",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "id",
|
||||||
|
"columnName": "id",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "name",
|
||||||
|
"columnName": "name",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "wgQuick",
|
||||||
|
"columnName": "wg_quick",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "tunnelNetworks",
|
||||||
|
"columnName": "tunnel_networks",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "''"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "isMobileDataTunnel",
|
||||||
|
"columnName": "is_mobile_data_tunnel",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "false"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "isPrimaryTunnel",
|
||||||
|
"columnName": "is_primary_tunnel",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "false"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "amQuick",
|
||||||
|
"columnName": "am_quick",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "''"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "isActive",
|
||||||
|
"columnName": "is_Active",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "false"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "isPingEnabled",
|
||||||
|
"columnName": "is_ping_enabled",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "false"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "pingInterval",
|
||||||
|
"columnName": "ping_interval",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"defaultValue": "null"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "pingCooldown",
|
||||||
|
"columnName": "ping_cooldown",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"defaultValue": "null"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "pingIp",
|
||||||
|
"columnName": "ping_ip",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"defaultValue": "null"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "isEthernetTunnel",
|
||||||
|
"columnName": "is_ethernet_tunnel",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "false"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "isIpv4Preferred",
|
||||||
|
"columnName": "is_ipv4_preferred",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "true"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": true,
|
||||||
|
"columnNames": [
|
||||||
|
"id"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"indices": [
|
||||||
|
{
|
||||||
|
"name": "index_TunnelConfig_name",
|
||||||
|
"unique": true,
|
||||||
|
"columnNames": [
|
||||||
|
"name"
|
||||||
|
],
|
||||||
|
"orders": [],
|
||||||
|
"createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_TunnelConfig_name` ON `${TABLE_NAME}` (`name`)"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"setupQueries": [
|
||||||
|
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
|
||||||
|
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '380d82359c99933cc9ce783347c4ec31')"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -62,6 +62,10 @@
|
|||||||
android:supportsRtl="true"
|
android:supportsRtl="true"
|
||||||
android:theme="@style/Theme.App.Start"
|
android:theme="@style/Theme.App.Start"
|
||||||
tools:targetApi="tiramisu">
|
tools:targetApi="tiramisu">
|
||||||
|
<activity
|
||||||
|
android:name="com.journeyapps.barcodescanner.CaptureActivity"
|
||||||
|
android:screenOrientation="portrait"
|
||||||
|
tools:replace="screenOrientation" />
|
||||||
<activity
|
<activity
|
||||||
android:name=".MainActivity"
|
android:name=".MainActivity"
|
||||||
android:exported="true"
|
android:exported="true"
|
||||||
@@ -78,10 +82,6 @@
|
|||||||
<action android:name="android.service.quicksettings.action.QS_TILE_PREFERENCES" />
|
<action android:name="android.service.quicksettings.action.QS_TILE_PREFERENCES" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
<activity
|
|
||||||
android:name="com.journeyapps.barcodescanner.CaptureActivity"
|
|
||||||
android:screenOrientation="portrait"
|
|
||||||
tools:replace="screenOrientation" />
|
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".core.shortcut.ShortcutsActivity"
|
android:name=".core.shortcut.ShortcutsActivity"
|
||||||
@@ -168,7 +168,6 @@
|
|||||||
android:enabled="true"
|
android:enabled="true"
|
||||||
android:exported="false">
|
android:exported="false">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.USER_PRESENT" />
|
|
||||||
<action android:name="android.intent.action.BOOT_COMPLETED" />
|
<action android:name="android.intent.action.BOOT_COMPLETED" />
|
||||||
<action android:name="android.intent.action.QUICKBOOT_POWERON" />
|
<action android:name="android.intent.action.QUICKBOOT_POWERON" />
|
||||||
<action android:name="com.htc.intent.action.QUICKBOOT_POWERON" />
|
<action android:name="com.htc.intent.action.QUICKBOOT_POWERON" />
|
||||||
|
|||||||
@@ -22,7 +22,6 @@ import androidx.compose.animation.slideOutVertically
|
|||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.gestures.detectTapGestures
|
import androidx.compose.foundation.gestures.detectTapGestures
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
import androidx.compose.material.icons.rounded.Settings
|
|
||||||
import androidx.compose.material3.*
|
import androidx.compose.material3.*
|
||||||
import androidx.compose.runtime.*
|
import androidx.compose.runtime.*
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
@@ -50,11 +49,11 @@ import com.zaneschepke.wireguardautotunnel.ui.navigation.components.DynamicTopAp
|
|||||||
import com.zaneschepke.wireguardautotunnel.ui.navigation.components.currentNavBackStackEntryAsNavBarState
|
import com.zaneschepke.wireguardautotunnel.ui.navigation.components.currentNavBackStackEntryAsNavBarState
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.screens.autotunnel.AutoTunnelScreen
|
import com.zaneschepke.wireguardautotunnel.ui.screens.autotunnel.AutoTunnelScreen
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.screens.autotunnel.advanced.AutoTunnelAdvancedScreen
|
import com.zaneschepke.wireguardautotunnel.ui.screens.autotunnel.advanced.AutoTunnelAdvancedScreen
|
||||||
|
import com.zaneschepke.wireguardautotunnel.ui.screens.autotunnel.detection.WifiDetectionMethodScreen
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.screens.autotunnel.disclosure.LocationDisclosureScreen
|
import com.zaneschepke.wireguardautotunnel.ui.screens.autotunnel.disclosure.LocationDisclosureScreen
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.screens.main.MainScreen
|
import com.zaneschepke.wireguardautotunnel.ui.screens.main.MainScreen
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.screens.main.autotunnel.TunnelAutoTunnelScreen
|
import com.zaneschepke.wireguardautotunnel.ui.screens.main.autotunnel.TunnelAutoTunnelScreen
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.screens.main.config.ConfigScreen
|
import com.zaneschepke.wireguardautotunnel.ui.screens.main.config.ConfigScreen
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.screens.main.scanner.ScannerScreen
|
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.screens.main.splittunnel.SplitTunnelScreen
|
import com.zaneschepke.wireguardautotunnel.ui.screens.main.splittunnel.SplitTunnelScreen
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.screens.main.tunneloptions.TunnelOptionsScreen
|
import com.zaneschepke.wireguardautotunnel.ui.screens.main.tunneloptions.TunnelOptionsScreen
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.screens.pin.PinLockScreen
|
import com.zaneschepke.wireguardautotunnel.ui.screens.pin.PinLockScreen
|
||||||
@@ -88,6 +87,8 @@ class MainActivity : AppCompatActivity() {
|
|||||||
|
|
||||||
private var lastLocationPermissionState: Boolean? = null
|
private var lastLocationPermissionState: Boolean? = null
|
||||||
|
|
||||||
|
val REQUEST_CODE = 123
|
||||||
|
|
||||||
@SuppressLint("BatteryLife")
|
@SuppressLint("BatteryLife")
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
enableEdgeToEdge(
|
enableEdgeToEdge(
|
||||||
@@ -134,6 +135,7 @@ class MainActivity : AppCompatActivity() {
|
|||||||
vpnPermissionDenied = true
|
vpnPermissionDenied = true
|
||||||
} else {
|
} else {
|
||||||
vpnPermissionDenied = false
|
vpnPermissionDenied = false
|
||||||
|
showVpnPermissionDialog = false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
@@ -208,7 +210,10 @@ class MainActivity : AppCompatActivity() {
|
|||||||
WireguardAutoTunnelTheme(theme = appUiState.appState.theme) {
|
WireguardAutoTunnelTheme(theme = appUiState.appState.theme) {
|
||||||
VpnDeniedDialog(
|
VpnDeniedDialog(
|
||||||
showVpnPermissionDialog,
|
showVpnPermissionDialog,
|
||||||
onDismiss = { showVpnPermissionDialog = false },
|
onDismiss = {
|
||||||
|
showVpnPermissionDialog = false
|
||||||
|
vpnPermissionDenied = false
|
||||||
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
Scaffold(
|
Scaffold(
|
||||||
@@ -282,6 +287,9 @@ class MainActivity : AppCompatActivity() {
|
|||||||
composable<Route.AutoTunnelAdvanced> {
|
composable<Route.AutoTunnelAdvanced> {
|
||||||
AutoTunnelAdvancedScreen(appUiState, viewModel)
|
AutoTunnelAdvancedScreen(appUiState, viewModel)
|
||||||
}
|
}
|
||||||
|
composable<Route.WifiDetectionMethod> {
|
||||||
|
WifiDetectionMethodScreen(appUiState, viewModel)
|
||||||
|
}
|
||||||
composable<Route.Logs> { LogsScreen(appViewState, viewModel) }
|
composable<Route.Logs> { LogsScreen(appViewState, viewModel) }
|
||||||
composable<Route.Config> { backStack ->
|
composable<Route.Config> { backStack ->
|
||||||
val args = backStack.toRoute<Route.Config>()
|
val args = backStack.toRoute<Route.Config>()
|
||||||
@@ -294,11 +302,10 @@ class MainActivity : AppCompatActivity() {
|
|||||||
appUiState.tunnels
|
appUiState.tunnels
|
||||||
.firstOrNull { it.id == args.id }
|
.firstOrNull { it.id == args.id }
|
||||||
?.let { config ->
|
?.let { config ->
|
||||||
TunnelOptionsScreen(config, viewModel)
|
TunnelOptionsScreen(config, viewModel, appViewState)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
composable<Route.Lock> { PinLockScreen(viewModel) }
|
composable<Route.Lock> { PinLockScreen(viewModel) }
|
||||||
composable<Route.Scanner> { ScannerScreen(viewModel) }
|
|
||||||
composable<Route.KillSwitch> {
|
composable<Route.KillSwitch> {
|
||||||
KillSwitchScreen(appUiState, viewModel)
|
KillSwitchScreen(appUiState, viewModel)
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-4
@@ -3,12 +3,12 @@ package com.zaneschepke.wireguardautotunnel.core.broadcast
|
|||||||
import android.content.BroadcastReceiver
|
import android.content.BroadcastReceiver
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
|
import com.zaneschepke.logcatter.LogReader
|
||||||
import com.zaneschepke.wireguardautotunnel.core.service.ServiceManager
|
import com.zaneschepke.wireguardautotunnel.core.service.ServiceManager
|
||||||
import com.zaneschepke.wireguardautotunnel.core.tunnel.TunnelManager
|
import com.zaneschepke.wireguardautotunnel.core.tunnel.TunnelManager
|
||||||
import com.zaneschepke.wireguardautotunnel.di.ApplicationScope
|
import com.zaneschepke.wireguardautotunnel.di.ApplicationScope
|
||||||
import com.zaneschepke.wireguardautotunnel.di.IoDispatcher
|
import com.zaneschepke.wireguardautotunnel.di.IoDispatcher
|
||||||
import com.zaneschepke.wireguardautotunnel.domain.repository.AppDataRepository
|
import com.zaneschepke.wireguardautotunnel.domain.repository.AppDataRepository
|
||||||
import com.zaneschepke.wireguardautotunnel.util.extensions.isRunningOnTv
|
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import kotlinx.coroutines.CoroutineDispatcher
|
import kotlinx.coroutines.CoroutineDispatcher
|
||||||
@@ -26,13 +26,12 @@ class RestartReceiver : BroadcastReceiver() {
|
|||||||
|
|
||||||
@Inject lateinit var tunnelManager: TunnelManager
|
@Inject lateinit var tunnelManager: TunnelManager
|
||||||
|
|
||||||
|
@Inject lateinit var logReader: LogReader
|
||||||
|
|
||||||
@Inject @IoDispatcher lateinit var ioDispatcher: CoroutineDispatcher
|
@Inject @IoDispatcher lateinit var ioDispatcher: CoroutineDispatcher
|
||||||
|
|
||||||
override fun onReceive(context: Context, intent: Intent) {
|
override fun onReceive(context: Context, intent: Intent) {
|
||||||
Timber.d("RestartReceiver triggered with action: ${intent.action}")
|
Timber.d("RestartReceiver triggered with action: ${intent.action}")
|
||||||
// screen on for Android TV only to help with sleep shutdowns
|
|
||||||
val isTv = context.isRunningOnTv()
|
|
||||||
if (intent.action == Intent.ACTION_USER_PRESENT && !isTv) return
|
|
||||||
serviceManager.updateTunnelTile()
|
serviceManager.updateTunnelTile()
|
||||||
serviceManager.updateAutoTunnelTile()
|
serviceManager.updateAutoTunnelTile()
|
||||||
applicationScope.launch(ioDispatcher) {
|
applicationScope.launch(ioDispatcher) {
|
||||||
@@ -50,6 +49,7 @@ class RestartReceiver : BroadcastReceiver() {
|
|||||||
} else {
|
} else {
|
||||||
Timber.d("Restore on boot disabled, skipping")
|
Timber.d("Restore on boot disabled, skipping")
|
||||||
}
|
}
|
||||||
|
if (intent.action == Intent.ACTION_MY_PACKAGE_REPLACED) logReader.deleteAndClearLogs()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+9
-17
@@ -14,9 +14,9 @@ import com.zaneschepke.wireguardautotunnel.core.notification.NotificationManager
|
|||||||
import com.zaneschepke.wireguardautotunnel.core.notification.WireGuardNotification
|
import com.zaneschepke.wireguardautotunnel.core.notification.WireGuardNotification
|
||||||
import com.zaneschepke.wireguardautotunnel.core.tunnel.TunnelManager
|
import com.zaneschepke.wireguardautotunnel.core.tunnel.TunnelManager
|
||||||
import com.zaneschepke.wireguardautotunnel.di.IoDispatcher
|
import com.zaneschepke.wireguardautotunnel.di.IoDispatcher
|
||||||
import com.zaneschepke.wireguardautotunnel.domain.entity.TunnelConf
|
|
||||||
import com.zaneschepke.wireguardautotunnel.domain.enums.NotificationAction
|
import com.zaneschepke.wireguardautotunnel.domain.enums.NotificationAction
|
||||||
import com.zaneschepke.wireguardautotunnel.domain.enums.TunnelStatus
|
import com.zaneschepke.wireguardautotunnel.domain.enums.TunnelStatus
|
||||||
|
import com.zaneschepke.wireguardautotunnel.domain.model.TunnelConf
|
||||||
import com.zaneschepke.wireguardautotunnel.domain.repository.TunnelRepository
|
import com.zaneschepke.wireguardautotunnel.domain.repository.TunnelRepository
|
||||||
import com.zaneschepke.wireguardautotunnel.domain.state.TunnelState
|
import com.zaneschepke.wireguardautotunnel.domain.state.TunnelState
|
||||||
import com.zaneschepke.wireguardautotunnel.util.Constants
|
import com.zaneschepke.wireguardautotunnel.util.Constants
|
||||||
@@ -24,22 +24,10 @@ import com.zaneschepke.wireguardautotunnel.util.extensions.distinctByKeys
|
|||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
import java.util.concurrent.ConcurrentHashMap
|
import java.util.concurrent.ConcurrentHashMap
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import kotlinx.coroutines.CoroutineDispatcher
|
import kotlinx.coroutines.*
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.flow.*
|
||||||
import kotlinx.coroutines.NonCancellable
|
|
||||||
import kotlinx.coroutines.coroutineScope
|
|
||||||
import kotlinx.coroutines.delay
|
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
|
||||||
import kotlinx.coroutines.flow.collectLatest
|
|
||||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
|
||||||
import kotlinx.coroutines.flow.filterNotNull
|
|
||||||
import kotlinx.coroutines.flow.flowOn
|
|
||||||
import kotlinx.coroutines.flow.map
|
|
||||||
import kotlinx.coroutines.isActive
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import kotlinx.coroutines.sync.Mutex
|
import kotlinx.coroutines.sync.Mutex
|
||||||
import kotlinx.coroutines.sync.withLock
|
import kotlinx.coroutines.sync.withLock
|
||||||
import kotlinx.coroutines.withContext
|
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
|
||||||
@AndroidEntryPoint
|
@AndroidEntryPoint
|
||||||
@@ -165,8 +153,12 @@ class TunnelForegroundService : LifecycleService() {
|
|||||||
} else {
|
} else {
|
||||||
pingJobs[tun]?.cancel() // Cancel any stale job
|
pingJobs[tun]?.cancel() // Cancel any stale job
|
||||||
if (tun.isPingEnabled) {
|
if (tun.isPingEnabled) {
|
||||||
pingJobs[tun] = startPingJob(tun)
|
if (tun.isStaticallyConfigured()) {
|
||||||
Timber.d("Started ping job for ${tun.tunName}")
|
Timber.d("Skipping ping for statically configured tunnel")
|
||||||
|
} else {
|
||||||
|
pingJobs[tun] = startPingJob(tun)
|
||||||
|
Timber.d("Started ping job for ${tun.tunName}")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+44
-25
@@ -15,12 +15,12 @@ import com.zaneschepke.wireguardautotunnel.core.notification.WireGuardNotificati
|
|||||||
import com.zaneschepke.wireguardautotunnel.core.service.ServiceManager
|
import com.zaneschepke.wireguardautotunnel.core.service.ServiceManager
|
||||||
import com.zaneschepke.wireguardautotunnel.core.tunnel.TunnelManager
|
import com.zaneschepke.wireguardautotunnel.core.tunnel.TunnelManager
|
||||||
import com.zaneschepke.wireguardautotunnel.di.IoDispatcher
|
import com.zaneschepke.wireguardautotunnel.di.IoDispatcher
|
||||||
import com.zaneschepke.wireguardautotunnel.domain.entity.AppSettings
|
|
||||||
import com.zaneschepke.wireguardautotunnel.domain.entity.TunnelConf
|
|
||||||
import com.zaneschepke.wireguardautotunnel.domain.enums.BackendState
|
import com.zaneschepke.wireguardautotunnel.domain.enums.BackendState
|
||||||
import com.zaneschepke.wireguardautotunnel.domain.enums.NotificationAction
|
import com.zaneschepke.wireguardautotunnel.domain.enums.NotificationAction
|
||||||
import com.zaneschepke.wireguardautotunnel.domain.events.AutoTunnelEvent
|
import com.zaneschepke.wireguardautotunnel.domain.events.AutoTunnelEvent
|
||||||
import com.zaneschepke.wireguardautotunnel.domain.events.KillSwitchEvent
|
import com.zaneschepke.wireguardautotunnel.domain.events.KillSwitchEvent
|
||||||
|
import com.zaneschepke.wireguardautotunnel.domain.model.AppSettings
|
||||||
|
import com.zaneschepke.wireguardautotunnel.domain.model.TunnelConf
|
||||||
import com.zaneschepke.wireguardautotunnel.domain.repository.AppDataRepository
|
import com.zaneschepke.wireguardautotunnel.domain.repository.AppDataRepository
|
||||||
import com.zaneschepke.wireguardautotunnel.domain.state.AutoTunnelState
|
import com.zaneschepke.wireguardautotunnel.domain.state.AutoTunnelState
|
||||||
import com.zaneschepke.wireguardautotunnel.domain.state.NetworkState
|
import com.zaneschepke.wireguardautotunnel.domain.state.NetworkState
|
||||||
@@ -29,20 +29,8 @@ import com.zaneschepke.wireguardautotunnel.util.extensions.Tunnels
|
|||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import javax.inject.Provider
|
import javax.inject.Provider
|
||||||
import kotlinx.coroutines.CoroutineDispatcher
|
import kotlinx.coroutines.*
|
||||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
import kotlinx.coroutines.flow.*
|
||||||
import kotlinx.coroutines.FlowPreview
|
|
||||||
import kotlinx.coroutines.Job
|
|
||||||
import kotlinx.coroutines.flow.Flow
|
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
|
||||||
import kotlinx.coroutines.flow.combine
|
|
||||||
import kotlinx.coroutines.flow.debounce
|
|
||||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
|
||||||
import kotlinx.coroutines.flow.flatMapLatest
|
|
||||||
import kotlinx.coroutines.flow.flowOn
|
|
||||||
import kotlinx.coroutines.flow.map
|
|
||||||
import kotlinx.coroutines.flow.update
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
|
||||||
@AndroidEntryPoint
|
@AndroidEntryPoint
|
||||||
@@ -72,6 +60,8 @@ class AutoTunnelService : LifecycleService() {
|
|||||||
|
|
||||||
private val binder = LocalBinder(this)
|
private val binder = LocalBinder(this)
|
||||||
|
|
||||||
|
private var isServiceRunning = false
|
||||||
|
|
||||||
override fun onCreate() {
|
override fun onCreate() {
|
||||||
super.onCreate()
|
super.onCreate()
|
||||||
launchWatcherNotification()
|
launchWatcherNotification()
|
||||||
@@ -90,6 +80,8 @@ class AutoTunnelService : LifecycleService() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun start() {
|
fun start() {
|
||||||
|
if (isServiceRunning) return
|
||||||
|
isServiceRunning = true
|
||||||
kotlin
|
kotlin
|
||||||
.runCatching {
|
.runCatching {
|
||||||
launchWatcherNotification()
|
launchWatcherNotification()
|
||||||
@@ -102,6 +94,7 @@ class AutoTunnelService : LifecycleService() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun stop() {
|
fun stop() {
|
||||||
|
isServiceRunning = false
|
||||||
wakeLock?.let { if (it.isHeld) it.release() }
|
wakeLock?.let { if (it.isHeld) it.release() }
|
||||||
stopSelf()
|
stopSelf()
|
||||||
}
|
}
|
||||||
@@ -261,18 +254,44 @@ class AutoTunnelService : LifecycleService() {
|
|||||||
lifecycleScope.launch(ioDispatcher) {
|
lifecycleScope.launch(ioDispatcher) {
|
||||||
Timber.i("Starting auto-tunnel network event watcher")
|
Timber.i("Starting auto-tunnel network event watcher")
|
||||||
val settings = appDataRepository.get().settings.get()
|
val settings = appDataRepository.get().settings.get()
|
||||||
Timber.d("Starting with debounce delay of: ${settings.debounceDelaySeconds} seconds")
|
|
||||||
|
var reevaluationJob: Job? = null
|
||||||
|
|
||||||
autoTunnelStateFlow.debounce(settings.debounceDelayMillis()).collect { watcherState ->
|
autoTunnelStateFlow.debounce(settings.debounceDelayMillis()).collect { watcherState ->
|
||||||
if (watcherState == defaultState) return@collect
|
if (watcherState == defaultState) return@collect
|
||||||
Timber.d("New auto tunnel state emitted ${watcherState.networkState}")
|
reevaluationJob?.cancel()
|
||||||
when (val event = watcherState.asAutoTunnelEvent()) {
|
handleAutoTunnelEvent(watcherState)
|
||||||
is AutoTunnelEvent.Start ->
|
|
||||||
(event.tunnelConf ?: appDataRepository.get().getPrimaryOrFirstTunnel())
|
// schedule one-time re-evaluation
|
||||||
?.let { tunnelManager.startTunnel(it) }
|
reevaluationJob = launch {
|
||||||
// TODO improve this to target specific tunnels to better support multi-tunnel
|
delay(REEVALUATE_CHECK_DELAY)
|
||||||
is AutoTunnelEvent.Stop -> tunnelManager.stopTunnel()
|
if (watcherState != defaultState) {
|
||||||
AutoTunnelEvent.DoNothing -> Timber.i("Auto-tunneling: no condition met")
|
Timber.d("Re-evaluating auto-tunnel state..")
|
||||||
|
handleAutoTunnelEvent(watcherState)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private suspend fun handleAutoTunnelEvent(watcherState: AutoTunnelState) {
|
||||||
|
Timber.i("Auto-tunnel settings: ${watcherState.settings.toAutoTunnelStateString()}")
|
||||||
|
Timber.i("Auto-tunnel network state: ${watcherState.networkState}")
|
||||||
|
when (
|
||||||
|
val event =
|
||||||
|
watcherState.asAutoTunnelEvent().also {
|
||||||
|
Timber.i("Auto-tunnel event: ${it.javaClass.simpleName}")
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
is AutoTunnelEvent.Start ->
|
||||||
|
(event.tunnelConf ?: appDataRepository.get().getPrimaryOrFirstTunnel())?.let {
|
||||||
|
tunnelManager.startTunnel(it)
|
||||||
|
}
|
||||||
|
is AutoTunnelEvent.Stop -> tunnelManager.stopTunnel()
|
||||||
|
AutoTunnelEvent.DoNothing -> Timber.i("Auto-tunneling: nothing to do")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val REEVALUATE_CHECK_DELAY = 5_000L
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -13,7 +13,7 @@ import com.zaneschepke.wireguardautotunnel.R
|
|||||||
import com.zaneschepke.wireguardautotunnel.WireGuardAutoTunnel
|
import com.zaneschepke.wireguardautotunnel.WireGuardAutoTunnel
|
||||||
import com.zaneschepke.wireguardautotunnel.core.service.ServiceManager
|
import com.zaneschepke.wireguardautotunnel.core.service.ServiceManager
|
||||||
import com.zaneschepke.wireguardautotunnel.core.tunnel.TunnelManager
|
import com.zaneschepke.wireguardautotunnel.core.tunnel.TunnelManager
|
||||||
import com.zaneschepke.wireguardautotunnel.domain.entity.TunnelConf
|
import com.zaneschepke.wireguardautotunnel.domain.model.TunnelConf
|
||||||
import com.zaneschepke.wireguardautotunnel.domain.repository.AppDataRepository
|
import com.zaneschepke.wireguardautotunnel.domain.repository.AppDataRepository
|
||||||
import com.zaneschepke.wireguardautotunnel.domain.state.TunnelState
|
import com.zaneschepke.wireguardautotunnel.domain.state.TunnelState
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
|
|||||||
@@ -3,9 +3,9 @@ package com.zaneschepke.wireguardautotunnel.core.tunnel
|
|||||||
import com.wireguard.android.backend.Tunnel
|
import com.wireguard.android.backend.Tunnel
|
||||||
import com.zaneschepke.wireguardautotunnel.core.service.ServiceManager
|
import com.zaneschepke.wireguardautotunnel.core.service.ServiceManager
|
||||||
import com.zaneschepke.wireguardautotunnel.di.ApplicationScope
|
import com.zaneschepke.wireguardautotunnel.di.ApplicationScope
|
||||||
import com.zaneschepke.wireguardautotunnel.domain.entity.TunnelConf
|
|
||||||
import com.zaneschepke.wireguardautotunnel.domain.enums.BackendError
|
import com.zaneschepke.wireguardautotunnel.domain.enums.BackendError
|
||||||
import com.zaneschepke.wireguardautotunnel.domain.enums.TunnelStatus
|
import com.zaneschepke.wireguardautotunnel.domain.enums.TunnelStatus
|
||||||
|
import com.zaneschepke.wireguardautotunnel.domain.model.TunnelConf
|
||||||
import com.zaneschepke.wireguardautotunnel.domain.repository.AppDataRepository
|
import com.zaneschepke.wireguardautotunnel.domain.repository.AppDataRepository
|
||||||
import com.zaneschepke.wireguardautotunnel.domain.state.TunnelState
|
import com.zaneschepke.wireguardautotunnel.domain.state.TunnelState
|
||||||
import com.zaneschepke.wireguardautotunnel.domain.state.TunnelStatistics
|
import com.zaneschepke.wireguardautotunnel.domain.state.TunnelStatistics
|
||||||
@@ -114,16 +114,16 @@ abstract class BaseTunnel(
|
|||||||
if (this@BaseTunnel is UserspaceTunnel) stopActiveTunnels()
|
if (this@BaseTunnel is UserspaceTunnel) stopActiveTunnels()
|
||||||
tunMutex.withLock {
|
tunMutex.withLock {
|
||||||
tunThreads[tunnelConf.id] = thread {
|
tunThreads[tunnelConf.id] = thread {
|
||||||
runBlocking {
|
try {
|
||||||
try {
|
runBlocking {
|
||||||
Timber.d("Starting tunnel ${tunnelConf.id}...")
|
Timber.d("Starting tunnel ${tunnelConf.id}...")
|
||||||
startTunnelInner(tunnelConf)
|
startTunnelInner(tunnelConf)
|
||||||
Timber.d("Started complete for tunnel ${tunnelConf.name}...")
|
Timber.d("Started complete for tunnel ${tunnelConf.name}...")
|
||||||
} catch (e: InterruptedException) {
|
|
||||||
Timber.w(
|
|
||||||
"Tunnel start has been interrupted as ${tunnelConf.name} failed to start"
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
} catch (e: InterruptedException) {
|
||||||
|
Timber.w(
|
||||||
|
"Tunnel start has been interrupted as ${tunnelConf.name} failed to start"
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
package com.zaneschepke.wireguardautotunnel.core.tunnel
|
package com.zaneschepke.wireguardautotunnel.core.tunnel
|
||||||
|
|
||||||
import com.zaneschepke.wireguardautotunnel.domain.entity.TunnelConf
|
|
||||||
import com.zaneschepke.wireguardautotunnel.domain.enums.TunnelStatus
|
import com.zaneschepke.wireguardautotunnel.domain.enums.TunnelStatus
|
||||||
|
import com.zaneschepke.wireguardautotunnel.domain.model.TunnelConf
|
||||||
import com.zaneschepke.wireguardautotunnel.domain.state.TunnelState
|
import com.zaneschepke.wireguardautotunnel.domain.state.TunnelState
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
|
||||||
|
|||||||
@@ -5,9 +5,9 @@ import com.wireguard.android.backend.BackendException
|
|||||||
import com.wireguard.android.backend.Tunnel
|
import com.wireguard.android.backend.Tunnel
|
||||||
import com.zaneschepke.wireguardautotunnel.core.service.ServiceManager
|
import com.zaneschepke.wireguardautotunnel.core.service.ServiceManager
|
||||||
import com.zaneschepke.wireguardautotunnel.di.ApplicationScope
|
import com.zaneschepke.wireguardautotunnel.di.ApplicationScope
|
||||||
import com.zaneschepke.wireguardautotunnel.domain.entity.TunnelConf
|
|
||||||
import com.zaneschepke.wireguardautotunnel.domain.enums.BackendState
|
import com.zaneschepke.wireguardautotunnel.domain.enums.BackendState
|
||||||
import com.zaneschepke.wireguardautotunnel.domain.enums.TunnelStatus
|
import com.zaneschepke.wireguardautotunnel.domain.enums.TunnelStatus
|
||||||
|
import com.zaneschepke.wireguardautotunnel.domain.model.TunnelConf
|
||||||
import com.zaneschepke.wireguardautotunnel.domain.repository.AppDataRepository
|
import com.zaneschepke.wireguardautotunnel.domain.repository.AppDataRepository
|
||||||
import com.zaneschepke.wireguardautotunnel.domain.state.TunnelStatistics
|
import com.zaneschepke.wireguardautotunnel.domain.state.TunnelStatistics
|
||||||
import com.zaneschepke.wireguardautotunnel.domain.state.WireGuardStatistics
|
import com.zaneschepke.wireguardautotunnel.domain.state.WireGuardStatistics
|
||||||
|
|||||||
@@ -4,10 +4,10 @@ import com.zaneschepke.wireguardautotunnel.di.ApplicationScope
|
|||||||
import com.zaneschepke.wireguardautotunnel.di.IoDispatcher
|
import com.zaneschepke.wireguardautotunnel.di.IoDispatcher
|
||||||
import com.zaneschepke.wireguardautotunnel.di.Kernel
|
import com.zaneschepke.wireguardautotunnel.di.Kernel
|
||||||
import com.zaneschepke.wireguardautotunnel.di.Userspace
|
import com.zaneschepke.wireguardautotunnel.di.Userspace
|
||||||
import com.zaneschepke.wireguardautotunnel.domain.entity.TunnelConf
|
|
||||||
import com.zaneschepke.wireguardautotunnel.domain.enums.BackendError
|
import com.zaneschepke.wireguardautotunnel.domain.enums.BackendError
|
||||||
import com.zaneschepke.wireguardautotunnel.domain.enums.BackendState
|
import com.zaneschepke.wireguardautotunnel.domain.enums.BackendState
|
||||||
import com.zaneschepke.wireguardautotunnel.domain.enums.TunnelStatus
|
import com.zaneschepke.wireguardautotunnel.domain.enums.TunnelStatus
|
||||||
|
import com.zaneschepke.wireguardautotunnel.domain.model.TunnelConf
|
||||||
import com.zaneschepke.wireguardautotunnel.domain.repository.AppDataRepository
|
import com.zaneschepke.wireguardautotunnel.domain.repository.AppDataRepository
|
||||||
import com.zaneschepke.wireguardautotunnel.domain.state.TunnelStatistics
|
import com.zaneschepke.wireguardautotunnel.domain.state.TunnelStatistics
|
||||||
import java.util.concurrent.ConcurrentHashMap
|
import java.util.concurrent.ConcurrentHashMap
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
package com.zaneschepke.wireguardautotunnel.core.tunnel
|
package com.zaneschepke.wireguardautotunnel.core.tunnel
|
||||||
|
|
||||||
import com.zaneschepke.wireguardautotunnel.domain.entity.TunnelConf
|
|
||||||
import com.zaneschepke.wireguardautotunnel.domain.enums.BackendError
|
import com.zaneschepke.wireguardautotunnel.domain.enums.BackendError
|
||||||
import com.zaneschepke.wireguardautotunnel.domain.enums.BackendState
|
import com.zaneschepke.wireguardautotunnel.domain.enums.BackendState
|
||||||
import com.zaneschepke.wireguardautotunnel.domain.enums.TunnelStatus
|
import com.zaneschepke.wireguardautotunnel.domain.enums.TunnelStatus
|
||||||
|
import com.zaneschepke.wireguardautotunnel.domain.model.TunnelConf
|
||||||
import com.zaneschepke.wireguardautotunnel.domain.state.TunnelState
|
import com.zaneschepke.wireguardautotunnel.domain.state.TunnelState
|
||||||
import com.zaneschepke.wireguardautotunnel.domain.state.TunnelStatistics
|
import com.zaneschepke.wireguardautotunnel.domain.state.TunnelStatistics
|
||||||
import java.util.concurrent.ConcurrentHashMap
|
import java.util.concurrent.ConcurrentHashMap
|
||||||
|
|||||||
+1
-1
@@ -2,9 +2,9 @@ package com.zaneschepke.wireguardautotunnel.core.tunnel
|
|||||||
|
|
||||||
import com.zaneschepke.wireguardautotunnel.core.service.ServiceManager
|
import com.zaneschepke.wireguardautotunnel.core.service.ServiceManager
|
||||||
import com.zaneschepke.wireguardautotunnel.di.ApplicationScope
|
import com.zaneschepke.wireguardautotunnel.di.ApplicationScope
|
||||||
import com.zaneschepke.wireguardautotunnel.domain.entity.TunnelConf
|
|
||||||
import com.zaneschepke.wireguardautotunnel.domain.enums.BackendState
|
import com.zaneschepke.wireguardautotunnel.domain.enums.BackendState
|
||||||
import com.zaneschepke.wireguardautotunnel.domain.enums.TunnelStatus
|
import com.zaneschepke.wireguardautotunnel.domain.enums.TunnelStatus
|
||||||
|
import com.zaneschepke.wireguardautotunnel.domain.model.TunnelConf
|
||||||
import com.zaneschepke.wireguardautotunnel.domain.repository.AppDataRepository
|
import com.zaneschepke.wireguardautotunnel.domain.repository.AppDataRepository
|
||||||
import com.zaneschepke.wireguardautotunnel.domain.state.AmneziaStatistics
|
import com.zaneschepke.wireguardautotunnel.domain.state.AmneziaStatistics
|
||||||
import com.zaneschepke.wireguardautotunnel.domain.state.TunnelStatistics
|
import com.zaneschepke.wireguardautotunnel.domain.state.TunnelStatistics
|
||||||
|
|||||||
@@ -8,12 +8,12 @@ import androidx.room.TypeConverters
|
|||||||
import androidx.room.migration.AutoMigrationSpec
|
import androidx.room.migration.AutoMigrationSpec
|
||||||
import com.zaneschepke.wireguardautotunnel.data.dao.SettingsDao
|
import com.zaneschepke.wireguardautotunnel.data.dao.SettingsDao
|
||||||
import com.zaneschepke.wireguardautotunnel.data.dao.TunnelConfigDao
|
import com.zaneschepke.wireguardautotunnel.data.dao.TunnelConfigDao
|
||||||
import com.zaneschepke.wireguardautotunnel.data.model.Settings
|
import com.zaneschepke.wireguardautotunnel.data.entity.Settings
|
||||||
import com.zaneschepke.wireguardautotunnel.data.model.TunnelConfig
|
import com.zaneschepke.wireguardautotunnel.data.entity.TunnelConfig
|
||||||
|
|
||||||
@Database(
|
@Database(
|
||||||
entities = [Settings::class, TunnelConfig::class],
|
entities = [Settings::class, TunnelConfig::class],
|
||||||
version = 16,
|
version = 17,
|
||||||
autoMigrations =
|
autoMigrations =
|
||||||
[
|
[
|
||||||
AutoMigration(from = 1, to = 2),
|
AutoMigration(from = 1, to = 2),
|
||||||
@@ -31,10 +31,11 @@ import com.zaneschepke.wireguardautotunnel.data.model.TunnelConfig
|
|||||||
AutoMigration(from = 13, to = 14),
|
AutoMigration(from = 13, to = 14),
|
||||||
AutoMigration(from = 14, to = 15),
|
AutoMigration(from = 14, to = 15),
|
||||||
AutoMigration(from = 15, to = 16),
|
AutoMigration(from = 15, to = 16),
|
||||||
|
AutoMigration(from = 16, to = 17, spec = WifiDetectionMigration::class),
|
||||||
],
|
],
|
||||||
exportSchema = true,
|
exportSchema = true,
|
||||||
)
|
)
|
||||||
@TypeConverters(DatabaseListConverters::class)
|
@TypeConverters(DatabaseConverters::class)
|
||||||
abstract class AppDatabase : RoomDatabase() {
|
abstract class AppDatabase : RoomDatabase() {
|
||||||
abstract fun settingDao(): SettingsDao
|
abstract fun settingDao(): SettingsDao
|
||||||
|
|
||||||
@@ -47,3 +48,6 @@ class RemoveLegacySettingColumnsMigration : AutoMigrationSpec
|
|||||||
|
|
||||||
@DeleteColumn(tableName = "Settings", columnName = "is_auto_tunnel_paused")
|
@DeleteColumn(tableName = "Settings", columnName = "is_auto_tunnel_paused")
|
||||||
class RemoveTunnelPauseMigration : AutoMigrationSpec
|
class RemoveTunnelPauseMigration : AutoMigrationSpec
|
||||||
|
|
||||||
|
@DeleteColumn(tableName = "Settings", columnName = "is_wifi_by_shell_enabled")
|
||||||
|
class WifiDetectionMigration : AutoMigrationSpec
|
||||||
|
|||||||
+8
-1
@@ -1,9 +1,10 @@
|
|||||||
package com.zaneschepke.wireguardautotunnel.data
|
package com.zaneschepke.wireguardautotunnel.data
|
||||||
|
|
||||||
import androidx.room.TypeConverter
|
import androidx.room.TypeConverter
|
||||||
|
import com.zaneschepke.wireguardautotunnel.data.entity.Settings
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
|
|
||||||
class DatabaseListConverters {
|
class DatabaseConverters {
|
||||||
@TypeConverter
|
@TypeConverter
|
||||||
fun listToString(value: MutableList<String>): String {
|
fun listToString(value: MutableList<String>): String {
|
||||||
return Json.encodeToString(value)
|
return Json.encodeToString(value)
|
||||||
@@ -20,4 +21,10 @@ class DatabaseListConverters {
|
|||||||
Json.decodeFromString<MutableList<String>>(json)
|
Json.decodeFromString<MutableList<String>>(json)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@TypeConverter fun fromStatus(status: Settings.WifiDetectionMethod): Int = status.value
|
||||||
|
|
||||||
|
@TypeConverter
|
||||||
|
fun toStatus(value: Int): Settings.WifiDetectionMethod =
|
||||||
|
Settings.WifiDetectionMethod.fromValue(value)
|
||||||
}
|
}
|
||||||
@@ -5,7 +5,7 @@ import androidx.room.Delete
|
|||||||
import androidx.room.Insert
|
import androidx.room.Insert
|
||||||
import androidx.room.OnConflictStrategy
|
import androidx.room.OnConflictStrategy
|
||||||
import androidx.room.Query
|
import androidx.room.Query
|
||||||
import com.zaneschepke.wireguardautotunnel.data.model.Settings
|
import com.zaneschepke.wireguardautotunnel.data.entity.Settings
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
|
||||||
@Dao
|
@Dao
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import androidx.room.Delete
|
|||||||
import androidx.room.Insert
|
import androidx.room.Insert
|
||||||
import androidx.room.OnConflictStrategy
|
import androidx.room.OnConflictStrategy
|
||||||
import androidx.room.Query
|
import androidx.room.Query
|
||||||
import com.zaneschepke.wireguardautotunnel.data.model.TunnelConfig
|
import com.zaneschepke.wireguardautotunnel.data.entity.TunnelConfig
|
||||||
import com.zaneschepke.wireguardautotunnel.util.extensions.TunnelConfigs
|
import com.zaneschepke.wireguardautotunnel.util.extensions.TunnelConfigs
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
package com.zaneschepke.wireguardautotunnel.data.model
|
package com.zaneschepke.wireguardautotunnel.data.entity
|
||||||
|
|
||||||
import kotlinx.serialization.SerialName
|
import kotlinx.serialization.SerialName
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
package com.zaneschepke.wireguardautotunnel.data.entity
|
||||||
|
|
||||||
|
import com.zaneschepke.wireguardautotunnel.ui.theme.Theme
|
||||||
|
|
||||||
|
data class GeneralState(
|
||||||
|
val isLocationDisclosureShown: Boolean = LOCATION_DISCLOSURE_SHOWN_DEFAULT,
|
||||||
|
val isBatteryOptimizationDisableShown: Boolean = BATTERY_OPTIMIZATION_DISABLE_SHOWN_DEFAULT,
|
||||||
|
val isPinLockEnabled: Boolean = PIN_LOCK_ENABLED_DEFAULT,
|
||||||
|
val expandedTunnelIds: List<Int> = emptyList(),
|
||||||
|
val isLocalLogsEnabled: Boolean = IS_LOGS_ENABLED_DEFAULT,
|
||||||
|
val isRemoteControlEnabled: Boolean = IS_REMOTE_CONTROL_ENABLED,
|
||||||
|
val remoteKey: String? = null,
|
||||||
|
val locale: String? = null,
|
||||||
|
val theme: Theme = Theme.AUTOMATIC,
|
||||||
|
) {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val LOCATION_DISCLOSURE_SHOWN_DEFAULT = false
|
||||||
|
const val BATTERY_OPTIMIZATION_DISABLE_SHOWN_DEFAULT = false
|
||||||
|
const val PIN_LOCK_ENABLED_DEFAULT = false
|
||||||
|
const val IS_LOGS_ENABLED_DEFAULT = false
|
||||||
|
const val IS_REMOTE_CONTROL_ENABLED = false
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
package com.zaneschepke.wireguardautotunnel.data.entity
|
||||||
|
|
||||||
|
import kotlinx.serialization.SerialName
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class GitHubRelease(
|
||||||
|
@SerialName("tag_name") val tagName: String,
|
||||||
|
val name: String?,
|
||||||
|
val body: String?,
|
||||||
|
val assets: List<Asset>,
|
||||||
|
)
|
||||||
+15
-56
@@ -1,9 +1,8 @@
|
|||||||
package com.zaneschepke.wireguardautotunnel.data.model
|
package com.zaneschepke.wireguardautotunnel.data.entity
|
||||||
|
|
||||||
import androidx.room.ColumnInfo
|
import androidx.room.ColumnInfo
|
||||||
import androidx.room.Entity
|
import androidx.room.Entity
|
||||||
import androidx.room.PrimaryKey
|
import androidx.room.PrimaryKey
|
||||||
import com.zaneschepke.wireguardautotunnel.domain.entity.AppSettings
|
|
||||||
|
|
||||||
@Entity
|
@Entity
|
||||||
data class Settings(
|
data class Settings(
|
||||||
@@ -32,8 +31,6 @@ data class Settings(
|
|||||||
val isAmneziaEnabled: Boolean = false,
|
val isAmneziaEnabled: Boolean = false,
|
||||||
@ColumnInfo(name = "is_wildcards_enabled", defaultValue = "false")
|
@ColumnInfo(name = "is_wildcards_enabled", defaultValue = "false")
|
||||||
val isWildcardsEnabled: Boolean = false,
|
val isWildcardsEnabled: Boolean = false,
|
||||||
@ColumnInfo(name = "is_wifi_by_shell_enabled", defaultValue = "false")
|
|
||||||
val isWifiNameByShellEnabled: Boolean = false,
|
|
||||||
@ColumnInfo(name = "is_stop_on_no_internet_enabled", defaultValue = "false")
|
@ColumnInfo(name = "is_stop_on_no_internet_enabled", defaultValue = "false")
|
||||||
val isStopOnNoInternetEnabled: Boolean = false,
|
val isStopOnNoInternetEnabled: Boolean = false,
|
||||||
@ColumnInfo(name = "is_vpn_kill_switch_enabled", defaultValue = "false")
|
@ColumnInfo(name = "is_vpn_kill_switch_enabled", defaultValue = "false")
|
||||||
@@ -46,61 +43,23 @@ data class Settings(
|
|||||||
val debounceDelaySeconds: Int = 3,
|
val debounceDelaySeconds: Int = 3,
|
||||||
@ColumnInfo(name = "is_disable_kill_switch_on_trusted_enabled", defaultValue = "false")
|
@ColumnInfo(name = "is_disable_kill_switch_on_trusted_enabled", defaultValue = "false")
|
||||||
val isDisableKillSwitchOnTrustedEnabled: Boolean = false,
|
val isDisableKillSwitchOnTrustedEnabled: Boolean = false,
|
||||||
|
@ColumnInfo(name = "is_tunnel_on_unsecure_enabled", defaultValue = "false")
|
||||||
|
val isTunnelOnUnsecureEnabled: Boolean = false,
|
||||||
|
@ColumnInfo(name = "split_tunnel_apps", defaultValue = "")
|
||||||
|
val splitTunnelApps: MutableList<String> = mutableListOf(),
|
||||||
|
@ColumnInfo(name = "wifi_detection_method", defaultValue = "0")
|
||||||
|
val wifiDetectionMethod: WifiDetectionMethod = WifiDetectionMethod.fromValue(0),
|
||||||
) {
|
) {
|
||||||
|
|
||||||
fun toAppSettings(): AppSettings {
|
enum class WifiDetectionMethod(val value: Int) {
|
||||||
return AppSettings(
|
DEFAULT(0),
|
||||||
id,
|
LEGACY(1),
|
||||||
isAutoTunnelEnabled,
|
ROOT(2),
|
||||||
isTunnelOnMobileDataEnabled,
|
SHIZUKU(3);
|
||||||
trustedNetworkSSIDs,
|
|
||||||
isAlwaysOnVpnEnabled,
|
|
||||||
isTunnelOnEthernetEnabled,
|
|
||||||
isShortcutsEnabled,
|
|
||||||
isTunnelOnWifiEnabled,
|
|
||||||
isKernelEnabled,
|
|
||||||
isRestoreOnBootEnabled,
|
|
||||||
isMultiTunnelEnabled,
|
|
||||||
isPingEnabled,
|
|
||||||
isAmneziaEnabled,
|
|
||||||
isWildcardsEnabled,
|
|
||||||
isWifiNameByShellEnabled,
|
|
||||||
isStopOnNoInternetEnabled,
|
|
||||||
isVpnKillSwitchEnabled,
|
|
||||||
isKernelKillSwitchEnabled,
|
|
||||||
isLanOnKillSwitchEnabled,
|
|
||||||
debounceDelaySeconds,
|
|
||||||
isDisableKillSwitchOnTrustedEnabled,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
fun from(appSettings: AppSettings): Settings {
|
fun fromValue(value: Int): WifiDetectionMethod =
|
||||||
return with(appSettings) {
|
entries.find { it.value == value } ?: DEFAULT
|
||||||
Settings(
|
|
||||||
id,
|
|
||||||
isAutoTunnelEnabled,
|
|
||||||
isTunnelOnMobileDataEnabled,
|
|
||||||
trustedNetworkSSIDs.toMutableList(),
|
|
||||||
isAlwaysOnVpnEnabled,
|
|
||||||
isTunnelOnEthernetEnabled,
|
|
||||||
isShortcutsEnabled,
|
|
||||||
isTunnelOnWifiEnabled,
|
|
||||||
isKernelEnabled,
|
|
||||||
isRestoreOnBootEnabled,
|
|
||||||
isMultiTunnelEnabled,
|
|
||||||
isPingEnabled,
|
|
||||||
isAmneziaEnabled,
|
|
||||||
isWildcardsEnabled,
|
|
||||||
isWifiNameByShellEnabled,
|
|
||||||
isStopOnNoInternetEnabled,
|
|
||||||
isVpnKillSwitchEnabled,
|
|
||||||
isKernelKillSwitchEnabled,
|
|
||||||
isLanOnKillSwitchEnabled,
|
|
||||||
debounceDelaySeconds,
|
|
||||||
isDisableKillSwitchOnTrustedEnabled,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+1
-43
@@ -1,10 +1,9 @@
|
|||||||
package com.zaneschepke.wireguardautotunnel.data.model
|
package com.zaneschepke.wireguardautotunnel.data.entity
|
||||||
|
|
||||||
import androidx.room.ColumnInfo
|
import androidx.room.ColumnInfo
|
||||||
import androidx.room.Entity
|
import androidx.room.Entity
|
||||||
import androidx.room.Index
|
import androidx.room.Index
|
||||||
import androidx.room.PrimaryKey
|
import androidx.room.PrimaryKey
|
||||||
import com.zaneschepke.wireguardautotunnel.domain.entity.TunnelConf
|
|
||||||
|
|
||||||
@Entity(indices = [Index(value = ["name"], unique = true)])
|
@Entity(indices = [Index(value = ["name"], unique = true)])
|
||||||
data class TunnelConfig(
|
data class TunnelConfig(
|
||||||
@@ -30,48 +29,7 @@ data class TunnelConfig(
|
|||||||
var isIpv4Preferred: Boolean = true,
|
var isIpv4Preferred: Boolean = true,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
fun toTunnel(): TunnelConf {
|
|
||||||
return TunnelConf(
|
|
||||||
id,
|
|
||||||
name,
|
|
||||||
wgQuick,
|
|
||||||
tunnelNetworks,
|
|
||||||
isMobileDataTunnel,
|
|
||||||
isPrimaryTunnel,
|
|
||||||
amQuick,
|
|
||||||
isActive,
|
|
||||||
isPingEnabled,
|
|
||||||
pingInterval,
|
|
||||||
pingCooldown,
|
|
||||||
pingIp,
|
|
||||||
isEthernetTunnel,
|
|
||||||
isIpv4Preferred,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
||||||
const val AM_QUICK_DEFAULT = ""
|
const val AM_QUICK_DEFAULT = ""
|
||||||
|
|
||||||
fun from(tunnelConf: TunnelConf): TunnelConfig {
|
|
||||||
return with(tunnelConf) {
|
|
||||||
return TunnelConfig(
|
|
||||||
id,
|
|
||||||
tunName,
|
|
||||||
wgQuick,
|
|
||||||
tunnelNetworks.toMutableList(),
|
|
||||||
isMobileDataTunnel,
|
|
||||||
isPrimaryTunnel,
|
|
||||||
amQuick,
|
|
||||||
isActive,
|
|
||||||
isPingEnabled,
|
|
||||||
pingInterval,
|
|
||||||
pingCooldown,
|
|
||||||
pingIp,
|
|
||||||
isEthernetTunnel,
|
|
||||||
isIpv4Preferred,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+37
@@ -0,0 +1,37 @@
|
|||||||
|
package com.zaneschepke.wireguardautotunnel.data.mapper
|
||||||
|
|
||||||
|
import com.zaneschepke.wireguardautotunnel.data.entity.GeneralState
|
||||||
|
import com.zaneschepke.wireguardautotunnel.domain.model.AppState
|
||||||
|
|
||||||
|
object GeneralStateMapper {
|
||||||
|
fun toAppState(generalState: GeneralState): AppState =
|
||||||
|
with(generalState) {
|
||||||
|
AppState(
|
||||||
|
isLocationDisclosureShown,
|
||||||
|
isBatteryOptimizationDisableShown,
|
||||||
|
isPinLockEnabled,
|
||||||
|
expandedTunnelIds,
|
||||||
|
isLocalLogsEnabled,
|
||||||
|
isRemoteControlEnabled,
|
||||||
|
remoteKey,
|
||||||
|
locale,
|
||||||
|
theme,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun toGeneralState(appState: AppState): GeneralState {
|
||||||
|
return with(appState) {
|
||||||
|
GeneralState(
|
||||||
|
isLocationDisclosureShown,
|
||||||
|
isBatteryOptimizationDisableShown,
|
||||||
|
isPinLockEnabled,
|
||||||
|
expandedTunnelIds,
|
||||||
|
isLocalLogsEnabled,
|
||||||
|
isRemoteControlEnabled,
|
||||||
|
remoteKey,
|
||||||
|
locale,
|
||||||
|
theme,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+20
@@ -0,0 +1,20 @@
|
|||||||
|
package com.zaneschepke.wireguardautotunnel.data.mapper
|
||||||
|
|
||||||
|
import com.zaneschepke.wireguardautotunnel.data.entity.GitHubRelease
|
||||||
|
import com.zaneschepke.wireguardautotunnel.domain.model.AppUpdate
|
||||||
|
import kotlin.collections.firstOrNull
|
||||||
|
|
||||||
|
object GitHubReleaseMapper {
|
||||||
|
fun toAppUpdate(gitHubRelease: GitHubRelease, newVersion: String): AppUpdate {
|
||||||
|
with(gitHubRelease) {
|
||||||
|
val apkAsset = assets.firstOrNull { it.name.endsWith(".apk") }
|
||||||
|
return AppUpdate(
|
||||||
|
version = newVersion,
|
||||||
|
title = name ?: "Update $tagName",
|
||||||
|
releaseNotes = body ?: "No release notes provided",
|
||||||
|
apkUrl = apkAsset?.browserDownloadUrl,
|
||||||
|
apkFileName = apkAsset?.name,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
package com.zaneschepke.wireguardautotunnel.data.mapper
|
||||||
|
|
||||||
|
import com.zaneschepke.networkmonitor.AndroidNetworkMonitor
|
||||||
|
import com.zaneschepke.wireguardautotunnel.data.entity.Settings
|
||||||
|
import com.zaneschepke.wireguardautotunnel.domain.model.AppSettings
|
||||||
|
|
||||||
|
object SettingsMapper {
|
||||||
|
fun toAppSettings(settings: Settings): AppSettings {
|
||||||
|
return AppSettings(
|
||||||
|
id = settings.id,
|
||||||
|
isAutoTunnelEnabled = settings.isAutoTunnelEnabled,
|
||||||
|
isTunnelOnMobileDataEnabled = settings.isTunnelOnMobileDataEnabled,
|
||||||
|
trustedNetworkSSIDs = settings.trustedNetworkSSIDs,
|
||||||
|
isAlwaysOnVpnEnabled = settings.isAlwaysOnVpnEnabled,
|
||||||
|
isTunnelOnEthernetEnabled = settings.isTunnelOnEthernetEnabled,
|
||||||
|
isShortcutsEnabled = settings.isShortcutsEnabled,
|
||||||
|
isTunnelOnWifiEnabled = settings.isTunnelOnWifiEnabled,
|
||||||
|
isKernelEnabled = settings.isKernelEnabled,
|
||||||
|
isRestoreOnBootEnabled = settings.isRestoreOnBootEnabled,
|
||||||
|
isMultiTunnelEnabled = settings.isMultiTunnelEnabled,
|
||||||
|
isPingEnabled = settings.isPingEnabled,
|
||||||
|
isAmneziaEnabled = settings.isAmneziaEnabled,
|
||||||
|
isWildcardsEnabled = settings.isWildcardsEnabled,
|
||||||
|
isStopOnNoInternetEnabled = settings.isStopOnNoInternetEnabled,
|
||||||
|
isVpnKillSwitchEnabled = settings.isVpnKillSwitchEnabled,
|
||||||
|
isKernelKillSwitchEnabled = settings.isKernelKillSwitchEnabled,
|
||||||
|
isLanOnKillSwitchEnabled = settings.isLanOnKillSwitchEnabled,
|
||||||
|
debounceDelaySeconds = settings.debounceDelaySeconds,
|
||||||
|
isDisableKillSwitchOnTrustedEnabled = settings.isDisableKillSwitchOnTrustedEnabled,
|
||||||
|
isTunnelOnUnsecureEnabled = settings.isTunnelOnUnsecureEnabled,
|
||||||
|
splitTunnelApps = settings.splitTunnelApps,
|
||||||
|
wifiDetectionMethod =
|
||||||
|
AndroidNetworkMonitor.WifiDetectionMethod.fromValue(
|
||||||
|
settings.wifiDetectionMethod.value
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun toSettings(appSettings: AppSettings): Settings {
|
||||||
|
return Settings(
|
||||||
|
id = appSettings.id,
|
||||||
|
isAutoTunnelEnabled = appSettings.isAutoTunnelEnabled,
|
||||||
|
isTunnelOnMobileDataEnabled = appSettings.isTunnelOnMobileDataEnabled,
|
||||||
|
trustedNetworkSSIDs = appSettings.trustedNetworkSSIDs.toMutableList(),
|
||||||
|
isAlwaysOnVpnEnabled = appSettings.isAlwaysOnVpnEnabled,
|
||||||
|
isTunnelOnEthernetEnabled = appSettings.isTunnelOnEthernetEnabled,
|
||||||
|
isShortcutsEnabled = appSettings.isShortcutsEnabled,
|
||||||
|
isTunnelOnWifiEnabled = appSettings.isTunnelOnWifiEnabled,
|
||||||
|
isKernelEnabled = appSettings.isKernelEnabled,
|
||||||
|
isRestoreOnBootEnabled = appSettings.isRestoreOnBootEnabled,
|
||||||
|
isMultiTunnelEnabled = appSettings.isMultiTunnelEnabled,
|
||||||
|
isPingEnabled = appSettings.isPingEnabled,
|
||||||
|
isAmneziaEnabled = appSettings.isAmneziaEnabled,
|
||||||
|
isWildcardsEnabled = appSettings.isWildcardsEnabled,
|
||||||
|
isStopOnNoInternetEnabled = appSettings.isStopOnNoInternetEnabled,
|
||||||
|
isVpnKillSwitchEnabled = appSettings.isVpnKillSwitchEnabled,
|
||||||
|
isKernelKillSwitchEnabled = appSettings.isKernelKillSwitchEnabled,
|
||||||
|
isLanOnKillSwitchEnabled = appSettings.isLanOnKillSwitchEnabled,
|
||||||
|
debounceDelaySeconds = appSettings.debounceDelaySeconds,
|
||||||
|
isDisableKillSwitchOnTrustedEnabled = appSettings.isDisableKillSwitchOnTrustedEnabled,
|
||||||
|
isTunnelOnUnsecureEnabled = appSettings.isTunnelOnUnsecureEnabled,
|
||||||
|
splitTunnelApps = appSettings.splitTunnelApps.toMutableList(),
|
||||||
|
wifiDetectionMethod =
|
||||||
|
Settings.WifiDetectionMethod.fromValue(appSettings.wifiDetectionMethod.value),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
+48
@@ -0,0 +1,48 @@
|
|||||||
|
package com.zaneschepke.wireguardautotunnel.data.mapper
|
||||||
|
|
||||||
|
import com.zaneschepke.wireguardautotunnel.data.entity.TunnelConfig
|
||||||
|
import com.zaneschepke.wireguardautotunnel.domain.model.TunnelConf
|
||||||
|
|
||||||
|
object TunnelConfigMapper {
|
||||||
|
fun toTunnelConf(tunnelConfig: TunnelConfig): TunnelConf {
|
||||||
|
return with(tunnelConfig) {
|
||||||
|
TunnelConf(
|
||||||
|
id,
|
||||||
|
name,
|
||||||
|
wgQuick,
|
||||||
|
tunnelNetworks,
|
||||||
|
isMobileDataTunnel,
|
||||||
|
isPrimaryTunnel,
|
||||||
|
amQuick,
|
||||||
|
isActive,
|
||||||
|
isPingEnabled,
|
||||||
|
pingInterval,
|
||||||
|
pingCooldown,
|
||||||
|
pingIp,
|
||||||
|
isEthernetTunnel,
|
||||||
|
isIpv4Preferred,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun toTunnelConfig(tunnelConf: TunnelConf): TunnelConfig {
|
||||||
|
return with(tunnelConf) {
|
||||||
|
TunnelConfig(
|
||||||
|
id,
|
||||||
|
tunName,
|
||||||
|
wgQuick,
|
||||||
|
tunnelNetworks.toMutableList(),
|
||||||
|
isMobileDataTunnel,
|
||||||
|
isPrimaryTunnel,
|
||||||
|
amQuick,
|
||||||
|
isActive,
|
||||||
|
isPingEnabled,
|
||||||
|
pingInterval,
|
||||||
|
pingCooldown,
|
||||||
|
pingIp,
|
||||||
|
isEthernetTunnel,
|
||||||
|
isIpv4Preferred,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,54 +0,0 @@
|
|||||||
package com.zaneschepke.wireguardautotunnel.data.model
|
|
||||||
|
|
||||||
import com.zaneschepke.wireguardautotunnel.domain.entity.AppState
|
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.theme.Theme
|
|
||||||
|
|
||||||
data class GeneralState(
|
|
||||||
val isLocationDisclosureShown: Boolean = LOCATION_DISCLOSURE_SHOWN_DEFAULT,
|
|
||||||
val isBatteryOptimizationDisableShown: Boolean = BATTERY_OPTIMIZATION_DISABLE_SHOWN_DEFAULT,
|
|
||||||
val isPinLockEnabled: Boolean = PIN_LOCK_ENABLED_DEFAULT,
|
|
||||||
val expandedTunnelIds: List<Int> = emptyList(),
|
|
||||||
val isLocalLogsEnabled: Boolean = IS_LOGS_ENABLED_DEFAULT,
|
|
||||||
val isRemoteControlEnabled: Boolean = IS_REMOTE_CONTROL_ENABLED,
|
|
||||||
val remoteKey: String? = null,
|
|
||||||
val locale: String? = null,
|
|
||||||
val theme: Theme = Theme.AUTOMATIC,
|
|
||||||
) {
|
|
||||||
|
|
||||||
fun toAppState(): AppState =
|
|
||||||
AppState(
|
|
||||||
isLocationDisclosureShown,
|
|
||||||
isBatteryOptimizationDisableShown,
|
|
||||||
isPinLockEnabled,
|
|
||||||
expandedTunnelIds,
|
|
||||||
isLocalLogsEnabled,
|
|
||||||
isRemoteControlEnabled,
|
|
||||||
remoteKey,
|
|
||||||
locale,
|
|
||||||
theme,
|
|
||||||
)
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
fun from(appState: AppState): GeneralState {
|
|
||||||
return with(appState) {
|
|
||||||
GeneralState(
|
|
||||||
isLocationDisclosureShown,
|
|
||||||
isBatteryOptimizationDisableShown,
|
|
||||||
isPinLockEnabled,
|
|
||||||
expandedTunnelIds,
|
|
||||||
isLocalLogsEnabled,
|
|
||||||
isRemoteControlEnabled,
|
|
||||||
remoteKey,
|
|
||||||
locale,
|
|
||||||
theme,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const val LOCATION_DISCLOSURE_SHOWN_DEFAULT = false
|
|
||||||
const val BATTERY_OPTIMIZATION_DISABLE_SHOWN_DEFAULT = false
|
|
||||||
const val PIN_LOCK_ENABLED_DEFAULT = false
|
|
||||||
const val IS_LOGS_ENABLED_DEFAULT = false
|
|
||||||
const val IS_REMOTE_CONTROL_ENABLED = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
package com.zaneschepke.wireguardautotunnel.data.model
|
|
||||||
|
|
||||||
import com.zaneschepke.wireguardautotunnel.domain.entity.AppUpdate
|
|
||||||
import kotlinx.serialization.SerialName
|
|
||||||
import kotlinx.serialization.Serializable
|
|
||||||
|
|
||||||
@Serializable
|
|
||||||
data class GitHubRelease(
|
|
||||||
@SerialName("tag_name") val tagName: String,
|
|
||||||
val name: String?,
|
|
||||||
val body: String?,
|
|
||||||
val assets: List<Asset>,
|
|
||||||
) {
|
|
||||||
fun toAppUpdate(): AppUpdate {
|
|
||||||
val apkAsset = assets.firstOrNull { it.name.endsWith(".apk") }
|
|
||||||
return AppUpdate(
|
|
||||||
version = tagName.removePrefix("v"),
|
|
||||||
title = name ?: "Update $tagName",
|
|
||||||
releaseNotes = body ?: "No release notes provided",
|
|
||||||
apkUrl = apkAsset?.browserDownloadUrl,
|
|
||||||
apkFileName = apkAsset?.name,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
package com.zaneschepke.wireguardautotunnel.data.network
|
package com.zaneschepke.wireguardautotunnel.data.network
|
||||||
|
|
||||||
import com.zaneschepke.wireguardautotunnel.data.model.GitHubRelease
|
import com.zaneschepke.wireguardautotunnel.data.entity.GitHubRelease
|
||||||
|
|
||||||
interface GitHubApi {
|
interface GitHubApi {
|
||||||
suspend fun getLatestRelease(owner: String, repo: String): Result<GitHubRelease>
|
suspend fun getLatestRelease(owner: String, repo: String): Result<GitHubRelease>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
package com.zaneschepke.wireguardautotunnel.data.network
|
package com.zaneschepke.wireguardautotunnel.data.network
|
||||||
|
|
||||||
import com.zaneschepke.wireguardautotunnel.data.model.GitHubRelease
|
import com.zaneschepke.wireguardautotunnel.data.entity.GitHubRelease
|
||||||
import io.ktor.client.HttpClient
|
import io.ktor.client.HttpClient
|
||||||
import io.ktor.client.call.body
|
import io.ktor.client.call.body
|
||||||
import io.ktor.client.plugins.ClientRequestException
|
import io.ktor.client.plugins.ClientRequestException
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
package com.zaneschepke.wireguardautotunnel.data.repository
|
package com.zaneschepke.wireguardautotunnel.data.repository
|
||||||
|
|
||||||
import com.zaneschepke.wireguardautotunnel.domain.entity.TunnelConf
|
import com.zaneschepke.wireguardautotunnel.domain.model.TunnelConf
|
||||||
import com.zaneschepke.wireguardautotunnel.domain.repository.AppDataRepository
|
import com.zaneschepke.wireguardautotunnel.domain.repository.AppDataRepository
|
||||||
import com.zaneschepke.wireguardautotunnel.domain.repository.AppSettingRepository
|
import com.zaneschepke.wireguardautotunnel.domain.repository.AppSettingRepository
|
||||||
import com.zaneschepke.wireguardautotunnel.domain.repository.AppStateRepository
|
import com.zaneschepke.wireguardautotunnel.domain.repository.AppStateRepository
|
||||||
|
|||||||
+4
-3
@@ -1,8 +1,9 @@
|
|||||||
package com.zaneschepke.wireguardautotunnel.data.repository
|
package com.zaneschepke.wireguardautotunnel.data.repository
|
||||||
|
|
||||||
import com.zaneschepke.wireguardautotunnel.data.DataStoreManager
|
import com.zaneschepke.wireguardautotunnel.data.DataStoreManager
|
||||||
import com.zaneschepke.wireguardautotunnel.data.model.GeneralState
|
import com.zaneschepke.wireguardautotunnel.data.entity.GeneralState
|
||||||
import com.zaneschepke.wireguardautotunnel.domain.entity.AppState
|
import com.zaneschepke.wireguardautotunnel.data.mapper.GeneralStateMapper
|
||||||
|
import com.zaneschepke.wireguardautotunnel.domain.model.AppState
|
||||||
import com.zaneschepke.wireguardautotunnel.domain.repository.AppStateRepository
|
import com.zaneschepke.wireguardautotunnel.domain.repository.AppStateRepository
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.theme.Theme
|
import com.zaneschepke.wireguardautotunnel.ui.theme.Theme
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
@@ -153,5 +154,5 @@ class DataStoreAppStateRepository(private val dataStoreManager: DataStoreManager
|
|||||||
}
|
}
|
||||||
} ?: GeneralState()
|
} ?: GeneralState()
|
||||||
}
|
}
|
||||||
.map { it.toAppState() }
|
.map(GeneralStateMapper::toAppState)
|
||||||
}
|
}
|
||||||
|
|||||||
+16
-8
@@ -2,10 +2,12 @@ package com.zaneschepke.wireguardautotunnel.data.repository
|
|||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import com.zaneschepke.wireguardautotunnel.BuildConfig
|
import com.zaneschepke.wireguardautotunnel.BuildConfig
|
||||||
|
import com.zaneschepke.wireguardautotunnel.data.mapper.GitHubReleaseMapper
|
||||||
import com.zaneschepke.wireguardautotunnel.data.network.GitHubApi
|
import com.zaneschepke.wireguardautotunnel.data.network.GitHubApi
|
||||||
import com.zaneschepke.wireguardautotunnel.di.IoDispatcher
|
import com.zaneschepke.wireguardautotunnel.di.IoDispatcher
|
||||||
import com.zaneschepke.wireguardautotunnel.domain.entity.AppUpdate
|
import com.zaneschepke.wireguardautotunnel.domain.model.AppUpdate
|
||||||
import com.zaneschepke.wireguardautotunnel.domain.repository.UpdateRepository
|
import com.zaneschepke.wireguardautotunnel.domain.repository.UpdateRepository
|
||||||
|
import com.zaneschepke.wireguardautotunnel.util.Constants
|
||||||
import com.zaneschepke.wireguardautotunnel.util.NumberUtils
|
import com.zaneschepke.wireguardautotunnel.util.NumberUtils
|
||||||
import io.ktor.client.HttpClient
|
import io.ktor.client.HttpClient
|
||||||
import io.ktor.client.request.get
|
import io.ktor.client.request.get
|
||||||
@@ -30,24 +32,30 @@ class GitHubUpdateRepository(
|
|||||||
override suspend fun checkForUpdate(currentVersion: String): Result<AppUpdate?> =
|
override suspend fun checkForUpdate(currentVersion: String): Result<AppUpdate?> =
|
||||||
withContext(ioDispatcher) {
|
withContext(ioDispatcher) {
|
||||||
Timber.i("Checking for update")
|
Timber.i("Checking for update")
|
||||||
|
val isNightly = BuildConfig.VERSION_NAME.contains("nightly")
|
||||||
val release =
|
val release =
|
||||||
if (BuildConfig.VERSION_NAME.contains("nightly")) {
|
if (isNightly) {
|
||||||
gitHubApi.getNightlyRelease(githubOwner, githubRepo)
|
gitHubApi.getNightlyRelease(githubOwner, githubRepo).onFailure(Timber::e)
|
||||||
} else {
|
} else {
|
||||||
gitHubApi.getLatestRelease(githubOwner, githubRepo)
|
gitHubApi.getLatestRelease(githubOwner, githubRepo).onFailure(Timber::e)
|
||||||
}
|
}
|
||||||
release.map { release ->
|
release.map { release ->
|
||||||
val apkAsset =
|
val apkAsset =
|
||||||
release.assets.find { asset ->
|
release.assets.find { asset ->
|
||||||
asset.name.startsWith("wgtunnel-full-v") && asset.name.endsWith(".apk")
|
asset.name.startsWith("wgtunnel-${Constants.STANDALONE_FLAVOR}-v") &&
|
||||||
|
asset.name.endsWith(".apk")
|
||||||
}
|
}
|
||||||
val newVersion =
|
val newVersion =
|
||||||
apkAsset?.name?.removePrefix("wgtunnel-full-v")?.removeSuffix(".apk")
|
apkAsset
|
||||||
?: return@map null
|
?.name
|
||||||
|
?.removePrefix("wgtunnel-${Constants.STANDALONE_FLAVOR}-v")
|
||||||
|
?.removeSuffix(".apk") ?: return@map null
|
||||||
|
|
||||||
Timber.i("Latest version: $newVersion, current version: $currentVersion")
|
Timber.i("Latest version: $newVersion, current version: $currentVersion")
|
||||||
|
if (isNightly && newVersion != currentVersion)
|
||||||
|
return@map GitHubReleaseMapper.toAppUpdate(release, newVersion)
|
||||||
if (NumberUtils.compareVersions(newVersion, currentVersion) > 0) {
|
if (NumberUtils.compareVersions(newVersion, currentVersion) > 0) {
|
||||||
release.toAppUpdate()
|
GitHubReleaseMapper.toAppUpdate(release, newVersion)
|
||||||
} else {
|
} else {
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
|
|||||||
+6
-5
@@ -1,9 +1,10 @@
|
|||||||
package com.zaneschepke.wireguardautotunnel.data.repository
|
package com.zaneschepke.wireguardautotunnel.data.repository
|
||||||
|
|
||||||
import com.zaneschepke.wireguardautotunnel.data.dao.SettingsDao
|
import com.zaneschepke.wireguardautotunnel.data.dao.SettingsDao
|
||||||
import com.zaneschepke.wireguardautotunnel.data.model.Settings
|
import com.zaneschepke.wireguardautotunnel.data.entity.Settings
|
||||||
|
import com.zaneschepke.wireguardautotunnel.data.mapper.SettingsMapper
|
||||||
import com.zaneschepke.wireguardautotunnel.di.IoDispatcher
|
import com.zaneschepke.wireguardautotunnel.di.IoDispatcher
|
||||||
import com.zaneschepke.wireguardautotunnel.domain.entity.AppSettings
|
import com.zaneschepke.wireguardautotunnel.domain.model.AppSettings
|
||||||
import com.zaneschepke.wireguardautotunnel.domain.repository.AppSettingRepository
|
import com.zaneschepke.wireguardautotunnel.domain.repository.AppSettingRepository
|
||||||
import kotlinx.coroutines.CoroutineDispatcher
|
import kotlinx.coroutines.CoroutineDispatcher
|
||||||
import kotlinx.coroutines.flow.flowOn
|
import kotlinx.coroutines.flow.flowOn
|
||||||
@@ -16,15 +17,15 @@ class RoomSettingsRepository(
|
|||||||
) : AppSettingRepository {
|
) : AppSettingRepository {
|
||||||
|
|
||||||
override suspend fun save(appSettings: AppSettings) {
|
override suspend fun save(appSettings: AppSettings) {
|
||||||
withContext(ioDispatcher) { settingsDoa.save(Settings.from(appSettings)) }
|
withContext(ioDispatcher) { settingsDoa.save(SettingsMapper.toSettings(appSettings)) }
|
||||||
}
|
}
|
||||||
|
|
||||||
override val flow =
|
override val flow =
|
||||||
settingsDoa.getSettingsFlow().flowOn(ioDispatcher).map { it.toAppSettings() }
|
settingsDoa.getSettingsFlow().flowOn(ioDispatcher).map(SettingsMapper::toAppSettings)
|
||||||
|
|
||||||
override suspend fun get(): AppSettings {
|
override suspend fun get(): AppSettings {
|
||||||
return withContext(ioDispatcher) {
|
return withContext(ioDispatcher) {
|
||||||
(settingsDoa.getAll().firstOrNull() ?: Settings()).toAppSettings()
|
SettingsMapper.toAppSettings(settingsDoa.getAll().firstOrNull() ?: Settings())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+29
-13
@@ -1,9 +1,9 @@
|
|||||||
package com.zaneschepke.wireguardautotunnel.data.repository
|
package com.zaneschepke.wireguardautotunnel.data.repository
|
||||||
|
|
||||||
import com.zaneschepke.wireguardautotunnel.data.dao.TunnelConfigDao
|
import com.zaneschepke.wireguardautotunnel.data.dao.TunnelConfigDao
|
||||||
import com.zaneschepke.wireguardautotunnel.data.model.TunnelConfig
|
import com.zaneschepke.wireguardautotunnel.data.mapper.TunnelConfigMapper
|
||||||
import com.zaneschepke.wireguardautotunnel.di.IoDispatcher
|
import com.zaneschepke.wireguardautotunnel.di.IoDispatcher
|
||||||
import com.zaneschepke.wireguardautotunnel.domain.entity.TunnelConf
|
import com.zaneschepke.wireguardautotunnel.domain.model.TunnelConf
|
||||||
import com.zaneschepke.wireguardautotunnel.domain.repository.TunnelRepository
|
import com.zaneschepke.wireguardautotunnel.domain.repository.TunnelRepository
|
||||||
import com.zaneschepke.wireguardautotunnel.util.extensions.Tunnels
|
import com.zaneschepke.wireguardautotunnel.util.extensions.Tunnels
|
||||||
import kotlinx.coroutines.CoroutineDispatcher
|
import kotlinx.coroutines.CoroutineDispatcher
|
||||||
@@ -17,19 +17,25 @@ class RoomTunnelRepository(
|
|||||||
) : TunnelRepository {
|
) : TunnelRepository {
|
||||||
|
|
||||||
override val flow =
|
override val flow =
|
||||||
tunnelConfigDao.getAllFlow().flowOn(ioDispatcher).map { it.map { it.toTunnel() } }
|
tunnelConfigDao.getAllFlow().flowOn(ioDispatcher).map {
|
||||||
|
it.map(TunnelConfigMapper::toTunnelConf)
|
||||||
|
}
|
||||||
|
|
||||||
override suspend fun getAll(): Tunnels {
|
override suspend fun getAll(): Tunnels {
|
||||||
return withContext(ioDispatcher) { tunnelConfigDao.getAll().map { it.toTunnel() } }
|
return withContext(ioDispatcher) {
|
||||||
|
tunnelConfigDao.getAll().map(TunnelConfigMapper::toTunnelConf)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun save(tunnelConf: TunnelConf) {
|
override suspend fun save(tunnelConf: TunnelConf) {
|
||||||
withContext(ioDispatcher) { tunnelConfigDao.save(TunnelConfig.from(tunnelConf)) }
|
withContext(ioDispatcher) {
|
||||||
|
tunnelConfigDao.save(TunnelConfigMapper.toTunnelConfig(tunnelConf))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun saveAll(tunnelConfList: List<TunnelConf>) {
|
override suspend fun saveAll(tunnelConfList: List<TunnelConf>) {
|
||||||
withContext(ioDispatcher) {
|
withContext(ioDispatcher) {
|
||||||
tunnelConfigDao.saveAll(tunnelConfList.map(TunnelConfig::from))
|
tunnelConfigDao.saveAll(tunnelConfList.map(TunnelConfigMapper::toTunnelConfig))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -55,15 +61,21 @@ class RoomTunnelRepository(
|
|||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun delete(tunnelConf: TunnelConf) {
|
override suspend fun delete(tunnelConf: TunnelConf) {
|
||||||
withContext(ioDispatcher) { tunnelConfigDao.delete(TunnelConfig.from(tunnelConf)) }
|
withContext(ioDispatcher) {
|
||||||
|
tunnelConfigDao.delete(TunnelConfigMapper.toTunnelConfig(tunnelConf))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun getById(id: Int): TunnelConf? {
|
override suspend fun getById(id: Int): TunnelConf? {
|
||||||
return withContext(ioDispatcher) { tunnelConfigDao.getById(id.toLong())?.toTunnel() }
|
return withContext(ioDispatcher) {
|
||||||
|
tunnelConfigDao.getById(id.toLong())?.let(TunnelConfigMapper::toTunnelConf)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun getActive(): Tunnels {
|
override suspend fun getActive(): Tunnels {
|
||||||
return withContext(ioDispatcher) { tunnelConfigDao.getActive().map { it.toTunnel() } }
|
return withContext(ioDispatcher) {
|
||||||
|
tunnelConfigDao.getActive().map(TunnelConfigMapper::toTunnelConf)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun count(): Int {
|
override suspend fun count(): Int {
|
||||||
@@ -71,22 +83,26 @@ class RoomTunnelRepository(
|
|||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun findByTunnelName(name: String): TunnelConf? {
|
override suspend fun findByTunnelName(name: String): TunnelConf? {
|
||||||
return withContext(ioDispatcher) { tunnelConfigDao.getByName(name)?.toTunnel() }
|
return withContext(ioDispatcher) {
|
||||||
|
tunnelConfigDao.getByName(name)?.let(TunnelConfigMapper::toTunnelConf)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun findByTunnelNetworksName(name: String): Tunnels {
|
override suspend fun findByTunnelNetworksName(name: String): Tunnels {
|
||||||
return withContext(ioDispatcher) {
|
return withContext(ioDispatcher) {
|
||||||
tunnelConfigDao.findByTunnelNetworkName(name).map { it.toTunnel() }
|
tunnelConfigDao.findByTunnelNetworkName(name).map(TunnelConfigMapper::toTunnelConf)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun findByMobileDataTunnel(): Tunnels {
|
override suspend fun findByMobileDataTunnel(): Tunnels {
|
||||||
return withContext(ioDispatcher) {
|
return withContext(ioDispatcher) {
|
||||||
tunnelConfigDao.findByMobileDataTunnel().map { it.toTunnel() }
|
tunnelConfigDao.findByMobileDataTunnel().map(TunnelConfigMapper::toTunnelConf)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun findPrimary(): Tunnels {
|
override suspend fun findPrimary(): Tunnels {
|
||||||
return withContext(ioDispatcher) { tunnelConfigDao.findByPrimary().map { it.toTunnel() } }
|
return withContext(ioDispatcher) {
|
||||||
|
tunnelConfigDao.findByPrimary().map(TunnelConfigMapper::toTunnelConf)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,7 +21,9 @@ import dagger.hilt.components.SingletonComponent
|
|||||||
import javax.inject.Singleton
|
import javax.inject.Singleton
|
||||||
import kotlinx.coroutines.CoroutineDispatcher
|
import kotlinx.coroutines.CoroutineDispatcher
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.distinctUntilChangedBy
|
||||||
|
import kotlinx.coroutines.flow.map
|
||||||
import org.amnezia.awg.backend.Backend
|
import org.amnezia.awg.backend.Backend
|
||||||
import org.amnezia.awg.backend.GoBackend
|
import org.amnezia.awg.backend.GoBackend
|
||||||
import org.amnezia.awg.backend.RootTunnelActionHandler
|
import org.amnezia.awg.backend.RootTunnelActionHandler
|
||||||
@@ -112,10 +114,23 @@ class TunnelModule {
|
|||||||
fun provideNetworkMonitor(
|
fun provideNetworkMonitor(
|
||||||
@ApplicationContext context: Context,
|
@ApplicationContext context: Context,
|
||||||
settingsRepository: AppSettingRepository,
|
settingsRepository: AppSettingRepository,
|
||||||
|
@ApplicationScope applicationScope: CoroutineScope,
|
||||||
|
@AppShell appShell: RootShell,
|
||||||
): NetworkMonitor {
|
): NetworkMonitor {
|
||||||
return AndroidNetworkMonitor(context) {
|
return AndroidNetworkMonitor(
|
||||||
runBlocking { settingsRepository.get().isWifiNameByShellEnabled }
|
context,
|
||||||
}
|
object : AndroidNetworkMonitor.ConfigurationListener {
|
||||||
|
override val detectionMethod: Flow<AndroidNetworkMonitor.WifiDetectionMethod>
|
||||||
|
get() =
|
||||||
|
settingsRepository.flow
|
||||||
|
.distinctUntilChangedBy { it.wifiDetectionMethod }
|
||||||
|
.map { it.wifiDetectionMethod }
|
||||||
|
|
||||||
|
override val rootShell: RootShell
|
||||||
|
get() = appShell
|
||||||
|
},
|
||||||
|
applicationScope,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Singleton
|
@Singleton
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
package com.zaneschepke.wireguardautotunnel.domain.enums
|
package com.zaneschepke.wireguardautotunnel.domain.enums
|
||||||
|
|
||||||
enum class ConfigType {
|
enum class ConfigType {
|
||||||
AMNEZIA,
|
AM,
|
||||||
WG,
|
WG,
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
package com.zaneschepke.wireguardautotunnel.domain.events
|
package com.zaneschepke.wireguardautotunnel.domain.events
|
||||||
|
|
||||||
import com.zaneschepke.wireguardautotunnel.domain.entity.TunnelConf
|
import com.zaneschepke.wireguardautotunnel.domain.model.TunnelConf
|
||||||
|
|
||||||
sealed class AutoTunnelEvent {
|
sealed class AutoTunnelEvent {
|
||||||
data class Start(val tunnelConf: TunnelConf? = null) : AutoTunnelEvent()
|
data class Start(val tunnelConf: TunnelConf? = null) : AutoTunnelEvent()
|
||||||
|
|||||||
+19
-2
@@ -1,4 +1,6 @@
|
|||||||
package com.zaneschepke.wireguardautotunnel.domain.entity
|
package com.zaneschepke.wireguardautotunnel.domain.model
|
||||||
|
|
||||||
|
import com.zaneschepke.networkmonitor.AndroidNetworkMonitor
|
||||||
|
|
||||||
data class AppSettings(
|
data class AppSettings(
|
||||||
val id: Int = 0,
|
val id: Int = 0,
|
||||||
@@ -15,15 +17,30 @@ data class AppSettings(
|
|||||||
val isPingEnabled: Boolean = false,
|
val isPingEnabled: Boolean = false,
|
||||||
val isAmneziaEnabled: Boolean = false,
|
val isAmneziaEnabled: Boolean = false,
|
||||||
val isWildcardsEnabled: Boolean = false,
|
val isWildcardsEnabled: Boolean = false,
|
||||||
val isWifiNameByShellEnabled: Boolean = false,
|
|
||||||
val isStopOnNoInternetEnabled: Boolean = false,
|
val isStopOnNoInternetEnabled: Boolean = false,
|
||||||
val isVpnKillSwitchEnabled: Boolean = false,
|
val isVpnKillSwitchEnabled: Boolean = false,
|
||||||
val isKernelKillSwitchEnabled: Boolean = false,
|
val isKernelKillSwitchEnabled: Boolean = false,
|
||||||
val isLanOnKillSwitchEnabled: Boolean = false,
|
val isLanOnKillSwitchEnabled: Boolean = false,
|
||||||
val debounceDelaySeconds: Int = 3,
|
val debounceDelaySeconds: Int = 3,
|
||||||
val isDisableKillSwitchOnTrustedEnabled: Boolean = false,
|
val isDisableKillSwitchOnTrustedEnabled: Boolean = false,
|
||||||
|
val isTunnelOnUnsecureEnabled: Boolean = false,
|
||||||
|
val splitTunnelApps: List<String> = emptyList(),
|
||||||
|
val wifiDetectionMethod: AndroidNetworkMonitor.WifiDetectionMethod =
|
||||||
|
AndroidNetworkMonitor.WifiDetectionMethod.DEFAULT,
|
||||||
) {
|
) {
|
||||||
fun debounceDelayMillis(): Long {
|
fun debounceDelayMillis(): Long {
|
||||||
return debounceDelaySeconds * 1000L
|
return debounceDelaySeconds * 1000L
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun toAutoTunnelStateString(): String {
|
||||||
|
return """
|
||||||
|
TunnelOnWifi: $isTunnelOnWifiEnabled
|
||||||
|
TunnelOnMobileData: $isTunnelOnMobileDataEnabled
|
||||||
|
TunnelOnEthernet: $isTunnelOnEthernetEnabled
|
||||||
|
Wildcards: $isWildcardsEnabled
|
||||||
|
StopOnNoInternet: $isStopOnNoInternetEnabled
|
||||||
|
Trusted Networks: $trustedNetworkSSIDs
|
||||||
|
"""
|
||||||
|
.trimIndent()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
package com.zaneschepke.wireguardautotunnel.domain.entity
|
package com.zaneschepke.wireguardautotunnel.domain.model
|
||||||
|
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.theme.Theme
|
import com.zaneschepke.wireguardautotunnel.ui.theme.Theme
|
||||||
|
|
||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
package com.zaneschepke.wireguardautotunnel.domain.entity
|
package com.zaneschepke.wireguardautotunnel.domain.model
|
||||||
|
|
||||||
data class AppUpdate(
|
data class AppUpdate(
|
||||||
val version: String,
|
val version: String,
|
||||||
+5
-4
@@ -1,4 +1,4 @@
|
|||||||
package com.zaneschepke.wireguardautotunnel.domain.entity
|
package com.zaneschepke.wireguardautotunnel.domain.model
|
||||||
|
|
||||||
import com.wireguard.android.backend.Tunnel
|
import com.wireguard.android.backend.Tunnel
|
||||||
import com.wireguard.config.Config
|
import com.wireguard.config.Config
|
||||||
@@ -26,7 +26,6 @@ data class TunnelConf(
|
|||||||
val pingIp: String? = null,
|
val pingIp: String? = null,
|
||||||
val isEthernetTunnel: Boolean = false,
|
val isEthernetTunnel: Boolean = false,
|
||||||
val isIpv4Preferred: Boolean = true,
|
val isIpv4Preferred: Boolean = true,
|
||||||
val useCache: Boolean = false,
|
|
||||||
@Transient private var stateChangeCallback: ((Any) -> Unit)? = null,
|
@Transient private var stateChangeCallback: ((Any) -> Unit)? = null,
|
||||||
) : Tunnel, org.amnezia.awg.backend.Tunnel {
|
) : Tunnel, org.amnezia.awg.backend.Tunnel {
|
||||||
|
|
||||||
@@ -60,6 +59,10 @@ data class TunnelConf(
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun isStaticallyConfigured(): Boolean {
|
||||||
|
return toAmConfig().peers.all { it.endpoint.get().host.isValidIpv4orIpv6Address() }
|
||||||
|
}
|
||||||
|
|
||||||
fun copyWithCallback(
|
fun copyWithCallback(
|
||||||
id: Int = this.id,
|
id: Int = this.id,
|
||||||
tunName: String = this.tunName,
|
tunName: String = this.tunName,
|
||||||
@@ -107,8 +110,6 @@ data class TunnelConf(
|
|||||||
|
|
||||||
override fun isIpv4ResolutionPreferred(): Boolean = isIpv4Preferred
|
override fun isIpv4ResolutionPreferred(): Boolean = isIpv4Preferred
|
||||||
|
|
||||||
override fun useCache(): Boolean = useCache
|
|
||||||
|
|
||||||
override fun onStateChange(newState: org.amnezia.awg.backend.Tunnel.State) {
|
override fun onStateChange(newState: org.amnezia.awg.backend.Tunnel.State) {
|
||||||
stateChangeCallback?.invoke(newState)
|
stateChangeCallback?.invoke(newState)
|
||||||
}
|
}
|
||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
package com.zaneschepke.wireguardautotunnel.domain.repository
|
package com.zaneschepke.wireguardautotunnel.domain.repository
|
||||||
|
|
||||||
import com.zaneschepke.wireguardautotunnel.domain.entity.TunnelConf
|
import com.zaneschepke.wireguardautotunnel.domain.model.TunnelConf
|
||||||
|
|
||||||
interface AppDataRepository {
|
interface AppDataRepository {
|
||||||
suspend fun getPrimaryOrFirstTunnel(): TunnelConf?
|
suspend fun getPrimaryOrFirstTunnel(): TunnelConf?
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
package com.zaneschepke.wireguardautotunnel.domain.repository
|
package com.zaneschepke.wireguardautotunnel.domain.repository
|
||||||
|
|
||||||
import com.zaneschepke.wireguardautotunnel.domain.entity.AppSettings
|
import com.zaneschepke.wireguardautotunnel.domain.model.AppSettings
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
|
||||||
interface AppSettingRepository {
|
interface AppSettingRepository {
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
package com.zaneschepke.wireguardautotunnel.domain.repository
|
package com.zaneschepke.wireguardautotunnel.domain.repository
|
||||||
|
|
||||||
import com.zaneschepke.wireguardautotunnel.domain.entity.AppState
|
import com.zaneschepke.wireguardautotunnel.domain.model.AppState
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.theme.Theme
|
import com.zaneschepke.wireguardautotunnel.ui.theme.Theme
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
package com.zaneschepke.wireguardautotunnel.domain.repository
|
package com.zaneschepke.wireguardautotunnel.domain.repository
|
||||||
|
|
||||||
import com.zaneschepke.wireguardautotunnel.domain.entity.TunnelConf
|
import com.zaneschepke.wireguardautotunnel.domain.model.TunnelConf
|
||||||
import com.zaneschepke.wireguardautotunnel.util.extensions.Tunnels
|
import com.zaneschepke.wireguardautotunnel.util.extensions.Tunnels
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
package com.zaneschepke.wireguardautotunnel.domain.repository
|
package com.zaneschepke.wireguardautotunnel.domain.repository
|
||||||
|
|
||||||
import com.zaneschepke.wireguardautotunnel.domain.entity.AppUpdate
|
import com.zaneschepke.wireguardautotunnel.domain.model.AppUpdate
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
interface UpdateRepository {
|
interface UpdateRepository {
|
||||||
|
|||||||
+4
-2
@@ -3,10 +3,10 @@ package com.zaneschepke.wireguardautotunnel.domain.state
|
|||||||
import com.zaneschepke.wireguardautotunnel.core.tunnel.allDown
|
import com.zaneschepke.wireguardautotunnel.core.tunnel.allDown
|
||||||
import com.zaneschepke.wireguardautotunnel.core.tunnel.hasActive
|
import com.zaneschepke.wireguardautotunnel.core.tunnel.hasActive
|
||||||
import com.zaneschepke.wireguardautotunnel.core.tunnel.isUp
|
import com.zaneschepke.wireguardautotunnel.core.tunnel.isUp
|
||||||
import com.zaneschepke.wireguardautotunnel.domain.entity.AppSettings
|
|
||||||
import com.zaneschepke.wireguardautotunnel.domain.entity.TunnelConf
|
|
||||||
import com.zaneschepke.wireguardautotunnel.domain.events.AutoTunnelEvent
|
import com.zaneschepke.wireguardautotunnel.domain.events.AutoTunnelEvent
|
||||||
import com.zaneschepke.wireguardautotunnel.domain.events.KillSwitchEvent
|
import com.zaneschepke.wireguardautotunnel.domain.events.KillSwitchEvent
|
||||||
|
import com.zaneschepke.wireguardautotunnel.domain.model.AppSettings
|
||||||
|
import com.zaneschepke.wireguardautotunnel.domain.model.TunnelConf
|
||||||
import com.zaneschepke.wireguardautotunnel.util.extensions.isMatchingToWildcardList
|
import com.zaneschepke.wireguardautotunnel.util.extensions.isMatchingToWildcardList
|
||||||
|
|
||||||
data class AutoTunnelState(
|
data class AutoTunnelState(
|
||||||
@@ -16,6 +16,7 @@ data class AutoTunnelState(
|
|||||||
val tunnels: List<TunnelConf> = emptyList(),
|
val tunnels: List<TunnelConf> = emptyList(),
|
||||||
) {
|
) {
|
||||||
|
|
||||||
|
// also need to check for Wi-Fi state as there is some overlap when they are both connected
|
||||||
private fun isMobileDataActive(): Boolean {
|
private fun isMobileDataActive(): Boolean {
|
||||||
return !networkState.isEthernetConnected &&
|
return !networkState.isEthernetConnected &&
|
||||||
!networkState.isWifiConnected &&
|
!networkState.isWifiConnected &&
|
||||||
@@ -50,6 +51,7 @@ data class AutoTunnelState(
|
|||||||
return getTunnelWithMatchingTunnelNetwork() ?: tunnels.firstOrNull { it.isPrimaryTunnel }
|
return getTunnelWithMatchingTunnelNetwork() ?: tunnels.firstOrNull { it.isPrimaryTunnel }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ignore cellular state as there is overlap where it may still be active, but not prioritized
|
||||||
private fun isWifiActive(): Boolean {
|
private fun isWifiActive(): Boolean {
|
||||||
return !networkState.isEthernetConnected && networkState.isWifiConnected
|
return !networkState.isEthernetConnected && networkState.isWifiConnected
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,6 +13,8 @@ sealed class Route {
|
|||||||
|
|
||||||
@Serializable data object AutoTunnelAdvanced : Route()
|
@Serializable data object AutoTunnelAdvanced : Route()
|
||||||
|
|
||||||
|
@Serializable data object WifiDetectionMethod : Route()
|
||||||
|
|
||||||
@Serializable data object LocationDisclosure : Route()
|
@Serializable data object LocationDisclosure : Route()
|
||||||
|
|
||||||
@Serializable data object Appearance : Route()
|
@Serializable data object Appearance : Route()
|
||||||
@@ -29,8 +31,6 @@ sealed class Route {
|
|||||||
|
|
||||||
@Serializable data object Lock : Route()
|
@Serializable data object Lock : Route()
|
||||||
|
|
||||||
@Serializable data object Scanner : Route()
|
|
||||||
|
|
||||||
@Serializable data object License : Route()
|
@Serializable data object License : Route()
|
||||||
|
|
||||||
@Serializable data class Config(val id: Int) : Route()
|
@Serializable data class Config(val id: Int) : Route()
|
||||||
|
|||||||
+1
-1
@@ -73,7 +73,7 @@ fun IconSurfaceButton(
|
|||||||
else MaterialTheme.colorScheme.onSurface,
|
else MaterialTheme.colorScheme.onSurface,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
Column {
|
Column(verticalArrangement = Arrangement.spacedBy(4.dp)) {
|
||||||
Text(title, style = MaterialTheme.typography.titleMedium)
|
Text(title, style = MaterialTheme.typography.titleMedium)
|
||||||
description?.let {
|
description?.let {
|
||||||
Text(
|
Text(
|
||||||
|
|||||||
+20
-12
@@ -4,16 +4,7 @@ import android.os.Build
|
|||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.size
|
import androidx.compose.foundation.layout.size
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.rounded.Add
|
import androidx.compose.material.icons.rounded.*
|
||||||
import androidx.compose.material.icons.rounded.CopyAll
|
|
||||||
import androidx.compose.material.icons.rounded.Delete
|
|
||||||
import androidx.compose.material.icons.rounded.Download
|
|
||||||
import androidx.compose.material.icons.rounded.Edit
|
|
||||||
import androidx.compose.material.icons.rounded.Menu
|
|
||||||
import androidx.compose.material.icons.rounded.PlayArrow
|
|
||||||
import androidx.compose.material.icons.rounded.Save
|
|
||||||
import androidx.compose.material.icons.rounded.SelectAll
|
|
||||||
import androidx.compose.material.icons.rounded.Stop
|
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.IconButton
|
import androidx.compose.material3.IconButton
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
@@ -197,6 +188,14 @@ fun currentNavBackStackEntryAsNavBarState(
|
|||||||
route = Route.Display,
|
route = Route.Display,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
backStackEntry.isCurrentRoute(Route.WifiDetectionMethod::class) ->
|
||||||
|
NavBarState(
|
||||||
|
showTop = true,
|
||||||
|
showBottom = true,
|
||||||
|
topTitle = { Text(stringResource(R.string.wifi_detection_method)) },
|
||||||
|
route = Route.WifiDetectionMethod,
|
||||||
|
)
|
||||||
|
|
||||||
backStackEntry.isCurrentRoute(Route.KillSwitch::class) ->
|
backStackEntry.isCurrentRoute(Route.KillSwitch::class) ->
|
||||||
NavBarState(
|
NavBarState(
|
||||||
showTop = true,
|
showTop = true,
|
||||||
@@ -240,8 +239,17 @@ fun currentNavBackStackEntryAsNavBarState(
|
|||||||
showBottom = true,
|
showBottom = true,
|
||||||
topTitle = { tunnel?.name?.let { Text(it) } },
|
topTitle = { tunnel?.name?.let { Text(it) } },
|
||||||
topTrailing = {
|
topTrailing = {
|
||||||
ActionIconButton(Icons.Rounded.Edit, R.string.edit_tunnel) {
|
Row {
|
||||||
tunnel?.id?.let { navController.navigate(Route.Config(it)) }
|
ActionIconButton(Icons.Rounded.QrCode2, R.string.show_qr) {
|
||||||
|
tunnel?.id?.let {
|
||||||
|
viewModel.handleEvent(
|
||||||
|
AppEvent.SetShowModal(AppViewState.ModalType.QR)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ActionIconButton(Icons.Rounded.Edit, R.string.edit_tunnel) {
|
||||||
|
tunnel?.id?.let { navController.navigate(Route.Config(it)) }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
route = args?.let { Route.TunnelOptions(it.id) },
|
route = args?.let { Route.TunnelOptions(it.id) },
|
||||||
|
|||||||
+41
-41
@@ -1,18 +1,9 @@
|
|||||||
package com.zaneschepke.wireguardautotunnel.ui.screens.autotunnel.components
|
package com.zaneschepke.wireguardautotunnel.ui.screens.autotunnel.components
|
||||||
|
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.*
|
||||||
import androidx.compose.foundation.layout.Column
|
|
||||||
import androidx.compose.foundation.layout.Row
|
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
|
||||||
import androidx.compose.foundation.layout.padding
|
|
||||||
import androidx.compose.foundation.layout.size
|
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.outlined.Code
|
import androidx.compose.material.icons.outlined.*
|
||||||
import androidx.compose.material.icons.outlined.Filter1
|
|
||||||
import androidx.compose.material.icons.outlined.Security
|
|
||||||
import androidx.compose.material.icons.outlined.VpnKeyOff
|
|
||||||
import androidx.compose.material.icons.outlined.Wifi
|
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
@@ -26,14 +17,19 @@ import androidx.compose.ui.platform.LocalContext
|
|||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
import com.zaneschepke.networkmonitor.AndroidNetworkMonitor
|
||||||
import com.zaneschepke.networkmonitor.NetworkStatus
|
import com.zaneschepke.networkmonitor.NetworkStatus
|
||||||
import com.zaneschepke.wireguardautotunnel.R
|
import com.zaneschepke.wireguardautotunnel.R
|
||||||
|
import com.zaneschepke.wireguardautotunnel.ui.Route
|
||||||
|
import com.zaneschepke.wireguardautotunnel.ui.common.button.ForwardButton
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.ScaledSwitch
|
import com.zaneschepke.wireguardautotunnel.ui.common.button.ScaledSwitch
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SelectionItem
|
import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SelectionItem
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.common.functions.rememberClipboardHelper
|
import com.zaneschepke.wireguardautotunnel.ui.common.functions.rememberClipboardHelper
|
||||||
|
import com.zaneschepke.wireguardautotunnel.ui.navigation.LocalNavController
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.screens.settings.components.LearnMoreLinkLabel
|
import com.zaneschepke.wireguardautotunnel.ui.screens.settings.components.LearnMoreLinkLabel
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.state.AppUiState
|
import com.zaneschepke.wireguardautotunnel.ui.state.AppUiState
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.theme.iconSize
|
import com.zaneschepke.wireguardautotunnel.ui.theme.iconSize
|
||||||
|
import com.zaneschepke.wireguardautotunnel.util.extensions.asString
|
||||||
import com.zaneschepke.wireguardautotunnel.util.extensions.openWebUrl
|
import com.zaneschepke.wireguardautotunnel.util.extensions.openWebUrl
|
||||||
import com.zaneschepke.wireguardautotunnel.viewmodel.AppViewModel
|
import com.zaneschepke.wireguardautotunnel.viewmodel.AppViewModel
|
||||||
import com.zaneschepke.wireguardautotunnel.viewmodel.event.AppEvent
|
import com.zaneschepke.wireguardautotunnel.viewmodel.event.AppEvent
|
||||||
@@ -47,6 +43,7 @@ fun WifiTunnelingItems(
|
|||||||
isWifiNameReadable: () -> Boolean,
|
isWifiNameReadable: () -> Boolean,
|
||||||
): List<SelectionItem> {
|
): List<SelectionItem> {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
|
val navController = LocalNavController.current
|
||||||
val clipboardHelper = rememberClipboardHelper()
|
val clipboardHelper = rememberClipboardHelper()
|
||||||
|
|
||||||
val baseItems =
|
val baseItems =
|
||||||
@@ -107,40 +104,40 @@ fun WifiTunnelingItems(
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
onClick = { viewModel.handleEvent(AppEvent.ToggleAutoTunnelOnWifi) },
|
onClick = { viewModel.handleEvent(AppEvent.ToggleAutoTunnelOnWifi) },
|
||||||
),
|
)
|
||||||
SelectionItem(
|
|
||||||
leadingIcon = Icons.Outlined.Code,
|
|
||||||
title = {
|
|
||||||
Text(
|
|
||||||
stringResource(R.string.wifi_name_via_shell),
|
|
||||||
style =
|
|
||||||
MaterialTheme.typography.bodyMedium.copy(
|
|
||||||
MaterialTheme.colorScheme.onSurface
|
|
||||||
),
|
|
||||||
)
|
|
||||||
},
|
|
||||||
description = {
|
|
||||||
Text(
|
|
||||||
stringResource(R.string.use_root_shell_for_wifi),
|
|
||||||
style =
|
|
||||||
MaterialTheme.typography.bodySmall.copy(
|
|
||||||
MaterialTheme.colorScheme.outline
|
|
||||||
),
|
|
||||||
)
|
|
||||||
},
|
|
||||||
trailing = {
|
|
||||||
ScaledSwitch(
|
|
||||||
checked = uiState.appSettings.isWifiNameByShellEnabled,
|
|
||||||
onClick = { viewModel.handleEvent(AppEvent.ToggleRootShellWifi) },
|
|
||||||
)
|
|
||||||
},
|
|
||||||
onClick = { viewModel.handleEvent(AppEvent.ToggleRootShellWifi) },
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
return if (uiState.appSettings.isTunnelOnWifiEnabled) {
|
return if (uiState.appSettings.isTunnelOnWifiEnabled) {
|
||||||
baseItems +
|
baseItems +
|
||||||
listOf(
|
listOf(
|
||||||
|
SelectionItem(
|
||||||
|
leadingIcon = Icons.Outlined.WifiFind,
|
||||||
|
title = {
|
||||||
|
Text(
|
||||||
|
stringResource(R.string.wifi_detection_method),
|
||||||
|
style =
|
||||||
|
MaterialTheme.typography.bodyMedium.copy(
|
||||||
|
MaterialTheme.colorScheme.onSurface
|
||||||
|
),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
description = {
|
||||||
|
Text(
|
||||||
|
stringResource(
|
||||||
|
R.string.current_template,
|
||||||
|
uiState.appSettings.wifiDetectionMethod.asString(context),
|
||||||
|
),
|
||||||
|
style =
|
||||||
|
MaterialTheme.typography.bodySmall.copy(
|
||||||
|
MaterialTheme.colorScheme.outline
|
||||||
|
),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
trailing = {
|
||||||
|
ForwardButton { navController.navigate(Route.WifiDetectionMethod) }
|
||||||
|
},
|
||||||
|
onClick = { navController.navigate(Route.WifiDetectionMethod) },
|
||||||
|
),
|
||||||
SelectionItem(
|
SelectionItem(
|
||||||
leadingIcon = Icons.Outlined.Filter1,
|
leadingIcon = Icons.Outlined.Filter1,
|
||||||
title = {
|
title = {
|
||||||
@@ -205,7 +202,10 @@ fun WifiTunnelingItems(
|
|||||||
currentText = currentText,
|
currentText = currentText,
|
||||||
onSave = { ssid ->
|
onSave = { ssid ->
|
||||||
if (
|
if (
|
||||||
uiState.appSettings.isWifiNameByShellEnabled ||
|
uiState.appSettings.wifiDetectionMethod ==
|
||||||
|
AndroidNetworkMonitor.WifiDetectionMethod.ROOT ||
|
||||||
|
uiState.appSettings.wifiDetectionMethod ==
|
||||||
|
AndroidNetworkMonitor.WifiDetectionMethod.SHIZUKU ||
|
||||||
isWifiNameReadable()
|
isWifiNameReadable()
|
||||||
) {
|
) {
|
||||||
viewModel.handleEvent(AppEvent.SaveTrustedSSID(ssid))
|
viewModel.handleEvent(AppEvent.SaveTrustedSSID(ssid))
|
||||||
|
|||||||
+39
@@ -0,0 +1,39 @@
|
|||||||
|
package com.zaneschepke.wireguardautotunnel.ui.screens.autotunnel.detection
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import com.zaneschepke.networkmonitor.AndroidNetworkMonitor
|
||||||
|
import com.zaneschepke.wireguardautotunnel.ui.common.button.IconSurfaceButton
|
||||||
|
import com.zaneschepke.wireguardautotunnel.ui.state.AppUiState
|
||||||
|
import com.zaneschepke.wireguardautotunnel.util.extensions.asDescriptionString
|
||||||
|
import com.zaneschepke.wireguardautotunnel.util.extensions.asString
|
||||||
|
import com.zaneschepke.wireguardautotunnel.viewmodel.AppViewModel
|
||||||
|
import com.zaneschepke.wireguardautotunnel.viewmodel.event.AppEvent
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun WifiDetectionMethodScreen(uiState: AppUiState, viewModel: AppViewModel) {
|
||||||
|
val context = LocalContext.current
|
||||||
|
Column(
|
||||||
|
horizontalAlignment = Alignment.Start,
|
||||||
|
verticalArrangement = Arrangement.spacedBy(24.dp, Alignment.Top),
|
||||||
|
modifier = Modifier.fillMaxSize().padding(top = 24.dp).padding(horizontal = 24.dp),
|
||||||
|
) {
|
||||||
|
enumValues<AndroidNetworkMonitor.WifiDetectionMethod>().forEach {
|
||||||
|
val title = it.asString(context)
|
||||||
|
val description = it.asDescriptionString(context)
|
||||||
|
IconSurfaceButton(
|
||||||
|
title = title,
|
||||||
|
onClick = { viewModel.handleEvent(AppEvent.SetDetectionMethod(it)) },
|
||||||
|
selected = uiState.appSettings.wifiDetectionMethod == it,
|
||||||
|
description = description,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+14
-1
@@ -9,6 +9,8 @@ import androidx.compose.runtime.*
|
|||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
import com.journeyapps.barcodescanner.ScanContract
|
||||||
|
import com.journeyapps.barcodescanner.ScanOptions
|
||||||
import com.zaneschepke.wireguardautotunnel.R
|
import com.zaneschepke.wireguardautotunnel.R
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.Route
|
import com.zaneschepke.wireguardautotunnel.ui.Route
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.common.dialog.InfoDialog
|
import com.zaneschepke.wireguardautotunnel.ui.common.dialog.InfoDialog
|
||||||
@@ -45,6 +47,15 @@ fun MainScreen(appUiState: AppUiState, appViewState: AppViewState, viewModel: Ap
|
|||||||
onData = { data -> viewModel.handleEvent(AppEvent.ImportTunnelFromFile(data)) },
|
onData = { data -> viewModel.handleEvent(AppEvent.ImportTunnelFromFile(data)) },
|
||||||
)
|
)
|
||||||
|
|
||||||
|
val scanLauncher =
|
||||||
|
rememberLauncherForActivityResult(
|
||||||
|
contract = ScanContract(),
|
||||||
|
onResult = { result ->
|
||||||
|
if (result != null && result.contents.isNotEmpty())
|
||||||
|
viewModel.handleEvent(AppEvent.ImportTunnelFromQrCode(result.contents))
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
val requestPermissionLauncher =
|
val requestPermissionLauncher =
|
||||||
rememberLauncherForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted
|
rememberLauncherForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted
|
||||||
->
|
->
|
||||||
@@ -56,7 +67,9 @@ fun MainScreen(appUiState: AppUiState, appViewState: AppViewState, viewModel: Ap
|
|||||||
)
|
)
|
||||||
return@rememberLauncherForActivityResult
|
return@rememberLauncherForActivityResult
|
||||||
}
|
}
|
||||||
navController.navigate(Route.Scanner)
|
scanLauncher.launch(
|
||||||
|
ScanOptions().setDesiredBarcodeFormats(ScanOptions.QR_CODE).setBeepEnabled(false)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (appViewState.showModal == AppViewState.ModalType.DELETE) {
|
if (appViewState.showModal == AppViewState.ModalType.DELETE) {
|
||||||
|
|||||||
+2
-2
@@ -15,8 +15,8 @@ import androidx.compose.runtime.setValue
|
|||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import com.zaneschepke.wireguardautotunnel.domain.entity.AppSettings
|
import com.zaneschepke.wireguardautotunnel.domain.model.AppSettings
|
||||||
import com.zaneschepke.wireguardautotunnel.domain.entity.TunnelConf
|
import com.zaneschepke.wireguardautotunnel.domain.model.TunnelConf
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SurfaceSelectionGroupButton
|
import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SurfaceSelectionGroupButton
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.screens.main.autotunnel.components.EthernetTunnelItem
|
import com.zaneschepke.wireguardautotunnel.ui.screens.main.autotunnel.components.EthernetTunnelItem
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.screens.main.autotunnel.components.MobileDataTunnelItem
|
import com.zaneschepke.wireguardautotunnel.ui.screens.main.autotunnel.components.MobileDataTunnelItem
|
||||||
|
|||||||
+1
-1
@@ -7,7 +7,7 @@ import androidx.compose.material3.Text
|
|||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import com.zaneschepke.wireguardautotunnel.R
|
import com.zaneschepke.wireguardautotunnel.R
|
||||||
import com.zaneschepke.wireguardautotunnel.domain.entity.TunnelConf
|
import com.zaneschepke.wireguardautotunnel.domain.model.TunnelConf
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.ScaledSwitch
|
import com.zaneschepke.wireguardautotunnel.ui.common.button.ScaledSwitch
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SelectionItem
|
import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SelectionItem
|
||||||
import com.zaneschepke.wireguardautotunnel.viewmodel.AppViewModel
|
import com.zaneschepke.wireguardautotunnel.viewmodel.AppViewModel
|
||||||
|
|||||||
+1
-1
@@ -7,7 +7,7 @@ import androidx.compose.material3.Text
|
|||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import com.zaneschepke.wireguardautotunnel.R
|
import com.zaneschepke.wireguardautotunnel.R
|
||||||
import com.zaneschepke.wireguardautotunnel.domain.entity.TunnelConf
|
import com.zaneschepke.wireguardautotunnel.domain.model.TunnelConf
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.ScaledSwitch
|
import com.zaneschepke.wireguardautotunnel.ui.common.button.ScaledSwitch
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SelectionItem
|
import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SelectionItem
|
||||||
import com.zaneschepke.wireguardautotunnel.viewmodel.AppViewModel
|
import com.zaneschepke.wireguardautotunnel.viewmodel.AppViewModel
|
||||||
|
|||||||
+2
-2
@@ -16,8 +16,8 @@ import androidx.compose.ui.Modifier
|
|||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import com.zaneschepke.wireguardautotunnel.R
|
import com.zaneschepke.wireguardautotunnel.R
|
||||||
import com.zaneschepke.wireguardautotunnel.domain.entity.AppSettings
|
import com.zaneschepke.wireguardautotunnel.domain.model.AppSettings
|
||||||
import com.zaneschepke.wireguardautotunnel.domain.entity.TunnelConf
|
import com.zaneschepke.wireguardautotunnel.domain.model.TunnelConf
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SelectionItem
|
import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SelectionItem
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.screens.autotunnel.components.TrustedNetworkTextBox
|
import com.zaneschepke.wireguardautotunnel.ui.screens.autotunnel.components.TrustedNetworkTextBox
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.screens.autotunnel.components.WildcardsLabel
|
import com.zaneschepke.wireguardautotunnel.ui.screens.autotunnel.components.WildcardsLabel
|
||||||
|
|||||||
+1
-1
@@ -93,7 +93,7 @@ fun ExportTunnelsBottomSheet(viewModel: AppViewModel) {
|
|||||||
ExportOptionRow(
|
ExportOptionRow(
|
||||||
label = stringResource(R.string.export_tunnels_amnezia),
|
label = stringResource(R.string.export_tunnels_amnezia),
|
||||||
onClick = {
|
onClick = {
|
||||||
exportConfigType = ConfigType.AMNEZIA
|
exportConfigType = ConfigType.AM
|
||||||
if (!isAuthorized && !isTv) {
|
if (!isAuthorized && !isTv) {
|
||||||
showAuthPrompt = true
|
showAuthPrompt = true
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
+1
-1
@@ -16,7 +16,7 @@ import androidx.compose.ui.input.pointer.pointerInput
|
|||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import com.zaneschepke.wireguardautotunnel.core.tunnel.getValueById
|
import com.zaneschepke.wireguardautotunnel.core.tunnel.getValueById
|
||||||
import com.zaneschepke.wireguardautotunnel.domain.entity.TunnelConf
|
import com.zaneschepke.wireguardautotunnel.domain.model.TunnelConf
|
||||||
import com.zaneschepke.wireguardautotunnel.domain.state.TunnelState
|
import com.zaneschepke.wireguardautotunnel.domain.state.TunnelState
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.Route
|
import com.zaneschepke.wireguardautotunnel.ui.Route
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.navigation.LocalIsAndroidTV
|
import com.zaneschepke.wireguardautotunnel.ui.navigation.LocalIsAndroidTV
|
||||||
|
|||||||
+1
-1
@@ -22,7 +22,7 @@ import androidx.compose.ui.graphics.Color
|
|||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import com.zaneschepke.wireguardautotunnel.R
|
import com.zaneschepke.wireguardautotunnel.R
|
||||||
import com.zaneschepke.wireguardautotunnel.domain.entity.TunnelConf
|
import com.zaneschepke.wireguardautotunnel.domain.model.TunnelConf
|
||||||
import com.zaneschepke.wireguardautotunnel.domain.state.TunnelState
|
import com.zaneschepke.wireguardautotunnel.domain.state.TunnelState
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.common.ExpandingRowListItem
|
import com.zaneschepke.wireguardautotunnel.ui.common.ExpandingRowListItem
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.ScaledSwitch
|
import com.zaneschepke.wireguardautotunnel.ui.common.button.ScaledSwitch
|
||||||
|
|||||||
+1
-1
@@ -16,7 +16,7 @@ import androidx.compose.ui.Modifier
|
|||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import com.zaneschepke.wireguardautotunnel.R
|
import com.zaneschepke.wireguardautotunnel.R
|
||||||
import com.zaneschepke.wireguardautotunnel.domain.entity.TunnelConf
|
import com.zaneschepke.wireguardautotunnel.domain.model.TunnelConf
|
||||||
import com.zaneschepke.wireguardautotunnel.domain.state.TunnelStatistics
|
import com.zaneschepke.wireguardautotunnel.domain.state.TunnelStatistics
|
||||||
import com.zaneschepke.wireguardautotunnel.util.NumberUtils
|
import com.zaneschepke.wireguardautotunnel.util.NumberUtils
|
||||||
import com.zaneschepke.wireguardautotunnel.util.extensions.toThreeDecimalPlaceString
|
import com.zaneschepke.wireguardautotunnel.util.extensions.toThreeDecimalPlaceString
|
||||||
|
|||||||
+1
-1
@@ -20,7 +20,7 @@ import androidx.hilt.navigation.compose.hiltViewModel
|
|||||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
import com.zaneschepke.wireguardautotunnel.MainActivity
|
import com.zaneschepke.wireguardautotunnel.MainActivity
|
||||||
import com.zaneschepke.wireguardautotunnel.R
|
import com.zaneschepke.wireguardautotunnel.R
|
||||||
import com.zaneschepke.wireguardautotunnel.domain.entity.TunnelConf
|
import com.zaneschepke.wireguardautotunnel.domain.model.TunnelConf
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.common.prompt.AuthorizationPrompt
|
import com.zaneschepke.wireguardautotunnel.ui.common.prompt.AuthorizationPrompt
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.screens.main.config.components.AddPeerButton
|
import com.zaneschepke.wireguardautotunnel.ui.screens.main.config.components.AddPeerButton
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.screens.main.config.components.InterfaceSection
|
import com.zaneschepke.wireguardautotunnel.ui.screens.main.config.components.InterfaceSection
|
||||||
|
|||||||
+1
-1
@@ -4,7 +4,7 @@ import androidx.lifecycle.ViewModel
|
|||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import com.zaneschepke.wireguardautotunnel.R
|
import com.zaneschepke.wireguardautotunnel.R
|
||||||
import com.zaneschepke.wireguardautotunnel.di.IoDispatcher
|
import com.zaneschepke.wireguardautotunnel.di.IoDispatcher
|
||||||
import com.zaneschepke.wireguardautotunnel.domain.entity.TunnelConf
|
import com.zaneschepke.wireguardautotunnel.domain.model.TunnelConf
|
||||||
import com.zaneschepke.wireguardautotunnel.domain.repository.TunnelRepository
|
import com.zaneschepke.wireguardautotunnel.domain.repository.TunnelRepository
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.screens.main.config.state.ConfigUiState
|
import com.zaneschepke.wireguardautotunnel.ui.screens.main.config.state.ConfigUiState
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.state.ConfigProxy
|
import com.zaneschepke.wireguardautotunnel.ui.state.ConfigProxy
|
||||||
|
|||||||
+2
-1
@@ -52,8 +52,9 @@ fun InterfaceFields(
|
|||||||
if (isAuthenticated) VisualTransformation.None else PasswordVisualTransformation(),
|
if (isAuthenticated) VisualTransformation.None else PasswordVisualTransformation(),
|
||||||
trailingIcon = {
|
trailingIcon = {
|
||||||
IconButton(
|
IconButton(
|
||||||
enabled = isAuthenticated,
|
enabled = true,
|
||||||
onClick = {
|
onClick = {
|
||||||
|
if (!isAuthenticated) return@IconButton showAuthPrompt()
|
||||||
val keypair = com.wireguard.crypto.KeyPair()
|
val keypair = com.wireguard.crypto.KeyPair()
|
||||||
onInterfaceChange(
|
onInterfaceChange(
|
||||||
interfaceState.copy(
|
interfaceState.copy(
|
||||||
|
|||||||
-33
@@ -1,33 +0,0 @@
|
|||||||
package com.zaneschepke.wireguardautotunnel.ui.screens.main.scanner
|
|
||||||
|
|
||||||
import android.app.Activity
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.runtime.DisposableEffect
|
|
||||||
import androidx.compose.runtime.remember
|
|
||||||
import androidx.compose.ui.platform.LocalContext
|
|
||||||
import androidx.compose.ui.viewinterop.AndroidView
|
|
||||||
import com.journeyapps.barcodescanner.CompoundBarcodeView
|
|
||||||
import com.zaneschepke.wireguardautotunnel.viewmodel.AppViewModel
|
|
||||||
import com.zaneschepke.wireguardautotunnel.viewmodel.event.AppEvent
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun ScannerScreen(viewModel: AppViewModel) {
|
|
||||||
val context = LocalContext.current
|
|
||||||
|
|
||||||
val barcodeView = remember {
|
|
||||||
CompoundBarcodeView(context).apply {
|
|
||||||
this.initializeFromIntent((context as Activity).intent)
|
|
||||||
this.setStatusText("")
|
|
||||||
this.decodeSingle { result ->
|
|
||||||
result.text?.let { barCodeOrQr ->
|
|
||||||
viewModel.handleEvent(AppEvent.ImportTunnelFromQrCode(barCodeOrQr))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
AndroidView(factory = { barcodeView })
|
|
||||||
DisposableEffect(Unit) {
|
|
||||||
barcodeView.resume()
|
|
||||||
onDispose { barcodeView.pause() }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+1
-1
@@ -4,7 +4,7 @@ import android.content.Context
|
|||||||
import androidx.lifecycle.SavedStateHandle
|
import androidx.lifecycle.SavedStateHandle
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import com.zaneschepke.wireguardautotunnel.domain.entity.TunnelConf
|
import com.zaneschepke.wireguardautotunnel.domain.model.TunnelConf
|
||||||
import com.zaneschepke.wireguardautotunnel.domain.repository.TunnelRepository
|
import com.zaneschepke.wireguardautotunnel.domain.repository.TunnelRepository
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.Route
|
import com.zaneschepke.wireguardautotunnel.ui.Route
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.screens.main.splittunnel.state.SplitOption
|
import com.zaneschepke.wireguardautotunnel.ui.screens.main.splittunnel.state.SplitOption
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
package com.zaneschepke.wireguardautotunnel.ui.screens.main.splittunnel.state
|
package com.zaneschepke.wireguardautotunnel.ui.screens.main.splittunnel.state
|
||||||
|
|
||||||
import com.zaneschepke.wireguardautotunnel.domain.entity.TunnelConf
|
import com.zaneschepke.wireguardautotunnel.domain.model.TunnelConf
|
||||||
|
|
||||||
data class SplitTunnelUiState(
|
data class SplitTunnelUiState(
|
||||||
val loading: Boolean = true,
|
val loading: Boolean = true,
|
||||||
|
|||||||
+38
-3
@@ -6,18 +6,53 @@ import androidx.compose.foundation.layout.fillMaxSize
|
|||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.rememberScrollState
|
import androidx.compose.foundation.rememberScrollState
|
||||||
import androidx.compose.foundation.verticalScroll
|
import androidx.compose.foundation.verticalScroll
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.*
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import com.zaneschepke.wireguardautotunnel.domain.entity.TunnelConf
|
import com.zaneschepke.wireguardautotunnel.domain.model.TunnelConf
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.common.SectionDivider
|
import com.zaneschepke.wireguardautotunnel.ui.common.SectionDivider
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SurfaceSelectionGroupButton
|
import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SurfaceSelectionGroupButton
|
||||||
|
import com.zaneschepke.wireguardautotunnel.ui.navigation.LocalIsAndroidTV
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.screens.main.tunneloptions.components.*
|
import com.zaneschepke.wireguardautotunnel.ui.screens.main.tunneloptions.components.*
|
||||||
|
import com.zaneschepke.wireguardautotunnel.ui.screens.settings.components.AuthorizationPromptWrapper
|
||||||
|
import com.zaneschepke.wireguardautotunnel.ui.state.AppViewState
|
||||||
import com.zaneschepke.wireguardautotunnel.viewmodel.AppViewModel
|
import com.zaneschepke.wireguardautotunnel.viewmodel.AppViewModel
|
||||||
|
import com.zaneschepke.wireguardautotunnel.viewmodel.event.AppEvent
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun TunnelOptionsScreen(tunnelConf: TunnelConf, viewModel: AppViewModel) {
|
fun TunnelOptionsScreen(
|
||||||
|
tunnelConf: TunnelConf,
|
||||||
|
viewModel: AppViewModel,
|
||||||
|
appViewState: AppViewState,
|
||||||
|
) {
|
||||||
|
val isTv = LocalIsAndroidTV.current
|
||||||
|
|
||||||
|
var showAuthPrompt by remember { mutableStateOf(!isTv) }
|
||||||
|
var isAuthorized by remember { mutableStateOf(isTv) }
|
||||||
|
|
||||||
|
if (appViewState.showModal == AppViewState.ModalType.QR) {
|
||||||
|
|
||||||
|
// Show authorization prompt if needed
|
||||||
|
if (showAuthPrompt) {
|
||||||
|
AuthorizationPromptWrapper(
|
||||||
|
onDismiss = { showAuthPrompt = false },
|
||||||
|
onSuccess = {
|
||||||
|
showAuthPrompt = false
|
||||||
|
isAuthorized = true
|
||||||
|
},
|
||||||
|
viewModel = viewModel,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (isAuthorized) {
|
||||||
|
QrCodeDialog(
|
||||||
|
tunnelConf = tunnelConf,
|
||||||
|
onDismiss = {
|
||||||
|
viewModel.handleEvent(AppEvent.SetShowModal(AppViewState.ModalType.NONE))
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Column(
|
Column(
|
||||||
horizontalAlignment = Alignment.Start,
|
horizontalAlignment = Alignment.Start,
|
||||||
|
|||||||
+1
-1
@@ -7,7 +7,7 @@ import androidx.compose.material3.Text
|
|||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import com.zaneschepke.wireguardautotunnel.R
|
import com.zaneschepke.wireguardautotunnel.R
|
||||||
import com.zaneschepke.wireguardautotunnel.domain.entity.TunnelConf
|
import com.zaneschepke.wireguardautotunnel.domain.model.TunnelConf
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.Route
|
import com.zaneschepke.wireguardautotunnel.ui.Route
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.ForwardButton
|
import com.zaneschepke.wireguardautotunnel.ui.common.button.ForwardButton
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SelectionItem
|
import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SelectionItem
|
||||||
|
|||||||
+1
-1
@@ -9,7 +9,7 @@ import androidx.compose.ui.text.input.ImeAction
|
|||||||
import androidx.compose.ui.text.input.KeyboardType
|
import androidx.compose.ui.text.input.KeyboardType
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import com.zaneschepke.wireguardautotunnel.R
|
import com.zaneschepke.wireguardautotunnel.R
|
||||||
import com.zaneschepke.wireguardautotunnel.domain.entity.TunnelConf
|
import com.zaneschepke.wireguardautotunnel.domain.model.TunnelConf
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SelectionItem
|
import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SelectionItem
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.common.config.SubmitConfigurationTextBox
|
import com.zaneschepke.wireguardautotunnel.ui.common.config.SubmitConfigurationTextBox
|
||||||
import com.zaneschepke.wireguardautotunnel.util.Constants
|
import com.zaneschepke.wireguardautotunnel.util.Constants
|
||||||
|
|||||||
+1
-1
@@ -7,7 +7,7 @@ import androidx.compose.material3.Text
|
|||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import com.zaneschepke.wireguardautotunnel.R
|
import com.zaneschepke.wireguardautotunnel.R
|
||||||
import com.zaneschepke.wireguardautotunnel.domain.entity.TunnelConf
|
import com.zaneschepke.wireguardautotunnel.domain.model.TunnelConf
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.ScaledSwitch
|
import com.zaneschepke.wireguardautotunnel.ui.common.button.ScaledSwitch
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SelectionItem
|
import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SelectionItem
|
||||||
import com.zaneschepke.wireguardautotunnel.viewmodel.AppViewModel
|
import com.zaneschepke.wireguardautotunnel.viewmodel.AppViewModel
|
||||||
|
|||||||
+1
-1
@@ -7,7 +7,7 @@ import androidx.compose.material3.Text
|
|||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import com.zaneschepke.wireguardautotunnel.R
|
import com.zaneschepke.wireguardautotunnel.R
|
||||||
import com.zaneschepke.wireguardautotunnel.domain.entity.TunnelConf
|
import com.zaneschepke.wireguardautotunnel.domain.model.TunnelConf
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.ScaledSwitch
|
import com.zaneschepke.wireguardautotunnel.ui.common.button.ScaledSwitch
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SelectionItem
|
import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SelectionItem
|
||||||
import com.zaneschepke.wireguardautotunnel.viewmodel.AppViewModel
|
import com.zaneschepke.wireguardautotunnel.viewmodel.AppViewModel
|
||||||
|
|||||||
+190
@@ -0,0 +1,190 @@
|
|||||||
|
package com.zaneschepke.wireguardautotunnel.ui.screens.main.tunneloptions.components
|
||||||
|
|
||||||
|
import androidx.compose.foundation.Image
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.outlined.Check
|
||||||
|
import androidx.compose.material.icons.outlined.VpnKey
|
||||||
|
import androidx.compose.material3.AlertDialog
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.MultiChoiceSegmentedButtonRow
|
||||||
|
import androidx.compose.material3.SegmentedButton
|
||||||
|
import androidx.compose.material3.SegmentedButtonDefaults
|
||||||
|
import androidx.compose.material3.Surface
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.material3.TextButton
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.DisposableEffect
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.window.DialogProperties
|
||||||
|
import com.zaneschepke.wireguardautotunnel.MainActivity
|
||||||
|
import com.zaneschepke.wireguardautotunnel.R
|
||||||
|
import com.zaneschepke.wireguardautotunnel.domain.enums.ConfigType
|
||||||
|
import com.zaneschepke.wireguardautotunnel.domain.model.TunnelConf
|
||||||
|
import com.zaneschepke.wireguardautotunnel.util.extensions.setScreenBrightness
|
||||||
|
import io.github.alexzhirkevich.qrose.options.QrBallShape
|
||||||
|
import io.github.alexzhirkevich.qrose.options.QrBrush
|
||||||
|
import io.github.alexzhirkevich.qrose.options.QrErrorCorrectionLevel
|
||||||
|
import io.github.alexzhirkevich.qrose.options.QrFrameShape
|
||||||
|
import io.github.alexzhirkevich.qrose.options.QrOptions
|
||||||
|
import io.github.alexzhirkevich.qrose.options.QrPixelShape
|
||||||
|
import io.github.alexzhirkevich.qrose.options.circle
|
||||||
|
import io.github.alexzhirkevich.qrose.options.roundCorners
|
||||||
|
import io.github.alexzhirkevich.qrose.options.solid
|
||||||
|
import io.github.alexzhirkevich.qrose.rememberQrCodePainter
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun QrCodeDialog(tunnelConf: TunnelConf, onDismiss: () -> Unit) {
|
||||||
|
val context = LocalContext.current
|
||||||
|
val activity = context as? MainActivity
|
||||||
|
|
||||||
|
// Handle screen brightness
|
||||||
|
DisposableEffect(Unit) {
|
||||||
|
activity?.setScreenBrightness(1.0f)
|
||||||
|
onDispose { activity?.setScreenBrightness(-1f) }
|
||||||
|
}
|
||||||
|
|
||||||
|
QrCodeAlertDialog(tunnelConf = tunnelConf, onDismiss = onDismiss)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun QrCodeAlertDialog(tunnelConf: TunnelConf, onDismiss: () -> Unit) {
|
||||||
|
Surface(color = Color.White, tonalElevation = 0.dp) {
|
||||||
|
AlertDialog(
|
||||||
|
containerColor = Color.White,
|
||||||
|
onDismissRequest = onDismiss,
|
||||||
|
confirmButton = {
|
||||||
|
TextButton(onClick = onDismiss) {
|
||||||
|
Text(stringResource(R.string.done), color = MaterialTheme.colorScheme.surface)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
title = {
|
||||||
|
Text(
|
||||||
|
text = tunnelConf.name,
|
||||||
|
color = Color.Black,
|
||||||
|
style = MaterialTheme.typography.titleLarge,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
text = { QrCodeContent(tunnelConf = tunnelConf) },
|
||||||
|
properties = DialogProperties(usePlatformDefaultWidth = true),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun QrCodeContent(tunnelConf: TunnelConf) {
|
||||||
|
var selectedOption by remember { mutableStateOf(ConfigType.WG) }
|
||||||
|
val qrCodeText =
|
||||||
|
when (selectedOption) {
|
||||||
|
ConfigType.AM -> tunnelConf.toAmConfig().toAwgQuickString(true)
|
||||||
|
ConfigType.WG -> tunnelConf.toWgConfig().toWgQuickString(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
Column(
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
|
verticalArrangement = Arrangement.spacedBy(12.dp, Alignment.Top),
|
||||||
|
) {
|
||||||
|
val qrCodePainter = rememberQrCodePainter(data = qrCodeText, options = createQrOptions())
|
||||||
|
Image(
|
||||||
|
painter = qrCodePainter,
|
||||||
|
contentDescription = stringResource(R.string.show_qr),
|
||||||
|
modifier =
|
||||||
|
Modifier.size(300.dp)
|
||||||
|
.align(Alignment.CenterHorizontally)
|
||||||
|
.padding(16.dp)
|
||||||
|
.background(Color.White),
|
||||||
|
)
|
||||||
|
ConfigTypeSelector(
|
||||||
|
selectedOption = selectedOption,
|
||||||
|
onOptionSelected = { selectedOption = it },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun ConfigTypeSelector(selectedOption: ConfigType, onOptionSelected: (ConfigType) -> Unit) {
|
||||||
|
MultiChoiceSegmentedButtonRow(modifier = Modifier.fillMaxWidth().padding(horizontal = 24.dp)) {
|
||||||
|
ConfigType.entries.sortedDescending().forEachIndexed { index, entry ->
|
||||||
|
val isActive = selectedOption == entry
|
||||||
|
val typeName =
|
||||||
|
stringResource(
|
||||||
|
when (entry) {
|
||||||
|
ConfigType.AM -> R.string.amnezia
|
||||||
|
ConfigType.WG -> R.string.wireguard
|
||||||
|
}
|
||||||
|
)
|
||||||
|
SegmentedButton(
|
||||||
|
shape =
|
||||||
|
SegmentedButtonDefaults.itemShape(
|
||||||
|
index = index,
|
||||||
|
count = ConfigType.entries.size,
|
||||||
|
baseShape = RoundedCornerShape(8.dp),
|
||||||
|
),
|
||||||
|
icon = {
|
||||||
|
SegmentedButtonDefaults.Icon(
|
||||||
|
active = isActive,
|
||||||
|
activeContent = {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Outlined.Check,
|
||||||
|
contentDescription = stringResource(R.string.select),
|
||||||
|
tint = MaterialTheme.colorScheme.primary,
|
||||||
|
modifier = Modifier.size(SegmentedButtonDefaults.IconSize),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Outlined.VpnKey,
|
||||||
|
contentDescription = typeName,
|
||||||
|
tint = Color.Black,
|
||||||
|
modifier = Modifier.size(SegmentedButtonDefaults.IconSize),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
colors =
|
||||||
|
SegmentedButtonDefaults.colors()
|
||||||
|
.copy(
|
||||||
|
activeContainerColor = Color.White,
|
||||||
|
inactiveContainerColor = Color.White,
|
||||||
|
),
|
||||||
|
onCheckedChange = { onOptionSelected(entry) },
|
||||||
|
checked = isActive,
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = typeName,
|
||||||
|
color = Color.Black,
|
||||||
|
style = MaterialTheme.typography.labelMedium,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createQrOptions(): QrOptions = QrOptions {
|
||||||
|
shapes {
|
||||||
|
darkPixel = QrPixelShape.circle()
|
||||||
|
ball = QrBallShape.circle()
|
||||||
|
frame = QrFrameShape.roundCorners(0.2f)
|
||||||
|
}
|
||||||
|
colors {
|
||||||
|
dark = QrBrush.solid(Color.Black)
|
||||||
|
frame = QrBrush.solid(Color.Black)
|
||||||
|
ball = QrBrush.solid(Color.Black)
|
||||||
|
}
|
||||||
|
errorCorrectionLevel = QrErrorCorrectionLevel.Medium
|
||||||
|
}
|
||||||
+1
-1
@@ -7,7 +7,7 @@ import androidx.compose.material3.Text
|
|||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import com.zaneschepke.wireguardautotunnel.R
|
import com.zaneschepke.wireguardautotunnel.R
|
||||||
import com.zaneschepke.wireguardautotunnel.domain.entity.TunnelConf
|
import com.zaneschepke.wireguardautotunnel.domain.model.TunnelConf
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.ScaledSwitch
|
import com.zaneschepke.wireguardautotunnel.ui.common.button.ScaledSwitch
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SelectionItem
|
import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SelectionItem
|
||||||
import com.zaneschepke.wireguardautotunnel.viewmodel.AppViewModel
|
import com.zaneschepke.wireguardautotunnel.viewmodel.AppViewModel
|
||||||
|
|||||||
+1
-1
@@ -7,7 +7,7 @@ import androidx.compose.material3.Text
|
|||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import com.zaneschepke.wireguardautotunnel.R
|
import com.zaneschepke.wireguardautotunnel.R
|
||||||
import com.zaneschepke.wireguardautotunnel.domain.entity.TunnelConf
|
import com.zaneschepke.wireguardautotunnel.domain.model.TunnelConf
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.Route
|
import com.zaneschepke.wireguardautotunnel.ui.Route
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.ForwardButton
|
import com.zaneschepke.wireguardautotunnel.ui.common.button.ForwardButton
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SelectionItem
|
import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SelectionItem
|
||||||
|
|||||||
+46
-5
@@ -12,6 +12,12 @@ import androidx.compose.ui.Modifier
|
|||||||
import androidx.compose.ui.graphics.StrokeCap
|
import androidx.compose.ui.graphics.StrokeCap
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.text.LinkAnnotation
|
||||||
|
import androidx.compose.ui.text.SpanStyle
|
||||||
|
import androidx.compose.ui.text.TextLinkStyles
|
||||||
|
import androidx.compose.ui.text.buildAnnotatedString
|
||||||
|
import androidx.compose.ui.text.style.TextDecoration
|
||||||
|
import androidx.compose.ui.text.withLink
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.hilt.navigation.compose.hiltViewModel
|
import androidx.hilt.navigation.compose.hiltViewModel
|
||||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
@@ -23,6 +29,7 @@ import com.zaneschepke.wireguardautotunnel.ui.common.label.GroupLabel
|
|||||||
import com.zaneschepke.wireguardautotunnel.ui.screens.support.components.ContactSupportOptions
|
import com.zaneschepke.wireguardautotunnel.ui.screens.support.components.ContactSupportOptions
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.screens.support.components.GeneralSupportOptions
|
import com.zaneschepke.wireguardautotunnel.ui.screens.support.components.GeneralSupportOptions
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.screens.support.components.UpdateSection
|
import com.zaneschepke.wireguardautotunnel.ui.screens.support.components.UpdateSection
|
||||||
|
import com.zaneschepke.wireguardautotunnel.util.Constants
|
||||||
import com.zaneschepke.wireguardautotunnel.util.extensions.canInstallPackages
|
import com.zaneschepke.wireguardautotunnel.util.extensions.canInstallPackages
|
||||||
import com.zaneschepke.wireguardautotunnel.util.extensions.openWebUrl
|
import com.zaneschepke.wireguardautotunnel.util.extensions.openWebUrl
|
||||||
import com.zaneschepke.wireguardautotunnel.util.extensions.requestInstallPackagesPermission
|
import com.zaneschepke.wireguardautotunnel.util.extensions.requestInstallPackagesPermission
|
||||||
@@ -54,7 +61,7 @@ fun SupportScreen(viewModel: SupportViewModel = hiltViewModel(), appViewModel: A
|
|||||||
InfoDialog(
|
InfoDialog(
|
||||||
onDismiss = { viewModel.handleUpdateShown() },
|
onDismiss = { viewModel.handleUpdateShown() },
|
||||||
onAttest = {
|
onAttest = {
|
||||||
if (BuildConfig.FLAVOR != "full") {
|
if (BuildConfig.FLAVOR != Constants.STANDALONE_FLAVOR) {
|
||||||
uiState.appUpdate?.apkUrl?.let { context.openWebUrl(it) }
|
uiState.appUpdate?.apkUrl?.let { context.openWebUrl(it) }
|
||||||
return@InfoDialog
|
return@InfoDialog
|
||||||
}
|
}
|
||||||
@@ -71,8 +78,41 @@ fun SupportScreen(viewModel: SupportViewModel = hiltViewModel(), appViewModel: A
|
|||||||
verticalArrangement = Arrangement.spacedBy(12.dp, Alignment.Top),
|
verticalArrangement = Arrangement.spacedBy(12.dp, Alignment.Top),
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
) {
|
) {
|
||||||
Text(uiState.appUpdate?.version ?: "")
|
val annotatedString = buildAnnotatedString {
|
||||||
Text(uiState.appUpdate?.releaseNotes ?: "")
|
append("${uiState.appUpdate?.version ?: ""}\n")
|
||||||
|
// Add clickable text for second line
|
||||||
|
withLink(
|
||||||
|
link =
|
||||||
|
LinkAnnotation.Clickable(
|
||||||
|
tag = stringResource(id = R.string.release_notes),
|
||||||
|
linkInteractionListener = {
|
||||||
|
val version =
|
||||||
|
if (BuildConfig.VERSION_NAME.contains("nightly")) {
|
||||||
|
"nightly"
|
||||||
|
} else {
|
||||||
|
uiState.appUpdate
|
||||||
|
?.version
|
||||||
|
?.removePrefix("v")
|
||||||
|
?.trim() ?: ""
|
||||||
|
}
|
||||||
|
val url = "${Constants.BASE_RELEASE_URL}$version".trim()
|
||||||
|
context.openWebUrl(url)
|
||||||
|
},
|
||||||
|
styles =
|
||||||
|
TextLinkStyles(
|
||||||
|
style =
|
||||||
|
SpanStyle(
|
||||||
|
color = MaterialTheme.colorScheme.primary,
|
||||||
|
textDecoration = TextDecoration.Underline,
|
||||||
|
)
|
||||||
|
),
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
append(stringResource(R.string.release_notes))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Text(text = annotatedString)
|
||||||
if (uiState.isLoading) {
|
if (uiState.isLoading) {
|
||||||
LinearProgressIndicator(
|
LinearProgressIndicator(
|
||||||
progress = { uiState.downloadProgress },
|
progress = { uiState.downloadProgress },
|
||||||
@@ -86,7 +126,8 @@ fun SupportScreen(viewModel: SupportViewModel = hiltViewModel(), appViewModel: A
|
|||||||
},
|
},
|
||||||
confirmText = {
|
confirmText = {
|
||||||
Text(
|
Text(
|
||||||
if (BuildConfig.FLAVOR != "full") stringResource(R.string.download)
|
if (BuildConfig.FLAVOR != Constants.STANDALONE_FLAVOR)
|
||||||
|
stringResource(R.string.download)
|
||||||
else stringResource(R.string.download_and_install)
|
else stringResource(R.string.download_and_install)
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
@@ -124,7 +165,7 @@ fun SupportScreen(viewModel: SupportViewModel = hiltViewModel(), appViewModel: A
|
|||||||
if (
|
if (
|
||||||
BuildConfig.DEBUG ||
|
BuildConfig.DEBUG ||
|
||||||
BuildConfig.VERSION_NAME.contains("beta") ||
|
BuildConfig.VERSION_NAME.contains("beta") ||
|
||||||
BuildConfig.FLAVOR == "google"
|
BuildConfig.FLAVOR == Constants.GOOGLE_PLAY_FLAVOR
|
||||||
)
|
)
|
||||||
return@UpdateSection context.showToast(R.string.update_check_unsupported)
|
return@UpdateSection context.showToast(R.string.update_check_unsupported)
|
||||||
context.showToast(R.string.checking_for_update)
|
context.showToast(R.string.checking_for_update)
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
package com.zaneschepke.wireguardautotunnel.ui.screens.support
|
package com.zaneschepke.wireguardautotunnel.ui.screens.support
|
||||||
|
|
||||||
import com.zaneschepke.wireguardautotunnel.domain.entity.AppUpdate
|
import com.zaneschepke.wireguardautotunnel.domain.model.AppUpdate
|
||||||
import com.zaneschepke.wireguardautotunnel.util.StringValue
|
import com.zaneschepke.wireguardautotunnel.util.StringValue
|
||||||
|
|
||||||
data class SupportUiState(
|
data class SupportUiState(
|
||||||
|
|||||||
+1
-1
@@ -4,7 +4,7 @@ import androidx.lifecycle.ViewModel
|
|||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import com.zaneschepke.wireguardautotunnel.BuildConfig
|
import com.zaneschepke.wireguardautotunnel.BuildConfig
|
||||||
import com.zaneschepke.wireguardautotunnel.R
|
import com.zaneschepke.wireguardautotunnel.R
|
||||||
import com.zaneschepke.wireguardautotunnel.domain.entity.AppUpdate
|
import com.zaneschepke.wireguardautotunnel.domain.model.AppUpdate
|
||||||
import com.zaneschepke.wireguardautotunnel.domain.repository.UpdateRepository
|
import com.zaneschepke.wireguardautotunnel.domain.repository.UpdateRepository
|
||||||
import com.zaneschepke.wireguardautotunnel.util.FileUtils
|
import com.zaneschepke.wireguardautotunnel.util.FileUtils
|
||||||
import com.zaneschepke.wireguardautotunnel.util.StringValue
|
import com.zaneschepke.wireguardautotunnel.util.StringValue
|
||||||
|
|||||||
+1
-1
@@ -85,7 +85,7 @@ fun ContactSupportOptions(context: android.content.Context) {
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
if (BuildConfig.FLAVOR == Constants.FDROID_FLAVOR) {
|
if (BuildConfig.FLAVOR != Constants.GOOGLE_PLAY_FLAVOR) {
|
||||||
add(
|
add(
|
||||||
SelectionItem(
|
SelectionItem(
|
||||||
leadingIcon = Icons.Filled.Favorite,
|
leadingIcon = Icons.Filled.Favorite,
|
||||||
|
|||||||
@@ -1,17 +1,18 @@
|
|||||||
package com.zaneschepke.wireguardautotunnel.ui.state
|
package com.zaneschepke.wireguardautotunnel.ui.state
|
||||||
|
|
||||||
import com.zaneschepke.networkmonitor.NetworkStatus
|
import com.zaneschepke.networkmonitor.NetworkStatus
|
||||||
import com.zaneschepke.wireguardautotunnel.data.model.GeneralState
|
import com.zaneschepke.wireguardautotunnel.data.entity.GeneralState
|
||||||
import com.zaneschepke.wireguardautotunnel.domain.entity.AppSettings
|
import com.zaneschepke.wireguardautotunnel.data.mapper.GeneralStateMapper
|
||||||
import com.zaneschepke.wireguardautotunnel.domain.entity.AppState
|
import com.zaneschepke.wireguardautotunnel.domain.model.AppSettings
|
||||||
import com.zaneschepke.wireguardautotunnel.domain.entity.TunnelConf
|
import com.zaneschepke.wireguardautotunnel.domain.model.AppState
|
||||||
|
import com.zaneschepke.wireguardautotunnel.domain.model.TunnelConf
|
||||||
import com.zaneschepke.wireguardautotunnel.domain.state.TunnelState
|
import com.zaneschepke.wireguardautotunnel.domain.state.TunnelState
|
||||||
|
|
||||||
data class AppUiState(
|
data class AppUiState(
|
||||||
val appSettings: AppSettings = AppSettings(),
|
val appSettings: AppSettings = AppSettings(),
|
||||||
val tunnels: List<TunnelConf> = emptyList(),
|
val tunnels: List<TunnelConf> = emptyList(),
|
||||||
val activeTunnels: Map<TunnelConf, TunnelState> = emptyMap(),
|
val activeTunnels: Map<TunnelConf, TunnelState> = emptyMap(),
|
||||||
val appState: AppState = GeneralState().toAppState(),
|
val appState: AppState = GeneralStateMapper.toAppState(GeneralState()),
|
||||||
val isAutoTunnelActive: Boolean = false,
|
val isAutoTunnelActive: Boolean = false,
|
||||||
val appConfigurationChange: Boolean = false,
|
val appConfigurationChange: Boolean = false,
|
||||||
val isAppLoaded: Boolean = false,
|
val isAppLoaded: Boolean = false,
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
package com.zaneschepke.wireguardautotunnel.ui.state
|
package com.zaneschepke.wireguardautotunnel.ui.state
|
||||||
|
|
||||||
import com.zaneschepke.wireguardautotunnel.domain.entity.TunnelConf
|
import com.zaneschepke.wireguardautotunnel.domain.model.TunnelConf
|
||||||
import com.zaneschepke.wireguardautotunnel.util.StringValue
|
import com.zaneschepke.wireguardautotunnel.util.StringValue
|
||||||
|
|
||||||
data class AppViewState(
|
data class AppViewState(
|
||||||
@@ -18,6 +18,7 @@ data class AppViewState(
|
|||||||
NONE,
|
NONE,
|
||||||
DELETE,
|
DELETE,
|
||||||
INFO,
|
INFO,
|
||||||
|
QR,
|
||||||
}
|
}
|
||||||
|
|
||||||
enum class BottomSheet {
|
enum class BottomSheet {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
package com.zaneschepke.wireguardautotunnel.ui.state
|
package com.zaneschepke.wireguardautotunnel.ui.state
|
||||||
|
|
||||||
import com.wireguard.config.Peer
|
import com.wireguard.config.Peer
|
||||||
import com.zaneschepke.wireguardautotunnel.domain.entity.TunnelConf
|
import com.zaneschepke.wireguardautotunnel.domain.model.TunnelConf
|
||||||
import com.zaneschepke.wireguardautotunnel.util.extensions.joinAndTrim
|
import com.zaneschepke.wireguardautotunnel.util.extensions.joinAndTrim
|
||||||
|
|
||||||
data class PeerProxy(
|
data class PeerProxy(
|
||||||
|
|||||||
@@ -35,5 +35,9 @@ object Constants {
|
|||||||
const val QR_CODE_NAME_PROPERTY = "# Name ="
|
const val QR_CODE_NAME_PROPERTY = "# Name ="
|
||||||
|
|
||||||
const val FDROID_FLAVOR = "fdroid"
|
const val FDROID_FLAVOR = "fdroid"
|
||||||
|
const val GOOGLE_PLAY_FLAVOR = "google"
|
||||||
|
const val STANDALONE_FLAVOR = "standalone"
|
||||||
const val RELEASE = "release"
|
const val RELEASE = "release"
|
||||||
|
|
||||||
|
const val BASE_RELEASE_URL = "https://github.com/wgtunnel/wgtunnel/releases/tag/"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,8 +10,8 @@ import android.provider.OpenableColumns
|
|||||||
import androidx.annotation.RequiresApi
|
import androidx.annotation.RequiresApi
|
||||||
import androidx.core.content.FileProvider
|
import androidx.core.content.FileProvider
|
||||||
import com.zaneschepke.wireguardautotunnel.R
|
import com.zaneschepke.wireguardautotunnel.R
|
||||||
import com.zaneschepke.wireguardautotunnel.data.model.TunnelConfig
|
import com.zaneschepke.wireguardautotunnel.data.entity.TunnelConfig
|
||||||
import com.zaneschepke.wireguardautotunnel.domain.entity.TunnelConf
|
import com.zaneschepke.wireguardautotunnel.domain.model.TunnelConf
|
||||||
import com.zaneschepke.wireguardautotunnel.util.extensions.getInputStreamFromUri
|
import com.zaneschepke.wireguardautotunnel.util.extensions.getInputStreamFromUri
|
||||||
import com.zaneschepke.wireguardautotunnel.util.extensions.installApk
|
import com.zaneschepke.wireguardautotunnel.util.extensions.installApk
|
||||||
import com.zaneschepke.wireguardautotunnel.util.extensions.launchShareFile
|
import com.zaneschepke.wireguardautotunnel.util.extensions.launchShareFile
|
||||||
|
|||||||
+23
@@ -1,6 +1,7 @@
|
|||||||
package com.zaneschepke.wireguardautotunnel.util.extensions
|
package com.zaneschepke.wireguardautotunnel.util.extensions
|
||||||
|
|
||||||
import android.Manifest
|
import android.Manifest
|
||||||
|
import android.app.Activity
|
||||||
import android.content.ComponentName
|
import android.content.ComponentName
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Context.POWER_SERVICE
|
import android.content.Context.POWER_SERVICE
|
||||||
@@ -17,6 +18,9 @@ import android.widget.Toast
|
|||||||
import androidx.core.content.FileProvider
|
import androidx.core.content.FileProvider
|
||||||
import androidx.core.location.LocationManagerCompat
|
import androidx.core.location.LocationManagerCompat
|
||||||
import androidx.core.net.toUri
|
import androidx.core.net.toUri
|
||||||
|
import androidx.core.view.WindowCompat
|
||||||
|
import androidx.core.view.WindowInsetsCompat
|
||||||
|
import androidx.core.view.WindowInsetsControllerCompat
|
||||||
import com.zaneschepke.wireguardautotunnel.R
|
import com.zaneschepke.wireguardautotunnel.R
|
||||||
import com.zaneschepke.wireguardautotunnel.core.service.tile.AutoTunnelControlTile
|
import com.zaneschepke.wireguardautotunnel.core.service.tile.AutoTunnelControlTile
|
||||||
import com.zaneschepke.wireguardautotunnel.core.service.tile.TunnelControlTile
|
import com.zaneschepke.wireguardautotunnel.core.service.tile.TunnelControlTile
|
||||||
@@ -225,3 +229,22 @@ fun Context.installApk(apkFile: File) {
|
|||||||
}
|
}
|
||||||
startActivity(intent)
|
startActivity(intent)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun Activity.setScreenBrightness(brightness: Float) {
|
||||||
|
window.attributes = window.attributes.apply { screenBrightness = brightness }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Activity.enableImmersiveMode() {
|
||||||
|
WindowCompat.setDecorFitsSystemWindows(window, false)
|
||||||
|
val controller = WindowCompat.getInsetsController(window, window.decorView)
|
||||||
|
controller.systemBarsBehavior =
|
||||||
|
WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
|
||||||
|
controller.hide(WindowInsetsCompat.Type.systemBars())
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Activity.disableImmersiveMode() {
|
||||||
|
WindowCompat.setDecorFitsSystemWindows(window, true)
|
||||||
|
val controller = WindowCompat.getInsetsController(window, window.decorView)
|
||||||
|
controller.show(WindowInsetsCompat.Type.systemBars())
|
||||||
|
window.statusBarColor = android.graphics.Color.TRANSPARENT
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
package com.zaneschepke.wireguardautotunnel.util.extensions
|
package com.zaneschepke.wireguardautotunnel.util.extensions
|
||||||
|
|
||||||
import android.content.pm.PackageInfo
|
import android.content.pm.PackageInfo
|
||||||
import com.zaneschepke.wireguardautotunnel.data.model.TunnelConfig
|
import com.zaneschepke.wireguardautotunnel.data.entity.TunnelConfig
|
||||||
import com.zaneschepke.wireguardautotunnel.domain.entity.TunnelConf
|
import com.zaneschepke.wireguardautotunnel.domain.model.TunnelConf
|
||||||
import java.math.BigDecimal
|
import java.math.BigDecimal
|
||||||
import java.text.DecimalFormat
|
import java.text.DecimalFormat
|
||||||
|
|
||||||
|
|||||||
+19
-4
@@ -1,14 +1,29 @@
|
|||||||
package com.zaneschepke.wireguardautotunnel.util.extensions
|
package com.zaneschepke.wireguardautotunnel.util.extensions
|
||||||
|
|
||||||
import java.util.regex.Pattern
|
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
|
||||||
val hasNumberInParentheses = """^(.+?)\((\d+)\)$""".toRegex()
|
val hasNumberInParentheses = """^(.+?)\((\d+)\)$""".toRegex()
|
||||||
|
|
||||||
fun String.isValidIpv4orIpv6Address(): Boolean {
|
fun String.isValidIpv4orIpv6Address(): Boolean {
|
||||||
val ipv4Pattern = Pattern.compile("^(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\$")
|
val sanitized = removeSurrounding("[", "]")
|
||||||
val ipv6Pattern = Pattern.compile("^([0-9A-Fa-f]{1,4}:){7}[0-9A-Fa-f]{1,4}\$")
|
val ipv6Pattern =
|
||||||
return ipv4Pattern.matcher(this).matches() || ipv6Pattern.matcher(this).matches()
|
Regex(
|
||||||
|
"(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:)" +
|
||||||
|
"{1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]" +
|
||||||
|
"{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:" +
|
||||||
|
"[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4})" +
|
||||||
|
"{1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}" +
|
||||||
|
":((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]" +
|
||||||
|
"{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}" +
|
||||||
|
"[0-9]){0,1}[0-9])\\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:)" +
|
||||||
|
"{1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))"
|
||||||
|
)
|
||||||
|
val ipv4Pattern =
|
||||||
|
Regex(
|
||||||
|
"^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}" +
|
||||||
|
"(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$"
|
||||||
|
)
|
||||||
|
return ipv4Pattern.matches(sanitized) || ipv6Pattern.matches(sanitized)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun String.hasNumberInParentheses(): Boolean {
|
fun String.hasNumberInParentheses(): Boolean {
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
package com.zaneschepke.wireguardautotunnel.util.extensions
|
package com.zaneschepke.wireguardautotunnel.util.extensions
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
import androidx.navigation.NavController
|
import androidx.navigation.NavController
|
||||||
|
import com.zaneschepke.networkmonitor.AndroidNetworkMonitor
|
||||||
|
import com.zaneschepke.wireguardautotunnel.R
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.Route
|
import com.zaneschepke.wireguardautotunnel.ui.Route
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.navigation.isCurrentRoute
|
import com.zaneschepke.wireguardautotunnel.ui.navigation.isCurrentRoute
|
||||||
|
|
||||||
@@ -11,3 +14,25 @@ fun NavController.goFromRoot(route: Route) {
|
|||||||
launchSingleTop = true
|
launchSingleTop = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun AndroidNetworkMonitor.WifiDetectionMethod.asString(context: Context): String {
|
||||||
|
return when (this) {
|
||||||
|
AndroidNetworkMonitor.WifiDetectionMethod.DEFAULT -> context.getString(R.string._default)
|
||||||
|
AndroidNetworkMonitor.WifiDetectionMethod.LEGACY -> context.getString(R.string.legacy)
|
||||||
|
AndroidNetworkMonitor.WifiDetectionMethod.ROOT -> context.getString(R.string.root)
|
||||||
|
AndroidNetworkMonitor.WifiDetectionMethod.SHIZUKU -> context.getString(R.string.shizuku)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun AndroidNetworkMonitor.WifiDetectionMethod.asDescriptionString(context: Context): String? {
|
||||||
|
return when (this) {
|
||||||
|
AndroidNetworkMonitor.WifiDetectionMethod.LEGACY ->
|
||||||
|
context.getString(R.string.legacy_api_description)
|
||||||
|
AndroidNetworkMonitor.WifiDetectionMethod.ROOT ->
|
||||||
|
context.getString(R.string.use_root_shell_for_wifi)
|
||||||
|
AndroidNetworkMonitor.WifiDetectionMethod.SHIZUKU ->
|
||||||
|
context.getString(R.string.use_shell_via_shizuku)
|
||||||
|
AndroidNetworkMonitor.WifiDetectionMethod.DEFAULT ->
|
||||||
|
context.getString(R.string.use_android_recommended)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package com.zaneschepke.wireguardautotunnel.viewmodel
|
package com.zaneschepke.wireguardautotunnel.viewmodel
|
||||||
|
|
||||||
|
import android.content.pm.PackageManager.PERMISSION_GRANTED
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
@@ -8,6 +9,7 @@ import com.wireguard.android.backend.WgQuickBackend
|
|||||||
import com.wireguard.android.util.RootShell
|
import com.wireguard.android.util.RootShell
|
||||||
import com.zaneschepke.logcatter.LogReader
|
import com.zaneschepke.logcatter.LogReader
|
||||||
import com.zaneschepke.logcatter.model.LogMessage
|
import com.zaneschepke.logcatter.model.LogMessage
|
||||||
|
import com.zaneschepke.networkmonitor.AndroidNetworkMonitor
|
||||||
import com.zaneschepke.networkmonitor.NetworkMonitor
|
import com.zaneschepke.networkmonitor.NetworkMonitor
|
||||||
import com.zaneschepke.networkmonitor.NetworkStatus
|
import com.zaneschepke.networkmonitor.NetworkStatus
|
||||||
import com.zaneschepke.wireguardautotunnel.R
|
import com.zaneschepke.wireguardautotunnel.R
|
||||||
@@ -18,11 +20,11 @@ import com.zaneschepke.wireguardautotunnel.core.tunnel.TunnelManager
|
|||||||
import com.zaneschepke.wireguardautotunnel.di.AppShell
|
import com.zaneschepke.wireguardautotunnel.di.AppShell
|
||||||
import com.zaneschepke.wireguardautotunnel.di.IoDispatcher
|
import com.zaneschepke.wireguardautotunnel.di.IoDispatcher
|
||||||
import com.zaneschepke.wireguardautotunnel.di.MainDispatcher
|
import com.zaneschepke.wireguardautotunnel.di.MainDispatcher
|
||||||
import com.zaneschepke.wireguardautotunnel.domain.entity.AppSettings
|
|
||||||
import com.zaneschepke.wireguardautotunnel.domain.entity.AppState
|
|
||||||
import com.zaneschepke.wireguardautotunnel.domain.entity.TunnelConf
|
|
||||||
import com.zaneschepke.wireguardautotunnel.domain.enums.BackendState
|
import com.zaneschepke.wireguardautotunnel.domain.enums.BackendState
|
||||||
import com.zaneschepke.wireguardautotunnel.domain.enums.ConfigType
|
import com.zaneschepke.wireguardautotunnel.domain.enums.ConfigType
|
||||||
|
import com.zaneschepke.wireguardautotunnel.domain.model.AppSettings
|
||||||
|
import com.zaneschepke.wireguardautotunnel.domain.model.AppState
|
||||||
|
import com.zaneschepke.wireguardautotunnel.domain.model.TunnelConf
|
||||||
import com.zaneschepke.wireguardautotunnel.domain.repository.AppDataRepository
|
import com.zaneschepke.wireguardautotunnel.domain.repository.AppDataRepository
|
||||||
import com.zaneschepke.wireguardautotunnel.domain.state.TunnelState
|
import com.zaneschepke.wireguardautotunnel.domain.state.TunnelState
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.state.AppUiState
|
import com.zaneschepke.wireguardautotunnel.ui.state.AppUiState
|
||||||
@@ -39,14 +41,13 @@ import java.time.Instant
|
|||||||
import java.util.*
|
import java.util.*
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import javax.inject.Provider
|
import javax.inject.Provider
|
||||||
import kotlin.collections.component1
|
|
||||||
import kotlin.collections.component2
|
|
||||||
import kotlinx.coroutines.*
|
import kotlinx.coroutines.*
|
||||||
import kotlinx.coroutines.flow.*
|
import kotlinx.coroutines.flow.*
|
||||||
import kotlinx.coroutines.sync.Mutex
|
import kotlinx.coroutines.sync.Mutex
|
||||||
import kotlinx.coroutines.sync.withLock
|
import kotlinx.coroutines.sync.withLock
|
||||||
import org.amnezia.awg.config.BadConfigException
|
import org.amnezia.awg.config.BadConfigException
|
||||||
import org.amnezia.awg.config.Config
|
import org.amnezia.awg.config.Config
|
||||||
|
import rikka.shizuku.Shizuku
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import xyz.teamgravity.pin_lock_compose.PinManager
|
import xyz.teamgravity.pin_lock_compose.PinManager
|
||||||
|
|
||||||
@@ -179,7 +180,6 @@ constructor(
|
|||||||
handleDeleteTrustedSSID(event.ssid, state.appSettings)
|
handleDeleteTrustedSSID(event.ssid, state.appSettings)
|
||||||
AppEvent.ToggleAutoTunnelWildcards ->
|
AppEvent.ToggleAutoTunnelWildcards ->
|
||||||
handleToggleAutoTunnelWildcards(state.appSettings)
|
handleToggleAutoTunnelWildcards(state.appSettings)
|
||||||
AppEvent.ToggleRootShellWifi -> handleToggleRootShellWifi(state.appSettings)
|
|
||||||
is AppEvent.SaveTrustedSSID ->
|
is AppEvent.SaveTrustedSSID ->
|
||||||
handleSaveTrustedSSID(event.ssid, state.appSettings)
|
handleSaveTrustedSSID(event.ssid, state.appSettings)
|
||||||
AppEvent.ToggleAutoTunnelOnEthernet ->
|
AppEvent.ToggleAutoTunnelOnEthernet ->
|
||||||
@@ -212,10 +212,46 @@ constructor(
|
|||||||
AppEvent.ClearSelectedTunnels -> clearSelectedTunnels()
|
AppEvent.ClearSelectedTunnels -> clearSelectedTunnels()
|
||||||
is AppEvent.SetShowModal ->
|
is AppEvent.SetShowModal ->
|
||||||
_appViewState.update { it.copy(showModal = event.modalType) }
|
_appViewState.update { it.copy(showModal = event.modalType) }
|
||||||
|
|
||||||
|
is AppEvent.SetDetectionMethod ->
|
||||||
|
handleSetDetectionMethod(event.detectionMethod, state.appSettings)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private suspend fun handleSetDetectionMethod(
|
||||||
|
detectionMethod: AndroidNetworkMonitor.WifiDetectionMethod,
|
||||||
|
appSettings: AppSettings,
|
||||||
|
) {
|
||||||
|
if (detectionMethod == appSettings.wifiDetectionMethod) return
|
||||||
|
when (detectionMethod) {
|
||||||
|
AndroidNetworkMonitor.WifiDetectionMethod.ROOT -> if (!requestRoot()) return
|
||||||
|
AndroidNetworkMonitor.WifiDetectionMethod.SHIZUKU -> {
|
||||||
|
Shizuku.addRequestPermissionResultListener(
|
||||||
|
Shizuku.OnRequestPermissionResultListener { requestCode: Int, grantResult: Int
|
||||||
|
->
|
||||||
|
if (grantResult != PERMISSION_GRANTED)
|
||||||
|
return@OnRequestPermissionResultListener
|
||||||
|
viewModelScope.launch {
|
||||||
|
saveSettings(appSettings.copy(wifiDetectionMethod = detectionMethod))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
try {
|
||||||
|
if (Shizuku.checkSelfPermission() != PERMISSION_GRANTED)
|
||||||
|
return Shizuku.requestPermission(123)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Timber.e(e)
|
||||||
|
return handleShowMessage(
|
||||||
|
StringValue.StringResource(R.string.shizuku_not_detected)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else -> Unit
|
||||||
|
}
|
||||||
|
saveSettings(appSettings.copy(wifiDetectionMethod = detectionMethod))
|
||||||
|
}
|
||||||
|
|
||||||
private fun handleToggleSelectAllTunnels(tunnels: List<TunnelConf>) =
|
private fun handleToggleSelectAllTunnels(tunnels: List<TunnelConf>) =
|
||||||
_appViewState.update { it ->
|
_appViewState.update { it ->
|
||||||
val remove = tunnels.size == it.selectedTunnels.size
|
val remove = tunnels.size == it.selectedTunnels.size
|
||||||
@@ -266,6 +302,7 @@ constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO
|
||||||
private fun handleTunnelErrors() =
|
private fun handleTunnelErrors() =
|
||||||
viewModelScope.launch { tunnelManager.errorEvents.collect { errorEvent -> } }
|
viewModelScope.launch { tunnelManager.errorEvents.collect { errorEvent -> } }
|
||||||
|
|
||||||
@@ -632,14 +669,6 @@ constructor(
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
private suspend fun handleToggleRootShellWifi(appSettings: AppSettings) {
|
|
||||||
if (requestRoot()) {
|
|
||||||
saveSettings(
|
|
||||||
appSettings.copy(isWifiNameByShellEnabled = !appSettings.isWifiNameByShellEnabled)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private suspend fun handleToggleTunnelOnEthernet(appSettings: AppSettings) =
|
private suspend fun handleToggleTunnelOnEthernet(appSettings: AppSettings) =
|
||||||
saveSettings(
|
saveSettings(
|
||||||
appSettings.copy(isTunnelOnEthernetEnabled = !appSettings.isTunnelOnEthernetEnabled)
|
appSettings.copy(isTunnelOnEthernetEnabled = !appSettings.isTunnelOnEthernetEnabled)
|
||||||
@@ -690,7 +719,7 @@ constructor(
|
|||||||
if (tunnels.isEmpty()) return
|
if (tunnels.isEmpty()) return
|
||||||
val (files, shareFileName) =
|
val (files, shareFileName) =
|
||||||
when (configType) {
|
when (configType) {
|
||||||
ConfigType.AMNEZIA -> {
|
ConfigType.AM -> {
|
||||||
val amFiles = fileUtils.createAmFiles(tunnels)
|
val amFiles = fileUtils.createAmFiles(tunnels)
|
||||||
if (amFiles.isEmpty()) {
|
if (amFiles.isEmpty()) {
|
||||||
throw IOException("No valid Amnezia config files created")
|
throw IOException("No valid Amnezia config files created")
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
package com.zaneschepke.wireguardautotunnel.viewmodel.event
|
package com.zaneschepke.wireguardautotunnel.viewmodel.event
|
||||||
|
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import com.zaneschepke.wireguardautotunnel.domain.entity.TunnelConf
|
import com.zaneschepke.networkmonitor.AndroidNetworkMonitor
|
||||||
import com.zaneschepke.wireguardautotunnel.domain.enums.ConfigType
|
import com.zaneschepke.wireguardautotunnel.domain.enums.ConfigType
|
||||||
|
import com.zaneschepke.wireguardautotunnel.domain.model.TunnelConf
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.state.AppViewState
|
import com.zaneschepke.wireguardautotunnel.ui.state.AppViewState
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.theme.Theme
|
import com.zaneschepke.wireguardautotunnel.ui.theme.Theme
|
||||||
import com.zaneschepke.wireguardautotunnel.util.StringValue
|
import com.zaneschepke.wireguardautotunnel.util.StringValue
|
||||||
@@ -78,6 +79,9 @@ sealed class AppEvent {
|
|||||||
|
|
||||||
data class SetTheme(val theme: Theme) : AppEvent()
|
data class SetTheme(val theme: Theme) : AppEvent()
|
||||||
|
|
||||||
|
data class SetDetectionMethod(val detectionMethod: AndroidNetworkMonitor.WifiDetectionMethod) :
|
||||||
|
AppEvent()
|
||||||
|
|
||||||
data object ToggleAutoTunnelOnWifi : AppEvent()
|
data object ToggleAutoTunnelOnWifi : AppEvent()
|
||||||
|
|
||||||
data object ToggleAutoTunnelOnCellular : AppEvent()
|
data object ToggleAutoTunnelOnCellular : AppEvent()
|
||||||
@@ -90,8 +94,6 @@ sealed class AppEvent {
|
|||||||
|
|
||||||
data object ToggleAutoTunnelWildcards : AppEvent()
|
data object ToggleAutoTunnelWildcards : AppEvent()
|
||||||
|
|
||||||
data object ToggleRootShellWifi : AppEvent()
|
|
||||||
|
|
||||||
data class DeleteTrustedSSID(val ssid: String) : AppEvent()
|
data class DeleteTrustedSSID(val ssid: String) : AppEvent()
|
||||||
|
|
||||||
data class SaveTrustedSSID(val ssid: String) : AppEvent()
|
data class SaveTrustedSSID(val ssid: String) : AppEvent()
|
||||||
|
|||||||
@@ -0,0 +1,2 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources></resources>
|
||||||
@@ -82,7 +82,7 @@
|
|||||||
<string name="no_browser_detected">Žádný prohlížeč nebyl nalezen</string>
|
<string name="no_browser_detected">Žádný prohlížeč nebyl nalezen</string>
|
||||||
<string name="read_logs">Přečíst si logy</string>
|
<string name="read_logs">Přečíst si logy</string>
|
||||||
<string name="pin_created">PIN úspěšně vytvořen</string>
|
<string name="pin_created">PIN úspěšně vytvořen</string>
|
||||||
<string name="enter_pin">Vložte Váš PIN</string>
|
<string name="enter_pin">Zadejte Váš PIN</string>
|
||||||
<string name="enable_app_lock">Zapnout zámek aplikace</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="restart_on_ping">Restartovat při selhání pingu</string>
|
||||||
<string name="mobile_data_tunnel">Nastavit jako tunel pro mobilní data</string>
|
<string name="mobile_data_tunnel">Nastavit jako tunel pro mobilní data</string>
|
||||||
@@ -163,4 +163,162 @@
|
|||||||
<string name="learn_more">Zjistit více</string>
|
<string name="learn_more">Zjistit více</string>
|
||||||
<string name="stop">zastavit</string>
|
<string name="stop">zastavit</string>
|
||||||
<string name="server_ipv4">Překlad názvu hostitele IPv4</string>
|
<string name="server_ipv4">Překlad názvu hostitele IPv4</string>
|
||||||
|
<string name="select_all">Vybrat vše</string>
|
||||||
|
<string name="share">Sdílet</string>
|
||||||
|
<string name="trusted_ssid_value_description">Odeslat SSID</string>
|
||||||
|
<string name="app_settings">nastavení aplikace</string>
|
||||||
|
<string name="debounce_delay">Zpoždění odezvy</string>
|
||||||
|
<string name="always_on_message">Autorizace připojení VPN byla zamítnuta. Zkontrolujte prosím</string>
|
||||||
|
<string name="bio_not_created">Biometrické údaje nebyly vytvořeny</string>
|
||||||
|
<string name="bio_not_supported">Biometrie není podporována</string>
|
||||||
|
<string name="bio_subtitle">Přihlášení pomocí biometrických údajů</string>
|
||||||
|
<string name="config_error">Chybná konfigurace</string>
|
||||||
|
<string name="prominent_background_location_title">Zpřístupnění stávající polohy na pozadí</string>
|
||||||
|
<string name="vpn_denied_dialog_title">Povolení zamítnuto</string>
|
||||||
|
<string name="app_permission_title">Řídicí most pro WG tunely</string>
|
||||||
|
<string name="app_permission_description">Ovládání funkcí tunelu a automatického tunelu.</string>
|
||||||
|
<string name="enable_remote_app_control">Povolit vzdálené ovládání aplikace</string>
|
||||||
|
<string name="tunnel_starting">Spuštění tunelu</string>
|
||||||
|
<string name="bio_auth_title">Biometrické ověření</string>
|
||||||
|
<string name="nothing_here_yet">Zatím zde nic není!</string>
|
||||||
|
<string name="export_success">Export byl úspěšně dokončen</string>
|
||||||
|
<string name="download">Stáhnout</string>
|
||||||
|
<string name="check_for_update">Zkontrolovat aktualizaci</string>
|
||||||
|
<string name="update_check_failed">Kontrola aktualizace se nezdařila.</string>
|
||||||
|
<string name="version_template">Verze: %1$s</string>
|
||||||
|
<string name="update_download_failed">Stažení aktualizace se nezdařilo.</string>
|
||||||
|
<string name="update_available">Dostupná aktualizace!</string>
|
||||||
|
<string name="download_and_install">Stáhnout a nainstalovat</string>
|
||||||
|
<string name="allow">Povolit</string>
|
||||||
|
<string name="permission_required">Je vyžadováno oprávnění</string>
|
||||||
|
<string name="licenses">Licence</string>
|
||||||
|
<string name="latest_installed">Již používáte nejnovější verzi.</string>
|
||||||
|
<string name="install_updated_permission">Tato aplikace potřebuje oprávnění k instalaci aktualizací.</string>
|
||||||
|
<string name="checking_for_update">Kontrola aktualizací</string>
|
||||||
|
<string name="add_from_url">Přidat z adresy URL</string>
|
||||||
|
<string name="inactive">Neaktivní</string>
|
||||||
|
<string name="auth_error">Chyba: neautorizováno</string>
|
||||||
|
<string name="kernel_name_error">Chyba názvu modulu jádra</string>
|
||||||
|
<string name="export_failed">Export se nezdařil</string>
|
||||||
|
<string name="delete">Smazat</string>
|
||||||
|
<string name="export_tunnels_wireguard">Exportovat tunely jako WireGuard</string>
|
||||||
|
<string name="export_tunnels_amnezia">Exportovat tunely jako Amnezia</string>
|
||||||
|
<string name="remote_key_template">Klíč: %1$s</string>
|
||||||
|
<string name="active">Aktivní</string>
|
||||||
|
<string name="service_running_error">Chyba: Služba není spuštěna</string>
|
||||||
|
<string name="wifi_name_template">Aktivní: %1$s</string>
|
||||||
|
<string name="tunnel_error_template">Tunel selhal s: %1$s</string>
|
||||||
|
<string name="camera_permission_required">Vyžadováno oprávnění k použití fotoaparátu</string>
|
||||||
|
<string name="info">Informace</string>
|
||||||
|
<string name="copy">Kopírovat</string>
|
||||||
|
<string name="status">Stav</string>
|
||||||
|
<string name="launch_app_settings">Spustit nastavení aplikace</string>
|
||||||
|
<string name="tunnel_running">Tunel je v provozu</string>
|
||||||
|
<string name="wildcards_active">Zástupné znaky(wildcards) aktivní</string>
|
||||||
|
<string name="root_accepted">Root shell přijata</string>
|
||||||
|
<string name="background_location_message">Autorizace povolit vždy polohu a/nebo přesná poloha je vyžadováno pro tuto funkci. Viz</string>
|
||||||
|
<string name="update_check_unsupported">Kontrola aktualizací není u tohoto typu sestavení podporována.</string>
|
||||||
|
<string name="background_location_message2">abyste se ujistili, že jsou tato oprávnění povolena</string>
|
||||||
|
<string name="darker">Tmavší</string>
|
||||||
|
<string name="amoled">AMOLED</string>
|
||||||
|
<string name="default_ping_ip">(nepovinné, výchozí hodnota je peers)</string>
|
||||||
|
<string name="monitoring_state_changes">Monitorování změn stavu</string>
|
||||||
|
<string name="pre_up">Před aktivací</string>
|
||||||
|
<string name="pre_down">Před deaktivací</string>
|
||||||
|
<string name="post_up">Po aktivaci</string>
|
||||||
|
<string name="optional_default">"nepovinné, výchozí: "</string>
|
||||||
|
<string name="flavor_template">Varianta: %1$s</string>
|
||||||
|
<string name="security_template">Zabezpečení: %1$s</string>
|
||||||
|
<string name="done">Hotovo</string>
|
||||||
|
<string name="wireguard">WireGuard</string>
|
||||||
|
<string name="amnezia">Amnezia</string>
|
||||||
|
<string name="show_qr">Zobrazit QR kód</string>
|
||||||
|
<string name="always_on_message2">ujistěte se, že je pro všechny ostatní aplikace vypnutá funkce trvalé připojení VPN, a zkuste to znovu</string>
|
||||||
|
<string name="use_wildcards">Použít zástupné znaky(wildcards) pro názvy</string>
|
||||||
|
<string name="multiple">Několik</string>
|
||||||
|
<string name="enter_config_url">Zadejte adresu URL konfigurace</string>
|
||||||
|
<string name="search">Hledat</string>
|
||||||
|
<string name="join_matrix">Připojte se k Matrix komunitě</string>
|
||||||
|
<string name="join_telegram">Připojte se k Telegram komunitě</string>
|
||||||
|
<string name="post_down">Po deaktivaci</string>
|
||||||
|
<string name="invalid_config_error">chyba_neplatné_konfigurace</string>
|
||||||
|
<string name="error_download_failed">Nepodařilo se stáhnout konfiguraci</string>
|
||||||
|
<string name="select">Vybrat</string>
|
||||||
|
<string name="save">Uložit</string>
|
||||||
|
<string name="dns_resolve_error">Chyba překladu dns</string>
|
||||||
|
<string name="bio_update_required">Vyžadována aktualizace biometrického zabezpečení</string>
|
||||||
|
<string name="export_logs">Exportovat uložené protokoly</string>
|
||||||
|
<string name="add_tunnel">Přidat tunel</string>
|
||||||
|
<string name="delete_logs">Smazat a vyčistit protokoly</string>
|
||||||
|
<string name="dropdown">Rozbalovací nabídka</string>
|
||||||
|
<string name="select_all">Vybrat vše</string>
|
||||||
|
<string name="share">Sdílet</string>
|
||||||
|
<string name="trusted_ssid_value_description">Odeslat SSID</string>
|
||||||
|
<string name="app_settings">nastavení aplikace</string>
|
||||||
|
<string name="debounce_delay">Debounce zpoždění</string>
|
||||||
|
<string name="always_on_message">Autorizace připojení VPN byla zamítnuta. Zkontrolujte prosím</string>
|
||||||
|
<string name="bio_not_created">Biometrické údaje nebyly vytvořeny</string>
|
||||||
|
<string name="bio_not_supported">Biometrie není podporována</string>
|
||||||
|
<string name="bio_subtitle">Přihlášení pomocí biometrických údajů</string>
|
||||||
|
<string name="config_error">Chybná konfigurace</string>
|
||||||
|
<string name="prominent_background_location_title">Zpřístupnění stávající polohy na pozadí</string>
|
||||||
|
<string name="vpn_denied_dialog_title">Povolení zamítnuto</string>
|
||||||
|
<string name="app_permission_title">Řídicí most pro WG tunely</string>
|
||||||
|
<string name="app_permission_description">Ovládání funkcí tunelu a automatického tunelu.</string>
|
||||||
|
<string name="enable_remote_app_control">Povolit vzdálené ovládání aplikace</string>
|
||||||
|
<string name="tunnel_starting">Spuštění tunelu</string>
|
||||||
|
<string name="bio_auth_title">Biometrické ověření</string>
|
||||||
|
<string name="nothing_here_yet">Zatím zde nic není!</string>
|
||||||
|
<string name="export_success">Export byl úspěšně dokončen</string>
|
||||||
|
<string name="download">Stáhnout</string>
|
||||||
|
<string name="check_for_update">Zkontrolovat aktualizaci</string>
|
||||||
|
<string name="update_check_failed">Kontrola aktualizace se nezdařila.</string>
|
||||||
|
<string name="version_template">Verze: %1$s</string>
|
||||||
|
<string name="update_download_failed">Stažení aktualizace se nezdařilo.</string>
|
||||||
|
<string name="update_available">Dostupná aktualizace!</string>
|
||||||
|
<string name="download_and_install">Stáhnout a nainstalovat</string>
|
||||||
|
<string name="allow">Povolit</string>
|
||||||
|
<string name="permission_required">Je vyžadováno oprávnění</string>
|
||||||
|
<string name="licenses">Licence</string>
|
||||||
|
<string name="latest_installed">Již používáte nejnovější verzi.</string>
|
||||||
|
<string name="install_updated_permission">Tato aplikace potřebuje oprávnění k instalaci aktualizací.</string>
|
||||||
|
<string name="checking_for_update">Kontrola aktualizací</string>
|
||||||
|
<string name="add_from_url">Přidat z adresy URL</string>
|
||||||
|
<string name="inactive">Neaktivní</string>
|
||||||
|
<string name="auth_error">Chyba: neautorizováno</string>
|
||||||
|
<string name="kernel_name_error">Chyba názvu modulu jádra</string>
|
||||||
|
<string name="export_failed">Export se nezdařil</string>
|
||||||
|
<string name="delete">Smazat</string>
|
||||||
|
<string name="export_tunnels_wireguard">Exportovat tunely jako WireGuard</string>
|
||||||
|
<string name="export_tunnels_amnezia">Exportovat tunely jako Amnezia</string>
|
||||||
|
<string name="remote_key_template">Klíč: %1$s</string>
|
||||||
|
<string name="active">Aktivní</string>
|
||||||
|
<string name="service_running_error">Chyba: Služba není spuštěna</string>
|
||||||
|
<string name="wifi_name_template">Aktivní: %1$s</string>
|
||||||
|
<string name="tunnel_error_template">Tunel selhal s: %1$s</string>
|
||||||
|
<string name="camera_permission_required">Vyžadováno oprávnění k použití fotoaparátu</string>
|
||||||
|
<string name="info">Informace</string>
|
||||||
|
<string name="copy">Kopírovat</string>
|
||||||
|
<string name="status">Stav</string>
|
||||||
|
<string name="launch_app_settings">Spustit nastavení aplikace</string>
|
||||||
|
<string name="tunnel_running">Tunel je v provozu</string>
|
||||||
|
<string name="wildcards_active">Wildcards aktivní</string>
|
||||||
|
<string name="root_accepted">Root shell přijata</string>
|
||||||
|
<string name="background_location_message">Autorizace povolit vždy polohu a/nebo přesná poloha je vyžadováno pro tuto funkci. Viz</string>
|
||||||
|
<string name="update_check_unsupported">Kontrola aktualizací není u tohoto typu sestavení podporována.</string>
|
||||||
|
<string name="background_location_message2">abyste se ujistili, že jsou tato oprávnění povolena</string>
|
||||||
|
<string name="darker">Tmavší</string>
|
||||||
|
<string name="amoled">AMOLED</string>
|
||||||
|
<string name="default_ping_ip">(nepovinné, výchozí hodnota je peers)</string>
|
||||||
|
<string name="monitoring_state_changes">Monitorování změn stavu</string>
|
||||||
|
<string name="pre_up">Před aktivací</string>
|
||||||
|
<string name="pre_down">Před deaktivací</string>
|
||||||
|
<string name="post_up">Po aktivaci</string>
|
||||||
|
<string name="optional_default">"nepovinné, výchozí: "</string>
|
||||||
|
<string name="flavor_template">Varianta: %1$s</string>
|
||||||
|
<string name="security_template">Zabezpečení: %1$s</string>
|
||||||
|
<string name="done">Hotovo</string>
|
||||||
|
<string name="wireguard">WireGuard</string>
|
||||||
|
<string name="amnezia">Amnezia</string>
|
||||||
|
<string name="show_qr">Zobrazit QR kód</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
<string name="turn_on_tunnel">Für diese Aktion muss ein aktiver Tunnel bestehen</string>
|
<string name="turn_on_tunnel">Für diese Aktion muss ein aktiver Tunnel bestehen</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_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ür die Nutzung von WG Tunnel!</string>
|
||||||
<string name="trusted_ssid_value_description">SSID übermitteln</string>
|
<string name="trusted_ssid_value_description">SSID übermitteln</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 öffnen</string>
|
<string name="open_file">Datei öffnen</string>
|
||||||
@@ -140,7 +140,7 @@
|
|||||||
<string name="local_logging">Lokales Logging</string>
|
<string name="local_logging">Lokales Logging</string>
|
||||||
<string name="enable_local_logging">Lokales Logging aktivieren</string>
|
<string name="enable_local_logging">Lokales Logging aktivieren</string>
|
||||||
<string name="add_from_clipboard">Aus Zwischenablage einfügen</string>
|
<string name="add_from_clipboard">Aus Zwischenablage einfügen</string>
|
||||||
<string name="kill_switch">Kill Switch</string>
|
<string name="kill_switch">Notschalter</string>
|
||||||
<string name="automatic">Automatisch</string>
|
<string name="automatic">Automatisch</string>
|
||||||
<string name="language">Sprache</string>
|
<string name="language">Sprache</string>
|
||||||
<string name="display_theme">Anzeigetheme</string>
|
<string name="display_theme">Anzeigetheme</string>
|
||||||
@@ -227,4 +227,29 @@
|
|||||||
<string name="wifi_name_template">Aktiv: %1$s</string>
|
<string name="wifi_name_template">Aktiv: %1$s</string>
|
||||||
<string name="delete">Löschen</string>
|
<string name="delete">Löschen</string>
|
||||||
<string name="nothing_here_yet">Noch nix hier!</string>
|
<string name="nothing_here_yet">Noch nix hier!</string>
|
||||||
|
<string name="share">Teilen</string>
|
||||||
|
<string name="select_all">Alles auswählen</string>
|
||||||
|
<string name="version_template">Version: %1$s</string>
|
||||||
|
<string name="export_success">Export Erfolg</string>
|
||||||
|
<string name="download">Download</string>
|
||||||
|
<string name="check_for_update">Auf Update prüfen</string>
|
||||||
|
<string name="update_check_failed">Updateprüfung fehlgeschlagen.</string>
|
||||||
|
<string name="checking_for_update">Überpüfe auf Updates</string>
|
||||||
|
<string name="update_download_failed">Updatedownload fehlgeschlagen.</string>
|
||||||
|
<string name="update_available">Update verfügbar!</string>
|
||||||
|
<string name="download_and_install">Herunterladen und installieren</string>
|
||||||
|
<string name="permission_required">Berechtigung erforderlich</string>
|
||||||
|
<string name="licenses">Lizenzen</string>
|
||||||
|
<string name="allow">Erlauben</string>
|
||||||
|
<string name="install_updated_permission">Diese App benötigt die Berechtigung, um Updates zu installieren.</string>
|
||||||
|
<string name="latest_installed">Du verwendesz bereits die neueste Version.</string>
|
||||||
|
<string name="security_template">Sicherheit: %1$s</string>
|
||||||
|
<string name="amoled">AMOLED</string>
|
||||||
|
<string name="flavor_template">Variante: %1$s</string>
|
||||||
|
<string name="darker">Dunkler</string>
|
||||||
|
<string name="update_check_unsupported">Updateprüfung wird bei diesem Build-Typ nicht unterstützt.</string>
|
||||||
|
<string name="wireguard">WireGuard</string>
|
||||||
|
<string name="done">Erledigt</string>
|
||||||
|
<string name="show_qr">QR anzeigen</string>
|
||||||
|
<string name="amnezia">Amnezia</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user