Merge pull request #6456 from element-hq/feature/bma/iterateOnSpaceHeader

Iterate on space header
This commit is contained in:
Benoit Marty
2026-03-24 17:54:02 +01:00
committed by GitHub
85 changed files with 239 additions and 275 deletions

View File

@@ -53,6 +53,9 @@ import io.element.android.libraries.matrix.ui.components.AvatarPickerView
import io.element.android.libraries.permissions.api.PermissionsView import io.element.android.libraries.permissions.api.PermissionsView
import io.element.android.libraries.ui.strings.CommonStrings import io.element.android.libraries.ui.strings.CommonStrings
/**
* https://www.figma.com/design/pDlJZGBsri47FNTXMnEdXB/Compound-Android-Templates?node-id=3182-36115&t=U1vS3px9HzlzWYd7-4
*/
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
fun EditUserProfileView( fun EditUserProfileView(
@@ -125,7 +128,7 @@ fun EditUserProfileView(
style = ElementTheme.typography.fontBodyLgRegular, style = ElementTheme.typography.fontBodyLgRegular,
textAlign = TextAlign.Center, textAlign = TextAlign.Center,
) )
Spacer(modifier = Modifier.height(40.dp)) Spacer(modifier = Modifier.height(32.dp))
TextField( TextField(
label = stringResource(R.string.screen_edit_profile_display_name), label = stringResource(R.string.screen_edit_profile_display_name),
value = state.displayName, value = state.displayName,

View File

@@ -51,6 +51,12 @@ import io.element.android.libraries.matrix.ui.components.AvatarPickerView
import io.element.android.libraries.permissions.api.PermissionsView import io.element.android.libraries.permissions.api.PermissionsView
import io.element.android.libraries.ui.strings.CommonStrings import io.element.android.libraries.ui.strings.CommonStrings
/**
* For space:
* https://www.figma.com/design/pDlJZGBsri47FNTXMnEdXB/Compound-Android-Templates?node-id=2216-110711
* For room:
* https://www.figma.com/design/pDlJZGBsri47FNTXMnEdXB/Compound-Android-Templates?node-id=3187-47342
*/
@Composable @Composable
fun RoomDetailsEditView( fun RoomDetailsEditView(
state: RoomDetailsEditState, state: RoomDetailsEditState,
@@ -102,11 +108,11 @@ fun RoomDetailsEditView(
) { ) {
Spacer(modifier = Modifier.height(24.dp)) Spacer(modifier = Modifier.height(24.dp))
val avatarPickerState = remember(state.roomAvatarUrl, state.roomRawName) { val avatarPickerState = remember(state.roomAvatarUrl, state.roomRawName) {
val size = AvatarSize.EditRoomDetails val size = if (state.isSpace) AvatarSize.EditSpaceDetails else AvatarSize.EditRoomDetails
val type = if (state.isSpace) AvatarType.Space() else AvatarType.Room() val type = if (state.isSpace) AvatarType.Space() else AvatarType.Room()
AvatarPickerState.Selected( AvatarPickerState.Selected(
avatarData = AvatarData(id = state.roomId.value, name = state.roomRawName, size = size, url = state.roomAvatarUrl), avatarData = AvatarData(id = state.roomId.value, name = state.roomRawName, size = size, url = state.roomAvatarUrl),
type = type type = type,
) )
} }
AvatarPickerView( AvatarPickerView(

View File

@@ -46,7 +46,8 @@ enum class AvatarSize(val dp: Dp) {
RoomInviteItem(52.dp), RoomInviteItem(52.dp),
InviteSender(16.dp), InviteSender(16.dp),
EditRoomDetails(68.dp), EditRoomDetails(64.dp),
EditSpaceDetails(96.dp),
RoomListManageUser(96.dp), RoomListManageUser(96.dp),
NotificationsOptIn(32.dp), NotificationsOptIn(32.dp),

View File

@@ -61,8 +61,16 @@ import io.element.android.libraries.testtags.TestTags
import io.element.android.libraries.testtags.testTag import io.element.android.libraries.testtags.testTag
import io.element.android.libraries.ui.strings.CommonStrings import io.element.android.libraries.ui.strings.CommonStrings
private val editIconContainerSize = 30.dp
private val editIconContainerRadius = editIconContainerSize / 2
private val editIconContainerPadding = 4.dp
private val editIconSize = 20.dp
private val editIconOffset = 8.dp
/** /**
* Avatar picker view, based on https://www.figma.com/design/kcnHxunG1LDWXsJhaNuiHz/ER-145--Spaces-on-Element-X?node-id=5918-97417&t=JYDQysgjS33AZb74-4 * Avatar picker view.
*
* https://www.figma.com/design/G1xy0HDZKJf5TCRFmKb5d5/Compound-Android-Components?node-id=1949-1384
* *
* It takes a [state], which can be [AvatarPickerState.Pick] for displaying the 'pick avatar' button, or [AvatarPickerState.Selected] when an avatar has * It takes a [state], which can be [AvatarPickerState.Pick] for displaying the 'pick avatar' button, or [AvatarPickerState.Selected] when an avatar has
* already been selected. * already been selected.
@@ -96,7 +104,6 @@ fun AvatarPickerView(
fun eraseBackgroundModifier( fun eraseBackgroundModifier(
parentWidth: Dp, parentWidth: Dp,
editIconRadius: Dp,
) = Modifier ) = Modifier
.graphicsLayer { .graphicsLayer {
compositingStrategy = CompositingStrategy.Offscreen compositingStrategy = CompositingStrategy.Offscreen
@@ -107,13 +114,13 @@ fun AvatarPickerView(
color = Color.Black, color = Color.Black,
center = Offset( center = Offset(
x = if (layoutDirection == LayoutDirection.Ltr) { x = if (layoutDirection == LayoutDirection.Ltr) {
parentWidth.toPx() - editIconRadius.toPx() * 0.48f (parentWidth - editIconContainerRadius + editIconOffset).toPx()
} else { } else {
editIconRadius.toPx() * 0.48f (editIconContainerRadius - editIconOffset).toPx()
}, },
y = size.height - editIconRadius.toPx(), y = size.height - editIconContainerRadius.toPx(),
), ),
radius = editIconRadius.toPx() * 1.2f, radius = (editIconContainerRadius + editIconContainerPadding).toPx(),
blendMode = BlendMode.Clear, blendMode = BlendMode.Clear,
) )
} }
@@ -132,7 +139,7 @@ fun AvatarPickerView(
is AvatarPickerState.Selected -> { is AvatarPickerState.Selected -> {
Box(modifier = modifier) { Box(modifier = modifier) {
val backgroundModifier = if (enabled) { val backgroundModifier = if (enabled) {
eraseBackgroundModifier(state.avatarData.size.dp, state.avatarData.size.dp * 0.225f) eraseBackgroundModifier(state.avatarData.size.dp)
} else { } else {
Modifier Modifier
} }
@@ -143,7 +150,6 @@ fun AvatarPickerView(
) )
if (enabled) { if (enabled) {
OverlayEditButton( OverlayEditButton(
editButtonSize = state.avatarData.size.dp * 0.44f,
onClick = onClick, onClick = onClick,
interactionSource = interactionSource interactionSource = interactionSource
) )
@@ -179,15 +185,14 @@ private fun PickButton(
@Composable @Composable
private fun BoxScope.OverlayEditButton( private fun BoxScope.OverlayEditButton(
editButtonSize: Dp,
onClick: () -> Unit, onClick: () -> Unit,
interactionSource: MutableInteractionSource interactionSource: MutableInteractionSource
) { ) {
Box( Box(
modifier = Modifier modifier = Modifier
.align(Alignment.BottomEnd) .align(Alignment.BottomEnd)
.size(editButtonSize) .size(editIconContainerSize)
.offset(x = editButtonSize * 0.266f) .offset(x = editIconOffset)
.clip(CircleShape) .clip(CircleShape)
.clickable(interactionSource = interactionSource, onClick = onClick, indication = null) .clickable(interactionSource = interactionSource, onClick = onClick, indication = null)
.background(ElementTheme.colors.bgCanvasDefault) .background(ElementTheme.colors.bgCanvasDefault)
@@ -195,7 +200,7 @@ private fun BoxScope.OverlayEditButton(
contentAlignment = Alignment.Center, contentAlignment = Alignment.Center,
) { ) {
Icon( Icon(
modifier = Modifier.size(editButtonSize * 0.66f), modifier = Modifier.size(editIconSize),
imageVector = CompoundIcons.Edit(), imageVector = CompoundIcons.Edit(),
contentDescription = null, contentDescription = null,
) )
@@ -234,97 +239,45 @@ internal fun AvatarPickerViewRtlPreview() = CompositionLocalProvider(
@PreviewsDayNight @PreviewsDayNight
@Composable @Composable
internal fun AvatarPickerSizesPreview() = ElementPreview { internal fun AvatarPickerSizesPreview() = ElementPreview {
Column { // Size used across the codebase
Row { val sizes = listOf(
AvatarPickerView(AvatarPickerState.Pick(buttonSize = 24.dp, externalPadding = PaddingValues(6.dp)), onClick = {}) AvatarSize.EditRoomDetails,
AvatarPickerView(AvatarPickerState.Pick(buttonSize = 32.dp, externalPadding = PaddingValues(6.dp)), onClick = {}) AvatarSize.EditProfileDetails,
AvatarPickerView(AvatarPickerState.Pick(buttonSize = 48.dp, externalPadding = PaddingValues(6.dp)), onClick = {}) )
AvatarPickerView(AvatarPickerState.Pick(buttonSize = 64.dp, externalPadding = PaddingValues(6.dp)), onClick = {}) Column(
AvatarPickerView(AvatarPickerState.Pick(buttonSize = 96.dp, externalPadding = PaddingValues(6.dp)), onClick = {}) modifier = Modifier.padding(12.dp)
) {
Row(verticalAlignment = Alignment.CenterVertically) {
sizes.forEach {
AvatarPickerView(
state = AvatarPickerState.Pick(buttonSize = it.dp, externalPadding = PaddingValues(6.dp)),
onClick = {},
)
}
} }
Row { Row(verticalAlignment = Alignment.CenterVertically) {
AvatarPickerView( sizes.forEach {
AvatarPickerState.Selected( AvatarPickerView(
avatarData = AvatarData("@user:example.com", "User", "content://test", size = AvatarSize.TimelineThreadLatestEventSender), AvatarPickerState.Selected(
type = AvatarType.User avatarData = AvatarData("@user:example.com", "User", "content://test", size = it),
), type = AvatarType.User,
onClick = {}, ),
modifier = Modifier.padding(6.dp) onClick = {},
) modifier = Modifier.padding(6.dp)
AvatarPickerView( )
AvatarPickerState.Selected( }
avatarData = AvatarData("@user:example.com", "User", "content://test", size = AvatarSize.ReadReceiptList),
type = AvatarType.User
),
onClick = {},
modifier = Modifier.padding(6.dp)
)
AvatarPickerView(
AvatarPickerState.Selected(
avatarData = AvatarData("@user:example.com", "User", "content://test", size = AvatarSize.SelectedUser),
type = AvatarType.User
),
onClick = {},
modifier = Modifier.padding(6.dp)
)
AvatarPickerView(
AvatarPickerState.Selected(
avatarData = AvatarData("@user:example.com", "User", "content://test", size = AvatarSize.EditRoomDetails),
type = AvatarType.User
),
onClick = {},
modifier = Modifier.padding(6.dp)
)
AvatarPickerView(
AvatarPickerState.Selected(
avatarData = AvatarData("@user:example.com", "User", "content://test", size = AvatarSize.RoomListManageUser),
type = AvatarType.User
),
onClick = {},
modifier = Modifier.padding(6.dp)
)
} }
Row { Row(verticalAlignment = Alignment.CenterVertically) {
AvatarPickerView( sizes.forEach {
AvatarPickerState.Selected( AvatarPickerView(
avatarData = AvatarData("@user:example.com", "User", "content://test", size = AvatarSize.TimelineThreadLatestEventSender), AvatarPickerState.Selected(
type = AvatarType.Space() avatarData = AvatarData("@user:example.com", "User", "content://test", size = it),
), type = AvatarType.Space(),
onClick = {}, ),
modifier = Modifier.padding(6.dp) onClick = {},
) modifier = Modifier.padding(6.dp)
AvatarPickerView( )
AvatarPickerState.Selected( }
avatarData = AvatarData("@user:example.com", "User", "content://test", size = AvatarSize.ReadReceiptList),
type = AvatarType.Space()
),
onClick = {},
modifier = Modifier.padding(6.dp)
)
AvatarPickerView(
AvatarPickerState.Selected(
avatarData = AvatarData("@user:example.com", "User", "content://test", size = AvatarSize.SelectedUser),
type = AvatarType.Space()
),
onClick = {},
modifier = Modifier.padding(6.dp)
)
AvatarPickerView(
AvatarPickerState.Selected(
avatarData = AvatarData("@user:example.com", "User", "content://test", size = AvatarSize.EditRoomDetails),
type = AvatarType.Space()
),
onClick = {},
modifier = Modifier.padding(6.dp)
)
AvatarPickerView(
AvatarPickerState.Selected(
avatarData = AvatarData("@user:example.com", "User", "content://test", size = AvatarSize.RoomListManageUser),
type = AvatarType.Space()
),
onClick = {},
modifier = Modifier.padding(6.dp)
)
} }
} }
} }
@@ -335,8 +288,9 @@ private fun PreviewContent() {
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
horizontalAlignment = Alignment.CenterHorizontally horizontalAlignment = Alignment.CenterHorizontally
) { ) {
val size = AvatarSize.EditRoomDetails
Text("Pick image") Text("Pick image")
AvatarPickerView(AvatarPickerState.Pick(buttonSize = 48.dp, externalPadding = PaddingValues(6.dp)), onClick = {}) AvatarPickerView(AvatarPickerState.Pick(buttonSize = size.dp, externalPadding = PaddingValues(6.dp)), onClick = {})
HorizontalDivider() HorizontalDivider()
Text("User avatar") Text("User avatar")
@@ -345,7 +299,7 @@ private fun PreviewContent() {
Text("No url") Text("No url")
AvatarPickerView( AvatarPickerView(
AvatarPickerState.Selected( AvatarPickerState.Selected(
avatarData = AvatarData("@user:example.com", "User", null, size = AvatarSize.EditRoomDetails), avatarData = AvatarData("@user:example.com", "User", null, size = size),
type = AvatarType.User type = AvatarType.User
), ),
onClick = {}, onClick = {},
@@ -356,7 +310,7 @@ private fun PreviewContent() {
Text("Local") Text("Local")
AvatarPickerView( AvatarPickerView(
AvatarPickerState.Selected( AvatarPickerState.Selected(
avatarData = AvatarData("@user:example.com", "User", "content://test", size = AvatarSize.EditRoomDetails), avatarData = AvatarData("@user:example.com", "User", "content://test", size = size),
type = AvatarType.User type = AvatarType.User
), ),
onClick = {}, onClick = {},
@@ -367,7 +321,7 @@ private fun PreviewContent() {
Text("MXC") Text("MXC")
AvatarPickerView( AvatarPickerView(
AvatarPickerState.Selected( AvatarPickerState.Selected(
avatarData = AvatarData("@user:example.com", "User", "mxc://test", size = AvatarSize.EditRoomDetails), avatarData = AvatarData("@user:example.com", "User", "mxc://test", size = size),
type = AvatarType.User type = AvatarType.User
), ),
onClick = {}, onClick = {},
@@ -383,7 +337,7 @@ private fun PreviewContent() {
Text("No url") Text("No url")
AvatarPickerView( AvatarPickerView(
AvatarPickerState.Selected( AvatarPickerState.Selected(
avatarData = AvatarData("!room:example.com", "Room", null, size = AvatarSize.EditRoomDetails), avatarData = AvatarData("!room:example.com", "Room", null, size = size),
type = AvatarType.Room() type = AvatarType.Room()
), ),
onClick = {}, onClick = {},
@@ -394,7 +348,7 @@ private fun PreviewContent() {
Text("Local") Text("Local")
AvatarPickerView( AvatarPickerView(
AvatarPickerState.Selected( AvatarPickerState.Selected(
avatarData = AvatarData("!room:example.com", "Room", "content://test", size = AvatarSize.EditRoomDetails), avatarData = AvatarData("!room:example.com", "Room", "content://test", size = size),
type = AvatarType.Room() type = AvatarType.Room()
), ),
onClick = {}, onClick = {},
@@ -405,7 +359,7 @@ private fun PreviewContent() {
Text("MXC") Text("MXC")
AvatarPickerView( AvatarPickerView(
AvatarPickerState.Selected( AvatarPickerState.Selected(
avatarData = AvatarData("!room:example.com", "Room", "mxc://test", size = AvatarSize.EditRoomDetails), avatarData = AvatarData("!room:example.com", "Room", "mxc://test", size = size),
type = AvatarType.Room() type = AvatarType.Room()
), ),
onClick = {}, onClick = {},
@@ -421,7 +375,7 @@ private fun PreviewContent() {
Text("No url") Text("No url")
AvatarPickerView( AvatarPickerView(
AvatarPickerState.Selected( AvatarPickerState.Selected(
avatarData = AvatarData("!room:example.com", "Space", null, size = AvatarSize.EditRoomDetails), avatarData = AvatarData("!room:example.com", "Space", null, size = size),
type = AvatarType.Space() type = AvatarType.Space()
), ),
onClick = {}, onClick = {},
@@ -432,7 +386,7 @@ private fun PreviewContent() {
Text("Local") Text("Local")
AvatarPickerView( AvatarPickerView(
AvatarPickerState.Selected( AvatarPickerState.Selected(
avatarData = AvatarData("!room:example.com", "Space", "content://test", size = AvatarSize.EditRoomDetails), avatarData = AvatarData("!room:example.com", "Space", "content://test", size = size),
type = AvatarType.Space() type = AvatarType.Space()
), ),
onClick = {}, onClick = {},
@@ -443,7 +397,7 @@ private fun PreviewContent() {
Text("MXC") Text("MXC")
AvatarPickerView( AvatarPickerView(
AvatarPickerState.Selected( AvatarPickerState.Selected(
avatarData = AvatarData("!room:example.com", "Space", "mxc://test", size = AvatarSize.EditRoomDetails), avatarData = AvatarData("!room:example.com", "Space", "mxc://test", size = size),
type = AvatarType.Space() type = AvatarType.Space()
), ),
onClick = {}, onClick = {},

View File

@@ -48,7 +48,7 @@ fun SpaceInfoRow(
) { ) {
if (iconVector != null) { if (iconVector != null) {
Icon( Icon(
modifier = Modifier.size(20.dp), modifier = Modifier.size(16.dp),
imageVector = iconVector, imageVector = iconVector,
contentDescription = null, contentDescription = null,
tint = ElementTheme.colors.iconTertiary, tint = ElementTheme.colors.iconTertiary,
@@ -61,7 +61,7 @@ fun SpaceInfoRow(
} }
Text( Text(
text = text, text = text,
style = ElementTheme.typography.fontBodyLgRegular, style = ElementTheme.typography.fontBodyMdRegular,
color = ElementTheme.colors.textSecondary, color = ElementTheme.colors.textSecondary,
textAlign = TextAlign.Center, textAlign = TextAlign.Center,
) )