https://kotlinlang.org logo
Title
d

Dirk Hoffmann

05/27/2021, 2:56 PM
I am trying to let Typography TextStyles (for body, h1, subtitle, ...) *color*s to depend on darkMode light Mode with switching while App is running. But on switching the TextStyle colors are not honoured ... what's the way to achieve this?
Window {
        DesktopTheme {
            MyCustomDeskMaterialTheme {
                MinRootContent()
                ...
@Composable
fun MyCustomDeskMaterialTheme(content: @Composable () -> Unit) {
    MaterialTheme(
        colors = if (isDarkTheme()) DarkColorPalette() else LightColorPalette(),
        typography = if (isDarkTheme()) DarkTypography() else LightTypography()
    ) {
        ...

fun DarkTypography() : Typography {
    val orig = Typography()
    return orig.copy(
            subtitle1 = orig.subtitle1.copy(color = DarkColorPalette().onBackground),
            subtitle2 = orig.subtitle2.copy(color = DarkColorPalette().onBackground),
            body1 = orig.body1.copy(color = DarkColorPalette().onSurface),
            body2 = orig.body2.copy(color = DarkColorPalette().onSurface))
}

fun LightTypography() : Typography {
    val orig = Typography()
    return orig.copy(
            subtitle1 = orig.subtitle1.copy(color = LightColorPalette().onBackground),
            subtitle2 = orig.subtitle2.copy(color = LightColorPalette().onBackground),
            body1 = orig.body1.copy(color = LightColorPalette().onSurface),
            body2 = orig.body2.copy(color = LightColorPalette().onSurface))
}

fun DarkColorPalette() = darkColors(
    primary = Solarized.Base1,
    ...
when accessing it with e.g.
MaterialTheme.typography.subtitle2
font has always the color of the mode app started with
z

Zach Klippenstein (he/him) [MOD]

05/27/2021, 3:34 PM
If you look at the
Text
source code:
val textColor = color.takeOrElse {
        style.color.takeOrElse {
            LocalContentColor.current.copy(alpha = LocalContentAlpha.current)
        }
    }
    val mergedStyle = style.merge(
        TextStyle(
            color = textColor,
            …
        )
    )
You can see the actual logic used to choose the color. If you have a composable that’s passing an explicit color in, that will always be used. Otherwise the `LocalTextStyle`’s color will be used – are you sure that ambient is being set correctly?
d

Dirk Hoffmann

05/27/2021, 4:13 PM
so how would I set/change the LocalTextStyle any time the user switches between dark and light?
z

Zach Klippenstein (he/him) [MOD]

05/27/2021, 4:15 PM
I don’t remember what all sets
LocalTextStyle
– you can do a “Find Usages” in android studio to find out.
c

Chris Sinco [G]

05/27/2021, 4:59 PM
I believe
LocalContentColor
tries to use
onSurface
or
onBackground
from MaterialTheme depending on where the
Text
composable sits in a hierarchy. If you change those two keys in the color palettes passed to your app theme, it should switch automatically without needing conditionals at the call site of your Text composables.
@Louis Pullen-Freilich [G] can confirm though with more details
1
d

Dirk Hoffmann

05/27/2021, 6:39 PM
well, ideally I not only want to change the
LocalTextStyle
when my mutableStateOf<Boolean> for `dark/light`mode change, but I'd like to be able to set some/all of the Typography TextStyles (body1, body2, h1, h2, ...) to values the `Outlined|...|TextField(... styledText: TextStyle)`throughout my UI are using ... (
MaterialTheme.typography.(body1|body2|subtitle1|subtitile2|h1|h2|h3|error|onError|...
)
l

Louis Pullen-Freilich [G]

05/27/2021, 7:12 PM
LocalTextStyle
is provided by different components, and they just read from
MaterialTheme.typography
. So if you change
MaterialTheme.typography
, then any components that provide a value for
LocalTextStyle
will also update with the new styles
MaterialTheme
for example provides a default of
body1
, so if you change between light and dark,
MaterialTheme
will provide the new
body1
defined for your dark set of typography
d

Dirk Hoffmann

05/27/2021, 7:15 PM
so what's wrong with my code?
l

Louis Pullen-Freilich [G]

05/27/2021, 7:20 PM
As mentioned by others, setting color directly on a
TextStyle
is not what you want here - colors should be provided by the components outside the
Text
itself
I.e if your text is inside a
Surface()
, by default it will automatically use
onSurface
as the color
If you really want to do it this way, then you would need to explicitly set the color of
Text
to be the color from the style, since it will use
LocalContentColor
by default
It does look like this color should be used though, since colors from the style are used before
LocalContentColor
- what exactly is not working for you here?
d

Dirk Hoffmann

05/28/2021, 8:53 AM
let me explain deeper: let's say I have a custom (editable) @Composable with a TextField for section headings, that is using, e.g.:
...
TextField(textStyle = MaterialTheme.typography.section1)
now with my switchable dark/light mode State (am trying to do it as much solarized as possible, so the colors of default typography TextStyles do not fit anymore, as they are sometimes using different primary/surface/background colors in light vs dark mode), I wanna achieve that every time a TextField has e.g.
section1
text style, it has a) a custom color and b) this color should be a special one that is depending on current light or dark mode. What I CAN do is e.g.
TextField(textStyle = if(ADState.ADTheme.isDarkTheme()) DarkTypography().subtitle1 else LightTypography().subtitle1)
But I'd very much prefer to just say
TextField(textStyle = MaterialTheme.typography.section1)
and having the color of the textStyle defined in my Theme Typography() BUT it should be a dark or light one, depending on if I am currently in light or dark mode. This is all working fine, if I start the App in either light or dark mode. BUT if I change from light to dark or vice versa at runtime! MaterialTheme.typography.section1 still gives me the textStyle color of the Dark|LightTypography it has been on startup of the App (if I just reference
MaterialTheme.typography.section1
. So in my Code at the beginning of this thread, switching dark light, does NOT honour my
DarkTypography()
LightTypography()
settings when I dynamically change between dark and light modes at runtime
l

Louis Pullen-Freilich [G]

05/28/2021, 1:41 PM
Hard to say, nothing looks obviously wrong from the code you shared earlier. If you have a small reproducible sample, you can file a bug
d

Dirk Hoffmann

05/28/2021, 2:16 PM
ok, I found out, that id DOES WORK correctly, but I have some strange side effects in my App ... sorry for the inconveniences ...
hmm, ok, interessting nevertheless: I have this Component:
@ExperimentalAnimationApi
@Composable
fun QuickSearchAutoComplete(items: List<Person>) {
    val (text, setText) = remember { mutableStateOf(TextFieldValue("")) }

    TextField("OutOfBox", onValueChange = {}, textStyle = MaterialTheme.typography.subtitle1)
    AutoCompleteDropdown(
        items,
        itemContent = { person: Person ->
            PersonDropdownAutoCompleteItem(person)
        },
    ) { // this: AutoCompleteStateScope<Person>
        // /** things that interact with the `AutoCompleteState` */
        // interface AutoCompleteStateScope<T : AutoCompleteEntity> : AutoCompleteStateDesignScope, IsVisibleState {
        //     fun filter(query: String)
        //     fun onItemSelected(block:  (T) -> Unit = {}) }

        onItemSelected { person ->
            setText(TextFieldValue(person.name, selection = TextRange(person.name.length)))
            filter(person.name)
            visible = false
        }

        TextField(
            value = text,
            onValueChange = { setText(it) ; filter(it.text) },
            modifier = Modifier
                .onGloballyPositioned { textFieldRect.value = it.rectInWindow() }
                .onPreviewKeyEvent {
                    when (it.key) {
                        Key.Enter -> {
                            hide()
                            true // true = stop propagation KeyEvent upwards
                        }
                        Key.Escape -> {
                            hide()
                            true
                        }

                        else -> {
                            if (text.text.isBlank()) {
                                hide()
                            } else {
                                show()
                            }
                            false // false = propagate upwards (to textField)
                        }
                    }
                }
                .onFocusChanged { visible = it.isFocused },
            textStyle = MaterialTheme.typography.subtitle1,
            label = { Text("quick search:") },
            placeholder = { Text("e.g. sign contract") },
            trailingIcon = {
                IconButton(
                    onClick = { println("TODO trailing quicksearch Icon clicked") ; setText(TextFieldValue("")) }
                ) {
                    Icon(Icons.TwoTone.Clear, contentDescription = "Top Search")
                }
            },
            singleLine = true
        )
    }
}
I have the IDENTICAL Component call
QuickSearchAutoComplete(FakeData.PERSONS)
a) in the AppTopBar of Scaffold b) in a column in AppTopBar of Scaffold the given textStyle is honored on dynamic dark/light change .. in a column in "normal" content it is not (see screenshots)
ah, ok, if I simply add a
val dummy = ADState.ADTheme.darkTheme.value
to my component THEN it is dependant on dark/light boolean state and THEN it will honour the new typography, otherwise the
remember { mutableStateOf(TextFieldValue("")) }
I pass into the TextField will "remember" also the typgraphy textStyle it "once had"
but question remains, why it is honouring the typography textStyle inside the TopAppBar without explicitly depending on dark/light State, but in my custom
QuickSearchAutoComplete
(providing an AutoCompleteStateScope) it does not honour it without that explicit depencency