From 13109c61c2e5106063a1c01685e010b23ad196db Mon Sep 17 00:00:00 2001 From: Infi Date: Sun, 16 Jun 2024 01:50:49 +0200 Subject: [PATCH] feat: add corner radius option for avatars Signed-off-by: Infi --- .../java/chat/revolt/api/schemas/Settings.kt | 5 + .../chat/revolt/api/settings/GlobalState.kt | 4 + .../revolt/components/generic/UserAvatar.kt | 6 +- .../settings/appearance/CornerRadiusPicker.kt | 191 ++++++++++++++++++ .../settings/AppearanceSettingsScreen.kt | 27 +++ .../main/res/drawable/ux_corner_circular.xml | 10 + app/src/main/res/drawable/ux_corner_other.xml | 9 + .../main/res/drawable/ux_corner_rounded.xml | 10 + app/src/main/res/drawable/ux_corner_sharp.xml | 10 + app/src/main/res/values/strings.xml | 13 +- 10 files changed, 282 insertions(+), 3 deletions(-) create mode 100644 app/src/main/java/chat/revolt/components/screens/settings/appearance/CornerRadiusPicker.kt create mode 100644 app/src/main/res/drawable/ux_corner_circular.xml create mode 100644 app/src/main/res/drawable/ux_corner_other.xml create mode 100644 app/src/main/res/drawable/ux_corner_rounded.xml create mode 100644 app/src/main/res/drawable/ux_corner_sharp.xml diff --git a/app/src/main/java/chat/revolt/api/schemas/Settings.kt b/app/src/main/java/chat/revolt/api/schemas/Settings.kt index f3c18595..beabd9e8 100644 --- a/app/src/main/java/chat/revolt/api/schemas/Settings.kt +++ b/app/src/main/java/chat/revolt/api/schemas/Settings.kt @@ -25,4 +25,9 @@ data class AndroidSpecificSettings( * Can be one of `{ None, SwipeFromEnd, DoubleTap }` */ var messageReplyStyle: String? = null, + /** + * Avatar radius. + * Must be integer in range 0..50 inclusive. + */ + var avatarRadius: Int? = null ) diff --git a/app/src/main/java/chat/revolt/api/settings/GlobalState.kt b/app/src/main/java/chat/revolt/api/settings/GlobalState.kt index 044108c1..6a4ce030 100644 --- a/app/src/main/java/chat/revolt/api/settings/GlobalState.kt +++ b/app/src/main/java/chat/revolt/api/settings/GlobalState.kt @@ -1,6 +1,7 @@ package chat.revolt.api.settings import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue import chat.revolt.ui.theme.Theme @@ -15,16 +16,19 @@ enum class MessageReplyStyle { object GlobalState { var theme by mutableStateOf(getDefaultTheme()) var messageReplyStyle by mutableStateOf(MessageReplyStyle.SwipeFromEnd) + var avatarRadius by mutableIntStateOf(50) fun hydrateWithSettings(settings: SyncedSettings) { this.theme = settings.android.theme?.let { Theme.valueOf(it) } ?: getDefaultTheme() this.messageReplyStyle = settings.android.messageReplyStyle?.let { MessageReplyStyle.valueOf(it) } ?: MessageReplyStyle.SwipeFromEnd + this.avatarRadius = settings.android.avatarRadius ?: 50 } fun reset() { theme = getDefaultTheme() messageReplyStyle = MessageReplyStyle.SwipeFromEnd + avatarRadius = 50 } } diff --git a/app/src/main/java/chat/revolt/components/generic/UserAvatar.kt b/app/src/main/java/chat/revolt/components/generic/UserAvatar.kt index a1bad473..99d55d5b 100644 --- a/app/src/main/java/chat/revolt/components/generic/UserAvatar.kt +++ b/app/src/main/java/chat/revolt/components/generic/UserAvatar.kt @@ -8,6 +8,7 @@ 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.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable @@ -24,6 +25,7 @@ import chat.revolt.R import chat.revolt.api.REVOLT_BASE import chat.revolt.api.REVOLT_FILES import chat.revolt.api.schemas.AutumnResource +import chat.revolt.api.settings.GlobalState enum class Presence { Online, @@ -101,7 +103,7 @@ fun UserAvatar( contentScale = ContentScale.Crop, description = stringResource(id = R.string.avatar_alt, username), modifier = Modifier - .clip(CircleShape) + .clip(RoundedCornerShape(GlobalState.avatarRadius)) .size(size) .then( if (onLongClick != null || onClick != null) { @@ -120,7 +122,7 @@ fun UserAvatar( url = "$REVOLT_BASE/users/$userId/default_avatar", description = stringResource(id = R.string.avatar_alt, username), modifier = Modifier - .clip(CircleShape) + .clip(RoundedCornerShape(GlobalState.avatarRadius)) .size(size) .then( if (onLongClick != null || onClick != null) { diff --git a/app/src/main/java/chat/revolt/components/screens/settings/appearance/CornerRadiusPicker.kt b/app/src/main/java/chat/revolt/components/screens/settings/appearance/CornerRadiusPicker.kt new file mode 100644 index 00000000..be84e224 --- /dev/null +++ b/app/src/main/java/chat/revolt/components/screens/settings/appearance/CornerRadiusPicker.kt @@ -0,0 +1,191 @@ +package chat.revolt.components.screens.settings.appearance + +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.RowScope +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.AlertDialog +import androidx.compose.material3.Button +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Slider +import androidx.compose.material3.Text +import androidx.compose.material3.TextButton +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.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.painter.Painter +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import chat.revolt.R + +enum class CornerRadiusPreset(val percentage: Int) { + SHARP(0), + ROUNDED(15), + CIRCULAR(50), +} + +@Composable +fun CornerRadiusPicker(percentage: Int, onUpdate: (Int) -> Unit, modifier: Modifier = Modifier) { + var showOtherModal by remember { mutableStateOf(false) } + + if (showOtherModal) { + var sliderPosition by remember { mutableStateOf(percentage.toFloat()) } + AlertDialog( + onDismissRequest = { showOtherModal = false }, + title = { + Text( + text = stringResource(R.string.corner_radius_picker_choose_radius), + ) + }, + text = { + Column { + Spacer(modifier = Modifier.size(16.dp)) + + Column( + verticalArrangement = Arrangement.spacedBy(16.dp), + horizontalAlignment = Alignment.CenterHorizontally, + ) { + Box( + modifier = Modifier + .clip(RoundedCornerShape(sliderPosition.toInt())) + .background(MaterialTheme.colorScheme.primary) + .size(64.dp), + ) + + Text( + sliderPosition.toInt().toString(), + style = MaterialTheme.typography.labelLarge.copy( + fontFeatureSettings = "tnum" + ), + ) + + Slider( + value = sliderPosition, + onValueChange = { sliderPosition = it }, + valueRange = 0f..50f, + steps = 51 + ) + } + } + }, + confirmButton = { + Button( + onClick = { + showOtherModal = false + onUpdate(sliderPosition.toInt()) + } + ) { + Text(stringResource(R.string.corner_radius_picker_choose_radius_yes)) + } + }, + dismissButton = { + TextButton( + onClick = { + showOtherModal = false + } + ) { + Text(stringResource(R.string.corner_radius_picker_choose_radius_cancel)) + } + } + ) + } + + Row( + horizontalArrangement = Arrangement.spacedBy(8.dp), + modifier = modifier.clip(MaterialTheme.shapes.medium) + ) { + CornerRadiusPickerElement( + selected = percentage == CornerRadiusPreset.SHARP.percentage, + onSelect = { + onUpdate(CornerRadiusPreset.SHARP.percentage) + }, + icon = painterResource(R.drawable.ux_corner_sharp), + label = stringResource(R.string.corner_radius_picker_sharp), + ) + CornerRadiusPickerElement( + selected = percentage == CornerRadiusPreset.ROUNDED.percentage, + onSelect = { + onUpdate(CornerRadiusPreset.ROUNDED.percentage) + }, + icon = painterResource(R.drawable.ux_corner_rounded), + label = stringResource(R.string.corner_radius_picker_rounded), + ) + CornerRadiusPickerElement( + selected = percentage == CornerRadiusPreset.CIRCULAR.percentage, + onSelect = { + onUpdate(CornerRadiusPreset.CIRCULAR.percentage) + }, + icon = painterResource(R.drawable.ux_corner_circular), + label = stringResource(R.string.corner_radius_picker_circular), + ) + CornerRadiusPickerElement( + selected = percentage !in CornerRadiusPreset.entries.map { it.percentage }, + onSelect = { + showOtherModal = true + }, + icon = painterResource(R.drawable.ux_corner_other), + label = if (percentage !in CornerRadiusPreset.entries.map { it.percentage }) { + percentage.toString() + } else stringResource(R.string.corner_radius_picker_other), + highlightLabel = percentage !in CornerRadiusPreset.entries.map { it.percentage }, + ) + } +} + +@Composable +fun RowScope.CornerRadiusPickerElement( + selected: Boolean, + onSelect: () -> Unit, + icon: Painter, + label: String, + modifier: Modifier = Modifier, + highlightLabel: Boolean = false, +) { + Column( + verticalArrangement = Arrangement.spacedBy(8.dp), + horizontalAlignment = Alignment.CenterHorizontally, + modifier = modifier + .clip(MaterialTheme.shapes.medium) + .clickable { onSelect() } + .weight(1f) + .background( + if (selected) + MaterialTheme.colorScheme.onSurface.copy(alpha = 0.1f) + else + Color.Transparent + ) + .padding(vertical = 8.dp) + ) { + Icon( + painter = icon, + contentDescription = null, + tint = if (selected) + MaterialTheme.colorScheme.primary + else + MaterialTheme.colorScheme.onSurface.copy(alpha = 0.6f), + ) + Text( + text = label, + style = MaterialTheme.typography.labelMedium, + color = if (highlightLabel) + MaterialTheme.colorScheme.primary + else + MaterialTheme.colorScheme.onSurface, + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/chat/revolt/screens/settings/AppearanceSettingsScreen.kt b/app/src/main/java/chat/revolt/screens/settings/AppearanceSettingsScreen.kt index aa7fa68c..2b1a7696 100644 --- a/app/src/main/java/chat/revolt/screens/settings/AppearanceSettingsScreen.kt +++ b/app/src/main/java/chat/revolt/screens/settings/AppearanceSettingsScreen.kt @@ -75,6 +75,7 @@ import chat.revolt.api.settings.GlobalState import chat.revolt.api.settings.SyncedSettings import chat.revolt.components.generic.ListHeader import chat.revolt.components.screens.settings.appearance.ColourChip +import chat.revolt.components.screens.settings.appearance.CornerRadiusPicker import chat.revolt.ui.theme.ClearRippleTheme import chat.revolt.ui.theme.OverridableColourScheme import chat.revolt.ui.theme.Theme @@ -111,6 +112,13 @@ class AppearanceSettingsScreenViewModel @Inject constructor( } } + fun saveNewAvatarRadius(radius: Int) { + GlobalState.avatarRadius = radius + viewModelScope.launch { + SyncedSettings.updateAndroid(SyncedSettings.android.copy(avatarRadius = radius)) + } + } + fun updateColourOverrides(fieldName: String, value: Int?) { viewModelScope.launch { val overrides = SyncedSettings.android.copy().colourOverrides @@ -389,6 +397,25 @@ fun AppearanceSettingsScreen( } } + ListHeader { + Text(stringResource(R.string.settings_appearance_avatar_shape)) + } + + Text( + stringResource(R.string.settings_appearance_avatar_shape_description), + modifier = Modifier + .padding(vertical = 8.dp, horizontal = 16.dp), + ) + + Box(Modifier.padding(horizontal = 16.dp)) { + CornerRadiusPicker( + percentage = GlobalState.avatarRadius, + onUpdate = { + viewModel.saveNewAvatarRadius(it) + } + ) + } + Spacer(modifier = Modifier.height(20.dp)) Row( diff --git a/app/src/main/res/drawable/ux_corner_circular.xml b/app/src/main/res/drawable/ux_corner_circular.xml new file mode 100644 index 00000000..63e7e334 --- /dev/null +++ b/app/src/main/res/drawable/ux_corner_circular.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ux_corner_other.xml b/app/src/main/res/drawable/ux_corner_other.xml new file mode 100644 index 00000000..ecfd031f --- /dev/null +++ b/app/src/main/res/drawable/ux_corner_other.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ux_corner_rounded.xml b/app/src/main/res/drawable/ux_corner_rounded.xml new file mode 100644 index 00000000..c6bfc303 --- /dev/null +++ b/app/src/main/res/drawable/ux_corner_rounded.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ux_corner_sharp.xml b/app/src/main/res/drawable/ux_corner_sharp.xml new file mode 100644 index 00000000..cc0c7892 --- /dev/null +++ b/app/src/main/res/drawable/ux_corner_sharp.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 55e6e34c..127beb37 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -466,6 +466,14 @@ Pick media… Remove + Sharp + Rounded + Circular + Other + Choose radius + Use + Cancel + Close skin tone menu No skin tone Light skin tone @@ -516,6 +524,9 @@ Material You (unsupported) Material You is not supported on this device. + Profile Picture Shape + Choose the rounding grade for profile pictures, including in chat and profiles. This applies to all users. + Colour overrides Primary On Primary @@ -597,5 +608,5 @@ This is not a valid share intent. This attachment is too large for Revolt (max. $1$s). Search channels - Please select a channel to share to. + m select a channel to share to.