diff --git a/app/src/main/java/chat/stoat/composables/generic/UserAvatar.kt b/app/src/main/java/chat/stoat/composables/generic/UserAvatar.kt index 6d44f06b..12cca3a7 100644 --- a/app/src/main/java/chat/stoat/composables/generic/UserAvatar.kt +++ b/app/src/main/java/chat/stoat/composables/generic/UserAvatar.kt @@ -2,31 +2,37 @@ package chat.stoat.composables.generic import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.background -import androidx.compose.foundation.border import androidx.compose.foundation.combinedClickable import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width -import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip +import androidx.compose.ui.draw.drawWithCache +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.graphics.BlendMode import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.CompositingStrategy import androidx.compose.ui.graphics.Shape +import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.LayoutDirection import androidx.compose.ui.unit.dp import chat.stoat.R -import chat.stoat.core.model.schemas.AutumnResource import chat.stoat.api.settings.LoadedSettings import chat.stoat.core.model.data.STOAT_BASE import chat.stoat.core.model.data.STOAT_FILES +import chat.stoat.core.model.schemas.AutumnResource enum class Presence { Online, @@ -68,17 +74,61 @@ fun presenceColour(presence: Presence): Color { } } +private fun Presence.iconResource(): Int = when (this) { + Presence.Online -> R.drawable.ic_circle_24dp__fill + Presence.Idle -> R.drawable.ic_bedtime_24dp + Presence.Dnd -> R.drawable.ic_do_not_disturb_on_24dp + Presence.Focus -> R.drawable.ic_circle_circle_24dp + Presence.Offline -> R.drawable.ic_circle_24dp +} + +private fun Presence.descriptionResource(): Int = when (this) { + Presence.Online -> R.string.status_online + Presence.Idle -> R.string.status_idle + Presence.Dnd -> R.string.status_dnd + Presence.Focus -> R.string.status_focus + Presence.Offline -> R.string.status_offline +} + @Composable fun PresenceBadge(presence: Presence, size: Dp = 16.dp) { Box( - modifier = Modifier - .size(size) - .clip(CircleShape) - .border(2.dp, MaterialTheme.colorScheme.background, CircleShape) - .background(presenceColour(presence)) - ) + contentAlignment = Alignment.Center, + modifier = Modifier.size(size) + ) { + Icon( + painter = painterResource(presence.iconResource()), + contentDescription = stringResource(presence.descriptionResource()), + tint = presenceColour(presence), + modifier = Modifier + .size((size - PresenceCutoutPadding * 2).coerceAtLeast(0.dp)) + ) + } } +private val PresenceCutoutPadding = 2.dp + +private fun Modifier.presenceCutout(diameter: Dp): Modifier = + graphicsLayer { + compositingStrategy = CompositingStrategy.Offscreen + }.drawWithCache { + val radius = diameter.toPx() / 2 + val center = Offset( + x = if (layoutDirection == LayoutDirection.Ltr) size.width - radius else radius, + y = size.height - radius + ) + + onDrawWithContent { + drawContent() + drawCircle( + color = Color.Transparent, + radius = radius, + center = center, + blendMode = BlendMode.Clear + ) + } + } + @OptIn(ExperimentalFoundationApi::class) @Composable fun UserAvatar( @@ -109,6 +159,13 @@ fun UserAvatar( modifier = Modifier .clip(shape) .size(size) + .then( + if (presence != null) { + Modifier.presenceCutout(presenceSize) + } else { + Modifier + } + ) .then( if (onLongClick != null || onClick != null) { Modifier @@ -129,6 +186,13 @@ fun UserAvatar( modifier = Modifier .clip(shape) .size(size) + .then( + if (presence != null) { + Modifier.presenceCutout(presenceSize) + } else { + Modifier + } + ) .then( if (onLongClick != null || onClick != null) { Modifier diff --git a/app/src/main/res/drawable/ic_adjust_24dp.xml b/app/src/main/res/drawable/ic_adjust_24dp.xml new file mode 100644 index 00000000..d34d6a5e --- /dev/null +++ b/app/src/main/res/drawable/ic_adjust_24dp.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_bedtime_24dp.xml b/app/src/main/res/drawable/ic_bedtime_24dp.xml new file mode 100644 index 00000000..155abb46 --- /dev/null +++ b/app/src/main/res/drawable/ic_bedtime_24dp.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_circle_24dp.xml b/app/src/main/res/drawable/ic_circle_24dp.xml new file mode 100644 index 00000000..29228525 --- /dev/null +++ b/app/src/main/res/drawable/ic_circle_24dp.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_circle_24dp__fill.xml b/app/src/main/res/drawable/ic_circle_24dp__fill.xml new file mode 100644 index 00000000..d7e8c25b --- /dev/null +++ b/app/src/main/res/drawable/ic_circle_24dp__fill.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_circle_circle_24dp.xml b/app/src/main/res/drawable/ic_circle_circle_24dp.xml new file mode 100644 index 00000000..67e81edd --- /dev/null +++ b/app/src/main/res/drawable/ic_circle_circle_24dp.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_do_not_disturb_on_24dp.xml b/app/src/main/res/drawable/ic_do_not_disturb_on_24dp.xml new file mode 100644 index 00000000..3d31aa17 --- /dev/null +++ b/app/src/main/res/drawable/ic_do_not_disturb_on_24dp.xml @@ -0,0 +1,10 @@ + + +