From 7c38f49c7dd6b2230eb3c100b26e15d2dfb25465 Mon Sep 17 00:00:00 2001 From: kari-ts Date: Wed, 22 May 2024 15:07:49 -0700 Subject: [PATCH] ExitNodePicker: don't allow run as exit node while using exit node Updates tailscale/corp#19122 Signed-off-by: kari-ts --- .../tailscale/ipn/ui/view/ExitNodePicker.kt | 30 +++++++++++-------- .../ui/viewModel/ExitNodePickerViewModel.kt | 9 +++--- 2 files changed, 23 insertions(+), 16 deletions(-) diff --git a/android/src/main/java/com/tailscale/ipn/ui/view/ExitNodePicker.kt b/android/src/main/java/com/tailscale/ipn/ui/view/ExitNodePicker.kt index c8ef29aa3b..143dedab4e 100644 --- a/android/src/main/java/com/tailscale/ipn/ui/view/ExitNodePicker.kt +++ b/android/src/main/java/com/tailscale/ipn/ui/view/ExitNodePicker.kt @@ -5,8 +5,8 @@ package com.tailscale.ipn.ui.view import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.Row import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.Check @@ -18,9 +18,6 @@ import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue -import androidx.compose.runtime.remember -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.rememberUpdatedState import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.lifecycle.viewmodel.compose.viewModel @@ -37,13 +34,14 @@ import com.tailscale.ipn.ui.viewModel.ExitNodePickerNav import com.tailscale.ipn.ui.viewModel.ExitNodePickerViewModel import com.tailscale.ipn.ui.viewModel.ExitNodePickerViewModelFactory import com.tailscale.ipn.ui.viewModel.selected +import kotlinx.coroutines.flow.MutableStateFlow @Composable fun ExitNodePicker( nav: ExitNodePickerNav, model: ExitNodePickerViewModel = viewModel(factory = ExitNodePickerViewModelFactory(nav)) ) { - LoadingIndicator.Wrap { + LoadingIndicator.Wrap { Scaffold(topBar = { Header(R.string.choose_exit_node, onBack = nav.onNavigateBackHome) }) { innerPadding -> val tailnetExitNodes by model.tailnetExitNodes.collectAsState() @@ -60,13 +58,13 @@ fun ExitNodePicker( model, ExitNodePickerViewModel.ExitNode( label = stringResource(R.string.none), - online = true, + online = MutableStateFlow(true), selected = !anyActive, )) if (showRunAsExitNode == ShowHide.Show) { Lists.ItemDivider() - RunAsExitNodeItem(nav = nav, viewModel = model) + RunAsExitNodeItem(nav = nav, viewModel = model, anyActive) } } @@ -102,17 +100,18 @@ fun ExitNodeItem( viewModel: ExitNodePickerViewModel, node: ExitNodePickerViewModel.ExitNode, ) { - val online by rememberUpdatedState(newValue = node.online) + val online by node.online.collectAsState() + val isRunningExitNode = viewModel.isRunningExitNode.collectAsState().value Box { var modifier: Modifier = Modifier - if (online) { + if (online && !isRunningExitNode) { modifier = modifier.clickable { viewModel.setExitNode(node) } } ListItem( modifier = modifier, colors = - if (online) MaterialTheme.colorScheme.listItem + if (online && !isRunningExitNode) MaterialTheme.colorScheme.listItem else MaterialTheme.colorScheme.disabledListItem, headlineContent = { Text(node.city.ifEmpty { node.label }, style = MaterialTheme.typography.bodyMedium) @@ -155,12 +154,19 @@ fun MullvadItem(nav: ExitNodePickerNav, count: Int, selected: Boolean) { } @Composable -fun RunAsExitNodeItem(nav: ExitNodePickerNav, viewModel: ExitNodePickerViewModel) { +fun RunAsExitNodeItem(nav: ExitNodePickerNav, viewModel: ExitNodePickerViewModel, anyActive: Boolean) { val isRunningExitNode = viewModel.isRunningExitNode.collectAsState().value Box { + var modifier: Modifier = Modifier + if (!anyActive) { + modifier = modifier.clickable { nav.onNavigateToRunAsExitNode() } + } ListItem( - modifier = Modifier.clickable { nav.onNavigateToRunAsExitNode() }, + modifier = modifier, + colors = + if (!anyActive) MaterialTheme.colorScheme.listItem + else MaterialTheme.colorScheme.disabledListItem, headlineContent = { Text( stringResource(id = R.string.run_as_exit_node), diff --git a/android/src/main/java/com/tailscale/ipn/ui/viewModel/ExitNodePickerViewModel.kt b/android/src/main/java/com/tailscale/ipn/ui/viewModel/ExitNodePickerViewModel.kt index 7b2649d035..bd0cfc06bc 100644 --- a/android/src/main/java/com/tailscale/ipn/ui/viewModel/ExitNodePickerViewModel.kt +++ b/android/src/main/java/com/tailscale/ipn/ui/viewModel/ExitNodePickerViewModel.kt @@ -39,7 +39,7 @@ class ExitNodePickerViewModel(private val nav: ExitNodePickerNav) : IpnViewModel data class ExitNode( val id: StableNodeID? = null, val label: String, - val online: Boolean, + val online: StateFlow, val selected: Boolean, val mullvad: Boolean = false, val priority: Int = 0, @@ -72,7 +72,7 @@ class ExitNodePickerViewModel(private val nav: ExitNodePickerNav) : IpnViewModel ExitNode( id = it.StableID, label = it.displayName, - online = it.Online ?: false, + online = MutableStateFlow(it.Online ?: false), selected = it.StableID == exitNodeId, mullvad = it.Name.endsWith(".mullvad.ts.net."), priority = it.Hostinfo.Location?.Priority ?: 0, @@ -86,9 +86,10 @@ class ExitNodePickerViewModel(private val nav: ExitNodePickerNav) : IpnViewModel tailnetExitNodes.set(tailnetNodes.sortedWith { a, b -> a.label.compareTo(b.label) }) val allMullvadExitNodes = - allNodes.filter { + allNodes.filter { node -> // Pick all mullvad nodes that are online or the currently selected - it.mullvad && (it.selected || it.online) + val online = node.online.value + node.mullvad && (node.selected || online) } val mullvadExitNodes = allMullvadExitNodes