Skip to content

Commit

Permalink
Added a dark theme (#1457)
Browse files Browse the repository at this point in the history
Since there are a lot of custom colors used in this app, I created a
JetLaggedExtraColors that is handed down as a composition local similar
to the main MaterialTheme. I created dark versions of the colors trying
to quickly eyeball what looks good, but we can always change them later
since they now flow from one place.

This also updates the shaders to accept colors. I used a threshold to
make the moving stripes one switch to the background color for the last
draw since that was the easiest fix. With more time, adjusting the loop
might make it a bit more robust, but I think this works well as a first
stab (and we can easily change the colors). The solar flare shader also
takes colors now. That one was using alpha to blend, so now it's instead
mixing the color channels based on what the alpha would have been.

![light
theme](https://github.com/user-attachments/assets/c78b54ba-daaa-41e3-ad9b-8507218f60f5)
![dark
theme](https://github.com/user-attachments/assets/2b576d5b-ba1a-4222-afea-ff6d5a6ea120)
  • Loading branch information
IanGClifton authored Sep 12, 2024
2 parents 74a5ef4 + 98e0270 commit 405b060
Show file tree
Hide file tree
Showing 15 changed files with 327 additions and 127 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ package com.example.jetlagged
import androidx.compose.ui.test.assertIsDisplayed
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.onNodeWithText
import com.example.jetlagged.ui.theme.JetLaggedTheme
import org.junit.Before
import org.junit.Rule
import org.junit.Test
Expand All @@ -31,7 +32,9 @@ class AppTest {
@Before
fun setUp() {
composeTestRule.setContent {
JetLaggedScreen()
JetLaggedTheme {
JetLaggedScreen()
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,9 +64,7 @@ import com.example.jetlagged.backgrounds.BubbleBackground
import com.example.jetlagged.backgrounds.FadingCircleBackground
import com.example.jetlagged.data.WellnessData
import com.example.jetlagged.ui.theme.HeadingStyle
import com.example.jetlagged.ui.theme.LightBlue
import com.example.jetlagged.ui.theme.Lilac
import com.example.jetlagged.ui.theme.MintGreen
import com.example.jetlagged.ui.theme.JetLaggedTheme
import com.example.jetlagged.ui.theme.SmallHeadingStyle

@Composable
Expand All @@ -78,7 +76,9 @@ fun BasicInformationalCard(
val shape = RoundedCornerShape(24.dp)
Card(
shape = shape,
colors = CardDefaults.cardColors(containerColor = Color.White),
colors = CardDefaults.cardColors(
containerColor = JetLaggedTheme.extraColors.cardBackground
),
modifier = modifier
.padding(8.dp),
border = BorderStroke(2.dp, borderColor)
Expand Down Expand Up @@ -174,7 +174,7 @@ fun TwoLineInfoCard(
@Composable
fun AverageTimeInBedCard(modifier: Modifier = Modifier) {
TwoLineInfoCard(
borderColor = Lilac,
borderColor = JetLaggedTheme.extraColors.bed,
firstLineText = stringResource(R.string.ave_time_in_bed_heading),
secondLineText = "8h42min",
icon = Icons.Default.Watch,
Expand All @@ -189,7 +189,7 @@ fun AverageTimeInBedCard(modifier: Modifier = Modifier) {
@Composable
fun AverageTimeAsleepCard(modifier: Modifier = Modifier) {
TwoLineInfoCard(
borderColor = MintGreen,
borderColor = JetLaggedTheme.extraColors.sleep,
firstLineText = stringResource(R.string.ave_time_sleep_heading),
secondLineText = "7h42min",
icon = Icons.Default.SingleBed,
Expand All @@ -207,12 +207,12 @@ fun WellnessCard(
wellnessData: WellnessData = WellnessData(0, 0, 0)
) {
BasicInformationalCard(
borderColor = LightBlue,
borderColor = JetLaggedTheme.extraColors.wellness,
modifier = modifier
.widthIn(max = 400.dp)
.heightIn(min = 200.dp)
) {
FadingCircleBackground(36.dp, LightBlue.copy(0.25f))
FadingCircleBackground(36.dp, JetLaggedTheme.extraColors.wellness.copy(0.25f))
Column(
horizontalAlignment = CenterHorizontally,
modifier = Modifier
Expand Down Expand Up @@ -249,15 +249,16 @@ fun WellnessBubble(
titleText: String,
countText: String,
metric: String,
modifier: Modifier = Modifier
modifier: Modifier = Modifier,
bubbleColor: Color = JetLaggedTheme.extraColors.wellness
) {
Column(
modifier = modifier
.padding(4.dp)
.sizeIn(maxHeight = 100.dp)
.aspectRatio(1f)
.drawBehind {
drawCircle(LightBlue)
drawCircle(bubbleColor)
},
verticalArrangement = Arrangement.Center,
horizontalAlignment = CenterHorizontally
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@ import androidx.compose.material.icons.filled.Leaderboard
import androidx.compose.material.icons.filled.Settings
import androidx.compose.material3.Icon
import androidx.compose.material3.NavigationDrawerItem
import androidx.compose.material3.NavigationDrawerItemDefaults
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.material3.windowsizeclass.WindowSizeClass
Expand All @@ -50,7 +49,6 @@ import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.input.pointer.util.VelocityTracker
Expand Down Expand Up @@ -249,16 +247,14 @@ private fun HomeScreenDrawerContents(
.padding(16.dp),
verticalArrangement = Arrangement.Center
) {
Screen.values().forEach {
Screen.entries.forEach {
NavigationDrawerItem(
label = {
Text(it.text)
},
icon = {
Icon(imageVector = it.icon, contentDescription = it.text)
},
colors =
NavigationDrawerItemDefaults.colors(unselectedContainerColor = Color.White),
selected = selectedScreen == it,
onClick = {
onScreenSelected(it)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,10 @@ import androidx.compose.foundation.layout.widthIn
import androidx.compose.foundation.layout.windowInsetsPadding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.windowsizeclass.WindowWidthSizeClass
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.lifecycle.viewmodel.compose.viewModel
Expand All @@ -45,6 +45,7 @@ import com.example.jetlagged.data.JetLaggedHomeScreenViewModel
import com.example.jetlagged.heartrate.HeartRateCard
import com.example.jetlagged.sleep.JetLaggedHeader
import com.example.jetlagged.sleep.JetLaggedSleepGraphCard
import com.example.jetlagged.ui.theme.JetLaggedTheme
import com.example.jetlagged.ui.util.MultiDevicePreview

@OptIn(ExperimentalLayoutApi::class)
Expand All @@ -58,12 +59,16 @@ fun JetLaggedScreen(
) {
Column(
modifier = modifier
.background(Color.White)
.fillMaxSize()
.verticalScroll(rememberScrollState())
.background(Color.White)
.background(MaterialTheme.colorScheme.background)
) {
Column(modifier = Modifier.movingStripesBackground()) {
Column(
modifier = Modifier.movingStripesBackground(
stripeColor = JetLaggedTheme.extraColors.header,
backgroundColor = MaterialTheme.colorScheme.background,
)
) {
JetLaggedHeader(
modifier = Modifier.fillMaxWidth(),
onDrawerClicked = onDrawerClicked
Expand Down Expand Up @@ -96,7 +101,8 @@ fun JetLaggedScreen(
if (windowSizeClass == WindowWidthSizeClass.Compact) {
WellnessCard(
wellnessData = uiState.value.wellnessData,
modifier = Modifier.widthIn(max = 400.dp)
modifier = Modifier
.widthIn(max = 400.dp)
.heightIn(min = 200.dp)
)
HeartRateCard(
Expand All @@ -107,7 +113,8 @@ fun JetLaggedScreen(
FlowColumn {
WellnessCard(
wellnessData = uiState.value.wellnessData,
modifier = Modifier.widthIn(max = 400.dp)
modifier = Modifier
.widthIn(max = 400.dp)
.heightIn(min = 200.dp)
)
HeartRateCard(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,9 @@

package com.example.jetlagged

import android.graphics.Color
import android.content.res.Configuration
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.SystemBarStyle
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.compose.material3.windowsizeclass.ExperimentalMaterial3WindowSizeClassApi
Expand All @@ -30,9 +29,7 @@ class MainActivity : ComponentActivity() {

@OptIn(ExperimentalMaterial3WindowSizeClassApi::class)
override fun onCreate(savedInstanceState: Bundle?) {
enableEdgeToEdge(
statusBarStyle = SystemBarStyle.light(Color.TRANSPARENT, Color.TRANSPARENT),
)
enableEdgeToEdge()
super.onCreate(savedInstanceState)
setContent {
val windowSizeClass = calculateWindowSizeClass(this)
Expand All @@ -41,4 +38,10 @@ class MainActivity : ComponentActivity() {
}
}
}

override fun onConfigurationChanged(newConfig: Configuration) {
super.onConfigurationChanged(newConfig)
// Changing the theme doesn't recreate the activity, so set the E2E values again
enableEdgeToEdge()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,43 +22,71 @@ import androidx.annotation.RequiresApi
import androidx.compose.animation.core.withInfiniteAnimationFrameMillis
import androidx.compose.runtime.mutableFloatStateOf
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.ShaderBrush
import androidx.compose.ui.graphics.drawscope.ContentDrawScope
import androidx.compose.ui.node.DrawModifierNode
import androidx.compose.ui.node.ModifierNodeElement
import com.example.jetlagged.ui.theme.Yellow
import kotlinx.coroutines.launch
import org.intellij.lang.annotations.Language

/**
* Background modifier that displays a custom shader for Android T and above and a linear gradient
* for older versions of Android
*/
fun Modifier.solarFlareShaderBackground(): Modifier =
fun Modifier.solarFlareShaderBackground(
baseColor: Color,
backgroundColor: Color,
): Modifier =
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
this.then(SolarFlareShaderBackgroundElement)
this.then(SolarFlareShaderBackgroundElement(baseColor, backgroundColor))
} else {
this.then(Modifier.simpleGradient())
}

@RequiresApi(Build.VERSION_CODES.TIRAMISU)
private data object SolarFlareShaderBackgroundElement :
private data class SolarFlareShaderBackgroundElement(
val baseColor: Color,
val backgroundColor: Color,
) :
ModifierNodeElement<SolarFlairShaderBackgroundNode>() {
override fun create() = SolarFlairShaderBackgroundNode()
override fun create() = SolarFlairShaderBackgroundNode(baseColor, backgroundColor)
override fun update(node: SolarFlairShaderBackgroundNode) {
node.updateColors(baseColor, backgroundColor)
}
}

@RequiresApi(Build.VERSION_CODES.TIRAMISU)
private class SolarFlairShaderBackgroundNode : DrawModifierNode, Modifier.Node() {
private class SolarFlairShaderBackgroundNode(
baseColor: Color,
backgroundColor: Color,
) : DrawModifierNode, Modifier.Node() {
private val shader = RuntimeShader(SHADER)
private val shaderBrush = ShaderBrush(shader)
private val time = mutableFloatStateOf(0f)

init {
updateColors(baseColor, backgroundColor)
}

fun updateColors(baseColor: Color, backgroundColor: Color) {
shader.setColorUniform(
"baseColor",
android.graphics.Color.valueOf(Yellow.red, Yellow.green, Yellow.blue, Yellow.alpha)
android.graphics.Color.valueOf(
baseColor.red,
baseColor.green,
baseColor.blue,
baseColor.alpha
)
)
shader.setColorUniform(
"backgroundColor",
android.graphics.Color.valueOf(
backgroundColor.red,
backgroundColor.green,
backgroundColor.blue,
backgroundColor.alpha
)
)
}

Expand Down Expand Up @@ -86,6 +114,7 @@ private val SHADER = """
uniform float2 resolution;
uniform float time;
layout(color) uniform half4 baseColor;
layout(color) uniform half4 backgroundColor;
const int ITERATIONS = 2;
const float INTENSITY = 100.0;
Expand Down Expand Up @@ -119,9 +148,16 @@ private val SHADER = """
colorPart = 1.6 - (colorPart / float(ITERATIONS));
// Fade out the bottom on a curve
float alpha = 1.0 - (uv.y * uv.y);
float mixRatio = 1.0 - (uv.y * uv.y);
// Mix calculated color with the incoming base color
float4 color = float4(colorPart * baseColor.r, colorPart * baseColor.g, colorPart * baseColor.b, alpha);
float4 color = float4(colorPart * baseColor.r, colorPart * baseColor.g, colorPart * baseColor.b, 1.0);
// Mix color with the background
color = float4(
mix(backgroundColor.r, color.r, mixRatio),
mix(backgroundColor.g, color.g, mixRatio),
mix(backgroundColor.b, color.b, mixRatio),
1.0
);
// Keep all channels within valid bounds of 0.0 and 1.0
return clamp(color, 0.0, 1.0);
}
Expand Down
Loading

0 comments on commit 405b060

Please sign in to comment.