Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| ec26ab67c8 |
@@ -48,9 +48,9 @@ jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
SIGNING_KEY_ALIAS: ${{ secrets.ANDROID_SIGNING_KEY_ALIAS }}
|
||||
SIGNING_KEY_PASSWORD: ${{ secrets.ANDROID_SIGNING_KEY_PASSWORD }}
|
||||
SIGNING_STORE_PASSWORD: ${{ secrets.ANDROID_SIGNING_STORE_PASSWORD }}
|
||||
SIGNING_KEY_ALIAS: ${{ secrets.SIGNING_KEY_ALIAS }}
|
||||
SIGNING_KEY_PASSWORD: ${{ secrets.SIGNING_KEY_PASSWORD }}
|
||||
SIGNING_STORE_PASSWORD: ${{ secrets.SIGNING_STORE_PASSWORD }}
|
||||
KEY_STORE_FILE: 'android_keystore.jks'
|
||||
KEY_STORE_LOCATION: ${{ github.workspace }}/app/keystore/
|
||||
outputs:
|
||||
@@ -74,7 +74,7 @@ jobs:
|
||||
with:
|
||||
fileName: ${{ env.KEY_STORE_FILE }}
|
||||
fileDir: ${{ env.KEY_STORE_LOCATION }}
|
||||
encodedString: ${{ secrets.ANDROID_KEYSTORE }}
|
||||
encodedString: ${{ secrets.KEYSTORE }}
|
||||
|
||||
# create keystore path for gradle to read
|
||||
- name: Create keystore path env var
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
name: on-issue
|
||||
|
||||
on:
|
||||
issues:
|
||||
types: [ opened, closed, reopened ]
|
||||
|
||||
|
||||
jobs:
|
||||
|
||||
on-issue:
|
||||
name: On new issue
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Send Telegram Message
|
||||
run: |
|
||||
msg_text='${{ github.actor }} updated an issue:
|
||||
status: ${{ github.event.issue.state }} - #${{ github.event.issue.number }} ${{ github.event.issue.title }}
|
||||
https://github.com/zaneschepke/wgtunnel/issues/${{ github.event.issue.number }}'
|
||||
curl -s -X POST 'https://api.telegram.org/bot${{ secrets.TELEGRAM_TOKEN }}/sendMessage' \
|
||||
-d "chat_id=${{ secrets.TELEGRAM_TO }}&text=${msg_text}&message_thread_id=${{ vars.TELEGRAM_ACTIVITY_TOPIC }}"
|
||||
|
||||
- name: Send Matrix Message
|
||||
run: |
|
||||
msg_text='${{ github.actor }} updated an issue:
|
||||
status: ${{ github.event.issue.state }} - #${{ github.event.issue.number }} ${{ github.event.issue.title }}
|
||||
https://github.com/zaneschepke/wgtunnel/issues/${{ github.event.issue.number }}'
|
||||
# Escape newlines and quotes for JSON
|
||||
formatted_msg=$(echo -n "$msg_text" | sed ':a;N;$ba;s/\n/\\n/g' | sed 's/"/\\"/g')
|
||||
curl -s -X POST \
|
||||
-H "Authorization: Bearer ${{ secrets.MATRIX_TOKEN }}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"msgtype": "m.text",
|
||||
"body": "'"$formatted_msg"'"
|
||||
}' \
|
||||
"https://matrix.org/_matrix/client/v3/rooms/${{ vars.MATRIX_ACTIVITY_TOPIC }}/send/m.room.message/$(date +%s)"
|
||||
@@ -0,0 +1,37 @@
|
||||
name: on-publish
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [ published ]
|
||||
|
||||
|
||||
jobs:
|
||||
on-publish:
|
||||
name: On publish
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Send Telegram Message
|
||||
run: |
|
||||
msg_text='${{ github.actor }} published a new release:
|
||||
Release: ${{ github.event.release.tag_name }}
|
||||
${{ github.event.release.body }}
|
||||
https://github.com/zaneschepke/wgtunnel/releases/tag/${{ github.event.release.tag_name }}'
|
||||
curl -s -X POST 'https://api.telegram.org/bot${{ secrets.TELEGRAM_TOKEN }}/sendMessage' \
|
||||
-d "chat_id=${{ secrets.TELEGRAM_TO }}&text=${msg_text}&message_thread_id=${{ vars.TELEGRAM_RELEASE_TOPIC }}"
|
||||
|
||||
- name: Send Matrix Message
|
||||
run: |
|
||||
msg_text='${{ github.actor }} published a new release:
|
||||
Release: ${{ github.event.release.tag_name }}
|
||||
${{ github.event.release.body }}
|
||||
https://github.com/zaneschepke/wgtunnel/releases/tag/${{ github.event.release.tag_name }}'
|
||||
# Escape newlines and quotes for JSON
|
||||
formatted_msg=$(echo -n "$msg_text" | sed ':a;N;$ba;s/\n/\\n/g' | sed 's/"/\\"/g')
|
||||
curl -s -X POST \
|
||||
-H "Authorization: Bearer ${{ secrets.MATRIX_TOKEN }}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"msgtype": "m.text",
|
||||
"body": "'"$formatted_msg"'"
|
||||
}' \
|
||||
"https://matrix.org/_matrix/client/v3/rooms/${{ vars.MATRIX_RELEASE_TOPIC }}/send/m.room.message/$(date +%s)"
|
||||
@@ -34,9 +34,6 @@ on:
|
||||
env:
|
||||
UPLOAD_DIR_ANDROID: android_artifacts
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
check_commits:
|
||||
name: Check for New Commits
|
||||
@@ -46,14 +43,14 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0 # This fetches all history so we can check commits
|
||||
|
||||
- name: Check for new commits
|
||||
id: check
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.PAT }}
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
# This script checks for commits newer than 23 hours ago
|
||||
NEW_COMMITS=$(git rev-list --count --after="$(date -Iseconds -d '23 hours ago')" ${{ github.sha }})
|
||||
@@ -74,9 +71,9 @@ jobs:
|
||||
name: publish-github
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
GH_USER: ${{ secrets.PAT_USERNAME }}
|
||||
GH_USER: ${{ secrets.GH_USER }}
|
||||
# GH needed for gh cli
|
||||
GH_TOKEN: ${{ secrets.PAT }}
|
||||
GH_TOKEN: ${{ secrets.GH_TOKEN }}
|
||||
GH_REPO: ${{ github.repository }}
|
||||
|
||||
steps:
|
||||
@@ -93,7 +90,6 @@ jobs:
|
||||
tag: "latest" # or any tag name you wish to use
|
||||
message: "Automated tag for HEAD commit"
|
||||
force_push_tag: true
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
tag_exists_error: false
|
||||
|
||||
- name: Get latest release
|
||||
@@ -122,7 +118,7 @@ jobs:
|
||||
if: ${{ inputs.release_type == '' || inputs.release_type == 'nightly' || inputs.release_type == 'prerelease' }}
|
||||
uses: ad-m/github-push-action@master
|
||||
with:
|
||||
github_token: ${{ secrets.PAT }}
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
branch: ${{ github.ref }}
|
||||
|
||||
- name: Make download dir
|
||||
@@ -172,7 +168,7 @@ jobs:
|
||||
id: create_release
|
||||
uses: softprops/action-gh-release@v2
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
GITHUB_TOKEN: ${{ secrets.GH_TOKEN }}
|
||||
with:
|
||||
body: |
|
||||
${{ env.RELEASE_NOTES }}
|
||||
@@ -197,6 +193,14 @@ jobs:
|
||||
files: |
|
||||
${{ github.workspace }}/temp/*
|
||||
|
||||
# notify socials
|
||||
- name: Trigger on-publish workflow
|
||||
if: ${{ inputs.release_type == 'release' }}
|
||||
uses: peter-evans/repository-dispatch@v3
|
||||
with:
|
||||
token: ${{ secrets.PAT }}
|
||||
event-type: publish-release
|
||||
|
||||
publish-fdroid:
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
@@ -216,13 +220,13 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
env:
|
||||
SIGNING_KEY_ALIAS: ${{ secrets.ANDROID_SIGNING_KEY_ALIAS }}
|
||||
SIGNING_KEY_PASSWORD: ${{ secrets.ANDROID_SIGNING_KEY_PASSWORD }}
|
||||
SIGNING_STORE_PASSWORD: ${{ secrets.ANDROID_SIGNING_STORE_PASSWORD }}
|
||||
SIGNING_KEY_ALIAS: ${{ secrets.SIGNING_KEY_ALIAS }}
|
||||
SIGNING_KEY_PASSWORD: ${{ secrets.SIGNING_KEY_PASSWORD }}
|
||||
SIGNING_STORE_PASSWORD: ${{ secrets.SIGNING_STORE_PASSWORD }}
|
||||
KEY_STORE_FILE: 'android_keystore.jks'
|
||||
KEY_STORE_LOCATION: ${{ github.workspace }}/app/keystore/
|
||||
GH_USER: ${{ secrets.PAT_USERNAME }}
|
||||
GH_TOKEN: ${{ secrets.PAT }}
|
||||
GH_USER: ${{ secrets.GH_USER }}
|
||||
GH_TOKEN: ${{ secrets.GH_TOKEN }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
@@ -244,7 +248,7 @@ jobs:
|
||||
with:
|
||||
fileName: ${{ env.KEY_STORE_FILE }}
|
||||
fileDir: ${{ env.KEY_STORE_LOCATION }}
|
||||
encodedString: ${{ secrets.ANDROID_KEYSTORE }}
|
||||
encodedString: ${{ secrets.KEYSTORE }}
|
||||
|
||||
# create keystore path for gradle to read
|
||||
- name: Create keystore path env var
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
MIT License
|
||||
|
||||
Copyright © 2023-2025 Zane Schepke
|
||||
Copyright (c) 2023 WG Auto Tunnel
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
-dontwarn com.google.errorprone.annotations.**
|
||||
|
||||
-keepclassmembers class * extends androidx.datastore.preferences.protobuf.GeneratedMessageLite {
|
||||
<fields>;
|
||||
}
|
||||
|
||||
# Keep all classes in the org.xbill.DNS package and subpackages
|
||||
-keep class org.xbill.DNS.** { *; }
|
||||
-dontwarn org.xbill.DNS.**
|
||||
|
||||
# Preserve JNA classes if used (e.g., for IPHlpAPI on Windows)
|
||||
-keep class com.sun.jna.** { *; }
|
||||
-dontwarn com.sun.jna.**
|
||||
|
||||
# Keep DNS resolver configuration classes that might be loaded dynamically
|
||||
-keep class org.xbill.DNS.config.** { *; }
|
||||
-dontwarn org.xbill.DNS.config.**
|
||||
|
||||
-keep class org.xbill.DNS.** { *; }
|
||||
|
||||
# Prevent optimization issues with native or reflection-based calls
|
||||
-dontoptimize
|
||||
-dontshrink
|
||||
# Uncomment the above if errors persist, but use sparingly as they’re broad
|
||||
|
||||
# Suppress warnings about missing classes if not all features are used
|
||||
-dontwarn java.lang.management.**
|
||||
-dontwarn sun.nio.ch.**
|
||||
|
||||
-dontwarn com.google.api.client.http.GenericUrl
|
||||
-dontwarn com.google.api.client.http.HttpHeaders
|
||||
-dontwarn com.google.api.client.http.HttpRequest
|
||||
-dontwarn com.google.api.client.http.HttpRequestFactory
|
||||
-dontwarn com.google.api.client.http.HttpResponse
|
||||
-dontwarn com.google.api.client.http.HttpTransport
|
||||
-dontwarn com.google.api.client.http.javanet.NetHttpTransport$Builder
|
||||
-dontwarn com.google.api.client.http.javanet.NetHttpTransport
|
||||
-dontwarn javax.lang.model.element.Modifier
|
||||
-dontwarn org.joda.time.Instant
|
||||
-dontwarn org.slf4j.impl.StaticLoggerBinder
|
||||
-dontwarn org.slf4j.impl.StaticMDCBinder
|
||||
-dontwarn org.slf4j.impl.StaticMarkerBinder
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
# Add project specific ProGuard rules here.
|
||||
# You can control the set of applied configuration files using the
|
||||
# proguardFiles setting in build.gradle.
|
||||
#
|
||||
# For more details, see
|
||||
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||
|
||||
# If your project uses WebView with JS, uncomment the following
|
||||
# and specify the fully qualified class name to the JavaScript interface
|
||||
# class:
|
||||
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||
# public *;
|
||||
#}
|
||||
|
||||
# Uncomment this to preserve the line number information for
|
||||
# debugging stack traces.
|
||||
#-keepattributes SourceFile,LineNumberTable
|
||||
|
||||
# If you keep the line number information, uncomment this to
|
||||
# hide the original source file name.
|
||||
#-renamesourcefileattribute SourceFile
|
||||
-keepclassmembers class * extends androidx.datastore.preferences.protobuf.GeneratedMessageLite {
|
||||
<fields>;
|
||||
}
|
||||
|
||||
# Keep all classes in the org.xbill.DNS package and subpackages
|
||||
-keep class org.xbill.DNS.** { *; }
|
||||
-dontwarn org.xbill.DNS.**
|
||||
|
||||
# Preserve JNA classes if used (e.g., for IPHlpAPI on Windows)
|
||||
-keep class com.sun.jna.** { *; }
|
||||
-dontwarn com.sun.jna.**
|
||||
|
||||
# Keep DNS resolver configuration classes that might be loaded dynamically
|
||||
-keep class org.xbill.DNS.config.** { *; }
|
||||
-dontwarn org.xbill.DNS.config.**
|
||||
|
||||
-keep class org.xbill.DNS.** { *; }
|
||||
|
||||
# Prevent optimization issues with native or reflection-based calls
|
||||
-dontoptimize
|
||||
-dontshrink
|
||||
# Uncomment the above if errors persist, but use sparingly as they’re broad
|
||||
|
||||
# Suppress warnings about missing classes if not all features are used
|
||||
-dontwarn java.lang.management.**
|
||||
-dontwarn sun.nio.ch.**
|
||||
|
||||
-dontwarn com.google.api.client.http.GenericUrl
|
||||
-dontwarn com.google.api.client.http.HttpHeaders
|
||||
-dontwarn com.google.api.client.http.HttpRequest
|
||||
-dontwarn com.google.api.client.http.HttpRequestFactory
|
||||
-dontwarn com.google.api.client.http.HttpResponse
|
||||
-dontwarn com.google.api.client.http.HttpTransport
|
||||
-dontwarn com.google.api.client.http.javanet.NetHttpTransport$Builder
|
||||
-dontwarn com.google.api.client.http.javanet.NetHttpTransport
|
||||
-dontwarn javax.lang.model.element.Modifier
|
||||
-dontwarn org.joda.time.Instant
|
||||
-dontwarn org.slf4j.impl.StaticLoggerBinder
|
||||
-dontwarn org.slf4j.impl.StaticMDCBinder
|
||||
-dontwarn org.slf4j.impl.StaticMarkerBinder
|
||||
@@ -66,7 +66,6 @@
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:exported="true"
|
||||
android:banner="@mipmap/ic_banner"
|
||||
android:windowSoftInputMode="adjustNothing"
|
||||
android:theme="@style/Theme.WireguardAutoTunnel"
|
||||
android:configChanges="orientation|screenSize|keyboardHidden"
|
||||
@@ -115,7 +114,7 @@
|
||||
<service
|
||||
android:name=".core.service.tile.TunnelControlTile"
|
||||
android:exported="true"
|
||||
android:icon="@drawable/ic_notification"
|
||||
android:icon="@drawable/ic_launcher"
|
||||
android:label="@string/tunnel_control"
|
||||
android:permission="android.permission.BIND_QUICK_SETTINGS_TILE">
|
||||
<meta-data
|
||||
@@ -132,7 +131,7 @@
|
||||
<service
|
||||
android:name=".core.service.tile.AutoTunnelControlTile"
|
||||
android:exported="true"
|
||||
android:icon="@drawable/ic_notification"
|
||||
android:icon="@drawable/ic_launcher"
|
||||
android:label="@string/auto_tunnel"
|
||||
android:permission="android.permission.BIND_QUICK_SETTINGS_TILE">
|
||||
<meta-data
|
||||
@@ -171,7 +170,7 @@
|
||||
android:exported="false"
|
||||
android:directBootAware="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.SCREEN_ON" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<action android:name="android.intent.action.BOOT_COMPLETED" />
|
||||
<action android:name="android.intent.action.QUICKBOOT_POWERON" />
|
||||
<action android:name="com.htc.intent.action.QUICKBOOT_POWERON" />
|
||||
|
||||
|
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 16 KiB |
@@ -84,8 +84,8 @@ import com.zaneschepke.wireguardautotunnel.ui.screens.settings.appearance.displa
|
||||
import com.zaneschepke.wireguardautotunnel.ui.screens.settings.appearance.language.LanguageScreen
|
||||
import com.zaneschepke.wireguardautotunnel.ui.screens.settings.disclosure.LocationDisclosureScreen
|
||||
import com.zaneschepke.wireguardautotunnel.ui.screens.settings.killswitch.KillSwitchScreen
|
||||
import com.zaneschepke.wireguardautotunnel.ui.screens.settings.logs.LogsScreen
|
||||
import com.zaneschepke.wireguardautotunnel.ui.screens.support.SupportScreen
|
||||
import com.zaneschepke.wireguardautotunnel.ui.screens.support.logs.LogsScreen
|
||||
import com.zaneschepke.wireguardautotunnel.ui.theme.WireguardAutoTunnelTheme
|
||||
import com.zaneschepke.wireguardautotunnel.util.extensions.goFromRoot
|
||||
import com.zaneschepke.wireguardautotunnel.viewmodel.AppViewModel
|
||||
|
||||
@@ -80,6 +80,9 @@ class WireGuardAutoTunnel : Application(), Configuration.Provider {
|
||||
ServiceWorker.start(this)
|
||||
|
||||
applicationScope.launch {
|
||||
if (!appDataRepository.settings.get().isKernelEnabled) {
|
||||
tunnelManager.setBackendState(BackendState.SERVICE_ACTIVE, emptyList())
|
||||
}
|
||||
appDataRepository.appState.getLocale()?.let {
|
||||
withContext(mainDispatcher) { LocaleUtil.changeLocale(it) }
|
||||
}
|
||||
@@ -91,7 +94,7 @@ class WireGuardAutoTunnel : Application(), Configuration.Provider {
|
||||
|
||||
override fun onTerminate() {
|
||||
applicationScope.launch {
|
||||
tunnelManager.setBackendState(BackendState.INACTIVE, emptyList())
|
||||
tunnelManager.setBackendState(BackendState.SERVICE_ACTIVE, emptyList())
|
||||
}
|
||||
super.onTerminate()
|
||||
}
|
||||
|
||||
@@ -8,7 +8,6 @@ import com.zaneschepke.wireguardautotunnel.core.tunnel.TunnelManager
|
||||
import com.zaneschepke.wireguardautotunnel.di.ApplicationScope
|
||||
import com.zaneschepke.wireguardautotunnel.di.IoDispatcher
|
||||
import com.zaneschepke.wireguardautotunnel.domain.repository.AppDataRepository
|
||||
import com.zaneschepke.wireguardautotunnel.util.extensions.isRunningOnTv
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import javax.inject.Inject
|
||||
import kotlinx.coroutines.CoroutineDispatcher
|
||||
@@ -30,8 +29,6 @@ class RestartReceiver : BroadcastReceiver() {
|
||||
|
||||
override fun onReceive(context: Context, intent: Intent) {
|
||||
Timber.d("RestartReceiver triggered with action: ${intent.action}")
|
||||
// screen on for Android TV only to help with sleep shutdowns
|
||||
if (intent.action == Intent.ACTION_SCREEN_ON && !context.isRunningOnTv()) return
|
||||
serviceManager.updateTunnelTile()
|
||||
serviceManager.updateAutoTunnelTile()
|
||||
applicationScope.launch(ioDispatcher) {
|
||||
|
||||
@@ -60,7 +60,7 @@ class WireGuardNotification @Inject constructor(@ApplicationContext override val
|
||||
setOngoing(onGoing)
|
||||
setPriority(NotificationCompat.PRIORITY_HIGH)
|
||||
setShowWhen(showTimestamp)
|
||||
setSmallIcon(R.drawable.ic_notification)
|
||||
setSmallIcon(R.drawable.ic_launcher)
|
||||
}
|
||||
.build()
|
||||
}
|
||||
@@ -102,7 +102,7 @@ class WireGuardNotification @Inject constructor(@ApplicationContext override val
|
||||
PendingIntent.FLAG_IMMUTABLE,
|
||||
)
|
||||
return NotificationCompat.Action.Builder(
|
||||
R.drawable.ic_notification,
|
||||
R.drawable.ic_launcher,
|
||||
notificationAction.title(context).uppercase(),
|
||||
pendingIntent,
|
||||
)
|
||||
|
||||
@@ -17,7 +17,6 @@ import com.zaneschepke.wireguardautotunnel.domain.entity.TunnelConf
|
||||
import com.zaneschepke.wireguardautotunnel.domain.enums.NotificationAction
|
||||
import com.zaneschepke.wireguardautotunnel.domain.enums.TunnelStatus
|
||||
import com.zaneschepke.wireguardautotunnel.domain.repository.TunnelRepository
|
||||
import com.zaneschepke.wireguardautotunnel.domain.state.TunnelState
|
||||
import com.zaneschepke.wireguardautotunnel.util.Constants
|
||||
import com.zaneschepke.wireguardautotunnel.util.extensions.distinctByKeys
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
@@ -37,8 +36,6 @@ import kotlinx.coroutines.flow.flowOn
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.isActive
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
import kotlinx.coroutines.withContext
|
||||
import timber.log.Timber
|
||||
|
||||
@@ -60,9 +57,6 @@ class TunnelForegroundService : LifecycleService() {
|
||||
private val isNetworkConnected = MutableStateFlow(true)
|
||||
|
||||
private val tunnelJobs = ConcurrentHashMap<TunnelConf, Job>()
|
||||
private val pingJobs = ConcurrentHashMap<TunnelConf, Job>()
|
||||
|
||||
private val jobsMutex = Mutex()
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
@@ -94,85 +88,31 @@ class TunnelForegroundService : LifecycleService() {
|
||||
}
|
||||
|
||||
fun start() =
|
||||
lifecycleScope.launch(ioDispatcher) {
|
||||
tunnelManager.activeTunnels.distinctByKeys().collect { activeTunnels ->
|
||||
// No active tunnels and no jobs: nothing to do
|
||||
if (activeTunnels.isEmpty() && tunnelJobs.isEmpty()) return@collect
|
||||
|
||||
// Synchronize jobs with active tunnels
|
||||
synchronizeJobs(activeTunnels)
|
||||
lifecycleScope.launch {
|
||||
tunnelManager.activeTunnels.distinctByKeys().collect { tuns ->
|
||||
if (tuns.isEmpty() && tunnelJobs.isEmpty()) return@collect
|
||||
if (tuns.isEmpty() && tunnelJobs.isNotEmpty()) {
|
||||
return@collect tunnelJobs.forEach { (key, _) ->
|
||||
Timber.d("Stopping all tunnel jobs")
|
||||
tunnelJobs[key]?.cancel()
|
||||
tunnelJobs.remove(key)
|
||||
}
|
||||
}
|
||||
val (jobsToStop, jobsToStart) = findMissingKeys(tuns, tunnelJobs)
|
||||
if (jobsToStop.isEmpty() && jobsToStart.isEmpty()) return@collect
|
||||
jobsToStop.forEach { tun ->
|
||||
Timber.d("Stopping tunnel jobs for ${tun.tunName}")
|
||||
tunnelJobs[tun]?.cancel()
|
||||
tunnelJobs.remove(tun)
|
||||
}
|
||||
jobsToStart.forEach { tun ->
|
||||
Timber.d("Starting tunnel jobs for ${tun.tunName}")
|
||||
tunnelJobs += (tun to startTunnelJobs(tun))
|
||||
}
|
||||
updateServiceNotification()
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun synchronizeJobs(activeTunnels: Map<TunnelConf, TunnelState>) {
|
||||
jobsMutex.withLock {
|
||||
// Stop jobs for tunnels that are no longer active
|
||||
stopInactiveJobs(activeTunnels)
|
||||
// Start jobs for new tunnels
|
||||
startNewJobs(activeTunnels)
|
||||
}
|
||||
}
|
||||
|
||||
private fun stopInactiveJobs(activeTunnels: Map<TunnelConf, TunnelState>) {
|
||||
// If no active tunnels, clear all jobs
|
||||
if (activeTunnels.isEmpty()) {
|
||||
clearAllJobs()
|
||||
return
|
||||
}
|
||||
// Stop jobs for tunnels not in activeTunnels
|
||||
val tunnelsToStop = tunnelJobs.keys - activeTunnels.keys
|
||||
tunnelsToStop.forEach { tun -> stopTunnelJobs(tun) }
|
||||
}
|
||||
|
||||
private fun clearAllJobs() {
|
||||
tunnelJobs.forEach { (tun, job) ->
|
||||
Timber.d("Stopping tunnel job for ${tun.tunName}")
|
||||
job.cancel()
|
||||
}
|
||||
tunnelJobs.clear()
|
||||
|
||||
pingJobs.forEach { (tun, job) ->
|
||||
if (isPingBounce(tun)) {
|
||||
Timber.d("Preserving ping job for ${tun.tunName} due to PING bounce")
|
||||
return@forEach
|
||||
}
|
||||
Timber.d("Stopping ping job for ${tun.tunName}")
|
||||
job.cancel()
|
||||
}
|
||||
pingJobs.entries.removeIf { (tun, _) -> !isPingBounce(tun) }
|
||||
}
|
||||
|
||||
private fun stopTunnelJobs(tun: TunnelConf) {
|
||||
tunnelJobs.remove(tun)?.cancel()
|
||||
Timber.d("Stopped tunnel job for ${tun.tunName}")
|
||||
if (isPingBounce(tun))
|
||||
return Timber.d("Preserving ${tun.tunName} ping job due to ping bounce")
|
||||
pingJobs.remove(tun)?.cancel()
|
||||
Timber.d("Stopped ping job for ${tun.tunName}")
|
||||
}
|
||||
|
||||
private fun startNewJobs(activeTunnels: Map<TunnelConf, TunnelState>) {
|
||||
val tunnelsToStart = activeTunnels.keys - tunnelJobs.keys
|
||||
tunnelsToStart.forEach { tun ->
|
||||
tunnelJobs[tun] = startTunnelJobs(tun)
|
||||
Timber.d("Started tunnel job for ${tun.tunName}")
|
||||
|
||||
if (pingJobs[tun]?.isActive == true) {
|
||||
Timber.d("Reusing active ping job for ${tun.tunName}")
|
||||
} else {
|
||||
pingJobs[tun]?.cancel() // Cancel any stale job
|
||||
if (tun.isPingEnabled) {
|
||||
pingJobs[tun] = startPingJob(tun)
|
||||
Timber.d("Started ping job for ${tun.tunName}")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun isPingBounce(tun: TunnelConf): Boolean =
|
||||
tunnelManager.bouncingTunnelIds[tun.id] == TunnelStatus.StopReason.PING
|
||||
|
||||
// TODO Would be cool to have this include kill switch
|
||||
// TODO also we need to include errors
|
||||
private fun updateServiceNotification() {
|
||||
@@ -192,15 +132,26 @@ class TunnelForegroundService : LifecycleService() {
|
||||
|
||||
// use same scope so we can cancel all of these
|
||||
private fun startTunnelJobs(tunnelConf: TunnelConf) =
|
||||
lifecycleScope.launch(ioDispatcher) {
|
||||
lifecycleScope.launch {
|
||||
// monitor if we have internet connectivity
|
||||
launch { startNetworkMonitorJob() }
|
||||
// job to trigger stats emit on interval
|
||||
launch { startTunnelStatsJob(tunnelConf) }
|
||||
// monitor changes to the tunnel config
|
||||
launch { startTunnelConfChangesJob(tunnelConf) }
|
||||
// monitor tunnel ping
|
||||
launch { startPingJob(tunnelConf) }
|
||||
}
|
||||
|
||||
private fun findMissingKeys(
|
||||
map1: Map<TunnelConf, Any>,
|
||||
map2: Map<TunnelConf, Any>,
|
||||
): Pair<Set<TunnelConf>, Set<TunnelConf>> {
|
||||
val missingMap1 = map2.keys - map1.keys
|
||||
val missingMap2 = map1.keys - map2.keys
|
||||
return missingMap1 to missingMap2
|
||||
}
|
||||
|
||||
private suspend fun startTunnelConfChangesJob(tunnelConf: TunnelConf) {
|
||||
tunnelRepo.flow
|
||||
.flowOn(ioDispatcher)
|
||||
@@ -237,25 +188,24 @@ class TunnelForegroundService : LifecycleService() {
|
||||
}
|
||||
}
|
||||
|
||||
private fun startPingJob(tunnel: TunnelConf) =
|
||||
lifecycleScope.launch(ioDispatcher) {
|
||||
// delay for initial duration
|
||||
delay(tunnel.pingInterval ?: Constants.PING_INTERVAL)
|
||||
while (isActive) {
|
||||
val shouldBounce = shouldBounceTunnel(tunnel)
|
||||
val delayMs =
|
||||
if (shouldBounce) {
|
||||
// let this complete, even after cancel
|
||||
withContext(NonCancellable) {
|
||||
tunnelManager.bounceTunnel(tunnel, TunnelStatus.StopReason.PING)
|
||||
}
|
||||
tunnel.pingCooldown ?: Constants.PING_COOLDOWN
|
||||
} else {
|
||||
tunnel.pingInterval ?: Constants.PING_INTERVAL
|
||||
// TODO fix cooldown
|
||||
private suspend fun startPingJob(tunnel: TunnelConf) = coroutineScope {
|
||||
delay(PING_START_DELAY)
|
||||
while (isActive) {
|
||||
val shouldBounce = shouldBounceTunnel(tunnel)
|
||||
val delayMs =
|
||||
if (shouldBounce) {
|
||||
// let this complete, even after cancel
|
||||
withContext(NonCancellable) {
|
||||
tunnelManager.bounceTunnel(tunnel, TunnelStatus.StopReason.PING)
|
||||
}
|
||||
delay(delayMs)
|
||||
}
|
||||
tunnel.pingCooldown ?: Constants.PING_COOLDOWN
|
||||
} else {
|
||||
tunnel.pingInterval ?: Constants.PING_INTERVAL
|
||||
}
|
||||
delay(delayMs)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun shouldBounceTunnel(tunnel: TunnelConf): Boolean {
|
||||
if (!isNetworkConnected.value) {
|
||||
@@ -313,10 +263,10 @@ class TunnelForegroundService : LifecycleService() {
|
||||
// TODO add notification handling and optional log reading for restart on handshake failures
|
||||
companion object {
|
||||
const val STATS_DELAY = 1_000L
|
||||
const val PING_START_DELAY = 30_000L
|
||||
// ipv6 disabled or block on network
|
||||
// Failed to send handshake initiation: write udp [::]"
|
||||
// Failed to send data packets: write udp [::]
|
||||
// Failed to send data packets: write udp 0.0.0.0:51820
|
||||
// Handshake did not complete after 5 seconds, retrying
|
||||
// const val userspaceStartFailed = "Failed to send handshake initiation: write udp [::]"
|
||||
// const val ipv6Fails = "Failed to send data packets: write udp [::]"
|
||||
// const val ipv4Fails = "Failed to send data packets: write udp 0.0.0.0:51820"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -148,12 +148,11 @@ class TunnelControlTile : TileService(), LifecycleOwner {
|
||||
|
||||
private fun setTileDescription(description: String) {
|
||||
runCatching {
|
||||
if (qsTile == null) return@runCatching
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||
qsTile.subtitle = description
|
||||
}
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||
qsTile.subtitle = description
|
||||
qsTile.stateDescription = description
|
||||
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||
qsTile.subtitle = description
|
||||
}
|
||||
qsTile.updateTile()
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package com.zaneschepke.wireguardautotunnel.core.tunnel
|
||||
|
||||
import com.wireguard.android.backend.BackendException
|
||||
import com.wireguard.android.backend.Tunnel
|
||||
import com.zaneschepke.wireguardautotunnel.core.service.ServiceManager
|
||||
import com.zaneschepke.wireguardautotunnel.di.ApplicationScope
|
||||
@@ -11,11 +10,10 @@ import com.zaneschepke.wireguardautotunnel.domain.repository.AppDataRepository
|
||||
import com.zaneschepke.wireguardautotunnel.domain.state.TunnelState
|
||||
import com.zaneschepke.wireguardautotunnel.domain.state.TunnelStatistics
|
||||
import com.zaneschepke.wireguardautotunnel.util.extensions.asTunnelState
|
||||
import com.zaneschepke.wireguardautotunnel.util.extensions.toBackendError
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
import kotlin.concurrent.thread
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.flow.update
|
||||
@@ -37,9 +35,8 @@ abstract class BaseTunnel(
|
||||
|
||||
private val tunMutex = Mutex()
|
||||
private val tunStatusMutex = Mutex()
|
||||
private val bounceTunnelMutex = Mutex()
|
||||
|
||||
override val bouncingTunnelIds = ConcurrentHashMap<Int, TunnelStatus.StopReason>()
|
||||
private val isBouncing = AtomicBoolean(false)
|
||||
|
||||
abstract suspend fun startBackend(tunnel: TunnelConf)
|
||||
|
||||
@@ -101,7 +98,7 @@ abstract class BaseTunnel(
|
||||
is org.amnezia.awg.backend.Tunnel.State ->
|
||||
updateTunnelStatus(tunnelConf, state.asTunnelState())
|
||||
}
|
||||
handleServiceStateOnChange()
|
||||
handleServiceChangesOnStop()
|
||||
}
|
||||
serviceManager.updateTunnelTile()
|
||||
}
|
||||
@@ -114,10 +111,13 @@ abstract class BaseTunnel(
|
||||
|
||||
override suspend fun startTunnel(tunnelConf: TunnelConf) {
|
||||
if (activeTuns.exists(tunnelConf.id) || tunThreads.containsKey(tunnelConf.id)) return
|
||||
// stop active tunnels if we are userspace
|
||||
if (this@BaseTunnel is UserspaceTunnel) stopActiveTunnels()
|
||||
tunMutex.withLock {
|
||||
tunThreads[tunnelConf.id] = thread {
|
||||
runCatching {
|
||||
// use thread to interrupt java backend if stuck (like in dns resolution)
|
||||
tunThreads +=
|
||||
tunnelConf.id to
|
||||
thread {
|
||||
runBlocking {
|
||||
try {
|
||||
Timber.d("Starting tunnel ${tunnelConf.id}...")
|
||||
@@ -127,32 +127,23 @@ abstract class BaseTunnel(
|
||||
Timber.e(e, "Failed to start tunnel ${tunnelConf.name} userspace")
|
||||
updateTunnelStatus(tunnelConf, TunnelStatus.Error(e))
|
||||
} catch (e: InterruptedException) {
|
||||
Timber.w(
|
||||
Timber.i(
|
||||
"Tunnel start has been interrupted as ${tunnelConf.name} failed to start"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
.onFailure { Timber.w("Tunnel start has been interrupted") }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun startTunnelInner(tunnelConf: TunnelConf) {
|
||||
configureTunnelCallbacks(tunnelConf)
|
||||
Timber.d("Starting backend for tunnel ${tunnelConf.id}...")
|
||||
try {
|
||||
startBackend(tunnelConf)
|
||||
updateTunnelStatus(tunnelConf, TunnelStatus.Up)
|
||||
Timber.d("Started for tun ${tunnelConf.id}...")
|
||||
saveTunnelActiveState(tunnelConf, true)
|
||||
serviceManager.startTunnelForegroundService()
|
||||
} catch (e: BackendException) {
|
||||
Timber.e(e, "Failed to start backend for ${tunnelConf.name}")
|
||||
val backendError = e.toBackendError()
|
||||
updateTunnelStatus(tunnelConf, TunnelStatus.Error(backendError))
|
||||
throw backendError
|
||||
}
|
||||
Timber.d("Started backend for tunnel ${tunnelConf.id}...")
|
||||
startBackend(tunnelConf)
|
||||
updateTunnelStatus(tunnelConf, TunnelStatus.Up)
|
||||
Timber.d("DONE for tun ${tunnelConf.id}...")
|
||||
saveTunnelActiveState(tunnelConf, true)
|
||||
serviceManager.startTunnelForegroundService()
|
||||
}
|
||||
|
||||
private suspend fun saveTunnelActiveState(tunnelConf: TunnelConf, active: Boolean) {
|
||||
@@ -182,9 +173,9 @@ abstract class BaseTunnel(
|
||||
removeActiveTunnel(tunnel)
|
||||
}
|
||||
|
||||
private suspend fun handleServiceStateOnChange() {
|
||||
if (activeTuns.value.isEmpty() && bouncingTunnelIds.isEmpty())
|
||||
serviceManager.stopTunnelForegroundService()
|
||||
private suspend fun handleServiceChangesOnStop() {
|
||||
if (activeTuns.value.isEmpty() && !isBouncing.get())
|
||||
return serviceManager.stopTunnelForegroundService()
|
||||
}
|
||||
|
||||
private suspend fun handleStuckStartingTunnelShutdown(tunnel: TunnelConf) {
|
||||
@@ -214,23 +205,11 @@ abstract class BaseTunnel(
|
||||
}
|
||||
|
||||
override suspend fun bounceTunnel(tunnelConf: TunnelConf, reason: TunnelStatus.StopReason) {
|
||||
bounceTunnelMutex.withLock {
|
||||
Timber.i(
|
||||
"Bounce tunnel ${tunnelConf.name} for reason: $reason, current bouncing: ${bouncingTunnelIds.size}"
|
||||
)
|
||||
bouncingTunnelIds[tunnelConf.id] = reason
|
||||
try {
|
||||
stopTunnel(tunnelConf, reason)
|
||||
delay(300L)
|
||||
startTunnel(tunnelConf)
|
||||
} finally {
|
||||
bouncingTunnelIds.remove(tunnelConf.id)
|
||||
handleServiceStateOnChange()
|
||||
Timber.d(
|
||||
"Cleared bounce state for ${tunnelConf.name}, remaining: ${bouncingTunnelIds.size}"
|
||||
)
|
||||
}
|
||||
}
|
||||
Timber.i("Bounce tunnel ${tunnelConf.name}")
|
||||
isBouncing.set(true)
|
||||
stopTunnel(tunnelConf, reason)
|
||||
startTunnel(tunnelConf)
|
||||
isBouncing.set(false)
|
||||
}
|
||||
|
||||
override suspend fun runningTunnelNames(): Set<String> =
|
||||
|
||||
@@ -9,7 +9,6 @@ import com.zaneschepke.wireguardautotunnel.domain.enums.BackendState
|
||||
import com.zaneschepke.wireguardautotunnel.domain.enums.TunnelStatus
|
||||
import com.zaneschepke.wireguardautotunnel.domain.repository.AppDataRepository
|
||||
import com.zaneschepke.wireguardautotunnel.domain.state.TunnelStatistics
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
import javax.inject.Inject
|
||||
import kotlinx.coroutines.CoroutineDispatcher
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
@@ -62,9 +61,6 @@ constructor(
|
||||
initialValue = emptyMap(),
|
||||
)
|
||||
|
||||
override val bouncingTunnelIds: ConcurrentHashMap<Int, TunnelStatus.StopReason> =
|
||||
tunnelProviderFlow.value.bouncingTunnelIds
|
||||
|
||||
override fun hasVpnPermission(): Boolean {
|
||||
return userspaceTunnel.hasVpnPermission()
|
||||
}
|
||||
@@ -82,11 +78,11 @@ constructor(
|
||||
}
|
||||
|
||||
override suspend fun stopTunnel(tunnelConf: TunnelConf?, reason: TunnelStatus.StopReason) {
|
||||
tunnelProviderFlow.value.stopTunnel(tunnelConf, reason)
|
||||
tunnelProviderFlow.value.stopTunnel(tunnelConf)
|
||||
}
|
||||
|
||||
override suspend fun bounceTunnel(tunnelConf: TunnelConf, reason: TunnelStatus.StopReason) {
|
||||
tunnelProviderFlow.value.bounceTunnel(tunnelConf, reason)
|
||||
tunnelProviderFlow.value.bounceTunnel(tunnelConf)
|
||||
}
|
||||
|
||||
override fun setBackendState(backendState: BackendState, allowedIps: Collection<String>) {
|
||||
|
||||
@@ -5,32 +5,16 @@ import com.zaneschepke.wireguardautotunnel.domain.enums.BackendState
|
||||
import com.zaneschepke.wireguardautotunnel.domain.enums.TunnelStatus
|
||||
import com.zaneschepke.wireguardautotunnel.domain.state.TunnelState
|
||||
import com.zaneschepke.wireguardautotunnel.domain.state.TunnelStatistics
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
|
||||
interface TunnelProvider {
|
||||
/** Starts the specified tunnel configuration. */
|
||||
suspend fun startTunnel(tunnelConf: TunnelConf)
|
||||
|
||||
/**
|
||||
* Stops the specified tunnel, or all tunnels if none is provided.
|
||||
*
|
||||
* @param tunnelConf The tunnel to stop, or null to stop all active tunnels.
|
||||
* @param reason The reason for stopping, defaults to USER for manual stops. Callers should
|
||||
* override with specific reasons (e.g., PING, CONFIG_CHANGED) when applicable.
|
||||
*/
|
||||
suspend fun stopTunnel(
|
||||
tunnelConf: TunnelConf? = null,
|
||||
reason: TunnelStatus.StopReason = TunnelStatus.StopReason.USER,
|
||||
)
|
||||
|
||||
/**
|
||||
* Bounces (stops and restarts) the specified tunnel.
|
||||
*
|
||||
* @param tunnelConf The tunnel to bounce.
|
||||
* @param reason The reason for bouncing, defaults to USER for manual actions. Callers should
|
||||
* override with specific reasons (e.g., PING, CONFIG_CHANGED) when applicable.
|
||||
*/
|
||||
suspend fun bounceTunnel(
|
||||
tunnelConf: TunnelConf,
|
||||
reason: TunnelStatus.StopReason = TunnelStatus.StopReason.USER,
|
||||
@@ -46,8 +30,6 @@ interface TunnelProvider {
|
||||
|
||||
val activeTunnels: StateFlow<Map<TunnelConf, TunnelState>>
|
||||
|
||||
val bouncingTunnelIds: ConcurrentHashMap<Int, TunnelStatus.StopReason>
|
||||
|
||||
fun hasVpnPermission(): Boolean
|
||||
|
||||
suspend fun clearError(tunnelConf: TunnelConf)
|
||||
|
||||
@@ -24,6 +24,7 @@ class DataStoreManager(
|
||||
companion object {
|
||||
val locationDisclosureShown = booleanPreferencesKey("LOCATION_DISCLOSURE_SHOWN")
|
||||
val batteryDisableShown = booleanPreferencesKey("BATTERY_OPTIMIZE_DISABLE_SHOWN")
|
||||
val currentSSID = stringPreferencesKey("CURRENT_SSID")
|
||||
val pinLockEnabled = booleanPreferencesKey("PIN_LOCK_ENABLED")
|
||||
val tunnelStatsExpanded = booleanPreferencesKey("TUNNEL_STATS_EXPANDED")
|
||||
val isLocalLogsEnabled = booleanPreferencesKey("LOCAL_LOGS_ENABLED")
|
||||
|
||||
@@ -21,7 +21,7 @@ data class GeneralState(
|
||||
isBatteryOptimizationDisableShown,
|
||||
isPinLockEnabled,
|
||||
isTunnelStatsExpanded,
|
||||
isLocalLogsEnabled,
|
||||
isLocationDisclosureShown,
|
||||
isRemoteControlEnabled,
|
||||
remoteKey,
|
||||
locale,
|
||||
|
||||
@@ -35,7 +35,7 @@ class RepositoryModule {
|
||||
AppDatabase::class.java,
|
||||
context.getString(R.string.db_name),
|
||||
)
|
||||
.fallbackToDestructiveMigration(true)
|
||||
.fallbackToDestructiveMigration()
|
||||
.addCallback(DatabaseCallback())
|
||||
.build()
|
||||
}
|
||||
|
||||
@@ -3,7 +3,11 @@ package com.zaneschepke.wireguardautotunnel.domain.entity
|
||||
import com.wireguard.android.backend.Tunnel
|
||||
import com.wireguard.config.Config
|
||||
import com.zaneschepke.wireguardautotunnel.util.Constants
|
||||
import com.zaneschepke.wireguardautotunnel.util.extensions.*
|
||||
import com.zaneschepke.wireguardautotunnel.util.extensions.defaultName
|
||||
import com.zaneschepke.wireguardautotunnel.util.extensions.extractNameAndNumber
|
||||
import com.zaneschepke.wireguardautotunnel.util.extensions.hasNumberInParentheses
|
||||
import com.zaneschepke.wireguardautotunnel.util.extensions.isReachable
|
||||
import com.zaneschepke.wireguardautotunnel.util.extensions.toWgQuickString
|
||||
import java.io.InputStream
|
||||
import java.net.InetAddress
|
||||
import java.nio.charset.StandardCharsets
|
||||
@@ -26,7 +30,6 @@ data class TunnelConf(
|
||||
val pingIp: String? = null,
|
||||
val isEthernetTunnel: Boolean = false,
|
||||
val isIpv4Preferred: Boolean = true,
|
||||
val useCache: Boolean = false,
|
||||
@Transient private var stateChangeCallback: ((Any) -> Unit)? = null,
|
||||
) : Tunnel, org.amnezia.awg.backend.Tunnel {
|
||||
|
||||
@@ -92,9 +95,21 @@ data class TunnelConf(
|
||||
isEthernetTunnel,
|
||||
isIpv4Preferred,
|
||||
)
|
||||
.apply { stateChangeCallback = this@TunnelConf.stateChangeCallback }
|
||||
.apply {
|
||||
stateChangeCallback = this@TunnelConf.stateChangeCallback
|
||||
// tunnelStatsCallback = this@TunnelConf.tunnelStatsCallback
|
||||
// bounceTunnelCallback = this@TunnelConf.bounceTunnelCallback
|
||||
}
|
||||
}
|
||||
|
||||
// fun onUpdateStatistics() {
|
||||
// tunnelStatsCallback?.invoke()
|
||||
// }
|
||||
//
|
||||
// fun bounceTunnel(tunnelConf: TunnelConf, reason: TunnelStatus.StopReason) {
|
||||
// bounceTunnelCallback?.invoke(tunnelConf, reason)
|
||||
// }
|
||||
|
||||
fun toAmConfig(): org.amnezia.awg.config.Config {
|
||||
return configFromAmQuick(amQuick.ifBlank { wgQuick })
|
||||
}
|
||||
@@ -107,8 +122,6 @@ data class TunnelConf(
|
||||
|
||||
override fun isIpv4ResolutionPreferred(): Boolean = isIpv4Preferred
|
||||
|
||||
override fun useCache(): Boolean = useCache
|
||||
|
||||
override fun onStateChange(newState: org.amnezia.awg.backend.Tunnel.State) {
|
||||
stateChangeCallback?.invoke(newState)
|
||||
}
|
||||
@@ -117,6 +130,12 @@ data class TunnelConf(
|
||||
stateChangeCallback?.invoke(newState)
|
||||
}
|
||||
|
||||
fun isTunnelConfigChanged(updatedConf: TunnelConf): Boolean {
|
||||
return updatedConf.wgQuick != wgQuick ||
|
||||
updatedConf.amQuick != amQuick ||
|
||||
updatedConf.name != name
|
||||
}
|
||||
|
||||
fun generateUniqueName(tunnelNames: List<String>): String {
|
||||
var tunnelName = this.tunName
|
||||
var num = 1
|
||||
|
||||
@@ -33,18 +33,18 @@ fun ExpandingRowListItem(
|
||||
Box(
|
||||
modifier =
|
||||
Modifier.animateContentSize()
|
||||
.clip(RoundedCornerShape(8.dp))
|
||||
.clip(RoundedCornerShape(30.dp))
|
||||
.combinedClickable(onClick = { onClick() }, onLongClick = { onHold() })
|
||||
) {
|
||||
Column {
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth().padding(horizontal = 12.dp),
|
||||
modifier = Modifier.fillMaxWidth().padding(horizontal = 15.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(16.dp),
|
||||
horizontalArrangement = Arrangement.spacedBy(15.dp),
|
||||
modifier = Modifier.fillMaxWidth(13 / 20f),
|
||||
) {
|
||||
leading()
|
||||
|
||||
@@ -47,7 +47,7 @@ fun SelectionItemButton(
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.Start,
|
||||
modifier = Modifier.fillMaxSize().padding(horizontal = 12.dp),
|
||||
modifier = Modifier.fillMaxSize().padding(end = 10.dp),
|
||||
) {
|
||||
leading?.let { it() }
|
||||
Text(
|
||||
|
||||
@@ -1,16 +1,22 @@
|
||||
package com.zaneschepke.wireguardautotunnel.ui.common.button.surface
|
||||
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.Card
|
||||
import androidx.compose.material3.CardDefaults
|
||||
import androidx.compose.material3.HorizontalDivider
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.zaneschepke.wireguardautotunnel.ui.theme.iconSize
|
||||
|
||||
@@ -21,17 +27,16 @@ fun SurfaceSelectionGroupButton(items: List<SelectionItem>) {
|
||||
shape = RoundedCornerShape(8.dp),
|
||||
colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surface),
|
||||
) {
|
||||
items.map { item ->
|
||||
items.mapIndexed { index, item ->
|
||||
Box(
|
||||
contentAlignment = Alignment.Center,
|
||||
modifier =
|
||||
Modifier.fillMaxWidth()
|
||||
.clip(RoundedCornerShape(8.dp))
|
||||
.then(item.onClick?.let { Modifier.clickable { it() } } ?: Modifier),
|
||||
Modifier.then(item.onClick?.let { Modifier.clickable { it() } } ?: Modifier)
|
||||
.fillMaxWidth(),
|
||||
) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier.fillMaxWidth().padding(horizontal = 12.dp),
|
||||
modifier = Modifier.fillMaxWidth().padding(vertical = 4.dp),
|
||||
) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
@@ -52,7 +57,6 @@ fun SurfaceSelectionGroupButton(items: List<SelectionItem>) {
|
||||
modifier =
|
||||
Modifier.fillMaxWidth()
|
||||
.padding(start = if (item.leadingIcon != null) 16.dp else 0.dp)
|
||||
.weight(1f)
|
||||
.padding(
|
||||
vertical = if (item.description == null) 16.dp else 6.dp
|
||||
),
|
||||
@@ -64,13 +68,15 @@ fun SurfaceSelectionGroupButton(items: List<SelectionItem>) {
|
||||
item.trailing?.let {
|
||||
Box(
|
||||
contentAlignment = Alignment.CenterEnd,
|
||||
modifier = Modifier.padding(start = 16.dp),
|
||||
modifier = Modifier.padding(start = 16.dp).weight(1f),
|
||||
) {
|
||||
it()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (index + 1 != items.size)
|
||||
HorizontalDivider(color = MaterialTheme.colorScheme.outline.copy(0.30f))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,16 +6,11 @@ import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
|
||||
@Composable
|
||||
fun GroupLabel(title: String, modifier: Modifier = Modifier) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.Start,
|
||||
modifier = modifier,
|
||||
) {
|
||||
fun GroupLabel(title: String) {
|
||||
Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.Start) {
|
||||
Text(
|
||||
title,
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
|
||||
@@ -8,8 +8,6 @@ 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.material3.HorizontalDivider
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
@@ -92,12 +90,12 @@ fun AutoTunnelScreen(uiState: AppUiState, viewModel: AppViewModel) {
|
||||
|
||||
Column(
|
||||
horizontalAlignment = Alignment.Start,
|
||||
verticalArrangement = Arrangement.spacedBy(12.dp, Alignment.Top),
|
||||
verticalArrangement = Arrangement.spacedBy(24.dp, Alignment.Top),
|
||||
modifier =
|
||||
Modifier.fillMaxSize()
|
||||
.verticalScroll(rememberScrollState())
|
||||
.padding(vertical = 24.dp)
|
||||
.padding(horizontal = 12.dp),
|
||||
.padding(horizontal = 24.dp),
|
||||
) {
|
||||
SurfaceSelectionGroupButton(
|
||||
items =
|
||||
@@ -109,9 +107,7 @@ fun AutoTunnelScreen(uiState: AppUiState, viewModel: AppViewModel) {
|
||||
{ isWifiNameReadable() },
|
||||
)
|
||||
)
|
||||
HorizontalDivider(color = MaterialTheme.colorScheme.outline.copy(0.30f))
|
||||
SurfaceSelectionGroupButton(items = NetworkTunnelingItems(uiState, viewModel))
|
||||
HorizontalDivider(color = MaterialTheme.colorScheme.outline.copy(0.30f))
|
||||
SurfaceSelectionGroupButton(
|
||||
items =
|
||||
listOf(
|
||||
|
||||
@@ -18,12 +18,12 @@ import com.zaneschepke.wireguardautotunnel.viewmodel.AppViewModel
|
||||
fun AutoTunnelAdvancedScreen(appUiState: AppUiState, viewModel: AppViewModel) {
|
||||
Column(
|
||||
horizontalAlignment = Alignment.Start,
|
||||
verticalArrangement = Arrangement.spacedBy(12.dp, Alignment.Top),
|
||||
verticalArrangement = Arrangement.spacedBy(24.dp, Alignment.Top),
|
||||
modifier =
|
||||
Modifier.fillMaxSize()
|
||||
.verticalScroll(rememberScrollState())
|
||||
.padding(vertical = 24.dp)
|
||||
.padding(horizontal = 12.dp),
|
||||
.padding(top = 24.dp)
|
||||
.padding(horizontal = 24.dp),
|
||||
) {
|
||||
DebounceDelaySelector(
|
||||
currentDelay = appUiState.appSettings.debounceDelaySeconds,
|
||||
|
||||
@@ -121,7 +121,8 @@ fun MainScreen(appUiState: AppUiState, appViewState: AppViewState, viewModel: Ap
|
||||
viewModel.handleEvent(AppEvent.CopyTunnel(it))
|
||||
viewModel.handleEvent(AppEvent.SetSelectedTunnel(null))
|
||||
},
|
||||
modifier = Modifier.fillMaxSize().padding(vertical = 24.dp).padding(horizontal = 12.dp),
|
||||
modifier =
|
||||
Modifier.fillMaxSize().padding(top = 12.dp, bottom = 24.dp).padding(horizontal = 12.dp),
|
||||
viewModel = viewModel,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -35,12 +35,12 @@ fun TunnelAutoTunnelScreen(
|
||||
|
||||
Column(
|
||||
horizontalAlignment = Alignment.Start,
|
||||
verticalArrangement = Arrangement.spacedBy(12.dp, Alignment.Top),
|
||||
verticalArrangement = Arrangement.spacedBy(24.dp, Alignment.Top),
|
||||
modifier =
|
||||
Modifier.fillMaxSize()
|
||||
.verticalScroll(rememberScrollState())
|
||||
.padding(vertical = 24.dp)
|
||||
.padding(horizontal = 12.dp),
|
||||
.padding(top = 24.dp)
|
||||
.padding(horizontal = 24.dp),
|
||||
) {
|
||||
SurfaceSelectionGroupButton(
|
||||
items =
|
||||
|
||||
@@ -50,7 +50,6 @@ constructor(
|
||||
tunnelId?.let { loadInitialState(it) }
|
||||
}
|
||||
|
||||
// TODO improve this loading experience
|
||||
private fun loadInitialState(tunnelId: Int) =
|
||||
viewModelScope.launch {
|
||||
val tunnel = tunnelRepository.getById(tunnelId) ?: return@launch
|
||||
@@ -109,7 +108,6 @@ constructor(
|
||||
loading = false,
|
||||
tunnelConf = tunnel,
|
||||
tunneledApps = tunneledApps,
|
||||
queriedApps = tunneledApps,
|
||||
splitOption = splitOption,
|
||||
)
|
||||
}
|
||||
@@ -118,14 +116,14 @@ constructor(
|
||||
fun onSearchQuery(query: String) {
|
||||
val filteredApps =
|
||||
if (query.isBlank()) {
|
||||
_uiState.value.tunneledApps
|
||||
allTunneledApps
|
||||
} else {
|
||||
_uiState.value.tunneledApps.filter {
|
||||
allTunneledApps.filter {
|
||||
it.first.name.contains(query, ignoreCase = true) ||
|
||||
it.first.`package`.contains(query, ignoreCase = true)
|
||||
}
|
||||
}
|
||||
_uiState.update { it.copy(searchQuery = query, queriedApps = filteredApps) }
|
||||
_uiState.update { it.copy(searchQuery = query, tunneledApps = filteredApps) }
|
||||
}
|
||||
|
||||
fun updateSplitOption(newOption: SplitOption) {
|
||||
@@ -138,12 +136,7 @@ constructor(
|
||||
currentState.tunneledApps.map { (app, selected) ->
|
||||
if (app.`package` == packageName) Pair(app, !selected) else Pair(app, selected)
|
||||
}
|
||||
val updatedQueryApps =
|
||||
currentState.queriedApps.map { (app, selected) ->
|
||||
if (app.`package` == packageName) Pair(app, !selected) else Pair(app, selected)
|
||||
}
|
||||
_uiState.value =
|
||||
currentState.copy(tunneledApps = updatedApps, queriedApps = updatedQueryApps)
|
||||
_uiState.value = currentState.copy(tunneledApps = updatedApps)
|
||||
}
|
||||
|
||||
fun saveChanges() =
|
||||
|
||||
@@ -29,7 +29,7 @@ fun SplitTunnelContent(
|
||||
)
|
||||
if (uiState.splitOption != SplitOption.ALL) {
|
||||
AppListSection(
|
||||
apps = uiState.queriedApps,
|
||||
apps = uiState.tunneledApps,
|
||||
onAppSelectionToggle = onAppSelectionToggle,
|
||||
onQueryChange = onQueryChange,
|
||||
uiState.searchQuery,
|
||||
|
||||
@@ -6,7 +6,6 @@ data class SplitTunnelUiState(
|
||||
val loading: Boolean = true,
|
||||
val tunnelConf: TunnelConf? = null,
|
||||
val tunneledApps: SplitTunnelApps = emptyList(),
|
||||
val queriedApps: SplitTunnelApps = emptyList(),
|
||||
val splitOption: SplitOption = SplitOption.ALL,
|
||||
val searchQuery: String = "",
|
||||
val success: Boolean? = null,
|
||||
|
||||
@@ -6,8 +6,6 @@ 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.material3.HorizontalDivider
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
@@ -36,12 +34,12 @@ fun TunnelOptionsScreen(tunnelConf: TunnelConf, appUiState: AppUiState, viewMode
|
||||
|
||||
Column(
|
||||
horizontalAlignment = Alignment.Start,
|
||||
verticalArrangement = Arrangement.spacedBy(12.dp, Alignment.Top),
|
||||
verticalArrangement = Arrangement.spacedBy(24.dp, Alignment.Top),
|
||||
modifier =
|
||||
Modifier.fillMaxSize()
|
||||
.verticalScroll(rememberScrollState())
|
||||
.padding(top = 24.dp)
|
||||
.padding(horizontal = 12.dp),
|
||||
.padding(horizontal = 24.dp),
|
||||
) {
|
||||
SurfaceSelectionGroupButton(
|
||||
items =
|
||||
@@ -52,7 +50,6 @@ fun TunnelOptionsScreen(tunnelConf: TunnelConf, appUiState: AppUiState, viewMode
|
||||
SplitTunnelingItem(tunnelConf),
|
||||
)
|
||||
)
|
||||
HorizontalDivider(color = MaterialTheme.colorScheme.outline.copy(0.30f))
|
||||
SurfaceSelectionGroupButton(
|
||||
items =
|
||||
buildList {
|
||||
|
||||
@@ -8,8 +8,6 @@ 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.material3.HorizontalDivider
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
@@ -50,12 +48,13 @@ fun SettingsScreen(uiState: AppUiState, appViewState: AppViewState, viewModel: A
|
||||
|
||||
Column(
|
||||
horizontalAlignment = Alignment.Start,
|
||||
verticalArrangement = Arrangement.spacedBy(12.dp, Alignment.Top),
|
||||
verticalArrangement = Arrangement.spacedBy(24.dp, Alignment.Top),
|
||||
modifier =
|
||||
Modifier.verticalScroll(rememberScrollState())
|
||||
.fillMaxSize()
|
||||
.padding(vertical = 24.dp)
|
||||
.padding(horizontal = 12.dp)
|
||||
.padding(top = 24.dp)
|
||||
.padding(bottom = 40.dp)
|
||||
.padding(horizontal = 24.dp)
|
||||
.then(
|
||||
if (!isRunningOnTv) {
|
||||
Modifier.clickable(
|
||||
@@ -77,7 +76,6 @@ fun SettingsScreen(uiState: AppUiState, appViewState: AppViewState, viewModel: A
|
||||
add(RestartAtBootItem(uiState, viewModel))
|
||||
}
|
||||
)
|
||||
HorizontalDivider(color = MaterialTheme.colorScheme.outline.copy(0.30f))
|
||||
SurfaceSelectionGroupButton(
|
||||
items =
|
||||
buildList {
|
||||
@@ -87,10 +85,8 @@ fun SettingsScreen(uiState: AppUiState, appViewState: AppViewState, viewModel: A
|
||||
add(PinLockItem(uiState, viewModel))
|
||||
}
|
||||
)
|
||||
HorizontalDivider(color = MaterialTheme.colorScheme.outline.copy(0.30f))
|
||||
if (!isRunningOnTv) {
|
||||
SurfaceSelectionGroupButton(items = listOf(KernelModeItem(uiState, viewModel)))
|
||||
HorizontalDivider(color = MaterialTheme.colorScheme.outline.copy(0.30f))
|
||||
}
|
||||
SurfaceSelectionGroupButton(
|
||||
items =
|
||||
|
||||
@@ -38,12 +38,12 @@ fun SettingsAdvancedScreen(appUiState: AppUiState, viewModel: AppViewModel) {
|
||||
|
||||
Column(
|
||||
horizontalAlignment = Alignment.Start,
|
||||
verticalArrangement = Arrangement.spacedBy(12.dp, Alignment.Top),
|
||||
verticalArrangement = Arrangement.spacedBy(24.dp, Alignment.Top),
|
||||
modifier =
|
||||
Modifier.fillMaxSize()
|
||||
.verticalScroll(rememberScrollState())
|
||||
.padding(vertical = 24.dp)
|
||||
.padding(horizontal = 12.dp),
|
||||
.padding(top = 24.dp)
|
||||
.padding(horizontal = 24.dp),
|
||||
) {
|
||||
SurfaceSelectionGroupButton(listOf(RemoteControlItem(appUiState, viewModel)))
|
||||
}
|
||||
|
||||
@@ -4,8 +4,6 @@ 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.material3.HorizontalDivider
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
@@ -19,13 +17,11 @@ import com.zaneschepke.wireguardautotunnel.ui.screens.settings.appearance.compon
|
||||
fun AppearanceScreen() {
|
||||
Column(
|
||||
horizontalAlignment = Alignment.Start,
|
||||
verticalArrangement = Arrangement.spacedBy(12.dp, Alignment.Top),
|
||||
modifier = Modifier.fillMaxSize().padding(vertical = 24.dp).padding(horizontal = 12.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(24.dp, Alignment.Top),
|
||||
modifier = Modifier.fillMaxSize().padding(top = 24.dp).padding(horizontal = 24.dp),
|
||||
) {
|
||||
SurfaceSelectionGroupButton(items = listOf(LanguageItem()))
|
||||
HorizontalDivider(color = MaterialTheme.colorScheme.outline.copy(0.30f))
|
||||
SurfaceSelectionGroupButton(items = listOf(NotificationsItem()))
|
||||
HorizontalDivider(color = MaterialTheme.colorScheme.outline.copy(0.30f))
|
||||
SurfaceSelectionGroupButton(items = listOf(DisplayThemeItem()))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,13 +8,11 @@ import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.zaneschepke.wireguardautotunnel.ui.screens.settings.appearance.language.components.AutomaticLanguageItem
|
||||
import com.zaneschepke.wireguardautotunnel.ui.screens.settings.appearance.language.components.LanguageItem
|
||||
import com.zaneschepke.wireguardautotunnel.ui.state.AppUiState
|
||||
import com.zaneschepke.wireguardautotunnel.util.LocaleUtil
|
||||
import com.zaneschepke.wireguardautotunnel.util.extensions.isRunningOnTv
|
||||
import com.zaneschepke.wireguardautotunnel.viewmodel.AppViewModel
|
||||
import java.text.Collator
|
||||
import java.util.*
|
||||
@@ -23,9 +21,6 @@ import java.util.*
|
||||
fun LanguageScreen(appUiState: AppUiState, viewModel: AppViewModel) {
|
||||
val collator = Collator.getInstance(Locale.getDefault())
|
||||
|
||||
val context = LocalContext.current
|
||||
val isAndroidTv = remember { context.isRunningOnTv() }
|
||||
|
||||
val locales =
|
||||
LocaleUtil.supportedLocales.map {
|
||||
val tag = it.replace("_", "-")
|
||||
@@ -40,11 +35,9 @@ fun LanguageScreen(appUiState: AppUiState, viewModel: AppViewModel) {
|
||||
LazyColumn(
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
verticalArrangement = Arrangement.Top,
|
||||
modifier = Modifier.padding(horizontal = 12.dp),
|
||||
modifier = Modifier.padding(horizontal = 24.dp),
|
||||
) {
|
||||
item { AutomaticLanguageItem(appUiState, viewModel, isAndroidTv) }
|
||||
items(sortedLocales, key = { it }) { locale ->
|
||||
LanguageItem(locale, appUiState, viewModel, isAndroidTv)
|
||||
}
|
||||
item { AutomaticLanguageItem(appUiState, viewModel) }
|
||||
items(sortedLocales, key = { it }) { locale -> LanguageItem(locale, appUiState, viewModel) }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ import com.zaneschepke.wireguardautotunnel.viewmodel.AppViewModel
|
||||
import com.zaneschepke.wireguardautotunnel.viewmodel.event.AppEvent
|
||||
|
||||
@Composable
|
||||
fun AutomaticLanguageItem(appUiState: AppUiState, viewModel: AppViewModel, isAndroidTv: Boolean) {
|
||||
fun AutomaticLanguageItem(appUiState: AppUiState, viewModel: AppViewModel) {
|
||||
Box(modifier = Modifier.padding(top = 24.dp)) {
|
||||
SelectionItemButton(
|
||||
buttonText = stringResource(R.string.automatic),
|
||||
@@ -29,7 +29,7 @@ fun AutomaticLanguageItem(appUiState: AppUiState, viewModel: AppViewModel, isAnd
|
||||
}
|
||||
}
|
||||
},
|
||||
ripple = isAndroidTv,
|
||||
ripple = false,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,12 +9,7 @@ import com.zaneschepke.wireguardautotunnel.viewmodel.event.AppEvent
|
||||
import java.util.*
|
||||
|
||||
@Composable
|
||||
fun LanguageItem(
|
||||
locale: Locale,
|
||||
appUiState: AppUiState,
|
||||
viewModel: AppViewModel,
|
||||
isAndroidTv: Boolean,
|
||||
) {
|
||||
fun LanguageItem(locale: Locale, appUiState: AppUiState, viewModel: AppViewModel) {
|
||||
SelectionItemButton(
|
||||
buttonText =
|
||||
locale.getDisplayLanguage(locale).replaceFirstChar {
|
||||
@@ -33,6 +28,6 @@ fun LanguageItem(
|
||||
SelectedLabel()
|
||||
}
|
||||
},
|
||||
ripple = isAndroidTv,
|
||||
ripple = false,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -4,8 +4,6 @@ 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.material3.HorizontalDivider
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
@@ -24,12 +22,11 @@ fun KillSwitchScreen(uiState: AppUiState, viewModel: AppViewModel) {
|
||||
|
||||
Column(
|
||||
horizontalAlignment = Alignment.Start,
|
||||
verticalArrangement = Arrangement.spacedBy(12.dp, Alignment.Top),
|
||||
modifier = Modifier.fillMaxSize().padding(vertical = 24.dp).padding(horizontal = 12.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(24.dp, Alignment.Top),
|
||||
modifier = Modifier.fillMaxSize().padding(top = 24.dp).padding(horizontal = 24.dp),
|
||||
) {
|
||||
if (!context.isRunningOnTv()) {
|
||||
SurfaceSelectionGroupButton(items = listOf(NativeKillSwitchItem()))
|
||||
HorizontalDivider(color = MaterialTheme.colorScheme.outline.copy(0.30f))
|
||||
}
|
||||
SurfaceSelectionGroupButton(
|
||||
items =
|
||||
|
||||
@@ -6,8 +6,6 @@ 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.material3.HorizontalDivider
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
@@ -27,18 +25,14 @@ fun SupportScreen() {
|
||||
Column(
|
||||
modifier =
|
||||
Modifier.fillMaxSize()
|
||||
.padding(vertical = 24.dp)
|
||||
.padding(horizontal = 12.dp)
|
||||
.padding(top = 24.dp)
|
||||
.padding(horizontal = 24.dp)
|
||||
.verticalScroll(rememberScrollState()),
|
||||
horizontalAlignment = Alignment.Start,
|
||||
verticalArrangement = Arrangement.spacedBy(12.dp, Alignment.Top),
|
||||
verticalArrangement = Arrangement.spacedBy(24.dp, Alignment.Top),
|
||||
) {
|
||||
GroupLabel(
|
||||
stringResource(R.string.thank_you),
|
||||
modifier = Modifier.padding(horizontal = 12.dp),
|
||||
)
|
||||
GroupLabel(stringResource(R.string.thank_you))
|
||||
GeneralSupportOptions(context)
|
||||
HorizontalDivider(color = MaterialTheme.colorScheme.outline.copy(0.30f))
|
||||
ContactSupportOptions(context)
|
||||
VersionLabel()
|
||||
}
|
||||
|
||||
@@ -1,11 +1,8 @@
|
||||
package com.zaneschepke.wireguardautotunnel.ui.screens.settings.logs
|
||||
package com.zaneschepke.wireguardautotunnel.ui.screens.support.logs
|
||||
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
@@ -14,15 +11,11 @@ import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.runtime.snapshotFlow
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.font.FontStyle
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.zaneschepke.wireguardautotunnel.R
|
||||
import com.zaneschepke.wireguardautotunnel.ui.screens.settings.logs.components.LogList
|
||||
import com.zaneschepke.wireguardautotunnel.ui.screens.settings.logs.components.LogsBottomSheet
|
||||
import com.zaneschepke.wireguardautotunnel.ui.screens.support.logs.components.LogList
|
||||
import com.zaneschepke.wireguardautotunnel.ui.screens.support.logs.components.LogsBottomSheet
|
||||
import com.zaneschepke.wireguardautotunnel.ui.state.AppViewState
|
||||
import com.zaneschepke.wireguardautotunnel.viewmodel.AppViewModel
|
||||
|
||||
@@ -69,20 +62,6 @@ fun LogsScreen(appViewState: AppViewState, viewModel: AppViewModel) {
|
||||
LogsBottomSheet(viewModel)
|
||||
}
|
||||
|
||||
if (logs.isEmpty()) {
|
||||
return Box(
|
||||
modifier = Modifier.fillMaxSize().padding(horizontal = 24.dp),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(R.string.nothing_here_yet),
|
||||
fontStyle = FontStyle.Italic,
|
||||
style = MaterialTheme.typography.bodyLarge,
|
||||
color = MaterialTheme.colorScheme.onSurface,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
LogList(
|
||||
logs = logs,
|
||||
lazyColumnListState = lazyColumnListState,
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.zaneschepke.wireguardautotunnel.ui.screens.settings.logs.components
|
||||
package com.zaneschepke.wireguardautotunnel.ui.screens.support.logs.components
|
||||
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.zaneschepke.wireguardautotunnel.ui.screens.settings.logs.components
|
||||
package com.zaneschepke.wireguardautotunnel.ui.screens.support.logs.components
|
||||
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.zaneschepke.wireguardautotunnel.ui.screens.settings.logs.components
|
||||
package com.zaneschepke.wireguardautotunnel.ui.screens.support.logs.components
|
||||
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Row
|
||||
@@ -78,7 +78,6 @@ fun WireguardAutoTunnelTheme(theme: Theme = Theme.AUTOMATIC, content: @Composabl
|
||||
|
||||
val view = LocalView.current
|
||||
if (!view.isInEditMode) {
|
||||
@Suppress("DEPRECATION")
|
||||
SideEffect {
|
||||
val window = (view.context as Activity).window
|
||||
WindowCompat.setDecorFitsSystemWindows(window, false)
|
||||
|
||||
@@ -2,7 +2,6 @@ package com.zaneschepke.wireguardautotunnel.util
|
||||
|
||||
object Constants {
|
||||
|
||||
const val MAX_LOG_SIZE = 10_000L
|
||||
const val BASE_PACKAGE = "com.zaneschepke.wireguardautotunnel"
|
||||
|
||||
const val BASE_LOG_FILE_NAME = "wg_tunnel_logs"
|
||||
|
||||
@@ -27,7 +27,12 @@ import com.zaneschepke.wireguardautotunnel.domain.state.TunnelState
|
||||
import com.zaneschepke.wireguardautotunnel.ui.state.AppUiState
|
||||
import com.zaneschepke.wireguardautotunnel.ui.state.AppViewState
|
||||
import com.zaneschepke.wireguardautotunnel.ui.theme.Theme
|
||||
import com.zaneschepke.wireguardautotunnel.util.*
|
||||
import com.zaneschepke.wireguardautotunnel.util.Constants
|
||||
import com.zaneschepke.wireguardautotunnel.util.FileReadException
|
||||
import com.zaneschepke.wireguardautotunnel.util.FileUtils
|
||||
import com.zaneschepke.wireguardautotunnel.util.InvalidFileExtensionException
|
||||
import com.zaneschepke.wireguardautotunnel.util.LocaleUtil
|
||||
import com.zaneschepke.wireguardautotunnel.util.StringValue
|
||||
import com.zaneschepke.wireguardautotunnel.util.extensions.withFirstState
|
||||
import com.zaneschepke.wireguardautotunnel.viewmodel.event.AppEvent
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
@@ -36,10 +41,20 @@ import java.time.Instant
|
||||
import java.util.*
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Provider
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.CoroutineDispatcher
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.flowOn
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.plus
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.amnezia.awg.config.BadConfigException
|
||||
import org.amnezia.awg.config.Config
|
||||
import timber.log.Timber
|
||||
@@ -58,15 +73,12 @@ constructor(
|
||||
private val logReader: LogReader,
|
||||
private val fileUtils: FileUtils,
|
||||
private val shortcutManager: ShortcutManager,
|
||||
networkMonitor: NetworkMonitor,
|
||||
private val networkMonitor: NetworkMonitor,
|
||||
) : ViewModel() {
|
||||
|
||||
private var logsJob: Job? = null
|
||||
|
||||
private val tunnelMutex = Mutex()
|
||||
private val settingsMutex = Mutex()
|
||||
private val tunControlMutex = Mutex()
|
||||
private val loggerMutex = Mutex()
|
||||
|
||||
private val _screenCallback = MutableStateFlow<(() -> Unit)?>(null)
|
||||
|
||||
@@ -75,7 +87,7 @@ constructor(
|
||||
|
||||
private val _logs = MutableStateFlow<List<LogMessage>>(emptyList())
|
||||
val logs: StateFlow<List<LogMessage>> = _logs.asStateFlow()
|
||||
private val maxLogSize = Constants.MAX_LOG_SIZE
|
||||
private val maxLogSize = 10_000
|
||||
|
||||
val uiState =
|
||||
combine(
|
||||
@@ -88,7 +100,7 @@ constructor(
|
||||
) { array ->
|
||||
val settings = array[0] as AppSettings
|
||||
val tunnels = array[1] as List<TunnelConf>
|
||||
val appState = array[2] as AppState
|
||||
val generalState = array[2] as AppState
|
||||
val activeTunnels = array[3] as Map<TunnelConf, TunnelState>
|
||||
val autoTunnel = array[4] as Boolean
|
||||
val network = array[5] as NetworkStatus
|
||||
@@ -97,7 +109,7 @@ constructor(
|
||||
appSettings = settings,
|
||||
tunnels = tunnels,
|
||||
activeTunnels = activeTunnels,
|
||||
appState = appState,
|
||||
appState = generalState,
|
||||
isAutoTunnelActive = autoTunnel,
|
||||
isAppLoaded = true,
|
||||
networkStatus = network,
|
||||
@@ -115,7 +127,7 @@ constructor(
|
||||
initPin(state.appState.isPinLockEnabled)
|
||||
handleKillSwitchChange(state.appSettings)
|
||||
initServicesFromSavedState(state)
|
||||
if (state.appState.isLocalLogsEnabled) logsJob = startCollectingLogs()
|
||||
if (state.appState.isLocalLogsEnabled) startCollectingLogs()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -213,7 +225,7 @@ constructor(
|
||||
appDataRepository.appState.setIsRemoteControlEnabled(enabled)
|
||||
}
|
||||
|
||||
private fun startCollectingLogs() =
|
||||
private fun startCollectingLogs() {
|
||||
viewModelScope.launch {
|
||||
logReader.bufferedLogs.flowOn(ioDispatcher).collect { logMessage ->
|
||||
_logs.update { currentList ->
|
||||
@@ -226,6 +238,7 @@ constructor(
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun handleAppReadyCheck(tunnels: List<TunnelConf>) {
|
||||
if (tunnels.size == appDataRepository.tunnels.count()) {
|
||||
@@ -243,16 +256,14 @@ constructor(
|
||||
saveTunnel(tunnel.copy(isPingEnabled = !tunnel.isPingEnabled))
|
||||
|
||||
private suspend fun handleToggleLocalLogging(currentlyEnabled: Boolean) {
|
||||
loggerMutex.withLock {
|
||||
val enable = !currentlyEnabled
|
||||
appDataRepository.appState.setLocalLogsEnabled(enable)
|
||||
val enable = !currentlyEnabled
|
||||
appDataRepository.appState.setLocalLogsEnabled(enable)
|
||||
withContext(mainDispatcher) {
|
||||
if (enable) {
|
||||
logsJob?.cancel()
|
||||
logReader.start()
|
||||
logsJob = startCollectingLogs()
|
||||
startCollectingLogs()
|
||||
} else {
|
||||
logReader.stop()
|
||||
logsJob?.cancel()
|
||||
_logs.update { emptyList() }
|
||||
}
|
||||
}
|
||||
@@ -434,8 +445,8 @@ constructor(
|
||||
saveSettings(appSettings.copy(isAlwaysOnVpnEnabled = !appSettings.isAlwaysOnVpnEnabled))
|
||||
|
||||
private suspend fun handleLocaleChange(localeTag: String) {
|
||||
withContext(mainDispatcher) { LocaleUtil.changeLocale(localeTag) }
|
||||
appDataRepository.appState.setLocale(localeTag)
|
||||
LocaleUtil.changeLocale(localeTag)
|
||||
_appViewState.update { it.copy(isConfigChanged = true) }
|
||||
}
|
||||
|
||||
@@ -463,8 +474,6 @@ constructor(
|
||||
}
|
||||
|
||||
private fun handleKillSwitchChange(appSettings: AppSettings) {
|
||||
// let auto tunnel handle kill switch changes if running
|
||||
if (uiState.value.isAutoTunnelActive) return
|
||||
if (!appSettings.isVpnKillSwitchEnabled)
|
||||
return tunnelManager.setBackendState(BackendState.SERVICE_ACTIVE, emptyList())
|
||||
Timber.d("Starting kill switch")
|
||||
@@ -678,13 +687,9 @@ constructor(
|
||||
}
|
||||
|
||||
private suspend fun handleDeleteLogs() {
|
||||
loggerMutex.withLock {
|
||||
logsJob?.cancel()
|
||||
_logs.update { emptyList() }
|
||||
logReader.stop()
|
||||
withContext(mainDispatcher) {
|
||||
logReader.deleteAndClearLogs()
|
||||
logReader.start()
|
||||
logsJob = startCollectingLogs()
|
||||
_logs.update { emptyList() }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:tint="#FFFFFF"
|
||||
android:viewportWidth="640"
|
||||
android:viewportHeight="640">
|
||||
<group
|
||||
android:scaleX="1.0132159"
|
||||
android:scaleY="1.0132159"
|
||||
android:translateX="-4.229075"
|
||||
android:translateY="-4.229075">
|
||||
<path
|
||||
android:fillColor="#53bdb6"
|
||||
android:fillType="evenOdd"
|
||||
android:pathData="M316.72,80.15C314.94,80.82 312.08,83.95 308.79,88.84C275.66,138.15 157.88,161.96 119.66,127.08C109.97,118.24 101.21,118.84 98.97,128.5C96.01,141.29 98.49,204.07 103.12,233.5C123.71,364.32 186.77,465.69 303.03,554.88C314.06,563.34 316.63,563.42 326.93,555.61C329.91,553.35 336.21,548.63 340.93,545.13C345.64,541.63 350.87,537.46 352.55,535.88C354.23,534.29 357.92,531.53 360.75,529.74C413.56,496.43 481.74,399.04 510.38,316C527.22,267.19 534.86,236.66 539.96,197.9C547.99,136.74 545.31,124.46 526,134.01C469.85,161.74 361.02,137.16 333.39,90.49C327.63,80.75 322.93,77.84 316.72,80.15M307.5,195.34C282.24,203.76 266.16,237.38 269.85,274.04C270.99,285.39 271.18,285 264.63,285C254.13,285 254.15,284.87 255,352.05C255.64,402.94 250.1,397.02 298.5,398.52C352.54,400.2 377.63,400.23 379.23,398.63C381.67,396.18 381.86,392.86 382.46,339.89C383.09,283.93 383.39,286 374.51,286C367.07,286 367.21,286.26 367.77,273.58C369.89,225.38 338.74,184.92 307.5,195.34M308.93,216.98C294.31,224.7 281.68,270.05 290.33,283.75C291.74,285.99 346.85,285.51 347.78,283.25C359.48,254.75 330.62,205.51 308.93,216.98M309.54,317.1C304.18,321.81 304.39,327.76 310.11,332.99C314.78,337.26 314.82,336.51 309.33,349.89C307.44,354.51 306.13,358.89 306.42,359.64C306.96,361.06 329,361.75 329,360.35C329,359.98 327.42,354.82 325.5,348.86C323.58,342.91 322,337.52 322,336.89C322,336.26 323.58,334 325.5,331.87C335.69,320.59 321.02,307.02 309.54,317.1"
|
||||
android:strokeColor="#00000000" />
|
||||
</group>
|
||||
</vector>
|
||||
|
Before Width: | Height: | Size: 777 B |
|
After Width: | Height: | Size: 544 B |
|
Before Width: | Height: | Size: 715 B |
|
Before Width: | Height: | Size: 525 B |
|
After Width: | Height: | Size: 382 B |
|
Before Width: | Height: | Size: 471 B |
|
Before Width: | Height: | Size: 1.0 KiB |
|
Before Width: | Height: | Size: 5.1 KiB After Width: | Height: | Size: 4.2 KiB |
|
After Width: | Height: | Size: 698 B |
|
Before Width: | Height: | Size: 987 B |
|
Before Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 1.0 KiB |
|
Before Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 2.1 KiB |
|
Before Width: | Height: | Size: 2.0 KiB |
@@ -0,0 +1,56 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="320dp"
|
||||
android:height="180dp"
|
||||
android:viewportWidth="640"
|
||||
android:viewportHeight="640">
|
||||
<group
|
||||
android:scaleX="0.6666667"
|
||||
android:scaleY="0.6666667"
|
||||
android:translateX="106.666664"
|
||||
android:translateY="106.666664">
|
||||
<group
|
||||
android:scaleX="0.315"
|
||||
android:scaleY="0.56"
|
||||
android:translateX="46.4"
|
||||
android:translateY="140.8">
|
||||
<path
|
||||
android:fillColor="#53bdb6"
|
||||
android:fillType="evenOdd"
|
||||
android:pathData="M316.72,80.15C314.94,80.82 312.08,83.95 308.79,88.84C275.66,138.15 157.88,161.96 119.66,127.08C109.97,118.24 101.21,118.84 98.97,128.5C96.01,141.29 98.49,204.07 103.12,233.5C123.71,364.32 186.77,465.69 303.03,554.88C314.06,563.34 316.63,563.42 326.93,555.61C329.91,553.35 336.21,548.63 340.93,545.13C345.64,541.63 350.87,537.46 352.55,535.88C354.23,534.29 357.92,531.53 360.75,529.74C413.56,496.43 481.74,399.04 510.38,316C527.22,267.19 534.86,236.66 539.96,197.9C547.99,136.74 545.31,124.46 526,134.01C469.85,161.74 361.02,137.16 333.39,90.49C327.63,80.75 322.93,77.84 316.72,80.15M307.5,195.34C282.24,203.76 266.16,237.38 269.85,274.04C270.99,285.39 271.18,285 264.63,285C254.13,285 254.15,284.87 255,352.05C255.64,402.94 250.1,397.02 298.5,398.52C352.54,400.2 377.63,400.23 379.23,398.63C381.67,396.18 381.86,392.86 382.46,339.89C383.09,283.93 383.39,286 374.51,286C367.07,286 367.21,286.26 367.77,273.58C369.89,225.38 338.74,184.92 307.5,195.34M308.93,216.98C294.31,224.7 281.68,270.05 290.33,283.75C291.74,285.99 346.85,285.51 347.78,283.25C359.48,254.75 330.62,205.51 308.93,216.98M309.54,317.1C304.18,321.81 304.39,327.76 310.11,332.99C314.78,337.26 314.82,336.51 309.33,349.89C307.44,354.51 306.13,358.89 306.42,359.64C306.96,361.06 329,361.75 329,360.35C329,359.98 327.42,354.82 325.5,348.86C323.58,342.91 322,337.52 322,336.89C322,336.26 323.58,334 325.5,331.87C335.69,320.59 321.02,307.02 309.54,317.1"
|
||||
android:strokeColor="#00000000" />
|
||||
</group>
|
||||
|
||||
<group
|
||||
android:scaleX="0.52644"
|
||||
android:scaleY="0.20707752"
|
||||
android:translateX="320.4"
|
||||
android:translateY="70.26513">
|
||||
<group android:translateY="132.92308">
|
||||
<path
|
||||
android:fillColor="#53BDB6"
|
||||
android:pathData="M76.06154,-101.68616L87.95077,-101.68616L72.44308,0L55.606155,0L43.93846,-76.72615L32.196922,0L14.990769,0L0.6646154,-101.68616L13.513846,-101.68616L24.36923,-14.104615L36.553844,-89.870766L52.283077,-89.870766L63.876923,-14.104615Z" />
|
||||
<path
|
||||
android:fillColor="#53BDB6"
|
||||
android:pathData="M135.30154,1.6984615Q123.11692,1.6984615,114.25539,-4.1723075Q105.393845,-10.0430765,100.55692,-21.821539Q95.72,-33.6,95.72,-50.88Q95.72,-67.643074,101.62769,-79.495384Q107.535385,-91.347694,117.246155,-97.32923Q126.956924,-103.31077,138.25539,-103.31077Q147.78154,-103.31077,154.42769,-100.61539Q161.07385,-97.92,167.35077,-92.16L159.59692,-84.11077Q154.94461,-88.46769,149.77539,-90.535385Q144.60616,-92.60307,138.32922,-92.60307Q130.42769,-92.60307,123.92923,-88.504616Q117.43077,-84.40615,113.44308,-75.10154Q109.45538,-65.79692,109.45538,-50.88Q109.45538,-29.095385,116.175385,-19.089231Q122.895386,-9.0830765,136.48308,-9.0830765Q146.7477,-9.0830765,155.83076,-13.9569235L155.83076,-44.97231L135.81847,-44.97231L134.26768,-55.606155L168.5323,-55.606155L168.5323,-7.163077Q160.70462,-2.88,152.98769,-0.59076923Q145.27077,1.6984615,135.30154,1.6984615Z" />
|
||||
<path
|
||||
android:fillColor="#53BDB6"
|
||||
android:pathData="M348.30463,-90.683075L317.43692,-90.683075L317.43692,0L304.66153,0L304.66153,-90.683075L272.90768,-90.683075L272.90768,-101.68616L349.63385,-101.68616Z" />
|
||||
<path
|
||||
android:fillColor="#53BDB6"
|
||||
android:pathData="M383.24924,-22.670769Q383.24924,-15.064615,386.3877,-11.630769Q389.52615,-8.196923,396.32,-8.196923Q402.44922,-8.196923,408.24615,-11.741538Q414.0431,-15.286154,417.36615,-20.52923L417.36615,-77.76L429.7723,-77.76L429.7723,0L419.2123,0L418.17847,-10.486154Q413.67386,-4.726154,406.99078,-1.5138462Q400.30768,1.6984615,393.2923,1.6984615Q382.14154,1.6984615,376.4923,-4.246154Q370.84308,-10.190769,370.84308,-21.193846L370.84308,-77.76L383.24924,-77.76Z" />
|
||||
<path
|
||||
android:fillColor="#53BDB6"
|
||||
android:pathData="M459.84308,0L459.84308,-77.76L470.40308,-77.76L471.36307,-66.97846Q476.01538,-72.81231,483.0677,-76.098465Q490.12,-79.38461,497.06152,-79.38461Q508.2123,-79.38461,513.56616,-73.47692Q518.92,-67.56923,518.92,-56.49231L518.92,0L506.51385,0L506.51385,-47.335384Q506.51385,-56.049232,505.59076,-60.627693Q504.6677,-65.206154,501.82462,-67.42154Q498.98154,-69.636925,493.22153,-69.636925Q486.9446,-69.636925,481.36923,-65.76Q475.79385,-61.883076,472.24924,-56.64L472.24924,0Z" />
|
||||
<path
|
||||
android:fillColor="#53BDB6"
|
||||
android:pathData="M548.8431,0L548.8431,-77.76L559.4031,-77.76L560.3631,-66.97846Q565.0154,-72.81231,572.0677,-76.098465Q579.12,-79.38461,586.0615,-79.38461Q597.2123,-79.38461,602.56616,-73.47692Q607.92,-67.56923,607.92,-56.49231L607.92,0L595.51385,0L595.51385,-47.335384Q595.51385,-56.049232,594.59076,-60.627693Q593.66766,-65.206154,590.8246,-67.42154Q587.98157,-69.636925,582.22156,-69.636925Q575.94464,-69.636925,570.3692,-65.76Q564.7938,-61.883076,561.2492,-56.64L561.2492,0Z" />
|
||||
<path
|
||||
android:fillColor="#53BDB6"
|
||||
android:pathData="M647.59076,-34.486153Q647.88617,-25.772308,650.9877,-19.975384Q654.08923,-14.178461,659.1108,-11.409231Q664.1323,-8.64,670.3354,-8.64Q676.0954,-8.64,680.9323,-10.338462Q685.7692,-12.036923,691.0862,-15.655385L696.92,-7.4584618Q691.4554,-3.1753845,684.4031,-0.73846155Q677.35077,1.6984615,670.1877,1.6984615Q659.0369,1.6984615,650.9877,-3.323077Q642.9385,-8.344615,638.7662,-17.50154Q634.5939,-26.65846,634.5939,-38.76923Q634.5939,-50.51077,638.8031,-59.74154Q643.0123,-68.972305,650.6923,-74.17846Q658.3723,-79.38461,668.56305,-79.38461Q678.3108,-79.38461,685.43695,-74.80615Q692.56305,-70.22769,696.36615,-61.624615Q700.16925,-53.021538,700.16925,-41.28Q700.16925,-37.883076,699.87384,-34.486153ZM668.71075,-69.19385Q659.70154,-69.19385,654.0523,-62.88Q648.4031,-56.566154,647.6646,-44.086155L688.2062,-44.086155Q687.9846,-56.344616,682.81537,-62.76923Q677.6462,-69.19385,668.71075,-69.19385Z" />
|
||||
<path
|
||||
android:fillColor="#53BDB6"
|
||||
android:pathData="M757.12,-19.2Q757.12,-13.735385,760.55383,-11.187693Q763.9877,-8.64,769.96924,-8.64Q776.24615,-8.64,783.04,-11.372308L786.3631,-2.2892308Q782.8185,-0.51692307,778.0923,0.59076923Q773.36615,1.6984615,767.90155,1.6984615Q761.0339,1.6984615,755.75385,-0.8861538Q750.4738,-3.4707692,747.5939,-8.344615Q744.71387,-13.218462,744.71387,-19.864614L744.71387,-99.24923L720.8615,-99.24923L720.8615,-109.07077L757.12,-109.07077Z" />
|
||||
</group>
|
||||
</group>
|
||||
</group>
|
||||
</vector>
|
||||
@@ -0,0 +1,17 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="108dp"
|
||||
android:height="108dp"
|
||||
android:viewportWidth="640"
|
||||
android:viewportHeight="640">
|
||||
<group
|
||||
android:scaleX="0.6"
|
||||
android:scaleY="0.6"
|
||||
android:translateX="128"
|
||||
android:translateY="128">
|
||||
<path
|
||||
android:fillColor="#53bdb6"
|
||||
android:fillType="evenOdd"
|
||||
android:pathData="M316.72,80.15C314.94,80.82 312.08,83.95 308.79,88.84C275.66,138.15 157.88,161.96 119.66,127.08C109.97,118.24 101.21,118.84 98.97,128.5C96.01,141.29 98.49,204.07 103.12,233.5C123.71,364.32 186.77,465.69 303.03,554.88C314.06,563.34 316.63,563.42 326.93,555.61C329.91,553.35 336.21,548.63 340.93,545.13C345.64,541.63 350.87,537.46 352.55,535.88C354.23,534.29 357.92,531.53 360.75,529.74C413.56,496.43 481.74,399.04 510.38,316C527.22,267.19 534.86,236.66 539.96,197.9C547.99,136.74 545.31,124.46 526,134.01C469.85,161.74 361.02,137.16 333.39,90.49C327.63,80.75 322.93,77.84 316.72,80.15M307.5,195.34C282.24,203.76 266.16,237.38 269.85,274.04C270.99,285.39 271.18,285 264.63,285C254.13,285 254.15,284.87 255,352.05C255.64,402.94 250.1,397.02 298.5,398.52C352.54,400.2 377.63,400.23 379.23,398.63C381.67,396.18 381.86,392.86 382.46,339.89C383.09,283.93 383.39,286 374.51,286C367.07,286 367.21,286.26 367.77,273.58C369.89,225.38 338.74,184.92 307.5,195.34M308.93,216.98C294.31,224.7 281.68,270.05 290.33,283.75C291.74,285.99 346.85,285.51 347.78,283.25C359.48,254.75 330.62,205.51 308.93,216.98M309.54,317.1C304.18,321.81 304.39,327.76 310.11,332.99C314.78,337.26 314.82,336.51 309.33,349.89C307.44,354.51 306.13,358.89 306.42,359.64C306.96,361.06 329,361.75 329,360.35C329,359.98 327.42,354.82 325.5,348.86C323.58,342.91 322,337.52 322,336.89C322,336.26 323.58,334 325.5,331.87C335.69,320.59 321.02,307.02 309.54,317.1"
|
||||
android:strokeColor="#00000000" />
|
||||
</group>
|
||||
</vector>
|
||||
@@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@color/ic_banner_background"/>
|
||||
<foreground android:drawable="@mipmap/ic_banner_foreground"/>
|
||||
<background android:drawable="@color/ic_banner_background" />
|
||||
<foreground android:drawable="@drawable/ic_banner_foreground" />
|
||||
</adaptive-icon>
|
||||
@@ -1,5 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@color/ic_channel_background"/>
|
||||
<foreground android:drawable="@mipmap/ic_channel_foreground"/>
|
||||
<background android:drawable="@color/ic_channel_background" />
|
||||
</adaptive-icon>
|
||||
@@ -1,6 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@color/ic_launcher_background"/>
|
||||
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
|
||||
<monochrome android:drawable="@mipmap/ic_launcher_foreground"/>
|
||||
<background android:drawable="@color/ic_launcher_background" />
|
||||
<foreground android:drawable="@drawable/ic_launcher_foreground" />
|
||||
<!-- support for adaptive theme icons -->
|
||||
<monochrome android:drawable="@drawable/ic_launcher_foreground" />
|
||||
</adaptive-icon>
|
||||
@@ -1,6 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@color/ic_launcher_background"/>
|
||||
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
|
||||
<monochrome android:drawable="@mipmap/ic_launcher_foreground"/>
|
||||
<background android:drawable="@color/ic_launcher_background" />
|
||||
<foreground android:drawable="@drawable/ic_launcher_foreground" />
|
||||
<!-- support for adaptive theme icons -->
|
||||
<monochrome android:drawable="@drawable/ic_launcher_foreground" />
|
||||
</adaptive-icon>
|
||||
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.0 KiB |
|
Before Width: | Height: | Size: 1000 B |
|
Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 2.5 KiB |
|
Before Width: | Height: | Size: 916 B After Width: | Height: | Size: 796 B |
|
Before Width: | Height: | Size: 672 B |
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 5.1 KiB After Width: | Height: | Size: 4.2 KiB |
|
Before Width: | Height: | Size: 4.2 KiB After Width: | Height: | Size: 3.7 KiB |
|
Before Width: | Height: | Size: 3.8 KiB |
|
Before Width: | Height: | Size: 7.9 KiB |
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 3.4 KiB After Width: | Height: | Size: 3.4 KiB |
|
Before Width: | Height: | Size: 2.3 KiB After Width: | Height: | Size: 2.4 KiB |
|
Before Width: | Height: | Size: 2.0 KiB |
|
Before Width: | Height: | Size: 5.6 KiB After Width: | Height: | Size: 5.5 KiB |
|
Before Width: | Height: | Size: 3.2 KiB After Width: | Height: | Size: 3.1 KiB |
|
Before Width: | Height: | Size: 2.8 KiB |
|
Before Width: | Height: | Size: 7.8 KiB After Width: | Height: | Size: 7.4 KiB |
@@ -32,6 +32,7 @@
|
||||
<string name="name">Name</string>
|
||||
<string name="always_on_vpn_support">Always-On VPN erlauben</string>
|
||||
<string name="location_services_not_detected">Standortdienste nicht erkannt</string>
|
||||
<string name="hint_search_packages">Pakete suchen</string>
|
||||
<string name="vpn_on">VPN an</string>
|
||||
<string name="vpn_off">VPN aus</string>
|
||||
<string name="create_import">Von Grund auf neu erstellen</string>
|
||||
@@ -48,6 +49,7 @@
|
||||
<string name="listen_port">Eingehender Port</string>
|
||||
<string name="random">(zufällig)</string>
|
||||
<string name="optional">(optional)</string>
|
||||
<string name="optional_no_recommend">(Optional, nicht empfohlen)</string>
|
||||
<string name="seconds">Sekunden</string>
|
||||
<string name="cancel">Abbrechen</string>
|
||||
<string name="preshared_key">Geteilter Schlüssel</string>
|
||||
@@ -74,9 +76,12 @@
|
||||
<string name="settings">Einstellungen</string>
|
||||
<string name="support">Unterstützung</string>
|
||||
<string name="error_authentication_failed">Authentifizierung fehlgeschlagen</string>
|
||||
<string name="export_configs">Konfigurationen exportieren</string>
|
||||
<string name="unknown_error">Unbekannter Fehler aufgetreten</string>
|
||||
<string name="email_chooser">Eine E-Mail senden…</string>
|
||||
<string name="error_authorization_failed">Autorisierung fehlgeschlagen</string>
|
||||
<string name="error_invalid_code">Ungültiger QR Code</string>
|
||||
<string name="tunneling_apps">Getunnelte Apps</string>
|
||||
<string name="no_email_detected">Keine E-Mail-App erkannt</string>
|
||||
<string name="create_pin">PIN erstellen</string>
|
||||
<string name="use_tunnel_on_wifi_name">Tunnel für WLAN-Namen verwenden</string>
|
||||
@@ -107,11 +112,13 @@
|
||||
<string name="background_location_message">Für diese Funktion ist die Erlaubnis, den Standort jederzeit zu bestimmen, und/oder ein genauer Standort erforderlich. Bitte prüfe die</string>
|
||||
<string name="vpn_settings">VPN-Systemeinstellungen</string>
|
||||
<string name="always_on_message">Die Genehmigung für eine VPN-Verbindung wurde verweigert. Bitte überprüfe die</string>
|
||||
<string name="chat_description">Tritt der Community bei</string>
|
||||
<string name="set_custom_ping_cooldown">Ping-Neustart-Cooldown (sek)</string>
|
||||
<string name="set_custom_ping_ip">Benutzerdefinierte Ping-IP einstellen</string>
|
||||
<string name="default_ping_ip">(optional, Standardwert: Gegenstelle)</string>
|
||||
<string name="set_custom_ping_internal">Pingintervall (sek)</string>
|
||||
<string name="always_on_message2">um sicherzustellen, dass Always-on VPN für alle anderen Apps ausgeschaltet ist und versuche es erneut</string>
|
||||
<string name="tunnel_required">Feature erfordert mindestens einen Tunnel</string>
|
||||
<string name="sec">Sek</string>
|
||||
<string name="app_settings">App-Einstellungen</string>
|
||||
<string name="background_location_message2">um sicherzustellen, dass diese Berechtigungen aktiviert sind</string>
|
||||
@@ -123,6 +130,7 @@
|
||||
<string name="logs">Logeinträge</string>
|
||||
<string name="kernel_not_supported">Kernel nicht unterstützt</string>
|
||||
<string name="trusted_wifi_names">Vertrauenswürdige WLAN Namen</string>
|
||||
<string name="requires_app_relaunch">Diese Änderung erfordert einen Neustart der App. Möchten Sie fortfahren?</string>
|
||||
<string name="use_root_shell_for_wifi">Root-Shell verwenden, um WLAN-Namen zu ermitteln</string>
|
||||
<string name="light">Hell</string>
|
||||
<string name="add_wifi_name">WLAN Namen hinzufügen</string>
|
||||
@@ -168,6 +176,9 @@
|
||||
<string name="post_up">Nach Aktivierung</string>
|
||||
<string name="pre_down">Vor Deaktivierung</string>
|
||||
<string name="post_down">Nach Deaktivierung</string>
|
||||
<string name="amnezia_kernel_message">Amnezia nicht verfügbar im Kernel-Modus</string>
|
||||
<string name="enable_amnezia">Amnezia aktivieren</string>
|
||||
<string name="wg_compat_mode">Wireguard Kompatibilitätsmodus</string>
|
||||
<string name="quick_actions">Schnellaktionen</string>
|
||||
<string name="stop_on_internet_loss">Stoppen, wenn die Internetverbindung getrennt wird</string>
|
||||
<string name="bypass_lan_for_kill_switch">LAN umgehen für Notschalter</string>
|
||||
@@ -181,50 +192,12 @@
|
||||
<string name="exclude_lan">LAN ausschließen</string>
|
||||
<string name="tunnel_control">Tunnelsteuerung</string>
|
||||
<string name="kill_switch_off">Notschalter stoppen bei vertrauenswürdigen</string>
|
||||
<string name="error_tunnel_start">Start des Tunnels fehlgeschlagen</string>
|
||||
<string name="auto_tunnel">Auto-Tunnel</string>
|
||||
<string name="export_amnezia">Als Amnezia exportieren</string>
|
||||
<string name="export_wireguard">Als WireGuard exportieren</string>
|
||||
<string name="server_ipv4">IPv4 Hostnamensauflösung</string>
|
||||
<string name="prefer_ipv4">IPv4 Verbindung bevorzugen</string>
|
||||
<string name="start_failed_config">Starten des Tunnels wegen Konfigfehler fehlgeschlagen.</string>
|
||||
<string name="multiple">Mehrere</string>
|
||||
<string name="bio_subtitle">Melde dich mit deinen biometrischen Anmeldeinformationen an</string>
|
||||
<string name="select">Auswählen</string>
|
||||
<string name="status">Status</string>
|
||||
<string name="invalid_config_error">ungültige_konfiguration_fehler</string>
|
||||
<string name="matrix_url">https://matrix.to/#/#wg-tunnel-space:matrix.org</string>
|
||||
<string name="search">Suchen</string>
|
||||
<string name="config_error">Konfigurationsfehler</string>
|
||||
<string name="join_matrix">Matrix-Community beitreten</string>
|
||||
<string name="bio_auth_title">Biometrische Authentifizierung</string>
|
||||
<string name="error_download_failed">Download der Konfiguration fehlgeschlagen</string>
|
||||
<string name="add_from_url">Von URL hinzufügen</string>
|
||||
<string name="export_logs">Gespeicherte Logs exportieren</string>
|
||||
<string name="app_permission_title">WG Tunnel Control Bridge</string>
|
||||
<string name="active">Aktiv</string>
|
||||
<string name="delete_logs">Logs leeren und löschen</string>
|
||||
<string name="tunnel_error_template">Tunnel mit: %1$s fehlgeschlagen</string>
|
||||
<string name="save">Speichern</string>
|
||||
<string name="bio_not_supported">Biometrische Daten werden nicht unterstützt</string>
|
||||
<string name="remote_key_template">Schlüssel: %1$s</string>
|
||||
<string name="enter_config_url">Konfigurations-URL eingeben</string>
|
||||
<string name="join_telegram">Telegram-Community beitreten</string>
|
||||
<string name="dropdown">Auswahlliste</string>
|
||||
<string name="add_tunnel">Tunnel hinzufügen</string>
|
||||
<string name="copy">Kopieren</string>
|
||||
<string name="info">Info</string>
|
||||
<string name="kernel_name_error">Fehler im Kernel-Modulnamen</string>
|
||||
<string name="service_running_error">Fehler: Dienst läuft nicht</string>
|
||||
<string name="auth_error">Fehler: nicht autorisiert</string>
|
||||
<string name="inactive">Inaktiv</string>
|
||||
<string name="bio_not_created">Biometrische Daten nicht erstellt</string>
|
||||
<string name="bio_update_required">Biometrisches Sicherheitsupdate erforderlich</string>
|
||||
<string name="tunnel_starting">Starte Tunnel</string>
|
||||
<string name="enable_remote_app_control">App-Fernsteuerung aktivieren</string>
|
||||
<string name="export_tunnels_amnezia">Tunnel als Amnezia exportieren</string>
|
||||
<string name="export_failed">Export fehlgeschlagen</string>
|
||||
<string name="app_permission_description">Steuere Tunnel und Auto-Tunnel Funktionen.</string>
|
||||
<string name="dns_resolve_error">dns-Auflösungsfehler</string>
|
||||
<string name="export_tunnels_wireguard">Tunnel als WireGuard exportieren</string>
|
||||
<string name="camera_permission_required">Kameraberechtigung erforderlich</string>
|
||||
<string name="wifi_name_template">Aktiv: %1$s</string>
|
||||
<string name="delete">Löschen</string>
|
||||
<string name="nothing_here_yet">Noch nix hier!</string>
|
||||
</resources>
|
||||
|
||||
@@ -42,6 +42,7 @@
|
||||
<string name="name">Nombre</string>
|
||||
<string name="always_on_vpn_support">Permitir VPN siempre-activada</string>
|
||||
<string name="location_services_not_detected">Servicios de Ubicación No Detectados</string>
|
||||
<string name="hint_search_packages">Buscar paquetes</string>
|
||||
<string name="auto_tunneling">Túnel-automático</string>
|
||||
<string name="vpn_on">VPN on</string>
|
||||
<string name="vpn_off">VPN off</string>
|
||||
@@ -51,11 +52,13 @@
|
||||
<string name="comma_separated_list">lista separada por comas</string>
|
||||
<string name="random">(aleatorio)</string>
|
||||
<string name="optional">(opcional)</string>
|
||||
<string name="optional_no_recommend">(opcional, no recomendado)</string>
|
||||
<string name="seconds">segundos</string>
|
||||
<string name="cancel">Cancelar</string>
|
||||
<string name="error_authentication_failed">Fallo de autenticación</string>
|
||||
<string name="error_authorization_failed">Fallo de autorización</string>
|
||||
<string name="enabled_app_shortcuts">Habilitar acesos directos de app</string>
|
||||
<string name="export_configs">Exportar configuración</string>
|
||||
<string name="unknown_error">Error desconocido</string>
|
||||
<string name="email_subject">Ayuda WG Tunnel</string>
|
||||
<string name="interface_">Interfaz</string>
|
||||
@@ -68,10 +71,12 @@
|
||||
<string name="error_ssid_exists">SSID existente</string>
|
||||
<string name="error_root_denied">Shell root denegado</string>
|
||||
<string name="error_no_file_explorer">Explorador de archivos no instalado</string>
|
||||
<string name="error_invalid_code">Código QR no valido</string>
|
||||
<string name="auto_tunnel_title">Servicio de túnel-automático</string>
|
||||
<string name="delete_tunnel">Eliminar túnel</string>
|
||||
<string name="delete_tunnel_message">¿Estás seguro de que quieres eliminar este túnel?</string>
|
||||
<string name="yes">Sí</string>
|
||||
<string name="tunneling_apps">Apps por el túnel</string>
|
||||
<string name="no_email_detected">Ninguna app de email detectada</string>
|
||||
<string name="no_browser_detected">Ningún navegador detectado</string>
|
||||
<string name="open_issue">Abrir una incidencia</string>
|
||||
@@ -110,6 +115,8 @@
|
||||
<string name="error_file_format">Configuración del formato del túnel no válida</string>
|
||||
<string name="vpn_denied_dialog_title">Permiso denegado</string>
|
||||
<string name="vpn_settings">Configuraciones VPN del sistema</string>
|
||||
<string name="chat_description">Unirse a la comunidad</string>
|
||||
<string name="tunnel_required">La función requiere al menos un túnel</string>
|
||||
<string name="background_location_message">Permitir todo el tiempo que se requiera permiso de ubicación y/o ubicación precisa para esta función. Consultar</string>
|
||||
<string name="response_packet_junk_size">Tamaño basura del paquete de respuesta</string>
|
||||
<string name="init_packet_magic_header">Encabezado del paquete de inicialización</string>
|
||||
@@ -128,11 +135,13 @@
|
||||
<string name="use_wildcards">Usar comodines de nombre</string>
|
||||
<string name="primary_tunnel">Túnel principal</string>
|
||||
<string name="add_wifi_name">Añadir nombre de wifi</string>
|
||||
<string name="on_demand_rules">Reglas del túnel bajo demanda</string>
|
||||
<string name="kernel_not_supported">Kernel no compatible</string>
|
||||
<string name="start_auto">Iniciar túnel automático</string>
|
||||
<string name="stop_auto">Detener túnel automático</string>
|
||||
<string name="local_logging">Registro local</string>
|
||||
<string name="enable_local_logging">Habilitar registro local</string>
|
||||
<string name="configuration_change">Cambio de configuración</string>
|
||||
<string name="exclude_lan">Excluir LAN</string>
|
||||
<string name="enable_amnezia_compatibility">Activar compatibilidad con Amnezia</string>
|
||||
<string name="wifi_name_via_shell">Nombre del wifi a través del shell</string>
|
||||
@@ -151,6 +160,7 @@
|
||||
<string name="notifications">Notificaciones</string>
|
||||
<string name="light">Claro</string>
|
||||
<string name="dark">Oscuro</string>
|
||||
<string name="requires_app_relaunch">Este cambio requiere un reinicio de la aplicación. ¿Desea continuar?</string>
|
||||
<string name="use_root_shell_for_wifi">Utilizar el shell root para obtener el nombre del wifi</string>
|
||||
<string name="tunnel_running">Túnel funcionando</string>
|
||||
<string name="monitoring_state_changes">Monitorizando cambios de estado</string>
|
||||
@@ -176,13 +186,23 @@
|
||||
<string name="pre_up">Pre up</string>
|
||||
<string name="post_up">Post up</string>
|
||||
<string name="pre_down">Pre down</string>
|
||||
<string name="amnezia_kernel_message">Amnezia no disponible en modo kernel</string>
|
||||
<string name="post_down">Post down</string>
|
||||
<string name="enable_amnezia">Activar Amnezia</string>
|
||||
<string name="wg_compat_mode">Modo compatibilidad de WG</string>
|
||||
<string name="quick_actions">Acciones rápidas</string>
|
||||
<string name="remove_amnezia_compatibility">Eliminar compatibilidad con Amnezia</string>
|
||||
<string name="error_tunnel_start">Fallo al iniciar el túnel</string>
|
||||
<string name="tunnel_control">Control del túnel</string>
|
||||
<string name="auto_tunnel">Túnel automático</string>
|
||||
<string name="kill_switch_off">Detener interruptor de apagado en confianza</string>
|
||||
<string name="server_ipv4">Resolución de host IPv4</string>
|
||||
<string name="prefer_ipv4">Preferir conexión IPv4</string>
|
||||
<string name="dns_error">No se ha podido resolver el DNS del punto final.</string>
|
||||
<string name="start_failed_config">Fallo al iniciar túnel con error de configuración.</string>
|
||||
<string name="unauthorized">Fallo al iniciar túnel, no autorizado.</string>
|
||||
<string name="tunne_start_failed_title">Fallo del túnel</string>
|
||||
<string name="multiple">Múltiple</string>
|
||||
<string name="export_amnezia">Exportar cómo Amnezia</string>
|
||||
<string name="export_wireguard">Exportar cómo WireGuard</string>
|
||||
</resources>
|
||||
|
||||
@@ -47,6 +47,7 @@
|
||||
<string name="email_description">Lähetä minulle sähköpostia</string>
|
||||
<string name="error_ssid_exists">SSID on jo luettelossa</string>
|
||||
<string name="error_no_file_explorer">Tiedostonhallintasovellusta ei ole asennettuna</string>
|
||||
<string name="error_invalid_code">Virheellinen QR-koodi</string>
|
||||
<string name="location_services_missing_message">Sovellus ei tunnista laitteessasi käytössä olevia sijaintipalveluja. Laitteesta riippuen tämä voi aiheuttaa sen, että epäluotettava wlan-ominaisuus ei pysty lukemaan wlan-nimeä. Haluatko kuitenkin jatkaa?</string>
|
||||
<string name="auto_tunnel_title">Automaattinen tunnelointipalvelu</string>
|
||||
<string name="delete_tunnel">Poista tunneli</string>
|
||||
@@ -69,11 +70,16 @@
|
||||
<string name="vpn_denied_dialog_title">Ei käyttöoikeutta</string>
|
||||
<string name="vpn_settings">VPN järjestelmäasetukset</string>
|
||||
<string name="always_on_message">VPN-yhteyden käyttöoikeus on evätty. Tarkista</string>
|
||||
<string name="chat_description">Liity yhteisöön</string>
|
||||
<string name="tunnel_required">Ominaisuus vaatii vähintään yhden tunnelin</string>
|
||||
<string name="prominent_background_location_message">Tämä toiminto vaatii taustapaikannusoikeuden, jotta Wi-Fi SSID:n seuranta on mahdollista myös sovelluksen ollessa suljettuna. Lisätietoja löydät Tuki-näkymään linkatusta tietosuojakäytännöstä.</string>
|
||||
<string name="optional_default">"valinnainen, oletus: "</string>
|
||||
<string name="donate">Lahjoita projektille</string>
|
||||
<string name="requires_app_relaunch">Muutos edellyttää sovelluksen uudelleenkäynnistämistä. Haluatko jatkaa?</string>
|
||||
<string name="add_from_clipboard">Lisää leikepöydältä</string>
|
||||
<string name="ethernet_tunnel">Ethernet-tunneli</string>
|
||||
<string name="enable_amnezia">Ota Amnezia käyttöön</string>
|
||||
<string name="wg_compat_mode">WG-yhteensopivuustila</string>
|
||||
<string name="quick_actions">Pikatoiminnot</string>
|
||||
<string name="advanced_settings">Lisäasetukset</string>
|
||||
<string name="hide_scripts">Piilota skriptit</string>
|
||||
@@ -82,6 +88,7 @@
|
||||
<string name="error_file_extension">Tiedosto ei ole .conf tai .zip-tiedosto</string>
|
||||
<string name="tunnel_mobile_data">Tunneloi mobiilidatalla</string>
|
||||
<string name="mtu">MTU</string>
|
||||
<string name="optional_no_recommend">(valinnainen, ei suositeltu)</string>
|
||||
<string name="preshared_key">Esijaettu avain</string>
|
||||
<string name="all">kaikki</string>
|
||||
<string name="never">ei koskaan</string>
|
||||
@@ -89,6 +96,7 @@
|
||||
<string name="set_ethernet_tunnel">Aseta ethernet-tunneliksi</string>
|
||||
<string name="seconds">sekuntia</string>
|
||||
<string name="cancel">Peruuta</string>
|
||||
<string name="export_configs">Vie asetukset</string>
|
||||
<string name="no_browser_detected">Selainta ei tunnistettu</string>
|
||||
<string name="handshake">kädenpuristus</string>
|
||||
<string name="light">Vaalea</string>
|
||||
@@ -115,4 +123,4 @@
|
||||
<string name="sec">sek</string>
|
||||
<string name="read_logs">Lue lokitiedot</string>
|
||||
<string name="mobile_tunnel">Mobiilidatatunneli</string>
|
||||
</resources>
|
||||
</resources>
|
||||
@@ -20,6 +20,7 @@
|
||||
<string name="name">Nom</string>
|
||||
<string name="always_on_vpn_support">Autoriser le VPN permanent</string>
|
||||
<string name="location_services_not_detected">Services de localisation non détectés</string>
|
||||
<string name="hint_search_packages">Rechercher des paquets</string>
|
||||
<string name="auto_tunneling">Tunnel automatique</string>
|
||||
<string name="vpn_off">VPN éteint</string>
|
||||
<string name="turn_on_tunnel">Cette action nécessite un tunnel actif</string>
|
||||
@@ -46,6 +47,7 @@
|
||||
<string name="comma_separated_list">liste séparée par des virgules</string>
|
||||
<string name="listen_port">Port d\'écoute</string>
|
||||
<string name="random">(aléatoire)</string>
|
||||
<string name="optional_no_recommend">(optionnel, non recommandé)</string>
|
||||
<string name="persistent_keepalive">Keepalive persistant</string>
|
||||
<string name="optional">(optionnel)</string>
|
||||
<string name="preshared_key">Clé pré-partagée</string>
|
||||
@@ -53,6 +55,7 @@
|
||||
<string name="seconds">secondes</string>
|
||||
<string name="error_authentication_failed">Échec de l\'authentification</string>
|
||||
<string name="error_authorization_failed">Autorisation échouée</string>
|
||||
<string name="export_configs">Exporter les configs</string>
|
||||
<string name="tunnel_on_wifi">Tunnel sur wifi non approuvé</string>
|
||||
<string name="email_chooser">Envoyer un mail…</string>
|
||||
<string name="docs_description">Lire la documentation</string>
|
||||
@@ -64,6 +67,7 @@
|
||||
<string name="auto_tunnel_title">Service de tunnel automatique</string>
|
||||
<string name="delete_tunnel">Supprimer un tunnel</string>
|
||||
<string name="delete_tunnel_message">Êtes-vous sûr de vouloir supprimer ce tunnel ?</string>
|
||||
<string name="tunneling_apps">Applis de tunnel</string>
|
||||
<string name="no_email_detected">Aucune application de mail détectée</string>
|
||||
<string name="no_browser_detected">Aucun navigateur détecté</string>
|
||||
<string name="open_issue">Signaler un problème</string>
|
||||
@@ -84,6 +88,7 @@
|
||||
<string name="error_file_format">Format de configuration du tunnel invalide</string>
|
||||
<string name="restart_at_boot">Redémarrer au démarrage du système</string>
|
||||
<string name="vpn_settings">paramètres système des VPN</string>
|
||||
<string name="tunnel_required">Cette fonctionnalité nécessite au moins un tunnel</string>
|
||||
<string name="app_settings">les réglages de l\'application</string>
|
||||
<string name="background_location_message2">afin de s\'assurer que ces permissions soient actives</string>
|
||||
<string name="root_accepted">Accès au shell root autorisé</string>
|
||||
@@ -93,6 +98,7 @@
|
||||
<string name="never">jamais</string>
|
||||
<string name="handshake">handshake</string>
|
||||
<string name="logs">Journaux</string>
|
||||
<string name="error_invalid_code">Code QR invalide</string>
|
||||
<string name="enabled_app_shortcuts">Activer les raccourcis</string>
|
||||
<string name="unknown_error">Une erreur inconnue s\'est produite</string>
|
||||
<string name="email_subject">Assistance WG Tunnel</string>
|
||||
@@ -116,6 +122,7 @@
|
||||
<string name="vpn_denied_dialog_title">Permission Refusée</string>
|
||||
<string name="always_on_message">Connexion au VPN interdite. Merci de vérifier les</string>
|
||||
<string name="always_on_message2">afin de s\'assurer que le VPN permanent est désactivé pour toutes les autres applis puis réessayer</string>
|
||||
<string name="chat_description">Rejoindre la communauté</string>
|
||||
<string name="background_location_message">L\'accès à la permission de localisation permanente et/ou la localisation précise est nécessaire pour cette fonctionnalité. Veuillez vérifier dans</string>
|
||||
<string name="default_ping_ip">(facultatif, par défaut les pairs)</string>
|
||||
<string name="set_custom_ping_internal">Intervalle de ping (sec)</string>
|
||||
@@ -132,6 +139,7 @@
|
||||
<string name="dark">Sombre</string>
|
||||
<string name="dynamic">Dynamique</string>
|
||||
<string name="language">Langue</string>
|
||||
<string name="on_demand_rules">Règles de tunnel à la demande</string>
|
||||
<string name="launch_app_settings">Ouvrir les paramètres de l\'appli</string>
|
||||
<string name="display_theme">Thème d\'affichage</string>
|
||||
<string name="trusted_wifi_names">Nom wifi approuvés</string>
|
||||
@@ -148,8 +156,10 @@
|
||||
<string name="donate">Faire un don au projet</string>
|
||||
<string name="local_logging">Journalisation locale</string>
|
||||
<string name="enable_local_logging">Activer la journalisation locale</string>
|
||||
<string name="configuration_change">Configuration modifiée</string>
|
||||
<string name="kernel_not_supported">Noyau non supporté</string>
|
||||
<string name="start_auto">Démarrer l\'auto-tunnel</string>
|
||||
<string name="requires_app_relaunch">Cette modification nécessite un redémarrage de l\'application. Voulez-vous continuer ?</string>
|
||||
<string name="exclude_lan">Exclure le LAN</string>
|
||||
<string name="tunnel_specific_settings">Réglages spécifiques du tunnel</string>
|
||||
<string name="show_scripts">Voir les scripts</string>
|
||||
@@ -168,7 +178,10 @@
|
||||
<string name="bypass_lan_for_kill_switch">Contourner le LAN en cas d\'arêt d\'urgence</string>
|
||||
<string name="stop">arrêter</string>
|
||||
<string name="splt_tunneling">Tunnel partagé</string>
|
||||
<string name="amnezia_kernel_message">Amnezia n\'est pas disponible en mode noyau</string>
|
||||
<string name="enable_amnezia">Activer Amnezia</string>
|
||||
<string name="wg_compat_mode">Mode de compatibilité WG</string>
|
||||
<string name="quick_actions">Actions rapides</string>
|
||||
<string name="enable_amnezia_compatibility">Activer la prise en charge d\'Amnezia</string>
|
||||
<string name="include_lan">Inclure le LAN</string>
|
||||
</resources>
|
||||
</resources>
|
||||
@@ -33,12 +33,14 @@
|
||||
<string name="listen_port">Dengarkan port</string>
|
||||
<string name="random">(acak)</string>
|
||||
<string name="optional">(opsional)</string>
|
||||
<string name="optional_no_recommend">(opsional, tidak direkomendasikan)</string>
|
||||
<string name="preshared_key">kunci Pre-shared</string>
|
||||
<string name="seconds">detik</string>
|
||||
<string name="persistent_keepalive">Tetap hidup</string>
|
||||
<string name="cancel">Batal</string>
|
||||
<string name="error_authorization_failed">Gagal mengotorisasi</string>
|
||||
<string name="enabled_app_shortcuts">Mengaktifkan pintasan aplikasi</string>
|
||||
<string name="export_configs">Ekspor konfigurasi</string>
|
||||
<string name="unknown_error">Terjadi kesalahan yang tidak diketahui</string>
|
||||
<string name="tunnel_on_wifi">Tunnel di wifi yang tidak tepercaya</string>
|
||||
<string name="email_subject">WG Tunnel Support</string>
|
||||
@@ -49,9 +51,11 @@
|
||||
<string name="error_ssid_exists">SSID sudah ada</string>
|
||||
<string name="error_root_denied">Root shell ditolak</string>
|
||||
<string name="error_no_file_explorer">Tidak ada file explorer yang diinstal</string>
|
||||
<string name="error_invalid_code">Kode QR tidak valid</string>
|
||||
<string name="location_services_missing_message">Aplikasi tidak mendeteksi layanan lokasi apa pun yang diaktifkan di perangkat Anda. Tergantung pada perangkatnya, hal ini dapat menyebabkan fitur wifi yang tidak tepercaya gagal membaca nama wifi. Apakah Anda ingin melanjutkan?</string>
|
||||
<string name="auto_tunnel_title">Layanan Auto-tunnel</string>
|
||||
<string name="yes">Ya</string>
|
||||
<string name="tunneling_apps">Aplikasi tunneling</string>
|
||||
<string name="incorrect_pin">Pin salah</string>
|
||||
<string name="pin_created">Pin berhasil dibuat</string>
|
||||
<string name="enter_pin">Masukkan pin Anda</string>
|
||||
@@ -75,6 +79,8 @@
|
||||
<string name="vpn_settings">Pengaturan sistem VPN</string>
|
||||
<string name="always_on_message">Izin koneksi VPN telah ditolak. Silakan periksa</string>
|
||||
<string name="always_on_message2">untuk memastikan VPN Selalu aktif dimatikan untuk semua aplikasi lain dan coba lagi</string>
|
||||
<string name="chat_description">Bergabunglah dengan komunitas</string>
|
||||
<string name="tunnel_required">Fitur membutuhkan setidaknya satu tunnel</string>
|
||||
<string name="background_location_message">Izinkan izin lokasi sepanjang waktu dan/atau lokasi yang tepat diperlukan untuk fitur ini. Silakan lihat</string>
|
||||
<string name="app_settings">pengaturan aplikasi</string>
|
||||
<string name="background_location_message2">untuk memastikan izin ini diaktifkan.</string>
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
<string name="listen_port">Porta d\'ascolto</string>
|
||||
<string name="error_authorization_failed">Autorizzazione fallita</string>
|
||||
<string name="error_no_file_explorer">Nessun esploratore di file installato</string>
|
||||
<string name="error_invalid_code">QR code non valido</string>
|
||||
<string name="app_name">Tunnel WG</string>
|
||||
<string name="vpn_channel_name">Canale di notifica VPN</string>
|
||||
<string name="turn_off_tunnel">L\'operaz. richiede la disatt. del tunnel</string>
|
||||
@@ -18,6 +19,7 @@
|
||||
<string name="addresses">Indirizzi</string>
|
||||
<string name="dns_servers">Server DNS</string>
|
||||
<string name="enabled_app_shortcuts">Abilita le scorciatoie da app</string>
|
||||
<string name="export_configs">Esporta configurazioni</string>
|
||||
<string name="email_subject">Supporto di Tunnel WG</string>
|
||||
<string name="email_chooser">Invia un email…</string>
|
||||
<string name="docs_description">Read the docs</string>
|
||||
@@ -27,6 +29,7 @@
|
||||
<string name="mtu">MTU</string>
|
||||
<string name="random">(casuale)</string>
|
||||
<string name="optional">(opzionale)</string>
|
||||
<string name="optional_no_recommend">(opzionale, non raccomandato)</string>
|
||||
<string name="preshared_key">Chiave pre-condivisa</string>
|
||||
<string name="error_authentication_failed">Autenticazione fallita</string>
|
||||
<string name="tunnels">Tunnels</string>
|
||||
@@ -37,6 +40,7 @@
|
||||
<string name="name">Nome</string>
|
||||
<string name="always_on_vpn_support">Permetti VPN sempre attiva</string>
|
||||
<string name="location_services_not_detected">Servizi di localizzazione non rilevati</string>
|
||||
<string name="hint_search_packages">Cerca pacchetti</string>
|
||||
<string name="auto_tunneling">Tunnel automatico</string>
|
||||
<string name="vpn_on">VPN on</string>
|
||||
<string name="vpn_off">VPN off</string>
|
||||
@@ -60,6 +64,7 @@
|
||||
<string name="unsure_how">se non sei sicuro di come procedere</string>
|
||||
<string name="vpn_settings">Impostazioni sistema VPN</string>
|
||||
<string name="always_on_message">Permessi connessione VPN negati. Verifica la</string>
|
||||
<string name="chat_description">Unisciti alla community</string>
|
||||
<string name="junk_packet_maximum_size">Dimensione massima pacchetti indesiderati</string>
|
||||
<string name="delete_tunnel">Cancella tunnel</string>
|
||||
<string name="init_packet_junk_size">Inizializza la dimensione dei pacchetti indesiderati</string>
|
||||
@@ -97,6 +102,7 @@
|
||||
<string name="error_file_format">Formato configurazione tunnel non valido</string>
|
||||
<string name="restart_at_boot">Riavvia al boot</string>
|
||||
<string name="vpn_denied_dialog_title">Permesso Negato</string>
|
||||
<string name="tunnel_required">Questa funzione richiede almeno un tunnel</string>
|
||||
<string name="app_settings">impostazioni app</string>
|
||||
<string name="background_location_message2">per assicurarti che questi permessi siano abilitati</string>
|
||||
<string name="root_accepted">Accesso alla shell root accettata</string>
|
||||
@@ -111,6 +117,7 @@
|
||||
<string name="add_from_qr">Aggiungi da codice QR</string>
|
||||
<string name="qr_scan">Scansione QR</string>
|
||||
<string name="tunnel_name">Nome Tunnel</string>
|
||||
<string name="tunneling_apps">App nel tunnel</string>
|
||||
<string name="open_issue">Apri un problema</string>
|
||||
<string name="enter_pin">Inserisci il tuo PIN</string>
|
||||
<string name="create_pin">Crea PIN</string>
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
<string name="error_no_file_explorer">ファイルエクスプローラーはインストールされていません</string>
|
||||
<string name="error_authorization_failed">認証に失敗しました</string>
|
||||
<string name="enabled_app_shortcuts">アプリのショートカットを有効にする</string>
|
||||
<string name="export_configs">設定のエクスポート</string>
|
||||
<string name="location_services_missing_message">アプリがデバイスで有効になっている位置情報サービスを検出しません。 デバイスによっては、信頼されていない無線LAN機能がWiFi名を読み取れない可能性があります。このまま続けますか?</string>
|
||||
<string name="no_email_detected">メールアプリは検出されません</string>
|
||||
<string name="email_description">メールを送る</string>
|
||||
@@ -35,6 +36,7 @@
|
||||
<string name="add_tunnels_text">ファイルまたはzipから追加する</string>
|
||||
<string name="open_file">ファイルを開く</string>
|
||||
<string name="dns_servers">DNSサーバー</string>
|
||||
<string name="optional_no_recommend">(オプション、非推奨)</string>
|
||||
<string name="preshared_key">事前共有鍵</string>
|
||||
<string name="delete_tunnel_message">このトンネルを削除しますか?</string>
|
||||
<string name="yes">はい</string>
|
||||
@@ -53,5 +55,6 @@
|
||||
<string name="endpoint">エンドポイント</string>
|
||||
<string name="turn_on_tunnel">実行に有効なVPNトンネルが必要です</string>
|
||||
<string name="add_peer">ピアの追加</string>
|
||||
<string name="error_invalid_code">無効なQRコード</string>
|
||||
<string name="delete_tunnel">トンネルの削除</string>
|
||||
</resources>
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
<string name="allowed_ips">Allowed IPs</string>
|
||||
<string name="always_on_vpn_support">Altijd-aan VPN toestaan</string>
|
||||
<string name="location_services_not_detected">Locatieservices niet gevonden</string>
|
||||
<string name="hint_search_packages">Zoek apps</string>
|
||||
<string name="endpoint">Endpoint</string>
|
||||
<string name="name">Naam</string>
|
||||
<string name="auto_tunneling">Auto-tunnelen</string>
|
||||
@@ -25,6 +26,7 @@
|
||||
<string name="persistent_keepalive">Persistent keepalive</string>
|
||||
<string name="error_authorization_failed">Autorisatie mislukt</string>
|
||||
<string name="enabled_app_shortcuts">App snelkoppelingen inschakelen</string>
|
||||
<string name="export_configs">Configs exporteren</string>
|
||||
<string name="random">(willekeurig)</string>
|
||||
<string name="thank_you">Bedankt voor het gebruiken van WG Tunnel!</string>
|
||||
<string name="trusted_ssid_value_description">Verstuur SSID</string>
|
||||
@@ -49,9 +51,11 @@
|
||||
<string name="base64_key">Base64 sleutel</string>
|
||||
<string name="optional">(optioneel)</string>
|
||||
<string name="cancel">Annuleren</string>
|
||||
<string name="optional_no_recommend">(optioneel, niet aanbevolen)</string>
|
||||
<string name="listen_port">Luisterpoort</string>
|
||||
<string name="preshared_key">Pre-shared key</string>
|
||||
<string name="auto_tunnel_title">Auto-tunnel service</string>
|
||||
<string name="error_invalid_code">Ongeldige QR code</string>
|
||||
<string name="open_issue">Open een melding</string>
|
||||
<string name="create_pin">Stel PIN in</string>
|
||||
<string name="enable_app_lock">Schakel app-lock in</string>
|
||||
@@ -71,6 +75,7 @@
|
||||
<string name="settings">Instellingen</string>
|
||||
<string name="support">Ondersteuning</string>
|
||||
<string name="junk_packet_minimum_size">Junk packet minimum grootte</string>
|
||||
<string name="tunneling_apps">Tunnel apps</string>
|
||||
<string name="all">alle</string>
|
||||
<string name="no_browser_detected">Geen browser gevonden</string>
|
||||
<string name="kernel">Kernel</string>
|
||||
@@ -84,6 +89,7 @@
|
||||
<string name="set_custom_ping_ip">Stel eigen ping IP in</string>
|
||||
<string name="add_peer">Peer toevoegen</string>
|
||||
<string name="init_packet_magic_header">Initiële packet magic header</string>
|
||||
<string name="chat_description">Wordt lid van de community</string>
|
||||
<string name="prominent_background_location_title">Achtergrondlocatievrijgave</string>
|
||||
<string name="unknown_error">Onbekende fout opgetreden</string>
|
||||
<string name="email_subject">WG Tunnel ondersteuning</string>
|
||||
@@ -113,6 +119,7 @@
|
||||
<string name="getting_started_guide">\"aan de slag\" handleiding</string>
|
||||
<string name="vpn_denied_dialog_title">Toegang geweigerd</string>
|
||||
<string name="vpn_settings">VPN systeeminstellingen</string>
|
||||
<string name="tunnel_required">Functie vereist tenminste één tunnel</string>
|
||||
<string name="app_settings">app instellingen</string>
|
||||
<string name="root_accepted">Root toegang verleend</string>
|
||||
<string name="set_custom_ping_internal">Ping interval (sec)</string>
|
||||
@@ -129,25 +136,4 @@
|
||||
<string name="language">Taal</string>
|
||||
<string name="automatic">Automatisch</string>
|
||||
<string name="notifications">Meldingen</string>
|
||||
<string name="quick_actions">Snelle acties</string>
|
||||
<string name="ethernet_tunnel">Ethernet tunnel</string>
|
||||
<string name="set_ethernet_tunnel">Instellen als ethernet tunnel</string>
|
||||
<string name="allow_lan_traffic">LAN trafiek toestaan</string>
|
||||
<string name="vpn_channel_description">Een kanaal voor VPN status notificaties</string>
|
||||
<string name="stop">stop</string>
|
||||
<string name="splt_tunneling">Gesplitste tunneling</string>
|
||||
<string name="tunnel_specific_settings">Tunnel specifieke instellingen</string>
|
||||
<string name="advanced_settings">Geavanceerde instellingen</string>
|
||||
<string name="hide_amnezia_properties">Amnezia eigenschappen verbergen</string>
|
||||
<string name="hide_scripts">Scripts verbergen</string>
|
||||
<string name="remove_amnezia_compatibility">Amnezia compatibiliteit verwijderen</string>
|
||||
<string name="add_from_clipboard">Toevoegen van klembord</string>
|
||||
<string name="show_scripts">Toon scripts</string>
|
||||
<string name="auto_tunnel_channel_name">Auto-tunnel Notificatie Kanaal</string>
|
||||
<string name="enable_local_logging">lokale logging inschakelen</string>
|
||||
<string name="auto_tunnel_channel_description">Een kanaal voor auto-tunnel status notificaties</string>
|
||||
<string name="enable_amnezia_compatibility">Amnezia compatibiliteit inschakelen</string>
|
||||
<string name="add_from_url">Toevoegen met link</string>
|
||||
<string name="stop_on_no_internet">Stoppen wanneer geen internet</string>
|
||||
<string name="stop_on_internet_loss">Stop tunnel bij verlies van internet</string>
|
||||
</resources>
|
||||
|
||||
@@ -12,16 +12,17 @@
|
||||
<string name="addresses">Adresy</string>
|
||||
<string name="mtu">MTU</string>
|
||||
<string name="auto_tunneling">Autotunelowanie</string>
|
||||
<string name="vpn_on">Włącz VPN</string>
|
||||
<string name="vpn_off">Wyłącz VPN</string>
|
||||
<string name="vpn_on">VPN włączono</string>
|
||||
<string name="vpn_off">VPN wyłączono</string>
|
||||
<string name="turn_on_tunnel">Czynność wymaga aktywnego tunelu</string>
|
||||
<string name="interface_">Interfejs</string>
|
||||
<string name="enabled_app_shortcuts">Włącz skróty aplikacji</string>
|
||||
<string name="privacy_policy">Wyświetl politykę prywatności</string>
|
||||
<string name="tunnel_mobile_data">Tunel przez mobilną transmisję danych</string>
|
||||
<string name="hint_search_packages">Wyszukaj pakiety</string>
|
||||
<string name="random">(losowy)</string>
|
||||
<string name="pin_created">Kod PIN został pomyślnie utworzony</string>
|
||||
<string name="enter_pin">Podaj kod PIN</string>
|
||||
<string name="enter_pin">Podaj swój kod PIN</string>
|
||||
<string name="enable_app_lock">Włącz blokadę aplikacji</string>
|
||||
<string name="response_packet_junk_size">Rozmiar śmieciowego pakietu odpowiedzi</string>
|
||||
<string name="response_packet_magic_header">Nagłówek magicznego pakietu odpowiedzi</string>
|
||||
@@ -37,21 +38,27 @@
|
||||
<string name="seconds">sek.</string>
|
||||
<string name="prominent_background_location_message">Ta funkcja wymaga pozwolenia na dostęp do lokalizacji w tle, aby włączyć monitorowanie SSID sieci Wi-Fi nawet wtedy, gdy aplikacja jest zamknięta. Więcej szczegółów znajdziesz w polityce prywatności znajdującej się na ekranie Obsługa.</string>
|
||||
<string name="optional">(opcjonalnie)</string>
|
||||
<string name="optional_no_recommend">(opcjonalnie, niezalecane)</string>
|
||||
<string name="preshared_key">Klucz wstępnie udostępniony</string>
|
||||
<string name="location_services_missing_message">Aplikacja nie wykrywa żadnych usług lokalizacyjnych włączonych na tym urządzeniu. W zależności od urządzenia może to spowodować, że funkcja niezaufanej sieci Wi-Fi nie będzie w stanie odczytać nazwy sieci Wi-Fi. Czy chcesz kontynuować mimo to?</string>
|
||||
<string name="read_logs">Przeczytaj dzienniki</string>
|
||||
<string name="support">Obsługa</string>
|
||||
<string name="init_packet_magic_header">Nagłówek magicznego pakietu początkowego</string>
|
||||
<string name="chat_description">Dołącz do społeczności</string>
|
||||
<string name="cancel">Anuluj</string>
|
||||
<string name="always_on_message2">w celu upewnienia się, że funkcja stałego VPN jest wyłączona dla wszystkich innych aplikacji, następnie spróbuj ponownie</string>
|
||||
<string name="requires_app_relaunch">Ta zmiana wymaga ponownego uruchomienia aplikacji. Czy chcesz kontynuować?</string>
|
||||
<string name="use_tunnel_on_wifi_name">Użyj tunelu na nazwę sieci Wi-Fi</string>
|
||||
<string name="root_accepted">Zezwolono na powłokę użytkownika root</string>
|
||||
<string name="tunneling_apps">Aplikacje tunelujące</string>
|
||||
<string name="vpn_channel_name">Kanał powiadomień VPN</string>
|
||||
<string name="create_import">Utwórz od nowa</string>
|
||||
<string name="error_invalid_code">Nieprawidłowy kod QR</string>
|
||||
<string name="always_on_message">Zezwolenie na połączenie VPN zostało odrzucone. Sprawdź</string>
|
||||
<string name="persistent_keepalive">Trwałe utrzymywanie połączenia</string>
|
||||
<string name="vpn_settings">Ustawienia systemowe VPN</string>
|
||||
<string name="no_browser_detected">Nie wykryto przeglądarki</string>
|
||||
<string name="configuration_change">Zmiana konfiguracji</string>
|
||||
<string name="prominent_background_location_title">Ujawnienie lokalizacji w tle</string>
|
||||
<string name="email_subject">Obsługa aplikacji WG Tunnel</string>
|
||||
<string name="auto">(automatycznie)</string>
|
||||
@@ -61,6 +68,7 @@
|
||||
<string name="tunnel_on_wifi">Tunel przez niezaufaną sieć Wi-Fi</string>
|
||||
<string name="dns_servers">Serwery DNS</string>
|
||||
<string name="error_file_extension">Plik nie jest w formacie .conf lub .zip</string>
|
||||
<string name="export_configs">Eksportuj konfiguracje</string>
|
||||
<string name="copy_public_key">Skopiuj klucz publiczny</string>
|
||||
<string name="restart_on_ping">Uruchom ponownie w przypadku niepowodzenia pingowania (beta)</string>
|
||||
<string name="junk_packet_minimum_size">Minimalny rozmiar pakietu śmieciowego</string>
|
||||
@@ -112,6 +120,7 @@
|
||||
<string name="junk_packet_count">Liczba pakietów śmieciowych</string>
|
||||
<string name="init_packet_junk_size">Rozmiar śmieciowego pakietu początkowego</string>
|
||||
<string name="vpn_denied_dialog_title">Odmowa zezwolenia</string>
|
||||
<string name="tunnel_required">Funkcja wymaga co najmniej jednego tunelu</string>
|
||||
<string name="app_settings">ustawienia aplikacji</string>
|
||||
<string name="set_custom_ping_ip">Ustaw niestandardowy adres IP pingowania</string>
|
||||
<string name="default_ping_ip">(opcjonalnie, domyślnie do peerów)</string>
|
||||
@@ -129,6 +138,7 @@
|
||||
<string name="display_theme">Motyw wyświetlania</string>
|
||||
<string name="trusted_wifi_names">Nazwy zaufanych sieci Wi-Fi</string>
|
||||
<string name="add_wifi_name">Dodaj nazwę sieci Wi-Fi</string>
|
||||
<string name="on_demand_rules">Zasady korzystania z tunelu na żądanie</string>
|
||||
<string name="primary_tunnel">Tunel podstawowy</string>
|
||||
<string name="mobile_tunnel">Tunel mobilnej transmisji danych</string>
|
||||
<string name="skip">Pomiń</string>
|
||||
@@ -167,6 +177,9 @@
|
||||
<string name="post_up">Po aktywacji</string>
|
||||
<string name="pre_down">Przed dezaktywacją</string>
|
||||
<string name="post_down">Po dezaktywacji</string>
|
||||
<string name="amnezia_kernel_message">Protokół Amnezia niedostępny w trybie jądra</string>
|
||||
<string name="enable_amnezia">Włącz protokół Amnezia</string>
|
||||
<string name="wg_compat_mode">Tryb zgodności WG</string>
|
||||
<string name="quick_actions">Szybkie czynności</string>
|
||||
<string name="native_kill_switch">Natywny wyłącznik awaryjny</string>
|
||||
<string name="vpn_kill_switch">Wyłącznik awaryjny VPN</string>
|
||||
@@ -180,52 +193,17 @@
|
||||
<string name="include_lan">Uwzględnij LAN</string>
|
||||
<string name="advanced_settings">Ustawienia zaawansowane</string>
|
||||
<string name="hide_amnezia_properties">Ukryj właściwości protokołu Amnezia</string>
|
||||
<string name="error_tunnel_start">Nie udało się uruchomić tunelu</string>
|
||||
<string name="tunnel_control">Kontrola tunelu</string>
|
||||
<string name="auto_tunnel">Autotunel</string>
|
||||
<string name="kill_switch_off">Zatrzymaj wyłącznik awaryjny w zaufanej</string>
|
||||
<string name="server_ipv4">Rozpoznawanie nazw hostów IPv6</string>
|
||||
<string name="prefer_ipv4">Preferuj połączenie IPv4</string>
|
||||
<string name="unauthorized">Nie udało się uruchomić tunelu, brak autoryzacji.</string>
|
||||
<string name="multiple">Wiele</string>
|
||||
<string name="bio_auth_title">Uwierzytelnianie biometryczne</string>
|
||||
<string name="active">Aktywny</string>
|
||||
<string name="status">Stan</string>
|
||||
<string name="app_permission_description">Sterowanie tunelami i funkcjami autotunelowania.</string>
|
||||
<string name="kernel_name_error">błąd nazwy modułu jądra</string>
|
||||
<string name="add_from_url">Dodaj z adresu URL</string>
|
||||
<string name="export_failed">Eksport nie powiódł się</string>
|
||||
<string name="inactive">Nieaktywny</string>
|
||||
<string name="wifi_name_template">Aktywny: %1$s</string>
|
||||
<string name="delete_logs">Usuń i wyczyść dzienniki</string>
|
||||
<string name="join_telegram">Dołącz do społeczności Telegramu</string>
|
||||
<string name="error_download_failed">Nie udało się pobrać konfiguracji</string>
|
||||
<string name="service_running_error">błąd braku działania usługi</string>
|
||||
<string name="app_permission_title">Mostek sterujący WG Tunnel</string>
|
||||
<string name="enter_config_url">Wpisz adres URL konfiguracji</string>
|
||||
<string name="save">Zapisz</string>
|
||||
<string name="search">Szukaj</string>
|
||||
<string name="join_matrix">Dołącz do społeczności Matriksa</string>
|
||||
<string name="matrix_url">https://matrix.to/#/#wg-tunnel-space:matrix.org</string>
|
||||
<string name="export_logs">Eksportuj zapisane dzienniki</string>
|
||||
<string name="copy">Skopiuj</string>
|
||||
<string name="info">Informacje</string>
|
||||
<string name="export_tunnels_wireguard">Eksportuj tunele jako WireGuard</string>
|
||||
<string name="delete">Usuń</string>
|
||||
<string name="camera_permission_required">Wymagane pozwolenie na dostęp do aparatu</string>
|
||||
<string name="tunnel_error_template">Tunel nie powiódł się z powodu: %1$s</string>
|
||||
<string name="remote_key_template">Klucz: %1$s</string>
|
||||
<string name="config_error">błąd konfiguracji</string>
|
||||
<string name="dropdown">Rozwijane</string>
|
||||
<string name="auth_error">błąd braku autoryzacji</string>
|
||||
<string name="bio_not_supported">Dane biometryczne nie są obsługiwane</string>
|
||||
<string name="tunnel_starting">Uruchamianie tunelu</string>
|
||||
<string name="enable_remote_app_control">Włącz zdalne sterowanie aplikacją</string>
|
||||
<string name="bio_not_created">Dane biometryczne nie zostały utworzone</string>
|
||||
<string name="bio_update_required">Wymagana aktualizacja zabezpieczeń biometrycznych</string>
|
||||
<string name="add_tunnel">Dodaj tunel</string>
|
||||
<string name="bio_subtitle">Zaloguj się przy użyciu danych biometrycznych</string>
|
||||
<string name="select">Wybierz</string>
|
||||
<string name="export_tunnels_amnezia">Eksportuj tunele jako Amnezia</string>
|
||||
<string name="invalid_config_error">błąd nieprawidłowej konfiguracji</string>
|
||||
<string name="dns_resolve_error">błąd rozwiązywania DNS</string>
|
||||
<string name="nothing_here_yet">Jeszcze nic tu nie ma!</string>
|
||||
<string name="start_failed_config">Nie udało się uruchomić tunelu z powodu błędu konfiguracji.</string>
|
||||
<string name="tunne_start_failed_title">Awaria tunelu</string>
|
||||
<string name="dns_error">Nie udało się rozpoznać punktu końcowego DNS.</string>
|
||||
<string name="export_amnezia">Eksportuj jako Amnezia</string>
|
||||
<string name="export_wireguard">Eksportuj jako WireGuard</string>
|
||||
</resources>
|
||||
|
||||