Implement Now in Android Material 3 components and icons

Bug: 216019424

Change-Id: I3f23b07dbaa5a834bb05f70e8b68f31ae66d6722
pull/2/head
Nick Rout 3 years ago
parent 5b54858642
commit a53066769d

@ -0,0 +1,449 @@
/*
* Copyright 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.samples.apps.nowinandroid.core.ui.component
import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.RowScope
import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.sizeIn
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonColors
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.ProvideTextStyle
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
/**
* Now in Android filled button with generic content slot. Wraps Material 3 [Button].
*
* @param onClick Will be called when the user clicks the button.
* @param modifier Modifier to be applied to the button.
* @param enabled Controls the enabled state of the button. When `false`, this button will not be
* clickable and will appear disabled to accessibility services.
* @param small Whether or not the size of the button should be small or regular.
* @param colors [ButtonColors] that will be used to resolve the container and content color for
* this button in different states. See [NiaButtonDefaults.filledButtonColors].
* @param contentPadding The spacing values to apply internally between the container and the
* content. See [NiaButtonDefaults.buttonContentPadding].
* @param content The button content.
*/
@Composable
fun NiaFilledButton(
onClick: () -> Unit,
modifier: Modifier = Modifier,
enabled: Boolean = true,
small: Boolean = false,
colors: ButtonColors = NiaButtonDefaults.filledButtonColors(),
contentPadding: PaddingValues = NiaButtonDefaults.buttonContentPadding(small = small),
content: @Composable RowScope.() -> Unit
) {
Button(
onClick = onClick,
modifier = if (small) {
Modifier
.heightIn(min = NiaButtonDefaults.SmallButtonHeight)
.then(modifier)
} else {
modifier
},
enabled = enabled,
colors = colors,
contentPadding = contentPadding,
content = {
ProvideTextStyle(value = MaterialTheme.typography.labelSmall) {
content()
}
}
)
}
/**
* Now in Android filled button with text and icon content slots.
*
* @param onClick Will be called when the user clicks the button.
* @param modifier Modifier to be applied to the button.
* @param enabled Controls the enabled state of the button. When `false`, this button will not be
* clickable and will appear disabled to accessibility services.
* @param small Whether or not the size of the button should be small or regular.
* @param colors [ButtonColors] that will be used to resolve the container and content color for
* this button in different states. See [NiaButtonDefaults.filledButtonColors].
* @param text The button text label content.
* @param leadingIcon The button leading icon content. Pass `null` here for no leading icon.
* @param trailingIcon The button trailing icon content. Pass `null` here for no trailing icon.
*/
@Composable
fun NiaFilledButton(
onClick: () -> Unit,
modifier: Modifier = Modifier,
enabled: Boolean = true,
small: Boolean = false,
colors: ButtonColors = NiaButtonDefaults.filledButtonColors(),
text: @Composable () -> Unit,
leadingIcon: @Composable (() -> Unit)? = null,
trailingIcon: @Composable (() -> Unit)? = null
) {
NiaFilledButton(
onClick = onClick,
modifier = modifier,
enabled = enabled,
small = small,
colors = colors,
contentPadding = NiaButtonDefaults.buttonContentPadding(
small = small,
leadingIcon = leadingIcon != null,
trailingIcon = trailingIcon != null
)
) {
NiaButtonContent(
text = text,
leadingIcon = leadingIcon,
trailingIcon = trailingIcon
)
}
}
/**
* Now in Android outlined button with generic content slot. Wraps Material 3 [OutlinedButton].
*
* @param onClick Will be called when the user clicks the button.
* @param modifier Modifier to be applied to the button.
* @param enabled Controls the enabled state of the button. When `false`, this button will not be
* clickable and will appear disabled to accessibility services.
* @param small Whether or not the size of the button should be small or regular.
* @param border Border to draw around the button. Pass `null` here for no border.
* @param colors [ButtonColors] that will be used to resolve the container and content color for
* this button in different states. See [NiaButtonDefaults.outlinedButtonColors].
* @param contentPadding The spacing values to apply internally between the container and the
* content. See [NiaButtonDefaults.buttonContentPadding].
* @param content The button content.
*/
@Composable
fun NiaOutlinedButton(
onClick: () -> Unit,
modifier: Modifier = Modifier,
enabled: Boolean = true,
small: Boolean = false,
border: BorderStroke? = NiaButtonDefaults.outlinedButtonBorder(enabled = enabled),
colors: ButtonColors = NiaButtonDefaults.outlinedButtonColors(),
contentPadding: PaddingValues = NiaButtonDefaults.buttonContentPadding(small = small),
content: @Composable RowScope.() -> Unit
) {
OutlinedButton(
onClick = onClick,
modifier = if (small) {
Modifier
.heightIn(min = NiaButtonDefaults.SmallButtonHeight)
.then(modifier)
} else {
modifier
},
enabled = enabled,
border = border,
colors = colors,
contentPadding = contentPadding,
content = {
ProvideTextStyle(value = MaterialTheme.typography.labelSmall) {
content()
}
}
)
}
/**
* Now in Android outlined button with text and icon content slots.
*
* @param onClick Will be called when the user clicks the button.
* @param modifier Modifier to be applied to the button.
* @param enabled Controls the enabled state of the button. When `false`, this button will not be
* clickable and will appear disabled to accessibility services.
* @param small Whether or not the size of the button should be small or regular.
* @param border Border to draw around the button. Pass `null` here for no border.
* @param colors [ButtonColors] that will be used to resolve the container and content color for
* this button in different states. See [NiaButtonDefaults.outlinedButtonColors].
* @param text The button text label content.
* @param leadingIcon The button leading icon content. Pass `null` here for no leading icon.
* @param trailingIcon The button trailing icon content. Pass `null` here for no trailing icon.
*/
@Composable
fun NiaOutlinedButton(
onClick: () -> Unit,
modifier: Modifier = Modifier,
enabled: Boolean = true,
small: Boolean = false,
border: BorderStroke? = NiaButtonDefaults.outlinedButtonBorder(enabled = enabled),
colors: ButtonColors = NiaButtonDefaults.outlinedButtonColors(),
text: @Composable () -> Unit,
leadingIcon: @Composable (() -> Unit)? = null,
trailingIcon: @Composable (() -> Unit)? = null
) {
NiaOutlinedButton(
onClick = onClick,
modifier = modifier,
enabled = enabled,
small = small,
border = border,
colors = colors,
contentPadding = NiaButtonDefaults.buttonContentPadding(
small = small,
leadingIcon = leadingIcon != null,
trailingIcon = trailingIcon != null
)
) {
NiaButtonContent(
text = text,
leadingIcon = leadingIcon,
trailingIcon = trailingIcon
)
}
}
/**
* Now in Android text button with generic content slot. Wraps Material 3 [TextButton].
*
* @param onClick Will be called when the user clicks the button.
* @param modifier Modifier to be applied to the button.
* @param enabled Controls the enabled state of the button. When `false`, this button will not be
* clickable and will appear disabled to accessibility services.
* @param small Whether or not the size of the button should be small or regular.
* @param colors [ButtonColors] that will be used to resolve the container and content color for
* this button in different states. See [NiaButtonDefaults.textButtonColors].
* @param contentPadding The spacing values to apply internally between the container and the
* content. See [NiaButtonDefaults.buttonContentPadding].
* @param content The button content.
*/
@Composable
fun NiaTextButton(
onClick: () -> Unit,
modifier: Modifier = Modifier,
enabled: Boolean = true,
small: Boolean = false,
colors: ButtonColors = NiaButtonDefaults.textButtonColors(),
contentPadding: PaddingValues = NiaButtonDefaults.buttonContentPadding(small = small),
content: @Composable RowScope.() -> Unit
) {
TextButton(
onClick = onClick,
modifier = if (small) {
Modifier
.heightIn(min = NiaButtonDefaults.SmallButtonHeight)
.then(modifier)
} else {
modifier
},
enabled = enabled,
colors = colors,
contentPadding = contentPadding,
content = {
ProvideTextStyle(value = MaterialTheme.typography.labelSmall) {
content()
}
}
)
}
/**
* Now in Android text button with text and icon content slots.
*
* @param onClick Will be called when the user clicks the button.
* @param modifier Modifier to be applied to the button.
* @param enabled Controls the enabled state of the button. When `false`, this button will not be
* clickable and will appear disabled to accessibility services.
* @param small Whether or not the size of the button should be small or regular.
* @param colors [ButtonColors] that will be used to resolve the container and content color for
* this button in different states. See [NiaButtonDefaults.textButtonColors].
* @param text The button text label content.
* @param leadingIcon The button leading icon content. Pass `null` here for no leading icon.
* @param trailingIcon The button trailing icon content. Pass `null` here for no trailing icon.
*/
@Composable
fun NiaTextButton(
onClick: () -> Unit,
modifier: Modifier = Modifier,
enabled: Boolean = true,
small: Boolean = false,
colors: ButtonColors = NiaButtonDefaults.textButtonColors(),
text: @Composable () -> Unit,
leadingIcon: @Composable (() -> Unit)? = null,
trailingIcon: @Composable (() -> Unit)? = null
) {
NiaTextButton(
onClick = onClick,
modifier = modifier,
enabled = enabled,
small = small,
colors = colors,
contentPadding = NiaButtonDefaults.buttonContentPadding(
small = small,
leadingIcon = leadingIcon != null,
trailingIcon = trailingIcon != null
)
) {
NiaButtonContent(
text = text,
leadingIcon = leadingIcon,
trailingIcon = trailingIcon
)
}
}
/**
* Internal Now in Android button content layout for arranging the text label, leading icon and
* trailing icon.
*
* @param text The button text label content.
* @param leadingIcon The button leading icon content. Pass `null` here for no leading icon.
* @param trailingIcon The button trailing icon content. Pass `null` here for no trailing icon.
*/
@Composable
private fun RowScope.NiaButtonContent(
text: @Composable () -> Unit,
leadingIcon: @Composable (() -> Unit)?,
trailingIcon: @Composable (() -> Unit)?
) {
if (leadingIcon != null) {
Box(Modifier.sizeIn(maxHeight = NiaButtonDefaults.ButtonIconSize)) {
leadingIcon()
}
}
Box(
Modifier
.weight(1f, fill = false)
.padding(
start = if (leadingIcon != null) {
NiaButtonDefaults.ButtonContentSpacing
} else {
0.dp
},
end = if (trailingIcon != null) {
NiaButtonDefaults.ButtonContentSpacing
} else {
0.dp
}
)
) {
text()
}
if (trailingIcon != null) {
Box(Modifier.sizeIn(maxHeight = NiaButtonDefaults.ButtonIconSize)) {
trailingIcon()
}
}
}
/**
* Now in Android button default values.
*/
object NiaButtonDefaults {
val SmallButtonHeight = 32.dp
const val DisabledButtonContainerAlpha = 0.12f
const val DisabledButtonContentAlpha = 0.38f
val ButtonHorizontalPadding = 24.dp
val ButtonHorizontalIconPadding = 16.dp
val ButtonVerticalPadding = 8.dp
val SmallButtonHorizontalPadding = 16.dp
val SmallButtonHorizontalIconPadding = 12.dp
val SmallButtonVerticalPadding = 7.dp
val ButtonContentSpacing = 8.dp
val ButtonIconSize = 18.dp
fun buttonContentPadding(
small: Boolean,
leadingIcon: Boolean = false,
trailingIcon: Boolean = false
): PaddingValues {
return PaddingValues(
start = when {
small && leadingIcon -> SmallButtonHorizontalIconPadding
small -> SmallButtonHorizontalPadding
leadingIcon -> ButtonHorizontalIconPadding
else -> ButtonHorizontalPadding
},
top = if (small) SmallButtonVerticalPadding else ButtonVerticalPadding,
end = when {
small && trailingIcon -> SmallButtonHorizontalIconPadding
small -> SmallButtonHorizontalPadding
trailingIcon -> ButtonHorizontalIconPadding
else -> ButtonHorizontalPadding
},
bottom = if (small) SmallButtonVerticalPadding else ButtonVerticalPadding
)
}
@Composable
fun filledButtonColors(
containerColor: Color = MaterialTheme.colorScheme.onBackground,
contentColor: Color = MaterialTheme.colorScheme.onPrimary,
disabledContainerColor: Color = MaterialTheme.colorScheme.onBackground.copy(
alpha = DisabledButtonContainerAlpha
),
disabledContentColor: Color = MaterialTheme.colorScheme.onBackground.copy(
alpha = DisabledButtonContentAlpha
)
) = ButtonDefaults.buttonColors(
containerColor = containerColor,
contentColor = contentColor,
disabledContainerColor = disabledContainerColor,
disabledContentColor = disabledContentColor
)
@Composable
fun outlinedButtonBorder(
enabled: Boolean,
width: Dp = 1.dp,
color: Color = MaterialTheme.colorScheme.onBackground,
disabledColor: Color = MaterialTheme.colorScheme.onBackground.copy(
alpha = DisabledButtonContainerAlpha
)
): BorderStroke = BorderStroke(
width = width,
color = if (enabled) color else disabledColor
)
@Composable
fun outlinedButtonColors(
containerColor: Color = Color.Transparent,
contentColor: Color = MaterialTheme.colorScheme.onBackground,
disabledContainerColor: Color = Color.Transparent,
disabledContentColor: Color = MaterialTheme.colorScheme.onBackground.copy(
alpha = DisabledButtonContentAlpha
)
) = ButtonDefaults.outlinedButtonColors(
containerColor = containerColor,
contentColor = contentColor,
disabledContainerColor = disabledContainerColor,
disabledContentColor = disabledContentColor
)
@Composable
fun textButtonColors(
containerColor: Color = Color.Transparent,
contentColor: Color = MaterialTheme.colorScheme.onBackground,
disabledContainerColor: Color = Color.Transparent,
disabledContentColor: Color = MaterialTheme.colorScheme.onBackground.copy(
alpha = DisabledButtonContentAlpha
)
) = ButtonDefaults.textButtonColors(
containerColor = containerColor,
contentColor = contentColor,
disabledContainerColor = disabledContainerColor,
disabledContentColor = disabledContentColor
)
}

@ -0,0 +1,571 @@
/*
* Copyright 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.samples.apps.nowinandroid.core.ui.component
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.add
import androidx.compose.foundation.layout.asPaddingValues
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.systemBars
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.dp
import com.google.samples.apps.nowinandroid.core.ui.icon.NiaIcons
/**
* Now in Android component catalog.
*/
@Composable
fun NiaComponentCatalog() {
val contentPadding = WindowInsets
.systemBars
.add(WindowInsets(left = 16.dp, top = 16.dp, right = 16.dp, bottom = 16.dp))
.asPaddingValues()
LazyColumn(
modifier = Modifier.fillMaxSize(),
contentPadding = contentPadding,
verticalArrangement = Arrangement.spacedBy(16.dp)
) {
// Buttons
item {
Row(horizontalArrangement = Arrangement.spacedBy(16.dp)) {
NiaFilledButton(onClick = {}) {
Text(text = "Enabled")
}
NiaOutlinedButton(onClick = {}) {
Text(text = "Enabled")
}
NiaTextButton(onClick = {}) {
Text(text = "Enabled")
}
}
}
// Disabled buttons
item {
Row(horizontalArrangement = Arrangement.spacedBy(16.dp)) {
NiaFilledButton(
onClick = {},
enabled = false
) {
Text(text = "Disabled")
}
NiaOutlinedButton(
onClick = {},
enabled = false
) {
Text(text = "Disabled")
}
NiaTextButton(
onClick = {},
enabled = false
) {
Text(text = "Disabled")
}
}
}
// Buttons with leading icons
item {
Row(horizontalArrangement = Arrangement.spacedBy(16.dp)) {
NiaFilledButton(
onClick = {},
text = { Text(text = "Enabled") },
leadingIcon = {
Icon(imageVector = NiaIcons.Add, contentDescription = null)
}
)
NiaOutlinedButton(
onClick = {},
text = { Text(text = "Enabled") },
leadingIcon = {
Icon(imageVector = NiaIcons.Add, contentDescription = null)
}
)
NiaTextButton(
onClick = {},
text = { Text(text = "Enabled") },
leadingIcon = {
Icon(imageVector = NiaIcons.Add, contentDescription = null)
}
)
}
}
// Disabled buttons with leading icons
item {
Row(horizontalArrangement = Arrangement.spacedBy(16.dp)) {
NiaFilledButton(
onClick = {},
enabled = false,
text = { Text(text = "Disabled") },
leadingIcon = {
Icon(imageVector = NiaIcons.Add, contentDescription = null)
}
)
NiaOutlinedButton(
onClick = {},
enabled = false,
text = { Text(text = "Disabled") },
leadingIcon = {
Icon(imageVector = NiaIcons.Add, contentDescription = null)
}
)
NiaTextButton(
onClick = {},
enabled = false,
text = { Text(text = "Disabled") },
leadingIcon = {
Icon(imageVector = NiaIcons.Add, contentDescription = null)
}
)
}
}
// Buttons with trailing icons
item {
Row(horizontalArrangement = Arrangement.spacedBy(16.dp)) {
NiaFilledButton(
onClick = {},
text = { Text(text = "Enabled") },
trailingIcon = {
Icon(imageVector = NiaIcons.Add, contentDescription = null)
}
)
NiaOutlinedButton(
onClick = {},
text = { Text(text = "Enabled") },
trailingIcon = {
Icon(imageVector = NiaIcons.Add, contentDescription = null)
}
)
NiaTextButton(
onClick = {},
text = { Text(text = "Enabled") },
trailingIcon = {
Icon(imageVector = NiaIcons.Add, contentDescription = null)
}
)
}
}
// Disabled buttons with trailing icons
item {
Row(horizontalArrangement = Arrangement.spacedBy(16.dp)) {
NiaFilledButton(
onClick = {},
enabled = false,
text = { Text(text = "Disabled") },
trailingIcon = {
Icon(imageVector = NiaIcons.Add, contentDescription = null)
}
)
NiaOutlinedButton(
onClick = {},
enabled = false,
text = { Text(text = "Disabled") },
trailingIcon = {
Icon(imageVector = NiaIcons.Add, contentDescription = null)
}
)
NiaTextButton(
onClick = {},
enabled = false,
text = { Text(text = "Disabled") },
trailingIcon = {
Icon(imageVector = NiaIcons.Add, contentDescription = null)
}
)
}
}
// Small buttons
item {
Row(horizontalArrangement = Arrangement.spacedBy(16.dp)) {
NiaFilledButton(
onClick = {},
small = true
) {
Text(text = "Enabled")
}
NiaOutlinedButton(
onClick = {},
small = true
) {
Text(text = "Enabled")
}
NiaTextButton(
onClick = {},
small = true
) {
Text(text = "Enabled")
}
}
}
// Disabled small buttons
item {
Row(horizontalArrangement = Arrangement.spacedBy(16.dp)) {
NiaFilledButton(
onClick = {},
enabled = false,
small = true
) {
Text(text = "Disabled")
}
NiaOutlinedButton(
onClick = {},
enabled = false,
small = true
) {
Text(text = "Disabled")
}
NiaTextButton(
onClick = {},
enabled = false,
small = true
) {
Text(text = "Disabled")
}
}
}
// Small buttons with leading icons
item {
Row(horizontalArrangement = Arrangement.spacedBy(16.dp)) {
NiaFilledButton(
onClick = {},
small = true,
text = { Text(text = "Enabled") },
leadingIcon = {
Icon(imageVector = NiaIcons.Add, contentDescription = null)
}
)
NiaOutlinedButton(
onClick = {},
small = true,
text = { Text(text = "Enabled") },
leadingIcon = {
Icon(imageVector = NiaIcons.Add, contentDescription = null)
}
)
NiaTextButton(
onClick = {},
small = true,
text = { Text(text = "Enabled") },
leadingIcon = {
Icon(imageVector = NiaIcons.Add, contentDescription = null)
}
)
}
}
// Disabled small buttons with leading icons
item {
Row(horizontalArrangement = Arrangement.spacedBy(16.dp)) {
NiaFilledButton(
onClick = {},
enabled = false,
small = true,
text = { Text(text = "Disabled") },
leadingIcon = {
Icon(imageVector = NiaIcons.Add, contentDescription = null)
}
)
NiaOutlinedButton(
onClick = {},
enabled = false,
small = true,
text = { Text(text = "Disabled") },
leadingIcon = {
Icon(imageVector = NiaIcons.Add, contentDescription = null)
}
)
NiaTextButton(
onClick = {},
enabled = false,
small = true,
text = { Text(text = "Disabled") },
leadingIcon = {
Icon(imageVector = NiaIcons.Add, contentDescription = null)
}
)
}
}
// Small buttons with trailing icons
item {
Row(horizontalArrangement = Arrangement.spacedBy(16.dp)) {
NiaFilledButton(
onClick = {},
small = true,
text = { Text(text = "Enabled") },
trailingIcon = {
Icon(imageVector = NiaIcons.Add, contentDescription = null)
}
)
NiaOutlinedButton(
onClick = {},
small = true,
text = { Text(text = "Enabled") },
trailingIcon = {
Icon(imageVector = NiaIcons.Add, contentDescription = null)
}
)
NiaTextButton(
onClick = {},
small = true,
text = { Text(text = "Enabled") },
trailingIcon = {
Icon(imageVector = NiaIcons.Add, contentDescription = null)
}
)
}
}
// Disabled small buttons with trailing icons
item {
Row(horizontalArrangement = Arrangement.spacedBy(16.dp)) {
NiaFilledButton(
onClick = {},
enabled = false,
small = true,
text = { Text(text = "Disabled") },
trailingIcon = {
Icon(imageVector = NiaIcons.Add, contentDescription = null)
}
)
NiaOutlinedButton(
onClick = {},
enabled = false,
small = true,
text = { Text(text = "Disabled") },
trailingIcon = {
Icon(imageVector = NiaIcons.Add, contentDescription = null)
}
)
NiaTextButton(
onClick = {},
enabled = false,
small = true,
text = { Text(text = "Disabled") },
trailingIcon = {
Icon(imageVector = NiaIcons.Add, contentDescription = null)
}
)
}
}
// Dropdown menu
item {
NiaDropdownMenuButton(
text = { Text("Newest first") },
items = listOf("Item 1", "Item 2", "Item 3"),
onItemClick = {},
itemText = { item -> Text(item) }
)
}
// Chips
item {
Row(horizontalArrangement = Arrangement.spacedBy(16.dp)) {
var firstChecked by remember { mutableStateOf(false) }
NiaFilterChip(
checked = firstChecked,
onCheckedChange = { checked -> firstChecked = checked },
text = { Text(text = "Enabled".uppercase()) }
)
var secondChecked by remember { mutableStateOf(true) }
NiaFilterChip(
checked = secondChecked,
onCheckedChange = { checked -> secondChecked = checked },
text = { Text(text = "Enabled".uppercase()) }
)
var thirdChecked by remember { mutableStateOf(true) }
NiaFilterChip(
checked = thirdChecked,
onCheckedChange = { checked -> thirdChecked = checked },
enabled = false,
text = { Text(text = "Disabled".uppercase()) }
)
}
}
// Toggle buttons
item {
Row(horizontalArrangement = Arrangement.spacedBy(16.dp)) {
var firstChecked by remember { mutableStateOf(false) }
NiaToggleButton(
checked = firstChecked,
onCheckedChange = { checked -> firstChecked = checked },
icon = {
Icon(
painter = painterResource(id = NiaIcons.BookmarkBorder),
contentDescription = null
)
},
checkedIcon = {
Icon(
painter = painterResource(id = NiaIcons.Bookmark),
contentDescription = null
)
}
)
var secondChecked by remember { mutableStateOf(true) }
NiaToggleButton(
checked = secondChecked,
onCheckedChange = { checked -> secondChecked = checked },
icon = {
Icon(
painter = painterResource(id = NiaIcons.BookmarkBorder),
contentDescription = null
)
},
checkedIcon = {
Icon(
painter = painterResource(id = NiaIcons.Bookmark),
contentDescription = null
)
}
)
var thirdChecked by remember { mutableStateOf(false) }
NiaToggleButton(
checked = thirdChecked,
onCheckedChange = { checked -> thirdChecked = checked },
icon = {
Icon(imageVector = NiaIcons.Add, contentDescription = null)
},
checkedIcon = {
Icon(imageVector = NiaIcons.Check, contentDescription = null)
}
)
var fourthChecked by remember { mutableStateOf(true) }
NiaToggleButton(
checked = fourthChecked,
onCheckedChange = { checked -> fourthChecked = checked },
icon = {
Icon(imageVector = NiaIcons.Add, contentDescription = null)
},
checkedIcon = {
Icon(imageVector = NiaIcons.Check, contentDescription = null)
}
)
}
}
// View toggle
item {
Row(horizontalArrangement = Arrangement.spacedBy(16.dp)) {
var firstExpanded by remember { mutableStateOf(false) }
NiaViewToggleButton(
expanded = firstExpanded,
onExpandedChange = { expanded -> firstExpanded = expanded },
compactText = { Text(text = "Compact view") },
expandedText = { Text(text = "Expanded view") }
)
var secondExpanded by remember { mutableStateOf(true) }
NiaViewToggleButton(
expanded = secondExpanded,
onExpandedChange = { expanded -> secondExpanded = expanded },
compactText = { Text(text = "Compact view") },
expandedText = { Text(text = "Expanded view") }
)
}
}
// Tags
item {
Row(horizontalArrangement = Arrangement.spacedBy(16.dp)) {
var firstFollowed by remember { mutableStateOf(false) }
NiaTopicTag(
followed = firstFollowed,
onFollowClick = { firstFollowed = true },
onUnfollowClick = { firstFollowed = false },
onBrowseClick = {},
text = { Text(text = "Topic".uppercase()) },
followText = { Text(text = "Follow") },
unFollowText = { Text(text = "Unfollow") },
browseText = { Text(text = "Browse topic") }
)
var secondFollowed by remember { mutableStateOf(true) }
NiaTopicTag(
followed = secondFollowed,
onFollowClick = { secondFollowed = true },
onUnfollowClick = { secondFollowed = false },
onBrowseClick = {},
text = { Text(text = "Topic".uppercase()) },
followText = { Text(text = "Follow") },
unFollowText = { Text(text = "Unfollow") },
browseText = { Text(text = "Browse topic") }
)
}
}
// Tabs
item {
var selectedTabIndex by remember { mutableStateOf(0) }
val titles = listOf("Topics", "People")
NiaTabRow(selectedTabIndex = selectedTabIndex) {
titles.forEachIndexed { index, title ->
NiaTab(
selected = selectedTabIndex == index,
onClick = { selectedTabIndex = index },
text = { Text(text = title) }
)
}
}
}
// Navigation
item {
var selectedItem by remember { mutableStateOf(0) }
val items = listOf("For you", "Episodes", "Saved", "Interests")
val icons = listOf(
NiaIcons.UpcomingBorder,
NiaIcons.MenuBookBorder,
NiaIcons.BookmarksBorder
)
val selectedIcons = listOf(
NiaIcons.Upcoming,
NiaIcons.MenuBook,
NiaIcons.Bookmarks
)
val tagIcon = NiaIcons.Tag
NiaNavigationBar {
items.forEachIndexed { index, item ->
NiaNavigationBarItem(
icon = {
if (index == 3) {
Icon(imageVector = tagIcon, contentDescription = null)
} else {
Icon(
painter = painterResource(id = icons[index]),
contentDescription = item
)
}
},
selectedIcon = {
if (index == 3) {
Icon(imageVector = tagIcon, contentDescription = null)
} else {
Icon(
painter = painterResource(id = selectedIcons[index]),
contentDescription = item
)
}
},
label = { Text(item) },
selected = selectedItem == index,
onClick = { selectedItem = index }
)
}
}
}
}
}

@ -0,0 +1,90 @@
/*
* Copyright 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.samples.apps.nowinandroid.core.ui.component
import androidx.compose.foundation.selection.toggleable
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.semantics.Role
import com.google.samples.apps.nowinandroid.core.ui.icon.NiaIcons
/**
* Now in Android filter chip with included leading checked icon as well as text content slot.
*
* @param checked Whether the chip is currently checked.
* @param onCheckedChange Called when the user clicks the chip and toggles checked.
* @param modifier Modifier to be applied to the chip.
* @param enabled Controls the enabled state of the chip. When `false`, this chip will not be
* clickable and will appear disabled to accessibility services.
* @param text The text label content.
*/
@Composable
fun NiaFilterChip(
checked: Boolean,
onCheckedChange: (Boolean) -> Unit,
modifier: Modifier = Modifier,
enabled: Boolean = true,
text: @Composable () -> Unit
) {
// TODO: Replace with Chip when available in Compose Material 3: b/197399111
NiaOutlinedButton(
onClick = { onCheckedChange(!checked) },
modifier = Modifier
.toggleable(value = checked, enabled = enabled, role = Role.Button, onValueChange = {})
.then(modifier),
enabled = enabled,
small = true,
border = NiaButtonDefaults.outlinedButtonBorder(
enabled = enabled,
disabledColor = MaterialTheme.colorScheme.onBackground.copy(
alpha = if (checked) {
NiaButtonDefaults.DisabledButtonContentAlpha
} else {
NiaButtonDefaults.DisabledButtonContainerAlpha
}
)
),
colors = NiaButtonDefaults.outlinedButtonColors(
containerColor = if (checked) {
MaterialTheme.colorScheme.primaryContainer
} else {
Color.Transparent
},
disabledContainerColor = if (checked) {
MaterialTheme.colorScheme.onBackground.copy(
alpha = NiaButtonDefaults.DisabledButtonContainerAlpha
)
} else {
Color.Transparent
}
),
text = text,
leadingIcon = if (checked) {
{
Icon(
imageVector = NiaIcons.Check,
contentDescription = null
)
}
} else {
null
}
)
}

@ -0,0 +1,132 @@
/*
* Copyright 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.samples.apps.nowinandroid.core.ui.component
import androidx.compose.foundation.layout.Box
import androidx.compose.material3.DropdownMenu
import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.Icon
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import com.google.samples.apps.nowinandroid.core.ui.icon.NiaIcons
/**
* Now in Android dropdown menu button with included trailing icon as well as text label and item
* content slots.
*
* @param items The list of items to display in the menu.
* @param onItemClick Called when the user clicks on a menu item.
* @param modifier Modifier to be applied to the button.
* @param enabled Controls the enabled state of the button. When `false`, this button will not be
* clickable and will appear disabled to accessibility services.
* @param dismissOnItemClick Whether the menu should be dismissed when an item is clicked.
* @param itemText The text label content for a given item.
* @param itemLeadingIcon The leading icon content for a given item.
* @param itemTrailingIcon The trailing icon content for a given item.
*/
@Composable
fun <T> NiaDropdownMenuButton(
items: List<T>,
onItemClick: (item: T) -> Unit,
modifier: Modifier = Modifier,
enabled: Boolean = true,
dismissOnItemClick: Boolean = true,
text: @Composable () -> Unit,
itemText: @Composable (item: T) -> Unit,
itemLeadingIcon: @Composable ((item: T) -> Unit)? = null,
itemTrailingIcon: @Composable ((item: T) -> Unit)? = null
) {
var expanded by remember { mutableStateOf(false) }
Box(modifier = modifier) {
NiaOutlinedButton(
onClick = { expanded = true },
enabled = enabled,
text = text,
trailingIcon = {
Icon(
imageVector = if (expanded) NiaIcons.ArrowDropUp else NiaIcons.ArrowDropDown,
contentDescription = null
)
}
)
NiaDropdownMenu(
expanded = expanded,
onDismissRequest = { expanded = false },
items = items,
onItemClick = onItemClick,
dismissOnItemClick = dismissOnItemClick,
itemText = itemText,
itemLeadingIcon = itemLeadingIcon,
itemTrailingIcon = itemTrailingIcon
)
}
}
/**
* Now in Android dropdown menu with item content slots. Wraps Material 3 [DropdownMenu] and
* [DropdownMenuItem].
*
* @param expanded Whether the menu is currently open and visible to the user.
* @param onDismissRequest Called when the user requests to dismiss the menu, such as by
* tapping outside the menu's bounds.
* @param items The list of items to display in the menu.
* @param onItemClick Called when the user clicks on a menu item.
* @param dismissOnItemClick Whether the menu should be dismissed when an item is clicked.
* @param itemText The text label content for a given item.
* @param itemLeadingIcon The leading icon content for a given item.
* @param itemTrailingIcon The trailing icon content for a given item.
*/
@Composable
fun <T> NiaDropdownMenu(
expanded: Boolean,
onDismissRequest: () -> Unit,
items: List<T>,
onItemClick: (item: T) -> Unit,
dismissOnItemClick: Boolean = true,
itemText: @Composable (item: T) -> Unit,
itemLeadingIcon: @Composable ((item: T) -> Unit)? = null,
itemTrailingIcon: @Composable ((item: T) -> Unit)? = null
) {
DropdownMenu(
expanded = expanded,
onDismissRequest = onDismissRequest
) {
items.forEach { item ->
DropdownMenuItem(
text = { itemText(item) },
onClick = {
onItemClick(item)
if (dismissOnItemClick) onDismissRequest()
},
leadingIcon = if (itemLeadingIcon != null) {
{ itemLeadingIcon(item) }
} else {
null
},
trailingIcon = if (itemTrailingIcon != null) {
{ itemTrailingIcon(item) }
} else {
null
}
)
}
}
}

@ -0,0 +1,176 @@
/*
* Copyright 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.samples.apps.nowinandroid.core.ui.component
import androidx.compose.foundation.layout.ColumnScope
import androidx.compose.foundation.layout.RowScope
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.NavigationBar
import androidx.compose.material3.NavigationBarItem
import androidx.compose.material3.NavigationBarItemDefaults
import androidx.compose.material3.NavigationRail
import androidx.compose.material3.NavigationRailItem
import androidx.compose.material3.NavigationRailItemDefaults
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
/**
* Now in Android navigation bar item with icon and label content slots. Wraps Material 3
* [NavigationBarItem].
*
* @param selected Whether this item is selected.
* @param onClick The callback to be invoked when this item is selected.
* @param icon The item icon content.
* @param modifier Modifier to be applied to this item.
* @param selectedIcon The item icon content when selected.
* @param enabled controls the enabled state of this item. When `false`, this item will not be
* clickable and will appear disabled to accessibility services.
* @param label The item text label content.
* @param alwaysShowLabel Whether to always show the label for this item. If false, the label will
* only be shown when this item is selected.
*/
@Composable
fun RowScope.NiaNavigationBarItem(
selected: Boolean,
onClick: () -> Unit,
icon: @Composable () -> Unit,
modifier: Modifier = Modifier,
selectedIcon: @Composable () -> Unit = icon,
enabled: Boolean = true,
label: @Composable (() -> Unit)? = null,
alwaysShowLabel: Boolean = true
) {
NavigationBarItem(
selected = selected,
onClick = onClick,
icon = if (selected) selectedIcon else icon,
modifier = modifier,
enabled = enabled,
label = label,
alwaysShowLabel = alwaysShowLabel,
colors = NavigationBarItemDefaults.colors(
selectedIconColor = NiaNavigationDefaults.navigationSelectedItemColor(),
unselectedIconColor = NiaNavigationDefaults.navigationContentColor(),
selectedTextColor = NiaNavigationDefaults.navigationSelectedItemColor(),
unselectedTextColor = NiaNavigationDefaults.navigationContentColor(),
indicatorColor = NiaNavigationDefaults.navigationIndicatorColor()
)
)
}
/**
* Now in Android navigation bar with content slot. Wraps Material 3 [NavigationBar].
*
* @param modifier Modifier to be applied to the navigation bar.
* @param content Destinations inside the navigation bar. This should contain multiple
* [NavigationBarItem]s.
*/
@Composable
fun NiaNavigationBar(
modifier: Modifier = Modifier,
content: @Composable RowScope.() -> Unit
) {
NavigationBar(
modifier = modifier,
containerColor = NiaNavigationDefaults.NavigationContainerColor,
contentColor = NiaNavigationDefaults.navigationContentColor(),
tonalElevation = 0.dp,
content = content
)
}
/**
* Now in Android navigation rail item with icon and label content slots. Wraps Material 3
* [NavigationRailItem].
*
* @param selected Whether this item is selected.
* @param onClick The callback to be invoked when this item is selected.
* @param icon The item icon content.
* @param modifier Modifier to be applied to this item.
* @param selectedIcon The item icon content when selected.
* @param enabled controls the enabled state of this item. When `false`, this item will not be
* clickable and will appear disabled to accessibility services.
* @param label The item text label content.
* @param alwaysShowLabel Whether to always show the label for this item. If false, the label will
* only be shown when this item is selected.
*/
@Composable
fun NiaNavigationRailItem(
selected: Boolean,
onClick: () -> Unit,
icon: @Composable () -> Unit,
modifier: Modifier = Modifier,
selectedIcon: @Composable () -> Unit = icon,
enabled: Boolean = true,
label: @Composable (() -> Unit)? = null,
alwaysShowLabel: Boolean = true
) {
NavigationRailItem(
selected = selected,
onClick = onClick,
icon = if (selected) selectedIcon else icon,
modifier = modifier,
enabled = enabled,
label = label,
alwaysShowLabel = alwaysShowLabel,
colors = NavigationRailItemDefaults.colors(
selectedIconColor = NiaNavigationDefaults.navigationSelectedItemColor(),
unselectedIconColor = NiaNavigationDefaults.navigationContentColor(),
selectedTextColor = NiaNavigationDefaults.navigationSelectedItemColor(),
unselectedTextColor = NiaNavigationDefaults.navigationContentColor(),
indicatorColor = NiaNavigationDefaults.navigationIndicatorColor()
)
)
}
/**
* Now in Android navigation rail with header and content slots. Wraps Material 3 [NavigationRail].
*
* @param modifier Modifier to be applied to the navigation rail.
* @param header Optional header that may hold a floating action button or a logo.
* @param content Destinations inside the navigation rail. This should contain multiple
* [NavigationRailItem]s.
*/
@Composable
fun NiaNavigationRail(
modifier: Modifier = Modifier,
header: @Composable (ColumnScope.() -> Unit)? = null,
content: @Composable ColumnScope.() -> Unit
) {
NavigationRail(
modifier = modifier,
containerColor = NiaNavigationDefaults.NavigationContainerColor,
contentColor = NiaNavigationDefaults.navigationContentColor(),
header = header,
content = content
)
}
/**
* Now in Android navigation default values.
*/
object NiaNavigationDefaults {
val NavigationContainerColor = Color.Transparent
@Composable
fun navigationContentColor() = MaterialTheme.colorScheme.onSurfaceVariant
@Composable
fun navigationSelectedItemColor() = MaterialTheme.colorScheme.onPrimaryContainer
@Composable
fun navigationIndicatorColor() = MaterialTheme.colorScheme.primaryContainer
}

@ -0,0 +1,102 @@
/*
* Copyright 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.samples.apps.nowinandroid.core.ui.component
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.ProvideTextStyle
import androidx.compose.material3.Tab
import androidx.compose.material3.TabRow
import androidx.compose.material3.TabRowDefaults
import androidx.compose.material3.TabRowDefaults.tabIndicatorOffset
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
/**
* Now in Android tab. Wraps Material 3 [Tab] and shifts text label down.
*
* @param selected Whether this tab is selected or not.
* @param onClick The callback to be invoked when this tab is selected.
* @param modifier Modifier to be applied to the tab.
* @param enabled Controls the enabled state of the tab. When `false`, this tab will not be
* clickable and will appear disabled to accessibility services.
* @param text The text label content.
*/
@Composable
fun NiaTab(
selected: Boolean,
onClick: () -> Unit,
modifier: Modifier = Modifier,
enabled: Boolean = true,
text: @Composable () -> Unit
) {
Tab(
selected = selected,
onClick = onClick,
modifier = modifier,
enabled = enabled,
text = {
val style = MaterialTheme.typography.labelLarge.copy(textAlign = TextAlign.Center)
ProvideTextStyle(
value = style,
content = {
Box(modifier = Modifier.padding(top = NiaTabDefaults.TabTopPadding)) {
text()
}
}
)
}
)
}
/**
* Now in Android tab row. Wraps Material 3 [TabRow].
*
* @param selectedTabIndex The index of the currently selected tab.
* @param modifier Modifier to be applied to the tab row.
* @param tabs The tabs inside this tab row. Typically this will be multiple [NiaTab]s. Each element
* inside this lambda will be measured and placed evenly across the row, each taking up equal space.
*/
@Composable
fun NiaTabRow(
selectedTabIndex: Int,
modifier: Modifier = Modifier,
tabs: @Composable () -> Unit
) {
TabRow(
selectedTabIndex = selectedTabIndex,
modifier = modifier,
containerColor = Color.Transparent,
contentColor = MaterialTheme.colorScheme.onSurface,
indicator = { tabPositions ->
TabRowDefaults.Indicator(
modifier = Modifier.tabIndicatorOffset(tabPositions[selectedTabIndex]),
height = 2.dp,
color = MaterialTheme.colorScheme.onSurface
)
},
tabs = tabs
)
}
object NiaTabDefaults {
val TabTopPadding = 7.dp
}

@ -0,0 +1,91 @@
/*
* Copyright 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.samples.apps.nowinandroid.core.ui.component
import androidx.compose.foundation.layout.Box
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.contentColorFor
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
@Composable
fun NiaTopicTag(
followed: Boolean,
onFollowClick: () -> Unit,
onUnfollowClick: () -> Unit,
onBrowseClick: () -> Unit,
modifier: Modifier = Modifier,
enabled: Boolean = true,
text: @Composable () -> Unit,
followText: @Composable () -> Unit,
unFollowText: @Composable () -> Unit,
browseText: @Composable () -> Unit
) {
var expanded by remember { mutableStateOf(false) }
Box(modifier = modifier) {
val containerColor = if (followed) {
MaterialTheme.colorScheme.primaryContainer
} else {
MaterialTheme.colorScheme.surfaceVariant
}
NiaTextButton(
onClick = { expanded = true },
enabled = enabled,
small = true,
colors = NiaButtonDefaults.textButtonColors(
containerColor = containerColor,
contentColor = contentColorFor(backgroundColor = containerColor),
disabledContainerColor = if (followed) {
MaterialTheme.colorScheme.onBackground.copy(
alpha = NiaButtonDefaults.DisabledButtonContentAlpha
)
} else {
Color.Transparent
}
),
text = text
)
NiaDropdownMenu(
expanded = expanded,
onDismissRequest = { expanded = false },
items = if (followed) listOf(UNFOLLOW, BROWSE) else listOf(FOLLOW, BROWSE),
onItemClick = { item ->
when (item) {
FOLLOW -> onFollowClick()
UNFOLLOW -> onUnfollowClick()
BROWSE -> onBrowseClick()
}
},
itemText = { item ->
when (item) {
FOLLOW -> followText()
UNFOLLOW -> unFollowText()
BROWSE -> browseText()
}
}
)
}
}
private const val FOLLOW = 1
private const val UNFOLLOW = 2
private const val BROWSE = 3

@ -0,0 +1,87 @@
/*
* Copyright 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.samples.apps.nowinandroid.core.ui.component
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.sizeIn
import androidx.compose.foundation.selection.toggleable
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.drawBehind
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.semantics.Role
import androidx.compose.ui.unit.dp
/**
* Now in Android toggle button with icon and checked icon content slots. Wraps Material 3
* [IconButton].
*
* @param checked Whether the toggle button is currently checked.
* @param onCheckedChange Called when the user clicks the toggle button and toggles checked.
* @param modifier Modifier to be applied to the toggle button.
* @param enabled Controls the enabled state of the toggle button. When `false`, this toggle button
* will not be clickable and will appear disabled to accessibility services.
* @param icon The icon content to show when unchecked.
* @param checkedIcon The icon content to show when checked.
*/
@Composable
fun NiaToggleButton(
checked: Boolean,
onCheckedChange: (Boolean) -> Unit,
modifier: Modifier = Modifier,
enabled: Boolean = true,
icon: @Composable () -> Unit,
checkedIcon: @Composable () -> Unit = icon
) {
val checkedColor = MaterialTheme.colorScheme.primaryContainer
val checkedRadius = with(LocalDensity.current) {
(NiaToggleButtonDefaults.ToggleButtonSize / 2).toPx()
}
IconButton(
onClick = { onCheckedChange(!checked) },
modifier = Modifier
.toggleable(value = checked, enabled = enabled, role = Role.Button, onValueChange = {})
.drawBehind {
if (checked) drawCircle(
color = checkedColor,
radius = checkedRadius
)
}
.then(modifier),
enabled = enabled,
content = {
Box(
modifier = Modifier.sizeIn(
maxWidth = NiaToggleButtonDefaults.ToggleButtonIconSize,
maxHeight = NiaToggleButtonDefaults.ToggleButtonIconSize
)
) {
if (checked) checkedIcon() else icon()
}
}
)
}
/**
* Now in Android toggle button default values.
*/
object NiaToggleButtonDefaults {
val ToggleButtonSize = 40.dp
val ToggleButtonIconSize = 18.dp
}

@ -0,0 +1,57 @@
/*
* Copyright 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.samples.apps.nowinandroid.core.ui.component
import androidx.compose.material3.Icon
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import com.google.samples.apps.nowinandroid.core.ui.icon.NiaIcons
/**
* Now in Android view toggle button with included trailing icon as well as compact and expanded
* text label content slots.
*
* @param expanded Whether the view toggle is currently in expanded mode or compact mode.
* @param onExpandedChange Called when the user clicks the button and toggles the mode.
* @param modifier Modifier to be applied to the button.
* @param enabled Controls the enabled state of the button. When `false`, this button will not be
* clickable and will appear disabled to accessibility services.
* @param compactText The text label content to show in expanded mode.
* @param expandedText The text label content to show in compact mode.
*/
@Composable
fun NiaViewToggleButton(
expanded: Boolean,
onExpandedChange: (Boolean) -> Unit,
modifier: Modifier = Modifier,
enabled: Boolean = true,
compactText: @Composable () -> Unit,
expandedText: @Composable () -> Unit
) {
NiaTextButton(
onClick = { onExpandedChange(!expanded) },
modifier = modifier,
enabled = enabled,
text = if (expanded) expandedText else compactText,
trailingIcon = {
Icon(
imageVector = if (expanded) NiaIcons.ViewDay else NiaIcons.ShortText,
contentDescription = null
)
}
)
}

@ -0,0 +1,69 @@
/*
* Copyright 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.samples.apps.nowinandroid.core.ui.icon
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.MoreVert
import androidx.compose.material.icons.outlined.AccountCircle
import androidx.compose.material.icons.rounded.Add
import androidx.compose.material.icons.rounded.ArrowBack
import androidx.compose.material.icons.rounded.ArrowDropDown
import androidx.compose.material.icons.rounded.ArrowDropUp
import androidx.compose.material.icons.rounded.Check
import androidx.compose.material.icons.rounded.Close
import androidx.compose.material.icons.rounded.ExpandLess
import androidx.compose.material.icons.rounded.Fullscreen
import androidx.compose.material.icons.rounded.PlayArrow
import androidx.compose.material.icons.rounded.Search
import androidx.compose.material.icons.rounded.ShortText
import androidx.compose.material.icons.rounded.Tag
import androidx.compose.material.icons.rounded.ViewDay
import androidx.compose.material.icons.rounded.VolumeOff
import androidx.compose.material.icons.rounded.VolumeUp
import androidx.compose.ui.graphics.vector.ImageVector
import com.google.samples.apps.nowinandroid.core.ui.R
/**
* Now in Android icons. Material icons are [ImageVector]s, custom icons are drawable resource IDs.
*/
object NiaIcons {
val AccountCircle = Icons.Outlined.AccountCircle
val Add = Icons.Rounded.Add
val ArrowBack = Icons.Rounded.ArrowBack
val ArrowDropDown = Icons.Rounded.ArrowDropDown
val ArrowDropUp = Icons.Rounded.ArrowDropUp
val Bookmark = R.drawable.ic_bookmark
val BookmarkBorder = R.drawable.ic_bookmark_border
val Bookmarks = R.drawable.ic_bookmarks
val BookmarksBorder = R.drawable.ic_bookmarks_border
val Check = Icons.Rounded.Check
val Close = Icons.Rounded.Close
val ExpandLess = Icons.Rounded.ExpandLess
val Fullscreen = Icons.Rounded.Fullscreen
val MenuBook = R.drawable.ic_menu_book
val MenuBookBorder = R.drawable.ic_menu_book_border
val MoreVert = Icons.Default.MoreVert
val PlayArrow = Icons.Rounded.PlayArrow
val Search = Icons.Rounded.Search
val ShortText = Icons.Rounded.ShortText
val Tag = Icons.Rounded.Tag
val Upcoming = R.drawable.ic_upcoming
val UpcomingBorder = R.drawable.ic_upcoming_border
val ViewDay = Icons.Rounded.ViewDay
val VolumeOff = Icons.Rounded.VolumeOff
val VolumeUp = Icons.Rounded.VolumeUp
}

@ -0,0 +1,24 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ Copyright 2022 Google LLC
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ https://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M17,3H7C5.9,3 5,3.9 5,5V19.483C5,20.201 5.734,20.685 6.394,20.403L12,18L17.606,20.403C18.266,20.685 19,20.201 19,19.483V5C19,3.9 18.1,3 17,3Z"
android:fillColor="#201A1B"/>
</vector>

@ -0,0 +1,24 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ Copyright 2022 Google LLC
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ https://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M17,3H7C5.9,3 5,3.9 5,5V19.483C5,20.201 5.734,20.685 6.394,20.403L12,18L17.606,20.403C18.266,20.685 19,20.201 19,19.483V5C19,3.9 18.1,3 17,3ZM17,18L12.4,15.994C12.145,15.883 11.855,15.883 11.6,15.994L7,18V5H17V18Z"
android:fillColor="#201A1B"/>
</vector>

@ -0,0 +1,27 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ Copyright 2022 Google LLC
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ https://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M19,19C19,19.552 19.448,20 20,20C20.552,20 21,19.552 21,19V3C21,1.9 20.1,1 19,1H7C6.448,1 6,1.448 6,2C6,2.552 6.448,3 7,3H19V19Z"
android:fillColor="#201A1B"/>
<path
android:pathData="M15,5H5C3.9,5 3,5.9 3,7V21.483C3,22.201 3.734,22.685 4.394,22.403L10,20L15.606,22.403C16.266,22.685 17,22.201 17,21.483V7C17,5.9 16.1,5 15,5Z"
android:fillColor="#201A1B"/>
</vector>

@ -0,0 +1,28 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ Copyright 2022 Google LLC
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ https://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M19,19C19,19.552 19.448,20 20,20C20.552,20 21,19.552 21,19V3C21,1.9 20.1,1 19,1H7C6.448,1 6,1.448 6,2C6,2.552 6.448,3 7,3H19V19Z"
android:fillColor="#201A1B"/>
<path
android:pathData="M5,5H15C16.1,5 17,5.9 17,7V21.483C17,22.201 16.266,22.685 15.606,22.403L10,20L4.394,22.403C3.734,22.685 3,22.201 3,21.483V7C3,5.9 3.9,5 5,5ZM15,19.97V7H5V19.97C6.535,19.31 8.07,18.65 9.605,17.99C9.857,17.882 10.143,17.882 10.395,17.99L15,19.97Z"
android:fillColor="#201A1B"
android:fillType="evenOdd"/>
</vector>

@ -0,0 +1,34 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ Copyright 2022 Google LLC
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ https://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M12.005,5.178C13.455,4.078 15.555,3.678 17.505,3.678C18.955,3.678 20.495,3.898 21.775,4.468C22.505,4.798 22.995,5.508 22.995,6.318V17.598C22.995,18.908 21.775,19.868 20.515,19.538C19.535,19.288 18.495,19.178 17.495,19.178C15.935,19.178 14.275,19.428 12.935,20.098C12.345,20.398 11.665,20.398 11.065,20.098C9.725,19.438 8.065,19.178 6.505,19.178C5.505,19.178 4.465,19.288 3.485,19.538C2.225,19.858 1.005,18.898 1.005,17.598V6.318C1.005,5.508 1.495,4.798 2.225,4.468C3.515,3.898 5.055,3.678 6.505,3.678C8.455,3.678 10.555,4.078 12.005,5.178ZM21,17.5C19.9,17.15 18.7,17 17.5,17C16.16,17 14.37,17.41 13,17.99V6.49C14.37,5.9 16.16,5.5 17.5,5.5C18.7,5.5 19.9,5.65 21,6V17.5Z"
android:fillColor="#201A1B"
android:fillType="evenOdd"/>
<path
android:pathData="M17.5,9.5C18.38,9.5 19.23,9.59 20,9.76V8.24C19.21,8.09 18.36,8 17.5,8C16.22,8 15.04,8.16 14,8.47V10.04C14.99,9.69 16.18,9.5 17.5,9.5Z"
android:fillColor="#201A1B"/>
<path
android:pathData="M17.5,12.16C18.38,12.16 19.23,12.25 20,12.42V10.9C19.21,10.75 18.36,10.66 17.5,10.66C16.22,10.66 15.04,10.82 14,11.13V12.7C14.99,12.36 16.18,12.16 17.5,12.16Z"
android:fillColor="#201A1B"/>
<path
android:pathData="M17.5,14.83C18.38,14.83 19.23,14.92 20,15.09V13.57C19.21,13.42 18.36,13.33 17.5,13.33C16.22,13.33 15.04,13.49 14,13.8V15.37C14.99,15.02 16.18,14.83 17.5,14.83Z"
android:fillColor="#201A1B"/>
</vector>

@ -0,0 +1,34 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ Copyright 2022 Google LLC
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ https://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M12.005,5.178C13.455,4.078 15.555,3.678 17.505,3.678C18.955,3.678 20.495,3.898 21.775,4.468C22.505,4.798 22.995,5.508 22.995,6.318V17.598C22.995,18.908 21.775,19.868 20.515,19.538C19.535,19.288 18.495,19.178 17.495,19.178C15.935,19.178 14.275,19.428 12.935,20.098C12.345,20.398 11.665,20.398 11.065,20.098C9.725,19.438 8.065,19.178 6.505,19.178C5.505,19.178 4.465,19.288 3.485,19.538C2.225,19.858 1.005,18.898 1.005,17.598V6.318C1.005,5.508 1.495,4.798 2.225,4.468C3.515,3.898 5.055,3.678 6.505,3.678C8.455,3.678 10.555,4.078 12.005,5.178ZM6.5,5.5C7.84,5.5 9.63,5.91 11,6.49V17.99C9.63,17.41 7.84,17 6.5,17C5.3,17 4.1,17.15 3,17.5V6C4.1,5.65 5.3,5.5 6.5,5.5ZM21,17.5C19.9,17.15 18.7,17 17.5,17C16.16,17 14.37,17.41 13,17.99V6.49C14.37,5.9 16.16,5.5 17.5,5.5C18.7,5.5 19.9,5.65 21,6V17.5Z"
android:fillColor="#201A1B"
android:fillType="evenOdd"/>
<path
android:pathData="M17.5,9.5C18.38,9.5 19.23,9.59 20,9.76V8.24C19.21,8.09 18.36,8 17.5,8C16.22,8 15.04,8.16 14,8.47V10.04C14.99,9.69 16.18,9.5 17.5,9.5Z"
android:fillColor="#201A1B"/>
<path
android:pathData="M17.5,12.16C18.38,12.16 19.23,12.25 20,12.42V10.9C19.21,10.75 18.36,10.66 17.5,10.66C16.22,10.66 15.04,10.82 14,11.13V12.7C14.99,12.36 16.18,12.16 17.5,12.16Z"
android:fillColor="#201A1B"/>
<path
android:pathData="M17.5,14.83C18.38,14.83 19.23,14.92 20,15.09V13.57C19.21,13.42 18.36,13.33 17.5,13.33C16.22,13.33 15.04,13.49 14,13.8V15.37C14.99,15.02 16.18,14.83 17.5,14.83Z"
android:fillColor="#201A1B"/>
</vector>

@ -0,0 +1,33 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ Copyright 2022 Google LLC
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ https://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M20.45,6.55C20.07,6.17 19.44,6.17 19.06,6.55L16.89,8.7C16.5,9.08 16.5,9.71 16.89,10.09L16.9,10.1C17.29,10.49 17.91,10.49 18.3,10.1C18.92,9.47 19.82,8.56 20.45,7.93C20.83,7.55 20.83,6.93 20.45,6.55Z"
android:fillColor="#201A1B"/>
<path
android:pathData="M12.02,3H11.99C11.44,3 11,3.44 11,3.98V7.01C11,7.56 11.44,8 11.98,8H12.01C12.56,8 13,7.56 13,7.02V3.98C13,3.44 12.56,3 12.02,3Z"
android:fillColor="#201A1B"/>
<path
android:pathData="M7.1,10.11L7.11,10.1C7.49,9.72 7.49,9.09 7.11,8.71L4.96,6.54C4.58,6.15 3.95,6.15 3.57,6.54L3.55,6.55C3.16,6.94 3.16,7.56 3.55,7.94C4.18,8.56 5.08,9.48 5.7,10.11C6.09,10.49 6.72,10.49 7.1,10.11Z"
android:fillColor="#201A1B"/>
<path
android:pathData="M12,15C10.76,15 9.69,14.25 9.24,13.17C8.92,12.43 8.14,12 7.34,12H4C2.9,12 2,12.9 2,14V19C2,20.1 2.9,21 4,21H20C21.1,21 22,20.1 22,19V14C22,12.9 21.1,12 20,12H16.66C15.86,12 15.08,12.43 14.76,13.17C14.31,14.25 13.24,15 12,15Z"
android:fillColor="#201A1B"/>
</vector>

@ -0,0 +1,34 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ Copyright 2022 Google LLC
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ https://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M20.45,6.55C20.07,6.17 19.44,6.17 19.06,6.55L16.89,8.7C16.5,9.08 16.5,9.71 16.89,10.09L16.9,10.1C17.29,10.49 17.91,10.49 18.3,10.1C18.92,9.47 19.82,8.56 20.45,7.93C20.83,7.55 20.83,6.93 20.45,6.55Z"
android:fillColor="#201A1B"/>
<path
android:pathData="M12.02,3H11.99C11.44,3 11,3.44 11,3.98V7.01C11,7.56 11.44,8 11.98,8H12.01C12.56,8 13,7.56 13,7.02V3.98C13,3.44 12.56,3 12.02,3Z"
android:fillColor="#201A1B"/>
<path
android:pathData="M7.1,10.11L7.11,10.1C7.49,9.72 7.49,9.09 7.11,8.71L4.96,6.54C4.58,6.15 3.95,6.15 3.57,6.54L3.55,6.55C3.16,6.94 3.16,7.56 3.55,7.94C4.18,8.56 5.08,9.48 5.7,10.11C6.09,10.49 6.72,10.49 7.1,10.11Z"
android:fillColor="#201A1B"/>
<path
android:pathData="M9.24,13.17C9.69,14.25 10.76,15 12,15C13.24,15 14.31,14.25 14.76,13.17C15.08,12.43 15.86,12 16.66,12H20C21.1,12 22,12.9 22,14V19C22,20.1 21.1,21 20,21H4C2.9,21 2,20.1 2,19V14C2,12.9 2.9,12 4,12H7.34C8.14,12 8.92,12.43 9.24,13.17ZM20,14H16.58C15.81,15.76 14.04,17 12,17C9.96,17 8.19,15.76 7.42,14H4V19H20V14Z"
android:fillColor="#201A1B"
android:fillType="evenOdd"/>
</vector>

@ -6,7 +6,7 @@ androidxActivity = "1.4.0"
androidxAppCompat = "1.3.0"
androidxCompose = "1.2.0-alpha03"
androidxMaterialWindow = "1.2.0-SNAPSHOT"
androidxComposeMaterial3 = "1.0.0-alpha03"
androidxComposeMaterial3 = "1.0.0-alpha07"
androidxCore = "1.7.0"
androidxDataStore = "1.0.0"
androidxEspresso = "3.3.0"

Loading…
Cancel
Save