@@ -12,6 +12,8 @@ import androidx.compose.foundation.layout.fillMaxSize
1212import androidx.compose.foundation.layout.fillMaxWidth
1313import androidx.compose.foundation.layout.imePadding
1414import androidx.compose.foundation.layout.padding
15+ import androidx.compose.foundation.layout.widthIn
16+ import androidx.compose.foundation.shape.RoundedCornerShape
1517import androidx.compose.material.icons.Icons
1618import androidx.compose.material.icons.automirrored.filled.ArrowBack
1719import androidx.compose.material.icons.filled.Add
@@ -27,6 +29,7 @@ import androidx.compose.material3.Icon
2729import androidx.compose.material3.IconButton
2830import androidx.compose.material3.MaterialTheme
2931import androidx.compose.material3.RadioButton
32+ import androidx.compose.material3.Surface
3033import androidx.compose.material3.Text
3134import androidx.compose.material3.TopAppBar
3235import androidx.compose.runtime.Composable
@@ -39,9 +42,16 @@ import androidx.compose.runtime.remember
3942import androidx.compose.runtime.setValue
4043import androidx.compose.ui.Alignment
4144import androidx.compose.ui.Modifier
45+ import androidx.compose.ui.graphics.Color
4246import androidx.compose.ui.platform.LocalContext
4347import androidx.compose.ui.platform.LocalSoftwareKeyboardController
4448import androidx.compose.ui.res.stringResource
49+ import androidx.compose.ui.text.LinkAnnotation
50+ import androidx.compose.ui.text.SpanStyle
51+ import androidx.compose.ui.text.TextLinkStyles
52+ import androidx.compose.ui.text.buildAnnotatedString
53+ import androidx.compose.ui.text.style.TextDecoration
54+ import androidx.compose.ui.text.withLink
4555import androidx.compose.ui.unit.dp
4656import androidx.compose.ui.viewinterop.AndroidView
4757import androidx.lifecycle.viewmodel.compose.viewModel
@@ -298,6 +308,24 @@ fun TailscaleSSHTerminalScreen(
298308 color = MaterialTheme .colorScheme.onSurface,
299309 modifier = Modifier .padding(top = 16 .dp),
300310 )
311+ val banner = activeSession.terminalSession.authBanner
312+ if (! banner.isNullOrBlank()) {
313+ val linkColor = MaterialTheme .colorScheme.primary
314+ Surface (
315+ color = MaterialTheme .colorScheme.surfaceVariant,
316+ shape = RoundedCornerShape (10 .dp),
317+ modifier = Modifier
318+ .padding(top = 16 .dp, start = 16 .dp, end = 16 .dp)
319+ .widthIn(max = 480 .dp),
320+ ) {
321+ Text (
322+ remember(banner, linkColor) { bannerAnnotatedString(banner, linkColor) },
323+ style = MaterialTheme .typography.bodyMedium,
324+ color = MaterialTheme .colorScheme.onSurfaceVariant,
325+ modifier = Modifier .padding(12 .dp),
326+ )
327+ }
328+ }
301329 }
302330 }
303331 }
@@ -331,6 +359,30 @@ private class TerminalViewRef {
331359 var view: TerminalView ? = null
332360}
333361
362+ private val bannerUrlRegex = Regex (""" https?://\S+""" )
363+
364+ private fun bannerAnnotatedString (text : String , linkColor : Color ) = buildAnnotatedString {
365+ var lastIndex = 0
366+ for (match in bannerUrlRegex.findAll(text)) {
367+ if (match.range.first > lastIndex) {
368+ append(text.substring(lastIndex, match.range.first))
369+ }
370+ val url = match.value
371+ withLink(
372+ LinkAnnotation .Url (
373+ url,
374+ TextLinkStyles (SpanStyle (color = linkColor, textDecoration = TextDecoration .Underline )),
375+ ),
376+ ) {
377+ append(url)
378+ }
379+ lastIndex = match.range.last + 1
380+ }
381+ if (lastIndex < text.length) {
382+ append(text.substring(lastIndex))
383+ }
384+ }
385+
334386private fun resolveTypeface (fontFamily : String , customFontPath : String ): Typeface ? {
335387 if (customFontPath.isNotBlank()) {
336388 val typeface = ImportedFontStore .loadTypeface(customFontPath)
0 commit comments