android/ui: navigation improvements

1. More careful back navigation to avoid navigating to blank screen
2. After adding an account, navigate back to main view

Updates tailscale/corp#18202

Signed-off-by: Percy Wegmann <percy@tailscale.com>
pull/297/head
Percy Wegmann 2 years ago committed by Percy Wegmann
parent 32e407d06b
commit d676dca4f4

@ -3,6 +3,7 @@
package com.tailscale.ipn package com.tailscale.ipn
import android.annotation.SuppressLint
import android.app.Activity import android.app.Activity
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
@ -30,6 +31,7 @@ import androidx.compose.ui.Modifier
import androidx.core.net.toUri import androidx.core.net.toUri
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.navigation.NavHostController
import androidx.navigation.NavType import androidx.navigation.NavType
import androidx.navigation.compose.NavHost import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable import androidx.navigation.compose.composable
@ -45,7 +47,6 @@ import com.tailscale.ipn.ui.util.AndroidTVUtil
import com.tailscale.ipn.ui.util.set import com.tailscale.ipn.ui.util.set
import com.tailscale.ipn.ui.util.universalFit import com.tailscale.ipn.ui.util.universalFit
import com.tailscale.ipn.ui.view.AboutView import com.tailscale.ipn.ui.view.AboutView
import com.tailscale.ipn.ui.view.BackNavigation
import com.tailscale.ipn.ui.view.BugReportView import com.tailscale.ipn.ui.view.BugReportView
import com.tailscale.ipn.ui.view.DNSSettingsView import com.tailscale.ipn.ui.view.DNSSettingsView
import com.tailscale.ipn.ui.view.ExitNodePicker import com.tailscale.ipn.ui.view.ExitNodePicker
@ -73,14 +74,12 @@ import kotlinx.coroutines.launch
class MainActivity : ComponentActivity() { class MainActivity : ComponentActivity() {
private lateinit var requestVpnPermission: ActivityResultLauncher<Unit> private lateinit var requestVpnPermission: ActivityResultLauncher<Unit>
private lateinit var navController: NavHostController
companion object { companion object {
// Request codes for Android callbacks.
// requestPrepareVPN is for when Android's VpnService.prepare completes.
@JvmStatic val requestPrepareVPN: Int = 1001
const val WRITE_STORAGE_RESULT = 1000
private const val TAG = "Main Activity" private const val TAG = "Main Activity"
private const val START_AT_ROOT = "startAtRoot"
} }
private fun Context.isLandscapeCapable(): Boolean { private fun Context.isLandscapeCapable(): Boolean {
@ -93,6 +92,7 @@ class MainActivity : ComponentActivity() {
// simply opening the URL. This should be consumed once it has been handled. // simply opening the URL. This should be consumed once it has been handled.
private val loginQRCode: StateFlow<String?> = MutableStateFlow(null) private val loginQRCode: StateFlow<String?> = MutableStateFlow(null)
@SuppressLint("SourceLockedOrientationActivity")
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
@ -124,6 +124,10 @@ class MainActivity : ComponentActivity() {
popExitTransition = { popExitTransition = {
slideOutHorizontally(animationSpec = tween(150), targetOffsetX = { it }) slideOutHorizontally(animationSpec = tween(150), targetOffsetX = { it })
}) { }) {
fun backTo(route: String): () -> Unit = {
navController.popBackStack(route = route, inclusive = false)
}
val mainViewNav = val mainViewNav =
MainViewNavigation( MainViewNavigation(
onNavigateToSettings = { navController.navigate("settings") }, onNavigateToSettings = { navController.navigate("settings") },
@ -143,19 +147,17 @@ class MainActivity : ComponentActivity() {
onNavigateToManagedBy = { navController.navigate("managedBy") }, onNavigateToManagedBy = { navController.navigate("managedBy") },
onNavigateToUserSwitcher = { navController.navigate("userSwitcher") }, onNavigateToUserSwitcher = { navController.navigate("userSwitcher") },
onNavigateToPermissions = { navController.navigate("permissions") }, onNavigateToPermissions = { navController.navigate("permissions") },
onBackPressed = { navController.popBackStack() }, onBackToSettings = backTo("settings"),
) onNavigateBackHome = backTo("main"))
val backNav = BackNavigation(onBack = { navController.popBackStack() })
val exitNodePickerNav = val exitNodePickerNav =
ExitNodePickerNav( ExitNodePickerNav(
onNavigateHome = { onNavigateBackHome = {
navController.popBackStack(route = "main", inclusive = false) navController.popBackStack(route = "main", inclusive = false)
}, },
onNavigateBack = { navController.popBackStack() }, onNavigateBackToExitNodes = backTo("exitNodes"),
onNavigateToExitNodePicker = { navController.popBackStack() },
onNavigateToMullvad = { navController.navigate("mullvad") }, onNavigateToMullvad = { navController.navigate("mullvad") },
onNavigateBackToMullvad = backTo("mullvad"),
onNavigateToMullvadCountry = { navController.navigate("mullvad/$it") }, onNavigateToMullvadCountry = { navController.navigate("mullvad/$it") },
onNavigateToRunAsExitNode = { navController.navigate("runExitNode") }) onNavigateToRunAsExitNode = { navController.navigate("runExitNode") })
@ -176,26 +178,21 @@ class MainActivity : ComponentActivity() {
composable( composable(
"peerDetails/{nodeId}", "peerDetails/{nodeId}",
arguments = listOf(navArgument("nodeId") { type = NavType.StringType })) { arguments = listOf(navArgument("nodeId") { type = NavType.StringType })) {
PeerDetails(nav = backNav, it.arguments?.getString("nodeId") ?: "") PeerDetails(backTo("main"), it.arguments?.getString("nodeId") ?: "")
} }
composable("bugReport") { BugReportView(nav = backNav) } composable("bugReport") { BugReportView(backTo("settings")) }
composable("dnsSettings") { DNSSettingsView(nav = backNav) } composable("dnsSettings") { DNSSettingsView(backTo("settings")) }
composable("tailnetLock") { TailnetLockSetupView(nav = backNav) } composable("tailnetLock") { TailnetLockSetupView(backTo("settings")) }
composable("about") { AboutView(nav = backNav) } composable("about") { AboutView(backTo("settings")) }
composable("mdmSettings") { MDMSettingsDebugView(nav = backNav) } composable("mdmSettings") { MDMSettingsDebugView(backTo("settings")) }
composable("managedBy") { ManagedByView(nav = backNav) } composable("managedBy") { ManagedByView(backTo("settings")) }
composable("userSwitcher") { composable("userSwitcher") {
UserSwitcherView( UserSwitcherView(backTo("settings"), backTo("main"))
nav = backNav,
onNavigateHome = {
navController.popBackStack(route = "main", inclusive = false)
})
} }
composable("permissions") { composable("permissions") {
PermissionsView( PermissionsView(backTo("settings"), ::openApplicationSettings)
nav = backNav, openApplicationSettings = ::openApplicationSettings)
} }
composable("intro") { IntroView { navController.popBackStack() } } composable("intro") { IntroView(backTo("main")) }
} }
// Show the intro screen one time // Show the intro screen one time
@ -245,6 +242,13 @@ class MainActivity : ComponentActivity() {
return AndroidTVUtil.isAndroidTV() return AndroidTVUtil.isAndroidTV()
} }
override fun onNewIntent(intent: Intent?) {
super.onNewIntent(intent)
if (intent?.getBooleanExtra(START_AT_ROOT, false) == true) {
navController.popBackStack(route = "main", inclusive = false)
}
}
private fun login(urlString: String) { private fun login(urlString: String) {
// Launch coroutine to listen for state changes. When the user completes login, relaunch // Launch coroutine to listen for state changes. When the user completes login, relaunch
// MainActivity to bring the app back to focus. // MainActivity to bring the app back to focus.
@ -257,6 +261,7 @@ class MainActivity : ComponentActivity() {
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP) addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP)
action = Intent.ACTION_MAIN action = Intent.ACTION_MAIN
addCategory(Intent.CATEGORY_LAUNCHER) addCategory(Intent.CATEGORY_LAUNCHER)
putExtra(START_AT_ROOT, true)
} }
startActivity(intent) startActivity(intent)

@ -30,8 +30,9 @@ import com.tailscale.ipn.R
import com.tailscale.ipn.ui.Links import com.tailscale.ipn.ui.Links
@Composable @Composable
fun AboutView(nav: BackNavigation) { fun AboutView(backToSettings: BackNavigation) {
Scaffold(topBar = { Header(R.string.about_view_title, onBack = nav.onBack) }) { innerPadding -> Scaffold(topBar = { Header(R.string.about_view_title, onBack = backToSettings) }) { innerPadding
->
Column( Column(
verticalArrangement = verticalArrangement =
Arrangement.spacedBy(space = 20.dp, alignment = Alignment.CenterVertically), Arrangement.spacedBy(space = 20.dp, alignment = Alignment.CenterVertically),

@ -29,11 +29,12 @@ import com.tailscale.ipn.ui.util.ClipboardValueView
import com.tailscale.ipn.ui.viewModel.BugReportViewModel import com.tailscale.ipn.ui.viewModel.BugReportViewModel
@Composable @Composable
fun BugReportView(nav: BackNavigation, model: BugReportViewModel = viewModel()) { fun BugReportView(backToSettings: BackNavigation, model: BugReportViewModel = viewModel()) {
val handler = LocalUriHandler.current val handler = LocalUriHandler.current
val bugReportID = model.bugReportID.collectAsState().value val bugReportID = model.bugReportID.collectAsState().value
Scaffold(topBar = { Header(R.string.bug_report_title, onBack = nav.onBack) }) { innerPadding -> Scaffold(topBar = { Header(R.string.bug_report_title, onBack = backToSettings) }) { innerPadding
->
Column(modifier = Modifier.padding(innerPadding).fillMaxWidth().fillMaxHeight()) { Column(modifier = Modifier.padding(innerPadding).fillMaxWidth().fillMaxHeight()) {
ListItem( ListItem(
headlineContent = { headlineContent = {

@ -35,7 +35,7 @@ data class ViewableRoute(val name: String, val resolvers: List<DnsType.Resolver>
@OptIn(ExperimentalFoundationApi::class) @OptIn(ExperimentalFoundationApi::class)
@Composable @Composable
fun DNSSettingsView( fun DNSSettingsView(
nav: BackNavigation, backToSettings: BackNavigation,
model: DNSSettingsViewModel = viewModel(factory = DNSSettingsViewModelFactory()) model: DNSSettingsViewModel = viewModel(factory = DNSSettingsViewModelFactory())
) { ) {
val state: DNSEnablementState = model.enablementState.collectAsState().value val state: DNSEnablementState = model.enablementState.collectAsState().value
@ -47,7 +47,7 @@ fun DNSSettingsView(
} ?: emptyList() } ?: emptyList()
val useCorpDNS = Notifier.prefs.collectAsState().value?.CorpDNS == true val useCorpDNS = Notifier.prefs.collectAsState().value?.CorpDNS == true
Scaffold(topBar = { Header(R.string.dns_settings, onBack = nav.onBack) }) { innerPadding -> Scaffold(topBar = { Header(R.string.dns_settings, onBack = backToSettings) }) { innerPadding ->
LoadingIndicator.Wrap { LoadingIndicator.Wrap {
LazyColumn(Modifier.padding(innerPadding)) { LazyColumn(Modifier.padding(innerPadding)) {
item("state") { item("state") {

@ -38,7 +38,7 @@ fun ExitNodePicker(
model: ExitNodePickerViewModel = viewModel(factory = ExitNodePickerViewModelFactory(nav)) model: ExitNodePickerViewModel = viewModel(factory = ExitNodePickerViewModelFactory(nav))
) { ) {
LoadingIndicator.Wrap { LoadingIndicator.Wrap {
Scaffold(topBar = { Header(R.string.choose_exit_node, onBack = nav.onNavigateBack) }) { Scaffold(topBar = { Header(R.string.choose_exit_node, onBack = nav.onNavigateBackHome) }) {
innerPadding -> innerPadding ->
val tailnetExitNodes = model.tailnetExitNodes.collectAsState().value val tailnetExitNodes = model.tailnetExitNodes.collectAsState().value
val mullvadExitNodesByCountryCode = model.mullvadExitNodesByCountryCode.collectAsState().value val mullvadExitNodesByCountryCode = model.mullvadExitNodesByCountryCode.collectAsState().value

@ -24,9 +24,9 @@ import com.tailscale.ipn.ui.viewModel.IpnViewModel
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
fun MDMSettingsDebugView(nav: BackNavigation, model: IpnViewModel = viewModel()) { fun MDMSettingsDebugView(backToSettings: BackNavigation, model: IpnViewModel = viewModel()) {
Scaffold(topBar = { Header(R.string.current_mdm_settings, onBack = nav.onBack) }) { innerPadding Scaffold(topBar = { Header(R.string.current_mdm_settings, onBack = backToSettings) }) {
-> innerPadding ->
LazyColumn(modifier = Modifier.padding(innerPadding)) { LazyColumn(modifier = Modifier.padding(innerPadding)) {
itemsWithDividers(MDMSettings.allSettings.sortedBy { "${it::class.java.name}|${it.key}" }) { itemsWithDividers(MDMSettings.allSettings.sortedBy { "${it::class.java.name}|${it.key}" }) {
setting -> setting ->

@ -21,8 +21,8 @@ import com.tailscale.ipn.mdm.MDMSettings
import com.tailscale.ipn.ui.viewModel.IpnViewModel import com.tailscale.ipn.ui.viewModel.IpnViewModel
@Composable @Composable
fun ManagedByView(nav: BackNavigation, model: IpnViewModel = viewModel()) { fun ManagedByView(backToSettings: BackNavigation, model: IpnViewModel = viewModel()) {
Scaffold(topBar = { Header(R.string.managed_by, onBack = nav.onBack) }) { innerPadding -> Scaffold(topBar = { Header(R.string.managed_by, onBack = backToSettings) }) { innerPadding ->
Column( Column(
verticalArrangement = verticalArrangement =
Arrangement.spacedBy(space = 20.dp, alignment = Alignment.CenterVertically), Arrangement.spacedBy(space = 20.dp, alignment = Alignment.CenterVertically),

@ -40,7 +40,7 @@ fun MullvadExitNodePicker(
topBar = { topBar = {
Header( Header(
title = { Text("${countryCode.flag()} ${any.country}") }, title = { Text("${countryCode.flag()} ${any.country}") },
onBack = nav.onNavigateBack) onBack = nav.onNavigateBackToExitNodes)
}) { innerPadding -> }) { innerPadding ->
LazyColumn(modifier = Modifier.padding(innerPadding)) { LazyColumn(modifier = Modifier.padding(innerPadding)) {
if (nodes.size > 1) { if (nodes.size > 1) {

@ -36,8 +36,10 @@ fun MullvadExitNodePickerList(
model: ExitNodePickerViewModel = viewModel(factory = ExitNodePickerViewModelFactory(nav)) model: ExitNodePickerViewModel = viewModel(factory = ExitNodePickerViewModelFactory(nav))
) { ) {
LoadingIndicator.Wrap { LoadingIndicator.Wrap {
Scaffold(topBar = { Header(R.string.choose_mullvad_exit_node, onBack = nav.onNavigateBack) }) { Scaffold(
innerPadding -> topBar = {
Header(R.string.choose_mullvad_exit_node, onBack = nav.onNavigateBackToMullvad)
}) { innerPadding ->
val mullvadExitNodes = model.mullvadExitNodesByCountryCode.collectAsState() val mullvadExitNodes = model.mullvadExitNodesByCountryCode.collectAsState()
LazyColumn(modifier = Modifier.padding(innerPadding)) { LazyColumn(modifier = Modifier.padding(innerPadding)) {
@ -49,7 +51,8 @@ fun MullvadExitNodePickerList(
val first = nodes.first() val first = nodes.first()
// TODO(oxtoacart): the modifier on the ListItem occasionally causes a crash // TODO(oxtoacart): the modifier on the ListItem occasionally causes a crash
// with java.lang.ClassCastException: androidx.compose.ui.ComposedModifier cannot be cast // with java.lang.ClassCastException: androidx.compose.ui.ComposedModifier cannot be
// cast
// to androidx.compose.runtime.RecomposeScopeImpl // to androidx.compose.runtime.RecomposeScopeImpl
// Wrapping it in a Box eliminates this. It appears to be some kind of // Wrapping it in a Box eliminates this. It appears to be some kind of
// interaction between the LazyList and the modifier. // interaction between the LazyList and the modifier.

@ -43,7 +43,7 @@ import com.tailscale.ipn.ui.viewModel.PeerDetailsViewModelFactory
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
fun PeerDetails( fun PeerDetails(
nav: BackNavigation, backToHome: BackNavigation,
nodeId: String, nodeId: String,
model: PeerDetailsViewModel = model: PeerDetailsViewModel =
viewModel(factory = PeerDetailsViewModelFactory(nodeId, LocalContext.current.filesDir)) viewModel(factory = PeerDetailsViewModelFactory(nodeId, LocalContext.current.filesDir))
@ -74,7 +74,7 @@ fun PeerDetails(
} }
} }
}, },
onBack = { nav.onBack() }) onBack = backToHome)
}, },
) { innerPadding -> ) { innerPadding ->
LazyColumn( LazyColumn(

@ -25,10 +25,10 @@ import com.tailscale.ipn.ui.util.itemsWithDividers
@OptIn(ExperimentalPermissionsApi::class) @OptIn(ExperimentalPermissionsApi::class)
@Composable @Composable
fun PermissionsView(nav: BackNavigation, openApplicationSettings: () -> Unit) { fun PermissionsView(backToSettings: BackNavigation, openApplicationSettings: () -> Unit) {
val permissions = Permissions.withGrantedStatus val permissions = Permissions.withGrantedStatus
Scaffold(topBar = { Header(titleRes = R.string.permissions, onBack = nav.onBack) }) { innerPadding Scaffold(topBar = { Header(titleRes = R.string.permissions, onBack = backToSettings) }) {
-> innerPadding ->
LazyColumn(modifier = Modifier.padding(innerPadding)) { LazyColumn(modifier = Modifier.padding(innerPadding)) {
itemsWithDividers(permissions) { (permission, granted) -> itemsWithDividers(permissions) { (permission, granted) ->
ListItem( ListItem(

@ -40,7 +40,7 @@ fun RunExitNodeView(
val isRunningExitNode = model.isRunningExitNode.collectAsState().value val isRunningExitNode = model.isRunningExitNode.collectAsState().value
Scaffold( Scaffold(
topBar = { Header(R.string.run_as_exit_node, onBack = nav.onNavigateToExitNodePicker) }) { topBar = { Header(R.string.run_as_exit_node, onBack = nav.onNavigateBackToExitNodes) }) {
innerPadding -> innerPadding ->
LoadingIndicator.Wrap { LoadingIndicator.Wrap {
Column( Column(

@ -4,6 +4,7 @@
package com.tailscale.ipn.ui.view package com.tailscale.ipn.ui.view
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.rememberScrollState
@ -41,8 +42,9 @@ fun SettingsView(settingsNav: SettingsNav, viewModel: SettingsViewModel = viewMo
val isAdmin = viewModel.isAdmin.collectAsState().value val isAdmin = viewModel.isAdmin.collectAsState().value
val managedByOrganization = viewModel.managedByOrganization.collectAsState().value val managedByOrganization = viewModel.managedByOrganization.collectAsState().value
Scaffold(topBar = { Scaffold(
Header(titleRes = R.string.settings_title, onBack = settingsNav.onBackPressed) topBar = {
Header(titleRes = R.string.settings_title, onBack = settingsNav.onNavigateBackHome)
}) { innerPadding -> }) { innerPadding ->
Column(modifier = Modifier.padding(innerPadding).verticalScroll(rememberScrollState())) { Column(modifier = Modifier.padding(innerPadding).verticalScroll(rememberScrollState())) {
UserView( UserView(

@ -29,9 +29,7 @@ import androidx.compose.ui.unit.dp
import com.tailscale.ipn.ui.theme.topAppBar import com.tailscale.ipn.ui.theme.topAppBar
import com.tailscale.ipn.ui.theme.ts_color_light_blue import com.tailscale.ipn.ui.theme.ts_color_light_blue
data class BackNavigation( typealias BackNavigation = () -> Unit
val onBack: () -> Unit,
)
// Header view for all secondary screens // Header view for all secondary screens
// @see TopAppBar actions for additional actions (usually a row of icons) // @see TopAppBar actions for additional actions (usually a row of icons)

@ -37,14 +37,14 @@ import com.tailscale.ipn.ui.viewModel.TailnetLockSetupViewModelFactory
@Composable @Composable
fun TailnetLockSetupView( fun TailnetLockSetupView(
nav: BackNavigation, backToSettings: BackNavigation,
model: TailnetLockSetupViewModel = viewModel(factory = TailnetLockSetupViewModelFactory()) model: TailnetLockSetupViewModel = viewModel(factory = TailnetLockSetupViewModelFactory())
) { ) {
val statusItems = model.statusItems.collectAsState().value val statusItems = model.statusItems.collectAsState().value
val nodeKey = model.nodeKey.collectAsState().value val nodeKey = model.nodeKey.collectAsState().value
val tailnetLockKey = model.tailnetLockKey.collectAsState().value val tailnetLockKey = model.tailnetLockKey.collectAsState().value
Scaffold(topBar = { Header(R.string.tailnet_lock, onBack = nav.onBack) }) { innerPadding -> Scaffold(topBar = { Header(R.string.tailnet_lock, onBack = backToSettings) }) { innerPadding ->
LoadingIndicator.Wrap { LoadingIndicator.Wrap {
LazyColumn(modifier = Modifier.padding(innerPadding)) { LazyColumn(modifier = Modifier.padding(innerPadding)) {
item(key = "header") { ExplainerView() } item(key = "header") { ExplainerView() }

@ -43,7 +43,7 @@ import com.tailscale.ipn.ui.viewModel.UserSwitcherViewModel
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
fun UserSwitcherView( fun UserSwitcherView(
nav: BackNavigation, backToSettings: BackNavigation,
onNavigateHome: () -> Unit, onNavigateHome: () -> Unit,
viewModel: UserSwitcherViewModel = viewModel() viewModel: UserSwitcherViewModel = viewModel()
) { ) {
@ -56,7 +56,7 @@ fun UserSwitcherView(
topBar = { topBar = {
Header( Header(
R.string.accounts, R.string.accounts,
onBack = nav.onBack, onBack = backToSettings,
actions = { actions = {
Row { Row {
FusMenu(viewModel = viewModel) FusMenu(viewModel = viewModel)

@ -20,10 +20,10 @@ import kotlinx.coroutines.launch
import java.util.TreeMap import java.util.TreeMap
data class ExitNodePickerNav( data class ExitNodePickerNav(
val onNavigateHome: () -> Unit, val onNavigateBackHome: () -> Unit,
val onNavigateBack: () -> Unit, val onNavigateBackToExitNodes: () -> Unit,
val onNavigateToExitNodePicker: () -> Unit,
val onNavigateToMullvad: () -> Unit, val onNavigateToMullvad: () -> Unit,
val onNavigateBackToMullvad: () -> Unit,
val onNavigateToMullvadCountry: (String) -> Unit, val onNavigateToMullvadCountry: (String) -> Unit,
val onNavigateToRunAsExitNode: () -> Unit, val onNavigateToRunAsExitNode: () -> Unit,
) )
@ -138,7 +138,7 @@ class ExitNodePickerViewModel(private val nav: ExitNodePickerNav) : IpnViewModel
val prefsOut = Ipn.MaskedPrefs() val prefsOut = Ipn.MaskedPrefs()
prefsOut.ExitNodeID = node.id prefsOut.ExitNodeID = node.id
Client(viewModelScope).editPrefs(prefsOut) { Client(viewModelScope).editPrefs(prefsOut) {
nav.onNavigateHome() nav.onNavigateBackHome()
LoadingIndicator.stop() LoadingIndicator.stop()
} }
} }

@ -20,7 +20,8 @@ data class SettingsNav(
val onNavigateToManagedBy: () -> Unit, val onNavigateToManagedBy: () -> Unit,
val onNavigateToUserSwitcher: () -> Unit, val onNavigateToUserSwitcher: () -> Unit,
val onNavigateToPermissions: () -> Unit, val onNavigateToPermissions: () -> Unit,
val onBackPressed: () -> Unit, val onNavigateBackHome: () -> Unit,
val onBackToSettings: () -> Unit,
) )
class SettingsViewModel() : IpnViewModel() { class SettingsViewModel() : IpnViewModel() {

Loading…
Cancel
Save