androidandroid-jetpack-composematerial-components-androidandroid-jetpack-compose-material3android-dark-theme

How to declare colors based on dark theme enabled or not, in compose with material 3


in my app, I have some custom colors and I use this method to know if the dark theme is enabled but there are some colors I want to make global based on the dark theme to use it from any composable fun in the project

This example of my colors topAppBarBackgroundColor and topAppBarContentColor declared in each composable and I am looking for a solution to make it global in Color.kt file to avoid repeated codes

@Composable
fun ListAppBar(
    sharedViewModel: SharedViewModel,
    searchAppbarState: SearchAppbarState,
    searchTextState: String
) {

    when (searchAppbarState) {
        SearchAppbarState.CLOSED -> {
            DefaultListAppBar(
                onSearchClicked = {
                    sharedViewModel.searchAppbarState.value = SearchAppbarState.OPENED
                },
                onSortClicked = {},
                onDeleteClicked = {}
            )
        }

        else -> {
            SearchAppBar(
                text = searchTextState,
                onTextChange = {newText->
                    sharedViewModel.searchTextState.value = newText
                },
                onCloseClicked = {
                    sharedViewModel.searchAppbarState.value = SearchAppbarState.CLOSED
                    sharedViewModel.searchTextState.value = ""
                }) {

            }
        }
    }


}

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun DefaultListAppBar(
    onSearchClicked: () -> Unit,
    onSortClicked: (Priority) -> Unit,
    onDeleteClicked: () -> Unit
) {


    val isLight = rememberSaveable() {
        mutableStateOf(true)
    }

    isLight.value = MaterialTheme.colorScheme.isLight()


    val topAppBarContentColor: Color = if (isLight.value)
        Color.White else Color.LightGray

    val topAppBarBackgroundColor: Color = if (isLight.value)
        Purple500 else Color.Black

    TopAppBar(title = {
        Text(
            text = "Tasks",
            color = topAppBarContentColor
        )
    },
        colors = TopAppBarDefaults.smallTopAppBarColors(
            containerColor = topAppBarBackgroundColor
        ),

        actions = {
            ListAppBarActions(
                onSearchClicked = onSearchClicked,
                onSortClicked = onSortClicked,
                onDeleteClicked = {}
            )
        }

    )
}

@Composable
fun ListAppBarActions(
    onSearchClicked: () -> Unit,
    onSortClicked: (Priority) -> Unit,
    onDeleteClicked: () -> Unit
) {

    SearchAction(onSearchClicked = onSearchClicked)

    SortAction(onSortClicked = onSortClicked)

    DeleteAllAction(onDeleteClicked = onDeleteClicked)
}

@Composable
fun SearchAction(onSearchClicked: () -> Unit) {

    val isLight = rememberSaveable() {
        mutableStateOf(true)
    }

    isLight.value = MaterialTheme.colorScheme.isLight()


    val topAppBarContentColor: Color = if (isLight.value)
        Color.White else Color.LightGray

    val topAppBarBackgroundColor: Color = if (isLight.value)
    Purple500 else Color.Black

    IconButton(
        onClick = onSearchClicked
    ) {
        Icon(
            imageVector = Icons.Filled.Search,
            contentDescription = stringResource(R.string.search_tasks),
            tint = topAppBarContentColor
        )
    }
}

@Composable
fun SortAction(onSortClicked: (Priority) -> Unit) {

    val isLight = rememberSaveable() {
        mutableStateOf(true)
    }

    isLight.value = MaterialTheme.colorScheme.isLight()


    val topAppBarContentColor: Color = if (isLight.value)
        Color.White else Color.LightGray

    var expanded by remember() {
        mutableStateOf(false)
    }

    IconButton(onClick = { expanded = true }) {
        Icon(
            painter = painterResource(id = R.drawable.baseline_filter_list_24),
            contentDescription = stringResource(R.string.sort_tasks),
            tint = topAppBarContentColor
        )

        DropdownMenu(expanded = expanded, onDismissRequest = { expanded = false }) {
            DropdownMenuItem(text = { PriorityItem(priority = Priority.LOW) },
                onClick = {
                    expanded = false
                    onSortClicked(Priority.LOW)
                })

            DropdownMenuItem(text = { PriorityItem(priority = Priority.HIGH) },
                onClick = {
                    expanded = false
                    onSortClicked(Priority.HIGH)
                })

            DropdownMenuItem(text = { PriorityItem(priority = Priority.NONE) },
                onClick = {
                    expanded = false
                    onSortClicked(Priority.NONE)
                })
        }
    }
}


@Composable
fun DeleteAllAction(onDeleteClicked: () -> Unit) {
    val isLight = rememberSaveable() {
        mutableStateOf(true)
    }

    isLight.value = MaterialTheme.colorScheme.isLight()


    val topAppBarContentColor: Color = if (isLight.value)
        Color.White else Color.LightGray

    var expanded by remember() {
        mutableStateOf(false)
    }

    IconButton(onClick = { expanded = true }) {
        Icon(
            painter = painterResource(id = R.drawable.baseline_more_vert_24),
            contentDescription = stringResource(R.string.delete_all),
            tint = topAppBarContentColor
        )

        DropdownMenu(expanded = expanded, onDismissRequest = { expanded = false }) {
            DropdownMenuItem(
                text = {

                    Text(
                        modifier = Modifier.padding(start = LARGE_PADDING),
                        text = stringResource(R.string.delete_all),
                        style = Typography.headlineSmall
                    )
                },
                onClick = {
                    expanded = false
                    onDeleteClicked()
                }
            )
        }

    }
}


@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun SearchAppBar(
    text: String,
    onTextChange: (String) -> Unit,
    onCloseClicked: () -> Unit,
    onSearchClicked: (String) -> Unit
) {


    val isLight = rememberSaveable {
        mutableStateOf(true)
    }

    isLight.value = MaterialTheme.colorScheme.isLight()


    val topAppBarBackgroundColor: Color = if (isLight.value)
        Purple500 else Color.Black

    val topAppBarContentColor: Color = if (isLight.value)
        Color.White else Color.LightGray


    Surface(
        modifier = Modifier
            .fillMaxWidth()
            .height(56.dp),
        color = topAppBarBackgroundColor

    ) {
        TextField(
            value = text,
            onValueChange = {
                onTextChange(it)
            },
            modifier = Modifier
                .fillMaxWidth()
                .background(Color.Transparent),
            placeholder = {
                Text(
                    text = "Search here...",
                    modifier = Modifier.alpha(0.3F),
                    color = Color.White,
                )

            },
            textStyle = TextStyle(
                fontSize = MaterialTheme.typography.titleMedium.fontSize
            ),
            singleLine = true,
            leadingIcon = {
                IconButton(
                    onClick = {},
                    modifier = Modifier.alpha(0.5F)
                ) {

                    Icon(
                        imageVector = Icons.Default.Search,
                        contentDescription = stringResource(R.string.search_icon),
                        tint = Color.White
                    )

                }
            },
            trailingIcon = {
                IconButton(
                    onClick = {
                        if (text.isNotEmpty()) {
                            onTextChange("")
                        } else {
                            onCloseClicked()
                        }
                    }
                ) {

                    Icon(
                        imageVector = Icons.Default.Close,
                        contentDescription = stringResource(R.string.close_icon),
                        tint = Color.White
                    )

                }
            },
            keyboardOptions = KeyboardOptions(
                imeAction = ImeAction.Search
            ),
            keyboardActions = KeyboardActions(
                onSearch = {
                    onSearchClicked(text)
                }
            ),
            colors = TextFieldDefaults.textFieldColors(
                textColor = Color.White,
                containerColor = Color.Transparent,
                cursorColor = topAppBarContentColor,
                focusedIndicatorColor = Color.Transparent,
                unfocusedIndicatorColor = Color.Transparent,
            )
        )
    }

}


Solution

  • You can make pretty much the same thing as with isLight() extension function - make it an extension function/property of ColorScheme:

    fun ColorScheme.isLight() = this.background.luminance() > 0.5
    
    val ColorScheme.topAppBarBackgroundColor: Color get() {
        return if (isLight()) Purple500 else Color.Black
    }
    
    val ColorScheme.topAppBarContentColor: Color get() {
        return if (isLight()) Color.White else Color.LightGray
    }
    

    you can then use it just like this:

    @Composable
    fun DefaultListAppBar() {
        val contentColor = MaterialTheme.colorScheme.topAppBarContentColor
    }
    

    On a side note, isLight() doesn't have to be a @Composable function (and shouldn't be then), same as the newly declared topAppBarBackgroundColor and topAppBarContentColor.
    Also, it doesn't make any sense to create that isLight MutableState you did:

    val isLight = rememberSaveable() {
        mutableStateOf(true)
    }
    
    isLight.value = MaterialTheme.colorScheme.isLight()
    

    you create MutableState here but then you update it in every recomposition, so it is equivalent to simply doing val isLight = MaterialTheme.colorScheme.isLight()