feat: add corner radius option for avatars
Signed-off-by: Infi <infi@infi.sh>
This commit is contained in:
parent
70c906f232
commit
13109c61c2
|
|
@ -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
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
|
|||
Loading…
Reference in New Issue