From 70df081840da751575fede43e1488942b50186d6 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 16 May 2023 11:45:58 +0200 Subject: [PATCH 01/17] Move RoomLastMessageFormatter and related class and resource to the new module `eventformatter`, in order to prepare rendering of state event in the timeline. --- .../src/main/res/values-de/translations.xml | 3 +- .../src/main/res/values-de/translations.xml | 4 ++ .../src/main/res/values-de/translations.xml | 2 + features/roomlist/impl/build.gradle.kts | 2 + .../roomlist/impl/RoomListPresenter.kt | 1 + .../src/main/res/values-de/translations.xml | 36 ------------ .../src/main/res/values-es/translations.xml | 54 ------------------ .../src/main/res/values-it/translations.xml | 54 ------------------ .../src/main/res/values-ro/translations.xml | 54 ------------------ .../impl/src/main/res/values/localazy.xml | 54 ------------------ .../roomlist/impl/RoomListPresenterTests.kt | 1 + libraries/eventformatter/api/build.gradle.kts | 27 +++++++++ .../api}/RoomLastMessageFormatter.kt | 2 +- .../eventformatter/impl/build.gradle.kts | 49 ++++++++++++++++ .../impl/DefaultRoomLastMessageFormatter.kt | 3 +- .../src/main/res/values-de/translations.xml | 39 +++++++++++++ .../src/main/res/values-es/translations.xml | 57 +++++++++++++++++++ .../src/main/res/values-it/translations.xml | 57 +++++++++++++++++++ .../src/main/res/values-ro/translations.xml | 57 +++++++++++++++++++ .../impl/src/main/res/values/localazy.xml | 57 +++++++++++++++++++ .../DefaultRoomLastMessageFormatterTests.kt | 2 +- .../eventformatter/test/build.gradle.kts | 28 +++++++++ .../test}/FakeRoomLastMessageFormatter.kt | 3 +- .../src/main/res/values-de/translations.xml | 5 ++ .../src/main/res/values-de/translations.xml | 3 + .../kotlin/extension/DependencyHandleScope.kt | 1 + samples/minimal/build.gradle.kts | 1 + .../android/samples/minimal/RoomListScreen.kt | 2 +- tools/localazy/config.json | 7 ++- 29 files changed, 406 insertions(+), 259 deletions(-) create mode 100644 libraries/eventformatter/api/build.gradle.kts rename {features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl => libraries/eventformatter/api/src/main/kotlin/io/element/android/libraries/eventformatter/api}/RoomLastMessageFormatter.kt (93%) create mode 100644 libraries/eventformatter/impl/build.gradle.kts rename {features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist => libraries/eventformatter/impl/src/main/kotlin/io/element/android/libraries/eventformatter}/impl/DefaultRoomLastMessageFormatter.kt (99%) create mode 100644 libraries/eventformatter/impl/src/main/res/values-de/translations.xml create mode 100644 libraries/eventformatter/impl/src/main/res/values-es/translations.xml create mode 100644 libraries/eventformatter/impl/src/main/res/values-it/translations.xml create mode 100644 libraries/eventformatter/impl/src/main/res/values-ro/translations.xml create mode 100644 libraries/eventformatter/impl/src/main/res/values/localazy.xml rename {features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist => libraries/eventformatter/impl/src/test/kotlin/io/element/android/libraries/eventformatter}/impl/DefaultRoomLastMessageFormatterTests.kt (99%) create mode 100644 libraries/eventformatter/test/build.gradle.kts rename {features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl => libraries/eventformatter/test/src/main/kotlin/io/element/android/libraries/eventformatter/test}/FakeRoomLastMessageFormatter.kt (88%) 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..210f7f4cb4 100644 --- a/features/createroom/impl/src/main/res/values-de/translations.xml +++ b/features/createroom/impl/src/main/res/values-de/translations.xml @@ -1,8 +1,9 @@ "Neuer Raum" + "Personen einladen" "Personen hinzufügen" "Privater Raum (nur auf Einladung)" "Raumname" "Thema (optional)" - \ No newline at end of file + 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/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/RoomListPresenter.kt b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListPresenter.kt index e1f37ae5e6..0c54525f00 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 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 93% 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..a7daf55891 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,7 +14,7 @@ * 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 diff --git a/libraries/eventformatter/impl/build.gradle.kts b/libraries/eventformatter/impl/build.gradle.kts new file mode 100644 index 0000000000..d93043efd1 --- /dev/null +++ b/libraries/eventformatter/impl/build.gradle.kts @@ -0,0 +1,49 @@ +/* + * 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.architecture) + implementation(projects.libraries.matrix.api) + implementation(projects.libraries.uiStrings) + api(projects.libraries.eventformatter.api) + + testImplementation(libs.test.junit) + testImplementation(libs.test.robolectric) + testImplementation(libs.test.truth) + testImplementation(projects.libraries.matrix.test) +} diff --git a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/DefaultRoomLastMessageFormatter.kt b/libraries/eventformatter/impl/src/main/kotlin/io/element/android/libraries/eventformatter/impl/DefaultRoomLastMessageFormatter.kt similarity index 99% rename from features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/DefaultRoomLastMessageFormatter.kt rename to libraries/eventformatter/impl/src/main/kotlin/io/element/android/libraries/eventformatter/impl/DefaultRoomLastMessageFormatter.kt index e2971c93d7..4eeef1c62f 100644 --- a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/DefaultRoomLastMessageFormatter.kt +++ b/libraries/eventformatter/impl/src/main/kotlin/io/element/android/libraries/eventformatter/impl/DefaultRoomLastMessageFormatter.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 @@ -49,6 +49,7 @@ import io.element.android.libraries.matrix.api.timeline.item.event.UnableToDecry 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.libraries.eventformatter.api.RoomLastMessageFormatter import timber.log.Timber import javax.inject.Inject import io.element.android.libraries.ui.strings.R as StringR 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 99% 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..8888b2c3eb 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 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 88% 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..2293727718 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,8 +14,9 @@ * 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 { 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..428d42ec14 100644 --- a/libraries/ui-strings/src/main/res/values-de/translations.xml +++ b/libraries/ui-strings/src/main/res/values-de/translations.xml @@ -61,8 +61,11 @@ "Offline" "Passwort" "Reaktionen" + "Suchergebnisse" "Sicherheit" + "Server wird nicht unterstützt" "Einstellungen" + "Chat wird gestartet…" "Sticker" "Erfolg" "Vorschläge" diff --git a/plugins/src/main/kotlin/extension/DependencyHandleScope.kt b/plugins/src/main/kotlin/extension/DependencyHandleScope.kt index c433ed2e07..53fcec10a0 100644 --- a/plugins/src/main/kotlin/extension/DependencyHandleScope.kt +++ b/plugins/src/main/kotlin/extension/DependencyHandleScope.kt @@ -74,6 +74,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 73e09d2314..41390999f0 100644 --- a/samples/minimal/build.gradle.kts +++ b/samples/minimal/build.gradle.kts @@ -55,6 +55,7 @@ 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) 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..b9703aa98a 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,6 +31,7 @@ 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.matrix.api.MatrixClient import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.room.RoomMembershipObserver diff --git a/tools/localazy/config.json b/tools/localazy/config.json index 5219187f75..1e0e125137 100644 --- a/tools/localazy/config.json +++ b/tools/localazy/config.json @@ -56,6 +56,12 @@ "error_no_compatible_app_found" ] }, + { + "name": ":libraries:messageformatter: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_.*" ] From ac636d97379f7f381f1a5c8c1d8af1fa0ee0ff7b Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 16 May 2023 12:21:06 +0200 Subject: [PATCH 02/17] Inject StringProvider instead of context. --- .../eventformatter/impl/build.gradle.kts | 2 + .../impl/DefaultRoomLastMessageFormatter.kt | 132 +++++++++--------- .../DefaultRoomLastMessageFormatterTests.kt | 4 +- samples/minimal/build.gradle.kts | 1 + .../android/samples/minimal/RoomListScreen.kt | 3 +- 5 files changed, 73 insertions(+), 69 deletions(-) diff --git a/libraries/eventformatter/impl/build.gradle.kts b/libraries/eventformatter/impl/build.gradle.kts index d93043efd1..e1a1fdb70a 100644 --- a/libraries/eventformatter/impl/build.gradle.kts +++ b/libraries/eventformatter/impl/build.gradle.kts @@ -40,8 +40,10 @@ dependencies { 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) 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 index 4eeef1c62f..3c84166ddf 100644 --- 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 @@ -16,15 +16,14 @@ package io.element.android.libraries.eventformatter.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.eventformatter.api.RoomLastMessageFormatter 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 @@ -49,15 +48,14 @@ import io.element.android.libraries.matrix.api.timeline.item.event.UnableToDecry 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.libraries.eventformatter.api.RoomLastMessageFormatter +import io.element.android.services.toolbox.api.strings.StringProvider 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 sp: StringProvider, private val matrixClient: MatrixClient, ) : RoomLastMessageFormatter { @@ -67,7 +65,7 @@ class DefaultRoomLastMessageFormatter @Inject constructor( return when (val content = event.content) { is MessageContent -> processMessageContents(content, senderDisplayName, isDmRoom) RedactedContent -> { - val message = context.getString(StringR.string.common_message_removed) + val message = sp.getString(StringR.string.common_message_removed) if (!isDmRoom) { prefix(message, senderDisplayName) } else { @@ -78,7 +76,7 @@ class DefaultRoomLastMessageFormatter @Inject constructor( content.body } is UnableToDecryptContent -> { - val message = context.getString(StringR.string.common_decryption_error) + val message = sp.getString(StringR.string.common_decryption_error) if (!isDmRoom) { prefix(message, senderDisplayName) } else { @@ -95,7 +93,7 @@ class DefaultRoomLastMessageFormatter @Inject constructor( processRoomStateChange(content, senderDisplayName, isOutgoing) } is FailedToParseMessageLikeContent, is FailedToParseStateContent, is UnknownContent -> { - prefixIfNeeded(context.getString(StringR.string.common_unsupported_event), senderDisplayName, isDmRoom) + prefixIfNeeded(sp.getString(StringR.string.common_unsupported_event), senderDisplayName, isDmRoom) } } } @@ -112,19 +110,19 @@ class DefaultRoomLastMessageFormatter @Inject constructor( messageType.body } is VideoMessageType -> { - context.getString(StringR.string.common_video) + sp.getString(StringR.string.common_video) } is ImageMessageType -> { - context.getString(StringR.string.common_image) + sp.getString(StringR.string.common_image) } is FileMessageType -> { - context.getString(StringR.string.common_file) + sp.getString(StringR.string.common_file) } is AudioMessageType -> { - context.getString(StringR.string.common_audio) + sp.getString(StringR.string.common_audio) } UnknownMessageType -> { - context.getString(StringR.string.common_unsupported_event) + sp.getString(StringR.string.common_unsupported_event) } is NoticeMessageType -> { messageType.body @@ -138,73 +136,73 @@ class DefaultRoomLastMessageFormatter @Inject constructor( 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) + sp.getString(R.string.state_event_room_join_by_you) } else { - context.getString(R.string.state_event_room_join, userId.value) + sp.getString(R.string.state_event_room_join, userId.value) } MembershipChange.LEFT -> if (memberIsYou) { - context.getString(R.string.state_event_room_leave_by_you) + sp.getString(R.string.state_event_room_leave_by_you) } else { - context.getString(R.string.state_event_room_leave, userId.value) + sp.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) + sp.getString(R.string.state_event_room_ban_by_you, userId.value) } else { - context.getString(R.string.state_event_room_ban, senderDisplayName, userId.value) + sp.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) + sp.getString(R.string.state_event_room_unban_by_you, userId.value) } else { - context.getString(R.string.state_event_room_unban, senderDisplayName, userId.value) + sp.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) + sp.getString(R.string.state_event_room_remove_by_you, userId.value) } else { - context.getString(R.string.state_event_room_remove, senderDisplayName, userId.value) + sp.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) + sp.getString(R.string.state_event_room_invite_by_you, userId.value) } else if (memberIsYou) { - context.getString(R.string.state_event_room_invite_you, senderDisplayName) + sp.getString(R.string.state_event_room_invite_you, senderDisplayName) } else { - context.getString(R.string.state_event_room_invite, senderDisplayName, userId.value) + sp.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) + sp.getString(R.string.state_event_room_invite_accepted_by_you) } else { - context.getString(R.string.state_event_room_invite_accepted, userId.value) + sp.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) + sp.getString(R.string.state_event_room_reject_by_you) } else { - context.getString(R.string.state_event_room_reject, userId.value) + sp.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) + sp.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) + sp.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) + sp.getString(R.string.state_event_room_knock_by_you) } else { - context.getString(R.string.state_event_room_knock, userId.value) + sp.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) + sp.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) + sp.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) + sp.getString(R.string.state_event_room_knock_retracted_by_you) } else { - context.getString(R.string.state_event_room_knock_retracted, userId.value) + sp.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) + sp.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) + sp.getString(R.string.state_event_room_knock_denied_you, senderDisplayName) } else { - context.getString(R.string.state_event_room_knock_denied, senderDisplayName, userId.value) + sp.getString(R.string.state_event_room_knock_denied, senderDisplayName, userId.value) } else -> { Timber.v("Filtering timeline item for room membership: $membershipContent") @@ -218,27 +216,27 @@ class DefaultRoomLastMessageFormatter @Inject constructor( 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) + 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) { - context.getString(R.string.state_event_room_created_by_you) + sp.getString(R.string.state_event_room_created_by_you) } else { - context.getString(R.string.state_event_room_created, senderDisplayName) + sp.getString(R.string.state_event_room_created, senderDisplayName) } } - is OtherState.RoomEncryption -> context.getString(StringR.string.common_encryption_enabled) + is OtherState.RoomEncryption -> sp.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) + 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 -> { @@ -247,18 +245,18 @@ class DefaultRoomLastMessageFormatter @Inject constructor( return null } if (senderIsYou) { - context.getString(R.string.state_event_room_third_party_invite_by_you, content.displayName) + sp.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) + sp.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) + 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) } } else -> { @@ -278,35 +276,35 @@ class DefaultRoomLastMessageFormatter @Inject constructor( 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) + val avatarChangedToo = sp.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) + sp.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) + sp.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) + sp.getString(R.string.state_event_display_name_set_by_you, displayName) } else { - context.getString(R.string.state_event_display_name_set, senderDisplayName, displayName) + sp.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) + sp.getString(R.string.state_event_display_name_removed_by_you, prevDisplayName) } else { - context.getString(R.string.state_event_display_name_removed, senderDisplayName, prevDisplayName) + sp.getString(R.string.state_event_display_name_removed, senderDisplayName, prevDisplayName) } } } avatarChanged -> { if (senderIsYou) { - context.getString(R.string.state_event_avatar_url_changed_by_you) + sp.getString(R.string.state_event_avatar_url_changed_by_you) } else { - context.getString(R.string.state_event_avatar_url_changed, senderDisplayName) + sp.getString(R.string.state_event_avatar_url_changed, senderDisplayName) } } else -> null diff --git a/libraries/eventformatter/impl/src/test/kotlin/io/element/android/libraries/eventformatter/impl/DefaultRoomLastMessageFormatterTests.kt b/libraries/eventformatter/impl/src/test/kotlin/io/element/android/libraries/eventformatter/impl/DefaultRoomLastMessageFormatterTests.kt index 8888b2c3eb..4fb7fc3e9c 100644 --- a/libraries/eventformatter/impl/src/test/kotlin/io/element/android/libraries/eventformatter/impl/DefaultRoomLastMessageFormatterTests.kt +++ b/libraries/eventformatter/impl/src/test/kotlin/io/element/android/libraries/eventformatter/impl/DefaultRoomLastMessageFormatterTests.kt @@ -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,7 @@ class DefaultRoomLastMessageFormatterTests { fun setup() { context = RuntimeEnvironment.getApplication() as Context fakeMatrixClient = FakeMatrixClient() - formatter = DefaultRoomLastMessageFormatter(context, fakeMatrixClient) + formatter = DefaultRoomLastMessageFormatter(AndroidStringProvider(context.resources), fakeMatrixClient) } @Test @@ -145,6 +146,7 @@ class DefaultRoomLastMessageFormatterTests { fun createMessageContent(type: MessageType): MessageContent { return MessageContent(body, null, false, type) } + val sharedContentMessagesTypes = arrayOf( TextMessageType(body, null), VideoMessageType(body, "url", null), diff --git a/samples/minimal/build.gradle.kts b/samples/minimal/build.gradle.kts index 41390999f0..7d4b222713 100644 --- a/samples/minimal/build.gradle.kts +++ b/samples/minimal/build.gradle.kts @@ -61,6 +61,7 @@ dependencies { 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("com.android.tools:desugar_jdk_libs:2.0.3") } 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 b9703aa98a..ace7572858 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 @@ -35,6 +35,7 @@ import io.element.android.libraries.eventformatter.impl.DefaultRoomLastMessageFo 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 @@ -55,7 +56,7 @@ class RoomListScreen( private val presenter = RoomListPresenter( client = matrixClient, lastMessageTimestampFormatter = DefaultLastMessageTimestampFormatter(dateTimeProvider, dateFormatters), - roomLastMessageFormatter = DefaultRoomLastMessageFormatter(context, matrixClient), + roomLastMessageFormatter = DefaultRoomLastMessageFormatter(AndroidStringProvider(context.resources), matrixClient), sessionVerificationService = sessionVerificationService, networkMonitor = NetworkMonitorImpl(context), snackbarDispatcher = SnackbarDispatcher(), From 69ae145467e06e4681d5195f40da23a6e75fa375 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 16 May 2023 12:23:45 +0200 Subject: [PATCH 03/17] Rename `processMessageItem` to simple `format` --- .../roomlist/impl/RoomListPresenter.kt | 2 +- .../api/RoomLastMessageFormatter.kt | 2 +- .../impl/DefaultRoomLastMessageFormatter.kt | 2 +- .../DefaultRoomLastMessageFormatterTests.kt | 154 +++++++++--------- .../test/FakeRoomLastMessageFormatter.kt | 11 +- 5 files changed, 86 insertions(+), 85 deletions(-) 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 0c54525f00..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 @@ -200,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/libraries/eventformatter/api/src/main/kotlin/io/element/android/libraries/eventformatter/api/RoomLastMessageFormatter.kt b/libraries/eventformatter/api/src/main/kotlin/io/element/android/libraries/eventformatter/api/RoomLastMessageFormatter.kt index a7daf55891..4dd5978bc6 100644 --- a/libraries/eventformatter/api/src/main/kotlin/io/element/android/libraries/eventformatter/api/RoomLastMessageFormatter.kt +++ b/libraries/eventformatter/api/src/main/kotlin/io/element/android/libraries/eventformatter/api/RoomLastMessageFormatter.kt @@ -19,5 +19,5 @@ 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/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 index 3c84166ddf..1f18698026 100644 --- 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 @@ -59,7 +59,7 @@ class DefaultRoomLastMessageFormatter @Inject constructor( private val matrixClient: MatrixClient, ) : RoomLastMessageFormatter { - override fun processMessageItem(event: EventTimelineItem, isDmRoom: Boolean): CharSequence? { + 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) { diff --git a/libraries/eventformatter/impl/src/test/kotlin/io/element/android/libraries/eventformatter/impl/DefaultRoomLastMessageFormatterTests.kt b/libraries/eventformatter/impl/src/test/kotlin/io/element/android/libraries/eventformatter/impl/DefaultRoomLastMessageFormatterTests.kt index 4fb7fc3e9c..3016ff4884 100644 --- a/libraries/eventformatter/impl/src/test/kotlin/io/element/android/libraries/eventformatter/impl/DefaultRoomLastMessageFormatterTests.kt +++ b/libraries/eventformatter/impl/src/test/kotlin/io/element/android/libraries/eventformatter/impl/DefaultRoomLastMessageFormatterTests.kt @@ -77,7 +77,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 { @@ -93,7 +93,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) } @@ -104,7 +104,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 { @@ -126,7 +126,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 { @@ -165,7 +165,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 { @@ -173,7 +173,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 { @@ -237,11 +237,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") } @@ -253,11 +253,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") } @@ -271,19 +271,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}") } @@ -295,11 +295,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}") } @@ -311,11 +311,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}") } @@ -327,15 +327,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}") } @@ -347,11 +347,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") } @@ -363,11 +363,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") } @@ -378,11 +378,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") } @@ -394,11 +394,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") } @@ -409,11 +409,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") } @@ -425,11 +425,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") } @@ -441,15 +441,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") } @@ -461,7 +461,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 } @@ -480,19 +480,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") } @@ -503,11 +503,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") } @@ -518,11 +518,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") } @@ -535,19 +535,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") } @@ -560,19 +560,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() } @@ -585,19 +585,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") } @@ -613,7 +613,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 } @@ -635,35 +635,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() } @@ -680,35 +680,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() } @@ -737,15 +737,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/src/main/kotlin/io/element/android/libraries/eventformatter/test/FakeRoomLastMessageFormatter.kt b/libraries/eventformatter/test/src/main/kotlin/io/element/android/libraries/eventformatter/test/FakeRoomLastMessageFormatter.kt index 2293727718..cd723a27af 100644 --- a/libraries/eventformatter/test/src/main/kotlin/io/element/android/libraries/eventformatter/test/FakeRoomLastMessageFormatter.kt +++ b/libraries/eventformatter/test/src/main/kotlin/io/element/android/libraries/eventformatter/test/FakeRoomLastMessageFormatter.kt @@ -21,12 +21,13 @@ import io.element.android.libraries.matrix.api.timeline.item.event.EventTimeline 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 } } From bbe4b7d26aa69d519da348034a10ca0f2117e49e Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 16 May 2023 15:14:30 +0200 Subject: [PATCH 04/17] Process state event in timeline - WIP --- features/messages/impl/build.gradle.kts | 1 + .../event/TimelineItemContentFactory.kt | 12 +++--- ...TimelineItemContentProfileChangeFactory.kt | 15 ++++--- ...imelineItemContentRoomMembershipFactory.kt | 15 ++++--- .../event/TimelineItemContentStateFactory.kt | 15 ++++--- .../event/TimelineItemEventFactory.kt | 2 +- .../event/TimelineItemProfileChangeContent.kt | 26 ++++++++++++ .../TimelineItemRoomMembershipContent.kt | 26 ++++++++++++ .../event/TimelineItemStateEventContent.kt | 26 ++++++++++++ .../api/TimelineEventFormatter.kt | 23 ++++++++++ .../impl/DefaultTimelineEventFormatter.kt | 42 +++++++++++++++++++ 11 files changed, 181 insertions(+), 22 deletions(-) create mode 100644 features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemProfileChangeContent.kt create mode 100644 features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemRoomMembershipContent.kt create mode 100644 features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemStateEventContent.kt create mode 100644 libraries/eventformatter/api/src/main/kotlin/io/element/android/libraries/eventformatter/api/TimelineEventFormatter.kt create mode 100644 libraries/eventformatter/impl/src/main/kotlin/io/element/android/libraries/eventformatter/impl/DefaultTimelineEventFormatter.kt 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/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/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..29836dc28c --- /dev/null +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemProfileChangeContent.kt @@ -0,0 +1,26 @@ +/* + * 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 + +import org.jsoup.nodes.Document + +data class TimelineItemProfileChangeContent( + override val body: String, + override val htmlDocument: Document? = null +) : TimelineItemTextBasedContent { + 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..c18393b7a0 --- /dev/null +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemRoomMembershipContent.kt @@ -0,0 +1,26 @@ +/* + * 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 + +import org.jsoup.nodes.Document + +data class TimelineItemRoomMembershipContent( + override val body: String, + override val htmlDocument: Document? = null +) : TimelineItemTextBasedContent { + override val type: String = "TimelineItemRoomMembershipContent" +} 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..8ac3e5a40b --- /dev/null +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemStateEventContent.kt @@ -0,0 +1,26 @@ +/* + * 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 + +import org.jsoup.nodes.Document + +data class TimelineItemStateEventContent( + override val body: String, + override val htmlDocument: Document? = null +) : TimelineItemTextBasedContent { + override val type: String = "TimelineItemStateEventContent" +} 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/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..74219997ce --- /dev/null +++ b/libraries/eventformatter/impl/src/main/kotlin/io/element/android/libraries/eventformatter/impl/DefaultTimelineEventFormatter.kt @@ -0,0 +1,42 @@ +/* + * 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.di.SessionScope +import io.element.android.libraries.eventformatter.api.RoomLastMessageFormatter +import io.element.android.libraries.eventformatter.api.TimelineEventFormatter +import io.element.android.libraries.matrix.api.timeline.item.event.EventTimelineItem +import javax.inject.Inject + +/** + * For now use the same formatter than for the room list using [RoomLastMessageFormatter]. + * We will change this if we want to have a different rendering in the timeline. + */ +@ContributesBinding(SessionScope::class) +class DefaultTimelineEventFormatter @Inject constructor( + private val roomLastMessageFormatter: RoomLastMessageFormatter, +) : TimelineEventFormatter { + + override fun format(event: EventTimelineItem): CharSequence? { + return roomLastMessageFormatter.format( + event, + /* We do not want to distinguish DM and room here */ + isDmRoom = false, + ) + } +} From be478cadaa75071da7d079183e0ba7c4e7e0c2a6 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 16 May 2023 17:18:19 +0200 Subject: [PATCH 05/17] Extract to sub classes --- .../eventformatter/impl/build.gradle.kts | 1 + .../impl/DefaultRoomLastMessageFormatter.kt | 193 +-------------- .../impl/DefaultTimelineEventFormatter.kt | 58 ++++- .../impl/ProfileChangeContentFormatter.kt | 70 ++++++ .../impl/RoomMembershipContentFormatter.kt | 113 +++++++++ .../impl/StateContentFormatter.kt | 219 ++++++++++++++++++ .../eventformatter/impl/mode/RenderingMode.kt | 22 ++ .../android/samples/minimal/RoomListScreen.kt | 12 +- 8 files changed, 490 insertions(+), 198 deletions(-) create mode 100644 libraries/eventformatter/impl/src/main/kotlin/io/element/android/libraries/eventformatter/impl/ProfileChangeContentFormatter.kt create mode 100644 libraries/eventformatter/impl/src/main/kotlin/io/element/android/libraries/eventformatter/impl/RoomMembershipContentFormatter.kt create mode 100644 libraries/eventformatter/impl/src/main/kotlin/io/element/android/libraries/eventformatter/impl/StateContentFormatter.kt create mode 100644 libraries/eventformatter/impl/src/main/kotlin/io/element/android/libraries/eventformatter/impl/mode/RenderingMode.kt diff --git a/libraries/eventformatter/impl/build.gradle.kts b/libraries/eventformatter/impl/build.gradle.kts index e1a1fdb70a..3bee2df488 100644 --- a/libraries/eventformatter/impl/build.gradle.kts +++ b/libraries/eventformatter/impl/build.gradle.kts @@ -37,6 +37,7 @@ dependencies { anvil(projects.anvilcodegen) implementation(projects.anvilannotations) + implementation(projects.libraries.core) implementation(projects.libraries.architecture) implementation(projects.libraries.matrix.api) implementation(projects.libraries.uiStrings) 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 index 1f18698026..327c18cb34 100644 --- 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 @@ -24,6 +24,7 @@ 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 @@ -32,11 +33,9 @@ import io.element.android.libraries.matrix.api.timeline.item.event.FailedToParse 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 @@ -49,7 +48,6 @@ import io.element.android.libraries.matrix.api.timeline.item.event.UnknownConten 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 timber.log.Timber import javax.inject.Inject import io.element.android.libraries.ui.strings.R as StringR @@ -57,6 +55,9 @@ import io.element.android.libraries.ui.strings.R as StringR 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? { @@ -84,13 +85,13 @@ class DefaultRoomLastMessageFormatter @Inject constructor( } } is RoomMembershipContent -> { - processRoomMembershipChange(content, senderDisplayName, isOutgoing) + roomMembershipContentFormatter.format(content, senderDisplayName, isOutgoing) } is ProfileChangeContent -> { - processProfileChangeContent(content, senderDisplayName, isOutgoing) + profileChangeContentFormatter.format(content, senderDisplayName, isOutgoing) } is StateContent -> { - processRoomStateChange(content, senderDisplayName, isOutgoing) + stateContentFormatter.format(content, senderDisplayName, isOutgoing, RenderingMode.RoomList) } is FailedToParseMessageLikeContent, is FailedToParseStateContent, is UnknownContent -> { prefixIfNeeded(sp.getString(StringR.string.common_unsupported_event), senderDisplayName, isDmRoom) @@ -131,186 +132,6 @@ class DefaultRoomLastMessageFormatter @Inject constructor( 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) { - 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 - } - } - } - - 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 -> 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(StringR.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) - } - } - 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 = 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 - } - } - private fun prefixIfNeeded(message: String, senderDisplayName: String, isDmRoom: Boolean): CharSequence = if (isDmRoom) { message } else { 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 index 74219997ce..4bcae94c1e 100644 --- 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 @@ -17,26 +17,62 @@ 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.RoomLastMessageFormatter 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 -/** - * For now use the same formatter than for the room list using [RoomLastMessageFormatter]. - * We will change this if we want to have a different rendering in the timeline. - */ @ContributesBinding(SessionScope::class) class DefaultTimelineEventFormatter @Inject constructor( - private val roomLastMessageFormatter: RoomLastMessageFormatter, + 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? { - return roomLastMessageFormatter.format( - event, - /* We do not want to distinguish DM and room here */ - isDmRoom = false, - ) + 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/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 ace7572858..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 @@ -32,6 +32,9 @@ import io.element.android.libraries.dateformatter.impl.DefaultLastMessageTimesta 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 @@ -53,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(AndroidStringProvider(context.resources), matrixClient), + roomLastMessageFormatter = DefaultRoomLastMessageFormatter( + sp = stringProvider, + matrixClient = matrixClient, + roomMembershipContentFormatter = RoomMembershipContentFormatter(matrixClient, stringProvider), + profileChangeContentFormatter = ProfileChangeContentFormatter(stringProvider), + stateContentFormatter = StateContentFormatter(stringProvider), + ), sessionVerificationService = sessionVerificationService, networkMonitor = NetworkMonitorImpl(context), snackbarDispatcher = SnackbarDispatcher(), From e81d20ec2ed2b3deff7e07f1a15eccccc302dff5 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 17 May 2023 11:21:14 +0200 Subject: [PATCH 06/17] Render State Event in the timeline. --- .../messages/impl/MessagesStateProvider.kt | 4 +- .../impl/timeline/TimelineStateProvider.kt | 24 +++-- .../messages/impl/timeline/TimelineView.kt | 68 +++++++++++-- .../timeline/components/MessageEventBubble.kt | 1 + .../components/MessageStateEventContainer.kt | 99 +++++++++++++++++++ .../event/TimelineItemContentView.kt | 5 + .../components/event/TimelineItemStateView.kt | 58 +++++++++++ .../event/TimelineItemEventContentProvider.kt | 4 + .../event/TimelineItemProfileChangeContent.kt | 5 +- .../TimelineItemRoomMembershipContent.kt | 5 +- .../model/event/TimelineItemStateContent.kt | 21 ++++ .../event/TimelineItemStateEventContent.kt | 5 +- 12 files changed, 266 insertions(+), 33 deletions(-) create mode 100644 features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/MessageStateEventContainer.kt create mode 100644 features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemStateView.kt create mode 100644 features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemStateContent.kt 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/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..954b1321f7 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 @@ -55,6 +55,7 @@ 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.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.virtual.TimelineItemDaySeparatorView @@ -63,6 +64,7 @@ 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 @@ -130,11 +132,12 @@ fun TimelineItemRow( onLongClick: (TimelineItem.Event) -> Unit, ) { when (timelineItem) { - is TimelineItem.Virtual -> TimelineItemVirtualRow( - virtual = timelineItem - ) + is TimelineItem.Virtual -> { + TimelineItemVirtualRow( + virtual = timelineItem + ) + } is TimelineItem.Event -> { - fun onClick() { onClick(timelineItem) } @@ -143,12 +146,21 @@ fun TimelineItemRow( onLongClick(timelineItem) } - TimelineItemEventRow( - event = timelineItem, - isHighlighted = isHighlighted, - onClick = ::onClick, - onLongClick = ::onLongClick - ) + if (timelineItem.content is TimelineItemStateContent) { + TimelineItemStateEventRow( + event = timelineItem, + isHighlighted = isHighlighted, + onClick = ::onClick, + onLongClick = ::onLongClick + ) + } else { + TimelineItemEventRow( + event = timelineItem, + isHighlighted = isHighlighted, + onClick = ::onClick, + onLongClick = ::onLongClick + ) + } } } } @@ -232,6 +244,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/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 index 29836dc28c..7d56394893 100644 --- 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 @@ -16,11 +16,8 @@ package io.element.android.features.messages.impl.timeline.model.event -import org.jsoup.nodes.Document - data class TimelineItemProfileChangeContent( override val body: String, - override val htmlDocument: Document? = null -) : TimelineItemTextBasedContent { +) : 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 index c18393b7a0..93607f01e6 100644 --- 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 @@ -16,11 +16,8 @@ package io.element.android.features.messages.impl.timeline.model.event -import org.jsoup.nodes.Document - data class TimelineItemRoomMembershipContent( override val body: String, - override val htmlDocument: Document? = null -) : TimelineItemTextBasedContent { +) : 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 index 8ac3e5a40b..1c656c9b96 100644 --- 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 @@ -16,11 +16,8 @@ package io.element.android.features.messages.impl.timeline.model.event -import org.jsoup.nodes.Document - data class TimelineItemStateEventContent( override val body: String, - override val htmlDocument: Document? = null -) : TimelineItemTextBasedContent { +) : TimelineItemStateContent { override val type: String = "TimelineItemStateEventContent" } From e886312ff6ad2f60633f903bf526c4f7b0136822 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 17 May 2023 17:16:01 +0200 Subject: [PATCH 07/17] No actions (yet) for State Event. --- .../impl/actionlist/ActionListPresenter.kt | 30 +++++++++++-------- 1 file changed, 18 insertions(+), 12 deletions(-) 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) + } } } } From f0550e354ab0b96f053892cf21fde7053f09adde Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 22 May 2023 13:53:06 +0200 Subject: [PATCH 08/17] Group some state events in the timeline --- .../src/main/res/values-de/translations.xml | 2 +- .../features/messages/impl/MessagesView.kt | 13 +- .../messages/impl/timeline/TimelineEvents.kt | 2 + .../impl/timeline/TimelinePresenter.kt | 13 +- .../messages/impl/timeline/TimelineView.kt | 45 +++++- .../components/group/GroupHeaderView.kt | 134 ++++++++++++++++++ .../timeline/groups/TimelineItemGrouper.kt | 82 +++++++++++ .../impl/timeline/model/TimelineItem.kt | 12 ++ .../src/main/res/values-es/translations.xml | 7 + .../src/main/res/values-it/translations.xml | 7 + .../src/main/res/values-ro/translations.xml | 8 ++ .../impl/src/main/res/values/localazy.xml | 4 + .../src/main/res/values-de/translations.xml | 9 +- .../src/main/res/values-es/translations.xml | 4 - .../src/main/res/values-it/translations.xml | 4 - .../src/main/res/values-ro/translations.xml | 5 - .../src/main/res/values/localazy.xml | 8 +- tools/localazy/config.json | 5 +- 18 files changed, 332 insertions(+), 32 deletions(-) create mode 100644 features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/group/GroupHeaderView.kt create mode 100644 features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/groups/TimelineItemGrouper.kt create mode 100644 features/messages/impl/src/main/res/values-es/translations.xml create mode 100644 features/messages/impl/src/main/res/values-it/translations.xml create mode 100644 features/messages/impl/src/main/res/values-ro/translations.xml 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 210f7f4cb4..6c3a7c8130 100644 --- a/features/createroom/impl/src/main/res/values-de/translations.xml +++ b/features/createroom/impl/src/main/res/values-de/translations.xml @@ -6,4 +6,4 @@ "Privater Raum (nur auf Einladung)" "Raumname" "Thema (optional)" - + \ No newline at end of file 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/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/TimelineView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineView.kt index 954b1321f7..4b8a1b4869 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 @@ -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, + ) + } } } 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..d952d573f9 --- /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.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 = {} + ) + } +} 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..1ec382a4e5 --- /dev/null +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/groups/TimelineItemGrouper.kt @@ -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, 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. + 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 + } + } +} 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/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/libraries/ui-strings/src/main/res/values-de/translations.xml b/libraries/ui-strings/src/main/res/values-de/translations.xml index 428d42ec14..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,6 +63,7 @@ "Offline" "Passwort" "Reaktionen" + "Fehler melden" "Suchergebnisse" "Sicherheit" "Server wird nicht unterstützt" @@ -87,6 +90,7 @@ "Reisen & Orte" "Symbole" "Fehler beim Laden der Nachrichten" + "Einige Nachrichten wurden nicht gesendet" "Entschuldigung, ein Fehler ist aufgetreten." "%1$s Android" @@ -96,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/tools/localazy/config.json b/tools/localazy/config.json index 1e0e125137..ef7110a160 100644 --- a/tools/localazy/config.json +++ b/tools/localazy/config.json @@ -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_.*", From bab276f7e3e6453f36f7c7481eb29006f46ac853 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 22 May 2023 15:26:30 +0200 Subject: [PATCH 09/17] Record screenshots --- ...Group_GroupHeaderViewDarkPreview_0_null,NEXUS_5,1.0,en].png | 3 +++ ...roup_GroupHeaderViewLightPreview_0_null,NEXUS_5,1.0,en].png | 3 +++ 2 files changed, 6 insertions(+) create mode 100644 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 create mode 100644 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 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..2cd38edf69 --- /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:a7aa84af08d14990c5a28f0fff6ec28cec7881393c4e2116ec50a2bffb3c647a +size 53307 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..8a2b055cca --- /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:7609a858f1d08750e0007c27b1855cb3eddc623ebde17e66d3f8175d5f68562b +size 52390 From bf3d7207deb59e01f0618061fb66c5ee7b786ae6 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 22 May 2023 17:21:16 +0200 Subject: [PATCH 10/17] Animate collapse/expand change --- .../messages/impl/timeline/TimelineView.kt | 43 ++++++++++--------- 1 file changed, 23 insertions(+), 20 deletions(-) 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 4b8a1b4869..7c607ca8d4 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,6 +16,7 @@ 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 @@ -173,29 +174,31 @@ fun TimelineItemRow( onExpandGroupClick(timelineItem) } - if (timelineItem.expanded) { - Column { - timelineItem.events.forEach { subGroupEvent -> - TimelineItemRow( - timelineItem = subGroupEvent, - highlightedItem = highlightedItem, - onClick = onClick, - onLongClick = onLongClick, - onExpandGroupClick = {} - ) + 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 = {} + ) + } } } } - 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, - ) } } } From 4b2f0b1328394f5d5557a7839e9daa813e4bc181 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 22 May 2023 17:23:53 +0200 Subject: [PATCH 11/17] Better icon for this use case. --- .../impl/timeline/components/group/GroupHeaderView.kt | 8 ++++---- ..._GroupHeaderViewDarkPreview_0_null,NEXUS_5,1.0,en].png | 4 ++-- ...GroupHeaderViewLightPreview_0_null,NEXUS_5,1.0,en].png | 4 ++-- 3 files changed, 8 insertions(+), 8 deletions(-) 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 index d952d573f9..e747134405 100644 --- 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 @@ -25,8 +25,8 @@ 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.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 @@ -83,9 +83,9 @@ fun GroupHeaderView( fontSize = 13.sp ) val icon = if (isExpanded) { - Icons.Default.ArrowDropDown + Icons.Default.ExpandLess } else { - Icons.Default.ArrowRight + Icons.Default.ExpandMore } Icon(icon, "", tint = MaterialTheme.colorScheme.secondary) } 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 index 2cd38edf69..fc37edc2ef 100644 --- 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 @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a7aa84af08d14990c5a28f0fff6ec28cec7881393c4e2116ec50a2bffb3c647a -size 53307 +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 index 8a2b055cca..f638023723 100644 --- 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 @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7609a858f1d08750e0007c27b1855cb3eddc623ebde17e66d3f8175d5f68562b -size 52390 +oid sha256:6c90a40ad84d4ee108ddbd94fb7dfb9de0203f792c51409565b146f26fcf3763 +size 53225 From c47ce5bc1cc13c8b2e214854af7264241221e1b8 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 22 May 2023 17:27:31 +0200 Subject: [PATCH 12/17] This is default value when `reverseLayout = true` --- .../android/features/messages/impl/timeline/TimelineView.kt | 3 --- 1 file changed, 3 deletions(-) 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 7c607ca8d4..c9168fd369 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 @@ -18,7 +18,6 @@ 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 @@ -100,8 +99,6 @@ fun TimelineView( LazyColumn( modifier = Modifier.fillMaxSize(), state = lazyListState, - horizontalAlignment = Alignment.Start, - verticalArrangement = Arrangement.Bottom, reverseLayout = true ) { itemsIndexed( From d702d3d265084cde65618db86d812434ca3c79ae Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 23 May 2023 11:26:02 +0200 Subject: [PATCH 13/17] Fix test compilation --- .../timeline/groups/TimelineItemGrouper.kt | 6 +-- .../messages/MessagesPresenterTest.kt | 2 + .../messages/fixtures/timelineItemsFactory.kt | 51 ++++++++++++------- .../MessageComposerPresenterTest.kt | 3 ++ .../timeline/TimelinePresenterTest.kt | 4 ++ .../DefaultRoomLastMessageFormatterTests.kt | 9 +++- 6 files changed, 52 insertions(+), 23 deletions(-) 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 index 1ec382a4e5..622400ee93 100644 --- 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 @@ -32,10 +32,10 @@ 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() { + /** + * 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() 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..e8fe681692 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,6 +23,7 @@ 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.libraries.matrix.test.AN_EVENT_ID import io.element.android.libraries.matrix.test.room.FakeMatrixRoom import kotlinx.coroutines.test.runTest @@ -33,6 +34,7 @@ class TimelinePresenterTest { fun `present - initial state`() = runTest { val presenter = TimelinePresenter( timelineItemsFactory = aTimelineItemsFactory(), + timelineItemGrouper = TimelineItemGrouper(), room = FakeMatrixRoom(), ) moleculeFlow(RecompositionClock.Immediate) { @@ -49,6 +51,7 @@ class TimelinePresenterTest { fun `present - load more`() = runTest { val presenter = TimelinePresenter( timelineItemsFactory = aTimelineItemsFactory(), + timelineItemGrouper = TimelineItemGrouper(), room = FakeMatrixRoom(), ) moleculeFlow(RecompositionClock.Immediate) { @@ -71,6 +74,7 @@ class TimelinePresenterTest { fun `present - set highlighted event`() = runTest { val presenter = TimelinePresenter( timelineItemsFactory = aTimelineItemsFactory(), + timelineItemGrouper = TimelineItemGrouper(), room = FakeMatrixRoom(), ) moleculeFlow(RecompositionClock.Immediate) { diff --git a/libraries/eventformatter/impl/src/test/kotlin/io/element/android/libraries/eventformatter/impl/DefaultRoomLastMessageFormatterTests.kt b/libraries/eventformatter/impl/src/test/kotlin/io/element/android/libraries/eventformatter/impl/DefaultRoomLastMessageFormatterTests.kt index 3016ff4884..6e8eb3d81c 100644 --- a/libraries/eventformatter/impl/src/test/kotlin/io/element/android/libraries/eventformatter/impl/DefaultRoomLastMessageFormatterTests.kt +++ b/libraries/eventformatter/impl/src/test/kotlin/io/element/android/libraries/eventformatter/impl/DefaultRoomLastMessageFormatterTests.kt @@ -67,7 +67,14 @@ class DefaultRoomLastMessageFormatterTests { fun setup() { context = RuntimeEnvironment.getApplication() as Context fakeMatrixClient = FakeMatrixClient() - formatter = DefaultRoomLastMessageFormatter(AndroidStringProvider(context.resources), 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 From d601dffa88dbea9b98088dcc8621a74caaa0fd3d Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 23 May 2023 11:30:13 +0200 Subject: [PATCH 14/17] Fix missing modifier parameter. --- .../features/messages/impl/timeline/TimelineView.kt | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) 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 c9168fd369..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 @@ -134,11 +134,13 @@ fun TimelineItemRow( onClick: (TimelineItem.Event) -> Unit, onLongClick: (TimelineItem.Event) -> Unit, onExpandGroupClick: (TimelineItem.GroupedEvents) -> Unit, + modifier: Modifier = Modifier ) { when (timelineItem) { is TimelineItem.Virtual -> { TimelineItemVirtualRow( - virtual = timelineItem + virtual = timelineItem, + modifier = modifier, ) } is TimelineItem.Event -> { @@ -155,14 +157,16 @@ fun TimelineItemRow( event = timelineItem, isHighlighted = highlightedItem == timelineItem.identifier(), onClick = ::onClick, - onLongClick = ::onLongClick + onLongClick = ::onLongClick, + modifier = modifier, ) } else { TimelineItemEventRow( event = timelineItem, isHighlighted = highlightedItem == timelineItem.identifier(), onClick = ::onClick, - onLongClick = ::onLongClick + onLongClick = ::onLongClick, + modifier = modifier, ) } } @@ -171,7 +175,7 @@ fun TimelineItemRow( onExpandGroupClick(timelineItem) } - Column(modifier = Modifier.animateContentSize()) { + Column(modifier = modifier.animateContentSize()) { GroupHeaderView( text = pluralStringResource( id = R.plurals.room_timeline_state_changes, From 02842d978f761d20c54556fe158809f5a26cabf0 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 25 May 2023 14:39:39 +0200 Subject: [PATCH 15/17] Record screenshot --- ..._DefaultGroup_AvatarDarkPreview_0_null,NEXUS_5,1.0,en].png | 4 ++-- ...DefaultGroup_AvatarLightPreview_0_null,NEXUS_5,1.0,en].png | 4 ++-- ...elineItemImageViewDarkPreview_0_null_1,NEXUS_5,1.0,en].png | 4 ++-- ...lineItemImageViewLightPreview_0_null_1,NEXUS_5,1.0,en].png | 4 ++-- ...imelineItemStateViewDarkPreview_0_null,NEXUS_5,1.0,en].png | 3 +++ ...melineItemStateViewLightPreview_0_null,NEXUS_5,1.0,en].png | 3 +++ ...eStateEventContainerDarkPreview_0_null,NEXUS_5,1.0,en].png | 3 +++ ...StateEventContainerLightPreview_0_null,NEXUS_5,1.0,en].png | 3 +++ ...agesReactionButtonDarkPreview_0_null_0,NEXUS_5,1.0,en].png | 4 ++-- ...agesReactionButtonDarkPreview_0_null_1,NEXUS_5,1.0,en].png | 4 ++-- ...agesReactionButtonDarkPreview_0_null_2,NEXUS_5,1.0,en].png | 4 ++-- ...agesReactionButtonDarkPreview_0_null_3,NEXUS_5,1.0,en].png | 4 ++-- ...gesReactionButtonLightPreview_0_null_0,NEXUS_5,1.0,en].png | 4 ++-- ...gesReactionButtonLightPreview_0_null_1,NEXUS_5,1.0,en].png | 4 ++-- ...gesReactionButtonLightPreview_0_null_2,NEXUS_5,1.0,en].png | 4 ++-- ...gesReactionButtonLightPreview_0_null_3,NEXUS_5,1.0,en].png | 4 ++-- ...ineItemReactionsViewDarkPreview_0_null,NEXUS_5,1.0,en].png | 4 ++-- ...neItemReactionsViewLightPreview_0_null,NEXUS_5,1.0,en].png | 4 ++-- ...Group_TimelineViewDarkPreview_0_null_0,NEXUS_5,1.0,en].png | 4 ++-- ...Group_TimelineViewDarkPreview_0_null_1,NEXUS_5,1.0,en].png | 4 ++-- ...Group_TimelineViewDarkPreview_0_null_2,NEXUS_5,1.0,en].png | 4 ++-- ...Group_TimelineViewDarkPreview_0_null_3,NEXUS_5,1.0,en].png | 4 ++-- ...Group_TimelineViewDarkPreview_0_null_4,NEXUS_5,1.0,en].png | 4 ++-- ...Group_TimelineViewDarkPreview_0_null_5,NEXUS_5,1.0,en].png | 4 ++-- ...roup_TimelineViewLightPreview_0_null_0,NEXUS_5,1.0,en].png | 4 ++-- ...roup_TimelineViewLightPreview_0_null_1,NEXUS_5,1.0,en].png | 4 ++-- ...roup_TimelineViewLightPreview_0_null_2,NEXUS_5,1.0,en].png | 4 ++-- ...roup_TimelineViewLightPreview_0_null_3,NEXUS_5,1.0,en].png | 4 ++-- ...roup_TimelineViewLightPreview_0_null_4,NEXUS_5,1.0,en].png | 4 ++-- ...roup_TimelineViewLightPreview_0_null_5,NEXUS_5,1.0,en].png | 4 ++-- ...Group_MessagesViewDarkPreview_0_null_0,NEXUS_5,1.0,en].png | 4 ++-- ...Group_MessagesViewDarkPreview_0_null_1,NEXUS_5,1.0,en].png | 4 ++-- ...Group_MessagesViewDarkPreview_0_null_2,NEXUS_5,1.0,en].png | 4 ++-- ...Group_MessagesViewDarkPreview_0_null_3,NEXUS_5,1.0,en].png | 4 ++-- ...roup_MessagesViewLightPreview_0_null_0,NEXUS_5,1.0,en].png | 4 ++-- ...roup_MessagesViewLightPreview_0_null_1,NEXUS_5,1.0,en].png | 4 ++-- ...roup_MessagesViewLightPreview_0_null_2,NEXUS_5,1.0,en].png | 4 ++-- ...roup_MessagesViewLightPreview_0_null_3,NEXUS_5,1.0,en].png | 4 ++-- ...oup_OnBoardingScreenDarkPreview_0_null,NEXUS_5,1.0,en].png | 4 ++-- ...up_OnBoardingScreenLightPreview_0_null,NEXUS_5,1.0,en].png | 4 ++-- ...roup_BugReportViewDarkPreview_0_null_1,NEXUS_5,1.0,en].png | 2 +- ...oup_BugReportViewLightPreview_0_null_1,NEXUS_5,1.0,en].png | 4 ++-- 42 files changed, 87 insertions(+), 75 deletions(-) create mode 100644 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 create mode 100644 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 create mode 100644 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 create mode 100644 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 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_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 From d899a8e881751c6d1db8a3421fcddd1946d1047d Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 25 May 2023 17:32:55 +0200 Subject: [PATCH 16/17] Add test for TimelineItemGrouper and fix a bug: the last group if any was not added to the list. --- .../timeline/groups/TimelineItemGrouper.kt | 35 ++-- .../groups/TimelineItemGrouperTest.kt | 170 ++++++++++++++++++ 2 files changed, 194 insertions(+), 11 deletions(-) create mode 100644 features/messages/impl/src/test/kotlin/io/element/android/features/messages/timeline/groups/TimelineItemGrouperTest.kt 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 index 622400ee93..3cd1b8fd4a 100644 --- 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 @@ -46,22 +46,15 @@ class TimelineItemGrouper @Inject constructor() { // 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() - ) - ) - } + result.addGroup(currentGroup, expandedGroups) currentGroup.clear() } result.add(timelineItem) } } + if (currentGroup.isNotEmpty()) { + result.addGroup(currentGroup, expandedGroups) + } return result } @@ -80,3 +73,23 @@ class TimelineItemGrouper @Inject constructor() { } } } + +/** + * 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/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() + ) + ) + ) + } +} From be105f501c906e924cc65ba7adb1397ce34c6c73 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 25 May 2023 18:10:32 +0200 Subject: [PATCH 17/17] Add test for TimelinePresenter: Collapse and expand group. --- .../timeline/TimelinePresenterTest.kt | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) 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 e8fe681692..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 @@ -24,8 +24,12 @@ 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 @@ -91,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() + } + } }