mirror of
https://github.com/wgtunnel/android.git
synced 2026-07-03 14:07:49 +02:00
Compare commits
30 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 880d30fdfa | |||
| 765785ff41 | |||
| 9991e06c44 | |||
| 433d383b0b | |||
| 041f12dc77 | |||
| afdc49629c | |||
| f846d54d78 | |||
| ffd9c4192a | |||
| d6138b80eb | |||
| 0874db8bbf | |||
| 6d483459a6 | |||
| 7e4f055833 | |||
| 8ea432b4f6 | |||
| 6637539d1f | |||
| bfe3533030 | |||
| b61d49469f | |||
| 349b56b2e2 | |||
| 42aa378938 | |||
| 062f59aa33 | |||
| 883b9f7dae | |||
| 1f8d24c704 | |||
| 68ab3fdc52 | |||
| b4b96a7e77 | |||
| 59a70e53ff | |||
| 170b12ab79 | |||
| 0f365e2ef8 | |||
| 950d75b57f | |||
| ea90896061 | |||
| c62b328187 | |||
| 46a962a730 |
@@ -1,22 +0,0 @@
|
|||||||
# Contributor Code of Conduct
|
|
||||||
|
|
||||||
## Pledge
|
|
||||||
|
|
||||||
We as individuals involved in this project, pledge to participate in this
|
|
||||||
community in a respectful, constructive, and civil manner as we work towards a common goal
|
|
||||||
of delivering free, open source, and value adding software for all.
|
|
||||||
|
|
||||||
## Standard
|
|
||||||
|
|
||||||
The standard for this community is the Golden Rule.
|
|
||||||
|
|
||||||
> “Do unto others as you would have them do unto you.”
|
|
||||||
|
|
||||||
## Scope
|
|
||||||
|
|
||||||
This Code of Conduct applies to all spaces related to WG Tunnel.
|
|
||||||
|
|
||||||
## Incidents or Concerns
|
|
||||||
|
|
||||||
For any incidents or concerns, reach out to Zane at
|
|
||||||
<support@zaneschepke.com>.
|
|
||||||
@@ -114,15 +114,11 @@ jobs:
|
|||||||
- name: Get release apk path
|
- name: Get release apk path
|
||||||
id: apk-path
|
id: apk-path
|
||||||
run: echo "path=$(find . -regex '^.*/build/outputs/apk/${{ inputs.flavor }}/${{ inputs.build_type }}/.*\.apk$' -type f | head -1 | tail -c+2)" >> $GITHUB_OUTPUT
|
run: echo "path=$(find . -regex '^.*/build/outputs/apk/${{ inputs.flavor }}/${{ inputs.build_type }}/.*\.apk$' -type f | head -1 | tail -c+2)" >> $GITHUB_OUTPUT
|
||||||
- name: Upload APK
|
- name: Upload All APK Artifacts
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v5
|
||||||
with:
|
with:
|
||||||
name: android_artifacts_${{ inputs.flavor }}
|
name: android_artifacts_${{ inputs.flavor }}
|
||||||
path: >-
|
path: >-
|
||||||
app/build/outputs/apk/${{ inputs.flavor }}/${{ inputs.build_type }}/${{
|
app/build/outputs/apk/${{ inputs.flavor }}/${{ inputs.build_type }}/*.apk
|
||||||
inputs.flavor == 'fdroid' && inputs.build_type == 'release'
|
|
||||||
&& 'wgtunnel-fdroid-release-*.apk'
|
|
||||||
|| format('wgtunnel-{0}-v*.apk', inputs.flavor)
|
|
||||||
}}
|
|
||||||
retention-days: 1
|
retention-days: 1
|
||||||
if-no-files-found: warn
|
if-no-files-found: warn
|
||||||
@@ -26,6 +26,9 @@ jobs:
|
|||||||
echo "new_commits=$NEW_COMMITS" >> $GITHUB_OUTPUT
|
echo "new_commits=$NEW_COMMITS" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
build-standalone-nightly:
|
build-standalone-nightly:
|
||||||
|
needs:
|
||||||
|
- check_commits
|
||||||
|
if: ${{ needs.check_commits.outputs.has_new_commits > 0 && inputs.release_type != 'none' }}
|
||||||
uses: ./.github/workflows/build.yml
|
uses: ./.github/workflows/build.yml
|
||||||
secrets: inherit
|
secrets: inherit
|
||||||
with:
|
with:
|
||||||
@@ -34,7 +37,6 @@ jobs:
|
|||||||
|
|
||||||
publish:
|
publish:
|
||||||
needs:
|
needs:
|
||||||
- check_commits
|
|
||||||
- build-standalone-nightly
|
- build-standalone-nightly
|
||||||
if: ${{ needs.check_commits.outputs.has_new_commits > 0 && inputs.release_type != 'none' }}
|
if: ${{ needs.check_commits.outputs.has_new_commits > 0 && inputs.release_type != 'none' }}
|
||||||
name: publish-nightly
|
name: publish-nightly
|
||||||
@@ -69,7 +71,7 @@ jobs:
|
|||||||
run: mkdir ${{ github.workspace }}/temp
|
run: mkdir ${{ github.workspace }}/temp
|
||||||
|
|
||||||
- name: Download artifacts
|
- name: Download artifacts
|
||||||
uses: actions/download-artifact@v5
|
uses: actions/download-artifact@v6
|
||||||
with:
|
with:
|
||||||
pattern: android_artifacts_*
|
pattern: android_artifacts_*
|
||||||
path: ${{ github.workspace }}/temp
|
path: ${{ github.workspace }}/temp
|
||||||
@@ -124,4 +126,4 @@ jobs:
|
|||||||
files: |
|
files: |
|
||||||
${{ github.workspace }}/temp/**/*.apk
|
${{ github.workspace }}/temp/**/*.apk
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ github.token }}
|
GITHUB_TOKEN: ${{ secrets.PAT }}
|
||||||
@@ -1,10 +1,13 @@
|
|||||||
name: notifications
|
name: notifications
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
packages: write
|
||||||
|
|
||||||
on:
|
on:
|
||||||
issues:
|
issues:
|
||||||
types: [opened, closed]
|
types: [opened, closed]
|
||||||
release:
|
release:
|
||||||
types: [published]
|
types: [published, prereleased]
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
notify:
|
notify:
|
||||||
@@ -43,15 +46,23 @@ jobs:
|
|||||||
--data-urlencode "text=$TEXT"
|
--data-urlencode "text=$TEXT"
|
||||||
|
|
||||||
- name: Send to Telegram - New Release
|
- name: Send to Telegram - New Release
|
||||||
if: github.event_name == 'release' && github.event.action == 'published'
|
if: github.event_name == 'release' && ((github.event.action == 'published' && !github.event.release.prerelease) || (github.event.action == 'prereleased' && github.event.release.prerelease && github.event.release.name == 'nightly'))
|
||||||
env:
|
env:
|
||||||
NAME: ${{ github.event.release.name }}
|
NAME: ${{ github.event.release.name }}
|
||||||
TAG: ${{ github.event.release.tag_name }}
|
TAG: ${{ github.event.release.tag_name }}
|
||||||
BODY: ${{ github.event.release.body || 'No notes provided' }}
|
BODY: ${{ github.event.release.body || 'No notes provided' }}
|
||||||
URL: ${{ github.event.release.html_url }}
|
URL: ${{ github.event.release.html_url }}
|
||||||
|
ACTION: ${{ github.event.action }}
|
||||||
run: |
|
run: |
|
||||||
BODY_TRUNC="${BODY:0:200}" # Truncate to avoid spam
|
BODY_TRUNC="${BODY:0:200}" # Truncate to avoid spam
|
||||||
TEXT=$(echo -e "🚀 New Release *$NAME* ($TAG)\n\n$BODY_TRUNC\n\n[View Release]($URL)")
|
if [ "$ACTION" == "prereleased" ]; then
|
||||||
|
ICON="🌙"
|
||||||
|
PREFIX="New Nightly Release"
|
||||||
|
else
|
||||||
|
ICON="🚀"
|
||||||
|
PREFIX="New Release"
|
||||||
|
fi
|
||||||
|
TEXT=$(echo -e "$ICON $PREFIX *$NAME* ($TAG)\n\n$BODY_TRUNC\n\n[View Release]($URL)")
|
||||||
curl -s -X POST "https://api.telegram.org/bot${{ secrets.TELEGRAM_TOKEN }}/sendMessage" \
|
curl -s -X POST "https://api.telegram.org/bot${{ secrets.TELEGRAM_TOKEN }}/sendMessage" \
|
||||||
-d chat_id="${{ vars.TELEGRAM_CHAT_ID }}" \
|
-d chat_id="${{ vars.TELEGRAM_CHAT_ID }}" \
|
||||||
${{ vars.TELEGRAM_THREAD_ID && format('-d message_thread_id="{0}"', vars.TELEGRAM_THREAD_ID) || '' }} \
|
${{ vars.TELEGRAM_THREAD_ID && format('-d message_thread_id="{0}"', vars.TELEGRAM_THREAD_ID) || '' }} \
|
||||||
@@ -106,15 +117,23 @@ jobs:
|
|||||||
-d "$PAYLOAD"
|
-d "$PAYLOAD"
|
||||||
|
|
||||||
- name: Send to Matrix - New Release
|
- name: Send to Matrix - New Release
|
||||||
if: github.event_name == 'release' && github.event.action == 'published'
|
if: github.event_name == 'release' && ((github.event.action == 'published' && !github.event.release.prerelease) || (github.event.action == 'prereleased' && github.event.release.prerelease && github.event.release.name == 'nightly'))
|
||||||
env:
|
env:
|
||||||
NAME: ${{ github.event.release.name }}
|
NAME: ${{ github.event.release.name }}
|
||||||
TAG: ${{ github.event.release.tag_name }}
|
TAG: ${{ github.event.release.tag_name }}
|
||||||
BODY: ${{ github.event.release.body || 'No notes provided' }}
|
BODY: ${{ github.event.release.body || 'No notes provided' }}
|
||||||
URL: ${{ github.event.release.html_url }}
|
URL: ${{ github.event.release.html_url }}
|
||||||
|
ACTION: ${{ github.event.action }}
|
||||||
run: |
|
run: |
|
||||||
PLAIN_MESSAGE=$(echo -e "🚀 New Release $NAME ($TAG)\n\n$BODY\n\nView Release: $URL")
|
if [ "$ACTION" == "prereleased" ]; then
|
||||||
HTML_MESSAGE=$(echo -e "<p>🚀 New Release <strong>$NAME</strong> ($TAG)</p><p>$BODY</p><p><a href=\"$URL\">View Release</a></p>")
|
ICON="🌙"
|
||||||
|
PREFIX="New Nightly Release"
|
||||||
|
else
|
||||||
|
ICON="🚀"
|
||||||
|
PREFIX="New Release"
|
||||||
|
fi
|
||||||
|
PLAIN_MESSAGE=$(echo -e "$ICON $PREFIX $NAME ($TAG)\n\n$BODY\n\nView Release: $URL")
|
||||||
|
HTML_MESSAGE=$(echo -e "<p>$ICON $PREFIX <strong>$NAME</strong> ($TAG)</p><p>$BODY</p><p><a href=\"$URL\">View Release</a></p>")
|
||||||
PLAIN_MESSAGE="${PLAIN_MESSAGE:0:220}"
|
PLAIN_MESSAGE="${PLAIN_MESSAGE:0:220}"
|
||||||
PAYLOAD=$(jq -n --arg body "$PLAIN_MESSAGE" --arg formatted "$HTML_MESSAGE" '{
|
PAYLOAD=$(jq -n --arg body "$PLAIN_MESSAGE" --arg formatted "$HTML_MESSAGE" '{
|
||||||
"msgtype": "m.text",
|
"msgtype": "m.text",
|
||||||
|
|||||||
@@ -109,7 +109,7 @@ jobs:
|
|||||||
run: mkdir ${{ github.workspace }}/temp
|
run: mkdir ${{ github.workspace }}/temp
|
||||||
|
|
||||||
- name: Download artifacts
|
- name: Download artifacts
|
||||||
uses: actions/download-artifact@v5
|
uses: actions/download-artifact@v6
|
||||||
with:
|
with:
|
||||||
pattern: android_artifacts_*
|
pattern: android_artifacts_*
|
||||||
path: ${{ github.workspace }}/temp
|
path: ${{ github.workspace }}/temp
|
||||||
@@ -118,8 +118,8 @@ jobs:
|
|||||||
- name: Set version release notes
|
- name: Set version release notes
|
||||||
if: ${{ github.event_name == 'push' || 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_CODE=$(grep "const val VERSION_CODE" 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_CODE}.txt || echo "No changelog found for ${VERSION_CODE}")"
|
||||||
echo "RELEASE_NOTES<<EOF" >> $GITHUB_ENV
|
echo "RELEASE_NOTES<<EOF" >> $GITHUB_ENV
|
||||||
echo "$RELEASE_NOTES" >> $GITHUB_ENV
|
echo "$RELEASE_NOTES" >> $GITHUB_ENV
|
||||||
echo "EOF" >> $GITHUB_ENV
|
echo "EOF" >> $GITHUB_ENV
|
||||||
@@ -162,7 +162,7 @@ jobs:
|
|||||||
files: |
|
files: |
|
||||||
${{ github.workspace }}/temp/**/*.apk
|
${{ github.workspace }}/temp/**/*.apk
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ github.token }}
|
GITHUB_TOKEN: ${{ secrets.PAT }}
|
||||||
|
|
||||||
publish-fdroid-public:
|
publish-fdroid-public:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|||||||
@@ -21,8 +21,7 @@ and [AmneziaWG](https://docs.amnezia.org/documentation/amnezia-wg/)
|
|||||||
<div align="center">
|
<div align="center">
|
||||||
|
|
||||||
[](https://play.google.com/store/apps/details?id=com.zaneschepke.wireguardautotunnel)
|
[](https://play.google.com/store/apps/details?id=com.zaneschepke.wireguardautotunnel)
|
||||||
[](https://f-droid.org/packages/com.zaneschepke.wireguardautotunnel/)
|
[](https://github.com/zaneschepke/fdroid)
|
||||||
[](https://github.com/zaneschepke/fdroid)
|
|
||||||
[](https://apps.obtainium.imranr.dev/redirect?r=obtainium://app/%7B%22id%22%3A%22com.zaneschepke.wireguardautotunnel%22%2C%22url%22%3A%22https%3A%2F%2Fgithub.com%2Fzaneschepke%2Fwgtunnel%22%2C%22author%22%3A%22zaneschepke%22%2C%22name%22%3A%22WG%20Tunnel%22%2C%22preferredApkIndex%22%3A0%2C%22additionalSettings%22%3A%22%7B%5C%22includePrereleases%5C%22%3Afalse%2C%5C%22fallbackToOlderReleases%5C%22%3Atrue%2C%5C%22filterReleaseTitlesByRegEx%5C%22%3A%5C%22%5C%22%2C%5C%22filterReleaseNotesByRegEx%5C%22%3A%5C%22%5C%22%2C%5C%22verifyLatestTag%5C%22%3Atrue%2C%5C%22sortMethodChoice%5C%22%3A%5C%22date%5C%22%2C%5C%22useLatestAssetDateAsReleaseDate%5C%22%3Afalse%2C%5C%22releaseTitleAsVersion%5C%22%3Afalse%2C%5C%22trackOnly%5C%22%3Afalse%2C%5C%22versionExtractionRegEx%5C%22%3A%5C%22%5C%22%2C%5C%22matchGroupToUse%5C%22%3A%5C%22%5C%22%2C%5C%22versionDetection%5C%22%3Atrue%2C%5C%22releaseDateAsVersion%5C%22%3Afalse%2C%5C%22useVersionCodeAsOSVersion%5C%22%3Afalse%2C%5C%22apkFilterRegEx%5C%22%3A%5C%22%5C%22%2C%5C%22invertAPKFilter%5C%22%3Afalse%2C%5C%22autoApkFilterByArch%5C%22%3Atrue%2C%5C%22appName%5C%22%3A%5C%22WG%20Tunnel%5C%22%2C%5C%22appAuthor%5C%22%3A%5C%22Zane%20Schepke%5C%22%2C%5C%22shizukuPretendToBeGooglePlay%5C%22%3Afalse%2C%5C%22allowInsecure%5C%22%3Afalse%2C%5C%22exemptFromBackgroundUpdates%5C%22%3Afalse%2C%5C%22skipUpdateNotifications%5C%22%3Afalse%2C%5C%22about%5C%22%3A%5C%22%5C%22%2C%5C%22refreshBeforeDownload%5C%22%3Afalse%7D%22%2C%22overrideSource%22%3Anull%7D)
|
[](https://apps.obtainium.imranr.dev/redirect?r=obtainium://app/%7B%22id%22%3A%22com.zaneschepke.wireguardautotunnel%22%2C%22url%22%3A%22https%3A%2F%2Fgithub.com%2Fzaneschepke%2Fwgtunnel%22%2C%22author%22%3A%22zaneschepke%22%2C%22name%22%3A%22WG%20Tunnel%22%2C%22preferredApkIndex%22%3A0%2C%22additionalSettings%22%3A%22%7B%5C%22includePrereleases%5C%22%3Afalse%2C%5C%22fallbackToOlderReleases%5C%22%3Atrue%2C%5C%22filterReleaseTitlesByRegEx%5C%22%3A%5C%22%5C%22%2C%5C%22filterReleaseNotesByRegEx%5C%22%3A%5C%22%5C%22%2C%5C%22verifyLatestTag%5C%22%3Atrue%2C%5C%22sortMethodChoice%5C%22%3A%5C%22date%5C%22%2C%5C%22useLatestAssetDateAsReleaseDate%5C%22%3Afalse%2C%5C%22releaseTitleAsVersion%5C%22%3Afalse%2C%5C%22trackOnly%5C%22%3Afalse%2C%5C%22versionExtractionRegEx%5C%22%3A%5C%22%5C%22%2C%5C%22matchGroupToUse%5C%22%3A%5C%22%5C%22%2C%5C%22versionDetection%5C%22%3Atrue%2C%5C%22releaseDateAsVersion%5C%22%3Afalse%2C%5C%22useVersionCodeAsOSVersion%5C%22%3Afalse%2C%5C%22apkFilterRegEx%5C%22%3A%5C%22%5C%22%2C%5C%22invertAPKFilter%5C%22%3Afalse%2C%5C%22autoApkFilterByArch%5C%22%3Atrue%2C%5C%22appName%5C%22%3A%5C%22WG%20Tunnel%5C%22%2C%5C%22appAuthor%5C%22%3A%5C%22Zane%20Schepke%5C%22%2C%5C%22shizukuPretendToBeGooglePlay%5C%22%3Afalse%2C%5C%22allowInsecure%5C%22%3Afalse%2C%5C%22exemptFromBackgroundUpdates%5C%22%3Afalse%2C%5C%22skipUpdateNotifications%5C%22%3Afalse%2C%5C%22about%5C%22%3A%5C%22%5C%22%2C%5C%22refreshBeforeDownload%5C%22%3Afalse%7D%22%2C%22overrideSource%22%3Anull%7D)
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
@@ -60,14 +59,12 @@ WG Tunnel is an alternative Android client for WireGuard and AmneziaWG, inspired
|
|||||||
|
|
||||||
</div>
|
</div>
|
||||||
<div style="display: flex; flex-wrap: wrap; justify-content: left; gap: 10px;">
|
<div style="display: flex; flex-wrap: wrap; justify-content: left; gap: 10px;">
|
||||||
<img label="Main" src="fastlane/metadata/android/en-US/images/phoneScreenshots/main_screen.png" width="200" />
|
<img label="Main" src="fastlane/metadata/android/en-US/images/phoneScreenshots/main_screen.png" width="200" alt="Main"/>
|
||||||
<img label="Settings" src="fastlane/metadata/android/en-US/images/phoneScreenshots/settings_screen.png" width="200" />
|
<img label="Config" src="fastlane/metadata/android/en-US/images/phoneScreenshots/config_screen.png" width="200" alt="Config"/>
|
||||||
<img label="Auto" src="fastlane/metadata/android/en-US/images/phoneScreenshots/auto_screen.png" width="200" />
|
<img label="Settings" src="fastlane/metadata/android/en-US/images/phoneScreenshots/settings_screen.png" width="200" alt="Settings"/>
|
||||||
<img label="Config" src="fastlane/metadata/android/en-US/images/phoneScreenshots/config_screen.png" width="200" />
|
<img label="Auto-tunnel" src="fastlane/metadata/android/en-US/images/phoneScreenshots/auto_screen.png" width="200" alt="Auto-tunnel"/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div style="text-align: left;">
|
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
- **Tunnel Import Methods**: Easily add tunnels using .conf files, ZIP archives, manual entry, or QR code scanning.
|
- **Tunnel Import Methods**: Easily add tunnels using .conf files, ZIP archives, manual entry, or QR code scanning.
|
||||||
|
|||||||
+36
-12
@@ -1,3 +1,4 @@
|
|||||||
|
import com.android.build.gradle.internal.api.BaseVariantOutputImpl
|
||||||
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
|
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
@@ -27,6 +28,15 @@ android {
|
|||||||
// fix okhttp proguard issue
|
// fix okhttp proguard issue
|
||||||
packaging { resources { pickFirsts.add("okhttp3/internal/publicsuffix/publicsuffixes.gz") } }
|
packaging { resources { pickFirsts.add("okhttp3/internal/publicsuffix/publicsuffixes.gz") } }
|
||||||
|
|
||||||
|
splits {
|
||||||
|
abi {
|
||||||
|
isEnable = true
|
||||||
|
reset()
|
||||||
|
include("armeabi-v7a", "arm64-v8a")
|
||||||
|
isUniversalApk = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
applicationId = Constants.APP_ID
|
applicationId = Constants.APP_ID
|
||||||
minSdk = Constants.MIN_SDK
|
minSdk = Constants.MIN_SDK
|
||||||
@@ -128,19 +138,33 @@ android {
|
|||||||
allowedLicenseUrls().forEach { allowUrl(it) }
|
allowedLicenseUrls().forEach { allowUrl(it) }
|
||||||
}
|
}
|
||||||
|
|
||||||
applicationVariants.all {
|
android.applicationVariants.all {
|
||||||
val variant = this
|
val variant = this
|
||||||
variant.outputs
|
|
||||||
.map { it as com.android.build.gradle.internal.api.BaseVariantOutputImpl }
|
val abiNameMap =
|
||||||
.forEach { output ->
|
mapOf(
|
||||||
val outputFileName =
|
"armeabi-v7a" to "armv7",
|
||||||
if (variant.flavorName == "fdroid" && variant.buildType.name == "release") {
|
"arm64-v8a" to "arm64",
|
||||||
"${Constants.APP_NAME}-fdroid-release-${variant.versionName}.apk"
|
"x86" to "x86",
|
||||||
} else {
|
"x86_64" to "x64",
|
||||||
"${Constants.APP_NAME}-${variant.flavorName}-v${variant.versionName}.apk"
|
)
|
||||||
}
|
|
||||||
output.outputFileName = outputFileName
|
variant.outputs.all {
|
||||||
}
|
val output = this as BaseVariantOutputImpl
|
||||||
|
val abi = output.getFilter("ABI")
|
||||||
|
|
||||||
|
val baseFileName = "${Constants.APP_NAME}-${variant.flavorName}-v${variant.versionName}"
|
||||||
|
|
||||||
|
val outputFileName =
|
||||||
|
if (!abi.isNullOrEmpty()) {
|
||||||
|
val shortAbiName = abiNameMap.getOrDefault(abi, abi)
|
||||||
|
"${baseFileName}-${shortAbiName}.apk"
|
||||||
|
} else {
|
||||||
|
"${baseFileName}.apk"
|
||||||
|
}
|
||||||
|
|
||||||
|
output.outputFileName = outputFileName
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,509 @@
|
|||||||
|
{
|
||||||
|
"formatVersion": 1,
|
||||||
|
"database": {
|
||||||
|
"version": 26,
|
||||||
|
"identityHash": "a420594a08fff58ecda3e0424fb43e47",
|
||||||
|
"entities": [
|
||||||
|
{
|
||||||
|
"tableName": "tunnel_config",
|
||||||
|
"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, `restart_on_ping_failure` INTEGER NOT NULL DEFAULT false, `ping_target` TEXT DEFAULT null, `is_ethernet_tunnel` INTEGER NOT NULL DEFAULT false, `is_ipv4_preferred` INTEGER NOT NULL DEFAULT true, `position` INTEGER NOT NULL DEFAULT 0, `auto_tunnel_apps` TEXT NOT NULL DEFAULT '[]')",
|
||||||
|
"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": "restartOnPingFailure",
|
||||||
|
"columnName": "restart_on_ping_failure",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "false"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "pingTarget",
|
||||||
|
"columnName": "ping_target",
|
||||||
|
"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"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "position",
|
||||||
|
"columnName": "position",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "autoTunnelApps",
|
||||||
|
"columnName": "auto_tunnel_apps",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "'[]'"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": true,
|
||||||
|
"columnNames": [
|
||||||
|
"id"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"indices": [
|
||||||
|
{
|
||||||
|
"name": "index_tunnel_config_name",
|
||||||
|
"unique": true,
|
||||||
|
"columnNames": [
|
||||||
|
"name"
|
||||||
|
],
|
||||||
|
"orders": [],
|
||||||
|
"createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_tunnel_config_name` ON `${TABLE_NAME}` (`name`)"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "proxy_settings",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `socks5_proxy_enabled` INTEGER NOT NULL DEFAULT 0, `socks5_proxy_bind_address` TEXT, `http_proxy_enable` INTEGER NOT NULL DEFAULT 0, `http_proxy_bind_address` TEXT, `proxy_username` TEXT, `proxy_password` TEXT)",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "id",
|
||||||
|
"columnName": "id",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "socks5ProxyEnabled",
|
||||||
|
"columnName": "socks5_proxy_enabled",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "socks5ProxyBindAddress",
|
||||||
|
"columnName": "socks5_proxy_bind_address",
|
||||||
|
"affinity": "TEXT"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "httpProxyEnabled",
|
||||||
|
"columnName": "http_proxy_enable",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "httpProxyBindAddress",
|
||||||
|
"columnName": "http_proxy_bind_address",
|
||||||
|
"affinity": "TEXT"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "proxyUsername",
|
||||||
|
"columnName": "proxy_username",
|
||||||
|
"affinity": "TEXT"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "proxyPassword",
|
||||||
|
"columnName": "proxy_password",
|
||||||
|
"affinity": "TEXT"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": true,
|
||||||
|
"columnNames": [
|
||||||
|
"id"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "general_settings",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_shortcuts_enabled` INTEGER NOT NULL DEFAULT 0, `is_restore_on_boot_enabled` INTEGER NOT NULL DEFAULT 0, `is_multi_tunnel_enabled` INTEGER NOT NULL DEFAULT 0, `is_tunnel_globals_enabled` INTEGER NOT NULL DEFAULT 0, `app_mode` INTEGER NOT NULL DEFAULT 0, `theme` TEXT NOT NULL DEFAULT 'AUTOMATIC', `locale` TEXT, `remote_key` TEXT, `is_remote_control_enabled` INTEGER NOT NULL DEFAULT 0, `is_pin_lock_enabled` INTEGER NOT NULL DEFAULT 0, `is_always_on_vpn_enabled` INTEGER NOT NULL DEFAULT 0, `custom_split_packages` TEXT NOT NULL DEFAULT '{}')",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "id",
|
||||||
|
"columnName": "id",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "isShortcutsEnabled",
|
||||||
|
"columnName": "is_shortcuts_enabled",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "isRestoreOnBootEnabled",
|
||||||
|
"columnName": "is_restore_on_boot_enabled",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "isMultiTunnelEnabled",
|
||||||
|
"columnName": "is_multi_tunnel_enabled",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "isTunnelGlobalsEnabled",
|
||||||
|
"columnName": "is_tunnel_globals_enabled",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "appMode",
|
||||||
|
"columnName": "app_mode",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "theme",
|
||||||
|
"columnName": "theme",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "'AUTOMATIC'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "locale",
|
||||||
|
"columnName": "locale",
|
||||||
|
"affinity": "TEXT"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "remoteKey",
|
||||||
|
"columnName": "remote_key",
|
||||||
|
"affinity": "TEXT"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "isRemoteControlEnabled",
|
||||||
|
"columnName": "is_remote_control_enabled",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "isPinLockEnabled",
|
||||||
|
"columnName": "is_pin_lock_enabled",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "isAlwaysOnVpnEnabled",
|
||||||
|
"columnName": "is_always_on_vpn_enabled",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "customSplitPackages",
|
||||||
|
"columnName": "custom_split_packages",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "'{}'"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": true,
|
||||||
|
"columnNames": [
|
||||||
|
"id"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "auto_tunnel_settings",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_tunnel_enabled` INTEGER NOT NULL DEFAULT 0, `is_tunnel_on_mobile_data_enabled` INTEGER NOT NULL DEFAULT 0, `trusted_network_ssids` TEXT NOT NULL DEFAULT '', `is_tunnel_on_ethernet_enabled` INTEGER NOT NULL DEFAULT 0, `is_tunnel_on_wifi_enabled` INTEGER NOT NULL DEFAULT 0, `is_wildcards_enabled` INTEGER NOT NULL DEFAULT 0, `is_stop_on_no_internet_enabled` INTEGER NOT NULL DEFAULT 0, `debounce_delay_seconds` INTEGER NOT NULL DEFAULT 3, `is_tunnel_on_unsecure_enabled` INTEGER NOT NULL DEFAULT 0, `wifi_detection_method` INTEGER NOT NULL DEFAULT 0, `start_on_boot` INTEGER NOT NULL DEFAULT 0)",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "id",
|
||||||
|
"columnName": "id",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "isAutoTunnelEnabled",
|
||||||
|
"columnName": "is_tunnel_enabled",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "isTunnelOnMobileDataEnabled",
|
||||||
|
"columnName": "is_tunnel_on_mobile_data_enabled",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "trustedNetworkSSIDs",
|
||||||
|
"columnName": "trusted_network_ssids",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "''"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "isTunnelOnEthernetEnabled",
|
||||||
|
"columnName": "is_tunnel_on_ethernet_enabled",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "isTunnelOnWifiEnabled",
|
||||||
|
"columnName": "is_tunnel_on_wifi_enabled",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "isWildcardsEnabled",
|
||||||
|
"columnName": "is_wildcards_enabled",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "isStopOnNoInternetEnabled",
|
||||||
|
"columnName": "is_stop_on_no_internet_enabled",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "debounceDelaySeconds",
|
||||||
|
"columnName": "debounce_delay_seconds",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "3"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "isTunnelOnUnsecureEnabled",
|
||||||
|
"columnName": "is_tunnel_on_unsecure_enabled",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "wifiDetectionMethod",
|
||||||
|
"columnName": "wifi_detection_method",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "startOnBoot",
|
||||||
|
"columnName": "start_on_boot",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": true,
|
||||||
|
"columnNames": [
|
||||||
|
"id"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "monitoring_settings",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_ping_enabled` INTEGER NOT NULL DEFAULT 0, `is_ping_monitoring_enabled` INTEGER NOT NULL DEFAULT 1, `tunnel_ping_interval_sec` INTEGER NOT NULL DEFAULT 30, `tunnel_ping_attempts` INTEGER NOT NULL DEFAULT 3, `tunnel_ping_timeout_sec` INTEGER, `show_detailed_ping_stats` INTEGER NOT NULL DEFAULT 0, `is_local_logs_enabled` INTEGER NOT NULL DEFAULT 0)",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "id",
|
||||||
|
"columnName": "id",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "isPingEnabled",
|
||||||
|
"columnName": "is_ping_enabled",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "isPingMonitoringEnabled",
|
||||||
|
"columnName": "is_ping_monitoring_enabled",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "tunnelPingIntervalSeconds",
|
||||||
|
"columnName": "tunnel_ping_interval_sec",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "30"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "tunnelPingAttempts",
|
||||||
|
"columnName": "tunnel_ping_attempts",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "3"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "tunnelPingTimeoutSeconds",
|
||||||
|
"columnName": "tunnel_ping_timeout_sec",
|
||||||
|
"affinity": "INTEGER"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "showDetailedPingStats",
|
||||||
|
"columnName": "show_detailed_ping_stats",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "isLocalLogsEnabled",
|
||||||
|
"columnName": "is_local_logs_enabled",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": true,
|
||||||
|
"columnNames": [
|
||||||
|
"id"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "dns_settings",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `dns_protocol` INTEGER NOT NULL DEFAULT 0, `dns_endpoint` TEXT)",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "id",
|
||||||
|
"columnName": "id",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "dnsProtocol",
|
||||||
|
"columnName": "dns_protocol",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "dnsEndpoint",
|
||||||
|
"columnName": "dns_endpoint",
|
||||||
|
"affinity": "TEXT"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": true,
|
||||||
|
"columnNames": [
|
||||||
|
"id"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "lockdown_settings",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `bypass_lan` INTEGER NOT NULL DEFAULT 0, `metered` INTEGER NOT NULL DEFAULT 0, `dual_stack` INTEGER NOT NULL DEFAULT 0)",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "id",
|
||||||
|
"columnName": "id",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "bypassLan",
|
||||||
|
"columnName": "bypass_lan",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "metered",
|
||||||
|
"columnName": "metered",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "dualStack",
|
||||||
|
"columnName": "dual_stack",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": true,
|
||||||
|
"columnNames": [
|
||||||
|
"id"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"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, 'a420594a08fff58ecda3e0424fb43e47')"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,523 @@
|
|||||||
|
{
|
||||||
|
"formatVersion": 1,
|
||||||
|
"database": {
|
||||||
|
"version": 27,
|
||||||
|
"identityHash": "98452d8160a1ae66c852ec8cd739e675",
|
||||||
|
"entities": [
|
||||||
|
{
|
||||||
|
"tableName": "tunnel_config",
|
||||||
|
"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, `restart_on_ping_failure` INTEGER NOT NULL DEFAULT false, `ping_target` TEXT DEFAULT null, `is_ethernet_tunnel` INTEGER NOT NULL DEFAULT false, `is_ipv4_preferred` INTEGER NOT NULL DEFAULT true, `position` INTEGER NOT NULL DEFAULT 0, `auto_tunnel_apps` TEXT NOT NULL DEFAULT '[]', `is_metered` 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": "restartOnPingFailure",
|
||||||
|
"columnName": "restart_on_ping_failure",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "false"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "pingTarget",
|
||||||
|
"columnName": "ping_target",
|
||||||
|
"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"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "position",
|
||||||
|
"columnName": "position",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "autoTunnelApps",
|
||||||
|
"columnName": "auto_tunnel_apps",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "'[]'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "isMetered",
|
||||||
|
"columnName": "is_metered",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "true"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": true,
|
||||||
|
"columnNames": [
|
||||||
|
"id"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"indices": [
|
||||||
|
{
|
||||||
|
"name": "index_tunnel_config_name",
|
||||||
|
"unique": true,
|
||||||
|
"columnNames": [
|
||||||
|
"name"
|
||||||
|
],
|
||||||
|
"orders": [],
|
||||||
|
"createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_tunnel_config_name` ON `${TABLE_NAME}` (`name`)"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "proxy_settings",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `socks5_proxy_enabled` INTEGER NOT NULL DEFAULT 0, `socks5_proxy_bind_address` TEXT, `http_proxy_enable` INTEGER NOT NULL DEFAULT 0, `http_proxy_bind_address` TEXT, `proxy_username` TEXT, `proxy_password` TEXT)",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "id",
|
||||||
|
"columnName": "id",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "socks5ProxyEnabled",
|
||||||
|
"columnName": "socks5_proxy_enabled",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "socks5ProxyBindAddress",
|
||||||
|
"columnName": "socks5_proxy_bind_address",
|
||||||
|
"affinity": "TEXT"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "httpProxyEnabled",
|
||||||
|
"columnName": "http_proxy_enable",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "httpProxyBindAddress",
|
||||||
|
"columnName": "http_proxy_bind_address",
|
||||||
|
"affinity": "TEXT"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "proxyUsername",
|
||||||
|
"columnName": "proxy_username",
|
||||||
|
"affinity": "TEXT"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "proxyPassword",
|
||||||
|
"columnName": "proxy_password",
|
||||||
|
"affinity": "TEXT"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": true,
|
||||||
|
"columnNames": [
|
||||||
|
"id"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "general_settings",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_shortcuts_enabled` INTEGER NOT NULL DEFAULT 0, `is_restore_on_boot_enabled` INTEGER NOT NULL DEFAULT 0, `is_multi_tunnel_enabled` INTEGER NOT NULL DEFAULT 0, `global_split_tunnel_enabled` INTEGER NOT NULL DEFAULT 0, `app_mode` INTEGER NOT NULL DEFAULT 0, `theme` TEXT NOT NULL DEFAULT 'AUTOMATIC', `locale` TEXT, `remote_key` TEXT, `is_remote_control_enabled` INTEGER NOT NULL DEFAULT 0, `is_pin_lock_enabled` INTEGER NOT NULL DEFAULT 0, `is_always_on_vpn_enabled` INTEGER NOT NULL DEFAULT 0, `custom_split_packages` TEXT NOT NULL DEFAULT '{}')",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "id",
|
||||||
|
"columnName": "id",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "isShortcutsEnabled",
|
||||||
|
"columnName": "is_shortcuts_enabled",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "isRestoreOnBootEnabled",
|
||||||
|
"columnName": "is_restore_on_boot_enabled",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "isMultiTunnelEnabled",
|
||||||
|
"columnName": "is_multi_tunnel_enabled",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "isGlobalSplitTunnelEnabled",
|
||||||
|
"columnName": "global_split_tunnel_enabled",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "appMode",
|
||||||
|
"columnName": "app_mode",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "theme",
|
||||||
|
"columnName": "theme",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "'AUTOMATIC'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "locale",
|
||||||
|
"columnName": "locale",
|
||||||
|
"affinity": "TEXT"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "remoteKey",
|
||||||
|
"columnName": "remote_key",
|
||||||
|
"affinity": "TEXT"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "isRemoteControlEnabled",
|
||||||
|
"columnName": "is_remote_control_enabled",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "isPinLockEnabled",
|
||||||
|
"columnName": "is_pin_lock_enabled",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "isAlwaysOnVpnEnabled",
|
||||||
|
"columnName": "is_always_on_vpn_enabled",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "customSplitPackages",
|
||||||
|
"columnName": "custom_split_packages",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "'{}'"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": true,
|
||||||
|
"columnNames": [
|
||||||
|
"id"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "auto_tunnel_settings",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_tunnel_enabled` INTEGER NOT NULL DEFAULT 0, `is_tunnel_on_mobile_data_enabled` INTEGER NOT NULL DEFAULT 0, `trusted_network_ssids` TEXT NOT NULL DEFAULT '', `is_tunnel_on_ethernet_enabled` INTEGER NOT NULL DEFAULT 0, `is_tunnel_on_wifi_enabled` INTEGER NOT NULL DEFAULT 0, `is_wildcards_enabled` INTEGER NOT NULL DEFAULT 0, `is_stop_on_no_internet_enabled` INTEGER NOT NULL DEFAULT 0, `debounce_delay_seconds` INTEGER NOT NULL DEFAULT 3, `is_tunnel_on_unsecure_enabled` INTEGER NOT NULL DEFAULT 0, `wifi_detection_method` INTEGER NOT NULL DEFAULT 0, `start_on_boot` INTEGER NOT NULL DEFAULT 0)",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "id",
|
||||||
|
"columnName": "id",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "isAutoTunnelEnabled",
|
||||||
|
"columnName": "is_tunnel_enabled",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "isTunnelOnMobileDataEnabled",
|
||||||
|
"columnName": "is_tunnel_on_mobile_data_enabled",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "trustedNetworkSSIDs",
|
||||||
|
"columnName": "trusted_network_ssids",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "''"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "isTunnelOnEthernetEnabled",
|
||||||
|
"columnName": "is_tunnel_on_ethernet_enabled",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "isTunnelOnWifiEnabled",
|
||||||
|
"columnName": "is_tunnel_on_wifi_enabled",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "isWildcardsEnabled",
|
||||||
|
"columnName": "is_wildcards_enabled",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "isStopOnNoInternetEnabled",
|
||||||
|
"columnName": "is_stop_on_no_internet_enabled",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "debounceDelaySeconds",
|
||||||
|
"columnName": "debounce_delay_seconds",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "3"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "isTunnelOnUnsecureEnabled",
|
||||||
|
"columnName": "is_tunnel_on_unsecure_enabled",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "wifiDetectionMethod",
|
||||||
|
"columnName": "wifi_detection_method",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "startOnBoot",
|
||||||
|
"columnName": "start_on_boot",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": true,
|
||||||
|
"columnNames": [
|
||||||
|
"id"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "monitoring_settings",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_ping_enabled` INTEGER NOT NULL DEFAULT 0, `is_ping_monitoring_enabled` INTEGER NOT NULL DEFAULT 1, `tunnel_ping_interval_sec` INTEGER NOT NULL DEFAULT 30, `tunnel_ping_attempts` INTEGER NOT NULL DEFAULT 3, `tunnel_ping_timeout_sec` INTEGER, `show_detailed_ping_stats` INTEGER NOT NULL DEFAULT 0, `is_local_logs_enabled` INTEGER NOT NULL DEFAULT 0)",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "id",
|
||||||
|
"columnName": "id",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "isPingEnabled",
|
||||||
|
"columnName": "is_ping_enabled",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "isPingMonitoringEnabled",
|
||||||
|
"columnName": "is_ping_monitoring_enabled",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "tunnelPingIntervalSeconds",
|
||||||
|
"columnName": "tunnel_ping_interval_sec",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "30"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "tunnelPingAttempts",
|
||||||
|
"columnName": "tunnel_ping_attempts",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "3"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "tunnelPingTimeoutSeconds",
|
||||||
|
"columnName": "tunnel_ping_timeout_sec",
|
||||||
|
"affinity": "INTEGER"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "showDetailedPingStats",
|
||||||
|
"columnName": "show_detailed_ping_stats",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "isLocalLogsEnabled",
|
||||||
|
"columnName": "is_local_logs_enabled",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": true,
|
||||||
|
"columnNames": [
|
||||||
|
"id"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "dns_settings",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `dns_protocol` INTEGER NOT NULL DEFAULT 0, `dns_endpoint` TEXT, `global_tunnel_dns_enabled` INTEGER NOT NULL DEFAULT 0)",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "id",
|
||||||
|
"columnName": "id",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "dnsProtocol",
|
||||||
|
"columnName": "dns_protocol",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "dnsEndpoint",
|
||||||
|
"columnName": "dns_endpoint",
|
||||||
|
"affinity": "TEXT"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "isGlobalTunnelDnsEnabled",
|
||||||
|
"columnName": "global_tunnel_dns_enabled",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": true,
|
||||||
|
"columnNames": [
|
||||||
|
"id"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "lockdown_settings",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `bypass_lan` INTEGER NOT NULL DEFAULT 0, `metered` INTEGER NOT NULL DEFAULT 0, `dual_stack` INTEGER NOT NULL DEFAULT 0)",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "id",
|
||||||
|
"columnName": "id",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "bypassLan",
|
||||||
|
"columnName": "bypass_lan",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "metered",
|
||||||
|
"columnName": "metered",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "dualStack",
|
||||||
|
"columnName": "dual_stack",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": true,
|
||||||
|
"columnNames": [
|
||||||
|
"id"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"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, '98452d8160a1ae66c852ec8cd739e675')"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,523 @@
|
|||||||
|
{
|
||||||
|
"formatVersion": 1,
|
||||||
|
"database": {
|
||||||
|
"version": 28,
|
||||||
|
"identityHash": "4792d0cc61a527c69962b5e58463e6da",
|
||||||
|
"entities": [
|
||||||
|
{
|
||||||
|
"tableName": "tunnel_config",
|
||||||
|
"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, `restart_on_ping_failure` INTEGER NOT NULL DEFAULT false, `ping_target` TEXT DEFAULT null, `is_ethernet_tunnel` INTEGER NOT NULL DEFAULT false, `is_ipv4_preferred` INTEGER NOT NULL DEFAULT true, `position` INTEGER NOT NULL DEFAULT 0, `auto_tunnel_apps` TEXT NOT NULL DEFAULT '[]', `is_metered` 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": "restartOnPingFailure",
|
||||||
|
"columnName": "restart_on_ping_failure",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "false"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "pingTarget",
|
||||||
|
"columnName": "ping_target",
|
||||||
|
"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"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "position",
|
||||||
|
"columnName": "position",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "autoTunnelApps",
|
||||||
|
"columnName": "auto_tunnel_apps",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "'[]'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "isMetered",
|
||||||
|
"columnName": "is_metered",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "true"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": true,
|
||||||
|
"columnNames": [
|
||||||
|
"id"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"indices": [
|
||||||
|
{
|
||||||
|
"name": "index_tunnel_config_name",
|
||||||
|
"unique": true,
|
||||||
|
"columnNames": [
|
||||||
|
"name"
|
||||||
|
],
|
||||||
|
"orders": [],
|
||||||
|
"createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_tunnel_config_name` ON `${TABLE_NAME}` (`name`)"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "proxy_settings",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `socks5_proxy_enabled` INTEGER NOT NULL DEFAULT 0, `socks5_proxy_bind_address` TEXT, `http_proxy_enable` INTEGER NOT NULL DEFAULT 0, `http_proxy_bind_address` TEXT, `proxy_username` TEXT, `proxy_password` TEXT)",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "id",
|
||||||
|
"columnName": "id",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "socks5ProxyEnabled",
|
||||||
|
"columnName": "socks5_proxy_enabled",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "socks5ProxyBindAddress",
|
||||||
|
"columnName": "socks5_proxy_bind_address",
|
||||||
|
"affinity": "TEXT"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "httpProxyEnabled",
|
||||||
|
"columnName": "http_proxy_enable",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "httpProxyBindAddress",
|
||||||
|
"columnName": "http_proxy_bind_address",
|
||||||
|
"affinity": "TEXT"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "proxyUsername",
|
||||||
|
"columnName": "proxy_username",
|
||||||
|
"affinity": "TEXT"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "proxyPassword",
|
||||||
|
"columnName": "proxy_password",
|
||||||
|
"affinity": "TEXT"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": true,
|
||||||
|
"columnNames": [
|
||||||
|
"id"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "general_settings",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_shortcuts_enabled` INTEGER NOT NULL DEFAULT 0, `is_restore_on_boot_enabled` INTEGER NOT NULL DEFAULT 0, `is_multi_tunnel_enabled` INTEGER NOT NULL DEFAULT 0, `global_split_tunnel_enabled` INTEGER NOT NULL DEFAULT 0, `app_mode` INTEGER NOT NULL DEFAULT 0, `theme` TEXT NOT NULL DEFAULT 'AUTOMATIC', `locale` TEXT, `remote_key` TEXT, `is_remote_control_enabled` INTEGER NOT NULL DEFAULT 0, `is_pin_lock_enabled` INTEGER NOT NULL DEFAULT 0, `is_always_on_vpn_enabled` INTEGER NOT NULL DEFAULT 0, `already_donated` INTEGER NOT NULL DEFAULT 0)",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "id",
|
||||||
|
"columnName": "id",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "isShortcutsEnabled",
|
||||||
|
"columnName": "is_shortcuts_enabled",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "isRestoreOnBootEnabled",
|
||||||
|
"columnName": "is_restore_on_boot_enabled",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "isMultiTunnelEnabled",
|
||||||
|
"columnName": "is_multi_tunnel_enabled",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "isGlobalSplitTunnelEnabled",
|
||||||
|
"columnName": "global_split_tunnel_enabled",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "appMode",
|
||||||
|
"columnName": "app_mode",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "theme",
|
||||||
|
"columnName": "theme",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "'AUTOMATIC'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "locale",
|
||||||
|
"columnName": "locale",
|
||||||
|
"affinity": "TEXT"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "remoteKey",
|
||||||
|
"columnName": "remote_key",
|
||||||
|
"affinity": "TEXT"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "isRemoteControlEnabled",
|
||||||
|
"columnName": "is_remote_control_enabled",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "isPinLockEnabled",
|
||||||
|
"columnName": "is_pin_lock_enabled",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "isAlwaysOnVpnEnabled",
|
||||||
|
"columnName": "is_always_on_vpn_enabled",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "alreadyDonated",
|
||||||
|
"columnName": "already_donated",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": true,
|
||||||
|
"columnNames": [
|
||||||
|
"id"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "auto_tunnel_settings",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_tunnel_enabled` INTEGER NOT NULL DEFAULT 0, `is_tunnel_on_mobile_data_enabled` INTEGER NOT NULL DEFAULT 0, `trusted_network_ssids` TEXT NOT NULL DEFAULT '', `is_tunnel_on_ethernet_enabled` INTEGER NOT NULL DEFAULT 0, `is_tunnel_on_wifi_enabled` INTEGER NOT NULL DEFAULT 0, `is_wildcards_enabled` INTEGER NOT NULL DEFAULT 0, `is_stop_on_no_internet_enabled` INTEGER NOT NULL DEFAULT 0, `debounce_delay_seconds` INTEGER NOT NULL DEFAULT 3, `is_tunnel_on_unsecure_enabled` INTEGER NOT NULL DEFAULT 0, `wifi_detection_method` INTEGER NOT NULL DEFAULT 0, `start_on_boot` INTEGER NOT NULL DEFAULT 0)",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "id",
|
||||||
|
"columnName": "id",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "isAutoTunnelEnabled",
|
||||||
|
"columnName": "is_tunnel_enabled",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "isTunnelOnMobileDataEnabled",
|
||||||
|
"columnName": "is_tunnel_on_mobile_data_enabled",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "trustedNetworkSSIDs",
|
||||||
|
"columnName": "trusted_network_ssids",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "''"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "isTunnelOnEthernetEnabled",
|
||||||
|
"columnName": "is_tunnel_on_ethernet_enabled",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "isTunnelOnWifiEnabled",
|
||||||
|
"columnName": "is_tunnel_on_wifi_enabled",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "isWildcardsEnabled",
|
||||||
|
"columnName": "is_wildcards_enabled",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "isStopOnNoInternetEnabled",
|
||||||
|
"columnName": "is_stop_on_no_internet_enabled",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "debounceDelaySeconds",
|
||||||
|
"columnName": "debounce_delay_seconds",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "3"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "isTunnelOnUnsecureEnabled",
|
||||||
|
"columnName": "is_tunnel_on_unsecure_enabled",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "wifiDetectionMethod",
|
||||||
|
"columnName": "wifi_detection_method",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "startOnBoot",
|
||||||
|
"columnName": "start_on_boot",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": true,
|
||||||
|
"columnNames": [
|
||||||
|
"id"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "monitoring_settings",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_ping_enabled` INTEGER NOT NULL DEFAULT 0, `is_ping_monitoring_enabled` INTEGER NOT NULL DEFAULT 1, `tunnel_ping_interval_sec` INTEGER NOT NULL DEFAULT 30, `tunnel_ping_attempts` INTEGER NOT NULL DEFAULT 3, `tunnel_ping_timeout_sec` INTEGER, `show_detailed_ping_stats` INTEGER NOT NULL DEFAULT 0, `is_local_logs_enabled` INTEGER NOT NULL DEFAULT 0)",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "id",
|
||||||
|
"columnName": "id",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "isPingEnabled",
|
||||||
|
"columnName": "is_ping_enabled",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "isPingMonitoringEnabled",
|
||||||
|
"columnName": "is_ping_monitoring_enabled",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "tunnelPingIntervalSeconds",
|
||||||
|
"columnName": "tunnel_ping_interval_sec",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "30"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "tunnelPingAttempts",
|
||||||
|
"columnName": "tunnel_ping_attempts",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "3"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "tunnelPingTimeoutSeconds",
|
||||||
|
"columnName": "tunnel_ping_timeout_sec",
|
||||||
|
"affinity": "INTEGER"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "showDetailedPingStats",
|
||||||
|
"columnName": "show_detailed_ping_stats",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "isLocalLogsEnabled",
|
||||||
|
"columnName": "is_local_logs_enabled",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": true,
|
||||||
|
"columnNames": [
|
||||||
|
"id"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "dns_settings",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `dns_protocol` INTEGER NOT NULL DEFAULT 0, `dns_endpoint` TEXT, `global_tunnel_dns_enabled` INTEGER NOT NULL DEFAULT 0)",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "id",
|
||||||
|
"columnName": "id",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "dnsProtocol",
|
||||||
|
"columnName": "dns_protocol",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "dnsEndpoint",
|
||||||
|
"columnName": "dns_endpoint",
|
||||||
|
"affinity": "TEXT"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "isGlobalTunnelDnsEnabled",
|
||||||
|
"columnName": "global_tunnel_dns_enabled",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": true,
|
||||||
|
"columnNames": [
|
||||||
|
"id"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "lockdown_settings",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `bypass_lan` INTEGER NOT NULL DEFAULT 0, `metered` INTEGER NOT NULL DEFAULT 0, `dual_stack` INTEGER NOT NULL DEFAULT 0)",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "id",
|
||||||
|
"columnName": "id",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "bypassLan",
|
||||||
|
"columnName": "bypass_lan",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "metered",
|
||||||
|
"columnName": "metered",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "dualStack",
|
||||||
|
"columnName": "dual_stack",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": true,
|
||||||
|
"columnNames": [
|
||||||
|
"id"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"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, '4792d0cc61a527c69962b5e58463e6da')"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,6 +5,9 @@
|
|||||||
<uses-permission android:name="android.permission.INTERNET" />
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
||||||
|
|
||||||
|
<!--for split tunneling-->
|
||||||
|
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
|
||||||
|
|
||||||
<!--foreground service special use for non VPN service tunnels, android 14-->
|
<!--foreground service special use for non VPN service tunnels, android 14-->
|
||||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE" />
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE" />
|
||||||
<!--foreground service special use for VPN service tunnels, android 14-->
|
<!--foreground service special use for VPN service tunnels, android 14-->
|
||||||
@@ -14,6 +17,7 @@
|
|||||||
<!--foreground service permissions-->
|
<!--foreground service permissions-->
|
||||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||||
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" />
|
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" />
|
||||||
|
|
||||||
<!--start service on boot permission-->
|
<!--start service on boot permission-->
|
||||||
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
|
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
|
||||||
|
|
||||||
@@ -42,17 +46,6 @@
|
|||||||
|
|
||||||
<uses-feature android:name="android.hardware.wifi"
|
<uses-feature android:name="android.hardware.wifi"
|
||||||
android:required="false"/>
|
android:required="false"/>
|
||||||
|
|
||||||
<queries>
|
|
||||||
<intent>
|
|
||||||
<action android:name="android.intent.action.MAIN" />
|
|
||||||
<category android:name="android.intent.category.LAUNCHER" />
|
|
||||||
</intent>
|
|
||||||
<intent>
|
|
||||||
<action android:name="android.intent.action.MAIN" />
|
|
||||||
<category android:name="android.intent.category.LEANBACK_LAUNCHER" />
|
|
||||||
</intent>
|
|
||||||
</queries>
|
|
||||||
<application
|
<application
|
||||||
android:name=".WireGuardAutoTunnel"
|
android:name=".WireGuardAutoTunnel"
|
||||||
android:allowBackup="false"
|
android:allowBackup="false"
|
||||||
|
|||||||
@@ -18,9 +18,16 @@ import androidx.compose.foundation.background
|
|||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
import androidx.compose.material3.*
|
import androidx.compose.material3.*
|
||||||
import androidx.compose.runtime.*
|
import androidx.compose.runtime.*
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
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.compose.ui.zIndex
|
import androidx.compose.ui.zIndex
|
||||||
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
|
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
|
||||||
@@ -33,12 +40,12 @@ import androidx.navigation3.runtime.entryProvider
|
|||||||
import androidx.navigation3.runtime.rememberNavBackStack
|
import androidx.navigation3.runtime.rememberNavBackStack
|
||||||
import androidx.navigation3.runtime.rememberSaveableStateHolderNavEntryDecorator
|
import androidx.navigation3.runtime.rememberSaveableStateHolderNavEntryDecorator
|
||||||
import androidx.navigation3.ui.NavDisplay
|
import androidx.navigation3.ui.NavDisplay
|
||||||
import com.zaneschepke.networkmonitor.NetworkMonitor
|
|
||||||
import com.zaneschepke.wireguardautotunnel.core.tunnel.TunnelManager
|
|
||||||
import com.zaneschepke.wireguardautotunnel.data.AppDatabase
|
import com.zaneschepke.wireguardautotunnel.data.AppDatabase
|
||||||
|
import com.zaneschepke.wireguardautotunnel.data.DataStoreManager.Companion.shouldShowDonationSnackbar
|
||||||
import com.zaneschepke.wireguardautotunnel.data.model.AppMode
|
import com.zaneschepke.wireguardautotunnel.data.model.AppMode
|
||||||
import com.zaneschepke.wireguardautotunnel.domain.model.TunnelConfig
|
import com.zaneschepke.wireguardautotunnel.domain.model.TunnelConfig
|
||||||
import com.zaneschepke.wireguardautotunnel.domain.repository.AppStateRepository
|
import com.zaneschepke.wireguardautotunnel.domain.repository.AppStateRepository
|
||||||
|
import com.zaneschepke.wireguardautotunnel.domain.repository.TunnelRepository
|
||||||
import com.zaneschepke.wireguardautotunnel.domain.sideeffect.GlobalSideEffect
|
import com.zaneschepke.wireguardautotunnel.domain.sideeffect.GlobalSideEffect
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.LocalIsAndroidTV
|
import com.zaneschepke.wireguardautotunnel.ui.LocalIsAndroidTV
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.LocalNavController
|
import com.zaneschepke.wireguardautotunnel.ui.LocalNavController
|
||||||
@@ -46,6 +53,9 @@ import com.zaneschepke.wireguardautotunnel.ui.LocalSharedVm
|
|||||||
import com.zaneschepke.wireguardautotunnel.ui.common.banner.AppAlertBanner
|
import com.zaneschepke.wireguardautotunnel.ui.common.banner.AppAlertBanner
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.common.dialog.VpnDeniedDialog
|
import com.zaneschepke.wireguardautotunnel.ui.common.dialog.VpnDeniedDialog
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.common.snackbar.CustomSnackBar
|
import com.zaneschepke.wireguardautotunnel.ui.common.snackbar.CustomSnackBar
|
||||||
|
import com.zaneschepke.wireguardautotunnel.ui.common.snackbar.SnackbarInfo
|
||||||
|
import com.zaneschepke.wireguardautotunnel.ui.common.snackbar.SnackbarType
|
||||||
|
import com.zaneschepke.wireguardautotunnel.ui.common.snackbar.rememberCustomSnackbarState
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.navigation.Route
|
import com.zaneschepke.wireguardautotunnel.ui.navigation.Route
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.navigation.Tab
|
import com.zaneschepke.wireguardautotunnel.ui.navigation.Tab
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.navigation.components.BottomNavbar
|
import com.zaneschepke.wireguardautotunnel.ui.navigation.components.BottomNavbar
|
||||||
@@ -64,8 +74,8 @@ import com.zaneschepke.wireguardautotunnel.ui.screens.settings.appearance.Appear
|
|||||||
import com.zaneschepke.wireguardautotunnel.ui.screens.settings.appearance.display.DisplayScreen
|
import com.zaneschepke.wireguardautotunnel.ui.screens.settings.appearance.display.DisplayScreen
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.screens.settings.appearance.language.LanguageScreen
|
import com.zaneschepke.wireguardautotunnel.ui.screens.settings.appearance.language.LanguageScreen
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.screens.settings.dns.DnsSettingsScreen
|
import com.zaneschepke.wireguardautotunnel.ui.screens.settings.dns.DnsSettingsScreen
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.screens.settings.globals.TunnelGlobalsScreen
|
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.screens.settings.integrations.AndroidIntegrationsScreen
|
import com.zaneschepke.wireguardautotunnel.ui.screens.settings.integrations.AndroidIntegrationsScreen
|
||||||
|
import com.zaneschepke.wireguardautotunnel.ui.screens.settings.lockdown.LockdownSettingsScreen
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.screens.settings.monitoring.TunnelMonitoringScreen
|
import com.zaneschepke.wireguardautotunnel.ui.screens.settings.monitoring.TunnelMonitoringScreen
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.screens.settings.monitoring.logs.LogsScreen
|
import com.zaneschepke.wireguardautotunnel.ui.screens.settings.monitoring.logs.LogsScreen
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.screens.settings.monitoring.ping.PingTargetScreen
|
import com.zaneschepke.wireguardautotunnel.ui.screens.settings.monitoring.ping.PingTargetScreen
|
||||||
@@ -75,9 +85,9 @@ import com.zaneschepke.wireguardautotunnel.ui.screens.support.donate.crypto.Addr
|
|||||||
import com.zaneschepke.wireguardautotunnel.ui.screens.support.license.LicenseScreen
|
import com.zaneschepke.wireguardautotunnel.ui.screens.support.license.LicenseScreen
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.screens.tunnels.TunnelsScreen
|
import com.zaneschepke.wireguardautotunnel.ui.screens.tunnels.TunnelsScreen
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.screens.tunnels.config.ConfigScreen
|
import com.zaneschepke.wireguardautotunnel.ui.screens.tunnels.config.ConfigScreen
|
||||||
|
import com.zaneschepke.wireguardautotunnel.ui.screens.tunnels.settings.TunnelSettingsScreen
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.screens.tunnels.sort.SortScreen
|
import com.zaneschepke.wireguardautotunnel.ui.screens.tunnels.sort.SortScreen
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.screens.tunnels.splittunnel.SplitTunnelScreen
|
import com.zaneschepke.wireguardautotunnel.ui.screens.tunnels.splittunnel.SplitTunnelScreen
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.screens.tunnels.tunneloptions.TunnelOptionsScreen
|
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.theme.AlertRed
|
import com.zaneschepke.wireguardautotunnel.ui.theme.AlertRed
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.theme.OffWhite
|
import com.zaneschepke.wireguardautotunnel.ui.theme.OffWhite
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.theme.WireguardAutoTunnelTheme
|
import com.zaneschepke.wireguardautotunnel.ui.theme.WireguardAutoTunnelTheme
|
||||||
@@ -99,8 +109,7 @@ import xyz.teamgravity.pin_lock_compose.PinManager
|
|||||||
class MainActivity : AppCompatActivity() {
|
class MainActivity : AppCompatActivity() {
|
||||||
|
|
||||||
@Inject lateinit var appStateRepository: AppStateRepository
|
@Inject lateinit var appStateRepository: AppStateRepository
|
||||||
@Inject lateinit var tunnelManager: TunnelManager
|
@Inject lateinit var tunnelRepository: TunnelRepository
|
||||||
@Inject lateinit var networkMonitor: NetworkMonitor
|
|
||||||
@Inject lateinit var appDatabase: AppDatabase
|
@Inject lateinit var appDatabase: AppDatabase
|
||||||
|
|
||||||
private lateinit var roomBackup: RoomBackup
|
private lateinit var roomBackup: RoomBackup
|
||||||
@@ -127,16 +136,16 @@ class MainActivity : AppCompatActivity() {
|
|||||||
setContent {
|
setContent {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
val isTv = isRunningOnTv()
|
val isTv = isRunningOnTv()
|
||||||
val appState by viewModel.container.stateFlow.collectAsStateWithLifecycle()
|
val uiState by viewModel.container.stateFlow.collectAsStateWithLifecycle()
|
||||||
val scope = rememberCoroutineScope()
|
val scope = rememberCoroutineScope()
|
||||||
|
|
||||||
LaunchedEffect(appState.isAppLoaded) {
|
LaunchedEffect(uiState.isAppLoaded) {
|
||||||
if (appState.isAppLoaded) {
|
if (uiState.isAppLoaded) {
|
||||||
appState.locale.let { LocaleUtil.changeLocale(it) }
|
uiState.locale.let { LocaleUtil.changeLocale(it) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val snackbar = remember { SnackbarHostState() }
|
val snackbarState = rememberCustomSnackbarState()
|
||||||
var showVpnPermissionDialog by remember { mutableStateOf(false) }
|
var showVpnPermissionDialog by remember { mutableStateOf(false) }
|
||||||
var vpnPermissionDenied by remember { mutableStateOf(false) }
|
var vpnPermissionDenied by remember { mutableStateOf(false) }
|
||||||
var requestingAppMode by remember {
|
var requestingAppMode by remember {
|
||||||
@@ -146,14 +155,14 @@ class MainActivity : AppCompatActivity() {
|
|||||||
val startingStack = buildList {
|
val startingStack = buildList {
|
||||||
add(Route.Tunnels)
|
add(Route.Tunnels)
|
||||||
if (intent?.action == Intent.ACTION_APPLICATION_PREFERENCES) add(Route.Settings)
|
if (intent?.action == Intent.ACTION_APPLICATION_PREFERENCES) add(Route.Settings)
|
||||||
if (appState.pinLockEnabled) add(Route.Lock)
|
if (uiState.pinLockEnabled) add(Route.Lock)
|
||||||
}
|
}
|
||||||
|
|
||||||
val backStack = rememberNavBackStack(*startingStack.toTypedArray())
|
val backStack = rememberNavBackStack(*startingStack.toTypedArray())
|
||||||
var previousRoute by remember { mutableStateOf<Route?>(null) }
|
var previousRoute by remember { mutableStateOf<Route?>(null) }
|
||||||
|
|
||||||
val navController =
|
val navController =
|
||||||
rememberNavController<NavKey>(backStack, appState.isLocationDisclosureShown) {
|
rememberNavController<NavKey>(backStack, uiState.isLocationDisclosureShown) {
|
||||||
previousKey ->
|
previousKey ->
|
||||||
previousRoute = previousKey as? Route
|
previousRoute = previousKey as? Route
|
||||||
}
|
}
|
||||||
@@ -189,10 +198,20 @@ class MainActivity : AppCompatActivity() {
|
|||||||
vpnActivity.launch(VpnService.prepare(this@MainActivity))
|
vpnActivity.launch(VpnService.prepare(this@MainActivity))
|
||||||
}
|
}
|
||||||
|
|
||||||
is GlobalSideEffect.Snackbar ->
|
is GlobalSideEffect.Snackbar -> {
|
||||||
scope.launch {
|
scope.launch {
|
||||||
snackbar.showSnackbar(sideEffect.message.asString(context))
|
snackbarState.showSnackbar(
|
||||||
|
SnackbarInfo(
|
||||||
|
message =
|
||||||
|
buildAnnotatedString {
|
||||||
|
append(sideEffect.message.asString(context))
|
||||||
|
},
|
||||||
|
type = sideEffect.type ?: SnackbarType.INFO,
|
||||||
|
durationMs = sideEffect.durationMs ?: 4000L,
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
is GlobalSideEffect.Toast ->
|
is GlobalSideEffect.Toast ->
|
||||||
scope.launch { context.showToast(sideEffect.message.asString(context)) }
|
scope.launch { context.showToast(sideEffect.message.asString(context)) }
|
||||||
@@ -203,19 +222,19 @@ class MainActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!appState.isAppLoaded) return@setContent
|
if (!uiState.isAppLoaded) return@setContent
|
||||||
|
|
||||||
var showLock by remember {
|
var showLock by remember {
|
||||||
mutableStateOf(appState.pinLockEnabled && !appState.isPinVerified)
|
mutableStateOf(uiState.pinLockEnabled && !uiState.isPinVerified)
|
||||||
}
|
}
|
||||||
LaunchedEffect(appState.isPinVerified) { if (appState.isPinVerified) showLock = false }
|
LaunchedEffect(uiState.isPinVerified) { if (uiState.isPinVerified) showLock = false }
|
||||||
|
|
||||||
CompositionLocalProvider(
|
CompositionLocalProvider(
|
||||||
LocalIsAndroidTV provides isTv,
|
LocalIsAndroidTV provides isTv,
|
||||||
LocalSharedVm provides viewModel,
|
LocalSharedVm provides viewModel,
|
||||||
LocalNavController provides navController,
|
LocalNavController provides navController,
|
||||||
) {
|
) {
|
||||||
WireguardAutoTunnelTheme(theme = appState.theme) {
|
WireguardAutoTunnelTheme(theme = uiState.theme) {
|
||||||
VpnDeniedDialog(
|
VpnDeniedDialog(
|
||||||
showVpnPermissionDialog,
|
showVpnPermissionDialog,
|
||||||
onDismiss = {
|
onDismiss = {
|
||||||
@@ -224,6 +243,55 @@ class MainActivity : AppCompatActivity() {
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
val annotatedMessage = buildAnnotatedString {
|
||||||
|
append(context.getString(R.string.donation_prompt_prefix))
|
||||||
|
append(" ")
|
||||||
|
withLink(
|
||||||
|
LinkAnnotation.Clickable(
|
||||||
|
tag = context.getString(R.string.support),
|
||||||
|
styles =
|
||||||
|
TextLinkStyles(
|
||||||
|
style =
|
||||||
|
SpanStyle(
|
||||||
|
textDecoration = TextDecoration.Underline,
|
||||||
|
color = MaterialTheme.colorScheme.primary,
|
||||||
|
),
|
||||||
|
focusedStyle =
|
||||||
|
SpanStyle(
|
||||||
|
textDecoration = TextDecoration.Underline,
|
||||||
|
color = MaterialTheme.colorScheme.primary,
|
||||||
|
background =
|
||||||
|
MaterialTheme.colorScheme.primary.copy(
|
||||||
|
alpha = 0.2f
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
) {
|
||||||
|
snackbarState.dismissCurrent()
|
||||||
|
navController.push(Route.Donate)
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
append(context.getString(R.string.donation_prompt_link))
|
||||||
|
}
|
||||||
|
append(" ")
|
||||||
|
append(context.getString(R.string.donation_prompt_suffix))
|
||||||
|
}
|
||||||
|
|
||||||
|
LaunchedEffect(uiState.shouldShowDonationSnackbar) {
|
||||||
|
if (
|
||||||
|
uiState.shouldShowDonationSnackbar && !uiState.settings.alreadyDonated
|
||||||
|
) {
|
||||||
|
viewModel.setShouldShowDonationSnackbar(false)
|
||||||
|
snackbarState.showSnackbar(
|
||||||
|
SnackbarInfo(
|
||||||
|
message = annotatedMessage,
|
||||||
|
type = SnackbarType.THANK_YOU,
|
||||||
|
durationMs = 30_000L,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (showLock) {
|
if (showLock) {
|
||||||
PinManager.initialize(context = this@MainActivity)
|
PinManager.initialize(context = this@MainActivity)
|
||||||
PinLockScreen()
|
PinLockScreen()
|
||||||
@@ -236,14 +304,14 @@ class MainActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
val navState by
|
val navState by
|
||||||
currentRouteAsNavbarState(
|
currentRouteAsNavbarState(
|
||||||
appState,
|
uiState,
|
||||||
viewModel,
|
viewModel,
|
||||||
currentRoute,
|
currentRoute,
|
||||||
navController,
|
navController,
|
||||||
)
|
)
|
||||||
|
|
||||||
Box(modifier = Modifier.fillMaxSize()) {
|
Box(modifier = Modifier.fillMaxSize()) {
|
||||||
if (appState.settings.appMode == AppMode.LOCK_DOWN) {
|
if (uiState.settings.appMode == AppMode.LOCK_DOWN) {
|
||||||
AppAlertBanner(
|
AppAlertBanner(
|
||||||
stringResource(R.string.locked_down)
|
stringResource(R.string.locked_down)
|
||||||
.uppercase(Locale.getDefault()),
|
.uppercase(Locale.getDefault()),
|
||||||
@@ -254,14 +322,25 @@ class MainActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
Scaffold(
|
Scaffold(
|
||||||
snackbarHost = {
|
snackbarHost = {
|
||||||
SnackbarHost(snackbar) { snackbarData ->
|
snackbarState.SnackbarHost(
|
||||||
|
modifier =
|
||||||
|
Modifier.align(Alignment.BottomCenter)
|
||||||
|
.padding(
|
||||||
|
bottom =
|
||||||
|
if (LocalIsAndroidTV.current) 120.dp
|
||||||
|
else 80.dp
|
||||||
|
)
|
||||||
|
) { info ->
|
||||||
CustomSnackBar(
|
CustomSnackBar(
|
||||||
snackbarData.visuals.message,
|
message = info.message,
|
||||||
isRtl = false,
|
type = info.type,
|
||||||
|
onDismiss = { snackbarState.dismissCurrent() },
|
||||||
containerColor =
|
containerColor =
|
||||||
MaterialTheme.colorScheme.surfaceColorAtElevation(
|
MaterialTheme.colorScheme.surfaceColorAtElevation(
|
||||||
2.dp
|
2.dp
|
||||||
),
|
),
|
||||||
|
modifier =
|
||||||
|
Modifier.wrapContentHeight(align = Alignment.Top),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -269,7 +348,7 @@ class MainActivity : AppCompatActivity() {
|
|||||||
bottomBar = {
|
bottomBar = {
|
||||||
if (navState.showBottomItems) {
|
if (navState.showBottomItems) {
|
||||||
BottomNavbar(
|
BottomNavbar(
|
||||||
appState.isAutoTunnelActive,
|
uiState.isAutoTunnelActive,
|
||||||
currentTab,
|
currentTab,
|
||||||
onTabSelected = { tab ->
|
onTabSelected = { tab ->
|
||||||
navController.popUpTo(tab.startRoute)
|
navController.popUpTo(tab.startRoute)
|
||||||
@@ -331,7 +410,7 @@ class MainActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
entry<Route.Tunnels> { TunnelsScreen() }
|
entry<Route.Tunnels> { TunnelsScreen() }
|
||||||
entry<Route.Sort> { SortScreen() }
|
entry<Route.Sort> { SortScreen() }
|
||||||
entry<Route.TunnelOptions> { key ->
|
entry<Route.TunnelSettings> { key ->
|
||||||
val viewModel =
|
val viewModel =
|
||||||
hiltViewModel<
|
hiltViewModel<
|
||||||
TunnelViewModel,
|
TunnelViewModel,
|
||||||
@@ -341,7 +420,7 @@ class MainActivity : AppCompatActivity() {
|
|||||||
factory.create(key.id)
|
factory.create(key.id)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
TunnelOptionsScreen(viewModel)
|
TunnelSettingsScreen(viewModel)
|
||||||
}
|
}
|
||||||
entry<Route.SplitTunnel> { key ->
|
entry<Route.SplitTunnel> { key ->
|
||||||
val viewModel =
|
val viewModel =
|
||||||
@@ -388,9 +467,6 @@ class MainActivity : AppCompatActivity() {
|
|||||||
AndroidIntegrationsScreen()
|
AndroidIntegrationsScreen()
|
||||||
}
|
}
|
||||||
entry<Route.Dns> { DnsSettingsScreen() }
|
entry<Route.Dns> { DnsSettingsScreen() }
|
||||||
entry<Route.TunnelGlobals> { key ->
|
|
||||||
TunnelGlobalsScreen(key.id)
|
|
||||||
}
|
|
||||||
entry<Route.ConfigGlobal> { key ->
|
entry<Route.ConfigGlobal> { key ->
|
||||||
val viewModel =
|
val viewModel =
|
||||||
hiltViewModel<
|
hiltViewModel<
|
||||||
@@ -415,6 +491,9 @@ class MainActivity : AppCompatActivity() {
|
|||||||
)
|
)
|
||||||
SplitTunnelScreen(viewModel)
|
SplitTunnelScreen(viewModel)
|
||||||
}
|
}
|
||||||
|
entry<Route.LockdownSettings> {
|
||||||
|
LockdownSettingsScreen()
|
||||||
|
}
|
||||||
entry<Route.ProxySettings> { ProxySettingsScreen() }
|
entry<Route.ProxySettings> { ProxySettingsScreen() }
|
||||||
entry<Route.Appearance> { AppearanceScreen() }
|
entry<Route.Appearance> { AppearanceScreen() }
|
||||||
entry<Route.Language> { LanguageScreen() }
|
entry<Route.Language> { LanguageScreen() }
|
||||||
@@ -442,7 +521,6 @@ class MainActivity : AppCompatActivity() {
|
|||||||
override fun onResume() {
|
override fun onResume() {
|
||||||
super.onResume()
|
super.onResume()
|
||||||
WireGuardAutoTunnel.setUiActive(true)
|
WireGuardAutoTunnel.setUiActive(true)
|
||||||
networkMonitor.checkPermissionsAndUpdateState()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onPause() {
|
override fun onPause() {
|
||||||
@@ -452,6 +530,9 @@ class MainActivity : AppCompatActivity() {
|
|||||||
|
|
||||||
fun performBackup() =
|
fun performBackup() =
|
||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
|
// reset active tuns before backup to prevent trying to start them without permission on
|
||||||
|
// restore
|
||||||
|
tunnelRepository.resetActiveTunnels()
|
||||||
roomBackup
|
roomBackup
|
||||||
.database(appDatabase)
|
.database(appDatabase)
|
||||||
.backupLocation(RoomBackup.BACKUP_FILE_LOCATION_CUSTOM_DIALOG)
|
.backupLocation(RoomBackup.BACKUP_FILE_LOCATION_CUSTOM_DIALOG)
|
||||||
|
|||||||
@@ -7,18 +7,15 @@ import androidx.hilt.work.HiltWorkerFactory
|
|||||||
import androidx.work.Configuration
|
import androidx.work.Configuration
|
||||||
import com.zaneschepke.logcatter.LogReader
|
import com.zaneschepke.logcatter.LogReader
|
||||||
import com.zaneschepke.wireguardautotunnel.core.notification.NotificationMonitor
|
import com.zaneschepke.wireguardautotunnel.core.notification.NotificationMonitor
|
||||||
import com.zaneschepke.wireguardautotunnel.core.tunnel.TunnelManager
|
|
||||||
import com.zaneschepke.wireguardautotunnel.core.worker.ServiceWorker
|
import com.zaneschepke.wireguardautotunnel.core.worker.ServiceWorker
|
||||||
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.enums.BackendMode
|
|
||||||
import com.zaneschepke.wireguardautotunnel.domain.repository.MonitoringSettingsRepository
|
import com.zaneschepke.wireguardautotunnel.domain.repository.MonitoringSettingsRepository
|
||||||
import com.zaneschepke.wireguardautotunnel.util.ReleaseTree
|
import com.zaneschepke.wireguardautotunnel.util.ReleaseTree
|
||||||
import dagger.hilt.android.HiltAndroidApp
|
import dagger.hilt.android.HiltAndroidApp
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import kotlinx.coroutines.CoroutineDispatcher
|
import kotlinx.coroutines.CoroutineDispatcher
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.cancel
|
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
import kotlinx.coroutines.flow.update
|
import kotlinx.coroutines.flow.update
|
||||||
@@ -43,8 +40,6 @@ class WireGuardAutoTunnel : Application(), Configuration.Provider {
|
|||||||
|
|
||||||
@Inject lateinit var notificationMonitor: NotificationMonitor
|
@Inject lateinit var notificationMonitor: NotificationMonitor
|
||||||
|
|
||||||
@Inject lateinit var tunnelManager: TunnelManager
|
|
||||||
|
|
||||||
override fun onCreate() {
|
override fun onCreate() {
|
||||||
super.onCreate()
|
super.onCreate()
|
||||||
instance = this
|
instance = this
|
||||||
@@ -73,12 +68,6 @@ class WireGuardAutoTunnel : Application(), Configuration.Provider {
|
|||||||
ServiceWorker.start(this)
|
ServiceWorker.start(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onTerminate() {
|
|
||||||
applicationScope.cancel()
|
|
||||||
tunnelManager.setBackendMode(BackendMode.Inactive)
|
|
||||||
super.onTerminate()
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
||||||
private val _uiActive = MutableStateFlow(false)
|
private val _uiActive = MutableStateFlow(false)
|
||||||
|
|||||||
+4
@@ -6,6 +6,7 @@ import android.content.Intent
|
|||||||
import com.zaneschepke.logcatter.LogReader
|
import com.zaneschepke.logcatter.LogReader
|
||||||
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.domain.repository.AppStateRepository
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
@@ -19,6 +20,8 @@ class RestartReceiver : BroadcastReceiver() {
|
|||||||
|
|
||||||
@Inject lateinit var tunnelManager: TunnelManager
|
@Inject lateinit var tunnelManager: TunnelManager
|
||||||
|
|
||||||
|
@Inject lateinit var appStateRepository: AppStateRepository
|
||||||
|
|
||||||
@Inject lateinit var logReader: LogReader
|
@Inject lateinit var logReader: LogReader
|
||||||
|
|
||||||
override fun onReceive(context: Context, intent: Intent) {
|
override fun onReceive(context: Context, intent: Intent) {
|
||||||
@@ -33,6 +36,7 @@ class RestartReceiver : BroadcastReceiver() {
|
|||||||
Intent.ACTION_MY_PACKAGE_REPLACED -> {
|
Intent.ACTION_MY_PACKAGE_REPLACED -> {
|
||||||
tunnelManager.handleRestore()
|
tunnelManager.handleRestore()
|
||||||
logReader.deleteAndClearLogs()
|
logReader.deleteAndClearLogs()
|
||||||
|
appStateRepository.setShouldShowDonationSnackbar(true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -32,7 +32,7 @@ constructor(
|
|||||||
description =
|
description =
|
||||||
StringValue.StringResource(
|
StringValue.StringResource(
|
||||||
R.string.tunnel_error_template,
|
R.string.tunnel_error_template,
|
||||||
error.toStringRes(),
|
error.toStringValue(),
|
||||||
),
|
),
|
||||||
groupKey = NotificationManager.VPN_GROUP_KEY,
|
groupKey = NotificationManager.VPN_GROUP_KEY,
|
||||||
)
|
)
|
||||||
|
|||||||
+1
@@ -93,6 +93,7 @@ class AutoTunnelService : LifecycleService() {
|
|||||||
|
|
||||||
override fun onDestroy() {
|
override fun onDestroy() {
|
||||||
serviceManager.handleAutoTunnelServiceDestroy()
|
serviceManager.handleAutoTunnelServiceDestroy()
|
||||||
|
networkMonitor.destroy()
|
||||||
ServiceCompat.stopForeground(this, ServiceCompat.STOP_FOREGROUND_REMOVE)
|
ServiceCompat.stopForeground(this, ServiceCompat.STOP_FOREGROUND_REMOVE)
|
||||||
super.onDestroy()
|
super.onDestroy()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -106,9 +106,6 @@ abstract class BaseTunnel(
|
|||||||
) {
|
) {
|
||||||
return Timber.w("Tunnel is already running: ${tunnelConfig.name}")
|
return Timber.w("Tunnel is already running: ${tunnelConfig.name}")
|
||||||
}
|
}
|
||||||
|
|
||||||
updateTunnelStatus(tunnelConfig.id, TunnelStatus.Starting)
|
|
||||||
|
|
||||||
val job =
|
val job =
|
||||||
applicationScope.launch(ioDispatcher) {
|
applicationScope.launch(ioDispatcher) {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -3,18 +3,23 @@ package com.zaneschepke.wireguardautotunnel.core.tunnel
|
|||||||
import com.wireguard.android.backend.Backend
|
import com.wireguard.android.backend.Backend
|
||||||
import com.wireguard.android.backend.BackendException
|
import com.wireguard.android.backend.BackendException
|
||||||
import com.wireguard.android.backend.Tunnel as WgTunnel
|
import com.wireguard.android.backend.Tunnel as WgTunnel
|
||||||
|
import com.zaneschepke.wireguardautotunnel.R
|
||||||
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.di.Kernel
|
import com.zaneschepke.wireguardautotunnel.di.Kernel
|
||||||
import com.zaneschepke.wireguardautotunnel.domain.enums.BackendMode
|
import com.zaneschepke.wireguardautotunnel.domain.enums.BackendMode
|
||||||
import com.zaneschepke.wireguardautotunnel.domain.enums.TunnelStatus
|
import com.zaneschepke.wireguardautotunnel.domain.enums.TunnelStatus
|
||||||
import com.zaneschepke.wireguardautotunnel.domain.events.BackendCoreException
|
import com.zaneschepke.wireguardautotunnel.domain.events.DnsFailure
|
||||||
|
import com.zaneschepke.wireguardautotunnel.domain.events.InvalidConfig
|
||||||
|
import com.zaneschepke.wireguardautotunnel.domain.events.KernelTunnelName
|
||||||
|
import com.zaneschepke.wireguardautotunnel.domain.events.UnknownError
|
||||||
import com.zaneschepke.wireguardautotunnel.domain.model.TunnelConfig
|
import com.zaneschepke.wireguardautotunnel.domain.model.TunnelConfig
|
||||||
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
|
||||||
import com.zaneschepke.wireguardautotunnel.util.extensions.asTunnelState
|
import com.zaneschepke.wireguardautotunnel.util.extensions.asTunnelState
|
||||||
import com.zaneschepke.wireguardautotunnel.util.extensions.toBackendCoreException
|
import com.zaneschepke.wireguardautotunnel.util.extensions.toBackendCoreException
|
||||||
import java.util.concurrent.ConcurrentHashMap
|
import java.util.concurrent.ConcurrentHashMap
|
||||||
|
import java.util.regex.Pattern
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import kotlinx.coroutines.*
|
import kotlinx.coroutines.*
|
||||||
import kotlinx.coroutines.channels.Channel
|
import kotlinx.coroutines.channels.Channel
|
||||||
@@ -30,14 +35,27 @@ class KernelTunnel
|
|||||||
constructor(
|
constructor(
|
||||||
@ApplicationScope applicationScope: CoroutineScope,
|
@ApplicationScope applicationScope: CoroutineScope,
|
||||||
@IoDispatcher ioDispatcher: CoroutineDispatcher,
|
@IoDispatcher ioDispatcher: CoroutineDispatcher,
|
||||||
|
private val runConfigHelper: RunConfigHelper,
|
||||||
@Kernel private val backend: Backend,
|
@Kernel private val backend: Backend,
|
||||||
) : BaseTunnel(applicationScope, ioDispatcher) {
|
) : BaseTunnel(applicationScope, ioDispatcher) {
|
||||||
|
|
||||||
private val runtimeTunnels = ConcurrentHashMap<Int, WgTunnel>()
|
private val runtimeTunnels = ConcurrentHashMap<Int, WgTunnel>()
|
||||||
|
|
||||||
// TODO Add DNS settings
|
private fun validateWireGuardInterfaceName(name: String): Result<Unit> {
|
||||||
|
if (name.isEmpty() || name.length > 15)
|
||||||
|
return Result.failure(KernelTunnelName(R.string.kernel_name_error))
|
||||||
|
if (name == "." || name == "..") {
|
||||||
|
return Result.failure(KernelTunnelName(R.string.kernel_name_dots))
|
||||||
|
}
|
||||||
|
val pattern = Pattern.compile("^[a-zA-Z0-9_=+.-]{1,15}$")
|
||||||
|
if (!pattern.matcher(name).matches()) {
|
||||||
|
return Result.failure(KernelTunnelName(R.string.kernel_name_special_characters))
|
||||||
|
}
|
||||||
|
return Result.success(Unit)
|
||||||
|
}
|
||||||
|
|
||||||
override fun tunnelStateFlow(tunnelConfig: TunnelConfig): Flow<TunnelStatus> = callbackFlow {
|
override fun tunnelStateFlow(tunnelConfig: TunnelConfig): Flow<TunnelStatus> = callbackFlow {
|
||||||
if (!tunnelConfig.isNameKernelCompatible) close(BackendCoreException.TunnelNameTooLong)
|
validateWireGuardInterfaceName(tunnelConfig.name).onFailure { close(it) }
|
||||||
|
|
||||||
val stateChannel = Channel<WgTunnel.State>()
|
val stateChannel = Channel<WgTunnel.State>()
|
||||||
|
|
||||||
@@ -51,21 +69,22 @@ constructor(
|
|||||||
try {
|
try {
|
||||||
withTimeout(STARTUP_TIMEOUT_MS) {
|
withTimeout(STARTUP_TIMEOUT_MS) {
|
||||||
updateTunnelStatus(tunnelConfig.id, TunnelStatus.Starting)
|
updateTunnelStatus(tunnelConfig.id, TunnelStatus.Starting)
|
||||||
backend.setState(runtimeTunnel, WgTunnel.State.UP, tunnelConfig.toWgConfig())
|
val runConfig = runConfigHelper.buildWgRunConfig(tunnelConfig)
|
||||||
|
backend.setState(runtimeTunnel, WgTunnel.State.UP, runConfig)
|
||||||
}
|
}
|
||||||
} catch (e: TimeoutCancellationException) {
|
} catch (e: TimeoutCancellationException) {
|
||||||
Timber.e("Startup timed out for ${tunnelConfig.name}")
|
Timber.e("Startup timed out for ${tunnelConfig.name}")
|
||||||
errors.emit(tunnelConfig.name to BackendCoreException.DNS)
|
errors.emit(tunnelConfig.name to DnsFailure())
|
||||||
forceStopTunnel(tunnelConfig.id)
|
forceStopTunnel(tunnelConfig.id)
|
||||||
close()
|
close()
|
||||||
} catch (e: BackendException) {
|
} catch (e: BackendException) {
|
||||||
close(e.toBackendCoreException())
|
close(e.toBackendCoreException())
|
||||||
} catch (e: IllegalArgumentException) {
|
} catch (e: IllegalArgumentException) {
|
||||||
Timber.e(e, "Invalid backend arguments")
|
Timber.e(e, "Invalid backend arguments")
|
||||||
close(BackendCoreException.Config)
|
close(InvalidConfig())
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Timber.e(e, "Error while setting tunnel state")
|
Timber.e(e, "Error while setting tunnel state")
|
||||||
close(BackendCoreException.Unknown)
|
close(UnknownError())
|
||||||
}
|
}
|
||||||
|
|
||||||
awaitClose {
|
awaitClose {
|
||||||
|
|||||||
@@ -0,0 +1,104 @@
|
|||||||
|
package com.zaneschepke.wireguardautotunnel.core.tunnel
|
||||||
|
|
||||||
|
import com.zaneschepke.wireguardautotunnel.data.model.AppMode
|
||||||
|
import com.zaneschepke.wireguardautotunnel.data.model.DnsProtocol
|
||||||
|
import com.zaneschepke.wireguardautotunnel.domain.events.InvalidConfig
|
||||||
|
import com.zaneschepke.wireguardautotunnel.domain.model.DnsSettings
|
||||||
|
import com.zaneschepke.wireguardautotunnel.domain.model.GeneralSettings
|
||||||
|
import com.zaneschepke.wireguardautotunnel.domain.model.ProxySettings
|
||||||
|
import com.zaneschepke.wireguardautotunnel.domain.model.TunnelConfig
|
||||||
|
import com.zaneschepke.wireguardautotunnel.domain.repository.DnsSettingsRepository
|
||||||
|
import com.zaneschepke.wireguardautotunnel.domain.repository.GeneralSettingRepository
|
||||||
|
import com.zaneschepke.wireguardautotunnel.domain.repository.ProxySettingsRepository
|
||||||
|
import com.zaneschepke.wireguardautotunnel.domain.repository.TunnelRepository
|
||||||
|
import java.util.*
|
||||||
|
import javax.inject.Inject
|
||||||
|
import kotlinx.coroutines.flow.firstOrNull
|
||||||
|
import org.amnezia.awg.config.Config
|
||||||
|
import org.amnezia.awg.config.proxy.HttpProxy
|
||||||
|
import org.amnezia.awg.config.proxy.Socks5Proxy
|
||||||
|
|
||||||
|
class RunConfigHelper
|
||||||
|
@Inject
|
||||||
|
constructor(
|
||||||
|
private val settingsRepository: GeneralSettingRepository,
|
||||||
|
private val proxySettingsRepository: ProxySettingsRepository,
|
||||||
|
private val dnsSettingsRepository: DnsSettingsRepository,
|
||||||
|
private val tunnelsRepository: TunnelRepository,
|
||||||
|
) {
|
||||||
|
|
||||||
|
private data class PrepResult(
|
||||||
|
val effectiveConfig: TunnelConfig,
|
||||||
|
val generalSettings: GeneralSettings,
|
||||||
|
val dnsSettings: DnsSettings,
|
||||||
|
)
|
||||||
|
|
||||||
|
private suspend fun prepare(tunnelConfig: TunnelConfig): PrepResult {
|
||||||
|
val generalSettings = settingsRepository.getGeneralSettings()
|
||||||
|
val dnsSettings = dnsSettingsRepository.getDnsSettings()
|
||||||
|
val effectiveConfig =
|
||||||
|
if (
|
||||||
|
generalSettings.isGlobalSplitTunnelEnabled || dnsSettings.isGlobalTunnelDnsEnabled
|
||||||
|
) {
|
||||||
|
val globalConfig =
|
||||||
|
tunnelsRepository.globalTunnelFlow.firstOrNull() ?: throw InvalidConfig()
|
||||||
|
tunnelConfig.copyWithGlobalValues(
|
||||||
|
globalConfig,
|
||||||
|
dnsSettings.isGlobalTunnelDnsEnabled,
|
||||||
|
generalSettings.isGlobalSplitTunnelEnabled,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
tunnelConfig
|
||||||
|
}
|
||||||
|
return PrepResult(effectiveConfig, generalSettings, dnsSettings)
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun buildAmRunConfig(tunnelConfig: TunnelConfig): Config {
|
||||||
|
val prep = prepare(tunnelConfig)
|
||||||
|
val proxies =
|
||||||
|
if (prep.generalSettings.appMode == AppMode.PROXY) {
|
||||||
|
val proxySettings = proxySettingsRepository.getProxySettings()
|
||||||
|
buildList {
|
||||||
|
if (proxySettings.socks5ProxyEnabled) {
|
||||||
|
add(
|
||||||
|
Socks5Proxy(
|
||||||
|
proxySettings.socks5ProxyBindAddress
|
||||||
|
?: ProxySettings.DEFAULT_SOCKS_BIND_ADDRESS,
|
||||||
|
proxySettings.proxyUsername,
|
||||||
|
proxySettings.proxyPassword,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (proxySettings.httpProxyEnabled) {
|
||||||
|
add(
|
||||||
|
HttpProxy(
|
||||||
|
proxySettings.httpProxyBindAddress
|
||||||
|
?: ProxySettings.DEFAULT_HTTP_BIND_ADDRESS,
|
||||||
|
proxySettings.proxyUsername,
|
||||||
|
proxySettings.proxyPassword,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
emptyList()
|
||||||
|
}
|
||||||
|
val amConfig = prep.effectiveConfig.toAmConfig()
|
||||||
|
return Config.Builder()
|
||||||
|
.setInterface(amConfig.`interface`)
|
||||||
|
.addPeers(amConfig.peers)
|
||||||
|
.addProxies(proxies)
|
||||||
|
.setDnsSettings(
|
||||||
|
org.amnezia.awg.config.DnsSettings(
|
||||||
|
prep.dnsSettings.dnsProtocol == DnsProtocol.DOH,
|
||||||
|
Optional.ofNullable(prep.dnsSettings.dnsEndpoint),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun buildWgRunConfig(tunnelConfig: TunnelConfig): com.wireguard.config.Config {
|
||||||
|
val prep = prepare(tunnelConfig)
|
||||||
|
return prep.effectiveConfig.toWgConfig()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -16,4 +16,6 @@ class RuntimeAwgTunnel(
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun isIpv4ResolutionPreferred() = tunnelConfig.isIpv4Preferred
|
override fun isIpv4ResolutionPreferred() = tunnelConfig.isIpv4Preferred
|
||||||
|
|
||||||
|
override fun isMetered() = tunnelConfig.isMetered
|
||||||
}
|
}
|
||||||
|
|||||||
+74
-56
@@ -1,18 +1,19 @@
|
|||||||
package com.zaneschepke.wireguardautotunnel.core.tunnel
|
package com.zaneschepke.wireguardautotunnel.core.tunnel
|
||||||
|
|
||||||
import com.zaneschepke.wireguardautotunnel.core.service.ServiceManager
|
import com.zaneschepke.wireguardautotunnel.core.service.ServiceManager
|
||||||
import com.zaneschepke.wireguardautotunnel.data.entity.TunnelConfig as Entity
|
|
||||||
import com.zaneschepke.wireguardautotunnel.data.model.AppMode
|
import com.zaneschepke.wireguardautotunnel.data.model.AppMode
|
||||||
import com.zaneschepke.wireguardautotunnel.di.*
|
import com.zaneschepke.wireguardautotunnel.di.*
|
||||||
import com.zaneschepke.wireguardautotunnel.domain.enums.BackendMode
|
import com.zaneschepke.wireguardautotunnel.domain.enums.BackendMode
|
||||||
import com.zaneschepke.wireguardautotunnel.domain.enums.TunnelStatus
|
import com.zaneschepke.wireguardautotunnel.domain.enums.TunnelStatus
|
||||||
import com.zaneschepke.wireguardautotunnel.domain.events.BackendCoreException
|
import com.zaneschepke.wireguardautotunnel.domain.events.BackendCoreException
|
||||||
import com.zaneschepke.wireguardautotunnel.domain.events.BackendMessage
|
import com.zaneschepke.wireguardautotunnel.domain.events.BackendMessage
|
||||||
|
import com.zaneschepke.wireguardautotunnel.domain.events.NotAuthorized
|
||||||
import com.zaneschepke.wireguardautotunnel.domain.model.AutoTunnelSettings
|
import com.zaneschepke.wireguardautotunnel.domain.model.AutoTunnelSettings
|
||||||
import com.zaneschepke.wireguardautotunnel.domain.model.GeneralSettings
|
import com.zaneschepke.wireguardautotunnel.domain.model.GeneralSettings
|
||||||
import com.zaneschepke.wireguardautotunnel.domain.model.TunnelConfig
|
import com.zaneschepke.wireguardautotunnel.domain.model.TunnelConfig
|
||||||
import com.zaneschepke.wireguardautotunnel.domain.repository.AutoTunnelSettingsRepository
|
import com.zaneschepke.wireguardautotunnel.domain.repository.AutoTunnelSettingsRepository
|
||||||
import com.zaneschepke.wireguardautotunnel.domain.repository.GeneralSettingRepository
|
import com.zaneschepke.wireguardautotunnel.domain.repository.GeneralSettingRepository
|
||||||
|
import com.zaneschepke.wireguardautotunnel.domain.repository.LockdownSettingsRepository
|
||||||
import com.zaneschepke.wireguardautotunnel.domain.repository.TunnelRepository
|
import com.zaneschepke.wireguardautotunnel.domain.repository.TunnelRepository
|
||||||
import com.zaneschepke.wireguardautotunnel.domain.state.LogHealthState
|
import com.zaneschepke.wireguardautotunnel.domain.state.LogHealthState
|
||||||
import com.zaneschepke.wireguardautotunnel.domain.state.PingState
|
import com.zaneschepke.wireguardautotunnel.domain.state.PingState
|
||||||
@@ -39,6 +40,7 @@ constructor(
|
|||||||
private val serviceManager: ServiceManager,
|
private val serviceManager: ServiceManager,
|
||||||
private val settingsRepository: GeneralSettingRepository,
|
private val settingsRepository: GeneralSettingRepository,
|
||||||
private val autoTunnelSettingsRepository: AutoTunnelSettingsRepository,
|
private val autoTunnelSettingsRepository: AutoTunnelSettingsRepository,
|
||||||
|
private val lockdownSettingsRepository: LockdownSettingsRepository,
|
||||||
private val tunnelsRepository: TunnelRepository,
|
private val tunnelsRepository: TunnelRepository,
|
||||||
private val tunnelMonitor: TunnelMonitor,
|
private val tunnelMonitor: TunnelMonitor,
|
||||||
@ApplicationScope private val applicationScope: CoroutineScope,
|
@ApplicationScope private val applicationScope: CoroutineScope,
|
||||||
@@ -69,12 +71,6 @@ constructor(
|
|||||||
val condition: (SideEffectState) -> Boolean,
|
val condition: (SideEffectState) -> Boolean,
|
||||||
)
|
)
|
||||||
|
|
||||||
private suspend fun getSettings(): GeneralSettings =
|
|
||||||
settingsRepository.flow.filterNotNull().first { it != GeneralSettings() }
|
|
||||||
|
|
||||||
private suspend fun getTunnels(): List<TunnelConfig> =
|
|
||||||
tunnelsRepository.flow.first { it.isNotEmpty() }
|
|
||||||
|
|
||||||
private val tunnelProviderFlow: StateFlow<TunnelProvider> = run {
|
private val tunnelProviderFlow: StateFlow<TunnelProvider> = run {
|
||||||
val currentBackend = AtomicReference(userspaceTunnel)
|
val currentBackend = AtomicReference(userspaceTunnel)
|
||||||
val currentSettings = AtomicReference(GeneralSettings())
|
val currentSettings = AtomicReference(GeneralSettings())
|
||||||
@@ -84,10 +80,7 @@ constructor(
|
|||||||
.filterNotNull()
|
.filterNotNull()
|
||||||
// ignore default state
|
// ignore default state
|
||||||
.filterNot { it == GeneralSettings() }
|
.filterNot { it == GeneralSettings() }
|
||||||
.distinctUntilChanged { old, new ->
|
.distinctUntilChangedBy { it.appMode }
|
||||||
old.appMode == new.appMode &&
|
|
||||||
old.isLanOnKillSwitchEnabled == new.isLanOnKillSwitchEnabled
|
|
||||||
}
|
|
||||||
.map { settings ->
|
.map { settings ->
|
||||||
Timber.d("App mode changes with ${settings.appMode}")
|
Timber.d("App mode changes with ${settings.appMode}")
|
||||||
val backend =
|
val backend =
|
||||||
@@ -108,7 +101,7 @@ constructor(
|
|||||||
handleModeChangeCleanup(previousBackend, previousSettings.appMode)
|
handleModeChangeCleanup(previousBackend, previousSettings.appMode)
|
||||||
}
|
}
|
||||||
if (settings.appMode == AppMode.LOCK_DOWN) {
|
if (settings.appMode == AppMode.LOCK_DOWN) {
|
||||||
handleLockDownModeInit(settings.isLanOnKillSwitchEnabled)
|
handleLockDownModeInit()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.map { (_, backend) -> backend }
|
.map { (_, backend) -> backend }
|
||||||
@@ -235,17 +228,7 @@ constructor(
|
|||||||
activeTunnels.first { it.isEmpty() }
|
activeTunnels.first { it.isEmpty() }
|
||||||
} ?: run { activeTunnels.value.keys.forEach { id -> provider.forceStopTunnel(id) } }
|
} ?: run { activeTunnels.value.keys.forEach { id -> provider.forceStopTunnel(id) } }
|
||||||
}
|
}
|
||||||
val runConfig =
|
tunnelProviderFlow.value.startTunnel(tunnelConfig)
|
||||||
tunnelConfig.run {
|
|
||||||
if (getSettings().isTunnelGlobalsEnabled) {
|
|
||||||
val globalTunnel =
|
|
||||||
getTunnels().firstOrNull { it.name == Entity.GLOBAL_CONFIG_NAME }
|
|
||||||
?: return@run this
|
|
||||||
return@run copyWithGlobalValues(globalTunnel)
|
|
||||||
}
|
|
||||||
this
|
|
||||||
}
|
|
||||||
tunnelProviderFlow.value.startTunnel(runConfig)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun stopTunnel(tunnelId: Int) {
|
override suspend fun stopTunnel(tunnelId: Int) {
|
||||||
@@ -302,13 +285,23 @@ constructor(
|
|||||||
serviceManager.updateTunnelTile()
|
serviceManager.updateTunnelTile()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleLockDownModeInit(withLanBypass: Boolean) {
|
// TODO this can crash if we haven't started foreground service yet, especially for
|
||||||
val allowedIps = if (withLanBypass) TunnelConfig.IPV4_PUBLIC_NETWORKS else emptySet()
|
// workerManager
|
||||||
|
private suspend fun handleLockDownModeInit() {
|
||||||
|
val lockdownSettings = lockdownSettingsRepository.getLockdownSettings()
|
||||||
|
val allowedIps =
|
||||||
|
if (lockdownSettings.bypassLan) TunnelConfig.IPV4_PUBLIC_NETWORKS else emptySet()
|
||||||
try {
|
try {
|
||||||
if (serviceManager.hasVpnPermission()) {
|
if (serviceManager.hasVpnPermission()) {
|
||||||
proxyUserspaceTunnel.setBackendMode(BackendMode.KillSwitch(allowedIps))
|
proxyUserspaceTunnel.setBackendMode(
|
||||||
|
BackendMode.KillSwitch(
|
||||||
|
allowedIps,
|
||||||
|
lockdownSettings.metered,
|
||||||
|
lockdownSettings.dualStack,
|
||||||
|
)
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
throw BackendCoreException.NotAuthorized
|
throw NotAuthorized()
|
||||||
}
|
}
|
||||||
} catch (e: BackendCoreException) {
|
} catch (e: BackendCoreException) {
|
||||||
localErrorEvents.tryEmit(null to e)
|
localErrorEvents.tryEmit(null to e)
|
||||||
@@ -325,18 +318,6 @@ constructor(
|
|||||||
proxyUserspaceTunnel.setBackendMode(BackendMode.Inactive)
|
proxyUserspaceTunnel.setBackendMode(BackendMode.Inactive)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun isVpnAuthorized(
|
|
||||||
mode: AppMode,
|
|
||||||
hasVpnPermission: () -> Boolean = { serviceManager.hasVpnPermission() },
|
|
||||||
): Boolean {
|
|
||||||
return when (mode) {
|
|
||||||
AppMode.VPN,
|
|
||||||
AppMode.LOCK_DOWN -> hasVpnPermission()
|
|
||||||
AppMode.KERNEL,
|
|
||||||
AppMode.PROXY -> true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun handleRestore() =
|
suspend fun handleRestore() =
|
||||||
withContext(ioDispatcher) {
|
withContext(ioDispatcher) {
|
||||||
val settings = settingsRepository.getGeneralSettings()
|
val settings = settingsRepository.getGeneralSettings()
|
||||||
@@ -344,20 +325,19 @@ constructor(
|
|||||||
val tunnels = tunnelsRepository.getAll()
|
val tunnels = tunnelsRepository.getAll()
|
||||||
if (autoTunnelSettings.isAutoTunnelEnabled)
|
if (autoTunnelSettings.isAutoTunnelEnabled)
|
||||||
return@withContext restoreAutoTunnel(autoTunnelSettings)
|
return@withContext restoreAutoTunnel(autoTunnelSettings)
|
||||||
if (isVpnAuthorized(settings.appMode)) {
|
if (settings.appMode == AppMode.LOCK_DOWN) handleLockDownModeInit()
|
||||||
when (val mode = settings.appMode) {
|
if (tunnels.any { it.isActive }) {
|
||||||
|
if (settings.appMode == AppMode.VPN && !serviceManager.hasVpnPermission())
|
||||||
|
return@withContext localErrorEvents.emit(null to NotAuthorized())
|
||||||
|
when (settings.appMode) {
|
||||||
AppMode.VPN,
|
AppMode.VPN,
|
||||||
AppMode.PROXY,
|
AppMode.PROXY,
|
||||||
AppMode.LOCK_DOWN -> {
|
AppMode.LOCK_DOWN -> {
|
||||||
if (mode == AppMode.LOCK_DOWN)
|
|
||||||
handleLockDownModeInit(settings.isLanOnKillSwitchEnabled)
|
|
||||||
tunnels.firstOrNull { it.isActive }?.let { startTunnel(it) }
|
tunnels.firstOrNull { it.isActive }?.let { startTunnel(it) }
|
||||||
}
|
}
|
||||||
AppMode.KERNEL ->
|
AppMode.KERNEL ->
|
||||||
tunnels.filter { it.isActive }.forEach { conf -> startTunnel(conf) }
|
tunnels.filter { it.isActive }.forEach { conf -> startTunnel(conf) }
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
localErrorEvents.emit(null to BackendCoreException.NotAuthorized)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -375,18 +355,15 @@ constructor(
|
|||||||
return@withContext restoreAutoTunnel(autoTunnelSettings)
|
return@withContext restoreAutoTunnel(autoTunnelSettings)
|
||||||
if (settings.isRestoreOnBootEnabled) {
|
if (settings.isRestoreOnBootEnabled) {
|
||||||
tunnelsRepository.resetActiveTunnels()
|
tunnelsRepository.resetActiveTunnels()
|
||||||
if (isVpnAuthorized(settings.appMode)) {
|
when (settings.appMode) {
|
||||||
when (val mode = settings.appMode) {
|
AppMode.LOCK_DOWN -> handleLockDownModeInit()
|
||||||
AppMode.LOCK_DOWN ->
|
AppMode.VPN ->
|
||||||
handleLockDownModeInit(settings.isLanOnKillSwitchEnabled)
|
if (!serviceManager.hasVpnPermission())
|
||||||
AppMode.KERNEL,
|
return@withContext localErrorEvents.emit(null to NotAuthorized())
|
||||||
AppMode.VPN,
|
AppMode.KERNEL,
|
||||||
AppMode.PROXY -> Unit
|
AppMode.PROXY -> Unit
|
||||||
}
|
|
||||||
defaultTunnel?.let { startTunnel(it) }
|
|
||||||
} else {
|
|
||||||
localErrorEvents.emit(null to BackendCoreException.NotAuthorized)
|
|
||||||
}
|
}
|
||||||
|
defaultTunnel?.let { startTunnel(it) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -420,6 +397,46 @@ constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
suspend fun restartActiveTunnel(id: Int) =
|
||||||
|
withContext(ioDispatcher) {
|
||||||
|
val activeIds = activeTunnels.value.keys.toList()
|
||||||
|
if (activeIds.isEmpty()) return@withContext
|
||||||
|
if (!activeIds.contains(id)) return@withContext
|
||||||
|
val tunnel = tunnelsRepository.getById(id) ?: return@withContext
|
||||||
|
restartTunnel(tunnel)
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun restartActiveTunnels() =
|
||||||
|
withContext(ioDispatcher) {
|
||||||
|
val activeIds = activeTunnels.value.keys.toList()
|
||||||
|
if (activeIds.isEmpty()) return@withContext
|
||||||
|
|
||||||
|
val tunnels = tunnelsRepository.getAll()
|
||||||
|
if (tunnels.isEmpty()) return@withContext
|
||||||
|
|
||||||
|
supervisorScope {
|
||||||
|
activeIds.forEach { id ->
|
||||||
|
val tunnel =
|
||||||
|
tunnels.find { it.id == id }
|
||||||
|
?: run {
|
||||||
|
Timber.w("Tunnel config $id not found; skipping restart")
|
||||||
|
return@forEach
|
||||||
|
}
|
||||||
|
restartTunnel(tunnel)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun restartTunnel(tunnel: TunnelConfig) {
|
||||||
|
runCatching { stopTunnel(tunnel.id) }
|
||||||
|
.onFailure { e -> Timber.e(e, "Failed to stop tunnel ${tunnel.id} during restart") }
|
||||||
|
|
||||||
|
delay(RESTART_TUNNEL_DELAY)
|
||||||
|
|
||||||
|
runCatching { startTunnel(tunnel) }
|
||||||
|
.onFailure { e -> Timber.e(e, "Failed to restart tunnel ${tunnel.id}") }
|
||||||
|
}
|
||||||
|
|
||||||
private suspend fun handleDynamicDnsMonitoring(
|
private suspend fun handleDynamicDnsMonitoring(
|
||||||
activeTuns: Map<Int, TunnelState>,
|
activeTuns: Map<Int, TunnelState>,
|
||||||
configs: List<TunnelConfig>,
|
configs: List<TunnelConfig>,
|
||||||
@@ -536,5 +553,6 @@ constructor(
|
|||||||
companion object {
|
companion object {
|
||||||
const val BASE_BACKOFF = 30_000L
|
const val BASE_BACKOFF = 30_000L
|
||||||
const val MAX_BACKOFF_TIME = 300_000L
|
const val MAX_BACKOFF_TIME = 300_000L
|
||||||
|
const val RESTART_TUNNEL_DELAY = 300L
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+31
-18
@@ -1,5 +1,6 @@
|
|||||||
package com.zaneschepke.wireguardautotunnel.core.tunnel
|
package com.zaneschepke.wireguardautotunnel.core.tunnel
|
||||||
|
|
||||||
|
import android.os.PowerManager
|
||||||
import com.zaneschepke.logcatter.LogReader
|
import com.zaneschepke.logcatter.LogReader
|
||||||
import com.zaneschepke.networkmonitor.NetworkMonitor
|
import com.zaneschepke.networkmonitor.NetworkMonitor
|
||||||
import com.zaneschepke.wireguardautotunnel.data.model.AppMode
|
import com.zaneschepke.wireguardautotunnel.data.model.AppMode
|
||||||
@@ -11,17 +12,17 @@ import com.zaneschepke.wireguardautotunnel.domain.repository.TunnelRepository
|
|||||||
import com.zaneschepke.wireguardautotunnel.domain.state.*
|
import com.zaneschepke.wireguardautotunnel.domain.state.*
|
||||||
import com.zaneschepke.wireguardautotunnel.util.extensions.toMillis
|
import com.zaneschepke.wireguardautotunnel.util.extensions.toMillis
|
||||||
import com.zaneschepke.wireguardautotunnel.util.network.NetworkUtils
|
import com.zaneschepke.wireguardautotunnel.util.network.NetworkUtils
|
||||||
import dagger.hilt.android.scopes.ServiceScoped
|
|
||||||
import inet.ipaddr.AddressValueException
|
import inet.ipaddr.AddressValueException
|
||||||
import inet.ipaddr.IPAddress
|
import inet.ipaddr.IPAddress
|
||||||
import inet.ipaddr.IPAddressString
|
import inet.ipaddr.IPAddressString
|
||||||
import io.ktor.util.collections.*
|
import io.ktor.util.collections.*
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
import javax.inject.Singleton
|
||||||
import kotlinx.coroutines.*
|
import kotlinx.coroutines.*
|
||||||
import kotlinx.coroutines.flow.*
|
import kotlinx.coroutines.flow.*
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
|
||||||
@ServiceScoped
|
@Singleton
|
||||||
class TunnelMonitor
|
class TunnelMonitor
|
||||||
@Inject
|
@Inject
|
||||||
constructor(
|
constructor(
|
||||||
@@ -31,6 +32,7 @@ constructor(
|
|||||||
private val networkMonitor: NetworkMonitor,
|
private val networkMonitor: NetworkMonitor,
|
||||||
private val networkUtils: NetworkUtils,
|
private val networkUtils: NetworkUtils,
|
||||||
private val logReader: LogReader,
|
private val logReader: LogReader,
|
||||||
|
private val powerManager: PowerManager,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
@OptIn(FlowPreview::class)
|
@OptIn(FlowPreview::class)
|
||||||
@@ -74,7 +76,7 @@ constructor(
|
|||||||
else -> null
|
else -> null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.distinctUntilChangedBy { it.isHealthy } // Only emit when health changes
|
.distinctUntilChangedBy { it.isHealthy }
|
||||||
.collect { logHealthState ->
|
.collect { logHealthState ->
|
||||||
Timber.d("Tunnel log health updated for ${tunnelConfig.name}: $logHealthState")
|
Timber.d("Tunnel log health updated for ${tunnelConfig.name}: $logHealthState")
|
||||||
updateTunnelStatus(tunnelConfig.id, null, null, null, logHealthState)
|
updateTunnelStatus(tunnelConfig.id, null, null, null, logHealthState)
|
||||||
@@ -199,6 +201,7 @@ constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
val attemptTime = System.currentTimeMillis()
|
val attemptTime = System.currentTimeMillis()
|
||||||
|
val timeout = settings.tunnelPingTimeoutSeconds?.toMillis() ?: 5000L
|
||||||
runCatching {
|
runCatching {
|
||||||
withTimeout(
|
withTimeout(
|
||||||
settings.tunnelPingTimeoutSeconds?.toMillis() ?: 5000L
|
settings.tunnelPingTimeoutSeconds?.toMillis() ?: 5000L
|
||||||
@@ -270,20 +273,28 @@ constructor(
|
|||||||
|
|
||||||
while (isActive) {
|
while (isActive) {
|
||||||
ensureActive()
|
ensureActive()
|
||||||
if (isNetworkConnected.value) {
|
if (!powerManager.isDeviceIdleMode) {
|
||||||
performPing()
|
if (isNetworkConnected.value) {
|
||||||
} else {
|
performPing()
|
||||||
pingStatsFlow.update { current ->
|
} else {
|
||||||
current.mapValues { entry ->
|
pingStatsFlow.update { current ->
|
||||||
entry.value.copy(
|
current.mapValues { entry ->
|
||||||
isReachable = false,
|
entry.value.copy(
|
||||||
failureReason = FailureReason.NoConnectivity,
|
isReachable = false,
|
||||||
lastPingAttemptMillis = System.currentTimeMillis(),
|
failureReason = FailureReason.NoConnectivity,
|
||||||
)
|
lastPingAttemptMillis = System.currentTimeMillis(),
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
ensureActive()
|
||||||
|
updateTunnelStatus(
|
||||||
|
tunnelConfig.id,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
pingStatsFlow.value,
|
||||||
|
null,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
ensureActive()
|
|
||||||
updateTunnelStatus(tunnelConfig.id, null, null, pingStatsFlow.value, null)
|
|
||||||
}
|
}
|
||||||
delay(settings.tunnelPingIntervalSeconds.toMillis())
|
delay(settings.tunnelPingIntervalSeconds.toMillis())
|
||||||
}
|
}
|
||||||
@@ -300,9 +311,11 @@ constructor(
|
|||||||
) = coroutineScope {
|
) = coroutineScope {
|
||||||
while (isActive) {
|
while (isActive) {
|
||||||
ensureActive()
|
ensureActive()
|
||||||
val stats = getStatistics(tunnelId)
|
if (!powerManager.isDeviceIdleMode) {
|
||||||
ensureActive()
|
val stats = getStatistics(tunnelId)
|
||||||
updateTunnelStatus(tunnelId, null, stats, null, null)
|
ensureActive()
|
||||||
|
updateTunnelStatus(tunnelId, null, stats, null, null)
|
||||||
|
}
|
||||||
delay(STATS_DELAY)
|
delay(STATS_DELAY)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+11
-69
@@ -1,15 +1,11 @@
|
|||||||
package com.zaneschepke.wireguardautotunnel.core.tunnel
|
package com.zaneschepke.wireguardautotunnel.core.tunnel
|
||||||
|
|
||||||
import com.zaneschepke.wireguardautotunnel.data.model.DnsProtocol
|
|
||||||
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.enums.BackendMode
|
import com.zaneschepke.wireguardautotunnel.domain.enums.BackendMode
|
||||||
import com.zaneschepke.wireguardautotunnel.domain.enums.TunnelStatus
|
import com.zaneschepke.wireguardautotunnel.domain.enums.TunnelStatus
|
||||||
import com.zaneschepke.wireguardautotunnel.domain.events.BackendCoreException
|
import com.zaneschepke.wireguardautotunnel.domain.events.*
|
||||||
import com.zaneschepke.wireguardautotunnel.domain.model.ProxySettings
|
|
||||||
import com.zaneschepke.wireguardautotunnel.domain.model.TunnelConfig
|
import com.zaneschepke.wireguardautotunnel.domain.model.TunnelConfig
|
||||||
import com.zaneschepke.wireguardautotunnel.domain.repository.DnsSettingsRepository
|
|
||||||
import com.zaneschepke.wireguardautotunnel.domain.repository.ProxySettingsRepository
|
|
||||||
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
|
||||||
import com.zaneschepke.wireguardautotunnel.util.extensions.asAmBackendMode
|
import com.zaneschepke.wireguardautotunnel.util.extensions.asAmBackendMode
|
||||||
@@ -17,7 +13,6 @@ import com.zaneschepke.wireguardautotunnel.util.extensions.asBackendMode
|
|||||||
import com.zaneschepke.wireguardautotunnel.util.extensions.asTunnelState
|
import com.zaneschepke.wireguardautotunnel.util.extensions.asTunnelState
|
||||||
import com.zaneschepke.wireguardautotunnel.util.extensions.toBackendCoreException
|
import com.zaneschepke.wireguardautotunnel.util.extensions.toBackendCoreException
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.util.*
|
|
||||||
import java.util.concurrent.ConcurrentHashMap
|
import java.util.concurrent.ConcurrentHashMap
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import kotlinx.coroutines.*
|
import kotlinx.coroutines.*
|
||||||
@@ -29,13 +24,7 @@ import kotlinx.coroutines.flow.consumeAsFlow
|
|||||||
import kotlinx.coroutines.flow.update
|
import kotlinx.coroutines.flow.update
|
||||||
import org.amnezia.awg.backend.Backend
|
import org.amnezia.awg.backend.Backend
|
||||||
import org.amnezia.awg.backend.BackendException
|
import org.amnezia.awg.backend.BackendException
|
||||||
import org.amnezia.awg.backend.ProxyGoBackend
|
|
||||||
import org.amnezia.awg.backend.Tunnel as AwgTunnel
|
import org.amnezia.awg.backend.Tunnel as AwgTunnel
|
||||||
import org.amnezia.awg.config.Config
|
|
||||||
import org.amnezia.awg.config.DnsSettings
|
|
||||||
import org.amnezia.awg.config.proxy.HttpProxy
|
|
||||||
import org.amnezia.awg.config.proxy.Proxy
|
|
||||||
import org.amnezia.awg.config.proxy.Socks5Proxy
|
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
|
||||||
class UserspaceTunnel
|
class UserspaceTunnel
|
||||||
@@ -43,9 +32,8 @@ class UserspaceTunnel
|
|||||||
constructor(
|
constructor(
|
||||||
@ApplicationScope applicationScope: CoroutineScope,
|
@ApplicationScope applicationScope: CoroutineScope,
|
||||||
@IoDispatcher ioDispatcher: CoroutineDispatcher,
|
@IoDispatcher ioDispatcher: CoroutineDispatcher,
|
||||||
private val proxySettingsRepository: ProxySettingsRepository,
|
|
||||||
private val dnsSettingsRepository: DnsSettingsRepository,
|
|
||||||
private val backend: Backend,
|
private val backend: Backend,
|
||||||
|
private val runConfigHelper: RunConfigHelper,
|
||||||
) : BaseTunnel(applicationScope, ioDispatcher) {
|
) : BaseTunnel(applicationScope, ioDispatcher) {
|
||||||
|
|
||||||
private val runtimeTunnels = ConcurrentHashMap<Int, AwgTunnel>()
|
private val runtimeTunnels = ConcurrentHashMap<Int, AwgTunnel>()
|
||||||
@@ -63,67 +51,21 @@ constructor(
|
|||||||
try {
|
try {
|
||||||
withTimeout(STARTUP_TIMEOUT_MS) {
|
withTimeout(STARTUP_TIMEOUT_MS) {
|
||||||
updateTunnelStatus(tunnelConfig.id, TunnelStatus.Starting)
|
updateTunnelStatus(tunnelConfig.id, TunnelStatus.Starting)
|
||||||
|
val runConfig = runConfigHelper.buildAmRunConfig(tunnelConfig)
|
||||||
val proxies: List<Proxy> =
|
backend.setState(runtimeTunnel, AwgTunnel.State.UP, runConfig)
|
||||||
when (backend) {
|
|
||||||
is ProxyGoBackend -> {
|
|
||||||
val proxySettings = proxySettingsRepository.getProxySettings()
|
|
||||||
Timber.d("Adding proxy configs")
|
|
||||||
buildList {
|
|
||||||
if (proxySettings.socks5ProxyEnabled) {
|
|
||||||
add(
|
|
||||||
Socks5Proxy(
|
|
||||||
proxySettings.socks5ProxyBindAddress
|
|
||||||
?: ProxySettings.DEFAULT_SOCKS_BIND_ADDRESS,
|
|
||||||
proxySettings.proxyUsername,
|
|
||||||
proxySettings.proxyPassword,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
if (proxySettings.httpProxyEnabled) {
|
|
||||||
add(
|
|
||||||
HttpProxy(
|
|
||||||
proxySettings.httpProxyBindAddress
|
|
||||||
?: ProxySettings.DEFAULT_HTTP_BIND_ADDRESS,
|
|
||||||
proxySettings.proxyUsername,
|
|
||||||
proxySettings.proxyPassword,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else -> emptyList()
|
|
||||||
}
|
|
||||||
val setting = dnsSettingsRepository.getDnsSettings()
|
|
||||||
val config = tunnelConfig.toAmConfig()
|
|
||||||
val updatedConfig =
|
|
||||||
Config.Builder()
|
|
||||||
.apply {
|
|
||||||
setInterface(config.`interface`)
|
|
||||||
addPeers(config.peers)
|
|
||||||
addProxies(proxies)
|
|
||||||
setDnsSettings(
|
|
||||||
DnsSettings(
|
|
||||||
setting.dnsProtocol == DnsProtocol.DOH,
|
|
||||||
Optional.ofNullable(setting.dnsEndpoint),
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
.build()
|
|
||||||
backend.setState(runtimeTunnel, AwgTunnel.State.UP, updatedConfig)
|
|
||||||
}
|
}
|
||||||
} catch (e: TimeoutCancellationException) {
|
} catch (_: TimeoutCancellationException) {
|
||||||
Timber.e("Startup timed out for ${tunnelConfig.name} (likely DNS hang)")
|
Timber.e("Startup timed out for ${tunnelConfig.name} (likely DNS hang)")
|
||||||
errors.emit(tunnelConfig.name to BackendCoreException.DNS)
|
errors.emit(tunnelConfig.name to DnsFailure())
|
||||||
forceStopTunnel(tunnelConfig.id)
|
forceStopTunnel(tunnelConfig.id)
|
||||||
close()
|
close()
|
||||||
} catch (e: BackendException) {
|
} catch (e: BackendException) {
|
||||||
close(e.toBackendCoreException())
|
close(e.toBackendCoreException())
|
||||||
} catch (e: IllegalArgumentException) {
|
} catch (_: IllegalArgumentException) {
|
||||||
close(BackendCoreException.Config)
|
close(InvalidConfig())
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Timber.e(e, "Error while setting tunnel state")
|
Timber.e(e, "Error while setting tunnel state")
|
||||||
close(BackendCoreException.Unknown)
|
close(UnknownError())
|
||||||
}
|
}
|
||||||
|
|
||||||
awaitClose {
|
awaitClose {
|
||||||
@@ -149,7 +91,7 @@ constructor(
|
|||||||
throw e.toBackendCoreException()
|
throw e.toBackendCoreException()
|
||||||
// TODO this should be mapped to BackendException in the lib
|
// TODO this should be mapped to BackendException in the lib
|
||||||
} catch (e: IOException) {
|
} catch (e: IOException) {
|
||||||
throw BackendCoreException.NotAuthorized
|
throw VpnUnauthorized()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -158,7 +100,7 @@ constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun handleDnsReresolve(tunnelConfig: TunnelConfig): Boolean {
|
override fun handleDnsReresolve(tunnelConfig: TunnelConfig): Boolean {
|
||||||
val tunnel = runtimeTunnels[tunnelConfig.id] ?: throw BackendCoreException.ServiceNotRunning
|
val tunnel = runtimeTunnels[tunnelConfig.id] ?: throw ServiceNotRunning()
|
||||||
return backend.resolveDDNS(tunnelConfig.toAmConfig(), tunnel.isIpv4ResolutionPreferred)
|
return backend.resolveDDNS(tunnelConfig.toAmConfig(), tunnel.isIpv4ResolutionPreferred)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -15,8 +15,9 @@ import com.zaneschepke.wireguardautotunnel.data.entity.*
|
|||||||
AutoTunnelSettings::class,
|
AutoTunnelSettings::class,
|
||||||
MonitoringSettings::class,
|
MonitoringSettings::class,
|
||||||
DnsSettings::class,
|
DnsSettings::class,
|
||||||
|
LockdownSettings::class,
|
||||||
],
|
],
|
||||||
version = 25,
|
version = 28,
|
||||||
autoMigrations =
|
autoMigrations =
|
||||||
[
|
[
|
||||||
AutoMigration(from = 1, to = 2),
|
AutoMigration(from = 1, to = 2),
|
||||||
@@ -42,6 +43,8 @@ import com.zaneschepke.wireguardautotunnel.data.entity.*
|
|||||||
AutoMigration(from = 21, to = 22),
|
AutoMigration(from = 21, to = 22),
|
||||||
AutoMigration(from = 22, to = 23),
|
AutoMigration(from = 22, to = 23),
|
||||||
AutoMigration(from = 24, to = 25),
|
AutoMigration(from = 24, to = 25),
|
||||||
|
AutoMigration(from = 26, to = 27, spec = GlobalsMigration::class),
|
||||||
|
AutoMigration(from = 27, to = 28, spec = DonationMigration::class),
|
||||||
],
|
],
|
||||||
exportSchema = true,
|
exportSchema = true,
|
||||||
)
|
)
|
||||||
@@ -57,6 +60,8 @@ abstract class AppDatabase : RoomDatabase() {
|
|||||||
|
|
||||||
abstract fun monitoringSettingsDao(): MonitoringSettingsDao
|
abstract fun monitoringSettingsDao(): MonitoringSettingsDao
|
||||||
|
|
||||||
|
abstract fun lockdownSettingsDao(): LockdownSettingsDao
|
||||||
|
|
||||||
abstract fun dnsSettingsDao(): DnsSettingsDao
|
abstract fun dnsSettingsDao(): DnsSettingsDao
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -112,3 +117,15 @@ class FixProxySettingsMigration : AutoMigrationSpec {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@RenameColumn.Entries(
|
||||||
|
RenameColumn(
|
||||||
|
tableName = "general_settings",
|
||||||
|
fromColumnName = "is_tunnel_globals_enabled",
|
||||||
|
toColumnName = "global_split_tunnel_enabled",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
class GlobalsMigration : AutoMigrationSpec
|
||||||
|
|
||||||
|
@DeleteColumn(tableName = "general_settings", columnName = "custom_split_packages")
|
||||||
|
class DonationMigration : AutoMigrationSpec
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ class DataStoreManager(
|
|||||||
companion object {
|
companion object {
|
||||||
val locationDisclosureShown = booleanPreferencesKey("LOCATION_DISCLOSURE_SHOWN")
|
val locationDisclosureShown = booleanPreferencesKey("LOCATION_DISCLOSURE_SHOWN")
|
||||||
val batteryDisableShown = booleanPreferencesKey("BATTERY_OPTIMIZE_DISABLE_SHOWN")
|
val batteryDisableShown = booleanPreferencesKey("BATTERY_OPTIMIZE_DISABLE_SHOWN")
|
||||||
|
val shouldShowDonationSnackbar = booleanPreferencesKey("SHOW_DONATION_SNACK")
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun init() {
|
suspend fun init() {
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
package com.zaneschepke.wireguardautotunnel.data.dao
|
||||||
|
|
||||||
|
import androidx.room.Dao
|
||||||
|
import androidx.room.Query
|
||||||
|
import androidx.room.Upsert
|
||||||
|
import com.zaneschepke.wireguardautotunnel.data.entity.LockdownSettings
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
|
||||||
|
@Dao
|
||||||
|
interface LockdownSettingsDao {
|
||||||
|
@Query("SELECT * FROM lockdown_settings LIMIT 1")
|
||||||
|
suspend fun getLockdownSettings(): LockdownSettings?
|
||||||
|
|
||||||
|
@Upsert suspend fun upsert(lockdownSettings: LockdownSettings)
|
||||||
|
|
||||||
|
@Query("SELECT * FROM lockdown_settings LIMIT 1")
|
||||||
|
fun getLockdownSettingsFlow(): Flow<LockdownSettings?>
|
||||||
|
}
|
||||||
@@ -3,4 +3,5 @@ package com.zaneschepke.wireguardautotunnel.data.entity
|
|||||||
data class AppState(
|
data class AppState(
|
||||||
val isLocationDisclosureShown: Boolean = false,
|
val isLocationDisclosureShown: Boolean = false,
|
||||||
val isBatteryOptimizationDisableShown: Boolean = false,
|
val isBatteryOptimizationDisableShown: Boolean = false,
|
||||||
|
val shouldShowDonationSnackbar: Boolean = false,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -11,4 +11,6 @@ data class DnsSettings(
|
|||||||
@ColumnInfo(name = "dns_protocol", defaultValue = "0")
|
@ColumnInfo(name = "dns_protocol", defaultValue = "0")
|
||||||
val dnsProtocol: DnsProtocol = DnsProtocol.fromValue(0),
|
val dnsProtocol: DnsProtocol = DnsProtocol.fromValue(0),
|
||||||
@ColumnInfo(name = "dns_endpoint") val dnsEndpoint: String? = null,
|
@ColumnInfo(name = "dns_endpoint") val dnsEndpoint: String? = null,
|
||||||
|
@ColumnInfo(name = "global_tunnel_dns_enabled", defaultValue = "0")
|
||||||
|
val isGlobalTunnelDnsEnabled: Boolean = false,
|
||||||
)
|
)
|
||||||
|
|||||||
+3
-6
@@ -14,8 +14,8 @@ data class GeneralSettings(
|
|||||||
val isRestoreOnBootEnabled: Boolean = false,
|
val isRestoreOnBootEnabled: Boolean = false,
|
||||||
@ColumnInfo(name = "is_multi_tunnel_enabled", defaultValue = "0")
|
@ColumnInfo(name = "is_multi_tunnel_enabled", defaultValue = "0")
|
||||||
val isMultiTunnelEnabled: Boolean = false,
|
val isMultiTunnelEnabled: Boolean = false,
|
||||||
@ColumnInfo(name = "is_tunnel_globals_enabled", defaultValue = "0")
|
@ColumnInfo(name = "global_split_tunnel_enabled", defaultValue = "0")
|
||||||
val isTunnelGlobalsEnabled: Boolean = false,
|
val isGlobalSplitTunnelEnabled: Boolean = false,
|
||||||
@ColumnInfo(name = "app_mode", defaultValue = "0") val appMode: AppMode = AppMode.fromValue(0),
|
@ColumnInfo(name = "app_mode", defaultValue = "0") val appMode: AppMode = AppMode.fromValue(0),
|
||||||
@ColumnInfo(name = "theme", defaultValue = "AUTOMATIC") val theme: String = "AUTOMATIC",
|
@ColumnInfo(name = "theme", defaultValue = "AUTOMATIC") val theme: String = "AUTOMATIC",
|
||||||
@ColumnInfo(name = "locale") val locale: String? = null,
|
@ColumnInfo(name = "locale") val locale: String? = null,
|
||||||
@@ -26,8 +26,5 @@ data class GeneralSettings(
|
|||||||
val isPinLockEnabled: Boolean = false,
|
val isPinLockEnabled: Boolean = false,
|
||||||
@ColumnInfo(name = "is_always_on_vpn_enabled", defaultValue = "0")
|
@ColumnInfo(name = "is_always_on_vpn_enabled", defaultValue = "0")
|
||||||
val isAlwaysOnVpnEnabled: Boolean = false,
|
val isAlwaysOnVpnEnabled: Boolean = false,
|
||||||
@ColumnInfo(name = "is_lan_on_kill_switch_enabled", defaultValue = "0")
|
@ColumnInfo(name = "already_donated", defaultValue = "0") val alreadyDonated: Boolean = false,
|
||||||
val isLanOnKillSwitchEnabled: Boolean = false,
|
|
||||||
@ColumnInfo(name = "custom_split_packages", defaultValue = "{}")
|
|
||||||
val customSplitPackages: Map<String, String> = emptyMap(),
|
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
package com.zaneschepke.wireguardautotunnel.data.entity
|
||||||
|
|
||||||
|
import androidx.room.ColumnInfo
|
||||||
|
import androidx.room.Entity
|
||||||
|
import androidx.room.PrimaryKey
|
||||||
|
|
||||||
|
@Entity(tableName = "lockdown_settings")
|
||||||
|
data class LockdownSettings(
|
||||||
|
@PrimaryKey(autoGenerate = true) val id: Long = 0,
|
||||||
|
@ColumnInfo(name = "bypass_lan", defaultValue = "0") val bypassLan: Boolean = false,
|
||||||
|
@ColumnInfo(name = "metered", defaultValue = "0") val metered: Boolean = false,
|
||||||
|
@ColumnInfo(name = "dual_stack", defaultValue = "0") val dualStack: Boolean = false,
|
||||||
|
)
|
||||||
@@ -28,8 +28,8 @@ data class TunnelConfig(
|
|||||||
@ColumnInfo(name = "position", defaultValue = "0") val position: Int = 0,
|
@ColumnInfo(name = "position", defaultValue = "0") val position: Int = 0,
|
||||||
@ColumnInfo(name = "auto_tunnel_apps", defaultValue = "[]")
|
@ColumnInfo(name = "auto_tunnel_apps", defaultValue = "[]")
|
||||||
val autoTunnelApps: Set<String> = emptySet(),
|
val autoTunnelApps: Set<String> = emptySet(),
|
||||||
|
@ColumnInfo(name = "is_metered", defaultValue = "true") val isMetered: Boolean = true,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val GLOBAL_CONFIG_NAME = "4675ab06-903a-438b-8485-6ea4187a9512"
|
const val GLOBAL_CONFIG_NAME = "4675ab06-903a-438b-8485-6ea4187a9512"
|
||||||
}
|
}
|
||||||
|
|||||||
+12
-2
@@ -4,7 +4,17 @@ import com.zaneschepke.wireguardautotunnel.data.entity.DnsSettings as Entity
|
|||||||
import com.zaneschepke.wireguardautotunnel.domain.model.DnsSettings as Domain
|
import com.zaneschepke.wireguardautotunnel.domain.model.DnsSettings as Domain
|
||||||
|
|
||||||
fun Entity.toDomain(): Domain =
|
fun Entity.toDomain(): Domain =
|
||||||
Domain(id = id, dnsProtocol = dnsProtocol, dnsEndpoint = dnsEndpoint)
|
Domain(
|
||||||
|
id = id,
|
||||||
|
dnsProtocol = dnsProtocol,
|
||||||
|
dnsEndpoint = dnsEndpoint,
|
||||||
|
isGlobalTunnelDnsEnabled = isGlobalTunnelDnsEnabled,
|
||||||
|
)
|
||||||
|
|
||||||
fun Domain.toEntity(): Entity =
|
fun Domain.toEntity(): Entity =
|
||||||
Entity(id = id, dnsProtocol = dnsProtocol, dnsEndpoint = dnsEndpoint)
|
Entity(
|
||||||
|
id = id,
|
||||||
|
dnsProtocol = dnsProtocol,
|
||||||
|
dnsEndpoint = dnsEndpoint,
|
||||||
|
isGlobalTunnelDnsEnabled = isGlobalTunnelDnsEnabled,
|
||||||
|
)
|
||||||
|
|||||||
+1
@@ -7,4 +7,5 @@ fun Entity.toDomain(): Domain =
|
|||||||
Domain(
|
Domain(
|
||||||
isLocationDisclosureShown = isLocationDisclosureShown,
|
isLocationDisclosureShown = isLocationDisclosureShown,
|
||||||
isBatteryOptimizationDisableShown = isBatteryOptimizationDisableShown,
|
isBatteryOptimizationDisableShown = isBatteryOptimizationDisableShown,
|
||||||
|
shouldShowDonationSnackbar = shouldShowDonationSnackbar,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
package com.zaneschepke.wireguardautotunnel.data.mapper
|
||||||
|
|
||||||
|
import com.zaneschepke.wireguardautotunnel.data.entity.LockdownSettings as Entity
|
||||||
|
import com.zaneschepke.wireguardautotunnel.domain.model.LockdownSettings as Domain
|
||||||
|
|
||||||
|
fun Entity.toDomain(): Domain =
|
||||||
|
Domain(id = id, bypassLan = bypassLan, metered = metered, dualStack = dualStack)
|
||||||
|
|
||||||
|
fun Domain.toEntity(): Entity =
|
||||||
|
Entity(id = id, bypassLan = bypassLan, metered = metered, dualStack = dualStack)
|
||||||
@@ -10,7 +10,7 @@ fun Entity.toDomain(): Domain =
|
|||||||
isShortcutsEnabled = isShortcutsEnabled,
|
isShortcutsEnabled = isShortcutsEnabled,
|
||||||
isRestoreOnBootEnabled = isRestoreOnBootEnabled,
|
isRestoreOnBootEnabled = isRestoreOnBootEnabled,
|
||||||
isMultiTunnelEnabled = isMultiTunnelEnabled,
|
isMultiTunnelEnabled = isMultiTunnelEnabled,
|
||||||
isTunnelGlobalsEnabled = isTunnelGlobalsEnabled,
|
isGlobalSplitTunnelEnabled = isGlobalSplitTunnelEnabled,
|
||||||
appMode = appMode,
|
appMode = appMode,
|
||||||
theme = Theme.valueOf(theme.uppercase()),
|
theme = Theme.valueOf(theme.uppercase()),
|
||||||
locale = locale,
|
locale = locale,
|
||||||
@@ -18,8 +18,7 @@ fun Entity.toDomain(): Domain =
|
|||||||
isRemoteControlEnabled = isRemoteControlEnabled,
|
isRemoteControlEnabled = isRemoteControlEnabled,
|
||||||
isPinLockEnabled = isPinLockEnabled,
|
isPinLockEnabled = isPinLockEnabled,
|
||||||
isAlwaysOnVpnEnabled = isAlwaysOnVpnEnabled,
|
isAlwaysOnVpnEnabled = isAlwaysOnVpnEnabled,
|
||||||
isLanOnKillSwitchEnabled = isLanOnKillSwitchEnabled,
|
alreadyDonated = alreadyDonated,
|
||||||
customSplitPackages = customSplitPackages,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
fun Domain.toEntity(): Entity =
|
fun Domain.toEntity(): Entity =
|
||||||
@@ -28,7 +27,7 @@ fun Domain.toEntity(): Entity =
|
|||||||
isShortcutsEnabled = isShortcutsEnabled,
|
isShortcutsEnabled = isShortcutsEnabled,
|
||||||
isRestoreOnBootEnabled = isRestoreOnBootEnabled,
|
isRestoreOnBootEnabled = isRestoreOnBootEnabled,
|
||||||
isMultiTunnelEnabled = isMultiTunnelEnabled,
|
isMultiTunnelEnabled = isMultiTunnelEnabled,
|
||||||
isTunnelGlobalsEnabled = isTunnelGlobalsEnabled,
|
isGlobalSplitTunnelEnabled = isGlobalSplitTunnelEnabled,
|
||||||
appMode = appMode,
|
appMode = appMode,
|
||||||
theme = theme.name,
|
theme = theme.name,
|
||||||
locale = locale,
|
locale = locale,
|
||||||
@@ -36,6 +35,5 @@ fun Domain.toEntity(): Entity =
|
|||||||
isRemoteControlEnabled = isRemoteControlEnabled,
|
isRemoteControlEnabled = isRemoteControlEnabled,
|
||||||
isPinLockEnabled = isPinLockEnabled,
|
isPinLockEnabled = isPinLockEnabled,
|
||||||
isAlwaysOnVpnEnabled = isAlwaysOnVpnEnabled,
|
isAlwaysOnVpnEnabled = isAlwaysOnVpnEnabled,
|
||||||
isLanOnKillSwitchEnabled = isLanOnKillSwitchEnabled,
|
alreadyDonated = alreadyDonated,
|
||||||
customSplitPackages = customSplitPackages,
|
|
||||||
)
|
)
|
||||||
|
|||||||
+2
@@ -19,6 +19,7 @@ fun Entity.toDomain(): Domain =
|
|||||||
isIpv4Preferred = isIpv4Preferred,
|
isIpv4Preferred = isIpv4Preferred,
|
||||||
position = position,
|
position = position,
|
||||||
autoTunnelApps = autoTunnelApps,
|
autoTunnelApps = autoTunnelApps,
|
||||||
|
isMetered = isMetered,
|
||||||
)
|
)
|
||||||
|
|
||||||
fun Domain.toEntity(): Entity =
|
fun Domain.toEntity(): Entity =
|
||||||
@@ -37,4 +38,5 @@ fun Domain.toEntity(): Entity =
|
|||||||
isIpv4Preferred = isIpv4Preferred,
|
isIpv4Preferred = isIpv4Preferred,
|
||||||
position = position,
|
position = position,
|
||||||
autoTunnelApps = autoTunnelApps,
|
autoTunnelApps = autoTunnelApps,
|
||||||
|
isMetered = isMetered,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -316,3 +316,98 @@ val MIGRATION_23_24 =
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val MIGRATION_25_26 =
|
||||||
|
object : Migration(25, 26) {
|
||||||
|
override fun migrate(db: SupportSQLiteDatabase) {
|
||||||
|
db.execSQL(
|
||||||
|
"""
|
||||||
|
CREATE TABLE IF NOT EXISTS `lockdown_settings` (
|
||||||
|
`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
|
||||||
|
`bypass_lan` INTEGER NOT NULL DEFAULT 0,
|
||||||
|
`metered` INTEGER NOT NULL DEFAULT 0,
|
||||||
|
`dual_stack` INTEGER NOT NULL DEFAULT 0
|
||||||
|
)
|
||||||
|
"""
|
||||||
|
.trimIndent()
|
||||||
|
)
|
||||||
|
|
||||||
|
val cursor =
|
||||||
|
db.query("SELECT `is_lan_on_kill_switch_enabled` FROM `general_settings` LIMIT 1")
|
||||||
|
var bypassLan = 0
|
||||||
|
if (cursor.moveToFirst()) {
|
||||||
|
bypassLan = if (cursor.getInt(0) != 0) 1 else 0
|
||||||
|
}
|
||||||
|
cursor.close()
|
||||||
|
|
||||||
|
db.execSQL(
|
||||||
|
"""
|
||||||
|
INSERT INTO `lockdown_settings` (`bypass_lan`, `metered`, `dual_stack`)
|
||||||
|
VALUES (?, 0, 0)
|
||||||
|
"""
|
||||||
|
.trimIndent(),
|
||||||
|
arrayOf(bypassLan),
|
||||||
|
)
|
||||||
|
|
||||||
|
db.execSQL(
|
||||||
|
"""
|
||||||
|
CREATE TABLE IF NOT EXISTS `general_settings_new` (
|
||||||
|
`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
|
||||||
|
`is_shortcuts_enabled` INTEGER NOT NULL DEFAULT 0,
|
||||||
|
`is_restore_on_boot_enabled` INTEGER NOT NULL DEFAULT 0,
|
||||||
|
`is_multi_tunnel_enabled` INTEGER NOT NULL DEFAULT 0,
|
||||||
|
`is_tunnel_globals_enabled` INTEGER NOT NULL DEFAULT 0,
|
||||||
|
`app_mode` INTEGER NOT NULL DEFAULT 0,
|
||||||
|
`theme` TEXT NOT NULL DEFAULT 'AUTOMATIC',
|
||||||
|
`locale` TEXT,
|
||||||
|
`remote_key` TEXT,
|
||||||
|
`is_remote_control_enabled` INTEGER NOT NULL DEFAULT 0,
|
||||||
|
`is_pin_lock_enabled` INTEGER NOT NULL DEFAULT 0,
|
||||||
|
`is_always_on_vpn_enabled` INTEGER NOT NULL DEFAULT 0,
|
||||||
|
`custom_split_packages` TEXT NOT NULL DEFAULT '{}'
|
||||||
|
)
|
||||||
|
"""
|
||||||
|
.trimIndent()
|
||||||
|
)
|
||||||
|
|
||||||
|
db.execSQL(
|
||||||
|
"""
|
||||||
|
INSERT INTO `general_settings_new` (
|
||||||
|
`id`,
|
||||||
|
`is_shortcuts_enabled`,
|
||||||
|
`is_restore_on_boot_enabled`,
|
||||||
|
`is_multi_tunnel_enabled`,
|
||||||
|
`is_tunnel_globals_enabled`,
|
||||||
|
`app_mode`,
|
||||||
|
`theme`,
|
||||||
|
`locale`,
|
||||||
|
`remote_key`,
|
||||||
|
`is_remote_control_enabled`,
|
||||||
|
`is_pin_lock_enabled`,
|
||||||
|
`is_always_on_vpn_enabled`,
|
||||||
|
`custom_split_packages`
|
||||||
|
)
|
||||||
|
SELECT
|
||||||
|
`id`,
|
||||||
|
`is_shortcuts_enabled`,
|
||||||
|
`is_restore_on_boot_enabled`,
|
||||||
|
`is_multi_tunnel_enabled`,
|
||||||
|
`is_tunnel_globals_enabled`,
|
||||||
|
`app_mode`,
|
||||||
|
`theme`,
|
||||||
|
`locale`,
|
||||||
|
`remote_key`,
|
||||||
|
`is_remote_control_enabled`,
|
||||||
|
`is_pin_lock_enabled`,
|
||||||
|
`is_always_on_vpn_enabled`,
|
||||||
|
`custom_split_packages`
|
||||||
|
FROM `general_settings`
|
||||||
|
"""
|
||||||
|
.trimIndent()
|
||||||
|
)
|
||||||
|
|
||||||
|
db.execSQL("DROP TABLE `general_settings`")
|
||||||
|
|
||||||
|
db.execSQL("ALTER TABLE `general_settings_new` RENAME TO `general_settings`")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -20,10 +20,7 @@ enum class DnsProtocol(val value: Int) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
data class DnsSettings(
|
data class DnsSettings(val protocol: DnsProtocol = DnsProtocol.SYSTEM, val endpoint: String? = null)
|
||||||
val protocol: DnsProtocol = DnsProtocol.SYSTEM,
|
|
||||||
val endpoint: String? = null,
|
|
||||||
)
|
|
||||||
|
|
||||||
enum class DnsProvider(private val systemAddress: String, private val dohAddress: String) {
|
enum class DnsProvider(private val systemAddress: String, private val dohAddress: String) {
|
||||||
CLOUDFLARE("1.1.1.1", "https://1.1.1.1/dns-query"),
|
CLOUDFLARE("1.1.1.1", "https://1.1.1.1/dns-query"),
|
||||||
|
|||||||
+8
@@ -37,6 +37,14 @@ class DataStoreAppStateRepository(
|
|||||||
dataStoreManager.saveToDataStore(DataStoreManager.batteryDisableShown, shown)
|
dataStoreManager.saveToDataStore(DataStoreManager.batteryDisableShown, shown)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override suspend fun setShouldShowDonationSnackbar(show: Boolean) {
|
||||||
|
dataStoreManager.saveToDataStore(DataStoreManager.shouldShowDonationSnackbar, show)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun shouldShowDonationSnackbar(): Boolean {
|
||||||
|
return dataStoreManager.getFromStore(DataStoreManager.shouldShowDonationSnackbar) ?: false
|
||||||
|
}
|
||||||
|
|
||||||
override val flow: Flow<Domain> =
|
override val flow: Flow<Domain> =
|
||||||
dataStoreManager.preferencesFlow
|
dataStoreManager.preferencesFlow
|
||||||
.map { prefs ->
|
.map { prefs ->
|
||||||
|
|||||||
+25
-12
@@ -38,27 +38,40 @@ class GitHubUpdateRepository(
|
|||||||
gitHubApi.getLatestRelease(githubOwner, githubRepo).onFailure(Timber::e)
|
gitHubApi.getLatestRelease(githubOwner, githubRepo).onFailure(Timber::e)
|
||||||
}
|
}
|
||||||
release.map { release ->
|
release.map { release ->
|
||||||
val standaloneApkAsset =
|
val universalApkAsset =
|
||||||
release.assets.find { asset ->
|
release.assets.find { asset ->
|
||||||
asset.name.startsWith("wgtunnel-${Constants.STANDALONE_FLAVOR}-v") &&
|
val prefix = "wgtunnel-${Constants.STANDALONE_FLAVOR}-v"
|
||||||
asset.name.endsWith(".apk")
|
val apkSuffix = ".apk"
|
||||||
|
asset.name.startsWith(prefix) &&
|
||||||
|
asset.name.endsWith(apkSuffix) &&
|
||||||
|
!asset.name.endsWith("-arm64$apkSuffix") &&
|
||||||
|
!asset.name.endsWith("-armv7$apkSuffix")
|
||||||
}
|
}
|
||||||
val newVersion =
|
val newVersion =
|
||||||
standaloneApkAsset
|
universalApkAsset
|
||||||
?.name
|
?.name
|
||||||
?.removePrefix("wgtunnel-${Constants.STANDALONE_FLAVOR}-v")
|
?.removePrefix("wgtunnel-${Constants.STANDALONE_FLAVOR}-v")
|
||||||
?.removeSuffix(".apk") ?: return@map null
|
?.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)
|
if (isNightly) {
|
||||||
return@map GitHubReleaseMapper.toAppUpdate(release, newVersion)
|
if (newVersion != currentVersion) {
|
||||||
if (NumberUtils.compareVersions(newVersion, currentVersion) > 0) {
|
GitHubReleaseMapper.toAppUpdate(
|
||||||
GitHubReleaseMapper.toAppUpdate(
|
release.copy(assets = listOf(universalApkAsset)),
|
||||||
release.copy(assets = listOf(standaloneApkAsset)),
|
newVersion,
|
||||||
newVersion,
|
)
|
||||||
)
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
null
|
if (NumberUtils.compareVersions(newVersion, currentVersion) > 0) {
|
||||||
|
GitHubReleaseMapper.toAppUpdate(
|
||||||
|
release.copy(assets = listOf(universalApkAsset)),
|
||||||
|
newVersion,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-32
@@ -1,20 +1,15 @@
|
|||||||
package com.zaneschepke.wireguardautotunnel.data.repository
|
package com.zaneschepke.wireguardautotunnel.data.repository
|
||||||
|
|
||||||
import android.content.BroadcastReceiver
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
|
||||||
import android.content.IntentFilter
|
|
||||||
import android.content.pm.PackageManager
|
import android.content.pm.PackageManager
|
||||||
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.model.InstalledPackage
|
import com.zaneschepke.wireguardautotunnel.domain.model.InstalledPackage
|
||||||
import com.zaneschepke.wireguardautotunnel.domain.repository.InstalledPackageRepository
|
import com.zaneschepke.wireguardautotunnel.domain.repository.InstalledPackageRepository
|
||||||
import com.zaneschepke.wireguardautotunnel.util.extensions.getAllInternetCapablePackages
|
|
||||||
import com.zaneschepke.wireguardautotunnel.util.extensions.getFriendlyAppName
|
import com.zaneschepke.wireguardautotunnel.util.extensions.getFriendlyAppName
|
||||||
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.launch
|
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
|
||||||
@@ -27,32 +22,6 @@ class InstalledAndroidPackageRepository(
|
|||||||
|
|
||||||
private var cachedPackages: List<InstalledPackage>? = null
|
private var cachedPackages: List<InstalledPackage>? = null
|
||||||
|
|
||||||
init {
|
|
||||||
val receiver =
|
|
||||||
object : BroadcastReceiver() {
|
|
||||||
override fun onReceive(context: Context, intent: Intent) {
|
|
||||||
when (intent.action) {
|
|
||||||
Intent.ACTION_PACKAGE_ADDED,
|
|
||||||
Intent.ACTION_PACKAGE_REMOVED,
|
|
||||||
Intent.ACTION_PACKAGE_CHANGED -> {
|
|
||||||
// don't update if we have nothing cached
|
|
||||||
if (cachedPackages == null) return
|
|
||||||
Timber.d("Updating installed packages cache")
|
|
||||||
applicationScope.launch { refreshInstalledPackages() }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
val filter =
|
|
||||||
IntentFilter().apply {
|
|
||||||
addAction(Intent.ACTION_PACKAGE_ADDED)
|
|
||||||
addAction(Intent.ACTION_PACKAGE_REMOVED)
|
|
||||||
addAction(Intent.ACTION_PACKAGE_CHANGED)
|
|
||||||
addDataScheme("package")
|
|
||||||
}
|
|
||||||
context.registerReceiver(receiver, filter)
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun getInstalledPackages(): List<InstalledPackage> =
|
override suspend fun getInstalledPackages(): List<InstalledPackage> =
|
||||||
withContext(ioDispatcher) {
|
withContext(ioDispatcher) {
|
||||||
cachedPackages?.let {
|
cachedPackages?.let {
|
||||||
@@ -63,7 +32,7 @@ class InstalledAndroidPackageRepository(
|
|||||||
|
|
||||||
override suspend fun refreshInstalledPackages(): List<InstalledPackage> =
|
override suspend fun refreshInstalledPackages(): List<InstalledPackage> =
|
||||||
withContext(ioDispatcher) {
|
withContext(ioDispatcher) {
|
||||||
val packages = context.getAllInternetCapablePackages()
|
val packages = context.packageManager.getInstalledPackages(0)
|
||||||
|
|
||||||
val installedPackages =
|
val installedPackages =
|
||||||
packages.mapNotNull { packageInfo ->
|
packages.mapNotNull { packageInfo ->
|
||||||
|
|||||||
+34
@@ -0,0 +1,34 @@
|
|||||||
|
package com.zaneschepke.wireguardautotunnel.data.repository
|
||||||
|
|
||||||
|
import com.zaneschepke.wireguardautotunnel.data.dao.LockdownSettingsDao
|
||||||
|
import com.zaneschepke.wireguardautotunnel.data.entity.LockdownSettings as Entity
|
||||||
|
import com.zaneschepke.wireguardautotunnel.data.mapper.toDomain
|
||||||
|
import com.zaneschepke.wireguardautotunnel.data.mapper.toEntity
|
||||||
|
import com.zaneschepke.wireguardautotunnel.di.IoDispatcher
|
||||||
|
import com.zaneschepke.wireguardautotunnel.domain.model.LockdownSettings as Domain
|
||||||
|
import com.zaneschepke.wireguardautotunnel.domain.repository.LockdownSettingsRepository
|
||||||
|
import kotlinx.coroutines.CoroutineDispatcher
|
||||||
|
import kotlinx.coroutines.flow.flowOn
|
||||||
|
import kotlinx.coroutines.flow.map
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
|
||||||
|
class RoomLockdownSettingsRepository(
|
||||||
|
private val lockdownSettingsDao: LockdownSettingsDao,
|
||||||
|
@IoDispatcher private val ioDispatcher: CoroutineDispatcher,
|
||||||
|
) : LockdownSettingsRepository {
|
||||||
|
override suspend fun upsert(lockdownSettings: Domain) {
|
||||||
|
withContext(ioDispatcher) { lockdownSettingsDao.upsert(lockdownSettings.toEntity()) }
|
||||||
|
}
|
||||||
|
|
||||||
|
override val flow =
|
||||||
|
lockdownSettingsDao
|
||||||
|
.getLockdownSettingsFlow()
|
||||||
|
.map { (it ?: Entity()).toDomain() }
|
||||||
|
.flowOn(ioDispatcher)
|
||||||
|
|
||||||
|
override suspend fun getLockdownSettings(): Domain {
|
||||||
|
return withContext(ioDispatcher) {
|
||||||
|
(lockdownSettingsDao.getLockdownSettings() ?: Entity()).toDomain()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
-1
@@ -23,7 +23,6 @@ class RoomProxySettingsRepository(
|
|||||||
override val flow =
|
override val flow =
|
||||||
proxySettingsDao
|
proxySettingsDao
|
||||||
.getProxySettingsFlow()
|
.getProxySettingsFlow()
|
||||||
.flowOn(ioDispatcher)
|
|
||||||
.map { (it ?: Entity()).toDomain() }
|
.map { (it ?: Entity()).toDomain() }
|
||||||
.flowOn(ioDispatcher)
|
.flowOn(ioDispatcher)
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package com.zaneschepke.wireguardautotunnel.di
|
package com.zaneschepke.wireguardautotunnel.di
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import android.os.PowerManager
|
||||||
import com.zaneschepke.logcatter.LogReader
|
import com.zaneschepke.logcatter.LogReader
|
||||||
import com.zaneschepke.logcatter.LogcatReader
|
import com.zaneschepke.logcatter.LogcatReader
|
||||||
import com.zaneschepke.wireguardautotunnel.core.notification.NotificationManager
|
import com.zaneschepke.wireguardautotunnel.core.notification.NotificationManager
|
||||||
@@ -66,4 +67,9 @@ class AppModule {
|
|||||||
): NotificationMonitor {
|
): NotificationMonitor {
|
||||||
return NotificationMonitor(tunnelManager, notificationManager)
|
return NotificationMonitor(tunnelManager, notificationManager)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
fun providePowerManager(@ApplicationContext context: Context): PowerManager {
|
||||||
|
return context.getSystemService(Context.POWER_SERVICE) as PowerManager
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import com.zaneschepke.wireguardautotunnel.data.DataStoreManager
|
|||||||
import com.zaneschepke.wireguardautotunnel.data.DatabaseCallback
|
import com.zaneschepke.wireguardautotunnel.data.DatabaseCallback
|
||||||
import com.zaneschepke.wireguardautotunnel.data.dao.*
|
import com.zaneschepke.wireguardautotunnel.data.dao.*
|
||||||
import com.zaneschepke.wireguardautotunnel.data.migrations.MIGRATION_23_24
|
import com.zaneschepke.wireguardautotunnel.data.migrations.MIGRATION_23_24
|
||||||
|
import com.zaneschepke.wireguardautotunnel.data.migrations.MIGRATION_25_26
|
||||||
import com.zaneschepke.wireguardautotunnel.data.network.GitHubApi
|
import com.zaneschepke.wireguardautotunnel.data.network.GitHubApi
|
||||||
import com.zaneschepke.wireguardautotunnel.data.network.KtorClient
|
import com.zaneschepke.wireguardautotunnel.data.network.KtorClient
|
||||||
import com.zaneschepke.wireguardautotunnel.data.network.KtorGitHubApi
|
import com.zaneschepke.wireguardautotunnel.data.network.KtorGitHubApi
|
||||||
@@ -56,6 +57,7 @@ class RepositoryModule {
|
|||||||
context.getString(R.string.db_name),
|
context.getString(R.string.db_name),
|
||||||
)
|
)
|
||||||
.addMigrations(MIGRATION_23_24(dataStoreManager.dataStore))
|
.addMigrations(MIGRATION_23_24(dataStoreManager.dataStore))
|
||||||
|
.addMigrations(MIGRATION_25_26)
|
||||||
.fallbackToDestructiveMigration(true)
|
.fallbackToDestructiveMigration(true)
|
||||||
.addCallback(callback)
|
.addCallback(callback)
|
||||||
.build()
|
.build()
|
||||||
@@ -67,6 +69,12 @@ class RepositoryModule {
|
|||||||
return appDatabase.generalSettingsDao()
|
return appDatabase.generalSettingsDao()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Singleton
|
||||||
|
@Provides
|
||||||
|
fun provideLockdownDoa(appDatabase: AppDatabase): LockdownSettingsDao {
|
||||||
|
return appDatabase.lockdownSettingsDao()
|
||||||
|
}
|
||||||
|
|
||||||
@Singleton
|
@Singleton
|
||||||
@Provides
|
@Provides
|
||||||
fun provideDnsSettingsDao(appDatabase: AppDatabase): DnsSettingsDao {
|
fun provideDnsSettingsDao(appDatabase: AppDatabase): DnsSettingsDao {
|
||||||
@@ -106,6 +114,15 @@ class RepositoryModule {
|
|||||||
return RoomTunnelRepository(tunnelConfigDao, ioDispatcher)
|
return RoomTunnelRepository(tunnelConfigDao, ioDispatcher)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Singleton
|
||||||
|
@Provides
|
||||||
|
fun provideLockdownSettingsRepository(
|
||||||
|
lockdownSettingsDao: LockdownSettingsDao,
|
||||||
|
@IoDispatcher ioDispatcher: CoroutineDispatcher,
|
||||||
|
): LockdownSettingsRepository {
|
||||||
|
return RoomLockdownSettingsRepository(lockdownSettingsDao, ioDispatcher)
|
||||||
|
}
|
||||||
|
|
||||||
@Singleton
|
@Singleton
|
||||||
@Provides
|
@Provides
|
||||||
fun provideGeneralSettingsRepository(
|
fun provideGeneralSettingsRepository(
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package com.zaneschepke.wireguardautotunnel.di
|
package com.zaneschepke.wireguardautotunnel.di
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import android.os.PowerManager
|
||||||
import com.wireguard.android.backend.WgQuickBackend
|
import com.wireguard.android.backend.WgQuickBackend
|
||||||
import com.wireguard.android.util.RootShell
|
import com.wireguard.android.util.RootShell
|
||||||
import com.wireguard.android.util.ToolsInstaller
|
import com.wireguard.android.util.ToolsInstaller
|
||||||
@@ -85,8 +86,9 @@ class TunnelModule {
|
|||||||
@ApplicationScope applicationScope: CoroutineScope,
|
@ApplicationScope applicationScope: CoroutineScope,
|
||||||
@IoDispatcher ioDispatcher: CoroutineDispatcher,
|
@IoDispatcher ioDispatcher: CoroutineDispatcher,
|
||||||
backend: com.wireguard.android.backend.Backend,
|
backend: com.wireguard.android.backend.Backend,
|
||||||
|
runConfigHelper: RunConfigHelper,
|
||||||
): TunnelProvider {
|
): TunnelProvider {
|
||||||
return KernelTunnel(applicationScope, ioDispatcher, backend)
|
return KernelTunnel(applicationScope, ioDispatcher, runConfigHelper, backend)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@@ -94,18 +96,11 @@ class TunnelModule {
|
|||||||
@Userspace
|
@Userspace
|
||||||
fun provideUserspaceProvider(
|
fun provideUserspaceProvider(
|
||||||
@ApplicationScope applicationScope: CoroutineScope,
|
@ApplicationScope applicationScope: CoroutineScope,
|
||||||
proxySettingsRepository: ProxySettingsRepository,
|
runConfigHelper: RunConfigHelper,
|
||||||
dnsSettingsRepository: DnsSettingsRepository,
|
|
||||||
@Userspace backend: Backend,
|
@Userspace backend: Backend,
|
||||||
@IoDispatcher ioDispatcher: CoroutineDispatcher,
|
@IoDispatcher ioDispatcher: CoroutineDispatcher,
|
||||||
): TunnelProvider {
|
): TunnelProvider {
|
||||||
return UserspaceTunnel(
|
return UserspaceTunnel(applicationScope, ioDispatcher, backend, runConfigHelper)
|
||||||
applicationScope,
|
|
||||||
ioDispatcher,
|
|
||||||
proxySettingsRepository,
|
|
||||||
dnsSettingsRepository,
|
|
||||||
backend,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@@ -113,18 +108,11 @@ class TunnelModule {
|
|||||||
@ProxyUserspace
|
@ProxyUserspace
|
||||||
fun provideProxyUserspaceProvider(
|
fun provideProxyUserspaceProvider(
|
||||||
@ApplicationScope applicationScope: CoroutineScope,
|
@ApplicationScope applicationScope: CoroutineScope,
|
||||||
dnsSettingsRepository: DnsSettingsRepository,
|
runConfigHelper: RunConfigHelper,
|
||||||
proxySettingsRepository: ProxySettingsRepository,
|
|
||||||
@ProxyUserspace backend: Backend,
|
@ProxyUserspace backend: Backend,
|
||||||
@IoDispatcher ioDispatcher: CoroutineDispatcher,
|
@IoDispatcher ioDispatcher: CoroutineDispatcher,
|
||||||
): TunnelProvider {
|
): TunnelProvider {
|
||||||
return UserspaceTunnel(
|
return UserspaceTunnel(applicationScope, ioDispatcher, backend, runConfigHelper)
|
||||||
applicationScope,
|
|
||||||
ioDispatcher,
|
|
||||||
proxySettingsRepository,
|
|
||||||
dnsSettingsRepository,
|
|
||||||
backend,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@@ -135,6 +123,7 @@ class TunnelModule {
|
|||||||
@ProxyUserspace proxyTunnel: TunnelProvider,
|
@ProxyUserspace proxyTunnel: TunnelProvider,
|
||||||
serviceManager: ServiceManager,
|
serviceManager: ServiceManager,
|
||||||
tunnelRepository: TunnelRepository,
|
tunnelRepository: TunnelRepository,
|
||||||
|
lockdownSettingsRepository: LockdownSettingsRepository,
|
||||||
settingsRepository: GeneralSettingRepository,
|
settingsRepository: GeneralSettingRepository,
|
||||||
autoTunnelSettingsRepository: AutoTunnelSettingsRepository,
|
autoTunnelSettingsRepository: AutoTunnelSettingsRepository,
|
||||||
tunnelMonitor: TunnelMonitor,
|
tunnelMonitor: TunnelMonitor,
|
||||||
@@ -148,6 +137,7 @@ class TunnelModule {
|
|||||||
serviceManager,
|
serviceManager,
|
||||||
settingsRepository,
|
settingsRepository,
|
||||||
autoTunnelSettingsRepository,
|
autoTunnelSettingsRepository,
|
||||||
|
lockdownSettingsRepository,
|
||||||
tunnelRepository,
|
tunnelRepository,
|
||||||
tunnelMonitor,
|
tunnelMonitor,
|
||||||
applicationScope,
|
applicationScope,
|
||||||
@@ -155,6 +145,62 @@ class TunnelModule {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
fun provideTunnelConfigHelper(
|
||||||
|
settingsRepository: GeneralSettingRepository,
|
||||||
|
proxySettingsRepository: ProxySettingsRepository,
|
||||||
|
dnsSettingsRepository: DnsSettingsRepository,
|
||||||
|
tunnelRepository: TunnelRepository,
|
||||||
|
): RunConfigHelper {
|
||||||
|
return RunConfigHelper(
|
||||||
|
settingsRepository,
|
||||||
|
proxySettingsRepository,
|
||||||
|
dnsSettingsRepository,
|
||||||
|
tunnelRepository,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Singleton
|
||||||
|
@Provides
|
||||||
|
fun provideServiceManager(
|
||||||
|
@ApplicationContext context: Context,
|
||||||
|
@IoDispatcher ioDispatcher: CoroutineDispatcher,
|
||||||
|
@MainDispatcher mainCoroutineDispatcher: CoroutineDispatcher,
|
||||||
|
@ApplicationScope applicationScope: CoroutineScope,
|
||||||
|
autoTunnelSettingsRepository: AutoTunnelSettingsRepository,
|
||||||
|
): ServiceManager {
|
||||||
|
return ServiceManager(
|
||||||
|
context,
|
||||||
|
ioDispatcher,
|
||||||
|
applicationScope,
|
||||||
|
mainCoroutineDispatcher,
|
||||||
|
autoTunnelSettingsRepository,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Singleton
|
||||||
|
@Provides
|
||||||
|
fun provideTunnelMonitor(
|
||||||
|
powerManager: PowerManager,
|
||||||
|
networkMonitor: NetworkMonitor,
|
||||||
|
networkUtils: NetworkUtils,
|
||||||
|
logReader: LogReader,
|
||||||
|
tunnelsRepository: TunnelRepository,
|
||||||
|
settingsRepository: GeneralSettingRepository,
|
||||||
|
monitoringSettingsRepository: MonitoringSettingsRepository,
|
||||||
|
): TunnelMonitor {
|
||||||
|
return TunnelMonitor(
|
||||||
|
settingsRepository,
|
||||||
|
tunnelsRepository,
|
||||||
|
monitoringSettingsRepository,
|
||||||
|
networkMonitor,
|
||||||
|
networkUtils,
|
||||||
|
logReader,
|
||||||
|
powerManager,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@Singleton
|
@Singleton
|
||||||
fun provideNetworkMonitor(
|
fun provideNetworkMonitor(
|
||||||
@@ -178,42 +224,4 @@ class TunnelModule {
|
|||||||
applicationScope,
|
applicationScope,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Singleton
|
|
||||||
@Provides
|
|
||||||
fun provideServiceManager(
|
|
||||||
@ApplicationContext context: Context,
|
|
||||||
@IoDispatcher ioDispatcher: CoroutineDispatcher,
|
|
||||||
@MainDispatcher mainCoroutineDispatcher: CoroutineDispatcher,
|
|
||||||
@ApplicationScope applicationScope: CoroutineScope,
|
|
||||||
autoTunnelSettingsRepository: AutoTunnelSettingsRepository,
|
|
||||||
): ServiceManager {
|
|
||||||
return ServiceManager(
|
|
||||||
context,
|
|
||||||
ioDispatcher,
|
|
||||||
applicationScope,
|
|
||||||
mainCoroutineDispatcher,
|
|
||||||
autoTunnelSettingsRepository,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Singleton
|
|
||||||
@Provides
|
|
||||||
fun provideTunnelMonitor(
|
|
||||||
networkMonitor: NetworkMonitor,
|
|
||||||
networkUtils: NetworkUtils,
|
|
||||||
logReader: LogReader,
|
|
||||||
tunnelsRepository: TunnelRepository,
|
|
||||||
settingsRepository: GeneralSettingRepository,
|
|
||||||
monitoringSettingsRepository: MonitoringSettingsRepository,
|
|
||||||
): TunnelMonitor {
|
|
||||||
return TunnelMonitor(
|
|
||||||
settingsRepository,
|
|
||||||
tunnelsRepository,
|
|
||||||
monitoringSettingsRepository,
|
|
||||||
networkMonitor,
|
|
||||||
networkUtils,
|
|
||||||
logReader,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,5 +3,9 @@ package com.zaneschepke.wireguardautotunnel.domain.enums
|
|||||||
sealed class BackendMode {
|
sealed class BackendMode {
|
||||||
data object Inactive : BackendMode()
|
data object Inactive : BackendMode()
|
||||||
|
|
||||||
data class KillSwitch(val allowedIps: Set<String>) : BackendMode()
|
data class KillSwitch(
|
||||||
|
val allowedIps: Set<String>,
|
||||||
|
val isMetered: Boolean,
|
||||||
|
val dualStack: Boolean,
|
||||||
|
) : BackendMode()
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-3
@@ -1,11 +1,12 @@
|
|||||||
package com.zaneschepke.wireguardautotunnel.domain.events
|
package com.zaneschepke.wireguardautotunnel.domain.events
|
||||||
|
|
||||||
|
import androidx.annotation.Keep
|
||||||
import com.zaneschepke.wireguardautotunnel.domain.model.TunnelConfig
|
import com.zaneschepke.wireguardautotunnel.domain.model.TunnelConfig
|
||||||
|
|
||||||
sealed class AutoTunnelEvent {
|
sealed class AutoTunnelEvent {
|
||||||
data class Start(val tunnelConfig: TunnelConfig? = null) : AutoTunnelEvent()
|
@Keep data class Start(val tunnelConfig: TunnelConfig? = null) : AutoTunnelEvent()
|
||||||
|
|
||||||
data object Stop : AutoTunnelEvent()
|
@Keep data object Stop : AutoTunnelEvent()
|
||||||
|
|
||||||
data object DoNothing : AutoTunnelEvent()
|
@Keep data object DoNothing : AutoTunnelEvent()
|
||||||
}
|
}
|
||||||
|
|||||||
+32
-31
@@ -4,38 +4,39 @@ import com.zaneschepke.wireguardautotunnel.R
|
|||||||
import com.zaneschepke.wireguardautotunnel.util.StringValue
|
import com.zaneschepke.wireguardautotunnel.util.StringValue
|
||||||
|
|
||||||
sealed class BackendCoreException : Exception() {
|
sealed class BackendCoreException : Exception() {
|
||||||
data object DNS : BackendCoreException()
|
abstract val stringRes: Int
|
||||||
|
|
||||||
data object Unauthorized : BackendCoreException()
|
|
||||||
|
|
||||||
data object Config : BackendCoreException()
|
|
||||||
|
|
||||||
data object KernelModuleName : BackendCoreException()
|
|
||||||
|
|
||||||
data object NotAuthorized : BackendCoreException()
|
|
||||||
|
|
||||||
data object ServiceNotRunning : BackendCoreException()
|
|
||||||
|
|
||||||
data object Unknown : BackendCoreException()
|
|
||||||
|
|
||||||
data object TunnelNameTooLong : BackendCoreException()
|
|
||||||
|
|
||||||
data object UapiUpdateFailed : BackendCoreException()
|
|
||||||
|
|
||||||
fun toStringRes() =
|
|
||||||
when (this) {
|
|
||||||
Config -> R.string.config_error
|
|
||||||
DNS -> R.string.dns_resolve_error
|
|
||||||
KernelModuleName -> R.string.kernel_name_error
|
|
||||||
NotAuthorized,
|
|
||||||
Unauthorized -> R.string.auth_error
|
|
||||||
ServiceNotRunning -> R.string.service_running_error
|
|
||||||
Unknown -> R.string.unknown_error
|
|
||||||
TunnelNameTooLong -> R.string.error_tunnel_name
|
|
||||||
UapiUpdateFailed -> R.string.active_tunnel_update_failed
|
|
||||||
}
|
|
||||||
|
|
||||||
fun toStringValue(): StringValue {
|
fun toStringValue(): StringValue {
|
||||||
return StringValue.StringResource(toStringRes())
|
return StringValue.StringResource(stringRes)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class DnsFailure : BackendCoreException() {
|
||||||
|
override val stringRes = R.string.dns_resolve_error
|
||||||
|
}
|
||||||
|
|
||||||
|
class VpnUnauthorized : BackendCoreException() {
|
||||||
|
override val stringRes = R.string.auth_error
|
||||||
|
}
|
||||||
|
|
||||||
|
class InvalidConfig : BackendCoreException() {
|
||||||
|
override val stringRes = R.string.config_error
|
||||||
|
}
|
||||||
|
|
||||||
|
class KernelTunnelName(override val stringRes: Int) : BackendCoreException() {}
|
||||||
|
|
||||||
|
class NotAuthorized : BackendCoreException() {
|
||||||
|
override val stringRes = R.string.auth_error
|
||||||
|
}
|
||||||
|
|
||||||
|
class ServiceNotRunning : BackendCoreException() {
|
||||||
|
override val stringRes = R.string.service_running_error
|
||||||
|
}
|
||||||
|
|
||||||
|
class UnknownError : BackendCoreException() {
|
||||||
|
override val stringRes = R.string.unknown_error
|
||||||
|
}
|
||||||
|
|
||||||
|
class UapiUpdateFailed : BackendCoreException() {
|
||||||
|
override val stringRes = R.string.active_tunnel_update_failed
|
||||||
|
}
|
||||||
|
|||||||
@@ -3,4 +3,5 @@ package com.zaneschepke.wireguardautotunnel.domain.model
|
|||||||
data class AppState(
|
data class AppState(
|
||||||
val isLocationDisclosureShown: Boolean = false,
|
val isLocationDisclosureShown: Boolean = false,
|
||||||
val isBatteryOptimizationDisableShown: Boolean = false,
|
val isBatteryOptimizationDisableShown: Boolean = false,
|
||||||
|
val shouldShowDonationSnackbar: Boolean = false,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -6,4 +6,5 @@ data class DnsSettings(
|
|||||||
val id: Int = 0,
|
val id: Int = 0,
|
||||||
val dnsProtocol: DnsProtocol = DnsProtocol.fromValue(0),
|
val dnsProtocol: DnsProtocol = DnsProtocol.fromValue(0),
|
||||||
val dnsEndpoint: String? = null,
|
val dnsEndpoint: String? = null,
|
||||||
|
val isGlobalTunnelDnsEnabled: Boolean = false,
|
||||||
)
|
)
|
||||||
|
|||||||
+3
-3
@@ -8,7 +8,7 @@ data class GeneralSettings(
|
|||||||
val isShortcutsEnabled: Boolean = false,
|
val isShortcutsEnabled: Boolean = false,
|
||||||
val isRestoreOnBootEnabled: Boolean = false,
|
val isRestoreOnBootEnabled: Boolean = false,
|
||||||
val isMultiTunnelEnabled: Boolean = false,
|
val isMultiTunnelEnabled: Boolean = false,
|
||||||
val isTunnelGlobalsEnabled: Boolean = false,
|
val isGlobalSplitTunnelEnabled: Boolean = false,
|
||||||
val appMode: AppMode = AppMode.fromValue(0),
|
val appMode: AppMode = AppMode.fromValue(0),
|
||||||
val theme: Theme = Theme.AUTOMATIC,
|
val theme: Theme = Theme.AUTOMATIC,
|
||||||
val locale: String? = null,
|
val locale: String? = null,
|
||||||
@@ -16,6 +16,6 @@ data class GeneralSettings(
|
|||||||
val isRemoteControlEnabled: Boolean = false,
|
val isRemoteControlEnabled: Boolean = false,
|
||||||
val isPinLockEnabled: Boolean = false,
|
val isPinLockEnabled: Boolean = false,
|
||||||
val isAlwaysOnVpnEnabled: Boolean = false,
|
val isAlwaysOnVpnEnabled: Boolean = false,
|
||||||
val isLanOnKillSwitchEnabled: Boolean = false,
|
val isKillSwitchMetered: Boolean = true,
|
||||||
val customSplitPackages: Map<String, String> = emptyMap(),
|
val alreadyDonated: Boolean = false,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
package com.zaneschepke.wireguardautotunnel.domain.model
|
||||||
|
|
||||||
|
data class LockdownSettings(
|
||||||
|
val id: Long = 0L,
|
||||||
|
val bypassLan: Boolean = false,
|
||||||
|
val metered: Boolean = false,
|
||||||
|
val dualStack: Boolean = false,
|
||||||
|
)
|
||||||
+10
-53
@@ -27,8 +27,8 @@ data class TunnelConfig(
|
|||||||
val isIpv4Preferred: Boolean = true,
|
val isIpv4Preferred: Boolean = true,
|
||||||
val position: Int = 0,
|
val position: Int = 0,
|
||||||
val autoTunnelApps: Set<String> = setOf(),
|
val autoTunnelApps: Set<String> = setOf(),
|
||||||
|
val isMetered: Boolean = true,
|
||||||
) {
|
) {
|
||||||
val isNameKernelCompatible: Boolean = (name.length <= 15)
|
|
||||||
|
|
||||||
override fun equals(other: Any?): Boolean {
|
override fun equals(other: Any?): Boolean {
|
||||||
if (this === other) return true
|
if (this === other) return true
|
||||||
@@ -43,7 +43,8 @@ data class TunnelConfig(
|
|||||||
pingTarget == other.pingTarget &&
|
pingTarget == other.pingTarget &&
|
||||||
restartOnPingFailure == other.restartOnPingFailure &&
|
restartOnPingFailure == other.restartOnPingFailure &&
|
||||||
tunnelNetworks == other.tunnelNetworks &&
|
tunnelNetworks == other.tunnelNetworks &&
|
||||||
isIpv4Preferred == other.isIpv4Preferred
|
isIpv4Preferred == other.isIpv4Preferred &&
|
||||||
|
isMetered == other.isMetered
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun hashCode(): Int {
|
override fun hashCode(): Int {
|
||||||
@@ -66,7 +67,11 @@ data class TunnelConfig(
|
|||||||
return configFromWgQuick(wgQuick)
|
return configFromWgQuick(wgQuick)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun copyWithGlobalValues(globalTunnel: TunnelConfig): TunnelConfig {
|
fun copyWithGlobalValues(
|
||||||
|
globalTunnel: TunnelConfig,
|
||||||
|
includeDns: Boolean,
|
||||||
|
includeSpitTunneling: Boolean,
|
||||||
|
): TunnelConfig {
|
||||||
val existingConfig = toAmConfig()
|
val existingConfig = toAmConfig()
|
||||||
val globalConfig = globalTunnel.toAmConfig()
|
val globalConfig = globalTunnel.toAmConfig()
|
||||||
|
|
||||||
@@ -115,62 +120,14 @@ data class TunnelConfig(
|
|||||||
setPreDown(existingConfig.`interface`.preDown)
|
setPreDown(existingConfig.`interface`.preDown)
|
||||||
setPostDown(existingConfig.`interface`.postDown)
|
setPostDown(existingConfig.`interface`.postDown)
|
||||||
|
|
||||||
globalConfig.`interface`.mtu.ifPresent { setMtu(it) }
|
if (includeDns) {
|
||||||
if (globalConfig.`interface`.dnsServers.isNotEmpty()) {
|
|
||||||
setDnsServers(globalConfig.`interface`.dnsServers)
|
setDnsServers(globalConfig.`interface`.dnsServers)
|
||||||
}
|
|
||||||
if (globalConfig.`interface`.dnsSearchDomains.isNotEmpty()) {
|
|
||||||
setDnsSearchDomains(globalConfig.`interface`.dnsSearchDomains)
|
setDnsSearchDomains(globalConfig.`interface`.dnsSearchDomains)
|
||||||
}
|
}
|
||||||
|
if (includeSpitTunneling) {
|
||||||
if (globalConfig.`interface`.excludedApplications.isNotEmpty()) {
|
|
||||||
setExcludedApplications(globalConfig.`interface`.excludedApplications)
|
setExcludedApplications(globalConfig.`interface`.excludedApplications)
|
||||||
}
|
|
||||||
if (!globalConfig.`interface`.includedApplications.isEmpty()) {
|
|
||||||
setIncludedApplications(globalConfig.`interface`.includedApplications)
|
setIncludedApplications(globalConfig.`interface`.includedApplications)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (globalConfig.`interface`.preUp.isNotEmpty()) {
|
|
||||||
setPreUp(globalConfig.`interface`.preUp)
|
|
||||||
}
|
|
||||||
if (globalConfig.`interface`.postUp.isNotEmpty()) {
|
|
||||||
setPostUp(globalConfig.`interface`.postUp)
|
|
||||||
}
|
|
||||||
if (globalConfig.`interface`.preDown.isNotEmpty()) {
|
|
||||||
setPreDown(globalConfig.`interface`.preDown)
|
|
||||||
}
|
|
||||||
if (globalConfig.`interface`.postDown.isNotEmpty()) {
|
|
||||||
setPostDown(globalConfig.`interface`.postDown)
|
|
||||||
}
|
|
||||||
|
|
||||||
globalConfig.`interface`.junkPacketCount.ifPresent { setJunkPacketCount(it) }
|
|
||||||
globalConfig.`interface`.junkPacketMinSize.ifPresent { setJunkPacketMinSize(it) }
|
|
||||||
globalConfig.`interface`.junkPacketMaxSize.ifPresent { setJunkPacketMaxSize(it) }
|
|
||||||
globalConfig.`interface`.initPacketJunkSize.ifPresent { setInitPacketJunkSize(it) }
|
|
||||||
globalConfig.`interface`.responsePacketJunkSize.ifPresent {
|
|
||||||
setResponsePacketJunkSize(it)
|
|
||||||
}
|
|
||||||
globalConfig.`interface`.initPacketMagicHeader.ifPresent {
|
|
||||||
setInitPacketMagicHeader(it)
|
|
||||||
}
|
|
||||||
globalConfig.`interface`.responsePacketMagicHeader.ifPresent {
|
|
||||||
setResponsePacketMagicHeader(it)
|
|
||||||
}
|
|
||||||
globalConfig.`interface`.underloadPacketMagicHeader.ifPresent {
|
|
||||||
setUnderloadPacketMagicHeader(it)
|
|
||||||
}
|
|
||||||
globalConfig.`interface`.transportPacketMagicHeader.ifPresent {
|
|
||||||
setTransportPacketMagicHeader(it)
|
|
||||||
}
|
|
||||||
globalConfig.`interface`.i1.ifPresent { setI1(it) }
|
|
||||||
globalConfig.`interface`.i2.ifPresent { setI2(it) }
|
|
||||||
globalConfig.`interface`.i3.ifPresent { setI3(it) }
|
|
||||||
globalConfig.`interface`.i4.ifPresent { setI4(it) }
|
|
||||||
globalConfig.`interface`.i5.ifPresent { setI5(it) }
|
|
||||||
globalConfig.`interface`.j1.ifPresent { setJ1(it) }
|
|
||||||
globalConfig.`interface`.j2.ifPresent { setJ2(it) }
|
|
||||||
globalConfig.`interface`.j3.ifPresent { setJ3(it) }
|
|
||||||
globalConfig.`interface`.itime.ifPresent { setItime(it) }
|
|
||||||
}
|
}
|
||||||
val newInterface = newInterfaceBuilder.build()
|
val newInterface = newInterfaceBuilder.build()
|
||||||
|
|
||||||
|
|||||||
+4
@@ -12,5 +12,9 @@ interface AppStateRepository {
|
|||||||
|
|
||||||
suspend fun setBatteryOptimizationDisableShown(shown: Boolean)
|
suspend fun setBatteryOptimizationDisableShown(shown: Boolean)
|
||||||
|
|
||||||
|
suspend fun setShouldShowDonationSnackbar(show: Boolean)
|
||||||
|
|
||||||
|
suspend fun shouldShowDonationSnackbar(): Boolean
|
||||||
|
|
||||||
val flow: Flow<AppState>
|
val flow: Flow<AppState>
|
||||||
}
|
}
|
||||||
|
|||||||
+12
@@ -0,0 +1,12 @@
|
|||||||
|
package com.zaneschepke.wireguardautotunnel.domain.repository
|
||||||
|
|
||||||
|
import com.zaneschepke.wireguardautotunnel.domain.model.LockdownSettings
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
|
||||||
|
interface LockdownSettingsRepository {
|
||||||
|
suspend fun upsert(lockdownSettings: LockdownSettings)
|
||||||
|
|
||||||
|
val flow: Flow<LockdownSettings>
|
||||||
|
|
||||||
|
suspend fun getLockdownSettings(): LockdownSettings
|
||||||
|
}
|
||||||
+8
-1
@@ -2,12 +2,19 @@ package com.zaneschepke.wireguardautotunnel.domain.sideeffect
|
|||||||
|
|
||||||
import com.zaneschepke.wireguardautotunnel.data.model.AppMode
|
import com.zaneschepke.wireguardautotunnel.data.model.AppMode
|
||||||
import com.zaneschepke.wireguardautotunnel.domain.model.TunnelConfig
|
import com.zaneschepke.wireguardautotunnel.domain.model.TunnelConfig
|
||||||
|
import com.zaneschepke.wireguardautotunnel.ui.common.snackbar.SnackbarType
|
||||||
import com.zaneschepke.wireguardautotunnel.util.StringValue
|
import com.zaneschepke.wireguardautotunnel.util.StringValue
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
sealed class GlobalSideEffect {
|
sealed class GlobalSideEffect {
|
||||||
|
|
||||||
data class Snackbar(val message: StringValue) : GlobalSideEffect()
|
data class Snackbar(
|
||||||
|
val message: StringValue,
|
||||||
|
val type: SnackbarType? = null,
|
||||||
|
val actionLabel: String? = null,
|
||||||
|
val onAction: (() -> Unit)? = null,
|
||||||
|
val durationMs: Long? = null,
|
||||||
|
) : GlobalSideEffect()
|
||||||
|
|
||||||
data class Toast(val message: StringValue) : GlobalSideEffect()
|
data class Toast(val message: StringValue) : GlobalSideEffect()
|
||||||
|
|
||||||
|
|||||||
+46
@@ -0,0 +1,46 @@
|
|||||||
|
package com.zaneschepke.wireguardautotunnel.ui.common.button
|
||||||
|
|
||||||
|
import android.R.attr.onClick
|
||||||
|
import androidx.compose.foundation.gestures.detectTapGestures
|
||||||
|
import androidx.compose.foundation.layout.*
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.outlined.ExpandMore
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.IconButton
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.VerticalDivider
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.input.pointer.pointerInput
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import com.zaneschepke.wireguardautotunnel.R
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun SheetButtonWithDivider(
|
||||||
|
showDivider: Boolean = true,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
onClick: () -> Unit,
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.height(IntrinsicSize.Min),
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(12.dp),
|
||||||
|
) {
|
||||||
|
if (showDivider) {
|
||||||
|
VerticalDivider(
|
||||||
|
modifier = Modifier.fillMaxHeight().padding(horizontal = 8.dp, vertical = 8.dp),
|
||||||
|
color = MaterialTheme.colorScheme.outline,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Box(modifier = Modifier.pointerInput(Unit) { detectTapGestures {} }) {
|
||||||
|
IconButton(onClick = onClick, modifier) {
|
||||||
|
Icon(
|
||||||
|
Icons.Outlined.ExpandMore,
|
||||||
|
contentDescription = stringResource(R.string.select),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+23
-6
@@ -1,5 +1,6 @@
|
|||||||
package com.zaneschepke.wireguardautotunnel.ui.common.button
|
package com.zaneschepke.wireguardautotunnel.ui.common.button
|
||||||
|
|
||||||
|
import android.view.KeyEvent
|
||||||
import androidx.compose.animation.animateContentSize
|
import androidx.compose.animation.animateContentSize
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.combinedClickable
|
import androidx.compose.foundation.combinedClickable
|
||||||
@@ -16,10 +17,15 @@ import androidx.compose.ui.Modifier
|
|||||||
import androidx.compose.ui.focus.FocusRequester
|
import androidx.compose.ui.focus.FocusRequester
|
||||||
import androidx.compose.ui.focus.focusProperties
|
import androidx.compose.ui.focus.focusProperties
|
||||||
import androidx.compose.ui.focus.focusRequester
|
import androidx.compose.ui.focus.focusRequester
|
||||||
|
import androidx.compose.ui.input.key.Key
|
||||||
|
import androidx.compose.ui.input.key.key
|
||||||
|
import androidx.compose.ui.input.key.onKeyEvent
|
||||||
import androidx.compose.ui.layout.onSizeChanged
|
import androidx.compose.ui.layout.onSizeChanged
|
||||||
import androidx.compose.ui.platform.LocalDensity
|
import androidx.compose.ui.platform.LocalDensity
|
||||||
import androidx.compose.ui.text.AnnotatedString
|
import androidx.compose.ui.text.AnnotatedString
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
import com.zaneschepke.wireguardautotunnel.ui.LocalIsAndroidTV
|
||||||
|
import com.zaneschepke.wireguardautotunnel.ui.theme.Disabled
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun SurfaceRow(
|
fun SurfaceRow(
|
||||||
@@ -28,13 +34,14 @@ fun SurfaceRow(
|
|||||||
onClick: (() -> Unit)? = null,
|
onClick: (() -> Unit)? = null,
|
||||||
description: @Composable (() -> Unit)? = null,
|
description: @Composable (() -> Unit)? = null,
|
||||||
expandedContent: @Composable (() -> Unit)? = null,
|
expandedContent: @Composable (() -> Unit)? = null,
|
||||||
onLongClick: () -> Unit = {},
|
onLongClick: (() -> Unit)? = null,
|
||||||
enabled: Boolean = true,
|
enabled: Boolean = true,
|
||||||
selected: Boolean = false,
|
selected: Boolean = false,
|
||||||
leading: @Composable (() -> Unit)? = null,
|
leading: @Composable (() -> Unit)? = null,
|
||||||
trailing: @Composable ((Modifier) -> Unit)? = null,
|
trailing: @Composable ((Modifier) -> Unit)? = null,
|
||||||
) {
|
) {
|
||||||
val density = LocalDensity.current
|
val density = LocalDensity.current
|
||||||
|
val isTv = LocalIsAndroidTV.current
|
||||||
var leadingPadding by remember { mutableStateOf(0.dp) }
|
var leadingPadding by remember { mutableStateOf(0.dp) }
|
||||||
val interactionSource = remember { MutableInteractionSource() }
|
val interactionSource = remember { MutableInteractionSource() }
|
||||||
val mainFocusRequester = remember { FocusRequester() }
|
val mainFocusRequester = remember { FocusRequester() }
|
||||||
@@ -44,7 +51,6 @@ fun SurfaceRow(
|
|||||||
modifier =
|
modifier =
|
||||||
modifier
|
modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
// .focusGroup()
|
|
||||||
.indication(interactionSource, ripple())
|
.indication(interactionSource, ripple())
|
||||||
.background(
|
.background(
|
||||||
if (!selected) MaterialTheme.colorScheme.surface
|
if (!selected) MaterialTheme.colorScheme.surface
|
||||||
@@ -62,7 +68,20 @@ fun SurfaceRow(
|
|||||||
) {
|
) {
|
||||||
Row(
|
Row(
|
||||||
modifier =
|
modifier =
|
||||||
Modifier.focusRequester(mainFocusRequester)
|
Modifier.onKeyEvent { event ->
|
||||||
|
if (onLongClick == null || isTv) {
|
||||||
|
if (
|
||||||
|
event.key == Key.DirectionCenter &&
|
||||||
|
event.nativeKeyEvent.action == KeyEvent.ACTION_DOWN
|
||||||
|
) {
|
||||||
|
// Consume the down event to prevent the default long press
|
||||||
|
// behavior
|
||||||
|
return@onKeyEvent true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
false
|
||||||
|
}
|
||||||
|
.focusRequester(mainFocusRequester)
|
||||||
.focusProperties {
|
.focusProperties {
|
||||||
if (onClick != null) {
|
if (onClick != null) {
|
||||||
right = trailingFocusRequester
|
right = trailingFocusRequester
|
||||||
@@ -109,9 +128,7 @@ fun SurfaceRow(
|
|||||||
Text(
|
Text(
|
||||||
text = title,
|
text = title,
|
||||||
style = MaterialTheme.typography.bodyLarge,
|
style = MaterialTheme.typography.bodyLarge,
|
||||||
color =
|
color = if (enabled) MaterialTheme.colorScheme.onSurface else Disabled,
|
||||||
if (enabled) MaterialTheme.colorScheme.onSurface
|
|
||||||
else MaterialTheme.colorScheme.onSurface.copy(alpha = 0.38f),
|
|
||||||
)
|
)
|
||||||
if (description != null) {
|
if (description != null) {
|
||||||
description()
|
description()
|
||||||
|
|||||||
+1
-1
@@ -27,7 +27,7 @@ fun SwitchWithDivider(
|
|||||||
color = MaterialTheme.colorScheme.outline,
|
color = MaterialTheme.colorScheme.outline,
|
||||||
)
|
)
|
||||||
Box(modifier = Modifier.pointerInput(Unit) { detectTapGestures {} }) {
|
Box(modifier = Modifier.pointerInput(Unit) { detectTapGestures {} }) {
|
||||||
ScaledSwitch(
|
ThemedSwitch(
|
||||||
checked = checked,
|
checked = checked,
|
||||||
onClick = onClick,
|
onClick = onClick,
|
||||||
enabled = enabled,
|
enabled = enabled,
|
||||||
|
|||||||
+7
-3
@@ -5,9 +5,10 @@ import androidx.compose.material3.Switch
|
|||||||
import androidx.compose.material3.SwitchDefaults
|
import androidx.compose.material3.SwitchDefaults
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import com.zaneschepke.wireguardautotunnel.ui.theme.Disabled
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun ScaledSwitch(
|
fun ThemedSwitch(
|
||||||
checked: Boolean,
|
checked: Boolean,
|
||||||
onClick: (checked: Boolean) -> Unit,
|
onClick: (checked: Boolean) -> Unit,
|
||||||
enabled: Boolean = true,
|
enabled: Boolean = true,
|
||||||
@@ -21,12 +22,15 @@ fun ScaledSwitch(
|
|||||||
colors =
|
colors =
|
||||||
SwitchDefaults.colors()
|
SwitchDefaults.colors()
|
||||||
.copy(
|
.copy(
|
||||||
checkedThumbColor = MaterialTheme.colorScheme.background,
|
checkedThumbColor = MaterialTheme.colorScheme.surface,
|
||||||
checkedIconColor = MaterialTheme.colorScheme.background,
|
checkedIconColor = MaterialTheme.colorScheme.surface,
|
||||||
uncheckedTrackColor = MaterialTheme.colorScheme.surface,
|
uncheckedTrackColor = MaterialTheme.colorScheme.surface,
|
||||||
uncheckedBorderColor = MaterialTheme.colorScheme.outline,
|
uncheckedBorderColor = MaterialTheme.colorScheme.outline,
|
||||||
uncheckedThumbColor = MaterialTheme.colorScheme.outline,
|
uncheckedThumbColor = MaterialTheme.colorScheme.outline,
|
||||||
uncheckedIconColor = MaterialTheme.colorScheme.outline,
|
uncheckedIconColor = MaterialTheme.colorScheme.outline,
|
||||||
|
disabledUncheckedBorderColor = Disabled,
|
||||||
|
disabledUncheckedThumbColor = Disabled,
|
||||||
|
disabledUncheckedIconColor = Disabled,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
+10
-5
@@ -2,6 +2,7 @@ package com.zaneschepke.wireguardautotunnel.ui.common.dialog
|
|||||||
|
|
||||||
import androidx.compose.material3.*
|
import androidx.compose.material3.*
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
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 androidx.compose.ui.window.DialogProperties
|
import androidx.compose.ui.window.DialogProperties
|
||||||
@@ -11,22 +12,26 @@ import com.zaneschepke.wireguardautotunnel.R
|
|||||||
fun InfoDialog(
|
fun InfoDialog(
|
||||||
onAttest: () -> Unit,
|
onAttest: () -> Unit,
|
||||||
onDismiss: () -> Unit,
|
onDismiss: () -> Unit,
|
||||||
title: @Composable () -> Unit,
|
title: String,
|
||||||
body: @Composable () -> Unit,
|
body: @Composable (() -> Unit),
|
||||||
confirmText: @Composable () -> Unit,
|
confirmText: String,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
MaterialTheme(colorScheme = MaterialTheme.colorScheme.copy()) {
|
MaterialTheme(colorScheme = MaterialTheme.colorScheme.copy()) {
|
||||||
Surface(color = MaterialTheme.colorScheme.surface, tonalElevation = 0.dp) {
|
Surface(color = MaterialTheme.colorScheme.surface, tonalElevation = 0.dp) {
|
||||||
AlertDialog(
|
AlertDialog(
|
||||||
|
modifier = modifier,
|
||||||
onDismissRequest = { onDismiss() },
|
onDismissRequest = { onDismiss() },
|
||||||
confirmButton = { TextButton(onClick = { onAttest() }) { confirmText() } },
|
confirmButton = {
|
||||||
|
TextButton(onClick = { onAttest() }) { Text(text = confirmText) }
|
||||||
|
},
|
||||||
dismissButton = {
|
dismissButton = {
|
||||||
TextButton(onClick = { onDismiss() }) {
|
TextButton(onClick = { onDismiss() }) {
|
||||||
Text(text = stringResource(R.string.cancel))
|
Text(text = stringResource(R.string.cancel))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
containerColor = MaterialTheme.colorScheme.surface,
|
containerColor = MaterialTheme.colorScheme.surface,
|
||||||
title = { title() },
|
title = { Text(text = title) },
|
||||||
text = { body() },
|
text = { body() },
|
||||||
properties = DialogProperties(usePlatformDefaultWidth = true),
|
properties = DialogProperties(usePlatformDefaultWidth = true),
|
||||||
)
|
)
|
||||||
|
|||||||
+2
-2
@@ -34,7 +34,7 @@ fun VpnDeniedDialog(show: Boolean, onDismiss: () -> Unit) {
|
|||||||
InfoDialog(
|
InfoDialog(
|
||||||
onDismiss = { onDismiss() },
|
onDismiss = { onDismiss() },
|
||||||
onAttest = { onDismiss() },
|
onAttest = { onDismiss() },
|
||||||
title = { Text(text = stringResource(R.string.vpn_denied_dialog_title)) },
|
title = stringResource(R.string.vpn_denied_dialog_title),
|
||||||
body = {
|
body = {
|
||||||
Text(
|
Text(
|
||||||
text = alwaysOnDescription,
|
text = alwaysOnDescription,
|
||||||
@@ -44,7 +44,7 @@ fun VpnDeniedDialog(show: Boolean, onDismiss: () -> Unit) {
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
confirmText = { Text(text = stringResource(R.string.okay)) },
|
confirmText = stringResource(R.string.okay),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+4
@@ -14,6 +14,7 @@ import androidx.compose.ui.graphics.vector.ImageVector
|
|||||||
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.ui.LocalIsAndroidTV
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun SheetOption(
|
fun SheetOption(
|
||||||
@@ -50,9 +51,12 @@ fun SheetOption(
|
|||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun CustomBottomSheet(options: List<SheetOption>, onDismiss: () -> Unit) {
|
fun CustomBottomSheet(options: List<SheetOption>, onDismiss: () -> Unit) {
|
||||||
|
val isTv = LocalIsAndroidTV.current
|
||||||
|
val sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = isTv)
|
||||||
ModalBottomSheet(
|
ModalBottomSheet(
|
||||||
containerColor = MaterialTheme.colorScheme.surface,
|
containerColor = MaterialTheme.colorScheme.surface,
|
||||||
onDismissRequest = onDismiss,
|
onDismissRequest = onDismiss,
|
||||||
|
sheetState = sheetState,
|
||||||
) {
|
) {
|
||||||
options.forEachIndexed { index, option ->
|
options.forEachIndexed { index, option ->
|
||||||
SheetOption(option.label, option.leadingIcon, option.onClick, option.selected)
|
SheetOption(option.label, option.leadingIcon, option.onClick, option.selected)
|
||||||
|
|||||||
+55
-18
@@ -1,56 +1,93 @@
|
|||||||
package com.zaneschepke.wireguardautotunnel.ui.common.snackbar
|
package com.zaneschepke.wireguardautotunnel.ui.common.snackbar
|
||||||
|
|
||||||
|
import android.R.attr.padding
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.outlined.Favorite
|
||||||
|
import androidx.compose.material.icons.rounded.Close
|
||||||
import androidx.compose.material.icons.rounded.Info
|
import androidx.compose.material.icons.rounded.Info
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material.icons.rounded.Warning
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.*
|
||||||
import androidx.compose.material3.Snackbar
|
|
||||||
import androidx.compose.material3.Text
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.CompositionLocalProvider
|
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.platform.LocalLayoutDirection
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.unit.LayoutDirection
|
import androidx.compose.ui.text.AnnotatedString
|
||||||
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
import com.zaneschepke.wireguardautotunnel.R
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.LocalIsAndroidTV
|
import com.zaneschepke.wireguardautotunnel.ui.LocalIsAndroidTV
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun CustomSnackBar(
|
fun CustomSnackBar(
|
||||||
message: String,
|
message: AnnotatedString,
|
||||||
isRtl: Boolean = true,
|
onDismiss: () -> Unit,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
type: SnackbarType = SnackbarType.INFO,
|
||||||
containerColor: Color = MaterialTheme.colorScheme.surface,
|
containerColor: Color = MaterialTheme.colorScheme.surface,
|
||||||
) {
|
) {
|
||||||
val isTv = LocalIsAndroidTV.current
|
val isTv = LocalIsAndroidTV.current
|
||||||
|
val icon =
|
||||||
|
when (type) {
|
||||||
|
SnackbarType.INFO -> Icons.Rounded.Info
|
||||||
|
SnackbarType.WARNING -> Icons.Rounded.Warning
|
||||||
|
SnackbarType.THANK_YOU -> Icons.Outlined.Favorite
|
||||||
|
}
|
||||||
|
val iconDescription =
|
||||||
|
when (type) {
|
||||||
|
SnackbarType.INFO -> stringResource(R.string.info)
|
||||||
|
SnackbarType.WARNING -> stringResource(R.string.warning)
|
||||||
|
SnackbarType.THANK_YOU -> stringResource(R.string.thank_you)
|
||||||
|
}
|
||||||
|
|
||||||
Snackbar(
|
Snackbar(
|
||||||
containerColor = containerColor,
|
containerColor = containerColor,
|
||||||
modifier = Modifier.fillMaxWidth(if (isTv) 1 / 3f else 2 / 3f).padding(bottom = 100.dp),
|
modifier =
|
||||||
|
modifier
|
||||||
|
.wrapContentHeight(align = Alignment.Top)
|
||||||
|
.padding(horizontal = if (isTv) 48.dp else 16.dp),
|
||||||
shape = RoundedCornerShape(16.dp),
|
shape = RoundedCornerShape(16.dp),
|
||||||
) {
|
) {
|
||||||
CompositionLocalProvider(
|
Row(
|
||||||
LocalLayoutDirection provides if (isRtl) LayoutDirection.Rtl else LayoutDirection.Ltr
|
modifier =
|
||||||
|
Modifier.fillMaxWidth()
|
||||||
|
.height(IntrinsicSize.Min)
|
||||||
|
.width(IntrinsicSize.Min)
|
||||||
|
.padding(vertical = 16.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
horizontalArrangement = Arrangement.SpaceBetween,
|
||||||
) {
|
) {
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier.width(IntrinsicSize.Max).height(IntrinsicSize.Min),
|
modifier = Modifier.fillMaxWidth().weight(1f),
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
horizontalArrangement = Arrangement.Start,
|
horizontalArrangement = Arrangement.Start,
|
||||||
) {
|
) {
|
||||||
val icon = Icons.Rounded.Info
|
|
||||||
Icon(
|
Icon(
|
||||||
icon,
|
icon,
|
||||||
contentDescription = icon.name,
|
contentDescription = iconDescription,
|
||||||
tint = MaterialTheme.colorScheme.onSurface,
|
tint = MaterialTheme.colorScheme.onSurface,
|
||||||
modifier = Modifier.padding(end = 10.dp),
|
|
||||||
)
|
)
|
||||||
|
Spacer(modifier = Modifier.width(16.dp))
|
||||||
Text(
|
Text(
|
||||||
message,
|
text = message,
|
||||||
color = MaterialTheme.colorScheme.onSurface,
|
color = MaterialTheme.colorScheme.onSurface,
|
||||||
modifier = Modifier.padding(end = 5.dp),
|
maxLines = 8,
|
||||||
|
overflow = TextOverflow.Ellipsis,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Row {
|
||||||
|
IconButton(onClick = onDismiss, modifier = Modifier.size(24.dp)) {
|
||||||
|
Icon(
|
||||||
|
Icons.Rounded.Close,
|
||||||
|
contentDescription = stringResource(R.string.stop),
|
||||||
|
tint = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+70
@@ -0,0 +1,70 @@
|
|||||||
|
package com.zaneschepke.wireguardautotunnel.ui.common.snackbar
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.runtime.*
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import kotlinx.coroutines.channels.Channel
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun rememberCustomSnackbarState(): CustomSnackbarState {
|
||||||
|
return remember { CustomSnackbarState() }
|
||||||
|
}
|
||||||
|
|
||||||
|
class CustomSnackbarState {
|
||||||
|
private val _snackbars = Channel<SnackbarInfo>(Channel.BUFFERED)
|
||||||
|
val snackbars: Channel<SnackbarInfo> = _snackbars
|
||||||
|
|
||||||
|
private var currentSnackbar by mutableStateOf<SnackbarInfo?>(null)
|
||||||
|
private var isShowing by mutableStateOf(false)
|
||||||
|
|
||||||
|
fun showSnackbar(info: SnackbarInfo) {
|
||||||
|
_snackbars.trySend(info)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun dismissCurrent() {
|
||||||
|
currentSnackbar = null
|
||||||
|
isShowing = false
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun SnackbarHost(
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
snackbar: @Composable (SnackbarInfo) -> Unit = { info ->
|
||||||
|
CustomSnackBar(
|
||||||
|
message = info.message,
|
||||||
|
type = info.type,
|
||||||
|
onDismiss = { dismissCurrent() },
|
||||||
|
modifier = Modifier,
|
||||||
|
containerColor = MaterialTheme.colorScheme.surface.copy(.1f),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
val scope = rememberCoroutineScope()
|
||||||
|
LaunchedEffect(Unit) {
|
||||||
|
for (info in snackbars) {
|
||||||
|
currentSnackbar = info
|
||||||
|
isShowing = true
|
||||||
|
|
||||||
|
scope.launch {
|
||||||
|
delay(info.durationMs)
|
||||||
|
if (currentSnackbar?.id == info.id) {
|
||||||
|
dismissCurrent()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
while (isShowing && currentSnackbar?.id == info.id) {
|
||||||
|
delay(100)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
currentSnackbar?.let { info ->
|
||||||
|
if (isShowing) {
|
||||||
|
Box(modifier = modifier) { snackbar(info) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+16
@@ -0,0 +1,16 @@
|
|||||||
|
package com.zaneschepke.wireguardautotunnel.ui.common.snackbar
|
||||||
|
|
||||||
|
import androidx.compose.ui.text.AnnotatedString
|
||||||
|
|
||||||
|
enum class SnackbarType {
|
||||||
|
INFO,
|
||||||
|
WARNING,
|
||||||
|
THANK_YOU,
|
||||||
|
}
|
||||||
|
|
||||||
|
data class SnackbarInfo(
|
||||||
|
val message: AnnotatedString,
|
||||||
|
val type: SnackbarType = SnackbarType.INFO,
|
||||||
|
val durationMs: Long = 4000L,
|
||||||
|
val id: String = System.currentTimeMillis().toString(),
|
||||||
|
)
|
||||||
+15
-4
@@ -5,21 +5,32 @@ import androidx.compose.material3.Text
|
|||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.text.AnnotatedString
|
import androidx.compose.ui.text.AnnotatedString
|
||||||
|
import com.zaneschepke.wireguardautotunnel.ui.theme.Disabled
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun DescriptionText(text: String, modifier: Modifier = Modifier) {
|
fun DescriptionText(text: String, modifier: Modifier = Modifier, disabled: Boolean = false) {
|
||||||
Text(
|
Text(
|
||||||
text = text,
|
text = text,
|
||||||
style = MaterialTheme.typography.bodySmall.copy(color = MaterialTheme.colorScheme.outline),
|
style =
|
||||||
|
MaterialTheme.typography.bodySmall.copy(
|
||||||
|
color = if (disabled) Disabled else MaterialTheme.colorScheme.outline
|
||||||
|
),
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun DescriptionText(text: AnnotatedString, modifier: Modifier = Modifier) {
|
fun DescriptionText(
|
||||||
|
text: AnnotatedString,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
disabled: Boolean = false,
|
||||||
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = text,
|
text = text,
|
||||||
style = MaterialTheme.typography.bodySmall.copy(color = MaterialTheme.colorScheme.outline),
|
style =
|
||||||
|
MaterialTheme.typography.bodySmall.copy(
|
||||||
|
color = if (disabled) Disabled else MaterialTheme.colorScheme.outline
|
||||||
|
),
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-2
@@ -2,7 +2,7 @@ package com.zaneschepke.wireguardautotunnel.ui.common.textbox
|
|||||||
|
|
||||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.height
|
import androidx.compose.foundation.layout.heightIn
|
||||||
import androidx.compose.foundation.text.KeyboardActions
|
import androidx.compose.foundation.text.KeyboardActions
|
||||||
import androidx.compose.foundation.text.KeyboardOptions
|
import androidx.compose.foundation.text.KeyboardOptions
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
@@ -39,7 +39,7 @@ fun ConfigurationTextBox(
|
|||||||
isError = isError,
|
isError = isError,
|
||||||
textStyle =
|
textStyle =
|
||||||
MaterialTheme.typography.bodySmall.copy(color = MaterialTheme.colorScheme.onSurface),
|
MaterialTheme.typography.bodySmall.copy(color = MaterialTheme.colorScheme.onSurface),
|
||||||
modifier = modifier.fillMaxWidth().height(48.dp),
|
modifier = modifier.fillMaxWidth().heightIn(48.dp),
|
||||||
value = value,
|
value = value,
|
||||||
visualTransformation = visualTransformation,
|
visualTransformation = visualTransformation,
|
||||||
singleLine = singleLine,
|
singleLine = singleLine,
|
||||||
|
|||||||
+27
-3
@@ -52,10 +52,18 @@ fun CustomTextField(
|
|||||||
val editable = enabled && !readOnly
|
val editable = enabled && !readOnly
|
||||||
val mainFocusRequester = remember { FocusRequester() }
|
val mainFocusRequester = remember { FocusRequester() }
|
||||||
val trailingFocusRequester = remember { FocusRequester() }
|
val trailingFocusRequester = remember { FocusRequester() }
|
||||||
|
val disabledAlpha = 0.38f
|
||||||
|
val disabledBorderAlpha = 0.12f
|
||||||
|
val effectiveTextStyle =
|
||||||
|
if (enabled) {
|
||||||
|
textStyle
|
||||||
|
} else {
|
||||||
|
textStyle.copy(color = textStyle.color.copy(alpha = disabledAlpha))
|
||||||
|
}
|
||||||
|
|
||||||
BasicTextField(
|
BasicTextField(
|
||||||
value = value,
|
value = value,
|
||||||
textStyle = textStyle,
|
textStyle = effectiveTextStyle,
|
||||||
onValueChange = { onValueChange(it) },
|
onValueChange = { onValueChange(it) },
|
||||||
keyboardActions = keyboardActions,
|
keyboardActions = keyboardActions,
|
||||||
keyboardOptions = keyboardOptions,
|
keyboardOptions = keyboardOptions,
|
||||||
@@ -105,7 +113,18 @@ fun CustomTextField(
|
|||||||
colors =
|
colors =
|
||||||
TextFieldDefaults.colors()
|
TextFieldDefaults.colors()
|
||||||
.copy(
|
.copy(
|
||||||
disabledLabelColor = MaterialTheme.colorScheme.onSurface,
|
disabledTextColor =
|
||||||
|
MaterialTheme.colorScheme.onSurface.copy(alpha = disabledAlpha),
|
||||||
|
disabledLabelColor =
|
||||||
|
MaterialTheme.colorScheme.onSurface.copy(alpha = disabledAlpha),
|
||||||
|
disabledPlaceholderColor =
|
||||||
|
MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = disabledAlpha),
|
||||||
|
disabledLeadingIconColor =
|
||||||
|
MaterialTheme.colorScheme.onSurface.copy(alpha = disabledAlpha),
|
||||||
|
disabledTrailingIconColor =
|
||||||
|
MaterialTheme.colorScheme.onSurface.copy(alpha = disabledAlpha),
|
||||||
|
disabledSupportingTextColor =
|
||||||
|
MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = disabledAlpha),
|
||||||
disabledContainerColor = containerColor,
|
disabledContainerColor = containerColor,
|
||||||
focusedLabelColor = MaterialTheme.colorScheme.onSurface,
|
focusedLabelColor = MaterialTheme.colorScheme.onSurface,
|
||||||
focusedContainerColor = containerColor,
|
focusedContainerColor = containerColor,
|
||||||
@@ -127,8 +146,13 @@ fun CustomTextField(
|
|||||||
TextFieldDefaults.colors()
|
TextFieldDefaults.colors()
|
||||||
.copy(
|
.copy(
|
||||||
errorContainerColor = containerColor,
|
errorContainerColor = containerColor,
|
||||||
disabledLabelColor = MaterialTheme.colorScheme.onSurface,
|
disabledLabelColor =
|
||||||
|
MaterialTheme.colorScheme.onSurface.copy(alpha = disabledAlpha),
|
||||||
disabledContainerColor = containerColor,
|
disabledContainerColor = containerColor,
|
||||||
|
disabledIndicatorColor =
|
||||||
|
MaterialTheme.colorScheme.onSurface.copy(
|
||||||
|
alpha = disabledBorderAlpha
|
||||||
|
),
|
||||||
focusedIndicatorColor = MaterialTheme.colorScheme.primary,
|
focusedIndicatorColor = MaterialTheme.colorScheme.primary,
|
||||||
unfocusedIndicatorColor = MaterialTheme.colorScheme.outline,
|
unfocusedIndicatorColor = MaterialTheme.colorScheme.outline,
|
||||||
focusedLabelColor = MaterialTheme.colorScheme.onSurface,
|
focusedLabelColor = MaterialTheme.colorScheme.onSurface,
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ sealed class Route : NavKey {
|
|||||||
|
|
||||||
@Keep @Serializable data object Tunnels : Route()
|
@Keep @Serializable data object Tunnels : Route()
|
||||||
|
|
||||||
@Keep @Serializable data class TunnelOptions(val id: Int) : Route()
|
@Keep @Serializable data class TunnelSettings(val id: Int) : Route()
|
||||||
|
|
||||||
@Keep @Serializable data class Config(val id: Int?) : Route()
|
@Keep @Serializable data class Config(val id: Int?) : Route()
|
||||||
|
|
||||||
@@ -42,8 +42,6 @@ sealed class Route : NavKey {
|
|||||||
|
|
||||||
@Keep @Serializable data class ConfigGlobal(val id: Int?) : Route()
|
@Keep @Serializable data class ConfigGlobal(val id: Int?) : Route()
|
||||||
|
|
||||||
@Keep @Serializable data class TunnelGlobals(val id: Int) : Route()
|
|
||||||
|
|
||||||
@Keep @Serializable data class SplitTunnelGlobal(val id: Int) : Route()
|
@Keep @Serializable data class SplitTunnelGlobal(val id: Int) : Route()
|
||||||
|
|
||||||
@Keep @Serializable data object Sort : Route()
|
@Keep @Serializable data object Sort : Route()
|
||||||
@@ -58,6 +56,8 @@ sealed class Route : NavKey {
|
|||||||
|
|
||||||
@Keep @Serializable data object ProxySettings : Route()
|
@Keep @Serializable data object ProxySettings : Route()
|
||||||
|
|
||||||
|
@Keep @Serializable data object LockdownSettings : Route()
|
||||||
|
|
||||||
@Keep @Serializable data object AutoTunnel : Route()
|
@Keep @Serializable data object AutoTunnel : Route()
|
||||||
|
|
||||||
@Keep @Serializable data object AdvancedAutoTunnel : Route()
|
@Keep @Serializable data object AdvancedAutoTunnel : Route()
|
||||||
@@ -107,7 +107,7 @@ enum class Tab(
|
|||||||
when (route) {
|
when (route) {
|
||||||
is Route.Tunnels,
|
is Route.Tunnels,
|
||||||
Route.Sort,
|
Route.Sort,
|
||||||
is Route.TunnelOptions,
|
is Route.TunnelSettings,
|
||||||
is Route.Config,
|
is Route.Config,
|
||||||
is Route.Lock,
|
is Route.Lock,
|
||||||
is Route.SplitTunnel -> TUNNELS
|
is Route.SplitTunnel -> TUNNELS
|
||||||
@@ -121,14 +121,14 @@ enum class Tab(
|
|||||||
Route.TunnelMonitoring,
|
Route.TunnelMonitoring,
|
||||||
Route.AndroidIntegrations,
|
Route.AndroidIntegrations,
|
||||||
Route.Dns,
|
Route.Dns,
|
||||||
is Route.TunnelGlobals,
|
|
||||||
is Route.ConfigGlobal,
|
|
||||||
is Route.SplitTunnelGlobal,
|
is Route.SplitTunnelGlobal,
|
||||||
Route.ProxySettings,
|
Route.ProxySettings,
|
||||||
|
Route.LockdownSettings,
|
||||||
Route.Appearance,
|
Route.Appearance,
|
||||||
Route.Language,
|
Route.Language,
|
||||||
Route.Display,
|
Route.Display,
|
||||||
Route.PingTarget,
|
Route.PingTarget,
|
||||||
|
is Route.ConfigGlobal,
|
||||||
Route.Logs -> SETTINGS
|
Route.Logs -> SETTINGS
|
||||||
is Route.Support,
|
is Route.Support,
|
||||||
Route.License,
|
Route.License,
|
||||||
|
|||||||
+35
-65
@@ -113,6 +113,29 @@ fun currentRouteAsNavbarState(
|
|||||||
showBottomItems = true,
|
showBottomItems = true,
|
||||||
topTitle = context.getString(R.string.language),
|
topTitle = context.getString(R.string.language),
|
||||||
)
|
)
|
||||||
|
LockdownSettings ->
|
||||||
|
NavbarState(
|
||||||
|
topLeading = {
|
||||||
|
IconButton(onClick = { navController.pop() }) {
|
||||||
|
Icon(
|
||||||
|
Icons.AutoMirrored.Rounded.ArrowBack,
|
||||||
|
stringResource(R.string.back),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
showBottomItems = true,
|
||||||
|
topTitle = context.getString(R.string.lockdown_settings),
|
||||||
|
topTrailing = {
|
||||||
|
IconButton(
|
||||||
|
onClick = {
|
||||||
|
keyboardController?.hide()
|
||||||
|
sharedViewModel.postSideEffect(LocalSideEffect.SaveChanges)
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
Icon(Icons.Rounded.Save, stringResource(R.string.save))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
License ->
|
License ->
|
||||||
NavbarState(
|
NavbarState(
|
||||||
topLeading = {
|
topLeading = {
|
||||||
@@ -211,8 +234,11 @@ fun currentRouteAsNavbarState(
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
is Config -> {
|
is Config,
|
||||||
val tunnelName = sharedState.tunnels.find { it.id == route.id }?.name
|
is ConfigGlobal -> {
|
||||||
|
val tunnelName =
|
||||||
|
if (route is Config) sharedState.tunnels.find { it.id == route.id }?.name
|
||||||
|
else context.getString(R.string.global_dns_servers)
|
||||||
NavbarState(
|
NavbarState(
|
||||||
topLeading = {
|
topLeading = {
|
||||||
IconButton(onClick = { navController.pop() }) {
|
IconButton(onClick = { navController.pop() }) {
|
||||||
@@ -236,8 +262,12 @@ fun currentRouteAsNavbarState(
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
is SplitTunnel -> {
|
is SplitTunnel,
|
||||||
val tunnelName = sharedState.tunnels.find { it.id == route.id }?.name
|
is SplitTunnelGlobal -> {
|
||||||
|
val tunnelName =
|
||||||
|
if (route is SplitTunnel)
|
||||||
|
sharedState.tunnels.find { it.id == route.id }?.name
|
||||||
|
else context.getString(R.string.global_split_tunneling)
|
||||||
NavbarState(
|
NavbarState(
|
||||||
topLeading = {
|
topLeading = {
|
||||||
IconButton(onClick = { navController.pop() }) {
|
IconButton(onClick = { navController.pop() }) {
|
||||||
@@ -260,52 +290,6 @@ fun currentRouteAsNavbarState(
|
|||||||
showBottomItems = true,
|
showBottomItems = true,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
is SplitTunnelGlobal -> {
|
|
||||||
NavbarState(
|
|
||||||
topLeading = {
|
|
||||||
IconButton(onClick = { navController.pop() }) {
|
|
||||||
Icon(
|
|
||||||
Icons.AutoMirrored.Rounded.ArrowBack,
|
|
||||||
stringResource(R.string.back),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
topTitle = context.getString(R.string.splt_tunneling),
|
|
||||||
topTrailing = {
|
|
||||||
IconButton(
|
|
||||||
onClick = {
|
|
||||||
sharedViewModel.postSideEffect(LocalSideEffect.SaveChanges)
|
|
||||||
}
|
|
||||||
) {
|
|
||||||
Icon(Icons.Rounded.Save, stringResource(R.string.save))
|
|
||||||
}
|
|
||||||
},
|
|
||||||
showBottomItems = true,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
is ConfigGlobal -> {
|
|
||||||
NavbarState(
|
|
||||||
topLeading = {
|
|
||||||
IconButton(onClick = { navController.pop() }) {
|
|
||||||
Icon(
|
|
||||||
Icons.AutoMirrored.Rounded.ArrowBack,
|
|
||||||
stringResource(R.string.back),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
showBottomItems = true,
|
|
||||||
topTitle = context.getString(R.string.configuration),
|
|
||||||
topTrailing = {
|
|
||||||
IconButton(
|
|
||||||
onClick = {
|
|
||||||
sharedViewModel.postSideEffect(LocalSideEffect.SaveChanges)
|
|
||||||
}
|
|
||||||
) {
|
|
||||||
Icon(Icons.Rounded.Save, stringResource(R.string.save))
|
|
||||||
}
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
Support ->
|
Support ->
|
||||||
NavbarState(
|
NavbarState(
|
||||||
topTitle = context.getString(R.string.support),
|
topTitle = context.getString(R.string.support),
|
||||||
@@ -337,7 +321,7 @@ fun currentRouteAsNavbarState(
|
|||||||
topTitle = context.getString(R.string.ping_monitor),
|
topTitle = context.getString(R.string.ping_monitor),
|
||||||
showBottomItems = true,
|
showBottomItems = true,
|
||||||
)
|
)
|
||||||
is TunnelOptions -> {
|
is TunnelSettings -> {
|
||||||
val tunnelName = sharedState.tunnels.find { it.id == route.id }?.name
|
val tunnelName = sharedState.tunnels.find { it.id == route.id }?.name
|
||||||
NavbarState(
|
NavbarState(
|
||||||
topLeading = {
|
topLeading = {
|
||||||
@@ -497,20 +481,6 @@ fun currentRouteAsNavbarState(
|
|||||||
showBottomItems = true,
|
showBottomItems = true,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
is TunnelGlobals -> {
|
|
||||||
NavbarState(
|
|
||||||
topLeading = {
|
|
||||||
IconButton(onClick = { navController.pop() }) {
|
|
||||||
Icon(
|
|
||||||
Icons.AutoMirrored.Rounded.ArrowBack,
|
|
||||||
stringResource(R.string.back),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
topTitle = context.getString(R.string.global_overrides),
|
|
||||||
showBottomItems = true,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
is WifiPreferences -> {
|
is WifiPreferences -> {
|
||||||
NavbarState(
|
NavbarState(
|
||||||
topLeading = {
|
topLeading = {
|
||||||
|
|||||||
+3
-3
@@ -40,9 +40,9 @@ import com.zaneschepke.wireguardautotunnel.R
|
|||||||
import com.zaneschepke.wireguardautotunnel.domain.enums.NetworkType
|
import com.zaneschepke.wireguardautotunnel.domain.enums.NetworkType
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.LocalNavController
|
import com.zaneschepke.wireguardautotunnel.ui.LocalNavController
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.LocalSharedVm
|
import com.zaneschepke.wireguardautotunnel.ui.LocalSharedVm
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.ScaledSwitch
|
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.SurfaceRow
|
import com.zaneschepke.wireguardautotunnel.ui.common.button.SurfaceRow
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.SwitchWithDivider
|
import com.zaneschepke.wireguardautotunnel.ui.common.button.SwitchWithDivider
|
||||||
|
import com.zaneschepke.wireguardautotunnel.ui.common.button.ThemedSwitch
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.common.functions.rememberClipboardHelper
|
import com.zaneschepke.wireguardautotunnel.ui.common.functions.rememberClipboardHelper
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.common.label.GroupLabel
|
import com.zaneschepke.wireguardautotunnel.ui.common.label.GroupLabel
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.common.text.DescriptionText
|
import com.zaneschepke.wireguardautotunnel.ui.common.text.DescriptionText
|
||||||
@@ -294,7 +294,7 @@ fun AutoTunnelScreen(viewModel: AutoTunnelViewModel = hiltViewModel()) {
|
|||||||
title = stringResource(R.string.stop_on_no_internet),
|
title = stringResource(R.string.stop_on_no_internet),
|
||||||
description = { DescriptionText(stringResource(R.string.stop_on_internet_loss)) },
|
description = { DescriptionText(stringResource(R.string.stop_on_internet_loss)) },
|
||||||
trailing = {
|
trailing = {
|
||||||
ScaledSwitch(
|
ThemedSwitch(
|
||||||
checked = autoTunnelState.autoTunnelSettings.isStopOnNoInternetEnabled,
|
checked = autoTunnelState.autoTunnelSettings.isStopOnNoInternetEnabled,
|
||||||
onClick = { viewModel.setStopOnNoInternetEnabled(it) },
|
onClick = { viewModel.setStopOnNoInternetEnabled(it) },
|
||||||
)
|
)
|
||||||
@@ -315,7 +315,7 @@ fun AutoTunnelScreen(viewModel: AutoTunnelViewModel = hiltViewModel()) {
|
|||||||
leading = { Icon(Icons.Outlined.RestartAlt, contentDescription = null) },
|
leading = { Icon(Icons.Outlined.RestartAlt, contentDescription = null) },
|
||||||
title = stringResource(R.string.restart_at_boot),
|
title = stringResource(R.string.restart_at_boot),
|
||||||
trailing = {
|
trailing = {
|
||||||
ScaledSwitch(
|
ThemedSwitch(
|
||||||
checked = autoTunnelState.autoTunnelSettings.startOnBoot,
|
checked = autoTunnelState.autoTunnelSettings.startOnBoot,
|
||||||
onClick = { viewModel.setStartAtBoot(it) },
|
onClick = { viewModel.setStartAtBoot(it) },
|
||||||
)
|
)
|
||||||
|
|||||||
+4
-4
@@ -26,8 +26,8 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
|||||||
import com.zaneschepke.wireguardautotunnel.R
|
import com.zaneschepke.wireguardautotunnel.R
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.LocalNavController
|
import com.zaneschepke.wireguardautotunnel.ui.LocalNavController
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.common.banner.WarningBanner
|
import com.zaneschepke.wireguardautotunnel.ui.common.banner.WarningBanner
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.ScaledSwitch
|
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.SurfaceRow
|
import com.zaneschepke.wireguardautotunnel.ui.common.button.SurfaceRow
|
||||||
|
import com.zaneschepke.wireguardautotunnel.ui.common.button.ThemedSwitch
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.common.dialog.InfoDialog
|
import com.zaneschepke.wireguardautotunnel.ui.common.dialog.InfoDialog
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.common.label.GroupLabel
|
import com.zaneschepke.wireguardautotunnel.ui.common.label.GroupLabel
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.common.text.DescriptionText
|
import com.zaneschepke.wireguardautotunnel.ui.common.text.DescriptionText
|
||||||
@@ -88,9 +88,9 @@ fun WifiSettingsScreen(viewModel: AutoTunnelViewModel = hiltViewModel()) {
|
|||||||
showLocationDialog = false
|
showLocationDialog = false
|
||||||
},
|
},
|
||||||
onDismiss = { showLocationDialog = false },
|
onDismiss = { showLocationDialog = false },
|
||||||
title = { Text(stringResource(R.string.location_permissions)) },
|
title = stringResource(R.string.location_permissions),
|
||||||
body = { Text(stringResource(R.string.location_justification)) },
|
body = { Text(stringResource(R.string.location_justification)) },
|
||||||
confirmText = { Text(stringResource(R.string.open_settings)) },
|
confirmText = stringResource(R.string.open_settings),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -156,7 +156,7 @@ fun WifiSettingsScreen(viewModel: AutoTunnelViewModel = hiltViewModel()) {
|
|||||||
)
|
)
|
||||||
},
|
},
|
||||||
trailing = {
|
trailing = {
|
||||||
ScaledSwitch(
|
ThemedSwitch(
|
||||||
checked = autoTunnelState.autoTunnelSettings.isWildcardsEnabled,
|
checked = autoTunnelState.autoTunnelSettings.isWildcardsEnabled,
|
||||||
onClick = { viewModel.setWildcardsEnabled(it) },
|
onClick = { viewModel.setWildcardsEnabled(it) },
|
||||||
)
|
)
|
||||||
|
|||||||
+80
-93
@@ -7,16 +7,16 @@ 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.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.automirrored.outlined.CallSplit
|
||||||
import androidx.compose.material.icons.automirrored.outlined.ViewQuilt
|
import androidx.compose.material.icons.automirrored.outlined.ViewQuilt
|
||||||
import androidx.compose.material.icons.outlined.*
|
import androidx.compose.material.icons.outlined.*
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.IconButton
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Text
|
|
||||||
import androidx.compose.runtime.*
|
import androidx.compose.runtime.*
|
||||||
import androidx.compose.runtime.saveable.rememberSaveable
|
import androidx.compose.runtime.saveable.rememberSaveable
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.graphics.Color
|
|
||||||
import androidx.compose.ui.graphics.vector.ImageVector
|
import androidx.compose.ui.graphics.vector.ImageVector
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
@@ -29,14 +29,16 @@ import com.zaneschepke.wireguardautotunnel.R
|
|||||||
import com.zaneschepke.wireguardautotunnel.data.model.AppMode
|
import com.zaneschepke.wireguardautotunnel.data.model.AppMode
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.LocalNavController
|
import com.zaneschepke.wireguardautotunnel.ui.LocalNavController
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.LocalSharedVm
|
import com.zaneschepke.wireguardautotunnel.ui.LocalSharedVm
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.ScaledSwitch
|
import com.zaneschepke.wireguardautotunnel.ui.common.button.SheetButtonWithDivider
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.SurfaceRow
|
import com.zaneschepke.wireguardautotunnel.ui.common.button.SurfaceRow
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.SwitchWithDivider
|
import com.zaneschepke.wireguardautotunnel.ui.common.button.SwitchWithDivider
|
||||||
|
import com.zaneschepke.wireguardautotunnel.ui.common.button.ThemedSwitch
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.common.label.GroupLabel
|
import com.zaneschepke.wireguardautotunnel.ui.common.label.GroupLabel
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.common.text.DescriptionText
|
import com.zaneschepke.wireguardautotunnel.ui.common.text.DescriptionText
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.navigation.Route
|
import com.zaneschepke.wireguardautotunnel.ui.navigation.Route
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.screens.settings.components.BackupBottomSheet
|
import com.zaneschepke.wireguardautotunnel.ui.screens.settings.components.BackupBottomSheet
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.screens.settings.proxy.compoents.AppModeBottomSheet
|
import com.zaneschepke.wireguardautotunnel.ui.screens.settings.proxy.compoents.AppModeBottomSheet
|
||||||
|
import com.zaneschepke.wireguardautotunnel.ui.theme.Disabled
|
||||||
import com.zaneschepke.wireguardautotunnel.util.StringValue
|
import com.zaneschepke.wireguardautotunnel.util.StringValue
|
||||||
import com.zaneschepke.wireguardautotunnel.util.extensions.asString
|
import com.zaneschepke.wireguardautotunnel.util.extensions.asString
|
||||||
import com.zaneschepke.wireguardautotunnel.util.extensions.asTitleString
|
import com.zaneschepke.wireguardautotunnel.util.extensions.asTitleString
|
||||||
@@ -53,29 +55,24 @@ fun SettingsScreen(viewModel: SettingsViewModel = hiltViewModel()) {
|
|||||||
|
|
||||||
val locale = remember { Locale.getDefault() }
|
val locale = remember { Locale.getDefault() }
|
||||||
|
|
||||||
val sharedState by sharedViewModel.container.stateFlow.collectAsStateWithLifecycle()
|
val sharedUiState by sharedViewModel.container.stateFlow.collectAsStateWithLifecycle()
|
||||||
val settingsState by viewModel.container.stateFlow.collectAsStateWithLifecycle()
|
val uiState by viewModel.container.stateFlow.collectAsStateWithLifecycle()
|
||||||
|
|
||||||
if (settingsState.isLoading) return
|
if (uiState.isLoading) return
|
||||||
|
|
||||||
var showBackupSheet by rememberSaveable { mutableStateOf(false) }
|
var showBackupSheet by rememberSaveable { mutableStateOf(false) }
|
||||||
var showAppModeSheet by rememberSaveable { mutableStateOf(false) }
|
var showAppModeSheet by rememberSaveable { mutableStateOf(false) }
|
||||||
|
|
||||||
val appMode = settingsState.settings.appMode
|
val appMode = uiState.settings.appMode
|
||||||
val dnsEnabled by rememberSaveable(appMode) { mutableStateOf(appMode != AppMode.KERNEL) }
|
val dnsEnabled by rememberSaveable(appMode) { mutableStateOf(appMode != AppMode.KERNEL) }
|
||||||
|
|
||||||
val showProxySettings by
|
val showModeDivider by
|
||||||
remember(appMode) {
|
remember(appMode) {
|
||||||
derivedStateOf {
|
derivedStateOf { appMode == AppMode.PROXY || appMode == AppMode.LOCK_DOWN }
|
||||||
when (appMode) {
|
|
||||||
AppMode.PROXY -> true
|
|
||||||
else -> false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun performBackupRestore(action: () -> Unit) {
|
fun performBackupRestore(action: () -> Unit) {
|
||||||
if (sharedState.activeTunnels.isNotEmpty() || sharedState.isAutoTunnelActive)
|
if (sharedUiState.activeTunnels.isNotEmpty() || sharedUiState.isAutoTunnelActive)
|
||||||
return context.showToast(R.string.all_services_disabled)
|
return context.showToast(R.string.all_services_disabled)
|
||||||
showBackupSheet = false
|
showBackupSheet = false
|
||||||
action()
|
action()
|
||||||
@@ -89,22 +86,10 @@ fun SettingsScreen(viewModel: SettingsViewModel = hiltViewModel()) {
|
|||||||
showBackupSheet = false
|
showBackupSheet = false
|
||||||
}
|
}
|
||||||
if (showAppModeSheet)
|
if (showAppModeSheet)
|
||||||
AppModeBottomSheet(sharedViewModel::setAppMode, settingsState.settings.appMode) {
|
AppModeBottomSheet(sharedViewModel::setAppMode, uiState.settings.appMode) {
|
||||||
showAppModeSheet = false
|
showAppModeSheet = false
|
||||||
}
|
}
|
||||||
|
|
||||||
val isPingMonitoringAvailable by
|
|
||||||
remember(settingsState.settings.appMode) {
|
|
||||||
derivedStateOf {
|
|
||||||
settingsState.settings.appMode != AppMode.PROXY &&
|
|
||||||
settingsState.settings.appMode != AppMode.LOCK_DOWN
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
LaunchedEffect(isPingMonitoringAvailable) {
|
|
||||||
if (!isPingMonitoringAvailable) viewModel.setPingEnabled(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
Column(
|
Column(
|
||||||
horizontalAlignment = Alignment.Start,
|
horizontalAlignment = Alignment.Start,
|
||||||
verticalArrangement = Arrangement.spacedBy(16.dp, Alignment.Top),
|
verticalArrangement = Arrangement.spacedBy(16.dp, Alignment.Top),
|
||||||
@@ -119,11 +104,8 @@ fun SettingsScreen(viewModel: SettingsViewModel = hiltViewModel()) {
|
|||||||
leading = {
|
leading = {
|
||||||
Icon(ImageVector.vectorResource(R.drawable.sdk), contentDescription = null)
|
Icon(ImageVector.vectorResource(R.drawable.sdk), contentDescription = null)
|
||||||
},
|
},
|
||||||
trailing = {
|
trailing = { modifier ->
|
||||||
Icon(
|
SheetButtonWithDivider(showModeDivider, modifier) { showAppModeSheet = true }
|
||||||
Icons.Outlined.ExpandMore,
|
|
||||||
contentDescription = stringResource(R.string.select),
|
|
||||||
)
|
|
||||||
},
|
},
|
||||||
title = stringResource(R.string.backend_mode),
|
title = stringResource(R.string.backend_mode),
|
||||||
description = {
|
description = {
|
||||||
@@ -131,40 +113,23 @@ fun SettingsScreen(viewModel: SettingsViewModel = hiltViewModel()) {
|
|||||||
stringResource(R.string.current_template, appMode.asTitleString(context))
|
stringResource(R.string.current_template, appMode.asTitleString(context))
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
onClick = { showAppModeSheet = true },
|
onClick = {
|
||||||
|
when (appMode) {
|
||||||
|
AppMode.PROXY -> navController.push(Route.ProxySettings)
|
||||||
|
AppMode.LOCK_DOWN -> navController.push(Route.LockdownSettings)
|
||||||
|
AppMode.KERNEL,
|
||||||
|
AppMode.VPN -> showAppModeSheet = true
|
||||||
|
}
|
||||||
|
},
|
||||||
)
|
)
|
||||||
if (appMode == AppMode.LOCK_DOWN) {
|
|
||||||
SurfaceRow(
|
|
||||||
leading = { Icon(Icons.Outlined.Lan, contentDescription = null) },
|
|
||||||
title = stringResource(R.string.allow_lan_traffic),
|
|
||||||
description = {
|
|
||||||
Text(
|
|
||||||
text = stringResource(R.string.bypass_lan_for_kill_switch),
|
|
||||||
style =
|
|
||||||
MaterialTheme.typography.bodySmall.copy(
|
|
||||||
MaterialTheme.colorScheme.outline
|
|
||||||
),
|
|
||||||
)
|
|
||||||
},
|
|
||||||
trailing = {
|
|
||||||
ScaledSwitch(
|
|
||||||
checked = settingsState.settings.isLanOnKillSwitchEnabled,
|
|
||||||
onClick = { viewModel.setLanKillSwitchEnabled(it) },
|
|
||||||
)
|
|
||||||
},
|
|
||||||
onClick = {
|
|
||||||
viewModel.setLanKillSwitchEnabled(
|
|
||||||
!settingsState.settings.isLanOnKillSwitchEnabled
|
|
||||||
)
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
SurfaceRow(
|
SurfaceRow(
|
||||||
leading = {
|
leading = {
|
||||||
Icon(
|
Icon(
|
||||||
Icons.Outlined.Dns,
|
Icons.Outlined.Dns,
|
||||||
null,
|
null,
|
||||||
tint = if (dnsEnabled) MaterialTheme.colorScheme.onSurface else Color.Gray,
|
tint =
|
||||||
|
if (dnsEnabled) MaterialTheme.colorScheme.onSurface
|
||||||
|
else MaterialTheme.colorScheme.outline,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
title = stringResource(R.string.dns_settings),
|
title = stringResource(R.string.dns_settings),
|
||||||
@@ -182,29 +147,39 @@ fun SettingsScreen(viewModel: SettingsViewModel = hiltViewModel()) {
|
|||||||
)
|
)
|
||||||
SurfaceRow(
|
SurfaceRow(
|
||||||
leading = {
|
leading = {
|
||||||
Icon(ImageVector.vectorResource(R.drawable.globe), contentDescription = null)
|
Icon(
|
||||||
},
|
Icons.AutoMirrored.Outlined.CallSplit,
|
||||||
title = stringResource(R.string.global_overrides),
|
contentDescription = null,
|
||||||
trailing = { modifier ->
|
tint =
|
||||||
SwitchWithDivider(
|
if (sharedUiState.proxyEnabled) Disabled
|
||||||
checked = settingsState.settings.isTunnelGlobalsEnabled,
|
else MaterialTheme.colorScheme.onSurface,
|
||||||
onClick = { viewModel.setTunnelGlobals(it) },
|
|
||||||
modifier = modifier,
|
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
enabled = !sharedUiState.proxyEnabled,
|
||||||
|
title = stringResource(R.string.global_split_tunneling),
|
||||||
|
trailing = { modifier ->
|
||||||
|
SwitchWithDivider(
|
||||||
|
checked = uiState.settings.isGlobalSplitTunnelEnabled,
|
||||||
|
onClick = { viewModel.setGlobalSplitTunneling(it) },
|
||||||
|
modifier = modifier,
|
||||||
|
enabled = !sharedUiState.proxyEnabled,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
description =
|
||||||
|
if (sharedUiState.proxyEnabled) {
|
||||||
|
{
|
||||||
|
DescriptionText(
|
||||||
|
stringResource(R.string.unavailable_in_mode),
|
||||||
|
disabled = true,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else null,
|
||||||
onClick = {
|
onClick = {
|
||||||
settingsState.globalTunnelConfig?.let {
|
uiState.globalTunnelConfig?.let {
|
||||||
navController.push(Route.TunnelGlobals(it.id))
|
navController.push(Route.SplitTunnelGlobal(id = it.id))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
if (showProxySettings) {
|
|
||||||
SurfaceRow(
|
|
||||||
leading = { Icon(ImageVector.vectorResource(R.drawable.proxy), null) },
|
|
||||||
title = stringResource(R.string.proxy_settings),
|
|
||||||
onClick = { navController.push(Route.ProxySettings) },
|
|
||||||
)
|
|
||||||
}
|
|
||||||
SurfaceRow(
|
SurfaceRow(
|
||||||
leading = { Icon(Icons.Outlined.Android, null) },
|
leading = { Icon(Icons.Outlined.Android, null) },
|
||||||
title = stringResource(R.string.android_integrations),
|
title = stringResource(R.string.android_integrations),
|
||||||
@@ -222,17 +197,27 @@ fun SettingsScreen(viewModel: SettingsViewModel = hiltViewModel()) {
|
|||||||
Icons.Outlined.NetworkPing,
|
Icons.Outlined.NetworkPing,
|
||||||
contentDescription = null,
|
contentDescription = null,
|
||||||
tint =
|
tint =
|
||||||
if (isPingMonitoringAvailable) MaterialTheme.colorScheme.onSurface
|
if (!sharedUiState.proxyEnabled) MaterialTheme.colorScheme.onSurface
|
||||||
else Color.Gray,
|
else Disabled,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
title = stringResource(R.string.ping_monitor),
|
title = stringResource(R.string.ping_monitor),
|
||||||
enabled = isPingMonitoringAvailable,
|
enabled = !sharedUiState.proxyEnabled,
|
||||||
trailing = {
|
description =
|
||||||
|
if (sharedUiState.proxyEnabled) {
|
||||||
|
{
|
||||||
|
DescriptionText(
|
||||||
|
stringResource(R.string.unavailable_in_mode),
|
||||||
|
disabled = true,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else null,
|
||||||
|
trailing = { modifier ->
|
||||||
SwitchWithDivider(
|
SwitchWithDivider(
|
||||||
checked = settingsState.monitoring.isPingEnabled,
|
checked = uiState.monitoring.isPingEnabled,
|
||||||
onClick = { viewModel.setPingEnabled(it) },
|
onClick = { viewModel.setPingEnabled(it) },
|
||||||
enabled = isPingMonitoringAvailable,
|
enabled = !sharedUiState.proxyEnabled,
|
||||||
|
modifier = modifier,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
onClick = { navController.push(Route.TunnelMonitoring) },
|
onClick = { navController.push(Route.TunnelMonitoring) },
|
||||||
@@ -242,7 +227,7 @@ fun SettingsScreen(viewModel: SettingsViewModel = hiltViewModel()) {
|
|||||||
title = stringResource(R.string.local_logging),
|
title = stringResource(R.string.local_logging),
|
||||||
trailing = { modifier ->
|
trailing = { modifier ->
|
||||||
SwitchWithDivider(
|
SwitchWithDivider(
|
||||||
checked = settingsState.monitoring.isLocalLogsEnabled,
|
checked = uiState.monitoring.isLocalLogsEnabled,
|
||||||
onClick = { viewModel.setLocalLogging(it) },
|
onClick = { viewModel.setLocalLogging(it) },
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
)
|
)
|
||||||
@@ -266,8 +251,8 @@ fun SettingsScreen(viewModel: SettingsViewModel = hiltViewModel()) {
|
|||||||
leading = { Icon(Icons.Outlined.Pin, contentDescription = null) },
|
leading = { Icon(Icons.Outlined.Pin, contentDescription = null) },
|
||||||
title = stringResource(R.string.enable_app_lock),
|
title = stringResource(R.string.enable_app_lock),
|
||||||
trailing = {
|
trailing = {
|
||||||
ScaledSwitch(
|
ThemedSwitch(
|
||||||
checked = settingsState.isPinLockEnabled,
|
checked = uiState.isPinLockEnabled,
|
||||||
onClick = {
|
onClick = {
|
||||||
if (it) {
|
if (it) {
|
||||||
navController.push(Route.Lock)
|
navController.push(Route.Lock)
|
||||||
@@ -278,7 +263,7 @@ fun SettingsScreen(viewModel: SettingsViewModel = hiltViewModel()) {
|
|||||||
)
|
)
|
||||||
},
|
},
|
||||||
onClick = {
|
onClick = {
|
||||||
if (!settingsState.isPinLockEnabled) {
|
if (!uiState.isPinLockEnabled) {
|
||||||
navController.push(Route.Lock)
|
navController.push(Route.Lock)
|
||||||
} else {
|
} else {
|
||||||
sharedViewModel.setPinLockEnabled(false)
|
sharedViewModel.setPinLockEnabled(false)
|
||||||
@@ -289,11 +274,13 @@ fun SettingsScreen(viewModel: SettingsViewModel = hiltViewModel()) {
|
|||||||
leading = { Icon(Icons.Outlined.SettingsBackupRestore, contentDescription = null) },
|
leading = { Icon(Icons.Outlined.SettingsBackupRestore, contentDescription = null) },
|
||||||
title = stringResource(R.string.backup_and_restore),
|
title = stringResource(R.string.backup_and_restore),
|
||||||
onClick = { showBackupSheet = true },
|
onClick = { showBackupSheet = true },
|
||||||
trailing = {
|
trailing = { modifier ->
|
||||||
Icon(
|
IconButton(modifier = modifier, onClick = { showBackupSheet = true }) {
|
||||||
Icons.Outlined.ExpandMore,
|
Icon(
|
||||||
contentDescription = stringResource(R.string.select),
|
Icons.Outlined.ExpandMore,
|
||||||
)
|
contentDescription = stringResource(R.string.select),
|
||||||
|
)
|
||||||
|
}
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
+37
-1
@@ -4,6 +4,7 @@ import androidx.compose.animation.AnimatedVisibility
|
|||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
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.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
@@ -12,32 +13,45 @@ import androidx.compose.material.icons.outlined.Dns
|
|||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.vector.ImageVector
|
||||||
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.res.vectorResource
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
import com.zaneschepke.wireguardautotunnel.R
|
import com.zaneschepke.wireguardautotunnel.R
|
||||||
import com.zaneschepke.wireguardautotunnel.data.model.DnsProtocol
|
import com.zaneschepke.wireguardautotunnel.data.model.DnsProtocol
|
||||||
import com.zaneschepke.wireguardautotunnel.data.model.DnsProvider
|
import com.zaneschepke.wireguardautotunnel.data.model.DnsProvider
|
||||||
|
import com.zaneschepke.wireguardautotunnel.ui.LocalNavController
|
||||||
|
import com.zaneschepke.wireguardautotunnel.ui.common.button.SurfaceRow
|
||||||
|
import com.zaneschepke.wireguardautotunnel.ui.common.button.SwitchWithDivider
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.common.dropdown.LabelledDropdown
|
import com.zaneschepke.wireguardautotunnel.ui.common.dropdown.LabelledDropdown
|
||||||
|
import com.zaneschepke.wireguardautotunnel.ui.common.label.GroupLabel
|
||||||
|
import com.zaneschepke.wireguardautotunnel.ui.navigation.Route
|
||||||
|
import com.zaneschepke.wireguardautotunnel.util.extensions.capitalize
|
||||||
import com.zaneschepke.wireguardautotunnel.viewmodel.DnsViewModel
|
import com.zaneschepke.wireguardautotunnel.viewmodel.DnsViewModel
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun DnsSettingsScreen(viewModel: DnsViewModel = hiltViewModel()) {
|
fun DnsSettingsScreen(viewModel: DnsViewModel = hiltViewModel()) {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
|
val navController = LocalNavController.current
|
||||||
val dnsUiState by viewModel.container.stateFlow.collectAsStateWithLifecycle()
|
val dnsUiState by viewModel.container.stateFlow.collectAsStateWithLifecycle()
|
||||||
|
|
||||||
if (dnsUiState.isLoading) return
|
if (dnsUiState.isLoading) return
|
||||||
|
val locale = remember { Locale.getDefault() }
|
||||||
|
|
||||||
Column(
|
Column(
|
||||||
horizontalAlignment = Alignment.Start,
|
horizontalAlignment = Alignment.Start,
|
||||||
verticalArrangement = Arrangement.spacedBy(12.dp, Alignment.Top),
|
verticalArrangement = Arrangement.spacedBy(16.dp, Alignment.Top),
|
||||||
modifier = Modifier.fillMaxSize().verticalScroll(rememberScrollState()),
|
modifier = Modifier.fillMaxSize().verticalScroll(rememberScrollState()),
|
||||||
) {
|
) {
|
||||||
Column {
|
Column {
|
||||||
|
GroupLabel(stringResource(R.string.endpoint), Modifier.padding(horizontal = 16.dp))
|
||||||
LabelledDropdown(
|
LabelledDropdown(
|
||||||
title = stringResource(R.string.dns_protocol),
|
title = stringResource(R.string.dns_protocol),
|
||||||
leading = { Icon(Icons.Outlined.Dns, contentDescription = null) },
|
leading = { Icon(Icons.Outlined.Dns, contentDescription = null) },
|
||||||
@@ -59,5 +73,27 @@ fun DnsSettingsScreen(viewModel: DnsViewModel = hiltViewModel()) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Column {
|
||||||
|
GroupLabel(
|
||||||
|
stringResource(R.string.tunnel).capitalize(locale),
|
||||||
|
Modifier.padding(horizontal = 16.dp),
|
||||||
|
)
|
||||||
|
SurfaceRow(
|
||||||
|
leading = {
|
||||||
|
Icon(ImageVector.vectorResource(R.drawable.host), contentDescription = null)
|
||||||
|
},
|
||||||
|
title = stringResource(R.string.global_dns_servers),
|
||||||
|
trailing = { modifier ->
|
||||||
|
SwitchWithDivider(
|
||||||
|
checked = dnsUiState.dnsSettings.isGlobalTunnelDnsEnabled,
|
||||||
|
onClick = { viewModel.setGlobalTunnelDnsEnabled(it) },
|
||||||
|
modifier = modifier,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
onClick = {
|
||||||
|
dnsUiState.globalConfig?.let { navController.push(Route.ConfigGlobal(it.id)) }
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
-46
@@ -1,46 +0,0 @@
|
|||||||
package com.zaneschepke.wireguardautotunnel.ui.screens.settings.globals
|
|
||||||
|
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
|
||||||
import androidx.compose.foundation.layout.Column
|
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
|
||||||
import androidx.compose.foundation.rememberScrollState
|
|
||||||
import androidx.compose.foundation.verticalScroll
|
|
||||||
import androidx.compose.material.icons.Icons
|
|
||||||
import androidx.compose.material.icons.automirrored.outlined.CallSplit
|
|
||||||
import androidx.compose.material.icons.outlined.Settings
|
|
||||||
import androidx.compose.material3.Icon
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.ui.Alignment
|
|
||||||
import androidx.compose.ui.Modifier
|
|
||||||
import androidx.compose.ui.res.stringResource
|
|
||||||
import androidx.compose.ui.unit.dp
|
|
||||||
import com.zaneschepke.wireguardautotunnel.R
|
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.LocalNavController
|
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.SurfaceRow
|
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.navigation.Route
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun TunnelGlobalsScreen(globalTunnelId: Int) {
|
|
||||||
val navController = LocalNavController.current
|
|
||||||
|
|
||||||
Column(
|
|
||||||
horizontalAlignment = Alignment.Start,
|
|
||||||
verticalArrangement = Arrangement.spacedBy(16.dp, Alignment.Top),
|
|
||||||
modifier = Modifier.verticalScroll(rememberScrollState()).fillMaxSize(),
|
|
||||||
) {
|
|
||||||
Column {
|
|
||||||
SurfaceRow(
|
|
||||||
leading = { Icon(Icons.Outlined.Settings, contentDescription = null) },
|
|
||||||
title = stringResource(R.string.configuration),
|
|
||||||
onClick = { navController.push(Route.ConfigGlobal(globalTunnelId)) },
|
|
||||||
)
|
|
||||||
SurfaceRow(
|
|
||||||
leading = {
|
|
||||||
Icon(Icons.AutoMirrored.Outlined.CallSplit, contentDescription = null)
|
|
||||||
},
|
|
||||||
title = stringResource(R.string.splt_tunneling),
|
|
||||||
onClick = { navController.push(Route.SplitTunnelGlobal(id = globalTunnelId)) },
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+6
-7
@@ -16,7 +16,6 @@ import androidx.compose.material3.Text
|
|||||||
import androidx.compose.runtime.*
|
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.graphics.Color
|
|
||||||
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.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
@@ -25,8 +24,8 @@ import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
|||||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
import com.zaneschepke.wireguardautotunnel.R
|
import com.zaneschepke.wireguardautotunnel.R
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.LocalIsAndroidTV
|
import com.zaneschepke.wireguardautotunnel.ui.LocalIsAndroidTV
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.ScaledSwitch
|
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.SurfaceRow
|
import com.zaneschepke.wireguardautotunnel.ui.common.button.SurfaceRow
|
||||||
|
import com.zaneschepke.wireguardautotunnel.ui.common.button.ThemedSwitch
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.common.functions.rememberClipboardHelper
|
import com.zaneschepke.wireguardautotunnel.ui.common.functions.rememberClipboardHelper
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.common.label.GroupLabel
|
import com.zaneschepke.wireguardautotunnel.ui.common.label.GroupLabel
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.common.security.SecureScreenFromRecording
|
import com.zaneschepke.wireguardautotunnel.ui.common.security.SecureScreenFromRecording
|
||||||
@@ -72,7 +71,7 @@ fun AndroidIntegrationsScreen(viewModel: SettingsViewModel = hiltViewModel()) {
|
|||||||
SurfaceRow(
|
SurfaceRow(
|
||||||
leading = { Icon(Icons.Outlined.VpnLock, contentDescription = null) },
|
leading = { Icon(Icons.Outlined.VpnLock, contentDescription = null) },
|
||||||
trailing = {
|
trailing = {
|
||||||
ScaledSwitch(
|
ThemedSwitch(
|
||||||
checked = isAlwaysOnEnabled,
|
checked = isAlwaysOnEnabled,
|
||||||
onClick = { viewModel.setAlwaysOnVpnEnabled(it) },
|
onClick = { viewModel.setAlwaysOnVpnEnabled(it) },
|
||||||
)
|
)
|
||||||
@@ -94,12 +93,12 @@ fun AndroidIntegrationsScreen(viewModel: SettingsViewModel = hiltViewModel()) {
|
|||||||
Icons.Outlined.Restore,
|
Icons.Outlined.Restore,
|
||||||
contentDescription = null,
|
contentDescription = null,
|
||||||
tint =
|
tint =
|
||||||
if (isAlwaysOnEnabled) Color.Gray
|
if (isAlwaysOnEnabled) MaterialTheme.colorScheme.outline
|
||||||
else MaterialTheme.colorScheme.onSurface,
|
else MaterialTheme.colorScheme.onSurface,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
trailing = {
|
trailing = {
|
||||||
ScaledSwitch(
|
ThemedSwitch(
|
||||||
checked = settingsState.settings.isRestoreOnBootEnabled,
|
checked = settingsState.settings.isRestoreOnBootEnabled,
|
||||||
onClick = { viewModel.setRestoreOnBootEnabled(it) },
|
onClick = { viewModel.setRestoreOnBootEnabled(it) },
|
||||||
enabled = !isAlwaysOnEnabled,
|
enabled = !isAlwaysOnEnabled,
|
||||||
@@ -117,7 +116,7 @@ fun AndroidIntegrationsScreen(viewModel: SettingsViewModel = hiltViewModel()) {
|
|||||||
SurfaceRow(
|
SurfaceRow(
|
||||||
leading = { Icon(Icons.Filled.AppShortcut, contentDescription = null) },
|
leading = { Icon(Icons.Filled.AppShortcut, contentDescription = null) },
|
||||||
trailing = {
|
trailing = {
|
||||||
ScaledSwitch(
|
ThemedSwitch(
|
||||||
checked = settingsState.settings.isShortcutsEnabled,
|
checked = settingsState.settings.isShortcutsEnabled,
|
||||||
onClick = { viewModel.setShortcutsEnabled(it) },
|
onClick = { viewModel.setShortcutsEnabled(it) },
|
||||||
)
|
)
|
||||||
@@ -130,7 +129,7 @@ fun AndroidIntegrationsScreen(viewModel: SettingsViewModel = hiltViewModel()) {
|
|||||||
SurfaceRow(
|
SurfaceRow(
|
||||||
leading = { Icon(Icons.Filled.SmartToy, contentDescription = null) },
|
leading = { Icon(Icons.Filled.SmartToy, contentDescription = null) },
|
||||||
trailing = {
|
trailing = {
|
||||||
ScaledSwitch(
|
ThemedSwitch(
|
||||||
checked = settingsState.isRemoteEnabled,
|
checked = settingsState.isRemoteEnabled,
|
||||||
onClick = { viewModel.setRemoteEnabled(it) },
|
onClick = { viewModel.setRemoteEnabled(it) },
|
||||||
)
|
)
|
||||||
|
|||||||
+123
@@ -0,0 +1,123 @@
|
|||||||
|
package com.zaneschepke.wireguardautotunnel.ui.screens.settings.lockdown
|
||||||
|
|
||||||
|
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.foundation.rememberScrollState
|
||||||
|
import androidx.compose.foundation.verticalScroll
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.outlined.DataUsage
|
||||||
|
import androidx.compose.material.icons.outlined.Lan
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
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.vector.ImageVector
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.res.vectorResource
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||||
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
|
import com.zaneschepke.wireguardautotunnel.R
|
||||||
|
import com.zaneschepke.wireguardautotunnel.ui.LocalSharedVm
|
||||||
|
import com.zaneschepke.wireguardautotunnel.ui.common.button.SurfaceRow
|
||||||
|
import com.zaneschepke.wireguardautotunnel.ui.common.button.ThemedSwitch
|
||||||
|
import com.zaneschepke.wireguardautotunnel.ui.common.dialog.InfoDialog
|
||||||
|
import com.zaneschepke.wireguardautotunnel.ui.common.label.GroupLabel
|
||||||
|
import com.zaneschepke.wireguardautotunnel.ui.common.text.DescriptionText
|
||||||
|
import com.zaneschepke.wireguardautotunnel.ui.sideeffect.LocalSideEffect
|
||||||
|
import com.zaneschepke.wireguardautotunnel.viewmodel.LockdownViewModel
|
||||||
|
import org.orbitmvi.orbit.compose.collectSideEffect
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun LockdownSettingsScreen(viewModel: LockdownViewModel = hiltViewModel()) {
|
||||||
|
|
||||||
|
val sharedViewModel = LocalSharedVm.current
|
||||||
|
|
||||||
|
val uiState by viewModel.container.stateFlow.collectAsStateWithLifecycle()
|
||||||
|
|
||||||
|
if (uiState.isLoading) return
|
||||||
|
|
||||||
|
var metered by remember { mutableStateOf(uiState.lockdownSettings.metered) }
|
||||||
|
var dualStack by remember { mutableStateOf(uiState.lockdownSettings.dualStack) }
|
||||||
|
var bypassLan by remember { mutableStateOf(uiState.lockdownSettings.bypassLan) }
|
||||||
|
|
||||||
|
sharedViewModel.collectSideEffect {
|
||||||
|
if (it is LocalSideEffect.SaveChanges) viewModel.setShowSaveModal(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (uiState.showSaveModal) {
|
||||||
|
InfoDialog(
|
||||||
|
onDismiss = { viewModel.setShowSaveModal(false) },
|
||||||
|
onAttest = {
|
||||||
|
viewModel.setLockdownSettings(
|
||||||
|
uiState.lockdownSettings.copy(
|
||||||
|
metered = metered,
|
||||||
|
dualStack = dualStack,
|
||||||
|
bypassLan = bypassLan,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
},
|
||||||
|
title = stringResource(R.string.save_changes),
|
||||||
|
body = {
|
||||||
|
Text(
|
||||||
|
stringResource(
|
||||||
|
R.string.restart_message_template,
|
||||||
|
stringResource(R.string.kill_switch),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
},
|
||||||
|
confirmText = stringResource(R.string._continue),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Column(
|
||||||
|
horizontalAlignment = Alignment.Start,
|
||||||
|
verticalArrangement = Arrangement.spacedBy(12.dp, Alignment.Top),
|
||||||
|
modifier = Modifier.fillMaxSize().verticalScroll(rememberScrollState()),
|
||||||
|
) {
|
||||||
|
Column {
|
||||||
|
GroupLabel(
|
||||||
|
stringResource(R.string.configuration),
|
||||||
|
modifier = Modifier.padding(horizontal = 16.dp),
|
||||||
|
)
|
||||||
|
SurfaceRow(
|
||||||
|
leading = { Icon(Icons.Outlined.Lan, contentDescription = null) },
|
||||||
|
title = stringResource(R.string.allow_lan_traffic),
|
||||||
|
description = {
|
||||||
|
Text(
|
||||||
|
text = stringResource(R.string.bypass_lan_for_kill_switch),
|
||||||
|
style =
|
||||||
|
MaterialTheme.typography.bodySmall.copy(
|
||||||
|
MaterialTheme.colorScheme.outline
|
||||||
|
),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
trailing = { ThemedSwitch(checked = bypassLan, onClick = { bypassLan = it }) },
|
||||||
|
onClick = { bypassLan = !bypassLan },
|
||||||
|
)
|
||||||
|
SurfaceRow(
|
||||||
|
leading = { Icon(Icons.Outlined.DataUsage, contentDescription = null) },
|
||||||
|
title = stringResource(R.string.metered_tunnel),
|
||||||
|
trailing = { ThemedSwitch(checked = metered, onClick = { metered = it }) },
|
||||||
|
onClick = { metered = !metered },
|
||||||
|
)
|
||||||
|
SurfaceRow(
|
||||||
|
leading = {
|
||||||
|
Icon(ImageVector.vectorResource(R.drawable.host), contentDescription = null)
|
||||||
|
},
|
||||||
|
title = stringResource(R.string.dual_stack),
|
||||||
|
description = { DescriptionText(stringResource(R.string.dual_stack_description)) },
|
||||||
|
trailing = { ThemedSwitch(checked = dualStack, onClick = { dualStack = it }) },
|
||||||
|
onClick = { dualStack = !dualStack },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+2
-2
@@ -21,8 +21,8 @@ import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
|||||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
import com.zaneschepke.wireguardautotunnel.R
|
import com.zaneschepke.wireguardautotunnel.R
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.LocalNavController
|
import com.zaneschepke.wireguardautotunnel.ui.LocalNavController
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.ScaledSwitch
|
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.SurfaceRow
|
import com.zaneschepke.wireguardautotunnel.ui.common.button.SurfaceRow
|
||||||
|
import com.zaneschepke.wireguardautotunnel.ui.common.button.ThemedSwitch
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.common.dropdown.LabelledDropdown
|
import com.zaneschepke.wireguardautotunnel.ui.common.dropdown.LabelledDropdown
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.common.label.GroupLabel
|
import com.zaneschepke.wireguardautotunnel.ui.common.label.GroupLabel
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.navigation.Route
|
import com.zaneschepke.wireguardautotunnel.ui.navigation.Route
|
||||||
@@ -86,7 +86,7 @@ fun TunnelMonitoringScreen(viewModel: MonitoringViewModel = hiltViewModel()) {
|
|||||||
leading = { Icon(Icons.Outlined.QueryStats, contentDescription = null) },
|
leading = { Icon(Icons.Outlined.QueryStats, contentDescription = null) },
|
||||||
title = stringResource(R.string.display_detailed_ping_stats),
|
title = stringResource(R.string.display_detailed_ping_stats),
|
||||||
trailing = {
|
trailing = {
|
||||||
ScaledSwitch(
|
ThemedSwitch(
|
||||||
checked = monitoringUiState.monitoringSettings.showDetailedPingStats,
|
checked = monitoringUiState.monitoringSettings.showDetailedPingStats,
|
||||||
onClick = { viewModel.setDetailedPingStats(it) },
|
onClick = { viewModel.setDetailedPingStats(it) },
|
||||||
)
|
)
|
||||||
|
|||||||
+58
-48
@@ -10,6 +10,7 @@ import androidx.compose.material.icons.outlined.Http
|
|||||||
import androidx.compose.material.icons.outlined.RemoveRedEye
|
import androidx.compose.material.icons.outlined.RemoveRedEye
|
||||||
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.runtime.*
|
import androidx.compose.runtime.*
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
@@ -24,73 +25,84 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
|||||||
import com.zaneschepke.wireguardautotunnel.R
|
import com.zaneschepke.wireguardautotunnel.R
|
||||||
import com.zaneschepke.wireguardautotunnel.domain.model.ProxySettings
|
import com.zaneschepke.wireguardautotunnel.domain.model.ProxySettings
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.LocalSharedVm
|
import com.zaneschepke.wireguardautotunnel.ui.LocalSharedVm
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.ScaledSwitch
|
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.SurfaceRow
|
import com.zaneschepke.wireguardautotunnel.ui.common.button.SurfaceRow
|
||||||
|
import com.zaneschepke.wireguardautotunnel.ui.common.button.ThemedSwitch
|
||||||
|
import com.zaneschepke.wireguardautotunnel.ui.common.dialog.InfoDialog
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.common.label.GroupLabel
|
import com.zaneschepke.wireguardautotunnel.ui.common.label.GroupLabel
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.common.security.SecureScreenFromRecording
|
import com.zaneschepke.wireguardautotunnel.ui.common.security.SecureScreenFromRecording
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.common.textbox.ConfigurationTextBox
|
import com.zaneschepke.wireguardautotunnel.ui.common.textbox.ConfigurationTextBox
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.sideeffect.LocalSideEffect
|
import com.zaneschepke.wireguardautotunnel.ui.sideeffect.LocalSideEffect
|
||||||
import com.zaneschepke.wireguardautotunnel.viewmodel.ProxySettingsViewModel
|
import com.zaneschepke.wireguardautotunnel.viewmodel.ProxySettingsViewModel
|
||||||
|
import java.util.Locale
|
||||||
import org.orbitmvi.orbit.compose.collectSideEffect
|
import org.orbitmvi.orbit.compose.collectSideEffect
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun ProxySettingsScreen(viewModel: ProxySettingsViewModel = hiltViewModel()) {
|
fun ProxySettingsScreen(viewModel: ProxySettingsViewModel = hiltViewModel()) {
|
||||||
val sharedViewModel = LocalSharedVm.current
|
val sharedViewModel = LocalSharedVm.current
|
||||||
val proxySettingsState by viewModel.container.stateFlow.collectAsStateWithLifecycle()
|
|
||||||
|
|
||||||
if (proxySettingsState.isLoading) return
|
val uiState by viewModel.container.stateFlow.collectAsStateWithLifecycle()
|
||||||
|
|
||||||
val proxySettings by
|
if (uiState.isLoading) return
|
||||||
remember(proxySettingsState) { mutableStateOf(proxySettingsState.proxySettings) }
|
|
||||||
|
val locale = remember { Locale.getDefault() }
|
||||||
|
|
||||||
|
val proxySettings by remember(uiState) { mutableStateOf(uiState.proxySettings) }
|
||||||
|
|
||||||
var socks5Enabled by
|
var socks5Enabled by
|
||||||
remember(proxySettings) {
|
remember(proxySettings) { mutableStateOf(uiState.proxySettings.socks5ProxyEnabled) }
|
||||||
mutableStateOf(proxySettingsState.proxySettings.socks5ProxyEnabled)
|
|
||||||
}
|
|
||||||
var httpEnabled by
|
var httpEnabled by
|
||||||
remember(proxySettings) {
|
remember(proxySettings) { mutableStateOf(uiState.proxySettings.httpProxyEnabled) }
|
||||||
mutableStateOf(proxySettingsState.proxySettings.httpProxyEnabled)
|
|
||||||
}
|
|
||||||
var socksBindAddress by
|
var socksBindAddress by
|
||||||
remember(proxySettings) {
|
remember(proxySettings) {
|
||||||
mutableStateOf(proxySettingsState.proxySettings.socks5ProxyBindAddress ?: "")
|
mutableStateOf(uiState.proxySettings.socks5ProxyBindAddress ?: "")
|
||||||
}
|
}
|
||||||
var httpBindAddress by
|
var httpBindAddress by
|
||||||
remember(proxySettings) {
|
remember(proxySettings) { mutableStateOf(uiState.proxySettings.httpProxyBindAddress ?: "") }
|
||||||
mutableStateOf(proxySettingsState.proxySettings.httpProxyBindAddress ?: "")
|
|
||||||
}
|
|
||||||
var proxyUsername by
|
var proxyUsername by
|
||||||
remember(proxySettings) {
|
remember(proxySettings) { mutableStateOf(uiState.proxySettings.proxyUsername ?: "") }
|
||||||
mutableStateOf(proxySettingsState.proxySettings.proxyUsername ?: "")
|
|
||||||
}
|
|
||||||
var proxyPassword by
|
var proxyPassword by
|
||||||
remember(proxySettings) {
|
remember(proxySettings) { mutableStateOf(uiState.proxySettings.proxyPassword ?: "") }
|
||||||
mutableStateOf(proxySettingsState.proxySettings.proxyPassword ?: "")
|
var passwordVisible by remember(proxySettings) { mutableStateOf(uiState.passwordVisible) }
|
||||||
}
|
|
||||||
var passwordVisible by
|
|
||||||
remember(proxySettings) { mutableStateOf(proxySettingsState.passwordVisible) }
|
|
||||||
|
|
||||||
val keyboardController = LocalSoftwareKeyboardController.current
|
val keyboardController = LocalSoftwareKeyboardController.current
|
||||||
|
|
||||||
val keyboardActions = KeyboardActions(onDone = { keyboardController?.hide() })
|
val keyboardActions = KeyboardActions(onDone = { keyboardController?.hide() })
|
||||||
val keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done)
|
val keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done)
|
||||||
|
|
||||||
|
fun saveChanges() {
|
||||||
|
viewModel.save(
|
||||||
|
ProxySettings(
|
||||||
|
socks5ProxyEnabled = socks5Enabled,
|
||||||
|
socks5ProxyBindAddress = socksBindAddress,
|
||||||
|
httpProxyEnabled = httpEnabled,
|
||||||
|
httpProxyBindAddress = httpBindAddress,
|
||||||
|
proxyUsername = proxyUsername,
|
||||||
|
proxyPassword = proxyPassword,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
sharedViewModel.collectSideEffect { sideEffect ->
|
sharedViewModel.collectSideEffect { sideEffect ->
|
||||||
when (sideEffect) {
|
if (sideEffect is LocalSideEffect.SaveChanges) {
|
||||||
LocalSideEffect.SaveChanges -> {
|
if (uiState.activeTuns.isNotEmpty()) viewModel.setShowSaveModal(true) else saveChanges()
|
||||||
viewModel.save(
|
}
|
||||||
ProxySettings(
|
}
|
||||||
socks5ProxyEnabled = socks5Enabled,
|
|
||||||
socks5ProxyBindAddress = socksBindAddress,
|
if (uiState.showSaveModal) {
|
||||||
httpProxyEnabled = httpEnabled,
|
InfoDialog(
|
||||||
httpProxyBindAddress = httpBindAddress,
|
onDismiss = { viewModel.setShowSaveModal(false) },
|
||||||
proxyUsername = proxyUsername,
|
onAttest = { saveChanges() },
|
||||||
proxyPassword = proxyPassword,
|
title = stringResource(R.string.save_changes),
|
||||||
|
body = {
|
||||||
|
Text(
|
||||||
|
stringResource(
|
||||||
|
R.string.restart_message_template,
|
||||||
|
stringResource(R.string.tunnels).lowercase(locale),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
},
|
||||||
else -> Unit
|
confirmText = stringResource(R.string._continue),
|
||||||
}
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
SecureScreenFromRecording()
|
SecureScreenFromRecording()
|
||||||
@@ -105,7 +117,7 @@ fun ProxySettingsScreen(viewModel: ProxySettingsViewModel = hiltViewModel()) {
|
|||||||
leading = { Icon(Icons.Outlined.Forward5, contentDescription = null) },
|
leading = { Icon(Icons.Outlined.Forward5, contentDescription = null) },
|
||||||
title = stringResource(R.string.socks_5_proxy),
|
title = stringResource(R.string.socks_5_proxy),
|
||||||
trailing = {
|
trailing = {
|
||||||
ScaledSwitch(checked = socks5Enabled, onClick = { socks5Enabled = it })
|
ThemedSwitch(checked = socks5Enabled, onClick = { socks5Enabled = it })
|
||||||
},
|
},
|
||||||
onClick = { socks5Enabled = !socks5Enabled },
|
onClick = { socks5Enabled = !socks5Enabled },
|
||||||
)
|
)
|
||||||
@@ -119,10 +131,9 @@ fun ProxySettingsScreen(viewModel: ProxySettingsViewModel = hiltViewModel()) {
|
|||||||
),
|
),
|
||||||
label = stringResource(R.string.socks_5_bind_address),
|
label = stringResource(R.string.socks_5_bind_address),
|
||||||
value = socksBindAddress,
|
value = socksBindAddress,
|
||||||
isError = proxySettingsState.isSocks5BindAddressError,
|
isError = uiState.isSocks5BindAddressError,
|
||||||
onValueChange = {
|
onValueChange = {
|
||||||
if (proxySettingsState.isSocks5BindAddressError)
|
if (uiState.isSocks5BindAddressError) viewModel.clearSocks5BindError()
|
||||||
viewModel.clearSocks5BindError()
|
|
||||||
socksBindAddress = it
|
socksBindAddress = it
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
@@ -132,7 +143,7 @@ fun ProxySettingsScreen(viewModel: ProxySettingsViewModel = hiltViewModel()) {
|
|||||||
SurfaceRow(
|
SurfaceRow(
|
||||||
leading = { Icon(Icons.Outlined.Http, contentDescription = null) },
|
leading = { Icon(Icons.Outlined.Http, contentDescription = null) },
|
||||||
title = stringResource(R.string.http_proxy),
|
title = stringResource(R.string.http_proxy),
|
||||||
trailing = { ScaledSwitch(checked = httpEnabled, onClick = { httpEnabled = it }) },
|
trailing = { ThemedSwitch(checked = httpEnabled, onClick = { httpEnabled = it }) },
|
||||||
onClick = { httpEnabled = !httpEnabled },
|
onClick = { httpEnabled = !httpEnabled },
|
||||||
)
|
)
|
||||||
if (httpEnabled) {
|
if (httpEnabled) {
|
||||||
@@ -144,10 +155,9 @@ fun ProxySettingsScreen(viewModel: ProxySettingsViewModel = hiltViewModel()) {
|
|||||||
),
|
),
|
||||||
label = stringResource(R.string.http_bind_address),
|
label = stringResource(R.string.http_bind_address),
|
||||||
value = httpBindAddress,
|
value = httpBindAddress,
|
||||||
isError = proxySettingsState.isHttpBindAddressError,
|
isError = uiState.isHttpBindAddressError,
|
||||||
onValueChange = {
|
onValueChange = {
|
||||||
if (proxySettingsState.isSocks5BindAddressError)
|
if (uiState.isSocks5BindAddressError) viewModel.clearHttpBindError()
|
||||||
viewModel.clearHttpBindError()
|
|
||||||
httpBindAddress = it
|
httpBindAddress = it
|
||||||
},
|
},
|
||||||
modifier = Modifier.padding(horizontal = 12.dp),
|
modifier = Modifier.padding(horizontal = 12.dp),
|
||||||
@@ -169,11 +179,11 @@ fun ProxySettingsScreen(viewModel: ProxySettingsViewModel = hiltViewModel()) {
|
|||||||
ConfigurationTextBox(
|
ConfigurationTextBox(
|
||||||
value = proxyUsername,
|
value = proxyUsername,
|
||||||
onValueChange = {
|
onValueChange = {
|
||||||
if (proxySettingsState.isUserNameError) viewModel.clearUsernameError()
|
if (uiState.isUserNameError) viewModel.clearUsernameError()
|
||||||
proxyUsername = it
|
proxyUsername = it
|
||||||
},
|
},
|
||||||
label = stringResource(R.string.username),
|
label = stringResource(R.string.username),
|
||||||
isError = proxySettingsState.isUserNameError,
|
isError = uiState.isUserNameError,
|
||||||
hint = "",
|
hint = "",
|
||||||
keyboardActions = keyboardActions,
|
keyboardActions = keyboardActions,
|
||||||
keyboardOptions = keyboardOptions,
|
keyboardOptions = keyboardOptions,
|
||||||
@@ -181,11 +191,11 @@ fun ProxySettingsScreen(viewModel: ProxySettingsViewModel = hiltViewModel()) {
|
|||||||
ConfigurationTextBox(
|
ConfigurationTextBox(
|
||||||
value = proxyPassword,
|
value = proxyPassword,
|
||||||
onValueChange = {
|
onValueChange = {
|
||||||
if (proxySettingsState.isUserNameError) viewModel.clearPasswordError()
|
if (uiState.isUserNameError) viewModel.clearPasswordError()
|
||||||
proxyPassword = it
|
proxyPassword = it
|
||||||
},
|
},
|
||||||
label = stringResource(R.string.password),
|
label = stringResource(R.string.password),
|
||||||
isError = proxySettingsState.isPasswordError,
|
isError = uiState.isPasswordError,
|
||||||
hint = "",
|
hint = "",
|
||||||
keyboardActions = keyboardActions,
|
keyboardActions = keyboardActions,
|
||||||
keyboardOptions = keyboardOptions,
|
keyboardOptions = keyboardOptions,
|
||||||
|
|||||||
+17
-12
@@ -3,6 +3,7 @@ package com.zaneschepke.wireguardautotunnel.ui.screens.settings.proxy.compoents
|
|||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import com.zaneschepke.wireguardautotunnel.data.model.AppMode
|
import com.zaneschepke.wireguardautotunnel.data.model.AppMode
|
||||||
|
import com.zaneschepke.wireguardautotunnel.ui.LocalIsAndroidTV
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.common.sheet.CustomBottomSheet
|
import com.zaneschepke.wireguardautotunnel.ui.common.sheet.CustomBottomSheet
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.common.sheet.SheetOption
|
import com.zaneschepke.wireguardautotunnel.ui.common.sheet.SheetOption
|
||||||
import com.zaneschepke.wireguardautotunnel.util.extensions.asIcon
|
import com.zaneschepke.wireguardautotunnel.util.extensions.asIcon
|
||||||
@@ -15,19 +16,23 @@ fun AppModeBottomSheet(
|
|||||||
onDismiss: () -> Unit,
|
onDismiss: () -> Unit,
|
||||||
) {
|
) {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
|
val isTv = LocalIsAndroidTV.current
|
||||||
|
|
||||||
CustomBottomSheet(
|
CustomBottomSheet(
|
||||||
enumValues<AppMode>().map {
|
enumValues<AppMode>()
|
||||||
val icon = it.asIcon()
|
.filterNot { isTv && it == AppMode.KERNEL }
|
||||||
SheetOption(
|
.map {
|
||||||
icon,
|
val icon = it.asIcon()
|
||||||
label = it.asTitleString(context),
|
SheetOption(
|
||||||
onClick = {
|
icon,
|
||||||
onDismiss()
|
label = it.asTitleString(context),
|
||||||
onAppModeChange(it)
|
onClick = {
|
||||||
},
|
onDismiss()
|
||||||
selected = appMode == it,
|
onAppModeChange(it)
|
||||||
)
|
},
|
||||||
}
|
selected = appMode == it,
|
||||||
|
)
|
||||||
|
}
|
||||||
) {
|
) {
|
||||||
onDismiss()
|
onDismiss()
|
||||||
}
|
}
|
||||||
|
|||||||
+10
-3
@@ -73,6 +73,11 @@ fun SupportScreen(viewModel: SupportViewModel = hiltViewModel()) {
|
|||||||
)
|
)
|
||||||
Column {
|
Column {
|
||||||
GroupLabel(stringResource(R.string.resources), Modifier.padding(horizontal = 16.dp))
|
GroupLabel(stringResource(R.string.resources), Modifier.padding(horizontal = 16.dp))
|
||||||
|
SurfaceRow(
|
||||||
|
leading = { Icon(Icons.Outlined.Favorite, contentDescription = null) },
|
||||||
|
title = stringResource(R.string.donate),
|
||||||
|
onClick = { navController.push(Route.Donate) },
|
||||||
|
)
|
||||||
SurfaceRow(
|
SurfaceRow(
|
||||||
stringResource(R.string.docs_description),
|
stringResource(R.string.docs_description),
|
||||||
onClick = { context.openWebUrl(context.getString(R.string.docs_url)) },
|
onClick = { context.openWebUrl(context.getString(R.string.docs_url)) },
|
||||||
@@ -86,9 +91,11 @@ fun SupportScreen(viewModel: SupportViewModel = hiltViewModel()) {
|
|||||||
trailing = { Icon(Icons.AutoMirrored.Outlined.Launch, null) },
|
trailing = { Icon(Icons.AutoMirrored.Outlined.Launch, null) },
|
||||||
)
|
)
|
||||||
SurfaceRow(
|
SurfaceRow(
|
||||||
leading = { Icon(Icons.Outlined.Favorite, contentDescription = null) },
|
stringResource(R.string.translation),
|
||||||
title = stringResource(R.string.donate),
|
onClick = { context.openWebUrl(context.getString(R.string.translation_url)) },
|
||||||
onClick = { navController.push(Route.Donate) },
|
description = { DescriptionText(stringResource(R.string.help_translate)) },
|
||||||
|
leading = { Icon(Icons.Outlined.Translate, contentDescription = null) },
|
||||||
|
trailing = { Icon(Icons.AutoMirrored.Outlined.Launch, null) },
|
||||||
)
|
)
|
||||||
SurfaceRow(
|
SurfaceRow(
|
||||||
leading = { Icon(Icons.Outlined.Balance, contentDescription = null) },
|
leading = { Icon(Icons.Outlined.Balance, contentDescription = null) },
|
||||||
|
|||||||
+2
-2
@@ -16,8 +16,8 @@ fun PermissionDialog(context: Context, onDismiss: () -> Unit) {
|
|||||||
context.requestInstallPackagesPermission()
|
context.requestInstallPackagesPermission()
|
||||||
onDismiss()
|
onDismiss()
|
||||||
},
|
},
|
||||||
title = { Text(stringResource(R.string.permission_required)) },
|
title = stringResource(R.string.permission_required),
|
||||||
body = { Text(stringResource(R.string.install_updated_permission)) },
|
body = { Text(stringResource(R.string.install_updated_permission)) },
|
||||||
confirmText = { Text(stringResource(R.string.allow)) },
|
confirmText = stringResource(R.string.allow),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
+5
-9
@@ -46,7 +46,7 @@ fun UpdateDialog(viewModel: SupportViewModel, context: Context, onPermissionNeed
|
|||||||
onPermissionNeeded()
|
onPermissionNeeded()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
title = { Text(stringResource(R.string.update_available)) },
|
title = stringResource(R.string.update_available),
|
||||||
body = {
|
body = {
|
||||||
Column(
|
Column(
|
||||||
horizontalAlignment = Alignment.Start,
|
horizontalAlignment = Alignment.Start,
|
||||||
@@ -77,7 +77,7 @@ fun UpdateDialog(viewModel: SupportViewModel, context: Context, onPermissionNeed
|
|||||||
|
|
||||||
Text(text = annotatedString)
|
Text(text = annotatedString)
|
||||||
if (supportState.isLoading) {
|
if (supportState.isLoading) {
|
||||||
val stroke = Stroke(cap = StrokeCap.Round)
|
val stroke = Stroke(cap = StrokeCap.Round, width = 4.0f)
|
||||||
LinearWavyProgressIndicator(
|
LinearWavyProgressIndicator(
|
||||||
progress = { supportState.downloadProgress },
|
progress = { supportState.downloadProgress },
|
||||||
modifier = Modifier.fillMaxWidth().padding(top = 16.dp),
|
modifier = Modifier.fillMaxWidth().padding(top = 16.dp),
|
||||||
@@ -89,12 +89,8 @@ fun UpdateDialog(viewModel: SupportViewModel, context: Context, onPermissionNeed
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
confirmText = {
|
confirmText =
|
||||||
Text(
|
if (BuildConfig.FLAVOR != Constants.STANDALONE_FLAVOR) stringResource(R.string.download)
|
||||||
if (BuildConfig.FLAVOR != Constants.STANDALONE_FLAVOR)
|
else stringResource(R.string.download_and_install),
|
||||||
stringResource(R.string.download)
|
|
||||||
else stringResource(R.string.download_and_install)
|
|
||||||
)
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
+30
-1
@@ -6,8 +6,10 @@ import androidx.compose.foundation.verticalScroll
|
|||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.automirrored.outlined.Launch
|
import androidx.compose.material.icons.automirrored.outlined.Launch
|
||||||
import androidx.compose.material.icons.outlined.CurrencyBitcoin
|
import androidx.compose.material.icons.outlined.CurrencyBitcoin
|
||||||
|
import androidx.compose.material.icons.outlined.Done
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
@@ -16,19 +18,26 @@ import androidx.compose.ui.platform.LocalContext
|
|||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.res.vectorResource
|
import androidx.compose.ui.res.vectorResource
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||||
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
import com.zaneschepke.wireguardautotunnel.BuildConfig
|
import com.zaneschepke.wireguardautotunnel.BuildConfig
|
||||||
import com.zaneschepke.wireguardautotunnel.R
|
import com.zaneschepke.wireguardautotunnel.R
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.LocalNavController
|
import com.zaneschepke.wireguardautotunnel.ui.LocalNavController
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.SurfaceRow
|
import com.zaneschepke.wireguardautotunnel.ui.common.button.SurfaceRow
|
||||||
|
import com.zaneschepke.wireguardautotunnel.ui.common.button.ThemedSwitch
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.common.label.GroupLabel
|
import com.zaneschepke.wireguardautotunnel.ui.common.label.GroupLabel
|
||||||
|
import com.zaneschepke.wireguardautotunnel.ui.common.text.DescriptionText
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.navigation.Route
|
import com.zaneschepke.wireguardautotunnel.ui.navigation.Route
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.screens.support.donate.components.DonationHeroSection
|
import com.zaneschepke.wireguardautotunnel.ui.screens.support.donate.components.DonationHeroSection
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.screens.support.donate.components.GoogleDonationMessage
|
import com.zaneschepke.wireguardautotunnel.ui.screens.support.donate.components.GoogleDonationMessage
|
||||||
import com.zaneschepke.wireguardautotunnel.util.Constants
|
import com.zaneschepke.wireguardautotunnel.util.Constants
|
||||||
import com.zaneschepke.wireguardautotunnel.util.extensions.openWebUrl
|
import com.zaneschepke.wireguardautotunnel.util.extensions.openWebUrl
|
||||||
|
import com.zaneschepke.wireguardautotunnel.viewmodel.SettingsViewModel
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun DonateScreen() {
|
fun DonateScreen(viewModel: SettingsViewModel = hiltViewModel()) {
|
||||||
|
val uiState by viewModel.container.stateFlow.collectAsStateWithLifecycle()
|
||||||
|
if (uiState.isLoading) return
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
val navController = LocalNavController.current
|
val navController = LocalNavController.current
|
||||||
val isGoogleFlavor = remember { BuildConfig.FLAVOR == Constants.GOOGLE_PLAY_FLAVOR }
|
val isGoogleFlavor = remember { BuildConfig.FLAVOR == Constants.GOOGLE_PLAY_FLAVOR }
|
||||||
@@ -91,6 +100,26 @@ fun DonateScreen() {
|
|||||||
} else {
|
} else {
|
||||||
GoogleDonationMessage()
|
GoogleDonationMessage()
|
||||||
}
|
}
|
||||||
|
SurfaceRow(
|
||||||
|
leading = {
|
||||||
|
Icon(
|
||||||
|
Icons.Outlined.Done,
|
||||||
|
contentDescription = null,
|
||||||
|
modifier = Modifier.size(24.dp),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
title = stringResource(R.string.already_donated),
|
||||||
|
description = {
|
||||||
|
DescriptionText(stringResource(R.string.already_donated_description))
|
||||||
|
},
|
||||||
|
trailing = {
|
||||||
|
ThemedSwitch(
|
||||||
|
checked = uiState.settings.alreadyDonated,
|
||||||
|
onClick = { viewModel.setAlreadyDonated(it) },
|
||||||
|
)
|
||||||
|
},
|
||||||
|
onClick = { viewModel.setAlreadyDonated(!uiState.settings.alreadyDonated) },
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-2
@@ -12,6 +12,7 @@ import androidx.compose.runtime.setValue
|
|||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
import com.journeyapps.barcodescanner.ScanContract
|
import com.journeyapps.barcodescanner.ScanContract
|
||||||
import com.journeyapps.barcodescanner.ScanOptions
|
import com.journeyapps.barcodescanner.ScanOptions
|
||||||
import com.zaneschepke.wireguardautotunnel.R
|
import com.zaneschepke.wireguardautotunnel.R
|
||||||
@@ -94,9 +95,9 @@ fun TunnelsScreen() {
|
|||||||
viewModel.deleteSelectedTunnels()
|
viewModel.deleteSelectedTunnels()
|
||||||
showDeleteModal = false
|
showDeleteModal = false
|
||||||
},
|
},
|
||||||
title = { Text(text = stringResource(R.string.delete_tunnel)) },
|
title = stringResource(R.string.delete_tunnel),
|
||||||
body = { Text(text = stringResource(R.string.delete_tunnel_message)) },
|
body = { Text(text = stringResource(R.string.delete_tunnel_message)) },
|
||||||
confirmText = { Text(text = stringResource(R.string.yes)) },
|
confirmText = stringResource(R.string.yes),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -94,7 +94,7 @@ fun TunnelList(
|
|||||||
if (sharedState.selectedTunnels.isNotEmpty()) {
|
if (sharedState.selectedTunnels.isNotEmpty()) {
|
||||||
viewModel.toggleSelectedTunnel(tunnel.id)
|
viewModel.toggleSelectedTunnel(tunnel.id)
|
||||||
} else {
|
} else {
|
||||||
navController.push(Route.TunnelOptions(tunnel.id))
|
navController.push(Route.TunnelSettings(tunnel.id))
|
||||||
viewModel.clearSelectedTunnels()
|
viewModel.clearSelectedTunnels()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
+32
-10
@@ -5,14 +5,18 @@ import androidx.compose.foundation.layout.Column
|
|||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.rememberScrollState
|
import androidx.compose.foundation.rememberScrollState
|
||||||
import androidx.compose.foundation.verticalScroll
|
import androidx.compose.foundation.verticalScroll
|
||||||
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.*
|
import androidx.compose.runtime.*
|
||||||
import androidx.compose.runtime.saveable.rememberSaveable
|
import androidx.compose.runtime.saveable.rememberSaveable
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
|
import com.zaneschepke.wireguardautotunnel.R
|
||||||
import com.zaneschepke.wireguardautotunnel.data.entity.TunnelConfig
|
import com.zaneschepke.wireguardautotunnel.data.entity.TunnelConfig
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.LocalSharedVm
|
import com.zaneschepke.wireguardautotunnel.ui.LocalSharedVm
|
||||||
|
import com.zaneschepke.wireguardautotunnel.ui.common.dialog.InfoDialog
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.common.security.SecureScreenFromRecording
|
import com.zaneschepke.wireguardautotunnel.ui.common.security.SecureScreenFromRecording
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.screens.tunnels.config.components.AddPeerButton
|
import com.zaneschepke.wireguardautotunnel.ui.screens.tunnels.config.components.AddPeerButton
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.screens.tunnels.config.components.InterfaceSection
|
import com.zaneschepke.wireguardautotunnel.ui.screens.tunnels.config.components.InterfaceSection
|
||||||
@@ -21,33 +25,50 @@ import com.zaneschepke.wireguardautotunnel.ui.sideeffect.LocalSideEffect
|
|||||||
import com.zaneschepke.wireguardautotunnel.ui.state.ConfigProxy
|
import com.zaneschepke.wireguardautotunnel.ui.state.ConfigProxy
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.state.PeerProxy
|
import com.zaneschepke.wireguardautotunnel.ui.state.PeerProxy
|
||||||
import com.zaneschepke.wireguardautotunnel.viewmodel.ConfigViewModel
|
import com.zaneschepke.wireguardautotunnel.viewmodel.ConfigViewModel
|
||||||
|
import java.util.Locale
|
||||||
import org.orbitmvi.orbit.compose.collectSideEffect
|
import org.orbitmvi.orbit.compose.collectSideEffect
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun ConfigScreen(viewModel: ConfigViewModel) {
|
fun ConfigScreen(viewModel: ConfigViewModel) {
|
||||||
val sharedViewModel = LocalSharedVm.current
|
val sharedViewModel = LocalSharedVm.current
|
||||||
|
|
||||||
val configUiState by viewModel.container.stateFlow.collectAsStateWithLifecycle()
|
val uiState by viewModel.container.stateFlow.collectAsStateWithLifecycle()
|
||||||
|
|
||||||
if (configUiState.isLoading) return
|
if (uiState.isLoading) return
|
||||||
|
|
||||||
|
val locale = remember { Locale.getDefault() }
|
||||||
|
|
||||||
var configProxy by remember {
|
var configProxy by remember {
|
||||||
mutableStateOf(
|
mutableStateOf(uiState.tunnel?.let { ConfigProxy.from(it.toAmConfig()) } ?: ConfigProxy())
|
||||||
configUiState.tunnel?.let { ConfigProxy.from(it.toAmConfig()) } ?: ConfigProxy()
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var tunnelName by remember { mutableStateOf(configUiState.tunnel?.name ?: "") }
|
var tunnelName by remember { mutableStateOf(uiState.tunnel?.name ?: "") }
|
||||||
val isGlobalConfig = rememberSaveable { tunnelName == TunnelConfig.GLOBAL_CONFIG_NAME }
|
val isGlobalConfig = rememberSaveable { tunnelName == TunnelConfig.GLOBAL_CONFIG_NAME }
|
||||||
|
|
||||||
val isTunnelNameTaken by
|
val isTunnelNameTaken by
|
||||||
remember(tunnelName) {
|
remember(tunnelName) { derivedStateOf { uiState.unavailableNames.contains(tunnelName) } }
|
||||||
derivedStateOf { configUiState.unavailableNames.contains(tunnelName) }
|
|
||||||
}
|
|
||||||
|
|
||||||
sharedViewModel.collectSideEffect { sideEffect ->
|
sharedViewModel.collectSideEffect { sideEffect ->
|
||||||
if (sideEffect is LocalSideEffect.SaveChanges)
|
if (sideEffect is LocalSideEffect.SaveChanges)
|
||||||
viewModel.saveConfigProxy(configProxy, tunnelName)
|
if (uiState.isRunning) viewModel.setShowSaveModal(true)
|
||||||
|
else viewModel.saveConfigProxy(configProxy, tunnelName)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (uiState.showSaveModal) {
|
||||||
|
InfoDialog(
|
||||||
|
onDismiss = { viewModel.setShowSaveModal(false) },
|
||||||
|
onAttest = { viewModel.saveConfigProxy(configProxy, tunnelName) },
|
||||||
|
title = stringResource(R.string.save_changes),
|
||||||
|
body = {
|
||||||
|
Text(
|
||||||
|
stringResource(
|
||||||
|
R.string.restart_message_template,
|
||||||
|
stringResource(R.string.tunnels).lowercase(locale),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
},
|
||||||
|
confirmText = stringResource(R.string._continue),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
SecureScreenFromRecording()
|
SecureScreenFromRecording()
|
||||||
@@ -60,6 +81,7 @@ fun ConfigScreen(viewModel: ConfigViewModel) {
|
|||||||
InterfaceSection(
|
InterfaceSection(
|
||||||
isGlobalConfig,
|
isGlobalConfig,
|
||||||
configProxy = configProxy,
|
configProxy = configProxy,
|
||||||
|
uiState.isRunning,
|
||||||
tunnelName,
|
tunnelName,
|
||||||
isTunnelNameTaken,
|
isTunnelNameTaken,
|
||||||
onInterfaceChange = { configProxy = configProxy.copy(`interface` = it) },
|
onInterfaceChange = { configProxy = configProxy.copy(`interface` = it) },
|
||||||
|
|||||||
+9
-8
@@ -165,14 +165,15 @@ fun InterfaceFields(
|
|||||||
.lowercase(locale),
|
.lowercase(locale),
|
||||||
modifier = Modifier.weight(3f),
|
modifier = Modifier.weight(3f),
|
||||||
)
|
)
|
||||||
ConfigurationTextBox(
|
if (!isGlobalConfig)
|
||||||
value = interfaceState.mtu,
|
ConfigurationTextBox(
|
||||||
onValueChange = { onInterfaceChange(interfaceState.copy(mtu = it)) },
|
value = interfaceState.mtu,
|
||||||
label = stringResource(R.string.mtu),
|
onValueChange = { onInterfaceChange(interfaceState.copy(mtu = it)) },
|
||||||
hint = stringResource(R.string.auto).lowercase(locale),
|
label = stringResource(R.string.mtu),
|
||||||
modifier = Modifier.weight(2f),
|
hint = stringResource(R.string.auto).lowercase(locale),
|
||||||
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number),
|
modifier = Modifier.weight(2f),
|
||||||
)
|
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
if (showScripts) {
|
if (showScripts) {
|
||||||
ConfigurationTextBox(
|
ConfigurationTextBox(
|
||||||
|
|||||||
+66
-53
@@ -18,6 +18,7 @@ import com.wireguard.crypto.KeyPair
|
|||||||
import com.zaneschepke.wireguardautotunnel.R
|
import com.zaneschepke.wireguardautotunnel.R
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.LocalIsAndroidTV
|
import com.zaneschepke.wireguardautotunnel.ui.LocalIsAndroidTV
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.common.label.GroupLabel
|
import com.zaneschepke.wireguardautotunnel.ui.common.label.GroupLabel
|
||||||
|
import com.zaneschepke.wireguardautotunnel.ui.common.text.DescriptionText
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.common.textbox.ConfigurationTextBox
|
import com.zaneschepke.wireguardautotunnel.ui.common.textbox.ConfigurationTextBox
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.state.ConfigProxy
|
import com.zaneschepke.wireguardautotunnel.ui.state.ConfigProxy
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.state.InterfaceProxy
|
import com.zaneschepke.wireguardautotunnel.ui.state.InterfaceProxy
|
||||||
@@ -27,6 +28,7 @@ import java.util.*
|
|||||||
fun InterfaceSection(
|
fun InterfaceSection(
|
||||||
isGlobalConfig: Boolean,
|
isGlobalConfig: Boolean,
|
||||||
configProxy: ConfigProxy,
|
configProxy: ConfigProxy,
|
||||||
|
isRunning: Boolean,
|
||||||
tunnelName: String,
|
tunnelName: String,
|
||||||
isTunnelNameTaken: Boolean,
|
isTunnelNameTaken: Boolean,
|
||||||
onInterfaceChange: (InterfaceProxy) -> Unit,
|
onInterfaceChange: (InterfaceProxy) -> Unit,
|
||||||
@@ -64,60 +66,62 @@ fun InterfaceSection(
|
|||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
) {
|
) {
|
||||||
GroupLabel(
|
if (!isGlobalConfig)
|
||||||
stringResource(R.string.interface_),
|
GroupLabel(
|
||||||
modifier = Modifier.padding(horizontal = 16.dp),
|
stringResource(R.string.interface_),
|
||||||
)
|
modifier = Modifier.padding(horizontal = 16.dp),
|
||||||
Row {
|
|
||||||
if (isTv && !isGlobalConfig) {
|
|
||||||
IconButton(onClick = { showPrivateKey = !showPrivateKey }) {
|
|
||||||
Icon(
|
|
||||||
Icons.Outlined.RemoveRedEye,
|
|
||||||
stringResource(R.string.show_password),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
IconButton(
|
|
||||||
enabled = true,
|
|
||||||
onClick = {
|
|
||||||
val keypair = KeyPair()
|
|
||||||
onInterfaceChange(
|
|
||||||
configProxy.`interface`.copy(
|
|
||||||
privateKey = keypair.privateKey.toBase64(),
|
|
||||||
publicKey = keypair.publicKey.toBase64(),
|
|
||||||
)
|
|
||||||
)
|
|
||||||
},
|
|
||||||
) {
|
|
||||||
Icon(
|
|
||||||
Icons.Rounded.Refresh,
|
|
||||||
stringResource(R.string.rotate_keys),
|
|
||||||
tint = MaterialTheme.colorScheme.onSurface,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
InterfaceDropdown(
|
|
||||||
expanded = isDropDownExpanded,
|
|
||||||
onExpandedChange = { isDropDownExpanded = it },
|
|
||||||
showScripts = showScripts,
|
|
||||||
showAmneziaValues = showAmneziaValues,
|
|
||||||
isAmneziaCompatibilitySet = isAmneziaCompatibilitySet,
|
|
||||||
onToggleScripts = { showScripts = !showScripts },
|
|
||||||
onToggleAmneziaValues = { showAmneziaValues = !showAmneziaValues },
|
|
||||||
onToggleAmneziaCompatibility = { toggleAmneziaCompat() },
|
|
||||||
onMimicQuic = {
|
|
||||||
showAmneziaValues = true
|
|
||||||
onMimicQuic()
|
|
||||||
},
|
|
||||||
onMimicDns = {
|
|
||||||
showAmneziaValues = true
|
|
||||||
onMimicDns()
|
|
||||||
},
|
|
||||||
onMimicSip = {
|
|
||||||
showAmneziaValues = true
|
|
||||||
onMimicSip()
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
}
|
if (!isGlobalConfig)
|
||||||
|
Row {
|
||||||
|
if (isTv) {
|
||||||
|
IconButton(onClick = { showPrivateKey = !showPrivateKey }) {
|
||||||
|
Icon(
|
||||||
|
Icons.Outlined.RemoveRedEye,
|
||||||
|
stringResource(R.string.show_password),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
IconButton(
|
||||||
|
enabled = true,
|
||||||
|
onClick = {
|
||||||
|
val keypair = KeyPair()
|
||||||
|
onInterfaceChange(
|
||||||
|
configProxy.`interface`.copy(
|
||||||
|
privateKey = keypair.privateKey.toBase64(),
|
||||||
|
publicKey = keypair.publicKey.toBase64(),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
Icons.Rounded.Refresh,
|
||||||
|
stringResource(R.string.rotate_keys),
|
||||||
|
tint = MaterialTheme.colorScheme.onSurface,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
InterfaceDropdown(
|
||||||
|
expanded = isDropDownExpanded,
|
||||||
|
onExpandedChange = { isDropDownExpanded = it },
|
||||||
|
showScripts = showScripts,
|
||||||
|
showAmneziaValues = showAmneziaValues,
|
||||||
|
isAmneziaCompatibilitySet = isAmneziaCompatibilitySet,
|
||||||
|
onToggleScripts = { showScripts = !showScripts },
|
||||||
|
onToggleAmneziaValues = { showAmneziaValues = !showAmneziaValues },
|
||||||
|
onToggleAmneziaCompatibility = { toggleAmneziaCompat() },
|
||||||
|
onMimicQuic = {
|
||||||
|
showAmneziaValues = true
|
||||||
|
onMimicQuic()
|
||||||
|
},
|
||||||
|
onMimicDns = {
|
||||||
|
showAmneziaValues = true
|
||||||
|
onMimicDns()
|
||||||
|
},
|
||||||
|
onMimicSip = {
|
||||||
|
showAmneziaValues = true
|
||||||
|
onMimicSip()
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Column(
|
Column(
|
||||||
verticalArrangement = Arrangement.spacedBy(12.dp),
|
verticalArrangement = Arrangement.spacedBy(12.dp),
|
||||||
@@ -126,9 +130,18 @@ fun InterfaceSection(
|
|||||||
if (!isGlobalConfig)
|
if (!isGlobalConfig)
|
||||||
ConfigurationTextBox(
|
ConfigurationTextBox(
|
||||||
value = tunnelName,
|
value = tunnelName,
|
||||||
|
enabled = !isRunning,
|
||||||
onValueChange = onTunnelNameChange,
|
onValueChange = onTunnelNameChange,
|
||||||
label = stringResource(R.string.name),
|
label = stringResource(R.string.name),
|
||||||
isError = isTunnelNameTaken,
|
isError = isTunnelNameTaken,
|
||||||
|
supportingText =
|
||||||
|
if (isRunning) {
|
||||||
|
{
|
||||||
|
DescriptionText(
|
||||||
|
stringResource(R.string.tunnel_running_name_message)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else null,
|
||||||
hint =
|
hint =
|
||||||
stringResource(
|
stringResource(
|
||||||
R.string.hint_template,
|
R.string.hint_template,
|
||||||
|
|||||||
+60
-10
@@ -1,4 +1,4 @@
|
|||||||
package com.zaneschepke.wireguardautotunnel.ui.screens.tunnels.tunneloptions
|
package com.zaneschepke.wireguardautotunnel.ui.screens.tunnels.settings
|
||||||
|
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
@@ -8,6 +8,7 @@ import androidx.compose.foundation.rememberScrollState
|
|||||||
import androidx.compose.foundation.verticalScroll
|
import androidx.compose.foundation.verticalScroll
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.automirrored.outlined.CallSplit
|
import androidx.compose.material.icons.automirrored.outlined.CallSplit
|
||||||
|
import androidx.compose.material.icons.outlined.DataUsage
|
||||||
import androidx.compose.material.icons.outlined.Dns
|
import androidx.compose.material.icons.outlined.Dns
|
||||||
import androidx.compose.material.icons.outlined.Star
|
import androidx.compose.material.icons.outlined.Star
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
@@ -28,21 +29,24 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
|||||||
import com.zaneschepke.wireguardautotunnel.R
|
import com.zaneschepke.wireguardautotunnel.R
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.LocalNavController
|
import com.zaneschepke.wireguardautotunnel.ui.LocalNavController
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.LocalSharedVm
|
import com.zaneschepke.wireguardautotunnel.ui.LocalSharedVm
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.ScaledSwitch
|
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.SurfaceRow
|
import com.zaneschepke.wireguardautotunnel.ui.common.button.SurfaceRow
|
||||||
|
import com.zaneschepke.wireguardautotunnel.ui.common.button.ThemedSwitch
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.common.label.GroupLabel
|
import com.zaneschepke.wireguardautotunnel.ui.common.label.GroupLabel
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.common.text.DescriptionText
|
import com.zaneschepke.wireguardautotunnel.ui.common.text.DescriptionText
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.navigation.Route
|
import com.zaneschepke.wireguardautotunnel.ui.navigation.Route
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.screens.tunnels.tunneloptions.components.QrCodeDialog
|
import com.zaneschepke.wireguardautotunnel.ui.screens.tunnels.settings.components.QrCodeDialog
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.sideeffect.LocalSideEffect
|
import com.zaneschepke.wireguardautotunnel.ui.sideeffect.LocalSideEffect
|
||||||
|
import com.zaneschepke.wireguardautotunnel.ui.theme.Disabled
|
||||||
import com.zaneschepke.wireguardautotunnel.viewmodel.TunnelViewModel
|
import com.zaneschepke.wireguardautotunnel.viewmodel.TunnelViewModel
|
||||||
import org.orbitmvi.orbit.compose.collectSideEffect
|
import org.orbitmvi.orbit.compose.collectSideEffect
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun TunnelOptionsScreen(viewModel: TunnelViewModel) {
|
fun TunnelSettingsScreen(viewModel: TunnelViewModel) {
|
||||||
val navController = LocalNavController.current
|
val navController = LocalNavController.current
|
||||||
val sharedViewModel = LocalSharedVm.current
|
val sharedViewModel = LocalSharedVm.current
|
||||||
|
|
||||||
|
val sharedUiState by sharedViewModel.container.stateFlow.collectAsStateWithLifecycle()
|
||||||
|
|
||||||
val tunnelState by viewModel.container.stateFlow.collectAsStateWithLifecycle()
|
val tunnelState by viewModel.container.stateFlow.collectAsStateWithLifecycle()
|
||||||
|
|
||||||
if (tunnelState.isLoading) return
|
if (tunnelState.isLoading) return
|
||||||
@@ -81,7 +85,7 @@ fun TunnelOptionsScreen(viewModel: TunnelViewModel) {
|
|||||||
)
|
)
|
||||||
},
|
},
|
||||||
trailing = {
|
trailing = {
|
||||||
ScaledSwitch(
|
ThemedSwitch(
|
||||||
checked = tunnel.isPrimaryTunnel,
|
checked = tunnel.isPrimaryTunnel,
|
||||||
onClick = { viewModel.togglePrimaryTunnel() },
|
onClick = { viewModel.togglePrimaryTunnel() },
|
||||||
)
|
)
|
||||||
@@ -90,9 +94,25 @@ fun TunnelOptionsScreen(viewModel: TunnelViewModel) {
|
|||||||
)
|
)
|
||||||
SurfaceRow(
|
SurfaceRow(
|
||||||
leading = {
|
leading = {
|
||||||
Icon(Icons.AutoMirrored.Outlined.CallSplit, contentDescription = null)
|
Icon(
|
||||||
|
Icons.AutoMirrored.Outlined.CallSplit,
|
||||||
|
contentDescription = null,
|
||||||
|
tint =
|
||||||
|
if (sharedUiState.proxyEnabled) Disabled
|
||||||
|
else MaterialTheme.colorScheme.onSurface,
|
||||||
|
)
|
||||||
},
|
},
|
||||||
|
enabled = !sharedUiState.proxyEnabled,
|
||||||
title = stringResource(R.string.splt_tunneling),
|
title = stringResource(R.string.splt_tunneling),
|
||||||
|
description =
|
||||||
|
if (sharedUiState.proxyEnabled) {
|
||||||
|
{
|
||||||
|
DescriptionText(
|
||||||
|
stringResource(R.string.unavailable_in_mode),
|
||||||
|
disabled = true,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else null,
|
||||||
onClick = { navController.push(Route.SplitTunnel(id = tunnel.id)) },
|
onClick = { navController.push(Route.SplitTunnel(id = tunnel.id)) },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -108,7 +128,7 @@ fun TunnelOptionsScreen(viewModel: TunnelViewModel) {
|
|||||||
DescriptionText(stringResource(R.string.ddns_auto_update_description))
|
DescriptionText(stringResource(R.string.ddns_auto_update_description))
|
||||||
},
|
},
|
||||||
trailing = {
|
trailing = {
|
||||||
ScaledSwitch(
|
ThemedSwitch(
|
||||||
checked = tunnel.restartOnPingFailure,
|
checked = tunnel.restartOnPingFailure,
|
||||||
onClick = { viewModel.setRestartOnPing(it) },
|
onClick = { viewModel.setRestartOnPing(it) },
|
||||||
)
|
)
|
||||||
@@ -121,12 +141,42 @@ fun TunnelOptionsScreen(viewModel: TunnelViewModel) {
|
|||||||
},
|
},
|
||||||
title = stringResource(R.string.prefer_ipv6_resolution),
|
title = stringResource(R.string.prefer_ipv6_resolution),
|
||||||
trailing = {
|
trailing = {
|
||||||
ScaledSwitch(
|
ThemedSwitch(
|
||||||
checked = !tunnel.isIpv4Preferred,
|
checked = !tunnel.isIpv4Preferred,
|
||||||
onClick = { viewModel.toggleIpv4Preferred() },
|
onClick = { viewModel.setIpv4Preferred(!it) },
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
onClick = { viewModel.toggleIpv4Preferred() },
|
onClick = { viewModel.setIpv4Preferred(!tunnel.isIpv4Preferred) },
|
||||||
|
)
|
||||||
|
SurfaceRow(
|
||||||
|
leading = {
|
||||||
|
Icon(
|
||||||
|
Icons.Outlined.DataUsage,
|
||||||
|
contentDescription = null,
|
||||||
|
tint =
|
||||||
|
if (sharedUiState.proxyEnabled) Disabled
|
||||||
|
else MaterialTheme.colorScheme.onSurface,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
title = stringResource(R.string.metered_tunnel),
|
||||||
|
enabled = !sharedUiState.proxyEnabled,
|
||||||
|
description =
|
||||||
|
if (sharedUiState.proxyEnabled) {
|
||||||
|
{
|
||||||
|
DescriptionText(
|
||||||
|
stringResource(R.string.unavailable_in_mode),
|
||||||
|
disabled = true,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else null,
|
||||||
|
trailing = {
|
||||||
|
ThemedSwitch(
|
||||||
|
checked = tunnel.isMetered,
|
||||||
|
onClick = { viewModel.setMetered(it) },
|
||||||
|
enabled = !sharedUiState.proxyEnabled,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
onClick = { viewModel.setMetered(!tunnel.isMetered) },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+9
-6
@@ -1,4 +1,4 @@
|
|||||||
package com.zaneschepke.wireguardautotunnel.ui.screens.tunnels.tunneloptions.components
|
package com.zaneschepke.wireguardautotunnel.ui.screens.tunnels.settings.components
|
||||||
|
|
||||||
import androidx.compose.foundation.Image
|
import androidx.compose.foundation.Image
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
@@ -154,8 +154,10 @@ private fun ConfigTypeSelector(
|
|||||||
)
|
)
|
||||||
val activeContainerColor = Color.White
|
val activeContainerColor = Color.White
|
||||||
val inactiveContainerColor = Color.White
|
val inactiveContainerColor = Color.White
|
||||||
val activeContentColor = if (isEnabled) Color.Black else Color.Gray
|
val activeContentColor =
|
||||||
val inactiveContentColor = if (isEnabled) Color.Black else Color.Gray
|
if (isEnabled) Color.Black else MaterialTheme.colorScheme.outline
|
||||||
|
val inactiveContentColor =
|
||||||
|
if (isEnabled) Color.Black else MaterialTheme.colorScheme.outline
|
||||||
SegmentedButton(
|
SegmentedButton(
|
||||||
shape =
|
shape =
|
||||||
SegmentedButtonDefaults.itemShape(
|
SegmentedButtonDefaults.itemShape(
|
||||||
@@ -172,7 +174,7 @@ private fun ConfigTypeSelector(
|
|||||||
contentDescription = stringResource(R.string.select),
|
contentDescription = stringResource(R.string.select),
|
||||||
tint =
|
tint =
|
||||||
if (isEnabled) MaterialTheme.colorScheme.primary
|
if (isEnabled) MaterialTheme.colorScheme.primary
|
||||||
else Color.Gray,
|
else MaterialTheme.colorScheme.outline,
|
||||||
modifier = Modifier.size(SegmentedButtonDefaults.IconSize),
|
modifier = Modifier.size(SegmentedButtonDefaults.IconSize),
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
@@ -180,7 +182,8 @@ private fun ConfigTypeSelector(
|
|||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.Outlined.VpnKey,
|
imageVector = Icons.Outlined.VpnKey,
|
||||||
contentDescription = typeName,
|
contentDescription = typeName,
|
||||||
tint = if (isEnabled) Color.Black else Color.Gray,
|
tint =
|
||||||
|
if (isEnabled) Color.Black else MaterialTheme.colorScheme.outline,
|
||||||
modifier = Modifier.size(SegmentedButtonDefaults.IconSize),
|
modifier = Modifier.size(SegmentedButtonDefaults.IconSize),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -197,7 +200,7 @@ private fun ConfigTypeSelector(
|
|||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = typeName,
|
text = typeName,
|
||||||
color = if (isEnabled) Color.Black else Color.Gray,
|
color = if (isEnabled) Color.Black else MaterialTheme.colorScheme.outline,
|
||||||
style = MaterialTheme.typography.labelMedium,
|
style = MaterialTheme.typography.labelMedium,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -6,4 +6,6 @@ data class ConfigUiState(
|
|||||||
val unavailableNames: List<String> = emptyList(),
|
val unavailableNames: List<String> = emptyList(),
|
||||||
val isLoading: Boolean = true,
|
val isLoading: Boolean = true,
|
||||||
val tunnel: TunnelConfig? = null,
|
val tunnel: TunnelConfig? = null,
|
||||||
|
val isRunning: Boolean = false,
|
||||||
|
val showSaveModal: Boolean = false,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,5 +1,10 @@
|
|||||||
package com.zaneschepke.wireguardautotunnel.ui.state
|
package com.zaneschepke.wireguardautotunnel.ui.state
|
||||||
|
|
||||||
import com.zaneschepke.wireguardautotunnel.domain.model.DnsSettings
|
import com.zaneschepke.wireguardautotunnel.domain.model.DnsSettings
|
||||||
|
import com.zaneschepke.wireguardautotunnel.domain.model.TunnelConfig
|
||||||
|
|
||||||
data class DnsUiState(val isLoading: Boolean = true, val dnsSettings: DnsSettings = DnsSettings())
|
data class DnsUiState(
|
||||||
|
val isLoading: Boolean = true,
|
||||||
|
val dnsSettings: DnsSettings = DnsSettings(),
|
||||||
|
val globalConfig: TunnelConfig? = null,
|
||||||
|
)
|
||||||
|
|||||||
+9
@@ -0,0 +1,9 @@
|
|||||||
|
package com.zaneschepke.wireguardautotunnel.ui.state
|
||||||
|
|
||||||
|
import com.zaneschepke.wireguardautotunnel.domain.model.LockdownSettings
|
||||||
|
|
||||||
|
data class LockdownSettingsUiState(
|
||||||
|
val lockdownSettings: LockdownSettings = LockdownSettings(),
|
||||||
|
val isLoading: Boolean = true,
|
||||||
|
val showSaveModal: Boolean = false,
|
||||||
|
)
|
||||||
@@ -17,7 +17,7 @@ data class PeerProxy(
|
|||||||
parsePublicKey(publicKey)
|
parsePublicKey(publicKey)
|
||||||
if (preSharedKey.isNotBlank()) parsePreSharedKey(preSharedKey)
|
if (preSharedKey.isNotBlank()) parsePreSharedKey(preSharedKey)
|
||||||
if (persistentKeepalive.isNotBlank()) parsePersistentKeepalive(persistentKeepalive)
|
if (persistentKeepalive.isNotBlank()) parsePersistentKeepalive(persistentKeepalive)
|
||||||
parseEndpoint(endpoint)
|
if (endpoint.isNotBlank()) parseEndpoint(endpoint)
|
||||||
parseAllowedIPs(allowedIps)
|
parseAllowedIPs(allowedIps)
|
||||||
}
|
}
|
||||||
.build()
|
.build()
|
||||||
@@ -29,7 +29,7 @@ data class PeerProxy(
|
|||||||
parsePublicKey(publicKey)
|
parsePublicKey(publicKey)
|
||||||
if (preSharedKey.isNotBlank()) parsePreSharedKey(preSharedKey)
|
if (preSharedKey.isNotBlank()) parsePreSharedKey(preSharedKey)
|
||||||
if (persistentKeepalive.isNotBlank()) parsePersistentKeepalive(persistentKeepalive)
|
if (persistentKeepalive.isNotBlank()) parsePersistentKeepalive(persistentKeepalive)
|
||||||
parseEndpoint(endpoint)
|
if (endpoint.isNotBlank()) parseEndpoint(endpoint)
|
||||||
parseAllowedIPs(allowedIps)
|
parseAllowedIPs(allowedIps)
|
||||||
}
|
}
|
||||||
.build()
|
.build()
|
||||||
|
|||||||
@@ -1,13 +1,16 @@
|
|||||||
package com.zaneschepke.wireguardautotunnel.ui.state
|
package com.zaneschepke.wireguardautotunnel.ui.state
|
||||||
|
|
||||||
import com.zaneschepke.wireguardautotunnel.domain.model.ProxySettings
|
import com.zaneschepke.wireguardautotunnel.domain.model.ProxySettings
|
||||||
|
import com.zaneschepke.wireguardautotunnel.domain.state.TunnelState
|
||||||
|
|
||||||
data class ProxySettingsUiState(
|
data class ProxySettingsUiState(
|
||||||
val proxySettings: ProxySettings = ProxySettings(),
|
val proxySettings: ProxySettings = ProxySettings(),
|
||||||
|
val activeTuns: Map<Int, TunnelState> = emptyMap(),
|
||||||
val isSocks5BindAddressError: Boolean = false,
|
val isSocks5BindAddressError: Boolean = false,
|
||||||
val isHttpBindAddressError: Boolean = false,
|
val isHttpBindAddressError: Boolean = false,
|
||||||
val isUserNameError: Boolean = false,
|
val isUserNameError: Boolean = false,
|
||||||
val isPasswordError: Boolean = false,
|
val isPasswordError: Boolean = false,
|
||||||
val passwordVisible: Boolean = false,
|
val passwordVisible: Boolean = false,
|
||||||
val isLoading: Boolean = true,
|
val isLoading: Boolean = true,
|
||||||
|
val showSaveModal: Boolean = false,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ data class SharedAppUiState(
|
|||||||
val theme: Theme = Theme.AUTOMATIC,
|
val theme: Theme = Theme.AUTOMATIC,
|
||||||
val locale: String = LocaleUtil.OPTION_PHONE_LANGUAGE,
|
val locale: String = LocaleUtil.OPTION_PHONE_LANGUAGE,
|
||||||
val pinLockEnabled: Boolean = false,
|
val pinLockEnabled: Boolean = false,
|
||||||
|
val shouldShowDonationSnackbar: Boolean = false,
|
||||||
val tunnels: List<TunnelConfig> = emptyList(),
|
val tunnels: List<TunnelConfig> = emptyList(),
|
||||||
val selectedTunnels: List<TunnelConfig> = emptyList(),
|
val selectedTunnels: List<TunnelConfig> = emptyList(),
|
||||||
val activeTunnels: Map<Int, TunnelState> = emptyMap(),
|
val activeTunnels: Map<Int, TunnelState> = emptyMap(),
|
||||||
@@ -20,5 +21,6 @@ data class SharedAppUiState(
|
|||||||
val isAutoTunnelActive: Boolean = false,
|
val isAutoTunnelActive: Boolean = false,
|
||||||
val isLocationDisclosureShown: Boolean = false,
|
val isLocationDisclosureShown: Boolean = false,
|
||||||
val isBatteryOptimizationShown: Boolean = false,
|
val isBatteryOptimizationShown: Boolean = false,
|
||||||
|
val proxyEnabled: Boolean = false,
|
||||||
val settings: GeneralSettings = GeneralSettings(),
|
val settings: GeneralSettings = GeneralSettings(),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -19,6 +19,8 @@ val AlertRed = Color(0xFFCF6679)
|
|||||||
|
|
||||||
val Straw = Color(0xFFD4C483)
|
val Straw = Color(0xFFD4C483)
|
||||||
|
|
||||||
|
val Disabled = CoolGray.copy(alpha = 0.4f)
|
||||||
|
|
||||||
sealed class ThemeColors(
|
sealed class ThemeColors(
|
||||||
val background: Color,
|
val background: Color,
|
||||||
val surface: Color,
|
val surface: Color,
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user