Jetpack Compose Getting Light vs Dark Mode from MaterialTheme Using Material 3 - android

With Jetpack Compose, using Material 2, you can see if a theme is in light mode easily with the following
val light = MaterialTheme.colors.isLight
Using Material 3, I don't see this ability. Is there a way to do this with a Material 3 theme?

Found a solution. Color in Compose has a built in method luminance that returns the relative luminance as a float between 0 and 1. I wrote an extension function for ColorScheme that returns true if the luminance of the background is greater than 0.5.
#Composable
fun ColorScheme.isLight() = this.background.luminance() > 0.5
Using it as:
val isLight = MaterialTheme.colorScheme.isLight()
Now whether the system is in dark mode doesn't matter, only the theme.

In JetpackCompose you can use this method that returns if the light / dark mode is enabled or not
isSystemInDarkTheme()
And then in your code
if (isSystemInDarkTheme()){} \\Dark mode enabled
else {} //Light mode enabled

Related

How to define shape for Button in Jetpack compose when using Material design 3

I am using Material design 3 with Jetpack compose.
By default the Shape for Button has a corner radius of type full which is 20dp. As per the documentation.
https://m3.material.io/components/buttons/overview
If I look at the shape documentation, I can see following shapes in token section
However when I provide the Shapes object to the Theme for the app. I don't have an option to customise Full Shape.
Here is my code
Shapes(
extraSmall = RoundedCornerShape(4.dp),
small = RoundedCornerShape(8.dp),
medium = RoundedCornerShape(12.dp),
large = RoundedCornerShape(16.dp),
extraLarge = RoundedCornerShape(8.dp)
)
What I am trying to achieve:
I don't want to specify shape explicitly to the each button or create a composable for button and then reuse it everywhere.
I want to apply it once and not need to specify it explicitly as other components
As you mentioned, M3 FilledButton's container shape is by default ShapeKeyTokens.CornerFull, as you can see in FilledButtonTokens. This token is then converted to Shape in Shapes.fromToken function:
internal fun Shapes.fromToken(value: ShapeKeyTokens): Shape {
return when (value) {
ShapeKeyTokens.CornerExtraLarge -> extraLarge
ShapeKeyTokens.CornerExtraLargeTop -> extraLarge.top()
ShapeKeyTokens.CornerExtraSmall -> extraSmall
ShapeKeyTokens.CornerExtraSmallTop -> extraSmall.top()
ShapeKeyTokens.CornerFull -> CircleShape
ShapeKeyTokens.CornerLarge -> large
ShapeKeyTokens.CornerLargeEnd -> large.end()
ShapeKeyTokens.CornerLargeTop -> large.top()
ShapeKeyTokens.CornerMedium -> medium
ShapeKeyTokens.CornerNone -> RectangleShape
ShapeKeyTokens.CornerSmall -> small
}
}
As you can see, ShapeKeyTokens.CornerFull, unlike most others, isn't converted to some Shape from the theme, but simply to CircleShape. This means that it's not possible to do what you want. You will either have to pass your shape to each button or create custom button composable.

Create color value dynamically

My app shows buttons depending on logged user.
Details and Conditions of the app:
The number of buttons that will be shown is unknown (could be 1, 20, 100, etc.)
A different color must be set for each button
I want some control over color values, as text is always white, so text must always be readable
How can I create a dynamic color value under these conditions?
First you need to decide how "readable" the text should be. WCAG 2.1 is a common standard for accessibility requirements, and its minimum contrast requirement is 4.5:1. (The spec definition is here, or for a lighter overview with some nice examples there's this.)
That amount of contrast will guarantee your text can be read by people with "moderately low vision". 3:1 is recommended for "standard text and standard vision", but I'd always recommend going with the accessible ratio - especially since you're using white and random colours, which will vary in readability quite a bit!
WCAG 2.1 also allows for that 3:1 ratio for large-scale text, which is 18pt or 14pt bold. That works out to about 40dp for regular text and 31dp for bold. Depends on the font too, and also since you often use sp instead the user can control how big the fonts are, so it complicates things. But basically, big text = lower contrast requirements
Now you have your contrast level, you can check whether your colour combo meets it or not. There's a nice tool in ColorUtils that does this for you - it uses the WCAG formula for calculating contrast:
fun meetsMinContrast(#ColorInt foreground: Int, #ColorInt background: Int): Boolean {
val minContrast = 4.5
val actual = ColorUtils.calculateContrast(foreground, background)
return actual >= minContrast
}
As for actually generating colours, I don't know the "smart" way to do it, if there is one - you could possibly generate a colour space of valid colours when paired with white, and pick from that, but I don't really know anything about it - maybe someone else can chime in with a better solution!
So for a purely naive random approach:
val colours = generateSequence {
Color.valueOf(
Random.nextInt(0, 255),
Random.nextInt(0, 255),
Random.nextInt(0, 255)
)
}
val accessibleBackgrounds = colours.filter { background ->
meetsMinContrast(Color.WHITE, background)
}
and then you have a stream of valid, random colours you can set on your buttons.
If you don't like the "just keep generating randomly until you hit one that works" approach (which is pretty hacky and could be slow if you're unlucky), you could work with HSV instead:
fun getAccessibleBackground(): Color {
val hsv = floatArrayOf(
Random.nextFloat() * 360f,
Random.nextFloat(),
Random.nextFloat()
)
var colour: Color
while(true) {
colour = Color.HSVtoColor(hsv)
if (meetsMinContrast(Color.WHITE, colour)) return colour
// darken the colour a bit (subtract 1% value) and try again
hsv[2] -= 0.01f
}
}
(I'd be more explicit about error checking and providing a fallback there, especially if you made it work with a foreground colour parameter, but that should always return a valid colour before you need to worry about subtracting from value too much - it's just a simple example)

Surface at +(...)% colors in jetpack compose

In official Material Design 3 resources (e.g. the Figma design kit), there have been many references to colors called "Surface at +x". These colors are the surface color mixed with x% of the primary color.
Now my question:
How can you implement the "Surface at +x" colors in Jetpack Compose? There is no documentation and no property on the MaterialTheme.colorScheme object.
Figma Design Kit reference:
In case anyone need to get it in a non-compose code, use SurfaceColors enums:
int colorSurface1 = SurfaceColors.SURFACE_2.getColor(context);
Documentation can be found here
Update September 2022
With Material 3, if for some reason you need the elevate color surface but you can't use the Surface, now you can use directly:
MaterialTheme.colorScheme.surfaceColorAtElevation(4.dp)
Surface uses MaterialTheme.colorScheme.surface by default, they also have a new tonalElevation property which you can read about here.
The gist of it is that increasing the tonal elevation changes the color automatically, try it yourself:
Surface(tonalElevation = 5.dp) {
// content
}

Does Jetpack Compose have the tools to create custom OverscrollEffect?

Is there a way to create OverscrollEffect in jetpack compose?
Something like this:
The overscroll effect can be controlled by the LocalOverscrollConfiguration, which currently has the following parameters:
glowColor - color for the glow effect, if the platform effect is a glow effect, otherwise ignored.
forceShowAlways - force show overscroll even if content doesn't scroll (is smaller than a scrollable container itself)
drawPadding - the amount of padding to apply from scrollable container bounds to effect before drawing it
CompositionLocalProvider(
LocalOverscrollConfiguration provides OverScrollConfiguration(
glowColor = Color.Gray,
forceShowAlways = false,
drawPadding = PaddingValues()
)
) {
// effected views
}
If you think there should be more parameters, you can let the maintainers know by creating a feature request.
p.s. in 1.2.0-rc01 LocalOverScrollConfiguration has being renamed to LocalOverscrollConfiguration

Does jetpack compose use drawable-night folder?

We have a View based Android app with some drawables in res/drawable folder, and their counterpart for night mode in res/drawable-night folder
When using legacy views, referencing a drawable R.drawable.foo from a XML layout file, the system would pick the drawable from either res/drawable or res/drawable-night folders depending on whether we are in day or night mode.
When using jetpack compose, we reference the drawable in an Image composable like this:
Image(painter = painterResource(R.drawable.foo))
However, this always pick the drawable from res/drawable folder, ignoring day / night mode.
We could do something like this to select the right drawable, but we would need to test the night mode (isSystemInDarkTheme()) within all composables that uses drawables depending on nigh mode:
Image(painter = painterResource(id = if (isSystemInDarkTheme()) R.drawable.foo_dark else R.drawable.foo_light))
Is there a way in compose to ensure that the drawable from day or night mode are picked correctly, and transpartently, as in legacy view system?
Maybe Compose was updated since the other answers were posted, but I can confirm that in a simple app using only Compose and two drawables with the same name in drawable and drawable-night folders, the app is picking up the dark one, if the phone is set to dark mode.
That's also without a composable theme defined, so this simple code does the job:
#Preview
#Preview(uiMode = UI_MODE_NIGHT_YES)
#Composable
fun ImagePreview() {
Image(
painter = painterResource(id = R.drawable.my_icon),
contentDescription = null
)
}
Note that you can also see this in a Preview - having two #Preview annotations produces two previews in Android Studio, and specifying that you want to see your composable in dark mode is also possible!
Seeing how the dark & light palette is currently implemented in the theming codelab I'd go with creating my own somewhat similar abstraction:
class LightDrawables: Drawables
class DarkDrawables: Drawables
fun getDrawable(darkTheme: Boolean = isSystemInDarkTheme(), #DrawableRes drawableRes: Int) = if(darkTheme) DarkDrawables.xy else LightDrawables.xy
As of now, "NO".
How to use /drawable-night?
Google provided support for some basic compose utils through Accompanist which does not come with standard Jetpack Compose. For example, PagerLayout, Swipe Refresh and many more.
One of those utils is Drawable Painter and that is something you can use.
After you add dependency, you can use this util using:
#Composable
fun DrawDrawable() {
val drawable = AppCompatResources.getDrawable(LocalContext.current, R.drawable.foo)
Image(
painter = rememberDrawablePainter(drawable = drawable),
contentDescription = "content description",
)
}
Document Source
Note:
Don't expect dark variant of the drawable to show up in #Preview. But it will be loaded at runtime.

Categories

Resources