@ -3,6 +3,8 @@
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
@ -10,8 +12,10 @@ 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
@ -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
@ -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 = {
@ -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
@ -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 (