diff --git a/CHANGES.md b/CHANGES.md
index c2c2641b34..2661597461 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -1,3 +1,80 @@
+Changes in Element X v0.5.0 (2024-07-24)
+=========================================
+
+### 🙌 Improvements
+* Add icon for "Mark as read" and "Mark as unread" actions. by @bmarty in https://github.com/element-hq/element-x-android/pull/3144
+* Add support for Picture In Picture for Element Call by @bmarty in https://github.com/element-hq/element-x-android/pull/3159
+* Set pin grace period to 2 minutes by @bmarty in https://github.com/element-hq/element-x-android/pull/3172
+* Unify the way we decide whether a room is a DM or a group room by @jmartinesp in https://github.com/element-hq/element-x-android/pull/3100
+* Subscribe to `RoomListItems` in the visible range by @jmartinesp in https://github.com/element-hq/element-x-android/pull/3169
+* Improve pip and add feature flag. by @bmarty in https://github.com/element-hq/element-x-android/pull/3199
+* Open Source licenses: add color for links. by @bmarty in https://github.com/element-hq/element-x-android/pull/3215
+* Cancel ringing call notification on call cancellation by @jmartinesp in https://github.com/element-hq/element-x-android/pull/3047
+
+### 🐛 Bugfixes
+* Fix `MainActionButton` layout for long texts by @jmartinesp in https://github.com/element-hq/element-x-android/pull/3158
+* Always follow the desired theme for Pin, Incoming Call and Element Call screens by @bmarty in https://github.com/element-hq/element-x-android/pull/3165
+* Fix empty screen issue after clearing the cache by @bmarty in https://github.com/element-hq/element-x-android/pull/3163
+* Restore intentional mentions in the markdown/plain text editor by @jmartinesp in https://github.com/element-hq/element-x-android/pull/3193
+* Fix crash in the room list after a forced log out in background by @jmartinesp in https://github.com/element-hq/element-x-android/pull/3180
+* Clear existing notification when a room is marked as read by @bmarty in https://github.com/element-hq/element-x-android/pull/3203
+* Fix crash when Pin code screen is displayed by @bmarty in https://github.com/element-hq/element-x-android/pull/3205
+* Fix pillification not working for non formatted message bodies by @jmartinesp in https://github.com/element-hq/element-x-android/pull/3201
+* Update grammar on Matrix Ids to be more spec compliant and render error instead of infinite loading in room member list screen by @bmarty in https://github.com/element-hq/element-x-android/pull/3206
+* Reduce the risk of text truncation in buttons. by @bmarty in https://github.com/element-hq/element-x-android/pull/3209
+* Ensure that the manual dark theme is rendering correctly regarding -night resource and keyboard by @bmarty in https://github.com/element-hq/element-x-android/pull/3216
+* Fix rendering issue of SunsetPage in dark mode by @bmarty in https://github.com/element-hq/element-x-android/pull/3217
+* Fix linkification not working for `Spanned` strings in text messages by @jmartinesp in https://github.com/element-hq/element-x-android/pull/3233
+* Edit : fallback to room.edit when timeline item is not found. by @ganfra in https://github.com/element-hq/element-x-android/pull/3239
+
+### 🗣 Translations
+* Sync Strings by @ElementBot in https://github.com/element-hq/element-x-android/pull/3156
+* Sync Strings by @ElementBot in https://github.com/element-hq/element-x-android/pull/3192
+* Sync Strings by @ElementBot in https://github.com/element-hq/element-x-android/pull/3232
+
+### 🧱 Build
+* Remove Showkase processor not found warning from Danger by @jmartinesp in https://github.com/element-hq/element-x-android/pull/3148
+* Set targetSDK to 34 by @bmarty in https://github.com/element-hq/element-x-android/pull/3149
+* Add a local copy of `inplace-fix.py` and `fix-pg-map-id.py` by @bmarty in https://github.com/element-hq/element-x-android/pull/3167
+* Only add private SSH keys and clone submodules in the original repo by @jmartinesp in https://github.com/element-hq/element-x-android/pull/3225
+* Fix CI for forks by @jmartinesp in https://github.com/element-hq/element-x-android/pull/3226
+
+### Dependency upgrades
+* Update dependency io.element.android:compound-android to v0.0.7 by @renovate in https://github.com/element-hq/element-x-android/pull/3143
+* Update dependency org.matrix.rustcomponents:sdk-android to v0.2.31 by @renovate in https://github.com/element-hq/element-x-android/pull/3145
+* Update dependency com.squareup:kotlinpoet to v1.18.0 by @renovate in https://github.com/element-hq/element-x-android/pull/3150
+* Update dependency org.robolectric:robolectric to v4.13 by @renovate in https://github.com/element-hq/element-x-android/pull/3157
+* Update plugin dependencycheck to v10.0.2 by @renovate in https://github.com/element-hq/element-x-android/pull/3154
+* Update wysiwyg to v2.37.5 by @renovate in https://github.com/element-hq/element-x-android/pull/3162
+* Update plugin sonarqube to v5.1.0.4882 by @renovate in https://github.com/element-hq/element-x-android/pull/3139
+* Update dependency org.jsoup:jsoup to v1.18.1 by @renovate in https://github.com/element-hq/element-x-android/pull/3171
+* Update dependency com.google.firebase:firebase-bom to v33.1.2 by @renovate in https://github.com/element-hq/element-x-android/pull/3178
+* Update telephoto to v0.12.0 by @renovate in https://github.com/element-hq/element-x-android/pull/3191
+* Update dependency com.google.truth:truth to v1.4.4 by @renovate in https://github.com/element-hq/element-x-android/pull/3187
+* Update dependency com.squareup:kotlinpoet to v1.18.1 by @renovate in https://github.com/element-hq/element-x-android/pull/3194
+* Update dependency io.mockk:mockk to v1.13.12 by @renovate in https://github.com/element-hq/element-x-android/pull/3198
+* Update dependency io.sentry:sentry-android to v7.12.0 by @renovate in https://github.com/element-hq/element-x-android/pull/3200
+* Update plugin dependencycheck to v10.0.3 by @renovate in https://github.com/element-hq/element-x-android/pull/3204
+* Update dependency gradle to v8.9 by @renovate in https://github.com/element-hq/element-x-android/pull/3177
+* Update dependency org.matrix.rustcomponents:sdk-android to v0.2.32 by @renovate in https://github.com/element-hq/element-x-android/pull/3202
+* Update coil to v2.7.0 by @renovate in https://github.com/element-hq/element-x-android/pull/3210
+* Update dependency org.matrix.rustcomponents:sdk-android to v0.2.33 by @renovate in https://github.com/element-hq/element-x-android/pull/3220
+* Update wysiwyg to v2.37.7 by @renovate in https://github.com/element-hq/element-x-android/pull/3218
+* Update telephoto to v0.12.1 by @renovate in https://github.com/element-hq/element-x-android/pull/3230
+* Update dependency org.matrix.rustcomponents:sdk-android to v0.2.34 by @renovate in https://github.com/element-hq/element-x-android/pull/3237
+
+### Others
+* Reduce delay when selecting room list filters by @jmartinesp in https://github.com/element-hq/element-x-android/pull/3160
+* Add `--alignment-preserved true` when signing APK for F-Droid. by @bmarty in https://github.com/element-hq/element-x-android/pull/3161
+* Ensure that all callback plugins are invoked. by @bmarty in https://github.com/element-hq/element-x-android/pull/3146
+* Add generated screen to show open source licenses in Gplay variant by @bmarty in https://github.com/element-hq/element-x-android/pull/3207
+* Performance : improve time to open a room. by @ganfra in https://github.com/element-hq/element-x-android/pull/3186
+* Add logging to help debug forced logout issues by @jmartinesp in https://github.com/element-hq/element-x-android/pull/3208
+* Use the right filename for log files so they're sorted in rageshakes by @jmartinesp in https://github.com/element-hq/element-x-android/pull/3219
+* Compose : add immutability to some Reaction classes by @ganfra in https://github.com/element-hq/element-x-android/pull/3224
+* Fix stickers display text on room summary by @surakin in https://github.com/element-hq/element-x-android/pull/3221
+* Rework FakeMatrixRoom so that it contains only lambdas. by @bmarty in https://github.com/element-hq/element-x-android/pull/3229
+
Changes in Element X v0.4.16 (2024-07-05)
=========================================
diff --git a/app/src/main/res/xml/locales_config.xml b/app/src/main/res/xml/locales_config.xml
index 2884d43c88..fa0e1e2199 100644
--- a/app/src/main/res/xml/locales_config.xml
+++ b/app/src/main/res/xml/locales_config.xml
@@ -13,7 +13,9 @@
+
+
diff --git a/appnav/src/test/kotlin/io/element/android/appnav/JoinRoomLoadedFlowNodeTest.kt b/appnav/src/test/kotlin/io/element/android/appnav/JoinRoomLoadedFlowNodeTest.kt
index d307f851cd..b1009f20da 100644
--- a/appnav/src/test/kotlin/io/element/android/appnav/JoinRoomLoadedFlowNodeTest.kt
+++ b/appnav/src/test/kotlin/io/element/android/appnav/JoinRoomLoadedFlowNodeTest.kt
@@ -123,7 +123,9 @@ class JoinRoomLoadedFlowNodeTest {
@Test
fun `given a room flow node when initialized then it loads messages entry point`() = runTest {
// GIVEN
- val room = FakeMatrixRoom()
+ val room = FakeMatrixRoom(
+ updateMembersResult = { }
+ )
val fakeMessagesEntryPoint = FakeMessagesEntryPoint()
val inputs = JoinedRoomLoadedFlowNode.Inputs(room, RoomNavigationTarget.Messages())
val roomFlowNode = createJoinedRoomLoadedFlowNode(
@@ -144,7 +146,9 @@ class JoinRoomLoadedFlowNodeTest {
@Test
fun `given a room flow node when callback on room details is triggered then it loads room details entry point`() = runTest {
// GIVEN
- val room = FakeMatrixRoom()
+ val room = FakeMatrixRoom(
+ updateMembersResult = { }
+ )
val fakeMessagesEntryPoint = FakeMessagesEntryPoint()
val fakeRoomDetailsEntryPoint = FakeRoomDetailsEntryPoint()
val inputs = JoinedRoomLoadedFlowNode.Inputs(room, RoomNavigationTarget.Messages())
diff --git a/appnav/src/test/kotlin/io/element/android/appnav/loggedin/SendQueuesTest.kt b/appnav/src/test/kotlin/io/element/android/appnav/loggedin/SendQueuesTest.kt
index bb4e8f3dbf..91f0aee517 100644
--- a/appnav/src/test/kotlin/io/element/android/appnav/loggedin/SendQueuesTest.kt
+++ b/appnav/src/test/kotlin/io/element/android/appnav/loggedin/SendQueuesTest.kt
@@ -33,7 +33,6 @@ import org.junit.Test
@OptIn(ExperimentalCoroutinesApi::class) class SendQueuesTest {
private val matrixClient = FakeMatrixClient()
- private val room = FakeMatrixRoom()
private val networkMonitor = FakeNetworkMonitor()
private val sut = SendQueues(matrixClient, networkMonitor)
@@ -43,11 +42,11 @@ import org.junit.Test
val setAllSendQueuesEnabledLambda = lambdaRecorder { _: Boolean -> }
matrixClient.sendQueueDisabledFlow = sendQueueDisabledFlow
matrixClient.setAllSendQueuesEnabledLambda = setAllSendQueuesEnabledLambda
- matrixClient.givenGetRoomResult(room.roomId, room)
-
val setRoomSendQueueEnabledLambda = lambdaRecorder { _: Boolean -> }
- room.setSendQueueEnabledLambda = setRoomSendQueueEnabledLambda
-
+ val room = FakeMatrixRoom(
+ setSendQueueEnabledLambda = setRoomSendQueueEnabledLambda
+ )
+ matrixClient.givenGetRoomResult(room.roomId, room)
sut.launchIn(backgroundScope)
sendQueueDisabledFlow.emit(room.roomId)
@@ -72,10 +71,11 @@ import org.junit.Test
matrixClient.sendQueueDisabledFlow = sendQueueDisabledFlow
matrixClient.setAllSendQueuesEnabledLambda = setAllSendQueuesEnabledLambda
networkMonitor.connectivity.value = NetworkStatus.Offline
- matrixClient.givenGetRoomResult(room.roomId, room)
-
val setRoomSendQueueEnabledLambda = lambdaRecorder { _: Boolean -> }
- room.setSendQueueEnabledLambda = setRoomSendQueueEnabledLambda
+ val room = FakeMatrixRoom(
+ setSendQueueEnabledLambda = setRoomSendQueueEnabledLambda
+ )
+ matrixClient.givenGetRoomResult(room.roomId, room)
sut.launchIn(backgroundScope)
diff --git a/fastlane/metadata/android/en-US/changelogs/40005000.txt b/fastlane/metadata/android/en-US/changelogs/40005000.txt
new file mode 100644
index 0000000000..dd8c30a1b9
--- /dev/null
+++ b/fastlane/metadata/android/en-US/changelogs/40005000.txt
@@ -0,0 +1,2 @@
+Main changes in this version: mostly bug fixes and performance improvements.
+Full changelog: https://github.com/element-hq/element-x-android/releases
\ No newline at end of file
diff --git a/features/analytics/api/src/main/res/values-pl/translations.xml b/features/analytics/api/src/main/res/values-pl/translations.xml
new file mode 100644
index 0000000000..26aa9c6073
--- /dev/null
+++ b/features/analytics/api/src/main/res/values-pl/translations.xml
@@ -0,0 +1,7 @@
+
+
+ "Udostępniaj anonimowe dane dotyczące użytkowania, aby pomóc nam identyfikować problemy."
+ "Możesz przeczytać wszystkie nasze warunki %1$s."
+ "tutaj"
+ "Udostępniaj dane analityczne"
+
diff --git a/features/analytics/api/src/main/res/values-pt-rBR/translations.xml b/features/analytics/api/src/main/res/values-pt-rBR/translations.xml
new file mode 100644
index 0000000000..4e7dc9775f
--- /dev/null
+++ b/features/analytics/api/src/main/res/values-pt-rBR/translations.xml
@@ -0,0 +1,7 @@
+
+
+ "Compartilhe dados de uso anônimos para nos ajudar a identificar problemas."
+ "Você pode ler todos os nossos termos %1$s ."
+ "aqui"
+ "Compartilhar dados de utilização"
+
diff --git a/features/analytics/impl/src/main/res/values-pl/translations.xml b/features/analytics/impl/src/main/res/values-pl/translations.xml
new file mode 100644
index 0000000000..99e3e441bb
--- /dev/null
+++ b/features/analytics/impl/src/main/res/values-pl/translations.xml
@@ -0,0 +1,10 @@
+
+
+ "Nie będziemy rejestrować ani profilować żadnych danych osobistych"
+ "Udostępniaj anonimowe dane dotyczące użytkowania, aby pomóc nam identyfikować problemy."
+ "Możesz przeczytać wszystkie nasze warunki %1$s."
+ "tutaj"
+ "Możesz to wyłączyć w dowolnym momencie"
+ "Nie będziemy udostępniać Twoich danych podmiotom trzecim"
+ "Pomóż nam ulepszyć %1$s"
+
diff --git a/features/analytics/impl/src/main/res/values-pt-rBR/translations.xml b/features/analytics/impl/src/main/res/values-pt-rBR/translations.xml
new file mode 100644
index 0000000000..b2fe6e00bc
--- /dev/null
+++ b/features/analytics/impl/src/main/res/values-pt-rBR/translations.xml
@@ -0,0 +1,10 @@
+
+
+ "Não registraremos nem criaremos perfil baseado em qualquer dado pessoal"
+ "Compartilhe dados de uso anônimos para nos ajudar a identificar problemas."
+ "Você pode ler todos os nossos termos %1$s ."
+ "aqui"
+ "Você pode desativar isso a qualquer momento"
+ "Não compartilharemos seus dados com terceiros"
+ "Ajude a melhorar o %1$s"
+
diff --git a/features/call/impl/src/main/res/values-de/translations.xml b/features/call/impl/src/main/res/values-de/translations.xml
index d58a616780..6429dbe956 100644
--- a/features/call/impl/src/main/res/values-de/translations.xml
+++ b/features/call/impl/src/main/res/values-de/translations.xml
@@ -3,4 +3,5 @@
"Laufender Anruf"
"Tippen, um zum Anruf zurückzukehren"
"☎️ Anruf läuft"
+ "Eingehender Element Call"
diff --git a/features/call/impl/src/main/res/values-pl/translations.xml b/features/call/impl/src/main/res/values-pl/translations.xml
new file mode 100644
index 0000000000..27133cd91b
--- /dev/null
+++ b/features/call/impl/src/main/res/values-pl/translations.xml
@@ -0,0 +1,6 @@
+
+
+ "Połączenie w trakcie"
+ "Stuknij, aby wrócić do rozmowy"
+ "☎️ Rozmowa w toku"
+
diff --git a/features/call/impl/src/main/res/values-pt-rBR/translations.xml b/features/call/impl/src/main/res/values-pt-rBR/translations.xml
new file mode 100644
index 0000000000..c3d90e4bb0
--- /dev/null
+++ b/features/call/impl/src/main/res/values-pt-rBR/translations.xml
@@ -0,0 +1,6 @@
+
+
+ "Chamada em andamento"
+ "Toque para retornar à chamada"
+ "☎️ Chamada em andamento"
+
diff --git a/features/call/impl/src/test/kotlin/io/element/android/features/call/utils/DefaultCallWidgetProviderTest.kt b/features/call/impl/src/test/kotlin/io/element/android/features/call/utils/DefaultCallWidgetProviderTest.kt
index 7eb426c35e..a0c2f9a80e 100644
--- a/features/call/impl/src/test/kotlin/io/element/android/features/call/utils/DefaultCallWidgetProviderTest.kt
+++ b/features/call/impl/src/test/kotlin/io/element/android/features/call/utils/DefaultCallWidgetProviderTest.kt
@@ -54,9 +54,9 @@ class DefaultCallWidgetProviderTest {
@Test
fun `getWidget - fails if it can't generate the URL for the widget`() = runTest {
- val room = FakeMatrixRoom().apply {
- givenGenerateWidgetWebViewUrlResult(Result.failure(Exception("Can't generate URL for widget")))
- }
+ val room = FakeMatrixRoom(
+ generateWidgetWebViewUrlResult = { _, _, _, _ -> Result.failure(Exception("Can't generate URL for widget")) }
+ )
val client = FakeMatrixClient().apply {
givenGetRoomResult(A_ROOM_ID, room)
}
@@ -66,10 +66,10 @@ class DefaultCallWidgetProviderTest {
@Test
fun `getWidget - fails if it can't get the widget driver`() = runTest {
- val room = FakeMatrixRoom().apply {
- givenGenerateWidgetWebViewUrlResult(Result.success("url"))
- givenGetWidgetDriverResult(Result.failure(Exception("Can't get a widget driver")))
- }
+ val room = FakeMatrixRoom(
+ generateWidgetWebViewUrlResult = { _, _, _, _ -> Result.success("url") },
+ getWidgetDriverResult = { Result.failure(Exception("Can't get a widget driver")) }
+ )
val client = FakeMatrixClient().apply {
givenGetRoomResult(A_ROOM_ID, room)
}
@@ -79,10 +79,10 @@ class DefaultCallWidgetProviderTest {
@Test
fun `getWidget - returns a widget driver when all steps are successful`() = runTest {
- val room = FakeMatrixRoom().apply {
- givenGenerateWidgetWebViewUrlResult(Result.success("url"))
- givenGetWidgetDriverResult(Result.success(FakeMatrixWidgetDriver()))
- }
+ val room = FakeMatrixRoom(
+ generateWidgetWebViewUrlResult = { _, _, _, _ -> Result.success("url") },
+ getWidgetDriverResult = { Result.success(FakeMatrixWidgetDriver()) },
+ )
val client = FakeMatrixClient().apply {
givenGetRoomResult(A_ROOM_ID, room)
}
@@ -92,10 +92,10 @@ class DefaultCallWidgetProviderTest {
@Test
fun `getWidget - will use a custom base url if it exists`() = runTest {
- val room = FakeMatrixRoom().apply {
- givenGenerateWidgetWebViewUrlResult(Result.success("url"))
- givenGetWidgetDriverResult(Result.success(FakeMatrixWidgetDriver()))
- }
+ val room = FakeMatrixRoom(
+ generateWidgetWebViewUrlResult = { _, _, _, _ -> Result.success("url") },
+ getWidgetDriverResult = { Result.success(FakeMatrixWidgetDriver()) },
+ )
val client = FakeMatrixClient().apply {
givenGetRoomResult(A_ROOM_ID, room)
}
@@ -120,10 +120,10 @@ class DefaultCallWidgetProviderTest {
val elementCallBaseUrlProvider = FakeElementCallBaseUrlProvider { matrixClient ->
providesLambda(matrixClient)
}
- val room = FakeMatrixRoom().apply {
- givenGenerateWidgetWebViewUrlResult(Result.success("url"))
- givenGetWidgetDriverResult(Result.success(FakeMatrixWidgetDriver()))
- }
+ val room = FakeMatrixRoom(
+ generateWidgetWebViewUrlResult = { _, _, _, _ -> Result.success("url") },
+ getWidgetDriverResult = { Result.success(FakeMatrixWidgetDriver()) },
+ )
val client = FakeMatrixClient().apply {
givenGetRoomResult(A_ROOM_ID, room)
}
diff --git a/features/createroom/impl/src/main/res/values-pl/translations.xml b/features/createroom/impl/src/main/res/values-pl/translations.xml
new file mode 100644
index 0000000000..eb64b46d8c
--- /dev/null
+++ b/features/createroom/impl/src/main/res/values-pl/translations.xml
@@ -0,0 +1,14 @@
+
+
+ "Nowy pokój"
+ "Zaproś znajomych"
+ "Wystąpił błąd podczas tworzenia pokoju"
+ "Wiadomości w tym pokoju są szyfrowane. Szyfrowania nie można później wyłączyć."
+ "Pokój prywatny (tylko zaproszenie)"
+ "Wiadomości nie są szyfrowane i każdy może je odczytać. Możesz aktywować szyfrowanie później."
+ "Pokój publiczny (każdy)"
+ "Nazwa pokoju"
+ "Utwórz pokój"
+ "Temat (opcjonalnie)"
+ "Wystąpił błąd podczas próby rozpoczęcia czatu"
+
diff --git a/features/createroom/impl/src/main/res/values-pt-rBR/translations.xml b/features/createroom/impl/src/main/res/values-pt-rBR/translations.xml
new file mode 100644
index 0000000000..bc61411edc
--- /dev/null
+++ b/features/createroom/impl/src/main/res/values-pt-rBR/translations.xml
@@ -0,0 +1,14 @@
+
+
+ "Nova sala"
+ "Convidar pessoas"
+ "Ocorreu um erro ao criar a sala"
+ "As mensagens nesta sala serão criptografadas. A criptografia não pode ser desativada posteriormente."
+ "Sala privativa (somente por convite)"
+ "As mensagens não serão criptografadas e qualquer pessoa pode lê-las. Você pode ativar a criptografia posteriormente."
+ "Sala pública (qualquer pessoa)"
+ "Nome da sala"
+ "Criar uma sala"
+ "Tópico (opcional)"
+ "Ocorreu um erro ao tentar iniciar um chat"
+
diff --git a/features/ftue/impl/src/main/res/values-pl/translations.xml b/features/ftue/impl/src/main/res/values-pl/translations.xml
new file mode 100644
index 0000000000..b1c1909ac2
--- /dev/null
+++ b/features/ftue/impl/src/main/res/values-pl/translations.xml
@@ -0,0 +1,11 @@
+
+
+ "Możesz zmienić ustawienia później."
+ "Zezwól na powiadomienia i nie przegap żadnej wiadomości"
+ "Połączenia, ankiety, wyszukiwanie i inne zostaną dodane później w tym roku."
+ "Historia wiadomości dla pokoi szyfrowanych nie jest jeszcze dostępna."
+ "Chętnie poznamy Twoją opinię. Daj nam znać, co myślisz na stronie ustawień."
+ "Naprzód!"
+ "Oto, co musisz wiedzieć:"
+ "Witamy w %1$s!"
+
diff --git a/features/ftue/impl/src/main/res/values-pt-rBR/translations.xml b/features/ftue/impl/src/main/res/values-pt-rBR/translations.xml
new file mode 100644
index 0000000000..1528027bd4
--- /dev/null
+++ b/features/ftue/impl/src/main/res/values-pt-rBR/translations.xml
@@ -0,0 +1,11 @@
+
+
+ "Você pode alterar suas configurações mais tarde."
+ "Permita notificações e nunca perca uma mensagem"
+ "Chamadas, enquetes, pesquisa e muito mais serão adicionadas ainda este ano."
+ "O histórico de mensagens para salas criptografadas ainda não está disponível."
+ "Adoraríamos ouvir sua opinião. Deixe-nos saber o que você pensa através da página de configurações."
+ "Vamos lá!"
+ "Aqui está o que você precisa saber:"
+ "Bem-vindo ao %1$s!"
+
diff --git a/features/invite/impl/src/main/kotlin/io/element/android/features/invite/impl/response/AcceptDeclineInvitePresenter.kt b/features/invite/impl/src/main/kotlin/io/element/android/features/invite/impl/response/AcceptDeclineInvitePresenter.kt
index 73a82a71f5..95bebc07e6 100644
--- a/features/invite/impl/src/main/kotlin/io/element/android/features/invite/impl/response/AcceptDeclineInvitePresenter.kt
+++ b/features/invite/impl/src/main/kotlin/io/element/android/features/invite/impl/response/AcceptDeclineInvitePresenter.kt
@@ -33,6 +33,7 @@ import io.element.android.libraries.architecture.runCatchingUpdatingState
import io.element.android.libraries.architecture.runUpdatingState
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.toRoomIdOrAlias
import io.element.android.libraries.matrix.api.room.join.JoinRoom
import io.element.android.libraries.push.api.notifications.NotificationCleaner
import kotlinx.coroutines.CoroutineScope
@@ -107,7 +108,7 @@ class AcceptDeclineInvitePresenter @Inject constructor(
) = launch {
acceptedAction.runUpdatingState {
joinRoom(
- roomId = roomId,
+ roomIdOrAlias = roomId.toRoomIdOrAlias(),
serverNames = emptyList(),
trigger = JoinedRoom.Trigger.Invite,
)
diff --git a/features/invite/impl/src/main/res/values-pl/translations.xml b/features/invite/impl/src/main/res/values-pl/translations.xml
new file mode 100644
index 0000000000..e33b1ae106
--- /dev/null
+++ b/features/invite/impl/src/main/res/values-pl/translations.xml
@@ -0,0 +1,9 @@
+
+
+ "Czy na pewno chcesz odrzucić zaproszenie do dołączenia do %1$s?"
+ "Odrzuć zaproszenie"
+ "Czy na pewno chcesz odrzucić rozmowę prywatną z %1$s?"
+ "Odrzuć czat"
+ "Brak zaproszeń"
+ "%1$s (%2$s) zaprosił Cię"
+
diff --git a/features/invite/impl/src/main/res/values-pt-rBR/translations.xml b/features/invite/impl/src/main/res/values-pt-rBR/translations.xml
new file mode 100644
index 0000000000..9cd284690c
--- /dev/null
+++ b/features/invite/impl/src/main/res/values-pt-rBR/translations.xml
@@ -0,0 +1,9 @@
+
+
+ "Tem certeza de que deseja recusar o convite para ingressar em %1$s?"
+ "Recusar convite"
+ "Tem certeza de que deseja recusar esse chat privado com %1$s?"
+ "Recusar chat"
+ "Sem convites"
+ "%1$s(%2$s) convidou você"
+
diff --git a/features/invite/impl/src/test/kotlin/io/element/android/features/invite/impl/response/AcceptDeclineInvitePresenterTest.kt b/features/invite/impl/src/test/kotlin/io/element/android/features/invite/impl/response/AcceptDeclineInvitePresenterTest.kt
index 9a12e33b33..239ac9c597 100644
--- a/features/invite/impl/src/test/kotlin/io/element/android/features/invite/impl/response/AcceptDeclineInvitePresenterTest.kt
+++ b/features/invite/impl/src/test/kotlin/io/element/android/features/invite/impl/response/AcceptDeclineInvitePresenterTest.kt
@@ -23,7 +23,9 @@ import io.element.android.features.invite.api.response.InviteData
import io.element.android.libraries.architecture.AsyncAction
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.RoomIdOrAlias
import io.element.android.libraries.matrix.api.core.SessionId
+import io.element.android.libraries.matrix.api.core.toRoomIdOrAlias
import io.element.android.libraries.matrix.test.A_ROOM_ID
import io.element.android.libraries.matrix.test.A_ROOM_NAME
import io.element.android.libraries.matrix.test.A_SESSION_ID
@@ -92,9 +94,9 @@ class AcceptDeclineInvitePresenterTest {
val client = FakeMatrixClient().apply {
givenGetRoomResult(
roomId = A_ROOM_ID,
- result = FakeMatrixRoom().apply {
+ result = FakeMatrixRoom(
leaveRoomLambda = declineInviteFailure
- }
+ )
)
}
val presenter = createAcceptDeclineInvitePresenter(client = client)
@@ -142,9 +144,9 @@ class AcceptDeclineInvitePresenterTest {
val client = FakeMatrixClient().apply {
givenGetRoomResult(
roomId = A_ROOM_ID,
- result = FakeMatrixRoom().apply {
+ result = FakeMatrixRoom(
leaveRoomLambda = declineInviteSuccess
- }
+ )
)
}
val presenter = createAcceptDeclineInvitePresenter(
@@ -178,8 +180,8 @@ class AcceptDeclineInvitePresenterTest {
@Test
fun `present - accepting invite error flow`() = runTest {
- val joinRoomFailure = lambdaRecorder { roomId: RoomId, _: List, _: JoinedRoom.Trigger ->
- Result.failure(RuntimeException("Failed to join room $roomId"))
+ val joinRoomFailure = lambdaRecorder { roomIdOrAlias: RoomIdOrAlias, _: List, _: JoinedRoom.Trigger ->
+ Result.failure(RuntimeException("Failed to join room $roomIdOrAlias"))
}
val presenter = createAcceptDeclineInvitePresenter(joinRoomLambda = joinRoomFailure)
presenter.test {
@@ -208,7 +210,7 @@ class AcceptDeclineInvitePresenterTest {
assert(joinRoomFailure)
.isCalledOnce()
.with(
- value(A_ROOM_ID),
+ value(A_ROOM_ID.toRoomIdOrAlias()),
value(emptyList()),
value(JoinedRoom.Trigger.Invite)
)
@@ -222,7 +224,7 @@ class AcceptDeclineInvitePresenterTest {
val fakeNotificationCleaner = FakeNotificationCleaner(
clearMembershipNotificationForRoomLambda = clearMembershipNotificationForRoomLambda
)
- val joinRoomSuccess = lambdaRecorder { _: RoomId, _: List, _: JoinedRoom.Trigger ->
+ val joinRoomSuccess = lambdaRecorder { _: RoomIdOrAlias, _: List, _: JoinedRoom.Trigger ->
Result.success(Unit)
}
val presenter = createAcceptDeclineInvitePresenter(
@@ -248,7 +250,7 @@ class AcceptDeclineInvitePresenterTest {
assert(joinRoomSuccess)
.isCalledOnce()
.with(
- value(A_ROOM_ID),
+ value(A_ROOM_ID.toRoomIdOrAlias()),
value(emptyList()),
value(JoinedRoom.Trigger.Invite)
)
@@ -271,7 +273,7 @@ class AcceptDeclineInvitePresenterTest {
private fun createAcceptDeclineInvitePresenter(
client: MatrixClient = FakeMatrixClient(),
- joinRoomLambda: (RoomId, List, JoinedRoom.Trigger) -> Result = { _, _, _ ->
+ joinRoomLambda: (RoomIdOrAlias, List, JoinedRoom.Trigger) -> Result = { _, _, _ ->
Result.success(Unit)
},
notificationCleaner: NotificationCleaner = FakeNotificationCleaner(),
diff --git a/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/JoinRoomPresenter.kt b/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/JoinRoomPresenter.kt
index bba2a82a34..3fb4659c96 100644
--- a/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/JoinRoomPresenter.kt
+++ b/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/JoinRoomPresenter.kt
@@ -96,7 +96,7 @@ class JoinRoomPresenter @AssistedInject constructor(
}
else -> {
value = ContentState.Loading(roomIdOrAlias)
- val result = matrixClient.getRoomPreviewFromRoomId(roomId, serverNames)
+ val result = matrixClient.getRoomPreview(roomIdOrAlias, serverNames)
value = result.fold(
onSuccess = { roomPreview ->
roomPreview.toContentState()
@@ -153,7 +153,7 @@ class JoinRoomPresenter @AssistedInject constructor(
private fun CoroutineScope.joinRoom(joinAction: MutableState>) = launch {
joinAction.runUpdatingState {
joinRoom.invoke(
- roomId = roomId,
+ roomIdOrAlias = roomIdOrAlias,
serverNames = serverNames,
trigger = trigger
)
diff --git a/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/JoinRoomState.kt b/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/JoinRoomState.kt
index ab66d0d80c..f71ece39af 100644
--- a/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/JoinRoomState.kt
+++ b/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/JoinRoomState.kt
@@ -37,7 +37,11 @@ data class JoinRoomState(
val eventSink: (JoinRoomEvents) -> Unit
) {
val joinAuthorisationStatus = when (contentState) {
+ // Use the join authorisation status from the loaded content state
is ContentState.Loaded -> contentState.joinAuthorisationStatus
+ // Assume that if the room is unknown, the user can join it
+ is ContentState.UnknownRoom -> JoinAuthorisationStatus.CanJoin
+ // Otherwise assume that the user can't join the room
else -> JoinAuthorisationStatus.Unknown
}
}
diff --git a/features/joinroom/impl/src/test/kotlin/io/element/android/features/joinroom/impl/JoinRoomPresenterTest.kt b/features/joinroom/impl/src/test/kotlin/io/element/android/features/joinroom/impl/JoinRoomPresenterTest.kt
index 7a6e08244c..b636e1497c 100644
--- a/features/joinroom/impl/src/test/kotlin/io/element/android/features/joinroom/impl/JoinRoomPresenterTest.kt
+++ b/features/joinroom/impl/src/test/kotlin/io/element/android/features/joinroom/impl/JoinRoomPresenterTest.kt
@@ -29,6 +29,7 @@ import io.element.android.libraries.core.meta.BuildMeta
import io.element.android.libraries.matrix.api.MatrixClient
import io.element.android.libraries.matrix.api.core.RoomAlias
import io.element.android.libraries.matrix.api.core.RoomId
+import io.element.android.libraries.matrix.api.core.RoomIdOrAlias
import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.api.core.toRoomIdOrAlias
import io.element.android.libraries.matrix.api.room.CurrentUserMembership
@@ -180,7 +181,7 @@ class JoinRoomPresenterTest {
@Test
fun `present - when room is joined with success, all the parameters are provided`() = runTest {
val aTrigger = JoinedRoom.Trigger.MobilePermalink
- val joinRoomLambda = lambdaRecorder { _: RoomId, _: List, _: JoinedRoom.Trigger ->
+ val joinRoomLambda = lambdaRecorder { _: RoomIdOrAlias, _: List, _: JoinedRoom.Trigger ->
Result.success(Unit)
}
val presenter = createJoinRoomPresenter(
@@ -201,7 +202,7 @@ class JoinRoomPresenterTest {
}
joinRoomLambda.assertions()
.isCalledOnce()
- .with(value(A_ROOM_ID), value(A_SERVER_LIST), value(aTrigger))
+ .with(value(A_ROOM_ID.toRoomIdOrAlias()), value(A_SERVER_LIST), value(aTrigger))
}
}
@@ -366,7 +367,7 @@ class JoinRoomPresenterTest {
@Test
fun `present - when room is not known RoomPreview is loaded`() = runTest {
val client = FakeMatrixClient(
- getRoomPreviewFromRoomIdResult = { _, _ ->
+ getRoomPreviewResult = { _, _ ->
Result.success(
RoomPreview(
roomId = A_ROOM_ID,
@@ -411,7 +412,7 @@ class JoinRoomPresenterTest {
@Test
fun `present - when room is not known RoomPreview is loaded with error`() = runTest {
val client = FakeMatrixClient(
- getRoomPreviewFromRoomIdResult = { _, _ ->
+ getRoomPreviewResult = { _, _ ->
Result.failure(AN_EXCEPTION)
}
)
@@ -449,7 +450,7 @@ class JoinRoomPresenterTest {
@Test
fun `present - when room is not known RoomPreview is loaded with error 403`() = runTest {
val client = FakeMatrixClient(
- getRoomPreviewFromRoomIdResult = { _, _ ->
+ getRoomPreviewResult = { _, _ ->
Result.failure(Exception("403"))
}
)
@@ -474,7 +475,7 @@ class JoinRoomPresenterTest {
serverNames: List = emptyList(),
trigger: JoinedRoom.Trigger = JoinedRoom.Trigger.Invite,
matrixClient: MatrixClient = FakeMatrixClient(),
- joinRoomLambda: (RoomId, List, JoinedRoom.Trigger) -> Result = { _, _, _ ->
+ joinRoomLambda: (RoomIdOrAlias, List, JoinedRoom.Trigger) -> Result = { _, _, _ ->
Result.success(Unit)
},
knockRoom: KnockRoom = FakeKnockRoom(),
diff --git a/features/leaveroom/api/src/main/res/values-pl/translations.xml b/features/leaveroom/api/src/main/res/values-pl/translations.xml
new file mode 100644
index 0000000000..0fd8769389
--- /dev/null
+++ b/features/leaveroom/api/src/main/res/values-pl/translations.xml
@@ -0,0 +1,7 @@
+
+
+ "Czy na pewno chcesz opuścić tę konwersację? Konwersacja nie jest publiczna i nie będziesz mógł dołączyć ponownie bez zaproszenia."
+ "Jesteś pewien, że chcesz opuścić ten pokój? Jesteś tu jedyną osobą. Jeśli wyjdziesz, nikt nie będzie mógł dołączyć, w tym Ty."
+ "Czy na pewno chcesz opuścić ten pokój? Ten pokój nie jest publiczny i nie będziesz mógł do niego wrócić bez zaproszenia."
+ "Jesteś pewien, że chcesz wyjść z tego pokoju?"
+
diff --git a/features/leaveroom/api/src/main/res/values-pt-rBR/translations.xml b/features/leaveroom/api/src/main/res/values-pt-rBR/translations.xml
new file mode 100644
index 0000000000..bb4f35d07f
--- /dev/null
+++ b/features/leaveroom/api/src/main/res/values-pt-rBR/translations.xml
@@ -0,0 +1,6 @@
+
+
+ "Tem certeza de que deseja sair desta sala? Você é a única pessoa aqui. Se você sair, ninguém poderá ingressar no futuro, inclusive você."
+ "Tem certeza de que deseja sair desta sala? Esta sala não é pública e você não poderá entrar novamente sem um convite."
+ "Tem certeza de que deseja sair da sala?"
+
diff --git a/features/leaveroom/impl/src/test/kotlin/io/element/android/features/leaveroom/impl/DefaultLeaveRoomPresenterTest.kt b/features/leaveroom/impl/src/test/kotlin/io/element/android/features/leaveroom/impl/DefaultLeaveRoomPresenterTest.kt
index 49b44851de..a063b9ca90 100644
--- a/features/leaveroom/impl/src/test/kotlin/io/element/android/features/leaveroom/impl/DefaultLeaveRoomPresenterTest.kt
+++ b/features/leaveroom/impl/src/test/kotlin/io/element/android/features/leaveroom/impl/DefaultLeaveRoomPresenterTest.kt
@@ -140,7 +140,9 @@ class DefaultLeaveRoomPresenterTest {
client = FakeMatrixClient().apply {
givenGetRoomResult(
roomId = A_ROOM_ID,
- result = FakeMatrixRoom(),
+ result = FakeMatrixRoom(
+ leaveRoomLambda = { Result.success(Unit) }
+ ),
)
},
roomMembershipObserver = roomMembershipObserver
@@ -162,9 +164,9 @@ class DefaultLeaveRoomPresenterTest {
client = FakeMatrixClient().apply {
givenGetRoomResult(
roomId = A_ROOM_ID,
- result = FakeMatrixRoom().apply {
- this.leaveRoomLambda = { Result.failure(RuntimeException("Blimey!")) }
- },
+ result = FakeMatrixRoom(
+ leaveRoomLambda = { Result.failure(RuntimeException("Blimey!")) }
+ ),
)
}
)
@@ -186,7 +188,9 @@ class DefaultLeaveRoomPresenterTest {
client = FakeMatrixClient().apply {
givenGetRoomResult(
roomId = A_ROOM_ID,
- result = FakeMatrixRoom(),
+ result = FakeMatrixRoom(
+ leaveRoomLambda = { Result.success(Unit) }
+ ),
)
}
)
@@ -208,9 +212,9 @@ class DefaultLeaveRoomPresenterTest {
client = FakeMatrixClient().apply {
givenGetRoomResult(
roomId = A_ROOM_ID,
- result = FakeMatrixRoom().apply {
- this.leaveRoomLambda = { Result.failure(RuntimeException("Blimey!")) }
- },
+ result = FakeMatrixRoom(
+ leaveRoomLambda = { Result.failure(RuntimeException("Blimey!")) }
+ ),
)
}
)
diff --git a/features/location/impl/src/test/kotlin/io/element/android/features/location/impl/send/SendLocationPresenterTest.kt b/features/location/impl/src/test/kotlin/io/element/android/features/location/impl/send/SendLocationPresenterTest.kt
index 93e15f7868..24fcd3537c 100644
--- a/features/location/impl/src/test/kotlin/io/element/android/features/location/impl/send/SendLocationPresenterTest.kt
+++ b/features/location/impl/src/test/kotlin/io/element/android/features/location/impl/send/SendLocationPresenterTest.kt
@@ -29,13 +29,15 @@ import io.element.android.features.location.impl.common.permissions.PermissionsE
import io.element.android.features.location.impl.common.permissions.PermissionsPresenter
import io.element.android.features.location.impl.common.permissions.PermissionsState
import io.element.android.features.messages.test.FakeMessageComposerContext
+import io.element.android.libraries.matrix.api.room.MatrixRoom
import io.element.android.libraries.matrix.api.room.location.AssetType
import io.element.android.libraries.matrix.test.core.aBuildMeta
import io.element.android.libraries.matrix.test.room.FakeMatrixRoom
-import io.element.android.libraries.matrix.test.room.SendLocationInvocation
import io.element.android.libraries.textcomposer.model.MessageComposerMode
import io.element.android.services.analytics.test.FakeAnalyticsService
import io.element.android.tests.testutils.WarmUpRule
+import io.element.android.tests.testutils.lambda.lambdaRecorder
+import io.element.android.tests.testutils.lambda.value
import kotlinx.coroutines.delay
import kotlinx.coroutines.test.runTest
import org.junit.Rule
@@ -46,16 +48,18 @@ class SendLocationPresenterTest {
val warmUpRule = WarmUpRule()
private val fakePermissionsPresenter = FakePermissionsPresenter()
- private val fakeMatrixRoom = FakeMatrixRoom()
private val fakeAnalyticsService = FakeAnalyticsService()
private val fakeMessageComposerContext = FakeMessageComposerContext()
private val fakeLocationActions = FakeLocationActions()
private val fakeBuildMeta = aBuildMeta(applicationName = "app name")
- private val sendLocationPresenter: SendLocationPresenter = SendLocationPresenter(
+
+ private fun createSendLocationPresenter(
+ matrixRoom: MatrixRoom = FakeMatrixRoom(),
+ ): SendLocationPresenter = SendLocationPresenter(
permissionsPresenterFactory = object : PermissionsPresenter.Factory {
override fun create(permissions: List): PermissionsPresenter = fakePermissionsPresenter
},
- room = fakeMatrixRoom,
+ room = matrixRoom,
analyticsService = fakeAnalyticsService,
messageComposerContext = fakeMessageComposerContext,
locationActions = fakeLocationActions,
@@ -64,6 +68,7 @@ class SendLocationPresenterTest {
@Test
fun `initial state with permissions granted`() = runTest {
+ val sendLocationPresenter = createSendLocationPresenter()
fakePermissionsPresenter.givenState(
aPermissionsState(
permissions = PermissionsState.Permissions.AllGranted,
@@ -90,6 +95,7 @@ class SendLocationPresenterTest {
@Test
fun `initial state with permissions partially granted`() = runTest {
+ val sendLocationPresenter = createSendLocationPresenter()
fakePermissionsPresenter.givenState(
aPermissionsState(
permissions = PermissionsState.Permissions.SomeGranted,
@@ -116,6 +122,7 @@ class SendLocationPresenterTest {
@Test
fun `initial state with permissions denied`() = runTest {
+ val sendLocationPresenter = createSendLocationPresenter()
fakePermissionsPresenter.givenState(
aPermissionsState(
permissions = PermissionsState.Permissions.NoneGranted,
@@ -142,6 +149,7 @@ class SendLocationPresenterTest {
@Test
fun `initial state with permissions denied once`() = runTest {
+ val sendLocationPresenter = createSendLocationPresenter()
fakePermissionsPresenter.givenState(
aPermissionsState(
permissions = PermissionsState.Permissions.NoneGranted,
@@ -168,6 +176,7 @@ class SendLocationPresenterTest {
@Test
fun `rationale dialog dismiss`() = runTest {
+ val sendLocationPresenter = createSendLocationPresenter()
fakePermissionsPresenter.givenState(
aPermissionsState(
permissions = PermissionsState.Permissions.NoneGranted,
@@ -199,6 +208,7 @@ class SendLocationPresenterTest {
@Test
fun `rationale dialog continue`() = runTest {
+ val sendLocationPresenter = createSendLocationPresenter()
fakePermissionsPresenter.givenState(
aPermissionsState(
permissions = PermissionsState.Permissions.NoneGranted,
@@ -227,6 +237,7 @@ class SendLocationPresenterTest {
@Test
fun `permission denied dialog dismiss`() = runTest {
+ val sendLocationPresenter = createSendLocationPresenter()
fakePermissionsPresenter.givenState(
aPermissionsState(
permissions = PermissionsState.Permissions.NoneGranted,
@@ -258,6 +269,13 @@ class SendLocationPresenterTest {
@Test
fun `share sender location`() = runTest {
+ val sendLocationResult = lambdaRecorder> { _, _, _, _, _ ->
+ Result.success(Unit)
+ }
+ val matrixRoom = FakeMatrixRoom(
+ sendLocationResult = sendLocationResult,
+ )
+ val sendLocationPresenter = createSendLocationPresenter(matrixRoom)
fakePermissionsPresenter.givenState(
aPermissionsState(
permissions = PermissionsState.Permissions.AllGranted,
@@ -289,16 +307,14 @@ class SendLocationPresenterTest {
delay(1) // Wait for the coroutine to finish
- assertThat(fakeMatrixRoom.sentLocations.size).isEqualTo(1)
- assertThat(fakeMatrixRoom.sentLocations.last()).isEqualTo(
- SendLocationInvocation(
- body = "Location was shared at geo:3.0,4.0;u=5.0",
- geoUri = "geo:3.0,4.0;u=5.0",
- description = null,
- zoomLevel = 15,
- assetType = AssetType.SENDER
+ sendLocationResult.assertions().isCalledOnce()
+ .with(
+ value("Location was shared at geo:3.0,4.0;u=5.0"),
+ value("geo:3.0,4.0;u=5.0"),
+ value(null),
+ value(15),
+ value(AssetType.SENDER),
)
- )
assertThat(fakeAnalyticsService.capturedEvents.size).isEqualTo(1)
assertThat(fakeAnalyticsService.capturedEvents.last()).isEqualTo(
@@ -314,6 +330,13 @@ class SendLocationPresenterTest {
@Test
fun `share pin location`() = runTest {
+ val sendLocationResult = lambdaRecorder> { _, _, _, _, _ ->
+ Result.success(Unit)
+ }
+ val matrixRoom = FakeMatrixRoom(
+ sendLocationResult = sendLocationResult,
+ )
+ val sendLocationPresenter = createSendLocationPresenter(matrixRoom)
fakePermissionsPresenter.givenState(
aPermissionsState(
permissions = PermissionsState.Permissions.NoneGranted,
@@ -345,16 +368,14 @@ class SendLocationPresenterTest {
delay(1) // Wait for the coroutine to finish
- assertThat(fakeMatrixRoom.sentLocations.size).isEqualTo(1)
- assertThat(fakeMatrixRoom.sentLocations.last()).isEqualTo(
- SendLocationInvocation(
- body = "Location was shared at geo:0.0,1.0",
- geoUri = "geo:0.0,1.0",
- description = null,
- zoomLevel = 15,
- assetType = AssetType.PIN
+ sendLocationResult.assertions().isCalledOnce()
+ .with(
+ value("Location was shared at geo:0.0,1.0"),
+ value("geo:0.0,1.0"),
+ value(null),
+ value(15),
+ value(AssetType.PIN),
)
- )
assertThat(fakeAnalyticsService.capturedEvents.size).isEqualTo(1)
assertThat(fakeAnalyticsService.capturedEvents.last()).isEqualTo(
@@ -370,6 +391,13 @@ class SendLocationPresenterTest {
@Test
fun `composer context passes through analytics`() = runTest {
+ val sendLocationResult = lambdaRecorder> { _, _, _, _, _ ->
+ Result.success(Unit)
+ }
+ val matrixRoom = FakeMatrixRoom(
+ sendLocationResult = sendLocationResult,
+ )
+ val sendLocationPresenter = createSendLocationPresenter(matrixRoom)
fakePermissionsPresenter.givenState(
aPermissionsState(
permissions = PermissionsState.Permissions.NoneGranted,
@@ -418,6 +446,7 @@ class SendLocationPresenterTest {
@Test
fun `open settings activity`() = runTest {
+ val sendLocationPresenter = createSendLocationPresenter()
fakePermissionsPresenter.givenState(
aPermissionsState(
permissions = PermissionsState.Permissions.NoneGranted,
@@ -452,6 +481,7 @@ class SendLocationPresenterTest {
@Test
fun `application name is in state`() = runTest {
+ val sendLocationPresenter = createSendLocationPresenter()
moleculeFlow(RecompositionMode.Immediate) {
sendLocationPresenter.present()
}.test {
diff --git a/features/lockscreen/impl/build.gradle.kts b/features/lockscreen/impl/build.gradle.kts
index 21f3e05421..b8dc1c5035 100644
--- a/features/lockscreen/impl/build.gradle.kts
+++ b/features/lockscreen/impl/build.gradle.kts
@@ -41,6 +41,7 @@ dependencies {
implementation(projects.libraries.featureflag.api)
implementation(projects.libraries.cryptography.api)
implementation(projects.libraries.preferences.api)
+ implementation(projects.features.logout.api)
implementation(projects.libraries.uiStrings)
implementation(projects.libraries.sessionStorage.api)
implementation(projects.services.appnavstate.api)
@@ -59,4 +60,5 @@ dependencies {
testImplementation(projects.libraries.featureflag.test)
testImplementation(projects.libraries.sessionStorage.test)
testImplementation(projects.services.appnavstate.test)
+ testImplementation(projects.features.logout.test)
}
diff --git a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/unlock/PinUnlockPresenter.kt b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/unlock/PinUnlockPresenter.kt
index db56b8c17b..b563b3ac70 100644
--- a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/unlock/PinUnlockPresenter.kt
+++ b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/unlock/PinUnlockPresenter.kt
@@ -29,7 +29,7 @@ import io.element.android.features.lockscreen.impl.biometric.BiometricUnlockMana
import io.element.android.features.lockscreen.impl.pin.PinCodeManager
import io.element.android.features.lockscreen.impl.pin.model.PinEntry
import io.element.android.features.lockscreen.impl.unlock.keypad.PinKeypadModel
-import io.element.android.features.lockscreen.impl.unlock.signout.SignOut
+import io.element.android.features.logout.api.LogoutUseCase
import io.element.android.libraries.architecture.AsyncData
import io.element.android.libraries.architecture.Presenter
import io.element.android.libraries.architecture.runCatchingUpdatingState
@@ -41,7 +41,7 @@ import javax.inject.Inject
class PinUnlockPresenter @Inject constructor(
private val pinCodeManager: PinCodeManager,
private val biometricUnlockManager: BiometricUnlockManager,
- private val signOut: SignOut,
+ private val logoutUseCase: LogoutUseCase,
private val coroutineScope: CoroutineScope,
private val pinUnlockHelper: PinUnlockHelper,
) : Presenter {
@@ -179,7 +179,7 @@ class PinUnlockPresenter @Inject constructor(
private fun CoroutineScope.signOut(signOutAction: MutableState>) = launch {
suspend {
- signOut()
+ logoutUseCase.logout(ignoreSdkError = true)
}.runCatchingUpdatingState(signOutAction)
}
}
diff --git a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/unlock/di/PinUnlockBindings.kt b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/unlock/di/PinUnlockBindings.kt
index ddd62d2fb6..0f71222ebd 100644
--- a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/unlock/di/PinUnlockBindings.kt
+++ b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/unlock/di/PinUnlockBindings.kt
@@ -18,9 +18,9 @@ package io.element.android.features.lockscreen.impl.unlock.di
import com.squareup.anvil.annotations.ContributesTo
import io.element.android.features.lockscreen.impl.unlock.activity.PinUnlockActivity
-import io.element.android.libraries.di.AppScope
+import io.element.android.libraries.di.SessionScope
-@ContributesTo(AppScope::class)
+@ContributesTo(SessionScope::class)
interface PinUnlockBindings {
fun inject(activity: PinUnlockActivity)
}
diff --git a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/unlock/signout/DefaultSignOut.kt b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/unlock/signout/DefaultSignOut.kt
deleted file mode 100644
index 2c541911e6..0000000000
--- a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/unlock/signout/DefaultSignOut.kt
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * Copyright (c) 2024 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.lockscreen.impl.unlock.signout
-
-import com.squareup.anvil.annotations.ContributesBinding
-import io.element.android.libraries.di.AppScope
-import io.element.android.libraries.matrix.api.MatrixClientProvider
-import io.element.android.libraries.matrix.api.auth.MatrixAuthenticationService
-import javax.inject.Inject
-
-@ContributesBinding(AppScope::class)
-class DefaultSignOut @Inject constructor(
- private val authenticationService: MatrixAuthenticationService,
- private val matrixClientProvider: MatrixClientProvider,
-) : SignOut {
- override suspend fun invoke(): String? {
- val currentSession = authenticationService.getLatestSessionId()
- return if (currentSession != null) {
- matrixClientProvider.getOrRestore(currentSession)
- .getOrThrow()
- .logout(ignoreSdkError = true)
- } else {
- error("No session to sign out")
- }
- }
-}
diff --git a/features/lockscreen/impl/src/main/res/values-ka/translations.xml b/features/lockscreen/impl/src/main/res/values-ka/translations.xml
index 26fd97b671..022d4ee33c 100644
--- a/features/lockscreen/impl/src/main/res/values-ka/translations.xml
+++ b/features/lockscreen/impl/src/main/res/values-ka/translations.xml
@@ -1,12 +1,26 @@
+ "ბიომეტრიული ავტორიზაცია"
+ "ბიომეტრიული განბლოკვა"
+ "განბლოკვა ბიომეტრიით"
"დაგავიწყდათ PIN?"
"PIN კოდის შეცვლა"
"ბიომეტრიული განბლოკვის დაშვება"
"პინ კოდის წაშლა"
"დარწმუნებული ხართ, რომ გსურთ PIN-ის წაშლა?"
"გსურთ PIN-ის წაშლა?"
+ "%1$s დაშვება"
+ "მირჩევნია PIN-ის გამოყენება"
+ "დაზოგეთ დრო და გამოიყენეთ %1$s აპლიკაციის განსაბლოკად."
+ "აირჩიეთ PIN"
"დაადასტურეთ PIN"
+ "თქვენი ჩატების დამატებითი უსაფრთხოებისათვის დაბლოკეთ %1$s.
+
+აირჩიეთ რაიმე ისეთი, რაც დაგამახსოვრდებათ. თუ დაგავიწყდებათ ეს PIN, აპლიკაციიდან გამოხვალთ."
+ "თქვენ არ შეგიძლიათ აირჩიოთ ეს PIN კოდი უსაფრთხოების მიზეზების გამო"
+ "აირჩიეთ სხვა PIN"
+ "გთხოვთ შეიყვანოთ იგივე PIN ორჯერ"
+ "PIN-ები არ ემთხვევა"
"გასაგრძელებლად საჭიროა ხელახლა შესვლა და ახალი PIN-ის შექმნა"
"თქვენ ახლა გადიხართ…"
@@ -17,5 +31,7 @@
- "არასწორი PIN. თქვენ %1$d შანსი დაგრჩათ"
- "არასწორი PIN. თქვენ %1$d შანსი დაგრჩათ"
+ "გამოიყენეთ ბიომეტრია"
+ "გამოიყენეთ PIN"
"გასვლა…"
diff --git a/features/lockscreen/impl/src/main/res/values-pl/translations.xml b/features/lockscreen/impl/src/main/res/values-pl/translations.xml
new file mode 100644
index 0000000000..1eb904b13e
--- /dev/null
+++ b/features/lockscreen/impl/src/main/res/values-pl/translations.xml
@@ -0,0 +1,39 @@
+
+
+ "uwierzytelnienie biometryczne"
+ "odblokowanie biometryczne"
+ "Odblokuj za pomocą biometrii"
+ "Nie pamiętasz kodu PIN?"
+ "Zmień kod PIN"
+ "Zezwól na uwierzytelnienie biometryczne"
+ "Usuń PIN"
+ "Czy na pewno chcesz usunąć PIN?"
+ "Usunąć PIN?"
+ "Zezwól na %1$s"
+ "Wolę korzystać z kodu PIN"
+ "Zaoszczędź sobie trochę czasu i korzystaj z %1$s do odblokowywania aplikacji"
+ "Wybierz PIN"
+ "Potwierdź PIN"
+ "Zablokuj %1$s, aby zwiększyć bezpieczeństwo swoich czatów.
+
+Wybierz coś łatwego do zapamiętania. Jeśli zapomnisz tego PINU, zostaniesz wylogowany z aplikacji."
+ "Nie możesz wybrać tego PINU ze względów bezpieczeństwa"
+ "Wybierz inny kod PIN"
+ "Wprowadź ten sam kod PIN dwa razy"
+ "PINY nie pasują do siebie"
+ "Aby kontynuować, zaloguj się ponownie i utwórz nowy kod PIN"
+ "Trwa wylogowywanie"
+
+ - "Masz %1$d próbę, żeby odblokować"
+ - "Masz %1$d próby, żeby odblokować"
+ - "Masz %1$d prób, żeby odblokować"
+
+
+ - "Błędny PIN. Pozostała %1$d próba"
+ - "Błędny PIN. Pozostały %1$d próby"
+ - "Błędny PIN. Pozostało %1$d prób"
+
+ "Użyj biometrii"
+ "Użyj kodu PIN"
+ "Wylogowywanie…"
+
diff --git a/features/lockscreen/impl/src/main/res/values-pt-rBR/translations.xml b/features/lockscreen/impl/src/main/res/values-pt-rBR/translations.xml
new file mode 100644
index 0000000000..b7af1aba25
--- /dev/null
+++ b/features/lockscreen/impl/src/main/res/values-pt-rBR/translations.xml
@@ -0,0 +1,22 @@
+
+
+ "Esqueceu o PIN?"
+ "Mudar código de PIN"
+ "Permitir desbloqueio biométrico"
+ "Remover PIN"
+ "Tem certeza de que quer remover o PIN?"
+ "Remover PIN?"
+ "Escolher PIN"
+ "Confirmar PIN"
+ "Os PINs não correspondem"
+ "Você está sendo desconectado"
+
+ - "Você tem %1$d tentativa de debloqueio"
+ - "Você tem %1$d tentativas de debloqueio"
+
+
+ - "PIN incorreto. Você tem mais %1$d chance"
+ - "PIN incorreto. Você tem mais %1$d chances"
+
+ "Saindo…"
+
diff --git a/features/lockscreen/impl/src/test/kotlin/io/element/android/features/lockscreen/impl/unlock/DefaultSignOutTest.kt b/features/lockscreen/impl/src/test/kotlin/io/element/android/features/lockscreen/impl/unlock/DefaultSignOutTest.kt
deleted file mode 100644
index 392693d9ef..0000000000
--- a/features/lockscreen/impl/src/test/kotlin/io/element/android/features/lockscreen/impl/unlock/DefaultSignOutTest.kt
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * Copyright (c) 2024 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.lockscreen.impl.unlock
-
-import com.google.common.truth.Truth.assertThat
-import io.element.android.features.lockscreen.impl.unlock.signout.DefaultSignOut
-import io.element.android.libraries.matrix.test.FakeMatrixClient
-import io.element.android.libraries.matrix.test.FakeMatrixClientProvider
-import io.element.android.libraries.matrix.test.auth.FakeMatrixAuthenticationService
-import io.element.android.tests.testutils.lambda.assert
-import io.element.android.tests.testutils.lambda.lambdaRecorder
-import kotlinx.coroutines.test.runTest
-import org.junit.Test
-
-class DefaultSignOutTest {
- private val matrixClient = FakeMatrixClient()
- private val authenticationService = FakeMatrixAuthenticationService()
- private val matrixClientProvider = FakeMatrixClientProvider(getClient = { Result.success(matrixClient) })
- private val sut = DefaultSignOut(authenticationService, matrixClientProvider)
-
- @Test
- fun `when no active session then it throws`() = runTest {
- authenticationService.getLatestSessionIdLambda = { null }
- val result = runCatching { sut.invoke() }
- assertThat(result.isFailure).isTrue()
- }
-
- @Test
- fun `with one active session and successful logout on client`() = runTest {
- val logoutLambda = lambdaRecorder { _: Boolean -> null }
- authenticationService.getLatestSessionIdLambda = { matrixClient.sessionId }
- matrixClient.logoutLambda = logoutLambda
- val result = runCatching { sut.invoke() }
- assertThat(result.isSuccess).isTrue()
- assert(logoutLambda).isCalledOnce()
- }
-
- @Test
- fun `with one active session and and failed logout on client`() = runTest {
- val logoutLambda = lambdaRecorder { _: Boolean -> error("Failed to logout") }
- authenticationService.getLatestSessionIdLambda = { matrixClient.sessionId }
- matrixClient.logoutLambda = logoutLambda
- val result = runCatching { sut.invoke() }
- assertThat(result.isFailure).isTrue()
- assert(logoutLambda).isCalledOnce()
- }
-}
diff --git a/features/lockscreen/impl/src/test/kotlin/io/element/android/features/lockscreen/impl/unlock/PinUnlockPresenterTest.kt b/features/lockscreen/impl/src/test/kotlin/io/element/android/features/lockscreen/impl/unlock/PinUnlockPresenterTest.kt
index 89d0e92ee2..8299b5a1ab 100644
--- a/features/lockscreen/impl/src/test/kotlin/io/element/android/features/lockscreen/impl/unlock/PinUnlockPresenterTest.kt
+++ b/features/lockscreen/impl/src/test/kotlin/io/element/android/features/lockscreen/impl/unlock/PinUnlockPresenterTest.kt
@@ -28,7 +28,7 @@ import io.element.android.features.lockscreen.impl.pin.PinCodeManager
import io.element.android.features.lockscreen.impl.pin.model.PinEntry
import io.element.android.features.lockscreen.impl.pin.model.assertText
import io.element.android.features.lockscreen.impl.unlock.keypad.PinKeypadModel
-import io.element.android.features.lockscreen.impl.unlock.signout.SignOut
+import io.element.android.features.logout.test.FakeLogoutUseCase
import io.element.android.libraries.architecture.AsyncData
import io.element.android.tests.testutils.lambda.assert
import io.element.android.tests.testutils.lambda.lambdaRecorder
@@ -106,9 +106,9 @@ class PinUnlockPresenterTest {
@Test
fun `present - forgot pin flow`() = runTest {
- val signOutLambda = lambdaRecorder { null }
- val signOut = FakeSignOut(signOutLambda)
- val presenter = createPinUnlockPresenter(this, signOut = signOut)
+ val signOutLambda = lambdaRecorder { "" }
+ val signOut = FakeLogoutUseCase(signOutLambda)
+ val presenter = createPinUnlockPresenter(this, logoutUseCase = signOut)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
@@ -135,7 +135,7 @@ class PinUnlockPresenterTest {
awaitItem().also { state ->
assertThat(state.signOutAction).isInstanceOf(AsyncData.Success::class.java)
}
- assert(signOutLambda).isCalledOnce().withNoParameter()
+ assert(signOutLambda).isCalledOnce()
}
}
@@ -147,7 +147,7 @@ class PinUnlockPresenterTest {
scope: CoroutineScope,
biometricUnlockManager: BiometricUnlockManager = FakeBiometricUnlockManager(),
callback: PinCodeManager.Callback = DefaultPinCodeManagerCallback(),
- signOut: SignOut = FakeSignOut(),
+ logoutUseCase: FakeLogoutUseCase = FakeLogoutUseCase(logoutLambda = { "" }),
): PinUnlockPresenter {
val pinCodeManager = aPinCodeManager().apply {
addCallback(callback)
@@ -156,7 +156,7 @@ class PinUnlockPresenterTest {
return PinUnlockPresenter(
pinCodeManager = pinCodeManager,
biometricUnlockManager = biometricUnlockManager,
- signOut = signOut,
+ logoutUseCase = logoutUseCase,
coroutineScope = scope,
pinUnlockHelper = PinUnlockHelper(biometricUnlockManager, pinCodeManager),
)
diff --git a/features/login/impl/src/main/res/values-ka/translations.xml b/features/login/impl/src/main/res/values-ka/translations.xml
index 2187fc9e95..84e97e0a34 100644
--- a/features/login/impl/src/main/res/values-ka/translations.xml
+++ b/features/login/impl/src/main/res/values-ka/translations.xml
@@ -27,6 +27,7 @@
"Matrix არის ღია ქსელი უსაფრთხო, დეცენტრალიზებული კომუნიკაციისთვის."
"კეთილი იყოს თქვენი მობრძანება!"
"შესვლა %1$s-ში"
+ "ხელახლა ცდა"
"შეცვალეთ ანგარიშის მომწოდებელი"
"კერძო სერვერი Element-ის თანამშრომლებისთვის."
"Matrix არის ღია ქსელი უსაფრთხო, დეცენტრალიზებული კომუნიკაციისთვის."
diff --git a/features/login/impl/src/main/res/values-pl/translations.xml b/features/login/impl/src/main/res/values-pl/translations.xml
new file mode 100644
index 0000000000..8adfbcb8a8
--- /dev/null
+++ b/features/login/impl/src/main/res/values-pl/translations.xml
@@ -0,0 +1,43 @@
+
+
+ "Zmień dostawcę konta"
+ "Adres serwera domowego"
+ "Wprowadź wyszukiwane hasło lub adres domeny."
+ "Szukaj serwera firmowego, społeczności lub prywatnego."
+ "Znajdź dostawcę konta"
+ "Tutaj będą przechowywane Twoje konwersacje - w podobnej formie jak wiadomości widnieją na skrzynce e-mail."
+ "Zamierzasz się zalogować %s"
+ "Tutaj będą przechowywane Twoje konwersacje - w podobnej formie jak wiadomości widnieją na skrzynce e-mail."
+ "Zamierzasz założyć konto na %s"
+ "Matrix.org jest ogromnym i darmowym serwerem na publicznej sieci Matrix zapewniający bezpieczną i zdecentralizowaną komunikację zarządzaną przez Fundację Matrix.org."
+ "Inne"
+ "Użyj innego dostawcy konta, takiego jak własny serwer lub konta służbowego."
+ "Zmień dostawcę konta"
+ "Nie mogliśmy połączyć się z tym serwerem domowym. Sprawdź, czy adres URL serwera został wprowadzony poprawnie. Jeśli adres URL jest poprawny, skontaktuj się z administratorem serwera w celu uzyskania dalszej pomocy."
+ "Ten serwer obecnie nie obsługuje technologii Sliding Sync."
+ "Adres URL serwera domowego"
+ "Możesz połączyć się tylko z serwerem, który obsługuje technologię Sliding Sync. Administrator serwera domowego będzie musiał ją skonfigurować. %1$s"
+ "Jaki jest adres Twojego serwera?"
+ "Wybierz swój serwer"
+ "To konto zostało dezaktywowane."
+ "Nieprawidłowa nazwa użytkownika i/lub hasło"
+ "To nie jest prawidłowy identyfikator użytkownika. Oczekiwany format: \'@user:homeserver.org\'"
+ "Wybrany serwer domowy nie obsługuje uwierzytelniania hasłem, ani OIDC. Skontaktuj się z jego administratorem lub wybierz inny serwer domowy."
+ "Wprowadź swoje dane"
+ "Matrix to otwarta sieć do bezpiecznej i zdecentralizowanej komunikacji."
+ "Witaj ponownie!"
+ "Zaloguj się do %1$s"
+ "Spróbuj ponownie"
+ "Zmień dostawcę konta"
+ "Serwer prywatny dla pracowników Element."
+ "Matrix to otwarta sieć do bezpiecznej i zdecentralizowanej komunikacji."
+ "Tutaj będą przechowywane Twoje konwersacje - w podobnej formie jak wiadomości widnieją na skrzynce e-mail."
+ "Zamierzasz się zalogować do %1$s"
+ "Zamierzasz utworzyć konto na %1$s"
+ "Obecnie istnieje duże zapotrzebowanie na %1$s na %2$s. Wróć do aplikacji za kilka dni i spróbuj ponownie.
+
+Dziękujemy za Twoją cierpliwość!"
+ "Witamy w %1$s!"
+ "Już prawie gotowe!"
+ "Witamy!"
+
diff --git a/features/login/impl/src/main/res/values-pt-rBR/translations.xml b/features/login/impl/src/main/res/values-pt-rBR/translations.xml
new file mode 100644
index 0000000000..4cb9b4f7b0
--- /dev/null
+++ b/features/login/impl/src/main/res/values-pt-rBR/translations.xml
@@ -0,0 +1,43 @@
+
+
+ "Alterar provedor da conta"
+ "Endereço do servidor"
+ "Insira um termo de pesquisa ou um endereço de domínio."
+ "Procure uma empresa, comunidade ou servidor privado."
+ "Encontre um provedor de contas"
+ "Aqui é onde suas conversas vão ficar — assim como você usa um provedor de e-mails para manter seus e-mails."
+ "Você está prestes a entrar em %s"
+ "Aqui é onde suas conversas vão ficar — assim como você usa um provedor de e-mails para manter seus e-mails."
+ "Você está prestes a criar uma conta em %s"
+ "O Matrix.org é um grande servidor gratuito na rede pública Matrix para comunicação segura e descentralizada, administrado pela Fundação Matrix.org."
+ "Outro"
+ "Use um provedor de conta diferente, como seu próprio servidor privado ou uma conta corporativa."
+ "Alterar provedor da conta"
+ "Não conseguimos acessar esse servidor. Verifique se você inseriu a URL do servidor corretamente. Se a URL estiver correta, entre em contato com o administrador do servidor para obter mais ajuda."
+ "Este servidor atualmente não oferece suporte à tecnologia sliding sync."
+ "URL do servidor"
+ "Você só pode se conectar a um servidor existente que ofereça suporte à tecnologia sliding sync. O administrador do seu servidor precisará configurá-lo. %1$s"
+ "Qual é o endereço do seu servidor?"
+ "Selecione seu servidor"
+ "Essa conta foi desativada."
+ "Nome de usuário e/ou senha incorretos"
+ "Esse não é um identificador de usuário válido. Formato esperado: \'@usuário:servidor.org\'"
+ "O servidor selecionado não suporta senha ou login no OIDC. Entre em contato com o administrador ou escolha outro servidor."
+ "Insira seus dados"
+ "A Matrix é uma rede aberta para comunicação segura e descentralizada."
+ "Bem-vindo de volta!"
+ "Iniciar sessão em %1$s"
+ "Tente novamente"
+ "Alterar provedor da conta"
+ "Um servidor privado para funcionários do Element."
+ "A Matrix é uma rede aberta para comunicação segura e descentralizada."
+ "Aqui é onde suas conversas vão ficar — assim como você usa um provedor de e-mails para manter seus e-mails."
+ "Você está prestes a fazer login em %1$s"
+ "Você está prestes a criar uma conta em %1$s"
+ "Há uma grande demanda por %1$s sobre %2$s no momento. Volte ao aplicativo em alguns dias e tente novamente.
+
+Obrigado pela sua paciência!"
+ "Bem-vindo ao %1$s!"
+ "Você está quase lá."
+ "Você está dentro."
+
diff --git a/features/logout/api/src/main/kotlin/io/element/android/features/logout/api/LogoutUseCase.kt b/features/logout/api/src/main/kotlin/io/element/android/features/logout/api/LogoutUseCase.kt
new file mode 100644
index 0000000000..a06b7117b1
--- /dev/null
+++ b/features/logout/api/src/main/kotlin/io/element/android/features/logout/api/LogoutUseCase.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (c) 2024 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
+ *
+ * https://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.logout.api
+
+/**
+ * Used to trigger a log out of the current user from any part of the app.
+ */
+interface LogoutUseCase {
+ /**
+ * Log out the current user and then perform any needed cleanup tasks.
+ * @param ignoreSdkError if true, the SDK error will be ignored and the user will be logged out anyway.
+ * @return the session id of the logged out user.
+ */
+ suspend fun logout(ignoreSdkError: Boolean): String
+
+ interface Factory {
+ fun create(sessionId: String): LogoutUseCase
+ }
+}
diff --git a/features/logout/impl/src/main/kotlin/io/element/android/features/logout/impl/DefaultLogoutUseCase.kt b/features/logout/impl/src/main/kotlin/io/element/android/features/logout/impl/DefaultLogoutUseCase.kt
new file mode 100644
index 0000000000..8e5f08a87a
--- /dev/null
+++ b/features/logout/impl/src/main/kotlin/io/element/android/features/logout/impl/DefaultLogoutUseCase.kt
@@ -0,0 +1,43 @@
+/*
+ * Copyright (c) 2024 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
+ *
+ * https://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.logout.impl
+
+import com.squareup.anvil.annotations.ContributesBinding
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+import io.element.android.features.logout.api.LogoutUseCase
+import io.element.android.libraries.di.AppScope
+import io.element.android.libraries.matrix.api.MatrixClientProvider
+import io.element.android.libraries.matrix.api.core.SessionId
+
+class DefaultLogoutUseCase @AssistedInject constructor(
+ @Assisted private val sessionId: String,
+ private val matrixClientProvider: MatrixClientProvider,
+) : LogoutUseCase {
+ @ContributesBinding(AppScope::class)
+ @AssistedFactory
+ interface Factory : LogoutUseCase.Factory {
+ override fun create(sessionId: String): DefaultLogoutUseCase
+ }
+
+ override suspend fun logout(ignoreSdkError: Boolean): String {
+ val matrixClient = matrixClientProvider.getOrRestore(SessionId(sessionId)).getOrThrow()
+ matrixClient.logout(ignoreSdkError = ignoreSdkError)
+ return sessionId
+ }
+}
diff --git a/features/logout/impl/src/main/kotlin/io/element/android/features/logout/impl/SessionLogoutModule.kt b/features/logout/impl/src/main/kotlin/io/element/android/features/logout/impl/SessionLogoutModule.kt
new file mode 100644
index 0000000000..ec725a34a6
--- /dev/null
+++ b/features/logout/impl/src/main/kotlin/io/element/android/features/logout/impl/SessionLogoutModule.kt
@@ -0,0 +1,36 @@
+/*
+ * Copyright (c) 2024 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
+ *
+ * https://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.logout.impl
+
+import com.squareup.anvil.annotations.ContributesTo
+import dagger.Module
+import dagger.Provides
+import io.element.android.features.logout.api.LogoutUseCase
+import io.element.android.libraries.di.SessionScope
+import io.element.android.libraries.matrix.api.user.CurrentSessionIdHolder
+
+@Module
+@ContributesTo(SessionScope::class)
+object SessionLogoutModule {
+ @Provides
+ fun provideLogoutUseCase(
+ currentSessionIdHolder: CurrentSessionIdHolder,
+ factory: DefaultLogoutUseCase.Factory,
+ ): LogoutUseCase {
+ return factory.create(currentSessionIdHolder.current.value)
+ }
+}
diff --git a/features/logout/impl/src/main/res/values-ka/translations.xml b/features/logout/impl/src/main/res/values-ka/translations.xml
index 2226fd240f..adf15b8e46 100644
--- a/features/logout/impl/src/main/res/values-ka/translations.xml
+++ b/features/logout/impl/src/main/res/values-ka/translations.xml
@@ -4,5 +4,15 @@
"გამოსვლა"
"გამოსვლა"
"გასვლა…"
+ "თქვენ აპირებთ გასვლას თქვენი ბოლო სესიიდან. თუ ახლა გამოხვალთ, დაკარგავთ წვდომას თქვენს დაშიფრულ შეტყობინებებზე."
+ "თქვენ გამორთეთ სარეზერვო ასლი"
+ "თქვენი გასაღებების სარეზერვო ასლის შექმნა მიმდინარეობდა იმ დროს, როდესაც გამოხვედით. დაკავშირდით ისევ ისე, რომ სარეზერვო ასლი შეიქმნას ანგარიშიდან გამოსვლის გარეშე."
+ "თქვენი გასაღებების სარეზერვო ასლი ჯერ კიდევ შექმნის პროცესშია"
+ "გთხოვთ დაელოდეთ ამის დასრულებას სისტემიდან გამოსვლამდე."
+ "თქვენი გასაღებების სარეზერვო ასლი ჯერ კიდევ შექმნის პროცესშია"
"გამოსვლა"
+ "თქვენ აპირებთ გასვლას თქვენი ბოლო სესიიდან. თუ ახლა გამოხვალთ, დაკარგავთ წვდომას თქვენს დაშიფრულ შეტყობინებებზე."
+ "აღდგენა არ არის დაყენებული"
+ "თქვენ აპირებთ გასვლას თქვენი ბოლო სესიიდან. თუ ახლა გამოხვალთ, შესაძლოა დაკარგოთ წვდომა თქვენს დაშიფრულ შეტყობინებებზე."
+ "შეინახეთ თქვენი აღდგენის გასაღები?"
diff --git a/features/logout/impl/src/main/res/values-pl/translations.xml b/features/logout/impl/src/main/res/values-pl/translations.xml
new file mode 100644
index 0000000000..ca0d9dc4af
--- /dev/null
+++ b/features/logout/impl/src/main/res/values-pl/translations.xml
@@ -0,0 +1,18 @@
+
+
+ "Czy na pewno chcesz się wylogować?"
+ "Wyloguj się"
+ "Wyloguj się"
+ "Wylogowywanie…"
+ "Zamierzasz wylogować się ze swojej ostatniej sesji. Jeśli wylogujesz się teraz, stracisz dostęp do swoich wiadomości szyfrowanych."
+ "Wyłączyłeś backup"
+ "Twoje klucze były nadal archiwizowane po przejściu w tryb offline. Połącz się ponownie, aby zapisać w chmurze przed wylogowaniem."
+ "Twoje klucze są nadal archiwizowane"
+ "Zanim się wylogujesz, poczekaj na zakończenie operacji."
+ "Twoje klucze są nadal archiwizowane"
+ "Wyloguj się"
+ "Zamierzasz wylogować się ze swojej ostatniej sesji. Jeśli wylogujesz się teraz, stracisz dostęp do swoich wiadomości szyfrowanych."
+ "Nie ustawiono przywracania"
+ "Zamierzasz wylogować się ze swojej ostatniej sesji. Jeśli wylogujesz się teraz, stracisz dostęp do swoich wiadomości szyfrowanych."
+ "Czy zapisałeś swój klucz przywracania?"
+
diff --git a/features/logout/impl/src/main/res/values-pt-rBR/translations.xml b/features/logout/impl/src/main/res/values-pt-rBR/translations.xml
new file mode 100644
index 0000000000..65ccf4ca6f
--- /dev/null
+++ b/features/logout/impl/src/main/res/values-pt-rBR/translations.xml
@@ -0,0 +1,8 @@
+
+
+ "Você tem certeza que deseja sair?"
+ "Sair"
+ "Sair"
+ "Saindo…"
+ "Sair"
+
diff --git a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/unlock/signout/SignOut.kt b/features/logout/test/build.gradle.kts
similarity index 64%
rename from features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/unlock/signout/SignOut.kt
rename to features/logout/test/build.gradle.kts
index f4c91aece6..7be20ef279 100644
--- a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/unlock/signout/SignOut.kt
+++ b/features/logout/test/build.gradle.kts
@@ -5,7 +5,7 @@
* 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
+ * https://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,
@@ -14,8 +14,16 @@
* limitations under the License.
*/
-package io.element.android.features.lockscreen.impl.unlock.signout
-
-interface SignOut {
- suspend operator fun invoke(): String?
+plugins {
+ id("io.element.android-library")
+}
+
+android {
+ namespace = "io.element.android.features.logout.test"
+}
+
+dependencies {
+ implementation(libs.coroutines.core)
+ implementation(projects.tests.testutils)
+ api(projects.features.logout.api)
}
diff --git a/features/lockscreen/impl/src/test/kotlin/io/element/android/features/lockscreen/impl/unlock/FakeSignOut.kt b/features/logout/test/src/main/kotlin/io/element/android/features/logout/test/FakeLogoutUseCase.kt
similarity index 56%
rename from features/lockscreen/impl/src/test/kotlin/io/element/android/features/lockscreen/impl/unlock/FakeSignOut.kt
rename to features/logout/test/src/main/kotlin/io/element/android/features/logout/test/FakeLogoutUseCase.kt
index 883a5bf97b..bbc67765d1 100644
--- a/features/lockscreen/impl/src/test/kotlin/io/element/android/features/lockscreen/impl/unlock/FakeSignOut.kt
+++ b/features/logout/test/src/main/kotlin/io/element/android/features/logout/test/FakeLogoutUseCase.kt
@@ -5,7 +5,7 @@
* 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
+ * https://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,
@@ -14,15 +14,15 @@
* limitations under the License.
*/
-package io.element.android.features.lockscreen.impl.unlock
+package io.element.android.features.logout.test
-import io.element.android.features.lockscreen.impl.unlock.signout.SignOut
-import io.element.android.tests.testutils.simulateLongTask
+import io.element.android.features.logout.api.LogoutUseCase
+import io.element.android.tests.testutils.lambda.lambdaError
-class FakeSignOut(
- var lambda: () -> String? = { null }
-) : SignOut {
- override suspend fun invoke(): String? = simulateLongTask {
- lambda()
+class FakeLogoutUseCase(
+ var logoutLambda: (Boolean) -> String = lambdaError()
+) : LogoutUseCase {
+ override suspend fun logout(ignoreSdkError: Boolean): String {
+ return logoutLambda(ignoreSdkError)
}
}
diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerPresenter.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerPresenter.kt
index 7bd6bd1867..d0e50d114c 100644
--- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerPresenter.kt
+++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerPresenter.kt
@@ -60,6 +60,7 @@ import io.element.android.libraries.matrix.api.room.Mention
import io.element.android.libraries.matrix.api.room.draft.ComposerDraft
import io.element.android.libraries.matrix.api.room.draft.ComposerDraftType
import io.element.android.libraries.matrix.api.room.isDm
+import io.element.android.libraries.matrix.api.timeline.TimelineException
import io.element.android.libraries.matrix.ui.messages.RoomMemberProfilesCache
import io.element.android.libraries.matrix.ui.messages.reply.InReplyToDetails
import io.element.android.libraries.matrix.ui.messages.reply.map
@@ -436,7 +437,14 @@ class MessageComposerPresenter @Inject constructor(
val eventId = capturedMode.eventId
val transactionId = capturedMode.transactionId
timelineController.invokeOnCurrentTimeline {
+ // First try to edit the message in the current timeline
editMessage(eventId, transactionId, message.markdown, message.html, message.mentions)
+ .onFailure { cause ->
+ if (cause is TimelineException.EventNotFound && eventId != null) {
+ // if the event is not found in the timeline, try to edit the message directly
+ room.editMessage(eventId, message.markdown, message.html, message.mentions)
+ }
+ }
}
}
diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentMessageFactory.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentMessageFactory.kt
index 48141ccc03..9d9542d595 100644
--- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentMessageFactory.kt
+++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentMessageFactory.kt
@@ -264,27 +264,27 @@ class TimelineItemContentMessageFactory @Inject constructor(
}
private fun CharSequence.withFixedURLSpans(): CharSequence {
- if (this !is Spannable) return this
+ val spannable = this.toSpannable()
// Get all URL spans, as they will be removed by LinkifyCompat.addLinks
- val oldURLSpans = getSpans(0, length).associateWith {
- val start = getSpanStart(it)
- val end = getSpanEnd(it)
+ val oldURLSpans = spannable.getSpans(0, length).associateWith {
+ val start = spannable.getSpanStart(it)
+ val end = spannable.getSpanEnd(it)
Pair(start, end)
}
// Find and set as URLSpans any links present in the text
- LinkifyCompat.addLinks(this, Linkify.WEB_URLS or Linkify.PHONE_NUMBERS or Linkify.EMAIL_ADDRESSES)
+ LinkifyCompat.addLinks(spannable, Linkify.WEB_URLS or Linkify.PHONE_NUMBERS or Linkify.EMAIL_ADDRESSES)
// Restore old spans, remove new ones if there is a conflict
for ((urlSpan, location) in oldURLSpans) {
val (start, end) = location
- val addedSpans = getSpans(start, end).orEmpty()
+ val addedSpans = spannable.getSpans(start, end).orEmpty()
if (addedSpans.isNotEmpty()) {
for (span in addedSpans) {
- removeSpan(span)
+ spannable.removeSpan(span)
}
}
- setSpan(urlSpan, start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
+ spannable.setSpan(urlSpan, start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
}
- return this
+ return spannable
}
}
diff --git a/features/messages/impl/src/main/res/values-ka/translations.xml b/features/messages/impl/src/main/res/values-ka/translations.xml
index 080696776f..47261affcc 100644
--- a/features/messages/impl/src/main/res/values-ka/translations.xml
+++ b/features/messages/impl/src/main/res/values-ka/translations.xml
@@ -21,8 +21,10 @@
"გამოკითხვა"
"ტექსტის ფორმატირება"
"შეტყობინებების ისტორია ამჟამად მიუწვდომელია."
+ "შეტყობინებების ისტორია ამ ოთახში მიუწვდომელია. დაადასტურეთ ეს მოწყობილობა თქვენი შეტყობინებების ისტორიის სანახავად."
"გსურთ მათი კვლავ მოწვევა?"
"თქვენ მარტო ხართ ამ ჩატში"
+ "მთელი ოთახისათვის შეტყობინება"
"ყველა"
"Ხელახლა გაგზავნა"
"თქვენი შეტყობინების გაგზავნა ვერ მოხერხდა"
diff --git a/features/messages/impl/src/main/res/values-pl/translations.xml b/features/messages/impl/src/main/res/values-pl/translations.xml
new file mode 100644
index 0000000000..ebb2b54369
--- /dev/null
+++ b/features/messages/impl/src/main/res/values-pl/translations.xml
@@ -0,0 +1,50 @@
+
+
+ "Aktywności"
+ "Flagi"
+ "Jedzenie i napoje"
+ "Zwierzęta i natura"
+ "Obiekty"
+ "Buźki i osoby"
+ "Podróż i miejsca"
+ "Symbole"
+ "Zablokuj użytkownika"
+ "Sprawdź, czy chcesz ukryć wszystkie bieżące i przyszłe wiadomości od tego użytkownika."
+ "Ta wiadomość zostanie zgłoszona do administratora Twojego serwera domowego. Nie będzie mógł on przeczytać żadnych zaszyfrowanych wiadomości."
+ "Powód zgłoszenia treści"
+ "Kamera"
+ "Zrób zdjęcie"
+ "Nagraj film"
+ "Załącznik"
+ "Zdjęcia i filmy"
+ "Lokalizacja"
+ "Ankieta"
+ "Formatowanie tekstu"
+ "Historia wiadomości jest obecnie niedostępna."
+ "Historia wiadomości jest niedostępna w tym pokoju. Zweryfikuj to urządzenie, aby zobaczyć historię wiadomości."
+ "Czy chcesz zaprosić ich z powrotem?"
+ "Jesteś sam na tym czacie"
+ "Powiadom cały pokój"
+ "Wszyscy"
+ "Wyślij ponownie"
+ "Nie udało się wysłać wiadomości"
+ "Dodaj emoji"
+ "To jest początek %1$s"
+ "To jest początek tej konwersacji"
+ "Pokaż mniej"
+ "Skopiowano wiadomość"
+ "Nie masz uprawnień, aby pisać w tym pokoju"
+ "Pokaż mniej"
+ "Pokaż więcej"
+ "Nowe"
+
+ - "%1$d zmiana pokoju"
+ - "%1$d zmian pokoju"
+ - "%1$d zmiany pokoju"
+
+
+ - "%1$s piszę"
+ - "%1$s piszą"
+ - "%1$s piszą"
+
+
diff --git a/features/messages/impl/src/main/res/values-pt-rBR/translations.xml b/features/messages/impl/src/main/res/values-pt-rBR/translations.xml
new file mode 100644
index 0000000000..6d324fd695
--- /dev/null
+++ b/features/messages/impl/src/main/res/values-pt-rBR/translations.xml
@@ -0,0 +1,42 @@
+
+
+ "Atividades"
+ "Bandeiras"
+ "Comida & Bebida"
+ "Animais & Natureza"
+ "Objetos"
+ "Sorrisos & Pessoas"
+ "Viagens & Lugares"
+ "Símbolos"
+ "Bloquear usuário"
+ "Marque se você deseja ocultar todas as mensagens atuais e futuras desse usuário"
+ "Essa mensagem será reportada ao administrador do seu homeserver. Eles não conseguirão ler nenhuma mensagem criptografada."
+ "Motivo para denunciar este conteúdo"
+ "Câmera"
+ "Tirar foto"
+ "Gravar vídeo"
+ "Anexo"
+ "Biblioteca de fotos e vídeos"
+ "Localização"
+ "Enquete"
+ "Formatação de texto"
+ "O histórico de mensagens não está disponível no momento."
+ "Gostaria de convidá-los de volta?"
+ "Você está sozinho neste chat"
+ "Todos"
+ "Enviar novamente"
+ "Sua mensagem não foi enviada"
+ "Adicionar emoji"
+ "Este é o início do %1$s."
+ "Este é o início desta conversa."
+ "Mostrar menos"
+ "Mensagem copiada"
+ "Você não tem permissão para postar nesta sala"
+ "Mostrar menos"
+ "Mostrar mais"
+ "Novo"
+
+ - "%1$d mudança de sala"
+ - "%1$d mudanças de salas"
+
+
diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/MessagesPresenterTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/MessagesPresenterTest.kt
index bc8daf2b00..597d302416 100644
--- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/MessagesPresenterTest.kt
+++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/MessagesPresenterTest.kt
@@ -67,6 +67,7 @@ import io.element.android.libraries.featureflag.api.FeatureFlags
import io.element.android.libraries.featureflag.test.FakeFeatureFlagService
import io.element.android.libraries.matrix.api.core.EventId
import io.element.android.libraries.matrix.api.core.TransactionId
+import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.api.media.MediaSource
import io.element.android.libraries.matrix.api.permalink.PermalinkParser
import io.element.android.libraries.matrix.api.room.MatrixRoom
@@ -105,6 +106,7 @@ import io.element.android.tests.testutils.WarmUpRule
import io.element.android.tests.testutils.consumeItemsUntilPredicate
import io.element.android.tests.testutils.consumeItemsUntilTimeout
import io.element.android.tests.testutils.lambda.assert
+import io.element.android.tests.testutils.lambda.lambdaError
import io.element.android.tests.testutils.lambda.lambdaRecorder
import io.element.android.tests.testutils.lambda.value
import io.element.android.tests.testutils.testCoroutineDispatchers
@@ -138,7 +140,7 @@ class MessagesPresenterTest {
assertThat(initialState.roomAvatar)
.isEqualTo(AsyncData.Success(AvatarData(id = A_ROOM_ID.value, name = "", url = AN_AVATAR_URL, size = AvatarSize.TimelineRoom)))
assertThat(initialState.userHasPermissionToSendMessage).isTrue()
- assertThat(initialState.userHasPermissionToRedactOwn).isFalse()
+ assertThat(initialState.userHasPermissionToRedactOwn).isTrue()
assertThat(initialState.hasNetworkConnection).isTrue()
assertThat(initialState.snackbarMessage).isNull()
assertThat(initialState.inviteProgress).isEqualTo(AsyncData.Uninitialized)
@@ -149,7 +151,13 @@ class MessagesPresenterTest {
@OptIn(ExperimentalCoroutinesApi::class)
@Test
fun `present - check that the room's unread flag is removed`() = runTest {
- val room = FakeMatrixRoom()
+ val room = FakeMatrixRoom(
+ canUserSendMessageResult = { _, _ -> Result.success(true) },
+ canRedactOwnResult = { Result.success(true) },
+ canRedactOtherResult = { Result.success(true) },
+ canUserJoinCallResult = { Result.success(true) },
+ typingNoticeResult = { Result.success(Unit) },
+ )
assertThat(room.markAsReadCalls).isEmpty()
val presenter = createMessagesPresenter(matrixRoom = room)
moleculeFlow(RecompositionMode.Immediate) {
@@ -163,8 +171,13 @@ class MessagesPresenterTest {
@Test
fun `present - call is disabled if user cannot join it even if there is an ongoing call`() = runTest {
- val room = FakeMatrixRoom().apply {
- givenCanUserJoinCall(Result.success(false))
+ val room = FakeMatrixRoom(
+ canUserJoinCallResult = { Result.success(false) },
+ canUserSendMessageResult = { _, _ -> Result.success(true) },
+ canRedactOwnResult = { Result.success(true) },
+ canRedactOtherResult = { Result.success(true) },
+ typingNoticeResult = { Result.success(Unit) },
+ ).apply {
givenRoomInfo(aRoomInfo(hasRoomCall = true))
}
val presenter = createMessagesPresenter(matrixRoom = room)
@@ -185,7 +198,14 @@ class MessagesPresenterTest {
val timeline = FakeTimeline().apply {
this.toggleReactionLambda = toggleReactionSuccess
}
- val room = FakeMatrixRoom(liveTimeline = timeline)
+ val room = FakeMatrixRoom(
+ liveTimeline = timeline,
+ canUserSendMessageResult = { _, _ -> Result.success(true) },
+ canRedactOwnResult = { Result.success(true) },
+ canRedactOtherResult = { Result.success(true) },
+ canUserJoinCallResult = { Result.success(true) },
+ typingNoticeResult = { Result.success(Unit) },
+ )
val presenter = createMessagesPresenter(matrixRoom = room, coroutineDispatchers = coroutineDispatchers)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
@@ -215,7 +235,14 @@ class MessagesPresenterTest {
val timeline = FakeTimeline().apply {
this.toggleReactionLambda = toggleReactionSuccess
}
- val room = FakeMatrixRoom(liveTimeline = timeline)
+ val room = FakeMatrixRoom(
+ liveTimeline = timeline,
+ canUserSendMessageResult = { _, _ -> Result.success(true) },
+ canRedactOwnResult = { Result.success(true) },
+ canRedactOtherResult = { Result.success(true) },
+ canUserJoinCallResult = { Result.success(true) },
+ typingNoticeResult = { Result.success(Unit) },
+ )
val presenter = createMessagesPresenter(matrixRoom = room, coroutineDispatchers = coroutineDispatchers)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
@@ -268,6 +295,11 @@ class MessagesPresenterTest {
val event = aMessageEvent()
val matrixRoom = FakeMatrixRoom(
eventPermalinkResult = { Result.success("a link") },
+ canUserSendMessageResult = { _, _ -> Result.success(true) },
+ canRedactOwnResult = { Result.success(true) },
+ canRedactOtherResult = { Result.success(true) },
+ canUserJoinCallResult = { Result.success(true) },
+ typingNoticeResult = { Result.success(Unit) },
)
val presenter = createMessagesPresenter(
clipboardHelper = clipboardHelper,
@@ -450,7 +482,14 @@ class MessagesPresenterTest {
val coroutineDispatchers = testCoroutineDispatchers(useUnconfinedTestDispatcher = true)
val liveTimeline = FakeTimeline()
- val matrixRoom = FakeMatrixRoom(liveTimeline = liveTimeline)
+ val matrixRoom = FakeMatrixRoom(
+ liveTimeline = liveTimeline,
+ canUserSendMessageResult = { _, _ -> Result.success(true) },
+ canRedactOwnResult = { Result.success(true) },
+ canRedactOtherResult = { Result.success(true) },
+ canUserJoinCallResult = { Result.success(true) },
+ typingNoticeResult = { Result.success(Unit) },
+ )
val redactEventLambda = lambdaRecorder { _: EventId?, _: TransactionId?, _: String? -> Result.success(true) }
liveTimeline.redactEventLambda = redactEventLambda
@@ -515,7 +554,16 @@ class MessagesPresenterTest {
@Test
fun `present - shows prompt to reinvite users in DM`() = runTest {
- val room = FakeMatrixRoom(sessionId = A_SESSION_ID, isDirect = true, activeMemberCount = 1L)
+ val room = FakeMatrixRoom(
+ sessionId = A_SESSION_ID,
+ isDirect = true,
+ activeMemberCount = 1L,
+ canUserSendMessageResult = { _, _ -> Result.success(true) },
+ canRedactOwnResult = { Result.success(true) },
+ canRedactOtherResult = { Result.success(true) },
+ canUserJoinCallResult = { Result.success(true) },
+ typingNoticeResult = { Result.success(Unit) },
+ )
val presenter = createMessagesPresenter(matrixRoom = room)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
@@ -541,7 +589,16 @@ class MessagesPresenterTest {
@Test
fun `present - doesn't show reinvite prompt in non-direct room`() = runTest {
- val room = FakeMatrixRoom(sessionId = A_SESSION_ID, isDirect = false, activeMemberCount = 1L)
+ val room = FakeMatrixRoom(
+ sessionId = A_SESSION_ID,
+ isDirect = false,
+ activeMemberCount = 1L,
+ canUserSendMessageResult = { _, _ -> Result.success(true) },
+ canRedactOwnResult = { Result.success(true) },
+ canRedactOtherResult = { Result.success(true) },
+ canUserJoinCallResult = { Result.success(true) },
+ typingNoticeResult = { Result.success(Unit) },
+ )
val presenter = createMessagesPresenter(matrixRoom = room)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
@@ -556,7 +613,16 @@ class MessagesPresenterTest {
@Test
fun `present - doesn't show reinvite prompt if other party is present`() = runTest {
- val room = FakeMatrixRoom(sessionId = A_SESSION_ID, isDirect = true, activeMemberCount = 2L)
+ val room = FakeMatrixRoom(
+ sessionId = A_SESSION_ID,
+ isDirect = true,
+ activeMemberCount = 2L,
+ canUserSendMessageResult = { _, _ -> Result.success(true) },
+ canRedactOwnResult = { Result.success(true) },
+ canRedactOtherResult = { Result.success(true) },
+ canUserJoinCallResult = { Result.success(true) },
+ typingNoticeResult = { Result.success(Unit) },
+ )
val presenter = createMessagesPresenter(matrixRoom = room)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
@@ -571,7 +637,16 @@ class MessagesPresenterTest {
@Test
fun `present - handle reinviting other user when memberlist is ready`() = runTest {
- val room = FakeMatrixRoom(sessionId = A_SESSION_ID)
+ val inviteUserResult = lambdaRecorder { _: UserId -> Result.success(Unit) }
+ val room = FakeMatrixRoom(
+ sessionId = A_SESSION_ID,
+ inviteUserResult = inviteUserResult,
+ canUserSendMessageResult = { _, _ -> Result.success(true) },
+ canRedactOwnResult = { Result.success(true) },
+ canRedactOtherResult = { Result.success(true) },
+ canUserJoinCallResult = { Result.success(true) },
+ typingNoticeResult = { Result.success(Unit) },
+ )
room.givenRoomMembersState(
MatrixRoomMembersState.Ready(
persistentListOf(
@@ -591,13 +666,22 @@ class MessagesPresenterTest {
assertThat(loadingState.inviteProgress.isLoading()).isTrue()
val newState = awaitItem()
assertThat(newState.inviteProgress.isSuccess()).isTrue()
- assertThat(room.invitedUserId).isEqualTo(A_SESSION_ID_2)
+ inviteUserResult.assertions().isCalledOnce().with(value(A_SESSION_ID_2))
}
}
@Test
fun `present - handle reinviting other user when memberlist is error`() = runTest {
- val room = FakeMatrixRoom(sessionId = A_SESSION_ID)
+ val inviteUserResult = lambdaRecorder { _: UserId -> Result.success(Unit) }
+ val room = FakeMatrixRoom(
+ sessionId = A_SESSION_ID,
+ inviteUserResult = inviteUserResult,
+ canUserSendMessageResult = { _, _ -> Result.success(true) },
+ canRedactOwnResult = { Result.success(true) },
+ canRedactOtherResult = { Result.success(true) },
+ canUserJoinCallResult = { Result.success(true) },
+ typingNoticeResult = { Result.success(Unit) },
+ )
room.givenRoomMembersState(
MatrixRoomMembersState.Error(
failure = Throwable(),
@@ -620,13 +704,20 @@ class MessagesPresenterTest {
assertThat(loadingState.inviteProgress.isLoading()).isTrue()
val newState = awaitItem()
assertThat(newState.inviteProgress.isSuccess()).isTrue()
- assertThat(room.invitedUserId).isEqualTo(A_SESSION_ID_2)
+ inviteUserResult.assertions().isCalledOnce().with(value(A_SESSION_ID_2))
}
}
@Test
fun `present - handle reinviting other user when memberlist is not ready`() = runTest {
- val room = FakeMatrixRoom(sessionId = A_SESSION_ID)
+ val room = FakeMatrixRoom(
+ sessionId = A_SESSION_ID,
+ canUserSendMessageResult = { _, _ -> Result.success(true) },
+ canRedactOwnResult = { Result.success(true) },
+ canRedactOtherResult = { Result.success(true) },
+ canUserJoinCallResult = { Result.success(true) },
+ typingNoticeResult = { Result.success(Unit) },
+ )
room.givenRoomMembersState(MatrixRoomMembersState.Unknown)
val presenter = createMessagesPresenter(matrixRoom = room)
moleculeFlow(RecompositionMode.Immediate) {
@@ -644,7 +735,15 @@ class MessagesPresenterTest {
@Test
fun `present - handle reinviting other user when inviting fails`() = runTest {
- val room = FakeMatrixRoom(sessionId = A_SESSION_ID)
+ val room = FakeMatrixRoom(
+ sessionId = A_SESSION_ID,
+ inviteUserResult = { Result.failure(Throwable("Oops!")) },
+ canUserSendMessageResult = { _, _ -> Result.success(true) },
+ canRedactOwnResult = { Result.success(true) },
+ canRedactOtherResult = { Result.success(true) },
+ canUserJoinCallResult = { Result.success(true) },
+ typingNoticeResult = { Result.success(Unit) },
+ )
room.givenRoomMembersState(
MatrixRoomMembersState.Ready(
persistentListOf(
@@ -653,7 +752,6 @@ class MessagesPresenterTest {
)
)
)
- room.givenInviteUserResult(Result.failure(Throwable("Oops!")))
val presenter = createMessagesPresenter(matrixRoom = room)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
@@ -673,8 +771,19 @@ class MessagesPresenterTest {
@Test
fun `present - permission to post`() = runTest {
- val matrixRoom = FakeMatrixRoom()
- matrixRoom.givenCanSendEventResult(MessageEventType.ROOM_MESSAGE, Result.success(true))
+ val matrixRoom = FakeMatrixRoom(
+ canUserSendMessageResult = { _, messageEventType ->
+ when (messageEventType) {
+ MessageEventType.ROOM_MESSAGE -> Result.success(true)
+ MessageEventType.REACTION -> Result.success(true)
+ else -> lambdaError()
+ }
+ },
+ canRedactOwnResult = { Result.success(true) },
+ canRedactOtherResult = { Result.success(true) },
+ canUserJoinCallResult = { Result.success(true) },
+ typingNoticeResult = { Result.success(Unit) },
+ )
val presenter = createMessagesPresenter(matrixRoom = matrixRoom)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
@@ -686,8 +795,19 @@ class MessagesPresenterTest {
@Test
fun `present - no permission to post`() = runTest {
- val matrixRoom = FakeMatrixRoom()
- matrixRoom.givenCanSendEventResult(MessageEventType.ROOM_MESSAGE, Result.success(false))
+ val matrixRoom = FakeMatrixRoom(
+ canUserSendMessageResult = { _, messageEventType ->
+ when (messageEventType) {
+ MessageEventType.ROOM_MESSAGE -> Result.success(false)
+ MessageEventType.REACTION -> Result.success(false)
+ else -> lambdaError()
+ }
+ },
+ canRedactOwnResult = { Result.success(true) },
+ canRedactOtherResult = { Result.success(true) },
+ canUserJoinCallResult = { Result.success(true) },
+ typingNoticeResult = { Result.success(Unit) },
+ )
val presenter = createMessagesPresenter(matrixRoom = matrixRoom)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
@@ -702,7 +822,13 @@ class MessagesPresenterTest {
@Test
fun `present - permission to redact own`() = runTest {
- val matrixRoom = FakeMatrixRoom(canRedactOwn = true)
+ val matrixRoom = FakeMatrixRoom(
+ canRedactOwnResult = { Result.success(true) },
+ canUserSendMessageResult = { _, _ -> Result.success(true) },
+ canRedactOtherResult = { Result.success(false) },
+ canUserJoinCallResult = { Result.success(true) },
+ typingNoticeResult = { Result.success(Unit) },
+ )
val presenter = createMessagesPresenter(matrixRoom = matrixRoom)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
@@ -716,7 +842,13 @@ class MessagesPresenterTest {
@Test
fun `present - permission to redact other`() = runTest {
- val matrixRoom = FakeMatrixRoom(canRedactOther = true)
+ val matrixRoom = FakeMatrixRoom(
+ canRedactOtherResult = { Result.success(true) },
+ canUserSendMessageResult = { _, _ -> Result.success(true) },
+ canRedactOwnResult = { Result.success(false) },
+ canUserJoinCallResult = { Result.success(true) },
+ typingNoticeResult = { Result.success(Unit) },
+ )
val presenter = createMessagesPresenter(matrixRoom = matrixRoom)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
@@ -756,7 +888,13 @@ class MessagesPresenterTest {
private fun TestScope.createMessagesPresenter(
coroutineDispatchers: CoroutineDispatchers = testCoroutineDispatchers(),
- matrixRoom: MatrixRoom = FakeMatrixRoom().apply {
+ matrixRoom: MatrixRoom = FakeMatrixRoom(
+ canUserSendMessageResult = { _, _ -> Result.success(true) },
+ canRedactOwnResult = { Result.success(true) },
+ canRedactOtherResult = { Result.success(true) },
+ canUserJoinCallResult = { Result.success(true) },
+ typingNoticeResult = { Result.success(Unit) },
+ ).apply {
givenRoomInfo(aRoomInfo(id = roomId, name = ""))
},
navigator: FakeMessagesNavigator = FakeMessagesNavigator(),
diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/attachments/AttachmentsPreviewPresenterTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/attachments/AttachmentsPreviewPresenterTest.kt
index 0168fcbde5..e2fbeebe3a 100644
--- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/attachments/AttachmentsPreviewPresenterTest.kt
+++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/attachments/AttachmentsPreviewPresenterTest.kt
@@ -26,7 +26,9 @@ import com.google.common.truth.Truth.assertThat
import io.element.android.features.messages.impl.attachments.preview.AttachmentsPreviewEvents
import io.element.android.features.messages.impl.attachments.preview.AttachmentsPreviewPresenter
import io.element.android.features.messages.impl.attachments.preview.SendActionState
+import io.element.android.libraries.matrix.api.core.ProgressCallback
import io.element.android.libraries.matrix.api.room.MatrixRoom
+import io.element.android.libraries.matrix.test.media.FakeMediaUploadHandler
import io.element.android.libraries.matrix.test.room.FakeMatrixRoom
import io.element.android.libraries.mediaupload.api.MediaPreProcessor
import io.element.android.libraries.mediaupload.api.MediaSender
@@ -34,6 +36,7 @@ import io.element.android.libraries.mediaupload.test.FakeMediaPreProcessor
import io.element.android.libraries.mediaviewer.api.local.LocalMedia
import io.element.android.libraries.mediaviewer.test.viewer.aLocalMedia
import io.element.android.tests.testutils.WarmUpRule
+import io.element.android.tests.testutils.lambda.lambdaRecorder
import io.mockk.mockk
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runTest
@@ -49,13 +52,16 @@ class AttachmentsPreviewPresenterTest {
@Test
fun `present - send media success scenario`() = runTest {
- val room = FakeMatrixRoom()
- room.givenProgressCallbackValues(
- listOf(
+ val sendMediaResult = lambdaRecorder> {
+ Result.success(FakeMediaUploadHandler())
+ }
+ val room = FakeMatrixRoom(
+ progressCallbackValues = listOf(
Pair(0, 10),
Pair(5, 10),
Pair(10, 10)
- )
+ ),
+ sendMediaResult = sendMediaResult,
)
val presenter = createAttachmentsPreviewPresenter(room = room)
moleculeFlow(RecompositionMode.Immediate) {
@@ -70,15 +76,19 @@ class AttachmentsPreviewPresenterTest {
assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Sending.Uploading(1f))
val successState = awaitItem()
assertThat(successState.sendActionState).isEqualTo(SendActionState.Done)
- assertThat(room.sendMediaCount).isEqualTo(1)
+ sendMediaResult.assertions().isCalledOnce()
}
}
@Test
fun `present - send media failure scenario`() = runTest {
- val room = FakeMatrixRoom()
val failure = MediaPreProcessor.Failure(null)
- room.givenSendMediaResult(Result.failure(failure))
+ val sendMediaResult = lambdaRecorder> {
+ Result.failure(failure)
+ }
+ val room = FakeMatrixRoom(
+ sendMediaResult = sendMediaResult,
+ )
val presenter = createAttachmentsPreviewPresenter(room = room)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
@@ -90,7 +100,7 @@ class AttachmentsPreviewPresenterTest {
assertThat(loadingState.sendActionState).isEqualTo(SendActionState.Sending.Processing)
val failureState = awaitItem()
assertThat(failureState.sendActionState).isEqualTo(SendActionState.Failure(failure))
- assertThat(room.sendMediaCount).isEqualTo(0)
+ sendMediaResult.assertions().isCalledOnce()
failureState.eventSink(AttachmentsPreviewEvents.ClearSendState)
val clearedState = awaitItem()
assertThat(clearedState.sendActionState).isEqualTo(SendActionState.Idle)
diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/report/ReportMessagePresenterTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/report/ReportMessagePresenterTest.kt
index b5408434de..a15ea45a6c 100644
--- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/report/ReportMessagePresenterTest.kt
+++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/report/ReportMessagePresenterTest.kt
@@ -22,11 +22,14 @@ import app.cash.turbine.test
import com.google.common.truth.Truth.assertThat
import io.element.android.libraries.architecture.AsyncAction
import io.element.android.libraries.designsystem.utils.snackbar.SnackbarDispatcher
+import io.element.android.libraries.matrix.api.core.EventId
+import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.api.room.MatrixRoom
import io.element.android.libraries.matrix.test.AN_EVENT_ID
import io.element.android.libraries.matrix.test.A_USER_ID
import io.element.android.libraries.matrix.test.room.FakeMatrixRoom
import io.element.android.tests.testutils.WarmUpRule
+import io.element.android.tests.testutils.lambda.lambdaRecorder
import kotlinx.coroutines.test.runTest
import org.junit.Rule
import org.junit.Test
@@ -81,7 +84,12 @@ class ReportMessagePresenterTest {
@Test
fun `presenter - handle successful report and block user`() = runTest {
- val room = FakeMatrixRoom()
+ val reportContentResult = lambdaRecorder> { _, _, _ ->
+ Result.success(Unit)
+ }
+ val room = FakeMatrixRoom(
+ reportContentResult = reportContentResult
+ )
val presenter = createReportMessagePresenter(matrixRoom = room)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
@@ -92,13 +100,18 @@ class ReportMessagePresenterTest {
initialState.eventSink(ReportMessageEvents.Report)
assertThat(awaitItem().result).isInstanceOf(AsyncAction.Loading::class.java)
assertThat(awaitItem().result).isInstanceOf(AsyncAction.Success::class.java)
- assertThat(room.reportedContentCount).isEqualTo(1)
+ reportContentResult.assertions().isCalledOnce()
}
}
@Test
fun `presenter - handle successful report`() = runTest {
- val room = FakeMatrixRoom()
+ val reportContentResult = lambdaRecorder> { _, _, _ ->
+ Result.success(Unit)
+ }
+ val room = FakeMatrixRoom(
+ reportContentResult = reportContentResult
+ )
val presenter = createReportMessagePresenter(matrixRoom = room)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
@@ -107,15 +120,18 @@ class ReportMessagePresenterTest {
initialState.eventSink(ReportMessageEvents.Report)
assertThat(awaitItem().result).isInstanceOf(AsyncAction.Loading::class.java)
assertThat(awaitItem().result).isInstanceOf(AsyncAction.Success::class.java)
- assertThat(room.reportedContentCount).isEqualTo(1)
+ reportContentResult.assertions().isCalledOnce()
}
}
@Test
fun `presenter - handle failed report`() = runTest {
- val room = FakeMatrixRoom().apply {
- givenReportContentResult(Result.failure(Exception("Failed to report content")))
+ val reportContentResult = lambdaRecorder> { _, _, _ ->
+ Result.failure(Exception("Failed to report content"))
}
+ val room = FakeMatrixRoom(
+ reportContentResult = reportContentResult
+ )
val presenter = createReportMessagePresenter(matrixRoom = room)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
@@ -125,7 +141,7 @@ class ReportMessagePresenterTest {
assertThat(awaitItem().result).isInstanceOf(AsyncAction.Loading::class.java)
val resultState = awaitItem()
assertThat(resultState.result).isInstanceOf(AsyncAction.Failure::class.java)
- assertThat(room.reportedContentCount).isEqualTo(1)
+ reportContentResult.assertions().isCalledOnce()
resultState.eventSink(ReportMessageEvents.ClearError)
assertThat(awaitItem().result).isInstanceOf(AsyncAction.Uninitialized::class.java)
diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/textcomposer/MessageComposerPresenterTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/textcomposer/MessageComposerPresenterTest.kt
index de48ee62b6..dcb09fb7c8 100644
--- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/textcomposer/MessageComposerPresenterTest.kt
+++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/textcomposer/MessageComposerPresenterTest.kt
@@ -43,6 +43,7 @@ import io.element.android.libraries.featureflag.api.FeatureFlagService
import io.element.android.libraries.featureflag.api.FeatureFlags
import io.element.android.libraries.featureflag.test.FakeFeatureFlagService
import io.element.android.libraries.matrix.api.core.EventId
+import io.element.android.libraries.matrix.api.core.ProgressCallback
import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.matrix.api.core.TransactionId
import io.element.android.libraries.matrix.api.media.ImageInfo
@@ -55,6 +56,7 @@ import io.element.android.libraries.matrix.api.room.Mention
import io.element.android.libraries.matrix.api.room.RoomMembershipState
import io.element.android.libraries.matrix.api.room.draft.ComposerDraft
import io.element.android.libraries.matrix.api.room.draft.ComposerDraftType
+import io.element.android.libraries.matrix.api.timeline.TimelineException
import io.element.android.libraries.matrix.api.timeline.item.event.InReplyTo
import io.element.android.libraries.matrix.test.ANOTHER_MESSAGE
import io.element.android.libraries.matrix.test.AN_EVENT_ID
@@ -67,6 +69,7 @@ import io.element.android.libraries.matrix.test.A_USER_ID_2
import io.element.android.libraries.matrix.test.A_USER_ID_3
import io.element.android.libraries.matrix.test.A_USER_ID_4
import io.element.android.libraries.matrix.test.core.aBuildMeta
+import io.element.android.libraries.matrix.test.media.FakeMediaUploadHandler
import io.element.android.libraries.matrix.test.permalink.FakePermalinkBuilder
import io.element.android.libraries.matrix.test.permalink.FakePermalinkParser
import io.element.android.libraries.matrix.test.room.FakeMatrixRoom
@@ -297,7 +300,13 @@ class MessageComposerPresenterTest {
@Test
fun `present - send message with rich text enabled`() = runTest {
- val presenter = createPresenter(this)
+ val presenter = createPresenter(
+ coroutineScope = this,
+ room = FakeMatrixRoom(
+ sendMessageResult = { _, _, _ -> Result.success(Unit) },
+ typingNoticeResult = { Result.success(Unit) }
+ ),
+ )
moleculeFlow(RecompositionMode.Immediate) {
val state = presenter.present()
remember(state, state.textEditorState.messageHtml()) { state }
@@ -324,7 +333,14 @@ class MessageComposerPresenterTest {
@Test
fun `present - send message with plain text enabled`() = runTest {
val permalinkBuilder = FakePermalinkBuilder(permalinkForUserLambda = { Result.success("") })
- val presenter = createPresenter(this, isRichTextEditorEnabled = false)
+ val presenter = createPresenter(
+ coroutineScope = this,
+ isRichTextEditorEnabled = false,
+ room = FakeMatrixRoom(
+ sendMessageResult = { _, _, _ -> Result.success(Unit) },
+ typingNoticeResult = { Result.success(Unit) }
+ ),
+ )
moleculeFlow(RecompositionMode.Immediate) {
val state = presenter.present()
val messageMarkdown = state.textEditorState.messageMarkdown(permalinkBuilder)
@@ -358,7 +374,10 @@ class MessageComposerPresenterTest {
val timeline = FakeTimeline().apply {
this.editMessageLambda = editMessageLambda
}
- val fakeMatrixRoom = FakeMatrixRoom(liveTimeline = timeline)
+ val fakeMatrixRoom = FakeMatrixRoom(
+ liveTimeline = timeline,
+ typingNoticeResult = { Result.success(Unit) }
+ )
val presenter = createPresenter(
this,
fakeMatrixRoom,
@@ -399,6 +418,67 @@ class MessageComposerPresenterTest {
}
}
+ @Test
+ fun `present - edit sent message event not found`() = runTest {
+ val timelineEditMessageLambda = lambdaRecorder { _: EventId?, _: TransactionId?, _: String, _: String?, _: List ->
+ Result.failure(TimelineException.EventNotFound)
+ }
+ val timeline = FakeTimeline().apply {
+ this.editMessageLambda = timelineEditMessageLambda
+ }
+ val roomEditMessageLambda = lambdaRecorder { _: EventId?, _: String, _: String?, _: List ->
+ Result.success(Unit)
+ }
+ val fakeMatrixRoom = FakeMatrixRoom(
+ liveTimeline = timeline,
+ typingNoticeResult = { Result.success(Unit) }
+ ).apply {
+ this.editMessageLambda = roomEditMessageLambda
+ }
+ val presenter = createPresenter(
+ this,
+ fakeMatrixRoom,
+ )
+ moleculeFlow(RecompositionMode.Immediate) {
+ val state = presenter.present()
+ remember(state, state.textEditorState.messageHtml()) { state }
+ }.test {
+ val initialState = awaitFirstItem()
+ assertThat(initialState.textEditorState.messageHtml()).isEqualTo("")
+ val mode = anEditMode()
+ initialState.eventSink.invoke(MessageComposerEvents.SetMode(mode))
+ val withMessageState = awaitItem()
+ assertThat(withMessageState.mode).isEqualTo(mode)
+ assertThat(withMessageState.textEditorState.messageHtml()).isEqualTo(A_MESSAGE)
+ withMessageState.textEditorState.setHtml(ANOTHER_MESSAGE)
+ val withEditedMessageState = awaitItem()
+ assertThat(withEditedMessageState.textEditorState.messageHtml()).isEqualTo(ANOTHER_MESSAGE)
+ withEditedMessageState.eventSink.invoke(MessageComposerEvents.SendMessage)
+ skipItems(1)
+ val messageSentState = awaitItem()
+ assertThat(messageSentState.textEditorState.messageHtml()).isEqualTo("")
+
+ advanceUntilIdle()
+
+ assert(timelineEditMessageLambda)
+ .isCalledOnce()
+ .with(value(AN_EVENT_ID), value(null), value(ANOTHER_MESSAGE), value(ANOTHER_MESSAGE), any())
+
+ assert(roomEditMessageLambda)
+ .isCalledOnce()
+ .with(value(AN_EVENT_ID), value(ANOTHER_MESSAGE), value(ANOTHER_MESSAGE), any())
+
+ assertThat(analyticsService.capturedEvents).containsExactly(
+ Composer(
+ inThread = false,
+ isEditing = true,
+ isReply = false,
+ messageType = Composer.MessageType.Text,
+ )
+ )
+ }
+ }
+
@Test
fun `present - edit not sent message`() = runTest {
val editMessageLambda = lambdaRecorder { _: EventId?, _: TransactionId?, _: String, _: String?, _: List ->
@@ -407,7 +487,10 @@ class MessageComposerPresenterTest {
val timeline = FakeTimeline().apply {
this.editMessageLambda = editMessageLambda
}
- val fakeMatrixRoom = FakeMatrixRoom(liveTimeline = timeline)
+ val fakeMatrixRoom = FakeMatrixRoom(
+ liveTimeline = timeline,
+ typingNoticeResult = { Result.success(Unit) },
+ )
val presenter = createPresenter(
this,
fakeMatrixRoom,
@@ -456,7 +539,10 @@ class MessageComposerPresenterTest {
val timeline = FakeTimeline().apply {
this.replyMessageLambda = replyMessageLambda
}
- val fakeMatrixRoom = FakeMatrixRoom(liveTimeline = timeline)
+ val fakeMatrixRoom = FakeMatrixRoom(
+ liveTimeline = timeline,
+ typingNoticeResult = { Result.success(Unit) }
+ )
val presenter = createPresenter(
this,
fakeMatrixRoom,
@@ -524,7 +610,9 @@ class MessageComposerPresenterTest {
@Test
fun `present - Pick image from gallery`() = runTest {
- val room = FakeMatrixRoom()
+ val room = FakeMatrixRoom(
+ typingNoticeResult = { Result.success(Unit) }
+ )
val presenter = createPresenter(this, room = room)
pickerProvider.givenMimeType(MimeTypes.Images)
mediaPreProcessor.givenResult(
@@ -557,7 +645,9 @@ class MessageComposerPresenterTest {
@Test
fun `present - Pick video from gallery`() = runTest {
- val room = FakeMatrixRoom()
+ val room = FakeMatrixRoom(
+ typingNoticeResult = { Result.success(Unit) }
+ )
val presenter = createPresenter(this, room = room)
pickerProvider.givenMimeType(MimeTypes.Videos)
mediaPreProcessor.givenResult(
@@ -607,13 +697,17 @@ class MessageComposerPresenterTest {
@Test
fun `present - Pick file from storage`() = runTest {
- val room = FakeMatrixRoom()
- room.givenProgressCallbackValues(
- listOf(
+ val sendMediaResult = lambdaRecorder { _: ProgressCallback? ->
+ Result.success(FakeMediaUploadHandler())
+ }
+ val room = FakeMatrixRoom(
+ progressCallbackValues = listOf(
Pair(0, 10),
Pair(5, 10),
Pair(10, 10)
- )
+ ),
+ sendMediaResult = sendMediaResult,
+ typingNoticeResult = { Result.success(Unit) }
)
val presenter = createPresenter(this, room = room)
moleculeFlow(RecompositionMode.Immediate) {
@@ -629,13 +723,15 @@ class MessageComposerPresenterTest {
assertThat(awaitItem().attachmentsState).isEqualTo(AttachmentsState.Sending.Uploading(1f))
val sentState = awaitItem()
assertThat(sentState.attachmentsState).isEqualTo(AttachmentsState.None)
- assertThat(room.sendMediaCount).isEqualTo(1)
+ sendMediaResult.assertions().isCalledOnce()
}
}
@Test
fun `present - create poll`() = runTest {
- val room = FakeMatrixRoom()
+ val room = FakeMatrixRoom(
+ typingNoticeResult = { Result.success(Unit) }
+ )
val presenter = createPresenter(this, room = room)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
@@ -652,7 +748,9 @@ class MessageComposerPresenterTest {
@Test
fun `present - share location`() = runTest {
- val room = FakeMatrixRoom()
+ val room = FakeMatrixRoom(
+ typingNoticeResult = { Result.success(Unit) }
+ )
val presenter = createPresenter(this, room = room)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
@@ -669,7 +767,9 @@ class MessageComposerPresenterTest {
@Test
fun `present - Take photo`() = runTest {
- val room = FakeMatrixRoom()
+ val room = FakeMatrixRoom(
+ typingNoticeResult = { Result.success(Unit) }
+ )
val permissionPresenter = FakePermissionsPresenter().apply { setPermissionGranted() }
val presenter = createPresenter(
this,
@@ -689,7 +789,9 @@ class MessageComposerPresenterTest {
@Test
fun `present - Take photo with permission request`() = runTest {
- val room = FakeMatrixRoom()
+ val room = FakeMatrixRoom(
+ typingNoticeResult = { Result.success(Unit) }
+ )
val permissionPresenter = FakePermissionsPresenter()
val presenter = createPresenter(
this,
@@ -714,7 +816,9 @@ class MessageComposerPresenterTest {
@Test
fun `present - Record video`() = runTest {
- val room = FakeMatrixRoom()
+ val room = FakeMatrixRoom(
+ typingNoticeResult = { Result.success(Unit) }
+ )
val permissionPresenter = FakePermissionsPresenter().apply { setPermissionGranted() }
val presenter = createPresenter(
this,
@@ -734,7 +838,9 @@ class MessageComposerPresenterTest {
@Test
fun `present - Record video with permission request`() = runTest {
- val room = FakeMatrixRoom()
+ val room = FakeMatrixRoom(
+ typingNoticeResult = { Result.success(Unit) }
+ )
val permissionPresenter = FakePermissionsPresenter()
val presenter = createPresenter(
this,
@@ -759,9 +865,10 @@ class MessageComposerPresenterTest {
@Test
fun `present - Uploading media failure can be recovered from`() = runTest {
- val room = FakeMatrixRoom().apply {
- givenSendMediaResult(Result.failure(Exception()))
- }
+ val room = FakeMatrixRoom(
+ sendMediaResult = { Result.failure(Exception()) },
+ typingNoticeResult = { Result.success(Unit) }
+ )
val presenter = createPresenter(this, room = room)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
@@ -842,15 +949,17 @@ class MessageComposerPresenterTest {
val invitedUser = aRoomMember(userId = A_USER_ID_3, membership = RoomMembershipState.INVITE)
val bob = aRoomMember(userId = A_USER_ID_2, membership = RoomMembershipState.JOIN)
val david = aRoomMember(userId = A_USER_ID_4, displayName = "Dave", membership = RoomMembershipState.JOIN)
+ var canUserTriggerRoomNotificationResult = true
val room = FakeMatrixRoom(
isDirect = false,
+ canUserTriggerRoomNotificationResult = { Result.success(canUserTriggerRoomNotificationResult) },
+ typingNoticeResult = { Result.success(Unit) }
).apply {
givenRoomMembersState(
MatrixRoomMembersState.Ready(
persistentListOf(currentUser, invitedUser, bob, david),
)
)
- givenCanTriggerRoomNotification(Result.success(true))
}
val flagsService = FakeFeatureFlagService(
mapOf(
@@ -890,13 +999,10 @@ class MessageComposerPresenterTest {
assertThat(awaitItem().memberSuggestions).isEmpty()
// If user has no permission to send `@room` mentions, `RoomMemberSuggestion.Room` is not returned
- room.givenCanTriggerRoomNotification(Result.success(false))
+ canUserTriggerRoomNotificationResult = false
initialState.eventSink(MessageComposerEvents.SuggestionReceived(Suggestion(0, 0, SuggestionType.Mention, "")))
assertThat(awaitItem().memberSuggestions)
.containsExactly(ResolvedMentionSuggestion.Member(bob), ResolvedMentionSuggestion.Member(david))
-
- // If room is a DM, `RoomMemberSuggestion.Room` is not returned
- room.givenCanTriggerRoomNotification(Result.success(true))
}
}
@@ -910,13 +1016,14 @@ class MessageComposerPresenterTest {
isDirect = true,
activeMemberCount = 2,
isEncrypted = true,
+ canUserTriggerRoomNotificationResult = { Result.success(true) },
+ typingNoticeResult = { Result.success(Unit) }
).apply {
givenRoomMembersState(
MatrixRoomMembersState.Ready(
persistentListOf(currentUser, invitedUser, bob, david),
)
)
- givenCanTriggerRoomNotification(Result.success(true))
}
val flagsService = FakeFeatureFlagService(
mapOf(
@@ -973,7 +1080,14 @@ class MessageComposerPresenterTest {
this.replyMessageLambda = replyMessageLambda
this.editMessageLambda = editMessageLambda
}
- val room = FakeMatrixRoom(liveTimeline = timeline)
+ val sendMessageResult = lambdaRecorder { _: String, _: String?, _: List ->
+ Result.success(Unit)
+ }
+ val room = FakeMatrixRoom(
+ liveTimeline = timeline,
+ sendMessageResult = sendMessageResult,
+ typingNoticeResult = { Result.success(Unit) }
+ )
val presenter = createPresenter(room = room, coroutineScope = this)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
@@ -993,7 +1107,8 @@ class MessageComposerPresenterTest {
advanceUntilIdle()
- assertThat(room.sendMessageMentions).isEqualTo(listOf(Mention.User(A_USER_ID)))
+ sendMessageResult.assertions().isCalledOnce()
+ .with(value(A_MESSAGE), any(), value(listOf(Mention.User(A_USER_ID))))
// Check intentional mentions on reply sent
initialState.eventSink(MessageComposerEvents.SetMode(aReplyMode()))
@@ -1049,22 +1164,32 @@ class MessageComposerPresenterTest {
@Test
fun `present - handle typing notice event`() = runTest {
- val room = FakeMatrixRoom()
+ val typingNoticeResult = lambdaRecorder> { Result.success(Unit) }
+ val room = FakeMatrixRoom(
+ typingNoticeResult = typingNoticeResult
+ )
val presenter = createPresenter(room = room, coroutineScope = this)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
val initialState = awaitFirstItem()
- assertThat(room.typingRecord).isEmpty()
+ typingNoticeResult.assertions().isNeverCalled()
initialState.eventSink.invoke(MessageComposerEvents.TypingNotice(true))
initialState.eventSink.invoke(MessageComposerEvents.TypingNotice(false))
- assertThat(room.typingRecord).isEqualTo(listOf(true, false))
+ typingNoticeResult.assertions().isCalledExactly(2)
+ .withSequence(
+ listOf(value(true)),
+ listOf(value(false)),
+ )
}
}
@Test
fun `present - handle typing notice event when sending typing notice is disabled`() = runTest {
- val room = FakeMatrixRoom()
+ val typingNoticeResult = lambdaRecorder> { Result.success(Unit) }
+ val room = FakeMatrixRoom(
+ typingNoticeResult = typingNoticeResult
+ )
val store = InMemorySessionPreferencesStore(
isSendTypingNotificationsEnabled = false
)
@@ -1073,10 +1198,10 @@ class MessageComposerPresenterTest {
presenter.present()
}.test {
val initialState = awaitFirstItem()
- assertThat(room.typingRecord).isEmpty()
+ typingNoticeResult.assertions().isNeverCalled()
initialState.eventSink.invoke(MessageComposerEvents.TypingNotice(true))
initialState.eventSink.invoke(MessageComposerEvents.TypingNotice(false))
- assertThat(room.typingRecord).isEmpty()
+ typingNoticeResult.assertions().isNeverCalled()
}
}
@@ -1215,7 +1340,10 @@ class MessageComposerPresenterTest {
val timeline = FakeTimeline().apply {
this.loadReplyDetailsLambda = loadReplyDetailsLambda
}
- val room = FakeMatrixRoom(liveTimeline = timeline)
+ val room = FakeMatrixRoom(
+ liveTimeline = timeline,
+ typingNoticeResult = { Result.success(Unit) },
+ )
val permalinkBuilder = FakePermalinkBuilder()
val presenter = createPresenter(
room = room,
@@ -1352,7 +1480,9 @@ class MessageComposerPresenterTest {
private fun createPresenter(
coroutineScope: CoroutineScope,
- room: MatrixRoom = FakeMatrixRoom(),
+ room: MatrixRoom = FakeMatrixRoom(
+ typingNoticeResult = { Result.success(Unit) }
+ ),
pickerProvider: PickerProvider = this.pickerProvider,
featureFlagService: FeatureFlagService = this.featureFlagService,
sessionPreferencesStore: SessionPreferencesStore = InMemorySessionPreferencesStore(),
diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/TimelineControllerTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/TimelineControllerTest.kt
index 58cf11c4a7..887ce88d95 100644
--- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/TimelineControllerTest.kt
+++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/TimelineControllerTest.kt
@@ -26,6 +26,7 @@ import io.element.android.libraries.matrix.test.A_UNIQUE_ID
import io.element.android.libraries.matrix.test.room.FakeMatrixRoom
import io.element.android.libraries.matrix.test.timeline.FakeTimeline
import io.element.android.libraries.matrix.test.timeline.anEventTimelineItem
+import io.element.android.tests.testutils.lambda.lambdaError
import io.element.android.tests.testutils.lambda.lambdaRecorder
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.flowOf
@@ -38,9 +39,9 @@ class TimelineControllerTest {
val liveTimeline = FakeTimeline(name = "live")
val detachedTimeline = FakeTimeline(name = "detached")
val matrixRoom = FakeMatrixRoom(
- liveTimeline = liveTimeline
+ liveTimeline = liveTimeline,
+ timelineFocusedOnEventResult = { Result.success(detachedTimeline) }
)
- matrixRoom.givenTimelineFocusedOnEventResult(Result.success(detachedTimeline))
val sut = TimelineController(matrixRoom)
sut.activeTimelineFlow().test {
@@ -68,8 +69,17 @@ class TimelineControllerTest {
val liveTimeline = FakeTimeline(name = "live")
val detachedTimeline1 = FakeTimeline(name = "detached 1")
val detachedTimeline2 = FakeTimeline(name = "detached 2")
+ var callNumber = 0
val matrixRoom = FakeMatrixRoom(
- liveTimeline = liveTimeline
+ liveTimeline = liveTimeline,
+ timelineFocusedOnEventResult = {
+ callNumber++
+ when (callNumber) {
+ 1 -> Result.success(detachedTimeline1)
+ 2 -> Result.success(detachedTimeline2)
+ else -> lambdaError()
+ }
+ }
)
val sut = TimelineController(matrixRoom)
@@ -77,7 +87,6 @@ class TimelineControllerTest {
awaitItem().also { state ->
assertThat(state).isEqualTo(liveTimeline)
}
- matrixRoom.givenTimelineFocusedOnEventResult(Result.success(detachedTimeline1))
sut.focusOnEvent(AN_EVENT_ID)
awaitItem().also { state ->
assertThat(state).isEqualTo(detachedTimeline1)
@@ -85,7 +94,6 @@ class TimelineControllerTest {
assertThat(detachedTimeline1.closeCounter).isEqualTo(0)
assertThat(detachedTimeline2.closeCounter).isEqualTo(0)
// Focus on another event should close the previous detached timeline
- matrixRoom.givenTimelineFocusedOnEventResult(Result.success(detachedTimeline2))
sut.focusOnEvent(AN_EVENT_ID)
awaitItem().also { state ->
assertThat(state).isEqualTo(detachedTimeline2)
@@ -117,11 +125,10 @@ class TimelineControllerTest {
val liveTimeline = FakeTimeline(name = "live")
val detachedTimeline = FakeTimeline(name = "detached")
val matrixRoom = FakeMatrixRoom(
- liveTimeline = liveTimeline
+ liveTimeline = liveTimeline,
+ timelineFocusedOnEventResult = { Result.success(detachedTimeline) }
)
- matrixRoom.givenTimelineFocusedOnEventResult(Result.success(detachedTimeline))
val sut = TimelineController(matrixRoom)
-
sut.activeTimelineFlow().test {
awaitItem().also { state ->
assertThat(state).isEqualTo(liveTimeline)
@@ -168,9 +175,9 @@ class TimelineControllerTest {
sendMessageLambda = lambdaForDetached
}
val matrixRoom = FakeMatrixRoom(
- liveTimeline = liveTimeline
+ liveTimeline = liveTimeline,
+ timelineFocusedOnEventResult = { Result.success(detachedTimeline) }
)
- matrixRoom.givenTimelineFocusedOnEventResult(Result.success(detachedTimeline))
val sut = TimelineController(matrixRoom)
sut.activeTimelineFlow().test {
sut.focusOnEvent(AN_EVENT_ID)
@@ -193,9 +200,9 @@ class TimelineControllerTest {
val liveTimeline = FakeTimeline(name = "live")
val detachedTimeline = FakeTimeline(name = "detached")
val matrixRoom = FakeMatrixRoom(
- liveTimeline = liveTimeline
+ liveTimeline = liveTimeline,
+ timelineFocusedOnEventResult = { Result.success(detachedTimeline) }
)
- matrixRoom.givenTimelineFocusedOnEventResult(Result.success(detachedTimeline))
val sut = TimelineController(matrixRoom)
sut.activeTimelineFlow().test {
diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/TimelinePresenterTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/TimelinePresenterTest.kt
index ad52af11ca..5a171227e7 100644
--- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/TimelinePresenterTest.kt
+++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/TimelinePresenterTest.kt
@@ -133,7 +133,10 @@ private const val FAKE_UNIQUE_ID_2 = "FAKE_UNIQUE_ID_2"
)
)
)
- val room = FakeMatrixRoom(liveTimeline = timeline)
+ val room = FakeMatrixRoom(
+ liveTimeline = timeline,
+ canUserSendMessageResult = { _, _ -> Result.success(true) },
+ )
val sessionPreferencesStore = InMemorySessionPreferencesStore(isSendPublicReadReceiptsEnabled = false)
val presenter = createTimelinePresenter(
timeline = timeline,
@@ -482,9 +485,9 @@ private const val FAKE_UNIQUE_ID_2 = "FAKE_UNIQUE_ID_2"
)
val room = FakeMatrixRoom(
liveTimeline = liveTimeline,
- ).apply {
- givenTimelineFocusedOnEventResult(Result.success(detachedTimeline))
- }
+ timelineFocusedOnEventResult = { Result.success(detachedTimeline) },
+ canUserSendMessageResult = { _, _ -> Result.success(true) },
+ )
val presenter = createTimelinePresenter(
room = room,
)
@@ -529,6 +532,7 @@ private const val FAKE_UNIQUE_ID_2 = "FAKE_UNIQUE_ID_2"
)
)
),
+ canUserSendMessageResult = { _, _ -> Result.success(true) },
),
timelineItemIndexer = timelineItemIndexer,
)
@@ -551,9 +555,9 @@ private const val FAKE_UNIQUE_ID_2 = "FAKE_UNIQUE_ID_2"
liveTimeline = FakeTimeline(
timelineItems = flowOf(emptyList()),
),
- ).apply {
- givenTimelineFocusedOnEventResult(Result.failure(Throwable("An error")))
- },
+ timelineFocusedOnEventResult = { Result.failure(Throwable("An error")) },
+ canUserSendMessageResult = { _, _ -> Result.success(true) },
+ )
)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
@@ -594,7 +598,10 @@ private const val FAKE_UNIQUE_ID_2 = "FAKE_UNIQUE_ID_2"
)
)
)
- val room = FakeMatrixRoom(liveTimeline = timeline).apply {
+ val room = FakeMatrixRoom(
+ liveTimeline = timeline,
+ canUserSendMessageResult = { _, _ -> Result.success(true) },
+ ).apply {
givenRoomMembersState(MatrixRoomMembersState.Unknown)
}
@@ -626,7 +633,10 @@ private const val FAKE_UNIQUE_ID_2 = "FAKE_UNIQUE_ID_2"
private fun TestScope.createTimelinePresenter(
timeline: Timeline = FakeTimeline(),
- room: FakeMatrixRoom = FakeMatrixRoom(liveTimeline = timeline),
+ room: FakeMatrixRoom = FakeMatrixRoom(
+ liveTimeline = timeline,
+ canUserSendMessageResult = { _, _ -> Result.success(true) }
+ ),
timelineItemsFactory: TimelineItemsFactory = aTimelineItemsFactory(),
redactedVoiceMessageManager: RedactedVoiceMessageManager = FakeRedactedVoiceMessageManager(),
messagesNavigator: FakeMessagesNavigator = FakeMessagesNavigator(),
diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentMessageFactoryTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentMessageFactoryTest.kt
index 59cd3cf383..ffb247ed1a 100644
--- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentMessageFactoryTest.kt
+++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentMessageFactoryTest.kt
@@ -20,9 +20,11 @@ import android.net.Uri
import android.text.SpannableString
import android.text.SpannableStringBuilder
import android.text.Spanned
+import android.text.SpannedString
import android.text.style.URLSpan
import androidx.core.text.buildSpannedString
import androidx.core.text.inSpans
+import androidx.core.text.toSpannable
import com.google.common.truth.Truth.assertThat
import io.element.android.features.location.api.Location
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemAudioContent
@@ -74,6 +76,7 @@ import io.element.android.libraries.mediaviewer.api.util.FileExtensionExtractorW
import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.toImmutableList
import kotlinx.coroutines.test.runTest
+import org.junit.Assert.fail
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner
@@ -195,7 +198,7 @@ class TimelineItemContentMessageFactoryTest {
inSpans(URLSpan("https://matrix.org")) {
append("and manually added link")
}
- }
+ }.toSpannable()
val sut = createTimelineItemContentMessageFactory(
htmlConverterTransform = { expected }
)
@@ -610,7 +613,7 @@ class TimelineItemContentMessageFactoryTest {
senderDisambiguatedDisplayName = "Bob",
eventId = AN_EVENT_ID,
)
- assertThat((result as TimelineItemNoticeContent).formattedBody).isEqualTo("formatted")
+ (result as TimelineItemNoticeContent).formattedBody.assertSpannedEquals(SpannedString("formatted"))
}
@Test
@@ -644,7 +647,8 @@ class TimelineItemContentMessageFactoryTest {
senderDisambiguatedDisplayName = "Bob",
eventId = AN_EVENT_ID,
)
- assertThat((result as TimelineItemEmoteContent).formattedBody).isEqualTo(SpannableString("* Bob formatted"))
+
+ (result as TimelineItemEmoteContent).formattedBody.assertSpannedEquals(SpannableString("* Bob formatted"))
}
@Test
@@ -654,7 +658,7 @@ class TimelineItemContentMessageFactoryTest {
inSpans(URLSpan("https://www.example.org")) {
append("me@matrix.org")
}
- }
+ }.toSpannable()
val sut = createTimelineItemContentMessageFactory(
htmlConverterTransform = { expectedSpanned },
permalinkParser = FakePermalinkParser { PermalinkData.FallbackLink(Uri.EMPTY) }
@@ -669,7 +673,59 @@ class TimelineItemContentMessageFactoryTest {
senderDisambiguatedDisplayName = "Bob",
eventId = AN_EVENT_ID,
)
- assertThat((result as TimelineItemTextContent).formattedBody).isEqualTo(expectedSpanned)
+ (result as TimelineItemTextContent).formattedBody.assertSpannedEquals(expectedSpanned)
+ }
+
+ @Test
+ fun `a message with plain URL in a formatted body Spanned format gets linkified too`() = runTest {
+ val expectedSpanned = buildSpannedString {
+ append("Test ")
+ inSpansWithFlags(URLSpan("https://www.example.org"), flags = Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) {
+ append("https://www.example.org")
+ }
+ }
+ val sut = createTimelineItemContentMessageFactory(
+ htmlConverterTransform = { expectedSpanned },
+ permalinkParser = FakePermalinkParser { PermalinkData.FallbackLink(Uri.EMPTY) }
+ )
+ val result = sut.create(
+ content = createMessageContent(
+ type = TextMessageType(
+ body = "Test [me@matrix.org](https://www.example.org)",
+ formatted = FormattedBody(MessageFormat.HTML, "Test https://www.example.org")
+ )
+ ),
+ senderDisambiguatedDisplayName = "Bob",
+ eventId = AN_EVENT_ID,
+ )
+ (result as TimelineItemTextContent).formattedBody.assertSpannedEquals(expectedSpanned)
+ }
+
+ @Test
+ fun `a message with plain URL in a formatted body with plain text format gets linkified too`() = runTest {
+ val resultString = "Test https://www.example.org"
+ val expectedSpanned = buildSpannedString {
+ append("Test ")
+ inSpansWithFlags(URLSpan("https://www.example.org"), flags = Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) {
+ append("https://www.example.org")
+ }
+ }.toSpannable()
+ val sut = createTimelineItemContentMessageFactory(
+ htmlConverterTransform = { resultString },
+ permalinkParser = FakePermalinkParser { PermalinkData.FallbackLink(Uri.EMPTY) }
+ )
+ val result = sut.create(
+ content = createMessageContent(
+ type = TextMessageType(
+ body = "Test [me@matrix.org](https://www.example.org)",
+ formatted = FormattedBody(MessageFormat.HTML, "Test https://www.example.org")
+ )
+ ),
+ senderDisambiguatedDisplayName = "Bob",
+ eventId = AN_EVENT_ID,
+ )
+
+ (result as TimelineItemTextContent).formattedBody.assertSpannedEquals(expectedSpanned)
}
private fun createMessageContent(
@@ -718,3 +774,40 @@ class TimelineItemContentMessageFactoryTest {
fileExtensionExtractor = FileExtensionExtractorWithoutValidation()
)
}
+
+private inline fun SpannableStringBuilder.inSpansWithFlags(span: Any, flags: Int, action: SpannableStringBuilder.() -> Unit) {
+ val start = this.length
+ action()
+ val end = this.length
+ setSpan(span, start, end, flags)
+}
+
+fun CharSequence?.assertSpannedEquals(other: CharSequence?) {
+ if (this == null && other == null) {
+ return
+ } else if (this is Spanned && other is Spanned) {
+ assertThat(this.toString()).isEqualTo(other.toString())
+ assertThat(this.length).isEqualTo(other.length)
+ val thisSpans = this.getSpans(0, this.length, Any::class.java)
+ val otherSpans = other.getSpans(0, other.length, Any::class.java)
+ if (thisSpans.size != otherSpans.size) {
+ fail("Expected ${thisSpans.size} spans, got ${otherSpans.size}")
+ }
+ thisSpans.forEachIndexed { index, span ->
+ val otherSpan = otherSpans[index]
+ // URLSpans don't have a proper `equals` implementation, so we compare the URL instead
+ if (span is URLSpan && otherSpan is URLSpan) {
+ assertThat(span.url).isEqualTo(otherSpan.url)
+ } else {
+ assertThat(span).isEqualTo(otherSpan)
+ }
+ assertThat(this.getSpanStart(span)).isEqualTo(other.getSpanStart(otherSpan))
+ assertThat(this.getSpanEnd(span)).isEqualTo(other.getSpanEnd(otherSpan))
+ assertThat(this.getSpanFlags(span)).isEqualTo(other.getSpanFlags(otherSpan))
+ }
+ } else {
+ val thisString = this?.toString() ?: "null"
+ val otherString = other?.toString() ?: "null"
+ fail("Expected Spanned, got $thisString and $otherString")
+ }
+}
diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/voicemessages/composer/VoiceMessageComposerPresenterTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/voicemessages/composer/VoiceMessageComposerPresenterTest.kt
index 1c08a76e3d..0e507bdecf 100644
--- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/voicemessages/composer/VoiceMessageComposerPresenterTest.kt
+++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/voicemessages/composer/VoiceMessageComposerPresenterTest.kt
@@ -28,7 +28,9 @@ import com.google.common.truth.Truth.assertThat
import im.vector.app.features.analytics.plan.Composer
import io.element.android.features.messages.impl.voicemessages.VoiceMessageException
import io.element.android.features.messages.test.FakeMessageComposerContext
+import io.element.android.libraries.matrix.api.core.ProgressCallback
import io.element.android.libraries.matrix.test.AN_EVENT_ID
+import io.element.android.libraries.matrix.test.media.FakeMediaUploadHandler
import io.element.android.libraries.matrix.test.room.FakeMatrixRoom
import io.element.android.libraries.matrix.ui.messages.reply.InReplyToDetails
import io.element.android.libraries.mediaplayer.test.FakeMediaPlayer
@@ -45,6 +47,7 @@ import io.element.android.libraries.textcomposer.model.VoiceMessageState
import io.element.android.libraries.voicerecorder.test.FakeVoiceRecorder
import io.element.android.services.analytics.test.FakeAnalyticsService
import io.element.android.tests.testutils.WarmUpRule
+import io.element.android.tests.testutils.lambda.lambdaRecorder
import kotlinx.collections.immutable.toImmutableList
import kotlinx.collections.immutable.toPersistentList
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -63,7 +66,10 @@ class VoiceMessageComposerPresenterTest {
recordingDuration = RECORDING_DURATION
)
private val analyticsService = FakeAnalyticsService()
- private val matrixRoom = FakeMatrixRoom()
+ private val sendMediaResult = lambdaRecorder> { Result.success(FakeMediaUploadHandler()) }
+ private val matrixRoom = FakeMatrixRoom(
+ sendMediaResult = sendMediaResult
+ )
private val mediaPreProcessor = FakeMediaPreProcessor().apply { givenAudioResult() }
private val mediaSender = MediaSender(mediaPreProcessor, matrixRoom)
private val messageComposerContext = FakeMessageComposerContext()
@@ -295,7 +301,7 @@ class VoiceMessageComposerPresenterTest {
val finalState = awaitItem()
assertThat(finalState.voiceMessageState).isEqualTo(VoiceMessageState.Idle)
- assertThat(matrixRoom.sendMediaCount).isEqualTo(1)
+ sendMediaResult.assertions().isCalledOnce()
voiceRecorder.assertCalls(started = 1, stopped = 1, deleted = 1)
testPauseAndDestroy(finalState)
@@ -346,7 +352,7 @@ class VoiceMessageComposerPresenterTest {
val finalState = awaitItem()
assertThat(finalState.voiceMessageState).isEqualTo(VoiceMessageState.Idle)
- assertThat(matrixRoom.sendMediaCount).isEqualTo(1)
+ sendMediaResult.assertions().isCalledOnce()
voiceRecorder.assertCalls(started = 1, stopped = 1, deleted = 1)
testPauseAndDestroy(finalState)
@@ -369,7 +375,7 @@ class VoiceMessageComposerPresenterTest {
val finalState = awaitItem()
assertThat(finalState.voiceMessageState).isEqualTo(VoiceMessageState.Idle)
- assertThat(matrixRoom.sendMediaCount).isEqualTo(1)
+ sendMediaResult.assertions().isCalledOnce()
voiceRecorder.assertCalls(started = 1, stopped = 1, deleted = 1)
testPauseAndDestroy(finalState)
@@ -393,7 +399,7 @@ class VoiceMessageComposerPresenterTest {
val finalState = awaitItem()
assertThat(finalState.voiceMessageState).isEqualTo(aPreviewState(isSending = true))
- assertThat(matrixRoom.sendMediaCount).isEqualTo(0)
+ sendMediaResult.assertions().isNeverCalled()
assertThat(analyticsService.trackedErrors).hasSize(0)
voiceRecorder.assertCalls(started = 1, stopped = 1, deleted = 0)
@@ -418,13 +424,13 @@ class VoiceMessageComposerPresenterTest {
ensureAllEventsConsumed()
assertThat(previewState.voiceMessageState).isEqualTo(aPreviewState())
- assertThat(matrixRoom.sendMediaCount).isEqualTo(0)
+ sendMediaResult.assertions().isNeverCalled()
mediaPreProcessor.givenAudioResult()
previewState.eventSink(VoiceMessageComposerEvents.SendVoiceMessage)
val finalState = awaitItem()
assertThat(finalState.voiceMessageState).isEqualTo(VoiceMessageState.Idle)
- assertThat(matrixRoom.sendMediaCount).isEqualTo(1)
+ sendMediaResult.assertions().isCalledOnce()
voiceRecorder.assertCalls(started = 1, stopped = 1, deleted = 1)
testPauseAndDestroy(finalState)
@@ -461,7 +467,7 @@ class VoiceMessageComposerPresenterTest {
assertThat(showSendFailureDialog).isFalse()
}
- assertThat(matrixRoom.sendMediaCount).isEqualTo(0)
+ sendMediaResult.assertions().isNeverCalled()
testPauseAndDestroy(finalState)
}
}
@@ -477,7 +483,7 @@ class VoiceMessageComposerPresenterTest {
initialState.eventSink(VoiceMessageComposerEvents.SendVoiceMessage)
assertThat(initialState.voiceMessageState).isEqualTo(VoiceMessageState.Idle)
- assertThat(matrixRoom.sendMediaCount).isEqualTo(0)
+ sendMediaResult.assertions().isNeverCalled()
assertThat(analyticsService.trackedErrors).hasSize(1)
voiceRecorder.assertCalls(started = 0)
@@ -496,7 +502,7 @@ class VoiceMessageComposerPresenterTest {
val initialState = awaitItem()
initialState.eventSink(VoiceMessageComposerEvents.RecorderEvent(VoiceMessageRecorderEvent.Start))
- assertThat(matrixRoom.sendMediaCount).isEqualTo(0)
+ sendMediaResult.assertions().isNeverCalled()
assertThat(analyticsService.trackedErrors).containsExactly(
VoiceMessageException.PermissionMissing(message = "Expected permission to record but none", cause = exception)
)
diff --git a/features/onboarding/impl/src/main/res/values-pl/translations.xml b/features/onboarding/impl/src/main/res/values-pl/translations.xml
new file mode 100644
index 0000000000..5ac4d81c27
--- /dev/null
+++ b/features/onboarding/impl/src/main/res/values-pl/translations.xml
@@ -0,0 +1,9 @@
+
+
+ "Zaloguj się ręcznie"
+ "Zaloguj się za pomocą kodu QR"
+ "Utwórz konto"
+ "Witamy w %1$s. Szybszy i prostszy niż kiedykolwiek."
+ "Witamy w %1$s. Doładowany, dla szybkości i prostoty."
+ "Be in your element"
+
diff --git a/features/onboarding/impl/src/main/res/values-pt-rBR/translations.xml b/features/onboarding/impl/src/main/res/values-pt-rBR/translations.xml
new file mode 100644
index 0000000000..04a26fe212
--- /dev/null
+++ b/features/onboarding/impl/src/main/res/values-pt-rBR/translations.xml
@@ -0,0 +1,8 @@
+
+
+ "Iniciar sessão manualmente"
+ "Iniciar sessão com código QR"
+ "Criar conta"
+ "Bem-vindo ao mais rápido %1$s de todos os tempos. Turbinado para velocidade e simplicidade."
+ "Bem-vindo ao %1$s. Turbinado, para velocidade e simplicidade"
+
diff --git a/features/poll/impl/src/main/res/values-ka/translations.xml b/features/poll/impl/src/main/res/values-ka/translations.xml
index c2417983af..0cdbc950b8 100644
--- a/features/poll/impl/src/main/res/values-ka/translations.xml
+++ b/features/poll/impl/src/main/res/values-ka/translations.xml
@@ -4,8 +4,16 @@
"შედეგების ჩვენება მხოლოდ გამოკითხვის დასრულების შემდეგ"
"ხმების დამალვა"
"ვარიანტი %1$d"
+ "თქვენი ცვლილებები არ არის შენახული. დარწმუნებული ხართ, რომ გსურთ დაბრუნება?"
"კითხვა ან თემა"
"რას ეხება გამოკითხვა?"
"გამოკითხვის შექმნა"
+ "დარწმუნებული ხართ, რომ გსურთ ამ გამოკითხვის წაშლა?"
+ "გამოკითხვის წაშლა"
"გამოკითხვის რედაქტირება"
+ "მიმდინარე გამოკითხვები ვერ მოიძებნა."
+ "ბოლო გამოკითხვების მოძებნა ვერ მოხერხდა."
+ "მიმდინარე"
+ "წარსული"
+ "გამოკითხვები"
diff --git a/features/poll/impl/src/main/res/values-pl/translations.xml b/features/poll/impl/src/main/res/values-pl/translations.xml
new file mode 100644
index 0000000000..c6797b370e
--- /dev/null
+++ b/features/poll/impl/src/main/res/values-pl/translations.xml
@@ -0,0 +1,19 @@
+
+
+ "Dodaj opcję"
+ "Pokaż wyniki dopiero po zakończeniu ankiety"
+ "Ukryj głosy"
+ "Opcja %1$d"
+ "Twoje zmiany nie zostały zapisane. Czy na pewno chcesz wrócić?"
+ "Pytanie lub temat"
+ "Czego dotyczy ankieta?"
+ "Utwórz ankietę"
+ "Czy na pewno chcesz usunąć tę ankietę?"
+ "Usuń ankietę"
+ "Edytuj ankietę"
+ "Nie znaleziono ankiet w trakcie."
+ "Nie znaleziono ankiet."
+ "W trakcie"
+ "Przeszłe"
+ "Ankiety"
+
diff --git a/features/poll/impl/src/main/res/values-pt-rBR/translations.xml b/features/poll/impl/src/main/res/values-pt-rBR/translations.xml
new file mode 100644
index 0000000000..058dab7d42
--- /dev/null
+++ b/features/poll/impl/src/main/res/values-pt-rBR/translations.xml
@@ -0,0 +1,12 @@
+
+
+ "Adicionar opção"
+ "Mostrar resultados somente após o término da enquete"
+ "Ocultar votos"
+ "Opção %1$d"
+ "Pergunta ou tópico"
+ "Sobre o que é a enquete?"
+ "Criar enquete"
+ "Excluir Enquete"
+ "Editar enquete"
+
diff --git a/features/poll/impl/src/test/kotlin/io/element/android/features/poll/impl/create/CreatePollPresenterTest.kt b/features/poll/impl/src/test/kotlin/io/element/android/features/poll/impl/create/CreatePollPresenterTest.kt
index 89536deaec..334ea7018b 100644
--- a/features/poll/impl/src/test/kotlin/io/element/android/features/poll/impl/create/CreatePollPresenterTest.kt
+++ b/features/poll/impl/src/test/kotlin/io/element/android/features/poll/impl/create/CreatePollPresenterTest.kt
@@ -35,7 +35,6 @@ import io.element.android.libraries.matrix.api.room.MatrixRoom
import io.element.android.libraries.matrix.api.timeline.item.event.PollContent
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.SavePollInvocation
import io.element.android.libraries.matrix.test.timeline.FakeTimeline
import io.element.android.libraries.matrix.test.timeline.LiveTimelineProvider
import io.element.android.services.analytics.test.FakeAnalyticsService
@@ -51,9 +50,9 @@ import kotlinx.coroutines.test.runTest
import org.junit.Rule
import org.junit.Test
-@OptIn(ExperimentalCoroutinesApi::class) class CreatePollPresenterTest {
- @get:Rule
- val warmUpRule = WarmUpRule()
+@OptIn(ExperimentalCoroutinesApi::class)
+class CreatePollPresenterTest {
+ @get:Rule val warmUpRule = WarmUpRule()
private val pollEventId = AN_EVENT_ID
private var navUpInvocationsCount = 0
@@ -128,7 +127,13 @@ import org.junit.Test
@Test
fun `create poll sends a poll start event`() = runTest {
- val presenter = createCreatePollPresenter(mode = CreatePollMode.NewPoll)
+ val createPollResult = lambdaRecorder, Int, PollKind, Result> { _, _, _, _ -> Result.success(Unit) }
+ val presenter = createCreatePollPresenter(
+ room = FakeMatrixRoom(
+ createPollResult = createPollResult
+ ),
+ mode = CreatePollMode.NewPoll,
+ )
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
@@ -139,15 +144,13 @@ import org.junit.Test
skipItems(3)
initial.eventSink(CreatePollEvents.Save)
delay(1) // Wait for the coroutine to finish
- assertThat(fakeMatrixRoom.createPollInvocations.size).isEqualTo(1)
- assertThat(fakeMatrixRoom.createPollInvocations.last()).isEqualTo(
- SavePollInvocation(
- question = "A question?",
- answers = listOf("Answer 1", "Answer 2"),
- maxSelections = 1,
- pollKind = PollKind.Disclosed
+ createPollResult.assertions().isCalledOnce()
+ .with(
+ value("A question?"),
+ value(listOf("Answer 1", "Answer 2")),
+ value(1),
+ value(PollKind.Disclosed),
)
- )
assertThat(fakeAnalyticsService.capturedEvents.size).isEqualTo(2)
assertThat(fakeAnalyticsService.capturedEvents[0]).isEqualTo(
Composer(
@@ -170,8 +173,15 @@ import org.junit.Test
@Test
fun `when poll creation fails, error is tracked`() = runTest {
val error = Exception("cause")
- fakeMatrixRoom.givenCreatePollResult(Result.failure(error))
- val presenter = createCreatePollPresenter(mode = CreatePollMode.NewPoll)
+ val createPollResult = lambdaRecorder, Int, PollKind, Result> { _, _, _, _ ->
+ Result.failure(error)
+ }
+ val presenter = createCreatePollPresenter(
+ room = FakeMatrixRoom(
+ createPollResult = createPollResult
+ ),
+ mode = CreatePollMode.NewPoll,
+ )
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
@@ -180,7 +190,7 @@ import org.junit.Test
awaitItem().eventSink(CreatePollEvents.SetAnswer(1, "Answer 2"))
awaitItem().eventSink(CreatePollEvents.Save)
delay(1) // Wait for the coroutine to finish
- assertThat(fakeMatrixRoom.createPollInvocations).hasSize(1)
+ createPollResult.assertions().isCalledOnce()
assertThat(fakeAnalyticsService.capturedEvents).isEmpty()
assertThat(fakeAnalyticsService.trackedErrors).hasSize(1)
assertThat(fakeAnalyticsService.trackedErrors).containsExactly(
@@ -252,14 +262,22 @@ import org.junit.Test
@Test
fun `when edit poll fails, error is tracked`() = runTest {
val error = Exception("cause")
+ val editPollResult = lambdaRecorder { _: EventId, _: String, _: List, _: Int, _: PollKind ->
+ Result.failure(error)
+ }
+ val presenter = createCreatePollPresenter(
+ room = FakeMatrixRoom(
+ editPollResult = editPollResult,
+ liveTimeline = timeline,
+ ),
+ mode = CreatePollMode.EditPoll(pollEventId),
+ )
val editPollLambda = lambdaRecorder { _: EventId, _: String, _: List, _: Int, _: PollKind ->
Result.failure(error)
}
timeline.apply {
this.editPollLambda = editPollLambda
}
- fakeMatrixRoom.givenEditPollResult(Result.failure(error))
- val presenter = createCreatePollPresenter(mode = CreatePollMode.EditPoll(pollEventId))
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
diff --git a/features/preferences/impl/build.gradle.kts b/features/preferences/impl/build.gradle.kts
index ead4515360..ec265d739e 100644
--- a/features/preferences/impl/build.gradle.kts
+++ b/features/preferences/impl/build.gradle.kts
@@ -90,6 +90,7 @@ dependencies {
testImplementation(projects.features.ftue.test)
testImplementation(projects.features.rageshake.test)
testImplementation(projects.features.rageshake.impl)
+ testImplementation(projects.features.logout.test)
testImplementation(projects.features.roomlist.test)
testImplementation(projects.libraries.indicator.impl)
testImplementation(projects.libraries.pushproviders.test)
diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsEvents.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsEvents.kt
index 58f7ac7c02..bb0252e04d 100644
--- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsEvents.kt
+++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsEvents.kt
@@ -21,5 +21,6 @@ import io.element.android.libraries.featureflag.ui.model.FeatureUiModel
sealed interface DeveloperSettingsEvents {
data class UpdateEnabledFeature(val feature: FeatureUiModel, val isEnabled: Boolean) : DeveloperSettingsEvents
data class SetCustomElementCallBaseUrl(val baseUrl: String?) : DeveloperSettingsEvents
+ data class SetSimplifiedSlidingSyncEnabled(val isEnabled: Boolean) : DeveloperSettingsEvents
data object ClearCache : DeveloperSettingsEvents
}
diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsPresenter.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsPresenter.kt
index 8295b815c9..7b95463ac8 100644
--- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsPresenter.kt
+++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsPresenter.kt
@@ -28,6 +28,7 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.snapshots.SnapshotStateMap
import io.element.android.appconfig.ElementCallConfig
+import io.element.android.features.logout.api.LogoutUseCase
import io.element.android.features.preferences.impl.tasks.ClearCacheUseCase
import io.element.android.features.preferences.impl.tasks.ComputeCacheSizeUseCase
import io.element.android.features.rageshake.api.preferences.RageshakePreferencesPresenter
@@ -55,6 +56,7 @@ class DeveloperSettingsPresenter @Inject constructor(
private val rageshakePresenter: RageshakePreferencesPresenter,
private val appPreferencesStore: AppPreferencesStore,
private val buildMeta: BuildMeta,
+ private val logoutUseCase: LogoutUseCase,
) : Presenter {
@Composable
override fun present(): DeveloperSettingsState {
@@ -75,6 +77,9 @@ class DeveloperSettingsPresenter @Inject constructor(
val customElementCallBaseUrl by appPreferencesStore
.getCustomElementCallBaseUrlFlow()
.collectAsState(initial = null)
+ val isSimplifiedSlidingSyncEnabled by appPreferencesStore
+ .isSimplifiedSlidingSyncEnabledFlow()
+ .collectAsState(initial = false)
LaunchedEffect(Unit) {
FeatureFlags.entries
@@ -114,6 +119,10 @@ class DeveloperSettingsPresenter @Inject constructor(
appPreferencesStore.setCustomElementCallBaseUrl(urlToSave)
}
DeveloperSettingsEvents.ClearCache -> coroutineScope.clearCache(clearCacheAction)
+ is DeveloperSettingsEvents.SetSimplifiedSlidingSyncEnabled -> coroutineScope.launch {
+ appPreferencesStore.setSimplifiedSlidingSyncEnabled(event.isEnabled)
+ logoutUseCase.logout(ignoreSdkError = true)
+ }
}
}
@@ -127,6 +136,7 @@ class DeveloperSettingsPresenter @Inject constructor(
defaultUrl = ElementCallConfig.DEFAULT_BASE_URL,
validator = ::customElementCallUrlValidator,
),
+ isSimpleSlidingSyncEnabled = isSimplifiedSlidingSyncEnabled,
eventSink = ::handleEvents
)
}
diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsState.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsState.kt
index 9a12823686..91cf7051c3 100644
--- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsState.kt
+++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsState.kt
@@ -27,6 +27,7 @@ data class DeveloperSettingsState(
val rageshakeState: RageshakePreferencesState,
val clearCacheAction: AsyncData,
val customElementCallBaseUrlState: CustomElementCallBaseUrlState,
+ val isSimpleSlidingSyncEnabled: Boolean,
val eventSink: (DeveloperSettingsEvents) -> Unit
)
diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsStateProvider.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsStateProvider.kt
index fb93a63ffc..01c2971777 100644
--- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsStateProvider.kt
+++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsStateProvider.kt
@@ -39,6 +39,7 @@ open class DeveloperSettingsStateProvider : PreviewParameterProvider = AsyncData.Uninitialized,
customElementCallBaseUrlState: CustomElementCallBaseUrlState = aCustomElementCallBaseUrlState(),
+ isSimplifiedSlidingSyncEnabled: Boolean = false,
eventSink: (DeveloperSettingsEvents) -> Unit = {},
) = DeveloperSettingsState(
features = aFeatureUiModelList(),
@@ -46,6 +47,7 @@ fun aDeveloperSettingsState(
cacheSize = AsyncData.Success("1.2 MB"),
clearCacheAction = clearCacheAction,
customElementCallBaseUrlState = customElementCallBaseUrlState,
+ isSimpleSlidingSyncEnabled = isSimplifiedSlidingSyncEnabled,
eventSink = eventSink,
)
diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsView.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsView.kt
index dd684fcaaa..f46637e6cb 100644
--- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsView.kt
+++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsView.kt
@@ -26,6 +26,7 @@ import io.element.android.features.preferences.impl.R
import io.element.android.features.rageshake.api.preferences.RageshakePreferencesView
import io.element.android.libraries.designsystem.components.preferences.PreferenceCategory
import io.element.android.libraries.designsystem.components.preferences.PreferencePage
+import io.element.android.libraries.designsystem.components.preferences.PreferenceSwitch
import io.element.android.libraries.designsystem.components.preferences.PreferenceText
import io.element.android.libraries.designsystem.components.preferences.PreferenceTextField
import io.element.android.libraries.designsystem.preview.ElementPreview
@@ -60,6 +61,14 @@ fun DeveloperSettingsView(
title = "Configure tracing",
onClick = onOpenConfigureTracing,
)
+ PreferenceSwitch(
+ title = "Enable Simplified Sliding Sync",
+ subtitle = "When toggled you'll be logged out of the app and will need to log in again.",
+ isChecked = state.isSimpleSlidingSyncEnabled,
+ onCheckedChange = {
+ state.eventSink(DeveloperSettingsEvents.SetSimplifiedSlidingSyncEnabled(it))
+ }
+ )
}
PreferenceCategory(title = "Showkase") {
PreferenceText(
diff --git a/features/preferences/impl/src/main/res/values-el/translations.xml b/features/preferences/impl/src/main/res/values-el/translations.xml
index 32659bca9f..9af59929fc 100644
--- a/features/preferences/impl/src/main/res/values-el/translations.xml
+++ b/features/preferences/impl/src/main/res/values-el/translations.xml
@@ -15,9 +15,11 @@
"Κοινή χρήση παρουσίας"
"Εάν απενεργοποιηθεί, δεν θα μπορείς να στέλνεις ή να λαμβάνεις αποδεικτικά ανάγνωσης ή ειδοποιήσεις πληκτρολόγησης."
"Ενεργοποίησε την επιλογή για προβολή πηγής μηνυμάτων στη ροή."
+ "Δεν έχεις αποκλεισμένους χρήστες"
"Άρση αποκλεισμού"
"Θα μπορείς να δεις ξανά όλα τα μηνύματα του."
"Κατάργηση αποκλεισμού χρήστη"
+ "Άρση αποκλεισμού…"
"Εμφανιζόμενο όνομα"
"Το εμφανιζόμενο όνομά σου"
"Παρουσιάστηκε ένα άγνωστο σφάλμα και οι πληροφορίες δεν μπορούσαν να αλλάξουν."
@@ -45,7 +47,7 @@
"Αναφορές"
"Όλα"
"Αναφορές"
- "Ειδοποιήσε με για"
+ "Ειδοποίησέ με για"
"Ειδοποίηση για @room"
"Για να λαμβάνεις ειδοποιήσεις, άλλαξε το %1$s ."
"ρυθμίσεις συστήματος"
diff --git a/features/preferences/impl/src/main/res/values-it/translations.xml b/features/preferences/impl/src/main/res/values-it/translations.xml
index ea7395492c..5acfeff05b 100644
--- a/features/preferences/impl/src/main/res/values-it/translations.xml
+++ b/features/preferences/impl/src/main/res/values-it/translations.xml
@@ -13,7 +13,7 @@
"Ricevute di visualizzazione"
"Se disattivato, le tue ricevute di visualizzazione non verranno inviate a nessuno. Riceverai comunque ricevute di visualizzazione da altri utenti."
"Condividi presenza online"
- "Se disattivato, non potrai inviare o ricevere ricevute di visualizzazione o notifiche di scrittura."
+ "Se disattivato, non potrai inviare o ricevere ricevute di lettura o notifiche di scrittura."
"Attiva l\'opzione per visualizzare il codice sorgente del messaggio nella conversazione."
"Non hai utenti bloccati"
"Sblocca"
diff --git a/features/preferences/impl/src/main/res/values-ka/translations.xml b/features/preferences/impl/src/main/res/values-ka/translations.xml
index f27eac6d40..89c39c1c44 100644
--- a/features/preferences/impl/src/main/res/values-ka/translations.xml
+++ b/features/preferences/impl/src/main/res/values-ka/translations.xml
@@ -7,6 +7,7 @@
"დააყენეთ საბაზისო URL Element-ის ზარებისათვის."
"არასწორი URL, გთხოვთ, დარწმუნდეთ, რომ შეიტანეთ პროტოკოლი (http/https) და სწორი მისამართი."
"გამორთეთ მდიდარი ტექსტის რედაქტორი, რათა ხელით აკრიფოთ Markdown."
+ "ჩართეთ ოპცია რათა შეტყობინების წყაროს დროის ისტორია ნახოთ."
"განბლოკვა"
"თქვენ კვლავ შეძლებთ მათგან ყველა შეტყობინების ნახვას."
"Მომხმარებლის განბლოკვა"
@@ -32,6 +33,8 @@
"შეტყობინებების ჩართვა ამ მოწყობილობაზე"
"კონფიგურაცია არ გამოსწორებულა, გთხოვთ, კვლავ სცადოთ."
"ჯგუფური ჩატები"
+ "მოსაწვევები"
+ "თქვენი სახლის სერვერი არ უჭერს მხარს ამ პარამეტრს დაშიფრულ ოთახებში, ზოგიერთ ოთახში შეიძლება არ მიიღოთ შეტყობინება."
"ხსენებები"
"ყველა"
"ხსენებები"
diff --git a/features/preferences/impl/src/main/res/values-pl/translations.xml b/features/preferences/impl/src/main/res/values-pl/translations.xml
new file mode 100644
index 0000000000..021d7595f1
--- /dev/null
+++ b/features/preferences/impl/src/main/res/values-pl/translations.xml
@@ -0,0 +1,49 @@
+
+
+ "Upewnij się, że nie pominiesz żadnego połączenia. Zmień swoje ustawienia i zezwól na powiadomienia na blokadzie ekranu."
+ "Popraw jakość swoich rozmów"
+ "Wybierz sposób otrzymywania powiadomień"
+ "Tryb dewelopera"
+ "Włącz, aby uzyskać dostęp do funkcji dla deweloperów."
+ "Własny bazowy URL dla połączeń Element"
+ "Ustaw własny bazowy URL dla połączeń Element"
+ "Nieprawidłowy adres URL, upewnij się, że zawiera protokół (http/https) i poprawny adres."
+ "Wyłącz edytor tekstu bogatego, aby pisać tekst Markdown ręcznie."
+ "Włącz opcję, aby wyświetlić źródło wiadomości na osi czasu."
+ "Odblokuj"
+ "Będziesz mógł ponownie zobaczyć wszystkie wiadomości od tego użytkownika."
+ "Odblokuj użytkownika"
+ "Wyświetlana nazwa"
+ "Twoja wyświetlana nazwa"
+ "Wystąpił nieznany błąd przez co nie można było zmienić informacji."
+ "Nie można zaktualizować profilu"
+ "Edytuj profil"
+ "Aktualizowanie profilu…"
+ "Dodatkowe ustawienia"
+ "Połączenia audio i wideo"
+ "Niezgodność konfiguracji"
+ "Uprościliśmy Ustawienia powiadomień, aby ułatwić nawigowanie między opcjami. Niektóre ustawienia, które wybrałeś mogły zniknąć, lecz są wciąż aktywne.
+
+Niektóre ustawienia mogą ulec zmianie, jeśli kontynuujesz."
+ "Czaty prywatne"
+ "Ustawienia własne wybranego czatu"
+ "Wystąpił błąd podczas aktualizacji ustawienia powiadomień."
+ "Wszystkie wiadomości"
+ "Tylko wzmianki i słowa kluczowe"
+ "Na czatach prywatnych, powiadamiaj mnie przez"
+ "Na czatach grupowych powiadamiaj mnie przez"
+ "Włącz powiadomienia na tym urządzeniu"
+ "Konfiguracja nie została poprawiona, spróbuj ponownie."
+ "Czaty grupowe"
+ "Zaproszenia"
+ "Twój serwer domowy nie wspiera tej opcji w pokojach szyfrowanych, możesz nie otrzymać powiadomień z niektórych pokoi."
+ "Wzmianki"
+ "Wszystkie"
+ "Wzmianki"
+ "Powiadamiaj mnie przez"
+ "Powiadom mnie na @pokój"
+ "Aby otrzymywać powiadomienia, zmień swoje%1$s ."
+ "ustawienia systemowe"
+ "Powiadomienia systemowe wyłączone"
+ "Powiadomienia"
+
diff --git a/features/preferences/impl/src/main/res/values-pt-rBR/translations.xml b/features/preferences/impl/src/main/res/values-pt-rBR/translations.xml
new file mode 100644
index 0000000000..2aa9704981
--- /dev/null
+++ b/features/preferences/impl/src/main/res/values-pt-rBR/translations.xml
@@ -0,0 +1,40 @@
+
+
+ "Escolha como receber notificações"
+ "Modo de desenvolvedor"
+ "Habilite para ter acesso a recursos e funcionalidades para desenvolvedores."
+ "Desative o editor de rich text para digitar Markdown manualmente."
+ "Desbloquear"
+ "Você poderá ver todas as mensagens deles novamente."
+ "Desbloquear usuário"
+ "Nome de exibição"
+ "Seu nome de exibição"
+ "Um erro desconhecido foi encontrado e as informações não puderam ser alteradas."
+ "Não foi possível atualizar o perfil"
+ "Editar perfil"
+ "Atualizando o perfil…"
+ "Configurações adicionais"
+ "Chamadas de áudio e vídeo"
+ "Incompatibilidade de configuração"
+ "Simplificamos as configurações de notificações para facilitar a localização das opções. Algumas configurações personalizadas que você escolheu no passado não são mostradas aqui, mas ainda estão ativas.
+
+Se você continuar, algumas de suas configurações poderão mudar."
+ "Conversas privadas"
+ "Configuração personalizada por chat"
+ "Ocorreu um erro ao atualizar a configuração de notificação."
+ "Todas as mensagens"
+ "Somente menções e palavras-chave"
+ "Em conversas privadas, me notifique para"
+ "Em conversas em grupos, me notifique para"
+ "Ativar notificações neste dispositivo"
+ "A configuração não foi corrigida, tente novamente."
+ "Bate-papos em grupo"
+ "Menções"
+ "Todos"
+ "Menções"
+ "Me notifique para"
+ "Notifique-me em @room"
+ "configurações do sistema"
+ "Notificações do sistema desativadas"
+ "Notificações"
+
diff --git a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsPresenterTest.kt b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsPresenterTest.kt
index 113125d950..9fb1c8b37f 100644
--- a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsPresenterTest.kt
+++ b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsPresenterTest.kt
@@ -21,6 +21,7 @@ import app.cash.molecule.moleculeFlow
import app.cash.turbine.test
import com.google.common.truth.Truth.assertThat
import io.element.android.appconfig.ElementCallConfig
+import io.element.android.features.logout.test.FakeLogoutUseCase
import io.element.android.features.preferences.impl.tasks.FakeClearCacheUseCase
import io.element.android.features.preferences.impl.tasks.FakeComputeCacheSizeUseCase
import io.element.android.features.rageshake.impl.preferences.DefaultRageshakePreferencesPresenter
@@ -35,6 +36,8 @@ import io.element.android.libraries.matrix.test.core.aBuildMeta
import io.element.android.libraries.preferences.test.InMemoryAppPreferencesStore
import io.element.android.tests.testutils.WarmUpRule
import io.element.android.tests.testutils.awaitLastSequentialItem
+import io.element.android.tests.testutils.lambda.lambdaRecorder
+import kotlinx.coroutines.flow.first
import kotlinx.coroutines.test.runTest
import org.junit.Rule
import org.junit.Test
@@ -55,6 +58,7 @@ class DeveloperSettingsPresenterTest {
assertThat(initialState.cacheSize).isEqualTo(AsyncData.Uninitialized)
assertThat(initialState.customElementCallBaseUrlState).isNotNull()
assertThat(initialState.customElementCallBaseUrlState.baseUrl).isNull()
+ assertThat(initialState.isSimpleSlidingSyncEnabled).isFalse()
val loadedState = awaitItem()
assertThat(loadedState.rageshakeState.isEnabled).isFalse()
assertThat(loadedState.rageshakeState.isSupported).isTrue()
@@ -160,6 +164,30 @@ class DeveloperSettingsPresenterTest {
}
}
+ @Test
+ fun `present - toggling simplified sliding sync changes the preferences and logs out the user`() = runTest {
+ val logoutCallRecorder = lambdaRecorder { "" }
+ val logoutUseCase = FakeLogoutUseCase(logoutLambda = logoutCallRecorder)
+ val preferences = InMemoryAppPreferencesStore()
+ val presenter = createDeveloperSettingsPresenter(preferencesStore = preferences, logoutUseCase = logoutUseCase)
+ moleculeFlow(RecompositionMode.Immediate) {
+ presenter.present()
+ }.test {
+ val initialState = awaitLastSequentialItem()
+ assertThat(initialState.isSimpleSlidingSyncEnabled).isFalse()
+
+ initialState.eventSink(DeveloperSettingsEvents.SetSimplifiedSlidingSyncEnabled(true))
+ assertThat(awaitItem().isSimpleSlidingSyncEnabled).isTrue()
+ assertThat(preferences.isSimplifiedSlidingSyncEnabledFlow().first()).isTrue()
+ logoutCallRecorder.assertions().isCalledOnce()
+
+ initialState.eventSink(DeveloperSettingsEvents.SetSimplifiedSlidingSyncEnabled(false))
+ assertThat(awaitItem().isSimpleSlidingSyncEnabled).isFalse()
+ assertThat(preferences.isSimplifiedSlidingSyncEnabledFlow().first()).isFalse()
+ logoutCallRecorder.assertions().isCalledExactly(times = 2)
+ }
+ }
+
private fun createDeveloperSettingsPresenter(
featureFlagService: FakeFeatureFlagService = FakeFeatureFlagService(),
cacheSizeUseCase: FakeComputeCacheSizeUseCase = FakeComputeCacheSizeUseCase(),
@@ -167,6 +195,7 @@ class DeveloperSettingsPresenterTest {
rageshakePresenter: DefaultRageshakePreferencesPresenter = DefaultRageshakePreferencesPresenter(FakeRageShake(), FakeRageshakeDataStore()),
preferencesStore: InMemoryAppPreferencesStore = InMemoryAppPreferencesStore(),
buildMeta: BuildMeta = aBuildMeta(),
+ logoutUseCase: FakeLogoutUseCase = FakeLogoutUseCase(logoutLambda = { "" })
): DeveloperSettingsPresenter {
return DeveloperSettingsPresenter(
featureFlagService = featureFlagService,
@@ -175,6 +204,7 @@ class DeveloperSettingsPresenterTest {
rageshakePresenter = rageshakePresenter,
appPreferencesStore = preferencesStore,
buildMeta = buildMeta,
+ logoutUseCase = logoutUseCase,
)
}
}
diff --git a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsViewTest.kt b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsViewTest.kt
index 7288b76d3c..aa993c39bc 100644
--- a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsViewTest.kt
+++ b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsViewTest.kt
@@ -109,6 +109,19 @@ class DeveloperSettingsViewTest {
rule.onNodeWithText("Clear cache").performClick()
eventsRecorder.assertSingle(DeveloperSettingsEvents.ClearCache)
}
+
+ @Config(qualifiers = "h1500dp")
+ @Test
+ fun `clicking on the simplified sliding sync switch emits the expected event`() {
+ val eventsRecorder = EventsRecorder()
+ rule.setDeveloperSettingsView(
+ state = aDeveloperSettingsState(
+ eventSink = eventsRecorder
+ ),
+ )
+ rule.onNodeWithText("Enable Simplified Sliding Sync").performClick()
+ eventsRecorder.assertSingle(DeveloperSettingsEvents.SetSimplifiedSlidingSyncEnabled(true))
+ }
}
private fun AndroidComposeTestRule.setDeveloperSettingsView(
diff --git a/features/rageshake/api/src/main/res/values-pl/translations.xml b/features/rageshake/api/src/main/res/values-pl/translations.xml
new file mode 100644
index 0000000000..d9b42eb749
--- /dev/null
+++ b/features/rageshake/api/src/main/res/values-pl/translations.xml
@@ -0,0 +1,7 @@
+
+
+ "%1$s uległ awarii podczas ostatniego użycia. Czy chcesz przesłać nam raport o awarii?"
+ "Wygląda na to, że potrząsasz telefonem z frustracji. Czy chcesz otworzyć ekran zgłaszania błędów?"
+ "Gniewne wstrząsanie"
+ "Próg wykrywania"
+
diff --git a/features/rageshake/api/src/main/res/values-pt-rBR/translations.xml b/features/rageshake/api/src/main/res/values-pt-rBR/translations.xml
new file mode 100644
index 0000000000..3985149a27
--- /dev/null
+++ b/features/rageshake/api/src/main/res/values-pt-rBR/translations.xml
@@ -0,0 +1,7 @@
+
+
+ "%1$s fechou inesperadamente na última vez que foi usado. Gostaria de compartilhar um relatório de falhas conosco?"
+ "Você parece estar sacudindo o telefone em sinal de frustração. Você gostaria de abrir a tela de relatório de erros?"
+ "Rageshake"
+ "Limiar de deteção"
+
diff --git a/features/rageshake/impl/src/main/res/values-pl/translations.xml b/features/rageshake/impl/src/main/res/values-pl/translations.xml
new file mode 100644
index 0000000000..04bf123498
--- /dev/null
+++ b/features/rageshake/impl/src/main/res/values-pl/translations.xml
@@ -0,0 +1,16 @@
+
+
+ "Dołącz zrzut ekranu"
+ "Możecie skontaktować się ze mną, jeśli macie jakiekolwiek dodatkowe pytania."
+ "Napisz do mnie"
+ "Edytuj zrzut ekranu"
+ "Opisz problem. Co zrobiłeś? Czego oczekiwałeś? Co się stało zamiast tego. Podaj jak najwięcej szczegółów."
+ "Opisz problem…"
+ "Jeśli to możliwe, napisz zgłoszenje w języku angielskim."
+ "Wyślij logi awarii"
+ "Zezwól na logi"
+ "Wyślij zrzut ekranu"
+ "Logi zostaną dołączone do Twojej wiadomości, aby upewnić się, że wszystko działa poprawnie. Aby wysłać wiadomość bez logów, wyłącz to ustawienie."
+ "%1$s uległ awarii podczas ostatniego użycia. Czy chcesz przesłać nam raport o awarii?"
+ "Wyświetl logi"
+
diff --git a/features/rageshake/impl/src/main/res/values-pt-rBR/translations.xml b/features/rageshake/impl/src/main/res/values-pt-rBR/translations.xml
new file mode 100644
index 0000000000..d046391f6a
--- /dev/null
+++ b/features/rageshake/impl/src/main/res/values-pt-rBR/translations.xml
@@ -0,0 +1,15 @@
+
+
+ "Anexar captura de tela"
+ "Você pode entrar em contato comigo se tiver alguma pergunta adicional."
+ "Entre em contato comigo"
+ "Editar captura de tela"
+ "Descreva o problema. O que você fez? O que você esperava que acontecesse? O que realmente aconteceu? Por favor, forneça o máximo de detalhes possível."
+ "Descreva o problema…"
+ "Se possível, escreva a descrição em inglês."
+ "Enviar registros de falhas"
+ "Permitir registros"
+ "Enviar captura de tela"
+ "Os registros serão incluídos com sua mensagem para garantir que tudo esteja funcionando corretamente. Para enviar sua mensagem sem registros, desative essa configuração."
+ "%1$s fechou inesperadamente na última vez que foi usado. Gostaria de compartilhar um relatório de falhas conosco?"
+
diff --git a/features/roomdetails/impl/src/main/res/values-el/translations.xml b/features/roomdetails/impl/src/main/res/values-el/translations.xml
index 9262a346c5..2c1886ce40 100644
--- a/features/roomdetails/impl/src/main/res/values-el/translations.xml
+++ b/features/roomdetails/impl/src/main/res/values-el/translations.xml
@@ -4,6 +4,7 @@
"Ο οικιακός διακομιστής σου δεν υποστηρίζει αυτήν την επιλογή σε κρυπτογραφημένα δωμάτια, ενδέχεται να μην λάβεις ειδοποίηση σε ορισμένα δωμάτια."
"Δημοσκοπήσεις"
"Μόνο διαχειριστές"
+ "Αποκλεισμός ατόμων"
"Αφαίρεση μηνυμάτων"
"Όλοι"
"Πρόσκληση ατόμων"
@@ -58,17 +59,23 @@
"Πληροφορίες δωματίου"
"Θέμα"
"Ενημέρωση δωματίου…"
+ "Αποκλεισμός"
"Δεν θα μπορεί να συμμετέχει ξανά σε αυτό το δωμάτιο εάν προσκληθεί."
+ "Θες σίγουρα να αποκλείσεις αυτό το μέλος;"
+ "Δεν υπάρχουν αποκλεισμένοι χρήστες σε αυτό το δωμάτιο."
+ "Αποκλεισμός του χρήστη %1$s"
- "%1$d άτομο"
- "%1$d άτομα"
+ "Αφαίρεση και αποκλεισμός μέλους"
"Αφαίρεση από το δωμάτιο"
"Αφαίρεση και αποκλεισμός μέλους"
"Μόνο αφαίρεση μέλους"
"Αφαίρεση μέλους και απαγόρευση συμμετοχής στο μέλλον;"
"Αναίρεση αποκλεισμού"
"Θα μπορεί να συμμετάσχει ξανά στο δωμάτιο εάν προσκληθεί."
+ "Άρση αποκλεισμού χρήστη"
"Προβολή προφίλ"
"Αποκλεισμένοι"
"Μέλη"
@@ -77,6 +84,7 @@
"Διαχειριστής"
"Συντονιστής"
"Μέλη δωματίου"
+ "Άρση αποκλεισμού %1$s"
"Να επιτρέπεται η προσαρμοσμένη ρύθμιση"
"Η ενεργοποίηση αυτής της ρύθμισης θα παρακάμψει την προεπιλεγμένη ρύθμιση"
"Ειδοποιήσε με σε αυτήν τη συνομιλία για"
diff --git a/features/roomdetails/impl/src/main/res/values-ka/translations.xml b/features/roomdetails/impl/src/main/res/values-ka/translations.xml
index 75f7121b45..65ce26797e 100644
--- a/features/roomdetails/impl/src/main/res/values-ka/translations.xml
+++ b/features/roomdetails/impl/src/main/res/values-ka/translations.xml
@@ -1,6 +1,8 @@
"შეტყობინებების პარამეტრის განახლებისას მოხდა შეცდომა."
+ "თქვენი სახლის სერვერი არ უჭერს მხარს ამ პარამეტრს დაშიფრულ ოთახებში, ზოგიერთ ოთახში შეიძლება არ მიიღოთ შეტყობინება."
+ "გამოკითხვები"
"ყველა"
"თემის დამატება"
"უკვე წევრია"
@@ -39,6 +41,7 @@
"შეტყობინებების პარამეტრების ჩატვირთვისას მოხდა შეცდომა."
"ნაგულისხმევი რეჟიმის აღდგენა ვერ მოხერხდა, გთხოვთ, სცადოთ ხელახლა."
"რეჟიმის დაყენება ვერ მოხერხდა, გთხოვთ, სცადოთ ხელახლა."
+ "თქვენი სახლის სერვერი არ უჭერს მხარს ამ პარამეტრს დაშიფრულ ოთახებში, თქვენ არ მიიღებთ შეტყობინებას ამ ოთახში."
"ყველა შეტყობინება"
"მხოლოდ ხსენებები და საკვანძო სიტყვები"
"ამ ოთახში, შემატყობინეთ:"
diff --git a/features/roomdetails/impl/src/main/res/values-pl/translations.xml b/features/roomdetails/impl/src/main/res/values-pl/translations.xml
new file mode 100644
index 0000000000..569e167d92
--- /dev/null
+++ b/features/roomdetails/impl/src/main/res/values-pl/translations.xml
@@ -0,0 +1,50 @@
+
+
+ "Wystąpił błąd podczas aktualizacji ustawienia powiadomień."
+ "Twój serwer domowy nie wspiera tej opcji w pokojach szyfrowanych, możesz nie otrzymać powiadomień z niektórych pokoi."
+ "Ankiety"
+ "Wszyscy"
+ "Dodaj temat"
+ "Jest już członkiem"
+ "Już zaproszony"
+ "Edytuj pokój"
+ "Wystąpił nieznany błąd i nie można było zmienić informacji."
+ "Nie można zaktualizować pokoju"
+ "Wiadomości są zabezpieczone kłódkami. Tylko Ty i odbiorcy macie unikalne klucze do ich odblokowania."
+ "Szyfrowanie wiadomości włączone"
+ "Wystąpił błąd podczas ładowania ustawień powiadomień."
+ "Wyciszenie tego pokoju nie powiodło się, spróbuj ponownie."
+ "Nie udało się wyłączyć wyciszenia tego pokoju. Spróbuj ponownie."
+ "Zaproś znajomych"
+ "Opuść rozmowę"
+ "Opuść pokój"
+ "Niestandardowy"
+ "Domyślny"
+ "Powiadomienia"
+ "Nazwa pokoju"
+ "Bezpieczeństwo"
+ "Udostępnij pokój"
+ "Temat"
+ "Aktualizuję pokój…"
+
+ - "%1$d osoba"
+ - "%1$d osoby"
+ - "%1$d osób"
+
+ "Oczekiwanie"
+ "Członkowie pokoju"
+ "Zezwalaj na ustawienia niestandardowe"
+ "Włączenie tej opcji nadpisze ustawienie domyślne"
+ "Powiadamiaj mnie o tym czacie przez"
+ "Możesz to zmienić w swoim %1$s."
+ "ustawienia globalne"
+ "Ustawienie domyślne"
+ "Usuń ustawienia własne"
+ "Wystąpił błąd podczas ładowania ustawień powiadomień."
+ "Nie udało się przywrócić trybu domyślnego, spróbuj ponownie."
+ "Nie udało się ustawić trybu, spróbuj ponownie."
+ "Twój serwer domowy nie wspiera tej opcji w pokojach szyfrowanych, możesz nie otrzymać powiadomień z tego pokoju."
+ "Wszystkie wiadomości"
+ "Tylko wzmianki i słowa kluczowe"
+ "W tym pokoju, powiadamiaj mnie przez"
+
diff --git a/features/roomdetails/impl/src/main/res/values-pt-rBR/translations.xml b/features/roomdetails/impl/src/main/res/values-pt-rBR/translations.xml
new file mode 100644
index 0000000000..4989168201
--- /dev/null
+++ b/features/roomdetails/impl/src/main/res/values-pt-rBR/translations.xml
@@ -0,0 +1,46 @@
+
+
+ "Ocorreu um erro ao atualizar a configuração de notificação."
+ "Todos"
+ "Adicionar tópico"
+ "Já é membro"
+ "Já foi convidado"
+ "Editar sala"
+ "Ocorreu um erro desconhecido e as informações não puderam ser alteradas."
+ "Não foi possível atualizar a sala"
+ "As mensagens são protegidas com bloqueios. Somente você e os destinatários têm as chaves exclusivas para desbloqueá-los."
+ "Criptografia de mensagens ativada"
+ "Ocorreu um erro ao carregar as configurações de notificação."
+ "Falha ao silenciar esta sala, tente novamente."
+ "Falha ao ativar o som desta sala. Tente novamente."
+ "Convidar pessoas"
+ "Sair da conversa"
+ "Sair da sala"
+ "Personalizado"
+ "Padrão"
+ "Notificações"
+ "Nome da sala"
+ "Segurança"
+ "Compartilhar sala"
+ "Tópico"
+ "Atualizando a sala…"
+
+ - "%1$d pessoa"
+ - "%1$d pessoas"
+
+ "Pendente"
+ "Membros da sala"
+ "Permitir configuração personalizada"
+ "Ativar isso substituirá sua configuração padrão"
+ "Me notifique nesta conversa para"
+ "Você pode alterá-lo no seu %1$s."
+ "configurações globais"
+ "Configuração padrão"
+ "Remover configuração personalizada"
+ "Ocorreu um erro ao carregar as configurações de notificação."
+ "Falha ao restaurar o modo padrão, tente novamente."
+ "Falha ao definir o modo, tente novamente."
+ "Todas as mensagens"
+ "Somente menções e palavras-chave"
+ "Nesta sala, notifique-me para"
+
diff --git a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/MatrixRoomFixture.kt b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/MatrixRoomFixture.kt
index cffd2ae1f1..9fd89f482d 100644
--- a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/MatrixRoomFixture.kt
+++ b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/MatrixRoomFixture.kt
@@ -17,11 +17,15 @@
package io.element.android.features.roomdetails
import io.element.android.libraries.matrix.api.core.RoomId
+import io.element.android.libraries.matrix.api.core.UserId
+import io.element.android.libraries.matrix.api.room.RoomMember
+import io.element.android.libraries.matrix.api.room.StateEventType
import io.element.android.libraries.matrix.test.A_ROOM_ID
import io.element.android.libraries.matrix.test.A_ROOM_NAME
import io.element.android.libraries.matrix.test.notificationsettings.FakeNotificationSettingsService
import io.element.android.libraries.matrix.test.room.FakeMatrixRoom
import io.element.android.libraries.matrix.test.room.aRoomInfo
+import io.element.android.tests.testutils.lambda.lambdaError
fun aMatrixRoom(
roomId: RoomId = A_ROOM_ID,
@@ -34,6 +38,16 @@ fun aMatrixRoom(
isDirect: Boolean = false,
notificationSettingsService: FakeNotificationSettingsService = FakeNotificationSettingsService(),
emitRoomInfo: Boolean = false,
+ canInviteResult: (UserId) -> Result = { lambdaError() },
+ canSendStateResult: (UserId, StateEventType) -> Result = { _, _ -> lambdaError() },
+ userDisplayNameResult: () -> Result = { lambdaError() },
+ userAvatarUrlResult: () -> Result = { lambdaError() },
+ setNameResult: (String) -> Result = { lambdaError() },
+ setTopicResult: (String) -> Result = { lambdaError() },
+ updateAvatarResult: (String, ByteArray) -> Result = { _, _ -> lambdaError() },
+ removeAvatarResult: () -> Result = { lambdaError() },
+ canUserJoinCallResult: (UserId) -> Result = { lambdaError() },
+ getUpdatedMemberResult: (UserId) -> Result = { lambdaError() },
) = FakeMatrixRoom(
roomId = roomId,
displayName = displayName,
@@ -42,7 +56,17 @@ fun aMatrixRoom(
isEncrypted = isEncrypted,
isPublic = isPublic,
isDirect = isDirect,
- notificationSettingsService = notificationSettingsService
+ notificationSettingsService = notificationSettingsService,
+ canInviteResult = canInviteResult,
+ canSendStateResult = canSendStateResult,
+ userDisplayNameResult = userDisplayNameResult,
+ userAvatarUrlResult = userAvatarUrlResult,
+ setNameResult = setNameResult,
+ setTopicResult = setTopicResult,
+ updateAvatarResult = updateAvatarResult,
+ removeAvatarResult = removeAvatarResult,
+ canUserJoinCallResult = canUserJoinCallResult,
+ getUpdatedMemberResult = getUpdatedMemberResult,
).apply {
if (emitRoomInfo) {
givenRoomInfo(
diff --git a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/RoomDetailsPresenterTest.kt b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/RoomDetailsPresenterTest.kt
index 92ac27454a..ff89aba9be 100644
--- a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/RoomDetailsPresenterTest.kt
+++ b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/RoomDetailsPresenterTest.kt
@@ -53,6 +53,9 @@ import io.element.android.services.analytics.test.FakeAnalyticsService
import io.element.android.tests.testutils.FakeLifecycleOwner
import io.element.android.tests.testutils.WarmUpRule
import io.element.android.tests.testutils.consumeItemsUntilPredicate
+import io.element.android.tests.testutils.lambda.lambdaError
+import io.element.android.tests.testutils.lambda.lambdaRecorder
+import io.element.android.tests.testutils.lambda.value
import io.element.android.tests.testutils.testCoroutineDispatchers
import io.element.android.tests.testutils.withFakeLifecycleOwner
import kotlinx.collections.immutable.persistentListOf
@@ -110,7 +113,11 @@ class RoomDetailsPresenterTest {
@Test
fun `present - initial state is created from room if roomInfo is null`() = runTest {
- val room = aMatrixRoom()
+ val room = aMatrixRoom(
+ canInviteResult = { Result.success(true) },
+ canUserJoinCallResult = { Result.success(true) },
+ canSendStateResult = { _, _ -> Result.success(true) },
+ )
val presenter = createRoomDetailsPresenter(room)
presenter.test {
val initialState = awaitItem()
@@ -128,7 +135,11 @@ class RoomDetailsPresenterTest {
@Test
fun `present - initial state is updated with roomInfo if it exists`() = runTest {
val roomInfo = aRoomInfo(name = "A room name", topic = "A topic", avatarUrl = "https://matrix.org/avatar.jpg")
- val room = aMatrixRoom().apply {
+ val room = aMatrixRoom(
+ canInviteResult = { Result.success(true) },
+ canUserJoinCallResult = { Result.success(true) },
+ canSendStateResult = { _, _ -> Result.success(true) },
+ ).apply {
givenRoomInfo(roomInfo)
}
val presenter = createRoomDetailsPresenter(room)
@@ -145,7 +156,12 @@ class RoomDetailsPresenterTest {
@Test
fun `present - initial state with no room name`() = runTest {
- val room = aMatrixRoom(displayName = "")
+ val room = aMatrixRoom(
+ displayName = "",
+ canInviteResult = { Result.success(true) },
+ canUserJoinCallResult = { Result.success(true) },
+ canSendStateResult = { _, _ -> Result.success(true) },
+ )
val presenter = createRoomDetailsPresenter(room)
presenter.test {
val initialState = awaitItem()
@@ -162,6 +178,16 @@ class RoomDetailsPresenterTest {
val room = aMatrixRoom(
isEncrypted = true,
isDirect = true,
+ canInviteResult = { Result.success(true) },
+ canUserJoinCallResult = { Result.success(true) },
+ canSendStateResult = { _, _ -> Result.success(true) },
+ getUpdatedMemberResult = { userId ->
+ when (userId) {
+ A_SESSION_ID -> Result.success(myRoomMember)
+ A_USER_ID_2 -> Result.success(otherRoomMember)
+ else -> lambdaError()
+ }
+ },
).apply {
val roomMembers = persistentListOf(myRoomMember, otherRoomMember)
givenRoomMembersState(MatrixRoomMembersState.Ready(roomMembers))
@@ -181,9 +207,11 @@ class RoomDetailsPresenterTest {
@Test
fun `present - initial state when user can invite others to room`() = runTest {
- val room = aMatrixRoom().apply {
- givenCanInviteResult(Result.success(true))
- }
+ val room = aMatrixRoom(
+ canInviteResult = { Result.success(true) },
+ canUserJoinCallResult = { Result.success(true) },
+ canSendStateResult = { _, _ -> Result.success(true) },
+ )
val presenter = createRoomDetailsPresenter(room, dispatchers = testCoroutineDispatchers())
presenter.test {
// Initially false
@@ -197,9 +225,11 @@ class RoomDetailsPresenterTest {
@Test
fun `present - initial state when user can not invite others to room`() = runTest {
- val room = aMatrixRoom().apply {
- givenCanInviteResult(Result.success(false))
- }
+ val room = aMatrixRoom(
+ canInviteResult = { Result.success(false) },
+ canUserJoinCallResult = { Result.success(true) },
+ canSendStateResult = { _, _ -> Result.success(true) },
+ )
val presenter = createRoomDetailsPresenter(room)
presenter.test {
assertThat(awaitItem().canInvite).isFalse()
@@ -210,9 +240,11 @@ class RoomDetailsPresenterTest {
@Test
fun `present - initial state when canInvite errors`() = runTest {
- val room = aMatrixRoom().apply {
- givenCanInviteResult(Result.failure(Throwable("Whoops")))
- }
+ val room = aMatrixRoom(
+ canInviteResult = { Result.failure(Throwable("Whoops")) },
+ canUserJoinCallResult = { Result.success(true) },
+ canSendStateResult = { _, _ -> Result.success(true) },
+ )
val presenter = createRoomDetailsPresenter(room)
presenter.test {
assertThat(awaitItem().canInvite).isFalse()
@@ -223,12 +255,18 @@ class RoomDetailsPresenterTest {
@Test
fun `present - initial state when user can edit one attribute`() = runTest {
- val room = aMatrixRoom().apply {
- givenCanSendStateResult(StateEventType.ROOM_TOPIC, Result.success(true))
- givenCanSendStateResult(StateEventType.ROOM_NAME, Result.success(false))
- givenCanSendStateResult(StateEventType.ROOM_AVATAR, Result.failure(Throwable("Whelp")))
- givenCanInviteResult(Result.success(false))
- }
+ val room = aMatrixRoom(
+ canSendStateResult = { _, stateEventType ->
+ when (stateEventType) {
+ StateEventType.ROOM_TOPIC -> Result.success(true)
+ StateEventType.ROOM_NAME -> Result.success(false)
+ StateEventType.ROOM_AVATAR -> Result.failure(Throwable("Whelp"))
+ else -> lambdaError()
+ }
+ },
+ canInviteResult = { Result.success(false) },
+ canUserJoinCallResult = { Result.success(true) },
+ )
val presenter = createRoomDetailsPresenter(room)
presenter.test {
// Initially false
@@ -247,14 +285,26 @@ class RoomDetailsPresenterTest {
val room = aMatrixRoom(
isEncrypted = true,
isDirect = true,
+ canSendStateResult = { _, stateEventType ->
+ when (stateEventType) {
+ StateEventType.ROOM_TOPIC -> Result.success(true)
+ StateEventType.ROOM_NAME -> Result.success(true)
+ StateEventType.ROOM_AVATAR -> Result.success(true)
+ else -> lambdaError()
+ }
+ },
+ canInviteResult = { Result.success(false) },
+ canUserJoinCallResult = { Result.success(true) },
+ getUpdatedMemberResult = { userId ->
+ when (userId) {
+ A_SESSION_ID -> Result.success(myRoomMember)
+ A_USER_ID_2 -> Result.success(otherRoomMember)
+ else -> lambdaError()
+ }
+ },
).apply {
val roomMembers = persistentListOf(myRoomMember, otherRoomMember)
givenRoomMembersState(MatrixRoomMembersState.Ready(roomMembers))
-
- givenCanSendStateResult(StateEventType.ROOM_TOPIC, Result.success(true))
- givenCanSendStateResult(StateEventType.ROOM_NAME, Result.success(true))
- givenCanSendStateResult(StateEventType.ROOM_AVATAR, Result.success(true))
- givenCanInviteResult(Result.success(false))
}
val presenter = createRoomDetailsPresenter(room)
presenter.test {
@@ -278,12 +328,28 @@ class RoomDetailsPresenterTest {
isEncrypted = true,
isDirect = true,
topic = null,
+ canSendStateResult = { _, stateEventType ->
+ when (stateEventType) {
+ StateEventType.ROOM_AVATAR,
+ StateEventType.ROOM_TOPIC,
+ StateEventType.ROOM_NAME -> Result.success(true)
+ else -> lambdaError()
+ }
+ },
+ canInviteResult = { Result.success(true) },
+ canUserJoinCallResult = { Result.success(true) },
+ getUpdatedMemberResult = { userId ->
+ when (userId) {
+ A_SESSION_ID -> Result.success(myRoomMember)
+ A_USER_ID_2 -> Result.success(otherRoomMember)
+ else -> lambdaError()
+ }
+ },
).apply {
val roomMembers = persistentListOf(myRoomMember, otherRoomMember)
givenRoomMembersState(MatrixRoomMembersState.Ready(roomMembers))
-
- givenCanSendStateResult(StateEventType.ROOM_TOPIC, Result.success(true))
}
+
val presenter = createRoomDetailsPresenter(room)
presenter.test {
skipItems(1)
@@ -297,12 +363,20 @@ class RoomDetailsPresenterTest {
@Test
fun `present - initial state when user can edit all attributes`() = runTest {
- val room = aMatrixRoom().apply {
- givenCanSendStateResult(StateEventType.ROOM_TOPIC, Result.success(true))
- givenCanSendStateResult(StateEventType.ROOM_NAME, Result.success(true))
- givenCanSendStateResult(StateEventType.ROOM_AVATAR, Result.success(true))
- givenCanInviteResult(Result.success(false))
- }
+ val room = aMatrixRoom(
+ canSendStateResult = { _, stateEventType ->
+ when (stateEventType) {
+ StateEventType.ROOM_TOPIC -> Result.success(true)
+ StateEventType.ROOM_NAME -> Result.success(true)
+ StateEventType.ROOM_AVATAR -> Result.success(true)
+ else -> lambdaError()
+ }
+ },
+ canInviteResult = {
+ Result.success(false)
+ },
+ canUserJoinCallResult = { Result.success(true) },
+ )
val presenter = createRoomDetailsPresenter(room)
presenter.test {
// Initially false
@@ -316,12 +390,20 @@ class RoomDetailsPresenterTest {
@Test
fun `present - initial state when user can edit no attributes`() = runTest {
- val room = aMatrixRoom().apply {
- givenCanSendStateResult(StateEventType.ROOM_TOPIC, Result.success(false))
- givenCanSendStateResult(StateEventType.ROOM_NAME, Result.success(false))
- givenCanSendStateResult(StateEventType.ROOM_AVATAR, Result.success(false))
- givenCanInviteResult(Result.success(false))
- }
+ val room = aMatrixRoom(
+ canSendStateResult = { _, stateEventType ->
+ when (stateEventType) {
+ StateEventType.ROOM_TOPIC -> Result.success(false)
+ StateEventType.ROOM_NAME -> Result.success(false)
+ StateEventType.ROOM_AVATAR -> Result.success(false)
+ else -> lambdaError()
+ }
+ },
+ canInviteResult = {
+ Result.success(false)
+ },
+ canUserJoinCallResult = { Result.success(true) },
+ )
val presenter = createRoomDetailsPresenter(room)
presenter.test {
// Initially false, and no further events
@@ -333,11 +415,21 @@ class RoomDetailsPresenterTest {
@Test
fun `present - topic state is hidden when no topic and user has no permission`() = runTest {
- val room = aMatrixRoom(topic = null).apply {
- givenCanSendStateResult(StateEventType.ROOM_TOPIC, Result.success(false))
- givenCanInviteResult(Result.success(false))
- }
-
+ val room = aMatrixRoom(
+ topic = null,
+ canSendStateResult = { _, stateEventType ->
+ when (stateEventType) {
+ StateEventType.ROOM_AVATAR,
+ StateEventType.ROOM_NAME -> Result.success(true)
+ StateEventType.ROOM_TOPIC -> Result.success(false)
+ else -> lambdaError()
+ }
+ },
+ canInviteResult = {
+ Result.success(false)
+ },
+ canUserJoinCallResult = { Result.success(true) },
+ )
val presenter = createRoomDetailsPresenter(room)
presenter.test {
// The initial state is "hidden" and no further state changes happen
@@ -349,12 +441,23 @@ class RoomDetailsPresenterTest {
@Test
fun `present - topic state is 'can add topic' when no topic and user has permission`() = runTest {
- val room = aMatrixRoom(topic = null).apply {
- givenCanSendStateResult(StateEventType.ROOM_TOPIC, Result.success(true))
- givenCanInviteResult(Result.success(false))
+ val room = aMatrixRoom(
+ topic = null,
+ canSendStateResult = { _, stateEventType ->
+ when (stateEventType) {
+ StateEventType.ROOM_AVATAR,
+ StateEventType.ROOM_TOPIC,
+ StateEventType.ROOM_NAME -> Result.success(true)
+ else -> lambdaError()
+ }
+ },
+ canInviteResult = {
+ Result.success(false)
+ },
+ canUserJoinCallResult = { Result.success(true) },
+ ).apply {
givenRoomInfo(aRoomInfo(topic = null))
}
-
val presenter = createRoomDetailsPresenter(room)
presenter.test {
// Ignore the initial state
@@ -370,7 +473,11 @@ class RoomDetailsPresenterTest {
@Test
fun `present - leave room event is passed on to leave room presenter`() = runTest {
val leaveRoomPresenter = FakeLeaveRoomPresenter()
- val room = aMatrixRoom()
+ val room = aMatrixRoom(
+ canInviteResult = { Result.success(true) },
+ canUserJoinCallResult = { Result.success(true) },
+ canSendStateResult = { _, _ -> Result.success(true) },
+ )
val presenter = createRoomDetailsPresenter(
room = room,
leaveRoomPresenter = leaveRoomPresenter,
@@ -379,7 +486,11 @@ class RoomDetailsPresenterTest {
presenter.test {
awaitItem().eventSink(RoomDetailsEvent.LeaveRoom)
- assertThat(leaveRoomPresenter.events).contains(LeaveRoomEvent.ShowConfirmation(room.roomId))
+ assertThat(leaveRoomPresenter.events).contains(
+ LeaveRoomEvent.ShowConfirmation(
+ room.roomId
+ )
+ )
cancelAndIgnoreRemainingEvents()
}
@@ -389,33 +500,54 @@ class RoomDetailsPresenterTest {
fun `present - notification mode changes`() = runTest {
val leaveRoomPresenter = FakeLeaveRoomPresenter()
val notificationSettingsService = FakeNotificationSettingsService()
- val room = aMatrixRoom(notificationSettingsService = notificationSettingsService)
+ val room = aMatrixRoom(
+ notificationSettingsService = notificationSettingsService,
+ canInviteResult = { Result.success(true) },
+ canUserJoinCallResult = { Result.success(true) },
+ canSendStateResult = { _, _ -> Result.success(true) },
+ )
val presenter = createRoomDetailsPresenter(
room = room,
leaveRoomPresenter = leaveRoomPresenter,
notificationSettingsService = notificationSettingsService,
)
presenter.test {
- notificationSettingsService.setRoomNotificationMode(room.roomId, RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY)
+ notificationSettingsService.setRoomNotificationMode(
+ room.roomId,
+ RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY
+ )
val updatedState = consumeItemsUntilPredicate {
it.roomNotificationSettings?.mode == RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY
}.last()
- assertThat(updatedState.roomNotificationSettings?.mode).isEqualTo(RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY)
+ assertThat(updatedState.roomNotificationSettings?.mode).isEqualTo(
+ RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY
+ )
cancelAndIgnoreRemainingEvents()
}
}
@Test
fun `present - mute room notifications`() = runTest {
- val notificationSettingsService = FakeNotificationSettingsService(initialRoomMode = RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY)
- val room = aMatrixRoom(notificationSettingsService = notificationSettingsService)
- val presenter = createRoomDetailsPresenter(room = room, notificationSettingsService = notificationSettingsService)
+ val notificationSettingsService =
+ FakeNotificationSettingsService(initialRoomMode = RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY)
+ val room = aMatrixRoom(
+ notificationSettingsService = notificationSettingsService,
+ canInviteResult = { Result.success(true) },
+ canUserJoinCallResult = { Result.success(true) },
+ canSendStateResult = { _, _ -> Result.success(true) },
+ )
+ val presenter = createRoomDetailsPresenter(
+ room = room,
+ notificationSettingsService = notificationSettingsService
+ )
presenter.test {
awaitItem().eventSink(RoomDetailsEvent.MuteNotification)
val updatedState = consumeItemsUntilPredicate(timeout = 250.milliseconds) {
it.roomNotificationSettings?.mode == RoomNotificationMode.MUTE
}.last()
- assertThat(updatedState.roomNotificationSettings?.mode).isEqualTo(RoomNotificationMode.MUTE)
+ assertThat(updatedState.roomNotificationSettings?.mode).isEqualTo(
+ RoomNotificationMode.MUTE
+ )
cancelAndIgnoreRemainingEvents()
}
}
@@ -426,29 +558,50 @@ class RoomDetailsPresenterTest {
initialRoomMode = RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY,
initialEncryptedGroupDefaultMode = RoomNotificationMode.ALL_MESSAGES
)
- val room = aMatrixRoom(notificationSettingsService = notificationSettingsService)
- val presenter = createRoomDetailsPresenter(room = room, notificationSettingsService = notificationSettingsService)
+ val room = aMatrixRoom(
+ notificationSettingsService = notificationSettingsService,
+ canInviteResult = { Result.success(true) },
+ canUserJoinCallResult = { Result.success(true) },
+ canSendStateResult = { _, _ -> Result.success(true) },
+ )
+ val presenter = createRoomDetailsPresenter(
+ room = room,
+ notificationSettingsService = notificationSettingsService
+ )
presenter.test {
awaitItem().eventSink(RoomDetailsEvent.UnmuteNotification)
val updatedState = consumeItemsUntilPredicate {
it.roomNotificationSettings?.mode == RoomNotificationMode.ALL_MESSAGES
}.last()
- assertThat(updatedState.roomNotificationSettings?.mode).isEqualTo(RoomNotificationMode.ALL_MESSAGES)
+ assertThat(updatedState.roomNotificationSettings?.mode).isEqualTo(
+ RoomNotificationMode.ALL_MESSAGES
+ )
cancelAndIgnoreRemainingEvents()
}
}
@Test
fun `present - when set is favorite event is emitted, then the action is called`() = runTest {
- val room = FakeMatrixRoom()
+ val setIsFavoriteResult = lambdaRecorder> { _ -> Result.success(Unit) }
+ val room = FakeMatrixRoom(
+ setIsFavoriteResult = setIsFavoriteResult,
+ canInviteResult = { Result.success(true) },
+ canUserJoinCallResult = { Result.success(true) },
+ canSendStateResult = { _, _ -> Result.success(true) },
+ )
val analyticsService = FakeAnalyticsService()
- val presenter = createRoomDetailsPresenter(room = room, analyticsService = analyticsService)
+ val presenter =
+ createRoomDetailsPresenter(room = room, analyticsService = analyticsService)
presenter.test {
val initialState = awaitItem()
initialState.eventSink(RoomDetailsEvent.SetFavorite(true))
- assertThat(room.setIsFavoriteCalls).isEqualTo(listOf(true))
+ setIsFavoriteResult.assertions().isCalledOnce().with(value(true))
initialState.eventSink(RoomDetailsEvent.SetFavorite(false))
- assertThat(room.setIsFavoriteCalls).isEqualTo(listOf(true, false))
+ setIsFavoriteResult.assertions().isCalledExactly(2)
+ .withSequence(
+ listOf(value(true)),
+ listOf(value(false)),
+ )
assertThat(analyticsService.capturedEvents).containsExactly(
Interaction(name = Interaction.Name.MobileRoomFavouriteToggle),
Interaction(name = Interaction.Name.MobileRoomFavouriteToggle)
@@ -459,7 +612,11 @@ class RoomDetailsPresenterTest {
@Test
fun `present - changes in room info updates the is favorite flag`() = runTest {
- val room = aMatrixRoom()
+ val room = aMatrixRoom(
+ canInviteResult = { Result.success(true) },
+ canUserJoinCallResult = { Result.success(true) },
+ canSendStateResult = { _, _ -> Result.success(true) },
+ )
val presenter = createRoomDetailsPresenter(room = room)
presenter.test {
room.givenRoomInfo(aRoomInfo(isFavorite = true))
diff --git a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/edit/RoomDetailsEditPresenterTest.kt b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/edit/RoomDetailsEditPresenterTest.kt
index f287fe4ab4..5d8a43ca4b 100644
--- a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/edit/RoomDetailsEditPresenterTest.kt
+++ b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/edit/RoomDetailsEditPresenterTest.kt
@@ -39,6 +39,9 @@ import io.element.android.libraries.permissions.api.PermissionsPresenter
import io.element.android.libraries.permissions.test.FakePermissionsPresenter
import io.element.android.libraries.permissions.test.FakePermissionsPresenterFactory
import io.element.android.tests.testutils.WarmUpRule
+import io.element.android.tests.testutils.lambda.lambdaError
+import io.element.android.tests.testutils.lambda.lambdaRecorder
+import io.element.android.tests.testutils.lambda.value
import io.mockk.every
import io.mockk.mockk
import io.mockk.mockkStatic
@@ -98,6 +101,7 @@ class RoomDetailsEditPresenterTest {
displayName = A_ROOM_NAME,
rawName = A_ROOM_RAW_NAME,
emitRoomInfo = true,
+ canSendStateResult = { _, _ -> Result.success(true) }
)
val presenter = createRoomDetailsEditPresenter(room)
moleculeFlow(RecompositionMode.Immediate) {
@@ -120,11 +124,17 @@ class RoomDetailsEditPresenterTest {
@Test
fun `present - sets canChangeName if user has permission`() = runTest {
- val room = aMatrixRoom(avatarUrl = AN_AVATAR_URL).apply {
- givenCanSendStateResult(StateEventType.ROOM_NAME, Result.success(true))
- givenCanSendStateResult(StateEventType.ROOM_AVATAR, Result.success(false))
- givenCanSendStateResult(StateEventType.ROOM_TOPIC, Result.failure(Throwable("Oops")))
- }
+ val room = aMatrixRoom(
+ avatarUrl = AN_AVATAR_URL,
+ canSendStateResult = { _, stateEventType ->
+ when (stateEventType) {
+ StateEventType.ROOM_NAME -> Result.success(true)
+ StateEventType.ROOM_AVATAR -> Result.success(false)
+ StateEventType.ROOM_TOPIC -> Result.failure(Throwable("Oops"))
+ else -> lambdaError()
+ }
+ },
+ )
val presenter = createRoomDetailsEditPresenter(room)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
@@ -144,11 +154,17 @@ class RoomDetailsEditPresenterTest {
@Test
fun `present - sets canChangeAvatar if user has permission`() = runTest {
- val room = aMatrixRoom(avatarUrl = AN_AVATAR_URL).apply {
- givenCanSendStateResult(StateEventType.ROOM_NAME, Result.success(false))
- givenCanSendStateResult(StateEventType.ROOM_AVATAR, Result.success(true))
- givenCanSendStateResult(StateEventType.ROOM_TOPIC, Result.failure(Throwable("Oops")))
- }
+ val room = aMatrixRoom(
+ avatarUrl = AN_AVATAR_URL,
+ canSendStateResult = { _, stateEventType ->
+ when (stateEventType) {
+ StateEventType.ROOM_NAME -> Result.success(false)
+ StateEventType.ROOM_AVATAR -> Result.success(true)
+ StateEventType.ROOM_TOPIC -> Result.failure(Throwable("Oops"))
+ else -> lambdaError()
+ }
+ }
+ )
val presenter = createRoomDetailsEditPresenter(room)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
@@ -168,11 +184,17 @@ class RoomDetailsEditPresenterTest {
@Test
fun `present - sets canChangeTopic if user has permission`() = runTest {
- val room = aMatrixRoom(avatarUrl = AN_AVATAR_URL).apply {
- givenCanSendStateResult(StateEventType.ROOM_NAME, Result.success(false))
- givenCanSendStateResult(StateEventType.ROOM_AVATAR, Result.failure(Throwable("Oops")))
- givenCanSendStateResult(StateEventType.ROOM_TOPIC, Result.success(true))
- }
+ val room = aMatrixRoom(
+ avatarUrl = AN_AVATAR_URL,
+ canSendStateResult = { _, stateEventType ->
+ when (stateEventType) {
+ StateEventType.ROOM_NAME -> Result.success(false)
+ StateEventType.ROOM_AVATAR -> Result.failure(Throwable("Oops"))
+ StateEventType.ROOM_TOPIC -> Result.success(true)
+ else -> lambdaError()
+ }
+ }
+ )
val presenter = createRoomDetailsEditPresenter(room)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
@@ -197,6 +219,7 @@ class RoomDetailsEditPresenterTest {
displayName = "Name",
avatarUrl = AN_AVATAR_URL,
emitRoomInfo = true,
+ canSendStateResult = { _, _ -> Result.success(true) }
)
val presenter = createRoomDetailsEditPresenter(room)
moleculeFlow(RecompositionMode.Immediate) {
@@ -240,6 +263,7 @@ class RoomDetailsEditPresenterTest {
displayName = "Name",
avatarUrl = AN_AVATAR_URL,
emitRoomInfo = true,
+ canSendStateResult = { _, _ -> Result.success(true) }
)
fakePickerProvider.givenResult(anotherAvatarUri)
val presenter = createRoomDetailsEditPresenter(room)
@@ -262,6 +286,7 @@ class RoomDetailsEditPresenterTest {
displayName = "Name",
avatarUrl = AN_AVATAR_URL,
emitRoomInfo = true,
+ canSendStateResult = { _, _ -> Result.success(true) }
)
fakePickerProvider.givenResult(anotherAvatarUri)
val fakePermissionsPresenter = FakePermissionsPresenter()
@@ -298,6 +323,7 @@ class RoomDetailsEditPresenterTest {
displayName = "Name",
avatarUrl = AN_AVATAR_URL,
emitRoomInfo = true,
+ canSendStateResult = { _, _ -> Result.success(true) }
)
fakePickerProvider.givenResult(roomAvatarUri)
val presenter = createRoomDetailsEditPresenter(room)
@@ -346,6 +372,7 @@ class RoomDetailsEditPresenterTest {
displayName = "fallback",
avatarUrl = null,
emitRoomInfo = true,
+ canSendStateResult = { _, _ -> Result.success(true) }
)
fakePickerProvider.givenResult(roomAvatarUri)
val presenter = createRoomDetailsEditPresenter(room)
@@ -389,11 +416,18 @@ class RoomDetailsEditPresenterTest {
@Test
fun `present - save changes room details if different`() = runTest {
+ val setNameResult = lambdaRecorder { _: String -> Result.success(Unit) }
+ val setTopicResult = lambdaRecorder { _: String -> Result.success(Unit) }
+ val removeAvatarResult = lambdaRecorder> { Result.success(Unit) }
val room = aMatrixRoom(
topic = "My topic",
displayName = "Name",
avatarUrl = AN_AVATAR_URL,
emitRoomInfo = true,
+ setNameResult = setNameResult,
+ setTopicResult = setTopicResult,
+ removeAvatarResult = removeAvatarResult,
+ canSendStateResult = { _, _ -> Result.success(true) }
)
val presenter = createRoomDetailsEditPresenter(room)
moleculeFlow(RecompositionMode.Immediate) {
@@ -405,16 +439,20 @@ class RoomDetailsEditPresenterTest {
initialState.eventSink(RoomDetailsEditEvents.HandleAvatarAction(AvatarAction.Remove))
initialState.eventSink(RoomDetailsEditEvents.Save)
skipItems(5)
- assertThat(room.newName).isEqualTo("New name")
- assertThat(room.newTopic).isEqualTo("New topic")
- assertThat(room.newAvatarData).isNull()
- assertThat(room.removedAvatar).isTrue()
+ setNameResult.assertions().isCalledOnce().with(value("New name"))
+ setTopicResult.assertions().isCalledOnce().with(value("New topic"))
+ removeAvatarResult.assertions().isCalledOnce()
}
}
@Test
fun `present - save doesn't change room details if they're the same trimmed`() = runTest {
- val room = aMatrixRoom(topic = "My topic", displayName = "Name", avatarUrl = AN_AVATAR_URL)
+ val room = aMatrixRoom(
+ topic = "My topic",
+ displayName = "Name",
+ avatarUrl = AN_AVATAR_URL,
+ canSendStateResult = { _, _ -> Result.success(true) }
+ )
val presenter = createRoomDetailsEditPresenter(room)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
@@ -423,17 +461,18 @@ class RoomDetailsEditPresenterTest {
initialState.eventSink(RoomDetailsEditEvents.UpdateRoomName(" Name "))
initialState.eventSink(RoomDetailsEditEvents.UpdateRoomTopic(" My topic "))
initialState.eventSink(RoomDetailsEditEvents.Save)
- assertThat(room.newName).isNull()
- assertThat(room.newTopic).isNull()
- assertThat(room.newAvatarData).isNull()
- assertThat(room.removedAvatar).isFalse()
cancelAndIgnoreRemainingEvents()
}
}
@Test
fun `present - save doesn't change topic if it was unset and is now blank`() = runTest {
- val room = aMatrixRoom(topic = null, displayName = "Name", avatarUrl = AN_AVATAR_URL)
+ val room = aMatrixRoom(
+ topic = null,
+ displayName = "Name",
+ avatarUrl = AN_AVATAR_URL,
+ canSendStateResult = { _, _ -> Result.success(true) }
+ )
val presenter = createRoomDetailsEditPresenter(room)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
@@ -441,17 +480,18 @@ class RoomDetailsEditPresenterTest {
val initialState = awaitItem()
initialState.eventSink(RoomDetailsEditEvents.UpdateRoomTopic(""))
initialState.eventSink(RoomDetailsEditEvents.Save)
- assertThat(room.newName).isNull()
- assertThat(room.newTopic).isNull()
- assertThat(room.newAvatarData).isNull()
- assertThat(room.removedAvatar).isFalse()
cancelAndIgnoreRemainingEvents()
}
}
@Test
fun `present - save doesn't change name if it's now empty`() = runTest {
- val room = aMatrixRoom(topic = "My topic", displayName = "Name", avatarUrl = AN_AVATAR_URL)
+ val room = aMatrixRoom(
+ topic = "My topic",
+ displayName = "Name",
+ avatarUrl = AN_AVATAR_URL,
+ canSendStateResult = { _, _ -> Result.success(true) }
+ )
val presenter = createRoomDetailsEditPresenter(room)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
@@ -459,17 +499,20 @@ class RoomDetailsEditPresenterTest {
val initialState = awaitItem()
initialState.eventSink(RoomDetailsEditEvents.UpdateRoomName(""))
initialState.eventSink(RoomDetailsEditEvents.Save)
- assertThat(room.newName).isNull()
- assertThat(room.newTopic).isNull()
- assertThat(room.newAvatarData).isNull()
- assertThat(room.removedAvatar).isFalse()
cancelAndIgnoreRemainingEvents()
}
}
@Test
fun `present - save processes and sets avatar when processor returns successfully`() = runTest {
- val room = aMatrixRoom(topic = "My topic", displayName = "Name", avatarUrl = AN_AVATAR_URL)
+ val updateAvatarResult = lambdaRecorder { _: String, _: ByteArray -> Result.success(Unit) }
+ val room = aMatrixRoom(
+ topic = "My topic",
+ displayName = "Name",
+ avatarUrl = AN_AVATAR_URL,
+ updateAvatarResult = updateAvatarResult,
+ canSendStateResult = { _, _ -> Result.success(true) }
+ )
givenPickerReturnsFile()
val presenter = createRoomDetailsEditPresenter(room)
moleculeFlow(RecompositionMode.Immediate) {
@@ -478,17 +521,19 @@ class RoomDetailsEditPresenterTest {
val initialState = awaitItem()
initialState.eventSink(RoomDetailsEditEvents.HandleAvatarAction(AvatarAction.ChoosePhoto))
initialState.eventSink(RoomDetailsEditEvents.Save)
- skipItems(3)
- assertThat(room.newName).isNull()
- assertThat(room.newTopic).isNull()
- assertThat(room.newAvatarData).isSameInstanceAs(fakeFileContents)
- assertThat(room.removedAvatar).isFalse()
+ skipItems(4)
+ updateAvatarResult.assertions().isCalledOnce().with(value("image/jpeg"), value(fakeFileContents))
}
}
@Test
fun `present - save does not set avatar data if processor fails`() = runTest {
- val room = aMatrixRoom(topic = "My topic", displayName = "Name", avatarUrl = AN_AVATAR_URL)
+ val room = aMatrixRoom(
+ topic = "My topic",
+ displayName = "Name",
+ avatarUrl = AN_AVATAR_URL,
+ canSendStateResult = { _, _ -> Result.success(true) }
+ )
fakePickerProvider.givenResult(anotherAvatarUri)
fakeMediaPreProcessor.givenResult(Result.failure(Throwable("Oh no")))
val presenter = createRoomDetailsEditPresenter(room)
@@ -498,11 +543,7 @@ class RoomDetailsEditPresenterTest {
val initialState = awaitItem()
initialState.eventSink(RoomDetailsEditEvents.HandleAvatarAction(AvatarAction.ChoosePhoto))
initialState.eventSink(RoomDetailsEditEvents.Save)
- skipItems(2)
- assertThat(room.newName).isNull()
- assertThat(room.newTopic).isNull()
- assertThat(room.newAvatarData).isNull()
- assertThat(room.removedAvatar).isFalse()
+ skipItems(3)
assertThat(awaitItem().saveAction).isInstanceOf(AsyncAction.Failure::class.java)
}
}
@@ -514,9 +555,9 @@ class RoomDetailsEditPresenterTest {
displayName = "Name",
avatarUrl = AN_AVATAR_URL,
emitRoomInfo = true,
- ).apply {
- givenSetNameResult(Result.failure(Throwable("!")))
- }
+ setNameResult = { Result.failure(Throwable("!")) },
+ canSendStateResult = { _, _ -> Result.success(true) }
+ )
saveAndAssertFailure(room, RoomDetailsEditEvents.UpdateRoomName("New name"))
}
@@ -527,9 +568,9 @@ class RoomDetailsEditPresenterTest {
displayName = "Name",
avatarUrl = AN_AVATAR_URL,
emitRoomInfo = true,
- ).apply {
- givenSetTopicResult(Result.failure(Throwable("!")))
- }
+ setTopicResult = { Result.failure(Throwable("!")) },
+ canSendStateResult = { _, _ -> Result.success(true) }
+ )
saveAndAssertFailure(room, RoomDetailsEditEvents.UpdateRoomTopic("New topic"))
}
@@ -540,9 +581,9 @@ class RoomDetailsEditPresenterTest {
displayName = "Name",
avatarUrl = AN_AVATAR_URL,
emitRoomInfo = true,
- ).apply {
- givenRemoveAvatarResult(Result.failure(Throwable("!")))
- }
+ removeAvatarResult = { Result.failure(Throwable("!")) },
+ canSendStateResult = { _, _ -> Result.success(true) }
+ )
saveAndAssertFailure(room, RoomDetailsEditEvents.HandleAvatarAction(AvatarAction.Remove))
}
@@ -554,18 +595,22 @@ class RoomDetailsEditPresenterTest {
displayName = "Name",
avatarUrl = AN_AVATAR_URL,
emitRoomInfo = true,
- ).apply {
- givenUpdateAvatarResult(Result.failure(Throwable("!")))
- }
+ updateAvatarResult = { _, _ -> Result.failure(Throwable("!")) },
+ canSendStateResult = { _, _ -> Result.success(true) }
+ )
saveAndAssertFailure(room, RoomDetailsEditEvents.HandleAvatarAction(AvatarAction.ChoosePhoto))
}
@Test
fun `present - CancelSaveChanges resets save action state`() = runTest {
givenPickerReturnsFile()
- val room = aMatrixRoom(topic = "My topic", displayName = "Name", avatarUrl = AN_AVATAR_URL).apply {
- givenSetTopicResult(Result.failure(Throwable("!")))
- }
+ val room = aMatrixRoom(
+ topic = "My topic",
+ displayName = "Name",
+ avatarUrl = AN_AVATAR_URL,
+ setTopicResult = { Result.failure(Throwable("!")) },
+ canSendStateResult = { _, _ -> Result.success(true) }
+ )
val presenter = createRoomDetailsEditPresenter(room)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
@@ -573,7 +618,7 @@ class RoomDetailsEditPresenterTest {
val initialState = awaitItem()
initialState.eventSink(RoomDetailsEditEvents.UpdateRoomTopic("foo"))
initialState.eventSink(RoomDetailsEditEvents.Save)
- skipItems(2)
+ skipItems(3)
assertThat(awaitItem().saveAction).isInstanceOf(AsyncAction.Failure::class.java)
initialState.eventSink(RoomDetailsEditEvents.CancelSaveChanges)
assertThat(awaitItem().saveAction).isInstanceOf(AsyncAction.Uninitialized::class.java)
diff --git a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/members/RoomMemberListPresenterTest.kt b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/members/RoomMemberListPresenterTest.kt
index a352d8bf27..8ca8b7b810 100644
--- a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/members/RoomMemberListPresenterTest.kt
+++ b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/members/RoomMemberListPresenterTest.kt
@@ -52,7 +52,10 @@ class RoomMemberListPresenterTest {
@Test
fun `member loading is done automatically on start, but is async`() = runTest {
- val room = FakeMatrixRoom().apply {
+ val room = FakeMatrixRoom(
+ updateMembersResult = { Result.success(Unit) },
+ canInviteResult = { Result.success(true) }
+ ).apply {
// Needed to avoid discarding the loaded members as a partial and invalid result
givenRoomInfo(aRoomInfo(joinedMembersCount = 2))
}
@@ -78,7 +81,12 @@ class RoomMemberListPresenterTest {
@Test
fun `open search`() = runTest {
- val presenter = createPresenter()
+ val presenter = createPresenter(
+ matrixRoom = FakeMatrixRoom(
+ updateMembersResult = { Result.success(Unit) },
+ canInviteResult = { Result.success(true) }
+ )
+ )
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
@@ -93,7 +101,12 @@ class RoomMemberListPresenterTest {
@Test
fun `search for something which is not found`() = runTest {
- val presenter = createPresenter()
+ val presenter = createPresenter(
+ matrixRoom = FakeMatrixRoom(
+ updateMembersResult = { Result.success(Unit) },
+ canInviteResult = { Result.success(true) }
+ )
+ )
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
@@ -112,7 +125,12 @@ class RoomMemberListPresenterTest {
@Test
fun `search for something which is found`() = runTest {
- val presenter = createPresenter()
+ val presenter = createPresenter(
+ matrixRoom = FakeMatrixRoom(
+ updateMembersResult = { Result.success(Unit) },
+ canInviteResult = { Result.success(true) }
+ )
+ )
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
@@ -134,9 +152,10 @@ class RoomMemberListPresenterTest {
@Test
fun `present - asynchronously sets canInvite when user has correct power level`() = runTest {
val presenter = createPresenter(
- matrixRoom = FakeMatrixRoom().apply {
- givenCanInviteResult(Result.success(true))
- }
+ matrixRoom = FakeMatrixRoom(
+ canInviteResult = { Result.success(true) },
+ updateMembersResult = { Result.success(Unit) }
+ )
)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
@@ -150,9 +169,10 @@ class RoomMemberListPresenterTest {
@Test
fun `present - asynchronously sets canInvite when user does not have correct power level`() = runTest {
val presenter = createPresenter(
- matrixRoom = FakeMatrixRoom().apply {
- givenCanInviteResult(Result.success(false))
- }
+ matrixRoom = FakeMatrixRoom(
+ canInviteResult = { Result.success(false) },
+ updateMembersResult = { Result.success(Unit) }
+ )
)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
@@ -166,9 +186,10 @@ class RoomMemberListPresenterTest {
@Test
fun `present - asynchronously sets canInvite when power level check fails`() = runTest {
val presenter = createPresenter(
- matrixRoom = FakeMatrixRoom().apply {
- givenCanInviteResult(Result.failure(Throwable("Eek")))
- }
+ matrixRoom = FakeMatrixRoom(
+ canInviteResult = { Result.failure(Throwable("Eek")) },
+ updateMembersResult = { Result.success(Unit) }
+ )
)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
@@ -183,7 +204,14 @@ class RoomMemberListPresenterTest {
fun `present - RoomMemberSelected by default opens the room member details through the navigator`() = runTest {
val navigator = FakeRoomMemberListNavigator()
val moderationPresenter = FakeRoomMembersModerationPresenter(canDisplayModerationActions = false)
- val presenter = createPresenter(moderationPresenter = moderationPresenter, navigator = navigator)
+ val presenter = createPresenter(
+ moderationPresenter = moderationPresenter,
+ navigator = navigator,
+ matrixRoom = FakeMatrixRoom(
+ updateMembersResult = { Result.success(Unit) },
+ canInviteResult = { Result.success(true) }
+ )
+ )
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
@@ -205,7 +233,14 @@ class RoomMemberListPresenterTest {
val moderationPresenter = FakeRoomMembersModerationPresenter(canDisplayModerationActions = true).apply {
givenState(capturingState)
}
- val presenter = createPresenter(moderationPresenter = moderationPresenter, navigator = navigator)
+ val presenter = createPresenter(
+ moderationPresenter = moderationPresenter,
+ navigator = navigator,
+ matrixRoom = FakeMatrixRoom(
+ updateMembersResult = { Result.success(Unit) },
+ canInviteResult = { Result.success(true) }
+ )
+ )
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
@@ -236,10 +271,12 @@ private fun TestScope.createDataSource(
@ExperimentalCoroutinesApi
private fun TestScope.createPresenter(
coroutineDispatchers: CoroutineDispatchers = testCoroutineDispatchers(useUnconfinedTestDispatcher = true),
- matrixRoom: MatrixRoom = FakeMatrixRoom(),
+ matrixRoom: MatrixRoom = FakeMatrixRoom(
+ updateMembersResult = { Result.success(Unit) }
+ ),
roomMemberListDataSource: RoomMemberListDataSource = createDataSource(coroutineDispatchers = coroutineDispatchers),
moderationPresenter: FakeRoomMembersModerationPresenter = FakeRoomMembersModerationPresenter(),
- navigator: RoomMemberListNavigator = object : RoomMemberListNavigator { }
+ navigator: RoomMemberListNavigator = object : RoomMemberListNavigator {}
) = RoomMemberListPresenter(
room = matrixRoom,
roomMemberListDataSource = roomMemberListDataSource,
diff --git a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/members/details/RoomMemberDetailsPresenterTest.kt b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/members/details/RoomMemberDetailsPresenterTest.kt
index c9df8d381c..1012de62bd 100644
--- a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/members/details/RoomMemberDetailsPresenterTest.kt
+++ b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/members/details/RoomMemberDetailsPresenterTest.kt
@@ -34,6 +34,7 @@ import io.element.android.libraries.matrix.api.MatrixClient
import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.api.room.MatrixRoom
import io.element.android.libraries.matrix.api.room.MatrixRoomMembersState
+import io.element.android.libraries.matrix.test.AN_EXCEPTION
import io.element.android.libraries.matrix.test.A_ROOM_ID
import io.element.android.libraries.matrix.test.A_THROWABLE
import io.element.android.libraries.matrix.test.FakeMatrixClient
@@ -53,9 +54,11 @@ class RoomMemberDetailsPresenterTest {
@Test
fun `present - returns the room member's data, then updates it if needed`() = runTest {
val roomMember = aRoomMember(displayName = "Alice")
- val room = aMatrixRoom().apply {
- givenUserDisplayNameResult(Result.success("A custom name"))
- givenUserAvatarUrlResult(Result.success("A custom avatar"))
+ val room = aMatrixRoom(
+ userDisplayNameResult = { Result.success("A custom name") },
+ userAvatarUrlResult = { Result.success("A custom avatar") },
+ getUpdatedMemberResult = { Result.success(roomMember) },
+ ).apply {
givenRoomMembersState(MatrixRoomMembersState.Ready(persistentListOf(roomMember)))
}
val presenter = createRoomMemberDetailsPresenter(
@@ -82,11 +85,14 @@ class RoomMemberDetailsPresenterTest {
@Test
fun `present - will recover when retrieving room member details fails`() = runTest {
val roomMember = aRoomMember(displayName = "Alice")
- val room = aMatrixRoom().apply {
- givenUserDisplayNameResult(Result.failure(Throwable()))
- givenUserAvatarUrlResult(Result.failure(Throwable()))
+ val room = aMatrixRoom(
+ userDisplayNameResult = { Result.failure(Throwable()) },
+ userAvatarUrlResult = { Result.failure(Throwable()) },
+ getUpdatedMemberResult = { Result.failure(AN_EXCEPTION) },
+ ).apply {
givenRoomMembersState(MatrixRoomMembersState.Ready(persistentListOf(roomMember)))
}
+
val presenter = createRoomMemberDetailsPresenter(
room = room,
roomMemberId = roomMember.userId
@@ -105,9 +111,11 @@ class RoomMemberDetailsPresenterTest {
@Test
fun `present - will fallback to original data if the updated data is null`() = runTest {
val roomMember = aRoomMember(displayName = "Alice")
- val room = aMatrixRoom().apply {
- givenUserDisplayNameResult(Result.success(null))
- givenUserAvatarUrlResult(Result.success(null))
+ val room = aMatrixRoom(
+ userDisplayNameResult = { Result.success(null) },
+ userAvatarUrlResult = { Result.success(null) },
+ getUpdatedMemberResult = { Result.success(roomMember) }
+ ).apply {
givenRoomMembersState(MatrixRoomMembersState.Ready(persistentListOf(roomMember)))
}
val presenter = createRoomMemberDetailsPresenter(
@@ -128,10 +136,11 @@ class RoomMemberDetailsPresenterTest {
@Test
fun `present - will fallback to user profile if user is not a member of the room`() = runTest {
val bobProfile = aMatrixUser("@bob:server.org", "Bob", avatarUrl = "anAvatarUrl")
- val room = aMatrixRoom().apply {
- givenUserDisplayNameResult(Result.failure(Exception("Not a member!")))
- givenUserAvatarUrlResult(Result.failure(Exception("Not a member!")))
- }
+ val room = aMatrixRoom(
+ userDisplayNameResult = { Result.failure(Exception("Not a member!")) },
+ userAvatarUrlResult = { Result.failure(Exception("Not a member!")) },
+ getUpdatedMemberResult = { Result.failure(AN_EXCEPTION) },
+ )
val client = FakeMatrixClient().apply {
givenGetProfileResult(bobProfile.userId, Result.success(bobProfile))
}
@@ -154,7 +163,13 @@ class RoomMemberDetailsPresenterTest {
@Test
fun `present - BlockUser needing confirmation displays confirmation dialog`() = runTest {
- val presenter = createRoomMemberDetailsPresenter()
+ val presenter = createRoomMemberDetailsPresenter(
+ room = aMatrixRoom(
+ getUpdatedMemberResult = { Result.failure(AN_EXCEPTION) },
+ userDisplayNameResult = { Result.success("Alice") },
+ userAvatarUrlResult = { Result.success("anAvatarUrl") },
+ )
+ )
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
@@ -176,6 +191,11 @@ class RoomMemberDetailsPresenterTest {
val client = FakeMatrixClient()
val roomMember = aRoomMember()
val presenter = createRoomMemberDetailsPresenter(
+ room = aMatrixRoom(
+ getUpdatedMemberResult = { Result.failure(AN_EXCEPTION) },
+ userDisplayNameResult = { Result.success("Alice") },
+ userAvatarUrlResult = { Result.success("anAvatarUrl") },
+ ),
client = client,
roomMemberId = roomMember.userId
)
@@ -199,13 +219,21 @@ class RoomMemberDetailsPresenterTest {
fun `present - BlockUser with error`() = runTest {
val matrixClient = FakeMatrixClient()
matrixClient.givenIgnoreUserResult(Result.failure(A_THROWABLE))
- val presenter = createRoomMemberDetailsPresenter(client = matrixClient)
+ val presenter = createRoomMemberDetailsPresenter(
+ client = matrixClient,
+ room = aMatrixRoom(
+ getUpdatedMemberResult = { Result.success(aRoomMember(displayName = "Alice")) },
+ userDisplayNameResult = { Result.success("Alice") },
+ userAvatarUrlResult = { Result.success("anAvatarUrl") },
+ ),
+ )
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
val initialState = awaitFirstItem()
initialState.eventSink(UserProfileEvents.BlockUser(needsConfirmation = false))
assertThat(awaitItem().isBlocked.isLoading()).isTrue()
+ skipItems(2)
val errorState = awaitItem()
assertThat(errorState.isBlocked.errorOrNull()).isEqualTo(A_THROWABLE)
// Clear error
@@ -218,13 +246,21 @@ class RoomMemberDetailsPresenterTest {
fun `present - UnblockUser with error`() = runTest {
val matrixClient = FakeMatrixClient()
matrixClient.givenUnignoreUserResult(Result.failure(A_THROWABLE))
- val presenter = createRoomMemberDetailsPresenter(client = matrixClient)
+ val presenter = createRoomMemberDetailsPresenter(
+ room = aMatrixRoom(
+ getUpdatedMemberResult = { Result.success(aRoomMember(displayName = "Alice")) },
+ userDisplayNameResult = { Result.success("Alice") },
+ userAvatarUrlResult = { Result.success("anAvatarUrl") },
+ ),
+ client = matrixClient,
+ )
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
val initialState = awaitFirstItem()
initialState.eventSink(UserProfileEvents.UnblockUser(needsConfirmation = false))
assertThat(awaitItem().isBlocked.isLoading()).isTrue()
+ skipItems(2)
val errorState = awaitItem()
assertThat(errorState.isBlocked.errorOrNull()).isEqualTo(A_THROWABLE)
// Clear error
@@ -235,7 +271,13 @@ class RoomMemberDetailsPresenterTest {
@Test
fun `present - UnblockUser needing confirmation displays confirmation dialog`() = runTest {
- val presenter = createRoomMemberDetailsPresenter()
+ val presenter = createRoomMemberDetailsPresenter(
+ room = aMatrixRoom(
+ getUpdatedMemberResult = { Result.failure(AN_EXCEPTION) },
+ userDisplayNameResult = { Result.success("Alice") },
+ userAvatarUrlResult = { Result.success("anAvatarUrl") },
+ ),
+ )
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
@@ -255,7 +297,14 @@ class RoomMemberDetailsPresenterTest {
@Test
fun `present - start DM action complete scenario`() = runTest {
val startDMAction = FakeStartDMAction()
- val presenter = createRoomMemberDetailsPresenter(startDMAction = startDMAction)
+ val presenter = createRoomMemberDetailsPresenter(
+ room = aMatrixRoom(
+ getUpdatedMemberResult = { Result.success(aRoomMember(displayName = "Alice")) },
+ userDisplayNameResult = { Result.success("Alice") },
+ userAvatarUrlResult = { Result.success("anAvatarUrl") },
+ ),
+ startDMAction = startDMAction,
+ )
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
@@ -268,6 +317,7 @@ class RoomMemberDetailsPresenterTest {
startDMAction.givenExecuteResult(startDMFailureResult)
initialState.eventSink(UserProfileEvents.StartDM)
assertThat(awaitItem().startDmActionState).isInstanceOf(AsyncAction.Loading::class.java)
+ skipItems(2)
awaitItem().also { state ->
assertThat(state.startDmActionState).isEqualTo(startDMFailureResult)
state.eventSink(UserProfileEvents.ClearStartDMState)
@@ -292,8 +342,8 @@ class RoomMemberDetailsPresenterTest {
}
private fun createRoomMemberDetailsPresenter(
+ room: MatrixRoom,
client: MatrixClient = FakeMatrixClient(),
- room: MatrixRoom = aMatrixRoom(),
roomMemberId: UserId = UserId("@alice:server.org"),
startDMAction: StartDMAction = FakeStartDMAction()
): RoomMemberDetailsPresenter {
diff --git a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/members/moderation/DefaultRoomMembersModerationPresenterTest.kt b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/members/moderation/DefaultRoomMembersModerationPresenterTest.kt
index ae789f8958..5f704a23d4 100644
--- a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/members/moderation/DefaultRoomMembersModerationPresenterTest.kt
+++ b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/members/moderation/DefaultRoomMembersModerationPresenterTest.kt
@@ -54,29 +54,34 @@ class DefaultRoomMembersModerationPresenterTest {
@Test
fun `canDisplayModerationActions - when user can kick other users, FF is enabled and room is not a DM returns true`() = runTest {
- val room = FakeMatrixRoom(isDirect = false, activeMemberCount = 10).apply {
- givenCanKickResult(Result.success(true))
- }
+ val room = FakeMatrixRoom(
+ isDirect = false,
+ activeMemberCount = 10,
+ canKickResult = { Result.success(true) },
+ canBanResult = { Result.success(true) },
+ )
val presenter = createDefaultRoomMembersModerationPresenter(matrixRoom = room)
assertThat(presenter.canDisplayModerationActions()).isTrue()
}
@Test
fun `canDisplayModerationActions - when user can ban other users, FF is enabled and room is not a DM returns true`() = runTest {
- val room = FakeMatrixRoom(isDirect = false, activeMemberCount = 10).apply {
- givenCanBanResult(Result.success(true))
- }
+ val room = FakeMatrixRoom(
+ isDirect = false,
+ activeMemberCount = 10,
+ canBanResult = { Result.success(true) },
+ )
val presenter = createDefaultRoomMembersModerationPresenter(matrixRoom = room)
assertThat(presenter.canDisplayModerationActions()).isTrue()
}
@Test
fun `present - SelectRoomMember when the current user has permissions displays member actions`() = runTest {
- val room = FakeMatrixRoom().apply {
- givenCanKickResult(Result.success(true))
- givenCanBanResult(Result.success(true))
- givenUserRoleResult(Result.success(RoomMember.Role.ADMIN))
- }
+ val room = FakeMatrixRoom(
+ canKickResult = { Result.success(true) },
+ canBanResult = { Result.success(true) },
+ userRoleResult = { Result.success(RoomMember.Role.ADMIN) },
+ )
val selectedMember = aVictor()
val presenter = createDefaultRoomMembersModerationPresenter(matrixRoom = room)
moleculeFlow(RecompositionMode.Immediate) {
@@ -98,11 +103,12 @@ class DefaultRoomMembersModerationPresenterTest {
@Test
fun `present - SelectRoomMember displays only view profile if selected member has same power level as the current user`() = runTest {
- val room = FakeMatrixRoom(sessionId = A_USER_ID).apply {
- givenCanKickResult(Result.success(true))
- givenCanBanResult(Result.success(true))
- givenUserRoleResult(Result.success(RoomMember.Role.ADMIN))
- }
+ val room = FakeMatrixRoom(
+ sessionId = A_USER_ID,
+ canKickResult = { Result.success(true) },
+ canBanResult = { Result.success(true) },
+ userRoleResult = { Result.success(RoomMember.Role.ADMIN) },
+ )
val selectedMember = aRoomMember(A_USER_ID_2, powerLevel = 100L)
val presenter = createDefaultRoomMembersModerationPresenter(matrixRoom = room)
moleculeFlow(RecompositionMode.Immediate) {
@@ -123,11 +129,11 @@ class DefaultRoomMembersModerationPresenterTest {
@Test
fun `present - SelectRoomMember displays an unban confirmation dialog when the member is banned`() = runTest {
val selectedMember = aRoomMember(A_USER_ID_2, membership = RoomMembershipState.BAN)
- val room = FakeMatrixRoom().apply {
- givenCanKickResult(Result.success(true))
- givenCanBanResult(Result.success(true))
- givenUserRoleResult(Result.success(RoomMember.Role.ADMIN))
- }
+ val room = FakeMatrixRoom(
+ canKickResult = { Result.success(true) },
+ canBanResult = { Result.success(true) },
+ userRoleResult = { Result.success(RoomMember.Role.ADMIN) },
+ )
val presenter = createDefaultRoomMembersModerationPresenter(matrixRoom = room)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
@@ -144,11 +150,12 @@ class DefaultRoomMembersModerationPresenterTest {
@Test
fun `present - Kick removes the user`() = runTest {
val analyticsService = FakeAnalyticsService()
- val room = FakeMatrixRoom().apply {
- givenCanKickResult(Result.success(true))
- givenCanBanResult(Result.success(true))
- givenUserRoleResult(Result.success(RoomMember.Role.ADMIN))
- }
+ val room = FakeMatrixRoom(
+ canKickResult = { Result.success(true) },
+ canBanResult = { Result.success(true) },
+ userRoleResult = { Result.success(RoomMember.Role.ADMIN) },
+ kickUserResult = { _, _ -> Result.success(Unit) },
+ )
val selectedMember = aVictor()
val presenter = createDefaultRoomMembersModerationPresenter(matrixRoom = room, analyticsService = analyticsService)
moleculeFlow(RecompositionMode.Immediate) {
@@ -171,11 +178,12 @@ class DefaultRoomMembersModerationPresenterTest {
@Test
fun `present - BanUser requires confirmation and then bans the user`() = runTest {
val analyticsService = FakeAnalyticsService()
- val room = FakeMatrixRoom().apply {
- givenCanKickResult(Result.success(true))
- givenCanBanResult(Result.success(true))
- givenUserRoleResult(Result.success(RoomMember.Role.ADMIN))
- }
+ val room = FakeMatrixRoom(
+ canKickResult = { Result.success(true) },
+ canBanResult = { Result.success(true) },
+ userRoleResult = { Result.success(RoomMember.Role.ADMIN) },
+ banUserResult = { _, _ -> Result.success(Unit) },
+ )
val selectedMember = aVictor()
val presenter = createDefaultRoomMembersModerationPresenter(matrixRoom = room, analyticsService = analyticsService)
moleculeFlow(RecompositionMode.Immediate) {
@@ -204,11 +212,13 @@ class DefaultRoomMembersModerationPresenterTest {
fun `present - UnbanUser requires confirmation and then unbans the user`() = runTest {
val analyticsService = FakeAnalyticsService()
val selectedMember = aRoomMember(A_USER_ID_2, membership = RoomMembershipState.BAN)
- val room = FakeMatrixRoom().apply {
- givenCanKickResult(Result.success(true))
- givenCanBanResult(Result.success(true))
+ val room = FakeMatrixRoom(
+ canKickResult = { Result.success(true) },
+ canBanResult = { Result.success(true) },
+ userRoleResult = { Result.success(RoomMember.Role.ADMIN) },
+ unBanUserResult = { _, _ -> Result.success(Unit) },
+ ).apply {
givenRoomMembersState(MatrixRoomMembersState.Ready(persistentListOf(selectedMember)))
- givenUserRoleResult(Result.success(RoomMember.Role.ADMIN))
}
val presenter = createDefaultRoomMembersModerationPresenter(matrixRoom = room, analyticsService = analyticsService)
moleculeFlow(RecompositionMode.Immediate) {
@@ -231,10 +241,11 @@ class DefaultRoomMembersModerationPresenterTest {
@Test
fun `present - Reset removes the selected user and actions`() = runTest {
- val room = FakeMatrixRoom().apply {
- givenCanKickResult(Result.success(true))
- givenCanBanResult(Result.success(true))
- }
+ val room = FakeMatrixRoom(
+ canKickResult = { Result.success(true) },
+ canBanResult = { Result.success(true) },
+ userRoleResult = { Result.success(RoomMember.Role.USER) },
+ )
val presenter = createDefaultRoomMembersModerationPresenter(matrixRoom = room)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
@@ -251,13 +262,14 @@ class DefaultRoomMembersModerationPresenterTest {
@Test
fun `present - Reset resets any async actions`() = runTest {
- val room = FakeMatrixRoom().apply {
- givenCanKickResult(Result.success(true))
- givenCanBanResult(Result.success(true))
- givenKickUserResult(Result.failure(Throwable("Eek")))
- givenBanUserResult(Result.failure(Throwable("Eek")))
- givenUnbanUserResult(Result.failure(Throwable("Eek")))
- }
+ val room = FakeMatrixRoom(
+ canKickResult = { Result.success(true) },
+ canBanResult = { Result.success(true) },
+ kickUserResult = { _, _ -> Result.failure(Throwable("Eek")) },
+ banUserResult = { _, _ -> Result.failure(Throwable("Eek")) },
+ unBanUserResult = { _, _ -> Result.failure(Throwable("Eek")) },
+ userRoleResult = { Result.success(RoomMember.Role.USER) },
+ )
val presenter = createDefaultRoomMembersModerationPresenter(matrixRoom = room)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
diff --git a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/rolesandpermissions/RolesAndPermissionPresenterTest.kt b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/rolesandpermissions/RolesAndPermissionPresenterTest.kt
index 35353f0d1a..c2eb07002c 100644
--- a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/rolesandpermissions/RolesAndPermissionPresenterTest.kt
+++ b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/rolesandpermissions/RolesAndPermissionPresenterTest.kt
@@ -27,6 +27,7 @@ import io.element.android.libraries.architecture.AsyncAction
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
import io.element.android.libraries.matrix.api.room.RoomMember
import io.element.android.libraries.matrix.test.room.FakeMatrixRoom
+import io.element.android.libraries.matrix.test.room.defaultRoomPowerLevels
import io.element.android.services.analytics.test.FakeAnalyticsService
import io.element.android.tests.testutils.testCoroutineDispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -67,7 +68,12 @@ class RolesAndPermissionPresenterTest {
@OptIn(ExperimentalCoroutinesApi::class)
@Test
fun `present - DemoteSelfTo changes own role to the specified one`() = runTest(StandardTestDispatcher()) {
- val presenter = createRolesAndPermissionsPresenter(dispatchers = testCoroutineDispatchers())
+ val presenter = createRolesAndPermissionsPresenter(
+ dispatchers = testCoroutineDispatchers(),
+ room = FakeMatrixRoom(
+ updateUserRoleResult = { Result.success(Unit) }
+ ),
+ )
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
@@ -85,9 +91,9 @@ class RolesAndPermissionPresenterTest {
@OptIn(ExperimentalCoroutinesApi::class)
@Test
fun `present - DemoteSelfTo can handle failures and clean them`() = runTest(StandardTestDispatcher()) {
- val room = FakeMatrixRoom().apply {
- givenUpdateUserRoleResult(Result.failure(Exception("Failed to update role")))
- }
+ val room = FakeMatrixRoom(
+ updateUserRoleResult = { Result.failure(Exception("Failed to update role")) }
+ )
val presenter = createRolesAndPermissionsPresenter(room = room, dispatchers = testCoroutineDispatchers())
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
@@ -123,7 +129,12 @@ class RolesAndPermissionPresenterTest {
@Test
fun `present - ResetPermissions needs confirmation, then resets permissions`() = runTest {
val analyticsService = FakeAnalyticsService()
- val presenter = createRolesAndPermissionsPresenter(analyticsService = analyticsService)
+ val presenter = createRolesAndPermissionsPresenter(
+ analyticsService = analyticsService,
+ room = FakeMatrixRoom(
+ resetPowerLevelsResult = { Result.success(defaultRoomPowerLevels()) }
+ )
+ )
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
diff --git a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/rolesandpermissions/changeroles/ChangeRolesPresenterTest.kt b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/rolesandpermissions/changeroles/ChangeRolesPresenterTest.kt
index d4155d9924..71223c8ff3 100644
--- a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/rolesandpermissions/changeroles/ChangeRolesPresenterTest.kt
+++ b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/rolesandpermissions/changeroles/ChangeRolesPresenterTest.kt
@@ -275,7 +275,10 @@ class ChangeRolesPresenterTest {
@Test
fun `present - Save will display a confirmation when adding admins`() = runTest {
- val room = FakeMatrixRoom().apply {
+ val room = FakeMatrixRoom(
+ updateUserRoleResult = { Result.success(Unit) },
+ updateMembersResult = { Result.success(Unit) },
+ ).apply {
givenRoomMembersState(MatrixRoomMembersState.Ready(aRoomMemberList()))
givenRoomInfo(aRoomInfo(userPowerLevels = persistentMapOf(A_USER_ID to 100)))
}
@@ -325,7 +328,10 @@ class ChangeRolesPresenterTest {
@Test
fun `present - Save will just save the data for moderators`() = runTest {
val analyticsService = FakeAnalyticsService()
- val room = FakeMatrixRoom().apply {
+ val room = FakeMatrixRoom(
+ updateUserRoleResult = { Result.success(Unit) },
+ updateMembersResult = { Result.success(Unit) },
+ ).apply {
givenRoomMembersState(MatrixRoomMembersState.Ready(aRoomMemberList()))
givenRoomInfo(aRoomInfo(userPowerLevels = persistentMapOf(A_USER_ID to 50)))
}
@@ -351,10 +357,11 @@ class ChangeRolesPresenterTest {
@Test
fun `present - Save can handle failures and ClearError clears them`() = runTest {
- val room = FakeMatrixRoom().apply {
+ val room = FakeMatrixRoom(
+ updateUserRoleResult = { Result.failure(IllegalStateException("Failed")) }
+ ).apply {
givenRoomMembersState(MatrixRoomMembersState.Ready(aRoomMemberList()))
givenRoomInfo(aRoomInfo(userPowerLevels = persistentMapOf(A_USER_ID to 50)))
- givenUpdateUserRoleResult(Result.failure(IllegalStateException("Failed")))
}
val presenter = createChangeRolesPresenter(role = RoomMember.Role.MODERATOR, room = room)
moleculeFlow(RecompositionMode.Immediate) {
diff --git a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/rolesandpermissions/permissions/ChangeRoomPermissionsPresenterTest.kt b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/rolesandpermissions/permissions/ChangeRoomPermissionsPresenterTest.kt
index c37d458d67..5d2ae18783 100644
--- a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/rolesandpermissions/permissions/ChangeRoomPermissionsPresenterTest.kt
+++ b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/rolesandpermissions/permissions/ChangeRoomPermissionsPresenterTest.kt
@@ -164,7 +164,13 @@ class ChangeRoomPermissionsPresenterTest {
@Test
fun `present - Save updates the current permissions and resets hasChanges`() = runTest {
val analyticsService = FakeAnalyticsService()
- val presenter = createChangeRoomPermissionsPresenter(analyticsService = analyticsService)
+ val presenter = createChangeRoomPermissionsPresenter(
+ analyticsService = analyticsService,
+ room = FakeMatrixRoom(
+ updatePowerLevelsResult = { Result.success(Unit) },
+ powerLevelsResult = { Result.success(defaultPermissions()) }
+ ),
+ )
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
@@ -208,9 +214,9 @@ class ChangeRoomPermissionsPresenterTest {
@Test
fun `present - Save will fail if there are not current permissions`() = runTest {
- val room = FakeMatrixRoom().apply {
- givenPowerLevelsResult(Result.failure(IllegalStateException("Failed to load power levels")))
- }
+ val room = FakeMatrixRoom(
+ powerLevelsResult = { Result.failure(IllegalStateException("Failed to load power levels")) }
+ )
val presenter = createChangeRoomPermissionsPresenter(room = room)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
@@ -225,9 +231,10 @@ class ChangeRoomPermissionsPresenterTest {
@Test
fun `present - Save can handle failures and they can be cleared`() = runTest {
- val room = FakeMatrixRoom().apply {
- givenUpdatePowerLevelsResult(Result.failure(IllegalStateException("Failed to update power levels")))
- }
+ val room = FakeMatrixRoom(
+ powerLevelsResult = { Result.success(defaultPermissions()) },
+ updatePowerLevelsResult = { Result.failure(IllegalStateException("Failed to update power levels")) },
+ )
val presenter = createChangeRoomPermissionsPresenter(room = room)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
@@ -292,7 +299,9 @@ class ChangeRoomPermissionsPresenterTest {
private fun createChangeRoomPermissionsPresenter(
section: ChangeRoomPermissionsSection = ChangeRoomPermissionsSection.RoomDetails,
- room: FakeMatrixRoom = FakeMatrixRoom(),
+ room: FakeMatrixRoom = FakeMatrixRoom(
+ powerLevelsResult = { Result.success(defaultPermissions()) }
+ ),
analyticsService: FakeAnalyticsService = FakeAnalyticsService(),
) = ChangeRoomPermissionsPresenter(
section = section,
diff --git a/features/roomlist/impl/src/main/res/values-el/translations.xml b/features/roomlist/impl/src/main/res/values-el/translations.xml
index b3a4a8f04d..71aca711ad 100644
--- a/features/roomlist/impl/src/main/res/values-el/translations.xml
+++ b/features/roomlist/impl/src/main/res/values-el/translations.xml
@@ -25,12 +25,13 @@
"Μπορείς να καταργήσεις την επιλογή φίλτρων για να δεις τις άλλες συνομιλίες σου"
"Δεν έχεις συνομιλίες για αυτήν την επιλογή"
"Άτομα"
+ "Δεν έχεις ακόμα ΠΜ"
"Δωμάτια"
"Δεν είσαι ακόμα σε κανένα δωμάτιο"
"Μη αναγνωσμένα"
"Συγχαρητήρια!
Δεν έχεις μη αναγνωσμένα μηνύματα!"
- "Συζητήσεις"
+ "Συνομιλίες"
"Επισήμανση ως αναγνωσμένου"
"Επισήμανση ως μη αναγνωσμένου"
"Περιήγηση σε όλα τα δωμάτια"
diff --git a/features/roomlist/impl/src/main/res/values-ka/translations.xml b/features/roomlist/impl/src/main/res/values-ka/translations.xml
index de6358f443..dac714cd0a 100644
--- a/features/roomlist/impl/src/main/res/values-ka/translations.xml
+++ b/features/roomlist/impl/src/main/res/values-ka/translations.xml
@@ -1,5 +1,7 @@
+ "თქვენი ჩეთების სარეზერვო ასლი ამჟამად არ არის სინქრონიზებული. თქვენ უნდა შეიყვანოთ თქვენი აღდგენის გასაღები, რათა შეინარჩუნოთ წვდომა ჩეთების სარეზერვო ასლზე."
+ "შეიყვანეთ აღდგენის გასაღები"
"დარწმუნებული ხართ, რომ გსურთ, უარი თქვათ მოწვევაზე %1$s-ში?"
"მოწვევაზე უარის თქმა"
"დარწმუნებული ხართ, რომ გსურთ, უარი თქვათ ჩატზე %1$s-თან?"
diff --git a/features/roomlist/impl/src/main/res/values-pl/translations.xml b/features/roomlist/impl/src/main/res/values-pl/translations.xml
new file mode 100644
index 0000000000..0ae62297a1
--- /dev/null
+++ b/features/roomlist/impl/src/main/res/values-pl/translations.xml
@@ -0,0 +1,22 @@
+
+
+ "Twoja kopia zapasowa czatu jest obecnie niezsynchronizowana. Aby zachować dostęp do kopii zapasowej czatu, musisz potwierdzić klucz odzyskiwania."
+ "Potwierdź klucz odzyskiwania"
+ "Upewnij się, że nie pominiesz żadnego połączenia. Zmień swoje ustawienia i zezwól na powiadomienia na blokadzie ekranu."
+ "Popraw jakość swoich rozmów"
+ "Czy na pewno chcesz odrzucić zaproszenie do dołączenia do %1$s?"
+ "Odrzuć zaproszenie"
+ "Czy na pewno chcesz odrzucić rozmowę prywatną z %1$s?"
+ "Odrzuć czat"
+ "Brak zaproszeń"
+ "%1$s (%2$s) zaprosił Cię"
+ "Jest to jednorazowy proces, dziękujemy za czekanie."
+ "Konfigurowanie Twojego konta."
+ "Utwórz nową rozmowę lub pokój"
+ "Wyślij komuś wiadomość, aby rozpocząć."
+ "Brak czatów."
+ "Osoby"
+ "Wszystkie czaty"
+ "Wygląda na to, że używasz nowego urządzenia. Zweryfikuj się innym urządzeniem, aby uzyskać dostęp do zaszyfrowanych wiadomości."
+ "Potwierdź, że to Ty"
+
diff --git a/features/roomlist/impl/src/main/res/values-pt-rBR/translations.xml b/features/roomlist/impl/src/main/res/values-pt-rBR/translations.xml
new file mode 100644
index 0000000000..6765199a70
--- /dev/null
+++ b/features/roomlist/impl/src/main/res/values-pt-rBR/translations.xml
@@ -0,0 +1,19 @@
+
+
+ "Insira sua chave de recuperação"
+ "Tem certeza de que deseja recusar o convite para ingressar em %1$s?"
+ "Recusar convite"
+ "Tem certeza de que deseja recusar esse chat privado com %1$s?"
+ "Recusar chat"
+ "Sem convites"
+ "%1$s(%2$s) convidou você"
+ "Este é um processo único, obrigado por esperar."
+ "Configurando sua conta."
+ "Criar uma nova conversa ou sala"
+ "Comece enviando uma mensagem para alguém."
+ "Ainda não há conversas."
+ "Pessoas"
+ "Conversas"
+ "Parece que você está usando um novo dispositivo. Verifique com outro dispositivo para acessar suas mensagens criptografadas."
+ "Verifique se é você"
+
diff --git a/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/RoomListPresenterTest.kt b/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/RoomListPresenterTest.kt
index b48a603a85..7a9a8b2744 100644
--- a/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/RoomListPresenterTest.kt
+++ b/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/RoomListPresenterTest.kt
@@ -441,7 +441,10 @@ class RoomListPresenterTest {
@Test
fun `present - when set is favorite event is emitted, then the action is called`() = runTest {
val scope = CoroutineScope(coroutineContext + SupervisorJob())
- val room = FakeMatrixRoom()
+ val setIsFavoriteResult = lambdaRecorder { _: Boolean -> Result.success(Unit) }
+ val room = FakeMatrixRoom(
+ setIsFavoriteResult = setIsFavoriteResult
+ )
val analyticsService = FakeAnalyticsService()
val client = FakeMatrixClient().apply {
givenGetRoomResult(A_ROOM_ID, room)
@@ -452,9 +455,13 @@ class RoomListPresenterTest {
}.test {
val initialState = awaitItem()
initialState.eventSink(RoomListEvents.SetRoomIsFavorite(A_ROOM_ID, true))
- assertThat(room.setIsFavoriteCalls).isEqualTo(listOf(true))
+ setIsFavoriteResult.assertions().isCalledOnce().with(value(true))
initialState.eventSink(RoomListEvents.SetRoomIsFavorite(A_ROOM_ID, false))
- assertThat(room.setIsFavoriteCalls).isEqualTo(listOf(true, false))
+ setIsFavoriteResult.assertions().isCalledExactly(2)
+ .withSequence(
+ listOf(value(true)),
+ listOf(value(false)),
+ )
assertThat(analyticsService.capturedEvents).containsExactly(
Interaction(name = Interaction.Name.MobileRoomListRoomContextMenuFavouriteToggle),
Interaction(name = Interaction.Name.MobileRoomListRoomContextMenuFavouriteToggle)
diff --git a/features/securebackup/impl/src/main/res/values-ka/translations.xml b/features/securebackup/impl/src/main/res/values-ka/translations.xml
index e31153859c..6c1ed02eb5 100644
--- a/features/securebackup/impl/src/main/res/values-ka/translations.xml
+++ b/features/securebackup/impl/src/main/res/values-ka/translations.xml
@@ -22,10 +22,14 @@
"აღდგენის გასაღები შეიცვალა"
"გსურთ აღდგენის გასაღების შეცვლა?"
"დარწმუნდით, რომ ვერავინ ხედავს ამ ეკრანს!"
+ "გთხოვთ, სცადოთ ხელახლა, რათა თქვენი ჩეთის სარეზერვო ასლაზე წვდომა დაადასტუროთ"
+ "აღდგენის არასწორი გასაღები"
"თუ თქვენ გაქვთ უსაფრთხოების გასაღები ან უსაფრთხოების ფრაზა, ეს ასევე იმუშავებს."
"შეყვანა"
"აღდგენის გასაღები დადასტურებულია"
"შეიყვანეთ თქვენი აღდგენის გასაღები"
+ "დაკოპირებულია აღდგენის გასაღები"
+ "გენერირება…"
"აღდგენის გასაღების შენახვა"
"ჩაწერეთ თქვენი აღდგენის გასაღები სადმე უსაფრთხო ადგილას ან შეინახეთ პაროლის მენეჯერში."
"აღდგენის გასაღების დასაკოპირებლად, დააწკაპუნეთ"
diff --git a/features/securebackup/impl/src/main/res/values-pl/translations.xml b/features/securebackup/impl/src/main/res/values-pl/translations.xml
new file mode 100644
index 0000000000..ca1589c5ac
--- /dev/null
+++ b/features/securebackup/impl/src/main/res/values-pl/translations.xml
@@ -0,0 +1,44 @@
+
+
+ "Wyłącz backup"
+ "Włącz backup"
+ "Backup zapewnia, że nie stracisz swojej historii wiadomości. %1$s"
+ "Backup"
+ "Zmień klucz przywracania"
+ "Wprowadź klucz przywracania"
+ "Backup czatu nie jest zsynchronizowany."
+ "Skonfiguruj przywracanie"
+ "Uzyskaj dostęp do swoich wiadomości szyfrowanych, jeśli utracisz wszystkie swoje urządzenia lub zostaniesz wylogowany z %1$s."
+ "Wyłącz"
+ "Utracisz dostęp do wiadomości szyfrowanych, jeśli zostaniesz wylogowany ze wszystkich urządzeń."
+ "Czy na pewno chcesz wyłączyć backup?"
+ "Wyłączenie backupu spowoduje usunięcie kopii klucza szyfrowania i wyłączenie innych funkcji bezpieczeństwa. W takim przypadku będziesz:"
+ "Posiadał historii wiadomości szyfrowanych na nowych urządzeniach"
+ "Utracisz dostęp do wiadomości szyfrowanych, jeśli zostaniesz wszędzie wylogowany z %1$s"
+ "Czy na pewno chcesz wyłączyć backup?"
+ "Uzyskaj nowy klucz przywracania, jeśli straciłeś dostęp do obecnego. Po zmianie klucza przywracania stary nie będzie już działał."
+ "Generuj nowy klucz przywracania"
+ "Upewnij się, że klucz przywracania będzie trzymany w bezpiecznym miejscu"
+ "Zmieniono klucz przywracania"
+ "Zmienić klucz przywracania?"
+ "Upewnij się, że nikt nie widzi tego ekranu!"
+ "Spróbuj ponownie, aby potwierdzić dostęp do backupu czatu."
+ "Nieprawidłowy klucz przywracania"
+ "To też zadziała, jeśli posiadasz klucz lub frazę bezpieczeństwa."
+ "Wprowadź…"
+ "Potwierdzono klucz przywracania"
+ "Wprowadź klucz przywracania"
+ "Skopiowano klucz przywracania"
+ "Generuję…"
+ "Zapisz klucz przywracania"
+ "Zapisz klucz przywracania w bezpiecznym miejscu lub zapisz go w menedżerze haseł."
+ "Stuknij, by skopiować klucz przywracania"
+ "Zapisz klucz przywracania"
+ "Po tym kroku nie będziesz mieć dostępu do nowego klucza przywracania."
+ "Czy zapisałeś swój klucz przywracania?"
+ "Backup czatu jest chroniony przez klucz przywracania. Jeśli potrzebujesz utworzyć nowy klucz, możesz to zrobić wybierając `Zmień klucz przywracania`."
+ "Wygeneruj klucz przywracania"
+ "Upewnij się, że klucz przywracania możesz przechowywać w bezpiecznym miejscu"
+ "Skonfigurowano przywracanie pomyślnie"
+ "Skonfiguruj przywracanie"
+
diff --git a/features/securebackup/impl/src/main/res/values-pt-rBR/translations.xml b/features/securebackup/impl/src/main/res/values-pt-rBR/translations.xml
new file mode 100644
index 0000000000..786de3f4da
--- /dev/null
+++ b/features/securebackup/impl/src/main/res/values-pt-rBR/translations.xml
@@ -0,0 +1,40 @@
+
+
+ "Desativar o backup"
+ "Ativar o backup"
+ "O backup garante que você não perca seu histórico de mensagens. %1$s."
+ "Backup"
+ "Mudar chave de recuperação"
+ "Insira a chave de recuperação"
+ "Seu backup das conversas está atualmente fora de sincronia."
+ "Configurar a recuperação"
+ "Tenha acesso às suas mensagens criptografadas se você perder todos os seus dispositivos ou for desconectado do %1$s em qualquer lugar."
+ "Desligar"
+ "Você perderá suas mensagens criptografadas se estiver desconectado de todos os dispositivos."
+ "Tem certeza de que deseja desativar o backup?"
+ "Desativar o backup removerá o backup da chave de criptografia atual e desativará outros recursos de segurança. Neste caso, você irá:"
+ "Não ter histórico de mensagens criptografadas em novos dispositivos"
+ "Perder o acesso às suas mensagens criptografadas se você estiver desconectado %1$s em todos os lugares"
+ "Tem certeza de que deseja desativar o backup?"
+ "Obtenha uma nova chave de recuperação caso tenha perdido a existente. Depois de alterar sua chave de recuperação, a antiga não funcionará mais."
+ "Gere uma nova chave de recuperação"
+ "Certifique-se de que você pode armazenar sua chave de recuperação em algum lugar seguro"
+ "Chave de recuperação alterada"
+ "Alterar chave de recuperação?"
+ "Certifique-se de que ninguém possa ver essa tela!"
+ "Se você tiver uma chave de segurança ou frase de segurança, isso também funcionará."
+ "Inserir…"
+ "Chave de recuperação confirmada"
+ "Insira sua chave de recuperação"
+ "Salvar chave de recuperação"
+ "Anote sua chave de recuperação em algum lugar seguro ou salve-a em um gerenciador de senhas."
+ "Toque para copiar a chave de recuperação"
+ "Salve sua chave de recuperação"
+ "Você não poderá acessar sua nova chave de recuperação após essa etapa."
+ "Você salvou sua chave de recuperação?"
+ "Seu backup das conversas é protegido por uma chave de recuperação. Se precisar de uma nova chave de recuperação após a configuração, você pode recriá-la selecionando “Alterar chave de recuperação”."
+ "Gere sua chave de recuperação"
+ "Certifique-se de que você pode armazenar sua chave de recuperação em algum lugar seguro"
+ "Configuração de recuperação bem-sucedida"
+ "Configurar a recuperação"
+
diff --git a/features/share/impl/src/test/kotlin/io/element/android/features/share/impl/SharePresenterTest.kt b/features/share/impl/src/test/kotlin/io/element/android/features/share/impl/SharePresenterTest.kt
index 42c053bafb..8c91ea530c 100644
--- a/features/share/impl/src/test/kotlin/io/element/android/features/share/impl/SharePresenterTest.kt
+++ b/features/share/impl/src/test/kotlin/io/element/android/features/share/impl/SharePresenterTest.kt
@@ -28,6 +28,7 @@ import io.element.android.libraries.matrix.api.MatrixClient
import io.element.android.libraries.matrix.test.A_MESSAGE
import io.element.android.libraries.matrix.test.A_ROOM_ID
import io.element.android.libraries.matrix.test.FakeMatrixClient
+import io.element.android.libraries.matrix.test.media.FakeMediaUploadHandler
import io.element.android.libraries.matrix.test.room.FakeMatrixRoom
import io.element.android.libraries.mediaupload.api.MediaPreProcessor
import io.element.android.libraries.mediaupload.test.FakeMediaPreProcessor
@@ -92,7 +93,9 @@ class SharePresenterTest {
@Test
fun `present - send text ok`() = runTest {
- val matrixRoom = FakeMatrixRoom()
+ val matrixRoom = FakeMatrixRoom(
+ sendMessageResult = { _, _, _ -> Result.success(Unit) },
+ )
val matrixClient = FakeMatrixClient().apply {
givenGetRoomResult(A_ROOM_ID, matrixRoom)
}
@@ -117,7 +120,9 @@ class SharePresenterTest {
@Test
fun `present - send media ok`() = runTest {
- val matrixRoom = FakeMatrixRoom()
+ val matrixRoom = FakeMatrixRoom(
+ sendMediaResult = { Result.success(FakeMediaUploadHandler()) },
+ )
val matrixClient = FakeMatrixClient().apply {
givenGetRoomResult(A_ROOM_ID, matrixRoom)
}
diff --git a/features/signedout/impl/src/main/res/values-pl/translations.xml b/features/signedout/impl/src/main/res/values-pl/translations.xml
new file mode 100644
index 0000000000..9b83e7d692
--- /dev/null
+++ b/features/signedout/impl/src/main/res/values-pl/translations.xml
@@ -0,0 +1,8 @@
+
+
+ "Zmieniono hasło w innej sesji"
+ "Sesja została usunięta z innej sesji"
+ "Administrator serwera unieważnił Twój dostęp"
+ "Mogłeś zostać wylogowany z powodów wymienionych poniżej. Zaloguj się ponownie, aby dalej korzystać z %s."
+ "Zostałeś wylogowany"
+
diff --git a/features/signedout/impl/src/main/res/values-pt-rBR/translations.xml b/features/signedout/impl/src/main/res/values-pt-rBR/translations.xml
new file mode 100644
index 0000000000..3c31806492
--- /dev/null
+++ b/features/signedout/impl/src/main/res/values-pt-rBR/translations.xml
@@ -0,0 +1,8 @@
+
+
+ "Você alterou sua senha em outra sessão"
+ "Você excluiu essa sessão através de outra sessão"
+ "O administrador do seu servidor invalidou seu acesso"
+ "Você pode ter sido desconectado por um dos motivos listados abaixo. Faça login novamente para continuar usando %s."
+ "Você está desconectado"
+
diff --git a/features/userprofile/shared/src/main/res/values-pl/translations.xml b/features/userprofile/shared/src/main/res/values-pl/translations.xml
new file mode 100644
index 0000000000..8ee9d296ac
--- /dev/null
+++ b/features/userprofile/shared/src/main/res/values-pl/translations.xml
@@ -0,0 +1,10 @@
+
+
+ "Zablokuj"
+ "Zablokowani użytkownicy nie będą mogli wysyłać Ci wiadomości, a wszystkie ich wiadomości zostaną ukryte. Możesz odblokować ich w dowolnym momencie."
+ "Zablokuj użytkownika"
+ "Odblokuj"
+ "Będziesz mógł ponownie zobaczyć wszystkie wiadomości od tego użytkownika."
+ "Odblokuj użytkownika"
+ "Wystąpił błąd podczas próby rozpoczęcia czatu"
+
diff --git a/features/userprofile/shared/src/main/res/values-pt-rBR/translations.xml b/features/userprofile/shared/src/main/res/values-pt-rBR/translations.xml
new file mode 100644
index 0000000000..d5a26a77ca
--- /dev/null
+++ b/features/userprofile/shared/src/main/res/values-pt-rBR/translations.xml
@@ -0,0 +1,10 @@
+
+
+ "Bloquear"
+ "Usuários bloqueados não poderão enviar mensagens para você e todas as mensagens deles serão ocultadas. Você pode desbloqueá-los a qualquer momento."
+ "Bloquear usuário"
+ "Desbloquear"
+ "Você poderá ver todas as mensagens deles novamente."
+ "Desbloquear usuário"
+ "Ocorreu um erro ao tentar iniciar um chat"
+
diff --git a/features/verifysession/impl/src/main/res/values-ka/translations.xml b/features/verifysession/impl/src/main/res/values-ka/translations.xml
index fb1f3c6db4..afd1b03a56 100644
--- a/features/verifysession/impl/src/main/res/values-ka/translations.xml
+++ b/features/verifysession/impl/src/main/res/values-ka/translations.xml
@@ -3,12 +3,15 @@
"რაღაცა არასწორადაა. ან მოთხოვნის ვადაა ამოწურული, ან მოთხოვნა უარყოფილი იყო."
"დაადასტურეთ, რომ ქვემოთ მოყვანილი ემოჯიები შეესაბამება თქვენს სხვა სესიაზე ნაჩვენებს."
"შეადარეთ ემოჯიები"
+ "დაადასტურეთ, რომ ქვემოთ მოცემული ნომრები ემთხვევა თქვენს სხვა სესიაზე ნაჩვენები ნომრებს."
+ "შეადარეთ რიცხვები"
"თქვენი ახალი სესია დადასტურებულია. მას აქვს წვდომა დაშიფრულ შეტყობინებებზე და სხვა მომხმარებლები მას სანდოდ ხედავენ."
"დაამტკიცეთ, რომ ეს თქვენ ხართ, რათა მიიღოთ წვდომა თქვენი დაშიფრული შეტყობინებების ისტორიასთან."
"არსებული სესიის გახსნა"
"დადასტურების ხელახლა ცდა"
"მზად ვარ"
"ველოდებით დამთხვევას"
+ "შეადარეთ ემოციების უნიკალური ნაკრები."
"შეადარეთ უნიკალური ემოჯი, დარწმუნდით, რომ ისინი ერთი დ იმავე თანმიმდევრობით გამოჩნდნენ."
"ისინი არ ემთხვევიან ერთმანეთს"
"ისინი ემთხვევიან ერთმანეთს"
diff --git a/features/verifysession/impl/src/main/res/values-pl/translations.xml b/features/verifysession/impl/src/main/res/values-pl/translations.xml
new file mode 100644
index 0000000000..6f39abf905
--- /dev/null
+++ b/features/verifysession/impl/src/main/res/values-pl/translations.xml
@@ -0,0 +1,20 @@
+
+
+ "Coś tu nie gra. Albo upłynął limit czasu, albo żądanie zostało odrzucone."
+ "Upewnij się, że poniższe emotikony pasują do tych wyświetlanych na innej sesji."
+ "Porównaj emotki"
+ "Upewnij się, że liczby poniżej pasują do tych wyświetlanych na innej sesji."
+ "Porównaj liczby"
+ "Twoja nowa sesja jest teraz zweryfikowana. Ma ona dostęp do Twoich zaszyfrowanych wiadomości, a inni użytkownicy będą widzieć ją jako zaufaną."
+ "Udowodnij, że to ty, aby uzyskać dostęp do historii zaszyfrowanych wiadomości."
+ "Otwórz istniejącą sesję"
+ "Ponów weryfikację"
+ "Jestem gotowy(a)"
+ "Oczekiwanie na dopasowanie"
+ "Porównaj unikalny zestaw emoji."
+ "Porównaj unikalne emoji, upewniając się, że pojawiły się w tej samej kolejności."
+ "Nie pasują do siebie"
+ "Pasują do siebie"
+ "Zaakceptuj prośbę o rozpoczęcie procesu weryfikacji w innej sesji, aby kontynuować."
+ "Oczekiwanie na zaakceptowanie żądania"
+
diff --git a/features/verifysession/impl/src/main/res/values-pt-rBR/translations.xml b/features/verifysession/impl/src/main/res/values-pt-rBR/translations.xml
new file mode 100644
index 0000000000..9d4658be82
--- /dev/null
+++ b/features/verifysession/impl/src/main/res/values-pt-rBR/translations.xml
@@ -0,0 +1,17 @@
+
+
+ "Algo não parece certo. Ou a solicitação atingiu o tempo limite ou a solicitação foi negada."
+ "Confirme se os emojis abaixo correspondem aos mostrados em sua outra sessão."
+ "Compare os emojis"
+ "Sua nova sessão está agora verificada. Ela tem acesso às suas mensagens criptografadas e outros usuários a verão como confiável."
+ "Prove que é você para acessar seu histórico de mensagens criptografadas."
+ "Abrir uma sessão existente"
+ "Repetir verificação"
+ "Estou pronto"
+ "Esperando para combinar"
+ "Compare os emojis únicos, garantindo que apareçam na mesma ordem."
+ "Eles não combinam"
+ "Eles combinam"
+ "Aceite a solicitação para iniciar o processo de verificação em sua outra sessão para continuar."
+ "Aguardando para aceitar a solicitação"
+
diff --git a/gradle.properties b/gradle.properties
index 9808f9eda1..b1f6468108 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -49,7 +49,7 @@ signing.element.nightly.keyPassword=Secret
# Customise the Lint version to use a more recent version than the one bundled with AGP
# https://googlesamples.github.io/android-custom-lint-rules/usage/newer-lint.md.html
-android.experimental.lint.version=8.5.0-alpha07
+android.experimental.lint.version=8.7.0-alpha01
# Enable test fixture for all modules by default
android.experimental.enableTestFixtures=true
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index 97f6585b34..0609a17dc2 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -19,7 +19,7 @@ datastore = "1.0.0"
constraintlayout = "2.1.4"
constraintlayout_compose = "1.0.1"
lifecycle = "2.7.0"
-activity = "1.9.0"
+activity = "1.9.1"
media3 = "1.3.1"
camera = "1.3.4"
@@ -163,7 +163,7 @@ jsoup = "org.jsoup:jsoup:1.18.1"
appyx_core = { module = "com.bumble.appyx:core", version.ref = "appyx" }
molecule-runtime = "app.cash.molecule:molecule-runtime:2.0.0"
timber = "com.jakewharton.timber:timber:5.0.1"
-matrix_sdk = "org.matrix.rustcomponents:sdk-android:0.2.33"
+matrix_sdk = "org.matrix.rustcomponents:sdk-android:0.2.34"
matrix_richtexteditor = { module = "io.element.android:wysiwyg", version.ref = "wysiwyg" }
matrix_richtexteditor_compose = { module = "io.element.android:wysiwyg-compose", version.ref = "wysiwyg" }
sqldelight-driver-android = { module = "app.cash.sqldelight:android-driver", version.ref = "sqldelight" }
@@ -177,7 +177,7 @@ vanniktech_blurhash = "com.vanniktech:blurhash:0.3.0"
telephoto_zoomableimage = { module = "me.saket.telephoto:zoomable-image-coil", version.ref = "telephoto" }
telephoto_flick = { module = "me.saket.telephoto:flick-android", version.ref = "telephoto" }
statemachine = "com.freeletics.flowredux:compose:1.2.2"
-maplibre = "org.maplibre.gl:android-sdk:11.0.1"
+maplibre = "org.maplibre.gl:android-sdk:11.1.0"
maplibre_ktx = "org.maplibre.gl:android-sdk-ktx-v7:3.0.0"
maplibre_annotation = "org.maplibre.gl:android-plugin-annotation-v9:3.0.0"
opusencoder = "io.element.android:opusencoder:1.1.0"
diff --git a/libraries/androidutils/src/main/res/values-pl/translations.xml b/libraries/androidutils/src/main/res/values-pl/translations.xml
new file mode 100644
index 0000000000..6302f19472
--- /dev/null
+++ b/libraries/androidutils/src/main/res/values-pl/translations.xml
@@ -0,0 +1,4 @@
+
+
+ "Nie znaleziono kompatybilnej aplikacji do obsługi tej akcji."
+
diff --git a/libraries/androidutils/src/main/res/values-pt-rBR/translations.xml b/libraries/androidutils/src/main/res/values-pt-rBR/translations.xml
new file mode 100644
index 0000000000..cde5b743f6
--- /dev/null
+++ b/libraries/androidutils/src/main/res/values-pt-rBR/translations.xml
@@ -0,0 +1,4 @@
+
+
+ "Nenhum aplicativo compatível foi encontrado para lidar com essa ação."
+
diff --git a/libraries/eventformatter/impl/src/main/res/values-pl/translations.xml b/libraries/eventformatter/impl/src/main/res/values-pl/translations.xml
new file mode 100644
index 0000000000..0c3bddc3c1
--- /dev/null
+++ b/libraries/eventformatter/impl/src/main/res/values-pl/translations.xml
@@ -0,0 +1,59 @@
+
+
+ "(zdjęcie profilowe też zostało zmienione)"
+ "%1$s zmienił swoje zdjęcie profilowe"
+ "Zmieniłeś swoje zdjęcie profilowe"
+ "%1$s zmienił swoją wyświetlaną nazwę z %2$s na %3$s"
+ "Zmieniłeś swoją wyświetlaną nazwę z %1$s na %2$s"
+ "%1$s usunął swoją wyświetlaną nazwę (byo to %2$s)"
+ "Usunąłeś swoją wyświetlaną nazwę (było to %1$s)"
+ "%1$s ustawił swoją wyświetlaną nazwę na %2$s"
+ "Ustawiłeś swoją wyświetlaną nazwę na %1$s"
+ "%1$s zmienił zdjęcie profilowe pokoju"
+ "Zmieniłeś zdjęcie profilowe pokoju"
+ "%1$s usunął zdjęcie profilowe pokoju"
+ "Usunąłeś zdjęcie profilowe pokoju"
+ "%1$s zbanował %2$s"
+ "Zbanowałeś %1$s"
+ "%1$s stworzył pokój"
+ "Stworzyłeś pokój"
+ "%1$s zaprosił %2$s"
+ "%1$s zaakceptował zaproszenie"
+ "Zaakceptowałeś zaproszenie"
+ "Zaprosiłeś %1$s"
+ "%1$s zaprosił Cię"
+ "%1$s dołączył do pokoju"
+ "Dołączyłeś(aś) do pokoju"
+ "%1$s prosi o możliwość dołączenia"
+ "%1$s zezwolił %2$s na dołączenie"
+ "Zezwoliłeś %1$s na dołączenie"
+ "Poprosiłeś o możliwość dołączenia"
+ "%1$s odrzucił prośbę %2$s o dołączenie"
+ "Odrzuciłeś prośbę %1$s o dołączenie"
+ "%1$s odrzucił Twoją prośbę o dołączenie"
+ "%1$s nie jest już zainteresowany dołączeniem"
+ "Anulowałeś prośbę o dołączenie"
+ "%1$s opuścił pokój"
+ "Opuściłeś pokój"
+ "%1$s zmienił nazwę pokoju na: %2$s"
+ "Zmieniłeś nazwę pokoju na: %1$s"
+ "%1$s usunął nazwę pokoju"
+ "Usunąłeś nazwę pokoju"
+ "%1$s nie wprowadził żadnych zmian"
+ "Nie wprowadzono żadnych zmian"
+ "%1$s odrzucił zaproszenie"
+ "Odrzuciłeś(aś) zaproszenie"
+ "%1$s usunął %2$s"
+ "Usunąłeś %1$s"
+ "%1$s wysłał zaproszenie do %2$s, aby dołączył do pokoju"
+ "Wysłano zaproszenie do %1$s, aby dołączył do pokoju"
+ "%1$s cofnął zaproszenie dla %2$s do tego pokoju"
+ "Odwołano zaproszenie %1$s, aby dołączył do pokoju"
+ "%1$s zmienił temat na: %2$s"
+ "Zmieniłeś temat na: %1$s"
+ "%1$s usunął temat pokoju"
+ "Usunąłeś temat pokoju"
+ "%1$s odbanował %2$s"
+ "Odbanowałeś %1$s"
+ "%1$s dokonał nieznanej zmiany w swoim członkostwie"
+
diff --git a/libraries/eventformatter/impl/src/main/res/values-pt-rBR/translations.xml b/libraries/eventformatter/impl/src/main/res/values-pt-rBR/translations.xml
new file mode 100644
index 0000000000..8cb375f1b7
--- /dev/null
+++ b/libraries/eventformatter/impl/src/main/res/values-pt-rBR/translations.xml
@@ -0,0 +1,57 @@
+
+
+ "(o avatar também foi alterado)"
+ "%1$s mudou seu avatar"
+ "Você mudou seu avatar"
+ "%1$s mudou seu nome de exibição de %2$s para %3$s"
+ "Você alterou seu nome de exibição de %1$s para %2$s"
+ "%1$s removeu seu nome de exibição (era %2$s)"
+ "Você removeu seu nome de exibição (era %1$s)"
+ "%1$s definiu seu nome de exibição como %2$s"
+ "Você definiu seu nome de exibição como %1$s"
+ "%1$s mudou o avatar da sala"
+ "Você mudou o avatar da sala"
+ "%1$s removeu o avatar da sala"
+ "Você removeu o avatar da sala"
+ "%1$s baniu %2$s"
+ "Você baniu %1$s"
+ "%1$s criou a sala"
+ "Você criou a sala"
+ "%1$s convidou %2$s"
+ "%1$s aceitou o convite"
+ "Você aceitou o convite"
+ "Você convidou %1$s"
+ "%1$s convidou você"
+ "%1$s entrou na sala"
+ "Você entrou na sala"
+ "%1$s solicitou entrada"
+ "%1$s permitiu que o %2$s entrar"
+ "Você permitiu que o %1$s entrasse"
+ "Você solicitou entrada"
+ "%1$s rejeitou a solicitação de %2$s para entrar"
+ "Você rejeitou a solicitação de %1$s para entrar"
+ "%1$s rejeitou sua solicitação para entrar"
+ "%1$s não está mais interessado em entrar"
+ "Você cancelou seu pedido para entrar"
+ "%1$s saiu da sala"
+ "Você saiu da sala"
+ "%1$s mudou o nome da sala para: %2$s"
+ "Você mudou o nome da sala para: %1$s"
+ "%1$s removeu o nome da sala"
+ "Você removeu o nome da sala"
+ "%1$s rejeitou o convite"
+ "Você rejeitou o convite"
+ "%1$s removido %2$s"
+ "Você removeu %1$s"
+ "%1$s enviou um convite para %2$s para entrar na sala"
+ "Você enviou um convite para %1$s para entrar na sala"
+ "%1$s revogou o convite para %2$s para entrar na sala"
+ "Você revogou o convite para %1$s para entrar na sala"
+ "%1$s mudou o tópico para: %2$s"
+ "Você mudou o tópico para: %1$s"
+ "%1$s removeu o tópico da sala"
+ "Você removeu o tópico da sala"
+ "%1$s desbaniu %2$s"
+ "Você desbaniu %1$s"
+ "%1$s fez uma alteração desconhecida em sua associação"
+
diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/MatrixClient.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/MatrixClient.kt
index b3b47d4499..3209d49e03 100644
--- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/MatrixClient.kt
+++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/MatrixClient.kt
@@ -19,6 +19,7 @@ package io.element.android.libraries.matrix.api
import io.element.android.libraries.matrix.api.core.ProgressCallback
import io.element.android.libraries.matrix.api.core.RoomAlias
import io.element.android.libraries.matrix.api.core.RoomId
+import io.element.android.libraries.matrix.api.core.RoomIdOrAlias
import io.element.android.libraries.matrix.api.core.SessionId
import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.api.createroom.CreateRoomParameters
@@ -35,6 +36,7 @@ import io.element.android.libraries.matrix.api.room.alias.ResolvedRoomAlias
import io.element.android.libraries.matrix.api.room.preview.RoomPreview
import io.element.android.libraries.matrix.api.roomdirectory.RoomDirectoryService
import io.element.android.libraries.matrix.api.roomlist.RoomListService
+import io.element.android.libraries.matrix.api.roomlist.RoomSummary
import io.element.android.libraries.matrix.api.sync.SyncService
import io.element.android.libraries.matrix.api.user.MatrixSearchUserResults
import io.element.android.libraries.matrix.api.user.MatrixUser
@@ -65,8 +67,8 @@ interface MatrixClient : Closeable {
suspend fun setDisplayName(displayName: String): Result
suspend fun uploadAvatar(mimeType: String, data: ByteArray): Result
suspend fun removeAvatar(): Result
- suspend fun joinRoom(roomId: RoomId): Result
- suspend fun joinRoomByIdOrAlias(roomId: RoomId, serverNames: List): Result
+ suspend fun joinRoom(roomId: RoomId): Result
+ suspend fun joinRoomByIdOrAlias(roomIdOrAlias: RoomIdOrAlias, serverNames: List): Result
suspend fun knockRoom(roomId: RoomId): Result
fun syncService(): SyncService
fun sessionVerificationService(): SessionVerificationService
@@ -104,7 +106,6 @@ interface MatrixClient : Closeable {
suspend fun trackRecentlyVisitedRoom(roomId: RoomId): Result
suspend fun getRecentlyVisitedRooms(): Result>
suspend fun resolveRoomAlias(roomAlias: RoomAlias): Result
- suspend fun getRoomPreviewFromRoomId(roomId: RoomId, serverNames: List): Result
/**
* Enables or disables the sending queue, according to the given parameter.
@@ -132,4 +133,5 @@ interface MatrixClient : Closeable {
* Execute generic GET requests through the SDKs internal HTTP client.
*/
suspend fun getUrl(url: String): Result
+ suspend fun getRoomPreview(roomIdOrAlias: RoomIdOrAlias, serverNames: List): Result
}
diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MatrixRoom.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MatrixRoom.kt
index 926df6ae21..378ada5b16 100644
--- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MatrixRoom.kt
+++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MatrixRoom.kt
@@ -126,6 +126,8 @@ interface MatrixRoom : Closeable {
suspend fun sendMessage(body: String, htmlBody: String?, mentions: List): Result
+ suspend fun editMessage(eventId: EventId, body: String, htmlBody: String?, mentions: List): Result
+
suspend fun sendImage(
file: File,
thumbnailFile: File?,
diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/join/JoinRoom.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/join/JoinRoom.kt
index fe6a2d9e47..87cc76e199 100644
--- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/join/JoinRoom.kt
+++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/join/JoinRoom.kt
@@ -17,11 +17,11 @@
package io.element.android.libraries.matrix.api.room.join
import im.vector.app.features.analytics.plan.JoinedRoom
-import io.element.android.libraries.matrix.api.core.RoomId
+import io.element.android.libraries.matrix.api.core.RoomIdOrAlias
interface JoinRoom {
suspend operator fun invoke(
- roomId: RoomId,
+ roomIdOrAlias: RoomIdOrAlias,
serverNames: List,
trigger: JoinedRoom.Trigger,
): Result
diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/roomlist/RoomSummary.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/roomlist/RoomSummary.kt
index 6841af9721..b932b235fa 100644
--- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/roomlist/RoomSummary.kt
+++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/roomlist/RoomSummary.kt
@@ -28,6 +28,7 @@ data class RoomSummary(
val roomId: RoomId,
val name: String?,
val canonicalAlias: RoomAlias?,
+ val alternativeAliases: List,
val isDirect: Boolean,
val avatarUrl: String?,
val lastMessage: RoomMessage?,
@@ -44,4 +45,6 @@ data class RoomSummary(
val heroes: List,
) {
val lastMessageTimestamp = lastMessage?.originServerTs
+ val aliases: List
+ get() = listOfNotNull(canonicalAlias) + alternativeAliases
}
diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/TimelineException.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/TimelineException.kt
index e3970619cd..ba44de0ba3 100644
--- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/TimelineException.kt
+++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/TimelineException.kt
@@ -18,4 +18,5 @@ package io.element.android.libraries.matrix.api.timeline
sealed class TimelineException : Exception() {
data object CannotPaginate : TimelineException()
+ data object EventNotFound : TimelineException()
}
diff --git a/libraries/matrix/impl/build.gradle.kts b/libraries/matrix/impl/build.gradle.kts
index 523ea2fda1..c74cb6ce07 100644
--- a/libraries/matrix/impl/build.gradle.kts
+++ b/libraries/matrix/impl/build.gradle.kts
@@ -37,12 +37,13 @@ dependencies {
debugImplementation(libs.matrix.sdk)
}
implementation(projects.appconfig)
- implementation(projects.libraries.di)
implementation(projects.libraries.androidutils)
+ implementation(projects.libraries.di)
+ implementation(projects.libraries.featureflag.api)
implementation(projects.libraries.network)
+ implementation(projects.libraries.preferences.api)
implementation(projects.services.analytics.api)
implementation(projects.services.toolbox.api)
- implementation(projects.libraries.featureflag.api)
api(projects.libraries.matrix.api)
implementation(libs.dagger)
implementation(projects.libraries.core)
diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClient.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClient.kt
index db86e1a2a5..3aab5bebed 100644
--- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClient.kt
+++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClient.kt
@@ -24,7 +24,9 @@ import io.element.android.libraries.matrix.api.MatrixClient
import io.element.android.libraries.matrix.api.core.ProgressCallback
import io.element.android.libraries.matrix.api.core.RoomAlias
import io.element.android.libraries.matrix.api.core.RoomId
+import io.element.android.libraries.matrix.api.core.RoomIdOrAlias
import io.element.android.libraries.matrix.api.core.UserId
+import io.element.android.libraries.matrix.api.core.toRoomIdOrAlias
import io.element.android.libraries.matrix.api.createroom.CreateRoomParameters
import io.element.android.libraries.matrix.api.createroom.RoomPreset
import io.element.android.libraries.matrix.api.createroom.RoomVisibility
@@ -41,6 +43,7 @@ import io.element.android.libraries.matrix.api.room.alias.ResolvedRoomAlias
import io.element.android.libraries.matrix.api.room.preview.RoomPreview
import io.element.android.libraries.matrix.api.roomdirectory.RoomDirectoryService
import io.element.android.libraries.matrix.api.roomlist.RoomListService
+import io.element.android.libraries.matrix.api.roomlist.RoomSummary
import io.element.android.libraries.matrix.api.sync.SyncService
import io.element.android.libraries.matrix.api.sync.SyncState
import io.element.android.libraries.matrix.api.user.MatrixSearchUserResults
@@ -175,7 +178,7 @@ class RustMatrixClient(
val (anonymizedAccessToken, anonymizedRefreshToken) = existingData.anonymizedTokens()
clientLog.d(
"Removing session data with access token '$anonymizedAccessToken' " +
- "and refresh token '$anonymizedRefreshToken'."
+ "and refresh token '$anonymizedRefreshToken'."
)
if (existingData != null) {
// Set isTokenValid to false
@@ -320,16 +323,23 @@ class RustMatrixClient(
/**
* Wait for the room to be available in the room list.
- * @param roomId the room id to wait for
+ * @param roomIdOrAlias the room id or alias to wait for
* @param timeout the timeout to wait for the room to be available
* @throws TimeoutCancellationException if the room is not available after the timeout
*/
- private suspend fun awaitRoom(roomId: RoomId, timeout: Duration) {
- withTimeout(timeout) {
+ private suspend fun awaitRoom(roomIdOrAlias: RoomIdOrAlias, timeout: Duration): RoomSummary {
+ val predicate: (List) -> Boolean = when (roomIdOrAlias) {
+ is RoomIdOrAlias.Alias -> { roomSummaries: List ->
+ roomSummaries.flatMap { it.aliases }.contains(roomIdOrAlias.roomAlias)
+ }
+ is RoomIdOrAlias.Id -> { roomSummaries: List ->
+ roomSummaries.map { it.roomId }.contains(roomIdOrAlias.roomId)
+ }
+ }
+ return withTimeout(timeout) {
roomListService.allRooms.summaries
- .filter { roomSummaries ->
- roomSummaries.map { it.roomId }.contains(roomId)
- }
+ .filter(predicate)
+ .first()
.first()
}
}
@@ -373,7 +383,7 @@ class RustMatrixClient(
val roomId = RoomId(client.createRoom(rustParams))
// Wait to receive the room back from the sync but do not returns failure if it fails.
try {
- awaitRoom(roomId, 30.seconds)
+ awaitRoom(roomId.toRoomIdOrAlias(), 30.seconds)
} catch (e: Exception) {
Timber.e(e, "Timeout waiting for the room to be available in the room list")
}
@@ -424,30 +434,29 @@ class RustMatrixClient(
runCatching { client.removeAvatar() }
}
- override suspend fun joinRoom(roomId: RoomId): Result = withContext(sessionDispatcher) {
+ override suspend fun joinRoom(roomId: RoomId): Result = withContext(sessionDispatcher) {
runCatching {
client.joinRoomById(roomId.value).destroy()
try {
- awaitRoom(roomId, 10.seconds)
+ awaitRoom(roomId.toRoomIdOrAlias(), 10.seconds)
} catch (e: Exception) {
Timber.e(e, "Timeout waiting for the room to be available in the room list")
+ null
}
}
}
- override suspend fun joinRoomByIdOrAlias(
- roomId: RoomId,
- serverNames: List,
- ): Result = withContext(sessionDispatcher) {
+ override suspend fun joinRoomByIdOrAlias(roomIdOrAlias: RoomIdOrAlias, serverNames: List): Result = withContext(sessionDispatcher) {
runCatching {
client.joinRoomByIdOrAlias(
- roomIdOrAlias = roomId.value,
+ roomIdOrAlias = roomIdOrAlias.identifier,
serverNames = serverNames,
).destroy()
try {
- awaitRoom(roomId, 10.seconds)
+ awaitRoom(roomIdOrAlias, 10.seconds)
} catch (e: Exception) {
Timber.e(e, "Timeout waiting for the room to be available in the room list")
+ null
}
}
}
@@ -478,12 +487,12 @@ class RustMatrixClient(
}
}
- override suspend fun getRoomPreviewFromRoomId(roomId: RoomId, serverNames: List): Result = withContext(sessionDispatcher) {
+ override suspend fun getRoomPreview(roomIdOrAlias: RoomIdOrAlias, serverNames: List): Result = withContext(sessionDispatcher) {
runCatching {
- client.getRoomPreviewFromRoomId(
- roomId = roomId.value,
- viaServers = serverNames,
- ).let(RoomPreviewMapper::map)
+ when (roomIdOrAlias) {
+ is RoomIdOrAlias.Alias -> client.getRoomPreviewFromRoomAlias(roomIdOrAlias.roomAlias.value)
+ is RoomIdOrAlias.Id -> client.getRoomPreviewFromRoomId(roomIdOrAlias.roomId.value, serverNames)
+ }.let(RoomPreviewMapper::map)
}
}
@@ -581,7 +590,7 @@ class RustMatrixClient(
var room = getRoom(roomId)
if (room == null) {
emit(Optional.empty())
- awaitRoom(roomId, INFINITE)
+ awaitRoom(roomId.toRoomIdOrAlias(), INFINITE)
room = getRoom(roomId)
}
room?.use {
diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClientFactory.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClientFactory.kt
index 7ddf54a300..51e71f3855 100644
--- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClientFactory.kt
+++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClientFactory.kt
@@ -23,10 +23,12 @@ import io.element.android.libraries.matrix.impl.certificates.UserCertificatesPro
import io.element.android.libraries.matrix.impl.proxy.ProxyProvider
import io.element.android.libraries.matrix.impl.util.anonymizedTokens
import io.element.android.libraries.network.useragent.UserAgentProvider
+import io.element.android.libraries.preferences.api.store.AppPreferencesStore
import io.element.android.libraries.sessionstorage.api.SessionData
import io.element.android.libraries.sessionstorage.api.SessionStore
import io.element.android.services.toolbox.api.systemclock.SystemClock
import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.first
import kotlinx.coroutines.withContext
import org.matrix.rustcomponents.sdk.ClientBuilder
import org.matrix.rustcomponents.sdk.Session
@@ -46,9 +48,18 @@ class RustMatrixClientFactory @Inject constructor(
private val proxyProvider: ProxyProvider,
private val clock: SystemClock,
private val utdTracker: UtdTracker,
+ private val appPreferencesStore: AppPreferencesStore,
) {
suspend fun create(sessionData: SessionData): RustMatrixClient = withContext(coroutineDispatchers.io) {
- val client = getBaseClientBuilder(sessionData.sessionPath, sessionData.passphrase)
+ val client = getBaseClientBuilder(
+ sessionPath = sessionData.sessionPath,
+ passphrase = sessionData.passphrase,
+ slidingSync = if (appPreferencesStore.isSimplifiedSlidingSyncEnabledFlow().first()) {
+ ClientBuilderSlidingSync.Simplified
+ } else {
+ ClientBuilderSlidingSync.Restored
+ },
+ )
.homeserverUrl(sessionData.homeserverUrl)
.username(sessionData.userId)
.use { it.build() }
@@ -79,6 +90,7 @@ class RustMatrixClientFactory @Inject constructor(
sessionPath: String,
passphrase: String?,
slidingSyncProxy: String? = null,
+ slidingSync: ClientBuilderSlidingSync,
): ClientBuilder {
return ClientBuilder()
.sessionPath(sessionPath)
@@ -88,6 +100,13 @@ class RustMatrixClientFactory @Inject constructor(
.addRootCertificates(userCertificatesProvider.provides())
.autoEnableBackups(true)
.autoEnableCrossSigning(true)
+ .run {
+ when (slidingSync) {
+ ClientBuilderSlidingSync.Restored -> this
+ ClientBuilderSlidingSync.Discovered -> requiresSlidingSync()
+ ClientBuilderSlidingSync.Simplified -> simplifiedSlidingSync(true)
+ }
+ }
.run {
// Workaround for non-nullable proxy parameter in the SDK, since each call to the ClientBuilder returns a new reference we need to keep
proxyProvider.provides()?.let { proxy(it) } ?: this
@@ -95,6 +114,17 @@ class RustMatrixClientFactory @Inject constructor(
}
}
+enum class ClientBuilderSlidingSync {
+ // The proxy will be supplied when restoring the Session.
+ Restored,
+
+ // A proxy must be discovered whilst building the session.
+ Discovered,
+
+ // Use Simplified Sliding Sync (discovery isn't a thing yet).
+ Simplified,
+}
+
private fun SessionData.toSession() = Session(
accessToken = accessToken,
refreshToken = refreshToken,
diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/RustMatrixAuthenticationService.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/RustMatrixAuthenticationService.kt
index 8c0546fa8b..e1ff0811e1 100644
--- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/RustMatrixAuthenticationService.kt
+++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/RustMatrixAuthenticationService.kt
@@ -29,6 +29,7 @@ import io.element.android.libraries.matrix.api.auth.OidcDetails
import io.element.android.libraries.matrix.api.auth.qrlogin.MatrixQrCodeLoginData
import io.element.android.libraries.matrix.api.auth.qrlogin.QrCodeLoginStep
import io.element.android.libraries.matrix.api.core.SessionId
+import io.element.android.libraries.matrix.impl.ClientBuilderSlidingSync
import io.element.android.libraries.matrix.impl.RustMatrixClientFactory
import io.element.android.libraries.matrix.impl.auth.qrlogin.QrErrorMapper
import io.element.android.libraries.matrix.impl.auth.qrlogin.SdkQrCodeLoginData
@@ -210,6 +211,7 @@ class RustMatrixAuthenticationService @Inject constructor(
sessionPath = sessionPath,
passphrase = pendingPassphrase,
slidingSyncProxy = AuthenticationConfig.SLIDING_SYNC_PROXY_URL,
+ slidingSync = ClientBuilderSlidingSync.Discovered,
)
.buildWithQrCode(
qrCodeData = (qrCodeData as SdkQrCodeLoginData).rustQrCodeData,
@@ -251,8 +253,8 @@ class RustMatrixAuthenticationService @Inject constructor(
sessionPath = sessionPath,
passphrase = pendingPassphrase,
slidingSyncProxy = AuthenticationConfig.SLIDING_SYNC_PROXY_URL,
+ slidingSync = ClientBuilderSlidingSync.Discovered,
)
- .requiresSlidingSync()
private fun clear() {
currentClient?.close()
diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustMatrixRoom.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustMatrixRoom.kt
index 0a09ea005e..b594bba5e4 100644
--- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustMatrixRoom.kt
+++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustMatrixRoom.kt
@@ -56,6 +56,7 @@ import io.element.android.libraries.matrix.impl.room.member.RoomMemberMapper
import io.element.android.libraries.matrix.impl.room.powerlevels.RoomPowerLevelsMapper
import io.element.android.libraries.matrix.impl.timeline.RustTimeline
import io.element.android.libraries.matrix.impl.timeline.toRustReceiptType
+import io.element.android.libraries.matrix.impl.util.MessageEventContent
import io.element.android.libraries.matrix.impl.util.mxCallbackFlow
import io.element.android.libraries.matrix.impl.widget.RustWidgetDriver
import io.element.android.libraries.matrix.impl.widget.generateWidgetWebViewUrl
@@ -324,6 +325,14 @@ class RustMatrixRoom(
}
}
+ override suspend fun editMessage(eventId: EventId, body: String, htmlBody: String?, mentions: List): Result = withContext(roomDispatcher) {
+ runCatching {
+ MessageEventContent.from(body, htmlBody, mentions).use { newContent ->
+ innerRoom.edit(eventId.value, newContent)
+ }
+ }
+ }
+
override suspend fun sendMessage(body: String, htmlBody: String?, mentions: List): Result {
return liveTimeline.sendMessage(body, htmlBody, mentions)
}
diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/join/DefaultJoinRoom.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/join/DefaultJoinRoom.kt
index d2ce4e61d5..063facbc96 100644
--- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/join/DefaultJoinRoom.kt
+++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/join/DefaultJoinRoom.kt
@@ -20,8 +20,9 @@ import com.squareup.anvil.annotations.ContributesBinding
import im.vector.app.features.analytics.plan.JoinedRoom
import io.element.android.libraries.di.SessionScope
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.RoomIdOrAlias
import io.element.android.libraries.matrix.api.room.join.JoinRoom
+import io.element.android.libraries.matrix.api.roomlist.RoomSummary
import io.element.android.libraries.matrix.impl.analytics.toAnalyticsJoinedRoom
import io.element.android.services.analytics.api.AnalyticsService
import javax.inject.Inject
@@ -32,18 +33,30 @@ class DefaultJoinRoom @Inject constructor(
private val analyticsService: AnalyticsService,
) : JoinRoom {
override suspend fun invoke(
- roomId: RoomId,
+ roomIdOrAlias: RoomIdOrAlias,
serverNames: List,
- trigger: JoinedRoom.Trigger,
+ trigger: JoinedRoom.Trigger
): Result {
- return if (serverNames.isEmpty()) {
- client.joinRoom(roomId)
- } else {
- client.joinRoomByIdOrAlias(roomId, serverNames)
- }.onSuccess {
- client.getRoom(roomId)?.use { room ->
- analyticsService.capture(room.toAnalyticsJoinedRoom(trigger))
+ return when (roomIdOrAlias) {
+ is RoomIdOrAlias.Id -> {
+ if (serverNames.isEmpty()) {
+ client.joinRoom(roomIdOrAlias.roomId)
+ } else {
+ client.joinRoomByIdOrAlias(roomIdOrAlias, serverNames)
+ }
}
+ is RoomIdOrAlias.Alias -> {
+ client.joinRoomByIdOrAlias(roomIdOrAlias, serverNames = emptyList())
+ }
+ }.onSuccess { roomSummary ->
+ client.captureJoinedRoomAnalytics(roomSummary, trigger)
+ }.map { }
+ }
+
+ private suspend fun MatrixClient.captureJoinedRoomAnalytics(roomSummary: RoomSummary?, trigger: JoinedRoom.Trigger) {
+ if (roomSummary == null) return
+ getRoom(roomSummary.roomId)?.use { room ->
+ analyticsService.capture(room.toAnalyticsJoinedRoom(trigger))
}
}
}
diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomlist/RoomSummaryDetailsFactory.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomlist/RoomSummaryDetailsFactory.kt
index a833734a5c..9c67eb1221 100644
--- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomlist/RoomSummaryDetailsFactory.kt
+++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomlist/RoomSummaryDetailsFactory.kt
@@ -38,6 +38,7 @@ class RoomSummaryDetailsFactory(private val roomMessageFactory: RoomMessageFacto
roomId = RoomId(roomInfo.id),
name = roomInfo.displayName,
canonicalAlias = roomInfo.canonicalAlias?.let(::RoomAlias),
+ alternativeAliases = roomInfo.alternativeAliases.map(::RoomAlias),
isDirect = roomInfo.isDirect,
avatarUrl = roomInfo.avatarUrl,
numUnreadMentions = roomInfo.numUnreadMentions.toInt(),
diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/RustTimeline.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/RustTimeline.kt
index 00c79bf687..b57efc5603 100644
--- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/RustTimeline.kt
+++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/RustTimeline.kt
@@ -42,7 +42,6 @@ import io.element.android.libraries.matrix.impl.media.toMSC3246range
import io.element.android.libraries.matrix.impl.poll.toInner
import io.element.android.libraries.matrix.impl.room.RoomContentForwarder
import io.element.android.libraries.matrix.impl.room.location.toInner
-import io.element.android.libraries.matrix.impl.room.map
import io.element.android.libraries.matrix.impl.timeline.item.event.EventTimelineItemMapper
import io.element.android.libraries.matrix.impl.timeline.item.event.TimelineEventContentMapper
import io.element.android.libraries.matrix.impl.timeline.item.virtual.VirtualTimelineItemMapper
@@ -51,6 +50,7 @@ import io.element.android.libraries.matrix.impl.timeline.postprocessor.LoadingIn
import io.element.android.libraries.matrix.impl.timeline.postprocessor.RoomBeginningPostProcessor
import io.element.android.libraries.matrix.impl.timeline.postprocessor.TimelineEncryptedHistoryPostProcessor
import io.element.android.libraries.matrix.impl.timeline.reply.InReplyToMapper
+import io.element.android.libraries.matrix.impl.util.MessageEventContent
import io.element.android.services.toolbox.api.systemclock.SystemClock
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.CoroutineDispatcher
@@ -70,12 +70,10 @@ import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
+import org.matrix.rustcomponents.sdk.EventTimelineItem
import org.matrix.rustcomponents.sdk.FormattedBody
import org.matrix.rustcomponents.sdk.MessageFormat
-import org.matrix.rustcomponents.sdk.RoomMessageEventContentWithoutRelation
import org.matrix.rustcomponents.sdk.SendAttachmentJoinHandle
-import org.matrix.rustcomponents.sdk.messageEventContentFromHtml
-import org.matrix.rustcomponents.sdk.messageEventContentFromMarkdown
import org.matrix.rustcomponents.sdk.use
import timber.log.Timber
import uniffi.matrix_sdk_ui.LiveBackPaginationStatus
@@ -266,7 +264,7 @@ class RustTimeline(
}
override suspend fun sendMessage(body: String, htmlBody: String?, mentions: List): Result = withContext(dispatcher) {
- messageEventContentFromParts(body, htmlBody).withMentions(mentions.map()).use { content ->
+ MessageEventContent.from(body, htmlBody, mentions).use { content ->
runCatching {
inner.send(content)
}
@@ -275,20 +273,8 @@ class RustTimeline(
override suspend fun redactEvent(eventId: EventId?, transactionId: TransactionId?, reason: String?): Result = withContext(dispatcher) {
runCatching {
- when {
- eventId != null -> {
- inner.getEventTimelineItemByEventId(eventId.value).use {
- inner.redactEvent(item = it, reason = reason)
- }
- }
- transactionId != null -> {
- inner.getEventTimelineItemByTransactionId(transactionId.value).use {
- inner.redactEvent(item = it, reason = reason)
- }
- }
- else -> {
- error("Either eventId or transactionId must be non-null")
- }
+ getEventTimelineItem(eventId, transactionId).use { item ->
+ inner.redactEvent(item = item, reason = reason)
}
}
}
@@ -302,26 +288,11 @@ class RustTimeline(
): Result =
withContext(dispatcher) {
runCatching {
- when {
- originalEventId != null -> {
- inner.getEventTimelineItemByEventId(originalEventId.value).use {
- inner.edit(
- newContent = messageEventContentFromParts(body, htmlBody).withMentions(mentions.map()),
- item = it,
- )
- }
- }
- transactionId != null -> {
- inner.getEventTimelineItemByTransactionId(transactionId.value).use {
- inner.edit(
- newContent = messageEventContentFromParts(body, htmlBody).withMentions(mentions.map()),
- item = it,
- )
- }
- }
- else -> {
- error("Either originalEventId or transactionId must be non null")
- }
+ getEventTimelineItem(originalEventId, transactionId).use { item ->
+ inner.edit(
+ newContent = MessageEventContent.from(body, htmlBody, mentions),
+ item = item,
+ )
}
}
}
@@ -334,7 +305,7 @@ class RustTimeline(
fromNotification: Boolean,
): Result = withContext(dispatcher) {
runCatching {
- val msg = messageEventContentFromParts(body, htmlBody).withMentions(mentions.map())
+ val msg = MessageEventContent.from(body, htmlBody, mentions)
inner.sendReply(msg, eventId.value)
}
}
@@ -361,6 +332,20 @@ class RustTimeline(
}
}
+ @Throws
+ private suspend fun getEventTimelineItem(eventId: EventId?, transactionId: TransactionId?): EventTimelineItem {
+ return try {
+ when {
+ eventId != null -> inner.getEventTimelineItemByEventId(eventId.value)
+ transactionId != null -> inner.getEventTimelineItemByTransactionId(transactionId.value)
+ else -> error("Either eventId or transactionId must be non-null")
+ }
+ } catch (e: Exception) {
+ Timber.e(e, "Failed to get event timeline item")
+ throw TimelineException.EventNotFound
+ }
+ }
+
override suspend fun sendVideo(
file: File,
thumbnailFile: File?,
@@ -517,13 +502,6 @@ class RustTimeline(
)
}
- private fun messageEventContentFromParts(body: String, htmlBody: String?): RoomMessageEventContentWithoutRelation =
- if (htmlBody != null) {
- messageEventContentFromHtml(body, htmlBody)
- } else {
- messageEventContentFromMarkdown(body)
- }
-
private fun sendAttachment(files: List, handle: () -> SendAttachmentJoinHandle): Result {
return runCatching {
MediaUploadHandlerImpl(files, handle())
diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/util/MessageEventContent.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/util/MessageEventContent.kt
new file mode 100644
index 0000000000..e1728bb528
--- /dev/null
+++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/util/MessageEventContent.kt
@@ -0,0 +1,36 @@
+/*
+ * Copyright (c) 2024 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
+ *
+ * https://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.matrix.impl.util
+
+import io.element.android.libraries.matrix.api.room.Mention
+import io.element.android.libraries.matrix.impl.room.map
+import org.matrix.rustcomponents.sdk.RoomMessageEventContentWithoutRelation
+import org.matrix.rustcomponents.sdk.messageEventContentFromHtml
+import org.matrix.rustcomponents.sdk.messageEventContentFromMarkdown
+
+/**
+ * Creates a [RoomMessageEventContentWithoutRelation] from a body, an html body and a list of mentions.
+ */
+object MessageEventContent {
+ fun from(body: String, htmlBody: String?, mentions: List): RoomMessageEventContentWithoutRelation {
+ return if (htmlBody != null) {
+ messageEventContentFromHtml(body, htmlBody)
+ } else {
+ messageEventContentFromMarkdown(body)
+ }.withMentions(mentions.map())
+ }
+}
diff --git a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/room/join/DefaultJoinRoomTest.kt b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/room/join/DefaultJoinRoomTest.kt
index 6296f219de..9941b6b1ed 100644
--- a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/room/join/DefaultJoinRoomTest.kt
+++ b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/room/join/DefaultJoinRoomTest.kt
@@ -20,11 +20,15 @@ import com.google.common.truth.Truth.assertThat
import im.vector.app.features.analytics.plan.JoinedRoom
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.RoomIdOrAlias
+import io.element.android.libraries.matrix.api.core.toRoomIdOrAlias
import io.element.android.libraries.matrix.impl.analytics.toAnalyticsJoinedRoom
+import io.element.android.libraries.matrix.test.A_ROOM_ALIAS
import io.element.android.libraries.matrix.test.A_ROOM_ID
import io.element.android.libraries.matrix.test.A_SERVER_LIST
import io.element.android.libraries.matrix.test.FakeMatrixClient
import io.element.android.libraries.matrix.test.room.FakeMatrixRoom
+import io.element.android.libraries.matrix.test.room.aRoomSummaryFilled
import io.element.android.services.analytics.test.FakeAnalyticsService
import io.element.android.tests.testutils.lambda.lambdaRecorder
import io.element.android.tests.testutils.lambda.value
@@ -33,9 +37,10 @@ import org.junit.Test
class DefaultJoinRoomTest {
@Test
- fun `when there is no server names, the classic join room API is used`() = runTest {
- val joinRoomLambda = lambdaRecorder { _: RoomId -> Result.success(Unit) }
- val joinRoomByIdOrAliasLambda = lambdaRecorder { _: RoomId, _: List -> Result.success(Unit) }
+ fun `when using roomId and there is no server names, the classic join room API is used`() = runTest {
+ val roomSummary = aRoomSummaryFilled()
+ val joinRoomLambda = lambdaRecorder { _: RoomId -> Result.success(roomSummary) }
+ val joinRoomByIdOrAliasLambda = lambdaRecorder { _: RoomIdOrAlias, _: List -> Result.success(roomSummary) }
val roomResult = FakeMatrixRoom()
val aTrigger = JoinedRoom.Trigger.MobilePermalink
val client: MatrixClient = FakeMatrixClient().also {
@@ -51,7 +56,7 @@ class DefaultJoinRoomTest {
client = client,
analyticsService = analyticsService,
)
- sut.invoke(A_ROOM_ID, emptyList(), aTrigger)
+ sut.invoke(A_ROOM_ID.toRoomIdOrAlias(), emptyList(), aTrigger)
joinRoomByIdOrAliasLambda
.assertions()
.isNeverCalled()
@@ -67,9 +72,10 @@ class DefaultJoinRoomTest {
}
@Test
- fun `when server names are available, joinRoomByIdOrAlias API is used`() = runTest {
- val joinRoomLambda = lambdaRecorder { _: RoomId -> Result.success(Unit) }
- val joinRoomByIdOrAliasLambda = lambdaRecorder { _: RoomId, _: List -> Result.success(Unit) }
+ fun `when using roomId and server names are available, joinRoomByIdOrAlias API is used`() = runTest {
+ val roomSummary = aRoomSummaryFilled()
+ val joinRoomLambda = lambdaRecorder { _: RoomId -> Result.success(roomSummary) }
+ val joinRoomByIdOrAliasLambda = lambdaRecorder { _: RoomIdOrAlias, _: List -> Result.success(roomSummary) }
val roomResult = FakeMatrixRoom()
val aTrigger = JoinedRoom.Trigger.MobilePermalink
val client: MatrixClient = FakeMatrixClient().also {
@@ -85,12 +91,12 @@ class DefaultJoinRoomTest {
client = client,
analyticsService = analyticsService,
)
- sut.invoke(A_ROOM_ID, A_SERVER_LIST, aTrigger)
+ sut.invoke(A_ROOM_ID.toRoomIdOrAlias(), A_SERVER_LIST, aTrigger)
joinRoomByIdOrAliasLambda
.assertions()
.isCalledOnce()
.with(
- value(A_ROOM_ID),
+ value(A_ROOM_ID.toRoomIdOrAlias()),
value(A_SERVER_LIST)
)
joinRoomLambda
@@ -100,4 +106,40 @@ class DefaultJoinRoomTest {
roomResult.toAnalyticsJoinedRoom(aTrigger)
)
}
+
+ @Test
+ fun `when using roomAlias, joinRoomByIdOrAlias API is used`() = runTest {
+ val roomSummary = aRoomSummaryFilled()
+ val joinRoomLambda = lambdaRecorder { _: RoomId -> Result.success(roomSummary) }
+ val joinRoomByIdOrAliasLambda = lambdaRecorder { _: RoomIdOrAlias, _: List -> Result.success(roomSummary) }
+ val roomResult = FakeMatrixRoom()
+ val aTrigger = JoinedRoom.Trigger.MobilePermalink
+ val client: MatrixClient = FakeMatrixClient().also {
+ it.joinRoomLambda = joinRoomLambda
+ it.joinRoomByIdOrAliasLambda = joinRoomByIdOrAliasLambda
+ it.givenGetRoomResult(
+ roomId = A_ROOM_ID,
+ result = roomResult
+ )
+ }
+ val analyticsService = FakeAnalyticsService()
+ val sut = DefaultJoinRoom(
+ client = client,
+ analyticsService = analyticsService,
+ )
+ sut.invoke(A_ROOM_ALIAS.toRoomIdOrAlias(), A_SERVER_LIST, aTrigger)
+ joinRoomByIdOrAliasLambda
+ .assertions()
+ .isCalledOnce()
+ .with(
+ value(A_ROOM_ALIAS.toRoomIdOrAlias()),
+ value(emptyList())
+ )
+ joinRoomLambda
+ .assertions()
+ .isNeverCalled()
+ assertThat(analyticsService.capturedEvents).containsExactly(
+ roomResult.toAnalyticsJoinedRoom(aTrigger)
+ )
+ }
}
diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/FakeMatrixClient.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/FakeMatrixClient.kt
index 1b6223279c..1fb8878488 100644
--- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/FakeMatrixClient.kt
+++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/FakeMatrixClient.kt
@@ -20,6 +20,7 @@ import io.element.android.libraries.matrix.api.MatrixClient
import io.element.android.libraries.matrix.api.core.ProgressCallback
import io.element.android.libraries.matrix.api.core.RoomAlias
import io.element.android.libraries.matrix.api.core.RoomId
+import io.element.android.libraries.matrix.api.core.RoomIdOrAlias
import io.element.android.libraries.matrix.api.core.SessionId
import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.api.createroom.CreateRoomParameters
@@ -36,6 +37,7 @@ import io.element.android.libraries.matrix.api.room.alias.ResolvedRoomAlias
import io.element.android.libraries.matrix.api.room.preview.RoomPreview
import io.element.android.libraries.matrix.api.roomdirectory.RoomDirectoryService
import io.element.android.libraries.matrix.api.roomlist.RoomListService
+import io.element.android.libraries.matrix.api.roomlist.RoomSummary
import io.element.android.libraries.matrix.api.user.MatrixSearchUserResults
import io.element.android.libraries.matrix.api.user.MatrixUser
import io.element.android.libraries.matrix.api.verification.SessionVerificationService
@@ -80,7 +82,7 @@ class FakeMatrixClient(
private val roomDirectoryService: RoomDirectoryService = FakeRoomDirectoryService(),
private val accountManagementUrlString: Result = Result.success(null),
private val resolveRoomAliasResult: (RoomAlias) -> Result = { Result.success(ResolvedRoomAlias(A_ROOM_ID, emptyList())) },
- private val getRoomPreviewFromRoomIdResult: (RoomId, List) -> Result = { _, _ -> Result.failure(AN_EXCEPTION) },
+ private val getRoomPreviewResult: (RoomIdOrAlias, List) -> Result = { _, _ -> Result.failure(AN_EXCEPTION) },
private val clearCacheLambda: () -> Unit = { lambdaError() },
private val userIdServerNameLambda: () -> String = { lambdaError() },
private val getUrlLambda: (String) -> Result = { lambdaError() },
@@ -108,11 +110,11 @@ class FakeMatrixClient(
private var setDisplayNameResult: Result = Result.success(Unit)
private var uploadAvatarResult: Result = Result.success(Unit)
private var removeAvatarResult: Result = Result.success(Unit)
- var joinRoomLambda: (RoomId) -> Result = {
- Result.success(Unit)
+ var joinRoomLambda: (RoomId) -> Result = {
+ Result.success(null)
}
- var joinRoomByIdOrAliasLambda: (RoomId, List) -> Result = { _, _ ->
- Result.success(Unit)
+ var joinRoomByIdOrAliasLambda: (RoomIdOrAlias, List) -> Result = { _, _ ->
+ Result.success(null)
}
var knockRoomLambda: (RoomId) -> Result = {
Result.success(Unit)
@@ -207,10 +209,10 @@ class FakeMatrixClient(
return removeAvatarResult
}
- override suspend fun joinRoom(roomId: RoomId): Result = joinRoomLambda(roomId)
+ override suspend fun joinRoom(roomId: RoomId): Result = joinRoomLambda(roomId)
- override suspend fun joinRoomByIdOrAlias(roomId: RoomId, serverNames: List): Result {
- return joinRoomByIdOrAliasLambda(roomId, serverNames)
+ override suspend fun joinRoomByIdOrAlias(roomIdOrAlias: RoomIdOrAlias, serverNames: List): Result {
+ return joinRoomByIdOrAliasLambda(roomIdOrAlias, serverNames)
}
override suspend fun knockRoom(roomId: RoomId): Result = knockRoomLambda(roomId)
@@ -297,8 +299,8 @@ class FakeMatrixClient(
resolveRoomAliasResult(roomAlias)
}
- override suspend fun getRoomPreviewFromRoomId(roomId: RoomId, serverNames: List) = simulateLongTask {
- getRoomPreviewFromRoomIdResult(roomId, serverNames)
+ override suspend fun getRoomPreview(roomIdOrAlias: RoomIdOrAlias, serverNames: List): Result = simulateLongTask {
+ getRoomPreviewResult(roomIdOrAlias, serverNames)
}
override suspend fun getRecentlyVisitedRooms(): Result> {
diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/FakeMatrixRoom.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/FakeMatrixRoom.kt
index d7a4b105b4..cfd0267516 100644
--- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/FakeMatrixRoom.kt
+++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/FakeMatrixRoom.kt
@@ -56,7 +56,7 @@ import io.element.android.libraries.matrix.test.A_SESSION_ID
import io.element.android.libraries.matrix.test.media.FakeMediaUploadHandler
import io.element.android.libraries.matrix.test.notificationsettings.FakeNotificationSettingsService
import io.element.android.libraries.matrix.test.timeline.FakeTimeline
-import io.element.android.libraries.matrix.test.widget.FakeMatrixWidgetDriver
+import io.element.android.tests.testutils.lambda.lambdaError
import io.element.android.tests.testutils.simulateLongTask
import kotlinx.collections.immutable.ImmutableMap
import kotlinx.collections.immutable.persistentMapOf
@@ -84,126 +84,88 @@ class FakeMatrixRoom(
override val activeMemberCount: Long = 234L,
val notificationSettingsService: NotificationSettingsService = FakeNotificationSettingsService(),
override val liveTimeline: Timeline = FakeTimeline(),
- private var roomPermalinkResult: () -> Result = { Result.success("room link") },
- private var eventPermalinkResult: (EventId) -> Result = { Result.success("event link") },
- var sendCallNotificationIfNeededResult: () -> Result = { Result.success(Unit) },
- canRedactOwn: Boolean = false,
- canRedactOther: Boolean = false,
+ private var roomPermalinkResult: () -> Result = { lambdaError() },
+ private var eventPermalinkResult: (EventId) -> Result = { lambdaError() },
+ private val sendCallNotificationIfNeededResult: () -> Result = { lambdaError() },
+ private val userDisplayNameResult: () -> Result = { lambdaError() },
+ private val userAvatarUrlResult: () -> Result = { lambdaError() },
+ private val userRoleResult: () -> Result = { lambdaError() },
+ private val getUpdatedMemberResult: (UserId) -> Result = { lambdaError() },
+ private val joinRoomResult: () -> Result = { lambdaError() },
+ private val inviteUserResult: (UserId) -> Result = { lambdaError() },
+ private val canInviteResult: (UserId) -> Result = { lambdaError() },
+ private val canKickResult: (UserId) -> Result = { lambdaError() },
+ private val canBanResult: (UserId) -> Result = { lambdaError() },
+ private val canRedactOwnResult: (UserId) -> Result = { lambdaError() },
+ private val canRedactOtherResult: (UserId) -> Result = { lambdaError() },
+ private val canSendStateResult: (UserId, StateEventType) -> Result = { _, _ -> lambdaError() },
+ private val canUserSendMessageResult: (UserId, MessageEventType) -> Result = { _, _ -> lambdaError() },
+ private val sendMediaResult: (ProgressCallback?) -> Result = { lambdaError() },
+ private val setNameResult: (String) -> Result = { lambdaError() },
+ private val setTopicResult: (String) -> Result = { lambdaError() },
+ private val updateAvatarResult: (String, ByteArray) -> Result = { _, _ -> lambdaError() },
+ private val removeAvatarResult: () -> Result = { lambdaError() },
+ private val sendMessageResult: (String, String?, List) -> Result = { _, _, _ -> lambdaError() },
+ private val updateUserRoleResult: () -> Result = { lambdaError() },
+ private val toggleReactionResult: (String, EventId) -> Result = { _, _ -> lambdaError() },
+ private val retrySendMessageResult: (TransactionId) -> Result = { lambdaError() },
+ private val cancelSendResult: (TransactionId) -> Result = { lambdaError() },
+ private val forwardEventResult: (EventId, List) -> Result = { _, _ -> lambdaError() },
+ private val reportContentResult: (EventId, String, UserId?) -> Result = { _, _, _ -> lambdaError() },
+ private val kickUserResult: (UserId, String?) -> Result = { _, _ -> lambdaError() },
+ private val banUserResult: (UserId, String?) -> Result = { _, _ -> lambdaError() },
+ private val unBanUserResult: (UserId, String?) -> Result = { _, _ -> lambdaError() },
+ private val sendLocationResult: (String, String, String?, Int?, AssetType?) -> Result = { _, _, _, _, _ -> lambdaError() },
+ private val createPollResult: (String, List, Int, PollKind) -> Result = { _, _, _, _ -> lambdaError() },
+ private val editPollResult: (EventId, String, List, Int, PollKind) -> Result = { _, _, _, _, _ -> lambdaError() },
+ private val sendPollResponseResult: (EventId, List) -> Result = { _, _ -> lambdaError() },
+ private val endPollResult: (EventId, String) -> Result = { _, _ -> lambdaError() },
+ private val progressCallbackValues: List> = emptyList(),
+ private val generateWidgetWebViewUrlResult: (MatrixWidgetSettings, String, String?, String?) -> Result = { _, _, _, _ -> lambdaError() },
+ private val getWidgetDriverResult: (MatrixWidgetSettings) -> Result = { lambdaError() },
+ private val canUserTriggerRoomNotificationResult: (UserId) -> Result = { lambdaError() },
+ private val canUserJoinCallResult: (UserId) -> Result = { lambdaError() },
+ private val setIsFavoriteResult: (Boolean) -> Result = { lambdaError() },
+ private val powerLevelsResult: () -> Result = { lambdaError() },
+ private val updatePowerLevelsResult: () -> Result = { lambdaError() },
+ private val resetPowerLevelsResult: () -> Result = { lambdaError() },
+ private val typingNoticeResult: (Boolean) -> Result = { lambdaError() },
+ private val leaveRoomLambda: () -> Result = { lambdaError() },
+ private val updateMembersResult: () -> Unit = { lambdaError() },
+ private val getMembersResult: (Int) -> Result> = { lambdaError() },
+ private val timelineFocusedOnEventResult: (EventId) -> Result = { lambdaError() },
+ private val setSendQueueEnabledLambda: (Boolean) -> Unit = { _: Boolean -> },
+ private val saveComposerDraftLambda: (ComposerDraft) -> Result = { _: ComposerDraft -> Result.success(Unit) },
+ private val loadComposerDraftLambda: () -> Result = { Result.success(null) },
+ private val clearComposerDraftLambda: () -> Result = { Result.success(Unit) },
) : MatrixRoom {
- private var ignoreResult: Result = Result.success(Unit)
- private var unignoreResult: Result = Result.success(Unit)
- private var userDisplayNameResult = Result.success(null)
- private var userAvatarUrlResult = Result.success(null)
- private var userRoleResult = Result.success(RoomMember.Role.USER)
- private var getRoomMemberResult = Result.failure(IllegalStateException("Member not found"))
- private var joinRoomResult = Result.success(Unit)
- private var inviteUserResult = Result.success(Unit)
- private var canInviteResult = Result.success(true)
- private var canKickResult = Result.success(false)
- private var canBanResult = Result.success(false)
- private var canRedactOwnResult = Result.success(canRedactOwn)
- private var canRedactOtherResult = Result.success(canRedactOther)
- private val canSendStateResults = mutableMapOf>()
- private val canSendEventResults = mutableMapOf>()
- private var sendMediaResult = Result.success(FakeMediaUploadHandler())
- private var setNameResult = Result.success(Unit)
- private var setTopicResult = Result.success(Unit)
- private var updateAvatarResult = Result.success(Unit)
- private var removeAvatarResult = Result.success(Unit)
- private var updateUserRoleResult = Result.success(Unit)
- private var toggleReactionResult = Result.success(Unit)
- private var retrySendMessageResult = Result.success(Unit)
- private var cancelSendResult = Result.success(true)
- private var forwardEventResult = Result.success(Unit)
- private var reportContentResult = Result.success(Unit)
- private var kickUserResult = Result.success(Unit)
- private var banUserResult = Result.success(Unit)
- private var unBanUserResult = Result.success(Unit)
- private var sendLocationResult = Result.success(Unit)
- private var createPollResult = Result.success(Unit)
- private var editPollResult = Result.success(Unit)
- private var sendPollResponseResult = Result.success(Unit)
- private var endPollResult = Result.success(Unit)
- private var progressCallbackValues = emptyList>()
- private var generateWidgetWebViewUrlResult = Result.success("https://call.element.io")
- private var getWidgetDriverResult: Result = Result.success(FakeMatrixWidgetDriver())
- private var canUserTriggerRoomNotificationResult: Result = Result.success(true)
- private var canUserJoinCallResult: Result = Result.success(true)
- private var setIsFavoriteResult = Result.success(Unit)
- private var powerLevelsResult = Result.success(defaultRoomPowerLevels())
- private var updatePowerLevelsResult = Result.success(Unit)
- private var resetPowerLevelsResult = Result.success(defaultRoomPowerLevels())
- var sendMessageMentions = emptyList()
- private val _typingRecord = mutableListOf()
- val typingRecord: List
- get() = _typingRecord
-
- var sendMediaCount = 0
- private set
-
- private val _myReactions = mutableSetOf()
- val myReactions: Set = _myReactions
-
- var retrySendMessageCount: Int = 0
- private set
-
- var cancelSendCount: Int = 0
- private set
-
- var reportedContentCount: Int = 0
- private set
-
- private val _sentLocations = mutableListOf()
- val sentLocations: List = _sentLocations
-
- private val _createPollInvocations = mutableListOf()
- val createPollInvocations: List = _createPollInvocations
-
- private val _editPollInvocations = mutableListOf()
- val editPollInvocations: List = _editPollInvocations
-
- private val _sendPollResponseInvocations = mutableListOf()
- val sendPollResponseInvocations: List = _sendPollResponseInvocations
-
- private val _endPollInvocations = mutableListOf()
- val endPollInvocations: List = _endPollInvocations
-
- var invitedUserId: UserId? = null
- private set
-
- var newTopic: String? = null
- private set
-
- var newName: String? = null
- private set
-
- var newAvatarData: ByteArray? = null
- private set
-
- var removedAvatar: Boolean = false
- private set
-
- var leaveRoomLambda: (() -> Result) = { Result.success(Unit) }
-
private val _roomInfoFlow: MutableSharedFlow