android/ui: distinguish Tailnet domains in user view

Updates tailscale/corp#18202

Signed-off-by: Percy Wegmann <percy@tailscale.com>
pull/275/head
Percy Wegmann 8 months ago committed by Percy Wegmann
parent e9465988dd
commit 91c1a8d0f3

@ -20,6 +20,7 @@ import androidx.compose.material3.lightColorScheme
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.DisposableEffect
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import com.google.accompanist.systemuicontroller.rememberSystemUiController import com.google.accompanist.systemuicontroller.rememberSystemUiController
@ -225,3 +226,6 @@ val ColorScheme.searchBarColors: TextFieldColors
focusedBorderColor = Color.Transparent, focusedBorderColor = Color.Transparent,
unfocusedBorderColor = Color.Transparent) unfocusedBorderColor = Color.Transparent)
} }
val TextStyle.short: TextStyle
get() = copy(lineHeight = 20.sp)

@ -57,7 +57,6 @@ import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.lifecycle.viewmodel.compose.viewModel import androidx.lifecycle.viewmodel.compose.viewModel
import com.google.accompanist.permissions.ExperimentalPermissionsApi import com.google.accompanist.permissions.ExperimentalPermissionsApi
import com.google.accompanist.permissions.isGranted import com.google.accompanist.permissions.isGranted
@ -74,6 +73,7 @@ import com.tailscale.ipn.ui.theme.listItem
import com.tailscale.ipn.ui.theme.primaryListItem import com.tailscale.ipn.ui.theme.primaryListItem
import com.tailscale.ipn.ui.theme.searchBarColors import com.tailscale.ipn.ui.theme.searchBarColors
import com.tailscale.ipn.ui.theme.secondaryButton import com.tailscale.ipn.ui.theme.secondaryButton
import com.tailscale.ipn.ui.theme.short
import com.tailscale.ipn.ui.theme.surfaceContainerListItem import com.tailscale.ipn.ui.theme.surfaceContainerListItem
import com.tailscale.ipn.ui.util.Lists import com.tailscale.ipn.ui.util.Lists
import com.tailscale.ipn.ui.util.LoadingIndicator import com.tailscale.ipn.ui.util.LoadingIndicator
@ -97,11 +97,10 @@ fun MainView(navigation: MainViewNavigation, viewModel: MainViewModel = viewMode
Column( Column(
modifier = Modifier.fillMaxWidth().padding(paddingInsets), modifier = Modifier.fillMaxWidth().padding(paddingInsets),
verticalArrangement = Arrangement.Center) { verticalArrangement = Arrangement.Center) {
val state = viewModel.ipnState.collectAsState(initial = Ipn.State.NoState) val state = viewModel.ipnState.collectAsState(initial = Ipn.State.NoState).value
val user = viewModel.loggedInUser.collectAsState(initial = null) val user = viewModel.loggedInUser.collectAsState(initial = null).value
val stateVal = viewModel.stateRes.collectAsState(initial = R.string.placeholder) val stateVal = viewModel.stateRes.collectAsState(initial = R.string.placeholder).value
val stateStr = stringResource(id = stateVal.value) val stateStr = stringResource(id = stateVal)
val username = viewModel.userName
ListItem( ListItem(
colors = MaterialTheme.colorScheme.surfaceContainerListItem, colors = MaterialTheme.colorScheme.surfaceContainerListItem,
@ -110,39 +109,31 @@ fun MainView(navigation: MainViewNavigation, viewModel: MainViewModel = viewMode
TintedSwitch( TintedSwitch(
onCheckedChange = { viewModel.toggleVpn() }, onCheckedChange = { viewModel.toggleVpn() },
checked = isOn.value, checked = isOn.value,
enabled = state.value != Ipn.State.NoState) enabled = state != Ipn.State.NoState)
}, },
headlineContent = { headlineContent = {
if (username.isNotEmpty()) { user?.NetworkProfile?.DomainName?.let { domain ->
Text( Text(
text = username, text = domain,
style = MaterialTheme.typography.titleMedium.copy(lineHeight = 20.sp)) maxLines = 1,
overflow = TextOverflow.Ellipsis,
style = MaterialTheme.typography.titleMedium.short)
} }
}, },
supportingContent = { supportingContent = {
if (username.isNotEmpty()) { Text(text = stateStr, style = MaterialTheme.typography.bodyMedium.short)
Text(
text = stateStr,
style = MaterialTheme.typography.bodyMedium.copy(lineHeight = 20.sp))
} else {
Text(
text = stateStr,
style = MaterialTheme.typography.bodyMedium.copy(lineHeight = 20.sp))
}
}, },
trailingContent = { trailingContent = {
Box(modifier = Modifier.weight(1f), contentAlignment = Alignment.CenterEnd) { Box(modifier = Modifier.weight(1f), contentAlignment = Alignment.CenterEnd) {
when (user.value) { when (user) {
null -> SettingsButton(user.value) { navigation.onNavigateToSettings() } null -> SettingsButton { navigation.onNavigateToSettings() }
else -> else ->
Avatar(profile = user.value, size = 36) { Avatar(profile = user, size = 36) { navigation.onNavigateToSettings() }
navigation.onNavigateToSettings()
}
} }
} }
}) })
when (state.value) { when (state) {
Ipn.State.Running -> { Ipn.State.Running -> {
PromptPermissionsIfNecessary(permissions = Permissions.all) PromptPermissionsIfNecessary(permissions = Permissions.all)
@ -156,9 +147,7 @@ fun MainView(navigation: MainViewNavigation, viewModel: MainViewModel = viewMode
} }
Ipn.State.NoState, Ipn.State.NoState,
Ipn.State.Starting -> StartingView() Ipn.State.Starting -> StartingView()
else -> else -> ConnectView(state, user, { viewModel.toggleVpn() }, { viewModel.login {} })
ConnectView(
state.value, user.value, { viewModel.toggleVpn() }, { viewModel.login {} })
} }
} }
} }
@ -225,7 +214,7 @@ fun ExitNodeStatus(navAction: () -> Unit, viewModel: MainViewModel) {
} }
@Composable @Composable
fun SettingsButton(user: IpnLocal.LoginProfile?, action: () -> Unit) { fun SettingsButton(action: () -> Unit) {
// (jonathan) TODO: On iOS this is the users avatar or a letter avatar. // (jonathan) TODO: On iOS this is the users avatar or a letter avatar.
IconButton(modifier = Modifier.size(24.dp), onClick = { action() }) { IconButton(modifier = Modifier.size(24.dp), onClick = { action() }) {

@ -31,10 +31,10 @@ import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.lifecycle.viewmodel.compose.viewModel import androidx.lifecycle.viewmodel.compose.viewModel
import com.tailscale.ipn.R import com.tailscale.ipn.R
import com.tailscale.ipn.ui.theme.listItem import com.tailscale.ipn.ui.theme.listItem
import com.tailscale.ipn.ui.theme.short
import com.tailscale.ipn.ui.theme.ts_color_light_blue import com.tailscale.ipn.ui.theme.ts_color_light_blue
import com.tailscale.ipn.ui.util.Lists import com.tailscale.ipn.ui.util.Lists
import com.tailscale.ipn.ui.util.itemsWithDividers import com.tailscale.ipn.ui.util.itemsWithDividers
@ -58,7 +58,7 @@ fun PeerDetails(
Column { Column {
Text( Text(
text = node.displayName, text = node.displayName,
style = MaterialTheme.typography.titleMedium.copy(lineHeight = 20.sp), style = MaterialTheme.typography.titleMedium.short,
color = MaterialTheme.colorScheme.onSurface) color = MaterialTheme.colorScheme.onSurface)
Row(verticalAlignment = Alignment.CenterVertically) { Row(verticalAlignment = Alignment.CenterVertically) {
Box( Box(
@ -70,7 +70,7 @@ fun PeerDetails(
Spacer(modifier = Modifier.size(8.dp)) Spacer(modifier = Modifier.size(8.dp))
Text( Text(
text = stringResource(id = node.connectedStrRes(netmap)), text = stringResource(id = node.connectedStrRes(netmap)),
style = MaterialTheme.typography.bodyMedium.copy(lineHeight = 20.sp), style = MaterialTheme.typography.bodyMedium.short,
color = MaterialTheme.colorScheme.onSurfaceVariant) color = MaterialTheme.colorScheme.onSurfaceVariant)
} }
} }

@ -13,6 +13,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import com.tailscale.ipn.R import com.tailscale.ipn.R
import com.tailscale.ipn.ui.model.IpnLocal import com.tailscale.ipn.ui.model.IpnLocal
import com.tailscale.ipn.ui.theme.short
// Used to decorate UserViews. // Used to decorate UserViews.
// NONE indicates no decoration // NONE indicates no decoration
@ -40,10 +41,12 @@ fun UserView(
headlineContent = { headlineContent = {
Text( Text(
text = profile.UserProfile.DisplayName, text = profile.UserProfile.DisplayName,
style = MaterialTheme.typography.titleMedium) style = MaterialTheme.typography.titleMedium.short)
}, },
supportingContent = { supportingContent = {
Text(text = profile.Name, style = MaterialTheme.typography.bodyMedium) Text(
text = profile.NetworkProfile?.DomainName ?: "",
style = MaterialTheme.typography.bodyMedium.short)
}, },
trailingContent = { trailingContent = {
when (actionState) { when (actionState) {

@ -39,11 +39,6 @@ class MainViewModel : IpnViewModel() {
private val peerCategorizer = PeerCategorizer() private val peerCategorizer = PeerCategorizer()
val userName: String
get() {
return loggedInUser.value?.Name ?: ""
}
init { init {
viewModelScope.launch { viewModelScope.launch {
Notifier.state.collect { state -> Notifier.state.collect { state ->

Loading…
Cancel
Save