refactor: monitoring ui

This commit is contained in:
Zane Schepke
2025-10-17 15:04:55 -04:00
parent 01b5298254
commit a160802f81
16 changed files with 137 additions and 104 deletions
@@ -1,7 +1,10 @@
package com.zaneschepke.wireguardautotunnel.ui.common.button
import androidx.compose.animation.animateContentSize
import androidx.compose.foundation.*
import androidx.compose.foundation.background
import androidx.compose.foundation.combinedClickable
import androidx.compose.foundation.focusable
import androidx.compose.foundation.indication
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.*
import androidx.compose.material3.MaterialTheme
@@ -1,7 +1,5 @@
package com.zaneschepke.wireguardautotunnel.ui.navigation.components
import android.R.attr.button
import android.R.attr.theme
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
@@ -334,7 +334,7 @@ fun currentRouteAsNavbarState(
)
}
},
topTitle = context.getString(R.string.tunnel_monitoring),
topTitle = context.getString(R.string.ping_monitor),
showBottomItems = true,
)
is TunnelOptions -> {
@@ -193,6 +193,9 @@ fun WifiSettingsScreen(viewModel: AutoTunnelViewModel = hiltViewModel()) {
SurfaceRow(
leading = { Icon(Icons.Outlined.Map, contentDescription = null) },
title = stringResource(R.string.tunnel_mapping),
description = {
DescriptionText(stringResource(R.string.tunnel_mapping_description))
},
onClick = { navController.push(Route.PreferredTunnel(TunnelNetwork.WIFI)) },
)
}
@@ -16,6 +16,7 @@ import androidx.compose.runtime.*
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
@@ -60,20 +61,13 @@ fun SettingsScreen(viewModel: SettingsViewModel = hiltViewModel()) {
var showBackupSheet by rememberSaveable { mutableStateOf(false) }
var showAppModeSheet by rememberSaveable { mutableStateOf(false) }
val appMode by
rememberSaveable(settingsState.settings.appMode) {
mutableStateOf(settingsState.settings.appMode)
}
val monitoringEnabled by
rememberSaveable(appMode) {
mutableStateOf(appMode == AppMode.VPN || appMode == AppMode.KERNEL)
}
val appMode = settingsState.settings.appMode
val dnsEnabled by rememberSaveable(appMode) { mutableStateOf(appMode != AppMode.KERNEL) }
val showProxySettings by
remember(settingsState.settings.appMode) {
remember(appMode) {
derivedStateOf {
when (settingsState.settings.appMode) {
when (appMode) {
AppMode.PROXY -> true
else -> false
}
@@ -99,6 +93,18 @@ fun SettingsScreen(viewModel: SettingsViewModel = hiltViewModel()) {
showAppModeSheet = false
}
val isPingMonitoringAvailable by
remember(settingsState.settings.appMode) {
derivedStateOf {
settingsState.settings.appMode != AppMode.PROXY &&
settingsState.settings.appMode != AppMode.LOCK_DOWN
}
}
LaunchedEffect(isPingMonitoringAvailable) {
if (!isPingMonitoringAvailable) viewModel.setPingEnabled(false)
}
Column(
horizontalAlignment = Alignment.Start,
verticalArrangement = Arrangement.spacedBy(16.dp, Alignment.Top),
@@ -153,15 +159,14 @@ fun SettingsScreen(viewModel: SettingsViewModel = hiltViewModel()) {
},
)
}
SurfaceRow(
leading = { Icon(Icons.Outlined.MonitorHeart, null) },
title = stringResource(R.string.tunnel_monitoring),
enabled = monitoringEnabled,
onClick = { navController.push(Route.TunnelMonitoring) },
)
SurfaceRow(
leading = { Icon(Icons.Outlined.Dns, null) },
leading = {
Icon(
Icons.Outlined.Dns,
null,
tint = if (dnsEnabled) MaterialTheme.colorScheme.onSurface else Color.Gray,
)
},
title = stringResource(R.string.dns_settings),
enabled = dnsEnabled,
onClick = {
@@ -207,6 +212,45 @@ fun SettingsScreen(viewModel: SettingsViewModel = hiltViewModel()) {
)
}
Column {
GroupLabel(
stringResource(R.string.monitoring),
modifier = Modifier.padding(horizontal = 16.dp),
)
SurfaceRow(
leading = {
Icon(
Icons.Outlined.NetworkPing,
contentDescription = null,
tint =
if (isPingMonitoringAvailable) MaterialTheme.colorScheme.onSurface
else Color.Gray,
)
},
title = stringResource(R.string.ping_monitor),
enabled = isPingMonitoringAvailable,
trailing = {
SwitchWithDivider(
checked = settingsState.monitoring.isPingEnabled,
onClick = { viewModel.setPingEnabled(it) },
enabled = isPingMonitoringAvailable,
)
},
onClick = { navController.push(Route.TunnelMonitoring) },
)
SurfaceRow(
leading = { Icon(Icons.Outlined.ViewHeadline, contentDescription = null) },
title = stringResource(R.string.local_logging),
trailing = { modifier ->
SwitchWithDivider(
checked = settingsState.monitoring.isLocalLogsEnabled,
onClick = { viewModel.setLocalLogging(it) },
modifier = modifier,
)
},
onClick = { navController.push(Route.Logs) },
)
}
Column(modifier = Modifier.padding(bottom = 16.dp)) {
GroupLabel(
stringResource(R.string.general),
modifier = Modifier.padding(horizontal = 16.dp),
@@ -16,6 +16,7 @@ import androidx.compose.material3.Text
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextOverflow
@@ -23,6 +24,7 @@ import androidx.compose.ui.unit.dp
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.zaneschepke.wireguardautotunnel.R
import com.zaneschepke.wireguardautotunnel.ui.LocalIsAndroidTV
import com.zaneschepke.wireguardautotunnel.ui.common.button.ScaledSwitch
import com.zaneschepke.wireguardautotunnel.ui.common.button.SurfaceRow
import com.zaneschepke.wireguardautotunnel.ui.common.functions.rememberClipboardHelper
@@ -35,6 +37,7 @@ import com.zaneschepke.wireguardautotunnel.viewmodel.SettingsViewModel
@Composable
fun AndroidIntegrationsScreen(viewModel: SettingsViewModel = hiltViewModel()) {
val context = LocalContext.current
val isTv = LocalIsAndroidTV.current
val settingsState by viewModel.container.stateFlow.collectAsStateWithLifecycle()
@@ -44,33 +47,41 @@ fun AndroidIntegrationsScreen(viewModel: SettingsViewModel = hiltViewModel()) {
SecureScreenFromRecording()
val isAlwaysOnEnabled = settingsState.settings.isAlwaysOnVpnEnabled
LaunchedEffect(isAlwaysOnEnabled) {
if (isAlwaysOnEnabled) viewModel.setRestoreOnBootEnabled(false)
}
Column(
horizontalAlignment = Alignment.Start,
verticalArrangement = Arrangement.spacedBy(16.dp, Alignment.Top),
modifier = Modifier.fillMaxSize().verticalScroll(rememberScrollState()),
) {
Column {
GroupLabel(stringResource(id = R.string.vpn), Modifier.padding(horizontal = 16.dp))
SurfaceRow(
leading = { Icon(Icons.Outlined.AdminPanelSettings, contentDescription = null) },
title = stringResource(R.string.native_kill_switch),
trailing = { Icon(Icons.AutoMirrored.Outlined.Launch, null) },
onClick = { context.launchVpnSettings() },
)
SurfaceRow(
leading = { Icon(Icons.Outlined.VpnLock, contentDescription = null) },
trailing = {
ScaledSwitch(
checked = settingsState.settings.isAlwaysOnVpnEnabled,
onClick = { viewModel.setAlwaysOnVpnEnabled(it) },
)
},
title = stringResource(R.string.always_on_vpn_support),
onClick = {
viewModel.setAlwaysOnVpnEnabled(!settingsState.settings.isAlwaysOnVpnEnabled)
},
description = { DescriptionText(stringResource(R.string.aovpn_description)) },
)
if (!isTv) {
Column {
GroupLabel(stringResource(id = R.string.vpn), Modifier.padding(horizontal = 16.dp))
SurfaceRow(
leading = {
Icon(Icons.Outlined.AdminPanelSettings, contentDescription = null)
},
title = stringResource(R.string.native_kill_switch),
trailing = { Icon(Icons.AutoMirrored.Outlined.Launch, null) },
onClick = { context.launchVpnSettings() },
)
SurfaceRow(
leading = { Icon(Icons.Outlined.VpnLock, contentDescription = null) },
trailing = {
ScaledSwitch(
checked = isAlwaysOnEnabled,
onClick = { viewModel.setAlwaysOnVpnEnabled(it) },
)
},
title = stringResource(R.string.always_on_vpn_support),
onClick = { viewModel.setAlwaysOnVpnEnabled(!isAlwaysOnEnabled) },
description = { DescriptionText(stringResource(R.string.aovpn_description)) },
)
}
}
Column {
GroupLabel(
@@ -78,11 +89,20 @@ fun AndroidIntegrationsScreen(viewModel: SettingsViewModel = hiltViewModel()) {
Modifier.padding(horizontal = 16.dp),
)
SurfaceRow(
leading = { Icon(Icons.Outlined.Restore, contentDescription = null) },
leading = {
Icon(
Icons.Outlined.Restore,
contentDescription = null,
tint =
if (isAlwaysOnEnabled) Color.Gray
else MaterialTheme.colorScheme.onSurface,
)
},
trailing = {
ScaledSwitch(
checked = settingsState.settings.isRestoreOnBootEnabled,
onClick = { viewModel.setRestoreOnBootEnabled(it) },
enabled = !isAlwaysOnEnabled,
)
},
title = stringResource(R.string.restart_at_boot),
@@ -91,6 +111,7 @@ fun AndroidIntegrationsScreen(viewModel: SettingsViewModel = hiltViewModel()) {
!settingsState.settings.isRestoreOnBootEnabled
)
},
enabled = !isAlwaysOnEnabled,
description = { DescriptionText(stringResource(R.string.tunnel_boot_description)) },
)
SurfaceRow(
@@ -23,7 +23,6 @@ import com.zaneschepke.wireguardautotunnel.R
import com.zaneschepke.wireguardautotunnel.ui.LocalNavController
import com.zaneschepke.wireguardautotunnel.ui.common.button.ScaledSwitch
import com.zaneschepke.wireguardautotunnel.ui.common.button.SurfaceRow
import com.zaneschepke.wireguardautotunnel.ui.common.button.SwitchWithDivider
import com.zaneschepke.wireguardautotunnel.ui.common.dropdown.LabelledDropdown
import com.zaneschepke.wireguardautotunnel.ui.common.label.GroupLabel
import com.zaneschepke.wireguardautotunnel.ui.navigation.Route
@@ -46,19 +45,6 @@ fun TunnelMonitoringScreen(viewModel: MonitoringViewModel = hiltViewModel()) {
stringResource(R.string.ping),
modifier = Modifier.padding(horizontal = 16.dp),
)
SurfaceRow(
leading = { Icon(Icons.Outlined.NetworkPing, contentDescription = null) },
title = stringResource(R.string.ping_monitor),
trailing = {
ScaledSwitch(
checked = monitoringUiState.monitoringSettings.isPingEnabled,
onClick = { viewModel.setPingEnabled(it) },
)
},
onClick = {
viewModel.setPingEnabled(!monitoringUiState.monitoringSettings.isPingEnabled)
},
)
LabelledDropdown(
title = stringResource(R.string.tunnel_ping_interval),
leading = { Icon(Icons.Outlined.Timer, contentDescription = null) },
@@ -117,23 +103,5 @@ fun TunnelMonitoringScreen(viewModel: MonitoringViewModel = hiltViewModel()) {
onClick = { navController.push(Route.PingTarget) },
)
}
Column {
GroupLabel(
stringResource(R.string.other),
modifier = Modifier.padding(horizontal = 16.dp),
)
SurfaceRow(
leading = { Icon(Icons.Outlined.ViewHeadline, contentDescription = null) },
title = stringResource(R.string.local_logging),
trailing = { modifier ->
SwitchWithDivider(
checked = monitoringUiState.monitoringSettings.isLocalLogsEnabled,
onClick = { viewModel.setLocalLogging(it) },
modifier = modifier,
)
},
onClick = { navController.push(Route.Logs) },
)
}
}
}
@@ -1,7 +1,5 @@
package com.zaneschepke.wireguardautotunnel.ui.screens.tunnels.config.components
import android.R.attr.enabled
import android.R.attr.singleLine
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions
@@ -1,10 +1,6 @@
package com.zaneschepke.wireguardautotunnel.ui.screens.tunnels.config.components
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.*
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.RemoveRedEye
import androidx.compose.material.icons.rounded.Refresh
@@ -12,12 +12,8 @@ import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.*
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
import androidx.compose.ui.res.stringResource
@@ -1,11 +1,7 @@
package com.zaneschepke.wireguardautotunnel.ui.screens.tunnels.config.components
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.*
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.RemoveRedEye
import androidx.compose.material.icons.rounded.Delete
@@ -1,10 +1,12 @@
package com.zaneschepke.wireguardautotunnel.ui.state
import com.zaneschepke.wireguardautotunnel.data.model.AppMode
import com.zaneschepke.wireguardautotunnel.domain.model.MonitoringSettings
import com.zaneschepke.wireguardautotunnel.domain.model.TunnelConfig
data class MonitoringUiState(
val monitoringSettings: MonitoringSettings = MonitoringSettings(),
val tunnels: List<TunnelConfig> = emptyList(),
val appMode: AppMode = AppMode.VPN,
val isLoading: Boolean = true,
)
@@ -1,6 +1,7 @@
package com.zaneschepke.wireguardautotunnel.ui.state
import com.zaneschepke.wireguardautotunnel.domain.model.GeneralSettings
import com.zaneschepke.wireguardautotunnel.domain.model.MonitoringSettings
import com.zaneschepke.wireguardautotunnel.domain.model.TunnelConfig
data class SettingUiState(
@@ -13,4 +14,5 @@ data class SettingUiState(
val isLoading: Boolean = true,
val globalTunnelConfig: TunnelConfig? = null,
val tunnels: List<TunnelConfig> = emptyList(),
val monitoring: MonitoringSettings = MonitoringSettings(),
)
@@ -25,7 +25,7 @@ constructor(
MonitoringUiState(),
buildSettings = { repeatOnSubscribedStopTimeout = 5000L },
) {
combine(monitoringSettingsRepository.flow, tunnelRepository.flow) {
combine(monitoringSettingsRepository.flow, tunnelRepository.userTunnelsFlow) {
monitoringSettings,
tunnels ->
state.copy(
@@ -37,10 +37,6 @@ constructor(
.collect { reduce { it } }
}
fun setPingEnabled(to: Boolean) = intent {
monitoringSettingsRepository.upsert(state.monitoringSettings.copy(isPingEnabled = to))
}
fun setTunnelPingIntervalSeconds(to: Int) = intent {
monitoringSettingsRepository.upsert(
state.monitoringSettings.copy(tunnelPingIntervalSeconds = to)
@@ -63,10 +59,6 @@ constructor(
)
}
fun setLocalLogging(to: Boolean) = intent {
monitoringSettingsRepository.upsert(state.monitoringSettings.copy(isLocalLogsEnabled = to))
}
fun setPingTarget(tunnel: TunnelConfig, target: String?) = intent {
tunnelsRepository.save(tunnel.copy(pingTarget = target?.ifBlank { null }))
}
@@ -5,6 +5,7 @@ import com.zaneschepke.wireguardautotunnel.core.shortcut.ShortcutManager
import com.zaneschepke.wireguardautotunnel.domain.model.TunnelConfig
import com.zaneschepke.wireguardautotunnel.domain.repository.GeneralSettingRepository
import com.zaneschepke.wireguardautotunnel.domain.repository.GlobalEffectRepository
import com.zaneschepke.wireguardautotunnel.domain.repository.MonitoringSettingsRepository
import com.zaneschepke.wireguardautotunnel.domain.repository.TunnelRepository
import com.zaneschepke.wireguardautotunnel.domain.sideeffect.GlobalSideEffect
import com.zaneschepke.wireguardautotunnel.ui.state.SettingUiState
@@ -22,6 +23,7 @@ constructor(
private val settingsRepository: GeneralSettingRepository,
private val shortcutManager: ShortcutManager,
private val tunnelsRepository: TunnelRepository,
private val monitoringRepository: MonitoringSettingsRepository,
private val globalEffectRepository: GlobalEffectRepository,
) : ContainerHost<SettingUiState, Nothing>, ViewModel() {
@@ -35,7 +37,8 @@ constructor(
settingsRepository.flow,
tunnelsRepository.globalTunnelFlow,
tunnelsRepository.userTunnelsFlow,
) { settings, tunnel, tunnels ->
monitoringRepository.flow,
) { settings, tunnel, tunnels, monitoring ->
state.copy(
settings = settings,
remoteKey = settings.remoteKey,
@@ -43,6 +46,7 @@ constructor(
isPinLockEnabled = settings.isPinLockEnabled,
isLoading = false,
globalTunnelConfig = tunnel,
monitoring = monitoring,
tunnels = tunnels,
)
}
@@ -77,6 +81,14 @@ constructor(
tunnelsRepository.save(TunnelConfig.generateDefaultGlobalConfig())
}
fun setLocalLogging(to: Boolean) = intent {
monitoringRepository.upsert(state.monitoring.copy(isLocalLogsEnabled = to))
}
fun setPingEnabled(to: Boolean) = intent {
monitoringRepository.upsert(state.monitoring.copy(isPingEnabled = to))
}
fun setRemoteEnabled(to: Boolean) = intent {
settingsRepository.upsert(
state.settings.copy(
+2
View File
@@ -413,4 +413,6 @@
<string name="aovpn_description">Allows Android to start tunnels</string>
<string name="tunnel_boot_description">Start the default tunnel on boot</string>
<string name="remote_key">Remote key</string>
<string name="tunnel_mapping_description">Map tunnels as preferred by Wi-Fi name</string>
<string name="monitoring">Monitoring</string>
</resources>