From 72b0e365e9229865b9122679106984252352803f Mon Sep 17 00:00:00 2001 From: Jonathan Nobels Date: Fri, 12 Apr 2024 10:06:23 -0400 Subject: [PATCH] android/ui: handle NeedsMachineAuth state Fixes corp#19119 Adds a variation on the ConnectView to render a header and explainer text for the NeedsMachineAuth state. A button to take you directly to the admin page is presented if you are an admin. Signed-off-by: Jonathan Nobels --- .../com/tailscale/ipn/ui/view/MainView.kt | 22 +++++++++---------- .../ipn/ui/viewModel/MainViewModel.kt | 3 +++ 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/android/src/main/java/com/tailscale/ipn/ui/view/MainView.kt b/android/src/main/java/com/tailscale/ipn/ui/view/MainView.kt index fb154e8..585f38f 100644 --- a/android/src/main/java/com/tailscale/ipn/ui/view/MainView.kt +++ b/android/src/main/java/com/tailscale/ipn/ui/view/MainView.kt @@ -26,7 +26,6 @@ import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.ArrowDropDown import androidx.compose.material.icons.outlined.Clear import androidx.compose.material.icons.outlined.Close -import androidx.compose.material.icons.outlined.Lock import androidx.compose.material.icons.outlined.Search import androidx.compose.material.icons.outlined.Settings import androidx.compose.material3.Button @@ -107,6 +106,7 @@ fun MainView(navigation: MainViewNavigation, viewModel: MainViewModel = viewMode val stateVal = viewModel.stateRes.collectAsState(initial = R.string.placeholder).value val stateStr = stringResource(id = stateVal) val netmap = viewModel.netmap.collectAsState(initial = null) + val isAdmin = viewModel.isAdmin.collectAsState(initial = false).value ListItem( colors = MaterialTheme.colorScheme.surfaceContainerListItem, @@ -163,7 +163,7 @@ fun MainView(navigation: MainViewNavigation, viewModel: MainViewModel = viewMode Ipn.State.NoState, Ipn.State.Starting -> StartingView() else -> { - ConnectView(state, user, { viewModel.toggleVpn() }, { viewModel.login {} }) + ConnectView(state, user, { viewModel.toggleVpn() }, { viewModel.login {} }, isAdmin) } } } @@ -257,7 +257,8 @@ fun ConnectView( state: Ipn.State, user: IpnLocal.LoginProfile?, connectAction: () -> Unit, - loginAction: () -> Unit + loginAction: () -> Unit, + isAdmin: Boolean, ) { val handler = LocalUriHandler.current @@ -269,10 +270,6 @@ fun ConnectView( horizontalAlignment = Alignment.CenterHorizontally, ) { if (state == Ipn.State.NeedsMachineAuth) { - Icon( - modifier = Modifier.size(40.dp), - imageVector = Icons.Outlined.Lock, - contentDescription = "Device requires authentication") Text( text = stringResource(id = R.string.machine_auth_required), style = MaterialTheme.typography.titleMedium, @@ -281,11 +278,12 @@ fun ConnectView( text = stringResource(id = R.string.machine_auth_explainer), style = MaterialTheme.typography.bodyMedium, textAlign = TextAlign.Center) - Spacer(modifier = Modifier.size(1.dp)) - PrimaryActionButton(onClick = { handler.openUri(Links.ADMIN_URL) }) { - Text( - text = stringResource(id = R.string.open_admin_console), - fontSize = MaterialTheme.typography.titleMedium.fontSize) + if (isAdmin) { + PrimaryActionButton(onClick = { handler.openUri(Links.ADMIN_URL) }) { + Text( + text = stringResource(id = R.string.open_admin_console), + fontSize = MaterialTheme.typography.titleMedium.fontSize) + } } } else if (state != Ipn.State.NeedsLogin && user != null && !user.isEmpty()) { Icon( diff --git a/android/src/main/java/com/tailscale/ipn/ui/viewModel/MainViewModel.kt b/android/src/main/java/com/tailscale/ipn/ui/viewModel/MainViewModel.kt index 692038c..0c2e590 100644 --- a/android/src/main/java/com/tailscale/ipn/ui/viewModel/MainViewModel.kt +++ b/android/src/main/java/com/tailscale/ipn/ui/viewModel/MainViewModel.kt @@ -34,6 +34,8 @@ class MainViewModel : IpnViewModel() { val prefs = Notifier.prefs val netmap = Notifier.netmap + val isAdmin: StateFlow = MutableStateFlow(false) + // The active search term for filtering peers val searchTerm: StateFlow = MutableStateFlow("") @@ -53,6 +55,7 @@ class MainViewModel : IpnViewModel() { it?.let { netmap -> peerCategorizer.regenerateGroupedPeers(netmap) peers.set(peerCategorizer.groupedAndFilteredPeers(searchTerm.value)) + isAdmin.set(netmap.SelfNode.isAdmin) } } }