Compare commits

..

18 Commits

Author SHA1 Message Date
zaneschepke 2c78e9fcbd refactor: app icons and assets, change nightly launcher color
Remove old lincensee task
2026-07-03 04:43:10 -04:00
zaneschepke 8bdeff515e fix: aboutlibraries r8 crash 2026-07-03 02:56:19 -04:00
zaneschepke fa89715ede fix: disable on captive portal toggle bug
closes #1325
2026-07-03 01:35:50 -04:00
zaneschepke 4a94905893 feat: seamless roaming
Fix for broadcast receivers to goAsync to fix issues on slower devices
Add "seamless roaming" to bind wireguard UDP sockets to the active network to help resolve handshake issues on network transitions
2026-07-03 01:30:20 -04:00
zaneschepke cf184f2042 refactor: remove old license viewmodel 2026-07-01 03:15:58 -04:00
zaneschepke e0ddb8730d refactor: switch license view to aboutlibraries, add native dep licenses 2026-07-01 02:37:00 -04:00
zaneschepke c8c041b872 fix: improve ktor client to prevent download failures from github
Add user agent headers with default request and increased timeout to prevent download denial from GitHub
2026-06-30 00:35:38 -04:00
zaneschepke 7c8adb380b chore: release 5.0.7 2026-06-29 12:56:55 -04:00
zaneschepke 614f97fd14 feat: add disable tunnel on captive portal to auto tunnel
Refactor of network monitoring and auto tunnel logic to adapt to the lest strict network monitoring of active network to support system DNS.

Add disable tunnel on captive portal feature to allow auto disable of vpn while captive portal is not completed.
2026-06-29 12:08:34 -04:00
zaneschepke fbd470f5d2 fix: make network monitor less strict for network capabilities
Network monitor was too strict with capability checks, impacting our DNS resolver which needs to bind to underlying network. Capabilities have been separated out into a separate state property so we always pass the active network to connectivity state for system dns

Improve system dns by supporting DnsResolver on modern devices

#1270
2026-06-28 13:41:08 -04:00
zaneschepke 5f89b2ed31 refactor: improve mobile network detection, cleanup network monitor
#1270
2026-06-28 04:52:41 -04:00
zaneschepke 9503a3284b fix: kill switch should restore properly on tunnel up if it was killed by system or another app
closes #1313
2026-06-28 02:52:24 -04:00
zaneschepke 68c1a19bd3 fix: remove ipv6 address from lockdown causing routing issues ipv4 only tunnels 2026-06-28 02:15:45 -04:00
zaneschepke f3bb6667c3 fix: private dns to use network bind, bootstrap custom with system dns
closes #1312
closes #1311

#1303
#1270
2026-06-27 20:06:06 -04:00
zaneschepke 244a990c37 fix: ddns job logic, respect user dns setting for DDNS with default fallback if cache suspected
#1312
#1303
2026-06-27 03:57:18 -04:00
zaneschepke cbf07600b4 fix: ddns checking logic, force well known DoH to bypass system dns cache
#1303
2026-06-26 12:47:38 -04:00
zaneschepke ec8d90d13d chore: bump ktor and leakcanary
closes #1309
closes #1308
2026-06-26 03:45:38 -04:00
zaneschepke 85acca8604 fix: local network permission dialog theme and wording 2026-06-26 03:36:59 -04:00
135 changed files with 2355 additions and 940 deletions
+17 -17
View File
@@ -8,6 +8,7 @@ plugins {
alias(libs.plugins.compose.compiler)
alias(libs.plugins.grgit)
alias(libs.plugins.licensee)
alias(libs.plugins.aboutlibraries)
}
ksp {
@@ -17,9 +18,15 @@ ksp {
licensee {
allowedLicenses().forEach { allow(it) }
allowedLicenseUrls().forEach { allowUrl(it) }
// foss, but missing licenses
ignoreDependencies("com.github.T8RIN.QuickieExtended")
ignoreDependencies("com.github.topjohnwu.libsu")
allowDependency("com.github.T8RIN.QuickieExtended", "quickie-foss", "1.18.1") {
because("FOSS library, but JitPack doesn't publish license metadata")
allow("Apache-2.0")
}
allowDependency("com.github.topjohnwu.libsu", "core", "6.0.0") {
because("FOSS library, but JitPack doesn't publish license metadata")
allow("Apache-2.0")
}
}
configure<ApplicationExtension> {
@@ -94,21 +101,24 @@ configure<ApplicationExtension> {
"proguard-rules.pro",
)
signingConfig = signingConfigs.getByName(Constants.RELEASE)
resValue("string", "provider", "\"${Constants.APP_NAME}.provider\"")
manifestPlaceholders["providerAuthority"] = "${Constants.APP_NAME}.provider"
buildConfigField("String", "FILE_PROVIDER_AUTHORITY", "\"${Constants.APP_NAME}.provider\"")
}
debug {
applicationIdSuffix = ".debug"
resValue("string", "app_name", "WG Tunnel Debug")
isDebuggable = true
resValue("string", "provider", "\"${Constants.APP_NAME}.provider.debug\"")
manifestPlaceholders["providerAuthority"] = "${Constants.APP_NAME}.provider.debug"
buildConfigField("String", "FILE_PROVIDER_AUTHORITY", "\"${Constants.APP_NAME}.provider.debug\"")
}
create(Constants.NIGHTLY) {
initWith(buildTypes.getByName(Constants.RELEASE))
applicationIdSuffix = ".nightly"
resValue("string", "app_name", "WG Tunnel Nightly")
resValue("string", "provider", "\"${Constants.APP_NAME}.provider.nightly\"")
manifestPlaceholders["providerAuthority"] = "${Constants.APP_NAME}.provider.nightly"
buildConfigField("String", "FILE_PROVIDER_AUTHORITY", "\"${Constants.APP_NAME}.provider.nightly\"")
}
}
@@ -231,6 +241,7 @@ dependencies {
implementation(libs.bundles.ui.utilities)
implementation(libs.lottie.compose)
implementation(libs.sonner)
implementation(libs.aboutlibraries.compose)
// Misc utilities
implementation(libs.bundles.misc.utilities)
@@ -271,17 +282,6 @@ dependencies {
implementation(libs.koin.worker)
}
tasks.register<Copy>("copyLicenseeJsonToAssets") {
dependsOn("licensee")
val outputAssets = layout.projectDirectory.dir("src/main/assets")
from(layout.buildDirectory.file("reports/licensee/androidFdroidRelease/artifacts.json")) {
rename("artifacts.json", "licenses.json")
}
into(outputAssets)
}
tasks.named("preBuild") { dependsOn("copyLicenseeJsonToAssets") }
// https://gist.github.com/obfusk/61046e09cee352ae6dd109911534b12e#fix-proposed-by-linsui-disable-baseline-profiles
tasks.configureEach {
if (name.contains("ArtProfile")) {
@@ -0,0 +1,513 @@
{
"formatVersion": 1,
"database": {
"version": 31,
"identityHash": "1dee3799f1c6526c48723fd2fee58d11",
"entities": [
{
"tableName": "tunnel_config",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` 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, `quick_config` TEXT NOT NULL DEFAULT '', `dynamic_dns` INTEGER NOT NULL DEFAULT false, `is_ethernet_tunnel` INTEGER NOT NULL DEFAULT false, `prefer_ipv6` INTEGER NOT NULL DEFAULT false, `position` INTEGER NOT NULL DEFAULT 0, `auto_tunnel_apps` TEXT NOT NULL DEFAULT '[]', `is_metered` INTEGER NOT NULL DEFAULT false, `ipv4_fallback` INTEGER NOT NULL DEFAULT false, `ipv6_restore` INTEGER NOT NULL DEFAULT false)",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "name",
"columnName": "name",
"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": "quickConfig",
"columnName": "quick_config",
"affinity": "TEXT",
"notNull": true,
"defaultValue": "''"
},
{
"fieldPath": "dynamicDnsEnabled",
"columnName": "dynamic_dns",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "false"
},
{
"fieldPath": "isEthernetTunnel",
"columnName": "is_ethernet_tunnel",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "false"
},
{
"fieldPath": "isIpv6Preferred",
"columnName": "prefer_ipv6",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "false"
},
{
"fieldPath": "position",
"columnName": "position",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "0"
},
{
"fieldPath": "autoTunnelApps",
"columnName": "auto_tunnel_apps",
"affinity": "TEXT",
"notNull": true,
"defaultValue": "'[]'"
},
{
"fieldPath": "isMetered",
"columnName": "is_metered",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "false"
},
{
"fieldPath": "ipv4FallbackEnabled",
"columnName": "ipv4_fallback",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "false"
},
{
"fieldPath": "ipv6RestoreEnabled",
"columnName": "ipv6_restore",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "false"
}
],
"primaryKey": {
"autoGenerate": true,
"columnNames": [
"id"
]
},
"indices": [
{
"name": "index_tunnel_config_name",
"unique": true,
"columnNames": [
"name"
],
"orders": [],
"createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_tunnel_config_name` ON `${TABLE_NAME}` (`name`)"
}
]
},
{
"tableName": "proxy_settings",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `socks5_proxy_enabled` INTEGER NOT NULL DEFAULT 0, `socks5_proxy_bind_address` TEXT, `http_proxy_enable` INTEGER NOT NULL DEFAULT 0, `http_proxy_bind_address` TEXT, `proxy_username` TEXT, `proxy_password` TEXT)",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "socks5ProxyEnabled",
"columnName": "socks5_proxy_enabled",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "0"
},
{
"fieldPath": "socks5ProxyBindAddress",
"columnName": "socks5_proxy_bind_address",
"affinity": "TEXT"
},
{
"fieldPath": "httpProxyEnabled",
"columnName": "http_proxy_enable",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "0"
},
{
"fieldPath": "httpProxyBindAddress",
"columnName": "http_proxy_bind_address",
"affinity": "TEXT"
},
{
"fieldPath": "proxyUsername",
"columnName": "proxy_username",
"affinity": "TEXT"
},
{
"fieldPath": "proxyPassword",
"columnName": "proxy_password",
"affinity": "TEXT"
}
],
"primaryKey": {
"autoGenerate": true,
"columnNames": [
"id"
]
}
},
{
"tableName": "general_settings",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_shortcuts_enabled` INTEGER NOT NULL DEFAULT 0, `is_restore_on_boot_enabled` INTEGER NOT NULL DEFAULT 0, `is_multi_tunnel_enabled` INTEGER NOT NULL DEFAULT 0, `global_split_tunnel_enabled` INTEGER NOT NULL DEFAULT 0, `app_mode` INTEGER NOT NULL DEFAULT 0, `theme` TEXT NOT NULL DEFAULT 'AUTOMATIC', `locale` TEXT, `remote_key` TEXT, `is_remote_control_enabled` INTEGER NOT NULL DEFAULT 0, `is_pin_lock_enabled` INTEGER NOT NULL DEFAULT 0, `is_always_on_vpn_enabled` INTEGER NOT NULL DEFAULT 0, `already_donated` INTEGER NOT NULL DEFAULT 0, `screen_recording_security` INTEGER NOT NULL DEFAULT 1, `global_amnezia_enabled` INTEGER NOT NULL DEFAULT 0, `tunnel_scripting_enabled` INTEGER NOT NULL DEFAULT 0)",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "isShortcutsEnabled",
"columnName": "is_shortcuts_enabled",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "0"
},
{
"fieldPath": "isRestoreOnBootEnabled",
"columnName": "is_restore_on_boot_enabled",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "0"
},
{
"fieldPath": "isMultiTunnelEnabled",
"columnName": "is_multi_tunnel_enabled",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "0"
},
{
"fieldPath": "isGlobalSplitTunnelEnabled",
"columnName": "global_split_tunnel_enabled",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "0"
},
{
"fieldPath": "tunnelMode",
"columnName": "app_mode",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "0"
},
{
"fieldPath": "theme",
"columnName": "theme",
"affinity": "TEXT",
"notNull": true,
"defaultValue": "'AUTOMATIC'"
},
{
"fieldPath": "locale",
"columnName": "locale",
"affinity": "TEXT"
},
{
"fieldPath": "remoteKey",
"columnName": "remote_key",
"affinity": "TEXT"
},
{
"fieldPath": "isRemoteControlEnabled",
"columnName": "is_remote_control_enabled",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "0"
},
{
"fieldPath": "isPinLockEnabled",
"columnName": "is_pin_lock_enabled",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "0"
},
{
"fieldPath": "isAlwaysOnVpnEnabled",
"columnName": "is_always_on_vpn_enabled",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "0"
},
{
"fieldPath": "alreadyDonated",
"columnName": "already_donated",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "0"
},
{
"fieldPath": "screenRecordingSecurityEnabled",
"columnName": "screen_recording_security",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "1"
},
{
"fieldPath": "isGlobalAmneziaEnabled",
"columnName": "global_amnezia_enabled",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "0"
},
{
"fieldPath": "tunnelScriptingEnabled",
"columnName": "tunnel_scripting_enabled",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "0"
}
],
"primaryKey": {
"autoGenerate": true,
"columnNames": [
"id"
]
}
},
{
"tableName": "auto_tunnel_settings",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_tunnel_enabled` INTEGER NOT NULL DEFAULT 0, `is_tunnel_on_mobile_data_enabled` INTEGER NOT NULL DEFAULT 0, `trusted_network_ssids` TEXT NOT NULL DEFAULT '', `is_tunnel_on_ethernet_enabled` INTEGER NOT NULL DEFAULT 0, `is_tunnel_on_wifi_enabled` INTEGER NOT NULL DEFAULT 0, `is_wildcards_enabled` INTEGER NOT NULL DEFAULT 0, `is_stop_on_no_internet_enabled` INTEGER NOT NULL DEFAULT 0, `is_tunnel_on_unsecure_enabled` INTEGER NOT NULL DEFAULT 0, `wifi_detection_method` INTEGER NOT NULL DEFAULT 0, `start_on_boot` INTEGER NOT NULL DEFAULT 0, `disable_on_captive_portal` INTEGER NOT NULL DEFAULT 1)",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "isAutoTunnelEnabled",
"columnName": "is_tunnel_enabled",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "0"
},
{
"fieldPath": "isTunnelOnMobileDataEnabled",
"columnName": "is_tunnel_on_mobile_data_enabled",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "0"
},
{
"fieldPath": "trustedNetworkSSIDs",
"columnName": "trusted_network_ssids",
"affinity": "TEXT",
"notNull": true,
"defaultValue": "''"
},
{
"fieldPath": "isTunnelOnEthernetEnabled",
"columnName": "is_tunnel_on_ethernet_enabled",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "0"
},
{
"fieldPath": "isTunnelOnWifiEnabled",
"columnName": "is_tunnel_on_wifi_enabled",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "0"
},
{
"fieldPath": "isWildcardsEnabled",
"columnName": "is_wildcards_enabled",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "0"
},
{
"fieldPath": "isStopOnNoInternetEnabled",
"columnName": "is_stop_on_no_internet_enabled",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "0"
},
{
"fieldPath": "isTunnelOnUnsecureEnabled",
"columnName": "is_tunnel_on_unsecure_enabled",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "0"
},
{
"fieldPath": "wifiDetectionMethod",
"columnName": "wifi_detection_method",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "0"
},
{
"fieldPath": "startOnBoot",
"columnName": "start_on_boot",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "0"
},
{
"fieldPath": "disableTunnelOnCaptivePortal",
"columnName": "disable_on_captive_portal",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "1"
}
],
"primaryKey": {
"autoGenerate": true,
"columnNames": [
"id"
]
}
},
{
"tableName": "monitoring_settings",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_local_logs_enabled` INTEGER NOT NULL DEFAULT 0, `tunnel_statistics_enabled` INTEGER NOT NULL DEFAULT 1, `tunnel_statistics_poll_interval` INTEGER NOT NULL DEFAULT 3)",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "isLocalLogsEnabled",
"columnName": "is_local_logs_enabled",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "0"
},
{
"fieldPath": "tunnelStatisticsEnabled",
"columnName": "tunnel_statistics_enabled",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "1"
},
{
"fieldPath": "tunnelStatisticsPollInterval",
"columnName": "tunnel_statistics_poll_interval",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "3"
}
],
"primaryKey": {
"autoGenerate": true,
"columnNames": [
"id"
]
}
},
{
"tableName": "dns_settings",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `dns_protocol` INTEGER NOT NULL DEFAULT 0, `dns_endpoint` TEXT, `global_tunnel_dns_enabled` INTEGER NOT NULL DEFAULT 0)",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "dnsProtocol",
"columnName": "dns_protocol",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "0"
},
{
"fieldPath": "dnsEndpoint",
"columnName": "dns_endpoint",
"affinity": "TEXT"
},
{
"fieldPath": "isGlobalTunnelDnsEnabled",
"columnName": "global_tunnel_dns_enabled",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "0"
}
],
"primaryKey": {
"autoGenerate": true,
"columnNames": [
"id"
]
}
},
{
"tableName": "lockdown_settings",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `bypass_lan` INTEGER NOT NULL DEFAULT 0, `metered` INTEGER NOT NULL DEFAULT 0, `dual_stack` INTEGER NOT NULL DEFAULT 0)",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "bypassLan",
"columnName": "bypass_lan",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "0"
},
{
"fieldPath": "metered",
"columnName": "metered",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "0"
},
{
"fieldPath": "dualStack",
"columnName": "dual_stack",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "0"
}
],
"primaryKey": {
"autoGenerate": true,
"columnNames": [
"id"
]
}
}
],
"setupQueries": [
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '1dee3799f1c6526c48723fd2fee58d11')"
]
}
}
@@ -0,0 +1,520 @@
{
"formatVersion": 1,
"database": {
"version": 32,
"identityHash": "fd4803fc483f41704303be9246dcfb4d",
"entities": [
{
"tableName": "tunnel_config",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` 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, `quick_config` TEXT NOT NULL DEFAULT '', `dynamic_dns` INTEGER NOT NULL DEFAULT false, `is_ethernet_tunnel` INTEGER NOT NULL DEFAULT false, `prefer_ipv6` INTEGER NOT NULL DEFAULT false, `position` INTEGER NOT NULL DEFAULT 0, `auto_tunnel_apps` TEXT NOT NULL DEFAULT '[]', `is_metered` INTEGER NOT NULL DEFAULT false, `ipv4_fallback` INTEGER NOT NULL DEFAULT false, `ipv6_restore` INTEGER NOT NULL DEFAULT false)",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "name",
"columnName": "name",
"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": "quickConfig",
"columnName": "quick_config",
"affinity": "TEXT",
"notNull": true,
"defaultValue": "''"
},
{
"fieldPath": "dynamicDnsEnabled",
"columnName": "dynamic_dns",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "false"
},
{
"fieldPath": "isEthernetTunnel",
"columnName": "is_ethernet_tunnel",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "false"
},
{
"fieldPath": "isIpv6Preferred",
"columnName": "prefer_ipv6",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "false"
},
{
"fieldPath": "position",
"columnName": "position",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "0"
},
{
"fieldPath": "autoTunnelApps",
"columnName": "auto_tunnel_apps",
"affinity": "TEXT",
"notNull": true,
"defaultValue": "'[]'"
},
{
"fieldPath": "isMetered",
"columnName": "is_metered",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "false"
},
{
"fieldPath": "ipv4FallbackEnabled",
"columnName": "ipv4_fallback",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "false"
},
{
"fieldPath": "ipv6RestoreEnabled",
"columnName": "ipv6_restore",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "false"
}
],
"primaryKey": {
"autoGenerate": true,
"columnNames": [
"id"
]
},
"indices": [
{
"name": "index_tunnel_config_name",
"unique": true,
"columnNames": [
"name"
],
"orders": [],
"createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_tunnel_config_name` ON `${TABLE_NAME}` (`name`)"
}
]
},
{
"tableName": "proxy_settings",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `socks5_proxy_enabled` INTEGER NOT NULL DEFAULT 0, `socks5_proxy_bind_address` TEXT, `http_proxy_enable` INTEGER NOT NULL DEFAULT 0, `http_proxy_bind_address` TEXT, `proxy_username` TEXT, `proxy_password` TEXT)",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "socks5ProxyEnabled",
"columnName": "socks5_proxy_enabled",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "0"
},
{
"fieldPath": "socks5ProxyBindAddress",
"columnName": "socks5_proxy_bind_address",
"affinity": "TEXT"
},
{
"fieldPath": "httpProxyEnabled",
"columnName": "http_proxy_enable",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "0"
},
{
"fieldPath": "httpProxyBindAddress",
"columnName": "http_proxy_bind_address",
"affinity": "TEXT"
},
{
"fieldPath": "proxyUsername",
"columnName": "proxy_username",
"affinity": "TEXT"
},
{
"fieldPath": "proxyPassword",
"columnName": "proxy_password",
"affinity": "TEXT"
}
],
"primaryKey": {
"autoGenerate": true,
"columnNames": [
"id"
]
}
},
{
"tableName": "general_settings",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_shortcuts_enabled` INTEGER NOT NULL DEFAULT 0, `is_restore_on_boot_enabled` INTEGER NOT NULL DEFAULT 0, `is_multi_tunnel_enabled` INTEGER NOT NULL DEFAULT 0, `global_split_tunnel_enabled` INTEGER NOT NULL DEFAULT 0, `app_mode` INTEGER NOT NULL DEFAULT 0, `theme` TEXT NOT NULL DEFAULT 'AUTOMATIC', `locale` TEXT, `remote_key` TEXT, `is_remote_control_enabled` INTEGER NOT NULL DEFAULT 0, `is_pin_lock_enabled` INTEGER NOT NULL DEFAULT 0, `is_always_on_vpn_enabled` INTEGER NOT NULL DEFAULT 0, `already_donated` INTEGER NOT NULL DEFAULT 0, `screen_recording_security` INTEGER NOT NULL DEFAULT 1, `global_amnezia_enabled` INTEGER NOT NULL DEFAULT 0, `tunnel_scripting_enabled` INTEGER NOT NULL DEFAULT 0, `seamless_roaming_enabled` INTEGER NOT NULL DEFAULT 0)",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "isShortcutsEnabled",
"columnName": "is_shortcuts_enabled",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "0"
},
{
"fieldPath": "isRestoreOnBootEnabled",
"columnName": "is_restore_on_boot_enabled",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "0"
},
{
"fieldPath": "isMultiTunnelEnabled",
"columnName": "is_multi_tunnel_enabled",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "0"
},
{
"fieldPath": "isGlobalSplitTunnelEnabled",
"columnName": "global_split_tunnel_enabled",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "0"
},
{
"fieldPath": "tunnelMode",
"columnName": "app_mode",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "0"
},
{
"fieldPath": "theme",
"columnName": "theme",
"affinity": "TEXT",
"notNull": true,
"defaultValue": "'AUTOMATIC'"
},
{
"fieldPath": "locale",
"columnName": "locale",
"affinity": "TEXT"
},
{
"fieldPath": "remoteKey",
"columnName": "remote_key",
"affinity": "TEXT"
},
{
"fieldPath": "isRemoteControlEnabled",
"columnName": "is_remote_control_enabled",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "0"
},
{
"fieldPath": "isPinLockEnabled",
"columnName": "is_pin_lock_enabled",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "0"
},
{
"fieldPath": "isAlwaysOnVpnEnabled",
"columnName": "is_always_on_vpn_enabled",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "0"
},
{
"fieldPath": "alreadyDonated",
"columnName": "already_donated",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "0"
},
{
"fieldPath": "screenRecordingSecurityEnabled",
"columnName": "screen_recording_security",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "1"
},
{
"fieldPath": "isGlobalAmneziaEnabled",
"columnName": "global_amnezia_enabled",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "0"
},
{
"fieldPath": "tunnelScriptingEnabled",
"columnName": "tunnel_scripting_enabled",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "0"
},
{
"fieldPath": "seamlessRoamingEnabled",
"columnName": "seamless_roaming_enabled",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "0"
}
],
"primaryKey": {
"autoGenerate": true,
"columnNames": [
"id"
]
}
},
{
"tableName": "auto_tunnel_settings",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_tunnel_enabled` INTEGER NOT NULL DEFAULT 0, `is_tunnel_on_mobile_data_enabled` INTEGER NOT NULL DEFAULT 0, `trusted_network_ssids` TEXT NOT NULL DEFAULT '', `is_tunnel_on_ethernet_enabled` INTEGER NOT NULL DEFAULT 0, `is_tunnel_on_wifi_enabled` INTEGER NOT NULL DEFAULT 0, `is_wildcards_enabled` INTEGER NOT NULL DEFAULT 0, `is_stop_on_no_internet_enabled` INTEGER NOT NULL DEFAULT 0, `is_tunnel_on_unsecure_enabled` INTEGER NOT NULL DEFAULT 0, `wifi_detection_method` INTEGER NOT NULL DEFAULT 0, `start_on_boot` INTEGER NOT NULL DEFAULT 0, `disable_on_captive_portal` INTEGER NOT NULL DEFAULT 1)",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "isAutoTunnelEnabled",
"columnName": "is_tunnel_enabled",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "0"
},
{
"fieldPath": "isTunnelOnMobileDataEnabled",
"columnName": "is_tunnel_on_mobile_data_enabled",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "0"
},
{
"fieldPath": "trustedNetworkSSIDs",
"columnName": "trusted_network_ssids",
"affinity": "TEXT",
"notNull": true,
"defaultValue": "''"
},
{
"fieldPath": "isTunnelOnEthernetEnabled",
"columnName": "is_tunnel_on_ethernet_enabled",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "0"
},
{
"fieldPath": "isTunnelOnWifiEnabled",
"columnName": "is_tunnel_on_wifi_enabled",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "0"
},
{
"fieldPath": "isWildcardsEnabled",
"columnName": "is_wildcards_enabled",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "0"
},
{
"fieldPath": "isStopOnNoInternetEnabled",
"columnName": "is_stop_on_no_internet_enabled",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "0"
},
{
"fieldPath": "isTunnelOnUnsecureEnabled",
"columnName": "is_tunnel_on_unsecure_enabled",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "0"
},
{
"fieldPath": "wifiDetectionMethod",
"columnName": "wifi_detection_method",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "0"
},
{
"fieldPath": "startOnBoot",
"columnName": "start_on_boot",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "0"
},
{
"fieldPath": "disableTunnelOnCaptivePortal",
"columnName": "disable_on_captive_portal",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "1"
}
],
"primaryKey": {
"autoGenerate": true,
"columnNames": [
"id"
]
}
},
{
"tableName": "monitoring_settings",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_local_logs_enabled` INTEGER NOT NULL DEFAULT 0, `tunnel_statistics_enabled` INTEGER NOT NULL DEFAULT 1, `tunnel_statistics_poll_interval` INTEGER NOT NULL DEFAULT 3)",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "isLocalLogsEnabled",
"columnName": "is_local_logs_enabled",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "0"
},
{
"fieldPath": "tunnelStatisticsEnabled",
"columnName": "tunnel_statistics_enabled",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "1"
},
{
"fieldPath": "tunnelStatisticsPollInterval",
"columnName": "tunnel_statistics_poll_interval",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "3"
}
],
"primaryKey": {
"autoGenerate": true,
"columnNames": [
"id"
]
}
},
{
"tableName": "dns_settings",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `dns_protocol` INTEGER NOT NULL DEFAULT 0, `dns_endpoint` TEXT, `global_tunnel_dns_enabled` INTEGER NOT NULL DEFAULT 0)",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "dnsProtocol",
"columnName": "dns_protocol",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "0"
},
{
"fieldPath": "dnsEndpoint",
"columnName": "dns_endpoint",
"affinity": "TEXT"
},
{
"fieldPath": "isGlobalTunnelDnsEnabled",
"columnName": "global_tunnel_dns_enabled",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "0"
}
],
"primaryKey": {
"autoGenerate": true,
"columnNames": [
"id"
]
}
},
{
"tableName": "lockdown_settings",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `bypass_lan` INTEGER NOT NULL DEFAULT 0, `metered` INTEGER NOT NULL DEFAULT 0, `dual_stack` INTEGER NOT NULL DEFAULT 0)",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "bypassLan",
"columnName": "bypass_lan",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "0"
},
{
"fieldPath": "metered",
"columnName": "metered",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "0"
},
{
"fieldPath": "dualStack",
"columnName": "dual_stack",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "0"
}
],
"primaryKey": {
"autoGenerate": true,
"columnNames": [
"id"
]
}
}
],
"setupQueries": [
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'fd4803fc483f41704303be9246dcfb4d')"
]
}
}
+3 -3
View File
@@ -156,7 +156,7 @@
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="@string/provider"
android:authorities="${providerAuthority}"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
@@ -177,7 +177,7 @@
<service
android:name=".service.tile.TunnelControlTile"
android:exported="true"
android:icon="@drawable/ic_qs_logo"
android:icon="@drawable/qs_logo"
android:label="@string/tunnel_control"
android:permission="android.permission.BIND_QUICK_SETTINGS_TILE">
<meta-data
@@ -194,7 +194,7 @@
<service
android:name=".service.tile.AutoTunnelControlTile"
android:exported="true"
android:icon="@drawable/ic_qs_logo"
android:icon="@drawable/qs_logo"
android:label="@string/auto_tunnel"
android:permission="android.permission.BIND_QUICK_SETTINGS_TILE">
<meta-data
View File
@@ -251,26 +251,6 @@ class MainActivity : AppCompatActivity() {
}
}
if (showLocalNetworkRationale) {
LocalNetworkPermissionDialog(
onDismiss = {
showLocalNetworkRationale = false
toaster.show(
message = context.getString(R.string.local_network_permission_denied),
type = ToastType.Warning,
duration = 6000.milliseconds,
)
},
onContinue = {
showLocalNetworkRationale = false
localNetworkPermissionLauncher.launch(
Manifest.permission.ACCESS_LOCAL_NETWORK
)
},
)
}
val startingStack = buildList {
add(Route.Tunnels)
if (intent?.action == Intent.ACTION_APPLICATION_PREFERENCES) add(Route.Settings)
@@ -360,6 +340,27 @@ class MainActivity : AppCompatActivity() {
},
)
if (showLocalNetworkRationale) {
LocalNetworkPermissionDialog(
onDismiss = {
showLocalNetworkRationale = false
toaster.show(
message =
context.getString(R.string.local_network_permission_denied),
type = ToastType.Warning,
duration = 6000.milliseconds,
)
},
onAttest = {
showLocalNetworkRationale = false
localNetworkPermissionLauncher.launch(
Manifest.permission.ACCESS_LOCAL_NETWORK
)
},
)
}
uiState.pendingWgImportUrl?.let { url ->
val host = Uri.parse(url).host ?: url
InfoDialog(
@@ -48,47 +48,51 @@ class RemoteControlReceiver : BroadcastReceiver(), KoinComponent {
}
override fun onReceive(context: Context, intent: Intent) {
val action = intent.action ?: return
val appAction = Action.fromAction(action) ?: return
val pendingResult = goAsync()
applicationScope.launch {
val settings = settingsRepository.getGeneralSettings()
try {
val action = intent.action ?: return@launch
val appAction = Action.fromAction(action) ?: return@launch
if (!settings.isRemoteControlEnabled) return@launch
val settings = settingsRepository.getGeneralSettings()
if (!validateKey(settings, intent)) return@launch
if (!settings.isRemoteControlEnabled) return@launch
when (appAction) {
Action.START_TUNNEL -> {
val tunnel =
resolveTunnel(intent)
?: tunnelsRepository.getDefaultTunnel()
?: return@launch
if (!validateKey(settings, intent)) return@launch
tunnelCoordinator.startTunnel(tunnel)
}
when (appAction) {
Action.START_TUNNEL -> {
val tunnel =
resolveTunnel(intent)
?: tunnelsRepository.getDefaultTunnel()
?: return@launch
Action.STOP_TUNNEL -> {
val tunnelName = intent.getStringExtra(EXTRA_TUN_NAME)
if (tunnelName == null) {
tunnelCoordinator.stopActiveTunnels()
return@launch
tunnelCoordinator.startTunnel(tunnel)
}
val tunnel = tunnelsRepository.findByTunnelName(tunnelName) ?: return@launch
Action.STOP_TUNNEL -> {
val tunnelName = intent.getStringExtra(EXTRA_TUN_NAME)
tunnelCoordinator.stopTunnel(tunnel.id)
}
if (tunnelName == null) {
tunnelCoordinator.stopActiveTunnels()
return@launch
}
Action.START_AUTO_TUNNEL -> {
autoTunnelCoordinator.enable()
}
val tunnel = tunnelsRepository.findByTunnelName(tunnelName) ?: return@launch
Action.STOP_AUTO_TUNNEL -> {
autoTunnelCoordinator.disable()
tunnelCoordinator.stopTunnel(tunnel.id)
}
Action.START_AUTO_TUNNEL -> {
autoTunnelCoordinator.enable()
}
Action.STOP_AUTO_TUNNEL -> {
autoTunnelCoordinator.disable()
}
}
} finally {
pendingResult.finish()
}
}
}
@@ -27,19 +27,31 @@ class RestartReceiver : BroadcastReceiver(), KoinComponent {
override fun onReceive(context: Context, intent: Intent) {
Timber.d("RestartReceiver triggered with action: ${intent.action}")
val pendingResult = goAsync()
applicationScope.launch {
when (intent.action) {
Intent.ACTION_BOOT_COMPLETED,
"android.intent.action.QUICKBOOT_POWERON",
"com.htc.intent.action.QUICKBOOT_POWERON" -> {
startupCoordinator.applyStartupPolicy()
}
Intent.ACTION_MY_PACKAGE_REPLACED -> {
Timber.i("Restoring state on package upgrade")
startupCoordinator.applyStartupPolicy()
logReader.deleteAndClearLogs()
appStateRepository.setShouldShowDonationSnackbar(true)
try {
when (intent.action) {
Intent.ACTION_BOOT_COMPLETED,
"android.intent.action.QUICKBOOT_POWERON",
"com.htc.intent.action.QUICKBOOT_POWERON" -> {
startupCoordinator.applyStartupPolicy()
}
Intent.ACTION_MY_PACKAGE_REPLACED -> {
Timber.i("Restoring state on package upgrade")
startupCoordinator.applyStartupPolicy()
logReader.deleteAndClearLogs()
appStateRepository.setShouldShowDonationSnackbar(true)
}
else -> {
Timber.w("Unhandled action in RestartReceiver: ${intent.action}")
}
}
} finally {
pendingResult.finish()
}
}
}
@@ -39,7 +39,7 @@ class AppBoostrapCoordinator(
listOf(
async { bootstrapDns() },
async { ensureGlobalConfig() },
async { restoreLockdown() },
async { restoreBackendConfiguration() },
)
try {
@@ -73,9 +73,13 @@ class AppBoostrapCoordinator(
tunnelRepository.ensureGlobalConfigExists()
}
private suspend fun restoreLockdown() {
private suspend fun restoreBackendConfiguration() {
val settings = settingsRepository.getGeneralSettings()
if (settings.seamlessRoamingEnabled) {
tunnelProvider.setSeamlessRoaming(true)
}
when (settings.tunnelMode) {
TunnelMode.LOCK_DOWN -> {
val lockdownSettings = lockdownRepository.getLockdownSettings()
@@ -5,7 +5,7 @@ import com.zaneschepke.wireguardautotunnel.domain.enums.TunnelMode
import com.zaneschepke.wireguardautotunnel.domain.repository.GeneralSettingRepository
import com.zaneschepke.wireguardautotunnel.domain.repository.LockdownSettingsRepository
class TunnelModeCoordinator(
class TunnelBackendCoordinator(
private val tunnelProvider: TunnelProvider,
private val settingsRepository: GeneralSettingRepository,
private val lockdownRepository: LockdownSettingsRepository,
@@ -42,7 +42,6 @@ class TunnelModeCoordinator(
when (newMode) {
TunnelMode.LOCK_DOWN -> {
val lockdownSettings = lockdownRepository.getLockdownSettings()
tunnelProvider.setLockDown(lockdownSettings).getOrThrow()
}
@@ -50,4 +49,9 @@ class TunnelModeCoordinator(
TunnelMode.PROXY -> Unit
}
}
suspend fun changeSeamlessRoaming(enabled: Boolean) {
tunnelProvider.setSeamlessRoaming(enabled).getOrThrow()
settingsRepository.updateSeamlessRoaming(enabled)
}
}
@@ -9,10 +9,12 @@ import com.zaneschepke.wireguardautotunnel.domain.enums.TunnelMode
import com.zaneschepke.wireguardautotunnel.domain.events.TunnelActionEvent
import com.zaneschepke.wireguardautotunnel.domain.model.DnsSettings
import com.zaneschepke.wireguardautotunnel.domain.model.GeneralSettings
import com.zaneschepke.wireguardautotunnel.domain.model.LockdownSettings
import com.zaneschepke.wireguardautotunnel.domain.model.MonitoringSettings
import com.zaneschepke.wireguardautotunnel.domain.model.ProxySettings
import com.zaneschepke.wireguardautotunnel.domain.model.TunnelConfig
import com.zaneschepke.wireguardautotunnel.domain.repository.GeneralSettingRepository
import com.zaneschepke.wireguardautotunnel.domain.repository.LockdownSettingsRepository
import com.zaneschepke.wireguardautotunnel.domain.repository.MonitoringSettingsRepository
import com.zaneschepke.wireguardautotunnel.domain.repository.ProxySettingsRepository
import com.zaneschepke.wireguardautotunnel.domain.repository.TunnelRepository
@@ -44,6 +46,7 @@ class TunnelCoordinator(
dnsSettingsRepository: RoomDnsSettingsRepository,
monitoringSettingsRepository: MonitoringSettingsRepository,
proxyRepository: ProxySettingsRepository,
lockdownModeRepository: LockdownSettingsRepository,
scope: CoroutineScope,
) {
@@ -66,6 +69,7 @@ class TunnelCoordinator(
val dns: DnsSettings,
val monitoring: MonitoringSettings,
val proxy: ProxySettings,
val lockdown: LockdownSettings,
)
private val runtimeSettingsSnapshot =
@@ -74,12 +78,14 @@ class TunnelCoordinator(
dnsSettingsRepository.flow,
monitoringSettingsRepository.flow,
proxyRepository.flow,
) { general, dns, monitoring, proxy ->
lockdownModeRepository.flow,
) { general, dns, monitoring, proxy, lockdown ->
RuntimeSettingsSnapshot(
general = general,
dns = dns,
monitoring = monitoring,
proxy = proxy,
lockdown = lockdown,
)
}
@@ -149,6 +155,7 @@ class TunnelCoordinator(
val dnsSettings = snapshot.dns
val proxySettings = snapshot.proxy
val monitoringSettings = snapshot.monitoring
val lockdownSettings = snapshot.lockdown
val config = tunnelConfig.getConfig()
val policy =
@@ -184,8 +191,10 @@ class TunnelCoordinator(
}
TunnelMode.LOCK_DOWN -> {
BackendMode.Proxy.KillSwitchPrimary(runConfig)
BackendMode.Proxy.KillSwitchPrimary(
runConfig,
lockdownSettings.toKillSwitchConfig(),
)
}
}
@@ -49,4 +49,8 @@ class TunnelBackendProvider(
override suspend fun disableLockDown(): Result<Unit> {
return backend.disableKillSwitch()
}
override suspend fun setSeamlessRoaming(enabled: Boolean): Result<Unit> {
return backend.setSeamlessRoaming(enabled)
}
}
@@ -20,6 +20,8 @@ interface TunnelProvider {
suspend fun disableLockDown(): Result<Unit>
suspend fun setSeamlessRoaming(enabled: Boolean): Result<Unit>
val backendStatus: StateFlow<BackendStatus>
val events: Flow<TunnelEvent>
@@ -34,7 +34,7 @@ import com.zaneschepke.wireguardautotunnel.data.entity.TunnelConfig
DnsSettings::class,
LockdownSettings::class,
],
version = 30,
version = 32,
autoMigrations =
[
AutoMigration(from = 1, to = 2),
@@ -63,6 +63,8 @@ import com.zaneschepke.wireguardautotunnel.data.entity.TunnelConfig
AutoMigration(from = 26, to = 27, spec = GlobalsMigration::class),
AutoMigration(from = 27, to = 28, spec = DonationMigration::class),
AutoMigration(from = 29, to = 30, spec = SingleConfigMigration::class),
AutoMigration(from = 30, to = 31),
AutoMigration(from = 31, to = 32),
],
exportSchema = true,
)
@@ -18,4 +18,7 @@ interface AutoTunnelSettingsDao {
@Query("UPDATE auto_tunnel_settings SET is_tunnel_enabled = :enabled")
suspend fun updateAutoTunnelEnabled(enabled: Boolean)
@Query("UPDATE auto_tunnel_settings SET disable_on_captive_portal = :enabled")
suspend fun updateDisableOnCaptivePortal(enabled: Boolean)
}
@@ -34,4 +34,7 @@ interface GeneralSettingsDao {
@Query("UPDATE general_settings SET screen_recording_security = :enabled")
suspend fun updateScreenRecordingSecurity(enabled: Boolean)
@Query("UPDATE general_settings SET seamless_roaming_enabled = :enabled")
suspend fun updateSeamlessRoaming(enabled: Boolean)
}
@@ -27,4 +27,6 @@ data class AutoTunnelSettings(
@ColumnInfo(name = "wifi_detection_method", defaultValue = "0")
val wifiDetectionMethod: WifiDetectionMethod = WifiDetectionMethod.fromValue(0),
@ColumnInfo(name = "start_on_boot", defaultValue = "0") val startOnBoot: Boolean = false,
@ColumnInfo(name = "disable_on_captive_portal", defaultValue = "1")
val disableTunnelOnCaptivePortal: Boolean = true,
)
@@ -34,4 +34,6 @@ data class GeneralSettings(
val isGlobalAmneziaEnabled: Boolean = false,
@ColumnInfo(name = "tunnel_scripting_enabled", defaultValue = "0")
val tunnelScriptingEnabled: Boolean = true,
@ColumnInfo(name = "seamless_roaming_enabled", defaultValue = "0")
val seamlessRoamingEnabled: Boolean = true,
)
@@ -16,6 +16,7 @@ fun Entity.toDomain(): Domain =
isTunnelOnUnsecureEnabled = isTunnelOnUnsecureEnabled,
wifiDetectionMethod = wifiDetectionMethod,
startOnBoot = startOnBoot,
disableTunnelOnCaptivePortal = disableTunnelOnCaptivePortal
)
fun Domain.toEntity(): Entity =
@@ -31,4 +32,5 @@ fun Domain.toEntity(): Entity =
isTunnelOnUnsecureEnabled = isTunnelOnUnsecureEnabled,
wifiDetectionMethod = wifiDetectionMethod,
startOnBoot = startOnBoot,
disableTunnelOnCaptivePortal = disableTunnelOnCaptivePortal
)
@@ -22,6 +22,7 @@ fun Entity.toDomain(): Domain =
screenRecordingSecurityEnabled = screenRecordingSecurityEnabled,
isGlobalAmneziaEnabled = isGlobalAmneziaEnabled,
tunnelScriptingEnabled = tunnelScriptingEnabled,
seamlessRoamingEnabled = seamlessRoamingEnabled,
)
fun Domain.toEntity(): Entity =
@@ -42,4 +43,5 @@ fun Domain.toEntity(): Entity =
screenRecordingSecurityEnabled = screenRecordingSecurityEnabled,
isGlobalAmneziaEnabled = isGlobalAmneziaEnabled,
tunnelScriptingEnabled = tunnelScriptingEnabled,
seamlessRoamingEnabled = seamlessRoamingEnabled,
)
@@ -1,15 +1,25 @@
package com.zaneschepke.wireguardautotunnel.data.network
import com.zaneschepke.wireguardautotunnel.BuildConfig
import io.ktor.client.HttpClient
import io.ktor.client.engine.okhttp.OkHttp
import io.ktor.client.plugins.DefaultRequest
import io.ktor.client.plugins.HttpTimeout
import io.ktor.client.plugins.contentnegotiation.ContentNegotiation
import io.ktor.http.HttpHeaders
import io.ktor.serialization.kotlinx.json.json
import kotlinx.serialization.json.Json
object KtorClient {
fun create(): HttpClient {
return HttpClient(OkHttp) {
install(DefaultRequest) {
headers {
append(HttpHeaders.UserAgent, "wgtunnel/${BuildConfig.VERSION_NAME} (Android)")
append(HttpHeaders.Accept, "*/*")
}
}
install(ContentNegotiation) {
json(
Json {
@@ -18,10 +28,11 @@ object KtorClient {
}
)
}
install(HttpTimeout) {
requestTimeoutMillis = 15000
connectTimeoutMillis = 15000
socketTimeoutMillis = 15000
requestTimeoutMillis = 120_000L
connectTimeoutMillis = 30_000L
socketTimeoutMillis = 120_000L
}
}
}
@@ -26,4 +26,8 @@ class RoomAutoTunnelSettingsRepository(private val autoTunnelSettingsDao: AutoTu
override suspend fun updateAutoTunnelEnabled(enabled: Boolean) {
autoTunnelSettingsDao.updateAutoTunnelEnabled(enabled)
}
override suspend fun updateDisableOnCaptivePortal(enabled: Boolean) {
autoTunnelSettingsDao.updateDisableOnCaptivePortal(enabled)
}
}
@@ -45,4 +45,8 @@ class RoomSettingsRepository(private val settingsDao: GeneralSettingsDao) :
override suspend fun updateScreenRecordingSecurity(enabled: Boolean) {
settingsDao.updateScreenRecordingSecurity(enabled)
}
override suspend fun updateSeamlessRoaming(enabled: Boolean) {
settingsDao.updateSeamlessRoaming(enabled)
}
}
@@ -19,7 +19,6 @@ import com.zaneschepke.wireguardautotunnel.util.network.NetworkUtils
import com.zaneschepke.wireguardautotunnel.viewmodel.AutoTunnelViewModel
import com.zaneschepke.wireguardautotunnel.viewmodel.ConfigEditViewModel
import com.zaneschepke.wireguardautotunnel.viewmodel.DnsViewModel
import com.zaneschepke.wireguardautotunnel.viewmodel.LicenseViewModel
import com.zaneschepke.wireguardautotunnel.viewmodel.LockdownViewModel
import com.zaneschepke.wireguardautotunnel.viewmodel.LoggerViewModel
import com.zaneschepke.wireguardautotunnel.viewmodel.MonitoringViewModel
@@ -80,7 +79,6 @@ val appModule = module {
viewModelOf(::AutoTunnelViewModel)
viewModel { (id: Int?) -> ConfigEditViewModel(get(), get(), get(), get(), get(), id) }
viewModelOf(::DnsViewModel)
viewModelOf(::LicenseViewModel)
viewModelOf(::LockdownViewModel)
viewModelOf(::LoggerViewModel)
viewModelOf(::MonitoringViewModel)
@@ -5,15 +5,15 @@ import com.zaneschepke.wireguardautotunnel.core.orchestration.AutoTunnelCoordina
import com.zaneschepke.wireguardautotunnel.core.orchestration.DnsSettingsCoordinator
import com.zaneschepke.wireguardautotunnel.core.orchestration.ShortcutCoordinator
import com.zaneschepke.wireguardautotunnel.core.orchestration.StartupCoordinator
import com.zaneschepke.wireguardautotunnel.core.orchestration.TunnelBackendCoordinator
import com.zaneschepke.wireguardautotunnel.core.orchestration.TunnelCoordinator
import com.zaneschepke.wireguardautotunnel.core.orchestration.TunnelModeCoordinator
import org.koin.core.module.dsl.singleOf
import org.koin.core.qualifier.named
import org.koin.dsl.module
val coordinatorModule = module {
singleOf(::ShortcutCoordinator)
singleOf(::TunnelModeCoordinator)
singleOf(::TunnelBackendCoordinator)
singleOf(::StartupCoordinator)
singleOf(::AutoTunnelCoordinator)
singleOf(::DnsSettingsCoordinator)
@@ -27,6 +27,7 @@ val coordinatorModule = module {
get(),
get(),
get(),
get(),
get(named(Scope.APPLICATION)),
)
}
@@ -14,4 +14,5 @@ data class AutoTunnelSettings(
val isTunnelOnUnsecureEnabled: Boolean = false,
val wifiDetectionMethod: WifiDetectionMethod = WifiDetectionMethod.fromValue(0),
val startOnBoot: Boolean = false,
val disableTunnelOnCaptivePortal: Boolean = true,
)
@@ -20,5 +20,6 @@ data class GeneralSettings(
val alreadyDonated: Boolean = false,
val screenRecordingSecurityEnabled: Boolean = true,
val isGlobalAmneziaEnabled: Boolean = false,
val tunnelScriptingEnabled: Boolean = true,
val tunnelScriptingEnabled: Boolean = false,
val seamlessRoamingEnabled: Boolean = false,
)
@@ -11,4 +11,6 @@ interface AutoTunnelSettingsRepository {
suspend fun getAutoTunnelSettings(): AutoTunnelSettings
suspend fun updateAutoTunnelEnabled(enabled: Boolean)
suspend fun updateDisableOnCaptivePortal(enabled: Boolean)
}
@@ -23,4 +23,6 @@ interface GeneralSettingRepository {
suspend fun updateGlobalAmneziaEnabled(enabled: Boolean)
suspend fun updateScreenRecordingSecurity(enabled: Boolean)
suspend fun updateSeamlessRoaming(enabled: Boolean)
}
@@ -11,16 +11,20 @@ sealed class ActiveNetwork {
data object Cellular : ActiveNetwork()
data class Wifi(val ssid: String, val isSecure: Boolean?) : ActiveNetwork()
data class Wifi(
val ssid: String,
val isSecure: Boolean?,
val requiresCaptivePortalLogin: Boolean,
) : ActiveNetwork()
}
data class NetworkState(
val activeNetwork: ActiveNetwork = ActiveNetwork.Disconnected,
val locationServicesEnabled: Boolean = false,
val locationPermissionGranted: Boolean = false,
) {
fun hasInternet(): Boolean = activeNetwork !is ActiveNetwork.Disconnected
}
// Has a network that can actually transfer data (not suspended)
val hasUsableNetwork: Boolean = false,
)
fun ConnectivityState.toDomain(): NetworkState {
val domainNetwork: ActiveNetwork =
@@ -33,7 +37,11 @@ fun ConnectivityState.toDomain(): NetworkState {
null -> null
else -> true
}
ActiveNetwork.Wifi(ssid = network.ssid, isSecure = isSecure)
ActiveNetwork.Wifi(
ssid = network.ssid,
isSecure = isSecure,
requiresCaptivePortalLogin(),
)
}
is MonitorActiveNetwork.Cellular -> ActiveNetwork.Cellular
is MonitorActiveNetwork.Ethernet -> ActiveNetwork.Ethernet
@@ -44,5 +52,6 @@ fun ConnectivityState.toDomain(): NetworkState {
activeNetwork = domainNetwork,
locationPermissionGranted = this.locationPermissionsGranted,
locationServicesEnabled = this.locationServicesEnabled,
hasUsableNetwork = hasUsableNetwork(),
)
}
@@ -28,7 +28,19 @@ class AutoTunnelEngine {
val activeTunnelIds = backend.activeTunnels.keys.toSet()
if (!network.hasInternet()) {
val isOnCaptivePortalWifi =
network.activeNetwork is ActiveNetwork.Wifi &&
network.activeNetwork.requiresCaptivePortalLogin
if (isOnCaptivePortalWifi && settings.disableTunnelOnCaptivePortal) {
return if (activeTunnelIds.isNotEmpty()) {
Decision.Sync(start = emptySet(), stop = activeTunnelIds)
} else {
Decision.None
}
}
if (!network.hasUsableNetwork) {
return if (settings.isStopOnNoInternetEnabled) {
Decision.StopDueToNoInternet
} else {
@@ -192,11 +192,11 @@ class AutoTunnelService : LifecycleService() {
reconciliationMutex.withLock {
val currentNetworkState = networkEngine.stableState.value?.state?.toDomain()
val stillNoInternet = currentNetworkState?.hasInternet() == false
val stillNoUsableNetwork = currentNetworkState?.hasUsableNetwork == false
val stopOnNoInternetEnabled =
autoTunnelRepository.flow.firstOrNull()?.isStopOnNoInternetEnabled == true
if (stillNoInternet && stopOnNoInternetEnabled) {
if (stillNoUsableNetwork && stopOnNoInternetEnabled) {
val currentActiveIds =
tunnelCoordinator.backendStatus.value.activeTunnels.keys
@@ -1,12 +1,11 @@
package com.zaneschepke.wireguardautotunnel.ui.common.dialog
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.height
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
@@ -15,11 +14,12 @@ import androidx.compose.ui.unit.dp
import com.zaneschepke.wireguardautotunnel.R
@Composable
fun LocalNetworkPermissionDialog(onDismiss: () -> Unit, onContinue: () -> Unit) {
AlertDialog(
onDismissRequest = onDismiss,
title = { Text(text = stringResource(R.string.local_network_permission_title)) },
text = {
fun LocalNetworkPermissionDialog(onDismiss: () -> Unit, onAttest: () -> Unit) {
InfoDialog(
onAttest = onAttest,
onDismiss = onDismiss,
title = stringResource(R.string.local_network_permission_title),
body = {
Column {
Text(
text = stringResource(R.string.local_network_permission_intro),
@@ -31,45 +31,43 @@ fun LocalNetworkPermissionDialog(onDismiss: () -> Unit, onContinue: () -> Unit)
Text(
text = stringResource(R.string.local_network_permission_issues_intro),
style = MaterialTheme.typography.bodyMedium,
fontWeight = FontWeight.Medium,
fontWeight = FontWeight.Bold,
)
Spacer(modifier = Modifier.height(4.dp))
Spacer(modifier = Modifier.height(8.dp))
Text(
text = stringResource(R.string.local_network_permission_feature_tunnels),
style = MaterialTheme.typography.bodyMedium,
)
Text(
text = stringResource(R.string.local_network_permission_feature_autotunnel),
style = MaterialTheme.typography.bodyMedium,
)
Text(
text = stringResource(R.string.local_network_permission_feature_proxy),
style = MaterialTheme.typography.bodyMedium,
)
Spacer(modifier = Modifier.height(16.dp))
Text(
text = stringResource(R.string.local_network_permission_nearby_devices),
style = MaterialTheme.typography.bodyMedium,
)
Column(verticalArrangement = Arrangement.spacedBy(4.dp)) {
Text(
text = stringResource(R.string.local_network_permission_feature_tunnels),
style = MaterialTheme.typography.bodyMedium,
)
Text(
text = stringResource(R.string.local_network_permission_feature_autotunnel),
style = MaterialTheme.typography.bodyMedium,
)
Text(
text = stringResource(R.string.local_network_permission_feature_proxy),
style = MaterialTheme.typography.bodyMedium,
)
}
Spacer(modifier = Modifier.height(16.dp))
Text(
text = stringResource(R.string.local_network_permission_recommendation),
style = MaterialTheme.typography.bodyMedium,
fontWeight = FontWeight.Medium,
fontWeight = FontWeight.Bold,
)
Spacer(modifier = Modifier.height(16.dp))
Text(
text = stringResource(R.string.local_network_permission_nearby_devices),
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurfaceVariant,
)
}
},
confirmButton = {
TextButton(onClick = onContinue) { Text(text = stringResource(R.string._continue)) }
},
dismissButton = {
TextButton(onClick = onDismiss) { Text(text = stringResource(R.string.not_now)) }
},
confirmText = stringResource(R.string._continue),
)
}
@@ -10,6 +10,7 @@ import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.Filter1
import androidx.compose.material.icons.outlined.Map
import androidx.compose.material.icons.outlined.PublicOff
import androidx.compose.material.icons.outlined.WifiFind
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
@@ -191,6 +192,21 @@ fun WifiSettingsScreen(viewModel: AutoTunnelViewModel = koinViewModel()) {
)
},
)
SurfaceRow(
leading = { Icon(Icons.Outlined.PublicOff, contentDescription = null) },
title = stringResource(R.string.stop_while_captive_portal),
onClick = {
viewModel.setDisabledOnCaptivePortal(
!uiState.autoTunnelSettings.disableTunnelOnCaptivePortal
)
},
trailing = {
ThemedSwitch(
checked = uiState.autoTunnelSettings.disableTunnelOnCaptivePortal,
onClick = { viewModel.setDisabledOnCaptivePortal(it) },
)
},
)
}
Column {
GroupLabel(stringResource(R.string.tunnels), Modifier.padding(horizontal = 16.dp))
@@ -9,6 +9,7 @@ import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.outlined.ViewQuilt
import androidx.compose.material.icons.outlined.Android
import androidx.compose.material.icons.outlined.CellWifi
import androidx.compose.material.icons.outlined.Dns
import androidx.compose.material.icons.outlined.ExpandMore
import androidx.compose.material.icons.outlined.MonitorHeart
@@ -234,6 +235,23 @@ fun SettingsScreen(
viewModel.setTunnelScriptedEnabled(!uiState.settings.tunnelScriptingEnabled)
},
)
SurfaceRow(
leading = { Icon(Icons.Outlined.CellWifi, contentDescription = null) },
title = stringResource(R.string.seamless_roaming),
trailing = { modifier ->
ThemedSwitch(
checked = uiState.settings.seamlessRoamingEnabled,
onClick = { viewModel.setSeamlessNetworkRoaming(enabled = it) },
modifier = modifier,
)
},
description = {
DescriptionText(stringResource(R.string.seamless_roaming_description))
},
onClick = {
viewModel.setSeamlessNetworkRoaming(!uiState.settings.seamlessRoamingEnabled)
},
)
SurfaceRow(
leading = { Icon(Icons.Outlined.MonitorHeart, null) },
title = stringResource(R.string.tunnel_monitoring),
@@ -0,0 +1,213 @@
package com.zaneschepke.wireguardautotunnel.ui.screens.support.license
import android.content.Context
import com.mikepenz.aboutlibraries.Libs
import com.mikepenz.aboutlibraries.entity.Developer
import com.mikepenz.aboutlibraries.entity.Library
import com.mikepenz.aboutlibraries.entity.License
import com.mikepenz.aboutlibraries.entity.Scm
import com.mikepenz.aboutlibraries.util.withContext
fun buildLibsWithAdditionalLibraries(context: Context): Libs {
val baseLibs = Libs.Builder().withContext(context).build()
val cleanedBaseLibs =
baseLibs.libraries.filterNot { library ->
library.uniqueId.contains("com.github.topjohnwu.libsu", ignoreCase = true) ||
library.uniqueId.contains("com.github.T8RIN.QuickieExtended", ignoreCase = true)
}
val nativeLibraries =
listOf(
Library(
uniqueId = "github.com.wgtunnel:amneziawg-go",
artifactVersion = "v0.0.0-20260618075902-e1b699b2104b",
name = "AmneziaWG Go (Fork)",
description = "WireGuard implementation with Amnezia obfuscation",
website = "https://wgtunnel.com",
developers =
listOf(
Developer(
name = "Zane Schepke (Fork Maintainer)",
organisationUrl = "https://wgtunnel.com",
),
Developer(
name = "Jason A. Donenfeld (Original WireGuard)",
organisationUrl = "https://www.wireguard.com/",
),
Developer(
name = "Amnezia VPN Team",
organisationUrl = "https://amnezia.org/",
),
),
organization = null,
scm = Scm(null, null, "https://github.com/wgtunnel/amneziawg-go"),
licenses =
setOf(
License(
name = "MIT License",
url = "https://opensource.org/licenses/MIT",
spdxId = "MIT",
hash = "mit-license-amneziawg-fork",
)
),
funding = emptySet(),
tag = "native",
),
Library(
uniqueId = "github.com.wgtunnel:wireproxy-awg",
artifactVersion = "v0.0.0-20260309043206-ff4200f20ff2",
name = "Wireproxy AWG (Fork)",
description = "WireGuard proxy with Amnezia support",
website = "https://wgtunnel.com",
developers =
listOf(
Developer(
name = "Zane Schepke (Fork Maintainer)",
organisationUrl = "https://wgtunnel.com",
),
Developer(name = "Artem Russkikh (Original)", organisationUrl = null),
),
organization = null,
scm = Scm(null, null, "https://github.com/wgtunnel/wireproxy-awg"),
licenses =
setOf(
License(
name = "MIT License",
url = "https://opensource.org/licenses/MIT",
spdxId = "MIT",
hash = "mit-license-wireproxy-fork",
)
),
funding = emptySet(),
tag = "native",
),
Library(
uniqueId = "github.com.wgtunnel:go-socks5",
artifactVersion = "v0.0.0-20260307052555-86f8d93b9534",
name = "go-socks5 (Fork)",
description = "SOCKS5 proxy server implementation",
website = "https://wgtunnel.com",
developers =
listOf(
Developer(
name = "Zane Schepke (Fork Maintainer)",
organisationUrl = "https://wgtunnel.com",
),
Developer(name = "Things-go Team (Original)", organisationUrl = null),
),
organization = null,
scm = Scm(null, null, "https://github.com/wgtunnel/go-socks5"),
licenses =
setOf(
License(
name = "MIT License",
url = "https://opensource.org/licenses/MIT",
spdxId = "MIT",
hash = "mit-license-go-socks5-fork",
)
),
funding = emptySet(),
tag = "native",
),
Library(
uniqueId = "github.com.miekg:dns",
artifactVersion = "v1.1.69",
name = "miekg/dns",
description = "DNS library for Go",
website = "https://github.com/miekg/dns",
developers = listOf(Developer(name = "Miek Gieben", organisationUrl = null)),
organization = null,
scm = Scm(null, null, "https://github.com/miekg/dns"),
licenses =
setOf(
License(
name = "BSD 3-Clause \"New\" or \"Revised\" License",
url = "https://opensource.org/licenses/BSD-3-Clause",
spdxId = "BSD-3-Clause",
hash = "bsd3-miekg-dns",
)
),
funding = emptySet(),
tag = "go",
),
Library(
uniqueId = "github.com.heiher:hev-socks5-tunnel",
artifactVersion = "2.15.0",
name = "hev-socks5-tunnel",
description = "High performance SOCKS5 tunnel",
website = "https://github.com/heiher/hev-socks5-tunnel",
developers = listOf(Developer(name = "heiher", organisationUrl = null)),
organization = null,
scm = Scm(null, null, "https://github.com/heiher/hev-socks5-tunnel"),
licenses =
setOf(
License(
name = "MIT License",
url = "https://opensource.org/licenses/MIT",
spdxId = "MIT",
hash = "mit-license-hev",
)
),
funding = emptySet(),
tag = "native",
),
)
val additionalLibraries =
listOf(
Library(
uniqueId = "com.github.T8RIN.QuickieExtended:quickie-foss",
artifactVersion = "1.18.1",
name = "QuickieFoss",
description = "Camera QR code scanner",
website = "https://github.com/T8RIN/QuickieExtended",
developers = listOf(Developer(name = "T8RIN", null)),
organization = null,
scm = Scm(null, null, "https://github.com/T8RIN/QuickieExtended"),
licenses =
setOf(
License(
name = "Apache License 2.0",
url = "https://www.apache.org/licenses/LICENSE-2.0",
spdxId = "Apache-2.0",
hash = "apache-2-quickie",
)
),
funding = emptySet(),
tag = "ui",
),
Library(
uniqueId = "com.github.topjohnwu.libsu:core",
artifactVersion = "6.0.0",
name = "libsu",
description = "Root shell library for Android",
website = "https://github.com/topjohnwu/libsu",
developers = listOf(Developer(name = "topjohnwu", null)),
organization = null,
scm = Scm(null, null, "https://github.com/topjohnwu/libsu"),
licenses =
setOf(
License(
name = "Apache License 2.0",
url = "https://www.apache.org/licenses/LICENSE-2.0",
spdxId = "Apache-2.0",
hash = "apache-2-libsu",
)
),
funding = emptySet(),
tag = "system",
),
)
return Libs(
libraries =
(cleanedBaseLibs + nativeLibraries + additionalLibraries).sortedBy {
it.name.lowercase()
},
licenses =
baseLibs.licenses +
nativeLibraries.flatMap { it.licenses } +
additionalLibraries.flatMap { it.licenses },
)
}
@@ -1,15 +0,0 @@
import kotlinx.serialization.Serializable
@Serializable
data class LicenseFileEntry(
val groupId: String,
val artifactId: String,
val version: String,
val name: String? = null,
val spdxLicenses: List<SpdxLicense> = emptyList(),
val scm: Scm? = null,
)
@Serializable data class SpdxLicense(val identifier: String, val name: String, val url: String)
@Serializable data class Scm(val url: String)
@@ -1,30 +1,23 @@
package com.zaneschepke.wireguardautotunnel.ui.screens.support.license
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.size
import androidx.compose.material3.CircularWavyProgressIndicator
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import com.zaneschepke.wireguardautotunnel.ui.screens.support.license.components.LicenseList
import com.zaneschepke.wireguardautotunnel.viewmodel.LicenseViewModel
import org.koin.androidx.compose.koinViewModel
import org.orbitmvi.orbit.compose.collectAsState
import androidx.compose.ui.platform.LocalContext
import com.mikepenz.aboutlibraries.ui.compose.m3.LibrariesContainer
import com.mikepenz.aboutlibraries.ui.compose.variant.LibraryDetailMode
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
@Composable
fun LicenseScreen(viewModel: LicenseViewModel = koinViewModel()) {
val licenseUiState by viewModel.collectAsState()
fun LicenseScreen() {
val context = LocalContext.current
val libs = remember { buildLibsWithAdditionalLibraries(context) }
if (licenseUiState.isLoading) {
Box(Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
CircularWavyProgressIndicator(waveSpeed = 60.dp, modifier = Modifier.size(48.dp))
}
} else {
LicenseList(licenseUiState.licenses)
}
LibrariesContainer(
libraries = libs,
modifier = Modifier.fillMaxSize(),
detailMode = LibraryDetailMode.Sheet,
)
}
@@ -1,69 +0,0 @@
package com.zaneschepke.wireguardautotunnel.ui.screens.support.license.components
import LicenseFileEntry
import androidx.compose.foundation.clickable
import androidx.compose.foundation.gestures.Orientation
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.outlined.Launch
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.material3.scrollbar
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.unit.dp
import com.zaneschepke.wireguardautotunnel.util.extensions.openWebUrl
@Composable
fun LicenseList(licenses: List<LicenseFileEntry>) {
val context = LocalContext.current
val lazyListState = rememberLazyListState()
LazyColumn(
modifier =
Modifier.fillMaxSize()
.scrollbar(
state = lazyListState.scrollIndicatorState,
orientation = Orientation.Vertical,
),
state = lazyListState,
) {
items(licenses) { entry ->
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween,
modifier =
Modifier.clickable(enabled = entry.scm?.url != null) {
entry.scm?.url?.let { context.openWebUrl(it) }
}
.padding(horizontal = 16.dp, vertical = 8.dp),
) {
Column(modifier = Modifier.weight(1f)) {
Text(
text = "${entry.artifactId} (${entry.version})",
style = MaterialTheme.typography.titleSmall,
)
entry.spdxLicenses.forEach { license ->
Text(
text = license.name,
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.primary,
)
}
}
entry.scm?.url?.let { Icon(Icons.AutoMirrored.Outlined.Launch, null) }
}
}
}
}
@@ -1,8 +0,0 @@
package com.zaneschepke.wireguardautotunnel.ui.state
import LicenseFileEntry
data class LicenseUiState(
val isLoading: Boolean = true,
val licenses: List<LicenseFileEntry> = emptyList(),
)
@@ -6,15 +6,11 @@ object Constants {
const val BASE_LOG_FILE_NAME = "wg_tunnel_logs"
const val VPN_SETTINGS_PACKAGE = "android.net.vpn.SETTINGS"
const val SYSTEM_EXEMPT_SERVICE_TYPE_ID = 1 shl 10
const val SPECIAL_USE_SERVICE_TYPE_ID = 1 shl 30
const val QR_CODE_NAME_PROPERTY = "# Name ="
const val FDROID_FLAVOR = "fdroid"
const val GOOGLE_PLAY_FLAVOR = "google"
const val STANDALONE_FLAVOR = "standalone"
const val RELEASE = "release"
const val BASE_RELEASE_URL = "https://github.com/wgtunnel/wgtunnel/releases/tag/"
}
@@ -81,7 +81,7 @@ object DnsValidator {
return Result.Valid
}
private fun validateUdp(value: String): DnsValidator.Result {
private fun validateUdp(value: String): Result {
val parts = value.split(":")
val host = parts.getOrNull(0)?.trim()
@@ -93,14 +93,14 @@ object DnsValidator {
// basic IP/hostname sanity check
if (!isValidHostOrIp(host)) {
return DnsValidator.Result.Invalid(DnsError.InvalidIpOrHost)
return Result.Invalid(DnsError.InvalidIpOrHost)
}
if (port !in 1..65535) {
return DnsValidator.Result.Invalid(DnsError.InvalidPort)
return Result.Invalid(DnsError.InvalidPort)
}
return DnsValidator.Result.Valid
return Result.Valid
}
private fun isValidHostOrIp(value: String): Boolean {
@@ -1,6 +1,5 @@
package com.zaneschepke.wireguardautotunnel.util
import LicenseFileEntry
import android.content.ContentValues
import android.content.Context
import android.database.Cursor
@@ -23,7 +22,6 @@ import java.util.zip.ZipInputStream
import java.util.zip.ZipOutputStream
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.withContext
import kotlinx.serialization.json.Json
import timber.log.Timber
class FileUtils(private val context: Context, private val ioDispatcher: CoroutineDispatcher) {
@@ -293,15 +291,6 @@ class FileUtils(private val context: Context, private val ioDispatcher: Coroutin
return@withContext itemUri
}
suspend fun readLibraryLicensesFromAssets(): List<LicenseFileEntry> =
withContext(ioDispatcher) {
val json = Json { ignoreUnknownKeys = true }
val jsonResult =
context.assets.open("licenses.json").bufferedReader().use { it.readText() }
json.decodeFromString(jsonResult)
}
companion object {
const val CONF_FILE_EXTENSION = ".conf"
const val ZIP_FILE_EXTENSION = ".zip"
@@ -1,22 +1,9 @@
package com.zaneschepke.wireguardautotunnel.util
import com.vdurmont.semver4j.Semver
import java.math.BigDecimal
import kotlin.math.pow
import timber.log.Timber
object NumberUtils {
private const val BYTES_IN_KB = 1024.0
private val BYTES_IN_MB = BYTES_IN_KB.pow(2.0)
private val keyValidationRegex = """^[A-Za-z0-9+/]{42}[AEIMQUYcgkosw480]=${'$'}""".toRegex()
fun bytesToMB(bytes: Long): BigDecimal {
return bytes.toBigDecimal().divide(BYTES_IN_MB.toBigDecimal())
}
fun isValidKey(key: String): Boolean {
return key.matches(keyValidationRegex)
}
fun generateRandomTunnelName(): String {
return "tunnel${randomFive()}"
@@ -1,26 +1,18 @@
package com.zaneschepke.wireguardautotunnel.util.extensions
import android.Manifest
import android.app.Activity
import android.content.ComponentName
import android.content.Context
import android.content.Context.POWER_SERVICE
import android.content.Intent
import android.content.pm.ApplicationInfo
import android.content.pm.PackageInfo
import android.content.pm.PackageManager
import android.net.Uri
import android.os.Build
import android.os.PowerManager
import android.provider.Settings
import android.service.quicksettings.TileService
import androidx.core.content.FileProvider
import androidx.core.net.toUri
import com.zaneschepke.wireguardautotunnel.BuildConfig
import com.zaneschepke.wireguardautotunnel.MainActivity
import com.zaneschepke.wireguardautotunnel.R
import com.zaneschepke.wireguardautotunnel.service.tile.AutoTunnelControlTile
import com.zaneschepke.wireguardautotunnel.service.tile.TunnelControlTile
import com.zaneschepke.wireguardautotunnel.ui.screens.tunnels.splittunnel.state.TunnelApp
import com.zaneschepke.wireguardautotunnel.util.Constants
import com.zaneschepke.wireguardautotunnel.util.FileUtils
import java.io.File
@@ -36,11 +28,6 @@ fun Context.openWebUrl(url: String): Result<Unit> = runCatching {
startActivity(intent)
}
fun Context.isBatteryOptimizationsDisabled(): Boolean {
val pm = getSystemService(POWER_SERVICE) as PowerManager
return pm.isIgnoringBatteryOptimizations(packageName)
}
fun Context.launchNotificationSettings() {
if (isRunningOnTv()) return launchAppSettings()
val settingsIntent: Intent =
@@ -87,21 +74,6 @@ fun Context.hasSAFSupport(mimeType: String): Boolean {
}
}
fun Context.launchShareFile(file: File) {
FileProvider.getUriForFile(this, getString(R.string.provider), file)
val shareIntent =
Intent().apply {
action = Intent.ACTION_SEND
type = FileUtils.ALL_FILE_TYPES
putExtra(Intent.EXTRA_STREAM, file)
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
}
val chooserIntent =
Intent.createChooser(shareIntent, "").apply { addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) }
this.startActivity(chooserIntent)
}
fun Context.launchSupportEmail(): Result<Unit> = runCatching {
val intent =
Intent(Intent.ACTION_SENDTO).apply {
@@ -128,7 +100,7 @@ fun Context.isRunningOnTv(): Boolean {
fun Context.launchVpnSettings(): Result<Unit> {
return kotlin.runCatching {
val intent =
Intent(Constants.VPN_SETTINGS_PACKAGE).apply { setFlags(Intent.FLAG_ACTIVITY_NEW_TASK) }
Intent(Constants.VPN_SETTINGS_PACKAGE).apply { flags = Intent.FLAG_ACTIVITY_NEW_TASK }
startActivity(intent)
}
}
@@ -147,14 +119,6 @@ fun Context.launchLocationServicesSettings(): Result<Unit> {
}
}
fun Context.launchSettings(): Result<Unit> {
return kotlin.runCatching {
val intent =
Intent(Settings.ACTION_SETTINGS).apply { flags = Intent.FLAG_ACTIVITY_NEW_TASK }
startActivity(intent)
}
}
fun Context.launchAppSettings() {
kotlin
.runCatching {
@@ -171,48 +135,6 @@ fun Context.launchAppSettings() {
}
}
fun Context.requestTunnelTileServiceStateUpdate() =
runCatching {
TileService.requestListeningState(
this,
ComponentName(this, TunnelControlTile::class.java),
)
}
.onFailure { Timber.w(it) }
fun Context.requestAutoTunnelTileServiceUpdate() =
runCatching {
TileService.requestListeningState(
this,
ComponentName(this, AutoTunnelControlTile::class.java),
)
}
.onFailure { Timber.w(it) }
fun Context.getAllInternetCapablePackages(): List<PackageInfo> {
val permissions = arrayOf(Manifest.permission.INTERNET)
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
packageManager.getPackagesHoldingPermissions(
permissions,
PackageManager.PackageInfoFlags.of(0L),
)
} else {
packageManager.getPackagesHoldingPermissions(permissions, 0)
}
}
fun Context.getSplitTunnelApps(): List<TunnelApp> {
val packages = getAllInternetCapablePackages()
return packages
.filter { it.applicationInfo != null }
.map { pkg ->
TunnelApp(
packageManager.getApplicationLabel(pkg.applicationInfo!!).toString(),
pkg.packageName,
)
}
}
fun Context.canInstallPackages(): Boolean {
return packageManager.canRequestPackageInstalls()
}
@@ -227,7 +149,7 @@ fun Context.requestInstallPackagesPermission() {
}
fun Context.installApk(apkFile: File) {
val apkUri = FileProvider.getUriForFile(this, getString(R.string.provider), apkFile)
val apkUri = FileProvider.getUriForFile(this, BuildConfig.FILE_PROVIDER_AUTHORITY, apkFile)
val intent =
Intent(Intent.ACTION_VIEW).apply {
setDataAndType(apkUri, "application/vnd.android.package-archive")
@@ -1,8 +0,0 @@
package com.zaneschepke.wireguardautotunnel.util.extensions
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.distinctUntilChanged
fun <K, V> Flow<Map<K, V>>.distinctByKeys(): Flow<Map<K, V>> {
return distinctUntilChanged { old, new -> old.keys == new.keys }
}
@@ -18,10 +18,6 @@ fun <T, R : Comparable<R>> List<T>.isSortedBy(selector: (T) -> R): Boolean {
return zipWithNext().all { (a, b) -> selector(a) <= selector(b) }
}
fun Int.toMillis(): Long {
return this * 1_000L
}
fun Double.round(decimals: Int): Double {
val factor = 10.0.pow(decimals)
return (this * factor).roundToInt() / factor
@@ -145,6 +145,10 @@ class AutoTunnelViewModel(
)
}
fun setDisabledOnCaptivePortal(enabled: Boolean) = intent {
autoTunnelRepository.updateDisableOnCaptivePortal(enabled)
}
fun removeTunnelNetwork(tunnel: TunnelConfig, ssid: String) = intent {
tunnelsRepository.save(
tunnel.copy(
@@ -1,23 +0,0 @@
package com.zaneschepke.wireguardautotunnel.viewmodel
import androidx.lifecycle.ViewModel
import com.zaneschepke.wireguardautotunnel.ui.state.LicenseUiState
import com.zaneschepke.wireguardautotunnel.util.FileUtils
import kotlinx.coroutines.ExperimentalCoroutinesApi
import org.orbitmvi.orbit.ContainerHost
import org.orbitmvi.orbit.viewmodel.container
class LicenseViewModel(private val fileUtils: FileUtils) :
ContainerHost<LicenseUiState, Nothing>, ViewModel() {
@OptIn(ExperimentalCoroutinesApi::class)
override val container =
container<LicenseUiState, Nothing>(
LicenseUiState(),
buildSettings = { repeatOnSubscribedStopTimeout = 5000L },
) {
intent {
val licenses = fileUtils.readLibraryLicensesFromAssets()
reduce { state.copy(isLoading = false, licenses = licenses) }
}
}
}
@@ -4,6 +4,7 @@ import androidx.lifecycle.ViewModel
import com.dokar.sonner.ToastType
import com.zaneschepke.tunnel.util.RootShell
import com.zaneschepke.wireguardautotunnel.R
import com.zaneschepke.wireguardautotunnel.core.orchestration.TunnelBackendCoordinator
import com.zaneschepke.wireguardautotunnel.core.orchestration.TunnelCoordinator
import com.zaneschepke.wireguardautotunnel.core.shortcut.ShortcutManager
import com.zaneschepke.wireguardautotunnel.domain.repository.GeneralSettingRepository
@@ -27,6 +28,7 @@ class SettingsViewModel(
private val monitoringRepository: MonitoringSettingsRepository,
private val globalEffectRepository: GlobalEffectRepository,
private val tunnelCoordinator: TunnelCoordinator,
private val tunnelBackendCoordinator: TunnelBackendCoordinator,
) : ContainerHost<SettingUiState, Nothing>, ViewModel() {
override val container =
@@ -114,6 +116,10 @@ class SettingsViewModel(
settingsRepository.upsert(state.settings.copy(tunnelScriptingEnabled = to))
}
fun setSeamlessNetworkRoaming(enabled: Boolean) = intent {
tunnelBackendCoordinator.changeSeamlessRoaming(enabled)
}
fun setAlreadyDonated(to: Boolean) = intent {
settingsRepository.upsert(state.settings.copy(alreadyDonated = to))
}
@@ -5,8 +5,8 @@ import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.dokar.sonner.ToastType
import com.zaneschepke.wireguardautotunnel.R
import com.zaneschepke.wireguardautotunnel.core.orchestration.TunnelBackendCoordinator
import com.zaneschepke.wireguardautotunnel.core.orchestration.TunnelCoordinator
import com.zaneschepke.wireguardautotunnel.core.orchestration.TunnelModeCoordinator
import com.zaneschepke.wireguardautotunnel.domain.enums.TunnelMode
import com.zaneschepke.wireguardautotunnel.domain.model.TunnelConfig
import com.zaneschepke.wireguardautotunnel.domain.repository.AppStateRepository
@@ -61,7 +61,7 @@ class SharedAppViewModel(
private val settingsRepository: GeneralSettingRepository,
private val autoTunnelStateHolder: AutoTunnelStateHolder,
private val selectedTunnelsRepository: SelectedTunnelsRepository,
private val tunnelModeCoordinator: TunnelModeCoordinator,
private val tunnelBackendCoordinator: TunnelBackendCoordinator,
private val httpClient: HttpClient,
private val fileUtils: FileUtils,
private val networkUtils: NetworkUtils,
@@ -172,7 +172,7 @@ class SharedAppViewModel(
}
}
tunnelModeCoordinator.changeMode(mode)
tunnelBackendCoordinator.changeMode(mode)
}
fun setShouldShowDonationSnackbar(to: Boolean) = intent {
-20
View File
@@ -1,20 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="1024"
android:viewportHeight="1024">
<group
android:scaleX="1.18"
android:scaleY="1.18"
android:pivotX="512"
android:pivotY="512"
android:translateX="-45"
android:translateY="-45">
<path
android:pathData="M779.7,207.8C782.5,207.8 785.4,207.8 788.2,207.8C851.8,207.6 851.8,207.6 871.1,225.5C882.4,237.3 887.6,250.9 887.5,267.1C886.9,284.9 879,300.9 872,317C871,319.2 870.1,321.4 869.1,323.7C867.7,327.1 866.2,330.6 864.7,334.1C861,342.7 857.3,351.4 853.7,360.1C847.1,375.7 840.5,391.3 833.8,406.9C831.1,413.1 828.5,419.2 825.8,425.4C822.9,432.3 819.9,439.2 817,446C804.9,474 804.9,474 793.3,502.2C791,507.8 788.5,513.3 785.9,518.7C782,527 778.5,535.5 775,544C772,551 769,557.9 766.1,564.9C765.6,566 765.1,567.2 764.6,568.4C763.5,570.7 762.5,573.1 761.5,575.5C760,579 758.5,582.6 757,586.1C751.8,598.2 746.6,610.4 741.2,622.4C737.3,631.3 733.4,640.3 729.5,649.2C725.8,657.9 722,666.6 718,675.2C715.7,680.4 713.5,685.6 711.4,690.8C708.5,697.8 705.4,704.7 702.1,711.6C700.8,714.5 699.4,717.4 698,720.4C685.7,746.7 672.9,772.5 643.9,783.1C639.6,784.4 635.5,784.7 631,785C630.2,785.1 629.5,785.1 628.7,785.2C610.7,785.8 596.7,779.5 583.5,767.8C569.4,754.5 562,735.7 554.3,718.3C552.1,713.5 549.9,708.7 547.5,704.1C543.9,696.9 540.8,689.6 537.6,682.2C534.6,675 531.4,667.8 528.3,660.6C522.3,646.9 516.3,633.3 510.4,619.6C508.5,615.1 506.5,610.6 504.6,606.1C494.1,582.1 494.1,582.1 489.6,571.2C488,567.4 486.4,563.6 484.6,559.8C481.8,553.6 479.2,547.4 476.6,541.1C472.4,531.2 472.4,531.2 468,521.5C461.3,507.3 455.5,492.6 449.5,478.1C444.9,466.9 440.2,455.8 435.3,444.7C431.5,436.1 427.7,427.4 424.1,418.8C423.8,418.1 423.5,417.4 423.2,416.6C420,409.1 416.8,401.6 413.7,394C413.4,393.3 413.2,392.7 412.9,392C411.6,389 410.4,386 409.2,383C406.8,377.1 404.3,371.4 401.6,365.7C397.9,357.9 394.6,349.9 391.3,341.9C390.4,339.6 389.5,337.4 388.6,335.2C388,333.7 387.3,332.2 386.7,330.7C384.7,325.7 382.6,320.7 380.6,315.8C380,314.5 379.5,313.2 378.9,311.9C377.9,309.5 376.9,307.1 375.9,304.7C368.4,286.7 361.9,264.8 369.7,245.7C373.8,237.4 377.9,230 385,224C385.6,223.5 386.2,223 386.8,222.4C397,213.9 412.5,209.7 425.8,210.1C442.3,212.1 455.2,220.4 466,233C471.9,241.6 476.5,250.7 480.9,260.1C481.5,261.3 482.2,262.6 482.8,263.9C487.3,273.5 491.7,283.1 496,292.8C497,294.9 497.9,297.1 498.9,299.2C503.6,309.6 508.1,320.1 512.5,330.6C515.1,336.6 517.7,342.5 520.5,348.4C524.1,356.1 527.4,363.9 530.6,371.8C531.7,374.4 532.8,377 533.9,379.7C534.3,380.6 534.3,380.6 534.7,381.6C536.3,385.3 537.8,388.9 539.5,392.5C541.5,396.7 543.3,401 545.1,405.3C545.4,406 545.6,406.6 545.9,407.3C547,409.9 548.1,412.6 549.2,415.2C552.5,423.2 555.9,431.1 559.6,438.9C561.7,443.4 563.7,447.9 565.5,452.5C567.6,457.7 569.8,462.9 572.3,467.9C575.9,475.7 579.2,483.5 582.5,491.4C585.8,499.2 589.1,507 592.5,514.7C617.2,571.5 617.2,571.5 625.1,591.7C625.8,593.9 625.8,593.9 627,595C627.3,594.3 627.6,593.5 627.9,592.7C632.8,580 637.7,567.3 643.4,554.9C646.9,547.2 650.2,539.5 653.4,531.8C653.7,531.1 654,530.4 654.3,529.7C657.2,522.9 660,516 662.9,509.1C663.3,508 663.8,506.9 664.2,505.8C665,503.9 665.9,501.9 666.7,499.9C668.9,494.5 671.3,489.1 673.8,483.7C675.7,479.4 677.6,475 679.5,470.7C679.9,469.7 680.3,468.7 680.8,467.7C682.2,464.5 683.6,461.2 685,458C686,455.6 687.1,453.3 688.1,450.9C689.7,447.1 691.3,443.4 693,439.6C695.8,433.2 698.5,426.8 701.3,420.3C714.6,389.7 714.6,389.7 727.2,358.9C729.7,352.7 732.4,346.8 735.3,340.8C736.9,337.4 738.2,333.8 739.6,330.3C740.1,329.1 740.5,327.9 741,326.7C741.3,325.8 741.6,324.9 742,324C741.2,324 741.2,324 740.3,324C726.5,324.1 712.7,324.1 698.9,324.1C692.2,324.1 685.5,324.1 678.9,324.2C672.4,324.2 665.9,324.2 659.5,324.2C657,324.2 654.6,324.2 652.1,324.2C640.5,324.3 629,324.2 617.4,323.1C616.6,323.1 615.8,323 614.9,322.9C597,321 581.4,314.3 569,301C566.5,297.8 564.7,294.6 563,291C562.5,290 562,289 561.5,288C556.5,276.1 555.8,261.8 559.6,249.4C562,243.5 565.3,238.2 569,233C569.7,232 569.7,232 570.4,231C581.2,217.3 599.1,212.3 615.8,210.2C637.1,208 659,208.7 680.4,208.5C683.5,208.4 686.5,208.4 689.5,208.4C719.6,208.1 749.6,207.9 779.7,207.8Z"
android:fillColor="#FFFFFF"/>
<path
android:pathData="M263.3,223.5C276.3,234.5 282.6,248.9 289.6,264C290.9,266.8 292.2,269.5 293.5,272.2C303,291.5 311.4,311.3 320,331C320.3,331.7 320.6,332.5 321,333.2C323.4,338.9 325.9,344.5 328.3,350.2C332.2,359.1 336.1,368.1 340,377C346.2,391.1 352.3,405.2 358.4,419.3C360.8,424.9 363.3,430.5 365.7,436.1C366.5,438 367.3,439.9 368.1,441.7C370.6,447.4 373,453 375.5,458.7C382.5,474.8 389.4,491 396.4,507.2C398.1,511.3 399.9,515.5 401.7,519.6C408.7,536 415.8,552.5 422.7,568.9C424,572 425.3,575.2 426.7,578.3C433,593.2 439.2,608.1 445.5,623C447.5,627.7 449.4,632.4 451.4,637C453.8,642.9 456.3,648.7 458.7,654.6C459.9,657.5 461.2,660.4 462.4,663.3C467.1,674.6 471.8,685.9 476.4,697.3C477.1,698.8 477.1,698.8 477.7,700.4C484.3,716.8 488.7,735.5 482,752.6C479.3,758.3 475.9,763.2 472,768C471.6,768.6 471.1,769.2 470.7,769.8C464.5,777.8 455.6,782.4 446,785C445,785.3 444,785.7 443,786C431.6,787 420.6,786.5 410,782C408.7,781.5 408.7,781.5 407.4,781C380.7,769.4 369.2,736.1 358.2,711.6C356.7,708.3 355.1,705 353.6,701.7C340.8,674.4 329,646.7 317.5,618.9C316,615.2 314.3,611.5 312.7,607.9C309.7,601.4 306.9,594.9 304.1,588.4C303.9,587.8 303.6,587.2 303.3,586.5C299.7,578 296.1,569.4 292.6,560.8C291,556.9 289.2,553 287.4,549.1C284.7,543 282,536.9 279.3,530.8C278.7,529.3 278.1,527.9 277.4,526.5C276.2,523.6 274.9,520.8 273.7,517.9C272.3,514.6 270.8,511.3 269.4,508C264.2,496.1 259.1,484.2 254.1,472.3C250.4,463.6 246.7,455 242.9,446.3C242.5,445.4 242.2,444.5 241.8,443.6C240,439.6 238.3,435.6 236.5,431.6C234.9,427.8 233.2,424.1 231.6,420.4C231.4,419.7 231.1,419.1 230.8,418.5C226.1,407.6 221.5,396.7 217,385.8C214.5,379.6 211.9,373.5 209.2,367.5C207.4,363.7 205.8,359.9 204.1,356.1C203.8,355.2 203.4,354.4 203,353.5C199.1,344.2 195.2,334.9 191.3,325.6C191,324.9 190.7,324.1 190.3,323.3C187.1,315.5 183.8,307.6 180.7,299.8C180.2,298.6 179.7,297.4 179.2,296.2C177.6,291.9 176.2,287.5 175,283C174.8,282.4 174.7,281.8 174.5,281.2C171.2,266.5 173.7,250.9 181.4,238.2C191.3,223.5 203.1,215.7 220.6,212.3C236.3,210.9 250.7,213.5 263.3,223.5Z"
android:fillColor="#FFFFFF"/>
</group>
</vector>
+7
View File
@@ -0,0 +1,7 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:viewportHeight="1024" android:viewportWidth="1024" android:width="24dp">
<path android:fillColor="#FEFEFE" android:pathData="M654.6,237.7C658.8,237.7 663.1,237.7 667.4,237.7C677.4,237.7 687.5,237.7 697.6,237.7C707.9,237.7 718.2,237.7 728.6,237.7C737.5,237.6 746.4,237.6 755.4,237.6C760.7,237.6 766,237.6 771.3,237.6C776.3,237.6 781.3,237.6 786.3,237.6C788.1,237.6 789.9,237.6 791.7,237.6C807.6,237.5 826.2,238.6 838.3,250.2C849.7,262.5 857,276.4 856.4,293.7C855.7,300 853.9,306 852,312C851.7,313.1 851.3,314.1 851,315.3C845.5,331.6 837.9,347.2 830.5,362.7C825.4,373.2 821,383.7 817.2,394.7C815.8,398.5 814.2,402 812.5,405.6C810.4,410.4 808.5,415.1 806.5,419.9C806.1,420.9 805.7,421.9 805.3,422.9C804.5,424.8 803.8,426.7 803,428.6C799.2,438 795.1,447.3 791.1,456.6C787.3,465.3 783.5,474.1 779.8,483C777.8,487.9 775.7,492.8 773.4,497.6C771.5,501.8 769.8,506 768.1,510.3C765.8,516 763.2,521.7 760.8,527.4C757,536.1 753.2,544.8 749.6,553.5C746.9,560 743.9,566.3 740.7,572.6C736.3,581.3 732.6,590.3 729.1,599.4C728.8,600.4 728.8,600.4 728.4,601.4C727.1,604.8 725.8,608.1 724.5,611.5C721.1,620.5 717.2,629.3 713,638C711.6,641.1 710.2,644.1 708.8,647.2C708.4,648 708.1,648.8 707.7,649.7C704.4,656.9 701.3,664.2 698.2,671.6C690.6,689.4 682.8,707.1 674.6,724.6C673.2,727.6 671.8,730.7 670.5,733.8C661.2,755.7 650.6,777.3 627.3,787.1C619.6,789.9 612.7,790.6 604.4,790.5C603.3,790.5 602.2,790.5 601,790.5C589.3,790.5 580.2,787.1 571,780C570.3,779.4 569.5,778.9 568.8,778.3C557.3,768.9 551.3,756.1 545.3,742.9C543,737.9 540.7,732.8 538.3,727.8C537.5,726 536.7,724.2 535.8,722.4C533,716.3 530.1,710.2 527.2,704.1C523.7,696.8 520.4,689.5 517.5,682C516,678.3 514.4,674.7 512.6,671.2C509.4,665 507,658.5 504.6,651.9C502.5,646.6 500.3,641.4 497.7,636.3C493.6,628.4 490.2,620.2 486.9,611.9C486.3,610.4 485.7,608.9 485.1,607.5C483.9,604.6 482.8,601.8 481.6,598.9C477.4,588.5 473,578.2 468.1,568.1C460.7,552.6 454.6,536.7 448.8,520.7C445.8,512.5 442,504.8 438,497.1C432.2,485.5 427.2,473.8 422.8,461.7C420.1,454.6 417.1,447.7 413.8,440.9C411,435 408.4,428.9 405.8,422.9C404.9,420.9 404,418.8 403.1,416.8C399.9,409.5 396.9,402.3 394.1,394.9C392.6,390.9 390.9,387 389.1,383.1C388.8,382.4 388.4,381.6 388.1,380.8C385.6,375.3 383,369.8 380.4,364.3C378.1,359.3 376.1,354.2 374.2,349C373.1,346.3 371.9,343.8 370.6,341.3C367.3,334.9 365.1,328.1 362.8,321.3C362,319 361.1,316.7 360.3,314.5C354.4,298.6 354.5,284.7 361,269C365.9,258.7 375.2,249.3 385.8,244.7C399.3,240.2 417.7,237.5 431,244C455.6,257.2 466.2,288.1 475.7,312.7C477.3,316.9 479.2,320.8 481.1,324.8C487.7,338.7 493.6,352.9 499.3,367.2C503,376.4 507,385.4 511.3,394.3C517.4,407.2 522.6,420.3 527.7,433.6C531,442.1 534.5,450.5 538.5,458.8C540.3,462.6 541.9,466.5 543.5,470.5C545.9,476.3 548.4,482 551.1,487.7C554.6,495.1 557.7,502.6 560.8,510.1C563.6,516.7 566.5,523.3 569.4,529.8C573.7,539.2 577.6,548.7 581.6,558.2C585.6,568 589.7,577.7 593.9,587.4C595.9,592.2 598,596.9 600,601.6C600.4,602.5 600.8,603.4 601.2,604.3C601.5,605.2 601.9,606 602.3,606.9C602.6,607.6 602.9,608.3 603.2,609.1C603.9,610.7 604.4,612.3 605,614C607.7,608.6 610.3,603.2 612.7,597.7C613.1,597 613.4,596.2 613.8,595.4C617.7,586.5 621.4,577.4 625.1,568.4C631.8,551.7 638.9,535.2 646.4,518.9C649.1,513 651.6,507.1 654,501C657.6,491.9 661.3,482.8 665.1,473.7C665.5,472.7 665.9,471.7 666.3,470.7C668.3,465.9 670.3,461.3 672.7,456.7C674.7,452.8 676.1,449 677.4,444.8C679.4,438.4 682.1,432.6 685.2,426.7C687.2,422.6 688.9,418.5 690.6,414.3C692.8,408.9 695.1,403.7 697.7,398.6C700.3,393.3 702.6,387.8 705,382.4C706.1,379.8 707.2,377.2 708.4,374.6C709.2,372.8 710,370.9 710.8,369.1C711.2,368.2 711.6,367.3 712,366.4C715.1,359.2 717.7,352.9 716,345C714.8,345 713.6,345 712.4,345C701.2,344.9 690,344.8 678.7,344.7C673,344.6 667.2,344.6 661.4,344.5C617.2,344.1 617.2,344.1 600.4,343.6C599.3,343.5 598.2,343.5 597.1,343.5C577.7,342.7 563.5,336.2 550.1,322C544.3,315 540.5,305.1 540,296C539.9,295.3 539.9,294.5 539.8,293.8C539.3,280.1 544.1,267.9 553.3,257.8C578.1,231.3 621.5,237.7 654.6,237.7Z"/>
<path android:fillColor="#FEFEFE" android:pathData="M253,250C253.7,250.5 254.4,250.9 255.1,251.4C269.1,262.1 275.9,283.6 282.8,299.1C284.1,302 285.4,304.8 286.7,307.7C291.1,316.8 294.9,326.1 298.7,335.5C301.8,343.2 305.1,350.9 308.4,358.5C308.8,359.4 309.2,360.3 309.6,361.2C311.5,365.6 313.5,370 315.5,374.4C318.1,380.2 320.6,386 323,391.9C323.6,393.4 323.6,393.4 324.3,394.9C326.4,400 328.5,405.2 330.4,410.5C332.9,417 335.9,423.2 339,429.4C345.8,443.2 351.7,457.2 357.5,471.4C361,479.9 364.6,488.3 368.5,496.6C369.1,497.8 369.7,499.1 370.3,500.3C371.4,502.7 372.5,505 373.6,507.4C374.1,508.5 374.6,509.5 375.1,510.6C375.6,511.5 376,512.5 376.4,513.4C378.3,517.8 380,522.2 381.7,526.6C383.7,531.7 385.8,536.8 387.9,541.8C388.4,542.9 388.9,544 389.3,545.1C392,551.4 394.7,557.7 397.5,564C397.8,564.7 398.1,565.3 398.4,566C398.9,567 398.9,567 399.3,568.1C399.9,569.4 400.5,570.8 401.1,572.2C401.4,572.9 401.7,573.5 402,574.2C406.2,583.8 410.2,593.4 413.9,603.1C416.1,608.9 418.6,614.5 421.2,620.1C424.6,627.2 427.7,634.3 430.9,641.5C431.3,642.5 431.3,642.5 431.8,643.6C436.1,653.4 440.2,663.3 444,673.4C446.5,679.7 449.2,685.9 452.1,692C472.4,734.7 472.4,734.7 465.3,755.8C459.9,769.4 451,781 437.5,787.2C436.4,787.7 435.3,788.2 434.2,788.7C422.2,793.5 408.1,791.5 396.4,787C369.2,774.6 356.6,742.9 346.5,716.7C344.7,712.4 342.8,708.2 340.8,704C333,687.8 326,671.2 319,654.6C316.1,647.6 313.1,640.6 310.1,633.7C307.7,628.1 305.4,622.5 303.3,616.8C301.6,612.4 299.7,608.2 297.5,604C291.1,591.1 285.6,577.7 280,564.4C277.2,557.7 274.3,551.1 271.4,544.4C268.9,538.9 266.7,533.3 264.6,527.7C263.4,524.8 262,522 260.6,519.2C258,514.1 255.8,508.9 253.7,503.6C252.9,501.6 252.1,499.7 251.3,497.8C250.9,496.8 250.6,495.8 250.2,494.8C248.4,490.6 246.7,486.3 244.9,482.1C244.6,481.4 244.3,480.7 244,480C242.3,475.9 240.5,471.9 238.6,467.9C234.7,459.7 231.2,451.4 227.8,443.1C226.9,440.9 226,438.7 225.1,436.5C222.4,430 219.8,423.5 217.3,417C215.4,411.9 213.2,407 210.8,402.1C203.8,387.5 197.4,372.6 191.3,357.6C189.5,353.1 187.5,348.6 185.4,344.1C182.4,337.5 179.7,330.9 176.9,324.3C176.4,322.9 175.8,321.5 175.2,320.1C168.5,304.1 166.4,288.4 173,272C179.5,258.3 189.7,249.1 203.7,243.7C220.2,238.1 238.5,240.3 253,250Z"/>
</vector>
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_banner_background"/>
<foreground android:drawable="@mipmap/ic_banner_foreground"/>
</adaptive-icon>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 115 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 166 KiB

+3
View File
@@ -0,0 +1,3 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:tools="http://schemas.android.com/tools"
tools:keep="@raw/aboutlibraries" />
@@ -1,2 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources></resources>
<resources>
<color name="ic_banner_background">#21272A</color>
</resources>
@@ -1,2 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources></resources>
+4 -3
View File
@@ -544,14 +544,15 @@
<string name="local_network_permission_issues_intro">Without this permission, you may experience issues with:</string>
<string name="local_network_permission_feature_tunnels">- Connecting to certain tunnels</string>
<string name="local_network_permission_feature_tunnels">- Connection issues with split tunneling, LAN bypass, or servers hosted on your local network</string>
<string name="local_network_permission_feature_autotunnel">- Auto-tunneling and split tunneling features</string>
<string name="local_network_permission_feature_proxy">- Local proxy and bypass functionality</string>
<string name="local_network_permission_recommendation">Granting this permission is strongly recommended.</string>
<string name="local_network_permission_nearby_devices">Note: Android labels this permission as “nearby devices”.</string>
<string name="not_now">Not now</string>
<string name="local_network_permission_denied">Local network access denied. Some features may not work properly</string>
<string name="stop_while_captive_portal">Stop tunnel while captive portal is present</string>
<string name="seamless_roaming">Seamless roaming</string>
<string name="seamless_roaming_description">Bind tunnels to the active network to improve reliability on network handoffs</string>
</resources>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 871 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 762 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 735 B

After

Width:  |  Height:  |  Size: 629 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 576 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 503 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 466 B

After

Width:  |  Height:  |  Size: 423 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 995 B

After

Width:  |  Height:  |  Size: 839 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

+9
View File
@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:viewportHeight="512" android:viewportWidth="512" android:width="24dp">
<path android:fillColor="#FEFEFE" android:pathData="M387.85,105.96C390.08,105.99 392.31,106.01 394.54,106.02C425.06,106.26 425.06,106.26 435.06,115.31C440.67,121.68 443.41,128.87 443.28,137.35C442.12,148.45 436.55,159.27 432.19,169.44C431.49,171.08 430.79,172.73 430.09,174.37C428.56,177.97 427.03,181.57 425.49,185.17C422.88,191.28 420.28,197.41 417.68,203.53C353.09,355.51 353.09,355.51 338.06,385.06C337.46,386.28 337.46,386.28 336.85,387.51C331.72,397.31 324.43,403.52 314,407C305.35,407.63 297.48,407.88 290,403C289.35,402.61 288.7,402.21 288.03,401.81C267.22,386.65 255.93,343.57 246.12,320.63C243.84,315.3 241.56,309.97 239.27,304.64C235.16,295.07 231.06,285.5 226.96,275.93C216,250.31 216,250.31 205,224.7C195.27,202.04 185.54,179.37 176.31,156.5C176,155.72 175.68,154.95 175.36,154.15C170.97,143.16 169.24,134.79 173.75,123.58C177.5,115.83 183.04,112.05 191,109C199.93,106.88 206.75,108.16 215,112C223.45,117.42 227.57,127.73 231.75,136.56C232.2,137.51 232.65,138.46 233.12,139.44C244.41,163.45 254.74,187.93 265.15,212.33C268.22,219.52 271.33,226.7 274.44,233.88C274.98,235.13 275.53,236.39 276.07,237.65C278.53,243.32 281,248.99 283.47,254.66C289.07,267.5 294.62,280.36 300,293.3C300.54,294.59 301.08,295.88 301.62,297.17C302.36,298.95 303.1,300.73 303.83,302.52C304.25,303.53 304.67,304.54 305.1,305.59C306,308 306,308 306,310C306.66,310 307.32,310 308,310C308.17,309.38 308.35,308.77 308.52,308.13C310.24,302.19 312.3,296.56 314.77,290.89C315.13,290.05 315.49,289.22 315.86,288.36C317.03,285.65 318.2,282.95 319.38,280.25C320.19,278.35 321.01,276.46 321.83,274.56C323.85,269.88 325.88,265.2 327.9,260.53C329.44,256.98 330.98,253.42 332.52,249.87C334.58,245.1 336.65,240.34 338.71,235.57C341.15,229.96 343.58,224.35 346,218.74C346.35,217.95 346.69,217.15 347.05,216.33C348.78,212.33 350.51,208.33 352.23,204.32C355.22,197.41 358.21,190.5 361.24,183.61C361.71,182.53 362.19,181.45 362.67,180.34C363.53,178.38 364.39,176.42 365.26,174.47C365.63,173.63 366,172.79 366.38,171.93C366.85,170.85 366.85,170.85 367.34,169.75C368.12,168 368.12,168 368,166C366.76,165.99 366.76,165.99 365.49,165.98C357.63,165.9 349.78,165.81 341.92,165.71C337.88,165.66 333.84,165.62 329.81,165.58C325.9,165.54 322,165.49 318.1,165.44C316.62,165.42 315.13,165.41 313.65,165.4C300.1,165.29 288.99,164.82 278.75,155.19C273.15,149.32 271.03,143.02 271,135C271.65,126.87 275.22,120.31 281.34,114.95C296.39,103.76 318.78,106.75 336.69,106.44C338.39,106.41 338.39,106.41 340.12,106.37C373.39,105.77 373.39,105.77 387.85,105.96Z"/>
<path android:fillColor="#FEFEFE" android:pathData="M114,113C120.76,117.8 124.22,123.2 127.69,130.63C128.19,131.68 128.7,132.73 129.22,133.81C130.84,137.2 132.43,140.59 134,144C134.5,145.08 135,146.16 135.52,147.28C144.69,167.16 153.19,187.35 161.79,207.48C165.04,215.1 168.33,222.71 171.63,230.31C172.14,231.49 172.65,232.67 173.18,233.89C175.39,239 177.61,244.1 179.83,249.21C189.5,271.38 198.88,293.66 208.11,316.01C211.04,323.11 214.01,330.18 217,337.25C217.43,338.26 217.86,339.28 218.3,340.32C220.39,345.24 222.5,350.16 224.63,355.07C225.05,356.04 225.47,357.01 225.91,358C226.69,359.81 227.48,361.62 228.27,363.42C232.37,372.86 233.7,381.34 229.94,391.19C226.56,398.54 222.59,403.08 215.19,406.44C207.09,409.27 200.15,408.78 192.38,405.23C182.8,400.26 178.73,393.43 174,384C173.52,383.06 173.05,382.12 172.56,381.15C162.64,361.39 153.83,341.17 145.1,320.88C143.13,316.31 141.16,311.74 139.17,307.18C130.23,286.6 121.43,265.97 112.65,245.32C107.35,232.86 102.04,220.41 96.69,207.98C90.69,194.05 84.73,180.11 78.92,166.1C78.32,164.64 77.71,163.18 77.1,161.72C67.42,138.54 67.42,138.54 71.56,126.94C74.89,119.43 79.6,114.1 87.11,110.69C96.25,107.35 105.69,107.97 114,113Z"/>
<path android:fillColor="#FEFEFE" android:pathData="M324,178C331.04,182.91 336.31,189.42 338,198C338.98,206.07 336.97,213.6 332.42,220.34C327.99,225.71 322.98,229.65 316,231C306.84,231.81 299.67,231.33 292,226C289.13,223.5 289.13,223.5 287,221C286.28,220.2 285.56,219.39 284.81,218.56C279.84,211.53 280.29,203.26 281,195C281.85,191.88 283.09,189.59 285,187C285.42,186.39 285.85,185.78 286.29,185.14C295.31,173.35 311.34,170.15 324,178Z"/>
</vector>
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_banner_background"/>
<foreground android:drawable="@mipmap/ic_banner_foreground"/>
</adaptive-icon>
@@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@mipmap/ic_launcher_background"/>
<background android:drawable="@color/ic_launcher_background"/>
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
<monochrome android:drawable="@mipmap/ic_launcher_foreground"/>
</adaptive-icon>
@@ -1,5 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@mipmap/ic_launcher_background"/>
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
</adaptive-icon>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 942 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1000 B

After

Width:  |  Height:  |  Size: 1012 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1012 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.7 KiB

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

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