Update :core-designsystem module based on Figma file

pull/485/head
Nick Rout 2 years ago
parent 68dc1d517a
commit c12fe1fbe5

@ -38,7 +38,7 @@ import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.dp
import com.google.accompanist.flowlayout.FlowRow
import com.google.samples.apps.nowinandroid.core.designsystem.component.NiaDropdownMenuButton
import com.google.samples.apps.nowinandroid.core.designsystem.component.NiaFilledButton
import com.google.samples.apps.nowinandroid.core.designsystem.component.NiaButton
import com.google.samples.apps.nowinandroid.core.designsystem.component.NiaFilterChip
import com.google.samples.apps.nowinandroid.core.designsystem.component.NiaNavigationBar
import com.google.samples.apps.nowinandroid.core.designsystem.component.NiaNavigationBarItem
@ -46,7 +46,7 @@ import com.google.samples.apps.nowinandroid.core.designsystem.component.NiaOutli
import com.google.samples.apps.nowinandroid.core.designsystem.component.NiaTab
import com.google.samples.apps.nowinandroid.core.designsystem.component.NiaTabRow
import com.google.samples.apps.nowinandroid.core.designsystem.component.NiaTextButton
import com.google.samples.apps.nowinandroid.core.designsystem.component.NiaToggleButton
import com.google.samples.apps.nowinandroid.core.designsystem.component.NiaIconToggleButton
import com.google.samples.apps.nowinandroid.core.designsystem.component.NiaTopicTag
import com.google.samples.apps.nowinandroid.core.designsystem.component.NiaViewToggleButton
import com.google.samples.apps.nowinandroid.core.designsystem.icon.NiaIcons
@ -77,7 +77,7 @@ fun NiaCatalog() {
item { Text("Buttons", Modifier.padding(top = 16.dp)) }
item {
FlowRow(mainAxisSpacing = 16.dp) {
NiaFilledButton(onClick = {}) {
NiaButton(onClick = {}) {
Text(text = "Enabled")
}
NiaOutlinedButton(onClick = {}) {
@ -91,7 +91,7 @@ fun NiaCatalog() {
item { Text("Disabled buttons", Modifier.padding(top = 16.dp)) }
item {
FlowRow(mainAxisSpacing = 16.dp) {
NiaFilledButton(
NiaButton(
onClick = {},
enabled = false
) {
@ -114,7 +114,7 @@ fun NiaCatalog() {
item { Text("Buttons with leading icons", Modifier.padding(top = 16.dp)) }
item {
FlowRow(mainAxisSpacing = 16.dp) {
NiaFilledButton(
NiaButton(
onClick = {},
text = { Text(text = "Enabled") },
leadingIcon = {
@ -140,7 +140,7 @@ fun NiaCatalog() {
item { Text("Disabled buttons with leading icons", Modifier.padding(top = 16.dp)) }
item {
FlowRow(mainAxisSpacing = 16.dp) {
NiaFilledButton(
NiaButton(
onClick = {},
enabled = false,
text = { Text(text = "Disabled") },
@ -166,250 +166,23 @@ fun NiaCatalog() {
)
}
}
item { Text("Buttons with trailing icons", Modifier.padding(top = 16.dp)) }
item { Text("Dropdown menus", Modifier.padding(top = 16.dp)) }
item {
FlowRow(mainAxisSpacing = 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)
}
)
}
}
item { Text("Disabled buttons with trailing icons", Modifier.padding(top = 16.dp)) }
item {
FlowRow(mainAxisSpacing = 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)
}
)
}
}
item { Text("Small buttons", Modifier.padding(top = 16.dp)) }
item {
FlowRow(mainAxisSpacing = 16.dp) {
NiaFilledButton(
onClick = {},
small = true
) {
Text(text = "Enabled")
}
NiaOutlinedButton(
onClick = {},
small = true
) {
Text(text = "Enabled")
}
NiaTextButton(
onClick = {},
small = true
) {
Text(text = "Enabled")
}
}
}
item { Text("Disabled small buttons", Modifier.padding(top = 16.dp)) }
item {
FlowRow(mainAxisSpacing = 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")
}
}
}
item { Text("Small buttons with leading icons", Modifier.padding(top = 16.dp)) }
item {
FlowRow(mainAxisSpacing = 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)
}
)
}
}
item {
Text(
"Disabled small buttons with leading icons",
Modifier.padding(top = 16.dp)
)
}
item {
FlowRow(mainAxisSpacing = 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)
}
)
}
}
item { Text("Small buttons with trailing icons", Modifier.padding(top = 16.dp)) }
item {
FlowRow(mainAxisSpacing = 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)
}
)
}
}
item {
Text(
"Disabled small buttons with trailing icons",
Modifier.padding(top = 16.dp)
)
}
item {
FlowRow(mainAxisSpacing = 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)
}
)
}
}
item { Text("Dropdown menu", Modifier.padding(top = 16.dp)) }
item {
NiaDropdownMenuButton(
text = { Text("Newest first") },
text = { Text("Enabled") },
items = listOf("Item 1", "Item 2", "Item 3"),
onItemClick = {},
itemText = { item -> Text(item) }
)
NiaDropdownMenuButton(
text = { Text("Disabled") },
items = listOf("Item 1", "Item 2", "Item 3"),
onItemClick = {},
itemText = { item -> Text(item) },
enabled = false
)
}
}
item { Text("Chips", Modifier.padding(top = 16.dp)) }
item {
@ -418,28 +191,33 @@ fun NiaCatalog() {
NiaFilterChip(
selected = firstChecked,
onSelectedChange = { checked -> firstChecked = checked },
label = { Text(text = "Enabled".uppercase()) }
label = { Text(text = "Enabled") }
)
var secondChecked by remember { mutableStateOf(true) }
NiaFilterChip(
selected = secondChecked,
onSelectedChange = { checked -> secondChecked = checked },
label = { Text(text = "Enabled".uppercase()) }
label = { Text(text = "Enabled") }
)
NiaFilterChip(
selected = false,
onSelectedChange = {},
enabled = false,
label = { Text(text = "Disabled") }
)
var thirdChecked by remember { mutableStateOf(true) }
NiaFilterChip(
selected = thirdChecked,
onSelectedChange = { checked -> thirdChecked = checked },
selected = true,
onSelectedChange = {},
enabled = false,
label = { Text(text = "Disabled".uppercase()) }
label = { Text(text = "Disabled") }
)
}
}
item { Text("Toggle buttons", Modifier.padding(top = 16.dp)) }
item { Text("Icon buttons", Modifier.padding(top = 16.dp)) }
item {
FlowRow(mainAxisSpacing = 16.dp) {
var firstChecked by remember { mutableStateOf(false) }
NiaToggleButton(
NiaIconToggleButton(
checked = firstChecked,
onCheckedChange = { checked -> firstChecked = checked },
icon = {
@ -456,7 +234,7 @@ fun NiaCatalog() {
}
)
var secondChecked by remember { mutableStateOf(true) }
NiaToggleButton(
NiaIconToggleButton(
checked = secondChecked,
onCheckedChange = { checked -> secondChecked = checked },
icon = {
@ -472,27 +250,39 @@ fun NiaCatalog() {
)
}
)
var thirdChecked by remember { mutableStateOf(false) }
NiaToggleButton(
checked = thirdChecked,
onCheckedChange = { checked -> thirdChecked = checked },
NiaIconToggleButton(
checked = false,
onCheckedChange = {},
icon = {
Icon(imageVector = NiaIcons.Add, contentDescription = null)
Icon(
painter = painterResource(id = NiaIcons.BookmarkBorder),
contentDescription = null
)
},
checkedIcon = {
Icon(imageVector = NiaIcons.Check, contentDescription = null)
}
Icon(
painter = painterResource(id = NiaIcons.Bookmark),
contentDescription = null
)
},
enabled = false
)
var fourthChecked by remember { mutableStateOf(true) }
NiaToggleButton(
checked = fourthChecked,
onCheckedChange = { checked -> fourthChecked = checked },
NiaIconToggleButton(
checked = true,
onCheckedChange = {},
icon = {
Icon(imageVector = NiaIcons.Add, contentDescription = null)
Icon(
painter = painterResource(id = NiaIcons.BookmarkBorder),
contentDescription = null
)
},
checkedIcon = {
Icon(imageVector = NiaIcons.Check, contentDescription = null)
}
Icon(
painter = painterResource(id = NiaIcons.Bookmark),
contentDescription = null
)
},
enabled = false
)
}
}
@ -513,6 +303,13 @@ fun NiaCatalog() {
compactText = { Text(text = "Compact view") },
expandedText = { Text(text = "Expanded view") }
)
NiaViewToggleButton(
expanded = false,
onExpandedChange = {},
compactText = { Text(text = "Disabled") },
expandedText = { Text(text = "Disabled") },
enabled = false
)
}
}
item { Text("Tags", Modifier.padding(top = 16.dp)) }
@ -524,7 +321,7 @@ fun NiaCatalog() {
NiaTopicTag(
expanded = expandedTopicId == "Topic 1",
followed = firstFollowed,
onDropMenuToggle = { show ->
onDropdownMenuToggle = { show ->
expandedTopicId = if (show) "Topic 1" else null
},
onFollowClick = { firstFollowed = true },
@ -539,7 +336,7 @@ fun NiaCatalog() {
NiaTopicTag(
expanded = expandedTopicId == "Topic 2",
followed = secondFollowed,
onDropMenuToggle = { show ->
onDropdownMenuToggle = { show ->
expandedTopicId = if (show) "Topic 2" else null
},
onFollowClick = { secondFollowed = true },
@ -550,6 +347,16 @@ fun NiaCatalog() {
unFollowText = { Text(text = "Unfollow") },
browseText = { Text(text = "Browse topic") }
)
NiaTopicTag(
expanded = false,
followed = false,
onDropdownMenuToggle = {},
onFollowClick = {},
onUnfollowClick = {},
onBrowseClick = {},
text = { Text(text = "Disabled".uppercase()) },
enabled = false
)
}
}
item { Text("Tabs", Modifier.padding(top = 16.dp)) }

@ -32,7 +32,6 @@ import com.google.samples.apps.nowinandroid.core.designsystem.theme.GradientColo
import com.google.samples.apps.nowinandroid.core.designsystem.theme.LightAndroidBackgroundTheme
import com.google.samples.apps.nowinandroid.core.designsystem.theme.LightAndroidColorScheme
import com.google.samples.apps.nowinandroid.core.designsystem.theme.LightDefaultColorScheme
import com.google.samples.apps.nowinandroid.core.designsystem.theme.LightDefaultGradientColors
import com.google.samples.apps.nowinandroid.core.designsystem.theme.LocalBackgroundTheme
import com.google.samples.apps.nowinandroid.core.designsystem.theme.LocalGradientColors
import com.google.samples.apps.nowinandroid.core.designsystem.theme.NiaTheme
@ -63,7 +62,11 @@ class ThemeTest {
) {
val colorScheme = LightDefaultColorScheme
assertColorSchemesEqual(colorScheme, MaterialTheme.colorScheme)
val gradientColors = LightDefaultGradientColors
val gradientColors = GradientColors(
top = colorScheme.inverseOnSurface,
bottom = colorScheme.primaryContainer,
container = colorScheme.surface
)
assertEquals(gradientColors, LocalGradientColors.current)
val backgroundTheme = BackgroundTheme(
color = colorScheme.surface,
@ -84,7 +87,11 @@ class ThemeTest {
) {
val colorScheme = DarkDefaultColorScheme
assertColorSchemesEqual(colorScheme, MaterialTheme.colorScheme)
val gradientColors = GradientColors()
val gradientColors = GradientColors(
top = colorScheme.inverseOnSurface,
bottom = colorScheme.primaryContainer,
container = colorScheme.surface
)
assertEquals(gradientColors, LocalGradientColors.current)
val backgroundTheme = BackgroundTheme(
color = colorScheme.surface,
@ -111,7 +118,11 @@ class ThemeTest {
val gradientColors = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
GradientColors()
} else {
LightDefaultGradientColors
GradientColors(
top = colorScheme.inverseOnSurface,
bottom = colorScheme.primaryContainer,
container = colorScheme.surface
)
}
assertEquals(gradientColors, LocalGradientColors.current)
val backgroundTheme = BackgroundTheme(
@ -136,7 +147,15 @@ class ThemeTest {
DarkDefaultColorScheme
}
assertColorSchemesEqual(colorScheme, MaterialTheme.colorScheme)
val gradientColors = GradientColors()
val gradientColors = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
GradientColors()
} else {
GradientColors(
top = colorScheme.inverseOnSurface,
bottom = colorScheme.primaryContainer,
container = colorScheme.surface
)
}
assertEquals(gradientColors, LocalGradientColors.current)
val backgroundTheme = BackgroundTheme(
color = colorScheme.surface,
@ -192,7 +211,15 @@ class ThemeTest {
) {
val colorScheme = LightAndroidColorScheme
assertColorSchemesEqual(colorScheme, MaterialTheme.colorScheme)
val gradientColors = GradientColors()
val gradientColors = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
GradientColors()
} else {
GradientColors(
top = colorScheme.inverseOnSurface,
bottom = colorScheme.primaryContainer,
container = colorScheme.surface
)
}
assertEquals(gradientColors, LocalGradientColors.current)
val backgroundTheme = LightAndroidBackgroundTheme
assertEquals(backgroundTheme, LocalBackgroundTheme.current)
@ -209,7 +236,15 @@ class ThemeTest {
) {
val colorScheme = DarkAndroidColorScheme
assertColorSchemesEqual(colorScheme, MaterialTheme.colorScheme)
val gradientColors = GradientColors()
val gradientColors = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
GradientColors()
} else {
GradientColors(
top = colorScheme.inverseOnSurface,
bottom = colorScheme.primaryContainer,
container = colorScheme.surface
)
}
assertEquals(gradientColors, LocalGradientColors.current)
val backgroundTheme = DarkAndroidBackgroundTheme
assertEquals(backgroundTheme, LocalBackgroundTheme.current)
@ -249,6 +284,8 @@ class ThemeTest {
assertEquals(expectedColorScheme.onSurface, actualColorScheme.onSurface)
assertEquals(expectedColorScheme.surfaceVariant, actualColorScheme.surfaceVariant)
assertEquals(expectedColorScheme.onSurfaceVariant, actualColorScheme.onSurfaceVariant)
assertEquals(expectedColorScheme.inverseSurface, actualColorScheme.inverseSurface)
assertEquals(expectedColorScheme.inverseOnSurface, actualColorScheme.inverseOnSurface)
assertEquals(expectedColorScheme.outline, actualColorScheme.outline)
}
}

@ -41,7 +41,7 @@ import kotlin.math.tan
/**
* The main background for the app.
* Uses [LocalBackgroundTheme] to set the color and tonal elevation of a [Box].
* Uses [LocalBackgroundTheme] to set the color and tonal elevation of a [Surface].
*
* @param modifier Modifier to be applied to the background.
* @param content The background content.
@ -66,23 +66,28 @@ fun NiaBackground(
/**
* A gradient background for select screens. Uses [LocalBackgroundTheme] to set the gradient colors
* of a [Box].
* of a [Box] within a [Surface].
*
* @param modifier Modifier to be applied to the background.
* @param topColor The top gradient color to be rendered.
* @param bottomColor The bottom gradient color to be rendered.
* @param containerColor The container color over which the gradient will be rendered.
* @param content The background content.
*/
@Composable
fun NiaGradientBackground(
modifier: Modifier = Modifier,
topColor: Color = LocalGradientColors.current.primary,
bottomColor: Color = LocalGradientColors.current.secondary,
topColor: Color = LocalGradientColors.current.top,
bottomColor: Color = LocalGradientColors.current.bottom,
containerColor: Color = LocalGradientColors.current.container,
content: @Composable () -> Unit
) {
val currentTopColor by rememberUpdatedState(topColor)
val currentBottomColor by rememberUpdatedState(bottomColor)
NiaBackground(modifier) {
Surface(
color = if (containerColor == Color.Unspecified) Color.Transparent else containerColor,
modifier = modifier.fillMaxSize()
) {
Box(
Modifier
.fillMaxSize()

@ -20,20 +20,15 @@ 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
/**
@ -43,38 +38,27 @@ import androidx.compose.ui.unit.dp
* @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].
* content.
* @param content The button content.
*/
@Composable
fun NiaFilledButton(
fun NiaButton(
onClick: () -> Unit,
modifier: Modifier = Modifier,
enabled: Boolean = true,
small: Boolean = false,
colors: ButtonColors = NiaButtonDefaults.filledButtonColors(),
contentPadding: PaddingValues = NiaButtonDefaults.buttonContentPadding(small = small),
contentPadding: PaddingValues = ButtonDefaults.ContentPadding,
content: @Composable RowScope.() -> Unit
) {
Button(
onClick = onClick,
modifier = if (small) {
modifier.heightIn(min = NiaButtonDefaults.SmallButtonHeight)
} else {
modifier
},
modifier = modifier,
enabled = enabled,
colors = colors,
colors = ButtonDefaults.buttonColors(
containerColor = MaterialTheme.colorScheme.onBackground
),
contentPadding = contentPadding,
content = {
ProvideTextStyle(value = MaterialTheme.typography.labelSmall) {
content()
}
}
content = content
)
}
@ -85,40 +69,30 @@ fun NiaFilledButton(
* @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(
fun NiaButton(
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
leadingIcon: @Composable (() -> Unit)? = null
) {
NiaFilledButton(
NiaButton(
onClick = onClick,
modifier = modifier,
enabled = enabled,
small = small,
colors = colors,
contentPadding = NiaButtonDefaults.buttonContentPadding(
small = small,
leadingIcon = leadingIcon != null,
trailingIcon = trailingIcon != null
)
contentPadding = if (leadingIcon != null) {
ButtonDefaults.ButtonWithIconContentPadding
} else {
ButtonDefaults.ContentPadding
}
) {
NiaButtonContent(
text = text,
leadingIcon = leadingIcon,
trailingIcon = trailingIcon
leadingIcon = leadingIcon
)
}
}
@ -130,12 +104,8 @@ fun NiaFilledButton(
* @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].
* content.
* @param content The button content.
*/
@Composable
@ -143,28 +113,28 @@ 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),
contentPadding: PaddingValues = ButtonDefaults.ContentPadding,
content: @Composable RowScope.() -> Unit
) {
OutlinedButton(
onClick = onClick,
modifier = if (small) {
modifier.heightIn(min = NiaButtonDefaults.SmallButtonHeight)
} else {
modifier
},
modifier = modifier,
enabled = enabled,
border = border,
colors = colors,
contentPadding = contentPadding,
content = {
ProvideTextStyle(value = MaterialTheme.typography.labelSmall) {
content()
}
colors = ButtonDefaults.outlinedButtonColors(
contentColor = MaterialTheme.colorScheme.onBackground
),
border = BorderStroke(
width = NiaButtonDefaults.OutlinedButtonBorderWidth,
color = if (enabled) {
MaterialTheme.colorScheme.outline
} else {
MaterialTheme.colorScheme.onSurface.copy(
alpha = NiaButtonDefaults.DisabledOutlinedButtonBorderAlpha
)
}
),
contentPadding = contentPadding,
content = content
)
}
@ -175,43 +145,30 @@ fun NiaOutlinedButton(
* @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
leadingIcon: @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
)
contentPadding = if (leadingIcon != null) {
ButtonDefaults.ButtonWithIconContentPadding
} else {
ButtonDefaults.ContentPadding
}
) {
NiaButtonContent(
text = text,
leadingIcon = leadingIcon,
trailingIcon = trailingIcon
leadingIcon = leadingIcon
)
}
}
@ -223,11 +180,6 @@ fun NiaOutlinedButton(
* @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
@ -235,26 +187,16 @@ 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)
} else {
modifier
},
modifier = modifier,
enabled = enabled,
colors = colors,
contentPadding = contentPadding,
content = {
ProvideTextStyle(value = MaterialTheme.typography.labelSmall) {
content()
}
}
colors = ButtonDefaults.textButtonColors(
contentColor = MaterialTheme.colorScheme.onBackground
),
content = content
)
}
@ -265,60 +207,42 @@ fun NiaTextButton(
* @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
leadingIcon: @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
)
enabled = enabled
) {
NiaButtonContent(
text = text,
leadingIcon = leadingIcon,
trailingIcon = trailingIcon
leadingIcon = leadingIcon
)
}
}
/**
* Internal Now in Android button content layout for arranging the text label, leading icon and
* trailing icon.
* Internal Now in Android button content layout for arranging the text label and leading 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(
private fun NiaButtonContent(
text: @Composable () -> Unit,
leadingIcon: @Composable (() -> Unit)?,
trailingIcon: @Composable (() -> Unit)?
leadingIcon: @Composable (() -> Unit)?
) {
if (leadingIcon != null) {
Box(Modifier.sizeIn(maxHeight = NiaButtonDefaults.ButtonIconSize)) {
Box(Modifier.sizeIn(maxHeight = ButtonDefaults.IconSize)) {
leadingIcon()
}
}
@ -326,12 +250,7 @@ private fun RowScope.NiaButtonContent(
Modifier
.padding(
start = if (leadingIcon != null) {
NiaButtonDefaults.ButtonContentSpacing
} else {
0.dp
},
end = if (trailingIcon != null) {
NiaButtonDefaults.ButtonContentSpacing
ButtonDefaults.IconSpacing
} else {
0.dp
}
@ -339,104 +258,16 @@ private fun RowScope.NiaButtonContent(
) {
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
)
// TODO: File bug
// OutlinedButton border color doesn't respect disabled state by default
const val DisabledOutlinedButtonBorderAlpha = 0.12f
// TODO: File bug
// OutlinedButton default border width isn't exposed via ButtonDefaults
val OutlinedButtonBorderWidth = 1.dp
}

@ -58,11 +58,15 @@ fun NiaFilterChip(
},
modifier = modifier,
enabled = enabled,
trailingIcon = {
leadingIcon = if (selected) {
{
Icon(
imageVector = NiaIcons.Check,
contentDescription = null
)
}
} else {
null
},
shape = CircleShape,
border = FilterChipDefaults.filterChipBorder(
@ -74,11 +78,9 @@ fun NiaFilterChip(
disabledSelectedBorderColor = MaterialTheme.colorScheme.onBackground.copy(
alpha = NiaChipDefaults.DisabledChipContentAlpha
),
borderWidth = NiaChipDefaults.ChipBorderWidth,
selectedBorderWidth = NiaChipDefaults.ChipBorderWidth
),
colors = FilterChipDefaults.filterChipColors(
containerColor = Color.Transparent,
labelColor = MaterialTheme.colorScheme.onBackground,
iconColor = MaterialTheme.colorScheme.onBackground,
disabledContainerColor = if (selected) {
@ -105,6 +107,8 @@ fun NiaFilterChip(
* Now in Android chip default values.
*/
object NiaChipDefaults {
// TODO: File bug
// FilterChip default values aren't exposed via FilterChipDefaults
const val DisabledChipContainerAlpha = 0.12f
const val DisabledChipContentAlpha = 0.38f
val ChipBorderWidth = 1.dp

@ -16,16 +16,25 @@
package com.google.samples.apps.nowinandroid.core.designsystem.component
import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.sizeIn
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.DropdownMenu
import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.ProvideTextStyle
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.unit.dp
import com.google.samples.apps.nowinandroid.core.designsystem.icon.NiaIcons
/**
@ -56,9 +65,25 @@ fun <T> NiaDropdownMenuButton(
) {
var expanded by remember { mutableStateOf(false) }
Box(modifier = modifier) {
NiaOutlinedButton(
OutlinedButton(
onClick = { expanded = true },
enabled = enabled,
colors = ButtonDefaults.outlinedButtonColors(
contentColor = MaterialTheme.colorScheme.onBackground
),
border = BorderStroke(
width = NiaDropdownMenuDefaults.DropdownMenuButtonBorderWidth,
color = if (enabled) {
MaterialTheme.colorScheme.outline
} else {
MaterialTheme.colorScheme.onSurface.copy(
alpha = NiaDropdownMenuDefaults.DisabledDropdownMenuButtonBorderAlpha
)
}
),
contentPadding = NiaDropdownMenuDefaults.DropdownMenuButtonContentPadding
) {
NiaDropdownMenuButtonContent(
text = text,
trailingIcon = {
Icon(
@ -67,6 +92,7 @@ fun <T> NiaDropdownMenuButton(
)
}
)
}
NiaDropdownMenu(
expanded = expanded,
onDismissRequest = { expanded = false },
@ -80,6 +106,39 @@ fun <T> NiaDropdownMenuButton(
}
}
/**
* Internal Now in Android dropdown menu button content layout for arranging the text label and
* trailing icon.
*
* @param text The button text label content.
* @param trailingIcon The button trailing icon content. Pass `null` here for no trailing icon.
*/
@Composable
private fun NiaDropdownMenuButtonContent(
text: @Composable () -> Unit,
trailingIcon: @Composable (() -> Unit)?,
) {
Box(
Modifier
.padding(
end = if (trailingIcon != null) {
ButtonDefaults.IconSpacing
} else {
0.dp
}
)
) {
ProvideTextStyle(value = MaterialTheme.typography.labelSmall) {
text()
}
}
if (trailingIcon != null) {
Box(Modifier.sizeIn(maxHeight = ButtonDefaults.IconSize)) {
trailingIcon()
}
}
}
/**
* Now in Android dropdown menu with item content slots. Wraps Material 3 [DropdownMenu] and
* [DropdownMenuItem].
@ -130,3 +189,24 @@ fun <T> NiaDropdownMenu(
}
}
}
/**
* Now in Android dropdown menu default values.
*/
object NiaDropdownMenuDefaults {
// TODO: File bug
// OutlinedButton border color doesn't respect disabled state by default
const val DisabledDropdownMenuButtonBorderAlpha = 0.12f
// TODO: File bug
// OutlinedButton default border width isn't exposed via ButtonDefaults
val DropdownMenuButtonBorderWidth = 1.dp
// TODO: File bug
// Various default button padding values aren't exposed via ButtonDefaults
val DropdownMenuButtonContentPadding =
PaddingValues(
start = 24.dp,
top = 8.dp,
end = 16.dp,
bottom = 8.dp
)
}

@ -0,0 +1,80 @@
/*
* 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.designsystem.component
import androidx.compose.material3.FilledIconToggleButton
import androidx.compose.material3.IconButton
import androidx.compose.material3.IconButtonDefaults
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.graphics.Color.Companion
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 NiaIconToggleButton(
checked: Boolean,
onCheckedChange: (Boolean) -> Unit,
modifier: Modifier = Modifier,
enabled: Boolean = true,
icon: @Composable () -> Unit,
checkedIcon: @Composable () -> Unit = icon
) {
// TODO: File bug
// Can't use regular IconToggleButton as it doesn't include a shape (appears square)
FilledIconToggleButton(
checked = checked,
onCheckedChange = onCheckedChange,
modifier = modifier,
enabled = enabled,
colors = IconButtonDefaults.iconToggleButtonColors(
checkedContainerColor = MaterialTheme.colorScheme.primaryContainer,
checkedContentColor = MaterialTheme.colorScheme.onPrimaryContainer,
disabledContainerColor = if (checked) {
MaterialTheme.colorScheme.onBackground.copy(
alpha = NiaIconButtonDefaults.DisabledIconButtonContainerAlpha
)
} else {
Color.Transparent
}
)
) {
if (checked) checkedIcon() else icon()
}
}
/**
* Now in Android icon button default values.
*/
object NiaIconButtonDefaults {
// TODO: File bug
// IconToggleButton disabled container alpha not exposed by IconButtonDefaults
const val DisabledIconButtonContainerAlpha = 0.12f
}

@ -17,24 +17,26 @@
package com.google.samples.apps.nowinandroid.core.designsystem.component
import androidx.compose.foundation.layout.Box
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.ProvideTextStyle
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.material3.contentColorFor
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.stringResource
import com.google.samples.apps.nowinandroid.core.designsystem.R
@Composable
fun NiaTopicTag(
modifier: Modifier = Modifier,
expanded: Boolean = false,
followed: Boolean,
onDropMenuToggle: (show: Boolean) -> Unit = {},
onDropdownMenuToggle: (show: Boolean) -> Unit = {},
onFollowClick: () -> Unit,
onUnfollowClick: () -> Unit,
onBrowseClick: () -> Unit,
modifier: Modifier = Modifier,
enabled: Boolean = true,
text: @Composable () -> Unit,
followText: @Composable () -> Unit = { Text(stringResource(R.string.follow)) },
@ -46,28 +48,28 @@ fun NiaTopicTag(
val containerColor = if (followed) {
MaterialTheme.colorScheme.primaryContainer
} else {
MaterialTheme.colorScheme.surfaceVariant
MaterialTheme.colorScheme.surfaceVariant.copy(
alpha = NiaTagDefaults.UnfollowedTopicTagContainerAlpha
)
}
NiaTextButton(
onClick = { onDropMenuToggle(true) },
TextButton(
onClick = { onDropdownMenuToggle(true) },
enabled = enabled,
small = true,
colors = NiaButtonDefaults.textButtonColors(
colors = ButtonDefaults.textButtonColors(
containerColor = containerColor,
contentColor = contentColorFor(backgroundColor = containerColor),
disabledContainerColor = if (followed) {
MaterialTheme.colorScheme.onBackground.copy(
alpha = NiaButtonDefaults.DisabledButtonContentAlpha
disabledContainerColor = MaterialTheme.colorScheme.onSurface.copy(
alpha = NiaTagDefaults.DisabledTopicTagContainerAlpha
)
} else {
Color.Transparent
}
),
text = text
)
) {
ProvideTextStyle(value = MaterialTheme.typography.labelSmall) {
text()
}
}
NiaDropdownMenu(
expanded = expanded,
onDismissRequest = { onDropMenuToggle(false) },
onDismissRequest = { onDropdownMenuToggle(false) },
items = if (followed) listOf(UNFOLLOW, BROWSE) else listOf(FOLLOW, BROWSE),
onItemClick = { item ->
when (item) {
@ -87,6 +89,16 @@ fun NiaTopicTag(
}
}
/**
* Now in Android tag default values.
*/
object NiaTagDefaults {
const val UnfollowedTopicTagContainerAlpha = 0.5f
// TODO: File bug
// Button disabled container alpha value not exposed by ButtonDefaults
const val DisabledTopicTagContainerAlpha = 0.12f
}
private const val FOLLOW = 1
private const val UNFOLLOW = 2
private const val BROWSE = 3

@ -1,107 +0,0 @@
/*
* 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.designsystem.component
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.sizeIn
import androidx.compose.foundation.selection.toggleable
import androidx.compose.material3.IconButton
import androidx.compose.material3.LocalContentColor
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.contentColorFor
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.drawBehind
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.semantics.Role
import androidx.compose.ui.unit.Dp
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.
* @param size The size of the toggle button.
* @param iconSize The size of the icon.
* @param backgroundColor The background color when unchecked.
* @param checkedBackgroundColor The background color when checked.
* @param iconColor The icon color when unchecked.
* @param iconColor The icon color when checked.
*/
@Composable
fun NiaToggleButton(
checked: Boolean,
onCheckedChange: (Boolean) -> Unit,
modifier: Modifier = Modifier,
enabled: Boolean = true,
icon: @Composable () -> Unit,
checkedIcon: @Composable () -> Unit = icon,
size: Dp = NiaToggleButtonDefaults.ToggleButtonSize,
iconSize: Dp = NiaToggleButtonDefaults.ToggleButtonIconSize,
backgroundColor: Color = Color.Transparent,
checkedBackgroundColor: Color = MaterialTheme.colorScheme.primaryContainer,
iconColor: Color = contentColorFor(backgroundColor),
checkedIconColor: Color = contentColorFor(checkedBackgroundColor)
) {
val radius = with(LocalDensity.current) { (size / 2).toPx() }
IconButton(
onClick = { onCheckedChange(!checked) },
modifier = modifier
.size(size)
.toggleable(value = checked, enabled = enabled, role = Role.Button, onValueChange = {
onCheckedChange(!checked)
})
.drawBehind {
drawCircle(
color = if (checked) checkedBackgroundColor else backgroundColor,
radius = radius
)
},
enabled = enabled,
content = {
Box(
modifier = Modifier.sizeIn(
maxWidth = iconSize,
maxHeight = iconSize
)
) {
val contentColor = if (checked) checkedIconColor else iconColor
CompositionLocalProvider(LocalContentColor provides contentColor) {
if (checked) checkedIcon() else icon()
}
}
}
)
}
/**
* Now in Android toggle button default values.
*/
object NiaToggleButtonDefaults {
val ToggleButtonSize = 40.dp
val ToggleButtonIconSize = 18.dp
}

@ -14,6 +14,8 @@
* limitations under the License.
*/
@file:OptIn(ExperimentalMaterial3Api::class)
package com.google.samples.apps.nowinandroid.core.designsystem.component
import androidx.annotation.StringRes

@ -16,9 +16,18 @@
package com.google.samples.apps.nowinandroid.core.designsystem.component
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.sizeIn
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.ProvideTextStyle
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import com.google.samples.apps.nowinandroid.core.designsystem.icon.NiaIcons
/**
@ -42,10 +51,16 @@ fun NiaViewToggleButton(
compactText: @Composable () -> Unit,
expandedText: @Composable () -> Unit
) {
NiaTextButton(
TextButton(
onClick = { onExpandedChange(!expanded) },
modifier = modifier,
enabled = enabled,
colors = ButtonDefaults.textButtonColors(
contentColor = MaterialTheme.colorScheme.onBackground
),
contentPadding = NiaViewToggleDefaults.ViewToggleButtonContentPadding
) {
NiaViewToggleButtonContent(
text = if (expanded) expandedText else compactText,
trailingIcon = {
Icon(
@ -54,4 +69,53 @@ fun NiaViewToggleButton(
)
}
)
}
}
/**
* Internal Now in Android view toggle button content layout for arranging the text label and
* trailing icon.
*
* @param text The button text label content.
* @param trailingIcon The button trailing icon content. Pass `null` here for no trailing icon.
*/
@Composable
private fun NiaViewToggleButtonContent(
text: @Composable () -> Unit,
trailingIcon: @Composable (() -> Unit)?,
) {
Box(
Modifier
.padding(
end = if (trailingIcon != null) {
ButtonDefaults.IconSpacing
} else {
0.dp
}
)
) {
ProvideTextStyle(value = MaterialTheme.typography.labelSmall) {
text()
}
}
if (trailingIcon != null) {
Box(Modifier.sizeIn(maxHeight = ButtonDefaults.IconSize)) {
trailingIcon()
}
}
}
/**
* Now in Android view toggle default values.
*/
object NiaViewToggleDefaults {
// TODO: File bug
// Various default button padding values aren't exposed via ButtonDefaults
val ViewToggleButtonContentPadding =
PaddingValues(
start = 16.dp,
top = 8.dp,
end = 12.dp,
bottom = 8.dp
)
}

@ -18,12 +18,12 @@ package com.google.samples.apps.nowinandroid.core.designsystem.icon
import androidx.annotation.DrawableRes
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowDropDown
import androidx.compose.material.icons.filled.ArrowDropUp
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
@ -48,8 +48,8 @@ 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 ArrowDropDown = Icons.Default.ArrowDropDown
val ArrowDropUp = Icons.Default.ArrowDropUp
val Bookmark = R.drawable.ic_bookmark
val BookmarkBorder = R.drawable.ic_bookmark_border
val Bookmarks = R.drawable.ic_bookmarks

@ -21,13 +21,13 @@ import androidx.compose.ui.graphics.Color
/**
* Now in Android colors.
*/
internal val Blue10 = Color(0xFF001F29)
internal val Blue10 = Color(0xFF001F28)
internal val Blue20 = Color(0xFF003544)
internal val Blue30 = Color(0xFF004D61)
internal val Blue40 = Color(0xFF006781)
internal val Blue80 = Color(0xFF5DD4FB)
internal val Blue90 = Color(0xFFB5EAFF)
internal val Blue95 = Color(0xFFDCF5FF)
internal val Blue40 = Color(0xFF006780)
internal val Blue80 = Color(0xFF5DD5FC)
internal val Blue90 = Color(0xFFB8EAFF)
internal val Blue95 = Color(0xFFDDF4FF)
internal val DarkGreen10 = Color(0xFF0D1F12)
internal val DarkGreen20 = Color(0xFF223526)
internal val DarkGreen30 = Color(0xFF394B3C)
@ -35,10 +35,12 @@ internal val DarkGreen40 = Color(0xFF4F6352)
internal val DarkGreen80 = Color(0xFFB7CCB8)
internal val DarkGreen90 = Color(0xFFD3E8D3)
internal val DarkGreenGray10 = Color(0xFF1A1C1A)
internal val DarkGreenGray20 = Color(0xFF2F312E)
internal val DarkGreenGray90 = Color(0xFFE2E3DE)
internal val DarkGreenGray95 = Color(0xFFF0F1EC)
internal val DarkGreenGray99 = Color(0xFFFBFDF7)
internal val DarkPurpleGray10 = Color(0xFF201A1B)
internal val DarkPurpleGray20 = Color(0xFF362F30)
internal val DarkPurpleGray90 = Color(0xFFECDFE0)
internal val DarkPurpleGray95 = Color(0xFFFAEEEF)
internal val DarkPurpleGray99 = Color(0xFFFCFCFC)
@ -53,31 +55,31 @@ internal val GreenGray50 = Color(0xFF727971)
internal val GreenGray60 = Color(0xFF8B938A)
internal val GreenGray80 = Color(0xFFC1C9BF)
internal val GreenGray90 = Color(0xFFDDE5DB)
internal val Orange10 = Color(0xFF390C00)
internal val Orange20 = Color(0xFF5D1900)
internal val Orange10 = Color(0xFF380D00)
internal val Orange20 = Color(0xFF5B1A00)
internal val Orange30 = Color(0xFF812800)
internal val Orange40 = Color(0xFFA23F16)
internal val Orange80 = Color(0xFFFFB599)
internal val Orange90 = Color(0xFFFFDBCE)
internal val Orange95 = Color(0xFFFFEDE6)
internal val Purple10 = Color(0xFF36003D)
internal val Purple20 = Color(0xFF560A5E)
internal val Orange80 = Color(0xFFFFB59B)
internal val Orange90 = Color(0xFFFFDBCF)
internal val Orange95 = Color(0xFFFFEDE8)
internal val Purple10 = Color(0xFF36003C)
internal val Purple20 = Color(0xFF560A5D)
internal val Purple30 = Color(0xFF702776)
internal val Purple40 = Color(0xFF8C4190)
internal val Purple80 = Color(0xFFFFA8FF)
internal val Purple90 = Color(0xFFFFD5FC)
internal val Purple95 = Color(0xFFFFEBFB)
internal val PurpleGray30 = Color(0xFF4E444C)
internal val Purple40 = Color(0xFF8B418F)
internal val Purple80 = Color(0xFFFFA9FE)
internal val Purple90 = Color(0xFFFFD6FA)
internal val Purple95 = Color(0xFFFFEBFA)
internal val PurpleGray30 = Color(0xFF4D444C)
internal val PurpleGray50 = Color(0xFF7F747C)
internal val PurpleGray60 = Color(0xFF998D96)
internal val PurpleGray80 = Color(0xFFD0C2CC)
internal val PurpleGray80 = Color(0xFFD0C3CC)
internal val PurpleGray90 = Color(0xFFEDDEE8)
internal val Red10 = Color(0xFF410001)
internal val Red20 = Color(0xFF680003)
internal val Red30 = Color(0xFF930006)
internal val Red40 = Color(0xFFBA1B1B)
internal val Red80 = Color(0xFFFFB4A9)
internal val Red90 = Color(0xFFFFDAD4)
internal val Red10 = Color(0xFF410002)
internal val Red20 = Color(0xFF690005)
internal val Red30 = Color(0xFF93000A)
internal val Red40 = Color(0xFFBA1A1A)
internal val Red80 = Color(0xFFFFB4AB)
internal val Red90 = Color(0xFFFFDAD6)
internal val Teal10 = Color(0xFF001F26)
internal val Teal20 = Color(0xFF02363F)
internal val Teal30 = Color(0xFF214D56)

@ -25,10 +25,9 @@ import androidx.compose.ui.graphics.Color
*/
@Immutable
data class GradientColors(
val primary: Color = Color.Unspecified,
val secondary: Color = Color.Unspecified,
val tertiary: Color = Color.Unspecified,
val neutral: Color = Color.Unspecified
val top: Color = Color.Unspecified,
val bottom: Color = Color.Unspecified,
val container: Color = Color.Unspecified
)
/**

@ -58,6 +58,8 @@ val LightDefaultColorScheme = lightColorScheme(
onSurface = DarkPurpleGray10,
surfaceVariant = PurpleGray90,
onSurfaceVariant = PurpleGray30,
inverseSurface = DarkPurpleGray20,
inverseOnSurface = DarkPurpleGray95,
outline = PurpleGray50
)
@ -88,6 +90,8 @@ val DarkDefaultColorScheme = darkColorScheme(
onSurface = DarkPurpleGray90,
surfaceVariant = PurpleGray30,
onSurfaceVariant = PurpleGray80,
inverseSurface = DarkPurpleGray90,
inverseOnSurface = DarkPurpleGray10,
outline = PurpleGray60
)
@ -118,6 +122,8 @@ val LightAndroidColorScheme = lightColorScheme(
onSurface = DarkGreenGray10,
surfaceVariant = GreenGray90,
onSurfaceVariant = GreenGray30,
inverseSurface = DarkGreenGray20,
inverseOnSurface = DarkGreenGray95,
outline = GreenGray50
)
@ -148,19 +154,11 @@ val DarkAndroidColorScheme = darkColorScheme(
onSurface = DarkGreenGray90,
surfaceVariant = GreenGray30,
onSurfaceVariant = GreenGray80,
inverseSurface = DarkGreenGray90,
inverseOnSurface = DarkGreenGray10,
outline = GreenGray60
)
/**
* Light default gradient colors
*/
val LightDefaultGradientColors = GradientColors(
primary = Purple95,
secondary = Orange95,
tertiary = Blue95,
neutral = DarkPurpleGray95
)
/**
* Light Android background theme
*/
@ -207,22 +205,38 @@ internal fun NiaTheme(
disableDynamicTheming: Boolean,
content: @Composable () -> Unit
) {
val colorScheme = if (androidTheme) {
if (darkTheme) DarkAndroidColorScheme else LightAndroidColorScheme
} else if (!disableDynamicTheming && supportsDynamicTheming()) {
// Color scheme
val colorScheme = when {
!disableDynamicTheming -> {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
val context = LocalContext.current
if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context)
} else {
if (darkTheme) DarkDefaultColorScheme else LightDefaultColorScheme
}
val defaultGradientColors = GradientColors()
val gradientColors = if (androidTheme || (!disableDynamicTheming && supportsDynamicTheming())) {
defaultGradientColors
}
androidTheme -> if (darkTheme) DarkAndroidColorScheme else LightAndroidColorScheme
else -> if (darkTheme) DarkDefaultColorScheme else LightDefaultColorScheme
}
// Gradient colors
val emptyGradientColors = GradientColors()
val defaultGradientColors = GradientColors(
top = colorScheme.inverseOnSurface,
bottom = colorScheme.primaryContainer,
container = colorScheme.surface
)
val gradientColors = when {
!disableDynamicTheming -> {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
emptyGradientColors
} else {
if (darkTheme) defaultGradientColors else LightDefaultGradientColors
defaultGradientColors
}
}
androidTheme -> emptyGradientColors
else -> defaultGradientColors
}
// Background theme
val defaultBackgroundTheme = BackgroundTheme(
color = colorScheme.surface,
tonalElevation = 2.dp
@ -232,7 +246,7 @@ internal fun NiaTheme(
} else {
defaultBackgroundTheme
}
// Composition locals
CompositionLocalProvider(
LocalGradientColors provides gradientColors,
LocalBackgroundTheme provides backgroundTheme

@ -18,98 +18,101 @@ package com.google.samples.apps.nowinandroid.core.designsystem.theme
import androidx.compose.material3.Typography
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.sp
/**
* Now in Android typography.
*
* TODO: Add custom font
*/
internal val NiaTypography = Typography(
displayLarge = TextStyle(
fontWeight = FontWeight.W400,
fontWeight = FontWeight.Normal,
fontSize = 57.sp,
lineHeight = 64.sp,
letterSpacing = (-0.25).sp
),
displayMedium = TextStyle(
fontWeight = FontWeight.W400,
fontWeight = FontWeight.Normal,
fontSize = 45.sp,
lineHeight = 52.sp
lineHeight = 52.sp,
letterSpacing = 0.sp
),
displaySmall = TextStyle(
fontWeight = FontWeight.W400,
fontWeight = FontWeight.Normal,
fontSize = 36.sp,
lineHeight = 44.sp
lineHeight = 44.sp,
letterSpacing = 0.sp
),
headlineLarge = TextStyle(
fontWeight = FontWeight.W400,
fontWeight = FontWeight.Normal,
fontSize = 32.sp,
lineHeight = 40.sp
lineHeight = 40.sp,
letterSpacing = 0.sp
),
headlineMedium = TextStyle(
fontWeight = FontWeight.W400,
fontWeight = FontWeight.Normal,
fontSize = 28.sp,
lineHeight = 36.sp
lineHeight = 36.sp,
letterSpacing = 0.sp
),
headlineSmall = TextStyle(
fontWeight = FontWeight.W400,
fontWeight = FontWeight.Normal,
fontSize = 24.sp,
lineHeight = 32.sp
lineHeight = 32.sp,
letterSpacing = 0.sp
),
titleLarge = TextStyle(
fontWeight = FontWeight.W700,
fontWeight = FontWeight.Bold,
fontSize = 22.sp,
lineHeight = 28.sp
lineHeight = 28.sp,
letterSpacing = 0.sp
),
titleMedium = TextStyle(
fontWeight = FontWeight.W700,
fontSize = 16.sp,
fontWeight = FontWeight.Bold,
fontSize = 18.sp,
lineHeight = 24.sp,
letterSpacing = 0.1.sp
),
titleSmall = TextStyle(
fontWeight = FontWeight.W500,
fontWeight = FontWeight.Medium,
fontSize = 14.sp,
lineHeight = 20.sp,
letterSpacing = 0.1.sp
),
bodyLarge = TextStyle(
fontWeight = FontWeight.W400,
fontWeight = FontWeight.Normal,
fontSize = 16.sp,
lineHeight = 24.sp,
letterSpacing = 0.5.sp
),
bodyMedium = TextStyle(
fontWeight = FontWeight.W400,
fontWeight = FontWeight.Normal,
fontSize = 14.sp,
lineHeight = 20.sp,
letterSpacing = 0.25.sp
),
bodySmall = TextStyle(
fontWeight = FontWeight.W400,
fontWeight = FontWeight.Normal,
fontSize = 12.sp,
lineHeight = 16.sp,
letterSpacing = 0.4.sp
),
labelLarge = TextStyle(
fontWeight = FontWeight.W400,
fontWeight = FontWeight.Medium,
fontSize = 14.sp,
lineHeight = 20.sp,
letterSpacing = 0.1.sp
),
labelMedium = TextStyle(
fontWeight = FontWeight.W400,
fontWeight = FontWeight.Medium,
fontSize = 12.sp,
lineHeight = 16.sp,
letterSpacing = 0.5.sp
),
labelSmall = TextStyle(
fontFamily = FontFamily.Monospace,
fontWeight = FontWeight.W500,
fontWeight = FontWeight.Medium,
fontSize = 10.sp,
lineHeight = 16.sp
lineHeight = 16.sp,
letterSpacing = 0.sp
)
)

@ -52,7 +52,7 @@ import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import coil.compose.AsyncImage
import com.google.samples.apps.nowinandroid.core.designsystem.R as DesignsystemR
import com.google.samples.apps.nowinandroid.core.designsystem.component.NiaToggleButton
import com.google.samples.apps.nowinandroid.core.designsystem.component.NiaIconToggleButton
import com.google.samples.apps.nowinandroid.core.designsystem.component.NiaTopicTag
import com.google.samples.apps.nowinandroid.core.designsystem.icon.NiaIcons
import com.google.samples.apps.nowinandroid.core.designsystem.theme.NiaTheme
@ -155,7 +155,7 @@ fun BookmarkButton(
onClick: () -> Unit,
modifier: Modifier = Modifier
) {
NiaToggleButton(
NiaIconToggleButton(
checked = isBookmarked,
onCheckedChange = { onClick() },
modifier = modifier,
@ -232,7 +232,7 @@ fun NewsResourceTopics(
NiaTopicTag(
expanded = expandedTopicId == topic.id,
followed = true, // ToDo: Check if topic is followed
onDropMenuToggle = { show ->
onDropdownMenuToggle = { show ->
expandedTopicId = if (show) topic.id else null
},
onFollowClick = { }, // ToDo

@ -77,9 +77,9 @@ import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.ExperimentalLifecycleComposeApi
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import coil.compose.AsyncImage
import com.google.samples.apps.nowinandroid.core.designsystem.component.NiaFilledButton
import com.google.samples.apps.nowinandroid.core.designsystem.component.NiaButton
import com.google.samples.apps.nowinandroid.core.designsystem.component.NiaOverlayLoadingWheel
import com.google.samples.apps.nowinandroid.core.designsystem.component.NiaToggleButton
import com.google.samples.apps.nowinandroid.core.designsystem.component.NiaIconToggleButton
import com.google.samples.apps.nowinandroid.core.designsystem.icon.NiaIcons
import com.google.samples.apps.nowinandroid.core.designsystem.theme.NiaTheme
import com.google.samples.apps.nowinandroid.core.domain.model.FollowableTopic
@ -257,7 +257,7 @@ private fun LazyGridScope.onboarding(
horizontalArrangement = Arrangement.Center,
modifier = Modifier.fillMaxWidth()
) {
NiaFilledButton(
NiaButton(
onClick = saveFollowedTopics,
enabled = onboardingUiState.isDismissable,
modifier = Modifier
@ -350,7 +350,7 @@ private fun SingleTopicButton(
.weight(1f),
color = MaterialTheme.colorScheme.onSurface
)
NiaToggleButton(
NiaIconToggleButton(
checked = isSelected,
onCheckedChange = { checked -> onClick(topicId, checked) },
icon = {

@ -37,7 +37,7 @@ import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import coil.compose.AsyncImage
import com.google.samples.apps.nowinandroid.core.designsystem.component.NiaToggleButton
import com.google.samples.apps.nowinandroid.core.designsystem.component.NiaIconToggleButton
import com.google.samples.apps.nowinandroid.core.designsystem.icon.NiaIcons
import com.google.samples.apps.nowinandroid.core.designsystem.theme.NiaTheme
import com.google.samples.apps.nowinandroid.feature.interests.R.string
@ -69,7 +69,7 @@ fun InterestsItem(
Spacer(modifier = Modifier.width(16.dp))
InterestContent(name, description)
}
NiaToggleButton(
NiaIconToggleButton(
checked = following,
onCheckedChange = onFollowButtonClick,
icon = {

@ -80,7 +80,7 @@ class DesignSystemDetector : Detector(), Detector.UastScanner {
// instead of hardcoded names.
val METHOD_NAMES = mapOf(
"MaterialTheme" to "NiaTheme",
"Button" to "NiaFilledButton",
"Button" to "NiaButton",
"OutlinedButton" to "NiaOutlinedButton",
"TextButton" to "NiaTextButton",
"FilterChip" to "NiaFilterChip",
@ -92,10 +92,10 @@ class DesignSystemDetector : Detector(), Detector.UastScanner {
"NavigationRailItem" to "NiaNavigationRailItem",
"TabRow" to "NiaTabRow",
"Tab" to "NiaTab",
"IconToggleButton" to "NiaToggleButton",
"FilledIconToggleButton" to "NiaToggleButton",
"FilledTonalIconToggleButton" to "NiaToggleButton",
"OutlinedIconToggleButton" to "NiaToggleButton",
"IconToggleButton" to "NiaIconToggleButton",
"FilledIconToggleButton" to "NiaIconToggleButton",
"FilledTonalIconToggleButton" to "NiaIconToggleButton",
"OutlinedIconToggleButton" to "NiaIconToggleButton",
"CenterAlignedTopAppBar" to "NiaTopAppBar",
"SmallTopAppBar" to "NiaTopAppBar",
"MediumTopAppBar" to "NiaTopAppBar",

Loading…
Cancel
Save