From 921e33cb7017015fa52c9072304456f4fdae8c85 Mon Sep 17 00:00:00 2001 From: Zane Schepke Date: Sat, 30 Nov 2024 12:33:07 -0500 Subject: [PATCH] feat: add ethernet tunnel, stop tunnel on offline closes #460 --- .../12.json | 246 ++++++++++++++++++ .../wireguardautotunnel/data/AppDatabase.kt | 6 +- .../data/TunnelConfigDao.kt | 3 + .../data/domain/Settings.kt | 5 + .../data/domain/TunnelConfig.kt | 5 + .../repository/RoomTunnelConfigRepository.kt | 13 + .../data/repository/TunnelConfigRepository.kt | 2 + .../foreground/autotunnel/AutoTunnelEvent.kt | 2 +- .../foreground/autotunnel/AutoTunnelState.kt | 51 ++-- .../screens/main/components/TunnelRowItem.kt | 15 +- .../ui/screens/options/OptionsScreen.kt | 23 ++ .../ui/screens/options/OptionsViewModel.kt | 8 + .../settings/autotunnel/AutoTunnelScreen.kt | 25 ++ .../autotunnel/AutoTunnelViewModel.kt | 8 + app/src/main/res/values/strings.xml | 4 + 15 files changed, 388 insertions(+), 28 deletions(-) create mode 100644 app/schemas/com.zaneschepke.wireguardautotunnel.data.AppDatabase/12.json diff --git a/app/schemas/com.zaneschepke.wireguardautotunnel.data.AppDatabase/12.json b/app/schemas/com.zaneschepke.wireguardautotunnel.data.AppDatabase/12.json new file mode 100644 index 00000000..dc6d0c9e --- /dev/null +++ b/app/schemas/com.zaneschepke.wireguardautotunnel.data.AppDatabase/12.json @@ -0,0 +1,246 @@ +{ + "formatVersion": 1, + "database": { + "version": 12, + "identityHash": "acf79ac5defacda5be6c3f976e777de3", + "entities": [ + { + "tableName": "Settings", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_tunnel_enabled` INTEGER NOT NULL, `is_tunnel_on_mobile_data_enabled` INTEGER NOT NULL, `trusted_network_ssids` TEXT NOT NULL, `is_always_on_vpn_enabled` INTEGER NOT NULL, `is_tunnel_on_ethernet_enabled` INTEGER NOT NULL, `is_shortcuts_enabled` INTEGER NOT NULL DEFAULT false, `is_tunnel_on_wifi_enabled` INTEGER NOT NULL DEFAULT false, `is_kernel_enabled` INTEGER NOT NULL DEFAULT false, `is_restore_on_boot_enabled` INTEGER NOT NULL DEFAULT false, `is_multi_tunnel_enabled` INTEGER NOT NULL DEFAULT false, `is_ping_enabled` INTEGER NOT NULL DEFAULT false, `is_amnezia_enabled` INTEGER NOT NULL DEFAULT false, `is_wildcards_enabled` INTEGER NOT NULL DEFAULT false, `is_wifi_by_shell_enabled` INTEGER NOT NULL DEFAULT false, `is_stop_on_no_internet_enabled` INTEGER NOT NULL DEFAULT false)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isAutoTunnelEnabled", + "columnName": "is_tunnel_enabled", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isTunnelOnMobileDataEnabled", + "columnName": "is_tunnel_on_mobile_data_enabled", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "trustedNetworkSSIDs", + "columnName": "trusted_network_ssids", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "isAlwaysOnVpnEnabled", + "columnName": "is_always_on_vpn_enabled", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isTunnelOnEthernetEnabled", + "columnName": "is_tunnel_on_ethernet_enabled", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isShortcutsEnabled", + "columnName": "is_shortcuts_enabled", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "false" + }, + { + "fieldPath": "isTunnelOnWifiEnabled", + "columnName": "is_tunnel_on_wifi_enabled", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "false" + }, + { + "fieldPath": "isKernelEnabled", + "columnName": "is_kernel_enabled", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "false" + }, + { + "fieldPath": "isRestoreOnBootEnabled", + "columnName": "is_restore_on_boot_enabled", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "false" + }, + { + "fieldPath": "isMultiTunnelEnabled", + "columnName": "is_multi_tunnel_enabled", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "false" + }, + { + "fieldPath": "isPingEnabled", + "columnName": "is_ping_enabled", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "false" + }, + { + "fieldPath": "isAmneziaEnabled", + "columnName": "is_amnezia_enabled", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "false" + }, + { + "fieldPath": "isWildcardsEnabled", + "columnName": "is_wildcards_enabled", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "false" + }, + { + "fieldPath": "isWifiNameByShellEnabled", + "columnName": "is_wifi_by_shell_enabled", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "false" + }, + { + "fieldPath": "isStopOnNoInternetEnabled", + "columnName": "is_stop_on_no_internet_enabled", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "false" + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "TunnelConfig", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NOT NULL, `wg_quick` TEXT NOT NULL, `tunnel_networks` TEXT NOT NULL DEFAULT '', `is_mobile_data_tunnel` INTEGER NOT NULL DEFAULT false, `is_primary_tunnel` INTEGER NOT NULL DEFAULT false, `am_quick` TEXT NOT NULL DEFAULT '', `is_Active` INTEGER NOT NULL DEFAULT false, `is_ping_enabled` INTEGER NOT NULL DEFAULT false, `ping_interval` INTEGER DEFAULT null, `ping_cooldown` INTEGER DEFAULT null, `ping_ip` TEXT DEFAULT null, `is_ethernet_tunnel` INTEGER NOT NULL DEFAULT false)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "wgQuick", + "columnName": "wg_quick", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "tunnelNetworks", + "columnName": "tunnel_networks", + "affinity": "TEXT", + "notNull": true, + "defaultValue": "''" + }, + { + "fieldPath": "isMobileDataTunnel", + "columnName": "is_mobile_data_tunnel", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "false" + }, + { + "fieldPath": "isPrimaryTunnel", + "columnName": "is_primary_tunnel", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "false" + }, + { + "fieldPath": "amQuick", + "columnName": "am_quick", + "affinity": "TEXT", + "notNull": true, + "defaultValue": "''" + }, + { + "fieldPath": "isActive", + "columnName": "is_Active", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "false" + }, + { + "fieldPath": "isPingEnabled", + "columnName": "is_ping_enabled", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "false" + }, + { + "fieldPath": "pingInterval", + "columnName": "ping_interval", + "affinity": "INTEGER", + "notNull": false, + "defaultValue": "null" + }, + { + "fieldPath": "pingCooldown", + "columnName": "ping_cooldown", + "affinity": "INTEGER", + "notNull": false, + "defaultValue": "null" + }, + { + "fieldPath": "pingIp", + "columnName": "ping_ip", + "affinity": "TEXT", + "notNull": false, + "defaultValue": "null" + }, + { + "fieldPath": "isEthernetTunnel", + "columnName": "is_ethernet_tunnel", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "false" + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [ + { + "name": "index_TunnelConfig_name", + "unique": true, + "columnNames": [ + "name" + ], + "orders": [], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_TunnelConfig_name` ON `${TABLE_NAME}` (`name`)" + } + ], + "foreignKeys": [] + } + ], + "views": [], + "setupQueries": [ + "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'acf79ac5defacda5be6c3f976e777de3')" + ] + } +} \ 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 052a5d7e..0df62b46 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 = 11, + version = 12, autoMigrations = [ AutoMigration(from = 1, to = 2), @@ -41,6 +41,10 @@ import com.zaneschepke.wireguardautotunnel.data.domain.TunnelConfig to = 11, spec = RemoveTunnelPauseMigration::class, ), + AutoMigration( + from = 11, + to = 12, + ), ], exportSchema = true, ) diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/data/TunnelConfigDao.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/data/TunnelConfigDao.kt index cbb520a6..f3fb52e6 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/data/TunnelConfigDao.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/data/TunnelConfigDao.kt @@ -44,6 +44,9 @@ interface TunnelConfigDao { @Query("UPDATE TunnelConfig SET is_mobile_data_tunnel = 0 WHERE is_mobile_data_tunnel =1") suspend fun resetMobileDataTunnel() + @Query("UPDATE TunnelConfig SET is_ethernet_tunnel = 0 WHERE is_ethernet_tunnel =1") + suspend fun resetEthernetTunnel() + @Query("SELECT * FROM TUNNELCONFIG WHERE is_primary_tunnel=1") suspend fun findByPrimary(): TunnelConfigs 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 aceda142..345198ba 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 @@ -60,4 +60,9 @@ data class Settings( defaultValue = "false", ) val isWifiNameByShellEnabled: Boolean = false, + @ColumnInfo( + name = "is_stop_on_no_internet_enabled", + defaultValue = "false", + ) + val isStopOnNoInternetEnabled: Boolean = false, ) diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/data/domain/TunnelConfig.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/data/domain/TunnelConfig.kt index 028ebc7f..77b899a0 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/data/domain/TunnelConfig.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/data/domain/TunnelConfig.kt @@ -58,6 +58,11 @@ data class TunnelConfig( defaultValue = "null", ) var pingIp: String? = null, + @ColumnInfo( + name = "is_ethernet_tunnel", + defaultValue = "false", + ) + var isEthernetTunnel: Boolean = false, ) { fun toAmConfig(): org.amnezia.awg.config.Config { diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/data/repository/RoomTunnelConfigRepository.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/data/repository/RoomTunnelConfigRepository.kt index eeae7d59..1f058990 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/data/repository/RoomTunnelConfigRepository.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/data/repository/RoomTunnelConfigRepository.kt @@ -53,6 +53,19 @@ class RoomTunnelConfigRepository( } } + override suspend fun updateEthernetTunnel(tunnelConfig: TunnelConfig?) { + withContext(ioDispatcher) { + tunnelConfigDao.resetEthernetTunnel() + tunnelConfig?.let { + save( + it.copy( + isEthernetTunnel = true, + ), + ) + } + } + } + override suspend fun delete(tunnelConfig: TunnelConfig) { withContext(ioDispatcher) { tunnelConfigDao.delete(tunnelConfig) diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/data/repository/TunnelConfigRepository.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/data/repository/TunnelConfigRepository.kt index a823af51..66a464f1 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/data/repository/TunnelConfigRepository.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/data/repository/TunnelConfigRepository.kt @@ -15,6 +15,8 @@ interface TunnelConfigRepository { suspend fun updateMobileDataTunnel(tunnelConfig: TunnelConfig?) + suspend fun updateEthernetTunnel(tunnelConfig: TunnelConfig?) + suspend fun delete(tunnelConfig: TunnelConfig) suspend fun getById(id: Int): TunnelConfig? diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/foreground/autotunnel/AutoTunnelEvent.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/foreground/autotunnel/AutoTunnelEvent.kt index dbe7c14a..5a08de33 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/foreground/autotunnel/AutoTunnelEvent.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/foreground/autotunnel/AutoTunnelEvent.kt @@ -4,6 +4,6 @@ import com.zaneschepke.wireguardautotunnel.data.domain.TunnelConfig sealed class AutoTunnelEvent { data class Start(val tunnelConfig: TunnelConfig? = null) : AutoTunnelEvent() - data class Stop(val tunnelConfig: TunnelConfig?) : AutoTunnelEvent() + data object Stop : AutoTunnelEvent() data object DoNothing : AutoTunnelEvent() } diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/foreground/autotunnel/AutoTunnelState.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/foreground/autotunnel/AutoTunnelState.kt index b1c9b249..de9b1039 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/foreground/autotunnel/AutoTunnelState.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/foreground/autotunnel/AutoTunnelState.kt @@ -26,10 +26,19 @@ data class AutoTunnelState( vpnState.status.isUp() && preferredTunnel.id != vpnState.tunnelConfig?.id } + private fun isEthernetTunnelChangeNeeded(): Boolean { + val preferredTunnel = preferredEthernetTunnel() + return preferredTunnel != null && vpnState.status.isUp() && preferredTunnel.id != vpnState.tunnelConfig?.id + } + private fun preferredMobileDataTunnel(): TunnelConfig? { return tunnels.firstOrNull { it.isMobileDataTunnel } ?: tunnels.firstOrNull { it.isPrimaryTunnel } } + private fun preferredEthernetTunnel(): TunnelConfig? { + return tunnels.firstOrNull { it.isEthernetTunnel } ?: tunnels.firstOrNull { it.isPrimaryTunnel } + } + private fun preferredWifiTunnel(): TunnelConfig? { return getTunnelWithMatchingTunnelNetwork() ?: tunnels.firstOrNull { it.isPrimaryTunnel } } @@ -42,6 +51,14 @@ data class AutoTunnelState( return isEthernetConnected && settings.isTunnelOnEthernetEnabled && vpnState.status.isDown() } + private fun stopOnEthernet() : Boolean { + return isEthernetConnected && !settings.isTunnelOnEthernetEnabled && vpnState.status.isUp() + } + + private fun isNoConnectivity(): Boolean { + return !isEthernetConnected && !isWifiConnected && !isMobileDataConnected + } + private fun stopOnMobileData(): Boolean { return isMobileDataActive() && !settings.isTunnelOnMobileDataEnabled && vpnState.status.isUp() } @@ -54,6 +71,10 @@ data class AutoTunnelState( return isMobileDataActive() && settings.isTunnelOnMobileDataEnabled && isMobileTunnelDataChangeNeeded() } + private fun changeOnEthernet(): Boolean { + return isEthernetConnected && settings.isTunnelOnEthernetEnabled && isEthernetTunnelChangeNeeded() + } + private fun stopOnWifi(): Boolean { return isWifiActive() && !settings.isTunnelOnWifiEnabled && vpnState.status.isUp() } @@ -75,25 +96,23 @@ data class AutoTunnelState( val vpnTunnel = vpnState.tunnelConfig return if (preferred != null && vpnTunnel != null) { preferred.id == vpnTunnel.id - } else { - true - } + } else true } - // TODO add shutdown on no connectivity fun asAutoTunnelEvent(): AutoTunnelEvent { return when { // ethernet scenarios - startOnEthernet() -> AutoTunnelEvent.Start() + stopOnEthernet() -> AutoTunnelEvent.Stop + startOnEthernet() || changeOnEthernet() -> AutoTunnelEvent.Start(preferredEthernetTunnel()) // mobile data scenarios - stopOnMobileData() -> AutoTunnelEvent.Stop(vpnState.tunnelConfig) - startOnMobileData() -> AutoTunnelEvent.Start(tunnels.firstOrNull { it.isMobileDataTunnel }) - changeOnMobileData() -> AutoTunnelEvent.Start(preferredMobileDataTunnel()) + stopOnMobileData() -> AutoTunnelEvent.Stop + startOnMobileData() || changeOnMobileData() -> AutoTunnelEvent.Start(preferredMobileDataTunnel()) // wifi scenarios - stopOnWifi() -> AutoTunnelEvent.Stop(vpnState.tunnelConfig) - stopOnTrustedWifi() -> AutoTunnelEvent.Stop(vpnState.tunnelConfig) - startOnUntrustedWifi() -> AutoTunnelEvent.Start(preferredWifiTunnel()) - changeOnUntrustedWifi() -> AutoTunnelEvent.Start(preferredWifiTunnel()) + stopOnWifi() -> AutoTunnelEvent.Stop + stopOnTrustedWifi() -> AutoTunnelEvent.Stop + startOnUntrustedWifi() || changeOnUntrustedWifi() -> AutoTunnelEvent.Start(preferredWifiTunnel()) + // no connectivity + isNoConnectivity() && settings.isStopOnNoInternetEnabled -> AutoTunnelEvent.Stop else -> AutoTunnelEvent.DoNothing } } @@ -101,18 +120,14 @@ data class AutoTunnelState( private fun isCurrentSSIDTrusted(): Boolean { return if (settings.isWildcardsEnabled) { settings.trustedNetworkSSIDs.isMatchingToWildcardList(currentNetworkSSID) - } else { - settings.trustedNetworkSSIDs.contains(currentNetworkSSID) - } + } else settings.trustedNetworkSSIDs.contains(currentNetworkSSID) } private fun getTunnelWithMatchingTunnelNetwork(): TunnelConfig? { return tunnels.firstOrNull { if (settings.isWildcardsEnabled) { it.tunnelNetworks.isMatchingToWildcardList(currentNetworkSSID) - } else { - it.tunnelNetworks.contains(currentNetworkSSID) - } + } else it.tunnelNetworks.contains(currentNetworkSSID) } } } diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/main/components/TunnelRowItem.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/main/components/TunnelRowItem.kt index 11e2cd4d..741cb084 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/main/components/TunnelRowItem.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/main/components/TunnelRowItem.kt @@ -9,6 +9,7 @@ import androidx.compose.material.icons.rounded.CopyAll import androidx.compose.material.icons.rounded.Delete import androidx.compose.material.icons.rounded.Info import androidx.compose.material.icons.rounded.Settings +import androidx.compose.material.icons.rounded.SettingsEthernet import androidx.compose.material.icons.rounded.Smartphone import androidx.compose.material.icons.rounded.Star import androidx.compose.material3.Icon @@ -55,14 +56,12 @@ fun TunnelRowItem( val itemFocusRequester = remember { FocusRequester() } ExpandingRowListItem( leading = { - val icon = - if (tunnel.isPrimaryTunnel) { - Icons.Rounded.Star - } else if (tunnel.isMobileDataTunnel) { - Icons.Rounded.Smartphone - } else { - Icons.Rounded.Circle - } + val icon = when { + tunnel.isPrimaryTunnel -> Icons.Rounded.Star + tunnel.isMobileDataTunnel -> Icons.Rounded.Smartphone + tunnel.isEthernetTunnel -> Icons.Rounded.SettingsEthernet + else -> Icons.Rounded.Circle + } Icon( icon, icon.name, diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/options/OptionsScreen.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/options/OptionsScreen.kt index 480a811c..3bd321eb 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/options/OptionsScreen.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/options/OptionsScreen.kt @@ -15,6 +15,7 @@ import androidx.compose.material.icons.outlined.Edit import androidx.compose.material.icons.outlined.NetworkPing import androidx.compose.material.icons.outlined.PhoneAndroid import androidx.compose.material.icons.outlined.Security +import androidx.compose.material.icons.outlined.SettingsEthernet import androidx.compose.material.icons.outlined.Star import androidx.compose.material3.Icon import androidx.compose.material3.IconButton @@ -139,6 +140,28 @@ fun OptionsScreen(optionsViewModel: OptionsViewModel = hiltViewModel(), appUiSta }, onClick = { optionsViewModel.onToggleIsMobileDataTunnel(config) }, ), + SelectionItem( + Icons.Outlined.SettingsEthernet, + title = { + Text( + stringResource(R.string.ethernet_tunnel), + style = MaterialTheme.typography.bodyMedium.copy(MaterialTheme.colorScheme.onSurface), + ) + }, + description = { + Text( + stringResource(R.string.set_ethernet_tunnel), + style = MaterialTheme.typography.bodyMedium.copy(MaterialTheme.colorScheme.outline), + ) + }, + trailing = { + ScaledSwitch( + config.isEthernetTunnel, + onClick = { optionsViewModel.onToggleIsEthernetTunnel(config) }, + ) + }, + onClick = { optionsViewModel.onToggleIsEthernetTunnel(config) }, + ), SelectionItem( Icons.Outlined.NetworkPing, title = { diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/options/OptionsViewModel.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/options/OptionsViewModel.kt index 6fc4f967..e1bfaa5c 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/options/OptionsViewModel.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/options/OptionsViewModel.kt @@ -77,4 +77,12 @@ constructor( ), ) } + + fun onToggleIsEthernetTunnel(tunnelConfig: TunnelConfig) = viewModelScope.launch { + if (tunnelConfig.isEthernetTunnel) { + appDataRepository.tunnels.updateEthernetTunnel(null) + } else { + appDataRepository.tunnels.updateEthernetTunnel(tunnelConfig) + } + } } 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 a6094c6c..6a741277 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 @@ -12,6 +12,7 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.outlined.AirplanemodeActive import androidx.compose.material.icons.outlined.Code import androidx.compose.material.icons.outlined.Filter1 import androidx.compose.material.icons.outlined.NetworkPing @@ -323,6 +324,30 @@ fun AutoTunnelScreen(uiState: AppUiState, viewModel: AutoTunnelViewModel = hiltV viewModel.onToggleRestartOnPing() }, ), + SelectionItem( + Icons.Outlined.AirplanemodeActive, + title = { + Text( + stringResource(R.string.stop_on_no_internet), + style = MaterialTheme.typography.bodyMedium.copy(MaterialTheme.colorScheme.onSurface), + ) + }, + description = { + Text( + stringResource(R.string.stop_on_internet_loss), + style = MaterialTheme.typography.bodySmall.copy(MaterialTheme.colorScheme.outline), + ) + }, + trailing = { + ScaledSwitch( + checked = uiState.settings.isStopOnNoInternetEnabled, + onClick = { viewModel.onToggleStopOnNoInternet() }, + ) + }, + onClick = { + viewModel.onToggleStopOnNoInternet() + }, + ), ), ) } 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 83104ba2..2ea86423 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 @@ -128,4 +128,12 @@ constructor( ) } } + + fun onToggleStopOnNoInternet() = viewModelScope.launch { + with(settings.value) { + appDataRepository.settings.save( + copy(isStopOnNoInternetEnabled = !isStopOnNoInternetEnabled), + ) + } + } } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 904803b4..299aa92b 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -178,4 +178,8 @@ Configuration change This change requires an app relaunch. Would you like to proceed? Add from clipboard + Stop on no internet + Stop tunnel on internet loss + Ethernet tunnel + Set as ethernet tunnel