mirror of
https://github.com/wgtunnel/android.git
synced 2026-07-03 14:07:49 +02:00
Compare commits
9 Commits
testing123
..
3.9.1
| Author | SHA1 | Date | |
|---|---|---|---|
| a05f11739d | |||
| fe519be5cc | |||
| ab0f67c897 | |||
| a5639cd129 | |||
| babbab051f | |||
| 2e984e8b6f | |||
| 044e6da7f5 | |||
| 77aa2c30d7 | |||
| e773238e6b |
@@ -1,4 +1,6 @@
|
|||||||
name: Build
|
name: build
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
on:
|
on:
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
@@ -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.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-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,9 @@
|
|||||||
name: publish
|
name: publish
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
packages: write
|
||||||
|
|
||||||
on:
|
on:
|
||||||
schedule:
|
|
||||||
- cron: "4 3 * * *"
|
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
inputs:
|
inputs:
|
||||||
track:
|
track:
|
||||||
@@ -22,14 +23,13 @@ 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"
|
||||||
@@ -46,71 +46,37 @@ on:
|
|||||||
required: false
|
required: false
|
||||||
default: standalone
|
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: ${{ 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: ${{ inputs.release_type }}
|
||||||
flavor: fdroid
|
flavor: fdroid
|
||||||
|
|
||||||
build-standalone:
|
build-standalone:
|
||||||
if: ${{ inputs.release_type == 'release' || inputs.release_type == 'nightly' || inputs.release_type == 'prerelease' || inputs.flavor == 'standalone' }}
|
if: ${{ 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: ${{ inputs.release_type }}
|
||||||
flavor: standalone
|
flavor: standalone
|
||||||
|
|
||||||
publish:
|
publish:
|
||||||
needs:
|
needs:
|
||||||
- check_commits
|
|
||||||
- build-standalone
|
- build-standalone
|
||||||
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: 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,22 +92,26 @@ 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: ${{ inputs.release_type == 'release' }}
|
||||||
run: |
|
run: |
|
||||||
@@ -151,36 +121,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: ${{ 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 +156,13 @@ jobs:
|
|||||||
|
|
||||||
### Changelog
|
### Changelog
|
||||||
${{ steps.changelog.outputs.changes }}
|
${{ steps.changelog.outputs.changes }}
|
||||||
tag_name: ${{ env.TAG_NAME }}
|
tag_name: ${{ github.event.inputs.tag_name }}
|
||||||
name: ${{ env.TAG_NAME }}
|
name: ${{ github.event.inputs.tag_name }}
|
||||||
draft: false
|
draft: false
|
||||||
prerelease: ${{ inputs.release_type == 'prerelease' || inputs.release_type == '' || inputs.release_type == 'nightly' }}
|
prerelease: ${{ inputs.release_type == 'prerelease' }}
|
||||||
make_latest: ${{ inputs.release_type == 'release' }}
|
make_latest: ${{ 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 }}
|
||||||
|
|
||||||
@@ -224,7 +180,7 @@ jobs:
|
|||||||
event-type: fdroid-update
|
event-type: fdroid-update
|
||||||
|
|
||||||
publish-play:
|
publish-play:
|
||||||
if: ${{ inputs.track != 'none' && inputs.track != '' }}
|
if: ${{ inputs.track != 'none' }}
|
||||||
name: Publish to Google Play
|
name: Publish to Google Play
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
|||||||
@@ -110,9 +110,9 @@ android {
|
|||||||
}
|
}
|
||||||
|
|
||||||
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 {
|
||||||
@@ -124,7 +124,6 @@ android {
|
|||||||
licensee {
|
licensee {
|
||||||
Constants.allowedLicenses.forEach { allow(it) }
|
Constants.allowedLicenses.forEach { allow(it) }
|
||||||
allowUrl(Constants.XZING_LICENSE_URL)
|
allowUrl(Constants.XZING_LICENSE_URL)
|
||||||
allowUrl("https://rafaellins.mit-license.org/2021/")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
applicationVariants.all {
|
applicationVariants.all {
|
||||||
@@ -198,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)
|
||||||
@@ -210,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)
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -54,7 +53,6 @@ import com.zaneschepke.wireguardautotunnel.ui.screens.autotunnel.disclosure.Loca
|
|||||||
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
|
||||||
@@ -298,11 +296,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)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|
||||||
@@ -111,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,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,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,8 +29,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()
|
||||||
|
|||||||
+10
-10
@@ -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
|
||||||
@@ -240,9 +231,18 @@ fun currentNavBackStackEntryAsNavBarState(
|
|||||||
showBottom = true,
|
showBottom = true,
|
||||||
topTitle = { tunnel?.name?.let { Text(it) } },
|
topTitle = { tunnel?.name?.let { Text(it) } },
|
||||||
topTrailing = {
|
topTrailing = {
|
||||||
|
Row {
|
||||||
|
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) {
|
ActionIconButton(Icons.Rounded.Edit, R.string.edit_tunnel) {
|
||||||
tunnel?.id?.let { navController.navigate(Route.Config(it)) }
|
tunnel?.id?.let { navController.navigate(Route.Config(it)) }
|
||||||
}
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
route = args?.let { Route.TunnelOptions(it.id) },
|
route = args?.let { Route.TunnelOptions(it.id) },
|
||||||
)
|
)
|
||||||
|
|||||||
+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,17 @@ 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 +69,7 @@ fun MainScreen(appUiState: AppUiState, appViewState: AppViewState, viewModel: Ap
|
|||||||
)
|
)
|
||||||
return@rememberLauncherForActivityResult
|
return@rememberLauncherForActivityResult
|
||||||
}
|
}
|
||||||
navController.navigate(Route.Scanner)
|
scanLauncher.launch(ScanOptions().setDesiredBarcodeFormats(ScanOptions.QR_CODE))
|
||||||
}
|
}
|
||||||
|
|
||||||
if (appViewState.showModal == AppViewState.ModalType.DELETE) {
|
if (appViewState.showModal == AppViewState.ModalType.DELETE) {
|
||||||
|
|||||||
+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 {
|
||||||
|
|||||||
-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() }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+37
-2
@@ -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.entity.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,
|
||||||
|
|||||||
+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.entity.TunnelConf
|
||||||
|
import com.zaneschepke.wireguardautotunnel.domain.enums.ConfigType
|
||||||
|
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
|
||||||
|
}
|
||||||
+2
-1
@@ -87,7 +87,8 @@ fun SupportScreen(viewModel: SupportViewModel = hiltViewModel(), appViewModel: A
|
|||||||
},
|
},
|
||||||
confirmText = {
|
confirmText = {
|
||||||
Text(
|
Text(
|
||||||
if (BuildConfig.FLAVOR != Constants.STANDALONE_FLAVOR) 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)
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ data class AppViewState(
|
|||||||
NONE,
|
NONE,
|
||||||
DELETE,
|
DELETE,
|
||||||
INFO,
|
INFO,
|
||||||
|
QR,
|
||||||
}
|
}
|
||||||
|
|
||||||
enum class BottomSheet {
|
enum class BottomSheet {
|
||||||
|
|||||||
+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
|
||||||
|
}
|
||||||
|
|||||||
@@ -39,8 +39,6 @@ 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
|
||||||
@@ -402,6 +400,7 @@ constructor(
|
|||||||
|
|
||||||
private suspend fun handleClipboardImport(config: String, tunnels: List<TunnelConf>) {
|
private suspend fun handleClipboardImport(config: String, tunnels: List<TunnelConf>) {
|
||||||
runCatching {
|
runCatching {
|
||||||
|
Timber.d("Config: $config")
|
||||||
val amConfig = TunnelConf.configFromAmQuick(config)
|
val amConfig = TunnelConf.configFromAmQuick(config)
|
||||||
val tunnelConf = TunnelConf.tunnelConfigFromAmConfig(amConfig)
|
val tunnelConf = TunnelConf.tunnelConfigFromAmConfig(amConfig)
|
||||||
saveTunnel(
|
saveTunnel(
|
||||||
@@ -690,7 +689,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")
|
||||||
|
|||||||
@@ -258,4 +258,8 @@
|
|||||||
<string name="update_check_unsupported">Update check not supported this build type.</string>
|
<string name="update_check_unsupported">Update check not supported this build type.</string>
|
||||||
<string name="darker">Darker</string>
|
<string name="darker">Darker</string>
|
||||||
<string name="amoled">AMOLED</string>
|
<string name="amoled">AMOLED</string>
|
||||||
|
<string name="show_qr">Show QR</string>
|
||||||
|
<string name="amnezia">Amnezia</string>
|
||||||
|
<string name="wireguard">WireGuard</string>
|
||||||
|
<string name="done">Done</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -6,6 +6,8 @@
|
|||||||
<item name="android:colorPrimary">@color/background</item>
|
<item name="android:colorPrimary">@color/background</item>
|
||||||
<item name="android:windowAllowReturnTransitionOverlap">true</item>
|
<item name="android:windowAllowReturnTransitionOverlap">true</item>
|
||||||
<item name="android:windowAllowEnterTransitionOverlap">true</item>
|
<item name="android:windowAllowEnterTransitionOverlap">true</item>
|
||||||
|
<item name="android:statusBarColor">@android:color/transparent</item>
|
||||||
|
<item name="android:navigationBarColor">@android:color/transparent</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style name="Theme.App.Start" parent="@style/Theme.SplashScreen">
|
<style name="Theme.App.Start" parent="@style/Theme.SplashScreen">
|
||||||
|
|||||||
@@ -8,6 +8,6 @@ repositories {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation("org.semver4j:semver4j:5.6.0")
|
implementation("org.semver4j:semver4j:5.7.0")
|
||||||
implementation("org.ajoberstar.grgit:grgit-core:5.3.0")
|
implementation("org.ajoberstar.grgit:grgit-core:5.3.0")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
object Constants {
|
object Constants {
|
||||||
const val VERSION_NAME = "3.9.0"
|
const val VERSION_NAME = "3.9.1"
|
||||||
const val JVM_TARGET = "17"
|
const val JVM_TARGET = "17"
|
||||||
const val VERSION_CODE = 38900
|
const val VERSION_CODE = 39100
|
||||||
const val TARGET_SDK = 35
|
const val TARGET_SDK = 35
|
||||||
const val MIN_SDK = 26
|
const val MIN_SDK = 26
|
||||||
const val APP_ID = "com.zaneschepke.wireguardautotunnel"
|
const val APP_ID = "com.zaneschepke.wireguardautotunnel"
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
What's new:
|
||||||
|
- Fix tunnel no connectivity DNS bug
|
||||||
|
- Fix tunnel stuck starting bug
|
||||||
|
- Add view tunnel QR code
|
||||||
|
- Add resolved endpoint to statistics
|
||||||
|
- Various other bug fixes
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
[versions]
|
[versions]
|
||||||
accompanist = "0.37.2"
|
accompanist = "0.37.3"
|
||||||
activityCompose = "1.10.1"
|
activityCompose = "1.10.1"
|
||||||
amneziawgAndroid = "1.3.10"
|
amneziawgAndroid = "1.4.0"
|
||||||
androidx-junit = "1.2.1"
|
androidx-junit = "1.2.1"
|
||||||
appcompat = "1.7.0"
|
appcompat = "1.7.0"
|
||||||
biometricKtx = "1.2.0-alpha05"
|
biometricKtx = "1.2.0-alpha05"
|
||||||
@@ -18,15 +18,15 @@ lifecycle-runtime-compose = "2.8.7"
|
|||||||
material3 = "1.3.2"
|
material3 = "1.3.2"
|
||||||
navigationCompose = "2.8.9"
|
navigationCompose = "2.8.9"
|
||||||
pinLockCompose = "1.0.4"
|
pinLockCompose = "1.0.4"
|
||||||
qrcodeKotlin = "4.4.1"
|
qrose = "1.0.1"
|
||||||
roomVersion = "2.7.1"
|
roomVersion = "2.7.1"
|
||||||
semver4j = "3.1.0"
|
semver4j = "3.1.0"
|
||||||
slf4jAndroid = "1.7.36"
|
slf4jAndroid = "1.7.36"
|
||||||
timber = "5.0.1"
|
timber = "5.0.1"
|
||||||
tunnel = "1.2.16"
|
tunnel = "1.3.0"
|
||||||
androidGradlePlugin = "8.9.2"
|
androidGradlePlugin = "8.9.2"
|
||||||
kotlin = "2.1.20"
|
kotlin = "2.1.20"
|
||||||
ksp = "2.1.20-2.0.0"
|
ksp = "2.1.20-2.0.1"
|
||||||
composeBom = "2025.04.01"
|
composeBom = "2025.04.01"
|
||||||
compose = "1.8.0"
|
compose = "1.8.0"
|
||||||
icons = "1.7.8"
|
icons = "1.7.8"
|
||||||
@@ -99,7 +99,7 @@ lifecycle-runtime-compose = { module = "androidx.lifecycle:lifecycle-runtime-com
|
|||||||
material-icons-extended = { module = "androidx.compose.material:material-icons-extended", version.ref = "icons" }
|
material-icons-extended = { module = "androidx.compose.material:material-icons-extended", version.ref = "icons" }
|
||||||
|
|
||||||
pin-lock-compose = { module = "com.zaneschepke:pin_lock_compose", version.ref = "pinLockCompose" }
|
pin-lock-compose = { module = "com.zaneschepke:pin_lock_compose", version.ref = "pinLockCompose" }
|
||||||
qrcode-kotlin = { module = "io.github.g0dkar:qrcode-kotlin", version.ref = "qrcodeKotlin" }
|
qrose = { module = "io.github.alexzhirkevich:qrose", version.ref = "qrose" }
|
||||||
semver4j = { module = "com.vdurmont:semver4j", version.ref = "semver4j" }
|
semver4j = { module = "com.vdurmont:semver4j", version.ref = "semver4j" }
|
||||||
slf4j-android = { module = "org.slf4j:slf4j-android", version.ref = "slf4jAndroid" }
|
slf4j-android = { module = "org.slf4j:slf4j-android", version.ref = "slf4jAndroid" }
|
||||||
timber = { module = "com.jakewharton.timber:timber", version.ref = "timber" }
|
timber = { module = "com.jakewharton.timber:timber", version.ref = "timber" }
|
||||||
|
|||||||
Reference in New Issue
Block a user