feat: improved navigation animations

This commit is contained in:
Zane Schepke
2025-10-02 22:03:43 -04:00
parent c86733c572
commit 9ea608beb5
22 changed files with 193 additions and 113 deletions
@@ -16,6 +16,7 @@ import androidx.activity.result.ActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
import androidx.compose.animation.*
import androidx.compose.foundation.background
import androidx.compose.foundation.gestures.detectTapGestures
import androidx.compose.foundation.layout.*
@@ -33,7 +34,10 @@ import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.viewmodel.navigation3.rememberViewModelStoreNavEntryDecorator
import androidx.navigation3.runtime.*
import androidx.navigation3.runtime.NavKey
import androidx.navigation3.runtime.entryProvider
import androidx.navigation3.runtime.rememberNavBackStack
import androidx.navigation3.runtime.rememberSavedStateNavEntryDecorator
import androidx.navigation3.scene.rememberSceneSetupNavEntryDecorator
import androidx.navigation3.ui.NavDisplay
import com.zaneschepke.networkmonitor.NetworkMonitor
@@ -43,8 +47,8 @@ import com.zaneschepke.wireguardautotunnel.data.model.AppMode
import com.zaneschepke.wireguardautotunnel.domain.model.TunnelConf
import com.zaneschepke.wireguardautotunnel.domain.repository.AppStateRepository
import com.zaneschepke.wireguardautotunnel.domain.sideeffect.GlobalSideEffect
import com.zaneschepke.wireguardautotunnel.ui.LocalBackStack
import com.zaneschepke.wireguardautotunnel.ui.LocalIsAndroidTV
import com.zaneschepke.wireguardautotunnel.ui.LocalNavController
import com.zaneschepke.wireguardautotunnel.ui.LocalSharedVm
import com.zaneschepke.wireguardautotunnel.ui.common.banner.AppAlertBanner
import com.zaneschepke.wireguardautotunnel.ui.common.dialog.VpnDeniedDialog
@@ -54,6 +58,7 @@ import com.zaneschepke.wireguardautotunnel.ui.navigation.Tab
import com.zaneschepke.wireguardautotunnel.ui.navigation.components.BottomNavbar
import com.zaneschepke.wireguardautotunnel.ui.navigation.components.DynamicTopAppBar
import com.zaneschepke.wireguardautotunnel.ui.navigation.components.currentRouteAsNavbarState
import com.zaneschepke.wireguardautotunnel.ui.navigation.functions.rememberNavController
import com.zaneschepke.wireguardautotunnel.ui.screens.autotunnel.AutoTunnelScreen
import com.zaneschepke.wireguardautotunnel.ui.screens.autotunnel.advanced.AutoTunnelAdvancedScreen
import com.zaneschepke.wireguardautotunnel.ui.screens.autotunnel.detection.WifiDetectionMethodScreen
@@ -83,11 +88,10 @@ import com.zaneschepke.wireguardautotunnel.ui.theme.OffWhite
import com.zaneschepke.wireguardautotunnel.ui.theme.WireguardAutoTunnelTheme
import com.zaneschepke.wireguardautotunnel.util.LocaleUtil
import com.zaneschepke.wireguardautotunnel.util.extensions.*
import com.zaneschepke.wireguardautotunnel.viewmodel.*
import com.zaneschepke.wireguardautotunnel.viewmodel.SharedAppViewModel
import dagger.hilt.android.AndroidEntryPoint
import de.raphaelebner.roomdatabasebackup.core.RoomBackup
import java.util.*
import java.util.Map.entry
import javax.inject.Inject
import kotlinx.coroutines.launch
import xyz.teamgravity.pin_lock_compose.PinManager
@@ -148,6 +152,12 @@ class MainActivity : AppCompatActivity() {
}
val backStack = rememberNavBackStack(*startingStack.toTypedArray())
var previousRoute by remember { mutableStateOf<Route?>(null) }
val navController =
rememberNavController<NavKey>(backStack) { previousKey ->
previousRoute = previousKey as? Route
}
val vpnActivity =
rememberLauncherForActivityResult(
@@ -190,7 +200,7 @@ class MainActivity : AppCompatActivity() {
viewModel.globalSideEffect.collect { sideEffect ->
when (sideEffect) {
GlobalSideEffect.ConfigChanged -> restartApp()
GlobalSideEffect.PopBackStack -> backStack.removeLastOrNull()
GlobalSideEffect.PopBackStack -> navController.pop()
GlobalSideEffect.RequestBatteryOptimizationDisabled ->
requestDisableBatteryOptimizations()
@@ -223,7 +233,7 @@ class MainActivity : AppCompatActivity() {
CompositionLocalProvider(
LocalIsAndroidTV provides isTv,
LocalSharedVm provides viewModel,
LocalBackStack provides backStack,
LocalNavController provides navController,
) {
WireguardAutoTunnelTheme(theme = appState.theme) {
VpnDeniedDialog(
@@ -254,7 +264,7 @@ class MainActivity : AppCompatActivity() {
viewModel,
currentRoute,
selectedCount,
backStack,
navController,
)
Box(modifier = Modifier.fillMaxSize()) {
@@ -287,10 +297,7 @@ class MainActivity : AppCompatActivity() {
appState.isAutoTunnelActive,
currentTab,
onTabSelected = { tab ->
backStack.popUpTo(tab.startRoute)
if (backStack.last() != tab.startRoute) {
backStack.add(tab.startRoute)
}
navController.popUpTo(tab.startRoute)
},
)
}
@@ -314,7 +321,30 @@ class MainActivity : AppCompatActivity() {
NavDisplay(
backStack = backStack,
modifier = Modifier.fillMaxSize(),
onBack = { backStack.removeLastOrNull() },
onBack = { navController.pop() },
transitionSpec = {
val initialIndex =
previousRoute?.let(Tab::fromRoute)?.index ?: 0
val targetIndex =
currentRoute?.let(Tab::fromRoute)?.index ?: 0
if (initialIndex != targetIndex) {
val dir = if (targetIndex > initialIndex) 1 else -1
(slideInHorizontally { dir * it } +
fadeIn()) togetherWith
(slideOutHorizontally { dir * -it } + fadeOut())
} else {
(slideInHorizontally { it } + fadeIn()) togetherWith
(slideOutHorizontally { -it } + fadeOut())
}
},
popTransitionSpec = {
(slideInHorizontally { -it } + fadeIn()) togetherWith
(slideOutHorizontally { it } + fadeOut())
},
predictivePopTransitionSpec = {
(slideInHorizontally { -it } + fadeIn()) togetherWith
(slideOutHorizontally { it } + fadeOut())
},
entryDecorators =
listOf(
rememberSceneSetupNavEntryDecorator(),
@@ -3,6 +3,7 @@ package com.zaneschepke.wireguardautotunnel.ui
import androidx.compose.runtime.staticCompositionLocalOf
import androidx.navigation3.runtime.NavBackStack
import androidx.navigation3.runtime.NavKey
import com.zaneschepke.wireguardautotunnel.ui.navigation.NavController
import com.zaneschepke.wireguardautotunnel.viewmodel.SharedAppViewModel
val LocalIsAndroidTV = staticCompositionLocalOf { false }
@@ -10,6 +11,6 @@ val LocalIsAndroidTV = staticCompositionLocalOf { false }
val LocalSharedVm =
staticCompositionLocalOf<SharedAppViewModel> { error("No shared viewmodel provided") }
val LocalBackStack = staticCompositionLocalOf<BackStack> { error("No backstack provided") }
val LocalNavController = staticCompositionLocalOf<NavController> { error("No backstack provided") }
typealias BackStack = NavBackStack<NavKey>
@@ -0,0 +1,39 @@
package com.zaneschepke.wireguardautotunnel.ui.navigation
import androidx.navigation3.runtime.NavBackStack
import androidx.navigation3.runtime.NavKey
class NavController(
private val backStack: NavBackStack<NavKey>,
private val onChange: (previous: NavKey?) -> Unit = {},
) {
fun push(route: NavKey) {
onChange(currentRoute)
backStack.add(route)
}
fun pop(): Boolean {
if (currentRoute != null) {
onChange(currentRoute)
backStack.removeLastOrNull()
return true
}
return false
}
fun popUpTo(route: NavKey) {
onChange(currentRoute)
while (backStack.size > 1 && backStack.last() != route) {
backStack.removeLast()
}
if (backStack.last() != route) {
backStack.add(route)
}
}
val currentRoute: NavKey?
get() = backStack.lastOrNull()
val canPop: Boolean
get() = backStack.size > 1
}
@@ -81,11 +81,12 @@ enum class Tab(
val startRoute: Route,
val titleRes: Int,
val icon: androidx.compose.ui.graphics.vector.ImageVector,
val index: Int,
) {
TUNNELS(Route.Tunnels, R.string.tunnels, Icons.Rounded.Home),
AUTOTUNNEL(Route.AutoTunnel, R.string.auto_tunnel, Icons.Rounded.Bolt),
SETTINGS(Route.Settings, R.string.settings, Icons.Rounded.Settings),
SUPPORT(Route.Support, R.string.support, Icons.Rounded.QuestionMark);
TUNNELS(Route.Tunnels, R.string.tunnels, Icons.Rounded.Home, 0),
AUTOTUNNEL(Route.AutoTunnel, R.string.auto_tunnel, Icons.Rounded.Bolt, 1),
SETTINGS(Route.Settings, R.string.settings, Icons.Rounded.Settings, 2),
SUPPORT(Route.Support, R.string.support, Icons.Rounded.QuestionMark, 3);
companion object {
fun fromRoute(route: Route): Tab =
@@ -12,8 +12,8 @@ import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.zaneschepke.wireguardautotunnel.R
import com.zaneschepke.wireguardautotunnel.ui.BackStack
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.sideeffect.LocalSideEffect
import com.zaneschepke.wireguardautotunnel.ui.state.NavbarState
@@ -24,7 +24,7 @@ fun currentRouteAsNavbarState(
sharedViewModel: SharedAppViewModel,
route: Route?,
selectedCount: Int,
backStack: BackStack,
navController: NavController,
): State<NavbarState> {
val sharedState by sharedViewModel.container.stateFlow.collectAsStateWithLifecycle()
val keyboardController = LocalSoftwareKeyboardController.current
@@ -37,7 +37,7 @@ fun currentRouteAsNavbarState(
NavbarState(
topLeading = {
ActionIconButton(Icons.AutoMirrored.Rounded.ArrowBack, R.string.back) {
backStack.removeLastOrNull()
navController.pop()
}
},
showBottomItems = true,
@@ -47,7 +47,7 @@ fun currentRouteAsNavbarState(
NavbarState(
topLeading = {
ActionIconButton(Icons.AutoMirrored.Rounded.ArrowBack, R.string.back) {
backStack.removeLastOrNull()
navController.pop()
}
},
showBottomItems = true,
@@ -66,7 +66,7 @@ fun currentRouteAsNavbarState(
NavbarState(
topLeading = {
ActionIconButton(Icons.AutoMirrored.Rounded.ArrowBack, R.string.back) {
backStack.removeLastOrNull()
navController.pop()
}
},
showBottomItems = true,
@@ -76,7 +76,7 @@ fun currentRouteAsNavbarState(
NavbarState(
topLeading = {
ActionIconButton(Icons.AutoMirrored.Rounded.ArrowBack, R.string.back) {
backStack.removeLastOrNull()
navController.pop()
}
},
showBottomItems = true,
@@ -86,7 +86,7 @@ fun currentRouteAsNavbarState(
NavbarState(
topLeading = {
ActionIconButton(Icons.AutoMirrored.Rounded.ArrowBack, R.string.back) {
backStack.removeLastOrNull()
navController.pop()
}
},
showBottomItems = true,
@@ -96,7 +96,7 @@ fun currentRouteAsNavbarState(
NavbarState(
topLeading = {
ActionIconButton(Icons.AutoMirrored.Rounded.ArrowBack, R.string.back) {
backStack.removeLastOrNull()
navController.pop()
}
},
showBottomItems = true,
@@ -108,7 +108,7 @@ fun currentRouteAsNavbarState(
NavbarState(
topLeading = {
ActionIconButton(Icons.AutoMirrored.Rounded.ArrowBack, R.string.back) {
backStack.removeLastOrNull()
navController.pop()
}
},
showBottomItems = true,
@@ -123,7 +123,7 @@ fun currentRouteAsNavbarState(
NavbarState(
topLeading = {
ActionIconButton(Icons.AutoMirrored.Rounded.ArrowBack, R.string.back) {
backStack.removeLastOrNull()
navController.pop()
}
},
showBottomItems = true,
@@ -152,7 +152,7 @@ fun currentRouteAsNavbarState(
NavbarState(
topLeading = {
ActionIconButton(Icons.AutoMirrored.Rounded.ArrowBack, R.string.back) {
backStack.removeLastOrNull()
navController.pop()
}
},
showBottomItems = true,
@@ -173,7 +173,7 @@ fun currentRouteAsNavbarState(
NavbarState(
topLeading = {
ActionIconButton(Icons.AutoMirrored.Rounded.ArrowBack, R.string.back) {
backStack.removeLastOrNull()
navController.pop()
}
},
showBottomItems = true,
@@ -191,7 +191,7 @@ fun currentRouteAsNavbarState(
NavbarState(
topLeading = {
ActionIconButton(Icons.AutoMirrored.Rounded.ArrowBack, R.string.back) {
backStack.removeLastOrNull()
navController.pop()
}
},
topTitle = tunnel?.tunName ?: "",
@@ -207,7 +207,7 @@ fun currentRouteAsNavbarState(
NavbarState(
topLeading = {
ActionIconButton(Icons.AutoMirrored.Rounded.ArrowBack, R.string.back) {
backStack.removeLastOrNull()
navController.pop()
}
},
topTitle = context.getString(R.string.splt_tunneling),
@@ -223,7 +223,7 @@ fun currentRouteAsNavbarState(
NavbarState(
topLeading = {
ActionIconButton(Icons.AutoMirrored.Rounded.ArrowBack, R.string.back) {
backStack.removeLastOrNull()
navController.pop()
}
},
showBottomItems = true,
@@ -245,7 +245,7 @@ fun currentRouteAsNavbarState(
NavbarState(
topLeading = {
ActionIconButton(Icons.AutoMirrored.Rounded.ArrowBack, R.string.back) {
backStack.removeLastOrNull()
navController.pop()
}
},
topTitle = context.getString(R.string.android_integrations),
@@ -256,7 +256,7 @@ fun currentRouteAsNavbarState(
NavbarState(
topLeading = {
ActionIconButton(Icons.AutoMirrored.Rounded.ArrowBack, R.string.back) {
backStack.removeLastOrNull()
navController.pop()
}
},
showBottomItems = true,
@@ -267,7 +267,7 @@ fun currentRouteAsNavbarState(
NavbarState(
topLeading = {
ActionIconButton(Icons.AutoMirrored.Rounded.ArrowBack, R.string.back) {
backStack.removeLastOrNull()
navController.pop()
}
},
topTitle = context.getString(R.string.tunnel_monitoring),
@@ -278,7 +278,7 @@ fun currentRouteAsNavbarState(
NavbarState(
topLeading = {
ActionIconButton(Icons.AutoMirrored.Rounded.ArrowBack, R.string.back) {
backStack.removeLastOrNull()
navController.pop()
}
},
showBottomItems = true,
@@ -289,7 +289,7 @@ fun currentRouteAsNavbarState(
sharedViewModel.postSideEffect(LocalSideEffect.Modal.QR)
}
ActionIconButton(Icons.Rounded.Edit, R.string.edit_tunnel) {
backStack.add(Route.Config(route.id))
navController.push(Route.Config(route.id))
}
}
},
@@ -306,7 +306,7 @@ fun currentRouteAsNavbarState(
Icons.AutoMirrored.Rounded.Sort,
R.string.sort,
) {
backStack.add(Route.Sort)
navController.push(Route.Sort)
}
ActionIconButton(Icons.Rounded.Add, R.string.add_tunnel) {
sharedViewModel.postSideEffect(
@@ -359,7 +359,7 @@ fun currentRouteAsNavbarState(
NavbarState(
topLeading = {
ActionIconButton(Icons.AutoMirrored.Rounded.ArrowBack, R.string.back) {
backStack.removeLastOrNull()
navController.pop()
}
},
topTitle = context.getString(R.string.wifi_detection_method),
@@ -369,7 +369,7 @@ fun currentRouteAsNavbarState(
NavbarState(
topLeading = {
ActionIconButton(Icons.AutoMirrored.Rounded.ArrowBack, R.string.back) {
backStack.removeLastOrNull()
navController.pop()
}
},
topTitle = context.getString(R.string.donate_title),
@@ -380,7 +380,7 @@ fun currentRouteAsNavbarState(
NavbarState(
topLeading = {
ActionIconButton(Icons.AutoMirrored.Rounded.ArrowBack, R.string.back) {
backStack.removeLastOrNull()
navController.pop()
}
},
topTitle = context.getString(R.string.addresses),
@@ -391,7 +391,7 @@ fun currentRouteAsNavbarState(
NavbarState(
topLeading = {
ActionIconButton(Icons.AutoMirrored.Rounded.ArrowBack, R.string.back) {
backStack.removeLastOrNull()
navController.pop()
}
},
topTitle = context.getString(R.string.tunnel_global_overrides),
@@ -0,0 +1,15 @@
package com.zaneschepke.wireguardautotunnel.ui.navigation.functions
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.navigation3.runtime.NavBackStack
import androidx.navigation3.runtime.NavKey
import com.zaneschepke.wireguardautotunnel.ui.navigation.NavController
@Composable
fun <T : NavKey> rememberNavController(
backStack: NavBackStack<NavKey>,
onChange: (NavKey?) -> Unit = {},
): NavController {
return remember(backStack, onChange) { NavController(backStack, onChange) }
}
@@ -21,7 +21,7 @@ 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.LocalBackStack
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
@@ -39,12 +39,12 @@ import com.zaneschepke.wireguardautotunnel.viewmodel.AutoTunnelViewModel
@Composable
fun AutoTunnelScreen(viewModel: AutoTunnelViewModel = hiltViewModel()) {
val context = LocalContext.current
val backStack = LocalBackStack.current
val navController = LocalNavController.current
val autoTunnelState by viewModel.container.stateFlow.collectAsStateWithLifecycle()
LaunchedEffect(autoTunnelState.stateInitialized) {
if (!autoTunnelState.isLocationDisclosureShown && autoTunnelState.stateInitialized) {
backStack.add(Route.LocationDisclosure)
navController.push(Route.LocationDisclosure)
}
}
@@ -168,7 +168,9 @@ fun AutoTunnelScreen(viewModel: AutoTunnelViewModel = hiltViewModel()) {
SectionDivider()
SurfaceSelectionGroupButton(
items =
listOf(AdvancedSettingsItem(onClick = { backStack.add(Route.AdvancedAutoTunnel) }))
listOf(
AdvancedSettingsItem(onClick = { navController.push(Route.AdvancedAutoTunnel) })
)
)
}
}
@@ -19,7 +19,7 @@ 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.LocalBackStack
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
@@ -38,7 +38,7 @@ fun wifiTunnelingItems(
viewModel: AutoTunnelViewModel,
): List<SelectionItem> {
val context = LocalContext.current
val backStack = LocalBackStack.current
val navController = LocalNavController.current
val clipboardHelper = rememberClipboardHelper()
var currentText by rememberSaveable { mutableStateOf("") }
@@ -138,8 +138,8 @@ fun wifiTunnelingItems(
),
)
},
trailing = { ForwardButton { backStack.add(Route.WifiDetectionMethod) } },
onClick = { backStack.add(Route.WifiDetectionMethod) },
trailing = { ForwardButton { navController.push(Route.WifiDetectionMethod) } },
onClick = { navController.push(Route.WifiDetectionMethod) },
),
SelectionItem(
leading = { Icon(Icons.Outlined.Filter1, contentDescription = null) },
@@ -16,22 +16,21 @@ 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 com.zaneschepke.wireguardautotunnel.ui.LocalBackStack
import com.zaneschepke.wireguardautotunnel.ui.LocalNavController
import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SurfaceSelectionGroupButton
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.util.extensions.popUpTo
import com.zaneschepke.wireguardautotunnel.viewmodel.AutoTunnelViewModel
@Composable
fun LocationDisclosureScreen(viewModel: AutoTunnelViewModel = hiltViewModel()) {
val context = LocalContext.current
val backStack = LocalBackStack.current
val navController = LocalNavController.current
fun goToAutoTunnel() {
backStack.popUpTo(Route.AutoTunnel)
navController.popUpTo(Route.AutoTunnel)
}
val settingsLauncher =
@@ -10,7 +10,7 @@ import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.res.stringResource
import com.zaneschepke.wireguardautotunnel.R
import com.zaneschepke.wireguardautotunnel.ui.LocalBackStack
import com.zaneschepke.wireguardautotunnel.ui.LocalNavController
import com.zaneschepke.wireguardautotunnel.ui.LocalSharedVm
import com.zaneschepke.wireguardautotunnel.ui.navigation.Route
import com.zaneschepke.wireguardautotunnel.util.StringValue
@@ -20,14 +20,13 @@ import xyz.teamgravity.pin_lock_compose.PinManager
@Composable
fun PinLockScreen() {
val sharedViewModel = LocalSharedVm.current
val backStack = LocalBackStack.current
val navController = LocalNavController.current
val pinAlreadyExists by rememberSaveable { mutableStateOf(PinManager.pinExists()) }
var pinCreated by rememberSaveable { mutableStateOf(false) }
fun onPinCorrect() {
sharedViewModel.authenticated()
backStack.removeLastOrNull()
backStack.add(Route.Tunnels)
navController.popUpTo(Route.Tunnels)
}
PinLock(
@@ -57,6 +56,6 @@ fun PinLockScreen() {
)
BackHandler(enabled = (!pinAlreadyExists && !pinCreated)) {
PinManager.clearPin()
backStack.add(Route.Settings)
navController.push(Route.Settings)
}
}
@@ -21,8 +21,8 @@ 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.LocalBackStack
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
@@ -39,7 +39,7 @@ import org.orbitmvi.orbit.compose.collectSideEffect
fun SettingsScreen(viewModel: SettingsViewModel = hiltViewModel()) {
val isTv = LocalIsAndroidTV.current
val context = LocalContext.current
val backStack = LocalBackStack.current
val navController = LocalNavController.current
val focusManager = LocalFocusManager.current
val sharedViewModel = LocalSharedVm.current
val interactionSource = remember { MutableInteractionSource() }
@@ -115,7 +115,7 @@ fun SettingsScreen(viewModel: SettingsViewModel = hiltViewModel()) {
add(
tunnelMonitoringItem(
appMode,
onClick = { backStack.add(Route.TunnelMonitoring) },
onClick = { navController.push(Route.TunnelMonitoring) },
) {
sharedViewModel.showSnackMessage(
StringValue.StringResource(
@@ -126,7 +126,7 @@ fun SettingsScreen(viewModel: SettingsViewModel = hiltViewModel()) {
}
)
add(
dnsSettingsItem(appMode, onClick = { backStack.add(Route.Dns) }) {
dnsSettingsItem(appMode, onClick = { navController.push(Route.Dns) }) {
sharedViewModel.showSnackMessage(
StringValue.StringResource(
R.string.mode_disabled_template,
@@ -141,34 +141,34 @@ fun SettingsScreen(viewModel: SettingsViewModel = hiltViewModel()) {
onClick = viewModel::setTunnelGlobals,
) {
settingsState.globalTunnelConf?.let {
backStack.add(Route.TunnelGlobals(it.id))
navController.push(Route.TunnelGlobals(it.id))
}
}
)
if (showProxySettings)
add(proxYSettingsItem { backStack.add(Route.ProxySettings) })
add(proxYSettingsItem { navController.push(Route.ProxySettings) })
}
)
SectionDivider()
SurfaceSelectionGroupButton(
listOf(systemFeaturesItem { backStack.add(Route.SystemFeatures) })
listOf(systemFeaturesItem { navController.push(Route.SystemFeatures) })
)
SectionDivider()
SurfaceSelectionGroupButton(
items =
buildList {
add(appearanceItem { backStack.add(Route.Appearance) })
add(appearanceItem { navController.push(Route.Appearance) })
add(
localLoggingItem(settingsState.isLocalLoggingEnabled) {
viewModel.setLocalLogging(it)
}
)
if (settingsState.isLocalLoggingEnabled)
add(readLogsItem { backStack.add(Route.Logs) })
add(readLogsItem { navController.push(Route.Logs) })
add(
pinLockItem(settingsState.isPinLockEnabled) { enabled ->
if (enabled) {
backStack.add(Route.Lock)
navController.push(Route.Lock)
} else {
sharedViewModel.setPinLockEnabled(false)
}
@@ -8,14 +8,14 @@ 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.LocalBackStack
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 backStack = LocalBackStack.current
val navController = LocalNavController.current
return SelectionItem(
leading = { Icon(Icons.Outlined.Contrast, contentDescription = null) },
title = {
@@ -25,7 +25,7 @@ fun DisplayThemeItem(): SelectionItem {
MaterialTheme.typography.bodyMedium.copy(MaterialTheme.colorScheme.onSurface),
)
},
trailing = { ForwardButton { backStack.add(Route.Display) } },
onClick = { backStack.add(Route.Display) },
trailing = { ForwardButton { navController.push(Route.Display) } },
onClick = { navController.push(Route.Display) },
)
}
@@ -8,14 +8,14 @@ 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.LocalBackStack
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 backStack = LocalBackStack.current
val navController = LocalNavController.current
return SelectionItem(
leading = { Icon(Icons.Outlined.Translate, contentDescription = null) },
title = {
@@ -25,7 +25,7 @@ fun LanguageItem(): SelectionItem {
MaterialTheme.typography.bodyMedium.copy(MaterialTheme.colorScheme.onSurface),
)
},
trailing = { ForwardButton { backStack.add(Route.Language) } },
onClick = { backStack.add(Route.Language) },
trailing = { ForwardButton { navController.push(Route.Language) } },
onClick = { navController.push(Route.Language) },
)
}
@@ -12,7 +12,7 @@ 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.LocalBackStack
import com.zaneschepke.wireguardautotunnel.ui.LocalNavController
import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SurfaceSelectionGroupButton
import com.zaneschepke.wireguardautotunnel.ui.navigation.Route
import com.zaneschepke.wireguardautotunnel.ui.screens.settings.globals.components.globalConfigItem
@@ -20,7 +20,7 @@ import com.zaneschepke.wireguardautotunnel.ui.screens.tunnels.tunneloptions.comp
@Composable
fun TunnelGlobalsScreen(globalTunnelId: Int) {
val backStack = LocalBackStack.current
val navController = LocalNavController.current
Column(
horizontalAlignment = Alignment.Start,
@@ -30,9 +30,9 @@ fun TunnelGlobalsScreen(globalTunnelId: Int) {
) {
SurfaceSelectionGroupButton(
listOf(
globalConfigItem { backStack.add(Route.ConfigGlobal(globalTunnelId)) },
globalConfigItem { navController.push(Route.ConfigGlobal(globalTunnelId)) },
splitTunnelingItem(stringResource(R.string.splt_tunneling)) {
backStack.add(Route.SplitTunnelGlobal(id = globalTunnelId))
navController.push(Route.SplitTunnelGlobal(id = globalTunnelId))
},
)
)
@@ -23,7 +23,7 @@ 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.LocalBackStack
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.label.GroupLabel
@@ -41,7 +41,7 @@ import com.zaneschepke.wireguardautotunnel.viewmodel.SupportViewModel
@Composable
fun SupportScreen(viewModel: SupportViewModel = hiltViewModel()) {
val context = LocalContext.current
val backStack = LocalBackStack.current
val navController = LocalNavController.current
val supportState by viewModel.container.stateFlow.collectAsStateWithLifecycle()
var showPermissionDialog by rememberSaveable { mutableStateOf(false) }
@@ -135,7 +135,7 @@ fun SupportScreen(viewModel: SupportViewModel = hiltViewModel()) {
GroupLabel(stringResource(R.string.thank_you), modifier = Modifier.padding(bottom = 12.dp))
UpdateSection { viewModel.checkForUpdate() }
SectionDivider()
DonateSection { backStack.add(Route.Donate) }
DonateSection { navController.push(Route.Donate) }
SectionDivider()
GeneralSupportOptions()
SectionDivider()
@@ -9,7 +9,7 @@ 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.LocalBackStack
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
@@ -22,7 +22,7 @@ import com.zaneschepke.wireguardautotunnel.util.extensions.openWebUrl
@Composable
fun GeneralSupportOptions() {
val context = LocalContext.current
val backStack = LocalBackStack.current
val navController = LocalNavController.current
SurfaceSelectionGroupButton(
items =
buildList {
@@ -71,8 +71,8 @@ fun GeneralSupportOptions() {
SelectionLabelType.TITLE,
)
},
trailing = { ForwardButton { backStack.add(Route.License) } },
onClick = { backStack.add(Route.License) },
trailing = { ForwardButton { navController.push(Route.License) } },
onClick = { navController.push(Route.License) },
)
)
}
@@ -11,7 +11,7 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import com.zaneschepke.wireguardautotunnel.BuildConfig
import com.zaneschepke.wireguardautotunnel.ui.LocalBackStack
import com.zaneschepke.wireguardautotunnel.ui.LocalNavController
import com.zaneschepke.wireguardautotunnel.ui.common.SectionDivider
import com.zaneschepke.wireguardautotunnel.ui.navigation.Route
import com.zaneschepke.wireguardautotunnel.ui.screens.support.donate.components.DonationHeroSection
@@ -21,7 +21,7 @@ import com.zaneschepke.wireguardautotunnel.util.Constants
@Composable
fun DonateScreen() {
val backStack = LocalBackStack.current
val navController = LocalNavController.current
Column(
horizontalAlignment = Alignment.Start,
verticalArrangement = Arrangement.spacedBy(12.dp, Alignment.Top),
@@ -31,7 +31,7 @@ fun DonateScreen() {
DonationHeroSection()
SectionDivider()
if (BuildConfig.FLAVOR != Constants.GOOGLE_PLAY_FLAVOR) {
DonationOptions { backStack.add(Route.Addresses) }
DonationOptions { navController.push(Route.Addresses) }
} else {
GoogleDonationMessage()
}
@@ -16,7 +16,7 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.journeyapps.barcodescanner.ScanContract
import com.journeyapps.barcodescanner.ScanOptions
import com.zaneschepke.wireguardautotunnel.R
import com.zaneschepke.wireguardautotunnel.ui.LocalBackStack
import com.zaneschepke.wireguardautotunnel.ui.LocalNavController
import com.zaneschepke.wireguardautotunnel.ui.LocalSharedVm
import com.zaneschepke.wireguardautotunnel.ui.common.dialog.InfoDialog
import com.zaneschepke.wireguardautotunnel.ui.common.functions.rememberClipboardHelper
@@ -35,7 +35,7 @@ import org.orbitmvi.orbit.compose.collectSideEffect
@Composable
fun TunnelsScreen(viewModel: TunnelsViewModel = hiltViewModel()) {
val sharedViewModel = LocalSharedVm.current
val backStack = LocalBackStack.current
val navController = LocalNavController.current
val clipboard = rememberClipboardHelper()
val sharedState by sharedViewModel.container.stateFlow.collectAsStateWithLifecycle()
@@ -125,7 +125,7 @@ fun TunnelsScreen(viewModel: TunnelsViewModel = hiltViewModel()) {
if (result != null) viewModel.importFromClipboard(result)
}
},
onManualImportClick = { backStack.add(Route.Config(null)) },
onManualImportClick = { navController.push(Route.Config(null)) },
onUrlClick = { showUrlDialog = true },
)
}
@@ -16,8 +16,8 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.platform.LocalContext
import com.zaneschepke.wireguardautotunnel.domain.state.TunnelState
import com.zaneschepke.wireguardautotunnel.ui.LocalBackStack
import com.zaneschepke.wireguardautotunnel.ui.LocalIsAndroidTV
import com.zaneschepke.wireguardautotunnel.ui.LocalNavController
import com.zaneschepke.wireguardautotunnel.ui.navigation.Route
import com.zaneschepke.wireguardautotunnel.ui.state.SharedAppUiState
import com.zaneschepke.wireguardautotunnel.ui.state.TunnelsUiState
@@ -32,7 +32,7 @@ fun TunnelList(
modifier: Modifier = Modifier,
sharedViewModel: SharedAppViewModel,
) {
val backStack = LocalBackStack.current
val navController = LocalNavController.current
val isTv = LocalIsAndroidTV.current
val context = LocalContext.current
@@ -69,7 +69,7 @@ fun TunnelList(
isSelected = selected,
tunnel = tunnel,
tunnelState = tunnelState,
onTvClick = { backStack.add(Route.TunnelOptions(tunnel.id)) },
onTvClick = { navController.push(Route.TunnelOptions(tunnel.id)) },
onToggleSelectedTunnel = { tunnel ->
sharedViewModel.toggleSelectedTunnel(tunnel.id)
},
@@ -87,7 +87,7 @@ fun TunnelList(
if (sharedState.selectedTunnels.isNotEmpty()) {
sharedViewModel.toggleSelectedTunnel(tunnel.id)
} else {
backStack.add(Route.TunnelOptions(tunnel.id))
navController.push(Route.TunnelOptions(tunnel.id))
sharedViewModel.clearSelectedTunnels()
}
},
@@ -16,7 +16,7 @@ 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.LocalBackStack
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
@@ -28,7 +28,7 @@ import org.orbitmvi.orbit.compose.collectSideEffect
@Composable
fun TunnelOptionsScreen(tunnelId: Int, viewModel: TunnelsViewModel = hiltViewModel()) {
val backStack = LocalBackStack.current
val navController = LocalNavController.current
val sharedViewModel = LocalSharedVm.current
val tunnelsState by viewModel.container.stateFlow.collectAsStateWithLifecycle()
@@ -66,7 +66,7 @@ fun TunnelOptionsScreen(tunnelId: Int, viewModel: TunnelsViewModel = hiltViewMod
add(autoTunnelingItem(tunnelConf))
add(
splitTunnelingItem(stringResource(R.string.splt_tunneling)) {
backStack.add(Route.SplitTunnel(id = tunnelConf.id))
navController.push(Route.SplitTunnel(id = tunnelConf.id))
}
)
}
@@ -9,14 +9,14 @@ 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.LocalBackStack
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 backStack = LocalBackStack.current
val navController = LocalNavController.current
return SelectionItem(
leading = { Icon(Icons.Outlined.Bolt, contentDescription = null) },
title = {
@@ -32,7 +32,9 @@ fun autoTunnelingItem(tunnelConf: TunnelConf): SelectionItem {
style = MaterialTheme.typography.bodySmall.copy(MaterialTheme.colorScheme.outline),
)
},
trailing = { ForwardButton { backStack.add(Route.TunnelAutoTunnel(id = tunnelConf.id)) } },
onClick = { backStack.add(Route.TunnelAutoTunnel(id = tunnelConf.id)) },
trailing = {
ForwardButton { navController.push(Route.TunnelAutoTunnel(id = tunnelConf.id)) }
},
onClick = { navController.push(Route.TunnelAutoTunnel(id = tunnelConf.id)) },
)
}
@@ -14,18 +14,10 @@ import com.zaneschepke.wireguardautotunnel.R
import com.zaneschepke.wireguardautotunnel.data.model.AppMode
import com.zaneschepke.wireguardautotunnel.data.model.WifiDetectionMethod
import com.zaneschepke.wireguardautotunnel.domain.state.TunnelState
import com.zaneschepke.wireguardautotunnel.ui.BackStack
import com.zaneschepke.wireguardautotunnel.ui.navigation.Route
import com.zaneschepke.wireguardautotunnel.ui.theme.AlertRed
import com.zaneschepke.wireguardautotunnel.ui.theme.SilverTree
import com.zaneschepke.wireguardautotunnel.ui.theme.Straw
fun BackStack.popUpTo(route: Route) {
while (size > 1 && last() != route) {
removeLast()
}
}
fun WifiDetectionMethod.asTitleString(context: Context): String {
return when (this) {
WifiDetectionMethod.DEFAULT -> context.getString(R.string._default)