From ff5331bea9b8d2604b7d48deae4b87795852503b Mon Sep 17 00:00:00 2001 From: Zane Schepke Date: Fri, 17 Oct 2025 03:43:41 -0400 Subject: [PATCH] refactor: remove nav ripple on mobile, improve key ui --- .../ui/navigation/components/BottomNavbar.kt | 55 ++++++++------- .../ui/screens/settings/SettingsScreen.kt | 10 +-- .../integrations/AndroidIntegrationsScreen.kt | 68 ++++++++++++------- app/src/main/res/values/strings.xml | 7 +- 4 files changed, 87 insertions(+), 53 deletions(-) diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/navigation/components/BottomNavbar.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/navigation/components/BottomNavbar.kt index b74f2240..a2e1a085 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/navigation/components/BottomNavbar.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/navigation/components/BottomNavbar.kt @@ -1,5 +1,7 @@ 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 @@ -7,11 +9,13 @@ import androidx.compose.foundation.layout.offset import androidx.compose.foundation.layout.size import androidx.compose.material3.* import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.Stable import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.unit.dp +import com.zaneschepke.wireguardautotunnel.ui.LocalIsAndroidTV import com.zaneschepke.wireguardautotunnel.ui.common.animations.AnimatedFloatIcon import com.zaneschepke.wireguardautotunnel.ui.navigation.Tab import com.zaneschepke.wireguardautotunnel.ui.theme.SilverTree @@ -19,6 +23,9 @@ import com.zaneschepke.wireguardautotunnel.ui.theme.SilverTree @OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterial3ExpressiveApi::class) @Composable fun BottomNavbar(isAutoTunnelActive: Boolean, currentTab: Tab, onTabSelected: (Tab) -> Unit) { + val isTv = LocalIsAndroidTV.current + val rippleTheme = LocalRippleConfiguration.current + val theme = remember { if (isTv) rippleTheme else null } FlexibleBottomAppBar( containerColor = MaterialTheme.colorScheme.surface, contentColor = MaterialTheme.colorScheme.onSurface, @@ -31,29 +38,31 @@ fun BottomNavbar(isAutoTunnelActive: Boolean, currentTab: Tab, onTabSelected: (T val hasBadge = tab == Tab.AUTOTUNNEL && isAutoTunnelActive val button = @Stable @Composable { - IconButton( - onClick = { onTabSelected(tab) }, - colors = - IconButtonDefaults.iconButtonColors( - containerColor = Color.Transparent, - contentColor = - if (isSelected) { - MaterialTheme.colorScheme.primary - } else { - MaterialTheme.colorScheme.onSurfaceVariant - }, - disabledContainerColor = Color.Transparent, - disabledContentColor = - MaterialTheme.colorScheme.onSurfaceVariant, - ), - interactionSource = interactionSource, - ) { - AnimatedFloatIcon( - activeIcon = tab.activeIcon, - inactiveIcon = tab.inactiveIcon, - isSelected = isSelected, - modifier = Modifier.size(24.dp), - ) + CompositionLocalProvider(LocalRippleConfiguration provides theme) { + IconButton( + onClick = { onTabSelected(tab) }, + colors = + IconButtonDefaults.iconButtonColors( + containerColor = Color.Transparent, + contentColor = + if (isSelected) { + MaterialTheme.colorScheme.primary + } else { + MaterialTheme.colorScheme.onSurfaceVariant + }, + disabledContainerColor = Color.Transparent, + disabledContentColor = + MaterialTheme.colorScheme.onSurfaceVariant, + ), + interactionSource = interactionSource, + ) { + AnimatedFloatIcon( + activeIcon = tab.activeIcon, + inactiveIcon = tab.inactiveIcon, + isSelected = isSelected, + modifier = Modifier.size(24.dp), + ) + } } } if (hasBadge) { diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/SettingsScreen.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/SettingsScreen.kt index f0605c82..91fdf93b 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/SettingsScreen.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/SettingsScreen.kt @@ -200,17 +200,17 @@ fun SettingsScreen(viewModel: SettingsViewModel = hiltViewModel()) { onClick = { navController.push(Route.ProxySettings) }, ) } + SurfaceRow( + leading = { Icon(Icons.Outlined.Android, null) }, + title = stringResource(R.string.android_integrations), + onClick = { navController.push(Route.AndroidIntegrations) }, + ) } Column { GroupLabel( stringResource(R.string.general), modifier = Modifier.padding(horizontal = 16.dp), ) - SurfaceRow( - leading = { Icon(Icons.Outlined.Android, null) }, - title = stringResource(R.string.android_integrations), - onClick = { navController.push(Route.AndroidIntegrations) }, - ) SurfaceRow( leading = { Icon(Icons.AutoMirrored.Outlined.ViewQuilt, contentDescription = null) diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/integrations/AndroidIntegrationsScreen.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/integrations/AndroidIntegrationsScreen.kt index 5ed06686..c0acceea 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/integrations/AndroidIntegrationsScreen.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/integrations/AndroidIntegrationsScreen.kt @@ -1,25 +1,19 @@ package com.zaneschepke.wireguardautotunnel.ui.screens.settings.integrations import androidx.compose.animation.AnimatedVisibility -import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.* import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.outlined.Launch import androidx.compose.material.icons.filled.AppShortcut import androidx.compose.material.icons.filled.SmartToy -import androidx.compose.material.icons.outlined.AdminPanelSettings -import androidx.compose.material.icons.outlined.Restore -import androidx.compose.material.icons.outlined.VpnLock +import androidx.compose.material.icons.outlined.* 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.getValue +import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext @@ -34,6 +28,7 @@ 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.ui.common.text.DescriptionText import com.zaneschepke.wireguardautotunnel.util.extensions.launchVpnSettings import com.zaneschepke.wireguardautotunnel.viewmodel.SettingsViewModel @@ -74,6 +69,7 @@ fun AndroidIntegrationsScreen(viewModel: SettingsViewModel = hiltViewModel()) { onClick = { viewModel.setAlwaysOnVpnEnabled(!settingsState.settings.isAlwaysOnVpnEnabled) }, + description = { DescriptionText(stringResource(R.string.aovpn_description)) }, ) } Column { @@ -95,6 +91,7 @@ fun AndroidIntegrationsScreen(viewModel: SettingsViewModel = hiltViewModel()) { !settingsState.settings.isRestoreOnBootEnabled ) }, + description = { DescriptionText(stringResource(R.string.tunnel_boot_description)) }, ) SurfaceRow( leading = { Icon(Icons.Filled.AppShortcut, contentDescription = null) }, @@ -117,25 +114,50 @@ fun AndroidIntegrationsScreen(viewModel: SettingsViewModel = hiltViewModel()) { onClick = { viewModel.setRemoteEnabled(it) }, ) }, - description = { - settingsState.remoteKey?.let { key -> - AnimatedVisibility(visible = settingsState.isRemoteEnabled) { + title = stringResource(R.string.enable_remote_app_control), + onClick = { viewModel.setRemoteEnabled(!settingsState.isRemoteEnabled) }, + ) + AnimatedVisibility(settingsState.isRemoteEnabled) { + settingsState.remoteKey?.let { key -> + var passwordProtected by remember { mutableStateOf(true) } + val keyText by + remember(passwordProtected) { + derivedStateOf { + if (passwordProtected) "•".repeat(key.length) else key + } + } + SurfaceRow( + leading = { Icon(Icons.Outlined.Key, contentDescription = null) }, + title = stringResource(R.string.remote_key), + description = { Text( - text = stringResource(R.string.remote_key_template, key), + text = keyText, style = MaterialTheme.typography.bodySmall.copy( color = MaterialTheme.colorScheme.outline ), - maxLines = 1, - overflow = TextOverflow.Ellipsis, - modifier = Modifier.clickable { clipboard.copy(key) }, + overflow = TextOverflow.Clip, ) - } - } - }, - title = stringResource(R.string.enable_remote_app_control), - onClick = { viewModel.setRemoteEnabled(!settingsState.isRemoteEnabled) }, - ) + }, + trailing = { + Row { + IconButton(onClick = { passwordProtected = !passwordProtected }) { + Icon( + Icons.Outlined.RemoveRedEye, + contentDescription = stringResource(R.string.show_password), + ) + } + IconButton(onClick = { clipboard.copy(key) }) { + Icon( + Icons.Outlined.ContentCopy, + contentDescription = stringResource(R.string.copy), + ) + } + } + }, + ) + } + } } } } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index fa0bb9f0..672788d3 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -101,7 +101,7 @@ if you are unsure how to proceed See the getting started guide - Restart on boot + Start on boot Permission Denied VPN system settings VPN connection permission has been denied. Please check the @@ -409,5 +409,8 @@ Auto-tunnel and tunnels must be disabled to perform this action. Dynamic DNS endpoint successfully updated. Reliability - Delay between rapid changes + Delay between rapid network changes + Allows Android to start tunnels + Start the default tunnel on boot + Remote key