Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ui: add Mullvad info view #450

Merged
merged 1 commit into from
Jul 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions android/src/main/java/com/tailscale/ipn/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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") })
Expand All @@ -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 =
Expand Down
24 changes: 24 additions & 0 deletions android/src/main/java/com/tailscale/ipn/ui/view/ExitNodePicker.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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,
Expand Down
56 changes: 56 additions & 0 deletions android/src/main/java/com/tailscale/ipn/ui/view/MullvadInfoView.kt
Original file line number Diff line number Diff line change
@@ -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)
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -55,6 +56,7 @@ class ExitNodePickerViewModel(private val nav: ExitNodePickerNav) : IpnViewModel
val mullvadBestAvailableByCountry: StateFlow<Map<String, ExitNode>> = MutableStateFlow(TreeMap())
val mullvadExitNodeCount: StateFlow<Int> = MutableStateFlow(0)
val anyActive: StateFlow<Boolean> = MutableStateFlow(false)
val shouldShowMullvadInfo: StateFlow<Boolean> = MutableStateFlow(false)

init {
viewModelScope.launch {
Expand Down Expand Up @@ -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"))
}
}
}
}
Expand Down
Binary file added android/src/main/res/drawable/mullvad_logo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 6 additions & 0 deletions android/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -283,4 +283,10 @@
<string name="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">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.</string>
<string name="included_packages">Included packages</string>
<string name="excluded_packages">Excluded packages</string>

<!-- Strings for the Mullvad info view -->
<string name="enable_in_the_admin_console">Enable in the admin console</string>
<string name="mullvad_info_explainer">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.</string>
<string name="mullvad_info_title">Mullvad VPN is not configured</string>
<string name="the_mullvad_vpn_logo">The Mullvad VPN logo</string>
</resources>
Loading