From 71461f4d0bd366b67fc0e44d795793225de13b0e Mon Sep 17 00:00:00 2001 From: Zane Schepke Date: Sat, 11 Oct 2025 20:43:43 -0400 Subject: [PATCH] refactor: ui improvements --- .../wireguardautotunnel/MainActivity.kt | 31 +- .../ui/common/button/ActionIconButton.kt | 21 - .../ui/common/button/ClickableIconButton.kt | 9 +- .../ui/common/button/ForwardButton.kt | 19 - .../ui/common/button/IconSurfaceButton.kt | 8 +- .../ui/common/button/LaunchButton.kt | 19 - .../ui/common/button/LinkIconButton.kt | 19 - .../ui/common/button/ScaledSwitch.kt | 4 +- .../ui/common/button/SelectionItemButton.kt | 73 --- .../ui/common/button/SurfaceRow.kt | 167 +++++++ .../ui/common/button/SwitchWithDivider.kt | 44 ++ .../ui/common/button/surface/SelectionItem.kt | 20 - .../button/surface/SelectionItemLabel.kt | 28 -- .../surface/SurfaceSelectionGroupButton.kt | 117 ----- .../ui/common/dropdown/DropdownSelector.kt | 52 ++- .../common/dropdown/LabelledNumberDropdown.kt | 37 +- .../ui/common/label/GroupLabel.kt | 9 +- .../ui/common/text/DescriptionText.kt | 15 + .../ui/common/textbox/ConfigurationTextBox.kt | 11 +- .../ui/navigation/NavController.kt | 4 + .../ui/navigation/Route.kt | 23 +- .../currentBackStackEntryAsNavbarState.kt | 419 ++++++++++++------ .../functions/rememberNavController.kt | 5 +- .../ui/screens/autotunnel/AutoTunnelScreen.kt | 285 ++++++++---- .../advanced/AutoTunnelAdvancedScreen.kt | 16 +- .../components/AdvancedSettingsItem.kt | 28 -- .../components/NetworkTunnelingItems.kt | 133 ------ .../components/TrustNetworksTextBox.kt | 29 +- .../components/wifiTunnelingItems.kt | 218 --------- .../disclosure/LocationDisclosureScreen.kt | 49 +- .../disclosure/components/AppSettingsItem.kt | 27 -- .../components/LocationDisclosureHeader.kt | 45 +- .../disclosure/components/SkipItem.kt | 20 - .../preferences/PreferredTunnelScreen.kt | 271 +++++++++++ .../preferences/wifi/WifiSettingsScreen.kt | 117 +++++ .../ui/screens/settings/SettingsScreen.kt | 265 ++++++----- .../settings/appearance/AppearanceScreen.kt | 48 +- .../appearance/components/DisplayItem.kt | 31 -- .../appearance/components/LanguageItem.kt | 31 -- .../appearance/components/NotificationItem.kt | 31 -- .../appearance/display/DisplayScreen.kt | 25 +- .../appearance/language/LanguageScreen.kt | 42 +- .../components/AutomaticLanguageItem.kt | 30 -- .../language/components/LanguageItem.kt | 32 -- .../settings/components/LanTrafficItem.kt | 37 -- .../settings/components/LocalLoggingItem.kt | 33 -- .../components/LocationServicesDialog.kt | 34 -- .../settings/components/PinLockItem.kt | 29 -- .../settings/components/appModeItem.kt | 47 -- .../settings/components/appearanceItem.kt | 28 -- .../settings/components/dnsSettingsItem.kt | 43 -- .../settings/components/proxySettingsItem.kt | 28 -- .../settings/components/readLogsItem.kt | 24 - .../settings/components/systemFeaturesItem.kt | 28 -- .../components/tunnelGlobalsSettingItem.kt | 33 -- .../components/tunnelMonitoringItem.kt | 47 -- .../screens/settings/dns/DnsSettingsScreen.kt | 60 +-- .../settings/globals/TunnelGlobalsScreen.kt | 35 +- .../globals/components/globalConfigItem.kt | 24 - .../monitoring/TunnelMonitoringScreen.kt | 104 +++-- .../components/detailedPingStatsItem.kt | 31 -- .../components/enablePingMonitoringItem.kt | 31 -- .../settings/proxy/ProxySettingsScreen.kt | 123 +++-- .../system/AndroidIntegrationsScreen.kt | 124 ++++++ .../settings/system/SystemFeaturesScreen.kt | 70 --- .../system/components/alwaysOnVpnItem.kt | 38 -- .../system/components/appShortcutItem.kt | 28 -- .../system/components/nativeKillSwitchItem.kt | 31 -- .../system/components/remoteControlItem.kt | 55 --- .../system/components/restartAtBootItem.kt | 28 -- .../ui/screens/support/SupportScreen.kt | 265 ++++++----- .../components/ContactSupportOptions.kt | 99 ----- .../support/components/DonateSection.kt | 29 -- .../components/GeneralSupportOptions.kt | 80 ---- .../support/components/PermissionDialog.kt | 23 + .../support/components/UpdateDialog.kt | 100 +++++ .../support/components/UpdateSection.kt | 52 --- .../ui/screens/support/donate/DonateScreen.kt | 81 +++- .../donate/components/DonationHeroSection.kt | 3 +- .../donate/components/DonationOptions.kt | 97 ---- .../components/GoogleDonationMessage.kt | 33 +- .../support/donate/crypto/AddressesScreen.kt | 14 +- .../donate/crypto/components/addressItem.kt | 14 +- .../screens/support/license/LicenseScreen.kt | 32 +- .../support/license/components/LicenseList.kt | 22 +- .../autotunnel/TunnelAutoTunnelScreen.kt | 58 --- .../components/MobileDataTunnelItem.kt | 34 -- .../autotunnel/components/WifiTunnelItem.kt | 70 --- .../components/ethernetTunnelItem.kt | 34 -- .../screens/tunnels/components/TunnelList.kt | 96 ++-- .../tunnels/components/TunnelStatisticsRow.kt | 6 +- .../ui/screens/tunnels/config/ConfigScreen.kt | 1 - .../config/components/InterfaceDropdown.kt | 4 +- .../tunnels/config/components/PeersSection.kt | 11 +- .../splittunnel/components/AppListItem.kt | 21 +- .../splittunnel/components/AppListSection.kt | 2 +- .../components/SplitOptionsSelector.kt | 2 + .../components/SplitTunnelContent.kt | 5 +- .../tunneloptions/TunnelOptionsScreen.kt | 189 ++++++-- .../components/autoTunnelingItem.kt | 40 -- .../components/dynamicDnsItem.kt | 33 -- .../components/pingConfigItem.kt | 112 ----- .../components/preferIpv6Item.kt | 28 -- .../components/primaryTunnelItem.kt | 35 -- .../components/splitTunnelingItem.kt | 21 - .../ui/state/AutoTunnelUiState.kt | 4 +- .../ui/state/LicenseUiState.kt | 8 + .../ui/state/SettingUiState.kt | 2 +- .../wireguardautotunnel/ui/theme/Size.kt | 2 - .../wireguardautotunnel/ui/theme/Theme.kt | 28 +- .../wireguardautotunnel/util/FileUtils.kt | 11 + .../util/extensions/ContextExtensions.kt | 42 ++ .../util/extensions/StringExtensions.kt | 5 + .../viewmodel/AutoTunnelViewModel.kt | 33 +- .../viewmodel/LicenseViewModel.kt | 26 ++ .../viewmodel/SettingsViewModel.kt | 2 +- .../viewmodel/SplitTunnelViewModel.kt | 6 +- .../viewmodel/SupportViewModel.kt | 8 +- .../viewmodel/TunnelViewModel.kt | 16 - app/src/main/res/values/strings.xml | 31 +- 120 files changed, 2623 insertions(+), 3467 deletions(-) delete mode 100644 app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/common/button/ActionIconButton.kt delete mode 100644 app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/common/button/ForwardButton.kt delete mode 100644 app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/common/button/LaunchButton.kt delete mode 100644 app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/common/button/LinkIconButton.kt delete mode 100644 app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/common/button/SelectionItemButton.kt create mode 100644 app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/common/button/SurfaceRow.kt create mode 100644 app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/common/button/SwitchWithDivider.kt delete mode 100644 app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/common/button/surface/SelectionItem.kt delete mode 100644 app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/common/button/surface/SelectionItemLabel.kt delete mode 100644 app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/common/button/surface/SurfaceSelectionGroupButton.kt create mode 100644 app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/common/text/DescriptionText.kt delete mode 100644 app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/autotunnel/components/AdvancedSettingsItem.kt delete mode 100644 app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/autotunnel/components/NetworkTunnelingItems.kt delete mode 100644 app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/autotunnel/components/wifiTunnelingItems.kt delete mode 100644 app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/autotunnel/disclosure/components/AppSettingsItem.kt delete mode 100644 app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/autotunnel/disclosure/components/SkipItem.kt create mode 100644 app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/autotunnel/preferences/PreferredTunnelScreen.kt create mode 100644 app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/autotunnel/preferences/wifi/WifiSettingsScreen.kt delete mode 100644 app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/appearance/components/DisplayItem.kt delete mode 100644 app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/appearance/components/LanguageItem.kt delete mode 100644 app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/appearance/components/NotificationItem.kt delete mode 100644 app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/appearance/language/components/AutomaticLanguageItem.kt delete mode 100644 app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/appearance/language/components/LanguageItem.kt delete mode 100644 app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/components/LanTrafficItem.kt delete mode 100644 app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/components/LocalLoggingItem.kt delete mode 100644 app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/components/LocationServicesDialog.kt delete mode 100644 app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/components/PinLockItem.kt delete mode 100644 app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/components/appModeItem.kt delete mode 100644 app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/components/appearanceItem.kt delete mode 100644 app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/components/dnsSettingsItem.kt delete mode 100644 app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/components/proxySettingsItem.kt delete mode 100644 app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/components/readLogsItem.kt delete mode 100644 app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/components/systemFeaturesItem.kt delete mode 100644 app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/components/tunnelGlobalsSettingItem.kt delete mode 100644 app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/components/tunnelMonitoringItem.kt delete mode 100644 app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/globals/components/globalConfigItem.kt delete mode 100644 app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/monitoring/components/detailedPingStatsItem.kt delete mode 100644 app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/monitoring/components/enablePingMonitoringItem.kt create mode 100644 app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/system/AndroidIntegrationsScreen.kt delete mode 100644 app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/system/SystemFeaturesScreen.kt delete mode 100644 app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/system/components/alwaysOnVpnItem.kt delete mode 100644 app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/system/components/appShortcutItem.kt delete mode 100644 app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/system/components/nativeKillSwitchItem.kt delete mode 100644 app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/system/components/remoteControlItem.kt delete mode 100644 app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/system/components/restartAtBootItem.kt delete mode 100644 app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/support/components/ContactSupportOptions.kt delete mode 100644 app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/support/components/DonateSection.kt delete mode 100644 app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/support/components/GeneralSupportOptions.kt create mode 100644 app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/support/components/PermissionDialog.kt create mode 100644 app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/support/components/UpdateDialog.kt delete mode 100644 app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/support/components/UpdateSection.kt delete mode 100644 app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/support/donate/components/DonationOptions.kt delete mode 100644 app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/tunnels/autotunnel/TunnelAutoTunnelScreen.kt delete mode 100644 app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/tunnels/autotunnel/components/MobileDataTunnelItem.kt delete mode 100644 app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/tunnels/autotunnel/components/WifiTunnelItem.kt delete mode 100644 app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/tunnels/autotunnel/components/ethernetTunnelItem.kt delete mode 100644 app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/tunnels/tunneloptions/components/autoTunnelingItem.kt delete mode 100644 app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/tunnels/tunneloptions/components/dynamicDnsItem.kt delete mode 100644 app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/tunnels/tunneloptions/components/pingConfigItem.kt delete mode 100644 app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/tunnels/tunneloptions/components/preferIpv6Item.kt delete mode 100644 app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/tunnels/tunneloptions/components/primaryTunnelItem.kt delete mode 100644 app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/tunnels/tunneloptions/components/splitTunnelingItem.kt create mode 100644 app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/state/LicenseUiState.kt create mode 100644 app/src/main/java/com/zaneschepke/wireguardautotunnel/viewmodel/LicenseViewModel.kt diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/MainActivity.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/MainActivity.kt index 5bf1f29a..2ba713da 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/MainActivity.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/MainActivity.kt @@ -61,6 +61,8 @@ import com.zaneschepke.wireguardautotunnel.ui.screens.autotunnel.AutoTunnelScree import com.zaneschepke.wireguardautotunnel.ui.screens.autotunnel.advanced.AutoTunnelAdvancedScreen import com.zaneschepke.wireguardautotunnel.ui.screens.autotunnel.detection.WifiDetectionMethodScreen import com.zaneschepke.wireguardautotunnel.ui.screens.autotunnel.disclosure.LocationDisclosureScreen +import com.zaneschepke.wireguardautotunnel.ui.screens.autotunnel.preferences.PreferredTunnelScreen +import com.zaneschepke.wireguardautotunnel.ui.screens.autotunnel.preferences.wifi.WifiSettingsScreen import com.zaneschepke.wireguardautotunnel.ui.screens.pin.PinLockScreen import com.zaneschepke.wireguardautotunnel.ui.screens.settings.SettingsScreen import com.zaneschepke.wireguardautotunnel.ui.screens.settings.appearance.AppearanceScreen @@ -70,13 +72,12 @@ 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.logs.LogsScreen import com.zaneschepke.wireguardautotunnel.ui.screens.settings.monitoring.TunnelMonitoringScreen -import com.zaneschepke.wireguardautotunnel.ui.screens.settings.system.SystemFeaturesScreen +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 import com.zaneschepke.wireguardautotunnel.ui.screens.support.donate.crypto.AddressesScreen import com.zaneschepke.wireguardautotunnel.ui.screens.support.license.LicenseScreen import com.zaneschepke.wireguardautotunnel.ui.screens.tunnels.TunnelsScreen -import com.zaneschepke.wireguardautotunnel.ui.screens.tunnels.autotunnel.TunnelAutoTunnelScreen import com.zaneschepke.wireguardautotunnel.ui.screens.tunnels.config.ConfigScreen import com.zaneschepke.wireguardautotunnel.ui.screens.tunnels.sort.SortScreen import com.zaneschepke.wireguardautotunnel.ui.screens.tunnels.splittunnel.SplitTunnelScreen @@ -149,13 +150,15 @@ class MainActivity : AppCompatActivity() { val startingStack = buildList { add(Route.Tunnels) if (intent?.action == Intent.ACTION_APPLICATION_PREFERENCES) add(Route.Settings) + if (appState.pinLockEnabled) add(Route.Lock) } val backStack = rememberNavBackStack(*startingStack.toTypedArray()) var previousRoute by remember { mutableStateOf(null) } val navController = - rememberNavController(backStack) { previousKey -> + rememberNavController(backStack, appState.isLocationDisclosureShown) { + previousKey -> previousRoute = previousKey as? Route } @@ -376,18 +379,6 @@ class MainActivity : AppCompatActivity() { ) SplitTunnelScreen(viewModel) } - entry { key -> - val viewModel = - hiltViewModel< - TunnelViewModel, - TunnelViewModel.Factory, - >( - creationCallback = { factory -> - factory.create(key.id) - } - ) - TunnelAutoTunnelScreen(viewModel) - } entry { key -> val viewModel = hiltViewModel< @@ -404,6 +395,9 @@ class MainActivity : AppCompatActivity() { LocationDisclosureScreen() } entry { AutoTunnelScreen() } + entry { + WifiSettingsScreen() + } entry { AutoTunnelAdvancedScreen() } @@ -414,8 +408,8 @@ class MainActivity : AppCompatActivity() { entry { TunnelMonitoringScreen() } - entry { - SystemFeaturesScreen() + entry { + AndroidIntegrationsScreen() } entry { DnsSettingsScreen() } entry { key -> @@ -454,6 +448,9 @@ class MainActivity : AppCompatActivity() { entry { LicenseScreen() } entry { DonateScreen() } entry { AddressesScreen() } + entry { key -> + PreferredTunnelScreen(key.tunnelNetwork) + } }, ) } diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/common/button/ActionIconButton.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/common/button/ActionIconButton.kt deleted file mode 100644 index 5bd8a558..00000000 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/common/button/ActionIconButton.kt +++ /dev/null @@ -1,21 +0,0 @@ -package com.zaneschepke.wireguardautotunnel.ui.common.button - -import androidx.compose.foundation.layout.size -import androidx.compose.material3.Icon -import androidx.compose.material3.IconButton -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.vector.ImageVector -import androidx.compose.ui.res.stringResource -import com.zaneschepke.wireguardautotunnel.ui.theme.iconSize - -@Composable -fun ActionIconButton(icon: ImageVector, labelRes: Int, onClick: () -> Unit) { - IconButton(onClick = onClick) { - Icon( - icon, - contentDescription = stringResource(labelRes), - modifier = Modifier.size(iconSize), - ) - } -} diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/common/button/ClickableIconButton.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/common/button/ClickableIconButton.kt index f4c64ee7..3017dcef 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/common/button/ClickableIconButton.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/common/button/ClickableIconButton.kt @@ -1,6 +1,5 @@ package com.zaneschepke.wireguardautotunnel.ui.common.button -import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.size import androidx.compose.material3.* @@ -11,7 +10,6 @@ import androidx.compose.ui.graphics.vector.ImageVector @Composable fun ClickableIconButton( onClick: () -> Unit, - onIconClick: () -> Unit, text: String, icon: ImageVector, enabled: Boolean = true, @@ -27,12 +25,7 @@ fun ClickableIconButton( Icon( imageVector = icon, contentDescription = icon.name, - modifier = - Modifier.size(ButtonDefaults.IconSize).weight(1f, false).clickable { - if (enabled) { - onIconClick() - } - }, + modifier = Modifier.size(ButtonDefaults.IconSize).weight(1f, false), ) } } diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/common/button/ForwardButton.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/common/button/ForwardButton.kt deleted file mode 100644 index 596d6f3d..00000000 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/common/button/ForwardButton.kt +++ /dev/null @@ -1,19 +0,0 @@ -package com.zaneschepke.wireguardautotunnel.ui.common.button - -import androidx.compose.foundation.focusable -import androidx.compose.foundation.layout.size -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.automirrored.outlined.ArrowForward -import androidx.compose.material3.Icon -import androidx.compose.material3.IconButton -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import com.zaneschepke.wireguardautotunnel.ui.theme.iconSize - -@Composable -fun ForwardButton(modifier: Modifier = Modifier.focusable(), onClick: () -> Unit) { - IconButton(modifier = modifier, onClick = onClick) { - val icon = Icons.AutoMirrored.Outlined.ArrowForward - Icon(icon, icon.name, Modifier.size(iconSize)) - } -} diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/common/button/IconSurfaceButton.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/common/button/IconSurfaceButton.kt index 75f46511..d65f2661 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/common/button/IconSurfaceButton.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/common/button/IconSurfaceButton.kt @@ -12,8 +12,6 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp -import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SelectionItemLabel -import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SelectionLabelType @Composable fun IconSurfaceButton( @@ -59,7 +57,11 @@ fun IconSurfaceButton( ) { leading?.invoke() Column(verticalArrangement = Arrangement.spacedBy(4.dp)) { - SelectionItemLabel(title, SelectionLabelType.TITLE) + Text( + text = title, + style = MaterialTheme.typography.bodyLarge, + color = MaterialTheme.colorScheme.onSurface, + ) description?.let { Text( description, diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/common/button/LaunchButton.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/common/button/LaunchButton.kt deleted file mode 100644 index 6fb8b07c..00000000 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/common/button/LaunchButton.kt +++ /dev/null @@ -1,19 +0,0 @@ -package com.zaneschepke.wireguardautotunnel.ui.common.button - -import androidx.compose.foundation.focusable -import androidx.compose.foundation.layout.size -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.automirrored.outlined.Launch -import androidx.compose.material3.Icon -import androidx.compose.material3.IconButton -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import com.zaneschepke.wireguardautotunnel.ui.theme.iconSize - -@Composable -fun LaunchButton(modifier: Modifier = Modifier.focusable(), onClick: () -> Unit) { - IconButton(modifier = modifier, onClick = onClick) { - val icon = Icons.AutoMirrored.Outlined.Launch - Icon(icon, icon.name, Modifier.size(iconSize)) - } -} diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/common/button/LinkIconButton.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/common/button/LinkIconButton.kt deleted file mode 100644 index 12105a66..00000000 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/common/button/LinkIconButton.kt +++ /dev/null @@ -1,19 +0,0 @@ -package com.zaneschepke.wireguardautotunnel.ui.common.button - -import androidx.compose.foundation.focusable -import androidx.compose.foundation.layout.size -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.automirrored.outlined.OpenInNew -import androidx.compose.material3.Icon -import androidx.compose.material3.IconButton -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import com.zaneschepke.wireguardautotunnel.ui.theme.iconSize - -@Composable -fun LinkIconButton(modifier: Modifier = Modifier.focusable(), onClick: () -> Unit) { - IconButton(modifier = modifier, onClick = onClick) { - val icon = Icons.AutoMirrored.Outlined.OpenInNew - Icon(icon, icon.name, Modifier.size(iconSize)) - } -} diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/common/button/ScaledSwitch.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/common/button/ScaledSwitch.kt index 1d082cc1..57496ad3 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/common/button/ScaledSwitch.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/common/button/ScaledSwitch.kt @@ -5,8 +5,6 @@ import androidx.compose.material3.Switch import androidx.compose.material3.SwitchDefaults import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.scale -import androidx.compose.ui.unit.dp @Composable fun ScaledSwitch( @@ -18,7 +16,7 @@ fun ScaledSwitch( Switch( checked, { onClick(it) }, - modifier.scale((52.dp / 52.dp)), + modifier = modifier, enabled = enabled, colors = SwitchDefaults.colors() diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/common/button/SelectionItemButton.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/common/button/SelectionItemButton.kt deleted file mode 100644 index d3f4ca01..00000000 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/common/button/SelectionItemButton.kt +++ /dev/null @@ -1,73 +0,0 @@ -package com.zaneschepke.wireguardautotunnel.ui.common.button - -import androidx.compose.foundation.clickable -import androidx.compose.foundation.interaction.MutableInteractionSource -import androidx.compose.foundation.layout.* -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material3.Card -import androidx.compose.material3.CardDefaults -import androidx.compose.material3.ripple -import androidx.compose.runtime.Composable -import androidx.compose.runtime.remember -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.unit.dp -import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SelectionItemLabel -import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SelectionLabelType - -@Composable -fun SelectionItemButton( - buttonText: String, - description: String? = null, - onClick: () -> Unit, - modifier: Modifier = Modifier, - leading: (@Composable () -> Unit)? = null, - trailing: (@Composable () -> Unit)? = null, - ripple: Boolean = true, -) { - Card( - modifier = - modifier - .clip(RoundedCornerShape(8.dp)) - .clickable( - indication = if (ripple) ripple() else null, - interactionSource = remember { MutableInteractionSource() }, - onClick = { onClick() }, - ) - .height(IntrinsicSize.Min), - colors = CardDefaults.cardColors(containerColor = Color.Transparent), - ) { - Row( - verticalAlignment = Alignment.CenterVertically, - modifier = Modifier.fillMaxSize().padding(horizontal = 12.dp), - ) { - Row( - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(24.dp), - ) { - leading?.let { it() } - Column( - horizontalAlignment = Alignment.Start, - modifier = Modifier.weight(1f).padding(end = 12.dp), - verticalArrangement = Arrangement.Center, - ) { - SelectionItemLabel( - buttonText, - SelectionLabelType.TITLE, - modifier = Modifier.weight(1f).padding(end = 24.dp), - ) - description?.let { - SelectionItemLabel( - it, - SelectionLabelType.DESCRIPTION, - modifier = Modifier.weight(1f).padding(end = 24.dp), - ) - } - } - } - trailing?.let { it() } - } - } -} 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 new file mode 100644 index 00000000..4bd1ae64 --- /dev/null +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/common/button/SurfaceRow.kt @@ -0,0 +1,167 @@ +package com.zaneschepke.wireguardautotunnel.ui.common.button + +import android.R.attr.description +import android.R.attr.enabled +import androidx.compose.animation.animateContentSize +import androidx.compose.foundation.background +import androidx.compose.foundation.combinedClickable +import androidx.compose.foundation.focusGroup +import androidx.compose.foundation.focusable +import androidx.compose.foundation.indication +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.defaultMinSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.material3.ripple +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.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.focus.FocusRequester +import androidx.compose.ui.focus.focusProperties +import androidx.compose.ui.focus.focusRequester +import androidx.compose.ui.layout.onSizeChanged +import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.text.AnnotatedString +import androidx.compose.ui.unit.dp + +@Composable +fun SurfaceRow( + title: AnnotatedString, + modifier: Modifier = Modifier, + onClick: (() -> Unit)? = null, + description: @Composable (() -> Unit)? = null, + expandedContent: @Composable (() -> Unit)? = null, + onLongClick: () -> Unit = {}, + enabled: Boolean = true, + selected: Boolean = false, + leading: @Composable (() -> Unit)? = null, + trailing: @Composable ((Modifier) -> Unit)? = null, +) { + val density = LocalDensity.current + var leadingPadding by remember { mutableStateOf(0.dp) } + val interactionSource = remember { MutableInteractionSource() } + val mainFocusRequester = remember { FocusRequester() } + val trailingFocusRequester = remember { FocusRequester() } + + Column( + modifier = + modifier + .fillMaxWidth() + .focusGroup() + .indication(interactionSource, ripple()) + .background( + if (!selected) MaterialTheme.colorScheme.surface + else MaterialTheme.colorScheme.primary.copy(alpha = 0.1f) + ) + .padding(horizontal = 16.dp, vertical = 8.dp) + .defaultMinSize(minHeight = 42.dp) + .animateContentSize(), + verticalArrangement = Arrangement.Center, + ) { + Row( + modifier = Modifier.fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.Start, + ) { + Row( + modifier = + Modifier.weight(1f) + .focusRequester(mainFocusRequester) + .focusProperties { right = trailingFocusRequester } + .run { + if (onClick != null) { + combinedClickable( + onClick = onClick, + onLongClick = onLongClick, + enabled = enabled, + interactionSource = interactionSource, + indication = null, + ) + } else { + this + } + } + .focusable(), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.Start, + ) { + if (leading != null) { + Row( + modifier = + Modifier.onSizeChanged { + leadingPadding = with(density) { it.width.toDp() } + } + ) { + leading() + Spacer(modifier = Modifier.width(16.dp)) + } + } + Column( + modifier = Modifier.padding(end = 16.dp), + verticalArrangement = Arrangement.spacedBy(4.dp), + ) { + Text( + text = title, + style = MaterialTheme.typography.bodyLarge, + color = + if (enabled) MaterialTheme.colorScheme.onSurface + else MaterialTheme.colorScheme.onSurface.copy(alpha = 0.38f), + ) + if (description != null) { + description() + } + } + } + if (trailing != null) { + trailing( + Modifier.focusRequester(trailingFocusRequester).focusProperties { + left = mainFocusRequester + } + ) + } + } + if (expandedContent != null) { + Row(modifier = Modifier.fillMaxWidth().padding(start = leadingPadding, top = 4.dp)) { + expandedContent() + } + } + } +} + +@Composable +fun SurfaceRow( + title: String, + modifier: Modifier = Modifier, + onClick: (() -> Unit)? = null, + description: @Composable (() -> Unit)? = null, + expandedContent: @Composable (() -> Unit)? = null, + onLongClick: () -> Unit = {}, + enabled: Boolean = true, + selected: Boolean = false, + leading: @Composable (() -> Unit)? = null, + trailing: @Composable ((Modifier) -> Unit)? = null, +) { + SurfaceRow( + title = AnnotatedString(title), + onClick = onClick, + description = description, + expandedContent = expandedContent, + onLongClick = onLongClick, + enabled = enabled, + selected = selected, + leading = leading, + trailing = trailing, + modifier = modifier, + ) +} diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/common/button/SwitchWithDivider.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/common/button/SwitchWithDivider.kt new file mode 100644 index 00000000..28a58f37 --- /dev/null +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/common/button/SwitchWithDivider.kt @@ -0,0 +1,44 @@ +package com.zaneschepke.wireguardautotunnel.ui.common.button + +import androidx.compose.foundation.gestures.detectTapGestures +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.IntrinsicSize +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxHeight +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.VerticalDivider +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.input.pointer.pointerInput +import androidx.compose.ui.unit.dp + +@Composable +fun SwitchWithDivider( + checked: Boolean, + onClick: (Boolean) -> Unit, + enabled: Boolean = true, + modifier: Modifier = Modifier, +) { + Row( + modifier = Modifier.height(IntrinsicSize.Min), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(12.dp), + ) { + VerticalDivider( + modifier = Modifier.fillMaxHeight().padding(horizontal = 8.dp, vertical = 8.dp), + color = MaterialTheme.colorScheme.outline, + ) + Box(modifier = Modifier.pointerInput(Unit) { detectTapGestures {} }) { + ScaledSwitch( + checked = checked, + onClick = onClick, + enabled = enabled, + modifier = modifier, + ) + } + } +} diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/common/button/surface/SelectionItem.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/common/button/surface/SelectionItem.kt deleted file mode 100644 index 97f1d81b..00000000 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/common/button/surface/SelectionItem.kt +++ /dev/null @@ -1,20 +0,0 @@ -package com.zaneschepke.wireguardautotunnel.ui.common.button.surface - -import androidx.compose.foundation.layout.height -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import androidx.compose.ui.unit.Dp -import androidx.compose.ui.unit.dp - -data class SelectionItem( - val leading: (@Composable () -> Unit)? = null, - val trailing: (@Composable () -> Unit)? = null, - val title: (@Composable () -> Unit), - val description: (@Composable () -> Unit)? = null, - val onClick: (() -> Unit)? = null, - val isEnabled: Boolean = true, - val disabledReason: String? = null, - val modifier: Modifier = Modifier.height(48.dp), - val padding: Dp = if (description == null && disabledReason == null) 16.dp else 6.dp, - val onLongPress: (() -> Unit)? = null, -) diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/common/button/surface/SelectionItemLabel.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/common/button/surface/SelectionItemLabel.kt deleted file mode 100644 index 5fc987c4..00000000 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/common/button/surface/SelectionItemLabel.kt +++ /dev/null @@ -1,28 +0,0 @@ -package com.zaneschepke.wireguardautotunnel.ui.common.button.surface - -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier - -@Composable -fun SelectionItemLabel(text: String, labelType: SelectionLabelType, modifier: Modifier = Modifier) { - - val style = - when (labelType) { - SelectionLabelType.DESCRIPTION -> - MaterialTheme.typography.bodySmall.copy(color = MaterialTheme.colorScheme.outline) - SelectionLabelType.TITLE -> - MaterialTheme.typography.bodyMedium.copy( - color = MaterialTheme.colorScheme.onSurface - ) - } - - Text(text = text, style = style, modifier = modifier) -} - -enum class SelectionLabelType { - - DESCRIPTION, - TITLE, -} diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/common/button/surface/SurfaceSelectionGroupButton.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/common/button/surface/SurfaceSelectionGroupButton.kt deleted file mode 100644 index 6b587e7e..00000000 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/common/button/surface/SurfaceSelectionGroupButton.kt +++ /dev/null @@ -1,117 +0,0 @@ -package com.zaneschepke.wireguardautotunnel.ui.common.button.surface - -import androidx.compose.foundation.combinedClickable -import androidx.compose.foundation.layout.* -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material3.Card -import androidx.compose.material3.CardDefaults -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.alpha -import androidx.compose.ui.draw.clip -import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.semantics.semantics -import androidx.compose.ui.semantics.stateDescription -import androidx.compose.ui.unit.dp -import com.zaneschepke.wireguardautotunnel.R - -@Composable -fun SurfaceSelectionGroupButton(items: List, modifier: Modifier = Modifier) { - if (items.isEmpty()) return - val context = LocalContext.current - Card( - modifier = modifier.fillMaxWidth(), - shape = RoundedCornerShape(8.dp), - colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surface), - ) { - Column( - verticalArrangement = Arrangement.spacedBy(2.dp), - modifier = Modifier.fillMaxWidth(), - ) { - items.forEach { item -> - Box( - contentAlignment = Alignment.Center, - modifier = - Modifier.fillMaxWidth() - .clip(RoundedCornerShape(8.dp)) - .alpha(if (item.isEnabled) 1f else 0.6f) - .semantics { - if (!item.isEnabled) { - stateDescription = - item.disabledReason ?: context.getString(R.string.disabled) - } - } - .combinedClickable( - onClick = { item.onClick?.invoke() }, - onLongClick = { item.onLongPress?.invoke() }, - enabled = true, - ), - ) { - Row( - verticalAlignment = Alignment.CenterVertically, - modifier = Modifier.fillMaxWidth(), - ) { - Row( - verticalAlignment = Alignment.CenterVertically, - modifier = - Modifier.weight(4f, false) - .fillMaxWidth() - .padding(vertical = item.padding), - ) { - item.leading?.let { - Box(modifier = Modifier.alpha(if (item.isEnabled) 1f else 0.6f)) { - it() - } - } - Column( - horizontalAlignment = Alignment.Start, - verticalArrangement = - Arrangement.spacedBy(2.dp, Alignment.CenterVertically), - modifier = - Modifier.fillMaxWidth() - .padding(start = if (item.leading != null) 16.dp else 0.dp) - .weight(1f), - ) { - Box(modifier = Modifier.alpha(if (item.isEnabled) 1f else 0.6f)) { - item.title() - } - item.description?.invoke() - if (!item.isEnabled && item.disabledReason != null) { - Text( - text = item.disabledReason, - style = MaterialTheme.typography.bodySmall, - color = - MaterialTheme.colorScheme.onSurface.copy(alpha = 0.6f), - ) - } - } - } - item.trailing?.let { - Box( - contentAlignment = Alignment.CenterEnd, - modifier = - Modifier.padding(start = 16.dp) - .alpha(if (item.isEnabled) 1f else 0.6f) - .run { - if (!item.isEnabled) { - semantics { - stateDescription = - context.getString(R.string.disabled) - } - } else { - this - } - }, - ) { - it() - } - } - } - } - } - } - } -} diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/common/dropdown/DropdownSelector.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/common/dropdown/DropdownSelector.kt index a2c58e7b..64a81e79 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/common/dropdown/DropdownSelector.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/common/dropdown/DropdownSelector.kt @@ -1,6 +1,7 @@ package com.zaneschepke.wireguardautotunnel.ui.common.dropdown import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.heightIn import androidx.compose.foundation.rememberScrollState @@ -27,30 +28,35 @@ fun DropdownSelector( it?.toString() ?: stringResource(R.string._default) }, ) { - Row( - horizontalArrangement = Arrangement.spacedBy(5.dp), - verticalAlignment = Alignment.CenterVertically, - ) { - if (label != null) label() - Text(text = optionToString(currentValue), style = MaterialTheme.typography.bodyMedium) - Icon(Icons.Default.ArrowDropDown, contentDescription = stringResource(R.string.dropdown)) - } - - DropdownMenu( - modifier = modifier.heightIn(max = 250.dp), - scrollState = rememberScrollState(), - containerColor = MaterialTheme.colorScheme.surface, - expanded = isExpanded, - onDismissRequest = onDismiss, - ) { - options.forEach { option -> - DropdownMenuItem( - text = { Text(optionToString(option)) }, - onClick = { - onValueSelected(option) - onDismiss() - }, + Box(modifier = modifier) { + Row( + horizontalArrangement = Arrangement.spacedBy(5.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + if (label != null) label() + Text(text = optionToString(currentValue), style = MaterialTheme.typography.bodyMedium) + Icon( + Icons.Default.ArrowDropDown, + contentDescription = stringResource(R.string.dropdown), ) } + + DropdownMenu( + modifier = modifier.heightIn(max = 250.dp), + scrollState = rememberScrollState(), + containerColor = MaterialTheme.colorScheme.surface, + expanded = isExpanded, + onDismissRequest = onDismiss, + ) { + options.forEach { option -> + DropdownMenuItem( + text = { Text(optionToString(option)) }, + onClick = { + onValueSelected(option) + onDismiss() + }, + ) + } + } } } diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/common/dropdown/LabelledNumberDropdown.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/common/dropdown/LabelledNumberDropdown.kt index 4a6b6e78..2d7adba5 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/common/dropdown/LabelledNumberDropdown.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/common/dropdown/LabelledNumberDropdown.kt @@ -1,12 +1,11 @@ package com.zaneschepke.wireguardautotunnel.ui.common.dropdown import androidx.compose.runtime.* -import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SelectionItem -import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SurfaceSelectionGroupButton +import com.zaneschepke.wireguardautotunnel.ui.common.button.SurfaceRow @Composable fun LabelledDropdown( - title: @Composable () -> Unit, + title: String, description: (@Composable () -> Unit)? = null, leading: @Composable () -> Unit, onSelected: (T?) -> Unit, @@ -16,24 +15,20 @@ fun LabelledDropdown( ) { var isDropDownExpanded by remember { mutableStateOf(false) } - SurfaceSelectionGroupButton( - listOf( - SelectionItem( - leading = leading, - title = title, - description = description, - onClick = { isDropDownExpanded = true }, - trailing = { - DropdownSelector( - currentValue = currentValue, - options = options, - onValueSelected = { selected -> onSelected(selected) }, - isExpanded = isDropDownExpanded, - onDismiss = { isDropDownExpanded = false }, - optionToString = optionToString, - ) - }, + SurfaceRow( + leading = leading, + title = title, + description = description, + onClick = { isDropDownExpanded = true }, + trailing = { + DropdownSelector( + currentValue = currentValue, + options = options, + onValueSelected = { selected -> onSelected(selected) }, + isExpanded = isDropDownExpanded, + onDismiss = { isDropDownExpanded = false }, + optionToString = optionToString, ) - ) + }, ) } diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/common/label/GroupLabel.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/common/label/GroupLabel.kt index f6e323c4..6b663894 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/common/label/GroupLabel.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/common/label/GroupLabel.kt @@ -7,10 +7,15 @@ import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color import androidx.compose.ui.text.font.FontWeight @Composable -fun GroupLabel(title: String, modifier: Modifier = Modifier) { +fun GroupLabel( + title: String, + modifier: Modifier = Modifier, + color: Color = MaterialTheme.colorScheme.primary, +) { Row( verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.Start, @@ -20,7 +25,7 @@ fun GroupLabel(title: String, modifier: Modifier = Modifier) { title, style = MaterialTheme.typography.titleSmall, fontWeight = FontWeight.Bold, - color = MaterialTheme.colorScheme.onBackground, + color = color, ) } } diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/common/text/DescriptionText.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/common/text/DescriptionText.kt new file mode 100644 index 00000000..cb30ed70 --- /dev/null +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/common/text/DescriptionText.kt @@ -0,0 +1,15 @@ +package com.zaneschepke.wireguardautotunnel.ui.common.text + +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier + +@Composable +fun DescriptionText(text: String, modifier: Modifier = Modifier) { + Text( + text = text, + style = MaterialTheme.typography.bodySmall.copy(color = MaterialTheme.colorScheme.outline), + modifier = modifier, + ) +} 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 749e96b6..4be9fee9 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,12 +3,18 @@ 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 import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.text.input.KeyboardCapitalization import androidx.compose.ui.text.input.VisualTransformation @@ -34,11 +40,14 @@ fun ConfigurationTextBox( readOnly: Boolean = false, singleLine: Boolean = true, ) { + val focusRequester = remember { FocusRequester() } + CustomTextField( isError = isError, textStyle = MaterialTheme.typography.bodySmall.copy(color = MaterialTheme.colorScheme.onSurface), - modifier = modifier.fillMaxWidth().height(48.dp), + modifier = + modifier.fillMaxWidth().height(48.dp).focusRequester(focusRequester).imePadding(), value = value, visualTransformation = visualTransformation, singleLine = singleLine, diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/navigation/NavController.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/navigation/NavController.kt index 9044e166..ff0c96b3 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/navigation/NavController.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/navigation/NavController.kt @@ -5,6 +5,7 @@ import androidx.navigation3.runtime.NavKey class NavController( private val backStack: NavBackStack, + private val isDisclosureShown: Boolean, private val onChange: (previous: NavKey?) -> Unit = {}, ) { fun push(route: NavKey) { @@ -27,6 +28,9 @@ class NavController( backStack.removeLast() } if (backStack.last() != route) { + val route = + if (route is Route.AutoTunnel && !isDisclosureShown) Route.LocationDisclosure + else route backStack.add(route) } } 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 940452a0..681df130 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 @@ -46,15 +46,13 @@ sealed class Route : NavKey { @Keep @Serializable data class SplitTunnelGlobal(val id: Int) : Route() - @Keep @Serializable data class TunnelAutoTunnel(val id: Int) : Route() - @Keep @Serializable data object Sort : Route() @Keep @Serializable data object Settings : Route() @Keep @Serializable data object TunnelMonitoring : Route() - @Keep @Serializable data object SystemFeatures : Route() + @Keep @Serializable data object AndroidIntegrations : Route() @Keep @Serializable data object Dns : Route() @@ -66,11 +64,22 @@ sealed class Route : NavKey { @Keep @Serializable data object WifiDetectionMethod : Route() + @Keep @Serializable data object WifiPreferences : Route() + @Keep @Serializable data object LocationDisclosure : Route() @Keep @Serializable data object Donate : Route() @Keep @Serializable data object Addresses : Route() + + @Keep @Serializable data class PreferredTunnel(val tunnelNetwork: TunnelNetwork) : Route() +} + +@Serializable +enum class TunnelNetwork { + MOBILE_DATA, + ETHERNET, + WIFI, } enum class Tab( @@ -98,15 +107,17 @@ enum class Tab( Route.Sort, is Route.TunnelOptions, is Route.Config, - is Route.SplitTunnel, - is Route.TunnelAutoTunnel -> TUNNELS + is Route.Lock, + is Route.SplitTunnel -> TUNNELS is Route.AutoTunnel, Route.AdvancedAutoTunnel, Route.WifiDetectionMethod, + Route.WifiPreferences, + is Route.PreferredTunnel, Route.LocationDisclosure -> AUTOTUNNEL is Route.Settings, Route.TunnelMonitoring, - Route.SystemFeatures, + Route.AndroidIntegrations, Route.Dns, is Route.TunnelGlobals, is Route.ConfigGlobal, 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 2d3dc737..5f0d6ed4 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 @@ -6,14 +6,18 @@ import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.rounded.ArrowBack import androidx.compose.material.icons.automirrored.rounded.Sort import androidx.compose.material.icons.rounded.* +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton import androidx.compose.runtime.* import androidx.compose.runtime.derivedStateOf import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalSoftwareKeyboardController +import androidx.compose.ui.res.stringResource import com.zaneschepke.wireguardautotunnel.R -import com.zaneschepke.wireguardautotunnel.ui.common.button.ActionIconButton import com.zaneschepke.wireguardautotunnel.ui.navigation.NavController import com.zaneschepke.wireguardautotunnel.ui.navigation.Route +import com.zaneschepke.wireguardautotunnel.ui.navigation.Route.* +import com.zaneschepke.wireguardautotunnel.ui.navigation.TunnelNetwork import com.zaneschepke.wireguardautotunnel.ui.sideeffect.LocalSideEffect import com.zaneschepke.wireguardautotunnel.ui.state.NavbarState import com.zaneschepke.wireguardautotunnel.ui.state.SharedAppUiState @@ -32,27 +36,33 @@ fun currentRouteAsNavbarState( return remember(route, sharedState) { derivedStateOf { when (route) { - Route.AdvancedAutoTunnel -> + AdvancedAutoTunnel -> NavbarState( topLeading = { - ActionIconButton(Icons.AutoMirrored.Rounded.ArrowBack, R.string.back) { - navController.pop() + IconButton(onClick = { navController.pop() }) { + Icon( + Icons.AutoMirrored.Rounded.ArrowBack, + stringResource(R.string.back), + ) } }, showBottomItems = true, topTitle = context.getString(R.string.advanced_settings), ) - Route.Appearance -> + Appearance -> NavbarState( topLeading = { - ActionIconButton(Icons.AutoMirrored.Rounded.ArrowBack, R.string.back) { - navController.pop() + IconButton(onClick = { navController.pop() }) { + Icon( + Icons.AutoMirrored.Rounded.ArrowBack, + stringResource(R.string.back), + ) } }, showBottomItems = true, topTitle = context.getString(R.string.appearance), ) - Route.AutoTunnel -> + AutoTunnel -> NavbarState( showBottomItems = true, topTitle = @@ -61,295 +71,392 @@ fun currentRouteAsNavbarState( context.getString(R.string.auto_tunnel) }, ) - Route.Display -> + Display -> NavbarState( topLeading = { - ActionIconButton(Icons.AutoMirrored.Rounded.ArrowBack, R.string.back) { - navController.pop() + IconButton(onClick = { navController.pop() }) { + Icon( + Icons.AutoMirrored.Rounded.ArrowBack, + stringResource(R.string.back), + ) } }, showBottomItems = true, topTitle = context.getString(R.string.display_theme), ) - Route.Dns -> + Dns -> NavbarState( topLeading = { - ActionIconButton(Icons.AutoMirrored.Rounded.ArrowBack, R.string.back) { - navController.pop() + IconButton(onClick = { navController.pop() }) { + Icon( + Icons.AutoMirrored.Rounded.ArrowBack, + stringResource(R.string.back), + ) } }, showBottomItems = true, topTitle = context.getString(R.string.dns_settings), ) - Route.Language -> + Language -> NavbarState( topLeading = { - ActionIconButton(Icons.AutoMirrored.Rounded.ArrowBack, R.string.back) { - navController.pop() + IconButton(onClick = { navController.pop() }) { + Icon( + Icons.AutoMirrored.Rounded.ArrowBack, + stringResource(R.string.back), + ) } }, showBottomItems = true, topTitle = context.getString(R.string.language), ) - Route.License -> + License -> NavbarState( topLeading = { - ActionIconButton(Icons.AutoMirrored.Rounded.ArrowBack, R.string.back) { - navController.pop() + IconButton(onClick = { navController.pop() }) { + Icon( + Icons.AutoMirrored.Rounded.ArrowBack, + stringResource(R.string.back), + ) } }, showBottomItems = true, topTitle = context.getString(R.string.licenses), ) - Route.LocationDisclosure -> NavbarState(showBottomItems = true) - Route.Lock -> NavbarState(showBottomItems = false) - Route.Logs -> + LocationDisclosure -> NavbarState(showBottomItems = true) + Lock -> NavbarState(showBottomItems = false) + Logs -> NavbarState( topLeading = { - ActionIconButton(Icons.AutoMirrored.Rounded.ArrowBack, R.string.back) { - navController.pop() + IconButton(onClick = { navController.pop() }) { + Icon( + Icons.AutoMirrored.Rounded.ArrowBack, + stringResource(R.string.back), + ) } }, showBottomItems = true, topTitle = context.getString(R.string.logs), topTrailing = { - ActionIconButton(Icons.Rounded.Menu, R.string.quick_actions) { - sharedViewModel.postSideEffect(LocalSideEffect.Sheet.LoggerActions) + IconButton( + onClick = { + sharedViewModel.postSideEffect( + LocalSideEffect.Sheet.LoggerActions + ) + } + ) { + Icon(Icons.Rounded.Menu, stringResource(R.string.quick_actions)) } }, ) - Route.ProxySettings -> + ProxySettings -> NavbarState( topLeading = { - ActionIconButton(Icons.AutoMirrored.Rounded.ArrowBack, R.string.back) { - navController.pop() + IconButton(onClick = { navController.pop() }) { + Icon( + Icons.AutoMirrored.Rounded.ArrowBack, + stringResource(R.string.back), + ) } }, showBottomItems = true, topTitle = context.getString(R.string.proxy_settings), topTrailing = { - ActionIconButton(Icons.Rounded.Save, R.string.save) { - keyboardController?.hide() - sharedViewModel.postSideEffect(LocalSideEffect.SaveChanges) + IconButton( + onClick = { + keyboardController?.hide() + sharedViewModel.postSideEffect(LocalSideEffect.SaveChanges) + } + ) { + Icon(Icons.Rounded.Save, stringResource(R.string.save)) } }, ) - Route.Settings -> + Settings -> NavbarState( showBottomItems = true, topTitle = context.getString(R.string.settings), topTrailing = { - ActionIconButton( - Icons.Rounded.SettingsBackupRestore, - R.string.quick_actions, + IconButton( + onClick = { + sharedViewModel.postSideEffect(LocalSideEffect.Sheet.BackupApp) + } ) { - sharedViewModel.postSideEffect(LocalSideEffect.Sheet.BackupApp) + Icon( + Icons.Rounded.SettingsBackupRestore, + stringResource(R.string.quick_actions), + ) } }, ) - Route.Sort -> + Sort -> NavbarState( topLeading = { - ActionIconButton(Icons.AutoMirrored.Rounded.ArrowBack, R.string.back) { - navController.pop() + IconButton(onClick = { navController.pop() }) { + Icon( + Icons.AutoMirrored.Rounded.ArrowBack, + stringResource(R.string.back), + ) } }, showBottomItems = true, topTitle = context.getString(R.string.sort), topTrailing = { Row { - ActionIconButton(Icons.Rounded.SortByAlpha, R.string.sort) { - sharedViewModel.postSideEffect(LocalSideEffect.Sort) + IconButton( + onClick = { + sharedViewModel.postSideEffect(LocalSideEffect.Sort) + } + ) { + Icon(Icons.Rounded.SortByAlpha, stringResource(R.string.sort)) } - ActionIconButton(Icons.Rounded.Save, R.string.save) { - sharedViewModel.postSideEffect(LocalSideEffect.SaveChanges) + IconButton( + onClick = { + sharedViewModel.postSideEffect(LocalSideEffect.SaveChanges) + } + ) { + Icon(Icons.Rounded.Save, stringResource(R.string.save)) } } }, ) - is Route.Config -> { + is Config -> { val tunnelName = sharedState.tunnelNames[route.id] NavbarState( topLeading = { - ActionIconButton(Icons.AutoMirrored.Rounded.ArrowBack, R.string.back) { - navController.pop() + IconButton(onClick = { navController.pop() }) { + Icon( + Icons.AutoMirrored.Rounded.ArrowBack, + stringResource(R.string.back), + ) } }, showBottomItems = true, topTitle = tunnelName ?: context.getString(R.string.new_tunnel), topTrailing = { - ActionIconButton(Icons.Rounded.Save, R.string.save) { - keyboardController?.hide() - sharedViewModel.postSideEffect(LocalSideEffect.SaveChanges) + IconButton( + onClick = { + keyboardController?.hide() + sharedViewModel.postSideEffect(LocalSideEffect.SaveChanges) + } + ) { + Icon(Icons.Rounded.Save, stringResource(R.string.save)) } }, ) } - is Route.SplitTunnel -> { + is SplitTunnel -> { val tunnelName = sharedState.tunnelNames[route.id] NavbarState( topLeading = { - ActionIconButton(Icons.AutoMirrored.Rounded.ArrowBack, R.string.back) { - navController.pop() + IconButton(onClick = { navController.pop() }) { + Icon( + Icons.AutoMirrored.Rounded.ArrowBack, + stringResource(R.string.back), + ) } }, topTitle = tunnelName ?: "", topTrailing = { - ActionIconButton(Icons.Rounded.Save, R.string.save) { - sharedViewModel.postSideEffect(LocalSideEffect.SaveChanges) + IconButton( + onClick = { + sharedViewModel.postSideEffect(LocalSideEffect.SaveChanges) + } + ) { + Icon(Icons.Rounded.Save, stringResource(R.string.save)) } }, showBottomItems = true, ) } - is Route.SplitTunnelGlobal -> { + is SplitTunnelGlobal -> { NavbarState( topLeading = { - ActionIconButton(Icons.AutoMirrored.Rounded.ArrowBack, R.string.back) { - navController.pop() + IconButton(onClick = { navController.pop() }) { + Icon( + Icons.AutoMirrored.Rounded.ArrowBack, + stringResource(R.string.back), + ) } }, topTitle = context.getString(R.string.splt_tunneling), topTrailing = { - ActionIconButton(Icons.Rounded.Save, R.string.save) { - sharedViewModel.postSideEffect(LocalSideEffect.SaveChanges) + IconButton( + onClick = { + sharedViewModel.postSideEffect(LocalSideEffect.SaveChanges) + } + ) { + Icon(Icons.Rounded.Save, stringResource(R.string.save)) } }, showBottomItems = true, ) } - is Route.ConfigGlobal -> { + is ConfigGlobal -> { NavbarState( topLeading = { - ActionIconButton(Icons.AutoMirrored.Rounded.ArrowBack, R.string.back) { - navController.pop() + IconButton(onClick = { navController.pop() }) { + Icon( + Icons.AutoMirrored.Rounded.ArrowBack, + stringResource(R.string.back), + ) } }, showBottomItems = true, topTitle = context.getString(R.string.configuration), topTrailing = { - ActionIconButton(Icons.Rounded.Save, R.string.save) { - keyboardController?.hide() - sharedViewModel.postSideEffect(LocalSideEffect.SaveChanges) + IconButton( + onClick = { + sharedViewModel.postSideEffect(LocalSideEffect.SaveChanges) + } + ) { + Icon(Icons.Rounded.Save, stringResource(R.string.save)) } }, ) } - Route.Support -> + Support -> NavbarState( topTitle = context.getString(R.string.support), showBottomItems = true, ) - Route.SystemFeatures -> + AndroidIntegrations -> NavbarState( topLeading = { - ActionIconButton(Icons.AutoMirrored.Rounded.ArrowBack, R.string.back) { - navController.pop() + IconButton(onClick = { navController.pop() }) { + Icon( + Icons.AutoMirrored.Rounded.ArrowBack, + stringResource(R.string.back), + ) } }, topTitle = context.getString(R.string.android_integrations), showBottomItems = true, ) - is Route.TunnelAutoTunnel -> { - val tunnelName = sharedState.tunnelNames[route.id] + TunnelMonitoring -> NavbarState( topLeading = { - ActionIconButton(Icons.AutoMirrored.Rounded.ArrowBack, R.string.back) { - navController.pop() - } - }, - showBottomItems = true, - topTitle = tunnelName ?: "", - ) - } - Route.TunnelMonitoring -> - NavbarState( - topLeading = { - ActionIconButton(Icons.AutoMirrored.Rounded.ArrowBack, R.string.back) { - navController.pop() + IconButton(onClick = { navController.pop() }) { + Icon( + Icons.AutoMirrored.Rounded.ArrowBack, + stringResource(R.string.back), + ) } }, topTitle = context.getString(R.string.tunnel_monitoring), showBottomItems = true, ) - is Route.TunnelOptions -> { + is TunnelOptions -> { val tunnelName = sharedState.tunnelNames[route.id] NavbarState( topLeading = { - ActionIconButton(Icons.AutoMirrored.Rounded.ArrowBack, R.string.back) { - navController.pop() + IconButton(onClick = { navController.pop() }) { + Icon( + Icons.AutoMirrored.Rounded.ArrowBack, + stringResource(R.string.back), + ) } }, showBottomItems = true, topTitle = tunnelName ?: "", topTrailing = { Row { - ActionIconButton(Icons.Rounded.QrCode2, R.string.show_qr) { - sharedViewModel.postSideEffect(LocalSideEffect.Modal.QR) + IconButton( + onClick = { + sharedViewModel.postSideEffect(LocalSideEffect.Modal.QR) + } + ) { + Icon(Icons.Rounded.QrCode2, stringResource(R.string.show_qr)) } - ActionIconButton(Icons.Rounded.Edit, R.string.edit_tunnel) { - navController.push(Route.Config(route.id)) + IconButton(onClick = { navController.push(Config(route.id)) }) { + Icon(Icons.Rounded.Edit, stringResource(R.string.edit_tunnel)) } } }, ) } - Route.Tunnels -> { + Tunnels -> { NavbarState( topTitle = context.getString(R.string.tunnels), topTrailing = { when (sharedState.selectedTunnelCount) { 0 -> Row { - ActionIconButton( - Icons.AutoMirrored.Rounded.Sort, - R.string.sort, - ) { - navController.push(Route.Sort) + IconButton(onClick = { navController.push(Sort) }) { + Icon( + Icons.AutoMirrored.Rounded.Sort, + stringResource(R.string.sort), + ) } - ActionIconButton(Icons.Rounded.Add, R.string.add_tunnel) { - sharedViewModel.postSideEffect( - LocalSideEffect.Sheet.ImportTunnels + IconButton( + onClick = { + sharedViewModel.postSideEffect( + LocalSideEffect.Sheet.ImportTunnels + ) + } + ) { + Icon( + Icons.Rounded.Add, + stringResource(R.string.add_tunnel), ) } } else -> Row { - ActionIconButton( - Icons.Rounded.SelectAll, - R.string.select_all, + IconButton( + onClick = { + sharedViewModel.postSideEffect( + LocalSideEffect.SelectedTunnels.SelectAll + ) + } ) { - sharedViewModel.postSideEffect( - LocalSideEffect.SelectedTunnels.SelectAll + Icon( + Icons.Rounded.SelectAll, + stringResource(R.string.select_all), ) } // due to permissions, and SAF issues on TV, not support // less than Android // 10 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { - ActionIconButton( - Icons.Rounded.Download, - R.string.download, + IconButton( + onClick = { + sharedViewModel.postSideEffect( + LocalSideEffect.Sheet.ExportTunnels + ) + } ) { - sharedViewModel.postSideEffect( - LocalSideEffect.Sheet.ExportTunnels + Icon( + Icons.Rounded.Download, + stringResource(R.string.download), ) } } if (sharedState.selectedTunnelCount == 1) { - ActionIconButton(Icons.Rounded.CopyAll, R.string.copy) { - sharedViewModel.postSideEffect( - LocalSideEffect.SelectedTunnels.Copy + IconButton( + onClick = { + sharedViewModel.postSideEffect( + LocalSideEffect.SelectedTunnels.Copy + ) + } + ) { + Icon( + Icons.Rounded.CopyAll, + stringResource(R.string.copy), ) } } - ActionIconButton( - Icons.Rounded.Delete, - R.string.delete_tunnel, + IconButton( + onClick = { + sharedViewModel.postSideEffect( + LocalSideEffect.Modal.DeleteTunnels + ) + } ) { - sharedViewModel.postSideEffect( - LocalSideEffect.Modal.DeleteTunnels + Icon( + Icons.Rounded.Delete, + stringResource(R.string.delete_tunnel), ) } } @@ -358,46 +465,92 @@ fun currentRouteAsNavbarState( showBottomItems = true, ) } - Route.WifiDetectionMethod -> + WifiDetectionMethod -> NavbarState( topLeading = { - ActionIconButton(Icons.AutoMirrored.Rounded.ArrowBack, R.string.back) { - navController.pop() + IconButton(onClick = { navController.pop() }) { + Icon( + Icons.AutoMirrored.Rounded.ArrowBack, + stringResource(R.string.back), + ) } }, topTitle = context.getString(R.string.wifi_detection_method), showBottomItems = true, ) - Route.Donate -> { + Donate -> { NavbarState( topLeading = { - ActionIconButton(Icons.AutoMirrored.Rounded.ArrowBack, R.string.back) { - navController.pop() + IconButton(onClick = { navController.pop() }) { + Icon( + Icons.AutoMirrored.Rounded.ArrowBack, + stringResource(R.string.back), + ) } }, topTitle = context.getString(R.string.donate_title), showBottomItems = true, ) } - Route.Addresses -> { + Addresses -> { NavbarState( topLeading = { - ActionIconButton(Icons.AutoMirrored.Rounded.ArrowBack, R.string.back) { - navController.pop() + IconButton(onClick = { navController.pop() }) { + Icon( + Icons.AutoMirrored.Rounded.ArrowBack, + stringResource(R.string.back), + ) } }, topTitle = context.getString(R.string.addresses), showBottomItems = true, ) } - is Route.TunnelGlobals -> { + is TunnelGlobals -> { NavbarState( topLeading = { - ActionIconButton(Icons.AutoMirrored.Rounded.ArrowBack, R.string.back) { - navController.pop() + IconButton(onClick = { navController.pop() }) { + Icon( + Icons.AutoMirrored.Rounded.ArrowBack, + stringResource(R.string.back), + ) } }, - topTitle = context.getString(R.string.tunnel_global_overrides), + topTitle = context.getString(R.string.global_overrides), + showBottomItems = true, + ) + } + is WifiPreferences -> { + NavbarState( + topLeading = { + IconButton(onClick = { navController.pop() }) { + Icon( + Icons.AutoMirrored.Rounded.ArrowBack, + stringResource(R.string.back), + ) + } + }, + topTitle = context.getString(R.string.wifi_settings), + showBottomItems = true, + ) + } + is PreferredTunnel -> { + val title = + when (route.tunnelNetwork) { + TunnelNetwork.MOBILE_DATA, + TunnelNetwork.ETHERNET -> context.getString(R.string.preferred_tunnel) + TunnelNetwork.WIFI -> context.getString(R.string.tunnel_mapping) + } + NavbarState( + topLeading = { + IconButton(onClick = { navController.pop() }) { + Icon( + Icons.AutoMirrored.Rounded.ArrowBack, + stringResource(R.string.back), + ) + } + }, + topTitle = title, showBottomItems = true, ) } diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/navigation/functions/rememberNavController.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/navigation/functions/rememberNavController.kt index 6bc40610..3cee1b0f 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/navigation/functions/rememberNavController.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/navigation/functions/rememberNavController.kt @@ -9,7 +9,10 @@ import com.zaneschepke.wireguardautotunnel.ui.navigation.NavController @Composable fun rememberNavController( backStack: NavBackStack, + isDisclosureShown: Boolean, onChange: (NavKey?) -> Unit = {}, ): NavController { - return remember(backStack, onChange) { NavController(backStack, onChange) } + return remember(backStack, onChange, isDisclosureShown) { + NavController(backStack, isDisclosureShown, onChange) + } } diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/autotunnel/AutoTunnelScreen.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/autotunnel/AutoTunnelScreen.kt index 5ba4262d..d8cd3093 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/autotunnel/AutoTunnelScreen.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/autotunnel/AutoTunnelScreen.kt @@ -7,30 +7,31 @@ 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.CheckCircle -import androidx.compose.material.icons.outlined.Info +import androidx.compose.material.icons.outlined.* import androidx.compose.material3.* import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.SpanStyle +import androidx.compose.ui.text.buildAnnotatedString import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.withStyle import androidx.compose.ui.unit.dp import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.google.accompanist.permissions.ExperimentalPermissionsApi import com.zaneschepke.wireguardautotunnel.R import com.zaneschepke.wireguardautotunnel.ui.LocalNavController -import com.zaneschepke.wireguardautotunnel.ui.common.SectionDivider import com.zaneschepke.wireguardautotunnel.ui.common.banner.WarningBanner -import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SelectionItem -import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SurfaceSelectionGroupButton +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.dialog.InfoDialog +import com.zaneschepke.wireguardautotunnel.ui.common.label.GroupLabel import com.zaneschepke.wireguardautotunnel.ui.navigation.Route -import com.zaneschepke.wireguardautotunnel.ui.screens.autotunnel.components.AdvancedSettingsItem -import com.zaneschepke.wireguardautotunnel.ui.screens.autotunnel.components.networkTunnelingItems -import com.zaneschepke.wireguardautotunnel.ui.screens.autotunnel.components.wifiTunnelingItems +import com.zaneschepke.wireguardautotunnel.ui.navigation.TunnelNetwork import com.zaneschepke.wireguardautotunnel.util.extensions.launchAppSettings import com.zaneschepke.wireguardautotunnel.util.extensions.launchLocationServicesSettings import com.zaneschepke.wireguardautotunnel.viewmodel.AutoTunnelViewModel @@ -42,15 +43,18 @@ fun AutoTunnelScreen(viewModel: AutoTunnelViewModel = hiltViewModel()) { val navController = LocalNavController.current val autoTunnelState by viewModel.container.stateFlow.collectAsStateWithLifecycle() - LaunchedEffect(autoTunnelState.stateInitialized) { - if (!autoTunnelState.isLocationDisclosureShown && autoTunnelState.stateInitialized) { - navController.push(Route.LocationDisclosure) - } - } - - if (!autoTunnelState.stateInitialized) return + if (autoTunnelState.isLoading) return var showLocationDialog by remember { mutableStateOf(false) } + val (ethernetTunnel, mobileDataTunnel, mappedTunnels) = + remember(autoTunnelState.tunnels) { + Triple( + autoTunnelState.tunnels.firstOrNull { it.isEthernetTunnel }, + autoTunnelState.tunnels.firstOrNull { it.isMobileDataTunnel }, + autoTunnelState.tunnels.any { it.tunnelNetworks.isNotEmpty() }, + ) + } + val showLocationServicesWarning by remember( autoTunnelState.connectivityState?.wifiState, @@ -92,83 +96,194 @@ fun AutoTunnelScreen(viewModel: AutoTunnelViewModel = hiltViewModel()) { Column( horizontalAlignment = Alignment.Start, - verticalArrangement = Arrangement.spacedBy(12.dp, Alignment.Top), - modifier = - Modifier.fillMaxSize().verticalScroll(rememberScrollState()).padding(horizontal = 16.dp), + verticalArrangement = Arrangement.spacedBy(16.dp, Alignment.Top), + modifier = Modifier.fillMaxSize().verticalScroll(rememberScrollState()), ) { - WarningBanner( - stringResource(R.string.location_services_not_detected), - showLocationServicesWarning, - trailing = { - TextButton({ context.launchLocationServicesSettings() }) { - Text( - stringResource(R.string.fix), - color = MaterialTheme.colorScheme.primary, - style = MaterialTheme.typography.bodyMedium, - ) - } - }, - ) - WarningBanner( - stringResource(R.string.location_permissions_missing), - showLocationPermissionsWarning, - trailing = { - TextButton({ showLocationDialog = true }) { - Text( - stringResource(R.string.fix), - color = MaterialTheme.colorScheme.primary, - style = MaterialTheme.typography.bodyMedium, - ) - } - }, - ) - val (title, buttonText, icon) = - remember(autoTunnelState.autoTunnelActive) { - when (autoTunnelState.autoTunnelActive) { - true -> - Triple( - context.getString(R.string.auto_tunnel_running), - context.getString(R.string.stop), - Icons.Outlined.CheckCircle, + Column { + WarningBanner( + stringResource(R.string.location_services_not_detected), + showLocationServicesWarning, + trailing = { + TextButton({ context.launchLocationServicesSettings() }) { + Text( + stringResource(R.string.fix), + color = MaterialTheme.colorScheme.primary, + style = MaterialTheme.typography.bodyMedium, ) - false -> - Triple( - context.getString(R.string.auto_tunnel_not_running), - context.getString(R.string.start), - Icons.Outlined.Info, + } + }, + ) + WarningBanner( + stringResource(R.string.location_permissions_missing), + showLocationPermissionsWarning, + trailing = { + TextButton({ showLocationDialog = true }) { + Text( + stringResource(R.string.fix), + color = MaterialTheme.colorScheme.primary, + style = MaterialTheme.typography.bodyMedium, ) + } + }, + ) + val (title, buttonText, icon) = + remember(autoTunnelState.autoTunnelActive) { + when (autoTunnelState.autoTunnelActive) { + true -> + Triple( + context.getString(R.string.auto_tunnel_running), + context.getString(R.string.stop), + Icons.Outlined.CheckCircle, + ) + false -> + Triple( + context.getString(R.string.auto_tunnel_not_running), + context.getString(R.string.start), + Icons.Outlined.Info, + ) + } } - } - SurfaceSelectionGroupButton( - items = - listOf( - SelectionItem( - leading = { Icon(icon, null) }, - title = { Text(title) }, - trailing = { - Button({ viewModel.toggleAutoTunnel() }) { - Text( - buttonText, - fontWeight = FontWeight.Bold, - style = - MaterialTheme.typography.bodyMedium.copy( - color = MaterialTheme.colorScheme.surface - ), + SurfaceRow( + leading = { Icon(icon, null) }, + title = title, + trailing = { + Button({ viewModel.toggleAutoTunnel() }) { + Text( + buttonText, + fontWeight = FontWeight.Bold, + style = + MaterialTheme.typography.bodyMedium.copy( + color = MaterialTheme.colorScheme.surface + ), + ) + } + }, + onClick = { viewModel.toggleAutoTunnel() }, + ) + } + Column { + GroupLabel( + stringResource(R.string.networks), + modifier = Modifier.padding(horizontal = 16.dp), + ) + SurfaceRow( + leading = { Icon(Icons.Outlined.Wifi, contentDescription = null) }, + title = stringResource(R.string.tunnel_on_wifi), + trailing = { modifier -> + SwitchWithDivider( + checked = autoTunnelState.settings.isTunnelOnWifiEnabled, + onClick = { viewModel.setAutoTunnelOnWifiEnabled(it) }, + modifier = modifier, + ) + }, + description = { + Text( + buildAnnotatedString { + append(stringResource(R.string.preferred_tunnel_label)) + withStyle(style = SpanStyle(fontWeight = FontWeight.Bold)) { + append( + if (mappedTunnels) stringResource(R.string.mapped) + else stringResource(R.string._default) ) } }, + style = + MaterialTheme.typography.bodySmall.copy( + color = MaterialTheme.colorScheme.outline + ), ) - ) - ) - SurfaceSelectionGroupButton(items = wifiTunnelingItems(autoTunnelState, viewModel)) - SectionDivider() - SurfaceSelectionGroupButton(items = networkTunnelingItems(autoTunnelState, viewModel)) - SectionDivider() - SurfaceSelectionGroupButton( - items = - listOf( - AdvancedSettingsItem(onClick = { navController.push(Route.AdvancedAutoTunnel) }) - ) - ) + }, + onClick = { navController.push(Route.WifiPreferences) }, + ) + SurfaceRow( + leading = { Icon(Icons.Outlined.SignalCellular4Bar, contentDescription = null) }, + title = stringResource(R.string.tunnel_mobile_data), + trailing = { modifier -> + SwitchWithDivider( + checked = autoTunnelState.settings.isTunnelOnMobileDataEnabled, + onClick = { viewModel.setTunnelOnCellular(it) }, + modifier = modifier, + ) + }, + description = { + Text( + buildAnnotatedString { + append(stringResource(R.string.preferred_tunnel_label)) + mobileDataTunnel?.tunName?.let { append(it) } + ?: withStyle(style = SpanStyle(fontWeight = FontWeight.Bold)) { + append(stringResource(R.string._default)) + } + }, + style = + MaterialTheme.typography.bodySmall.copy( + color = MaterialTheme.colorScheme.outline + ), + ) + }, + onClick = { navController.push(Route.PreferredTunnel(TunnelNetwork.MOBILE_DATA)) }, + ) + SurfaceRow( + leading = { Icon(Icons.Outlined.SettingsEthernet, contentDescription = null) }, + title = stringResource(R.string.tunnel_on_ethernet), + trailing = { modifier -> + SwitchWithDivider( + checked = autoTunnelState.settings.isTunnelOnEthernetEnabled, + onClick = { viewModel.setTunnelOnEthernet(it) }, + modifier = modifier, + ) + }, + description = { + Text( + buildAnnotatedString { + append(stringResource(R.string.preferred_tunnel_label)) + ethernetTunnel?.tunName?.let { append(it) } + ?: withStyle(style = SpanStyle(fontWeight = FontWeight.Bold)) { + append(stringResource(R.string._default)) + } + }, + style = + MaterialTheme.typography.bodySmall.copy( + color = MaterialTheme.colorScheme.outline + ), + ) + }, + onClick = { navController.push(Route.PreferredTunnel(TunnelNetwork.ETHERNET)) }, + ) + } + Column { + GroupLabel( + stringResource(R.string.other), + modifier = Modifier.padding(horizontal = 16.dp), + ) + SurfaceRow( + leading = { Icon(Icons.Outlined.PublicOff, contentDescription = null) }, + title = stringResource(R.string.stop_on_no_internet), + description = { + Text( + stringResource(R.string.stop_on_internet_loss), + style = + MaterialTheme.typography.bodySmall.copy( + MaterialTheme.colorScheme.outline + ), + ) + }, + trailing = { + ScaledSwitch( + checked = autoTunnelState.settings.isStopOnNoInternetEnabled, + onClick = { viewModel.setStopOnNoInternetEnabled(it) }, + ) + }, + onClick = { + viewModel.setStopOnNoInternetEnabled( + !autoTunnelState.settings.isStopOnNoInternetEnabled + ) + }, + ) + SurfaceRow( + leading = { Icon(Icons.Outlined.Settings, contentDescription = null) }, + title = stringResource(R.string.advanced_settings), + onClick = { navController.push(Route.AdvancedAutoTunnel) }, + ) + } } } diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/autotunnel/advanced/AutoTunnelAdvancedScreen.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/autotunnel/advanced/AutoTunnelAdvancedScreen.kt index dba8e168..44867cf3 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/autotunnel/advanced/AutoTunnelAdvancedScreen.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/autotunnel/advanced/AutoTunnelAdvancedScreen.kt @@ -3,14 +3,11 @@ package com.zaneschepke.wireguardautotunnel.ui.screens.autotunnel.advanced 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.rememberScrollState import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.PauseCircle import androidx.compose.material3.Icon -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment @@ -29,19 +26,10 @@ fun AutoTunnelAdvancedScreen(viewModel: AutoTunnelViewModel = hiltViewModel()) { Column( horizontalAlignment = Alignment.Start, verticalArrangement = Arrangement.spacedBy(12.dp, Alignment.Top), - modifier = - Modifier.fillMaxSize().verticalScroll(rememberScrollState()).padding(horizontal = 16.dp), + modifier = Modifier.fillMaxSize().verticalScroll(rememberScrollState()), ) { LabelledDropdown( - title = { - Text( - stringResource(R.string.debounce_delay), - style = - MaterialTheme.typography.bodyMedium.copy( - color = MaterialTheme.colorScheme.onSurface - ), - ) - }, + title = stringResource(R.string.debounce_delay), leading = { Icon(Icons.Outlined.PauseCircle, null) }, onSelected = { selected -> viewModel.setDebounceDelay(selected!!) }, options = (0..10).toList(), diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/autotunnel/components/AdvancedSettingsItem.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/autotunnel/components/AdvancedSettingsItem.kt deleted file mode 100644 index 43763c8a..00000000 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/autotunnel/components/AdvancedSettingsItem.kt +++ /dev/null @@ -1,28 +0,0 @@ -package com.zaneschepke.wireguardautotunnel.ui.screens.autotunnel.components - -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.outlined.Settings -import androidx.compose.material3.Icon -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.ui.res.stringResource -import com.zaneschepke.wireguardautotunnel.R -import com.zaneschepke.wireguardautotunnel.ui.common.button.ForwardButton -import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SelectionItem - -@Composable -fun AdvancedSettingsItem(onClick: () -> Unit): SelectionItem { - return SelectionItem( - leading = { Icon(Icons.Outlined.Settings, contentDescription = null) }, - title = { - Text( - stringResource(R.string.advanced_settings), - style = - MaterialTheme.typography.bodyMedium.copy(MaterialTheme.colorScheme.onSurface), - ) - }, - trailing = { ForwardButton { onClick() } }, - onClick = { onClick() }, - ) -} diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/autotunnel/components/NetworkTunnelingItems.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/autotunnel/components/NetworkTunnelingItems.kt deleted file mode 100644 index 5863ef6e..00000000 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/autotunnel/components/NetworkTunnelingItems.kt +++ /dev/null @@ -1,133 +0,0 @@ -package com.zaneschepke.wireguardautotunnel.ui.screens.autotunnel.components - -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.outlined.PublicOff -import androidx.compose.material.icons.outlined.SettingsEthernet -import androidx.compose.material.icons.outlined.SignalCellular4Bar -import androidx.compose.material3.Icon -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.runtime.remember -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.text.style.TextOverflow -import com.zaneschepke.wireguardautotunnel.R -import com.zaneschepke.wireguardautotunnel.ui.common.button.ScaledSwitch -import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SelectionItem -import com.zaneschepke.wireguardautotunnel.ui.state.AutoTunnelUiState -import com.zaneschepke.wireguardautotunnel.viewmodel.AutoTunnelViewModel - -@Composable -fun networkTunnelingItems( - autoTunnelState: AutoTunnelUiState, - viewModel: AutoTunnelViewModel, -): List { - return listOf( - SelectionItem( - leading = { Icon(Icons.Outlined.SignalCellular4Bar, contentDescription = null) }, - title = { - Text( - stringResource(R.string.tunnel_mobile_data), - style = - MaterialTheme.typography.bodyMedium.copy( - MaterialTheme.colorScheme.onSurface - ), - ) - }, - trailing = { - ScaledSwitch( - checked = autoTunnelState.settings.isTunnelOnMobileDataEnabled, - onClick = { viewModel.setTunnelOnCellular(it) }, - ) - }, - description = { - val cellularActive = - remember(autoTunnelState.connectivityState) { - autoTunnelState.connectivityState?.cellularConnected ?: false - } - Text( - text = - if (cellularActive) stringResource(R.string.active) - else stringResource(R.string.inactive), - style = - MaterialTheme.typography.bodySmall.copy( - color = MaterialTheme.colorScheme.outline - ), - maxLines = 1, - overflow = TextOverflow.Ellipsis, - ) - }, - onClick = { - viewModel.setTunnelOnCellular(!autoTunnelState.settings.isTunnelOnMobileDataEnabled) - }, - ), - SelectionItem( - leading = { Icon(Icons.Outlined.SettingsEthernet, contentDescription = null) }, - title = { - Text( - stringResource(R.string.tunnel_on_ethernet), - style = - MaterialTheme.typography.bodyMedium.copy( - MaterialTheme.colorScheme.onSurface - ), - ) - }, - trailing = { - ScaledSwitch( - checked = autoTunnelState.settings.isTunnelOnEthernetEnabled, - onClick = { viewModel.setTunnelOnEthernet(it) }, - ) - }, - description = { - val ethernetActive = - remember(autoTunnelState.connectivityState) { - autoTunnelState.connectivityState?.ethernetConnected ?: false - } - Text( - text = - if (ethernetActive) stringResource(R.string.active) - else stringResource(R.string.inactive), - style = - MaterialTheme.typography.bodySmall.copy( - color = MaterialTheme.colorScheme.outline - ), - maxLines = 1, - overflow = TextOverflow.Ellipsis, - ) - }, - onClick = { - viewModel.setTunnelOnEthernet(!autoTunnelState.settings.isTunnelOnEthernetEnabled) - }, - ), - SelectionItem( - leading = { Icon(Icons.Outlined.PublicOff, contentDescription = null) }, - 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 = autoTunnelState.settings.isStopOnNoInternetEnabled, - onClick = { viewModel.setStopOnNoInternetEnabled(it) }, - ) - }, - onClick = { - viewModel.setStopOnNoInternetEnabled( - !autoTunnelState.settings.isStopOnNoInternetEnabled - ) - }, - ), - ) -} 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 87269299..87bf117c 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 @@ -1,7 +1,11 @@ package com.zaneschepke.wireguardautotunnel.ui.screens.autotunnel.components +import android.R.attr.text +import androidx.compose.animation.animateContentSize import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.* +import androidx.compose.foundation.relocation.BringIntoViewRequester +import androidx.compose.foundation.relocation.bringIntoViewRequester import androidx.compose.foundation.text.KeyboardActions import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.material.icons.Icons @@ -12,6 +16,7 @@ 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.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -20,7 +25,6 @@ import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.text.input.KeyboardCapitalization import androidx.compose.ui.unit.dp import com.zaneschepke.wireguardautotunnel.R -import com.zaneschepke.wireguardautotunnel.ui.LocalIsAndroidTV import com.zaneschepke.wireguardautotunnel.ui.common.button.ClickableIconButton import com.zaneschepke.wireguardautotunnel.ui.common.textbox.CustomTextField @@ -33,21 +37,23 @@ fun TrustedNetworkTextBox( onSave: (ssid: String) -> Unit, onValueChange: (network: String) -> Unit, supporting: @Composable () -> Unit, + modifier: Modifier = Modifier, ) { - val isTv = LocalIsAndroidTV.current - Column(verticalArrangement = Arrangement.spacedBy(10.dp)) { + val bringIntoViewRequester = remember { BringIntoViewRequester() } + + LaunchedEffect(trustedNetworks.size) { bringIntoViewRequester.bringIntoView() } + + Column( + verticalArrangement = Arrangement.spacedBy(10.dp), + modifier = modifier.animateContentSize().imePadding(), + ) { FlowRow( - modifier = Modifier.fillMaxWidth(), + modifier = Modifier.fillMaxWidth().animateContentSize(), horizontalArrangement = Arrangement.spacedBy(5.dp, Alignment.CenterHorizontally), ) { trustedNetworks.forEach { ssid -> ClickableIconButton( - onClick = { - if (isTv) { - onDelete(ssid) - } - }, - onIconClick = { onDelete(ssid) }, + onClick = { onDelete(ssid) }, text = ssid, icon = Icons.Filled.Close, ) @@ -70,8 +76,7 @@ fun TrustedNetworkTextBox( }, containerColor = MaterialTheme.colorScheme.surface, supportingText = supporting, - modifier = - Modifier.padding(top = 5.dp, bottom = 10.dp).fillMaxWidth().padding(end = 16.dp), + modifier = Modifier.fillMaxWidth().bringIntoViewRequester(bringIntoViewRequester), singleLine = true, keyboardOptions = KeyboardOptions( diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/autotunnel/components/wifiTunnelingItems.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/autotunnel/components/wifiTunnelingItems.kt deleted file mode 100644 index 50e5cbd0..00000000 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/autotunnel/components/wifiTunnelingItems.kt +++ /dev/null @@ -1,218 +0,0 @@ -package com.zaneschepke.wireguardautotunnel.ui.screens.autotunnel.components - -import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.* -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.outlined.Filter1 -import androidx.compose.material.icons.outlined.Security -import androidx.compose.material.icons.outlined.Wifi -import androidx.compose.material.icons.outlined.WifiFind -import androidx.compose.material3.Icon -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text -import androidx.compose.runtime.* -import androidx.compose.runtime.saveable.rememberSaveable -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.text.style.TextOverflow -import androidx.compose.ui.unit.dp -import com.zaneschepke.wireguardautotunnel.R -import com.zaneschepke.wireguardautotunnel.ui.LocalNavController -import com.zaneschepke.wireguardautotunnel.ui.common.button.ForwardButton -import com.zaneschepke.wireguardautotunnel.ui.common.button.ScaledSwitch -import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SelectionItem -import com.zaneschepke.wireguardautotunnel.ui.common.functions.rememberClipboardHelper -import com.zaneschepke.wireguardautotunnel.ui.navigation.Route -import com.zaneschepke.wireguardautotunnel.ui.screens.settings.components.LearnMoreLinkLabel -import com.zaneschepke.wireguardautotunnel.ui.state.AutoTunnelUiState -import com.zaneschepke.wireguardautotunnel.ui.theme.iconSize -import com.zaneschepke.wireguardautotunnel.util.extensions.asTitleString -import com.zaneschepke.wireguardautotunnel.util.extensions.openWebUrl -import com.zaneschepke.wireguardautotunnel.viewmodel.AutoTunnelViewModel - -@Composable -fun wifiTunnelingItems( - autoTunnelState: AutoTunnelUiState, - viewModel: AutoTunnelViewModel, -): List { - val context = LocalContext.current - val navController = LocalNavController.current - val clipboardHelper = rememberClipboardHelper() - - var currentText by rememberSaveable { mutableStateOf("") } - - LaunchedEffect(autoTunnelState.settings.trustedNetworkSSIDs) { currentText = "" } - - val baseItems = - listOf( - SelectionItem( - leading = { Icon(Icons.Outlined.Wifi, contentDescription = null) }, - title = { - Text( - stringResource(R.string.tunnel_on_wifi), - style = - MaterialTheme.typography.bodyMedium.copy( - MaterialTheme.colorScheme.onSurface - ), - ) - }, - trailing = { - ScaledSwitch( - checked = autoTunnelState.settings.isTunnelOnWifiEnabled, - onClick = { viewModel.setAutoTunnelOnWifiEnabled(it) }, - ) - }, - description = { - val wifiInfo by - remember(autoTunnelState.connectivityState) { - derivedStateOf { - autoTunnelState.connectivityState - ?.wifiState - ?.takeIf { it.connected } - .let { Pair(it?.ssid, it?.securityType) } - } - } - val (wifiName, securityType) = wifiInfo - Column { - Text( - text = - wifiName?.let { stringResource(R.string.wifi_name_template, it) } - ?: stringResource(R.string.inactive), - style = - MaterialTheme.typography.bodySmall.copy( - color = MaterialTheme.colorScheme.outline - ), - maxLines = 1, - overflow = TextOverflow.Ellipsis, - modifier = - Modifier.clickable { wifiName?.let { clipboardHelper.copy(it) } }, - ) - securityType?.let { - Text( - text = stringResource(R.string.security_template, it.name), - style = - MaterialTheme.typography.bodySmall.copy( - color = MaterialTheme.colorScheme.outline - ), - maxLines = 1, - overflow = TextOverflow.Ellipsis, - ) - } - } - }, - onClick = { - viewModel.setAutoTunnelOnWifiEnabled( - !autoTunnelState.settings.isTunnelOnWifiEnabled - ) - }, - ) - ) - - return if (autoTunnelState.settings.isTunnelOnWifiEnabled) { - baseItems + - listOf( - SelectionItem( - leading = { Icon(Icons.Outlined.WifiFind, contentDescription = null) }, - title = { - Text( - stringResource(R.string.wifi_detection_method), - style = - MaterialTheme.typography.bodyMedium.copy( - MaterialTheme.colorScheme.onSurface - ), - ) - }, - description = { - Text( - stringResource( - R.string.current_template, - autoTunnelState.settings.wifiDetectionMethod.asTitleString(context), - ), - style = - MaterialTheme.typography.bodySmall.copy( - MaterialTheme.colorScheme.outline - ), - ) - }, - trailing = { ForwardButton { navController.push(Route.WifiDetectionMethod) } }, - onClick = { navController.push(Route.WifiDetectionMethod) }, - ), - SelectionItem( - leading = { Icon(Icons.Outlined.Filter1, contentDescription = null) }, - title = { - Text( - stringResource(R.string.use_wildcards), - style = - MaterialTheme.typography.bodyMedium.copy( - MaterialTheme.colorScheme.onSurface - ), - ) - }, - description = { - LearnMoreLinkLabel( - { context.openWebUrl(it) }, - stringResource(R.string.docs_wildcards), - ) - }, - trailing = { - ScaledSwitch( - checked = autoTunnelState.settings.isWildcardsEnabled, - onClick = { viewModel.setWildcardsEnabled(it) }, - ) - }, - onClick = { - viewModel.setWildcardsEnabled(!autoTunnelState.settings.isWildcardsEnabled) - }, - ), - SelectionItem( - title = { - Row( - verticalAlignment = Alignment.CenterVertically, - modifier = Modifier.fillMaxWidth().padding(vertical = 4.dp), - ) { - Row( - verticalAlignment = Alignment.CenterVertically, - modifier = Modifier.weight(4f, false).fillMaxWidth(), - ) { - val icon = Icons.Outlined.Security - Icon(icon, icon.name, modifier = Modifier.size(iconSize)) - Column( - horizontalAlignment = Alignment.Start, - verticalArrangement = - Arrangement.spacedBy(2.dp, Alignment.CenterVertically), - modifier = - Modifier.fillMaxWidth() - .padding(start = 16.dp) - .padding(vertical = 6.dp), - ) { - Text( - stringResource(R.string.trusted_wifi_names), - style = - MaterialTheme.typography.bodyMedium.copy( - MaterialTheme.colorScheme.onSurface - ), - ) - } - } - } - }, - description = { - TrustedNetworkTextBox( - autoTunnelState.settings.trustedNetworkSSIDs, - onDelete = { viewModel.removeTrustedNetworkName(it) }, - currentText = currentText, - onSave = { ssid -> viewModel.saveTrustedNetworkName(ssid) }, - onValueChange = { currentText = it }, - supporting = { - if (autoTunnelState.settings.isWildcardsEnabled) WildcardsLabel() - }, - ) - }, - ), - ) - } else { - baseItems - } -} diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/autotunnel/disclosure/LocationDisclosureScreen.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/autotunnel/disclosure/LocationDisclosureScreen.kt index e52acf6a..36bb6b33 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/autotunnel/disclosure/LocationDisclosureScreen.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/autotunnel/disclosure/LocationDisclosureScreen.kt @@ -9,19 +9,24 @@ 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.material.icons.Icons +import androidx.compose.material.icons.automirrored.outlined.DirectionsWalk +import androidx.compose.material.icons.automirrored.outlined.Launch +import androidx.compose.material.icons.outlined.Settings +import androidx.compose.material3.Icon import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel +import com.zaneschepke.wireguardautotunnel.R import com.zaneschepke.wireguardautotunnel.ui.LocalNavController -import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SurfaceSelectionGroupButton +import com.zaneschepke.wireguardautotunnel.ui.common.button.SurfaceRow import com.zaneschepke.wireguardautotunnel.ui.navigation.Route import com.zaneschepke.wireguardautotunnel.ui.screens.autotunnel.disclosure.components.LocationDisclosureHeader -import com.zaneschepke.wireguardautotunnel.ui.screens.autotunnel.disclosure.components.appSettingsItem -import com.zaneschepke.wireguardautotunnel.ui.screens.autotunnel.disclosure.components.skipItem import com.zaneschepke.wireguardautotunnel.viewmodel.AutoTunnelViewModel @Composable @@ -43,21 +48,29 @@ fun LocationDisclosureScreen(viewModel: AutoTunnelViewModel = hiltViewModel()) { Column( horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.spacedBy(18.dp, Alignment.Top), - modifier = Modifier.fillMaxSize().padding(top = 18.dp).padding(horizontal = 16.dp), + modifier = Modifier.fillMaxSize().padding(top = 18.dp), ) { - LocationDisclosureHeader() - SurfaceSelectionGroupButton( - items = - listOf( - appSettingsItem { - val intent = - Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS).apply { - data = Uri.fromParts("package", context.packageName, null) - } - settingsLauncher.launch(intent) - } - ) - ) - SurfaceSelectionGroupButton(items = listOf(skipItem { goToAutoTunnel() })) + LocationDisclosureHeader(Modifier.padding(horizontal = 16.dp)) + Column { + SurfaceRow( + leading = { Icon(Icons.Outlined.Settings, contentDescription = null) }, + title = stringResource(R.string.launch_app_settings), + trailing = { Icon(Icons.AutoMirrored.Outlined.Launch, null) }, + onClick = { + val intent = + Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS).apply { + data = Uri.fromParts("package", context.packageName, null) + } + settingsLauncher.launch(intent) + }, + ) + SurfaceRow( + leading = { + Icon(Icons.AutoMirrored.Outlined.DirectionsWalk, contentDescription = null) + }, + title = stringResource(R.string.skip), + onClick = { goToAutoTunnel() }, + ) + } } } diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/autotunnel/disclosure/components/AppSettingsItem.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/autotunnel/disclosure/components/AppSettingsItem.kt deleted file mode 100644 index f102699d..00000000 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/autotunnel/disclosure/components/AppSettingsItem.kt +++ /dev/null @@ -1,27 +0,0 @@ -package com.zaneschepke.wireguardautotunnel.ui.screens.autotunnel.disclosure.components - -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.outlined.LocationOn -import androidx.compose.material3.Icon -import androidx.compose.runtime.Composable -import androidx.compose.ui.res.stringResource -import com.zaneschepke.wireguardautotunnel.R -import com.zaneschepke.wireguardautotunnel.ui.common.button.LaunchButton -import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SelectionItem -import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SelectionItemLabel -import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SelectionLabelType - -@Composable -fun appSettingsItem(onClick: () -> Unit): SelectionItem { - return SelectionItem( - leading = { Icon(Icons.Outlined.LocationOn, contentDescription = null) }, - title = { - SelectionItemLabel( - stringResource(R.string.launch_app_settings), - labelType = SelectionLabelType.TITLE, - ) - }, - trailing = { LaunchButton { onClick() } }, - onClick = onClick, - ) -} diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/autotunnel/disclosure/components/LocationDisclosureHeader.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/autotunnel/disclosure/components/LocationDisclosureHeader.kt index 8a8fd07a..9a702901 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/autotunnel/disclosure/components/LocationDisclosureHeader.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/autotunnel/disclosure/components/LocationDisclosureHeader.kt @@ -1,6 +1,8 @@ package com.zaneschepke.wireguardautotunnel.ui.screens.autotunnel.disclosure.components -import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.size import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.PermScanWifi @@ -8,26 +10,37 @@ import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import com.zaneschepke.wireguardautotunnel.R @Composable -fun LocationDisclosureHeader() { - val icon = Icons.Rounded.PermScanWifi - Icon( - imageVector = icon, - contentDescription = icon.name, - modifier = Modifier.padding(24.dp).size(100.dp), - ) - Text( - text = stringResource(R.string.prominent_background_location_title), - style = MaterialTheme.typography.titleMedium.copy(fontWeight = FontWeight.Bold), - ) - Text( - text = stringResource(R.string.prominent_background_location_message), - style = MaterialTheme.typography.bodyMedium, - ) +fun LocationDisclosureHeader(modifier: Modifier = Modifier) { + Column( + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.spacedBy(16.dp), + modifier = modifier, + ) { + Icon( + imageVector = Icons.Rounded.PermScanWifi, + contentDescription = null, + modifier = Modifier.size(100.dp), + ) + Text( + text = stringResource(R.string.prominent_background_location_title), + style = MaterialTheme.typography.titleMedium.copy(fontWeight = FontWeight.Bold), + textAlign = TextAlign.Center, + modifier = Modifier.fillMaxWidth(), + ) + Text( + text = stringResource(R.string.prominent_background_location_message), + style = MaterialTheme.typography.bodyMedium, + textAlign = TextAlign.Start, + modifier = Modifier.fillMaxWidth(), + ) + } } diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/autotunnel/disclosure/components/SkipItem.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/autotunnel/disclosure/components/SkipItem.kt deleted file mode 100644 index 422c8763..00000000 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/autotunnel/disclosure/components/SkipItem.kt +++ /dev/null @@ -1,20 +0,0 @@ -package com.zaneschepke.wireguardautotunnel.ui.screens.autotunnel.disclosure.components - -import androidx.compose.runtime.Composable -import androidx.compose.ui.res.stringResource -import com.zaneschepke.wireguardautotunnel.R -import com.zaneschepke.wireguardautotunnel.ui.common.button.ForwardButton -import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SelectionItem -import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SelectionItemLabel -import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SelectionLabelType - -@Composable -fun skipItem(onClick: () -> Unit): SelectionItem { - return SelectionItem( - title = { - SelectionItemLabel(stringResource(R.string.skip), labelType = SelectionLabelType.TITLE) - }, - trailing = { ForwardButton { onClick() } }, - onClick = onClick, - ) -} diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/autotunnel/preferences/PreferredTunnelScreen.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/autotunnel/preferences/PreferredTunnelScreen.kt new file mode 100644 index 00000000..a2309efd --- /dev/null +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/autotunnel/preferences/PreferredTunnelScreen.kt @@ -0,0 +1,271 @@ +package com.zaneschepke.wireguardautotunnel.ui.screens.autotunnel.preferences + +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.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 +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Close +import androidx.compose.material.icons.outlined.Add +import androidx.compose.material.icons.outlined.Check +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.input.pointer.pointerInput +import androidx.compose.ui.platform.LocalSoftwareKeyboardController +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.SpanStyle +import androidx.compose.ui.text.buildAnnotatedString +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.input.ImeAction +import androidx.compose.ui.text.input.KeyboardCapitalization +import androidx.compose.ui.text.withStyle +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.LocalNavController +import com.zaneschepke.wireguardautotunnel.ui.common.button.ClickableIconButton +import com.zaneschepke.wireguardautotunnel.ui.common.button.SurfaceRow +import com.zaneschepke.wireguardautotunnel.ui.common.label.GroupLabel +import com.zaneschepke.wireguardautotunnel.ui.common.textbox.CustomTextField +import com.zaneschepke.wireguardautotunnel.ui.navigation.TunnelNetwork +import com.zaneschepke.wireguardautotunnel.ui.screens.autotunnel.components.WildcardsLabel +import com.zaneschepke.wireguardautotunnel.viewmodel.AutoTunnelViewModel + +@Composable +fun PreferredTunnelScreen( + tunnelNetwork: TunnelNetwork, + viewModel: AutoTunnelViewModel = hiltViewModel(), +) { + val navController = LocalNavController.current + + val autoTunnelState by viewModel.container.stateFlow.collectAsStateWithLifecycle() + + if (autoTunnelState.isLoading) return + + var selectedTunnel by remember { mutableStateOf(null) } + + val currentSelection = + remember(autoTunnelState.tunnels) { + when (tunnelNetwork) { + TunnelNetwork.MOBILE_DATA -> + autoTunnelState.tunnels.firstOrNull { it.isMobileDataTunnel } + TunnelNetwork.ETHERNET -> + autoTunnelState.tunnels.firstOrNull { it.isEthernetTunnel } + TunnelNetwork.WIFI -> null + } + } + + LazyColumn( + horizontalAlignment = Alignment.Start, + verticalArrangement = Arrangement.Top, + modifier = + Modifier.pointerInput(Unit) { + if (autoTunnelState.tunnels.isEmpty()) return@pointerInput + } + .overscroll(rememberOverscrollEffect()), + state = rememberLazyListState(), + userScrollEnabled = true, + reverseLayout = false, + flingBehavior = ScrollableDefaults.flingBehavior(), + ) { + item { GroupLabel(stringResource(R.string.tunnels), Modifier.padding(horizontal = 16.dp)) } + if (tunnelNetwork != TunnelNetwork.WIFI) { + item { + SurfaceRow( + title = + buildAnnotatedString { + withStyle(style = SpanStyle(fontWeight = FontWeight.Bold)) { + append(stringResource(R.string._default)) + } + }, + trailing = + if (currentSelection == null) { + { + Icon( + Icons.Outlined.Check, + stringResource(id = R.string.selected), + tint = MaterialTheme.colorScheme.primary, + ) + } + } else null, + onClick = { + when (tunnelNetwork) { + TunnelNetwork.MOBILE_DATA -> { + viewModel.setPreferredMobileDataTunnel(null) + navController.pop() + } + TunnelNetwork.ETHERNET -> { + viewModel.setPreferredEthernetTunnel(null) + navController.pop() + } + TunnelNetwork.WIFI -> Unit + } + }, + ) + } + } + items(autoTunnelState.tunnels, key = { it.id }) { tunnel -> + SurfaceRow( + title = tunnel.tunName, + trailing = + if (currentSelection?.id == tunnel.id) { + { + Icon( + Icons.Outlined.Check, + stringResource(id = R.string.selected), + tint = MaterialTheme.colorScheme.primary, + ) + } + } else null, + onClick = { + when (tunnelNetwork) { + TunnelNetwork.MOBILE_DATA -> { + viewModel.setPreferredMobileDataTunnel(tunnel) + navController.pop() + } + + TunnelNetwork.ETHERNET -> { + viewModel.setPreferredEthernetTunnel(tunnel) + navController.pop() + } + + TunnelNetwork.WIFI -> { + selectedTunnel = tunnel + } + } + }, + expandedContent = + if (tunnelNetwork == TunnelNetwork.WIFI) { + { + Column(verticalArrangement = Arrangement.spacedBy(10.dp)) { + FlowRow( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = + Arrangement.spacedBy(5.dp, Alignment.CenterHorizontally), + ) { + tunnel.tunnelNetworks.forEach { ssid -> + ClickableIconButton( + onClick = { + viewModel.removeTunnelNetwork(tunnel, ssid) + }, + text = ssid, + icon = Icons.Filled.Close, + ) + } + } + 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() + } + + CustomTextField( + textStyle = + MaterialTheme.typography.bodySmall.copy( + color = MaterialTheme.colorScheme.onSurface + ), + value = currentText, + onValueChange = { currentText = it }, + interactionSource = remember { MutableInteractionSource() }, + label = { + Text( + stringResource(R.string.add_wifi_name), + color = MaterialTheme.colorScheme.onSurface, + style = MaterialTheme.typography.labelMedium, + ) + }, + containerColor = MaterialTheme.colorScheme.surface, + supportingText = + if (autoTunnelState.settings.isWildcardsEnabled) { + { WildcardsLabel() } + } else null, + modifier = + Modifier.fillMaxWidth() + .focusRequester(focusRequester) + .bringIntoViewRequester(bringIntoViewRequester) + .imePadding(), + singleLine = true, + keyboardOptions = + KeyboardOptions( + capitalization = KeyboardCapitalization.None, + imeAction = ImeAction.Done, + ), + keyboardActions = + KeyboardActions( + onDone = { + viewModel.addTunnelNetwork(tunnel, currentText) + currentText = "" + } + ), + trailing = { + if (currentText != "") { + IconButton( + onClick = { + viewModel.addTunnelNetwork( + tunnel, + currentText, + ) + currentText = "" + } + ) { + val icon = Icons.Outlined.Add + Icon( + imageVector = icon, + contentDescription = + stringResource( + R.string + .trusted_ssid_value_description + ), + tint = MaterialTheme.colorScheme.primary, + ) + } + } + }, + ) + } + } + } + } else null, + ) + } + } +} diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/autotunnel/preferences/wifi/WifiSettingsScreen.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/autotunnel/preferences/wifi/WifiSettingsScreen.kt new file mode 100644 index 00000000..4b200c0d --- /dev/null +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/autotunnel/preferences/wifi/WifiSettingsScreen.kt @@ -0,0 +1,117 @@ +package com.zaneschepke.wireguardautotunnel.ui.screens.autotunnel.preferences.wifi + +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.rememberScrollState +import androidx.compose.foundation.verticalScroll +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.outlined.Filter1 +import androidx.compose.material.icons.outlined.Map +import androidx.compose.material.icons.outlined.WifiFind +import androidx.compose.material3.Icon +import androidx.compose.runtime.* +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.stringResource +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.LocalNavController +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.navigation.Route +import com.zaneschepke.wireguardautotunnel.ui.navigation.TunnelNetwork +import com.zaneschepke.wireguardautotunnel.ui.screens.autotunnel.components.TrustedNetworkTextBox +import com.zaneschepke.wireguardautotunnel.ui.screens.autotunnel.components.WildcardsLabel +import com.zaneschepke.wireguardautotunnel.ui.screens.settings.components.LearnMoreLinkLabel +import com.zaneschepke.wireguardautotunnel.util.extensions.asTitleString +import com.zaneschepke.wireguardautotunnel.util.extensions.openWebUrl +import com.zaneschepke.wireguardautotunnel.viewmodel.AutoTunnelViewModel + +@Composable +fun WifiSettingsScreen(viewModel: AutoTunnelViewModel = hiltViewModel()) { + val context = LocalContext.current + val navController = LocalNavController.current + + val autoTunnelState by viewModel.container.stateFlow.collectAsStateWithLifecycle() + + var currentText by rememberSaveable { mutableStateOf("") } + + LaunchedEffect(autoTunnelState.settings.trustedNetworkSSIDs) { currentText = "" } + + Column( + horizontalAlignment = Alignment.Start, + verticalArrangement = Arrangement.spacedBy(16.dp, Alignment.Top), + modifier = Modifier.fillMaxSize().verticalScroll(rememberScrollState()), + ) { + Column { + GroupLabel(stringResource(R.string.general), Modifier.padding(horizontal = 16.dp)) + SurfaceRow( + leading = { Icon(Icons.Outlined.WifiFind, contentDescription = null) }, + title = stringResource(R.string.wifi_detection_method), + description = { + DescriptionText( + stringResource( + R.string.current_template, + autoTunnelState.settings.wifiDetectionMethod.asTitleString(context), + ) + ) + }, + onClick = { navController.push(Route.WifiDetectionMethod) }, + ) + SurfaceRow( + leading = { Icon(Icons.Outlined.Filter1, contentDescription = null) }, + title = stringResource(R.string.use_wildcards), + description = { + LearnMoreLinkLabel( + { context.openWebUrl(it) }, + stringResource(R.string.docs_wildcards), + ) + }, + trailing = { + ScaledSwitch( + checked = autoTunnelState.settings.isWildcardsEnabled, + onClick = { viewModel.setWildcardsEnabled(it) }, + ) + }, + onClick = { + viewModel.setWildcardsEnabled(!autoTunnelState.settings.isWildcardsEnabled) + }, + ) + } + Column { + GroupLabel(stringResource(R.string.networks), Modifier.padding(horizontal = 16.dp)) + SurfaceRow( + title = stringResource(R.string.trusted_wifi_names), + expandedContent = { + TrustedNetworkTextBox( + autoTunnelState.settings.trustedNetworkSSIDs, + onDelete = { viewModel.removeTrustedNetworkName(it) }, + currentText = currentText, + onSave = { ssid -> viewModel.saveTrustedNetworkName(ssid) }, + onValueChange = { currentText = it }, + supporting = { + if (autoTunnelState.settings.isWildcardsEnabled) WildcardsLabel() + }, + modifier = Modifier.padding(top = 4.dp), + ) + }, + ) + } + Column { + GroupLabel(stringResource(R.string.tunnels), Modifier.padding(horizontal = 16.dp)) + SurfaceRow( + leading = { Icon(Icons.Outlined.Map, contentDescription = null) }, + title = stringResource(R.string.tunnel_mapping), + onClick = { navController.push(Route.PreferredTunnel(TunnelNetwork.WIFI)) }, + ) + } + } +} 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 e5bc698c..1bcf9723 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 @@ -1,51 +1,66 @@ package com.zaneschepke.wireguardautotunnel.ui.screens.settings -import androidx.compose.foundation.clickable -import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Spacer 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.automirrored.outlined.ViewQuilt +import androidx.compose.material.icons.outlined.Android +import androidx.compose.material.icons.outlined.Dns +import androidx.compose.material.icons.outlined.ExpandMore +import androidx.compose.material.icons.outlined.Lan +import androidx.compose.material.icons.outlined.MonitorHeart +import androidx.compose.material.icons.outlined.Pin +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text 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.vector.ImageVector import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.platform.LocalFocusManager +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.res.vectorResource 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.data.model.AppMode -import com.zaneschepke.wireguardautotunnel.ui.LocalIsAndroidTV import com.zaneschepke.wireguardautotunnel.ui.LocalNavController import com.zaneschepke.wireguardautotunnel.ui.LocalSharedVm -import com.zaneschepke.wireguardautotunnel.ui.common.SectionDivider -import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SurfaceSelectionGroupButton +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.label.GroupLabel +import com.zaneschepke.wireguardautotunnel.ui.common.text.DescriptionText import com.zaneschepke.wireguardautotunnel.ui.navigation.Route import com.zaneschepke.wireguardautotunnel.ui.screens.settings.components.* import com.zaneschepke.wireguardautotunnel.ui.screens.settings.proxy.compoents.AppModeBottomSheet import com.zaneschepke.wireguardautotunnel.ui.sideeffect.LocalSideEffect import com.zaneschepke.wireguardautotunnel.util.StringValue import com.zaneschepke.wireguardautotunnel.util.extensions.asString +import com.zaneschepke.wireguardautotunnel.util.extensions.asTitleString +import com.zaneschepke.wireguardautotunnel.util.extensions.capitalize import com.zaneschepke.wireguardautotunnel.viewmodel.SettingsViewModel +import java.util.Locale import org.orbitmvi.orbit.compose.collectSideEffect @Composable fun SettingsScreen(viewModel: SettingsViewModel = hiltViewModel()) { - val isTv = LocalIsAndroidTV.current val context = LocalContext.current val navController = LocalNavController.current - val focusManager = LocalFocusManager.current val sharedViewModel = LocalSharedVm.current - val interactionSource = remember { MutableInteractionSource() } + + val locale = remember { Locale.getDefault() } val settingsState by viewModel.container.stateFlow.collectAsStateWithLifecycle() + if (settingsState.isLoading) return + var showBackupSheet by rememberSaveable { mutableStateOf(false) } var showAppModeSheet by rememberSaveable { mutableStateOf(false) } @@ -53,8 +68,11 @@ fun SettingsScreen(viewModel: SettingsViewModel = hiltViewModel()) { rememberSaveable(settingsState.settings.appMode) { mutableStateOf(settingsState.settings.appMode) } - - if (!settingsState.stateInitialized) return + val monitoringEnabled by + rememberSaveable(appMode) { + mutableStateOf(appMode == AppMode.VPN || appMode == AppMode.KERNEL) + } + val dnsEnabled by rememberSaveable(appMode) { mutableStateOf(appMode != AppMode.KERNEL) } sharedViewModel.collectSideEffect { sideEffect -> when (sideEffect) { @@ -81,101 +99,146 @@ fun SettingsScreen(viewModel: SettingsViewModel = hiltViewModel()) { Column( horizontalAlignment = Alignment.Start, - verticalArrangement = Arrangement.spacedBy(12.dp, Alignment.Top), - modifier = - Modifier.verticalScroll(rememberScrollState()) - .fillMaxSize() - .then( - if (!isTv) { - Modifier.clickable( - indication = null, - interactionSource = interactionSource, - onClick = { focusManager.clearFocus() }, - ) - } else { - Modifier - } - ) - .padding(horizontal = 16.dp), + verticalArrangement = Arrangement.spacedBy(16.dp, Alignment.Top), + modifier = Modifier.verticalScroll(rememberScrollState()).fillMaxSize(), ) { - SurfaceSelectionGroupButton( - listOf(appModeItem(settingsState.settings.appMode) { showAppModeSheet = true }) - ) - SectionDivider() - SurfaceSelectionGroupButton( - items = - buildList { - if (appMode == AppMode.LOCK_DOWN) { - add( - lanTrafficItem(settingsState.settings.isLanOnKillSwitchEnabled) { - viewModel.setLanKillSwitchEnabled(it) - } + Column { + GroupLabel( + stringResource(R.string.tunnel).capitalize(locale), + modifier = Modifier.padding(horizontal = 16.dp), + ) + SurfaceRow( + leading = { + Icon(ImageVector.vectorResource(R.drawable.sdk), contentDescription = null) + }, + trailing = { + Icon( + Icons.Outlined.ExpandMore, + contentDescription = stringResource(R.string.select), + ) + }, + title = stringResource(R.string.backend_mode), + description = { + DescriptionText( + stringResource(R.string.current_template, appMode.asTitleString(context)) + ) + }, + onClick = { showAppModeSheet = true }, + ) + if (appMode == AppMode.LOCK_DOWN) { + SurfaceRow( + leading = { Icon(Icons.Outlined.Lan, contentDescription = null) }, + title = stringResource(R.string.allow_lan_traffic), + description = { + Text( + text = stringResource(R.string.bypass_lan_for_kill_switch), + style = + MaterialTheme.typography.bodySmall.copy( + MaterialTheme.colorScheme.outline + ), ) + }, + trailing = { + ScaledSwitch( + checked = settingsState.settings.isLanOnKillSwitchEnabled, + onClick = { viewModel.setLanKillSwitchEnabled(it) }, + ) + }, + onClick = { + viewModel.setLanKillSwitchEnabled( + !settingsState.settings.isLanOnKillSwitchEnabled + ) + }, + ) + } + + 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) }, + title = stringResource(R.string.dns_settings), + enabled = dnsEnabled, + onClick = { + if (dnsEnabled) navController.push(Route.Dns) + else + sharedViewModel.showSnackMessage( + StringValue.StringResource( + R.string.mode_disabled_template, + appMode.asString(context), + ) + ) + }, + ) + SurfaceRow( + leading = { + Icon(ImageVector.vectorResource(R.drawable.globe), contentDescription = null) + }, + title = stringResource(R.string.global_overrides), + trailing = { modifier -> + SwitchWithDivider( + checked = settingsState.settings.isTunnelGlobalsEnabled, + onClick = { viewModel.setTunnelGlobals(it) }, + modifier = modifier, + ) + }, + onClick = { + settingsState.globalTunnelConf?.let { + navController.push(Route.TunnelGlobals(it.id)) } - add( - tunnelMonitoringItem( - appMode, - onClick = { navController.push(Route.TunnelMonitoring) }, - ) { - sharedViewModel.showSnackMessage( - StringValue.StringResource( - R.string.mode_disabled_template, - appMode.asString(context), - ) - ) - } - ) - add( - dnsSettingsItem(appMode, onClick = { navController.push(Route.Dns) }) { - sharedViewModel.showSnackMessage( - StringValue.StringResource( - R.string.mode_disabled_template, - appMode.asString(context), - ) - ) - } - ) - add( - tunnelGlobalsSettingItem( - settingsState.settings.isTunnelGlobalsEnabled, - onClick = viewModel::setTunnelGlobals, - ) { - settingsState.globalTunnelConf?.let { - navController.push(Route.TunnelGlobals(it.id)) - } - } - ) - if (showProxySettings) - add(proxYSettingsItem { navController.push(Route.ProxySettings) }) - } - ) - SectionDivider() - SurfaceSelectionGroupButton( - listOf(systemFeaturesItem { navController.push(Route.SystemFeatures) }) - ) - SectionDivider() - SurfaceSelectionGroupButton( - items = - buildList { - add(appearanceItem { navController.push(Route.Appearance) }) - add( - localLoggingItem(settingsState.isLocalLoggingEnabled) { - viewModel.setLocalLogging(it) - } - ) - if (settingsState.isLocalLoggingEnabled) - add(readLogsItem { navController.push(Route.Logs) }) - add( - pinLockItem(settingsState.isPinLockEnabled) { enabled -> - if (enabled) { + }, + ) + if (showProxySettings) { + SurfaceRow( + leading = { Icon(ImageVector.vectorResource(R.drawable.proxy), null) }, + title = stringResource(R.string.proxy_settings), + onClick = { navController.push(Route.ProxySettings) }, + ) + } + } + 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) + }, + title = stringResource(R.string.appearance), + onClick = { navController.push(Route.Appearance) }, + ) + SurfaceRow( + leading = { Icon(Icons.Outlined.Pin, contentDescription = null) }, + title = stringResource(R.string.enable_app_lock), + trailing = { + ScaledSwitch( + checked = settingsState.isPinLockEnabled, + onClick = { + if (it) { navController.push(Route.Lock) } else { sharedViewModel.setPinLockEnabled(false) } - } + }, ) - } - ) - Spacer(Modifier.height(80.dp)) + }, + onClick = { + if (!settingsState.isPinLockEnabled) { + navController.push(Route.Lock) + } else { + sharedViewModel.setPinLockEnabled(false) + } + }, + ) + } } } diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/appearance/AppearanceScreen.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/appearance/AppearanceScreen.kt index 482595d8..4c6cb9d1 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/appearance/AppearanceScreen.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/appearance/AppearanceScreen.kt @@ -3,28 +3,50 @@ package com.zaneschepke.wireguardautotunnel.ui.screens.settings.appearance 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.material.icons.Icons +import androidx.compose.material.icons.automirrored.outlined.Launch +import androidx.compose.material.icons.outlined.Contrast +import androidx.compose.material.icons.outlined.Notifications +import androidx.compose.material.icons.outlined.Translate +import androidx.compose.material3.Icon import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp -import com.zaneschepke.wireguardautotunnel.ui.common.SectionDivider -import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SurfaceSelectionGroupButton -import com.zaneschepke.wireguardautotunnel.ui.screens.settings.appearance.components.DisplayThemeItem -import com.zaneschepke.wireguardautotunnel.ui.screens.settings.appearance.components.LanguageItem -import com.zaneschepke.wireguardautotunnel.ui.screens.settings.appearance.components.NotificationsItem +import com.zaneschepke.wireguardautotunnel.R +import com.zaneschepke.wireguardautotunnel.ui.LocalNavController +import com.zaneschepke.wireguardautotunnel.ui.common.button.SurfaceRow +import com.zaneschepke.wireguardautotunnel.ui.navigation.Route +import com.zaneschepke.wireguardautotunnel.util.extensions.launchNotificationSettings @Composable fun AppearanceScreen() { Column( horizontalAlignment = Alignment.Start, - verticalArrangement = Arrangement.spacedBy(12.dp, Alignment.Top), - modifier = Modifier.fillMaxSize().padding(horizontal = 16.dp), + verticalArrangement = Arrangement.spacedBy(16.dp, Alignment.Top), + modifier = Modifier.fillMaxSize(), ) { - SurfaceSelectionGroupButton(items = listOf(LanguageItem())) - SectionDivider() - SurfaceSelectionGroupButton(items = listOf(NotificationsItem())) - SectionDivider() - SurfaceSelectionGroupButton(items = listOf(DisplayThemeItem())) + val navController = LocalNavController.current + val context = LocalContext.current + Column { + SurfaceRow( + leading = { Icon(Icons.Outlined.Translate, contentDescription = null) }, + title = stringResource(R.string.language), + onClick = { navController.push(Route.Language) }, + ) + SurfaceRow( + leading = { Icon(Icons.Outlined.Notifications, contentDescription = null) }, + title = stringResource(R.string.notifications), + trailing = { Icon(Icons.AutoMirrored.Outlined.Launch, null) }, + onClick = { context.launchNotificationSettings() }, + ) + SurfaceRow( + leading = { Icon(Icons.Outlined.Contrast, contentDescription = null) }, + title = stringResource(R.string.display_theme), + onClick = { navController.push(Route.Display) }, + ) + } } } diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/appearance/components/DisplayItem.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/appearance/components/DisplayItem.kt deleted file mode 100644 index 67814b61..00000000 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/appearance/components/DisplayItem.kt +++ /dev/null @@ -1,31 +0,0 @@ -package com.zaneschepke.wireguardautotunnel.ui.screens.settings.appearance.components - -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.outlined.Contrast -import androidx.compose.material3.Icon -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.ui.res.stringResource -import com.zaneschepke.wireguardautotunnel.R -import com.zaneschepke.wireguardautotunnel.ui.LocalNavController -import com.zaneschepke.wireguardautotunnel.ui.common.button.ForwardButton -import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SelectionItem -import com.zaneschepke.wireguardautotunnel.ui.navigation.Route - -@Composable -fun DisplayThemeItem(): SelectionItem { - val navController = LocalNavController.current - return SelectionItem( - leading = { Icon(Icons.Outlined.Contrast, contentDescription = null) }, - title = { - Text( - text = stringResource(R.string.display_theme), - style = - MaterialTheme.typography.bodyMedium.copy(MaterialTheme.colorScheme.onSurface), - ) - }, - trailing = { ForwardButton { navController.push(Route.Display) } }, - onClick = { navController.push(Route.Display) }, - ) -} diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/appearance/components/LanguageItem.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/appearance/components/LanguageItem.kt deleted file mode 100644 index 5a6d3f8a..00000000 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/appearance/components/LanguageItem.kt +++ /dev/null @@ -1,31 +0,0 @@ -package com.zaneschepke.wireguardautotunnel.ui.screens.settings.appearance.components - -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.outlined.Translate -import androidx.compose.material3.Icon -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.ui.res.stringResource -import com.zaneschepke.wireguardautotunnel.R -import com.zaneschepke.wireguardautotunnel.ui.LocalNavController -import com.zaneschepke.wireguardautotunnel.ui.common.button.ForwardButton -import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SelectionItem -import com.zaneschepke.wireguardautotunnel.ui.navigation.Route - -@Composable -fun LanguageItem(): SelectionItem { - val navController = LocalNavController.current - return SelectionItem( - leading = { Icon(Icons.Outlined.Translate, contentDescription = null) }, - title = { - Text( - text = stringResource(R.string.language), - style = - MaterialTheme.typography.bodyMedium.copy(MaterialTheme.colorScheme.onSurface), - ) - }, - trailing = { ForwardButton { navController.push(Route.Language) } }, - onClick = { navController.push(Route.Language) }, - ) -} diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/appearance/components/NotificationItem.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/appearance/components/NotificationItem.kt deleted file mode 100644 index 79f9d4a6..00000000 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/appearance/components/NotificationItem.kt +++ /dev/null @@ -1,31 +0,0 @@ -package com.zaneschepke.wireguardautotunnel.ui.screens.settings.appearance.components - -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.outlined.Notifications -import androidx.compose.material3.Icon -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.res.stringResource -import com.zaneschepke.wireguardautotunnel.R -import com.zaneschepke.wireguardautotunnel.ui.common.button.LaunchButton -import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SelectionItem -import com.zaneschepke.wireguardautotunnel.util.extensions.launchNotificationSettings - -@Composable -fun NotificationsItem(): SelectionItem { - val context = LocalContext.current - return SelectionItem( - leading = { Icon(Icons.Outlined.Notifications, contentDescription = null) }, - title = { - Text( - text = stringResource(R.string.notifications), - style = - MaterialTheme.typography.bodyMedium.copy(MaterialTheme.colorScheme.onSurface), - ) - }, - trailing = { LaunchButton { context.launchNotificationSettings() } }, - onClick = { context.launchNotificationSettings() }, - ) -} diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/appearance/display/DisplayScreen.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/appearance/display/DisplayScreen.kt index 3d6dad43..52eee9d7 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/appearance/display/DisplayScreen.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/appearance/display/DisplayScreen.kt @@ -3,17 +3,19 @@ package com.zaneschepke.wireguardautotunnel.ui.screens.settings.appearance.displ 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.material.icons.Icons +import androidx.compose.material.icons.outlined.Check +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource -import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.zaneschepke.wireguardautotunnel.R import com.zaneschepke.wireguardautotunnel.ui.LocalSharedVm -import com.zaneschepke.wireguardautotunnel.ui.common.button.IconSurfaceButton +import com.zaneschepke.wireguardautotunnel.ui.common.button.SurfaceRow import com.zaneschepke.wireguardautotunnel.ui.theme.Theme @Composable @@ -25,8 +27,8 @@ fun DisplayScreen() { Column( horizontalAlignment = Alignment.Start, - verticalArrangement = Arrangement.spacedBy(12.dp, Alignment.Top), - modifier = Modifier.fillMaxSize().padding(horizontal = 16.dp), + verticalArrangement = Arrangement.Top, + modifier = Modifier.fillMaxSize(), ) { enumValues().forEach { val title = @@ -38,10 +40,19 @@ fun DisplayScreen() { Theme.DARKER -> stringResource(R.string.darker) Theme.AMOLED -> stringResource(R.string.amoled) } - IconSurfaceButton( + SurfaceRow( title = title, + trailing = + if (appState.theme == it) { + { + Icon( + Icons.Outlined.Check, + stringResource(id = R.string.selected), + tint = MaterialTheme.colorScheme.primary, + ) + } + } else null, onClick = { sharedViewModel.setTheme(it) }, - selected = appState.theme == it, ) } } diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/appearance/language/LanguageScreen.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/appearance/language/LanguageScreen.kt index 8c0bc106..5a6b8086 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/appearance/language/LanguageScreen.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/appearance/language/LanguageScreen.kt @@ -1,22 +1,23 @@ package com.zaneschepke.wireguardautotunnel.ui.screens.settings.appearance.language import androidx.compose.foundation.layout.Arrangement -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.material.icons.Icons +import androidx.compose.material.icons.outlined.Check +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource -import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.zaneschepke.wireguardautotunnel.R import com.zaneschepke.wireguardautotunnel.ui.LocalSharedVm -import com.zaneschepke.wireguardautotunnel.ui.common.button.IconSurfaceButton +import com.zaneschepke.wireguardautotunnel.ui.common.button.SurfaceRow import com.zaneschepke.wireguardautotunnel.util.LocaleUtil import java.text.Collator import java.util.* @@ -62,13 +63,21 @@ fun LanguageScreen() { state = lazyListState, horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.Top, - modifier = Modifier.padding(horizontal = 16.dp), ) { item { - IconSurfaceButton( - stringResource(R.string.automatic), - { sharedViewModel.setLocale(LocaleUtil.OPTION_PHONE_LANGUAGE) }, - selected = appState.locale == LocaleUtil.OPTION_PHONE_LANGUAGE, + SurfaceRow( + title = stringResource(R.string.automatic), + trailing = + if (appState.locale == LocaleUtil.OPTION_PHONE_LANGUAGE) { + { + Icon( + Icons.Outlined.Check, + stringResource(id = R.string.selected), + tint = MaterialTheme.colorScheme.primary, + ) + } + } else null, + onClick = { sharedViewModel.setLocale(LocaleUtil.OPTION_PHONE_LANGUAGE) }, ) } items(sortedLocales, key = { it }) { locale -> @@ -76,10 +85,19 @@ fun LanguageScreen() { locale.getDisplayName(locale).replaceFirstChar { if (it.isLowerCase()) it.titlecase(locale) else it.toString() } - IconSurfaceButton( - buttonText, + SurfaceRow( + title = buttonText, + trailing = + if (appState.locale == locale.toLanguageTag()) { + { + Icon( + Icons.Outlined.Check, + stringResource(id = R.string.selected), + tint = MaterialTheme.colorScheme.primary, + ) + } + } else null, onClick = { sharedViewModel.setLocale(locale.toLanguageTag()) }, - selected = appState.locale == locale.toLanguageTag(), ) } } diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/appearance/language/components/AutomaticLanguageItem.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/appearance/language/components/AutomaticLanguageItem.kt deleted file mode 100644 index 8b2c6bfe..00000000 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/appearance/language/components/AutomaticLanguageItem.kt +++ /dev/null @@ -1,30 +0,0 @@ -package com.zaneschepke.wireguardautotunnel.ui.screens.settings.appearance.language.components - -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.padding -import androidx.compose.runtime.Composable -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.LocalIsAndroidTV -import com.zaneschepke.wireguardautotunnel.ui.common.button.SelectionItemButton -import com.zaneschepke.wireguardautotunnel.ui.common.label.SelectedLabel -import com.zaneschepke.wireguardautotunnel.util.LocaleUtil - -@Composable -fun automaticLanguageItem(locale: String, onChange: (locale: String) -> Unit) { - val isTv = LocalIsAndroidTV.current - Box(modifier = Modifier.padding(top = 24.dp)) { - SelectionItemButton( - buttonText = stringResource(R.string.automatic), - onClick = { onChange(LocaleUtil.OPTION_PHONE_LANGUAGE) }, - trailing = { - if (locale == LocaleUtil.OPTION_PHONE_LANGUAGE) { - SelectedLabel() - } - }, - ripple = isTv, - ) - } -} diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/appearance/language/components/LanguageItem.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/appearance/language/components/LanguageItem.kt deleted file mode 100644 index ab8cd643..00000000 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/appearance/language/components/LanguageItem.kt +++ /dev/null @@ -1,32 +0,0 @@ -package com.zaneschepke.wireguardautotunnel.ui.screens.settings.appearance.language.components - -import androidx.compose.runtime.Composable -import com.zaneschepke.wireguardautotunnel.ui.LocalIsAndroidTV -import com.zaneschepke.wireguardautotunnel.ui.common.button.SelectionItemButton -import com.zaneschepke.wireguardautotunnel.ui.common.label.SelectedLabel -import java.util.* - -@Composable -fun languageItem(currentLocale: String, locale: Locale, onChange: (String) -> Unit) { - val isTv = LocalIsAndroidTV.current - SelectionItemButton( - buttonText = - locale.getDisplayLanguage(locale).replaceFirstChar { - if (it.isLowerCase()) it.titlecase(locale) else it.toString() - } + - if (locale.toLanguageTag().contains("-")) { - " (${locale.getDisplayCountry(locale).replaceFirstChar { - if (it.isLowerCase()) it.titlecase(locale) else it.toString() - }})" - } else { - "" - }, - onClick = { onChange(locale.toLanguageTag()) }, - trailing = { - if (locale.toLanguageTag() == currentLocale) { - SelectedLabel() - } - }, - ripple = isTv, - ) -} diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/components/LanTrafficItem.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/components/LanTrafficItem.kt deleted file mode 100644 index a01385f2..00000000 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/components/LanTrafficItem.kt +++ /dev/null @@ -1,37 +0,0 @@ -package com.zaneschepke.wireguardautotunnel.ui.screens.settings.components - -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.outlined.Lan -import androidx.compose.material3.Icon -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.ui.res.stringResource -import com.zaneschepke.wireguardautotunnel.R -import com.zaneschepke.wireguardautotunnel.ui.common.button.ScaledSwitch -import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SelectionItem - -@Composable -fun lanTrafficItem( - isLanKillSwitchEnabled: Boolean, - onClick: (enable: Boolean) -> Unit, -): SelectionItem { - return SelectionItem( - leading = { Icon(Icons.Outlined.Lan, contentDescription = null) }, - title = { - Text( - text = stringResource(R.string.allow_lan_traffic), - style = - MaterialTheme.typography.bodyMedium.copy(MaterialTheme.colorScheme.onSurface), - ) - }, - description = { - Text( - text = stringResource(R.string.bypass_lan_for_kill_switch), - style = MaterialTheme.typography.bodySmall.copy(MaterialTheme.colorScheme.outline), - ) - }, - trailing = { ScaledSwitch(checked = isLanKillSwitchEnabled, onClick = onClick) }, - onClick = { onClick(!isLanKillSwitchEnabled) }, - ) -} diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/components/LocalLoggingItem.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/components/LocalLoggingItem.kt deleted file mode 100644 index 16fb7d97..00000000 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/components/LocalLoggingItem.kt +++ /dev/null @@ -1,33 +0,0 @@ -package com.zaneschepke.wireguardautotunnel.ui.screens.settings.components - -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.outlined.ViewHeadline -import androidx.compose.material3.Icon -import androidx.compose.runtime.Composable -import androidx.compose.ui.res.stringResource -import com.zaneschepke.wireguardautotunnel.R -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.SelectionItemLabel -import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SelectionLabelType - -@Composable -fun localLoggingItem( - isLocalLoggingEnabled: Boolean, - onClick: (checked: Boolean) -> Unit, -): SelectionItem { - return SelectionItem( - leading = { Icon(Icons.Outlined.ViewHeadline, contentDescription = null) }, - title = { - SelectionItemLabel(stringResource(R.string.local_logging), SelectionLabelType.TITLE) - }, - description = { - SelectionItemLabel( - stringResource(R.string.enable_local_logging), - SelectionLabelType.DESCRIPTION, - ) - }, - trailing = { ScaledSwitch(checked = isLocalLoggingEnabled, onClick = onClick) }, - onClick = { onClick(!isLocalLoggingEnabled) }, - ) -} diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/components/LocationServicesDialog.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/components/LocationServicesDialog.kt deleted file mode 100644 index 36dc4b28..00000000 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/components/LocationServicesDialog.kt +++ /dev/null @@ -1,34 +0,0 @@ -package com.zaneschepke.wireguardautotunnel.ui.screens.settings.components - -import androidx.compose.material3.AlertDialog -import androidx.compose.material3.Text -import androidx.compose.material3.TextButton -import androidx.compose.runtime.Composable -import androidx.compose.ui.res.stringResource -import com.zaneschepke.wireguardautotunnel.R - -@Composable -fun LocationServicesDialog(show: Boolean, onDismiss: () -> Unit, onAttest: () -> Unit) { - if (show) { - AlertDialog( - onDismissRequest = { onDismiss() }, - confirmButton = { - TextButton( - onClick = { - onDismiss() - onAttest() - } - ) { - Text(text = stringResource(R.string.okay)) - } - }, - dismissButton = { - TextButton(onClick = { onDismiss() }) { - Text(text = stringResource(R.string.cancel)) - } - }, - title = { Text(text = stringResource(R.string.location_services_not_detected)) }, - text = { Text(text = stringResource(R.string.location_services_missing_message)) }, - ) - } -} diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/components/PinLockItem.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/components/PinLockItem.kt deleted file mode 100644 index 1877102d..00000000 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/components/PinLockItem.kt +++ /dev/null @@ -1,29 +0,0 @@ -package com.zaneschepke.wireguardautotunnel.ui.screens.settings.components - -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.outlined.Pin -import androidx.compose.material3.Icon -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.ui.res.stringResource -import com.zaneschepke.wireguardautotunnel.R -import com.zaneschepke.wireguardautotunnel.ui.common.button.ScaledSwitch -import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SelectionItem - -@Composable -fun pinLockItem(isPinLockEnabled: Boolean, onClick: (checked: Boolean) -> Unit): SelectionItem { - - return SelectionItem( - leading = { Icon(Icons.Outlined.Pin, contentDescription = null) }, - title = { - Text( - text = stringResource(R.string.enable_app_lock), - style = - MaterialTheme.typography.bodyMedium.copy(MaterialTheme.colorScheme.onSurface), - ) - }, - trailing = { ScaledSwitch(checked = isPinLockEnabled, onClick = onClick) }, - onClick = { onClick(!isPinLockEnabled) }, - ) -} diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/components/appModeItem.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/components/appModeItem.kt deleted file mode 100644 index 7e42c753..00000000 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/components/appModeItem.kt +++ /dev/null @@ -1,47 +0,0 @@ -package com.zaneschepke.wireguardautotunnel.ui.screens.settings.components - -import androidx.compose.foundation.layout.size -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.outlined.ExpandMore -import androidx.compose.material3.Icon -import androidx.compose.material3.IconButton -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.vector.ImageVector -import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.res.vectorResource -import com.zaneschepke.wireguardautotunnel.R -import com.zaneschepke.wireguardautotunnel.data.model.AppMode -import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SelectionItem -import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SelectionItemLabel -import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SelectionLabelType -import com.zaneschepke.wireguardautotunnel.ui.theme.iconSize -import com.zaneschepke.wireguardautotunnel.util.extensions.asTitleString - -@Composable -fun appModeItem(appMode: AppMode, onClick: () -> Unit): SelectionItem { - val context = LocalContext.current - return SelectionItem( - leading = { Icon(ImageVector.vectorResource(R.drawable.sdk), contentDescription = null) }, - trailing = { - IconButton(onClick = onClick) { - Icon( - Icons.Outlined.ExpandMore, - contentDescription = stringResource(R.string.select), - modifier = Modifier.size(iconSize), - ) - } - }, - title = { - SelectionItemLabel(stringResource(R.string.backend_mode), SelectionLabelType.TITLE) - }, - description = { - SelectionItemLabel( - stringResource(R.string.current_template, appMode.asTitleString(context)), - SelectionLabelType.DESCRIPTION, - ) - }, - onClick = onClick, - ) -} diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/components/appearanceItem.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/components/appearanceItem.kt deleted file mode 100644 index 78945139..00000000 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/components/appearanceItem.kt +++ /dev/null @@ -1,28 +0,0 @@ -package com.zaneschepke.wireguardautotunnel.ui.screens.settings.components - -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.automirrored.outlined.ViewQuilt -import androidx.compose.material3.Icon -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.ui.res.stringResource -import com.zaneschepke.wireguardautotunnel.R -import com.zaneschepke.wireguardautotunnel.ui.common.button.ForwardButton -import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SelectionItem - -@Composable -fun appearanceItem(onClick: () -> Unit): SelectionItem { - return SelectionItem( - leading = { Icon(Icons.AutoMirrored.Outlined.ViewQuilt, contentDescription = null) }, - title = { - Text( - text = stringResource(R.string.appearance), - style = - MaterialTheme.typography.bodyMedium.copy(MaterialTheme.colorScheme.onSurface), - ) - }, - trailing = { ForwardButton { onClick() } }, - onClick = onClick, - ) -} diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/components/dnsSettingsItem.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/components/dnsSettingsItem.kt deleted file mode 100644 index 1513a7cb..00000000 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/components/dnsSettingsItem.kt +++ /dev/null @@ -1,43 +0,0 @@ -package com.zaneschepke.wireguardautotunnel.ui.screens.settings.components - -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.outlined.Dns -import androidx.compose.material3.Icon -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.saveable.rememberSaveable -import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.res.stringResource -import com.zaneschepke.wireguardautotunnel.R -import com.zaneschepke.wireguardautotunnel.data.model.AppMode -import com.zaneschepke.wireguardautotunnel.ui.common.button.ForwardButton -import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SelectionItem -import com.zaneschepke.wireguardautotunnel.util.extensions.asString - -@Composable -fun dnsSettingsItem( - appMode: AppMode, - onClick: () -> Unit, - onDisabledClick: () -> Unit, -): SelectionItem { - val context = LocalContext.current - val enabled by rememberSaveable(appMode) { mutableStateOf(appMode != AppMode.KERNEL) } - val click = if (enabled) onClick else onDisabledClick - return SelectionItem( - leading = { Icon(Icons.Outlined.Dns, null) }, - trailing = { ForwardButton { click() } }, - title = { - Text( - text = stringResource(R.string.dns_settings), - style = - MaterialTheme.typography.bodyMedium.copy(MaterialTheme.colorScheme.onSurface), - ) - }, - onClick = click, - disabledReason = - context.getString(R.string.mode_disabled_template, appMode.asString(context)), - ) -} diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/components/proxySettingsItem.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/components/proxySettingsItem.kt deleted file mode 100644 index fb052f2f..00000000 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/components/proxySettingsItem.kt +++ /dev/null @@ -1,28 +0,0 @@ -package com.zaneschepke.wireguardautotunnel.ui.screens.settings.components - -import androidx.compose.material3.Icon -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.ui.graphics.vector.ImageVector -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.res.vectorResource -import com.zaneschepke.wireguardautotunnel.R -import com.zaneschepke.wireguardautotunnel.ui.common.button.ForwardButton -import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SelectionItem - -@Composable -fun proxYSettingsItem(onClick: () -> Unit): SelectionItem { - return SelectionItem( - leading = { Icon(ImageVector.vectorResource(R.drawable.proxy), null) }, - trailing = { ForwardButton { onClick() } }, - title = { - Text( - text = stringResource(R.string.proxy_settings), - style = - MaterialTheme.typography.bodyMedium.copy(MaterialTheme.colorScheme.onSurface), - ) - }, - onClick = onClick, - ) -} diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/components/readLogsItem.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/components/readLogsItem.kt deleted file mode 100644 index bf116a3f..00000000 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/components/readLogsItem.kt +++ /dev/null @@ -1,24 +0,0 @@ -package com.zaneschepke.wireguardautotunnel.ui.screens.settings.components - -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.ViewTimeline -import androidx.compose.material3.Icon -import androidx.compose.runtime.Composable -import androidx.compose.ui.res.stringResource -import com.zaneschepke.wireguardautotunnel.R -import com.zaneschepke.wireguardautotunnel.ui.common.button.ForwardButton -import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SelectionItem -import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SelectionItemLabel -import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SelectionLabelType - -@Composable -fun readLogsItem(onClick: () -> Unit): SelectionItem { - return SelectionItem( - leading = { Icon(Icons.Filled.ViewTimeline, contentDescription = null) }, - title = { - SelectionItemLabel(stringResource(R.string.read_logs), SelectionLabelType.TITLE) - }, - trailing = { ForwardButton { onClick() } }, - onClick = onClick, - ) -} diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/components/systemFeaturesItem.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/components/systemFeaturesItem.kt deleted file mode 100644 index ab60108e..00000000 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/components/systemFeaturesItem.kt +++ /dev/null @@ -1,28 +0,0 @@ -package com.zaneschepke.wireguardautotunnel.ui.screens.settings.components - -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.outlined.Android -import androidx.compose.material3.Icon -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.ui.res.stringResource -import com.zaneschepke.wireguardautotunnel.R -import com.zaneschepke.wireguardautotunnel.ui.common.button.ForwardButton -import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SelectionItem - -@Composable -fun systemFeaturesItem(onClick: () -> Unit): SelectionItem { - return SelectionItem( - leading = { Icon(Icons.Outlined.Android, null) }, - trailing = { ForwardButton { onClick.invoke() } }, - title = { - Text( - text = stringResource(R.string.system_features), - style = - MaterialTheme.typography.bodyMedium.copy(MaterialTheme.colorScheme.onSurface), - ) - }, - onClick = onClick, - ) -} diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/components/tunnelGlobalsSettingItem.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/components/tunnelGlobalsSettingItem.kt deleted file mode 100644 index c89601f0..00000000 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/components/tunnelGlobalsSettingItem.kt +++ /dev/null @@ -1,33 +0,0 @@ -package com.zaneschepke.wireguardautotunnel.ui.screens.settings.components - -import android.R.attr.enabled -import android.R.attr.onClick -import androidx.compose.material3.Icon -import androidx.compose.runtime.Composable -import androidx.compose.ui.graphics.vector.ImageVector -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.res.vectorResource -import com.zaneschepke.wireguardautotunnel.R -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.SelectionItemLabel -import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SelectionLabelType - -@Composable -fun tunnelGlobalsSettingItem( - enabled: Boolean, - onClick: (Boolean) -> Unit, - onItemClick: () -> Unit, -): SelectionItem { - return SelectionItem( - leading = { Icon(ImageVector.vectorResource(R.drawable.globe), contentDescription = null) }, - title = { - SelectionItemLabel( - stringResource(R.string.tunnel_global_overrides), - SelectionLabelType.TITLE, - ) - }, - trailing = { ScaledSwitch(checked = enabled, onClick = onClick) }, - onClick = onItemClick, - ) -} diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/components/tunnelMonitoringItem.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/components/tunnelMonitoringItem.kt deleted file mode 100644 index 919d04b5..00000000 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/components/tunnelMonitoringItem.kt +++ /dev/null @@ -1,47 +0,0 @@ -package com.zaneschepke.wireguardautotunnel.ui.screens.settings.components - -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.outlined.MonitorHeart -import androidx.compose.material3.Icon -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.saveable.rememberSaveable -import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.res.stringResource -import com.zaneschepke.wireguardautotunnel.R -import com.zaneschepke.wireguardautotunnel.data.model.AppMode -import com.zaneschepke.wireguardautotunnel.ui.common.button.ForwardButton -import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SelectionItem -import com.zaneschepke.wireguardautotunnel.util.extensions.asString - -@Composable -fun tunnelMonitoringItem( - appMode: AppMode, - onClick: () -> Unit, - onDisabledClick: () -> Unit, -): SelectionItem { - val context = LocalContext.current - val enabled by - rememberSaveable(appMode) { - mutableStateOf(appMode == AppMode.VPN || appMode == AppMode.KERNEL) - } - val click = if (enabled) onClick else onDisabledClick - return SelectionItem( - leading = { Icon(Icons.Outlined.MonitorHeart, null) }, - trailing = { ForwardButton { click() } }, - title = { - Text( - text = stringResource(R.string.tunnel_monitoring), - style = - MaterialTheme.typography.bodyMedium.copy(MaterialTheme.colorScheme.onSurface), - ) - }, - isEnabled = enabled, - onClick = click, - disabledReason = - context.getString(R.string.mode_disabled_template, appMode.asString(context)), - ) -} diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/dns/DnsSettingsScreen.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/dns/DnsSettingsScreen.kt index 5750dbe8..5b625dde 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/dns/DnsSettingsScreen.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/dns/DnsSettingsScreen.kt @@ -4,15 +4,12 @@ import androidx.compose.animation.AnimatedVisibility 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.rememberScrollState import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.Cloud import androidx.compose.material.icons.outlined.Dns import androidx.compose.material3.Icon -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment @@ -33,47 +30,34 @@ fun DnsSettingsScreen(viewModel: SettingsViewModel = hiltViewModel()) { val context = LocalContext.current val settingsState by viewModel.container.stateFlow.collectAsStateWithLifecycle() + if (settingsState.isLoading) return + Column( horizontalAlignment = Alignment.Start, verticalArrangement = Arrangement.spacedBy(12.dp, Alignment.Top), - modifier = - Modifier.fillMaxSize().verticalScroll(rememberScrollState()).padding(horizontal = 16.dp), + modifier = Modifier.fillMaxSize().verticalScroll(rememberScrollState()), ) { - LabelledDropdown( - title = { - Text( - text = stringResource(R.string.dns_protocol), - style = - MaterialTheme.typography.bodyMedium.copy( - color = MaterialTheme.colorScheme.onSurface - ), - ) - }, - leading = { Icon(Icons.Outlined.Dns, contentDescription = null) }, - currentValue = settingsState.settings.dnsProtocol, - onSelected = { selected -> selected?.let { viewModel.setDnsProtocol(it) } }, - options = DnsProtocol.entries, - optionToString = { (it ?: DnsProtocol.SYSTEM).asString(context) }, - ) - AnimatedVisibility(settingsState.settings.dnsProtocol != DnsProtocol.SYSTEM) { + Column { LabelledDropdown( - title = { - Text( - text = stringResource(R.string.dns_provider), - style = - MaterialTheme.typography.bodyMedium.copy( - color = MaterialTheme.colorScheme.onSurface - ), - ) - }, - leading = { Icon(Icons.Outlined.Cloud, contentDescription = null) }, - currentValue = - settingsState.settings.dnsEndpoint?.let { DnsProvider.fromAddress(it) } - ?: DnsProvider.CLOUDFLARE, - onSelected = { selected -> selected?.let { viewModel.setDnsProvider(it) } }, - options = DnsProvider.entries, - optionToString = { it?.name ?: DnsProvider.CLOUDFLARE.name }, + title = stringResource(R.string.dns_protocol), + leading = { Icon(Icons.Outlined.Dns, contentDescription = null) }, + currentValue = settingsState.settings.dnsProtocol, + onSelected = { selected -> selected?.let { viewModel.setDnsProtocol(it) } }, + options = DnsProtocol.entries, + optionToString = { (it ?: DnsProtocol.SYSTEM).asString(context) }, ) + AnimatedVisibility(settingsState.settings.dnsProtocol != DnsProtocol.SYSTEM) { + LabelledDropdown( + title = stringResource(R.string.dns_provider), + leading = { Icon(Icons.Outlined.Cloud, contentDescription = null) }, + currentValue = + settingsState.settings.dnsEndpoint?.let { DnsProvider.fromAddress(it) } + ?: DnsProvider.CLOUDFLARE, + onSelected = { selected -> selected?.let { viewModel.setDnsProvider(it) } }, + options = DnsProvider.entries, + optionToString = { it?.name ?: DnsProvider.CLOUDFLARE.name }, + ) + } } } } diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/globals/TunnelGlobalsScreen.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/globals/TunnelGlobalsScreen.kt index a3d4e679..170426a1 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/globals/TunnelGlobalsScreen.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/globals/TunnelGlobalsScreen.kt @@ -1,11 +1,15 @@ package com.zaneschepke.wireguardautotunnel.ui.screens.settings.globals +import android.R.attr.onClick 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.rememberScrollState 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.Settings +import androidx.compose.material3.Icon import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -13,10 +17,8 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.zaneschepke.wireguardautotunnel.R import com.zaneschepke.wireguardautotunnel.ui.LocalNavController -import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SurfaceSelectionGroupButton +import com.zaneschepke.wireguardautotunnel.ui.common.button.SurfaceRow import com.zaneschepke.wireguardautotunnel.ui.navigation.Route -import com.zaneschepke.wireguardautotunnel.ui.screens.settings.globals.components.globalConfigItem -import com.zaneschepke.wireguardautotunnel.ui.screens.tunnels.tunneloptions.components.splitTunnelingItem @Composable fun TunnelGlobalsScreen(globalTunnelId: Int) { @@ -24,17 +26,22 @@ fun TunnelGlobalsScreen(globalTunnelId: Int) { Column( horizontalAlignment = Alignment.Start, - verticalArrangement = Arrangement.spacedBy(12.dp, Alignment.Top), - modifier = - Modifier.verticalScroll(rememberScrollState()).fillMaxSize().padding(horizontal = 16.dp), + verticalArrangement = Arrangement.spacedBy(16.dp, Alignment.Top), + modifier = Modifier.verticalScroll(rememberScrollState()).fillMaxSize(), ) { - SurfaceSelectionGroupButton( - listOf( - globalConfigItem { navController.push(Route.ConfigGlobal(globalTunnelId)) }, - splitTunnelingItem(stringResource(R.string.splt_tunneling)) { - navController.push(Route.SplitTunnelGlobal(id = globalTunnelId)) - }, + Column { + SurfaceRow( + leading = { Icon(Icons.Outlined.Settings, contentDescription = null) }, + title = stringResource(R.string.configuration), + onClick = { navController.push(Route.ConfigGlobal(globalTunnelId)) }, ) - ) + SurfaceRow( + leading = { + Icon(Icons.AutoMirrored.Outlined.CallSplit, contentDescription = null) + }, + title = stringResource(R.string.splt_tunneling), + onClick = { navController.push(Route.SplitTunnelGlobal(id = globalTunnelId)) }, + ) + } } } diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/globals/components/globalConfigItem.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/globals/components/globalConfigItem.kt deleted file mode 100644 index 02d42cf1..00000000 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/globals/components/globalConfigItem.kt +++ /dev/null @@ -1,24 +0,0 @@ -package com.zaneschepke.wireguardautotunnel.ui.screens.settings.globals.components - -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.outlined.Settings -import androidx.compose.material3.Icon -import androidx.compose.runtime.Composable -import androidx.compose.ui.res.stringResource -import com.zaneschepke.wireguardautotunnel.R -import com.zaneschepke.wireguardautotunnel.ui.common.button.ForwardButton -import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SelectionItem -import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SelectionItemLabel -import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SelectionLabelType - -@Composable -fun globalConfigItem(onClick: () -> Unit): SelectionItem { - return SelectionItem( - leading = { Icon(Icons.Outlined.Settings, contentDescription = null) }, - title = { - SelectionItemLabel(stringResource(R.string.configuration), SelectionLabelType.TITLE) - }, - trailing = { ForwardButton { onClick() } }, - onClick = onClick, - ) -} 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 7b759150..30762dd5 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,9 +7,12 @@ 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.NetworkPing +import androidx.compose.material.icons.outlined.QueryStats import androidx.compose.material.icons.outlined.Replay import androidx.compose.material.icons.outlined.Timer import androidx.compose.material.icons.outlined.TimerOff +import androidx.compose.material.icons.outlined.ViewHeadline import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text @@ -22,40 +25,46 @@ 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.button.surface.SurfaceSelectionGroupButton +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.screens.settings.monitoring.components.detailedPingStatsItem -import com.zaneschepke.wireguardautotunnel.ui.screens.settings.monitoring.components.enablePingMonitoringItem +import com.zaneschepke.wireguardautotunnel.ui.common.label.GroupLabel +import com.zaneschepke.wireguardautotunnel.ui.common.text.DescriptionText +import com.zaneschepke.wireguardautotunnel.ui.navigation.Route import com.zaneschepke.wireguardautotunnel.viewmodel.SettingsViewModel @Composable fun TunnelMonitoringScreen(viewModel: SettingsViewModel = hiltViewModel()) { + val navController = LocalNavController.current val settingsState by viewModel.container.stateFlow.collectAsStateWithLifecycle() + if (settingsState.isLoading) return + Column( horizontalAlignment = Alignment.Start, verticalArrangement = Arrangement.spacedBy(12.dp, Alignment.Top), - modifier = - Modifier.fillMaxSize().verticalScroll(rememberScrollState()).padding(horizontal = 16.dp), + modifier = Modifier.fillMaxSize().verticalScroll(rememberScrollState()), ) { - SurfaceSelectionGroupButton( - listOf( - enablePingMonitoringItem(settingsState.settings.isPingEnabled) { - viewModel.setPingEnabled(it) - } + Column { + GroupLabel( + stringResource(R.string.ping), + modifier = Modifier.padding(horizontal = 16.dp), ) - ) - if (settingsState.settings.isPingEnabled) { - LabelledDropdown( - title = { - Text( - text = stringResource(R.string.tunnel_ping_interval), - style = - MaterialTheme.typography.bodyMedium.copy( - color = MaterialTheme.colorScheme.onSurface - ), + SurfaceRow( + leading = { Icon(Icons.Outlined.NetworkPing, contentDescription = null) }, + title = stringResource(R.string.ping_monitor), + trailing = { + ScaledSwitch( + checked = settingsState.showDetailedPingStats, + onClick = { viewModel.setPingEnabled(it) }, ) }, + onClick = { viewModel.setPingEnabled(!settingsState.settings.isPingEnabled) }, + ) + LabelledDropdown( + title = stringResource(R.string.tunnel_ping_interval), leading = { Icon(Icons.Outlined.Timer, contentDescription = null) }, currentValue = settingsState.settings.tunnelPingIntervalSeconds, onSelected = { selected -> @@ -65,15 +74,7 @@ fun TunnelMonitoringScreen(viewModel: SettingsViewModel = hiltViewModel()) { optionToString = { it?.toString() ?: stringResource(R.string._default) }, ) LabelledDropdown( - title = { - Text( - text = stringResource(R.string.attempts_per_interval), - style = - MaterialTheme.typography.bodyMedium.copy( - color = MaterialTheme.colorScheme.onSurface - ), - ) - }, + title = stringResource(R.string.attempts_per_interval), leading = { Icon(Icons.Outlined.Replay, contentDescription = null) }, currentValue = settingsState.settings.tunnelPingAttempts, onSelected = { selected -> selected?.let { viewModel.setTunnelPingAttempts(it) } }, @@ -81,15 +82,7 @@ fun TunnelMonitoringScreen(viewModel: SettingsViewModel = hiltViewModel()) { optionToString = { it?.toString() ?: stringResource(R.string._default) }, ) LabelledDropdown( - title = { - Text( - text = stringResource(R.string.ping_timeout), - style = - MaterialTheme.typography.bodyMedium.copy( - color = MaterialTheme.colorScheme.onSurface - ), - ) - }, + title = stringResource(R.string.ping_timeout), leading = { Icon(Icons.Outlined.TimerOff, contentDescription = null) }, currentValue = settingsState.settings.tunnelPingTimeoutSeconds, description = { @@ -107,12 +100,35 @@ fun TunnelMonitoringScreen(viewModel: SettingsViewModel = hiltViewModel()) { options = (10..20).toList() + null, optionToString = { it?.toString() ?: stringResource(R.string._default) }, ) - SurfaceSelectionGroupButton( - listOf( - detailedPingStatsItem(settingsState.showDetailedPingStats) { - viewModel.setDetailedPingStats(it) - } - ) + SurfaceRow( + leading = { Icon(Icons.Outlined.QueryStats, contentDescription = null) }, + title = stringResource(R.string.display_detailed_ping_stats), + trailing = { + ScaledSwitch( + checked = settingsState.showDetailedPingStats, + onClick = { viewModel.setDetailedPingStats(it) }, + ) + }, + onClick = { viewModel.setDetailedPingStats(!settingsState.showDetailedPingStats) }, + ) + } + 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), + description = { DescriptionText(stringResource(R.string.enable_local_logging)) }, + trailing = { modifier -> + SwitchWithDivider( + checked = settingsState.isLocalLoggingEnabled, + onClick = { viewModel.setLocalLogging(it) }, + modifier = modifier, + ) + }, + onClick = { navController.push(Route.Logs) }, ) } } diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/monitoring/components/detailedPingStatsItem.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/monitoring/components/detailedPingStatsItem.kt deleted file mode 100644 index a4b1ea2c..00000000 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/monitoring/components/detailedPingStatsItem.kt +++ /dev/null @@ -1,31 +0,0 @@ -package com.zaneschepke.wireguardautotunnel.ui.screens.settings.monitoring.components - -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.outlined.QueryStats -import androidx.compose.material3.Icon -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.ui.res.stringResource -import com.zaneschepke.wireguardautotunnel.R -import com.zaneschepke.wireguardautotunnel.ui.common.button.ScaledSwitch -import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SelectionItem - -@Composable -fun detailedPingStatsItem( - detailedPingStatsEnabled: Boolean, - onChange: (checked: Boolean) -> Unit, -): SelectionItem { - return SelectionItem( - leading = { Icon(Icons.Outlined.QueryStats, contentDescription = null) }, - title = { - Text( - text = stringResource(R.string.display_detailed_ping_stats), - style = - MaterialTheme.typography.bodyMedium.copy(MaterialTheme.colorScheme.onSurface), - ) - }, - trailing = { ScaledSwitch(checked = detailedPingStatsEnabled, onClick = onChange) }, - onClick = { onChange(!detailedPingStatsEnabled) }, - ) -} diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/monitoring/components/enablePingMonitoringItem.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/monitoring/components/enablePingMonitoringItem.kt deleted file mode 100644 index 6463354f..00000000 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/monitoring/components/enablePingMonitoringItem.kt +++ /dev/null @@ -1,31 +0,0 @@ -package com.zaneschepke.wireguardautotunnel.ui.screens.settings.monitoring.components - -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.outlined.NetworkPing -import androidx.compose.material3.Icon -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.ui.res.stringResource -import com.zaneschepke.wireguardautotunnel.R -import com.zaneschepke.wireguardautotunnel.ui.common.button.ScaledSwitch -import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SelectionItem - -@Composable -fun enablePingMonitoringItem( - isPingEnabled: Boolean, - onChange: (checked: Boolean) -> Unit, -): SelectionItem { - return SelectionItem( - leading = { Icon(Icons.Outlined.NetworkPing, contentDescription = null) }, - trailing = { ScaledSwitch(checked = isPingEnabled, onClick = onChange) }, - title = { - Text( - text = stringResource(R.string.monitoring_ping), - style = - MaterialTheme.typography.bodyMedium.copy(MaterialTheme.colorScheme.onSurface), - ) - }, - onClick = { onChange(!isPingEnabled) }, - ) -} 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 f9688a2b..3ef92473 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 @@ -26,10 +26,7 @@ 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.surface.SelectionItem -import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SelectionItemLabel -import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SelectionLabelType -import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SurfaceSelectionGroupButton +import com.zaneschepke.wireguardautotunnel.ui.common.button.SurfaceRow import com.zaneschepke.wireguardautotunnel.ui.common.label.GroupLabel import com.zaneschepke.wireguardautotunnel.ui.common.textbox.ConfigurationTextBox import com.zaneschepke.wireguardautotunnel.ui.sideeffect.LocalSideEffect @@ -100,82 +97,68 @@ fun ProxySettingsScreen(viewModel: ProxySettingsViewModel = hiltViewModel()) { Column( horizontalAlignment = Alignment.Start, - verticalArrangement = Arrangement.spacedBy(24.dp, Alignment.Top), - modifier = Modifier.fillMaxSize().padding(horizontal = 16.dp), + verticalArrangement = Arrangement.spacedBy(16.dp, Alignment.Top), + modifier = Modifier.fillMaxSize(), ) { - SurfaceSelectionGroupButton( - listOf( - SelectionItem( - leading = { Icon(Icons.Outlined.Forward5, contentDescription = null) }, - title = { - SelectionItemLabel( - stringResource(R.string.socks_5_proxy), - SelectionLabelType.TITLE, - ) - }, - trailing = { - ScaledSwitch(checked = socks5Enabled, onClick = { socks5Enabled = it }) - }, - onClick = { socks5Enabled = !socks5Enabled }, - ) - ) - ) - if (socks5Enabled) { - ConfigurationTextBox( - modifier = Modifier.padding(horizontal = 12.dp), - hint = - stringResource( - R.string.defaults_to_template, - AppProxySettings.DEFAULT_SOCKS_BIND_ADDRESS, - ), - label = stringResource(R.string.socks_5_bind_address), - value = socksBindAddress, - isError = proxySettingsState.isSocks5BindAddressError, - onValueChange = { - if (proxySettingsState.isSocks5BindAddressError) - viewModel.clearSocks5BindError() - socksBindAddress = it + Column(verticalArrangement = Arrangement.spacedBy(8.dp)) { + SurfaceRow( + leading = { Icon(Icons.Outlined.Forward5, contentDescription = null) }, + title = stringResource(R.string.socks_5_proxy), + trailing = { + ScaledSwitch(checked = socks5Enabled, onClick = { socks5Enabled = it }) }, + onClick = { socks5Enabled = !socks5Enabled }, ) + if (socks5Enabled) { + ConfigurationTextBox( + modifier = Modifier.padding(horizontal = 16.dp), + hint = + stringResource( + R.string.defaults_to_template, + AppProxySettings.DEFAULT_SOCKS_BIND_ADDRESS, + ), + label = stringResource(R.string.socks_5_bind_address), + value = socksBindAddress, + isError = proxySettingsState.isSocks5BindAddressError, + onValueChange = { + if (proxySettingsState.isSocks5BindAddressError) + viewModel.clearSocks5BindError() + socksBindAddress = it + }, + ) + } } - SurfaceSelectionGroupButton( - listOf( - SelectionItem( - leading = { Icon(Icons.Outlined.Http, contentDescription = null) }, - title = { - SelectionItemLabel( - stringResource(R.string.http_proxy), - SelectionLabelType.TITLE, - ) + Column(verticalArrangement = Arrangement.spacedBy(8.dp)) { + SurfaceRow( + leading = { Icon(Icons.Outlined.Http, contentDescription = null) }, + title = stringResource(R.string.http_proxy), + trailing = { ScaledSwitch(checked = httpEnabled, onClick = { httpEnabled = it }) }, + onClick = { httpEnabled = !httpEnabled }, + ) + if (httpEnabled) { + ConfigurationTextBox( + hint = + stringResource( + R.string.defaults_to_template, + AppProxySettings.DEFAULT_HTTP_BIND_ADDRESS, + ), + label = stringResource(R.string.http_bind_address), + value = httpBindAddress, + isError = proxySettingsState.isHttpBindAddressError, + onValueChange = { + if (proxySettingsState.isSocks5BindAddressError) + viewModel.clearHttpBindError() + httpBindAddress = it }, - trailing = { - ScaledSwitch(checked = httpEnabled, onClick = { httpEnabled = it }) - }, - onClick = { httpEnabled = !httpEnabled }, + modifier = Modifier.padding(horizontal = 12.dp), ) - ) - ) - if (httpEnabled) { - ConfigurationTextBox( - hint = - stringResource( - R.string.defaults_to_template, - AppProxySettings.DEFAULT_HTTP_BIND_ADDRESS, - ), - label = stringResource(R.string.http_bind_address), - value = httpBindAddress, - isError = proxySettingsState.isHttpBindAddressError, - onValueChange = { - if (proxySettingsState.isSocks5BindAddressError) viewModel.clearHttpBindError() - httpBindAddress = it - }, - modifier = Modifier.padding(horizontal = 12.dp), - ) + } } if (socks5Enabled || httpEnabled) { Column( horizontalAlignment = Alignment.Start, verticalArrangement = Arrangement.spacedBy(24.dp, Alignment.Top), + modifier = Modifier.padding(horizontal = 16.dp), ) { GroupLabel( stringResource( @@ -194,7 +177,6 @@ fun ProxySettingsScreen(viewModel: ProxySettingsViewModel = hiltViewModel()) { hint = "", keyboardActions = keyboardActions, keyboardOptions = keyboardOptions, - modifier = Modifier.padding(horizontal = 12.dp), ) ConfigurationTextBox( value = proxyPassword, @@ -207,7 +189,6 @@ fun ProxySettingsScreen(viewModel: ProxySettingsViewModel = hiltViewModel()) { hint = "", keyboardActions = keyboardActions, keyboardOptions = keyboardOptions, - modifier = Modifier.padding(horizontal = 12.dp), trailing = { IconButton(onClick = { passwordVisible = !passwordVisible }) { Icon( 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 new file mode 100644 index 00000000..d2b323ba --- /dev/null +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/system/AndroidIntegrationsScreen.kt @@ -0,0 +1,124 @@ +package com.zaneschepke.wireguardautotunnel.ui.screens.settings.system + +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.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.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.style.TextOverflow +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.util.extensions.launchVpnSettings +import com.zaneschepke.wireguardautotunnel.viewmodel.SettingsViewModel + +@Composable +fun AndroidIntegrationsScreen(viewModel: SettingsViewModel = hiltViewModel()) { + val context = LocalContext.current + + val settingsState by viewModel.container.stateFlow.collectAsStateWithLifecycle() + + val clipboard = rememberClipboardHelper() + + SecureScreenFromRecording() + + Column( + horizontalAlignment = Alignment.Start, + verticalArrangement = Arrangement.spacedBy(12.dp, Alignment.Top), + modifier = Modifier.fillMaxSize().verticalScroll(rememberScrollState()), + ) { + 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.Restore, contentDescription = null) }, + trailing = { + ScaledSwitch( + checked = settingsState.settings.isRestoreOnBootEnabled, + onClick = { viewModel.setRestoreOnBootEnabled(it) }, + ) + }, + title = stringResource(R.string.restart_at_boot), + onClick = { + viewModel.setRestoreOnBootEnabled(!settingsState.settings.isRestoreOnBootEnabled) + }, + ) + 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) + }, + ) + SurfaceRow( + leading = { Icon(Icons.Filled.AppShortcut, contentDescription = null) }, + trailing = { + ScaledSwitch( + checked = settingsState.settings.isShortcutsEnabled, + onClick = { viewModel.setShortcutsEnabled(it) }, + ) + }, + title = stringResource(R.string.enabled_app_shortcuts), + onClick = { viewModel.setShortcutsEnabled(!settingsState.settings.isShortcutsEnabled) }, + ) + SurfaceRow( + leading = { Icon(Icons.Filled.SmartToy, contentDescription = null) }, + trailing = { + ScaledSwitch( + checked = settingsState.isRemoteEnabled, + onClick = { viewModel.setRemoteEnabled(it) }, + ) + }, + description = { + settingsState.remoteKey?.let { key -> + AnimatedVisibility(visible = settingsState.isRemoteEnabled) { + Text( + text = stringResource(R.string.remote_key_template, key), + style = + MaterialTheme.typography.bodySmall.copy( + color = MaterialTheme.colorScheme.outline + ), + maxLines = 1, + overflow = TextOverflow.Ellipsis, + modifier = Modifier.clickable { clipboard.copy(key) }, + ) + } + } + }, + title = stringResource(R.string.enable_remote_app_control), + onClick = { viewModel.setRemoteEnabled(!settingsState.isRemoteEnabled) }, + ) + } +} diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/system/SystemFeaturesScreen.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/system/SystemFeaturesScreen.kt deleted file mode 100644 index 0e7357aa..00000000 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/system/SystemFeaturesScreen.kt +++ /dev/null @@ -1,70 +0,0 @@ -package com.zaneschepke.wireguardautotunnel.ui.screens.settings.system - -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.rememberScrollState -import androidx.compose.foundation.verticalScroll -import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.unit.dp -import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel -import androidx.lifecycle.compose.collectAsStateWithLifecycle -import com.zaneschepke.wireguardautotunnel.ui.LocalIsAndroidTV -import com.zaneschepke.wireguardautotunnel.ui.common.SectionDivider -import com.zaneschepke.wireguardautotunnel.ui.common.SecureScreenFromRecording -import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SurfaceSelectionGroupButton -import com.zaneschepke.wireguardautotunnel.ui.screens.settings.system.components.* -import com.zaneschepke.wireguardautotunnel.viewmodel.SettingsViewModel - -@Composable -fun SystemFeaturesScreen(viewModel: SettingsViewModel = hiltViewModel()) { - val settingsState by viewModel.container.stateFlow.collectAsStateWithLifecycle() - - val isTv = LocalIsAndroidTV.current - - SecureScreenFromRecording() - - Column( - horizontalAlignment = Alignment.Start, - verticalArrangement = Arrangement.spacedBy(12.dp, Alignment.Top), - modifier = - Modifier.fillMaxSize().verticalScroll(rememberScrollState()).padding(horizontal = 16.dp), - ) { - SurfaceSelectionGroupButton(buildList { if (!isTv) add(nativeKillSwitchItem()) }) - SectionDivider() - SurfaceSelectionGroupButton( - buildList { - add( - restartAtBootItem(settingsState.settings.isRestoreOnBootEnabled) { - viewModel.setRestoreOnBootEnabled(it) - } - ) - if (!isTv) - add( - alwaysOnVpnItem(settingsState.settings.isAlwaysOnVpnEnabled) { - viewModel.setAlwaysOnVpnEnabled(it) - } - ) - } - ) - SectionDivider() - SurfaceSelectionGroupButton( - buildList { - add( - appShortcutsItem(settingsState.settings.isShortcutsEnabled) { - viewModel.setShortcutsEnabled(it) - } - ) - add( - remoteControlItem(settingsState.isRemoteEnabled, settingsState.remoteKey) { - viewModel.setRemoteEnabled(it) - } - ) - } - ) - } -} diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/system/components/alwaysOnVpnItem.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/system/components/alwaysOnVpnItem.kt deleted file mode 100644 index 76b4d1ae..00000000 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/system/components/alwaysOnVpnItem.kt +++ /dev/null @@ -1,38 +0,0 @@ -package com.zaneschepke.wireguardautotunnel.ui.screens.settings.system.components - -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.outlined.VpnLock -import androidx.compose.material3.Icon -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.ui.res.stringResource -import com.zaneschepke.wireguardautotunnel.R -import com.zaneschepke.wireguardautotunnel.ui.common.button.ScaledSwitch -import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SelectionItem - -@Composable -fun alwaysOnVpnItem(checked: Boolean, onChange: (checked: Boolean) -> Unit): SelectionItem { - return SelectionItem( - leading = { Icon(Icons.Outlined.VpnLock, contentDescription = null) }, - trailing = { - ScaledSwitch( - // enabled = - // !((uiState.generalSettings.isTunnelOnWifiEnabled || - // uiState.generalSettings.isTunnelOnEthernetEnabled || - // uiState.generalSettings.isTunnelOnMobileDataEnabled) && - // uiState.generalSettings.isAutoTunnelEnabled), - checked = checked, - onClick = onChange, - ) - }, - title = { - Text( - text = stringResource(R.string.always_on_vpn_support), - style = - MaterialTheme.typography.bodyMedium.copy(MaterialTheme.colorScheme.onSurface), - ) - }, - onClick = { onChange(!checked) }, - ) -} diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/system/components/appShortcutItem.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/system/components/appShortcutItem.kt deleted file mode 100644 index c4d8b1be..00000000 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/system/components/appShortcutItem.kt +++ /dev/null @@ -1,28 +0,0 @@ -package com.zaneschepke.wireguardautotunnel.ui.screens.settings.system.components - -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.AppShortcut -import androidx.compose.material3.Icon -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.ui.res.stringResource -import com.zaneschepke.wireguardautotunnel.R -import com.zaneschepke.wireguardautotunnel.ui.common.button.ScaledSwitch -import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SelectionItem - -@Composable -fun appShortcutsItem(checked: Boolean, onChange: (checked: Boolean) -> Unit): SelectionItem { - return SelectionItem( - leading = { Icon(Icons.Filled.AppShortcut, contentDescription = null) }, - trailing = { ScaledSwitch(checked = checked, onClick = onChange) }, - title = { - Text( - text = stringResource(R.string.enabled_app_shortcuts), - style = - MaterialTheme.typography.bodyMedium.copy(MaterialTheme.colorScheme.onSurface), - ) - }, - onClick = { onChange(!checked) }, - ) -} diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/system/components/nativeKillSwitchItem.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/system/components/nativeKillSwitchItem.kt deleted file mode 100644 index c013ddb7..00000000 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/system/components/nativeKillSwitchItem.kt +++ /dev/null @@ -1,31 +0,0 @@ -package com.zaneschepke.wireguardautotunnel.ui.screens.settings.system.components - -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.outlined.AdminPanelSettings -import androidx.compose.material3.Icon -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.res.stringResource -import com.zaneschepke.wireguardautotunnel.R -import com.zaneschepke.wireguardautotunnel.ui.common.button.LaunchButton -import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SelectionItem -import com.zaneschepke.wireguardautotunnel.util.extensions.launchVpnSettings - -@Composable -fun nativeKillSwitchItem(): SelectionItem { - val context = LocalContext.current - return SelectionItem( - leading = { Icon(Icons.Outlined.AdminPanelSettings, contentDescription = null) }, - title = { - Text( - text = stringResource(R.string.native_kill_switch), - style = - MaterialTheme.typography.bodyMedium.copy(MaterialTheme.colorScheme.onSurface), - ) - }, - trailing = { LaunchButton { context.launchVpnSettings() } }, - onClick = { context.launchVpnSettings() }, - ) -} diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/system/components/remoteControlItem.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/system/components/remoteControlItem.kt deleted file mode 100644 index 7175d496..00000000 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/system/components/remoteControlItem.kt +++ /dev/null @@ -1,55 +0,0 @@ -package com.zaneschepke.wireguardautotunnel.ui.screens.settings.system.components - -import androidx.compose.animation.AnimatedVisibility -import androidx.compose.foundation.clickable -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.SmartToy -import androidx.compose.material3.Icon -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.text.style.TextOverflow -import com.zaneschepke.wireguardautotunnel.R -import com.zaneschepke.wireguardautotunnel.ui.common.button.ScaledSwitch -import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SelectionItem -import com.zaneschepke.wireguardautotunnel.ui.common.functions.rememberClipboardHelper - -@Composable -fun remoteControlItem( - checked: Boolean, - key: String?, - onChange: (checked: Boolean) -> Unit, -): SelectionItem { - val clipboardManager = rememberClipboardHelper() - - return SelectionItem( - leading = { Icon(Icons.Filled.SmartToy, contentDescription = null) }, - trailing = { ScaledSwitch(checked = checked, onClick = onChange) }, - description = { - key?.let { key -> - AnimatedVisibility(visible = checked) { - Text( - text = stringResource(R.string.remote_key_template, key), - style = - MaterialTheme.typography.bodySmall.copy( - color = MaterialTheme.colorScheme.outline - ), - maxLines = 1, - overflow = TextOverflow.Ellipsis, - modifier = Modifier.clickable { clipboardManager.copy(key) }, - ) - } - } - }, - title = { - Text( - text = stringResource(R.string.enable_remote_app_control), - style = - MaterialTheme.typography.bodyMedium.copy(MaterialTheme.colorScheme.onSurface), - ) - }, - onClick = { onChange(!checked) }, - ) -} diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/system/components/restartAtBootItem.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/system/components/restartAtBootItem.kt deleted file mode 100644 index 28a5a2e1..00000000 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/system/components/restartAtBootItem.kt +++ /dev/null @@ -1,28 +0,0 @@ -package com.zaneschepke.wireguardautotunnel.ui.screens.settings.system.components - -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.outlined.Restore -import androidx.compose.material3.Icon -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.ui.res.stringResource -import com.zaneschepke.wireguardautotunnel.R -import com.zaneschepke.wireguardautotunnel.ui.common.button.ScaledSwitch -import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SelectionItem - -@Composable -fun restartAtBootItem(enabled: Boolean, onChange: (checked: Boolean) -> Unit): SelectionItem { - return SelectionItem( - leading = { Icon(Icons.Outlined.Restore, contentDescription = null) }, - trailing = { ScaledSwitch(checked = enabled, onClick = onChange) }, - title = { - Text( - text = stringResource(R.string.restart_at_boot), - style = - MaterialTheme.typography.bodyMedium.copy(MaterialTheme.colorScheme.onSurface), - ) - }, - onClick = { onChange(!enabled) }, - ) -} diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/support/SupportScreen.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/support/SupportScreen.kt index 1d2b9f76..70c2dee0 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/support/SupportScreen.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/support/SupportScreen.kt @@ -1,144 +1,203 @@ package com.zaneschepke.wireguardautotunnel.ui.screens.support -import androidx.compose.foundation.layout.* +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.size import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll -import androidx.compose.material3.LinearProgressIndicator +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.outlined.Launch +import androidx.compose.material.icons.outlined.* +import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -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.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.StrokeCap +import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource -import androidx.compose.ui.text.* -import androidx.compose.ui.text.style.TextDecoration +import androidx.compose.ui.res.vectorResource import androidx.compose.ui.unit.dp import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.zaneschepke.wireguardautotunnel.BuildConfig import com.zaneschepke.wireguardautotunnel.R import com.zaneschepke.wireguardautotunnel.ui.LocalNavController -import com.zaneschepke.wireguardautotunnel.ui.common.SectionDivider -import com.zaneschepke.wireguardautotunnel.ui.common.dialog.InfoDialog +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.text.DescriptionText import com.zaneschepke.wireguardautotunnel.ui.navigation.Route -import com.zaneschepke.wireguardautotunnel.ui.screens.support.components.ContactSupportOptions -import com.zaneschepke.wireguardautotunnel.ui.screens.support.components.DonateSection -import com.zaneschepke.wireguardautotunnel.ui.screens.support.components.GeneralSupportOptions -import com.zaneschepke.wireguardautotunnel.ui.screens.support.components.UpdateSection +import com.zaneschepke.wireguardautotunnel.ui.screens.support.components.PermissionDialog +import com.zaneschepke.wireguardautotunnel.ui.screens.support.components.UpdateDialog import com.zaneschepke.wireguardautotunnel.util.Constants -import com.zaneschepke.wireguardautotunnel.util.extensions.canInstallPackages -import com.zaneschepke.wireguardautotunnel.util.extensions.openWebUrl -import com.zaneschepke.wireguardautotunnel.util.extensions.requestInstallPackagesPermission +import com.zaneschepke.wireguardautotunnel.util.extensions.* import com.zaneschepke.wireguardautotunnel.viewmodel.SupportViewModel @Composable fun SupportScreen(viewModel: SupportViewModel = hiltViewModel()) { val context = LocalContext.current val navController = LocalNavController.current + val supportState by viewModel.container.stateFlow.collectAsStateWithLifecycle() + val clipboardManager = rememberClipboardHelper() + + val version = remember { + "v${BuildConfig.VERSION_NAME + + if(BuildConfig.DEBUG) "-debug" else "" }" + } + var showPermissionDialog by rememberSaveable { mutableStateOf(false) } if (supportState.appUpdate != null) { - InfoDialog( - onDismiss = { viewModel.dismissUpdate() }, - onAttest = { - if (BuildConfig.FLAVOR != Constants.STANDALONE_FLAVOR) { - supportState.appUpdate?.apkUrl?.let { context.openWebUrl(it) } - return@InfoDialog - } - if (context.canInstallPackages()) { - viewModel.downloadAndInstall() - } else { - showPermissionDialog = true - } - }, - title = { Text(stringResource(R.string.update_available)) }, - body = { - Column( - horizontalAlignment = Alignment.Start, - verticalArrangement = Arrangement.spacedBy(12.dp, Alignment.Top), - modifier = Modifier.fillMaxWidth(), - ) { - val annotatedString = buildAnnotatedString { - append("${supportState.appUpdate?.version ?: ""}\n") - // Add clickable text for second line - withLink( - link = - LinkAnnotation.Clickable( - tag = stringResource(id = R.string.release_notes), - linkInteractionListener = { viewModel.viewReleaseNotes() }, - styles = - TextLinkStyles( - style = - SpanStyle( - color = MaterialTheme.colorScheme.primary, - textDecoration = TextDecoration.Underline, - ) - ), - ) - ) { - append(stringResource(R.string.release_notes)) - } - } - - Text(text = annotatedString) - if (supportState.isLoading) { - LinearProgressIndicator( - progress = { supportState.downloadProgress }, - modifier = Modifier.fillMaxWidth().padding(top = 16.dp), - trackColor = MaterialTheme.colorScheme.primary.copy(alpha = 0.1f), - color = MaterialTheme.colorScheme.primary, - strokeCap = StrokeCap.Round, - ) - } - } - }, - confirmText = { - Text( - if (BuildConfig.FLAVOR != Constants.STANDALONE_FLAVOR) - stringResource(R.string.download) - else stringResource(R.string.download_and_install) - ) - }, + UpdateDialog( + viewModel = viewModel, + context = context, + onPermissionNeeded = { showPermissionDialog = true }, ) } if (showPermissionDialog) { - InfoDialog( - onDismiss = { showPermissionDialog = false }, - onAttest = { - context.requestInstallPackagesPermission() - showPermissionDialog = false - }, - title = { Text(stringResource(R.string.permission_required)) }, - body = { Text(stringResource(R.string.install_updated_permission)) }, - confirmText = { Text(stringResource(R.string.allow)) }, - ) + PermissionDialog(context = context, onDismiss = { showPermissionDialog = false }) } Column( - modifier = - Modifier.fillMaxSize() - .verticalScroll(rememberScrollState()) - .padding(horizontal = 16.dp), + modifier = Modifier.fillMaxSize().verticalScroll(rememberScrollState()), horizontalAlignment = Alignment.Start, - verticalArrangement = Arrangement.spacedBy(12.dp, Alignment.Top), + verticalArrangement = Arrangement.spacedBy(16.dp, Alignment.Top), ) { - GroupLabel(stringResource(R.string.thank_you), modifier = Modifier.padding(bottom = 12.dp)) - UpdateSection { viewModel.checkForUpdate() } - SectionDivider() - DonateSection { navController.push(Route.Donate) } - SectionDivider() - GeneralSupportOptions() - SectionDivider() - ContactSupportOptions(context) + GroupLabel( + stringResource(R.string.thank_you), + modifier = Modifier.padding(horizontal = 16.dp), + MaterialTheme.colorScheme.onSurface, + ) + Column { + GroupLabel(stringResource(R.string.resources), Modifier.padding(horizontal = 16.dp)) + SurfaceRow( + stringResource(R.string.docs_description), + onClick = { context.openWebUrl(context.getString(R.string.docs_url)) }, + leading = { Icon(Icons.Outlined.Book, contentDescription = null) }, + trailing = { Icon(Icons.AutoMirrored.Outlined.Launch, null) }, + ) + SurfaceRow( + stringResource(R.string.website), + onClick = { context.openWebUrl(context.getString(R.string.website_url)) }, + leading = { Icon(Icons.Outlined.Web, contentDescription = null) }, + trailing = { Icon(Icons.AutoMirrored.Outlined.Launch, null) }, + ) + SurfaceRow( + leading = { Icon(Icons.Outlined.Favorite, contentDescription = null) }, + title = stringResource(R.string.donate), + onClick = { navController.push(Route.Donate) }, + ) + SurfaceRow( + leading = { Icon(Icons.Outlined.Balance, contentDescription = null) }, + title = stringResource(R.string.licenses), + onClick = { navController.push(Route.License) }, + ) + SurfaceRow( + leading = { Icon(Icons.Outlined.Policy, contentDescription = null) }, + title = stringResource(R.string.privacy_policy), + trailing = { Icon(Icons.AutoMirrored.Outlined.Launch, null) }, + onClick = { context.openWebUrl(context.getString(R.string.privacy_policy_url)) }, + ) + if (BuildConfig.FLAVOR == Constants.GOOGLE_PLAY_FLAVOR) { + SurfaceRow( + leading = { Icon(Icons.Outlined.Reviews, contentDescription = null) }, + title = stringResource(R.string.review), + trailing = { Icon(Icons.AutoMirrored.Outlined.Launch, null) }, + onClick = { context.launchPlayStoreReview() }, + ) + } + } + Column { + GroupLabel( + stringResource(R.string.contact), + modifier = Modifier.padding(horizontal = 16.dp), + ) + SurfaceRow( + leading = { + Icon( + ImageVector.vectorResource(R.drawable.matrix), + contentDescription = null, + modifier = Modifier.size(24.dp), + ) + }, + title = stringResource(R.string.join_matrix), + trailing = { Icon(Icons.AutoMirrored.Outlined.Launch, null) }, + onClick = { context.openWebUrl(context.getString(R.string.matrix_url)) }, + ) + SurfaceRow( + leading = { + Icon( + ImageVector.vectorResource(R.drawable.telegram), + contentDescription = null, + modifier = Modifier.size(24.dp), + ) + }, + title = stringResource(R.string.join_telegram), + trailing = { Icon(Icons.AutoMirrored.Outlined.Launch, null) }, + onClick = { context.openWebUrl(context.getString(R.string.telegram_url)) }, + ) + SurfaceRow( + leading = { + Icon( + ImageVector.vectorResource(R.drawable.github), + contentDescription = null, + modifier = Modifier.size(24.dp), + ) + }, + title = stringResource(R.string.open_issue), + trailing = { Icon(Icons.AutoMirrored.Outlined.Launch, null) }, + onClick = { context.openWebUrl(context.getString(R.string.github_url)) }, + ) + SurfaceRow( + leading = { Icon(Icons.Outlined.Mail, contentDescription = null) }, + title = stringResource(R.string.email_description), + trailing = { Icon(Icons.AutoMirrored.Outlined.Launch, null) }, + onClick = { context.launchSupportEmail() }, + ) + } + Column { + GroupLabel( + stringResource(R.string.other), + modifier = Modifier.padding(horizontal = 16.dp), + ) + SurfaceRow( + leading = { Icon(Icons.Outlined.Memory, contentDescription = null) }, + title = stringResource(R.string.about), + description = { + Column { + DescriptionText(stringResource(R.string.version_template, version)) + DescriptionText( + stringResource(R.string.flavor_template, BuildConfig.FLAVOR) + ) + } + }, + onClick = { clipboardManager.copy(version) }, + ) + SurfaceRow( + leading = { Icon(Icons.Outlined.InstallMobile, contentDescription = null) }, + title = stringResource(R.string.check_for_update), + onClick = { + if (BuildConfig.DEBUG) + return@SurfaceRow context.showToast(R.string.update_check_unsupported) + when (BuildConfig.FLAVOR) { + Constants.GOOGLE_PLAY_FLAVOR -> context.launchPlayStoreListing() + Constants.FDROID_FLAVOR -> context.launchFDroidListing() + else -> viewModel.checkForStandaloneUpdate() + } + }, + trailing = + if (BuildConfig.FLAVOR == Constants.STANDALONE_FLAVOR) null + else { + { Icon(Icons.AutoMirrored.Outlined.Launch, null) } + }, + ) + } } } diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/support/components/ContactSupportOptions.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/support/components/ContactSupportOptions.kt deleted file mode 100644 index fa4f7edc..00000000 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/support/components/ContactSupportOptions.kt +++ /dev/null @@ -1,99 +0,0 @@ -package com.zaneschepke.wireguardautotunnel.ui.screens.support.components - -import androidx.compose.foundation.layout.size -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.outlined.Mail -import androidx.compose.material3.Icon -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.vector.ImageVector -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.res.vectorResource -import com.zaneschepke.wireguardautotunnel.R -import com.zaneschepke.wireguardautotunnel.ui.common.button.LaunchButton -import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SelectionItem -import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SelectionItemLabel -import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SelectionLabelType -import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SurfaceSelectionGroupButton -import com.zaneschepke.wireguardautotunnel.ui.theme.iconSize -import com.zaneschepke.wireguardautotunnel.util.extensions.launchSupportEmail -import com.zaneschepke.wireguardautotunnel.util.extensions.openWebUrl - -@Composable -fun ContactSupportOptions(context: android.content.Context) { - SurfaceSelectionGroupButton( - items = - listOf( - SelectionItem( - leading = { - Icon( - ImageVector.vectorResource(R.drawable.matrix), - contentDescription = null, - Modifier.size(iconSize), - ) - }, - title = { - SelectionItemLabel( - stringResource(R.string.join_matrix), - SelectionLabelType.TITLE, - ) - }, - trailing = { - LaunchButton { context.openWebUrl(context.getString(R.string.matrix_url)) } - }, - onClick = { context.openWebUrl(context.getString(R.string.matrix_url)) }, - ), - SelectionItem( - leading = { - Icon( - ImageVector.vectorResource(R.drawable.telegram), - contentDescription = null, - Modifier.size(iconSize), - ) - }, - title = { - SelectionItemLabel( - stringResource(R.string.join_telegram), - SelectionLabelType.TITLE, - ) - }, - trailing = { - LaunchButton { - context.openWebUrl(context.getString(R.string.telegram_url)) - } - }, - onClick = { context.openWebUrl(context.getString(R.string.telegram_url)) }, - ), - SelectionItem( - leading = { - Icon( - ImageVector.vectorResource(R.drawable.github), - contentDescription = null, - Modifier.size(iconSize), - ) - }, - title = { - SelectionItemLabel( - stringResource(R.string.open_issue), - SelectionLabelType.TITLE, - ) - }, - trailing = { - LaunchButton { context.openWebUrl(context.getString(R.string.github_url)) } - }, - onClick = { context.openWebUrl(context.getString(R.string.github_url)) }, - ), - SelectionItem( - leading = { Icon(Icons.Outlined.Mail, contentDescription = null) }, - title = { - SelectionItemLabel( - stringResource(R.string.email_description), - SelectionLabelType.TITLE, - ) - }, - trailing = { LaunchButton { context.launchSupportEmail() } }, - onClick = { context.launchSupportEmail() }, - ), - ) - ) -} diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/support/components/DonateSection.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/support/components/DonateSection.kt deleted file mode 100644 index 982396b1..00000000 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/support/components/DonateSection.kt +++ /dev/null @@ -1,29 +0,0 @@ -package com.zaneschepke.wireguardautotunnel.ui.screens.support.components - -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.outlined.Favorite -import androidx.compose.material3.Icon -import androidx.compose.runtime.Composable -import androidx.compose.ui.res.stringResource -import com.zaneschepke.wireguardautotunnel.R -import com.zaneschepke.wireguardautotunnel.ui.common.button.ForwardButton -import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SelectionItem -import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SelectionItemLabel -import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SelectionLabelType -import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SurfaceSelectionGroupButton - -@Composable -fun DonateSection(onClick: () -> Unit) { - SurfaceSelectionGroupButton( - listOf( - SelectionItem( - leading = { Icon(Icons.Outlined.Favorite, contentDescription = null) }, - title = { - SelectionItemLabel(stringResource(R.string.donate), SelectionLabelType.TITLE) - }, - trailing = { ForwardButton { onClick() } }, - onClick = onClick, - ) - ) - ) -} diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/support/components/GeneralSupportOptions.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/support/components/GeneralSupportOptions.kt deleted file mode 100644 index 51ddb40f..00000000 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/support/components/GeneralSupportOptions.kt +++ /dev/null @@ -1,80 +0,0 @@ -package com.zaneschepke.wireguardautotunnel.ui.screens.support.components - -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.outlined.Balance -import androidx.compose.material.icons.outlined.Book -import androidx.compose.material.icons.outlined.Policy -import androidx.compose.material3.Icon -import androidx.compose.runtime.Composable -import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.res.stringResource -import com.zaneschepke.wireguardautotunnel.R -import com.zaneschepke.wireguardautotunnel.ui.LocalNavController -import com.zaneschepke.wireguardautotunnel.ui.common.button.ForwardButton -import com.zaneschepke.wireguardautotunnel.ui.common.button.LaunchButton -import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SelectionItem -import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SelectionItemLabel -import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SelectionLabelType -import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SurfaceSelectionGroupButton -import com.zaneschepke.wireguardautotunnel.ui.navigation.Route -import com.zaneschepke.wireguardautotunnel.util.extensions.openWebUrl - -@Composable -fun GeneralSupportOptions() { - val context = LocalContext.current - val navController = LocalNavController.current - SurfaceSelectionGroupButton( - items = - buildList { - add( - SelectionItem( - leading = { Icon(Icons.Outlined.Book, contentDescription = null) }, - title = { - SelectionItemLabel( - stringResource(R.string.docs_description), - SelectionLabelType.TITLE, - ) - }, - trailing = { - LaunchButton { - context.openWebUrl(context.getString(R.string.docs_url)) - } - }, - onClick = { context.openWebUrl(context.getString(R.string.docs_url)) }, - ) - ) - add( - SelectionItem( - leading = { Icon(Icons.Outlined.Policy, contentDescription = null) }, - title = { - SelectionItemLabel( - stringResource(R.string.privacy_policy), - SelectionLabelType.TITLE, - ) - }, - trailing = { - LaunchButton { - context.openWebUrl(context.getString(R.string.privacy_policy_url)) - } - }, - onClick = { - context.openWebUrl(context.getString(R.string.privacy_policy_url)) - }, - ) - ) - add( - SelectionItem( - leading = { Icon(Icons.Outlined.Balance, contentDescription = null) }, - title = { - SelectionItemLabel( - stringResource(R.string.licenses), - SelectionLabelType.TITLE, - ) - }, - trailing = { ForwardButton { navController.push(Route.License) } }, - onClick = { navController.push(Route.License) }, - ) - ) - } - ) -} diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/support/components/PermissionDialog.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/support/components/PermissionDialog.kt new file mode 100644 index 00000000..54f8e30a --- /dev/null +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/support/components/PermissionDialog.kt @@ -0,0 +1,23 @@ +package com.zaneschepke.wireguardautotunnel.ui.screens.support.components + +import android.content.Context +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.res.stringResource +import com.zaneschepke.wireguardautotunnel.R +import com.zaneschepke.wireguardautotunnel.ui.common.dialog.InfoDialog +import com.zaneschepke.wireguardautotunnel.util.extensions.requestInstallPackagesPermission + +@Composable +fun PermissionDialog(context: Context, onDismiss: () -> Unit) { + InfoDialog( + onDismiss = onDismiss, + onAttest = { + context.requestInstallPackagesPermission() + onDismiss() + }, + title = { Text(stringResource(R.string.permission_required)) }, + body = { Text(stringResource(R.string.install_updated_permission)) }, + confirmText = { Text(stringResource(R.string.allow)) }, + ) +} diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/support/components/UpdateDialog.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/support/components/UpdateDialog.kt new file mode 100644 index 00000000..f2850b1f --- /dev/null +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/support/components/UpdateDialog.kt @@ -0,0 +1,100 @@ +package com.zaneschepke.wireguardautotunnel.ui.screens.support.components + +import android.content.Context +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi +import androidx.compose.material3.LinearWavyProgressIndicator +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.StrokeCap +import androidx.compose.ui.graphics.drawscope.Stroke +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.* +import androidx.compose.ui.text.style.TextDecoration +import androidx.compose.ui.unit.dp +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.zaneschepke.wireguardautotunnel.BuildConfig +import com.zaneschepke.wireguardautotunnel.R +import com.zaneschepke.wireguardautotunnel.ui.common.dialog.InfoDialog +import com.zaneschepke.wireguardautotunnel.util.Constants +import com.zaneschepke.wireguardautotunnel.util.extensions.canInstallPackages +import com.zaneschepke.wireguardautotunnel.util.extensions.openWebUrl +import com.zaneschepke.wireguardautotunnel.viewmodel.SupportViewModel + +@OptIn(ExperimentalMaterial3ExpressiveApi::class) +@Composable +fun UpdateDialog(viewModel: SupportViewModel, context: Context, onPermissionNeeded: () -> Unit) { + val supportState by viewModel.container.stateFlow.collectAsStateWithLifecycle() + + InfoDialog( + onDismiss = { viewModel.dismissUpdate() }, + onAttest = { + if (BuildConfig.FLAVOR != Constants.STANDALONE_FLAVOR) { + supportState.appUpdate?.apkUrl?.let { context.openWebUrl(it) } + return@InfoDialog + } + if (context.canInstallPackages()) { + viewModel.downloadAndInstall() + } else { + onPermissionNeeded() + } + }, + title = { Text(stringResource(R.string.update_available)) }, + body = { + Column( + horizontalAlignment = Alignment.Start, + verticalArrangement = Arrangement.spacedBy(12.dp, Alignment.Top), + modifier = Modifier.fillMaxWidth(), + ) { + val annotatedString = buildAnnotatedString { + append("${supportState.appUpdate?.version ?: ""}\n") + // Add clickable text for second line + withLink( + link = + LinkAnnotation.Clickable( + tag = stringResource(id = R.string.release_notes), + linkInteractionListener = { viewModel.viewReleaseNotes() }, + styles = + TextLinkStyles( + style = + SpanStyle( + color = MaterialTheme.colorScheme.primary, + textDecoration = TextDecoration.Underline, + ) + ), + ) + ) { + append(stringResource(R.string.release_notes)) + } + } + + Text(text = annotatedString) + if (supportState.isLoading) { + val stroke = Stroke(cap = StrokeCap.Round) + LinearWavyProgressIndicator( + progress = { supportState.downloadProgress }, + modifier = Modifier.fillMaxWidth().padding(top = 16.dp), + color = MaterialTheme.colorScheme.primary, + trackColor = MaterialTheme.colorScheme.primary.copy(alpha = 0.1f), + stroke = stroke, + trackStroke = stroke, + ) + } + } + }, + confirmText = { + Text( + if (BuildConfig.FLAVOR != Constants.STANDALONE_FLAVOR) + stringResource(R.string.download) + else stringResource(R.string.download_and_install) + ) + }, + ) +} diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/support/components/UpdateSection.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/support/components/UpdateSection.kt deleted file mode 100644 index debecc6e..00000000 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/support/components/UpdateSection.kt +++ /dev/null @@ -1,52 +0,0 @@ -package com.zaneschepke.wireguardautotunnel.ui.screens.support.components - -import androidx.compose.foundation.layout.Column -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.outlined.CloudDownload -import androidx.compose.material3.Icon -import androidx.compose.runtime.Composable -import androidx.compose.runtime.remember -import androidx.compose.ui.res.stringResource -import com.zaneschepke.wireguardautotunnel.BuildConfig -import com.zaneschepke.wireguardautotunnel.R -import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SelectionItem -import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SelectionItemLabel -import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SelectionLabelType -import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SurfaceSelectionGroupButton -import com.zaneschepke.wireguardautotunnel.ui.common.functions.rememberClipboardHelper - -@Composable -fun UpdateSection(onUpdateCheck: () -> Unit) { - val clipboardManager = rememberClipboardHelper() - val version = remember { - "v${BuildConfig.VERSION_NAME + - if(BuildConfig.DEBUG) "-debug" else "" }" - } - SurfaceSelectionGroupButton( - listOf( - SelectionItem( - leading = { Icon(Icons.Outlined.CloudDownload, contentDescription = null) }, - title = { - SelectionItemLabel( - stringResource(R.string.check_for_update), - SelectionLabelType.TITLE, - ) - }, - description = { - Column { - SelectionItemLabel( - stringResource(R.string.version_template, version), - SelectionLabelType.DESCRIPTION, - ) - SelectionItemLabel( - stringResource(R.string.flavor_template, BuildConfig.FLAVOR), - SelectionLabelType.DESCRIPTION, - ) - } - }, - onClick = onUpdateCheck, - onLongPress = { clipboardManager.copy(version) }, - ) - ) - ) -} diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/support/donate/DonateScreen.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/support/donate/DonateScreen.kt index 567e9666..79196813 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/support/donate/DonateScreen.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/support/donate/DonateScreen.kt @@ -4,36 +4,97 @@ 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.size 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.outlined.CurrencyBitcoin +import androidx.compose.material3.Icon import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.res.vectorResource import androidx.compose.ui.unit.dp import com.zaneschepke.wireguardautotunnel.BuildConfig +import com.zaneschepke.wireguardautotunnel.R import com.zaneschepke.wireguardautotunnel.ui.LocalNavController -import com.zaneschepke.wireguardautotunnel.ui.common.SectionDivider +import com.zaneschepke.wireguardautotunnel.ui.common.button.SurfaceRow +import com.zaneschepke.wireguardautotunnel.ui.common.label.GroupLabel import com.zaneschepke.wireguardautotunnel.ui.navigation.Route import com.zaneschepke.wireguardautotunnel.ui.screens.support.donate.components.DonationHeroSection -import com.zaneschepke.wireguardautotunnel.ui.screens.support.donate.components.DonationOptions import com.zaneschepke.wireguardautotunnel.ui.screens.support.donate.components.GoogleDonationMessage import com.zaneschepke.wireguardautotunnel.util.Constants +import com.zaneschepke.wireguardautotunnel.util.extensions.openWebUrl @Composable fun DonateScreen() { + val context = LocalContext.current val navController = LocalNavController.current + val isGoogleFlavor = remember { BuildConfig.FLAVOR == Constants.GOOGLE_PLAY_FLAVOR } Column( horizontalAlignment = Alignment.Start, - verticalArrangement = Arrangement.spacedBy(12.dp, Alignment.Top), - modifier = - Modifier.fillMaxSize().verticalScroll(rememberScrollState()).padding(horizontal = 16.dp), + verticalArrangement = Arrangement.spacedBy(16.dp, Alignment.Top), + modifier = Modifier.fillMaxSize().verticalScroll(rememberScrollState()), ) { DonationHeroSection() - SectionDivider() - if (BuildConfig.FLAVOR != Constants.GOOGLE_PLAY_FLAVOR) { - DonationOptions { navController.push(Route.Addresses) } - } else { - GoogleDonationMessage() + Column { + GroupLabel( + stringResource(R.string.options), + Modifier.padding(bottom = if (isGoogleFlavor) 8.dp else 0.dp) + .padding(horizontal = 16.dp), + ) + if (!isGoogleFlavor) { + SurfaceRow( + leading = { Icon(Icons.Outlined.CurrencyBitcoin, contentDescription = null) }, + title = stringResource(R.string.crypto), + onClick = { navController.push(Route.Addresses) }, + ) + SurfaceRow( + leading = { + Icon( + ImageVector.vectorResource(R.drawable.github), + contentDescription = null, + modifier = Modifier.size(24.dp), + ) + }, + title = stringResource(R.string.github_sponsors), + trailing = { Icon(Icons.AutoMirrored.Outlined.Launch, null) }, + onClick = { + context.openWebUrl(context.getString(R.string.github_sponsors_url)) + }, + ) + SurfaceRow( + leading = { + Icon( + ImageVector.vectorResource(R.drawable.liberapay), + contentDescription = null, + modifier = Modifier.size(24.dp), + ) + }, + title = stringResource(R.string.liberapay), + trailing = { Icon(Icons.AutoMirrored.Outlined.Launch, null) }, + onClick = { context.openWebUrl(context.getString(R.string.liberapay_url)) }, + ) + SurfaceRow( + leading = { + Icon( + ImageVector.vectorResource(R.drawable.kofi), + contentDescription = null, + modifier = Modifier.size(24.dp), + ) + }, + title = stringResource(R.string.kofi), + trailing = { Icon(Icons.AutoMirrored.Outlined.Launch, null) }, + onClick = { context.openWebUrl(context.getString(R.string.kofi_url)) }, + ) + } else { + GoogleDonationMessage() + } } } } diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/support/donate/components/DonationHeroSection.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/support/donate/components/DonationHeroSection.kt index ac87c50c..72bac644 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/support/donate/components/DonationHeroSection.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/support/donate/components/DonationHeroSection.kt @@ -3,6 +3,7 @@ package com.zaneschepke.wireguardautotunnel.ui.screens.support.donate.components import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Surface import androidx.compose.material3.Text @@ -19,7 +20,7 @@ import com.zaneschepke.wireguardautotunnel.R fun DonationHeroSection() { Surface(modifier = Modifier.fillMaxWidth()) { Column( - modifier = Modifier.fillMaxWidth(), + modifier = Modifier.fillMaxWidth().padding(horizontal = 16.dp), horizontalAlignment = Alignment.Start, verticalArrangement = Arrangement.spacedBy(12.dp), ) { diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/support/donate/components/DonationOptions.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/support/donate/components/DonationOptions.kt deleted file mode 100644 index 16cac99a..00000000 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/support/donate/components/DonationOptions.kt +++ /dev/null @@ -1,97 +0,0 @@ -package com.zaneschepke.wireguardautotunnel.ui.screens.support.donate.components - -import androidx.compose.foundation.layout.size -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.outlined.CurrencyBitcoin -import androidx.compose.material3.Icon -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.vector.ImageVector -import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.res.vectorResource -import com.zaneschepke.wireguardautotunnel.R -import com.zaneschepke.wireguardautotunnel.ui.common.button.ForwardButton -import com.zaneschepke.wireguardautotunnel.ui.common.button.LaunchButton -import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SelectionItem -import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SelectionItemLabel -import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SelectionLabelType -import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SurfaceSelectionGroupButton -import com.zaneschepke.wireguardautotunnel.ui.theme.iconSize -import com.zaneschepke.wireguardautotunnel.util.extensions.openWebUrl - -@Composable -fun DonationOptions(onAddressesClick: () -> Unit) { - val context = LocalContext.current - SurfaceSelectionGroupButton( - listOf( - SelectionItem( - leading = { - Icon( - Icons.Outlined.CurrencyBitcoin, - contentDescription = null, - modifier = Modifier.size(iconSize), - ) - }, - title = { - SelectionItemLabel(stringResource(R.string.crypto), SelectionLabelType.TITLE) - }, - trailing = { ForwardButton { onAddressesClick() } }, - onClick = onAddressesClick, - ), - SelectionItem( - leading = { - Icon( - ImageVector.vectorResource(R.drawable.github), - contentDescription = null, - modifier = Modifier.size(iconSize), - ) - }, - title = { - SelectionItemLabel( - stringResource(R.string.github_sponsors), - SelectionLabelType.TITLE, - ) - }, - trailing = { - LaunchButton { - context.openWebUrl(context.getString(R.string.github_sponsors_url)) - } - }, - onClick = { context.openWebUrl(context.getString(R.string.github_sponsors_url)) }, - ), - SelectionItem( - leading = { - Icon( - ImageVector.vectorResource(R.drawable.liberapay), - contentDescription = null, - modifier = Modifier.size(iconSize), - ) - }, - title = { - SelectionItemLabel(stringResource(R.string.liberapay), SelectionLabelType.TITLE) - }, - trailing = { - LaunchButton { context.openWebUrl(context.getString(R.string.liberapay_url)) } - }, - onClick = { context.openWebUrl(context.getString(R.string.liberapay_url)) }, - ), - SelectionItem( - leading = { - Icon( - ImageVector.vectorResource(R.drawable.kofi), - contentDescription = null, - modifier = Modifier.size(iconSize), - ) - }, - title = { - SelectionItemLabel(stringResource(R.string.kofi), SelectionLabelType.TITLE) - }, - trailing = { - LaunchButton { context.openWebUrl(context.getString(R.string.kofi_url)) } - }, - onClick = { context.openWebUrl(context.getString(R.string.kofi_url)) }, - ), - ) - ) -} diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/support/donate/components/GoogleDonationMessage.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/support/donate/components/GoogleDonationMessage.kt index bef518cf..2cf8ebfa 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/support/donate/components/GoogleDonationMessage.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/support/donate/components/GoogleDonationMessage.kt @@ -4,9 +4,6 @@ import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material3.Card -import androidx.compose.material3.CardDefaults import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable @@ -19,24 +16,18 @@ import com.zaneschepke.wireguardautotunnel.R @Composable fun GoogleDonationMessage() { - Card( - modifier = Modifier.fillMaxWidth(), - shape = RoundedCornerShape(12.dp), - colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surface), + Column( + modifier = Modifier.fillMaxWidth().padding(horizontal = 16.dp), + horizontalAlignment = Alignment.Start, + verticalArrangement = Arrangement.spacedBy(12.dp), ) { - Column( - modifier = Modifier.fillMaxWidth().padding(horizontal = 16.dp), - horizontalAlignment = Alignment.Start, - verticalArrangement = Arrangement.spacedBy(12.dp), - ) { - Text( - text = stringResource(R.string.google_donation_message), - style = - MaterialTheme.typography.bodyMedium.copy( - color = MaterialTheme.colorScheme.onSurface - ), - textAlign = TextAlign.Start, - ) - } + Text( + text = stringResource(R.string.google_donation_message), + style = + MaterialTheme.typography.bodySmall.copy( + color = MaterialTheme.colorScheme.onSurface + ), + textAlign = TextAlign.Start, + ) } } diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/support/donate/crypto/AddressesScreen.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/support/donate/crypto/AddressesScreen.kt index 5e2494f6..39aa716c 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/support/donate/crypto/AddressesScreen.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/support/donate/crypto/AddressesScreen.kt @@ -3,28 +3,22 @@ package com.zaneschepke.wireguardautotunnel.ui.screens.support.donate.crypto 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.rememberScrollState import androidx.compose.foundation.verticalScroll import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.unit.dp -import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SurfaceSelectionGroupButton import com.zaneschepke.wireguardautotunnel.ui.common.functions.rememberClipboardHelper -import com.zaneschepke.wireguardautotunnel.ui.screens.support.donate.crypto.components.addressItem +import com.zaneschepke.wireguardautotunnel.ui.screens.support.donate.crypto.components.AddressItem @Composable fun AddressesScreen() { Column( horizontalAlignment = Alignment.Start, - verticalArrangement = Arrangement.spacedBy(12.dp, Alignment.Top), - modifier = - Modifier.fillMaxSize().verticalScroll(rememberScrollState()).padding(horizontal = 16.dp), + verticalArrangement = Arrangement.Top, + modifier = Modifier.fillMaxSize().verticalScroll(rememberScrollState()), ) { val clipboard = rememberClipboardHelper() - SurfaceSelectionGroupButton( - Address.allAddresses.map { addressItem(it) { address -> clipboard.copy(address) } } - ) + Address.allAddresses.forEach { AddressItem(it) { address -> clipboard.copy(address) } } } } diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/support/donate/crypto/components/addressItem.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/support/donate/crypto/components/addressItem.kt index eb6f1232..d29aa11a 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/support/donate/crypto/components/addressItem.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/support/donate/crypto/components/addressItem.kt @@ -19,23 +19,21 @@ import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextOverflow -import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SelectionItem -import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SelectionItemLabel -import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SelectionLabelType +import androidx.compose.ui.unit.dp +import com.zaneschepke.wireguardautotunnel.ui.common.button.SurfaceRow import com.zaneschepke.wireguardautotunnel.ui.screens.support.donate.crypto.Address -import com.zaneschepke.wireguardautotunnel.ui.theme.iconSize @Composable -fun addressItem(address: Address, onClick: (address: String) -> Unit): SelectionItem { +fun AddressItem(address: Address, onClick: (address: String) -> Unit) { val context = LocalContext.current val walletAddress = context.getString(address.address) var expand by rememberSaveable { mutableStateOf(false) } - return SelectionItem( + SurfaceRow( leading = { Image( painter = painterResource(id = address.icon), contentDescription = null, - modifier = Modifier.size(iconSize), + modifier = Modifier.size(24.dp), ) }, trailing = { @@ -43,7 +41,7 @@ fun addressItem(address: Address, onClick: (address: String) -> Unit): Selection Icon(Icons.Outlined.CopyAll, contentDescription = null) } }, - title = { SelectionItemLabel(stringResource(address.name), SelectionLabelType.TITLE) }, + title = stringResource(address.name), description = { Text( text = walletAddress, diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/support/license/LicenseScreen.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/support/license/LicenseScreen.kt index 6d7e92bc..7fa445fa 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/support/license/LicenseScreen.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/support/license/LicenseScreen.kt @@ -1,44 +1,30 @@ package com.zaneschepke.wireguardautotunnel.ui.screens.support.license -import LicenseFileEntry -import android.content.Context import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.size import androidx.compose.material3.CircularWavyProgressIndicator import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi -import androidx.compose.runtime.* +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.unit.dp +import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel +import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.zaneschepke.wireguardautotunnel.ui.screens.support.license.components.LicenseList -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.withContext -import kotlinx.serialization.json.Json +import com.zaneschepke.wireguardautotunnel.viewmodel.LicenseViewModel @OptIn(ExperimentalMaterial3ExpressiveApi::class) @Composable -fun LicenseScreen() { - val context = LocalContext.current - var licenses by remember { mutableStateOf>(emptyList()) } +fun LicenseScreen(viewModel: LicenseViewModel = hiltViewModel()) { + val licenseUiState by viewModel.container.stateFlow.collectAsStateWithLifecycle() - LaunchedEffect(Unit) { licenses = loadLicenseeJson(context) } - - if (licenses.isEmpty()) { + if (licenseUiState.isLoading) { Box(Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { CircularWavyProgressIndicator(waveSpeed = 60.dp, modifier = Modifier.size(48.dp)) } } else { - LicenseList(licenses) - } -} - -suspend fun loadLicenseeJson(context: Context): List { - return withContext(Dispatchers.IO) { - val json = Json { ignoreUnknownKeys = true } - - val jsonResult = context.assets.open("licenses.json").bufferedReader().use { it.readText() } - json.decodeFromString(jsonResult) + LicenseList(licenseUiState.licenses) } } diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/support/license/components/LicenseList.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/support/license/components/LicenseList.kt index 9667df75..1e3de179 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/support/license/components/LicenseList.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/support/license/components/LicenseList.kt @@ -1,10 +1,13 @@ package com.zaneschepke.wireguardautotunnel.ui.screens.support.license.components import LicenseFileEntry -import androidx.compose.foundation.focusable +import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.* import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.outlined.Launch +import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable @@ -12,21 +15,22 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.unit.dp -import com.zaneschepke.wireguardautotunnel.ui.common.button.LinkIconButton import com.zaneschepke.wireguardautotunnel.util.extensions.openWebUrl @Composable fun LicenseList(licenses: List) { val context = LocalContext.current - LazyColumn( - modifier = Modifier.fillMaxSize().padding(horizontal = 16.dp), - verticalArrangement = Arrangement.spacedBy(8.dp), - ) { + LazyColumn(modifier = Modifier.fillMaxSize()) { items(licenses) { entry -> Row( verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.SpaceBetween, + modifier = + Modifier.clickable(enabled = entry.scm?.url != null) { + entry.scm?.url?.let { context.openWebUrl(it) } + } + .padding(horizontal = 16.dp, vertical = 8.dp), ) { Column(modifier = Modifier.weight(1f)) { Text( @@ -42,11 +46,7 @@ fun LicenseList(licenses: List) { ) } } - entry.scm?.url?.let { scmUrl -> - LinkIconButton(modifier = Modifier.size(20.dp).focusable()) { - context.openWebUrl(scmUrl) - } - } + entry.scm?.url?.let { Icon(Icons.AutoMirrored.Outlined.Launch, null) } } } } diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/tunnels/autotunnel/TunnelAutoTunnelScreen.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/tunnels/autotunnel/TunnelAutoTunnelScreen.kt deleted file mode 100644 index 6f535461..00000000 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/tunnels/autotunnel/TunnelAutoTunnelScreen.kt +++ /dev/null @@ -1,58 +0,0 @@ -package com.zaneschepke.wireguardautotunnel.ui.screens.tunnels.autotunnel - -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.rememberScrollState -import androidx.compose.foundation.verticalScroll -import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.unit.dp -import androidx.lifecycle.compose.collectAsStateWithLifecycle -import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SurfaceSelectionGroupButton -import com.zaneschepke.wireguardautotunnel.ui.screens.tunnels.autotunnel.components.MobileDataTunnelItem -import com.zaneschepke.wireguardautotunnel.ui.screens.tunnels.autotunnel.components.WifiTunnelItem -import com.zaneschepke.wireguardautotunnel.ui.screens.tunnels.autotunnel.components.ethernetTunnelItem -import com.zaneschepke.wireguardautotunnel.viewmodel.TunnelViewModel - -@Composable -fun TunnelAutoTunnelScreen(viewModel: TunnelViewModel) { - val tunnelState by viewModel.container.stateFlow.collectAsStateWithLifecycle() - - if (tunnelState.isLoading) return - val tunnel = tunnelState.tunnel ?: return - - Column( - horizontalAlignment = Alignment.Start, - verticalArrangement = Arrangement.spacedBy(12.dp, Alignment.Top), - modifier = - Modifier.fillMaxSize().verticalScroll(rememberScrollState()).padding(horizontal = 16.dp), - ) { - SurfaceSelectionGroupButton( - items = - buildList { - add( - MobileDataTunnelItem(tunnel.isMobileDataTunnel) { - viewModel.setMobileDataTunnel(it) - } - ) - add( - ethernetTunnelItem(tunnel.isEthernetTunnel) { - viewModel.setEthernetTunnel(it) - } - ) - add( - WifiTunnelItem( - tunnel.tunnelNetworks, - isWildcardsEnabled = tunnelState.isWildcardsEnabled, - onSaveTunnelNetwork = { viewModel.addTunnelNetwork(it) }, - onDeleteTunnelNetwork = { viewModel.removeTunnelNetwork(it) }, - ) - ) - } - ) - } -} diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/tunnels/autotunnel/components/MobileDataTunnelItem.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/tunnels/autotunnel/components/MobileDataTunnelItem.kt deleted file mode 100644 index 27de554f..00000000 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/tunnels/autotunnel/components/MobileDataTunnelItem.kt +++ /dev/null @@ -1,34 +0,0 @@ -package com.zaneschepke.wireguardautotunnel.ui.screens.tunnels.autotunnel.components - -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.outlined.PhoneAndroid -import androidx.compose.material3.Icon -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.ui.res.stringResource -import com.zaneschepke.wireguardautotunnel.R -import com.zaneschepke.wireguardautotunnel.ui.common.button.ScaledSwitch -import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SelectionItem - -@Composable -fun MobileDataTunnelItem(enabled: Boolean, onClick: (Boolean) -> Unit): SelectionItem { - return SelectionItem( - leading = { Icon(Icons.Outlined.PhoneAndroid, contentDescription = null) }, - title = { - Text( - text = stringResource(R.string.mobile_tunnel), - style = - MaterialTheme.typography.bodyMedium.copy(MaterialTheme.colorScheme.onSurface), - ) - }, - description = { - Text( - text = stringResource(R.string.mobile_data_tunnel), - style = MaterialTheme.typography.bodySmall.copy(MaterialTheme.colorScheme.outline), - ) - }, - trailing = { ScaledSwitch(checked = enabled, onClick = onClick) }, - onClick = { onClick(!enabled) }, - ) -} diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/tunnels/autotunnel/components/WifiTunnelItem.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/tunnels/autotunnel/components/WifiTunnelItem.kt deleted file mode 100644 index f3285eca..00000000 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/tunnels/autotunnel/components/WifiTunnelItem.kt +++ /dev/null @@ -1,70 +0,0 @@ -package com.zaneschepke.wireguardautotunnel.ui.screens.tunnels.autotunnel.components - -import androidx.compose.foundation.layout.* -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.outlined.Security -import androidx.compose.material3.Icon -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text -import androidx.compose.runtime.* -import androidx.compose.runtime.saveable.rememberSaveable -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.common.button.surface.SelectionItem -import com.zaneschepke.wireguardautotunnel.ui.screens.autotunnel.components.TrustedNetworkTextBox -import com.zaneschepke.wireguardautotunnel.ui.screens.autotunnel.components.WildcardsLabel -import com.zaneschepke.wireguardautotunnel.ui.theme.iconSize - -@Composable -fun WifiTunnelItem( - tunnelNetworks: Set, - isWildcardsEnabled: Boolean, - onSaveTunnelNetwork: (String) -> Unit, - onDeleteTunnelNetwork: (String) -> Unit, -): SelectionItem { - - var currentText by rememberSaveable { mutableStateOf("") } - - LaunchedEffect(tunnelNetworks) { currentText = "" } - - return SelectionItem( - title = { - Row( - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.Start, - modifier = Modifier.fillMaxWidth().padding(vertical = 8.dp), - ) { - Icon( - imageVector = Icons.Outlined.Security, - contentDescription = stringResource(R.string.use_tunnel_on_wifi_name), - modifier = Modifier.size(iconSize), - ) - Text( - text = stringResource(R.string.use_tunnel_on_wifi_name), - style = - MaterialTheme.typography.bodyMedium.copy( - MaterialTheme.colorScheme.onSurface - ), - modifier = Modifier.padding(start = 16.dp), - ) - } - }, - description = { - TrustedNetworkTextBox( - trustedNetworks = tunnelNetworks, - onDelete = onDeleteTunnelNetwork, - currentText = currentText, - onSave = onSaveTunnelNetwork, - onValueChange = { currentText = it }, - supporting = { - if (isWildcardsEnabled) { - WildcardsLabel() - } - }, - ) - }, - ) -} diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/tunnels/autotunnel/components/ethernetTunnelItem.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/tunnels/autotunnel/components/ethernetTunnelItem.kt deleted file mode 100644 index c2cff8b1..00000000 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/tunnels/autotunnel/components/ethernetTunnelItem.kt +++ /dev/null @@ -1,34 +0,0 @@ -package com.zaneschepke.wireguardautotunnel.ui.screens.tunnels.autotunnel.components - -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.outlined.SettingsEthernet -import androidx.compose.material3.Icon -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.ui.res.stringResource -import com.zaneschepke.wireguardautotunnel.R -import com.zaneschepke.wireguardautotunnel.ui.common.button.ScaledSwitch -import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SelectionItem - -@Composable -fun ethernetTunnelItem(enabled: Boolean, onClick: (Boolean) -> Unit): SelectionItem { - return SelectionItem( - leading = { Icon(Icons.Outlined.SettingsEthernet, contentDescription = null) }, - title = { - Text( - text = stringResource(R.string.ethernet_tunnel), - style = - MaterialTheme.typography.bodyMedium.copy(MaterialTheme.colorScheme.onSurface), - ) - }, - description = { - Text( - text = stringResource(R.string.set_ethernet_tunnel), - style = MaterialTheme.typography.bodySmall.copy(MaterialTheme.colorScheme.outline), - ) - }, - trailing = { ScaledSwitch(checked = enabled, onClick = onClick) }, - onClick = { onClick(!enabled) }, - ) -} diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/tunnels/components/TunnelList.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/tunnels/components/TunnelList.kt index 388e40ad..62964658 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/tunnels/components/TunnelList.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/tunnels/components/TunnelList.kt @@ -1,26 +1,32 @@ package com.zaneschepke.wireguardautotunnel.ui.screens.tunnels.components import androidx.compose.foundation.ExperimentalFoundationApi -import androidx.compose.foundation.combinedClickable import androidx.compose.foundation.gestures.ScrollableDefaults import androidx.compose.foundation.gestures.detectTapGestures -import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.layout.size 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.runtime.Composable -import androidx.compose.runtime.remember +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.rounded.Circle +import androidx.compose.material3.Icon +import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import com.zaneschepke.wireguardautotunnel.R import com.zaneschepke.wireguardautotunnel.domain.state.TunnelState -import com.zaneschepke.wireguardautotunnel.ui.LocalIsAndroidTV import com.zaneschepke.wireguardautotunnel.ui.LocalNavController +import com.zaneschepke.wireguardautotunnel.ui.common.button.SurfaceRow +import com.zaneschepke.wireguardautotunnel.ui.common.button.SwitchWithDivider import com.zaneschepke.wireguardautotunnel.ui.navigation.Route import com.zaneschepke.wireguardautotunnel.ui.state.TunnelsUiState +import com.zaneschepke.wireguardautotunnel.util.extensions.asColor import com.zaneschepke.wireguardautotunnel.util.extensions.openWebUrl import com.zaneschepke.wireguardautotunnel.viewmodel.SharedAppViewModel import com.zaneschepke.wireguardautotunnel.viewmodel.TunnelsViewModel @@ -34,7 +40,6 @@ fun TunnelList( sharedViewModel: SharedAppViewModel, ) { val navController = LocalNavController.current - val isTv = LocalIsAndroidTV.current val context = LocalContext.current val lazyListState = rememberLazyListState() @@ -58,7 +63,7 @@ fun TunnelList( if (tunnelsState.tunnels.isEmpty()) { item { GettingStartedLabel(onClick = { context.openWebUrl(it) }) } } - items(tunnelsState.tunnels.toList(), key = { it.id }) { tunnel -> + items(tunnelsState.tunnels, key = { it.id }) { tunnel -> val tunnelState = remember(tunnelsState.activeTunnels) { tunnelsState.activeTunnels[tunnel.id] ?: TunnelState() @@ -67,35 +72,56 @@ fun TunnelList( remember(tunnelsState.selectedTunnels) { tunnelsState.selectedTunnels.any { it.id == tunnel.id } } - TunnelRowItem( - state = tunnelState, - isSelected = selected, - tunnel = tunnel, - onTvClick = { navController.push(Route.TunnelOptions(tunnel.id)) }, - onToggleSelectedTunnel = { tunnel -> viewModel.toggleSelectedTunnel(tunnel.id) }, - onSwitchClick = { checked -> - if (checked) sharedViewModel.startTunnel(tunnel) - else sharedViewModel.stopTunnel(tunnel) + var leadingIconColor by + remember( + tunnelState.status, + tunnelState.logHealthState, + tunnelState.pingStates, + tunnelState.statistics, + ) { + mutableStateOf(tunnelState.health().asColor()) + } + + SurfaceRow( + leading = { + Icon( + Icons.Rounded.Circle, + contentDescription = stringResource(R.string.tunnel_monitoring), + tint = leadingIconColor, + modifier = Modifier.size(14.dp), + ) + }, + title = tunnel.tunName, + onClick = { + if (tunnelsState.selectedTunnels.isNotEmpty()) { + viewModel.toggleSelectedTunnel(tunnel.id) + } else { + navController.push(Route.TunnelOptions(tunnel.id)) + viewModel.clearSelectedTunnels() + } + }, + selected = selected, + expandedContent = + if (!tunnelState.status.isDown()) { + { + TunnelStatisticsRow( + tunnelState, + tunnelsState.isPingEnabled, + tunnelsState.showPingStats, + ) + } + } else null, + onLongClick = { viewModel.toggleSelectedTunnel(tunnel.id) }, + trailing = { modifier -> + SwitchWithDivider( + checked = tunnelState.status.isUpOrStarting(), + onClick = { checked -> + if (checked) sharedViewModel.startTunnel(tunnel) + else sharedViewModel.stopTunnel(tunnel) + }, + modifier = modifier, + ) }, - isTv = isTv, - isPingEnabled = tunnelsState.isPingEnabled, - showDetailedStats = tunnelsState.showPingStats, - modifier = - (if (!isTv) - Modifier.combinedClickable( - onClick = { - if (tunnelsState.selectedTunnels.isNotEmpty()) { - viewModel.toggleSelectedTunnel(tunnel.id) - } else { - navController.push(Route.TunnelOptions(tunnel.id)) - viewModel.clearSelectedTunnels() - } - }, - onLongClick = { viewModel.toggleSelectedTunnel(tunnel.id) }, - interactionSource = remember { MutableInteractionSource() }, - indication = null, - ) - else Modifier), ) } } diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/tunnels/components/TunnelStatisticsRow.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/tunnels/components/TunnelStatisticsRow.kt index 382093ca..73fa4d26 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/tunnels/components/TunnelStatisticsRow.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/tunnels/components/TunnelStatisticsRow.kt @@ -48,7 +48,7 @@ fun TunnelStatisticsRow( statistics?.let { stats -> Column( - modifier = Modifier.fillMaxWidth().padding(start = 32.dp, bottom = 10.dp, end = 10.dp), + modifier = Modifier.fillMaxWidth(), verticalArrangement = Arrangement.spacedBy(10.dp), horizontalAlignment = Alignment.Start, ) { @@ -99,7 +99,6 @@ fun TunnelStatisticsRow( } Column(verticalArrangement = Arrangement.spacedBy(4.dp)) { - // Peer ID row Row( verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(16.dp), @@ -110,7 +109,6 @@ fun TunnelStatisticsRow( color = textColor, ) } - // RX/TX row Row( verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(16.dp), @@ -140,7 +138,6 @@ fun TunnelStatisticsRow( Text(formattedTx, style = textStyle, color = textColor) } } - // Handshake row Row( verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(16.dp), @@ -163,7 +160,6 @@ fun TunnelStatisticsRow( ) } } - // Ping block (per unique peer key) AnimatedVisibility(visible = pingState != null && pingEnabled) { pingState?.let { val reachableText = 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 6c2e4b97..66d0060e 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 @@ -12,7 +12,6 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle -import androidx.lifecycle.viewmodel.compose.viewModel import com.zaneschepke.wireguardautotunnel.data.entity.TunnelConfig import com.zaneschepke.wireguardautotunnel.ui.LocalSharedVm import com.zaneschepke.wireguardautotunnel.ui.common.SecureScreenFromRecording diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/tunnels/config/components/InterfaceDropdown.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/tunnels/config/components/InterfaceDropdown.kt index 22653981..32c9b405 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/tunnels/config/components/InterfaceDropdown.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/tunnels/config/components/InterfaceDropdown.kt @@ -2,7 +2,6 @@ package com.zaneschepke.wireguardautotunnel.ui.screens.tunnels.config.components import androidx.compose.foundation.background import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.size import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.MoreVert import androidx.compose.material3.* @@ -12,7 +11,6 @@ import androidx.compose.ui.draw.shadow import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.zaneschepke.wireguardautotunnel.R -import com.zaneschepke.wireguardautotunnel.ui.theme.iconSize @Composable fun InterfaceDropdown( @@ -29,7 +27,7 @@ fun InterfaceDropdown( onMimicSip: () -> Unit, ) { Column { - IconButton(modifier = Modifier.size(iconSize), onClick = { onExpandedChange(true) }) { + IconButton(onClick = { onExpandedChange(true) }) { Icon( Icons.Rounded.MoreVert, contentDescription = stringResource(R.string.quick_actions), diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/tunnels/config/components/PeersSection.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/tunnels/config/components/PeersSection.kt index 1a9624b7..6b09d7a8 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/tunnels/config/components/PeersSection.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/tunnels/config/components/PeersSection.kt @@ -17,7 +17,6 @@ import com.zaneschepke.wireguardautotunnel.R import com.zaneschepke.wireguardautotunnel.ui.common.label.GroupLabel import com.zaneschepke.wireguardautotunnel.ui.state.ConfigProxy import com.zaneschepke.wireguardautotunnel.ui.state.PeerProxy -import com.zaneschepke.wireguardautotunnel.ui.theme.iconSize @Composable fun PeersSection( @@ -44,20 +43,14 @@ fun PeersSection( horizontalArrangement = Arrangement.spacedBy(10.dp), verticalAlignment = Alignment.CenterVertically, ) { - IconButton( - modifier = Modifier.size(iconSize), - onClick = { onRemove(index) }, - ) { + IconButton(onClick = { onRemove(index) }) { Icon( Icons.Rounded.Delete, contentDescription = stringResource(R.string.delete), ) } Column { - IconButton( - modifier = Modifier.size(iconSize), - onClick = { isDropDownExpanded = true }, - ) { + IconButton(onClick = { isDropDownExpanded = true }) { Icon( Icons.Rounded.MoreVert, contentDescription = stringResource(R.string.quick_actions), diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/tunnels/splittunnel/components/AppListItem.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/tunnels/splittunnel/components/AppListItem.kt index d41cf40f..527ee429 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/tunnels/splittunnel/components/AppListItem.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/tunnels/splittunnel/components/AppListItem.kt @@ -7,19 +7,24 @@ import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.size import androidx.compose.material3.Checkbox +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.LocalMinimumInteractiveComponentSize import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.unit.dp import com.google.accompanist.drawablepainter.rememberDrawablePainter import com.zaneschepke.wireguardautotunnel.R import com.zaneschepke.wireguardautotunnel.domain.model.InstalledPackage -import com.zaneschepke.wireguardautotunnel.ui.common.button.SelectionItemButton -import com.zaneschepke.wireguardautotunnel.ui.theme.iconSize +import com.zaneschepke.wireguardautotunnel.ui.common.button.SurfaceRow +import com.zaneschepke.wireguardautotunnel.ui.common.text.DescriptionText +@OptIn(ExperimentalMaterial3Api::class) @Composable fun AppListItem( installedPackage: InstalledPackage, @@ -37,23 +42,25 @@ fun AppListItem( } } - SelectionItemButton( + SurfaceRow( leading = { Image( painter = rememberDrawablePainter(icon), contentDescription = null, - modifier = Modifier.size(iconSize), + modifier = Modifier.size(24.dp), ) }, - buttonText = installedPackage.name, - description = installedPackage.packageName, + title = installedPackage.name, + description = { DescriptionText(installedPackage.packageName) }, onClick = { onToggle(!isSelected) }, trailing = { Row( horizontalArrangement = Arrangement.End, verticalAlignment = Alignment.CenterVertically, ) { - Checkbox(checked = isSelected, onCheckedChange = { onToggle(it) }) + CompositionLocalProvider(LocalMinimumInteractiveComponentSize provides 0.dp) { + Checkbox(checked = isSelected, onCheckedChange = { onToggle(it) }) + } } }, ) diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/tunnels/splittunnel/components/AppListSection.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/tunnels/splittunnel/components/AppListSection.kt index 7e639cdd..4622fbc1 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/tunnels/splittunnel/components/AppListSection.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/tunnels/splittunnel/components/AppListSection.kt @@ -68,7 +68,7 @@ fun AppListSection( label = {}, leading = { Icon(Icons.Outlined.Search, stringResource(R.string.search)) }, containerColor = MaterialTheme.colorScheme.surface, - modifier = Modifier.fillMaxWidth().height(inputHeight), + modifier = Modifier.fillMaxWidth().height(inputHeight).padding(horizontal = 16.dp), singleLine = true, keyboardOptions = KeyboardOptions( diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/tunnels/splittunnel/components/SplitOptionsSelector.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/tunnels/splittunnel/components/SplitOptionsSelector.kt index c4f1c897..6a318097 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/tunnels/splittunnel/components/SplitOptionsSelector.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/tunnels/splittunnel/components/SplitOptionsSelector.kt @@ -3,6 +3,7 @@ package com.zaneschepke.wireguardautotunnel.ui.screens.tunnels.splittunnel.compo import androidx.compose.foundation.background import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.icons.Icons @@ -27,6 +28,7 @@ fun SplitOptionSelector(selectedOption: SplitOption, onOptionChange: (SplitOptio Modifier.background(color = MaterialTheme.colorScheme.surface) .fillMaxWidth() .height(inputHeight) + .padding(horizontal = 16.dp) ) { SplitOption.entries.forEachIndexed { index, entry -> val active = selectedOption == entry diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/tunnels/splittunnel/components/SplitTunnelContent.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/tunnels/splittunnel/components/SplitTunnelContent.kt index e0df5a2d..b267e853 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/tunnels/splittunnel/components/SplitTunnelContent.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/tunnels/splittunnel/components/SplitTunnelContent.kt @@ -2,8 +2,7 @@ package com.zaneschepke.wireguardautotunnel.ui.screens.tunnels.splittunnel.compo import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -21,7 +20,7 @@ fun SplitTunnelContent( Column( verticalArrangement = Arrangement.spacedBy(24.dp, Alignment.Top), horizontalAlignment = Alignment.CenterHorizontally, - modifier = Modifier.fillMaxWidth().padding(horizontal = 16.dp), + modifier = Modifier.fillMaxSize(), ) { SplitOptionSelector( selectedOption = splitConfig.first, 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 c8e8c9a7..a6326b15 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,27 +1,49 @@ 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.* 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 -import com.zaneschepke.wireguardautotunnel.data.model.AppMode import com.zaneschepke.wireguardautotunnel.ui.LocalNavController import com.zaneschepke.wireguardautotunnel.ui.LocalSharedVm -import com.zaneschepke.wireguardautotunnel.ui.common.SectionDivider -import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SurfaceSelectionGroupButton +import com.zaneschepke.wireguardautotunnel.ui.common.button.ScaledSwitch +import com.zaneschepke.wireguardautotunnel.ui.common.button.SurfaceRow +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 @@ -48,42 +70,141 @@ fun TunnelOptionsScreen(viewModel: TunnelViewModel) { Column( horizontalAlignment = Alignment.Start, verticalArrangement = Arrangement.spacedBy(12.dp, Alignment.Top), - modifier = - Modifier.fillMaxSize().verticalScroll(rememberScrollState()).padding(horizontal = 16.dp), + modifier = Modifier.fillMaxSize().verticalScroll(rememberScrollState()), ) { - SurfaceSelectionGroupButton( - items = - buildList { - add(primaryTunnelItem(tunnel) { viewModel.togglePrimaryTunnel() }) - add(autoTunnelingItem(tunnel)) - add( - splitTunnelingItem(stringResource(R.string.splt_tunneling)) { - navController.push(Route.SplitTunnel(id = tunnel.id)) - } + Column { + SurfaceRow( + leading = { Icon(Icons.Outlined.Star, contentDescription = null) }, + title = stringResource(R.string.primary_tunnel), + description = { + Text( + text = stringResource(R.string.set_primary_tunnel), + style = + MaterialTheme.typography.bodySmall.copy( + MaterialTheme.colorScheme.outline + ), ) - } - ) - SectionDivider() - SurfaceSelectionGroupButton( - items = - buildList { - add( - dynamicDnsItem(tunnel.restartOnPingFailure) { - viewModel.setRestartOnPing(it) - } + }, + trailing = { + ScaledSwitch( + checked = tunnel.isPrimaryTunnel, + onClick = { viewModel.togglePrimaryTunnel() }, ) - if (tunnelState.appMode != AppMode.KERNEL) - add( - preferIpv6Item(!tunnel.isIpv4Preferred) { - viewModel.toggleIpv4Preferred() + }, + onClick = { viewModel.togglePrimaryTunnel() }, + ) + SurfaceRow( + leading = { + Icon(Icons.AutoMirrored.Outlined.CallSplit, contentDescription = null) + }, + title = stringResource(R.string.splt_tunneling), + onClick = { navController.push(Route.SplitTunnel(id = tunnel.id)) }, + ) + } + Column { + SurfaceRow( + leading = { Icon(Icons.Outlined.Dns, contentDescription = null) }, + title = stringResource(R.string.ddns_auto_update), + description = { + DescriptionText(stringResource(R.string.ddns_auto_update_description)) + }, + trailing = { + ScaledSwitch( + checked = tunnel.restartOnPingFailure, + onClick = { viewModel.setRestartOnPing(it) }, + ) + }, + onClick = { viewModel.setRestartOnPing(!tunnel.restartOnPingFailure) }, + ) + SurfaceRow( + leading = { + Icon(ImageVector.vectorResource(R.drawable.host), contentDescription = null) + }, + title = stringResource(R.string.prefer_ipv6_resolution), + trailing = { + ScaledSwitch( + checked = !tunnel.isIpv4Preferred, + onClick = { viewModel.toggleIpv4Preferred() }, + ) + }, + 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, + ) + } + } + }, ) - } - ) - if (tunnelState.isPingEnabled) { - SectionDivider() - SurfaceSelectionGroupButton( - items = listOf(pingConfigItem(tunnel) { ip -> viewModel.setTunnelPingIp(ip) }) + } + }, ) } } diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/tunnels/tunneloptions/components/autoTunnelingItem.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/tunnels/tunneloptions/components/autoTunnelingItem.kt deleted file mode 100644 index 96351f4c..00000000 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/tunnels/tunneloptions/components/autoTunnelingItem.kt +++ /dev/null @@ -1,40 +0,0 @@ -package com.zaneschepke.wireguardautotunnel.ui.screens.tunnels.tunneloptions.components - -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.outlined.Bolt -import androidx.compose.material3.Icon -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.ui.res.stringResource -import com.zaneschepke.wireguardautotunnel.R -import com.zaneschepke.wireguardautotunnel.domain.model.TunnelConf -import com.zaneschepke.wireguardautotunnel.ui.LocalNavController -import com.zaneschepke.wireguardautotunnel.ui.common.button.ForwardButton -import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SelectionItem -import com.zaneschepke.wireguardautotunnel.ui.navigation.Route - -@Composable -fun autoTunnelingItem(tunnelConf: TunnelConf): SelectionItem { - val navController = LocalNavController.current - return SelectionItem( - leading = { Icon(Icons.Outlined.Bolt, contentDescription = null) }, - title = { - Text( - text = stringResource(R.string.auto_tunneling), - style = - MaterialTheme.typography.bodyMedium.copy(MaterialTheme.colorScheme.onSurface), - ) - }, - description = { - Text( - text = stringResource(R.string.tunnel_specific_settings), - style = MaterialTheme.typography.bodySmall.copy(MaterialTheme.colorScheme.outline), - ) - }, - trailing = { - ForwardButton { navController.push(Route.TunnelAutoTunnel(id = tunnelConf.id)) } - }, - onClick = { navController.push(Route.TunnelAutoTunnel(id = tunnelConf.id)) }, - ) -} diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/tunnels/tunneloptions/components/dynamicDnsItem.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/tunnels/tunneloptions/components/dynamicDnsItem.kt deleted file mode 100644 index a66b5bd8..00000000 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/tunnels/tunneloptions/components/dynamicDnsItem.kt +++ /dev/null @@ -1,33 +0,0 @@ -package com.zaneschepke.wireguardautotunnel.ui.screens.tunnels.tunneloptions.components - -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.outlined.Dns -import androidx.compose.material3.Icon -import androidx.compose.runtime.Composable -import androidx.compose.ui.res.stringResource -import com.zaneschepke.wireguardautotunnel.R -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.SelectionItemLabel -import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SelectionLabelType - -@Composable -fun dynamicDnsItem(enabled: Boolean, onClick: (checked: Boolean) -> Unit): SelectionItem { - return SelectionItem( - leading = { Icon(Icons.Outlined.Dns, contentDescription = null) }, - title = { - SelectionItemLabel( - stringResource(R.string.ddns_auto_update), - labelType = SelectionLabelType.TITLE, - ) - }, - description = { - SelectionItemLabel( - stringResource(R.string.ddns_auto_update_description), - labelType = SelectionLabelType.DESCRIPTION, - ) - }, - trailing = { ScaledSwitch(checked = enabled, onClick = onClick) }, - onClick = { onClick(!enabled) }, - ) -} diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/tunnels/tunneloptions/components/pingConfigItem.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/tunnels/tunneloptions/components/pingConfigItem.kt deleted file mode 100644 index 408d0fb6..00000000 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/tunnels/tunneloptions/components/pingConfigItem.kt +++ /dev/null @@ -1,112 +0,0 @@ -package com.zaneschepke.wireguardautotunnel.ui.screens.tunnels.tunneloptions.components - -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.fillMaxWidth -import androidx.compose.foundation.layout.padding -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.derivedStateOf -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.platform.LocalFocusManager -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 com.zaneschepke.wireguardautotunnel.R -import com.zaneschepke.wireguardautotunnel.domain.model.TunnelConf -import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SelectionItem -import com.zaneschepke.wireguardautotunnel.ui.common.textbox.CustomTextField -import com.zaneschepke.wireguardautotunnel.util.extensions.isValidIpv4orIpv6Address - -@Composable -fun pingConfigItem(tunnelConf: TunnelConf, onSubmit: (ip: String) -> Unit): SelectionItem { - return SelectionItem( - title = {}, - description = { - val focusManager = LocalFocusManager.current - val keyboardController = LocalSoftwareKeyboardController.current - - var stateValue by remember { mutableStateOf(tunnelConf.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 = { onSubmit(stateValue) }), - trailing = { - if (!isError) { - IconButton( - onClick = { - onSubmit(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/screens/tunnels/tunneloptions/components/preferIpv6Item.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/tunnels/tunneloptions/components/preferIpv6Item.kt deleted file mode 100644 index 532ed133..00000000 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/tunnels/tunneloptions/components/preferIpv6Item.kt +++ /dev/null @@ -1,28 +0,0 @@ -package com.zaneschepke.wireguardautotunnel.ui.screens.tunnels.tunneloptions.components - -import androidx.compose.material3.Icon -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.ui.graphics.vector.ImageVector -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.res.vectorResource -import com.zaneschepke.wireguardautotunnel.R -import com.zaneschepke.wireguardautotunnel.ui.common.button.ScaledSwitch -import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SelectionItem - -@Composable -fun preferIpv6Item(checked: Boolean, onClick: () -> Unit): SelectionItem { - return SelectionItem( - leading = { Icon(ImageVector.vectorResource(R.drawable.host), contentDescription = null) }, - title = { - Text( - text = stringResource(R.string.prefer_ipv6_resolution), - style = - MaterialTheme.typography.bodyMedium.copy(MaterialTheme.colorScheme.onSurface), - ) - }, - trailing = { ScaledSwitch(checked = checked, onClick = { onClick() }) }, - onClick = onClick, - ) -} diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/tunnels/tunneloptions/components/primaryTunnelItem.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/tunnels/tunneloptions/components/primaryTunnelItem.kt deleted file mode 100644 index efa46e42..00000000 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/tunnels/tunneloptions/components/primaryTunnelItem.kt +++ /dev/null @@ -1,35 +0,0 @@ -package com.zaneschepke.wireguardautotunnel.ui.screens.tunnels.tunneloptions.components - -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.outlined.Star -import androidx.compose.material3.Icon -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.ui.res.stringResource -import com.zaneschepke.wireguardautotunnel.R -import com.zaneschepke.wireguardautotunnel.domain.model.TunnelConf -import com.zaneschepke.wireguardautotunnel.ui.common.button.ScaledSwitch -import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SelectionItem - -@Composable -fun primaryTunnelItem(tunnelConf: TunnelConf, onClick: () -> Unit): SelectionItem { - return SelectionItem( - leading = { Icon(Icons.Outlined.Star, contentDescription = null) }, - title = { - Text( - text = stringResource(R.string.primary_tunnel), - style = - MaterialTheme.typography.bodyMedium.copy(MaterialTheme.colorScheme.onSurface), - ) - }, - description = { - Text( - text = stringResource(R.string.set_primary_tunnel), - style = MaterialTheme.typography.bodySmall.copy(MaterialTheme.colorScheme.outline), - ) - }, - trailing = { ScaledSwitch(checked = tunnelConf.isPrimaryTunnel, onClick = { onClick() }) }, - onClick = onClick, - ) -} diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/tunnels/tunneloptions/components/splitTunnelingItem.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/tunnels/tunneloptions/components/splitTunnelingItem.kt deleted file mode 100644 index c6b55aa2..00000000 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/tunnels/tunneloptions/components/splitTunnelingItem.kt +++ /dev/null @@ -1,21 +0,0 @@ -package com.zaneschepke.wireguardautotunnel.ui.screens.tunnels.tunneloptions.components - -import android.R.attr.onClick -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.automirrored.outlined.CallSplit -import androidx.compose.material3.Icon -import androidx.compose.runtime.Composable -import com.zaneschepke.wireguardautotunnel.ui.common.button.ForwardButton -import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SelectionItem -import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SelectionItemLabel -import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SelectionLabelType - -@Composable -fun splitTunnelingItem(label: String, onClick: () -> Unit): SelectionItem { - return SelectionItem( - leading = { Icon(Icons.AutoMirrored.Outlined.CallSplit, contentDescription = null) }, - title = { SelectionItemLabel(label, SelectionLabelType.TITLE) }, - trailing = { ForwardButton { onClick() } }, - onClick = onClick, - ) -} diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/state/AutoTunnelUiState.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/state/AutoTunnelUiState.kt index 9d4b2e3b..9a33ca1a 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/state/AutoTunnelUiState.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/state/AutoTunnelUiState.kt @@ -2,6 +2,7 @@ package com.zaneschepke.wireguardautotunnel.ui.state import com.zaneschepke.networkmonitor.ConnectivityState import com.zaneschepke.wireguardautotunnel.domain.model.GeneralSettings +import com.zaneschepke.wireguardautotunnel.domain.model.TunnelConf data class AutoTunnelUiState( val autoTunnelActive: Boolean = false, @@ -9,5 +10,6 @@ data class AutoTunnelUiState( val settings: GeneralSettings = GeneralSettings(), val isBatteryOptimizationShown: Boolean = false, val isLocationDisclosureShown: Boolean = false, - val stateInitialized: Boolean = false, + val tunnels: List = emptyList(), + val isLoading: Boolean = true, ) diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/state/LicenseUiState.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/state/LicenseUiState.kt new file mode 100644 index 00000000..fa07bfee --- /dev/null +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/state/LicenseUiState.kt @@ -0,0 +1,8 @@ +package com.zaneschepke.wireguardautotunnel.ui.state + +import LicenseFileEntry + +data class LicenseUiState( + val isLoading: Boolean = true, + val licenses: List = emptyList(), +) 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 37a4af8b..77043722 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 @@ -10,6 +10,6 @@ data class SettingUiState( val isRemoteEnabled: Boolean = false, val isPinLockEnabled: Boolean = false, val showDetailedPingStats: Boolean = false, - val stateInitialized: Boolean = false, + val isLoading: Boolean = true, val globalTunnelConf: TunnelConf? = null, ) diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/theme/Size.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/theme/Size.kt index e3d8cc67..d57c03db 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/theme/Size.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/theme/Size.kt @@ -2,6 +2,4 @@ package com.zaneschepke.wireguardautotunnel.ui.theme import androidx.compose.ui.unit.dp -val iconSize = 24.dp - val LockedDownBannerHeight = 20.dp 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 7676bf22..9a43f3bf 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 @@ -5,6 +5,7 @@ import android.os.Build import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.material.ripple.RippleAlpha import androidx.compose.material3.* +import androidx.compose.material3.MaterialTheme.colorScheme import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.SideEffect @@ -106,20 +107,25 @@ fun WireguardAutoTunnelTheme(theme: Theme = Theme.AUTOMATIC, content: @Composabl } } - // Make hover/ripple more obvious on TV val rippleConfig = - if (isTv) { - RippleConfiguration( - color = colorScheme.outline.copy(alpha = 0.12f), - rippleAlpha = + RippleConfiguration( + color = colorScheme.primary, + rippleAlpha = + if (isTv) RippleAlpha( - pressedAlpha = 0.7f, - focusedAlpha = 0.6f, - draggedAlpha = 0.9f, - hoveredAlpha = 0.3f, + pressedAlpha = 0.5f, + focusedAlpha = 0.4f, + draggedAlpha = 0.2f, + hoveredAlpha = 0.4f, + ) + else + RippleAlpha( + pressedAlpha = 0.2f, + focusedAlpha = 0.1f, + draggedAlpha = 0.2f, + hoveredAlpha = 0.1f, ), - ) - } else null + ) CompositionLocalProvider(LocalRippleConfiguration provides rippleConfig) { MaterialTheme(colorScheme = colorScheme, typography = Typography, content = content) diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/util/FileUtils.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/util/FileUtils.kt index 748fa93c..7a34aec0 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/util/FileUtils.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/util/FileUtils.kt @@ -1,5 +1,6 @@ package com.zaneschepke.wireguardautotunnel.util +import LicenseFileEntry import android.content.ContentValues import android.content.Context import android.database.Cursor @@ -18,6 +19,7 @@ import java.util.zip.ZipInputStream import java.util.zip.ZipOutputStream import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.withContext +import kotlinx.serialization.json.Json import timber.log.Timber class FileUtils( @@ -290,6 +292,15 @@ class FileUtils( return@withContext itemUri } + suspend fun readLibraryLicensesFromAssets(): List = + withContext(ioDispatcher) { + val json = Json { ignoreUnknownKeys = true } + + val jsonResult = + context.assets.open("licenses.json").bufferedReader().use { it.readText() } + json.decodeFromString(jsonResult) + } + companion object { const val CONF_FILE_EXTENSION = ".conf" const val ZIP_FILE_EXTENSION = ".zip" diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/util/extensions/ContextExtensions.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/util/extensions/ContextExtensions.kt index 55ad4630..d5b9a79d 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/util/extensions/ContextExtensions.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/util/extensions/ContextExtensions.kt @@ -242,6 +242,48 @@ fun Context.installApk(apkFile: File) { startActivity(intent) } +fun Context.launchPlayStoreListing() { + val intent = + Intent(Intent.ACTION_VIEW, Uri.parse("market://details?id=$packageName")).apply { + addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + setPackage("com.android.vending") + } + + if (intent.resolveActivity(packageManager) != null) { + startActivity(intent) + } else { + openWebUrl("https://play.google.com/store/apps/details?id=$packageName") + } +} + +fun Context.launchPlayStoreReview() { + val uri = Uri.parse("market://details?id=$packageName&showAllReviews=true") + val intent = + Intent(Intent.ACTION_VIEW, uri).apply { + addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + setPackage("com.android.vending") + } + if (intent.resolveActivity(packageManager) != null) { + startActivity(intent) + } else { + openWebUrl("https://play.google.com/store/apps/details?id=$packageName&showAllReviews=true") + } +} + +fun Context.launchFDroidListing() { + val intent = + Intent(Intent.ACTION_VIEW, Uri.parse("fdroid.app://details?id=$packageName")).apply { + addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + setPackage("org.fdroid.fdroid") + } + + if (intent.resolveActivity(packageManager) != null) { + startActivity(intent) + } else { + openWebUrl("https://f-droid.org/packages/$packageName/") + } +} + fun Activity.setScreenBrightness(brightness: Float) { window.attributes = window.attributes.apply { screenBrightness = brightness } } diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/util/extensions/StringExtensions.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/util/extensions/StringExtensions.kt index 82efcfe8..02863f7d 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/util/extensions/StringExtensions.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/util/extensions/StringExtensions.kt @@ -1,5 +1,6 @@ package com.zaneschepke.wireguardautotunnel.util.extensions +import java.util.Locale import timber.log.Timber val hasNumberInParentheses = """^(.+?)\((\d+)\)$""".toRegex() @@ -35,6 +36,10 @@ fun String.abbreviateKey(prefixLength: Int = 6): String { } } +fun String.capitalize(locale: Locale): String { + return replaceFirstChar { if (it.isLowerCase()) it.titlecase(locale) else it.toString() } +} + // only allow valid Android ports fun String.isValidAndroidProxyBindAddress(): Boolean { // Regex: IPv4 address with mandatory port (1–65535) diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/viewmodel/AutoTunnelViewModel.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/viewmodel/AutoTunnelViewModel.kt index b4373ab2..3675198e 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/viewmodel/AutoTunnelViewModel.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/viewmodel/AutoTunnelViewModel.kt @@ -7,9 +7,11 @@ import com.zaneschepke.wireguardautotunnel.R import com.zaneschepke.wireguardautotunnel.core.service.ServiceManager import com.zaneschepke.wireguardautotunnel.data.model.AppMode import com.zaneschepke.wireguardautotunnel.data.model.WifiDetectionMethod +import com.zaneschepke.wireguardautotunnel.domain.model.TunnelConf import com.zaneschepke.wireguardautotunnel.domain.repository.AppStateRepository import com.zaneschepke.wireguardautotunnel.domain.repository.GeneralSettingRepository import com.zaneschepke.wireguardautotunnel.domain.repository.GlobalEffectRepository +import com.zaneschepke.wireguardautotunnel.domain.repository.TunnelRepository import com.zaneschepke.wireguardautotunnel.domain.sideeffect.GlobalSideEffect import com.zaneschepke.wireguardautotunnel.ui.state.AutoTunnelUiState import com.zaneschepke.wireguardautotunnel.util.RootShellUtils @@ -30,6 +32,7 @@ constructor( private val serviceManager: ServiceManager, private val networkMonitor: NetworkMonitor, private val globalEffectRepository: GlobalEffectRepository, + private val tunnelsRepository: TunnelRepository, private val appStateRepository: AppStateRepository, private val rootShellUtils: RootShellUtils, ) : ContainerHost, ViewModel() { @@ -45,14 +48,16 @@ constructor( serviceManager.autoTunnelService.map { it != null }, settingsRepository.flow, appStateRepository.flow, - ) { connectivity, active, settings, appState -> - AutoTunnelUiState( + tunnelsRepository.userTunnelsFlow, + ) { connectivity, active, settings, appState, tunnels -> + state.copy( autoTunnelActive = active, connectivityState = connectivity, settings = settings, + tunnels = tunnels, isBatteryOptimizationShown = appState.isBatteryOptimizationDisableShown, isLocationDisclosureShown = appState.isLocationDisclosureShown, - stateInitialized = true, + isLoading = false, ) } .collect { reduce { it } } @@ -129,6 +134,28 @@ constructor( settingsRepository.save(state.settings.copy(debounceDelaySeconds = to)) } + fun setPreferredMobileDataTunnel(tunnel: TunnelConf?) = intent { + tunnelsRepository.updateMobileDataTunnel(tunnel) + } + + fun setPreferredEthernetTunnel(tunnel: TunnelConf?) = intent { + tunnelsRepository.updateEthernetTunnel(tunnel) + } + + fun addTunnelNetwork(tunnel: TunnelConf, ssid: String) = intent { + tunnelsRepository.save( + tunnel.copy(tunnelNetworks = tunnel.tunnelNetworks.toMutableSet().apply { add(ssid) }) + ) + } + + fun removeTunnelNetwork(tunnel: TunnelConf, ssid: String) = intent { + tunnelsRepository.save( + tunnel.copy( + tunnelNetworks = tunnel.tunnelNetworks.toMutableSet().apply { remove(ssid) } + ) + ) + } + fun setWifiDetectionMethod(method: WifiDetectionMethod) = intent { when (method) { WifiDetectionMethod.ROOT -> { diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/viewmodel/LicenseViewModel.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/viewmodel/LicenseViewModel.kt new file mode 100644 index 00000000..90da27d2 --- /dev/null +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/viewmodel/LicenseViewModel.kt @@ -0,0 +1,26 @@ +package com.zaneschepke.wireguardautotunnel.viewmodel + +import androidx.lifecycle.ViewModel +import com.zaneschepke.wireguardautotunnel.ui.state.LicenseUiState +import com.zaneschepke.wireguardautotunnel.util.FileUtils +import dagger.hilt.android.lifecycle.HiltViewModel +import javax.inject.Inject +import kotlinx.coroutines.ExperimentalCoroutinesApi +import org.orbitmvi.orbit.ContainerHost +import org.orbitmvi.orbit.viewmodel.container + +@HiltViewModel +class LicenseViewModel @Inject constructor(private val fileUtils: FileUtils) : + ContainerHost, ViewModel() { + @OptIn(ExperimentalCoroutinesApi::class) + override val container = + container( + LicenseUiState(), + buildSettings = { repeatOnSubscribedStopTimeout = 5000L }, + ) { + intent { + val licenses = fileUtils.readLibraryLicensesFromAssets() + reduce { state.copy(isLoading = false, licenses = licenses) } + } + } +} 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 ac129e6b..b7212a10 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/viewmodel/SettingsViewModel.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/viewmodel/SettingsViewModel.kt @@ -47,7 +47,7 @@ constructor( isRemoteEnabled = appState.isRemoteControlEnabled, isPinLockEnabled = appState.isPinLockEnabled, showDetailedPingStats = appState.showDetailedPingStats, - stateInitialized = true, + isLoading = false, globalTunnelConf = tunnel, ) } diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/viewmodel/SplitTunnelViewModel.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/viewmodel/SplitTunnelViewModel.kt index 454ac3fb..d1d4c7fa 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/viewmodel/SplitTunnelViewModel.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/viewmodel/SplitTunnelViewModel.kt @@ -43,11 +43,9 @@ constructor( combine( packagesFlow, - tunnelRepository.userTunnelsFlow.map { - it.firstOrNull { tun -> tun.id == tunnelId } - }, + tunnelRepository.flow.map { it.firstOrNull { tun -> tun.id == tunnelId } }, ) { packages, tunnel -> - SplitTunnelUiState(packages, tunnel == null, tunnel) + SplitTunnelUiState(packages, false, tunnel) } .collect { reduce { it } } } diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/viewmodel/SupportViewModel.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/viewmodel/SupportViewModel.kt index 5d0adbb7..e3550b7c 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/viewmodel/SupportViewModel.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/viewmodel/SupportViewModel.kt @@ -30,13 +30,7 @@ constructor( override val container = container(SupportUiState()) - fun checkForUpdate() = intent { - if (BuildConfig.DEBUG || BuildConfig.FLAVOR == Constants.GOOGLE_PLAY_FLAVOR) - return@intent postSideEffect( - GlobalSideEffect.Toast( - StringValue.StringResource(R.string.update_check_unsupported) - ) - ) + fun checkForStandaloneUpdate() = intent { postSideEffect( GlobalSideEffect.Toast(StringValue.StringResource(R.string.checking_for_update)) ) diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/viewmodel/TunnelViewModel.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/viewmodel/TunnelViewModel.kt index db248415..bc826925 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/viewmodel/TunnelViewModel.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/viewmodel/TunnelViewModel.kt @@ -56,22 +56,6 @@ constructor( tunnelRepository.save(tunnel.copy(pingTarget = ip.ifBlank { null })) } - fun addTunnelNetwork(ssid: String) = intent { - val tunnel = state.tunnel ?: return@intent - tunnelRepository.save( - tunnel.copy(tunnelNetworks = tunnel.tunnelNetworks.toMutableSet().apply { add(ssid) }) - ) - } - - fun removeTunnelNetwork(ssid: String) = intent { - val tunnel = state.tunnel ?: return@intent - tunnelRepository.save( - tunnel.copy( - tunnelNetworks = tunnel.tunnelNetworks.toMutableSet().apply { remove(ssid) } - ) - ) - } - fun setRestartOnPing(to: Boolean) = intent { val tunnel = state.tunnel ?: return@intent tunnelRepository.save(tunnel.copy(restartOnPingFailure = to)) diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 1cba4f02..cb9f0315 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -9,6 +9,7 @@ https://ko-fi.com/zaneschepke https://liberapay.com/zaneschepke https://wgtunnel.com/docs/getting-started + https://wgtunnel.com https://wgtunnel.com/privacy-policy/ https://wgtunnel.com/docs/auto-tunneling/#use-name-wildcards https://matrix.to/#/#wg-tunnel-space:matrix.org @@ -17,7 +18,7 @@ No tunnels added yet! Tunnels Tunnel on mobile data - View privacy policy + Privacy policy Okay Tunnel on ethernet This feature requires background location permission to enable Wi-Fi SSID monitoring even while the application is closed. For more details, please see the Privacy Policy linked on the Support screen. @@ -60,7 +61,7 @@ Cancel Enable app shortcuts Unknown error occurred - Tunnel on untrusted wifi + Tunnel on Wi-Fi support@zaneschepke.com WG Tunnel Support Send an email… @@ -87,7 +88,7 @@ Enable app lock Restart on ping fail (beta) Set as mobile data tunnel - Set as primary tunnel + Tunnel to be used when no preferred tunnel is configured Use tunnel on wifi name Edit tunnel Settings @@ -123,9 +124,9 @@ Dynamic Language Display theme - Trusted wifi names - Add wifi name - Primary tunnel + Trusted Wi-Fi names + Add Wi-Fi name + Default tunnel Mobile data tunnel Skip Launch app settings @@ -390,7 +391,23 @@ the Play Store version of this app. Please browse the project\'s webpages to find where to donate. Cannot delete active tunnel. - Global tunnel overrides + Global overrides Back Configuration + About + Resources + App website + Contact + Rate app + Other + Options + General + Networks + Preferred tunnel:  + Preferred tunnel + Mapped + Tunnel mapping + Wi-Fi settings + Ping + Ping monitor