mirror of
https://github.com/wgtunnel/android.git
synced 2026-06-02 08:33:40 +02:00
Compare commits
44 Commits
ui-update-pre
...
3.6.2
| Author | SHA1 | Date | |
|---|---|---|---|
| 6762d4733e | |||
| add18d5cef | |||
| c5b42b55c3 | |||
| 670d9d680c | |||
| bbfc0e2fab | |||
| 708b4c7646 | |||
| efba604c31 | |||
| 1441488053 | |||
| bb3b05d224 | |||
| cda747deee | |||
| c3a2e05eb2 | |||
| a992009c71 | |||
| 57676bf4bb | |||
| 921e33cb70 | |||
| 70649383e0 | |||
| 64a7680b81 | |||
| a81d3a8843 | |||
| bad2f55121 | |||
| fecc55fe9c | |||
| 4d2e9629f9 | |||
| 347c79741f | |||
| 9bb30069fe | |||
| 9a2d77c8bf | |||
| f79f922838 | |||
| a9d5994070 | |||
| f967b38af1 | |||
| a060f00490 | |||
| b15cdbce7c | |||
| 63d46c1817 | |||
| f9ef308f8a | |||
| b2938c3ac8 | |||
| 17b575b7f2 | |||
| 777a948244 | |||
| 72bf0a1979 | |||
| c7a45845d6 | |||
| c2725bf066 | |||
| 2993f1db22 | |||
| e9ae48f96c | |||
| 5cc2ae0d01 | |||
| 7fdec509e9 | |||
| cab2945930 | |||
| 6a90cd02b9 | |||
| 7d810c7c3d | |||
| dad34b9e24 |
@@ -94,6 +94,32 @@ jobs:
|
||||
fileDir: ${{ env.KEY_STORE_LOCATION }}
|
||||
encodedString: ${{ secrets.KEYSTORE }}
|
||||
|
||||
# update latest tag
|
||||
- name: Set latest tag
|
||||
uses: rickstaa/action-create-tag@v1
|
||||
id: tag_creation
|
||||
with:
|
||||
tag: "latest" # or any tag name you wish to use
|
||||
message: "Automated tag for HEAD commit"
|
||||
force_push_tag: true
|
||||
tag_exists_error: false
|
||||
|
||||
- name: Get latest release
|
||||
id: latest_release
|
||||
uses: kaliber5/action-get-release@v1
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
latest: true
|
||||
|
||||
- name: Generate Changelog
|
||||
id: changelog
|
||||
uses: requarks/changelog-action@v1
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
toTag: ${{ github.event_name == 'schedule' && 'nightly' || steps.latest_release.outputs.tag_name }}
|
||||
fromTag: "latest"
|
||||
writeToFile: false # we won't write to file, just output
|
||||
|
||||
# create keystore path for gradle to read
|
||||
- name: Create keystore path env var
|
||||
run: |
|
||||
@@ -209,6 +235,9 @@ jobs:
|
||||
|
||||
SHA256 fingerprint:
|
||||
```${{ steps.checksum.outputs.checksum }}```
|
||||
|
||||
### Changelog
|
||||
${{ steps.changelog.outputs.changes }}
|
||||
tag_name: ${{ env.TAG_NAME }}
|
||||
name: ${{ env.TAG_NAME }}
|
||||
draft: false
|
||||
|
||||
@@ -5,7 +5,6 @@ WG Tunnel
|
||||
<div align="center">
|
||||
|
||||
[](https://discord.gg/rbRRNh6H7V)
|
||||
[](https://twitter.com/i/communities/1780655267685736818)
|
||||
[](https://t.me/wgtunnel)
|
||||
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,246 @@
|
||||
{
|
||||
"formatVersion": 1,
|
||||
"database": {
|
||||
"version": 12,
|
||||
"identityHash": "acf79ac5defacda5be6c3f976e777de3",
|
||||
"entities": [
|
||||
{
|
||||
"tableName": "Settings",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_tunnel_enabled` INTEGER NOT NULL, `is_tunnel_on_mobile_data_enabled` INTEGER NOT NULL, `trusted_network_ssids` TEXT NOT NULL, `is_always_on_vpn_enabled` INTEGER NOT NULL, `is_tunnel_on_ethernet_enabled` INTEGER NOT NULL, `is_shortcuts_enabled` INTEGER NOT NULL DEFAULT false, `is_tunnel_on_wifi_enabled` INTEGER NOT NULL DEFAULT false, `is_kernel_enabled` INTEGER NOT NULL DEFAULT false, `is_restore_on_boot_enabled` INTEGER NOT NULL DEFAULT false, `is_multi_tunnel_enabled` INTEGER NOT NULL DEFAULT false, `is_ping_enabled` INTEGER NOT NULL DEFAULT false, `is_amnezia_enabled` INTEGER NOT NULL DEFAULT false, `is_wildcards_enabled` INTEGER NOT NULL DEFAULT false, `is_wifi_by_shell_enabled` INTEGER NOT NULL DEFAULT false, `is_stop_on_no_internet_enabled` INTEGER NOT NULL DEFAULT false)",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "id",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "isAutoTunnelEnabled",
|
||||
"columnName": "is_tunnel_enabled",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "isTunnelOnMobileDataEnabled",
|
||||
"columnName": "is_tunnel_on_mobile_data_enabled",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "trustedNetworkSSIDs",
|
||||
"columnName": "trusted_network_ssids",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "isAlwaysOnVpnEnabled",
|
||||
"columnName": "is_always_on_vpn_enabled",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "isTunnelOnEthernetEnabled",
|
||||
"columnName": "is_tunnel_on_ethernet_enabled",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "isShortcutsEnabled",
|
||||
"columnName": "is_shortcuts_enabled",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "false"
|
||||
},
|
||||
{
|
||||
"fieldPath": "isTunnelOnWifiEnabled",
|
||||
"columnName": "is_tunnel_on_wifi_enabled",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "false"
|
||||
},
|
||||
{
|
||||
"fieldPath": "isKernelEnabled",
|
||||
"columnName": "is_kernel_enabled",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "false"
|
||||
},
|
||||
{
|
||||
"fieldPath": "isRestoreOnBootEnabled",
|
||||
"columnName": "is_restore_on_boot_enabled",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "false"
|
||||
},
|
||||
{
|
||||
"fieldPath": "isMultiTunnelEnabled",
|
||||
"columnName": "is_multi_tunnel_enabled",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "false"
|
||||
},
|
||||
{
|
||||
"fieldPath": "isPingEnabled",
|
||||
"columnName": "is_ping_enabled",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "false"
|
||||
},
|
||||
{
|
||||
"fieldPath": "isAmneziaEnabled",
|
||||
"columnName": "is_amnezia_enabled",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "false"
|
||||
},
|
||||
{
|
||||
"fieldPath": "isWildcardsEnabled",
|
||||
"columnName": "is_wildcards_enabled",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "false"
|
||||
},
|
||||
{
|
||||
"fieldPath": "isWifiNameByShellEnabled",
|
||||
"columnName": "is_wifi_by_shell_enabled",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "false"
|
||||
},
|
||||
{
|
||||
"fieldPath": "isStopOnNoInternetEnabled",
|
||||
"columnName": "is_stop_on_no_internet_enabled",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "false"
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": true,
|
||||
"columnNames": [
|
||||
"id"
|
||||
]
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
},
|
||||
{
|
||||
"tableName": "TunnelConfig",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NOT NULL, `wg_quick` TEXT NOT NULL, `tunnel_networks` TEXT NOT NULL DEFAULT '', `is_mobile_data_tunnel` INTEGER NOT NULL DEFAULT false, `is_primary_tunnel` INTEGER NOT NULL DEFAULT false, `am_quick` TEXT NOT NULL DEFAULT '', `is_Active` INTEGER NOT NULL DEFAULT false, `is_ping_enabled` INTEGER NOT NULL DEFAULT false, `ping_interval` INTEGER DEFAULT null, `ping_cooldown` INTEGER DEFAULT null, `ping_ip` TEXT DEFAULT null, `is_ethernet_tunnel` INTEGER NOT NULL DEFAULT false)",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "id",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "name",
|
||||
"columnName": "name",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "wgQuick",
|
||||
"columnName": "wg_quick",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "tunnelNetworks",
|
||||
"columnName": "tunnel_networks",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true,
|
||||
"defaultValue": "''"
|
||||
},
|
||||
{
|
||||
"fieldPath": "isMobileDataTunnel",
|
||||
"columnName": "is_mobile_data_tunnel",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "false"
|
||||
},
|
||||
{
|
||||
"fieldPath": "isPrimaryTunnel",
|
||||
"columnName": "is_primary_tunnel",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "false"
|
||||
},
|
||||
{
|
||||
"fieldPath": "amQuick",
|
||||
"columnName": "am_quick",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true,
|
||||
"defaultValue": "''"
|
||||
},
|
||||
{
|
||||
"fieldPath": "isActive",
|
||||
"columnName": "is_Active",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "false"
|
||||
},
|
||||
{
|
||||
"fieldPath": "isPingEnabled",
|
||||
"columnName": "is_ping_enabled",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "false"
|
||||
},
|
||||
{
|
||||
"fieldPath": "pingInterval",
|
||||
"columnName": "ping_interval",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false,
|
||||
"defaultValue": "null"
|
||||
},
|
||||
{
|
||||
"fieldPath": "pingCooldown",
|
||||
"columnName": "ping_cooldown",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false,
|
||||
"defaultValue": "null"
|
||||
},
|
||||
{
|
||||
"fieldPath": "pingIp",
|
||||
"columnName": "ping_ip",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false,
|
||||
"defaultValue": "null"
|
||||
},
|
||||
{
|
||||
"fieldPath": "isEthernetTunnel",
|
||||
"columnName": "is_ethernet_tunnel",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "false"
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": true,
|
||||
"columnNames": [
|
||||
"id"
|
||||
]
|
||||
},
|
||||
"indices": [
|
||||
{
|
||||
"name": "index_TunnelConfig_name",
|
||||
"unique": true,
|
||||
"columnNames": [
|
||||
"name"
|
||||
],
|
||||
"orders": [],
|
||||
"createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_TunnelConfig_name` ON `${TABLE_NAME}` (`name`)"
|
||||
}
|
||||
],
|
||||
"foreignKeys": []
|
||||
}
|
||||
],
|
||||
"views": [],
|
||||
"setupQueries": [
|
||||
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
|
||||
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'acf79ac5defacda5be6c3f976e777de3')"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,267 @@
|
||||
{
|
||||
"formatVersion": 1,
|
||||
"database": {
|
||||
"version": 13,
|
||||
"identityHash": "ff209157b98a641c424f5086818ec585",
|
||||
"entities": [
|
||||
{
|
||||
"tableName": "Settings",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_tunnel_enabled` INTEGER NOT NULL, `is_tunnel_on_mobile_data_enabled` INTEGER NOT NULL, `trusted_network_ssids` TEXT NOT NULL, `is_always_on_vpn_enabled` INTEGER NOT NULL, `is_tunnel_on_ethernet_enabled` INTEGER NOT NULL, `is_shortcuts_enabled` INTEGER NOT NULL DEFAULT false, `is_tunnel_on_wifi_enabled` INTEGER NOT NULL DEFAULT false, `is_kernel_enabled` INTEGER NOT NULL DEFAULT false, `is_restore_on_boot_enabled` INTEGER NOT NULL DEFAULT false, `is_multi_tunnel_enabled` INTEGER NOT NULL DEFAULT false, `is_ping_enabled` INTEGER NOT NULL DEFAULT false, `is_amnezia_enabled` INTEGER NOT NULL DEFAULT false, `is_wildcards_enabled` INTEGER NOT NULL DEFAULT false, `is_wifi_by_shell_enabled` INTEGER NOT NULL DEFAULT false, `is_stop_on_no_internet_enabled` INTEGER NOT NULL DEFAULT false, `is_vpn_kill_switch_enabled` INTEGER NOT NULL DEFAULT false, `is_kernel_kill_switch_enabled` INTEGER NOT NULL DEFAULT false, `is_lan_on_kill_switch_enabled` INTEGER NOT NULL DEFAULT false)",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "id",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "isAutoTunnelEnabled",
|
||||
"columnName": "is_tunnel_enabled",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "isTunnelOnMobileDataEnabled",
|
||||
"columnName": "is_tunnel_on_mobile_data_enabled",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "trustedNetworkSSIDs",
|
||||
"columnName": "trusted_network_ssids",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "isAlwaysOnVpnEnabled",
|
||||
"columnName": "is_always_on_vpn_enabled",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "isTunnelOnEthernetEnabled",
|
||||
"columnName": "is_tunnel_on_ethernet_enabled",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "isShortcutsEnabled",
|
||||
"columnName": "is_shortcuts_enabled",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "false"
|
||||
},
|
||||
{
|
||||
"fieldPath": "isTunnelOnWifiEnabled",
|
||||
"columnName": "is_tunnel_on_wifi_enabled",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "false"
|
||||
},
|
||||
{
|
||||
"fieldPath": "isKernelEnabled",
|
||||
"columnName": "is_kernel_enabled",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "false"
|
||||
},
|
||||
{
|
||||
"fieldPath": "isRestoreOnBootEnabled",
|
||||
"columnName": "is_restore_on_boot_enabled",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "false"
|
||||
},
|
||||
{
|
||||
"fieldPath": "isMultiTunnelEnabled",
|
||||
"columnName": "is_multi_tunnel_enabled",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "false"
|
||||
},
|
||||
{
|
||||
"fieldPath": "isPingEnabled",
|
||||
"columnName": "is_ping_enabled",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "false"
|
||||
},
|
||||
{
|
||||
"fieldPath": "isAmneziaEnabled",
|
||||
"columnName": "is_amnezia_enabled",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "false"
|
||||
},
|
||||
{
|
||||
"fieldPath": "isWildcardsEnabled",
|
||||
"columnName": "is_wildcards_enabled",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "false"
|
||||
},
|
||||
{
|
||||
"fieldPath": "isWifiNameByShellEnabled",
|
||||
"columnName": "is_wifi_by_shell_enabled",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "false"
|
||||
},
|
||||
{
|
||||
"fieldPath": "isStopOnNoInternetEnabled",
|
||||
"columnName": "is_stop_on_no_internet_enabled",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "false"
|
||||
},
|
||||
{
|
||||
"fieldPath": "isVpnKillSwitchEnabled",
|
||||
"columnName": "is_vpn_kill_switch_enabled",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "false"
|
||||
},
|
||||
{
|
||||
"fieldPath": "isKernelKillSwitchEnabled",
|
||||
"columnName": "is_kernel_kill_switch_enabled",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "false"
|
||||
},
|
||||
{
|
||||
"fieldPath": "isLanOnKillSwitchEnabled",
|
||||
"columnName": "is_lan_on_kill_switch_enabled",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "false"
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": true,
|
||||
"columnNames": [
|
||||
"id"
|
||||
]
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
},
|
||||
{
|
||||
"tableName": "TunnelConfig",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NOT NULL, `wg_quick` TEXT NOT NULL, `tunnel_networks` TEXT NOT NULL DEFAULT '', `is_mobile_data_tunnel` INTEGER NOT NULL DEFAULT false, `is_primary_tunnel` INTEGER NOT NULL DEFAULT false, `am_quick` TEXT NOT NULL DEFAULT '', `is_Active` INTEGER NOT NULL DEFAULT false, `is_ping_enabled` INTEGER NOT NULL DEFAULT false, `ping_interval` INTEGER DEFAULT null, `ping_cooldown` INTEGER DEFAULT null, `ping_ip` TEXT DEFAULT null, `is_ethernet_tunnel` INTEGER NOT NULL DEFAULT false)",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "id",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "name",
|
||||
"columnName": "name",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "wgQuick",
|
||||
"columnName": "wg_quick",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "tunnelNetworks",
|
||||
"columnName": "tunnel_networks",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true,
|
||||
"defaultValue": "''"
|
||||
},
|
||||
{
|
||||
"fieldPath": "isMobileDataTunnel",
|
||||
"columnName": "is_mobile_data_tunnel",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "false"
|
||||
},
|
||||
{
|
||||
"fieldPath": "isPrimaryTunnel",
|
||||
"columnName": "is_primary_tunnel",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "false"
|
||||
},
|
||||
{
|
||||
"fieldPath": "amQuick",
|
||||
"columnName": "am_quick",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true,
|
||||
"defaultValue": "''"
|
||||
},
|
||||
{
|
||||
"fieldPath": "isActive",
|
||||
"columnName": "is_Active",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "false"
|
||||
},
|
||||
{
|
||||
"fieldPath": "isPingEnabled",
|
||||
"columnName": "is_ping_enabled",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "false"
|
||||
},
|
||||
{
|
||||
"fieldPath": "pingInterval",
|
||||
"columnName": "ping_interval",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false,
|
||||
"defaultValue": "null"
|
||||
},
|
||||
{
|
||||
"fieldPath": "pingCooldown",
|
||||
"columnName": "ping_cooldown",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false,
|
||||
"defaultValue": "null"
|
||||
},
|
||||
{
|
||||
"fieldPath": "pingIp",
|
||||
"columnName": "ping_ip",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false,
|
||||
"defaultValue": "null"
|
||||
},
|
||||
{
|
||||
"fieldPath": "isEthernetTunnel",
|
||||
"columnName": "is_ethernet_tunnel",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "false"
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": true,
|
||||
"columnNames": [
|
||||
"id"
|
||||
]
|
||||
},
|
||||
"indices": [
|
||||
{
|
||||
"name": "index_TunnelConfig_name",
|
||||
"unique": true,
|
||||
"columnNames": [
|
||||
"name"
|
||||
],
|
||||
"orders": [],
|
||||
"createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_TunnelConfig_name` ON `${TABLE_NAME}` (`name`)"
|
||||
}
|
||||
],
|
||||
"foreignKeys": []
|
||||
}
|
||||
],
|
||||
"views": [],
|
||||
"setupQueries": [
|
||||
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
|
||||
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'ff209157b98a641c424f5086818ec585')"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -68,7 +68,9 @@
|
||||
<activity
|
||||
android:name=".ui.MainActivity"
|
||||
android:exported="true"
|
||||
android:theme="@style/Theme.WireguardAutoTunnel">
|
||||
android:theme="@style/Theme.WireguardAutoTunnel"
|
||||
android:configChanges="orientation|screenSize|keyboardHidden"
|
||||
>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
|
||||
@@ -155,7 +157,7 @@
|
||||
android:value="true" />
|
||||
</service>
|
||||
<service
|
||||
android:name=".service.foreground.AutoTunnelService"
|
||||
android:name=".service.foreground.autotunnel.AutoTunnelService"
|
||||
android:enabled="true"
|
||||
android:exported="false"
|
||||
android:foregroundServiceType="systemExempted"
|
||||
@@ -202,5 +204,10 @@
|
||||
<action android:name="com.wireguard.android.action.REFRESH_TUNNEL_STATES" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
<receiver
|
||||
android:name=".receiver.NotificationActionReceiver"
|
||||
android:exported="false"
|
||||
android:permission="${applicationId}.permission.CONTROL_TUNNELS">
|
||||
</receiver>
|
||||
</application>
|
||||
</manifest>
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
package com.zaneschepke.wireguardautotunnel
|
||||
|
||||
import android.app.Application
|
||||
import android.content.Context
|
||||
import android.os.StrictMode
|
||||
import android.os.StrictMode.ThreadPolicy
|
||||
import com.zaneschepke.logcatter.LogReader
|
||||
import com.zaneschepke.wireguardautotunnel.data.datastore.LocaleStorage
|
||||
import com.zaneschepke.wireguardautotunnel.data.repository.AppStateRepository
|
||||
import com.zaneschepke.wireguardautotunnel.data.repository.SettingsRepository
|
||||
import com.zaneschepke.wireguardautotunnel.module.ApplicationScope
|
||||
import com.zaneschepke.wireguardautotunnel.module.IoDispatcher
|
||||
import com.zaneschepke.wireguardautotunnel.module.MainDispatcher
|
||||
import com.zaneschepke.wireguardautotunnel.service.tunnel.BackendState
|
||||
import com.zaneschepke.wireguardautotunnel.service.tunnel.TunnelService
|
||||
import com.zaneschepke.wireguardautotunnel.util.LocaleUtil
|
||||
import com.zaneschepke.wireguardautotunnel.util.ReleaseTree
|
||||
import com.zaneschepke.wireguardautotunnel.util.extensions.isRunningOnTv
|
||||
@@ -15,16 +18,13 @@ import dagger.hilt.android.HiltAndroidApp
|
||||
import kotlinx.coroutines.CoroutineDispatcher
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
@HiltAndroidApp
|
||||
class WireGuardAutoTunnel : Application() {
|
||||
|
||||
val localeStorage: LocaleStorage by lazy {
|
||||
LocaleStorage(this)
|
||||
}
|
||||
|
||||
@Inject
|
||||
@ApplicationScope
|
||||
lateinit var applicationScope: CoroutineScope
|
||||
@@ -32,10 +32,23 @@ class WireGuardAutoTunnel : Application() {
|
||||
@Inject
|
||||
lateinit var logReader: LogReader
|
||||
|
||||
@Inject
|
||||
lateinit var appStateRepository: AppStateRepository
|
||||
|
||||
@Inject
|
||||
lateinit var settingsRepository: SettingsRepository
|
||||
|
||||
@Inject
|
||||
lateinit var tunnelService: TunnelService
|
||||
|
||||
@Inject
|
||||
@IoDispatcher
|
||||
lateinit var ioDispatcher: CoroutineDispatcher
|
||||
|
||||
@Inject
|
||||
@MainDispatcher
|
||||
lateinit var mainDispatcher: CoroutineDispatcher
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
instance = this
|
||||
@@ -52,15 +65,26 @@ class WireGuardAutoTunnel : Application() {
|
||||
} else {
|
||||
Timber.plant(ReleaseTree())
|
||||
}
|
||||
if (!isRunningOnTv()) {
|
||||
applicationScope.launch(ioDispatcher) {
|
||||
logReader.start()
|
||||
|
||||
applicationScope.launch {
|
||||
withContext(mainDispatcher) {
|
||||
if (appStateRepository.isLocalLogsEnabled() && !isRunningOnTv()) logReader.initialize()
|
||||
}
|
||||
if (!settingsRepository.getSettings().isKernelEnabled) {
|
||||
tunnelService.setBackendState(BackendState.SERVICE_ACTIVE, emptyList())
|
||||
}
|
||||
|
||||
appStateRepository.getLocale()?.let {
|
||||
LocaleUtil.changeLocale(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun attachBaseContext(base: Context) {
|
||||
super.attachBaseContext(LocaleUtil.getLocalizedContext(base, LocaleStorage(base).getPreferredLocale()))
|
||||
override fun onTerminate() {
|
||||
applicationScope.launch {
|
||||
tunnelService.setBackendState(BackendState.INACTIVE, emptyList())
|
||||
}
|
||||
super.onTerminate()
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
@@ -11,7 +11,7 @@ import com.zaneschepke.wireguardautotunnel.data.domain.TunnelConfig
|
||||
|
||||
@Database(
|
||||
entities = [Settings::class, TunnelConfig::class],
|
||||
version = 11,
|
||||
version = 13,
|
||||
autoMigrations =
|
||||
[
|
||||
AutoMigration(from = 1, to = 2),
|
||||
@@ -41,6 +41,14 @@ import com.zaneschepke.wireguardautotunnel.data.domain.TunnelConfig
|
||||
to = 11,
|
||||
spec = RemoveTunnelPauseMigration::class,
|
||||
),
|
||||
AutoMigration(
|
||||
from = 11,
|
||||
to = 12,
|
||||
),
|
||||
AutoMigration(
|
||||
from = 12,
|
||||
to = 13,
|
||||
),
|
||||
],
|
||||
exportSchema = true,
|
||||
)
|
||||
|
||||
@@ -16,7 +16,7 @@ object Queries {
|
||||
VALUES
|
||||
('false',
|
||||
'false',
|
||||
'sampleSSID1,sampleSSID2',
|
||||
'',
|
||||
'false',
|
||||
'false',
|
||||
'false',
|
||||
|
||||
@@ -44,6 +44,9 @@ interface TunnelConfigDao {
|
||||
@Query("UPDATE TunnelConfig SET is_mobile_data_tunnel = 0 WHERE is_mobile_data_tunnel =1")
|
||||
suspend fun resetMobileDataTunnel()
|
||||
|
||||
@Query("UPDATE TunnelConfig SET is_ethernet_tunnel = 0 WHERE is_ethernet_tunnel =1")
|
||||
suspend fun resetEthernetTunnel()
|
||||
|
||||
@Query("SELECT * FROM TUNNELCONFIG WHERE is_primary_tunnel=1")
|
||||
suspend fun findByPrimary(): TunnelConfigs
|
||||
|
||||
|
||||
+2
@@ -26,6 +26,8 @@ class DataStoreManager(
|
||||
val currentSSID = stringPreferencesKey("CURRENT_SSID")
|
||||
val pinLockEnabled = booleanPreferencesKey("PIN_LOCK_ENABLED")
|
||||
val tunnelStatsExpanded = booleanPreferencesKey("TUNNEL_STATS_EXPANDED")
|
||||
val isLocalLogsEnabled = booleanPreferencesKey("LOCAL_LOGS_ENABLED")
|
||||
val locale = stringPreferencesKey("LOCALE")
|
||||
val theme = stringPreferencesKey("THEME")
|
||||
}
|
||||
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
package com.zaneschepke.wireguardautotunnel.data.datastore
|
||||
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import com.zaneschepke.wireguardautotunnel.util.LocaleUtil
|
||||
|
||||
class LocaleStorage(context: Context) {
|
||||
private var preferences: SharedPreferences = context.getSharedPreferences("sp", Context.MODE_PRIVATE)
|
||||
|
||||
fun getPreferredLocale(): String {
|
||||
return preferences.getString("preferred_locale", LocaleUtil.OPTION_PHONE_LANGUAGE)!!
|
||||
}
|
||||
|
||||
fun setPreferredLocale(localeCode: String) {
|
||||
preferences.edit().putString("preferred_locale", localeCode).apply()
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,8 @@ data class GeneralState(
|
||||
val isBatteryOptimizationDisableShown: Boolean = BATTERY_OPTIMIZATION_DISABLE_SHOWN_DEFAULT,
|
||||
val isPinLockEnabled: Boolean = PIN_LOCK_ENABLED_DEFAULT,
|
||||
val isTunnelStatsExpanded: Boolean = IS_TUNNEL_STATS_EXPANDED,
|
||||
val isLocalLogsEnabled: Boolean = IS_LOGS_ENABLED_DEFAULT,
|
||||
val locale: String? = null,
|
||||
val theme: Theme = Theme.AUTOMATIC,
|
||||
) {
|
||||
companion object {
|
||||
@@ -14,5 +16,6 @@ data class GeneralState(
|
||||
const val BATTERY_OPTIMIZATION_DISABLE_SHOWN_DEFAULT = false
|
||||
const val PIN_LOCK_ENABLED_DEFAULT = false
|
||||
const val IS_TUNNEL_STATS_EXPANDED = false
|
||||
const val IS_LOGS_ENABLED_DEFAULT = false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,4 +60,24 @@ data class Settings(
|
||||
defaultValue = "false",
|
||||
)
|
||||
val isWifiNameByShellEnabled: Boolean = false,
|
||||
@ColumnInfo(
|
||||
name = "is_stop_on_no_internet_enabled",
|
||||
defaultValue = "false",
|
||||
)
|
||||
val isStopOnNoInternetEnabled: Boolean = false,
|
||||
@ColumnInfo(
|
||||
name = "is_vpn_kill_switch_enabled",
|
||||
defaultValue = "false",
|
||||
)
|
||||
val isVpnKillSwitchEnabled: Boolean = false,
|
||||
@ColumnInfo(
|
||||
name = "is_kernel_kill_switch_enabled",
|
||||
defaultValue = "false",
|
||||
)
|
||||
val isKernelKillSwitchEnabled: Boolean = false,
|
||||
@ColumnInfo(
|
||||
name = "is_lan_on_kill_switch_enabled",
|
||||
defaultValue = "false",
|
||||
)
|
||||
val isLanOnKillSwitchEnabled: Boolean = false,
|
||||
)
|
||||
|
||||
@@ -5,6 +5,7 @@ import androidx.room.Entity
|
||||
import androidx.room.Index
|
||||
import androidx.room.PrimaryKey
|
||||
import com.wireguard.config.Config
|
||||
import com.zaneschepke.wireguardautotunnel.util.extensions.toWgQuickString
|
||||
import java.io.InputStream
|
||||
|
||||
@Entity(indices = [Index(value = ["name"], unique = true)])
|
||||
@@ -57,6 +58,11 @@ data class TunnelConfig(
|
||||
defaultValue = "null",
|
||||
)
|
||||
var pingIp: String? = null,
|
||||
@ColumnInfo(
|
||||
name = "is_ethernet_tunnel",
|
||||
defaultValue = "false",
|
||||
)
|
||||
var isEthernetTunnel: Boolean = false,
|
||||
) {
|
||||
|
||||
fun toAmConfig(): org.amnezia.awg.config.Config {
|
||||
@@ -79,6 +85,21 @@ data class TunnelConfig(
|
||||
}
|
||||
}
|
||||
|
||||
fun tunnelConfigFromAmConfig(config: org.amnezia.awg.config.Config, name: String): TunnelConfig {
|
||||
val amQuick = config.toAwgQuickString(true)
|
||||
val wgQuick = config.toWgQuickString()
|
||||
return TunnelConfig(name = name, wgQuick = wgQuick, amQuick = amQuick)
|
||||
}
|
||||
|
||||
const val AM_QUICK_DEFAULT = ""
|
||||
|
||||
val IPV4_PUBLIC_NETWORKS = setOf(
|
||||
"0.0.0.0/5", "8.0.0.0/7", "11.0.0.0/8", "12.0.0.0/6", "16.0.0.0/4", "32.0.0.0/3",
|
||||
"64.0.0.0/2", "128.0.0.0/3", "160.0.0.0/5", "168.0.0.0/6", "172.0.0.0/12",
|
||||
"172.32.0.0/11", "172.64.0.0/10", "172.128.0.0/9", "173.0.0.0/8", "174.0.0.0/7",
|
||||
"176.0.0.0/4", "192.0.0.0/9", "192.128.0.0/11", "192.160.0.0/13", "192.169.0.0/16",
|
||||
"192.170.0.0/15", "192.172.0.0/14", "192.176.0.0/12", "192.192.0.0/10",
|
||||
"193.0.0.0/8", "194.0.0.0/7", "196.0.0.0/6", "200.0.0.0/5", "208.0.0.0/4",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
+8
-4
@@ -17,10 +17,6 @@ interface AppStateRepository {
|
||||
|
||||
suspend fun setBatteryOptimizationDisableShown(shown: Boolean)
|
||||
|
||||
suspend fun getCurrentSsid(): String?
|
||||
|
||||
suspend fun setCurrentSsid(ssid: String)
|
||||
|
||||
suspend fun isTunnelStatsExpanded(): Boolean
|
||||
|
||||
suspend fun setTunnelStatsExpanded(expanded: Boolean)
|
||||
@@ -29,5 +25,13 @@ interface AppStateRepository {
|
||||
|
||||
suspend fun getTheme(): Theme
|
||||
|
||||
suspend fun isLocalLogsEnabled(): Boolean
|
||||
|
||||
suspend fun setLocalLogsEnabled(enabled: Boolean)
|
||||
|
||||
suspend fun setLocale(localeTag: String)
|
||||
|
||||
suspend fun getLocale(): String?
|
||||
|
||||
val generalStateFlow: Flow<GeneralState>
|
||||
}
|
||||
|
||||
+18
-8
@@ -38,14 +38,6 @@ class DataStoreAppStateRepository(
|
||||
dataStoreManager.saveToDataStore(DataStoreManager.batteryDisableShown, shown)
|
||||
}
|
||||
|
||||
override suspend fun getCurrentSsid(): String? {
|
||||
return dataStoreManager.getFromStore(DataStoreManager.currentSSID)
|
||||
}
|
||||
|
||||
override suspend fun setCurrentSsid(ssid: String) {
|
||||
dataStoreManager.saveToDataStore(DataStoreManager.currentSSID, ssid)
|
||||
}
|
||||
|
||||
override suspend fun isTunnelStatsExpanded(): Boolean {
|
||||
return dataStoreManager.getFromStore(DataStoreManager.tunnelStatsExpanded)
|
||||
?: GeneralState.IS_TUNNEL_STATS_EXPANDED
|
||||
@@ -69,6 +61,22 @@ class DataStoreAppStateRepository(
|
||||
} ?: Theme.AUTOMATIC
|
||||
}
|
||||
|
||||
override suspend fun isLocalLogsEnabled(): Boolean {
|
||||
return dataStoreManager.getFromStore(DataStoreManager.isLocalLogsEnabled) ?: GeneralState.IS_LOGS_ENABLED_DEFAULT
|
||||
}
|
||||
|
||||
override suspend fun setLocalLogsEnabled(enabled: Boolean) {
|
||||
dataStoreManager.saveToDataStore(DataStoreManager.isLocalLogsEnabled, enabled)
|
||||
}
|
||||
|
||||
override suspend fun setLocale(localeTag: String) {
|
||||
dataStoreManager.saveToDataStore(DataStoreManager.locale, localeTag)
|
||||
}
|
||||
|
||||
override suspend fun getLocale(): String? {
|
||||
return dataStoreManager.getFromStore(DataStoreManager.locale)
|
||||
}
|
||||
|
||||
override val generalStateFlow: Flow<GeneralState> =
|
||||
dataStoreManager.preferencesFlow.map { prefs ->
|
||||
prefs?.let { pref ->
|
||||
@@ -84,6 +92,8 @@ class DataStoreAppStateRepository(
|
||||
pref[DataStoreManager.pinLockEnabled]
|
||||
?: GeneralState.PIN_LOCK_ENABLED_DEFAULT,
|
||||
isTunnelStatsExpanded = pref[DataStoreManager.tunnelStatsExpanded] ?: GeneralState.IS_TUNNEL_STATS_EXPANDED,
|
||||
isLocalLogsEnabled = pref[DataStoreManager.isLocalLogsEnabled] ?: GeneralState.IS_LOGS_ENABLED_DEFAULT,
|
||||
locale = pref[DataStoreManager.locale],
|
||||
theme = getTheme(),
|
||||
)
|
||||
} catch (e: IllegalArgumentException) {
|
||||
|
||||
+13
@@ -53,6 +53,19 @@ class RoomTunnelConfigRepository(
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun updateEthernetTunnel(tunnelConfig: TunnelConfig?) {
|
||||
withContext(ioDispatcher) {
|
||||
tunnelConfigDao.resetEthernetTunnel()
|
||||
tunnelConfig?.let {
|
||||
save(
|
||||
it.copy(
|
||||
isEthernetTunnel = true,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun delete(tunnelConfig: TunnelConfig) {
|
||||
withContext(ioDispatcher) {
|
||||
tunnelConfigDao.delete(tunnelConfig)
|
||||
|
||||
+2
@@ -15,6 +15,8 @@ interface TunnelConfigRepository {
|
||||
|
||||
suspend fun updateMobileDataTunnel(tunnelConfig: TunnelConfig?)
|
||||
|
||||
suspend fun updateEthernetTunnel(tunnelConfig: TunnelConfig?)
|
||||
|
||||
suspend fun delete(tunnelConfig: TunnelConfig)
|
||||
|
||||
suspend fun getById(id: Int): TunnelConfig?
|
||||
|
||||
@@ -2,7 +2,9 @@ package com.zaneschepke.wireguardautotunnel.module
|
||||
|
||||
import android.content.Context
|
||||
import com.zaneschepke.logcatter.LogReader
|
||||
import com.zaneschepke.logcatter.LogcatCollector
|
||||
import com.zaneschepke.wireguardautotunnel.service.notification.NotificationService
|
||||
import com.zaneschepke.wireguardautotunnel.service.notification.WireGuardNotification
|
||||
import com.zaneschepke.logcatter.LogcatReader
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
@@ -25,6 +27,12 @@ class AppModule {
|
||||
@Singleton
|
||||
@Provides
|
||||
fun provideLogCollect(@ApplicationContext context: Context): LogReader {
|
||||
return LogcatCollector.init(context = context)
|
||||
return LogcatReader.init(storageDir = context.filesDir.absolutePath)
|
||||
}
|
||||
|
||||
@Singleton
|
||||
@Provides
|
||||
fun provideNotificationService(@ApplicationContext context: Context): NotificationService {
|
||||
return WireGuardNotification(context)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,3 +9,11 @@ annotation class Kernel
|
||||
@Qualifier
|
||||
@Retention(AnnotationRetention.BINARY)
|
||||
annotation class Userspace
|
||||
|
||||
@Qualifier
|
||||
@Retention(AnnotationRetention.BINARY)
|
||||
annotation class TunnelShell
|
||||
|
||||
@Qualifier
|
||||
@Retention(AnnotationRetention.BINARY)
|
||||
annotation class AppShell
|
||||
|
||||
@@ -4,8 +4,6 @@ import com.zaneschepke.wireguardautotunnel.service.network.EthernetService
|
||||
import com.zaneschepke.wireguardautotunnel.service.network.MobileDataService
|
||||
import com.zaneschepke.wireguardautotunnel.service.network.NetworkService
|
||||
import com.zaneschepke.wireguardautotunnel.service.network.WifiService
|
||||
import com.zaneschepke.wireguardautotunnel.service.notification.NotificationService
|
||||
import com.zaneschepke.wireguardautotunnel.service.notification.WireGuardNotification
|
||||
import dagger.Binds
|
||||
import dagger.Module
|
||||
import dagger.hilt.InstallIn
|
||||
@@ -15,10 +13,6 @@ import dagger.hilt.android.scopes.ServiceScoped
|
||||
@Module
|
||||
@InstallIn(ServiceComponent::class)
|
||||
abstract class ServiceModule {
|
||||
@Binds
|
||||
@ServiceScoped
|
||||
abstract fun provideNotificationService(wireGuardNotification: WireGuardNotification): NotificationService
|
||||
|
||||
@Binds
|
||||
@ServiceScoped
|
||||
abstract fun provideWifiService(wifiService: WifiService): NetworkService<WifiService>
|
||||
|
||||
@@ -10,6 +10,7 @@ import com.wireguard.android.util.ToolsInstaller
|
||||
import com.zaneschepke.wireguardautotunnel.data.repository.AppDataRepository
|
||||
import com.zaneschepke.wireguardautotunnel.data.repository.TunnelConfigRepository
|
||||
import com.zaneschepke.wireguardautotunnel.service.foreground.ServiceManager
|
||||
import com.zaneschepke.wireguardautotunnel.service.notification.NotificationService
|
||||
import com.zaneschepke.wireguardautotunnel.service.tunnel.TunnelService
|
||||
import com.zaneschepke.wireguardautotunnel.service.tunnel.WireGuardTunnel
|
||||
import dagger.Module
|
||||
@@ -25,9 +26,18 @@ import javax.inject.Singleton
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
class TunnelModule {
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideRootShell(@ApplicationContext context: Context): RootShell {
|
||||
@TunnelShell
|
||||
fun provideTunnelRootShell(@ApplicationContext context: Context): RootShell {
|
||||
return RootShell(context)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
@AppShell
|
||||
fun provideAppRootShell(@ApplicationContext context: Context): RootShell {
|
||||
return RootShell(context)
|
||||
}
|
||||
|
||||
@@ -40,14 +50,14 @@ class TunnelModule {
|
||||
@Provides
|
||||
@Singleton
|
||||
@Userspace
|
||||
fun provideUserspaceBackend(@ApplicationContext context: Context, rootShell: RootShell): Backend {
|
||||
fun provideUserspaceBackend(@ApplicationContext context: Context, @TunnelShell rootShell: RootShell): Backend {
|
||||
return GoBackend(context, RootTunnelActionHandler(rootShell))
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
@Kernel
|
||||
fun provideKernelBackend(@ApplicationContext context: Context, rootShell: RootShell): Backend {
|
||||
fun provideKernelBackend(@ApplicationContext context: Context, @TunnelShell rootShell: RootShell): Backend {
|
||||
return WgQuickBackend(context, rootShell, ToolsInstaller(context, rootShell), RootTunnelActionHandler(rootShell))
|
||||
}
|
||||
|
||||
@@ -67,6 +77,7 @@ class TunnelModule {
|
||||
@ApplicationScope applicationScope: CoroutineScope,
|
||||
@IoDispatcher ioDispatcher: CoroutineDispatcher,
|
||||
serviceManager: ServiceManager,
|
||||
notificationService: NotificationService,
|
||||
): TunnelService {
|
||||
return WireGuardTunnel(
|
||||
amneziaBackend,
|
||||
@@ -76,12 +87,17 @@ class TunnelModule {
|
||||
applicationScope,
|
||||
ioDispatcher,
|
||||
serviceManager,
|
||||
notificationService,
|
||||
)
|
||||
}
|
||||
|
||||
@Singleton
|
||||
@Provides
|
||||
fun provideServiceManager(@ApplicationContext context: Context): ServiceManager {
|
||||
return ServiceManager(context)
|
||||
fun provideServiceManager(
|
||||
@ApplicationContext context: Context,
|
||||
@IoDispatcher ioDispatcher: CoroutineDispatcher,
|
||||
appDataRepository: AppDataRepository,
|
||||
): ServiceManager {
|
||||
return ServiceManager(context, ioDispatcher, appDataRepository)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,8 +5,8 @@ import android.content.Context
|
||||
import android.content.Intent
|
||||
import com.zaneschepke.wireguardautotunnel.data.repository.TunnelConfigRepository
|
||||
import com.zaneschepke.wireguardautotunnel.module.ApplicationScope
|
||||
import com.zaneschepke.wireguardautotunnel.service.foreground.ServiceManager
|
||||
import com.zaneschepke.wireguardautotunnel.service.tunnel.TunnelService
|
||||
import com.zaneschepke.wireguardautotunnel.util.extensions.requestTunnelTileServiceStateUpdate
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.launch
|
||||
@@ -26,6 +26,9 @@ class KernelReceiver : BroadcastReceiver() {
|
||||
@Inject
|
||||
lateinit var tunnelConfigRepository: TunnelConfigRepository
|
||||
|
||||
@Inject
|
||||
lateinit var serviceManager: ServiceManager
|
||||
|
||||
override fun onReceive(context: Context, intent: Intent) {
|
||||
val action = intent.action ?: return
|
||||
applicationScope.launch {
|
||||
@@ -37,7 +40,7 @@ class KernelReceiver : BroadcastReceiver() {
|
||||
tunnelConfigRepository.save(it.copy(isActive = true))
|
||||
}
|
||||
}
|
||||
context.requestTunnelTileServiceStateUpdate()
|
||||
serviceManager.updateTunnelTile()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
package com.zaneschepke.wireguardautotunnel.receiver
|
||||
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import com.zaneschepke.wireguardautotunnel.module.ApplicationScope
|
||||
import com.zaneschepke.wireguardautotunnel.service.foreground.ServiceManager
|
||||
import com.zaneschepke.wireguardautotunnel.service.notification.NotificationAction
|
||||
import com.zaneschepke.wireguardautotunnel.service.tunnel.TunnelService
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
|
||||
@AndroidEntryPoint
|
||||
class NotificationActionReceiver : BroadcastReceiver() {
|
||||
|
||||
@Inject
|
||||
lateinit var serviceManager: ServiceManager
|
||||
|
||||
@Inject
|
||||
lateinit var tunnelService: TunnelService
|
||||
|
||||
@Inject
|
||||
@ApplicationScope
|
||||
lateinit var applicationScope: CoroutineScope
|
||||
|
||||
override fun onReceive(context: Context, intent: Intent) {
|
||||
applicationScope.launch {
|
||||
when (intent.action) {
|
||||
NotificationAction.AUTO_TUNNEL_OFF.name -> serviceManager.stopAutoTunnel()
|
||||
NotificationAction.TUNNEL_OFF.name -> tunnelService.stopTunnel()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
package com.zaneschepke.wireguardautotunnel.service.foreground
|
||||
|
||||
enum class Action {
|
||||
START,
|
||||
START_FOREGROUND,
|
||||
STOP,
|
||||
STOP_FOREGROUND,
|
||||
}
|
||||
-556
@@ -1,556 +0,0 @@
|
||||
package com.zaneschepke.wireguardautotunnel.service.foreground
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.net.NetworkCapabilities
|
||||
import android.os.IBinder
|
||||
import android.os.PowerManager
|
||||
import androidx.core.app.ServiceCompat
|
||||
import androidx.lifecycle.LifecycleService
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.wireguard.android.util.RootShell
|
||||
import com.zaneschepke.wireguardautotunnel.R
|
||||
import com.zaneschepke.wireguardautotunnel.data.domain.Settings
|
||||
import com.zaneschepke.wireguardautotunnel.data.domain.TunnelConfig
|
||||
import com.zaneschepke.wireguardautotunnel.data.repository.AppDataRepository
|
||||
import com.zaneschepke.wireguardautotunnel.module.IoDispatcher
|
||||
import com.zaneschepke.wireguardautotunnel.module.MainImmediateDispatcher
|
||||
import com.zaneschepke.wireguardautotunnel.service.network.EthernetService
|
||||
import com.zaneschepke.wireguardautotunnel.service.network.MobileDataService
|
||||
import com.zaneschepke.wireguardautotunnel.service.network.NetworkService
|
||||
import com.zaneschepke.wireguardautotunnel.service.network.NetworkStatus
|
||||
import com.zaneschepke.wireguardautotunnel.service.network.WifiService
|
||||
import com.zaneschepke.wireguardautotunnel.service.notification.NotificationService
|
||||
import com.zaneschepke.wireguardautotunnel.service.tunnel.TunnelService
|
||||
import com.zaneschepke.wireguardautotunnel.service.tunnel.TunnelState
|
||||
import com.zaneschepke.wireguardautotunnel.util.Constants
|
||||
import com.zaneschepke.wireguardautotunnel.util.extensions.cancelWithMessage
|
||||
import com.zaneschepke.wireguardautotunnel.util.extensions.getCurrentWifiName
|
||||
import com.zaneschepke.wireguardautotunnel.util.extensions.isDown
|
||||
import com.zaneschepke.wireguardautotunnel.util.extensions.isReachable
|
||||
import com.zaneschepke.wireguardautotunnel.util.extensions.onNotRunning
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import kotlinx.coroutines.CompletableDeferred
|
||||
import kotlinx.coroutines.CoroutineDispatcher
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import timber.log.Timber
|
||||
import java.net.InetAddress
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Provider
|
||||
|
||||
@AndroidEntryPoint
|
||||
class AutoTunnelService : LifecycleService() {
|
||||
private val foregroundId = 122
|
||||
|
||||
@Inject
|
||||
lateinit var rootShell: Provider<RootShell>
|
||||
|
||||
@Inject
|
||||
lateinit var wifiService: NetworkService<WifiService>
|
||||
|
||||
@Inject
|
||||
lateinit var mobileDataService: NetworkService<MobileDataService>
|
||||
|
||||
@Inject
|
||||
lateinit var ethernetService: NetworkService<EthernetService>
|
||||
|
||||
@Inject
|
||||
lateinit var appDataRepository: AppDataRepository
|
||||
|
||||
@Inject
|
||||
lateinit var notificationService: NotificationService
|
||||
|
||||
@Inject
|
||||
lateinit var tunnelService: Provider<TunnelService>
|
||||
|
||||
@Inject
|
||||
@IoDispatcher
|
||||
lateinit var ioDispatcher: CoroutineDispatcher
|
||||
|
||||
@Inject
|
||||
lateinit var serviceManager: ServiceManager
|
||||
|
||||
@Inject
|
||||
@MainImmediateDispatcher
|
||||
lateinit var mainImmediateDispatcher: CoroutineDispatcher
|
||||
|
||||
private val autoTunnelStateFlow = MutableStateFlow(AutoTunnelState())
|
||||
|
||||
private var wakeLock: PowerManager.WakeLock? = null
|
||||
|
||||
private var wifiJob: Job? = null
|
||||
private var mobileDataJob: Job? = null
|
||||
private var ethernetJob: Job? = null
|
||||
private var pingJob: Job? = null
|
||||
private var networkEventJob: Job? = null
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
lifecycleScope.launch(mainImmediateDispatcher) {
|
||||
kotlin.runCatching {
|
||||
launchWatcherNotification()
|
||||
}.onFailure {
|
||||
Timber.e(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onBind(intent: Intent): IBinder? {
|
||||
super.onBind(intent)
|
||||
// We don't provide binding, so return null
|
||||
return null
|
||||
}
|
||||
|
||||
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||
Timber.d("onStartCommand executed with startId: $startId")
|
||||
serviceManager.autoTunnelService.complete(this)
|
||||
return super.onStartCommand(intent, flags, startId)
|
||||
}
|
||||
|
||||
fun start() {
|
||||
kotlin.runCatching {
|
||||
lifecycleScope.launch(mainImmediateDispatcher) {
|
||||
launchWatcherNotification()
|
||||
initWakeLock()
|
||||
}
|
||||
startSettingsJob()
|
||||
startVpnStateJob()
|
||||
}.onFailure {
|
||||
Timber.e(it)
|
||||
}
|
||||
}
|
||||
|
||||
fun stop() {
|
||||
wakeLock?.let {
|
||||
if (it.isHeld) {
|
||||
it.release()
|
||||
}
|
||||
}
|
||||
stopSelf()
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
cancelAndResetNetworkJobs()
|
||||
cancelAndResetPingJob()
|
||||
serviceManager.autoTunnelService = CompletableDeferred()
|
||||
super.onDestroy()
|
||||
}
|
||||
|
||||
private fun launchWatcherNotification(description: String = getString(R.string.watcher_notification_text_active)) {
|
||||
val notification =
|
||||
notificationService.createNotification(
|
||||
channelId = getString(R.string.watcher_channel_id),
|
||||
channelName = getString(R.string.watcher_channel_name),
|
||||
title = getString(R.string.auto_tunnel_title),
|
||||
description = description,
|
||||
)
|
||||
ServiceCompat.startForeground(
|
||||
this,
|
||||
foregroundId,
|
||||
notification,
|
||||
Constants.SYSTEM_EXEMPT_SERVICE_TYPE_ID,
|
||||
)
|
||||
}
|
||||
|
||||
private fun initWakeLock() {
|
||||
wakeLock =
|
||||
(getSystemService(Context.POWER_SERVICE) as PowerManager).run {
|
||||
val tag = this.javaClass.name
|
||||
newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "$tag::lock").apply {
|
||||
try {
|
||||
Timber.i("Initiating wakelock with 10 min timeout")
|
||||
acquire(Constants.BATTERY_SAVER_WATCHER_WAKE_LOCK_TIMEOUT)
|
||||
} finally {
|
||||
release()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun startSettingsJob() = lifecycleScope.launch {
|
||||
watchForSettingsChanges()
|
||||
}
|
||||
|
||||
private fun startVpnStateJob() = lifecycleScope.launch {
|
||||
watchForVpnStateChanges()
|
||||
}
|
||||
|
||||
private fun startWifiJob() = lifecycleScope.launch {
|
||||
watchForWifiConnectivityChanges()
|
||||
}
|
||||
|
||||
private fun startMobileDataJob() = lifecycleScope.launch {
|
||||
watchForMobileDataConnectivityChanges()
|
||||
}
|
||||
|
||||
private fun startEthernetJob() = lifecycleScope.launch {
|
||||
watchForEthernetConnectivityChanges()
|
||||
}
|
||||
|
||||
private fun startPingJob() = lifecycleScope.launch {
|
||||
watchForPingFailure()
|
||||
}
|
||||
|
||||
private fun startNetworkEventJob() = lifecycleScope.launch {
|
||||
handleNetworkEventChanges()
|
||||
}
|
||||
|
||||
private suspend fun watchForMobileDataConnectivityChanges() {
|
||||
withContext(ioDispatcher) {
|
||||
Timber.i("Starting mobile data watcher")
|
||||
mobileDataService.networkStatus.collect { status ->
|
||||
when (status) {
|
||||
is NetworkStatus.Available -> {
|
||||
Timber.i("Gained Mobile data connection")
|
||||
autoTunnelStateFlow.update {
|
||||
it.copy(
|
||||
isMobileDataConnected = true,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
is NetworkStatus.CapabilitiesChanged -> {
|
||||
autoTunnelStateFlow.update {
|
||||
it.copy(
|
||||
isMobileDataConnected = true,
|
||||
)
|
||||
}
|
||||
Timber.i("Mobile data capabilities changed")
|
||||
}
|
||||
|
||||
is NetworkStatus.Unavailable -> {
|
||||
autoTunnelStateFlow.update {
|
||||
it.copy(
|
||||
isMobileDataConnected = false,
|
||||
)
|
||||
}
|
||||
Timber.i("Lost mobile data connection")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun watchForPingFailure() {
|
||||
withContext(ioDispatcher) {
|
||||
Timber.i("Starting ping watcher")
|
||||
runCatching {
|
||||
do {
|
||||
val vpnState = tunnelService.get().vpnState.value
|
||||
if (vpnState.status == TunnelState.UP) {
|
||||
if (vpnState.tunnelConfig != null) {
|
||||
val config = TunnelConfig.configFromWgQuick(vpnState.tunnelConfig.wgQuick)
|
||||
val results = if (vpnState.tunnelConfig.pingIp != null) {
|
||||
Timber.d("Pinging custom ip : ${vpnState.tunnelConfig.pingIp}")
|
||||
listOf(InetAddress.getByName(vpnState.tunnelConfig.pingIp).isReachable(Constants.PING_TIMEOUT.toInt()))
|
||||
} else {
|
||||
Timber.d("Pinging all peers")
|
||||
config.peers.map { peer ->
|
||||
peer.isReachable()
|
||||
}
|
||||
}
|
||||
Timber.i("Ping results reachable: $results")
|
||||
if (results.contains(false)) {
|
||||
Timber.i("Restarting VPN for ping failure")
|
||||
val cooldown = vpnState.tunnelConfig.pingCooldown
|
||||
tunnelService.get().bounceTunnel(vpnState.tunnelConfig)
|
||||
delay(cooldown ?: Constants.PING_COOLDOWN)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
delay(vpnState.tunnelConfig?.pingInterval ?: Constants.PING_INTERVAL)
|
||||
} while (true)
|
||||
}.onFailure {
|
||||
Timber.e(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun watchForSettingsChanges() {
|
||||
Timber.i("Starting settings watcher")
|
||||
withContext(ioDispatcher) {
|
||||
appDataRepository.settings.getSettingsFlow().combine(
|
||||
// ignore isActive changes to allow manual tunnel overrides
|
||||
appDataRepository.tunnels.getTunnelConfigsFlow().distinctUntilChanged { old, new ->
|
||||
old.map { it.isActive } != new.map { it.isActive }
|
||||
},
|
||||
) { settings, tunnels ->
|
||||
autoTunnelStateFlow.value.copy(
|
||||
settings = settings,
|
||||
tunnels = tunnels,
|
||||
)
|
||||
}.collect {
|
||||
Timber.d("got new settings: ${it.settings}")
|
||||
manageJobsBySettings(it.settings)
|
||||
autoTunnelStateFlow.emit(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun watchForVpnStateChanges() {
|
||||
Timber.i("Starting vpn state watcher")
|
||||
withContext(ioDispatcher) {
|
||||
tunnelService.get().vpnState.distinctUntilChanged { old, new ->
|
||||
old.tunnelConfig == new.tunnelConfig && old.status == new.status
|
||||
}.collect { state ->
|
||||
autoTunnelStateFlow.update {
|
||||
it.copy(vpnState = state)
|
||||
}
|
||||
state.tunnelConfig?.let {
|
||||
val settings = appDataRepository.settings.getSettings()
|
||||
if (it.isPingEnabled && !settings.isPingEnabled) {
|
||||
pingJob.onNotRunning { pingJob = startPingJob() }
|
||||
}
|
||||
if (!it.isPingEnabled && !settings.isPingEnabled) {
|
||||
cancelAndResetPingJob()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun manageJobsBySettings(settings: Settings) {
|
||||
with(settings) {
|
||||
if (isPingEnabled) {
|
||||
pingJob.onNotRunning { pingJob = startPingJob() }
|
||||
} else {
|
||||
cancelAndResetPingJob()
|
||||
}
|
||||
if (isTunnelOnWifiEnabled || isTunnelOnEthernetEnabled || isTunnelOnMobileDataEnabled) {
|
||||
startNetworkJobs()
|
||||
} else {
|
||||
cancelAndResetNetworkJobs()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun startNetworkJobs() {
|
||||
wifiJob.onNotRunning {
|
||||
Timber.i("Wifi job starting")
|
||||
wifiJob = startWifiJob()
|
||||
}
|
||||
ethernetJob.onNotRunning {
|
||||
ethernetJob = startEthernetJob()
|
||||
Timber.i("Ethernet job starting")
|
||||
}
|
||||
mobileDataJob.onNotRunning {
|
||||
mobileDataJob = startMobileDataJob()
|
||||
Timber.i("Mobile data job starting")
|
||||
}
|
||||
networkEventJob.onNotRunning {
|
||||
Timber.i("Network event job starting")
|
||||
networkEventJob = startNetworkEventJob()
|
||||
}
|
||||
}
|
||||
|
||||
private fun cancelAndResetPingJob() {
|
||||
pingJob?.cancelWithMessage("Ping job canceled")
|
||||
pingJob = null
|
||||
}
|
||||
|
||||
private fun cancelAndResetNetworkJobs() {
|
||||
networkEventJob?.cancelWithMessage("Network event job canceled")
|
||||
wifiJob?.cancelWithMessage("Wifi job canceled")
|
||||
ethernetJob?.cancelWithMessage("Ethernet job canceled")
|
||||
mobileDataJob?.cancelWithMessage("Mobile data job canceled")
|
||||
networkEventJob = null
|
||||
wifiJob = null
|
||||
ethernetJob = null
|
||||
mobileDataJob = null
|
||||
}
|
||||
|
||||
private fun updateEthernet(connected: Boolean) {
|
||||
autoTunnelStateFlow.update {
|
||||
it.copy(
|
||||
isEthernetConnected = connected,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateWifi(connected: Boolean) {
|
||||
autoTunnelStateFlow.update {
|
||||
it.copy(
|
||||
isWifiConnected = connected,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun watchForEthernetConnectivityChanges() {
|
||||
withContext(ioDispatcher) {
|
||||
Timber.i("Starting ethernet data watcher")
|
||||
ethernetService.networkStatus.collect { status ->
|
||||
when (status) {
|
||||
is NetworkStatus.Available -> {
|
||||
Timber.i("Gained Ethernet connection")
|
||||
updateEthernet(true)
|
||||
}
|
||||
|
||||
is NetworkStatus.CapabilitiesChanged -> {
|
||||
Timber.i("Ethernet capabilities changed")
|
||||
updateEthernet(true)
|
||||
}
|
||||
|
||||
is NetworkStatus.Unavailable -> {
|
||||
updateEthernet(false)
|
||||
Timber.i("Lost Ethernet connection")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun watchForWifiConnectivityChanges() {
|
||||
withContext(ioDispatcher) {
|
||||
Timber.i("Starting wifi watcher")
|
||||
wifiService.networkStatus.collect { status ->
|
||||
when (status) {
|
||||
is NetworkStatus.Available -> {
|
||||
Timber.i("Gained Wi-Fi connection")
|
||||
updateWifi(true)
|
||||
}
|
||||
|
||||
is NetworkStatus.CapabilitiesChanged -> {
|
||||
Timber.i("Wifi capabilities changed")
|
||||
updateWifi(true)
|
||||
val ssid = getWifiSSID(status.networkCapabilities)
|
||||
ssid?.let { name ->
|
||||
if (name.contains(Constants.UNREADABLE_SSID)) {
|
||||
Timber.w("SSID unreadable: missing permissions")
|
||||
} else {
|
||||
Timber.i("Detected valid SSID")
|
||||
}
|
||||
appDataRepository.appState.setCurrentSsid(name)
|
||||
autoTunnelStateFlow.update {
|
||||
it.copy(
|
||||
currentNetworkSSID = name,
|
||||
)
|
||||
}
|
||||
} ?: Timber.w("Failed to read ssid")
|
||||
}
|
||||
|
||||
is NetworkStatus.Unavailable -> {
|
||||
updateWifi(false)
|
||||
Timber.i("Lost Wi-Fi connection")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun getWifiSSID(networkCapabilities: NetworkCapabilities): String? {
|
||||
return withContext(ioDispatcher) {
|
||||
with(autoTunnelStateFlow.value.settings) {
|
||||
if (isWifiNameByShellEnabled) return@withContext rootShell.get().getCurrentWifiName()
|
||||
wifiService.getNetworkName(networkCapabilities)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun getMobileDataTunnel(): TunnelConfig? {
|
||||
return appDataRepository.tunnels.findByMobileDataTunnel().firstOrNull()
|
||||
}
|
||||
|
||||
private suspend fun handleNetworkEventChanges() {
|
||||
withContext(ioDispatcher) {
|
||||
Timber.i("Starting network event watcher")
|
||||
autoTunnelStateFlow.collectLatest { watcherState ->
|
||||
val autoTunnel = "Auto-tunnel watcher"
|
||||
Timber.d("New watcher state!")
|
||||
// delay for rapid network state changes and then collect latest
|
||||
delay(Constants.WATCHER_COLLECTION_DELAY)
|
||||
val activeTunnel = watcherState.vpnState.tunnelConfig
|
||||
val defaultTunnel = appDataRepository.getPrimaryOrFirstTunnel()
|
||||
when {
|
||||
watcherState.isEthernetConditionMet() -> {
|
||||
Timber.i("$autoTunnel - tunnel on on ethernet condition met")
|
||||
if (watcherState.vpnState.isDown()) {
|
||||
defaultTunnel?.let {
|
||||
tunnelService.get().startTunnel(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
watcherState.isMobileDataConditionMet() -> {
|
||||
Timber.i("$autoTunnel - tunnel on mobile data condition met")
|
||||
val mobileDataTunnel = getMobileDataTunnel()
|
||||
val tunnel =
|
||||
mobileDataTunnel ?: defaultTunnel
|
||||
if (watcherState.vpnState.isDown() || activeTunnel?.isMobileDataTunnel == false) {
|
||||
tunnel?.let {
|
||||
tunnelService.get().startTunnel(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
watcherState.isTunnelOffOnMobileDataConditionMet() -> {
|
||||
Timber.i("$autoTunnel - tunnel off on mobile data met, turning vpn off")
|
||||
if (!watcherState.vpnState.isDown()) {
|
||||
activeTunnel?.let {
|
||||
tunnelService.get().stopTunnel(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
watcherState.isUntrustedWifiConditionMet() -> {
|
||||
Timber.i("Untrusted wifi condition met")
|
||||
if (activeTunnel == null || watcherState.isCurrentSSIDActiveTunnelNetwork() == false ||
|
||||
watcherState.vpnState.isDown()
|
||||
) {
|
||||
Timber.i(
|
||||
"$autoTunnel - tunnel on ssid not associated with current tunnel condition met",
|
||||
)
|
||||
watcherState.getTunnelWithMatchingTunnelNetwork()?.let {
|
||||
Timber.i("Found tunnel associated with this SSID, bringing tunnel up: ${it.name}")
|
||||
if (watcherState.vpnState.isDown() || activeTunnel?.id != it.id) {
|
||||
tunnelService.get().startTunnel(it)
|
||||
}
|
||||
} ?: suspend {
|
||||
Timber.i("No tunnel associated with this SSID, using defaults")
|
||||
val default = appDataRepository.getPrimaryOrFirstTunnel()
|
||||
if (default?.name != tunnelService.get().name || watcherState.vpnState.isDown()) {
|
||||
default?.let {
|
||||
tunnelService.get().startTunnel(it)
|
||||
}
|
||||
}
|
||||
}.invoke()
|
||||
}
|
||||
}
|
||||
|
||||
watcherState.isTrustedWifiConditionMet() -> {
|
||||
Timber.i(
|
||||
"$autoTunnel - tunnel off on trusted wifi condition met, turning vpn off",
|
||||
)
|
||||
if (!watcherState.vpnState.isDown()) activeTunnel?.let { tunnelService.get().stopTunnel(it) }
|
||||
}
|
||||
|
||||
watcherState.isTunnelOffOnWifiConditionMet() -> {
|
||||
Timber.i(
|
||||
"$autoTunnel - tunnel off on wifi condition met, turning vpn off",
|
||||
)
|
||||
if (!watcherState.vpnState.isDown()) activeTunnel?.let { tunnelService.get().stopTunnel(it) }
|
||||
}
|
||||
|
||||
watcherState.isTunnelOffOnNoConnectivityMet() -> {
|
||||
Timber.i(
|
||||
"$autoTunnel - tunnel off on no connectivity met, turning vpn off",
|
||||
)
|
||||
if (!watcherState.vpnState.isDown()) activeTunnel?.let { tunnelService.get().stopTunnel(it) }
|
||||
}
|
||||
|
||||
else -> {
|
||||
Timber.i("$autoTunnel - no condition met")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
-107
@@ -1,107 +0,0 @@
|
||||
package com.zaneschepke.wireguardautotunnel.service.foreground
|
||||
|
||||
import com.zaneschepke.wireguardautotunnel.data.domain.Settings
|
||||
import com.zaneschepke.wireguardautotunnel.data.domain.TunnelConfig
|
||||
import com.zaneschepke.wireguardautotunnel.service.tunnel.VpnState
|
||||
import com.zaneschepke.wireguardautotunnel.util.extensions.TunnelConfigs
|
||||
import com.zaneschepke.wireguardautotunnel.util.extensions.isMatchingToWildcardList
|
||||
|
||||
data class AutoTunnelState(
|
||||
val vpnState: VpnState = VpnState(),
|
||||
val isWifiConnected: Boolean = false,
|
||||
val isEthernetConnected: Boolean = false,
|
||||
val isMobileDataConnected: Boolean = false,
|
||||
val currentNetworkSSID: String = "",
|
||||
val settings: Settings = Settings(),
|
||||
val tunnels: TunnelConfigs = emptyList(),
|
||||
) {
|
||||
fun isEthernetConditionMet(): Boolean {
|
||||
return (
|
||||
isEthernetConnected &&
|
||||
settings.isTunnelOnEthernetEnabled
|
||||
)
|
||||
}
|
||||
|
||||
fun isMobileDataConditionMet(): Boolean {
|
||||
return (
|
||||
!isEthernetConnected &&
|
||||
settings.isTunnelOnMobileDataEnabled &&
|
||||
!isWifiConnected &&
|
||||
isMobileDataConnected
|
||||
)
|
||||
}
|
||||
|
||||
fun isTunnelOffOnMobileDataConditionMet(): Boolean {
|
||||
return (
|
||||
!isEthernetConnected &&
|
||||
!settings.isTunnelOnMobileDataEnabled &&
|
||||
isMobileDataConnected &&
|
||||
!isWifiConnected
|
||||
)
|
||||
}
|
||||
|
||||
fun isUntrustedWifiConditionMet(): Boolean {
|
||||
return (
|
||||
!isEthernetConnected &&
|
||||
isWifiConnected &&
|
||||
!isCurrentSSIDTrusted() &&
|
||||
settings.isTunnelOnWifiEnabled
|
||||
)
|
||||
}
|
||||
|
||||
fun isTrustedWifiConditionMet(): Boolean {
|
||||
return (
|
||||
!isEthernetConnected &&
|
||||
(
|
||||
isWifiConnected &&
|
||||
isCurrentSSIDTrusted()
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
fun isTunnelOffOnWifiConditionMet(): Boolean {
|
||||
return (
|
||||
!isEthernetConnected &&
|
||||
(
|
||||
isWifiConnected &&
|
||||
!settings.isTunnelOnWifiEnabled
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
fun isTunnelOffOnNoConnectivityMet(): Boolean {
|
||||
return (
|
||||
!isEthernetConnected &&
|
||||
!isWifiConnected &&
|
||||
!isMobileDataConnected
|
||||
)
|
||||
}
|
||||
|
||||
fun isCurrentSSIDTrusted(): Boolean {
|
||||
return if (settings.isWildcardsEnabled) {
|
||||
settings.trustedNetworkSSIDs.isMatchingToWildcardList(currentNetworkSSID)
|
||||
} else {
|
||||
settings.trustedNetworkSSIDs.contains(currentNetworkSSID)
|
||||
}
|
||||
}
|
||||
fun isCurrentSSIDActiveTunnelNetwork(): Boolean {
|
||||
val currentTunnelNetworks = vpnState.tunnelConfig?.tunnelNetworks
|
||||
return (
|
||||
if (settings.isWildcardsEnabled) {
|
||||
currentTunnelNetworks?.isMatchingToWildcardList(currentNetworkSSID)
|
||||
} else {
|
||||
currentTunnelNetworks?.contains(currentNetworkSSID)
|
||||
}
|
||||
) == true
|
||||
}
|
||||
|
||||
fun getTunnelWithMatchingTunnelNetwork(): TunnelConfig? {
|
||||
return tunnels.firstOrNull {
|
||||
if (settings.isWildcardsEnabled) {
|
||||
it.tunnelNetworks.isMatchingToWildcardList(currentNetworkSSID)
|
||||
} else {
|
||||
it.tunnelNetworks.contains(currentNetworkSSID)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+54
-19
@@ -3,19 +3,25 @@ package com.zaneschepke.wireguardautotunnel.service.foreground
|
||||
import android.app.Service
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import com.zaneschepke.wireguardautotunnel.util.SingletonHolder
|
||||
import com.zaneschepke.wireguardautotunnel.data.repository.AppDataRepository
|
||||
import com.zaneschepke.wireguardautotunnel.service.foreground.autotunnel.AutoTunnelService
|
||||
import com.zaneschepke.wireguardautotunnel.service.tile.AutoTunnelControlTile
|
||||
import com.zaneschepke.wireguardautotunnel.service.tile.TunnelControlTile
|
||||
import com.zaneschepke.wireguardautotunnel.util.extensions.requestAutoTunnelTileServiceUpdate
|
||||
import com.zaneschepke.wireguardautotunnel.util.extensions.requestTunnelTileServiceStateUpdate
|
||||
import jakarta.inject.Inject
|
||||
import kotlinx.coroutines.CompletableDeferred
|
||||
import kotlinx.coroutines.CoroutineDispatcher
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.withContext
|
||||
import timber.log.Timber
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
class ServiceManager
|
||||
@Inject constructor(private val context: Context) {
|
||||
@Inject constructor(private val context: Context, private val ioDispatcher: CoroutineDispatcher, private val appDataRepository: AppDataRepository) {
|
||||
|
||||
private val _autoTunnelActive = MutableStateFlow(false)
|
||||
|
||||
@@ -23,25 +29,30 @@ class ServiceManager
|
||||
|
||||
var autoTunnelService = CompletableDeferred<AutoTunnelService>()
|
||||
var backgroundService = CompletableDeferred<TunnelBackgroundService>()
|
||||
|
||||
companion object : SingletonHolder<ServiceManager, Context>(::ServiceManager)
|
||||
var autoTunnelTile = CompletableDeferred<AutoTunnelControlTile>()
|
||||
var tunnelControlTile = CompletableDeferred<TunnelControlTile>()
|
||||
|
||||
private fun <T : Service> startService(cls: Class<T>, background: Boolean) {
|
||||
val intent = Intent(context, cls)
|
||||
if (background) {
|
||||
context.startForegroundService(intent)
|
||||
} else {
|
||||
context.startService(intent)
|
||||
}
|
||||
runCatching {
|
||||
val intent = Intent(context, cls)
|
||||
if (background) {
|
||||
context.startForegroundService(intent)
|
||||
} else {
|
||||
context.startService(intent)
|
||||
}
|
||||
}.onFailure { Timber.e(it) }
|
||||
}
|
||||
|
||||
suspend fun startAutoTunnel(background: Boolean) {
|
||||
val settings = appDataRepository.settings.getSettings()
|
||||
appDataRepository.settings.save(settings.copy(isAutoTunnelEnabled = true))
|
||||
if (autoTunnelService.isCompleted) return _autoTunnelActive.update { true }
|
||||
kotlin.runCatching {
|
||||
startService(AutoTunnelService::class.java, background)
|
||||
autoTunnelService.await()
|
||||
autoTunnelService.getCompleted().start()
|
||||
_autoTunnelActive.update { true }
|
||||
updateAutoTunnelTile()
|
||||
}.onFailure {
|
||||
Timber.e(it)
|
||||
}
|
||||
@@ -67,17 +78,41 @@ class ServiceManager
|
||||
}
|
||||
}
|
||||
|
||||
fun stopAutoTunnel() {
|
||||
if (!autoTunnelService.isCompleted) return
|
||||
runCatching {
|
||||
autoTunnelService.getCompleted().stop()
|
||||
_autoTunnelActive.update { false }
|
||||
}.onFailure {
|
||||
Timber.e(it)
|
||||
suspend fun toggleAutoTunnel(background: Boolean) {
|
||||
withContext(ioDispatcher) {
|
||||
if (_autoTunnelActive.value) return@withContext stopAutoTunnel()
|
||||
startAutoTunnel(background)
|
||||
}
|
||||
}
|
||||
|
||||
fun requestTunnelTileUpdate() {
|
||||
context.requestTunnelTileServiceStateUpdate()
|
||||
fun updateAutoTunnelTile() {
|
||||
if (autoTunnelTile.isCompleted) {
|
||||
autoTunnelTile.getCompleted().updateTileState()
|
||||
} else {
|
||||
context.requestAutoTunnelTileServiceUpdate()
|
||||
}
|
||||
}
|
||||
|
||||
fun updateTunnelTile() {
|
||||
if (tunnelControlTile.isCompleted) {
|
||||
tunnelControlTile.getCompleted().updateTileState()
|
||||
} else {
|
||||
context.requestTunnelTileServiceStateUpdate()
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun stopAutoTunnel() {
|
||||
withContext(ioDispatcher) {
|
||||
val settings = appDataRepository.settings.getSettings()
|
||||
appDataRepository.settings.save(settings.copy(isAutoTunnelEnabled = false))
|
||||
if (!autoTunnelService.isCompleted) return@withContext
|
||||
runCatching {
|
||||
autoTunnelService.getCompleted().stop()
|
||||
_autoTunnelActive.update { false }
|
||||
updateAutoTunnelTile()
|
||||
}.onFailure {
|
||||
Timber.e(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+4
-6
@@ -7,6 +7,7 @@ import androidx.core.app.ServiceCompat
|
||||
import androidx.lifecycle.LifecycleService
|
||||
import com.zaneschepke.wireguardautotunnel.R
|
||||
import com.zaneschepke.wireguardautotunnel.service.notification.NotificationService
|
||||
import com.zaneschepke.wireguardautotunnel.service.notification.WireGuardNotification
|
||||
import com.zaneschepke.wireguardautotunnel.util.Constants
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import kotlinx.coroutines.CompletableDeferred
|
||||
@@ -21,8 +22,6 @@ class TunnelBackgroundService : LifecycleService() {
|
||||
@Inject
|
||||
lateinit var serviceManager: ServiceManager
|
||||
|
||||
private val foregroundId = 123
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
start()
|
||||
@@ -42,7 +41,7 @@ class TunnelBackgroundService : LifecycleService() {
|
||||
fun start() {
|
||||
ServiceCompat.startForeground(
|
||||
this,
|
||||
foregroundId,
|
||||
NotificationService.KERNEL_SERVICE_NOTIFICATION_ID,
|
||||
createNotification(),
|
||||
Constants.SYSTEM_EXEMPT_SERVICE_TYPE_ID,
|
||||
)
|
||||
@@ -60,9 +59,8 @@ class TunnelBackgroundService : LifecycleService() {
|
||||
|
||||
private fun createNotification(): Notification {
|
||||
return notificationService.createNotification(
|
||||
getString(R.string.vpn_channel_id),
|
||||
getString(R.string.vpn_channel_name),
|
||||
getString(R.string.tunnel_start_text),
|
||||
WireGuardNotification.NotificationChannels.VPN,
|
||||
getString(R.string.tunnel_running),
|
||||
description = "",
|
||||
)
|
||||
}
|
||||
|
||||
+309
@@ -0,0 +1,309 @@
|
||||
package com.zaneschepke.wireguardautotunnel.service.foreground.autotunnel
|
||||
|
||||
import android.content.Intent
|
||||
import android.net.NetworkCapabilities
|
||||
import android.os.IBinder
|
||||
import android.os.PowerManager
|
||||
import androidx.core.app.ServiceCompat
|
||||
import androidx.lifecycle.LifecycleService
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.wireguard.android.util.RootShell
|
||||
import com.zaneschepke.wireguardautotunnel.R
|
||||
import com.zaneschepke.wireguardautotunnel.data.domain.Settings
|
||||
import com.zaneschepke.wireguardautotunnel.data.domain.TunnelConfig
|
||||
import com.zaneschepke.wireguardautotunnel.data.repository.AppDataRepository
|
||||
import com.zaneschepke.wireguardautotunnel.module.AppShell
|
||||
import com.zaneschepke.wireguardautotunnel.module.IoDispatcher
|
||||
import com.zaneschepke.wireguardautotunnel.module.MainImmediateDispatcher
|
||||
import com.zaneschepke.wireguardautotunnel.service.foreground.ServiceManager
|
||||
import com.zaneschepke.wireguardautotunnel.service.foreground.autotunnel.model.AutoTunnelEvent
|
||||
import com.zaneschepke.wireguardautotunnel.service.foreground.autotunnel.model.AutoTunnelState
|
||||
import com.zaneschepke.wireguardautotunnel.service.foreground.autotunnel.model.NetworkState
|
||||
import com.zaneschepke.wireguardautotunnel.service.network.EthernetService
|
||||
import com.zaneschepke.wireguardautotunnel.service.network.MobileDataService
|
||||
import com.zaneschepke.wireguardautotunnel.service.network.NetworkService
|
||||
import com.zaneschepke.wireguardautotunnel.service.network.NetworkStatus
|
||||
import com.zaneschepke.wireguardautotunnel.service.network.WifiService
|
||||
import com.zaneschepke.wireguardautotunnel.service.notification.NotificationAction
|
||||
import com.zaneschepke.wireguardautotunnel.service.notification.NotificationService
|
||||
import com.zaneschepke.wireguardautotunnel.service.notification.WireGuardNotification
|
||||
import com.zaneschepke.wireguardautotunnel.service.tunnel.TunnelService
|
||||
import com.zaneschepke.wireguardautotunnel.util.Constants
|
||||
import com.zaneschepke.wireguardautotunnel.util.extensions.TunnelConfigs
|
||||
import com.zaneschepke.wireguardautotunnel.util.extensions.cancelWithMessage
|
||||
import com.zaneschepke.wireguardautotunnel.util.extensions.getCurrentWifiName
|
||||
import com.zaneschepke.wireguardautotunnel.util.extensions.isReachable
|
||||
import com.zaneschepke.wireguardautotunnel.util.extensions.onNotRunning
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import kotlinx.coroutines.CompletableDeferred
|
||||
import kotlinx.coroutines.CoroutineDispatcher
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.filterNot
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import timber.log.Timber
|
||||
import java.net.InetAddress
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Provider
|
||||
|
||||
@AndroidEntryPoint
|
||||
class AutoTunnelService : LifecycleService() {
|
||||
|
||||
@Inject
|
||||
@AppShell
|
||||
lateinit var rootShell: Provider<RootShell>
|
||||
|
||||
@Inject
|
||||
lateinit var wifiService: NetworkService<WifiService>
|
||||
|
||||
@Inject
|
||||
lateinit var mobileDataService: NetworkService<MobileDataService>
|
||||
|
||||
@Inject
|
||||
lateinit var ethernetService: NetworkService<EthernetService>
|
||||
|
||||
@Inject
|
||||
lateinit var appDataRepository: Provider<AppDataRepository>
|
||||
|
||||
@Inject
|
||||
lateinit var notificationService: NotificationService
|
||||
|
||||
@Inject
|
||||
lateinit var tunnelService: Provider<TunnelService>
|
||||
|
||||
@Inject
|
||||
@IoDispatcher
|
||||
lateinit var ioDispatcher: CoroutineDispatcher
|
||||
|
||||
@Inject
|
||||
lateinit var serviceManager: ServiceManager
|
||||
|
||||
@Inject
|
||||
@MainImmediateDispatcher
|
||||
lateinit var mainImmediateDispatcher: CoroutineDispatcher
|
||||
|
||||
private val autoTunnelStateFlow = MutableStateFlow(AutoTunnelState())
|
||||
|
||||
private var wakeLock: PowerManager.WakeLock? = null
|
||||
|
||||
private val pingTunnelRestartActive = AtomicBoolean(false)
|
||||
|
||||
private var pingJob: Job? = null
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
serviceManager.autoTunnelService.complete(this)
|
||||
lifecycleScope.launch(mainImmediateDispatcher) {
|
||||
kotlin.runCatching {
|
||||
launchWatcherNotification()
|
||||
}.onFailure {
|
||||
Timber.e(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onBind(intent: Intent): IBinder? {
|
||||
super.onBind(intent)
|
||||
return null
|
||||
}
|
||||
|
||||
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||
Timber.d("onStartCommand executed with startId: $startId")
|
||||
serviceManager.autoTunnelService.complete(this)
|
||||
return super.onStartCommand(intent, flags, startId)
|
||||
}
|
||||
|
||||
fun start() {
|
||||
kotlin.runCatching {
|
||||
lifecycleScope.launch(mainImmediateDispatcher) {
|
||||
launchWatcherNotification()
|
||||
initWakeLock()
|
||||
}
|
||||
startAutoTunnelJob()
|
||||
startAutoTunnelStateJob()
|
||||
startPingStateJob()
|
||||
}.onFailure {
|
||||
Timber.e(it)
|
||||
}
|
||||
}
|
||||
|
||||
fun stop() {
|
||||
wakeLock?.let { if (it.isHeld) it.release() }
|
||||
stopSelf()
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
cancelAndResetPingJob()
|
||||
serviceManager.autoTunnelService = CompletableDeferred()
|
||||
super.onDestroy()
|
||||
}
|
||||
|
||||
private fun launchWatcherNotification(description: String = getString(R.string.monitoring_state_changes)) {
|
||||
val notification =
|
||||
notificationService.createNotification(
|
||||
WireGuardNotification.NotificationChannels.AUTO_TUNNEL,
|
||||
title = getString(R.string.auto_tunnel_title),
|
||||
description = description,
|
||||
actions = listOf(
|
||||
notificationService.createNotificationAction(NotificationAction.AUTO_TUNNEL_OFF),
|
||||
),
|
||||
)
|
||||
ServiceCompat.startForeground(
|
||||
this,
|
||||
NotificationService.AUTO_TUNNEL_NOTIFICATION_ID,
|
||||
notification,
|
||||
Constants.SYSTEM_EXEMPT_SERVICE_TYPE_ID,
|
||||
)
|
||||
}
|
||||
|
||||
private fun initWakeLock() {
|
||||
wakeLock = (getSystemService(POWER_SERVICE) as PowerManager).run {
|
||||
val tag = this.javaClass.name
|
||||
newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "$tag::lock").apply {
|
||||
try {
|
||||
Timber.i("Initiating wakelock with 10 min timeout")
|
||||
acquire(Constants.BATTERY_SAVER_WATCHER_WAKE_LOCK_TIMEOUT)
|
||||
} finally {
|
||||
release()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun startPingJob() = lifecycleScope.launch {
|
||||
watchForPingFailure()
|
||||
}
|
||||
|
||||
private fun startPingStateJob() = lifecycleScope.launch {
|
||||
autoTunnelStateFlow.collect {
|
||||
if (it.isPingEnabled()) {
|
||||
pingJob.onNotRunning { pingJob = startPingJob() }
|
||||
} else {
|
||||
if (!pingTunnelRestartActive.get()) cancelAndResetPingJob()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun watchForPingFailure() {
|
||||
withContext(ioDispatcher) {
|
||||
Timber.i("Starting ping watcher")
|
||||
runCatching {
|
||||
do {
|
||||
val vpnState = autoTunnelStateFlow.value.vpnState
|
||||
if (vpnState.status.isUp() && !autoTunnelStateFlow.value.isNoConnectivity()) {
|
||||
if (vpnState.tunnelConfig != null) {
|
||||
val config = TunnelConfig.configFromWgQuick(vpnState.tunnelConfig.wgQuick)
|
||||
val results = if (vpnState.tunnelConfig.pingIp != null) {
|
||||
Timber.d("Pinging custom ip : ${vpnState.tunnelConfig.pingIp}")
|
||||
listOf(InetAddress.getByName(vpnState.tunnelConfig.pingIp).isReachable(Constants.PING_TIMEOUT.toInt()))
|
||||
} else {
|
||||
Timber.d("Pinging all peers")
|
||||
config.peers.map { peer ->
|
||||
peer.isReachable()
|
||||
}
|
||||
}
|
||||
Timber.i("Ping results reachable: $results")
|
||||
if (results.contains(false)) {
|
||||
Timber.i("Restarting VPN for ping failure")
|
||||
val cooldown = vpnState.tunnelConfig.pingCooldown
|
||||
pingTunnelRestartActive.set(true)
|
||||
tunnelService.get().bounceTunnel()
|
||||
pingTunnelRestartActive.set(false)
|
||||
delay(cooldown ?: Constants.PING_COOLDOWN)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
delay(vpnState.tunnelConfig?.pingInterval ?: Constants.PING_INTERVAL)
|
||||
} while (true)
|
||||
}.onFailure {
|
||||
Timber.e(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun startAutoTunnelStateJob() = lifecycleScope.launch(ioDispatcher) {
|
||||
combine(
|
||||
combineSettings(),
|
||||
combineNetworkEventsJob(),
|
||||
) { double, networkState ->
|
||||
AutoTunnelState(tunnelService.get().vpnState.value, networkState, double.first, double.second)
|
||||
}.collect { state ->
|
||||
autoTunnelStateFlow.update {
|
||||
it.copy(state.vpnState, state.networkState, state.settings, state.tunnels)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun cancelAndResetPingJob() {
|
||||
pingJob?.cancelWithMessage("Ping job canceled")
|
||||
pingJob = null
|
||||
}
|
||||
|
||||
private fun combineNetworkEventsJob(): Flow<NetworkState> {
|
||||
return combine(
|
||||
wifiService.networkStatus,
|
||||
mobileDataService.networkStatus,
|
||||
ethernetService.networkStatus,
|
||||
) { wifi, mobileData, ethernet ->
|
||||
NetworkState(
|
||||
wifi.isConnected,
|
||||
mobileData.isConnected,
|
||||
ethernet.isConnected,
|
||||
when (wifi) {
|
||||
is NetworkStatus.CapabilitiesChanged -> getWifiSSID(wifi.networkCapabilities)
|
||||
is NetworkStatus.Available -> autoTunnelStateFlow.value.networkState.wifiName
|
||||
is NetworkStatus.Unavailable -> null
|
||||
},
|
||||
)
|
||||
}.distinctUntilChanged().filterNot { it.isWifiConnected && it.wifiName == null }
|
||||
}
|
||||
|
||||
private fun combineSettings(): Flow<Pair<Settings, TunnelConfigs>> {
|
||||
return combine(
|
||||
appDataRepository.get().settings.getSettingsFlow(),
|
||||
appDataRepository.get().tunnels.getTunnelConfigsFlow().distinctUntilChanged { old, new ->
|
||||
old.map { it.isActive } != new.map { it.isActive }
|
||||
},
|
||||
) { settings, tunnels ->
|
||||
Pair(settings, tunnels)
|
||||
}.distinctUntilChanged()
|
||||
}
|
||||
|
||||
private suspend fun getWifiSSID(networkCapabilities: NetworkCapabilities): String? {
|
||||
return withContext(ioDispatcher) {
|
||||
with(autoTunnelStateFlow.value.settings) {
|
||||
if (isWifiNameByShellEnabled) return@withContext rootShell.get().getCurrentWifiName()
|
||||
wifiService.getNetworkName(networkCapabilities)
|
||||
}.also {
|
||||
if (it?.contains(Constants.UNREADABLE_SSID) == true) {
|
||||
Timber.w("SSID unreadable: missing permissions")
|
||||
} else {
|
||||
Timber.i("Detected valid SSID")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun startAutoTunnelJob() = lifecycleScope.launch(ioDispatcher) {
|
||||
Timber.i("Starting auto-tunnel network event watcher")
|
||||
autoTunnelStateFlow.collect { watcherState ->
|
||||
Timber.d("New auto tunnel state emitted")
|
||||
when (val event = watcherState.asAutoTunnelEvent()) {
|
||||
is AutoTunnelEvent.Start -> tunnelService.get().startTunnel(
|
||||
event.tunnelConfig
|
||||
?: appDataRepository.get().getPrimaryOrFirstTunnel(),
|
||||
)
|
||||
is AutoTunnelEvent.Stop -> tunnelService.get().stopTunnel()
|
||||
AutoTunnelEvent.DoNothing -> Timber.i("Auto-tunneling: no condition met")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
package com.zaneschepke.wireguardautotunnel.service.foreground.autotunnel.model
|
||||
|
||||
import com.zaneschepke.wireguardautotunnel.data.domain.TunnelConfig
|
||||
|
||||
sealed class AutoTunnelEvent {
|
||||
data class Start(val tunnelConfig: TunnelConfig? = null) : AutoTunnelEvent()
|
||||
data object Stop : AutoTunnelEvent()
|
||||
data object DoNothing : AutoTunnelEvent()
|
||||
}
|
||||
+147
@@ -0,0 +1,147 @@
|
||||
package com.zaneschepke.wireguardautotunnel.service.foreground.autotunnel.model
|
||||
|
||||
import com.zaneschepke.wireguardautotunnel.data.domain.Settings
|
||||
import com.zaneschepke.wireguardautotunnel.data.domain.TunnelConfig
|
||||
import com.zaneschepke.wireguardautotunnel.service.tunnel.VpnState
|
||||
import com.zaneschepke.wireguardautotunnel.util.extensions.TunnelConfigs
|
||||
import com.zaneschepke.wireguardautotunnel.util.extensions.isMatchingToWildcardList
|
||||
import timber.log.Timber
|
||||
|
||||
data class AutoTunnelState(
|
||||
val vpnState: VpnState = VpnState(),
|
||||
val networkState: NetworkState = NetworkState(),
|
||||
val settings: Settings = Settings(),
|
||||
val tunnels: TunnelConfigs = emptyList(),
|
||||
) {
|
||||
|
||||
private fun isMobileDataActive(): Boolean {
|
||||
return !networkState.isEthernetConnected && !networkState.isWifiConnected && networkState.isMobileDataConnected
|
||||
}
|
||||
|
||||
private fun isMobileTunnelDataChangeNeeded(): Boolean {
|
||||
val preferredTunnel = preferredMobileDataTunnel()
|
||||
return preferredTunnel != null &&
|
||||
vpnState.status.isUp() && preferredTunnel.id != vpnState.tunnelConfig?.id
|
||||
}
|
||||
|
||||
private fun isEthernetTunnelChangeNeeded(): Boolean {
|
||||
val preferredTunnel = preferredEthernetTunnel()
|
||||
return preferredTunnel != null && vpnState.status.isUp() && preferredTunnel.id != vpnState.tunnelConfig?.id
|
||||
}
|
||||
|
||||
private fun preferredMobileDataTunnel(): TunnelConfig? {
|
||||
return tunnels.firstOrNull { it.isMobileDataTunnel } ?: tunnels.firstOrNull { it.isPrimaryTunnel }
|
||||
}
|
||||
|
||||
private fun preferredEthernetTunnel(): TunnelConfig? {
|
||||
return tunnels.firstOrNull { it.isEthernetTunnel } ?: tunnels.firstOrNull { it.isPrimaryTunnel }
|
||||
}
|
||||
|
||||
private fun preferredWifiTunnel(): TunnelConfig? {
|
||||
return getTunnelWithMatchingTunnelNetwork() ?: tunnels.firstOrNull { it.isPrimaryTunnel }
|
||||
}
|
||||
|
||||
private fun isWifiActive(): Boolean {
|
||||
return !networkState.isEthernetConnected && networkState.isWifiConnected
|
||||
}
|
||||
|
||||
private fun startOnEthernet(): Boolean {
|
||||
return networkState.isEthernetConnected && settings.isTunnelOnEthernetEnabled && vpnState.status.isDown()
|
||||
}
|
||||
|
||||
private fun stopOnEthernet(): Boolean {
|
||||
return networkState.isEthernetConnected && !settings.isTunnelOnEthernetEnabled && vpnState.status.isUp()
|
||||
}
|
||||
|
||||
fun isNoConnectivity(): Boolean {
|
||||
return !networkState.isEthernetConnected && !networkState.isWifiConnected && !networkState.isMobileDataConnected
|
||||
}
|
||||
|
||||
private fun stopOnMobileData(): Boolean {
|
||||
return isMobileDataActive() && !settings.isTunnelOnMobileDataEnabled && vpnState.status.isUp()
|
||||
}
|
||||
|
||||
private fun startOnMobileData(): Boolean {
|
||||
return isMobileDataActive() && settings.isTunnelOnMobileDataEnabled && vpnState.status.isDown()
|
||||
}
|
||||
|
||||
private fun changeOnMobileData(): Boolean {
|
||||
return isMobileDataActive() && settings.isTunnelOnMobileDataEnabled && isMobileTunnelDataChangeNeeded()
|
||||
}
|
||||
|
||||
private fun changeOnEthernet(): Boolean {
|
||||
return networkState.isEthernetConnected && settings.isTunnelOnEthernetEnabled && isEthernetTunnelChangeNeeded()
|
||||
}
|
||||
|
||||
private fun stopOnWifi(): Boolean {
|
||||
return isWifiActive() && !settings.isTunnelOnWifiEnabled && vpnState.status.isUp()
|
||||
}
|
||||
|
||||
private fun stopOnTrustedWifi(): Boolean {
|
||||
return isWifiActive() && settings.isTunnelOnWifiEnabled && vpnState.status.isUp() && isCurrentSSIDTrusted()
|
||||
}
|
||||
|
||||
private fun startOnUntrustedWifi(): Boolean {
|
||||
Timber.d("Is tunnel on wifi enabled ${settings.isTunnelOnWifiEnabled}")
|
||||
return isWifiActive() && settings.isTunnelOnWifiEnabled && vpnState.status.isDown() && !isCurrentSSIDTrusted()
|
||||
}
|
||||
|
||||
private fun changeOnUntrustedWifi(): Boolean {
|
||||
return isWifiActive() && settings.isTunnelOnWifiEnabled && vpnState.status.isUp() && !isCurrentSSIDTrusted() && !isWifiTunnelPreferred()
|
||||
}
|
||||
|
||||
private fun isWifiTunnelPreferred(): Boolean {
|
||||
val preferred = preferredWifiTunnel()
|
||||
val vpnTunnel = vpnState.tunnelConfig
|
||||
return if (preferred != null && vpnTunnel != null) {
|
||||
preferred.id == vpnTunnel.id
|
||||
} else {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
fun asAutoTunnelEvent(): AutoTunnelEvent {
|
||||
return when {
|
||||
// ethernet scenarios
|
||||
stopOnEthernet() -> AutoTunnelEvent.Stop
|
||||
startOnEthernet() || changeOnEthernet() -> AutoTunnelEvent.Start(preferredEthernetTunnel())
|
||||
// mobile data scenarios
|
||||
stopOnMobileData() -> AutoTunnelEvent.Stop
|
||||
startOnMobileData() || changeOnMobileData() -> AutoTunnelEvent.Start(preferredMobileDataTunnel())
|
||||
// wifi scenarios
|
||||
stopOnWifi() -> AutoTunnelEvent.Stop
|
||||
stopOnTrustedWifi() -> AutoTunnelEvent.Stop
|
||||
startOnUntrustedWifi() || changeOnUntrustedWifi() -> AutoTunnelEvent.Start(preferredWifiTunnel())
|
||||
// no connectivity
|
||||
isNoConnectivity() && settings.isStopOnNoInternetEnabled -> AutoTunnelEvent.Stop
|
||||
else -> AutoTunnelEvent.DoNothing
|
||||
}
|
||||
}
|
||||
|
||||
private fun isCurrentSSIDTrusted(): Boolean {
|
||||
return networkState.wifiName?.let {
|
||||
hasTrustedWifiName(it)
|
||||
} == true
|
||||
}
|
||||
|
||||
private fun hasTrustedWifiName(wifiName: String, wifiNames: List<String> = settings.trustedNetworkSSIDs): Boolean {
|
||||
return if (settings.isWildcardsEnabled) {
|
||||
wifiNames.isMatchingToWildcardList(wifiName)
|
||||
} else {
|
||||
wifiNames.contains(wifiName)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getTunnelWithMatchingTunnelNetwork(): TunnelConfig? {
|
||||
return networkState.wifiName?.let { wifiName ->
|
||||
tunnels.firstOrNull {
|
||||
hasTrustedWifiName(wifiName, it.tunnelNetworks)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun isPingEnabled(): Boolean {
|
||||
return settings.isPingEnabled ||
|
||||
(vpnState.status.isUp() && vpnState.tunnelConfig != null && tunnels.first { it.id == vpnState.tunnelConfig.id }.isPingEnabled)
|
||||
}
|
||||
}
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
package com.zaneschepke.wireguardautotunnel.service.foreground.autotunnel.model
|
||||
|
||||
data class NetworkState(
|
||||
val isWifiConnected: Boolean = false,
|
||||
val isMobileDataConnected: Boolean = false,
|
||||
val isEthernetConnected: Boolean = false,
|
||||
val wifiName: String? = null,
|
||||
)
|
||||
+20
-5
@@ -10,7 +10,10 @@ import android.os.Build
|
||||
import kotlinx.coroutines.channels.awaitClose
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.callbackFlow
|
||||
import kotlinx.coroutines.flow.catch
|
||||
import kotlinx.coroutines.flow.conflate
|
||||
import kotlinx.coroutines.flow.map
|
||||
import timber.log.Timber
|
||||
|
||||
abstract class BaseNetworkService<T : BaseNetworkService<T>>(
|
||||
val context: Context,
|
||||
@@ -22,8 +25,17 @@ abstract class BaseNetworkService<T : BaseNetworkService<T>>(
|
||||
val wifiManager =
|
||||
context.applicationContext.getSystemService(Context.WIFI_SERVICE) as WifiManager
|
||||
|
||||
fun checkHasCapability(networkCapability: Int): Boolean {
|
||||
val network = connectivityManager.activeNetwork
|
||||
val networkCapabilities = connectivityManager.getNetworkCapabilities(network)
|
||||
return networkCapabilities?.hasTransport(networkCapability) == true
|
||||
}
|
||||
|
||||
override val networkStatus =
|
||||
callbackFlow {
|
||||
if (!checkHasCapability(networkCapability)) {
|
||||
trySend(NetworkStatus.Unavailable())
|
||||
}
|
||||
val networkStatusCallback =
|
||||
when (Build.VERSION.SDK_INT) {
|
||||
in Build.VERSION_CODES.S..Int.MAX_VALUE -> {
|
||||
@@ -36,7 +48,7 @@ abstract class BaseNetworkService<T : BaseNetworkService<T>>(
|
||||
}
|
||||
|
||||
override fun onLost(network: Network) {
|
||||
trySend(NetworkStatus.Unavailable(network))
|
||||
trySend(NetworkStatus.Unavailable())
|
||||
}
|
||||
|
||||
override fun onCapabilitiesChanged(network: Network, networkCapabilities: NetworkCapabilities) {
|
||||
@@ -57,7 +69,7 @@ abstract class BaseNetworkService<T : BaseNetworkService<T>>(
|
||||
}
|
||||
|
||||
override fun onLost(network: Network) {
|
||||
trySend(NetworkStatus.Unavailable(network))
|
||||
trySend(NetworkStatus.Unavailable())
|
||||
}
|
||||
|
||||
override fun onCapabilitiesChanged(network: Network, networkCapabilities: NetworkCapabilities) {
|
||||
@@ -80,17 +92,20 @@ abstract class BaseNetworkService<T : BaseNetworkService<T>>(
|
||||
connectivityManager.registerNetworkCallback(request, networkStatusCallback)
|
||||
|
||||
awaitClose { connectivityManager.unregisterNetworkCallback(networkStatusCallback) }
|
||||
}
|
||||
}.catch {
|
||||
Timber.e(it)
|
||||
// conflate for backpressure
|
||||
}.conflate()
|
||||
}
|
||||
|
||||
inline fun <Result> Flow<NetworkStatus>.map(
|
||||
crossinline onUnavailable: suspend (network: Network) -> Result,
|
||||
crossinline onUnavailable: suspend () -> Result,
|
||||
crossinline onAvailable: suspend (network: Network) -> Result,
|
||||
crossinline onCapabilitiesChanged:
|
||||
suspend (network: Network, networkCapabilities: NetworkCapabilities) -> Result,
|
||||
): Flow<Result> = map { status ->
|
||||
when (status) {
|
||||
is NetworkStatus.Unavailable -> onUnavailable(status.network)
|
||||
is NetworkStatus.Unavailable -> onUnavailable()
|
||||
is NetworkStatus.Available -> onAvailable(status.network)
|
||||
is NetworkStatus.CapabilitiesChanged ->
|
||||
onCapabilitiesChanged(
|
||||
|
||||
+4
-3
@@ -4,10 +4,11 @@ import android.net.Network
|
||||
import android.net.NetworkCapabilities
|
||||
|
||||
sealed class NetworkStatus {
|
||||
class Available(val network: Network) : NetworkStatus()
|
||||
abstract val isConnected: Boolean
|
||||
class Available(val network: Network, override val isConnected: Boolean = true) : NetworkStatus()
|
||||
|
||||
class Unavailable(val network: Network) : NetworkStatus()
|
||||
class Unavailable(override val isConnected: Boolean = false) : NetworkStatus()
|
||||
|
||||
class CapabilitiesChanged(val network: Network, val networkCapabilities: NetworkCapabilities) :
|
||||
class CapabilitiesChanged(val network: Network, val networkCapabilities: NetworkCapabilities, override val isConnected: Boolean = true) :
|
||||
NetworkStatus()
|
||||
}
|
||||
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
package com.zaneschepke.wireguardautotunnel.service.notification
|
||||
|
||||
import android.content.Context
|
||||
import com.zaneschepke.wireguardautotunnel.R
|
||||
|
||||
enum class NotificationAction {
|
||||
TUNNEL_OFF,
|
||||
AUTO_TUNNEL_OFF,
|
||||
;
|
||||
|
||||
fun title(context: Context): String {
|
||||
return when (this) {
|
||||
TUNNEL_OFF -> context.getString(R.string.stop)
|
||||
AUTO_TUNNEL_OFF -> context.getString(R.string.stop)
|
||||
}
|
||||
}
|
||||
}
|
||||
+19
-8
@@ -2,21 +2,32 @@ package com.zaneschepke.wireguardautotunnel.service.notification
|
||||
|
||||
import android.app.Notification
|
||||
import android.app.NotificationManager
|
||||
import android.app.PendingIntent
|
||||
import android.content.Context
|
||||
import androidx.core.app.NotificationCompat
|
||||
import com.zaneschepke.wireguardautotunnel.service.notification.WireGuardNotification.NotificationChannels
|
||||
|
||||
interface NotificationService {
|
||||
val context: Context
|
||||
fun createNotification(
|
||||
channelId: String,
|
||||
channelName: String,
|
||||
channel: NotificationChannels,
|
||||
title: String = "",
|
||||
action: PendingIntent? = null,
|
||||
actionText: String? = null,
|
||||
description: String,
|
||||
actions: Collection<NotificationCompat.Action> = emptyList(),
|
||||
description: String = "",
|
||||
showTimestamp: Boolean = false,
|
||||
importance: Int = NotificationManager.IMPORTANCE_HIGH,
|
||||
vibration: Boolean = false,
|
||||
onGoing: Boolean = true,
|
||||
lights: Boolean = true,
|
||||
onlyAlertOnce: Boolean = true,
|
||||
): Notification
|
||||
|
||||
fun createNotificationAction(action: NotificationAction): NotificationCompat.Action
|
||||
|
||||
fun remove(notificationId: Int)
|
||||
|
||||
fun show(notificationId: Int, notification: Notification)
|
||||
|
||||
companion object {
|
||||
const val KERNEL_SERVICE_NOTIFICATION_ID = 123
|
||||
const val AUTO_TUNNEL_NOTIFICATION_ID = 122
|
||||
const val VPN_NOTIFICATION_ID = 100
|
||||
}
|
||||
}
|
||||
|
||||
+97
-68
@@ -1,105 +1,134 @@
|
||||
package com.zaneschepke.wireguardautotunnel.service.notification
|
||||
|
||||
import android.Manifest
|
||||
import android.app.Notification
|
||||
import android.app.NotificationChannel
|
||||
import android.app.NotificationManager
|
||||
import android.app.PendingIntent
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.pm.PackageManager
|
||||
import android.graphics.Color
|
||||
import androidx.core.app.ActivityCompat
|
||||
import androidx.core.app.NotificationCompat
|
||||
import androidx.core.app.NotificationManagerCompat
|
||||
import com.zaneschepke.wireguardautotunnel.R
|
||||
import com.zaneschepke.wireguardautotunnel.ui.MainActivity
|
||||
import com.zaneschepke.wireguardautotunnel.receiver.NotificationActionReceiver
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import javax.inject.Inject
|
||||
|
||||
class WireGuardNotification
|
||||
@Inject
|
||||
constructor(
|
||||
@ApplicationContext private val context: Context,
|
||||
) :
|
||||
NotificationService {
|
||||
private val notificationManager =
|
||||
context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
||||
@ApplicationContext override val context: Context,
|
||||
) : NotificationService {
|
||||
|
||||
private val watcherBuilder: NotificationCompat.Builder =
|
||||
NotificationCompat.Builder(
|
||||
context,
|
||||
context.getString(R.string.watcher_channel_id),
|
||||
)
|
||||
private val tunnelBuilder: NotificationCompat.Builder =
|
||||
NotificationCompat.Builder(
|
||||
context,
|
||||
context.getString(R.string.vpn_channel_id),
|
||||
)
|
||||
enum class NotificationChannels {
|
||||
VPN,
|
||||
AUTO_TUNNEL,
|
||||
}
|
||||
|
||||
private val notificationManager = NotificationManagerCompat.from(context)
|
||||
|
||||
override fun createNotification(
|
||||
channelId: String,
|
||||
channelName: String,
|
||||
channel: NotificationChannels,
|
||||
title: String,
|
||||
action: PendingIntent?,
|
||||
actionText: String?,
|
||||
actions: Collection<NotificationCompat.Action>,
|
||||
description: String,
|
||||
showTimestamp: Boolean,
|
||||
importance: Int,
|
||||
vibration: Boolean,
|
||||
onGoing: Boolean,
|
||||
lights: Boolean,
|
||||
onlyAlertOnce: Boolean,
|
||||
): Notification {
|
||||
val channel =
|
||||
NotificationChannel(
|
||||
channelId,
|
||||
channelName,
|
||||
importance,
|
||||
)
|
||||
.let {
|
||||
it.description = title
|
||||
it.enableLights(lights)
|
||||
it.lightColor = Color.RED
|
||||
it.enableVibration(vibration)
|
||||
it.vibrationPattern = longArrayOf(100, 200, 300)
|
||||
it
|
||||
}
|
||||
notificationManager.createNotificationChannel(channel)
|
||||
val pendingIntent: PendingIntent =
|
||||
Intent(context, MainActivity::class.java).let { notificationIntent ->
|
||||
PendingIntent.getActivity(
|
||||
notificationManager.createNotificationChannel(channel.asChannel())
|
||||
return channel.asBuilder().apply {
|
||||
actions.forEach {
|
||||
addAction(it)
|
||||
}
|
||||
setContentTitle(title)
|
||||
setContentText(description)
|
||||
setOnlyAlertOnce(onlyAlertOnce)
|
||||
setOngoing(onGoing)
|
||||
setPriority(NotificationCompat.PRIORITY_HIGH)
|
||||
setShowWhen(showTimestamp)
|
||||
setSmallIcon(R.drawable.ic_launcher)
|
||||
}.build()
|
||||
}
|
||||
|
||||
override fun createNotificationAction(notificationAction: NotificationAction): NotificationCompat.Action {
|
||||
val pendingIntent = PendingIntent.getBroadcast(
|
||||
context,
|
||||
0,
|
||||
Intent(context, NotificationActionReceiver::class.java).apply {
|
||||
action = notificationAction.name
|
||||
},
|
||||
PendingIntent.FLAG_IMMUTABLE,
|
||||
)
|
||||
return NotificationCompat.Action.Builder(
|
||||
R.drawable.ic_launcher,
|
||||
notificationAction.title(context).uppercase(),
|
||||
pendingIntent,
|
||||
).build()
|
||||
}
|
||||
|
||||
override fun remove(notificationId: Int) {
|
||||
notificationManager.cancel(notificationId)
|
||||
}
|
||||
|
||||
override fun show(notificationId: Int, notification: Notification) {
|
||||
with(notificationManager) {
|
||||
if (ActivityCompat.checkSelfPermission(context, Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED) {
|
||||
return
|
||||
}
|
||||
notify(notificationId, notification)
|
||||
}
|
||||
}
|
||||
|
||||
fun NotificationChannels.asBuilder(): NotificationCompat.Builder {
|
||||
return when (this) {
|
||||
NotificationChannels.VPN -> {
|
||||
NotificationCompat.Builder(
|
||||
context,
|
||||
0,
|
||||
notificationIntent,
|
||||
PendingIntent.FLAG_IMMUTABLE,
|
||||
context.getString(R.string.auto_tunnel_channel_id),
|
||||
)
|
||||
}
|
||||
NotificationChannels.AUTO_TUNNEL -> {
|
||||
NotificationCompat.Builder(
|
||||
context,
|
||||
context.getString(R.string.vpn_channel_id),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val builder =
|
||||
when (channelId) {
|
||||
context.getString(R.string.watcher_channel_id) -> watcherBuilder
|
||||
context.getString(R.string.vpn_channel_id) -> tunnelBuilder
|
||||
else -> {
|
||||
NotificationCompat.Builder(
|
||||
context,
|
||||
channelId,
|
||||
)
|
||||
fun NotificationChannels.asChannel(): NotificationChannel {
|
||||
return when (this) {
|
||||
NotificationChannels.VPN -> {
|
||||
NotificationChannel(
|
||||
context.getString(R.string.vpn_channel_id),
|
||||
context.getString(R.string.vpn_channel_name),
|
||||
NotificationManager.IMPORTANCE_HIGH,
|
||||
).apply {
|
||||
description = context.getString(R.string.vpn_channel_description)
|
||||
enableLights(true)
|
||||
lightColor = Color.WHITE
|
||||
enableVibration(false)
|
||||
vibrationPattern = longArrayOf(100, 200, 300)
|
||||
}
|
||||
}
|
||||
|
||||
return builder.let {
|
||||
if (action != null && actionText != null) {
|
||||
it.addAction(
|
||||
NotificationCompat.Action.Builder(0, actionText, action).build(),
|
||||
)
|
||||
it.setAutoCancel(true)
|
||||
NotificationChannels.AUTO_TUNNEL -> {
|
||||
NotificationChannel(
|
||||
context.getString(R.string.auto_tunnel_channel_id),
|
||||
context.getString(R.string.auto_tunnel_channel_name),
|
||||
NotificationManager.IMPORTANCE_HIGH,
|
||||
).apply {
|
||||
description = context.getString(R.string.auto_tunnel_channel_description)
|
||||
enableLights(true)
|
||||
lightColor = Color.WHITE
|
||||
enableVibration(false)
|
||||
vibrationPattern = longArrayOf(100, 200, 300)
|
||||
}
|
||||
}
|
||||
it.setContentTitle(title)
|
||||
.setContentText(description)
|
||||
.setOnlyAlertOnce(onlyAlertOnce)
|
||||
.setContentIntent(pendingIntent)
|
||||
.setOngoing(onGoing)
|
||||
.setPriority(NotificationCompat.PRIORITY_HIGH)
|
||||
.setShowWhen(showTimestamp)
|
||||
.setSmallIcon(R.drawable.ic_launcher)
|
||||
.build()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+7
-3
@@ -4,9 +4,8 @@ import android.os.Bundle
|
||||
import androidx.activity.ComponentActivity
|
||||
import com.zaneschepke.wireguardautotunnel.data.repository.AppDataRepository
|
||||
import com.zaneschepke.wireguardautotunnel.module.ApplicationScope
|
||||
import com.zaneschepke.wireguardautotunnel.service.foreground.Action
|
||||
import com.zaneschepke.wireguardautotunnel.service.foreground.AutoTunnelService
|
||||
import com.zaneschepke.wireguardautotunnel.service.foreground.ServiceManager
|
||||
import com.zaneschepke.wireguardautotunnel.service.foreground.autotunnel.AutoTunnelService
|
||||
import com.zaneschepke.wireguardautotunnel.service.tunnel.TunnelService
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
@@ -47,7 +46,7 @@ class ShortcutsActivity : ComponentActivity() {
|
||||
tunnelConfig?.let {
|
||||
when (intent.action) {
|
||||
Action.START.name -> tunnelService.get().startTunnel(it, true)
|
||||
Action.STOP.name -> tunnelService.get().stopTunnel(it)
|
||||
Action.STOP.name -> tunnelService.get().stopTunnel()
|
||||
else -> Unit
|
||||
}
|
||||
}
|
||||
@@ -64,6 +63,11 @@ class ShortcutsActivity : ComponentActivity() {
|
||||
finish()
|
||||
}
|
||||
|
||||
enum class Action {
|
||||
START,
|
||||
STOP,
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val LEGACY_TUNNEL_SERVICE_NAME = "WireGuardTunnelService"
|
||||
const val LEGACY_AUTO_TUNNEL_SERVICE_NAME = "WireGuardConnectivityWatcherService"
|
||||
|
||||
+8
-34
@@ -1,24 +1,18 @@
|
||||
package com.zaneschepke.wireguardautotunnel.service.tile
|
||||
|
||||
import android.content.Intent
|
||||
import android.os.IBinder
|
||||
import android.service.quicksettings.Tile
|
||||
import android.service.quicksettings.TileService
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import androidx.lifecycle.LifecycleRegistry
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.zaneschepke.wireguardautotunnel.data.repository.AppDataRepository
|
||||
import com.zaneschepke.wireguardautotunnel.module.ApplicationScope
|
||||
import com.zaneschepke.wireguardautotunnel.service.foreground.ServiceManager
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import kotlinx.coroutines.CompletableDeferred
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.launch
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
@AndroidEntryPoint
|
||||
class AutoTunnelControlTile : TileService(), LifecycleOwner {
|
||||
class AutoTunnelControlTile : TileService() {
|
||||
@Inject
|
||||
lateinit var appDataRepository: AppDataRepository
|
||||
|
||||
@@ -29,32 +23,26 @@ class AutoTunnelControlTile : TileService(), LifecycleOwner {
|
||||
@ApplicationScope
|
||||
lateinit var applicationScope: CoroutineScope
|
||||
|
||||
private val lifecycleRegistry: LifecycleRegistry = LifecycleRegistry(this)
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_CREATE)
|
||||
}
|
||||
|
||||
override fun onStopListening() {
|
||||
lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_STOP)
|
||||
serviceManager.autoTunnelTile.complete(this)
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_DESTROY)
|
||||
serviceManager.autoTunnelTile = CompletableDeferred()
|
||||
}
|
||||
|
||||
override fun onStartListening() {
|
||||
super.onStartListening()
|
||||
lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_START)
|
||||
lifecycleScope.launch {
|
||||
serviceManager.autoTunnelTile.complete(this)
|
||||
applicationScope.launch {
|
||||
if (appDataRepository.tunnels.getAll().isEmpty()) return@launch setUnavailable()
|
||||
updateTileState()
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateTileState() {
|
||||
fun updateTileState() {
|
||||
serviceManager.autoTunnelActive.value.let {
|
||||
if (it) setActive() else setInactive()
|
||||
}
|
||||
@@ -63,7 +51,7 @@ class AutoTunnelControlTile : TileService(), LifecycleOwner {
|
||||
override fun onClick() {
|
||||
super.onClick()
|
||||
unlockAndRun {
|
||||
lifecycleScope.launch {
|
||||
applicationScope.launch {
|
||||
if (serviceManager.autoTunnelActive.value) {
|
||||
serviceManager.stopAutoTunnel()
|
||||
setInactive()
|
||||
@@ -95,18 +83,4 @@ class AutoTunnelControlTile : TileService(), LifecycleOwner {
|
||||
qsTile.updateTile()
|
||||
}
|
||||
}
|
||||
|
||||
/* This works around an annoying unsolved frameworks bug some people are hitting. */
|
||||
override fun onBind(intent: Intent): IBinder? {
|
||||
var ret: IBinder? = null
|
||||
try {
|
||||
ret = super.onBind(intent)
|
||||
} catch (_: Throwable) {
|
||||
Timber.e("Failed to bind to TunnelControlTile")
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
override val lifecycle: Lifecycle
|
||||
get() = lifecycleRegistry
|
||||
}
|
||||
|
||||
+12
-35
@@ -1,27 +1,22 @@
|
||||
package com.zaneschepke.wireguardautotunnel.service.tile
|
||||
|
||||
import android.content.Intent
|
||||
import android.os.Build
|
||||
import android.os.IBinder
|
||||
import android.service.quicksettings.Tile
|
||||
import android.service.quicksettings.TileService
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import androidx.lifecycle.LifecycleRegistry
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.zaneschepke.wireguardautotunnel.data.domain.TunnelConfig
|
||||
import com.zaneschepke.wireguardautotunnel.data.repository.AppDataRepository
|
||||
import com.zaneschepke.wireguardautotunnel.module.ApplicationScope
|
||||
import com.zaneschepke.wireguardautotunnel.service.foreground.ServiceManager
|
||||
import com.zaneschepke.wireguardautotunnel.service.tunnel.TunnelService
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import kotlinx.coroutines.CompletableDeferred
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.launch
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Provider
|
||||
|
||||
@AndroidEntryPoint
|
||||
class TunnelControlTile : TileService(), LifecycleOwner {
|
||||
class TunnelControlTile : TileService() {
|
||||
@Inject
|
||||
lateinit var appDataRepository: AppDataRepository
|
||||
|
||||
@@ -32,33 +27,29 @@ class TunnelControlTile : TileService(), LifecycleOwner {
|
||||
@ApplicationScope
|
||||
lateinit var applicationScope: CoroutineScope
|
||||
|
||||
private val lifecycleRegistry: LifecycleRegistry = LifecycleRegistry(this)
|
||||
@Inject
|
||||
lateinit var serviceManager: ServiceManager
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_CREATE)
|
||||
}
|
||||
|
||||
override fun onStopListening() {
|
||||
lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_STOP)
|
||||
serviceManager.tunnelControlTile.complete(this)
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_DESTROY)
|
||||
serviceManager.tunnelControlTile = CompletableDeferred()
|
||||
}
|
||||
|
||||
override fun onStartListening() {
|
||||
super.onStartListening()
|
||||
lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_START)
|
||||
Timber.d("Updating tile!")
|
||||
lifecycleScope.launch {
|
||||
serviceManager.tunnelControlTile.complete(this)
|
||||
applicationScope.launch {
|
||||
if (appDataRepository.tunnels.getAll().isEmpty()) return@launch setUnavailable()
|
||||
updateTileState()
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun updateTileState() {
|
||||
fun updateTileState() = applicationScope.launch {
|
||||
val lastActive = appDataRepository.getStartTunnelConfig()
|
||||
lastActive?.let {
|
||||
updateTile(it)
|
||||
@@ -68,11 +59,11 @@ class TunnelControlTile : TileService(), LifecycleOwner {
|
||||
override fun onClick() {
|
||||
super.onClick()
|
||||
unlockAndRun {
|
||||
lifecycleScope.launch {
|
||||
applicationScope.launch {
|
||||
val lastActive = appDataRepository.getStartTunnelConfig()
|
||||
lastActive?.let { tunnel ->
|
||||
if (tunnel.isActive) {
|
||||
tunnelService.get().stopTunnel(tunnel)
|
||||
tunnelService.get().stopTunnel()
|
||||
} else {
|
||||
tunnelService.get().startTunnel(tunnel, true)
|
||||
}
|
||||
@@ -125,18 +116,4 @@ class TunnelControlTile : TileService(), LifecycleOwner {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* This works around an annoying unsolved frameworks bug some people are hitting. */
|
||||
override fun onBind(intent: Intent): IBinder? {
|
||||
var ret: IBinder? = null
|
||||
try {
|
||||
ret = super.onBind(intent)
|
||||
} catch (_: Throwable) {
|
||||
Timber.e("Failed to bind to TunnelControlTile")
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
override val lifecycle: Lifecycle
|
||||
get() = lifecycleRegistry
|
||||
}
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
package com.zaneschepke.wireguardautotunnel.service.tunnel
|
||||
|
||||
enum class BackendState {
|
||||
KILL_SWITCH_ACTIVE,
|
||||
SERVICE_ACTIVE,
|
||||
INACTIVE,
|
||||
}
|
||||
+10
-5
@@ -5,11 +5,16 @@ import com.zaneschepke.wireguardautotunnel.data.domain.TunnelConfig
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
|
||||
interface TunnelService : Tunnel, org.amnezia.awg.backend.Tunnel {
|
||||
suspend fun startTunnel(tunnelConfig: TunnelConfig, background: Boolean = false): Result<TunnelState>
|
||||
|
||||
suspend fun stopTunnel(tunnelConfig: TunnelConfig): Result<TunnelState>
|
||||
suspend fun startTunnel(tunnelConfig: TunnelConfig?, background: Boolean = false)
|
||||
|
||||
suspend fun bounceTunnel(tunnelConfig: TunnelConfig): Result<TunnelState>
|
||||
suspend fun stopTunnel()
|
||||
|
||||
suspend fun bounceTunnel()
|
||||
|
||||
suspend fun getBackendState(): BackendState
|
||||
|
||||
suspend fun setBackendState(backendState: BackendState, allowedIps: Collection<String>)
|
||||
|
||||
val vpnState: StateFlow<VpnState>
|
||||
|
||||
@@ -17,7 +22,7 @@ interface TunnelService : Tunnel, org.amnezia.awg.backend.Tunnel {
|
||||
|
||||
suspend fun getState(): TunnelState
|
||||
|
||||
fun cancelStatsJob()
|
||||
fun cancelActiveTunnelJobs()
|
||||
|
||||
fun startStatsJob()
|
||||
fun startActiveTunnelJobs()
|
||||
}
|
||||
|
||||
@@ -24,6 +24,14 @@ enum class TunnelState {
|
||||
}
|
||||
}
|
||||
|
||||
fun isDown(): Boolean {
|
||||
return this == DOWN
|
||||
}
|
||||
|
||||
fun isUp(): Boolean {
|
||||
return this == UP
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun from(state: Tunnel.State): TunnelState {
|
||||
return when (state) {
|
||||
|
||||
+193
-100
@@ -9,21 +9,29 @@ import com.zaneschepke.wireguardautotunnel.module.ApplicationScope
|
||||
import com.zaneschepke.wireguardautotunnel.module.IoDispatcher
|
||||
import com.zaneschepke.wireguardautotunnel.module.Kernel
|
||||
import com.zaneschepke.wireguardautotunnel.service.foreground.ServiceManager
|
||||
import com.zaneschepke.wireguardautotunnel.service.notification.NotificationService
|
||||
import com.zaneschepke.wireguardautotunnel.service.notification.WireGuardNotification
|
||||
import com.zaneschepke.wireguardautotunnel.service.tunnel.statistics.AmneziaStatistics
|
||||
import com.zaneschepke.wireguardautotunnel.service.tunnel.statistics.TunnelStatistics
|
||||
import com.zaneschepke.wireguardautotunnel.service.tunnel.statistics.WireGuardStatistics
|
||||
import com.zaneschepke.wireguardautotunnel.util.extensions.asAmBackendState
|
||||
import com.zaneschepke.wireguardautotunnel.util.extensions.asBackendState
|
||||
import kotlinx.coroutines.CoroutineDispatcher
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.amnezia.awg.backend.Tunnel
|
||||
import com.zaneschepke.wireguardautotunnel.R
|
||||
import com.zaneschepke.wireguardautotunnel.service.notification.NotificationAction
|
||||
import com.zaneschepke.wireguardautotunnel.service.notification.NotificationService.Companion.VPN_NOTIFICATION_ID
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Provider
|
||||
@@ -32,29 +40,38 @@ class WireGuardTunnel
|
||||
@Inject
|
||||
constructor(
|
||||
private val amneziaBackend: Provider<org.amnezia.awg.backend.Backend>,
|
||||
tunnelConfigRepository: TunnelConfigRepository,
|
||||
private val tunnelConfigRepository: TunnelConfigRepository,
|
||||
@Kernel private val kernelBackend: Provider<Backend>,
|
||||
private val appDataRepository: AppDataRepository,
|
||||
@ApplicationScope private val applicationScope: CoroutineScope,
|
||||
@IoDispatcher private val ioDispatcher: CoroutineDispatcher,
|
||||
private val serviceManager: ServiceManager,
|
||||
private val notificationService: NotificationService,
|
||||
) : TunnelService {
|
||||
|
||||
private val _vpnState = MutableStateFlow(VpnState())
|
||||
override val vpnState: StateFlow<VpnState> = _vpnState.combine(
|
||||
tunnelConfigRepository.getTunnelConfigsFlow(),
|
||||
) {
|
||||
vpnState, tunnels ->
|
||||
vpnState.copy(
|
||||
tunnelConfig = tunnels.firstOrNull { it.id == vpnState.tunnelConfig?.id },
|
||||
)
|
||||
}.stateIn(applicationScope, SharingStarted.Lazily, VpnState())
|
||||
override val vpnState: StateFlow<VpnState> = _vpnState.asStateFlow()
|
||||
|
||||
private var statsJob: Job? = null
|
||||
private var tunnelChangesJob: Job? = null
|
||||
|
||||
@get:Synchronized @set:Synchronized
|
||||
private var isKernelBackend: Boolean? = null
|
||||
|
||||
private val tunnelControlMutex = Mutex()
|
||||
|
||||
init {
|
||||
applicationScope.launch(ioDispatcher) {
|
||||
appDataRepository.settings.getSettingsFlow().collect {
|
||||
isKernelBackend = it.isKernelEnabled
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun backend(): Any {
|
||||
val settings = appDataRepository.settings.getSettings()
|
||||
if (settings.isKernelEnabled) return kernelBackend.get()
|
||||
val isKernelEnabled = isKernelBackend
|
||||
?: appDataRepository.settings.getSettings().isKernelEnabled
|
||||
if (isKernelEnabled) return kernelBackend.get()
|
||||
return amneziaBackend.get()
|
||||
}
|
||||
|
||||
@@ -89,116 +106,169 @@ constructor(
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun startTunnel(tunnelConfig: TunnelConfig, background: Boolean): Result<TunnelState> {
|
||||
return withContext(ioDispatcher) {
|
||||
onBeforeStart(tunnelConfig, background)
|
||||
setState(tunnelConfig, TunnelState.UP).onSuccess {
|
||||
emitTunnelState(it)
|
||||
}.onFailure {
|
||||
Timber.e(it)
|
||||
onStartFailed()
|
||||
private fun isTunnelAlreadyRunning(tunnelConfig: TunnelConfig): Boolean {
|
||||
val isRunning = tunnelConfig == _vpnState.value.tunnelConfig && _vpnState.value.status.isUp()
|
||||
if (isRunning) Timber.w("Tunnel already running")
|
||||
return isRunning
|
||||
}
|
||||
|
||||
override suspend fun startTunnel(tunnelConfig: TunnelConfig?, background: Boolean) {
|
||||
if (tunnelConfig == null) return
|
||||
withContext(ioDispatcher) {
|
||||
if (isTunnelAlreadyRunning(tunnelConfig)) return@withContext
|
||||
withServiceActive {
|
||||
onBeforeStart(background)
|
||||
tunnelControlMutex.withLock {
|
||||
setState(tunnelConfig, TunnelState.UP).onSuccess {
|
||||
startActiveTunnelJobs()
|
||||
if (it.isUp()) appDataRepository.tunnels.save(tunnelConfig.copy(isActive = true))
|
||||
with(notificationService) {
|
||||
val notification = createNotification(
|
||||
WireGuardNotification.NotificationChannels.VPN,
|
||||
title = "${context.getString(R.string.tunnel_running)} - ${tunnelConfig.name}",
|
||||
actions = listOf(
|
||||
notificationService.createNotificationAction(NotificationAction.TUNNEL_OFF),
|
||||
),
|
||||
)
|
||||
show(VPN_NOTIFICATION_ID, notification)
|
||||
}
|
||||
updateTunnelState(it, tunnelConfig)
|
||||
}
|
||||
}.onFailure {
|
||||
Timber.e(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun stopTunnel(tunnelConfig: TunnelConfig): Result<TunnelState> {
|
||||
return withContext(ioDispatcher) {
|
||||
onBeforeStop(tunnelConfig)
|
||||
setState(tunnelConfig, TunnelState.DOWN).onSuccess {
|
||||
emitTunnelState(it)
|
||||
}.onFailure {
|
||||
Timber.e(it)
|
||||
onStopFailed()
|
||||
override suspend fun stopTunnel() {
|
||||
withContext(ioDispatcher) {
|
||||
if (_vpnState.value.status.isDown()) return@withContext
|
||||
with(_vpnState.value) {
|
||||
if (tunnelConfig == null) return@withContext
|
||||
tunnelControlMutex.withLock {
|
||||
setState(tunnelConfig, TunnelState.DOWN).onSuccess {
|
||||
updateTunnelState(it, null)
|
||||
onStop(tunnelConfig)
|
||||
notificationService.remove(VPN_NOTIFICATION_ID)
|
||||
stopBackgroundService()
|
||||
}.onFailure {
|
||||
Timber.e(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// use this when we just want to bounce tunnel and not change tunnelConfig active state
|
||||
override suspend fun bounceTunnel(tunnelConfig: TunnelConfig): Result<TunnelState> {
|
||||
toggleTunnel(tunnelConfig)
|
||||
delay(VPN_RESTART_DELAY)
|
||||
return toggleTunnel(tunnelConfig)
|
||||
}
|
||||
|
||||
private suspend fun toggleTunnel(tunnelConfig: TunnelConfig): Result<TunnelState> {
|
||||
return withContext(ioDispatcher) {
|
||||
setState(tunnelConfig, TunnelState.TOGGLE).onSuccess {
|
||||
emitTunnelState(it)
|
||||
resetBackendStatistics()
|
||||
}.onFailure {
|
||||
Timber.e(it)
|
||||
private suspend fun toggleTunnel(tunnelConfig: TunnelConfig) {
|
||||
withContext(ioDispatcher) {
|
||||
tunnelControlMutex.withLock {
|
||||
setState(tunnelConfig, TunnelState.TOGGLE)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun onStopFailed() {
|
||||
// utility to keep vpnService alive during rapid changes to prevent bad states
|
||||
private suspend fun withServiceActive(callback: suspend () -> Unit) {
|
||||
when (val backend = backend()) {
|
||||
is org.amnezia.awg.backend.Backend -> {
|
||||
val backendState = backend.backendState
|
||||
if (backendState == org.amnezia.awg.backend.Backend.BackendState.INACTIVE) {
|
||||
backend.setBackendState(org.amnezia.awg.backend.Backend.BackendState.SERVICE_ACTIVE, emptyList())
|
||||
}
|
||||
callback()
|
||||
}
|
||||
is Backend -> callback()
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun bounceTunnel() {
|
||||
_vpnState.value.tunnelConfig?.let {
|
||||
appDataRepository.tunnels.save(it.copy(isActive = true))
|
||||
withServiceActive {
|
||||
toggleTunnel(it)
|
||||
toggleTunnel(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun onStartFailed() {
|
||||
_vpnState.value.tunnelConfig?.let {
|
||||
appDataRepository.tunnels.save(it.copy(isActive = false))
|
||||
override suspend fun getBackendState(): BackendState {
|
||||
return when (val backend = backend()) {
|
||||
is org.amnezia.awg.backend.Backend -> {
|
||||
backend.backendState.asBackendState()
|
||||
}
|
||||
is Backend -> BackendState.SERVICE_ACTIVE
|
||||
else -> BackendState.INACTIVE
|
||||
}
|
||||
cancelStatsJob()
|
||||
resetBackendStatistics()
|
||||
}
|
||||
|
||||
private suspend fun onBeforeStart(tunnelConfig: TunnelConfig, background: Boolean) {
|
||||
if (_vpnState.value.status == TunnelState.UP &&
|
||||
tunnelConfig != _vpnState.value.tunnelConfig
|
||||
) {
|
||||
vpnState.value.tunnelConfig?.let { stopTunnel(it) }
|
||||
override suspend fun setBackendState(backendState: BackendState, allowedIps: Collection<String>) {
|
||||
kotlin.runCatching {
|
||||
when (val backend = backend()) {
|
||||
is org.amnezia.awg.backend.Backend -> {
|
||||
backend.setBackendState(backendState.asAmBackendState(), allowedIps)
|
||||
}
|
||||
is Backend -> {
|
||||
// TODO not yet implemented
|
||||
Timber.d("Kernel backend state not yet implemented")
|
||||
}
|
||||
else -> Unit
|
||||
}
|
||||
}
|
||||
if (background) serviceManager.startBackgroundService()
|
||||
resetBackendStatistics()
|
||||
appDataRepository.tunnels.save(tunnelConfig.copy(isActive = true))
|
||||
emitVpnStateConfig(tunnelConfig)
|
||||
startStatsJob()
|
||||
Timber.d("Updating start")
|
||||
serviceManager.requestTunnelTileUpdate()
|
||||
}
|
||||
|
||||
private suspend fun onBeforeStop(tunnelConfig: TunnelConfig) {
|
||||
cancelStatsJob()
|
||||
resetBackendStatistics()
|
||||
appDataRepository.tunnels.save(tunnelConfig.copy(isActive = false))
|
||||
private suspend fun shutDownActiveTunnel() {
|
||||
with(_vpnState.value) {
|
||||
if (status.isUp()) {
|
||||
stopTunnel()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun startBackgroundService() {
|
||||
serviceManager.startBackgroundService()
|
||||
serviceManager.updateTunnelTile()
|
||||
}
|
||||
|
||||
private fun stopBackgroundService() {
|
||||
serviceManager.stopBackgroundService()
|
||||
Timber.d("UPdating stop")
|
||||
serviceManager.requestTunnelTileUpdate()
|
||||
serviceManager.updateTunnelTile()
|
||||
}
|
||||
|
||||
private fun emitTunnelState(state: TunnelState) {
|
||||
_vpnState.tryEmit(
|
||||
_vpnState.value.copy(
|
||||
status = state,
|
||||
),
|
||||
)
|
||||
private suspend fun onBeforeStart(background: Boolean) {
|
||||
shutDownActiveTunnel()
|
||||
resetBackendStatistics()
|
||||
val settings = appDataRepository.settings.getSettings()
|
||||
if (background || settings.isKernelEnabled) startBackgroundService()
|
||||
}
|
||||
|
||||
private fun emitBackendStatistics(statistics: TunnelStatistics) {
|
||||
_vpnState.tryEmit(
|
||||
_vpnState.value.copy(
|
||||
statistics = statistics,
|
||||
),
|
||||
)
|
||||
private suspend fun onStop(tunnelConfig: TunnelConfig) {
|
||||
appDataRepository.tunnels.save(tunnelConfig.copy(isActive = false))
|
||||
cancelActiveTunnelJobs()
|
||||
resetBackendStatistics()
|
||||
}
|
||||
|
||||
private fun emitVpnStateConfig(tunnelConfig: TunnelConfig) {
|
||||
_vpnState.tryEmit(
|
||||
_vpnState.value.copy(
|
||||
tunnelConfig = tunnelConfig,
|
||||
),
|
||||
)
|
||||
private fun updateTunnelState(state: TunnelState, tunnelConfig: TunnelConfig?) {
|
||||
_vpnState.update {
|
||||
it.copy(status = state, tunnelConfig = tunnelConfig)
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateTunnelConfig(tunnelConfig: TunnelConfig?) {
|
||||
_vpnState.update {
|
||||
it.copy(tunnelConfig = tunnelConfig)
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateBackendStatistics(statistics: TunnelStatistics) {
|
||||
_vpnState.update {
|
||||
it.copy(statistics = statistics)
|
||||
}
|
||||
}
|
||||
|
||||
private fun resetBackendStatistics() {
|
||||
_vpnState.tryEmit(
|
||||
_vpnState.value.copy(
|
||||
statistics = null,
|
||||
),
|
||||
)
|
||||
_vpnState.update {
|
||||
it.copy(statistics = null)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getState(): TunnelState {
|
||||
@@ -209,12 +279,14 @@ constructor(
|
||||
}
|
||||
}
|
||||
|
||||
override fun cancelStatsJob() {
|
||||
override fun cancelActiveTunnelJobs() {
|
||||
statsJob?.cancel()
|
||||
tunnelChangesJob?.cancel()
|
||||
}
|
||||
|
||||
override fun startStatsJob() {
|
||||
override fun startActiveTunnelJobs() {
|
||||
statsJob = startTunnelStatisticsJob()
|
||||
tunnelChangesJob = startTunnelConfigChangesJob()
|
||||
}
|
||||
|
||||
override fun getName(): String {
|
||||
@@ -226,11 +298,11 @@ constructor(
|
||||
delay(STATS_START_DELAY)
|
||||
while (true) {
|
||||
when (backend) {
|
||||
is Backend -> emitBackendStatistics(
|
||||
is Backend -> updateBackendStatistics(
|
||||
WireGuardStatistics(backend.getStatistics(this@WireGuardTunnel)),
|
||||
)
|
||||
is org.amnezia.awg.backend.Backend -> {
|
||||
emitBackendStatistics(
|
||||
updateBackendStatistics(
|
||||
AmneziaStatistics(
|
||||
backend.getStatistics(this@WireGuardTunnel),
|
||||
),
|
||||
@@ -241,17 +313,38 @@ constructor(
|
||||
}
|
||||
}
|
||||
|
||||
private fun startTunnelConfigChangesJob() = applicationScope.launch(ioDispatcher) {
|
||||
tunnelConfigRepository.getTunnelConfigsFlow().collect {
|
||||
with(_vpnState.value) {
|
||||
if (status.isDown() || tunnelConfig == null) return@collect
|
||||
val vpnConfigFromStorage = it.first { it.id == tunnelConfig.id }
|
||||
val isRestartNeeded = vpnConfigFromStorage.wgQuick != tunnelConfig.wgQuick ||
|
||||
vpnConfigFromStorage.amQuick != tunnelConfig.amQuick
|
||||
updateTunnelConfig(vpnConfigFromStorage)
|
||||
if (isRestartNeeded) {
|
||||
Timber.d("Bouncing tunnel on config change")
|
||||
bounceTunnel()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onStateChange(newState: Tunnel.State) {
|
||||
emitTunnelState(TunnelState.from(newState))
|
||||
_vpnState.update {
|
||||
it.copy(status = TunnelState.from(newState))
|
||||
}
|
||||
serviceManager.updateTunnelTile()
|
||||
}
|
||||
|
||||
override fun onStateChange(state: State) {
|
||||
emitTunnelState(TunnelState.from(state))
|
||||
_vpnState.update {
|
||||
it.copy(status = TunnelState.from(state))
|
||||
}
|
||||
serviceManager.updateTunnelTile()
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val STATS_START_DELAY = 5_000L
|
||||
const val STATS_START_DELAY = 1_000L
|
||||
const val VPN_STATISTIC_CHECK_INTERVAL = 1_000L
|
||||
const val VPN_RESTART_DELAY = 1_000L
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,13 +2,23 @@ package com.zaneschepke.wireguardautotunnel.ui
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.wireguard.android.backend.WgQuickBackend
|
||||
import com.wireguard.android.util.RootShell
|
||||
import com.zaneschepke.logcatter.LogReader
|
||||
import com.zaneschepke.wireguardautotunnel.R
|
||||
import com.zaneschepke.wireguardautotunnel.WireGuardAutoTunnel
|
||||
import com.zaneschepke.wireguardautotunnel.data.domain.TunnelConfig
|
||||
import com.zaneschepke.wireguardautotunnel.data.repository.AppDataRepository
|
||||
import com.zaneschepke.wireguardautotunnel.module.AppShell
|
||||
import com.zaneschepke.wireguardautotunnel.module.IoDispatcher
|
||||
import com.zaneschepke.wireguardautotunnel.service.foreground.ServiceManager
|
||||
import com.zaneschepke.wireguardautotunnel.service.tunnel.BackendState
|
||||
import com.zaneschepke.wireguardautotunnel.service.tunnel.TunnelService
|
||||
import com.zaneschepke.wireguardautotunnel.service.tunnel.TunnelState
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.snackbar.SnackbarController
|
||||
import com.zaneschepke.wireguardautotunnel.util.Constants
|
||||
import com.zaneschepke.wireguardautotunnel.util.LocaleUtil
|
||||
import com.zaneschepke.wireguardautotunnel.util.StringValue
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.CoroutineDispatcher
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
@@ -19,8 +29,11 @@ import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.onCompletion
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
import kotlinx.coroutines.flow.takeWhile
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.plus
|
||||
import kotlinx.coroutines.withContext
|
||||
import timber.log.Timber
|
||||
import xyz.teamgravity.pin_lock_compose.PinManager
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Provider
|
||||
@@ -32,7 +45,9 @@ constructor(
|
||||
private val appDataRepository: AppDataRepository,
|
||||
private val tunnelService: Provider<TunnelService>,
|
||||
@IoDispatcher private val ioDispatcher: CoroutineDispatcher,
|
||||
@AppShell private val rootShell: Provider<RootShell>,
|
||||
private val serviceManager: ServiceManager,
|
||||
private val logReader: LogReader,
|
||||
) : ViewModel() {
|
||||
|
||||
val uiState =
|
||||
@@ -56,13 +71,16 @@ constructor(
|
||||
AppUiState(),
|
||||
)
|
||||
|
||||
private val _isAppReady = MutableStateFlow<Boolean>(false)
|
||||
private val _isAppReady = MutableStateFlow(false)
|
||||
val isAppReady = _isAppReady.asStateFlow()
|
||||
|
||||
private val _configurationChange = MutableStateFlow(false)
|
||||
val configurationChange = _configurationChange.asStateFlow()
|
||||
|
||||
init {
|
||||
viewModelScope.launch {
|
||||
initPin()
|
||||
initAutoTunnel()
|
||||
initServices()
|
||||
initTunnel()
|
||||
appReadyCheck()
|
||||
}
|
||||
@@ -76,7 +94,7 @@ constructor(
|
||||
}
|
||||
|
||||
private suspend fun initTunnel() {
|
||||
if (tunnelService.get().getState() == TunnelState.UP) tunnelService.get().startStatsJob()
|
||||
if (tunnelService.get().getState() == TunnelState.UP) tunnelService.get().startActiveTunnelJobs()
|
||||
val activeTunnels = appDataRepository.tunnels.getActive()
|
||||
if (activeTunnels.isNotEmpty() &&
|
||||
tunnelService.get().getState() == TunnelState.DOWN
|
||||
@@ -90,9 +108,12 @@ constructor(
|
||||
if (isPinEnabled) PinManager.initialize(WireGuardAutoTunnel.instance)
|
||||
}
|
||||
|
||||
private suspend fun initAutoTunnel() {
|
||||
val settings = appDataRepository.settings.getSettings()
|
||||
if (settings.isAutoTunnelEnabled) serviceManager.startAutoTunnel(false)
|
||||
private suspend fun initServices() {
|
||||
withContext(ioDispatcher) {
|
||||
val settings = appDataRepository.settings.getSettings()
|
||||
handleVpnKillSwitchChange(settings.isVpnKillSwitchEnabled)
|
||||
if (settings.isAutoTunnelEnabled) serviceManager.startAutoTunnel(false)
|
||||
}
|
||||
}
|
||||
|
||||
fun onPinLockDisabled() = viewModelScope.launch(ioDispatcher) {
|
||||
@@ -107,4 +128,142 @@ constructor(
|
||||
fun setLocationDisclosureShown() = viewModelScope.launch {
|
||||
appDataRepository.appState.setLocationDisclosureShown(true)
|
||||
}
|
||||
|
||||
fun onToggleLocalLogging() = viewModelScope.launch(ioDispatcher) {
|
||||
with(uiState.value.generalState) {
|
||||
val toggledOn = !isLocalLogsEnabled
|
||||
appDataRepository.appState.setLocalLogsEnabled(toggledOn)
|
||||
if (!toggledOn) onLoggerStop()
|
||||
_configurationChange.update {
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun onLoggerStop() {
|
||||
logReader.deleteAndClearLogs()
|
||||
}
|
||||
|
||||
fun onToggleAlwaysOnVPN() = viewModelScope.launch {
|
||||
with(uiState.value.settings) {
|
||||
appDataRepository.settings.save(
|
||||
copy(
|
||||
isAlwaysOnVpnEnabled = !isAlwaysOnVpnEnabled,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun onLocaleChange(localeTag: String) = viewModelScope.launch {
|
||||
appDataRepository.appState.setLocale(localeTag)
|
||||
LocaleUtil.changeLocale(localeTag)
|
||||
_configurationChange.update {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
fun onToggleRestartAtBoot() = viewModelScope.launch {
|
||||
with(uiState.value.settings) {
|
||||
appDataRepository.settings.save(
|
||||
copy(
|
||||
isRestoreOnBootEnabled = !isRestoreOnBootEnabled,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun onToggleVpnKillSwitch(enabled: Boolean) = viewModelScope.launch {
|
||||
with(uiState.value.settings) {
|
||||
appDataRepository.settings.save(
|
||||
copy(
|
||||
isVpnKillSwitchEnabled = enabled,
|
||||
isLanOnKillSwitchEnabled = if (enabled) isLanOnKillSwitchEnabled else false,
|
||||
),
|
||||
)
|
||||
}
|
||||
handleVpnKillSwitchChange(enabled)
|
||||
}
|
||||
|
||||
private suspend fun handleVpnKillSwitchChange(enabled: Boolean) {
|
||||
withContext(ioDispatcher) {
|
||||
if (enabled) {
|
||||
Timber.d("Starting kill switch")
|
||||
val allowedIps = if (appDataRepository.settings.getSettings().isLanOnKillSwitchEnabled) {
|
||||
TunnelConfig.IPV4_PUBLIC_NETWORKS
|
||||
} else {
|
||||
emptySet()
|
||||
}
|
||||
tunnelService.get().setBackendState(BackendState.KILL_SWITCH_ACTIVE, allowedIps)
|
||||
} else {
|
||||
Timber.d("Sending shutdown of kill switch")
|
||||
tunnelService.get().setBackendState(BackendState.SERVICE_ACTIVE, emptySet())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun onToggleLanOnKillSwitch(enabled: Boolean) = viewModelScope.launch(ioDispatcher) {
|
||||
appDataRepository.settings.save(
|
||||
uiState.value.settings.copy(
|
||||
isLanOnKillSwitchEnabled = enabled,
|
||||
),
|
||||
)
|
||||
val allowedIps = if (enabled) TunnelConfig.IPV4_PUBLIC_NETWORKS else emptySet()
|
||||
Timber.d("Setting allowedIps $allowedIps")
|
||||
tunnelService.get().setBackendState(BackendState.KILL_SWITCH_ACTIVE, allowedIps)
|
||||
}
|
||||
|
||||
fun onToggleShortcutsEnabled() = viewModelScope.launch {
|
||||
with(uiState.value.settings) {
|
||||
appDataRepository.settings.save(
|
||||
copy(
|
||||
isShortcutsEnabled = !isShortcutsEnabled,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun saveKernelMode(enabled: Boolean) = viewModelScope.launch {
|
||||
with(uiState.value.settings) {
|
||||
appDataRepository.settings.save(
|
||||
this.copy(
|
||||
isKernelEnabled = enabled,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun onToggleKernelMode() = viewModelScope.launch {
|
||||
with(uiState.value.settings) {
|
||||
if (!isKernelEnabled) {
|
||||
requestRoot().onSuccess {
|
||||
if (!isKernelSupported()) return@onSuccess SnackbarController.showMessage(StringValue.StringResource(R.string.kernel_not_supported))
|
||||
appDataRepository.settings.save(
|
||||
copy(
|
||||
isKernelEnabled = true,
|
||||
isAmneziaEnabled = false,
|
||||
),
|
||||
)
|
||||
}
|
||||
} else {
|
||||
saveKernelMode(enabled = false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun isKernelSupported(): Boolean {
|
||||
return withContext(ioDispatcher) {
|
||||
WgQuickBackend.hasKernelSupport()
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun requestRoot(): Result<Unit> {
|
||||
return withContext(ioDispatcher) {
|
||||
kotlin.runCatching {
|
||||
rootShell.get().start()
|
||||
SnackbarController.showMessage(StringValue.StringResource(R.string.root_accepted))
|
||||
}.onFailure {
|
||||
SnackbarController.showMessage(StringValue.StringResource(R.string.error_root_denied))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package com.zaneschepke.wireguardautotunnel.ui
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import androidx.activity.compose.setContent
|
||||
import androidx.activity.viewModels
|
||||
@@ -24,9 +24,7 @@ import androidx.compose.material3.surfaceColorAtElevation
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.focus.FocusRequester
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
|
||||
@@ -36,13 +34,10 @@ import androidx.navigation.compose.composable
|
||||
import androidx.navigation.compose.rememberNavController
|
||||
import androidx.navigation.toRoute
|
||||
import com.zaneschepke.wireguardautotunnel.R
|
||||
import com.zaneschepke.wireguardautotunnel.WireGuardAutoTunnel
|
||||
import com.zaneschepke.wireguardautotunnel.data.datastore.LocaleStorage
|
||||
import com.zaneschepke.wireguardautotunnel.data.repository.AppStateRepository
|
||||
import com.zaneschepke.wireguardautotunnel.service.tunnel.TunnelService
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.navigation.BottomNavBar
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.navigation.BottomNavItem
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.navigation.LocalFocusRequester
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.navigation.LocalNavController
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.snackbar.CustomSnackBar
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.snackbar.SnackbarControllerProvider
|
||||
@@ -57,37 +52,30 @@ 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.autotunnel.AutoTunnelScreen
|
||||
import com.zaneschepke.wireguardautotunnel.ui.screens.settings.disclosure.LocationDisclosureScreen
|
||||
import com.zaneschepke.wireguardautotunnel.ui.screens.settings.killswitch.KillSwitchScreen
|
||||
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.Constants
|
||||
import com.zaneschepke.wireguardautotunnel.util.LocaleUtil
|
||||
import com.zaneschepke.wireguardautotunnel.util.extensions.requestAutoTunnelTileServiceUpdate
|
||||
import com.zaneschepke.wireguardautotunnel.util.extensions.requestTunnelTileServiceStateUpdate
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
import kotlin.system.exitProcess
|
||||
|
||||
@AndroidEntryPoint
|
||||
class MainActivity : AppCompatActivity() {
|
||||
|
||||
private val localeStorage: LocaleStorage by lazy {
|
||||
(application as WireGuardAutoTunnel).localeStorage
|
||||
}
|
||||
|
||||
private lateinit var oldPrefLocaleCode: String
|
||||
|
||||
@Inject
|
||||
lateinit var appStateRepository: AppStateRepository
|
||||
|
||||
@Inject
|
||||
lateinit var tunnelService: TunnelService
|
||||
|
||||
private val viewModel by viewModels<AppViewModel>()
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
val viewModel by viewModels<AppViewModel>()
|
||||
|
||||
installSplashScreen().apply {
|
||||
setKeepOnScreenCondition {
|
||||
!viewModel.isAppReady.value
|
||||
@@ -95,13 +83,17 @@ class MainActivity : AppCompatActivity() {
|
||||
}
|
||||
|
||||
setContent {
|
||||
val appUiState by viewModel.uiState.collectAsStateWithLifecycle(lifecycle = this.lifecycle)
|
||||
val appUiState by viewModel.uiState.collectAsStateWithLifecycle()
|
||||
val configurationChange by viewModel.configurationChange.collectAsStateWithLifecycle()
|
||||
val navController = rememberNavController()
|
||||
val rootItemFocusRequester = remember { FocusRequester() }
|
||||
|
||||
LaunchedEffect(appUiState.tunnels) {
|
||||
Timber.d("Updating launched")
|
||||
requestTunnelTileServiceStateUpdate()
|
||||
LaunchedEffect(configurationChange) {
|
||||
if (configurationChange) {
|
||||
Intent(this@MainActivity, MainActivity::class.java).also {
|
||||
startActivity(it)
|
||||
exitProcess(0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
LaunchedEffect(appUiState.autoTunnelActive) {
|
||||
@@ -114,109 +106,110 @@ class MainActivity : AppCompatActivity() {
|
||||
}
|
||||
}
|
||||
|
||||
CompositionLocalProvider(LocalFocusRequester provides rootItemFocusRequester) {
|
||||
CompositionLocalProvider(LocalNavController provides navController) {
|
||||
SnackbarControllerProvider { host ->
|
||||
WireguardAutoTunnelTheme(theme = appUiState.generalState.theme) {
|
||||
Scaffold(
|
||||
contentWindowInsets = WindowInsets(0.dp),
|
||||
snackbarHost = {
|
||||
SnackbarHost(host) { snackbarData: SnackbarData ->
|
||||
CustomSnackBar(
|
||||
snackbarData.visuals.message,
|
||||
isRtl = false,
|
||||
containerColor =
|
||||
MaterialTheme.colorScheme.surfaceColorAtElevation(
|
||||
2.dp,
|
||||
),
|
||||
)
|
||||
}
|
||||
},
|
||||
bottomBar = {
|
||||
BottomNavBar(
|
||||
navController,
|
||||
listOf(
|
||||
BottomNavItem(
|
||||
name = stringResource(R.string.tunnels),
|
||||
route = Route.Main,
|
||||
icon = Icons.Rounded.Home,
|
||||
),
|
||||
BottomNavItem(
|
||||
name = stringResource(R.string.settings),
|
||||
route = Route.Settings,
|
||||
icon = Icons.Rounded.Settings,
|
||||
),
|
||||
BottomNavItem(
|
||||
name = stringResource(R.string.support),
|
||||
route = Route.Support,
|
||||
icon = Icons.Rounded.QuestionMark,
|
||||
),
|
||||
CompositionLocalProvider(LocalNavController provides navController) {
|
||||
SnackbarControllerProvider { host ->
|
||||
WireguardAutoTunnelTheme(theme = appUiState.generalState.theme) {
|
||||
Scaffold(
|
||||
contentWindowInsets = WindowInsets(0.dp),
|
||||
snackbarHost = {
|
||||
SnackbarHost(host) { snackbarData: SnackbarData ->
|
||||
CustomSnackBar(
|
||||
snackbarData.visuals.message,
|
||||
isRtl = false,
|
||||
containerColor =
|
||||
MaterialTheme.colorScheme.surfaceColorAtElevation(
|
||||
2.dp,
|
||||
),
|
||||
)
|
||||
},
|
||||
) {
|
||||
Box(modifier = Modifier.fillMaxSize().padding(it)) {
|
||||
NavHost(
|
||||
navController,
|
||||
enterTransition = { fadeIn(tween(Constants.TRANSITION_ANIMATION_TIME)) },
|
||||
exitTransition = { fadeOut(tween(Constants.TRANSITION_ANIMATION_TIME)) },
|
||||
startDestination = (if (appUiState.generalState.isPinLockEnabled == true) Route.Lock else Route.Main),
|
||||
) {
|
||||
composable<Route.Main> {
|
||||
MainScreen(
|
||||
uiState = appUiState,
|
||||
)
|
||||
}
|
||||
composable<Route.Settings> {
|
||||
SettingsScreen(
|
||||
appViewModel = viewModel,
|
||||
uiState = appUiState,
|
||||
)
|
||||
}
|
||||
composable<Route.LocationDisclosure> {
|
||||
LocationDisclosureScreen(viewModel, appUiState)
|
||||
}
|
||||
composable<Route.AutoTunnel> {
|
||||
AutoTunnelScreen(
|
||||
appUiState,
|
||||
)
|
||||
}
|
||||
composable<Route.Appearance> {
|
||||
AppearanceScreen()
|
||||
}
|
||||
composable<Route.Language> {
|
||||
LanguageScreen(localeStorage)
|
||||
}
|
||||
composable<Route.Display> {
|
||||
DisplayScreen(appUiState)
|
||||
}
|
||||
composable<Route.Support> {
|
||||
SupportScreen()
|
||||
}
|
||||
composable<Route.Logs> {
|
||||
LogsScreen()
|
||||
}
|
||||
composable<Route.Config> {
|
||||
val args = it.toRoute<Route.Config>()
|
||||
ConfigScreen(
|
||||
tunnelId = args.id,
|
||||
)
|
||||
}
|
||||
composable<Route.Option> {
|
||||
val args = it.toRoute<Route.Option>()
|
||||
OptionsScreen(
|
||||
tunnelId = args.id,
|
||||
appUiState = appUiState,
|
||||
)
|
||||
}
|
||||
composable<Route.Lock> {
|
||||
PinLockScreen(
|
||||
appViewModel = viewModel,
|
||||
)
|
||||
}
|
||||
composable<Route.Scanner> {
|
||||
ScannerScreen()
|
||||
}
|
||||
}
|
||||
},
|
||||
bottomBar = {
|
||||
BottomNavBar(
|
||||
navController,
|
||||
listOf(
|
||||
BottomNavItem(
|
||||
name = stringResource(R.string.tunnels),
|
||||
route = Route.Main,
|
||||
icon = Icons.Rounded.Home,
|
||||
),
|
||||
BottomNavItem(
|
||||
name = stringResource(R.string.settings),
|
||||
route = Route.Settings,
|
||||
icon = Icons.Rounded.Settings,
|
||||
),
|
||||
BottomNavItem(
|
||||
name = stringResource(R.string.support),
|
||||
route = Route.Support,
|
||||
icon = Icons.Rounded.QuestionMark,
|
||||
),
|
||||
),
|
||||
)
|
||||
},
|
||||
) { padding ->
|
||||
Box(modifier = Modifier.fillMaxSize().padding(padding)) {
|
||||
NavHost(
|
||||
navController,
|
||||
enterTransition = { fadeIn(tween(Constants.TRANSITION_ANIMATION_TIME)) },
|
||||
exitTransition = { fadeOut(tween(Constants.TRANSITION_ANIMATION_TIME)) },
|
||||
startDestination = (if (appUiState.generalState.isPinLockEnabled) Route.Lock else Route.Main),
|
||||
) {
|
||||
composable<Route.Main> {
|
||||
MainScreen(
|
||||
uiState = appUiState,
|
||||
)
|
||||
}
|
||||
composable<Route.Settings> {
|
||||
SettingsScreen(
|
||||
appViewModel = viewModel,
|
||||
uiState = appUiState,
|
||||
)
|
||||
}
|
||||
composable<Route.LocationDisclosure> {
|
||||
LocationDisclosureScreen(viewModel, appUiState)
|
||||
}
|
||||
composable<Route.AutoTunnel> {
|
||||
AutoTunnelScreen(
|
||||
appUiState,
|
||||
)
|
||||
}
|
||||
composable<Route.Appearance> {
|
||||
AppearanceScreen()
|
||||
}
|
||||
composable<Route.Language> {
|
||||
LanguageScreen(appUiState, viewModel)
|
||||
}
|
||||
composable<Route.Display> {
|
||||
DisplayScreen(appUiState)
|
||||
}
|
||||
composable<Route.Support> {
|
||||
SupportScreen(appUiState, viewModel)
|
||||
}
|
||||
composable<Route.Logs> {
|
||||
LogsScreen()
|
||||
}
|
||||
composable<Route.Config> {
|
||||
val args = it.toRoute<Route.Config>()
|
||||
ConfigScreen(
|
||||
tunnelId = args.id,
|
||||
)
|
||||
}
|
||||
composable<Route.Option> {
|
||||
val args = it.toRoute<Route.Option>()
|
||||
OptionsScreen(
|
||||
tunnelId = args.id,
|
||||
appUiState = appUiState,
|
||||
)
|
||||
}
|
||||
composable<Route.Lock> {
|
||||
PinLockScreen(
|
||||
appViewModel = viewModel,
|
||||
)
|
||||
}
|
||||
composable<Route.Scanner> {
|
||||
ScannerScreen()
|
||||
}
|
||||
composable<Route.KillSwitch> {
|
||||
KillSwitchScreen(appUiState, viewModel)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -226,24 +219,9 @@ class MainActivity : AppCompatActivity() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun attachBaseContext(newBase: Context) {
|
||||
oldPrefLocaleCode = LocaleStorage(newBase).getPreferredLocale()
|
||||
applyOverrideConfiguration(LocaleUtil.getLocalizedConfiguration(oldPrefLocaleCode))
|
||||
super.attachBaseContext(newBase)
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
val currentLocaleCode = LocaleStorage(this).getPreferredLocale()
|
||||
if (oldPrefLocaleCode != currentLocaleCode) {
|
||||
recreate() // locale is changed, restart the activity to update
|
||||
oldPrefLocaleCode = currentLocaleCode
|
||||
}
|
||||
super.onResume()
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
tunnelService.cancelStatsJob()
|
||||
// save battery by not polling stats while app is closed
|
||||
tunnelService.cancelActiveTunnelJobs()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,6 +21,9 @@ sealed class Route {
|
||||
@Serializable
|
||||
data object Display : Route()
|
||||
|
||||
@Serializable
|
||||
data object KillSwitch : Route()
|
||||
|
||||
@Serializable
|
||||
data object Language : Route()
|
||||
|
||||
|
||||
-1
@@ -25,7 +25,6 @@ import androidx.compose.ui.unit.dp
|
||||
import com.zaneschepke.wireguardautotunnel.ui.theme.iconSize
|
||||
import com.zaneschepke.wireguardautotunnel.util.extensions.scaledHeight
|
||||
import com.zaneschepke.wireguardautotunnel.util.extensions.scaledWidth
|
||||
import kotlin.let
|
||||
|
||||
@androidx.compose.runtime.Composable
|
||||
fun IconSurfaceButton(title: String, onClick: () -> Unit, selected: Boolean, leadingIcon: ImageVector? = null, description: String? = null) {
|
||||
|
||||
+20
-13
@@ -3,6 +3,7 @@ package com.zaneschepke.wireguardautotunnel.ui.common.config
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.interaction.collectIsFocusedAsState
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.text.KeyboardActions
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.material.icons.Icons
|
||||
@@ -10,7 +11,6 @@ import androidx.compose.material.icons.outlined.Save
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.OutlinedTextField
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
@@ -20,10 +20,11 @@ import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalFocusManager
|
||||
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.input.ImeAction
|
||||
import androidx.compose.ui.text.input.KeyboardCapitalization
|
||||
import com.zaneschepke.wireguardautotunnel.R
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.textbox.CustomTextField
|
||||
import com.zaneschepke.wireguardautotunnel.util.extensions.scaledWidth
|
||||
|
||||
@Composable
|
||||
fun SubmitConfigurationTextBox(
|
||||
@@ -44,17 +45,22 @@ fun SubmitConfigurationTextBox(
|
||||
|
||||
var stateValue by remember { mutableStateOf(value ?: "") }
|
||||
|
||||
OutlinedTextField(
|
||||
CustomTextField(
|
||||
isError = isErrorValue(stateValue),
|
||||
modifier = Modifier
|
||||
.fillMaxWidth(),
|
||||
textStyle = MaterialTheme.typography.bodySmall,
|
||||
value = stateValue,
|
||||
singleLine = true,
|
||||
interactionSource = interactionSource,
|
||||
onValueChange = { stateValue = it },
|
||||
interactionSource = interactionSource,
|
||||
label = { Text(label) },
|
||||
maxLines = 1,
|
||||
placeholder = { Text(hint) },
|
||||
containerColor = MaterialTheme.colorScheme.surface,
|
||||
placeholder = { Text(hint, style = MaterialTheme.typography.bodySmall) },
|
||||
modifier =
|
||||
Modifier
|
||||
.padding(
|
||||
top = 5.dp,
|
||||
bottom = 10.dp,
|
||||
).fillMaxWidth().padding(end = 16.dp.scaledWidth()),
|
||||
singleLine = true,
|
||||
keyboardOptions = keyboardOptions,
|
||||
keyboardActions = KeyboardActions(
|
||||
onDone = {
|
||||
@@ -62,16 +68,17 @@ fun SubmitConfigurationTextBox(
|
||||
keyboardController?.hide()
|
||||
},
|
||||
),
|
||||
trailingIcon = {
|
||||
trailing = {
|
||||
if (!isErrorValue(stateValue) && isFocused) {
|
||||
IconButton(onClick = {
|
||||
onSubmit(stateValue)
|
||||
keyboardController?.hide()
|
||||
focusManager.clearFocus()
|
||||
}) {
|
||||
val icon = Icons.Outlined.Save
|
||||
Icon(
|
||||
imageVector = Icons.Outlined.Save,
|
||||
contentDescription = stringResource(R.string.save_changes),
|
||||
imageVector = icon,
|
||||
contentDescription = icon.name,
|
||||
tint = MaterialTheme.colorScheme.primary,
|
||||
)
|
||||
}
|
||||
|
||||
-3
@@ -1,11 +1,8 @@
|
||||
package com.zaneschepke.wireguardautotunnel.ui.common.navigation
|
||||
|
||||
import androidx.compose.runtime.compositionLocalOf
|
||||
import androidx.compose.ui.focus.FocusRequester
|
||||
import androidx.navigation.NavHostController
|
||||
|
||||
val LocalNavController = compositionLocalOf<NavHostController> {
|
||||
error("NavController was not provided")
|
||||
}
|
||||
|
||||
val LocalFocusRequester = compositionLocalOf<FocusRequester> { error("FocusRequester is not provided") }
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package com.zaneschepke.wireguardautotunnel.ui.screens.main.components
|
||||
package com.zaneschepke.wireguardautotunnel.ui.common.permission.vpn
|
||||
|
||||
import androidx.compose.foundation.text.ClickableText
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
package com.zaneschepke.wireguardautotunnel.ui.common.permission.vpn
|
||||
|
||||
import android.net.VpnService
|
||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.appcompat.app.AppCompatActivity.RESULT_OK
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
|
||||
@Composable
|
||||
inline fun <T> withVpnPermission(crossinline onSuccess: (t: T) -> Unit): (t: T) -> Unit {
|
||||
val context = LocalContext.current
|
||||
|
||||
var showVpnPermissionDialog by remember { mutableStateOf(false) }
|
||||
|
||||
val vpnActivity =
|
||||
rememberLauncherForActivityResult(
|
||||
ActivityResultContracts.StartActivityForResult(),
|
||||
onResult = {
|
||||
if (it.resultCode != RESULT_OK) showVpnPermissionDialog = true
|
||||
},
|
||||
)
|
||||
|
||||
VpnDeniedDialog(showVpnPermissionDialog, onDismiss = { showVpnPermissionDialog = false })
|
||||
|
||||
return {
|
||||
val intent = VpnService.prepare(context)
|
||||
if (intent != null) {
|
||||
vpnActivity.launch(intent)
|
||||
} else {
|
||||
onSuccess(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
package com.zaneschepke.wireguardautotunnel.ui.common.permission
|
||||
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.provider.Settings
|
||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||
import androidx.activity.result.ActivityResult
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import com.zaneschepke.wireguardautotunnel.util.extensions.isBatteryOptimizationsDisabled
|
||||
|
||||
@Composable
|
||||
inline fun withIgnoreBatteryOpt(ignore: Boolean, crossinline callback: () -> Unit): () -> Unit {
|
||||
val context = LocalContext.current
|
||||
val batteryActivity =
|
||||
rememberLauncherForActivityResult(
|
||||
ActivityResultContracts.StartActivityForResult(),
|
||||
) { result: ActivityResult ->
|
||||
// we only ask once
|
||||
callback()
|
||||
}
|
||||
return {
|
||||
if (ignore || context.isBatteryOptimizationsDisabled()) {
|
||||
callback()
|
||||
} else {
|
||||
batteryActivity.launch(
|
||||
Intent().apply {
|
||||
action = Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS
|
||||
data = Uri.parse("package:${context.packageName}")
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
-2
@@ -10,7 +10,6 @@ import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.OutlinedTextFieldDefaults
|
||||
import androidx.compose.material3.TextFieldDefaults
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.SolidColor
|
||||
@@ -37,8 +36,8 @@ fun CustomTextField(
|
||||
isError: Boolean = false,
|
||||
readOnly: Boolean = false,
|
||||
enabled: Boolean = true,
|
||||
interactionSource: MutableInteractionSource,
|
||||
) {
|
||||
val interactionSource = remember { MutableInteractionSource() }
|
||||
val space = " "
|
||||
BasicTextField(
|
||||
value = value,
|
||||
|
||||
+6
-6
@@ -90,8 +90,8 @@ fun ConfigScreen(tunnelId: Int) {
|
||||
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
|
||||
var configType by remember { mutableStateOf<ConfigType?>(null) }
|
||||
val derivedConfigType = remember {
|
||||
derivedStateOf<ConfigType> {
|
||||
configType ?: if (!uiState.hasAmneziaProperties()) ConfigType.WIREGUARD else ConfigType.AMNEZIA
|
||||
derivedStateOf {
|
||||
configType ?: if (!uiState.isAmneziaEnabled) ConfigType.WIREGUARD else ConfigType.AMNEZIA
|
||||
}
|
||||
}
|
||||
val saved by viewModel.saved.collectAsStateWithLifecycle(null)
|
||||
@@ -181,8 +181,8 @@ fun ConfigScreen(tunnelId: Int) {
|
||||
)
|
||||
}
|
||||
},
|
||||
) {
|
||||
Column(Modifier.padding(it)) {
|
||||
) { padding ->
|
||||
Column(Modifier.padding(padding)) {
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
verticalArrangement = Arrangement.Top,
|
||||
@@ -243,12 +243,12 @@ fun ConfigScreen(tunnelId: Int) {
|
||||
.clickable { showAuthPrompt = true },
|
||||
value = uiState.interfaceProxy.privateKey,
|
||||
visualTransformation =
|
||||
if ((tunnelId == Constants.MANUAL_TUNNEL_CONFIG_ID.toInt()) || isAuthenticated) {
|
||||
if ((tunnelId == Constants.MANUAL_TUNNEL_CONFIG_ID) || isAuthenticated) {
|
||||
VisualTransformation.None
|
||||
} else {
|
||||
PasswordVisualTransformation()
|
||||
},
|
||||
enabled = (tunnelId == Constants.MANUAL_TUNNEL_CONFIG_ID.toInt()) || isAuthenticated,
|
||||
enabled = (tunnelId == Constants.MANUAL_TUNNEL_CONFIG_ID) || isAuthenticated,
|
||||
onValueChange = { value -> viewModel.onPrivateKeyChange(value) },
|
||||
trailingIcon = {
|
||||
IconButton(
|
||||
|
||||
+1
-3
@@ -18,9 +18,6 @@ data class ConfigUiState(
|
||||
var tunnelName: String = "",
|
||||
val isAmneziaEnabled: Boolean = false,
|
||||
) {
|
||||
fun hasAmneziaProperties(): Boolean {
|
||||
return this.interfaceProxy.junkPacketCount != ""
|
||||
}
|
||||
companion object {
|
||||
fun from(config: Config): ConfigUiState {
|
||||
val proxyPeers = config.peers.map { PeerProxy.from(it) }
|
||||
@@ -77,6 +74,7 @@ data class ConfigUiState(
|
||||
return from(config).copy(
|
||||
tunnelName = tunnel.name,
|
||||
tunnel = tunnel,
|
||||
isAmneziaEnabled = config.`interface`.junkPacketCount.isPresent,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
+49
-66
@@ -1,13 +1,7 @@
|
||||
package com.zaneschepke.wireguardautotunnel.ui.screens.main
|
||||
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.net.VpnService
|
||||
import android.provider.Settings
|
||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||
import androidx.activity.result.ActivityResult
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.appcompat.app.AppCompatActivity.RESULT_OK
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.gestures.ScrollableDefaults
|
||||
import androidx.compose.foundation.gestures.detectTapGestures
|
||||
@@ -37,8 +31,11 @@ import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||
import androidx.compose.ui.input.pointer.pointerInput
|
||||
import androidx.compose.ui.platform.LocalClipboardManager
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.core.os.ConfigurationCompat
|
||||
import androidx.hilt.navigation.compose.hiltViewModel
|
||||
import com.zaneschepke.wireguardautotunnel.R
|
||||
import com.zaneschepke.wireguardautotunnel.data.domain.TunnelConfig
|
||||
@@ -49,50 +46,58 @@ import com.zaneschepke.wireguardautotunnel.ui.common.dialog.InfoDialog
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.functions.rememberFileImportLauncherForResult
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.navigation.LocalNavController
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.navigation.TopNavBar
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.permission.vpn.withVpnPermission
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.permission.withIgnoreBatteryOpt
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.snackbar.SnackbarController
|
||||
import com.zaneschepke.wireguardautotunnel.ui.screens.main.components.AutoTunnelRowItem
|
||||
import com.zaneschepke.wireguardautotunnel.ui.screens.main.components.GettingStartedLabel
|
||||
import com.zaneschepke.wireguardautotunnel.ui.screens.main.components.ScrollDismissFab
|
||||
import com.zaneschepke.wireguardautotunnel.ui.screens.main.components.TunnelImportSheet
|
||||
import com.zaneschepke.wireguardautotunnel.ui.screens.main.components.TunnelRowItem
|
||||
import com.zaneschepke.wireguardautotunnel.ui.screens.main.components.VpnDeniedDialog
|
||||
import com.zaneschepke.wireguardautotunnel.util.Constants
|
||||
import com.zaneschepke.wireguardautotunnel.util.extensions.isBatteryOptimizationsDisabled
|
||||
import com.zaneschepke.wireguardautotunnel.util.extensions.isRunningOnTv
|
||||
import com.zaneschepke.wireguardautotunnel.util.extensions.openWebUrl
|
||||
import com.zaneschepke.wireguardautotunnel.util.extensions.scaledHeight
|
||||
import java.text.Collator
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
@Composable
|
||||
fun MainScreen(viewModel: MainViewModel = hiltViewModel(), uiState: AppUiState) {
|
||||
val context = LocalContext.current
|
||||
val navController = LocalNavController.current
|
||||
val clipboard = LocalClipboardManager.current
|
||||
val snackbar = SnackbarController.current
|
||||
|
||||
var showBottomSheet by remember { mutableStateOf(false) }
|
||||
var showVpnPermissionDialog by remember { mutableStateOf(false) }
|
||||
var isFabVisible by rememberSaveable { mutableStateOf(true) }
|
||||
var showDeleteTunnelAlertDialog by remember { mutableStateOf(false) }
|
||||
var selectedTunnel by remember { mutableStateOf<TunnelConfig?>(null) }
|
||||
val isRunningOnTv = remember { context.isRunningOnTv() }
|
||||
|
||||
val currentLocale = ConfigurationCompat.getLocales(context.resources.configuration)[0]
|
||||
val collator = Collator.getInstance(currentLocale)
|
||||
|
||||
val sortedTunnels = remember(uiState.tunnels) {
|
||||
uiState.tunnels.sortedWith(compareBy(collator) { it.name })
|
||||
}
|
||||
|
||||
val startAutoTunnel = withVpnPermission<Unit> { viewModel.onToggleAutoTunnel() }
|
||||
val startTunnel = withVpnPermission<TunnelConfig> {
|
||||
viewModel.onTunnelStart(it, uiState.settings.isKernelEnabled)
|
||||
}
|
||||
val autoTunnelToggleBattery = withIgnoreBatteryOpt(uiState.generalState.isBatteryOptimizationDisableShown) {
|
||||
if (!uiState.generalState.isBatteryOptimizationDisableShown) viewModel.setBatteryOptimizeDisableShown()
|
||||
if (uiState.settings.isKernelEnabled) {
|
||||
viewModel.onToggleAutoTunnel()
|
||||
} else {
|
||||
startAutoTunnel.invoke(Unit)
|
||||
}
|
||||
}
|
||||
|
||||
val nestedScrollConnection = remember {
|
||||
NestedScrollListener({ isFabVisible = false }, { isFabVisible = true })
|
||||
}
|
||||
|
||||
val vpnActivity =
|
||||
rememberLauncherForActivityResult(
|
||||
ActivityResultContracts.StartActivityForResult(),
|
||||
onResult = {
|
||||
if (it.resultCode != RESULT_OK) showVpnPermissionDialog = true
|
||||
},
|
||||
)
|
||||
val batteryActivity =
|
||||
rememberLauncherForActivityResult(
|
||||
ActivityResultContracts.StartActivityForResult(),
|
||||
) { result: ActivityResult ->
|
||||
viewModel.setBatteryOptimizeDisableShown()
|
||||
}
|
||||
|
||||
val tunnelFileImportResultLauncher = rememberFileImportLauncherForResult(onNoFileExplorer = {
|
||||
snackbar.showMessage(
|
||||
context.getString(R.string.error_no_file_explorer),
|
||||
@@ -108,13 +113,11 @@ fun MainScreen(viewModel: MainViewModel = hiltViewModel(), uiState: AppUiState)
|
||||
navController.navigate(Route.Scanner)
|
||||
}
|
||||
|
||||
VpnDeniedDialog(showVpnPermissionDialog, onDismiss = { showVpnPermissionDialog = false })
|
||||
|
||||
if (showDeleteTunnelAlertDialog) {
|
||||
InfoDialog(
|
||||
onDismiss = { showDeleteTunnelAlertDialog = false },
|
||||
onAttest = {
|
||||
selectedTunnel?.let { viewModel::onDelete }
|
||||
selectedTunnel?.let { viewModel.onDelete(it) }
|
||||
showDeleteTunnelAlertDialog = false
|
||||
selectedTunnel = null
|
||||
},
|
||||
@@ -124,35 +127,13 @@ fun MainScreen(viewModel: MainViewModel = hiltViewModel(), uiState: AppUiState)
|
||||
)
|
||||
}
|
||||
|
||||
fun requestBatteryOptimizationsDisabled() {
|
||||
val intent =
|
||||
Intent().apply {
|
||||
action = Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS
|
||||
data = Uri.parse("package:${context.packageName}")
|
||||
}
|
||||
batteryActivity.launch(intent)
|
||||
}
|
||||
|
||||
fun onAutoTunnelToggle() {
|
||||
if (!uiState.generalState.isBatteryOptimizationDisableShown &&
|
||||
!context.isBatteryOptimizationsDisabled() && !isRunningOnTv
|
||||
) {
|
||||
return requestBatteryOptimizationsDisabled()
|
||||
}
|
||||
val intent = if (!uiState.settings.isKernelEnabled) {
|
||||
VpnService.prepare(context)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
if (intent != null) return vpnActivity.launch(intent)
|
||||
viewModel.onToggleAutoTunnel()
|
||||
}
|
||||
|
||||
fun onTunnelToggle(checked: Boolean, tunnel: TunnelConfig) {
|
||||
val intent = if (uiState.settings.isKernelEnabled) null else VpnService.prepare(context)
|
||||
if (intent != null) return vpnActivity.launch(intent)
|
||||
if (!checked) viewModel.onTunnelStop(tunnel).also { return }
|
||||
viewModel.onTunnelStart(tunnel, uiState.settings.isKernelEnabled)
|
||||
if (!checked) viewModel.onTunnelStop().also { return }
|
||||
if (uiState.settings.isKernelEnabled) {
|
||||
viewModel.onTunnelStart(tunnel, uiState.settings.isKernelEnabled)
|
||||
} else {
|
||||
startTunnel.invoke(tunnel)
|
||||
}
|
||||
}
|
||||
|
||||
Scaffold(
|
||||
@@ -199,12 +180,17 @@ fun MainScreen(viewModel: MainViewModel = hiltViewModel(), uiState: AppUiState)
|
||||
)
|
||||
}
|
||||
},
|
||||
) {
|
||||
) { padding ->
|
||||
TunnelImportSheet(
|
||||
showBottomSheet,
|
||||
onDismiss = { showBottomSheet = false },
|
||||
onFileClick = { tunnelFileImportResultLauncher.launch(Constants.ALLOWED_TV_FILE_TYPES) },
|
||||
onQrClick = { requestPermissionLauncher.launch(android.Manifest.permission.CAMERA) },
|
||||
onClipboardClick = {
|
||||
clipboard.getText()?.text?.let {
|
||||
viewModel.onClipboardImport(it)
|
||||
}
|
||||
},
|
||||
onManualImportClick = {
|
||||
navController.navigate(
|
||||
Route.Config(Constants.MANUAL_TUNNEL_CONFIG_ID),
|
||||
@@ -213,10 +199,10 @@ fun MainScreen(viewModel: MainViewModel = hiltViewModel(), uiState: AppUiState)
|
||||
)
|
||||
LazyColumn(
|
||||
horizontalAlignment = Alignment.Start,
|
||||
verticalArrangement = Arrangement.Top,
|
||||
verticalArrangement = Arrangement.spacedBy(5.dp.scaledHeight(), Alignment.Top),
|
||||
modifier =
|
||||
Modifier
|
||||
.fillMaxSize().padding(it)
|
||||
.fillMaxSize().padding(padding)
|
||||
.overscroll(ScrollableDefaults.overscrollEffect())
|
||||
.nestedScroll(nestedScrollConnection),
|
||||
state = rememberLazyListState(0, uiState.tunnels.count()),
|
||||
@@ -230,22 +216,19 @@ fun MainScreen(viewModel: MainViewModel = hiltViewModel(), uiState: AppUiState)
|
||||
}
|
||||
} else {
|
||||
item {
|
||||
AutoTunnelRowItem(uiState, {
|
||||
onAutoTunnelToggle()
|
||||
})
|
||||
AutoTunnelRowItem(uiState) {
|
||||
autoTunnelToggleBattery.invoke()
|
||||
}
|
||||
}
|
||||
}
|
||||
items(
|
||||
uiState.tunnels,
|
||||
sortedTunnels,
|
||||
key = { tunnel -> tunnel.id },
|
||||
) { tunnel ->
|
||||
val isActive = uiState.tunnels.any {
|
||||
it.id == tunnel.id &&
|
||||
it.isActive
|
||||
}
|
||||
val expanded = uiState.generalState.isTunnelStatsExpanded
|
||||
TunnelRowItem(
|
||||
isActive,
|
||||
tunnel.id == uiState.vpnState.tunnelConfig?.id &&
|
||||
uiState.vpnState.status.isUp(),
|
||||
expanded,
|
||||
selectedTunnel?.id == tunnel.id,
|
||||
tunnel,
|
||||
|
||||
+14
-21
@@ -72,9 +72,9 @@ constructor(
|
||||
tunnelService.get().startTunnel(tunnelConfig, background)
|
||||
}
|
||||
|
||||
fun onTunnelStop(tunnel: TunnelConfig) = viewModelScope.launch {
|
||||
fun onTunnelStop() = viewModelScope.launch {
|
||||
Timber.i("Stopping active tunnel")
|
||||
tunnelService.get().stopTunnel(tunnel)
|
||||
tunnelService.get().stopTunnel()
|
||||
}
|
||||
|
||||
private fun generateQrCodeDefaultName(config: String): String {
|
||||
@@ -159,18 +159,7 @@ constructor(
|
||||
}
|
||||
|
||||
fun onToggleAutoTunnel() = viewModelScope.launch {
|
||||
val settings = appDataRepository.settings.getSettings()
|
||||
val toggled = !settings.isAutoTunnelEnabled
|
||||
if (toggled) {
|
||||
serviceManager.startAutoTunnel(false)
|
||||
} else {
|
||||
serviceManager.stopAutoTunnel()
|
||||
}
|
||||
appDataRepository.settings.save(
|
||||
settings.copy(
|
||||
isAutoTunnelEnabled = toggled,
|
||||
),
|
||||
)
|
||||
serviceManager.toggleAutoTunnel(false)
|
||||
}
|
||||
|
||||
private suspend fun saveTunnelsFromZipUri(uri: Uri, context: Context) {
|
||||
@@ -252,13 +241,17 @@ constructor(
|
||||
|
||||
fun onCopyTunnel(tunnel: TunnelConfig) = viewModelScope.launch {
|
||||
saveTunnel(
|
||||
tunnel.copy(
|
||||
id = 0,
|
||||
isPrimaryTunnel = false,
|
||||
isMobileDataTunnel = false,
|
||||
isActive = false,
|
||||
name = makeTunnelNameUnique(tunnel.name),
|
||||
),
|
||||
TunnelConfig(name = makeTunnelNameUnique(tunnel.name), wgQuick = tunnel.wgQuick, amQuick = tunnel.amQuick),
|
||||
)
|
||||
}
|
||||
|
||||
fun onClipboardImport(config: String) = viewModelScope.launch(ioDispatcher) {
|
||||
runCatching {
|
||||
val amConfig = TunnelConfig.configFromAmQuick(config)
|
||||
val tunnelConfig = TunnelConfig.tunnelConfigFromAmConfig(amConfig, makeTunnelNameUnique(generateQrCodeDefaultName(config)))
|
||||
saveTunnel(tunnelConfig)
|
||||
}.onFailure {
|
||||
SnackbarController.showMessage(StringValue.StringResource(R.string.error_file_format))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+2
-6
@@ -5,10 +5,8 @@ import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.rounded.Bolt
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.scale
|
||||
import androidx.compose.ui.focus.FocusRequester
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
@@ -19,12 +17,10 @@ import com.zaneschepke.wireguardautotunnel.ui.common.ExpandingRowListItem
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.ScaledSwitch
|
||||
import com.zaneschepke.wireguardautotunnel.ui.theme.SilverTree
|
||||
import com.zaneschepke.wireguardautotunnel.util.extensions.isRunningOnTv
|
||||
import com.zaneschepke.wireguardautotunnel.util.extensions.scaledHeight
|
||||
|
||||
@Composable
|
||||
fun AutoTunnelRowItem(appUiState: AppUiState, onToggle: () -> Unit) {
|
||||
val context = LocalContext.current
|
||||
val itemFocusRequester = remember { FocusRequester() }
|
||||
ExpandingRowListItem(
|
||||
leading = {
|
||||
val icon = Icons.Rounded.Bolt
|
||||
@@ -33,7 +29,7 @@ fun AutoTunnelRowItem(appUiState: AppUiState, onToggle: () -> Unit) {
|
||||
icon.name,
|
||||
modifier =
|
||||
Modifier
|
||||
.size(16.dp.scaledHeight()).scale(1.5f),
|
||||
.size(16.dp).scale(1.5f),
|
||||
tint =
|
||||
if (!appUiState.autoTunnelActive) {
|
||||
Color.Gray
|
||||
@@ -53,7 +49,7 @@ fun AutoTunnelRowItem(appUiState: AppUiState, onToggle: () -> Unit) {
|
||||
},
|
||||
onClick = {
|
||||
if (context.isRunningOnTv()) {
|
||||
itemFocusRequester.requestFocus()
|
||||
onToggle()
|
||||
}
|
||||
},
|
||||
isExpanded = false,
|
||||
|
||||
+32
-1
@@ -5,6 +5,7 @@ import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.ContentPasteGo
|
||||
import androidx.compose.material.icons.filled.Create
|
||||
import androidx.compose.material.icons.filled.FileOpen
|
||||
import androidx.compose.material.icons.filled.QrCode
|
||||
@@ -22,9 +23,17 @@ import androidx.compose.ui.unit.dp
|
||||
import com.zaneschepke.wireguardautotunnel.R
|
||||
import com.zaneschepke.wireguardautotunnel.util.extensions.isRunningOnTv
|
||||
|
||||
// TODO refactor this component
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun TunnelImportSheet(show: Boolean, onDismiss: () -> Unit, onFileClick: () -> Unit, onQrClick: () -> Unit, onManualImportClick: () -> Unit) {
|
||||
fun TunnelImportSheet(
|
||||
show: Boolean,
|
||||
onDismiss: () -> Unit,
|
||||
onFileClick: () -> Unit,
|
||||
onQrClick: () -> Unit,
|
||||
onManualImportClick: () -> Unit,
|
||||
onClipboardClick: () -> Unit,
|
||||
) {
|
||||
val sheetState = rememberModalBottomSheetState()
|
||||
|
||||
val context = LocalContext.current
|
||||
@@ -77,6 +86,28 @@ fun TunnelImportSheet(show: Boolean, onDismiss: () -> Unit, onFileClick: () -> U
|
||||
modifier = Modifier.padding(10.dp),
|
||||
)
|
||||
}
|
||||
HorizontalDivider()
|
||||
Row(
|
||||
modifier =
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.clickable {
|
||||
onDismiss()
|
||||
onClipboardClick()
|
||||
}
|
||||
.padding(10.dp),
|
||||
) {
|
||||
val icon = Icons.Filled.ContentPasteGo
|
||||
Icon(
|
||||
icon,
|
||||
contentDescription = icon.name,
|
||||
modifier = Modifier.padding(10.dp),
|
||||
)
|
||||
Text(
|
||||
stringResource(id = R.string.add_from_clipboard),
|
||||
modifier = Modifier.padding(10.dp),
|
||||
)
|
||||
}
|
||||
}
|
||||
HorizontalDivider()
|
||||
Row(
|
||||
|
||||
+8
-11
@@ -9,6 +9,7 @@ import androidx.compose.material.icons.rounded.CopyAll
|
||||
import androidx.compose.material.icons.rounded.Delete
|
||||
import androidx.compose.material.icons.rounded.Info
|
||||
import androidx.compose.material.icons.rounded.Settings
|
||||
import androidx.compose.material.icons.rounded.SettingsEthernet
|
||||
import androidx.compose.material.icons.rounded.Smartphone
|
||||
import androidx.compose.material.icons.rounded.Star
|
||||
import androidx.compose.material3.Icon
|
||||
@@ -33,7 +34,6 @@ import com.zaneschepke.wireguardautotunnel.ui.common.navigation.LocalNavControll
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.snackbar.SnackbarController
|
||||
import com.zaneschepke.wireguardautotunnel.util.extensions.asColor
|
||||
import com.zaneschepke.wireguardautotunnel.util.extensions.isRunningOnTv
|
||||
import com.zaneschepke.wireguardautotunnel.util.extensions.scaledHeight
|
||||
|
||||
@Composable
|
||||
fun TunnelRowItem(
|
||||
@@ -56,20 +56,17 @@ fun TunnelRowItem(
|
||||
val itemFocusRequester = remember { FocusRequester() }
|
||||
ExpandingRowListItem(
|
||||
leading = {
|
||||
val circleIcon = Icons.Rounded.Circle
|
||||
val icon =
|
||||
if (tunnel.isPrimaryTunnel) {
|
||||
Icons.Rounded.Star
|
||||
} else if (tunnel.isMobileDataTunnel) {
|
||||
Icons.Rounded.Smartphone
|
||||
} else {
|
||||
circleIcon
|
||||
}
|
||||
val icon = when {
|
||||
tunnel.isPrimaryTunnel -> Icons.Rounded.Star
|
||||
tunnel.isMobileDataTunnel -> Icons.Rounded.Smartphone
|
||||
tunnel.isEthernetTunnel -> Icons.Rounded.SettingsEthernet
|
||||
else -> Icons.Rounded.Circle
|
||||
}
|
||||
Icon(
|
||||
icon,
|
||||
icon.name,
|
||||
tint = leadingIconColor,
|
||||
modifier = Modifier.size(16.dp.scaledHeight()),
|
||||
modifier = Modifier.size(16.dp),
|
||||
)
|
||||
},
|
||||
text = tunnel.name,
|
||||
|
||||
+190
-97
@@ -2,17 +2,20 @@ package com.zaneschepke.wireguardautotunnel.ui.screens.options
|
||||
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.ExperimentalLayoutApi
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.outlined.Edit
|
||||
import androidx.compose.material.icons.outlined.NetworkPing
|
||||
import androidx.compose.material.icons.outlined.PhoneAndroid
|
||||
import androidx.compose.material.icons.outlined.Security
|
||||
import androidx.compose.material.icons.outlined.SettingsEthernet
|
||||
import androidx.compose.material.icons.outlined.Star
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
@@ -28,6 +31,8 @@ import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.input.ImeAction
|
||||
import androidx.compose.ui.text.input.KeyboardType
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.hilt.navigation.compose.hiltViewModel
|
||||
import com.zaneschepke.wireguardautotunnel.R
|
||||
@@ -36,16 +41,18 @@ import com.zaneschepke.wireguardautotunnel.ui.Route
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.ScaledSwitch
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SelectionItem
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SurfaceSelectionGroupButton
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.config.SubmitConfigurationTextBox
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.label.GroupLabel
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.navigation.LocalNavController
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.navigation.TopNavBar
|
||||
import com.zaneschepke.wireguardautotunnel.ui.screens.settings.autotunnel.components.TrustedNetworkTextBox
|
||||
import com.zaneschepke.wireguardautotunnel.ui.screens.settings.autotunnel.components.WildcardsLabel
|
||||
import com.zaneschepke.wireguardautotunnel.ui.theme.iconSize
|
||||
import com.zaneschepke.wireguardautotunnel.util.Constants
|
||||
import com.zaneschepke.wireguardautotunnel.util.extensions.isValidIpv4orIpv6Address
|
||||
import com.zaneschepke.wireguardautotunnel.util.extensions.scaledHeight
|
||||
import com.zaneschepke.wireguardautotunnel.util.extensions.scaledWidth
|
||||
|
||||
@OptIn(ExperimentalLayoutApi::class)
|
||||
@Composable
|
||||
fun OptionsScreen(optionsViewModel: OptionsViewModel = hiltViewModel(), appUiState: AppUiState, tunnelId: Int) {
|
||||
val navController = LocalNavController.current
|
||||
@@ -80,117 +87,203 @@ fun OptionsScreen(optionsViewModel: OptionsViewModel = hiltViewModel(), appUiSta
|
||||
Modifier
|
||||
.fillMaxSize()
|
||||
.padding(it)
|
||||
.verticalScroll(rememberScrollState())
|
||||
.padding(top = 24.dp.scaledHeight())
|
||||
.padding(horizontal = 24.dp.scaledWidth()),
|
||||
) {
|
||||
GroupLabel(stringResource(R.string.auto_tunneling))
|
||||
SurfaceSelectionGroupButton(
|
||||
listOf(
|
||||
SelectionItem(
|
||||
Icons.Outlined.Star,
|
||||
title = {
|
||||
Text(
|
||||
stringResource(R.string.primary_tunnel),
|
||||
style = MaterialTheme.typography.bodyMedium.copy(MaterialTheme.colorScheme.onSurface),
|
||||
)
|
||||
},
|
||||
description = {
|
||||
Text(
|
||||
stringResource(R.string.set_primary_tunnel),
|
||||
style = MaterialTheme.typography.bodyMedium.copy(MaterialTheme.colorScheme.outline),
|
||||
)
|
||||
},
|
||||
trailing = {
|
||||
ScaledSwitch(
|
||||
config.isPrimaryTunnel,
|
||||
buildList {
|
||||
addAll(
|
||||
listOf(
|
||||
SelectionItem(
|
||||
Icons.Outlined.Star,
|
||||
title = {
|
||||
Text(
|
||||
stringResource(R.string.primary_tunnel),
|
||||
style = MaterialTheme.typography.bodyMedium.copy(MaterialTheme.colorScheme.onSurface),
|
||||
)
|
||||
},
|
||||
description = {
|
||||
Text(
|
||||
stringResource(R.string.set_primary_tunnel),
|
||||
style = MaterialTheme.typography.bodyMedium.copy(MaterialTheme.colorScheme.outline),
|
||||
)
|
||||
},
|
||||
trailing = {
|
||||
ScaledSwitch(
|
||||
config.isPrimaryTunnel,
|
||||
onClick = { optionsViewModel.onTogglePrimaryTunnel(config) },
|
||||
)
|
||||
},
|
||||
onClick = { optionsViewModel.onTogglePrimaryTunnel(config) },
|
||||
)
|
||||
},
|
||||
onClick = { optionsViewModel.onTogglePrimaryTunnel(config) },
|
||||
),
|
||||
SelectionItem(
|
||||
Icons.Outlined.PhoneAndroid,
|
||||
title = { Text(stringResource(R.string.mobile_tunnel), style = MaterialTheme.typography.bodyMedium.copy(MaterialTheme.colorScheme.onSurface)) },
|
||||
description = {
|
||||
Text(
|
||||
stringResource(R.string.mobile_data_tunnel),
|
||||
style = MaterialTheme.typography.bodyMedium.copy(MaterialTheme.colorScheme.outline),
|
||||
)
|
||||
},
|
||||
trailing = {
|
||||
ScaledSwitch(
|
||||
config.isMobileDataTunnel,
|
||||
),
|
||||
SelectionItem(
|
||||
Icons.Outlined.PhoneAndroid,
|
||||
title = {
|
||||
Text(
|
||||
stringResource(R.string.mobile_tunnel),
|
||||
style = MaterialTheme.typography.bodyMedium.copy(MaterialTheme.colorScheme.onSurface),
|
||||
)
|
||||
},
|
||||
description = {
|
||||
Text(
|
||||
stringResource(R.string.mobile_data_tunnel),
|
||||
style = MaterialTheme.typography.bodyMedium.copy(MaterialTheme.colorScheme.outline),
|
||||
)
|
||||
},
|
||||
trailing = {
|
||||
ScaledSwitch(
|
||||
config.isMobileDataTunnel,
|
||||
onClick = { optionsViewModel.onToggleIsMobileDataTunnel(config) },
|
||||
)
|
||||
},
|
||||
onClick = { optionsViewModel.onToggleIsMobileDataTunnel(config) },
|
||||
)
|
||||
},
|
||||
onClick = { optionsViewModel.onToggleIsMobileDataTunnel(config) },
|
||||
),
|
||||
SelectionItem(
|
||||
Icons.Outlined.NetworkPing,
|
||||
title = {
|
||||
Text(
|
||||
stringResource(R.string.restart_on_ping),
|
||||
style = MaterialTheme.typography.bodyMedium.copy(MaterialTheme.colorScheme.onSurface),
|
||||
)
|
||||
},
|
||||
trailing = {
|
||||
ScaledSwitch(
|
||||
checked = config.isPingEnabled,
|
||||
),
|
||||
SelectionItem(
|
||||
Icons.Outlined.SettingsEthernet,
|
||||
title = {
|
||||
Text(
|
||||
stringResource(R.string.ethernet_tunnel),
|
||||
style = MaterialTheme.typography.bodyMedium.copy(MaterialTheme.colorScheme.onSurface),
|
||||
)
|
||||
},
|
||||
description = {
|
||||
Text(
|
||||
stringResource(R.string.set_ethernet_tunnel),
|
||||
style = MaterialTheme.typography.bodyMedium.copy(MaterialTheme.colorScheme.outline),
|
||||
)
|
||||
},
|
||||
trailing = {
|
||||
ScaledSwitch(
|
||||
config.isEthernetTunnel,
|
||||
onClick = { optionsViewModel.onToggleIsEthernetTunnel(config) },
|
||||
)
|
||||
},
|
||||
onClick = { optionsViewModel.onToggleIsEthernetTunnel(config) },
|
||||
),
|
||||
SelectionItem(
|
||||
Icons.Outlined.NetworkPing,
|
||||
title = {
|
||||
Text(
|
||||
stringResource(R.string.restart_on_ping),
|
||||
style = MaterialTheme.typography.bodyMedium.copy(MaterialTheme.colorScheme.onSurface),
|
||||
)
|
||||
},
|
||||
trailing = {
|
||||
ScaledSwitch(
|
||||
checked = config.isPingEnabled,
|
||||
onClick = { optionsViewModel.onToggleRestartOnPing(config) },
|
||||
)
|
||||
},
|
||||
onClick = { optionsViewModel.onToggleRestartOnPing(config) },
|
||||
)
|
||||
},
|
||||
onClick = { optionsViewModel.onToggleRestartOnPing(config) },
|
||||
),
|
||||
SelectionItem(
|
||||
title = {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier.fillMaxWidth().padding(vertical = 4.dp.scaledHeight()),
|
||||
) {
|
||||
),
|
||||
),
|
||||
)
|
||||
if (config.isPingEnabled || appUiState.settings.isPingEnabled) {
|
||||
add(
|
||||
SelectionItem(
|
||||
title = {},
|
||||
description = {
|
||||
SubmitConfigurationTextBox(
|
||||
config.pingIp,
|
||||
stringResource(R.string.set_custom_ping_ip),
|
||||
stringResource(R.string.default_ping_ip),
|
||||
isErrorValue = { !it.isNullOrBlank() && !it.isValidIpv4orIpv6Address() },
|
||||
onSubmit = {
|
||||
optionsViewModel.saveTunnelChanges(
|
||||
config.copy(pingIp = it.ifBlank { null }),
|
||||
)
|
||||
},
|
||||
)
|
||||
fun isSecondsError(seconds: String?): Boolean {
|
||||
return seconds?.let { value -> if (value.isBlank()) false else value.toLong() >= Long.MAX_VALUE / 1000 } ?: false
|
||||
}
|
||||
SubmitConfigurationTextBox(
|
||||
config.pingInterval?.let { (it / 1000).toString() },
|
||||
stringResource(R.string.set_custom_ping_internal),
|
||||
"(${stringResource(R.string.optional_default)} ${Constants.PING_INTERVAL / 1000})",
|
||||
keyboardOptions = KeyboardOptions(
|
||||
keyboardType = KeyboardType.Number,
|
||||
imeAction = ImeAction.Done,
|
||||
),
|
||||
isErrorValue = ::isSecondsError,
|
||||
onSubmit = {
|
||||
optionsViewModel.saveTunnelChanges(
|
||||
config.copy(pingInterval = if (it.isBlank()) null else it.toLong() * 1000),
|
||||
)
|
||||
},
|
||||
)
|
||||
SubmitConfigurationTextBox(
|
||||
config.pingCooldown?.let { (it / 1000).toString() },
|
||||
stringResource(R.string.set_custom_ping_cooldown),
|
||||
"(${stringResource(R.string.optional_default)} ${Constants.PING_COOLDOWN / 1000})",
|
||||
keyboardOptions = KeyboardOptions(
|
||||
keyboardType = KeyboardType.Number,
|
||||
),
|
||||
isErrorValue = ::isSecondsError,
|
||||
onSubmit = {
|
||||
optionsViewModel.saveTunnelChanges(
|
||||
config.copy(pingCooldown = if (it.isBlank()) null else it.toLong() * 1000),
|
||||
)
|
||||
},
|
||||
)
|
||||
},
|
||||
),
|
||||
)
|
||||
}
|
||||
add(
|
||||
SelectionItem(
|
||||
title = {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier
|
||||
.weight(4f, false)
|
||||
.fillMaxWidth(),
|
||||
modifier = Modifier.fillMaxWidth().padding(vertical = 4.dp.scaledHeight()),
|
||||
) {
|
||||
val icon = Icons.Outlined.Security
|
||||
Icon(
|
||||
icon,
|
||||
icon.name,
|
||||
modifier = Modifier.size(iconSize),
|
||||
)
|
||||
Column(
|
||||
horizontalAlignment = Alignment.Start,
|
||||
verticalArrangement = Arrangement.spacedBy(2.dp, Alignment.CenterVertically),
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(start = 16.dp.scaledWidth())
|
||||
.padding(vertical = 6.dp.scaledHeight()),
|
||||
.weight(4f, false)
|
||||
.fillMaxWidth(),
|
||||
) {
|
||||
Text(
|
||||
stringResource(R.string.use_tunnel_on_wifi_name),
|
||||
style = MaterialTheme.typography.bodyMedium.copy(MaterialTheme.colorScheme.onSurface),
|
||||
val icon = Icons.Outlined.Security
|
||||
Icon(
|
||||
icon,
|
||||
icon.name,
|
||||
modifier = Modifier.size(iconSize),
|
||||
)
|
||||
Column(
|
||||
horizontalAlignment = Alignment.Start,
|
||||
verticalArrangement = Arrangement.spacedBy(2.dp, Alignment.CenterVertically),
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(start = 16.dp.scaledWidth())
|
||||
.padding(vertical = 6.dp.scaledHeight()),
|
||||
) {
|
||||
Text(
|
||||
stringResource(R.string.use_tunnel_on_wifi_name),
|
||||
style = MaterialTheme.typography.bodyMedium.copy(MaterialTheme.colorScheme.onSurface),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
description = {
|
||||
TrustedNetworkTextBox(
|
||||
config.tunnelNetworks,
|
||||
onDelete = { optionsViewModel.onDeleteRunSSID(it, config) },
|
||||
currentText = currentText,
|
||||
onSave = { optionsViewModel.onSaveRunSSID(it, config) },
|
||||
onValueChange = { currentText = it },
|
||||
supporting = {
|
||||
if (appUiState.settings.isWildcardsEnabled) {
|
||||
WildcardsLabel()
|
||||
}
|
||||
},
|
||||
)
|
||||
},
|
||||
),
|
||||
),
|
||||
},
|
||||
description = {
|
||||
TrustedNetworkTextBox(
|
||||
config.tunnelNetworks,
|
||||
onDelete = { optionsViewModel.onDeleteRunSSID(it, config) },
|
||||
currentText = currentText,
|
||||
onSave = { optionsViewModel.onSaveRunSSID(it, config) },
|
||||
onValueChange = { currentText = it },
|
||||
supporting = {
|
||||
if (appUiState.settings.isWildcardsEnabled) {
|
||||
WildcardsLabel()
|
||||
}
|
||||
},
|
||||
)
|
||||
},
|
||||
),
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
+8
@@ -77,4 +77,12 @@ constructor(
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
fun onToggleIsEthernetTunnel(tunnelConfig: TunnelConfig) = viewModelScope.launch {
|
||||
if (tunnelConfig.isEthernetTunnel) {
|
||||
appDataRepository.tunnels.updateEthernetTunnel(null)
|
||||
} else {
|
||||
appDataRepository.tunnels.updateEthernetTunnel(tunnelConfig)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
-2
@@ -9,11 +9,9 @@ import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.viewinterop.AndroidView
|
||||
import androidx.hilt.navigation.compose.hiltViewModel
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.google.accompanist.permissions.ExperimentalPermissionsApi
|
||||
import com.journeyapps.barcodescanner.CompoundBarcodeView
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.navigation.LocalNavController
|
||||
|
||||
@OptIn(ExperimentalPermissionsApi::class)
|
||||
@Composable
|
||||
fun ScannerScreen(viewModel: ScannerViewModel = hiltViewModel()) {
|
||||
val context = LocalContext.current
|
||||
|
||||
+1
-5
@@ -9,7 +9,6 @@ import com.zaneschepke.wireguardautotunnel.module.IoDispatcher
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.snackbar.SnackbarController
|
||||
import com.zaneschepke.wireguardautotunnel.util.NumberUtils
|
||||
import com.zaneschepke.wireguardautotunnel.util.StringValue
|
||||
import com.zaneschepke.wireguardautotunnel.util.extensions.toWgQuickString
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.CoroutineDispatcher
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
@@ -45,10 +44,7 @@ constructor(
|
||||
fun onTunnelQrResult(result: String) = viewModelScope.launch(ioDispatcher) {
|
||||
kotlin.runCatching {
|
||||
val amConfig = TunnelConfig.configFromAmQuick(result)
|
||||
val amQuick = amConfig.toAwgQuickString(true)
|
||||
val wgQuick = amConfig.toWgQuickString()
|
||||
val tunnelName = makeTunnelNameUnique(generateQrCodeDefaultName(result))
|
||||
val tunnelConfig = TunnelConfig(name = tunnelName, wgQuick = wgQuick, amQuick = amQuick)
|
||||
val tunnelConfig = TunnelConfig.tunnelConfigFromAmConfig(amConfig, makeTunnelNameUnique(generateQrCodeDefaultName(result)))
|
||||
appDataRepository.tunnels.save(tunnelConfig)
|
||||
_success.emit(true)
|
||||
}.onFailure {
|
||||
|
||||
+30
-38
@@ -5,7 +5,6 @@ import androidx.compose.foundation.focusable
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.ExperimentalLayoutApi
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
@@ -13,13 +12,13 @@ import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.outlined.ViewQuilt
|
||||
import androidx.compose.material.icons.filled.AppShortcut
|
||||
import androidx.compose.material.icons.outlined.AdminPanelSettings
|
||||
import androidx.compose.material.icons.outlined.Bolt
|
||||
import androidx.compose.material.icons.outlined.Code
|
||||
import androidx.compose.material.icons.outlined.FolderZip
|
||||
import androidx.compose.material.icons.outlined.Notifications
|
||||
import androidx.compose.material.icons.outlined.Pin
|
||||
import androidx.compose.material.icons.outlined.Restore
|
||||
import androidx.compose.material.icons.outlined.VpnKeyOff
|
||||
import androidx.compose.material.icons.outlined.VpnLock
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
@@ -30,13 +29,11 @@ import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.focus.focusRequester
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.LocalFocusManager
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.hilt.navigation.compose.hiltViewModel
|
||||
import com.google.accompanist.permissions.ExperimentalPermissionsApi
|
||||
import com.zaneschepke.wireguardautotunnel.R
|
||||
import com.zaneschepke.wireguardautotunnel.service.tunnel.TunnelState
|
||||
import com.zaneschepke.wireguardautotunnel.ui.AppUiState
|
||||
@@ -45,7 +42,6 @@ import com.zaneschepke.wireguardautotunnel.ui.Route
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.ScaledSwitch
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SelectionItem
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SurfaceSelectionGroupButton
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.navigation.LocalFocusRequester
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.navigation.LocalNavController
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.prompt.AuthorizationPrompt
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.snackbar.SnackbarController
|
||||
@@ -53,23 +49,17 @@ import com.zaneschepke.wireguardautotunnel.ui.screens.settings.components.Forwar
|
||||
import com.zaneschepke.wireguardautotunnel.ui.theme.topPadding
|
||||
import com.zaneschepke.wireguardautotunnel.util.extensions.isRunningOnTv
|
||||
import com.zaneschepke.wireguardautotunnel.util.extensions.launchNotificationSettings
|
||||
import com.zaneschepke.wireguardautotunnel.util.extensions.launchVpnSettings
|
||||
import com.zaneschepke.wireguardautotunnel.util.extensions.scaledHeight
|
||||
import com.zaneschepke.wireguardautotunnel.util.extensions.scaledWidth
|
||||
import com.zaneschepke.wireguardautotunnel.util.extensions.showToast
|
||||
import xyz.teamgravity.pin_lock_compose.PinManager
|
||||
|
||||
@OptIn(
|
||||
ExperimentalPermissionsApi::class,
|
||||
ExperimentalLayoutApi::class,
|
||||
)
|
||||
@Composable
|
||||
fun SettingsScreen(viewModel: SettingsViewModel = hiltViewModel(), appViewModel: AppViewModel, uiState: AppUiState) {
|
||||
val context = LocalContext.current
|
||||
val navController = LocalNavController.current
|
||||
val focusManager = LocalFocusManager.current
|
||||
val snackbar = SnackbarController.current
|
||||
val rootFocusRequester = LocalFocusRequester.current
|
||||
val isRunningOnTv = remember { context.isRunningOnTv() }
|
||||
|
||||
val interactionSource = remember { MutableInteractionSource() }
|
||||
@@ -135,32 +125,34 @@ fun SettingsScreen(viewModel: SettingsViewModel = hiltViewModel(), appViewModel:
|
||||
navController.navigate(Route.AutoTunnel)
|
||||
},
|
||||
trailing = {
|
||||
ForwardButton(Modifier.focusable().focusRequester(rootFocusRequester)) { navController.navigate(Route.AutoTunnel) }
|
||||
ForwardButton(Modifier.focusable()) { navController.navigate(Route.AutoTunnel) }
|
||||
},
|
||||
),
|
||||
),
|
||||
)
|
||||
SurfaceSelectionGroupButton(
|
||||
buildList {
|
||||
add(
|
||||
SelectionItem(
|
||||
Icons.Filled.AppShortcut,
|
||||
{
|
||||
ScaledSwitch(
|
||||
uiState.settings.isShortcutsEnabled,
|
||||
onClick = { appViewModel.onToggleShortcutsEnabled() },
|
||||
)
|
||||
},
|
||||
title = {
|
||||
Text(
|
||||
stringResource(R.string.enabled_app_shortcuts),
|
||||
style = MaterialTheme.typography.bodyMedium.copy(MaterialTheme.colorScheme.onSurface),
|
||||
)
|
||||
},
|
||||
onClick = { appViewModel.onToggleShortcutsEnabled() },
|
||||
),
|
||||
)
|
||||
if (!isRunningOnTv) {
|
||||
addAll(
|
||||
listOf(
|
||||
SelectionItem(
|
||||
Icons.Filled.AppShortcut,
|
||||
{
|
||||
ScaledSwitch(
|
||||
uiState.settings.isShortcutsEnabled,
|
||||
onClick = { viewModel.onToggleShortcutsEnabled() },
|
||||
)
|
||||
},
|
||||
title = {
|
||||
Text(
|
||||
stringResource(R.string.enabled_app_shortcuts),
|
||||
style = MaterialTheme.typography.bodyMedium.copy(MaterialTheme.colorScheme.onSurface),
|
||||
)
|
||||
},
|
||||
onClick = { viewModel.onToggleShortcutsEnabled() },
|
||||
),
|
||||
SelectionItem(
|
||||
Icons.Outlined.VpnLock,
|
||||
{
|
||||
@@ -173,7 +165,7 @@ fun SettingsScreen(viewModel: SettingsViewModel = hiltViewModel(), appViewModel:
|
||||
) &&
|
||||
uiState.settings.isAutoTunnelEnabled
|
||||
),
|
||||
onClick = { viewModel.onToggleAlwaysOnVPN() },
|
||||
onClick = { appViewModel.onToggleAlwaysOnVPN() },
|
||||
checked = uiState.settings.isAlwaysOnVpnEnabled,
|
||||
)
|
||||
},
|
||||
@@ -183,21 +175,21 @@ fun SettingsScreen(viewModel: SettingsViewModel = hiltViewModel(), appViewModel:
|
||||
style = MaterialTheme.typography.bodyMedium.copy(MaterialTheme.colorScheme.onSurface),
|
||||
)
|
||||
},
|
||||
onClick = { viewModel.onToggleAlwaysOnVPN() },
|
||||
onClick = { appViewModel.onToggleAlwaysOnVPN() },
|
||||
),
|
||||
SelectionItem(
|
||||
Icons.Outlined.AdminPanelSettings,
|
||||
Icons.Outlined.VpnKeyOff,
|
||||
title = {
|
||||
Text(
|
||||
stringResource(R.string.kill_switch),
|
||||
stringResource(R.string.kill_switch_options),
|
||||
style = MaterialTheme.typography.bodyMedium.copy(MaterialTheme.colorScheme.onSurface),
|
||||
)
|
||||
},
|
||||
onClick = {
|
||||
context.launchVpnSettings()
|
||||
navController.navigate(Route.KillSwitch)
|
||||
},
|
||||
trailing = {
|
||||
ForwardButton { context.launchVpnSettings() }
|
||||
ForwardButton { navController.navigate(Route.KillSwitch) }
|
||||
},
|
||||
),
|
||||
),
|
||||
@@ -209,7 +201,7 @@ fun SettingsScreen(viewModel: SettingsViewModel = hiltViewModel(), appViewModel:
|
||||
{
|
||||
ScaledSwitch(
|
||||
uiState.settings.isRestoreOnBootEnabled,
|
||||
onClick = { viewModel.onToggleRestartAtBoot() },
|
||||
onClick = { appViewModel.onToggleRestartAtBoot() },
|
||||
)
|
||||
},
|
||||
title = {
|
||||
@@ -218,7 +210,7 @@ fun SettingsScreen(viewModel: SettingsViewModel = hiltViewModel(), appViewModel:
|
||||
style = MaterialTheme.typography.bodyMedium.copy(MaterialTheme.colorScheme.onSurface),
|
||||
)
|
||||
},
|
||||
onClick = { viewModel.onToggleRestartAtBoot() },
|
||||
onClick = { appViewModel.onToggleRestartAtBoot() },
|
||||
),
|
||||
)
|
||||
},
|
||||
@@ -294,7 +286,7 @@ fun SettingsScreen(viewModel: SettingsViewModel = hiltViewModel(), appViewModel:
|
||||
trailing = {
|
||||
ScaledSwitch(
|
||||
uiState.settings.isKernelEnabled,
|
||||
onClick = { viewModel.onToggleKernelMode() },
|
||||
onClick = { appViewModel.onToggleKernelMode() },
|
||||
enabled = !(
|
||||
uiState.settings.isAutoTunnelEnabled ||
|
||||
uiState.settings.isAlwaysOnVpnEnabled ||
|
||||
@@ -303,7 +295,7 @@ fun SettingsScreen(viewModel: SettingsViewModel = hiltViewModel(), appViewModel:
|
||||
)
|
||||
},
|
||||
onClick = {
|
||||
viewModel.onToggleKernelMode()
|
||||
appViewModel.onToggleKernelMode()
|
||||
},
|
||||
),
|
||||
),
|
||||
|
||||
-95
@@ -4,118 +4,23 @@ import android.content.Context
|
||||
import androidx.core.content.FileProvider
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.wireguard.android.backend.WgQuickBackend
|
||||
import com.wireguard.android.util.RootShell
|
||||
import com.zaneschepke.wireguardautotunnel.R
|
||||
import com.zaneschepke.wireguardautotunnel.data.domain.Settings
|
||||
import com.zaneschepke.wireguardautotunnel.data.repository.AppDataRepository
|
||||
import com.zaneschepke.wireguardautotunnel.module.IoDispatcher
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.snackbar.SnackbarController
|
||||
import com.zaneschepke.wireguardautotunnel.util.FileUtils
|
||||
import com.zaneschepke.wireguardautotunnel.util.StringValue
|
||||
import com.zaneschepke.wireguardautotunnel.util.extensions.launchShareFile
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.CoroutineDispatcher
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.time.Instant
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Provider
|
||||
|
||||
@HiltViewModel
|
||||
class SettingsViewModel
|
||||
@Inject
|
||||
constructor(
|
||||
private val appDataRepository: AppDataRepository,
|
||||
private val rootShell: Provider<RootShell>,
|
||||
private val fileUtils: FileUtils,
|
||||
@IoDispatcher private val ioDispatcher: CoroutineDispatcher,
|
||||
) : ViewModel() {
|
||||
|
||||
private val settings = appDataRepository.settings.getSettingsFlow()
|
||||
.stateIn(viewModelScope, SharingStarted.Eagerly, Settings())
|
||||
|
||||
fun setLocationDisclosureShown() = viewModelScope.launch {
|
||||
appDataRepository.appState.setLocationDisclosureShown(true)
|
||||
}
|
||||
|
||||
fun onToggleAlwaysOnVPN() = viewModelScope.launch {
|
||||
with(settings.value) {
|
||||
appDataRepository.settings.save(
|
||||
copy(
|
||||
isAlwaysOnVpnEnabled = !isAlwaysOnVpnEnabled,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun onToggleShortcutsEnabled() = viewModelScope.launch {
|
||||
with(settings.value) {
|
||||
appDataRepository.settings.save(
|
||||
this.copy(
|
||||
isShortcutsEnabled = !isShortcutsEnabled,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun saveKernelMode(enabled: Boolean) = viewModelScope.launch {
|
||||
with(settings.value) {
|
||||
appDataRepository.settings.save(
|
||||
this.copy(
|
||||
isKernelEnabled = enabled,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun onToggleKernelMode() = viewModelScope.launch {
|
||||
with(settings.value) {
|
||||
if (!isKernelEnabled) {
|
||||
requestRoot().onSuccess {
|
||||
if (!isKernelSupported()) return@onSuccess SnackbarController.showMessage(StringValue.StringResource(R.string.kernel_not_supported))
|
||||
appDataRepository.settings.save(
|
||||
copy(
|
||||
isKernelEnabled = true,
|
||||
isAmneziaEnabled = false,
|
||||
),
|
||||
)
|
||||
}
|
||||
} else {
|
||||
saveKernelMode(enabled = false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun isKernelSupported(): Boolean {
|
||||
return withContext(ioDispatcher) {
|
||||
WgQuickBackend.hasKernelSupport()
|
||||
}
|
||||
}
|
||||
|
||||
fun onToggleRestartAtBoot() = viewModelScope.launch {
|
||||
with(settings.value) {
|
||||
appDataRepository.settings.save(
|
||||
copy(
|
||||
isRestoreOnBootEnabled = !isRestoreOnBootEnabled,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun requestRoot(): Result<Unit> {
|
||||
return withContext(ioDispatcher) {
|
||||
kotlin.runCatching {
|
||||
rootShell.get().start()
|
||||
SnackbarController.showMessage(StringValue.StringResource(R.string.root_accepted))
|
||||
}.onFailure {
|
||||
SnackbarController.showMessage(StringValue.StringResource(R.string.error_root_denied))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun exportAllConfigs(context: Context) = viewModelScope.launch {
|
||||
kotlin.runCatching {
|
||||
val shareFile = fileUtils.createNewShareFile("wg-export_${Instant.now().epochSecond}.zip")
|
||||
|
||||
+17
-38
@@ -2,48 +2,33 @@ package com.zaneschepke.wireguardautotunnel.ui.screens.settings.appearance.langu
|
||||
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.WindowInsets
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.navigationBars
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.windowInsetsPadding
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
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.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.zaneschepke.wireguardautotunnel.R
|
||||
import com.zaneschepke.wireguardautotunnel.data.datastore.LocaleStorage
|
||||
import com.zaneschepke.wireguardautotunnel.ui.Route
|
||||
import com.zaneschepke.wireguardautotunnel.ui.AppUiState
|
||||
import com.zaneschepke.wireguardautotunnel.ui.AppViewModel
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.SelectedLabel
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.SelectionItemButton
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.navigation.LocalNavController
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.navigation.TopNavBar
|
||||
import com.zaneschepke.wireguardautotunnel.util.LocaleUtil
|
||||
import com.zaneschepke.wireguardautotunnel.util.extensions.navigateAndForget
|
||||
import com.zaneschepke.wireguardautotunnel.util.extensions.scaledHeight
|
||||
import com.zaneschepke.wireguardautotunnel.util.extensions.scaledWidth
|
||||
import timber.log.Timber
|
||||
import java.text.Collator
|
||||
import java.util.Locale
|
||||
|
||||
@Composable
|
||||
fun LanguageScreen(localeStorage: LocaleStorage) {
|
||||
val navController = LocalNavController.current
|
||||
|
||||
val context = LocalContext.current
|
||||
|
||||
fun LanguageScreen(appUiState: AppUiState, appViewModel: AppViewModel) {
|
||||
val collator = Collator.getInstance(Locale.getDefault())
|
||||
|
||||
val currentLocale = remember { mutableStateOf(LocaleUtil.OPTION_PHONE_LANGUAGE) }
|
||||
|
||||
val locales = LocaleUtil.supportedLocales.map {
|
||||
val tag = it.replace("_", "-")
|
||||
Locale.forLanguageTag(tag)
|
||||
@@ -54,39 +39,28 @@ fun LanguageScreen(localeStorage: LocaleStorage) {
|
||||
locales.sortedWith(compareBy(collator) { it.getDisplayName(it) }).toList()
|
||||
}
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
currentLocale.value = localeStorage.getPreferredLocale()
|
||||
}
|
||||
|
||||
fun onChangeLocale(locale: String) {
|
||||
Timber.d("Setting preferred locale: $locale")
|
||||
localeStorage.setPreferredLocale(locale)
|
||||
LocaleUtil.applyLocalizedContext(context, locale)
|
||||
navController.navigateAndForget(Route.Main)
|
||||
}
|
||||
|
||||
Scaffold(
|
||||
topBar = {
|
||||
TopNavBar(stringResource(R.string.language))
|
||||
},
|
||||
) {
|
||||
) { padding ->
|
||||
LazyColumn(
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
verticalArrangement = Arrangement.Top,
|
||||
modifier =
|
||||
Modifier
|
||||
.fillMaxSize().padding(it)
|
||||
.padding(horizontal = 24.dp.scaledWidth()).windowInsetsPadding(WindowInsets.navigationBars),
|
||||
.fillMaxSize().padding(padding)
|
||||
.padding(horizontal = 24.dp.scaledWidth()),
|
||||
) {
|
||||
item {
|
||||
Box(modifier = Modifier.padding(top = 24.dp.scaledHeight())) {
|
||||
SelectionItemButton(
|
||||
buttonText = stringResource(R.string.automatic),
|
||||
onClick = {
|
||||
onChangeLocale(LocaleUtil.OPTION_PHONE_LANGUAGE)
|
||||
appViewModel.onLocaleChange(LocaleUtil.OPTION_PHONE_LANGUAGE)
|
||||
},
|
||||
trailing = {
|
||||
if (currentLocale.value == LocaleUtil.OPTION_PHONE_LANGUAGE) {
|
||||
if (appUiState.generalState.locale == LocaleUtil.OPTION_PHONE_LANGUAGE) {
|
||||
SelectedLabel()
|
||||
}
|
||||
},
|
||||
@@ -96,13 +70,18 @@ fun LanguageScreen(localeStorage: LocaleStorage) {
|
||||
}
|
||||
items(sortedLocales, key = { it }) { locale ->
|
||||
SelectionItemButton(
|
||||
buttonText = locale.getDisplayLanguage(locale).capitalize(locale) +
|
||||
if (locale.toLanguageTag().contains("-")) " (${locale.getDisplayCountry(locale).capitalize(locale)})" else "",
|
||||
buttonText = locale.getDisplayLanguage(locale).replaceFirstChar { if (it.isLowerCase()) it.titlecase(locale) else it.toString() } +
|
||||
if (locale.toLanguageTag().contains("-")) {
|
||||
" (${locale.getDisplayCountry(locale)
|
||||
.replaceFirstChar { if (it.isLowerCase()) it.titlecase(locale) else it.toString() }})"
|
||||
} else {
|
||||
""
|
||||
},
|
||||
onClick = {
|
||||
onChangeLocale(locale.toLanguageTag())
|
||||
appViewModel.onLocaleChange(locale.toLanguageTag())
|
||||
},
|
||||
trailing = {
|
||||
if (locale.toLanguageTag() == currentLocale.value) {
|
||||
if (locale.toLanguageTag() == appUiState.generalState.locale) {
|
||||
SelectedLabel()
|
||||
}
|
||||
},
|
||||
|
||||
+46
-18
@@ -6,12 +6,14 @@ import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.ExperimentalLayoutApi
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.WindowInsets
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.outlined.AirplanemodeActive
|
||||
import androidx.compose.material.icons.outlined.Code
|
||||
import androidx.compose.material.icons.outlined.Filter1
|
||||
import androidx.compose.material.icons.outlined.NetworkPing
|
||||
@@ -71,16 +73,18 @@ fun AutoTunnelScreen(uiState: AppUiState, viewModel: AutoTunnelViewModel = hiltV
|
||||
isBackgroundLocationGranted = fineLocationState.status.isGranted
|
||||
}
|
||||
|
||||
fun onAutoTunnelWifiChecked() {
|
||||
if (uiState.settings.isTunnelOnWifiEnabled) viewModel.onToggleTunnelOnWifi().also { return }
|
||||
when (false) {
|
||||
isBackgroundLocationGranted -> showLocationDialog = true
|
||||
fineLocationState.status.isGranted -> showLocationDialog = true
|
||||
context.isLocationServicesEnabled() ->
|
||||
showLocationServicesAlertDialog = true
|
||||
else -> {
|
||||
viewModel.onToggleTunnelOnWifi()
|
||||
fun isWifiNameReadable(): Boolean {
|
||||
return when {
|
||||
!isBackgroundLocationGranted ||
|
||||
!fineLocationState.status.isGranted -> {
|
||||
showLocationDialog = true
|
||||
false
|
||||
}
|
||||
!context.isLocationServicesEnabled() -> {
|
||||
showLocationServicesAlertDialog = true
|
||||
false
|
||||
}
|
||||
else -> true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -113,18 +117,18 @@ fun AutoTunnelScreen(uiState: AppUiState, viewModel: AutoTunnelViewModel = hiltV
|
||||
)
|
||||
|
||||
Scaffold(
|
||||
contentWindowInsets = WindowInsets(0.dp),
|
||||
topBar = {
|
||||
TopNavBar(stringResource(R.string.auto_tunneling))
|
||||
},
|
||||
) {
|
||||
) { padding ->
|
||||
Column(
|
||||
horizontalAlignment = Alignment.Start,
|
||||
verticalArrangement = Arrangement.spacedBy(24.dp.scaledHeight(), Alignment.Top),
|
||||
modifier =
|
||||
Modifier
|
||||
.fillMaxSize()
|
||||
.padding(it)
|
||||
.padding(padding)
|
||||
.verticalScroll(rememberScrollState())
|
||||
.padding(top = 24.dp.scaledHeight())
|
||||
.padding(horizontal = 24.dp.scaledWidth()),
|
||||
) {
|
||||
@@ -147,14 +151,12 @@ fun AutoTunnelScreen(uiState: AppUiState, viewModel: AutoTunnelViewModel = hiltV
|
||||
enabled = !uiState.settings.isAlwaysOnVpnEnabled,
|
||||
checked = uiState.settings.isTunnelOnWifiEnabled,
|
||||
onClick = {
|
||||
if (uiState.settings.isWifiNameByShellEnabled) viewModel.onToggleTunnelOnWifi().also { return@ScaledSwitch }
|
||||
onAutoTunnelWifiChecked()
|
||||
viewModel.onToggleTunnelOnWifi()
|
||||
},
|
||||
)
|
||||
},
|
||||
onClick = {
|
||||
if (uiState.settings.isWifiNameByShellEnabled) viewModel.onToggleTunnelOnWifi().also { return@SelectionItem }
|
||||
onAutoTunnelWifiChecked()
|
||||
viewModel.onToggleTunnelOnWifi()
|
||||
},
|
||||
),
|
||||
SelectionItem(
|
||||
@@ -250,7 +252,9 @@ fun AutoTunnelScreen(uiState: AppUiState, viewModel: AutoTunnelViewModel = hiltV
|
||||
uiState.settings.trustedNetworkSSIDs,
|
||||
onDelete = viewModel::onDeleteTrustedSSID,
|
||||
currentText = currentText,
|
||||
onSave = viewModel::onSaveTrustedSSID,
|
||||
onSave = { ssid ->
|
||||
if (uiState.settings.isWifiNameByShellEnabled || isWifiNameReadable()) viewModel.onSaveTrustedSSID(ssid)
|
||||
},
|
||||
onValueChange = { currentText = it },
|
||||
supporting = {
|
||||
if (uiState.settings.isWildcardsEnabled) {
|
||||
@@ -323,6 +327,30 @@ fun AutoTunnelScreen(uiState: AppUiState, viewModel: AutoTunnelViewModel = hiltV
|
||||
viewModel.onToggleRestartOnPing()
|
||||
},
|
||||
),
|
||||
SelectionItem(
|
||||
Icons.Outlined.AirplanemodeActive,
|
||||
title = {
|
||||
Text(
|
||||
stringResource(R.string.stop_on_no_internet),
|
||||
style = MaterialTheme.typography.bodyMedium.copy(MaterialTheme.colorScheme.onSurface),
|
||||
)
|
||||
},
|
||||
description = {
|
||||
Text(
|
||||
stringResource(R.string.stop_on_internet_loss),
|
||||
style = MaterialTheme.typography.bodySmall.copy(MaterialTheme.colorScheme.outline),
|
||||
)
|
||||
},
|
||||
trailing = {
|
||||
ScaledSwitch(
|
||||
checked = uiState.settings.isStopOnNoInternetEnabled,
|
||||
onClick = { viewModel.onToggleStopOnNoInternet() },
|
||||
)
|
||||
},
|
||||
onClick = {
|
||||
viewModel.onToggleStopOnNoInternet()
|
||||
},
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
+10
-1
@@ -6,6 +6,7 @@ import com.wireguard.android.util.RootShell
|
||||
import com.zaneschepke.wireguardautotunnel.R
|
||||
import com.zaneschepke.wireguardautotunnel.data.domain.Settings
|
||||
import com.zaneschepke.wireguardautotunnel.data.repository.AppDataRepository
|
||||
import com.zaneschepke.wireguardautotunnel.module.AppShell
|
||||
import com.zaneschepke.wireguardautotunnel.module.IoDispatcher
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.snackbar.SnackbarController
|
||||
import com.zaneschepke.wireguardautotunnel.util.StringValue
|
||||
@@ -23,7 +24,7 @@ class AutoTunnelViewModel
|
||||
@Inject
|
||||
constructor(
|
||||
private val appDataRepository: AppDataRepository,
|
||||
private val rootShell: Provider<RootShell>,
|
||||
@AppShell private val rootShell: Provider<RootShell>,
|
||||
@IoDispatcher private val ioDispatcher: CoroutineDispatcher,
|
||||
) : ViewModel() {
|
||||
|
||||
@@ -127,4 +128,12 @@ constructor(
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun onToggleStopOnNoInternet() = viewModelScope.launch {
|
||||
with(settings.value) {
|
||||
appDataRepository.settings.save(
|
||||
copy(isStopOnNoInternetEnabled = !isStopOnNoInternetEnabled),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+3
@@ -1,5 +1,6 @@
|
||||
package com.zaneschepke.wireguardautotunnel.ui.screens.settings.autotunnel.components
|
||||
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.ExperimentalLayoutApi
|
||||
@@ -16,6 +17,7 @@ import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
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
|
||||
@@ -66,6 +68,7 @@ fun TrustedNetworkTextBox(
|
||||
textStyle = MaterialTheme.typography.bodySmall,
|
||||
value = currentText,
|
||||
onValueChange = onValueChange,
|
||||
interactionSource = remember { MutableInteractionSource() },
|
||||
label = { Text(stringResource(R.string.add_wifi_name)) },
|
||||
containerColor = MaterialTheme.colorScheme.surface,
|
||||
supportingText = supporting,
|
||||
|
||||
+142
@@ -0,0 +1,142 @@
|
||||
package com.zaneschepke.wireguardautotunnel.ui.screens.settings.killswitch
|
||||
|
||||
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.material.icons.Icons
|
||||
import androidx.compose.material.icons.outlined.AdminPanelSettings
|
||||
import androidx.compose.material.icons.outlined.Lan
|
||||
import androidx.compose.material.icons.outlined.VpnKey
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.zaneschepke.wireguardautotunnel.R
|
||||
import com.zaneschepke.wireguardautotunnel.ui.AppUiState
|
||||
import com.zaneschepke.wireguardautotunnel.ui.AppViewModel
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.ScaledSwitch
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SelectionItem
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SurfaceSelectionGroupButton
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.navigation.TopNavBar
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.permission.vpn.withVpnPermission
|
||||
import com.zaneschepke.wireguardautotunnel.ui.screens.settings.components.ForwardButton
|
||||
import com.zaneschepke.wireguardautotunnel.util.extensions.launchVpnSettings
|
||||
import com.zaneschepke.wireguardautotunnel.util.extensions.scaledHeight
|
||||
import com.zaneschepke.wireguardautotunnel.util.extensions.scaledWidth
|
||||
|
||||
@Composable
|
||||
fun KillSwitchScreen(uiState: AppUiState, appViewModel: AppViewModel) {
|
||||
val context = LocalContext.current
|
||||
|
||||
val toggleVpnSwitch = withVpnPermission<Boolean> { appViewModel.onToggleVpnKillSwitch(it) }
|
||||
|
||||
fun toggleVpnKillSwitch() {
|
||||
with(uiState.settings) {
|
||||
if (isVpnKillSwitchEnabled) {
|
||||
appViewModel.onToggleVpnKillSwitch(false)
|
||||
} else {
|
||||
toggleVpnSwitch.invoke(true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun toggleLanOnKillSwitch() {
|
||||
with(uiState.settings) {
|
||||
appViewModel.onToggleLanOnKillSwitch(!isLanOnKillSwitchEnabled)
|
||||
}
|
||||
}
|
||||
|
||||
Scaffold(
|
||||
topBar = {
|
||||
TopNavBar(stringResource(R.string.kill_switch))
|
||||
},
|
||||
) { padding ->
|
||||
Column(
|
||||
horizontalAlignment = Alignment.Start,
|
||||
verticalArrangement = Arrangement.spacedBy(24.dp.scaledHeight(), Alignment.Top),
|
||||
modifier =
|
||||
Modifier
|
||||
.fillMaxSize().padding(padding)
|
||||
.padding(top = 24.dp.scaledHeight())
|
||||
.padding(horizontal = 24.dp.scaledWidth()),
|
||||
) {
|
||||
SurfaceSelectionGroupButton(
|
||||
listOf(
|
||||
SelectionItem(
|
||||
Icons.Outlined.AdminPanelSettings,
|
||||
title = {
|
||||
Text(
|
||||
stringResource(R.string.native_kill_switch),
|
||||
style = MaterialTheme.typography.bodyMedium.copy(MaterialTheme.colorScheme.onSurface),
|
||||
)
|
||||
},
|
||||
onClick = { context.launchVpnSettings() },
|
||||
trailing = {
|
||||
ForwardButton { context.launchVpnSettings() }
|
||||
},
|
||||
),
|
||||
),
|
||||
)
|
||||
SurfaceSelectionGroupButton(
|
||||
buildList {
|
||||
add(
|
||||
SelectionItem(
|
||||
Icons.Outlined.VpnKey,
|
||||
title = {
|
||||
Text(
|
||||
stringResource(R.string.vpn_kill_switch),
|
||||
style = MaterialTheme.typography.bodyMedium.copy(MaterialTheme.colorScheme.onSurface),
|
||||
)
|
||||
},
|
||||
onClick = {
|
||||
toggleVpnKillSwitch()
|
||||
},
|
||||
trailing = {
|
||||
ScaledSwitch(
|
||||
uiState.settings.isVpnKillSwitchEnabled,
|
||||
onClick = {
|
||||
toggleVpnKillSwitch()
|
||||
},
|
||||
)
|
||||
},
|
||||
),
|
||||
)
|
||||
if (uiState.settings.isVpnKillSwitchEnabled) {
|
||||
add(
|
||||
SelectionItem(
|
||||
Icons.Outlined.Lan,
|
||||
title = {
|
||||
Text(
|
||||
stringResource(R.string.allow_lan_traffic),
|
||||
style = MaterialTheme.typography.bodyMedium.copy(MaterialTheme.colorScheme.onSurface),
|
||||
)
|
||||
},
|
||||
onClick = { toggleLanOnKillSwitch() },
|
||||
description = {
|
||||
Text(
|
||||
stringResource(R.string.bypass_lan_for_kill_switch),
|
||||
style = MaterialTheme.typography.bodySmall.copy(MaterialTheme.colorScheme.outline),
|
||||
)
|
||||
},
|
||||
trailing = {
|
||||
ScaledSwitch(
|
||||
uiState.settings.isLanOnKillSwitchEnabled,
|
||||
onClick = {
|
||||
toggleLanOnKillSwitch()
|
||||
},
|
||||
)
|
||||
},
|
||||
),
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
+194
-99
@@ -4,14 +4,22 @@ import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.AttachMoney
|
||||
import androidx.compose.material.icons.filled.Book
|
||||
import androidx.compose.material.icons.filled.LineStyle
|
||||
import androidx.compose.material.icons.filled.Mail
|
||||
import androidx.compose.material.icons.filled.Policy
|
||||
import androidx.compose.material.icons.filled.ViewTimeline
|
||||
import androidx.compose.material.icons.outlined.ViewHeadline
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
@@ -19,25 +27,44 @@ import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.res.vectorResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.zaneschepke.wireguardautotunnel.BuildConfig
|
||||
import com.zaneschepke.wireguardautotunnel.R
|
||||
import com.zaneschepke.wireguardautotunnel.ui.AppUiState
|
||||
import com.zaneschepke.wireguardautotunnel.ui.AppViewModel
|
||||
import com.zaneschepke.wireguardautotunnel.ui.Route
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.ScaledSwitch
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SelectionItem
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SurfaceSelectionGroupButton
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.dialog.InfoDialog
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.label.GroupLabel
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.label.VersionLabel
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.navigation.LocalNavController
|
||||
import com.zaneschepke.wireguardautotunnel.ui.screens.settings.components.ForwardButton
|
||||
import com.zaneschepke.wireguardautotunnel.ui.theme.topPadding
|
||||
import com.zaneschepke.wireguardautotunnel.util.extensions.isRunningOnTv
|
||||
import com.zaneschepke.wireguardautotunnel.util.extensions.launchSupportEmail
|
||||
import com.zaneschepke.wireguardautotunnel.util.extensions.openWebUrl
|
||||
import com.zaneschepke.wireguardautotunnel.util.extensions.scaledHeight
|
||||
import com.zaneschepke.wireguardautotunnel.util.extensions.scaledWidth
|
||||
|
||||
@Composable
|
||||
fun SupportScreen() {
|
||||
fun SupportScreen(appUiState: AppUiState, appViewModel: AppViewModel) {
|
||||
val context = LocalContext.current
|
||||
val navController = LocalNavController.current
|
||||
|
||||
var showDialog by remember { mutableStateOf(false) }
|
||||
|
||||
if (showDialog) {
|
||||
InfoDialog(onAttest = {
|
||||
showDialog = false
|
||||
appViewModel.onToggleLocalLogging()
|
||||
}, onDismiss = {
|
||||
showDialog = false
|
||||
}, title = {
|
||||
Text(stringResource(R.string.configuration_change))
|
||||
}, body = { Text(stringResource(R.string.requires_app_relaunch)) }, confirmText = { Text(stringResource(R.string.yes)) })
|
||||
}
|
||||
|
||||
Column(
|
||||
horizontalAlignment = Alignment.Start,
|
||||
verticalArrangement = Arrangement.spacedBy(24.dp.scaledHeight(), Alignment.Top),
|
||||
@@ -45,110 +72,178 @@ fun SupportScreen() {
|
||||
Modifier
|
||||
.fillMaxSize()
|
||||
.padding(top = topPadding)
|
||||
.verticalScroll(rememberScrollState())
|
||||
.padding(horizontal = 24.dp.scaledWidth()),
|
||||
) {
|
||||
GroupLabel(stringResource(R.string.thank_you))
|
||||
SurfaceSelectionGroupButton(
|
||||
listOf(
|
||||
SelectionItem(
|
||||
Icons.Filled.Book,
|
||||
title = {
|
||||
Text(
|
||||
stringResource(R.string.docs_description),
|
||||
style = MaterialTheme.typography.bodyMedium.copy(MaterialTheme.colorScheme.onSurface),
|
||||
buildList {
|
||||
add(
|
||||
SelectionItem(
|
||||
Icons.Filled.Book,
|
||||
title = {
|
||||
Text(
|
||||
stringResource(R.string.docs_description),
|
||||
style = MaterialTheme.typography.bodyMedium.copy(MaterialTheme.colorScheme.onSurface),
|
||||
)
|
||||
},
|
||||
trailing = {
|
||||
ForwardButton { context.openWebUrl(context.getString(R.string.docs_url)) }
|
||||
},
|
||||
onClick = {
|
||||
context.openWebUrl(context.getString(R.string.docs_url))
|
||||
},
|
||||
),
|
||||
)
|
||||
if (!context.isRunningOnTv()) {
|
||||
add(
|
||||
SelectionItem(
|
||||
Icons.Outlined.ViewHeadline,
|
||||
title = {
|
||||
Text(
|
||||
stringResource(R.string.local_logging),
|
||||
style = MaterialTheme.typography.bodyMedium.copy(MaterialTheme.colorScheme.onSurface),
|
||||
)
|
||||
},
|
||||
description = {
|
||||
Text(
|
||||
stringResource(R.string.enable_local_logging),
|
||||
style = MaterialTheme.typography.bodyMedium.copy(MaterialTheme.colorScheme.outline),
|
||||
)
|
||||
},
|
||||
trailing = {
|
||||
ScaledSwitch(
|
||||
appUiState.generalState.isLocalLogsEnabled,
|
||||
onClick = {
|
||||
showDialog = true
|
||||
},
|
||||
)
|
||||
},
|
||||
onClick = {
|
||||
showDialog = true
|
||||
},
|
||||
),
|
||||
)
|
||||
if (appUiState.generalState.isLocalLogsEnabled) {
|
||||
add(
|
||||
SelectionItem(
|
||||
Icons.Filled.ViewTimeline,
|
||||
title = {
|
||||
Text(
|
||||
stringResource(R.string.read_logs),
|
||||
style = MaterialTheme.typography.bodyMedium.copy(MaterialTheme.colorScheme.onSurface),
|
||||
)
|
||||
},
|
||||
trailing = {
|
||||
ForwardButton {
|
||||
navController.navigate(Route.Logs)
|
||||
}
|
||||
},
|
||||
onClick = {
|
||||
navController.navigate(Route.Logs)
|
||||
},
|
||||
),
|
||||
)
|
||||
},
|
||||
trailing = {
|
||||
ForwardButton { context.openWebUrl(context.getString(R.string.docs_url)) }
|
||||
},
|
||||
onClick = {
|
||||
context.openWebUrl(context.getString(R.string.docs_url))
|
||||
},
|
||||
),
|
||||
SelectionItem(
|
||||
Icons.Filled.LineStyle,
|
||||
title = {
|
||||
Text(
|
||||
stringResource(R.string.read_logs),
|
||||
style = MaterialTheme.typography.bodyMedium.copy(MaterialTheme.colorScheme.onSurface),
|
||||
)
|
||||
},
|
||||
trailing = {
|
||||
ForwardButton {
|
||||
navController.navigate(Route.Logs)
|
||||
}
|
||||
},
|
||||
onClick = {
|
||||
navController.navigate(Route.Logs)
|
||||
},
|
||||
),
|
||||
SelectionItem(
|
||||
Icons.Filled.Policy,
|
||||
title = {
|
||||
Text(
|
||||
stringResource(R.string.privacy_policy),
|
||||
style = MaterialTheme.typography.bodyMedium.copy(MaterialTheme.colorScheme.onSurface),
|
||||
)
|
||||
},
|
||||
trailing = {
|
||||
ForwardButton { context.openWebUrl(context.getString(R.string.privacy_policy_url)) }
|
||||
},
|
||||
onClick = {
|
||||
context.openWebUrl(context.getString(R.string.privacy_policy_url))
|
||||
},
|
||||
),
|
||||
|
||||
),
|
||||
}
|
||||
}
|
||||
add(
|
||||
SelectionItem(
|
||||
Icons.Filled.Policy,
|
||||
title = {
|
||||
Text(
|
||||
stringResource(R.string.privacy_policy),
|
||||
style = MaterialTheme.typography.bodyMedium.copy(MaterialTheme.colorScheme.onSurface),
|
||||
)
|
||||
},
|
||||
trailing = {
|
||||
ForwardButton { context.openWebUrl(context.getString(R.string.privacy_policy_url)) }
|
||||
},
|
||||
onClick = {
|
||||
context.openWebUrl(context.getString(R.string.privacy_policy_url))
|
||||
},
|
||||
),
|
||||
)
|
||||
},
|
||||
)
|
||||
SurfaceSelectionGroupButton(
|
||||
listOf(
|
||||
SelectionItem(
|
||||
ImageVector.vectorResource(R.drawable.telegram),
|
||||
title = {
|
||||
Text(
|
||||
stringResource(R.string.chat_description),
|
||||
style = MaterialTheme.typography.bodyMedium.copy(MaterialTheme.colorScheme.onSurface),
|
||||
)
|
||||
},
|
||||
trailing = {
|
||||
ForwardButton {
|
||||
context.openWebUrl(context.getString(R.string.telegram_url))
|
||||
}
|
||||
},
|
||||
onClick = {
|
||||
context.openWebUrl(context.getString(R.string.telegram_url))
|
||||
},
|
||||
),
|
||||
SelectionItem(
|
||||
ImageVector.vectorResource(R.drawable.github),
|
||||
title = { Text(stringResource(R.string.open_issue), style = MaterialTheme.typography.bodyMedium.copy(MaterialTheme.colorScheme.onSurface)) },
|
||||
trailing = {
|
||||
ForwardButton {
|
||||
context.openWebUrl(context.getString(R.string.github_url))
|
||||
}
|
||||
},
|
||||
onClick = {
|
||||
context.openWebUrl(context.getString(R.string.github_url))
|
||||
},
|
||||
),
|
||||
SelectionItem(
|
||||
Icons.Filled.Mail,
|
||||
title = {
|
||||
Text(
|
||||
stringResource(R.string.email_description),
|
||||
style = MaterialTheme.typography.bodyMedium.copy(MaterialTheme.colorScheme.onSurface),
|
||||
)
|
||||
},
|
||||
trailing = {
|
||||
ForwardButton {
|
||||
context.launchSupportEmail()
|
||||
}
|
||||
},
|
||||
onClick = {
|
||||
context.launchSupportEmail()
|
||||
},
|
||||
),
|
||||
),
|
||||
buildList {
|
||||
addAll(
|
||||
listOf(
|
||||
SelectionItem(
|
||||
ImageVector.vectorResource(R.drawable.telegram),
|
||||
title = {
|
||||
Text(
|
||||
stringResource(R.string.chat_description),
|
||||
style = MaterialTheme.typography.bodyMedium.copy(MaterialTheme.colorScheme.onSurface),
|
||||
)
|
||||
},
|
||||
trailing = {
|
||||
ForwardButton {
|
||||
context.openWebUrl(context.getString(R.string.telegram_url))
|
||||
}
|
||||
},
|
||||
onClick = {
|
||||
context.openWebUrl(context.getString(R.string.telegram_url))
|
||||
},
|
||||
),
|
||||
SelectionItem(
|
||||
ImageVector.vectorResource(R.drawable.github),
|
||||
title = {
|
||||
Text(
|
||||
stringResource(R.string.open_issue),
|
||||
style = MaterialTheme.typography.bodyMedium.copy(MaterialTheme.colorScheme.onSurface),
|
||||
)
|
||||
},
|
||||
trailing = {
|
||||
ForwardButton {
|
||||
context.openWebUrl(context.getString(R.string.github_url))
|
||||
}
|
||||
},
|
||||
onClick = {
|
||||
context.openWebUrl(context.getString(R.string.github_url))
|
||||
},
|
||||
),
|
||||
SelectionItem(
|
||||
Icons.Filled.Mail,
|
||||
title = {
|
||||
Text(
|
||||
stringResource(R.string.email_description),
|
||||
style = MaterialTheme.typography.bodyMedium.copy(MaterialTheme.colorScheme.onSurface),
|
||||
)
|
||||
},
|
||||
trailing = {
|
||||
ForwardButton {
|
||||
context.launchSupportEmail()
|
||||
}
|
||||
},
|
||||
onClick = {
|
||||
context.launchSupportEmail()
|
||||
},
|
||||
),
|
||||
),
|
||||
)
|
||||
if (BuildConfig.FLAVOR == "fdroid") {
|
||||
add(
|
||||
SelectionItem(
|
||||
Icons.Filled.AttachMoney,
|
||||
title = {
|
||||
Text(
|
||||
stringResource(R.string.donate),
|
||||
style = MaterialTheme.typography.bodyMedium.copy(MaterialTheme.colorScheme.onSurface),
|
||||
)
|
||||
},
|
||||
trailing = {
|
||||
ForwardButton {
|
||||
context.openWebUrl(context.getString(R.string.donate_url))
|
||||
}
|
||||
},
|
||||
onClick = {
|
||||
context.openWebUrl(context.getString(R.string.donate_url))
|
||||
},
|
||||
),
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
VersionLabel()
|
||||
}
|
||||
|
||||
@@ -21,7 +21,6 @@ object Constants {
|
||||
const val SYSTEM_EXEMPT_SERVICE_TYPE_ID = 1024
|
||||
|
||||
const val SUBSCRIPTION_TIMEOUT = 5_000L
|
||||
const val FOCUS_REQUEST_DELAY = 500L
|
||||
|
||||
const val TRANSITION_ANIMATION_TIME = 200
|
||||
|
||||
|
||||
@@ -1,100 +1,20 @@
|
||||
package com.zaneschepke.wireguardautotunnel.util
|
||||
|
||||
import android.content.Context
|
||||
import android.content.res.Configuration
|
||||
import android.content.res.Resources
|
||||
import android.os.Build
|
||||
import android.os.LocaleList
|
||||
import androidx.core.os.ConfigurationCompat
|
||||
import androidx.appcompat.app.AppCompatDelegate
|
||||
import androidx.core.os.LocaleListCompat
|
||||
import com.zaneschepke.wireguardautotunnel.BuildConfig
|
||||
import java.util.Locale
|
||||
|
||||
object LocaleUtil {
|
||||
private const val DEFAULT_LANG = "en"
|
||||
val supportedLocales: Array<String> = BuildConfig.LANGUAGES
|
||||
const val OPTION_PHONE_LANGUAGE = "sys_def"
|
||||
|
||||
/**
|
||||
* returns the locale to use depending on the preference value
|
||||
* when preference value = "sys_def" returns the locale of current system
|
||||
* else it returns the locale code e.g. "en", "bn" etc.
|
||||
*/
|
||||
fun getLocaleFromPrefCode(prefCode: String): Locale {
|
||||
val localeCode = if (prefCode != OPTION_PHONE_LANGUAGE) {
|
||||
prefCode
|
||||
} else {
|
||||
val systemLang = ConfigurationCompat.getLocales(Resources.getSystem().configuration).get(0)?.language ?: DEFAULT_LANG
|
||||
if (systemLang in supportedLocales) {
|
||||
systemLang
|
||||
} else {
|
||||
DEFAULT_LANG
|
||||
}
|
||||
}
|
||||
return Locale.forLanguageTag(localeCode)
|
||||
fun changeLocale(locale: String) {
|
||||
if (locale == OPTION_PHONE_LANGUAGE) return resetToSystemLanguage()
|
||||
val appLocale: LocaleListCompat = LocaleListCompat.forLanguageTags(locale)
|
||||
AppCompatDelegate.setApplicationLocales(appLocale)
|
||||
}
|
||||
|
||||
fun getLocalizedConfiguration(prefLocaleCode: String): Configuration {
|
||||
val locale = getLocaleFromPrefCode(prefLocaleCode)
|
||||
return getLocalizedConfiguration(locale)
|
||||
}
|
||||
|
||||
private fun getLocalizedConfiguration(locale: Locale): Configuration {
|
||||
val config = Configuration()
|
||||
return config.apply {
|
||||
config.setLayoutDirection(locale)
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
config.setLocale(locale)
|
||||
val localeList = LocaleList(locale)
|
||||
LocaleList.setDefault(localeList)
|
||||
config.setLocales(localeList)
|
||||
} else {
|
||||
config.setLocale(locale)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun getLocalizedContext(baseContext: Context, prefLocaleCode: String?): Context {
|
||||
if (prefLocaleCode == null) return baseContext
|
||||
val currentLocale = getLocaleFromPrefCode(prefLocaleCode)
|
||||
val baseLocale = getLocaleFromConfiguration(baseContext.resources.configuration)
|
||||
Locale.setDefault(currentLocale)
|
||||
return if (!baseLocale.toString().equals(currentLocale.toString(), ignoreCase = true)) {
|
||||
val config = getLocalizedConfiguration(currentLocale)
|
||||
baseContext.createConfigurationContext(config)
|
||||
baseContext
|
||||
} else {
|
||||
baseContext
|
||||
}
|
||||
}
|
||||
|
||||
fun applyLocalizedContext(baseContext: Context, prefLocaleCode: String) {
|
||||
val currentLocale = getLocaleFromPrefCode(prefLocaleCode)
|
||||
val baseLocale = getLocaleFromConfiguration(baseContext.resources.configuration)
|
||||
Locale.setDefault(currentLocale)
|
||||
if (!baseLocale.toString().equals(currentLocale.toString(), ignoreCase = true)) {
|
||||
val config = getLocalizedConfiguration(currentLocale)
|
||||
baseContext.resources.updateConfiguration(config, baseContext.resources.displayMetrics)
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("DEPRECATION")
|
||||
private fun getLocaleFromConfiguration(configuration: Configuration): Locale {
|
||||
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
configuration.locales.get(0)
|
||||
} else {
|
||||
configuration.locale
|
||||
}
|
||||
}
|
||||
|
||||
fun getLocalizedResources(resources: Resources, prefLocaleCode: String): Resources {
|
||||
val locale = getLocaleFromPrefCode(prefLocaleCode)
|
||||
val config = resources.configuration
|
||||
@Suppress("DEPRECATION")
|
||||
config.locale = locale
|
||||
config.setLayoutDirection(locale)
|
||||
|
||||
@Suppress("DEPRECATION")
|
||||
resources.updateConfiguration(config, resources.displayMetrics)
|
||||
return resources
|
||||
private fun resetToSystemLanguage() {
|
||||
AppCompatDelegate.setApplicationLocales(LocaleListCompat.getEmptyLocaleList())
|
||||
}
|
||||
}
|
||||
|
||||
+5
-2
@@ -22,6 +22,7 @@ import com.zaneschepke.wireguardautotunnel.util.Constants
|
||||
private const val BASELINE_HEIGHT = 2201
|
||||
private const val BASELINE_WIDTH = 1080
|
||||
private const val BASELINE_DENSITY = 2.625
|
||||
private const val ANDROID_TV_SIZE_MULTIPLIER = 1.5f
|
||||
|
||||
fun Context.openWebUrl(url: String): Result<Unit> {
|
||||
return kotlin.runCatching {
|
||||
@@ -47,7 +48,8 @@ val Context.actionBarSize
|
||||
fun Context.resizeHeight(dp: Dp): Dp {
|
||||
val displayMetrics = resources.displayMetrics
|
||||
val density = displayMetrics.density
|
||||
val height = displayMetrics.heightPixels - this.actionBarSize
|
||||
val height = (displayMetrics.heightPixels - this.actionBarSize) *
|
||||
(if (isRunningOnTv()) ANDROID_TV_SIZE_MULTIPLIER else 1f)
|
||||
val resizeHeightPercentage =
|
||||
(height.toFloat() / BASELINE_HEIGHT) * (BASELINE_DENSITY.toFloat() / density)
|
||||
return dp * resizeHeightPercentage
|
||||
@@ -56,7 +58,8 @@ fun Context.resizeHeight(dp: Dp): Dp {
|
||||
fun Context.resizeHeight(textUnit: TextUnit): TextUnit {
|
||||
val displayMetrics = resources.displayMetrics
|
||||
val density = displayMetrics.density
|
||||
val height = displayMetrics.heightPixels - actionBarSize
|
||||
val height = (displayMetrics.heightPixels - actionBarSize) *
|
||||
(if (isRunningOnTv()) ANDROID_TV_SIZE_MULTIPLIER else 1f)
|
||||
val resizeHeightPercentage =
|
||||
(height.toFloat() / BASELINE_HEIGHT) * (BASELINE_DENSITY.toFloat() / density)
|
||||
return textUnit * resizeHeightPercentage * 1.1
|
||||
|
||||
+10
-6
@@ -3,14 +3,14 @@ package com.zaneschepke.wireguardautotunnel.util.extensions
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import com.wireguard.android.util.RootShell
|
||||
import com.wireguard.config.Peer
|
||||
import com.zaneschepke.wireguardautotunnel.service.tunnel.BackendState
|
||||
import com.zaneschepke.wireguardautotunnel.service.tunnel.HandshakeStatus
|
||||
import com.zaneschepke.wireguardautotunnel.service.tunnel.TunnelState
|
||||
import com.zaneschepke.wireguardautotunnel.service.tunnel.VpnState
|
||||
import com.zaneschepke.wireguardautotunnel.service.tunnel.statistics.TunnelStatistics
|
||||
import com.zaneschepke.wireguardautotunnel.ui.theme.SilverTree
|
||||
import com.zaneschepke.wireguardautotunnel.ui.theme.Straw
|
||||
import com.zaneschepke.wireguardautotunnel.util.Constants
|
||||
import com.zaneschepke.wireguardautotunnel.util.NumberUtils
|
||||
import org.amnezia.awg.backend.Backend
|
||||
import org.amnezia.awg.config.Config
|
||||
import timber.log.Timber
|
||||
import java.net.InetAddress
|
||||
@@ -23,10 +23,6 @@ fun TunnelStatistics.PeerStats.latestHandshakeSeconds(): Long? {
|
||||
return NumberUtils.getSecondsBetweenTimestampAndNow(this.latestHandshakeEpochMillis)
|
||||
}
|
||||
|
||||
fun VpnState.isDown(): Boolean {
|
||||
return this.status == TunnelState.DOWN
|
||||
}
|
||||
|
||||
fun TunnelStatistics.PeerStats.handshakeStatus(): HandshakeStatus {
|
||||
// TODO add never connected status after duration
|
||||
return this.latestHandshakeSeconds().let {
|
||||
@@ -91,3 +87,11 @@ fun RootShell.getCurrentWifiName(): String? {
|
||||
this.run(response, "dumpsys wifi | grep -o \"SSID: [^,]*\" | cut -d ' ' -f2- | tr -d '\"'")
|
||||
return response.lastOrNull()
|
||||
}
|
||||
|
||||
fun Backend.BackendState.asBackendState(): BackendState {
|
||||
return BackendState.valueOf(this.name)
|
||||
}
|
||||
|
||||
fun BackendState.asAmBackendState(): Backend.BackendState {
|
||||
return Backend.BackendState.valueOf(this.name)
|
||||
}
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="960"
|
||||
android:viewportHeight="960">
|
||||
<path
|
||||
android:fillColor="#e8eaed"
|
||||
android:pathData="M440,520L200,520v-80h240v-240h80v240h240v80L520,520v240h-80v-240Z" />
|
||||
</vector>
|
||||
@@ -1,9 +0,0 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="960"
|
||||
android:viewportHeight="960">
|
||||
<path
|
||||
android:fillColor="#e8eaed"
|
||||
android:pathData="m256,760 l-56,-56 224,-224 -224,-224 56,-56 224,224 224,-224 56,56 -224,224 224,224 -56,56 -224,-224 -224,224Z" />
|
||||
</vector>
|
||||
@@ -1,10 +0,0 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="800dp"
|
||||
android:height="800dp"
|
||||
android:viewportWidth="256"
|
||||
android:viewportHeight="256">
|
||||
<path
|
||||
android:fillColor="#5865F2"
|
||||
android:fillType="nonZero"
|
||||
android:pathData="M216.86,45.1C200.29,37.34 182.57,31.71 164.04,28.5C161.77,32.61 159.11,38.15 157.28,42.55C137.58,39.58 118.07,39.58 98.74,42.55C96.91,38.15 94.19,32.61 91.9,28.5C73.35,31.71 55.61,37.36 39.04,45.14C5.62,95.65 -3.44,144.9 1.09,193.46C23.26,210.01 44.74,220.07 65.86,226.65C71.08,219.47 75.73,211.84 79.74,203.8C72.1,200.9 64.79,197.32 57.89,193.17C59.72,191.81 61.51,190.39 63.24,188.93C105.37,208.63 151.13,208.63 192.75,188.93C194.51,190.39 196.3,191.81 198.11,193.17C191.18,197.34 183.85,200.92 176.22,203.82C180.23,211.84 184.86,219.49 190.1,226.67C211.24,220.09 232.74,210.03 254.91,193.46C260.23,137.17 245.83,88.37 216.86,45.1ZM85.47,163.59C72.83,163.59 62.46,151.79 62.46,137.41C62.46,123.04 72.61,111.21 85.47,111.21C98.34,111.21 108.71,123.02 108.49,137.41C108.51,151.79 98.34,163.59 85.47,163.59ZM170.53,163.59C157.88,163.59 147.51,151.79 147.51,137.41C147.51,123.04 157.66,111.21 170.53,111.21C183.39,111.21 193.76,123.02 193.54,137.41C193.54,151.79 183.39,163.59 170.53,163.59Z" />
|
||||
</vector>
|
||||
@@ -1,9 +0,0 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="960"
|
||||
android:viewportHeight="960">
|
||||
<path
|
||||
android:fillColor="#e8eaed"
|
||||
android:pathData="M200,760h57l391,-391 -57,-57 -391,391v57ZM120,840v-170l528,-527q12,-11 26.5,-17t30.5,-6q16,0 31,6t26,18l55,56q12,11 17.5,26t5.5,30q0,16 -5.5,30.5T817,313L290,840L120,840ZM760,256 L704,200 760,256ZM619,341 L591,312 648,369 619,341Z" />
|
||||
</vector>
|
||||
@@ -1,12 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="no_tunnels">Žádné tunely ještě nebyly přidány!</string>
|
||||
<string name="watcher_notification_text_paused">Monitorování změn ve stavu sítě: pozastaveno</string>
|
||||
<string name="notification_permission_required">Je vyžadováno oprávnění pro oznámení.</string>
|
||||
<string name="add_trusted_ssid">Přidat název důvěryhodné Wi-Fi</string>
|
||||
<string name="enable_auto_tunnel">Spustit automatické tunelování</string>
|
||||
<string name="tunnel_mobile_data">Tunelování na mobilních datech</string>
|
||||
<string name="privacy_policy">Otevřít zásady soukromí</string>
|
||||
<string name="privacy_policy">Otevřít zásady soukromí</string>
|
||||
<string name="okay">OK</string>
|
||||
<string name="thank_you">Děkujeme za používání WG Tunnel!</string>
|
||||
<string name="add_tunnels_text">Přidat ze souboru nebo zipu</string>
|
||||
@@ -14,23 +10,16 @@
|
||||
<string name="add_from_qr">Přidat z QR kódu</string>
|
||||
<string name="qr_scan">QR skenování</string>
|
||||
<string name="tunnel_name">Název tunelu</string>
|
||||
<string name="add_tunnel">Přidat tunel</string>
|
||||
<string name="exclude">Vyloučit</string>
|
||||
<string name="include">Zahrnout</string>
|
||||
<string name="save_changes">Uložit</string>
|
||||
<string name="endpoint">Koncový bod</string>
|
||||
<string name="vpn_connection_failed">Připojení selhalo</string>
|
||||
<string name="always_on_vpn_support">Povolit trvalé připojení VPN</string>
|
||||
<string name="location_services_not_detected">Služby polohy nebyly detekovány</string>
|
||||
<string name="hint_search_packages">Hledat balíčky</string>
|
||||
<string name="attempt_connection">Pokus o připojení...</string>
|
||||
<string name="vpn_starting">VPN se spouští</string>
|
||||
<string name="vpn_on">VPN zapnuto</string>
|
||||
<string name="vpn_off">VPN vypnuto</string>
|
||||
<string name="default_vpn_on">Hlavní VPN zapnuto</string>
|
||||
<string name="default_vpn_off">Hlavní VPN vypnuto</string>
|
||||
<string name="create_import">Vytvořit od základu</string>
|
||||
<string name="turn_off_auto">Akce vyžaduje, aby bylo automatické tunelování vypnuté či pozastavené</string>
|
||||
<string name="add_peer">Přidat peer</string>
|
||||
<string name="comma_separated_list">seznam oddělený čárkami</string>
|
||||
<string name="random">(náhodné)</string>
|
||||
@@ -43,12 +32,7 @@
|
||||
<string name="error_authorization_failed">Nepovedlo se autorizovat</string>
|
||||
<string name="enabled_app_shortcuts">Zapnout zkratky</string>
|
||||
<string name="export_configs">Exportovat konfigurace</string>
|
||||
<string name="export_configs_failed">Nepovedlo se exportovat konfigurace</string>
|
||||
<string name="location_services_required">Služby polohy vyžadovány</string>
|
||||
<string name="background_location_required">Oprávnění pro polohu na pozadí vyžadováno</string>
|
||||
<string name="precise_location_required">Oprávnění pro přesnou polohu vyžadováno</string>
|
||||
<string name="unknown_error">Došlo k neznámé chybě</string>
|
||||
<string name="exported_configs_message">Konfigurace exportovány do stažených souborů</string>
|
||||
<string name="tunnel_on_wifi">Tunelovat na nedůvěryhodné Wi-Fi</string>
|
||||
<string name="email_subject">WG Tunnel podpora</string>
|
||||
<string name="email_chooser">Poslat email…</string>
|
||||
@@ -57,22 +41,14 @@
|
||||
<string name="error_root_denied">Oprávnění root zamítnuto</string>
|
||||
<string name="error_no_file_explorer">Žádný průzkumník souborů není nainstalován</string>
|
||||
<string name="error_invalid_code">Neplatný QR kód</string>
|
||||
<string name="error_none">Žádná chyba</string>
|
||||
<string name="auto_tunnel_title">Služba automatického tunelování</string>
|
||||
<string name="resume">Obnovit</string>
|
||||
<string name="active">aktivní</string>
|
||||
<string name="open_issue">Otevřít případ</string>
|
||||
<string name="incorrect_pin">PIN je nesprávný</string>
|
||||
<string name="create_pin">Vytvořte PIN</string>
|
||||
<string name="set_primary_tunnel">Nastavit jako hlavní tunel</string>
|
||||
<string name="use_tunnel_on_wifi_name">Použít tunel pro Wi-Fi</string>
|
||||
<string name="edit_tunnel">Upravit tunel</string>
|
||||
<string name="disabled">vypnuto</string>
|
||||
<string name="auto_tun_on">Obnovit automatické tunelování</string>
|
||||
<string name="auto_tun_off">Pozastavit automatické tunelování</string>
|
||||
<string name="version">Verze</string>
|
||||
<string name="mode">Mód</string>
|
||||
<string name="use_amnezia">"Použít Amnezia userspace "</string>
|
||||
<string name="junk_packet_count">Junk packet počet</string>
|
||||
<string name="junk_packet_minimum_size">Junk packet minimální velikost</string>
|
||||
<string name="junk_packet_maximum_size">Junk packet maximální velikost</string>
|
||||
@@ -84,28 +60,17 @@
|
||||
<string name="error_file_format">Neplatný formát konfigurace</string>
|
||||
<string name="error_file_extension">Soubor není ve formátu .conf nebo .zip</string>
|
||||
<string name="turn_off_tunnel">Akce vyžaduje vypnutí tunelu</string>
|
||||
<string name="watcher_notification_text_active">Monitorování změn ve stavu sítě: aktivní</string>
|
||||
<string name="tunnel_on_ethernet">Tunelovat na ethernetu</string>
|
||||
<string name="tunnel_start_title">VPN připojeno</string>
|
||||
<string name="prominent_background_location_message">Tato funkce vyžaduje oprávnění pro přístup k poloze na pozadí pro zapnutí monitorování Wi-Fi SSID, i když je aplikace zavřená. Pro více detailů, podívejte se prosím na zásady soukromí umístěné v kategorii Podpora.</string>
|
||||
<string name="tunnel_start_text">Připojeno k tunelu</string>
|
||||
<string name="tunnels">Tunely</string>
|
||||
<string name="disable_auto_tunnel">Zastavit automatické tunelování</string>
|
||||
<string name="tunnel_all">Tunelovat všechny aplikace</string>
|
||||
<string name="config_changes_saved">Změny v konfiguraci uloženy.</string>
|
||||
<string name="icon">Ikona</string>
|
||||
<string name="no_thanks">Ne, děkuji</string>
|
||||
<string name="turn_on">Zapnout</string>
|
||||
<string name="map">Mapa</string>
|
||||
<string name="public_key">Veřejný klíč</string>
|
||||
<string name="addresses">Adresy</string>
|
||||
<string name="dns_servers">DNS servery</string>
|
||||
<string name="allowed_ips">Povolené IP adresy</string>
|
||||
<string name="name">Název</string>
|
||||
<string name="restart">Restartovat tunel</string>
|
||||
<string name="scanning_qr">Skenování QR</string>
|
||||
<string name="none">Žádné názvy důvěryhodných Wi-Fi</string>
|
||||
<string name="other">Ostatní</string>
|
||||
<string name="auto_tunneling">Automatické tunelování</string>
|
||||
<string name="turn_on_tunnel">Akce vyžaduje aktivní tunel</string>
|
||||
<string name="interface_">Rozhraní</string>
|
||||
@@ -116,30 +81,22 @@
|
||||
<string name="base64_key">base64 klíč</string>
|
||||
<string name="docs_description">Přečíst si dokumentaci</string>
|
||||
<string name="email_description">Poslat mi email</string>
|
||||
<string name="support_help_text">Pokud máte potíže, nápady pro zlepšení, nebo se chcete jen zapojit, následující prostředky jsou k dispozici:</string>
|
||||
<string name="location_services_missing_message">Aplikace nenašla žádné služby polohy zapnuté na Vašem zařízení. Dle Vašeho zařízení, tohle může způsobit, že funkce nedůvěryhodné Wi-Fi nedokáže přečíst jméno připojené Wi-Fi. Chcete i přesto pokračovat?</string>
|
||||
<string name="delete_tunnel">Smazat tunel</string>
|
||||
<string name="delete_tunnel_message">Jste si jisti, že chcete smazat tento tunel?</string>
|
||||
<string name="yes">Ano</string>
|
||||
<string name="pause">Pozastavit</string>
|
||||
<string name="paused">pozastaveno</string>
|
||||
<string name="tunneling_apps">Tunelování aplikací</string>
|
||||
<string name="all">vše</string>
|
||||
<string name="included">zahrnuto</string>
|
||||
<string name="excluded">vyloučeno</string>
|
||||
<string name="always_on_disabled">Trvalé VPN připojení se pokusilo spustit tunel, ale tato funkce je vypnutá v nastavení.</string>
|
||||
<string name="no_email_detected">Žádná emailová aplikace nebyla nalezena</string>
|
||||
<string name="no_browser_detected">Žádný prohlížeč nebyl nalezen</string>
|
||||
<string name="logs_saved">Logy uloženy do stažených souborů</string>
|
||||
<string name="read_logs">Přečíst si logy</string>
|
||||
<string name="config_parse_error">Nepovedlo se vložit konfiguraci</string>
|
||||
<string name="pin_created">PIN úspěšně vytvořen</string>
|
||||
<string name="enter_pin">Vložte Váš PIN</string>
|
||||
<string name="no_wifi_names_configured">Žádné názvy Wi-Fi nebyly nastaveny pro tento tunel</string>
|
||||
<string name="enable_app_lock">Zapnout zámek aplikace</string>
|
||||
<string name="restart_on_ping">Restartovat při selhání pingu</string>
|
||||
<string name="mobile_data_tunnel">Nastavit jako tunel pro mobilní data</string>
|
||||
<string name="general">Obecné</string>
|
||||
<string name="settings">Nastavení</string>
|
||||
<string name="support">Podpora</string>
|
||||
<string name="app_name">WG Tunnel</string>
|
||||
@@ -148,5 +105,4 @@
|
||||
<string name="listen_port">Naslouchací port</string>
|
||||
<string name="auto">(automaticky)</string>
|
||||
<string name="kernel">Kernel</string>
|
||||
<string name="backend">Backend</string>
|
||||
</resources>
|
||||
</resources>
|
||||
@@ -3,66 +3,43 @@
|
||||
<string name="app_name">WG Tunnel</string>
|
||||
<string name="error_file_extension">Datei ist keine .conf oder .zip</string>
|
||||
<string name="no_tunnels">Noch keine Tunnel hinzugefügt!</string>
|
||||
<string name="watcher_notification_text_active">Überwachung der Netzwerkänderungen: aktiv</string>
|
||||
<string name="tunnels">Tunnel</string>
|
||||
<string name="enable_auto_tunnel">Auto-Tunneln starten</string>
|
||||
<string name="tunnel_mobile_data">Tunnel für mobile Daten</string>
|
||||
<string name="privacy_policy">Datenschutzbestimmungen anzeigen</string>
|
||||
<string name="disable_auto_tunnel">Auto-Tunneln stoppen</string>
|
||||
<string name="privacy_policy">Siehe Privacy Policy</string>
|
||||
<string name="okay">Ok</string>
|
||||
<string name="tunnel_on_ethernet">Tunnel für Ethernet</string>
|
||||
<string name="attempt_connection">Verbindungsversuch..</string>
|
||||
<string name="auto_tunneling">Auto-Tunneln</string>
|
||||
<string name="default_vpn_off">Primärer VPN aus</string>
|
||||
<string name="turn_on_tunnel">Für diese Aktion muss ein aktiver Tunnel bestehen</string>
|
||||
<string name="watcher_notification_text_paused">Überwachung der Netzwerkänderungen: pausiert</string>
|
||||
<string name="tunnel_start_title">VPN verbunden</string>
|
||||
<string name="tunnel_start_text">Mit Tunnel verbunden</string>
|
||||
<string name="notification_permission_required">Benachrichtigungsberechtigung benötigt.</string>
|
||||
<string name="add_trusted_ssid">Vertrauenswürdigen WLAN-Namen hinzufügen</string>
|
||||
<string name="prominent_background_location_message">Diese Funktion erfordert die Erlaubnis zur Standortbestimmung im Hintergrund, um die Überwachung der WLAN SSID zu ermöglichen, auch wenn die Anwendung geschlossen ist. Weitere Einzelheiten in den Datenschutzbestimmungen, die auf dem Support-Bildschirm verlinkt sind.</string>
|
||||
<string name="prominent_background_location_title">Vereinbarung der Standortberechtigung im Hintergrund</string>
|
||||
<string name="thank_you">Danke fürs Benutzen von WG Tunnel!</string>
|
||||
<string name="trusted_ssid_empty_description">SSID eingeben</string>
|
||||
<string name="trusted_ssid_value_description">SSID bestätigen</string>
|
||||
<string name="trusted_ssid_value_description">SSID übermitteln</string>
|
||||
<string name="add_tunnels_text">Von Datei oder ZIP hinzufügen</string>
|
||||
<string name="open_file">Datei öffnen</string>
|
||||
<string name="add_from_qr">Über QR-Code hinzufügen</string>
|
||||
<string name="qr_scan">Scanne QR</string>
|
||||
<string name="tunnel_name">Tunnel Name</string>
|
||||
<string name="add_tunnel">Tunnel hinzufügen</string>
|
||||
<string name="qr_scan">QR--Code scannen</string>
|
||||
<string name="tunnel_name">Tunnel-Name</string>
|
||||
<string name="exclude">Ausschließen</string>
|
||||
<string name="include">Einschließen</string>
|
||||
<string name="tunnel_all">Alle Apps tunneln</string>
|
||||
<string name="config_changes_saved">Konfigurationsänderungen gespeichert.</string>
|
||||
<string name="save_changes">Speichern</string>
|
||||
<string name="icon">Symbol</string>
|
||||
<string name="no_thanks">Nein danke</string>
|
||||
<string name="turn_on">Einschalten</string>
|
||||
<string name="map">Karte</string>
|
||||
<string name="public_key">Öffentlicher Schlüssel</string>
|
||||
<string name="addresses">Adressen</string>
|
||||
<string name="dns_servers">DNS-Server</string>
|
||||
<string name="mtu">MTU</string>
|
||||
<string name="peer">Peer</string>
|
||||
<string name="peer">Gegenstelle</string>
|
||||
<string name="allowed_ips">Erlaubte IPs</string>
|
||||
<string name="endpoint">Endpunkt</string>
|
||||
<string name="name">Name</string>
|
||||
<string name="restart">Tunnel neustarten</string>
|
||||
<string name="vpn_connection_failed">Verbindung fehlgeschlagen</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_starting">VPN startet</string>
|
||||
<string name="db_name">wg-tunnel-db</string>
|
||||
<string name="scanning_qr">Scanne nach QR</string>
|
||||
<string name="none">Keine vertrauenswürdigen WLAN Namen</string>
|
||||
<string name="other">Sonstige</string>
|
||||
<string name="vpn_on">VPN an</string>
|
||||
<string name="vpn_off">VPN aus</string>
|
||||
<string name="default_vpn_on">Primärer VPN an</string>
|
||||
<string name="create_import">Von Grund auf neu erstellen</string>
|
||||
<string name="turn_off_auto">Für diese Aktion muss Auto-Tunneln ausgeschaltet oder pausiert sein</string>
|
||||
<string name="add_peer">Peer hinzufügen</string>
|
||||
<string name="done">Erledigt</string>
|
||||
<string name="rotate_keys">Schlüssel rotieren</string>
|
||||
@@ -72,7 +49,6 @@
|
||||
<string name="comma_separated_list">Kommaseparierte Liste</string>
|
||||
<string name="delete_tunnel">Tunnel löschen</string>
|
||||
<string name="persistent_keepalive">Dauerhaftes Keepalive</string>
|
||||
<string name="background_location_required">Hintergrund Standortdienste erforderlich</string>
|
||||
<string name="enable_app_lock">App-Sperre aktivieren</string>
|
||||
<string name="interface_">Schnittstelle</string>
|
||||
<string name="listen_port">Eingehender Port</string>
|
||||
@@ -83,87 +59,114 @@
|
||||
<string name="cancel">Abbrechen</string>
|
||||
<string name="preshared_key">Geteilter Schlüssel</string>
|
||||
<string name="enabled_app_shortcuts">App-Verknüpfungen aktivieren</string>
|
||||
<string name="exported_configs_message">Konfigurationen in Download Ordner exportiert</string>
|
||||
<string name="tunnel_on_wifi">Tunnel bei nicht vertrauenswürdigem WLAN</string>
|
||||
<string name="email_subject">WG Tunnel Unterstützung</string>
|
||||
<string name="docs_description">Dokumentation lesen</string>
|
||||
<string name="email_description">Sende mir eine E-Mail</string>
|
||||
<string name="support_help_text">Bei Fehlern oder Verbesserungsvorschlägen stehen folgende Ressourcen zur Verfügung:</string>
|
||||
<string name="error_root_denied">Root Shell verweigert</string>
|
||||
<string name="error_no_file_explorer">Kein Datei-Explorer installiert</string>
|
||||
<string name="location_services_missing_message">Die App erkennt keine auf deinem Gerät aktivierten Standortdienste. Je nach Gerät kann dies dazu führen, dass die Funktion \"Nicht vertrauenswürdiges WLAN\" den WLAN-Namen nicht lesen kann. Möchtest du trotzdem fortfahren?</string>
|
||||
<string name="auto_tunnel_title">Auto-Tunnel Service</string>
|
||||
<string name="delete_tunnel_message">Bist du sicher, dass du den Tunnel löschen möchtest?</string>
|
||||
<string name="yes">Ja</string>
|
||||
<string name="resume">Fortsetzen</string>
|
||||
<string name="pause">Pausieren</string>
|
||||
<string name="paused">Pausiert</string>
|
||||
<string name="active">Aktiv</string>
|
||||
<string name="go">Los</string>
|
||||
<string name="excluded">ausgeschlossen</string>
|
||||
<string name="all">Alle</string>
|
||||
<string name="always_on_disabled">Always-on VPN wollte einen Tunnel starten, aber dieses Feature ist in den Einstellungen deaktiviert.</string>
|
||||
<string name="all">alle</string>
|
||||
<string name="no_browser_detected">Keinen Browser erkannt</string>
|
||||
<string name="open_issue">Issue öffnen</string>
|
||||
<string name="read_logs">Logs lesen</string>
|
||||
<string name="auto">(automatisch)</string>
|
||||
<string name="config_parse_error">Fehler beim Lesen der Konfiguration</string>
|
||||
<string name="incorrect_pin">PIN nicht korrekt</string>
|
||||
<string name="pin_created">PIN erfolgreich erstellt</string>
|
||||
<string name="enter_pin">Deine PIN eingeben</string>
|
||||
<string name="auto_off">Auto-Tunneln pausieren</string>
|
||||
<string name="auto_tun_on">Auto-Tunneln fortsetzen</string>
|
||||
<string name="auto_tun_off">Auto-Tunneln pausieren</string>
|
||||
<string name="version">Version</string>
|
||||
<string name="mode">Modus</string>
|
||||
<string name="userspace">Benutzerfläche</string>
|
||||
<string name="settings">Einstellungen</string>
|
||||
<string name="support">Unterstützung</string>
|
||||
<string name="watcher_channel_id">Wächterkanal</string>
|
||||
<string name="error_authentication_failed">Anmeldung fehlgeschlagen</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">Sende eine E-Mail…</string>
|
||||
<string name="error_authorization_failed">Autorisierung fehlgeschlagen</string>
|
||||
<string name="location_services_required">Standortdienste erforderlich</string>
|
||||
<string name="precise_location_required">Genauer Standort erforderlich</string>
|
||||
<string name="error_invalid_code">Ungültiger QR Code</string>
|
||||
<string name="error_none">Kein Fehler</string>
|
||||
<string name="tunneling_apps">Getunnelte Apps</string>
|
||||
<string name="included">eingeschlossen</string>
|
||||
<string name="no_email_detected">Keine E-Mail-App erkannt</string>
|
||||
<string name="logs_saved">Logs im Download Ordner gespeichert</string>
|
||||
<string name="create_pin">PIN erstellen</string>
|
||||
<string name="use_tunnel_on_wifi_name">Tunnel für WLAN-Namen verwenden</string>
|
||||
<string name="no_wifi_names_configured">Keine WLAN-Namen für diesen Tunnel konfiguriert</string>
|
||||
<string name="disabled">Deaktiviert</string>
|
||||
<string name="mobile_data_tunnel">Als Tunnel für Mobile Daten setzen</string>
|
||||
<string name="general">Allgemein</string>
|
||||
<string name="restart_on_ping">Neustart bei PING Fehler (Beta)</string>
|
||||
<string name="edit_tunnel">Tunnel bearbeiten</string>
|
||||
<string name="set_primary_tunnel">Als Primären Tunnel setzen</string>
|
||||
<string name="auto_on">Auto-Tunneln fortsetzen</string>
|
||||
<string name="vpn_channel_id">VPN Kanal</string>
|
||||
<string name="vpn_channel_name">VPN Benachrichtigungskanal</string>
|
||||
<string name="watcher_channel_name">Wächterbenachrichtigungskanal</string>
|
||||
<string name="turn_off_tunnel">Aktion erfordert deaktivierten Tunnel</string>
|
||||
<string name="kernel">Kernel</string>
|
||||
<string name="use_kernel">Kernelmodul verwenden</string>
|
||||
<string name="error_ssid_exists">SSID existiert bereits</string>
|
||||
<string name="use_amnezia">"Amnezia Benutzerumgebung benutzen "</string>
|
||||
<string name="junk_packet_count">Junk-Paket Anzahl</string>
|
||||
<string name="junk_packet_maximum_size">Junk-Paket maximale Grösse</string>
|
||||
<string name="init_packet_junk_size">Initial Junk-Paketgröße</string>
|
||||
<string name="backend">Backend</string>
|
||||
<string name="junk_packet_minimum_size">Junk-Paket minimale Grösse</string>
|
||||
<string name="response_packet_junk_size">Antwort Junk-Paketgröße</string>
|
||||
<string name="init_packet_magic_header">Initialpaket magic header</string>
|
||||
<string name="getting_started_guide">Startanleitung erhalten</string>
|
||||
<string name="getting_started_guide">Schnellstartanleitung</string>
|
||||
<string name="transport_packet_magic_header">Transportpaket magic header</string>
|
||||
<string name="underload_packet_magic_header">Unterlastpaket magic header</string>
|
||||
<string name="see_the">Schaue das</string>
|
||||
<string name="see_the">wirf einen Blick in die</string>
|
||||
<string name="unsure_how">Wenn du nicht sicher bist, wie du weiterverfahren sollst</string>
|
||||
<string name="export_configs_failed">Konfigurationsexport fehlgeschlagen</string>
|
||||
<string name="error_file_format">Ungültige Konfiguration Tunnel-Format</string>
|
||||
<string name="error_file_format">Ungültiges Format der Tunnelkonfiguration</string>
|
||||
<string name="response_packet_magic_header">Antwortpaket magic header</string>
|
||||
<string name="restart_at_boot">Beim Hochfahren starten</string>
|
||||
<string name="optional_default">"optional, standard: "</string>
|
||||
<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, Vorgabe ist peers)</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>
|
||||
<string name="root_accepted">Root-Shell akzeptiert</string>
|
||||
<string name="show_amnezia_properties">Amnezia-Eigenschaften anzeigen</string>
|
||||
<string name="never">nie</string>
|
||||
<string name="handshake">Handshake</string>
|
||||
<string name="vpn_denied_dialog_title">Genehmigung verweigert</string>
|
||||
<string name="logs">Logs</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="selected">Ausgewählt</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>
|
||||
<string name="dark">Dunkel</string>
|
||||
<string name="appearance">Erscheinungsbild</string>
|
||||
<string name="notifications">Benachrichtigungen</string>
|
||||
<string name="dynamic">Dynamisch</string>
|
||||
<string name="skip">Überspringen</string>
|
||||
<string name="learn_more">Mehr erfahren</string>
|
||||
<string name="wildcards_active">Wildcards verfügbar</string>
|
||||
<string name="wifi_name_via_shell">WLAN Namen per Shell</string>
|
||||
<string name="start_auto">Auto-Tunnel starten</string>
|
||||
<string name="tunnel_running">Laufender Tunnel</string>
|
||||
<string name="donate">Fürs Projekt spenden</string>
|
||||
<string name="local_logging">Lokales Logging</string>
|
||||
<string name="enable_local_logging">Lokales Logging aktivieren</string>
|
||||
<string name="configuration_change">Konfigurationsänderung</string>
|
||||
<string name="add_from_clipboard">Aus Zwischenablage einfügen</string>
|
||||
<string name="kill_switch">Kill Switch</string>
|
||||
<string name="automatic">Automatisch</string>
|
||||
<string name="language">Sprache</string>
|
||||
<string name="display_theme">Anzeigetheme</string>
|
||||
<string name="on_demand_rules">Regeln für Tunnel bei Bedarf</string>
|
||||
<string name="launch_app_settings">App Einstellungen aufrufen</string>
|
||||
<string name="primary_tunnel">Primärer Tunnel</string>
|
||||
<string name="mobile_tunnel">Mobiler Daten-Tunnel</string>
|
||||
<string name="use_wildcards">Wildcards für Namen verwenden</string>
|
||||
<string name="stop_auto">Auto-Tunnel stoppen</string>
|
||||
<string name="monitoring_state_changes">Überwache Statusänderungen</string>
|
||||
</resources>
|
||||
|
||||
@@ -1,47 +1,33 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="map">Mapa</string>
|
||||
<string name="allowed_ips">Allowed IPs</string>
|
||||
<string name="email_chooser">Enviar un email…</string>
|
||||
<string name="go">ir</string>
|
||||
<string name="kernel">Kernel</string>
|
||||
<string name="restart">Reiniciar túnel</string>
|
||||
<string name="vpn_connection_failed">Error de conexión</string>
|
||||
<string name="error_none">Sin error</string>
|
||||
<string name="other">Otros</string>
|
||||
<string name="add_peer">Añadir peer</string>
|
||||
<string name="done">Hecho</string>
|
||||
<string name="copy_public_key">Copiar public key</string>
|
||||
<string name="base64_key">clave base64</string>
|
||||
<string name="exported_configs_message">Configuración exportada a Descargas</string>
|
||||
<string name="tunnel_on_wifi">Túnel en Wi-Fi no de confianza</string>
|
||||
<string name="location_services_missing_message">La app no detecta activado el servicio de ubicación en tu dispositivo. Dependiendo del dispositivo, esto podría hacer que la característica de Wi-Fi no de confianza falle al leer el nombre Wi-Fi. ¿Quieres continuar de todas formas?</string>
|
||||
<string name="excluded">excluida(s)</string>
|
||||
<string name="all">todas</string>
|
||||
<string name="mobile_data_tunnel">Establecer como túnel en datos móviles</string>
|
||||
<string name="use_tunnel_on_wifi_name">Usar tunnel en nombre Wi-Fi</string>
|
||||
<string name="disabled">desactivado</string>
|
||||
<string name="version">Versión</string>
|
||||
<string name="userspace">Espacio del usuario</string>
|
||||
<string name="mode">Modo</string>
|
||||
<string name="support">Ayuda</string>
|
||||
<string name="private_key">Clave privada</string>
|
||||
<string name="trusted_ssid_value_description">Enviar SSID</string>
|
||||
<string name="trusted_ssid_empty_description">Introducir SSID</string>
|
||||
<string name="add_tunnels_text">Añadir desde archivo o zip</string>
|
||||
<string name="open_file">Abrir archivo</string>
|
||||
<string name="add_from_qr">Añadir mediante código QR</string>
|
||||
<string name="qr_scan">Escanear QR</string>
|
||||
<string name="tunnel_name">Nombre de túnel</string>
|
||||
<string name="add_tunnel">Añadir túnel</string>
|
||||
<string name="exclude">Excluir</string>
|
||||
<string name="include">Incluir</string>
|
||||
<string name="tunnel_all">Todas las apps por el túnel</string>
|
||||
<string name="config_changes_saved">Cambios de configuración guardados.</string>
|
||||
<string name="save_changes">Guardar</string>
|
||||
<string name="icon">Icono</string>
|
||||
<string name="no_thanks">No gracias</string>
|
||||
<string name="turn_on">Activar</string>
|
||||
<string name="mtu">MTU</string>
|
||||
<string name="dns_servers">DNS servers</string>
|
||||
<string name="addresses">Addresses</string>
|
||||
@@ -49,17 +35,9 @@
|
||||
<string name="error_file_extension">No es un archivo .conf o .zip</string>
|
||||
<string name="turn_off_tunnel">Desactiva antes el túnel</string>
|
||||
<string name="no_tunnels">¡Ningún túnel añadido aún!</string>
|
||||
<string name="watcher_notification_text_paused">Monitoreando cambios de red: En pausa</string>
|
||||
<string name="watcher_notification_text_active">Monitoreando cambios de red: Activado</string>
|
||||
<string name="tunnel_start_title">VPN conectada</string>
|
||||
<string name="tunnel_start_text">Conectado al túnel</string>
|
||||
<string name="notification_permission_required">Necesita permiso de notificaciones.</string>
|
||||
<string name="add_trusted_ssid">Añadir nombres Wi-Fi de confianza</string>
|
||||
<string name="tunnels">Túneles</string>
|
||||
<string name="enable_auto_tunnel">Iniciar túnel-automático</string>
|
||||
<string name="disable_auto_tunnel">Parar túnel-automático</string>
|
||||
<string name="tunnel_mobile_data">Activar túnel en datos móviles</string>
|
||||
<string name="privacy_policy">Ver Política de Privacidad</string>
|
||||
<string name="privacy_policy">Ver Política de Privacidad</string>
|
||||
<string name="okay">OK</string>
|
||||
<string name="tunnel_on_ethernet">Túnel en ethernet</string>
|
||||
<string name="prominent_background_location_title">Divulgación de la ubicación en segundo plano</string>
|
||||
@@ -70,18 +48,11 @@
|
||||
<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="attempt_connection">Intentando conexión...</string>
|
||||
<string name="vpn_starting">Iniciando VPN</string>
|
||||
<string name="db_name">wg-tunnel-db</string>
|
||||
<string name="scanning_qr">Escaneando QR</string>
|
||||
<string name="none">Sin nombres Wi-Fi de confianza</string>
|
||||
<string name="auto_tunneling">Túnel-automático</string>
|
||||
<string name="vpn_on">VPN on</string>
|
||||
<string name="vpn_off">VPN off</string>
|
||||
<string name="default_vpn_on">VPN Principal on</string>
|
||||
<string name="default_vpn_off">VPN Principal off</string>
|
||||
<string name="create_import">Crear desde cero</string>
|
||||
<string name="turn_off_auto">La acción necesita que túnel-automático esté desactivado o en pausa</string>
|
||||
<string name="turn_on_tunnel">La acción necesita un túnel activado</string>
|
||||
<string name="rotate_keys">Rotar claves</string>
|
||||
<string name="comma_separated_list">lista separada por comas</string>
|
||||
@@ -94,9 +65,6 @@
|
||||
<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="background_location_required">Se necesita ubicación en segundo plano</string>
|
||||
<string name="location_services_required">Se necesita servicio de ubicación</string>
|
||||
<string name="precise_location_required">Necesita ubicación precisa</string>
|
||||
<string name="unknown_error">Error desconocido</string>
|
||||
<string name="email_subject">Ayuda WG Tunnel</string>
|
||||
<string name="interface_">Interfaz</string>
|
||||
@@ -105,7 +73,6 @@
|
||||
<string name="persistent_keepalive">Keepalive persistente</string>
|
||||
<string name="docs_description">Leer documentación</string>
|
||||
<string name="email_description">Envíame un email</string>
|
||||
<string name="support_help_text">Si tienes problemas, ideas para mejoras, o simlemente comprometerte, tienes disponibles los siguientes recursos:</string>
|
||||
<string name="use_kernel">Usar módulo del Kernel</string>
|
||||
<string name="error_ssid_exists">SSID existente</string>
|
||||
<string name="error_root_denied">Shell root denegado</string>
|
||||
@@ -115,20 +82,13 @@
|
||||
<string name="delete_tunnel">Eliminar túnel</string>
|
||||
<string name="delete_tunnel_message">¿Estás seguro de que quieres eliminar este túnel?</string>
|
||||
<string name="yes">Sí</string>
|
||||
<string name="resume">Reanudar</string>
|
||||
<string name="pause">Pausar</string>
|
||||
<string name="active">activado</string>
|
||||
<string name="paused">en pausa</string>
|
||||
<string name="tunneling_apps">Apps por el túnel</string>
|
||||
<string name="included">incluida(s)</string>
|
||||
<string name="always_on_disabled">VPN siempre-activada ha intentado iniciar un túnel, pero está característica está deshabilitada en los ajustes.</string>
|
||||
<string name="no_email_detected">Ninguna app de email detectada</string>
|
||||
<string name="no_browser_detected">Ningún navegador detectado</string>
|
||||
<string name="logs_saved">Registros guardados en Descargas</string>
|
||||
<string name="open_issue">Abrir una incidencia</string>
|
||||
<string name="read_logs">Leer los registros</string>
|
||||
<string name="auto">(automático)</string>
|
||||
<string name="config_parse_error">Fallo al analizar la configuración</string>
|
||||
<string name="incorrect_pin">El pin no es correcto</string>
|
||||
<string name="pin_created">Pin creado con éxito</string>
|
||||
<string name="enter_pin">Introduce tu pin</string>
|
||||
@@ -136,23 +96,13 @@
|
||||
<string name="enable_app_lock">Activar el bloqueo de aplicaciones</string>
|
||||
<string name="restart_on_ping">Reiniciar al fallar ping (beta)</string>
|
||||
<string name="set_primary_tunnel">Establecer como túnel Principal</string>
|
||||
<string name="no_wifi_names_configured">No hay nombres Wi-Fi configurados para este túnel</string>
|
||||
<string name="general">General</string>
|
||||
<string name="edit_tunnel">Editar túnel</string>
|
||||
<string name="auto_on">Reanudar túnel-automático</string>
|
||||
<string name="auto_off">Pausar túnel-automático</string>
|
||||
<string name="auto_tun_on">Reanudar el túnel automático</string>
|
||||
<string name="auto_tun_off">Pausa del túnel automático</string>
|
||||
<string name="settings">Ajustes</string>
|
||||
<string name="app_name">WG Tunnel</string>
|
||||
<string name="vpn_channel_id">Canal VPN</string>
|
||||
<string name="vpn_channel_name">Canal de notificación VPN</string>
|
||||
<string name="watcher_channel_id">Canal del obvervador</string>
|
||||
<string name="watcher_channel_name">Canal de notificación del obvervador</string>
|
||||
<string name="prominent_background_location_message">La monitorización SSID Wi-Fi necesita de permiso de ubicación en segundo plano incluso si la app está cerrada. Mira el enlace a la Política de Privacidad en la pantalla de ayuda para más detalles.</string>
|
||||
<string name="export_configs_failed">Error al exportar la configuración</string>
|
||||
<string name="use_amnezia">"Utilizar el entorno de usuario de Amnezia "</string>
|
||||
<string name="prominent_background_location_message">La monitorización SSID Wi-Fi necesita de permiso de ubicación en segundo plano incluso si la app está cerrada. Mira el enlace a la Política de Privacidad en la pantalla de ayuda para más detalles.</string>
|
||||
<string name="junk_packet_count">Recuento de paquetes basura</string>
|
||||
<string name="backend">Backend</string>
|
||||
<string name="junk_packet_minimum_size">Tamaño mínimo del paquete basura</string>
|
||||
<string name="add_from_clipboard">Agregar desde el portapapeles</string>
|
||||
</resources>
|
||||
|
||||
@@ -0,0 +1,172 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="vpn_channel_id">Canal VPN</string>
|
||||
<string name="turn_off_tunnel">Cette action nécessite la désactivation du tunnel</string>
|
||||
<string name="no_tunnels">Aucun tunnel n\'a été ajouté pour le moment !</string>
|
||||
<string name="tunnels">Tunnels</string>
|
||||
<string name="tunnel_mobile_data">Tunnel sur données mobiles</string>
|
||||
<string name="privacy_policy">Voir la Politique de Confidentialité</string>
|
||||
<string name="tunnel_on_ethernet">Tunnel sur Ethernet</string>
|
||||
<string name="prominent_background_location_title">Divulgation de la localisation en arrière-plan</string>
|
||||
<string name="thank_you">Merci d\'utiliser WG Tunnel !</string>
|
||||
<string name="add_tunnels_text">Ajouter depuis un fichier ou un zip</string>
|
||||
<string name="open_file">Fichier ouvert</string>
|
||||
<string name="qr_scan">Scan du code QR</string>
|
||||
<string name="exclude">Exclure</string>
|
||||
<string name="tunnel_all">Tunneliser toutes les applications</string>
|
||||
<string name="save_changes">Sauvegarder</string>
|
||||
<string name="icon">Icône</string>
|
||||
<string name="dns_servers">Serveurs DNS</string>
|
||||
<string name="mtu">MTU</string>
|
||||
<string name="peer">Pair</string>
|
||||
<string name="allowed_ips">IPs autorisées</string>
|
||||
<string name="endpoint">Point de sortie</string>
|
||||
<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>
|
||||
<string name="add_peer">Ajouter un pair</string>
|
||||
<string name="done">Terminé</string>
|
||||
<string name="rotate_keys">Rotation des clés</string>
|
||||
<string name="error_file_extension">Le fichier n\'est pas un .conf ou .zip</string>
|
||||
<string name="vpn_channel_name">Canal de notifications VPN</string>
|
||||
<string name="app_name">WG Tunnel</string>
|
||||
<string name="okay">Ok</string>
|
||||
<string name="prominent_background_location_message">Cette fonctionnalité nécessite d\'autoriser les services de localisation en arrière-plan pour activer la surveillance du SSID Wi-Fi même lorsque l\'application est fermée. Pour plus de détails, veuillez consulter la politique de confidentialité disponible sur l\'écran d\'assistance.</string>
|
||||
<string name="trusted_ssid_value_description">Envoyer le SSID</string>
|
||||
<string name="include">Inclure</string>
|
||||
<string name="add_from_qr">Ajouter depuis un code QR</string>
|
||||
<string name="tunnel_name">Nom du tunnel</string>
|
||||
<string name="config_changes_saved">Changements de la configuration sauvegardés.</string>
|
||||
<string name="public_key">Clé publique</string>
|
||||
<string name="addresses">Adresses</string>
|
||||
<string name="db_name">wg-tunnel-db</string>
|
||||
<string name="vpn_on">VPN allumé</string>
|
||||
<string name="create_import">Créer à partir de zéro</string>
|
||||
<string name="private_key">Clé privée</string>
|
||||
<string name="interface_">Interface</string>
|
||||
<string name="copy_public_key">Copier la clé publique</string>
|
||||
<string name="base64_key">clé base64</string>
|
||||
<string name="comma_separated_list">liste séparée par des virgules</string>
|
||||
<string name="listen_port">Écouter sur le port</string>
|
||||
<string name="random">(aléatoire)</string>
|
||||
<string name="optional_no_recommend">(optionnel, non recommandé)</string>
|
||||
<string name="persistent_keepalive">Keepalive persistent</string>
|
||||
<string name="optional">(optionnel)</string>
|
||||
<string name="preshared_key">Clé pré-partagée</string>
|
||||
<string name="cancel">Annuler</string>
|
||||
<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 fiable</string>
|
||||
<string name="email_chooser">Envoyer un mail…</string>
|
||||
<string name="docs_description">Lire la documentation</string>
|
||||
<string name="email_description">Envoyer un mail</string>
|
||||
<string name="use_kernel">Utiliser le module noyau</string>
|
||||
<string name="error_ssid_exists">Le SSID existe déjà</string>
|
||||
<string name="error_root_denied">Accès au shell en tant que root interdit</string>
|
||||
<string name="error_no_file_explorer">Aucun explorateur de fichier n\'est installé</string>
|
||||
<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="included">inclus</string>
|
||||
<string name="excluded">exclu</string>
|
||||
<string name="no_email_detected">Aucune appli 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>
|
||||
<string name="incorrect_pin">Code PIN incorrect</string>
|
||||
<string name="enter_pin">Entrez votre code PIN</string>
|
||||
<string name="create_pin">Créer un code PIN</string>
|
||||
<string name="restart_on_ping">Redémarrer après échec du ping (beta)</string>
|
||||
<string name="mobile_data_tunnel">Définir comme tunnel de données mobiles</string>
|
||||
<string name="version">Version</string>
|
||||
<string name="support">Support</string>
|
||||
<string name="junk_packet_minimum_size">Taille minimum des paquets indésirables</string>
|
||||
<string name="init_packet_junk_size">Initialiser la taille des paquets indésirables</string>
|
||||
<string name="response_packet_junk_size">Taille des paquets indésirables en réponse</string>
|
||||
<string name="init_packet_magic_header">Initialiser l\'en-tête magique des paquets indésirables</string>
|
||||
<string name="underload_packet_magic_header">En-tête magique des paquets indésirables de sous-charge</string>
|
||||
<string name="unsure_how">si vous n\'êtes pas sûr de comment procéder</string>
|
||||
<string name="see_the">Voir le</string>
|
||||
<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>
|
||||
<string name="set_custom_ping_ip">Personnaliser l\'ip de ping</string>
|
||||
<string name="optional_default">"optionnel, par défaut : "</string>
|
||||
<string name="show_amnezia_properties">Voir les propriétés d\'Amnezia</string>
|
||||
<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 de l\'app</string>
|
||||
<string name="unknown_error">Une erreur inconnue s\'est produite</string>
|
||||
<string name="email_subject">Assistance WG Tunnel</string>
|
||||
<string name="location_services_missing_message">L\'application détecte qu\'aucun service de localisation n\'est activé sur votre appareil. Selon votre appareil, cela peut empêcher la fonctionnalité « Wi-Fi de confiance » de lire le nom du Wi-Fi. Souhaitez-vous tout de même continuer ?</string>
|
||||
<string name="yes">Oui</string>
|
||||
<string name="all">tout</string>
|
||||
<string name="set_primary_tunnel">Définir comme tunnel principal</string>
|
||||
<string name="read_logs">Lire les journaux</string>
|
||||
<string name="auto">(auto)</string>
|
||||
<string name="pin_created">Code PIN bien créé</string>
|
||||
<string name="enable_app_lock">Activer le verrouillage de l\'appli</string>
|
||||
<string name="use_tunnel_on_wifi_name">Utiliser ce tunnel selon le nom du wifi</string>
|
||||
<string name="edit_tunnel">Éditer le tunnel</string>
|
||||
<string name="settings">Réglages</string>
|
||||
<string name="junk_packet_count">Nombre de paquets indésirables</string>
|
||||
<string name="kernel">Noyau</string>
|
||||
<string name="junk_packet_maximum_size">Taille maximum des paquets indésirables</string>
|
||||
<string name="response_packet_magic_header">En-tête magique des paquets indésirables de réponse</string>
|
||||
<string name="transport_packet_magic_header">En-tête magique des paquets indésirables de transport</string>
|
||||
<string name="getting_started_guide">guide de démarrage rapide</string>
|
||||
<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 aux pairs)</string>
|
||||
<string name="set_custom_ping_internal">Intervalle de ping (sec)</string>
|
||||
<string name="set_custom_ping_cooldown">Temps d\'attente avant redémarrage du ping (sec)</string>
|
||||
<string name="sec">sec</string>
|
||||
<string name="add_from_clipboard">Ajouter depuis le presse-papiers</string>
|
||||
<string name="primary_tunnel">Tunnel principal</string>
|
||||
<string name="stop_auto">Arrêter l\'auto-tunnel</string>
|
||||
<string name="kill_switch">Arrêt d\'urgence</string>
|
||||
<string name="appearance">Apparence</string>
|
||||
<string name="notifications">Notifications</string>
|
||||
<string name="automatic">Automatique</string>
|
||||
<string name="light">Clair</string>
|
||||
<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="selected">Sélectionné</string>
|
||||
<string name="trusted_wifi_names">Nom wifi de confiance</string>
|
||||
<string name="add_wifi_name">Ajouter un nom de wifi</string>
|
||||
<string name="mobile_tunnel">Tunnel de données mobiles</string>
|
||||
<string name="skip">Passer</string>
|
||||
<string name="learn_more">En savoir plus</string>
|
||||
<string name="use_wildcards">Utiliser les wildcards</string>
|
||||
<string name="wildcards_active">Wildcards activé</string>
|
||||
<string name="wifi_name_via_shell">Nom du Wifi via le shell</string>
|
||||
<string name="use_root_shell_for_wifi">Utiliser un shell root pour obtenir le nom du wifi</string>
|
||||
<string name="tunnel_running">Tunnel en cours d\'exécution</string>
|
||||
<string name="monitoring_state_changes">Surveiller les changements d’état</string>
|
||||
<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>
|
||||
</resources>
|
||||
@@ -0,0 +1,118 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="version">Versi</string>
|
||||
<string name="settings">Pengaturan</string>
|
||||
<string name="support">Support</string>
|
||||
<string name="kernel">Kernel</string>
|
||||
<string name="init_packet_junk_size">Ukuran sampah paket init</string>
|
||||
<string name="app_name">WG Tunnel</string>
|
||||
<string name="error_file_extension">File bukan .conf atau .zip</string>
|
||||
<string name="turn_off_tunnel">Aksi memerlukan tunnel mati</string>
|
||||
<string name="no_tunnels">Belum ada tunnel yang ditambahkan!</string>
|
||||
<string name="tunnels">Tunnel</string>
|
||||
<string name="tunnel_mobile_data">Tunnel pada data seluler</string>
|
||||
<string name="privacy_policy">Lihat Kebijakan Privasi</string>
|
||||
<string name="okay">Oke</string>
|
||||
<string name="tunnel_on_ethernet">Tunnel pada ethernet</string>
|
||||
<string name="prominent_background_location_title">Keterbukaan Lokasi Latar Belakang</string>
|
||||
<string name="thank_you">Terima kasih telah menggunakan WG Tunnel!</string>
|
||||
<string name="trusted_ssid_value_description">Kirim SSID</string>
|
||||
<string name="add_tunnels_text">Tambah dari file atau zip</string>
|
||||
<string name="open_file">Buka File</string>
|
||||
<string name="add_from_qr">Tambahkan dari kode QR</string>
|
||||
<string name="auto_tunneling">Tunnel-otomatis</string>
|
||||
<string name="vpn_off">VPN mati</string>
|
||||
<string name="vpn_on">VPN aktif</string>
|
||||
<string name="create_import">Buat dari awal</string>
|
||||
<string name="turn_on_tunnel">Tindakan memerlukan tunnel aktif</string>
|
||||
<string name="add_peer">Tambahkan rekan</string>
|
||||
<string name="done">Selesai</string>
|
||||
<string name="interface_">Interface</string>
|
||||
<string name="rotate_keys">Putar tombol</string>
|
||||
<string name="private_key">Kunci privat</string>
|
||||
<string name="comma_separated_list">Daftar yang dipisahkan koma</string>
|
||||
<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>
|
||||
<string name="email_chooser">Kirim email…</string>
|
||||
<string name="docs_description">Baca dokumentasi</string>
|
||||
<string name="email_description">Kirimkan email kepada saya</string>
|
||||
<string name="use_kernel">Gunakan modul kernel</string>
|
||||
<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="included">termasuk</string>
|
||||
<string name="incorrect_pin">Pin salah</string>
|
||||
<string name="pin_created">Pin berhasil dibuat</string>
|
||||
<string name="enter_pin">Masukkan pin Anda</string>
|
||||
<string name="restart_on_ping">Mulai ulang saat ping gagal (beta)</string>
|
||||
<string name="use_tunnel_on_wifi_name">Gunakan tunnel pada nama wifi</string>
|
||||
<string name="edit_tunnel">Edit tunnel</string>
|
||||
<string name="junk_packet_count">Jumlah paket sampah</string>
|
||||
<string name="junk_packet_minimum_size">Ukuran minimum paket sampah</string>
|
||||
<string name="junk_packet_maximum_size">Ukuran maksimum paket sampah</string>
|
||||
<string name="response_packet_junk_size">Ukuran sampah paket respons</string>
|
||||
<string name="init_packet_magic_header">Isi header ajaib paket</string>
|
||||
<string name="response_packet_magic_header">Header ajaib paket respons</string>
|
||||
<string name="transport_packet_magic_header">Header ajaib paket transportasi</string>
|
||||
<string name="underload_packet_magic_header">Header ajaib paket Underload</string>
|
||||
<string name="unsure_how">Jika Anda tidak yakin bagaimana melanjutkannya</string>
|
||||
<string name="see_the">Lihat</string>
|
||||
<string name="getting_started_guide">Panduan Memulai</string>
|
||||
<string name="error_file_format">Format konfigurasi tunnel tidak valid</string>
|
||||
<string name="restart_at_boot">Mulai ulang saat boot</string>
|
||||
<string name="vpn_denied_dialog_title">Izin Ditolak</string>
|
||||
<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>
|
||||
<string name="root_accepted">Shell root diterima</string>
|
||||
<string name="set_custom_ping_ip">Atur ip ping kustom</string>
|
||||
<string name="default_ping_ip">(optional, default ke peers)</string>
|
||||
<string name="set_custom_ping_internal">Interval ping (detik)</string>
|
||||
<string name="optional_default">"opsional, default: "</string>
|
||||
<string name="set_custom_ping_cooldown">Cooldown Ping restart (detik)</string>
|
||||
<string name="show_amnezia_properties">Tampilkan properti Amnezia</string>
|
||||
<string name="never">tidak pernah</string>
|
||||
<string name="sec">dtk</string>
|
||||
<string name="handshake">handshake</string>
|
||||
<string name="vpn_channel_id">Saluran VPN</string>
|
||||
<string name="vpn_channel_name">Notifikasi Saluran VPN</string>
|
||||
<string name="prominent_background_location_message">Fitur ini memerlukan izin lokasi latar belakang untuk mengaktifkan pemantauan SSID Wi-Fi bahkan saat aplikasi ditutup. Untuk detail lebih lanjut, silakan lihat Kebijakan Privasi yang ditautkan di layar Dukungan.</string>
|
||||
<string name="copy_public_key">Salin kunci publik</string>
|
||||
<string name="base64_key">kunci base64</string>
|
||||
<string name="error_authentication_failed">Autentikasi gagal</string>
|
||||
<string name="enable_app_lock">Aktifkan kunci aplikasi</string>
|
||||
<string name="delete_tunnel">Hapus tunnel</string>
|
||||
<string name="delete_tunnel_message">Apakah Anda yakin ingin menghapus tunnel ini?</string>
|
||||
<string name="no_email_detected">Tidak ada aplikasi email yang terdeteksi</string>
|
||||
<string name="no_browser_detected">Tidak ada browser yang terdeteksi</string>
|
||||
<string name="excluded">dikecualikan</string>
|
||||
<string name="all">semua</string>
|
||||
<string name="open_issue">Membuka masalah</string>
|
||||
<string name="read_logs">Baca log</string>
|
||||
<string name="auto">(auto)</string>
|
||||
<string name="create_pin">Buat pin</string>
|
||||
<string name="mobile_data_tunnel">Ditetapkan sebagai tunnel data seluler</string>
|
||||
<string name="set_primary_tunnel">Ditetapkan sebagai tunnel utama</string>
|
||||
</resources>
|
||||
@@ -0,0 +1,2 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources></resources>
|
||||
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="app_name">WG Tunnel</string>
|
||||
<string name="error_file_extension">ファイルが.confまたは.zipではありません</string>
|
||||
<string name="vpn_channel_id">VPNチャンネル</string>
|
||||
<string name="vpn_channel_name">VPN通知チャンネル</string>
|
||||
</resources>
|
||||
@@ -1,2 +1,139 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources></resources>
|
||||
<resources>
|
||||
<string name="error_file_extension">Bestand is geen .conf of .zip</string>
|
||||
<string name="turn_off_tunnel">Actie vereist uitgeschakelde tunnel</string>
|
||||
<string name="tunnel_mobile_data">Tunnel bij mobiele data</string>
|
||||
<string name="app_name">WG Tunnel</string>
|
||||
<string name="vpn_channel_id">VPN Kanaal</string>
|
||||
<string name="vpn_channel_name">VPN Notificatiekanaal</string>
|
||||
<string name="tunnels">Tunnels</string>
|
||||
<string name="privacy_policy">Bekijk privacybeleid</string>
|
||||
<string name="tunnel_on_ethernet">Tunnel bij ethernet</string>
|
||||
<string name="no_tunnels">Nog geen tunnels geconfigureerd!</string>
|
||||
<string name="okay">Oke</string>
|
||||
<string name="error_authentication_failed">Authenticatie mislukt</string>
|
||||
<string name="icon">Icoon</string>
|
||||
<string name="include">Meenemen</string>
|
||||
<string name="addresses">Adres</string>
|
||||
<string name="peer">Peer (extern systeem)</string>
|
||||
<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>
|
||||
<string name="turn_on_tunnel">Actie vereist actieve tunnel</string>
|
||||
<string name="seconds">seconden</string>
|
||||
<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>
|
||||
<string name="add_tunnels_text">Toevoegen uit bestand of zip</string>
|
||||
<string name="add_from_qr">Toevoegen met QR code</string>
|
||||
<string name="qr_scan">QR scannen</string>
|
||||
<string name="exclude">Uitsluiten</string>
|
||||
<string name="tunnel_all">Alle applicaties tunnelen</string>
|
||||
<string name="config_changes_saved">Configuratiewijzigingen opgeslagen.</string>
|
||||
<string name="comma_separated_list">Komma-gescheiden lijst</string>
|
||||
<string name="open_file">Bestand openen</string>
|
||||
<string name="public_key">Publieke sleutel</string>
|
||||
<string name="tunnel_name">Tunnelnaam</string>
|
||||
<string name="save_changes">Opslaan</string>
|
||||
<string name="dns_servers">DNS servers</string>
|
||||
<string name="mtu">MTU</string>
|
||||
<string name="db_name">wg-tunnel-db</string>
|
||||
<string name="vpn_on">VPN aan</string>
|
||||
<string name="done">Gereed</string>
|
||||
<string name="interface_">Interface</string>
|
||||
<string name="create_import">Nieuw beginnen</string>
|
||||
<string name="rotate_keys">Sleutels roteren</string>
|
||||
<string name="vpn_off">VPN uit</string>
|
||||
<string name="private_key">Prive sleutel</string>
|
||||
<string name="copy_public_key">Publieke sleutel kopiëren</string>
|
||||
<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="included">inclusief</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>
|
||||
<string name="restart_on_ping">Herstart bij mislukte ping (beta)</string>
|
||||
<string name="init_packet_junk_size">Initiële junk packet grootte</string>
|
||||
<string name="junk_packet_maximum_size">Junk packet maximum grootte</string>
|
||||
<string name="response_packet_junk_size">Response junk packet grootte</string>
|
||||
<string name="delete_tunnel_message">Weet je zeker dat je deze tunnel wil verwijderen?</string>
|
||||
<string name="yes">Ja</string>
|
||||
<string name="no_email_detected">Geen email app gevonden</string>
|
||||
<string name="read_logs">Lees logboeken</string>
|
||||
<string name="auto">(auto)</string>
|
||||
<string name="mobile_data_tunnel">Stel in als mobiele datatunnel</string>
|
||||
<string name="set_primary_tunnel">Stel in als primaire tunnel</string>
|
||||
<string name="edit_tunnel">Tunnel bewerken</string>
|
||||
<string name="version">Versie</string>
|
||||
<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>
|
||||
<string name="delete_tunnel">Tunnel verwijderen</string>
|
||||
<string name="junk_packet_count">Junk packetteller</string>
|
||||
<string name="incorrect_pin">Foutieve PIN</string>
|
||||
<string name="enter_pin">Vul PIN in</string>
|
||||
<string name="excluded">exclusief</string>
|
||||
<string name="pin_created">PIN aangemaakt</string>
|
||||
<string name="use_tunnel_on_wifi_name">Gebruik tunnel bij WiFi naam</string>
|
||||
<string name="error_file_format">Ongeldige tunnelconfiguratie</string>
|
||||
<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>
|
||||
<string name="email_chooser">Stuur een email…</string>
|
||||
<string name="docs_description">Lees de documentatie</string>
|
||||
<string name="use_kernel">Gebruik kernelmodus</string>
|
||||
<string name="error_ssid_exists">SSID bestaat al</string>
|
||||
<string name="error_root_denied">Root toegang geweigerd</string>
|
||||
<string name="tunnel_on_wifi">Tunnelen bij onvertrouwde WiFi</string>
|
||||
<string name="error_no_file_explorer">Geen bestandsverkenner geïnstalleerd</string>
|
||||
<string name="response_packet_magic_header">Response packet magic header</string>
|
||||
<string name="underload_packet_magic_header">Underload packet magic header</string>
|
||||
<string name="unsure_how">als je niet weet hoe verder te gaan</string>
|
||||
<string name="restart_at_boot">Herstart na systeemstart</string>
|
||||
<string name="always_on_message">VPN verbindingstoegang geweigerd. Controleer aub de</string>
|
||||
<string name="always_on_message2">om zeker te zijn dat Altijd-aan VPN is uitgeschakeld voor alle andere apps, en probeer dan opnieuw</string>
|
||||
<string name="default_ping_ip">(optioneel, standaard op peers)</string>
|
||||
<string name="set_custom_ping_cooldown">Ping herstart afkoelperiode (sec)</string>
|
||||
<string name="background_location_message2">om te controleren of toegang is verleend.</string>
|
||||
<string name="show_amnezia_properties">Amnezia instellingen weergeven</string>
|
||||
<string name="never">nooit</string>
|
||||
<string name="sec">sec</string>
|
||||
<string name="handshake">handshake</string>
|
||||
<string name="logs">Logboeken</string>
|
||||
<string name="email_description">Stuur mij een email</string>
|
||||
<string name="see_the">Bekijk de</string>
|
||||
<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>
|
||||
<string name="optional_default">"optioneel, standaard "</string>
|
||||
<string name="prominent_background_location_message">Deze functie vereist locatierechten om het WiFi SSID te kunnen bepalen. Ook als de applicatie is gesloten. Lees het privacybeleid op het scherm Ondersteuning voor meer informatie.</string>
|
||||
<string name="location_services_missing_message">De app kan geen ingeschakelde locatieservices op je apparaat vinden. Afhankelijk van het type apparaat kan dit leiden tot een niet functionerende herkenning van het verbonden WiFi netwerk. De niet-vertrouwde WiFi functionaliteit werkt daardoor mogelijk niet. Toch doorgaan?</string>
|
||||
<string name="background_location_message">Permanente achtergrondtoegang tot exacte locatie is vereist voor deze functie. Bekijk aub de</string>
|
||||
<string name="transport_packet_magic_header">Transport packet magic header</string>
|
||||
</resources>
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources></resources>
|
||||
@@ -1,2 +1,133 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources></resources>
|
||||
<resources>
|
||||
<string name="error_authorization_failed">Autorização falhou</string>
|
||||
<string name="export_configs">Exportar configurações</string>
|
||||
<string name="incorrect_pin">O Pin está errado</string>
|
||||
<string name="create_pin">Criar um pin</string>
|
||||
<string name="set_primary_tunnel">Selecionar como túnel principal</string>
|
||||
<string name="chat_description">Junte-se à comunidade</string>
|
||||
<string name="tunnel_required">Característica requer pelo menos um túnel</string>
|
||||
<string name="background_location_message2">para garantir que essas permissões estejam ativadas.</string>
|
||||
<string name="optional_no_recommend">(opcional, não recomendado)</string>
|
||||
<string name="dns_servers">Servidores DNS</string>
|
||||
<string name="save_changes">Gravar</string>
|
||||
<string name="add_from_qr">Adicionar a partir de código QR</string>
|
||||
<string name="tunnels">Túneis</string>
|
||||
<string name="email_description">Me envie um email</string>
|
||||
<string name="docs_description">Ler a documentação</string>
|
||||
<string name="no_email_detected">Nenhuma aplicação de email detetado</string>
|
||||
<string name="always_on_vpn_support">Permitir VPN sempre ligada</string>
|
||||
<string name="read_logs">Ler os registos</string>
|
||||
<string name="auto_tunneling">Auto-túnel</string>
|
||||
<string name="optional">(opcional)</string>
|
||||
<string name="settings">Configurações</string>
|
||||
<string name="support">Suporte</string>
|
||||
<string name="mtu">MTU</string>
|
||||
<string name="error_file_extension">O ficheiro não é .conf ou .zip</string>
|
||||
<string name="no_tunnels">Nenhum túnel foi adicionado!</string>
|
||||
<string name="tunnel_mobile_data">Túnel em dados móveis</string>
|
||||
<string name="privacy_policy">Ver a Política de Privacidade</string>
|
||||
<string name="okay">OK</string>
|
||||
<string name="tunnel_on_ethernet">Túnel na ethernet</string>
|
||||
<string name="create_import">Criar do zero</string>
|
||||
<string name="turn_on_tunnel">Esta ação precisa um túnel ativo</string>
|
||||
<string name="enabled_app_shortcuts">Ativar atalhos de aplicações</string>
|
||||
<string name="tunnel_on_wifi">Túnel em Wi-Fi não confiável</string>
|
||||
<string name="email_subject">Apoio para o WG Tunnel</string>
|
||||
<string name="email_chooser">Enviar um email…</string>
|
||||
<string name="prominent_background_location_title">Revelar a localização em segundo plano</string>
|
||||
<string name="thank_you">Obrigado por usar o WG Tunnel!</string>
|
||||
<string name="qr_scan">Escanear o código QR</string>
|
||||
<string name="tunnel_name">Nome do Túnel</string>
|
||||
<string name="location_services_not_detected">Serviço de localização não foi detetado</string>
|
||||
<string name="hint_search_packages">Procurar pacotes</string>
|
||||
<string name="delete_tunnel_message">Tem certeza que quer apagar este túnel?</string>
|
||||
<string name="yes">Sim</string>
|
||||
<string name="tunneling_apps">Aplicações com túnel</string>
|
||||
<string name="excluded">excluído</string>
|
||||
<string name="all">todos</string>
|
||||
<string name="auto">(automático)</string>
|
||||
<string name="pin_created">Pin criado com sucesso</string>
|
||||
<string name="enter_pin">Digite o seu pin</string>
|
||||
<string name="enable_app_lock">Ligar bloqueio de aplicação</string>
|
||||
<string name="restart_on_ping">Reiniciar em falha de ping (beta)</string>
|
||||
<string name="edit_tunnel">Editar túnel</string>
|
||||
<string name="version">Versão</string>
|
||||
<string name="kernel">Kernel</string>
|
||||
<string name="junk_packet_count">Quantidade de pacotes-lixo</string>
|
||||
<string name="junk_packet_minimum_size">Tamanho mínimo de pacote-lixo</string>
|
||||
<string name="response_packet_junk_size">Tamanho de resposta de pacote-lixo</string>
|
||||
<string name="unsure_how">se não tiver certeza em como continuar</string>
|
||||
<string name="error_file_format">Formato de configuração inválido</string>
|
||||
<string name="always_on_message">A permissão de conexão VPN foi negada. Por favor, verifique</string>
|
||||
<string name="background_location_message">Permitir que toda a permissão de localização do tempo e/ou localização precisa é necessária para este recurso. Por favor, veja</string>
|
||||
<string name="app_settings">configurações da app</string>
|
||||
<string name="root_accepted">Shell root aceito</string>
|
||||
<string name="set_custom_ping_ip">Definir ip ping personalizado</string>
|
||||
<string name="default_ping_ip">(opcional, padrão para pares)</string>
|
||||
<string name="set_custom_ping_internal">Intervalo de ping (seg)</string>
|
||||
<string name="optional_default">"opcional, padrão: "</string>
|
||||
<string name="show_amnezia_properties">Mostrar propriedades de Amnezia</string>
|
||||
<string name="never">nunca</string>
|
||||
<string name="sec">seg</string>
|
||||
<string name="handshake">handshake</string>
|
||||
<string name="turn_off_tunnel">Esta ação só é possível com o túnel inativo</string>
|
||||
<string name="allowed_ips">IPs Permitidos</string>
|
||||
<string name="endpoint">Endpoint</string>
|
||||
<string name="name">Nome</string>
|
||||
<string name="vpn_on">VPN ligada</string>
|
||||
<string name="vpn_off">VPN desligada</string>
|
||||
<string name="add_peer">Adicionar par</string>
|
||||
<string name="done">Feito</string>
|
||||
<string name="interface_">Interface</string>
|
||||
<string name="base64_key">Chave base64</string>
|
||||
<string name="persistent_keepalive">Manter a conexão persistente (keepalive)</string>
|
||||
<string name="cancel">Cancelar</string>
|
||||
<string name="unknown_error">Ocorreu um erro desconhecido</string>
|
||||
<string name="error_ssid_exists">SSID já existe</string>
|
||||
<string name="error_root_denied">Shell Root negado</string>
|
||||
<string name="delete_tunnel">Apagar túnel</string>
|
||||
<string name="included">incluso</string>
|
||||
<string name="junk_packet_maximum_size">Tamanho máximo de pacote-lixo</string>
|
||||
<string name="init_packet_junk_size">Tamanho de pacote-lixo inicial</string>
|
||||
<string name="vpn_denied_dialog_title">Permissão negada</string>
|
||||
<string name="vpn_settings">Configurações do sistema VPN</string>
|
||||
<string name="error_invalid_code">Código QR inválido</string>
|
||||
<string name="location_services_missing_message">A aplicação não detetou o serviço de localização ativado no seu dispositivo. Dependendo do dispositivo, isto pode causar que a função de Wi-Fi não confiável falhe em ler o nome do Wi-Fi. Quer continuar mesmo assim?</string>
|
||||
<string name="auto_tunnel_title">Serviço de Auto-túnel</string>
|
||||
<string name="error_no_file_explorer">Nenhum explorador de ficheiros instalado</string>
|
||||
<string name="app_name">WG Tunnel</string>
|
||||
<string name="vpn_channel_id">Canal de VPN</string>
|
||||
<string name="vpn_channel_name">Canal de notificações VPN</string>
|
||||
<string name="prominent_background_location_message">Este recurso precisa de permissões de localização em segundo plano para ativar o monitoramento do SSID da rede Wi-Fi mesmo quando a aplicação está fechado. Para mais pormenores, por favor veja a Política de Privacidade no ecrã de Suporte.</string>
|
||||
<string name="trusted_ssid_value_description">Envie o SSID</string>
|
||||
<string name="add_tunnels_text">Adicionar a partir de ficheiro ou zip</string>
|
||||
<string name="open_file">Abrir Ficheiro</string>
|
||||
<string name="exclude">Excluir</string>
|
||||
<string name="include">Incluir</string>
|
||||
<string name="tunnel_all">Todas as aplicações pelo túnel</string>
|
||||
<string name="config_changes_saved">Mudanças nas configurações gravadas.</string>
|
||||
<string name="icon">Ícone</string>
|
||||
<string name="public_key">Chave pública</string>
|
||||
<string name="addresses">Endereços</string>
|
||||
<string name="peer">Par</string>
|
||||
<string name="db_name">wg-tunnel-db</string>
|
||||
<string name="rotate_keys">Revezar chaves</string>
|
||||
<string name="private_key">Chave privada</string>
|
||||
<string name="copy_public_key">Copiar chave pública</string>
|
||||
<string name="comma_separated_list">Lista separada por vírgulas</string>
|
||||
<string name="listen_port">Porta de escuta</string>
|
||||
<string name="random">(aleatório)</string>
|
||||
<string name="preshared_key">Chave pré-partilhada</string>
|
||||
<string name="seconds">segundos</string>
|
||||
<string name="error_authentication_failed">Autenticação falhou</string>
|
||||
<string name="use_kernel">Usar o módulo do kernel</string>
|
||||
<string name="no_browser_detected">Nenhum navegador detetado</string>
|
||||
<string name="open_issue">Abrir um problema</string>
|
||||
<string name="mobile_data_tunnel">Selecionar como túnel em dados móveis</string>
|
||||
<string name="use_tunnel_on_wifi_name">Usar túnel em wifi com nome</string>
|
||||
<string name="see_the">Veja o</string>
|
||||
<string name="getting_started_guide">guia de início rápido</string>
|
||||
<string name="always_on_message2">para ter certeza que VPN Sempre-ligada é desligada para todas as outras aplicações e tente novamente</string>
|
||||
<string name="restart_at_boot">Reiniciar no arranque</string>
|
||||
</resources>
|
||||
|
||||
@@ -1,95 +1,66 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="watcher_notification_text_active">Monitoramento de mudanças do estado da rede: Ativo</string>
|
||||
<string name="watcher_notification_text_paused">Monitoramento de mudanças do estado da rede: pausado</string>
|
||||
<string name="tunnel_start_title">VPN conectada</string>
|
||||
<string name="tunnel_on_ethernet">Túnel na ethernet</string>
|
||||
<string name="save_changes">Salvar</string>
|
||||
<string name="save_changes">Gravar</string>
|
||||
<string name="public_key">Chave pública</string>
|
||||
<string name="addresses">Endereços</string>
|
||||
<string name="dns_servers">Servidores DNS</string>
|
||||
<string name="endpoint">Endpoint</string>
|
||||
<string name="name">Nome</string>
|
||||
<string name="restart">Reiniciar Túnel</string>
|
||||
<string name="create_import">Criar do zero</string>
|
||||
<string name="turn_off_auto">Esta ação precisa do auto-túnel desativado ou pausado</string>
|
||||
<string name="rotate_keys">Revezar chaves</string>
|
||||
<string name="private_key">Chave privada</string>
|
||||
<string name="base64_key">Chave base64</string>
|
||||
<string name="optional_no_recommend">(opcional, não recomendado)</string>
|
||||
<string name="preshared_key">Chave pré-compartilhada</string>
|
||||
<string name="preshared_key">Chave pré-partilhada</string>
|
||||
<string name="seconds">segundos</string>
|
||||
<string name="export_configs">Exportar configurações</string>
|
||||
<string name="export_configs_failed">Falhou ao exportar configurações</string>
|
||||
<string name="go">ir</string>
|
||||
<string name="error_no_file_explorer">Nenhum explorador de arquivos instalado</string>
|
||||
<string name="error_no_file_explorer">Nenhum explorador de ficheiros instalado</string>
|
||||
<string name="error_invalid_code">Código QR inválido</string>
|
||||
<string name="auto_tunnel_title">Serviço de Auto-túnel</string>
|
||||
<string name="excluded">excluído</string>
|
||||
<string name="all">todos</string>
|
||||
<string name="enter_pin">Digite seu pin</string>
|
||||
<string name="enter_pin">Digite o seu pin</string>
|
||||
<string name="use_tunnel_on_wifi_name">Usar túnel em wifi com nome</string>
|
||||
<string name="auto_tun_on">Continuar auto-túnel</string>
|
||||
<string name="auto_tun_off">Pausar auto-túnel</string>
|
||||
<string name="version">Versão</string>
|
||||
<string name="mode">Modo</string>
|
||||
<string name="use_amnezia">"Usar Amnezia em modo usuário "</string>
|
||||
<string name="junk_packet_count">Número de pacotes-lixo</string>
|
||||
<string name="junk_packet_count">Quantidade de pacotes-lixo</string>
|
||||
<string name="junk_packet_minimum_size">Tamanho mínimo de pacote-lixo</string>
|
||||
<string name="junk_packet_maximum_size">Tamanho máximo de pacote-lixo</string>
|
||||
<string name="init_packet_junk_size">Tamanho de pacote-lixo inicial</string>
|
||||
<string name="response_packet_junk_size">Tamanho de resposta de pacote-lixo</string>
|
||||
<string name="app_name">WG Tunnel</string>
|
||||
<string name="no_tunnels">Nenhum túnel foi adicionado!</string>
|
||||
<string name="error_file_extension">O arquivo não é .conf ou .zip</string>
|
||||
<string name="prominent_background_location_message">Este recurso precisa de permissões de localização em segundo plano para habilitar o monitoramento do SSID da rede Wi-Fi mesmo quando o aplicativo está fechado. Para mais detalhes, por favor veja a Política de Privacidade na tela de Suporte.</string>
|
||||
<string name="error_file_extension">O ficheiro não é .conf ou .zip</string>
|
||||
<string name="prominent_background_location_message">Este recurso precisa de permissões de localização em segundo plano para ativar o monitoramento do SSID da rede Wi-Fi mesmo quando a aplicação está fechado. Para mais pormenores, por favor veja a Política de Privacidade no ecrã de Suporte.</string>
|
||||
<string name="turn_off_tunnel">Esta ação só é possível com o túnel inativo</string>
|
||||
<string name="enabled_app_shortcuts">Habilitar atalhos do aplicativo</string>
|
||||
<string name="notification_permission_required">Necessita permissões de notificação.</string>
|
||||
<string name="add_trusted_ssid">Adicionar nome de Wi-Fi confiável</string>
|
||||
<string name="enable_auto_tunnel">Iniciar auto-túnel</string>
|
||||
<string name="enabled_app_shortcuts">Ativar atalhos de aplicações</string>
|
||||
<string name="tunnels">Túneis</string>
|
||||
<string name="disable_auto_tunnel">Parar auto-túnel</string>
|
||||
<string name="tunnel_start_text">Conectado ao túnel</string>
|
||||
<string name="trusted_ssid_empty_description">Digite o SSID</string>
|
||||
<string name="no_thanks">Não, obrigado</string>
|
||||
<string name="privacy_policy">Ver a Política de Privacidade</string>
|
||||
<string name="okay">OK</string>
|
||||
<string name="tunnel_mobile_data">Túnel em dados móveis</string>
|
||||
<string name="prominent_background_location_title">Revelar a localização em segundo plano</string>
|
||||
<string name="prominent_background_location_title">Revelar a localização em segundo plano</string>
|
||||
<string name="thank_you">Obrigado por usar o WG Tunnel!</string>
|
||||
<string name="trusted_ssid_value_description">Envie o SSID</string>
|
||||
<string name="open_file">Abrir Arquivo</string>
|
||||
<string name="open_file">Abrir Ficheiro</string>
|
||||
<string name="add_from_qr">Adicionar a partir de código QR</string>
|
||||
<string name="add_tunnels_text">Adicionar a partir de arquivo ou zip</string>
|
||||
<string name="tunnel_all">Todos os aplicativos pelo túnel</string>
|
||||
<string name="add_tunnels_text">Adicionar a partir de ficheiro ou zip</string>
|
||||
<string name="tunnel_all">Todas as aplicações pelo túnel</string>
|
||||
<string name="icon">Ícone</string>
|
||||
<string name="turn_on">Ligar</string>
|
||||
<string name="qr_scan">Escanear o código QR</string>
|
||||
<string name="tunnel_name">Nome do Túnel</string>
|
||||
<string name="add_tunnel">Adicionar Túnel</string>
|
||||
<string name="config_changes_saved">Mudanças nas configurações salvas.</string>
|
||||
<string name="config_changes_saved">Mudanças nas configurações gravadas.</string>
|
||||
<string name="exclude">Excluir</string>
|
||||
<string name="include">Incluir</string>
|
||||
<string name="map">Mapa</string>
|
||||
<string name="vpn_connection_failed">Falha na conexão</string>
|
||||
<string name="mtu">MTU</string>
|
||||
<string name="always_on_vpn_support">Permitir VPN sempre ligada</string>
|
||||
<string name="allowed_ips">IPs Permitidos</string>
|
||||
<string name="attempt_connection">Tentando conexão..</string>
|
||||
<string name="peer">Par</string>
|
||||
<string name="location_services_not_detected">Serviço de localização não foi detectado</string>
|
||||
<string name="location_services_not_detected">Serviço de localização não foi detetado</string>
|
||||
<string name="hint_search_packages">Procurar pacotes</string>
|
||||
<string name="vpn_starting">Iniciando VPN</string>
|
||||
<string name="other">Outro</string>
|
||||
<string name="scanning_qr">Escaneando código QR</string>
|
||||
<string name="none">Nenhum nome de Wi-Fi confiável</string>
|
||||
<string name="auto_tunneling">Auto-túnel</string>
|
||||
<string name="default_vpn_on">VPN Principal ligada</string>
|
||||
<string name="vpn_on">VPN ligada</string>
|
||||
<string name="vpn_off">VPN desligada</string>
|
||||
<string name="listen_port">Porta de escuta</string>
|
||||
<string name="default_vpn_off">VPN Principal desligada</string>
|
||||
<string name="turn_on_tunnel">Esta ação precisa um túnel ativo</string>
|
||||
<string name="done">Feito</string>
|
||||
<string name="add_peer">Adicionar par</string>
|
||||
@@ -103,58 +74,58 @@
|
||||
<string name="error_authentication_failed">Autenticação falhou</string>
|
||||
<string name="error_authorization_failed">Autorização falhou</string>
|
||||
<string name="restart_on_ping">Reiniciar em falha de ping (beta)</string>
|
||||
<string name="background_location_required">Necessita a localização em segundo plano</string>
|
||||
<string name="location_services_required">Necessita dos serviços de localização</string>
|
||||
<string name="email_description">Me envie um email</string>
|
||||
<string name="error_ssid_exists">SSID já existe</string>
|
||||
<string name="delete_tunnel_message">Tem certeza que você quer apagar este túnel?</string>
|
||||
<string name="delete_tunnel_message">Tem certeza que quer apagar este túnel?</string>
|
||||
<string name="yes">Sim</string>
|
||||
<string name="precise_location_required">Necessita da localização precisa</string>
|
||||
<string name="exported_configs_message">Configurações exportadas para downloads</string>
|
||||
<string name="unknown_error">Ocorreu um erro desconhecido</string>
|
||||
<string name="email_subject">Suporte para o WG Tunnel</string>
|
||||
<string name="email_subject">Apoio para o WG Tunnel</string>
|
||||
<string name="tunnel_on_wifi">Túnel em Wi-Fi não confiável</string>
|
||||
<string name="error_none">Nenhum erro</string>
|
||||
<string name="delete_tunnel">Apagar túnel</string>
|
||||
<string name="email_chooser">Enviar um email…</string>
|
||||
<string name="use_kernel">Usar o módulo do kernel</string>
|
||||
<string name="docs_description">Ler a documentação</string>
|
||||
<string name="support_help_text">Se você enfrentar problemas, tiver ideias para melhorias ou apenas quiser participar, os seguintes recursos estão disponíveis:</string>
|
||||
<string name="error_root_denied">Shell Root negado</string>
|
||||
<string name="location_services_missing_message">O aplicativo não detectou o serviço de localização habilitado no seu dispositivo. Dependendo do dispositivo, isto pode causar que a função de Wi-Fi não confiável falhe em ler o nome do Wi-Fi. Quer continuar mesmo assim?</string>
|
||||
<string name="paused">pausado</string>
|
||||
<string name="location_services_missing_message">A aplicação não detetou o serviço de localização ativado no seu dispositivo. Dependendo do dispositivo, isto pode causar que a função de Wi-Fi não confiável falhe em ler o nome do Wi-Fi. Quer continuar mesmo assim?</string>
|
||||
<string name="included">incluso</string>
|
||||
<string name="resume">Continuar</string>
|
||||
<string name="pause">Pausar</string>
|
||||
<string name="active">ativo</string>
|
||||
<string name="always_on_disabled">VPN sempre ligada tentou iniciar um túnel, mas este recurso está desligado nas configurações.</string>
|
||||
<string name="open_issue">Abrir um problema</string>
|
||||
<string name="tunneling_apps">Aplicativos em túnel</string>
|
||||
<string name="no_email_detected">Nenhum aplicativo de email detectado</string>
|
||||
<string name="no_browser_detected">Nenhum navegador detectado</string>
|
||||
<string name="logs_saved">Registros salvos em downloads</string>
|
||||
<string name="tunneling_apps">Aplicações com túnel</string>
|
||||
<string name="no_email_detected">Nenhuma aplicação de email detetado</string>
|
||||
<string name="no_browser_detected">Nenhum navegador detetado</string>
|
||||
<string name="incorrect_pin">O Pin está errado</string>
|
||||
<string name="auto">(automático)</string>
|
||||
<string name="disabled">desligado</string>
|
||||
<string name="read_logs">Ler os registros</string>
|
||||
<string name="config_parse_error">Falha na interpretação das configurações</string>
|
||||
<string name="read_logs">Ler os registos</string>
|
||||
<string name="pin_created">Pin criado com sucesso</string>
|
||||
<string name="auto_on">Continuar auto-túnel</string>
|
||||
<string name="create_pin">Criar um pin</string>
|
||||
<string name="enable_app_lock">Ligar trava de aplicativo</string>
|
||||
<string name="no_wifi_names_configured">Nenhum Wi-Fi configurado para este túnel</string>
|
||||
<string name="enable_app_lock">Ligar bloqueio de aplicação</string>
|
||||
<string name="edit_tunnel">Editar túnel</string>
|
||||
<string name="mobile_data_tunnel">Selecionar como túnel em dados móveis</string>
|
||||
<string name="set_primary_tunnel">Selecionar como túnel principal</string>
|
||||
<string name="general">Geral</string>
|
||||
<string name="userspace">Modo usuário</string>
|
||||
<string name="support">Suporte</string>
|
||||
<string name="kernel">Kernel</string>
|
||||
<string name="auto_off">Pausar auto-túnel</string>
|
||||
<string name="backend">Backend</string>
|
||||
<string name="settings">Configurações</string>
|
||||
<string name="unsure_how">se não tiver certeza em como continuar</string>
|
||||
<string name="see_the">Veja o</string>
|
||||
<string name="getting_started_guide">guia de início rápido</string>
|
||||
<string name="error_file_format">Formato de arquivo de configuração inválido</string>
|
||||
<string name="error_file_format">Formato de configuração inválido</string>
|
||||
<string name="vpn_channel_id">Canal de VPN</string>
|
||||
<string name="vpn_channel_name">Canal de notificações VPN</string>
|
||||
<string name="db_name">wg-tunnel-db</string>
|
||||
<string name="set_custom_ping_ip">Definir ip ping personalizado</string>
|
||||
<string name="vpn_denied_dialog_title">Permissão negada</string>
|
||||
<string name="vpn_settings">Configurações do sistema VPN</string>
|
||||
<string name="always_on_message">A permissão de conexão VPN foi negada. Por favor, verifique</string>
|
||||
<string name="chat_description">Junte-se à comunidade</string>
|
||||
<string name="tunnel_required">Característica requer pelo menos um túnel</string>
|
||||
<string name="app_settings">configurações da app</string>
|
||||
<string name="background_location_message2">para garantir que essas permissões estejam ativadas.</string>
|
||||
<string name="root_accepted">Shell root aceito</string>
|
||||
<string name="optional_default">"opcional, padrão: "</string>
|
||||
<string name="show_amnezia_properties">Mostrar propriedades de Amnezia</string>
|
||||
<string name="never">nunca</string>
|
||||
<string name="default_ping_ip">(opcional, padrão para pares)</string>
|
||||
<string name="set_custom_ping_internal">Intervalo de Ping (seg)</string>
|
||||
<string name="handshake">handshake</string>
|
||||
<string name="background_location_message">Permitir que toda a permissão de localização do tempo e/ou localização precisa é necessária para este recurso. Por favor, veja</string>
|
||||
<string name="sec">seg</string>
|
||||
</resources>
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="turn_off_tunnel">Действие требует отключения туннеля</string>
|
||||
<string name="add_tunnel">Добавить туннель</string>
|
||||
<string name="mtu">MTU</string>
|
||||
<string name="tunnel_name">Имя туннеля</string>
|
||||
<string name="public_key">Публичный ключ</string>
|
||||
@@ -9,10 +8,9 @@
|
||||
<string name="peer">Пир</string>
|
||||
<string name="privacy_policy">Посмотреть политику конфиденциальности</string>
|
||||
<string name="icon">Иконка</string>
|
||||
<string name="turn_on">Включить</string>
|
||||
<string name="add_from_qr">Добавить из QR</string>
|
||||
<string name="qr_scan">Сканировать QR</string>
|
||||
<string name="auto_tunneling">Авто-туннелирование</string>
|
||||
<string name="auto_tunneling">Автотуннелирование</string>
|
||||
<string name="no_tunnels">Туннели ещё не добавлены!</string>
|
||||
<string name="open_file">Открыть файл</string>
|
||||
<string name="exclude">Исключить</string>
|
||||
@@ -20,36 +18,26 @@
|
||||
<string name="tunnel_all">Туннель для всех приложений</string>
|
||||
<string name="config_changes_saved">Изменения конфигурации сохранены.</string>
|
||||
<string name="save_changes">Сохранить</string>
|
||||
<string name="no_thanks">Нет, спасибо</string>
|
||||
<string name="map">Карта</string>
|
||||
<string name="addresses">Адреса</string>
|
||||
<string name="dns_servers">DNS-серверы</string>
|
||||
<string name="allowed_ips">Разрешённые IP</string>
|
||||
<string name="endpoint">Конечная точка</string>
|
||||
<string name="restart">Перезапустить туннель</string>
|
||||
<string name="vpn_connection_failed">Ошибка соединения</string>
|
||||
<string name="always_on_vpn_support">Разрешить постоянный VPN</string>
|
||||
<string name="hint_search_packages">Поиск приложений</string>
|
||||
<string name="other">Другое</string>
|
||||
<string name="vpn_on">VPN вкл.</string>
|
||||
<string name="vpn_off">VPN откл.</string>
|
||||
<string name="interface_">Интерфейс</string>
|
||||
<string name="optional">(необязательно)</string>
|
||||
<string name="optional_no_recommend">(необязательно, не рекомендуется)</string>
|
||||
<string name="resume">Восстановить</string>
|
||||
<string name="tunneling_apps">Туннелируемые приложения</string>
|
||||
<string name="paused">приостановлено</string>
|
||||
<string name="active">активно</string>
|
||||
<string name="excluded">исключено</string>
|
||||
<string name="all">все</string>
|
||||
<string name="no_email_detected">Приложение для отправки почты не обнаружено</string>
|
||||
<string name="enter_pin">Введите свой PIN-код</string>
|
||||
<string name="enter_pin">Введите PIN-код</string>
|
||||
<string name="enable_app_lock">Включить блокировку приложения</string>
|
||||
<string name="restart_on_ping">Перезапуск при сбое пинга (бета)</string>
|
||||
<string name="settings">Настройки</string>
|
||||
<string name="support">Поддержка</string>
|
||||
<string name="backend">Модуль</string>
|
||||
<string name="use_amnezia">"Использовать модуль Amnezia режима пользователя "</string>
|
||||
<string name="init_packet_junk_size">Начальный размер «мусорного» пакета</string>
|
||||
<string name="response_packet_junk_size">Размер ответного «мусорного» пакета</string>
|
||||
<string name="init_packet_magic_header">Заголовок пакета инициализации</string>
|
||||
@@ -59,14 +47,10 @@
|
||||
<string name="cancel">Отмена</string>
|
||||
<string name="docs_description">Посмотреть документацию</string>
|
||||
<string name="email_chooser">Отправить письмо…</string>
|
||||
<string name="add_trusted_ssid">Добавить доверенное имя сети Wi-Fi</string>
|
||||
<string name="included">включено</string>
|
||||
<string name="vpn_starting">Идёт запуск VPN</string>
|
||||
<string name="auto_tunnel_title">Сервис авто-туннелирования</string>
|
||||
<string name="auto_tunnel_title">Сервис автотуннелирования</string>
|
||||
<string name="create_import">Создать с нуля</string>
|
||||
<string name="private_key">Закрытый ключ</string>
|
||||
<string name="pause">Приостановить</string>
|
||||
<string name="logs_saved">Журнал сохранён в Загрузки</string>
|
||||
<string name="db_name">wg-tunnel-db</string>
|
||||
<string name="base64_key">Ключ в base64</string>
|
||||
<string name="error_no_file_explorer">Не найден файловый менеджер</string>
|
||||
@@ -76,30 +60,16 @@
|
||||
<string name="tunnel_on_wifi">Туннель в недоверенных сетях Wi-Fi</string>
|
||||
<string name="error_file_extension">Файл не имеет расширение .conf или .zip</string>
|
||||
<string name="random">(случайно)</string>
|
||||
<string name="tunnel_start_text">Подключено к туннелю</string>
|
||||
<string name="app_name">WG Tunnel</string>
|
||||
<string name="vpn_channel_id">Канал VPN</string>
|
||||
<string name="vpn_channel_name">Канал уведомлений VPN</string>
|
||||
<string name="watcher_channel_id">Канал наблюдателя</string>
|
||||
<string name="watcher_channel_name">Канал уведомлений наблюдателя</string>
|
||||
<string name="watcher_notification_text_active">Отслеживание состояния сети: активно</string>
|
||||
<string name="notification_permission_required">Требуется разрешение на отображение уведомлений.</string>
|
||||
<string name="tunnels">Туннели</string>
|
||||
<string name="enable_auto_tunnel">Запустить авто-туннель</string>
|
||||
<string name="disable_auto_tunnel">Остановить авто-туннель</string>
|
||||
<string name="okay">Хорошо</string>
|
||||
<string name="okay">Хорошо</string>
|
||||
<string name="prominent_background_location_title">Фоновая передача местоположения</string>
|
||||
<string name="thank_you">Благодарим Вас за использование WG Tunnel!</string>
|
||||
<string name="thank_you">Благодарим вас за использование WG Tunnel!</string>
|
||||
<string name="trusted_ssid_value_description">Отправить SSID</string>
|
||||
<string name="trusted_ssid_empty_description">Введите SSID</string>
|
||||
<string name="add_tunnels_text">Добавить из файла или архива</string>
|
||||
<string name="location_services_not_detected">Сервисы местоположения не обнаружены</string>
|
||||
<string name="attempt_connection">Попытка соединения…</string>
|
||||
<string name="scanning_qr">Поиск QR-кода</string>
|
||||
<string name="none">Нет доверенных имён Wi-Fi</string>
|
||||
<string name="default_vpn_on">Основной VPN вкл.</string>
|
||||
<string name="default_vpn_off">Основной VPN выкл.</string>
|
||||
<string name="turn_off_auto">Необходимо приостановить или отключить авто-туннелирование</string>
|
||||
<string name="turn_on_tunnel">Действие требует наличие активного туннеля</string>
|
||||
<string name="add_peer">Добавить пира</string>
|
||||
<string name="rotate_keys">Обновить ключи</string>
|
||||
@@ -110,55 +80,34 @@
|
||||
<string name="persistent_keepalive">Поддержание работы туннеля (keepalive)</string>
|
||||
<string name="error_authentication_failed">Сбой аутентификации</string>
|
||||
<string name="export_configs">Экспорт конфигурации</string>
|
||||
<string name="location_services_required">Необходимо наличие служб местоположения</string>
|
||||
<string name="error_invalid_code">Некорректный QR-код</string>
|
||||
<string name="location_services_missing_message">Приложение не обнаружило сервис местоположения на вашем устройстве. На некоторых устройствах это может привести к невозможности определения имени сети Wi-Fi и сбою функции недоверенной сети. Всё равно продолжить?</string>
|
||||
<string name="error_ssid_exists">SSID уже существует</string>
|
||||
<string name="error_root_denied">Root-доступ запрещён</string>
|
||||
<string name="error_none">Нет ошибки</string>
|
||||
<string name="delete_tunnel">Удалить туннель</string>
|
||||
<string name="yes">Да</string>
|
||||
<string name="use_tunnel_on_wifi_name">Использовать туннель в сети Wi-Fi</string>
|
||||
<string name="disabled">отключено</string>
|
||||
<string name="auto_off">Приостановить авто-туннель</string>
|
||||
<string name="auto_tun_on">Восстановить авто-туннель</string>
|
||||
<string name="watcher_notification_text_paused">Отслеживание состояния сети: приостановлено</string>
|
||||
<string name="tunnel_start_title">VPN подключен</string>
|
||||
<string name="prominent_background_location_message">Эта функция требует фоновый доступ к местоположению для отслеживания имён сетей Wi-Fi, даже когда приложение закрыто. Для получения дополнительной информации, прочтите политику конфиденциальности на экране поддержки.</string>
|
||||
<string name="done">Готово</string>
|
||||
<string name="copy_public_key">Копировать открытый ключ</string>
|
||||
<string name="error_authorization_failed">Не удалось пройти аутентификацию</string>
|
||||
<string name="enabled_app_shortcuts">Включить ярлыки приложения</string>
|
||||
<string name="export_configs_failed">Не удалось экспортировать конфигурацию</string>
|
||||
<string name="precise_location_required">Необходимо точное местоположение</string>
|
||||
<string name="open_issue">Сообщить о проблеме</string>
|
||||
<string name="config_parse_error">Не удалось разобрать файл конфигурации</string>
|
||||
<string name="incorrect_pin">Некорректный PIN-код</string>
|
||||
<string name="pin_created">PIN создан успешно</string>
|
||||
<string name="pin_created">PIN-код создан успешно</string>
|
||||
<string name="create_pin">Создать PIN-код</string>
|
||||
<string name="mobile_data_tunnel">Назначить как туннель для мобильного интернета</string>
|
||||
<string name="edit_tunnel">Редактировать туннель</string>
|
||||
<string name="auto_on">Восстановить авто-туннель</string>
|
||||
<string name="auto_tun_off">Приостановить авто-туннель</string>
|
||||
<string name="junk_packet_count">Количество «мусорных» пакетов</string>
|
||||
<string name="junk_packet_maximum_size">Максимальный размер «мусорного» пакета</string>
|
||||
<string name="error_file_format">некорректный формат конфигурации туннеля</string>
|
||||
<string name="background_location_required">Необходим фоновый доступ к местоположению</string>
|
||||
<string name="exported_configs_message">Экспорт конфигурации в Загрузки</string>
|
||||
<string name="email_subject">Поддержка WG Tunnel</string>
|
||||
<string name="go">вперёд</string>
|
||||
<string name="email_description">Отправить письмо автору</string>
|
||||
<string name="support_help_text">Если у Вас возникли проблемы, или появилась идея по улучшению, или Вы просто хотите пообщаться, используйте следующие ресурсы:</string>
|
||||
<string name="use_kernel">Использовать модуль режима ядра</string>
|
||||
<string name="always_on_disabled">Функция всегда доступного VPN попыталась запустить туннель, но эта функция отключена в настройках.</string>
|
||||
<string name="no_browser_detected">Веб-браузер не обнаружен</string>
|
||||
<string name="no_browser_detected">Браузер не обнаружен</string>
|
||||
<string name="read_logs">Посмотреть журнал</string>
|
||||
<string name="auto">(авто)</string>
|
||||
<string name="set_primary_tunnel">Назначить как главный туннель</string>
|
||||
<string name="no_wifi_names_configured">Имена сетей Wi-Fi не назначены этому туннелю</string>
|
||||
<string name="general">Общее</string>
|
||||
<string name="mode">Режим</string>
|
||||
<string name="userspace">Пользователя</string>
|
||||
<string name="kernel">Модуль ядра</string>
|
||||
<string name="junk_packet_minimum_size">Минимальный размер «мусорного» пакета</string>
|
||||
<string name="response_packet_magic_header">Заголовок пакета ответа</string>
|
||||
@@ -166,4 +115,58 @@
|
||||
<string name="getting_started_guide">руководство по началу работы</string>
|
||||
<string name="unsure_how">, если не уверены, что делать дальше</string>
|
||||
<string name="underload_packet_magic_header">Заголовок пакета под нагрузкой</string>
|
||||
<string name="root_accepted">Root-доступ разрешён</string>
|
||||
<string name="always_on_message">Разрешение на VPN-соединение было отклонено, проверьте</string>
|
||||
<string name="set_custom_ping_cooldown">Время ожидания перезапуска пинга (сек.)</string>
|
||||
<string name="tunnel_required">Для этой функции необходим хотя бы один туннель</string>
|
||||
<string name="restart_at_boot">Перезапуск при загрузке</string>
|
||||
<string name="default_ping_ip">(необязательно, по умолчанию для пиров)</string>
|
||||
<string name="app_settings">настройки приложения</string>
|
||||
<string name="background_location_message2">, чтобы убедиться, что эти разрешения предоставлены.</string>
|
||||
<string name="always_on_message2">, чтобы убедиться, что функция «Постоянный VPN» отключена для всех других приложений, и повторите попытку</string>
|
||||
<string name="background_location_message">Разрешать всё время, пока для работы этой функции требуется доступ на определение местоположения и/или точное местоположение. Смотрите</string>
|
||||
<string name="vpn_settings">Системные настройки VPN</string>
|
||||
<string name="chat_description">Присоединиться к сообществу</string>
|
||||
<string name="vpn_denied_dialog_title">Нет разрешения</string>
|
||||
<string name="set_custom_ping_internal">Интервал пинга (сек.)</string>
|
||||
<string name="optional_default">"необязательно, по умолчанию: "</string>
|
||||
<string name="set_custom_ping_ip">Назначить свой IP для пинга</string>
|
||||
<string name="show_amnezia_properties">Показать настройки Amnezia</string>
|
||||
<string name="never">никогда</string>
|
||||
<string name="sec">сек.</string>
|
||||
<string name="handshake">рукопожатие</string>
|
||||
<string name="logs">Журналы</string>
|
||||
<string name="light">Светлая</string>
|
||||
<string name="automatic">Автоматически</string>
|
||||
<string name="dynamic">Динамическая</string>
|
||||
<string name="language">Язык</string>
|
||||
<string name="trusted_wifi_names">Доверенные сети Wi-Fi</string>
|
||||
<string name="primary_tunnel">Основной туннель</string>
|
||||
<string name="mobile_tunnel">Туннель для мобильных данных</string>
|
||||
<string name="learn_more">Узнать больше</string>
|
||||
<string name="kernel_not_supported">Ядро не поддерживается</string>
|
||||
<string name="requires_app_relaunch">Данное изменение требует перезапуска приложения. Продолжить?</string>
|
||||
<string name="stop_auto">Остановить автотуннель</string>
|
||||
<string name="donate">Пожертвовать на проект</string>
|
||||
<string name="local_logging">Локальное ведение журнала</string>
|
||||
<string name="wildcards_active">Подстановочные знаки используются</string>
|
||||
<string name="configuration_change">Изменение конфигурации</string>
|
||||
<string name="skip">Пропустить</string>
|
||||
<string name="use_wildcards">Использовать подстановочные знаки в имени</string>
|
||||
<string name="appearance">Внешний вид</string>
|
||||
<string name="notifications">Уведомления</string>
|
||||
<string name="kill_switch">Экстренный разрыв соединения</string>
|
||||
<string name="dark">Тёмная</string>
|
||||
<string name="display_theme">Тема</string>
|
||||
<string name="selected">Выбрано</string>
|
||||
<string name="add_wifi_name">Добавить сеть Wi-Fi</string>
|
||||
<string name="launch_app_settings">Настройки запуска приложения</string>
|
||||
<string name="on_demand_rules">Правила туннеля по запросу</string>
|
||||
<string name="use_root_shell_for_wifi">Использовать root-доступ для получения имени сети Wi-Fi</string>
|
||||
<string name="wifi_name_via_shell">Имя Wi-Fi через root</string>
|
||||
<string name="start_auto">Запустить автотуннель</string>
|
||||
<string name="tunnel_running">Туннель работает</string>
|
||||
<string name="monitoring_state_changes">Отслеживание изменений состояния</string>
|
||||
<string name="enable_local_logging">Включить ведение журнала</string>
|
||||
<string name="add_from_clipboard">Добавить из буфера обмена</string>
|
||||
</resources>
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources></resources>
|
||||
@@ -1,49 +1,34 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="app_name">WG Tunnel</string>
|
||||
<string name="vpn_channel_id">VPN Kanalı</string>
|
||||
<string name="vpn_channel_name">VPN Bildirim Kanalı</string>
|
||||
<string name="watcher_channel_id">İzleyici Kanalı</string>
|
||||
<string name="watcher_channel_name">İzleyici Bildirim Kanalı</string>
|
||||
<string name="github_url" translatable="false">https://github.com/zaneschepke/wgtunnel/issues</string>
|
||||
<string name="docs_url" translatable="false">https://zaneschepke.com/wgtunnel-docs/overview.html</string>
|
||||
<string name="privacy_policy_url" translatable="false">https://zaneschepke.com/wgtunnel-docs/privacypolicy.html</string>
|
||||
<string name="error_file_extension">Dosya .conf veya .zip değil</string>
|
||||
<string name="turn_off_tunnel">İşlem için tünelin kapalı olması gerekiyor</string>
|
||||
<string name="no_tunnels">Henüz tünel eklenmedi!</string>
|
||||
<string name="discord_url" translatable="false">https://discord.gg/rbRRNh6H7V</string>
|
||||
<string name="watcher_notification_text_active">Ağ durum değişiklikleri izleniyor: aktif</string>
|
||||
<string name="watcher_notification_text_paused">Ağ durum değişiklikleri izleniyor: duraklatıldı</string>
|
||||
<string name="tunnel_start_title">VPN bağlandı</string>
|
||||
<string name="tunnel_start_text">Tünele bağlandı</string>
|
||||
<string name="notification_permission_required">Bildirim izni gerekli.</string>
|
||||
<string name="add_trusted_ssid">Güvenilir wifi adı ekle</string>
|
||||
<string name="tunnels">Tüneller</string>
|
||||
<string name="enable_auto_tunnel">Otomatik tünellemeyi başlat</string>
|
||||
<string name="disable_auto_tunnel">Otomatik tünellemeyi durdur</string>
|
||||
<string name="tunnel_mobile_data">Mobil veride tünel</string>
|
||||
<string name="privacy_policy">Gizlilik Politikasını Görüntüle</string>
|
||||
<string name="privacy_policy">Gizlilik Politikasını Görüntüle</string>
|
||||
<string name="okay">Tamam</string>
|
||||
<string name="tunnel_on_ethernet">Ethernet\'te tünel</string>
|
||||
<string name="prominent_background_location_message">Bu özellik, uygulama kapalıyken bile Wi-Fi SSID izlemesini etkinleştirmek için arka plan konum iznine ihtiyaç duyar. Daha fazla ayrıntı için lütfen Destek ekranında bağlantısı verilen Gizlilik Politikasına bakın.</string>
|
||||
<string name="prominent_background_location_title">Arka Plan Konum Açıklaması</string>
|
||||
<string name="thank_you">WG Tunnel\'ı kullandığınız için teşekkürler!</string>
|
||||
<string name="trusted_ssid_empty_description">SSID girin</string>
|
||||
<string name="trusted_ssid_value_description">SSID\'yi gönder</string>
|
||||
<string name="add_tunnels_text">Dosyadan veya zip\'ten ekle</string>
|
||||
<string name="open_file">Dosya Aç</string>
|
||||
<string name="add_from_qr">QR kodundan ekle</string>
|
||||
<string name="qr_scan">QR Tarama</string>
|
||||
<string name="tunnel_name">Tünel Adı</string>
|
||||
<string name="add_tunnel">Tünel Ekle</string>
|
||||
<string name="exclude">Hariç tut</string>
|
||||
<string name="include">Dahil et</string>
|
||||
<string name="tunnel_all">Tüm uygulamaları tünelle</string>
|
||||
<string name="config_changes_saved">Yapılandırma değişiklikleri kaydedildi.</string>
|
||||
<string name="save_changes">Kaydet</string>
|
||||
<string name="icon">Simge</string>
|
||||
<string name="no_thanks">Hayır, teşekkürler</string>
|
||||
<string name="turn_on">Aç</string>
|
||||
<string name="map">Harita</string>
|
||||
<string name="public_key">Genel anahtar</string>
|
||||
<string name="addresses">Adresler</string>
|
||||
<string name="dns_servers">DNS sunucuları</string>
|
||||
@@ -52,24 +37,14 @@
|
||||
<string name="allowed_ips">İzin verilen IP\'ler</string>
|
||||
<string name="endpoint">Uç nokta (endpoint)</string>
|
||||
<string name="name">Ad</string>
|
||||
<string name="restart">Tüneli Yeniden Başlat</string>
|
||||
<string name="vpn_connection_failed">Bağlantı başarısız</string>
|
||||
<string name="always_on_vpn_support">Her Zaman Açık VPN\'e İzin Ver</string>
|
||||
<string name="location_services_not_detected">Konum Hizmetleri Algılanmadı</string>
|
||||
<string name="hint_search_packages">Paketlerde ara</string>
|
||||
<string name="attempt_connection">Bağlantı deneniyor..</string>
|
||||
<string name="vpn_starting">VPN başlatılıyor</string>
|
||||
<string name="db_name">wg-tunnel-db</string>
|
||||
<string name="scanning_qr">QR için taranıyor</string>
|
||||
<string name="none">Güvenilir wifi adı yok</string>
|
||||
<string name="other">Diğer</string>
|
||||
<string name="auto_tunneling">Otomatik tünelleme</string>
|
||||
<string name="vpn_on">VPN açık</string>
|
||||
<string name="vpn_off">VPN kapalı</string>
|
||||
<string name="default_vpn_on">Birincil VPN açık</string>
|
||||
<string name="default_vpn_off">Birincil VPN kapalı</string>
|
||||
<string name="create_import">Sıfırdan oluştur</string>
|
||||
<string name="turn_off_auto">İşlem için otomatik tünelin devre dışı veya duraklatılmış olması gerekiyor</string>
|
||||
<string name="turn_on_tunnel">İşlem için aktif tünel gerekiyor</string>
|
||||
<string name="add_peer">Eş ekle</string>
|
||||
<string name="done">Tamam</string>
|
||||
@@ -91,47 +66,32 @@
|
||||
<string name="error_authorization_failed">Yetkilendirme başarısız oldu</string>
|
||||
<string name="enabled_app_shortcuts">Uygulama kısayollarını etkinleştir</string>
|
||||
<string name="export_configs">Yapılandırmaları dışa aktar</string>
|
||||
<string name="export_configs_failed">Yapılandırmaları dışa aktarma başarısız oldu</string>
|
||||
<string name="location_services_required">Konum hizmetleri gerekli</string>
|
||||
<string name="background_location_required">Arka plan konumu gerekli</string>
|
||||
<string name="precise_location_required">Hassas konum gerekli</string>
|
||||
<string name="unknown_error">Bilinmeyen bir hata oluştu</string>
|
||||
<string name="exported_configs_message">Yapılandırmalar indirilenler klasörüne aktarıldı</string>
|
||||
<string name="tunnel_on_wifi">Güvenilmeyen wifi\'da tünel</string>
|
||||
<string name="my_email" translatable="false">support@zaneschepke.com</string>
|
||||
<string name="email_subject">WG Tunnel Desteği</string>
|
||||
<string name="email_chooser">E-posta gönder…</string>
|
||||
<string name="go">git</string>
|
||||
<string name="docs_description">Belgeleri oku</string>
|
||||
<string name="email_description">Bana e-posta gönder</string>
|
||||
<string name="support_help_text">Sorun yaşıyorsanız, iyileştirme fikirleriniz varsa veya sadece iletişime geçmek istiyorsanız, aşağıdaki kaynaklar mevcuttur:</string>
|
||||
<string name="use_kernel">Kernel modülünü kullan</string>
|
||||
<string name="error_ssid_exists">SSID zaten mevcut</string>
|
||||
<string name="error_root_denied">Root kabuğu reddedildi</string>
|
||||
<string name="error_no_file_explorer">Dosya gezgini yüklü değil</string>
|
||||
<string name="error_invalid_code">Geçersiz QR kodu</string>
|
||||
<string name="error_none">Hata yok</string>
|
||||
<string name="location_services_missing_message">Uygulama, cihazınızda etkinleştirilmiş herhangi bir konum hizmeti algılamıyor. Cihaza bağlı olarak, bu durum güvenilmeyen wifi özelliğinin wifi adını okumasını engelleyebilir. Yine de devam etmek istiyor musunuz?</string>
|
||||
<string name="auto_tunnel_title">Otomatik Tünel Hizmeti</string>
|
||||
<string name="delete_tunnel">Tüneli sil</string>
|
||||
<string name="delete_tunnel_message">Bu tüneli silmek istediğinizden emin misiniz?</string>
|
||||
<string name="yes">Evet</string>
|
||||
<string name="resume">Devam et</string>
|
||||
<string name="pause">Duraklat</string>
|
||||
<string name="paused">duraklatıldı</string>
|
||||
<string name="active">aktif</string>
|
||||
<string name="tunneling_apps">Tünellenen uygulamalar</string>
|
||||
<string name="included">dahil</string>
|
||||
<string name="excluded">hariç</string>
|
||||
<string name="all">tümü</string>
|
||||
<string name="always_on_disabled">Her Zaman Açık VPN bir tüneli başlatmaya çalıştı, ancak bu özellik ayarlarda devre dışı bırakılmış.</string>
|
||||
<string name="no_email_detected">E-posta uygulaması algılanmadı</string>
|
||||
<string name="no_browser_detected">Tarayıcı algılanmadı</string>
|
||||
<string name="logs_saved">Günlükler indirilenler klasörüne kaydedildi</string>
|
||||
<string name="open_issue">Sorun bildir</string>
|
||||
<string name="read_logs">Günlükleri oku</string>
|
||||
<string name="auto">(otomatik)</string>
|
||||
<string name="config_parse_error">Yapılandırma ayrıştırılamadı</string>
|
||||
<string name="incorrect_pin">PIN yanlış</string>
|
||||
<string name="pin_created">PIN başarıyla oluşturuldu</string>
|
||||
<string name="enter_pin">PIN\'inizi girin</string>
|
||||
@@ -141,22 +101,11 @@
|
||||
<string name="mobile_data_tunnel">Mobil veri tüneli olarak ayarla</string>
|
||||
<string name="set_primary_tunnel">Birincil tünel olarak ayarla</string>
|
||||
<string name="use_tunnel_on_wifi_name">Wifi adında tünel kullan</string>
|
||||
<string name="no_wifi_names_configured">Bu tünel için yapılandırılmış wifi adı yok</string>
|
||||
<string name="general">Genel</string>
|
||||
<string name="edit_tunnel">Tüneli düzenle</string>
|
||||
<string name="disabled">devre dışı</string>
|
||||
<string name="auto_on">Otomatik tüneli devam ettir</string>
|
||||
<string name="auto_off">Otomatik tüneli duraklat</string>
|
||||
<string name="auto_tun_on">Otomatik tüneli devam ettir</string>
|
||||
<string name="auto_tun_off">Otomatik tüneli duraklat</string>
|
||||
<string name="version">Sürüm</string>
|
||||
<string name="mode">Mod</string>
|
||||
<string name="userspace">Kullanıcı alanı</string>
|
||||
<string name="settings">Ayarlar</string>
|
||||
<string name="support">Destek</string>
|
||||
<string name="backend">Arka uç</string>
|
||||
<string name="kernel">Çekirdek</string>
|
||||
<string name="use_amnezia">"Amnezia kullanıcı alanını kullan "</string>
|
||||
<string name="junk_packet_count">Gereksiz paket sayısı</string>
|
||||
<string name="junk_packet_minimum_size">Gereksiz paket minimum boyutu</string>
|
||||
<string name="junk_packet_maximum_size">Gereksiz paket maksimum boyutu</string>
|
||||
@@ -171,8 +120,6 @@
|
||||
<string name="see_the">Bakın:</string>
|
||||
<string name="getting_started_url" translatable="false">https://zaneschepke.com/wgtunnel-docs/getting-started.html</string>
|
||||
<string name="getting_started_guide">başlangıç kılavuzu</string>
|
||||
<string name="amnezia" translatable="false">Amnezia</string>
|
||||
<string name="wireguard" translatable="false">WireGuard</string>
|
||||
<string name="error_file_format">Geçersiz tünel yapılandırma formatı</string>
|
||||
<string name="restart_at_boot">Önyüklemede yeniden başlat</string>
|
||||
</resources>
|
||||
|
||||
@@ -3,23 +3,17 @@
|
||||
<string name="comma_separated_list">список розділений комами</string>
|
||||
<string name="no_tunnels">Тунелі ще не додані!</string>
|
||||
<string name="app_name">WG Tunnel</string>
|
||||
<string name="add_trusted_ssid">Додати ім\'я довіреної мережі Wi-Fi</string>
|
||||
<string name="tunnels">Тунелі</string>
|
||||
<string name="enable_auto_tunnel">Запустити авто-тунелі</string>
|
||||
<string name="disable_auto_tunnel">Зупинити авто-тунелі</string>
|
||||
<string name="okay">ОК</string>
|
||||
<string name="tunnel_on_ethernet">Тунелювати Ethernet</string>
|
||||
<string name="prominent_background_location_title">Фонова передача місцезнаходження</string>
|
||||
<string name="trusted_ssid_value_description">Підтвердити SSID</string>
|
||||
<string name="qr_scan">Сканувати QR</string>
|
||||
<string name="tunnel_name">Ім\'я тунелю</string>
|
||||
<string name="add_tunnel">Додати тунель</string>
|
||||
<string name="include">Включити</string>
|
||||
<string name="tunnel_all">Тунель для всіх додатків</string>
|
||||
<string name="config_changes_saved">Зміни налаштувань збережено.</string>
|
||||
<string name="icon">Іконка</string>
|
||||
<string name="no_thanks">Ні, дякую</string>
|
||||
<string name="default_vpn_off">Основний VPN вимк.</string>
|
||||
<string name="turn_on_tunnel">Дія потребує активного тунелю</string>
|
||||
<string name="rotate_keys">Оновити ключі</string>
|
||||
<string name="private_key">Закритий ключ</string>
|
||||
@@ -28,45 +22,28 @@
|
||||
<string name="optional">(необов\'язково)</string>
|
||||
<string name="optional_no_recommend">(необов\'язково, не рекомендується)</string>
|
||||
<string name="cancel">Скасувати</string>
|
||||
<string name="export_configs_failed">Помилка експорту конфігурації</string>
|
||||
<string name="location_services_required">Необхідно сервіси місцезнаходження</string>
|
||||
<string name="precise_location_required">Необхідно доступ до точного місцезнаходження</string>
|
||||
<string name="exported_configs_message">Експорт конфігурації в Завантаження</string>
|
||||
<string name="email_chooser">Надіслати E-Mail…</string>
|
||||
<string name="error_root_denied">Root доступ заборонено</string>
|
||||
<string name="error_invalid_code">Некоректний QR-код</string>
|
||||
<string name="error_none">Нема помилок</string>
|
||||
<string name="logs_saved">Логи збережено в Завантаженнях</string>
|
||||
<string name="config_parse_error">Помилка аналізу файлу конфігурації</string>
|
||||
<string name="incorrect_pin">Невірний PIN-код</string>
|
||||
<string name="use_tunnel_on_wifi_name">Використовувати тунель в мережі Wi-Fi</string>
|
||||
<string name="disabled">відключено</string>
|
||||
<string name="version">Версія</string>
|
||||
<string name="mode">Режим</string>
|
||||
<string name="transport_packet_magic_header">Заголовок транспортного пакету</string>
|
||||
<string name="getting_started_guide">інструкція щодо початку роботи</string>
|
||||
<string name="error_file_format">некоректний формат конфігурації тунелю</string>
|
||||
<string name="watcher_notification_text_active">Моніторинг стану мережі: активний</string>
|
||||
<string name="vpn_channel_name">Канал сповіщення VPN</string>
|
||||
<string name="vpn_channel_id">Канал VPN</string>
|
||||
<string name="error_file_extension">Файл не є .conf або .zip файлом</string>
|
||||
<string name="turn_off_tunnel">Дія потребує вимкнення тунелю</string>
|
||||
<string name="watcher_notification_text_paused">Моніторинг стану мережі: призупинено</string>
|
||||
<string name="tunnel_start_text">Підключення до тунелю</string>
|
||||
<string name="tunnel_start_title">VPN підключено</string>
|
||||
<string name="notification_permission_required">Потрібен дозвіл на відображення сповіщень.</string>
|
||||
<string name="tunnel_mobile_data">Тунелювати мобільні дані</string>
|
||||
<string name="privacy_policy">Переглянути політику конфіденційності</string>
|
||||
<string name="privacy_policy">Переглянути політику конфіденційності</string>
|
||||
<string name="thank_you">Спасибі за використання WG Tunnel!</string>
|
||||
<string name="prominent_background_location_message">Дана функція потребує фоновий доступ до служби місцезнаходження для моніторингу назви мереж Wi-Fi навіть коли додаток закрито. Для отримання додаткової інформації прочитайте політику приватності на екрані Підтримки.</string>
|
||||
<string name="trusted_ssid_empty_description">Введіть SSID</string>
|
||||
<string name="add_tunnels_text">Додати з файлу або архіву</string>
|
||||
<string name="open_file">Відкрити файл</string>
|
||||
<string name="exclude">Виключити</string>
|
||||
<string name="add_from_qr">Додати з QR коду</string>
|
||||
<string name="save_changes">Зберегти</string>
|
||||
<string name="turn_on">Увімкнути</string>
|
||||
<string name="map">Карта</string>
|
||||
<string name="public_key">Публічний ключ</string>
|
||||
<string name="addresses">Адреса</string>
|
||||
<string name="allowed_ips">Дозволені IP</string>
|
||||
@@ -76,22 +53,13 @@
|
||||
<string name="endpoint">Кінцева точка</string>
|
||||
<string name="hint_search_packages">Пошук програм</string>
|
||||
<string name="name">Ім\'я</string>
|
||||
<string name="vpn_connection_failed">Помилка з\'єднання</string>
|
||||
<string name="restart">Перезапустити тунель</string>
|
||||
<string name="always_on_vpn_support">Дозволили Always-ON VPN</string>
|
||||
<string name="location_services_not_detected">Сервіси місце знаходження не знайдено</string>
|
||||
<string name="db_name">wg-tunnel-db</string>
|
||||
<string name="scanning_qr">Сканування QR коду</string>
|
||||
<string name="attempt_connection">Спроба з\'єднання...</string>
|
||||
<string name="other">Інше</string>
|
||||
<string name="vpn_starting">Запуск VPN</string>
|
||||
<string name="auto_tunneling">Авто-тунелювання</string>
|
||||
<string name="none">Нема довірених мереж Wi-Fi</string>
|
||||
<string name="vpn_on">VPN увімк.</string>
|
||||
<string name="vpn_off">VPN вимк.</string>
|
||||
<string name="default_vpn_on">Основний VPN увімк.</string>
|
||||
<string name="create_import">Створити з нуля</string>
|
||||
<string name="turn_off_auto">Необхідно вимкнути або призупинити авто-тунелювання</string>
|
||||
<string name="add_peer">Додати peer</string>
|
||||
<string name="done">Готово</string>
|
||||
<string name="interface_">Інтерфейс</string>
|
||||
@@ -104,14 +72,11 @@
|
||||
<string name="enabled_app_shortcuts">Дозволити ярлики</string>
|
||||
<string name="error_authentication_failed">Помилка автентифікації</string>
|
||||
<string name="export_configs">Експорт конфігурації</string>
|
||||
<string name="background_location_required">Необхідний фоновий доступ до місцезнаходження</string>
|
||||
<string name="unknown_error">Невідома помилка</string>
|
||||
<string name="tunnel_on_wifi">Тунелювати недовірені мережі Wi-Fi</string>
|
||||
<string name="email_subject">Підтримка WG-Tunnel</string>
|
||||
<string name="go">вперед</string>
|
||||
<string name="docs_description">Переглянути документацію</string>
|
||||
<string name="email_description">Відправити email автору</string>
|
||||
<string name="support_help_text">Якщо у вас виникли проблеми, є ідеї щодо покращення, чи бажання долучитися, скористайтесь наступними ресурсами:</string>
|
||||
<string name="use_kernel">Використовувати модуль режиму ядра</string>
|
||||
<string name="error_ssid_exists">SSID вже існує</string>
|
||||
<string name="error_no_file_explorer">Не знайдено файловий менеджер</string>
|
||||
@@ -121,13 +86,8 @@
|
||||
<string name="included">включено</string>
|
||||
<string name="delete_tunnel_message">Ви дійсно хочете видалити цей тунель?</string>
|
||||
<string name="yes">Так</string>
|
||||
<string name="active">активно</string>
|
||||
<string name="resume">Відновити</string>
|
||||
<string name="pause">Призупинити</string>
|
||||
<string name="paused">призупинено</string>
|
||||
<string name="tunneling_apps">Тунельовані додатки</string>
|
||||
<string name="excluded">виключено</string>
|
||||
<string name="always_on_disabled">Функція Always-on VPN спробувала запустити тунель, але функція вимкнена в налаштуваннях.</string>
|
||||
<string name="auto">(авто)</string>
|
||||
<string name="all">всі</string>
|
||||
<string name="no_email_detected">Програми для надсилання email не знайдено</string>
|
||||
@@ -139,20 +99,11 @@
|
||||
<string name="create_pin">Створити PINhкод</string>
|
||||
<string name="enable_app_lock">Увімкнути блокування додатку</string>
|
||||
<string name="edit_tunnel">Редагувати тунель</string>
|
||||
<string name="auto_on">Відновити авто-тунель</string>
|
||||
<string name="restart_on_ping">Перезапуск при помилці ping (бета)</string>
|
||||
<string name="mobile_data_tunnel">Встановити як тунель для мобільних даних</string>
|
||||
<string name="set_primary_tunnel">Встановити як основний тунель</string>
|
||||
<string name="no_wifi_names_configured">Імена мереж Wi-Fi не налаштовано для цього тунелю</string>
|
||||
<string name="general">Загальне</string>
|
||||
<string name="auto_tun_off">Призупинити авто-тунель</string>
|
||||
<string name="auto_off">Призупинити авто-тунель</string>
|
||||
<string name="auto_tun_on">Відновити авто-тунель</string>
|
||||
<string name="userspace">Користувача</string>
|
||||
<string name="support">Підтримка</string>
|
||||
<string name="settings">Налаштування</string>
|
||||
<string name="use_amnezia">"Використовувати модуль Amnezia режиму користувача "</string>
|
||||
<string name="backend">Модуль</string>
|
||||
<string name="kernel">Модуль ядра</string>
|
||||
<string name="junk_packet_count">Кількість «сміттєвих» пакетів</string>
|
||||
<string name="junk_packet_minimum_size">Мінімальний розмір «сміттєвого» пакету</string>
|
||||
@@ -164,4 +115,4 @@
|
||||
<string name="response_packet_magic_header">Заголовок пакету відповіді</string>
|
||||
<string name="unsure_how">, якщо не впевнені що робити далі</string>
|
||||
<string name="see_the">Дивіться</string>
|
||||
</resources>
|
||||
</resources>
|
||||
@@ -1,2 +1,172 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources></resources>
|
||||
<resources>
|
||||
<string name="turn_off_tunnel">请关闭连接再操作</string>
|
||||
<string name="no_tunnels">你还没有添加隧道!</string>
|
||||
<string name="tunnels">连接列表</string>
|
||||
<string name="privacy_policy">查看隐私政策</string>
|
||||
<string name="tunnel_mobile_data">允许隧道使用手机数据流量</string>
|
||||
<string name="okay">好</string>
|
||||
<string name="tunnel_on_ethernet">在局域网中使用隧道</string>
|
||||
<string name="error_file_extension">文件类型不是 .conf 或 .zip</string>
|
||||
<string name="trusted_ssid_value_description">确认修改 SSID</string>
|
||||
<string name="add_tunnels_text">从文件或 zip 添加</string>
|
||||
<string name="add_from_qr">从二维码添加</string>
|
||||
<string name="qr_scan">扫描二维码</string>
|
||||
<string name="tunnel_name">隧道名称</string>
|
||||
<string name="include">包含</string>
|
||||
<string name="tunnel_all">全局代理</string>
|
||||
<string name="icon">图标</string>
|
||||
<string name="public_key">公钥</string>
|
||||
<string name="addresses">地址</string>
|
||||
<string name="dns_servers">DNS 服务器</string>
|
||||
<string name="mtu">MTU</string>
|
||||
<string name="allowed_ips">允许的 IP</string>
|
||||
<string name="name">名称</string>
|
||||
<string name="peer">节点</string>
|
||||
<string name="always_on_vpn_support">允许VPN始终在线</string>
|
||||
<string name="location_services_not_detected">定位服务未开启</string>
|
||||
<string name="hint_search_packages">查找软件包</string>
|
||||
<string name="db_name">wg-tunnel 数据库</string>
|
||||
<string name="done">完成</string>
|
||||
<string name="rotate_keys">轮换秘钥</string>
|
||||
<string name="create_import">手动创建</string>
|
||||
<string name="private_key">私钥</string>
|
||||
<string name="listen_port">监听端口</string>
|
||||
<string name="optional">(可选)</string>
|
||||
<string name="optional_no_recommend">(可选,不建议)</string>
|
||||
<string name="error_authorization_failed">验证失败</string>
|
||||
<string name="enabled_app_shortcuts">创建快捷方式到桌面</string>
|
||||
<string name="export_configs">导出设置</string>
|
||||
<string name="docs_description">阅读文档</string>
|
||||
<string name="email_description">给作者发邮件</string>
|
||||
<string name="error_root_denied">root 权限未开启</string>
|
||||
<string name="error_no_file_explorer">没有安装任何的文件管理器</string>
|
||||
<string name="error_invalid_code">无效的二维码</string>
|
||||
<string name="copy_public_key">复制公钥</string>
|
||||
<string name="email_chooser">发送邮件…</string>
|
||||
<string name="persistent_keepalive">连接保活</string>
|
||||
<string name="turn_on_tunnel">此操作需要一个已建立的隧道</string>
|
||||
<string name="tunnel_on_wifi">在不受信任的 WiFi 上建立隧道</string>
|
||||
<string name="exclude">排除</string>
|
||||
<string name="comma_separated_list">逗号分隔列表</string>
|
||||
<string name="base64_key">Base64 编码</string>
|
||||
<string name="use_kernel">使用内核模块</string>
|
||||
<string name="endpoint">端点</string>
|
||||
<string name="thank_you">谢谢使用 WG Tunnel!</string>
|
||||
<string name="prominent_background_location_message">此功能是在应用关闭时,后台自动扫描 Wi-Fi SSID,需要开启后台位置信息访问权限。更多信息,请在支持页面查看隐私政策。</string>
|
||||
<string name="vpn_on">VPN 已连接</string>
|
||||
<string name="vpn_off">VPN 已关闭</string>
|
||||
<string name="auto_tunneling">自动建立隧道</string>
|
||||
<string name="add_peer">添加节点</string>
|
||||
<string name="random">(随机)</string>
|
||||
<string name="seconds">秒</string>
|
||||
<string name="cancel">取消</string>
|
||||
<string name="preshared_key">预共享密钥</string>
|
||||
<string name="error_authentication_failed">身份验证失败</string>
|
||||
<string name="unknown_error">发生了未知错误</string>
|
||||
<string name="error_ssid_exists">SSID 已经存在</string>
|
||||
<string name="open_file">打开文件</string>
|
||||
<string name="config_changes_saved">设置已保存。</string>
|
||||
<string name="save_changes">保存</string>
|
||||
<string name="interface_">接口</string>
|
||||
<string name="email_subject">WG Tunnel 支持</string>
|
||||
<string name="auto_tunnel_title">自动连接服务</string>
|
||||
<string name="delete_tunnel">删除隧道</string>
|
||||
<string name="delete_tunnel_message">确定删除这个隧道吗?</string>
|
||||
<string name="location_services_missing_message">此应用不会在你的设备上检测任何已开启的定位服务。根据不同的设备,可能会导致无法获得不可信 WiFi 的名称。你想继续吗?</string>
|
||||
<string name="yes">是</string>
|
||||
<string name="tunneling_apps">正使用隧道的应用</string>
|
||||
<string name="included">已包含</string>
|
||||
<string name="excluded">排除</string>
|
||||
<string name="all">全部</string>
|
||||
<string name="no_email_detected">未安装邮件应用</string>
|
||||
<string name="enable_app_lock">锁定应用</string>
|
||||
<string name="use_tunnel_on_wifi_name">在指定的wifi上使用此隧道</string>
|
||||
<string name="version">版本</string>
|
||||
<string name="settings">设置</string>
|
||||
<string name="support">支持</string>
|
||||
<string name="kernel">内核</string>
|
||||
<string name="junk_packet_minimum_size">无效包最小值</string>
|
||||
<string name="chat_description">加入社区</string>
|
||||
<string name="root_accepted">已获取 root 权限</string>
|
||||
<string name="default_ping_ip">(可选,默认选择节点)</string>
|
||||
<string name="set_custom_ping_internal">Ping 间隔(秒)</string>
|
||||
<string name="optional_default">"可选,默认: "</string>
|
||||
<string name="set_custom_ping_cooldown">Ping 重启间隔(秒)</string>
|
||||
<string name="show_amnezia_properties">显示 Amnezia 属性</string>
|
||||
<string name="no_browser_detected">没有安装浏览器</string>
|
||||
<string name="incorrect_pin">密码不正确</string>
|
||||
<string name="set_custom_ping_ip">自定义 Ping 的目标 ip</string>
|
||||
<string name="vpn_channel_id">VPN 频道</string>
|
||||
<string name="junk_packet_count">无效包计数</string>
|
||||
<string name="app_name">WG Tunnel</string>
|
||||
<string name="vpn_channel_name">VPN 通知频道</string>
|
||||
<string name="open_issue">查看问题</string>
|
||||
<string name="read_logs">查看日志</string>
|
||||
<string name="auto">(自动)</string>
|
||||
<string name="pin_created">创建密码成功</string>
|
||||
<string name="enter_pin">输入密码</string>
|
||||
<string name="create_pin">创建密码</string>
|
||||
<string name="set_primary_tunnel">设置为主隧道</string>
|
||||
<string name="mobile_data_tunnel">允许使用手机数据流量</string>
|
||||
<string name="init_packet_junk_size">初始化无效包大小</string>
|
||||
<string name="junk_packet_maximum_size">无效包最大值</string>
|
||||
<string name="response_packet_magic_header">响应包的 magic header</string>
|
||||
<string name="response_packet_junk_size">无效的响应包大小</string>
|
||||
<string name="vpn_denied_dialog_title">拒绝访问</string>
|
||||
<string name="tunnel_required">此功能需要至少一个隧道</string>
|
||||
<string name="app_settings">应用设置</string>
|
||||
<string name="background_location_message2">请确保这些权限已开启。</string>
|
||||
<string name="logs">日志</string>
|
||||
<string name="restart_on_ping">Ping 失败之后自动重启隧道(beta)</string>
|
||||
<string name="edit_tunnel">编辑隧道</string>
|
||||
<string name="init_packet_magic_header">初始化数据包的 magic header</string>
|
||||
<string name="error_file_format">无效的隧道配置文件格式</string>
|
||||
<string name="always_on_message">VPN 连接被拒绝,请检查</string>
|
||||
<string name="vpn_settings">系统 VPN 设置</string>
|
||||
<string name="always_on_message2">确保始终开启 VPN 功能已关闭,然后再尝试连接</string>
|
||||
<string name="never">从不</string>
|
||||
<string name="sec">秒</string>
|
||||
<string name="handshake">已握手</string>
|
||||
<string name="light">亮色</string>
|
||||
<string name="dark">暗色</string>
|
||||
<string name="trusted_wifi_names">可信 WiFi SSID</string>
|
||||
<string name="prominent_background_location_title">后台定位披露</string>
|
||||
<string name="dynamic">动态颜色</string>
|
||||
<string name="display_theme">主题</string>
|
||||
<string name="kill_switch">系统 VPN 设置</string>
|
||||
<string name="appearance">外观</string>
|
||||
<string name="notifications">通知</string>
|
||||
<string name="automatic">跟随系统</string>
|
||||
<string name="language">语言</string>
|
||||
<string name="add_wifi_name">添加 WiFi SSID</string>
|
||||
<string name="primary_tunnel">主隧道</string>
|
||||
<string name="mobile_tunnel">允许使用移动数据</string>
|
||||
<string name="add_from_clipboard">从剪贴板添加</string>
|
||||
<string name="transport_packet_magic_header">传输包的 magic header</string>
|
||||
<string name="underload_packet_magic_header">欠载数据包 magic header</string>
|
||||
<string name="restart_at_boot">开机时重新启动</string>
|
||||
<string name="background_location_message">需要允许所有时间位置权限和/或精确位置才能使用此功能。请参阅</string>
|
||||
<string name="learn_more">了解更多</string>
|
||||
<string name="unsure_how">如果你不确定如何进行</string>
|
||||
<string name="see_the">见</string>
|
||||
<string name="getting_started_guide">入门指南</string>
|
||||
<string name="selected">已选择</string>
|
||||
<string name="on_demand_rules">按需隧道规则</string>
|
||||
<string name="skip">取消</string>
|
||||
<string name="launch_app_settings">打开应用设置</string>
|
||||
<string name="use_wildcards">使用 SSID 通配符</string>
|
||||
<string name="wildcards_active">启用通配符</string>
|
||||
<string name="wifi_name_via_shell">通过 shell 获取 WiFi 名称</string>
|
||||
<string name="use_root_shell_for_wifi">使用 root 权限的 shell 来获取 WiFi 名称</string>
|
||||
<string name="kernel_not_supported">内核不支持</string>
|
||||
<string name="start_auto">开启自动隧道</string>
|
||||
<string name="monitoring_state_changes">监控状态变化</string>
|
||||
<string name="stop_auto">停止自动隧道</string>
|
||||
<string name="tunnel_running">隧道运行中</string>
|
||||
<string name="donate">捐赠</string>
|
||||
<string name="local_logging">开启日志</string>
|
||||
<string name="enable_local_logging">开启本地日志</string>
|
||||
<string name="configuration_change">配置更改</string>
|
||||
<string name="requires_app_relaunch">此更改需要重新启动应用程序。您是否要继续?</string>
|
||||
</resources>
|
||||
|
||||
@@ -2,26 +2,15 @@
|
||||
<string name="app_name">WG Tunnel</string>
|
||||
<string name="vpn_channel_id">VPN Channel</string>
|
||||
<string name="vpn_channel_name">VPN Notification Channel</string>
|
||||
<string name="watcher_channel_id">Watcher Channel</string>
|
||||
<string name="watcher_channel_name">Watcher Notification Channel</string>
|
||||
<string name="github_url" translatable="false">https://github.com/zaneschepke/wgtunnel/issues</string>
|
||||
<string name="github_url" translatable="false">https://github.com/zaneschepke/wgtunnel/issues</string>
|
||||
<string name="docs_url" translatable="false">https://zaneschepke.com/wgtunnel-docs/overview.html</string>
|
||||
<string name="docs_features" translatable="false">https://zaneschepke.com/wgtunnel-docs/features.html</string>
|
||||
<string name="privacy_policy_url" translatable="false">https://zaneschepke.com/wgtunnel-docs/privacypolicy.html</string>
|
||||
<string name="docs_wildcards" translatable="false" >https://zaneschepke.com/wgtunnel-docs/features.html#wildcard-wi-fi-name-support</string>
|
||||
<string name="donate_url" translatable="false">https://zaneschepke.com/donate/</string>
|
||||
<string name="error_file_extension">File is not a .conf or .zip</string>
|
||||
<string name="turn_off_tunnel">Action requires tunnel off</string>
|
||||
<string name="no_tunnels">No tunnels added yet!</string>
|
||||
<string name="discord_url" translatable="false">https://discord.gg/rbRRNh6H7V</string>
|
||||
<string name="watcher_notification_text_active">Monitoring network state changes: active</string>
|
||||
<string name="watcher_notification_text_paused">Monitoring network state changes: paused</string>
|
||||
<string name="tunnel_start_title">VPN connected</string>
|
||||
<string name="tunnel_start_text">Connected to tunnel</string>
|
||||
<string name="notification_permission_required">Notifications permission required.</string>
|
||||
<string name="add_trusted_ssid">Add trusted wifi name</string>
|
||||
<string name="tunnels">Tunnels</string>
|
||||
<string name="enable_auto_tunnel">Start auto-tunneling</string>
|
||||
<string name="disable_auto_tunnel">Stop auto-tunneling</string>
|
||||
<string name="tunnel_mobile_data">Tunnel on mobile data</string>
|
||||
<string name="privacy_policy">View privacy policy</string>
|
||||
<string name="okay">Okay</string>
|
||||
@@ -29,23 +18,18 @@
|
||||
<string name="prominent_background_location_message">This feature requires background location permission to enable Wi-Fi SSID monitoring even while the application is closed. For more details, please see the Privacy Policy linked on the Support screen.</string>
|
||||
<string name="prominent_background_location_title">Background Location Disclosure</string>
|
||||
<string name="thank_you">Thank you for using WG Tunnel!</string>
|
||||
<string name="trusted_ssid_empty_description">Enter SSID</string>
|
||||
<string name="trusted_ssid_value_description">Submit SSID</string>
|
||||
<string name="add_tunnels_text">Add from file or zip</string>
|
||||
<string name="open_file">File Open</string>
|
||||
<string name="add_from_qr">Add from QR code</string>
|
||||
<string name="qr_scan">QR Scan</string>
|
||||
<string name="tunnel_name">Tunnel Name</string>
|
||||
<string name="add_tunnel">Add Tunnel</string>
|
||||
<string name="exclude">Exclude</string>
|
||||
<string name="include">Include</string>
|
||||
<string name="tunnel_all">Tunnel all applications</string>
|
||||
<string name="config_changes_saved">Configuration changes saved.</string>
|
||||
<string name="save_changes">Save</string>
|
||||
<string name="icon">Icon</string>
|
||||
<string name="no_thanks">No thanks</string>
|
||||
<string name="turn_on">Turn on</string>
|
||||
<string name="map">Map</string>
|
||||
<string name="public_key">Public key</string>
|
||||
<string name="addresses">Addresses</string>
|
||||
<string name="dns_servers">DNS servers</string>
|
||||
@@ -54,24 +38,14 @@
|
||||
<string name="allowed_ips">Allowed IPs</string>
|
||||
<string name="endpoint">Endpoint</string>
|
||||
<string name="name">Name</string>
|
||||
<string name="restart">Restart Tunnel</string>
|
||||
<string name="vpn_connection_failed">Connection failed</string>
|
||||
<string name="always_on_vpn_support">Allow Always-On VPN </string>
|
||||
<string name="location_services_not_detected">Location Services Not Detected</string>
|
||||
<string name="hint_search_packages">Search packages</string>
|
||||
<string name="attempt_connection">Attempting connection..</string>
|
||||
<string name="vpn_starting">VPN starting</string>
|
||||
<string name="db_name">wg-tunnel-db</string>
|
||||
<string name="scanning_qr">Scanning for QR</string>
|
||||
<string name="none">No trusted wifi names</string>
|
||||
<string name="other">Other</string>
|
||||
<string name="auto_tunneling">Auto-tunneling</string>
|
||||
<string name="vpn_on">VPN on</string>
|
||||
<string name="vpn_off">VPN off</string>
|
||||
<string name="default_vpn_on">Primary VPN on</string>
|
||||
<string name="default_vpn_off">Primary VPN off</string>
|
||||
<string name="create_import">Create from scratch</string>
|
||||
<string name="turn_off_auto">Action requires auto-tunnel disabled or paused</string>
|
||||
<string name="turn_on_tunnel">Action requires active tunnel</string>
|
||||
<string name="add_peer">Add peer</string>
|
||||
<string name="done">Done</string>
|
||||
@@ -93,47 +67,32 @@
|
||||
<string name="error_authorization_failed">Failed to authorize</string>
|
||||
<string name="enabled_app_shortcuts">Enable app shortcuts</string>
|
||||
<string name="export_configs">Export configs</string>
|
||||
<string name="export_configs_failed">Failed to export configs</string>
|
||||
<string name="location_services_required">Location services required</string>
|
||||
<string name="background_location_required">Background location required</string>
|
||||
<string name="precise_location_required">Precise location required</string>
|
||||
<string name="unknown_error">Unknown error occurred</string>
|
||||
<string name="exported_configs_message">Exported configs to downloads</string>
|
||||
<string name="tunnel_on_wifi">Tunnel on untrusted wifi</string>
|
||||
<string name="my_email" translatable="false">support@zaneschepke.com</string>
|
||||
<string name="email_subject">WG Tunnel Support</string>
|
||||
<string name="email_chooser">Send an email…</string>
|
||||
<string name="go">go</string>
|
||||
<string name="docs_description">Read the docs</string>
|
||||
<string name="email_description">Send me an email</string>
|
||||
<string name="support_help_text">If you are experiencing issues, have improvement ideas, or just want to engage, the following resources are available:</string>
|
||||
<string name="use_kernel">Use kernel module</string>
|
||||
<string name="error_ssid_exists">SSID already exists</string>
|
||||
<string name="error_root_denied">Root shell denied</string>
|
||||
<string name="error_no_file_explorer">No file explorer installed</string>
|
||||
<string name="error_invalid_code">Invalid QR code</string>
|
||||
<string name="error_none">No error</string>
|
||||
<string name="location_services_missing_message">The app is not detecting any location services enabled on your device. Depending on the device, this could cause the untrusted wifi feature to fail to read the wifi name. Would you like to continue anyways?</string>
|
||||
<string name="auto_tunnel_title">Auto-tunnel Service</string>
|
||||
<string name="auto_tunnel_title">Auto-tunnel service</string>
|
||||
<string name="delete_tunnel">Delete tunnel</string>
|
||||
<string name="delete_tunnel_message">Are you sure you would like to delete this tunnel?</string>
|
||||
<string name="yes">Yes</string>
|
||||
<string name="resume">Resume</string>
|
||||
<string name="pause">Pause</string>
|
||||
<string name="paused">paused</string>
|
||||
<string name="active">active</string>
|
||||
<string name="tunneling_apps">Tunneling apps</string>
|
||||
<string name="included">included</string>
|
||||
<string name="excluded">excluded</string>
|
||||
<string name="all">all</string>
|
||||
<string name="always_on_disabled">Always-on VPN attempted to start a tunnel, but this feature is disabled in settings.</string>
|
||||
<string name="no_email_detected">No email app detected</string>
|
||||
<string name="no_browser_detected">No browser detected</string>
|
||||
<string name="logs_saved">Logs saved to downloads</string>
|
||||
<string name="open_issue">Open an issue</string>
|
||||
<string name="read_logs">Read the logs</string>
|
||||
<string name="auto">(auto)</string>
|
||||
<string name="config_parse_error">Failed to parse config</string>
|
||||
<string name="incorrect_pin">Pin is incorrect</string>
|
||||
<string name="pin_created">Pin successfully created</string>
|
||||
<string name="enter_pin">Enter your pin</string>
|
||||
@@ -143,22 +102,11 @@
|
||||
<string name="mobile_data_tunnel">Set as mobile data tunnel</string>
|
||||
<string name="set_primary_tunnel">Set as primary tunnel</string>
|
||||
<string name="use_tunnel_on_wifi_name">Use tunnel on wifi name</string>
|
||||
<string name="no_wifi_names_configured">No wifi names configured for this tunnel</string>
|
||||
<string name="general">General</string>
|
||||
<string name="edit_tunnel">Edit tunnel</string>
|
||||
<string name="disabled">disabled</string>
|
||||
<string name="auto_on">Resume auto tun</string>
|
||||
<string name="auto_off">Pause auto tun</string>
|
||||
<string name="auto_tun_on">Resume auto-tunnel</string>
|
||||
<string name="auto_tun_off">Pause auto-tunnel</string>
|
||||
<string name="version">Version</string>
|
||||
<string name="mode">Mode</string>
|
||||
<string name="userspace">Userspace</string>
|
||||
<string name="settings">Settings</string>
|
||||
<string name="support">Support</string>
|
||||
<string name="backend">Backend</string>
|
||||
<string name="kernel">Kernel</string>
|
||||
<string name="use_amnezia">"Use Amnezia userspace "</string>
|
||||
<string name="junk_packet_count">Junk packet count</string>
|
||||
<string name="junk_packet_minimum_size">Junk packet minimum size</string>
|
||||
<string name="junk_packet_maximum_size">Junk packet maximum size</string>
|
||||
@@ -173,8 +121,6 @@
|
||||
<string name="see_the">See the</string>
|
||||
<string name="getting_started_url" translatable="false">https://zaneschepke.com/wgtunnel-docs/getting-started.html</string>
|
||||
<string name="getting_started_guide">getting started guide</string>
|
||||
<string name="amnezia" translatable="false">Amnezia</string>
|
||||
<string name="wireguard" translatable="false">WireGuard</string>
|
||||
<string name="error_file_format">Invalid tunnel config format</string>
|
||||
<string name="restart_at_boot">Restart on boot</string>
|
||||
<string name="vpn_denied_dialog_title">Permission Denied</string>
|
||||
@@ -183,24 +129,21 @@
|
||||
<string name="always_on_message2">to make sure Always-on VPN is turned off for all other apps and try again</string>
|
||||
<string name="chat_description">Join the community</string>
|
||||
<string name="tunnel_required">Feature requires at least one tunnel</string>
|
||||
<string name="request_root">Request root</string>
|
||||
<string name="background_location_message">Allow all the time location permission and/or precise location is required for this feature. Please see</string>
|
||||
<string name="app_settings">app settings</string>
|
||||
<string name="background_location_message2">to make sure these permissions are enabled.</string>
|
||||
<string name="background_location_message2">to make sure these permissions are enabled</string>
|
||||
<string name="root_accepted">Root shell accepted</string>
|
||||
<string name="set_custom_ping_ip">Set custom ping ip</string>
|
||||
<string name="default_ping_ip">(optional, defaults to peers)</string>
|
||||
<string name="set_custom_ping_internal">Ping interval (sec)</string>
|
||||
<string name="optional_default">"optional, default: "</string>
|
||||
<string name="set_custom_ping_cooldown">Ping restart cooldown (sec)</string>
|
||||
<string name="details">details</string>
|
||||
<string name="show_amnezia_properties">Show Amnezia properties</string>
|
||||
<string name="show_amnezia_properties">Show Amnezia properties</string>
|
||||
<string name="never">never</string>
|
||||
<string name="sec">sec</string>
|
||||
<string name="handshake">handshake</string>
|
||||
<string name="logs">Logs</string>
|
||||
<string name="tunnel_notifications">Tunnel notifications</string>
|
||||
<string name="kill_switch">Kill switch</string>
|
||||
<string name="kill_switch">Kill switch</string>
|
||||
<string name="appearance">Appearance</string>
|
||||
<string name="notifications">Notifications</string>
|
||||
<string name="automatic">Automatic</string>
|
||||
@@ -225,4 +168,26 @@
|
||||
<string name="kernel_not_supported">Kernel not supported</string>
|
||||
<string name="start_auto">Start auto-tunnel</string>
|
||||
<string name="stop_auto">Stop auto-tunnel</string>
|
||||
<string name="tunnel_running">Tunnel running</string>
|
||||
<string name="monitoring_state_changes">Monitoring state changes</string>
|
||||
<string name="donate">Donate to project</string>
|
||||
<string name="local_logging">Local logging</string>
|
||||
<string name="enable_local_logging">Enable local logging</string>
|
||||
<string name="configuration_change">Configuration change</string>
|
||||
<string name="requires_app_relaunch">This change requires an app relaunch. Would you like to proceed?</string>
|
||||
<string name="add_from_clipboard">Add from clipboard</string>
|
||||
<string name="stop_on_no_internet">Stop on no internet</string>
|
||||
<string name="stop_on_internet_loss">Stop tunnel on internet loss</string>
|
||||
<string name="ethernet_tunnel">Ethernet tunnel</string>
|
||||
<string name="set_ethernet_tunnel">Set as ethernet tunnel</string>
|
||||
<string name="native_kill_switch">Native kill switch</string>
|
||||
<string name="vpn_kill_switch">VPN kill switch</string>
|
||||
<string name="kill_switch_options">Kill switch options</string>
|
||||
<string name="allow_lan_traffic">Allow LAN traffic</string>
|
||||
<string name="bypass_lan_for_kill_switch">Bypass LAN for kill switch</string>
|
||||
<string name="vpn_channel_description">A channel for VPN state notifications</string>
|
||||
<string name="auto_tunnel_channel_id">Auto-tunnel Channel</string>
|
||||
<string name="auto_tunnel_channel_name">Auto-tunnel Notification Channel</string>
|
||||
<string name="auto_tunnel_channel_description">A channel for auto-tunnel state notifications</string>
|
||||
<string name="stop">stop</string>
|
||||
</resources>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
object Constants {
|
||||
const val VERSION_NAME = "3.6.0"
|
||||
const val VERSION_NAME = "3.6.3"
|
||||
const val JVM_TARGET = "17"
|
||||
const val VERSION_CODE = 36000
|
||||
const val VERSION_CODE = 36300
|
||||
const val TARGET_SDK = 35
|
||||
const val MIN_SDK = 26
|
||||
const val APP_ID = "com.zaneschepke.wireguardautotunnel"
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user