From 02a8db0f9a5aadd6b6fd62493f3ff1a48bb39291 Mon Sep 17 00:00:00 2001 From: Zane Schepke Date: Tue, 31 Dec 2024 19:02:44 -0500 Subject: [PATCH] feat: add setting for debounce delay tuning closes #493 --- .../14.json | 274 ++++++++++++++++++ .../wireguardautotunnel/data/AppDatabase.kt | 6 +- .../data/domain/Settings.kt | 5 + .../autotunnel/AutoTunnelService.kt | 5 +- .../wireguardautotunnel/ui/AppViewModel.kt | 12 + .../wireguardautotunnel/ui/MainActivity.kt | 6 + .../wireguardautotunnel/ui/Route.kt | 3 + .../settings/autotunnel/AutoTunnelScreen.kt | 25 ++ .../autotunnel/advanced/AdvancedScreen.kt | 118 ++++++++ app/src/main/res/values/strings.xml | 2 + 10 files changed, 454 insertions(+), 2 deletions(-) create mode 100644 app/schemas/com.zaneschepke.wireguardautotunnel.data.AppDatabase/14.json create mode 100644 app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/autotunnel/advanced/AdvancedScreen.kt diff --git a/app/schemas/com.zaneschepke.wireguardautotunnel.data.AppDatabase/14.json b/app/schemas/com.zaneschepke.wireguardautotunnel.data.AppDatabase/14.json new file mode 100644 index 0000000..1ba1789 --- /dev/null +++ b/app/schemas/com.zaneschepke.wireguardautotunnel.data.AppDatabase/14.json @@ -0,0 +1,274 @@ +{ + "formatVersion": 1, + "database": { + "version": 14, + "identityHash": "f2b260c389fb2e53216de40e4b1047f3", + "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)", + "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" + } + ], + "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, 'f2b260c389fb2e53216de40e4b1047f3')" + ] + } +} \ 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 ca61ff4..f4a1041 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 = 13, + version = 14, autoMigrations = [ AutoMigration(from = 1, to = 2), @@ -49,6 +49,10 @@ import com.zaneschepke.wireguardautotunnel.data.domain.TunnelConfig from = 12, to = 13, ), + AutoMigration( + from = 13, + to = 14, + ), ], 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 d3679a7..f128495 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 @@ -80,4 +80,9 @@ data class Settings( defaultValue = "false", ) val isLanOnKillSwitchEnabled: Boolean = false, + @ColumnInfo( + name = "debounce_delay_seconds", + defaultValue = "3", + ) + val debounceDelaySeconds: Int = 3, ) 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 fe20712..263ffdf 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 @@ -284,7 +284,10 @@ class AutoTunnelService : LifecycleService() { @OptIn(FlowPreview::class) private fun startAutoTunnelJob() = lifecycleScope.launch(ioDispatcher) { Timber.i("Starting auto-tunnel network event watcher") - autoTunnelStateFlow.debounce(1000L).collect { watcherState -> + val settings = appDataRepository.get().settings.getSettings() + val debounce = settings.debounceDelaySeconds * 1000L + Timber.d("Starting with debounce delay of: $debounce") + autoTunnelStateFlow.debounce(debounce).collect { watcherState -> if (watcherState == defaultState) return@collect Timber.d("New auto tunnel state emitted") when (val event = watcherState.asAutoTunnelEvent()) { diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/AppViewModel.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/AppViewModel.kt index 911981c..11f0f6c 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/AppViewModel.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/AppViewModel.kt @@ -9,6 +9,7 @@ import com.wireguard.config.Config import com.zaneschepke.logcatter.LogReader import com.zaneschepke.wireguardautotunnel.R import com.zaneschepke.wireguardautotunnel.WireGuardAutoTunnel +import com.zaneschepke.wireguardautotunnel.data.domain.Settings import com.zaneschepke.wireguardautotunnel.data.domain.TunnelConfig import com.zaneschepke.wireguardautotunnel.data.repository.AppDataRepository import com.zaneschepke.wireguardautotunnel.module.AppShell @@ -27,6 +28,7 @@ import com.zaneschepke.wireguardautotunnel.util.StringValue import com.zaneschepke.wireguardautotunnel.util.extensions.getAllInternetCapablePackages import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.delay import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted @@ -132,6 +134,10 @@ constructor( } } + fun saveSettings(settings: Settings) = viewModelScope.launch { + appDataRepository.settings.save(settings) + } + fun onPinLockDisabled() = viewModelScope.launch(ioDispatcher) { PinManager.clearPin() appDataRepository.appState.setPinLockEnabled(false) @@ -369,6 +375,12 @@ constructor( } } + fun bounceAutoTunnel() = viewModelScope.launch(ioDispatcher) { + serviceManager.stopAutoTunnel() + delay(1000L) + serviceManager.startAutoTunnel(true) + } + fun onTogglePrimaryTunnel(tunnelConfig: TunnelConfig) = viewModelScope.launch { appDataRepository.tunnels.updatePrimaryTunnel( when (tunnelConfig.isPrimaryTunnel) { 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 613ea86..d44af87 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/MainActivity.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/MainActivity.kt @@ -12,6 +12,7 @@ import androidx.appcompat.app.AppCompatActivity import androidx.compose.animation.core.tween import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut +import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.ExperimentalLayoutApi import androidx.compose.foundation.layout.WindowInsets @@ -55,6 +56,7 @@ import com.zaneschepke.wireguardautotunnel.ui.screens.settings.appearance.Appear import com.zaneschepke.wireguardautotunnel.ui.screens.settings.appearance.display.DisplayScreen import com.zaneschepke.wireguardautotunnel.ui.screens.settings.appearance.language.LanguageScreen import com.zaneschepke.wireguardautotunnel.ui.screens.settings.autotunnel.AutoTunnelScreen +import com.zaneschepke.wireguardautotunnel.ui.screens.settings.autotunnel.advanced.AdvancedScreen import com.zaneschepke.wireguardautotunnel.ui.screens.settings.disclosure.LocationDisclosureScreen import com.zaneschepke.wireguardautotunnel.ui.screens.settings.killswitch.KillSwitchScreen import com.zaneschepke.wireguardautotunnel.ui.screens.support.SupportScreen @@ -137,6 +139,7 @@ class MainActivity : AppCompatActivity() { SnackbarControllerProvider { host -> WireguardAutoTunnelTheme(theme = appUiState.generalState.theme) { Scaffold( + modifier = Modifier.background(color = MaterialTheme.colorScheme.background), contentWindowInsets = WindowInsets(0), snackbarHost = { SnackbarHost(host) { snackbarData: SnackbarData -> @@ -211,6 +214,9 @@ class MainActivity : AppCompatActivity() { composable { SupportScreen(appUiState, viewModel) } + composable { + AdvancedScreen(appUiState, viewModel) + } composable { LogsScreen() } diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/Route.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/Route.kt index 3c1c79c..dd817f4 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/Route.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/Route.kt @@ -12,6 +12,9 @@ sealed class Route { @Serializable data object AutoTunnel : Route() + @Serializable + data object AutoTunnelAdvanced : Route() + @Serializable data object LocationDisclosure : Route() 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 e45bdb2..7da2c0d 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 @@ -18,8 +18,10 @@ import androidx.compose.material.icons.outlined.Code import androidx.compose.material.icons.outlined.Filter1 import androidx.compose.material.icons.outlined.NetworkPing 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,13 +44,16 @@ 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.ui.Route import com.zaneschepke.wireguardautotunnel.ui.common.button.ScaledSwitch import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SelectionItem import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SurfaceSelectionGroupButton +import com.zaneschepke.wireguardautotunnel.ui.common.navigation.LocalNavController import com.zaneschepke.wireguardautotunnel.ui.common.navigation.TopNavBar import com.zaneschepke.wireguardautotunnel.ui.screens.settings.autotunnel.components.TrustedNetworkTextBox import com.zaneschepke.wireguardautotunnel.ui.screens.settings.autotunnel.components.WildcardsLabel import com.zaneschepke.wireguardautotunnel.ui.screens.settings.components.BackgroundLocationDialog +import com.zaneschepke.wireguardautotunnel.ui.screens.settings.components.ForwardButton import com.zaneschepke.wireguardautotunnel.ui.screens.settings.components.LearnMoreLinkLabel import com.zaneschepke.wireguardautotunnel.ui.screens.settings.components.LocationServicesDialog import com.zaneschepke.wireguardautotunnel.ui.theme.iconSize @@ -62,6 +67,7 @@ import com.zaneschepke.wireguardautotunnel.util.extensions.scaledWidth @Composable fun AutoTunnelScreen(uiState: AppUiState, viewModel: AutoTunnelViewModel = hiltViewModel()) { val context = LocalContext.current + val navController = LocalNavController.current val fineLocationState = rememberPermissionState(Manifest.permission.ACCESS_FINE_LOCATION) var currentText by remember { mutableStateOf("") } @@ -353,6 +359,25 @@ fun AutoTunnelScreen(uiState: AppUiState, viewModel: AutoTunnelViewModel = hiltV ), ), ) + SurfaceSelectionGroupButton( + listOf( + SelectionItem( + Icons.Outlined.Settings, + title = { + Text( + stringResource(R.string.advanced_settings), + style = MaterialTheme.typography.bodyMedium.copy(MaterialTheme.colorScheme.onSurface), + ) + }, + onClick = { + navController.navigate(Route.AutoTunnelAdvanced) + }, + trailing = { + ForwardButton { navController.navigate(Route.AutoTunnelAdvanced) } + }, + ), + ) + ) } } } 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 new file mode 100644 index 0000000..b1dd15e --- /dev/null +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/autotunnel/advanced/AdvancedScreen.kt @@ -0,0 +1,118 @@ +package com.zaneschepke.wireguardautotunnel.ui.screens.settings.autotunnel.advanced + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.ArrowDropDown +import androidx.compose.material.icons.outlined.PauseCircle +import androidx.compose.material3.DropdownMenu +import androidx.compose.material3.DropdownMenuItem +import androidx.compose.material3.Icon +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 +import androidx.compose.ui.Alignment +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.ui.AppViewModel +import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SelectionItem +import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SurfaceSelectionGroupButton +import com.zaneschepke.wireguardautotunnel.ui.common.navigation.TopNavBar +import com.zaneschepke.wireguardautotunnel.util.extensions.scaledHeight +import com.zaneschepke.wireguardautotunnel.util.extensions.scaledWidth + +@Composable +fun AdvancedScreen(appUiState: AppUiState, 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)) + }, + ) { padding -> + Column( + horizontalAlignment = Alignment.Start, + verticalArrangement = Arrangement.spacedBy(24.dp.scaledHeight(), Alignment.Top), + modifier = + Modifier + .fillMaxSize() + .padding(padding) + .verticalScroll(rememberScrollState()) + .padding(top = 24.dp.scaledHeight()) + .padding(horizontal = 24.dp.scaledWidth()), + ) { + SurfaceSelectionGroupButton( + listOf( + SelectionItem( + Icons.Outlined.PauseCircle, + title = { + Text( + stringResource(R.string.debounce_delay), + style = MaterialTheme.typography.bodyMedium.copy(MaterialTheme.colorScheme.onSurface), + ) + }, + onClick = { + isDropDownExpanded = true + }, + trailing = { + Row( + horizontalArrangement = Arrangement.spacedBy(5.dp, Alignment.CenterHorizontally), + verticalAlignment = Alignment.CenterVertically) { + Text(text = selected.toString(), style = MaterialTheme.typography.bodyMedium) + val icon = Icons.Default.ArrowDropDown + Icon(icon, icon.name) + } + DropdownMenu( + modifier = Modifier.height(140.dp.scaledHeight()), + scrollState = rememberScrollState(), + containerColor = MaterialTheme.colorScheme.surface, + expanded = isDropDownExpanded, + onDismissRequest = { + isDropDownExpanded = false + }) { + (0..10).forEachIndexed { index, num -> + DropdownMenuItem(text = { + Text(text = num.toString()) + }, + onClick = { + isDropDownExpanded = false + selected = num + }) + } + } + }, + ) + ) + ) + } + } +} diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 3e825e0..d3c754f 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -194,4 +194,6 @@ Enable Amnezia WG compatibility mode Quick actions + Advanced settings + Debounce delay