Group some state events in the timeline

This commit is contained in:
Benoit Marty 2023-05-22 13:53:06 +02:00 committed by Benoit Marty
parent 26198140df
commit 0c95912c9c
18 changed files with 332 additions and 32 deletions

View file

@ -6,4 +6,4 @@
<string name="screen_create_room_private_option_title">"Privater Raum (nur auf Einladung)"</string>
<string name="screen_create_room_room_name_label">"Raumname"</string>
<string name="screen_create_room_topic_label">"Thema (optional)"</string>
</resources>
</resources>

View file

@ -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(

View file

@ -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
}

View file

@ -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<TimelineState> {
@ -53,6 +57,8 @@ class TimelinePresenter @Inject constructor(
val highlightedEventId: MutableState<EventId?> = rememberSaveable {
mutableStateOf(null)
}
val expandedGroups = remember { mutableStateMapOf<String, Boolean>() }
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
)
}

View file

@ -50,14 +50,17 @@ 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
@ -84,6 +87,7 @@ fun TimelineView(
modifier: Modifier = Modifier,
onMessageClicked: (TimelineItem.Event) -> Unit = {},
onMessageLongClicked: (TimelineItem.Event) -> Unit = {},
onExpandGroupClick: (TimelineItem.GroupedEvents) -> Unit = {},
) {
fun onReachedLoadMore() {
@ -106,9 +110,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()
@ -127,9 +132,10 @@ fun TimelineView(
@Composable
fun TimelineItemRow(
timelineItem: TimelineItem,
isHighlighted: Boolean,
highlightedItem: String?,
onClick: (TimelineItem.Event) -> Unit,
onLongClick: (TimelineItem.Event) -> Unit,
onExpandGroupClick: (TimelineItem.GroupedEvents) -> Unit,
) {
when (timelineItem) {
is TimelineItem.Virtual -> {
@ -149,19 +155,48 @@ fun TimelineItemRow(
if (timelineItem.content is TimelineItemStateContent) {
TimelineItemStateEventRow(
event = timelineItem,
isHighlighted = isHighlighted,
isHighlighted = highlightedItem == timelineItem.identifier(),
onClick = ::onClick,
onLongClick = ::onLongClick
)
} else {
TimelineItemEventRow(
event = timelineItem,
isHighlighted = isHighlighted,
isHighlighted = highlightedItem == timelineItem.identifier(),
onClick = ::onClick,
onLongClick = ::onLongClick
)
}
}
is TimelineItem.GroupedEvents -> {
fun onExpandGroupClick() {
onExpandGroupClick(timelineItem)
}
if (timelineItem.expanded) {
Column {
timelineItem.events.forEach { subGroupEvent ->
TimelineItemRow(
timelineItem = subGroupEvent,
highlightedItem = highlightedItem,
onClick = onClick,
onLongClick = onLongClick,
onExpandGroupClick = {}
)
}
}
}
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,
)
}
}
}

View file

@ -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.ArrowDropDown
import androidx.compose.material.icons.filled.ArrowRight
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.ArrowDropDown
} else {
Icons.Default.ArrowRight
}
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 = {}
)
}
}

View file

@ -0,0 +1,82 @@
/*
* 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
/**
* Create a new list of [TimelineItem] by grouping some of them into [TimelineItem.GroupedEvents].
*/
class TimelineItemGrouper @Inject constructor() {
fun group(from: List<TimelineItem>, expandedGroups: Map<String, Boolean>): List<TimelineItem> {
val result = mutableListOf<TimelineItem>()
val currentGroup = mutableListOf<TimelineItem.Event>()
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.
if (currentGroup.size == 1) {
// Do not create a group with just 1 item, just add the item to the result
result.add(currentGroup.first())
} else {
result.add(
TimelineItem.GroupedEvents(
expanded = expandedGroups[currentGroup.first().id + "_group"].orFalse(),
events = currentGroup.toImmutableList()
)
)
}
currentGroup.clear()
}
result.add(timelineItem)
}
}
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
}
}
}

View file

@ -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<Event>,
) : TimelineItem {
// use first id with a suffix
val id = events.first().id + "_group"
}
}

View file

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<plurals name="room_timeline_state_changes">
<item quantity="one">"%1$d cambio en la sala"</item>
<item quantity="other">"%1$d cambios en la sala"</item>
</plurals>
</resources>

View file

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<plurals name="room_timeline_state_changes">
<item quantity="one">"%1$d modifica alla stanza"</item>
<item quantity="other">"%1$d modifiche alla stanza"</item>
</plurals>
</resources>

View file

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<plurals name="room_timeline_state_changes">
<item quantity="one">"%1$d schimbare a camerii"</item>
<item quantity="few">"%1$d schimbări ale camerei"</item>
<item quantity="other">"%1$d schimbări ale camerei"</item>
</plurals>
</resources>

View file

@ -1,5 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<plurals name="room_timeline_state_changes">
<item quantity="one">"%1$d room change"</item>
<item quantity="other">"%1$d room changes"</item>
</plurals>
<string name="screen_room_attachment_source_camera">"Camera"</string>
<string name="screen_room_attachment_source_camera_photo">"Take photo"</string>
<string name="screen_room_attachment_source_camera_video">"Record a video"</string>

View file

@ -26,6 +26,7 @@
<string name="action_leave_room">"Raum verlassen"</string>
<string name="action_next">"Weiter"</string>
<string name="action_no">"Nein"</string>
<string name="action_not_now">"Nicht jetzt"</string>
<string name="action_ok">"OK"</string>
<string name="action_quick_reply">"Schnellantwort"</string>
<string name="action_quote">"Zitieren"</string>
@ -41,10 +42,11 @@
<string name="action_share">"Teilen"</string>
<string name="action_share_link">"Link teilen"</string>
<string name="action_skip">"Überspringen"</string>
<string name="action_start_chat">"Chat starten"</string>
<string name="action_take_photo">"Foto aufnehmen"</string>
<string name="action_yes">"Ja"</string>
<string name="common_about">"Über"</string>
<string name="common_analytics">"Analytik"</string>
<string name="common_analytics">"Analyse"</string>
<string name="common_audio">"Audio"</string>
<string name="common_bubbles">"Blasen"</string>
<string name="common_decryption_error">"Entschlüsselungsfehler"</string>
@ -61,6 +63,7 @@
<string name="common_offline">"Offline"</string>
<string name="common_password">"Passwort"</string>
<string name="common_reactions">"Reaktionen"</string>
<string name="common_report_a_bug">"Fehler melden"</string>
<string name="common_search_results">"Suchergebnisse"</string>
<string name="common_security">"Sicherheit"</string>
<string name="common_server_not_supported">"Server wird nicht unterstützt"</string>
@ -87,6 +90,7 @@
<string name="emoji_picker_category_places">"Reisen &amp; Orte"</string>
<string name="emoji_picker_category_symbols">"Symbole"</string>
<string name="error_failed_loading_messages">"Fehler beim Laden der Nachrichten"</string>
<string name="error_some_messages_have_not_been_sent">"Einige Nachrichten wurden nicht gesendet"</string>
<string name="error_unknown">"Entschuldigung, ein Fehler ist aufgetreten."</string>
<string name="login_initial_device_name_android">"%1$s Android"</string>
<plurals name="common_member_count">
@ -96,6 +100,9 @@
<string name="report_content_hint">"Grund für die Meldung dieses Inhalts"</string>
<string name="room_timeline_beginning_of_room">"Dies ist der Anfang von %1$s."</string>
<string name="room_timeline_read_marker_title">"Neu"</string>
<string name="screen_analytics_prompt_data_usage">"Wir erfassen und analysieren "<b>"keine"</b>" Account-Daten"</string>
<string name="screen_analytics_prompt_settings">"Sie können die Analyse jederzeit in den Einstellungen deaktivieren"</string>
<string name="screen_analytics_prompt_third_party_sharing">"Wir geben "<b>"keine"</b>" Informationen an Dritte weiter"</string>
<string name="screen_analytics_settings_share_data">"Teile Analyse-Daten"</string>
<string name="screen_media_picker_error_failed_selection">"Medienauswahl fehlgeschlagen, bitte versuche es erneut."</string>
<string name="settings_rageshake_detection_threshold">"Erkennungsschwelle"</string>

View file

@ -116,10 +116,6 @@
<item quantity="one">"%1$d miembro"</item>
<item quantity="other">"%1$d miembros"</item>
</plurals>
<plurals name="room_timeline_state_changes">
<item quantity="one">"%1$d cambio en la sala"</item>
<item quantity="other">"%1$d cambios en la sala"</item>
</plurals>
<string name="preference_rageshake">"Agitar con fuerza para informar de un error"</string>
<string name="rageshake_dialog_content">"Parece que sacudes el teléfono con frustración. ¿Quieres abrir la pantalla de informe de errores?"</string>
<string name="report_content_explanation">"Este mensaje se notificará al administrador de su homeserver. No podrán leer ningún mensaje cifrado."</string>

View file

@ -116,10 +116,6 @@
<item quantity="one">"%1$d membro"</item>
<item quantity="other">"%1$d membri"</item>
</plurals>
<plurals name="room_timeline_state_changes">
<item quantity="one">"%1$d modifica alla stanza"</item>
<item quantity="other">"%1$d modifiche alla stanza"</item>
</plurals>
<string name="preference_rageshake">"Scuoti per segnalare un problema"</string>
<string name="rageshake_dialog_content">"Sembra che tu stia scuotendo il telefono per la frustrazione. Vuoi aprire la schermata di segnalazione dei problemi?"</string>
<string name="report_content_explanation">"Questo messaggio verrà segnalato all\'amministratore dell\'homeserver. Questi non sarà in grado di leggere i messaggi criptati."</string>

View file

@ -125,11 +125,6 @@
<item quantity="few">"%1$d membri"</item>
<item quantity="other">"%1$d membri"</item>
</plurals>
<plurals name="room_timeline_state_changes">
<item quantity="one">"%1$d schimbare a camerii"</item>
<item quantity="few">"%1$d schimbări ale camerei"</item>
<item quantity="other">"%1$d schimbări ale camerei"</item>
</plurals>
<string name="preference_rageshake">"Rageshake pentru a raporta erori"</string>
<string name="rageshake_dialog_content">"Se pare că scuturați telefonul de frustrare. Doriți să deschdeți ecranul de raportare a unei erori?"</string>
<string name="report_content_explanation">"Acest mesaj va fi raportat administratorilor homeserver-ului tau. Ei nu vor putea citi niciun mesaj criptat."</string>

View file

@ -122,7 +122,7 @@
<string name="error_failed_loading_messages">"Failed loading messages"</string>
<string name="error_some_messages_have_not_been_sent">"Some messages have not been sent"</string>
<string name="error_unknown">"Sorry, an error occurred"</string>
<string name="invite_friends_rich_title">"🔐️ Join me on %1$s"</string>
<string name="invite_friends_rich_title">"🔐️ Join me on %1$s"</string>
<string name="invite_friends_text">"Hey, talk to me on %1$s: %2$s"</string>
<string name="leave_room_alert_empty_subtitle">"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."</string>
<string name="leave_room_alert_private_subtitle">"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."</string>
@ -132,10 +132,6 @@
<item quantity="one">"%1$d member"</item>
<item quantity="other">"%1$d members"</item>
</plurals>
<plurals name="room_timeline_state_changes">
<item quantity="one">"%1$d room change"</item>
<item quantity="other">"%1$d room changes"</item>
</plurals>
<string name="preference_rageshake">"Rageshake to report bug"</string>
<string name="rageshake_dialog_content">"You seem to be shaking the phone in frustration. Would you like to open the bug report screen?"</string>
<string name="report_content_explanation">"This message will be reported to your homeservers administrator. They will not be able to read any encrypted messages."</string>
@ -167,4 +163,4 @@
<string name="screen_analytics_settings_read_terms">"You can read all our terms %1$s."</string>
<string name="screen_analytics_settings_read_terms_content_link">"here"</string>
<string name="screen_report_content_block_user">"Block user"</string>
</resources>
</resources>

View file

@ -57,7 +57,7 @@
]
},
{
"name": ":libraries:messageformatter:impl",
"name": ":libraries:eventformatter:impl",
"includeRegex": [
"state_event_.*"
]
@ -95,7 +95,8 @@
"name": ":features:messages:impl",
"includeRegex": [
"screen_room_.*",
"screen_dm_details_.*"
"screen_dm_details_.*",
"room_timeline_state_changes"
],
"excludeRegex": [
"screen_room_details_.*",