Compare commits

..

1 Commits

Author SHA1 Message Date
dependabot[bot] ec26ab67c8 chore(deps): bump androidGradlePlugin
Bumps `androidGradlePlugin` from 8.8.0-alpha05 to 8.11.0-alpha05.

Updates `com.android.application` from 8.8.0-alpha05 to 8.11.0-alpha05

Updates `com.android.library` from 8.8.0-alpha05 to 8.11.0-alpha05

---
updated-dependencies:
- dependency-name: com.android.application
  dependency-version: 8.11.0-alpha05
  dependency-type: direct:production
  update-type: version-update:semver-minor
- dependency-name: com.android.library
  dependency-version: 8.11.0-alpha05
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-09 14:06:03 +00:00
152 changed files with 826 additions and 1042 deletions
+4 -4
View File
@@ -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
+36
View File
@@ -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)"
+37
View File
@@ -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)"
+20 -16
View File
@@ -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 -1
View File
@@ -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
+42
View File
@@ -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 theyre 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
+61
View File
@@ -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 theyre 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
+3 -4
View File
@@ -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" />
Binary file not shown.

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>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 777 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 544 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 715 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 525 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 382 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 471 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.1 KiB

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 698 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 987 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

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>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1000 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 916 B

After

Width:  |  Height:  |  Size: 796 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 672 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.1 KiB

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.6 KiB

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.8 KiB

After

Width:  |  Height:  |  Size: 7.4 KiB

+15 -42
View File
@@ -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>
+20
View File
@@ -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"></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>
+9 -1
View File
@@ -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>
+14 -1
View File
@@ -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>
+6
View File
@@ -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>
+7
View File
@@ -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>
+3
View File
@@ -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>
+7 -21
View File
@@ -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>
+23 -45
View File
@@ -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>

Some files were not shown because too many files have changed in this diff Show More