feat: disable kill switch on trusted

fix: debounce ui bug closes #532
This commit is contained in:
Zane Schepke 2025-01-19 12:37:43 -05:00
parent 3e6a2981cb
commit f3debcfe45
No known key found for this signature in database
GPG Key ID: 82217F0566F9B4A5
13 changed files with 433 additions and 72 deletions

View File

@ -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')"
]
}
}

View File

@ -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,
)

View File

@ -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

View File

@ -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")

View File

@ -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)

View File

@ -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()
}

View File

@ -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,
)

View File

@ -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

View File

@ -198,7 +198,7 @@ class MainActivity : AppCompatActivity() {
}
composable<Route.AutoTunnel> {
AutoTunnelScreen(
appUiState,
appUiState.settings,
)
}
composable<Route.Appearance> {
@ -214,7 +214,7 @@ class MainActivity : AppCompatActivity() {
SupportScreen(appUiState, viewModel)
}
composable<Route.AutoTunnelAdvanced> {
AdvancedScreen(appUiState, viewModel)
AdvancedScreen(appUiState.settings, viewModel)
}
composable<Route.Logs> {
LogsScreen()

View File

@ -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)
},
),
),

View File

@ -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),
)
}
}
}

View File

@ -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),
)
},
)
}

View File

@ -205,4 +205,5 @@
<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>