feat: add corner radius option for avatars

Signed-off-by: Infi <infi@infi.sh>
This commit is contained in:
Infi 2024-06-16 01:50:49 +02:00
parent 70c906f232
commit 13109c61c2
10 changed files with 282 additions and 3 deletions

View File

@ -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
)

View File

@ -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
}
}

View File

@ -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) {

View File

@ -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,
)
}
}

View File

@ -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(

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="48dp"
android:height="48dp"
android:viewportWidth="1000"
android:viewportHeight="1000">
<path
android:pathData="M763,1000C763,998.3 763,996.7 763,995V772C763,724.6 756.7,678.6 744.9,634.9C854.4,592.2 932,485.6 932,361C932,198.6 800.4,67 638,67C510.5,67 402,148.1 361.2,261.5C321.7,252 280.4,247 238,247H0V297H238C276,297 313,301.5 348.4,309.9C345.5,326.5 344,343.6 344,361C344,523.4 475.6,655 638,655C658.2,655 677.9,653 696.9,649.1C707.4,688.3 713,729.5 713,772V995C713,996.7 713,998.3 713,1000H763ZM403,361C403,358.4 403,355.8 403.1,353.3C515,397.4 605,485.2 652.1,595.6C647.4,595.9 642.7,596 638,596C508.2,596 403,490.8 403,361ZM426.6,258.2C464.7,179.9 545.1,126 638,126C767.8,126 873,231.2 873,361C873,450.6 822.8,528.6 749,568.2C691.3,423.7 574.2,309.5 427.9,255.7C427.5,256.5 427,257.4 426.6,258.2Z"
android:fillColor="#ffffff"
android:fillType="evenOdd"/>
</vector>

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="48dp"
android:height="48dp"
android:viewportWidth="1000"
android:viewportHeight="1000">
<path
android:pathData="M740.4,121L697,215.6L602.7,258.6L697,302L740.4,396.3L783.4,302L878,258.6L783.4,215.6M396.3,224.2L310.3,413.5L121,499.5L310.3,585.5L396.3,774.8L482.3,585.5L671.5,499.5L482.3,413.5M740.4,602.7L697,697L602.7,740.4L697,783.4L740.4,878L783.4,783.4L878,740.4L783.4,697"
android:fillColor="#ffffff"/>
</vector>

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="48dp"
android:height="48dp"
android:viewportWidth="1000"
android:viewportHeight="1000">
<path
android:pathData="M638,655C475.6,655 344,523.4 344,361C344,339 346.4,317.6 351,297H0V247H366.9C411.4,141.2 516.1,67 638,67C800.4,67 932,198.6 932,361C932,478.7 862.9,580.2 763,627.2V1000H713V645.3C689.1,651.6 663.9,655 638,655ZM403,361C403,338.8 406.1,317.3 411.8,297H411.8C409.5,305.2 407.6,313.5 406.2,322H581C640.1,322 688,369.9 688,429V590.7C671.9,594.2 655.2,596 638,596C508.2,596 403,490.8 403,361ZM432.5,247C437.3,238.3 442.6,230 448.5,222H581C695.3,222 788,314.7 788,429V541.9C839.9,498.8 873,433.8 873,361C873,231.2 767.8,126 638,126C549.6,126 472.6,174.8 432.5,247H432.5Z"
android:fillColor="#ffffff"
android:fillType="evenOdd"/>
</vector>

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="48dp"
android:height="48dp"
android:viewportWidth="1000"
android:viewportHeight="1000">
<path
android:pathData="M932,361C932,478.7 862.9,580.2 763,627.2V1000H713V645.3C689.1,651.6 663.9,655 638,655C475.6,655 344,523.4 344,361C344,339 346.4,317.6 351,297H0V247H366.9C411.4,141.2 516.1,67 638,67C800.4,67 932,198.6 932,361ZM406.2,322C407.6,313.5 409.5,305.2 411.8,297H411.8C406.1,317.3 403,338.8 403,361C403,490.8 508.2,596 638,596C655.2,596 671.9,594.2 688,590.7V322H406.2ZM448.5,222C442.6,230 437.3,238.3 432.5,247H432.5C472.6,174.8 549.6,126 638,126C767.8,126 873,231.2 873,361C873,433.8 839.9,498.8 788,541.9V222H448.5Z"
android:fillColor="#ffffff"
android:fillType="evenOdd"/>
</vector>

View File

@ -466,6 +466,14 @@
<string name="inline_media_picker_no_media_placeholder">Pick media…</string>
<string name="inline_media_picker_remove">Remove</string>
<string name="corner_radius_picker_sharp">Sharp</string>
<string name="corner_radius_picker_rounded">Rounded</string>
<string name="corner_radius_picker_circular">Circular</string>
<string name="corner_radius_picker_other">Other</string>
<string name="corner_radius_picker_choose_radius">Choose radius</string>
<string name="corner_radius_picker_choose_radius_yes">Use</string>
<string name="corner_radius_picker_choose_radius_cancel">Cancel</string>
<string name="emoji_picker_close_skin_tone_menu">Close skin tone menu</string>
<string name="emoji_picker_skin_tone_none">No skin tone</string>
<string name="emoji_picker_skin_tone_fitzpatrick_1_2">Light skin tone</string>
@ -516,6 +524,9 @@
<string name="settings_appearance_theme_m3dynamic_unsupported">Material You (unsupported)</string>
<string name="settings_appearance_theme_m3dynamic_unsupported_toast">Material You is not supported on this device.</string>
<string name="settings_appearance_avatar_shape">Profile Picture Shape</string>
<string name="settings_appearance_avatar_shape_description">Choose the rounding grade for profile pictures, including in chat and profiles. This applies to all users.</string>
<string name="settings_appearance_colour_overrides">Colour overrides</string>
<string name="settings_appearance_colour_overrides_primary">Primary</string>
<string name="settings_appearance_colour_overrides_on_primary">On Primary</string>
@ -597,5 +608,5 @@
<string name="share_target_invalid_intent">This is not a valid share intent.</string>
<string name="share_target_attachment_too_large">This attachment is too large for Revolt (max. $1$s).</string>
<string name="share_target_search_channels">Search channels</string>
<string name="share_target_select_channel">Please select a channel to share to.</string>
<string name="share_target_select_channel">m select a channel to share to.</string>
</resources>