diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 4ff935fad1..14b5112818 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -26,7 +26,7 @@ jobs: cancel-in-progress: true steps: - uses: actions/checkout@v3 - - uses: actions/cache@v2 + - uses: actions/cache@v3 with: path: | ~/.gradle/caches @@ -50,7 +50,7 @@ jobs: # Only runs on main, no concurrency. steps: - uses: actions/checkout@v3 - - uses: actions/cache@v2 + - uses: actions/cache@v3 with: path: | ~/.gradle/caches diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index 455545aeef..7f789b4610 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -34,7 +34,7 @@ jobs: uses: actions/setup-python@v3 with: python-version: 3.8 - - uses: actions/cache@v2 + - uses: actions/cache@v3 with: path: | ~/.gradle/caches @@ -43,7 +43,7 @@ jobs: restore-keys: | ${{ runner.os }}-gradle- - name: Start synapse server - uses: michaelkaye/setup-matrix-synapse@v0.3.0 + uses: michaelkaye/setup-matrix-synapse@v0.4.0 with: uploadLogs: true httpPort: 8080 @@ -221,7 +221,7 @@ jobs: uses: actions/setup-python@v3 with: python-version: 3.8 - - uses: actions/cache@v2 + - uses: actions/cache@v3 with: path: | ~/.gradle/caches @@ -230,7 +230,7 @@ jobs: restore-keys: | ${{ runner.os }}-gradle- - name: Start synapse server - uses: michaelkaye/setup-matrix-synapse@v0.3.0 + uses: michaelkaye/setup-matrix-synapse@v0.4.0 with: uploadLogs: true httpPort: 8080 @@ -273,7 +273,7 @@ jobs: with: distribution: 'adopt' java-version: '11' - - uses: actions/cache@v2 + - uses: actions/cache@v3 with: path: | ~/.gradle/caches @@ -302,7 +302,7 @@ jobs: with: distribution: 'adopt' java-version: '11' - - uses: actions/cache@v2 + - uses: actions/cache@v3 with: path: | ~/.gradle/caches diff --git a/.github/workflows/quality.yml b/.github/workflows/quality.yml index a588b91449..d427d65b7f 100644 --- a/.github/workflows/quality.yml +++ b/.github/workflows/quality.yml @@ -97,7 +97,7 @@ jobs: cancel-in-progress: true steps: - uses: actions/checkout@v3 - - uses: actions/cache@v2 + - uses: actions/cache@v3 with: path: | ~/.gradle/caches @@ -130,7 +130,7 @@ jobs: cancel-in-progress: true steps: - uses: actions/checkout@v3 - - uses: actions/cache@v2 + - uses: actions/cache@v3 with: path: | ~/.gradle/caches diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 587bf14488..98e5f588ca 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -25,7 +25,7 @@ jobs: with: distribution: 'adopt' java-version: 11 - - uses: actions/cache@v2 + - uses: actions/cache@v3 with: path: | ~/.gradle/caches @@ -45,7 +45,7 @@ jobs: cancel-in-progress: true steps: - uses: actions/checkout@v3 - - uses: actions/cache@v2 + - uses: actions/cache@v3 with: path: | ~/.gradle/caches diff --git a/CHANGES.md b/CHANGES.md index c411593627..e293a776dd 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,3 +1,57 @@ +Changes in Element v1.4.6 (2022-03-23) +====================================== + +Features ✨ +---------- + - Thread timeline is now live and much faster especially for large or old threads ([#5230](https://github.com/vector-im/element-android/issues/5230)) + - View all threads per room screen is now live when the home server supports threads ([#5232](https://github.com/vector-im/element-android/issues/5232)) + - Add a custom view to display a picker for share location options ([#5395](https://github.com/vector-im/element-android/issues/5395)) + - Add ability to pin a location on map for sharing ([#5417](https://github.com/vector-im/element-android/issues/5417)) + - Poll Integration Tests ([#5522](https://github.com/vector-im/element-android/issues/5522)) + - Live location sharing: adding build config field and show permission dialog ([#5536](https://github.com/vector-im/element-android/issues/5536)) + - Live location sharing: Adding indicator view when enabled ([#5571](https://github.com/vector-im/element-android/issues/5571)) + +Bugfixes 🐛 +---------- + - Poll system notifications on Android are not user friendly ([#4780](https://github.com/vector-im/element-android/issues/4780)) + - Add colors for shield vector drawable ([#4860](https://github.com/vector-im/element-android/issues/4860)) + - Support both stable and unstable prefixes for Events about Polls and Location ([#5340](https://github.com/vector-im/element-android/issues/5340)) + - Fix missing messages when loading messages forwards ([#5448](https://github.com/vector-im/element-android/issues/5448)) + - Fix presence indicator being aligned to the center of the room image ([#5489](https://github.com/vector-im/element-android/issues/5489)) + - Read receipt in wrong order ([#5514](https://github.com/vector-im/element-android/issues/5514)) + - Fix mentions using matrix.to rather than client defined permalink base url ([#5521](https://github.com/vector-im/element-android/issues/5521)) + - Fixes crash when tapping the timeline verification surround box instead of the buttons ([#5540](https://github.com/vector-im/element-android/issues/5540)) + - [Notification mode] Wrong mode is displayed when the mention only is selected on the web client ([#5547](https://github.com/vector-im/element-android/issues/5547)) + - Fix local echos not being shown when re-opening rooms ([#5551](https://github.com/vector-im/element-android/issues/5551)) + - Fix crash when closing a room while decrypting timeline events ([#5552](https://github.com/vector-im/element-android/issues/5552)) + - Fix sometimes read marker not properly updating ([#5564](https://github.com/vector-im/element-android/issues/5564)) + +In development 🚧 +---------------- + - Dynamically showing/hiding onboarding personalisation screens based on the users homeserver capabilities ([#5375](https://github.com/vector-im/element-android/issues/5375)) + - Introduces FTUE personalisation complete screen along with confetti celebration ([#5389](https://github.com/vector-im/element-android/issues/5389)) + +SDK API changes ⚠️ +------------------ + - Adds support for MSC3440, additional threads homeserver capabilities ([#5271](https://github.com/vector-im/element-android/issues/5271)) + +Other changes +------------- + - Improve headers UI in Rooms/Messages lists ([#4533](https://github.com/vector-im/element-android/issues/4533)) + - Number of unread messages on space badge now include number of unread DMs ([#5260](https://github.com/vector-im/element-android/issues/5260)) + - Amend spaces menu to be consistent with iOS version ([#5270](https://github.com/vector-im/element-android/issues/5270)) + - Selected space highlight changed in left panel ([#5346](https://github.com/vector-im/element-android/issues/5346)) + - [Rooms list] Do not suggest collapse the unique section ([#5347](https://github.com/vector-im/element-android/issues/5347)) + - Add analytics support for threads ([#5378](https://github.com/vector-im/element-android/issues/5378)) + - Add top margin before our first message ([#5384](https://github.com/vector-im/element-android/issues/5384)) + - Improved onboarding registration unit test coverage ([#5408](https://github.com/vector-im/element-android/issues/5408)) + - Adds stable room hierarchy endpoint with a fallback to the unstable one ([#5443](https://github.com/vector-im/element-android/issues/5443)) + - Use ColorPrimary for attachmentGalleryButton tint ([#5501](https://github.com/vector-im/element-android/issues/5501)) + - Added online presence indicator attribute online to match offline styling ([#5513](https://github.com/vector-im/element-android/issues/5513)) + - Add a presence sync enabling build config ([#5563](https://github.com/vector-im/element-android/issues/5563)) + - Show stickers on click ([#5572](https://github.com/vector-im/element-android/issues/5572)) + + Changes in Element v1.4.4 (2022-03-09) ====================================== diff --git a/changelog.d/4533.misc b/changelog.d/4533.misc deleted file mode 100644 index 1137a1c43c..0000000000 --- a/changelog.d/4533.misc +++ /dev/null @@ -1 +0,0 @@ -Improve headers UI in Rooms/Messages lists diff --git a/changelog.d/4780.bugfix b/changelog.d/4780.bugfix deleted file mode 100644 index 51eb1e4ad7..0000000000 --- a/changelog.d/4780.bugfix +++ /dev/null @@ -1 +0,0 @@ -Poll system notifications on Android are not user friendly \ No newline at end of file diff --git a/changelog.d/4860.bugfix b/changelog.d/4860.bugfix deleted file mode 100644 index 32049face4..0000000000 --- a/changelog.d/4860.bugfix +++ /dev/null @@ -1 +0,0 @@ -Add colors for shield vector drawable \ No newline at end of file diff --git a/changelog.d/5230.feature b/changelog.d/5230.feature deleted file mode 100644 index b333a3f2c7..0000000000 --- a/changelog.d/5230.feature +++ /dev/null @@ -1 +0,0 @@ -Thread timeline is now live and much faster especially for large or old threads \ No newline at end of file diff --git a/changelog.d/5232.feature b/changelog.d/5232.feature deleted file mode 100644 index 8f3bec97bd..0000000000 --- a/changelog.d/5232.feature +++ /dev/null @@ -1 +0,0 @@ -View all threads per room screen is now live when the home server supports threads \ No newline at end of file diff --git a/changelog.d/5260.misc b/changelog.d/5260.misc deleted file mode 100644 index 36812e2c83..0000000000 --- a/changelog.d/5260.misc +++ /dev/null @@ -1 +0,0 @@ -Number of unread messages on space badge now include number of unread DMs \ No newline at end of file diff --git a/changelog.d/5270.misc b/changelog.d/5270.misc deleted file mode 100644 index 9bbe41af59..0000000000 --- a/changelog.d/5270.misc +++ /dev/null @@ -1 +0,0 @@ -Amend spaces menu to be consistent with iOS version \ No newline at end of file diff --git a/changelog.d/5271.sdk b/changelog.d/5271.sdk deleted file mode 100644 index b73d97ee4f..0000000000 --- a/changelog.d/5271.sdk +++ /dev/null @@ -1 +0,0 @@ -Adds support for MSC3440, additional threads homeserver capabilities \ No newline at end of file diff --git a/changelog.d/5340.bugfix b/changelog.d/5340.bugfix deleted file mode 100644 index 4c53f0088c..0000000000 --- a/changelog.d/5340.bugfix +++ /dev/null @@ -1 +0,0 @@ -Support both stable and unstable prefixes for Events about Polls and Location \ No newline at end of file diff --git a/changelog.d/5346.misc b/changelog.d/5346.misc deleted file mode 100644 index f979c180ef..0000000000 --- a/changelog.d/5346.misc +++ /dev/null @@ -1 +0,0 @@ -Selected space highlight changed in left panel \ No newline at end of file diff --git a/changelog.d/5375.wip b/changelog.d/5375.wip deleted file mode 100644 index 352b2385a9..0000000000 --- a/changelog.d/5375.wip +++ /dev/null @@ -1 +0,0 @@ -Dynamically showing/hiding onboarding personalisation screens based on the users homeserver capabilities \ No newline at end of file diff --git a/changelog.d/5378.misc b/changelog.d/5378.misc deleted file mode 100644 index 1cf6da5e59..0000000000 --- a/changelog.d/5378.misc +++ /dev/null @@ -1 +0,0 @@ -Add analytics support for threads \ No newline at end of file diff --git a/changelog.d/5384.misc b/changelog.d/5384.misc deleted file mode 100644 index dca87422bb..0000000000 --- a/changelog.d/5384.misc +++ /dev/null @@ -1 +0,0 @@ -Add top margin before our first message diff --git a/changelog.d/5389.wip b/changelog.d/5389.wip deleted file mode 100644 index 089fe2da1a..0000000000 --- a/changelog.d/5389.wip +++ /dev/null @@ -1 +0,0 @@ -Introduces FTUE personalisation complete screen along with confetti celebration \ No newline at end of file diff --git a/changelog.d/5395.feature b/changelog.d/5395.feature deleted file mode 100644 index eb16c6cd81..0000000000 --- a/changelog.d/5395.feature +++ /dev/null @@ -1 +0,0 @@ -Add a custom view to display a picker for share location options diff --git a/changelog.d/5408.misc b/changelog.d/5408.misc deleted file mode 100644 index 3807ee1da8..0000000000 --- a/changelog.d/5408.misc +++ /dev/null @@ -1 +0,0 @@ -Improved onboarding registration unit test coverage \ No newline at end of file diff --git a/changelog.d/5417.feature b/changelog.d/5417.feature deleted file mode 100644 index 8b64f9fc7f..0000000000 --- a/changelog.d/5417.feature +++ /dev/null @@ -1 +0,0 @@ -Add ability to pin a location on map for sharing diff --git a/changelog.d/5426.feature b/changelog.d/5426.feature new file mode 100644 index 0000000000..2dee22f07a --- /dev/null +++ b/changelog.d/5426.feature @@ -0,0 +1 @@ +Allow scrolling position of Voice Message playback \ No newline at end of file diff --git a/changelog.d/5443.misc b/changelog.d/5443.misc deleted file mode 100644 index f9fd715403..0000000000 --- a/changelog.d/5443.misc +++ /dev/null @@ -1 +0,0 @@ -Adds stable room hierarchy endpoint with a fallback to the unstable one diff --git a/changelog.d/5448.bugfix b/changelog.d/5448.bugfix deleted file mode 100644 index c4e8fb4a49..0000000000 --- a/changelog.d/5448.bugfix +++ /dev/null @@ -1 +0,0 @@ -Fix missing messages when loading messages forwards diff --git a/changelog.d/5501.misc b/changelog.d/5501.misc deleted file mode 100644 index 6c46a105b7..0000000000 --- a/changelog.d/5501.misc +++ /dev/null @@ -1 +0,0 @@ -Use ColorPrimary for attachmentGalleryButton tint \ No newline at end of file diff --git a/changelog.d/5513.misc b/changelog.d/5513.misc deleted file mode 100644 index 767a9f1843..0000000000 --- a/changelog.d/5513.misc +++ /dev/null @@ -1 +0,0 @@ -Added online presence indicator attribute online to match offline styling diff --git a/changelog.d/5514.bugfix b/changelog.d/5514.bugfix deleted file mode 100644 index 0dfbca6e9a..0000000000 --- a/changelog.d/5514.bugfix +++ /dev/null @@ -1 +0,0 @@ -Read receipt in wrong order \ No newline at end of file diff --git a/changelog.d/5517.misc b/changelog.d/5517.misc new file mode 100644 index 0000000000..18269afcc6 --- /dev/null +++ b/changelog.d/5517.misc @@ -0,0 +1 @@ +Flattening the asynchronous onboarding state and passing all errors through the same pipeline \ No newline at end of file diff --git a/changelog.d/5521.bugfix b/changelog.d/5521.bugfix deleted file mode 100644 index 851396a770..0000000000 --- a/changelog.d/5521.bugfix +++ /dev/null @@ -1 +0,0 @@ -Fix mentions using matrix.to rather than client defined permalink base url diff --git a/changelog.d/5522.feature b/changelog.d/5522.feature deleted file mode 100644 index b50e8d1e60..0000000000 --- a/changelog.d/5522.feature +++ /dev/null @@ -1 +0,0 @@ -Poll Integration Tests \ No newline at end of file diff --git a/changelog.d/5536.feature b/changelog.d/5536.feature deleted file mode 100644 index bd0160f2fe..0000000000 --- a/changelog.d/5536.feature +++ /dev/null @@ -1 +0,0 @@ -Live location sharing: adding build config field and show permission dialog diff --git a/changelog.d/5540.bugfix b/changelog.d/5540.bugfix deleted file mode 100644 index 8887cf4074..0000000000 --- a/changelog.d/5540.bugfix +++ /dev/null @@ -1 +0,0 @@ -Fixes crash when tapping the timeline verification surround box instead of the buttons \ No newline at end of file diff --git a/changelog.d/5547.bugfix b/changelog.d/5547.bugfix deleted file mode 100644 index 3eb631902b..0000000000 --- a/changelog.d/5547.bugfix +++ /dev/null @@ -1 +0,0 @@ -[Notification mode] Wrong mode is displayed when the mention only is selected on the web client \ No newline at end of file diff --git a/changelog.d/5551.bugfix b/changelog.d/5551.bugfix deleted file mode 100644 index 22f9d51e18..0000000000 --- a/changelog.d/5551.bugfix +++ /dev/null @@ -1 +0,0 @@ -Fix local echos not being shown when re-opening rooms diff --git a/changelog.d/5552.bugfix b/changelog.d/5552.bugfix deleted file mode 100644 index 5061e642f0..0000000000 --- a/changelog.d/5552.bugfix +++ /dev/null @@ -1 +0,0 @@ -Fix crash when closing a room while decrypting timeline events diff --git a/changelog.d/5563.misc b/changelog.d/5563.misc deleted file mode 100644 index c0867365f6..0000000000 --- a/changelog.d/5563.misc +++ /dev/null @@ -1 +0,0 @@ -Add a presence sync enabling build config diff --git a/changelog.d/5571.feature b/changelog.d/5571.feature deleted file mode 100644 index 04b62b8940..0000000000 --- a/changelog.d/5571.feature +++ /dev/null @@ -1 +0,0 @@ -Live location sharing: Adding indicator view when enabled diff --git a/changelog.d/5572.misc b/changelog.d/5572.misc deleted file mode 100644 index d37d8fe07d..0000000000 --- a/changelog.d/5572.misc +++ /dev/null @@ -1,2 +0,0 @@ -Show stickers on click - diff --git a/dependencies.gradle b/dependencies.gradle index 1f2a08b6a6..7666a3bf9f 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -9,13 +9,13 @@ ext.versions = [ def gradle = "7.0.4" // Ref: https://kotlinlang.org/releases.html -def kotlin = "1.5.31" -def kotlinCoroutines = "1.5.2" +def kotlin = "1.6.0" +def kotlinCoroutines = "1.6.0" def dagger = "2.40.5" def retrofit = "2.9.0" def arrow = "0.8.2" def markwon = "4.6.2" -def moshi = "1.12.0" +def moshi = "1.13.0" def lifecycle = "2.4.0" def flowBinding = "1.2.0" def epoxy = "4.6.2" diff --git a/fastlane/metadata/android/en-US/changelogs/40104060.txt b/fastlane/metadata/android/en-US/changelogs/40104060.txt new file mode 100644 index 0000000000..1863bef5fb --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/40104060.txt @@ -0,0 +1,2 @@ +Main changes in this version: Thread timeline are now live and faster. Various bug fixes and stability improvements. +Full changelog: https://github.com/vector-im/element-android/releases/tag/v1.4.6 \ No newline at end of file diff --git a/fastlane/metadata/android/es-ES/changelogs/40104000.txt b/fastlane/metadata/android/es-ES/changelogs/40104000.txt new file mode 100644 index 0000000000..ea607fe19a --- /dev/null +++ b/fastlane/metadata/android/es-ES/changelogs/40104000.txt @@ -0,0 +1,2 @@ +Principales cambios de esta versión: primera implementación de los hilos de mensajes. Burbujas de mensajes. +Todos los cambios en: https://github.com/vector-im/element-android/releases/tag/v1.4.0 diff --git a/fastlane/metadata/android/es-ES/changelogs/40104020.txt b/fastlane/metadata/android/es-ES/changelogs/40104020.txt new file mode 100644 index 0000000000..8c2c78cb62 --- /dev/null +++ b/fastlane/metadata/android/es-ES/changelogs/40104020.txt @@ -0,0 +1,2 @@ +Principales cambios de esta versión: añadir @room, encuestas cerradas y muchos cambios menores más. +Todos los cambios en: https://github.com/vector-im/element-android/releases/tag/v1.4.2 diff --git a/fastlane/metadata/android/fa/changelogs/40104000.txt b/fastlane/metadata/android/fa/changelogs/40104000.txt new file mode 100644 index 0000000000..7beb79981f --- /dev/null +++ b/fastlane/metadata/android/fa/changelogs/40104000.txt @@ -0,0 +1,2 @@ +تغییرات اصلی در این نگارش: پیاده سازی نخستین پیام‌های رشته‌ای. حباب‌های پیام. +گزارش دگرگونی کامل: https://github.com/vector-im/element-android/releases/tag/v1.4.0 diff --git a/fastlane/metadata/android/fa/changelogs/40104020.txt b/fastlane/metadata/android/fa/changelogs/40104020.txt new file mode 100644 index 0000000000..6d5148220d --- /dev/null +++ b/fastlane/metadata/android/fa/changelogs/40104020.txt @@ -0,0 +1,2 @@ +تغییرات اصلی در این نگارش: افزودن پشتیبانی به ‪@room‬ و نظرسنجی‌های فاش نشده در کنار تغییرات کوچک دیگر. +گزارش دگرگونی کامل: https://github.com/vector-im/element-android/releases/tag/v1.4.2 diff --git a/fastlane/metadata/android/hu-HU/full_description.txt b/fastlane/metadata/android/hu-HU/full_description.txt index 0791eed7ba..b43613eb20 100644 --- a/fastlane/metadata/android/hu-HU/full_description.txt +++ b/fastlane/metadata/android/hu-HU/full_description.txt @@ -8,12 +8,13 @@ Az Element egy biztonságos üzenetküldő, és egy csapatmunka app, amely távo - Videochat, VoIP, és képernyőmegosztási lehetőséggel - Egyszerű integráció a kedvenc online kollaborációs eszközeiddel, projektkezelési eszközökkel, VoIP szolgáltatásokkal, és más csoportos üzenetküldő alkalmazásokkal -Element is completely different from other messaging and collaboration apps. It operates on Matrix, an open network for secure messaging and decentralized communication. It allows self-hosting to give users maximum ownership and control of their data and messages. +Az Element teljesen más, mint az összes többi üzenetküldő és kollaborációs alkalmazás. A biztonságos üzenetküldést és decentralizált kommunikációt biztosító Matrix platformot használja. Akár egyénileg üzemeltetett szervereket is lehet használni az adatok teljes kontrollálása érdekében. -Privacy and encrypted messaging -Element protects you from unwanted ads, data mining and walled gardens. It also secures all your data, one-to-one video and voice communication through end-to-end encryption and cross-signed device verification. +Magánszféra és titkosított csevegés +Az Element megvéd a nemkívánatos hirdetésektől, adatbányászattól, és a zárt platformoktól. Ezeken felül biztonságban tartja az összes adatod és 1:1 hívásod a végponti titkosításnak és az eszközök-közti hitelesítésnek köszönhetően. + +Az Element átadja neked az irányítást a magánszférád felett, miközben lehetővé teszi, hogy biztonságosan kommunikálj bárkivel a Matrix hálózatban, vagy a többi üzleti kommunikációs eszközt használókkal, az olyan appok integrálásának köszönhetően, mint például a Slack. -Element gives you control over your privacy while allowing you to communicate securely with anyone on the Matrix network, or other business collaboration tools by integrating with apps such as Slack. Element can be self-hosted To allow more control of your sensitive data and conversations, Element can be self-hosted or you can choose any Matrix-based host - the standard for open source, decentralized communication. Element gives you privacy, security compliance and integration flexibility. diff --git a/fastlane/metadata/android/ru-RU/changelogs/40104000.txt b/fastlane/metadata/android/ru-RU/changelogs/40104000.txt new file mode 100644 index 0000000000..f6bf34b3cc --- /dev/null +++ b/fastlane/metadata/android/ru-RU/changelogs/40104000.txt @@ -0,0 +1,2 @@ +Основные изменения в этой версии: Начальная реализация веток сообщений. Сообщения пузыри. +Полный список изменений: https://github.com/vector-im/element-android/releases/tag/v1.4.0 diff --git a/fastlane/metadata/android/ru-RU/changelogs/40104020.txt b/fastlane/metadata/android/ru-RU/changelogs/40104020.txt new file mode 100644 index 0000000000..864bd03d5e --- /dev/null +++ b/fastlane/metadata/android/ru-RU/changelogs/40104020.txt @@ -0,0 +1,2 @@ +Основные изменения в этой версии: добавлена поддержка @room и нераскрытых опросов, а также множество других мелких изменений. +Полный список изменений: https://github.com/vector-im/element-android/releases/tag/v1.4.2 diff --git a/library/jsonviewer/src/main/java/org/billcarsonfr/jsonviewer/JSonViewerEpoxyController.kt b/library/jsonviewer/src/main/java/org/billcarsonfr/jsonviewer/JSonViewerEpoxyController.kt index 96b5a9c997..9f8093f801 100644 --- a/library/jsonviewer/src/main/java/org/billcarsonfr/jsonviewer/JSonViewerEpoxyController.kt +++ b/library/jsonviewer/src/main/java/org/billcarsonfr/jsonviewer/JSonViewerEpoxyController.kt @@ -20,13 +20,12 @@ import android.content.Context import android.view.View import com.airbnb.epoxy.TypedEpoxyController import com.airbnb.mvrx.Fail -import com.airbnb.mvrx.Success import im.vector.lib.core.utils.epoxy.charsequence.toEpoxyCharSequence import me.gujun.android.span.Span import me.gujun.android.span.span internal class JSonViewerEpoxyController(private val context: Context) : - TypedEpoxyController() { + TypedEpoxyController() { private var styleProvider: JSonViewerStyleProvider = JSonViewerStyleProvider.default(context) @@ -44,10 +43,8 @@ internal class JSonViewerEpoxyController(private val context: Context) : text(async.error.localizedMessage?.toEpoxyCharSequence()) } } - is Success -> { - val model = data.root.invoke() - - model?.let { + else -> { + async.invoke()?.let { buildRec(it, 0, "") } } @@ -55,9 +52,9 @@ internal class JSonViewerEpoxyController(private val context: Context) : } private fun buildRec( - model: JSonViewerModel, - depth: Int, - idBase: String + model: JSonViewerModel, + depth: Int, + idBase: String ) { val host = this val id = "$idBase/${model.key ?: model.index}_${model.isExpanded}}" @@ -74,34 +71,34 @@ internal class JSonViewerEpoxyController(private val context: Context) : id(id + "_sum") depth(depth) text( - span { - if (model.key != null) { - span("\"${model.key}\"") { - textColor = host.styleProvider.keyColor - } - span(" : ") { - textColor = host.styleProvider.baseColor - } - } - if (model.index != null) { - span("${model.index}") { - textColor = host.styleProvider.secondaryColor - } - span(" : ") { - textColor = host.styleProvider.baseColor - } - } span { - +"{+${model.keys.size}}" - textColor = host.styleProvider.baseColor - } - }.toEpoxyCharSequence() + if (model.key != null) { + span("\"${model.key}\"") { + textColor = host.styleProvider.keyColor + } + span(" : ") { + textColor = host.styleProvider.baseColor + } + } + if (model.index != null) { + span("${model.index}") { + textColor = host.styleProvider.secondaryColor + } + span(" : ") { + textColor = host.styleProvider.baseColor + } + } + span { + +"{+${model.keys.size}}" + textColor = host.styleProvider.baseColor + } + }.toEpoxyCharSequence() ) itemClickListener(View.OnClickListener { host.itemClicked(model) }) } } } - is JSonViewerArray -> { + is JSonViewerArray -> { if (model.isExpanded) { open(id, model.key, model.index, depth, false, model) model.items.forEach { @@ -113,6 +110,38 @@ internal class JSonViewerEpoxyController(private val context: Context) : id(id + "_sum") depth(depth) text( + span { + if (model.key != null) { + span("\"${model.key}\"") { + textColor = host.styleProvider.keyColor + } + span(" : ") { + textColor = host.styleProvider.baseColor + } + } + if (model.index != null) { + span("${model.index}") { + textColor = host.styleProvider.secondaryColor + } + span(" : ") { + textColor = host.styleProvider.baseColor + } + } + span { + +"[+${model.items.size}]" + textColor = host.styleProvider.baseColor + } + }.toEpoxyCharSequence() + ) + itemClickListener(View.OnClickListener { host.itemClicked(model) }) + } + } + } + is JSonViewerLeaf -> { + valueItem { + id(id) + depth(depth) + text( span { if (model.key != null) { span("\"${model.key}\"") { @@ -122,6 +151,7 @@ internal class JSonViewerEpoxyController(private val context: Context) : textColor = host.styleProvider.baseColor } } + if (model.index != null) { span("${model.index}") { textColor = host.styleProvider.secondaryColor @@ -130,41 +160,8 @@ internal class JSonViewerEpoxyController(private val context: Context) : textColor = host.styleProvider.baseColor } } - span { - +"[+${model.items.size}]" - textColor = host.styleProvider.baseColor - } + append(host.valueToSpan(model)) }.toEpoxyCharSequence() - ) - itemClickListener(View.OnClickListener { host.itemClicked(model) }) - } - } - } - is JSonViewerLeaf -> { - valueItem { - id(id) - depth(depth) - text( - span { - if (model.key != null) { - span("\"${model.key}\"") { - textColor = host.styleProvider.keyColor - } - span(" : ") { - textColor = host.styleProvider.baseColor - } - } - - if (model.index != null) { - span("${model.index}") { - textColor = host.styleProvider.secondaryColor - } - span(" : ") { - textColor = host.styleProvider.baseColor - } - } - append(host.valueToSpan(model)) - }.toEpoxyCharSequence() ) copyValue(model.stringRes) } @@ -175,12 +172,12 @@ internal class JSonViewerEpoxyController(private val context: Context) : private fun valueToSpan(leaf: JSonViewerLeaf): Span { val host = this return when (leaf.type) { - JSONType.STRING -> { + JSONType.STRING -> { span("\"${leaf.stringRes}\"") { textColor = host.styleProvider.stringColor } } - JSONType.NUMBER -> { + JSONType.NUMBER -> { span(leaf.stringRes) { textColor = host.styleProvider.numberColor } @@ -190,7 +187,7 @@ internal class JSonViewerEpoxyController(private val context: Context) : textColor = host.styleProvider.booleanColor } } - JSONType.NULL -> { + JSONType.NULL -> { span("null") { textColor = host.styleProvider.booleanColor } @@ -199,42 +196,42 @@ internal class JSonViewerEpoxyController(private val context: Context) : } private fun open( - id: String, - key: String?, - index: Int?, - depth: Int, - isObject: Boolean = true, - composed: JSonViewerModel + id: String, + key: String?, + index: Int?, + depth: Int, + isObject: Boolean = true, + composed: JSonViewerModel ) { val host = this valueItem { id("${id}_Open") depth(depth) text( - span { - if (key != null) { - span("\"$key\"") { - textColor = host.styleProvider.keyColor + span { + if (key != null) { + span("\"$key\"") { + textColor = host.styleProvider.keyColor + } + span(" : ") { + textColor = host.styleProvider.baseColor + } } - span(" : ") { - textColor = host.styleProvider.baseColor + if (index != null) { + span("$index") { + textColor = host.styleProvider.secondaryColor + } + span(" : ") { + textColor = host.styleProvider.baseColor + } } - } - if (index != null) { - span("$index") { + span("- ") { textColor = host.styleProvider.secondaryColor } - span(" : ") { + span("{".takeIf { isObject } ?: "[") { textColor = host.styleProvider.baseColor } - } - span("- ") { - textColor = host.styleProvider.secondaryColor - } - span("{".takeIf { isObject } ?: "[") { - textColor = host.styleProvider.baseColor - } - }.toEpoxyCharSequence() + }.toEpoxyCharSequence() ) itemClickListener(View.OnClickListener { host.itemClicked(composed) }) } @@ -251,10 +248,10 @@ internal class JSonViewerEpoxyController(private val context: Context) : id("${id}_Close") depth(depth) text( - span { - text = "}".takeIf { isObject } ?: "]" - textColor = host.styleProvider.baseColor - }.toEpoxyCharSequence() + span { + text = "}".takeIf { isObject } ?: "]" + textColor = host.styleProvider.baseColor + }.toEpoxyCharSequence() ) } } diff --git a/library/ui-styles/build.gradle b/library/ui-styles/build.gradle index cee58414c7..0ac513b252 100644 --- a/library/ui-styles/build.gradle +++ b/library/ui-styles/build.gradle @@ -60,6 +60,4 @@ dependencies { implementation 'com.github.vector-im:PFLockScreen-Android:1.0.0-beta12' // dialpad dimen implementation 'im.dlg:android-dialer:1.2.5' - // AudioRecordView attr - implementation 'com.github.Armen101:AudioRecordView:1.0.5' } \ No newline at end of file diff --git a/library/ui-styles/src/main/res/values/stylable_audio_waveform_view.xml b/library/ui-styles/src/main/res/values/stylable_audio_waveform_view.xml new file mode 100644 index 0000000000..f2c703764a --- /dev/null +++ b/library/ui-styles/src/main/res/values/stylable_audio_waveform_view.xml @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/library/ui-styles/src/main/res/values/styles_voice_message.xml b/library/ui-styles/src/main/res/values/styles_voice_message.xml index 2e87353303..81d2e7581d 100644 --- a/library/ui-styles/src/main/res/values/styles_voice_message.xml +++ b/library/ui-styles/src/main/res/values/styles_voice_message.xml @@ -2,14 +2,14 @@ \ No newline at end of file diff --git a/matrix-sdk-android/build.gradle b/matrix-sdk-android/build.gradle index 2b2c38e22a..1e2eda166f 100644 --- a/matrix-sdk-android/build.gradle +++ b/matrix-sdk-android/build.gradle @@ -31,7 +31,7 @@ android { // that the app's state is completely cleared between tests. testInstrumentationRunnerArguments clearPackageData: 'true' - buildConfigField "String", "SDK_VERSION", "\"1.4.6\"" + buildConfigField "String", "SDK_VERSION", "\"1.4.8\"" buildConfigField "String", "GIT_SDK_REVISION", "\"${gitRevision()}\"" buildConfigField "String", "GIT_SDK_REVISION_UNIX_DATE", "\"${gitRevisionUnixDate()}\"" diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/MatrixConfiguration.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/MatrixConfiguration.kt index a97e7d8cbe..c4bc289b75 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/MatrixConfiguration.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/MatrixConfiguration.kt @@ -64,7 +64,11 @@ data class MatrixConfiguration( /** * True to enable presence information sync (if available). False to disable regardless of server setting. */ - val presenceSyncEnabled: Boolean = true + val presenceSyncEnabled: Boolean = true, + /** + * Thread messages default enable/disabled value + */ + val threadMessagesEnabledDefault: Boolean = false, ) { /** diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/failure/Extensions.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/failure/Extensions.kt index aabe6e0d06..89b4a343dd 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/failure/Extensions.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/failure/Extensions.kt @@ -58,12 +58,36 @@ fun Throwable.getRetryDelay(defaultValue: Long): Long { ?: defaultValue } +fun Throwable.isUsernameInUse(): Boolean { + return this is Failure.ServerError && error.code == MatrixError.M_USER_IN_USE +} + +fun Throwable.isInvalidUsername(): Boolean { + return this is Failure.ServerError && + error.code == MatrixError.M_INVALID_USERNAME +} + fun Throwable.isInvalidPassword(): Boolean { return this is Failure.ServerError && error.code == MatrixError.M_FORBIDDEN && error.message == "Invalid password" } +fun Throwable.isRegistrationDisabled(): Boolean { + return this is Failure.ServerError && error.code == MatrixError.M_FORBIDDEN && + httpCode == HttpsURLConnection.HTTP_FORBIDDEN +} + +fun Throwable.isWeakPassword(): Boolean { + return this is Failure.ServerError && error.code == MatrixError.M_WEAK_PASSWORD +} + +fun Throwable.isLoginEmailUnknown(): Boolean { + return this is Failure.ServerError && + error.code == MatrixError.M_FORBIDDEN && + error.message.isEmpty() +} + fun Throwable.isInvalidUIAAuth(): Boolean { return this is Failure.ServerError && error.code == MatrixError.M_FORBIDDEN && @@ -104,8 +128,8 @@ fun Throwable.isRegistrationAvailabilityError(): Boolean { return this is Failure.ServerError && httpCode == HttpsURLConnection.HTTP_BAD_REQUEST && /* 400 */ (error.code == MatrixError.M_USER_IN_USE || - error.code == MatrixError.M_INVALID_USERNAME || - error.code == MatrixError.M_EXCLUSIVE) + error.code == MatrixError.M_INVALID_USERNAME || + error.code == MatrixError.M_EXCLUSIVE) } /** diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/qrcode/DefaultQrCodeVerificationTransaction.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/qrcode/DefaultQrCodeVerificationTransaction.kt index 829e066bf3..90ede18dc8 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/qrcode/DefaultQrCodeVerificationTransaction.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/qrcode/DefaultQrCodeVerificationTransaction.kt @@ -29,7 +29,6 @@ import org.matrix.android.sdk.internal.crypto.crosssigning.fromBase64Safe import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore import org.matrix.android.sdk.internal.crypto.verification.DefaultVerificationTransaction import org.matrix.android.sdk.internal.crypto.verification.ValidVerificationInfoStart -import org.matrix.android.sdk.internal.util.exhaustive import timber.log.Timber internal class DefaultQrCodeVerificationTransaction( @@ -129,7 +128,7 @@ internal class DefaultQrCodeVerificationTransaction( // Nothing special here, we will send a reciprocate start event, and then the other session will trust it's view of the MSK } } - }.exhaustive + } val toVerifyDeviceIds = mutableListOf() @@ -174,7 +173,7 @@ internal class DefaultQrCodeVerificationTransaction( Unit } } - }.exhaustive + } if (!canTrustOtherUserMasterKey && toVerifyDeviceIds.isEmpty()) { // Nothing to verify @@ -272,6 +271,7 @@ internal class DefaultQrCodeVerificationTransaction( // I now know that i can trust my MSK trust(true, emptyList(), true) } + null -> Unit } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/lightweight/LightweightSettingsStorage.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/lightweight/LightweightSettingsStorage.kt index 700b94a985..069e539e2c 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/lightweight/LightweightSettingsStorage.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/lightweight/LightweightSettingsStorage.kt @@ -19,15 +19,19 @@ package org.matrix.android.sdk.internal.database.lightweight import android.content.Context import androidx.core.content.edit import androidx.preference.PreferenceManager +import org.matrix.android.sdk.api.MatrixConfiguration import javax.inject.Inject /** * The purpose of this class is to provide an alternative and lightweight way to store settings/data - * on the sdi without using the database. This should be used just for sdk/user preferences and + * on the sdk without using the database. This should be used just for sdk/user preferences and * not for large data sets */ -class LightweightSettingsStorage @Inject constructor(context: Context) { +class LightweightSettingsStorage @Inject constructor( + context: Context, + private val matrixConfiguration: MatrixConfiguration +) { private val sdkDefaultPrefs = PreferenceManager.getDefaultSharedPreferences(context.applicationContext) @@ -38,7 +42,7 @@ class LightweightSettingsStorage @Inject constructor(context: Context) { } fun areThreadMessagesEnabled(): Boolean { - return sdkDefaultPrefs.getBoolean(MATRIX_SDK_SETTINGS_THREAD_MESSAGES_ENABLED, false) + return sdkDefaultPrefs.getBoolean(MATRIX_SDK_SETTINGS_THREAD_MESSAGES_ENABLED, matrixConfiguration.threadMessagesEnabledDefault) } companion object { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/di/WorkManagerProvider.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/di/WorkManagerProvider.kt index 7d004bc5c0..fedd7d05f9 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/di/WorkManagerProvider.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/di/WorkManagerProvider.kt @@ -80,8 +80,8 @@ internal class WorkManagerProvider @Inject constructor( workManager.enqueue(checkWorkerRequest) val checkWorkerLiveState = workManager.getWorkInfoByIdLiveData(checkWorkerRequest.id) val observer = object : Observer { - override fun onChanged(workInfo: WorkInfo) { - if (workInfo.state.isFinished) { + override fun onChanged(workInfo: WorkInfo?) { + if (workInfo?.state?.isFinished == true) { checkWorkerLiveState.removeObserver(this) if (workInfo.state == WorkInfo.State.FAILED) { throw RuntimeException("MatrixWorkerFactory is not being set on your worker configuration.\n" + diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/threads/FetchThreadSummariesTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/threads/FetchThreadSummariesTask.kt index d316eed691..b596f2288e 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/threads/FetchThreadSummariesTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/threads/FetchThreadSummariesTask.kt @@ -44,7 +44,7 @@ internal interface FetchThreadSummariesTask : Task query.equalTo(RoomSummaryEntityFields.IS_DIRECT, false) RoomCategoryFilter.ONLY_WITH_NOTIFICATIONS -> query.greaterThan(RoomSummaryEntityFields.NOTIFICATION_COUNT, 0) RoomCategoryFilter.ALL -> Unit // nop + null -> Unit } // Timber.w("VAL: activeSpaceId : ${queryParams.activeSpaceId}") diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineChunk.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineChunk.kt index 8a7078fdf9..c8f2132ae6 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineChunk.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineChunk.kt @@ -83,11 +83,15 @@ internal class TimelineChunk(private val chunkEntity: ChunkEntity, isLastBackward.set(chunkEntity.isLastBackward) } if (changeSet.isFieldChanged(ChunkEntityFields.NEXT_CHUNK.`$`)) { - nextChunk = createTimelineChunk(chunkEntity.nextChunk) + nextChunk = createTimelineChunk(chunkEntity.nextChunk).also { + it?.prevChunk = this + } nextChunkLatch?.complete(Unit) } if (changeSet.isFieldChanged(ChunkEntityFields.PREV_CHUNK.`$`)) { - prevChunk = createTimelineChunk(chunkEntity.prevChunk) + prevChunk = createTimelineChunk(chunkEntity.prevChunk).also { + it?.nextChunk = this + } prevChunkLatch?.complete(Unit) } } @@ -194,7 +198,9 @@ internal class TimelineChunk(private val chunkEntity: ChunkEntity, when { nextChunkEntity != null -> { if (nextChunk == null) { - nextChunk = createTimelineChunk(nextChunkEntity) + nextChunk = createTimelineChunk(nextChunkEntity).also { + it?.prevChunk = this + } } nextChunk?.loadMore(offsetCount, direction, fetchFromServerIfNeeded) ?: LoadMoreResult.FAILURE } @@ -210,7 +216,9 @@ internal class TimelineChunk(private val chunkEntity: ChunkEntity, when { prevChunkEntity != null -> { if (prevChunk == null) { - prevChunk = createTimelineChunk(prevChunkEntity) + prevChunk = createTimelineChunk(prevChunkEntity).also { + it?.nextChunk = this + } } prevChunk?.loadMore(offsetCount, direction, fetchFromServerIfNeeded) ?: LoadMoreResult.FAILURE } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/Exhaustive.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/Exhaustive.kt deleted file mode 100644 index 097bdaf153..0000000000 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/Exhaustive.kt +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Copyright 2020 The Matrix.org Foundation C.I.C. - * - * 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 org.matrix.android.sdk.internal.util - -// Trick to ensure that when block is exhaustive -internal val T.exhaustive: T get() = this diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/pushers/DefaultAddPusherTaskTest.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/pushers/DefaultAddPusherTaskTest.kt index c8be0f5487..31fd86fe65 100644 --- a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/pushers/DefaultAddPusherTaskTest.kt +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/pushers/DefaultAddPusherTaskTest.kt @@ -16,7 +16,8 @@ package org.matrix.android.sdk.internal.session.pushers -import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.runTest import org.amshove.kluent.internal.assertFailsWith import org.amshove.kluent.shouldBeEqualTo import org.junit.Test @@ -39,6 +40,7 @@ private val A_JSON_PUSHER = JsonPusher( data = JsonPusherData(brand = "Element") ) +@ExperimentalCoroutinesApi class DefaultAddPusherTaskTest { private val pushersAPI = FakePushersAPI() @@ -55,7 +57,7 @@ class DefaultAddPusherTaskTest { fun `given no persisted pusher when adding Pusher then updates api and inserts result with Registered state`() { monarchy.givenWhereReturns(result = null) - runBlocking { addPusherTask.execute(AddPusherTask.Params(A_JSON_PUSHER)) } + runTest { addPusherTask.execute(AddPusherTask.Params(A_JSON_PUSHER)) } pushersAPI.verifySetPusher(A_JSON_PUSHER) monarchy.verifyInsertOrUpdate { @@ -70,7 +72,7 @@ class DefaultAddPusherTaskTest { val realmResult = PusherEntity(appDisplayName = null) monarchy.givenWhereReturns(result = realmResult) - runBlocking { addPusherTask.execute(AddPusherTask.Params(A_JSON_PUSHER)) } + runTest { addPusherTask.execute(AddPusherTask.Params(A_JSON_PUSHER)) } pushersAPI.verifySetPusher(A_JSON_PUSHER) @@ -85,7 +87,7 @@ class DefaultAddPusherTaskTest { pushersAPI.givenSetPusherErrors(SocketException()) assertFailsWith { - runBlocking { addPusherTask.execute(AddPusherTask.Params(A_JSON_PUSHER)) } + runTest { addPusherTask.execute(AddPusherTask.Params(A_JSON_PUSHER)) } } realmResult.state shouldBeEqualTo PusherState.FAILED_TO_REGISTER @@ -97,7 +99,7 @@ class DefaultAddPusherTaskTest { pushersAPI.givenSetPusherErrors(SocketException()) assertFailsWith { - runBlocking { addPusherTask.execute(AddPusherTask.Params(A_JSON_PUSHER)) } + runTest { addPusherTask.execute(AddPusherTask.Params(A_JSON_PUSHER)) } } } } diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/space/DefaultResolveSpaceInfoTaskTest.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/space/DefaultResolveSpaceInfoTaskTest.kt index f80c0f06d0..7203f89629 100644 --- a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/space/DefaultResolveSpaceInfoTaskTest.kt +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/space/DefaultResolveSpaceInfoTaskTest.kt @@ -17,7 +17,7 @@ package org.matrix.android.sdk.internal.session.space import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.test.runBlockingTest +import kotlinx.coroutines.test.runTest import okhttp3.ResponseBody.Companion.toResponseBody import org.amshove.kluent.shouldBeEqualTo import org.junit.Test @@ -35,7 +35,7 @@ internal class DefaultResolveSpaceInfoTaskTest { private val resolveSpaceInfoTask = DefaultResolveSpaceInfoTask(spaceApi.instance, globalErrorReceiver) @Test - fun `given stable endpoint works, when execute, then return stable api data`() = runBlockingTest { + fun `given stable endpoint works, when execute, then return stable api data`() = runTest { spaceApi.givenStableEndpointReturns(response) val result = resolveSpaceInfoTask.execute(spaceApi.params) @@ -44,7 +44,7 @@ internal class DefaultResolveSpaceInfoTaskTest { } @Test - fun `given stable endpoint fails, when execute, then fallback to unstable endpoint`() = runBlockingTest { + fun `given stable endpoint fails, when execute, then fallback to unstable endpoint`() = runTest { spaceApi.givenStableEndpointThrows(httpException) spaceApi.givenUnstableEndpointReturns(response) diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/task/CoroutineSequencersTest.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/task/CoroutineSequencersTest.kt index 0abca8bee3..149b964fd2 100644 --- a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/task/CoroutineSequencersTest.kt +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/task/CoroutineSequencersTest.kt @@ -21,7 +21,7 @@ import kotlinx.coroutines.asCoroutineDispatcher import kotlinx.coroutines.delay import kotlinx.coroutines.joinAll import kotlinx.coroutines.launch -import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.test.runTest import org.junit.Assert.assertEquals import org.junit.Test import org.matrix.android.sdk.MatrixTest @@ -51,7 +51,7 @@ class CoroutineSequencersTest : MatrixTest { .also { results.add(it) } } ) - runBlocking { + runTest { jobs.joinAll() } assertEquals(3, results.size) @@ -81,7 +81,7 @@ class CoroutineSequencersTest : MatrixTest { .also { results.add(it) } } ) - runBlocking { + runTest { jobs.joinAll() } assertEquals(3, results.size) @@ -109,7 +109,7 @@ class CoroutineSequencersTest : MatrixTest { ) // We are canceling the second job jobs[1].cancel() - runBlocking { + runTest { jobs.joinAll() } assertEquals(2, results.size) diff --git a/tools/templates/ElementFeature/root/src/app_package/ViewModel.kt.ftl b/tools/templates/ElementFeature/root/src/app_package/ViewModel.kt.ftl index 64e6a0f83f..62b1f40df5 100644 --- a/tools/templates/ElementFeature/root/src/app_package/ViewModel.kt.ftl +++ b/tools/templates/ElementFeature/root/src/app_package/ViewModel.kt.ftl @@ -7,7 +7,6 @@ import com.airbnb.mvrx.ViewModelContext import dagger.assisted.Assisted import dagger.assisted.AssistedInject import dagger.assisted.AssistedFactory -import im.vector.app.core.extensions.exhaustive import im.vector.app.core.platform.VectorViewModel <#if createViewEvents> @@ -42,6 +41,6 @@ class ${viewModelClass} @AssistedInject constructor(@Assisted initialState: ${vi override fun handle(action: ${actionClass}) { when (action) { - }.exhaustive + } } } diff --git a/vector-config/src/main/res/values/config-settings.xml b/vector-config/src/main/res/values/config-settings.xml index 0121ee9ae7..40fc68bbae 100755 --- a/vector-config/src/main/res/values/config-settings.xml +++ b/vector-config/src/main/res/values/config-settings.xml @@ -36,8 +36,9 @@ + false - + diff --git a/vector/build.gradle b/vector/build.gradle index aeaad19e02..9f8471bc18 100644 --- a/vector/build.gradle +++ b/vector/build.gradle @@ -18,7 +18,7 @@ ext.versionMinor = 4 // Note: even values are reserved for regular release, odd values for hotfix release. // When creating a hotfix, you should decrease the value, since the current value // is the value for the next regular release. -ext.versionPatch = 6 +ext.versionPatch = 8 static def getGitTimestamp() { def cmd = 'git show -s --format=%ct' @@ -411,7 +411,6 @@ dependencies { implementation 'jp.wasabeef:glide-transformations:4.3.0' implementation 'com.github.vector-im:PFLockScreen-Android:1.0.0-beta12' implementation 'com.github.hyuwah:DraggableView:1.0.0' - implementation 'com.github.Armen101:AudioRecordView:1.0.5' // Custom Tab implementation 'androidx.browser:browser:1.4.0' diff --git a/vector/src/androidTest/java/im/vector/app/ui/robot/OnboardingRobot.kt b/vector/src/androidTest/java/im/vector/app/ui/robot/OnboardingRobot.kt index b3bb5172e8..d051488ad7 100644 --- a/vector/src/androidTest/java/im/vector/app/ui/robot/OnboardingRobot.kt +++ b/vector/src/androidTest/java/im/vector/app/ui/robot/OnboardingRobot.kt @@ -21,6 +21,7 @@ import androidx.test.espresso.Espresso.onView import androidx.test.espresso.Espresso.pressBack import androidx.test.espresso.matcher.ViewMatchers.isRoot import androidx.test.espresso.matcher.ViewMatchers.withId +import androidx.test.espresso.matcher.ViewMatchers.withText import com.adevinta.android.barista.assertion.BaristaEnabledAssertions.assertDisabled import com.adevinta.android.barista.assertion.BaristaEnabledAssertions.assertEnabled import com.adevinta.android.barista.assertion.BaristaVisibilityAssertions.assertDisplayed @@ -55,6 +56,8 @@ class OnboardingRobot { fun createAccount(userId: String, password: String = "password", homeServerUrl: String = "http://10.0.2.2:8080") { initSession(true, userId, password, homeServerUrl) + waitUntilViewVisible(withText(R.string.ftue_account_created_congratulations_title)) + clickOn(R.string.ftue_account_created_take_me_home) } fun login(userId: String, password: String = "password", homeServerUrl: String = "http://10.0.2.2:8080") { diff --git a/vector/src/debug/java/im/vector/app/features/debug/analytics/DebugAnalyticsViewModel.kt b/vector/src/debug/java/im/vector/app/features/debug/analytics/DebugAnalyticsViewModel.kt index 03e416813a..e007e61c1c 100644 --- a/vector/src/debug/java/im/vector/app/features/debug/analytics/DebugAnalyticsViewModel.kt +++ b/vector/src/debug/java/im/vector/app/features/debug/analytics/DebugAnalyticsViewModel.kt @@ -22,7 +22,6 @@ import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import im.vector.app.core.di.MavericksAssistedViewModelFactory import im.vector.app.core.di.hiltMavericksViewModelFactory -import im.vector.app.core.extensions.exhaustive import im.vector.app.core.platform.EmptyViewEvents import im.vector.app.core.platform.VectorViewModel import im.vector.app.features.analytics.store.AnalyticsStore @@ -53,7 +52,7 @@ class DebugAnalyticsViewModel @AssistedInject constructor( override fun handle(action: DebugAnalyticsViewActions) { when (action) { DebugAnalyticsViewActions.ResetAnalyticsOptInDisplayed -> handleResetAnalyticsOptInDisplayed() - }.exhaustive + } } private fun handleResetAnalyticsOptInDisplayed() { diff --git a/vector/src/debug/java/im/vector/app/features/debug/settings/DebugPrivateSettingsViewModel.kt b/vector/src/debug/java/im/vector/app/features/debug/settings/DebugPrivateSettingsViewModel.kt index 62871023bc..e469dbacda 100644 --- a/vector/src/debug/java/im/vector/app/features/debug/settings/DebugPrivateSettingsViewModel.kt +++ b/vector/src/debug/java/im/vector/app/features/debug/settings/DebugPrivateSettingsViewModel.kt @@ -22,7 +22,6 @@ import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import im.vector.app.core.di.MavericksAssistedViewModelFactory import im.vector.app.core.di.hiltMavericksViewModelFactory -import im.vector.app.core.extensions.exhaustive import im.vector.app.core.platform.EmptyViewEvents import im.vector.app.core.platform.VectorViewModel import im.vector.app.features.debug.features.DebugVectorOverrides @@ -71,7 +70,7 @@ class DebugPrivateSettingsViewModel @AssistedInject constructor( is DebugPrivateSettingsViewActions.SetForceLoginFallbackEnabled -> handleSetForceLoginFallbackEnabled(action) is SetDisplayNameCapabilityOverride -> handSetDisplayNameCapabilityOverride(action) is SetAvatarCapabilityOverride -> handSetAvatarCapabilityOverride(action) - }.exhaustive + } } private fun handleSetDialPadVisibility(action: DebugPrivateSettingsViewActions.SetDialPadVisibility) { diff --git a/vector/src/main/assets/open_source_licenses.html b/vector/src/main/assets/open_source_licenses.html index 2c25606f57..0bead1f826 100755 --- a/vector/src/main/assets/open_source_licenses.html +++ b/vector/src/main/assets/open_source_licenses.html @@ -437,11 +437,6 @@ THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Copyright (c) 2017-present, dialog LLC <info@dlg.im> -
  • - Armen101 / AudioRecordView -
    - Copyright 2019 Armen Gevorgyan -
  •  Apache License
    diff --git a/vector/src/main/java/im/vector/app/core/di/SingletonModule.kt b/vector/src/main/java/im/vector/app/core/di/SingletonModule.kt
    index e3a84f95de..fdd6e3c2ba 100644
    --- a/vector/src/main/java/im/vector/app/core/di/SingletonModule.kt
    +++ b/vector/src/main/java/im/vector/app/core/di/SingletonModule.kt
    @@ -46,6 +46,7 @@ import im.vector.app.features.navigation.Navigator
     import im.vector.app.features.pin.PinCodeStore
     import im.vector.app.features.pin.SharedPrefPinCodeStore
     import im.vector.app.features.room.VectorRoomDisplayNameFallbackProvider
    +import im.vector.app.features.settings.VectorPreferences
     import im.vector.app.features.ui.SharedPreferencesUiStateRepository
     import im.vector.app.features.ui.UiStateRepository
     import kotlinx.coroutines.CoroutineScope
    @@ -113,10 +114,13 @@ object VectorStaticModule {
         }
     
         @Provides
    -    fun providesMatrixConfiguration(vectorRoomDisplayNameFallbackProvider: VectorRoomDisplayNameFallbackProvider): MatrixConfiguration {
    +    fun providesMatrixConfiguration(
    +            vectorPreferences: VectorPreferences,
    +            vectorRoomDisplayNameFallbackProvider: VectorRoomDisplayNameFallbackProvider): MatrixConfiguration {
             return MatrixConfiguration(
                     applicationFlavor = BuildConfig.FLAVOR_DESCRIPTION,
                     roomDisplayNameFallbackProvider = vectorRoomDisplayNameFallbackProvider,
    +                threadMessagesEnabledDefault = vectorPreferences.areThreadMessagesEnabled(),
                     presenceSyncEnabled = BuildConfig.PRESENCE_SYNC_ENABLED
             )
         }
    diff --git a/vector/src/main/java/im/vector/app/core/extensions/Exhaustive.kt b/vector/src/main/java/im/vector/app/core/extensions/Exhaustive.kt
    deleted file mode 100644
    index 158ea84f0c..0000000000
    --- a/vector/src/main/java/im/vector/app/core/extensions/Exhaustive.kt
    +++ /dev/null
    @@ -1,20 +0,0 @@
    -/*
    - * Copyright 2020 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 im.vector.app.core.extensions
    -
    -// Trick to ensure that when block is exhaustive
    -val  T.exhaustive: T get() = this
    diff --git a/vector/src/main/java/im/vector/app/core/platform/VectorBaseActivity.kt b/vector/src/main/java/im/vector/app/core/platform/VectorBaseActivity.kt
    index 2c161feb37..4796022856 100644
    --- a/vector/src/main/java/im/vector/app/core/platform/VectorBaseActivity.kt
    +++ b/vector/src/main/java/im/vector/app/core/platform/VectorBaseActivity.kt
    @@ -54,7 +54,6 @@ import im.vector.app.core.di.ActiveSessionHolder
     import im.vector.app.core.di.ActivityEntryPoint
     import im.vector.app.core.dialogs.DialogLocker
     import im.vector.app.core.dialogs.UnrecognizedCertificateDialog
    -import im.vector.app.core.extensions.exhaustive
     import im.vector.app.core.extensions.observeEvent
     import im.vector.app.core.extensions.observeNotNull
     import im.vector.app.core.extensions.registerStartForActivityResult
    @@ -267,7 +266,7 @@ abstract class VectorBaseActivity : AppCompatActivity(), Maver
                 is GlobalError.CertificateError     ->
                     handleCertificateError(globalError)
                 GlobalError.ExpiredAccount          -> Unit // TODO Handle account expiration
    -        }.exhaustive
    +        }
         }
     
         private fun handleCertificateError(certificateError: GlobalError.CertificateError) {
    diff --git a/vector/src/main/java/im/vector/app/core/preference/PushRulePreference.kt b/vector/src/main/java/im/vector/app/core/preference/PushRulePreference.kt
    index 1a7a79ed8c..78266cf5ee 100644
    --- a/vector/src/main/java/im/vector/app/core/preference/PushRulePreference.kt
    +++ b/vector/src/main/java/im/vector/app/core/preference/PushRulePreference.kt
    @@ -83,6 +83,7 @@ class PushRulePreference : VectorPreference {
                 NotificationIndex.NOISY  -> {
                     radioGroup?.check(R.id.bingPreferenceRadioBingRuleNoisy)
                 }
    +            null                     -> Unit
             }
     
             radioGroup?.setOnCheckedChangeListener { _, checkedId ->
    diff --git a/vector/src/main/java/im/vector/app/core/ui/views/KeysBackupBanner.kt b/vector/src/main/java/im/vector/app/core/ui/views/KeysBackupBanner.kt
    index 94c1ab6576..58a5666e94 100755
    --- a/vector/src/main/java/im/vector/app/core/ui/views/KeysBackupBanner.kt
    +++ b/vector/src/main/java/im/vector/app/core/ui/views/KeysBackupBanner.kt
    @@ -77,13 +77,10 @@ class KeysBackupBanner @JvmOverloads constructor(
     
         override fun onClick(v: View?) {
             when (state) {
    -            is State.Setup   -> {
    -                delegate?.setupKeysBackup()
    -            }
    +            is State.Setup   -> delegate?.setupKeysBackup()
                 is State.Update,
    -            is State.Recover -> {
    -                delegate?.recoverKeysBackup()
    -            }
    +            is State.Recover -> delegate?.recoverKeysBackup()
    +            else             -> Unit
             }
         }
     
    diff --git a/vector/src/main/java/im/vector/app/core/ui/views/NotificationAreaView.kt b/vector/src/main/java/im/vector/app/core/ui/views/NotificationAreaView.kt
    index 1615e77902..5190bb21a8 100644
    --- a/vector/src/main/java/im/vector/app/core/ui/views/NotificationAreaView.kt
    +++ b/vector/src/main/java/im/vector/app/core/ui/views/NotificationAreaView.kt
    @@ -27,7 +27,6 @@ import androidx.core.text.italic
     import im.vector.app.R
     import im.vector.app.core.epoxy.onClick
     import im.vector.app.core.error.ResourceLimitErrorFormatter
    -import im.vector.app.core.extensions.exhaustive
     import im.vector.app.core.utils.DimensionConverter
     import im.vector.app.databinding.ViewNotificationAreaBinding
     import im.vector.app.features.themes.ThemeUtils
    @@ -77,7 +76,7 @@ class NotificationAreaView @JvmOverloads constructor(
                 is State.UnsupportedAlgorithm       -> renderUnsupportedAlgorithm(newState)
                 is State.Tombstone                  -> renderTombstone()
                 is State.ResourceLimitExceededError -> renderResourceLimitExceededError(newState)
    -        }.exhaustive
    +        }
         }
     
         // PRIVATE METHODS ****************************************************************************************************************************************
    diff --git a/vector/src/main/java/im/vector/app/core/ui/views/PresenceStateImageView.kt b/vector/src/main/java/im/vector/app/core/ui/views/PresenceStateImageView.kt
    index 301f8afdc9..82675e8c11 100644
    --- a/vector/src/main/java/im/vector/app/core/ui/views/PresenceStateImageView.kt
    +++ b/vector/src/main/java/im/vector/app/core/ui/views/PresenceStateImageView.kt
    @@ -49,6 +49,7 @@ class PresenceStateImageView @JvmOverloads constructor(
                     setImageResource(R.drawable.ic_presence_offline)
                     contentDescription = context.getString(R.string.a11y_presence_offline)
                 }
    +            null                     -> Unit
             }
         }
     }
    diff --git a/vector/src/main/java/im/vector/app/core/ui/views/ShieldImageView.kt b/vector/src/main/java/im/vector/app/core/ui/views/ShieldImageView.kt
    index ac0b4408b2..713c177099 100644
    --- a/vector/src/main/java/im/vector/app/core/ui/views/ShieldImageView.kt
    +++ b/vector/src/main/java/im/vector/app/core/ui/views/ShieldImageView.kt
    @@ -40,21 +40,21 @@ class ShieldImageView @JvmOverloads constructor(
             isVisible = roomEncryptionTrustLevel != null
     
             when (roomEncryptionTrustLevel) {
    -            RoomEncryptionTrustLevel.Default -> {
    +            RoomEncryptionTrustLevel.Default                     -> {
                     contentDescription = context.getString(R.string.a11y_trust_level_default)
                     setImageResource(
                             if (borderLess) R.drawable.ic_shield_black_no_border
                             else R.drawable.ic_shield_black
                     )
                 }
    -            RoomEncryptionTrustLevel.Warning -> {
    +            RoomEncryptionTrustLevel.Warning                     -> {
                     contentDescription = context.getString(R.string.a11y_trust_level_warning)
                     setImageResource(
                             if (borderLess) R.drawable.ic_shield_warning_no_border
                             else R.drawable.ic_shield_warning
                     )
                 }
    -            RoomEncryptionTrustLevel.Trusted -> {
    +            RoomEncryptionTrustLevel.Trusted                     -> {
                     contentDescription = context.getString(R.string.a11y_trust_level_trusted)
                     setImageResource(
                             if (borderLess) R.drawable.ic_shield_trusted_no_border
    @@ -65,6 +65,7 @@ class ShieldImageView @JvmOverloads constructor(
                     contentDescription = context.getString(R.string.a11y_trust_level_trusted)
                     setImageResource(R.drawable.ic_warning_badge)
                 }
    +            null                                                 -> Unit
             }
         }
     }
    @@ -72,9 +73,9 @@ class ShieldImageView @JvmOverloads constructor(
     @DrawableRes
     fun RoomEncryptionTrustLevel.toDrawableRes(): Int {
         return when (this) {
    -        RoomEncryptionTrustLevel.Default -> R.drawable.ic_shield_black
    -        RoomEncryptionTrustLevel.Warning -> R.drawable.ic_shield_warning
    -        RoomEncryptionTrustLevel.Trusted -> R.drawable.ic_shield_trusted
    +        RoomEncryptionTrustLevel.Default                     -> R.drawable.ic_shield_black
    +        RoomEncryptionTrustLevel.Warning                     -> R.drawable.ic_shield_warning
    +        RoomEncryptionTrustLevel.Trusted                     -> R.drawable.ic_shield_trusted
             RoomEncryptionTrustLevel.E2EWithUnsupportedAlgorithm -> R.drawable.ic_warning_badge
         }
     }
    diff --git a/vector/src/main/java/im/vector/app/features/MainActivity.kt b/vector/src/main/java/im/vector/app/features/MainActivity.kt
    index 33b735551c..42bd2318b3 100644
    --- a/vector/src/main/java/im/vector/app/features/MainActivity.kt
    +++ b/vector/src/main/java/im/vector/app/features/MainActivity.kt
    @@ -241,7 +241,7 @@ class MainActivity : VectorBaseActivity(), UnlockedActivity
                     // We have a session.
                     // Check it can be opened
                     if (sessionHolder.getActiveSession().isOpenable) {
    -                    HomeActivity.newIntent(this)
    +                    HomeActivity.newIntent(this, existingSession = true)
                     } else {
                         // The token is still invalid
                         navigator.softLogout(this)
    diff --git a/vector/src/main/java/im/vector/app/features/analytics/ui/consent/AnalyticsConsentViewModel.kt b/vector/src/main/java/im/vector/app/features/analytics/ui/consent/AnalyticsConsentViewModel.kt
    index 2c7a8ac9bc..a570b31452 100644
    --- a/vector/src/main/java/im/vector/app/features/analytics/ui/consent/AnalyticsConsentViewModel.kt
    +++ b/vector/src/main/java/im/vector/app/features/analytics/ui/consent/AnalyticsConsentViewModel.kt
    @@ -22,7 +22,6 @@ import dagger.assisted.AssistedFactory
     import dagger.assisted.AssistedInject
     import im.vector.app.core.di.MavericksAssistedViewModelFactory
     import im.vector.app.core.di.hiltMavericksViewModelFactory
    -import im.vector.app.core.extensions.exhaustive
     import im.vector.app.core.platform.VectorViewModel
     import im.vector.app.features.analytics.VectorAnalytics
     import kotlinx.coroutines.launch
    @@ -55,7 +54,7 @@ class AnalyticsConsentViewModel @AssistedInject constructor(
         override fun handle(action: AnalyticsConsentViewActions) {
             when (action) {
                 is AnalyticsConsentViewActions.SetUserConsent -> handleSetUserConsent(action)
    -        }.exhaustive
    +        }
         }
     
         private fun handleSetUserConsent(action: AnalyticsConsentViewActions.SetUserConsent) {
    diff --git a/vector/src/main/java/im/vector/app/features/analytics/ui/consent/AnalyticsOptInActivity.kt b/vector/src/main/java/im/vector/app/features/analytics/ui/consent/AnalyticsOptInActivity.kt
    index c84031d2fd..c11cf582d3 100644
    --- a/vector/src/main/java/im/vector/app/features/analytics/ui/consent/AnalyticsOptInActivity.kt
    +++ b/vector/src/main/java/im/vector/app/features/analytics/ui/consent/AnalyticsOptInActivity.kt
    @@ -19,7 +19,6 @@ package im.vector.app.features.analytics.ui.consent
     import com.airbnb.mvrx.viewModel
     import dagger.hilt.android.AndroidEntryPoint
     import im.vector.app.core.extensions.addFragment
    -import im.vector.app.core.extensions.exhaustive
     import im.vector.app.core.platform.ScreenOrientationLocker
     import im.vector.app.core.platform.VectorBaseActivity
     import im.vector.app.databinding.ActivitySimpleBinding
    @@ -48,7 +47,7 @@ class AnalyticsOptInActivity : VectorBaseActivity() {
             viewModel.observeViewEvents {
                 when (it) {
                     AnalyticsOptInViewEvents.OnDataSaved -> finish()
    -            }.exhaustive
    +            }
             }
         }
     }
    diff --git a/vector/src/main/java/im/vector/app/features/attachments/preview/AttachmentsPreviewViewModel.kt b/vector/src/main/java/im/vector/app/features/attachments/preview/AttachmentsPreviewViewModel.kt
    index 0a0e700ce9..3e9d72e98b 100644
    --- a/vector/src/main/java/im/vector/app/features/attachments/preview/AttachmentsPreviewViewModel.kt
    +++ b/vector/src/main/java/im/vector/app/features/attachments/preview/AttachmentsPreviewViewModel.kt
    @@ -17,7 +17,6 @@
     
     package im.vector.app.features.attachments.preview
     
    -import im.vector.app.core.extensions.exhaustive
     import im.vector.app.core.platform.VectorViewModel
     
     class AttachmentsPreviewViewModel(initialState: AttachmentsPreviewViewState) :
    @@ -28,7 +27,7 @@ class AttachmentsPreviewViewModel(initialState: AttachmentsPreviewViewState) :
                 is AttachmentsPreviewAction.SetCurrentAttachment          -> handleSetCurrentAttachment(action)
                 is AttachmentsPreviewAction.UpdatePathOfCurrentAttachment -> handleUpdatePathOfCurrentAttachment(action)
                 AttachmentsPreviewAction.RemoveCurrentAttachment          -> handleRemoveCurrentAttachment()
    -        }.exhaustive
    +        }
         }
     
         private fun handleRemoveCurrentAttachment() = withState {
    diff --git a/vector/src/main/java/im/vector/app/features/call/CallControlsView.kt b/vector/src/main/java/im/vector/app/features/call/CallControlsView.kt
    index 8d30c4d5c5..b3fc36e5bc 100644
    --- a/vector/src/main/java/im/vector/app/features/call/CallControlsView.kt
    +++ b/vector/src/main/java/im/vector/app/features/call/CallControlsView.kt
    @@ -111,6 +111,7 @@ class CallControlsView @JvmOverloads constructor(
                     views.ringingControls.isVisible = false
                     views.connectedControls.isVisible = false
                 }
    +            null                      -> Unit
             }
         }
     
    diff --git a/vector/src/main/java/im/vector/app/features/call/VectorCallActivity.kt b/vector/src/main/java/im/vector/app/features/call/VectorCallActivity.kt
    index 23c7b79914..e9d16ee710 100644
    --- a/vector/src/main/java/im/vector/app/features/call/VectorCallActivity.kt
    +++ b/vector/src/main/java/im/vector/app/features/call/VectorCallActivity.kt
    @@ -525,8 +525,7 @@ class VectorCallActivity : VectorBaseActivity(), CallContro
                     navigator.openCallTransfer(this, callTransferActivityResultLauncher, callId)
                 }
                 is VectorCallViewEvents.FailToTransfer         -> showSnackbar(getString(R.string.call_transfer_failure))
    -            null                                           -> {
    -            }
    +            else                                           -> Unit
             }
         }
     
    diff --git a/vector/src/main/java/im/vector/app/features/call/VectorCallViewModel.kt b/vector/src/main/java/im/vector/app/features/call/VectorCallViewModel.kt
    index a26eec04f3..449a740cf3 100644
    --- a/vector/src/main/java/im/vector/app/features/call/VectorCallViewModel.kt
    +++ b/vector/src/main/java/im/vector/app/features/call/VectorCallViewModel.kt
    @@ -26,7 +26,6 @@ import dagger.assisted.AssistedFactory
     import dagger.assisted.AssistedInject
     import im.vector.app.core.di.MavericksAssistedViewModelFactory
     import im.vector.app.core.di.hiltMavericksViewModelFactory
    -import im.vector.app.core.extensions.exhaustive
     import im.vector.app.core.platform.VectorViewModel
     import im.vector.app.features.call.audio.CallAudioManager
     import im.vector.app.features.call.dialpad.DialPadLookup
    @@ -343,7 +342,7 @@ class VectorCallViewModel @AssistedInject constructor(
                     setState { VectorCallViewState(action.callArgs) }
                     setupCallWithCurrentState()
                 }
    -        }.exhaustive
    +        }
         }
     
         private fun handleCallTransfer() {
    @@ -358,7 +357,7 @@ class VectorCallViewModel @AssistedInject constructor(
             when (result) {
                 is CallTransferResult.ConnectWithUserId      -> connectWithUserId(result)
                 is CallTransferResult.ConnectWithPhoneNumber -> connectWithPhoneNumber(result)
    -        }.exhaustive
    +        }
         }
     
         private fun connectWithUserId(result: CallTransferResult.ConnectWithUserId) {
    diff --git a/vector/src/main/java/im/vector/app/features/call/conference/JitsiCallViewModel.kt b/vector/src/main/java/im/vector/app/features/call/conference/JitsiCallViewModel.kt
    index d04bebfd1b..f0b7b75afb 100644
    --- a/vector/src/main/java/im/vector/app/features/call/conference/JitsiCallViewModel.kt
    +++ b/vector/src/main/java/im/vector/app/features/call/conference/JitsiCallViewModel.kt
    @@ -27,7 +27,6 @@ import dagger.assisted.AssistedFactory
     import dagger.assisted.AssistedInject
     import im.vector.app.core.di.MavericksAssistedViewModelFactory
     import im.vector.app.core.di.hiltMavericksViewModelFactory
    -import im.vector.app.core.extensions.exhaustive
     import im.vector.app.core.platform.VectorViewModel
     import kotlinx.coroutines.Job
     import kotlinx.coroutines.delay
    @@ -103,7 +102,7 @@ class JitsiCallViewModel @AssistedInject constructor(
             when (action) {
                 is JitsiCallViewActions.SwitchTo      -> handleSwitchTo(action)
                 JitsiCallViewActions.OnConferenceLeft -> handleOnConferenceLeft()
    -        }.exhaustive
    +        }
         }
     
         private fun handleSwitchTo(action: JitsiCallViewActions.SwitchTo) = withState { state ->
    diff --git a/vector/src/main/java/im/vector/app/features/call/conference/VectorJitsiActivity.kt b/vector/src/main/java/im/vector/app/features/call/conference/VectorJitsiActivity.kt
    index a668f66f30..5a12337e4f 100644
    --- a/vector/src/main/java/im/vector/app/features/call/conference/VectorJitsiActivity.kt
    +++ b/vector/src/main/java/im/vector/app/features/call/conference/VectorJitsiActivity.kt
    @@ -35,7 +35,6 @@ import com.facebook.react.modules.core.PermissionListener
     import com.google.android.material.dialog.MaterialAlertDialogBuilder
     import dagger.hilt.android.AndroidEntryPoint
     import im.vector.app.R
    -import im.vector.app.core.extensions.exhaustive
     import im.vector.app.core.platform.VectorBaseActivity
     import im.vector.app.databinding.ActivityJitsiBinding
     import kotlinx.parcelize.Parcelize
    @@ -79,7 +78,7 @@ class VectorJitsiActivity : VectorBaseActivity(), JitsiMee
                     JitsiCallViewEvents.FailJoiningConference         -> handleFailJoining()
                     JitsiCallViewEvents.Finish                        -> finish()
                     JitsiCallViewEvents.LeaveConference               -> handleLeaveConference()
    -            }.exhaustive
    +            }
             }
             lifecycle.addObserver(ConferenceEventObserver(this, this::onBroadcastEvent))
         }
    diff --git a/vector/src/main/java/im/vector/app/features/call/transfer/CallTransferActivity.kt b/vector/src/main/java/im/vector/app/features/call/transfer/CallTransferActivity.kt
    index d8eede6a55..b10353be13 100644
    --- a/vector/src/main/java/im/vector/app/features/call/transfer/CallTransferActivity.kt
    +++ b/vector/src/main/java/im/vector/app/features/call/transfer/CallTransferActivity.kt
    @@ -26,7 +26,6 @@ import com.google.android.material.tabs.TabLayoutMediator
     import dagger.hilt.android.AndroidEntryPoint
     import im.vector.app.R
     import im.vector.app.core.error.ErrorFormatter
    -import im.vector.app.core.extensions.exhaustive
     import im.vector.app.core.platform.VectorBaseActivity
     import im.vector.app.databinding.ActivityCallTransferBinding
     import kotlinx.parcelize.Parcelize
    @@ -57,7 +56,7 @@ class CallTransferActivity : VectorBaseActivity() {
             callTransferViewModel.observeViewEvents {
                 when (it) {
                     is CallTransferViewEvents.Complete -> handleComplete()
    -            }.exhaustive
    +            }
             }
     
             sectionsPagerAdapter = CallTransferPagerAdapter(this)
    diff --git a/vector/src/main/java/im/vector/app/features/contactsbook/ContactsBookFragment.kt b/vector/src/main/java/im/vector/app/features/contactsbook/ContactsBookFragment.kt
    index ebd0089736..7425e0ae8a 100644
    --- a/vector/src/main/java/im/vector/app/features/contactsbook/ContactsBookFragment.kt
    +++ b/vector/src/main/java/im/vector/app/features/contactsbook/ContactsBookFragment.kt
    @@ -26,7 +26,6 @@ import com.airbnb.mvrx.activityViewModel
     import com.airbnb.mvrx.withState
     import im.vector.app.core.extensions.cleanup
     import im.vector.app.core.extensions.configureWith
    -import im.vector.app.core.extensions.exhaustive
     import im.vector.app.core.extensions.hideKeyboard
     import im.vector.app.core.platform.VectorBaseFragment
     import im.vector.app.core.utils.showIdentityServerConsentDialog
    @@ -73,7 +72,7 @@ class ContactsBookFragment @Inject constructor(
                 when (it) {
                     is ContactsBookViewEvents.Failure             -> showFailure(it.throwable)
                     is ContactsBookViewEvents.OnPoliciesRetrieved -> showConsentDialog(it)
    -            }.exhaustive
    +            }
             }
         }
     
    diff --git a/vector/src/main/java/im/vector/app/features/contactsbook/ContactsBookViewModel.kt b/vector/src/main/java/im/vector/app/features/contactsbook/ContactsBookViewModel.kt
    index 5678668b25..d016558764 100644
    --- a/vector/src/main/java/im/vector/app/features/contactsbook/ContactsBookViewModel.kt
    +++ b/vector/src/main/java/im/vector/app/features/contactsbook/ContactsBookViewModel.kt
    @@ -27,7 +27,6 @@ import im.vector.app.core.contacts.ContactsDataSource
     import im.vector.app.core.contacts.MappedContact
     import im.vector.app.core.di.MavericksAssistedViewModelFactory
     import im.vector.app.core.di.hiltMavericksViewModelFactory
    -import im.vector.app.core.extensions.exhaustive
     import im.vector.app.core.platform.VectorViewModel
     import im.vector.app.core.resources.StringProvider
     import im.vector.app.features.discovery.fetchIdentityServerWithTerms
    @@ -165,7 +164,7 @@ class ContactsBookViewModel @AssistedInject constructor(
                 is ContactsBookAction.OnlyBoundContacts -> handleOnlyBoundContacts(action)
                 ContactsBookAction.UserConsentGranted   -> handleUserConsentGranted()
                 ContactsBookAction.UserConsentRequest   -> handleUserConsentRequest()
    -        }.exhaustive
    +        }
         }
     
         private fun handleUserConsentRequest() {
    diff --git a/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomActivity.kt b/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomActivity.kt
    index 9df4f52d0f..0d36c7c7cc 100644
    --- a/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomActivity.kt
    +++ b/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomActivity.kt
    @@ -28,6 +28,7 @@ import com.airbnb.mvrx.Async
     import com.airbnb.mvrx.Fail
     import com.airbnb.mvrx.Loading
     import com.airbnb.mvrx.Success
    +import com.airbnb.mvrx.Uninitialized
     import com.airbnb.mvrx.viewModel
     import com.google.android.material.dialog.MaterialAlertDialogBuilder
     import dagger.hilt.android.AndroidEntryPoint
    @@ -35,7 +36,6 @@ import im.vector.app.R
     import im.vector.app.core.error.ErrorFormatter
     import im.vector.app.core.extensions.addFragment
     import im.vector.app.core.extensions.addFragmentToBackstack
    -import im.vector.app.core.extensions.exhaustive
     import im.vector.app.core.platform.SimpleFragmentActivity
     import im.vector.app.core.platform.WaitingViewData
     import im.vector.app.core.utils.PERMISSIONS_FOR_MEMBERS_SEARCH
    @@ -84,7 +84,7 @@ class CreateDirectRoomActivity : SimpleFragmentActivity() {
                             is UserListSharedAction.OnMenuItemSelected -> onMenuItemSelected(action)
                             UserListSharedAction.OpenPhoneBook         -> openPhoneBook()
                             UserListSharedAction.AddByQrCode           -> openAddByQrCode()
    -                    }.exhaustive
    +                    }
                     }
                     .launchIn(lifecycleScope)
             if (isFirstCreation()) {
    @@ -111,7 +111,7 @@ class CreateDirectRoomActivity : SimpleFragmentActivity() {
                         Toast.makeText(this, R.string.cannot_dm_self, Toast.LENGTH_SHORT).show()
                         finish()
                     }
    -            }.exhaustive
    +            }
             }
     
             qrViewModel.observeViewEvents {
    @@ -124,7 +124,7 @@ class CreateDirectRoomActivity : SimpleFragmentActivity() {
                         finish()
                     }
                     else                               -> Unit
    -            }.exhaustive
    +            }
             }
         }
     
    @@ -167,6 +167,7 @@ class CreateDirectRoomActivity : SimpleFragmentActivity() {
     
         private fun renderCreateAndInviteState(state: Async) {
             when (state) {
    +            Uninitialized,
                 is Loading -> renderCreationLoading()
                 is Success -> renderCreationSuccess(state())
                 is Fail    -> renderCreationFailure(state.error)
    diff --git a/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomViewModel.kt b/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomViewModel.kt
    index 9dd3ef6a9b..d3011496d2 100644
    --- a/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomViewModel.kt
    +++ b/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomViewModel.kt
    @@ -24,7 +24,6 @@ import dagger.assisted.AssistedFactory
     import dagger.assisted.AssistedInject
     import im.vector.app.core.di.MavericksAssistedViewModelFactory
     import im.vector.app.core.di.hiltMavericksViewModelFactory
    -import im.vector.app.core.extensions.exhaustive
     import im.vector.app.core.mvrx.runCatchingToAsync
     import im.vector.app.core.platform.VectorViewModel
     import im.vector.app.features.raw.wellknown.getElementWellknown
    @@ -56,7 +55,7 @@ class CreateDirectRoomViewModel @AssistedInject constructor(@Assisted
             when (action) {
                 is CreateDirectRoomAction.CreateRoomAndInviteSelectedUsers -> onSubmitInvitees(action.selections)
                 is CreateDirectRoomAction.QrScannedAction                  -> onCodeParsed(action)
    -        }.exhaustive
    +        }
         }
     
         private fun onCodeParsed(action: CreateDirectRoomAction.QrScannedAction) {
    @@ -108,7 +107,7 @@ class CreateDirectRoomViewModel @AssistedInject constructor(@Assisted
                                 when (it) {
                                     is PendingSelection.UserPendingSelection     -> invitedUserIds.add(it.user.userId)
                                     is PendingSelection.ThreePidPendingSelection -> invite3pids.add(it.threePid)
    -                            }.exhaustive
    +                            }
                             }
                             setDirectMessage()
                             enableEncryptionIfInvitedUsersSupportIt = adminE2EByDefault
    diff --git a/vector/src/main/java/im/vector/app/features/crypto/keysbackup/settings/KeysBackupSettingsRecyclerViewController.kt b/vector/src/main/java/im/vector/app/features/crypto/keysbackup/settings/KeysBackupSettingsRecyclerViewController.kt
    index 577572ef14..3c922e6309 100644
    --- a/vector/src/main/java/im/vector/app/features/crypto/keysbackup/settings/KeysBackupSettingsRecyclerViewController.kt
    +++ b/vector/src/main/java/im/vector/app/features/crypto/keysbackup/settings/KeysBackupSettingsRecyclerViewController.kt
    @@ -140,6 +140,7 @@ class KeysBackupSettingsRecyclerViewController @Inject constructor(
     
                     isBackupAlreadySetup = true
                 }
    +            null                                       -> Unit
             }
     
             if (isBackupAlreadySetup) {
    diff --git a/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecureStorageActivity.kt b/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecureStorageActivity.kt
    index b317ac95ad..0a105064d5 100644
    --- a/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecureStorageActivity.kt
    +++ b/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecureStorageActivity.kt
    @@ -116,12 +116,13 @@ class SharedSecureStorageActivity :
                 is SharedSecureStorageViewEvent.FinishSuccess        -> {
                     val dataResult = Intent()
                     dataResult.putExtra(EXTRA_DATA_RESULT, it.cypherResult)
    -                setResult(Activity.RESULT_OK, dataResult)
    +                setResult(RESULT_OK, dataResult)
                     finish()
                 }
                 is SharedSecureStorageViewEvent.ShowResetBottomSheet -> {
                     navigator.open4SSetup(this, SetupMode.HARD_RESET)
                 }
    +            else                                                 -> Unit
             }
         }
     
    diff --git a/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecureStorageViewModel.kt b/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecureStorageViewModel.kt
    index 8994ad901b..d324a52242 100644
    --- a/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecureStorageViewModel.kt
    +++ b/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecureStorageViewModel.kt
    @@ -29,7 +29,6 @@ import dagger.assisted.AssistedInject
     import im.vector.app.R
     import im.vector.app.core.di.MavericksAssistedViewModelFactory
     import im.vector.app.core.di.hiltMavericksViewModelFactory
    -import im.vector.app.core.extensions.exhaustive
     import im.vector.app.core.platform.VectorViewModel
     import im.vector.app.core.platform.WaitingViewData
     import im.vector.app.core.resources.StringProvider
    @@ -142,7 +141,7 @@ class SharedSecureStorageViewModel @AssistedInject constructor(
                 SharedSecureStorageAction.Back                        -> handleBack()
                 SharedSecureStorageAction.ForgotResetAll              -> handleResetAll()
                 SharedSecureStorageAction.DoResetAll                  -> handleDoResetAll()
    -        }.exhaustive
    +        }
         }
     
         private fun handleDoResetAll() {
    diff --git a/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecuredStorageKeyFragment.kt b/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecuredStorageKeyFragment.kt
    index 8e7f11f0f5..fd660367ae 100644
    --- a/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecuredStorageKeyFragment.kt
    +++ b/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecuredStorageKeyFragment.kt
    @@ -77,6 +77,7 @@ class SharedSecuredStorageKeyFragment @Inject constructor() : VectorBaseFragment
                     is SharedSecureStorageViewEvent.KeyInlineError -> {
                         views.ssssKeyEnterTil.error = it.message
                     }
    +                else                                           -> Unit
                 }
             }
     
    diff --git a/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecuredStoragePassphraseFragment.kt b/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecuredStoragePassphraseFragment.kt
    index 70c1003773..41507f2722 100644
    --- a/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecuredStoragePassphraseFragment.kt
    +++ b/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecuredStoragePassphraseFragment.kt
    @@ -86,6 +86,7 @@ class SharedSecuredStoragePassphraseFragment @Inject constructor(
                     is SharedSecureStorageViewEvent.InlineError -> {
                         views.ssssPassphraseEnterTil.error = it.message
                     }
    +                else                                        -> Unit
                 }
             }
     
    diff --git a/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapBottomSheet.kt b/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapBottomSheet.kt
    index 8448422a56..ac7662ca59 100644
    --- a/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapBottomSheet.kt
    +++ b/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapBottomSheet.kt
    @@ -36,7 +36,6 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder
     import dagger.hilt.android.AndroidEntryPoint
     import im.vector.app.R
     import im.vector.app.core.extensions.commitTransaction
    -import im.vector.app.core.extensions.exhaustive
     import im.vector.app.core.extensions.registerStartForActivityResult
     import im.vector.app.core.extensions.toMvRxBundle
     import im.vector.app.core.platform.VectorBaseBottomSheetDialogFragment
    @@ -209,7 +208,7 @@ class BootstrapBottomSheet : VectorBaseBottomSheetDialogFragment Unit
                 }
     
                 return@withState
    diff --git a/vector/src/main/java/im/vector/app/features/crypto/verification/VerificationBottomSheetViewModel.kt b/vector/src/main/java/im/vector/app/features/crypto/verification/VerificationBottomSheetViewModel.kt
    index 45f7f56957..2495ae4ea5 100644
    --- a/vector/src/main/java/im/vector/app/features/crypto/verification/VerificationBottomSheetViewModel.kt
    +++ b/vector/src/main/java/im/vector/app/features/crypto/verification/VerificationBottomSheetViewModel.kt
    @@ -28,7 +28,6 @@ import dagger.assisted.AssistedInject
     import im.vector.app.R
     import im.vector.app.core.di.MavericksAssistedViewModelFactory
     import im.vector.app.core.di.hiltMavericksViewModelFactory
    -import im.vector.app.core.extensions.exhaustive
     import im.vector.app.core.platform.VectorViewModel
     import im.vector.app.core.resources.StringProvider
     import kotlinx.coroutines.Dispatchers
    @@ -365,7 +364,7 @@ class VerificationBottomSheetViewModel @AssistedInject constructor(
                         copy(verifyingFrom4S = false)
                     }
                 }
    -        }.exhaustive
    +        }
         }
     
         private fun handleSecretBackFromSSSS(action: VerificationAction.GotResultFromSsss) {
    diff --git a/vector/src/main/java/im/vector/app/features/crypto/verification/emoji/VerificationEmojiCodeViewModel.kt b/vector/src/main/java/im/vector/app/features/crypto/verification/emoji/VerificationEmojiCodeViewModel.kt
    index 6f213adb7e..aec28f898e 100644
    --- a/vector/src/main/java/im/vector/app/features/crypto/verification/emoji/VerificationEmojiCodeViewModel.kt
    +++ b/vector/src/main/java/im/vector/app/features/crypto/verification/emoji/VerificationEmojiCodeViewModel.kt
    @@ -139,6 +139,7 @@ class VerificationEmojiCodeViewModel @AssistedInject constructor(
                         )
                     }
                 }
    +            else                                  -> Unit
             }
         }
     
    diff --git a/vector/src/main/java/im/vector/app/features/crypto/verification/request/VerificationRequestController.kt b/vector/src/main/java/im/vector/app/features/crypto/verification/request/VerificationRequestController.kt
    index 90997830a0..781677433b 100644
    --- a/vector/src/main/java/im/vector/app/features/crypto/verification/request/VerificationRequestController.kt
    +++ b/vector/src/main/java/im/vector/app/features/crypto/verification/request/VerificationRequestController.kt
    @@ -18,6 +18,7 @@ package im.vector.app.features.crypto.verification.request
     
     import androidx.core.text.toSpannable
     import com.airbnb.epoxy.EpoxyController
    +import com.airbnb.mvrx.Fail
     import com.airbnb.mvrx.Loading
     import com.airbnb.mvrx.Success
     import com.airbnb.mvrx.Uninitialized
    @@ -153,6 +154,7 @@ class VerificationRequestController @Inject constructor(
                             }
                         }
                     }
    +                is Fail          -> Unit
                 }
             }
     
    diff --git a/vector/src/main/java/im/vector/app/features/devtools/RoomDevToolActivity.kt b/vector/src/main/java/im/vector/app/features/devtools/RoomDevToolActivity.kt
    index 57d3ccc16b..f1f6142fa2 100644
    --- a/vector/src/main/java/im/vector/app/features/devtools/RoomDevToolActivity.kt
    +++ b/vector/src/main/java/im/vector/app/features/devtools/RoomDevToolActivity.kt
    @@ -34,7 +34,6 @@ import com.airbnb.mvrx.withState
     import com.google.android.material.dialog.MaterialAlertDialogBuilder
     import dagger.hilt.android.AndroidEntryPoint
     import im.vector.app.R
    -import im.vector.app.core.extensions.exhaustive
     import im.vector.app.core.extensions.replaceFragment
     import im.vector.app.core.platform.SimpleFragmentActivity
     import im.vector.app.core.resources.ColorProvider
    @@ -79,7 +78,7 @@ class RoomDevToolActivity : SimpleFragmentActivity(), FragmentManager.OnBackStac
                         Unit
                     }
                     is DevToolsViewEvents.ShowSnackMessage -> showSnackbar(it.message)
    -            }.exhaustive
    +            }
             }
             supportFragmentManager.addOnBackStackChangedListener(this)
         }
    diff --git a/vector/src/main/java/im/vector/app/features/discovery/DiscoverySettingsController.kt b/vector/src/main/java/im/vector/app/features/discovery/DiscoverySettingsController.kt
    index 551b72dd82..b338f367e3 100644
    --- a/vector/src/main/java/im/vector/app/features/discovery/DiscoverySettingsController.kt
    +++ b/vector/src/main/java/im/vector/app/features/discovery/DiscoverySettingsController.kt
    @@ -50,6 +50,7 @@ class DiscoverySettingsController @Inject constructor(
     
         override fun buildModels(data: DiscoverySettingsState) {
             when (data.identityServer) {
    +            Uninitialized,
                 is Loading -> {
                     loadingItem {
                         id("identityServerLoading")
    @@ -209,18 +210,19 @@ class DiscoverySettingsController @Inject constructor(
                 titleResId(R.string.settings_discovery_emails_title)
             }
             when (emails) {
    -            is Incomplete -> {
    +            Uninitialized,
    +            is Loading -> {
                     loadingItem {
                         id("emailsLoading")
                     }
                 }
    -            is Fail       -> {
    +            is Fail    -> {
                     settingsInfoItem {
                         id("emailsError")
                         helperText(emails.error.message)
                     }
                 }
    -            is Success    -> {
    +            is Success -> {
                     if (emails().isEmpty()) {
                         settingsInfoItem {
                             id("emailsEmpty")
    @@ -277,18 +279,19 @@ class DiscoverySettingsController @Inject constructor(
             }
     
             when (msisdns) {
    -            is Incomplete -> {
    +            Uninitialized,
    +            is Loading -> {
                     loadingItem {
                         id("msisdnLoading")
                     }
                 }
    -            is Fail       -> {
    +            is Fail    -> {
                     settingsInfoItem {
                         id("msisdnListError")
                         helperText(msisdns.error.message)
                     }
                 }
    -            is Success    -> {
    +            is Success -> {
                     if (msisdns().isEmpty()) {
                         settingsInfoItem {
                             id("no_msisdn")
    @@ -353,6 +356,7 @@ class DiscoverySettingsController @Inject constructor(
                 colorProvider(host.colorProvider)
                 stringProvider(host.stringProvider)
                 when (pidInfo.isShared) {
    +                Uninitialized,
                     is Loading -> {
                         buttonIndeterminate(true)
                     }
    @@ -384,6 +388,7 @@ class DiscoverySettingsController @Inject constructor(
                                 else          -> iconMode(IconMode.NONE)
                             }
                         }
    +                    null                            -> Unit
                     }
                 }
             }
    diff --git a/vector/src/main/java/im/vector/app/features/discovery/DiscoverySettingsFragment.kt b/vector/src/main/java/im/vector/app/features/discovery/DiscoverySettingsFragment.kt
    index 523e8cb9bb..2de03f296e 100644
    --- a/vector/src/main/java/im/vector/app/features/discovery/DiscoverySettingsFragment.kt
    +++ b/vector/src/main/java/im/vector/app/features/discovery/DiscoverySettingsFragment.kt
    @@ -28,7 +28,6 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder
     import im.vector.app.R
     import im.vector.app.core.extensions.cleanup
     import im.vector.app.core.extensions.configureWith
    -import im.vector.app.core.extensions.exhaustive
     import im.vector.app.core.extensions.observeEvent
     import im.vector.app.core.extensions.registerStartForActivityResult
     import im.vector.app.core.platform.VectorBaseFragment
    @@ -70,7 +69,7 @@ class DiscoverySettingsFragment @Inject constructor(
                 when (it) {
                     is DiscoverySharedViewModelAction.ChangeIdentityServer ->
                         viewModel.handle(DiscoverySettingsAction.ChangeIdentityServer(it.newUrl))
    -            }.exhaustive
    +            }
             }
     
             viewModel.observeViewEvents {
    @@ -78,7 +77,7 @@ class DiscoverySettingsFragment @Inject constructor(
                     is DiscoverySettingsViewEvents.Failure -> {
                         displayErrorDialog(it.throwable)
                     }
    -            }.exhaustive
    +            }
             }
             if (discoveryArgs.expandIdentityPolicies) {
                 viewModel.handle(DiscoverySettingsAction.SetPoliciesExpandState(expanded = true))
    diff --git a/vector/src/main/java/im/vector/app/features/discovery/DiscoverySettingsViewModel.kt b/vector/src/main/java/im/vector/app/features/discovery/DiscoverySettingsViewModel.kt
    index 19f233fe98..8c1caaf67a 100644
    --- a/vector/src/main/java/im/vector/app/features/discovery/DiscoverySettingsViewModel.kt
    +++ b/vector/src/main/java/im/vector/app/features/discovery/DiscoverySettingsViewModel.kt
    @@ -27,7 +27,6 @@ import dagger.assisted.AssistedInject
     import im.vector.app.R
     import im.vector.app.core.di.MavericksAssistedViewModelFactory
     import im.vector.app.core.di.hiltMavericksViewModelFactory
    -import im.vector.app.core.extensions.exhaustive
     import im.vector.app.core.platform.VectorViewModel
     import im.vector.app.core.resources.StringProvider
     import kotlinx.coroutines.flow.launchIn
    @@ -113,7 +112,7 @@ class DiscoverySettingsViewModel @AssistedInject constructor(
                 is DiscoverySettingsAction.FinalizeBind3pid       -> finalizeBind3pid(action, true)
                 is DiscoverySettingsAction.SubmitMsisdnToken      -> submitMsisdnToken(action)
                 is DiscoverySettingsAction.CancelBinding          -> cancelBinding(action)
    -        }.exhaustive
    +        }
         }
     
         private fun handleUpdateUserConsent(action: DiscoverySettingsAction.UpdateUserConsent) {
    @@ -235,7 +234,7 @@ class DiscoverySettingsViewModel @AssistedInject constructor(
             when (action.threePid) {
                 is ThreePid.Email  -> revokeEmail(action.threePid)
                 is ThreePid.Msisdn -> revokeMsisdn(action.threePid)
    -        }.exhaustive
    +        }
         }
     
         private fun revokeEmail(threePid: ThreePid.Email) = withState { state ->
    diff --git a/vector/src/main/java/im/vector/app/features/discovery/SettingsTextButtonSingleLineItem.kt b/vector/src/main/java/im/vector/app/features/discovery/SettingsTextButtonSingleLineItem.kt
    index 527d28dfad..29a44a1d8a 100644
    --- a/vector/src/main/java/im/vector/app/features/discovery/SettingsTextButtonSingleLineItem.kt
    +++ b/vector/src/main/java/im/vector/app/features/discovery/SettingsTextButtonSingleLineItem.kt
    @@ -34,7 +34,6 @@ import im.vector.app.core.epoxy.attributes.ButtonStyle
     import im.vector.app.core.epoxy.attributes.ButtonType
     import im.vector.app.core.epoxy.attributes.IconMode
     import im.vector.app.core.epoxy.onClick
    -import im.vector.app.core.extensions.exhaustive
     import im.vector.app.core.extensions.setTextOrHide
     import im.vector.app.core.resources.ColorProvider
     import im.vector.app.core.resources.StringProvider
    @@ -122,7 +121,7 @@ abstract class SettingsTextButtonSingleLineItem : EpoxyModelWithHolder {
                                 holder.mainButton.setTextColor(colorProvider.getColorFromAttribute(R.attr.colorError))
                             }
    -                    }.exhaustive
    +                    }
                         holder.mainButton.onClick(buttonClickListener)
                     }
                     ButtonType.SWITCH    -> {
    @@ -133,7 +132,7 @@ abstract class SettingsTextButtonSingleLineItem : EpoxyModelWithHolder useDefault()
                 is SetIdentityServerAction.UseCustomIdentityServer -> usedCustomIdentityServerUrl(action)
    -        }.exhaustive
    +        }
         }
     
         private fun useDefault() = withState { state ->
    diff --git a/vector/src/main/java/im/vector/app/features/home/HomeActivity.kt b/vector/src/main/java/im/vector/app/features/home/HomeActivity.kt
    index 964fb6f365..009edcc69e 100644
    --- a/vector/src/main/java/im/vector/app/features/home/HomeActivity.kt
    +++ b/vector/src/main/java/im/vector/app/features/home/HomeActivity.kt
    @@ -38,7 +38,6 @@ import dagger.hilt.android.AndroidEntryPoint
     import im.vector.app.AppStateHandler
     import im.vector.app.R
     import im.vector.app.core.di.ActiveSessionHolder
    -import im.vector.app.core.extensions.exhaustive
     import im.vector.app.core.extensions.hideKeyboard
     import im.vector.app.core.extensions.registerStartForActivityResult
     import im.vector.app.core.extensions.replaceFragment
    @@ -90,6 +89,7 @@ import javax.inject.Inject
     data class HomeActivityArgs(
             val clearNotification: Boolean,
             val accountCreation: Boolean,
    +        val hasExistingSession: Boolean = false,
             val inviteNotificationRoomId: String? = null
     ) : Parcelable
     
    @@ -106,6 +106,7 @@ class HomeActivity :
     
         @Suppress("UNUSED")
         private val analyticsAccountDataViewModel: AnalyticsAccountDataViewModel by viewModel()
    +
         @Suppress("UNUSED")
         private val userColorAccountDataViewModel: UserColorAccountDataViewModel by viewModel()
     
    @@ -231,7 +232,7 @@ class HomeActivity :
                             HomeActivitySharedAction.SendSpaceFeedBack    -> {
                                 bugReporter.openBugReportScreen(this, ReportType.SPACE_BETA_FEEDBACK)
                             }
    -                    }.exhaustive
    +                    }
                     }
                     .launchIn(lifecycleScope)
     
    @@ -253,7 +254,9 @@ class HomeActivity :
                     HomeActivityViewEvents.PromptToEnableSessionPush        -> handlePromptToEnablePush()
                     is HomeActivityViewEvents.OnCrossSignedInvalidated      -> handleCrossSigningInvalidated(it)
                     HomeActivityViewEvents.ShowAnalyticsOptIn               -> handleShowAnalyticsOptIn()
    -            }.exhaustive
    +                HomeActivityViewEvents.NotifyUserForThreadsMigration    -> handleNotifyUserForThreadsMigration()
    +                is HomeActivityViewEvents.MigrateThreads                -> migrateThreadsIfNeeded(it.checkSession)
    +            }
             }
             homeActivityViewModel.onEach { renderState(it) }
     
    @@ -269,6 +272,48 @@ class HomeActivity :
             navigator.openAnalyticsOptIn(this)
         }
     
    +    /**
    +     * Migrating from old threads io.element.thread to new m.thread needs an initial sync to
    +     * sync and display existing messages appropriately
    +     */
    +    private fun migrateThreadsIfNeeded(checkSession: Boolean) {
    +        if (checkSession) {
    +            // We should check session to ensure we will only clear cache if needed
    +            val args = intent.getParcelableExtra(Mavericks.KEY_ARG)
    +            if (args?.hasExistingSession == true) {
    +                // existingSession --> Will be true only if we came from an existing active session
    +                Timber.i("----> Migrating threads from an existing session..")
    +                handleThreadsMigration()
    +            } else {
    +                // We came from a new session and not an existing one,
    +                // so there is no need to migrate threads while an initial synced performed
    +                Timber.i("----> No thread migration needed, we are ok")
    +                vectorPreferences.setShouldMigrateThreads(shouldMigrate = false)
    +            }
    +        } else {
    +            // Proceed with migration
    +            handleThreadsMigration()
    +        }
    +    }
    +
    +    /**
    +     * Clear cache and restart to invoke an initial sync for threads migration
    +     */
    +    private fun handleThreadsMigration() {
    +        Timber.i("----> Threads Migration detected, clearing cache and sync...")
    +        vectorPreferences.setShouldMigrateThreads(shouldMigrate = false)
    +        MainActivity.restartApp(this, MainActivityArgs(clearCache = true))
    +    }
    +
    +    private fun handleNotifyUserForThreadsMigration() {
    +        MaterialAlertDialogBuilder(this)
    +                .setTitle(R.string.threads_notice_migration_title)
    +                .setMessage(R.string.threads_notice_migration_message)
    +                .setCancelable(true)
    +                .setPositiveButton(R.string.sas_got_it) { _, _ -> }
    +                .show()
    +    }
    +
         private fun handleIntent(intent: Intent?) {
             intent?.dataString?.let { deepLink ->
                 val resolvedLink = when {
    @@ -329,7 +374,7 @@ class HomeActivity :
                     // Idle or Incremental sync status
                     views.waitingView.root.isVisible = false
                 }
    -        }.exhaustive
    +        }
         }
     
         private fun handleAskPasswordToInitCrossSigning(events: HomeActivityViewEvents.AskPasswordToInitCrossSigning) {
    @@ -546,11 +591,13 @@ class HomeActivity :
             fun newIntent(context: Context,
                           clearNotification: Boolean = false,
                           accountCreation: Boolean = false,
    +                      existingSession: Boolean = false,
                           inviteNotificationRoomId: String? = null
             ): Intent {
                 val args = HomeActivityArgs(
                         clearNotification = clearNotification,
                         accountCreation = accountCreation,
    +                    hasExistingSession = existingSession,
                         inviteNotificationRoomId = inviteNotificationRoomId
                 )
     
    diff --git a/vector/src/main/java/im/vector/app/features/home/HomeActivityViewEvents.kt b/vector/src/main/java/im/vector/app/features/home/HomeActivityViewEvents.kt
    index adc44a57bd..5efd49a579 100644
    --- a/vector/src/main/java/im/vector/app/features/home/HomeActivityViewEvents.kt
    +++ b/vector/src/main/java/im/vector/app/features/home/HomeActivityViewEvents.kt
    @@ -25,4 +25,6 @@ sealed interface HomeActivityViewEvents : VectorViewEvents {
         data class OnCrossSignedInvalidated(val userItem: MatrixItem.UserItem) : HomeActivityViewEvents
         object PromptToEnableSessionPush : HomeActivityViewEvents
         object ShowAnalyticsOptIn : HomeActivityViewEvents
    +    object NotifyUserForThreadsMigration : HomeActivityViewEvents
    +    data class MigrateThreads(val checkSession: Boolean) : HomeActivityViewEvents
     }
    diff --git a/vector/src/main/java/im/vector/app/features/home/HomeActivityViewModel.kt b/vector/src/main/java/im/vector/app/features/home/HomeActivityViewModel.kt
    index 35c112b63a..87de0a32e3 100644
    --- a/vector/src/main/java/im/vector/app/features/home/HomeActivityViewModel.kt
    +++ b/vector/src/main/java/im/vector/app/features/home/HomeActivityViewModel.kt
    @@ -25,7 +25,6 @@ import im.vector.app.config.analyticsConfig
     import im.vector.app.core.di.ActiveSessionHolder
     import im.vector.app.core.di.MavericksAssistedViewModelFactory
     import im.vector.app.core.di.hiltMavericksViewModelFactory
    -import im.vector.app.core.extensions.exhaustive
     import im.vector.app.core.platform.VectorViewModel
     import im.vector.app.features.analytics.store.AnalyticsStore
     import im.vector.app.features.login.ReAuthHelper
    @@ -51,6 +50,7 @@ import org.matrix.android.sdk.api.util.toMatrixItem
     import org.matrix.android.sdk.flow.flow
     import org.matrix.android.sdk.internal.crypto.model.CryptoDeviceInfo
     import org.matrix.android.sdk.internal.crypto.model.MXUsersDevicesMap
    +import org.matrix.android.sdk.internal.database.lightweight.LightweightSettingsStorage
     import org.matrix.android.sdk.internal.util.awaitCallback
     import timber.log.Timber
     import kotlin.coroutines.Continuation
    @@ -62,6 +62,7 @@ class HomeActivityViewModel @AssistedInject constructor(
             private val activeSessionHolder: ActiveSessionHolder,
             private val reAuthHelper: ReAuthHelper,
             private val analyticsStore: AnalyticsStore,
    +        private val lightweightSettingsStorage: LightweightSettingsStorage,
             private val vectorPreferences: VectorPreferences
     ) : VectorViewModel(initialState) {
     
    @@ -84,6 +85,7 @@ class HomeActivityViewModel @AssistedInject constructor(
             checkSessionPushIsOn()
             observeCrossSigningReset()
             observeAnalytics()
    +        initThreadsMigration()
         }
     
         private fun observeAnalytics() {
    @@ -130,6 +132,46 @@ class HomeActivityViewModel @AssistedInject constructor(
                     .launchIn(viewModelScope)
         }
     
    +    /**
    +     * Handle threads migration. The migration includes:
    +     * - Notify users that had io.element.thread enabled from labs
    +     * - Re-Enable m.thread to those users (that they had enabled labs threads)
    +     * - Handle migration when threads are enabled by default
    +     */
    +    private fun initThreadsMigration() {
    +        // When we would like to enable threads for all users
    +//        if(vectorPreferences.shouldMigrateThreads()) {
    +//            vectorPreferences.setThreadMessagesEnabled()
    +//            lightweightSettingsStorage.setThreadMessagesEnabled(vectorPreferences.areThreadMessagesEnabled())
    +//        }
    +
    +        when {
    +            // Notify users
    +            vectorPreferences.shouldNotifyUserAboutThreads() && vectorPreferences.areThreadMessagesEnabled() -> {
    +                Timber.i("----> Notify users about threads")
    +                // Notify the user if needed that we migrated to support m.thread
    +                // instead of io.element.thread so old thread messages will be displayed as normal timeline messages
    +                _viewEvents.post(HomeActivityViewEvents.NotifyUserForThreadsMigration)
    +                vectorPreferences.userNotifiedAboutThreads()
    +            }
    +            // Migrate users with enabled lab settings
    +            vectorPreferences.shouldNotifyUserAboutThreads() && vectorPreferences.shouldMigrateThreads()     -> {
    +                Timber.i("----> Migrate threads with enabled labs")
    +                // If user had io.element.thread enabled then enable the new thread support,
    +                // clear cache to sync messages appropriately
    +                vectorPreferences.setThreadMessagesEnabled()
    +                lightweightSettingsStorage.setThreadMessagesEnabled(vectorPreferences.areThreadMessagesEnabled())
    +                // Clear Cache
    +                _viewEvents.post(HomeActivityViewEvents.MigrateThreads(checkSession = false))
    +            }
    +            // Enable all users
    +            vectorPreferences.shouldMigrateThreads() && vectorPreferences.areThreadMessagesEnabled()         -> {
    +                Timber.i("----> Try to migrate threads")
    +                _viewEvents.post(HomeActivityViewEvents.MigrateThreads(checkSession = true))
    +            }
    +        }
    +    }
    +
         private fun observeInitialSync() {
             val session = activeSessionHolder.getSafeActiveSession() ?: return
     
    @@ -263,6 +305,6 @@ class HomeActivityViewModel @AssistedInject constructor(
                 HomeActivityViewActions.ViewStarted               -> {
                     initialize()
                 }
    -        }.exhaustive
    +        }
         }
     }
    diff --git a/vector/src/main/java/im/vector/app/features/home/HomeDetailViewModel.kt b/vector/src/main/java/im/vector/app/features/home/HomeDetailViewModel.kt
    index e812942996..01d91f3edc 100644
    --- a/vector/src/main/java/im/vector/app/features/home/HomeDetailViewModel.kt
    +++ b/vector/src/main/java/im/vector/app/features/home/HomeDetailViewModel.kt
    @@ -273,6 +273,7 @@ class HomeDetailViewModel @AssistedInject constructor(
                                     )
                                 }
                             }
    +                        null                                -> Unit
                         }
                     }
                     .launchIn(viewModelScope)
    diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt
    index 2de37be8fb..9c754e042c 100644
    --- a/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt
    +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt
    @@ -72,7 +72,6 @@ import im.vector.app.core.dialogs.ConfirmationDialogBuilder
     import im.vector.app.core.dialogs.GalleryOrCameraDialogHelper
     import im.vector.app.core.epoxy.LayoutManagerStateRestorer
     import im.vector.app.core.extensions.cleanup
    -import im.vector.app.core.extensions.exhaustive
     import im.vector.app.core.extensions.hideKeyboard
     import im.vector.app.core.extensions.registerStartForActivityResult
     import im.vector.app.core.extensions.setTextOrHide
    @@ -446,7 +445,7 @@ class TimelineFragment @Inject constructor(
                         }
                         showErrorInSnackbar(it.throwable)
                     }
    -            }.exhaustive
    +            }
             }
     
             timelineViewModel.observeViewEvents {
    @@ -483,7 +482,7 @@ class TimelineFragment @Inject constructor(
                     RoomDetailViewEvents.StopChatEffects                     -> handleStopChatEffects()
                     is RoomDetailViewEvents.DisplayAndAcceptCall             -> acceptIncomingCall(it)
                     RoomDetailViewEvents.RoomReplacementStarted              -> handleRoomReplacement()
    -            }.exhaustive
    +            }
             }
     
             if (savedInstanceState == null) {
    @@ -784,6 +783,18 @@ class TimelineFragment @Inject constructor(
                     updateRecordingUiState(RecordingUiState.Draft)
                 }
     
    +            override fun onVoiceWaveformTouchedUp(percentage: Float, duration: Int) {
    +                messageComposerViewModel.handle(
    +                        MessageComposerAction.VoiceWaveformTouchedUp(VoiceMessagePlaybackTracker.RECORDING_ID, duration, percentage)
    +                )
    +            }
    +
    +            override fun onVoiceWaveformMoved(percentage: Float, duration: Int) {
    +                messageComposerViewModel.handle(
    +                        MessageComposerAction.VoiceWaveformTouchedUp(VoiceMessagePlaybackTracker.RECORDING_ID, duration, percentage)
    +                )
    +            }
    +
                 private fun updateRecordingUiState(state: RecordingUiState) {
                     messageComposerViewModel.handle(
                             MessageComposerAction.OnVoiceRecordingUiStateChanged(state))
    @@ -875,7 +886,7 @@ class TimelineFragment @Inject constructor(
                     onContentAttachmentsReady(sharedData.attachmentData)
                 }
                 null                      -> Timber.v("No share data to process")
    -        }.exhaustive
    +        }
         }
     
         private fun handleSpaceShare() {
    @@ -1241,7 +1252,7 @@ class TimelineFragment @Inject constructor(
                     insertUserDisplayNameInTextEditor(roomDetailPendingAction.userId)
                 is RoomDetailPendingAction.OpenRoom          ->
                     handleOpenRoom(RoomDetailViewEvents.OpenRoom(roomDetailPendingAction.roomId, roomDetailPendingAction.closeCurrentRoom))
    -        }.exhaustive
    +        }
         }
     
         override fun onPause() {
    @@ -1613,11 +1624,10 @@ class TimelineFragment @Inject constructor(
                     views.includeRoomToolbar.roomToolbarContentView.isClickable = roomSummary.membership == Membership.JOIN
                     views.includeRoomToolbar.roomToolbarTitleView.text = roomSummary.displayName
                     avatarRenderer.render(roomSummary.toMatrixItem(), views.includeRoomToolbar.roomToolbarAvatarImageView)
    -                views.includeRoomToolbar.roomToolbarDecorationImageView.render(roomSummary.roomEncryptionTrustLevel)
    -                views.includeRoomToolbar.roomToolbarPresenceImageView.render(
    -                        roomSummary.isDirect && matrixConfiguration.presenceSyncEnabled,
    -                        roomSummary.directUserPresence
    -                )
    +                val showPresence = roomSummary.isDirect && matrixConfiguration.presenceSyncEnabled
    +                views.includeRoomToolbar.roomToolbarPresenceImageView.render(showPresence, roomSummary.directUserPresence)
    +                val shieldView = if (showPresence) views.includeRoomToolbar.roomToolbarTitleShield else views.includeRoomToolbar.roomToolbarAvatarShield
    +                shieldView.render(roomSummary.roomEncryptionTrustLevel)
                     views.includeRoomToolbar.roomToolbarPublicImageView.isVisible = roomSummary.isPublic && !roomSummary.isDirect
                 }
             } else {
    @@ -1659,7 +1669,7 @@ class TimelineFragment @Inject constructor(
                 is MessageComposerViewEvents.SlashCommandNotSupportedInThreads -> {
                     displayCommandError(getString(R.string.command_not_supported_in_threads, sendMessageResult.command.command))
                 }
    -        } // .exhaustive
    +        } // 
     
             lockSendButton = false
         }
    @@ -1783,6 +1793,7 @@ class TimelineFragment @Inject constructor(
                             transactionId = data.transactionId,
                     ).show(parentFragmentManager, "REQ")
                 }
    +            else                                          -> Unit
             }
         }
     
    @@ -2052,6 +2063,14 @@ class TimelineFragment @Inject constructor(
             messageComposerViewModel.handle(MessageComposerAction.PlayOrPauseVoicePlayback(eventId, messageAudioContent))
         }
     
    +    override fun onVoiceWaveformTouchedUp(eventId: String, duration: Int, percentage: Float) {
    +        messageComposerViewModel.handle(MessageComposerAction.VoiceWaveformTouchedUp(eventId, duration, percentage))
    +    }
    +
    +    override fun onVoiceWaveformMovedTo(eventId: String, duration: Int, percentage: Float) {
    +        messageComposerViewModel.handle(MessageComposerAction.VoiceWaveformMovedTo(eventId, duration, percentage))
    +    }
    +
         private fun onShareActionClicked(action: EventSharedAction.Share) {
             when (action.messageContent) {
                 is MessageTextContent           -> shareText(requireContext(), action.messageContent.body)
    @@ -2237,6 +2256,8 @@ class TimelineFragment @Inject constructor(
                 is EventSharedAction.EndPoll                    -> {
                     askConfirmationToEndPoll(action.eventId)
                 }
    +            is EventSharedAction.ReportContent              -> Unit /* Not clickable */
    +            EventSharedAction.Separator                     -> Unit /* Not clickable */
             }
         }
     
    @@ -2435,7 +2456,7 @@ class TimelineFragment @Inject constructor(
                                     locationOwnerId = session.myUserId
                             )
                 }
    -        }.exhaustive
    +        }
         }
     
         // AttachmentsHelper.Callback
    diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineViewModel.kt
    index a9235b5699..6933adc758 100644
    --- a/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineViewModel.kt
    +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineViewModel.kt
    @@ -33,7 +33,6 @@ import im.vector.app.BuildConfig
     import im.vector.app.R
     import im.vector.app.core.di.MavericksAssistedViewModelFactory
     import im.vector.app.core.di.hiltMavericksViewModelFactory
    -import im.vector.app.core.extensions.exhaustive
     import im.vector.app.core.mvrx.runCatchingToAsync
     import im.vector.app.core.platform.VectorViewModel
     import im.vector.app.core.resources.StringProvider
    @@ -440,7 +439,7 @@ class TimelineViewModel @AssistedInject constructor(
                     _viewEvents.post(RoomDetailViewEvents.OpenRoom(action.replacementRoomId, closeCurrentRoom = true))
                 }
                 is RoomDetailAction.EndPoll                          -> handleEndPoll(action.eventId)
    -        }.exhaustive
    +        }
         }
     
         private fun handleJitsiCallJoinStatus(action: RoomDetailAction.UpdateJoinJitsiCallStatus) = withState { state ->
    diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerAction.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerAction.kt
    index 10cef39942..091e9f7869 100644
    --- a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerAction.kt
    +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerAction.kt
    @@ -40,4 +40,6 @@ sealed class MessageComposerAction : VectorViewModelAction {
         data class PlayOrPauseVoicePlayback(val eventId: String, val messageAudioContent: MessageAudioContent) : MessageComposerAction()
         object PlayOrPauseRecordingPlayback : MessageComposerAction()
         data class EndAllVoiceActions(val deleteRecord: Boolean = true) : MessageComposerAction()
    +    data class VoiceWaveformTouchedUp(val eventId: String, val duration: Int, val percentage: Float) : MessageComposerAction()
    +    data class VoiceWaveformMovedTo(val eventId: String, val duration: Int, val percentage: Float) : MessageComposerAction()
     }
    diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerViewModel.kt
    index 009d898940..976489eec3 100644
    --- a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerViewModel.kt
    +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerViewModel.kt
    @@ -23,7 +23,6 @@ import dagger.assisted.AssistedInject
     import im.vector.app.R
     import im.vector.app.core.di.MavericksAssistedViewModelFactory
     import im.vector.app.core.di.hiltMavericksViewModelFactory
    -import im.vector.app.core.extensions.exhaustive
     import im.vector.app.core.platform.VectorViewModel
     import im.vector.app.core.resources.StringProvider
     import im.vector.app.features.analytics.AnalyticsTracker
    @@ -109,6 +108,8 @@ class MessageComposerViewModel @AssistedInject constructor(
                 is MessageComposerAction.EndAllVoiceActions             -> handleEndAllVoiceActions(action.deleteRecord)
                 is MessageComposerAction.InitializeVoiceRecorder        -> handleInitializeVoiceRecorder(action.attachmentData)
                 is MessageComposerAction.OnEntersBackground             -> handleEntersBackground(action.composerText)
    +            is MessageComposerAction.VoiceWaveformTouchedUp         -> handleVoiceWaveformTouchedUp(action)
    +            is MessageComposerAction.VoiceWaveformMovedTo           -> handleVoiceWaveformMovedTo(action)
             }
         }
     
    @@ -463,7 +464,7 @@ class MessageComposerViewModel @AssistedInject constructor(
                                 _viewEvents.post(MessageComposerViewEvents.SlashCommandResultOk())
                                 popDraft()
                             }
    -                    }.exhaustive
    +                    }
                     }
                     is SendMode.Edit    -> {
                         // is original event a reply?
    @@ -536,7 +537,7 @@ class MessageComposerViewModel @AssistedInject constructor(
                     is SendMode.Voice   -> {
                         // do nothing
                     }
    -            }.exhaustive
    +            }
             }
         }
     
    @@ -869,12 +870,23 @@ class MessageComposerViewModel @AssistedInject constructor(
             voiceMessageHelper.pauseRecording()
         }
     
    +    private fun handleVoiceWaveformTouchedUp(action: MessageComposerAction.VoiceWaveformTouchedUp) {
    +        voiceMessageHelper.movePlaybackTo(action.eventId, action.percentage, action.duration)
    +    }
    +
    +    private fun handleVoiceWaveformMovedTo(action: MessageComposerAction.VoiceWaveformMovedTo) {
    +        voiceMessageHelper.movePlaybackTo(action.eventId, action.percentage, action.duration)
    +    }
    +
         private fun handleEntersBackground(composerText: String) {
    +        // Always stop all voice actions. It may be playing in timeline or active recording
    +        val playingAudioContent = voiceMessageHelper.stopAllVoiceActions(deleteRecord = false)
    +        voiceMessageHelper.clearTracker()
    +
             val isVoiceRecording = com.airbnb.mvrx.withState(this) { it.isVoiceRecording }
             if (isVoiceRecording) {
    -            voiceMessageHelper.clearTracker()
                 viewModelScope.launch {
    -                voiceMessageHelper.stopAllVoiceActions(deleteRecord = false)?.toContentAttachmentData()?.let { voiceDraft ->
    +                playingAudioContent?.toContentAttachmentData()?.let { voiceDraft ->
                         val content = voiceDraft.toJsonString()
                         room.saveDraft(UserDraft.Voice(content))
                         setState { copy(sendMode = SendMode.Voice(content)) }
    diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/VoiceMessageHelper.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/VoiceMessageHelper.kt
    index 735d356476..c5d8b7a5c1 100644
    --- a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/VoiceMessageHelper.kt
    +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/VoiceMessageHelper.kt
    @@ -132,9 +132,11 @@ class VoiceMessageHelper @Inject constructor(
         }
     
         fun startOrPausePlayback(id: String, file: File) {
    -        stopPlayback()
    +        val playbackState = playbackTracker.getPlaybackState(id)
    +        mediaPlayer?.stop()
    +        stopPlaybackTicker()
             stopRecordingAmplitudes()
    -        if (playbackTracker.getPlaybackState(id) is VoiceMessagePlaybackTracker.Listener.State.Playing) {
    +        if (playbackState is VoiceMessagePlaybackTracker.Listener.State.Playing) {
                 playbackTracker.pausePlayback(id)
             } else {
                 startPlayback(id, file)
    @@ -169,11 +171,19 @@ class VoiceMessageHelper @Inject constructor(
         }
     
         fun stopPlayback() {
    -        playbackTracker.stopPlayback(VoiceMessagePlaybackTracker.RECORDING_ID)
    +        playbackTracker.pausePlayback(VoiceMessagePlaybackTracker.RECORDING_ID)
             mediaPlayer?.stop()
             stopPlaybackTicker()
         }
     
    +    fun movePlaybackTo(id: String, percentage: Float, totalDuration: Int) {
    +        val toMillisecond = (totalDuration * percentage).toInt()
    +        playbackTracker.updateCurrentPlaybackTime(id, toMillisecond, percentage)
    +
    +        stopPlayback()
    +        playbackTracker.pausePlayback(id)
    +    }
    +
         private fun startRecordingAmplitudes() {
             amplitudeTicker?.stop()
             amplitudeTicker = CountUpTimer(50).apply {
    @@ -221,7 +231,9 @@ class VoiceMessageHelper @Inject constructor(
         private fun onPlaybackTick(id: String) {
             if (mediaPlayer?.isPlaying.orFalse()) {
                 val currentPosition = mediaPlayer?.currentPosition ?: 0
    -            playbackTracker.updateCurrentPlaybackTime(id, currentPosition)
    +            val totalDuration = mediaPlayer?.duration ?: 0
    +            val percentage = currentPosition.toFloat() / totalDuration
    +            playbackTracker.updateCurrentPlaybackTime(id, currentPosition, percentage)
             } else {
                 playbackTracker.stopPlayback(id)
                 stopPlaybackTicker()
    diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/voice/VoiceMessageRecorderView.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/voice/VoiceMessageRecorderView.kt
    index 9a643796a9..ab37d1a48c 100644
    --- a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/voice/VoiceMessageRecorderView.kt
    +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/voice/VoiceMessageRecorderView.kt
    @@ -23,7 +23,6 @@ import androidx.constraintlayout.widget.ConstraintLayout
     import dagger.hilt.android.AndroidEntryPoint
     import im.vector.app.BuildConfig
     import im.vector.app.R
    -import im.vector.app.core.extensions.exhaustive
     import im.vector.app.core.hardware.vibrate
     import im.vector.app.core.time.Clock
     import im.vector.app.core.utils.DimensionConverter
    @@ -53,6 +52,8 @@ class VoiceMessageRecorderView @JvmOverloads constructor(
             fun onDeleteVoiceMessage()
             fun onRecordingLimitReached()
             fun onRecordingWaveformClicked()
    +        fun onVoiceWaveformTouchedUp(percentage: Float, duration: Int)
    +        fun onVoiceWaveformMoved(percentage: Float, duration: Int)
         }
     
         @Inject lateinit var clock: Clock
    @@ -65,6 +66,7 @@ class VoiceMessageRecorderView @JvmOverloads constructor(
         private var recordingTicker: CountUpTimer? = null
         private var lastKnownState: RecordingUiState? = null
         private var dragState: DraggingState = DraggingState.Ignored
    +    private var recordingDuration: Long = 0
     
         init {
             inflate(this.context, R.layout.view_voice_message_recorder, this)
    @@ -95,9 +97,9 @@ class VoiceMessageRecorderView @JvmOverloads constructor(
                 override fun onDeleteVoiceMessage() = callback.onDeleteVoiceMessage()
                 override fun onWaveformClicked() {
                     when (lastKnownState) {
    -                    RecordingUiState.Draft  -> callback.onVoicePlaybackButtonClicked()
                         is RecordingUiState.Recording,
                         is RecordingUiState.Locked -> callback.onRecordingWaveformClicked()
    +                    else                       -> Unit
                     }
                 }
     
    @@ -105,6 +107,18 @@ class VoiceMessageRecorderView @JvmOverloads constructor(
                 override fun onMicButtonDrag(nextDragStateCreator: (DraggingState) -> DraggingState) {
                     onDrag(dragState, newDragState = nextDragStateCreator(dragState))
                 }
    +
    +            override fun onVoiceWaveformTouchedUp(percentage: Float) {
    +                if (lastKnownState == RecordingUiState.Draft) {
    +                    callback.onVoiceWaveformTouchedUp(percentage, recordingDuration.toInt())
    +                }
    +            }
    +
    +            override fun onVoiceWaveformMoved(percentage: Float) {
    +                if (lastKnownState == RecordingUiState.Draft) {
    +                    callback.onVoiceWaveformMoved(percentage, recordingDuration.toInt())
    +                }
    +            }
             })
         }
     
    @@ -119,7 +133,7 @@ class VoiceMessageRecorderView @JvmOverloads constructor(
         fun render(recordingState: RecordingUiState) {
             if (lastKnownState == recordingState) return
             when (recordingState) {
    -            RecordingUiState.Idle      -> {
    +            RecordingUiState.Idle         -> {
                     reset()
                 }
                 is RecordingUiState.Recording -> {
    @@ -137,7 +151,7 @@ class VoiceMessageRecorderView @JvmOverloads constructor(
                         voiceMessageViews.showRecordingLockedViews(recordingState)
                     }, 500)
                 }
    -            RecordingUiState.Draft   -> {
    +            RecordingUiState.Draft        -> {
                     stopRecordingTicker()
                     voiceMessageViews.showDraftViews()
                 }
    @@ -167,7 +181,7 @@ class VoiceMessageRecorderView @JvmOverloads constructor(
                 DraggingState.Ready         -> {
                     // do nothing
                 }
    -        }.exhaustive
    +        }
             dragState = newDragState
         }
     
    @@ -203,6 +217,7 @@ class VoiceMessageRecorderView @JvmOverloads constructor(
         }
     
         private fun stopRecordingTicker() {
    +        recordingDuration = recordingTicker?.elapsedTime() ?: 0
             recordingTicker?.stop()
             recordingTicker = null
         }
    diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/voice/VoiceMessageViews.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/voice/VoiceMessageViews.kt
    index 09284ea5fc..7a76657923 100644
    --- a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/voice/VoiceMessageViews.kt
    +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/voice/VoiceMessageViews.kt
    @@ -27,7 +27,6 @@ import androidx.core.view.doOnLayout
     import androidx.core.view.isInvisible
     import androidx.core.view.isVisible
     import androidx.core.view.updateLayoutParams
    -import com.visualizer.amplitude.AudioRecordView
     import im.vector.app.R
     import im.vector.app.core.extensions.setAttributeBackground
     import im.vector.app.core.extensions.setAttributeTintedBackground
    @@ -37,6 +36,8 @@ import im.vector.app.databinding.ViewVoiceMessageRecorderBinding
     import im.vector.app.features.home.room.detail.composer.voice.VoiceMessageRecorderView.DraggingState
     import im.vector.app.features.home.room.detail.composer.voice.VoiceMessageRecorderView.RecordingUiState
     import im.vector.app.features.home.room.detail.timeline.helper.VoiceMessagePlaybackTracker
    +import im.vector.app.features.themes.ThemeUtils
    +import im.vector.app.features.voice.AudioWaveformView
     
     class VoiceMessageViews(
             private val resources: Resources,
    @@ -59,8 +60,21 @@ class VoiceMessageViews(
                 actions.onDeleteVoiceMessage()
             }
     
    -        views.voicePlaybackWaveform.setOnClickListener {
    -            actions.onWaveformClicked()
    +        views.voicePlaybackWaveform.setOnTouchListener { view, motionEvent ->
    +            when (motionEvent.action) {
    +                MotionEvent.ACTION_DOWN -> {
    +                    actions.onWaveformClicked()
    +                }
    +                MotionEvent.ACTION_UP   -> {
    +                    val percentage = getTouchedPositionPercentage(motionEvent, view)
    +                    actions.onVoiceWaveformTouchedUp(percentage)
    +                }
    +                MotionEvent.ACTION_MOVE -> {
    +                    val percentage = getTouchedPositionPercentage(motionEvent, view)
    +                    actions.onVoiceWaveformMoved(percentage)
    +                }
    +            }
    +            true
             }
     
             views.voicePlaybackControlButton.setOnClickListener {
    @@ -69,6 +83,8 @@ class VoiceMessageViews(
             observeMicButton(actions)
         }
     
    +    private fun getTouchedPositionPercentage(motionEvent: MotionEvent, view: View) = (motionEvent.x / view.width).coerceIn(0f, 1f)
    +
         @SuppressLint("ClickableViewAccessibility")
         private fun observeMicButton(actions: Actions) {
             val draggableStateProcessor = DraggableStateProcessor(resources, dimensionConverter)
    @@ -284,7 +300,7 @@ class VoiceMessageViews(
             hideRecordingViews(RecordingUiState.Idle)
             views.voiceMessageMicButton.isVisible = true
             views.voiceMessageSendButton.isVisible = false
    -        views.voicePlaybackWaveform.post { views.voicePlaybackWaveform.recreate() }
    +        views.voicePlaybackWaveform.post { views.voicePlaybackWaveform.clear() }
         }
     
         fun renderPlaying(state: VoiceMessagePlaybackTracker.Listener.State.Playing) {
    @@ -292,11 +308,15 @@ class VoiceMessageViews(
             views.voicePlaybackControlButton.contentDescription = resources.getString(R.string.a11y_pause_voice_message)
             val formattedTimerText = DateUtils.formatElapsedTime((state.playbackTime / 1000).toLong())
             views.voicePlaybackTime.text = formattedTimerText
    +        val waveformColorIdle = ThemeUtils.getColor(views.voicePlaybackWaveform.context, R.attr.vctr_content_quaternary)
    +        val waveformColorPlayed = ThemeUtils.getColor(views.voicePlaybackWaveform.context, R.attr.vctr_content_secondary)
    +        views.voicePlaybackWaveform.updateColors(state.percentage, waveformColorPlayed, waveformColorIdle)
         }
     
         fun renderIdle() {
             views.voicePlaybackControlButton.setImageResource(R.drawable.ic_play_pause_play)
             views.voicePlaybackControlButton.contentDescription = resources.getString(R.string.a11y_play_voice_message)
    +        views.voicePlaybackWaveform.summarize()
         }
     
         fun renderToast(message: String) {
    @@ -327,8 +347,9 @@ class VoiceMessageViews(
     
         fun renderRecordingWaveform(amplitudeList: Array) {
             views.voicePlaybackWaveform.doOnLayout { waveFormView ->
    +            val waveformColor = ThemeUtils.getColor(waveFormView.context, R.attr.vctr_content_quaternary)
                 amplitudeList.iterator().forEach {
    -                (waveFormView as AudioRecordView).update(it)
    +                (waveFormView as AudioWaveformView).add(AudioWaveformView.FFT(it.toFloat(), waveformColor))
                 }
             }
         }
    @@ -349,5 +370,7 @@ class VoiceMessageViews(
             fun onDeleteVoiceMessage()
             fun onWaveformClicked()
             fun onVoicePlaybackButtonClicked()
    +        fun onVoiceWaveformTouchedUp(percentage: Float)
    +        fun onVoiceWaveformMoved(percentage: Float)
         }
     }
    diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/search/SearchFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/search/SearchFragment.kt
    index 62c142238e..fbcf29d863 100644
    --- a/vector/src/main/java/im/vector/app/features/home/room/detail/search/SearchFragment.kt
    +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/search/SearchFragment.kt
    @@ -26,6 +26,7 @@ import androidx.recyclerview.widget.LinearLayoutManager
     import com.airbnb.mvrx.Fail
     import com.airbnb.mvrx.Loading
     import com.airbnb.mvrx.Success
    +import com.airbnb.mvrx.Uninitialized
     import com.airbnb.mvrx.args
     import com.airbnb.mvrx.fragmentViewModel
     import com.airbnb.mvrx.withState
    @@ -88,6 +89,7 @@ class SearchFragment @Inject constructor(
         override fun invalidate() = withState(searchViewModel) { state ->
             if (state.searchResult.isNullOrEmpty()) {
                 when (state.asyncSearchRequest) {
    +                Uninitialized,
                     is Loading -> {
                         views.stateView.state = StateView.State.Loading
                     }
    diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/search/SearchViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/search/SearchViewModel.kt
    index 7bff76cc36..1702fb95cd 100644
    --- a/vector/src/main/java/im/vector/app/features/home/room/detail/search/SearchViewModel.kt
    +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/search/SearchViewModel.kt
    @@ -25,7 +25,6 @@ import dagger.assisted.AssistedFactory
     import dagger.assisted.AssistedInject
     import im.vector.app.core.di.MavericksAssistedViewModelFactory
     import im.vector.app.core.di.hiltMavericksViewModelFactory
    -import im.vector.app.core.extensions.exhaustive
     import im.vector.app.core.platform.VectorViewModel
     import kotlinx.coroutines.CancellationException
     import kotlinx.coroutines.Job
    @@ -56,7 +55,7 @@ class SearchViewModel @AssistedInject constructor(
                 is SearchAction.SearchWith -> handleSearchWith(action)
                 is SearchAction.LoadMore   -> handleLoadMore()
                 is SearchAction.Retry      -> handleRetry()
    -        }.exhaustive
    +        }
         }
     
         private fun handleSearchWith(action: SearchAction.SearchWith) {
    diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/TimelineEventController.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/TimelineEventController.kt
    index a14888362b..023c28cdc7 100644
    --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/TimelineEventController.kt
    +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/TimelineEventController.kt
    @@ -145,6 +145,8 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec
             fun getPreviewUrlRetriever(): PreviewUrlRetriever
     
             fun onVoiceControlButtonClicked(eventId: String, messageAudioContent: MessageAudioContent)
    +        fun onVoiceWaveformTouchedUp(eventId: String, duration: Int, percentage: Float)
    +        fun onVoiceWaveformMovedTo(eventId: String, duration: Int, percentage: Float)
     
             fun onAddMoreReaction(event: TimelineEvent)
         }
    diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/edithistory/ViewEditHistoryEpoxyController.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/edithistory/ViewEditHistoryEpoxyController.kt
    index 1dad6cc4a7..9f05547300 100644
    --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/edithistory/ViewEditHistoryEpoxyController.kt
    +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/edithistory/ViewEditHistoryEpoxyController.kt
    @@ -18,8 +18,9 @@ package im.vector.app.features.home.room.detail.timeline.edithistory
     import android.text.Spannable
     import com.airbnb.epoxy.TypedEpoxyController
     import com.airbnb.mvrx.Fail
    -import com.airbnb.mvrx.Incomplete
    +import com.airbnb.mvrx.Loading
     import com.airbnb.mvrx.Success
    +import com.airbnb.mvrx.Uninitialized
     import im.vector.app.R
     import im.vector.app.core.date.DateFormatKind
     import im.vector.app.core.date.VectorDateFormatter
    @@ -54,18 +55,19 @@ class ViewEditHistoryEpoxyController @Inject constructor(
         override fun buildModels(state: ViewEditHistoryViewState) {
             val host = this
             when (state.editList) {
    -            is Incomplete -> {
    +            Uninitialized,
    +            is Loading -> {
                     genericLoaderItem {
                         id("Spinner")
                     }
                 }
    -            is Fail       -> {
    +            is Fail    -> {
                     genericFooterItem {
                         id("failure")
                         text(host.stringProvider.getString(R.string.unknown_error).toEpoxyCharSequence())
                     }
                 }
    -            is Success    -> {
    +            is Success -> {
                     state.editList()?.let { renderEvents(it, state.isOriginalAReply) }
                 }
             }
    diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt
    index f6bb39f20c..03fda1ee6c 100644
    --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt
    +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt
    @@ -85,6 +85,7 @@ import im.vector.app.features.poll.PollState.Sending
     import im.vector.app.features.poll.PollState.Undisclosed
     import im.vector.app.features.poll.PollState.Voted
     import im.vector.app.features.settings.VectorPreferences
    +import im.vector.app.features.voice.AudioWaveformView
     import im.vector.lib.core.utils.epoxy.charsequence.toEpoxyCharSequence
     import me.gujun.android.span.span
     import org.matrix.android.sdk.api.MatrixUrls.isMxcUrl
    @@ -390,11 +391,24 @@ class MessageItemFactory @Inject constructor(
                 }
             }
     
    +        val waveformTouchListener: MessageVoiceItem.WaveformTouchListener = object : MessageVoiceItem.WaveformTouchListener {
    +            override fun onWaveformTouchedUp(percentage: Float) {
    +                val duration = messageContent.audioInfo?.duration ?: 0
    +                params.callback?.onVoiceWaveformTouchedUp(informationData.eventId, duration, percentage)
    +            }
    +
    +            override fun onWaveformMovedTo(percentage: Float) {
    +                val duration = messageContent.audioInfo?.duration ?: 0
    +                params.callback?.onVoiceWaveformMovedTo(informationData.eventId, duration, percentage)
    +            }
    +        }
    +
             return MessageVoiceItem_()
                 .attributes(attributes)
                 .duration(messageContent.audioWaveformInfo?.duration ?: 0)
                 .waveform(messageContent.audioWaveformInfo?.waveform?.toFft().orEmpty())
                 .playbackControlButtonClickListener(playbackControlButtonClickListener)
    +            .waveformTouchListener(waveformTouchListener)
                 .voiceMessagePlaybackTracker(voiceMessagePlaybackTracker)
                 .izLocalFile(localFilesHelper.isLocalFile(fileUrl))
                 .izDownloaded(session.fileService().isFileInCache(
    @@ -749,8 +763,8 @@ class MessageItemFactory @Inject constructor(
             return this
                 ?.filterNotNull()
                 ?.map {
    -                // Value comes from AudioRecordView.maxReportableAmp, and 1024 is the max value in the Matrix spec
    -                it * 22760 / 1024
    +                // Value comes from AudioWaveformView.MAX_FFT, and 1024 is the max value in the Matrix spec
    +                it * AudioWaveformView.MAX_FFT / 1024
                 }
         }
     
    diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/ContentUploadStateTrackerBinder.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/ContentUploadStateTrackerBinder.kt
    index 0909cbe8de..9ff8ddfbce 100644
    --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/ContentUploadStateTrackerBinder.kt
    +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/ContentUploadStateTrackerBinder.kt
    @@ -26,7 +26,6 @@ import dagger.hilt.android.scopes.ActivityScoped
     import im.vector.app.R
     import im.vector.app.core.di.ActiveSessionHolder
     import im.vector.app.core.error.ErrorFormatter
    -import im.vector.app.core.extensions.exhaustive
     import im.vector.app.core.utils.TextUtils
     import im.vector.app.features.home.room.detail.timeline.MessageColorProvider
     import org.matrix.android.sdk.api.session.content.ContentUploadStateTracker
    @@ -86,7 +85,7 @@ private class ContentMediaProgressUpdater(private val progressLayout: ViewGroup,
                 is ContentUploadStateTracker.State.Success             -> handleSuccess()
                 is ContentUploadStateTracker.State.CompressingImage    -> handleCompressingImage()
                 is ContentUploadStateTracker.State.CompressingVideo    -> handleCompressingVideo(state)
    -        }.exhaustive
    +        }
         }
     
         private fun handleIdle() {
    diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/VoiceMessagePlaybackTracker.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/VoiceMessagePlaybackTracker.kt
    index c6204bff1c..8167ad94af 100644
    --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/VoiceMessagePlaybackTracker.kt
    +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/VoiceMessagePlaybackTracker.kt
    @@ -70,7 +70,8 @@ class VoiceMessagePlaybackTracker @Inject constructor() {
     
         fun startPlayback(id: String) {
             val currentPlaybackTime = getPlaybackTime(id)
    -        val currentState = Listener.State.Playing(currentPlaybackTime)
    +        val currentPercentage = getPercentage(id)
    +        val currentState = Listener.State.Playing(currentPlaybackTime, currentPercentage)
             setState(id, currentState)
             // Pause any active playback
             states
    @@ -87,15 +88,16 @@ class VoiceMessagePlaybackTracker @Inject constructor() {
     
         fun pausePlayback(id: String) {
             val currentPlaybackTime = getPlaybackTime(id)
    -        setState(id, Listener.State.Paused(currentPlaybackTime))
    +        val currentPercentage = getPercentage(id)
    +        setState(id, Listener.State.Paused(currentPlaybackTime, currentPercentage))
         }
     
         fun stopPlayback(id: String) {
             setState(id, Listener.State.Idle)
         }
     
    -    fun updateCurrentPlaybackTime(id: String, time: Int) {
    -        setState(id, Listener.State.Playing(time))
    +    fun updateCurrentPlaybackTime(id: String, time: Int, percentage: Float) {
    +        setState(id, Listener.State.Playing(time, percentage))
         }
     
         fun updateCurrentRecording(id: String, amplitudeList: List) {
    @@ -113,6 +115,15 @@ class VoiceMessagePlaybackTracker @Inject constructor() {
             }
         }
     
    +    private fun getPercentage(id: String): Float {
    +        return when (val state = states[id]) {
    +            is Listener.State.Playing -> state.percentage
    +            is Listener.State.Paused  -> state.percentage
    +            /* Listener.State.Idle, */
    +            else                      -> 0f
    +        }
    +    }
    +
         fun clear() {
             listeners.forEach {
                 it.value.onUpdate(Listener.State.Idle)
    @@ -131,8 +142,8 @@ class VoiceMessagePlaybackTracker @Inject constructor() {
     
             sealed class State {
                 object Idle : State()
    -            data class Playing(val playbackTime: Int) : State()
    -            data class Paused(val playbackTime: Int) : State()
    +            data class Playing(val playbackTime: Int, val percentage: Float) : State()
    +            data class Paused(val playbackTime: Int, val percentage: Float) : State()
                 data class Recording(val amplitudeList: List) : State()
             }
         }
    diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceItem.kt
    index e9f728d976..aad30ef41e 100644
    --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceItem.kt
    +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceItem.kt
    @@ -19,14 +19,15 @@ package im.vector.app.features.home.room.detail.timeline.item
     import android.content.res.ColorStateList
     import android.graphics.Color
     import android.text.format.DateUtils
    +import android.view.MotionEvent
     import android.view.View
     import android.view.ViewGroup
     import android.widget.ImageButton
     import android.widget.TextView
    +import androidx.core.view.doOnLayout
     import androidx.core.view.isVisible
     import com.airbnb.epoxy.EpoxyAttribute
     import com.airbnb.epoxy.EpoxyModelClass
    -import com.visualizer.amplitude.AudioRecordView
     import im.vector.app.R
     import im.vector.app.core.epoxy.ClickListener
     import im.vector.app.features.home.room.detail.timeline.helper.ContentDownloadStateTrackerBinder
    @@ -34,10 +35,16 @@ import im.vector.app.features.home.room.detail.timeline.helper.ContentUploadStat
     import im.vector.app.features.home.room.detail.timeline.helper.VoiceMessagePlaybackTracker
     import im.vector.app.features.home.room.detail.timeline.style.TimelineMessageLayout
     import im.vector.app.features.themes.ThemeUtils
    +import im.vector.app.features.voice.AudioWaveformView
     
     @EpoxyModelClass(layout = R.layout.item_timeline_event_base)
     abstract class MessageVoiceItem : AbsMessageItem() {
     
    +    interface WaveformTouchListener {
    +        fun onWaveformTouchedUp(percentage: Float)
    +        fun onWaveformMovedTo(percentage: Float)
    +    }
    +
         @EpoxyAttribute
         var mxcUrl: String = ""
     
    @@ -62,6 +69,9 @@ abstract class MessageVoiceItem : AbsMessageItem() {
         @EpoxyAttribute(EpoxyAttribute.Option.DoNotHash)
         var playbackControlButtonClickListener: ClickListener? = null
     
    +    @EpoxyAttribute(EpoxyAttribute.Option.DoNotHash)
    +    var waveformTouchListener: WaveformTouchListener? = null
    +
         @EpoxyAttribute
         lateinit var voiceMessagePlaybackTracker: VoiceMessagePlaybackTracker
     
    @@ -76,13 +86,8 @@ abstract class MessageVoiceItem : AbsMessageItem() {
                 holder.progressLayout.isVisible = false
             }
     
    -        holder.voicePlaybackWaveform.setOnLongClickListener(attributes.itemLongClickListener)
    -
    -        holder.voicePlaybackWaveform.post {
    -            holder.voicePlaybackWaveform.recreate()
    -            waveform.forEach { amplitude ->
    -                holder.voicePlaybackWaveform.update(amplitude)
    -            }
    +        holder.voicePlaybackWaveform.doOnLayout {
    +            onWaveformViewReady(holder)
             }
     
             val backgroundTint = if (attributes.informationData.messageLayout is TimelineMessageLayout.Bubble) {
    @@ -92,34 +97,67 @@ abstract class MessageVoiceItem : AbsMessageItem() {
             }
             holder.voicePlaybackLayout.backgroundTintList = ColorStateList.valueOf(backgroundTint)
             holder.voicePlaybackControlButton.setOnClickListener { playbackControlButtonClickListener?.invoke(it) }
    +    }
    +
    +    private fun onWaveformViewReady(holder: Holder) {
    +        holder.voicePlaybackWaveform.setOnLongClickListener(attributes.itemLongClickListener)
    +
    +        val waveformColorIdle = ThemeUtils.getColor(holder.view.context, R.attr.vctr_content_quaternary)
    +        val waveformColorPlayed = ThemeUtils.getColor(holder.view.context, R.attr.vctr_content_secondary)
    +
    +        holder.voicePlaybackWaveform.clear()
    +        waveform.forEach { amplitude ->
    +            holder.voicePlaybackWaveform.add(AudioWaveformView.FFT(amplitude.toFloat(), waveformColorIdle))
    +        }
    +        holder.voicePlaybackWaveform.summarize()
    +
    +        holder.voicePlaybackWaveform.setOnTouchListener { view, motionEvent ->
    +            when (motionEvent.action) {
    +                MotionEvent.ACTION_UP   -> {
    +                    val percentage = getTouchedPositionPercentage(motionEvent, view)
    +                    waveformTouchListener?.onWaveformTouchedUp(percentage)
    +                }
    +                MotionEvent.ACTION_MOVE -> {
    +                    val percentage = getTouchedPositionPercentage(motionEvent, view)
    +                    waveformTouchListener?.onWaveformMovedTo(percentage)
    +                }
    +            }
    +            true
    +        }
     
             voiceMessagePlaybackTracker.track(attributes.informationData.eventId, object : VoiceMessagePlaybackTracker.Listener {
                 override fun onUpdate(state: VoiceMessagePlaybackTracker.Listener.State) {
                     when (state) {
    -                    is VoiceMessagePlaybackTracker.Listener.State.Idle    -> renderIdleState(holder)
    -                    is VoiceMessagePlaybackTracker.Listener.State.Playing -> renderPlayingState(holder, state)
    -                    is VoiceMessagePlaybackTracker.Listener.State.Paused  -> renderPausedState(holder, state)
    +                    is VoiceMessagePlaybackTracker.Listener.State.Idle    -> renderIdleState(holder, waveformColorIdle, waveformColorPlayed)
    +                    is VoiceMessagePlaybackTracker.Listener.State.Playing -> renderPlayingState(holder, state, waveformColorIdle, waveformColorPlayed)
    +                    is VoiceMessagePlaybackTracker.Listener.State.Paused  -> renderPausedState(holder, state, waveformColorIdle, waveformColorPlayed)
    +                    is VoiceMessagePlaybackTracker.Listener.State.Recording -> Unit
                     }
                 }
             })
         }
     
    -    private fun renderIdleState(holder: Holder) {
    +    private fun getTouchedPositionPercentage(motionEvent: MotionEvent, view: View) = (motionEvent.x / view.width).coerceIn(0f, 1f)
    +
    +    private fun renderIdleState(holder: Holder, idleColor: Int, playedColor: Int) {
             holder.voicePlaybackControlButton.setImageResource(R.drawable.ic_play_pause_play)
             holder.voicePlaybackControlButton.contentDescription = holder.view.context.getString(R.string.a11y_play_voice_message)
             holder.voicePlaybackTime.text = formatPlaybackTime(duration)
    +        holder.voicePlaybackWaveform.updateColors(0f, playedColor, idleColor)
         }
     
    -    private fun renderPlayingState(holder: Holder, state: VoiceMessagePlaybackTracker.Listener.State.Playing) {
    +    private fun renderPlayingState(holder: Holder, state: VoiceMessagePlaybackTracker.Listener.State.Playing, idleColor: Int, playedColor: Int) {
             holder.voicePlaybackControlButton.setImageResource(R.drawable.ic_play_pause_pause)
             holder.voicePlaybackControlButton.contentDescription = holder.view.context.getString(R.string.a11y_pause_voice_message)
             holder.voicePlaybackTime.text = formatPlaybackTime(state.playbackTime)
    +        holder.voicePlaybackWaveform.updateColors(state.percentage, playedColor, idleColor)
         }
     
    -    private fun renderPausedState(holder: Holder, state: VoiceMessagePlaybackTracker.Listener.State.Paused) {
    +    private fun renderPausedState(holder: Holder, state: VoiceMessagePlaybackTracker.Listener.State.Paused, idleColor: Int, playedColor: Int) {
             holder.voicePlaybackControlButton.setImageResource(R.drawable.ic_play_pause_play)
             holder.voicePlaybackControlButton.contentDescription = holder.view.context.getString(R.string.a11y_play_voice_message)
             holder.voicePlaybackTime.text = formatPlaybackTime(state.playbackTime)
    +        holder.voicePlaybackWaveform.updateColors(state.percentage, playedColor, idleColor)
         }
     
         private fun formatPlaybackTime(time: Int) = DateUtils.formatElapsedTime((time / 1000).toLong())
    @@ -138,7 +176,7 @@ abstract class MessageVoiceItem : AbsMessageItem() {
             val voiceLayout by bind(R.id.voiceLayout)
             val voicePlaybackControlButton by bind(R.id.voicePlaybackControlButton)
             val voicePlaybackTime by bind(R.id.voicePlaybackTime)
    -        val voicePlaybackWaveform by bind(R.id.voicePlaybackWaveform)
    +        val voicePlaybackWaveform by bind(R.id.voicePlaybackWaveform)
             val progressLayout by bind(R.id.messageFileUploadProgressLayout)
         }
     
    diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/PollOptionView.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/PollOptionView.kt
    index 2be933d9c3..80daa595b6 100644
    --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/PollOptionView.kt
    +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/PollOptionView.kt
    @@ -23,7 +23,6 @@ import androidx.appcompat.content.res.AppCompatResources
     import androidx.constraintlayout.widget.ConstraintLayout
     import androidx.core.view.isVisible
     import im.vector.app.R
    -import im.vector.app.core.extensions.exhaustive
     import im.vector.app.core.extensions.setAttributeTintedImageResource
     import im.vector.app.databinding.ItemPollOptionBinding
     
    @@ -49,7 +48,7 @@ class PollOptionView @JvmOverloads constructor(
                 is PollOptionViewState.PollReady       -> renderPollReady()
                 is PollOptionViewState.PollVoted       -> renderPollVoted(state)
                 is PollOptionViewState.PollUndisclosed -> renderPollUndisclosed(state)
    -        }.exhaustive
    +        }
         }
     
         private fun renderPollSending() {
    diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/VerificationRequestItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/VerificationRequestItem.kt
    index 821531416b..61fcddd123 100644
    --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/VerificationRequestItem.kt
    +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/VerificationRequestItem.kt
    @@ -31,7 +31,6 @@ import com.airbnb.epoxy.EpoxyModelClass
     import im.vector.app.R
     import im.vector.app.core.epoxy.ClickListener
     import im.vector.app.core.epoxy.onClick
    -import im.vector.app.core.extensions.exhaustive
     import im.vector.app.features.home.AvatarRenderer
     import im.vector.app.features.home.room.detail.RoomDetailAction
     import im.vector.app.features.home.room.detail.timeline.MessageColorProvider
    @@ -105,7 +104,7 @@ abstract class VerificationRequestItem : AbsBaseMessageItem {
    +            Uninitialized,
    +            is Loading -> {
                     genericLoaderItem {
                         id("Spinner")
                     }
                 }
    -            is Fail       -> {
    +            is Fail    -> {
                     genericFooterItem {
                         id("failure")
                         text(host.stringProvider.getString(R.string.unknown_error).toEpoxyCharSequence())
                     }
                 }
    -            is Success    -> {
    +            is Success -> {
                     state.mapReactionKeyToMemberList()?.forEach { reactionInfo ->
                         reactionInfoSimpleItem {
                             id(reactionInfo.eventId)
    diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListFragment.kt
    index 4265eebe62..18389b551f 100644
    --- a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListFragment.kt
    +++ b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListFragment.kt
    @@ -38,7 +38,6 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder
     import im.vector.app.R
     import im.vector.app.core.epoxy.LayoutManagerStateRestorer
     import im.vector.app.core.extensions.cleanup
    -import im.vector.app.core.extensions.exhaustive
     import im.vector.app.core.platform.OnBackPressed
     import im.vector.app.core.platform.StateView
     import im.vector.app.core.platform.VectorBaseFragment
    @@ -128,7 +127,7 @@ class RoomListFragment @Inject constructor(
                     is RoomListViewEvents.SelectRoom                -> handleSelectRoom(it, it.isInviteAlreadyAccepted)
                     is RoomListViewEvents.Done                      -> Unit
                     is RoomListViewEvents.NavigateToMxToBottomSheet -> handleShowMxToLink(it.link)
    -            }.exhaustive
    +            }
             }
     
             views.createChatFabMenu.listener = this
    @@ -418,7 +417,7 @@ class RoomListFragment @Inject constructor(
                 is RoomListQuickActionsSharedAction.Leave                     -> {
                     promptLeaveRoom(quickAction.roomId)
                 }
    -        }.exhaustive
    +        }
         }
     
         private fun promptLeaveRoom(roomId: String) {
    diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListViewModel.kt
    index ec8b01876b..70974bc1f6 100644
    --- a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListViewModel.kt
    +++ b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListViewModel.kt
    @@ -29,7 +29,6 @@ import im.vector.app.AppStateHandler
     import im.vector.app.RoomGroupingMethod
     import im.vector.app.core.di.MavericksAssistedViewModelFactory
     import im.vector.app.core.di.hiltMavericksViewModelFactory
    -import im.vector.app.core.extensions.exhaustive
     import im.vector.app.core.platform.VectorViewModel
     import im.vector.app.core.resources.StringProvider
     import im.vector.app.features.analytics.AnalyticsTracker
    @@ -163,7 +162,7 @@ class RoomListViewModel @AssistedInject constructor(
                 is RoomListAction.ToggleSection               -> handleToggleSection(action.section)
                 is RoomListAction.JoinSuggestedRoom           -> handleJoinSuggestedRoom(action)
                 is RoomListAction.ShowRoomDetails             -> handleShowRoomDetails(action)
    -        }.exhaustive
    +        }
         }
     
         fun isPublicRoom(roomId: String): Boolean {
    diff --git a/vector/src/main/java/im/vector/app/features/invite/InviteUsersToRoomActivity.kt b/vector/src/main/java/im/vector/app/features/invite/InviteUsersToRoomActivity.kt
    index 48a70fb164..7bb6670e96 100644
    --- a/vector/src/main/java/im/vector/app/features/invite/InviteUsersToRoomActivity.kt
    +++ b/vector/src/main/java/im/vector/app/features/invite/InviteUsersToRoomActivity.kt
    @@ -74,8 +74,7 @@ class InviteUsersToRoomActivity : SimpleFragmentActivity() {
                             is UserListSharedAction.OnMenuItemSelected -> onMenuItemSelected(sharedAction)
                             UserListSharedAction.OpenPhoneBook         -> openPhoneBook()
                             // not exhaustive because it's a sharedAction
    -                        else                                       -> {
    -                        }
    +                        else                                       -> Unit
                         }
                     }
                     .launchIn(lifecycleScope)
    diff --git a/vector/src/main/java/im/vector/app/features/location/LocationSharingFragment.kt b/vector/src/main/java/im/vector/app/features/location/LocationSharingFragment.kt
    index c4dccc1b73..d61d53ae51 100644
    --- a/vector/src/main/java/im/vector/app/features/location/LocationSharingFragment.kt
    +++ b/vector/src/main/java/im/vector/app/features/location/LocationSharingFragment.kt
    @@ -29,7 +29,6 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder
     import com.mapbox.mapboxsdk.maps.MapView
     import im.vector.app.BuildConfig
     import im.vector.app.R
    -import im.vector.app.core.extensions.exhaustive
     import im.vector.app.core.platform.VectorBaseFragment
     import im.vector.app.core.utils.PERMISSIONS_FOR_BACKGROUND_LOCATION_SHARING
     import im.vector.app.core.utils.PERMISSIONS_FOR_FOREGROUND_LOCATION_SHARING
    @@ -86,7 +85,7 @@ class LocationSharingFragment @Inject constructor(
                     LocationSharingViewEvents.Close                     -> locationSharingNavigator.quit()
                     LocationSharingViewEvents.LocationNotAvailableError -> handleLocationNotAvailableError()
                     is LocationSharingViewEvents.ZoomToUserLocation     -> handleZoomToUserLocationEvent(it)
    -            }.exhaustive
    +            }
             }
         }
     
    diff --git a/vector/src/main/java/im/vector/app/features/location/LocationSharingViewModel.kt b/vector/src/main/java/im/vector/app/features/location/LocationSharingViewModel.kt
    index 639666e63f..1d68247f2c 100644
    --- a/vector/src/main/java/im/vector/app/features/location/LocationSharingViewModel.kt
    +++ b/vector/src/main/java/im/vector/app/features/location/LocationSharingViewModel.kt
    @@ -23,7 +23,6 @@ import dagger.assisted.AssistedFactory
     import dagger.assisted.AssistedInject
     import im.vector.app.core.di.MavericksAssistedViewModelFactory
     import im.vector.app.core.di.hiltMavericksViewModelFactory
    -import im.vector.app.core.extensions.exhaustive
     import im.vector.app.core.platform.VectorViewModel
     import im.vector.app.features.home.room.detail.timeline.helper.LocationPinProvider
     import im.vector.app.features.location.domain.usecase.CompareLocationsUseCase
    @@ -122,7 +121,7 @@ class LocationSharingViewModel @AssistedInject constructor(
                 is LocationSharingAction.LocationTargetChange    -> handleLocationTargetChangeAction(action)
                 LocationSharingAction.ZoomToUserLocation         -> handleZoomToUserLocationAction()
                 LocationSharingAction.StartLiveLocationSharing   -> handleStartLiveLocationSharingAction()
    -        }.exhaustive
    +        }
         }
     
         private fun handleCurrentUserLocationSharingAction() = withState { state ->
    diff --git a/vector/src/main/java/im/vector/app/features/login/AbstractLoginFragment.kt b/vector/src/main/java/im/vector/app/features/login/AbstractLoginFragment.kt
    index 8b83873142..f5e48e84e7 100644
    --- a/vector/src/main/java/im/vector/app/features/login/AbstractLoginFragment.kt
    +++ b/vector/src/main/java/im/vector/app/features/login/AbstractLoginFragment.kt
    @@ -26,7 +26,6 @@ import com.airbnb.mvrx.withState
     import com.google.android.material.dialog.MaterialAlertDialogBuilder
     import im.vector.app.R
     import im.vector.app.core.dialogs.UnrecognizedCertificateDialog
    -import im.vector.app.core.extensions.exhaustive
     import im.vector.app.core.platform.OnBackPressed
     import im.vector.app.core.platform.VectorBaseFragment
     import kotlinx.coroutines.CancellationException
    @@ -69,7 +68,7 @@ abstract class AbstractLoginFragment : VectorBaseFragment(
                 else                       ->
                     // This is handled by the Activity
                     Unit
    -        }.exhaustive
    +        }
         }
     
         override fun showFailure(throwable: Throwable) {
    diff --git a/vector/src/main/java/im/vector/app/features/login/LoginActivity.kt b/vector/src/main/java/im/vector/app/features/login/LoginActivity.kt
    index a40f26acec..dec6fef040 100644
    --- a/vector/src/main/java/im/vector/app/features/login/LoginActivity.kt
    +++ b/vector/src/main/java/im/vector/app/features/login/LoginActivity.kt
    @@ -35,7 +35,6 @@ import im.vector.app.R
     import im.vector.app.core.extensions.POP_BACK_STACK_EXCLUSIVE
     import im.vector.app.core.extensions.addFragment
     import im.vector.app.core.extensions.addFragmentToBackstack
    -import im.vector.app.core.extensions.exhaustive
     import im.vector.app.core.extensions.validateBackPressed
     import im.vector.app.core.platform.VectorBaseActivity
     import im.vector.app.databinding.ActivityLoginBinding
    @@ -197,7 +196,7 @@ open class LoginActivity : VectorBaseActivity(), UnlockedA
                 is LoginViewEvents.Loading                                    ->
                     // This is handled by the Fragments
                     Unit
    -        }.exhaustive
    +        }
         }
     
         private fun updateWithState(loginViewState: LoginViewState) {
    @@ -260,13 +259,13 @@ open class LoginActivity : VectorBaseActivity(), UnlockedA
                                 tag = FRAGMENT_LOGIN_TAG,
                                 option = commonOption)
                         LoginMode.Unsupported -> onLoginModeNotSupported(state.loginModeSupportedTypes)
    -                }.exhaustive
    +                }
                 }
                 SignMode.SignInWithMatrixId -> addFragmentToBackstack(views.loginFragmentContainer,
                         LoginFragment::class.java,
                         tag = FRAGMENT_LOGIN_TAG,
                         option = commonOption)
    -        }.exhaustive
    +        }
         }
     
         /**
    diff --git a/vector/src/main/java/im/vector/app/features/login/LoginFragment.kt b/vector/src/main/java/im/vector/app/features/login/LoginFragment.kt
    index da61d95997..22f8792078 100644
    --- a/vector/src/main/java/im/vector/app/features/login/LoginFragment.kt
    +++ b/vector/src/main/java/im/vector/app/features/login/LoginFragment.kt
    @@ -29,8 +29,8 @@ import androidx.lifecycle.lifecycleScope
     import com.airbnb.mvrx.Fail
     import com.airbnb.mvrx.Loading
     import com.airbnb.mvrx.Success
    +import com.airbnb.mvrx.Uninitialized
     import im.vector.app.R
    -import im.vector.app.core.extensions.exhaustive
     import im.vector.app.core.extensions.hideKeyboard
     import im.vector.app.core.extensions.hidePassword
     import im.vector.app.core.extensions.toReducedUrl
    @@ -97,7 +97,7 @@ class LoginFragment @Inject constructor() : AbstractSSOLoginFragment SocialLoginButtonsView.Mode.MODE_SIGN_UP
                 SignMode.SignIn,
                 SignMode.SignInWithMatrixId -> SocialLoginButtonsView.Mode.MODE_SIGN_IN
    -        }.exhaustive
    +        }
         }
     
         private fun submit() {
    @@ -269,6 +269,7 @@ class LoginFragment @Inject constructor() : AbstractSSOLoginFragment {
                     // Ensure password is hidden
                     views.passwordField.hidePassword()
    @@ -300,7 +301,7 @@ class LoginFragment @Inject constructor() : AbstractSSOLoginFragment Unit
    +            else       -> Unit
             }
         }
     
    diff --git a/vector/src/main/java/im/vector/app/features/login/LoginResetPasswordFragment.kt b/vector/src/main/java/im/vector/app/features/login/LoginResetPasswordFragment.kt
    index d121245532..1d32944f9f 100644
    --- a/vector/src/main/java/im/vector/app/features/login/LoginResetPasswordFragment.kt
    +++ b/vector/src/main/java/im/vector/app/features/login/LoginResetPasswordFragment.kt
    @@ -23,7 +23,6 @@ import android.view.ViewGroup
     import androidx.lifecycle.lifecycleScope
     import com.airbnb.mvrx.Fail
     import com.airbnb.mvrx.Loading
    -import com.airbnb.mvrx.Success
     import com.google.android.material.dialog.MaterialAlertDialogBuilder
     import im.vector.app.R
     import im.vector.app.core.extensions.hideKeyboard
    @@ -129,7 +128,7 @@ class LoginResetPasswordFragment @Inject constructor() : AbstractLoginFragment {
                     views.resetPasswordEmailTil.error = errorFormatter.toHumanReadable(state.asyncResetPassword.error)
                 }
    -            is Success -> Unit
    +            else       -> Unit
             }
         }
     }
    diff --git a/vector/src/main/java/im/vector/app/features/login/LoginResetPasswordMailConfirmationFragment.kt b/vector/src/main/java/im/vector/app/features/login/LoginResetPasswordMailConfirmationFragment.kt
    index 5f376700f8..232e7ab622 100644
    --- a/vector/src/main/java/im/vector/app/features/login/LoginResetPasswordMailConfirmationFragment.kt
    +++ b/vector/src/main/java/im/vector/app/features/login/LoginResetPasswordMailConfirmationFragment.kt
    @@ -21,7 +21,6 @@ import android.view.LayoutInflater
     import android.view.View
     import android.view.ViewGroup
     import com.airbnb.mvrx.Fail
    -import com.airbnb.mvrx.Success
     import com.google.android.material.dialog.MaterialAlertDialogBuilder
     import im.vector.app.R
     import im.vector.app.databinding.FragmentLoginResetPasswordMailConfirmationBinding
    @@ -59,7 +58,7 @@ class LoginResetPasswordMailConfirmationFragment @Inject constructor() : Abstrac
             setupUi(state)
     
             when (state.asyncResetMailConfirmed) {
    -            is Fail    -> {
    +            is Fail -> {
                     // Link in email not yet clicked ?
                     val message = if (state.asyncResetMailConfirmed.error.is401()) {
                         getString(R.string.auth_reset_password_error_unauthorized)
    @@ -73,7 +72,7 @@ class LoginResetPasswordMailConfirmationFragment @Inject constructor() : Abstrac
                             .setPositiveButton(R.string.ok, null)
                             .show()
                 }
    -            is Success -> Unit
    +            else    -> Unit
             }
         }
     }
    diff --git a/vector/src/main/java/im/vector/app/features/login/LoginViewModel.kt b/vector/src/main/java/im/vector/app/features/login/LoginViewModel.kt
    index bfa924c155..246c3ad464 100644
    --- a/vector/src/main/java/im/vector/app/features/login/LoginViewModel.kt
    +++ b/vector/src/main/java/im/vector/app/features/login/LoginViewModel.kt
    @@ -31,7 +31,6 @@ import im.vector.app.core.di.ActiveSessionHolder
     import im.vector.app.core.di.MavericksAssistedViewModelFactory
     import im.vector.app.core.di.hiltMavericksViewModelFactory
     import im.vector.app.core.extensions.configureAndStart
    -import im.vector.app.core.extensions.exhaustive
     import im.vector.app.core.platform.VectorViewModel
     import im.vector.app.core.resources.StringProvider
     import im.vector.app.core.utils.ensureTrailingSlash
    @@ -131,7 +130,7 @@ class LoginViewModel @AssistedInject constructor(
                 is LoginAction.UserAcceptCertificate      -> handleUserAcceptCertificate(action)
                 LoginAction.ClearHomeServerHistory        -> handleClearHomeServerHistory()
                 is LoginAction.PostViewEvent              -> _viewEvents.post(action.viewEvent)
    -        }.exhaustive
    +        }
         }
     
         private fun handleOnGetStarted(action: LoginAction.OnGetStarted) {
    @@ -173,6 +172,7 @@ class LoginViewModel @AssistedInject constructor(
                                     .withAllowedFingerPrints(listOf(action.fingerprint))
                                     .build()
                     )
    +            else                            -> Unit
             }
         }
     
    @@ -447,7 +447,7 @@ class LoginViewModel @AssistedInject constructor(
                     handle(LoginAction.UpdateHomeServer(matrixOrgUrl))
                 ServerType.EMS,
                 ServerType.Other     -> _viewEvents.post(LoginViewEvents.OnServerSelectionDone(action.serverType))
    -        }.exhaustive
    +        }
         }
     
         private fun handleInitWith(action: LoginAction.InitWith) {
    @@ -555,7 +555,7 @@ class LoginViewModel @AssistedInject constructor(
                 SignMode.SignIn             -> handleLogin(action)
                 SignMode.SignUp             -> handleRegisterWith(action)
                 SignMode.SignInWithMatrixId -> handleDirectLogin(action, null)
    -        }.exhaustive
    +        }
         }
     
         private fun handleDirectLogin(action: LoginAction.LoginOrRegister, homeServerConnectionConfig: HomeServerConnectionConfig?) {
    @@ -585,7 +585,7 @@ class LoginViewModel @AssistedInject constructor(
                     else                          -> {
                         onWellKnownError()
                     }
    -            }.exhaustive
    +            }
             }
         }
     
    diff --git a/vector/src/main/java/im/vector/app/features/login2/AbstractLoginFragment2.kt b/vector/src/main/java/im/vector/app/features/login2/AbstractLoginFragment2.kt
    index 8c9749d91e..68568d1420 100644
    --- a/vector/src/main/java/im/vector/app/features/login2/AbstractLoginFragment2.kt
    +++ b/vector/src/main/java/im/vector/app/features/login2/AbstractLoginFragment2.kt
    @@ -26,7 +26,6 @@ import com.airbnb.mvrx.withState
     import com.google.android.material.dialog.MaterialAlertDialogBuilder
     import im.vector.app.R
     import im.vector.app.core.dialogs.UnrecognizedCertificateDialog
    -import im.vector.app.core.extensions.exhaustive
     import im.vector.app.core.platform.OnBackPressed
     import im.vector.app.core.platform.VectorBaseFragment
     import kotlinx.coroutines.CancellationException
    @@ -67,7 +66,7 @@ abstract class AbstractLoginFragment2 : VectorBaseFragment
                 else                        ->
                     // This is handled by the Activity
                     Unit
    -        }.exhaustive
    +        }
         }
     
         override fun showFailure(throwable: Throwable) {
    diff --git a/vector/src/main/java/im/vector/app/features/login2/LoginViewModel2.kt b/vector/src/main/java/im/vector/app/features/login2/LoginViewModel2.kt
    index b73988126b..8125c6e089 100644
    --- a/vector/src/main/java/im/vector/app/features/login2/LoginViewModel2.kt
    +++ b/vector/src/main/java/im/vector/app/features/login2/LoginViewModel2.kt
    @@ -18,7 +18,6 @@ package im.vector.app.features.login2
     
     import android.content.Context
     import android.net.Uri
    -import androidx.lifecycle.viewModelScope
     import com.airbnb.mvrx.Loading
     import com.airbnb.mvrx.MavericksViewModelFactory
     import dagger.assisted.Assisted
    @@ -29,7 +28,6 @@ import im.vector.app.core.di.ActiveSessionHolder
     import im.vector.app.core.di.MavericksAssistedViewModelFactory
     import im.vector.app.core.di.hiltMavericksViewModelFactory
     import im.vector.app.core.extensions.configureAndStart
    -import im.vector.app.core.extensions.exhaustive
     import im.vector.app.core.extensions.tryAsync
     import im.vector.app.core.platform.VectorViewModel
     import im.vector.app.core.resources.StringProvider
    @@ -137,7 +135,7 @@ class LoginViewModel2 @AssistedInject constructor(
                 LoginAction2.ClearHomeServerHistory        -> handleClearHomeServerHistory()
                 is LoginAction2.PostViewEvent              -> _viewEvents.post(action.viewEvent)
                 is LoginAction2.Finish                     -> handleFinish()
    -        }.exhaustive
    +        }
         }
     
         private fun handleFinish() {
    @@ -172,6 +170,7 @@ class LoginViewModel2 @AssistedInject constructor(
                     handleSetUserPassword(finalLastAction)
                 is LoginAction2.LoginWith        ->
                     handleLoginWith(finalLastAction)
    +            else                             -> Unit
             }
         }
     
    @@ -500,7 +499,7 @@ class LoginViewModel2 @AssistedInject constructor(
                 SignMode2.Unknown -> error("Developer error, invalid sign mode")
                 SignMode2.SignIn  -> handleSetUserNameForSignIn(action, null)
                 SignMode2.SignUp  -> handleSetUserNameForSignUp(action)
    -        }.exhaustive
    +        }
         }
     
         private fun handleSetUserPassword(action: LoginAction2.SetUserPassword) = withState { state ->
    @@ -508,7 +507,7 @@ class LoginViewModel2 @AssistedInject constructor(
                 SignMode2.Unknown -> error("Developer error, invalid sign mode")
                 SignMode2.SignIn  -> handleSignInWithPassword(action)
                 SignMode2.SignUp  -> handleRegisterWithPassword(action)
    -        }.exhaustive
    +        }
         }
     
         private fun handleRegisterWithPassword(action: LoginAction2.SetUserPassword) = withState { state ->
    @@ -588,7 +587,7 @@ class LoginViewModel2 @AssistedInject constructor(
                     else                          -> {
                         onWellKnownError()
                     }
    -            }.exhaustive
    +            }
             }
         }
     
    diff --git a/vector/src/main/java/im/vector/app/features/matrixto/MatrixToBottomSheet.kt b/vector/src/main/java/im/vector/app/features/matrixto/MatrixToBottomSheet.kt
    index 63e0398fc1..61dcd48779 100644
    --- a/vector/src/main/java/im/vector/app/features/matrixto/MatrixToBottomSheet.kt
    +++ b/vector/src/main/java/im/vector/app/features/matrixto/MatrixToBottomSheet.kt
    @@ -65,18 +65,17 @@ class MatrixToBottomSheet :
         override fun invalidate() = withState(viewModel) { state ->
             super.invalidate()
             when (state.linkType) {
    -            is PermalinkData.RoomLink     -> {
    +            is PermalinkData.RoomLink            -> {
                     views.matrixToCardContentLoading.isVisible = state.roomPeekResult is Incomplete
                     showFragment(MatrixToRoomSpaceFragment::class, Bundle())
                 }
    -            is PermalinkData.UserLink     -> {
    +            is PermalinkData.UserLink            -> {
                     views.matrixToCardContentLoading.isVisible = state.matrixItem is Incomplete
                     showFragment(MatrixToUserFragment::class, Bundle())
                 }
    -            is PermalinkData.GroupLink    -> {
    -            }
    -            is PermalinkData.FallbackLink -> {
    -            }
    +            is PermalinkData.GroupLink           -> Unit
    +            is PermalinkData.FallbackLink        -> Unit
    +            is PermalinkData.RoomEmailInviteLink -> Unit
             }
         }
     
    diff --git a/vector/src/main/java/im/vector/app/features/matrixto/MatrixToBottomSheetViewModel.kt b/vector/src/main/java/im/vector/app/features/matrixto/MatrixToBottomSheetViewModel.kt
    index e741f6fb39..04c2c8dd44 100644
    --- a/vector/src/main/java/im/vector/app/features/matrixto/MatrixToBottomSheetViewModel.kt
    +++ b/vector/src/main/java/im/vector/app/features/matrixto/MatrixToBottomSheetViewModel.kt
    @@ -28,7 +28,6 @@ import im.vector.app.R
     import im.vector.app.core.di.MavericksAssistedViewModelFactory
     import im.vector.app.core.di.hiltMavericksViewModelFactory
     import im.vector.app.core.error.ErrorFormatter
    -import im.vector.app.core.extensions.exhaustive
     import im.vector.app.core.platform.VectorViewModel
     import im.vector.app.core.resources.StringProvider
     import im.vector.app.features.createdirect.DirectRoomHelper
    @@ -49,8 +48,8 @@ class MatrixToBottomSheetViewModel @AssistedInject constructor(
             private val session: Session,
             private val stringProvider: StringProvider,
             private val directRoomHelper: DirectRoomHelper,
    -        private val errorFormatter: ErrorFormatter) :
    -    VectorViewModel(initialState) {
    +        private val errorFormatter: ErrorFormatter
    +) : VectorViewModel(initialState) {
     
         @AssistedFactory
         interface Factory : MavericksAssistedViewModelFactory {
    @@ -61,22 +60,23 @@ class MatrixToBottomSheetViewModel @AssistedInject constructor(
     
         init {
             when (initialState.linkType) {
    -            is PermalinkData.RoomLink     -> {
    +            is PermalinkData.RoomLink            -> {
                     setState {
                         copy(roomPeekResult = Loading())
                     }
                 }
    -            is PermalinkData.UserLink     -> {
    +            is PermalinkData.UserLink            -> {
                     setState {
                         copy(matrixItem = Loading())
                     }
                 }
    -            is PermalinkData.GroupLink    -> {
    +            is PermalinkData.GroupLink           -> {
                     // Not yet supported
                 }
    -            is PermalinkData.FallbackLink -> {
    +            is PermalinkData.FallbackLink        -> {
                     // Not yet supported
                 }
    +            is PermalinkData.RoomEmailInviteLink -> Unit
             }
             viewModelScope.launch(Dispatchers.IO) {
                 resolveLink(initialState)
    @@ -263,7 +263,7 @@ class MatrixToBottomSheetViewModel @AssistedInject constructor(
                 is MatrixToAction.OpenRoom              -> {
                     _viewEvents.post(MatrixToViewEvents.NavigateToRoom(action.roomId))
                 }
    -        }.exhaustive
    +        }
         }
     
         private fun handleJoinSpace(joinSpace: MatrixToAction.JoinSpace) {
    diff --git a/vector/src/main/java/im/vector/app/features/navigation/DefaultNavigator.kt b/vector/src/main/java/im/vector/app/features/navigation/DefaultNavigator.kt
    index 1dabdb5bf8..8ff70c2954 100644
    --- a/vector/src/main/java/im/vector/app/features/navigation/DefaultNavigator.kt
    +++ b/vector/src/main/java/im/vector/app/features/navigation/DefaultNavigator.kt
    @@ -320,6 +320,7 @@ class DefaultNavigator @Inject constructor(
                         }
                     }
                 }
    +            null                                -> Unit
             }
         }
     
    @@ -376,6 +377,7 @@ class DefaultNavigator @Inject constructor(
                         context.startActivity(intent)
                     }
                 }
    +            null                                -> Unit
             }
         }
     
    diff --git a/vector/src/main/java/im/vector/app/features/notifications/NotificationRenderer.kt b/vector/src/main/java/im/vector/app/features/notifications/NotificationRenderer.kt
    index 4078bb0b5c..e0e21a39a7 100644
    --- a/vector/src/main/java/im/vector/app/features/notifications/NotificationRenderer.kt
    +++ b/vector/src/main/java/im/vector/app/features/notifications/NotificationRenderer.kt
    @@ -47,11 +47,9 @@ class NotificationRenderer @Inject constructor(private val notificationDisplayer
                 )
     
                 // Remove summary first to avoid briefly displaying it after dismissing the last notification
    -            when (summaryNotification) {
    -                SummaryNotification.Removed -> {
    -                    Timber.d("Removing summary notification")
    -                    notificationDisplayer.cancelNotificationMessage(null, SUMMARY_NOTIFICATION_ID)
    -                }
    +            if (summaryNotification == SummaryNotification.Removed) {
    +                Timber.d("Removing summary notification")
    +                notificationDisplayer.cancelNotificationMessage(null, SUMMARY_NOTIFICATION_ID)
                 }
     
                 roomNotifications.forEach { wrapper ->
    @@ -94,11 +92,9 @@ class NotificationRenderer @Inject constructor(private val notificationDisplayer
                 }
     
                 // Update summary last to avoid briefly displaying it before other notifications
    -            when (summaryNotification) {
    -                is SummaryNotification.Update -> {
    -                    Timber.d("Updating summary notification")
    -                    notificationDisplayer.showNotificationMessage(null, SUMMARY_NOTIFICATION_ID, summaryNotification.notification)
    -                }
    +            if (summaryNotification is SummaryNotification.Update) {
    +                Timber.d("Updating summary notification")
    +                notificationDisplayer.showNotificationMessage(null, SUMMARY_NOTIFICATION_ID, summaryNotification.notification)
                 }
             }
         }
    diff --git a/vector/src/main/java/im/vector/app/features/onboarding/Login2Variant.kt b/vector/src/main/java/im/vector/app/features/onboarding/Login2Variant.kt
    index 107c08da5a..163af5d8d1 100644
    --- a/vector/src/main/java/im/vector/app/features/onboarding/Login2Variant.kt
    +++ b/vector/src/main/java/im/vector/app/features/onboarding/Login2Variant.kt
    @@ -30,7 +30,6 @@ import im.vector.app.R
     import im.vector.app.core.extensions.POP_BACK_STACK_EXCLUSIVE
     import im.vector.app.core.extensions.addFragment
     import im.vector.app.core.extensions.addFragmentToBackstack
    -import im.vector.app.core.extensions.exhaustive
     import im.vector.app.core.extensions.resetBackstack
     import im.vector.app.core.platform.VectorBaseActivity
     import im.vector.app.databinding.ActivityLoginBinding
    @@ -257,7 +256,7 @@ class Login2Variant(
                 is LoginViewEvents2.OnSessionCreated                           -> handleOnSessionCreated(event)
                 is LoginViewEvents2.Finish                                     -> terminate(true)
                 is LoginViewEvents2.CancelRegistration                         -> handleCancelRegistration()
    -        }.exhaustive
    +        }
         }
     
         private fun handleCancelRegistration() {
    diff --git a/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewModel.kt b/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewModel.kt
    index 6659058b4e..a18b9e62e0 100644
    --- a/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewModel.kt
    +++ b/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewModel.kt
    @@ -18,11 +18,7 @@ package im.vector.app.features.onboarding
     
     import android.content.Context
     import android.net.Uri
    -import com.airbnb.mvrx.Fail
    -import com.airbnb.mvrx.Loading
     import com.airbnb.mvrx.MavericksViewModelFactory
    -import com.airbnb.mvrx.Success
    -import com.airbnb.mvrx.Uninitialized
     import dagger.assisted.Assisted
     import dagger.assisted.AssistedFactory
     import dagger.assisted.AssistedInject
    @@ -31,7 +27,6 @@ import im.vector.app.core.di.ActiveSessionHolder
     import im.vector.app.core.di.MavericksAssistedViewModelFactory
     import im.vector.app.core.di.hiltMavericksViewModelFactory
     import im.vector.app.core.extensions.configureAndStart
    -import im.vector.app.core.extensions.exhaustive
     import im.vector.app.core.extensions.vectorStore
     import im.vector.app.core.platform.VectorViewModel
     import im.vector.app.core.resources.StringProvider
    @@ -166,7 +161,7 @@ class OnboardingViewModel @AssistedInject constructor(
                 OnboardingAction.SaveSelectedProfilePicture    -> updateProfilePicture()
                 is OnboardingAction.PostViewEvent              -> _viewEvents.post(action.viewEvent)
                 OnboardingAction.StopEmailValidationCheck      -> cancelWaitForEmailValidation()
    -        }.exhaustive
    +        }
         }
     
         private fun handleSplashAction(resetConfig: Boolean, onboardingFlow: OnboardingFlow) {
    @@ -222,6 +217,7 @@ class OnboardingViewModel @AssistedInject constructor(
                                     .withAllowedFingerPrints(listOf(action.fingerprint))
                                     .build()
                     )
    +            else                                 -> Unit
             }
         }
     
    @@ -239,31 +235,19 @@ class OnboardingViewModel @AssistedInject constructor(
             val safeLoginWizard = loginWizard
     
             if (safeLoginWizard == null) {
    -            setState {
    -                copy(
    -                        asyncLoginAction = Fail(Throwable("Bad configuration"))
    -                )
    -            }
    +            setState { copy(isLoading = false) }
    +            _viewEvents.post(OnboardingViewEvents.Failure(Throwable("Bad configuration")))
             } else {
    -            setState {
    -                copy(
    -                        asyncLoginAction = Loading()
    -                )
    -            }
    +            setState { copy(isLoading = true) }
     
                 currentJob = viewModelScope.launch {
                     try {
    -                    safeLoginWizard.loginWithToken(action.loginToken)
    +                    val result = safeLoginWizard.loginWithToken(action.loginToken)
    +                    onSessionCreated(result, isAccountCreated = false)
                     } catch (failure: Throwable) {
    +                    setState { copy(isLoading = false) }
                         _viewEvents.post(OnboardingViewEvents.Failure(failure))
    -                    setState {
    -                        copy(
    -                                asyncLoginAction = Fail(failure)
    -                        )
    -                    }
    -                    null
                     }
    -                        ?.let { onSessionCreated(it, isAccountCreated = false) }
                 }
             }
         }
    @@ -271,7 +255,7 @@ class OnboardingViewModel @AssistedInject constructor(
         private fun handleRegisterAction(action: RegisterAction) {
             currentJob = viewModelScope.launch {
                 if (action.hasLoadingState()) {
    -                setState { copy(asyncRegistration = Loading()) }
    +                setState { copy(isLoading = true) }
                 }
                 runCatching { registrationActionHandler.handleRegisterAction(registrationWizard, action) }
                         .fold(
    @@ -292,7 +276,7 @@ class OnboardingViewModel @AssistedInject constructor(
                                     }
                                 }
                         )
    -            setState { copy(asyncRegistration = Uninitialized) }
    +            setState { copy(isLoading = false) }
             }
         }
     
    @@ -322,7 +306,7 @@ class OnboardingViewModel @AssistedInject constructor(
                         authenticationService.reset()
                         setState {
                             copy(
    -                                asyncHomeServerLoginFlowRequest = Uninitialized,
    +                                isLoading = false,
                                     homeServerUrlFromUser = null,
                                     homeServerUrl = null,
                                     loginMode = LoginMode.Unknown,
    @@ -335,7 +319,7 @@ class OnboardingViewModel @AssistedInject constructor(
                 OnboardingAction.ResetSignMode       -> {
                     setState {
                         copy(
    -                            asyncHomeServerLoginFlowRequest = Uninitialized,
    +                            isLoading = false,
                                 signMode = SignMode.Unknown,
                                 loginMode = LoginMode.Unknown,
                                 loginModeSupportedTypes = emptyList()
    @@ -345,19 +329,13 @@ class OnboardingViewModel @AssistedInject constructor(
                 OnboardingAction.ResetLogin          -> {
                     viewModelScope.launch {
                         authenticationService.cancelPendingLoginOrRegistration()
    -                    setState {
    -                        copy(
    -                                asyncLoginAction = Uninitialized,
    -                                asyncRegistration = Uninitialized
    -                        )
    -                    }
    +                    setState { copy(isLoading = false) }
                     }
                 }
                 OnboardingAction.ResetResetPassword  -> {
                     setState {
                         copy(
    -                            asyncResetPassword = Uninitialized,
    -                            asyncResetMailConfirmed = Uninitialized,
    +                            isLoading = false,
                                 resetPasswordEmail = null
                         )
                     }
    @@ -403,7 +381,7 @@ class OnboardingViewModel @AssistedInject constructor(
                     handle(OnboardingAction.UpdateHomeServer(matrixOrgUrl))
                 ServerType.EMS,
                 ServerType.Other     -> _viewEvents.post(OnboardingViewEvents.OnServerSelectionDone(action.serverType))
    -        }.exhaustive
    +        }
         }
     
         private fun handleInitWith(action: OnboardingAction.InitWith) {
    @@ -426,35 +404,23 @@ class OnboardingViewModel @AssistedInject constructor(
             val safeLoginWizard = loginWizard
     
             if (safeLoginWizard == null) {
    -            setState {
    -                copy(
    -                        asyncResetPassword = Fail(Throwable("Bad configuration")),
    -                        asyncResetMailConfirmed = Uninitialized
    -                )
    -            }
    +            setState { copy(isLoading = false) }
    +            _viewEvents.post(OnboardingViewEvents.Failure(Throwable("Bad configuration")))
             } else {
    -            setState {
    -                copy(
    -                        asyncResetPassword = Loading(),
    -                        asyncResetMailConfirmed = Uninitialized
    -                )
    -            }
    +            setState { copy(isLoading = true) }
     
                 currentJob = viewModelScope.launch {
                     try {
                         safeLoginWizard.resetPassword(action.email, action.newPassword)
                     } catch (failure: Throwable) {
    -                    setState {
    -                        copy(
    -                                asyncResetPassword = Fail(failure)
    -                        )
    -                    }
    +                    setState { copy(isLoading = false) }
    +                    _viewEvents.post(OnboardingViewEvents.Failure(failure))
                         return@launch
                     }
     
                     setState {
                         copy(
    -                            asyncResetPassword = Success(Unit),
    +                            isLoading = false,
                                 resetPasswordEmail = action.email
                         )
                     }
    @@ -468,34 +434,22 @@ class OnboardingViewModel @AssistedInject constructor(
             val safeLoginWizard = loginWizard
     
             if (safeLoginWizard == null) {
    -            setState {
    -                copy(
    -                        asyncResetPassword = Uninitialized,
    -                        asyncResetMailConfirmed = Fail(Throwable("Bad configuration"))
    -                )
    -            }
    +            setState { copy(isLoading = false) }
    +            _viewEvents.post(OnboardingViewEvents.Failure(Throwable("Bad configuration")))
             } else {
    -            setState {
    -                copy(
    -                        asyncResetPassword = Uninitialized,
    -                        asyncResetMailConfirmed = Loading()
    -                )
    -            }
    +            setState { copy(isLoading = false) }
     
                 currentJob = viewModelScope.launch {
                     try {
                         safeLoginWizard.resetPasswordMailConfirmed()
                     } catch (failure: Throwable) {
    -                    setState {
    -                        copy(
    -                                asyncResetMailConfirmed = Fail(failure)
    -                        )
    -                    }
    +                    setState { copy(isLoading = false) }
    +                    _viewEvents.post(OnboardingViewEvents.Failure(failure))
                         return@launch
                     }
                     setState {
                         copy(
    -                            asyncResetMailConfirmed = Success(Unit),
    +                            isLoading = false,
                                 resetPasswordEmail = null
                         )
                     }
    @@ -511,15 +465,11 @@ class OnboardingViewModel @AssistedInject constructor(
                 SignMode.SignIn             -> handleLogin(action)
                 SignMode.SignUp             -> handleRegisterWith(action)
                 SignMode.SignInWithMatrixId -> handleDirectLogin(action, null)
    -        }.exhaustive
    +        }
         }
     
         private fun handleDirectLogin(action: OnboardingAction.LoginOrRegister, homeServerConnectionConfig: HomeServerConnectionConfig?) {
    -        setState {
    -            copy(
    -                    asyncLoginAction = Loading()
    -            )
    -        }
    +        setState { copy(isLoading = true) }
     
             currentJob = viewModelScope.launch {
                 val data = try {
    @@ -541,16 +491,12 @@ class OnboardingViewModel @AssistedInject constructor(
                     else                          -> {
                         onWellKnownError()
                     }
    -            }.exhaustive
    +            }
             }
         }
     
         private fun onWellKnownError() {
    -        setState {
    -            copy(
    -                    asyncLoginAction = Uninitialized
    -            )
    -        }
    +        setState { copy(isLoading = false) }
             _viewEvents.post(OnboardingViewEvents.Failure(Exception(stringProvider.getString(R.string.autodiscover_well_known_error))))
         }
     
    @@ -585,20 +531,12 @@ class OnboardingViewModel @AssistedInject constructor(
             when (failure) {
                 is MatrixIdFailure.InvalidMatrixId,
                 is Failure.UnrecognizedCertificateFailure -> {
    +                setState { copy(isLoading = false) }
                     // Display this error in a dialog
                     _viewEvents.post(OnboardingViewEvents.Failure(failure))
    -                setState {
    -                    copy(
    -                            asyncLoginAction = Uninitialized
    -                    )
    -                }
                 }
                 else                                      -> {
    -                setState {
    -                    copy(
    -                            asyncLoginAction = Fail(failure)
    -                    )
    -                }
    +                setState { copy(isLoading = false) }
                 }
             }
         }
    @@ -607,37 +545,23 @@ class OnboardingViewModel @AssistedInject constructor(
             val safeLoginWizard = loginWizard
     
             if (safeLoginWizard == null) {
    -            setState {
    -                copy(
    -                        asyncLoginAction = Fail(Throwable("Bad configuration"))
    -                )
    -            }
    +            setState { copy(isLoading = false) }
    +            _viewEvents.post(OnboardingViewEvents.Failure(Throwable("Bad configuration")))
             } else {
    -            setState {
    -                copy(
    -                        asyncLoginAction = Loading()
    -                )
    -            }
    -
    +            setState { copy(isLoading = true) }
                 currentJob = viewModelScope.launch {
                     try {
    -                    safeLoginWizard.login(
    +                    val result = safeLoginWizard.login(
                                 action.username,
                                 action.password,
                                 action.initialDeviceName
                         )
    +                    reAuthHelper.data = action.password
    +                    onSessionCreated(result, isAccountCreated = false)
                     } catch (failure: Throwable) {
    -                    setState {
    -                        copy(
    -                                asyncLoginAction = Fail(failure)
    -                        )
    -                    }
    -                    null
    +                    setState { copy(isLoading = false) }
    +                    _viewEvents.post(OnboardingViewEvents.Failure(failure))
                     }
    -                        ?.let {
    -                            reAuthHelper.data = action.password
    -                            onSessionCreated(it, isAccountCreated = false)
    -                        }
                 }
             }
         }
    @@ -678,12 +602,12 @@ class OnboardingViewModel @AssistedInject constructor(
                 true  -> {
                     val personalizationState = createPersonalizationState(session, state)
                     setState {
    -                    copy(asyncLoginAction = Success(Unit), personalizationState = personalizationState)
    +                    copy(isLoading = false, personalizationState = personalizationState)
                     }
                     _viewEvents.post(OnboardingViewEvents.OnAccountCreated)
                 }
                 false -> {
    -                setState { copy(asyncLoginAction = Success(Unit)) }
    +                setState { copy(isLoading = false) }
                     _viewEvents.post(OnboardingViewEvents.OnAccountSignedIn)
                 }
             }
    @@ -712,14 +636,11 @@ class OnboardingViewModel @AssistedInject constructor(
             } else {
                 currentJob = viewModelScope.launch {
                     try {
    -                    authenticationService.createSessionFromSso(homeServerConnectionConfigFinal, action.credentials)
    +                    val result = authenticationService.createSessionFromSso(homeServerConnectionConfigFinal, action.credentials)
    +                    onSessionCreated(result, isAccountCreated = false)
                     } catch (failure: Throwable) {
    -                    setState {
    -                        copy(asyncLoginAction = Fail(failure))
    -                    }
    -                    null
    +                    setState { copy(isLoading = false) }
                     }
    -                        ?.let { onSessionCreated(it, isAccountCreated = false) }
                 }
             }
         }
    @@ -743,7 +664,7 @@ class OnboardingViewModel @AssistedInject constructor(
     
                 setState {
                     copy(
    -                        asyncHomeServerLoginFlowRequest = Loading(),
    +                        isLoading = true,
                             // If user has entered https://matrix.org, ensure that server type is ServerType.MatrixOrg
                             // It is also useful to set the value again in the case of a certificate error on matrix.org
                             serverType = if (homeServerConnectionConfig.homeServerUri.toString() == matrixOrgUrl) {
    @@ -757,14 +678,14 @@ class OnboardingViewModel @AssistedInject constructor(
                 val data = try {
                     authenticationService.getLoginFlow(homeServerConnectionConfig)
                 } catch (failure: Throwable) {
    -                _viewEvents.post(OnboardingViewEvents.Failure(failure))
                     setState {
                         copy(
    -                            asyncHomeServerLoginFlowRequest = Uninitialized,
    +                            isLoading = false,
                                 // If we were trying to retrieve matrix.org login flow, also reset the serverType
                                 serverType = if (serverType == ServerType.MatrixOrg) ServerType.Unknown else serverType
                         )
                     }
    +                _viewEvents.post(OnboardingViewEvents.Failure(failure))
                     null
                 }
     
    @@ -785,7 +706,7 @@ class OnboardingViewModel @AssistedInject constructor(
     
                 setState {
                     copy(
    -                        asyncHomeServerLoginFlowRequest = Uninitialized,
    +                        isLoading = false,
                             homeServerUrlFromUser = homeServerConnectionConfig.homeServerUri.toString(),
                             homeServerUrl = data.homeServerUrl,
                             loginMode = loginMode,
    @@ -828,20 +749,20 @@ class OnboardingViewModel @AssistedInject constructor(
         }
     
         private fun updateDisplayName(displayName: String) {
    -        setState { copy(asyncDisplayName = Loading()) }
    +        setState { copy(isLoading = true) }
             viewModelScope.launch {
                 val activeSession = activeSessionHolder.getActiveSession()
                 try {
                     activeSession.setDisplayName(activeSession.myUserId, displayName)
                     setState {
                         copy(
    -                            asyncDisplayName = Success(Unit),
    +                            isLoading = false,
                                 personalizationState = personalizationState.copy(displayName = displayName)
                         )
                     }
                     handleDisplayNameStepComplete()
                 } catch (error: Throwable) {
    -                setState { copy(asyncDisplayName = Fail(error)) }
    +                setState { copy(isLoading = false) }
                     _viewEvents.post(OnboardingViewEvents.Failure(error))
                 }
             }
    @@ -883,7 +804,7 @@ class OnboardingViewModel @AssistedInject constructor(
                 when (val pictureUri = state.personalizationState.selectedPictureUri) {
                     null -> _viewEvents.post(OnboardingViewEvents.Failure(NullPointerException("picture uri is missing from state")))
                     else -> {
    -                    setState { copy(asyncProfilePicture = Loading()) }
    +                    setState { copy(isLoading = true) }
                         viewModelScope.launch {
                             val activeSession = activeSessionHolder.getActiveSession()
                             try {
    @@ -894,12 +815,12 @@ class OnboardingViewModel @AssistedInject constructor(
                                 )
                                 setState {
                                     copy(
    -                                        asyncProfilePicture = Success(Unit),
    +                                        isLoading = false,
                                     )
                                 }
                                 onProfilePictureSaved()
                             } catch (error: Throwable) {
    -                            setState { copy(asyncProfilePicture = Fail(error)) }
    +                            setState { copy(isLoading = false) }
                                 _viewEvents.post(OnboardingViewEvents.Failure(error))
                             }
                         }
    diff --git a/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewState.kt b/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewState.kt
    index 8747de6da8..b98e811679 100644
    --- a/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewState.kt
    +++ b/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewState.kt
    @@ -18,24 +18,15 @@ package im.vector.app.features.onboarding
     
     import android.net.Uri
     import android.os.Parcelable
    -import com.airbnb.mvrx.Async
    -import com.airbnb.mvrx.Loading
     import com.airbnb.mvrx.MavericksState
     import com.airbnb.mvrx.PersistState
    -import com.airbnb.mvrx.Uninitialized
     import im.vector.app.features.login.LoginMode
     import im.vector.app.features.login.ServerType
     import im.vector.app.features.login.SignMode
     import kotlinx.parcelize.Parcelize
     
     data class OnboardingViewState(
    -        val asyncLoginAction: Async = Uninitialized,
    -        val asyncHomeServerLoginFlowRequest: Async = Uninitialized,
    -        val asyncResetPassword: Async = Uninitialized,
    -        val asyncResetMailConfirmed: Async = Uninitialized,
    -        val asyncRegistration: Async = Uninitialized,
    -        val asyncDisplayName: Async = Uninitialized,
    -        val asyncProfilePicture: Async = Uninitialized,
    +        val isLoading: Boolean = false,
     
             @PersistState
             val onboardingFlow: OnboardingFlow? = null,
    @@ -71,18 +62,7 @@ data class OnboardingViewState(
     
             @PersistState
             val personalizationState: PersonalizationState = PersonalizationState()
    -) : MavericksState {
    -
    -    fun isLoading(): Boolean {
    -        return asyncLoginAction is Loading ||
    -                asyncHomeServerLoginFlowRequest is Loading ||
    -                asyncResetPassword is Loading ||
    -                asyncResetMailConfirmed is Loading ||
    -                asyncRegistration is Loading ||
    -                asyncDisplayName is Loading ||
    -                asyncProfilePicture is Loading
    -    }
    -}
    +) : MavericksState
     
     enum class OnboardingFlow {
         SignIn,
    diff --git a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/AbstractFtueAuthFragment.kt b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/AbstractFtueAuthFragment.kt
    index 0caf2ea152..64e29766c5 100644
    --- a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/AbstractFtueAuthFragment.kt
    +++ b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/AbstractFtueAuthFragment.kt
    @@ -26,7 +26,6 @@ import com.airbnb.mvrx.withState
     import com.google.android.material.dialog.MaterialAlertDialogBuilder
     import im.vector.app.R
     import im.vector.app.core.dialogs.UnrecognizedCertificateDialog
    -import im.vector.app.core.extensions.exhaustive
     import im.vector.app.core.platform.OnBackPressed
     import im.vector.app.core.platform.VectorBaseFragment
     import im.vector.app.features.onboarding.OnboardingAction
    @@ -35,8 +34,6 @@ import im.vector.app.features.onboarding.OnboardingViewModel
     import im.vector.app.features.onboarding.OnboardingViewState
     import kotlinx.coroutines.CancellationException
     import org.matrix.android.sdk.api.failure.Failure
    -import org.matrix.android.sdk.api.failure.MatrixError
    -import javax.net.ssl.HttpsURLConnection
     
     /**
      * Parent Fragment for all the login/registration screens
    @@ -73,7 +70,7 @@ abstract class AbstractFtueAuthFragment : VectorBaseFragment
                     // This is handled by the Activity
                     Unit
    -        }.exhaustive
    +        }
         }
     
         override fun showFailure(throwable: Throwable) {
    @@ -86,21 +83,8 @@ abstract class AbstractFtueAuthFragment : VectorBaseFragment
                     /* Ignore this error, user has cancelled the action */
                     Unit
    -            is Failure.ServerError                    ->
    -                if (throwable.error.code == MatrixError.M_FORBIDDEN &&
    -                        throwable.httpCode == HttpsURLConnection.HTTP_FORBIDDEN /* 403 */) {
    -                    MaterialAlertDialogBuilder(requireActivity())
    -                            .setTitle(R.string.dialog_title_error)
    -                            .setMessage(getString(R.string.login_registration_disabled))
    -                            .setPositiveButton(R.string.ok, null)
    -                            .show()
    -                } else {
    -                    onError(throwable)
    -                }
    -            is Failure.UnrecognizedCertificateFailure ->
    -                showUnrecognizedCertificateFailure(throwable)
    -            else                                      ->
    -                onError(throwable)
    +            is Failure.UnrecognizedCertificateFailure -> showUnrecognizedCertificateFailure(throwable)
    +            else                                      -> onError(throwable)
             }
         }
     
    diff --git a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthLoginFragment.kt b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthLoginFragment.kt
    index 5f15d9a35d..53c9fd4fcc 100644
    --- a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthLoginFragment.kt
    +++ b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthLoginFragment.kt
    @@ -26,11 +26,8 @@ import androidx.autofill.HintConstants
     import androidx.core.text.isDigitsOnly
     import androidx.core.view.isVisible
     import androidx.lifecycle.lifecycleScope
    -import com.airbnb.mvrx.Fail
    -import com.airbnb.mvrx.Loading
    -import com.airbnb.mvrx.Success
    +import com.google.android.material.dialog.MaterialAlertDialogBuilder
     import im.vector.app.R
    -import im.vector.app.core.extensions.exhaustive
     import im.vector.app.core.extensions.hideKeyboard
     import im.vector.app.core.extensions.hidePassword
     import im.vector.app.core.extensions.toReducedUrl
    @@ -47,9 +44,12 @@ import kotlinx.coroutines.flow.combine
     import kotlinx.coroutines.flow.launchIn
     import kotlinx.coroutines.flow.map
     import kotlinx.coroutines.flow.onEach
    -import org.matrix.android.sdk.api.failure.Failure
    -import org.matrix.android.sdk.api.failure.MatrixError
     import org.matrix.android.sdk.api.failure.isInvalidPassword
    +import org.matrix.android.sdk.api.failure.isInvalidUsername
    +import org.matrix.android.sdk.api.failure.isLoginEmailUnknown
    +import org.matrix.android.sdk.api.failure.isRegistrationDisabled
    +import org.matrix.android.sdk.api.failure.isUsernameInUse
    +import org.matrix.android.sdk.api.failure.isWeakPassword
     import reactivecircus.flowbinding.android.widget.textChanges
     import javax.inject.Inject
     
    @@ -105,7 +105,7 @@ class FtueAuthLoginFragment @Inject constructor() : AbstractSSOFtueAuthFragment<
                         views.loginField.setAutofillHints(HintConstants.AUTOFILL_HINT_USERNAME)
                         views.passwordField.setAutofillHints(HintConstants.AUTOFILL_HINT_PASSWORD)
                     }
    -            }.exhaustive
    +            }
             }
         }
     
    @@ -115,7 +115,7 @@ class FtueAuthLoginFragment @Inject constructor() : AbstractSSOFtueAuthFragment<
                 SignMode.SignUp             -> SocialLoginButtonsView.Mode.MODE_SIGN_UP
                 SignMode.SignIn,
                 SignMode.SignInWithMatrixId -> SocialLoginButtonsView.Mode.MODE_SIGN_IN
    -        }.exhaustive
    +        }
         }
     
         private fun submit() {
    @@ -258,12 +258,31 @@ class FtueAuthLoginFragment @Inject constructor() : AbstractSSOFtueAuthFragment<
         }
     
         override fun onError(throwable: Throwable) {
    -        // Show M_WEAK_PASSWORD error in the password field
    -        if (throwable is Failure.ServerError &&
    -                throwable.error.code == MatrixError.M_WEAK_PASSWORD) {
    -            views.passwordFieldTil.error = errorFormatter.toHumanReadable(throwable)
    -        } else {
    -            views.loginFieldTil.error = errorFormatter.toHumanReadable(throwable)
    +        // Trick to display the error without text.
    +        views.loginFieldTil.error = " "
    +        when {
    +            throwable.isUsernameInUse() || throwable.isInvalidUsername() -> {
    +                views.loginFieldTil.error = errorFormatter.toHumanReadable(throwable)
    +            }
    +            throwable.isLoginEmailUnknown()                              -> {
    +                views.loginFieldTil.error = getString(R.string.login_login_with_email_error)
    +            }
    +            throwable.isInvalidPassword() && spaceInPassword()           -> {
    +                views.passwordFieldTil.error = getString(R.string.auth_invalid_login_param_space_in_password)
    +            }
    +            throwable.isWeakPassword() || throwable.isInvalidPassword()  -> {
    +                views.passwordFieldTil.error = errorFormatter.toHumanReadable(throwable)
    +            }
    +            throwable.isRegistrationDisabled()                           -> {
    +                MaterialAlertDialogBuilder(requireActivity())
    +                        .setTitle(R.string.dialog_title_error)
    +                        .setMessage(getString(R.string.login_registration_disabled))
    +                        .setPositiveButton(R.string.ok, null)
    +                        .show()
    +            }
    +            else                                                         -> {
    +                super.onError(throwable)
    +            }
             }
         }
     
    @@ -276,39 +295,9 @@ class FtueAuthLoginFragment @Inject constructor() : AbstractSSOFtueAuthFragment<
             setupSocialLoginButtons(state)
             setupButtons(state)
     
    -        when (state.asyncLoginAction) {
    -            is Loading -> {
    -                // Ensure password is hidden
    -                views.passwordField.hidePassword()
    -            }
    -            is Fail    -> {
    -                val error = state.asyncLoginAction.error
    -                if (error is Failure.ServerError &&
    -                        error.error.code == MatrixError.M_FORBIDDEN &&
    -                        error.error.message.isEmpty()) {
    -                    // Login with email, but email unknown
    -                    views.loginFieldTil.error = getString(R.string.login_login_with_email_error)
    -                } else {
    -                    // Trick to display the error without text.
    -                    views.loginFieldTil.error = " "
    -                    if (error.isInvalidPassword() && spaceInPassword()) {
    -                        views.passwordFieldTil.error = getString(R.string.auth_invalid_login_param_space_in_password)
    -                    } else {
    -                        views.passwordFieldTil.error = errorFormatter.toHumanReadable(error)
    -                    }
    -                }
    -            }
    -            // Success is handled by the LoginActivity
    -            is Success -> Unit
    -        }
    -
    -        when (state.asyncRegistration) {
    -            is Loading -> {
    -                // Ensure password is hidden
    -                views.passwordField.hidePassword()
    -            }
    -            // Success is handled by the LoginActivity
    -            is Success -> Unit
    +        if (state.isLoading) {
    +            // Ensure password is hidden
    +            views.passwordField.hidePassword()
             }
         }
     
    diff --git a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthResetPasswordFragment.kt b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthResetPasswordFragment.kt
    index 6a224dfae8..b612ec34b5 100644
    --- a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthResetPasswordFragment.kt
    +++ b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthResetPasswordFragment.kt
    @@ -21,9 +21,6 @@ import android.view.LayoutInflater
     import android.view.View
     import android.view.ViewGroup
     import androidx.lifecycle.lifecycleScope
    -import com.airbnb.mvrx.Fail
    -import com.airbnb.mvrx.Loading
    -import com.airbnb.mvrx.Success
     import com.google.android.material.dialog.MaterialAlertDialogBuilder
     import im.vector.app.R
     import im.vector.app.core.extensions.hideKeyboard
    @@ -54,10 +51,13 @@ class FtueAuthResetPasswordFragment @Inject constructor() : AbstractFtueAuthFrag
     
         override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
             super.onViewCreated(view, savedInstanceState)
    -
             setupSubmitButton()
         }
     
    +    override fun onError(throwable: Throwable) {
    +        views.resetPasswordEmailTil.error = errorFormatter.toHumanReadable(throwable)
    +    }
    +
         private fun setupUi(state: OnboardingViewState) {
             views.resetPasswordTitle.text = getString(R.string.login_reset_password_on, state.homeServerUrlFromUser.toReducedUrl())
         }
    @@ -116,16 +116,9 @@ class FtueAuthResetPasswordFragment @Inject constructor() : AbstractFtueAuthFrag
     
         override fun updateWithState(state: OnboardingViewState) {
             setupUi(state)
    -
    -        when (state.asyncResetPassword) {
    -            is Loading -> {
    -                // Ensure new password is hidden
    -                views.passwordField.hidePassword()
    -            }
    -            is Fail    -> {
    -                views.resetPasswordEmailTil.error = errorFormatter.toHumanReadable(state.asyncResetPassword.error)
    -            }
    -            is Success -> Unit
    +        if (state.isLoading) {
    +            // Ensure new password is hidden
    +            views.passwordField.hidePassword()
             }
         }
     }
    diff --git a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthResetPasswordMailConfirmationFragment.kt b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthResetPasswordMailConfirmationFragment.kt
    index 1d5e1aa00a..f6141a4900 100644
    --- a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthResetPasswordMailConfirmationFragment.kt
    +++ b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthResetPasswordMailConfirmationFragment.kt
    @@ -20,8 +20,6 @@ import android.os.Bundle
     import android.view.LayoutInflater
     import android.view.View
     import android.view.ViewGroup
    -import com.airbnb.mvrx.Fail
    -import com.airbnb.mvrx.Success
     import com.google.android.material.dialog.MaterialAlertDialogBuilder
     import im.vector.app.R
     import im.vector.app.databinding.FragmentLoginResetPasswordMailConfirmationBinding
    @@ -59,23 +57,20 @@ class FtueAuthResetPasswordMailConfirmationFragment @Inject constructor() : Abst
     
         override fun updateWithState(state: OnboardingViewState) {
             setupUi(state)
    +    }
     
    -        when (state.asyncResetMailConfirmed) {
    -            is Fail    -> {
    -                // Link in email not yet clicked ?
    -                val message = if (state.asyncResetMailConfirmed.error.is401()) {
    -                    getString(R.string.auth_reset_password_error_unauthorized)
    -                } else {
    -                    errorFormatter.toHumanReadable(state.asyncResetMailConfirmed.error)
    -                }
    -
    -                MaterialAlertDialogBuilder(requireActivity())
    -                        .setTitle(R.string.dialog_title_error)
    -                        .setMessage(message)
    -                        .setPositiveButton(R.string.ok, null)
    -                        .show()
    -            }
    -            is Success -> Unit
    +    override fun onError(throwable: Throwable) {
    +        // Link in email not yet clicked ?
    +        val message = if (throwable.is401()) {
    +            getString(R.string.auth_reset_password_error_unauthorized)
    +        } else {
    +            errorFormatter.toHumanReadable(throwable)
             }
    +
    +        MaterialAlertDialogBuilder(requireActivity())
    +                .setTitle(R.string.dialog_title_error)
    +                .setMessage(message)
    +                .setPositiveButton(R.string.ok, null)
    +                .show()
         }
     }
    diff --git a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthVariant.kt b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthVariant.kt
    index 79a974038b..2b3a43df5d 100644
    --- a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthVariant.kt
    +++ b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthVariant.kt
    @@ -31,7 +31,6 @@ import im.vector.app.R
     import im.vector.app.core.extensions.POP_BACK_STACK_EXCLUSIVE
     import im.vector.app.core.extensions.addFragment
     import im.vector.app.core.extensions.addFragmentToBackstack
    -import im.vector.app.core.extensions.exhaustive
     import im.vector.app.core.extensions.popBackstack
     import im.vector.app.core.extensions.replaceFragment
     import im.vector.app.core.platform.ScreenOrientationLocker
    @@ -122,7 +121,7 @@ class FtueAuthVariant(
     
         private fun updateWithState(viewState: OnboardingViewState) {
             isForceLoginFallbackEnabled = viewState.isForceLoginFallbackEnabled
    -        views.loginLoading.isVisible = viewState.isLoading()
    +        views.loginLoading.isVisible = viewState.isLoading
         }
     
         override fun setIsLoading(isLoading: Boolean) = Unit
    @@ -229,7 +228,7 @@ class FtueAuthVariant(
                 OnboardingViewEvents.OnChooseProfilePicture                        -> onChooseProfilePicture()
                 OnboardingViewEvents.OnPersonalizationComplete                     -> onPersonalizationComplete()
                 OnboardingViewEvents.OnBack                                        -> activity.popBackstack()
    -        }.exhaustive
    +        }
         }
     
         private fun registrationShouldFallback(registrationFlowResult: OnboardingViewEvents.RegistrationFlowResult) =
    @@ -281,7 +280,7 @@ class FtueAuthVariant(
                 SignMode.SignUp             -> Unit // This case is processed in handleOnboardingViewEvents
                 SignMode.SignIn             -> handleSignInSelected(state)
                 SignMode.SignInWithMatrixId -> handleSignInWithMatrixId(state)
    -        }.exhaustive
    +        }
         }
     
         private fun handleSignInSelected(state: OnboardingViewState) {
    diff --git a/vector/src/main/java/im/vector/app/features/poll/create/CreatePollFragment.kt b/vector/src/main/java/im/vector/app/features/poll/create/CreatePollFragment.kt
    index c00c1ece0c..590c181ef5 100644
    --- a/vector/src/main/java/im/vector/app/features/poll/create/CreatePollFragment.kt
    +++ b/vector/src/main/java/im/vector/app/features/poll/create/CreatePollFragment.kt
    @@ -27,7 +27,6 @@ import com.airbnb.mvrx.args
     import com.airbnb.mvrx.withState
     import im.vector.app.R
     import im.vector.app.core.extensions.configureWith
    -import im.vector.app.core.extensions.exhaustive
     import im.vector.app.core.platform.VectorBaseFragment
     import im.vector.app.databinding.FragmentCreatePollBinding
     import im.vector.app.features.poll.PollMode
    @@ -69,7 +68,7 @@ class CreatePollFragment @Inject constructor(
                     views.createPollToolbar.title = getString(R.string.edit_poll_title)
                     views.createPollButton.text = getString(R.string.edit_poll_title)
                 }
    -        }.exhaustive
    +        }
     
             views.createPollRecyclerView.configureWith(controller, disableItemAnimation = true)
             // workaround for https://github.com/vector-im/element-android/issues/4735
    diff --git a/vector/src/main/java/im/vector/app/features/qrcode/QrCodeScannerActivity.kt b/vector/src/main/java/im/vector/app/features/qrcode/QrCodeScannerActivity.kt
    index dda7b2e2eb..b23f2f171d 100644
    --- a/vector/src/main/java/im/vector/app/features/qrcode/QrCodeScannerActivity.kt
    +++ b/vector/src/main/java/im/vector/app/features/qrcode/QrCodeScannerActivity.kt
    @@ -24,7 +24,6 @@ import androidx.activity.result.ActivityResultLauncher
     import com.airbnb.mvrx.viewModel
     import dagger.hilt.android.AndroidEntryPoint
     import im.vector.app.R
    -import im.vector.app.core.extensions.exhaustive
     import im.vector.app.core.extensions.replaceFragment
     import im.vector.app.core.platform.VectorBaseActivity
     import im.vector.app.databinding.ActivitySimpleBinding
    @@ -51,7 +50,7 @@ class QrCodeScannerActivity() : VectorBaseActivity() {
                         finish()
                     }
                     else                               -> Unit
    -            }.exhaustive
    +            }
             }
     
             if (isFirstCreation()) {
    diff --git a/vector/src/main/java/im/vector/app/features/room/RequireActiveMembershipViewModel.kt b/vector/src/main/java/im/vector/app/features/room/RequireActiveMembershipViewModel.kt
    index a77bd32f26..0cb49746f1 100644
    --- a/vector/src/main/java/im/vector/app/features/room/RequireActiveMembershipViewModel.kt
    +++ b/vector/src/main/java/im/vector/app/features/room/RequireActiveMembershipViewModel.kt
    @@ -23,7 +23,6 @@ import dagger.assisted.AssistedInject
     import im.vector.app.R
     import im.vector.app.core.di.MavericksAssistedViewModelFactory
     import im.vector.app.core.di.hiltMavericksViewModelFactory
    -import im.vector.app.core.extensions.exhaustive
     import im.vector.app.core.platform.VectorViewModel
     import im.vector.app.core.resources.StringProvider
     import kotlinx.coroutines.Dispatchers
    @@ -127,6 +126,6 @@ class RequireActiveMembershipViewModel @AssistedInject constructor(
                     }
                     roomIdFlow.tryEmit(Optional.from(action.roomId))
                 }
    -        }.exhaustive
    +        }
         }
     }
    diff --git a/vector/src/main/java/im/vector/app/features/roomdirectory/PublicRoomsFragment.kt b/vector/src/main/java/im/vector/app/features/roomdirectory/PublicRoomsFragment.kt
    index 14b50c2745..b8bba347fd 100644
    --- a/vector/src/main/java/im/vector/app/features/roomdirectory/PublicRoomsFragment.kt
    +++ b/vector/src/main/java/im/vector/app/features/roomdirectory/PublicRoomsFragment.kt
    @@ -28,7 +28,6 @@ import com.airbnb.mvrx.withState
     import im.vector.app.R
     import im.vector.app.core.extensions.cleanup
     import im.vector.app.core.extensions.configureWith
    -import im.vector.app.core.extensions.exhaustive
     import im.vector.app.core.extensions.trackItemsVisibilityChange
     import im.vector.app.core.platform.VectorBaseFragment
     import im.vector.app.core.platform.showOptimizedSnackbar
    @@ -96,7 +95,7 @@ class PublicRoomsFragment @Inject constructor(
                 is RoomDirectoryViewEvents.Failure -> {
                     views.coordinatorLayout.showOptimizedSnackbar(errorFormatter.toHumanReadable(viewEvents.throwable))
                 }
    -        }.exhaustive
    +        }
         }
     
         override fun onDestroyView() {
    diff --git a/vector/src/main/java/im/vector/app/features/roomdirectory/RoomDirectoryActivity.kt b/vector/src/main/java/im/vector/app/features/roomdirectory/RoomDirectoryActivity.kt
    index 48da9f4fa0..f0df31342e 100644
    --- a/vector/src/main/java/im/vector/app/features/roomdirectory/RoomDirectoryActivity.kt
    +++ b/vector/src/main/java/im/vector/app/features/roomdirectory/RoomDirectoryActivity.kt
    @@ -62,8 +62,8 @@ class RoomDirectoryActivity : VectorBaseActivity(), Matri
                     .stream()
                     .onEach { sharedAction ->
                         when (sharedAction) {
    -                        is RoomDirectorySharedAction.Back           -> popBackstack()
    -                        is RoomDirectorySharedAction.CreateRoom     -> {
    +                        is RoomDirectorySharedAction.Back              -> popBackstack()
    +                        is RoomDirectorySharedAction.CreateRoom        -> {
                                 // Transmit the filter to the CreateRoomFragment
                                 withState(roomDirectoryViewModel) {
                                     addFragmentToBackstack(
    @@ -73,9 +73,10 @@ class RoomDirectoryActivity : VectorBaseActivity(), Matri
                                     )
                                 }
                             }
    -                        is RoomDirectorySharedAction.ChangeProtocol ->
    +                        is RoomDirectorySharedAction.ChangeProtocol    ->
                                 addFragmentToBackstack(views.simpleFragmentContainer, RoomDirectoryPickerFragment::class.java)
    -                        is RoomDirectorySharedAction.Close          -> finish()
    +                        is RoomDirectorySharedAction.Close             -> finish()
    +                        is RoomDirectorySharedAction.CreateRoomSuccess -> Unit
                         }
                     }
                     .launchIn(lifecycleScope)
    diff --git a/vector/src/main/java/im/vector/app/features/roomdirectory/createroom/CreateRoomFragment.kt b/vector/src/main/java/im/vector/app/features/roomdirectory/createroom/CreateRoomFragment.kt
    index 2bd41ae3af..2871513c1f 100644
    --- a/vector/src/main/java/im/vector/app/features/roomdirectory/createroom/CreateRoomFragment.kt
    +++ b/vector/src/main/java/im/vector/app/features/roomdirectory/createroom/CreateRoomFragment.kt
    @@ -34,7 +34,6 @@ import im.vector.app.R
     import im.vector.app.core.dialogs.GalleryOrCameraDialogHelper
     import im.vector.app.core.extensions.cleanup
     import im.vector.app.core.extensions.configureWith
    -import im.vector.app.core.extensions.exhaustive
     import im.vector.app.core.platform.OnBackPressed
     import im.vector.app.core.platform.VectorBaseFragment
     import im.vector.app.core.resources.ColorProvider
    @@ -94,7 +93,7 @@ class CreateRoomFragment @Inject constructor(
                 when (it) {
                     CreateRoomViewEvents.Quit       -> vectorBaseActivity.onBackPressed()
                     is CreateRoomViewEvents.Failure -> showFailure(it.throwable)
    -            }.exhaustive
    +            }
             }
         }
     
    diff --git a/vector/src/main/java/im/vector/app/features/roomdirectory/createroom/CreateRoomViewModel.kt b/vector/src/main/java/im/vector/app/features/roomdirectory/createroom/CreateRoomViewModel.kt
    index 3b2e9de2d1..7d65c44a57 100644
    --- a/vector/src/main/java/im/vector/app/features/roomdirectory/createroom/CreateRoomViewModel.kt
    +++ b/vector/src/main/java/im/vector/app/features/roomdirectory/createroom/CreateRoomViewModel.kt
    @@ -28,7 +28,6 @@ import dagger.assisted.AssistedInject
     import im.vector.app.AppStateHandler
     import im.vector.app.core.di.MavericksAssistedViewModelFactory
     import im.vector.app.core.di.hiltMavericksViewModelFactory
    -import im.vector.app.core.extensions.exhaustive
     import im.vector.app.core.platform.VectorViewModel
     import im.vector.app.features.analytics.AnalyticsTracker
     import im.vector.app.features.analytics.plan.CreatedRoom
    @@ -138,7 +137,7 @@ class CreateRoomViewModel @AssistedInject constructor(
                 CreateRoomAction.Reset                    -> doReset()
                 CreateRoomAction.ToggleShowAdvanced       -> toggleShowAdvanced()
                 is CreateRoomAction.DisableFederation     -> disableFederation(action)
    -        }.exhaustive
    +        }
         }
     
         private fun disableFederation(action: CreateRoomAction.DisableFederation) {
    @@ -281,7 +280,7 @@ class CreateRoomViewModel @AssistedInject constructor(
                                 // Preset
                                 preset = CreateRoomPreset.PRESET_PRIVATE_CHAT
                             }
    -                    }.exhaustive
    +                    }
                         // Disabling federation
                         disableFederation = state.disableFederation
     
    diff --git a/vector/src/main/java/im/vector/app/features/roomdirectory/picker/RoomDirectoryPickerController.kt b/vector/src/main/java/im/vector/app/features/roomdirectory/picker/RoomDirectoryPickerController.kt
    index 08e044630d..7d121d1ff4 100644
    --- a/vector/src/main/java/im/vector/app/features/roomdirectory/picker/RoomDirectoryPickerController.kt
    +++ b/vector/src/main/java/im/vector/app/features/roomdirectory/picker/RoomDirectoryPickerController.kt
    @@ -22,7 +22,6 @@ import android.view.inputmethod.EditorInfo
     import android.widget.TextView
     import com.airbnb.epoxy.TypedEpoxyController
     import com.airbnb.mvrx.Fail
    -import com.airbnb.mvrx.Incomplete
     import com.airbnb.mvrx.Loading
     import com.airbnb.mvrx.Success
     import com.airbnb.mvrx.Uninitialized
    @@ -60,7 +59,7 @@ class RoomDirectoryPickerController @Inject constructor(
             val host = this
     
             when (val asyncThirdPartyProtocol = data.asyncThirdPartyRequest) {
    -            is Success    -> {
    +            is Success -> {
                     data.directories.join(
                             each = { _, roomDirectoryServer -> buildDirectory(roomDirectoryServer) },
                             between = { idx, _ -> buildDivider(idx) }
    @@ -71,12 +70,13 @@ class RoomDirectoryPickerController @Inject constructor(
                         heightInPx(host.dimensionConverter.dpToPx(16))
                     }
                 }
    -            is Incomplete -> {
    +            Uninitialized,
    +            is Loading -> {
                     loadingItem {
                         id("loading")
                     }
                 }
    -            is Fail       -> {
    +            is Fail    -> {
                     errorWithRetryItem {
                         id("error")
                         text(host.errorFormatter.toHumanReadable(asyncThirdPartyProtocol.error))
    diff --git a/vector/src/main/java/im/vector/app/features/roomdirectory/picker/RoomDirectoryPickerViewModel.kt b/vector/src/main/java/im/vector/app/features/roomdirectory/picker/RoomDirectoryPickerViewModel.kt
    index a5673e78a2..51af9a8286 100644
    --- a/vector/src/main/java/im/vector/app/features/roomdirectory/picker/RoomDirectoryPickerViewModel.kt
    +++ b/vector/src/main/java/im/vector/app/features/roomdirectory/picker/RoomDirectoryPickerViewModel.kt
    @@ -27,7 +27,6 @@ import dagger.assisted.AssistedInject
     import im.vector.app.R
     import im.vector.app.core.di.MavericksAssistedViewModelFactory
     import im.vector.app.core.di.hiltMavericksViewModelFactory
    -import im.vector.app.core.extensions.exhaustive
     import im.vector.app.core.platform.EmptyViewEvents
     import im.vector.app.core.platform.VectorViewModel
     import im.vector.app.core.resources.StringProvider
    @@ -104,7 +103,7 @@ class RoomDirectoryPickerViewModel @AssistedInject constructor(
                 is RoomDirectoryPickerAction.SetServerUrl -> handleSetServerUrl(action)
                 RoomDirectoryPickerAction.Submit          -> handleSubmit()
                 is RoomDirectoryPickerAction.RemoveServer -> handleRemoveServer(action)
    -        }.exhaustive
    +        }
         }
     
         private fun handleEnterEditMode() {
    diff --git a/vector/src/main/java/im/vector/app/features/roomdirectory/roompreview/RoomPreviewViewModel.kt b/vector/src/main/java/im/vector/app/features/roomdirectory/roompreview/RoomPreviewViewModel.kt
    index 42bec8c8b3..a22dc7ed95 100644
    --- a/vector/src/main/java/im/vector/app/features/roomdirectory/roompreview/RoomPreviewViewModel.kt
    +++ b/vector/src/main/java/im/vector/app/features/roomdirectory/roompreview/RoomPreviewViewModel.kt
    @@ -24,7 +24,6 @@ import dagger.assisted.AssistedFactory
     import dagger.assisted.AssistedInject
     import im.vector.app.core.di.MavericksAssistedViewModelFactory
     import im.vector.app.core.di.hiltMavericksViewModelFactory
    -import im.vector.app.core.extensions.exhaustive
     import im.vector.app.core.platform.EmptyViewEvents
     import im.vector.app.core.platform.VectorViewModel
     import im.vector.app.features.analytics.AnalyticsTracker
    @@ -204,7 +203,7 @@ class RoomPreviewViewModel @AssistedInject constructor(
             when (action) {
                 is RoomPreviewAction.Join        -> handleJoinRoom()
                 RoomPreviewAction.JoinThirdParty -> handleJoinRoomThirdParty()
    -        }.exhaustive
    +        }
         }
     
         private fun handleJoinRoomThirdParty() = withState { state ->
    diff --git a/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileFragment.kt b/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileFragment.kt
    index 7e919fb663..d9ed6d227a 100644
    --- a/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileFragment.kt
    +++ b/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileFragment.kt
    @@ -25,8 +25,9 @@ import android.view.View
     import android.view.ViewGroup
     import androidx.core.view.isVisible
     import com.airbnb.mvrx.Fail
    -import com.airbnb.mvrx.Incomplete
    +import com.airbnb.mvrx.Loading
     import com.airbnb.mvrx.Success
    +import com.airbnb.mvrx.Uninitialized
     import com.airbnb.mvrx.args
     import com.airbnb.mvrx.fragmentViewModel
     import com.airbnb.mvrx.withState
    @@ -38,7 +39,6 @@ import im.vector.app.core.dialogs.ConfirmationDialogBuilder
     import im.vector.app.core.extensions.cleanup
     import im.vector.app.core.extensions.configureWith
     import im.vector.app.core.extensions.copyOnLongClick
    -import im.vector.app.core.extensions.exhaustive
     import im.vector.app.core.extensions.setTextOrHide
     import im.vector.app.core.platform.StateView
     import im.vector.app.core.platform.VectorBaseFragment
    @@ -133,7 +133,7 @@ class RoomMemberProfileFragment @Inject constructor(
                     is RoomMemberProfileViewEvents.OnBanActionSuccess          -> Unit
                     is RoomMemberProfileViewEvents.OnIgnoreActionSuccess       -> Unit
                     is RoomMemberProfileViewEvents.OnInviteActionSuccess       -> Unit
    -            }.exhaustive
    +            }
             }
             setupLongClicks()
         }
    @@ -198,18 +198,19 @@ class RoomMemberProfileFragment @Inject constructor(
     
         override fun invalidate() = withState(viewModel) { state ->
             when (val asyncUserMatrixItem = state.userMatrixItem) {
    -            is Incomplete -> {
    +            Uninitialized,
    +            is Loading -> {
                     views.matrixProfileToolbarTitleView.text = state.userId
                     avatarRenderer.render(MatrixItem.UserItem(state.userId, null, null), views.matrixProfileToolbarAvatarImageView)
                     headerViews.memberProfileStateView.state = StateView.State.Loading
                 }
    -            is Fail       -> {
    +            is Fail    -> {
                     avatarRenderer.render(MatrixItem.UserItem(state.userId, null, null), views.matrixProfileToolbarAvatarImageView)
                     views.matrixProfileToolbarTitleView.text = state.userId
                     val failureMessage = errorFormatter.toHumanReadable(asyncUserMatrixItem.error)
                     headerViews.memberProfileStateView.state = StateView.State.Error(failureMessage)
                 }
    -            is Success    -> {
    +            is Success -> {
                     val userMatrixItem = asyncUserMatrixItem()
                     headerViews.memberProfileStateView.state = StateView.State.Content
                     headerViews.memberProfileIdView.text = userMatrixItem.id
    diff --git a/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileViewModel.kt b/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileViewModel.kt
    index a79a9f4c1d..db54f27910 100644
    --- a/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileViewModel.kt
    +++ b/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileViewModel.kt
    @@ -28,7 +28,6 @@ import dagger.assisted.AssistedInject
     import im.vector.app.R
     import im.vector.app.core.di.MavericksAssistedViewModelFactory
     import im.vector.app.core.di.hiltMavericksViewModelFactory
    -import im.vector.app.core.extensions.exhaustive
     import im.vector.app.core.mvrx.runCatchingToAsync
     import im.vector.app.core.platform.VectorViewModel
     import im.vector.app.core.resources.StringProvider
    @@ -170,7 +169,7 @@ class RoomMemberProfileViewModel @AssistedInject constructor(
                 RoomMemberProfileAction.InviteUser                -> handleInviteAction()
                 is RoomMemberProfileAction.SetUserColorOverride   -> handleSetUserColorOverride(action)
                 is RoomMemberProfileAction.OpenOrCreateDm         -> handleOpenOrCreateDm(action)
    -        }.exhaustive
    +        }
         }
     
         private fun handleOpenOrCreateDm(action: RoomMemberProfileAction.OpenOrCreateDm) {
    diff --git a/vector/src/main/java/im/vector/app/features/roommemberprofile/devices/DeviceListBottomSheet.kt b/vector/src/main/java/im/vector/app/features/roommemberprofile/devices/DeviceListBottomSheet.kt
    index bb2317b59c..8df0b3ffd5 100644
    --- a/vector/src/main/java/im/vector/app/features/roommemberprofile/devices/DeviceListBottomSheet.kt
    +++ b/vector/src/main/java/im/vector/app/features/roommemberprofile/devices/DeviceListBottomSheet.kt
    @@ -29,7 +29,6 @@ import com.airbnb.mvrx.withState
     import dagger.hilt.android.AndroidEntryPoint
     import im.vector.app.R
     import im.vector.app.core.extensions.commitTransaction
    -import im.vector.app.core.extensions.exhaustive
     import im.vector.app.core.platform.VectorBaseBottomSheetDialogFragment
     import im.vector.app.databinding.BottomSheetWithFragmentsBinding
     import im.vector.app.features.crypto.verification.VerificationBottomSheet
    @@ -57,7 +56,7 @@ class DeviceListBottomSheet :
                                 transactionId = it.txID
                         ).show(requireActivity().supportFragmentManager, "REQPOP")
                     }
    -            }.exhaustive
    +            }
             }
         }
     
    diff --git a/vector/src/main/java/im/vector/app/features/roommemberprofile/devices/DeviceListBottomSheetViewModel.kt b/vector/src/main/java/im/vector/app/features/roommemberprofile/devices/DeviceListBottomSheetViewModel.kt
    index d2491237ca..03e07a2f82 100644
    --- a/vector/src/main/java/im/vector/app/features/roommemberprofile/devices/DeviceListBottomSheetViewModel.kt
    +++ b/vector/src/main/java/im/vector/app/features/roommemberprofile/devices/DeviceListBottomSheetViewModel.kt
    @@ -28,7 +28,6 @@ import dagger.hilt.EntryPoints
     import im.vector.app.core.di.MavericksAssistedViewModelFactory
     import im.vector.app.core.di.SingletonEntryPoint
     import im.vector.app.core.di.hiltMavericksViewModelFactory
    -import im.vector.app.core.extensions.exhaustive
     import im.vector.app.core.platform.VectorViewModel
     import org.matrix.android.sdk.api.session.Session
     import org.matrix.android.sdk.api.session.crypto.crosssigning.MXCrossSigningInfo
    @@ -94,7 +93,7 @@ class DeviceListBottomSheetViewModel @AssistedInject constructor(@Assisted priva
                 is DeviceListAction.SelectDevice   -> selectDevice(action)
                 is DeviceListAction.DeselectDevice -> deselectDevice()
                 is DeviceListAction.ManuallyVerify -> manuallyVerify(action)
    -        }.exhaustive
    +        }
         }
     
         private fun refreshSelectedId() = withState { state ->
    diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileActivity.kt b/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileActivity.kt
    index 4c6d2ed2e3..12a5d94eca 100644
    --- a/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileActivity.kt
    +++ b/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileActivity.kt
    @@ -26,7 +26,6 @@ import com.airbnb.mvrx.viewModel
     import dagger.hilt.android.AndroidEntryPoint
     import im.vector.app.core.extensions.addFragment
     import im.vector.app.core.extensions.addFragmentToBackstack
    -import im.vector.app.core.extensions.exhaustive
     import im.vector.app.core.platform.VectorBaseActivity
     import im.vector.app.databinding.ActivitySimpleBinding
     import im.vector.app.features.home.room.detail.RoomDetailPendingActionStore
    @@ -102,7 +101,7 @@ class RoomProfileActivity :
                             RoomProfileSharedAction.OpenRoomUploads                 -> openRoomUploads()
                             RoomProfileSharedAction.OpenBannedRoomMembers        -> openBannedRoomMembers()
                             RoomProfileSharedAction.OpenRoomNotificationSettings -> openRoomNotificationSettings()
    -                    }.exhaustive
    +                    }
                     }
                     .launchIn(lifecycleScope)
     
    diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileFragment.kt b/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileFragment.kt
    index b13ef2a5d1..ba9280dc59 100644
    --- a/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileFragment.kt
    +++ b/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileFragment.kt
    @@ -37,7 +37,6 @@ import im.vector.app.core.animations.MatrixItemAppBarStateChangeListener
     import im.vector.app.core.extensions.cleanup
     import im.vector.app.core.extensions.configureWith
     import im.vector.app.core.extensions.copyOnLongClick
    -import im.vector.app.core.extensions.exhaustive
     import im.vector.app.core.extensions.setTextOrHide
     import im.vector.app.core.platform.VectorBaseFragment
     import im.vector.app.core.utils.copyToClipboard
    @@ -127,7 +126,7 @@ class RoomProfileFragment @Inject constructor(
                     is RoomProfileViewEvents.ShareRoomProfile -> onShareRoomProfile(it.permalink)
                     is RoomProfileViewEvents.OnShortcutReady  -> addShortcut(it)
                     RoomProfileViewEvents.DismissLoading      -> dismissLoadingDialog()
    -            }.exhaustive
    +            }
             }
             roomListQuickActionsSharedActionViewModel
                     .stream()
    diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileViewModel.kt b/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileViewModel.kt
    index b7c7d24888..61013c8eb6 100644
    --- a/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileViewModel.kt
    +++ b/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileViewModel.kt
    @@ -24,7 +24,6 @@ import dagger.assisted.AssistedInject
     import im.vector.app.R
     import im.vector.app.core.di.MavericksAssistedViewModelFactory
     import im.vector.app.core.di.hiltMavericksViewModelFactory
    -import im.vector.app.core.extensions.exhaustive
     import im.vector.app.core.platform.VectorViewModel
     import im.vector.app.core.resources.StringProvider
     import im.vector.app.features.home.ShortcutCreator
    @@ -137,7 +136,7 @@ class RoomProfileViewModel @AssistedInject constructor(
                 is RoomProfileAction.ShareRoomProfile            -> handleShareRoomProfile()
                 RoomProfileAction.CreateShortcut                 -> handleCreateShortcut()
                 RoomProfileAction.RestoreEncryptionState         -> restoreEncryptionState()
    -        }.exhaustive
    +        }
         }
     
         fun isPublicRoom(): Boolean {
    diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasController.kt b/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasController.kt
    index 03e6ab9984..fcf6bc3a47 100644
    --- a/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasController.kt
    +++ b/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasController.kt
    @@ -199,12 +199,13 @@ class RoomAliasController @Inject constructor(
             }
     
             when (val localAliases = data.localAliases) {
    -            is Uninitialized -> {
    +            Uninitialized,
    +            is Loading -> {
                     loadingItem {
                         id("loadingAliases")
                     }
                 }
    -            is Success       -> {
    +            is Success -> {
                     if (localAliases().isEmpty()) {
                         settingsInfoItem {
                             id("locEmpty")
    @@ -220,7 +221,7 @@ class RoomAliasController @Inject constructor(
                         }
                     }
                 }
    -            is Fail          -> {
    +            is Fail    -> {
                     errorWithRetryItem {
                         id("alt_error")
                         text(host.errorFormatter.toHumanReadable(localAliases.error))
    diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasFragment.kt b/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasFragment.kt
    index e48ce54e6c..2a738fd07c 100644
    --- a/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasFragment.kt
    +++ b/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasFragment.kt
    @@ -29,7 +29,6 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder
     import im.vector.app.R
     import im.vector.app.core.extensions.cleanup
     import im.vector.app.core.extensions.configureWith
    -import im.vector.app.core.extensions.exhaustive
     import im.vector.app.core.platform.VectorBaseFragment
     import im.vector.app.core.utils.shareText
     import im.vector.app.core.utils.toast
    @@ -77,7 +76,7 @@ class RoomAliasFragment @Inject constructor(
                 when (it) {
                     is RoomAliasViewEvents.Failure -> showFailure(it.throwable)
                     RoomAliasViewEvents.Success    -> showSuccess()
    -            }.exhaustive
    +            }
             }
     
             sharedActionViewModel
    diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasViewModel.kt b/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasViewModel.kt
    index 19f600e5de..adffbcbd06 100644
    --- a/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasViewModel.kt
    +++ b/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasViewModel.kt
    @@ -26,7 +26,6 @@ import dagger.assisted.AssistedFactory
     import dagger.assisted.AssistedInject
     import im.vector.app.core.di.MavericksAssistedViewModelFactory
     import im.vector.app.core.di.hiltMavericksViewModelFactory
    -import im.vector.app.core.extensions.exhaustive
     import im.vector.app.core.platform.VectorViewModel
     import im.vector.app.features.powerlevel.PowerLevelsFlowFactory
     import kotlinx.coroutines.flow.launchIn
    @@ -190,7 +189,7 @@ class RoomAliasViewModel @AssistedInject constructor(@Assisted initialState: Roo
                 is RoomAliasAction.RemoveLocalAlias           -> handleRemoveLocalAlias(action)
                 is RoomAliasAction.PublishAlias               -> handlePublishAlias(action)
                 RoomAliasAction.Retry                         -> handleRetry()
    -        }.exhaustive
    +        }
         }
     
         private fun handleRetry() = withState { state ->
    diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/banned/RoomBannedMemberListViewModel.kt b/vector/src/main/java/im/vector/app/features/roomprofile/banned/RoomBannedMemberListViewModel.kt
    index d7efc2fb79..ec249c75ba 100644
    --- a/vector/src/main/java/im/vector/app/features/roomprofile/banned/RoomBannedMemberListViewModel.kt
    +++ b/vector/src/main/java/im/vector/app/features/roomprofile/banned/RoomBannedMemberListViewModel.kt
    @@ -23,7 +23,6 @@ import dagger.assisted.AssistedInject
     import im.vector.app.R
     import im.vector.app.core.di.MavericksAssistedViewModelFactory
     import im.vector.app.core.di.hiltMavericksViewModelFactory
    -import im.vector.app.core.extensions.exhaustive
     import im.vector.app.core.platform.VectorViewModel
     import im.vector.app.core.resources.StringProvider
     import im.vector.app.features.powerlevel.PowerLevelsFlowFactory
    @@ -84,7 +83,7 @@ class RoomBannedMemberListViewModel @AssistedInject constructor(@Assisted initia
                 is RoomBannedMemberListAction.QueryInfo -> onQueryBanInfo(action.roomMemberSummary)
                 is RoomBannedMemberListAction.UnBanUser -> unBanUser(action.roomMemberSummary)
                 is RoomBannedMemberListAction.Filter    -> handleFilter(action)
    -        }.exhaustive
    +        }
         }
     
         private fun handleFilter(action: RoomBannedMemberListAction.Filter) {
    diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListViewModel.kt b/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListViewModel.kt
    index 0bbdd87f3e..c9a70fbef8 100644
    --- a/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListViewModel.kt
    +++ b/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListViewModel.kt
    @@ -23,7 +23,6 @@ import dagger.assisted.AssistedFactory
     import dagger.assisted.AssistedInject
     import im.vector.app.core.di.MavericksAssistedViewModelFactory
     import im.vector.app.core.di.hiltMavericksViewModelFactory
    -import im.vector.app.core.extensions.exhaustive
     import im.vector.app.core.platform.EmptyViewEvents
     import im.vector.app.core.platform.VectorViewModel
     import im.vector.app.features.powerlevel.PowerLevelsFlowFactory
    @@ -181,7 +180,7 @@ class RoomMemberListViewModel @AssistedInject constructor(@Assisted initialState
             when (action) {
                 is RoomMemberListAction.RevokeThreePidInvite -> handleRevokeThreePidInvite(action)
                 is RoomMemberListAction.FilterMemberList     -> handleFilterMemberList(action)
    -        }.exhaustive
    +        }
         }
     
         private fun handleRevokeThreePidInvite(action: RoomMemberListAction.RevokeThreePidInvite) {
    diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/permissions/RoomPermissionsFragment.kt b/vector/src/main/java/im/vector/app/features/roomprofile/permissions/RoomPermissionsFragment.kt
    index 0d5ac7dea8..c1175796fb 100644
    --- a/vector/src/main/java/im/vector/app/features/roomprofile/permissions/RoomPermissionsFragment.kt
    +++ b/vector/src/main/java/im/vector/app/features/roomprofile/permissions/RoomPermissionsFragment.kt
    @@ -27,7 +27,6 @@ import com.airbnb.mvrx.withState
     import im.vector.app.R
     import im.vector.app.core.extensions.cleanup
     import im.vector.app.core.extensions.configureWith
    -import im.vector.app.core.extensions.exhaustive
     import im.vector.app.core.platform.VectorBaseFragment
     import im.vector.app.core.utils.toast
     import im.vector.app.databinding.FragmentRoomSettingGenericBinding
    @@ -67,7 +66,7 @@ class RoomPermissionsFragment @Inject constructor(
                 when (it) {
                     is RoomPermissionsViewEvents.Failure -> showFailure(it.throwable)
                     RoomPermissionsViewEvents.Success    -> showSuccess()
    -            }.exhaustive
    +            }
             }
         }
     
    diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/permissions/RoomPermissionsViewModel.kt b/vector/src/main/java/im/vector/app/features/roomprofile/permissions/RoomPermissionsViewModel.kt
    index 7e8a66d12a..6fbc545b6c 100644
    --- a/vector/src/main/java/im/vector/app/features/roomprofile/permissions/RoomPermissionsViewModel.kt
    +++ b/vector/src/main/java/im/vector/app/features/roomprofile/permissions/RoomPermissionsViewModel.kt
    @@ -23,7 +23,6 @@ import dagger.assisted.AssistedFactory
     import dagger.assisted.AssistedInject
     import im.vector.app.core.di.MavericksAssistedViewModelFactory
     import im.vector.app.core.di.hiltMavericksViewModelFactory
    -import im.vector.app.core.extensions.exhaustive
     import im.vector.app.core.platform.VectorViewModel
     import im.vector.app.features.powerlevel.PowerLevelsFlowFactory
     import kotlinx.coroutines.flow.launchIn
    @@ -90,7 +89,7 @@ class RoomPermissionsViewModel @AssistedInject constructor(@Assisted initialStat
             when (action) {
                 is RoomPermissionsAction.UpdatePermission      -> updatePermission(action)
                 RoomPermissionsAction.ToggleShowAllPermissions -> toggleShowAllPermissions()
    -        }.exhaustive
    +        }
         }
     
         private fun toggleShowAllPermissions() {
    diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsFragment.kt b/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsFragment.kt
    index 51f6b247d4..0bde35f41e 100644
    --- a/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsFragment.kt
    +++ b/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsFragment.kt
    @@ -33,7 +33,6 @@ import im.vector.app.R
     import im.vector.app.core.dialogs.GalleryOrCameraDialogHelper
     import im.vector.app.core.extensions.cleanup
     import im.vector.app.core.extensions.configureWith
    -import im.vector.app.core.extensions.exhaustive
     import im.vector.app.core.intent.getFilenameFromUri
     import im.vector.app.core.platform.OnBackPressed
     import im.vector.app.core.platform.VectorBaseFragment
    @@ -98,7 +97,7 @@ class RoomSettingsFragment @Inject constructor(
                         ignoreChanges = true
                         vectorBaseActivity.onBackPressed()
                     }
    -            }.exhaustive
    +            }
             }
         }
     
    diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsViewModel.kt b/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsViewModel.kt
    index a0325cfc2b..8ad5bcdce6 100644
    --- a/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsViewModel.kt
    +++ b/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsViewModel.kt
    @@ -23,7 +23,6 @@ import dagger.assisted.AssistedFactory
     import dagger.assisted.AssistedInject
     import im.vector.app.core.di.MavericksAssistedViewModelFactory
     import im.vector.app.core.di.hiltMavericksViewModelFactory
    -import im.vector.app.core.extensions.exhaustive
     import im.vector.app.core.platform.VectorViewModel
     import im.vector.app.features.powerlevel.PowerLevelsFlowFactory
     import im.vector.app.features.settings.VectorPreferences
    @@ -201,7 +200,7 @@ class RoomSettingsViewModel @AssistedInject constructor(@Assisted initialState:
                 is RoomSettingsAction.SetRoomGuestAccess       -> handleSetGuestAccess(action)
                 is RoomSettingsAction.Save                     -> saveSettings()
                 is RoomSettingsAction.Cancel                   -> cancel()
    -        }.exhaustive
    +        }
         }
     
         private fun handleSetRoomJoinRule(action: RoomSettingsAction.SetRoomJoinRule) = withState { state ->
    diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/settings/joinrule/advanced/RoomJoinRuleChooseRestrictedViewModel.kt b/vector/src/main/java/im/vector/app/features/roomprofile/settings/joinrule/advanced/RoomJoinRuleChooseRestrictedViewModel.kt
    index 548ec9cfe4..f1897761b2 100644
    --- a/vector/src/main/java/im/vector/app/features/roomprofile/settings/joinrule/advanced/RoomJoinRuleChooseRestrictedViewModel.kt
    +++ b/vector/src/main/java/im/vector/app/features/roomprofile/settings/joinrule/advanced/RoomJoinRuleChooseRestrictedViewModel.kt
    @@ -29,7 +29,6 @@ import dagger.assisted.AssistedInject
     import im.vector.app.R
     import im.vector.app.core.di.MavericksAssistedViewModelFactory
     import im.vector.app.core.di.hiltMavericksViewModelFactory
    -import im.vector.app.core.extensions.exhaustive
     import im.vector.app.core.platform.VectorViewModel
     import im.vector.app.core.resources.StringProvider
     import im.vector.app.core.utils.styleMatchingText
    @@ -180,7 +179,7 @@ class RoomJoinRuleChooseRestrictedViewModel @AssistedInject constructor(
                 is RoomJoinRuleChooseRestrictedActions.SelectJoinRules            -> handleSelectRule(action)
                 is RoomJoinRuleChooseRestrictedActions.SwitchToRoomAfterMigration -> handleSwitchToRoom(action)
                 RoomJoinRuleChooseRestrictedActions.DoUpdateJoinRules             -> handleSubmit()
    -        }.exhaustive
    +        }
             checkForChanges()
         }
     
    diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/uploads/RoomUploadsFragment.kt b/vector/src/main/java/im/vector/app/features/roomprofile/uploads/RoomUploadsFragment.kt
    index a0adf42d5b..6a115ad272 100644
    --- a/vector/src/main/java/im/vector/app/features/roomprofile/uploads/RoomUploadsFragment.kt
    +++ b/vector/src/main/java/im/vector/app/features/roomprofile/uploads/RoomUploadsFragment.kt
    @@ -28,7 +28,6 @@ import com.airbnb.mvrx.withState
     import com.google.android.material.appbar.AppBarLayout
     import com.google.android.material.tabs.TabLayoutMediator
     import im.vector.app.R
    -import im.vector.app.core.extensions.exhaustive
     import im.vector.app.core.intent.getMimeTypeFromUri
     import im.vector.app.core.platform.VectorBaseFragment
     import im.vector.app.core.utils.saveMedia
    @@ -99,7 +98,7 @@ class RoomUploadsFragment @Inject constructor(
                         Unit
                     }
                     is RoomUploadsViewEvents.Failure             -> showFailure(it.throwable)
    -            }.exhaustive
    +            }
             }
         }
     
    diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/uploads/RoomUploadsViewModel.kt b/vector/src/main/java/im/vector/app/features/roomprofile/uploads/RoomUploadsViewModel.kt
    index 92ff33395e..c9aaca4373 100644
    --- a/vector/src/main/java/im/vector/app/features/roomprofile/uploads/RoomUploadsViewModel.kt
    +++ b/vector/src/main/java/im/vector/app/features/roomprofile/uploads/RoomUploadsViewModel.kt
    @@ -25,7 +25,6 @@ import dagger.assisted.AssistedFactory
     import dagger.assisted.AssistedInject
     import im.vector.app.core.di.MavericksAssistedViewModelFactory
     import im.vector.app.core.di.hiltMavericksViewModelFactory
    -import im.vector.app.core.extensions.exhaustive
     import im.vector.app.core.platform.VectorViewModel
     import kotlinx.coroutines.launch
     import org.matrix.android.sdk.api.session.Session
    @@ -110,7 +109,7 @@ class RoomUploadsViewModel @AssistedInject constructor(
                 is RoomUploadsAction.Share    -> handleShare(action)
                 RoomUploadsAction.Retry       -> handleLoadMore()
                 RoomUploadsAction.LoadMore    -> handleLoadMore()
    -        }.exhaustive
    +        }
         }
     
         private fun handleShare(action: RoomUploadsAction.Share) {
    diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/uploads/files/RoomUploadsFilesFragment.kt b/vector/src/main/java/im/vector/app/features/roomprofile/uploads/files/RoomUploadsFilesFragment.kt
    index 1739378761..953838aecd 100644
    --- a/vector/src/main/java/im/vector/app/features/roomprofile/uploads/files/RoomUploadsFilesFragment.kt
    +++ b/vector/src/main/java/im/vector/app/features/roomprofile/uploads/files/RoomUploadsFilesFragment.kt
    @@ -24,6 +24,7 @@ import androidx.core.content.ContextCompat
     import com.airbnb.mvrx.Fail
     import com.airbnb.mvrx.Loading
     import com.airbnb.mvrx.Success
    +import com.airbnb.mvrx.Uninitialized
     import com.airbnb.mvrx.parentFragmentViewModel
     import com.airbnb.mvrx.withState
     import im.vector.app.R
    @@ -91,6 +92,7 @@ class RoomUploadsFilesFragment @Inject constructor(
         override fun invalidate() = withState(uploadsViewModel) { state ->
             if (state.fileEvents.isEmpty()) {
                 when (state.asyncEventsRequest) {
    +                Uninitialized,
                     is Loading -> {
                         views.genericStateViewListStateView.state = StateView.State.Loading
                     }
    diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/uploads/media/RoomUploadsMediaFragment.kt b/vector/src/main/java/im/vector/app/features/roomprofile/uploads/media/RoomUploadsMediaFragment.kt
    index eb4337cffa..2f33f8403c 100644
    --- a/vector/src/main/java/im/vector/app/features/roomprofile/uploads/media/RoomUploadsMediaFragment.kt
    +++ b/vector/src/main/java/im/vector/app/features/roomprofile/uploads/media/RoomUploadsMediaFragment.kt
    @@ -29,6 +29,7 @@ import androidx.recyclerview.widget.GridLayoutManager
     import com.airbnb.mvrx.Fail
     import com.airbnb.mvrx.Loading
     import com.airbnb.mvrx.Success
    +import com.airbnb.mvrx.Uninitialized
     import com.airbnb.mvrx.parentFragmentViewModel
     import com.airbnb.mvrx.withState
     import com.google.android.material.appbar.AppBarLayout
    @@ -188,6 +189,7 @@ class RoomUploadsMediaFragment @Inject constructor(
         override fun invalidate() = withState(uploadsViewModel) { state ->
             if (state.mediaEvents.isEmpty()) {
                 when (state.asyncEventsRequest) {
    +                Uninitialized,
                     is Loading -> {
                         views.genericStateViewListStateView.state = StateView.State.Loading
                     }
    diff --git a/vector/src/main/java/im/vector/app/features/settings/VectorPreferences.kt b/vector/src/main/java/im/vector/app/features/settings/VectorPreferences.kt
    index 352c5768fb..8d93edc0ec 100755
    --- a/vector/src/main/java/im/vector/app/features/settings/VectorPreferences.kt
    +++ b/vector/src/main/java/im/vector/app/features/settings/VectorPreferences.kt
    @@ -201,7 +201,13 @@ class VectorPreferences @Inject constructor(private val context: Context) {
             private const val TAKE_PHOTO_VIDEO_MODE = "TAKE_PHOTO_VIDEO_MODE"
     
             private const val SETTINGS_LABS_RENDER_LOCATIONS_IN_TIMELINE = "SETTINGS_LABS_RENDER_LOCATIONS_IN_TIMELINE"
    -        const val SETTINGS_LABS_ENABLE_THREAD_MESSAGES = "SETTINGS_LABS_ENABLE_THREAD_MESSAGES"
    +
    +        // This key will be used to identify clients with the old thread support enabled io.element.thread
    +        const val SETTINGS_LABS_ENABLE_THREAD_MESSAGES_OLD_CLIENTS = "SETTINGS_LABS_ENABLE_THREAD_MESSAGES"
    +
    +        // This key will be used to identify clients with the new thread support enabled m.thread
    +        const val SETTINGS_LABS_ENABLE_THREAD_MESSAGES = "SETTINGS_LABS_ENABLE_THREAD_MESSAGES_FINAL"
    +        const val SETTINGS_THREAD_MESSAGES_SYNCED = "SETTINGS_THREAD_MESSAGES_SYNCED"
     
             // Possible values for TAKE_PHOTO_VIDEO_MODE
             const val TAKE_PHOTO_VIDEO_MODE_ALWAYS_ASK = 0
    @@ -1006,7 +1012,56 @@ class VectorPreferences @Inject constructor(private val context: Context) {
             return defaultPrefs.getBoolean(SETTINGS_LABS_RENDER_LOCATIONS_IN_TIMELINE, true)
         }
     
    +    /**
    +     * Indicates whether or not thread messages are enabled
    +     */
         fun areThreadMessagesEnabled(): Boolean {
    -        return defaultPrefs.getBoolean(SETTINGS_LABS_ENABLE_THREAD_MESSAGES, false)
    +        return defaultPrefs.getBoolean(SETTINGS_LABS_ENABLE_THREAD_MESSAGES, getDefault(R.bool.settings_labs_thread_messages_default))
    +    }
    +
    +    /**
    +     * Manually sets thread messages enabled, useful for migrating users from io.element.thread
    +     */
    +    fun setThreadMessagesEnabled() {
    +        defaultPrefs
    +                .edit()
    +                .putBoolean(SETTINGS_LABS_ENABLE_THREAD_MESSAGES, true)
    +                .apply()
    +    }
    +
    +    /**
    +     * Indicates whether or not the user will be notified about the new thread support
    +     * We should notify the user only if he had old thread support enabled
    +     */
    +    fun shouldNotifyUserAboutThreads(): Boolean {
    +        return defaultPrefs.getBoolean(SETTINGS_LABS_ENABLE_THREAD_MESSAGES_OLD_CLIENTS, false)
    +    }
    +
    +    /**
    +     * Indicates that the user have been notified about threads migration
    +     */
    +    fun userNotifiedAboutThreads() {
    +        defaultPrefs
    +                .edit()
    +                .putBoolean(SETTINGS_LABS_ENABLE_THREAD_MESSAGES_OLD_CLIENTS, false)
    +                .apply()
    +    }
    +
    +    /**
    +     * Indicates whether or not we should clear cache for threads migration.
    +     * Default value is true, for fresh installs and updates
    +     */
    +    fun shouldMigrateThreads(): Boolean {
    +        return defaultPrefs.getBoolean(SETTINGS_THREAD_MESSAGES_SYNCED, true)
    +    }
    +
    +    /**
    +     * Indicates that there no longer threads migration needed
    +     */
    +    fun setShouldMigrateThreads(shouldMigrate: Boolean) {
    +        defaultPrefs
    +                .edit()
    +                .putBoolean(SETTINGS_THREAD_MESSAGES_SYNCED, shouldMigrate)
    +                .apply()
         }
     }
    diff --git a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsLabsFragment.kt b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsLabsFragment.kt
    index 118e820f84..003832fb97 100644
    --- a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsLabsFragment.kt
    +++ b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsLabsFragment.kt
    @@ -42,6 +42,8 @@ class VectorSettingsLabsFragment @Inject constructor(
             // clear cache
             findPreference(VectorPreferences.SETTINGS_LABS_ENABLE_THREAD_MESSAGES)?.let {
                 it.onPreferenceClickListener = Preference.OnPreferenceClickListener {
    +                // We should migrate threads only if threads are disabled
    +                vectorPreferences.setShouldMigrateThreads(!vectorPreferences.areThreadMessagesEnabled())
                     lightweightSettingsStorage.setThreadMessagesEnabled(vectorPreferences.areThreadMessagesEnabled())
                     displayLoadingView()
                     MainActivity.restartApp(requireActivity(), MainActivityArgs(clearCache = true))
    diff --git a/vector/src/main/java/im/vector/app/features/settings/account/deactivation/DeactivateAccountFragment.kt b/vector/src/main/java/im/vector/app/features/settings/account/deactivation/DeactivateAccountFragment.kt
    index 631c375e62..4397da00c4 100644
    --- a/vector/src/main/java/im/vector/app/features/settings/account/deactivation/DeactivateAccountFragment.kt
    +++ b/vector/src/main/java/im/vector/app/features/settings/account/deactivation/DeactivateAccountFragment.kt
    @@ -25,7 +25,6 @@ import android.view.ViewGroup
     import androidx.appcompat.app.AppCompatActivity
     import com.airbnb.mvrx.fragmentViewModel
     import im.vector.app.R
    -import im.vector.app.core.extensions.exhaustive
     import im.vector.app.core.extensions.registerStartForActivityResult
     import im.vector.app.core.platform.VectorBaseFragment
     import im.vector.app.databinding.FragmentDeactivateAccountBinding
    @@ -128,7 +127,7 @@ class DeactivateAccountFragment @Inject constructor() : VectorBaseFragment {
                         views.waitingView.waitingView.isVisible = false
                     }
    -            }.exhaustive
    +            }
             }
         }
     
    diff --git a/vector/src/main/java/im/vector/app/features/settings/crosssigning/CrossSigningSettingsViewModel.kt b/vector/src/main/java/im/vector/app/features/settings/crosssigning/CrossSigningSettingsViewModel.kt
    index 644b7f33dd..5e691f64b2 100644
    --- a/vector/src/main/java/im/vector/app/features/settings/crosssigning/CrossSigningSettingsViewModel.kt
    +++ b/vector/src/main/java/im/vector/app/features/settings/crosssigning/CrossSigningSettingsViewModel.kt
    @@ -22,7 +22,6 @@ import dagger.assisted.AssistedInject
     import im.vector.app.R
     import im.vector.app.core.di.MavericksAssistedViewModelFactory
     import im.vector.app.core.di.hiltMavericksViewModelFactory
    -import im.vector.app.core.extensions.exhaustive
     import im.vector.app.core.platform.VectorViewModel
     import im.vector.app.core.resources.StringProvider
     import im.vector.app.features.auth.ReAuthActivity
    @@ -146,7 +145,7 @@ class CrossSigningSettingsViewModel @AssistedInject constructor(
                     uiaContinuation = null
                     pendingAuth = null
                 }
    -        }.exhaustive
    +        }
         }
     
         private fun handleInitializeXSigningError(failure: Throwable) {
    diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/VectorSettingsDevicesFragment.kt b/vector/src/main/java/im/vector/app/features/settings/devices/VectorSettingsDevicesFragment.kt
    index 5bbb03c8a4..407af19151 100644
    --- a/vector/src/main/java/im/vector/app/features/settings/devices/VectorSettingsDevicesFragment.kt
    +++ b/vector/src/main/java/im/vector/app/features/settings/devices/VectorSettingsDevicesFragment.kt
    @@ -32,7 +32,6 @@ import im.vector.app.R
     import im.vector.app.core.dialogs.ManuallyVerifyDialog
     import im.vector.app.core.extensions.cleanup
     import im.vector.app.core.extensions.configureWith
    -import im.vector.app.core.extensions.exhaustive
     import im.vector.app.core.extensions.registerStartForActivityResult
     import im.vector.app.core.platform.VectorBaseFragment
     import im.vector.app.databinding.DialogBaseEditTextBinding
    @@ -90,7 +89,7 @@ class VectorSettingsDevicesFragment @Inject constructor(
                             viewModel.handle(DevicesAction.MarkAsManuallyVerified(it.cryptoDeviceInfo))
                         }
                     }
    -            }.exhaustive
    +            }
             }
         }
     
    diff --git a/vector/src/main/java/im/vector/app/features/settings/devtools/AccountDataEpoxyController.kt b/vector/src/main/java/im/vector/app/features/settings/devtools/AccountDataEpoxyController.kt
    index f3ae18a72f..4748aeb45e 100644
    --- a/vector/src/main/java/im/vector/app/features/settings/devtools/AccountDataEpoxyController.kt
    +++ b/vector/src/main/java/im/vector/app/features/settings/devtools/AccountDataEpoxyController.kt
    @@ -21,6 +21,7 @@ import com.airbnb.epoxy.TypedEpoxyController
     import com.airbnb.mvrx.Fail
     import com.airbnb.mvrx.Loading
     import com.airbnb.mvrx.Success
    +import com.airbnb.mvrx.Uninitialized
     import im.vector.app.R
     import im.vector.app.core.epoxy.loadingItem
     import im.vector.app.core.resources.StringProvider
    @@ -45,6 +46,7 @@ class AccountDataEpoxyController @Inject constructor(
             if (data == null) return
             val host = this
             when (data.accountData) {
    +            Uninitialized,
                 is Loading -> {
                     loadingItem {
                         id("loading")
    diff --git a/vector/src/main/java/im/vector/app/features/settings/devtools/AccountDataViewModel.kt b/vector/src/main/java/im/vector/app/features/settings/devtools/AccountDataViewModel.kt
    index 6289699687..9576b84e98 100644
    --- a/vector/src/main/java/im/vector/app/features/settings/devtools/AccountDataViewModel.kt
    +++ b/vector/src/main/java/im/vector/app/features/settings/devtools/AccountDataViewModel.kt
    @@ -25,7 +25,6 @@ import dagger.assisted.AssistedFactory
     import dagger.assisted.AssistedInject
     import im.vector.app.core.di.MavericksAssistedViewModelFactory
     import im.vector.app.core.di.hiltMavericksViewModelFactory
    -import im.vector.app.core.extensions.exhaustive
     import im.vector.app.core.platform.EmptyViewEvents
     import im.vector.app.core.platform.VectorViewModel
     import kotlinx.coroutines.launch
    @@ -51,7 +50,7 @@ class AccountDataViewModel @AssistedInject constructor(@Assisted initialState: A
         override fun handle(action: AccountDataAction) {
             when (action) {
                 is AccountDataAction.DeleteAccountData -> handleDeleteAccountData(action)
    -        }.exhaustive
    +        }
         }
     
         private fun handleDeleteAccountData(action: AccountDataAction.DeleteAccountData) {
    diff --git a/vector/src/main/java/im/vector/app/features/settings/devtools/KeyRequestViewModel.kt b/vector/src/main/java/im/vector/app/features/settings/devtools/KeyRequestViewModel.kt
    index f480eb2db8..fd1cd3480d 100644
    --- a/vector/src/main/java/im/vector/app/features/settings/devtools/KeyRequestViewModel.kt
    +++ b/vector/src/main/java/im/vector/app/features/settings/devtools/KeyRequestViewModel.kt
    @@ -29,7 +29,6 @@ import dagger.assisted.AssistedFactory
     import dagger.assisted.AssistedInject
     import im.vector.app.core.di.MavericksAssistedViewModelFactory
     import im.vector.app.core.di.hiltMavericksViewModelFactory
    -import im.vector.app.core.extensions.exhaustive
     import im.vector.app.core.platform.VectorViewEvents
     import im.vector.app.core.platform.VectorViewModel
     import im.vector.app.core.platform.VectorViewModelAction
    @@ -64,7 +63,7 @@ class KeyRequestViewModel @AssistedInject constructor(
         override fun handle(action: KeyRequestAction) {
             when (action) {
                 is KeyRequestAction.ExportAudit -> exportAudit(action)
    -        }.exhaustive
    +        }
         }
     
         private fun exportAudit(action: KeyRequestAction.ExportAudit) {
    diff --git a/vector/src/main/java/im/vector/app/features/settings/devtools/KeyRequestsFragment.kt b/vector/src/main/java/im/vector/app/features/settings/devtools/KeyRequestsFragment.kt
    index d807fc620a..db2d07feef 100644
    --- a/vector/src/main/java/im/vector/app/features/settings/devtools/KeyRequestsFragment.kt
    +++ b/vector/src/main/java/im/vector/app/features/settings/devtools/KeyRequestsFragment.kt
    @@ -33,7 +33,6 @@ import com.airbnb.mvrx.fragmentViewModel
     import com.airbnb.mvrx.withState
     import com.google.android.material.tabs.TabLayoutMediator
     import im.vector.app.R
    -import im.vector.app.core.extensions.exhaustive
     import im.vector.app.core.extensions.registerStartForActivityResult
     import im.vector.app.core.platform.VectorBaseFragment
     import im.vector.app.core.utils.selectTxtFileToWrite
    @@ -111,7 +110,7 @@ class KeyRequestsFragment @Inject constructor() : VectorBaseFragment os.write(it.raw.toByteArray()) }
                         }
                     }
    -            }.exhaustive
    +            }
             }
         }
     
    diff --git a/vector/src/main/java/im/vector/app/features/settings/ignored/VectorSettingsIgnoredUsersFragment.kt b/vector/src/main/java/im/vector/app/features/settings/ignored/VectorSettingsIgnoredUsersFragment.kt
    index 509014492d..5c188fe933 100644
    --- a/vector/src/main/java/im/vector/app/features/settings/ignored/VectorSettingsIgnoredUsersFragment.kt
    +++ b/vector/src/main/java/im/vector/app/features/settings/ignored/VectorSettingsIgnoredUsersFragment.kt
    @@ -30,7 +30,6 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder
     import im.vector.app.R
     import im.vector.app.core.extensions.cleanup
     import im.vector.app.core.extensions.configureWith
    -import im.vector.app.core.extensions.exhaustive
     import im.vector.app.core.platform.VectorBaseFragment
     import im.vector.app.databinding.FragmentGenericRecyclerBinding
     import javax.inject.Inject
    @@ -57,7 +56,7 @@ class VectorSettingsIgnoredUsersFragment @Inject constructor(
                 when (it) {
                     is IgnoredUsersViewEvents.Loading -> showLoading(it.message)
                     is IgnoredUsersViewEvents.Failure -> showFailure(it.throwable)
    -            }.exhaustive
    +            }
             }
         }
     
    diff --git a/vector/src/main/java/im/vector/app/features/settings/legals/LegalsViewModel.kt b/vector/src/main/java/im/vector/app/features/settings/legals/LegalsViewModel.kt
    index 9d58535490..1497c793c2 100644
    --- a/vector/src/main/java/im/vector/app/features/settings/legals/LegalsViewModel.kt
    +++ b/vector/src/main/java/im/vector/app/features/settings/legals/LegalsViewModel.kt
    @@ -25,7 +25,6 @@ import dagger.assisted.AssistedInject
     import im.vector.app.R
     import im.vector.app.core.di.MavericksAssistedViewModelFactory
     import im.vector.app.core.di.hiltMavericksViewModelFactory
    -import im.vector.app.core.extensions.exhaustive
     import im.vector.app.core.platform.EmptyViewEvents
     import im.vector.app.core.platform.VectorViewModel
     import im.vector.app.core.resources.StringProvider
    @@ -50,7 +49,7 @@ class LegalsViewModel @AssistedInject constructor(
         override fun handle(action: LegalsAction) {
             when (action) {
                 LegalsAction.Refresh -> loadData()
    -        }.exhaustive
    +        }
         }
     
         private fun loadData() = withState { state ->
    diff --git a/vector/src/main/java/im/vector/app/features/settings/locale/LocalePickerController.kt b/vector/src/main/java/im/vector/app/features/settings/locale/LocalePickerController.kt
    index 4e1c62a4ec..cffef0da7b 100644
    --- a/vector/src/main/java/im/vector/app/features/settings/locale/LocalePickerController.kt
    +++ b/vector/src/main/java/im/vector/app/features/settings/locale/LocalePickerController.kt
    @@ -17,12 +17,16 @@
     package im.vector.app.features.settings.locale
     
     import com.airbnb.epoxy.TypedEpoxyController
    -import com.airbnb.mvrx.Incomplete
    +import com.airbnb.mvrx.Fail
    +import com.airbnb.mvrx.Loading
     import com.airbnb.mvrx.Success
    +import com.airbnb.mvrx.Uninitialized
     import im.vector.app.R
    +import im.vector.app.core.epoxy.errorWithRetryItem
     import im.vector.app.core.epoxy.loadingItem
     import im.vector.app.core.epoxy.noResultItem
     import im.vector.app.core.epoxy.profiles.profileSectionItem
    +import im.vector.app.core.error.ErrorFormatter
     import im.vector.app.core.resources.StringProvider
     import im.vector.app.core.utils.safeCapitalize
     import im.vector.app.features.settings.VectorLocale
    @@ -32,7 +36,8 @@ import javax.inject.Inject
     
     class LocalePickerController @Inject constructor(
             private val vectorPreferences: VectorPreferences,
    -        private val stringProvider: StringProvider
    +        private val stringProvider: StringProvider,
    +        private val errorFormatter: ErrorFormatter
     ) : TypedEpoxyController() {
     
         var listener: Listener? = null
    @@ -58,13 +63,14 @@ class LocalePickerController @Inject constructor(
                 title(host.stringProvider.getString(R.string.choose_locale_other_locales_title))
             }
             when (list) {
    -            is Incomplete -> {
    +            Uninitialized,
    +            is Loading -> {
                     loadingItem {
                         id("loading")
                         loadingText(host.stringProvider.getString(R.string.choose_locale_loading_locales))
                     }
                 }
    -            is Success    ->
    +            is Success ->
                     if (list().isEmpty()) {
                         noResultItem {
                             id("noResult")
    @@ -84,6 +90,11 @@ class LocalePickerController @Inject constructor(
                                     }
                                 }
                     }
    +            is Fail    ->
    +                errorWithRetryItem {
    +                    id("error")
    +                    text(host.errorFormatter.toHumanReadable(list.error))
    +                }
             }
         }
     
    diff --git a/vector/src/main/java/im/vector/app/features/settings/locale/LocalePickerFragment.kt b/vector/src/main/java/im/vector/app/features/settings/locale/LocalePickerFragment.kt
    index 601574c908..d46b66dd87 100644
    --- a/vector/src/main/java/im/vector/app/features/settings/locale/LocalePickerFragment.kt
    +++ b/vector/src/main/java/im/vector/app/features/settings/locale/LocalePickerFragment.kt
    @@ -26,7 +26,6 @@ import com.airbnb.mvrx.withState
     import im.vector.app.R
     import im.vector.app.core.extensions.cleanup
     import im.vector.app.core.extensions.configureWith
    -import im.vector.app.core.extensions.exhaustive
     import im.vector.app.core.extensions.restart
     import im.vector.app.core.platform.VectorBaseFragment
     import im.vector.app.databinding.FragmentLocalePickerBinding
    @@ -54,7 +53,7 @@ class LocalePickerFragment @Inject constructor(
                     LocalePickerViewEvents.RestartActivity -> {
                         activity?.restart()
                     }
    -            }.exhaustive
    +            }
             }
         }
     
    diff --git a/vector/src/main/java/im/vector/app/features/settings/locale/LocalePickerViewModel.kt b/vector/src/main/java/im/vector/app/features/settings/locale/LocalePickerViewModel.kt
    index d6b35fa4fe..0bbbc323e0 100644
    --- a/vector/src/main/java/im/vector/app/features/settings/locale/LocalePickerViewModel.kt
    +++ b/vector/src/main/java/im/vector/app/features/settings/locale/LocalePickerViewModel.kt
    @@ -23,7 +23,6 @@ import dagger.assisted.AssistedFactory
     import dagger.assisted.AssistedInject
     import im.vector.app.core.di.MavericksAssistedViewModelFactory
     import im.vector.app.core.di.hiltMavericksViewModelFactory
    -import im.vector.app.core.extensions.exhaustive
     import im.vector.app.core.platform.VectorViewModel
     import im.vector.app.features.configuration.VectorConfiguration
     import im.vector.app.features.settings.VectorLocale
    @@ -56,7 +55,7 @@ class LocalePickerViewModel @AssistedInject constructor(
         override fun handle(action: LocalePickerAction) {
             when (action) {
                 is LocalePickerAction.SelectLocale -> handleSelectLocale(action)
    -        }.exhaustive
    +        }
         }
     
         private fun handleSelectLocale(action: LocalePickerAction.SelectLocale) {
    diff --git a/vector/src/main/java/im/vector/app/features/settings/push/PushGatewaysFragment.kt b/vector/src/main/java/im/vector/app/features/settings/push/PushGatewaysFragment.kt
    index 65c62542bb..73a74b1e3f 100644
    --- a/vector/src/main/java/im/vector/app/features/settings/push/PushGatewaysFragment.kt
    +++ b/vector/src/main/java/im/vector/app/features/settings/push/PushGatewaysFragment.kt
    @@ -28,7 +28,6 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder
     import im.vector.app.R
     import im.vector.app.core.extensions.cleanup
     import im.vector.app.core.extensions.configureWith
    -import im.vector.app.core.extensions.exhaustive
     import im.vector.app.core.platform.VectorBaseFragment
     import im.vector.app.databinding.FragmentGenericRecyclerBinding
     import org.matrix.android.sdk.api.session.pushers.Pusher
    @@ -78,7 +77,7 @@ class PushGatewaysFragment @Inject constructor(
                                 .setPositiveButton(android.R.string.ok, null)
                                 .show()
                     }
    -            }.exhaustive
    +            }
             }
         }
     
    diff --git a/vector/src/main/java/im/vector/app/features/settings/push/PushGatewaysViewModel.kt b/vector/src/main/java/im/vector/app/features/settings/push/PushGatewaysViewModel.kt
    index 1256673364..4d95447f2d 100644
    --- a/vector/src/main/java/im/vector/app/features/settings/push/PushGatewaysViewModel.kt
    +++ b/vector/src/main/java/im/vector/app/features/settings/push/PushGatewaysViewModel.kt
    @@ -25,7 +25,6 @@ import dagger.assisted.AssistedFactory
     import dagger.assisted.AssistedInject
     import im.vector.app.core.di.MavericksAssistedViewModelFactory
     import im.vector.app.core.di.hiltMavericksViewModelFactory
    -import im.vector.app.core.extensions.exhaustive
     import im.vector.app.core.platform.VectorViewModel
     import kotlinx.coroutines.launch
     import org.matrix.android.sdk.api.session.Session
    @@ -65,7 +64,7 @@ class PushGatewaysViewModel @AssistedInject constructor(@Assisted initialState:
             when (action) {
                 is PushGatewayAction.Refresh      -> handleRefresh()
                 is PushGatewayAction.RemovePusher -> removePusher(action.pusher)
    -        }.exhaustive
    +        }
         }
     
         private fun removePusher(pusher: Pusher) {
    diff --git a/vector/src/main/java/im/vector/app/features/settings/threepids/ThreePidsSettingsController.kt b/vector/src/main/java/im/vector/app/features/settings/threepids/ThreePidsSettingsController.kt
    index d374357396..61d93b6f5f 100644
    --- a/vector/src/main/java/im/vector/app/features/settings/threepids/ThreePidsSettingsController.kt
    +++ b/vector/src/main/java/im/vector/app/features/settings/threepids/ThreePidsSettingsController.kt
    @@ -21,11 +21,11 @@ import com.airbnb.epoxy.TypedEpoxyController
     import com.airbnb.mvrx.Fail
     import com.airbnb.mvrx.Loading
     import com.airbnb.mvrx.Success
    +import com.airbnb.mvrx.Uninitialized
     import im.vector.app.R
     import im.vector.app.core.epoxy.loadingItem
     import im.vector.app.core.epoxy.noResultItem
     import im.vector.app.core.error.ErrorFormatter
    -import im.vector.app.core.extensions.exhaustive
     import im.vector.app.core.extensions.getFormattedValue
     import im.vector.app.core.resources.ColorProvider
     import im.vector.app.core.resources.StringProvider
    @@ -78,6 +78,7 @@ class ThreePidsSettingsController @Inject constructor(
             }
     
             when (data.threePids) {
    +            Uninitialized,
                 is Loading -> {
                     loadingItem {
                         id("loading")
    @@ -160,7 +161,7 @@ class ThreePidsSettingsController @Inject constructor(
                     }
                 }
                 is ThreePidsSettingsUiState.AddingPhoneNumber -> Unit
    -        }.exhaustive
    +        }
     
             settingsSectionTitleItem {
                 id("msisdn")
    @@ -223,7 +224,7 @@ class ThreePidsSettingsController @Inject constructor(
                         cancelOnClick { host.interactionListener?.cancelAdding() }
                     }
                 }
    -        }.exhaustive
    +        }
         }
     
         private fun buildThreePid(idPrefix: String, threePid: ThreePid) {
    diff --git a/vector/src/main/java/im/vector/app/features/settings/threepids/ThreePidsSettingsFragment.kt b/vector/src/main/java/im/vector/app/features/settings/threepids/ThreePidsSettingsFragment.kt
    index bdb1fb895f..ee7f8efab4 100644
    --- a/vector/src/main/java/im/vector/app/features/settings/threepids/ThreePidsSettingsFragment.kt
    +++ b/vector/src/main/java/im/vector/app/features/settings/threepids/ThreePidsSettingsFragment.kt
    @@ -28,7 +28,6 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder
     import im.vector.app.R
     import im.vector.app.core.extensions.cleanup
     import im.vector.app.core.extensions.configureWith
    -import im.vector.app.core.extensions.exhaustive
     import im.vector.app.core.extensions.getFormattedValue
     import im.vector.app.core.extensions.hideKeyboard
     import im.vector.app.core.extensions.isEmail
    @@ -64,7 +63,7 @@ class ThreePidsSettingsFragment @Inject constructor(
                 when (it) {
                     is ThreePidsSettingsViewEvents.Failure -> displayErrorDialog(it.throwable)
                     is ThreePidsSettingsViewEvents.RequestReAuth -> askAuthentication(it)
    -            }.exhaustive
    +            }
             }
         }
     
    diff --git a/vector/src/main/java/im/vector/app/features/settings/threepids/ThreePidsSettingsViewModel.kt b/vector/src/main/java/im/vector/app/features/settings/threepids/ThreePidsSettingsViewModel.kt
    index 12ff436ccb..acbe893d58 100644
    --- a/vector/src/main/java/im/vector/app/features/settings/threepids/ThreePidsSettingsViewModel.kt
    +++ b/vector/src/main/java/im/vector/app/features/settings/threepids/ThreePidsSettingsViewModel.kt
    @@ -25,7 +25,6 @@ import dagger.assisted.AssistedInject
     import im.vector.app.R
     import im.vector.app.core.di.MavericksAssistedViewModelFactory
     import im.vector.app.core.di.hiltMavericksViewModelFactory
    -import im.vector.app.core.extensions.exhaustive
     import im.vector.app.core.platform.VectorViewModel
     import im.vector.app.core.resources.StringProvider
     import im.vector.app.core.utils.ReadOnceTrue
    @@ -149,7 +148,7 @@ class ThreePidsSettingsViewModel @AssistedInject constructor(
                     uiaContinuation = null
                     pendingAuth = null
                 }
    -        }.exhaustive
    +        }
         }
     
         var uiaContinuation: Continuation? = null
    diff --git a/vector/src/main/java/im/vector/app/features/share/IncomingShareFragment.kt b/vector/src/main/java/im/vector/app/features/share/IncomingShareFragment.kt
    index 62fb064536..9dc433e96f 100644
    --- a/vector/src/main/java/im/vector/app/features/share/IncomingShareFragment.kt
    +++ b/vector/src/main/java/im/vector/app/features/share/IncomingShareFragment.kt
    @@ -34,7 +34,6 @@ import im.vector.app.R
     import im.vector.app.core.di.ActiveSessionHolder
     import im.vector.app.core.extensions.cleanup
     import im.vector.app.core.extensions.configureWith
    -import im.vector.app.core.extensions.exhaustive
     import im.vector.app.core.extensions.registerStartForActivityResult
     import im.vector.app.core.platform.VectorBaseFragment
     import im.vector.app.databinding.FragmentIncomingShareBinding
    @@ -81,7 +80,7 @@ class IncomingShareFragment @Inject constructor(
                     is IncomingShareViewEvents.ShareToRoom            -> handleShareToRoom(it)
                     is IncomingShareViewEvents.EditMediaBeforeSending -> handleEditMediaBeforeSending(it)
                     is IncomingShareViewEvents.MultipleRoomsShareDone -> handleMultipleRoomsShareDone(it)
    -            }.exhaustive
    +            }
             }
     
             val intent = vectorBaseActivity.intent
    diff --git a/vector/src/main/java/im/vector/app/features/share/IncomingShareViewModel.kt b/vector/src/main/java/im/vector/app/features/share/IncomingShareViewModel.kt
    index 4a413ad8ba..ca4148ebb7 100644
    --- a/vector/src/main/java/im/vector/app/features/share/IncomingShareViewModel.kt
    +++ b/vector/src/main/java/im/vector/app/features/share/IncomingShareViewModel.kt
    @@ -22,7 +22,6 @@ import dagger.assisted.AssistedFactory
     import dagger.assisted.AssistedInject
     import im.vector.app.core.di.MavericksAssistedViewModelFactory
     import im.vector.app.core.di.hiltMavericksViewModelFactory
    -import im.vector.app.core.extensions.exhaustive
     import im.vector.app.core.extensions.toggle
     import im.vector.app.core.platform.VectorViewModel
     import im.vector.app.features.attachments.isPreviewable
    @@ -96,7 +95,7 @@ class IncomingShareViewModel @AssistedInject constructor(
                 is IncomingShareAction.ShareMedia           -> handleShareMediaToSelectedRooms(action)
                 is IncomingShareAction.FilterWith           -> handleFilter(action)
                 is IncomingShareAction.UpdateSharedData     -> handleUpdateSharedData(action)
    -        }.exhaustive
    +        }
         }
     
         private fun handleUpdateSharedData(action: IncomingShareAction.UpdateSharedData) {
    @@ -127,7 +126,7 @@ class IncomingShareViewModel @AssistedInject constructor(
                     is SharedData.Attachments -> {
                         shareAttachments(sharedData.attachmentData, state.selectedRoomIds, proposeMediaEdition = true, compressMediaBeforeSending = false)
                     }
    -            }.exhaustive
    +            }
             }
         }
     
    diff --git a/vector/src/main/java/im/vector/app/features/signout/soft/SoftLogoutController.kt b/vector/src/main/java/im/vector/app/features/signout/soft/SoftLogoutController.kt
    index 0cd9cde547..e2f3c14e7d 100644
    --- a/vector/src/main/java/im/vector/app/features/signout/soft/SoftLogoutController.kt
    +++ b/vector/src/main/java/im/vector/app/features/signout/soft/SoftLogoutController.kt
    @@ -18,8 +18,9 @@ package im.vector.app.features.signout.soft
     
     import com.airbnb.epoxy.EpoxyController
     import com.airbnb.mvrx.Fail
    -import com.airbnb.mvrx.Incomplete
    +import com.airbnb.mvrx.Loading
     import com.airbnb.mvrx.Success
    +import com.airbnb.mvrx.Uninitialized
     import im.vector.app.R
     import im.vector.app.core.epoxy.loadingItem
     import im.vector.app.core.error.ErrorFormatter
    @@ -89,19 +90,20 @@ class SoftLogoutController @Inject constructor(
         private fun buildForm(state: SoftLogoutViewState) {
             val host = this
             when (state.asyncHomeServerLoginFlowRequest) {
    -            is Incomplete -> {
    +            Uninitialized,
    +            is Loading -> {
                     loadingItem {
                         id("loading")
                     }
                 }
    -            is Fail       -> {
    +            is Fail    -> {
                     loginErrorWithRetryItem {
                         id("errorRetry")
                         text(host.errorFormatter.toHumanReadable(state.asyncHomeServerLoginFlowRequest.error))
                         listener { host.listener?.retry() }
                     }
                 }
    -            is Success    -> {
    +            is Success -> {
                     when (state.asyncHomeServerLoginFlowRequest.invoke()) {
                         LoginMode.Password          -> {
                             loginPasswordFormItem {
    diff --git a/vector/src/main/java/im/vector/app/features/spaces/SpaceListFragment.kt b/vector/src/main/java/im/vector/app/features/spaces/SpaceListFragment.kt
    index dff98722eb..1fc131ca86 100644
    --- a/vector/src/main/java/im/vector/app/features/spaces/SpaceListFragment.kt
    +++ b/vector/src/main/java/im/vector/app/features/spaces/SpaceListFragment.kt
    @@ -22,13 +22,13 @@ import android.view.LayoutInflater
     import android.view.View
     import android.view.ViewGroup
     import com.airbnb.epoxy.EpoxyTouchHelper
    -import com.airbnb.mvrx.Incomplete
    +import com.airbnb.mvrx.Loading
     import com.airbnb.mvrx.Success
    +import com.airbnb.mvrx.Uninitialized
     import com.airbnb.mvrx.fragmentViewModel
     import com.airbnb.mvrx.withState
     import im.vector.app.core.extensions.cleanup
     import im.vector.app.core.extensions.configureWith
    -import im.vector.app.core.extensions.exhaustive
     import im.vector.app.core.platform.StateView
     import im.vector.app.core.platform.VectorBaseFragment
     import im.vector.app.databinding.FragmentGroupListBinding
    @@ -109,7 +109,7 @@ class SpaceListFragment @Inject constructor(
                     is SpaceListViewEvents.AddSpace         -> sharedActionViewModel.post(HomeActivitySharedAction.AddSpace)
                     is SpaceListViewEvents.OpenGroup        -> sharedActionViewModel.post(HomeActivitySharedAction.OpenGroup(it.groupingMethodHasChanged))
                     is SpaceListViewEvents.OpenSpaceInvite  -> sharedActionViewModel.post(HomeActivitySharedAction.OpenSpaceInvite(it.id))
    -            }.exhaustive
    +            }
             }
         }
     
    @@ -121,8 +121,10 @@ class SpaceListFragment @Inject constructor(
     
         override fun invalidate() = withState(viewModel) { state ->
             when (state.asyncSpaces) {
    -            is Incomplete -> views.stateView.state = StateView.State.Loading
    -            is Success    -> views.stateView.state = StateView.State.Content
    +            Uninitialized,
    +            is Loading -> views.stateView.state = StateView.State.Loading
    +            is Success -> views.stateView.state = StateView.State.Content
    +            else       -> Unit
             }
             spaceController.update(state)
         }
    diff --git a/vector/src/main/java/im/vector/app/features/spaces/create/CreateSpaceViewModel.kt b/vector/src/main/java/im/vector/app/features/spaces/create/CreateSpaceViewModel.kt
    index 8ddeab3223..2b8276a4d7 100644
    --- a/vector/src/main/java/im/vector/app/features/spaces/create/CreateSpaceViewModel.kt
    +++ b/vector/src/main/java/im/vector/app/features/spaces/create/CreateSpaceViewModel.kt
    @@ -29,7 +29,6 @@ import im.vector.app.R
     import im.vector.app.core.di.MavericksAssistedViewModelFactory
     import im.vector.app.core.di.hiltMavericksViewModelFactory
     import im.vector.app.core.error.ErrorFormatter
    -import im.vector.app.core.extensions.exhaustive
     import im.vector.app.core.extensions.isEmail
     import im.vector.app.core.platform.VectorViewModel
     import im.vector.app.core.resources.StringProvider
    @@ -192,7 +191,7 @@ class CreateSpaceViewModel @AssistedInject constructor(
                 is CreateSpaceAction.SetSpaceTopology         -> {
                     handleSetTopology(action)
                 }
    -        }.exhaustive
    +        }
         }
     
         private fun handleSetTopology(action: CreateSpaceAction.SetSpaceTopology) {
    diff --git a/vector/src/main/java/im/vector/app/features/spaces/manage/SpaceManageActivity.kt b/vector/src/main/java/im/vector/app/features/spaces/manage/SpaceManageActivity.kt
    index 85f80960b0..12ae8fc1f9 100644
    --- a/vector/src/main/java/im/vector/app/features/spaces/manage/SpaceManageActivity.kt
    +++ b/vector/src/main/java/im/vector/app/features/spaces/manage/SpaceManageActivity.kt
    @@ -85,6 +85,7 @@ class SpaceManageActivity : VectorBaseActivity() {
                         when (sharedAction) {
                             is RoomDirectorySharedAction.Back,
                             is RoomDirectorySharedAction.Close -> finish()
    +                        else                               -> Unit
                         }
                     }
                     .launchIn(lifecycleScope)
    diff --git a/vector/src/main/java/im/vector/app/features/spaces/manage/SpaceManageSharedViewModel.kt b/vector/src/main/java/im/vector/app/features/spaces/manage/SpaceManageSharedViewModel.kt
    index bedd1873e8..2a2598075f 100644
    --- a/vector/src/main/java/im/vector/app/features/spaces/manage/SpaceManageSharedViewModel.kt
    +++ b/vector/src/main/java/im/vector/app/features/spaces/manage/SpaceManageSharedViewModel.kt
    @@ -22,7 +22,6 @@ import dagger.assisted.AssistedFactory
     import dagger.assisted.AssistedInject
     import im.vector.app.core.di.MavericksAssistedViewModelFactory
     import im.vector.app.core.di.hiltMavericksViewModelFactory
    -import im.vector.app.core.extensions.exhaustive
     import im.vector.app.core.platform.VectorViewModel
     import org.matrix.android.sdk.api.session.Session
     
    @@ -51,6 +50,6 @@ class SpaceManageSharedViewModel @AssistedInject constructor(
                 SpaceManagedSharedAction.ManageRooms                 -> _viewEvents.post(SpaceManagedSharedViewEvents.NavigateToManageRooms)
                 SpaceManagedSharedAction.OpenSpaceAliasesSettings    -> _viewEvents.post(SpaceManagedSharedViewEvents.NavigateToAliasSettings)
                 SpaceManagedSharedAction.OpenSpacePermissionSettings -> _viewEvents.post(SpaceManagedSharedViewEvents.NavigateToPermissionSettings)
    -        }.exhaustive
    +        }
         }
     }
    diff --git a/vector/src/main/java/im/vector/app/features/spaces/manage/SpaceSettingsFragment.kt b/vector/src/main/java/im/vector/app/features/spaces/manage/SpaceSettingsFragment.kt
    index 266d08fd12..db9420abc2 100644
    --- a/vector/src/main/java/im/vector/app/features/spaces/manage/SpaceSettingsFragment.kt
    +++ b/vector/src/main/java/im/vector/app/features/spaces/manage/SpaceSettingsFragment.kt
    @@ -34,7 +34,6 @@ import im.vector.app.R
     import im.vector.app.core.dialogs.GalleryOrCameraDialogHelper
     import im.vector.app.core.extensions.cleanup
     import im.vector.app.core.extensions.configureWith
    -import im.vector.app.core.extensions.exhaustive
     import im.vector.app.core.intent.getFilenameFromUri
     import im.vector.app.core.platform.OnBackPressed
     import im.vector.app.core.platform.VectorBaseFragment
    @@ -102,7 +101,7 @@ class SpaceSettingsFragment @Inject constructor(
                         ignoreChanges = true
                         vectorBaseActivity.onBackPressed()
                     }
    -            }.exhaustive
    +            }
             }
         }
     
    diff --git a/vector/src/main/java/im/vector/app/features/spaces/people/SpacePeopleViewModel.kt b/vector/src/main/java/im/vector/app/features/spaces/people/SpacePeopleViewModel.kt
    index 55d1dbe61e..2e386697d4 100644
    --- a/vector/src/main/java/im/vector/app/features/spaces/people/SpacePeopleViewModel.kt
    +++ b/vector/src/main/java/im/vector/app/features/spaces/people/SpacePeopleViewModel.kt
    @@ -25,7 +25,6 @@ import dagger.assisted.AssistedFactory
     import dagger.assisted.AssistedInject
     import im.vector.app.core.di.MavericksAssistedViewModelFactory
     import im.vector.app.core.di.hiltMavericksViewModelFactory
    -import im.vector.app.core.extensions.exhaustive
     import im.vector.app.core.platform.VectorViewModel
     import im.vector.app.features.raw.wellknown.getElementWellknown
     import im.vector.app.features.raw.wellknown.isE2EByDefault
    @@ -52,7 +51,7 @@ class SpacePeopleViewModel @AssistedInject constructor(
             when (action) {
                 is SpacePeopleViewAction.ChatWith   -> handleChatWith(action)
                 SpacePeopleViewAction.InviteToSpace -> handleInviteToSpace()
    -        }.exhaustive
    +        }
         }
     
         private fun handleInviteToSpace() {
    diff --git a/vector/src/main/java/im/vector/app/features/terms/ReviewTermsActivity.kt b/vector/src/main/java/im/vector/app/features/terms/ReviewTermsActivity.kt
    index e6071fdd2a..9a86e550a8 100644
    --- a/vector/src/main/java/im/vector/app/features/terms/ReviewTermsActivity.kt
    +++ b/vector/src/main/java/im/vector/app/features/terms/ReviewTermsActivity.kt
    @@ -23,7 +23,6 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder
     import dagger.hilt.android.AndroidEntryPoint
     import im.vector.app.R
     import im.vector.app.core.error.ErrorFormatter
    -import im.vector.app.core.extensions.exhaustive
     import im.vector.app.core.extensions.replaceFragment
     import im.vector.app.core.platform.SimpleFragmentActivity
     import org.matrix.android.sdk.api.session.terms.TermsService
    @@ -63,7 +62,7 @@ class ReviewTermsActivity : SimpleFragmentActivity() {
                         setResult(Activity.RESULT_OK)
                         finish()
                     }
    -            }.exhaustive
    +            }
             }
         }
     
    diff --git a/vector/src/main/java/im/vector/app/features/terms/ReviewTermsFragment.kt b/vector/src/main/java/im/vector/app/features/terms/ReviewTermsFragment.kt
    index cb76e5b31f..53afbf7a07 100644
    --- a/vector/src/main/java/im/vector/app/features/terms/ReviewTermsFragment.kt
    +++ b/vector/src/main/java/im/vector/app/features/terms/ReviewTermsFragment.kt
    @@ -29,7 +29,6 @@ import im.vector.app.R
     import im.vector.app.core.epoxy.onClick
     import im.vector.app.core.extensions.cleanup
     import im.vector.app.core.extensions.configureWith
    -import im.vector.app.core.extensions.exhaustive
     import im.vector.app.core.platform.VectorBaseFragment
     import im.vector.app.core.utils.openUrlInChromeCustomTab
     import im.vector.app.databinding.FragmentReviewTermsBinding
    @@ -70,7 +69,7 @@ class ReviewTermsFragment @Inject constructor(
                     ReviewTermsViewEvents.Success    -> {
                         // Handled by the Activity
                     }
    -            }.exhaustive
    +            }
             }
     
             reviewTermsViewModel.handle(ReviewTermsAction.LoadTerms(getString(R.string.resources_language)))
    diff --git a/vector/src/main/java/im/vector/app/features/terms/ReviewTermsViewModel.kt b/vector/src/main/java/im/vector/app/features/terms/ReviewTermsViewModel.kt
    index 9932efb11a..8fe1f598f6 100644
    --- a/vector/src/main/java/im/vector/app/features/terms/ReviewTermsViewModel.kt
    +++ b/vector/src/main/java/im/vector/app/features/terms/ReviewTermsViewModel.kt
    @@ -24,7 +24,6 @@ import dagger.assisted.AssistedFactory
     import dagger.assisted.AssistedInject
     import im.vector.app.core.di.MavericksAssistedViewModelFactory
     import im.vector.app.core.di.hiltMavericksViewModelFactory
    -import im.vector.app.core.extensions.exhaustive
     import im.vector.app.core.platform.VectorViewModel
     import kotlinx.coroutines.launch
     import org.matrix.android.sdk.api.session.Session
    @@ -49,7 +48,7 @@ class ReviewTermsViewModel @AssistedInject constructor(
                 is ReviewTermsAction.LoadTerms          -> loadTerms(action)
                 is ReviewTermsAction.MarkTermAsAccepted -> markTermAsAccepted(action)
                 ReviewTermsAction.Accept                -> acceptTerms()
    -        }.exhaustive
    +        }
         }
     
         private fun markTermAsAccepted(action: ReviewTermsAction.MarkTermAsAccepted) = withState { state ->
    diff --git a/vector/src/main/java/im/vector/app/features/terms/TermsController.kt b/vector/src/main/java/im/vector/app/features/terms/TermsController.kt
    index 6109e9abc8..10238829b3 100644
    --- a/vector/src/main/java/im/vector/app/features/terms/TermsController.kt
    +++ b/vector/src/main/java/im/vector/app/features/terms/TermsController.kt
    @@ -17,8 +17,9 @@ package im.vector.app.features.terms
     
     import com.airbnb.epoxy.TypedEpoxyController
     import com.airbnb.mvrx.Fail
    -import com.airbnb.mvrx.Incomplete
    +import com.airbnb.mvrx.Loading
     import com.airbnb.mvrx.Success
    +import com.airbnb.mvrx.Uninitialized
     import im.vector.app.R
     import im.vector.app.core.epoxy.errorWithRetryItem
     import im.vector.app.core.epoxy.loadingItem
    @@ -38,19 +39,20 @@ class TermsController @Inject constructor(
             val host = this
     
             when (data.termsList) {
    -            is Incomplete -> {
    +            Uninitialized,
    +            is Loading -> {
                     loadingItem {
                         id("loading")
                     }
                 }
    -            is Fail       -> {
    +            is Fail    -> {
                     errorWithRetryItem {
                         id("errorRetry")
                         text(host.errorFormatter.toHumanReadable(data.termsList.error))
                         listener { host.listener?.retry() }
                     }
                 }
    -            is Success    -> buildTerms(data.termsList.invoke())
    +            is Success -> buildTerms(data.termsList.invoke())
             }
         }
     
    @@ -67,7 +69,7 @@ class TermsController @Inject constructor(
                     description(host.description)
                     checked(term.accepted)
     
    -                clickListener  { host.listener?.review(term) }
    +                clickListener { host.listener?.review(term) }
                     checkChangeListener { _, isChecked ->
                         host.listener?.setChecked(term, isChecked)
                     }
    diff --git a/vector/src/main/java/im/vector/app/features/usercode/UserCodeActivity.kt b/vector/src/main/java/im/vector/app/features/usercode/UserCodeActivity.kt
    index 356893aee2..9e0aa15297 100644
    --- a/vector/src/main/java/im/vector/app/features/usercode/UserCodeActivity.kt
    +++ b/vector/src/main/java/im/vector/app/features/usercode/UserCodeActivity.kt
    @@ -30,7 +30,6 @@ import com.airbnb.mvrx.viewModel
     import com.airbnb.mvrx.withState
     import dagger.hilt.android.AndroidEntryPoint
     import im.vector.app.R
    -import im.vector.app.core.extensions.exhaustive
     import im.vector.app.core.extensions.replaceFragment
     import im.vector.app.core.platform.VectorBaseActivity
     import im.vector.app.core.utils.onPermissionDeniedSnackbar
    @@ -127,7 +126,7 @@ class UserCodeActivity : VectorBaseActivity(),
                         Toast.makeText(this, R.string.qr_code_not_scanned, Toast.LENGTH_SHORT).show()
                         finish()
                     }
    -            }.exhaustive
    +            }
             }
         }
     
    @@ -153,7 +152,7 @@ class UserCodeActivity : VectorBaseActivity(),
                 UserCodeState.Mode.SHOW -> super.onBackPressed()
                 is UserCodeState.Mode.RESULT,
                 UserCodeState.Mode.SCAN -> sharedViewModel.handle(UserCodeActions.SwitchMode(UserCodeState.Mode.SHOW))
    -        }.exhaustive
    +        }
         }
     
         companion object {
    diff --git a/vector/src/main/java/im/vector/app/features/usercode/UserCodeSharedViewModel.kt b/vector/src/main/java/im/vector/app/features/usercode/UserCodeSharedViewModel.kt
    index 64bcf9cead..da894a42be 100644
    --- a/vector/src/main/java/im/vector/app/features/usercode/UserCodeSharedViewModel.kt
    +++ b/vector/src/main/java/im/vector/app/features/usercode/UserCodeSharedViewModel.kt
    @@ -62,12 +62,12 @@ class UserCodeSharedViewModel @AssistedInject constructor(
     
         override fun handle(action: UserCodeActions) {
             when (action) {
    -            UserCodeActions.DismissAction -> _viewEvents.post(UserCodeShareViewEvents.Dismiss)
    -            is UserCodeActions.SwitchMode -> setState { copy(mode = action.mode) }
    -            is UserCodeActions.DecodedQRCode -> handleQrCodeDecoded(action)
    -            is UserCodeActions.StartChattingWithUser -> handleStartChatting(action)
    +            UserCodeActions.DismissAction                 -> _viewEvents.post(UserCodeShareViewEvents.Dismiss)
    +            is UserCodeActions.SwitchMode                 -> setState { copy(mode = action.mode) }
    +            is UserCodeActions.DecodedQRCode              -> handleQrCodeDecoded(action)
    +            is UserCodeActions.StartChattingWithUser      -> handleStartChatting(action)
                 is UserCodeActions.CameraPermissionNotGranted -> _viewEvents.post(UserCodeShareViewEvents.CameraPermissionNotGranted(action.deniedPermanently))
    -            UserCodeActions.ShareByText -> handleShareByText()
    +            UserCodeActions.ShareByText                   -> handleShareByText()
             }
         }
     
    @@ -110,11 +110,11 @@ class UserCodeSharedViewModel @AssistedInject constructor(
             _viewEvents.post(UserCodeShareViewEvents.ShowWaitingScreen)
             viewModelScope.launch(Dispatchers.IO) {
                 when (linkedId) {
    -                is PermalinkData.RoomLink -> {
    +                is PermalinkData.RoomLink            -> {
                         // not yet supported
                         _viewEvents.post(UserCodeShareViewEvents.ToastMessage(stringProvider.getString(R.string.not_implemented)))
                     }
    -                is PermalinkData.UserLink -> {
    +                is PermalinkData.UserLink            -> {
                         val user = tryOrNull { session.resolveUser(linkedId.userId) }
                         // Create raw Uxid in case the user is not searchable
                                 ?: User(linkedId.userId, null, null)
    @@ -125,14 +125,15 @@ class UserCodeSharedViewModel @AssistedInject constructor(
                             )
                         }
                     }
    -                is PermalinkData.GroupLink -> {
    +                is PermalinkData.GroupLink           -> {
                         // not yet supported
                         _viewEvents.post(UserCodeShareViewEvents.ToastMessage(stringProvider.getString(R.string.not_implemented)))
                     }
    -                is PermalinkData.FallbackLink -> {
    +                is PermalinkData.FallbackLink        -> {
                         // not yet supported
                         _viewEvents.post(UserCodeShareViewEvents.ToastMessage(stringProvider.getString(R.string.not_implemented)))
                     }
    +                is PermalinkData.RoomEmailInviteLink -> Unit
                 }
                 _viewEvents.post(UserCodeShareViewEvents.HideWaitingScreen)
             }
    diff --git a/vector/src/main/java/im/vector/app/features/userdirectory/UserListViewModel.kt b/vector/src/main/java/im/vector/app/features/userdirectory/UserListViewModel.kt
    index 61f8bc35f3..039c7041b0 100644
    --- a/vector/src/main/java/im/vector/app/features/userdirectory/UserListViewModel.kt
    +++ b/vector/src/main/java/im/vector/app/features/userdirectory/UserListViewModel.kt
    @@ -26,7 +26,6 @@ import dagger.assisted.AssistedInject
     import im.vector.app.R
     import im.vector.app.core.di.MavericksAssistedViewModelFactory
     import im.vector.app.core.di.hiltMavericksViewModelFactory
    -import im.vector.app.core.extensions.exhaustive
     import im.vector.app.core.extensions.isEmail
     import im.vector.app.core.extensions.toggle
     import im.vector.app.core.platform.VectorViewModel
    @@ -113,7 +112,7 @@ class UserListViewModel @AssistedInject constructor(
                 UserListAction.UserConsentRequest            -> handleUserConsentRequest()
                 is UserListAction.UpdateUserConsent          -> handleISUpdateConsent(action)
                 UserListAction.Resumed                       -> handleResumed()
    -        }.exhaustive
    +        }
         }
     
         private fun handleUserConsentRequest() {
    diff --git a/vector/src/main/java/im/vector/app/features/voice/AudioWaveformView.kt b/vector/src/main/java/im/vector/app/features/voice/AudioWaveformView.kt
    new file mode 100644
    index 0000000000..32f30fe458
    --- /dev/null
    +++ b/vector/src/main/java/im/vector/app/features/voice/AudioWaveformView.kt
    @@ -0,0 +1,201 @@
    +/*
    + * Copyright (c) 2022 New Vector Ltd
    + *
    + * Licensed under the Apache License, Version 2.0 (the "License");
    + * you may not use this file except in compliance with the License.
    + * You may obtain a copy of the License at
    + *
    + *     http://www.apache.org/licenses/LICENSE-2.0
    + *
    + * Unless required by applicable law or agreed to in writing, software
    + * distributed under the License is distributed on an "AS IS" BASIS,
    + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    + * See the License for the specific language governing permissions and
    + * limitations under the License.
    + */
    +
    +package im.vector.app.features.voice
    +
    +import android.content.Context
    +import android.content.res.Resources
    +import android.graphics.Canvas
    +import android.graphics.Paint
    +import android.util.AttributeSet
    +import android.view.View
    +import im.vector.app.R
    +import kotlin.math.max
    +import kotlin.random.Random
    +
    +class AudioWaveformView @JvmOverloads constructor(
    +        context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
    +) : View(context, attrs, defStyleAttr) {
    +
    +    private enum class Alignment(var value: Int) {
    +        CENTER(0),
    +        BOTTOM(1),
    +        TOP(2)
    +    }
    +
    +    private enum class Flow(var value: Int) {
    +        LTR(0),
    +        RTL(1)
    +    }
    +
    +    data class FFT(val value: Float, var color: Int)
    +
    +    private fun Int.dp() = this * Resources.getSystem().displayMetrics.density
    +
    +    // Configuration fields
    +    private var alignment = Alignment.CENTER
    +    private var flow = Flow.LTR
    +    private var verticalPadding = 4.dp()
    +    private var horizontalPadding = 4.dp()
    +    private var barWidth = 2.dp()
    +    private var barSpace = 1.dp()
    +    private var barMinHeight = 1.dp()
    +    private var isBarRounded = true
    +
    +    private val rawFftList = mutableListOf()
    +    private var visibleBarHeights = mutableListOf()
    +
    +    private val barPaint = Paint()
    +
    +    init {
    +        attrs?.let {
    +            context
    +                    .theme
    +                    .obtainStyledAttributes(
    +                            attrs,
    +                            R.styleable.AudioWaveformView,
    +                            0,
    +                            0
    +                    )
    +                    .apply {
    +                        alignment = Alignment.values().find { it.value == getInt(R.styleable.AudioWaveformView_alignment, alignment.value) }!!
    +                        flow = Flow.values().find { it.value == getInt(R.styleable.AudioWaveformView_flow, alignment.value) }!!
    +                        verticalPadding = getDimension(R.styleable.AudioWaveformView_verticalPadding, verticalPadding)
    +                        horizontalPadding = getDimension(R.styleable.AudioWaveformView_horizontalPadding, horizontalPadding)
    +                        barWidth = getDimension(R.styleable.AudioWaveformView_barWidth, barWidth)
    +                        barSpace = getDimension(R.styleable.AudioWaveformView_barSpace, barSpace)
    +                        barMinHeight = getDimension(R.styleable.AudioWaveformView_barMinHeight, barMinHeight)
    +                        isBarRounded = getBoolean(R.styleable.AudioWaveformView_isBarRounded, isBarRounded)
    +                        setWillNotDraw(false)
    +                        barPaint.isAntiAlias = true
    +                    }
    +                    .apply { recycle() }
    +                    .also {
    +                        barPaint.strokeWidth = barWidth
    +                        barPaint.strokeCap = if (isBarRounded) Paint.Cap.ROUND else Paint.Cap.BUTT
    +                    }
    +        }
    +    }
    +
    +    fun initialize(fftList: List) {
    +        handleNewFftList(fftList)
    +        invalidate()
    +    }
    +
    +    fun add(fft: FFT) {
    +        handleNewFftList(listOf(fft))
    +        invalidate()
    +    }
    +
    +    fun summarize() {
    +        if (rawFftList.isEmpty()) return
    +
    +        val maxVisibleBarCount = getMaxVisibleBarCount()
    +        val summarizedFftList = rawFftList.summarize(maxVisibleBarCount)
    +        clear()
    +        handleNewFftList(summarizedFftList)
    +        invalidate()
    +    }
    +
    +    fun updateColors(limitPercentage: Float, colorBefore: Int, colorAfter: Int) {
    +        val size = visibleBarHeights.size
    +        val limitIndex = (size * limitPercentage).toInt()
    +        visibleBarHeights.forEachIndexed { index, fft ->
    +            fft.color = if (index < limitIndex) {
    +                colorBefore
    +            } else {
    +                colorAfter
    +            }
    +        }
    +        invalidate()
    +    }
    +
    +    fun clear() {
    +        rawFftList.clear()
    +        visibleBarHeights.clear()
    +    }
    +
    +    private fun List.summarize(target: Int): List {
    +        flow = Flow.LTR
    +        val result = mutableListOf()
    +        if (size <= target) {
    +            result.addAll(this)
    +            val missingItemCount = target - size
    +            repeat(missingItemCount) {
    +                val index = Random.nextInt(result.size)
    +                result.add(index, result[index])
    +            }
    +        } else {
    +            val step = (size.toDouble() - 1) / (target - 1)
    +            var index = 0.0
    +            while (index < size) {
    +                result.add(get(index.toInt()))
    +                index += step
    +            }
    +        }
    +        return result
    +    }
    +
    +    private fun handleNewFftList(fftList: List) {
    +        val maxVisibleBarCount = getMaxVisibleBarCount()
    +        fftList.forEach { fft ->
    +            rawFftList.add(fft)
    +            val barHeight = max(fft.value / MAX_FFT * (height - verticalPadding * 2), barMinHeight)
    +            visibleBarHeights.add(FFT(barHeight, fft.color))
    +            if (visibleBarHeights.size > maxVisibleBarCount) {
    +                visibleBarHeights = visibleBarHeights.subList(visibleBarHeights.size - maxVisibleBarCount, visibleBarHeights.size)
    +            }
    +        }
    +    }
    +
    +    private fun getMaxVisibleBarCount() = ((width - horizontalPadding * 2) / (barWidth + barSpace)).toInt()
    +
    +    private fun drawBars(canvas: Canvas) {
    +        var currentX = horizontalPadding
    +        val flowableBarHeights = if (flow == Flow.LTR) visibleBarHeights else visibleBarHeights.reversed()
    +
    +        flowableBarHeights.forEach {
    +            barPaint.color = it.color
    +            when (alignment) {
    +                Alignment.BOTTOM -> {
    +                    val startY = height - verticalPadding
    +                    val stopY = startY - it.value
    +                    canvas.drawLine(currentX, startY, currentX, stopY, barPaint)
    +                }
    +                Alignment.CENTER -> {
    +                    val startY = (height - it.value) / 2
    +                    val stopY = startY + it.value
    +                    canvas.drawLine(currentX, startY, currentX, stopY, barPaint)
    +                }
    +                Alignment.TOP    -> {
    +                    val startY = verticalPadding
    +                    val stopY = startY + it.value
    +                    canvas.drawLine(currentX, startY, currentX, stopY, barPaint)
    +                }
    +            }
    +            currentX += barWidth + barSpace
    +        }
    +    }
    +
    +    override fun onDraw(canvas: Canvas) {
    +        super.onDraw(canvas)
    +        drawBars(canvas)
    +    }
    +
    +    companion object {
    +        const val MAX_FFT = 32760
    +    }
    +}
    diff --git a/vector/src/main/java/im/vector/app/features/widgets/WidgetActivity.kt b/vector/src/main/java/im/vector/app/features/widgets/WidgetActivity.kt
    index 963bd9521c..77ec4c5b06 100644
    --- a/vector/src/main/java/im/vector/app/features/widgets/WidgetActivity.kt
    +++ b/vector/src/main/java/im/vector/app/features/widgets/WidgetActivity.kt
    @@ -80,6 +80,7 @@ class WidgetActivity : VectorBaseActivity() {
             viewModel.observeViewEvents {
                 when (it) {
                     is WidgetViewEvents.Close -> handleClose(it)
    +                else                      -> Unit
                 }
             }
     
    diff --git a/vector/src/main/java/im/vector/app/features/widgets/WidgetFragment.kt b/vector/src/main/java/im/vector/app/features/widgets/WidgetFragment.kt
    index 8fa9e07848..dbd63186b6 100644
    --- a/vector/src/main/java/im/vector/app/features/widgets/WidgetFragment.kt
    +++ b/vector/src/main/java/im/vector/app/features/widgets/WidgetFragment.kt
    @@ -29,7 +29,6 @@ import android.view.ViewGroup
     import androidx.core.view.isInvisible
     import androidx.core.view.isVisible
     import com.airbnb.mvrx.Fail
    -import com.airbnb.mvrx.Incomplete
     import com.airbnb.mvrx.Loading
     import com.airbnb.mvrx.Success
     import com.airbnb.mvrx.Uninitialized
    @@ -87,6 +86,7 @@ class WidgetFragment @Inject constructor() :
                     is WidgetViewEvents.OnURLFormatted            -> loadFormattedUrl(it)
                     is WidgetViewEvents.DisplayIntegrationManager -> displayIntegrationManager(it)
                     is WidgetViewEvents.Failure                   -> displayErrorDialog(it.throwable)
    +                is WidgetViewEvents.Close                     -> Unit
                 }
             }
             viewModel.handle(WidgetAction.LoadFormattedUrl)
    @@ -192,13 +192,14 @@ class WidgetFragment @Inject constructor() :
         override fun invalidate() = withState(viewModel) { state ->
             Timber.v("Invalidate state: $state")
             when (state.formattedURL) {
    -            is Incomplete -> {
    +            Uninitialized,
    +            is Loading -> {
                     setStateError(null)
                     views.widgetWebView.isInvisible = true
                     views.widgetProgressBar.isIndeterminate = true
                     views.widgetProgressBar.isVisible = true
                 }
    -            is Success    -> {
    +            is Success -> {
                     setStateError(null)
                     when (state.webviewLoadedUrl) {
                         Uninitialized -> {
    @@ -221,7 +222,7 @@ class WidgetFragment @Inject constructor() :
                         }
                     }
                 }
    -            is Fail       -> {
    +            is Fail    -> {
                     // we need to show Error
                     views.widgetWebView.isInvisible = true
                     views.widgetProgressBar.isVisible = false
    diff --git a/vector/src/main/java/im/vector/app/features/widgets/permissions/RoomWidgetPermissionViewModel.kt b/vector/src/main/java/im/vector/app/features/widgets/permissions/RoomWidgetPermissionViewModel.kt
    index f29e6d1928..78871da324 100644
    --- a/vector/src/main/java/im/vector/app/features/widgets/permissions/RoomWidgetPermissionViewModel.kt
    +++ b/vector/src/main/java/im/vector/app/features/widgets/permissions/RoomWidgetPermissionViewModel.kt
    @@ -93,6 +93,7 @@ class RoomWidgetPermissionViewModel @AssistedInject constructor(@Assisted val in
             when (action) {
                 RoomWidgetPermissionActions.AllowWidget -> handleAllowWidget()
                 RoomWidgetPermissionActions.BlockWidget -> handleRevokeWidget()
    +            RoomWidgetPermissionActions.DoClose     -> Unit
             }
         }
     
    diff --git a/vector/src/main/java/im/vector/app/features/workers/signout/SignoutCheckViewModel.kt b/vector/src/main/java/im/vector/app/features/workers/signout/SignoutCheckViewModel.kt
    index 4daaef6fe1..fbc0b8fcff 100644
    --- a/vector/src/main/java/im/vector/app/features/workers/signout/SignoutCheckViewModel.kt
    +++ b/vector/src/main/java/im/vector/app/features/workers/signout/SignoutCheckViewModel.kt
    @@ -28,7 +28,6 @@ import dagger.assisted.AssistedFactory
     import dagger.assisted.AssistedInject
     import im.vector.app.core.di.MavericksAssistedViewModelFactory
     import im.vector.app.core.di.hiltMavericksViewModelFactory
    -import im.vector.app.core.extensions.exhaustive
     import im.vector.app.core.platform.EmptyViewEvents
     import im.vector.app.core.platform.VectorViewModel
     import im.vector.app.core.platform.VectorViewModelAction
    @@ -124,7 +123,7 @@ class SignoutCheckViewModel @AssistedInject constructor(
                         copy(hasBeenExportedToFile = Success(true))
                     }
                 }
    -        }.exhaustive
    +        }
         }
     
         private fun handleExportKeys(action: Actions.ExportKeys) {
    diff --git a/vector/src/main/res/layout/item_timeline_event_voice_stub.xml b/vector/src/main/res/layout/item_timeline_event_voice_stub.xml
    index a180afbf8e..0fad714bd4 100644
    --- a/vector/src/main/res/layout/item_timeline_event_voice_stub.xml
    +++ b/vector/src/main/res/layout/item_timeline_event_voice_stub.xml
    @@ -40,7 +40,7 @@
                 app:layout_constraintTop_toTopOf="@id/voicePlaybackControlButton"
                 tools:text="0:23" />
     
    -        
     
    -    
    -
         
     
    @@ -59,27 +48,50 @@
             tools:ignore="MissingConstraints"
             tools:visibility="invisible" />
     
    +    
    +
         
    +        app:constraint_referenced_ids="roomToolbarAvatarImageView,roomToolbarAvatarShield,roomToolbarPresenceImageView,roomToolbarPublicImageView" />
    +
    +    
     
         
     
    -            فشلت إزالة الودجة
         فشلت إضافة الودجة
         يستمع للإشعارات
    +    ينتظر…
    +    تفاعلَ بـ: %s
    +    استبيان
    +    ملصق
    +    ملف
    +    تسجيل صوتي
    +    صوت
    +    صورة.
    +    فيديو.
    +    ليس آمن
    +    ينشئ فضاءً…
    +    فشل إرسال الاقتراح (%s)
    +    شكرًا، أُرسل اقتراحك
    +    صِف اقتراحك
    +    اكتب اقتراحك أدناه.
    +    إعدادات النظام
    +    احصل على المساعدة باستخدام ${app_name}
    +    الصوت والفيديو
    +    الأمان والخصوصية
    +    الخيارات
    +    عام
    +    "أُنشئت  الغرفة، لكن فشل إرسال بعض الدعوات بسبب:
    +\n
    +\n%s"
    +    يمكن لأي شخص الإنضمام لهذه الغرفة
    +    علنية
    +    إعدادات الغرفة
    +    الموضوع
    +    موضوع الغرفة (اختياري)
    +    الاسم
    +    اسم الغرفة
    +    أنشئ
    +    رسالة مباشرة
    +    الغرف
    +    لا يمكنك معاينة هذه الغرفة، هل تريد الإنضمام إليها؟
    +    لا يمكنك الوصول إلى هذه الغرفة حاليًا.
    +\nحاول لاحقًا، أو اسأل مدير الغرفة إن كان لديك نفاذ لها.
    +    تتعذر معاية هذه الغرفة
    +    كل المجتمعات
    +    رجاء انتظر…
    +    غيّر الشبكة
    +    أنشئ فضاء جديد
    +    أنشئ غرفة جديدة
    +    حدث حذفه مستخدم
    +    اعرض الرسائل المحذوفة
    +    حُذفت الرسالة
    +    الانفعالات
    +    اعرض الانفعالات
    +    أضف انفعالًا
    +    الانفعالات
     
    \ No newline at end of file
    diff --git a/vector/src/main/res/values-cs/strings.xml b/vector/src/main/res/values-cs/strings.xml
    index cf022172c2..c7e0f96433 100644
    --- a/vector/src/main/res/values-cs/strings.xml
    +++ b/vector/src/main/res/values-cs/strings.xml
    @@ -932,7 +932,7 @@
         Opakovat
         Poslali Vám pozvánku
         %s Vás pozval
    -    Vše jste dohnali!
    +    To je všechno!
         Nemáte již žádné nepřečtené zprávy
         Konverzace
         Tady budou zobrazeny Vaše přímé konverzace. Pro novou zprávu klepněte na + vpravo dole.
    diff --git a/vector/src/main/res/values-de/strings.xml b/vector/src/main/res/values-de/strings.xml
    index 7a8ac9c71b..4c5131d452 100644
    --- a/vector/src/main/res/values-de/strings.xml
    +++ b/vector/src/main/res/values-de/strings.xml
    @@ -1079,7 +1079,7 @@
         Erweitere und individualisiere dein Benutzererlebnis
         Mit %1$s verbinden
         Mit Element Matrix Services verbinden
    -    Mit einem individuellen Server verbinden
    +    Mit einem anderen Server verbinden
         Bei %1$s anmelden
         Registrieren
         Anmelden
    @@ -1257,7 +1257,7 @@
         Raum verlassen
         Verlasse den Raum…
         Administratoren
    -    Moderationen
    +    Moderatoren
         Benutzerdefiniert
         Eingeladen
         Nutzer
    @@ -2380,7 +2380,7 @@
         Organisiere Diskussionen mit Threads
         Threads im Raum filtern
         Möchtest du einem existierenden Server beitreten\?
    -    Communities
    +    Gemeinschaften
         Teams
         Wir helfen dir, in Verbindung zu kommen.
         Mit wem wirst du am meisten chatten\?
    diff --git a/vector/src/main/res/values-el/strings.xml b/vector/src/main/res/values-el/strings.xml
    index fb4956444a..a7da13bdb3 100644
    --- a/vector/src/main/res/values-el/strings.xml
    +++ b/vector/src/main/res/values-el/strings.xml
    @@ -1,7 +1,6 @@
     
     
         Ηλεκτρονική διεύθυνση
    -
         Ο/Η %1$s σας προσκάλεσε
         Ο/Η %1$s αποχώρησε
         Ο/Η %1$s απέρριψε την πρόσκληση
    @@ -9,7 +8,6 @@
         Ο/Η %1$s προσκάλεσε τον/την %2$s
         Η πρόσκληση του/της %s
         Αριθμός τηλεφώνου
    -
         Ο/Η %1$s απέκλεισε τον/την %2$s
         Ο/Η %1$s απέσυρε την πρόσκληση του/της %2$s
         Ο/Η %1$s άλλαξε εικονίδιο χρήστη
    @@ -20,7 +18,6 @@
         Ο/Η %1$s άλλαξε το όνομα του δωματίου σε: %2$s
         Ο/Η %s απάντησε στην κλήση.
         Ο/Η %s τερμάτισε την κλήση.
    -
         Ο/Η %s πραγματοποίησε μια κλήση βίντεο.
         Ο/Η %s πραγματοποίησε μια κλήση ήχου.
         Ο/Η %1$s κατέστησε το μελλοντικό ιστορικό του δωματίου ορατό στον/στην %2$s
    @@ -31,28 +28,15 @@
         Ο/Η %1$s αφαίρεσε το όνομα του δωματίου
         Ο/Η %1$s αφαίρεσε το θέμα του δωματίου
         Ο/Η %1$s δέχτηκε την πρόσκληση για το %2$s
    -
         ** Αδυναμία αποκρυπτογράφησης: %s **
         Η συσκευή του/της αποστολέα δεν μας έχει στείλει τα κλειδιά για αυτό το μήνυμα.
    -
         Αποτυχία αποστολής μηνύματος
    -
    -
         Σφάλμα του Matrix
    -
    -
         Ο/Η %1$s εισήλθε στο δωμάτιο
    -
         Πρόσκληση στο δωμάτιο
    -
         %1$s και %2$s
    -
    -
    -
         Άδειο δωμάτιο
    -
         όλα τα μέλη του δωματίου από την στιγμή που εισήλθαν.
    -
         Ο/Η %1$s έστειλε μία πρόσκληση στον/στην %2$s για να εισέλθει στο δωμάτιο
         Ακύρωση
         Κλείσιμο
    @@ -108,7 +92,6 @@
         Μόνο οι επαφές Matrix
         Δεν βρέθηκαν αποτελέσματα
         Δωμάτια
    -
         Κοινότητες
         Αποστολή καταγραφών σφαλμάτων
         Αναφορά σφάλματος
    @@ -214,8 +197,6 @@
         Σφάλμα εντολής
         Δημιουργία
         Δωμάτια
    -
    -
         Λόγος: %1$s
         Απενεργοποίηση λογαριασμού
         Απενεργοποίηση λογαριασμού
    @@ -295,7 +276,6 @@
             %d πρόσκληση
             %d προσκλήσεις
         
    -
         Σύνδεση
         Λόγος οριστικής αποβολής
         Αποκλεισμός χρήστη
    @@ -353,7 +333,6 @@
         Αποτυχία σύνδεσης σε πραγματικό χρόνο.
     \nΖητήστε από τον διαχειριστή του οικιακού σας διακομιστή να διαμορφώσει έναν διακομιστή TURN ώστε οι κλήσεις να λειτουργούν αξιόπιστα.
         Η Κλήση ${app_name} Aπέτυχε
    -
         Είσαστε σίγουροι ότι θέλετε να κάνετε κλήση βίντεο;
         Είσαστε σίγουροι ότι θέλετε να κάνετε κλήση ήχου;
         Αποστολή φωνής
    @@ -369,7 +348,6 @@
         Κατάργηση δημοσίευσης
         Αντιγραφή
         Τέλος κλήσης
    -
         Ειδοποιήσεις
         Αντιγράφηκε στο πρόχειρο
         Πρόσθεση
    @@ -392,7 +370,6 @@
         %s γράφει…
         %1$s & %2$s γράφουν…
         Δεν εξακριβώθηκε η ταυτότητα του εξωτερικού διακομιστή.
    -
         Κανένα αποτέλεσμα
         Φιλτράρισμα αποβεβλημένων χρηστών
         Φιλτράρισμα μελών δωματίου
    @@ -402,4 +379,9 @@
         Το πιστοποιητικό έχει αλλάξει από αυτό που εμπιστεύθηκε προηγουμένως η συσκευή σας. Αυτό είναι ΑΚΡΩΣ ΑΣΥΝΗΘΙΣΤΟ. Προτείνεται να ΜΗΝ ΑΠΟΔΕΚΤΕΙΤΕ το νεο πιστοποιητικό.
         Προεπιλογή Συστήματος
         Λανθασμένο όνομα χρήστη και/ή κωδικού
    +    Τερματισμός δημοσκόπησης
    +    Επεξεργασία δημοσκόπησης
    +    Άνοιγμα με
    +    Αποστολή αυτοκόλλητου
    +    %1$s δημιούργησε το δωμάτιο
     
    \ No newline at end of file
    diff --git a/vector/src/main/res/values-es/strings.xml b/vector/src/main/res/values-es/strings.xml
    index 8e0568e7ae..0f80bc8e0f 100644
    --- a/vector/src/main/res/values-es/strings.xml
    +++ b/vector/src/main/res/values-es/strings.xml
    @@ -39,7 +39,6 @@
         Invitación a Sala
         %1$s y %2$s
         Sala vacía
    -
         %1$s ha revocado la invitación a unirse a la sala para %2$s
         Sincronización inicial
     \nImportando cuenta…
    @@ -241,7 +240,6 @@
         Eliminar
         Renombrar
         Reportar contenido
    -
         o
         Invitar
         Cerrar sesión
    @@ -264,7 +262,6 @@
         Solo contactos de Matrix
         No hay resultados
         Salas
    -
         Enviar registros
         Enviar registros de fallas
         Enviar captura de pantalla
    @@ -292,11 +289,9 @@
         Esto no parece ser una dirección de correo electrónico válida
         Esta dirección de correo electrónico ya está definida.
         ¿Olvidaste tu contraseña?
    -
         Este Servidor Doméstico quiere asegurarse de que no eres un robot
         Debes ingresar la dirección de correo electrónico vinculada a tu cuenta.
         No se pudo verificar la dirección de correo electrónico: asegúrate de hacer clic en el enlace del correo electrónico
    -
         Por favor introduce una URL válida
         JSON mal formado
         No contenía un JSON válido
    @@ -312,15 +307,10 @@
         Llamada En Curso…
         El lado remoto no contestó.
         Información
    -
    -
         ${app_name} necesita permiso para acceder a tu micrófono para realizar llamadas de voz.
    -
         ${app_name} necesita permiso para acceder a tu cámara y micrófono para realizar llamadas de vídeo.
     \n
     \nPor favor permite el acceso en las próximas ventanas emergentes para poder realizar la llamada.
    -
    -
         
         NO
         Continuar
    @@ -328,7 +318,6 @@
         Unirse
         Rechazar
         Mensajes no leídos.
    -
         Salir de la sala
         ¿Seguro que quieres salir de la sala?
         CONVERSACIONES DIRECTAS
    @@ -355,7 +344,6 @@
         El certificado cambió de uno que era confiable para tu teléfono. Esto es MUY INUSUAL. Se recomienda NO ACEPTAR este nuevo certificado.
         El certificado cambió de uno que era confiable a uno que no es confiable. El servidor puede haber renovado su certificado. Contacta al administrador del servidor para obtener la huella digital.
         Solo acepta el certificado si el administrador del servidor ha publicado una huella digital que coincide con la anterior.
    -
         Buscar
         Filtrar miembros de la sala
         No hay resultados
    @@ -400,7 +388,6 @@
         Actualizar Nombre Público
         Visto por última vez
         %1$s @ %2$s
    -
         Autenticación
         Sesión iniciada como
         Servidor Doméstico
    @@ -431,7 +418,6 @@
         Estas son funcionalidades experimentales que pueden romperse de maneras inesperadas. Utilizar con precaución.
         Establecer como dirección principal
         Dejar de Establecer como dirección principal
    -
         Error de descifrado
         Nombre público
         ID de sesión
    @@ -442,7 +428,6 @@
         Exportar
         Ingresar frase de contraseña
         Confirmar frase de contraseña
    -
         Importar claves de salas con cifrado Extremo-a-Extremo
         Importar claves de sala
         Importar las claves desde un archivo local
    @@ -454,7 +439,6 @@
         Verificar
         Para verificar que esta sesión es confiable, por favor contacta a su dueño por algún otro medio (ej. cara a cara o por teléfono) y pregúntale si la clave que ve en sus Ajustes de Usuario para esta sesión coincide con la clave a continuación:
         Si coincide, presione el botón de verificar a continuación. Si no coincide, entonces alguien está interceptando esta sesión y probablemente debería prohibirlo. En el futuro, este proceso de verificación será más sofisticado.
    -
         Selecciona un directorio de salas
         Nombre del servidor
         Todas las salas en el servidor %s
    @@ -540,8 +524,6 @@
             %d mensaje nuevo
             %d mensajes nuevos
         
    -
    -
         
             %d sala
             %d salas
    @@ -550,12 +532,10 @@
             %d cambio de membresía
             %d cambios de membresía
         
    -
         
             %d mensaje sin leer
             %d mensajes sin leer
         
    -
         %1$s en %2$s
         
             %d componente activo
    @@ -608,16 +588,10 @@
         Haz clic aquí para ver mensajes más antiguos
         Degrada al usuario con la ID dada
         Alertas de Sistema
    -
    -
    -
    -
         
             $d seleccionado
             %d seleccionados
         
    -
    -
         contacta al administrador de tu servicio
         Este servidor doméstico ha excedido uno de sus límites de recursos, por lo que algunos usuarios no podrán iniciar sesión.
         Este servidor doméstico ha excedido uno de sus límites de recursos.
    @@ -741,7 +715,6 @@
         Incluye cambios en el avatar y en el nombre.
         Enviar mensaje con intro
         La tecla Intro enviará el mensaje en vez de añadir un salto de línea
    -
         Contraseña
         La contraseña no es válida
         Media
    @@ -801,7 +774,6 @@
         Guardar clave de recuperación
         Compartir
         Guardar como archivo
    -
         Por favor, haga una copia
         Compartir clave de recuperación con…
         Generando clave de recuperación usando una contraseña, este proceso puede tardar varios segundos.
    @@ -848,7 +820,6 @@
             Cargando %d de las claves…
         
         Firma
    -
         Origen predeterminado de medios
         Configurar copia de seguridad de las claves de cifrado
         Obteniendo una versión de copia de seguridad…
    @@ -857,7 +828,6 @@
         La copia de seguridad tiene una firma inválida de la sesión no verificada %s
         Para usar la copia de seguridad de la clave en esta sesión introduzca su contraseña o su clave de recuperación ahora.
         ¿Deseas borrar tus claves de cifrado guardadas en el servidor\? No podrás usar tu clave de recuperación para leer el historial de mensajes cifrados.
    -
         Reproducir sonido de cámara
         ip desconocida
         Una nueva sesión solicita claves de cifrado.
    @@ -878,8 +848,6 @@
         Comprobando copias de respaldo
         ¡Verificado!
         Ok
    -
    -
         Solicitud de verificación
         %s quiere verificar tu sesión
         Error desconocido
    @@ -933,7 +901,6 @@
         Integraciones
         Descubrimiento
         Gestione sus preferencias de descubrimiento.
    -
         Esta no es una dirección de servidor Matrix válida
         No se puede acceder al servidor en esta URL, por favor, compruébelo
         Modo Sincronización en segundo plano
    @@ -942,7 +909,6 @@
         ${app_name} se sincronizará en segundo plano periódicamente en un momento preciso (configurable).
     \nEsto afectará al uso de la radio y la batería, se mostrará una notificación permanente que indica que ${app_name} está escuchando a nuevos acontecimientos.
         No se le notificará de los mensajes entrantes cuando la aplicación esté en segundo plano.
    -
         Utiliza un Gestor de Integración para gestionar los bots, puentes, widgets y paquetes de pegatinas.
     \nLos Gestores de Integración reciben los datos de configuración y pueden modificar los widgets, enviar invitaciones a salas y establecer niveles de poder en su nombre.
         Permitir integraciones
    @@ -1078,7 +1044,6 @@
         Este contenido fue reportado como inapropiado.
     \n
     \nSi no quieres ver más contenido de este usuario, puedes bloquearlo para ocultar sus mensajes.
    -
         Ignorar usuario
         Todos los mensajes (sonido)
         Todos los mensajes
    @@ -1508,7 +1473,6 @@
     \n- El servidor privado al que está conectado el usuario que estás verificando
     \n- Su conexión a internet o la de otros usuarios
     \n- Su dispositivo o el de otros usuarios
    -
         Los mensajes de esta sala están cifrados Extremo-a-Extremo.
     \n
     \nSus mensajes están protegidos y sólo usted y el destinatario tienen las claves únicas para descifrarlos.
    @@ -1589,7 +1553,6 @@
         Imprímelo y guárdalo en un lugar seguro
         Guárdelo en una llave USB o unidad de respaldo
         Cópielo en su almacenamiento personal en la nube
    -
         Si cancela ahora, puede perder mensajes y datos cifrados si pierde el acceso a sus inicios de sesión.
     \n
     \nTambién puede configurar la Copia de Seguridad Segura y administrar sus claves en Configuración.
    @@ -1937,7 +1900,6 @@
         Transferir
         Conectar
         Preguntar primero
    -
         Llamada activa (%1$s)
         Pad de marcado
         Esta llamada ha terminado
    @@ -2062,7 +2024,6 @@
         Para llevar a cabo esta acción has de otorgar el permiso de Cámara en las preferencias del sistema.
         Se requieren permisos para llevar a cabo esta acción. Por favor, otórgalos desde las preferencias del sistema.
         Impedir a cualquiera que no forme parte de %s unirse a este sala
    -
         Dar consentimiento
         Revocar consentimiento
         Has dado tu consentimiento para enviar emails y números de teléfono a este servidor identidad para descubrir a otros usuarios desde tus contactos.
    @@ -2087,7 +2048,6 @@
         Actualización requerida
         Actualizar
         Por favor, se paciente. Ésto puede llevar algo de tiempo.
    -
         Sala sin nombre
         Por favor, contacta con el administrador de tu homeserver para más información
         Parece que tu homeserver no soporta Espacios todavía
    @@ -2143,7 +2103,6 @@
         Transferir a %1$s
         Consultando con %1$s
         Ocurrió un error al transferir la llamada
    -
         Hubo un error al buscar el número de teléfono
         Devolver la llamada
         Buscar contactos en Matrix
    @@ -2312,4 +2271,37 @@
         Ver en la sala
         Habilitar
         No estás autorizado a unirte a esta sala
    +    ¿No lo sabes todavía\? Puedes %s
    +    saltar esta pregunta
    +    Comunidades
    +    Equipos
    +    Familia y amigos
    +    Te vamos a ayudar a conectarte.
    +    ¿Con quién hablarás más\?
    +    Mensajería para tu equipo.
    +    Mensajería segura.
    +    Tú mandas.
    +    Toma el control de tus conversaciones.
    +    Ubicación
    +    Encuesta
    +    ¿Aceptas enviar esta información\?
    +    Ajustes del sistema
    +    Versiones
    +    Obtén ayuda sobre cómo usar ${app_name}
    +    Ayuda
    +    Ayuda
    +    Asuntos legales
    +    ¡Ya estás viendo este hilo!
    +    Ver en la sala
    +    Responder en un hilo
    +    El comando «%s» existe, pero no funciona dentro de hilos.
    +    Este servidor no ha devuelto ninguna política.
    +    Bibliotecas de terceros
    +    Política de tu servidor de identidad
    +    Política de tu servidor base
    +    Política de ${app_name}
    +    %1$s y %2$s
    +    Filtrar
    +    %1$s, %2$s y otros
    +    Copiar enlace al hilo
     
    \ No newline at end of file
    diff --git a/vector/src/main/res/values-fa/strings.xml b/vector/src/main/res/values-fa/strings.xml
    index eb8a29b6b2..a3ee61013e 100644
    --- a/vector/src/main/res/values-fa/strings.xml
    +++ b/vector/src/main/res/values-fa/strings.xml
    @@ -40,7 +40,6 @@
         شماره تلفن
         دعوت اتاق
         %1$s و %2$s
    -
         اتاق خالی
         همگام‌سازی نخستین:
     \nدرون‌ریزی حساب…
    @@ -221,7 +220,6 @@
         اولویت پایین
         گفتگوها
         اتاق‌ها
    -
         گزارش اشکال
         پیوستن به اتاق
         نام کاربری
    @@ -296,16 +294,11 @@
             %d عضو
             %d عضو
         
    -
    -
    -
    -
         ترک اتاق
         آیا از ترک این اتاق اطمینان دارید؟
         گپ‌های مستقیم
         دعوت
         بارگیری
    -
         رد شدن
         انجام شد
         نادیده‌گرفتن
    @@ -327,7 +320,6 @@
         در صورت عدم پشتیبان‌گیری از کلیدهای خود پیش از خروج، دسترسی شما به پیام‌های رمزنگاری شده از بین می‌رود.
         پیوند دائمی
         مشاهده منبع رمزگشایی شده
    -
         این کارساز خانگی می‌خواهد مطمئن شود که روبات نیستید
         بازدرخواست کلیدهای رمزنگاری از دیگر نشست‌هایتان.
         لطفاً المنت را روی افزاره‌ای دیگر که می‌تواند پیام را رمزگشایی کند، اجرا کنید تا بتواند کلیدها را به این نشست بفرستد.
    @@ -335,8 +327,6 @@
         قطع اتصال
         نپذیرفتن
         پالایش اعضای اتاق
    -
    -
         عکس نمایه
         نام نمایشی
         افزودن نشانی رایانامه
    @@ -419,7 +409,6 @@
         افزودن کاره‌های ماتریکس
         پیام رمزنگاری شده
         اتاق‌ها
    -
         (پیش‌رفته)
         (پیش‌رفته) برپایی با کلید بازیابی
         گرفتن نگارش پشتیبان…
    @@ -518,7 +507,6 @@
         ابطال
         باید نشانی رایانامهٔ پیوسته به حسابتان وارد شود.
         شکست در تأیید نشانی رایانامه: مطمئن شوید که پیوند درون رایانامه را کلیک کرده‌اید
    -
         لطفاً سیاست‌های این کارساز خانگی را بررسی کرده و بپذیرید:
         لطفا یک نشانی معتبر وارد کنید
         این یک نشانی کارساز ماتریکس معتبر نیست
    @@ -531,7 +519,6 @@
         گزینش صدای زنگ برای تماس‌ها:
         اطلاعات
         پرش به ناخوانده
    -
         تحریم
         رفع انسداد
         اخراج
    @@ -722,7 +709,6 @@
             %d پیام آگاهی نخوانده
             %d پیام آگاهی نخوانده
         
    -
         
             %1$s: %2$d پیام
             %1$s: %2$d پیام
    @@ -796,7 +782,6 @@
         ایجاد
         خانه
         دعوت شد
    -
         غیرفعّال‌سازی حساب
         غیرفعّال‌سازی حساب
         هرگز پیام‌های رمزشده را از دست ندهید
    @@ -1084,7 +1069,6 @@
         گرفتم
         اطّلاعات بیش‌تر
         خطای رمزگشایی
    -
         بازنشاندن از نشانی اصلی
         ارتباط با مدیر خدمتتان
         اکنون بازبینی شود
    @@ -1096,7 +1080,6 @@
         رمزنگاری این اتاق پشتیبانی نمی‌شود
         لطفاً نام کاربری‌ای وارد کنید.
         برای دیدن پیام‌های قدیمی‌تر، کلیک کنید
    -
         پیام‌های این‌جا، رمزنگاری سرتاسری شده‌اند. 
     \n 
     \nپیام‌هایتان با قفل‌هایی امن شده‌اند و فقط شما و گیرندگان دیگر، کلیدهای یکتا را برای قفل‌گشاییشان دارید.
    @@ -1215,7 +1198,6 @@
         دعوت‌ها، برداشتن‌ها و انسدادها تأثیر نمی‌پذیرند.
         نمایش پیام‌های پیوستن و ترک اتاق
         پیش‌نمایشی از آدرس‌های URL در پیام‌ها نمایش داده شود.
    -
         المنت بصورت دوره‌ای و در بازه‌های قابل تنظیم در پس زمینه همگام‌سازی می شود.
     \nاین بر مصرف باتری شما تأثیر می‌گذارد، یک اعلان دائمی نمایش داده می‌شود که المنت برای رویدادها گوش می‌دهد.
         المنت در پس زمینه همگام‌سازی می‌کند به گونه ای که منابع محدود دستگاه (باتری) حفظ می‌شود.
    @@ -1264,7 +1246,6 @@
         هیچ شماره تلفنی به حسابتان افزوده نشده
         نتیجه‌ای در پی نداشت
         فیلترکردن کاربران مسدود شده
    -
         
             %d مورد
             %d مورد
    @@ -1284,19 +1265,13 @@
         تنزل نقش شما در اتاق؟
         شما نمی‌توانید این تغییر را بازگردانید. زیرا در حال ارتقای سطح کاربر دیگر به سطح خودتان هستید.
     \nآیا مطمئن هستید؟
    -
    -
         المنت برای برقراری تماس تصویری نیازمند دسترسی به میکروفون و دوربین است.
     \n
     \nلطفا در پنجره های بعدی دسترسی های لازم را بدهید.
    -
         المنت برای برقراری تماس صوتی نیازمند دسترسی به میکروفون است.
    -
    -
         خطای SSL: هویت طرف مقابل تأیید نشد.
         شکست در برقراری ارتباط همزمان.
     \nلطاً از مدیر کارساز بخواهید برای برقراری مطمئن تماس‌ها، کارساز turn را پیکربندی کند.
    -
         بعد از راه‌اندازی مجدد، هیچ تاریخچه، پیام، دستگاه تائید شده یا کاربر تائید شده‌ای در حساب شما وجود نخواهد داشت
         اگر همه چیز را بازنشانی کنید
         تنها در صورتی این کار را انجام دهید که از هیچ دستگاه دیگری نمی‌توانید این دستگاه را تائید نمائید.
    @@ -1319,7 +1294,6 @@
         اگر اکنون لغو کنید، ممکن است در صورت قطع دسترسی به ورودهایتان، داده‌ها و پیام‌های رمزنگاشته را از دست بدهد.
     \n
     \nهمچنین می‌توانید در تنظیمات، پشتیبان امن برپا کرده و کلیدهایتان را مدیریت کنید.
    -
         آن را در فضای ابری خود کپی کنید
         آن را روی فلش یا حافظه‌ای دیگر ذخیره کنید
         آن را چاپ کرده و در محلی امن و مطمئن نگهداری کنید
    @@ -1478,7 +1452,6 @@
         ارسال به عنوان پیام تباه‌کننده
         حذف‌کردن از اولویت پایین
         اضافه‌کردن به اولویت پایین
    -
         این محتوا به عنوان محتوای نامناسب گزارش شده‌است.
     \n
     \nاگر نمی‌خواهید محتوای بیشتری از این کاربر مشاهده کنید ، می توانید او را نادیده بگیرید تا پیام‌های او را مشاهده نکنید.
    @@ -1553,11 +1526,8 @@
         خطای نامشخص
         %s می‌خواهد نشستتان را تأیید کند
         درخواست تأیید
    -
    -
         فهمیدم
         تأییدشده!
    -
         امضاء
         الگوریتم
         
    @@ -1568,7 +1538,6 @@
         پشتیبان‌گیری از کلیدهای شما. این ممکن است چند دقیقه طول بکشد…
         مدیریت در بخش پشتیبان‌گیری از کلید
         کلیدهای رمزگذاری جدید
    -
         کلیدهای رمزگذاری پشتیبان شما از سرور حذف شوند؟ در اینن صورت دیگر نخواهید توانست از کلید بازیابی خود برای خواندن پیام رمزشده‌ی قبلی خود استفاده کنید.
         حذف نسخه‌ی پشتیبان
         در حال بررسی وضعیت نسخه‌ی پشتیبان
    @@ -1609,7 +1578,6 @@
         به نظر می‌رسد شما در یک نشست دیگر کلید پشتیبان تهیه کرده‌اید. آیا می‌خواهید آن را با موردی که ایجاد می‌کنید جایگزین کنید؟
         از پیش، پشتیبانی روی کارساز خانگیتان وجود دارد
         کلید بازیابی ذخیره شد.
    -
         کلید بازیابی خود را در جایی بسیار امن نظیر برنامه‌های شناخته‌شده‌ی مدیریت گذرواژه نگه دارید
         کلید بازیابی شما برای روز مبادا است - اگر کلید امنیتی خود را فراموش کنید می توانید از آن برای بازیابی دسترسی به پیام‌های رمزگذاری شده استفاده کنید.
     \nکلید بازیابی خود را در جایی بسیار امن نظیر برنامه‌های شناخته‌شده‌ی مدیریت گذرواژه نگه دارید
    @@ -1686,13 +1654,11 @@
             %d دعوت
         
         نام کارساز
    -
         
             %1$d از %2$d کلید با موفقیت بارگذاری شد.
             %1$d از %2$d کلید با موفقیت بارگذاری شدند.
         
         کلیدها با موفقیت بر روی دستگاه استخراج شدند
    -
         لطفاً یک رمز برای رمزنگاری کلیدها وارد کنید. ورود این رمز برای بارگذاری کلیدها ضروری خواهد بود.
         اجتماع
         پخش صدای شاتر
    @@ -1707,13 +1673,11 @@
         لطفاً ایمیل خود را بررسی کنید و روی لینک ارسال شده، کلیک کنید. پس از انجام این کار، روی ادامه کلیک کنید.
         برای انجام این کار اجازه‌ی یکپارچه‌سازی را در تنظیمات فعال کنید.
         یکپارچه‌سازی‌ها غیر فعال هستند
    -
         %1$s @ %2$s
         آخرین اتصال
         نام عمومی
         به‌روزرسانی نام عمومی
         شناسه
    -
         المنت اطلاعاتی را جمع آوری می کند و با ارسال آنان به صورت ناشناس به ما امکان بهبود برنامه را می‌دهد.
         ارسال داده های تجزیه و تحلیل
         تجزیه و تحلیل
    @@ -1786,7 +1750,6 @@
         آغاز به گپ
         برون‌ریزی بازرسی
         اگر اتاق فقط برای تعامل با افراد داخل سرور خانه شما می‌باشد، این قابلیت را فعال کنید. این تنظیم را بعدا نمی‌توانید تغییر دهید.
    -
         یک کلید امنیتی ایجاد کنید تا در مکانی امن مانند سامانه مدیریت رمز عبور یا گاوصندوق آن را ذخیره کنید.
         ارتباطی با این شناسه وجود ندارد.
         هویت خود را تأیید کنید تا به پیام‌های رمز شده دسترسی پیدا کنید.
    @@ -1872,8 +1835,6 @@
         هنگام انتقال تماس خطایی روی داد
         انتقال
         متصل شوید
    -
    -
         تماس فعال (%1$s)
         هنگام جستجوی شماره تلفن خطایی روی داد
         ‬پد شماره گیری
    @@ -2039,7 +2000,7 @@
         من و همگروهی‌هایم
         یک فضای خصوصی برای نظم بخشی به اتاق‌هایتان
         فقط من
    -    مطمئن شوید که افراد درست به %s دسترسی دارند. می‌توانید بعدها این را تغییر دهید.
    +    مطمئن شوید که افراد درست به %s دسترسی دارند.
         ساخت یک فضا
         هر کسی در فضای این اتاق، می‌تواند اتاق را یافته و بپیوندد. فقط مدیران این اتاق می توانند به فضایی بیفزایندش.
         فقط اعضای فضا
    @@ -2123,7 +2084,6 @@
         ارتقا
         لطفاً شکیبا باشید. ممکن است کمی زمان ببرد.
         پیوستن به اتاق جایگزینی
    -
         ناپایدار
         پایدار
         نگارش پیش‌گزیده
    @@ -2313,7 +2273,6 @@
         پرسش یا موضوع نظرسنجی
         ایجاد نظرسنجی
         نظرسنجی
    -
         فرستادن رایانامه‌ّا و شماره‌های تلفن به %s
         آشنایانتان خصوصی هستند. برای کشف کاربران از آشنایانتان، نیاز به اجازه‌تان برای فرستادن اطّلاعات آشنا به کارساز هویتتان داریم.
         نشست خارج شده است!
    @@ -2446,4 +2405,18 @@
         رونوشت از پیوند به رشته
         دیدن در اتاق
         دیدن رشته‌ها
    +    
    +        %d تغییر سطح کنترل دسترسی
    +        %d تغییر سطح کنترل دسترسی
    +    
    +    آگاهی اتاق
    +    کاربران
    +    آگاهی به تمام اتاق
    +    
    +        %1$d بیش‌تر
    +        %1$d بیش‌تر
    +    
    +    نمایش کم‌تر
    +    %1$s، %2$s و دیگران
    +    %1$s و %2$s
     
    \ No newline at end of file
    diff --git a/vector/src/main/res/values-fi/strings.xml b/vector/src/main/res/values-fi/strings.xml
    index 7e22ce42a8..c5e67771dd 100644
    --- a/vector/src/main/res/values-fi/strings.xml
    +++ b/vector/src/main/res/values-fi/strings.xml
    @@ -46,7 +46,8 @@
         Alkusynkronointi:
     \nTuodaan huoneita
         Alkusynkronointi:
    -\nTuodaan liityttyjä huoneita
    +\nLadataan keskustelujasi
    +\nMikäli olet liittynyt moniin huoneisiin, tässä voi mennä tovi
         Alkusynkronointi:
     \nTuodaan kutsuttuja huoneita
         Alkusynkronointi:
    @@ -365,7 +366,7 @@
         Kokeelliset
         Nämä ovat kokeellisia ominaisuuksia, jotka voivat mennä rikki. Käytä varoen.
         Aseta pääosoitteeksi
    -    Poista pääosoite
    +    Kumoa pääosoitteeksi asettaminen
         Salauksenpurkuvirhe
         Julkinen nimi
         Istunnon tunnus
    @@ -520,7 +521,7 @@
             yksi jäsen
             %d jäsentä
         
    -    Poista huoneesta
    +    Poista keskustelusta
         
             yksi uusi viesti
             %d uutta viestiä
    @@ -628,7 +629,7 @@
         Määritä käyttäjän oikeuksien taso
         Poistaa käyttäjän operaattorioikeudet
         Kutsuu käyttäjän nykyiseen huoneeseen
    -    Liittyy annettuun huoneeseen
    +    Liittyy osoitteen mukaiseen huoneeseen
         Poistu huoneesta
         Aseta huoneen aihe
         Potkaisee käyttäjän pois
    @@ -646,7 +647,7 @@
     \nViestien näkyvyys Matrixissa on samantapainen kuin sähköpostissa. Viestiesi unohtaminen tarkoittaa, että lähettämiäsi viestejä ei näytetä uusille tai rekisteröitymättömille käyttäjille. Ne rekisteröityneet käyttäjät, joilla viestisi jo on, pääsevät kuitenkin näkemään oman kopionsa niistä jatkossakin.
         Unohda kaikki viestit, jotka olen lähettänyt, kun tilini on poistettu (Varoitus: tästä seuraa, että tulevat käyttäjät näkevät vanhat keskustelut epätäydellisinä)
         Syötä käyttäjätunnus.
    -    Tämä huone on korvattu toisella huoneella
    +    Tämä huone on korvattu toisella eikä ole enää aktiivinen.
         Keskustelu jatkuu täällä
         Tämä huone on jatkoa toiselle keskustelulle
         Paina tästä nähdäksesi vanhemmat viestit
    @@ -801,9 +802,9 @@
         %s kutsui
         Sinulla ei ole enempää lukemattomia viestejä
         Keskustelut
    -    Yksityisviestisi näytetään tässä. Napsauta + oikeasta alakulmasta aloittaaksesi.
    +    Yksityisviestisi näytetään tässä. Napauta + oikeasta alakulmasta aloittaaksesi keskustelun.
         Huoneet
    -    Huoneesi näytetään tässä. Napsauta + oikeasta alakulmasta aloittaaksesi.
    +    Huoneesi näytetään tässä. Napauta + oikeasta alakulmasta löytääksesi olemassa olevia tai perustaaksesi omiasi.
         Reaktiot
         Samaa mieltä
         Lisää reaktio
    @@ -1405,7 +1406,7 @@
         Viestieditori
         Muut kielet
         Näytä merkki poistettujen viestien paikalla
    -    Käytä /snow kometoa tai lähetä viesti jossa on ❄️ tai 🎉
    +    Käytä /confetti-komentoa tai lähetä viesti jossa on ❄️ tai 🎉
         Näytä keskustelujen tehosteet
         Näytä poistetut viestit
         Jos poistat käyttäjän porttikiellon, hän voi liittyä huoneeseen uudelleen.
    @@ -1507,7 +1508,7 @@
         Ensimmäinen synkronointi:
     \nLadataan tietoja…
         Ensimmäinen synkronointi:
    -\nOdotetaan palvelimen vastausta. . .
    +\nOdotetaan palvelimen vastausta…
         
             %1$s, %2$s, %3$s ja %4$d muu
             %1$s, %2$s, %3$s ja %4$d muuta
    @@ -1983,4 +1984,73 @@
         Yhdistä palvelimeen
         Minulla on jo tili
         Luo tili
    +    ${app_name} ei voinut käyttää sijaintiasi. Yritä myöhemmin uudelleen.
    +    ${app_name} ei voinut käyttää sijaintiasi
    +    Haluatko varmasti poistaa tämän kyselyn\? Et voi palauttaa sitä poistamisen jälkeen.
    +    Huomaa: sovellus käynnistetään uudelleen
    +    Tapahtuman sisältö
    +    Tapahtuman sisältö
    +    Lähetä mukautettu tapahtuma
    +    Oletusluottamustaso
    +    Huonetta, johon olet saanut porttikiellon ei voi avata.
    +    Noudetaan yhteystietojasi…
    +    Lähettää viestin lumisateen kera
    +    Lähettää viestin konfetin kera
    +    ${app_name} iOS
    +\n${app_name} Android
    +    Lähettää viestin pelkkänä tekstinä, tulkitsematta sitä markdowniksi
    +    Ota yhteyttä ylläpitäjään salauksen palauttamiseksi kelvolliseen tilaan.
    +    Lue koodi toisella laitteellasi tai vaihda ja lue tällä laitteella
    +    Ei-luotettu kirjautuminen
    +    Teit tästä kutsua edellyttävän.
    +    Sijainti
    +    Kysely
    +    
    +        %1$s, %2$s ja yksi muu lukivat
    +        %1$s, %2$s ja %3$d muuta lukivat
    +    
    +    Hyväksytkö näiden tietojen lähettämisen\?
    +    Versiot
    +    Ohje ja tuki
    +    Ohje
    +    Huone on luotu, mutta joitakin kutsuja ei ole lähetetty seuraavasta syystä:
    +\n
    +\n%s
    +    Tähän huoneeseen ei pääse tällä hetkellä.
    +\nYritä myöhemmin uudelleen tai kysy huoneen ylläpitäjältä onko sinulla pääsyä.
    +    Näytä huoneessa
    +    Tuntematon pääsyasetus (%s)
    +    Aseta osoitteita tälle huoneelle, jotta käyttäjät voivat löytää tämän huoneen kotipalvelimesi (%1$s) kautta
    +    Uusi julkaistu osoite (esim. #alias:palvelin)
    +    Kuka hyvänsä millä hyvänsä palvelimella voi käyttää julkaistua osoitetta huoneeseesi liittymiseen. Osoitteen julkaisemiseksi se täytyy ensin asettaa paikalliseksi osoitteeksi.
    +    Kolmansien osapuolten kirjastot
    +    Voit poistaa tämän käytöstä koska tahansa asetuksista
    +    Emme jaa tietoa kolmansien tahojen kanssa
    +    Emme tallenna tai profiloi mitään tilin tietoja
    +    Huoneesta on poistuttu!
    +    Sinulla ei ole lupaa päivittää rooleja, jotka vaaditaan huoneen eri osien muuttamiseen
    +    Valitse roolit, jotka vaaditaan huoneen eri osien muuttamiseen
    +    Tarkastele ja päivitä rooleja, jotka vaaditaan huoneen eri osien muuttamiseen.
    +    %1$s, %2$s ja muita
    +    %1$s ja %2$s
    +    Kotipalvelinta URL-osoitteesta %s ei tavoiteta. Tarkista linkki tai valitse kotipalvelin manuaalisesti.
    +    Näytä huoneessa
    +    Ota käyttöön
    +    
    +        Poistit tältä huoneelta vaihtoehtoisen osoitteen %1$s.
    +        Poistit tältä huoneelta vaihtoehtoiset osoitteet %1$s.
    +    
    +    
    +        %1$s poisti tältä huoneelta vaihtoehtoisen osoitteen %2$s.
    +        %1$s poisti tältä huoneelta vaihtoehtoiset osoitteet %2$s.
    +    
    +    
    +        Lisäsit tälle huoneelle vaihtoehtoisen osoitteen %1$s.
    +        Lisäsit tälle huoneelle vaihtoehtoiset osoitteet %1$s.
    +    
    +    
    +        %1$s lisäsi tälle huoneelle vaihtoehtoisen osoitteen %2$s.
    +        %1$s lisäsi tälle huoneelle vaihtoehtoiset osoitteet %2$s.
    +    
    +    Sinulla ei ole lupaa liittyä tähän huoneeseen
     
    \ No newline at end of file
    diff --git a/vector/src/main/res/values-fr-rCA/strings.xml b/vector/src/main/res/values-fr-rCA/strings.xml
    index cf2ef1a13f..c54c7c6222 100644
    --- a/vector/src/main/res/values-fr-rCA/strings.xml
    +++ b/vector/src/main/res/values-fr-rCA/strings.xml
    @@ -150,7 +150,6 @@
     \nVos messages sont sécurisés avec des verrous et seuls vous et le destinataire avez les clés uniques pour les déverrouiller.
         Les messages ici ne sont pas chiffrés de bout en bout.
         Les messages dans ce salon ne sont pas chiffrés de bout en bout.
    -
         Nous attendons %s…
         %s a été vérifié
         Vérifier %s
    @@ -396,7 +395,6 @@
     \nLes gestionnaires d’intégrations reçoivent des données de configuration et peuvent modifier des gadgets logiciels, envoyer des invitations de salon et définir des rangs à votre place.
         Taille maximum pour des téléversements sur ce serveur
         Téléversements
    -
         Mot de passe oublié\?
         Vous n’avez aucun jeu d\'autocollants activé pour le moment.
     \n
    @@ -422,8 +420,6 @@
         Impossible de valider le NIP, veillez en composer un nouveau.
         Confirmez le NIP
         Choisissez un NIP par sécurité
    -
    -
         
             %d entrée
             %d entrées
    @@ -476,8 +472,6 @@
             Sauvegarde restaurée avec %d clé.
             Sauvegarde restaurée avec %d clés.
         
    -
    -
         
             %d gadget logiciel actif
             %d gadgets logiciels actifs
    @@ -498,7 +492,6 @@
             %d salon
             %d salons
         
    -
         
             %d message notifié non lu
             %d messages notifiés non lu
    @@ -515,8 +508,6 @@
             %d seconde
             %d secondes
         
    -
    -
         Contenu de l’évènement
         Contenu d’évènement
         Envoyer des évènements d’état personnalisés
    @@ -532,21 +523,15 @@
             %d nouveau message
             %d nouveaux messages
         
    -
    -
    -
    -
         
             %d membre
             %d membres
         
         Voulez-vous vraiment quitter le salon\?
    -
         
             %d changement de statut
             %d changements de statut
         
    -
         
             Vous avez supprimé l’adresse alternative %1$s de ce salon.
             Vous avez supprimé les adresses alternatives %1$s de ce salon.
    @@ -582,11 +567,9 @@
     \nDésactiver votre compte ne nous fait pas oublier les messages que vous avez envoyés par défaut. Si vous souhaitez que nous oubliions vos messages, cochez la case ci-dessous.
     \n
     \nLa visibilité des messages dans Matrix est identique à celle des courriels. Si nous oublions vos messages, cela signifie que les messages que vous avez envoyés ne seront plus partagés avec les nouveaux utilisateurs ou les utilisateurs non enregistrés, mais les utilisateurs enregistrés qui ont déjà accès à ces messages en conserveront leur copie.
    -
         Impossible de vérifier l’adresse courriel : assurez-vous d’avoir cliqué sur le lien dans l’courriel
         Aucune adresse courriel n’a été ajoutée à votre compte
         Cette adresse courriel est déjà utilisée.
    -
         L’adresse courriel liée à votre compte doit être saisie.
         Vous n’avez pas accès à ce message
         Définir l’avatar
    @@ -739,7 +722,6 @@
         Si vous annulez maintenant, vous pourrez perdre les messages et données chiffrés si vous perdez accès à vos identifiants.
     \n
     \nVous pouvez aussi activer la sauvegarde sécurisée et gérer vos clés dans les paramètres.
    -
         Copiez-le sur votre stockage dans le cloud personnel
         Sauvegardez-le sur une clé USB ou un disque de sauvegarde
         Imprimez-le et conservez-le en lieu sûr
    @@ -765,8 +747,6 @@
         Phrase de récupération
         %s veut vérifier votre session
         Demande de vérification
    -
    -
         Compris
         Vérifié !
         Nouvelle invitation
    @@ -780,7 +760,6 @@
         Tous les salons sur le serveur %s
         URL du serveur d’accueil
         Sélectionner un répertoire de salons
    -
         Si elles ne correspondent pas, la sécurité de votre communication est peut-être compromise.
         Confirmez en comparant les informations suivantes avec les paramètres utilisateur dans votre autre session :
         Vérifier
    @@ -796,7 +775,6 @@
         Gérer la sauvegarde de clés
         Récupération des messages chiffrés
         Les clés ont bien été exportées
    -
         Veuillez créer une phrase secrète pour chiffrer les clés exportées. Vous devrez saisir cette même phrase secrète afin de pouvoir importer les clés.
         Exporter
         Exporter les clés vers un fichier local
    @@ -807,7 +785,6 @@
         Nom public
         Erreur de déchiffrement
         Thème
    -
         Désactiver comme adresse principale
         Définir comme adresse principale
         Ce sont des fonctionnalités expérimentales qui peuvent se comporter de façon inattendue. À utiliser avec précaution.
    @@ -1014,7 +991,6 @@
         Tous les messages
         Tous les messages (sonore)
         Bloquer l’utilisateur
    -
         Ce contenu a été signalé comme inapproprié.
     \n
     \nSi vous ne voulez plus voir de contenu de cet utilisateur, vous pouvez l’ignorer pour masquer ses messages.
    @@ -1045,7 +1021,6 @@
         Serveur d’accueil
         Connecté en tant que
         Authentification
    -
         %1$s @ %2$s
         Vu la dernière fois
         Mettre à jour le nom public
    @@ -1139,7 +1114,6 @@
         Filtrer les utilisateurs exclus
         Filtrer les membres du salon
         Rechercher
    -
         Changer le sujet
         Mettre à niveau le salon
         Changer les permissions
    @@ -1188,7 +1162,6 @@
         Nous vous avons envoyé un courriel de confirmation à %s, consultez vos courriels et cliquez sur le lien de confirmation
         Envoyer des courriels et des numéros de téléphone
         Vous avez donné votre autorisation pour envoyer des courriels et des numéros de téléphone à ce serveur d’identité pour découvrir d\'autres utilisateurs à partir de vos contacts.
    -
         Vous partagez actuellement des adresse courriels et des numéros de téléphone sur le serveur d’identité %1$s. Vous devrez vous reconnecter à %2$s pour arrêter de les partager.
         Acceptez les conditions de service du serveur d’identité (%s) pour vous permettre d’être découvrable avec une adresse courriel ou un numéro de téléphone.
         Un courriel de vérification sera envoyé à votre adresse pour confirmer la configuration de votre nouveau mot de passe.
    @@ -1221,7 +1194,6 @@
             %1$s a ajouté %2$s comme adresse pour ce salon.
             %1$s a ajouté %2$s comme adresses pour ce salon.
         
    -
         
             %1$s, %2$s, %3$s et %4$d autre
             %1$s, %2$s, %3$s et %4$d autres
    @@ -1289,7 +1261,6 @@
         Partager par SMS
         Impossible de trouver ce salon. Assurez-vous qu’il existe.
         Impossible d’ouvrir un salon dont vous êtes banni.
    -
         Signature
         Algorithme
         Version
    @@ -1298,7 +1269,6 @@
         On dirait que vous avez déjà configuré une sauvegarde de clé depuis une autre session. Voulez-vous la remplacer par celle que vous êtes en train de créer \?
         Une sauvegarde est déjà disponible sur votre serveur d’accueil
         La clé de récupération a été enregistrée.
    -
         Enregistrer dans un fichier
         Partager
         Sauvegarder la clé de récupération
    @@ -1479,7 +1449,6 @@
         Impossible d\'établir une connexion en temps réel.
     \nVeuillez demander à l’administrateur de votre serveur d’accueil de configurer un serveur TURN afin que les appels fonctionnent de manière fiable.
         Appel échoué
    -
         Envoyer un message vocal
         Nouvel appel vidéo
         Nouvel appel audio
    @@ -1554,7 +1523,6 @@
         Vous n\'avez pas la permission de lancer un appel dans ce salon
         Vous n’avez pas la permission de lancer une téléconférence
         Vous n’avez pas la permission de lancer une téléconférence dans ce salon
    -
         Commencer une conversation
         Réinitialiser
         Ignorer
    @@ -1588,7 +1556,6 @@
         Ne perdez jamais vos messages chiffrés
         Sécurité contre la perte d’accès aux messages et données chiffrées
         Sauvegarde sécurisée
    -
         Supprimer les clés de chiffrement sauvegardées sur le serveur \? Vous ne pourrez plus utiliser votre clé de récupération pour lire l’historique des messages chiffrés.
         Supprimer la sauvegarde
         Vérification de l’état de la sauvegarde
    @@ -1631,7 +1598,6 @@
         Veuillez en faire une copie
         Arrêter
         Remplacer
    -
         Gérer vos paramètres de découverte.
         Découverte
         Désactiver mon compte
    @@ -1689,7 +1655,6 @@
         Version de olm
         Version
         Délai entre chaque synchronisation
    -
         Délai d’attente de la requête de synchronisation
         Lancer au démarrage
         Vous ne serez pas notifié des messages entrants quand l’application est en arrière-plan.
    @@ -1843,7 +1808,7 @@
         • Les serveurs correspondant à des IP littérales sont maintenant interdits.
         • Les serveurs correspondants à des IP littérales sont maintenant autorisés.
         • Les serveurs correspondant à %s sont supprimés de la liste autorisée.
    -    • les serveur correspondant à %s sont maintenant autorisés.
    +    • les serveurs correspondant à %s sont maintenant autorisés.
         • Les serveurs correspondant à %s étaient supprimés de la liste des interdits.
         • Les serveurs correspondant à %s sont maintenant interdits.
         Vous avez changé les droits ACL du serveur pour ce salon.
    @@ -1927,10 +1892,7 @@
         ${app_name} a besoin d’accéder à votre appareil photo et à votre microphone pour passer des appels vidéo.
     \n
     \nVeuillez autoriser l’accès dans les prochaines fenêtres pour pouvoir effectuer l’appel.
    -
         ${app_name} a besoin d’accéder à votre microphone pour passer des appels audio.
    -
    -
         Information
         Le correspondant n’a pas décroché.
         Vous avez mis l’appel en attente
    @@ -1966,7 +1928,6 @@
         Ce n’est pas une adresse de serveur Matrix valide
         Veuillez saisir une URL valide
         Veuillez lire et accepter les politiques de ce serveur d’accueil :
    -
         Ce serveur d’accueil souhaite s’assurer que vous n’êtes pas un robot
         Le numéro de téléphone est déjà défini.
         Nom d’utilisateur et/ou mot de passe incorrect
    @@ -2100,4 +2061,17 @@
         Remarques
         Remarques sur les espaces
         Désolé, une erreur s’est produite en essayant d’entrer dans la conférence
    +    Certaines permissions manquent pour effectuer cette action, veuillez autoriser ces permissions depuis les réglages système.
    +    Espaces
    +    Écoute des notifications
    +    Vous n’êtes pas autorisé(e) à rejoindre ce salon
    +    
    +        %d changement des ACL du serveur
    +        %d changements des ACL du serveur
    +    
    +    Activer
    +    Voir les fils de discussions
    +    Permissions manquantes
    +    Pour envoyer des messages vocaux, veuillez accorder la permission Microphone.
    +    Pour effectuer cette action, veuillez autoriser la permission Caméra depuis les réglages système.
     
    \ No newline at end of file
    diff --git a/vector/src/main/res/values-fr/strings.xml b/vector/src/main/res/values-fr/strings.xml
    index 6bae118e59..e1b3b0043c 100644
    --- a/vector/src/main/res/values-fr/strings.xml
    +++ b/vector/src/main/res/values-fr/strings.xml
    @@ -34,7 +34,7 @@
         L’appareil de l’expéditeur ne nous a pas envoyé les clés pour ce message.
         Envoi du message impossible
         Erreur de Matrix
    -    Adresse e-mail
    +    Adresse électronique
         Numéro de téléphone
         Invitation au salon
         Salon vide
    @@ -290,7 +290,7 @@
         Aucun résultat
         Image de profil
         Nom affiché
    -    Ajouter une adresse e-mail
    +    Ajouter une adresse électronique
         Ajouter un numéro de téléphone
         Affiche les informations de l’application dans les paramètres système.
         Informations sur l’application
    @@ -372,11 +372,11 @@
         L’application s’est arrêtée anormalement la dernière fois. Souhaitez-vous ouvrir l’écran de rapport d’anomalie \?
         Le rapport d’anomalie a bien été envoyé
         L’envoi du rapport d’anomalie a échoué (%s)
    -    Ceci ne ressemble pas à une adresse e-mail valide
    -    Cette adresse e-mail est déjà utilisée.
    +    Ceci ne ressemble pas à une adresse électronique valide
    +    Cette adresse électronique est déjà utilisée.
         Ce serveur d’accueil souhaite s’assurer que vous n’êtes pas un robot
    -    L’adresse e-mail liée à votre compte doit être saisie.
    -    Impossible de vérifier l’adresse e-mail : assurez-vous d’avoir cliqué sur le lien dans l’e-mail
    +    L’adresse électronique liée à votre compte doit être saisie.
    +    Impossible de vérifier l’adresse électronique : assurez-vous d’avoir cliqué sur le lien dans le courriel
         Trop de requêtes ont été envoyées
         Quitter
         Citer
    @@ -406,7 +406,7 @@
         Quand je suis invité sur un salon
         Paramètres utilisateur
         %1$s @ %2$s
    -    Vérifiez votre e-mail et cliquez sur le lien qu’il contient. Une fois cela fait, cliquez sur continuer.
    +    Vérifiez votre courriel et cliquez sur le lien qu’il contient. Une fois ceci fait, cliquez sur continuer.
         Afficher tous les messages de %s \?
     \n
     \nVeuillez noter que cette action redémarrera l’application et pourra prendre un certain temps.
    @@ -422,7 +422,7 @@
         Interface utilisateur
         Langue
         Choisissez une langue
    -    Cette adresse e-mail est déjà utilisée.
    +    Cette adresse électronique est déjà utilisée.
         Ce numéro de téléphone est déjà utilisé.
         Lancer au démarrage
         Vider le cache des médias
    @@ -533,11 +533,11 @@
         Pour continuer à utiliser le serveur d’accueil %1$s, vous devez lire et accepter les conditions générales.
         Voir maintenant
         Désactiver le compte
    -    Votre compte sera inutilisable de façon permanente. Vous ne pourrez plus vous connecter et personne ne pourra se réenregistrer avec le même identifiant d’utilisateur. Le compte quittera tous les salons auxquels il participe et tous ses détails seront supprimés du serveur d’identité. Cette action est irréversible. 
    -\n 
    -\nDésactiver votre compte ne nous fait pas oublier les messages que vous avez envoyés par défaut. Si vous souhaitez que nous oubliions vos messages, cochez la case ci-dessous. 
    -\n 
    -\nLa visibilité des messages dans Matrix est identique à celle des e-mails. Si nous oublions vos messages, cela signifie que les messages que vous avez envoyés ne seront plus partagés avec les nouveaux utilisateurs ou les utilisateurs non enregistrés, mais les utilisateurs enregistrés qui ont déjà accès à ces messages en conserveront leur copie.
    +    Votre compte sera inutilisable de façon permanente. Vous ne pourrez plus vous connecter et personne ne pourra se réenregistrer avec le même identifiant d’utilisateur. Le compte quittera tous les salons auxquels il participe et tous ses détails seront supprimés du serveur d’identité. Cette action est irréversible.
    +\n
    +\nDésactiver votre compte ne nous fait pas oublier les messages que vous avez envoyés par défaut. Si vous souhaitez que nous oubliions vos messages, cochez la case ci-dessous.
    +\n
    +\nLa visibilité des messages dans Matrix est identique à celle des courriels. Si nous oublions vos messages, cela signifie que les messages que vous avez envoyés ne seront plus partagés avec les nouveaux utilisateurs ou les utilisateurs non enregistrés, mais les utilisateurs enregistrés qui ont déjà accès à ces messages en conserveront leur copie.
         Veuillez oublier tous les messages que j’ai envoyé quand mon compte sera désactivé (Avertissement : les futurs utilisateurs verront une version incomplète des conversations)
         Désactiver le compte
         Télécharger
    @@ -941,20 +941,20 @@
         Modifier le serveur d’identité
         Vous utilisez actuellement %1$s pour découvrir et être découvrable par les contacts existants que vous connaissez.
         Vous n’utilisez actuellement aucun serveur d’identité. Pour découvrir et être découvrable par les contacts existants que vous connaissez, configurez-en un ci-dessous.
    -    Adresses e-mail découvrables
    -    Les options de découverte apparaîtront quand vous aurez ajouté un e-mail.
    +    Adresses électronique découvrables
    +    Les options de découverte apparaîtront quand vous aurez ajouté un courriel.
         Les options de découverte apparaîtront quand vous aurez ajouté un numéro de téléphone.
    -    La déconnexion du serveur d’identité signifie que vous ne pourrez plus être découvrable par les autres utilisateurs et que vous ne pourrez plus inviter d’autres personnes par e-mail ou par téléphone.
    +    La déconnexion du serveur d’identité signifie que vous ne pourrez plus être découvrable par les autres utilisateurs et que vous ne pourrez plus inviter d’autres personnes par courriel ou par téléphone.
         Numéros de téléphone découvrables
    -    Nous vous avons envoyé un e-mail de confirmation à %s, consultez vos e-mails et cliquez sur le lien de confirmation
    +    Nous vous avons envoyé un courriel de confirmation à %s, consultez vos courriels et cliquez sur le lien de confirmation
         Renseignez l’URL d’un serveur d’identité
         Impossible de se connecter au serveur d’identité
         Veuillez renseigner l’URL du serveur d’identité
         Le serveur d’identité n’a pas de conditions de service
         Le serveur d’identité qui vous avez choisi n’a pas de conditions de service. Continuez uniquement si vous faites confiance au propriétaire de ce service
         Un SMS a été envoyé à %s. Saisissez le code de vérification qu’il contient.
    -    Vous partagez actuellement des adresse e-mails et des numéros de téléphone sur le serveur d’identité %1$s. Vous devrez vous reconnecter à %2$s pour arrêter de les partager.
    -    Acceptez les conditions de service du serveur d’identité (%s) pour vous permettre d’être découvrable avec une adresse e-mail ou un numéro de téléphone.
    +    Vous partagez actuellement des adresses électroniques et des numéros de téléphone sur le serveur d’identité %1$s. Vous devrez vous reconnecter à %2$s pour arrêter de les partager.
    +    Acceptez les conditions de service du serveur d’identité (%s) pour vous permettre d’être découvrable avec une adresse électronique ou un numéro de téléphone.
         Activer les journaux verbeux.
         Les journaux verbeux aideront les développeurs en fournissant plus de journaux quand vous envoyez un rapport d’anomalie. Même si cette option est activée, l’application n’envoie pas le contenu des messages ou toute autre donnée personnelle.
         Réessayez quand vous aurez accepté les termes et conditions de votre serveur d’accueil.
    @@ -1049,7 +1049,7 @@
         Étendez et personnalisez votre expérience
         Démarrer
         Sélectionner un serveur
    -    Tout comme les e-mails, les comptes ont un serveur d’accueil, même si vous pouvez parler à tout le monde
    +    Tout comme les courriels, les comptes ont un serveur d’accueil, même si vous pouvez parler à tout le monde
         Rejoignez des millions de personnes gratuitement sur le plus grand serveur public
         Hébergement premium pour les organisations
         En savoir plus
    @@ -1075,20 +1075,20 @@
         L’application ne peut pas créer de compte sur ce serveur d’accueil.
     \n
     \nVoulez-vous vous inscrire en utilisant un client web \?
    -    Cet e-mail n’est associé à aucun compte.
    +    Ce courriel n’est associé à aucun compte.
         Réinitialiser le mot de passe sur %1$s
    -    Un e-mail de vérification sera envoyé à votre adresse pour confirmer la configuration de votre nouveau mot de passe.
    +    Un courriel de vérification sera envoyé à votre adresse pour confirmer la configuration de votre nouveau mot de passe.
         Suivant
    -    E-mail
    +    Courriel
         Nouveau mot de passe
         Attention !
         Le changement de mot de passe réinitialisera toutes les clés de chiffrement sur toutes vos sessions, rendant l’historique des discussions chiffrées illisible. Configurez la sauvegarde de clés ou exportez vos clés de salon depuis une autre session avant de réinitialiser votre mot de passe.
         Poursuivre
    -    Cet e-mail n’est lié à aucun compte
    +    Ce courriel n’est lié à aucun compte
         Vérifiez votre boîte de réception
    -    Un e-mail de vérification a été envoyé à %1$s.
    +    Un courriel de vérification a été envoyé à %1$s.
         Touchez le lien pour confirmer votre nouveau mot de passe. Après avoir suivi le lien qu’il contient, cliquez ci-dessous.
    -    J’ai vérifié mon adresse e-mail
    +    J’ai vérifié mon adresse électronique
         Succès !
         Votre mot de passe a été réinitialisé.
         Vous avez été déconnecté de toutes les sessions et ne recevrez plus de notification. Pour réactiver les notifications, reconnectez-vous sur chaque appareil.
    @@ -1097,10 +1097,10 @@
         Votre mot de passe n’a pas encore été changé.
     \n
     \nArrêter le processus de changement \?
    -    Définir l’adresse e-mail
    -    Définir une adresse e-mail pour récupérer votre compte. Plus tard, vous pourrez éventuellement autoriser des personnes à vous retrouver avec votre adresse e-mail.
    -    E-mail
    -    E-mail (facultatif)
    +    Définir l’adresse électronique
    +    Définir une adresse électronique pour récupérer votre compte. Plus tard, vous pourrez éventuellement autoriser des personnes à vous retrouver avec votre adresse électronique.
    +    Courriel
    +    Courriel (facultatif)
         Suivant
         Définir le numéro de téléphone
         Définir un numéro de téléphone pour autoriser éventuellement des personnes à vous découvrir.
    @@ -1116,7 +1116,7 @@
         Les numéros de téléphone internationaux doivent commencer par « + »
         Le numéro de téléphone n’a pas l’air d’être valide. Veuillez le vérifier
         S’inscrire sur %1$s
    -    Nom d’utilisateur ou e-mail
    +    Nom d’utilisateur ou courriel
         Mot de passe
         Suivant
         Ce nom d’utilisateur est déjà pris
    @@ -1129,8 +1129,8 @@
         Sélectionner un serveur d’accueil personnalisé
         Veuillez compléter le captcha
         Acceptez les termes pour continuer
    -    Vérifiez vos e-mails
    -    Nous avons envoyé un e-mail à %1$s.
    +    Vérifiez vos courriels
    +    Nous avons envoyé un courriel à %1$s.
     \nCliquez sur le lien qu’il contient pour continuer la création du compte.
         Le code saisi n’est pas correct. Veuillez vérifier.
         Serveur d’accueil obsolète
    @@ -1184,7 +1184,7 @@
         Préfixe ¯\\_(ツ)_/¯ à un message en texte brut
         Activer le chiffrement
         Une fois qu’il est activé, le chiffrement ne peut pas être désactivé.
    -    Le domaine de votre adresse e-mail n’est pas autorisé à s’inscrire sur ce serveur
    +    Le domaine de votre adresse électronique n’est pas autorisé à s’inscrire sur ce serveur
         Connexion non fiable
         Ils correspondent
         Ils ne correspondent pas
    @@ -1443,7 +1443,7 @@
         Message supprimé
         Afficher les messages supprimés
         Afficher un remplaçant pour les messages supprimés
    -    Nous vous avons envoyé un e-mail de confirmation à %s, consultez vos e-mails et cliquez sur le lien de confirmation
    +    Nous vous avons envoyé un courriel de confirmation à %s, consultez vos courriels et cliquez sur le lien de confirmation
         Le code de vérification n’est pas correct.
         MÉDIA
         Il n’y a aucun média dans ce salon
    @@ -1466,7 +1466,7 @@
         Cette opération n’est pas possible. Le serveur d’accueil est obsolète.
         Veuillez d’abord configurer un serveur d’identité.
         Veuillez d’abord accepter les termes du serveur d’identité dans les paramètres.
    -    Pour votre vie privée, ${app_name} prend uniquement en charge l’envoi des adresses e-mail et des numéros de téléphone hachés.
    +    Pour votre vie privée, ${app_name} prend uniquement en charge l’envoi des adresses électronique et des numéros de téléphone hachés.
         L’association a échoué.
         Il n’y a actuellement aucune association avec cet identifiant.
         Votre serveur d’accueil (%1$s) propose d’utiliser %2$s comme serveur d’identité
    @@ -1653,8 +1653,8 @@
             %d utilisateur banni
             %d utilisateurs bannis
         
    -    Gérer les e-mails et numéros de téléphone liés à votre compte Matrix
    -    E-mails et numéros de téléphone
    +    Gérer les courriels et numéros de téléphone liés à votre compte Matrix
    +    Courriels et numéros de téléphone
         Sauvegarde sécurisée
         Démarrer la caméra
         Ouvrir la discussion
    @@ -1677,11 +1677,11 @@
             %d seconde
             %d secondes
         
    -    Assurez-vous d\'avoir cliqué sur le lien envoyé par e-mail.
    +    Assurez-vous d\'avoir cliqué sur le lien envoyé par courriel.
         Supprimer %s \?
         Numéros de téléphone
    -    Aucune adresse e-mail n’a été ajoutée à votre compte
    -    Adresses e-mail
    +    Aucune adresse électronique n’a été ajoutée à votre compte
    +    Adresses électroniques
         Aucun numéro de téléphone n’a été ajouté à votre compte
         Filtrer les utilisateurs exclus
         Ne plus ignorer cet utilisateur aura pour effet de ré-afficher ses messages.
    @@ -1745,8 +1745,8 @@
         %1$d de %2$d
         Autoriser
         Révoquer mon autorisation
    -    Vous avez donné votre autorisation pour envoyer des e-mails et des numéros de téléphone à ce serveur d’identité pour découvrir d\'autres utilisateurs à partir de vos contacts.
    -    Envoyer des e-mails et des numéros de téléphone
    +    Vous avez donné votre autorisation pour envoyer des courriels et des numéros de téléphone à ce serveur d’identité pour découvrir d\'autres utilisateurs à partir de vos contacts.
    +    Envoyer des courriels et des numéros de téléphone
         Suggestions
         Utilisateurs connus
         code QR
    @@ -1997,7 +1997,7 @@
         Permettra de parcourir les salons de %s
         Inviter dans %s
         Partager le lien
    -    Inviter par e-mail
    +    Inviter par courriel
         Vous êtes seul pour l’instant. %s sera plus agréable avec de la compagnie.
         Invitez des personnes dans votre espace
         Inviter des personnes
    @@ -2153,7 +2153,7 @@
         Mentions et mots-clés
         Notifications par défaut
         %s dans les paramètres pour recevoir les invitations directement dans ${app_name}.
    -    Lier cet e-mail à votre compte
    +    Lier ce courriel à votre compte
         Cette invitation à cette espace a été envoyée à %s qui n’est pas associé à votre compte
         Cette invitation à ce salon a été envoyée à %s qui n’est pas associé à votre compte
         Tous les salons dans lesquels vous vous trouvez seront affichés sur l’Accueil.
    @@ -2415,4 +2415,8 @@
         Copier le lien du fil de discussion
         Voir dans le salon
         Voir les fils de discussions
    +    
    +        %d changement des ACL du serveur
    +        %d changements des ACL du serveur
    +    
     
    \ No newline at end of file
    diff --git a/vector/src/main/res/values-in/strings.xml b/vector/src/main/res/values-in/strings.xml
    index d7856089e8..719b49fc4b 100644
    --- a/vector/src/main/res/values-in/strings.xml
    +++ b/vector/src/main/res/values-in/strings.xml
    @@ -2002,7 +2002,7 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan.
     \n${app_name} Desktop
         Atur kata sandi akun baru…
         Tidak dapat menyimpan file media
    -    Mengaktifkan pengaturan ini menambahkan FLAG_SECURE ke semua Aktifitas. Mulai ulang aplikasi ini untuk berpengaruh pada perubahannya.
    +    Mengaktifkan pengaturan ini menambahkan FLAG_SECURE ke semua Aktivitas. Mulai ulang aplikasi ini untuk berpengaruh pada perubahannya.
         Mencegah tangkapan layar dari aplikasi
         Kunci pemulihan kunci cadangan
         Tidak tahu Kunci Frasa Sandi Cadangan, Anda dapat %s.
    diff --git a/vector/src/main/res/values-is/strings.xml b/vector/src/main/res/values-is/strings.xml
    index 07fe4b55be..ee34c9e314 100644
    --- a/vector/src/main/res/values-is/strings.xml
    +++ b/vector/src/main/res/values-is/strings.xml
    @@ -4,9 +4,9 @@
         %1$s bauð %2$s
         %1$s bauð þér
         %1$s gekk í hópinn
    -    %1$s hætti
    +    %1$s hætti í spjallrásinni
         %1$s hafnaði boðinu
    -    %1$s sparkaði %2$s
    +    %1$s fjarlægði %2$s
         %1$s afbannaði %2$s
         %1$s bannaði %2$s
         %1$s breyttu auðkennismynd sinni
    @@ -20,10 +20,10 @@
         Villa í Matrix
         Tölvupóstfang
         Símanúmer
    -    %1$s tók til baka boð frá %2$s
    +    %1$s tók til baka boð til %2$s
         %1$s setti birtingarnafn sitt sem %2$s
         %1$s breytti birtingarnafni sínu úr %2$s í %3$s
    -    %1$s fjarlægði birtingarnafn sitt (%2$s)
    +    %1$s fjarlægði birtingarnafn sitt (sem var %2$s)
         %1$s breytti umræðuefninu í: %2$s
         %1$s breytti heiti spjallrásarinnar í: %2$s
         %s hringdi myndsamtal.
    @@ -38,7 +38,6 @@
         Tæki sendandans hefur ekki sent okkur dulritunarlyklana fyrir þessi skilaboð.
         Boð á spjallrás
         %1$s og %2$s
    -
         Tóm spjallrás
         Ljóst þema
         Dökkt þema
    @@ -80,7 +79,6 @@
         Samtöl
         Engar niðurstöður
         Spjallrásir
    -
         Senda atvikaskrá
         Senda hrunskrár
         Senda skjámynd
    @@ -121,14 +119,13 @@
         Taka þátt
         Hafna
         Listi yfir meðlimi
    -
         
             %d meðlimur
             %d meðlimir
         
         Fara af spjallrás
    -    Ertu viss um að þú viljir fara út spjallrásinni?
    -    BEINT SPJALL
    +    Ertu viss um að þú viljir fara úr spjallrásinni\?
    +    Bein skilaboð
         Bjóða
         %s er að skrifa…
         %1$s & %2$s eru að skrifa…
    @@ -145,7 +142,6 @@
         Leita
         Sía meðlimi spjallrásar
         Engar niðurstöður
    -
         Öll skilaboð
         Notandamynd
         Birtingarnafn
    @@ -180,7 +176,7 @@
         Veldu tungumál
         Breyta lykilorði
         eldra lykilorð
    -    nýtt lykilorð
    +    Nýtt lykilorð
         Mistókst að uppfæra lykilorð
         Lykilorðið þitt hefur verið uppfært
         Sýna öll skilaboð frá %s\?
    @@ -199,10 +195,10 @@
         Þema
         Afkóðunarvilla
         Heiti tækis
    -    Auðkenni tækis
    +    Auðkenni setu
         Dulritunarlykill tækis
         Flytja út
    -    Settu inn lykilsetningu (passphrase)
    +    Settu inn lykilsetningu
         Staðfestu lykilsetningu
         Flytja inn
         Sannreyna
    @@ -270,19 +266,18 @@
         Þér hefur verið sparkað úr %1$s af %2$s
         Þú hefur verið settur í bann á %1$s af %2$s
         Tilraunir
    -    Klaga efni
    +    Kæra efni
         Slóð á heimaþjón
         Ertu viss að þú viljir byrja raddsamtal?
         Ertu viss að þú viljir byrja myndsamtal?
    -    Fara í fyrstu ólesin skilaboð.
    +    Fara í ólesið
         Banna
         Afbanna
         Fela öll skilaboð frá þessum notanda
         Sýna öll skilaboð frá þessum notanda
    -    Þú hefur ekki heimild til að senda skilaboð á þessa spjallrás
    +    Þú hefur ekki heimild til að senda skilaboð á þessa spjallrás.
         Gat ekki sannreynt auðkenni fjartengds þjóns.
    -
    -    Bæta við flýtileið á aðalskjá
    +    Bæta við á upphafsskjá
         Hljóð með tilkynningu
         Virkja tilkynningar fyrir þennan notandaaðgang
         Virkja tilkynningar á þessu tæki
    @@ -294,7 +289,6 @@
         Heimildir fyrir tengiliði
         Alltaf birta tímamerki skilaboða
         Birta tímamerki á 12 stunda sniði (t.d. 2:30 fh)
    -
         Heimaþjónn
         Auðkennisþjónn
         Þetta tölvupóstfang er nú þegar í notkun.
    @@ -304,18 +298,16 @@
         Einungis meðlimir (síðan þeir skráðu sig)
         Innra auðkenni þessarar spjallrásar
         Veldu skrá yfir spjallrásir
    -    URL-slóð heimaþjóns
    +    Heiti heimaþjóns
         
             %d ólesið tilkynnt skilaboð
             %d ólesin tilkynnt skilaboð
         
    -
         Ertu viss um að þú viljir eyða viðmótshlutanum?
         Gat ekki búið til viðmótshluta.
         Mistókst að senda beiðni.
         Boðið
    -    Bönun notanda mun henda þeim út úr þessu herbergi og halda þeim frá því að koma aftur.
    -
    +    Bann á notanda mun henda honum út af þessari spjallrás og koma í veg fyrir að viðkomandi komi aftur.
         Skilaboð innihalda birtingarnafn mitt
         Skilaboð innihalda notandanafn mitt
         Skilaboð í maður-á-mann spjalli
    @@ -332,10 +324,8 @@
         Þú getur ekki afturkallað þessa aðgerð, þar sem þú ert að gefa notandanum jafn mikil völd og þú hefur sjálf/ur.
     \nErtu alveg viss\?
         Hlé milli tveggja samstillingarbeiðna
    -    Halda gögnum
    +    Halda myndefni
         Skoðaðu tölvupóstinn þinn og smelltu á tengilinn sem hann inniheldur. Þegar því er lokið skaltu smella á að halda áfram.
    -
    -
         Aðeins dulrita til sannvottaðra tækja
         Aldrei senda dulrituð skilaboð af þessu tæki til ósannvottaðra tækja.
         Völd verða að vera jákvæð heiltala.
    @@ -344,36 +334,1210 @@
         Vantar spjallrásarauðkenni í beiðni.
         Vantar notandaauðkenni í beiðni.
         Senda límmerki
    -
         Ekki var svarað á fjartengda endanum.
    -
    -
         ${app_name} þarf heimild til að nota hljóðnemann svo hægt sé að hringja hljóðsímtöl.
    -
         ${app_name} þarf heimild til að nota myndavélina og hljóðnemann svo hægt sé að hringja myndsímtöl.
     \n
     \nLeyfðu aðgang í næstu sprettgluggum til þess að geta hringt.
    -
    -
         Gera notandaaðgang óvirkann
         Gera notandaaðganginn minn óvirkann
         Senda greiningargögn
         Yfirfara núna
         Gera notandaaðgang óvirkann
         Gera notandaaðgang óvirkann
    -
         Lýstu villunni. Hvað varstu að gera? Hverju áttirðu von á? Hvað gerðist í raun?
         Til að geta greint vandamál eru atvikaskrár þessa forrits sendar með þessari villuskýrslu. Ef þú vilt einungis senda textann hér fyrir ofan, taktu þá gátmerkið úr reitnum:
         Það er eins og þú sért að hrista símann ákveðið. Myndirðu vilja senda villuskýrslu?
         Forritið hrundi síðast. Myndirðu vilja senda inn villuskýrslu?
         Senda límmerki
         ${app_name} safnar nafnlausum greiningargögnum til að gera okkur kleift að bæta forritið.
    -    Heimaskjár
    +    Upphafsskjár
         Festa spjallrásir með óskoðuðum tilkynningum
         Festa spjallrásir með ólesnum skilaboðum
         Sjálfgefið virkja forskoðun innfelldra vefslóða
         Þetta eru eiginleikar á tilraunastigi sem gætu bilað á óvæntan hátt. Notist með varúð.
         Setja sem aðalvistfang
    -    Ekki setja sem aðalvistfang
    +    Ekki hafa sem aðalvistfang
         Nauðsynlegt gildi vantar.
    +    Þú getur slökkt á þessu hvenær sem er í stillingunum
    +    Dulrituð skilaboð í hópaspjalli
    +    Dulrituð skilaboð í maður-á-mann spjalli
    +    Upphaf samstillingar:
    +\nHleð inn samtölunum þínum
    +\nÞetta getur tekið dálítinn tíma ef þú tekur þátt í mörgum spjallrásum
    +    Aðeins fólk sem hefur verið boðið getur fundið og tekið þátt
    +    Einka (einungis gegn boði)
    +    Þú getur sýsla- með tilkynningar í %1$s.
    +    Sjálfgefinn uppruni myndefnis
    +    Sjálfgefin þjöppun
    +    Sýslaðu með tölvupóstföng og símanúmer sem tengd eru við Matrix-aðganginn þinn
    +    Tölvupóstföng og símanúmer
    +    Lykilorðið er ekki gilt
    +    Utanaðkomandi aðgerðasöfn
    +    Hjálpaðu okkur að bæta ${app_name}
    +    Sýsla með uppgötvunarstillingarnar þínar.
    +    Smelltu á leskvittanir til að sjá ítarlegan lista.
    +    Birta leskvittanir
    +    Markdown-sníðing
    +    Láttu aðra sjá að þú sért að skrifa.
    +    Senda skriftilkynningar
    +    Umsýsla dulritunarlykla
    +    Dulrituð hópskilaboð
    +    Dulrituð bein skilaboð
    +    Bestun fyrir rafhlöðuendingu
    +    Birting tilkynninga
    +    Tilkynningar eru óvirkar í þessari setu.
    +\nYfirfarðu stillingar í ${app_name}.
    +    Tilkynningar eru virkar í þessari setu.
    +    Tilkynningar eru óvirkar fyrir notandaaðganginn þinn.
    +\nYfirfarðu stillingar aðgangsins.
    +    Tilkynningar eru virkar fyrir notandaaðganginn þinn.
    +    Tilkynningar eru óvirkar í kerfisstillingum.
    +\nYfirfarðu kerfisstillingarnar.
    +    Tilkynningar eru virkar í kerfisstillingum.
    +    Í keyrslu (%1$d af %2$d)
    +    Keyra prófanir
    +    Greining á vandamálum
    +    Leysa vandamál með tilkynningar
    +    Stikkorð mega ekki innihalda \'%s\'
    +    Stikkorð mega ekki byrja með \'.\'
    +    Bæta við nýju stikkorði
    +    Stikkorðin þín
    +    Minnst á og stikkorð
    +    Virkja tilkynningar í tölvupósti fyrir %s
    +    Til að fá tilkynningar í tölvupósti, þarf að tengja tölvupóstfang við Matrix-aðganginn þinn
    +    Tilkynning í tölvupósti
    +    Mikilvægi tilkynninga eftir atburðum
    +    Skráð út úr setunni!
    +    Spjallrásin hefur verið yfirgefin!
    +    Einungis þar sem er minnst á og stikkorð
    +    Sía bannaða notendur
    +    Úr spjallþræði
    +    Ábending: Ýttu lengi á skilaboð og notaðu “%s”.
    +    Spjallþræðir hjálpa til við að halda samræðum við efnið og gerir auðveldara að rekja þær.
    +    Haltu umræðum skipulögðum með spjallþráðum
    +    Birtir alla spjallþræði sem þú hefur tekið þátt í
    +    Birtir alla spjallþræði úr fyrirliggjandi spjallrás
    +    Spjallþræðirnir mínir
    +    Allir spjallþræðir
    +    Sía þræði spjallrásar
    +    Heimildir spjallrásar
    +    Þessi spjallrás er ekki opinber. Þú munt ekki geta tekið aftur þátt nema að vera boðið.
    +    Gefa heimild til að fá aðgang að tengiliðunum þínum.
    +    Til að skanna QR-kóða þarftu að veita aðgang að myndavélinni.
    +    Lýk símtali…
    +    Notandi upptekinn
    +    Þú settir símtalið í bið
    +    %s setti símtalið í bið
    +    Raddsímtal við %s
    +    Myndsamtal við %s
    +    Myndsamtal í gangi…
    +    
    +        Ósvarað myndsímtal
    +        %d ósvöruð myndsímtöl
    +    
    +    
    +        Ósvarað raddsímtal
    +        %d ósvöruð raddsímtöl
    +    
    +    Veldu hringitón fyrir símtöl:
    +    Innhringitónn
    +    Nota sjálfgefinn ${app_name} hringitón fyrir innhringingar
    +    Biðja um staðfestingu áður en símtal er hafið
    +    Koma í veg fyrir símtöl af slysni
    +    Þetta símanúmer er nú þegar skráð.
    +    Því miður, ekkert utankomandi forrit hefur fundist sem getur lokið þessari aðgerð.
    +    Í augnablikinu ertu ekki með neina límmerkjapakka virkjaða.
    +\n
    +\nBæta einhverjum við núna\?
    +    Veldu hljóðtæki
    +    ${app_name} símtal mistókst
    +    Senda tal
    +    Slóð á API-kerfisviðmót heimaþjóns
    +    Ef mögulegt, skaltu skrifa lýsinguna á ensku.
    +    Sýna allar spjallrásir í spjallrásalistanum, þar með taldar spjallrásir með viðkvæmu efni.
    +    Sýna spjallrásir með viðkvæmu efni
    +    Aðvaranir kerfis
    +    Afrita tengil á spjallþráð
    +    Taka úr birtingu
    +    Skoða spjallþræði
    +    Mistókst að fjarlægja viðmótshluta
    +    Mistókst að bæta við viðmótshluta
    +    Þú getur ekki byrjað símtal með sjálfum þér, bíddu eftir að þátttakendur samþykki boðið
    +    Þú getur ekki byrjað símtal með sjálfum þér
    +    Til að senda talskilaboð þarf að gefa heimild fyrir hljóðnema.
    +    Til að framkvæma þessa aðgerð þarf að gefa heimild fyrir myndavél í kerfisstillingum.
    +    Það vantar heimildir til að framkvæma þessa aðgerð, það þarf að gefa viðkomandi heimildir í kerfisstillingum.
    +    Hefja talfund
    +    Hefja myndfund
    +    Þú hefur ekki heimildir til að hefja símtal
    +    Þú hefur ekki heimildir til að hefja símtal á þessari spjallrás
    +    Þú hefur ekki heimildir til að hefja fjarfund
    +    Þú hefur ekki heimildir til að hefja fjarfund á þessari spjallrás
    +    Vantar heimildir
    +    Þú munt missa aðgang að dulrituðu skilaboðunum þínum nema þú takir öryggisafrit af dulritunarlyklum áður en þú skráir þig út.
    +    Öryggisafritun dulritunarlykla í gangi. Þú munt tapa dulrituðu skilaboðunum þínum ef þú skráir þig út núna.
    +    Þú munt tapa dulrituðu skilaboðunum þínum ef þú skráir þig út núna
    +    Þú kveiktir á enda-í-enda dulritun.
    +    %1$s kveikti á enda-í-enda dulritun.
    +    Taka öryggisafrit
    +    Öryggisafrita dulritunarlykla…
    +    Ég vil ekki dulrituðu skilaboðin mín
    +    Nota öryggisafrit af lykli
    +    Hlusta eftir tilkynningum
    +    Þú kveiktir á enda-í-enda dulritun (óþekkt algrími %1$s).
    +    %1$s kveikti á enda-í-enda dulritun (óþekkt algrími %2$s).
    +    Þú hefur bannað gestum að koma inn á spjallrásina.
    +    %1$s hefur bannað gestum að koma inn á spjallrásina.
    +    Þú hefur bannað gestum að koma inn á spjallrásina.
    +    %1$s hefur bannað gestum að koma inn á spjallrásina.
    +    Þú hefur leyft gestum að koma inn hér.
    +    %1$s hefur leyft gestum að koma inn hér.
    +    Þú hefur leyft gestum að koma inn á spjallrásina.
    +    %1$s hefur leyft gestum að koma inn á spjallrásina.
    +    
    +        Þú fjarlægðir varavistfangið %1$s af þessari spjallrás.
    +        Þú fjarlægðir varavistföngin %1$s af þessari spjallrás.
    +    
    +    
    +        %1$s fjarlægði varavistfangið %2$s af þessari spjallrás.
    +        %1$s fjarlægði varavistföngin %2$s af þessari spjallrás.
    +    
    +    
    +        Þú bættir við varavistfanginu %1$s fyrir þessa spjallrás.
    +        Þú bættir við varavistföngunum %1$s fyrir þessa spjallrás.
    +    
    +    
    +        %1$s bætti við varavistfanginu %2$s fyrir þessa spjallrás.
    +        %1$s bætti við varavistföngunum %2$s fyrir þessa spjallrás.
    +    
    +    Þú tókst til baka boð til %1$s. Ástæða: %2$s
    +    %1$s tók til baka boð til %2$s. Ástæða: %3$s
    +    Þú samþykktir boð um að taka þátt í %1$s. Ástæða: %2$s
    +    %1$s samþykkti boð um að taka þátt í %2$s. Ástæða: %3$s
    +    Þú bannaðir %1$s. Ástæða: %2$s
    +    %1$s bannaði %2$s. Ástæða: %3$s
    +    Þú tókst %1$s úr banni. Ástæða: %2$s
    +    %1$s tók %2$s úr banni. Ástæða: %3$s
    +    Þú fjarlægðir %1$s. Ástæða: %2$s
    +    %1$s fjarlægði %2$s. Ástæða: %3$s
    +    Þú hafnaðir boðinu. Ástæða: %1$s
    +    %1$s hafnaði boðinu. Ástæða: %2$s
    +    Þú hættir. Ástæða: %1$s
    +    %1$s hætti. Ástæða: %2$s
    +    Þú yfirgafst spjallrásina. Ástæða: %1$s
    +    %1$s yfirgaf spjallrásina. Ástæða: %2$s
    +    Þú tekur þátt. Ástæða: %1$s
    +    %1$s tekur þátt. Ástæða: %2$s
    +    Þú komst inn á spjallrásina. Ástæða: %1$s
    +    %1$s kom inn á spjallrásina. Ástæða: %2$s
    +    %1$s bauð þér. Ástæða: %2$s
    +    Þú bauðst %1$s. Ástæða: %2$s
    +    %1$s bauð %2$s. Ástæða: %3$s
    +    Boð um þátttöku til þín. Ástæða: %1$s
    +    Boð um þátttöku til %1$s. Ástæða: %2$s
    +    Upphaf samstillingar:
    +\nFlyt inn gögn úr notandaaðgangi
    +    Upphaf samstillingar:
    +\nFlyt inn samfélög
    +    Upphaf samstillingar:
    +\nFlyt inn yfirgefnar spjallrásir
    +    Upphaf samstillingar:
    +\nFlyt inn boð í spjallrásir
    +    Upphaf samstillingar:
    +\nFlyt inn spjallrásir
    +    Upphaf samstillingar:
    +\nFlyt inn dulritunargögn
    +    Upphaf samstillingar:
    +\nFlyt inn notandaaðgang…
    +    Upphaf samstillingar:
    +\nSæki gögn…
    +    Upphaf samstillingar:
    +\nBíð eftir svari frá netþjóni…
    +    Þú hefur ekki heimild til að taka þátt í þessari spjallrás
    +    Þú breyttir völdum %1$s.
    +    Þú breyttir %1$s viðmótshluta
    +    %1$s breytti %2$s viðmótshluta
    +    Þú fjarlægðir %1$s viðmótshluta
    +    %1$s fjarlægði %2$s viðmótshluta
    +    Þú bættir við %1$s viðmótshluta
    +    %1$s bætti við %2$s viðmótshluta
    +    Þú samþykktir boð um að taka þátt í %1$s
    +    Þú afturkallaðir boðið til %1$s
    +    %1$s afturkallaði boðið til %2$s
    +    Þú afturkallaðir boð til %1$s um þátttöku í spjallrásinni
    +    %1$s afturkallaði boð til %2$s um þátttöku í spjallrásinni
    +    Þú bauðst %1$s
    +    Þú sendir boð til %1$s um þátttöku í spjallrásinni
    +    Þú fjarlægðir auðkennismynd spjallrásarinnar
    +    %1$s fjarlægði auðkennismynd spjallrásarinnar
    +    Þú fjarlægðir umfjöllunarefni spjallrásar
    +    Þú fjarlægðir heiti spjallrásar
    +    
    +        Breyting á ACL á %d netþjóni
    +        Breyting á ACL á %d netþjóni
    +    
    +    Þú gerðir skilaboð héðan í frá sýnileg fyrir %1$s
    +    Bæta við fólki
    +    🎉 Öllum netþjónum er núna bannað að taka þátt! Þessa spjallrás er ekki lengur hægt að nota.
    +    • Netþjónar sem samsvara IP-tölum eru núna bannaðir.
    +    • Netþjónar sem samsvara IP-tölum eru núna leyfðir.
    +    • Netþjónar sem samsvara %s voru fjarlægðir af listanum yfir leyfilegt.
    +    • Netþjónar sem samsvara %s eru núna leyfðir.
    +    • Netþjónar sem samsvara %s voru fjarlægðir af bannlistanum.
    +    • Netþjónar sem samsvara %s eru núna bannaðir.
    +    Þú breyttir ACL á netþjóni fyrir þessa spjallrás.
    +    %s breytti ACL á netþjóni fyrir þessa spjallrás.
    +    • Netþjónar sem samsvara IP-tölum eru bannaðir.
    +    • Netþjónar sem samsvara IP-tölum eru leyfðir.
    +    • Netþjónar sem samsvara %s eru leyfðir.
    +    • Netþjónar sem samsvara %s eru bannaðir.
    +    Þú stilltir ACL á netþjóni fyrir þessa spjallrás.
    +    %s stillti ACL á netþjóni fyrir þessa spjallrás.
    +    Þú uppfærðir hér.
    +    %s uppfærði hér.
    +    Þú uppfærðir þessa spjallrás.
    +    %s uppfærði þessa spjallrás.
    +    %1$s gerði skilaboð héðan í frá sýnileg fyrir %2$s
    +    Þú gerðir ferilskrá spjallrásar héðan í frá sýnilega fyrir %1$s
    +    Þú laukst símtalinu.
    +    Þú svaraðir símtalinu.
    +    Þú sendir gögn til að setja upp samtalið.
    +    %s sendi gögn til að setja upp samtalið.
    +    Þú hringdir raddsamtal.
    +    Þú hringdir myndsamtal.
    +    Þú breyttir heiti spjallrásarinnar í: %1$s
    +    Þú breyttir auðkennismynd spjallrásarinnar
    +    %1$s breytti auðkennismynd spjallrásarinnar
    +    Þú breyttir umræðuefninu í: %1$s
    +    Þú fjarlægðir birtingarnafn þitt (sem var %1$s)
    +    Þú breytti birtingarnafni þínu úr %1$s í %2$s
    +    Þú settir birtingarnafn þitt sem %1$s
    +    Opna könnun
    +    Spila talskilaboð
    +    Þú þarft heimild til að uppfæra spjallrás
    +    Vertu þolinmóð/ur Þetta getur tekið nokkra stund.
    +    Opið öllum, best fyrir dreifða hópa
    +    Aðvara án hljóðs
    +    Aðvara með hljóði
    +    Opna emoji-tánmyndaval
    +    Skipta um auðkennismynd
    +    Opna viðmótshluta
    +    Virkt samtal (%1$s)
    +    Talnaborð
    +    Nýtt PIN-númer
    +    Opna notkunarskilmála %s
    +    Önnur tiltæk tungumál
    +    🔐️ Vertu með mér á ${app_name}
    +    Veldu þér lykilorð.
    +    Veldu þér notandanafn.
    +    Aðeins stutt í dulrituðum spjallrásum
    +    Settu inn endurheimtulykil
    +    Ný innskráning. Varst þetta þú\?
    +    Flugvélahamur er virkur
    +    Ekki treyst
    +    Sérsniðið (%1$d) í %2$s
    +    Sjálfgefið í %1$s
    +    Umsjónarmaður í %1$s
    +    Stjórnandi í %1$s
    +    Stjórnendur
    +    
    +        Einn aðili
    +        %1$d aðilar
    +    
    +    Aðgerðir stjórnanda
    +    Ekki öruggt
    +    Birta villuleitarupplýsingar á skjá
    +    Aðrar setur
    +    Úreltur heimaþjónn
    +    Athugaðu tölvupóstinn þinn
    +    Símanúmer lítur út fyrir að vera ógilt. Yfirfarðu það
    +    Nota alþjóðlega sniðið.
    +    Nýtt lykilorð
    +    Opna valmyndina til að útbúa spjallrás
    +    Settu inn slóð auðkennisþjónsins
    +    Engar breytingar fundust
    +    Aðrar tilkynningar frá utanaðkomandi aðilum
    +    Ekkert netkerfi. Athugaðu nettenginguna þína.
    +    Atburður undir umsjón stjórnanda spjallrásar
    +    Eyða öryggisafriti
    +    Öryggisafrit endurheimti %s !
    +    Settu inn endurheimtulykil
    +    Aflæsi ferli
    +    Flyt inn dulritunarlykla…
    +    Næ í dulritunarlykla…
    +    Reikna endurheimtulykil…
    +    Endurheimti úr öryggisafriti:
    +    Settu inn endurheimtulykil
    +    Endurheimtulykill
    +    Deila endurheimtulykli með…
    +    Gera afrit
    +    Endurheimtulykillinn hefur verið vistaður.
    +    Vista endurheimtulykil
    +    Ég hef gert afrit
    +    Því miður, villa kom upp
    +    Smelltu hér til að sjá eldri skilaboð
    +    Markdown-texti hefur verið gerður óvirkur.
    +    Markdown-texti hefur verið gerður virkur.
    +    Birtir upplýsingar um notanda
    +    Markdown-texti af/á
    +    Skilgreindu völd notanda
    +    Loka á allt
    +    Auðkenni viðmótshluta
    +    Þemað þitt
    +    Notandaauðkennið þitt
    +    Vefslóð á auðkennismyndina þína
    +    Birtingarnafnið þitt
    +    Þessum viðmótshluta var bætt við af:
    +    %1$s: %2$s %3$s
    +    ** Mistókst að senda - opnaðu spjallrásina
    +    %1$s í %2$s og %3$s
    +    
    +        %1$s: %2$d skilaboð
    +        %1$s: %2$d skilaboð
    +    
    +    
    +        %d boð
    +        %d boð
    +    
    +    Þessi netþjónn er nú þegar á listanum
    +    Fann ekki þennan netþjón eða spjallrásalista hans
    +    Sláðu inn nafn nýja netþjónsins sem þú vilt skoða.
    +    óþekkt IP-vistfang
    +    Sýsla með öryggisafrit dulritunarlykla
    +    Endurheimt dulritaðra skilaboða
    +    Útflutningur dulritunarlykla tókst
    +    Önnur svæði sem þú gætir ekki vitað um
    +    Spila hljóð við myndatöku
    +    Engin samstilling í bakgrunni
    +    Bestað gagnvart rauntíma
    +    Bestað gagnvart rafhleðslu
    +    Hamur samstillingar í bakgrunni
    +    Láta mig vita fyrir
    +    Engu tölvupóstfangi hefur verið bætt við notandaaðganginn þinn
    +    Engu símanúmeri hefur verið bætt við notandaaðganginn þinn
    +    Mistókst að koma á rauntímatengingu.
    +\nBiddu kerfisstjóra heimaþjónsins þíns um að setja upp TURN-þjón til að tryggja að símtöl virki eðlilega.
    +    %1$s úr %2$s í %3$s
    +    %1$s breytti völdum %2$s.
    +    Stjórnandi
    +    Þú breyttir auðkennismyndinni þinni
    +    Þú tókst til baka boð til %1$s
    +    Þú bannaðir %1$s
    +    Þú afbannaðir %1$s
    +    Þú fjarlægðir %1$s
    +    Þú hafnaðir boðinu
    +    Þú hættir í spjallrásinni
    +    %1$s hætti í spjallrásinni
    +    Þú hættir í spjallrásinni
    +    Þú gekkst í hópinn
    +    Þú gekkst í spjallrásina
    +    Þú bauðst %1$s
    +    Þú bjóst til umræðuna
    +    %1$s bjó til umræðuna
    +    Þú bjóst til spjallrásina
    +    %1$s bjó til spjallrásina
    +    %s gekk í hópinn.
    +    Þú stilltir aðalvistfang spjallrásarinnar sem %1$s.
    +    Sérsniðin kæra…
    +    Sýna allar spjallrásir á forsíðu
    +    Sýsla með spjallrásir og svæði
    +    Sýsla með spjallrásir
    +    Svæði eru ný leið til að hópa fólk og spjallrásir.
    +    Bæta við fyrirliggjandi svæðum
    +    Bæta við fyrirliggjandi spjallrásum
    +    Yfirgefa svæði
    +    Bæta við spjallrásum
    +    Kanna spjallrásir
    +    Búa til svæði
    +    Ég og félagar í teyminu mínu
    +    Bara ég
    +    Einkasvæðið þitt
    +    Opinbera svæðið þitt
    +    Bæta við svæði
    +    Einkasvæði
    +    Opinbert svæði
    +    Uppfærir spjallrás í nýja útgáfu
    +    Búa til svæði
    +    Almenningsspjallrás
    +    Eyða auðkennismynd
    +    Það kom upp villa við að fletta upp símanúmerinu
    +    Sendir skilaboðið með snjókomu
    +    Sendir skilaboðið með skrauti
    +    Uppfærsla dulritunar tiltæk
    +    Sendir skilaboð sem óbreyttur texti án þess að túlka það sem markdown
    +    Dulritun ekki virk
    +    Skilaboð í þessari spjallrás eru enda-í-enda dulrituð.
    +    Kerfisstjóri netþjónsins þíns hefur lokað á sjálfvirka dulritun í einkaspjallrásum og beinum skilaboðum.
    +    Stillingar spjallrásar
    +    Skilaboð í þessari spjallrás eru ekki enda-í-enda dulrituð.
    +    Límmerki
    +    Útbý svæði…
    +    Settu inn vistfang spjallrásar
    +    Þetta vistfang er nú þegar í notkun
    +    Vistfang svæðis
    +    Eftir að kveikt er á dulritun er ekki hægt að slökkva á henni.
    +    Setur ( ͡° ͜ʖ ͡°) framan við hrein textaskilaboð
    +    Setur ¯\\_(ツ)_/¯ framan við hrein textaskilaboð
    +    Þetta lítur ekki út eins og gilt tölvupóstfang
    +    Skrá tölvupóstfang
    +    Sláðu inn vistfang netþjónsins sem þú vilt nota
    +    Sláðu inn vistfang Modular Element-þjóns eða netþjónsins sem þú vilt nota
    +    Vistfang fyrir Element Matrix þjónustur
    +    Skrá inn í %1$s
    +    Tengjast við %1$s
    +    Skrá inn með %s
    +    Teymi
    +    Vinir og fjölskylda
    +    Sendir skilaboðin sem stríðni
    +    Stillingar spjallrásar
    +    Hunsa notanda
    +    Snúa og skera utan af
    +    Límmerki
    +    Bæta við mynd frá
    +    Búa til nýja spjallrás
    +    Samþykktu þjónustuskilmála auðkennisþjónsins (%s) svo hægt sé að finna þig með tölvupóstfangi eða símanúmeri.
    +    Þú ert núna að deila tölvupóstföngum eða símanúmerum á auðkennisþjóninum %1$s. Þú þarft að tengjast aftur við %2$s til að hætta að deila þessu.
    +    Finnanleg tölvupóstföng
    +    Búa til nýja spjallrás
    +    Notaðu vélmenni, viðmótshluta og límmerkjapakka
    +    Sýsla með samþættingar
    +    Leyfðu \'Sýsla með samþættingar\' í stillingunum til að gera þetta.
    +    Samþættingar eru óvirkar
    +    Samþættingarstýring
    +    Leyfa samþættingar
    +    Sýna lyklaborð með tjáningartáknum
    +    Senda skilaboð með \'Enter\'
    +    Hefur ekki áhrif á boð/fjarlægingu/bönn.
    +    Birta taka-þátt og hætta skilaboð
    +    Notaðu samþættingarstýringu til að stýra vélmennum, viðmótshlutum og límmerkjapökkum.
    +\nSamþættingarstýringar taka við stillingagögnum og geta breytt viðmótshlutum, sent boð í spjallrásir, auk þess að geta úthlutað völdum fyrir þína hönd.
    +    Samþættingar
    +    Þetta er upphaf ferils beinna skilaboða með %s.
    +    Bein skilaboð
    +    Leita að heiti
    +    Leita eftir heiti, auðkenni eða tölvupóstfangi
    +    Nafn eða auðkenni (#example:matrix.org)
    +    Skoða spjallrásalistann
    +    Senda ný bein skilaboð
    +    Breytingar á skilaboðum
    +    Þjappa myndskeiði %d%%
    +    Þjappa mynd…
    +    Sendi skrá (%1$s / %2$s)
    +    Dulrita skrá…
    +    Öll samfélög
    +    Sýna fjarlægð skilaboð
    +    Þú átt engin fleiri ólesin skilaboð
    +    Boðið af %s
    +    Sendi þér boð
    +    Svara í spjallþræði
    +    Allir lyklar öryggisafritaðir
    +    Setja upp á þessu tæki
    +    Varið öryggisafrit
    +    Búa til svæði
    +    Einungis gegn boði, best fyrir þig og lítinn hóp
    +    Fara í fyrstu leskvittun
    +    Sannprófa þessa setu
    +    Þau samsvara ekki
    +    Þau samsvara
    +    Fela ítarlegt
    +    Birta ítarlegt
    +    Hreinsa öll gögn
    +    Þú hefur verið skráður út úr öllum setum og munt ekki lengur fá ýti-tilkynningar. Til að endurvirkja tilkynningar, þarf að skrá sig aftur inn á hverju tæki fyrir sig.
    +    Eigðu samtölin þín.
    +    Aftengja auðkennisþjón
    +    Umsagnir um svæði
    +    Birta frátökutákn fyrir fjarlægð skilaboð
    +    Eftir að þetta hefur verið virkjað, muntu geta sent staðsetninguna þína á hvaða spjallrás sem er
    +    Niðurstöður birtast einungis eftir að þú hefur lokað könnuninni
    +    Kjósendur sjá niðurstöðurnar þegar þeir hafa kosið
    +    
    +        Lokaniðurstöður byggðar á %1$d atkvæði
    +        Lokaniðurstöður byggðar á %1$d atkvæðum
    +    
    +    
    +        %1$d atkvæði greitt. Greiddu atkvæði til að sjá útkomuna
    +        %1$d atkvæði greidd. Greiddu atkvæði til að sjá útkomuna
    +    
    +    Næ ekki að tengjast heimaþjóni á þessari slóð, athugaðu slóðina
    +    réttur valkostur
    +    Spurning eða viðfangsefni
    +    Endurræstu forritið til að breytingin taki gildi.
    +    Virkja LaTeX-stærðfræði
    +    Tengja þetta tölvupóstfang við notandaaðganginn þinn
    +    
    +        %1$d til viðbótar
    +        %1$d til viðbótar
    +    
    +    Birta skilaboðablöðrur
    +    Mistókst að hlaða inn landakorti
    +    Myndgera staðsetningu notenda á tímalínunni
    +    Virkja deilingu staðsetninga
    +    ${app_name} gat ekki fengið staðsetninguna þína. Reyndu aftur síðar.
    +    ${app_name} gat ekki fengið staðsetninguna þína
    +    Lokuð könnun
    +    Ljúka könnun
    +    Ljúka þessari könnun\?
    +    Ljúka könnun
    +    Engin atkvæði greidd
    +    
    +        Byggt á %1$d atkvæði
    +        Byggt á %1$d atkvæðum
    +    
    +    
    +        %1$d atkvæði
    +        %1$d atkvæði
    +    
    +    
    +        Það þarf allavega %1$s valkost
    +        Það þarf allavega %1$s valkosti
    +    
    +    Spurning má ekki vera auð
    +    Set upp öryggisafrit af lykli
    +    Útbý öruggislykil úr lykilsetningu
    +    Lykilsetning endurheimtu
    +    Notaðu lykilsetningu endurheimtu eða dulritunarlykil
    +    Sýsla með í öryggisafriti dulritunarlykla
    +    Nota öryggisafrit af lykli
    +    Verja öryggisafrit
    +    Eyða öryggisafriti
    +    Athuga ástand öryggisafrits
    +    Eyði öryggisafriti…
    +    Öryggisafrit af lyklum er ekki virkt í þessari setu.
    +    
    +        %d nýjum lykli hefur verið bætt við þessa setu.
    +        %d nýjum lyklum hefur verið bætt við þessa setu.
    +    
    +    
    +        Endurheimti öryggisafrit með %d lykli.
    +        Endurheimti öryggisafrit með %d lyklum.
    +    
    +    Ef þú veist ekki lykilsetningu fyrir endurheimtu, geturðu %s.
    +    notað endurheimtulykilinn þinn
    +    Öryggisafrit er þegar til staðar á heimaþjóninum þínum
    +    (Ítarlegt) Settu upp með endurheimtulykli
    +    Bý til öryggisafrit
    +    Stilla lykilsetningu
    +    Verðu öryggisafritið þitt með lykilsetningu.
    +    Byrja að nota öryggisafrit dulritunarlykla
    +    Lykilsetning er of veik
    +    Settu inn lykilsetningu
    +    Lykilsetningar samsvara ekki
    +    Búa til lykilsetningu
    +    Útbúðu lykilsetningu til að dulrita útfluttu dulritunarlyklana. Þú þarft að setja inn sama lykilsetningu til að geta flutt aftur inn þessa dulritunarlykla.
    +    Renna til að ljúka símtalinu
    +    Tapaðu aldrei dulrituðum skilaboðum
    +    Endilega %s til að halda áfram að nota þessa þjónustu.
    +    Endilega %s til að fá þessi takmörk hækkuð.
    +    Þessi heimaþjónn er kominn fram yfir takmörk á mánaðarlega virkum notendum.
    +     Þessi heimaþjónn er kominn fram yfir takmörk á mánaðarlega virkum notendum þannig að sumir notendur munu ekki geta skráð sig inn.
    +    Þessi heimaþjónn er kominn fram yfir takmörk á tilföngum sínum.
    +    Þessi heimaþjónn er kominn fram yfir takmörk á tilföngum sínum þannig að sumir notendur munu ekki geta skráð sig inn.
    +    hafðu samband við kerfisstjóra þjónustunnar þinnar
    +    Þessi spjallrás er framhald af öðru samtali
    +    Samtalið heldur áfram hér
    +    Þessari spjallrás hefur verið skipt út og er hún ekki lengur virk.
    +    Til að halda áfram að nota %1$s heimaþjóninn þarftu að yfirfara og samþykkja skilmálana og kvaðir.
    +    Til að laga umsýslu Matrix-forrita
    +    Beiðni um deilingu dulritunarlykils
    +    Þú verður að samþykkja þjónustuskilmálana til að geta haldið áfram.
    +    Ræstu myndavél kerfisins í stað sérsniðna myndavélaskjásins.
    +    Þessi viðmótshluti vill nota eftirfarandi tilföng:
    +    Fara af fyrirliggjandi fjarfundi og skipta yfir í hinn\?
    +    Því miður, villa kom upp við að reyna að tengjast fjarfundinum
    +    Því miður, fjarfundasímtöl með Jitsi eru ekki studd á eldri tækjum (tæki með Android OS minna en 6.0)
    +    Afturkalla aðgang fyrir mig
    +    Mistókst að hlaða inn viðmótshluta.
    +\n%s
    +    Að nota það gæti deilt gögnum með %s:
    +    Að nota það gæti stillt vefkökur og deilt gögnum með %s:
    +    Ef þetta samsvarar ekki, getur verið að samskiptin þín séu berskjölduð.
    +    Staðfestu með því að bera eftirfarandi saman við \'Stillingar notanda\' í hinni setunni þinni:
    +    
    +        Tókst að flytja inn%1$d/%2$d dulritunarlykli.
    +        Tókst að flytja inn%1$d/%2$d dulritunarlyklum.
    +    
    +    Veldu hvaða svæði hafa aðgang að þessari spjallrás. Ef svæði er valið geta meðlimir þess fundið og tekið þátt í spjallrásinni.
    +    Hver sem er á svæði með þessari spjallrás getur fundið hana og tekið þátt í henni. Aðeins stjórnendur spjallrásarinnar geta bætt henni í svæði.
    +    Hver sem er getur látið vita af sér á spjallrásinni, meðlimir geta þá samþykkt eða hafnað
    +    Mistókst að fá sýnileika spjallrásar á spjallrásaskrá (%1$s).
    +    Svæði sem þú veist að innihalda þessa spjallrás
    +    Veldu hverjir geta fundið spjallrásina og tekið þátt.
    +    Svæði sem hafa aðgang
    +    Leyfa meðlimum svæðis að finna og fá aðgang.
    +    Meðlimir svæðisins %s geta fundið, forskoðað og tekið þátt.
    +    Óþekkt aðgangsstilling (%s)
    +    Birta þessa spjallrás opinberlega á skrá %1$s yfir spjallrásir\?
    +    Einungis meðlimir svæðis
    +    Hver sem er getur fundið svæðið og tekið þátt
    +    Hver sem er getur fundið spjallrásina og tekið þátt
    +    Birta falda atburði í tímalínu
    +    Hjálp og um
    +    Rödd og myndband
    +    Stillingar spjallrásar
    +    Umfjöllunarefni spjallrásar (valkvætt)
    +    Skipta um netkerfi
    +    Búa til nýtt svæði
    +    Viðbrögð
    +    Skoða viðbrögð
    +    Bæta við viðbrögðum
    +    Viðbrögð
    +    Þú hefur klárað að lesa allt!
    +    Skoða á spjallrás
    +    Breytir auðkennismyndinni þinni einungis í fyrirliggjandi spjallrás
    +    Breytir auðkennismyndinni einungis í fyrirliggjandi spjallrás
    +    Breytir birtu gælunafni þínu einungis í fyrirliggjandi spjallrás
    +    Fjarlægir notanda með uppgefið auðkenni úr þessari spjallrás
    +    Stilla umfjöllunarefni spjallrásar
    +    Gengur til liðs við spjallrás með uppgefnu vistfangi
    +    Býður notanda með uppgefið auðkenni í fyrirliggjandi spjallrás
    +    Stillir heiti spjallrásar
    +    Hunsar notanda, felur skilaboð viðkomandi fyrir þér
    +    Bannar notanda með uppgefið auðkenni
    +    Engir virkir viðmótshlutar
    +    Nota hljóðnemann
    +    Nota myndavélina
    +    Hlaða inn viðmótshluta
    +    Nýtt boð
    +    Netþjónninn þinn
    +    Útgáfa spjallrásar
    +    
    +        %d bannaður notandi
    +        %d bannaðir notendur
    +    
    +    %1$s, %2$s, %3$s og %4$s
    +    Villa í SSL.
    +    Veldu heimaþjón
    +    Skrá inn með einfaldri innskráningu (single sign-on)
    +    Nota sem sjálfgefið og ekki spyrja aftur
    +    Kveikja á HD
    +    Slökkva á HD
    +    Skipta á milli myndavéla
    +    Þráðlaus heyrnartól
    +    Tilkynning á spjallrás
    +    Notendur
    +    Tilkynna öllum á spjallrásinni
    +    Sýna minna
    +    Deila staðsetningu
    +    Búa til könnun
    +    Opna tengiliði
    +    Senda límmerki
    +    Hlaða inn skrá
    +    Senda myndir og myndskeið
    +    Opna myndavél
    +    Opna með
    +    Deila staðsetningu
    +    Landakort
    +    Deila staðsetningu
    +    Staðsetning
    +    Deila staðsetningu
    +    Tegund könnunar
    +    Breyta könnun
    +    Fjarlægja könnun
    +    Atkvæði greitt
    +    ÚTBÚA KÖNNUN
    +    BÆTA VIÐ VALKOSTI
    +    Búa til valkosti
    +    Búa til könnun
    +    %1$ds eftir
    +    Eyða upptöku
    +    Stöðva upptöku
    +    Uppfærsla er nauðsynleg
    +    Uppfæra
    +    Nafnlaus spjallrás
    +    Tillaga
    +    Ljúka uppsetningu
    +    Sleppa í bili
    +    Deila tengli
    +    Bjóða fólki
    +    Lýsing
    +    Slembið
    +    Almennt
    +    Einka
    +    Opinbert
    +    Eyða ósendum skilaboðum
    +    Mistókst
    +    Sent
    +    Sendi
    +    Atburður sendur!
    +    Ekkert efni
    +    Stöðulykill
    +    Tegund
    +    Senda sérsniðinn atburð
    +    Skoða stöðu spjallrásar
    +    Ekki tiltækt
    +    Ónettengt
    +    Nettengt
    +    Ekki tilkynna
    +    Ekki skoðað
    +    Athugað
    +    Valið
    +    Myndskeið
    +    Mynd
    +    Skjámynd
    +    Tókst ekki að auðkenna
    +    Óþekktur einstaklingur
    +    Notendur
    +    Flutningur
    +    Tengjast
    +    Virkt samtal (%1$s) ·
    +    
    +        Virkt samtal ·
    +        %1$d virk samtöl ·
    +    
    +    Ekkert svar
    +    Innhringing myndsamtals
    +    Innhringing raddsamtals
    +    Hringja til baka
    +    Þessu símtali er lokið
    +    Henda breytingum
    +    Breyta PIN-númeri
    +    Virkja PIN-númer
    +    Gleymt PIN-númer\?
    +    Settu inn PIN-númerið þitt
    +    Staðfestu PIN-númer
    +    
    +        %d færsla
    +        %d færslur
    +    
    +    Tengiliðaskrá
    +    KANNA NÁNAR
    +    NÁÐI ÞVÍ
    +    Stilla auðkennismynd
    +    Umfjöllunarefni
    +    Nafn spjallrásar
    +    Setja upp
    +    Ræsa myndavélina
    +    Stöðva myndavélina
    +    Kveikja á hljóðnema
    +    Þagga niður í hljóðnema
    +    Opna spjall
    +    Hlutverk
    +    Stilla hlutverk
    +    Senda inn
    +    Nota %1$s
    +    Núverandi tungumál
    +    Bjóða vinum
    +    Bjóða notendum
    +    BJÓÐA
    +    Ódulritað
    +    Frumstilla allt
    +    Gat ekki vistað myndefnisskrá
    +    Skilaboð…
    +    Leysa vandamál
    +    "Umfjöllunarefni: "
    +    Dulritun virk
    +    Ljúka
    +    Hætt við staðfestingu
    +    Endurlesa
    +    Staðfesta fjarlægingu
    +    Fjarlægja…
    +    Tenging við netþjón hefur rofnað
    +    Nei
    +    
    +    QR-kóði
    +    Endurstilla dulritunarlykla
    +    Treyst
    +    Setur
    +    Aðvörun
    +    Sannreynt
    +    Sannreyna
    +    óstöðug
    +    stöðug
    +    Sjálfgefin útgáfa
    +    Útgáfa á þjóni
    +    Heiti þjóns
    +    Virkja dulritun
    +    Virkja dulritun\?
    +    Tímalína
    +    Hætta að hunsa
    +    Notendur
    +    Boðsgestir
    +    Sérsniðið
    +    Umsjónarmenn
    +    Fara út
    +    Fara af spjallrás
    +    Innsendingar
    +    Tilkynningar
    +    Stillingar
    +    Meira
    +    Kanna nánar
    +    Öryggi
    +    Bíð…
    +    Könnun
    +    Skrá
    +    Tal
    +    Hljóð
    +    Mynd.
    +    Myndskeið.
    +    Virkja dulritun
    +    Núverandi seta
    +    Stillingar
    +    Ítarlegar stillingar
    +    Lýsingin er of stutt
    +    Hreinsa gögn
    +    Lykilorð
    +    Skrá inn
    +    Skrá inn
    +    Matrix-auðkenni
    +    Aðvörun
    +    Næsta
    +    Lykilorð
    +    Notandanafn
    +    Notandanafn eða tölvupóstfang
    +    Næsta
    +    Settu inn kóða
    +    Staðfestu símanúmer
    +    Næsta
    +    Símanúmer (valfrjálst)
    +    Símanúmer
    +    Næsta
    +    Tölvupóstfang (valfrjálst)
    +    Tölvupóstur
    +    Könnuninni er lokið
    +    Valkostur %1$d
    +    Spurning eða viðfangsefni könnunar
    +    Aðvörun
    +    Lykilorðið þitt hefur verið endurstillt.
    +    Tókst!
    +    Ég hef staðfest tölvupóstfangið mitt
    +    Halda áfram
    +    Aðvörun!
    +    Tölvupóstur
    +    Næsta
    +    Vistfang
    +    Hreinsa vinnsluferil
    +    Skrá inn
    +    Nýskrá
    +    Halda áfram
    +    einfaldri innskráningu (single sign-on)
    +    Skrá inn með %s
    +    Halda áfram með %s
    +    Eða
    +    Annað
    +    Kanna nánar
    +    Ég er nú þegar með notandaaðgang
    +    Stofna aðgang
    +    Komast í gang
    +    Tengjast þjóni
    +    Samfélög
    +    Ólesin skilaboð
    +    Fjarlægja úr eftirlætum
    +    Bæta í eftirlæti
    +    Stillingar
    +    Þagga niður
    +    Aðeins minnst á
    +    Öll skilaboð
    +    Öll skilaboð (hávært)
    +    Tilkynnt sem óviðeigandi
    +    Tilkynnt sem ruslpóstur
    +    Efni tilkynnt
    +    KÆRA
    +    Ástæður fyrir kæru á þessu efni
    +    Kæra þetta efni
    +    Þetta er óviðeigandi
    +    Þetta er ruslpóstur
    +    %1$s kl. %2$s
    +    SKRÁR
    +    MYNDEFNI
    +    %1$d af %2$d
    +    Staðsetning
    +    Könnun
    +    Myndasafn
    +    Myndavél
    +    Tengiliður
    +    Skrá
    +    Opna leiðsagnarsleðann
    +    Kóði
    +    Auðkennisþjónn
    +    Þjónustuskilmálar
    +    Skoða breytingaskrá
    +    Tillögur
    +    QR-kóði
    +    Tengill afritaður á klippispjald
    +    (breytt)
    +    Bíð…
    +    Bein skilaboð
    +    Gefðu umsögn
    +    Umsagnir
    +    Kerfisstillingar
    +    Útgáfur
    +    Hjálp og aðstoð
    +    Hjálp
    +    Snið:
    +    Slóð:
    +    Öryggi og gagnaleynd
    +    Kjörstillingar
    +    Almennt
    +    Opinbert
    +    Umfjöllunarefni
    +    Heiti
    +    Nafn spjallrásar
    +    ÚTBÚA
    +    Bein skilaboð
    +    Spjallrásir
    +    Bíddu aðeins…
    +    Búa til nýja spjallrás
    +    Skilaboðum eytt
    +    Spjallrásir
    +    Samtöl
    +    Reyna aftur
    +    Svara
    +    Breyta
    +    Óþekkt villa
    +    Náði því
    +    Sannreynt!
    +    Undirritun
    +    Reiknirit
    +    Útgáfa
    +    Endurheimta úr öryggisafriti
    +    Ertu viss\?
    +    Óvænt villa
    +    Stöðva
    +    Skipta út
    +    Vista sem skrá
    +    Deila
    +    Lokið
    +    Tókst !
    +    (Ítarlegt)
    +    %d+
    +    %1$s: %2$s
    +    fella saman
    +    fletta út
    +    Settu inn lykilorðið þitt.
    +    Settu inn notandanafn.
    +    Þögult
    +    Breytir birtu gælunafni þínu
    +    Fara af spjallrás
    +    Birtir aðgerð
    +    Hunsa
    +    Deila
    +    Lesa DRM-varið myndefni
    +    Leyfa
    +    Auðkenni spjallrásar
    +    Opna í vafra
    +    Endurlesa viðmótshluta
    +    Viðmótshluti
    +    Virkir viðmótshlutar
    +    SKOÐA
    +    %1$s: %2$s
    +    Ég
    +    Ný skilaboð
    +    Spjallrás
    +    Nýr atburður
    +    %1$s og %2$s
    +    
    +        %d tilkynning
    +        %d tilkynningar
    +    
    +    Bæta við nýjum þjóni
    +    Opinbert
    +    Einka
    +    Staðvær vistföng
    +    Gefa út
    +    Aðgangur að spjallrás
    +    Stillingar notandaaðgangs
    +    Veldu
    +    Veldu
    +    Myndefni
    +    Lykilorð
    +    hér
    +    Forskoða myndefni fyrir sendingu
    +    
    +        %d sekúnda
    +        %d sekúndur
    +    
    +    Skilaboð frá vélmennum
    +    Boð á spjallrás
    +    Stikkorð
    +    \@spjallrás
    +    Hópskilaboð
    +    Bein skilaboð
    +    Notandanafnið mitt
    +    Birtingarnafn mitt
    +    Virkja í ræsingu
    +    Bæta við notandaaðgangi
    +    Sérsniðnar stillingar.
    +    Virkja
    +    Setustillingar.
    +    Virkja
    +    Stillingar notandaaðgangs.
    +    Opna stillingar
    +    Kerfisstillingar.
    +    Annað
    +    Sjálfgefnar tilkynningar
    +    Ítarlegar stillingar á tilkynningum
    +    Fjarlægja %s\?
    +    Símanúmer
    +    Tölvupóstföng
    +    Ekkert
    +    Sía
    +    Spjallþræðir
    +    Spjallþráður
    +    
    +        %d valið
    +        %d valið
    +    
    +    Breyta stillingum
    +    Bjóða notendum
    +    Heimildir
    +    %1$s og %2$s
    +    Taka notanda úr banni
    +    Banna notanda
    +    Fjarlægja notanda
    +    Lækka niður um stig
    +    Ekkert svar
    +    Bíða
    +    Halda áfram
    +    Símtöl
    +    Alltaf spyrja
    +    Aftan
    +    Fram
    +    Heyrnartól
    +    Hátalari
    +    Sími
    +    Svæði
    +    Skrá yfir spjallrásir
    +    Ekki fleiri niðurstöður
    +    Tilkynningar
    +    Nýtt gildi
    +    Tókst
    +    Villa
    +    Endurstilla
    +    Hafna
    +    Spila
    +    Aftengjast
    +    Afturkalla
    +    Sækja
    +    Hafna
    +    Hunsa
    +    Sleppa
    +    Samþykkja
    +    Breyta
    +    Samþykki
    +    Ekki núna
    +    Virkja
    +    Skipta um
    +    Bæta við
    +    Ýttu til að breyta svæðum
    +    Veldu svæði
    +    Hætta að birta þetta vistfang
    +    Birta þetta vistfang
    +    Bæta við staðværu vistfangi
    +    Þessi spjallrás er ekki með nein staðvær vistföng
    +    Stilltu vistföng fyrir þessa spjallrás svo notendur geti fundið hana í gegnum heimaþjóninn þinn (%1$s)
    +    Nýtt birt vistfangs (t.d. #samnefni:netþjónn)
    +    Engin önnur birt vistföng ennþá.
    +    Engin önnur birt vistföng ennþá, bættu einu við hér fyrir neðan.
    +    Eyða vistfanginu \"%1$s\"\?
    +    Hætta að birta vistfangið \"%1$s\"\?
    +    Birta nýtt vistfang handvirkt
    +    Önnur birt vistföng:
    +    Þetta er aðalvistfangið
    +    Birt vistföng getur hvaða einstaklingur eða netþjónn sem er notað til að taka þátt í spjallrásinni þinni. Til að birta vistfang, þarf fyrst að stilla það sem staðvært vistfang.
    +    Birt vistföng
    +    Sjá og sýsla með vistföng þessa svæðis.
    +    Vistföng svæða
    +    Sjá og sýsla með vistföng þessarar spjallrásar og sýnileika hennar í spjallrásaskránni.
    +    Vistföng spjallrása
    +    Leyfa gestum að taka þátt
    +    Aðgangur að svæði
    +    Hver hefur aðgang\?
    +    Láta mig vita fyrir
    +    Uppgötvun
    +    Uppfærslur spjallrásar
    +    Skilaboð sem innihalda @spjallrás
    +    Þegar spjallrásir eru uppfærðar
    +    Heimildir svæðis
    +    Ástæða fyrir banni
    +    Ástæða fjarlægingar
    +    Hætta við boð
    +    Hætta að hunsa notanda
    +    Hunsa notanda
    +    Lækka þig sjálfa/n í tign\?
    +    Fjarlægja úr spjalli
    +    Hætta við boð
    +    Notandinn sem þú hringdir í er upptekinn.
    +    Þetta er ekki gilt vistfang á Matrix-þjóni
    +    Tillögur að spjallrásum
    +    Skoða á spjallrás
    +    Þú breyttir vistföngum fyrir þessa spjallrás.
    +    %1$s breytti vistföngum fyrir þessa spjallrás.
    +    Þú breytti aðal- og varavistföngunum fyrir þessa spjallrás.
    +    %1$s breytti aðal- og varavistföngunum fyrir þessa spjallrás.
    +    Þú breyttir varavistfanginu fyrir þessa spjallrás.
    +    %1$s breytti varavistfanginu fyrir þessa spjallrás.
    +    Þú fjarlægðir aðalvistfang spjallrásarinnar.
    +    %1$s fjarlægði aðalvistfang spjallrásarinnar.
    +    %1$s stillti aðalvistfang spjallrásarinnar sem %2$s.
    +    Þú bættir við %1$s og fjarlægðir %2$s sem vistföng fyrir þessa spjallrás.
    +    %1$s bætti við %2$s og fjarlægði %3$s sem vistföng fyrir þessa spjallrás.
    +    
    +        Þú fjarlægðir %1$s sem vistfang fyrir þessa spjallrás.
    +        Þú fjarlægðir %1$s sem vistföng fyrir þessa spjallrás.
    +    
    +    
    +        %1$s fjarlægði %2$s sem vistfang fyrir þessa spjallrás.
    +        %1$s fjarlægði %2$s sem vistföng fyrir þessa spjallrás.
    +    
    +    
    +        Þú bættir við %1$s sem vistfangi fyrir þessa spjallrás.
    +        Þú bættir við %1$s sem vistföngum fyrir þessa spjallrás.
    +    
    +    
    +        %1$s bætti við %2$s sem vistfangi fyrir þessa spjallrás.
    +        %1$s bætti við %2$s sem vistföngum fyrir þessa spjallrás.
    +    
    +    Breyta umfjöllunarefni
    +    Uppfæra svæðið
    +    Uppfæra spjallrásina
    +    Senda m.room.server_acl atburði
    +    Breyta heimildum
    +    Breyta nafni svæðis
    +    Breyta nafni spjallrásar
    +    Breyta sýnileika ferils
    +    Virkja dulritun svæðis
    +    Virkja dulritun spjallrásar
    +    Skipta um aðalvistfang svæðisins
    +    Skipta um aðalvistfang spjallrásarinnar
    +    Skipta um táknmynd svæðis
    +    Skipta um auðkennismynd spjallrásar
    +    Breyta viðmótshlutum
    +    Tilkynna öllum
    +    Fjarlægja skilaboð send af öðrum
    +    Banna notendur
    +    Fjarlægja notendur
    +    Senda skilaboð
    +    Sjálfgefið hlutverk
    +    %1$s, %2$s og aðrir
    +    Hringing…
    +    Afrita
    +    Merkja sem lesið
    +    Ertu viss um að þú viljir skrá þig út\?
    +    Leggja á
    +    Hafna
    +    Samþykkja
    +    Lokið
    +    Svæði
    +    Hefja spjall
    +    Ekkert
    +    Ertu viss\?
    +    Öryggisafrit af lykli
    +    Sjálfgefið í kerfinu
    +    Sendi skilaboð…
    +    Skilaboð send
    +    Tóm spjallrás (var %s)
    +    
    +        %1$s, %2$s, %3$s og %4$d til viðbótar
    +        %1$s, %2$s, %3$s og %4$d til viðbótar
    +    
    +    %1$s, %2$s og %3$s
    +    Sérsniðið
    +    Sérsniðið (%1$d)
    +    Sjálfgefið
    +    Umsjónarmaður
    +    %1$s bauð %2$s
    +    Engin breyting.
    +    %1$s gekk í hópinn
    +    Boðið þitt
     
    \ No newline at end of file
    diff --git a/vector/src/main/res/values-iw/strings.xml b/vector/src/main/res/values-iw/strings.xml
    index 73a57823eb..b70034888f 100644
    --- a/vector/src/main/res/values-iw/strings.xml
    +++ b/vector/src/main/res/values-iw/strings.xml
    @@ -203,7 +203,7 @@
         יעדי התראות
         ניהול מפתחות קריפטוגרפיה
         קריפטוגרפיה
    -    השתמש במנהל שילוב לניהול בוטים, גשרים, ווידג\'טים וחבילות מדבקות.
    +    השתמש במנהל אינטגרציה לניהול בוטים, גשרים, ווידג\'טים וחבילות מדבקות.
     \nמנהלי אינטגרציה מקבלים נתוני תצורה ויכולים לשנות ווידג\'טים, לשלוח הזמנות לחדר ולהגדיר רמות כוח מטעמכם.
         אינטגרציות
         מתקדם
    @@ -440,7 +440,7 @@
         חסום הכל
         אפשר
         יישומון זה רוצה להשתמש במשאבים הבאים:
    -    מצטערים, שיחות ועידה עם Jitsi אינן נתמכות במכשירים ישנים (מכשירים עם מערכת הפעלה אנדרואיד מתחת ל -5.0)
    +    מצטערים, שיחות ועידה עם Jitsi אינן נתמכות במכשירים ישנים (מכשירים עם מערכת הפעלה אנדרואיד מתחת ל -6.0)
         מזהה חדר
         מזהה ישומון
         ערכת הנושא שלכם
    @@ -515,7 +515,7 @@
         
         כל החדרים המקומיים %s
         כל החדרים בשרת %s
    -    כתובת אתר של שרת בית
    +    שם השרת
         בחר מדריך חדרים
         אם הם לא תואמים, אבטחת התקשורת שלך עלולה להיפגע.
         אשרו על ידי השוואה בין הדברים הבאים להגדרות המשתמש בפגישה האחרת שלכם:
    @@ -686,7 +686,7 @@
         בועט משתמש עם מזהה נתון
         הגדר את נושא החדר
         עזוב חדר
    -    מצטרף לחדר עם כינוי נתון
    +    מצטרף לחדר עם כתובת ידועה
         מזמין משתמש עם זיהוי נתון לחדר הנוכחי
         משתמש מבוטל עם מזהה נתון
         הגדר את רמת ההרשאה של המשתמש
    @@ -705,7 +705,7 @@
         רטוט בעת אזכור משתמש
         כולל שינויים באווטאר ושמות תצוגה.
         הצג אירועי חשבון
    -    הזמנות, בעיטות ואיסורים אינם מושפעים.
    +    הזמנות, הסרות ואיסורים אינם מושפעים.
         הראה אירועי הצטרפות ועזיבה
         לחץ על אישורי הקריאה לרשימה מפורטת.
         הצג קבלות הצג קבלות קריאה
    @@ -2099,8 +2099,8 @@
         
             שיחה פעילה ·
             %1$d שיחות פעילות ·
    -        
    -        
    +        
    +        
         
         שיחה פעילה (%1$s)
         אירעה שגיאה בחיפוש מספר הטלפון
    @@ -2124,17 +2124,17 @@
         להודיע לכל החדר
         
             %1$d יותר
    -        
    -        
    -        
    +        
    +        
    +        
         
         %1$s, %2$s ואחרים
         %1$s ו %2$s
         
             %d שינוי ברשימות ACL בשרתים
    -        
    -        
    -        
    +        
    +        
    +        
         
         נהל חדרים
         החלט מי יכול לראות ולהצטרף לחדר זה.
    @@ -2175,4 +2175,246 @@
         שם המשתמש שלי
         הזמנות לחדר
         מילות מפתח
    +    הזמן אל %s
    +    הזמן אנשים
    +    הזמן אנשים למרחב שלך
    +    תֵאוּר
    +    יוצר מרחב…
    +    אַקרַאִי
    +    ראשי
    +    בואו ניצור חדר לכל אחד מהם. אתה יכול להוסיף עוד מאוחר יותר, כולל אלה שכבר קיימים.
    +    על איזה דברים אתה עובד\?
    +    ודא שלאנשים הנכונים יש גישה לחברה %s. תוכל להזמין עוד מאוחר יותר.
    +    מי הם חבריך לצוות\?
    +    אנחנו ניצור עבורם חדרים. אתה יכול להוסיף עוד מאוחר יותר גם.
    +    איזה דיונים אתה רוצה לקיים ב-%s\?
    +    תן לזה שם כדי להמשיך.
    +    הוסף כמה פרטים כדי לשפר לזיהוי. אתה יכול לשנות את זה בכל שלב.
    +    הוסף כמה פרטים כדי לבלוט. אתה יכול לשנות את זה בכל שלב.
    +    צור מרחב
    +    הזמן רק, הכי טוב לעצמך או לצוותים שלך
    +    פְּרָטִי
    +    פתוח לכל אחד, הכי מתאים לקהילות
    +    צִבּוּרִי
    +    מרחב פרטי עבורך ועבור חברי הצוות שלך
    +    אני וחברי הצוות
    +    מרחב פרטי לארגון החדרים שלך
    +    רק אני
    +    ודא שלאנשים הנכונים יש גישה אל %s.
    +    עם מי אתה עובד\?
    +    כדי להצטרף למרחב קיים, אתה צריך הזמנה.
    +    אתה יכול לשנות את זה מאוחר יותר
    +    איזה סוג מרחב אתה רוצה ליצור\?
    +    המרחב הפרטי שלך
    +    המרחב הציבורי שלך
    +    הוסף מרחב
    +    מרחב אישי
    +    מרחב ציבורי
    +    האם אתה בטוח שברצונך למחוק את כל ההודעות שלא נשלחו בחדר הזה\?
    +    מחק הודעות שלא נשלחו
    +    שליחת ההודעות נכשלה
    +    האם ברצונך לבטל את שליחת ההודעה\?
    +    מחק את כל ההודעות שנכשלו
    +    נִכשָׁל
    +    נשלח
    +    שְׁלִיחָה
    +    משדרג חדר לגרסה חדשה
    +    עזוב את החדר עם מזהה נתון (או החדר הנוכחי אם ריק)
    +    הצטרף למרחב עם המזהה הנתון
    +    הוסף למרחב הנתון
    +    צור מרחב
    +    תוכן האירוע
    +    מצב האירוע נשלח!
    +    האירוע נשלח!
    +    אירוע שגוי
    +    חסר סוג הודעה
    +    אין תוכן
    +    תוכן האירוע
    +    מצב מפתח
    +    סוג
    +    שלח אירוע מצב מותאם אישית
    +    ערוך תוכן
    +    מצב אירוע
    +    שלח מצב אירוע
    +    שלח אירוע מותאם אישית
    +    חקור מצב חדר
    +    כלי מפתח
    +    לא זמין
    +    לא על הקו
    +    על הקו
    +    חדר ציבורי
    +    צפה באישורי קריאה
    +    אל תתריעה
    +    מתריעה ללא קול
    +    מתריעה עם צליל
    +    ההודעה לא נשלחה עקב שגיאה
    +    לא בָּדוּק
    +    בָּדוּק
    +    הזמינו בדואר אלקטרוני
    +    זה רק אתה כרגע. %s יהיה טוב יותר עם אחרים.
    +    בחלל זה אין חדרים
    +    אנא צור קשר עם מנהל השרת הביתי שלך לקבלת מידע נוסף
    +    נראה שהשרת הביתי שלך עדיין לא תומך במרחבים
    +    מרגיש ניסיוני\?
    +\nניתן להוסיף חללים קיימים למרחב.
    +    הצג את כל החדרים בדף הבית
    +    כל החדרים שבהם אתה נמצא יוצגו בדף הבית.
    +    ניהול חדרים ומרחבים
    +    סמן כלא מוצע
    +    מוצע
    +    סמן כמוצע
    +    מחפש מישהו שאינו ב-%s\?
    +    %s מזמין אותך
    +    הערה: האפליקציה תופעל מחדש
    +    אפשר הודעות שרשור
    +    המערכת שלך תשלח אוטומטית יומנים כאשר מתרחשת שגיאה ללא יכולת לפענח
    +    דווח אוטומטית על שגיאות פענוח.
    +    אתם מוזמנים
    +    מרחבים הם דרך חדשה לאיחוד של חדרים ואנשים.
    +    הוסף מרחב לכל מרחב שאתה מנהל.
    +    הוסף מרחבים קיימים
    +    הוסף חדרים קיימים
    +    הוסף חדרים ומרחבים קיימים
    +    בחר דברים להשאיר
    +    השאירו חדרים ומרחבים ספציפיים…
    +    אל תעזובו חדרים וא מרחבים
    +    עזבו את כל החדרים והמרחבים
    +    אתה המנהל היחיד של המרחב הזה. עזיבה תגרום לכך שאף אחד לא ישלוט על המרחב.
    +    לא תוכל להצטרף מחדש אלא אם כן תוזמן מחדש.
    +    אתה האדם היחיד כאן. אם תעזוב, אף אחד לא יוכל להצטרף בעתיד, כולל אותך.
    +    האם אתה בטוח שברצונך לעזוב את %s\?
    +    עזוב את המרחב
    +    הוסיפו חדרים
    +    גלה חדרים
    +    
    +        אדם %d שאתה מכיר כבר הצטרף
    +        %d אנשים שאתה מכיר כבר הצטרפו
    +        %d אנשים שאתה מכיר כבר הצטרפו
    +        %d אנשים שאתה מכיר כבר הצטרפו
    +    
    +    גילוי (%s)
    +    סיים את ההגדרה
    +    הזמן בדואר אלקטרוני, מצא אנשי קשר ועוד…
    +    סיים להגדיר את הגילוי.
    +    אינך משתמש כעת בשרת זהות. על מנת להזמין חברים לצוות ולהיות ניתנים לגילוי על ידם, הגדר שרת זהות למטה.
    +    כינוי זה אינו נגיש בשלב זה.
    +\nנסה שוב מאוחר יותר, או בקש ממנהל חדר לבדוק אם יש לך גישה.
    +    הם לא יהיו חלק מ-%s
    +    רק לחדר הזה
    +    הם יוכלו לחקור את %s
    +    הזמן אל %s
    +    שתף קישור
    +    הזמן לפי שם משתמש או דואר אלקטרוני
    +    ההצפנה הוגדרה בצורה שגויה.
    +    אמת על ידי השוואת אימוג\'י
    +    סרוק עם המכשיר הקיים
    +    סרוק את הקוד עם המכשיר האחר או החלף וסרוק את הקוד מהמכשיר הקיים
    +    קול
    +    יצירת מרחב…
    +    כתובת מרחב
    +    קידומת ( ͡° ͜ʖ ͡°) להודעת טקסט רגילה
    +    הראה קצת מידע שימושי לעזור לטיפול בבאגים ביישום
    +    הראה מידע על באגים במסך
    +    נראה שכתובת הדואר האלקטרוני אינה תקפה
    +    התחבר לשרת
    +    מחפש להצטרף לשרת קיים \?
    +    עדיין לא בטוח \? אתה יכול %s
    +    קהילות
    +    צוותים
    +    חברים ומשפחה
    +    נעזור לך להיות מחובר.
    +    עם מי תדברו הכי הרבה\?
    +    הצפנה קצה לקצה ולא נדרש מספר טלפון. אין פרסומות או כריית מידע.
    +    בחירת מקום שמירת השיחה שלך , נותנת לך בקרה ועצמאות . מחובר דרך מטריקס.
    +    תקשורת מאובטחת ועצמאית המעניקה לך את אותה רמת פרטיות כמו שיחה פנים אל פנים בבית שלך.
    +    הודעות לקבוצתך.
    +    אתה בבקרה.
    +    שיחות בבעלותך.
    +    הגדרות חדר
    +    סקר
    +    הקובץ גדול מדי להעלאה.
    +    האם אתה מאשר את שליחת המידע \?
    +    שליחת דואר אלקטרוני ומספרי טלפון אל %s
    +    רשימת אנשי הקשר שלך הינה פרטית. לחשיפת משתמשים מרשימת אנשי הקשר שלך , נדרש אישורך לשליחת מידע על איש הקשר לשרת ההזדהות.
    +    לא סופקה מדיניות על ידי שרת הזדהות
    +    הסתר מדיניות שרת הזדהות
    +    הראה מדיניות שרת הזדהות
    +    פתח הגדרות
    +    משוב על מרחבים
    +    הגדרות מערכת
    +    גרסאות
    +    קבלת עזרה בשימוש ${app_name}
    +    עזרה ותמיכה
    +    עזרה
    +    משפטים
    +    אתה צופה בת\'רד זה!
    +    יצירת מרחב חדש
    +    צפייה בחדר
    +    מענה בת\'רד
    +    הצגת מידע על המשתמש
    +    משנה את האווטר שלך בחדר הנוכחי בלבד
    +    משנה את האווטר בחדר הנוכחי
    +    משנה את הכינוי בחדר הנוכחי בלבד
    +    הפקודה \"%s\" מזוהה אך אינה נתמכת.
    +    עזרו לנו לזהות ולשפר את ${app_name} על ידי שיתוף השימוש בצורה אנונימי. להבנת השימוש של אנשים במכשירים שונים , אנחנו נחולל מזהה אקראי , אשר ישותף על ידי המכשירים שלך .
    +\n
    +\n ניתן לקרוא את כל התנאים %s.
    +    
    +        התוצאה הסופית מתבססת על הצבעה %1$d
    +        
    +        התוצאה הסופית מתבססת על הצבעות %1$d
    +        
    +    
    +    בחר אילו מרחבים יכולים לגשת לחדר זה. בבחירת מרחב החברים יוכלו למצוא ולהצטרף לחדר.
    +    חברים במרחב %s יכולים למצוא , לצפות ולהצטרף.
    +    כל אחד במרחב בחדר זה יכול להצטרף. רק מנהלי מערכת של החדר יכולים להוסיף למרחב.
    +    אנחנו  לא  משתפים מידע עם חברות צד שלישי
    +    אנחנו  לא מקליטים או עושים פרופיל מנתוני החשבון
    +    הפסק להתעלם ממשתמש , אשר הודעותיו נראות
    +    הגדר את שם החדר
    +    התעלם ממשתמש , אשר מעלים הודעות ממך
    +    האם מאושרת עזיבת שיחת הועידה הנוכחית ומעבר לשיחת ועידה אחרת \?
    +    מצטערים , אירעה שגיאה בניסיון הצטרפות לשיחת הועידה
    +    השרת כבר נמצא ברשימה
    +    לא ניתן למצוא את השרת או את רשימת החדשים
    +    הכנס את שם השרת החדש שיחשף
    +    הוספת שרת חדש
    +    השרת שלך
    +    גרסת חדר
    +    מרחבים או חדרים שאינכם מכירים
    +    מרחב שמכיל את החדר
    +    בחרו מרחבים
    +    מרחבים פתוחים
    +    אפשרו לחברים במרחב להצטרף.
    +    מרחב לחברים בלבד
    +    כל אחד יכול למצוא את המרחב ולהצטרף
    +    כל אחד יכול להצטרף
    +    ציבורי
    +    רק מוזמנים יכולים להצטרף
    +    פרטי (מוזמן בלבד)
    +    פרטי
    +    הגדרת גישה לא ידועה(%s)
    +    כל אחד יכול לבקש להצטרף לחדר , החברים יוכלו לאשר או לדחות זאת
    +    סדר כתובות במרחב זה.
    +    מרחב כתובות
    +    אפשר לאורחים להצטרף
    +    מרחב גישה
    +    למי יש גישה\?
    +    הגדרות חשבון
    +    ניתן לסדר את ההתראות ב-%1$s.
    +    שימו לב שהתראות על מילים מוזכרות לא זמינות בחדרים מוצפנים בטלפון הנייד
    +    הודע לי עבור
    +    אין מדיניות לשרת זה
    +    ספריות חברות צד שלישי
    +    מדיניות שרת ההזדהות שלך
    +    מדיניות השרת הביתי שלך
    +    מדיניות ${app_name}
    +    ניתן לסגור בכל זמן בהגדרות
    +    כאן
    +    עזרה בשיפור ${app_name}
    +    לא תתקבלנה התראות על מילים המוזכרות בחדרים מוצפנים בטלפון הנייד.
    +    הודעות על ידי בוט
    +    חדר@
    +    הודעות קבוצתיות
    +    הודעות ישירות מוצפנות
     
    \ No newline at end of file
    diff --git a/vector/src/main/res/values-pt-rBR/strings.xml b/vector/src/main/res/values-pt-rBR/strings.xml
    index cd237407d2..f2eaf8d993 100644
    --- a/vector/src/main/res/values-pt-rBR/strings.xml
    +++ b/vector/src/main/res/values-pt-rBR/strings.xml
    @@ -1414,7 +1414,7 @@
         Quase lá! %s está mostrando um tick (✓)\?
         Sim
         Não
    -    A conexão com o servidor foi perdida
    +    Conectividade ao servidor tem sido perdida
         Modo avião está ligado
         Ferramentas Dev
         Dados de Conta
    diff --git a/vector/src/main/res/values-ru/strings.xml b/vector/src/main/res/values-ru/strings.xml
    index 465bbbb28c..9a925ecaa7 100644
    --- a/vector/src/main/res/values-ru/strings.xml
    +++ b/vector/src/main/res/values-ru/strings.xml
    @@ -39,7 +39,6 @@
         Приглашение в комнату
         %1$s и %2$s
         Пустая комната
    -
         Начальная синхронизация:
     \nИмпорт учетной записи…
         Начальная синхронизация:
    @@ -258,7 +257,6 @@
         Удалить
         Переименовать
         Пожаловаться на содержимое
    -
         или
         Приглашение
         Выйти из учётной записи
    @@ -281,7 +279,6 @@
         Только Matrix контакты
         Нет результатов
         Комнаты
    -
         Отправить логи
         Отправить журналы ошибок
         Отправить снимок экрана
    @@ -309,11 +306,9 @@
         Это не похоже на действительный адрес электронной почты
         Этот адрес электронной почты уже используется.
         Забыли пароль?
    -
         Этот домашний сервер хочет убедиться, что вы не робот
         Должен быть введен адрес электронной почты привязанный к учетной записи.
         Не удалось проверить адрес электронной почты: убедитесь, что вы перешли по ссылке из сообщения
    -
         Пожалуйста, введите корректный URL
         Неверный формат JSON
         Не содержит допустимого JSON
    @@ -329,12 +324,10 @@
         Идёт разговор…
         Вызываемый абонент не смог ответить.
         Информация
    -
         ${app_name} необходимы разрешения на доступ к микрофону, чтобы выполнять звонки.
         ${app_name} необходимы разрешения на доступ к камере и микрофону для видеовызовов.
     \n
     \nПожалуйста дайте разрешение в следующем окне для звонка.
    -
         ДА
         НЕТ
         Продолжить
    @@ -342,7 +335,6 @@
         Присоединиться
         Отклонить
         Перейти к непрочитанному
    -
         Покинуть комнату
         Вы уверены, что хотите выйти из комнаты\?
         ПРЯМЫЕ СООБЩЕНИЯ
    @@ -369,7 +361,6 @@
         Сертификат был изменен с того, которому доверял ваш телефон. Это ОЧЕНЬ НЕОБЫЧНО. Рекомендуется НЕ ПРИНИМАТЬ этот новый сертификат.
         Сертификат изменился с ранее доверенного на недействительный. Возможно, сервер обновил свой сертификат. Свяжитесь с администратором сервера для получения ожидаемого отпечатка сертификата.
         Примите сертификат только если администратор сервера опубликовал отпечаток сертификата, который соответствует указанному выше.
    -
         Поиск
         Фильтр списка пользователей
         Нет результатов
    @@ -414,7 +405,6 @@
         Обновить публичное имя
         Недавно
         %1$s @ %2$s
    -
         Аутентификация
         Авторизован как
         Домашний сервер
    @@ -445,7 +435,6 @@
         Это экспериментальные функции, которые могут повести себя неожиданным образом. Используйте с осторожностью.
         Установить как основной адрес
         Сбросить основной адрес
    -
         Ошибка дешифровки
         Публичное имя
         ID сессии
    @@ -456,7 +445,6 @@
         Экспорт
         Введите парольную фразу
         Подтвердите парольную фразу
    -
         Импорт E2E ключей комнаты
         Импорт ключей комнаты
         Импортировать ключи из локального файла
    @@ -468,7 +456,6 @@
         Подтвердить
         Чтобы убедиться, что этой сессии можно доверять, обратитесь к ее владельцу, используя другие способы (например, лично или по телефону), и спросите, соответствует ли ключ, который он видит в настройках для этой сессии:
         Если они не совпадают, безопасность вашего общения может быть поставлена под угрозу.
    -
         Выбор каталога комнат
         Имя сервера
         Все комнаты на сервере %s
    @@ -545,7 +532,6 @@
         Причина: %1$s
         Встряхните устройство, чтобы сообщить об ошибке
         Список участников
    -
         
             %d комната
             %d комнаты
    @@ -560,7 +546,6 @@
             
         
         Аватар
    -
         
             %d участник
             %d участника
    @@ -573,7 +558,6 @@
             %d новых сообщений
             
         
    -
         
             %d изменение членства
             %d изменения членства
    @@ -586,7 +570,6 @@
             %d непрочитанных уведомлений
             
         
    -
         Чутье
         Отправить стикер
         Отправить стикер
    @@ -633,18 +616,12 @@
         Нажмите здесь для просмотра старых сообщений
         Присоединиться к комнате с указанным адресом
         Для исправления управления приложениями Matrix
    -
    -
    -
    -
         
             %d выбран
             %d выбрано
             %d выбраны
             
         
    -
    -
         Системные оповещения
         Ошибка
         Создать парольную фразу
    @@ -733,7 +710,6 @@
         Показывать события о вступлении/выходе
         Показывать события аккаунта
         Включает изменения аватара и отображаемого имени.
    -
         Использовать системную камеру вместо камеры Element.
         %1$s: %2$s
         %d+
    @@ -814,7 +790,6 @@
         Невозможно расшифровать резервную копию с помощью этого ключа восстановления: убедитесь, что вы ввели правильный ключ.
         Невозможно расшифровать резервную копию с помощью этого пароля: убедитесь, что вы ввели правильный пароль.
         Генерация ключей восстановления с использованием парольной фразы может занять несколько секунд.
    -
         [%1$s]
     \nЭта ошибка вне контроля ${app_name}. На телефоне нет учетной записи Google. Пожалуйста, добавьте аккаунт Google.
         [%1$s]
    @@ -855,7 +830,6 @@
         Использовать резервное копирование ключей
         Управление резервным копированием ключей
         Новые ключи зашифрованных сообщений
    -
         Ваши ключи копируются.
         (Дополнительно) Настройка с ключом восстановления
         Или защитите резервную копию с помощью ключа восстановления, сохранив его в безопасном месте.
    @@ -910,11 +884,8 @@
         Поделиться
         Запрос поделится ключом
         Игнорировать
    -
         Проверено!
         Понял
    -
    -
         Запрос на подтверждение
         %s желает подтвердить вашу сессию
         Неизвестная ошибка
    @@ -1011,7 +982,6 @@
         Никто
         Отмена
         Отключить
    -
         Не удается связаться с домашним сервером по этому URL, пожалуйста, проверьте его
         Оптимизирован для батареи
         Оптимизирован для работы в реальном времени
    @@ -1023,7 +993,6 @@
         ${app_name} будет синхронизироваться в фоновом режиме периодически в точное время (настраивается).
     \nЭто повлияет на использование радио и батареи, появится постоянное уведомление о том, что ${app_name} прислушивается к событиям.
         Вы не будете уведомлены о входящих сообщениях, когда приложение находится в фоновом режиме.
    -
         Изменить настройки обнаружения.
         Вы не используете какой-либо сервер обнаружения
         Похоже, вы пытаетесь подключиться к другому домашнему серверу. Вы хотите выйти\?
    @@ -1090,7 +1059,7 @@
         Покинуть комнату
         %1$s сделал(а) комнату доступной для всех, у кого есть ссылка.
         %1$s сделал(а) комнату доступной только по приглашению.
    -    Подробные логи помогут разработчикам, предоставив больше информации, когда вы отправляете ВзмахЯрости. Даже когда они разрешены, приложение не записывает ваши сообщения и другие приватные данные.
    +    Подробные логи помогут разработчикам, предоставив больше информации, когда вы отправляете \"Яростное встряхивание\". Даже когда они разрешены, приложение не записывает ваши сообщения и другие приватные данные.
         Закройте меню создание комнаты…
         Вниз
         Контакт
    @@ -1276,7 +1245,6 @@
         Лента сообщений
         Ключ сообщения
         Распечатайте его и храните в безопасном месте
    -
         Шифрование включено
         Шифрование не включено
         %1$s: %2$s
    @@ -1311,7 +1279,6 @@
         Закрыть окно резервного копирования ключей
         %s прочитано
         Не удалось обработать данные
    -
         Воспроизвести
         Копировать
         Удачно
    @@ -1405,10 +1372,10 @@
         Это недопустимый идентификатор пользователя. Ожидаемый формат: \'@user:homeserver.org\'
         Не удалось найти действительный домашний сервер. Пожалуйста, проверьте свой идентификатор
         Начальная синхронизация…
    -    СотрясениеЯрости
    +    Яростное встряхивание
         Порог обнаружения
         Встряхните телефон, чтобы проверить порог обнаружения
    -    Обнаружено потрясение!
    +    Обнаружено встряхивание!
         Показываем только первые результаты, наберите больше букв…
         Раннее падение
         ${app_name} может падать чаще, когда происходит непредвиденная ошибка
    @@ -1443,7 +1410,6 @@
         Подтверждено %s
         Подтверждённых %s
         Ожидаем %s…
    -
         Сообщения в этой комнате не защищены сквозным шифрованием.
         Сообщения в этой комнате защищены сквозным шифрованием.
     \n
    @@ -1871,7 +1837,6 @@
         Скрыть дополнительные настройки
         Показать дополнительные настройки
         %1$d из %2$d
    -
         Дать согласие
         Отозвать моё согласие
         Больше никаких результатов
    @@ -1973,8 +1938,6 @@
         Перевод
         Подключиться
         Сначала посоветуйтесь
    -
    -
         Нет учётных данных, неправильная учётная запись пользователя и/или пароль
         Вы уверены, что хотите удалить все неотправленные сообщения в этой комнате\?
         Удалить неотправленные сообщения
    @@ -2061,7 +2024,6 @@
         Обновление
         Пожалуйста, будьте терпеливы, это может занять некоторое время.
         Присоединиться к замещенной комнате
    -
         Безымянная Комната
         Некоторые комнаты могут быть скрыты, потому что они приватные, и вам нужно приглашение.
         Некоторые комнаты могут быть скрыты, потому что они приватные, и вам нужно приглашение.
    @@ -2130,7 +2092,7 @@
         Приватное пространство для организации ваших комнат
         Я и члены команды
         Только я
    -    Убедитесь, что нужные люди имеют доступ к %s. Вы сможете изменить это позже.
    +    Убедитесь, что нужные люди имеют доступ к %s.
         С кем вы работаете\?
         Чтобы присоединиться к существующему пространству, вам необходимо получить приглашение.
         Вы сможете изменить это позже
    @@ -2453,7 +2415,6 @@
         Местоположение
         Вы согласны отправить эту информацию\?
         Чтобы обнаружить существующие контакты, необходимо отправить контактную информацию (электронную почту и номера телефонов) на сервер обнаружения. Мы хешируем ваши данные перед отправкой для обеспечения конфиденциальности.
    -
         Отправить электронные адреса и номера телефонов %s
         Ваши контакты приватны. Чтобы обнаружить пользователей из ваших контактов, нам необходимо ваше разрешение на отправку контактной информации на ваш сервер обнаружения.
         Системные настройки
    @@ -2485,16 +2446,16 @@
         Включить
         Слежка за уведомлениями
         Вам не разрешено подключаться к этой комнате
    -    Организуйте обсуждение в потоках
    -    Показать все потоки в которых вы участвуете
    -    Все Потоки
    -    Просмотр Потоков
    +    Организуйте обсуждение с помощью веток
    +    Показать все ветки, в которых вы участвуете
    +    Все ветки
    +    Посмотреть ветки
         Посмотреть в комнате
    -    Показать всплывающие сообщения
    +    Показывать сообщения в пузырях
         Не удалось загрузить карту
         Карта
         Примечание: приложение будет перезапущено
    -    Включить Сообщения Потока
    +    Включить ветки сообщений
         Подключиться к серверу
         Хотите присоединиться к существующему серверу\?
         пропустить вопрос
    @@ -2504,18 +2465,36 @@
         Друзья и семья
         Мы поможем вам подключится.
         С кем вы будете общаться больше всего\?
    -    Вы уже просматриваете этот Поток!
    +    Вы уже просматриваете эту ветку!
         Просмотр в Комнате
    -    Ответить в Поток
    -    Команда «%s» распознается, но не поддерживается в потоках.
    -    Из Потока
    +    Ответить в ветке
    +    Команда «%s» распознается, но не поддерживается в ветках.
    +    Из ветки
         Совет: нажмите и удерживайте сообщение и используйте «%s».
    -    Потоки помогают хранить ваши разговоры по темам и легко отслеживать их.
    -    Мои Потоки
    -    Показать все потоки в текущей комнате
    +    Ветки помогают хранить ваши разговоры по темам и легко отслеживать их.
    +    Мои ветки
    +    Показать все ветки этой комнаты
         Фильтр
    -    Потоки
    -    Поток
    -    Фильтровать Потоки в комнате
    -    Скопировать ссылку в поток
    +    Ветки
    +    Ветка
    +    Фильтровать ветки в комнате
    +    Скопировать ссылку в ветку
    +    Уведомления комнаты
    +    Пользователи
    +    Оповестить всю комнату
    +    
    +        И ещё %1$d
    +        И ещё %1$d
    +        И ещё %1$d
    +        И ещё %1$d
    +    
    +    Свернуть
    +    %1$s, %2$s и другие
    +    %1$s и %2$s
    +    
    +        %d изменение ACL сервера
    +        %d изменения ACL сервера
    +        %d изменений ACL сервера
    +        %d изменений ACL сервера
    +    
     
    \ No newline at end of file
    diff --git a/vector/src/main/res/values-sk/strings.xml b/vector/src/main/res/values-sk/strings.xml
    index 8d97ff7266..6650890ac3 100644
    --- a/vector/src/main/res/values-sk/strings.xml
    +++ b/vector/src/main/res/values-sk/strings.xml
    @@ -2274,7 +2274,7 @@
         Týmto sa zastaví možnosť hlasovania a zobrazia sa konečné výsledky ankety.
         Určite chcete túto anketu odstrániť\? Po odstránení ju už nebudete môcť obnoviť.
         Typ ankety
    -    Otvoriť anketu
    +    Otvorená anketa
         Hlasujúci uvidia výsledky hneď po hlasovaní
         Uzavretá anketa
         Zobraziť vlákna
    @@ -2452,4 +2452,18 @@
             %1$d ďalších
         
         Zadajte URL adresu servera Modular Element alebo adresu servera, ktorý si želáte použiť
    +    Hlasovať
    +    Odoslať stav udalosti
    +    Priradenie sa nepodarilo.
    +    V súčasnosti neexistuje žiadne priradenie k tomuto identifikátoru.
    +    použite záložný kľúč na obnovu kľúča
    +    Uloženie tajnej zálohy kľúčov v SSSS
    +    Generovanie kľúča SSSS z kľúča pre obnovu
    +    Definovanie predvoleného kľúča SSSS
    +    Rýchle-zlyhanie
    +    Najprv konzultovať
    +    Vyberte si, čo opustíte
    +    Opustiť miestnosť s daným id (alebo aktuálnu miestnosť, ak je prázdna)
    +    Varovná úroveň dôveryhodnosti
    +    Pripojiť
     
    \ No newline at end of file
    diff --git a/vector/src/main/res/values-sq/strings.xml b/vector/src/main/res/values-sq/strings.xml
    index 48fc274eb7..0fc5c7f6fb 100644
    --- a/vector/src/main/res/values-sq/strings.xml
    +++ b/vector/src/main/res/values-sq/strings.xml
    @@ -310,7 +310,7 @@
         PO
         Vazhdo
         Hiqe
    -    Bëhuni pjesë
    +    Hyni
         Hidheni tej
         Anëtarë liste
         Hidhu te të palexuarit
    diff --git a/vector/src/main/res/values-zh-rCN/strings.xml b/vector/src/main/res/values-zh-rCN/strings.xml
    index d2e17e856d..67bf3f5e06 100644
    --- a/vector/src/main/res/values-zh-rCN/strings.xml
    +++ b/vector/src/main/res/values-zh-rCN/strings.xml
    @@ -39,7 +39,6 @@
         空聊天室
         聊天室邀请
         %1$s 和 %2$s
    -
         初始化同步:
     \n正在导入账号…
         初始化同步:
    @@ -262,10 +261,8 @@
         拍摄照片或视频
         此主服务器想确认你不是机器人
         电子邮箱地址验证失败:请确保你已点击邮件中的链接
    -
         原始
         通话正在连接…
    -
         ${app_name} 需要权限以访问你的麦克风来进行语音通话。
         私聊
         邀请
    @@ -283,7 +280,6 @@
         忽略
         指纹(%s):
         无法验证远程服务器的身份。
    -
         应用信息
         启用这个账号的通知
         启用这个设备的通知
    @@ -301,7 +297,6 @@
         通知
         已忽略的用户
         通讯录权限
    -
         身份认证
         当前密码
         是否重新显示所有来自 %s 的消息? 
    @@ -319,14 +314,12 @@
         高级
         此聊天室的内部 ID
         这些是实验性功能,可能会出现不可预料的错误。请谨慎使用。
    -
         导出端对端聊天室密钥
         导出聊天室密钥
         导出密钥到本地文件
         导出
         输入密语
         确认密语
    -
         导入端对端聊天室密钥
         导入聊天室密钥
         从本地文件导入密钥
    @@ -338,25 +331,20 @@
         永久链接
         重命名
         举报内容
    -
         问题反馈
         为分析此问题,本客户端的日志将会随此问题反馈发送。本问题反馈,包括日志与截图,将不会被公开显示。若你希望仅发送上面的文字,请取消选择:
         问题反馈发送成功
         问题反馈发送失败(%s)
    -
         异常的 JSON
         呼入的视频通话
         呼入的语音通话
         通话中…
         通话未被接听。
         信息
    -
    -
         ${app_name} 需要权限以访问你的摄像机和麦克风来进行视频通话。
     \n
     \n请在接下来的弹出窗口中授权允许访问,以便进行通话。
         移除
    -
         你将不能撤销这个修改,因为你正在让这个用户和你拥有相同的特权级别。
     \n你确定吗?
         这可能意味着有人正在恶意劫持你的流量,或者你的手机不信任远程服务器提供的数字证书。
    @@ -375,7 +363,6 @@
         取消设置为主要地址
         确认
         你似乎沮丧地摇了摇手机。你想打开问题反馈界面吗?
    -
         证书已从一个先前受你的设备信任的证书更改为另一个。这非常反常!建议你不要接受此新证书。
         证书已从曾受信任的证书更改为不受信任的证书。服务器可能已更新其证书,请联系管理员并核对服务器的指纹。
         请仅在服务器管理员发布了与上述指纹匹配的指纹的情况下接受该证书。
    @@ -391,7 +378,6 @@
         实验室
         为验证此设备是否可信,请通过其他方式(例如面对面交换或拨打电话)与其拥有者联系,并询问他们该设备的用户设置中的密钥是否与以下密钥匹配:
         如果它们不匹配,你通讯的安全性可能会受到影响。
    -
         邀请
         收藏夹
         联系人
    @@ -479,7 +465,6 @@
         
         社群
         摇一摇快捷反馈问题
    -
         
             %d 位成员的状态发生了变化
         
    @@ -490,7 +475,6 @@
             %d 条未读消息
         
         显示成员
    -
         徽章
         
             %d 个聊天室
    @@ -505,9 +489,7 @@
         你已被 %2$s 从 %1$s 中封禁
         理由:%1$s
         头像
    -
         %1$s 条在 %2$s 中
    -
         停用账号
         停用我的账号
         发送统计分析数据
    @@ -553,16 +535,9 @@
         对话在此继续
         这个聊天室是另一个对话的延续
         点击此处查看更早的消息
    -
    -
    -
    -
    -
         
             已选择 %d 个
         
    -
    -
         系统警告
         联系你的服务管理员
         本服务器其中一项资源已超出限制,部分用户将无法登录
    @@ -631,7 +606,6 @@
         邀请、移除与封禁事件不受影响。
         显示账号变动事件
         包括头像与显示名称的变动。
    -
         密码
         %d+
         %1$s:%2$s
    @@ -719,7 +693,6 @@
         保存恢复密钥
         分享
         保存为文件
    -
         请制作一份拷贝
         分享恢复密钥…
         正在使用密语来生成恢复密钥,此过程可能会花费几秒钟。
    @@ -765,7 +738,6 @@
         正在删除备份…
         删除备份
         要从此服务器中删除你备份的加密密钥吗?你将无法再使用恢复密钥来读取加密的历史消息。
    -
         永不丢失已加密消息
         使用备份密钥
         新加密信息密钥
    @@ -783,7 +755,6 @@
         按回车发送消息
         软键盘的 Enter 按钮将发送消息而不是添加换行符
         密码无效
    -
         媒体
         默认压缩
         选择
    @@ -865,7 +836,6 @@
         撤消
         断开连接
         拒绝
    -
         這不是有效的 Matrix 服务器位置
         无法在此 URL 找到主服务器,请检查
         播放
    @@ -918,7 +888,6 @@
     \n这将影响网络和电池的使用,将显示一个永久通知表明 ${app_name} 正在监听事件。
         无后台同步
         应用在后台时你不会收到消息通知。
    -
         集成
         使用集成管理器管理机器人、桥接、部件和贴纸包。
     \n集成管理器接收配置数据,可以代表你修改部件、发送聊天室邀请及设置特权等级。
    @@ -972,8 +941,6 @@
         安全备份
         保护加密信息及数据的访问权
         设置安全备份
    -
    -
         你未使用身份服务器
         你似乎正在试图连接到另一个主服务器。你想要登出吗?
         你已经跟上了!
    @@ -1096,7 +1063,6 @@
         此内容已报告为不合适。
     \n
     \n如果你不希望再看到此用户的更多内容,你可以忽略他们以隐藏他们的消息。
    -
         忽略用户
         全部消息(嘈杂)
         全部消息
    @@ -1295,7 +1261,6 @@
         验证 %s
         已验证 %s
         正在等待 %s…
    -
         此聊天室的消息未经端对端加密。
         该聊天室的消息已被端对端加密。
     \n
    @@ -1446,7 +1411,6 @@
         密钥已是最新!
         保存到优盘或者备份盘
         复制到你的个人云存储
    -
         如果你现在取消,那么当你失去登录权限时也会丢失加密的信息和数据。
     \n
     \n你也可以通过设置菜单来建立保护备份以及管理你的密钥。
    @@ -1754,7 +1718,6 @@
         %2$d 的 %1$d
         旋转和裁剪
         添加图像自
    -
         授予许可
         撤销我的许可
         你已同意发送电子邮件和电话号码到身份服务器以从你的联系人发现其他用户。
    @@ -1823,7 +1786,6 @@
         已勾选
         已选中
         活跃通话(%1$s)
    -
         需要重新验证
         删除失败的消息
         你确定要取消发送消息吗?
    @@ -1987,7 +1949,6 @@
         ${app_name} 要求你输入凭据才能执行此操作。
         呼叫转移时发生错误
         先询问
    -
         查找电话号码时发生了错误
         此通话已结束
         %1$s 拒绝了此通话
    @@ -2075,7 +2036,6 @@
         输入你想要探索的新服务器的名称。
         添加一个新的服务器
         你的服务器
    -
         抱歉,尝试加入 %s 时发生了一个错误
         空间地址
         查看和管理这个空间的地址。
    @@ -2272,7 +2232,6 @@
         投票问题或主题
         创建投票
         投票
    -
         向 %s 发送电子邮件和电话号码
         您的联系人是私密的。 要从您的联系人中发现用户,我们需要您的许可才能将联系信息发送到您的身份服务器。
         已退出此会话!
    @@ -2321,4 +2280,8 @@
     \n你可以阅读我们所有的条款 %s。
         帮助改进 ${app_name}
         启用
    +    不允许加入此房间
    +    
    +        修改服务器 %d 的 ACLs
    +    
     
    \ No newline at end of file
    diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml
    index aed977d687..551e5961ec 100644
    --- a/vector/src/main/res/values/strings.xml
    +++ b/vector/src/main/res/values/strings.xml
    @@ -731,6 +731,8 @@
         
         Tip: Long tap a message and use “%s”.
         From a Thread
    +    Threads Approaching Beta 🎉
    +    We’re getting closer to releasing a public Beta for Threads.\n\nAs we prepare for it, we need to make some changes: threads created before this point will be displayed as regular replies.\n\nThis will be a one-off transition as Threads are now part of the Matrix specification.
     
         
         Search
    diff --git a/vector/src/main/res/xml/vector_settings_labs.xml b/vector/src/main/res/xml/vector_settings_labs.xml
    index 73193edfd5..5144f6fe1f 100644
    --- a/vector/src/main/res/xml/vector_settings_labs.xml
    +++ b/vector/src/main/res/xml/vector_settings_labs.xml
    @@ -52,8 +52,8 @@
         
     
         
     
    diff --git a/vector/src/test/java/im/vector/app/features/analytics/impl/DefaultVectorAnalyticsTest.kt b/vector/src/test/java/im/vector/app/features/analytics/impl/DefaultVectorAnalyticsTest.kt
    index b17c1a8bba..543d517db1 100644
    --- a/vector/src/test/java/im/vector/app/features/analytics/impl/DefaultVectorAnalyticsTest.kt
    +++ b/vector/src/test/java/im/vector/app/features/analytics/impl/DefaultVectorAnalyticsTest.kt
    @@ -30,7 +30,7 @@ import im.vector.app.test.fixtures.aVectorAnalyticsScreen
     import kotlinx.coroutines.CoroutineScope
     import kotlinx.coroutines.Dispatchers
     import kotlinx.coroutines.ExperimentalCoroutinesApi
    -import kotlinx.coroutines.test.runBlockingTest
    +import kotlinx.coroutines.test.runTest
     import org.junit.Before
     import org.junit.Test
     
    @@ -60,35 +60,35 @@ class DefaultVectorAnalyticsTest {
         }
     
         @Test
    -    fun `when setting user consent then updates analytics store`() = runBlockingTest {
    +    fun `when setting user consent then updates analytics store`() = runTest {
             defaultVectorAnalytics.setUserConsent(true)
     
             fakeAnalyticsStore.verifyConsentUpdated(updatedValue = true)
         }
     
         @Test
    -    fun `when consenting to analytics then updates posthog opt out to false`() = runBlockingTest {
    +    fun `when consenting to analytics then updates posthog opt out to false`() = runTest {
             fakeAnalyticsStore.givenUserContent(consent = true)
     
             fakePostHog.verifyOptOutStatus(optedOut = false)
         }
     
         @Test
    -    fun `when revoking consent to analytics then updates posthog opt out to true`() = runBlockingTest {
    +    fun `when revoking consent to analytics then updates posthog opt out to true`() = runTest {
             fakeAnalyticsStore.givenUserContent(consent = false)
     
             fakePostHog.verifyOptOutStatus(optedOut = true)
         }
     
         @Test
    -    fun `when setting the analytics id then updates analytics store`() = runBlockingTest {
    +    fun `when setting the analytics id then updates analytics store`() = runTest {
             defaultVectorAnalytics.setAnalyticsId(AN_ANALYTICS_ID)
     
             fakeAnalyticsStore.verifyAnalyticsIdUpdated(updatedValue = AN_ANALYTICS_ID)
         }
     
         @Test
    -    fun `given lateinit user properties when valid analytics id updates then identify with lateinit properties`() = runBlockingTest {
    +    fun `given lateinit user properties when valid analytics id updates then identify with lateinit properties`() = runTest {
             fakeLateInitUserPropertiesFactory.givenCreatesProperties(A_LATE_INIT_USER_PROPERTIES)
     
             fakeAnalyticsStore.givenAnalyticsId(AN_ANALYTICS_ID)
    @@ -97,7 +97,7 @@ class DefaultVectorAnalyticsTest {
         }
     
         @Test
    -    fun `when signing out then resets posthog`() = runBlockingTest {
    +    fun `when signing out then resets posthog`() = runTest {
             fakeAnalyticsStore.allowSettingAnalyticsIdToCallBackingFlow()
     
             defaultVectorAnalytics.onSignOut()
    @@ -106,7 +106,7 @@ class DefaultVectorAnalyticsTest {
         }
     
         @Test
    -    fun `given user consent when tracking screen events then submits to posthog`() = runBlockingTest {
    +    fun `given user consent when tracking screen events then submits to posthog`() = runTest {
             fakeAnalyticsStore.givenUserContent(consent = true)
     
             defaultVectorAnalytics.screen(A_SCREEN_EVENT)
    @@ -115,7 +115,7 @@ class DefaultVectorAnalyticsTest {
         }
     
         @Test
    -    fun `given user has not consented when tracking screen events then does not track`() = runBlockingTest {
    +    fun `given user has not consented when tracking screen events then does not track`() = runTest {
             fakeAnalyticsStore.givenUserContent(consent = false)
     
             defaultVectorAnalytics.screen(A_SCREEN_EVENT)
    @@ -124,7 +124,7 @@ class DefaultVectorAnalyticsTest {
         }
     
         @Test
    -    fun `given user consent when tracking events then submits to posthog`() = runBlockingTest {
    +    fun `given user consent when tracking events then submits to posthog`() = runTest {
             fakeAnalyticsStore.givenUserContent(consent = true)
     
             defaultVectorAnalytics.capture(AN_EVENT)
    @@ -133,7 +133,7 @@ class DefaultVectorAnalyticsTest {
         }
     
         @Test
    -    fun `given user has not consented when tracking events then does not track`() = runBlockingTest {
    +    fun `given user has not consented when tracking events then does not track`() = runTest {
             fakeAnalyticsStore.givenUserContent(consent = false)
     
             defaultVectorAnalytics.capture(AN_EVENT)
    diff --git a/vector/src/test/java/im/vector/app/features/analytics/impl/LateInitUserPropertiesFactoryTest.kt b/vector/src/test/java/im/vector/app/features/analytics/impl/LateInitUserPropertiesFactoryTest.kt
    index c2fa50f789..2068099ab9 100644
    --- a/vector/src/test/java/im/vector/app/features/analytics/impl/LateInitUserPropertiesFactoryTest.kt
    +++ b/vector/src/test/java/im/vector/app/features/analytics/impl/LateInitUserPropertiesFactoryTest.kt
    @@ -23,7 +23,7 @@ import im.vector.app.test.fakes.FakeContext
     import im.vector.app.test.fakes.FakeSession
     import im.vector.app.test.fakes.FakeVectorStore
     import kotlinx.coroutines.ExperimentalCoroutinesApi
    -import kotlinx.coroutines.test.runBlockingTest
    +import kotlinx.coroutines.test.runTest
     import org.amshove.kluent.shouldBeEqualTo
     import org.junit.Test
     
    @@ -43,14 +43,14 @@ class LateInitUserPropertiesFactoryTest {
         )
     
         @Test
    -    fun `given no active session when creating properties then returns null`() = runBlockingTest {
    +    fun `given no active session when creating properties then returns null`() = runTest {
             val result = lateInitUserProperties.createUserProperties()
     
             result shouldBeEqualTo null
         }
     
         @Test
    -    fun `given no use case set on an active session when creating properties then returns null`() = runBlockingTest {
    +    fun `given no use case set on an active session when creating properties then returns null`() = runTest {
             fakeVectorStore.givenUseCase(null)
             fakeSession.givenVectorStore(fakeVectorStore.instance)
             fakeActiveSessionDataSource.setActiveSession(fakeSession)
    @@ -61,7 +61,7 @@ class LateInitUserPropertiesFactoryTest {
         }
     
         @Test
    -    fun `given use case set on an active session when creating properties then includes the use case`() = runBlockingTest {
    +    fun `given use case set on an active session when creating properties then includes the use case`() = runTest {
             fakeVectorStore.givenUseCase(FtueUseCase.TEAMS)
             fakeActiveSessionDataSource.setActiveSession(fakeSession)
             val result = lateInitUserProperties.createUserProperties()
    diff --git a/vector/src/test/java/im/vector/app/features/crypto/keys/KeysExporterTest.kt b/vector/src/test/java/im/vector/app/features/crypto/keys/KeysExporterTest.kt
    index 57ad2a52ab..5d0317592d 100644
    --- a/vector/src/test/java/im/vector/app/features/crypto/keys/KeysExporterTest.kt
    +++ b/vector/src/test/java/im/vector/app/features/crypto/keys/KeysExporterTest.kt
    @@ -26,7 +26,7 @@ import io.mockk.every
     import io.mockk.mockk
     import io.mockk.verify
     import kotlinx.coroutines.Dispatchers
    -import kotlinx.coroutines.runBlocking
    +import kotlinx.coroutines.test.runTest
     import org.amshove.kluent.internal.assertFailsWith
     import org.junit.Before
     import org.junit.Test
    @@ -55,7 +55,7 @@ class KeysExporterTest {
             givenFileDescriptorWithSize(size = A_ROOM_KEYS_EXPORT.size.toLong())
             val outputStream = context.givenOutputStreamFor(A_URI)
     
    -        runBlocking { keysExporter.export(A_PASSWORD, A_URI) }
    +        runTest { keysExporter.export(A_PASSWORD, A_URI) }
     
             verify { outputStream.write(A_ROOM_KEYS_EXPORT) }
         }
    @@ -66,7 +66,7 @@ class KeysExporterTest {
             context.givenOutputStreamFor(A_URI)
     
             assertFailsWith {
    -            runBlocking { keysExporter.export(A_PASSWORD, A_URI) }
    +            runTest { keysExporter.export(A_PASSWORD, A_URI) }
             }
         }
     
    @@ -75,7 +75,7 @@ class KeysExporterTest {
             context.givenMissingOutputStreamFor(A_URI)
     
             assertFailsWith(message = "Unable to open file for writing") {
    -            runBlocking { keysExporter.export(A_PASSWORD, A_URI) }
    +            runTest { keysExporter.export(A_PASSWORD, A_URI) }
             }
         }
     
    @@ -85,7 +85,7 @@ class KeysExporterTest {
             context.givenOutputStreamFor(A_URI)
     
             assertFailsWith(message = "Exported file not found") {
    -            runBlocking { keysExporter.export(A_PASSWORD, A_URI) }
    +            runTest { keysExporter.export(A_PASSWORD, A_URI) }
             }
         }
     
    diff --git a/vector/src/test/java/im/vector/app/features/crypto/quads/SharedSecureStorageViewModelTest.kt b/vector/src/test/java/im/vector/app/features/crypto/quads/SharedSecureStorageViewModelTest.kt
    index fc4197e07a..7562dfdf14 100644
    --- a/vector/src/test/java/im/vector/app/features/crypto/quads/SharedSecureStorageViewModelTest.kt
    +++ b/vector/src/test/java/im/vector/app/features/crypto/quads/SharedSecureStorageViewModelTest.kt
    @@ -21,7 +21,7 @@ import com.airbnb.mvrx.test.MvRxTestRule
     import im.vector.app.test.fakes.FakeSession
     import im.vector.app.test.fakes.FakeStringProvider
     import im.vector.app.test.test
    -import kotlinx.coroutines.test.runBlockingTest
    +import kotlinx.coroutines.test.runTest
     import org.junit.Rule
     import org.junit.Test
     import org.matrix.android.sdk.api.session.securestorage.IntegrityResult
    @@ -47,117 +47,105 @@ class SharedSecureStorageViewModelTest {
         val args = SharedSecureStorageActivity.Args(keyId = null, emptyList(), "alias")
     
         @Test
    -    fun `given a key info with passphrase when initialising then step is EnterPassphrase`() {
    -        runBlockingTest {
    -            givenKey(KEY_INFO_WITH_PASSPHRASE)
    -            val viewModel = createViewModel()
    -            viewModel
    -                    .test(this)
    -                    .assertState(aViewState(
    -                            hasPassphrase = true,
    -                            step = SharedSecureStorageViewState.Step.EnterPassphrase
    -                    ))
    -                    .finish()
    -        }
    +    fun `given a key info with passphrase when initialising then step is EnterPassphrase`() = runTest {
    +        givenKey(KEY_INFO_WITH_PASSPHRASE)
    +        val viewModel = createViewModel()
    +        viewModel
    +                .test()
    +                .assertState(aViewState(
    +                        hasPassphrase = true,
    +                        step = SharedSecureStorageViewState.Step.EnterPassphrase
    +                ))
    +                .finish()
         }
     
         @Test
    -    fun `given a key info without passphrase when initialising then step is EnterKey`() {
    -        runBlockingTest {
    -            givenKey(KEY_INFO_WITHOUT_PASSPHRASE)
    +    fun `given a key info without passphrase when initialising then step is EnterKey`() = runTest {
    +        givenKey(KEY_INFO_WITHOUT_PASSPHRASE)
     
    -            val viewModel = createViewModel()
    +        val viewModel = createViewModel()
     
    -            viewModel
    -                    .test(this)
    -                    .assertState(aViewState(
    -                            hasPassphrase = false,
    -                            step = SharedSecureStorageViewState.Step.EnterKey
    -                    ))
    -                    .finish()
    -        }
    +        viewModel
    +                .test()
    +                .assertState(aViewState(
    +                        hasPassphrase = false,
    +                        step = SharedSecureStorageViewState.Step.EnterKey
    +                ))
    +                .finish()
         }
     
         @Test
    -    fun `given on EnterKey step when going back then dismisses`() {
    -        runBlockingTest {
    -            givenKey(KEY_INFO_WITHOUT_PASSPHRASE)
    +    fun `given on EnterKey step when going back then dismisses`() = runTest {
    +        givenKey(KEY_INFO_WITHOUT_PASSPHRASE)
     
    -            val viewModel = createViewModel()
    -            val test = viewModel.test(this)
    -            viewModel.handle(SharedSecureStorageAction.Back)
    -            test
    -                    .assertEvents(SharedSecureStorageViewEvent.Dismiss)
    -                    .finish()
    -        }
    +        val viewModel = createViewModel()
    +        val test = viewModel.test()
    +        viewModel.handle(SharedSecureStorageAction.Back)
    +        test
    +                .assertEvents(SharedSecureStorageViewEvent.Dismiss)
    +                .finish()
         }
     
         @Test
    -    fun `given on passphrase step when using key then step is EnterKey`() {
    -        runBlockingTest {
    -            givenKey(KEY_INFO_WITH_PASSPHRASE)
    -            val viewModel = createViewModel()
    -            val test = viewModel.test(this)
    +    fun `given on passphrase step when using key then step is EnterKey`() = runTest {
    +        givenKey(KEY_INFO_WITH_PASSPHRASE)
    +        val viewModel = createViewModel()
    +        val test = viewModel.test()
     
    -            viewModel.handle(SharedSecureStorageAction.UseKey)
    +        viewModel.handle(SharedSecureStorageAction.UseKey)
     
    -            test
    -                    .assertStates(
    -                            aViewState(
    -                                    hasPassphrase = true,
    -                                    step = SharedSecureStorageViewState.Step.EnterPassphrase
    -                            ),
    -                            aViewState(
    -                                    hasPassphrase = true,
    -                                    step = SharedSecureStorageViewState.Step.EnterKey
    -                            )
    -                    )
    -                    .finish()
    -        }
    +        test
    +                .assertStates(
    +                        aViewState(
    +                                hasPassphrase = true,
    +                                step = SharedSecureStorageViewState.Step.EnterPassphrase
    +                        ),
    +                        aViewState(
    +                                hasPassphrase = true,
    +                                step = SharedSecureStorageViewState.Step.EnterKey
    +                        )
    +                )
    +                .finish()
         }
     
         @Test
    -    fun `given a key info with passphrase and on EnterKey step when going back then step is EnterPassphrase`() {
    -        runBlockingTest {
    -            givenKey(KEY_INFO_WITH_PASSPHRASE)
    -            val viewModel = createViewModel()
    -            val test = viewModel.test(this)
    +    fun `given a key info with passphrase and on EnterKey step when going back then step is EnterPassphrase`() = runTest {
    +        givenKey(KEY_INFO_WITH_PASSPHRASE)
    +        val viewModel = createViewModel()
    +        val test = viewModel.test()
     
    -            viewModel.handle(SharedSecureStorageAction.UseKey)
    -            viewModel.handle(SharedSecureStorageAction.Back)
    +        viewModel.handle(SharedSecureStorageAction.UseKey)
    +        viewModel.handle(SharedSecureStorageAction.Back)
     
    -            test
    -                    .assertStates(
    -                            aViewState(
    -                                    hasPassphrase = true,
    -                                    step = SharedSecureStorageViewState.Step.EnterPassphrase
    -                            ),
    -                            aViewState(
    -                                    hasPassphrase = true,
    -                                    step = SharedSecureStorageViewState.Step.EnterKey
    -                            ),
    -                            aViewState(
    -                                    hasPassphrase = true,
    -                                    step = SharedSecureStorageViewState.Step.EnterPassphrase
    -                            )
    -                    )
    -                    .finish()
    -        }
    +        test
    +                .assertStates(
    +                        aViewState(
    +                                hasPassphrase = true,
    +                                step = SharedSecureStorageViewState.Step.EnterPassphrase
    +                        ),
    +                        aViewState(
    +                                hasPassphrase = true,
    +                                step = SharedSecureStorageViewState.Step.EnterKey
    +                        ),
    +                        aViewState(
    +                                hasPassphrase = true,
    +                                step = SharedSecureStorageViewState.Step.EnterPassphrase
    +                        )
    +                )
    +                .finish()
         }
     
         @Test
    -    fun `given on passphrase step when going back then dismisses`() {
    -        runBlockingTest {
    -            givenKey(KEY_INFO_WITH_PASSPHRASE)
    -            val viewModel = createViewModel()
    -            val test = viewModel.test(this)
    +    fun `given on passphrase step when going back then dismisses`() = runTest {
    +        givenKey(KEY_INFO_WITH_PASSPHRASE)
    +        val viewModel = createViewModel()
    +        val test = viewModel.test()
     
    -            viewModel.handle(SharedSecureStorageAction.Back)
    +        viewModel.handle(SharedSecureStorageAction.Back)
     
    -            test
    -                    .assertEvents(SharedSecureStorageViewEvent.Dismiss)
    -                    .finish()
    -        }
    +        test
    +                .assertEvents(SharedSecureStorageViewEvent.Dismiss)
    +                .finish()
         }
     
         private fun createViewModel(): SharedSecureStorageViewModel {
    diff --git a/vector/src/test/java/im/vector/app/features/location/domain/usecase/CompareLocationsUseCaseTest.kt b/vector/src/test/java/im/vector/app/features/location/domain/usecase/CompareLocationsUseCaseTest.kt
    index 015a27b0c8..7a80cbe87e 100644
    --- a/vector/src/test/java/im/vector/app/features/location/domain/usecase/CompareLocationsUseCaseTest.kt
    +++ b/vector/src/test/java/im/vector/app/features/location/domain/usecase/CompareLocationsUseCaseTest.kt
    @@ -21,7 +21,7 @@ import im.vector.app.features.location.LocationData
     import im.vector.app.test.fakes.FakeSession
     import io.mockk.MockKAnnotations
     import io.mockk.impl.annotations.OverrideMockKs
    -import kotlinx.coroutines.test.runBlockingTest
    +import kotlinx.coroutines.test.runTest
     import org.junit.Before
     import org.junit.Rule
     import org.junit.Test
    @@ -42,7 +42,7 @@ class CompareLocationsUseCaseTest {
         }
     
         @Test
    -    fun `given 2 very near locations when calling execute then these locations are considered as equal`() = runBlockingTest {
    +    fun `given 2 very near locations when calling execute then these locations are considered as equal`() = runTest {
             // Given
             val location1 = LocationData(
                     latitude = 48.858269,
    @@ -62,7 +62,7 @@ class CompareLocationsUseCaseTest {
         }
     
         @Test
    -    fun `given 2 far away locations when calling execute then these locations are considered as not equal`() = runBlockingTest {
    +    fun `given 2 far away locations when calling execute then these locations are considered as not equal`() = runTest {
             // Given
             val location1 = LocationData(
                     latitude = 48.858269,
    diff --git a/vector/src/test/java/im/vector/app/features/media/domain/usecase/DownloadMediaUseCaseTest.kt b/vector/src/test/java/im/vector/app/features/media/domain/usecase/DownloadMediaUseCaseTest.kt
    index 2fa8c7d5f7..bb05357cb2 100644
    --- a/vector/src/test/java/im/vector/app/features/media/domain/usecase/DownloadMediaUseCaseTest.kt
    +++ b/vector/src/test/java/im/vector/app/features/media/domain/usecase/DownloadMediaUseCaseTest.kt
    @@ -38,7 +38,7 @@ import io.mockk.runs
     import io.mockk.unmockkStatic
     import io.mockk.verify
     import io.mockk.verifyAll
    -import kotlinx.coroutines.test.runBlockingTest
    +import kotlinx.coroutines.test.runTest
     import org.junit.After
     import org.junit.Before
     import org.junit.Rule
    @@ -77,7 +77,7 @@ class DownloadMediaUseCaseTest {
         }
     
         @Test
    -    fun `given a file when calling execute then save the file in local with success`() = runBlockingTest {
    +    fun `given a file when calling execute then save the file in local with success`() = runTest {
             // Given
             val uri = mockk()
             val mimeType = "mimeType"
    @@ -105,7 +105,7 @@ class DownloadMediaUseCaseTest {
         }
     
         @Test
    -    fun `given a file when calling execute then save the file in local with error`() = runBlockingTest {
    +    fun `given a file when calling execute then save the file in local with error`() = runTest {
             // Given
             val uri = mockk()
             val mimeType = "mimeType"
    diff --git a/vector/src/test/java/im/vector/app/features/onboarding/OnboardingViewModelTest.kt b/vector/src/test/java/im/vector/app/features/onboarding/OnboardingViewModelTest.kt
    index f6c322af40..df4e0de65e 100644
    --- a/vector/src/test/java/im/vector/app/features/onboarding/OnboardingViewModelTest.kt
    +++ b/vector/src/test/java/im/vector/app/features/onboarding/OnboardingViewModelTest.kt
    @@ -17,10 +17,6 @@
     package im.vector.app.features.onboarding
     
     import android.net.Uri
    -import com.airbnb.mvrx.Fail
    -import com.airbnb.mvrx.Loading
    -import com.airbnb.mvrx.Success
    -import com.airbnb.mvrx.Uninitialized
     import com.airbnb.mvrx.test.MvRxTestRule
     import im.vector.app.features.login.ReAuthHelper
     import im.vector.app.features.login.SignMode
    @@ -40,7 +36,7 @@ import im.vector.app.test.fakes.FakeVectorFeatures
     import im.vector.app.test.fakes.FakeVectorOverrides
     import im.vector.app.test.fixtures.aHomeServerCapabilities
     import im.vector.app.test.test
    -import kotlinx.coroutines.test.runBlockingTest
    +import kotlinx.coroutines.test.runTest
     import org.junit.Before
     import org.junit.Rule
     import org.junit.Test
    @@ -82,8 +78,8 @@ class OnboardingViewModelTest {
         }
     
         @Test
    -    fun `when handling PostViewEvent, then emits contents as view event`() = runBlockingTest {
    -        val test = viewModel.test(this)
    +    fun `when handling PostViewEvent, then emits contents as view event`() = runTest {
    +        val test = viewModel.test()
     
             viewModel.handle(OnboardingAction.PostViewEvent(OnboardingViewEvents.OnTakeMeHome))
     
    @@ -93,10 +89,10 @@ class OnboardingViewModelTest {
         }
     
         @Test
    -    fun `given supports changing display name, when handling PersonalizeProfile, then emits contents choose display name`() = runBlockingTest {
    +    fun `given supports changing display name, when handling PersonalizeProfile, then emits contents choose display name`() = runTest {
             val initialState = initialState.copy(personalizationState = PersonalizationState(supportsChangingDisplayName = true, supportsChangingProfilePicture = false))
             viewModel = createViewModel(initialState)
    -        val test = viewModel.test(this)
    +        val test = viewModel.test()
     
             viewModel.handle(OnboardingAction.PersonalizeProfile)
     
    @@ -106,10 +102,10 @@ class OnboardingViewModelTest {
         }
     
         @Test
    -    fun `given only supports changing profile picture, when handling PersonalizeProfile, then emits contents choose profile picture`() = runBlockingTest {
    +    fun `given only supports changing profile picture, when handling PersonalizeProfile, then emits contents choose profile picture`() = runTest {
             val initialState = initialState.copy(personalizationState = PersonalizationState(supportsChangingDisplayName = false, supportsChangingProfilePicture = true))
             viewModel = createViewModel(initialState)
    -        val test = viewModel.test(this)
    +        val test = viewModel.test()
     
             viewModel.handle(OnboardingAction.PersonalizeProfile)
     
    @@ -119,9 +115,9 @@ class OnboardingViewModelTest {
         }
     
         @Test
    -    fun `when handling SignUp then sets sign mode to sign up and starts registration`() = runBlockingTest {
    +    fun `when handling SignUp then sets sign mode to sign up and starts registration`() = runTest {
             givenRegistrationResultFor(RegisterAction.StartRegistration, ANY_CONTINUING_REGISTRATION_RESULT)
    -        val test = viewModel.test(this)
    +        val test = viewModel.test()
     
             viewModel.handle(OnboardingAction.UpdateSignMode(SignMode.SignUp))
     
    @@ -129,16 +125,16 @@ class OnboardingViewModelTest {
                     .assertStatesChanges(
                             initialState,
                             { copy(signMode = SignMode.SignUp) },
    -                        { copy(asyncRegistration = Loading()) },
    -                        { copy(asyncRegistration = Uninitialized) }
    +                        { copy(isLoading = true) },
    +                        { copy(isLoading = false) }
                     )
                     .assertEvents(OnboardingViewEvents.RegistrationFlowResult(ANY_CONTINUING_REGISTRATION_RESULT.flowResult, isRegistrationStarted = true))
                     .finish()
         }
     
         @Test
    -    fun `given register action requires more steps, when handling action, then posts next steps`() = runBlockingTest {
    -        val test = viewModel.test(this)
    +    fun `given register action requires more steps, when handling action, then posts next steps`() = runTest {
    +        val test = viewModel.test()
             givenRegistrationResultFor(A_LOADABLE_REGISTER_ACTION, ANY_CONTINUING_REGISTRATION_RESULT)
     
             viewModel.handle(OnboardingAction.PostRegisterAction(A_LOADABLE_REGISTER_ACTION))
    @@ -146,16 +142,16 @@ class OnboardingViewModelTest {
             test
                     .assertStatesChanges(
                             initialState,
    -                        { copy(asyncRegistration = Loading()) },
    -                        { copy(asyncRegistration = Uninitialized) }
    +                        { copy(isLoading = true) },
    +                        { copy(isLoading = false) }
                     )
                     .assertEvents(OnboardingViewEvents.RegistrationFlowResult(ANY_CONTINUING_REGISTRATION_RESULT.flowResult, isRegistrationStarted = true))
                     .finish()
         }
     
         @Test
    -    fun `given register action is non loadable, when handling action, then posts next steps without loading`() = runBlockingTest {
    -        val test = viewModel.test(this)
    +    fun `given register action is non loadable, when handling action, then posts next steps without loading`() = runTest {
    +        val test = viewModel.test()
             givenRegistrationResultFor(A_NON_LOADABLE_REGISTER_ACTION, ANY_CONTINUING_REGISTRATION_RESULT)
     
             viewModel.handle(OnboardingAction.PostRegisterAction(A_NON_LOADABLE_REGISTER_ACTION))
    @@ -167,8 +163,8 @@ class OnboardingViewModelTest {
         }
     
         @Test
    -    fun `given register action ignores result, when handling action, then does nothing on success`() = runBlockingTest {
    -        val test = viewModel.test(this)
    +    fun `given register action ignores result, when handling action, then does nothing on success`() = runTest {
    +        val test = viewModel.test()
             givenRegistrationResultFor(A_RESULT_IGNORED_REGISTER_ACTION, RegistrationResult.FlowResponse(AN_IGNORED_FLOW_RESULT))
     
             viewModel.handle(OnboardingAction.PostRegisterAction(A_RESULT_IGNORED_REGISTER_ACTION))
    @@ -176,55 +172,53 @@ class OnboardingViewModelTest {
             test
                     .assertStatesChanges(
                             initialState,
    -                        { copy(asyncRegistration = Loading()) },
    -                        { copy(asyncRegistration = Uninitialized) }
    +                        { copy(isLoading = true) },
    +                        { copy(isLoading = false) }
                     )
                     .assertNoEvents()
                     .finish()
         }
     
         @Test
    -    fun `when registering account, then updates state and emits account created event`() = runBlockingTest {
    +    fun `when registering account, then updates state and emits account created event`() = runTest {
             givenRegistrationResultFor(A_LOADABLE_REGISTER_ACTION, RegistrationResult.Success(fakeSession))
             givenSuccessfullyCreatesAccount(A_HOMESERVER_CAPABILITIES)
    -        val test = viewModel.test(this)
    +        val test = viewModel.test()
     
             viewModel.handle(OnboardingAction.PostRegisterAction(A_LOADABLE_REGISTER_ACTION))
     
             test
                     .assertStatesChanges(
                             initialState,
    -                        { copy(asyncRegistration = Loading()) },
    -                        { copy(asyncLoginAction = Success(Unit), personalizationState = A_HOMESERVER_CAPABILITIES.toPersonalisationState()) },
    -                        { copy(asyncLoginAction = Success(Unit), asyncRegistration = Uninitialized) }
    +                        { copy(isLoading = true) },
    +                        { copy(isLoading = false, personalizationState = A_HOMESERVER_CAPABILITIES.toPersonalisationState()) }
                     )
                     .assertEvents(OnboardingViewEvents.OnAccountCreated)
                     .finish()
         }
     
         @Test
    -    fun `given registration has started and has dummy step to do, when handling action, then ignores other steps and executes dummy`() = runBlockingTest {
    +    fun `given registration has started and has dummy step to do, when handling action, then ignores other steps and executes dummy`() = runTest {
             givenSuccessfulRegistrationForStartAndDummySteps(missingStages = listOf(Stage.Dummy(mandatory = true)))
    -        val test = viewModel.test(this)
    +        val test = viewModel.test()
     
             viewModel.handle(OnboardingAction.PostRegisterAction(A_LOADABLE_REGISTER_ACTION))
     
             test
                     .assertStatesChanges(
                             initialState,
    -                        { copy(asyncRegistration = Loading()) },
    -                        { copy(asyncLoginAction = Success(Unit), personalizationState = A_HOMESERVER_CAPABILITIES.toPersonalisationState()) },
    -                        { copy(asyncRegistration = Uninitialized) }
    +                        { copy(isLoading = true) },
    +                        { copy(isLoading = false, personalizationState = A_HOMESERVER_CAPABILITIES.toPersonalisationState()) }
                     )
                     .assertEvents(OnboardingViewEvents.OnAccountCreated)
                     .finish()
         }
     
         @Test
    -    fun `given changing profile picture is supported, when updating display name, then updates upstream user display name and moves to choose profile picture`() = runBlockingTest {
    +    fun `given changing profile picture is supported, when updating display name, then updates upstream user display name and moves to choose profile picture`() = runTest {
             val personalisedInitialState = initialState.copy(personalizationState = PersonalizationState(supportsChangingProfilePicture = true))
             viewModel = createViewModel(personalisedInitialState)
    -        val test = viewModel.test(this)
    +        val test = viewModel.test()
     
             viewModel.handle(OnboardingAction.UpdateDisplayName(A_DISPLAY_NAME))
     
    @@ -236,10 +230,10 @@ class OnboardingViewModelTest {
         }
     
         @Test
    -    fun `given changing profile picture is not supported, when updating display name, then updates upstream user display name and completes personalization`() = runBlockingTest {
    +    fun `given changing profile picture is not supported, when updating display name, then updates upstream user display name and completes personalization`() = runTest {
             val personalisedInitialState = initialState.copy(personalizationState = PersonalizationState(supportsChangingProfilePicture = false))
             viewModel = createViewModel(personalisedInitialState)
    -        val test = viewModel.test(this)
    +        val test = viewModel.test()
     
             viewModel.handle(OnboardingAction.UpdateDisplayName(A_DISPLAY_NAME))
     
    @@ -251,8 +245,8 @@ class OnboardingViewModelTest {
         }
     
         @Test
    -    fun `given upstream failure, when handling display name update, then emits failure event`() = runBlockingTest {
    -        val test = viewModel.test(this)
    +    fun `given upstream failure, when handling display name update, then emits failure event`() = runTest {
    +        val test = viewModel.test()
             fakeSession.fakeProfileService.givenSetDisplayNameErrors(AN_ERROR)
     
             viewModel.handle(OnboardingAction.UpdateDisplayName(A_DISPLAY_NAME))
    @@ -260,16 +254,16 @@ class OnboardingViewModelTest {
             test
                     .assertStatesChanges(
                             initialState,
    -                        { copy(asyncDisplayName = Loading()) },
    -                        { copy(asyncDisplayName = Fail(AN_ERROR)) },
    +                        { copy(isLoading = true) },
    +                        { copy(isLoading = false) },
                     )
                     .assertEvents(OnboardingViewEvents.Failure(AN_ERROR))
                     .finish()
         }
     
         @Test
    -    fun `when handling profile picture selected, then updates selected picture state`() = runBlockingTest {
    -        val test = viewModel.test(this)
    +    fun `when handling profile picture selected, then updates selected picture state`() = runTest {
    +        val test = viewModel.test()
     
             viewModel.handle(OnboardingAction.ProfilePictureSelected(fakeUri.instance))
     
    @@ -283,10 +277,10 @@ class OnboardingViewModelTest {
         }
     
         @Test
    -    fun `given a selected picture, when handling save selected profile picture, then updates upstream avatar and completes personalization`() = runBlockingTest {
    +    fun `given a selected picture, when handling save selected profile picture, then updates upstream avatar and completes personalization`() = runTest {
             val initialStateWithPicture = givenPictureSelected(fakeUri.instance, A_PICTURE_FILENAME)
             viewModel = createViewModel(initialStateWithPicture)
    -        val test = viewModel.test(this)
    +        val test = viewModel.test()
     
             viewModel.handle(OnboardingAction.SaveSelectedProfilePicture)
     
    @@ -298,23 +292,23 @@ class OnboardingViewModelTest {
         }
     
         @Test
    -    fun `given upstream update avatar fails, when saving selected profile picture, then emits failure event`() = runBlockingTest {
    +    fun `given upstream update avatar fails, when saving selected profile picture, then emits failure event`() = runTest {
             fakeSession.fakeProfileService.givenUpdateAvatarErrors(AN_ERROR)
             val initialStateWithPicture = givenPictureSelected(fakeUri.instance, A_PICTURE_FILENAME)
             viewModel = createViewModel(initialStateWithPicture)
    -        val test = viewModel.test(this)
    +        val test = viewModel.test()
     
             viewModel.handle(OnboardingAction.SaveSelectedProfilePicture)
     
             test
    -                .assertStates(expectedProfilePictureFailureStates(initialStateWithPicture, AN_ERROR))
    +                .assertStates(expectedProfilePictureFailureStates(initialStateWithPicture))
                     .assertEvents(OnboardingViewEvents.Failure(AN_ERROR))
                     .finish()
         }
     
         @Test
    -    fun `given no selected picture, when saving selected profile picture, then emits failure event`() = runBlockingTest {
    -        val test = viewModel.test(this)
    +    fun `given no selected picture, when saving selected profile picture, then emits failure event`() = runTest {
    +        val test = viewModel.test()
     
             viewModel.handle(OnboardingAction.SaveSelectedProfilePicture)
     
    @@ -325,8 +319,8 @@ class OnboardingViewModelTest {
         }
     
         @Test
    -    fun `when handling profile skipped, then completes personalization`() = runBlockingTest {
    -        val test = viewModel.test(this)
    +    fun `when handling profile skipped, then completes personalization`() = runTest {
    +        val test = viewModel.test()
     
             viewModel.handle(OnboardingAction.UpdateProfilePictureSkipped)
     
    @@ -362,20 +356,20 @@ class OnboardingViewModelTest {
     
         private fun expectedProfilePictureSuccessStates(state: OnboardingViewState) = listOf(
                 state,
    -            state.copy(asyncProfilePicture = Loading()),
    -            state.copy(asyncProfilePicture = Success(Unit))
    +            state.copy(isLoading = true),
    +            state.copy(isLoading = false)
         )
     
    -    private fun expectedProfilePictureFailureStates(state: OnboardingViewState, cause: Exception) = listOf(
    +    private fun expectedProfilePictureFailureStates(state: OnboardingViewState) = listOf(
                 state,
    -            state.copy(asyncProfilePicture = Loading()),
    -            state.copy(asyncProfilePicture = Fail(cause))
    +            state.copy(isLoading = true),
    +            state.copy(isLoading = false)
         )
     
         private fun expectedSuccessfulDisplayNameUpdateStates(): List OnboardingViewState> {
             return listOf(
    -                { copy(asyncDisplayName = Loading()) },
    -                { copy(asyncDisplayName = Success(Unit), personalizationState = personalizationState.copy(displayName = A_DISPLAY_NAME)) }
    +                { copy(isLoading = true) },
    +                { copy(isLoading = false, personalizationState = personalizationState.copy(displayName = A_DISPLAY_NAME)) }
             )
         }
     
    diff --git a/vector/src/test/java/im/vector/app/features/onboarding/RegistrationActionHandlerTest.kt b/vector/src/test/java/im/vector/app/features/onboarding/RegistrationActionHandlerTest.kt
    index 2ca9aaef07..a7fa2a6331 100644
    --- a/vector/src/test/java/im/vector/app/features/onboarding/RegistrationActionHandlerTest.kt
    +++ b/vector/src/test/java/im/vector/app/features/onboarding/RegistrationActionHandlerTest.kt
    @@ -19,7 +19,7 @@ package im.vector.app.features.onboarding
     import im.vector.app.test.fakes.FakeRegistrationWizard
     import im.vector.app.test.fakes.FakeSession
     import io.mockk.coVerifyAll
    -import kotlinx.coroutines.test.runBlockingTest
    +import kotlinx.coroutines.test.runTest
     import org.amshove.kluent.shouldBeEqualTo
     import org.junit.Test
     import org.matrix.android.sdk.api.auth.registration.RegisterThreePid
    @@ -39,7 +39,7 @@ private val A_PID_TO_REGISTER = RegisterThreePid.Email("an email")
     class RegistrationActionHandlerTest {
     
         @Test
    -    fun `when handling register action then delegates to wizard`() = runBlockingTest {
    +    fun `when handling register action then delegates to wizard`() = runTest {
             val cases = listOf(
                     case(RegisterAction.StartRegistration) { getRegistrationFlow() },
                     case(RegisterAction.CaptchaDone(A_CAPTCHA_RESPONSE)) { performReCaptcha(A_CAPTCHA_RESPONSE) },
    diff --git a/vector/src/test/java/im/vector/app/test/Extensions.kt b/vector/src/test/java/im/vector/app/test/Extensions.kt
    index 67eff7ca11..b9521298e2 100644
    --- a/vector/src/test/java/im/vector/app/test/Extensions.kt
    +++ b/vector/src/test/java/im/vector/app/test/Extensions.kt
    @@ -21,12 +21,14 @@ import im.vector.app.core.platform.VectorViewEvents
     import im.vector.app.core.platform.VectorViewModel
     import im.vector.app.core.platform.VectorViewModelAction
     import kotlinx.coroutines.CoroutineScope
    +import kotlinx.coroutines.Dispatchers
     
     fun String.trimIndentOneLine() = trimIndent().replace("\n", "")
     
    -fun  VectorViewModel.test(coroutineScope: CoroutineScope): ViewModelTest {
    -    val state = stateFlow.test(coroutineScope)
    -    val viewEvents = viewEvents.stream().test(coroutineScope)
    +fun  VectorViewModel.test(): ViewModelTest {
    +    val testResultCollectingScope = CoroutineScope(Dispatchers.Unconfined)
    +    val state = stateFlow.test(testResultCollectingScope)
    +    val viewEvents = viewEvents.stream().test(testResultCollectingScope)
         return ViewModelTest(state, viewEvents)
     }