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 d8c1bb6c49..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 @@ -265,6 +265,7 @@ jobs: failure_screenshots/ codecov-units: + name: Unit tests with code coverage runs-on: macos-latest steps: - uses: actions/checkout@v3 @@ -272,7 +273,7 @@ jobs: with: distribution: 'adopt' java-version: '11' - - uses: actions/cache@v2 + - uses: actions/cache@v3 with: path: | ~/.gradle/caches @@ -290,6 +291,7 @@ jobs: build/reports/jacoco/allCodeCoverageReport/allCodeCoverageReport.xml sonarqube: + name: Sonarqube upload runs-on: macos-latest if: always() needs: @@ -300,7 +302,7 @@ jobs: with: distribution: 'adopt' java-version: '11' - - uses: actions/cache@v2 + - uses: actions/cache@v3 with: path: | ~/.gradle/caches @@ -319,6 +321,7 @@ jobs: # Notify the channel about scheduled runs, do not notify for manually triggered runs notify: + name: Notify matrix runs-on: ubuntu-latest needs: - integration-tests @@ -333,4 +336,4 @@ jobs: matrix_access_token: ${{ secrets.ELEMENT_ANDROID_NOTIFICATION_ACCESS_TOKEN }} matrix_room_id: ${{ secrets.ELEMENT_ANDROID_INTERNAL_ROOM_ID }} text_template: "Nightly test run: {{#each job_statuses }}{{#with this }}{{#if completed }} {{name}} {{conclusion}} at {{completed_at}}, {{/if}}{{/with}}{{/each}}" - html_template: "Nightly test run results: {{#each job_statuses }}{{#with this }}{{#if completed }}
{{name}} {{conclusion}} at {{completed_at}} [details]{{/if}}{{/with}}{{/each}}" + html_template: "Nightly test run results: {{#each job_statuses }}{{#with this }}{{#if completed }}
{{icon conclusion}} {{name}} {{conclusion}} at {{completed_at}} [details]{{/if}}{{/with}}{{/each}}" 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/changelog.d/5347.misc b/changelog.d/5347.misc new file mode 100644 index 0000000000..501ff71d7c --- /dev/null +++ b/changelog.d/5347.misc @@ -0,0 +1 @@ +[Rooms list] Do not suggest collapse the unique section \ No newline at end of file diff --git a/changelog.d/5408.misc b/changelog.d/5408.misc new file mode 100644 index 0000000000..3807ee1da8 --- /dev/null +++ b/changelog.d/5408.misc @@ -0,0 +1 @@ +Improved onboarding registration unit test coverage \ No newline at end of file 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/5489.bugfix b/changelog.d/5489.bugfix new file mode 100644 index 0000000000..69ef0118a8 --- /dev/null +++ b/changelog.d/5489.bugfix @@ -0,0 +1 @@ +Fix presence indicator being aligned to the center of the room image \ No newline at end of file diff --git a/changelog.d/5513.misc b/changelog.d/5513.misc new file mode 100644 index 0000000000..767a9f1843 --- /dev/null +++ b/changelog.d/5513.misc @@ -0,0 +1 @@ +Added online presence indicator attribute online to match offline styling diff --git a/changelog.d/5551.bugfix b/changelog.d/5551.bugfix new file mode 100644 index 0000000000..22f9d51e18 --- /dev/null +++ b/changelog.d/5551.bugfix @@ -0,0 +1 @@ +Fix local echos not being shown when re-opening rooms diff --git a/changelog.d/5564.bugfix b/changelog.d/5564.bugfix new file mode 100644 index 0000000000..64891b503c --- /dev/null +++ b/changelog.d/5564.bugfix @@ -0,0 +1 @@ +Fix sometimes read marker not properly updating diff --git a/changelog.d/5571.feature b/changelog.d/5571.feature new file mode 100644 index 0000000000..04b62b8940 --- /dev/null +++ b/changelog.d/5571.feature @@ -0,0 +1 @@ +Live location sharing: Adding indicator view when enabled diff --git a/changelog.d/5572.misc b/changelog.d/5572.misc new file mode 100644 index 0000000000..d37d8fe07d --- /dev/null +++ b/changelog.d/5572.misc @@ -0,0 +1,2 @@ +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/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/build.gradle b/library/jsonviewer/build.gradle index 15f46754b3..0cad8ac171 100644 --- a/library/jsonviewer/build.gradle +++ b/library/jsonviewer/build.gradle @@ -59,7 +59,7 @@ dependencies { implementation libs.jetbrains.coroutinesCore implementation libs.jetbrains.coroutinesAndroid - testImplementation 'org.json:json:20211205' + testImplementation 'org.json:json:20220320' testImplementation libs.tests.junit androidTestImplementation libs.androidx.junit androidTestImplementation libs.androidx.espressoCore 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/colors.xml b/library/ui-styles/src/main/res/values/colors.xml index 75b03a7d2e..d887e7774e 100644 --- a/library/ui-styles/src/main/res/values/colors.xml +++ b/library/ui-styles/src/main/res/values/colors.xml @@ -122,6 +122,10 @@ @color/palette_gray_100 @color/palette_gray_450 + + @color/palette_element_green + @color/palette_element_green + @color/palette_prune diff --git a/library/ui-styles/src/main/res/values/palette_mobile.xml b/library/ui-styles/src/main/res/values/palette_mobile.xml index ec2f1d0814..5610771f8a 100644 --- a/library/ui-styles/src/main/res/values/palette_mobile.xml +++ b/library/ui-styles/src/main/res/values/palette_mobile.xml @@ -53,5 +53,4 @@ @color/palette_verde @color/palette_azure @color/palette_grape - \ 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_location.xml b/library/ui-styles/src/main/res/values/styles_location.xml new file mode 100644 index 0000000000..5563d28342 --- /dev/null +++ b/library/ui-styles/src/main/res/values/styles_location.xml @@ -0,0 +1,11 @@ + + + + + + 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/library/ui-styles/src/main/res/values/theme_dark.xml b/library/ui-styles/src/main/res/values/theme_dark.xml index 06670ccd68..7177687fdd 100644 --- a/library/ui-styles/src/main/res/values/theme_dark.xml +++ b/library/ui-styles/src/main/res/values/theme_dark.xml @@ -43,6 +43,7 @@ @color/vctr_presence_indicator_offline_dark + @color/vctr_presence_indicator_online_dark ?vctr_system diff --git a/library/ui-styles/src/main/res/values/theme_light.xml b/library/ui-styles/src/main/res/values/theme_light.xml index c184464320..c90c021591 100644 --- a/library/ui-styles/src/main/res/values/theme_light.xml +++ b/library/ui-styles/src/main/res/values/theme_light.xml @@ -43,6 +43,7 @@ @color/vctr_presence_indicator_offline_light + @color/vctr_presence_indicator_online_light ?vctr_system 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/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/SendingEventsDataSource.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/SendingEventsDataSource.kt index 1262c09d97..cb61222de7 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/SendingEventsDataSource.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/SendingEventsDataSource.kt @@ -55,6 +55,7 @@ internal class RealmSendingEventsDataSource( roomEntity = RoomEntity.where(safeRealm, roomId = roomId).findFirst() sendingTimelineEvents = roomEntity?.sendingTimelineEvents sendingTimelineEvents?.addChangeListener(sendingTimelineEventsListener) + updateFrozenResults(sendingTimelineEvents) } override fun stop() { 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/ci/render_test_output.py b/tools/ci/render_test_output.py index 1e7940ce04..f955b93cf9 100755 --- a/tools/ci/render_test_output.py +++ b/tools/ci/render_test_output.py @@ -13,32 +13,35 @@ print("::group::Arguments") print(f"{sys.argv}") print("::endgroup::") for xmlfile in xmlfiles: - tree = ET.parse(xmlfile) + try: + tree = ET.parse(xmlfile) - root = tree.getroot() - name = root.attrib['name'] - time = root.attrib['time'] - tests = int(root.attrib['tests']) - skipped = int(root.attrib['skipped']) - errors = int(root.attrib['errors']) - failures = int(root.attrib['failures']) - success = tests - failures - errors - skipped - total = tests - skipped - print(f"::group::{name} {success}/{total} ({skipped} skipped) in {time}") - - for testcase in root: - if testcase.tag != "testcase": - continue - testname = testcase.attrib['classname'] - message = testcase.attrib['name'] - time = testcase.attrib['time'] - child = testcase.find("failure") - if child is None: - print(f"{message} in {time}s") - else: - print(f"::error file={testname}::{message} in {time}s") - print(child.text) - body = f"passed={success} failures={failures} errors={errors} skipped={skipped}" - print(f"::set-output name={suitename}::={body}") + root = tree.getroot() + name = root.attrib['name'] + time = root.attrib['time'] + tests = int(root.attrib['tests']) + skipped = int(root.attrib['skipped']) + errors = int(root.attrib['errors']) + failures = int(root.attrib['failures']) + success = tests - failures - errors - skipped + total = tests - skipped + print(f"::group::{name} {success}/{total} ({skipped} skipped) in {time}") + + for testcase in root: + if testcase.tag != "testcase": + continue + testname = testcase.attrib['classname'] + message = testcase.attrib['name'] + time = testcase.attrib['time'] + child = testcase.find("failure") + if child is None: + print(f"{message} in {time}s") + else: + print(f"::error file={testname}::{message} in {time}s") + print(child.text) + body = f" passed={success} failures={failures} errors={errors} skipped={skipped}" + print(f"::set-output name={suitename}::={body}") + except FileNotFoundError: + print(f"::error::Unable to open test results file {xmlfile} - check if the tests completed") print("::endgroup::") diff --git a/tools/emojis/emoji_picker_datasource_formatted.json b/tools/emojis/emoji_picker_datasource_formatted.json index c1aa590003..a1b944a7eb 100644 --- a/tools/emojis/emoji_picker_datasource_formatted.json +++ b/tools/emojis/emoji_picker_datasource_formatted.json @@ -2056,7 +2056,9 @@ "disappear", "dissolve", "liquid", - "melt" + "melt", + "hot", + "heat" ] }, "winking-face": { @@ -2351,7 +2353,10 @@ "disbelief", "embarrass", "scared", - "surprise" + "surprise", + "silence", + "secret", + "shock" ] }, "face-with-peeking-eye": { @@ -2360,7 +2365,10 @@ "j": [ "captivated", "peep", - "stare" + "stare", + "scared", + "frightening", + "embarrassing" ] }, "shushing-face": { @@ -2392,7 +2400,8 @@ "salute", "sunny", "troops", - "yes" + "yes", + "respect" ] }, "zippermouth-face": { @@ -2467,7 +2476,10 @@ "disappear", "hide", "introvert", - "invisible" + "invisible", + "lonely", + "isolation", + "depression" ] }, "face-in-clouds": { @@ -2863,7 +2875,11 @@ "disappointed", "meh", "skeptical", - "unsure" + "unsure", + "skeptic", + "confuse", + "frustrated", + "indifferent" ] }, "worried-face": { @@ -2969,7 +2985,9 @@ "cry", "proud", "resist", - "sad" + "sad", + "touched", + "gratitude" ] }, "frowning-face-with-open-mouth": { @@ -4065,7 +4083,9 @@ "j": [ "hand", "right", - "rightward" + "rightward", + "palm", + "offer" ] }, "leftwards-hand": { @@ -4074,7 +4094,9 @@ "j": [ "hand", "left", - "leftward" + "leftward", + "palm", + "offer" ] }, "palm-down-hand": { @@ -4083,7 +4105,8 @@ "j": [ "dismiss", "drop", - "shoo" + "shoo", + "palm" ] }, "palm-up-hand": { @@ -4093,7 +4116,9 @@ "beckon", "catch", "come", - "offer" + "offer", + "lift", + "demand" ] }, "ok-hand": { @@ -4290,7 +4315,8 @@ "b": "1FAF5", "j": [ "point", - "you" + "you", + "recruit" ] }, "thumbs-up": { @@ -4404,7 +4430,9 @@ "a": "⊛ Heart Hands", "b": "1FAF6", "j": [ - "love" + "love", + "appreciation", + "support" ] }, "open-hands": { @@ -4662,7 +4690,11 @@ "flirting", "nervous", "uncomfortable", - "worried" + "worried", + "flirt", + "sexy", + "pain", + "worry" ] }, "baby": { @@ -6058,7 +6090,8 @@ "monarch", "noble", "regal", - "royalty" + "royalty", + "power" ] }, "prince": { @@ -6231,7 +6264,8 @@ "belly", "bloated", "full", - "pregnant" + "pregnant", + "baby" ] }, "pregnant-person": { @@ -6241,7 +6275,8 @@ "belly", "bloated", "full", - "pregnant" + "pregnant", + "baby" ] }, "breastfeeding": { @@ -6635,7 +6670,8 @@ "j": [ "fairy tale", "fantasy", - "monster" + "monster", + "mystical" ] }, "person-getting-massage": { @@ -9374,7 +9410,8 @@ "b": "1FAB8", "j": [ "ocean", - "reef" + "reef", + "sea" ] }, "snail": { @@ -9587,7 +9624,9 @@ "Hinduism", "India", "purity", - "Vietnam" + "Vietnam", + "calm", + "meditation" ] }, "rosette": { @@ -9832,14 +9871,16 @@ "a": "⊛ Empty Nest", "b": "1FAB9", "j": [ - "nesting" + "nesting", + "bird" ] }, "nest-with-eggs": { "a": "⊛ Nest with Eggs", "b": "1FABA", "j": [ - "nesting" + "nesting", + "bird" ] }, "grapes": { @@ -11187,7 +11228,9 @@ "drink", "empty", "glass", - "spill" + "spill", + "cup", + "water" ] }, "cup-with-straw": { @@ -12003,7 +12046,9 @@ "b": "1F6DD", "j": [ "amusement park", - "play" + "play", + "fun", + "park" ] }, "ferris-wheel": { @@ -12533,7 +12578,9 @@ "j": [ "circle", "tire", - "turn" + "turn", + "car", + "transport" ] }, "police-car-light": { @@ -14666,7 +14713,8 @@ "hand", "Mary", "Miriam", - "protection" + "protection", + "religion" ] }, "video-game": { @@ -15864,7 +15912,9 @@ "b": "1FAAB", "j": [ "electronic", - "low energy" + "low energy", + "drained", + "dead" ] }, "electric-plug": { @@ -17508,7 +17558,9 @@ "disability", "hurt", "mobility aid", - "stick" + "stick", + "accessibility", + "assist" ] }, "stethoscope": { @@ -17528,7 +17580,9 @@ "bones", "doctor", "medical", - "skeleton" + "skeleton", + "x-ray", + "medicine" ] }, "door": { @@ -17733,7 +17787,10 @@ "burp", "clean", "soap", - "underwater" + "underwater", + "fun", + "carbonation", + "sparkling" ] }, "toothbrush": { @@ -17856,7 +17913,8 @@ "credentials", "ID", "license", - "security" + "security", + "document" ] }, "atm-sign": { 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..04b20599d4 100644 --- a/vector/build.gradle +++ b/vector/build.gradle @@ -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 1c06d0ef58..c6d0b45a04 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
    @@ -177,6 +176,7 @@ import im.vector.app.features.html.PillsPostProcessor
     import im.vector.app.features.invite.VectorInviteView
     import im.vector.app.features.location.LocationSharingMode
     import im.vector.app.features.location.toLocationData
    +import im.vector.app.features.media.AttachmentData
     import im.vector.app.features.media.ImageContentRenderer
     import im.vector.app.features.media.VideoContentRenderer
     import im.vector.app.features.notifications.NotificationDrawerManager
    @@ -445,7 +445,7 @@ class TimelineFragment @Inject constructor(
                         }
                         showErrorInSnackbar(it.throwable)
                     }
    -            }.exhaustive
    +            }
             }
     
             timelineViewModel.observeViewEvents {
    @@ -482,7 +482,7 @@ class TimelineFragment @Inject constructor(
                     RoomDetailViewEvents.StopChatEffects                     -> handleStopChatEffects()
                     is RoomDetailViewEvents.DisplayAndAcceptCall             -> acceptIncomingCall(it)
                     RoomDetailViewEvents.RoomReplacementStarted              -> handleRoomReplacement()
    -            }.exhaustive
    +            }
             }
     
             if (savedInstanceState == null) {
    @@ -783,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))
    @@ -874,7 +886,7 @@ class TimelineFragment @Inject constructor(
                     onContentAttachmentsReady(sharedData.attachmentData)
                 }
                 null                      -> Timber.v("No share data to process")
    -        }.exhaustive
    +        }
         }
     
         private fun handleSpaceShare() {
    @@ -1240,7 +1252,7 @@ class TimelineFragment @Inject constructor(
                     insertUserDisplayNameInTextEditor(roomDetailPendingAction.userId)
                 is RoomDetailPendingAction.OpenRoom          ->
                     handleOpenRoom(RoomDetailViewEvents.OpenRoom(roomDetailPendingAction.roomId, roomDetailPendingAction.closeCurrentRoom))
    -        }.exhaustive
    +        }
         }
     
         override fun onPause() {
    @@ -1612,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 {
    @@ -1658,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
         }
    @@ -1782,6 +1793,7 @@ class TimelineFragment @Inject constructor(
                             transactionId = data.transactionId,
                     ).show(parentFragmentManager, "REQ")
                 }
    +            else                                          -> Unit
             }
         }
     
    @@ -1871,12 +1883,16 @@ class TimelineFragment @Inject constructor(
             vectorBaseActivity.notImplemented("encrypted message click")
         }
     
    -    override fun onImageMessageClicked(messageImageContent: MessageImageInfoContent, mediaData: ImageContentRenderer.Data, view: View) {
    +    override fun onImageMessageClicked(messageImageContent: MessageImageInfoContent,
    +                                       mediaData: ImageContentRenderer.Data,
    +                                       view: View,
    +                                       inMemory: List) {
             navigator.openMediaViewer(
                     activity = requireActivity(),
                     roomId = timelineArgs.roomId,
                     mediaData = mediaData,
    -                view = view
    +                view = view,
    +                inMemory = inMemory
             ) { pairs ->
                 pairs.add(Pair(views.roomToolbar, ViewCompat.getTransitionName(views.roomToolbar) ?: ""))
                 pairs.add(Pair(views.composerLayout, ViewCompat.getTransitionName(views.composerLayout) ?: ""))
    @@ -2047,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)
    @@ -2232,6 +2256,8 @@ class TimelineFragment @Inject constructor(
                 is EventSharedAction.EndPoll                    -> {
                     askConfirmationToEndPoll(action.eventId)
                 }
    +            is EventSharedAction.ReportContent              -> Unit /* Not clickable */
    +            EventSharedAction.Separator                     -> Unit /* Not clickable */
             }
         }
     
    @@ -2430,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 fb47fb5136..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
    @@ -57,6 +57,7 @@ import im.vector.app.features.home.room.detail.timeline.item.ReactionsSummaryEve
     import im.vector.app.features.home.room.detail.timeline.item.ReadReceiptData
     import im.vector.app.features.home.room.detail.timeline.item.ReadReceiptsItem
     import im.vector.app.features.home.room.detail.timeline.url.PreviewUrlRetriever
    +import im.vector.app.features.media.AttachmentData
     import im.vector.app.features.media.ImageContentRenderer
     import im.vector.app.features.media.VideoContentRenderer
     import im.vector.app.features.settings.VectorPreferences
    @@ -127,7 +128,10 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec
             fun onEventVisible(event: TimelineEvent)
             fun onRoomCreateLinkClicked(url: String)
             fun onEncryptedMessageClicked(informationData: MessageInformationData, view: View)
    -        fun onImageMessageClicked(messageImageContent: MessageImageInfoContent, mediaData: ImageContentRenderer.Data, view: View)
    +        fun onImageMessageClicked(messageImageContent: MessageImageInfoContent,
    +                                  mediaData: ImageContentRenderer.Data,
    +                                  view: View,
    +                                  inMemory: List)
             fun onVideoMessageClicked(messageVideoContent: MessageVideoContent, mediaData: VideoContentRenderer.Data, view: View)
     
             //        fun onFileMessageClicked(eventId: String, messageFileContent: MessageFileContent)
    @@ -141,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 3189954e20..9c15532376 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
    @@ -74,6 +74,7 @@ import im.vector.app.features.location.toLocationData
     import im.vector.app.features.media.ImageContentRenderer
     import im.vector.app.features.media.VideoContentRenderer
     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
    @@ -362,11 +363,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(
    @@ -480,9 +494,12 @@ class MessageItemFactory @Inject constructor(
                     .apply {
                         if (messageContent.msgType == MessageType.MSGTYPE_STICKER_LOCAL) {
                             mode(ImageContentRenderer.Mode.STICKER)
    +                        clickListener { view ->
    +                            callback?.onImageMessageClicked(messageContent, data, view, listOf(data))
    +                        }
                         } else {
                             clickListener { view ->
    -                            callback?.onImageMessageClicked(messageContent, data, view)
    +                            callback?.onImageMessageClicked(messageContent, data, view, emptyList())
                             }
                         }
                     }
    @@ -696,8 +713,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..a827966a58 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
    @@ -295,7 +294,8 @@ class RoomListFragment @Inject constructor(
                                             section.notificationCount.observe(viewLifecycleOwner) { counts ->
                                                 sectionAdapter.updateSection(sectionAdapter.roomsSectionData.copy(
                                                         notificationCount = counts.totalCount,
    -                                                    isHighlighted = counts.isHighlight
    +                                                    isHighlighted = counts.isHighlight,
    +                                                    shouldShowExpandedArrow = shouldShowExpendedArrow()
                                                 ))
                                             }
                                             section.isExpanded.observe(viewLifecycleOwner) { _ ->
    @@ -329,14 +329,17 @@ class RoomListFragment @Inject constructor(
                                                 controller.setData(list)
                                                 sectionAdapter.updateSection(sectionAdapter.roomsSectionData.copy(
                                                         isHidden = list.isEmpty(),
    -                                                    isLoading = false))
    +                                                    isLoading = false,
    +                                                    shouldShowExpandedArrow = shouldShowExpendedArrow()
    +                                            ))
                                                 checkEmptyState()
                                             }
                                             observeItemCount(section, sectionAdapter)
                                             section.notificationCount.observe(viewLifecycleOwner) { counts ->
                                                 sectionAdapter.updateSection(sectionAdapter.roomsSectionData.copy(
                                                         notificationCount = counts.totalCount,
    -                                                    isHighlighted = counts.isHighlight
    +                                                    isHighlighted = counts.isHighlight,
    +                                                    shouldShowExpandedArrow = shouldShowExpendedArrow()
                                                 ))
                                             }
                                             section.isExpanded.observe(viewLifecycleOwner) { _ ->
    @@ -418,7 +421,7 @@ class RoomListFragment @Inject constructor(
                 is RoomListQuickActionsSharedAction.Leave                     -> {
                     promptLeaveRoom(quickAction.roomId)
                 }
    -        }.exhaustive
    +        }
         }
     
         private fun promptLeaveRoom(roomId: String) {
    @@ -444,6 +447,10 @@ class RoomListFragment @Inject constructor(
             footerController.setData(state)
         }
     
    +    private fun shouldShowExpendedArrow(): Boolean {
    +        return adapterInfosList.filter { !it.sectionHeaderAdapter.roomsSectionData.isHidden }.size >= 2
    +    }
    +
         private fun checkEmptyState() {
             val shouldShowEmpty = adapterInfosList.all { it.sectionHeaderAdapter.roomsSectionData.isHidden } &&
                     !adapterInfosList.any { it.sectionHeaderAdapter.roomsSectionData.isLoading }
    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/home/room/list/SectionHeaderAdapter.kt b/vector/src/main/java/im/vector/app/features/home/room/list/SectionHeaderAdapter.kt
    index 2e6436d21d..cd2879cf28 100644
    --- a/vector/src/main/java/im/vector/app/features/home/room/list/SectionHeaderAdapter.kt
    +++ b/vector/src/main/java/im/vector/app/features/home/room/list/SectionHeaderAdapter.kt
    @@ -17,6 +17,7 @@
     package im.vector.app.features.home.room.list
     
     import android.view.LayoutInflater
    +import android.view.View
     import android.view.ViewGroup
     import androidx.core.content.ContextCompat
     import androidx.core.graphics.drawable.DrawableCompat
    @@ -39,7 +40,8 @@ class SectionHeaderAdapter constructor(
                 val isHighlighted: Boolean = false,
                 val isHidden: Boolean = true,
                 // This will be false until real data has been submitted once
    -            val isLoading: Boolean = true
    +            val isLoading: Boolean = true,
    +            val shouldShowExpandedArrow: Boolean = false
         )
     
         lateinit var roomsSectionData: RoomsSectionData
    @@ -82,11 +84,16 @@ class SectionHeaderAdapter constructor(
             fun bind(roomsSectionData: RoomsSectionData) {
                 binding.roomCategoryTitleView.text = roomsSectionData.name
                 val tintColor = ThemeUtils.getColor(binding.root.context, R.attr.vctr_content_secondary)
    -            val expandedArrowDrawableRes = if (roomsSectionData.isExpanded) R.drawable.ic_expand_more else R.drawable.ic_expand_less
    -            val expandedArrowDrawable = ContextCompat.getDrawable(binding.root.context, expandedArrowDrawableRes)?.also {
    -                DrawableCompat.setTint(it, tintColor)
    +            if (roomsSectionData.shouldShowExpandedArrow) {
    +                binding.roomCategoryCounterView.visibility = View.VISIBLE
    +                val expandedArrowDrawableRes = if (roomsSectionData.isExpanded) R.drawable.ic_expand_more else R.drawable.ic_expand_less
    +                val expandedArrowDrawable = ContextCompat.getDrawable(binding.root.context, expandedArrowDrawableRes)?.also {
    +                    DrawableCompat.setTint(it, tintColor)
    +                }
    +                binding.roomCategoryCounterView.setCompoundDrawablesWithIntrinsicBounds(null, null, expandedArrowDrawable, null)
    +            } else {
    +                binding.roomCategoryCounterView.visibility = View.GONE
                 }
    -            binding.roomCategoryCounterView.setCompoundDrawablesWithIntrinsicBounds(null, null, expandedArrowDrawable, null)
                 binding.roomCategoryCounterView.text = roomsSectionData.itemCount.takeIf { it > 0 }?.toString().orEmpty()
                 binding.roomCategoryUnreadCounterBadgeView.render(UnreadCounterBadgeView.State(roomsSectionData.notificationCount, roomsSectionData.isHighlighted))
             }
    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 84efff0e0d..d4582d98b9 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
    @@ -31,7 +31,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
    @@ -89,7 +88,7 @@ class LocationSharingFragment @Inject constructor(
                     LocationSharingViewEvents.LocationNotAvailableError   -> handleLocationNotAvailableError()
                     is LocationSharingViewEvents.ZoomToUserLocation       -> handleZoomToUserLocationEvent(it)
                     is LocationSharingViewEvents.StartLiveLocationService -> handleStartLiveLocationService(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 8293ac6143..347aad7db9 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
    @@ -123,7 +122,7 @@ class LocationSharingViewModel @AssistedInject constructor(
                 is LocationSharingAction.LocationTargetChange     -> handleLocationTargetChangeAction(action)
                 LocationSharingAction.ZoomToUserLocation          -> handleZoomToUserLocationAction()
                 is LocationSharingAction.StartLiveLocationSharing -> handleStartLiveLocationSharingAction(action.duration)
    -        }.exhaustive
    +        }
         }
     
         private fun handleCurrentUserLocationSharingAction() = withState { state ->
    diff --git a/vector/src/main/java/im/vector/app/features/location/live/LocationLiveStatusView.kt b/vector/src/main/java/im/vector/app/features/location/live/LocationLiveStatusView.kt
    new file mode 100644
    index 0000000000..a4c58c9e5b
    --- /dev/null
    +++ b/vector/src/main/java/im/vector/app/features/location/live/LocationLiveStatusView.kt
    @@ -0,0 +1,39 @@
    +/*
    + * 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.location.live
    +
    +import android.content.Context
    +import android.util.AttributeSet
    +import android.view.LayoutInflater
    +import android.widget.Button
    +import androidx.constraintlayout.widget.ConstraintLayout
    +import im.vector.app.databinding.ViewLocationLiveStatusBinding
    +
    +class LocationLiveStatusView @JvmOverloads constructor(
    +        context: Context,
    +        attrs: AttributeSet? = null,
    +        defStyleAttr: Int = 0
    +) : ConstraintLayout(context, attrs, defStyleAttr) {
    +
    +    private val binding = ViewLocationLiveStatusBinding.inflate(
    +            LayoutInflater.from(context),
    +            this
    +    )
    +
    +    val stopButton: Button
    +        get() = binding.locationLiveStatusStop
    +}
    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/media/RoomEventsAttachmentProvider.kt b/vector/src/main/java/im/vector/app/features/media/RoomEventsAttachmentProvider.kt
    index 1e0a3a2ad9..781a176550 100644
    --- a/vector/src/main/java/im/vector/app/features/media/RoomEventsAttachmentProvider.kt
    +++ b/vector/src/main/java/im/vector/app/features/media/RoomEventsAttachmentProvider.kt
    @@ -25,6 +25,7 @@ import org.matrix.android.sdk.api.session.events.model.toModel
     import org.matrix.android.sdk.api.session.file.FileService
     import org.matrix.android.sdk.api.session.room.model.message.MessageContent
     import org.matrix.android.sdk.api.session.room.model.message.MessageImageContent
    +import org.matrix.android.sdk.api.session.room.model.message.MessageStickerContent
     import org.matrix.android.sdk.api.session.room.model.message.MessageVideoContent
     import org.matrix.android.sdk.api.session.room.model.message.MessageWithAttachmentContent
     import org.matrix.android.sdk.api.session.room.model.message.getFileUrl
    @@ -52,7 +53,10 @@ class RoomEventsAttachmentProvider(
     
         override fun getAttachmentInfoAt(position: Int): AttachmentInfo {
             return getItem(position).let {
    -            val content = it.root.getClearContent().toModel() as? MessageWithAttachmentContent
    +            val clearContent = it.root.getClearContent()
    +            val content = clearContent.toModel()
    +                    ?: clearContent.toModel()
    +                            as? MessageWithAttachmentContent
                 if (content is MessageImageContent) {
                     val data = ImageContentRenderer.Data(
                             eventId = it.eventId,
    @@ -66,6 +70,33 @@ class RoomEventsAttachmentProvider(
                             height = null,
                             allowNonMxcUrls = it.root.sendState.isSending()
     
    +                )
    +                if (content.mimeType == MimeTypes.Gif) {
    +                    AttachmentInfo.AnimatedImage(
    +                            uid = it.eventId,
    +                            url = content.url ?: "",
    +                            data = data
    +                    )
    +                } else {
    +                    AttachmentInfo.Image(
    +                            uid = it.eventId,
    +                            url = content.url ?: "",
    +                            data = data
    +                    )
    +                }
    +            } else if (content is MessageStickerContent) {
    +                val data = ImageContentRenderer.Data(
    +                        eventId = it.eventId,
    +                        filename = content.body,
    +                        mimeType = content.mimeType,
    +                        url = content.getFileUrl(),
    +                        elementToDecrypt = content.encryptedFileInfo?.toElementToDecrypt(),
    +                        maxHeight = -1,
    +                        maxWidth = -1,
    +                        width = null,
    +                        height = null,
    +                        allowNonMxcUrls = false
    +
                     )
                     if (content.mimeType == MimeTypes.Gif) {
                         AttachmentInfo.AnimatedImage(
    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 cc02687d93..4c3ce80339 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/OnboardingAction.kt b/vector/src/main/java/im/vector/app/features/onboarding/OnboardingAction.kt
    index 4f16231747..7fa75d1544 100644
    --- a/vector/src/main/java/im/vector/app/features/onboarding/OnboardingAction.kt
    +++ b/vector/src/main/java/im/vector/app/features/onboarding/OnboardingAction.kt
    @@ -22,63 +22,49 @@ import im.vector.app.features.login.LoginConfig
     import im.vector.app.features.login.ServerType
     import im.vector.app.features.login.SignMode
     import org.matrix.android.sdk.api.auth.data.Credentials
    -import org.matrix.android.sdk.api.auth.registration.RegisterThreePid
     import org.matrix.android.sdk.internal.network.ssl.Fingerprint
     
    -sealed class OnboardingAction : VectorViewModelAction {
    -    data class OnGetStarted(val resetLoginConfig: Boolean, val onboardingFlow: OnboardingFlow) : OnboardingAction()
    -    data class OnIAlreadyHaveAnAccount(val resetLoginConfig: Boolean, val onboardingFlow: OnboardingFlow) : OnboardingAction()
    +sealed interface OnboardingAction : VectorViewModelAction {
    +    data class OnGetStarted(val resetLoginConfig: Boolean, val onboardingFlow: OnboardingFlow) : OnboardingAction
    +    data class OnIAlreadyHaveAnAccount(val resetLoginConfig: Boolean, val onboardingFlow: OnboardingFlow) : OnboardingAction
     
    -    data class UpdateServerType(val serverType: ServerType) : OnboardingAction()
    -    data class UpdateHomeServer(val homeServerUrl: String) : OnboardingAction()
    -    data class UpdateUseCase(val useCase: FtueUseCase) : OnboardingAction()
    -    object ResetUseCase : OnboardingAction()
    -    data class UpdateSignMode(val signMode: SignMode) : OnboardingAction()
    -    data class LoginWithToken(val loginToken: String) : OnboardingAction()
    -    data class WebLoginSuccess(val credentials: Credentials) : OnboardingAction()
    -    data class InitWith(val loginConfig: LoginConfig?) : OnboardingAction()
    -    data class ResetPassword(val email: String, val newPassword: String) : OnboardingAction()
    -    object ResetPasswordMailConfirmed : OnboardingAction()
    +    data class UpdateServerType(val serverType: ServerType) : OnboardingAction
    +    data class UpdateHomeServer(val homeServerUrl: String) : OnboardingAction
    +    data class UpdateUseCase(val useCase: FtueUseCase) : OnboardingAction
    +    object ResetUseCase : OnboardingAction
    +    data class UpdateSignMode(val signMode: SignMode) : OnboardingAction
    +    data class LoginWithToken(val loginToken: String) : OnboardingAction
    +    data class WebLoginSuccess(val credentials: Credentials) : OnboardingAction
    +    data class InitWith(val loginConfig: LoginConfig?) : OnboardingAction
    +    data class ResetPassword(val email: String, val newPassword: String) : OnboardingAction
    +    object ResetPasswordMailConfirmed : OnboardingAction
     
         // Login or Register, depending on the signMode
    -    data class LoginOrRegister(val username: String, val password: String, val initialDeviceName: String) : OnboardingAction()
    +    data class LoginOrRegister(val username: String, val password: String, val initialDeviceName: String) : OnboardingAction
    +    object StopEmailValidationCheck : OnboardingAction
     
    -    // Register actions
    -    open class RegisterAction : OnboardingAction()
    -
    -    data class AddThreePid(val threePid: RegisterThreePid) : RegisterAction()
    -    object SendAgainThreePid : RegisterAction()
    -
    -    // TODO Confirm Email (from link in the email, open in the phone, intercepted by the app)
    -    data class ValidateThreePid(val code: String) : RegisterAction()
    -
    -    data class CheckIfEmailHasBeenValidated(val delayMillis: Long) : RegisterAction()
    -    object StopEmailValidationCheck : RegisterAction()
    -
    -    data class CaptchaDone(val captchaResponse: String) : RegisterAction()
    -    object AcceptTerms : RegisterAction()
    -    object RegisterDummy : RegisterAction()
    +    data class PostRegisterAction(val registerAction: RegisterAction) : OnboardingAction
     
         // Reset actions
    -    open class ResetAction : OnboardingAction()
    +    sealed interface ResetAction : OnboardingAction
     
    -    object ResetHomeServerType : ResetAction()
    -    object ResetHomeServerUrl : ResetAction()
    -    object ResetSignMode : ResetAction()
    -    object ResetLogin : ResetAction()
    -    object ResetResetPassword : ResetAction()
    +    object ResetHomeServerType : ResetAction
    +    object ResetHomeServerUrl : ResetAction
    +    object ResetSignMode : ResetAction
    +    object ResetLogin : ResetAction
    +    object ResetResetPassword : ResetAction
     
         // Homeserver history
    -    object ClearHomeServerHistory : OnboardingAction()
    +    object ClearHomeServerHistory : OnboardingAction
     
    -    data class PostViewEvent(val viewEvent: OnboardingViewEvents) : OnboardingAction()
    +    data class PostViewEvent(val viewEvent: OnboardingViewEvents) : OnboardingAction
     
    -    data class UserAcceptCertificate(val fingerprint: Fingerprint) : OnboardingAction()
    +    data class UserAcceptCertificate(val fingerprint: Fingerprint) : OnboardingAction
     
    -    object PersonalizeProfile : OnboardingAction()
    -    data class UpdateDisplayName(val displayName: String) : OnboardingAction()
    -    object UpdateDisplayNameSkipped : OnboardingAction()
    -    data class ProfilePictureSelected(val uri: Uri) : OnboardingAction()
    -    object SaveSelectedProfilePicture : OnboardingAction()
    -    object UpdateProfilePictureSkipped : OnboardingAction()
    +    object PersonalizeProfile : OnboardingAction
    +    data class UpdateDisplayName(val displayName: String) : OnboardingAction
    +    object UpdateDisplayNameSkipped : OnboardingAction
    +    data class ProfilePictureSelected(val uri: Uri) : OnboardingAction
    +    object SaveSelectedProfilePicture : OnboardingAction
    +    object UpdateProfilePictureSkipped : OnboardingAction
     }
    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 36020fbe61..e7302cb1e2 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
    @@ -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.extensions.vectorStore
     import im.vector.app.core.platform.VectorViewModel
     import im.vector.app.core.resources.StringProvider
    @@ -83,6 +82,7 @@ class OnboardingViewModel @AssistedInject constructor(
             private val vectorFeatures: VectorFeatures,
             private val analyticsTracker: AnalyticsTracker,
             private val uriFilenameResolver: UriFilenameResolver,
    +        private val registrationActionHandler: RegistrationActionHandler,
             private val vectorOverrides: VectorOverrides
     ) : VectorViewModel(initialState) {
     
    @@ -116,16 +116,16 @@ class OnboardingViewModel @AssistedInject constructor(
     
         private val matrixOrgUrl = stringProvider.getString(R.string.matrix_org_server_url).ensureTrailingSlash()
     
    +    private val registrationWizard: RegistrationWizard
    +        get() = authenticationService.getRegistrationWizard()
    +
         val currentThreePid: String?
    -        get() = registrationWizard?.currentThreePid
    +        get() = registrationWizard.currentThreePid
     
         // True when login and password has been sent with success to the homeserver
         val isRegistrationStarted: Boolean
             get() = authenticationService.isRegistrationStarted
     
    -    private val registrationWizard: RegistrationWizard?
    -        get() = authenticationService.getRegistrationWizard()
    -
         private val loginWizard: LoginWizard?
             get() = authenticationService.getLoginWizard()
     
    @@ -153,7 +153,7 @@ class OnboardingViewModel @AssistedInject constructor(
                 is OnboardingAction.WebLoginSuccess            -> handleWebLoginSuccess(action)
                 is OnboardingAction.ResetPassword              -> handleResetPassword(action)
                 is OnboardingAction.ResetPasswordMailConfirmed -> handleResetPasswordMailConfirmed()
    -            is OnboardingAction.RegisterAction             -> handleRegisterAction(action)
    +            is OnboardingAction.PostRegisterAction         -> handleRegisterAction(action.registerAction)
                 is OnboardingAction.ResetAction                -> handleResetAction(action)
                 is OnboardingAction.UserAcceptCertificate      -> handleUserAcceptCertificate(action)
                 OnboardingAction.ClearHomeServerHistory        -> handleClearHomeServerHistory()
    @@ -164,7 +164,8 @@ class OnboardingViewModel @AssistedInject constructor(
                 is OnboardingAction.ProfilePictureSelected     -> handleProfilePictureSelected(action)
                 OnboardingAction.SaveSelectedProfilePicture    -> updateProfilePicture()
                 is OnboardingAction.PostViewEvent              -> _viewEvents.post(action.viewEvent)
    -        }.exhaustive
    +            OnboardingAction.StopEmailValidationCheck      -> cancelWaitForEmailValidation()
    +        }
         }
     
         private fun handleSplashAction(resetConfig: Boolean, onboardingFlow: OnboardingFlow) {
    @@ -220,6 +221,7 @@ class OnboardingViewModel @AssistedInject constructor(
                                     .withAllowedFingerPrints(listOf(action.fingerprint))
                                     .build()
                     )
    +            else                                 -> Unit
             }
         }
     
    @@ -266,131 +268,41 @@ class OnboardingViewModel @AssistedInject constructor(
             }
         }
     
    -    private fun handleRegisterAction(action: OnboardingAction.RegisterAction) {
    -        when (action) {
    -            is OnboardingAction.CaptchaDone                  -> handleCaptchaDone(action)
    -            is OnboardingAction.AcceptTerms                  -> handleAcceptTerms()
    -            is OnboardingAction.RegisterDummy                -> handleRegisterDummy()
    -            is OnboardingAction.AddThreePid                  -> handleAddThreePid(action)
    -            is OnboardingAction.SendAgainThreePid            -> handleSendAgainThreePid()
    -            is OnboardingAction.ValidateThreePid             -> handleValidateThreePid(action)
    -            is OnboardingAction.CheckIfEmailHasBeenValidated -> handleCheckIfEmailHasBeenValidated(action)
    -            is OnboardingAction.StopEmailValidationCheck     -> handleStopEmailValidationCheck()
    -        }
    -    }
    -
    -    private fun handleCheckIfEmailHasBeenValidated(action: OnboardingAction.CheckIfEmailHasBeenValidated) {
    -        // We do not want the common progress bar to be displayed, so we do not change asyncRegistration value in the state
    -        currentJob = executeRegistrationStep(withLoading = false) {
    -            it.checkIfEmailHasBeenValidated(action.delayMillis)
    -        }
    -    }
    -
    -    private fun handleStopEmailValidationCheck() {
    -        currentJob = null
    -    }
    -
    -    private fun handleValidateThreePid(action: OnboardingAction.ValidateThreePid) {
    -        currentJob = executeRegistrationStep {
    -            it.handleValidateThreePid(action.code)
    -        }
    -    }
    -
    -    private fun executeRegistrationStep(withLoading: Boolean = true,
    -                                        block: suspend (RegistrationWizard) -> RegistrationResult): Job {
    -        if (withLoading) {
    -            setState { copy(asyncRegistration = Loading()) }
    -        }
    -        return viewModelScope.launch {
    -            try {
    -                registrationWizard?.let { block(it) }
    -                /*
    -                   // Simulate registration disabled
    -                   throw Failure.ServerError(MatrixError(
    -                           code = MatrixError.FORBIDDEN,
    -                           message = "Registration is disabled"
    -                   ), 403))
    -                */
    -            } catch (failure: Throwable) {
    -                if (failure !is CancellationException) {
    -                    _viewEvents.post(OnboardingViewEvents.Failure(failure))
    -                }
    -                null
    -            }
    -                    ?.let { data ->
    -                        when (data) {
    -                            is RegistrationResult.Success      -> onSessionCreated(data.session, isAccountCreated = true)
    -                            is RegistrationResult.FlowResponse -> onFlowResponse(data.flowResult)
    -                        }
    -                    }
    -
    -            setState {
    -                copy(
    -                        asyncRegistration = Uninitialized
    -                )
    -            }
    -        }
    -    }
    -
    -    private fun handleAddThreePid(action: OnboardingAction.AddThreePid) {
    -        setState { copy(asyncRegistration = Loading()) }
    +    private fun handleRegisterAction(action: RegisterAction) {
             currentJob = viewModelScope.launch {
    -            try {
    -                registrationWizard?.addThreePid(action.threePid)
    -            } catch (failure: Throwable) {
    -                _viewEvents.post(OnboardingViewEvents.Failure(failure))
    +            if (action.hasLoadingState()) {
    +                setState { copy(asyncRegistration = Loading()) }
                 }
    -            setState {
    -                copy(
    -                        asyncRegistration = Uninitialized
    -                )
    -            }
    -        }
    -    }
    -
    -    private fun handleSendAgainThreePid() {
    -        setState { copy(asyncRegistration = Loading()) }
    -        currentJob = viewModelScope.launch {
    -            try {
    -                registrationWizard?.sendAgainThreePid()
    -            } catch (failure: Throwable) {
    -                _viewEvents.post(OnboardingViewEvents.Failure(failure))
    -            }
    -            setState {
    -                copy(
    -                        asyncRegistration = Uninitialized
    -                )
    -            }
    -        }
    -    }
    -
    -    private fun handleAcceptTerms() {
    -        currentJob = executeRegistrationStep {
    -            it.acceptTerms()
    -        }
    -    }
    -
    -    private fun handleRegisterDummy() {
    -        currentJob = executeRegistrationStep {
    -            it.dummy()
    +            runCatching { registrationActionHandler.handleRegisterAction(registrationWizard, action) }
    +                    .fold(
    +                            onSuccess = {
    +                                when {
    +                                    action.ignoresResult() -> {
    +                                        // do nothing
    +                                    }
    +                                    else                   -> when (it) {
    +                                        is RegistrationResult.Success      -> onSessionCreated(it.session, isAccountCreated = true)
    +                                        is RegistrationResult.FlowResponse -> onFlowResponse(it.flowResult)
    +                                    }
    +                                }
    +                            },
    +                            onFailure = {
    +                                if (it !is CancellationException) {
    +                                    _viewEvents.post(OnboardingViewEvents.Failure(it))
    +                                }
    +                            }
    +                    )
    +            setState { copy(asyncRegistration = Uninitialized) }
             }
         }
     
         private fun handleRegisterWith(action: OnboardingAction.LoginOrRegister) {
             reAuthHelper.data = action.password
    -        currentJob = executeRegistrationStep {
    -            it.createAccount(
    -                    action.username,
    -                    action.password,
    -                    action.initialDeviceName
    -            )
    -        }
    -    }
    -
    -    private fun handleCaptchaDone(action: OnboardingAction.CaptchaDone) {
    -        currentJob = executeRegistrationStep {
    -            it.performReCaptcha(action.captchaResponse)
    -        }
    +        handleRegisterAction(RegisterAction.CreateAccount(
    +                action.username,
    +                action.password,
    +                action.initialDeviceName
    +        ))
         }
     
         private fun handleResetAction(action: OnboardingAction.ResetAction) {
    @@ -461,7 +373,7 @@ class OnboardingViewModel @AssistedInject constructor(
             }
     
             when (action.signMode) {
    -            SignMode.SignUp             -> startRegistrationFlow()
    +            SignMode.SignUp             -> handleRegisterAction(RegisterAction.StartRegistration)
                 SignMode.SignIn             -> startAuthenticationFlow()
                 SignMode.SignInWithMatrixId -> _viewEvents.post(OnboardingViewEvents.OnSignModeSelected(SignMode.SignInWithMatrixId))
                 SignMode.Unknown            -> Unit
    @@ -491,7 +403,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) {
    @@ -499,7 +411,7 @@ class OnboardingViewModel @AssistedInject constructor(
     
             // If there is a pending email validation continue on this step
             try {
    -            if (registrationWizard?.isRegistrationStarted == true) {
    +            if (registrationWizard.isRegistrationStarted) {
                     currentThreePid?.let {
                         handle(OnboardingAction.PostViewEvent(OnboardingViewEvents.OnSendEmailSuccess(it)))
                     }
    @@ -599,7 +511,7 @@ 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?) {
    @@ -629,7 +541,7 @@ class OnboardingViewModel @AssistedInject constructor(
                     else                          -> {
                         onWellKnownError()
                     }
    -            }.exhaustive
    +            }
             }
         }
     
    @@ -730,12 +642,6 @@ class OnboardingViewModel @AssistedInject constructor(
             }
         }
     
    -    private fun startRegistrationFlow() {
    -        currentJob = executeRegistrationStep {
    -            it.getRegistrationFlow()
    -        }
    -    }
    -
         private fun startAuthenticationFlow() {
             // Ensure Wizard is ready
             loginWizard
    @@ -745,8 +651,7 @@ class OnboardingViewModel @AssistedInject constructor(
     
         private fun onFlowResponse(flowResult: FlowResult) {
             // If dummy stage is mandatory, and password is already sent, do the dummy stage now
    -        if (isRegistrationStarted &&
    -                flowResult.missingStages.any { it is Stage.Dummy && it.mandatory }) {
    +        if (isRegistrationStarted && flowResult.missingStages.any { it is Stage.Dummy && it.mandatory }) {
                 handleRegisterDummy()
             } else {
                 // Notify the user
    @@ -754,6 +659,10 @@ class OnboardingViewModel @AssistedInject constructor(
             }
         }
     
    +    private fun handleRegisterDummy() {
    +        handleRegisterAction(RegisterAction.RegisterDummy)
    +    }
    +
         private suspend fun onSessionCreated(session: Session, isAccountCreated: Boolean) {
             val state = awaitState()
             state.useCase?.let { useCase ->
    @@ -1006,6 +915,10 @@ class OnboardingViewModel @AssistedInject constructor(
         private fun completePersonalization() {
             _viewEvents.post(OnboardingViewEvents.OnPersonalizationComplete)
         }
    +
    +    private fun cancelWaitForEmailValidation() {
    +        currentJob = null
    +    }
     }
     
     private fun LoginMode.supportsSignModeScreen(): Boolean {
    diff --git a/vector/src/main/java/im/vector/app/features/onboarding/RegistrationActionHandler.kt b/vector/src/main/java/im/vector/app/features/onboarding/RegistrationActionHandler.kt
    new file mode 100644
    index 0000000000..b4998d2ba0
    --- /dev/null
    +++ b/vector/src/main/java/im/vector/app/features/onboarding/RegistrationActionHandler.kt
    @@ -0,0 +1,67 @@
    +/*
    + * 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.onboarding
    +
    +import org.matrix.android.sdk.api.auth.registration.RegisterThreePid
    +import org.matrix.android.sdk.api.auth.registration.RegistrationResult
    +import org.matrix.android.sdk.api.auth.registration.RegistrationWizard
    +import javax.inject.Inject
    +
    +class RegistrationActionHandler @Inject constructor() {
    +
    +    suspend fun handleRegisterAction(registrationWizard: RegistrationWizard, action: RegisterAction): RegistrationResult {
    +        return when (action) {
    +            RegisterAction.StartRegistration               -> registrationWizard.getRegistrationFlow()
    +            is RegisterAction.CaptchaDone                  -> registrationWizard.performReCaptcha(action.captchaResponse)
    +            is RegisterAction.AcceptTerms                  -> registrationWizard.acceptTerms()
    +            is RegisterAction.RegisterDummy                -> registrationWizard.dummy()
    +            is RegisterAction.AddThreePid                  -> registrationWizard.addThreePid(action.threePid)
    +            is RegisterAction.SendAgainThreePid            -> registrationWizard.sendAgainThreePid()
    +            is RegisterAction.ValidateThreePid             -> registrationWizard.handleValidateThreePid(action.code)
    +            is RegisterAction.CheckIfEmailHasBeenValidated -> registrationWizard.checkIfEmailHasBeenValidated(action.delayMillis)
    +            is RegisterAction.CreateAccount                -> registrationWizard.createAccount(action.username, action.password, action.initialDeviceName)
    +        }
    +    }
    +}
    +
    +sealed interface RegisterAction {
    +    object StartRegistration : RegisterAction
    +    data class CreateAccount(val username: String, val password: String, val initialDeviceName: String) : RegisterAction
    +
    +    data class AddThreePid(val threePid: RegisterThreePid) : RegisterAction
    +    object SendAgainThreePid : RegisterAction
    +
    +    // TODO Confirm Email (from link in the email, open in the phone, intercepted by the app)
    +    data class ValidateThreePid(val code: String) : RegisterAction
    +
    +    data class CheckIfEmailHasBeenValidated(val delayMillis: Long) : RegisterAction
    +
    +    data class CaptchaDone(val captchaResponse: String) : RegisterAction
    +    object AcceptTerms : RegisterAction
    +    object RegisterDummy : RegisterAction
    +}
    +
    +fun RegisterAction.ignoresResult() = when (this) {
    +    is RegisterAction.AddThreePid       -> true
    +    is RegisterAction.SendAgainThreePid -> true
    +    else                                -> false
    +}
    +
    +fun RegisterAction.hasLoadingState() = when (this) {
    +    is RegisterAction.CheckIfEmailHasBeenValidated -> false
    +    else                                           -> true
    +}
    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..f8f6f6cefa 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
    @@ -73,7 +72,7 @@ abstract class AbstractFtueAuthFragment : VectorBaseFragment
                     // This is handled by the Activity
                     Unit
    -        }.exhaustive
    +        }
         }
     
         override fun showFailure(throwable: Throwable) {
    diff --git a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthCaptchaFragment.kt b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthCaptchaFragment.kt
    index e2e390ae2d..4773332138 100644
    --- a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthCaptchaFragment.kt
    +++ b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthCaptchaFragment.kt
    @@ -39,6 +39,7 @@ import im.vector.app.databinding.FragmentLoginCaptchaBinding
     import im.vector.app.features.login.JavascriptResponse
     import im.vector.app.features.onboarding.OnboardingAction
     import im.vector.app.features.onboarding.OnboardingViewState
    +import im.vector.app.features.onboarding.RegisterAction
     import kotlinx.parcelize.Parcelize
     import org.matrix.android.sdk.internal.di.MoshiProvider
     import timber.log.Timber
    @@ -181,7 +182,7 @@ class FtueAuthCaptchaFragment @Inject constructor(
     
                         val response = javascriptResponse?.response
                         if (javascriptResponse?.action == "verifyCallback" && response != null) {
    -                        viewModel.handle(OnboardingAction.CaptchaDone(response))
    +                        viewModel.handle(OnboardingAction.PostRegisterAction(RegisterAction.CaptchaDone(response)))
                         }
                     }
                     return true
    diff --git a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthGenericTextInputFormFragment.kt b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthGenericTextInputFormFragment.kt
    index bd5054f646..2800530152 100644
    --- a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthGenericTextInputFormFragment.kt
    +++ b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthGenericTextInputFormFragment.kt
    @@ -37,6 +37,7 @@ import im.vector.app.databinding.FragmentLoginGenericTextInputFormBinding
     import im.vector.app.features.login.TextInputFormFragmentMode
     import im.vector.app.features.onboarding.OnboardingAction
     import im.vector.app.features.onboarding.OnboardingViewEvents
    +import im.vector.app.features.onboarding.RegisterAction
     import kotlinx.coroutines.flow.launchIn
     import kotlinx.coroutines.flow.onEach
     import kotlinx.parcelize.Parcelize
    @@ -138,7 +139,7 @@ class FtueAuthGenericTextInputFormFragment @Inject constructor() : AbstractFtueA
         private fun onOtherButtonClicked() {
             when (params.mode) {
                 TextInputFormFragmentMode.ConfirmMsisdn -> {
    -                viewModel.handle(OnboardingAction.SendAgainThreePid)
    +                viewModel.handle(OnboardingAction.PostRegisterAction(RegisterAction.SendAgainThreePid))
                 }
                 else                                    -> {
                     // Should not happen, button is not displayed
    @@ -152,19 +153,19 @@ class FtueAuthGenericTextInputFormFragment @Inject constructor() : AbstractFtueA
     
             if (text.isEmpty()) {
                 // Perform dummy action
    -            viewModel.handle(OnboardingAction.RegisterDummy)
    +            viewModel.handle(OnboardingAction.PostRegisterAction(RegisterAction.RegisterDummy))
             } else {
                 when (params.mode) {
                     TextInputFormFragmentMode.SetEmail      -> {
    -                    viewModel.handle(OnboardingAction.AddThreePid(RegisterThreePid.Email(text)))
    +                    viewModel.handle(OnboardingAction.PostRegisterAction(RegisterAction.AddThreePid(RegisterThreePid.Email(text))))
                     }
                     TextInputFormFragmentMode.SetMsisdn     -> {
                         getCountryCodeOrShowError(text)?.let { countryCode ->
    -                        viewModel.handle(OnboardingAction.AddThreePid(RegisterThreePid.Msisdn(text, countryCode)))
    +                        viewModel.handle(OnboardingAction.PostRegisterAction(RegisterAction.AddThreePid(RegisterThreePid.Msisdn(text, countryCode))))
                         }
                     }
                     TextInputFormFragmentMode.ConfirmMsisdn -> {
    -                    viewModel.handle(OnboardingAction.ValidateThreePid(text))
    +                    viewModel.handle(OnboardingAction.PostRegisterAction(RegisterAction.ValidateThreePid(text)))
                     }
                 }
             }
    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..dacd8feab3 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
    @@ -28,9 +28,7 @@ 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 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
    @@ -105,7 +103,7 @@ class FtueAuthLoginFragment @Inject constructor() : AbstractSSOFtueAuthFragment<
                         views.loginField.setAutofillHints(HintConstants.AUTOFILL_HINT_USERNAME)
                         views.passwordField.setAutofillHints(HintConstants.AUTOFILL_HINT_PASSWORD)
                     }
    -            }.exhaustive
    +            }
             }
         }
     
    @@ -115,7 +113,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() {
    @@ -299,7 +297,7 @@ class FtueAuthLoginFragment @Inject constructor() : AbstractSSOFtueAuthFragment<
                     }
                 }
                 // Success is handled by the LoginActivity
    -            is Success -> Unit
    +            else       -> Unit
             }
     
             when (state.asyncRegistration) {
    @@ -308,7 +306,7 @@ class FtueAuthLoginFragment @Inject constructor() : AbstractSSOFtueAuthFragment<
                     views.passwordField.hidePassword()
                 }
                 // Success is handled by the LoginActivity
    -            is Success -> Unit
    +            else       -> Unit
             }
         }
     
    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..073801c920 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
    @@ -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
    @@ -125,7 +124,7 @@ class FtueAuthResetPasswordFragment @Inject constructor() : AbstractFtueAuthFrag
                 is Fail    -> {
                     views.resetPasswordEmailTil.error = errorFormatter.toHumanReadable(state.asyncResetPassword.error)
                 }
    -            is Success -> Unit
    +            else       -> Unit
             }
         }
     }
    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..f8b3266d37 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
    @@ -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
    @@ -61,7 +60,7 @@ class FtueAuthResetPasswordMailConfirmationFragment @Inject constructor() : Abst
             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)
    @@ -75,7 +74,7 @@ class FtueAuthResetPasswordMailConfirmationFragment @Inject constructor() : Abst
                             .setPositiveButton(R.string.ok, null)
                             .show()
                 }
    -            is Success -> Unit
    +            else    -> Unit
             }
         }
     }
    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..13b5f61010 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
    @@ -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/onboarding/ftueauth/FtueAuthWaitForEmailFragment.kt b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthWaitForEmailFragment.kt
    index 94758c7fad..ec72f52b9e 100644
    --- a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthWaitForEmailFragment.kt
    +++ b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthWaitForEmailFragment.kt
    @@ -25,6 +25,7 @@ import com.airbnb.mvrx.args
     import im.vector.app.R
     import im.vector.app.databinding.FragmentLoginWaitForEmailBinding
     import im.vector.app.features.onboarding.OnboardingAction
    +import im.vector.app.features.onboarding.RegisterAction
     import kotlinx.parcelize.Parcelize
     import org.matrix.android.sdk.api.failure.is401
     import javax.inject.Inject
    @@ -54,7 +55,7 @@ class FtueAuthWaitForEmailFragment @Inject constructor() : AbstractFtueAuthFragm
         override fun onResume() {
             super.onResume()
     
    -        viewModel.handle(OnboardingAction.CheckIfEmailHasBeenValidated(0))
    +        viewModel.handle(OnboardingAction.PostRegisterAction(RegisterAction.CheckIfEmailHasBeenValidated(0)))
         }
     
         override fun onPause() {
    @@ -70,7 +71,7 @@ class FtueAuthWaitForEmailFragment @Inject constructor() : AbstractFtueAuthFragm
         override fun onError(throwable: Throwable) {
             if (throwable.is401()) {
                 // Try again, with a delay
    -            viewModel.handle(OnboardingAction.CheckIfEmailHasBeenValidated(10_000))
    +            viewModel.handle(OnboardingAction.PostRegisterAction(RegisterAction.CheckIfEmailHasBeenValidated(10_000)))
             } else {
                 super.onError(throwable)
             }
    diff --git a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/terms/FtueAuthTermsFragment.kt b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/terms/FtueAuthTermsFragment.kt
    index 5ce9a5350d..03598d3a47 100755
    --- a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/terms/FtueAuthTermsFragment.kt
    +++ b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/terms/FtueAuthTermsFragment.kt
    @@ -32,6 +32,7 @@ import im.vector.app.features.login.terms.LoginTermsViewState
     import im.vector.app.features.login.terms.PolicyController
     import im.vector.app.features.onboarding.OnboardingAction
     import im.vector.app.features.onboarding.OnboardingViewState
    +import im.vector.app.features.onboarding.RegisterAction
     import im.vector.app.features.onboarding.ftueauth.AbstractFtueAuthFragment
     import kotlinx.parcelize.Parcelize
     import org.matrix.android.sdk.internal.auth.registration.LocalizedFlowDataLoginTerms
    @@ -111,7 +112,7 @@ class FtueAuthTermsFragment @Inject constructor(
         }
     
         private fun submit() {
    -        viewModel.handle(OnboardingAction.AcceptTerms)
    +        viewModel.handle(OnboardingAction.PostRegisterAction(RegisterAction.AcceptTerms))
         }
     
         override fun updateWithState(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 4483b00158..2abff7f22b 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.create.CreatePollViewModel.Companion.MAX_OPTIONS_COUNT
    @@ -68,7 +67,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/drawable/ic_presence_online.xml b/vector/src/main/res/drawable/ic_presence_online.xml
    index 2184f359b2..e5229de3fd 100644
    --- a/vector/src/main/res/drawable/ic_presence_online.xml
    +++ b/vector/src/main/res/drawable/ic_presence_online.xml
    @@ -16,7 +16,7 @@
     
             
     
         
    diff --git a/vector/src/main/res/layout/fragment_timeline.xml b/vector/src/main/res/layout/fragment_timeline.xml
    index 6ba326bcc4..14eac04db2 100644
    --- a/vector/src/main/res/layout/fragment_timeline.xml
    +++ b/vector/src/main/res/layout/fragment_timeline.xml
    @@ -17,7 +17,7 @@
                 android:layout_width="match_parent"
                 android:layout_height="wrap_content"
                 android:minHeight="48dp"
    -            android:visibility="gone"/>
    +            android:visibility="gone" />
     
             
     
    +    
    +
         
    +        app:layout_constraintStart_toStartOf="parent"
    +        app:layout_constraintEnd_toEndOf="parent"
    +        app:layout_constraintTop_toBottomOf="@id/locationLiveStatusIndicator" />
     
         
    +        app:layout_constraintTop_toBottomOf="@id/removeJitsiWidgetView" />
     
         
    +        app:layout_constraintBottom_toTopOf="@id/composerLayout"
    +        app:layout_constraintEnd_toEndOf="parent"
    +        app:layout_constraintStart_toStartOf="parent"
    +        app:layout_constraintTop_toBottomOf="@id/timelineRecyclerView"
    +        tools:visibility="visible" />
     
         
    +        tools:visibility="visible" />
     
         
    +        app:layout_constraintStart_toStartOf="parent" />
     
         
     
    -        
    +
    +
    +    
    +
    +    
    +
    +    
    +
    +