Skip to content

Change : add tombstoned room decoration #4891

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 13 commits into from
Jun 18, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -152,11 +152,8 @@ class MessagesPresenter @AssistedInject constructor(

val userEventPermissions by userEventPermissions(syncUpdateFlow.value)

val roomName: AsyncData<String> by remember {
derivedStateOf { roomInfo.name?.let { AsyncData.Success(it) } ?: AsyncData.Uninitialized }
}
val roomAvatar: AsyncData<AvatarData> by remember {
derivedStateOf { AsyncData.Success(roomInfo.avatarData()) }
val roomAvatar by remember {
derivedStateOf { roomInfo.avatarData() }
}
val heroes by remember {
derivedStateOf { roomInfo.heroes().toPersistentList() }
Expand Down Expand Up @@ -245,7 +242,7 @@ class MessagesPresenter @AssistedInject constructor(

return MessagesState(
roomId = room.roomId,
roomName = roomName,
roomName = roomInfo.name,
roomAvatar = roomAvatar,
heroes = heroes,
composerState = composerState,
Expand Down Expand Up @@ -292,7 +289,7 @@ class MessagesPresenter @AssistedInject constructor(
return AvatarData(
id = id.value,
name = name,
url = avatarUrl ?: room.info().avatarUrl,
url = avatarUrl,
size = AvatarSize.TimelineRoom
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@ import kotlinx.collections.immutable.ImmutableList
@Immutable
data class MessagesState(
val roomId: RoomId,
val roomName: AsyncData<String>,
val roomAvatar: AsyncData<AvatarData>,
val roomName: String?,
val roomAvatar: AvatarData,
val heroes: ImmutableList<AvatarData>,
val userEventPermissions: UserEventPermissions,
val composerState: MessageComposerState,
Expand All @@ -59,4 +59,6 @@ data class MessagesState(
val roomMemberModerationState: RoomMemberModerationState,
val successorRoom: SuccessorRoom?,
val eventSink: (MessagesEvents) -> Unit
)
) {
val isTombstoned = successorRoom != null
}
Original file line number Diff line number Diff line change
Expand Up @@ -58,10 +58,7 @@ open class MessagesStateProvider : PreviewParameterProvider<MessagesState> {
aMessagesState(composerState = aMessageComposerState(showAttachmentSourcePicker = true)),
aMessagesState(userEventPermissions = aUserEventPermissions(canSendMessage = false)),
aMessagesState(showReinvitePrompt = true),
aMessagesState(
roomName = AsyncData.Uninitialized,
roomAvatar = AsyncData.Uninitialized,
),
aMessagesState(roomName = null),
aMessagesState(composerState = aMessageComposerState(showTextFormatting = true)),
aMessagesState(
enableVoiceMessages = true,
Expand All @@ -86,15 +83,15 @@ open class MessagesStateProvider : PreviewParameterProvider<MessagesState> {
currentPinnedMessageIndex = 0,
),
),
aMessagesState(roomName = AsyncData.Success("A DM with a very looong name"), dmUserVerificationState = IdentityState.Verified),
aMessagesState(roomName = AsyncData.Success("A DM with a very looong name"), dmUserVerificationState = IdentityState.VerificationViolation),
aMessagesState(roomName = "A DM with a very looong name", dmUserVerificationState = IdentityState.Verified),
aMessagesState(roomName = "A DM with a very looong name", dmUserVerificationState = IdentityState.VerificationViolation),
aMessagesState(successorRoom = SuccessorRoom(RoomId("!id:domain"), null)),
)
}

fun aMessagesState(
roomName: AsyncData<String> = AsyncData.Success("Room name"),
roomAvatar: AsyncData<AvatarData> = AsyncData.Success(AvatarData("!id:domain", "Room name", size = AvatarSize.TimelineRoom)),
roomName: String? = "Room name",
roomAvatar: AvatarData = AvatarData("!id:domain", "Room name", size = AvatarSize.TimelineRoom),
userEventPermissions: UserEventPermissions = aUserEventPermissions(),
composerState: MessageComposerState = aMessageComposerState(
textEditorState = aTextEditorStateRich(initialText = "Hello", initialFocus = true),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,10 +83,8 @@ import io.element.android.features.networkmonitor.api.ui.ConnectivityIndicatorVi
import io.element.android.features.roomcall.api.RoomCallState
import io.element.android.libraries.androidutils.ui.hideKeyboard
import io.element.android.libraries.designsystem.atomic.molecules.ComposerAlertMolecule
import io.element.android.libraries.designsystem.atomic.molecules.IconTitlePlaceholdersRowMolecule
import io.element.android.libraries.designsystem.components.avatar.AvatarData
import io.element.android.libraries.designsystem.components.avatar.AvatarSize
import io.element.android.libraries.designsystem.components.avatar.CompositeAvatar
import io.element.android.libraries.designsystem.components.avatar.RoomAvatar
import io.element.android.libraries.designsystem.components.button.BackButton
import io.element.android.libraries.designsystem.components.dialogs.ConfirmationDialog
import io.element.android.libraries.designsystem.preview.ElementPreview
Expand Down Expand Up @@ -194,8 +192,9 @@ fun MessagesView(
Column {
ConnectivityIndicatorView(isOnline = state.hasNetworkConnection)
MessagesViewTopBar(
roomName = state.roomName.dataOrNull(),
roomAvatar = state.roomAvatar.dataOrNull(),
roomName = state.roomName,
roomAvatar = state.roomAvatar,
isTombstoned = state.isTombstoned,
heroes = state.heroes,
roomCallState = state.roomCallState,
dmUserIdentityState = state.dmUserVerificationState,
Expand Down Expand Up @@ -450,8 +449,8 @@ private fun MessagesViewComposerBottomSheetContents(
}
}),
roomId = state.roomId,
roomName = state.roomName.dataOrNull(),
roomAvatarData = state.roomAvatar.dataOrNull(),
roomName = state.roomName,
roomAvatarData = state.roomAvatar,
suggestions = state.composerState.suggestions,
onSelectSuggestion = {
state.composerState.eventSink(MessageComposerEvents.InsertSuggestion(it))
Expand Down Expand Up @@ -491,7 +490,8 @@ private fun MessagesViewComposerBottomSheetContents(
@Composable
private fun MessagesViewTopBar(
roomName: String?,
roomAvatar: AvatarData?,
roomAvatar: AvatarData,
isTombstoned: Boolean,
heroes: ImmutableList<AvatarData>,
roomCallState: RoomCallState,
dmUserIdentityState: IdentityState?,
Expand All @@ -513,19 +513,13 @@ private fun MessagesViewTopBar(
verticalAlignment = Alignment.CenterVertically,
) {
val titleModifier = Modifier.weight(1f, fill = false)
if (roomName != null && roomAvatar != null) {
RoomAvatarAndNameRow(
roomName = roomName,
roomAvatar = roomAvatar,
heroes = heroes,
modifier = titleModifier
)
} else {
IconTitlePlaceholdersRowMolecule(
iconSize = AvatarSize.TimelineRoom.dp,
modifier = titleModifier
)
}
RoomAvatarAndNameRow(
roomName = roomName,
roomAvatar = roomAvatar,
isTombstoned = isTombstoned,
heroes = heroes,
modifier = titleModifier
)

when (dmUserIdentityState) {
IdentityState.Verified -> {
Expand Down Expand Up @@ -559,23 +553,26 @@ private fun MessagesViewTopBar(

@Composable
private fun RoomAvatarAndNameRow(
roomName: String,
roomName: String?,
roomAvatar: AvatarData,
heroes: ImmutableList<AvatarData>,
isTombstoned: Boolean,
modifier: Modifier = Modifier
) {
Row(
modifier = modifier,
verticalAlignment = Alignment.CenterVertically
) {
CompositeAvatar(
RoomAvatar(
avatarData = roomAvatar,
heroes = heroes,
isTombstoned = isTombstoned,
)
Text(
modifier = Modifier.padding(horizontal = 8.dp),
text = roomName,
text = roomName ?: stringResource(CommonStrings.common_no_room_name),
style = ElementTheme.typography.fontBodyLgMedium,
fontStyle = FontStyle.Italic.takeIf { roomName == null },
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
Expand All @@ -586,9 +583,9 @@ private fun RoomAvatarAndNameRow(
private fun CantSendMessageBanner() {
Row(
modifier = Modifier
.fillMaxWidth()
.background(ElementTheme.colors.bgSubtleSecondary)
.padding(16.dp),
.fillMaxWidth()
.background(ElementTheme.colors.bgSubtleSecondary)
.padding(16.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.Center
) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import io.element.android.features.messages.impl.R
import io.element.android.libraries.designsystem.components.avatar.Avatar
import io.element.android.libraries.designsystem.components.avatar.AvatarData
import io.element.android.libraries.designsystem.components.avatar.AvatarSize
import io.element.android.libraries.designsystem.components.avatar.anAvatarData
import io.element.android.libraries.designsystem.preview.ElementPreview
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
import io.element.android.libraries.designsystem.theme.components.HorizontalDivider
Expand All @@ -45,7 +46,7 @@ import kotlinx.collections.immutable.persistentListOf
fun SuggestionsPickerView(
roomId: RoomId,
roomName: String?,
roomAvatarData: AvatarData?,
roomAvatarData: AvatarData,
suggestions: ImmutableList<ResolvedSuggestion>,
onSelectSuggestion: (ResolvedSuggestion) -> Unit,
modifier: Modifier = Modifier,
Expand Down Expand Up @@ -155,7 +156,7 @@ internal fun SuggestionsPickerViewPreview() {
SuggestionsPickerView(
roomId = RoomId("!room:matrix.org"),
roomName = "Room",
roomAvatarData = null,
roomAvatarData = anAvatarData(),
suggestions = persistentListOf(
ResolvedSuggestion.AtRoom,
ResolvedSuggestion.Member(roomMember),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -119,9 +119,9 @@ class MessagesPresenterTest {
presenter.testWithLifecycleOwner {
val initialState = consumeItemsUntilTimeout().last()
assertThat(initialState.roomId).isEqualTo(A_ROOM_ID)
assertThat(initialState.roomName).isEqualTo(AsyncData.Success(""))
assertThat(initialState.roomName).isEqualTo("")
assertThat(initialState.roomAvatar)
.isEqualTo(AsyncData.Success(AvatarData(id = A_ROOM_ID.value, name = "", url = AN_AVATAR_URL, size = AvatarSize.TimelineRoom)))
.isEqualTo(AvatarData(id = A_ROOM_ID.value, name = "", url = AN_AVATAR_URL, size = AvatarSize.TimelineRoom))
assertThat(initialState.userEventPermissions.canSendMessage).isTrue()
assertThat(initialState.userEventPermissions.canRedactOwn).isTrue()
assertThat(initialState.hasNetworkConnection).isTrue()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ class MessagesViewTest {
state = state,
onRoomDetailsClick = callback,
)
rule.onNodeWithText(state.roomName.dataOrNull().orEmpty(), useUnmergedTree = true).performClick()
rule.onNodeWithText(state.roomName.orEmpty(), useUnmergedTree = true).performClick()
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import androidx.compose.ui.text.font.FontStyle
import androidx.compose.ui.tooling.preview.PreviewParameter
import io.element.android.features.preferences.impl.R
import io.element.android.libraries.designsystem.components.async.AsyncActionView
import io.element.android.libraries.designsystem.components.avatar.CompositeAvatar
import io.element.android.libraries.designsystem.components.avatar.RoomAvatar
import io.element.android.libraries.designsystem.components.list.ListItemContent
import io.element.android.libraries.designsystem.components.preferences.PreferenceCategory
import io.element.android.libraries.designsystem.components.preferences.PreferencePage
Expand Down Expand Up @@ -97,7 +97,7 @@ fun EditDefaultNotificationSettingView(
Text(text = subtitle)
},
leadingContent = ListItemContent.Custom {
CompositeAvatar(
RoomAvatar(
avatarData = summary.avatarData,
heroes = summary.heroesAvatar,
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,7 @@ class RoomDetailsPresenter @Inject constructor(
canShowSecurityAndPrivacy = canShowSecurityAndPrivacy,
hasMemberVerificationViolations = hasMemberVerificationViolations,
canReportRoom = canReportRoom,
isTombstoned = roomInfo.successorRoom != null,
eventSink = ::handleEvents,
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ data class RoomDetailsState(
val canShowSecurityAndPrivacy: Boolean,
val hasMemberVerificationViolations: Boolean,
val canReportRoom: Boolean,
val isTombstoned: Boolean,
val eventSink: (RoomDetailsEvent) -> Unit
) {
val roomBadges = buildList {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ open class RoomDetailsStateProvider : PreviewParameterProvider<RoomDetailsState>
aRoomDetailsState(knockRequestsCount = null, canShowKnockRequests = true),
aRoomDetailsState(knockRequestsCount = 4, canShowKnockRequests = true),
aRoomDetailsState(hasMemberVerificationViolations = true),
aRoomDetailsState(isTombstoned = true),
aDmRoomDetailsState(dmRoomMemberVerificationState = UserProfileVerificationState.VERIFIED),
aDmRoomDetailsState(dmRoomMemberVerificationState = UserProfileVerificationState.VERIFICATION_VIOLATION),
// Add other state here
Expand Down Expand Up @@ -118,6 +119,7 @@ fun aRoomDetailsState(
canShowSecurityAndPrivacy: Boolean = true,
hasMemberVerificationViolations: Boolean = false,
canReportRoom: Boolean = true,
isTombstoned: Boolean = false,
eventSink: (RoomDetailsEvent) -> Unit = {},
) = RoomDetailsState(
roomId = roomId,
Expand Down Expand Up @@ -148,6 +150,7 @@ fun aRoomDetailsState(
canShowSecurityAndPrivacy = canShowSecurityAndPrivacy,
hasMemberVerificationViolations = hasMemberVerificationViolations,
canReportRoom = canReportRoom,
isTombstoned = isTombstoned,
eventSink = eventSink,
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,8 @@ import io.element.android.libraries.designsystem.atomic.molecules.MatrixBadgeRow
import io.element.android.libraries.designsystem.components.ClickableLinkText
import io.element.android.libraries.designsystem.components.avatar.AvatarData
import io.element.android.libraries.designsystem.components.avatar.AvatarSize
import io.element.android.libraries.designsystem.components.avatar.CompositeAvatar
import io.element.android.libraries.designsystem.components.avatar.DmAvatars
import io.element.android.libraries.designsystem.components.avatar.RoomAvatar
import io.element.android.libraries.designsystem.components.button.BackButton
import io.element.android.libraries.designsystem.components.button.MainActionButton
import io.element.android.libraries.designsystem.components.list.ListItemContent
Expand Down Expand Up @@ -138,6 +138,7 @@ fun RoomDetailsView(
roomName = state.roomName,
roomAlias = state.roomAlias,
heroes = state.heroes,
isTombstoned = state.isTombstoned,
openAvatarPreview = { avatarUrl ->
openAvatarPreview(state.roomName, avatarUrl)
},
Expand Down Expand Up @@ -380,6 +381,7 @@ private fun RoomHeaderSection(
roomName: String,
roomAlias: RoomAlias?,
heroes: ImmutableList<MatrixUser>,
isTombstoned: Boolean,
openAvatarPreview: (url: String) -> Unit,
onSubtitleClick: (String) -> Unit,
) {
Expand All @@ -389,11 +391,12 @@ private fun RoomHeaderSection(
.padding(horizontal = 16.dp),
horizontalAlignment = Alignment.CenterHorizontally,
) {
CompositeAvatar(
RoomAvatar(
avatarData = AvatarData(roomId.value, roomName, avatarUrl, AvatarSize.RoomHeader),
heroes = heroes.map { user ->
user.getAvatarData(size = AvatarSize.RoomHeader)
}.toPersistentList(),
isTombstoned = isTombstoned,
modifier = Modifier
.clickable(enabled = avatarUrl != null) { openAvatarPreview(avatarUrl!!) }
.testTag(TestTags.roomDetailAvatar)
Expand Down
Loading
Loading