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_.*",