ui: reintroduce dark mode theme

Fixes tailscale/corp#19137

Reintroduces the dark mode theme for the app, using mockups on Figma.

Signed-off-by: Andrea Gottardo <andrea@gottardo.me>
angott/reintroduce-dark-mode
Andrea Gottardo 3 weeks ago
parent a77edc6724
commit 8c2b6ec446

@ -16,6 +16,7 @@ import androidx.compose.material3.TextFieldColors
import androidx.compose.material3.TopAppBarColors
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.material3.Typography
import androidx.compose.material3.darkColorScheme
import androidx.compose.material3.lightColorScheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
@ -27,7 +28,12 @@ import com.google.accompanist.systemuicontroller.rememberSystemUiController
@Composable
fun AppTheme(useDarkTheme: Boolean = isSystemInDarkTheme(), content: @Composable() () -> Unit) {
val colors = LightColors
val colors =
if (useDarkTheme) {
DarkColors
} else {
LightColors
}
val typography =
Typography(
@ -78,6 +84,35 @@ private val LightColors =
scrim = Color(0xAA000000), // black
)
private val DarkColors =
darkColorScheme(
primary = Color(0xFF3f5eb3), // blue-600
onPrimary = Color(0xFFFFFFFF), // white
primaryContainer = Color(0xFFf0f5ff), // blue-0
onPrimaryContainer = Color(0xFF3f5eb3), // blue-600
error = Color(0xFF940822), // red-600
onError = Color(0xFFFFFFFF), // white
errorContainer = Color(0xFFfff6f4), // red-0
onErrorContainer = Color(0xFF940822), // red-600
surfaceDim = Color(0xFF1f1e1e), // gray-900
surface = Color(0xFF232222), // gray-800
background = Color(0xFF1f1e1e), // gray-900
surfaceBright = Color(0xFF444342), // gray-600
surfaceContainerLowest = Color(0xFF232222), // gray-800
surfaceContainerLow = Color(0xFF2e2d2d), // gray-700
surfaceContainer = Color(0xFF2e2d2d), // gray-700
surfaceContainerHigh = Color(0xFF2e2d2d), // gray-700
surfaceContainerHighest = Color(0xFF444342), // gray-600
surfaceVariant = Color(0xFF1f1e1e), // gray-900
onSurface = Color(0xFFfaf9f8), // gray-0
onSurfaceVariant = Color(0xFFafacab), // gray-400
outline = Color(0xFF706E6D), // gray-500
outlineVariant = Color(0xFF444342), // gray-600
inverseSurface = Color(0xFFEDEBEA), // gray-200
inverseOnSurface = Color(0xFF000000), // black
scrim = Color(0xAA000000), // black
)
val ColorScheme.warning: Color
get() = Color(0xFFD97916) // yellow-300
@ -106,7 +141,13 @@ val ColorScheme.on: Color
get() = Color(0xFF1CA672) // green-300
val ColorScheme.off: Color
get() = Color(0xFFD9D6D5) // gray-300
@Composable
get() =
if (isSystemInDarkTheme()) {
Color(0xFFAFACAB) // gray-400
} else {
Color(0xFFD9D6D5) // gray-300
}
val ColorScheme.link: Color
get() = onPrimaryContainer
@ -238,6 +279,60 @@ val ColorScheme.secondaryButton: ButtonColors
disabledContentColor = defaults.disabledContentColor)
}
val ColorScheme.defaultTextColor: Color
@Composable
get() =
if (isSystemInDarkTheme()) {
Color.White
} else {
Color.Black
}
val ColorScheme.logoBackground: Color
@Composable
get() =
if (isSystemInDarkTheme()) {
Color(0xFFFFFFFF) // white
} else {
Color(0xFF1F1E1E)
}
val ColorScheme.standaloneLogoDotEnabled: Color
@Composable
get() =
if (isSystemInDarkTheme()) {
Color(0xFFFFFFFF)
} else {
Color(0xFF000000)
}
val ColorScheme.standaloneLogoDotDisabled: Color
@Composable
get() =
if (isSystemInDarkTheme()) {
Color(0x66FFFFFF)
} else {
Color(0x661F1E1E)
}
val ColorScheme.onBackgroundLogoDotEnabled: Color
@Composable
get() =
if (isSystemInDarkTheme()) {
Color(0xFF141414)
} else {
Color(0xFFFFFFFF)
}
val ColorScheme.onBackgroundLogoDotDisabled: Color
@Composable
get() =
if (isSystemInDarkTheme()) {
Color(0x66141414)
} else {
Color(0x66FFFFFF)
}
val ColorScheme.disabled: Color
get() = Color(0xFFAFACAB) // gray-400
@ -252,9 +347,9 @@ val ColorScheme.searchBarColors: TextFieldColors
focusedTextColor = MaterialTheme.colorScheme.onSurface,
unfocusedTextColor = MaterialTheme.colorScheme.onSurfaceVariant,
disabledTextColor = MaterialTheme.colorScheme.onSurfaceVariant,
focusedContainerColor = MaterialTheme.colorScheme.background,
unfocusedContainerColor = MaterialTheme.colorScheme.background,
disabledContainerColor = MaterialTheme.colorScheme.background,
focusedContainerColor = MaterialTheme.colorScheme.surfaceContainer,
unfocusedContainerColor = MaterialTheme.colorScheme.surfaceContainer,
disabledContainerColor = MaterialTheme.colorScheme.surfaceContainer,
focusedBorderColor = Color.Transparent,
unfocusedBorderColor = Color.Transparent)
}

@ -15,6 +15,7 @@ import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.produceState
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
import com.tailscale.ipn.ui.view.TailscaleLogoView
@ -53,7 +54,7 @@ object LoadingIndicator {
Column(
modifier = Modifier.fillMaxWidth(),
horizontalAlignment = Alignment.CenterHorizontally) {
TailscaleLogoView(true, Modifier.size(72.dp))
TailscaleLogoView(true, usesOnBackgroundColors = false, Modifier.size(72.dp).alpha(0.4f))
}
}
}

@ -3,7 +3,6 @@
package com.tailscale.ipn.ui.view
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
@ -22,7 +21,6 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
@ -30,6 +28,7 @@ import androidx.compose.ui.unit.dp
import com.tailscale.ipn.BuildConfig
import com.tailscale.ipn.R
import com.tailscale.ipn.ui.Links
import com.tailscale.ipn.ui.theme.logoBackground
@Composable
fun AboutView(backToSettings: BackNavigation) {
@ -39,16 +38,19 @@ fun AboutView(backToSettings: BackNavigation) {
verticalArrangement =
Arrangement.spacedBy(space = 20.dp, alignment = Alignment.CenterVertically),
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier.fillMaxWidth().fillMaxHeight().padding(innerPadding).verticalScroll(rememberScrollState())) {
Image(
modifier =
Modifier.fillMaxWidth()
.fillMaxHeight()
.padding(innerPadding)
.verticalScroll(rememberScrollState())) {
TailscaleLogoView(
usesOnBackgroundColors = true,
modifier =
Modifier.width(100.dp)
.height(100.dp)
.clip(RoundedCornerShape(50))
.background(MaterialTheme.colorScheme.onSurface)
.padding(15.dp),
painter = painterResource(id = R.drawable.androidicon),
contentDescription = stringResource(R.string.app_icon_content_description))
.background(MaterialTheme.colorScheme.logoBackground)
.padding(25.dp))
Column(
verticalArrangement =

@ -25,6 +25,7 @@ import androidx.compose.ui.text.withStyle
import androidx.lifecycle.viewmodel.compose.viewModel
import com.tailscale.ipn.R
import com.tailscale.ipn.ui.Links
import com.tailscale.ipn.ui.theme.defaultTextColor
import com.tailscale.ipn.ui.theme.link
import com.tailscale.ipn.ui.util.ClipboardValueView
import com.tailscale.ipn.ui.util.Lists
@ -60,7 +61,9 @@ fun BugReportView(backToSettings: BackNavigation, model: BugReportViewModel = vi
@Composable
fun contactText(): AnnotatedString {
val annotatedString = buildAnnotatedString {
append(stringResource(id = R.string.bug_report_instructions_prefix))
withStyle(SpanStyle(color = MaterialTheme.colorScheme.defaultTextColor)) {
append(stringResource(id = R.string.bug_report_instructions_prefix))
}
pushStringAnnotation(tag = "reportLink", annotation = Links.SUPPORT_URL)
withStyle(
@ -72,7 +75,9 @@ fun contactText(): AnnotatedString {
}
pop()
append(stringResource(id = R.string.bug_report_instructions_suffix))
withStyle(SpanStyle(color = MaterialTheme.colorScheme.defaultTextColor)) {
append(stringResource(id = R.string.bug_report_instructions_suffix))
}
}
return annotatedString
}

@ -3,7 +3,6 @@
package com.tailscale.ipn.ui.view
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
@ -21,7 +20,6 @@ import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
@ -33,10 +31,7 @@ fun IntroView(onContinue: () -> Unit) {
modifier = Modifier.fillMaxHeight().fillMaxWidth().verticalScroll(rememberScrollState()),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center) {
Image(
modifier = Modifier.width(80.dp).height(80.dp),
painter = painterResource(id = R.drawable.androidicon_light),
contentDescription = stringResource(R.string.app_icon_content_description))
TailscaleLogoView(modifier = Modifier.width(60.dp).height(60.dp))
Spacer(modifier = Modifier.height(40.dp))
Text(
modifier = Modifier.padding(start = 40.dp, end = 40.dp, bottom = 40.dp),

@ -186,7 +186,9 @@ fun ExitNodeStatus(navAction: () -> Unit, viewModel: MainViewModel) {
modifier = Modifier.clickable { navAction() },
colors =
if (active) MaterialTheme.colorScheme.primaryListItem
else ListItemDefaults.colors(),
else ListItemDefaults.colors(
containerColor = MaterialTheme.colorScheme.surfaceContainerLowest
),
overlineContent = {
Text(
stringResource(R.string.exit_node),
@ -241,7 +243,7 @@ fun StartingView() {
modifier = Modifier.fillMaxSize(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally) {
TailscaleLogoView(animated = true, Modifier.size(40.dp))
TailscaleLogoView(animated = true, usesOnBackgroundColors = false, Modifier.size(40.dp))
}
}

@ -26,6 +26,7 @@ import androidx.compose.ui.text.withStyle
import androidx.lifecycle.viewmodel.compose.viewModel
import com.tailscale.ipn.R
import com.tailscale.ipn.ui.Links
import com.tailscale.ipn.ui.theme.defaultTextColor
import com.tailscale.ipn.ui.theme.link
import com.tailscale.ipn.ui.util.ClipboardValueView
import com.tailscale.ipn.ui.util.Lists
@ -97,7 +98,9 @@ private fun ExplainerView() {
@Composable
fun explainerText(): AnnotatedString {
val annotatedString = buildAnnotatedString {
append(stringResource(id = R.string.tailnet_lock_explainer))
withStyle(SpanStyle(color = MaterialTheme.colorScheme.defaultTextColor)) {
append(stringResource(id = R.string.tailnet_lock_explainer))
}
pushStringAnnotation(tag = "tailnetLockSupportURL", annotation = Links.TAILNET_LOCK_KB_URL)

@ -14,6 +14,10 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import com.tailscale.ipn.ui.theme.onBackgroundLogoDotDisabled
import com.tailscale.ipn.ui.theme.onBackgroundLogoDotEnabled
import com.tailscale.ipn.ui.theme.standaloneLogoDotDisabled
import com.tailscale.ipn.ui.theme.standaloneLogoDotEnabled
import com.tailscale.ipn.ui.util.set
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
@ -31,10 +35,24 @@ val logoDotsMatrix: DotsMatrix =
)
@Composable
fun TailscaleLogoView(animated: Boolean = false, modifier: Modifier) {
fun TailscaleLogoView(
animated: Boolean = false,
usesOnBackgroundColors: Boolean = false,
modifier: Modifier
) {
val primaryColor: Color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.3f)
val secondaryColor: Color = primaryColor.copy(alpha = 0.1f)
val primaryColor: Color =
if (usesOnBackgroundColors) {
MaterialTheme.colorScheme.onBackgroundLogoDotEnabled
} else {
MaterialTheme.colorScheme.standaloneLogoDotEnabled
}
val secondaryColor: Color =
if (usesOnBackgroundColors) {
MaterialTheme.colorScheme.onBackgroundLogoDotDisabled
} else {
MaterialTheme.colorScheme.standaloneLogoDotDisabled
}
val currentDotsMatrix: StateFlow<DotsMatrix> = MutableStateFlow(logoDotsMatrix)
var currentDotsMatrixIndex = 0

Loading…
Cancel
Save