diff --git a/features/createroom/impl/src/main/res/values-de/translations.xml b/features/createroom/impl/src/main/res/values-de/translations.xml
index ec038d764e..6c3a7c8130 100644
--- a/features/createroom/impl/src/main/res/values-de/translations.xml
+++ b/features/createroom/impl/src/main/res/values-de/translations.xml
@@ -1,6 +1,7 @@
"Neuer Raum"
+ "Personen einladen"
"Personen hinzufügen"
"Privater Raum (nur auf Einladung)"
"Raumname"
diff --git a/features/login/impl/src/main/res/values-de/translations.xml b/features/login/impl/src/main/res/values-de/translations.xml
index 061f3453df..4c9b232147 100644
--- a/features/login/impl/src/main/res/values-de/translations.xml
+++ b/features/login/impl/src/main/res/values-de/translations.xml
@@ -1,5 +1,9 @@
+ "Wir konnten diesen Homeserver nicht erreichen. Bitte überprüfen Sie, ob Sie die Homeserver-URL korrekt eingegeben haben. Wenn die URL korrekt ist, wenden Sie sich an Ihren Homeserver-Administrator, um weitere Hilfe zu erhalten."
+ "Dieser Server unterstützt derzeit kein Sliding Sync."
+ "Homeserver-URL"
+ "Sie können nur eine Verbindung zu einem vorhandenen Server herstellen, der Sliding Sync unterstützt. Ihr Homeserver-Administrator muss dies konfigurieren. %1$s"
"Wie lautet die Adresse deines Servers?"
"Willkommen zurück!"
"Passwort"
diff --git a/features/messages/impl/build.gradle.kts b/features/messages/impl/build.gradle.kts
index d50c02bc82..de41e5aa7f 100644
--- a/features/messages/impl/build.gradle.kts
+++ b/features/messages/impl/build.gradle.kts
@@ -41,6 +41,7 @@ dependencies {
implementation(projects.libraries.textcomposer)
implementation(projects.libraries.uiStrings)
implementation(projects.libraries.dateformatter.api)
+ implementation(projects.libraries.eventformatter.api)
implementation(projects.libraries.mediapickers.api)
implementation(projects.libraries.featureflag.api)
implementation(projects.libraries.mediaupload.api)
diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesStateProvider.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesStateProvider.kt
index 9249b808a1..beaba74a8c 100644
--- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesStateProvider.kt
+++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesStateProvider.kt
@@ -20,9 +20,9 @@ import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import io.element.android.features.messages.impl.actionlist.anActionListState
import io.element.android.features.messages.impl.textcomposer.AttachmentSourcePicker
import io.element.android.features.messages.impl.textcomposer.aMessageComposerState
-import io.element.android.features.messages.impl.timeline.aTimelineItemContent
import io.element.android.features.messages.impl.timeline.aTimelineItemList
import io.element.android.features.messages.impl.timeline.aTimelineState
+import io.element.android.features.messages.impl.timeline.model.event.aTimelineItemTextContent
import io.element.android.libraries.core.data.StableCharSequence
import io.element.android.libraries.designsystem.components.avatar.AvatarData
import io.element.android.libraries.matrix.api.core.RoomId
@@ -48,7 +48,7 @@ fun aMessagesState() = MessagesState(
mode = MessageComposerMode.Normal("Hello"),
),
timelineState = aTimelineState().copy(
- timelineItems = aTimelineItemList(aTimelineItemContent()),
+ timelineItems = aTimelineItemList(aTimelineItemTextContent()),
),
actionListState = anActionListState(),
hasNetworkConnection = true,
diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesView.kt
index a55ea1021d..c1708d3086 100644
--- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesView.kt
+++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesView.kt
@@ -63,6 +63,7 @@ import io.element.android.features.messages.impl.actionlist.model.TimelineItemAc
import io.element.android.features.messages.impl.textcomposer.AttachmentSourcePicker
import io.element.android.features.messages.impl.textcomposer.MessageComposerEvents
import io.element.android.features.messages.impl.textcomposer.MessageComposerView
+import io.element.android.features.messages.impl.timeline.TimelineEvents
import io.element.android.features.messages.impl.timeline.TimelineView
import io.element.android.features.messages.impl.timeline.model.TimelineItem
import io.element.android.features.networkmonitor.api.ui.ConnectivityIndicatorView
@@ -139,6 +140,11 @@ fun MessagesView(
}
}
+ fun onExpandGroupClick(event: TimelineItem.GroupedEvents) {
+ Timber.v("onExpandGroupClick= ${event.id}")
+ state.timelineState.eventSink(TimelineEvents.ToggleExpandGroup(event))
+ }
+
fun onActionSelected(action: TimelineItemAction, event: TimelineItem.Event) {
state.eventSink(MessagesEvents.HandleAction(action, event))
}
@@ -189,7 +195,8 @@ fun MessagesView(
.padding(padding)
.consumeWindowInsets(padding),
onMessageClicked = ::onMessageClicked,
- onMessageLongClicked = ::onMessageLongClicked
+ onMessageLongClicked = ::onMessageLongClicked,
+ onExpandGroupClick = ::onExpandGroupClick,
)
},
snackbarHost = {
@@ -214,6 +221,7 @@ fun MessagesViewContent(
modifier: Modifier = Modifier,
onMessageClicked: (TimelineItem.Event) -> Unit = {},
onMessageLongClicked: (TimelineItem.Event) -> Unit = {},
+ onExpandGroupClick: (TimelineItem.GroupedEvents) -> Unit = {},
) {
Column(
modifier = modifier
@@ -227,7 +235,8 @@ fun MessagesViewContent(
state = state.timelineState,
modifier = Modifier.weight(1f),
onMessageClicked = onMessageClicked,
- onMessageLongClicked = onMessageLongClicked
+ onMessageLongClicked = onMessageLongClicked,
+ onExpandGroupClick = onExpandGroupClick,
)
}
MessageComposerView(
diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListPresenter.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListPresenter.kt
index cdffa9c618..7eb3fbe433 100644
--- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListPresenter.kt
+++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListPresenter.kt
@@ -24,6 +24,7 @@ import androidx.compose.runtime.rememberCoroutineScope
import io.element.android.features.messages.impl.actionlist.model.TimelineItemAction
import io.element.android.features.messages.impl.timeline.model.TimelineItem
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemRedactedContent
+import io.element.android.features.messages.impl.timeline.model.event.TimelineItemStateContent
import io.element.android.libraries.architecture.Presenter
import kotlinx.collections.immutable.toImmutableList
import kotlinx.coroutines.CoroutineScope
@@ -53,20 +54,25 @@ class ActionListPresenter @Inject constructor() : Presenter {
)
}
- fun CoroutineScope.computeForMessage(timelineItem: TimelineItem.Event, target: MutableState) = launch {
+ private fun CoroutineScope.computeForMessage(timelineItem: TimelineItem.Event, target: MutableState) = launch {
target.value = ActionListState.Target.Loading(timelineItem)
val actions =
- if (timelineItem.content is TimelineItemRedactedContent) {
- emptyList()
- } else {
- mutableListOf(
- TimelineItemAction.Reply,
- TimelineItemAction.Forward,
- TimelineItemAction.Copy,
- ).also {
- if (timelineItem.isMine) {
- it.add(TimelineItemAction.Edit)
- it.add(TimelineItemAction.Redact)
+ when (timelineItem.content) {
+ is TimelineItemRedactedContent,
+ is TimelineItemStateContent -> {
+ // TODO Add Share action (also) here, and developer options
+ emptyList()
+ }
+ else -> {
+ mutableListOf(
+ TimelineItemAction.Reply,
+ TimelineItemAction.Forward,
+ TimelineItemAction.Copy,
+ ).also {
+ if (timelineItem.isMine) {
+ it.add(TimelineItemAction.Edit)
+ it.add(TimelineItemAction.Redact)
+ }
}
}
}
diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineEvents.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineEvents.kt
index ff64441198..11f1a1a483 100644
--- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineEvents.kt
+++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineEvents.kt
@@ -16,9 +16,11 @@
package io.element.android.features.messages.impl.timeline
+import io.element.android.features.messages.impl.timeline.model.TimelineItem
import io.element.android.libraries.matrix.api.core.EventId
sealed interface TimelineEvents {
object LoadMore : TimelineEvents
data class SetHighlightedEvent(val eventId: EventId?) : TimelineEvents
+ data class ToggleExpandGroup(val event: TimelineItem.GroupedEvents) : TimelineEvents
}
diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelinePresenter.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelinePresenter.kt
index 9418e59edc..1693a623f3 100644
--- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelinePresenter.kt
+++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelinePresenter.kt
@@ -17,15 +17,18 @@
package io.element.android.features.messages.impl.timeline
import androidx.compose.runtime.Composable
-import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.mutableStateMapOf
import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.saveable.rememberSaveable
import io.element.android.features.messages.impl.timeline.factories.TimelineItemsFactory
+import io.element.android.features.messages.impl.timeline.groups.TimelineItemGrouper
import io.element.android.libraries.architecture.Presenter
+import io.element.android.libraries.core.bool.orFalse
import io.element.android.libraries.matrix.api.core.EventId
import io.element.android.libraries.matrix.api.room.MatrixRoom
import io.element.android.libraries.matrix.api.timeline.MatrixTimeline
@@ -42,6 +45,7 @@ private const val backPaginationPageSize = 50
class TimelinePresenter @Inject constructor(
private val timelineItemsFactory: TimelineItemsFactory,
+ private val timelineItemGrouper: TimelineItemGrouper,
room: MatrixRoom,
) : Presenter {
@@ -53,6 +57,8 @@ class TimelinePresenter @Inject constructor(
val highlightedEventId: MutableState = rememberSaveable {
mutableStateOf(null)
}
+ val expandedGroups = remember { mutableStateMapOf() }
+
val timelineItems = timelineItemsFactory
.flow()
.collectAsState()
@@ -65,6 +71,9 @@ class TimelinePresenter @Inject constructor(
when (event) {
TimelineEvents.LoadMore -> localCoroutineScope.loadMore(paginationState.value)
is TimelineEvents.SetHighlightedEvent -> highlightedEventId.value = event.eventId
+ is TimelineEvents.ToggleExpandGroup -> {
+ expandedGroups[event.event.identifier()] = expandedGroups[event.event.identifier()].orFalse().not()
+ }
}
}
@@ -83,7 +92,7 @@ class TimelinePresenter @Inject constructor(
return TimelineState(
highlightedEventId = highlightedEventId.value,
paginationState = paginationState.value,
- timelineItems = timelineItems.value.toImmutableList(),
+ timelineItems = timelineItemGrouper.group(timelineItems.value, expandedGroups).toImmutableList(),
eventSink = ::handleEvents
)
}
diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineStateProvider.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineStateProvider.kt
index f6eaa55267..e119cb6e6c 100644
--- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineStateProvider.kt
+++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineStateProvider.kt
@@ -21,7 +21,8 @@ import io.element.android.features.messages.impl.timeline.model.TimelineItem
import io.element.android.features.messages.impl.timeline.model.TimelineItemGroupPosition
import io.element.android.features.messages.impl.timeline.model.TimelineItemReactions
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemEventContent
-import io.element.android.features.messages.impl.timeline.model.event.TimelineItemTextContent
+import io.element.android.features.messages.impl.timeline.model.event.aTimelineItemStateEventContent
+import io.element.android.features.messages.impl.timeline.model.event.aTimelineItemTextContent
import io.element.android.libraries.designsystem.components.avatar.AvatarData
import io.element.android.libraries.matrix.api.core.EventId
import io.element.android.libraries.matrix.api.core.UserId
@@ -55,6 +56,12 @@ internal fun aTimelineItemList(content: TimelineItemEventContent): ImmutableList
content = content,
groupPosition = TimelineItemGroupPosition.First
),
+ // A state event on top of it
+ aTimelineItemEvent(
+ isMine = false,
+ content = aTimelineItemStateEventContent(),
+ groupPosition = TimelineItemGroupPosition.None
+ ),
// 3 items (First Middle Last) with isMine = true
aTimelineItemEvent(
isMine = true,
@@ -71,12 +78,18 @@ internal fun aTimelineItemList(content: TimelineItemEventContent): ImmutableList
content = content,
groupPosition = TimelineItemGroupPosition.First
),
+ // A state event on top of it
+ aTimelineItemEvent(
+ isMine = true,
+ content = aTimelineItemStateEventContent(),
+ groupPosition = TimelineItemGroupPosition.None
+ ),
)
}
internal fun aTimelineItemEvent(
isMine: Boolean = false,
- content: TimelineItemEventContent = aTimelineItemContent(),
+ content: TimelineItemEventContent = aTimelineItemTextContent(),
groupPosition: TimelineItemGroupPosition = TimelineItemGroupPosition.First
): TimelineItem.Event {
val randomId = "\$" + Random.nextInt().toString()
@@ -96,10 +109,3 @@ internal fun aTimelineItemEvent(
groupPosition = groupPosition,
)
}
-
-internal fun aTimelineItemContent(): TimelineItemEventContent {
- return TimelineItemTextContent(
- body = "Text",
- htmlDocument = null
- )
-}
diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineView.kt
index a378c508f6..4478ddb9eb 100644
--- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineView.kt
+++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineView.kt
@@ -16,8 +16,8 @@
package io.element.android.features.messages.impl.timeline
+import androidx.compose.animation.animateContentSize
import androidx.compose.foundation.interaction.MutableInteractionSource
-import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.BoxScope
import androidx.compose.foundation.layout.Column
@@ -50,19 +50,24 @@ import androidx.compose.runtime.snapshotFlow
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.LastBaseline
+import androidx.compose.ui.res.pluralStringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.unit.dp
import androidx.compose.ui.zIndex
+import io.element.android.features.messages.impl.R
import io.element.android.features.messages.impl.timeline.components.MessageEventBubble
+import io.element.android.features.messages.impl.timeline.components.MessageStateEventContainer
import io.element.android.features.messages.impl.timeline.components.TimelineItemReactionsView
import io.element.android.features.messages.impl.timeline.components.event.TimelineItemEventContentView
+import io.element.android.features.messages.impl.timeline.components.group.GroupHeaderView
import io.element.android.features.messages.impl.timeline.components.virtual.TimelineItemDaySeparatorView
import io.element.android.features.messages.impl.timeline.components.virtual.TimelineLoadingMoreIndicator
import io.element.android.features.messages.impl.timeline.model.TimelineItem
import io.element.android.features.messages.impl.timeline.model.bubble.BubbleState
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemEventContent
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemEventContentProvider
+import io.element.android.features.messages.impl.timeline.model.event.TimelineItemStateContent
import io.element.android.features.messages.impl.timeline.model.virtual.TimelineItemDaySeparatorModel
import io.element.android.features.messages.impl.timeline.model.virtual.TimelineItemLoadingModel
import io.element.android.libraries.designsystem.components.avatar.Avatar
@@ -82,6 +87,7 @@ fun TimelineView(
modifier: Modifier = Modifier,
onMessageClicked: (TimelineItem.Event) -> Unit = {},
onMessageLongClicked: (TimelineItem.Event) -> Unit = {},
+ onExpandGroupClick: (TimelineItem.GroupedEvents) -> Unit = {},
) {
fun onReachedLoadMore() {
@@ -93,8 +99,6 @@ fun TimelineView(
LazyColumn(
modifier = Modifier.fillMaxSize(),
state = lazyListState,
- horizontalAlignment = Alignment.Start,
- verticalArrangement = Arrangement.Bottom,
reverseLayout = true
) {
itemsIndexed(
@@ -104,9 +108,10 @@ fun TimelineView(
) { index, timelineItem ->
TimelineItemRow(
timelineItem = timelineItem,
- isHighlighted = timelineItem.identifier() == state.highlightedEventId?.value,
+ highlightedItem = state.highlightedEventId?.value,
onClick = onMessageClicked,
- onLongClick = onMessageLongClicked
+ onLongClick = onMessageLongClicked,
+ onExpandGroupClick = onExpandGroupClick,
)
if (index == state.timelineItems.lastIndex) {
onReachedLoadMore()
@@ -125,16 +130,20 @@ fun TimelineView(
@Composable
fun TimelineItemRow(
timelineItem: TimelineItem,
- isHighlighted: Boolean,
+ highlightedItem: String?,
onClick: (TimelineItem.Event) -> Unit,
onLongClick: (TimelineItem.Event) -> Unit,
+ onExpandGroupClick: (TimelineItem.GroupedEvents) -> Unit,
+ modifier: Modifier = Modifier
) {
when (timelineItem) {
- is TimelineItem.Virtual -> TimelineItemVirtualRow(
- virtual = timelineItem
- )
+ is TimelineItem.Virtual -> {
+ TimelineItemVirtualRow(
+ virtual = timelineItem,
+ modifier = modifier,
+ )
+ }
is TimelineItem.Event -> {
-
fun onClick() {
onClick(timelineItem)
}
@@ -143,12 +152,54 @@ fun TimelineItemRow(
onLongClick(timelineItem)
}
- TimelineItemEventRow(
- event = timelineItem,
- isHighlighted = isHighlighted,
- onClick = ::onClick,
- onLongClick = ::onLongClick
- )
+ if (timelineItem.content is TimelineItemStateContent) {
+ TimelineItemStateEventRow(
+ event = timelineItem,
+ isHighlighted = highlightedItem == timelineItem.identifier(),
+ onClick = ::onClick,
+ onLongClick = ::onLongClick,
+ modifier = modifier,
+ )
+ } else {
+ TimelineItemEventRow(
+ event = timelineItem,
+ isHighlighted = highlightedItem == timelineItem.identifier(),
+ onClick = ::onClick,
+ onLongClick = ::onLongClick,
+ modifier = modifier,
+ )
+ }
+ }
+ is TimelineItem.GroupedEvents -> {
+ fun onExpandGroupClick() {
+ onExpandGroupClick(timelineItem)
+ }
+
+ Column(modifier = modifier.animateContentSize()) {
+ GroupHeaderView(
+ text = pluralStringResource(
+ id = R.plurals.room_timeline_state_changes,
+ count = timelineItem.events.size,
+ timelineItem.events.size
+ ),
+ isExpanded = timelineItem.expanded,
+ isHighlighted = !timelineItem.expanded && timelineItem.events.any { it.identifier() == highlightedItem },
+ onClick = ::onExpandGroupClick,
+ )
+ if (timelineItem.expanded) {
+ Column {
+ timelineItem.events.forEach { subGroupEvent ->
+ TimelineItemRow(
+ timelineItem = subGroupEvent,
+ highlightedItem = highlightedItem,
+ onClick = onClick,
+ onLongClick = onLongClick,
+ onExpandGroupClick = {}
+ )
+ }
+ }
+ }
+ }
}
}
}
@@ -232,6 +283,42 @@ fun TimelineItemEventRow(
}
}
+@Composable
+fun TimelineItemStateEventRow(
+ event: TimelineItem.Event,
+ isHighlighted: Boolean,
+ onClick: () -> Unit,
+ onLongClick: () -> Unit,
+ modifier: Modifier = Modifier
+) {
+ val interactionSource = remember { MutableInteractionSource() }
+ Box(
+ modifier = modifier
+ .fillMaxWidth()
+ .wrapContentHeight(),
+ contentAlignment = Alignment.Center
+ ) {
+ MessageStateEventContainer(
+ isHighlighted = isHighlighted,
+ interactionSource = interactionSource,
+ onClick = onClick,
+ onLongClick = onLongClick,
+ modifier = Modifier
+ .zIndex(-1f)
+ .widthIn(max = 320.dp)
+ ) {
+ val contentModifier = Modifier.padding(horizontal = 12.dp, vertical = 6.dp)
+ TimelineItemEventContentView(
+ content = event.content,
+ interactionSource = interactionSource,
+ onClick = onClick,
+ onLongClick = onLongClick,
+ modifier = contentModifier
+ )
+ }
+ }
+}
+
@Composable
private fun MessageSenderInformation(
sender: String,
diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/MessageEventBubble.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/MessageEventBubble.kt
index b696b63f92..606d185568 100644
--- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/MessageEventBubble.kt
+++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/MessageEventBubble.kt
@@ -84,6 +84,7 @@ fun MessageEventBubble(
fun Modifier.offsetForItem(): Modifier {
return if (state.isMine) {
+ // FIXME setting y offset to -12.dp can overlap a state event displayed above.
offset(y = -(12.dp))
} else {
offset(x = 20.dp, y = -(12.dp))
diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/MessageStateEventContainer.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/MessageStateEventContainer.kt
new file mode 100644
index 0000000000..fa1faaa93d
--- /dev/null
+++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/MessageStateEventContainer.kt
@@ -0,0 +1,99 @@
+/*
+ * Copyright (c) 2023 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.element.android.features.messages.impl.timeline.components
+
+import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.combinedClickable
+import androidx.compose.foundation.interaction.MutableInteractionSource
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.layout.widthIn
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material.ripple.rememberRipple
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import io.element.android.libraries.designsystem.preview.ElementPreviewDark
+import io.element.android.libraries.designsystem.preview.ElementPreviewLight
+import io.element.android.libraries.designsystem.theme.ElementTheme
+import io.element.android.libraries.designsystem.theme.components.Surface
+
+private val CORNER_RADIUS = 8.dp
+
+@OptIn(ExperimentalFoundationApi::class)
+@Composable
+fun MessageStateEventContainer(
+ isHighlighted: Boolean,
+ interactionSource: MutableInteractionSource,
+ modifier: Modifier = Modifier,
+ onClick: () -> Unit = {},
+ onLongClick: () -> Unit = {},
+ content: @Composable () -> Unit = {},
+) {
+ val backgroundColor = if (isHighlighted) {
+ ElementTheme.colors.messageHighlightedBackground
+ } else {
+ Color.Companion.Transparent
+ }
+ val shape = RoundedCornerShape(CORNER_RADIUS)
+ Surface(
+ modifier = modifier
+ .widthIn(min = 80.dp)
+ .clip(shape)
+ .combinedClickable(
+ onClick = onClick,
+ onLongClick = onLongClick,
+ indication = rememberRipple(),
+ interactionSource = interactionSource
+ ),
+ color = backgroundColor,
+ shape = shape,
+ content = content
+ )
+}
+
+@Preview
+@Composable
+internal fun MessageStateEventContainerLightPreview() =
+ ElementPreviewLight { ContentToPreview() }
+
+@Preview
+@Composable
+internal fun MessageStateEventContainerDarkPreview() =
+ ElementPreviewDark { ContentToPreview() }
+
+@Composable
+private fun ContentToPreview() {
+ Column {
+ MessageStateEventContainer(
+ isHighlighted = false,
+ interactionSource = MutableInteractionSource(),
+ ) {
+ Spacer(modifier = Modifier.size(width = 120.dp, height = 32.dp))
+ }
+ MessageStateEventContainer(
+ isHighlighted = true,
+ interactionSource = MutableInteractionSource(),
+ ) {
+ Spacer(modifier = Modifier.size(width = 120.dp, height = 32.dp))
+ }
+ }
+}
diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemContentView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemContentView.kt
index bfb1d7f0c7..103b444cf9 100644
--- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemContentView.kt
+++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemContentView.kt
@@ -23,6 +23,7 @@ import io.element.android.features.messages.impl.timeline.model.event.TimelineIt
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemEventContent
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemImageContent
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemRedactedContent
+import io.element.android.features.messages.impl.timeline.model.event.TimelineItemStateContent
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemTextBasedContent
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemUnknownContent
@@ -58,5 +59,9 @@ fun TimelineItemEventContentView(
content = content,
modifier = modifier
)
+ is TimelineItemStateContent -> TimelineItemStateView(
+ content = content,
+ modifier = modifier
+ )
}
}
diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemStateView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemStateView.kt
new file mode 100644
index 0000000000..1402d59fd6
--- /dev/null
+++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemStateView.kt
@@ -0,0 +1,58 @@
+/*
+ * Copyright (c) 2023 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.element.android.features.messages.impl.timeline.components.event
+
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.sp
+import io.element.android.features.messages.impl.timeline.model.event.TimelineItemStateContent
+import io.element.android.features.messages.impl.timeline.model.event.aTimelineItemStateEventContent
+import io.element.android.libraries.designsystem.preview.ElementPreviewDark
+import io.element.android.libraries.designsystem.preview.ElementPreviewLight
+import io.element.android.libraries.designsystem.theme.components.Text
+
+@Composable
+fun TimelineItemStateView(
+ content: TimelineItemStateContent,
+ modifier: Modifier = Modifier
+) {
+ Text(
+ modifier = modifier,
+ color = MaterialTheme.colorScheme.secondary,
+ fontSize = 13.sp,
+ text = content.body,
+ textAlign = TextAlign.Center,
+ )
+}
+
+@Preview
+@Composable
+internal fun TimelineItemStateViewLightPreview() = ElementPreviewLight { ContentToPreview() }
+
+@Preview
+@Composable
+internal fun TimelineItemStateViewDarkPreview() = ElementPreviewDark { ContentToPreview() }
+
+@Composable
+private fun ContentToPreview() {
+ TimelineItemStateView(
+ content = aTimelineItemStateEventContent(),
+ )
+}
diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/group/GroupHeaderView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/group/GroupHeaderView.kt
new file mode 100644
index 0000000000..e747134405
--- /dev/null
+++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/group/GroupHeaderView.kt
@@ -0,0 +1,134 @@
+/*
+ * Copyright (c) 2023 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.element.android.features.messages.impl.timeline.components.group
+
+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.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.ExpandLess
+import androidx.compose.material.icons.filled.ExpandMore
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.runtime.Composable
+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.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
+import io.element.android.libraries.designsystem.preview.ElementPreviewDark
+import io.element.android.libraries.designsystem.preview.ElementPreviewLight
+import io.element.android.libraries.designsystem.theme.ElementTheme
+import io.element.android.libraries.designsystem.theme.components.Icon
+import io.element.android.libraries.designsystem.theme.components.Surface
+import io.element.android.libraries.designsystem.theme.components.Text
+
+private val CORNER_RADIUS = 8.dp
+
+@Composable
+fun GroupHeaderView(
+ text: String,
+ isExpanded: Boolean,
+ isHighlighted: Boolean,
+ onClick: () -> Unit,
+ modifier: Modifier = Modifier
+) {
+ val backgroundColor = if (isHighlighted) {
+ ElementTheme.colors.messageHighlightedBackground
+ } else {
+ Color.Companion.Transparent
+ }
+ val shape = RoundedCornerShape(CORNER_RADIUS)
+
+ Box(
+ modifier = modifier
+ .fillMaxWidth(),
+ contentAlignment = Alignment.Center
+ ) {
+ Surface(
+ modifier = Modifier
+ .clip(shape)
+ .clickable(onClick = onClick),
+ color = backgroundColor,
+ shape = shape,
+ ) {
+ Row(
+ modifier = Modifier
+ .padding(8.dp),
+ verticalAlignment = Alignment.CenterVertically,
+ ) {
+ Text(
+ text = text,
+ color = MaterialTheme.colorScheme.secondary,
+ fontSize = 13.sp
+ )
+ val icon = if (isExpanded) {
+ Icons.Default.ExpandLess
+ } else {
+ Icons.Default.ExpandMore
+ }
+ Icon(icon, "", tint = MaterialTheme.colorScheme.secondary)
+ }
+ }
+ }
+}
+
+@Preview
+@Composable
+fun GroupHeaderViewLightPreview() =
+ ElementPreviewLight { ContentToPreview() }
+
+@Preview
+@Composable
+fun GroupHeaderViewDarkPreview() =
+ ElementPreviewDark { ContentToPreview() }
+
+@Composable
+private fun ContentToPreview() {
+ Column(verticalArrangement = Arrangement.spacedBy(4.dp)) {
+ GroupHeaderView(
+ text = "8 room changes (expanded)",
+ isExpanded = true,
+ isHighlighted = false,
+ onClick = {}
+ )
+ GroupHeaderView(
+ text = "8 room changes (not expanded)",
+ isExpanded = false,
+ isHighlighted = false,
+ onClick = {}
+ )
+ GroupHeaderView(
+ text = "8 room changes (expanded/h)",
+ isExpanded = true,
+ isHighlighted = true,
+ onClick = {}
+ )
+ GroupHeaderView(
+ text = "8 room changes (not expanded/h)",
+ isExpanded = false,
+ isHighlighted = true,
+ onClick = {}
+ )
+ }
+}
diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentFactory.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentFactory.kt
index 527de007b1..eb6d0e45c0 100644
--- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentFactory.kt
+++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentFactory.kt
@@ -18,6 +18,7 @@ package io.element.android.features.messages.impl.timeline.factories.event
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemEventContent
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemUnknownContent
+import io.element.android.libraries.matrix.api.timeline.item.event.EventTimelineItem
import io.element.android.libraries.matrix.api.timeline.item.event.FailedToParseMessageLikeContent
import io.element.android.libraries.matrix.api.timeline.item.event.FailedToParseStateContent
import io.element.android.libraries.matrix.api.timeline.item.event.MessageContent
@@ -26,7 +27,6 @@ import io.element.android.libraries.matrix.api.timeline.item.event.RedactedConte
import io.element.android.libraries.matrix.api.timeline.item.event.RoomMembershipContent
import io.element.android.libraries.matrix.api.timeline.item.event.StateContent
import io.element.android.libraries.matrix.api.timeline.item.event.StickerContent
-import io.element.android.libraries.matrix.api.timeline.item.event.EventContent
import io.element.android.libraries.matrix.api.timeline.item.event.UnableToDecryptContent
import io.element.android.libraries.matrix.api.timeline.item.event.UnknownContent
import javax.inject.Inject
@@ -43,15 +43,15 @@ class TimelineItemContentFactory @Inject constructor(
private val failedToParseStateFactory: TimelineItemContentFailedToParseStateFactory
) {
- fun create(itemContent: EventContent): TimelineItemEventContent {
- return when (itemContent) {
+ fun create(eventTimelineItem: EventTimelineItem): TimelineItemEventContent {
+ return when (val itemContent = eventTimelineItem.content) {
is FailedToParseMessageLikeContent -> failedToParseMessageFactory.create(itemContent)
is FailedToParseStateContent -> failedToParseStateFactory.create(itemContent)
is MessageContent -> messageFactory.create(itemContent)
- is ProfileChangeContent -> profileChangeFactory.create(itemContent)
+ is ProfileChangeContent -> profileChangeFactory.create(eventTimelineItem)
is RedactedContent -> redactedMessageFactory.create(itemContent)
- is RoomMembershipContent -> roomMembershipFactory.create(itemContent)
- is StateContent -> stateFactory.create(itemContent)
+ is RoomMembershipContent -> roomMembershipFactory.create(eventTimelineItem)
+ is StateContent -> stateFactory.create(eventTimelineItem)
is StickerContent -> stickerFactory.create(itemContent)
is UnableToDecryptContent -> utdFactory.create(itemContent)
is UnknownContent -> TimelineItemUnknownContent
diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentProfileChangeFactory.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentProfileChangeFactory.kt
index 689bbcbaa1..e54d88326d 100644
--- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentProfileChangeFactory.kt
+++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentProfileChangeFactory.kt
@@ -17,13 +17,18 @@
package io.element.android.features.messages.impl.timeline.factories.event
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemEventContent
-import io.element.android.features.messages.impl.timeline.model.event.TimelineItemUnknownContent
-import io.element.android.libraries.matrix.api.timeline.item.event.ProfileChangeContent
+import io.element.android.features.messages.impl.timeline.model.event.TimelineItemProfileChangeContent
+import io.element.android.libraries.core.extensions.orEmpty
+import io.element.android.libraries.eventformatter.api.TimelineEventFormatter
+import io.element.android.libraries.matrix.api.timeline.item.event.EventTimelineItem
import javax.inject.Inject
-class TimelineItemContentProfileChangeFactory @Inject constructor() {
+class TimelineItemContentProfileChangeFactory @Inject constructor(
+ private val timelineEventFormatter: TimelineEventFormatter,
+) {
- fun create(content: ProfileChangeContent): TimelineItemEventContent {
- return TimelineItemUnknownContent
+ fun create(eventTimelineItem: EventTimelineItem): TimelineItemEventContent {
+ val text = timelineEventFormatter.format(eventTimelineItem)
+ return TimelineItemProfileChangeContent(text.orEmpty().toString())
}
}
diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentRoomMembershipFactory.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentRoomMembershipFactory.kt
index 808e0d3b70..d5cf0cec2d 100644
--- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentRoomMembershipFactory.kt
+++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentRoomMembershipFactory.kt
@@ -17,13 +17,18 @@
package io.element.android.features.messages.impl.timeline.factories.event
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemEventContent
-import io.element.android.features.messages.impl.timeline.model.event.TimelineItemUnknownContent
-import io.element.android.libraries.matrix.api.timeline.item.event.RoomMembershipContent
+import io.element.android.features.messages.impl.timeline.model.event.TimelineItemRoomMembershipContent
+import io.element.android.libraries.core.extensions.orEmpty
+import io.element.android.libraries.eventformatter.api.TimelineEventFormatter
+import io.element.android.libraries.matrix.api.timeline.item.event.EventTimelineItem
import javax.inject.Inject
-class TimelineItemContentRoomMembershipFactory @Inject constructor() {
+class TimelineItemContentRoomMembershipFactory @Inject constructor(
+ private val timelineEventFormatter: TimelineEventFormatter,
+) {
- fun create(content: RoomMembershipContent): TimelineItemEventContent {
- return TimelineItemUnknownContent
+ fun create(eventTimelineItem: EventTimelineItem): TimelineItemEventContent {
+ val text = timelineEventFormatter.format(eventTimelineItem)
+ return TimelineItemRoomMembershipContent(text.orEmpty().toString())
}
}
diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentStateFactory.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentStateFactory.kt
index 1680006bc1..072b568af9 100644
--- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentStateFactory.kt
+++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentStateFactory.kt
@@ -17,13 +17,18 @@
package io.element.android.features.messages.impl.timeline.factories.event
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemEventContent
-import io.element.android.features.messages.impl.timeline.model.event.TimelineItemUnknownContent
-import io.element.android.libraries.matrix.api.timeline.item.event.StateContent
+import io.element.android.features.messages.impl.timeline.model.event.TimelineItemStateEventContent
+import io.element.android.libraries.core.extensions.orEmpty
+import io.element.android.libraries.eventformatter.api.TimelineEventFormatter
+import io.element.android.libraries.matrix.api.timeline.item.event.EventTimelineItem
import javax.inject.Inject
-class TimelineItemContentStateFactory @Inject constructor() {
+class TimelineItemContentStateFactory @Inject constructor(
+ private val timelineEventFormatter: TimelineEventFormatter,
+) {
- fun create(content: StateContent): TimelineItemEventContent {
- return TimelineItemUnknownContent
+ fun create(eventTimelineItem: EventTimelineItem): TimelineItemEventContent {
+ val text = timelineEventFormatter.format(eventTimelineItem)
+ return TimelineItemStateEventContent(text.orEmpty().toString())
}
}
diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemEventFactory.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemEventFactory.kt
index c21622b50f..a69fb8ddcb 100644
--- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemEventFactory.kt
+++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemEventFactory.kt
@@ -67,7 +67,7 @@ class TimelineItemEventFactory @Inject constructor(
senderId = currentSender,
senderDisplayName = senderDisplayName,
senderAvatar = senderAvatarData,
- content = contentFactory.create(currentTimelineItem.event.content),
+ content = contentFactory.create(currentTimelineItem.event),
isMine = currentTimelineItem.event.isOwn,
groupPosition = groupPosition,
reactionsState = currentTimelineItem.computeReactionsState()
diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/groups/TimelineItemGrouper.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/groups/TimelineItemGrouper.kt
new file mode 100644
index 0000000000..3cd1b8fd4a
--- /dev/null
+++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/groups/TimelineItemGrouper.kt
@@ -0,0 +1,95 @@
+/*
+ * Copyright (c) 2023 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.element.android.features.messages.impl.timeline.groups
+
+import io.element.android.features.messages.impl.timeline.model.TimelineItem
+import io.element.android.features.messages.impl.timeline.model.event.TimelineItemEmoteContent
+import io.element.android.features.messages.impl.timeline.model.event.TimelineItemEncryptedContent
+import io.element.android.features.messages.impl.timeline.model.event.TimelineItemImageContent
+import io.element.android.features.messages.impl.timeline.model.event.TimelineItemNoticeContent
+import io.element.android.features.messages.impl.timeline.model.event.TimelineItemProfileChangeContent
+import io.element.android.features.messages.impl.timeline.model.event.TimelineItemRedactedContent
+import io.element.android.features.messages.impl.timeline.model.event.TimelineItemRoomMembershipContent
+import io.element.android.features.messages.impl.timeline.model.event.TimelineItemStateEventContent
+import io.element.android.features.messages.impl.timeline.model.event.TimelineItemTextContent
+import io.element.android.features.messages.impl.timeline.model.event.TimelineItemUnknownContent
+import io.element.android.libraries.core.bool.orFalse
+import kotlinx.collections.immutable.toImmutableList
+
+import javax.inject.Inject
+
+class TimelineItemGrouper @Inject constructor() {
+ /**
+ * Create a new list of [TimelineItem] by grouping some of them into [TimelineItem.GroupedEvents].
+ */
+ fun group(from: List, expandedGroups: Map): List {
+ val result = mutableListOf()
+ val currentGroup = mutableListOf()
+ from.forEach { timelineItem ->
+ if (timelineItem is TimelineItem.Event && timelineItem.canBeGrouped()) {
+ currentGroup.add(0, timelineItem)
+ } else {
+ // timelineItem cannot be grouped
+ if (currentGroup.isNotEmpty()) {
+ // There is a pending group, create a TimelineItem.GroupedEvents if there is more than 1 Event in the pending group.
+ result.addGroup(currentGroup, expandedGroups)
+ currentGroup.clear()
+ }
+ result.add(timelineItem)
+ }
+ }
+ if (currentGroup.isNotEmpty()) {
+ result.addGroup(currentGroup, expandedGroups)
+ }
+ return result
+ }
+
+ private fun TimelineItem.Event.canBeGrouped(): Boolean {
+ return when (content) {
+ is TimelineItemEncryptedContent,
+ is TimelineItemImageContent,
+ TimelineItemRedactedContent,
+ is TimelineItemEmoteContent,
+ is TimelineItemNoticeContent,
+ is TimelineItemTextContent,
+ TimelineItemUnknownContent -> false
+ is TimelineItemProfileChangeContent,
+ is TimelineItemRoomMembershipContent,
+ is TimelineItemStateEventContent -> true
+ }
+ }
+}
+
+/**
+ * Will add a group if there is more than 1 item, else add the item to the list.
+ */
+private fun MutableList.addGroup(
+ group: MutableList,
+ expandedGroups: Map,
+) {
+ if (group.size == 1) {
+ // Do not create a group with just 1 item, just add the item to the result
+ add(group.first())
+ } else {
+ add(
+ TimelineItem.GroupedEvents(
+ expanded = expandedGroups[group.first().id + "_group"].orFalse(),
+ events = group.toImmutableList()
+ )
+ )
+ }
+}
diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/TimelineItem.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/TimelineItem.kt
index e1b6557f31..fe34cd5b1d 100644
--- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/TimelineItem.kt
+++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/TimelineItem.kt
@@ -22,6 +22,7 @@ import io.element.android.features.messages.impl.timeline.model.virtual.Timeline
import io.element.android.libraries.designsystem.components.avatar.AvatarData
import io.element.android.libraries.matrix.api.core.EventId
import io.element.android.libraries.matrix.api.core.UserId
+import kotlinx.collections.immutable.ImmutableList
@Immutable
sealed interface TimelineItem {
@@ -29,11 +30,13 @@ sealed interface TimelineItem {
fun identifier(): String = when (this) {
is Event -> id
is Virtual -> id
+ is GroupedEvents -> id
}
fun contentType(): String = when (this) {
is Event -> content.type
is Virtual -> model.type
+ is GroupedEvents -> "groupedEvent"
}
@Immutable
@@ -60,4 +63,13 @@ sealed interface TimelineItem {
val safeSenderName: String = senderDisplayName ?: senderId.value
}
+
+ @Immutable
+ data class GroupedEvents(
+ val expanded: Boolean,
+ val events: ImmutableList,
+ ) : TimelineItem {
+ // use first id with a suffix
+ val id = events.first().id + "_group"
+ }
}
diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemEventContentProvider.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemEventContentProvider.kt
index 7e4be1bbd1..433c69088e 100644
--- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemEventContentProvider.kt
+++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemEventContentProvider.kt
@@ -65,3 +65,7 @@ fun aTimelineItemTextContent() = TimelineItemTextContent(
)
fun aTimelineItemUnknownContent() = TimelineItemUnknownContent
+
+fun aTimelineItemStateEventContent() = TimelineItemStateEventContent(
+ body = "A state event",
+)
diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemProfileChangeContent.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemProfileChangeContent.kt
new file mode 100644
index 0000000000..7d56394893
--- /dev/null
+++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemProfileChangeContent.kt
@@ -0,0 +1,23 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.element.android.features.messages.impl.timeline.model.event
+
+data class TimelineItemProfileChangeContent(
+ override val body: String,
+) : TimelineItemStateContent {
+ override val type: String = "TimelineItemProfileChangeContent"
+}
diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemRoomMembershipContent.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemRoomMembershipContent.kt
new file mode 100644
index 0000000000..93607f01e6
--- /dev/null
+++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemRoomMembershipContent.kt
@@ -0,0 +1,23 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.element.android.features.messages.impl.timeline.model.event
+
+data class TimelineItemRoomMembershipContent(
+ override val body: String,
+) : TimelineItemStateContent {
+ override val type: String = "TimelineItemRoomMembershipContent"
+}
diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemStateContent.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemStateContent.kt
new file mode 100644
index 0000000000..b136a602b2
--- /dev/null
+++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemStateContent.kt
@@ -0,0 +1,21 @@
+/*
+ * Copyright (c) 2023 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.element.android.features.messages.impl.timeline.model.event
+
+sealed interface TimelineItemStateContent : TimelineItemEventContent {
+ val body: String
+}
diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemStateEventContent.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemStateEventContent.kt
new file mode 100644
index 0000000000..1c656c9b96
--- /dev/null
+++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemStateEventContent.kt
@@ -0,0 +1,23 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.element.android.features.messages.impl.timeline.model.event
+
+data class TimelineItemStateEventContent(
+ override val body: String,
+) : TimelineItemStateContent {
+ override val type: String = "TimelineItemStateEventContent"
+}
diff --git a/features/messages/impl/src/main/res/values-es/translations.xml b/features/messages/impl/src/main/res/values-es/translations.xml
new file mode 100644
index 0000000000..7cd4b6e764
--- /dev/null
+++ b/features/messages/impl/src/main/res/values-es/translations.xml
@@ -0,0 +1,7 @@
+
+
+
+ - "%1$d cambio en la sala"
+ - "%1$d cambios en la sala"
+
+
\ No newline at end of file
diff --git a/features/messages/impl/src/main/res/values-it/translations.xml b/features/messages/impl/src/main/res/values-it/translations.xml
new file mode 100644
index 0000000000..649a91405b
--- /dev/null
+++ b/features/messages/impl/src/main/res/values-it/translations.xml
@@ -0,0 +1,7 @@
+
+
+
+ - "%1$d modifica alla stanza"
+ - "%1$d modifiche alla stanza"
+
+
\ No newline at end of file
diff --git a/features/messages/impl/src/main/res/values-ro/translations.xml b/features/messages/impl/src/main/res/values-ro/translations.xml
new file mode 100644
index 0000000000..68d83cacfe
--- /dev/null
+++ b/features/messages/impl/src/main/res/values-ro/translations.xml
@@ -0,0 +1,8 @@
+
+
+
+ - "%1$d schimbare a camerii"
+ - "%1$d schimbări ale camerei"
+ - "%1$d schimbări ale camerei"
+
+
\ No newline at end of file
diff --git a/features/messages/impl/src/main/res/values/localazy.xml b/features/messages/impl/src/main/res/values/localazy.xml
index 8c53bca6c4..361e80a4de 100644
--- a/features/messages/impl/src/main/res/values/localazy.xml
+++ b/features/messages/impl/src/main/res/values/localazy.xml
@@ -1,5 +1,9 @@
+
+ - "%1$d room change"
+ - "%1$d room changes"
+
"Camera"
"Take photo"
"Record a video"
diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/MessagesPresenterTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/MessagesPresenterTest.kt
index e6ee61ee52..f01c8581ab 100644
--- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/MessagesPresenterTest.kt
+++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/MessagesPresenterTest.kt
@@ -28,6 +28,7 @@ import io.element.android.features.messages.impl.actionlist.ActionListPresenter
import io.element.android.features.messages.impl.actionlist.model.TimelineItemAction
import io.element.android.features.messages.impl.textcomposer.MessageComposerPresenter
import io.element.android.features.messages.impl.timeline.TimelinePresenter
+import io.element.android.features.messages.impl.timeline.groups.TimelineItemGrouper
import io.element.android.features.networkmonitor.test.FakeNetworkMonitor
import io.element.android.libraries.designsystem.utils.SnackbarDispatcher
import io.element.android.libraries.featureflag.test.FakeFeatureFlagService
@@ -138,6 +139,7 @@ class MessagesPresenterTest {
)
val timelinePresenter = TimelinePresenter(
timelineItemsFactory = aTimelineItemsFactory(),
+ timelineItemGrouper = TimelineItemGrouper(),
room = matrixRoom,
)
val actionListPresenter = ActionListPresenter()
diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/fixtures/timelineItemsFactory.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/fixtures/timelineItemsFactory.kt
index 058406d513..5d355be8d0 100644
--- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/fixtures/timelineItemsFactory.kt
+++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/fixtures/timelineItemsFactory.kt
@@ -31,26 +31,39 @@ import io.element.android.features.messages.impl.timeline.factories.event.Timeli
import io.element.android.features.messages.impl.timeline.factories.virtual.TimelineItemDaySeparatorFactory
import io.element.android.features.messages.impl.timeline.factories.virtual.TimelineItemVirtualFactory
import io.element.android.libraries.dateformatter.test.FakeDaySeparatorFormatter
+import io.element.android.libraries.eventformatter.api.TimelineEventFormatter
+import io.element.android.libraries.matrix.api.timeline.item.event.EventTimelineItem
import io.element.android.tests.testutils.testCoroutineDispatchers
-internal fun aTimelineItemsFactory() = TimelineItemsFactory(
- dispatchers = testCoroutineDispatchers(),
- eventItemFactory = TimelineItemEventFactory(
- TimelineItemContentFactory(
- messageFactory = TimelineItemContentMessageFactory(),
- redactedMessageFactory = TimelineItemContentRedactedFactory(),
- stickerFactory = TimelineItemContentStickerFactory(),
- utdFactory = TimelineItemContentUTDFactory(),
- roomMembershipFactory = TimelineItemContentRoomMembershipFactory(),
- profileChangeFactory = TimelineItemContentProfileChangeFactory(),
- stateFactory = TimelineItemContentStateFactory(),
- failedToParseMessageFactory = TimelineItemContentFailedToParseMessageFactory(),
- failedToParseStateFactory = TimelineItemContentFailedToParseStateFactory()
- )
- ),
- virtualItemFactory = TimelineItemVirtualFactory(
- daySeparatorFactory = TimelineItemDaySeparatorFactory(
- FakeDaySeparatorFormatter()
+internal fun aTimelineItemsFactory(): TimelineItemsFactory {
+ val timelineEventFormatter = aTimelineEventFormatter()
+ return TimelineItemsFactory(
+ dispatchers = testCoroutineDispatchers(),
+ eventItemFactory = TimelineItemEventFactory(
+ TimelineItemContentFactory(
+ messageFactory = TimelineItemContentMessageFactory(),
+ redactedMessageFactory = TimelineItemContentRedactedFactory(),
+ stickerFactory = TimelineItemContentStickerFactory(),
+ utdFactory = TimelineItemContentUTDFactory(),
+ roomMembershipFactory = TimelineItemContentRoomMembershipFactory(timelineEventFormatter),
+ profileChangeFactory = TimelineItemContentProfileChangeFactory(timelineEventFormatter),
+ stateFactory = TimelineItemContentStateFactory(timelineEventFormatter),
+ failedToParseMessageFactory = TimelineItemContentFailedToParseMessageFactory(),
+ failedToParseStateFactory = TimelineItemContentFailedToParseStateFactory()
+ )
),
+ virtualItemFactory = TimelineItemVirtualFactory(
+ daySeparatorFactory = TimelineItemDaySeparatorFactory(
+ FakeDaySeparatorFormatter()
+ ),
+ )
)
-)
+}
+
+internal fun aTimelineEventFormatter(): TimelineEventFormatter {
+ return object : TimelineEventFormatter {
+ override fun format(event: EventTimelineItem): CharSequence {
+ return ""
+ }
+ }
+}
diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/textcomposer/MessageComposerPresenterTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/textcomposer/MessageComposerPresenterTest.kt
index 9639759529..0c0b46cbe2 100644
--- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/textcomposer/MessageComposerPresenterTest.kt
+++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/textcomposer/MessageComposerPresenterTest.kt
@@ -14,6 +14,8 @@
* limitations under the License.
*/
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
package io.element.android.features.messages.textcomposer
import app.cash.molecule.RecompositionClock
@@ -50,6 +52,7 @@ import io.element.android.libraries.mediaupload.test.FakeMediaPreProcessor
import io.element.android.libraries.textcomposer.MessageComposerMode
import io.mockk.mockk
import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/timeline/TimelinePresenterTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/timeline/TimelinePresenterTest.kt
index a88b2251a4..7b13a93a5a 100644
--- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/timeline/TimelinePresenterTest.kt
+++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/timeline/TimelinePresenterTest.kt
@@ -23,8 +23,13 @@ import com.google.common.truth.Truth.assertThat
import io.element.android.features.messages.fixtures.aTimelineItemsFactory
import io.element.android.features.messages.impl.timeline.TimelineEvents
import io.element.android.features.messages.impl.timeline.TimelinePresenter
+import io.element.android.features.messages.impl.timeline.groups.TimelineItemGrouper
+import io.element.android.features.messages.impl.timeline.model.TimelineItem
+import io.element.android.libraries.matrix.api.timeline.MatrixTimelineItem
import io.element.android.libraries.matrix.test.AN_EVENT_ID
import io.element.android.libraries.matrix.test.room.FakeMatrixRoom
+import io.element.android.libraries.matrix.test.room.anEventTimelineItem
+import io.element.android.libraries.matrix.test.timeline.FakeMatrixTimeline
import kotlinx.coroutines.test.runTest
import org.junit.Test
@@ -33,6 +38,7 @@ class TimelinePresenterTest {
fun `present - initial state`() = runTest {
val presenter = TimelinePresenter(
timelineItemsFactory = aTimelineItemsFactory(),
+ timelineItemGrouper = TimelineItemGrouper(),
room = FakeMatrixRoom(),
)
moleculeFlow(RecompositionClock.Immediate) {
@@ -49,6 +55,7 @@ class TimelinePresenterTest {
fun `present - load more`() = runTest {
val presenter = TimelinePresenter(
timelineItemsFactory = aTimelineItemsFactory(),
+ timelineItemGrouper = TimelineItemGrouper(),
room = FakeMatrixRoom(),
)
moleculeFlow(RecompositionClock.Immediate) {
@@ -71,6 +78,7 @@ class TimelinePresenterTest {
fun `present - set highlighted event`() = runTest {
val presenter = TimelinePresenter(
timelineItemsFactory = aTimelineItemsFactory(),
+ timelineItemGrouper = TimelineItemGrouper(),
room = FakeMatrixRoom(),
)
moleculeFlow(RecompositionClock.Immediate) {
@@ -87,4 +95,37 @@ class TimelinePresenterTest {
assertThat(withoutHighlightedState.highlightedEventId).isNull()
}
}
+
+ @Test
+ fun `present - expand and collapse grouped events`() = runTest {
+ val fakeTimeline = FakeMatrixTimeline(
+ initialTimelineItems = listOf(
+ MatrixTimelineItem.Event(anEventTimelineItem() /* This is a groupable event */),
+ MatrixTimelineItem.Event(anEventTimelineItem() /* This is a groupable event */),
+ )
+ )
+ val fakeRoom = FakeMatrixRoom(matrixTimeline = fakeTimeline)
+ val presenter = TimelinePresenter(
+ timelineItemsFactory = aTimelineItemsFactory(),
+ timelineItemGrouper = TimelineItemGrouper(),
+ room = fakeRoom,
+ )
+ moleculeFlow(RecompositionClock.Immediate) {
+ presenter.present()
+ }.test {
+ skipItems(1)
+ fakeTimeline.updateTimelineItems { it }
+ val loadedState = awaitItem()
+ val group1 = loadedState.timelineItems.first() as TimelineItem.GroupedEvents
+ assertThat(group1.expanded).isFalse()
+ loadedState.eventSink.invoke(TimelineEvents.ToggleExpandGroup(group1))
+ val withExpandedGroup = awaitItem()
+ val group2 = withExpandedGroup.timelineItems.first() as TimelineItem.GroupedEvents
+ assertThat(group2.expanded).isTrue()
+ withExpandedGroup.eventSink.invoke(TimelineEvents.ToggleExpandGroup(group2))
+ val withCollapsedGroup = awaitItem()
+ val group3 = withCollapsedGroup.timelineItems.first() as TimelineItem.GroupedEvents
+ assertThat(group3.expanded).isFalse()
+ }
+ }
}
diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/timeline/groups/TimelineItemGrouperTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/timeline/groups/TimelineItemGrouperTest.kt
new file mode 100644
index 0000000000..5829bbad9f
--- /dev/null
+++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/timeline/groups/TimelineItemGrouperTest.kt
@@ -0,0 +1,170 @@
+/*
+ * Copyright (c) 2023 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.element.android.features.messages.timeline.groups
+
+import com.google.common.truth.Truth.assertThat
+import io.element.android.features.messages.fixtures.aMessageEvent
+import io.element.android.features.messages.impl.timeline.groups.TimelineItemGrouper
+import io.element.android.features.messages.impl.timeline.model.AggregatedReaction
+import io.element.android.features.messages.impl.timeline.model.TimelineItem
+import io.element.android.features.messages.impl.timeline.model.TimelineItemReactions
+import io.element.android.features.messages.impl.timeline.model.event.TimelineItemStateEventContent
+import io.element.android.features.messages.impl.timeline.model.virtual.aTimelineItemDaySeparatorModel
+import io.element.android.libraries.designsystem.components.avatar.anAvatarData
+import io.element.android.libraries.matrix.test.AN_EVENT_ID
+import io.element.android.libraries.matrix.test.AN_EVENT_ID_2
+import io.element.android.libraries.matrix.test.A_USER_ID
+import kotlinx.collections.immutable.toImmutableList
+import org.junit.Test
+
+class TimelineItemGrouperTest {
+ private val sut = TimelineItemGrouper()
+
+ private val aGroupableItem = TimelineItem.Event(
+ id = AN_EVENT_ID.value,
+ senderId = A_USER_ID,
+ senderAvatar = anAvatarData(),
+ senderDisplayName = "",
+ content = TimelineItemStateEventContent(body = "a state event"),
+ reactionsState = TimelineItemReactions(emptyList().toImmutableList())
+ )
+ private val aNonGroupableItem = aMessageEvent()
+ private val aNonGroupableItemNoEvent = TimelineItem.Virtual("virtual", aTimelineItemDaySeparatorModel("Today"))
+
+ @Test
+ fun `test empty`() {
+ val result = sut.group(emptyList(), emptyMap())
+ assertThat(result).isEmpty()
+ }
+
+ @Test
+ fun `test non groupables`() {
+ val result = sut.group(
+ listOf(
+ aNonGroupableItem,
+ aNonGroupableItem,
+ ),
+ emptyMap()
+ )
+ assertThat(result).isEqualTo(
+ listOf(
+ aNonGroupableItem,
+ aNonGroupableItem,
+ )
+ )
+ }
+
+ @Test
+ fun `test groupables and ensure reordering`() {
+ val result = sut.group(
+ listOf(
+ aGroupableItem.copy(id = AN_EVENT_ID_2.value),
+ aGroupableItem,
+ ),
+ emptyMap()
+ )
+ assertThat(result).isEqualTo(
+ listOf(
+ TimelineItem.GroupedEvents(
+ expanded = false,
+ events = listOf(
+ aGroupableItem,
+ aGroupableItem.copy(id = AN_EVENT_ID_2.value),
+ ).toImmutableList()
+ ),
+ )
+ )
+ }
+
+ @Test
+ fun `test groupables expanded`() {
+ val result = sut.group(
+ listOf(
+ aGroupableItem,
+ aGroupableItem.copy(id = AN_EVENT_ID_2.value),
+ ),
+ mapOf("${AN_EVENT_ID_2.value}_group" to true)
+ )
+ assertThat(result).isEqualTo(
+ listOf(
+ TimelineItem.GroupedEvents(
+ expanded = true,
+ events = listOf(
+ aGroupableItem.copy(id = AN_EVENT_ID_2.value),
+ aGroupableItem,
+ ).toImmutableList()
+ ),
+ )
+ )
+ }
+
+ @Test
+ fun `test 1 groupable, not group must be created`() {
+ val listsToTest = listOf(
+ listOf(aGroupableItem),
+ listOf(aGroupableItem, aNonGroupableItem),
+ listOf(aGroupableItem, aNonGroupableItemNoEvent),
+ listOf(aNonGroupableItem, aGroupableItem),
+ listOf(aNonGroupableItemNoEvent, aGroupableItem),
+ listOf(aNonGroupableItem, aGroupableItem, aNonGroupableItem),
+ listOf(aNonGroupableItemNoEvent, aGroupableItem, aNonGroupableItemNoEvent),
+ listOf(aGroupableItem, aNonGroupableItem, aGroupableItem),
+ listOf(aGroupableItem, aNonGroupableItemNoEvent, aGroupableItem),
+ listOf(aNonGroupableItem),
+ listOf(aNonGroupableItemNoEvent),
+ )
+ listsToTest.forEach { listToTest ->
+ val result = sut.group(listToTest, emptyMap())
+ assertThat(result).isEqualTo(listToTest)
+ }
+ }
+
+ @Test
+ fun `test 3 blocks`() {
+ val result = sut.group(
+ listOf(
+ aGroupableItem,
+ aGroupableItem,
+ aNonGroupableItem,
+ aGroupableItem,
+ aGroupableItem,
+ aGroupableItem,
+ ),
+ emptyMap()
+ )
+ assertThat(result).isEqualTo(
+ listOf(
+ TimelineItem.GroupedEvents(
+ expanded = false,
+ events = listOf(
+ aGroupableItem,
+ aGroupableItem,
+ ).toImmutableList()
+ ),
+ aNonGroupableItem,
+ TimelineItem.GroupedEvents(
+ expanded = false,
+ events = listOf(
+ aGroupableItem,
+ aGroupableItem,
+ aGroupableItem,
+ ).toImmutableList()
+ )
+ )
+ )
+ }
+}
diff --git a/features/roomdetails/impl/src/main/res/values-de/translations.xml b/features/roomdetails/impl/src/main/res/values-de/translations.xml
index 7581b585f1..4e5b3e9a74 100644
--- a/features/roomdetails/impl/src/main/res/values-de/translations.xml
+++ b/features/roomdetails/impl/src/main/res/values-de/translations.xml
@@ -4,11 +4,13 @@
- "1 Person"
- "%1$d Personen"
+ "Bereits eingeladen"
"Raum teilen"
"Blockieren"
"Nutzer blockieren"
"Blockierung aufheben"
"Nutzer entblockieren"
+ "Personen einladen"
"Raum verlassen"
"Sicherheit"
"Thema"
diff --git a/features/roomlist/impl/build.gradle.kts b/features/roomlist/impl/build.gradle.kts
index 100ab15437..c1ee491e7f 100644
--- a/features/roomlist/impl/build.gradle.kts
+++ b/features/roomlist/impl/build.gradle.kts
@@ -48,6 +48,7 @@ dependencies {
implementation(projects.libraries.testtags)
implementation(projects.libraries.uiStrings)
implementation(projects.libraries.dateformatter.api)
+ implementation(projects.libraries.eventformatter.api)
implementation(projects.features.invitelist.api)
implementation(projects.features.networkmonitor.api)
implementation(projects.features.leaveroom.api)
@@ -63,6 +64,7 @@ dependencies {
testImplementation(libs.test.robolectric)
testImplementation(projects.libraries.matrix.test)
testImplementation(projects.libraries.dateformatter.test)
+ testImplementation(projects.libraries.eventformatter.test)
testImplementation(projects.libraries.permissions.noop)
testImplementation(projects.features.invitelist.test)
testImplementation(projects.features.networkmonitor.test)
diff --git a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/DefaultRoomLastMessageFormatter.kt b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/DefaultRoomLastMessageFormatter.kt
deleted file mode 100644
index e2971c93d7..0000000000
--- a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/DefaultRoomLastMessageFormatter.kt
+++ /dev/null
@@ -1,330 +0,0 @@
-/*
- * Copyright (c) 2023 New Vector Ltd
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package io.element.android.features.roomlist.impl
-
-import android.content.Context
-import androidx.compose.ui.text.AnnotatedString
-import androidx.compose.ui.text.SpanStyle
-import androidx.compose.ui.text.buildAnnotatedString
-import androidx.compose.ui.text.font.FontWeight
-import androidx.compose.ui.text.withStyle
-import com.squareup.anvil.annotations.ContributesBinding
-import io.element.android.libraries.di.ApplicationContext
-import io.element.android.libraries.di.SessionScope
-import io.element.android.libraries.matrix.api.MatrixClient
-import io.element.android.libraries.matrix.api.timeline.item.event.AudioMessageType
-import io.element.android.libraries.matrix.api.timeline.item.event.EmoteMessageType
-import io.element.android.libraries.matrix.api.timeline.item.event.EventTimelineItem
-import io.element.android.libraries.matrix.api.timeline.item.event.FailedToParseMessageLikeContent
-import io.element.android.libraries.matrix.api.timeline.item.event.FailedToParseStateContent
-import io.element.android.libraries.matrix.api.timeline.item.event.FileMessageType
-import io.element.android.libraries.matrix.api.timeline.item.event.ImageMessageType
-import io.element.android.libraries.matrix.api.timeline.item.event.MembershipChange
-import io.element.android.libraries.matrix.api.timeline.item.event.MessageContent
-import io.element.android.libraries.matrix.api.timeline.item.event.MessageType
-import io.element.android.libraries.matrix.api.timeline.item.event.NoticeMessageType
-import io.element.android.libraries.matrix.api.timeline.item.event.OtherState
-import io.element.android.libraries.matrix.api.timeline.item.event.ProfileChangeContent
-import io.element.android.libraries.matrix.api.timeline.item.event.ProfileTimelineDetails
-import io.element.android.libraries.matrix.api.timeline.item.event.RedactedContent
-import io.element.android.libraries.matrix.api.timeline.item.event.RoomMembershipContent
-import io.element.android.libraries.matrix.api.timeline.item.event.StateContent
-import io.element.android.libraries.matrix.api.timeline.item.event.StickerContent
-import io.element.android.libraries.matrix.api.timeline.item.event.TextMessageType
-import io.element.android.libraries.matrix.api.timeline.item.event.UnableToDecryptContent
-import io.element.android.libraries.matrix.api.timeline.item.event.UnknownContent
-import io.element.android.libraries.matrix.api.timeline.item.event.UnknownMessageType
-import io.element.android.libraries.matrix.api.timeline.item.event.VideoMessageType
-import timber.log.Timber
-import javax.inject.Inject
-import io.element.android.libraries.ui.strings.R as StringR
-
-@ContributesBinding(SessionScope::class)
-class DefaultRoomLastMessageFormatter @Inject constructor(
- // TODO replace with StringProvider
- @ApplicationContext private val context: Context,
- private val matrixClient: MatrixClient,
-) : RoomLastMessageFormatter {
-
- override fun processMessageItem(event: EventTimelineItem, isDmRoom: Boolean): CharSequence? {
- val isOutgoing = event.sender == matrixClient.sessionId
- val senderDisplayName = (event.senderProfile as? ProfileTimelineDetails.Ready)?.displayName ?: event.sender.value
- return when (val content = event.content) {
- is MessageContent -> processMessageContents(content, senderDisplayName, isDmRoom)
- RedactedContent -> {
- val message = context.getString(StringR.string.common_message_removed)
- if (!isDmRoom) {
- prefix(message, senderDisplayName)
- } else {
- message
- }
- }
- is StickerContent -> {
- content.body
- }
- is UnableToDecryptContent -> {
- val message = context.getString(StringR.string.common_decryption_error)
- if (!isDmRoom) {
- prefix(message, senderDisplayName)
- } else {
- message
- }
- }
- is RoomMembershipContent -> {
- processRoomMembershipChange(content, senderDisplayName, isOutgoing)
- }
- is ProfileChangeContent -> {
- processProfileChangeContent(content, senderDisplayName, isOutgoing)
- }
- is StateContent -> {
- processRoomStateChange(content, senderDisplayName, isOutgoing)
- }
- is FailedToParseMessageLikeContent, is FailedToParseStateContent, is UnknownContent -> {
- prefixIfNeeded(context.getString(StringR.string.common_unsupported_event), senderDisplayName, isDmRoom)
- }
- }
- }
-
- private fun processMessageContents(messageContent: MessageContent, senderDisplayName: String, isDmRoom: Boolean): CharSequence? {
- val messageType: MessageType = messageContent.type ?: return null
-
- val internalMessage = when (messageType) {
- // Doesn't need a prefix
- is EmoteMessageType -> {
- return "- $senderDisplayName ${messageType.body}"
- }
- is TextMessageType -> {
- messageType.body
- }
- is VideoMessageType -> {
- context.getString(StringR.string.common_video)
- }
- is ImageMessageType -> {
- context.getString(StringR.string.common_image)
- }
- is FileMessageType -> {
- context.getString(StringR.string.common_file)
- }
- is AudioMessageType -> {
- context.getString(StringR.string.common_audio)
- }
- UnknownMessageType -> {
- context.getString(StringR.string.common_unsupported_event)
- }
- is NoticeMessageType -> {
- messageType.body
- }
- }
- return prefixIfNeeded(internalMessage, senderDisplayName, isDmRoom)
- }
-
- private fun processRoomMembershipChange(membershipContent: RoomMembershipContent, senderDisplayName: String, senderIsYou: Boolean): CharSequence? {
- val userId = membershipContent.userId
- val memberIsYou = userId == matrixClient.sessionId
- return when (val change = membershipContent.change) {
- MembershipChange.JOINED -> if (memberIsYou) {
- context.getString(R.string.state_event_room_join_by_you)
- } else {
- context.getString(R.string.state_event_room_join, userId.value)
- }
- MembershipChange.LEFT -> if (memberIsYou) {
- context.getString(R.string.state_event_room_leave_by_you)
- } else {
- context.getString(R.string.state_event_room_leave, userId.value)
- }
- MembershipChange.BANNED, MembershipChange.KICKED_AND_BANNED -> if (senderIsYou) {
- context.getString(R.string.state_event_room_ban_by_you, userId.value)
- } else {
- context.getString(R.string.state_event_room_ban, senderDisplayName, userId.value)
- }
- MembershipChange.UNBANNED -> if (senderIsYou) {
- context.getString(R.string.state_event_room_unban_by_you, userId.value)
- } else {
- context.getString(R.string.state_event_room_unban, senderDisplayName, userId.value)
- }
- MembershipChange.KICKED -> if (senderIsYou) {
- context.getString(R.string.state_event_room_remove_by_you, userId.value)
- } else {
- context.getString(R.string.state_event_room_remove, senderDisplayName, userId.value)
- }
- MembershipChange.INVITED -> if (senderIsYou) {
- context.getString(R.string.state_event_room_invite_by_you, userId.value)
- } else if (memberIsYou) {
- context.getString(R.string.state_event_room_invite_you, senderDisplayName)
- } else {
- context.getString(R.string.state_event_room_invite, senderDisplayName, userId.value)
- }
- MembershipChange.INVITATION_ACCEPTED -> if (memberIsYou) {
- context.getString(R.string.state_event_room_invite_accepted_by_you)
- } else {
- context.getString(R.string.state_event_room_invite_accepted, userId.value)
- }
- MembershipChange.INVITATION_REJECTED -> if (memberIsYou) {
- context.getString(R.string.state_event_room_reject_by_you)
- } else {
- context.getString(R.string.state_event_room_reject, userId.value)
- }
- MembershipChange.INVITATION_REVOKED -> if (senderIsYou) {
- context.getString(R.string.state_event_room_third_party_revoked_invite_by_you, userId.value)
- } else {
- context.getString(R.string.state_event_room_third_party_revoked_invite, senderDisplayName, userId.value)
- }
- MembershipChange.KNOCKED -> if (memberIsYou) {
- context.getString(R.string.state_event_room_knock_by_you)
- } else {
- context.getString(R.string.state_event_room_knock, userId.value)
- }
- MembershipChange.KNOCK_ACCEPTED -> if (senderIsYou) {
- context.getString(R.string.state_event_room_knock_accepted_by_you, userId.value)
- } else {
- context.getString(R.string.state_event_room_knock_accepted, senderDisplayName, userId.value)
- }
- MembershipChange.KNOCK_RETRACTED -> if (memberIsYou) {
- context.getString(R.string.state_event_room_knock_retracted_by_you)
- } else {
- context.getString(R.string.state_event_room_knock_retracted, userId.value)
- }
- MembershipChange.KNOCK_DENIED -> if (senderIsYou) {
- context.getString(R.string.state_event_room_knock_denied_by_you, userId.value)
- } else if (memberIsYou) {
- context.getString(R.string.state_event_room_knock_denied_you, senderDisplayName)
- } else {
- context.getString(R.string.state_event_room_knock_denied, senderDisplayName, userId.value)
- }
- else -> {
- Timber.v("Filtering timeline item for room membership: $membershipContent")
- null
- }
- }
- }
-
- private fun processRoomStateChange(stateContent: StateContent, senderDisplayName: String, senderIsYou: Boolean): CharSequence? {
- return when (val content = stateContent.content) {
- is OtherState.RoomAvatar -> {
- val hasAvatarUrl = content.url != null
- when {
- senderIsYou && hasAvatarUrl -> context.getString(R.string.state_event_room_avatar_changed_by_you)
- senderIsYou && !hasAvatarUrl -> context.getString(R.string.state_event_room_avatar_removed_by_you)
- !senderIsYou && hasAvatarUrl -> context.getString(R.string.state_event_room_avatar_changed, senderDisplayName)
- else -> context.getString(R.string.state_event_room_avatar_removed, senderDisplayName)
- }
- }
- is OtherState.RoomCreate -> {
- if (senderIsYou) {
- context.getString(R.string.state_event_room_created_by_you)
- } else {
- context.getString(R.string.state_event_room_created, senderDisplayName)
- }
- }
- is OtherState.RoomEncryption -> context.getString(StringR.string.common_encryption_enabled)
- is OtherState.RoomName -> {
- val hasRoomName = content.name != null
- when {
- senderIsYou && hasRoomName -> context.getString(R.string.state_event_room_name_changed_by_you, content.name)
- senderIsYou && !hasRoomName -> context.getString(R.string.state_event_room_name_removed_by_you)
- !senderIsYou && hasRoomName -> context.getString(R.string.state_event_room_name_changed, senderDisplayName, content.name)
- else -> context.getString(R.string.state_event_room_name_removed, senderDisplayName)
- }
- }
- is OtherState.RoomThirdPartyInvite -> {
- if (content.displayName == null) {
- Timber.e("RoomThirdPartyInvite undisplayable due to missing name")
- return null
- }
- if (senderIsYou) {
- context.getString(R.string.state_event_room_third_party_invite_by_you, content.displayName)
- } else {
- context.getString(R.string.state_event_room_third_party_invite, senderDisplayName, content.displayName)
- }
- }
- is OtherState.RoomTopic -> {
- val hasRoomTopic = content.topic != null
- when {
- senderIsYou && hasRoomTopic -> context.getString(R.string.state_event_room_topic_changed_by_you, content.topic)
- senderIsYou && !hasRoomTopic -> context.getString(R.string.state_event_room_topic_removed_by_you)
- !senderIsYou && hasRoomTopic -> context.getString(R.string.state_event_room_topic_changed, senderDisplayName, content.topic)
- else -> context.getString(R.string.state_event_room_topic_removed, senderDisplayName)
- }
- }
- else -> {
- Timber.v("Filtering timeline item for room state change: $content")
- null
- }
- }
- }
-
- private fun processProfileChangeContent(
- profileChangeContent: ProfileChangeContent,
- senderDisplayName: String,
- senderIsYou: Boolean
- ): String? = profileChangeContent.run {
- val displayNameChanged = displayName != prevDisplayName
- val avatarChanged = avatarUrl != prevAvatarUrl
- return when {
- avatarChanged && displayNameChanged -> {
- val message = processProfileChangeContent(profileChangeContent.copy(avatarUrl = null, prevAvatarUrl = null), senderDisplayName, senderIsYou)
- val avatarChangedToo = context.getString(R.string.state_event_avatar_changed_too)
- "$message\n$avatarChangedToo"
- }
- displayNameChanged -> {
- if (displayName != null && prevDisplayName != null) {
- if (senderIsYou) {
- context.getString(R.string.state_event_display_name_changed_from_by_you, prevDisplayName, displayName)
- } else {
- context.getString(R.string.state_event_display_name_changed_from, senderDisplayName, prevDisplayName, displayName)
- }
- } else if (displayName != null) {
- if (senderIsYou) {
- context.getString(R.string.state_event_display_name_set_by_you, displayName)
- } else {
- context.getString(R.string.state_event_display_name_set, senderDisplayName, displayName)
- }
- } else {
- if (senderIsYou) {
- context.getString(R.string.state_event_display_name_removed_by_you, prevDisplayName)
- } else {
- context.getString(R.string.state_event_display_name_removed, senderDisplayName, prevDisplayName)
- }
- }
- }
- avatarChanged -> {
- if (senderIsYou) {
- context.getString(R.string.state_event_avatar_url_changed_by_you)
- } else {
- context.getString(R.string.state_event_avatar_url_changed, senderDisplayName)
- }
- }
- else -> null
- }
- }
-
- private fun prefixIfNeeded(message: String, senderDisplayName: String, isDmRoom: Boolean): CharSequence = if (isDmRoom) {
- message
- } else {
- prefix(message, senderDisplayName)
- }
-
- private fun prefix(message: String, senderDisplayName: String): AnnotatedString {
- return buildAnnotatedString {
- withStyle(SpanStyle(fontWeight = FontWeight.Bold)) {
- append(senderDisplayName)
- }
- append(": ")
- append(message)
- }
- }
-}
diff --git a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListPresenter.kt b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListPresenter.kt
index e1f37ae5e6..9448ff4b1b 100644
--- a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListPresenter.kt
+++ b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListPresenter.kt
@@ -39,6 +39,7 @@ import io.element.android.libraries.dateformatter.api.LastMessageTimestampFormat
import io.element.android.libraries.designsystem.components.avatar.AvatarData
import io.element.android.libraries.designsystem.utils.SnackbarDispatcher
import io.element.android.libraries.designsystem.utils.handleSnackbarMessage
+import io.element.android.libraries.eventformatter.api.RoomLastMessageFormatter
import io.element.android.libraries.matrix.api.MatrixClient
import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.matrix.api.core.UserId
@@ -199,7 +200,7 @@ class RoomListPresenter @Inject constructor(
hasUnread = roomSummary.details.unreadNotificationCount > 0,
timestamp = lastMessageTimestampFormatter.format(roomSummary.details.lastMessageTimestamp),
lastMessage = roomSummary.details.lastMessage?.let { message ->
- roomLastMessageFormatter.processMessageItem(message.event, roomSummary.details.isDirect)
+ roomLastMessageFormatter.format(message.event, roomSummary.details.isDirect)
}.orEmpty(),
avatarData = avatarData,
)
diff --git a/features/roomlist/impl/src/main/res/values-de/translations.xml b/features/roomlist/impl/src/main/res/values-de/translations.xml
index 00b1431f00..be0109cbe2 100644
--- a/features/roomlist/impl/src/main/res/values-de/translations.xml
+++ b/features/roomlist/impl/src/main/res/values-de/translations.xml
@@ -1,40 +1,4 @@
"Alle Chats"
- "(Avatar wurde ebenfalls geändert)"
- "%1$s hat seinen Avatar geändert"
- "Du hast deinen Avatar geändert"
- "%1$s hat den Anzeigenamen von %2$s in %3$s geändert"
- "Du hast deinen Anzeigenamen von %1$s in %2$s geändert"
- "%1$s hat den Anzeigenamen entfernt (war %2$s)"
- "Du hast deinen Anzeigenamen entfernt (war %1$s)"
- "%1$s hat den Anzeigenamen auf %2$s gesetzt"
- "Du hast deinen Anzeigenamen auf %1$s gesetzt"
- "%1$s hat den Raum-Avatar geändert"
- "Du hast den Raum-Avatar geändert"
- "%1$s hat den Raum-Avatar entfernt"
- "%1$s hat den Raum erstellt"
- "Du hast den Raum erstellt"
- "%1$s hat %2$s eingeladen"
- "%1$s hat die Einladung angenommen"
- "Du hast die Einladung angenommen"
- "Du hast %1$s eingeladen"
- "%1$s hat dich eingeladen"
- "%1$s ist dem Raum beigetreten"
- "Du bist dem Raum beigetreten"
- "%1$s hat deine Beitrittsanfrage abgelehnt"
- "%1$s hat den Raum verlassen"
- "Du hast den Raum verlassen"
- "%1$s hat den Raumnamen geändert in: %2$s"
- "Sie haben den Raumnamen geändert in: %1$s"
- "%1$s hat den Raumnamen entfernt"
- "Du hast den Raumnamen entfernt"
- "%1$s hat die Einladung abgelehnt"
- "Du hast die Einladung abgelehnt"
- "%1$s hat %2$s entfernt"
- "Du hast %1$s entfernt"
- "%1$s hat das Thema geändert zu: %2$s"
- "Sie haben das Thema geändert zu: %1$s"
- "%1$s hat das Raumthema entfernt"
- "Du hast das Raumthema entfernt"
\ No newline at end of file
diff --git a/features/roomlist/impl/src/main/res/values-es/translations.xml b/features/roomlist/impl/src/main/res/values-es/translations.xml
index 079ffb2e72..7edd6192a1 100644
--- a/features/roomlist/impl/src/main/res/values-es/translations.xml
+++ b/features/roomlist/impl/src/main/res/values-es/translations.xml
@@ -4,58 +4,4 @@
"Todos los chats"
"Parece que estás usando un nuevo dispositivo. Verifica que eres tú para acceder a tus mensajes cifrados."
"Accede a tu historial de mensajes"
- "(el avatar también cambió)"
- "%1$s cambió su avatar"
- "Cambiaste tu avatar"
- "%1$s cambió su nombre de %2$s a %3$s"
- "Cambiaste tu nombre de %1$s a %2$s"
- "%1$s eliminó su nombre (era %2$s)"
- "Eliminaste tu nombre (era %1$s)"
- "%1$s cambió su nombre a %2$s"
- "Cambiaste tu nombre a %1$s"
- "%1$s cambió el avatar de la sala"
- "Cambiaste el avatar de la sala"
- "%1$s eliminó el avatar de la sala"
- "Eliminaste el avatar de la sala"
- "%1$s expulsó permanentemente a %2$s"
- "Expulsaste permanentemente a %1$s"
- "%1$s creó la sala"
- "Tú creaste la sala"
- "%1$s invitó a %2$s"
- "%1$s aceptó la invitación"
- "Aceptaste la invitación"
- "Invitaste a %1$s"
- "%1$s te invitó."
- "%1$s se unió a la sala"
- "Te uniste a la sala"
- "%1$s solicitó unirse"
- "%1$s permitió que %2$s se uniera"
- "%1$s te permitió unirte"
- "Solicitaste unirte"
- "%1$s rechazó la solicitud de %2$s para unirse"
- "Rechazaste la solicitud de %1$s para unirte"
- "%1$s rechazó su solicitud para unirte"
- "%1$s ya no está interesado en unirse"
- "Cancelaste tu solicitud de unirte"
- "%1$s salió de la sala"
- "Saliste de la sala"
- "%1$s cambió el nombre de la sala a: %2$s"
- "Cambiaste el nombre de la sala a: %1$s"
- "%1$s eliminó el nombre de la sala"
- "Eliminaste el nombre de la sala"
- "%1$s rechazó la invitación"
- "Rechazaste la invitación"
- "%1$s echó a %2$s"
- "Echaste a %1$s"
- "%1$s envió una invitación a %2$s para unirse a la sala"
- "Enviaste una invitación a %1$s para unirse a la sala"
- "%1$s revocó la invitación a %2$s para unirse a la sala"
- "Revocaste la invitación de %1$s para unirse a la sala"
- "%1$s cambió el tema a: %2$s"
- "Cambiaste el tema a: %1$s"
- "%1$s eliminó el tema de la sala"
- "Eliminaste el tema de la sala"
- "%1$s readmitió a %2$s"
- "Readmitiste a %1$s"
- "%1$s realizó un cambio desconocido en su membresía"
\ No newline at end of file
diff --git a/features/roomlist/impl/src/main/res/values-it/translations.xml b/features/roomlist/impl/src/main/res/values-it/translations.xml
index 20bf487937..6bfb8baa0c 100644
--- a/features/roomlist/impl/src/main/res/values-it/translations.xml
+++ b/features/roomlist/impl/src/main/res/values-it/translations.xml
@@ -4,58 +4,4 @@
"Tutte le conversazioni"
"Sembra che tu stia utilizzando un nuovo dispositivo. Verifica di essere tu per accedere ai tuoi messaggi crittografati."
"Accedi alla cronologia dei messaggi"
- "(anche l\'avatar è stato cambiato)"
- "%1$s ha cambiato il proprio avatar"
- "Hai cambiato il tuo avatar"
- "%1$s ha cambiato il proprio nome visualizzato da %2$s a %3$s"
- "Hai cambiato il tuo nome visualizzato da %1$s a %2$s"
- "%1$s ha rimosso il proprio nome visualizzato (era %2$s)"
- "Hai rimosso il tuo nome visualizzato (era %1$s)"
- "%1$s ha impostato il proprio nome visualizzato su %2$s"
- "Hai impostato il tuo nome visualizzato su %1$s"
- "%1$s ha cambiato l\'avatar della stanza"
- "Hai cambiato l\'avatar della stanza"
- "%1$s ha rimosso l\'avatar della stanza"
- "Hai rimosso l\'avatar della stanza"
- "%1$s ha rimosso %2$s"
- "Hai rimosso %1$s"
- "%1$s ha creato la stanza"
- "Hai creato la stanza"
- "%1$s ha invitato %2$s"
- "%1$s ha accettato l\'invito"
- "Hai accettato l\'invito"
- "Hai invitato %1$s"
- "%1$s ti ha invitato"
- "%1$s si è unito alla stanza"
- "Ti sei unito alla stanza"
- "%1$s ha chiesto di unirsi"
- "%1$s ha permesso a %2$s di unirsi"
- "%1$s ti ha permesso di unirti"
- "Hai richiesto di unirti"
- "%1$s ha rifiutato la richiesta di unirsi di %2$s"
- "Hai rifiutato la richiesta di unirsi di %1$s"
- "%1$s ha rifiutato la tua richiesta di unirti"
- "%1$s non è più interessato a partecipare"
- "Hai annullato la tua richiesta di unirti"
- "%1$s ha lasciato la stanza"
- "Hai lasciato la stanza"
- "%1$s ha cambiato il nome della stanza in: %2$s"
- "Hai cambiato il nome della stanza in: %1$s"
- "%1$s ha rimosso il nome della stanza"
- "Hai rimosso il nome della stanza"
- "%1$s ha rifiutato l\'invito"
- "Hai rifiutato l\'invito"
- "%1$s ha rimosso %2$s"
- "Hai rimosso %1$s"
- "%1$s ha inviato un invito a %2$s per unirsi alla stanza"
- "Hai inviato un invito a %1$s per unirsi alla stanza"
- "%1$s ha revocato l\'invito di %2$s ad unirsi alla stanza."
- "Hai revocato l\'invito a %1$s a universi alla stanza"
- "%1$s ha cambiato l\'oggetto in: %2$s"
- "Hai cambiato l\'oggetto in: %1$s"
- "%1$s ha rimosso l\'oggetto della stanza"
- "Hai rimosso l\'oggetto della stanza"
- "%1$s ha sbloccato %2$s"
- "Hai sbloccato %1$s"
- "%1$s ha apportato una modifica sconosciuta alla propria iscrizione"
\ No newline at end of file
diff --git a/features/roomlist/impl/src/main/res/values-ro/translations.xml b/features/roomlist/impl/src/main/res/values-ro/translations.xml
index 89760b3497..7401b30b82 100644
--- a/features/roomlist/impl/src/main/res/values-ro/translations.xml
+++ b/features/roomlist/impl/src/main/res/values-ro/translations.xml
@@ -4,58 +4,4 @@
"Toate conversatiile"
"Se pare că folosiți un dispozitiv nou. Verificați-vă identitatea pentru acces la mesajele dumneavoastră criptate."
"Accesați istoricul mesajelor"
- "(s-a schimbat si avatarul)"
- "%1$s și-a schimbat avatarul"
- "V-ați schimbat avatarul"
- "%1$s și-a schimbat numele din %2$s în %3$s"
- "V-ați schimbat numele din %1$s în %2$s"
- "%1$s și-a sters numele (era %2$s)"
- "V-ați sters numele (era %1$s)"
- "%1$s și-a schimbat numele %2$s"
- "V-ați schimbat numele în %1$s"
- "%1$s a schimbat avatarul camerei"
- "Ați schimbat avatarul camerei"
- "%1$s a șters avatarul camerei"
- "Ați șters avatarul camerei"
- "%1$s a adăugat o interdicție pentru %2$s"
- "Ați adăugat o interdicție pentru %1$s"
- "%1$s a creat camera"
- "Ați creat camera"
- "%1$s l-a invitat pe %2$s"
- "%1$s a acceptat invitația"
- "Ați acceptat invitația"
- "L-ați invitat pe %1$s"
- "%1$s v-a invitat"
- "%1$s a intrat în cameră"
- "Ați intrat în cameră"
- "%1$s a solicitat să se alăture camerei"
- "%1$s i-a permis lui %2$s să se alăture camerei"
- "%1$s v-a permis să vă alăturați camerei"
- "Ați solicitat să vă alăturați camerei"
- "%1$s a respins solicitarea de alăturare a lui %2$s"
- "Ați respins solicitarea de alăturare a lui %1$s"
- "%1$s a respins cererea dumneavoastră de alăturare"
- "%1$s nu mai este interesat să se alăture camerei"
- "Ați anulat cererea de alăturare"
- "%1$s a părăsit camera"
- "Ați părăsit camera"
- "%1$s a schimbat numele camerei în: %2$s"
- "Ați schimbat numele camerei în: %1$s"
- "%1$s a sters numele camerei"
- "Ați șters numele camerei"
- "%1$s a respins invitația"
- "Ați respins invitația"
- "%1$s l-a îndepărtat pe %2$s"
- "L-ați îndepărtat pe %1$s"
- "%1$s a trimis o invitație către %2$s pentru a se alătura camerei"
- "Ați trimis o invitație către %1$s pentru a se alătura camerei"
- "%1$s a revocat invitația pentru %2$s de a se alătura camerei"
- "Ați revocat invitația pentru %1$s de a se alătura camerei"
- "%1$s a schimbat subiectul în: %2$s"
- "Ați schimbat subiectul în: %1$s"
- "%1$s a șters subiectul camerei"
- "Ați șters subiectul camerei"
- "%1$s a anulat interdicția pentru %2$s"
- "Ați anulat interdicția pentru %1$s"
- "%1$s a făcut o modificare necunoscută asupra calității sale de membru"
\ No newline at end of file
diff --git a/features/roomlist/impl/src/main/res/values/localazy.xml b/features/roomlist/impl/src/main/res/values/localazy.xml
index 7177e3156d..613e6681ae 100644
--- a/features/roomlist/impl/src/main/res/values/localazy.xml
+++ b/features/roomlist/impl/src/main/res/values/localazy.xml
@@ -4,58 +4,4 @@
"All Chats"
"Looks like you’re using a new device. Verify it’s you to access your encrypted messages."
"Access your message history"
- "(avatar was changed too)"
- "%1$s changed their avatar"
- "You changed your avatar"
- "%1$s changed their display name from %2$s to %3$s"
- "You changed your display name from %1$s to %2$s"
- "%1$s removed their display name (it was %2$s)"
- "You removed your display name (it was %1$s)"
- "%1$s set their display name to %2$s"
- "You set your display name to %1$s"
- "%1$s changed the room avatar"
- "You changed the room avatar"
- "%1$s removed the room avatar"
- "You removed the room avatar"
- "%1$s banned %2$s"
- "You banned %1$s"
- "%1$s created the room"
- "You created the room"
- "%1$s invited %2$s"
- "%1$s accepted the invite"
- "You accepted the invite"
- "You invited %1$s"
- "%1$s invited you"
- "%1$s joined the room"
- "You joined the room"
- "%1$s requested to join"
- "%1$s allowed %2$s to join"
- "%1$s allowed you to join"
- "You requested to join"
- "%1$s rejected %2$s\'s request to join"
- "You rejected %1$s\'s request to join"
- "%1$s rejected your request to join"
- "%1$s is no longer interested in joining"
- "You cancelled your request to join"
- "%1$s left the room"
- "You left the room"
- "%1$s changed the room name to: %2$s"
- "You changed the room name to: %1$s"
- "%1$s removed the room name"
- "You removed the room name"
- "%1$s rejected the invitation"
- "You rejected the invitation"
- "%1$s removed %2$s"
- "You removed %1$s"
- "%1$s sent an invitation to %2$s to join the room"
- "You sent an invitation to %1$s to join the room"
- "%1$s revoked the invitation for %2$s to join the room"
- "You revoked the invitation for %1$s to join the room"
- "%1$s changed the topic to: %2$s"
- "You changed the topic to: %1$s"
- "%1$s removed the room topic"
- "You removed the room topic"
- "%1$s unbanned %2$s"
- "You unbanned %1$s"
- "%1$s made an unknown change to their membership"
\ No newline at end of file
diff --git a/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/RoomListPresenterTests.kt b/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/RoomListPresenterTests.kt
index 72996727dd..fe76d9837b 100644
--- a/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/RoomListPresenterTests.kt
+++ b/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/RoomListPresenterTests.kt
@@ -29,6 +29,7 @@ import io.element.android.libraries.dateformatter.api.LastMessageTimestampFormat
import io.element.android.libraries.dateformatter.test.FakeLastMessageTimestampFormatter
import io.element.android.libraries.designsystem.components.avatar.AvatarData
import io.element.android.libraries.designsystem.utils.SnackbarDispatcher
+import io.element.android.libraries.eventformatter.test.FakeRoomLastMessageFormatter
import io.element.android.libraries.matrix.api.verification.SessionVerifiedStatus
import io.element.android.libraries.matrix.test.AN_AVATAR_URL
import io.element.android.libraries.matrix.test.AN_EXCEPTION
diff --git a/libraries/eventformatter/api/build.gradle.kts b/libraries/eventformatter/api/build.gradle.kts
new file mode 100644
index 0000000000..ec2a56d780
--- /dev/null
+++ b/libraries/eventformatter/api/build.gradle.kts
@@ -0,0 +1,27 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+plugins {
+ id("io.element.android-library")
+}
+
+android {
+ namespace = "io.element.android.libraries.eventformatter.api"
+}
+
+dependencies {
+ implementation(projects.libraries.matrix.api)
+}
diff --git a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomLastMessageFormatter.kt b/libraries/eventformatter/api/src/main/kotlin/io/element/android/libraries/eventformatter/api/RoomLastMessageFormatter.kt
similarity index 84%
rename from features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomLastMessageFormatter.kt
rename to libraries/eventformatter/api/src/main/kotlin/io/element/android/libraries/eventformatter/api/RoomLastMessageFormatter.kt
index bd59d68592..4dd5978bc6 100644
--- a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomLastMessageFormatter.kt
+++ b/libraries/eventformatter/api/src/main/kotlin/io/element/android/libraries/eventformatter/api/RoomLastMessageFormatter.kt
@@ -14,10 +14,10 @@
* limitations under the License.
*/
-package io.element.android.features.roomlist.impl
+package io.element.android.libraries.eventformatter.api
import io.element.android.libraries.matrix.api.timeline.item.event.EventTimelineItem
interface RoomLastMessageFormatter {
- fun processMessageItem(event: EventTimelineItem, isDmRoom: Boolean): CharSequence?
+ fun format(event: EventTimelineItem, isDmRoom: Boolean): CharSequence?
}
diff --git a/libraries/eventformatter/api/src/main/kotlin/io/element/android/libraries/eventformatter/api/TimelineEventFormatter.kt b/libraries/eventformatter/api/src/main/kotlin/io/element/android/libraries/eventformatter/api/TimelineEventFormatter.kt
new file mode 100644
index 0000000000..6a966f1aba
--- /dev/null
+++ b/libraries/eventformatter/api/src/main/kotlin/io/element/android/libraries/eventformatter/api/TimelineEventFormatter.kt
@@ -0,0 +1,23 @@
+/*
+ * Copyright (c) 2023 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.element.android.libraries.eventformatter.api
+
+import io.element.android.libraries.matrix.api.timeline.item.event.EventTimelineItem
+
+interface TimelineEventFormatter {
+ fun format(event: EventTimelineItem): CharSequence?
+}
diff --git a/libraries/eventformatter/impl/build.gradle.kts b/libraries/eventformatter/impl/build.gradle.kts
new file mode 100644
index 0000000000..3bee2df488
--- /dev/null
+++ b/libraries/eventformatter/impl/build.gradle.kts
@@ -0,0 +1,52 @@
+/*
+ * Copyright (c) 2023 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+plugins {
+ id("io.element.android-compose-library")
+ alias(libs.plugins.anvil)
+}
+
+android {
+ namespace = "io.element.android.libraries.eventformatter.impl"
+
+ testOptions {
+ unitTests {
+ isIncludeAndroidResources = true
+ }
+ }
+}
+
+anvil {
+ generateDaggerFactories.set(true)
+}
+
+dependencies {
+ anvil(projects.anvilcodegen)
+ implementation(projects.anvilannotations)
+
+ implementation(projects.libraries.core)
+ implementation(projects.libraries.architecture)
+ implementation(projects.libraries.matrix.api)
+ implementation(projects.libraries.uiStrings)
+ implementation(projects.services.toolbox.api)
+ api(projects.libraries.eventformatter.api)
+
+ testImplementation(projects.services.toolbox.impl)
+ testImplementation(libs.test.junit)
+ testImplementation(libs.test.robolectric)
+ testImplementation(libs.test.truth)
+ testImplementation(projects.libraries.matrix.test)
+}
diff --git a/libraries/eventformatter/impl/src/main/kotlin/io/element/android/libraries/eventformatter/impl/DefaultRoomLastMessageFormatter.kt b/libraries/eventformatter/impl/src/main/kotlin/io/element/android/libraries/eventformatter/impl/DefaultRoomLastMessageFormatter.kt
new file mode 100644
index 0000000000..327c18cb34
--- /dev/null
+++ b/libraries/eventformatter/impl/src/main/kotlin/io/element/android/libraries/eventformatter/impl/DefaultRoomLastMessageFormatter.kt
@@ -0,0 +1,150 @@
+/*
+ * Copyright (c) 2023 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.element.android.libraries.eventformatter.impl
+
+import androidx.compose.ui.text.AnnotatedString
+import androidx.compose.ui.text.SpanStyle
+import androidx.compose.ui.text.buildAnnotatedString
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.text.withStyle
+import com.squareup.anvil.annotations.ContributesBinding
+import io.element.android.libraries.di.SessionScope
+import io.element.android.libraries.eventformatter.api.RoomLastMessageFormatter
+import io.element.android.libraries.eventformatter.impl.mode.RenderingMode
+import io.element.android.libraries.matrix.api.MatrixClient
+import io.element.android.libraries.matrix.api.timeline.item.event.AudioMessageType
+import io.element.android.libraries.matrix.api.timeline.item.event.EmoteMessageType
+import io.element.android.libraries.matrix.api.timeline.item.event.EventTimelineItem
+import io.element.android.libraries.matrix.api.timeline.item.event.FailedToParseMessageLikeContent
+import io.element.android.libraries.matrix.api.timeline.item.event.FailedToParseStateContent
+import io.element.android.libraries.matrix.api.timeline.item.event.FileMessageType
+import io.element.android.libraries.matrix.api.timeline.item.event.ImageMessageType
+import io.element.android.libraries.matrix.api.timeline.item.event.MessageContent
+import io.element.android.libraries.matrix.api.timeline.item.event.MessageType
+import io.element.android.libraries.matrix.api.timeline.item.event.NoticeMessageType
+import io.element.android.libraries.matrix.api.timeline.item.event.ProfileChangeContent
+import io.element.android.libraries.matrix.api.timeline.item.event.ProfileTimelineDetails
+import io.element.android.libraries.matrix.api.timeline.item.event.RedactedContent
+import io.element.android.libraries.matrix.api.timeline.item.event.RoomMembershipContent
+import io.element.android.libraries.matrix.api.timeline.item.event.StateContent
+import io.element.android.libraries.matrix.api.timeline.item.event.StickerContent
+import io.element.android.libraries.matrix.api.timeline.item.event.TextMessageType
+import io.element.android.libraries.matrix.api.timeline.item.event.UnableToDecryptContent
+import io.element.android.libraries.matrix.api.timeline.item.event.UnknownContent
+import io.element.android.libraries.matrix.api.timeline.item.event.UnknownMessageType
+import io.element.android.libraries.matrix.api.timeline.item.event.VideoMessageType
+import io.element.android.services.toolbox.api.strings.StringProvider
+import javax.inject.Inject
+import io.element.android.libraries.ui.strings.R as StringR
+
+@ContributesBinding(SessionScope::class)
+class DefaultRoomLastMessageFormatter @Inject constructor(
+ private val sp: StringProvider,
+ private val matrixClient: MatrixClient,
+ private val roomMembershipContentFormatter: RoomMembershipContentFormatter,
+ private val profileChangeContentFormatter: ProfileChangeContentFormatter,
+ private val stateContentFormatter: StateContentFormatter,
+) : RoomLastMessageFormatter {
+
+ override fun format(event: EventTimelineItem, isDmRoom: Boolean): CharSequence? {
+ val isOutgoing = event.sender == matrixClient.sessionId
+ val senderDisplayName = (event.senderProfile as? ProfileTimelineDetails.Ready)?.displayName ?: event.sender.value
+ return when (val content = event.content) {
+ is MessageContent -> processMessageContents(content, senderDisplayName, isDmRoom)
+ RedactedContent -> {
+ val message = sp.getString(StringR.string.common_message_removed)
+ if (!isDmRoom) {
+ prefix(message, senderDisplayName)
+ } else {
+ message
+ }
+ }
+ is StickerContent -> {
+ content.body
+ }
+ is UnableToDecryptContent -> {
+ val message = sp.getString(StringR.string.common_decryption_error)
+ if (!isDmRoom) {
+ prefix(message, senderDisplayName)
+ } else {
+ message
+ }
+ }
+ is RoomMembershipContent -> {
+ roomMembershipContentFormatter.format(content, senderDisplayName, isOutgoing)
+ }
+ is ProfileChangeContent -> {
+ profileChangeContentFormatter.format(content, senderDisplayName, isOutgoing)
+ }
+ is StateContent -> {
+ stateContentFormatter.format(content, senderDisplayName, isOutgoing, RenderingMode.RoomList)
+ }
+ is FailedToParseMessageLikeContent, is FailedToParseStateContent, is UnknownContent -> {
+ prefixIfNeeded(sp.getString(StringR.string.common_unsupported_event), senderDisplayName, isDmRoom)
+ }
+ }
+ }
+
+ private fun processMessageContents(messageContent: MessageContent, senderDisplayName: String, isDmRoom: Boolean): CharSequence? {
+ val messageType: MessageType = messageContent.type ?: return null
+
+ val internalMessage = when (messageType) {
+ // Doesn't need a prefix
+ is EmoteMessageType -> {
+ return "- $senderDisplayName ${messageType.body}"
+ }
+ is TextMessageType -> {
+ messageType.body
+ }
+ is VideoMessageType -> {
+ sp.getString(StringR.string.common_video)
+ }
+ is ImageMessageType -> {
+ sp.getString(StringR.string.common_image)
+ }
+ is FileMessageType -> {
+ sp.getString(StringR.string.common_file)
+ }
+ is AudioMessageType -> {
+ sp.getString(StringR.string.common_audio)
+ }
+ UnknownMessageType -> {
+ sp.getString(StringR.string.common_unsupported_event)
+ }
+ is NoticeMessageType -> {
+ messageType.body
+ }
+ }
+ return prefixIfNeeded(internalMessage, senderDisplayName, isDmRoom)
+ }
+
+ private fun prefixIfNeeded(message: String, senderDisplayName: String, isDmRoom: Boolean): CharSequence = if (isDmRoom) {
+ message
+ } else {
+ prefix(message, senderDisplayName)
+ }
+
+ private fun prefix(message: String, senderDisplayName: String): AnnotatedString {
+ return buildAnnotatedString {
+ withStyle(SpanStyle(fontWeight = FontWeight.Bold)) {
+ append(senderDisplayName)
+ }
+ append(": ")
+ append(message)
+ }
+ }
+}
diff --git a/libraries/eventformatter/impl/src/main/kotlin/io/element/android/libraries/eventformatter/impl/DefaultTimelineEventFormatter.kt b/libraries/eventformatter/impl/src/main/kotlin/io/element/android/libraries/eventformatter/impl/DefaultTimelineEventFormatter.kt
new file mode 100644
index 0000000000..4bcae94c1e
--- /dev/null
+++ b/libraries/eventformatter/impl/src/main/kotlin/io/element/android/libraries/eventformatter/impl/DefaultTimelineEventFormatter.kt
@@ -0,0 +1,78 @@
+/*
+ * Copyright (c) 2023 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.element.android.libraries.eventformatter.impl
+
+import com.squareup.anvil.annotations.ContributesBinding
+import io.element.android.libraries.core.meta.BuildMeta
+import io.element.android.libraries.di.SessionScope
+import io.element.android.libraries.eventformatter.api.TimelineEventFormatter
+import io.element.android.libraries.eventformatter.impl.mode.RenderingMode
+import io.element.android.libraries.matrix.api.MatrixClient
+import io.element.android.libraries.matrix.api.timeline.item.event.EventTimelineItem
+import io.element.android.libraries.matrix.api.timeline.item.event.FailedToParseMessageLikeContent
+import io.element.android.libraries.matrix.api.timeline.item.event.FailedToParseStateContent
+import io.element.android.libraries.matrix.api.timeline.item.event.MessageContent
+import io.element.android.libraries.matrix.api.timeline.item.event.ProfileChangeContent
+import io.element.android.libraries.matrix.api.timeline.item.event.ProfileTimelineDetails
+import io.element.android.libraries.matrix.api.timeline.item.event.RedactedContent
+import io.element.android.libraries.matrix.api.timeline.item.event.RoomMembershipContent
+import io.element.android.libraries.matrix.api.timeline.item.event.StateContent
+import io.element.android.libraries.matrix.api.timeline.item.event.StickerContent
+import io.element.android.libraries.matrix.api.timeline.item.event.UnableToDecryptContent
+import io.element.android.libraries.matrix.api.timeline.item.event.UnknownContent
+import io.element.android.libraries.ui.strings.R
+import io.element.android.services.toolbox.api.strings.StringProvider
+import javax.inject.Inject
+
+@ContributesBinding(SessionScope::class)
+class DefaultTimelineEventFormatter @Inject constructor(
+ private val sp: StringProvider,
+ private val matrixClient: MatrixClient,
+ private val buildMeta: BuildMeta,
+ private val roomMembershipContentFormatter: RoomMembershipContentFormatter,
+ private val profileChangeContentFormatter: ProfileChangeContentFormatter,
+ private val stateContentFormatter: StateContentFormatter,
+) : TimelineEventFormatter {
+
+ override fun format(event: EventTimelineItem): CharSequence? {
+ val isOutgoing = event.sender == matrixClient.sessionId
+ val senderDisplayName = (event.senderProfile as? ProfileTimelineDetails.Ready)?.displayName ?: event.sender.value
+ return when (val content = event.content) {
+ is RoomMembershipContent -> {
+ roomMembershipContentFormatter.format(content, senderDisplayName, isOutgoing)
+ }
+ is ProfileChangeContent -> {
+ profileChangeContentFormatter.format(content, senderDisplayName, isOutgoing)
+ }
+ is StateContent -> {
+ stateContentFormatter.format(content, senderDisplayName, isOutgoing, RenderingMode.Timeline)
+ }
+ RedactedContent,
+ is StickerContent,
+ is UnableToDecryptContent,
+ is MessageContent,
+ is FailedToParseMessageLikeContent,
+ is FailedToParseStateContent,
+ is UnknownContent -> {
+ if (buildMeta.isDebuggable) {
+ error("You should not use this formatter for this event: $event")
+ }
+ sp.getString(R.string.common_unsupported_event)
+ }
+ }
+ }
+}
diff --git a/libraries/eventformatter/impl/src/main/kotlin/io/element/android/libraries/eventformatter/impl/ProfileChangeContentFormatter.kt b/libraries/eventformatter/impl/src/main/kotlin/io/element/android/libraries/eventformatter/impl/ProfileChangeContentFormatter.kt
new file mode 100644
index 0000000000..aea43298f9
--- /dev/null
+++ b/libraries/eventformatter/impl/src/main/kotlin/io/element/android/libraries/eventformatter/impl/ProfileChangeContentFormatter.kt
@@ -0,0 +1,70 @@
+/*
+ * Copyright (c) 2023 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.element.android.libraries.eventformatter.impl
+
+import io.element.android.libraries.matrix.api.timeline.item.event.ProfileChangeContent
+import io.element.android.services.toolbox.api.strings.StringProvider
+import javax.inject.Inject
+
+class ProfileChangeContentFormatter @Inject constructor(
+ private val sp: StringProvider,
+) {
+ fun format(
+ profileChangeContent: ProfileChangeContent,
+ senderDisplayName: String,
+ senderIsYou: Boolean,
+ ): String? = profileChangeContent.run {
+ val displayNameChanged = displayName != prevDisplayName
+ val avatarChanged = avatarUrl != prevAvatarUrl
+ return when {
+ avatarChanged && displayNameChanged -> {
+ val message = format(profileChangeContent.copy(avatarUrl = null, prevAvatarUrl = null), senderDisplayName, senderIsYou)
+ val avatarChangedToo = sp.getString(R.string.state_event_avatar_changed_too)
+ "$message\n$avatarChangedToo"
+ }
+ displayNameChanged -> {
+ if (displayName != null && prevDisplayName != null) {
+ if (senderIsYou) {
+ sp.getString(R.string.state_event_display_name_changed_from_by_you, prevDisplayName, displayName)
+ } else {
+ sp.getString(R.string.state_event_display_name_changed_from, senderDisplayName, prevDisplayName, displayName)
+ }
+ } else if (displayName != null) {
+ if (senderIsYou) {
+ sp.getString(R.string.state_event_display_name_set_by_you, displayName)
+ } else {
+ sp.getString(R.string.state_event_display_name_set, senderDisplayName, displayName)
+ }
+ } else {
+ if (senderIsYou) {
+ sp.getString(R.string.state_event_display_name_removed_by_you, prevDisplayName)
+ } else {
+ sp.getString(R.string.state_event_display_name_removed, senderDisplayName, prevDisplayName)
+ }
+ }
+ }
+ avatarChanged -> {
+ if (senderIsYou) {
+ sp.getString(R.string.state_event_avatar_url_changed_by_you)
+ } else {
+ sp.getString(R.string.state_event_avatar_url_changed, senderDisplayName)
+ }
+ }
+ else -> null
+ }
+ }
+}
diff --git a/libraries/eventformatter/impl/src/main/kotlin/io/element/android/libraries/eventformatter/impl/RoomMembershipContentFormatter.kt b/libraries/eventformatter/impl/src/main/kotlin/io/element/android/libraries/eventformatter/impl/RoomMembershipContentFormatter.kt
new file mode 100644
index 0000000000..b5402454f3
--- /dev/null
+++ b/libraries/eventformatter/impl/src/main/kotlin/io/element/android/libraries/eventformatter/impl/RoomMembershipContentFormatter.kt
@@ -0,0 +1,113 @@
+/*
+ * Copyright (c) 2023 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.element.android.libraries.eventformatter.impl
+
+import io.element.android.libraries.matrix.api.MatrixClient
+import io.element.android.libraries.matrix.api.timeline.item.event.MembershipChange
+import io.element.android.libraries.matrix.api.timeline.item.event.RoomMembershipContent
+import io.element.android.services.toolbox.api.strings.StringProvider
+import timber.log.Timber
+import javax.inject.Inject
+
+class RoomMembershipContentFormatter @Inject constructor(
+ private val matrixClient: MatrixClient,
+ private val sp: StringProvider,
+) {
+ fun format(
+ membershipContent: RoomMembershipContent,
+ senderDisplayName: String,
+ senderIsYou: Boolean,
+ ): CharSequence? {
+ val userId = membershipContent.userId
+ val memberIsYou = userId == matrixClient.sessionId
+ return when (val change = membershipContent.change) {
+ MembershipChange.JOINED -> if (memberIsYou) {
+ sp.getString(R.string.state_event_room_join_by_you)
+ } else {
+ sp.getString(R.string.state_event_room_join, userId.value)
+ }
+ MembershipChange.LEFT -> if (memberIsYou) {
+ sp.getString(R.string.state_event_room_leave_by_you)
+ } else {
+ sp.getString(R.string.state_event_room_leave, userId.value)
+ }
+ MembershipChange.BANNED, MembershipChange.KICKED_AND_BANNED -> if (senderIsYou) {
+ sp.getString(R.string.state_event_room_ban_by_you, userId.value)
+ } else {
+ sp.getString(R.string.state_event_room_ban, senderDisplayName, userId.value)
+ }
+ MembershipChange.UNBANNED -> if (senderIsYou) {
+ sp.getString(R.string.state_event_room_unban_by_you, userId.value)
+ } else {
+ sp.getString(R.string.state_event_room_unban, senderDisplayName, userId.value)
+ }
+ MembershipChange.KICKED -> if (senderIsYou) {
+ sp.getString(R.string.state_event_room_remove_by_you, userId.value)
+ } else {
+ sp.getString(R.string.state_event_room_remove, senderDisplayName, userId.value)
+ }
+ MembershipChange.INVITED -> if (senderIsYou) {
+ sp.getString(R.string.state_event_room_invite_by_you, userId.value)
+ } else if (memberIsYou) {
+ sp.getString(R.string.state_event_room_invite_you, senderDisplayName)
+ } else {
+ sp.getString(R.string.state_event_room_invite, senderDisplayName, userId.value)
+ }
+ MembershipChange.INVITATION_ACCEPTED -> if (memberIsYou) {
+ sp.getString(R.string.state_event_room_invite_accepted_by_you)
+ } else {
+ sp.getString(R.string.state_event_room_invite_accepted, userId.value)
+ }
+ MembershipChange.INVITATION_REJECTED -> if (memberIsYou) {
+ sp.getString(R.string.state_event_room_reject_by_you)
+ } else {
+ sp.getString(R.string.state_event_room_reject, userId.value)
+ }
+ MembershipChange.INVITATION_REVOKED -> if (senderIsYou) {
+ sp.getString(R.string.state_event_room_third_party_revoked_invite_by_you, userId.value)
+ } else {
+ sp.getString(R.string.state_event_room_third_party_revoked_invite, senderDisplayName, userId.value)
+ }
+ MembershipChange.KNOCKED -> if (memberIsYou) {
+ sp.getString(R.string.state_event_room_knock_by_you)
+ } else {
+ sp.getString(R.string.state_event_room_knock, userId.value)
+ }
+ MembershipChange.KNOCK_ACCEPTED -> if (senderIsYou) {
+ sp.getString(R.string.state_event_room_knock_accepted_by_you, userId.value)
+ } else {
+ sp.getString(R.string.state_event_room_knock_accepted, senderDisplayName, userId.value)
+ }
+ MembershipChange.KNOCK_RETRACTED -> if (memberIsYou) {
+ sp.getString(R.string.state_event_room_knock_retracted_by_you)
+ } else {
+ sp.getString(R.string.state_event_room_knock_retracted, userId.value)
+ }
+ MembershipChange.KNOCK_DENIED -> if (senderIsYou) {
+ sp.getString(R.string.state_event_room_knock_denied_by_you, userId.value)
+ } else if (memberIsYou) {
+ sp.getString(R.string.state_event_room_knock_denied_you, senderDisplayName)
+ } else {
+ sp.getString(R.string.state_event_room_knock_denied, senderDisplayName, userId.value)
+ }
+ else -> {
+ Timber.v("Filtering timeline item for room membership: $membershipContent")
+ null
+ }
+ }
+ }
+}
diff --git a/libraries/eventformatter/impl/src/main/kotlin/io/element/android/libraries/eventformatter/impl/StateContentFormatter.kt b/libraries/eventformatter/impl/src/main/kotlin/io/element/android/libraries/eventformatter/impl/StateContentFormatter.kt
new file mode 100644
index 0000000000..169ec55a9d
--- /dev/null
+++ b/libraries/eventformatter/impl/src/main/kotlin/io/element/android/libraries/eventformatter/impl/StateContentFormatter.kt
@@ -0,0 +1,219 @@
+/*
+ * Copyright (c) 2023 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.element.android.libraries.eventformatter.impl
+
+import io.element.android.libraries.eventformatter.impl.mode.RenderingMode
+import io.element.android.libraries.matrix.api.timeline.item.event.OtherState
+import io.element.android.libraries.matrix.api.timeline.item.event.StateContent
+import io.element.android.services.toolbox.api.strings.StringProvider
+import timber.log.Timber
+import javax.inject.Inject
+
+class StateContentFormatter @Inject constructor(
+ private val sp: StringProvider,
+) {
+ fun format(
+ stateContent: StateContent,
+ senderDisplayName: String,
+ senderIsYou: Boolean,
+ renderingMode: RenderingMode,
+ ): CharSequence? {
+ return when (val content = stateContent.content) {
+ is OtherState.RoomAvatar -> {
+ val hasAvatarUrl = content.url != null
+ when {
+ senderIsYou && hasAvatarUrl -> sp.getString(R.string.state_event_room_avatar_changed_by_you)
+ senderIsYou && !hasAvatarUrl -> sp.getString(R.string.state_event_room_avatar_removed_by_you)
+ !senderIsYou && hasAvatarUrl -> sp.getString(R.string.state_event_room_avatar_changed, senderDisplayName)
+ else -> sp.getString(R.string.state_event_room_avatar_removed, senderDisplayName)
+ }
+ }
+ is OtherState.RoomCreate -> {
+ if (senderIsYou) {
+ sp.getString(R.string.state_event_room_created_by_you)
+ } else {
+ sp.getString(R.string.state_event_room_created, senderDisplayName)
+ }
+ }
+ is OtherState.RoomEncryption -> sp.getString(io.element.android.libraries.ui.strings.R.string.common_encryption_enabled)
+ is OtherState.RoomName -> {
+ val hasRoomName = content.name != null
+ when {
+ senderIsYou && hasRoomName -> sp.getString(R.string.state_event_room_name_changed_by_you, content.name)
+ senderIsYou && !hasRoomName -> sp.getString(R.string.state_event_room_name_removed_by_you)
+ !senderIsYou && hasRoomName -> sp.getString(R.string.state_event_room_name_changed, senderDisplayName, content.name)
+ else -> sp.getString(R.string.state_event_room_name_removed, senderDisplayName)
+ }
+ }
+ is OtherState.RoomThirdPartyInvite -> {
+ if (content.displayName == null) {
+ Timber.e("RoomThirdPartyInvite undisplayable due to missing name")
+ return null
+ }
+ if (senderIsYou) {
+ sp.getString(R.string.state_event_room_third_party_invite_by_you, content.displayName)
+ } else {
+ sp.getString(R.string.state_event_room_third_party_invite, senderDisplayName, content.displayName)
+ }
+ }
+ is OtherState.RoomTopic -> {
+ val hasRoomTopic = content.topic != null
+ when {
+ senderIsYou && hasRoomTopic -> sp.getString(R.string.state_event_room_topic_changed_by_you, content.topic)
+ senderIsYou && !hasRoomTopic -> sp.getString(R.string.state_event_room_topic_removed_by_you)
+ !senderIsYou && hasRoomTopic -> sp.getString(R.string.state_event_room_topic_changed, senderDisplayName, content.topic)
+ else -> sp.getString(R.string.state_event_room_topic_removed, senderDisplayName)
+ }
+ }
+ is OtherState.Custom -> when (renderingMode) {
+ RenderingMode.RoomList -> {
+ Timber.v("Filtering timeline item for room state change: $content")
+ null
+ }
+ RenderingMode.Timeline -> {
+ "Custom event ${content.eventType}"
+ }
+ }
+ OtherState.PolicyRuleRoom -> when (renderingMode) {
+ RenderingMode.RoomList -> {
+ Timber.v("Filtering timeline item for room state change: $content")
+ null
+ }
+ RenderingMode.Timeline -> {
+ "PolicyRuleRoom"
+ }
+ }
+ OtherState.PolicyRuleServer -> when (renderingMode) {
+ RenderingMode.RoomList -> {
+ Timber.v("Filtering timeline item for room state change: $content")
+ null
+ }
+ RenderingMode.Timeline -> {
+ "PolicyRuleServer"
+ }
+ }
+ OtherState.PolicyRuleUser -> when (renderingMode) {
+ RenderingMode.RoomList -> {
+ Timber.v("Filtering timeline item for room state change: $content")
+ null
+ }
+ RenderingMode.Timeline -> {
+ "PolicyRuleUser"
+ }
+ }
+ OtherState.RoomAliases -> when (renderingMode) {
+ RenderingMode.RoomList -> {
+ Timber.v("Filtering timeline item for room state change: $content")
+ null
+ }
+ RenderingMode.Timeline -> {
+ "RoomAliases"
+ }
+ }
+ OtherState.RoomCanonicalAlias -> when (renderingMode) {
+ RenderingMode.RoomList -> {
+ Timber.v("Filtering timeline item for room state change: $content")
+ null
+ }
+ RenderingMode.Timeline -> {
+ "RoomCanonicalAlias"
+ }
+ }
+ OtherState.RoomGuestAccess -> when (renderingMode) {
+ RenderingMode.RoomList -> {
+ Timber.v("Filtering timeline item for room state change: $content")
+ null
+ }
+ RenderingMode.Timeline -> {
+ "RoomGuestAccess"
+ }
+ }
+ OtherState.RoomHistoryVisibility -> when (renderingMode) {
+ RenderingMode.RoomList -> {
+ Timber.v("Filtering timeline item for room state change: $content")
+ null
+ }
+ RenderingMode.Timeline -> {
+ "RoomHistoryVisibility"
+ }
+ }
+ OtherState.RoomJoinRules -> when (renderingMode) {
+ RenderingMode.RoomList -> {
+ Timber.v("Filtering timeline item for room state change: $content")
+ null
+ }
+ RenderingMode.Timeline -> {
+ "RoomJoinRules"
+ }
+ }
+ OtherState.RoomPinnedEvents -> when (renderingMode) {
+ RenderingMode.RoomList -> {
+ Timber.v("Filtering timeline item for room state change: $content")
+ null
+ }
+ RenderingMode.Timeline -> {
+ "RoomPinnedEvents"
+ }
+ }
+ OtherState.RoomPowerLevels -> when (renderingMode) {
+ RenderingMode.RoomList -> {
+ Timber.v("Filtering timeline item for room state change: $content")
+ null
+ }
+ RenderingMode.Timeline -> {
+ "RoomPowerLevels"
+ }
+ }
+ OtherState.RoomServerAcl -> when (renderingMode) {
+ RenderingMode.RoomList -> {
+ Timber.v("Filtering timeline item for room state change: $content")
+ null
+ }
+ RenderingMode.Timeline -> {
+ "RoomServerAcl"
+ }
+ }
+ OtherState.RoomTombstone -> when (renderingMode) {
+ RenderingMode.RoomList -> {
+ Timber.v("Filtering timeline item for room state change: $content")
+ null
+ }
+ RenderingMode.Timeline -> {
+ "RoomTombstone"
+ }
+ }
+ OtherState.SpaceChild -> when (renderingMode) {
+ RenderingMode.RoomList -> {
+ Timber.v("Filtering timeline item for room state change: $content")
+ null
+ }
+ RenderingMode.Timeline -> {
+ "SpaceChild"
+ }
+ }
+ OtherState.SpaceParent -> when (renderingMode) {
+ RenderingMode.RoomList -> {
+ Timber.v("Filtering timeline item for room state change: $content")
+ null
+ }
+ RenderingMode.Timeline -> {
+ "SpaceParent"
+ }
+ }
+ }
+ }
+}
diff --git a/libraries/eventformatter/impl/src/main/kotlin/io/element/android/libraries/eventformatter/impl/mode/RenderingMode.kt b/libraries/eventformatter/impl/src/main/kotlin/io/element/android/libraries/eventformatter/impl/mode/RenderingMode.kt
new file mode 100644
index 0000000000..9f85dd4093
--- /dev/null
+++ b/libraries/eventformatter/impl/src/main/kotlin/io/element/android/libraries/eventformatter/impl/mode/RenderingMode.kt
@@ -0,0 +1,22 @@
+/*
+ * Copyright (c) 2023 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.element.android.libraries.eventformatter.impl.mode
+
+enum class RenderingMode {
+ RoomList,
+ Timeline,
+}
diff --git a/libraries/eventformatter/impl/src/main/res/values-de/translations.xml b/libraries/eventformatter/impl/src/main/res/values-de/translations.xml
new file mode 100644
index 0000000000..298bd3d40b
--- /dev/null
+++ b/libraries/eventformatter/impl/src/main/res/values-de/translations.xml
@@ -0,0 +1,39 @@
+
+
+ "(Avatar wurde ebenfalls geändert)"
+ "%1$s hat seinen Avatar geändert"
+ "Du hast deinen Avatar geändert"
+ "%1$s hat den Anzeigenamen von %2$s in %3$s geändert"
+ "Du hast deinen Anzeigenamen von %1$s in %2$s geändert"
+ "%1$s hat den Anzeigenamen entfernt (war %2$s)"
+ "Du hast deinen Anzeigenamen entfernt (war %1$s)"
+ "%1$s hat den Anzeigenamen auf %2$s gesetzt"
+ "Du hast deinen Anzeigenamen auf %1$s gesetzt"
+ "%1$s hat den Raum-Avatar geändert"
+ "Du hast den Raum-Avatar geändert"
+ "%1$s hat den Raum-Avatar entfernt"
+ "%1$s hat den Raum erstellt"
+ "Du hast den Raum erstellt"
+ "%1$s hat %2$s eingeladen"
+ "%1$s hat die Einladung angenommen"
+ "Du hast die Einladung angenommen"
+ "Du hast %1$s eingeladen"
+ "%1$s hat dich eingeladen"
+ "%1$s ist dem Raum beigetreten"
+ "Du bist dem Raum beigetreten"
+ "%1$s hat deine Beitrittsanfrage abgelehnt"
+ "%1$s hat den Raum verlassen"
+ "Du hast den Raum verlassen"
+ "%1$s hat den Raumnamen geändert in: %2$s"
+ "Sie haben den Raumnamen geändert in: %1$s"
+ "%1$s hat den Raumnamen entfernt"
+ "Du hast den Raumnamen entfernt"
+ "%1$s hat die Einladung abgelehnt"
+ "Du hast die Einladung abgelehnt"
+ "%1$s hat %2$s entfernt"
+ "Du hast %1$s entfernt"
+ "%1$s hat das Thema geändert zu: %2$s"
+ "Sie haben das Thema geändert zu: %1$s"
+ "%1$s hat das Raumthema entfernt"
+ "Du hast das Raumthema entfernt"
+
\ No newline at end of file
diff --git a/libraries/eventformatter/impl/src/main/res/values-es/translations.xml b/libraries/eventformatter/impl/src/main/res/values-es/translations.xml
new file mode 100644
index 0000000000..701f56f41c
--- /dev/null
+++ b/libraries/eventformatter/impl/src/main/res/values-es/translations.xml
@@ -0,0 +1,57 @@
+
+
+ "(el avatar también cambió)"
+ "%1$s cambió su avatar"
+ "Cambiaste tu avatar"
+ "%1$s cambió su nombre de %2$s a %3$s"
+ "Cambiaste tu nombre de %1$s a %2$s"
+ "%1$s eliminó su nombre (era %2$s)"
+ "Eliminaste tu nombre (era %1$s)"
+ "%1$s cambió su nombre a %2$s"
+ "Cambiaste tu nombre a %1$s"
+ "%1$s cambió el avatar de la sala"
+ "Cambiaste el avatar de la sala"
+ "%1$s eliminó el avatar de la sala"
+ "Eliminaste el avatar de la sala"
+ "%1$s expulsó permanentemente a %2$s"
+ "Expulsaste permanentemente a %1$s"
+ "%1$s creó la sala"
+ "Tú creaste la sala"
+ "%1$s invitó a %2$s"
+ "%1$s aceptó la invitación"
+ "Aceptaste la invitación"
+ "Invitaste a %1$s"
+ "%1$s te invitó."
+ "%1$s se unió a la sala"
+ "Te uniste a la sala"
+ "%1$s solicitó unirse"
+ "%1$s permitió que %2$s se uniera"
+ "%1$s te permitió unirte"
+ "Solicitaste unirte"
+ "%1$s rechazó la solicitud de %2$s para unirse"
+ "Rechazaste la solicitud de %1$s para unirte"
+ "%1$s rechazó su solicitud para unirte"
+ "%1$s ya no está interesado en unirse"
+ "Cancelaste tu solicitud de unirte"
+ "%1$s salió de la sala"
+ "Saliste de la sala"
+ "%1$s cambió el nombre de la sala a: %2$s"
+ "Cambiaste el nombre de la sala a: %1$s"
+ "%1$s eliminó el nombre de la sala"
+ "Eliminaste el nombre de la sala"
+ "%1$s rechazó la invitación"
+ "Rechazaste la invitación"
+ "%1$s echó a %2$s"
+ "Echaste a %1$s"
+ "%1$s envió una invitación a %2$s para unirse a la sala"
+ "Enviaste una invitación a %1$s para unirse a la sala"
+ "%1$s revocó la invitación a %2$s para unirse a la sala"
+ "Revocaste la invitación de %1$s para unirse a la sala"
+ "%1$s cambió el tema a: %2$s"
+ "Cambiaste el tema a: %1$s"
+ "%1$s eliminó el tema de la sala"
+ "Eliminaste el tema de la sala"
+ "%1$s readmitió a %2$s"
+ "Readmitiste a %1$s"
+ "%1$s realizó un cambio desconocido en su membresía"
+
\ No newline at end of file
diff --git a/libraries/eventformatter/impl/src/main/res/values-it/translations.xml b/libraries/eventformatter/impl/src/main/res/values-it/translations.xml
new file mode 100644
index 0000000000..0380d802f4
--- /dev/null
+++ b/libraries/eventformatter/impl/src/main/res/values-it/translations.xml
@@ -0,0 +1,57 @@
+
+
+ "(anche l\'avatar è stato cambiato)"
+ "%1$s ha cambiato il proprio avatar"
+ "Hai cambiato il tuo avatar"
+ "%1$s ha cambiato il proprio nome visualizzato da %2$s a %3$s"
+ "Hai cambiato il tuo nome visualizzato da %1$s a %2$s"
+ "%1$s ha rimosso il proprio nome visualizzato (era %2$s)"
+ "Hai rimosso il tuo nome visualizzato (era %1$s)"
+ "%1$s ha impostato il proprio nome visualizzato su %2$s"
+ "Hai impostato il tuo nome visualizzato su %1$s"
+ "%1$s ha cambiato l\'avatar della stanza"
+ "Hai cambiato l\'avatar della stanza"
+ "%1$s ha rimosso l\'avatar della stanza"
+ "Hai rimosso l\'avatar della stanza"
+ "%1$s ha rimosso %2$s"
+ "Hai rimosso %1$s"
+ "%1$s ha creato la stanza"
+ "Hai creato la stanza"
+ "%1$s ha invitato %2$s"
+ "%1$s ha accettato l\'invito"
+ "Hai accettato l\'invito"
+ "Hai invitato %1$s"
+ "%1$s ti ha invitato"
+ "%1$s si è unito alla stanza"
+ "Ti sei unito alla stanza"
+ "%1$s ha chiesto di unirsi"
+ "%1$s ha permesso a %2$s di unirsi"
+ "%1$s ti ha permesso di unirti"
+ "Hai richiesto di unirti"
+ "%1$s ha rifiutato la richiesta di unirsi di %2$s"
+ "Hai rifiutato la richiesta di unirsi di %1$s"
+ "%1$s ha rifiutato la tua richiesta di unirti"
+ "%1$s non è più interessato a partecipare"
+ "Hai annullato la tua richiesta di unirti"
+ "%1$s ha lasciato la stanza"
+ "Hai lasciato la stanza"
+ "%1$s ha cambiato il nome della stanza in: %2$s"
+ "Hai cambiato il nome della stanza in: %1$s"
+ "%1$s ha rimosso il nome della stanza"
+ "Hai rimosso il nome della stanza"
+ "%1$s ha rifiutato l\'invito"
+ "Hai rifiutato l\'invito"
+ "%1$s ha rimosso %2$s"
+ "Hai rimosso %1$s"
+ "%1$s ha inviato un invito a %2$s per unirsi alla stanza"
+ "Hai inviato un invito a %1$s per unirsi alla stanza"
+ "%1$s ha revocato l\'invito di %2$s ad unirsi alla stanza."
+ "Hai revocato l\'invito a %1$s a universi alla stanza"
+ "%1$s ha cambiato l\'oggetto in: %2$s"
+ "Hai cambiato l\'oggetto in: %1$s"
+ "%1$s ha rimosso l\'oggetto della stanza"
+ "Hai rimosso l\'oggetto della stanza"
+ "%1$s ha sbloccato %2$s"
+ "Hai sbloccato %1$s"
+ "%1$s ha apportato una modifica sconosciuta alla propria iscrizione"
+
\ No newline at end of file
diff --git a/libraries/eventformatter/impl/src/main/res/values-ro/translations.xml b/libraries/eventformatter/impl/src/main/res/values-ro/translations.xml
new file mode 100644
index 0000000000..2e3abf93d0
--- /dev/null
+++ b/libraries/eventformatter/impl/src/main/res/values-ro/translations.xml
@@ -0,0 +1,57 @@
+
+
+ "(s-a schimbat si avatarul)"
+ "%1$s și-a schimbat avatarul"
+ "V-ați schimbat avatarul"
+ "%1$s și-a schimbat numele din %2$s în %3$s"
+ "V-ați schimbat numele din %1$s în %2$s"
+ "%1$s și-a sters numele (era %2$s)"
+ "V-ați sters numele (era %1$s)"
+ "%1$s și-a schimbat numele %2$s"
+ "V-ați schimbat numele în %1$s"
+ "%1$s a schimbat avatarul camerei"
+ "Ați schimbat avatarul camerei"
+ "%1$s a șters avatarul camerei"
+ "Ați șters avatarul camerei"
+ "%1$s a adăugat o interdicție pentru %2$s"
+ "Ați adăugat o interdicție pentru %1$s"
+ "%1$s a creat camera"
+ "Ați creat camera"
+ "%1$s l-a invitat pe %2$s"
+ "%1$s a acceptat invitația"
+ "Ați acceptat invitația"
+ "L-ați invitat pe %1$s"
+ "%1$s v-a invitat"
+ "%1$s a intrat în cameră"
+ "Ați intrat în cameră"
+ "%1$s a solicitat să se alăture camerei"
+ "%1$s i-a permis lui %2$s să se alăture camerei"
+ "%1$s v-a permis să vă alăturați camerei"
+ "Ați solicitat să vă alăturați camerei"
+ "%1$s a respins solicitarea de alăturare a lui %2$s"
+ "Ați respins solicitarea de alăturare a lui %1$s"
+ "%1$s a respins cererea dumneavoastră de alăturare"
+ "%1$s nu mai este interesat să se alăture camerei"
+ "Ați anulat cererea de alăturare"
+ "%1$s a părăsit camera"
+ "Ați părăsit camera"
+ "%1$s a schimbat numele camerei în: %2$s"
+ "Ați schimbat numele camerei în: %1$s"
+ "%1$s a sters numele camerei"
+ "Ați șters numele camerei"
+ "%1$s a respins invitația"
+ "Ați respins invitația"
+ "%1$s l-a îndepărtat pe %2$s"
+ "L-ați îndepărtat pe %1$s"
+ "%1$s a trimis o invitație către %2$s pentru a se alătura camerei"
+ "Ați trimis o invitație către %1$s pentru a se alătura camerei"
+ "%1$s a revocat invitația pentru %2$s de a se alătura camerei"
+ "Ați revocat invitația pentru %1$s de a se alătura camerei"
+ "%1$s a schimbat subiectul în: %2$s"
+ "Ați schimbat subiectul în: %1$s"
+ "%1$s a șters subiectul camerei"
+ "Ați șters subiectul camerei"
+ "%1$s a anulat interdicția pentru %2$s"
+ "Ați anulat interdicția pentru %1$s"
+ "%1$s a făcut o modificare necunoscută asupra calității sale de membru"
+
\ No newline at end of file
diff --git a/libraries/eventformatter/impl/src/main/res/values/localazy.xml b/libraries/eventformatter/impl/src/main/res/values/localazy.xml
new file mode 100644
index 0000000000..2fd4217cd4
--- /dev/null
+++ b/libraries/eventformatter/impl/src/main/res/values/localazy.xml
@@ -0,0 +1,57 @@
+
+
+ "(avatar was changed too)"
+ "%1$s changed their avatar"
+ "You changed your avatar"
+ "%1$s changed their display name from %2$s to %3$s"
+ "You changed your display name from %1$s to %2$s"
+ "%1$s removed their display name (it was %2$s)"
+ "You removed your display name (it was %1$s)"
+ "%1$s set their display name to %2$s"
+ "You set your display name to %1$s"
+ "%1$s changed the room avatar"
+ "You changed the room avatar"
+ "%1$s removed the room avatar"
+ "You removed the room avatar"
+ "%1$s banned %2$s"
+ "You banned %1$s"
+ "%1$s created the room"
+ "You created the room"
+ "%1$s invited %2$s"
+ "%1$s accepted the invite"
+ "You accepted the invite"
+ "You invited %1$s"
+ "%1$s invited you"
+ "%1$s joined the room"
+ "You joined the room"
+ "%1$s requested to join"
+ "%1$s allowed %2$s to join"
+ "%1$s allowed you to join"
+ "You requested to join"
+ "%1$s rejected %2$s\'s request to join"
+ "You rejected %1$s\'s request to join"
+ "%1$s rejected your request to join"
+ "%1$s is no longer interested in joining"
+ "You cancelled your request to join"
+ "%1$s left the room"
+ "You left the room"
+ "%1$s changed the room name to: %2$s"
+ "You changed the room name to: %1$s"
+ "%1$s removed the room name"
+ "You removed the room name"
+ "%1$s rejected the invitation"
+ "You rejected the invitation"
+ "%1$s removed %2$s"
+ "You removed %1$s"
+ "%1$s sent an invitation to %2$s to join the room"
+ "You sent an invitation to %1$s to join the room"
+ "%1$s revoked the invitation for %2$s to join the room"
+ "You revoked the invitation for %1$s to join the room"
+ "%1$s changed the topic to: %2$s"
+ "You changed the topic to: %1$s"
+ "%1$s removed the room topic"
+ "You removed the room topic"
+ "%1$s unbanned %2$s"
+ "You unbanned %1$s"
+ "%1$s made an unknown change to their membership"
+
\ No newline at end of file
diff --git a/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/DefaultRoomLastMessageFormatterTests.kt b/libraries/eventformatter/impl/src/test/kotlin/io/element/android/libraries/eventformatter/impl/DefaultRoomLastMessageFormatterTests.kt
similarity index 83%
rename from features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/DefaultRoomLastMessageFormatterTests.kt
rename to libraries/eventformatter/impl/src/test/kotlin/io/element/android/libraries/eventformatter/impl/DefaultRoomLastMessageFormatterTests.kt
index d4c2ca7dd2..6e8eb3d81c 100644
--- a/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/DefaultRoomLastMessageFormatterTests.kt
+++ b/libraries/eventformatter/impl/src/test/kotlin/io/element/android/libraries/eventformatter/impl/DefaultRoomLastMessageFormatterTests.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package io.element.android.features.roomlist.impl
+package io.element.android.libraries.eventformatter.impl
import android.content.Context
import androidx.compose.ui.text.AnnotatedString
@@ -48,6 +48,7 @@ import io.element.android.libraries.matrix.test.A_USER_ID
import io.element.android.libraries.matrix.test.FakeMatrixClient
import io.element.android.libraries.matrix.test.room.aProfileChangeMessageContent
import io.element.android.libraries.matrix.test.room.anEventTimelineItem
+import io.element.android.services.toolbox.impl.strings.AndroidStringProvider
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -66,7 +67,14 @@ class DefaultRoomLastMessageFormatterTests {
fun setup() {
context = RuntimeEnvironment.getApplication() as Context
fakeMatrixClient = FakeMatrixClient()
- formatter = DefaultRoomLastMessageFormatter(context, fakeMatrixClient)
+ val stringProvider = AndroidStringProvider(context.resources)
+ formatter = DefaultRoomLastMessageFormatter(
+ sp = AndroidStringProvider(context.resources),
+ matrixClient = fakeMatrixClient,
+ roomMembershipContentFormatter = RoomMembershipContentFormatter(fakeMatrixClient, stringProvider),
+ profileChangeContentFormatter = ProfileChangeContentFormatter(stringProvider),
+ stateContentFormatter = StateContentFormatter(stringProvider)
+ )
}
@Test
@@ -76,7 +84,7 @@ class DefaultRoomLastMessageFormatterTests {
val senderName = "Someone"
sequenceOf(false, true).forEach { isDm ->
val message = createRoomEvent(false, senderName, RedactedContent)
- val result = formatter.processMessageItem(message, isDm)
+ val result = formatter.format(message, isDm)
if (isDm) {
Truth.assertThat(result).isEqualTo(expected)
} else {
@@ -92,7 +100,7 @@ class DefaultRoomLastMessageFormatterTests {
val body = "body"
val info = ImageInfo(null, null, null, null, null, null, null)
val message = createRoomEvent(false, null, StickerContent(body, info, "url"))
- val result = formatter.processMessageItem(message, false)
+ val result = formatter.format(message, false)
Truth.assertThat(result).isEqualTo(body)
}
@@ -103,7 +111,7 @@ class DefaultRoomLastMessageFormatterTests {
val senderName = "Someone"
sequenceOf(false, true).forEach { isDm ->
val message = createRoomEvent(false, senderName, UnableToDecryptContent(UnableToDecryptContent.Data.Unknown))
- val result = formatter.processMessageItem(message, isDm)
+ val result = formatter.format(message, isDm)
if (isDm) {
Truth.assertThat(result).isEqualTo(expected)
} else {
@@ -125,7 +133,7 @@ class DefaultRoomLastMessageFormatterTests {
UnknownContent,
).forEach { type ->
val message = createRoomEvent(false, senderName, type)
- val result = formatter.processMessageItem(message, isDm)
+ val result = formatter.format(message, isDm)
if (isDm) {
Truth.assertWithMessage("$type was not properly handled").that(result).isEqualTo(expected)
} else {
@@ -145,6 +153,7 @@ class DefaultRoomLastMessageFormatterTests {
fun createMessageContent(type: MessageType): MessageContent {
return MessageContent(body, null, false, type)
}
+
val sharedContentMessagesTypes = arrayOf(
TextMessageType(body, null),
VideoMessageType(body, "url", null),
@@ -163,7 +172,7 @@ class DefaultRoomLastMessageFormatterTests {
sharedContentMessagesTypes.forEach { type ->
val content = createMessageContent(type)
val message = createRoomEvent(sentByYou = false, senderDisplayName = "Someone", content = content)
- val result = formatter.processMessageItem(message, isDmRoom = isDm)
+ val result = formatter.format(message, isDmRoom = isDm)
if (isDm) {
resultsInDm.add(type to result)
} else {
@@ -171,7 +180,7 @@ class DefaultRoomLastMessageFormatterTests {
}
}
val unknownMessage = createRoomEvent(sentByYou = false, senderDisplayName = "Someone", content = createMessageContent(UnknownMessageType))
- val result = UnknownMessageType to formatter.processMessageItem(unknownMessage, isDmRoom = isDm)
+ val result = UnknownMessageType to formatter.format(unknownMessage, isDmRoom = isDm)
if (isDm) {
resultsInDm.add(result)
} else {
@@ -235,11 +244,11 @@ class DefaultRoomLastMessageFormatterTests {
val someoneContent = RoomMembershipContent(UserId("@someone_else:domain"), MembershipChange.JOINED)
val youJoinedRoomEvent = createRoomEvent(sentByYou = true, senderDisplayName = null, content = youContent)
- val youJoinedRoom = formatter.processMessageItem(youJoinedRoomEvent, false)
+ val youJoinedRoom = formatter.format(youJoinedRoomEvent, false)
Truth.assertThat(youJoinedRoom).isEqualTo("You joined the room")
val someoneJoinedRoomEvent = createRoomEvent(sentByYou = false, senderDisplayName = otherName, content = someoneContent)
- val someoneJoinedRoom = formatter.processMessageItem(someoneJoinedRoomEvent, false)
+ val someoneJoinedRoom = formatter.format(someoneJoinedRoomEvent, false)
Truth.assertThat(someoneJoinedRoom).isEqualTo("${someoneContent.userId} joined the room")
}
@@ -251,11 +260,11 @@ class DefaultRoomLastMessageFormatterTests {
val someoneContent = RoomMembershipContent(UserId("@someone_else:domain"), MembershipChange.LEFT)
val youLeftRoomEvent = createRoomEvent(sentByYou = true, senderDisplayName = null, content = youContent)
- val youLeftRoom = formatter.processMessageItem(youLeftRoomEvent, false)
+ val youLeftRoom = formatter.format(youLeftRoomEvent, false)
Truth.assertThat(youLeftRoom).isEqualTo("You left the room")
val someoneLeftRoomEvent = createRoomEvent(sentByYou = false, senderDisplayName = otherName, content = someoneContent)
- val someoneLeftRoom = formatter.processMessageItem(someoneLeftRoomEvent, false)
+ val someoneLeftRoom = formatter.format(someoneLeftRoomEvent, false)
Truth.assertThat(someoneLeftRoom).isEqualTo("${someoneContent.userId} left the room")
}
@@ -269,19 +278,19 @@ class DefaultRoomLastMessageFormatterTests {
val someoneKickedContent = RoomMembershipContent(UserId("@someone_else:domain"), MembershipChange.KICKED_AND_BANNED)
val youBannedEvent = createRoomEvent(sentByYou = true, senderDisplayName = null, content = youContent)
- val youBanned = formatter.processMessageItem(youBannedEvent, false)
+ val youBanned = formatter.format(youBannedEvent, false)
Truth.assertThat(youBanned).isEqualTo("You banned ${youContent.userId}")
val youKickBannedEvent = createRoomEvent(sentByYou = true, senderDisplayName = null, content = youKickedContent)
- val youKickedBanned = formatter.processMessageItem(youKickBannedEvent, false)
+ val youKickedBanned = formatter.format(youKickBannedEvent, false)
Truth.assertThat(youKickedBanned).isEqualTo("You banned ${youContent.userId}")
val someoneBannedEvent = createRoomEvent(sentByYou = false, senderDisplayName = otherName, content = someoneContent)
- val someoneBanned = formatter.processMessageItem(someoneBannedEvent, false)
+ val someoneBanned = formatter.format(someoneBannedEvent, false)
Truth.assertThat(someoneBanned).isEqualTo("$otherName banned ${someoneContent.userId}")
val someoneKickBannedEvent = createRoomEvent(sentByYou = false, senderDisplayName = otherName, content = someoneKickedContent)
- val someoneKickBanned = formatter.processMessageItem(someoneKickBannedEvent, false)
+ val someoneKickBanned = formatter.format(someoneKickBannedEvent, false)
Truth.assertThat(someoneKickBanned).isEqualTo("$otherName banned ${someoneContent.userId}")
}
@@ -293,11 +302,11 @@ class DefaultRoomLastMessageFormatterTests {
val someoneContent = RoomMembershipContent(UserId("@someone_else:domain"), MembershipChange.UNBANNED)
val youUnbannedEvent = createRoomEvent(sentByYou = true, senderDisplayName = null, content = youContent)
- val youUnbanned = formatter.processMessageItem(youUnbannedEvent, false)
+ val youUnbanned = formatter.format(youUnbannedEvent, false)
Truth.assertThat(youUnbanned).isEqualTo("You unbanned ${youContent.userId}")
val someoneUnbannedEvent = createRoomEvent(sentByYou = false, senderDisplayName = otherName, content = someoneContent)
- val someoneUnbanned = formatter.processMessageItem(someoneUnbannedEvent, false)
+ val someoneUnbanned = formatter.format(someoneUnbannedEvent, false)
Truth.assertThat(someoneUnbanned).isEqualTo("$otherName unbanned ${someoneContent.userId}")
}
@@ -309,11 +318,11 @@ class DefaultRoomLastMessageFormatterTests {
val someoneContent = RoomMembershipContent(UserId("@someone_else:domain"), MembershipChange.KICKED)
val youKickedEvent = createRoomEvent(sentByYou = true, senderDisplayName = null, content = youContent)
- val youKicked = formatter.processMessageItem(youKickedEvent, false)
+ val youKicked = formatter.format(youKickedEvent, false)
Truth.assertThat(youKicked).isEqualTo("You removed ${youContent.userId}")
val someoneKickedEvent = createRoomEvent(sentByYou = false, senderDisplayName = otherName, content = someoneContent)
- val someoneKicked = formatter.processMessageItem(someoneKickedEvent, false)
+ val someoneKicked = formatter.format(someoneKickedEvent, false)
Truth.assertThat(someoneKicked).isEqualTo("$otherName removed ${someoneContent.userId}")
}
@@ -325,15 +334,15 @@ class DefaultRoomLastMessageFormatterTests {
val someoneContent = RoomMembershipContent(UserId("@someone_else:domain"), MembershipChange.INVITED)
val youWereInvitedEvent = createRoomEvent(sentByYou = false, senderDisplayName = otherName, content = youContent)
- val youWereInvited = formatter.processMessageItem(youWereInvitedEvent, false)
+ val youWereInvited = formatter.format(youWereInvitedEvent, false)
Truth.assertThat(youWereInvited).isEqualTo("$otherName invited you")
val youInvitedEvent = createRoomEvent(sentByYou = true, senderDisplayName = null, content = someoneContent)
- val youInvited = formatter.processMessageItem(youInvitedEvent, false)
+ val youInvited = formatter.format(youInvitedEvent, false)
Truth.assertThat(youInvited).isEqualTo("You invited ${someoneContent.userId}")
val someoneInvitedEvent = createRoomEvent(sentByYou = false, senderDisplayName = otherName, content = someoneContent)
- val someoneInvited = formatter.processMessageItem(someoneInvitedEvent, false)
+ val someoneInvited = formatter.format(someoneInvitedEvent, false)
Truth.assertThat(someoneInvited).isEqualTo("$otherName invited ${someoneContent.userId}")
}
@@ -345,11 +354,11 @@ class DefaultRoomLastMessageFormatterTests {
val someoneContent = RoomMembershipContent(UserId("@someone_else:domain"), MembershipChange.INVITATION_ACCEPTED)
val youAcceptedInviteEvent = createRoomEvent(sentByYou = true, senderDisplayName = null, content = youContent)
- val youAcceptedInvite = formatter.processMessageItem(youAcceptedInviteEvent, false)
+ val youAcceptedInvite = formatter.format(youAcceptedInviteEvent, false)
Truth.assertThat(youAcceptedInvite).isEqualTo("You accepted the invite")
val someoneAcceptedInviteEvent = createRoomEvent(sentByYou = false, senderDisplayName = otherName, content = someoneContent)
- val someoneAcceptedInvite = formatter.processMessageItem(someoneAcceptedInviteEvent, false)
+ val someoneAcceptedInvite = formatter.format(someoneAcceptedInviteEvent, false)
Truth.assertThat(someoneAcceptedInvite).isEqualTo("${someoneContent.userId} accepted the invite")
}
@@ -361,11 +370,11 @@ class DefaultRoomLastMessageFormatterTests {
val someoneContent = RoomMembershipContent(UserId("@someone_else:domain"), MembershipChange.INVITATION_REJECTED)
val youRejectedInviteEvent = createRoomEvent(sentByYou = true, senderDisplayName = null, content = youContent)
- val youRejectedInvite = formatter.processMessageItem(youRejectedInviteEvent, false)
+ val youRejectedInvite = formatter.format(youRejectedInviteEvent, false)
Truth.assertThat(youRejectedInvite).isEqualTo("You rejected the invitation")
val someoneRejectedInviteEvent = createRoomEvent(sentByYou = false, senderDisplayName = otherName, content = someoneContent)
- val someoneRejectedInvite = formatter.processMessageItem(someoneRejectedInviteEvent, false)
+ val someoneRejectedInvite = formatter.format(someoneRejectedInviteEvent, false)
Truth.assertThat(someoneRejectedInvite).isEqualTo("${someoneContent.userId} rejected the invitation")
}
@@ -376,11 +385,11 @@ class DefaultRoomLastMessageFormatterTests {
val someoneContent = RoomMembershipContent(UserId("@someone_else:domain"), MembershipChange.INVITATION_REVOKED)
val youRevokedInviteEvent = createRoomEvent(sentByYou = true, senderDisplayName = null, content = someoneContent)
- val youRevokedInvite = formatter.processMessageItem(youRevokedInviteEvent, false)
+ val youRevokedInvite = formatter.format(youRevokedInviteEvent, false)
Truth.assertThat(youRevokedInvite).isEqualTo("You revoked the invitation for ${someoneContent.userId} to join the room")
val someoneRevokedInviteEvent = createRoomEvent(sentByYou = false, senderDisplayName = otherName, content = someoneContent)
- val someoneRevokedInvite = formatter.processMessageItem(someoneRevokedInviteEvent, false)
+ val someoneRevokedInvite = formatter.format(someoneRevokedInviteEvent, false)
Truth.assertThat(someoneRevokedInvite).isEqualTo("$otherName revoked the invitation for ${someoneContent.userId} to join the room")
}
@@ -392,11 +401,11 @@ class DefaultRoomLastMessageFormatterTests {
val someoneContent = RoomMembershipContent(UserId("@someone_else:domain"), MembershipChange.KNOCKED)
val youKnockedEvent = createRoomEvent(sentByYou = true, senderDisplayName = null, content = youContent)
- val youKnocked = formatter.processMessageItem(youKnockedEvent, false)
+ val youKnocked = formatter.format(youKnockedEvent, false)
Truth.assertThat(youKnocked).isEqualTo("You requested to join")
val someoneKnockedEvent = createRoomEvent(sentByYou = false, senderDisplayName = otherName, content = someoneContent)
- val someoneKnocked = formatter.processMessageItem(someoneKnockedEvent, false)
+ val someoneKnocked = formatter.format(someoneKnockedEvent, false)
Truth.assertThat(someoneKnocked).isEqualTo("${someoneContent.userId} requested to join")
}
@@ -407,11 +416,11 @@ class DefaultRoomLastMessageFormatterTests {
val someoneContent = RoomMembershipContent(UserId("@someone_else:domain"), MembershipChange.KNOCK_ACCEPTED)
val youAcceptedKnockEvent = createRoomEvent(sentByYou = true, senderDisplayName = null, content = someoneContent)
- val youAcceptedKnock = formatter.processMessageItem(youAcceptedKnockEvent, false)
+ val youAcceptedKnock = formatter.format(youAcceptedKnockEvent, false)
Truth.assertThat(youAcceptedKnock).isEqualTo("${someoneContent.userId} allowed you to join")
val someoneAcceptedKnockEvent = createRoomEvent(sentByYou = false, senderDisplayName = otherName, content = someoneContent)
- val someoneAcceptedKnock = formatter.processMessageItem(someoneAcceptedKnockEvent, false)
+ val someoneAcceptedKnock = formatter.format(someoneAcceptedKnockEvent, false)
Truth.assertThat(someoneAcceptedKnock).isEqualTo("$otherName allowed ${someoneContent.userId} to join")
}
@@ -423,11 +432,11 @@ class DefaultRoomLastMessageFormatterTests {
val someoneContent = RoomMembershipContent(UserId("@someone_else:domain"), MembershipChange.KNOCK_RETRACTED)
val youRetractedKnockEvent = createRoomEvent(sentByYou = true, senderDisplayName = null, content = youContent)
- val youRetractedKnock = formatter.processMessageItem(youRetractedKnockEvent, false)
+ val youRetractedKnock = formatter.format(youRetractedKnockEvent, false)
Truth.assertThat(youRetractedKnock).isEqualTo("You cancelled your request to join")
val someoneRetractedKnockEvent = createRoomEvent(sentByYou = false, senderDisplayName = otherName, content = someoneContent)
- val someoneRetractedKnock = formatter.processMessageItem(someoneRetractedKnockEvent, false)
+ val someoneRetractedKnock = formatter.format(someoneRetractedKnockEvent, false)
Truth.assertThat(someoneRetractedKnock).isEqualTo("${someoneContent.userId} is no longer interested in joining")
}
@@ -439,15 +448,15 @@ class DefaultRoomLastMessageFormatterTests {
val someoneContent = RoomMembershipContent(UserId("@someone_else:domain"), MembershipChange.KNOCK_DENIED)
val youDeniedKnockEvent = createRoomEvent(sentByYou = true, senderDisplayName = null, content = someoneContent)
- val youDeniedKnock = formatter.processMessageItem(youDeniedKnockEvent, false)
+ val youDeniedKnock = formatter.format(youDeniedKnockEvent, false)
Truth.assertThat(youDeniedKnock).isEqualTo("You rejected ${someoneContent.userId}'s request to join")
val someoneDeniedKnockEvent = createRoomEvent(sentByYou = false, senderDisplayName = otherName, content = someoneContent)
- val someoneDeniedKnock = formatter.processMessageItem(someoneDeniedKnockEvent, false)
+ val someoneDeniedKnock = formatter.format(someoneDeniedKnockEvent, false)
Truth.assertThat(someoneDeniedKnock).isEqualTo("$otherName rejected ${someoneContent.userId}'s request to join")
val someoneDeniedYourKnockEvent = createRoomEvent(sentByYou = false, senderDisplayName = otherName, content = youContent)
- val someoneDeniedYourKnock = formatter.processMessageItem(someoneDeniedYourKnockEvent, false)
+ val someoneDeniedYourKnock = formatter.format(someoneDeniedYourKnockEvent, false)
Truth.assertThat(someoneDeniedYourKnock).isEqualTo("$otherName rejected your request to join")
}
@@ -459,7 +468,7 @@ class DefaultRoomLastMessageFormatterTests {
val results = otherChanges.map { change ->
val content = RoomMembershipContent(A_USER_ID, change)
val event = createRoomEvent(sentByYou = false, senderDisplayName = "Someone", content = content)
- val result = formatter.processMessageItem(event, false)
+ val result = formatter.format(event, false)
change to result
}
val expected = otherChanges.map { it to null }
@@ -478,19 +487,19 @@ class DefaultRoomLastMessageFormatterTests {
val removedContent = StateContent("", OtherState.RoomAvatar(null))
val youChangedRoomAvatarEvent = createRoomEvent(sentByYou = true, senderDisplayName = null, content = changedContent)
- val youChangedRoomAvatar = formatter.processMessageItem(youChangedRoomAvatarEvent, false)
+ val youChangedRoomAvatar = formatter.format(youChangedRoomAvatarEvent, false)
Truth.assertThat(youChangedRoomAvatar).isEqualTo("You changed the room avatar")
val someoneChangedRoomAvatarEvent = createRoomEvent(sentByYou = false, senderDisplayName = otherName, content = changedContent)
- val someoneChangedRoomAvatar = formatter.processMessageItem(someoneChangedRoomAvatarEvent, false)
+ val someoneChangedRoomAvatar = formatter.format(someoneChangedRoomAvatarEvent, false)
Truth.assertThat(someoneChangedRoomAvatar).isEqualTo("$otherName changed the room avatar")
val youRemovedRoomAvatarEvent = createRoomEvent(sentByYou = true, senderDisplayName = null, content = removedContent)
- val youRemovedRoomAvatar = formatter.processMessageItem(youRemovedRoomAvatarEvent, false)
+ val youRemovedRoomAvatar = formatter.format(youRemovedRoomAvatarEvent, false)
Truth.assertThat(youRemovedRoomAvatar).isEqualTo("You removed the room avatar")
val someoneRemovedRoomAvatarEvent = createRoomEvent(sentByYou = false, senderDisplayName = otherName, content = removedContent)
- val someoneRemovedRoomAvatar = formatter.processMessageItem(someoneRemovedRoomAvatarEvent, false)
+ val someoneRemovedRoomAvatar = formatter.format(someoneRemovedRoomAvatarEvent, false)
Truth.assertThat(someoneRemovedRoomAvatar).isEqualTo("$otherName removed the room avatar")
}
@@ -501,11 +510,11 @@ class DefaultRoomLastMessageFormatterTests {
val content = StateContent("", OtherState.RoomCreate)
val youCreatedRoomMessage = createRoomEvent(sentByYou = true, senderDisplayName = null, content = content)
- val youCreatedRoom = formatter.processMessageItem(youCreatedRoomMessage, false)
+ val youCreatedRoom = formatter.format(youCreatedRoomMessage, false)
Truth.assertThat(youCreatedRoom).isEqualTo("You created the room")
val someoneCreatedRoomEvent = createRoomEvent(sentByYou = false, senderDisplayName = otherName, content = content)
- val someoneCreatedRoom = formatter.processMessageItem(someoneCreatedRoomEvent, false)
+ val someoneCreatedRoom = formatter.format(someoneCreatedRoomEvent, false)
Truth.assertThat(someoneCreatedRoom).isEqualTo("$otherName created the room")
}
@@ -516,11 +525,11 @@ class DefaultRoomLastMessageFormatterTests {
val content = StateContent("", OtherState.RoomEncryption)
val youCreatedRoomMessage = createRoomEvent(sentByYou = true, senderDisplayName = null, content = content)
- val youCreatedRoom = formatter.processMessageItem(youCreatedRoomMessage, false)
+ val youCreatedRoom = formatter.format(youCreatedRoomMessage, false)
Truth.assertThat(youCreatedRoom).isEqualTo("Encryption enabled")
val someoneCreatedRoomEvent = createRoomEvent(sentByYou = false, senderDisplayName = otherName, content = content)
- val someoneCreatedRoom = formatter.processMessageItem(someoneCreatedRoomEvent, false)
+ val someoneCreatedRoom = formatter.format(someoneCreatedRoomEvent, false)
Truth.assertThat(someoneCreatedRoom).isEqualTo("Encryption enabled")
}
@@ -533,19 +542,19 @@ class DefaultRoomLastMessageFormatterTests {
val removedContent = StateContent("", OtherState.RoomName(null))
val youChangedRoomNameEvent = createRoomEvent(sentByYou = true, senderDisplayName = null, content = changedContent)
- val youChangedRoomName = formatter.processMessageItem(youChangedRoomNameEvent, false)
+ val youChangedRoomName = formatter.format(youChangedRoomNameEvent, false)
Truth.assertThat(youChangedRoomName).isEqualTo("You changed the room name to: $newName")
val someoneChangedRoomNameEvent = createRoomEvent(sentByYou = false, senderDisplayName = otherName, content = changedContent)
- val someoneChangedRoomName = formatter.processMessageItem(someoneChangedRoomNameEvent, false)
+ val someoneChangedRoomName = formatter.format(someoneChangedRoomNameEvent, false)
Truth.assertThat(someoneChangedRoomName).isEqualTo("$otherName changed the room name to: $newName")
val youRemovedRoomNameEvent = createRoomEvent(sentByYou = true, senderDisplayName = null, content = removedContent)
- val youRemovedRoomName = formatter.processMessageItem(youRemovedRoomNameEvent, false)
+ val youRemovedRoomName = formatter.format(youRemovedRoomNameEvent, false)
Truth.assertThat(youRemovedRoomName).isEqualTo("You removed the room name")
val someoneRemovedRoomNameEvent = createRoomEvent(sentByYou = false, senderDisplayName = otherName, content = removedContent)
- val someoneRemovedRoomName = formatter.processMessageItem(someoneRemovedRoomNameEvent, false)
+ val someoneRemovedRoomName = formatter.format(someoneRemovedRoomNameEvent, false)
Truth.assertThat(someoneRemovedRoomName).isEqualTo("$otherName removed the room name")
}
@@ -558,19 +567,19 @@ class DefaultRoomLastMessageFormatterTests {
val removedContent = StateContent("", OtherState.RoomThirdPartyInvite(null))
val youInvitedSomeoneEvent = createRoomEvent(sentByYou = true, senderDisplayName = null, content = changedContent)
- val youInvitedSomeone = formatter.processMessageItem(youInvitedSomeoneEvent, false)
+ val youInvitedSomeone = formatter.format(youInvitedSomeoneEvent, false)
Truth.assertThat(youInvitedSomeone).isEqualTo("You sent an invitation to $inviteeName to join the room")
val someoneInvitedSomeoneEvent = createRoomEvent(sentByYou = false, senderDisplayName = otherName, content = changedContent)
- val someoneInvitedSomeone = formatter.processMessageItem(someoneInvitedSomeoneEvent, false)
+ val someoneInvitedSomeone = formatter.format(someoneInvitedSomeoneEvent, false)
Truth.assertThat(someoneInvitedSomeone).isEqualTo("$otherName sent an invitation to $inviteeName to join the room")
val youInvitedNoOneEvent = createRoomEvent(sentByYou = true, senderDisplayName = null, content = removedContent)
- val youInvitedNoOne = formatter.processMessageItem(youInvitedNoOneEvent, false)
+ val youInvitedNoOne = formatter.format(youInvitedNoOneEvent, false)
Truth.assertThat(youInvitedNoOne).isNull()
val someoneInvitedNoOneEvent = createRoomEvent(sentByYou = false, senderDisplayName = otherName, content = removedContent)
- val someoneInvitedNoOne = formatter.processMessageItem(someoneInvitedNoOneEvent, false)
+ val someoneInvitedNoOne = formatter.format(someoneInvitedNoOneEvent, false)
Truth.assertThat(someoneInvitedNoOne).isNull()
}
@@ -583,19 +592,19 @@ class DefaultRoomLastMessageFormatterTests {
val removedContent = StateContent("", OtherState.RoomTopic(null))
val youChangedRoomTopicEvent = createRoomEvent(sentByYou = true, senderDisplayName = null, content = changedContent)
- val youChangedRoomTopic = formatter.processMessageItem(youChangedRoomTopicEvent, false)
+ val youChangedRoomTopic = formatter.format(youChangedRoomTopicEvent, false)
Truth.assertThat(youChangedRoomTopic).isEqualTo("You changed the topic to: $roomTopic")
val someoneChangedRoomTopicEvent = createRoomEvent(sentByYou = false, senderDisplayName = otherName, content = changedContent)
- val someoneChangedRoomTopic = formatter.processMessageItem(someoneChangedRoomTopicEvent, false)
+ val someoneChangedRoomTopic = formatter.format(someoneChangedRoomTopicEvent, false)
Truth.assertThat(someoneChangedRoomTopic).isEqualTo("$otherName changed the topic to: $roomTopic")
val youRemovedRoomTopicEvent = createRoomEvent(sentByYou = true, senderDisplayName = null, content = removedContent)
- val youRemovedRoomTopic = formatter.processMessageItem(youRemovedRoomTopicEvent, false)
+ val youRemovedRoomTopic = formatter.format(youRemovedRoomTopicEvent, false)
Truth.assertThat(youRemovedRoomTopic).isEqualTo("You removed the room topic")
val someoneRemovedRoomTopicEvent = createRoomEvent(sentByYou = false, senderDisplayName = otherName, content = removedContent)
- val someoneRemovedRoomTopic = formatter.processMessageItem(someoneRemovedRoomTopicEvent, false)
+ val someoneRemovedRoomTopic = formatter.format(someoneRemovedRoomTopicEvent, false)
Truth.assertThat(someoneRemovedRoomTopic).isEqualTo("$otherName removed the room topic")
}
@@ -611,7 +620,7 @@ class DefaultRoomLastMessageFormatterTests {
val results = otherStates.map { state ->
val content = StateContent("", state)
val event = createRoomEvent(sentByYou = false, senderDisplayName = "Someone", content = content)
- val result = formatter.processMessageItem(event, false)
+ val result = formatter.format(event, false)
state to result
}
val expected = otherStates.map { it to null }
@@ -633,35 +642,35 @@ class DefaultRoomLastMessageFormatterTests {
val sameContent = aProfileChangeMessageContent(avatarUrl = "same_avatar_url", prevAvatarUrl = "same_avatar_url")
val youChangedAvatarEvent = createRoomEvent(sentByYou = true, senderDisplayName = null, content = changedContent)
- val youChangedAvatar = formatter.processMessageItem(youChangedAvatarEvent, false)
+ val youChangedAvatar = formatter.format(youChangedAvatarEvent, false)
Truth.assertThat(youChangedAvatar).isEqualTo("You changed your avatar")
val someoneChangeAvatarEvent = createRoomEvent(sentByYou = false, senderDisplayName = otherName, content = changedContent)
- val someoneChangeAvatar = formatter.processMessageItem(someoneChangeAvatarEvent, false)
+ val someoneChangeAvatar = formatter.format(someoneChangeAvatarEvent, false)
Truth.assertThat(someoneChangeAvatar).isEqualTo("$otherName changed their avatar")
val youSetAvatarEvent = createRoomEvent(sentByYou = true, senderDisplayName = null, content = setContent)
- val youSetAvatar = formatter.processMessageItem(youSetAvatarEvent, false)
+ val youSetAvatar = formatter.format(youSetAvatarEvent, false)
Truth.assertThat(youSetAvatar).isEqualTo("You changed your avatar")
val someoneSetAvatarEvent = createRoomEvent(sentByYou = false, senderDisplayName = otherName, content = setContent)
- val someoneSetAvatar = formatter.processMessageItem(someoneSetAvatarEvent, false)
+ val someoneSetAvatar = formatter.format(someoneSetAvatarEvent, false)
Truth.assertThat(someoneSetAvatar).isEqualTo("$otherName changed their avatar")
val youRemovedAvatarEvent = createRoomEvent(sentByYou = true, senderDisplayName = null, content = removedContent)
- val youRemovedAvatar = formatter.processMessageItem(youRemovedAvatarEvent, false)
+ val youRemovedAvatar = formatter.format(youRemovedAvatarEvent, false)
Truth.assertThat(youRemovedAvatar).isEqualTo("You changed your avatar")
val someoneRemovedAvatarEvent = createRoomEvent(sentByYou = false, senderDisplayName = otherName, content = removedContent)
- val someoneRemovedAvatar = formatter.processMessageItem(someoneRemovedAvatarEvent, false)
+ val someoneRemovedAvatar = formatter.format(someoneRemovedAvatarEvent, false)
Truth.assertThat(someoneRemovedAvatar).isEqualTo("$otherName changed their avatar")
val unchangedEvent = createRoomEvent(sentByYou = true, senderDisplayName = otherName, content = sameContent)
- val unchangedResult = formatter.processMessageItem(unchangedEvent, false)
+ val unchangedResult = formatter.format(unchangedEvent, false)
Truth.assertThat(unchangedResult).isNull()
val invalidEvent = createRoomEvent(sentByYou = true, senderDisplayName = otherName, content = invalidContent)
- val invalidResult = formatter.processMessageItem(invalidEvent, false)
+ val invalidResult = formatter.format(invalidEvent, false)
Truth.assertThat(invalidResult).isNull()
}
@@ -678,35 +687,35 @@ class DefaultRoomLastMessageFormatterTests {
val invalidContent = aProfileChangeMessageContent(displayName = null, prevDisplayName = null)
val youChangedDisplayNameEvent = createRoomEvent(sentByYou = true, senderDisplayName = null, content = changedContent)
- val youChangedDisplayName = formatter.processMessageItem(youChangedDisplayNameEvent, false)
+ val youChangedDisplayName = formatter.format(youChangedDisplayNameEvent, false)
Truth.assertThat(youChangedDisplayName).isEqualTo("You changed your display name from $oldDisplayName to $newDisplayName")
val someoneChangedDisplayNameEvent = createRoomEvent(sentByYou = false, senderDisplayName = otherName, content = changedContent)
- val someoneChangedDisplayName = formatter.processMessageItem(someoneChangedDisplayNameEvent, false)
+ val someoneChangedDisplayName = formatter.format(someoneChangedDisplayNameEvent, false)
Truth.assertThat(someoneChangedDisplayName).isEqualTo("$otherName changed their display name from $oldDisplayName to $newDisplayName")
val youSetDisplayNameEvent = createRoomEvent(sentByYou = true, senderDisplayName = null, content = setContent)
- val youSetDisplayName = formatter.processMessageItem(youSetDisplayNameEvent, false)
+ val youSetDisplayName = formatter.format(youSetDisplayNameEvent, false)
Truth.assertThat(youSetDisplayName).isEqualTo("You set your display name to $newDisplayName")
val someoneSetDisplayNameEvent = createRoomEvent(sentByYou = false, senderDisplayName = otherName, content = setContent)
- val someoneSetDisplayName = formatter.processMessageItem(someoneSetDisplayNameEvent, false)
+ val someoneSetDisplayName = formatter.format(someoneSetDisplayNameEvent, false)
Truth.assertThat(someoneSetDisplayName).isEqualTo("$otherName set their display name to $newDisplayName")
val youRemovedDisplayNameEvent = createRoomEvent(sentByYou = true, senderDisplayName = null, content = removedContent)
- val youRemovedDisplayName = formatter.processMessageItem(youRemovedDisplayNameEvent, false)
+ val youRemovedDisplayName = formatter.format(youRemovedDisplayNameEvent, false)
Truth.assertThat(youRemovedDisplayName).isEqualTo("You removed your display name (it was $oldDisplayName)")
val someoneRemovedDisplayNameEvent = createRoomEvent(sentByYou = false, senderDisplayName = otherName, content = removedContent)
- val someoneRemovedDisplayName = formatter.processMessageItem(someoneRemovedDisplayNameEvent, false)
+ val someoneRemovedDisplayName = formatter.format(someoneRemovedDisplayNameEvent, false)
Truth.assertThat(someoneRemovedDisplayName).isEqualTo("$otherName removed their display name (it was $oldDisplayName)")
val unchangedEvent = createRoomEvent(sentByYou = true, senderDisplayName = otherName, content = sameContent)
- val unchangedResult = formatter.processMessageItem(unchangedEvent, false)
+ val unchangedResult = formatter.format(unchangedEvent, false)
Truth.assertThat(unchangedResult).isNull()
val invalidEvent = createRoomEvent(sentByYou = true, senderDisplayName = otherName, content = invalidContent)
- val invalidResult = formatter.processMessageItem(invalidEvent, false)
+ val invalidResult = formatter.format(invalidEvent, false)
Truth.assertThat(invalidResult).isNull()
}
@@ -735,15 +744,15 @@ class DefaultRoomLastMessageFormatterTests {
)
val youChangedBothEvent = createRoomEvent(sentByYou = true, senderDisplayName = null, content = changedContent)
- val youChangedBoth = formatter.processMessageItem(youChangedBothEvent, false)
+ val youChangedBoth = formatter.format(youChangedBothEvent, false)
Truth.assertThat(youChangedBoth).isEqualTo("You changed your display name from $oldDisplayName to $newDisplayName\n(avatar was changed too)")
val invalidContentEvent = createRoomEvent(sentByYou = true, senderDisplayName = null, content = invalidContent)
- val invalidMessage = formatter.processMessageItem(invalidContentEvent, false)
+ val invalidMessage = formatter.format(invalidContentEvent, false)
Truth.assertThat(invalidMessage).isNull()
val sameContentEvent = createRoomEvent(sentByYou = true, senderDisplayName = null, content = sameContent)
- val sameMessage = formatter.processMessageItem(sameContentEvent, false)
+ val sameMessage = formatter.format(sameContentEvent, false)
Truth.assertThat(sameMessage).isNull()
}
diff --git a/libraries/eventformatter/test/build.gradle.kts b/libraries/eventformatter/test/build.gradle.kts
new file mode 100644
index 0000000000..8250c57247
--- /dev/null
+++ b/libraries/eventformatter/test/build.gradle.kts
@@ -0,0 +1,28 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+plugins {
+ id("io.element.android-library")
+}
+
+android {
+ namespace = "io.element.android.libraries.eventformatter.test"
+}
+
+dependencies {
+ implementation(projects.libraries.eventformatter.api)
+ implementation(projects.libraries.matrix.api)
+}
diff --git a/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/FakeRoomLastMessageFormatter.kt b/libraries/eventformatter/test/src/main/kotlin/io/element/android/libraries/eventformatter/test/FakeRoomLastMessageFormatter.kt
similarity index 67%
rename from features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/FakeRoomLastMessageFormatter.kt
rename to libraries/eventformatter/test/src/main/kotlin/io/element/android/libraries/eventformatter/test/FakeRoomLastMessageFormatter.kt
index e0763748bf..cd723a27af 100644
--- a/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/FakeRoomLastMessageFormatter.kt
+++ b/libraries/eventformatter/test/src/main/kotlin/io/element/android/libraries/eventformatter/test/FakeRoomLastMessageFormatter.kt
@@ -14,18 +14,20 @@
* limitations under the License.
*/
-package io.element.android.features.roomlist.impl
+package io.element.android.libraries.eventformatter.test
+import io.element.android.libraries.eventformatter.api.RoomLastMessageFormatter
import io.element.android.libraries.matrix.api.timeline.item.event.EventTimelineItem
class FakeRoomLastMessageFormatter : RoomLastMessageFormatter {
- private var processMessageItemResult: CharSequence? = null
- override fun processMessageItem(event: EventTimelineItem, isDmRoom: Boolean): CharSequence? {
- return processMessageItemResult
+ private var result: CharSequence? = null
+
+ override fun format(event: EventTimelineItem, isDmRoom: Boolean): CharSequence? {
+ return result
}
- fun givenRoomSummaryResult(result: CharSequence?) {
- processMessageItemResult = result
+ fun givenFormatResult(result: CharSequence?) {
+ this.result = result
}
}
diff --git a/libraries/push/impl/src/main/res/values-de/translations.xml b/libraries/push/impl/src/main/res/values-de/translations.xml
index 8bf4b581dd..d1ce970c3c 100644
--- a/libraries/push/impl/src/main/res/values-de/translations.xml
+++ b/libraries/push/impl/src/main/res/values-de/translations.xml
@@ -8,11 +8,16 @@
"%1$s: %2$s"
"%1$s: %2$s %3$s"
"%1$s und %2$s"
+ "%1$s in %2$s"
"%1$s in %2$s und %3$s"
- "%1$s: %2$d Nachricht"
- "%1$s: %2$d Nachrichten"
+
+ - "%d Mitteilung"
+ - "%d Mitteilungen"
+
- "%d Einladung"
- "%d Einladungen"
diff --git a/libraries/ui-strings/src/main/res/values-de/translations.xml b/libraries/ui-strings/src/main/res/values-de/translations.xml
index 4cd9d61ac9..2fb21016e5 100644
--- a/libraries/ui-strings/src/main/res/values-de/translations.xml
+++ b/libraries/ui-strings/src/main/res/values-de/translations.xml
@@ -26,6 +26,7 @@
"Raum verlassen"
"Weiter"
"Nein"
+ "Nicht jetzt"
"OK"
"Schnellantwort"
"Zitieren"
@@ -41,10 +42,11 @@
"Teilen"
"Link teilen"
"Überspringen"
+ "Chat starten"
"Foto aufnehmen"
"Ja"
"Über"
- "Analytik"
+ "Analyse"
"Audio"
"Blasen"
"Entschlüsselungsfehler"
@@ -61,8 +63,12 @@
"Offline"
"Passwort"
"Reaktionen"
+ "Fehler melden"
+ "Suchergebnisse"
"Sicherheit"
+ "Server wird nicht unterstützt"
"Einstellungen"
+ "Chat wird gestartet…"
"Sticker"
"Erfolg"
"Vorschläge"
@@ -84,6 +90,7 @@
"Reisen & Orte"
"Symbole"
"Fehler beim Laden der Nachrichten"
+ "Einige Nachrichten wurden nicht gesendet"
"Entschuldigung, ein Fehler ist aufgetreten."
"%1$s Android"
@@ -93,6 +100,9 @@
"Grund für die Meldung dieses Inhalts"
"Dies ist der Anfang von %1$s."
"Neu"
+ "Wir erfassen und analysieren ""keine"" Account-Daten"
+ "Sie können die Analyse jederzeit in den Einstellungen deaktivieren"
+ "Wir geben ""keine"" Informationen an Dritte weiter"
"Teile Analyse-Daten"
"Medienauswahl fehlgeschlagen, bitte versuche es erneut."
"Erkennungsschwelle"
diff --git a/libraries/ui-strings/src/main/res/values-es/translations.xml b/libraries/ui-strings/src/main/res/values-es/translations.xml
index 78a386f650..b1e73503fa 100644
--- a/libraries/ui-strings/src/main/res/values-es/translations.xml
+++ b/libraries/ui-strings/src/main/res/values-es/translations.xml
@@ -116,10 +116,6 @@
- "%1$d miembro"
- "%1$d miembros"
-
- - "%1$d cambio en la sala"
- - "%1$d cambios en la sala"
-
"Agitar con fuerza para informar de un error"
"Parece que sacudes el teléfono con frustración. ¿Quieres abrir la pantalla de informe de errores?"
"Este mensaje se notificará al administrador de su homeserver. No podrán leer ningún mensaje cifrado."
diff --git a/libraries/ui-strings/src/main/res/values-it/translations.xml b/libraries/ui-strings/src/main/res/values-it/translations.xml
index 20f255a21c..c8b16a7ea5 100644
--- a/libraries/ui-strings/src/main/res/values-it/translations.xml
+++ b/libraries/ui-strings/src/main/res/values-it/translations.xml
@@ -116,10 +116,6 @@
- "%1$d membro"
- "%1$d membri"
-
- - "%1$d modifica alla stanza"
- - "%1$d modifiche alla stanza"
-
"Scuoti per segnalare un problema"
"Sembra che tu stia scuotendo il telefono per la frustrazione. Vuoi aprire la schermata di segnalazione dei problemi?"
"Questo messaggio verrà segnalato all\'amministratore dell\'homeserver. Questi non sarà in grado di leggere i messaggi criptati."
diff --git a/libraries/ui-strings/src/main/res/values-ro/translations.xml b/libraries/ui-strings/src/main/res/values-ro/translations.xml
index d7b052badc..efcd69b3fc 100644
--- a/libraries/ui-strings/src/main/res/values-ro/translations.xml
+++ b/libraries/ui-strings/src/main/res/values-ro/translations.xml
@@ -125,11 +125,6 @@
- "%1$d membri"
- "%1$d membri"
-
- - "%1$d schimbare a camerii"
- - "%1$d schimbări ale camerei"
- - "%1$d schimbări ale camerei"
-
"Rageshake pentru a raporta erori"
"Se pare că scuturați telefonul de frustrare. Doriți să deschdeți ecranul de raportare a unei erori?"
"Acest mesaj va fi raportat administratorilor homeserver-ului tau. Ei nu vor putea citi niciun mesaj criptat."
diff --git a/libraries/ui-strings/src/main/res/values/localazy.xml b/libraries/ui-strings/src/main/res/values/localazy.xml
index ca2bcd1d28..163d65f19f 100644
--- a/libraries/ui-strings/src/main/res/values/localazy.xml
+++ b/libraries/ui-strings/src/main/res/values/localazy.xml
@@ -122,7 +122,7 @@
"Failed loading messages"
"Some messages have not been sent"
"Sorry, an error occurred"
- "🔐️ Join me on %1$s"
+ "🔐️ Join me on %1$s"
"Hey, talk to me on %1$s: %2$s"
"Are you sure that you want to leave this room? You are the only person here. If you leave, no one will be able to join in the future, including you."
"Are you sure that you want to leave this room? This room is not public and you will not be able to rejoin without an invite."
@@ -132,10 +132,6 @@
- "%1$d member"
- "%1$d members"
-
- - "%1$d room change"
- - "%1$d room changes"
-
"Rageshake to report bug"
"You seem to be shaking the phone in frustration. Would you like to open the bug report screen?"
"This message will be reported to your homeserver’s administrator. They will not be able to read any encrypted messages."
@@ -167,4 +163,4 @@
"You can read all our terms %1$s."
"here"
"Block user"
-
\ No newline at end of file
+
diff --git a/plugins/src/main/kotlin/extension/DependencyHandleScope.kt b/plugins/src/main/kotlin/extension/DependencyHandleScope.kt
index 595f30cf30..c5fc4dfd58 100644
--- a/plugins/src/main/kotlin/extension/DependencyHandleScope.kt
+++ b/plugins/src/main/kotlin/extension/DependencyHandleScope.kt
@@ -80,6 +80,7 @@ fun DependencyHandlerScope.allLibrariesImpl() {
implementation(project(":libraries:matrixui"))
implementation(project(":libraries:network"))
implementation(project(":libraries:core"))
+ implementation(project(":libraries:eventformatter:impl"))
implementation(project(":libraries:permissions:impl"))
implementation(project(":libraries:push:impl"))
implementation(project(":libraries:push:impl"))
diff --git a/samples/minimal/build.gradle.kts b/samples/minimal/build.gradle.kts
index 78212a1b9c..6ac7599d4a 100644
--- a/samples/minimal/build.gradle.kts
+++ b/samples/minimal/build.gradle.kts
@@ -55,11 +55,13 @@ dependencies {
implementation(projects.libraries.architecture)
implementation(projects.libraries.core)
implementation(projects.libraries.dateformatter.impl)
+ implementation(projects.libraries.eventformatter.impl)
implementation(projects.features.invitelist.impl)
implementation(projects.features.roomlist.impl)
implementation(projects.features.leaveroom.impl)
implementation(projects.features.login.impl)
implementation(projects.features.networkmonitor.impl)
+ implementation(projects.services.toolbox.impl)
implementation(libs.coroutines.core)
coreLibraryDesugaring(libs.android.desugar)
}
diff --git a/samples/minimal/src/main/kotlin/io/element/android/samples/minimal/RoomListScreen.kt b/samples/minimal/src/main/kotlin/io/element/android/samples/minimal/RoomListScreen.kt
index 9a5e1a8631..d6cc2e2e31 100644
--- a/samples/minimal/src/main/kotlin/io/element/android/samples/minimal/RoomListScreen.kt
+++ b/samples/minimal/src/main/kotlin/io/element/android/samples/minimal/RoomListScreen.kt
@@ -24,7 +24,6 @@ import io.element.android.features.invitelist.impl.DefaultSeenInvitesStore
import io.element.android.features.leaveroom.impl.LeaveRoomPresenterImpl
import io.element.android.features.networkmonitor.impl.NetworkMonitorImpl
import io.element.android.features.roomlist.impl.DefaultInviteStateDataSource
-import io.element.android.features.roomlist.impl.DefaultRoomLastMessageFormatter
import io.element.android.features.roomlist.impl.RoomListPresenter
import io.element.android.features.roomlist.impl.RoomListView
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
@@ -32,9 +31,14 @@ import io.element.android.libraries.dateformatter.impl.DateFormatters
import io.element.android.libraries.dateformatter.impl.DefaultLastMessageTimestampFormatter
import io.element.android.libraries.dateformatter.impl.LocalDateTimeProvider
import io.element.android.libraries.designsystem.utils.SnackbarDispatcher
+import io.element.android.libraries.eventformatter.impl.DefaultRoomLastMessageFormatter
+import io.element.android.libraries.eventformatter.impl.ProfileChangeContentFormatter
+import io.element.android.libraries.eventformatter.impl.RoomMembershipContentFormatter
+import io.element.android.libraries.eventformatter.impl.StateContentFormatter
import io.element.android.libraries.matrix.api.MatrixClient
import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.matrix.api.room.RoomMembershipObserver
+import io.element.android.services.toolbox.impl.strings.AndroidStringProvider
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import kotlinx.datetime.Clock
@@ -52,10 +56,17 @@ class RoomListScreen(
private val dateTimeProvider = LocalDateTimeProvider(clock, timeZone)
private val dateFormatters = DateFormatters(locale, clock, timeZone)
private val sessionVerificationService = matrixClient.sessionVerificationService()
+ private val stringProvider = AndroidStringProvider(context.resources)
private val presenter = RoomListPresenter(
client = matrixClient,
lastMessageTimestampFormatter = DefaultLastMessageTimestampFormatter(dateTimeProvider, dateFormatters),
- roomLastMessageFormatter = DefaultRoomLastMessageFormatter(context, matrixClient),
+ roomLastMessageFormatter = DefaultRoomLastMessageFormatter(
+ sp = stringProvider,
+ matrixClient = matrixClient,
+ roomMembershipContentFormatter = RoomMembershipContentFormatter(matrixClient, stringProvider),
+ profileChangeContentFormatter = ProfileChangeContentFormatter(stringProvider),
+ stateContentFormatter = StateContentFormatter(stringProvider),
+ ),
sessionVerificationService = sessionVerificationService,
networkMonitor = NetworkMonitorImpl(context),
snackbarDispatcher = SnackbarDispatcher(),
diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.createroom.impl.components_null_DefaultGroup_AvatarDarkPreview_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.createroom.impl.components_null_DefaultGroup_AvatarDarkPreview_0_null,NEXUS_5,1.0,en].png
index efe83873c0..a7e6d29b5f 100644
--- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.createroom.impl.components_null_DefaultGroup_AvatarDarkPreview_0_null,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.createroom.impl.components_null_DefaultGroup_AvatarDarkPreview_0_null,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:b7787897d8dda469f17da7151260f26b6dcc5500f93ce44fb317af42ae8d457c
-size 93739
+oid sha256:cbfbfa08085539bbfa4f20bc5d149bf0db0036169a32747c96f956d0d0f4b42d
+size 93741
diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.createroom.impl.components_null_DefaultGroup_AvatarLightPreview_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.createroom.impl.components_null_DefaultGroup_AvatarLightPreview_0_null,NEXUS_5,1.0,en].png
index 6dae242f3c..e278779a22 100644
--- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.createroom.impl.components_null_DefaultGroup_AvatarLightPreview_0_null,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.createroom.impl.components_null_DefaultGroup_AvatarLightPreview_0_null,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:6691512398d2c7e319d15397120054f1c2aae452ffed0be47df72ecb906ac4fa
-size 93675
+oid sha256:9e0ba60e784a0dc582826a3f5679a6015531dc9aca22617ec1e0dede38b97f30
+size 93678
diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components.event_null_DefaultGroup_TimelineItemImageViewDarkPreview_0_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components.event_null_DefaultGroup_TimelineItemImageViewDarkPreview_0_null_1,NEXUS_5,1.0,en].png
index 964891e6d2..ca85a9b25c 100644
--- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components.event_null_DefaultGroup_TimelineItemImageViewDarkPreview_0_null_1,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components.event_null_DefaultGroup_TimelineItemImageViewDarkPreview_0_null_1,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:44313efc4d87857b9eb0bb05c8d24330b17353fd95344bcce06c1ad1bba8df81
-size 492167
+oid sha256:766b2d44b51a36d1b15d8a39434ce5237f8427d1150f9b4c1255043fc1e4bb79
+size 492165
diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components.event_null_DefaultGroup_TimelineItemImageViewLightPreview_0_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components.event_null_DefaultGroup_TimelineItemImageViewLightPreview_0_null_1,NEXUS_5,1.0,en].png
index 964891e6d2..ca85a9b25c 100644
--- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components.event_null_DefaultGroup_TimelineItemImageViewLightPreview_0_null_1,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components.event_null_DefaultGroup_TimelineItemImageViewLightPreview_0_null_1,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:44313efc4d87857b9eb0bb05c8d24330b17353fd95344bcce06c1ad1bba8df81
-size 492167
+oid sha256:766b2d44b51a36d1b15d8a39434ce5237f8427d1150f9b4c1255043fc1e4bb79
+size 492165
diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components.event_null_DefaultGroup_TimelineItemStateViewDarkPreview_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components.event_null_DefaultGroup_TimelineItemStateViewDarkPreview_0_null,NEXUS_5,1.0,en].png
new file mode 100644
index 0000000000..ea5deaa132
--- /dev/null
+++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components.event_null_DefaultGroup_TimelineItemStateViewDarkPreview_0_null,NEXUS_5,1.0,en].png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:8a94a376c6b7f7db5e901e23cd9ba4858bdd47fa330161b4d30daa977efda048
+size 3078
diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components.event_null_DefaultGroup_TimelineItemStateViewLightPreview_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components.event_null_DefaultGroup_TimelineItemStateViewLightPreview_0_null,NEXUS_5,1.0,en].png
new file mode 100644
index 0000000000..a076d976ed
--- /dev/null
+++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components.event_null_DefaultGroup_TimelineItemStateViewLightPreview_0_null,NEXUS_5,1.0,en].png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:977ad7144c5d2cb5d6eec5bcfbedf81bc39a83e9fcd0243aab06252d9090640b
+size 3094
diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components.group_null_DefaultGroup_GroupHeaderViewDarkPreview_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components.group_null_DefaultGroup_GroupHeaderViewDarkPreview_0_null,NEXUS_5,1.0,en].png
new file mode 100644
index 0000000000..fc37edc2ef
--- /dev/null
+++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components.group_null_DefaultGroup_GroupHeaderViewDarkPreview_0_null,NEXUS_5,1.0,en].png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:a1846b6a8267752a502ded5cfba052569c929fbb7b53748885b6aba43638b745
+size 54262
diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components.group_null_DefaultGroup_GroupHeaderViewLightPreview_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components.group_null_DefaultGroup_GroupHeaderViewLightPreview_0_null,NEXUS_5,1.0,en].png
new file mode 100644
index 0000000000..f638023723
--- /dev/null
+++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components.group_null_DefaultGroup_GroupHeaderViewLightPreview_0_null,NEXUS_5,1.0,en].png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:6c90a40ad84d4ee108ddbd94fb7dfb9de0203f792c51409565b146f26fcf3763
+size 53225
diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components_null_DefaultGroup_MessageStateEventContainerDarkPreview_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components_null_DefaultGroup_MessageStateEventContainerDarkPreview_0_null,NEXUS_5,1.0,en].png
new file mode 100644
index 0000000000..5fb8afab7b
--- /dev/null
+++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components_null_DefaultGroup_MessageStateEventContainerDarkPreview_0_null,NEXUS_5,1.0,en].png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:0d7d5bc725daafd716308b8f5415a8357608941992c1f1133300aaccb81969ac
+size 1582
diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components_null_DefaultGroup_MessageStateEventContainerLightPreview_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components_null_DefaultGroup_MessageStateEventContainerLightPreview_0_null,NEXUS_5,1.0,en].png
new file mode 100644
index 0000000000..a8662beb70
--- /dev/null
+++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components_null_DefaultGroup_MessageStateEventContainerLightPreview_0_null,NEXUS_5,1.0,en].png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:dae0c56765a6004b43f904e652b1605294eb688c7b3d72af4385f2c58a8234eb
+size 1651
diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components_null_DefaultGroup_MessagesReactionButtonDarkPreview_0_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components_null_DefaultGroup_MessagesReactionButtonDarkPreview_0_null_0,NEXUS_5,1.0,en].png
index 17e1d2852a..7d93e5c715 100644
--- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components_null_DefaultGroup_MessagesReactionButtonDarkPreview_0_null_0,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components_null_DefaultGroup_MessagesReactionButtonDarkPreview_0_null_0,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:523d6607d70a331d2ec32aeabf033d59c4dc60e0e7373982893df0245ae924ce
-size 3332
+oid sha256:a3443f60b8ceec7eccfee92c1bca8cad151e25cff8e7845459a5f4c3d50e5e08
+size 3330
diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components_null_DefaultGroup_MessagesReactionButtonDarkPreview_0_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components_null_DefaultGroup_MessagesReactionButtonDarkPreview_0_null_1,NEXUS_5,1.0,en].png
index abfa1dae68..7b025a402e 100644
--- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components_null_DefaultGroup_MessagesReactionButtonDarkPreview_0_null_1,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components_null_DefaultGroup_MessagesReactionButtonDarkPreview_0_null_1,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:266490a1bdce29ac65d875543deda072d7f2a9aa2e216efb0d54b93c2a11aa5f
-size 4001
+oid sha256:d0b0cdb7e39d5ca5ce874477da4397ebed00ecf9f5d8980f9f3b03ab72259d9f
+size 4000
diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components_null_DefaultGroup_MessagesReactionButtonDarkPreview_0_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components_null_DefaultGroup_MessagesReactionButtonDarkPreview_0_null_2,NEXUS_5,1.0,en].png
index 17e1d2852a..7d93e5c715 100644
--- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components_null_DefaultGroup_MessagesReactionButtonDarkPreview_0_null_2,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components_null_DefaultGroup_MessagesReactionButtonDarkPreview_0_null_2,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:523d6607d70a331d2ec32aeabf033d59c4dc60e0e7373982893df0245ae924ce
-size 3332
+oid sha256:a3443f60b8ceec7eccfee92c1bca8cad151e25cff8e7845459a5f4c3d50e5e08
+size 3330
diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components_null_DefaultGroup_MessagesReactionButtonDarkPreview_0_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components_null_DefaultGroup_MessagesReactionButtonDarkPreview_0_null_3,NEXUS_5,1.0,en].png
index abfa1dae68..7b025a402e 100644
--- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components_null_DefaultGroup_MessagesReactionButtonDarkPreview_0_null_3,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components_null_DefaultGroup_MessagesReactionButtonDarkPreview_0_null_3,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:266490a1bdce29ac65d875543deda072d7f2a9aa2e216efb0d54b93c2a11aa5f
-size 4001
+oid sha256:d0b0cdb7e39d5ca5ce874477da4397ebed00ecf9f5d8980f9f3b03ab72259d9f
+size 4000
diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components_null_DefaultGroup_MessagesReactionButtonLightPreview_0_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components_null_DefaultGroup_MessagesReactionButtonLightPreview_0_null_0,NEXUS_5,1.0,en].png
index 66a540e8a7..eb0f1bcfde 100644
--- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components_null_DefaultGroup_MessagesReactionButtonLightPreview_0_null_0,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components_null_DefaultGroup_MessagesReactionButtonLightPreview_0_null_0,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:e94515b43bc4b8427edc60456ec657ec38b33539a034a2c45c92624a6b75d4e8
-size 3138
+oid sha256:d35eb1aac0d413c216dc77f45f655be97ae9d36c3cade57ee8f70e25c44fafd1
+size 3142
diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components_null_DefaultGroup_MessagesReactionButtonLightPreview_0_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components_null_DefaultGroup_MessagesReactionButtonLightPreview_0_null_1,NEXUS_5,1.0,en].png
index 6a087cd16c..69b558a480 100644
--- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components_null_DefaultGroup_MessagesReactionButtonLightPreview_0_null_1,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components_null_DefaultGroup_MessagesReactionButtonLightPreview_0_null_1,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:a4dc928a04bfc6331673be2fa0bf7f12e384dfd8ff91b07dc57b220c8dbbdd71
-size 3829
+oid sha256:782a439312e4b616a6dd3020d8c99d04042208b2ebc416fab73c95344adeba67
+size 3834
diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components_null_DefaultGroup_MessagesReactionButtonLightPreview_0_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components_null_DefaultGroup_MessagesReactionButtonLightPreview_0_null_2,NEXUS_5,1.0,en].png
index 66a540e8a7..eb0f1bcfde 100644
--- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components_null_DefaultGroup_MessagesReactionButtonLightPreview_0_null_2,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components_null_DefaultGroup_MessagesReactionButtonLightPreview_0_null_2,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:e94515b43bc4b8427edc60456ec657ec38b33539a034a2c45c92624a6b75d4e8
-size 3138
+oid sha256:d35eb1aac0d413c216dc77f45f655be97ae9d36c3cade57ee8f70e25c44fafd1
+size 3142
diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components_null_DefaultGroup_MessagesReactionButtonLightPreview_0_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components_null_DefaultGroup_MessagesReactionButtonLightPreview_0_null_3,NEXUS_5,1.0,en].png
index 6a087cd16c..69b558a480 100644
--- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components_null_DefaultGroup_MessagesReactionButtonLightPreview_0_null_3,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components_null_DefaultGroup_MessagesReactionButtonLightPreview_0_null_3,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:a4dc928a04bfc6331673be2fa0bf7f12e384dfd8ff91b07dc57b220c8dbbdd71
-size 3829
+oid sha256:782a439312e4b616a6dd3020d8c99d04042208b2ebc416fab73c95344adeba67
+size 3834
diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components_null_DefaultGroup_TimelineItemReactionsViewDarkPreview_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components_null_DefaultGroup_TimelineItemReactionsViewDarkPreview_0_null,NEXUS_5,1.0,en].png
index 89034f66b9..d6eab0d361 100644
--- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components_null_DefaultGroup_TimelineItemReactionsViewDarkPreview_0_null,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components_null_DefaultGroup_TimelineItemReactionsViewDarkPreview_0_null,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:25d0334932490997d432115c550c42e74e7226399ef82031430bf86ba33972bd
-size 6066
+oid sha256:6cab21ee595c96700e4577554f9c98fdc56a59bf5093aa9d81a54d10381ba122
+size 6065
diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components_null_DefaultGroup_TimelineItemReactionsViewLightPreview_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components_null_DefaultGroup_TimelineItemReactionsViewLightPreview_0_null,NEXUS_5,1.0,en].png
index c407012dfd..70722aa53f 100644
--- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components_null_DefaultGroup_TimelineItemReactionsViewLightPreview_0_null,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components_null_DefaultGroup_TimelineItemReactionsViewLightPreview_0_null,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:a2d15387248308e5234c84b9b2b282e736f05f0495c80807bacfb0acff31eb2b
-size 5890
+oid sha256:b9235621126fe90a51d557df616878d53120dada814830fba006b54f1556e69e
+size 5895
diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewDarkPreview_0_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewDarkPreview_0_null_0,NEXUS_5,1.0,en].png
index 3fc91b65e4..84913d3fb7 100644
--- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewDarkPreview_0_null_0,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewDarkPreview_0_null_0,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:5315b975f8b9def0b0d7575160cb34300511f1b83845ee2d1762188481cf6fe9
-size 30927
+oid sha256:b625f841a45111d58d76fd290cfad376a58e16265cc0b0d8e00318ffa1e3f48e
+size 35204
diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewDarkPreview_0_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewDarkPreview_0_null_1,NEXUS_5,1.0,en].png
index 7a61ecf737..66225f1c42 100644
--- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewDarkPreview_0_null_1,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewDarkPreview_0_null_1,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:099401d03621aca301d3c4590d90ba4e4642d400d7603a8e15140a1bfef0c0ec
-size 42758
+oid sha256:a5391a08d36198d28cc2c05c83757d30dc207dd56ada9684cd3a3ea7f9812b0a
+size 46977
diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewDarkPreview_0_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewDarkPreview_0_null_2,NEXUS_5,1.0,en].png
index e361e4a2d2..776728bf2a 100644
--- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewDarkPreview_0_null_2,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewDarkPreview_0_null_2,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:ca5dbc3aa00d6116de38fc0217b20ab0db4096b1f76bcc18b43ab9fd343f8110
-size 32722
+oid sha256:6f951f69dd21d36338ddbfb303d50c554ef915c40e57919f699d83afce507d75
+size 36906
diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewDarkPreview_0_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewDarkPreview_0_null_3,NEXUS_5,1.0,en].png
index 42f22c9a26..e5a622f671 100644
--- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewDarkPreview_0_null_3,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewDarkPreview_0_null_3,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:6211ec07d10d48e4713206e4c1fe24eae890fe2310d5bcc1f4ca54cc57f84ec9
-size 44564
+oid sha256:4dec759835366e66beae953d11e73a52717500b30d5c58df6e3912a3cc60f916
+size 48704
diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewDarkPreview_0_null_4,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewDarkPreview_0_null_4,NEXUS_5,1.0,en].png
index b974aec366..328ffb4a69 100644
--- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewDarkPreview_0_null_4,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewDarkPreview_0_null_4,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:8ac7985e70782a84fc351cb15536bfd700ca8d6d48d5da0ee9dcaf5c78226479
-size 28909
+oid sha256:3bebe664eb8cd7a0578977ad998ddfe24283a309a6830cc420416c99b00f1e60
+size 33114
diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewDarkPreview_0_null_5,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewDarkPreview_0_null_5,NEXUS_5,1.0,en].png
index cf23661de1..af164ce007 100644
--- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewDarkPreview_0_null_5,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewDarkPreview_0_null_5,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:c99138940ed1a8beac7da3c673600016a03b87195e07f205d5d389ef05be0fb0
-size 45978
+oid sha256:9ed02fce151bfb9eec7b380e9469b67c8ce9757eb7a0b7ebbcf0a5932c55087e
+size 50072
diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewLightPreview_0_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewLightPreview_0_null_0,NEXUS_5,1.0,en].png
index 20f62bf8e6..4113c9a338 100644
--- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewLightPreview_0_null_0,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewLightPreview_0_null_0,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:2f24112ad89e5ef93917d45e3232043168d5092ce6d62a7d414773dd0afda963
-size 30326
+oid sha256:73de3dd7281ea437f3cb364cca175cec5379046da19fd8f1152f3a52f43b6bfa
+size 34442
diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewLightPreview_0_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewLightPreview_0_null_1,NEXUS_5,1.0,en].png
index 534402d370..2781e8027e 100644
--- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewLightPreview_0_null_1,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewLightPreview_0_null_1,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:9c696c464515873a94d28f8909dc47255717fc3b012ce0d80000769714e3cb9c
-size 42427
+oid sha256:4bb36a52495fc17de86c0b66c6ac5eb8d4df64e3c512db41ec546b02c8157133
+size 46507
diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewLightPreview_0_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewLightPreview_0_null_2,NEXUS_5,1.0,en].png
index 88014ea46b..17545e89f6 100644
--- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewLightPreview_0_null_2,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewLightPreview_0_null_2,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:0fe506e4e7adbfe19fd48f0a40c19343471e0bb06e12fac8d5e6d54595e7193a
-size 32076
+oid sha256:b668eb7b9ce26c8d4b25d9e955ded21ef40c9db496a81cca99f299c84838fcd3
+size 36143
diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewLightPreview_0_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewLightPreview_0_null_3,NEXUS_5,1.0,en].png
index e70fa9d639..08e023fa8d 100644
--- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewLightPreview_0_null_3,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewLightPreview_0_null_3,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:c9c9374c303c9a71b0526fccfd18903d145c1b3b0059de3ccd41cf80f1541a80
-size 44427
+oid sha256:3ce9ae983222355419305a239dae4deefa36cf90b03840b69401a3e9329ad4df
+size 48421
diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewLightPreview_0_null_4,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewLightPreview_0_null_4,NEXUS_5,1.0,en].png
index 4ebc61d047..5404d4ef7e 100644
--- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewLightPreview_0_null_4,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewLightPreview_0_null_4,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:5f18d8b33eace857f78b92eed035621125bbfca1f0d43e5e47fd1c3965324de0
-size 28319
+oid sha256:7692c7dd3ce432abda585d3e539ab03eb51f1df7debca4b14ba015cfd0111e42
+size 32372
diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewLightPreview_0_null_5,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewLightPreview_0_null_5,NEXUS_5,1.0,en].png
index 336334e955..57c5b61d46 100644
--- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewLightPreview_0_null_5,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewLightPreview_0_null_5,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:be0722a804c2fc264f931f8784635bceab7ddcf08e378bed8ea510bd76813fbf
-size 45642
+oid sha256:ffccef423c7be0a6bcb15facabb174de1f20833aa73af0db7397dd3eb011642e
+size 49791
diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl_null_DefaultGroup_MessagesViewDarkPreview_0_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl_null_DefaultGroup_MessagesViewDarkPreview_0_null_0,NEXUS_5,1.0,en].png
index 1ba319f8ee..27584b85d5 100644
--- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl_null_DefaultGroup_MessagesViewDarkPreview_0_null_0,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl_null_DefaultGroup_MessagesViewDarkPreview_0_null_0,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:b77478183675fd3bb9a7ef34ef808e3ad75b504a0236b4dcb0d96a7faafc29cc
-size 39629
+oid sha256:71ead784370bd7007dc52c48d31651512721c42fdcd37a177cb078802848412d
+size 43813
diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl_null_DefaultGroup_MessagesViewDarkPreview_0_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl_null_DefaultGroup_MessagesViewDarkPreview_0_null_1,NEXUS_5,1.0,en].png
index 27b4e07cb9..c326a2eafa 100644
--- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl_null_DefaultGroup_MessagesViewDarkPreview_0_null_1,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl_null_DefaultGroup_MessagesViewDarkPreview_0_null_1,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:2645c93a097d35725a7afb83c97dd0ea6b5623990cbae2372aaf66c9011e353c
-size 41506
+oid sha256:e29a3b27b84b1be439b6b80e2a6df9ef30c87d74b51ca1079c683fd8d935d2c1
+size 44308
diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl_null_DefaultGroup_MessagesViewDarkPreview_0_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl_null_DefaultGroup_MessagesViewDarkPreview_0_null_2,NEXUS_5,1.0,en].png
index dee6d7dd1a..c74ec030c4 100644
--- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl_null_DefaultGroup_MessagesViewDarkPreview_0_null_2,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl_null_DefaultGroup_MessagesViewDarkPreview_0_null_2,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:394ffe0b730e7d4010ec0d7f96b5487b2775e70aa7e42b6bf8b2535ad5acc244
-size 37524
+oid sha256:78061b0dccb2cd76d0f124eb62f55db3bae1c05b533acadc76898669daa483db
+size 41497
diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl_null_DefaultGroup_MessagesViewDarkPreview_0_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl_null_DefaultGroup_MessagesViewDarkPreview_0_null_3,NEXUS_5,1.0,en].png
index 4aa436dc8c..e24e462642 100644
--- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl_null_DefaultGroup_MessagesViewDarkPreview_0_null_3,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl_null_DefaultGroup_MessagesViewDarkPreview_0_null_3,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:b87be006b0125029391e74a09bc2a07a906c33d9ea21ca17f884f2d8862af32a
-size 36302
+oid sha256:9f535800616d0b9a03fd00088a823bc58b359c0c20c311816de824279308fee3
+size 40267
diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl_null_DefaultGroup_MessagesViewLightPreview_0_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl_null_DefaultGroup_MessagesViewLightPreview_0_null_0,NEXUS_5,1.0,en].png
index c3e23147ca..fee4e7edc4 100644
--- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl_null_DefaultGroup_MessagesViewLightPreview_0_null_0,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl_null_DefaultGroup_MessagesViewLightPreview_0_null_0,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:93f8dd057f245d454a6ebf81607c879b6ae4c20287a64d001bee36764e0802a5
-size 38657
+oid sha256:6a733f5480087262e0e4a51d528ebe27941c6b38452ea7d2412ab44306ce4664
+size 42709
diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl_null_DefaultGroup_MessagesViewLightPreview_0_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl_null_DefaultGroup_MessagesViewLightPreview_0_null_1,NEXUS_5,1.0,en].png
index c3d01356c6..a0f6bfb231 100644
--- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl_null_DefaultGroup_MessagesViewLightPreview_0_null_1,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl_null_DefaultGroup_MessagesViewLightPreview_0_null_1,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:2a4ce62d7f8f27128fd59cd3e7eea068a002d7e2c0a054fef1f555d06cf91ebe
-size 40708
+oid sha256:5dd805be3de3c5a22038e95bb0d2c9716b27a2af0156cfdb363c9cab0ba1bc8e
+size 43460
diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl_null_DefaultGroup_MessagesViewLightPreview_0_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl_null_DefaultGroup_MessagesViewLightPreview_0_null_2,NEXUS_5,1.0,en].png
index 4660650dfb..551258be5c 100644
--- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl_null_DefaultGroup_MessagesViewLightPreview_0_null_2,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl_null_DefaultGroup_MessagesViewLightPreview_0_null_2,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:bee8752a24938844cae76aadae9148a19001302a9e4909cf69a192a98d583ba4
-size 36230
+oid sha256:896760a67355f8d7d332edd9e705d305503da7f96ed895a7a792d008e6352aea
+size 40210
diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl_null_DefaultGroup_MessagesViewLightPreview_0_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl_null_DefaultGroup_MessagesViewLightPreview_0_null_3,NEXUS_5,1.0,en].png
index 6e04daf76b..75c9b207a1 100644
--- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl_null_DefaultGroup_MessagesViewLightPreview_0_null_3,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl_null_DefaultGroup_MessagesViewLightPreview_0_null_3,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:8f89eebcabe514594c53e6e436ae63685e6b693628f2acf796c6306298aff1a8
-size 35199
+oid sha256:b5bcc73a77a86083a2229b174122ec76d0fdf07973f838c26862e9ece4990311
+size 39133
diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.onboarding.impl_null_DefaultGroup_OnBoardingScreenDarkPreview_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.onboarding.impl_null_DefaultGroup_OnBoardingScreenDarkPreview_0_null,NEXUS_5,1.0,en].png
index b1f708d57c..9749477ebd 100644
--- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.onboarding.impl_null_DefaultGroup_OnBoardingScreenDarkPreview_0_null,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.onboarding.impl_null_DefaultGroup_OnBoardingScreenDarkPreview_0_null,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:b53d55b5085673ac3a0a7663f7ff68e7d510fe352f3931b49c7e75e4c3767b93
-size 60007
+oid sha256:c820bd324df729db08710d1a6c17ca34a451a3bb94da2206fc57d6b4efe91e2f
+size 60019
diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.onboarding.impl_null_DefaultGroup_OnBoardingScreenLightPreview_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.onboarding.impl_null_DefaultGroup_OnBoardingScreenLightPreview_0_null,NEXUS_5,1.0,en].png
index d020b1e521..f24edcc4a1 100644
--- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.onboarding.impl_null_DefaultGroup_OnBoardingScreenLightPreview_0_null,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.onboarding.impl_null_DefaultGroup_OnBoardingScreenLightPreview_0_null,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:a761318f3dfbc2ce6e777cfabc19eeb2f89100ae7631660f4b4d7550cd947c84
-size 57580
+oid sha256:da2f9f87d89382b4b18d39a477a8e86f98067e2326adae4def8374b5bf09f316
+size 57588
diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.rageshake.impl.bugreport_null_DefaultGroup_BugReportViewDarkPreview_0_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.rageshake.impl.bugreport_null_DefaultGroup_BugReportViewDarkPreview_0_null_1,NEXUS_5,1.0,en].png
index 01ec8ec0fc..07451a0330 100644
--- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.rageshake.impl.bugreport_null_DefaultGroup_BugReportViewDarkPreview_0_null_1,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.rageshake.impl.bugreport_null_DefaultGroup_BugReportViewDarkPreview_0_null_1,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:147786f2cfcf7674147253cca5e41b4af96587a27216076a1c0802c81b0b1b46
+oid sha256:628f1f00dca9d15faabd8288a3c54b1b8581a380cf14edf364f0dee9ecc187d5
size 180117
diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.rageshake.impl.bugreport_null_DefaultGroup_BugReportViewLightPreview_0_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.rageshake.impl.bugreport_null_DefaultGroup_BugReportViewLightPreview_0_null_1,NEXUS_5,1.0,en].png
index a49d11835b..8146c241cb 100644
--- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.rageshake.impl.bugreport_null_DefaultGroup_BugReportViewLightPreview_0_null_1,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.rageshake.impl.bugreport_null_DefaultGroup_BugReportViewLightPreview_0_null_1,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:789a979f7207531b4b9ba488ae2de52e6046809e788baf6690e1383a933cf624
-size 178916
+oid sha256:827bccb0fdd3106fb24a95d665b4da2cfc15de48e8f508ae809c9f75d6d1bbf0
+size 178915
diff --git a/tools/localazy/config.json b/tools/localazy/config.json
index 5219187f75..ef7110a160 100644
--- a/tools/localazy/config.json
+++ b/tools/localazy/config.json
@@ -56,6 +56,12 @@
"error_no_compatible_app_found"
]
},
+ {
+ "name": ":libraries:eventformatter:impl",
+ "includeRegex": [
+ "state_event_.*"
+ ]
+ },
{
"name": ":libraries:push:impl",
"includeRegex": [
@@ -73,7 +79,6 @@
{
"name": ":features:roomlist:impl",
"includeRegex": [
- "state_event_.*",
"screen_roomlist_.*",
"session_verification_banner_.*"
]
@@ -90,7 +95,8 @@
"name": ":features:messages:impl",
"includeRegex": [
"screen_room_.*",
- "screen_dm_details_.*"
+ "screen_dm_details_.*",
+ "room_timeline_state_changes"
],
"excludeRegex": [
"screen_room_details_.*",