From 3a5dc6e7da1a4796512c4be51931bcc2899d9f84 Mon Sep 17 00:00:00 2001 From: Andrea Gottardo Date: Fri, 4 Oct 2024 11:10:53 -0700 Subject: [PATCH] ui/UserView: show custom control server URL in account switcher Fixes tailscale/corp#23660 Brings each row of the account switcher to parity with iOS by using `LoginName` instead of `DisplayName`, and showing a custom control server hostname as a third row when it is set. Signed-off-by: Andrea Gottardo --- .../com/tailscale/ipn/ui/model/IpnState.kt | 21 +++++++++++++++++ .../com/tailscale/ipn/ui/view/UserView.kt | 23 ++++++++++++++----- 2 files changed, 38 insertions(+), 6 deletions(-) diff --git a/android/src/main/java/com/tailscale/ipn/ui/model/IpnState.kt b/android/src/main/java/com/tailscale/ipn/ui/model/IpnState.kt index f54e8f4d56..e66fea7d77 100644 --- a/android/src/main/java/com/tailscale/ipn/ui/model/IpnState.kt +++ b/android/src/main/java/com/tailscale/ipn/ui/model/IpnState.kt @@ -4,6 +4,7 @@ package com.tailscale.ipn.ui.model import kotlinx.serialization.Serializable +import java.net.URL class IpnState { @Serializable @@ -123,9 +124,29 @@ class IpnLocal { val UserProfile: Tailcfg.UserProfile, val NetworkProfile: Tailcfg.NetworkProfile? = null, val LocalUserID: String, + var ControlURL: String? = null, ) { fun isEmpty(): Boolean { return ID.isEmpty() } + + // Returns true if the profile uses a custom control server (not Tailscale SaaS). + fun isUsingCustomControlServer(): Boolean { + return ControlURL != null && ControlURL != "controlplane.tailscale.com" + } + + // Returns the hostname of the custom control server, if any was set. + // + // Returns null if the ControlURL provided by the backend is an invalid URL, and + // a hostname cannot be extracted. + fun customControlServerHostname(): String? { + if (!isUsingCustomControlServer()) return null + + return try { + URL(ControlURL).host + } catch (e: Exception) { + null + } + } } } diff --git a/android/src/main/java/com/tailscale/ipn/ui/view/UserView.kt b/android/src/main/java/com/tailscale/ipn/ui/view/UserView.kt index 2279a8e435..0c2a3dc49b 100644 --- a/android/src/main/java/com/tailscale/ipn/ui/view/UserView.kt +++ b/android/src/main/java/com/tailscale/ipn/ui/view/UserView.kt @@ -5,6 +5,7 @@ package com.tailscale.ipn.ui.view import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.offset import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.filled.KeyboardArrowRight @@ -54,17 +55,27 @@ fun UserView( leadingContent = { Avatar(profile = profile, size = 36) }, headlineContent = { AutoResizingText( - text = profile.UserProfile.DisplayName, + text = profile.UserProfile.LoginName, style = MaterialTheme.typography.titleMedium.short, minFontSize = MaterialTheme.typography.minTextSize, overflow = TextOverflow.Ellipsis) }, supportingContent = { - AutoResizingText( - text = profile.NetworkProfile?.DomainName ?: "", - style = MaterialTheme.typography.bodyMedium.short, - minFontSize = MaterialTheme.typography.minTextSize, - overflow = TextOverflow.Ellipsis) + Column { + AutoResizingText( + text = profile.NetworkProfile?.DomainName ?: "", + style = MaterialTheme.typography.bodyMedium.short, + minFontSize = MaterialTheme.typography.minTextSize, + overflow = TextOverflow.Ellipsis) + + profile.customControlServerHostname()?.let { + AutoResizingText( + text = it, + style = MaterialTheme.typography.bodyMedium.short, + minFontSize = MaterialTheme.typography.minTextSize, + overflow = TextOverflow.Ellipsis) + } + } }, trailingContent = { when (actionState) {