Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update entity button with toggle styling to match M3 components #3923

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,22 @@ import androidx.compose.foundation.gestures.detectTapGestures
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalHapticFeedback
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
import androidx.wear.compose.material.ToggleChip
import androidx.wear.compose.material.ToggleChipDefaults
import androidx.wear.compose.material3.Button
import androidx.wear.compose.material3.Icon
import androidx.wear.compose.material3.LocalContentColor
import androidx.wear.compose.material3.LocalTextStyle
import androidx.wear.compose.material3.MaterialTheme
import androidx.wear.compose.material3.Text
import androidx.wear.tooling.preview.devices.WearDevices
import com.mikepenz.iconics.compose.Image
import io.homeassistant.companion.android.common.R
import io.homeassistant.companion.android.common.data.integration.Entity
import io.homeassistant.companion.android.common.data.integration.EntityExt
import io.homeassistant.companion.android.common.data.integration.domain
Expand All @@ -28,10 +28,12 @@ import io.homeassistant.companion.android.common.data.integration.isActive
import io.homeassistant.companion.android.common.util.STATE_UNAVAILABLE
import io.homeassistant.companion.android.theme.getFilledTonalButtonColors
import io.homeassistant.companion.android.theme.wearColorScheme
import io.homeassistant.companion.android.util.ToggleSwitch
import io.homeassistant.companion.android.util.WearToggleChip
import io.homeassistant.companion.android.util.onEntityClickedFeedback
import io.homeassistant.companion.android.util.previewEntity1
import io.homeassistant.companion.android.util.previewEntity3
import io.homeassistant.companion.android.util.previewEntity4

@Composable
fun EntityUi(
Expand All @@ -46,65 +48,63 @@ fun EntityUi(
val attributes = entity.attributes as Map<*, *>
val iconBitmap = entity.getIcon(LocalContext.current)
val friendlyName = attributes["friendly_name"].toString()
val nameModifier = Modifier
.fillMaxWidth()
.pointerInput(Unit) {
detectTapGestures(
onTap = {
onEntityClicked(entity.entityId, entity.state)
onEntityClickedFeedback(
isToastEnabled,
isHapticEnabled,
context,
friendlyName,
haptic
)
},
onLongPress = {
onEntityLongPressed(entity.entityId)
}
)
}

if (entity.domain in EntityExt.DOMAINS_TOGGLE) {
val isChecked = entity.isActive()
val isEnabled = entity.state != STATE_UNAVAILABLE
val colors = WearToggleChip.entityToggleChipBackgroundColors(entity, isChecked)
ToggleChip(
checked = isChecked,
onCheckedChange = {
onEntityClicked(entity.entityId, entity.state)
onEntityClickedFeedback(isToastEnabled, isHapticEnabled, context, friendlyName, haptic)
},
modifier = Modifier
.fillMaxWidth(),
modifier = Modifier.fillMaxWidth(),
appIcon = {
Image(
asset = iconBitmap,
colorFilter = ColorFilter.tint(wearColorScheme.onSurface)
)
},
label = {
Text(
text = friendlyName,
maxLines = 2,
overflow = TextOverflow.Ellipsis,
modifier = Modifier.fillMaxWidth().pointerInput(Unit) {
detectTapGestures(
onTap = {
onEntityClicked(entity.entityId, entity.state)
onEntityClickedFeedback(
isToastEnabled,
isHapticEnabled,
context,
friendlyName,
haptic
)
},
onLongPress = {
onEntityLongPressed(entity.entityId)
}
)
}
)
},
enabled = entity.state != STATE_UNAVAILABLE,
toggleControl = {
Icon(
imageVector = ToggleChipDefaults.switchIcon(isChecked),
contentDescription = if (isChecked) {
stringResource(R.string.enabled)
} else {
stringResource(R.string.disabled)
},
tint = if (isChecked) wearColorScheme.tertiary else wearColorScheme.onSurface
)
CompositionLocalProvider(
LocalTextStyle provides MaterialTheme.typography.labelMedium,
LocalContentColor provides colors.contentColor(enabled = isEnabled, checked = isChecked).value
) {
Text(
text = friendlyName,
maxLines = 2,
overflow = TextOverflow.Ellipsis,
modifier = nameModifier
)
}
},
colors = WearToggleChip.entityToggleChipBackgroundColors(entity, isChecked)
enabled = isEnabled,
toggleControl = { ToggleSwitch(isChecked) },
colors = colors
)
} else {
Button(
modifier = Modifier
.fillMaxWidth(),
modifier = Modifier.fillMaxWidth(),
icon = {
Image(
asset = iconBitmap,
Expand All @@ -116,23 +116,7 @@ fun EntityUi(
text = friendlyName,
maxLines = 2,
overflow = TextOverflow.Ellipsis,
modifier = Modifier.fillMaxWidth().pointerInput(Unit) {
detectTapGestures(
onTap = {
onEntityClicked(entity.entityId, entity.state)
onEntityClickedFeedback(
isToastEnabled,
isHapticEnabled,
context,
friendlyName,
haptic
)
},
onLongPress = {
onEntityLongPressed(entity.entityId)
}
)
}
modifier = nameModifier
)
},
enabled = entity.state != STATE_UNAVAILABLE,
Expand Down Expand Up @@ -163,5 +147,12 @@ private fun PreviewEntityUI() {
isToastEnabled = true,
onEntityLongPressed = { }
)
EntityUi(
entity = previewEntity4,
onEntityClicked = { _, _ -> },
isHapticEnabled = false,
isToastEnabled = true,
onEntityLongPressed = { }
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ val lightAttributes: Map<*, *> = mapOf(
"min_mireds" to 153,
"max_mireds" to 526,
"color_temp" to 300,
"rgb_color" to listOf(255, 187, 130),
"color_mode" to "color_temp"
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,11 @@ import io.homeassistant.companion.android.theme.wearColorScheme

object WearToggleChip {
/**
* A function that provides chip colors that mostly follow the default toggle chip colors, but when supported
* provide a background for active entities that reflects their state (position and color). Gradient code
* is based on [androidx.wear.compose.material.ToggleChipDefaults.toggleChipColors].
* A function that provides chip colors that mostly follow M3 styling, but for use with M2
* components that can to, when supported, provide a background for active entities that
* reflects their state (position and color). M3's ToggleButton does not allow anything more
* complicated than a single color on a button. Gradient code is based on
* [androidx.wear.compose.material.ToggleChipDefaults.toggleChipColors].
*
* @param entity The entity state on which the background for the active state should be based
*/
Expand All @@ -38,9 +40,9 @@ object WearToggleChip {
// For a toggleable entity, a custom background should only be used if it has:
// a. a position (eg. fan speed, light brightness)
// b. a custom color (eg. light color)
// If there is a position (a) but no color (b), use the default (theme) color for the 'active' part.
// If there is a color (b) but no position (a), use a smooth gradient similar to ToggleChip.
// If it doesn't have either or is 'off', it should use the default chip background.
// If there is a position (a) but no color (b), use the on (surfaceBright) color for the 'active' part.
// If there is a color (b) but no position (a), use the calculated color for the background.
// If it doesn't have either or is 'off', it should use the default off (surfaceDim) background.

val hasPosition = when (entity.domain) {
"cover" -> entity.state != "closed" && entity.getCoverPosition() != null
Expand All @@ -53,18 +55,24 @@ object WearToggleChip {

val contentBackgroundColor = if (hasColor) {
val entityColor = entity.getLightColor()
if (entityColor != null) Color(entityColor) else wearColorScheme.primary
if (entityColor != null) Color(entityColor) else wearColorScheme.surfaceBright
} else {
wearColorScheme.primary
wearColorScheme.surfaceBright
}

return when {
(hasPosition || hasColor) -> {
val checkedStartBackgroundColor = contentBackgroundColor.copy(alpha = 0.5f)
.compositeOver(wearColorScheme.outlineVariant)
val checkedEndBackgroundColor = wearColorScheme.outlineVariant.copy(alpha = 0f)
.compositeOver(wearColorScheme.outlineVariant)
val uncheckedBackgroundColor = wearColorScheme.outlineVariant
val checkedStartBackgroundColor = if (hasColor) {
contentBackgroundColor.copy(alpha = 0.5f).compositeOver(wearColorScheme.surfaceDim)
} else {
wearColorScheme.surfaceBright
}
val checkedEndBackgroundColor = if (hasPosition) {
wearColorScheme.surfaceDim // Used as 'off' color
} else {
checkedStartBackgroundColor // On no position = entire background 'on'
}
val uncheckedBackgroundColor = wearColorScheme.surfaceDim

var checkedBackgroundColors = listOf(
checkedStartBackgroundColor,
Expand Down Expand Up @@ -156,14 +164,28 @@ object WearToggleChip {
disabledUncheckedBackgroundPaint = WearBrushPainter(Brush.linearGradient(disabledUncheckedBackgroundColors))
}

defaultChipColors().apply {
defaultChipColors(
checkedContentColor = wearColorScheme.onPrimaryContainer,
checkedSecondaryContentColor = wearColorScheme.onPrimaryContainer.copy(alpha = 0.8f),
uncheckedContentColor = wearColorScheme.onSurface,
uncheckedSecondaryContentColor = wearColorScheme.onSurfaceVariant
).apply {
checkedBackgroundPainter = checkedBackgroundPaint
disabledCheckedBackgroundPainter = disabledCheckedBackgroundPaint
uncheckedBackgroundPainter = uncheckedBackgroundPaint
disabledUncheckedBackgroundPainter = disabledUncheckedBackgroundPaint
}
}
else -> ToggleChipDefaults.toggleChipColors()
else -> ToggleChipDefaults.toggleChipColors(
checkedStartBackgroundColor = wearColorScheme.surfaceBright,
checkedEndBackgroundColor = wearColorScheme.surfaceBright,
checkedContentColor = wearColorScheme.onPrimaryContainer,
checkedSecondaryContentColor = wearColorScheme.onPrimaryContainer.copy(alpha = 0.8f),
uncheckedStartBackgroundColor = wearColorScheme.surfaceDim,
uncheckedEndBackgroundColor = wearColorScheme.surfaceDim,
uncheckedContentColor = wearColorScheme.onSurface,
uncheckedSecondaryContentColor = wearColorScheme.onSurfaceVariant
)
}
}

Expand Down