From b0da2982d7f4c1211a0b5415fc35f1fdc38ed7ee Mon Sep 17 00:00:00 2001 From: Zane Schepke Date: Tue, 14 Oct 2025 02:56:48 -0400 Subject: [PATCH] fix: more ui fixes, move ping targets --- app/src/main/AndroidManifest.xml | 2 +- .../wireguardautotunnel/MainActivity.kt | 2 + .../service/tile/AutoTunnelControlTile.kt | 1 - .../core/service/tile/TunnelControlTile.kt | 1 - .../ui/common/SectionDivider.kt | 16 -- .../ui/common/button/SurfaceRow.kt | 6 +- .../SecureScreenFromRecording.kt | 2 +- .../ui/common/textbox/ConfigurationTextBox.kt | 6 +- .../ui/common/textbox/CustomTextField.kt | 21 ++- .../ui/navigation/Route.kt | 3 + .../currentBackStackEntryAsNavbarState.kt | 13 ++ .../components/TrustNetworksTextBox.kt | 2 +- .../preferred/PreferredTunnelScreen.kt | 28 +-- .../monitoring/TunnelMonitoringScreen.kt | 6 + .../monitoring/ping/PingTargetScreen.kt | 163 ++++++++++++++++++ .../settings/proxy/ProxySettingsScreen.kt | 2 +- .../system/AndroidIntegrationsScreen.kt | 2 +- .../ui/screens/tunnels/config/ConfigScreen.kt | 2 +- .../tunneloptions/TunnelOptionsScreen.kt | 92 +--------- .../ui/state/SettingUiState.kt | 1 + .../wireguardautotunnel/ui/theme/Theme.kt | 13 +- .../viewmodel/SettingsViewModel.kt | 10 +- app/src/main/res/values/strings.xml | 9 +- app/src/main/res/values/themes.xml | 2 + 24 files changed, 236 insertions(+), 169 deletions(-) delete mode 100644 app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/common/SectionDivider.kt rename app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/common/{ => security}/SecureScreenFromRecording.kt (92%) create mode 100644 app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/monitoring/ping/PingTargetScreen.kt diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index cb356406..991ec024 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -73,7 +73,7 @@ diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/MainActivity.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/MainActivity.kt index 1c44f360..ba9fd82b 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/MainActivity.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/MainActivity.kt @@ -71,6 +71,7 @@ import com.zaneschepke.wireguardautotunnel.ui.screens.settings.dns.DnsSettingsSc import com.zaneschepke.wireguardautotunnel.ui.screens.settings.globals.TunnelGlobalsScreen import com.zaneschepke.wireguardautotunnel.ui.screens.settings.monitoring.TunnelMonitoringScreen import com.zaneschepke.wireguardautotunnel.ui.screens.settings.monitoring.logs.LogsScreen +import com.zaneschepke.wireguardautotunnel.ui.screens.settings.monitoring.ping.PingTargetScreen import com.zaneschepke.wireguardautotunnel.ui.screens.settings.system.AndroidIntegrationsScreen import com.zaneschepke.wireguardautotunnel.ui.screens.support.SupportScreen import com.zaneschepke.wireguardautotunnel.ui.screens.support.donate.DonateScreen @@ -449,6 +450,7 @@ class MainActivity : AppCompatActivity() { entry { key -> PreferredTunnelScreen(key.tunnelNetwork) } + entry { PingTargetScreen() } }, ) } diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/core/service/tile/AutoTunnelControlTile.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/core/service/tile/AutoTunnelControlTile.kt index fcb4c026..592835d9 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/core/service/tile/AutoTunnelControlTile.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/core/service/tile/AutoTunnelControlTile.kt @@ -102,7 +102,6 @@ class AutoTunnelControlTile : TileService(), LifecycleOwner { return ret } - override val lifecycle: Lifecycle get() = lifecycleRegistry } diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/core/service/tile/TunnelControlTile.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/core/service/tile/TunnelControlTile.kt index 32901c1c..61e197ff 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/core/service/tile/TunnelControlTile.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/core/service/tile/TunnelControlTile.kt @@ -180,7 +180,6 @@ class TunnelControlTile : TileService(), LifecycleOwner { return ret } - override val lifecycle: Lifecycle get() = lifecycleRegistry } diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/common/SectionDivider.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/common/SectionDivider.kt deleted file mode 100644 index 60abe44c..00000000 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/common/SectionDivider.kt +++ /dev/null @@ -1,16 +0,0 @@ -package com.zaneschepke.wireguardautotunnel.ui.common - -import androidx.compose.foundation.layout.padding -import androidx.compose.material3.HorizontalDivider -import androidx.compose.material3.MaterialTheme -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import androidx.compose.ui.unit.dp - -@Composable -fun SectionDivider() { - HorizontalDivider( - color = MaterialTheme.colorScheme.outline.copy(0.30f), - modifier = Modifier.padding(horizontal = 12.dp), - ) -} diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/common/button/SurfaceRow.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/common/button/SurfaceRow.kt index fd70e8b7..c0ece604 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/common/button/SurfaceRow.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/common/button/SurfaceRow.kt @@ -76,8 +76,7 @@ fun SurfaceRow( ) { Row( modifier = - Modifier.weight(1f) - .focusRequester(mainFocusRequester) + Modifier.focusRequester(mainFocusRequester) .focusProperties { right = trailingFocusRequester } .run { if (onClick != null) { @@ -108,7 +107,7 @@ fun SurfaceRow( } } Column( - modifier = Modifier.padding(end = 16.dp), + modifier = Modifier.padding(end = 16.dp).weight(1f), verticalArrangement = Arrangement.spacedBy(4.dp), ) { Text( @@ -122,7 +121,6 @@ fun SurfaceRow( description() } } - Spacer(Modifier.weight(1f)) if (trailing != null) { trailing( Modifier.focusRequester(trailingFocusRequester).focusProperties { diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/common/SecureScreenFromRecording.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/common/security/SecureScreenFromRecording.kt similarity index 92% rename from app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/common/SecureScreenFromRecording.kt rename to app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/common/security/SecureScreenFromRecording.kt index 27c3fdd4..974ebefc 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/common/SecureScreenFromRecording.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/common/security/SecureScreenFromRecording.kt @@ -1,4 +1,4 @@ -package com.zaneschepke.wireguardautotunnel.ui.common +package com.zaneschepke.wireguardautotunnel.ui.common.security import android.view.WindowManager import androidx.compose.runtime.Composable diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/common/textbox/ConfigurationTextBox.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/common/textbox/ConfigurationTextBox.kt index 4be9fee9..4e3432bd 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/common/textbox/ConfigurationTextBox.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/common/textbox/ConfigurationTextBox.kt @@ -3,15 +3,12 @@ package com.zaneschepke.wireguardautotunnel.ui.common.textbox import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.imePadding import androidx.compose.foundation.text.KeyboardActions import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.focus.FocusRequester import androidx.compose.ui.focus.focusRequester @@ -46,8 +43,7 @@ fun ConfigurationTextBox( isError = isError, textStyle = MaterialTheme.typography.bodySmall.copy(color = MaterialTheme.colorScheme.onSurface), - modifier = - modifier.fillMaxWidth().height(48.dp).focusRequester(focusRequester).imePadding(), + modifier = modifier.fillMaxWidth().height(48.dp).focusRequester(focusRequester), value = value, visualTransformation = visualTransformation, singleLine = singleLine, diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/common/textbox/CustomTextField.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/common/textbox/CustomTextField.kt index 8c874e21..e561fb56 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/common/textbox/CustomTextField.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/common/textbox/CustomTextField.kt @@ -10,7 +10,12 @@ import androidx.compose.material3.MaterialTheme import androidx.compose.material3.OutlinedTextFieldDefaults import androidx.compose.material3.TextFieldDefaults import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier +import androidx.compose.ui.focus.onFocusChanged import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.text.TextStyle @@ -41,6 +46,11 @@ fun CustomTextField( interactionSource: MutableInteractionSource = MutableInteractionSource(), ) { val space = " " + var isFocused by remember { mutableStateOf(false) } + val cursorBrush = + if (isFocused) SolidColor(MaterialTheme.colorScheme.primary) + else SolidColor(Color.Transparent) + BasicTextField( value = value, textStyle = textStyle, @@ -48,8 +58,8 @@ fun CustomTextField( keyboardActions = keyboardActions, keyboardOptions = keyboardOptions, readOnly = readOnly, - cursorBrush = SolidColor(MaterialTheme.colorScheme.onSurface), - modifier = modifier, + cursorBrush = cursorBrush, + modifier = modifier.onFocusChanged { focusState -> isFocused = focusState.isFocused }, interactionSource = interactionSource, enabled = enabled, singleLine = singleLine, @@ -79,7 +89,7 @@ fun CustomTextField( focusedContainerColor = containerColor, unfocusedContainerColor = containerColor, focusedTextColor = MaterialTheme.colorScheme.onSurface, - cursorColor = MaterialTheme.colorScheme.onSurface, + cursorColor = MaterialTheme.colorScheme.primary, ), enabled = enabled, label = label, @@ -97,12 +107,13 @@ fun CustomTextField( errorContainerColor = containerColor, disabledLabelColor = MaterialTheme.colorScheme.onSurface, disabledContainerColor = containerColor, - focusedIndicatorColor = MaterialTheme.colorScheme.onSurface, + focusedIndicatorColor = MaterialTheme.colorScheme.primary, + unfocusedIndicatorColor = MaterialTheme.colorScheme.outline, focusedLabelColor = MaterialTheme.colorScheme.onSurface, focusedContainerColor = containerColor, unfocusedContainerColor = containerColor, focusedTextColor = MaterialTheme.colorScheme.onSurface, - cursorColor = MaterialTheme.colorScheme.onSurface, + cursorColor = MaterialTheme.colorScheme.primary, ), shape = RoundedCornerShape(8.dp), focusedBorderThickness = 0.5.dp, diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/navigation/Route.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/navigation/Route.kt index 985050f6..d09131e3 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/navigation/Route.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/navigation/Route.kt @@ -73,6 +73,8 @@ sealed class Route : NavKey { @Keep @Serializable data object Addresses : Route() @Keep @Serializable data class PreferredTunnel(val tunnelNetwork: TunnelNetwork) : Route() + + @Keep @Serializable data object PingTarget : Route() } @Serializable @@ -126,6 +128,7 @@ enum class Tab( Route.Appearance, Route.Language, Route.Display, + Route.PingTarget, Route.Logs -> SETTINGS is Route.Support, Route.License, diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/navigation/components/currentBackStackEntryAsNavbarState.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/navigation/components/currentBackStackEntryAsNavbarState.kt index de110c55..225ed998 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/navigation/components/currentBackStackEntryAsNavbarState.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/navigation/components/currentBackStackEntryAsNavbarState.kt @@ -542,6 +542,19 @@ fun currentRouteAsNavbarState( showBottomItems = true, ) } + PingTarget -> + NavbarState( + topLeading = { + IconButton(onClick = { navController.pop() }) { + Icon( + Icons.AutoMirrored.Rounded.ArrowBack, + stringResource(R.string.back), + ) + } + }, + topTitle = context.getString(R.string.ping_target), + showBottomItems = true, + ) null -> NavbarState() } } diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/autotunnel/components/TrustNetworksTextBox.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/autotunnel/components/TrustNetworksTextBox.kt index 87bf117c..01573802 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/autotunnel/components/TrustNetworksTextBox.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/autotunnel/components/TrustNetworksTextBox.kt @@ -45,7 +45,7 @@ fun TrustedNetworkTextBox( Column( verticalArrangement = Arrangement.spacedBy(10.dp), - modifier = modifier.animateContentSize().imePadding(), + modifier = modifier.animateContentSize(), ) { FlowRow( modifier = Modifier.fillMaxWidth().animateContentSize(), diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/autotunnel/preferred/PreferredTunnelScreen.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/autotunnel/preferred/PreferredTunnelScreen.kt index 51d00f82..a2dea70c 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/autotunnel/preferred/PreferredTunnelScreen.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/autotunnel/preferred/PreferredTunnelScreen.kt @@ -2,18 +2,11 @@ package com.zaneschepke.wireguardautotunnel.ui.screens.autotunnel.preferred import androidx.compose.foundation.gestures.ScrollableDefaults import androidx.compose.foundation.interaction.MutableInteractionSource -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.FlowRow -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.imePadding -import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.* import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.foundation.overscroll -import androidx.compose.foundation.relocation.BringIntoViewRequester -import androidx.compose.foundation.relocation.bringIntoViewRequester import androidx.compose.foundation.rememberOverscrollEffect import androidx.compose.foundation.text.KeyboardActions import androidx.compose.foundation.text.KeyboardOptions @@ -25,12 +18,7 @@ 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.remember -import androidx.compose.runtime.setValue +import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.focus.FocusRequester @@ -182,17 +170,10 @@ fun PreferredTunnelScreen( } if (selectedTunnel?.id == tunnel.id) { val focusRequester = remember { FocusRequester() } - val bringIntoViewRequester = remember { - BringIntoViewRequester() - } val keyboardController = LocalSoftwareKeyboardController.current var currentText by remember { mutableStateOf("") } - LaunchedEffect(tunnel.tunnelNetworks.size) { - bringIntoViewRequester.bringIntoView() - } - LaunchedEffect(Unit) { focusRequester.requestFocus() keyboardController?.show() @@ -219,10 +200,7 @@ fun PreferredTunnelScreen( { WildcardsLabel() } } else null, modifier = - Modifier.fillMaxWidth() - .focusRequester(focusRequester) - .bringIntoViewRequester(bringIntoViewRequester) - .imePadding(), + Modifier.fillMaxWidth().focusRequester(focusRequester), singleLine = true, keyboardOptions = KeyboardOptions( diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/monitoring/TunnelMonitoringScreen.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/monitoring/TunnelMonitoringScreen.kt index 2aa94154..9f7bca75 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/monitoring/TunnelMonitoringScreen.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/monitoring/TunnelMonitoringScreen.kt @@ -7,6 +7,7 @@ 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.outlined.Adjust import androidx.compose.material.icons.outlined.NetworkPing import androidx.compose.material.icons.outlined.QueryStats import androidx.compose.material.icons.outlined.Replay @@ -110,6 +111,11 @@ fun TunnelMonitoringScreen(viewModel: SettingsViewModel = hiltViewModel()) { }, onClick = { viewModel.setDetailedPingStats(!settingsState.showDetailedPingStats) }, ) + SurfaceRow( + leading = { Icon(Icons.Outlined.Adjust, contentDescription = null) }, + title = stringResource(R.string.set_custom_ping_target), + onClick = { navController.push(Route.PingTarget) }, + ) } Column { GroupLabel( diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/monitoring/ping/PingTargetScreen.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/monitoring/ping/PingTargetScreen.kt new file mode 100644 index 00000000..203943d8 --- /dev/null +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/monitoring/ping/PingTargetScreen.kt @@ -0,0 +1,163 @@ +package com.zaneschepke.wireguardautotunnel.ui.screens.settings.monitoring.ping + +import android.util.Patterns +import androidx.compose.foundation.gestures.ScrollableDefaults +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.lazy.rememberLazyListState +import androidx.compose.foundation.overscroll +import androidx.compose.foundation.rememberOverscrollEffect +import androidx.compose.foundation.text.KeyboardActions +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.outlined.Save +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.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.focus.FocusRequester +import androidx.compose.ui.focus.focusRequester +import androidx.compose.ui.platform.LocalSoftwareKeyboardController +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.input.ImeAction +import androidx.compose.ui.text.input.KeyboardCapitalization +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.domain.model.TunnelConf +import com.zaneschepke.wireguardautotunnel.ui.common.button.SurfaceRow +import com.zaneschepke.wireguardautotunnel.ui.common.label.GroupLabel +import com.zaneschepke.wireguardautotunnel.ui.common.text.DescriptionText +import com.zaneschepke.wireguardautotunnel.ui.common.textbox.CustomTextField +import com.zaneschepke.wireguardautotunnel.util.extensions.isValidIpv4orIpv6Address +import com.zaneschepke.wireguardautotunnel.viewmodel.SettingsViewModel + +@Composable +fun PingTargetScreen(viewModel: SettingsViewModel = hiltViewModel()) { + + val settingsState by viewModel.container.stateFlow.collectAsStateWithLifecycle() + + if (settingsState.isLoading) return + + var selectedTunnel by remember { mutableStateOf(null) } + + var isError by remember { mutableStateOf(false) } + + LazyColumn( + horizontalAlignment = Alignment.Start, + verticalArrangement = Arrangement.Top, + modifier = Modifier.overscroll(rememberOverscrollEffect()), + state = rememberLazyListState(), + userScrollEnabled = true, + reverseLayout = false, + flingBehavior = ScrollableDefaults.flingBehavior(), + ) { + item { + DescriptionText( + stringResource(R.string.ping_target_description), + modifier = Modifier.padding(horizontal = 16.dp).padding(bottom = 16.dp), + ) + GroupLabel( + stringResource(R.string.tunnels), + modifier = Modifier.padding(horizontal = 16.dp), + ) + } + items(settingsState.tunnels, key = { it.id }) { tunnel -> + SurfaceRow( + title = tunnel.tunName, + onClick = { selectedTunnel = tunnel }, + description = { + DescriptionText( + stringResource( + R.string.ping_target_template, + tunnel.pingTarget ?: stringResource(R.string._default), + ) + ) + }, + expandedContent = { + if (selectedTunnel?.id == tunnel.id) { + val focusRequester = remember { FocusRequester() } + val keyboardController = LocalSoftwareKeyboardController.current + + var currentText by remember { mutableStateOf(tunnel.pingTarget ?: "") } + var isError by remember { mutableStateOf(false) } + + LaunchedEffect(currentText) { isError = false } + + LaunchedEffect(Unit) { + focusRequester.requestFocus() + keyboardController?.show() + } + + fun onClick() { + val isValid = + currentText.isBlank() || + currentText.isValidIpv4orIpv6Address() || + Patterns.DOMAIN_NAME.matcher(currentText).matches() + if (!isValid) { + isError = true + return + } + viewModel.setPingTarget(tunnel, currentText) + selectedTunnel = null + } + + CustomTextField( + isError = isError, + textStyle = + MaterialTheme.typography.bodySmall.copy( + color = MaterialTheme.colorScheme.onSurface + ), + value = currentText, + onValueChange = { currentText = it }, + interactionSource = remember { MutableInteractionSource() }, + label = { + Text( + stringResource(R.string.set_ping_target), + color = MaterialTheme.colorScheme.onSurface, + style = MaterialTheme.typography.labelMedium, + ) + }, + containerColor = MaterialTheme.colorScheme.surface, + modifier = + Modifier.fillMaxWidth() + .focusRequester(focusRequester) + .padding(top = 8.dp), + singleLine = true, + keyboardOptions = + KeyboardOptions( + capitalization = KeyboardCapitalization.None, + imeAction = ImeAction.Done, + ), + keyboardActions = KeyboardActions(onDone = { onClick() }), + trailing = { + if (currentText != "") { + IconButton(onClick = { onClick() }) { + Icon( + imageVector = Icons.Outlined.Save, + contentDescription = stringResource(R.string.save), + tint = MaterialTheme.colorScheme.primary, + ) + } + } + }, + ) + } + }, + ) + } + } +} diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/proxy/ProxySettingsScreen.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/proxy/ProxySettingsScreen.kt index 3ef92473..682de699 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/proxy/ProxySettingsScreen.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/proxy/ProxySettingsScreen.kt @@ -24,10 +24,10 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.zaneschepke.wireguardautotunnel.R import com.zaneschepke.wireguardautotunnel.domain.model.AppProxySettings import com.zaneschepke.wireguardautotunnel.ui.LocalSharedVm -import com.zaneschepke.wireguardautotunnel.ui.common.SecureScreenFromRecording import com.zaneschepke.wireguardautotunnel.ui.common.button.ScaledSwitch import com.zaneschepke.wireguardautotunnel.ui.common.button.SurfaceRow import com.zaneschepke.wireguardautotunnel.ui.common.label.GroupLabel +import com.zaneschepke.wireguardautotunnel.ui.common.security.SecureScreenFromRecording import com.zaneschepke.wireguardautotunnel.ui.common.textbox.ConfigurationTextBox import com.zaneschepke.wireguardautotunnel.ui.sideeffect.LocalSideEffect import com.zaneschepke.wireguardautotunnel.viewmodel.ProxySettingsViewModel diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/system/AndroidIntegrationsScreen.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/system/AndroidIntegrationsScreen.kt index ecbe2b65..edb9720e 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/system/AndroidIntegrationsScreen.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/system/AndroidIntegrationsScreen.kt @@ -29,11 +29,11 @@ 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.common.SecureScreenFromRecording import com.zaneschepke.wireguardautotunnel.ui.common.button.ScaledSwitch import com.zaneschepke.wireguardautotunnel.ui.common.button.SurfaceRow import com.zaneschepke.wireguardautotunnel.ui.common.functions.rememberClipboardHelper import com.zaneschepke.wireguardautotunnel.ui.common.label.GroupLabel +import com.zaneschepke.wireguardautotunnel.ui.common.security.SecureScreenFromRecording import com.zaneschepke.wireguardautotunnel.util.extensions.launchVpnSettings import com.zaneschepke.wireguardautotunnel.viewmodel.SettingsViewModel diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/tunnels/config/ConfigScreen.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/tunnels/config/ConfigScreen.kt index 66d0060e..c0501985 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/tunnels/config/ConfigScreen.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/tunnels/config/ConfigScreen.kt @@ -14,7 +14,7 @@ import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.zaneschepke.wireguardautotunnel.data.entity.TunnelConfig import com.zaneschepke.wireguardautotunnel.ui.LocalSharedVm -import com.zaneschepke.wireguardautotunnel.ui.common.SecureScreenFromRecording +import com.zaneschepke.wireguardautotunnel.ui.common.security.SecureScreenFromRecording import com.zaneschepke.wireguardautotunnel.ui.screens.tunnels.config.components.AddPeerButton import com.zaneschepke.wireguardautotunnel.ui.screens.tunnels.config.components.InterfaceSection import com.zaneschepke.wireguardautotunnel.ui.screens.tunnels.config.components.PeersSection diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/tunnels/tunneloptions/TunnelOptionsScreen.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/tunnels/tunneloptions/TunnelOptionsScreen.kt index 74389125..fe3ece5f 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/tunnels/tunneloptions/TunnelOptionsScreen.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/tunnels/tunneloptions/TunnelOptionsScreen.kt @@ -1,23 +1,16 @@ package com.zaneschepke.wireguardautotunnel.ui.screens.tunnels.tunneloptions -import android.util.Patterns -import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.rememberScrollState -import androidx.compose.foundation.text.KeyboardActions -import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.outlined.CallSplit import androidx.compose.material.icons.outlined.Dns -import androidx.compose.material.icons.outlined.Save import androidx.compose.material.icons.outlined.Star import androidx.compose.material3.Icon -import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.* @@ -25,12 +18,8 @@ import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.vector.ImageVector -import androidx.compose.ui.platform.LocalFocusManager -import androidx.compose.ui.platform.LocalSoftwareKeyboardController import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.vectorResource -import androidx.compose.ui.text.input.ImeAction -import androidx.compose.ui.text.input.KeyboardCapitalization import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.zaneschepke.wireguardautotunnel.R @@ -40,11 +29,9 @@ import com.zaneschepke.wireguardautotunnel.ui.common.button.ScaledSwitch import com.zaneschepke.wireguardautotunnel.ui.common.button.SurfaceRow import com.zaneschepke.wireguardautotunnel.ui.common.label.GroupLabel import com.zaneschepke.wireguardautotunnel.ui.common.text.DescriptionText -import com.zaneschepke.wireguardautotunnel.ui.common.textbox.CustomTextField import com.zaneschepke.wireguardautotunnel.ui.navigation.Route import com.zaneschepke.wireguardautotunnel.ui.screens.tunnels.tunneloptions.components.* import com.zaneschepke.wireguardautotunnel.ui.sideeffect.LocalSideEffect -import com.zaneschepke.wireguardautotunnel.util.extensions.isValidIpv4orIpv6Address import com.zaneschepke.wireguardautotunnel.viewmodel.TunnelViewModel import org.orbitmvi.orbit.compose.collectSideEffect @@ -108,7 +95,7 @@ fun TunnelOptionsScreen(viewModel: TunnelViewModel) { } Column { GroupLabel( - stringResource(R.string.reliability), + stringResource(R.string.other), modifier = Modifier.padding(horizontal = 16.dp), ) SurfaceRow( @@ -138,83 +125,6 @@ fun TunnelOptionsScreen(viewModel: TunnelViewModel) { }, onClick = { viewModel.toggleIpv4Preferred() }, ) - SurfaceRow( - title = "", - description = { - val focusManager = LocalFocusManager.current - val keyboardController = LocalSoftwareKeyboardController.current - - var stateValue by remember { mutableStateOf(tunnel.pingTarget ?: "") } - val isError by - remember(stateValue) { - derivedStateOf { - stateValue.isNotBlank() && - !stateValue.isValidIpv4orIpv6Address() && - !Patterns.DOMAIN_NAME.matcher(stateValue).matches() - } - } - Column(verticalArrangement = Arrangement.spacedBy(16.dp)) { - CustomTextField( - isError = isError, - textStyle = - MaterialTheme.typography.bodySmall.copy( - color = MaterialTheme.colorScheme.onSurface - ), - value = stateValue, - onValueChange = { stateValue = it }, - interactionSource = remember { MutableInteractionSource() }, - label = { - Text( - stringResource(R.string.set_custom_ping_target), - color = MaterialTheme.colorScheme.onSurface, - style = MaterialTheme.typography.labelMedium, - ) - }, - placeholder = { - Text( - stringResource(R.string.ip_or_hostname), - style = MaterialTheme.typography.bodySmall, - color = MaterialTheme.colorScheme.outline, - ) - }, - containerColor = MaterialTheme.colorScheme.surface, - supportingText = { - Text(stringResource(R.string.ping_target_description)) - }, - modifier = - Modifier.padding(top = 5.dp, bottom = 10.dp) - .fillMaxWidth() - .padding(end = 16.dp), - singleLine = true, - keyboardOptions = - KeyboardOptions( - capitalization = KeyboardCapitalization.None, - imeAction = ImeAction.Done, - ), - keyboardActions = - KeyboardActions(onDone = { viewModel.setTunnelPingIp(stateValue) }), - trailing = { - if (!isError) { - IconButton( - onClick = { - viewModel.setTunnelPingIp(stateValue) - keyboardController?.hide() - focusManager.clearFocus() - } - ) { - val icon = Icons.Outlined.Save - Icon( - imageVector = icon, - contentDescription = icon.name, - tint = MaterialTheme.colorScheme.primary, - ) - } - } - }, - ) - } - }, - ) } } } diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/state/SettingUiState.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/state/SettingUiState.kt index 77043722..760f9f0c 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/state/SettingUiState.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/state/SettingUiState.kt @@ -12,4 +12,5 @@ data class SettingUiState( val showDetailedPingStats: Boolean = false, val isLoading: Boolean = true, val globalTunnelConf: TunnelConf? = null, + val tunnels: List = emptyList(), ) diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/theme/Theme.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/theme/Theme.kt index 9a43f3bf..d0a30168 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/theme/Theme.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/theme/Theme.kt @@ -10,10 +10,10 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.SideEffect import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.toArgb import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalView import androidx.core.view.WindowCompat +import androidx.core.view.WindowInsetsControllerCompat import com.zaneschepke.wireguardautotunnel.ui.LocalIsAndroidTV private val DarkColorScheme = @@ -94,16 +94,13 @@ fun WireguardAutoTunnelTheme(theme: Theme = Theme.AUTOMATIC, content: @Composabl val view = LocalView.current if (!view.isInEditMode) { - @Suppress("DEPRECATION") SideEffect { val window = (view.context as Activity).window WindowCompat.setDecorFitsSystemWindows(window, false) - window.navigationBarColor = Color.Transparent.toArgb() - window.statusBarColor = Color.Transparent.toArgb() - WindowCompat.getInsetsController(window, window.decorView).apply { - isAppearanceLightStatusBars = !isDark - isAppearanceLightNavigationBars = !isDark - } + // For API 33+, use WindowInsetsControllerCompat for appearance control + val insetsController = WindowInsetsControllerCompat(window, window.decorView) + insetsController.isAppearanceLightStatusBars = !isDark + insetsController.isAppearanceLightNavigationBars = !isDark } } diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/viewmodel/SettingsViewModel.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/viewmodel/SettingsViewModel.kt index b7212a10..02698e0a 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/viewmodel/SettingsViewModel.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/viewmodel/SettingsViewModel.kt @@ -39,8 +39,9 @@ constructor( settingsRepository.flow, appStateRepository.flow, tunnelsRepository.globalTunnelFlow, - ) { settings, appState, tunnel -> - SettingUiState( + tunnelsRepository.userTunnelsFlow, + ) { settings, appState, tunnel, tunnels -> + state.copy( settings = settings, isLocalLoggingEnabled = appState.isLocalLogsEnabled, remoteKey = appState.remoteKey, @@ -49,6 +50,7 @@ constructor( showDetailedPingStats = appState.showDetailedPingStats, isLoading = false, globalTunnelConf = tunnel, + tunnels = tunnels, ) } .collect { reduce { it } } @@ -113,6 +115,10 @@ constructor( appStateRepository.setIsRemoteControlEnabled(to) } + fun setPingTarget(tunnel: TunnelConf, target: String?) = intent { + tunnelsRepository.save(tunnel.copy(pingTarget = target?.ifBlank { null })) + } + fun setDnsProvider(dnsProvider: DnsProvider) = intent { settingsRepository.save( state.settings.copy(dnsEndpoint = dnsProvider.asAddress(state.settings.dnsProtocol)) diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 6de2681a..c0e2217c 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -272,11 +272,9 @@ Pinger restart failed. Tunnel recovered with old addresses. Pinger failed to recovery tunnel with error: %1$s Tunnel monitoring - Custom ping target (optional) + Custom ping targets IP or hostname - Used if no internal IP is available (e.g., in full-tunnel configs). Leaving - blank defaults to Cloudflare. - + Defaults to an internal tunnel IP for split configurations or Cloudflare for full tunnel configurations. Tunnel ping interval Ping attempts per interval Tunnel ping timeout @@ -411,5 +409,6 @@ Ping Ping monitor Backup and restore - Reliability + Ping target + Set ping target diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml index dd6003c2..c1f21057 100644 --- a/app/src/main/res/values/themes.xml +++ b/app/src/main/res/values/themes.xml @@ -8,6 +8,8 @@ true @android:color/transparent @android:color/transparent + true + true