mirror of
https://github.com/wgtunnel/android.git
synced 2026-07-03 14:07:49 +02:00
Compare commits
15 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| df716d4035 | |||
| 1857977d9b | |||
| 4784b1f65c | |||
| f3debcfe45 | |||
| a5229c62b1 | |||
| 3e6a2981cb | |||
| 00254874f0 | |||
| 1b9560b601 | |||
| ff97c65d4f | |||
| 02dea1e6b0 | |||
| 548362cdde | |||
| 932d27edd7 | |||
| d89870e6de | |||
| 0dcee673e1 | |||
| 7b7c8f6e8c |
@@ -173,8 +173,15 @@ jobs:
|
||||
body: |
|
||||
${{ env.RELEASE_NOTES }}
|
||||
|
||||
SHA256 fingerprint:
|
||||
```${{ steps.checksum.outputs.checksum }}```
|
||||
SHA-256 fingerprint for the 4096-bit signing certificate:
|
||||
```sh
|
||||
${{ steps.checksum.outputs.checksum }}
|
||||
```
|
||||
|
||||
To verify fingerprint:
|
||||
```sh
|
||||
apksigner verify --print-certs [path to APK file] | grep SHA-256
|
||||
```
|
||||
|
||||
### Changelog
|
||||
${{ steps.changelog.outputs.changes }}
|
||||
@@ -195,7 +202,7 @@ jobs:
|
||||
- name: Dispatch update for fdroid repo
|
||||
uses: peter-evans/repository-dispatch@v3
|
||||
with:
|
||||
token: ${{ secrets.ANDROID_PAT }}
|
||||
token: ${{ secrets.PAT }}
|
||||
repository: zaneschepke/fdroid
|
||||
event-type: fdroid-update
|
||||
|
||||
|
||||
@@ -191,7 +191,6 @@ dependencies {
|
||||
|
||||
// accompanist
|
||||
implementation(libs.accompanist.permissions)
|
||||
implementation(libs.accompanist.flowlayout)
|
||||
implementation(libs.accompanist.drawablepainter)
|
||||
|
||||
// storage
|
||||
|
||||
@@ -0,0 +1,281 @@
|
||||
{
|
||||
"formatVersion": 1,
|
||||
"database": {
|
||||
"version": 15,
|
||||
"identityHash": "4827f3b1ab5a4e5aa35937a0925d50e4",
|
||||
"entities": [
|
||||
{
|
||||
"tableName": "Settings",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_tunnel_enabled` INTEGER NOT NULL, `is_tunnel_on_mobile_data_enabled` INTEGER NOT NULL, `trusted_network_ssids` TEXT NOT NULL, `is_always_on_vpn_enabled` INTEGER NOT NULL, `is_tunnel_on_ethernet_enabled` INTEGER NOT NULL, `is_shortcuts_enabled` INTEGER NOT NULL DEFAULT false, `is_tunnel_on_wifi_enabled` INTEGER NOT NULL DEFAULT false, `is_kernel_enabled` INTEGER NOT NULL DEFAULT false, `is_restore_on_boot_enabled` INTEGER NOT NULL DEFAULT false, `is_multi_tunnel_enabled` INTEGER NOT NULL DEFAULT false, `is_ping_enabled` INTEGER NOT NULL DEFAULT false, `is_amnezia_enabled` INTEGER NOT NULL DEFAULT false, `is_wildcards_enabled` INTEGER NOT NULL DEFAULT false, `is_wifi_by_shell_enabled` INTEGER NOT NULL DEFAULT false, `is_stop_on_no_internet_enabled` INTEGER NOT NULL DEFAULT false, `is_vpn_kill_switch_enabled` INTEGER NOT NULL DEFAULT false, `is_kernel_kill_switch_enabled` INTEGER NOT NULL DEFAULT false, `is_lan_on_kill_switch_enabled` INTEGER NOT NULL DEFAULT false, `debounce_delay_seconds` INTEGER NOT NULL DEFAULT 3, `is_disable_kill_switch_on_trusted_enabled` INTEGER NOT NULL DEFAULT false)",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "id",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "isAutoTunnelEnabled",
|
||||
"columnName": "is_tunnel_enabled",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "isTunnelOnMobileDataEnabled",
|
||||
"columnName": "is_tunnel_on_mobile_data_enabled",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "trustedNetworkSSIDs",
|
||||
"columnName": "trusted_network_ssids",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "isAlwaysOnVpnEnabled",
|
||||
"columnName": "is_always_on_vpn_enabled",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "isTunnelOnEthernetEnabled",
|
||||
"columnName": "is_tunnel_on_ethernet_enabled",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "isShortcutsEnabled",
|
||||
"columnName": "is_shortcuts_enabled",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "false"
|
||||
},
|
||||
{
|
||||
"fieldPath": "isTunnelOnWifiEnabled",
|
||||
"columnName": "is_tunnel_on_wifi_enabled",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "false"
|
||||
},
|
||||
{
|
||||
"fieldPath": "isKernelEnabled",
|
||||
"columnName": "is_kernel_enabled",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "false"
|
||||
},
|
||||
{
|
||||
"fieldPath": "isRestoreOnBootEnabled",
|
||||
"columnName": "is_restore_on_boot_enabled",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "false"
|
||||
},
|
||||
{
|
||||
"fieldPath": "isMultiTunnelEnabled",
|
||||
"columnName": "is_multi_tunnel_enabled",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "false"
|
||||
},
|
||||
{
|
||||
"fieldPath": "isPingEnabled",
|
||||
"columnName": "is_ping_enabled",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "false"
|
||||
},
|
||||
{
|
||||
"fieldPath": "isAmneziaEnabled",
|
||||
"columnName": "is_amnezia_enabled",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "false"
|
||||
},
|
||||
{
|
||||
"fieldPath": "isWildcardsEnabled",
|
||||
"columnName": "is_wildcards_enabled",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "false"
|
||||
},
|
||||
{
|
||||
"fieldPath": "isWifiNameByShellEnabled",
|
||||
"columnName": "is_wifi_by_shell_enabled",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "false"
|
||||
},
|
||||
{
|
||||
"fieldPath": "isStopOnNoInternetEnabled",
|
||||
"columnName": "is_stop_on_no_internet_enabled",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "false"
|
||||
},
|
||||
{
|
||||
"fieldPath": "isVpnKillSwitchEnabled",
|
||||
"columnName": "is_vpn_kill_switch_enabled",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "false"
|
||||
},
|
||||
{
|
||||
"fieldPath": "isKernelKillSwitchEnabled",
|
||||
"columnName": "is_kernel_kill_switch_enabled",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "false"
|
||||
},
|
||||
{
|
||||
"fieldPath": "isLanOnKillSwitchEnabled",
|
||||
"columnName": "is_lan_on_kill_switch_enabled",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "false"
|
||||
},
|
||||
{
|
||||
"fieldPath": "debounceDelaySeconds",
|
||||
"columnName": "debounce_delay_seconds",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "3"
|
||||
},
|
||||
{
|
||||
"fieldPath": "isDisableKillSwitchOnTrustedEnabled",
|
||||
"columnName": "is_disable_kill_switch_on_trusted_enabled",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "false"
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": true,
|
||||
"columnNames": [
|
||||
"id"
|
||||
]
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
},
|
||||
{
|
||||
"tableName": "TunnelConfig",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NOT NULL, `wg_quick` TEXT NOT NULL, `tunnel_networks` TEXT NOT NULL DEFAULT '', `is_mobile_data_tunnel` INTEGER NOT NULL DEFAULT false, `is_primary_tunnel` INTEGER NOT NULL DEFAULT false, `am_quick` TEXT NOT NULL DEFAULT '', `is_Active` INTEGER NOT NULL DEFAULT false, `is_ping_enabled` INTEGER NOT NULL DEFAULT false, `ping_interval` INTEGER DEFAULT null, `ping_cooldown` INTEGER DEFAULT null, `ping_ip` TEXT DEFAULT null, `is_ethernet_tunnel` INTEGER NOT NULL DEFAULT false)",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "id",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "name",
|
||||
"columnName": "name",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "wgQuick",
|
||||
"columnName": "wg_quick",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "tunnelNetworks",
|
||||
"columnName": "tunnel_networks",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true,
|
||||
"defaultValue": "''"
|
||||
},
|
||||
{
|
||||
"fieldPath": "isMobileDataTunnel",
|
||||
"columnName": "is_mobile_data_tunnel",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "false"
|
||||
},
|
||||
{
|
||||
"fieldPath": "isPrimaryTunnel",
|
||||
"columnName": "is_primary_tunnel",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "false"
|
||||
},
|
||||
{
|
||||
"fieldPath": "amQuick",
|
||||
"columnName": "am_quick",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true,
|
||||
"defaultValue": "''"
|
||||
},
|
||||
{
|
||||
"fieldPath": "isActive",
|
||||
"columnName": "is_Active",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "false"
|
||||
},
|
||||
{
|
||||
"fieldPath": "isPingEnabled",
|
||||
"columnName": "is_ping_enabled",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "false"
|
||||
},
|
||||
{
|
||||
"fieldPath": "pingInterval",
|
||||
"columnName": "ping_interval",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false,
|
||||
"defaultValue": "null"
|
||||
},
|
||||
{
|
||||
"fieldPath": "pingCooldown",
|
||||
"columnName": "ping_cooldown",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false,
|
||||
"defaultValue": "null"
|
||||
},
|
||||
{
|
||||
"fieldPath": "pingIp",
|
||||
"columnName": "ping_ip",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false,
|
||||
"defaultValue": "null"
|
||||
},
|
||||
{
|
||||
"fieldPath": "isEthernetTunnel",
|
||||
"columnName": "is_ethernet_tunnel",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "false"
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": true,
|
||||
"columnNames": [
|
||||
"id"
|
||||
]
|
||||
},
|
||||
"indices": [
|
||||
{
|
||||
"name": "index_TunnelConfig_name",
|
||||
"unique": true,
|
||||
"columnNames": [
|
||||
"name"
|
||||
],
|
||||
"orders": [],
|
||||
"createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_TunnelConfig_name` ON `${TABLE_NAME}` (`name`)"
|
||||
}
|
||||
],
|
||||
"foreignKeys": []
|
||||
}
|
||||
],
|
||||
"views": [],
|
||||
"setupQueries": [
|
||||
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
|
||||
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '4827f3b1ab5a4e5aa35937a0925d50e4')"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -74,7 +74,8 @@
|
||||
>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
|
||||
<action android:name="android.intent.action.SHOW_APP_INFO" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
<category android:name="android.intent.category.LEANBACK_LAUNCHER" />
|
||||
<action android:name="android.service.quicksettings.action.QS_TILE_PREFERENCES" />
|
||||
@@ -109,7 +110,7 @@
|
||||
android:name=".service.tile.TunnelControlTile"
|
||||
android:exported="true"
|
||||
android:icon="@drawable/ic_launcher"
|
||||
android:label="Tunnel control"
|
||||
android:label="@string/tunnel_control"
|
||||
android:permission="android.permission.BIND_QUICK_SETTINGS_TILE">
|
||||
<meta-data
|
||||
android:name="android.service.quicksettings.ACTIVE_TILE"
|
||||
@@ -126,7 +127,7 @@
|
||||
android:name=".service.tile.AutoTunnelControlTile"
|
||||
android:exported="true"
|
||||
android:icon="@drawable/ic_launcher"
|
||||
android:label="Auto-tunnel"
|
||||
android:label="@string/auto_tunnel"
|
||||
android:permission="android.permission.BIND_QUICK_SETTINGS_TILE">
|
||||
<meta-data
|
||||
android:name="android.service.quicksettings.ACTIVE_TILE"
|
||||
|
||||
@@ -11,7 +11,7 @@ import com.zaneschepke.wireguardautotunnel.data.domain.TunnelConfig
|
||||
|
||||
@Database(
|
||||
entities = [Settings::class, TunnelConfig::class],
|
||||
version = 14,
|
||||
version = 15,
|
||||
autoMigrations =
|
||||
[
|
||||
AutoMigration(from = 1, to = 2),
|
||||
@@ -53,6 +53,10 @@ import com.zaneschepke.wireguardautotunnel.data.domain.TunnelConfig
|
||||
from = 13,
|
||||
to = 14,
|
||||
),
|
||||
AutoMigration(
|
||||
from = 14,
|
||||
to = 15,
|
||||
),
|
||||
],
|
||||
exportSchema = true,
|
||||
)
|
||||
|
||||
@@ -85,4 +85,13 @@ data class Settings(
|
||||
defaultValue = "3",
|
||||
)
|
||||
val debounceDelaySeconds: Int = 3,
|
||||
)
|
||||
@ColumnInfo(
|
||||
name = "is_disable_kill_switch_on_trusted_enabled",
|
||||
defaultValue = "false",
|
||||
)
|
||||
val isDisableKillSwitchOnTrustedEnabled: Boolean = false,
|
||||
) {
|
||||
fun debounceDelayMillis(): Long {
|
||||
return debounceDelaySeconds * 1000L
|
||||
}
|
||||
}
|
||||
|
||||
@@ -97,6 +97,9 @@ data class TunnelConfig(
|
||||
|
||||
const val AM_QUICK_DEFAULT = ""
|
||||
|
||||
const val IPV6_ALL_NETWORKS = "::/0"
|
||||
const val IPV4_ALL_NETWORKS = "0.0.0.0/0"
|
||||
val ALL_IPS = setOf(IPV4_ALL_NETWORKS, IPV6_ALL_NETWORKS)
|
||||
val IPV4_PUBLIC_NETWORKS = setOf(
|
||||
"0.0.0.0/5", "8.0.0.0/7", "11.0.0.0/8", "12.0.0.0/6", "16.0.0.0/4", "32.0.0.0/3",
|
||||
"64.0.0.0/2", "128.0.0.0/3", "160.0.0.0/5", "168.0.0.0/6", "172.0.0.0/12",
|
||||
@@ -105,5 +108,6 @@ data class TunnelConfig(
|
||||
"192.170.0.0/15", "192.172.0.0/14", "192.176.0.0/12", "192.192.0.0/10",
|
||||
"193.0.0.0/8", "194.0.0.0/7", "196.0.0.0/6", "200.0.0.0/5", "208.0.0.0/4",
|
||||
)
|
||||
val LAN_BYPASS_ALLOWED_IPS = setOf(IPV6_ALL_NETWORKS) + IPV4_PUBLIC_NETWORKS
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
package com.zaneschepke.wireguardautotunnel.module
|
||||
|
||||
import javax.inject.Qualifier
|
||||
|
||||
@Qualifier
|
||||
@Retention(AnnotationRetention.BINARY)
|
||||
annotation class Wifi
|
||||
|
||||
@Qualifier
|
||||
@Retention(AnnotationRetention.BINARY)
|
||||
annotation class MobileData
|
||||
|
||||
@Qualifier
|
||||
@Retention(AnnotationRetention.BINARY)
|
||||
annotation class Ethernet
|
||||
@@ -1,9 +1,7 @@
|
||||
package com.zaneschepke.wireguardautotunnel.module
|
||||
|
||||
import com.zaneschepke.wireguardautotunnel.service.network.EthernetService
|
||||
import com.zaneschepke.wireguardautotunnel.service.network.MobileDataService
|
||||
import com.zaneschepke.wireguardautotunnel.service.network.InternetConnectivityService
|
||||
import com.zaneschepke.wireguardautotunnel.service.network.NetworkService
|
||||
import com.zaneschepke.wireguardautotunnel.service.network.WifiService
|
||||
import dagger.Binds
|
||||
import dagger.Module
|
||||
import dagger.hilt.InstallIn
|
||||
@@ -14,14 +12,5 @@ import dagger.hilt.components.SingletonComponent
|
||||
abstract class ServiceModule {
|
||||
|
||||
@Binds
|
||||
@Wifi
|
||||
abstract fun provideWifiService(wifiService: WifiService): NetworkService
|
||||
|
||||
@Binds
|
||||
@MobileData
|
||||
abstract fun provideMobileDataService(mobileDataService: MobileDataService): NetworkService
|
||||
|
||||
@Binds
|
||||
@Ethernet
|
||||
abstract fun provideEthernetService(ethernetService: EthernetService): NetworkService
|
||||
abstract fun provideInternetConnectivityService(wifiService: InternetConnectivityService): NetworkService
|
||||
}
|
||||
|
||||
@@ -79,9 +79,7 @@ class TunnelModule {
|
||||
@IoDispatcher ioDispatcher: CoroutineDispatcher,
|
||||
serviceManager: ServiceManager,
|
||||
notificationService: NotificationService,
|
||||
@Wifi wifiService: NetworkService,
|
||||
@MobileData mobileDataService: NetworkService,
|
||||
@Ethernet ethernetService: NetworkService,
|
||||
internetConnectivityService: NetworkService,
|
||||
): TunnelService {
|
||||
return WireGuardTunnel(
|
||||
amneziaBackend,
|
||||
@@ -92,9 +90,7 @@ class TunnelModule {
|
||||
ioDispatcher,
|
||||
serviceManager,
|
||||
notificationService,
|
||||
wifiService,
|
||||
mobileDataService,
|
||||
ethernetService,
|
||||
internetConnectivityService,
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
+47
-45
@@ -1,6 +1,7 @@
|
||||
package com.zaneschepke.wireguardautotunnel.service.foreground.autotunnel
|
||||
|
||||
import android.content.Intent
|
||||
import android.net.NetworkCapabilities
|
||||
import android.os.IBinder
|
||||
import android.os.PowerManager
|
||||
import androidx.core.app.ServiceCompat
|
||||
@@ -11,20 +12,20 @@ import com.zaneschepke.wireguardautotunnel.R
|
||||
import com.zaneschepke.wireguardautotunnel.data.domain.Settings
|
||||
import com.zaneschepke.wireguardautotunnel.data.repository.AppDataRepository
|
||||
import com.zaneschepke.wireguardautotunnel.module.AppShell
|
||||
import com.zaneschepke.wireguardautotunnel.module.Ethernet
|
||||
import com.zaneschepke.wireguardautotunnel.module.IoDispatcher
|
||||
import com.zaneschepke.wireguardautotunnel.module.MainImmediateDispatcher
|
||||
import com.zaneschepke.wireguardautotunnel.module.MobileData
|
||||
import com.zaneschepke.wireguardautotunnel.module.Wifi
|
||||
import com.zaneschepke.wireguardautotunnel.service.foreground.ServiceManager
|
||||
import com.zaneschepke.wireguardautotunnel.service.foreground.autotunnel.model.AutoTunnelEvent
|
||||
import com.zaneschepke.wireguardautotunnel.service.foreground.autotunnel.model.AutoTunnelState
|
||||
import com.zaneschepke.wireguardautotunnel.service.foreground.autotunnel.model.KillSwitchEvent
|
||||
import com.zaneschepke.wireguardautotunnel.service.foreground.autotunnel.model.NetworkState
|
||||
import com.zaneschepke.wireguardautotunnel.service.network.InternetConnectivityService
|
||||
import com.zaneschepke.wireguardautotunnel.service.network.NetworkService
|
||||
import com.zaneschepke.wireguardautotunnel.service.network.WifiService
|
||||
import com.zaneschepke.wireguardautotunnel.service.network.NetworkStatus
|
||||
import com.zaneschepke.wireguardautotunnel.service.notification.NotificationAction
|
||||
import com.zaneschepke.wireguardautotunnel.service.notification.NotificationService
|
||||
import com.zaneschepke.wireguardautotunnel.service.notification.WireGuardNotification
|
||||
import com.zaneschepke.wireguardautotunnel.service.tunnel.BackendState
|
||||
import com.zaneschepke.wireguardautotunnel.service.tunnel.TunnelService
|
||||
import com.zaneschepke.wireguardautotunnel.util.Constants
|
||||
import com.zaneschepke.wireguardautotunnel.util.extensions.TunnelConfigs
|
||||
@@ -53,16 +54,7 @@ class AutoTunnelService : LifecycleService() {
|
||||
lateinit var rootShell: Provider<RootShell>
|
||||
|
||||
@Inject
|
||||
@Wifi
|
||||
lateinit var wifiService: NetworkService
|
||||
|
||||
@Inject
|
||||
@MobileData
|
||||
lateinit var mobileDataService: NetworkService
|
||||
|
||||
@Inject
|
||||
@Ethernet
|
||||
lateinit var ethernetService: NetworkService
|
||||
lateinit var networkService: NetworkService
|
||||
|
||||
@Inject
|
||||
lateinit var appDataRepository: Provider<AppDataRepository>
|
||||
@@ -121,6 +113,7 @@ class AutoTunnelService : LifecycleService() {
|
||||
}
|
||||
startAutoTunnelJob()
|
||||
startAutoTunnelStateJob()
|
||||
startKillSwitchJob()
|
||||
}.onFailure {
|
||||
Timber.e(it)
|
||||
}
|
||||
@@ -168,51 +161,44 @@ class AutoTunnelService : LifecycleService() {
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun buildNetworkState(networkStatus: NetworkStatus): NetworkState {
|
||||
return with(autoTunnelStateFlow.value.networkState) {
|
||||
val wifiName = when {
|
||||
networkStatus.wifiAvailable &&
|
||||
(wifiName == null || wifiName == Constants.UNREADABLE_SSID || networkService.didWifiChangeSinceLastCapabilitiesQuery) -> {
|
||||
networkService.getWifiCapabilities()?.let { getWifiName(it) } ?: wifiName
|
||||
}
|
||||
!networkStatus.wifiAvailable -> null
|
||||
else -> wifiName
|
||||
}
|
||||
copy(networkStatus.wifiAvailable, networkStatus.cellularAvailable, networkStatus.ethernetAvailable, wifiName)
|
||||
}
|
||||
}
|
||||
|
||||
private fun startAutoTunnelStateJob() = lifecycleScope.launch(ioDispatcher) {
|
||||
combine(
|
||||
combineSettings(),
|
||||
combineNetworkEventsJob(),
|
||||
networkService.status.map {
|
||||
buildNetworkState(it)
|
||||
}.distinctUntilChanged(),
|
||||
) { double, networkState ->
|
||||
var wifiName: String? = null
|
||||
if (networkState.wifiName == Constants.UNREADABLE_SSID && double.first.isTunnelOnWifiEnabled) {
|
||||
wifiName = getWifiName(double.first)
|
||||
}
|
||||
val netState = wifiName?.let { networkState.copy(wifiName = it) } ?: networkState
|
||||
AutoTunnelState(tunnelService.get().vpnState.value, netState, double.first, double.second)
|
||||
AutoTunnelState(tunnelService.get().vpnState.value, networkState, double.first, double.second)
|
||||
}.collect { state ->
|
||||
Timber.d("Network state: ${state.networkState}")
|
||||
autoTunnelStateFlow.update {
|
||||
it.copy(vpnState = state.vpnState, networkState = state.networkState, settings = state.settings, tunnels = state.tunnels)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun getWifiName(setting: Settings): String? {
|
||||
private suspend fun getWifiName(wifiCapabilities: NetworkCapabilities): String? {
|
||||
val setting = appDataRepository.get().settings.getSettings()
|
||||
return if (setting.isWifiNameByShellEnabled) {
|
||||
rootShell.get().getCurrentWifiName()
|
||||
} else if (wifiService.capabilities != null) {
|
||||
WifiService.getNetworkName(wifiService.capabilities!!, this@AutoTunnelService)
|
||||
} else {
|
||||
null
|
||||
InternetConnectivityService.getNetworkName(wifiCapabilities, this@AutoTunnelService)
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(FlowPreview::class)
|
||||
private fun combineNetworkEventsJob(): Flow<NetworkState> {
|
||||
return combine(
|
||||
wifiService.status,
|
||||
mobileDataService.status,
|
||||
ethernetService.status,
|
||||
) { wifi, mobileData, ethernet ->
|
||||
NetworkState(
|
||||
wifi.available,
|
||||
mobileData.available,
|
||||
ethernet.available,
|
||||
wifi.name,
|
||||
)
|
||||
}.distinctUntilChanged()
|
||||
}
|
||||
|
||||
private fun combineSettings(): Flow<Pair<Settings, TunnelConfigs>> {
|
||||
return combine(
|
||||
appDataRepository.get().settings.getSettingsFlow(),
|
||||
@@ -225,13 +211,29 @@ class AutoTunnelService : LifecycleService() {
|
||||
}.distinctUntilChanged()
|
||||
}
|
||||
|
||||
private fun startKillSwitchJob() = lifecycleScope.launch(ioDispatcher) {
|
||||
autoTunnelStateFlow.collect {
|
||||
if (it == defaultState) return@collect
|
||||
when (val event = it.asKillSwitchEvent()) {
|
||||
KillSwitchEvent.DoNothing -> Unit
|
||||
is KillSwitchEvent.Start -> {
|
||||
Timber.d("Starting kill switch")
|
||||
tunnelService.get().setBackendState(BackendState.KILL_SWITCH_ACTIVE, event.allowedIps)
|
||||
}
|
||||
KillSwitchEvent.Stop -> {
|
||||
Timber.d("Stopping kill switch")
|
||||
tunnelService.get().setBackendState(BackendState.SERVICE_ACTIVE, emptySet())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(FlowPreview::class)
|
||||
private fun startAutoTunnelJob() = lifecycleScope.launch(ioDispatcher) {
|
||||
Timber.i("Starting auto-tunnel network event watcher")
|
||||
val settings = appDataRepository.get().settings.getSettings()
|
||||
val debounce = settings.debounceDelaySeconds * 1000L
|
||||
Timber.d("Starting with debounce delay of: $debounce")
|
||||
autoTunnelStateFlow.debounce(debounce).collect { watcherState ->
|
||||
Timber.d("Starting with debounce delay of: ${settings.debounceDelaySeconds} seconds")
|
||||
autoTunnelStateFlow.debounce(settings.debounceDelayMillis()).collect { watcherState ->
|
||||
if (watcherState == defaultState) return@collect
|
||||
Timber.d("New auto tunnel state emitted")
|
||||
when (val event = watcherState.asAutoTunnelEvent()) {
|
||||
|
||||
+20
@@ -2,6 +2,7 @@ package com.zaneschepke.wireguardautotunnel.service.foreground.autotunnel.model
|
||||
|
||||
import com.zaneschepke.wireguardautotunnel.data.domain.Settings
|
||||
import com.zaneschepke.wireguardautotunnel.data.domain.TunnelConfig
|
||||
import com.zaneschepke.wireguardautotunnel.service.tunnel.BackendState
|
||||
import com.zaneschepke.wireguardautotunnel.service.tunnel.VpnState
|
||||
import com.zaneschepke.wireguardautotunnel.util.extensions.TunnelConfigs
|
||||
import com.zaneschepke.wireguardautotunnel.util.extensions.isMatchingToWildcardList
|
||||
@@ -52,6 +53,14 @@ data class AutoTunnelState(
|
||||
return networkState.isEthernetConnected && !settings.isTunnelOnEthernetEnabled && vpnState.status.isUp()
|
||||
}
|
||||
|
||||
private fun stopKillSwitchOnTrusted(): Boolean {
|
||||
return networkState.isWifiConnected && settings.isVpnKillSwitchEnabled && settings.isDisableKillSwitchOnTrustedEnabled && isCurrentSSIDTrusted() && vpnState.backendState == BackendState.KILL_SWITCH_ACTIVE
|
||||
}
|
||||
|
||||
private fun startKillSwitch(): Boolean {
|
||||
return settings.isVpnKillSwitchEnabled && vpnState.backendState != BackendState.KILL_SWITCH_ACTIVE && (!settings.isDisableKillSwitchOnTrustedEnabled || !isCurrentSSIDTrusted())
|
||||
}
|
||||
|
||||
fun isNoConnectivity(): Boolean {
|
||||
return !networkState.isEthernetConnected && !networkState.isWifiConnected && !networkState.isMobileDataConnected
|
||||
}
|
||||
@@ -116,6 +125,17 @@ data class AutoTunnelState(
|
||||
}
|
||||
}
|
||||
|
||||
fun asKillSwitchEvent(): KillSwitchEvent {
|
||||
return when {
|
||||
stopKillSwitchOnTrusted() -> KillSwitchEvent.Stop
|
||||
startKillSwitch() -> {
|
||||
val allowedIps = if (settings.isLanOnKillSwitchEnabled) TunnelConfig.LAN_BYPASS_ALLOWED_IPS else emptySet()
|
||||
KillSwitchEvent.Start(allowedIps)
|
||||
}
|
||||
else -> KillSwitchEvent.DoNothing
|
||||
}
|
||||
}
|
||||
|
||||
private fun isCurrentSSIDTrusted(): Boolean {
|
||||
return networkState.wifiName?.let {
|
||||
hasTrustedWifiName(it)
|
||||
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
package com.zaneschepke.wireguardautotunnel.service.foreground.autotunnel.model
|
||||
|
||||
sealed class KillSwitchEvent {
|
||||
data class Start(val allowedIps: Set<String>) : KillSwitchEvent()
|
||||
data object Stop : KillSwitchEvent()
|
||||
data object DoNothing : KillSwitchEvent()
|
||||
}
|
||||
-67
@@ -1,67 +0,0 @@
|
||||
package com.zaneschepke.wireguardautotunnel.service.network
|
||||
|
||||
import android.content.Context
|
||||
import android.net.ConnectivityManager
|
||||
import android.net.Network
|
||||
import android.net.NetworkCapabilities
|
||||
import android.net.NetworkRequest
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import kotlinx.coroutines.channels.awaitClose
|
||||
import kotlinx.coroutines.flow.callbackFlow
|
||||
import kotlinx.coroutines.flow.catch
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.onStart
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
class EthernetService
|
||||
@Inject
|
||||
constructor(
|
||||
@ApplicationContext context: Context,
|
||||
) : NetworkService {
|
||||
|
||||
override var capabilities: NetworkCapabilities? = null
|
||||
|
||||
private val connectivityManager =
|
||||
context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
|
||||
|
||||
override val status = callbackFlow {
|
||||
val networkStatusCallback = object : ConnectivityManager.NetworkCallback() {
|
||||
override fun onAvailable(network: Network) {
|
||||
trySend(NetworkStatus.Available(network))
|
||||
}
|
||||
override fun onLost(network: Network) {
|
||||
trySend(NetworkStatus.Unavailable())
|
||||
}
|
||||
override fun onCapabilitiesChanged(network: Network, networkCapabilities: NetworkCapabilities) {
|
||||
capabilities = networkCapabilities
|
||||
trySend(
|
||||
NetworkStatus.CapabilitiesChanged(
|
||||
network,
|
||||
networkCapabilities,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
val request =
|
||||
NetworkRequest.Builder()
|
||||
.addTransportType(NetworkCapabilities.TRANSPORT_ETHERNET)
|
||||
.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
|
||||
.addCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED)
|
||||
.build()
|
||||
connectivityManager.registerNetworkCallback(request, networkStatusCallback)
|
||||
|
||||
awaitClose { connectivityManager.unregisterNetworkCallback(networkStatusCallback) }
|
||||
}.onStart {
|
||||
// needed for services that are not yet available as it will impact later combine flows if we don't emit
|
||||
emit(NetworkStatus.Unavailable())
|
||||
}.catch {
|
||||
Timber.e(it)
|
||||
emit(NetworkStatus.Unavailable())
|
||||
}.map {
|
||||
when (it) {
|
||||
is NetworkStatus.Available, is NetworkStatus.CapabilitiesChanged -> Status(true, null)
|
||||
is NetworkStatus.Unavailable -> Status(false, null)
|
||||
}
|
||||
}
|
||||
}
|
||||
+77
-64
@@ -9,42 +9,82 @@ import android.net.NetworkRequest
|
||||
import android.net.wifi.SupplicantState
|
||||
import android.net.wifi.WifiManager
|
||||
import android.os.Build
|
||||
import com.wireguard.android.util.RootShell
|
||||
import com.zaneschepke.wireguardautotunnel.data.repository.SettingsRepository
|
||||
import com.zaneschepke.wireguardautotunnel.module.AppShell
|
||||
import com.zaneschepke.wireguardautotunnel.util.Constants
|
||||
import com.zaneschepke.wireguardautotunnel.util.extensions.getCurrentWifiName
|
||||
import com.zaneschepke.wireguardautotunnel.module.IoDispatcher
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import kotlinx.coroutines.CoroutineDispatcher
|
||||
import kotlinx.coroutines.channels.awaitClose
|
||||
import kotlinx.coroutines.flow.callbackFlow
|
||||
import kotlinx.coroutines.flow.catch
|
||||
import kotlinx.coroutines.flow.onStart
|
||||
import kotlinx.coroutines.flow.transform
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
import timber.log.Timber
|
||||
import kotlinx.coroutines.flow.flowOn
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Provider
|
||||
|
||||
class WifiService
|
||||
class InternetConnectivityService
|
||||
@Inject
|
||||
constructor(
|
||||
@ApplicationContext private val context: Context,
|
||||
private val settingsRepository: SettingsRepository,
|
||||
@AppShell private val rootShell: Provider<RootShell>,
|
||||
@IoDispatcher private val ioDispatcher: CoroutineDispatcher,
|
||||
) : NetworkService {
|
||||
|
||||
override var capabilities: NetworkCapabilities? = null
|
||||
|
||||
val mutex = Mutex()
|
||||
|
||||
private var ssid: String? = null
|
||||
private var available: Boolean = false
|
||||
|
||||
private val connectivityManager =
|
||||
context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
|
||||
|
||||
@get:Synchronized @set:Synchronized
|
||||
private var wifiCapabilities: NetworkCapabilities? = null
|
||||
|
||||
@get:Synchronized @set:Synchronized
|
||||
private var wifiNetworkChanged: Boolean = false
|
||||
|
||||
override val didWifiChangeSinceLastCapabilitiesQuery: Boolean
|
||||
get() = wifiNetworkChanged
|
||||
|
||||
override val status = callbackFlow {
|
||||
|
||||
var wifiState: Boolean = false
|
||||
var ethernetState: Boolean = false
|
||||
var cellularState: Boolean = false
|
||||
|
||||
fun emitState() {
|
||||
trySend(NetworkStatus(wifiState, ethernetState, cellularState))
|
||||
}
|
||||
|
||||
val currentNetwork = connectivityManager.activeNetwork
|
||||
if (currentNetwork == null) {
|
||||
emitState()
|
||||
}
|
||||
|
||||
fun updateCapabilityState(up: Boolean, network: Network) {
|
||||
with(connectivityManager.getNetworkCapabilities(network)) {
|
||||
when {
|
||||
this == null -> return
|
||||
hasTransport(NetworkCapabilities.TRANSPORT_WIFI) -> wifiState = up
|
||||
hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) ->
|
||||
cellularState = up
|
||||
|
||||
hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET) ->
|
||||
ethernetState = up
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun onWifiChange(network: Network, callback: () -> Unit) {
|
||||
if (connectivityManager.getNetworkCapabilities(network)?.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) == true) {
|
||||
callback()
|
||||
}
|
||||
}
|
||||
|
||||
fun onAvailable(network: Network) {
|
||||
onWifiChange(network) {
|
||||
wifiNetworkChanged = true
|
||||
}
|
||||
}
|
||||
|
||||
fun onCapabilitiesChanged(network: Network, networkCapabilities: NetworkCapabilities) {
|
||||
onWifiChange(network) {
|
||||
wifiCapabilities = networkCapabilities
|
||||
}
|
||||
updateCapabilityState(true, network)
|
||||
emitState()
|
||||
}
|
||||
|
||||
val networkStatusCallback =
|
||||
when (Build.VERSION.SDK_INT) {
|
||||
in Build.VERSION_CODES.S..Int.MAX_VALUE -> {
|
||||
@@ -53,20 +93,16 @@ constructor(
|
||||
FLAG_INCLUDE_LOCATION_INFO,
|
||||
) {
|
||||
override fun onAvailable(network: Network) {
|
||||
trySend(NetworkStatus.Available(network))
|
||||
onAvailable(network)
|
||||
}
|
||||
|
||||
override fun onLost(network: Network) {
|
||||
trySend(NetworkStatus.Unavailable())
|
||||
updateCapabilityState(false, network)
|
||||
emitState()
|
||||
}
|
||||
|
||||
override fun onCapabilitiesChanged(network: Network, networkCapabilities: NetworkCapabilities) {
|
||||
trySend(
|
||||
NetworkStatus.CapabilitiesChanged(
|
||||
network,
|
||||
networkCapabilities,
|
||||
),
|
||||
)
|
||||
onCapabilitiesChanged(network, networkCapabilities)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -74,21 +110,16 @@ constructor(
|
||||
else -> {
|
||||
object : ConnectivityManager.NetworkCallback() {
|
||||
override fun onAvailable(network: Network) {
|
||||
trySend(NetworkStatus.Available(network))
|
||||
onAvailable(network)
|
||||
}
|
||||
|
||||
override fun onLost(network: Network) {
|
||||
trySend(NetworkStatus.Unavailable())
|
||||
updateCapabilityState(false, network)
|
||||
emitState()
|
||||
}
|
||||
|
||||
override fun onCapabilitiesChanged(network: Network, networkCapabilities: NetworkCapabilities) {
|
||||
capabilities = networkCapabilities
|
||||
trySend(
|
||||
NetworkStatus.CapabilitiesChanged(
|
||||
network,
|
||||
networkCapabilities,
|
||||
),
|
||||
)
|
||||
onCapabilitiesChanged(network, networkCapabilities)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -96,37 +127,19 @@ constructor(
|
||||
val request =
|
||||
NetworkRequest.Builder()
|
||||
.addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
|
||||
.addTransportType(NetworkCapabilities.TRANSPORT_ETHERNET)
|
||||
.addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
|
||||
.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
|
||||
.addCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED)
|
||||
.build()
|
||||
connectivityManager.registerNetworkCallback(request, networkStatusCallback)
|
||||
|
||||
awaitClose { connectivityManager.unregisterNetworkCallback(networkStatusCallback) }
|
||||
}.onStart {
|
||||
// needed for services that are not yet available as it will impact later combine flows if we don't emit
|
||||
emit(NetworkStatus.Unavailable())
|
||||
}.catch {
|
||||
Timber.e(it)
|
||||
emit(NetworkStatus.Unavailable())
|
||||
}.transform {
|
||||
when (it) {
|
||||
is NetworkStatus.Available -> mutex.withLock {
|
||||
available = true
|
||||
}
|
||||
is NetworkStatus.CapabilitiesChanged -> mutex.withLock {
|
||||
if (available || ssid == null || ssid == Constants.UNREADABLE_SSID) {
|
||||
available = false
|
||||
Timber.d("Getting SSID from capabilities")
|
||||
ssid = if (settingsRepository.getSettings().isWifiNameByShellEnabled) {
|
||||
rootShell.get().getCurrentWifiName()
|
||||
} else {
|
||||
getNetworkName(it.networkCapabilities, context)
|
||||
}
|
||||
}
|
||||
emit(Status(true, ssid))
|
||||
}
|
||||
is NetworkStatus.Unavailable -> emit(Status(false, null))
|
||||
}
|
||||
}.flowOn(ioDispatcher)
|
||||
|
||||
override fun getWifiCapabilities(): NetworkCapabilities? {
|
||||
wifiNetworkChanged = false
|
||||
return wifiCapabilities
|
||||
}
|
||||
|
||||
companion object {
|
||||
-67
@@ -1,67 +0,0 @@
|
||||
package com.zaneschepke.wireguardautotunnel.service.network
|
||||
|
||||
import android.content.Context
|
||||
import android.net.ConnectivityManager
|
||||
import android.net.Network
|
||||
import android.net.NetworkCapabilities
|
||||
import android.net.NetworkRequest
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import kotlinx.coroutines.channels.awaitClose
|
||||
import kotlinx.coroutines.flow.callbackFlow
|
||||
import kotlinx.coroutines.flow.catch
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.onStart
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
class MobileDataService
|
||||
@Inject
|
||||
constructor(
|
||||
@ApplicationContext context: Context,
|
||||
) : NetworkService {
|
||||
|
||||
override var capabilities: NetworkCapabilities? = null
|
||||
|
||||
private val connectivityManager =
|
||||
context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
|
||||
|
||||
override val status = callbackFlow {
|
||||
val networkStatusCallback = object : ConnectivityManager.NetworkCallback() {
|
||||
override fun onAvailable(network: Network) {
|
||||
trySend(NetworkStatus.Available(network))
|
||||
}
|
||||
override fun onLost(network: Network) {
|
||||
trySend(NetworkStatus.Unavailable())
|
||||
}
|
||||
override fun onCapabilitiesChanged(network: Network, networkCapabilities: NetworkCapabilities) {
|
||||
capabilities = networkCapabilities
|
||||
trySend(
|
||||
NetworkStatus.CapabilitiesChanged(
|
||||
network,
|
||||
networkCapabilities,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
val request =
|
||||
NetworkRequest.Builder()
|
||||
.addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
|
||||
.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
|
||||
.addCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED)
|
||||
.build()
|
||||
connectivityManager.registerNetworkCallback(request, networkStatusCallback)
|
||||
|
||||
awaitClose { connectivityManager.unregisterNetworkCallback(networkStatusCallback) }
|
||||
}.onStart {
|
||||
// needed for services that are not yet available as it will impact later combine flows if we don't emit
|
||||
emit(NetworkStatus.Unavailable())
|
||||
}.catch {
|
||||
Timber.e(it)
|
||||
emit(NetworkStatus.Unavailable())
|
||||
}.map {
|
||||
when (it) {
|
||||
is NetworkStatus.Available, is NetworkStatus.CapabilitiesChanged -> Status(true, null)
|
||||
is NetworkStatus.Unavailable -> Status(false, null)
|
||||
}
|
||||
}
|
||||
}
|
||||
+4
-20
@@ -1,28 +1,12 @@
|
||||
package com.zaneschepke.wireguardautotunnel.service.network
|
||||
|
||||
import android.net.Network
|
||||
import android.net.NetworkCapabilities
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.map
|
||||
|
||||
interface NetworkService {
|
||||
val status: Flow<Status>
|
||||
var capabilities: NetworkCapabilities?
|
||||
}
|
||||
val status: Flow<NetworkStatus>
|
||||
|
||||
inline fun <Result> Flow<NetworkStatus>.map(
|
||||
crossinline onUnavailable: suspend () -> Result,
|
||||
crossinline onAvailable: suspend (network: Network) -> Result,
|
||||
crossinline onCapabilitiesChanged:
|
||||
suspend (network: Network, networkCapabilities: NetworkCapabilities) -> Result,
|
||||
): Flow<Result> = map { status ->
|
||||
when (status) {
|
||||
is NetworkStatus.Unavailable -> onUnavailable()
|
||||
is NetworkStatus.Available -> onAvailable(status.network)
|
||||
is NetworkStatus.CapabilitiesChanged ->
|
||||
onCapabilitiesChanged(
|
||||
status.network,
|
||||
status.networkCapabilities,
|
||||
)
|
||||
}
|
||||
// util to help limit location queries
|
||||
val didWifiChangeSinceLastCapabilitiesQuery: Boolean
|
||||
fun getWifiCapabilities(): NetworkCapabilities?
|
||||
}
|
||||
|
||||
+6
-11
@@ -1,14 +1,9 @@
|
||||
package com.zaneschepke.wireguardautotunnel.service.network
|
||||
|
||||
import android.net.Network
|
||||
import android.net.NetworkCapabilities
|
||||
|
||||
sealed class NetworkStatus {
|
||||
abstract val isConnected: Boolean
|
||||
class Available(val network: Network, override val isConnected: Boolean = true) : NetworkStatus()
|
||||
|
||||
class Unavailable(override val isConnected: Boolean = false) : NetworkStatus()
|
||||
|
||||
class CapabilitiesChanged(val network: Network, val networkCapabilities: NetworkCapabilities, override val isConnected: Boolean = true) :
|
||||
NetworkStatus()
|
||||
data class NetworkStatus(
|
||||
val wifiAvailable: Boolean,
|
||||
val ethernetAvailable: Boolean,
|
||||
val cellularAvailable: Boolean,
|
||||
) {
|
||||
val allOffline = !wifiAvailable && !ethernetAvailable && !cellularAvailable
|
||||
}
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
package com.zaneschepke.wireguardautotunnel.service.network
|
||||
|
||||
data class Status(
|
||||
val available: Boolean,
|
||||
val name: String?,
|
||||
)
|
||||
@@ -5,6 +5,7 @@ import com.zaneschepke.wireguardautotunnel.service.tunnel.statistics.TunnelStati
|
||||
|
||||
data class VpnState(
|
||||
val status: TunnelState = TunnelState.DOWN,
|
||||
val backendState: BackendState = BackendState.INACTIVE,
|
||||
val tunnelConfig: TunnelConfig? = null,
|
||||
val statistics: TunnelStatistics? = null,
|
||||
)
|
||||
|
||||
+8
-36
@@ -2,28 +2,24 @@ package com.zaneschepke.wireguardautotunnel.service.tunnel
|
||||
|
||||
import com.wireguard.android.backend.Backend
|
||||
import com.wireguard.android.backend.Tunnel.State
|
||||
import com.zaneschepke.wireguardautotunnel.R
|
||||
import com.zaneschepke.wireguardautotunnel.WireGuardAutoTunnel
|
||||
import com.zaneschepke.wireguardautotunnel.data.domain.TunnelConfig
|
||||
import com.zaneschepke.wireguardautotunnel.data.repository.AppDataRepository
|
||||
import com.zaneschepke.wireguardautotunnel.data.repository.TunnelConfigRepository
|
||||
import com.zaneschepke.wireguardautotunnel.module.ApplicationScope
|
||||
import com.zaneschepke.wireguardautotunnel.module.Ethernet
|
||||
import com.zaneschepke.wireguardautotunnel.module.IoDispatcher
|
||||
import com.zaneschepke.wireguardautotunnel.module.Kernel
|
||||
import com.zaneschepke.wireguardautotunnel.module.MobileData
|
||||
import com.zaneschepke.wireguardautotunnel.module.Wifi
|
||||
import com.zaneschepke.wireguardautotunnel.service.foreground.ServiceManager
|
||||
import com.zaneschepke.wireguardautotunnel.service.foreground.autotunnel.model.NetworkState
|
||||
import com.zaneschepke.wireguardautotunnel.service.network.NetworkService
|
||||
import com.zaneschepke.wireguardautotunnel.service.notification.NotificationService
|
||||
import com.zaneschepke.wireguardautotunnel.service.notification.NotificationService.Companion.VPN_NOTIFICATION_ID
|
||||
import com.zaneschepke.wireguardautotunnel.service.notification.WireGuardNotification
|
||||
import com.zaneschepke.wireguardautotunnel.service.tunnel.statistics.AmneziaStatistics
|
||||
import com.zaneschepke.wireguardautotunnel.service.tunnel.statistics.TunnelStatistics
|
||||
import com.zaneschepke.wireguardautotunnel.service.tunnel.statistics.WireGuardStatistics
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.snackbar.SnackbarController
|
||||
import com.zaneschepke.wireguardautotunnel.util.Constants
|
||||
import com.zaneschepke.wireguardautotunnel.R
|
||||
import com.zaneschepke.wireguardautotunnel.service.notification.WireGuardNotification
|
||||
import com.zaneschepke.wireguardautotunnel.util.StringValue
|
||||
import com.zaneschepke.wireguardautotunnel.util.extensions.asAmBackendState
|
||||
import com.zaneschepke.wireguardautotunnel.util.extensions.asBackendState
|
||||
@@ -31,14 +27,11 @@ import com.zaneschepke.wireguardautotunnel.util.extensions.cancelWithMessage
|
||||
import com.zaneschepke.wireguardautotunnel.util.extensions.isReachable
|
||||
import kotlinx.coroutines.CoroutineDispatcher
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.FlowPreview
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.launch
|
||||
@@ -63,9 +56,7 @@ constructor(
|
||||
@IoDispatcher private val ioDispatcher: CoroutineDispatcher,
|
||||
private val serviceManager: ServiceManager,
|
||||
private val notificationService: NotificationService,
|
||||
@Wifi private val wifiService: NetworkService,
|
||||
@MobileData private val mobileDataService: NetworkService,
|
||||
@Ethernet private val ethernetService: NetworkService,
|
||||
private val internetConnectivityService: NetworkService,
|
||||
) : TunnelService {
|
||||
|
||||
private val _vpnState = MutableStateFlow(VpnState())
|
||||
@@ -105,23 +96,6 @@ constructor(
|
||||
}
|
||||
}
|
||||
|
||||
// TODO refactor duplicate
|
||||
@OptIn(FlowPreview::class)
|
||||
private fun combineNetworkEventsJob(): Flow<NetworkState> {
|
||||
return combine(
|
||||
wifiService.status,
|
||||
mobileDataService.status,
|
||||
ethernetService.status,
|
||||
) { wifi, mobileData, ethernet ->
|
||||
NetworkState(
|
||||
wifi.available,
|
||||
mobileData.available,
|
||||
ethernet.available,
|
||||
wifi.name,
|
||||
)
|
||||
}.distinctUntilChanged()
|
||||
}
|
||||
|
||||
private suspend fun setState(tunnelConfig: TunnelConfig, tunnelState: TunnelState): Result<TunnelState> {
|
||||
return runCatching {
|
||||
when (val backend = backend()) {
|
||||
@@ -248,6 +222,9 @@ constructor(
|
||||
when (val backend = backend()) {
|
||||
is org.amnezia.awg.backend.Backend -> {
|
||||
backend.setBackendState(backendState.asAmBackendState(), allowedIps)
|
||||
_vpnState.update {
|
||||
it.copy(backendState = backendState)
|
||||
}
|
||||
}
|
||||
is Backend -> {
|
||||
// TODO not yet implemented
|
||||
@@ -447,13 +424,8 @@ constructor(
|
||||
}
|
||||
|
||||
private fun startNetworkJob() = applicationScope.launch(ioDispatcher) {
|
||||
combineNetworkEventsJob().collect {
|
||||
Timber.d("New network state: $it")
|
||||
if (!it.isWifiConnected && !it.isEthernetConnected && !it.isMobileDataConnected) {
|
||||
isNetworkAvailable.set(false)
|
||||
} else {
|
||||
isNetworkAvailable.set(true)
|
||||
}
|
||||
internetConnectivityService.status.distinctUntilChanged().collect {
|
||||
isNetworkAvailable.set(!it.allOffline)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -213,7 +213,7 @@ constructor(
|
||||
if (!enabled) return@withContext tunnelService.get().setBackendState(BackendState.SERVICE_ACTIVE, emptySet())
|
||||
Timber.d("Starting kill switch")
|
||||
val allowedIps = if (appDataRepository.settings.getSettings().isLanOnKillSwitchEnabled) {
|
||||
TunnelConfig.IPV4_PUBLIC_NETWORKS
|
||||
TunnelConfig.LAN_BYPASS_ALLOWED_IPS
|
||||
} else {
|
||||
emptySet()
|
||||
}
|
||||
@@ -227,7 +227,7 @@ constructor(
|
||||
isLanOnKillSwitchEnabled = enabled,
|
||||
),
|
||||
)
|
||||
val allowedIps = if (enabled) TunnelConfig.IPV4_PUBLIC_NETWORKS else emptySet()
|
||||
val allowedIps = if (enabled) TunnelConfig.LAN_BYPASS_ALLOWED_IPS else emptySet()
|
||||
Timber.d("Setting allowedIps $allowedIps")
|
||||
tunnelService.get().setBackendState(BackendState.KILL_SWITCH_ACTIVE, allowedIps)
|
||||
}
|
||||
|
||||
@@ -35,7 +35,6 @@ import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.navigation.compose.NavHost
|
||||
import androidx.navigation.compose.composable
|
||||
import androidx.navigation.compose.rememberNavController
|
||||
@@ -70,7 +69,6 @@ import com.zaneschepke.wireguardautotunnel.ui.theme.WireguardAutoTunnelTheme
|
||||
import com.zaneschepke.wireguardautotunnel.util.Constants
|
||||
import com.zaneschepke.wireguardautotunnel.util.extensions.requestAutoTunnelTileServiceUpdate
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
import kotlin.system.exitProcess
|
||||
|
||||
@@ -200,7 +198,7 @@ class MainActivity : AppCompatActivity() {
|
||||
}
|
||||
composable<Route.AutoTunnel> {
|
||||
AutoTunnelScreen(
|
||||
appUiState,
|
||||
appUiState.settings,
|
||||
)
|
||||
}
|
||||
composable<Route.Appearance> {
|
||||
@@ -216,7 +214,7 @@ class MainActivity : AppCompatActivity() {
|
||||
SupportScreen(appUiState, viewModel)
|
||||
}
|
||||
composable<Route.AutoTunnelAdvanced> {
|
||||
AdvancedScreen(appUiState, viewModel)
|
||||
AdvancedScreen(appUiState.settings, viewModel)
|
||||
}
|
||||
composable<Route.Logs> {
|
||||
LogsScreen()
|
||||
@@ -251,11 +249,9 @@ class MainActivity : AppCompatActivity() {
|
||||
TunnelAutoTunnelScreen(config, appUiState.settings)
|
||||
}
|
||||
}
|
||||
BackHandler(enabled = true) {
|
||||
lifecycleScope.launch {
|
||||
if (!navController.popBackStack()) {
|
||||
this@MainActivity.finish()
|
||||
}
|
||||
BackHandler {
|
||||
if (navController.previousBackStackEntry == null || !navController.popBackStack()) {
|
||||
this@MainActivity.finish()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+3
-3
@@ -47,13 +47,13 @@ fun SubmitConfigurationTextBox(
|
||||
|
||||
CustomTextField(
|
||||
isError = isErrorValue(stateValue),
|
||||
textStyle = MaterialTheme.typography.bodySmall,
|
||||
textStyle = MaterialTheme.typography.bodySmall.copy(color = MaterialTheme.colorScheme.onSurface),
|
||||
value = stateValue,
|
||||
onValueChange = { stateValue = it },
|
||||
interactionSource = interactionSource,
|
||||
label = { Text(label) },
|
||||
label = { Text(label, color = MaterialTheme.colorScheme.onSurface, style = MaterialTheme.typography.labelMedium) },
|
||||
containerColor = MaterialTheme.colorScheme.surface,
|
||||
placeholder = { Text(hint, style = MaterialTheme.typography.bodySmall) },
|
||||
placeholder = { Text(hint, style = MaterialTheme.typography.bodySmall, color = MaterialTheme.colorScheme.outline) },
|
||||
modifier =
|
||||
Modifier
|
||||
.padding(
|
||||
|
||||
+53
-29
@@ -21,6 +21,7 @@ import androidx.compose.material.icons.outlined.Security
|
||||
import androidx.compose.material.icons.outlined.Settings
|
||||
import androidx.compose.material.icons.outlined.SettingsEthernet
|
||||
import androidx.compose.material.icons.outlined.SignalCellular4Bar
|
||||
import androidx.compose.material.icons.outlined.VpnKeyOff
|
||||
import androidx.compose.material.icons.outlined.Wifi
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
@@ -42,7 +43,7 @@ import com.google.accompanist.permissions.ExperimentalPermissionsApi
|
||||
import com.google.accompanist.permissions.isGranted
|
||||
import com.google.accompanist.permissions.rememberPermissionState
|
||||
import com.zaneschepke.wireguardautotunnel.R
|
||||
import com.zaneschepke.wireguardautotunnel.ui.AppUiState
|
||||
import com.zaneschepke.wireguardautotunnel.data.domain.Settings
|
||||
import com.zaneschepke.wireguardautotunnel.ui.Route
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.ScaledSwitch
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SelectionItem
|
||||
@@ -64,7 +65,7 @@ import com.zaneschepke.wireguardautotunnel.util.extensions.scaledWidth
|
||||
|
||||
@OptIn(ExperimentalPermissionsApi::class, ExperimentalLayoutApi::class)
|
||||
@Composable
|
||||
fun AutoTunnelScreen(uiState: AppUiState, viewModel: AutoTunnelViewModel = hiltViewModel()) {
|
||||
fun AutoTunnelScreen(settings: Settings, viewModel: AutoTunnelViewModel = hiltViewModel()) {
|
||||
val context = LocalContext.current
|
||||
val navController = LocalNavController.current
|
||||
|
||||
@@ -103,7 +104,7 @@ fun AutoTunnelScreen(uiState: AppUiState, viewModel: AutoTunnelViewModel = hiltV
|
||||
}
|
||||
}
|
||||
|
||||
LaunchedEffect(uiState.settings.trustedNetworkSSIDs) {
|
||||
LaunchedEffect(settings.trustedNetworkSSIDs) {
|
||||
currentText = ""
|
||||
}
|
||||
|
||||
@@ -154,15 +155,15 @@ fun AutoTunnelScreen(uiState: AppUiState, viewModel: AutoTunnelViewModel = hiltV
|
||||
},
|
||||
trailing = {
|
||||
ScaledSwitch(
|
||||
enabled = !uiState.settings.isAlwaysOnVpnEnabled,
|
||||
checked = uiState.settings.isTunnelOnWifiEnabled,
|
||||
enabled = !settings.isAlwaysOnVpnEnabled,
|
||||
checked = settings.isTunnelOnWifiEnabled,
|
||||
onClick = {
|
||||
viewModel.onToggleTunnelOnWifi()
|
||||
viewModel.onToggleTunnelOnWifi(settings)
|
||||
},
|
||||
)
|
||||
},
|
||||
onClick = {
|
||||
viewModel.onToggleTunnelOnWifi()
|
||||
viewModel.onToggleTunnelOnWifi(settings)
|
||||
},
|
||||
),
|
||||
SelectionItem(
|
||||
@@ -181,19 +182,19 @@ fun AutoTunnelScreen(uiState: AppUiState, viewModel: AutoTunnelViewModel = hiltV
|
||||
},
|
||||
trailing = {
|
||||
ScaledSwitch(
|
||||
checked = uiState.settings.isWifiNameByShellEnabled,
|
||||
checked = settings.isWifiNameByShellEnabled,
|
||||
onClick = {
|
||||
viewModel.onRootShellWifiToggle()
|
||||
viewModel.onRootShellWifiToggle(settings)
|
||||
},
|
||||
)
|
||||
},
|
||||
onClick = {
|
||||
viewModel.onRootShellWifiToggle()
|
||||
viewModel.onRootShellWifiToggle(settings)
|
||||
},
|
||||
),
|
||||
),
|
||||
)
|
||||
if (uiState.settings.isTunnelOnWifiEnabled) {
|
||||
if (settings.isTunnelOnWifiEnabled) {
|
||||
addAll(
|
||||
listOf(
|
||||
SelectionItem(
|
||||
@@ -209,14 +210,14 @@ fun AutoTunnelScreen(uiState: AppUiState, viewModel: AutoTunnelViewModel = hiltV
|
||||
},
|
||||
trailing = {
|
||||
ScaledSwitch(
|
||||
checked = uiState.settings.isWildcardsEnabled,
|
||||
checked = settings.isWildcardsEnabled,
|
||||
onClick = {
|
||||
viewModel.onToggleWildcards()
|
||||
viewModel.onToggleWildcards(settings)
|
||||
},
|
||||
)
|
||||
},
|
||||
onClick = {
|
||||
viewModel.onToggleWildcards()
|
||||
viewModel.onToggleWildcards(settings)
|
||||
},
|
||||
),
|
||||
SelectionItem(
|
||||
@@ -255,21 +256,44 @@ fun AutoTunnelScreen(uiState: AppUiState, viewModel: AutoTunnelViewModel = hiltV
|
||||
},
|
||||
description = {
|
||||
TrustedNetworkTextBox(
|
||||
uiState.settings.trustedNetworkSSIDs,
|
||||
onDelete = viewModel::onDeleteTrustedSSID,
|
||||
settings.trustedNetworkSSIDs,
|
||||
onDelete = { viewModel.onDeleteTrustedSSID(it, settings) },
|
||||
currentText = currentText,
|
||||
onSave = { ssid ->
|
||||
if (uiState.settings.isWifiNameByShellEnabled || isWifiNameReadable()) viewModel.onSaveTrustedSSID(ssid)
|
||||
if (settings.isWifiNameByShellEnabled || isWifiNameReadable()) viewModel.onSaveTrustedSSID(ssid, settings)
|
||||
},
|
||||
onValueChange = { currentText = it },
|
||||
supporting = {
|
||||
if (uiState.settings.isWildcardsEnabled) {
|
||||
if (settings.isWildcardsEnabled) {
|
||||
WildcardsLabel()
|
||||
}
|
||||
},
|
||||
)
|
||||
},
|
||||
),
|
||||
SelectionItem(
|
||||
Icons.Outlined.VpnKeyOff,
|
||||
title = {
|
||||
Text(
|
||||
stringResource(R.string.kill_switch_off),
|
||||
style = MaterialTheme.typography.bodyMedium.copy(MaterialTheme.colorScheme.onSurface),
|
||||
)
|
||||
},
|
||||
description = {
|
||||
},
|
||||
trailing = {
|
||||
ScaledSwitch(
|
||||
enabled = settings.isVpnKillSwitchEnabled,
|
||||
checked = settings.isDisableKillSwitchOnTrustedEnabled,
|
||||
onClick = {
|
||||
viewModel.onToggleStopKillSwitchOnTrusted(settings)
|
||||
},
|
||||
)
|
||||
},
|
||||
onClick = {
|
||||
viewModel.onToggleStopKillSwitchOnTrusted(settings)
|
||||
},
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
@@ -287,13 +311,13 @@ fun AutoTunnelScreen(uiState: AppUiState, viewModel: AutoTunnelViewModel = hiltV
|
||||
},
|
||||
trailing = {
|
||||
ScaledSwitch(
|
||||
enabled = !uiState.settings.isAlwaysOnVpnEnabled,
|
||||
checked = uiState.settings.isTunnelOnMobileDataEnabled,
|
||||
onClick = { viewModel.onToggleTunnelOnMobileData() },
|
||||
enabled = !settings.isAlwaysOnVpnEnabled,
|
||||
checked = settings.isTunnelOnMobileDataEnabled,
|
||||
onClick = { viewModel.onToggleTunnelOnMobileData(settings) },
|
||||
)
|
||||
},
|
||||
onClick = {
|
||||
viewModel.onToggleTunnelOnMobileData()
|
||||
viewModel.onToggleTunnelOnMobileData(settings)
|
||||
},
|
||||
),
|
||||
SelectionItem(
|
||||
@@ -306,13 +330,13 @@ fun AutoTunnelScreen(uiState: AppUiState, viewModel: AutoTunnelViewModel = hiltV
|
||||
},
|
||||
trailing = {
|
||||
ScaledSwitch(
|
||||
enabled = !uiState.settings.isAlwaysOnVpnEnabled,
|
||||
checked = uiState.settings.isTunnelOnEthernetEnabled,
|
||||
onClick = { viewModel.onToggleTunnelOnEthernet() },
|
||||
enabled = !settings.isAlwaysOnVpnEnabled,
|
||||
checked = settings.isTunnelOnEthernetEnabled,
|
||||
onClick = { viewModel.onToggleTunnelOnEthernet(settings) },
|
||||
)
|
||||
},
|
||||
onClick = {
|
||||
viewModel.onToggleTunnelOnEthernet()
|
||||
viewModel.onToggleTunnelOnEthernet(settings)
|
||||
},
|
||||
),
|
||||
SelectionItem(
|
||||
@@ -331,12 +355,12 @@ fun AutoTunnelScreen(uiState: AppUiState, viewModel: AutoTunnelViewModel = hiltV
|
||||
},
|
||||
trailing = {
|
||||
ScaledSwitch(
|
||||
checked = uiState.settings.isStopOnNoInternetEnabled,
|
||||
onClick = { viewModel.onToggleStopOnNoInternet() },
|
||||
checked = settings.isStopOnNoInternetEnabled,
|
||||
onClick = { viewModel.onToggleStopOnNoInternet(settings) },
|
||||
)
|
||||
},
|
||||
onClick = {
|
||||
viewModel.onToggleStopOnNoInternet()
|
||||
viewModel.onToggleStopOnNoInternet(settings)
|
||||
},
|
||||
),
|
||||
),
|
||||
|
||||
+29
-23
@@ -12,8 +12,6 @@ import com.zaneschepke.wireguardautotunnel.ui.common.snackbar.SnackbarController
|
||||
import com.zaneschepke.wireguardautotunnel.util.StringValue
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.CoroutineDispatcher
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import javax.inject.Inject
|
||||
@@ -28,11 +26,8 @@ constructor(
|
||||
@IoDispatcher private val ioDispatcher: CoroutineDispatcher,
|
||||
) : ViewModel() {
|
||||
|
||||
private val settings = appDataRepository.settings.getSettingsFlow()
|
||||
.stateIn(viewModelScope, SharingStarted.Eagerly, Settings())
|
||||
|
||||
fun onToggleTunnelOnWifi() = viewModelScope.launch {
|
||||
with(settings.value) {
|
||||
fun onToggleTunnelOnWifi(settings: Settings) = viewModelScope.launch {
|
||||
with(settings) {
|
||||
appDataRepository.settings.save(
|
||||
copy(
|
||||
isTunnelOnWifiEnabled = !isTunnelOnWifiEnabled,
|
||||
@@ -41,8 +36,8 @@ constructor(
|
||||
}
|
||||
}
|
||||
|
||||
fun onToggleTunnelOnMobileData() = viewModelScope.launch {
|
||||
with(settings.value) {
|
||||
fun onToggleTunnelOnMobileData(settings: Settings) = viewModelScope.launch {
|
||||
with(settings) {
|
||||
appDataRepository.settings.save(
|
||||
copy(
|
||||
isTunnelOnMobileDataEnabled = !isTunnelOnMobileDataEnabled,
|
||||
@@ -51,8 +46,8 @@ constructor(
|
||||
}
|
||||
}
|
||||
|
||||
fun onToggleWildcards() = viewModelScope.launch {
|
||||
with(settings.value) {
|
||||
fun onToggleWildcards(settings: Settings) = viewModelScope.launch {
|
||||
with(settings) {
|
||||
appDataRepository.settings.save(
|
||||
copy(
|
||||
isWildcardsEnabled = !isWildcardsEnabled,
|
||||
@@ -61,8 +56,8 @@ constructor(
|
||||
}
|
||||
}
|
||||
|
||||
fun onDeleteTrustedSSID(ssid: String) = viewModelScope.launch {
|
||||
with(settings.value) {
|
||||
fun onDeleteTrustedSSID(ssid: String, settings: Settings) = viewModelScope.launch {
|
||||
with(settings) {
|
||||
appDataRepository.settings.save(
|
||||
copy(
|
||||
trustedNetworkSSIDs = (trustedNetworkSSIDs - ssid).toMutableList(),
|
||||
@@ -71,9 +66,9 @@ constructor(
|
||||
}
|
||||
}
|
||||
|
||||
fun onRootShellWifiToggle() = viewModelScope.launch {
|
||||
fun onRootShellWifiToggle(settings: Settings) = viewModelScope.launch {
|
||||
requestRoot().onSuccess {
|
||||
with(settings.value) {
|
||||
with(settings) {
|
||||
appDataRepository.settings.save(
|
||||
copy(isWifiNameByShellEnabled = !isWifiNameByShellEnabled),
|
||||
)
|
||||
@@ -92,8 +87,8 @@ constructor(
|
||||
}
|
||||
}
|
||||
|
||||
fun onToggleTunnelOnEthernet() = viewModelScope.launch {
|
||||
with(settings.value) {
|
||||
fun onToggleTunnelOnEthernet(settings: Settings) = viewModelScope.launch {
|
||||
with(settings) {
|
||||
appDataRepository.settings.save(
|
||||
copy(
|
||||
isTunnelOnEthernetEnabled = !isTunnelOnEthernetEnabled,
|
||||
@@ -102,13 +97,16 @@ constructor(
|
||||
}
|
||||
}
|
||||
|
||||
fun onSaveTrustedSSID(ssid: String) = viewModelScope.launch {
|
||||
fun onSaveTrustedSSID(ssid: String, settings: Settings) = viewModelScope.launch {
|
||||
if (ssid.isEmpty()) return@launch
|
||||
val trimmed = ssid.trim()
|
||||
with(settings.value) {
|
||||
with(settings) {
|
||||
if (!trustedNetworkSSIDs.contains(trimmed)) {
|
||||
this.trustedNetworkSSIDs.add(ssid)
|
||||
appDataRepository.settings.save(this)
|
||||
appDataRepository.settings.save(
|
||||
settings.copy(
|
||||
trustedNetworkSSIDs = (trustedNetworkSSIDs + ssid).toMutableList(),
|
||||
),
|
||||
)
|
||||
} else {
|
||||
SnackbarController.showMessage(
|
||||
StringValue.StringResource(
|
||||
@@ -119,11 +117,19 @@ constructor(
|
||||
}
|
||||
}
|
||||
|
||||
fun onToggleStopOnNoInternet() = viewModelScope.launch {
|
||||
with(settings.value) {
|
||||
fun onToggleStopOnNoInternet(settings: Settings) = viewModelScope.launch {
|
||||
with(settings) {
|
||||
appDataRepository.settings.save(
|
||||
copy(isStopOnNoInternetEnabled = !isStopOnNoInternetEnabled),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun onToggleStopKillSwitchOnTrusted(settings: Settings) = viewModelScope.launch {
|
||||
with(settings) {
|
||||
appDataRepository.settings.save(
|
||||
copy(isDisableKillSwitchOnTrustedEnabled = !isDisableKillSwitchOnTrustedEnabled),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+6
-16
@@ -18,9 +18,7 @@ import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableIntStateOf
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
@@ -29,7 +27,7 @@ import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.zaneschepke.wireguardautotunnel.R
|
||||
import com.zaneschepke.wireguardautotunnel.ui.AppUiState
|
||||
import com.zaneschepke.wireguardautotunnel.data.domain.Settings
|
||||
import com.zaneschepke.wireguardautotunnel.ui.AppViewModel
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SelectionItem
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SurfaceSelectionGroupButton
|
||||
@@ -38,21 +36,11 @@ import com.zaneschepke.wireguardautotunnel.util.extensions.scaledHeight
|
||||
import com.zaneschepke.wireguardautotunnel.util.extensions.scaledWidth
|
||||
|
||||
@Composable
|
||||
fun AdvancedScreen(appUiState: AppUiState, appViewModel: AppViewModel) {
|
||||
fun AdvancedScreen(settings: Settings, appViewModel: AppViewModel) {
|
||||
var isDropDownExpanded by remember {
|
||||
mutableStateOf(false)
|
||||
}
|
||||
|
||||
var selected by remember { mutableIntStateOf(appUiState.settings.debounceDelaySeconds) }
|
||||
|
||||
LaunchedEffect(selected) {
|
||||
if (selected == appUiState.settings.debounceDelaySeconds) return@LaunchedEffect
|
||||
appViewModel.saveSettings(appUiState.settings.copy(debounceDelaySeconds = selected))
|
||||
if (appUiState.settings.isAutoTunnelEnabled) {
|
||||
appViewModel.bounceAutoTunnel()
|
||||
}
|
||||
}
|
||||
|
||||
Scaffold(
|
||||
topBar = {
|
||||
TopNavBar(stringResource(R.string.advanced_settings))
|
||||
@@ -87,7 +75,7 @@ fun AdvancedScreen(appUiState: AppUiState, appViewModel: AppViewModel) {
|
||||
horizontalArrangement = Arrangement.spacedBy(5.dp, Alignment.CenterHorizontally),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Text(text = selected.toString(), style = MaterialTheme.typography.bodyMedium)
|
||||
Text(text = settings.debounceDelaySeconds.toString(), style = MaterialTheme.typography.bodyMedium)
|
||||
val icon = Icons.Default.ArrowDropDown
|
||||
Icon(icon, icon.name)
|
||||
}
|
||||
@@ -107,7 +95,9 @@ fun AdvancedScreen(appUiState: AppUiState, appViewModel: AppViewModel) {
|
||||
},
|
||||
onClick = {
|
||||
isDropDownExpanded = false
|
||||
selected = num
|
||||
appViewModel.saveSettings(
|
||||
settings.copy(debounceDelaySeconds = num),
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
+8
-2
@@ -65,11 +65,17 @@ fun TrustedNetworkTextBox(
|
||||
}
|
||||
}
|
||||
CustomTextField(
|
||||
textStyle = MaterialTheme.typography.bodySmall,
|
||||
textStyle = MaterialTheme.typography.bodySmall.copy(color = MaterialTheme.colorScheme.onSurface),
|
||||
value = currentText,
|
||||
onValueChange = onValueChange,
|
||||
interactionSource = remember { MutableInteractionSource() },
|
||||
label = { Text(stringResource(R.string.add_wifi_name)) },
|
||||
label = {
|
||||
Text(
|
||||
stringResource(R.string.add_wifi_name),
|
||||
color = MaterialTheme.colorScheme.onSurface,
|
||||
style = MaterialTheme.typography.labelMedium,
|
||||
)
|
||||
},
|
||||
containerColor = MaterialTheme.colorScheme.surface,
|
||||
supportingText = supporting,
|
||||
modifier =
|
||||
|
||||
+10
-12
@@ -57,6 +57,11 @@ fun OptionsScreen(tunnelConfig: TunnelConfig, viewModel: TunnelOptionsViewModel
|
||||
LaunchedEffect(tunnelConfig.tunnelNetworks) {
|
||||
currentText = ""
|
||||
}
|
||||
|
||||
val onPingToggle = {
|
||||
viewModel.saveTunnelChanges(tunnelConfig.copy(isPingEnabled = !tunnelConfig.isPingEnabled))
|
||||
}
|
||||
|
||||
Scaffold(
|
||||
topBar = {
|
||||
TopNavBar(tunnelConfig.name)
|
||||
@@ -165,10 +170,10 @@ fun OptionsScreen(tunnelConfig: TunnelConfig, viewModel: TunnelOptionsViewModel
|
||||
trailing = {
|
||||
ScaledSwitch(
|
||||
checked = tunnelConfig.isPingEnabled,
|
||||
onClick = { viewModel.onToggleRestartOnPing(tunnelConfig) },
|
||||
onClick = { onPingToggle() },
|
||||
)
|
||||
},
|
||||
onClick = { viewModel.onToggleRestartOnPing(tunnelConfig) },
|
||||
onClick = { onPingToggle() },
|
||||
),
|
||||
)
|
||||
if (tunnelConfig.isPingEnabled) {
|
||||
@@ -188,8 +193,7 @@ fun OptionsScreen(tunnelConfig: TunnelConfig, viewModel: TunnelOptionsViewModel
|
||||
},
|
||||
)
|
||||
fun isSecondsError(seconds: String?): Boolean {
|
||||
return seconds?.let { value -> if (value.isBlank()) false else value.toLong() >= Long.MAX_VALUE / 1000 }
|
||||
?: false
|
||||
return seconds?.let { value -> if (value.isBlank()) false else value.toLong() >= Long.MAX_VALUE / 1000 } == true
|
||||
}
|
||||
SubmitConfigurationTextBox(
|
||||
tunnelConfig.pingInterval?.let { (it / 1000).toString() },
|
||||
@@ -201,9 +205,7 @@ fun OptionsScreen(tunnelConfig: TunnelConfig, viewModel: TunnelOptionsViewModel
|
||||
),
|
||||
isErrorValue = ::isSecondsError,
|
||||
onSubmit = {
|
||||
viewModel.saveTunnelChanges(
|
||||
tunnelConfig.copy(pingInterval = if (it.isBlank()) null else it.toLong() * 1000),
|
||||
)
|
||||
viewModel.onPingIntervalChange(tunnelConfig, it)
|
||||
},
|
||||
)
|
||||
SubmitConfigurationTextBox(
|
||||
@@ -214,11 +216,7 @@ fun OptionsScreen(tunnelConfig: TunnelConfig, viewModel: TunnelOptionsViewModel
|
||||
keyboardType = KeyboardType.Number,
|
||||
),
|
||||
isErrorValue = ::isSecondsError,
|
||||
onSubmit = {
|
||||
viewModel.saveTunnelChanges(
|
||||
tunnelConfig.copy(pingCooldown = if (it.isBlank()) null else it.toLong() * 1000),
|
||||
)
|
||||
},
|
||||
onSubmit = { viewModel.onPingCoolDownChange(tunnelConfig, it) },
|
||||
)
|
||||
},
|
||||
),
|
||||
|
||||
+12
-7
@@ -14,13 +14,6 @@ class TunnelOptionsViewModel
|
||||
constructor(
|
||||
private val appDataRepository: AppDataRepository,
|
||||
) : ViewModel() {
|
||||
fun onToggleRestartOnPing(tunnelConfig: TunnelConfig) = viewModelScope.launch {
|
||||
appDataRepository.tunnels.save(
|
||||
tunnelConfig.copy(
|
||||
isPingEnabled = !tunnelConfig.isPingEnabled,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
fun onTogglePrimaryTunnel(tunnelConfig: TunnelConfig) = viewModelScope.launch {
|
||||
appDataRepository.tunnels.updatePrimaryTunnel(
|
||||
@@ -34,4 +27,16 @@ constructor(
|
||||
fun saveTunnelChanges(tunnelConfig: TunnelConfig) = viewModelScope.launch {
|
||||
appDataRepository.tunnels.save(tunnelConfig)
|
||||
}
|
||||
|
||||
fun onPingIntervalChange(tunnelConfig: TunnelConfig, interval: String) = viewModelScope.launch {
|
||||
val interval = if (interval.isBlank()) null else interval.toLong() * 1000
|
||||
saveTunnelChanges(
|
||||
tunnelConfig.copy(pingInterval = interval),
|
||||
)
|
||||
}
|
||||
|
||||
fun onPingCoolDownChange(tunnelConfig: TunnelConfig, cooldown: String) = viewModelScope.launch {
|
||||
val cooldown = if (cooldown.isBlank()) null else cooldown.toLong() * 1000
|
||||
saveTunnelChanges(tunnelConfig.copy(pingCooldown = cooldown))
|
||||
}
|
||||
}
|
||||
|
||||
+55
-10
@@ -303,7 +303,13 @@ fun ConfigScreen(tunnelConfig: TunnelConfig?, appViewModel: AppViewModel) {
|
||||
)
|
||||
}
|
||||
},
|
||||
label = { Text(stringResource(R.string.private_key)) },
|
||||
label = {
|
||||
Text(
|
||||
stringResource(R.string.private_key),
|
||||
color = MaterialTheme.colorScheme.onSurface,
|
||||
style = MaterialTheme.typography.labelMedium,
|
||||
)
|
||||
},
|
||||
singleLine = true,
|
||||
placeholder = {
|
||||
Text(
|
||||
@@ -342,7 +348,13 @@ fun ConfigScreen(tunnelConfig: TunnelConfig?, appViewModel: AppViewModel) {
|
||||
)
|
||||
}
|
||||
},
|
||||
label = { Text(stringResource(R.string.public_key)) },
|
||||
label = {
|
||||
Text(
|
||||
stringResource(R.string.public_key),
|
||||
color = MaterialTheme.colorScheme.onSurface,
|
||||
style = MaterialTheme.typography.labelMedium,
|
||||
)
|
||||
},
|
||||
singleLine = true,
|
||||
placeholder = {
|
||||
Text(
|
||||
@@ -677,15 +689,40 @@ fun ConfigScreen(tunnelConfig: TunnelConfig?, appViewModel: AppViewModel) {
|
||||
hint = stringResource(R.string.base64_key),
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
)
|
||||
ConfigurationTextBox(
|
||||
val presharedKeyEnabled = (tunnelConfig == null) || isAuthenticated ||
|
||||
with(configPair.second?.peers[index]?.preSharedKey) { this?.isEmpty == true || this?.isPresent == false }
|
||||
OutlinedTextField(
|
||||
textStyle = MaterialTheme.typography.labelLarge,
|
||||
modifier =
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.clickable { showAuthPrompt = true },
|
||||
value = peer.preSharedKey,
|
||||
onValueChange = { value ->
|
||||
peersState[index] = peersState[index].copy(preSharedKey = value)
|
||||
visualTransformation =
|
||||
if (presharedKeyEnabled) {
|
||||
VisualTransformation.None
|
||||
} else {
|
||||
PasswordVisualTransformation()
|
||||
},
|
||||
enabled = presharedKeyEnabled,
|
||||
onValueChange = { value -> peersState[index] = peersState[index].copy(preSharedKey = value) },
|
||||
label = {
|
||||
Text(
|
||||
stringResource(R.string.preshared_key),
|
||||
color = MaterialTheme.colorScheme.onSurface,
|
||||
style = MaterialTheme.typography.labelMedium,
|
||||
)
|
||||
},
|
||||
singleLine = true,
|
||||
placeholder = {
|
||||
Text(
|
||||
stringResource(R.string.optional),
|
||||
style = MaterialTheme.typography.labelLarge,
|
||||
color = MaterialTheme.colorScheme.outline,
|
||||
)
|
||||
},
|
||||
keyboardOptions = keyboardOptions,
|
||||
keyboardActions = keyboardActions,
|
||||
label = stringResource(R.string.preshared_key),
|
||||
hint = stringResource(R.string.optional),
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
)
|
||||
OutlinedTextField(
|
||||
textStyle = MaterialTheme.typography.labelLarge,
|
||||
@@ -702,7 +739,9 @@ fun ConfigScreen(tunnelConfig: TunnelConfig?, appViewModel: AppViewModel) {
|
||||
style = MaterialTheme.typography.labelMedium,
|
||||
)
|
||||
},
|
||||
label = { Text(stringResource(R.string.persistent_keepalive), style = MaterialTheme.typography.labelMedium) },
|
||||
label = {
|
||||
Text(stringResource(R.string.persistent_keepalive), color = MaterialTheme.colorScheme.onSurface, style = MaterialTheme.typography.labelMedium)
|
||||
},
|
||||
singleLine = true,
|
||||
placeholder = {
|
||||
Text(stringResource(R.string.optional_no_recommend), style = MaterialTheme.typography.labelLarge, color = MaterialTheme.colorScheme.outline)
|
||||
@@ -728,7 +767,13 @@ fun ConfigScreen(tunnelConfig: TunnelConfig?, appViewModel: AppViewModel) {
|
||||
onValueChange = { value ->
|
||||
peersState[index] = peersState[index].copy(allowedIps = value)
|
||||
},
|
||||
label = { Text(stringResource(R.string.allowed_ips)) },
|
||||
label = {
|
||||
Text(
|
||||
stringResource(R.string.allowed_ips),
|
||||
color = MaterialTheme.colorScheme.onSurface,
|
||||
style = MaterialTheme.typography.labelMedium,
|
||||
)
|
||||
},
|
||||
singleLine = true,
|
||||
placeholder = {
|
||||
Text(stringResource(R.string.comma_separated_list), style = MaterialTheme.typography.labelLarge, color = MaterialTheme.colorScheme.outline)
|
||||
|
||||
+5
-39
@@ -1,6 +1,7 @@
|
||||
package com.zaneschepke.wireguardautotunnel.ui.screens.tunneloptions.config.model
|
||||
|
||||
import com.wireguard.config.Peer
|
||||
import com.zaneschepke.wireguardautotunnel.data.domain.TunnelConfig
|
||||
import com.zaneschepke.wireguardautotunnel.util.extensions.joinAndTrim
|
||||
|
||||
data class PeerProxy(
|
||||
@@ -8,7 +9,7 @@ data class PeerProxy(
|
||||
val preSharedKey: String = "",
|
||||
val persistentKeepalive: String = "",
|
||||
val endpoint: String = "",
|
||||
val allowedIps: String = ALL_IPS.joinAndTrim(),
|
||||
val allowedIps: String = TunnelConfig.ALL_IPS.joinAndTrim(),
|
||||
) {
|
||||
fun toWgPeer(): Peer {
|
||||
return Peer.Builder().apply {
|
||||
@@ -30,18 +31,18 @@ data class PeerProxy(
|
||||
}
|
||||
|
||||
fun isLanExcluded(): Boolean {
|
||||
return this.allowedIps.contains(IPV4_PUBLIC_NETWORKS.joinAndTrim())
|
||||
return this.allowedIps.contains(TunnelConfig.LAN_BYPASS_ALLOWED_IPS.joinAndTrim())
|
||||
}
|
||||
|
||||
fun includeLan(): PeerProxy {
|
||||
return this.copy(
|
||||
allowedIps = ALL_IPS.joinAndTrim(),
|
||||
allowedIps = TunnelConfig.ALL_IPS.joinAndTrim(),
|
||||
)
|
||||
}
|
||||
|
||||
fun excludeLan(): PeerProxy {
|
||||
return this.copy(
|
||||
allowedIps = IPV4_PUBLIC_NETWORKS.joinAndTrim(),
|
||||
allowedIps = TunnelConfig.LAN_BYPASS_ALLOWED_IPS.joinAndTrim(),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -95,40 +96,5 @@ data class PeerProxy(
|
||||
allowedIps = peer.allowedIps.joinToString(", ").trim(),
|
||||
)
|
||||
}
|
||||
|
||||
val IPV4_PUBLIC_NETWORKS =
|
||||
setOf(
|
||||
"0.0.0.0/5",
|
||||
"8.0.0.0/7",
|
||||
"11.0.0.0/8",
|
||||
"12.0.0.0/6",
|
||||
"16.0.0.0/4",
|
||||
"32.0.0.0/3",
|
||||
"64.0.0.0/2",
|
||||
"128.0.0.0/3",
|
||||
"160.0.0.0/5",
|
||||
"168.0.0.0/6",
|
||||
"172.0.0.0/12",
|
||||
"172.32.0.0/11",
|
||||
"172.64.0.0/10",
|
||||
"172.128.0.0/9",
|
||||
"173.0.0.0/8",
|
||||
"174.0.0.0/7",
|
||||
"176.0.0.0/4",
|
||||
"192.0.0.0/9",
|
||||
"192.128.0.0/11",
|
||||
"192.160.0.0/13",
|
||||
"192.169.0.0/16",
|
||||
"192.170.0.0/15",
|
||||
"192.172.0.0/14",
|
||||
"192.176.0.0/12",
|
||||
"192.192.0.0/10",
|
||||
"193.0.0.0/8",
|
||||
"194.0.0.0/7",
|
||||
"196.0.0.0/6",
|
||||
"200.0.0.0/5",
|
||||
"208.0.0.0/4",
|
||||
)
|
||||
val ALL_IPS = setOf("0.0.0.0/0", "::/0")
|
||||
}
|
||||
}
|
||||
|
||||
+5
-5
@@ -87,8 +87,8 @@ fun Context.launchNotificationSettings() {
|
||||
|
||||
fun Context.launchShareFile(file: Uri) {
|
||||
val shareIntent = Intent().apply {
|
||||
setAction(Intent.ACTION_SEND)
|
||||
setType("*/*")
|
||||
action = Intent.ACTION_SEND
|
||||
type = "*/*"
|
||||
putExtra(Intent.EXTRA_STREAM, file)
|
||||
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
}
|
||||
@@ -146,7 +146,7 @@ fun Context.launchVpnSettings(): Result<Unit> {
|
||||
fun Context.launchLocationServicesSettings(): Result<Unit> {
|
||||
return kotlin.runCatching {
|
||||
val intent = Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS).apply {
|
||||
setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
flags = Intent.FLAG_ACTIVITY_NEW_TASK
|
||||
}
|
||||
startActivity(intent)
|
||||
}
|
||||
@@ -155,7 +155,7 @@ fun Context.launchLocationServicesSettings(): Result<Unit> {
|
||||
fun Context.launchSettings(): Result<Unit> {
|
||||
return kotlin.runCatching {
|
||||
val intent = Intent(Settings.ACTION_SETTINGS).apply {
|
||||
setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
flags = Intent.FLAG_ACTIVITY_NEW_TASK
|
||||
}
|
||||
startActivity(intent)
|
||||
}
|
||||
@@ -165,7 +165,7 @@ fun Context.launchAppSettings() {
|
||||
kotlin.runCatching {
|
||||
val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS).apply {
|
||||
data = Uri.fromParts("package", packageName, null)
|
||||
setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
flags = Intent.FLAG_ACTIVITY_NEW_TASK
|
||||
}
|
||||
startActivity(intent)
|
||||
}
|
||||
|
||||
@@ -1,2 +1,126 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources></resources>
|
||||
<resources>
|
||||
<string name="optional">(valinnainen)</string>
|
||||
<string name="exclude">Poissulje</string>
|
||||
<string name="open_issue">Raportoi ongelmasta</string>
|
||||
<string name="stop">pysäytä</string>
|
||||
<string name="no_email_detected">Sähköpostisovellusta ei tunnistettu</string>
|
||||
<string name="allowed_ips">Sallitut IP-osoitteet</string>
|
||||
<string name="create_import">Luo tyhjästä</string>
|
||||
<string name="appearance">Ulkoasu</string>
|
||||
<string name="tunnel_specific_settings">Tunnelikohtaiset asetukset</string>
|
||||
<string name="dns_servers">DNS-palvelimet</string>
|
||||
<string name="background_location_message2">varmistaaksesi, että käyttöoikeudet ovat myönnetty</string>
|
||||
<string name="app_name">WG Tunnel</string>
|
||||
<string name="vpn_channel_name">VPN-ilmoituskanava</string>
|
||||
<string name="delete_tunnel_message">Haluatko varmasti poistaa tämän tunnelin?</string>
|
||||
<string name="no_tunnels">Ei tunneleita määriteltynä!</string>
|
||||
<string name="tunnels">Tunnelit</string>
|
||||
<string name="privacy_policy">Näytä tietosuojakäytäntö</string>
|
||||
<string name="okay">Okei</string>
|
||||
<string name="tunnel_on_ethernet">Tunneloi ethernetissä</string>
|
||||
<string name="thank_you">Kiitos WG Tunnelin käyttämisestä!</string>
|
||||
<string name="trusted_ssid_value_description">Tallenna SSID</string>
|
||||
<string name="add_tunnels_text">Lisää tiedostosta tai zip-arkistosta</string>
|
||||
<string name="open_file">Avaa tiedosto</string>
|
||||
<string name="add_from_qr">Lisää QR-koodilla</string>
|
||||
<string name="config_changes_saved">Asetusten muutokset tallennettu.</string>
|
||||
<string name="public_key">Julkinen avain</string>
|
||||
<string name="addresses">Osoitteet</string>
|
||||
<string name="name">Nimi</string>
|
||||
<string name="always_on_vpn_support">Sall aina käytössä oleva VPN</string>
|
||||
<string name="location_services_not_detected">Sijaintipalveluita ei havaittu</string>
|
||||
<string name="auto_tunneling">Automaattinen tunnelointi</string>
|
||||
<string name="vpn_on">VPN päällä</string>
|
||||
<string name="vpn_off">VPN pois päältä</string>
|
||||
<string name="turn_on_tunnel">Toiminto vaatii aktiivisen tunnelin</string>
|
||||
<string name="private_key">Yksityinen avain</string>
|
||||
<string name="copy_public_key">Kopioi julkinen avain</string>
|
||||
<string name="base64_key">base64-avain</string>
|
||||
<string name="comma_separated_list">pilkulla eroteltu lista</string>
|
||||
<string name="random">(satunnainen)</string>
|
||||
<string name="unknown_error">Tapahtui tuntematon virhe</string>
|
||||
<string name="tunnel_on_wifi">Tunneloi epäluotettavilla wifi-yhteyksillä</string>
|
||||
<string name="email_subject">WG Tunnel-tuki</string>
|
||||
<string name="email_chooser">Lähetä sähköposti…</string>
|
||||
<string name="docs_description">Lue dokumentaatio</string>
|
||||
<string name="email_description">Lähetä minulle sähköpostia</string>
|
||||
<string name="error_ssid_exists">SSID on jo luettelossa</string>
|
||||
<string name="error_no_file_explorer">Tiedostonhallintasovellusta ei ole asennettuna</string>
|
||||
<string name="error_invalid_code">Virheellinen QR-koodi</string>
|
||||
<string name="location_services_missing_message">Sovellus ei tunnista laitteessasi käytössä olevia sijaintipalveluja. Laitteesta riippuen tämä voi aiheuttaa sen, että epäluotettava wlan-ominaisuus ei pysty lukemaan wlan-nimeä. Haluatko kuitenkin jatkaa?</string>
|
||||
<string name="auto_tunnel_title">Automaattinen tunnelointipalvelu</string>
|
||||
<string name="delete_tunnel">Poista tunneli</string>
|
||||
<string name="yes">Kyllä</string>
|
||||
<string name="auto">(auto)</string>
|
||||
<string name="incorrect_pin">Pin on virheellinen</string>
|
||||
<string name="pin_created">Pin luotu</string>
|
||||
<string name="enter_pin">Syötä pin-koodi</string>
|
||||
<string name="create_pin">Luo pin-koodi</string>
|
||||
<string name="enable_app_lock">Ota käyttöön sovelluksen lukitus</string>
|
||||
<string name="set_primary_tunnel">Aseta ensisijaiseksi tunneliksi</string>
|
||||
<string name="edit_tunnel">Muokkaa tunnelia</string>
|
||||
<string name="version">Versio</string>
|
||||
<string name="settings">Asetukset</string>
|
||||
<string name="support">Tuki</string>
|
||||
<string name="unsure_how">jos et tiedä, miten jatkaa</string>
|
||||
<string name="see_the">katso</string>
|
||||
<string name="getting_started_guide">aloitusopas</string>
|
||||
<string name="error_file_format">Virheellinen tunneliasetusten tiedostomuoto</string>
|
||||
<string name="vpn_denied_dialog_title">Ei käyttöoikeutta</string>
|
||||
<string name="vpn_settings">VPN järjestelmäasetukset</string>
|
||||
<string name="always_on_message">VPN-yhteyden käyttöoikeus on evätty. Tarkista</string>
|
||||
<string name="chat_description">Liity yhteisöön</string>
|
||||
<string name="tunnel_required">Ominaisuus vaatii vähintään yhden tunnelin</string>
|
||||
<string name="prominent_background_location_message">Tämä toiminto vaatii taustapaikannusoikeuden, jotta Wi-Fi SSID:n seuranta on mahdollista myös sovelluksen ollessa suljettuna. Lisätietoja löydät Tuki-näkymään linkatusta tietosuojakäytännöstä.</string>
|
||||
<string name="optional_default">"valinnainen, oletus: "</string>
|
||||
<string name="donate">Lahjoita projektille</string>
|
||||
<string name="requires_app_relaunch">Muutos edellyttää sovelluksen uudelleenkäynnistämistä. Haluatko jatkaa?</string>
|
||||
<string name="add_from_clipboard">Lisää leikepöydältä</string>
|
||||
<string name="ethernet_tunnel">Ethernet-tunneli</string>
|
||||
<string name="enable_amnezia">Ota Amnezia käyttöön</string>
|
||||
<string name="wg_compat_mode">WG-yhteensopivuustila</string>
|
||||
<string name="quick_actions">Pikatoiminnot</string>
|
||||
<string name="advanced_settings">Lisäasetukset</string>
|
||||
<string name="hide_scripts">Piilota skriptit</string>
|
||||
<string name="qr_scan">QR-skannaus</string>
|
||||
<string name="notifications">Ilmoitukset</string>
|
||||
<string name="error_file_extension">Tiedosto ei ole .conf tai .zip-tiedosto</string>
|
||||
<string name="tunnel_mobile_data">Tunneloi mobiilidatalla</string>
|
||||
<string name="mtu">MTU</string>
|
||||
<string name="optional_no_recommend">(valinnainen, ei suositeltu)</string>
|
||||
<string name="preshared_key">Esijaettu avain</string>
|
||||
<string name="all">kaikki</string>
|
||||
<string name="never">ei koskaan</string>
|
||||
<string name="logs">Lokitiedot</string>
|
||||
<string name="set_ethernet_tunnel">Aseta ethernet-tunneliksi</string>
|
||||
<string name="seconds">sekuntia</string>
|
||||
<string name="cancel">Peruuta</string>
|
||||
<string name="export_configs">Vie asetukset</string>
|
||||
<string name="no_browser_detected">Selainta ei tunnistettu</string>
|
||||
<string name="handshake">kädenpuristus</string>
|
||||
<string name="light">Vaalea</string>
|
||||
<string name="dark">Tumma</string>
|
||||
<string name="trusted_wifi_names">Luotetut WIFI:t</string>
|
||||
<string name="vpn_channel_description">Kanava VPN-tilan ilmoituksille</string>
|
||||
<string name="dynamic">Dynaaminen</string>
|
||||
<string name="primary_tunnel">Ensisijainen tunneli</string>
|
||||
<string name="learn_more">Lue lisää</string>
|
||||
<string name="show_scripts">Näytä skriptit</string>
|
||||
<string name="start_auto">Käynnistä automaattinen tunnelointi</string>
|
||||
<string name="stop_auto">Kytke automaattinen tunnelointi pois päältä</string>
|
||||
<string name="allow_lan_traffic">Salli LAN-tietoliikenne</string>
|
||||
<string name="turn_off_tunnel">Toiminto vaatii, että tunneli on pois päältä</string>
|
||||
<string name="error_authentication_failed">Tunnistautuminen epäonnistui</string>
|
||||
<string name="display_theme">Teema</string>
|
||||
<string name="skip">Ohita</string>
|
||||
<string name="add_wifi_name">Lisää WIFI:n nimi</string>
|
||||
<string name="language">Kieli</string>
|
||||
<string name="include">Sisällytä</string>
|
||||
<string name="enabled_app_shortcuts">Salli sovelluksen pikakuvakkeet</string>
|
||||
<string name="automatic">Automaattinen</string>
|
||||
<string name="tunnel_name">Tunnelin nimi</string>
|
||||
<string name="sec">sek</string>
|
||||
<string name="read_logs">Lue lokitiedot</string>
|
||||
<string name="mobile_tunnel">Mobiilidatatunneli</string>
|
||||
</resources>
|
||||
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="turn_off_tunnel">Cette action nécessite la désactivation du tunnel</string>
|
||||
<string name="turn_off_tunnel">Cette action nécessite la désactivation du tunnel</string>
|
||||
<string name="no_tunnels">Aucun tunnel n\'a été ajouté pour le moment !</string>
|
||||
<string name="tunnels">Tunnels</string>
|
||||
<string name="tunnel_mobile_data">Tunnel sur données mobiles</string>
|
||||
@@ -38,7 +38,7 @@
|
||||
<string name="config_changes_saved">Changements de la configuration sauvegardés.</string>
|
||||
<string name="public_key">Clé publique</string>
|
||||
<string name="addresses">Adresses</string>
|
||||
<string name="vpn_on">VPN allumé</string>
|
||||
<string name="vpn_on">VPN allumé</string>
|
||||
<string name="create_import">Créer à partir de zéro</string>
|
||||
<string name="private_key">Clé privée</string>
|
||||
<string name="interface_">Interface</string>
|
||||
@@ -90,7 +90,7 @@
|
||||
<string name="vpn_settings">paramètres système des VPN</string>
|
||||
<string name="tunnel_required">Cette fonctionnalité nécessite au moins un tunnel</string>
|
||||
<string name="app_settings">les réglages de l\'application</string>
|
||||
<string name="background_location_message2">afin de s\'assurer que ces permissions soient actives.</string>
|
||||
<string name="background_location_message2">afin de s\'assurer que ces permissions soient actives</string>
|
||||
<string name="root_accepted">Accès au shell root autorisé</string>
|
||||
<string name="set_custom_ping_ip">Personnaliser l\'ip à pinguer</string>
|
||||
<string name="optional_default">"optionnel, par défaut : "</string>
|
||||
@@ -160,4 +160,28 @@
|
||||
<string name="kernel_not_supported">Noyau non supporté</string>
|
||||
<string name="start_auto">Démarrer l\'auto-tunnel</string>
|
||||
<string name="requires_app_relaunch">Cette modification nécessite un redémarrage de l\'application. Voulez-vous continuer ?</string>
|
||||
</resources>
|
||||
<string name="exclude_lan">Exclure le LAN</string>
|
||||
<string name="tunnel_specific_settings">Réglages spécifiques du tunnel</string>
|
||||
<string name="show_scripts">Voir les scripts</string>
|
||||
<string name="hide_scripts">Cacher les scripts</string>
|
||||
<string name="remove_amnezia_compatibility">Retirer la prise en charge d\'Amnezia</string>
|
||||
<string name="hide_amnezia_properties">Cacher les propriétés d\'Amnezia</string>
|
||||
<string name="advanced_settings">Réglages avancés</string>
|
||||
<string name="stop_on_no_internet">Arrêt en l\'absence d\'internet</string>
|
||||
<string name="stop_on_internet_loss">Couper les tunnels en l\'absence d\'internet</string>
|
||||
<string name="ethernet_tunnel">Tunnel ethernet</string>
|
||||
<string name="set_ethernet_tunnel">Définir comme tunnel ethernet</string>
|
||||
<string name="native_kill_switch">Arrêt d\'urgence natif</string>
|
||||
<string name="vpn_kill_switch">Arrêt d\'urgence VPN</string>
|
||||
<string name="kill_switch_options">Options d\'arrêt d\'urgence</string>
|
||||
<string name="allow_lan_traffic">Autoriser le trafic LAN</string>
|
||||
<string name="bypass_lan_for_kill_switch">Contourner le LAN en cas d\'arêt d\'urgence</string>
|
||||
<string name="stop">arrêter</string>
|
||||
<string name="splt_tunneling">Tunnel partagé</string>
|
||||
<string name="amnezia_kernel_message">Amnezia n\'est pas disponible en mode noyau</string>
|
||||
<string name="enable_amnezia">Activer Amnezia</string>
|
||||
<string name="wg_compat_mode">Mode de compatibilité WG</string>
|
||||
<string name="quick_actions">Actions rapides</string>
|
||||
<string name="enable_amnezia_compatibility">Activer la prise en charge d\'Amnezia</string>
|
||||
<string name="include_lan">Inclure le LAN</string>
|
||||
</resources>
|
||||
@@ -193,4 +193,7 @@
|
||||
<string name="include_lan">Uwzględnij LAN</string>
|
||||
<string name="advanced_settings">Ustawienia zaawansowane</string>
|
||||
<string name="hide_amnezia_properties">Ukryj właściwości protokołu Amnezia</string>
|
||||
</resources>
|
||||
<string name="error_tunnel_start">Nie udało się uruchomić tunelu</string>
|
||||
<string name="tunnel_control">Kontrola tunelu</string>
|
||||
<string name="auto_tunnel">Autotunel</string>
|
||||
</resources>
|
||||
|
||||
@@ -102,7 +102,7 @@
|
||||
<string name="see_the">Veja o</string>
|
||||
<string name="getting_started_guide">guia de início rápido</string>
|
||||
<string name="error_file_format">Formato de configuração inválido</string>
|
||||
<string name="vpn_channel_name">Canal de notificações VPN</string>
|
||||
<string name="vpn_channel_name">Canal de notificações VPN</string>
|
||||
<string name="set_custom_ping_ip">Definir ip ping personalizado</string>
|
||||
<string name="vpn_denied_dialog_title">Permissão negada</string>
|
||||
<string name="vpn_settings">Configurações do sistema VPN</string>
|
||||
@@ -120,4 +120,58 @@
|
||||
<string name="handshake">handshake</string>
|
||||
<string name="background_location_message">Permitir que toda a permissão de localização do tempo e/ou localização precisa é necessária para este recurso. Por favor, veja</string>
|
||||
<string name="sec">seg</string>
|
||||
</resources>
|
||||
<string name="notifications">Notificações</string>
|
||||
<string name="exclude_lan">Excluir LAN</string>
|
||||
<string name="hide_scripts">Ocultar scripts</string>
|
||||
<string name="trusted_wifi_names">Nomes de Wi-Fi confiáveis</string>
|
||||
<string name="hide_amnezia_properties">Ocultar propriedades Amnezia</string>
|
||||
<string name="remove_amnezia_compatibility">Remover compatibilidade Amnezia</string>
|
||||
<string name="include_lan">Incluir LAN</string>
|
||||
<string name="language">Idioma</string>
|
||||
<string name="add_wifi_name">Adicionar nome Wi-Fi</string>
|
||||
<string name="display_theme">Tema</string>
|
||||
<string name="on_demand_rules">Regras de tunelamento sob demanda</string>
|
||||
<string name="dark">Escuro</string>
|
||||
<string name="dynamic">Dinâmico</string>
|
||||
<string name="skip">Pular</string>
|
||||
<string name="mobile_tunnel">Túnel com dados móveis</string>
|
||||
<string name="requires_app_relaunch">Para aplicar as mudanças é necessário reiniciar o aplicativo. Deseja prosseguir ?</string>
|
||||
<string name="add_from_clipboard">Adicionar da área de transferência</string>
|
||||
<string name="restart_at_boot">Ativar na inicialização</string>
|
||||
<string name="appearance">Aparência</string>
|
||||
<string name="automatic">Automático</string>
|
||||
<string name="light">Claro</string>
|
||||
<string name="wildcards_active">Wildcards ativos</string>
|
||||
<string name="learn_more">Saber mais</string>
|
||||
<string name="use_wildcards">Usar nomes coringas</string>
|
||||
<string name="wifi_name_via_shell">Nome do Wi-Fi por shell</string>
|
||||
<string name="use_root_shell_for_wifi">Obter o nome do Wi-Fi através do shell root</string>
|
||||
<string name="start_auto">Iniciar túnel automático</string>
|
||||
<string name="stop_auto">Pausar túnel automático</string>
|
||||
<string name="monitoring_state_changes">Monitorar status de alterações</string>
|
||||
<string name="tunnel_running">Túnel em execução</string>
|
||||
<string name="donate">Contribua com esse projeto</string>
|
||||
<string name="local_logging">Registro local</string>
|
||||
<string name="enable_local_logging">Ativar registro local</string>
|
||||
<string name="configuration_change">Configuração alterada</string>
|
||||
<string name="stop_on_no_internet">Interromper quando não há internet</string>
|
||||
<string name="stop_on_internet_loss">Interrompa o túnel quando a internet não estiver disponível</string>
|
||||
<string name="ethernet_tunnel">Túnel ethernet</string>
|
||||
<string name="set_ethernet_tunnel">Definir como túnel ethernet</string>
|
||||
<string name="native_kill_switch">Interruptor de desligamento padrão</string>
|
||||
<string name="vpn_kill_switch">Interruptor de desligamento VPN</string>
|
||||
<string name="kill_switch_options">Opções do interruptor de desligamento</string>
|
||||
<string name="allow_lan_traffic">Permitir tráfego LAN</string>
|
||||
<string name="bypass_lan_for_kill_switch">Ignorar LAN no interruptor de desligamento</string>
|
||||
<string name="splt_tunneling">Tunelamento dividido</string>
|
||||
<string name="stop">pausar</string>
|
||||
<string name="tunnel_specific_settings">Configurações específicas no túnel</string>
|
||||
<string name="show_scripts">Mostrar scripts</string>
|
||||
<string name="amnezia_kernel_message">Amnezia indisponível no kernel</string>
|
||||
<string name="enable_amnezia">Ativar Amnezia</string>
|
||||
<string name="wg_compat_mode">Modo de compatibilidade WG</string>
|
||||
<string name="quick_actions">Ações rápidas</string>
|
||||
<string name="kernel_not_supported">Kernel não suportado</string>
|
||||
<string name="advanced_settings">Configurações avançadas</string>
|
||||
<string name="enable_amnezia_compatibility">Ativar compatibilidade Amnezia</string>
|
||||
</resources>
|
||||
@@ -173,8 +173,8 @@
|
||||
<string name="native_kill_switch">Штатное экстренное отключение</string>
|
||||
<string name="vpn_kill_switch">Экстренное отключение VPN</string>
|
||||
<string name="kill_switch_options">Настройка экстренного отключения</string>
|
||||
<string name="allow_lan_traffic">Разрешать трафик LAN</string>
|
||||
<string name="bypass_lan_for_kill_switch">Обход LAN при экстренном отключении</string>
|
||||
<string name="allow_lan_traffic">Обход LAN</string>
|
||||
<string name="bypass_lan_for_kill_switch">Разрешать трафик LAN при экстренном отключении</string>
|
||||
<string name="vpn_channel_description">Канал уведомлений о состоянии VPN</string>
|
||||
<string name="auto_tunnel_channel_description">Канал уведомлений о состоянии автотуннеля</string>
|
||||
<string name="stop">стоп</string>
|
||||
@@ -186,10 +186,10 @@
|
||||
<string name="post_down">После деактивации</string>
|
||||
<string name="debounce_delay">Задержка отбоя</string>
|
||||
<string name="remove_amnezia_compatibility">Отключить совместимость с Amnezia</string>
|
||||
<string name="exclude_lan">Исключить локальную сеть</string>
|
||||
<string name="exclude_lan">Исключить LAN</string>
|
||||
<string name="hide_scripts">Скрыть сценарии</string>
|
||||
<string name="hide_amnezia_properties">Скрыть настройки Amnezia</string>
|
||||
<string name="advanced_settings">Дополнительные настройки</string>
|
||||
<string name="enable_amnezia_compatibility">Включить совместимость с Amnezia</string>
|
||||
<string name="include_lan">Включить локальную сеть</string>
|
||||
<string name="include_lan">Включить LAN</string>
|
||||
</resources>
|
||||
@@ -0,0 +1,2 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources></resources>
|
||||
@@ -203,4 +203,7 @@
|
||||
<string name="exclude_lan">Exclude LAN</string>
|
||||
<string name="include_lan">Include LAN</string>
|
||||
<string name="error_tunnel_start">Failed to starting tunnel</string>
|
||||
<string name="tunnel_control">Tunnel control</string>
|
||||
<string name="auto_tunnel">Auto-tunnel</string>
|
||||
<string name="kill_switch_off">Stop kill switch on trusted</string>
|
||||
</resources>
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
Novedades:
|
||||
- La función Ping ahora funciona independientemente del túnel automático
|
||||
- Añadida acción para compatibilidad con Amnezia
|
||||
- Añadida acción para excluir LAN del túnel
|
||||
- Añadida la opción de ajuste del retardo de rebote para el túnel automático.
|
||||
- Numerosas correcciones de errores y mejoras
|
||||
@@ -0,0 +1 @@
|
||||
Vaihtoehtoinen WireGuard-asiakassovellus lisäominaisuuksilla
|
||||
@@ -0,0 +1 @@
|
||||
WG Tunnel
|
||||
@@ -0,0 +1,6 @@
|
||||
Co nowego:
|
||||
- Funkcja ping działa teraz niezależnie od autotunelowania
|
||||
- Dodano wygodną czynność dla zgodności z protokołem Amnezia
|
||||
- Dodano wygodną czynność dla wykluczenia sieci LAN z tunelowania
|
||||
- Dodano opcję dostrajania opóźnienia odbicia dla autotunelowania
|
||||
- Wiele poprawek błędów i ulepszeń
|
||||
@@ -0,0 +1 @@
|
||||
Uma alternativa de cliente WireGuard VPN com recursos adicionais
|
||||
@@ -0,0 +1,6 @@
|
||||
Что нового:
|
||||
- Функция пинга теперь работает независимо от автоматического туннелирования
|
||||
- Функционал совместимости с Amnezia стал удобнее
|
||||
- Добавлен функционал исключения локальной сети из туннеля
|
||||
- Добавлен параметр задержки отбоя для автоматического туннелирования
|
||||
- Множество исправлений ошибок и улучшений
|
||||
@@ -0,0 +1,3 @@
|
||||
強化項目:
|
||||
- 修正 Android 9 以下版本的權限問題
|
||||
- 其他優化
|
||||
@@ -0,0 +1,14 @@
|
||||
特色功能
|
||||
|
||||
- 可透過 .conf 檔、zip 壓縮檔、手動輸入及掃瞄 QRCode 新增通道
|
||||
- 根據 Wi-Fi SSID、乙太網路或行動網路自動連線至 VPN
|
||||
- 為各別應用程式設定是否使用分割通道(支援搜尋功能)
|
||||
- 支援 WireGuard 內核模式與使用者空間模式
|
||||
- 支援 Amnezia 使用者空間模式,以應對 DPI 及審查保護
|
||||
- 支援隨時保持 VPN 連線
|
||||
- 支援將 Amnezia 及 WireGuard 通道匯出為 zip 檔
|
||||
- 支援以快速設定方塊切換 VPN
|
||||
- 支援主要通道靜態捷徑,便於自動化整合
|
||||
- 所有通道皆支援 Intent 自動化
|
||||
- 裝置重新啟動後自動重新啟動服務
|
||||
- 耗電節約措施
|
||||
@@ -0,0 +1 @@
|
||||
一款具備額外功能的 WireGuard VPN 客戶端應用程式
|
||||
@@ -0,0 +1 @@
|
||||
WG Tunnel
|
||||
@@ -1,18 +1,18 @@
|
||||
[versions]
|
||||
accompanist = "0.36.0"
|
||||
activityCompose = "1.9.3"
|
||||
accompanist = "0.37.0"
|
||||
activityCompose = "1.10.0"
|
||||
amneziawgAndroid = "1.2.4"
|
||||
androidx-junit = "1.2.1"
|
||||
appcompat = "1.7.0"
|
||||
biometricKtx = "1.2.0-alpha05"
|
||||
coreKtx = "1.15.0"
|
||||
datastorePreferences = "1.1.1"
|
||||
datastorePreferences = "1.1.2"
|
||||
desugar_jdk_libs = "2.1.4"
|
||||
espressoCore = "3.6.1"
|
||||
hiltAndroid = "2.54"
|
||||
hiltAndroid = "2.55"
|
||||
hiltNavigationCompose = "1.2.0"
|
||||
junit = "4.13.2"
|
||||
kotlinx-serialization-json = "1.7.3"
|
||||
kotlinx-serialization-json = "1.8.0"
|
||||
lifecycle-runtime-compose = "2.8.7"
|
||||
material3 = "1.3.1"
|
||||
navigationCompose = "2.8.5"
|
||||
@@ -20,11 +20,11 @@ pinLockCompose = "1.0.4"
|
||||
roomVersion = "2.6.1"
|
||||
timber = "5.0.1"
|
||||
tunnel = "1.2.1"
|
||||
androidGradlePlugin = "8.8.0-rc02"
|
||||
androidGradlePlugin = "8.8.0"
|
||||
kotlin = "2.1.0"
|
||||
ksp = "2.1.0-1.0.29"
|
||||
composeBom = "2024.12.01"
|
||||
compose = "1.7.6"
|
||||
composeBom = "2025.01.00"
|
||||
compose = "1.7.7"
|
||||
zxingAndroidEmbedded = "4.3.0"
|
||||
coreSplashscreen = "1.0.1"
|
||||
gradlePlugins-grgit = "5.3.0"
|
||||
@@ -38,7 +38,6 @@ gradlePlugins-ktlint="12.1.2"
|
||||
|
||||
# accompanist
|
||||
accompanist-drawablepainter = { module = "com.google.accompanist:accompanist-drawablepainter", version.ref = "accompanist" }
|
||||
accompanist-flowlayout = { module = "com.google.accompanist:accompanist-flowlayout", version.ref = "accompanist" }
|
||||
accompanist-permissions = { module = "com.google.accompanist:accompanist-permissions", version.ref = "accompanist" }
|
||||
|
||||
#room
|
||||
|
||||
Reference in New Issue
Block a user