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) {