diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/MainActivity.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/MainActivity.kt index 6c6a16e..d337e1c 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/MainActivity.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/MainActivity.kt @@ -11,6 +11,10 @@ import androidx.compose.animation.fadeOut import androidx.compose.foundation.focusable import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.padding +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.rounded.Home +import androidx.compose.material.icons.rounded.QuestionMark +import androidx.compose.material.icons.rounded.Settings import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Scaffold import androidx.compose.material3.SnackbarData @@ -25,24 +29,24 @@ import androidx.compose.ui.focus.FocusRequester import androidx.compose.ui.focus.focusProperties import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.toArgb +import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle -import androidx.navigation.NavType import androidx.navigation.compose.NavHost import androidx.navigation.compose.composable import androidx.navigation.compose.currentBackStackEntryAsState -import androidx.navigation.navArgument +import androidx.navigation.toRoute +import com.zaneschepke.wireguardautotunnel.R import com.zaneschepke.wireguardautotunnel.data.repository.AppStateRepository import com.zaneschepke.wireguardautotunnel.service.foreground.ServiceManager import com.zaneschepke.wireguardautotunnel.service.tunnel.TunnelService import com.zaneschepke.wireguardautotunnel.service.tunnel.TunnelState import com.zaneschepke.wireguardautotunnel.ui.common.navigation.BottomNavBar +import com.zaneschepke.wireguardautotunnel.ui.common.navigation.BottomNavItem import com.zaneschepke.wireguardautotunnel.ui.common.prompt.CustomSnackBar import com.zaneschepke.wireguardautotunnel.ui.common.snackbar.SnackbarControllerProvider import com.zaneschepke.wireguardautotunnel.ui.screens.config.ConfigScreen -import com.zaneschepke.wireguardautotunnel.ui.screens.config.ConfigViewModel -import com.zaneschepke.wireguardautotunnel.ui.screens.main.ConfigType import com.zaneschepke.wireguardautotunnel.ui.screens.main.MainScreen import com.zaneschepke.wireguardautotunnel.ui.screens.options.OptionsScreen import com.zaneschepke.wireguardautotunnel.ui.screens.pinlock.PinLockScreen @@ -111,8 +115,8 @@ class MainActivity : AppCompatActivity() { Modifier .focusable() .focusProperties { - when (navBackStackEntry?.destination?.route) { - Screen.Lock.route -> Unit + when (navBackStackEntry?.toRoute()) { + is Screens.Lock -> Unit else -> up = focusRequester } }, @@ -120,9 +124,21 @@ class MainActivity : AppCompatActivity() { BottomNavBar( navController, listOf( - Screen.Main.navItem, - Screen.Settings.navItem, - Screen.Support.navItem, + BottomNavItem( + name = stringResource(R.string.tunnels), + route = Screens.Main, + icon = Icons.Rounded.Home, + ), + BottomNavItem( + name = stringResource(R.string.settings), + route = Screens.Settings, + icon = Icons.Rounded.Settings, + ), + BottomNavItem( + name = stringResource(R.string.support), + route = Screens.Support, + icon = Icons.Rounded.QuestionMark, + ), ), ) }, @@ -132,20 +148,16 @@ class MainActivity : AppCompatActivity() { navController, enterTransition = { fadeIn(tween(Constants.TRANSITION_ANIMATION_TIME)) }, exitTransition = { fadeOut(tween(Constants.TRANSITION_ANIMATION_TIME)) }, - startDestination = (if (isPinLockEnabled == true) Screen.Lock.route else Screen.Main.route), + startDestination = (if (isPinLockEnabled == true) Screens.Lock else Screens.Main), ) { - composable( - Screen.Main.route, - ) { + composable { MainScreen( focusRequester = focusRequester, uiState = appUiState, navController = navController, ) } - composable( - Screen.Settings.route, - ) { + composable { SettingsScreen( appViewModel = appViewModel, uiState = appUiState, @@ -153,56 +165,33 @@ class MainActivity : AppCompatActivity() { focusRequester = focusRequester, ) } - composable( - Screen.Support.route, - ) { + composable { SupportScreen( focusRequester = focusRequester, navController = navController, appUiState = appUiState, ) } - composable(Screen.Support.Logs.route) { + composable { LogsScreen() } - composable( - "${Screen.Config.route}/{id}?configType={configType}", - arguments = - listOf( - navArgument("id") { - type = NavType.StringType - defaultValue = "0" - }, - navArgument("configType") { - type = NavType.StringType - defaultValue = ConfigType.WIREGUARD.name - }, - ), - ) { - val id = it.arguments?.getString("id") - if (!id.isNullOrBlank()) { - val viewModel = hiltViewModel { factory -> - factory.create(id.toInt()) - } - ConfigScreen( - viewModel = viewModel, - focusRequester = focusRequester, - tunnelId = id.toInt(), - ) - } + composable { + val args = it.toRoute() + ConfigScreen( + focusRequester = focusRequester, + tunnelId = args.id, + ) } - composable("${Screen.Option.route}/{id}") { - val id = it.arguments?.getString("id") - if (!id.isNullOrBlank()) { - OptionsScreen( - navController = navController, - tunnelId = id.toInt(), - focusRequester = focusRequester, - appUiState = appUiState, - ) - } + composable { + val args = it.toRoute() + OptionsScreen( + navController = navController, + tunnelId = args.id, + focusRequester = focusRequester, + appUiState = appUiState, + ) } - composable(Screen.Lock.route) { + composable { PinLockScreen( navController = navController, appViewModel = appViewModel, diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/Screen.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/Screen.kt deleted file mode 100644 index 465d179..0000000 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/Screen.kt +++ /dev/null @@ -1,46 +0,0 @@ -package com.zaneschepke.wireguardautotunnel.ui - -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.rounded.Home -import androidx.compose.material.icons.rounded.QuestionMark -import androidx.compose.material.icons.rounded.Settings -import com.zaneschepke.wireguardautotunnel.R -import com.zaneschepke.wireguardautotunnel.WireGuardAutoTunnel -import com.zaneschepke.wireguardautotunnel.ui.common.navigation.BottomNavItem - -sealed class Screen(val route: String) { - data object Main : Screen("main") { - val navItem = - BottomNavItem( - name = WireGuardAutoTunnel.instance.getString(R.string.tunnels), - route = route, - icon = Icons.Rounded.Home, - ) - } - - data object Settings : Screen("settings") { - val navItem = - BottomNavItem( - name = WireGuardAutoTunnel.instance.getString(R.string.settings), - route = route, - icon = Icons.Rounded.Settings, - ) - } - - data object Support : Screen("support") { - val navItem = - BottomNavItem( - name = WireGuardAutoTunnel.instance.getString(R.string.support), - route = route, - icon = Icons.Rounded.QuestionMark, - ) - - data object Logs : Screen("support/logs") - } - - data object Config : Screen("config") - - data object Lock : Screen("lock") - - data object Option : Screen("option") -} diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/Screens.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/Screens.kt new file mode 100644 index 0000000..be56164 --- /dev/null +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/Screens.kt @@ -0,0 +1,30 @@ +package com.zaneschepke.wireguardautotunnel.ui + +import kotlinx.serialization.Serializable + +sealed class Screens { + @Serializable + data object Support : Screens() + + @Serializable + data object Settings : Screens() + + @Serializable + data object Main : Screens() + + @Serializable + data class Option( + val id: Int, + ) : Screens() + + @Serializable + data object Lock : Screens() + + @Serializable + data class Config( + val id: Int, + ) : Screens() + + @Serializable + data object Logs : Screens() +} diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/common/navigation/BottomNavBar.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/common/navigation/BottomNavBar.kt index 1fe5b0e..6ca5523 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/common/navigation/BottomNavBar.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/common/navigation/BottomNavBar.kt @@ -12,6 +12,8 @@ import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.text.font.FontWeight import androidx.navigation.NavController +import androidx.navigation.NavDestination.Companion.hasRoute +import androidx.navigation.NavDestination.Companion.hierarchy import androidx.navigation.NavGraph.Companion.findStartDestination import androidx.navigation.compose.currentBackStackEntryAsState @@ -20,19 +22,24 @@ fun BottomNavBar(navController: NavController, bottomNavItems: List + bottomNavItems.map { dest.hasRoute(route = it.route::class) }.contains(true) + } == true + } != null if (showBottomBar) { NavigationBar( containerColor = MaterialTheme.colorScheme.surface, ) { bottomNavItems.forEach { item -> - val selected = navBackStackEntry?.destination?.route?.contains(item.route) == true - + val selected = navBackStackEntry?.destination?.hierarchy?.any { + it.hasRoute(route = item.route::class) + } == true NavigationBarItem( selected = selected, onClick = { - if (navBackStackEntry?.destination?.route == item.route) return@NavigationBarItem + if (selected) return@NavigationBarItem navController.navigate(item.route) { // Pop up to the start destination of the graph to // avoid building up a large stack of destinations diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/common/navigation/BottomNavItem.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/common/navigation/BottomNavItem.kt index 125fb8c..a586b77 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/common/navigation/BottomNavItem.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/common/navigation/BottomNavItem.kt @@ -1,9 +1,10 @@ package com.zaneschepke.wireguardautotunnel.ui.common.navigation import androidx.compose.ui.graphics.vector.ImageVector +import com.zaneschepke.wireguardautotunnel.ui.Screens data class BottomNavItem( val name: String, - val route: String, + val route: Screens, val icon: ImageVector, ) diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/config/ConfigScreen.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/config/ConfigScreen.kt index 66c04f2..e2399b5 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/config/ConfigScreen.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/config/ConfigScreen.kt @@ -53,6 +53,7 @@ import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.text.input.PasswordVisualTransformation import androidx.compose.ui.text.input.VisualTransformation import androidx.compose.ui.unit.dp +import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.zaneschepke.wireguardautotunnel.R import com.zaneschepke.wireguardautotunnel.ui.common.config.ConfigurationTextBox @@ -68,7 +69,11 @@ import kotlinx.coroutines.delay @SuppressLint("UnusedMaterial3ScaffoldPaddingParameter") @Composable -fun ConfigScreen(tunnelId: Int, viewModel: ConfigViewModel, focusRequester: FocusRequester) { +fun ConfigScreen(tunnelId: Int, focusRequester: FocusRequester) { + val viewModel = hiltViewModel { factory -> + factory.create(tunnelId) + } + val context = LocalContext.current val snackbar = SnackbarController.current val clipboardManager: ClipboardManager = LocalClipboardManager.current diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/config/ConfigViewModel.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/config/ConfigViewModel.kt index f37b6c6..4cbdee6 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/config/ConfigViewModel.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/config/ConfigViewModel.kt @@ -17,7 +17,7 @@ import com.zaneschepke.wireguardautotunnel.WireGuardAutoTunnel import com.zaneschepke.wireguardautotunnel.data.domain.TunnelConfig import com.zaneschepke.wireguardautotunnel.data.repository.AppDataRepository import com.zaneschepke.wireguardautotunnel.module.IoDispatcher -import com.zaneschepke.wireguardautotunnel.ui.Screen +import com.zaneschepke.wireguardautotunnel.ui.Screens import com.zaneschepke.wireguardautotunnel.ui.common.snackbar.SnackbarController import com.zaneschepke.wireguardautotunnel.ui.screens.config.model.PeerProxy import com.zaneschepke.wireguardautotunnel.util.Constants @@ -335,7 +335,7 @@ constructor( SnackbarController.showMessage( StringValue.StringResource(R.string.config_changes_saved), ) - navController.navigate(Screen.Main.route) + navController.navigate(Screens.Main) }.onFailure { Timber.e(it) val message = it.message?.substringAfter(":", missingDelimiterValue = "") diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/main/MainScreen.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/main/MainScreen.kt index 8c68bb2..e6b4d88 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/main/MainScreen.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/main/MainScreen.kt @@ -68,7 +68,7 @@ import com.zaneschepke.wireguardautotunnel.data.domain.TunnelConfig import com.zaneschepke.wireguardautotunnel.service.tunnel.HandshakeStatus import com.zaneschepke.wireguardautotunnel.service.tunnel.TunnelState import com.zaneschepke.wireguardautotunnel.ui.AppUiState -import com.zaneschepke.wireguardautotunnel.ui.Screen +import com.zaneschepke.wireguardautotunnel.ui.Screens import com.zaneschepke.wireguardautotunnel.ui.common.RowListItem import com.zaneschepke.wireguardautotunnel.ui.common.dialog.InfoDialog import com.zaneschepke.wireguardautotunnel.ui.common.functions.rememberFileImportLauncherForResult @@ -235,7 +235,7 @@ fun MainScreen(viewModel: MainViewModel = hiltViewModel(), uiState: AppUiState, onQrClick = { launchQrScanner() }, onManualImportClick = { navController.navigate( - "${Screen.Config.route}/${Constants.MANUAL_TUNNEL_CONFIG_ID}", + Screens.Config(Constants.MANUAL_TUNNEL_CONFIG_ID), ) }, ) @@ -407,9 +407,11 @@ fun MainScreen(viewModel: MainViewModel = hiltViewModel(), uiState: AppUiState, Row { IconButton( onClick = { - navController.navigate( - "${Screen.Option.route}/${selectedTunnel?.id}", - ) + selectedTunnel?.let { + navController.navigate( + Screens.Option(it.id), + ) + } }, ) { val icon = Icons.Rounded.Settings @@ -452,9 +454,11 @@ fun MainScreen(viewModel: MainViewModel = hiltViewModel(), uiState: AppUiState, IconButton( onClick = { selectedTunnel = tunnel - navController.navigate( - "${Screen.Option.route}/${selectedTunnel?.id}", - ) + selectedTunnel?.let { + navController.navigate( + Screens.Option(it.id), + ) + } }, ) { val icon = Icons.Rounded.Settings diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/options/OptionsScreen.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/options/OptionsScreen.kt index 3b7e678..fc2caa7 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/options/OptionsScreen.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/options/OptionsScreen.kt @@ -50,7 +50,7 @@ import androidx.hilt.navigation.compose.hiltViewModel import androidx.navigation.NavController import com.zaneschepke.wireguardautotunnel.R import com.zaneschepke.wireguardautotunnel.ui.AppUiState -import com.zaneschepke.wireguardautotunnel.ui.Screen +import com.zaneschepke.wireguardautotunnel.ui.Screens import com.zaneschepke.wireguardautotunnel.ui.common.ClickableIconButton import com.zaneschepke.wireguardautotunnel.ui.common.config.ConfigurationToggle import com.zaneschepke.wireguardautotunnel.ui.common.config.SubmitConfigurationTextBox @@ -114,7 +114,7 @@ fun OptionsScreen( ) }, focusRequester, isVisible = true, onClick = { navController.navigate( - "${Screen.Config.route}/${config.id}", + Screens.Config(config.id), ) }) }, diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/pinlock/PinLockScreen.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/pinlock/PinLockScreen.kt index 7750665..f03dc82 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/pinlock/PinLockScreen.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/pinlock/PinLockScreen.kt @@ -8,7 +8,7 @@ import androidx.compose.ui.res.stringResource import androidx.navigation.NavController import com.zaneschepke.wireguardautotunnel.R import com.zaneschepke.wireguardautotunnel.ui.AppViewModel -import com.zaneschepke.wireguardautotunnel.ui.Screen +import com.zaneschepke.wireguardautotunnel.ui.Screens import com.zaneschepke.wireguardautotunnel.ui.common.snackbar.SnackbarController import com.zaneschepke.wireguardautotunnel.util.StringValue import com.zaneschepke.wireguardautotunnel.util.extensions.isRunningOnTv @@ -36,11 +36,11 @@ fun PinLockScreen(navController: NavController, appViewModel: AppViewModel) { onPinCorrect = { // pin is correct, navigate or hide pin lock if (context.isRunningOnTv()) { - navController.navigate(Screen.Main.route) + navController.navigate(Screens.Main) } else { val isPopped = navController.popBackStack() if (!isPopped) { - navController.navigate(Screen.Main.route) + navController.navigate(Screens.Main) } } }, 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 2335d2f..9da776e 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 @@ -69,7 +69,7 @@ import com.zaneschepke.wireguardautotunnel.WireGuardAutoTunnel import com.zaneschepke.wireguardautotunnel.service.tunnel.TunnelState import com.zaneschepke.wireguardautotunnel.ui.AppUiState import com.zaneschepke.wireguardautotunnel.ui.AppViewModel -import com.zaneschepke.wireguardautotunnel.ui.Screen +import com.zaneschepke.wireguardautotunnel.ui.Screens import com.zaneschepke.wireguardautotunnel.ui.common.ClickableIconButton import com.zaneschepke.wireguardautotunnel.ui.common.config.ConfigurationToggle import com.zaneschepke.wireguardautotunnel.ui.common.prompt.AuthorizationPrompt @@ -581,7 +581,7 @@ fun SettingsScreen( } else { // TODO may want to show a dialog before proceeding in the future PinManager.initialize(WireGuardAutoTunnel.instance) - navController.navigate(Screen.Lock.route) + navController.navigate(Screens.Lock) } }, ) 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 d13b1da..7fabf37 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 @@ -46,7 +46,7 @@ import androidx.navigation.NavController import com.zaneschepke.wireguardautotunnel.BuildConfig import com.zaneschepke.wireguardautotunnel.R import com.zaneschepke.wireguardautotunnel.ui.AppUiState -import com.zaneschepke.wireguardautotunnel.ui.Screen +import com.zaneschepke.wireguardautotunnel.ui.Screens import com.zaneschepke.wireguardautotunnel.util.extensions.isRunningOnTv import com.zaneschepke.wireguardautotunnel.util.extensions.launchSupportEmail import com.zaneschepke.wireguardautotunnel.util.extensions.openWebUrl @@ -244,7 +244,7 @@ fun SupportScreen(navController: NavController, focusRequester: FocusRequester, color = MaterialTheme.colorScheme.onBackground, ) TextButton( - onClick = { navController.navigate(Screen.Support.Logs.route) }, + onClick = { navController.navigate(Screens.Logs) }, modifier = Modifier.padding(vertical = 5.dp), ) { Row( diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/util/Constants.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/util/Constants.kt index ae1c2a1..2d4732c 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/util/Constants.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/util/Constants.kt @@ -4,7 +4,7 @@ object Constants { const val BASE_LOG_FILE_NAME = "wg_tunnel_logs" const val LOG_BUFFER_SIZE = 3_000L - const val MANUAL_TUNNEL_CONFIG_ID = "0" + const val MANUAL_TUNNEL_CONFIG_ID = 0 const val BATTERY_SAVER_WATCHER_WAKE_LOCK_TIMEOUT = 10 * 60 * 1_000L // 10 minutes const val WATCHER_COLLECTION_DELAY = 3_000L