Skip to content
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
7 changes: 6 additions & 1 deletion android/src/main/java/com/tailscale/ipn/ui/model/NetMap.kt
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ class Netmap {
var Domain: String,
var UserProfiles: Map<String, Tailcfg.UserProfile>,
var TKAEnabled: Boolean,
var DNS: Tailcfg.DNSConfig? = null
var DNS: Tailcfg.DNSConfig? = null,
var AllCaps: List<String> = emptyList()
) {
// Keys are tailcfg.UserIDs thet get stringified
// Helpers
Expand Down Expand Up @@ -51,5 +52,9 @@ class Netmap {
UserProfiles == other.UserProfiles &&
TKAEnabled == other.TKAEnabled
}

fun hasCap(capability: String): Boolean {
return AllCaps.contains(capability)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,19 @@

package com.tailscale.ipn.ui.view

import android.content.Intent
import android.net.Uri
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.text.ClickableText
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.MoreVert
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.DropdownMenu
import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.ExperimentalMaterial3Api
Expand All @@ -20,13 +24,19 @@ import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.text.withStyle
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
Expand All @@ -46,10 +56,14 @@ data class UserSwitcherNav(
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun UserSwitcherView(nav: UserSwitcherNav, viewModel: UserSwitcherViewModel = viewModel()) {

val users by viewModel.loginProfiles.collectAsState()
val currentUser by viewModel.loggedInUser.collectAsState()
val showHeaderMenu by viewModel.showHeaderMenu.collectAsState()
var showDeleteDialog by remember { mutableStateOf(false) }
val context = LocalContext.current
val netmapState by viewModel.netmap.collectAsState()
val capabilityIsOwner = "https://tailscale.com/cap/is-owner"
val isOwner = netmapState?.hasCap(capabilityIsOwner) == true

Scaffold(
topBar = {
Expand Down Expand Up @@ -138,10 +152,47 @@ fun UserSwitcherView(nav: UserSwitcherNav, viewModel: UserSwitcherViewModel = vi
}
})
}

Lists.SectionDivider()
Setting.Text(R.string.delete_tailnet, destructive = true) {
showDeleteDialog = true
}
}
}
}
}

if (showDeleteDialog) {
AlertDialog(
onDismissRequest = { showDeleteDialog = false },
title = { Text(text = stringResource(R.string.delete_tailnet)) },
text = {
if (isOwner) {
OwnerDeleteDialogText {
val uri = Uri.parse("https://login.tailscale.com/admin/settings/general")
context.startActivity(Intent(Intent.ACTION_VIEW, uri))
}
} else {
Text(stringResource(R.string.request_deletion_nonowner))
}
},
confirmButton = {
TextButton(
onClick = {
val intent =
Intent(Intent.ACTION_VIEW, Uri.parse("https://tailscale.com/contact/support"))
context.startActivity(intent)
showDeleteDialog = false
}) {
Text(text = stringResource(R.string.contact_support))
}
},
dismissButton = {
TextButton(onClick = { showDeleteDialog = false }) {
Text(text = stringResource(R.string.cancel))
}
})
}
}

@Composable
Expand Down Expand Up @@ -171,6 +222,41 @@ fun FusMenu(
}
}

@Composable
fun OwnerDeleteDialogText(onSettingsClick: () -> Unit) {
val part1 = stringResource(R.string.request_deletion_owner_part1)
val part2a = stringResource(R.string.request_deletion_owner_part2a)
val part2b = stringResource(R.string.request_deletion_owner_part2b)

val annotatedText = buildAnnotatedString {
append(part1 + " ")

pushStringAnnotation(
tag = "settings", annotation = "https://login.tailscale.com/admin/settings/general")
withStyle(style = SpanStyle(color = MaterialTheme.colorScheme.primary)) {
append("Settings > General")
}
pop()

append(" $part2a\n\n") // newline after "Delete tailnet."
append(part2b)
}

val context = LocalContext.current
ClickableText(
text = annotatedText,
style = MaterialTheme.typography.bodyMedium.copy(color = MaterialTheme.colorScheme.onSurface),
onClick = { offset ->
annotatedText
.getStringAnnotations(tag = "settings", start = offset, end = offset)
.firstOrNull()
?.let { annotation ->
val intent = Intent(Intent.ACTION_VIEW, Uri.parse(annotation.item))
context.startActivity(intent)
}
})
}

@Composable
fun MenuItem(text: String, onClick: () -> Unit) {
DropdownMenuItem(
Expand Down
16 changes: 15 additions & 1 deletion android/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
<string name="not_connected">Not connected</string>
<string name="empty" translatable="false"> </string>
<string name="template" translatable="false">%s</string>
<string name="selected">Selected</string>
<string name="selected">Selected</string>
<string name="offline">Offline</string>
<string name="ok">OK</string>
<string name="_continue">Continue</string>
Expand Down Expand Up @@ -137,6 +137,20 @@
<string name="custom_control_url_title">Custom control server URL</string>
<string name="auth_key_input_title">Auth key</string>

<string name="delete_tailnet">Delete tailnet</string>
<string name="contact_support">Contact support</string>
<string name="request_deletion_nonowner">All requests related to the removal or deletion of data are handled by our Support team. To open a request, tap the Contact Support button below to be taken to our contact form in the browser. Complete the form, and a Customer Support Engineer will work with you directly to assist.</string>
<string name="request_deletion_owner_part1">
As the owner of this tailnet, to remove yourself from the tailnet you can either reassign ownership and contact our Support team, or delete the whole tailnet through the admin console. To do the latter, go to
</string>
<string name="request_deletion_owner_part2a">
and look for “Delete tailnet”.
</string>

<string name="request_deletion_owner_part2b">
All requests related to the removal or deletion of data are handled by our Support team. To open a request, tap the Contact Support button below to be taken to our contact form in the browser. Complete the form, and a Customer Support Engineer will work with you directly to assist.
</string>

<!-- Strings for ExitNode picker -->
<string name="choose_exit_node">Choose exit node</string>
<string name="choose_mullvad_exit_node">Mullvad exit nodes</string>
Expand Down