From 1465b2a67f4685ee6e88bcaac1d97eb1e5d63089 Mon Sep 17 00:00:00 2001 From: Andrea Gottardo Date: Wed, 24 Jul 2024 10:02:33 -0700 Subject: [PATCH] ui: add Mullvad info view (#450) Updates tailscale/tailscale#9421 Adds a view to highlight Mullvad support when it is not enabled. Signed-off-by: Andrea Gottardo --- .../java/com/tailscale/ipn/MainActivity.kt | 3 + .../tailscale/ipn/ui/view/ExitNodePicker.kt | 24 ++++++++ .../tailscale/ipn/ui/view/MullvadInfoView.kt | 56 ++++++++++++++++++ .../ui/viewModel/ExitNodePickerViewModel.kt | 9 +++ .../src/main/res/drawable/mullvad_logo.png | Bin 0 -> 16536 bytes android/src/main/res/values/strings.xml | 6 ++ 6 files changed, 98 insertions(+) create mode 100644 android/src/main/java/com/tailscale/ipn/ui/view/MullvadInfoView.kt create mode 100644 android/src/main/res/drawable/mullvad_logo.png diff --git a/android/src/main/java/com/tailscale/ipn/MainActivity.kt b/android/src/main/java/com/tailscale/ipn/MainActivity.kt index 56f154b..9a34051 100644 --- a/android/src/main/java/com/tailscale/ipn/MainActivity.kt +++ b/android/src/main/java/com/tailscale/ipn/MainActivity.kt @@ -59,6 +59,7 @@ import com.tailscale.ipn.ui.view.MainViewNavigation import com.tailscale.ipn.ui.view.ManagedByView import com.tailscale.ipn.ui.view.MullvadExitNodePicker import com.tailscale.ipn.ui.view.MullvadExitNodePickerList +import com.tailscale.ipn.ui.view.MullvadInfoView import com.tailscale.ipn.ui.view.PeerDetails import com.tailscale.ipn.ui.view.PermissionsView import com.tailscale.ipn.ui.view.RunExitNodeView @@ -179,6 +180,7 @@ class MainActivity : ComponentActivity() { }, onNavigateBackToExitNodes = backTo("exitNodes"), onNavigateToMullvad = { navController.navigate("mullvad") }, + onNavigateToMullvadInfo = { navController.navigate("mullvad_info") }, onNavigateBackToMullvad = backTo("mullvad"), onNavigateToMullvadCountry = { navController.navigate("mullvad/$it") }, onNavigateToRunAsExitNode = { navController.navigate("runExitNode") }) @@ -198,6 +200,7 @@ class MainActivity : ComponentActivity() { composable("settings") { SettingsView(settingsNav) } composable("exitNodes") { ExitNodePicker(exitNodePickerNav) } composable("mullvad") { MullvadExitNodePickerList(exitNodePickerNav) } + composable("mullvad_info") { MullvadInfoView(exitNodePickerNav) } composable( "mullvad/{countryCode}", arguments = 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 a1ad251..c72aa7a 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 @@ -49,6 +49,7 @@ fun ExitNodePicker( val mullvadExitNodesByCountryCode by model.mullvadExitNodesByCountryCode.collectAsState() val mullvadExitNodeCount by model.mullvadExitNodeCount.collectAsState() val anyActive by model.anyActive.collectAsState() + val shouldShowMullvadInfo by model.shouldShowMullvadInfo.collectAsState() val allowLANAccess = Notifier.prefs.collectAsState().value?.ExitNodeAllowLANAccess == true val showRunAsExitNode by MDMSettings.runExitNode.flow.collectAsState() val allowLanAccessMDMDisposition by MDMSettings.exitNodeAllowLANAccess.flow.collectAsState() @@ -91,6 +92,11 @@ fun ExitNodePicker( MullvadItem( nav, mullvadExitNodesByCountryCode.size, mullvadExitNodesByCountryCode.selected) } + } else if (shouldShowMullvadInfo) { + item(key = "mullvad_info") { + Lists.SectionDivider() + MullvadInfoItem(nav) + } } if (!allowLanAccessMDMDisposition.hiddenFromUser) { @@ -167,6 +173,24 @@ fun MullvadItem(nav: ExitNodePickerNav, count: Int, selected: Boolean) { } } +@Composable +fun MullvadInfoItem(nav: ExitNodePickerNav) { + Box { + ListItem( + modifier = Modifier.clickable { nav.onNavigateToMullvadInfo() }, + headlineContent = { + Text( + stringResource(R.string.mullvad_exit_nodes), + style = MaterialTheme.typography.bodyMedium) + }, + supportingContent = { + Text( + stringResource(R.string.enable_in_the_admin_console), + style = MaterialTheme.typography.bodyMedium) + }) + } +} + @Composable fun RunAsExitNodeItem( nav: ExitNodePickerNav, diff --git a/android/src/main/java/com/tailscale/ipn/ui/view/MullvadInfoView.kt b/android/src/main/java/com/tailscale/ipn/ui/view/MullvadInfoView.kt new file mode 100644 index 0000000..3f83337 --- /dev/null +++ b/android/src/main/java/com/tailscale/ipn/ui/view/MullvadInfoView.kt @@ -0,0 +1,56 @@ +// Copyright (c) Tailscale Inc & AUTHORS +// SPDX-License-Identifier: BSD-3-Clause + +package com.tailscale.ipn.ui.view + +import androidx.compose.foundation.Image +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import com.tailscale.ipn.R +import com.tailscale.ipn.ui.viewModel.ExitNodePickerNav + +@Composable +fun MullvadInfoView(nav: ExitNodePickerNav) { + Scaffold( + topBar = { + Header(R.string.choose_mullvad_exit_node, onBack = nav.onNavigateBackToExitNodes) + }) { innerPadding -> + LazyColumn( + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.spacedBy(20.dp), + contentPadding = PaddingValues(horizontal = 16.dp, vertical = 48.dp), + modifier = Modifier.padding(innerPadding)) { + item { + Image( + painter = painterResource(id = R.drawable.mullvad_logo), + contentDescription = stringResource(R.string.the_mullvad_vpn_logo)) + } + item { + Text( + stringResource(R.string.mullvad_info_title), + fontFamily = MaterialTheme.typography.titleLarge.fontFamily, + fontSize = MaterialTheme.typography.titleLarge.fontSize, + fontWeight = FontWeight.SemiBold) + } + item { + Text( + stringResource(R.string.mullvad_info_explainer), + color = MaterialTheme.colorScheme.secondary, + textAlign = TextAlign.Center) + } + } + } +} 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 0e952b6..c24b4e5 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 @@ -23,6 +23,7 @@ data class ExitNodePickerNav( val onNavigateBackHome: () -> Unit, val onNavigateBackToExitNodes: () -> Unit, val onNavigateToMullvad: () -> Unit, + val onNavigateToMullvadInfo: () -> Unit, val onNavigateBackToMullvad: () -> Unit, val onNavigateToMullvadCountry: (String) -> Unit, val onNavigateToRunAsExitNode: () -> Unit, @@ -55,6 +56,7 @@ class ExitNodePickerViewModel(private val nav: ExitNodePickerNav) : IpnViewModel val mullvadBestAvailableByCountry: StateFlow> = MutableStateFlow(TreeMap()) val mullvadExitNodeCount: StateFlow = MutableStateFlow(0) val anyActive: StateFlow = MutableStateFlow(false) + val shouldShowMullvadInfo: StateFlow = MutableStateFlow(false) init { viewModelScope.launch { @@ -128,6 +130,13 @@ class ExitNodePickerViewModel(private val nav: ExitNodePickerNav) : IpnViewModel mullvadBestAvailableByCountry.set(bestAvailableByCountry) anyActive.set(allNodes.any { it.selected }) + + prefs?.let { prefs -> + // Only show the Mullvad info view if the user is an admin and is using a Tailscale + // control server, as it wouldn't be actionable information otherwise. + shouldShowMullvadInfo.set( + netmap.SelfNode.isAdmin && prefs.ControlURL.endsWith(".tailscale.com")) + } } } } diff --git a/android/src/main/res/drawable/mullvad_logo.png b/android/src/main/res/drawable/mullvad_logo.png new file mode 100644 index 0000000000000000000000000000000000000000..9427849dcda0c560383a0336ed3a712d93bc53b1 GIT binary patch literal 16536 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 a(8*uET=*lh3;jm|02Kud`3hOHu>S+s%sra` literal 0 HcmV?d00001 diff --git a/android/src/main/res/values/strings.xml b/android/src/main/res/values/strings.xml index 664c68a..572c4eb 100644 --- a/android/src/main/res/values/strings.xml +++ b/android/src/main/res/values/strings.xml @@ -283,4 +283,10 @@ Specifies a list of apps that will always use Tailscale routes and DNS when Tailscale is running. All other apps won\'t use Tailscale if this value is non-empty. Included packages Excluded packages + + + Enable in the admin console + Once you enable Mullvad VPN on the admin console, you\'ll be able to encrypt and route your traffic using Mullvad’s global network of servers as exit nodes. + Mullvad VPN is not configured + The Mullvad VPN logo