diff --git a/app/schemas/com.zaneschepke.wireguardautotunnel.data.AppDatabase/15.json b/app/schemas/com.zaneschepke.wireguardautotunnel.data.AppDatabase/15.json new file mode 100644 index 0000000..ca33f90 --- /dev/null +++ b/app/schemas/com.zaneschepke.wireguardautotunnel.data.AppDatabase/15.json @@ -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')" + ] + } +} \ No newline at end of file diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/data/AppDatabase.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/data/AppDatabase.kt index f4a1041..17e6540 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/data/AppDatabase.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/data/AppDatabase.kt @@ -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, ) diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/data/domain/Settings.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/data/domain/Settings.kt index 799445b..b76fe02 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/data/domain/Settings.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/data/domain/Settings.kt @@ -85,6 +85,11 @@ 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 diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/foreground/autotunnel/AutoTunnelService.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/foreground/autotunnel/AutoTunnelService.kt index e2b9554..7107310 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/foreground/autotunnel/AutoTunnelService.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/foreground/autotunnel/AutoTunnelService.kt @@ -17,6 +17,7 @@ import com.zaneschepke.wireguardautotunnel.module.MainImmediateDispatcher import com.zaneschepke.wireguardautotunnel.service.foreground.ServiceManager import com.zaneschepke.wireguardautotunnel.service.foreground.autotunnel.model.AutoTunnelEvent import com.zaneschepke.wireguardautotunnel.service.foreground.autotunnel.model.AutoTunnelState +import com.zaneschepke.wireguardautotunnel.service.foreground.autotunnel.model.KillSwitchEvent import com.zaneschepke.wireguardautotunnel.service.foreground.autotunnel.model.NetworkState import com.zaneschepke.wireguardautotunnel.service.network.InternetConnectivityService import com.zaneschepke.wireguardautotunnel.service.network.NetworkService @@ -24,6 +25,7 @@ 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 @@ -111,6 +113,7 @@ class AutoTunnelService : LifecycleService() { } startAutoTunnelJob() startAutoTunnelStateJob() + startKillSwitchJob() }.onFailure { Timber.e(it) } @@ -181,7 +184,6 @@ class AutoTunnelService : LifecycleService() { ) { double, networkState -> 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) } @@ -209,6 +211,23 @@ 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") diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/foreground/autotunnel/model/AutoTunnelState.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/foreground/autotunnel/model/AutoTunnelState.kt index e85ff56..28432c5 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/foreground/autotunnel/model/AutoTunnelState.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/foreground/autotunnel/model/AutoTunnelState.kt @@ -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) diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/foreground/autotunnel/model/KillSwitchEvent.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/foreground/autotunnel/model/KillSwitchEvent.kt new file mode 100644 index 0000000..85239ce --- /dev/null +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/foreground/autotunnel/model/KillSwitchEvent.kt @@ -0,0 +1,7 @@ +package com.zaneschepke.wireguardautotunnel.service.foreground.autotunnel.model + +sealed class KillSwitchEvent { + data class Start(val allowedIps: Set) : KillSwitchEvent() + data object Stop : KillSwitchEvent() + data object DoNothing : KillSwitchEvent() +} diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/tunnel/VpnState.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/tunnel/VpnState.kt index a28c46c..4c40504 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/tunnel/VpnState.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/tunnel/VpnState.kt @@ -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, ) diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/tunnel/WireGuardTunnel.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/tunnel/WireGuardTunnel.kt index d40d285..91d1d8b 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/tunnel/WireGuardTunnel.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/tunnel/WireGuardTunnel.kt @@ -222,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 diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/MainActivity.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/MainActivity.kt index 29f75ed..7e129c6 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/MainActivity.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/MainActivity.kt @@ -198,7 +198,7 @@ class MainActivity : AppCompatActivity() { } composable { AutoTunnelScreen( - appUiState, + appUiState.settings, ) } composable { @@ -214,7 +214,7 @@ class MainActivity : AppCompatActivity() { SupportScreen(appUiState, viewModel) } composable { - AdvancedScreen(appUiState, viewModel) + AdvancedScreen(appUiState.settings, viewModel) } composable { LogsScreen() diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/autotunnel/AutoTunnelScreen.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/autotunnel/AutoTunnelScreen.kt index 695b487..a92ee7a 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/autotunnel/AutoTunnelScreen.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/autotunnel/AutoTunnelScreen.kt @@ -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) }, ), ), diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/autotunnel/AutoTunnelViewModel.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/autotunnel/AutoTunnelViewModel.kt index 9670d2d..1d9e456 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/autotunnel/AutoTunnelViewModel.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/autotunnel/AutoTunnelViewModel.kt @@ -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), + ) + } + } } diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/autotunnel/advanced/AdvancedScreen.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/autotunnel/advanced/AdvancedScreen.kt index 6740502..b84670c 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/autotunnel/advanced/AdvancedScreen.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/autotunnel/advanced/AdvancedScreen.kt @@ -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), + ) }, ) } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 3776089..92a5625 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -205,4 +205,5 @@ Failed to starting tunnel Tunnel control Auto-tunnel + Stop kill switch on trusted