From ab2001cd7f8f97440a57a7f85e28723c32bc20a2 Mon Sep 17 00:00:00 2001 From: Onuray Sahin Date: Wed, 2 Mar 2022 17:45:27 +0300 Subject: [PATCH 001/262] Create a custom audio waveform view. --- .../main/res/values/styles_voice_message.xml | 16 +- .../detail/composer/VoiceMessageHelper.kt | 4 +- .../composer/voice/VoiceMessageViews.kt | 12 +- .../timeline/factory/MessageItemFactory.kt | 4 +- .../helper/VoiceMessagePlaybackTracker.kt | 23 +- .../detail/timeline/item/MessageVoiceItem.kt | 40 ++-- .../app/features/voice/AudioWaveformView.kt | 199 ++++++++++++++++++ .../layout/item_timeline_event_voice_stub.xml | 2 +- .../layout/view_voice_message_recorder.xml | 2 +- .../main/res/values/audio_waveform_attr.xml | 22 ++ 10 files changed, 287 insertions(+), 37 deletions(-) create mode 100644 vector/src/main/java/im/vector/app/features/voice/AudioWaveformView.kt create mode 100644 vector/src/main/res/values/audio_waveform_attr.xml 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/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..f9dfecd1f5 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 @@ -221,7 +221,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/VoiceMessageViews.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/voice/VoiceMessageViews.kt index 09284ea5fc..8adecaad6e 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, @@ -284,7 +285,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 +293,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 +332,9 @@ class VoiceMessageViews( fun renderRecordingWaveform(amplitudeList: Array) { views.voicePlaybackWaveform.doOnLayout { waveFormView -> + val waveformColor = ThemeUtils.getColor(waveFormView.context, R.attr.vctr_content_secondary) amplitudeList.iterator().forEach { - (waveFormView as AudioRecordView).update(it) + (waveFormView as AudioWaveformView).add(AudioWaveformView.FFT(it.toFloat(), waveformColor)) } } } 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 0c836748c8..da97cf6984 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 @@ -73,6 +73,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 @@ -688,8 +689,7 @@ 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 + it * AudioWaveformView.MAX_FFT / 1024 } } 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..076c05b9c4 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() { } } + 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..82400a431d 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 @@ -26,7 +26,6 @@ import android.widget.TextView 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,6 +33,7 @@ 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() { @@ -78,11 +78,15 @@ abstract class MessageVoiceItem : AbsMessageItem() { 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.post { - holder.voicePlaybackWaveform.recreate() + holder.voicePlaybackWaveform.clear() waveform.forEach { amplitude -> - holder.voicePlaybackWaveform.update(amplitude) + holder.voicePlaybackWaveform.add(AudioWaveformView.FFT(amplitude.toFloat(), waveformColorIdle)) } + holder.voicePlaybackWaveform.summarize() } val backgroundTint = if (attributes.informationData.messageLayout is TimelineMessageLayout.Bubble) { @@ -93,33 +97,39 @@ abstract class MessageVoiceItem : AbsMessageItem() { holder.voicePlaybackLayout.backgroundTintList = ColorStateList.valueOf(backgroundTint) holder.voicePlaybackControlButton.setOnClickListener { playbackControlButtonClickListener?.invoke(it) } - 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) + // Don't track and don't try to update UI before view is present + holder.view.post { + voiceMessagePlaybackTracker.track(attributes.informationData.eventId, object : VoiceMessagePlaybackTracker.Listener { + override fun onUpdate(state: VoiceMessagePlaybackTracker.Listener.State) { + when (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) + } } - } - }) + }) + } } - private fun renderIdleState(holder: Holder) { + 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 +148,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/voice/AudioWaveformView.kt b/vector/src/main/java/im/vector/app/features/voice/AudioWaveformView.kt new file mode 100644 index 0000000000..9ba7597e60 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/voice/AudioWaveformView.kt @@ -0,0 +1,199 @@ +/* + * 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 { + 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 + visibleBarHeights.forEach { + barPaint.color = it.color + // TODO. Support flow + 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 { + private const val MAX_FFT = 32760f + } +} diff --git a/vector/src/main/res/layout/item_timeline_event_voice_stub.xml b/vector/src/main/res/layout/item_timeline_event_voice_stub.xml index a180afbf8e..0fad714bd4 100644 --- a/vector/src/main/res/layout/item_timeline_event_voice_stub.xml +++ b/vector/src/main/res/layout/item_timeline_event_voice_stub.xml @@ -40,7 +40,7 @@ app:layout_constraintTop_toTopOf="@id/voicePlaybackControlButton" tools:text="0:23" /> - - + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file From 243a714586b13afeaa2432344687bddbf9c25658 Mon Sep 17 00:00:00 2001 From: Onuray Sahin Date: Wed, 2 Mar 2022 17:46:09 +0300 Subject: [PATCH 002/262] Remove 3rd party waveform library. --- library/ui-styles/build.gradle | 2 -- vector/build.gradle | 1 - vector/src/main/assets/open_source_licenses.html | 5 ----- .../java/im/vector/app/features/voice/AudioWaveformView.kt | 2 +- 4 files changed, 1 insertion(+), 9 deletions(-) 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/vector/build.gradle b/vector/build.gradle index c6a2636acf..d58118eb24 100644 --- a/vector/build.gradle +++ b/vector/build.gradle @@ -416,7 +416,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/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/features/voice/AudioWaveformView.kt b/vector/src/main/java/im/vector/app/features/voice/AudioWaveformView.kt
    index 9ba7597e60..768635b2f7 100644
    --- a/vector/src/main/java/im/vector/app/features/voice/AudioWaveformView.kt
    +++ b/vector/src/main/java/im/vector/app/features/voice/AudioWaveformView.kt
    @@ -194,6 +194,6 @@ class AudioWaveformView @JvmOverloads constructor(
         }
     
         companion object {
    -        private const val MAX_FFT = 32760f
    +        const val MAX_FFT = 32760
         }
     }
    
    From 4254f4606535f6899e0cc130683cfbbfc46fa7e8 Mon Sep 17 00:00:00 2001
    From: Onuray Sahin 
    Date: Thu, 3 Mar 2022 17:59:51 +0300
    Subject: [PATCH 003/262] Support scrolling playback on timeline.
    
    ---
     .../home/room/detail/TimelineFragment.kt      |  8 ++++++
     .../detail/composer/MessageComposerAction.kt  |  2 ++
     .../composer/MessageComposerViewModel.kt      | 16 +++++++++++-
     .../detail/composer/VoiceMessageHelper.kt     |  8 ++++++
     .../timeline/TimelineEventController.kt       |  2 ++
     .../timeline/factory/MessageItemFactory.kt    | 11 ++++++++
     .../detail/timeline/item/MessageVoiceItem.kt  | 25 +++++++++++++++++++
     7 files changed, 71 insertions(+), 1 deletion(-)
    
    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 2da69bbe6c..d019cb1777 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
    @@ -2051,6 +2051,14 @@ class TimelineFragment @Inject constructor(
             messageComposerViewModel.handle(MessageComposerAction.PlayOrPauseVoicePlayback(eventId, messageAudioContent))
         }
     
    +    override fun onVoiceWaveformTouchedUp(eventId: String, messageAudioContent: MessageAudioContent, percentage: Float) {
    +        messageComposerViewModel.handle(MessageComposerAction.VoiceWaveformTouchedUp(eventId, messageAudioContent, percentage))
    +    }
    +
    +    override fun onVoiceWaveformMovedTo(eventId: String, messageAudioContent: MessageAudioContent, percentage: Float) {
    +        messageComposerViewModel.handle(MessageComposerAction.VoiceWaveformMovedTo(eventId, messageAudioContent, percentage))
    +    }
    +
         private fun onShareActionClicked(action: EventSharedAction.Share) {
             when (action.messageContent) {
                 is MessageTextContent           -> shareText(requireContext(), action.messageContent.body)
    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..daa5631d84 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 messageAudioContent: MessageAudioContent, val percentage: Float) : MessageComposerAction()
    +    data class VoiceWaveformMovedTo(val eventId: String, val messageAudioContent: MessageAudioContent, 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 0d90227168..ccb51d3796 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
    @@ -108,7 +108,9 @@ 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)
    +        }.exhaustive
         }
     
         private fun handleOnVoiceRecordingUiStateChanged(action: MessageComposerAction.OnVoiceRecordingUiStateChanged) = setState {
    @@ -861,6 +863,18 @@ class MessageComposerViewModel @AssistedInject constructor(
             voiceMessageHelper.pauseRecording()
         }
     
    +    private fun handleVoiceWaveformTouchedUp(action: MessageComposerAction.VoiceWaveformTouchedUp) {
    +        val duration = (action.messageAudioContent.audioInfo?.duration ?: 0)
    +        val toMillisecond = (action.percentage * duration).toInt()
    +        voiceMessageHelper.movePlaybackTo(action.eventId, toMillisecond, duration)
    +    }
    +
    +    private fun handleVoiceWaveformMovedTo(action: MessageComposerAction.VoiceWaveformMovedTo) {
    +        val duration = (action.messageAudioContent.audioInfo?.duration ?: 0)
    +        val toMillisecond = (action.percentage * duration).toInt()
    +        voiceMessageHelper.movePlaybackTo(action.eventId, toMillisecond, duration)
    +    }
    +
         private fun handleEntersBackground(composerText: String) {
             val isVoiceRecording = com.airbnb.mvrx.withState(this) { it.isVoiceRecording }
             if (isVoiceRecording) {
    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 f9dfecd1f5..b6a8dc2cd5 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
    @@ -174,6 +174,14 @@ class VoiceMessageHelper @Inject constructor(
             stopPlaybackTicker()
         }
     
    +    fun movePlaybackTo(id: String, toMillisecond: Int, totalDuration: Int) {
    +        val percentage = toMillisecond.toFloat() / totalDuration
    +        playbackTracker.updateCurrentPlaybackTime(id, toMillisecond, percentage)
    +
    +        stopPlayback()
    +        playbackTracker.pausePlayback(id)
    +    }
    +
         private fun startRecordingAmplitudes() {
             amplitudeTicker?.stop()
             amplitudeTicker = CountUpTimer(50).apply {
    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 2ac592797c..3965afdbaa 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
    @@ -138,6 +138,8 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec
             fun getPreviewUrlRetriever(): PreviewUrlRetriever
     
             fun onVoiceControlButtonClicked(eventId: String, messageAudioContent: MessageAudioContent)
    +        fun onVoiceWaveformTouchedUp(eventId: String, messageAudioContent: MessageAudioContent, percentage: Float)
    +        fun onVoiceWaveformMovedTo(eventId: String, messageAudioContent: MessageAudioContent, percentage: Float)
         }
     
         interface ReactionPillCallback {
    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 da97cf6984..8b0b43009d 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
    @@ -357,11 +357,22 @@ class MessageItemFactory @Inject constructor(
                 }
             }
     
    +        val waveformTouchListener: MessageVoiceItem.WaveformTouchListener = object : MessageVoiceItem.WaveformTouchListener {
    +            override fun onWaveformTouchedUp(percentage: Float) {
    +                params.callback?.onVoiceWaveformTouchedUp(informationData.eventId, messageContent, percentage)
    +            }
    +
    +            override fun onWaveformMovedTo(percentage: Float) {
    +                params.callback?.onVoiceWaveformMovedTo(informationData.eventId, messageContent, 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(
    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 82400a431d..d1c134a743 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,6 +19,7 @@ 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
    @@ -38,6 +39,11 @@ 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 +68,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
     
    @@ -87,6 +96,20 @@ abstract class MessageVoiceItem : AbsMessageItem() {
                     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
    +            }
             }
     
             val backgroundTint = if (attributes.informationData.messageLayout is TimelineMessageLayout.Bubble) {
    @@ -111,6 +134,8 @@ abstract class MessageVoiceItem : AbsMessageItem() {
             }
         }
     
    +    private fun getTouchedPositionPercentage(motionEvent: MotionEvent, view: View) = motionEvent.x / view.width
    +
         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)
    
    From 3bd4a4ccd3ece851ac3de5c4c4e1e11a406efc33 Mon Sep 17 00:00:00 2001
    From: Onuray Sahin 
    Date: Thu, 3 Mar 2022 19:54:13 +0300
    Subject: [PATCH 004/262] Fix voice recorder start/pause states.
    
    ---
     .../home/room/detail/composer/VoiceMessageHelper.kt         | 6 ++++--
     .../detail/timeline/helper/VoiceMessagePlaybackTracker.kt   | 2 +-
     2 files changed, 5 insertions(+), 3 deletions(-)
    
    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 b6a8dc2cd5..6bde4ada3d 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)
    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 076c05b9c4..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
    @@ -115,7 +115,7 @@ class VoiceMessagePlaybackTracker @Inject constructor() {
             }
         }
     
    -    fun getPercentage(id: String): Float {
    +    private fun getPercentage(id: String): Float {
             return when (val state = states[id]) {
                 is Listener.State.Playing -> state.percentage
                 is Listener.State.Paused  -> state.percentage
    
    From 5168d715ceb89ec62a31ea0504232952dd6c7c57 Mon Sep 17 00:00:00 2001
    From: Onuray Sahin 
    Date: Fri, 4 Mar 2022 16:21:28 +0300
    Subject: [PATCH 005/262] Support scrolling playback on recorded audio before
     sending.
    
    ---
     .../home/room/detail/TimelineFragment.kt      | 20 ++++++++++++----
     .../detail/composer/MessageComposerAction.kt  |  4 ++--
     .../composer/MessageComposerViewModel.kt      |  8 ++-----
     .../detail/composer/VoiceMessageHelper.kt     |  6 ++---
     .../voice/VoiceMessageRecorderView.kt         | 17 +++++++++++++-
     .../composer/voice/VoiceMessageViews.kt       | 23 ++++++++++++++++---
     .../timeline/TimelineEventController.kt       |  4 ++--
     .../timeline/factory/MessageItemFactory.kt    |  6 +++--
     8 files changed, 65 insertions(+), 23 deletions(-)
    
    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 d019cb1777..a0e8ddce3d 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
    @@ -786,6 +786,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))
    @@ -2051,12 +2063,12 @@ class TimelineFragment @Inject constructor(
             messageComposerViewModel.handle(MessageComposerAction.PlayOrPauseVoicePlayback(eventId, messageAudioContent))
         }
     
    -    override fun onVoiceWaveformTouchedUp(eventId: String, messageAudioContent: MessageAudioContent, percentage: Float) {
    -        messageComposerViewModel.handle(MessageComposerAction.VoiceWaveformTouchedUp(eventId, messageAudioContent, percentage))
    +    override fun onVoiceWaveformTouchedUp(eventId: String, duration: Int, percentage: Float) {
    +        messageComposerViewModel.handle(MessageComposerAction.VoiceWaveformTouchedUp(eventId, duration, percentage))
         }
     
    -    override fun onVoiceWaveformMovedTo(eventId: String, messageAudioContent: MessageAudioContent, percentage: Float) {
    -        messageComposerViewModel.handle(MessageComposerAction.VoiceWaveformMovedTo(eventId, messageAudioContent, percentage))
    +    override fun onVoiceWaveformMovedTo(eventId: String, duration: Int, percentage: Float) {
    +        messageComposerViewModel.handle(MessageComposerAction.VoiceWaveformMovedTo(eventId, duration, percentage))
         }
     
         private fun onShareActionClicked(action: EventSharedAction.Share) {
    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 daa5631d84..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,6 +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 messageAudioContent: MessageAudioContent, val percentage: Float) : MessageComposerAction()
    -    data class VoiceWaveformMovedTo(val eventId: String, val messageAudioContent: MessageAudioContent, val percentage: Float) : 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 ccb51d3796..fba3b8b5d3 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
    @@ -864,15 +864,11 @@ class MessageComposerViewModel @AssistedInject constructor(
         }
     
         private fun handleVoiceWaveformTouchedUp(action: MessageComposerAction.VoiceWaveformTouchedUp) {
    -        val duration = (action.messageAudioContent.audioInfo?.duration ?: 0)
    -        val toMillisecond = (action.percentage * duration).toInt()
    -        voiceMessageHelper.movePlaybackTo(action.eventId, toMillisecond, duration)
    +        voiceMessageHelper.movePlaybackTo(action.eventId, action.percentage, action.duration)
         }
     
         private fun handleVoiceWaveformMovedTo(action: MessageComposerAction.VoiceWaveformMovedTo) {
    -        val duration = (action.messageAudioContent.audioInfo?.duration ?: 0)
    -        val toMillisecond = (action.percentage * duration).toInt()
    -        voiceMessageHelper.movePlaybackTo(action.eventId, toMillisecond, duration)
    +        voiceMessageHelper.movePlaybackTo(action.eventId, action.percentage, action.duration)
         }
     
         private fun handleEntersBackground(composerText: String) {
    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 6bde4ada3d..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
    @@ -171,13 +171,13 @@ class VoiceMessageHelper @Inject constructor(
         }
     
         fun stopPlayback() {
    -        playbackTracker.stopPlayback(VoiceMessagePlaybackTracker.RECORDING_ID)
    +        playbackTracker.pausePlayback(VoiceMessagePlaybackTracker.RECORDING_ID)
             mediaPlayer?.stop()
             stopPlaybackTicker()
         }
     
    -    fun movePlaybackTo(id: String, toMillisecond: Int, totalDuration: Int) {
    -        val percentage = toMillisecond.toFloat() / totalDuration
    +    fun movePlaybackTo(id: String, percentage: Float, totalDuration: Int) {
    +        val toMillisecond = (totalDuration * percentage).toInt()
             playbackTracker.updateCurrentPlaybackTime(id, toMillisecond, percentage)
     
             stopPlayback()
    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..87a2630f2a 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
    @@ -53,6 +53,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 +67,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,7 +98,6 @@ 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()
                     }
    @@ -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())
    +                }
    +            }
             })
         }
     
    @@ -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 8adecaad6e..f3b1fc918d 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
    @@ -60,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 {
    @@ -70,6 +83,8 @@ class VoiceMessageViews(
             observeMicButton(actions)
         }
     
    +    private fun getTouchedPositionPercentage(motionEvent: MotionEvent, view: View) = motionEvent.x / view.width
    +
         @SuppressLint("ClickableViewAccessibility")
         private fun observeMicButton(actions: Actions) {
             val draggableStateProcessor = DraggableStateProcessor(resources, dimensionConverter)
    @@ -332,7 +347,7 @@ class VoiceMessageViews(
     
         fun renderRecordingWaveform(amplitudeList: Array) {
             views.voicePlaybackWaveform.doOnLayout { waveFormView ->
    -            val waveformColor = ThemeUtils.getColor(waveFormView.context, R.attr.vctr_content_secondary)
    +            val waveformColor = ThemeUtils.getColor(waveFormView.context, R.attr.vctr_content_quaternary)
                 amplitudeList.iterator().forEach {
                     (waveFormView as AudioWaveformView).add(AudioWaveformView.FFT(it.toFloat(), waveformColor))
                 }
    @@ -355,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/timeline/TimelineEventController.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/TimelineEventController.kt
    index 3965afdbaa..9c469dfead 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
    @@ -138,8 +138,8 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec
             fun getPreviewUrlRetriever(): PreviewUrlRetriever
     
             fun onVoiceControlButtonClicked(eventId: String, messageAudioContent: MessageAudioContent)
    -        fun onVoiceWaveformTouchedUp(eventId: String, messageAudioContent: MessageAudioContent, percentage: Float)
    -        fun onVoiceWaveformMovedTo(eventId: String, messageAudioContent: MessageAudioContent, percentage: Float)
    +        fun onVoiceWaveformTouchedUp(eventId: String, duration: Int, percentage: Float)
    +        fun onVoiceWaveformMovedTo(eventId: String, duration: Int, percentage: Float)
         }
     
         interface ReactionPillCallback {
    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 8b0b43009d..9116de92dd 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
    @@ -359,11 +359,13 @@ class MessageItemFactory @Inject constructor(
     
             val waveformTouchListener: MessageVoiceItem.WaveformTouchListener = object : MessageVoiceItem.WaveformTouchListener {
                 override fun onWaveformTouchedUp(percentage: Float) {
    -                params.callback?.onVoiceWaveformTouchedUp(informationData.eventId, messageContent, percentage)
    +                val duration = messageContent.audioInfo?.duration ?: 0
    +                params.callback?.onVoiceWaveformTouchedUp(informationData.eventId, duration, percentage)
                 }
     
                 override fun onWaveformMovedTo(percentage: Float) {
    -                params.callback?.onVoiceWaveformMovedTo(informationData.eventId, messageContent, percentage)
    +                val duration = messageContent.audioInfo?.duration ?: 0
    +                params.callback?.onVoiceWaveformMovedTo(informationData.eventId, duration, percentage)
                 }
             }
     
    
    From aae75ce52fa1541b32f54ee96a3442b65a92844a Mon Sep 17 00:00:00 2001
    From: Onuray Sahin 
    Date: Fri, 4 Mar 2022 16:54:56 +0300
    Subject: [PATCH 006/262] Always stop all voice actions and media player if app
     enters to the background.
    
    ---
     .../home/room/detail/composer/MessageComposerViewModel.kt  | 7 +++++--
     1 file changed, 5 insertions(+), 2 deletions(-)
    
    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 fba3b8b5d3..b71398c8a2 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
    @@ -872,11 +872,14 @@ class MessageComposerViewModel @AssistedInject constructor(
         }
     
         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)) }
    
    From 601f10a6fb7e62f43e9d1ec8dbcf898bcbf50b78 Mon Sep 17 00:00:00 2001
    From: Onuray Sahin 
    Date: Fri, 4 Mar 2022 17:16:09 +0300
    Subject: [PATCH 007/262] Support ltr and rtl flow of the recording waveform.
    
    ---
     .../java/im/vector/app/features/voice/AudioWaveformView.kt   | 5 +++--
     1 file changed, 3 insertions(+), 2 deletions(-)
    
    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
    index 768635b2f7..7cdb1d51d5 100644
    --- a/vector/src/main/java/im/vector/app/features/voice/AudioWaveformView.kt
    +++ b/vector/src/main/java/im/vector/app/features/voice/AudioWaveformView.kt
    @@ -164,9 +164,10 @@ class AudioWaveformView @JvmOverloads constructor(
     
         private fun drawBars(canvas: Canvas) {
             var currentX = horizontalPadding
    -        visibleBarHeights.forEach {
    +        val flowableBarHeights = if (flow == Flow.LTR) visibleBarHeights else visibleBarHeights.reversed()
    +
    +        flowableBarHeights.forEach {
                 barPaint.color = it.color
    -            // TODO. Support flow
                 when (alignment) {
                     Alignment.BOTTOM -> {
                         val startY = height - verticalPadding
    
    From e09b123a9191e1d0aaea845657675ce05f982ca1 Mon Sep 17 00:00:00 2001
    From: Onuray Sahin 
    Date: Fri, 4 Mar 2022 17:21:00 +0300
    Subject: [PATCH 008/262] Changelog added.
    
    ---
     changelog.d/5426.feature | 1 +
     1 file changed, 1 insertion(+)
     create mode 100644 changelog.d/5426.feature
    
    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
    
    From 4cb432e49704e2ccd5132fafcfad851754e40135 Mon Sep 17 00:00:00 2001
    From: Onuray Sahin 
    Date: Fri, 4 Mar 2022 17:47:34 +0300
    Subject: [PATCH 009/262] Do not allow to flow RTL after summarized, playback
     time always flows LTR.
    
    ---
     .../main/java/im/vector/app/features/voice/AudioWaveformView.kt  | 1 +
     1 file changed, 1 insertion(+)
    
    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
    index 7cdb1d51d5..32f30fe458 100644
    --- a/vector/src/main/java/im/vector/app/features/voice/AudioWaveformView.kt
    +++ b/vector/src/main/java/im/vector/app/features/voice/AudioWaveformView.kt
    @@ -129,6 +129,7 @@ class AudioWaveformView @JvmOverloads constructor(
         }
     
         private fun List.summarize(target: Int): List {
    +        flow = Flow.LTR
             val result = mutableListOf()
             if (size <= target) {
                 result.addAll(this)
    
    From 3156410965eb913de7606d276962ae0dc715faef Mon Sep 17 00:00:00 2001
    From: Onuray Sahin 
    Date: Mon, 7 Mar 2022 15:52:19 +0300
    Subject: [PATCH 010/262] Code review fixes.
    
    ---
     .../src/main/res/values/stylable_audio_waveform_view.xml        | 0
     .../home/room/detail/composer/voice/VoiceMessageViews.kt        | 2 +-
     .../home/room/detail/timeline/factory/MessageItemFactory.kt     | 1 +
     .../features/home/room/detail/timeline/item/MessageVoiceItem.kt | 2 +-
     4 files changed, 3 insertions(+), 2 deletions(-)
     rename vector/src/main/res/values/audio_waveform_attr.xml => library/ui-styles/src/main/res/values/stylable_audio_waveform_view.xml (100%)
    
    diff --git a/vector/src/main/res/values/audio_waveform_attr.xml b/library/ui-styles/src/main/res/values/stylable_audio_waveform_view.xml
    similarity index 100%
    rename from vector/src/main/res/values/audio_waveform_attr.xml
    rename to library/ui-styles/src/main/res/values/stylable_audio_waveform_view.xml
    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 f3b1fc918d..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
    @@ -83,7 +83,7 @@ class VoiceMessageViews(
             observeMicButton(actions)
         }
     
    -    private fun getTouchedPositionPercentage(motionEvent: MotionEvent, view: View) = motionEvent.x / view.width
    +    private fun getTouchedPositionPercentage(motionEvent: MotionEvent, view: View) = (motionEvent.x / view.width).coerceIn(0f, 1f)
     
         @SuppressLint("ClickableViewAccessibility")
         private fun observeMicButton(actions: Actions) {
    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 865e8f80bd..e8e8927b6d 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
    @@ -709,6 +709,7 @@ class MessageItemFactory @Inject constructor(
             return this
                     ?.filterNotNull()
                     ?.map {
    +                    // 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/item/MessageVoiceItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceItem.kt
    index d1c134a743..722e0f620a 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
    @@ -134,7 +134,7 @@ abstract class MessageVoiceItem : AbsMessageItem() {
             }
         }
     
    -    private fun getTouchedPositionPercentage(motionEvent: MotionEvent, view: View) = motionEvent.x / view.width
    +    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)
    
    From 6fef2f6d4e87f4deadd45f74387b39ef32312ecc Mon Sep 17 00:00:00 2001
    From: Onuray Sahin 
    Date: Mon, 7 Mar 2022 21:48:16 +0300
    Subject: [PATCH 011/262] Lint fixes.
    
    ---
     .../home/room/detail/composer/MessageComposerViewModel.kt       | 2 +-
     1 file changed, 1 insertion(+), 1 deletion(-)
    
    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 36fbdf4788..0c89226f5a 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
    @@ -877,7 +877,7 @@ class MessageComposerViewModel @AssistedInject constructor(
             // 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) {
                 viewModelScope.launch {
    
    From e4ce4ab1ab0633ea4b33668ca20d6dc2aa8210ab Mon Sep 17 00:00:00 2001
    From: ericdecanini 
    Date: Wed, 9 Mar 2022 12:07:16 +0100
    Subject: [PATCH 012/262] Adds server side vote blocking for ended polls
    
    ---
     .../room/detail/timeline/item/PollItem.kt     | 19 +++++++++++++------
     1 file changed, 13 insertions(+), 6 deletions(-)
    
    diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/PollItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/PollItem.kt
    index 2327a0f2e2..0ce225b21c 100644
    --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/PollItem.kt
    +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/PollItem.kt
    @@ -44,8 +44,8 @@ abstract class PollItem : AbsMessageItem() {
         @EpoxyAttribute
         var totalVotesText: String? = null
     
    -   @EpoxyAttribute
    -   var edited: Boolean = false
    +    @EpoxyAttribute
    +    var edited: Boolean = false
     
         @EpoxyAttribute
         lateinit var optionViewStates: List
    @@ -54,7 +54,6 @@ abstract class PollItem : AbsMessageItem() {
     
         override fun bind(holder: Holder) {
             super.bind(holder)
    -        val relatedEventId = eventId ?: return
     
             renderSendState(holder.view, holder.questionTextView)
     
    @@ -73,13 +72,21 @@ abstract class PollItem : AbsMessageItem() {
             optionViewStates.forEachIndexed { index, optionViewState ->
                 views.getOrNull(index)?.let {
                     it.render(optionViewState)
    -                it.setOnClickListener {
    -                    callback?.onTimelineItemAction(RoomDetailAction.VoteToPoll(relatedEventId, optionViewState.optionId))
    -                }
    +                it.setOnClickListener { onPollItemClick(optionViewState) }
                 }
             }
         }
     
    +    private fun onPollItemClick(optionViewState: PollOptionViewState) {
    +        val relatedEventId = eventId
    +
    +        if (isPollActive(optionViewState) && relatedEventId != null) {
    +            callback?.onTimelineItemAction(RoomDetailAction.VoteToPoll(relatedEventId, optionViewState.optionId))
    +        }
    +    }
    +
    +    private fun isPollActive(optionViewState: PollOptionViewState) = optionViewState !is PollOptionViewState.PollEnded
    +
         class Holder : AbsMessageItem.Holder(STUB_ID) {
             val questionTextView by bind(R.id.questionTextView)
             val optionsContainer by bind(R.id.optionsContainer)
    
    From 080844dc9de121c80b15034ff77dd4109913b7f3 Mon Sep 17 00:00:00 2001
    From: ericdecanini 
    Date: Thu, 10 Mar 2022 16:28:22 +0100
    Subject: [PATCH 013/262] Removes event timestamp condition for sdk poll voting
    
    ---
     .../session/room/EventRelationsAggregationProcessor.kt         | 2 +-
     .../app/features/home/room/detail/timeline/item/PollItem.kt    | 3 ++-
     2 files changed, 3 insertions(+), 2 deletions(-)
    
    diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EventRelationsAggregationProcessor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EventRelationsAggregationProcessor.kt
    index 1e0eb8b497..d186f74a94 100644
    --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EventRelationsAggregationProcessor.kt
    +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EventRelationsAggregationProcessor.kt
    @@ -385,7 +385,7 @@ internal class EventRelationsAggregationProcessor @Inject constructor(
                     }
     
             val closedTime = existingPollSummary?.closedTime
    -        if (closedTime != null && eventTimestamp > closedTime) {
    +        if (closedTime != null) {
                 Timber.v("## POLL is closed ignore event poll:$targetEventId, event :${event.eventId}")
                 return
             }
    diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/PollItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/PollItem.kt
    index 0ce225b21c..295f4c930b 100644
    --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/PollItem.kt
    +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/PollItem.kt
    @@ -85,7 +85,8 @@ abstract class PollItem : AbsMessageItem() {
             }
         }
     
    -    private fun isPollActive(optionViewState: PollOptionViewState) = optionViewState !is PollOptionViewState.PollEnded
    +    private fun isPollActive(optionViewState: PollOptionViewState) = true // TODO: Revert (true for debugging)
    +//    private fun isPollActive(optionViewState: PollOptionViewState) = optionViewState !is PollOptionViewState.PollEnded
     
         class Holder : AbsMessageItem.Holder(STUB_ID) {
             val questionTextView by bind(R.id.questionTextView)
    
    From 628a160c3a2bf9f1c785474d0c5624795f785265 Mon Sep 17 00:00:00 2001
    From: ericdecanini 
    Date: Thu, 10 Mar 2022 16:32:42 +0100
    Subject: [PATCH 014/262] Reverts timestamp condition but changes timing of
     setting closedTime
    
    ---
     .../session/room/EventRelationsAggregationProcessor.kt      | 6 +++---
     1 file changed, 3 insertions(+), 3 deletions(-)
    
    diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EventRelationsAggregationProcessor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EventRelationsAggregationProcessor.kt
    index d186f74a94..f3f55466da 100644
    --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EventRelationsAggregationProcessor.kt
    +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EventRelationsAggregationProcessor.kt
    @@ -385,7 +385,7 @@ internal class EventRelationsAggregationProcessor @Inject constructor(
                     }
     
             val closedTime = existingPollSummary?.closedTime
    -        if (closedTime != null) {
    +        if (closedTime != null && eventTimestamp > closedTime) {
                 Timber.v("## POLL is closed ignore event poll:$targetEventId, event :${event.eventId}")
                 return
             }
    @@ -499,6 +499,8 @@ internal class EventRelationsAggregationProcessor @Inject constructor(
             }
     
             val txId = event.unsignedData?.transactionId
    +        existingPollSummary.closedTime = event.originServerTs
    +
             // is it a remote echo?
             if (!isLocalEcho && existingPollSummary.sourceLocalEchoEvents.contains(txId)) {
                 // ok it has already been managed
    @@ -507,8 +509,6 @@ internal class EventRelationsAggregationProcessor @Inject constructor(
                 existingPollSummary.sourceEvents.add(event.eventId)
                 return
             }
    -
    -        existingPollSummary.closedTime = event.originServerTs
         }
     
         private fun getPollEvent(roomId: String, eventId: String): TimelineEvent? {
    
    From df6ae4b8482975f185458d9af8c264f35c1ae925 Mon Sep 17 00:00:00 2001
    From: ericdecanini 
    Date: Thu, 10 Mar 2022 18:26:49 +0100
    Subject: [PATCH 015/262] Fixes warning for debugging
    
    ---
     .../app/features/home/room/detail/timeline/item/PollItem.kt     | 2 +-
     1 file changed, 1 insertion(+), 1 deletion(-)
    
    diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/PollItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/PollItem.kt
    index 295f4c930b..5727c8583d 100644
    --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/PollItem.kt
    +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/PollItem.kt
    @@ -85,7 +85,7 @@ abstract class PollItem : AbsMessageItem() {
             }
         }
     
    -    private fun isPollActive(optionViewState: PollOptionViewState) = true // TODO: Revert (true for debugging)
    +    private fun isPollActive(optionViewState: PollOptionViewState) = optionViewState.let { true } // TODO: Revert (true for debugging)
     //    private fun isPollActive(optionViewState: PollOptionViewState) = optionViewState !is PollOptionViewState.PollEnded
     
         class Holder : AbsMessageItem.Holder(STUB_ID) {
    
    From fe3c9cc09f07c642410e0a76f2aeef53c35d8041 Mon Sep 17 00:00:00 2001
    From: ericdecanini 
    Date: Thu, 10 Mar 2022 19:15:12 +0100
    Subject: [PATCH 016/262] Reverts to fix by removing event timestamp condition
    
    ---
     .../session/room/EventRelationsAggregationProcessor.kt      | 6 +++---
     1 file changed, 3 insertions(+), 3 deletions(-)
    
    diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EventRelationsAggregationProcessor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EventRelationsAggregationProcessor.kt
    index f3f55466da..d186f74a94 100644
    --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EventRelationsAggregationProcessor.kt
    +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EventRelationsAggregationProcessor.kt
    @@ -385,7 +385,7 @@ internal class EventRelationsAggregationProcessor @Inject constructor(
                     }
     
             val closedTime = existingPollSummary?.closedTime
    -        if (closedTime != null && eventTimestamp > closedTime) {
    +        if (closedTime != null) {
                 Timber.v("## POLL is closed ignore event poll:$targetEventId, event :${event.eventId}")
                 return
             }
    @@ -499,8 +499,6 @@ internal class EventRelationsAggregationProcessor @Inject constructor(
             }
     
             val txId = event.unsignedData?.transactionId
    -        existingPollSummary.closedTime = event.originServerTs
    -
             // is it a remote echo?
             if (!isLocalEcho && existingPollSummary.sourceLocalEchoEvents.contains(txId)) {
                 // ok it has already been managed
    @@ -509,6 +507,8 @@ internal class EventRelationsAggregationProcessor @Inject constructor(
                 existingPollSummary.sourceEvents.add(event.eventId)
                 return
             }
    +
    +        existingPollSummary.closedTime = event.originServerTs
         }
     
         private fun getPollEvent(roomId: String, eventId: String): TimelineEvent? {
    
    From 6db1c377c42e81193d332cd33b3a5506a70b306a Mon Sep 17 00:00:00 2001
    From: ericdecanini 
    Date: Thu, 10 Mar 2022 19:16:22 +0100
    Subject: [PATCH 017/262] Reverts to fix by removing event timestamp condition
    
    ---
     changelog.d/5473.bugfix | 1 +
     1 file changed, 1 insertion(+)
     create mode 100644 changelog.d/5473.bugfix
    
    diff --git a/changelog.d/5473.bugfix b/changelog.d/5473.bugfix
    new file mode 100644
    index 0000000000..e53329e202
    --- /dev/null
    +++ b/changelog.d/5473.bugfix
    @@ -0,0 +1 @@
    +Fixes polls being votable after being ended
    
    From 6a59007eb5c916047673103e57675c0c66d43c64 Mon Sep 17 00:00:00 2001
    From: ericdecanini 
    Date: Thu, 10 Mar 2022 21:26:08 +0100
    Subject: [PATCH 018/262] Reverts debug isPollActive
    
    ---
     .../app/features/home/room/detail/timeline/item/PollItem.kt    | 3 +--
     1 file changed, 1 insertion(+), 2 deletions(-)
    
    diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/PollItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/PollItem.kt
    index 5727c8583d..0ce225b21c 100644
    --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/PollItem.kt
    +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/PollItem.kt
    @@ -85,8 +85,7 @@ abstract class PollItem : AbsMessageItem() {
             }
         }
     
    -    private fun isPollActive(optionViewState: PollOptionViewState) = optionViewState.let { true } // TODO: Revert (true for debugging)
    -//    private fun isPollActive(optionViewState: PollOptionViewState) = optionViewState !is PollOptionViewState.PollEnded
    +    private fun isPollActive(optionViewState: PollOptionViewState) = optionViewState !is PollOptionViewState.PollEnded
     
         class Holder : AbsMessageItem.Holder(STUB_ID) {
             val questionTextView by bind(R.id.questionTextView)
    
    From 610c67c208f5134f0cecd3bc4ad1781504e75cb1 Mon Sep 17 00:00:00 2001
    From: ericdecanini 
    Date: Sun, 13 Mar 2022 20:09:14 +0100
    Subject: [PATCH 019/262] Refactors buildPollItem in MessageItemFactory
    
    ---
     .../home/room/detail/TimelineFragment.kt      |  18 +-
     .../timeline/factory/MessageItemFactory.kt    | 775 ++++++++++--------
     .../room/detail/timeline/item/PollItem.kt     |   6 +-
     .../features/navigation/DefaultNavigator.kt   |   2 +-
     .../app/features/navigation/Navigator.kt      |   2 +-
     .../features/poll/{create => }/PollMode.kt    |   2 +-
     .../im/vector/app/features/poll/PollState.kt  |  27 +
     .../poll/create/CreatePollFragment.kt         |   1 +
     .../poll/create/CreatePollViewModel.kt        |   1 +
     .../poll/create/CreatePollViewState.kt        |  26 +-
     10 files changed, 467 insertions(+), 393 deletions(-)
     rename vector/src/main/java/im/vector/app/features/poll/{create => }/PollMode.kt (93%)
     create mode 100644 vector/src/main/java/im/vector/app/features/poll/PollState.kt
    
    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 662af3d546..9afaf89718 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
    @@ -182,7 +182,7 @@ import im.vector.app.features.notifications.NotificationDrawerManager
     import im.vector.app.features.notifications.NotificationUtils
     import im.vector.app.features.permalink.NavigationInterceptor
     import im.vector.app.features.permalink.PermalinkHandler
    -import im.vector.app.features.poll.create.PollMode
    +import im.vector.app.features.poll.PollMode
     import im.vector.app.features.reactions.EmojiReactionPickerActivity
     import im.vector.app.features.roomprofile.RoomProfileActivity
     import im.vector.app.features.session.coroutineScope
    @@ -2165,12 +2165,16 @@ class TimelineFragment @Inject constructor(
                     timelineViewModel.handle(RoomDetailAction.UpdateQuickReactAction(action.eventId, action.clickedOn, action.add))
                 }
                 is EventSharedAction.Edit                       -> {
    -                if (action.eventType == EventType.POLL_START) {
    -                    navigator.openCreatePoll(requireContext(), timelineArgs.roomId, action.eventId, PollMode.EDIT)
    -                } else if (withState(messageComposerViewModel) { it.isVoiceMessageIdle }) {
    -                    messageComposerViewModel.handle(MessageComposerAction.EnterEditMode(action.eventId, views.composerLayout.text.toString()))
    -                } else {
    -                    requireActivity().toast(R.string.error_voice_message_cannot_reply_or_edit)
    +                when {
    +                    action.eventType == EventType.POLL_START -> {
    +                        navigator.openCreatePoll(requireContext(), timelineArgs.roomId, action.eventId, PollMode.EDIT)
    +                    }
    +                    withState(messageComposerViewModel) { it.isVoiceMessageIdle } -> {
    +                        messageComposerViewModel.handle(MessageComposerAction.EnterEditMode(action.eventId, views.composerLayout.text.toString()))
    +                    }
    +                    else -> {
    +                        requireActivity().toast(R.string.error_voice_message_cannot_reply_or_edit)
    +                    }
                     }
                 }
                 is EventSharedAction.Quote                      -> {
    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 aa1758dd6c..2cc87dfef5 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
    @@ -56,7 +56,12 @@ import im.vector.app.features.home.room.detail.timeline.item.MessageVoiceItem
     import im.vector.app.features.home.room.detail.timeline.item.MessageVoiceItem_
     import im.vector.app.features.home.room.detail.timeline.item.PollItem
     import im.vector.app.features.home.room.detail.timeline.item.PollItem_
    -import im.vector.app.features.home.room.detail.timeline.item.PollOptionViewState
    +import im.vector.app.features.home.room.detail.timeline.item.PollOptionViewState.PollEnded
    +import im.vector.app.features.home.room.detail.timeline.item.PollOptionViewState.PollReady
    +import im.vector.app.features.home.room.detail.timeline.item.PollOptionViewState.PollSending
    +import im.vector.app.features.home.room.detail.timeline.item.PollOptionViewState.PollUndisclosed
    +import im.vector.app.features.home.room.detail.timeline.item.PollOptionViewState.PollVoted
    +import im.vector.app.features.home.room.detail.timeline.item.PollResponseData
     import im.vector.app.features.home.room.detail.timeline.item.RedactedMessageItem
     import im.vector.app.features.home.room.detail.timeline.item.RedactedMessageItem_
     import im.vector.app.features.home.room.detail.timeline.item.VerificationRequestItem
    @@ -73,6 +78,12 @@ import im.vector.app.features.location.UrlMapProvider
     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.poll.PollState
    +import im.vector.app.features.poll.PollState.Ended
    +import im.vector.app.features.poll.PollState.Ready
    +import im.vector.app.features.poll.PollState.Sending
    +import im.vector.app.features.poll.PollState.Undisclosed
    +import im.vector.app.features.poll.PollState.Voted
     import im.vector.app.features.settings.VectorPreferences
     import im.vector.lib.core.utils.epoxy.charsequence.toEpoxyCharSequence
     import me.gujun.android.span.span
    @@ -95,6 +106,7 @@ import org.matrix.android.sdk.api.session.room.model.message.MessageTextContent
     import org.matrix.android.sdk.api.session.room.model.message.MessageType
     import org.matrix.android.sdk.api.session.room.model.message.MessageVerificationRequestContent
     import org.matrix.android.sdk.api.session.room.model.message.MessageVideoContent
    +import org.matrix.android.sdk.api.session.room.model.message.PollAnswer
     import org.matrix.android.sdk.api.session.room.model.message.PollType
     import org.matrix.android.sdk.api.session.room.model.message.getFileName
     import org.matrix.android.sdk.api.session.room.model.message.getFileUrl
    @@ -107,30 +119,30 @@ import org.matrix.android.sdk.internal.database.lightweight.LightweightSettingsS
     import javax.inject.Inject
     
     class MessageItemFactory @Inject constructor(
    -        private val localFilesHelper: LocalFilesHelper,
    -        private val colorProvider: ColorProvider,
    -        private val dimensionConverter: DimensionConverter,
    -        private val timelineMediaSizeProvider: TimelineMediaSizeProvider,
    -        private val htmlRenderer: Lazy,
    -        private val htmlCompressor: VectorHtmlCompressor,
    -        private val textRendererFactory: EventTextRenderer.Factory,
    -        private val stringProvider: StringProvider,
    -        private val imageContentRenderer: ImageContentRenderer,
    -        private val messageInformationDataFactory: MessageInformationDataFactory,
    -        private val messageItemAttributesFactory: MessageItemAttributesFactory,
    -        private val contentUploadStateTrackerBinder: ContentUploadStateTrackerBinder,
    -        private val contentDownloadStateTrackerBinder: ContentDownloadStateTrackerBinder,
    -        private val defaultItemFactory: DefaultItemFactory,
    -        private val noticeItemFactory: NoticeItemFactory,
    -        private val avatarSizeProvider: AvatarSizeProvider,
    -        private val pillsPostProcessorFactory: PillsPostProcessor.Factory,
    -        private val lightweightSettingsStorage: LightweightSettingsStorage,
    -        private val spanUtils: SpanUtils,
    -        private val session: Session,
    -        private val voiceMessagePlaybackTracker: VoiceMessagePlaybackTracker,
    -        private val locationPinProvider: LocationPinProvider,
    -        private val vectorPreferences: VectorPreferences,
    -        private val urlMapProvider: UrlMapProvider,
    +    private val localFilesHelper: LocalFilesHelper,
    +    private val colorProvider: ColorProvider,
    +    private val dimensionConverter: DimensionConverter,
    +    private val timelineMediaSizeProvider: TimelineMediaSizeProvider,
    +    private val htmlRenderer: Lazy,
    +    private val htmlCompressor: VectorHtmlCompressor,
    +    private val textRendererFactory: EventTextRenderer.Factory,
    +    private val stringProvider: StringProvider,
    +    private val imageContentRenderer: ImageContentRenderer,
    +    private val messageInformationDataFactory: MessageInformationDataFactory,
    +    private val messageItemAttributesFactory: MessageItemAttributesFactory,
    +    private val contentUploadStateTrackerBinder: ContentUploadStateTrackerBinder,
    +    private val contentDownloadStateTrackerBinder: ContentDownloadStateTrackerBinder,
    +    private val defaultItemFactory: DefaultItemFactory,
    +    private val noticeItemFactory: NoticeItemFactory,
    +    private val avatarSizeProvider: AvatarSizeProvider,
    +    private val pillsPostProcessorFactory: PillsPostProcessor.Factory,
    +    private val lightweightSettingsStorage: LightweightSettingsStorage,
    +    private val spanUtils: SpanUtils,
    +    private val session: Session,
    +    private val voiceMessagePlaybackTracker: VoiceMessagePlaybackTracker,
    +    private val locationPinProvider: LocationPinProvider,
    +    private val vectorPreferences: VectorPreferences,
    +    private val urlMapProvider: UrlMapProvider,
     ) {
     
         // TODO inject this properly?
    @@ -165,7 +177,7 @@ class MessageItemFactory @Inject constructor(
                 return defaultItemFactory.create(malformedText, informationData, highlight, callback)
             }
             if (messageContent.relatesTo?.type == RelationType.REPLACE ||
    -                event.isEncrypted() && event.root.content.toModel()?.relatesTo?.type == RelationType.REPLACE
    +            event.isEncrypted() && event.root.content.toModel()?.relatesTo?.type == RelationType.REPLACE
             ) {
                 // This is an edit event, we should display it when debugging as a notice event
                 return noticeItemFactory.create(params)
    @@ -179,16 +191,16 @@ class MessageItemFactory @Inject constructor(
             // always hide summary when we are on thread timeline
             val attributes = messageItemAttributesFactory.create(messageContent, informationData, callback, threadDetails)
     
    -//        val all = event.root.toContent()
    -//        val ev = all.toModel()
    +        //        val all = event.root.toContent()
    +        //        val ev = all.toModel()
             val messageItem = when (messageContent) {
    -            is MessageEmoteContent               -> buildEmoteMessageItem(messageContent, informationData, highlight, callback, attributes)
    -            is MessageTextContent                -> buildItemForTextContent(messageContent, informationData, highlight, callback, attributes)
    -            is MessageImageInfoContent           -> buildImageMessageItem(messageContent, informationData, highlight, callback, attributes)
    -            is MessageNoticeContent              -> buildNoticeMessageItem(messageContent, informationData, highlight, callback, attributes)
    -            is MessageVideoContent               -> buildVideoMessageItem(messageContent, informationData, highlight, callback, attributes)
    -            is MessageFileContent                -> buildFileMessageItem(messageContent, highlight, attributes)
    -            is MessageAudioContent               -> {
    +            is MessageEmoteContent -> buildEmoteMessageItem(messageContent, informationData, highlight, callback, attributes)
    +            is MessageTextContent -> buildItemForTextContent(messageContent, informationData, highlight, callback, attributes)
    +            is MessageImageInfoContent -> buildImageMessageItem(messageContent, informationData, highlight, callback, attributes)
    +            is MessageNoticeContent -> buildNoticeMessageItem(messageContent, informationData, highlight, callback, attributes)
    +            is MessageVideoContent -> buildVideoMessageItem(messageContent, informationData, highlight, callback, attributes)
    +            is MessageFileContent -> buildFileMessageItem(messageContent, highlight, attributes)
    +            is MessageAudioContent -> {
                     if (messageContent.voiceMessageIndicator != null) {
                         buildVoiceMessageItem(params, messageContent, informationData, highlight, attributes)
                     } else {
    @@ -196,25 +208,27 @@ class MessageItemFactory @Inject constructor(
                     }
                 }
                 is MessageVerificationRequestContent -> buildVerificationRequestMessageItem(messageContent, informationData, highlight, callback, attributes)
    -            is MessagePollContent                -> buildPollItem(messageContent, informationData, highlight, callback, attributes)
    -            is MessageLocationContent            -> {
    +            is MessagePollContent -> buildPollItem(messageContent, informationData, highlight, callback, attributes)
    +            is MessageLocationContent -> {
                     if (vectorPreferences.labsRenderLocationsInTimeline()) {
                         buildLocationItem(messageContent, informationData, highlight, attributes)
                     } else {
                         buildMessageTextItem(messageContent.body, false, informationData, highlight, callback, attributes)
                     }
                 }
    -            else                                 -> buildNotHandledMessageItem(messageContent, informationData, highlight, callback, attributes)
    +            else -> buildNotHandledMessageItem(messageContent, informationData, highlight, callback, attributes)
             }
             return messageItem?.apply {
                 layout(informationData.messageLayout.layoutRes)
             }
         }
     
    -    private fun buildLocationItem(locationContent: MessageLocationContent,
    -                                  informationData: MessageInformationData,
    -                                  highlight: Boolean,
    -                                  attributes: AbsMessageItem.Attributes): MessageLocationItem? {
    +    private fun buildLocationItem(
    +        locationContent: MessageLocationContent,
    +        informationData: MessageInformationData,
    +        highlight: Boolean,
    +        attributes: AbsMessageItem.Attributes,
    +    ): MessageLocationItem? {
             val width = timelineMediaSizeProvider.getMaxSize().first
             val height = dimensionConverter.dpToPx(200)
     
    @@ -225,98 +239,109 @@ class MessageItemFactory @Inject constructor(
             val userId = if (locationContent.isSelfLocation()) informationData.senderId else null
     
             return MessageLocationItem_()
    -                .attributes(attributes)
    -                .locationUrl(locationUrl)
    -                .mapWidth(width)
    -                .mapHeight(height)
    -                .userId(userId)
    -                .locationPinProvider(locationPinProvider)
    -                .highlighted(highlight)
    -                .leftGuideline(avatarSizeProvider.leftGuideline)
    +            .attributes(attributes)
    +            .locationUrl(locationUrl)
    +            .mapWidth(width)
    +            .mapHeight(height)
    +            .userId(userId)
    +            .locationPinProvider(locationPinProvider)
    +            .highlighted(highlight)
    +            .leftGuideline(avatarSizeProvider.leftGuideline)
         }
     
    -    private fun buildPollItem(pollContent: MessagePollContent,
    -                              informationData: MessageInformationData,
    -                              highlight: Boolean,
    -                              callback: TimelineEventController.Callback?,
    -                              attributes: AbsMessageItem.Attributes): PollItem? {
    -        val optionViewStates = mutableListOf()
    -
    +    private fun buildPollItem(
    +        pollContent: MessagePollContent,
    +        informationData: MessageInformationData,
    +        highlight: Boolean,
    +        callback: TimelineEventController.Callback?,
    +        attributes: AbsMessageItem.Attributes,
    +    ): PollItem {
             val pollResponseSummary = informationData.pollResponseAggregatedSummary
    -        val isEnded = pollResponseSummary?.isClosed.orFalse()
    -        val didUserVoted = pollResponseSummary?.myVote?.isNotEmpty().orFalse()
    -        val winnerVoteCount = pollResponseSummary?.winnerVoteCount
    -        val isPollSent = informationData.sendState.isSent()
    -        val isPollUndisclosed = pollContent.pollCreationInfo?.kind == PollType.UNDISCLOSED
    -
    -        val totalVotesText = (pollResponseSummary?.totalVotes ?: 0).let {
    -            when {
    -                isEnded           -> stringProvider.getQuantityString(R.plurals.poll_total_vote_count_after_ended, it, it)
    -                isPollUndisclosed -> ""
    -                didUserVoted      -> stringProvider.getQuantityString(R.plurals.poll_total_vote_count_before_ended_and_voted, it, it)
    -                else              -> if (it == 0) {
    -                    stringProvider.getString(R.string.poll_no_votes_cast)
    -                } else {
    -                    stringProvider.getQuantityString(R.plurals.poll_total_vote_count_before_ended_and_not_voted, it, it)
    -                }
    -            }
    -        }
    -
    -        pollContent.pollCreationInfo?.answers?.forEach { option ->
    -            val voteSummary = pollResponseSummary?.votes?.get(option.id)
    -            val isMyVote = pollResponseSummary?.myVote == option.id
    -            val voteCount = voteSummary?.total ?: 0
    -            val votePercentage = voteSummary?.percentage ?: 0.0
    -            val optionId = option.id ?: ""
    -            val optionAnswer = option.answer ?: ""
    -
    -            optionViewStates.add(
    -                    if (!isPollSent) {
    -                        // Poll event is not send yet. Disable option.
    -                        PollOptionViewState.PollSending(optionId, optionAnswer)
    -                    } else if (isEnded) {
    -                        // Poll is ended. Disable option, show votes and mark the winner.
    -                        val isWinner = winnerVoteCount != 0 && voteCount == winnerVoteCount
    -                        PollOptionViewState.PollEnded(optionId, optionAnswer, voteCount, votePercentage, isWinner)
    -                    } else if (isPollUndisclosed) {
    -                        // Poll is closed. Enable option, hide votes and mark the user's selection.
    -                        PollOptionViewState.PollUndisclosed(optionId, optionAnswer, isMyVote)
    -                    } else if (didUserVoted) {
    -                        // User voted to the poll, but poll is not ended. Enable option, show votes and mark the user's selection.
    -                        PollOptionViewState.PollVoted(optionId, optionAnswer, voteCount, votePercentage, isMyVote)
    -                    } else {
    -                        // User didn't voted yet and poll is not ended yet. Enable options, hide votes.
    -                        PollOptionViewState.PollReady(optionId, optionAnswer)
    -                    }
    -            )
    -        }
    -
    -        val question = pollContent.pollCreationInfo?.question?.question ?: ""
    +        val pollState = createPollState(informationData, pollResponseSummary, pollContent)
    +        val optionViewStates = pollContent.pollCreationInfo?.answers?.mapToOptions(pollState, informationData)
    +        val questionText = pollContent.pollCreationInfo?.question?.question.orEmpty()
    +        val question = createPollQuestion(informationData, questionText, callback)
    +        val totalVotesText = createTotalVotesText(pollState, pollResponseSummary)
     
             return PollItem_()
    -                .attributes(attributes)
    -                .eventId(informationData.eventId)
    -                .pollQuestion(
    -                        if (informationData.hasBeenEdited) {
    -                            annotateWithEdited(question, callback, informationData)
    -                        } else {
    -                            question
    -                        }.toEpoxyCharSequence()
    -                )
    -                .pollSent(isPollSent)
    -                .totalVotesText(totalVotesText)
    -                .optionViewStates(optionViewStates)
    -                .edited(informationData.hasBeenEdited)
    -                .highlighted(highlight)
    -                .leftGuideline(avatarSizeProvider.leftGuideline)
    -                .callback(callback)
    +            .attributes(attributes)
    +            .eventId(informationData.eventId)
    +            .pollQuestion(question)
    +            .canVote(pollState.isVotable())
    +            .totalVotesText(totalVotesText)
    +            .optionViewStates(optionViewStates)
    +            .edited(informationData.hasBeenEdited)
    +            .highlighted(highlight)
    +            .leftGuideline(avatarSizeProvider.leftGuideline)
    +            .callback(callback)
         }
     
    -    private fun buildAudioMessageItem(messageContent: MessageAudioContent,
    -                                      @Suppress("UNUSED_PARAMETER")
    -                                      informationData: MessageInformationData,
    -                                      highlight: Boolean,
    -                                      attributes: AbsMessageItem.Attributes): MessageFileItem? {
    +    private fun createPollState(
    +        informationData: MessageInformationData,
    +        pollResponseSummary: PollResponseData?,
    +        pollContent: MessagePollContent,
    +    ): PollState = when {
    +        !informationData.sendState.isSent() -> Sending
    +        pollResponseSummary?.isClosed.orFalse() -> Ended
    +        pollContent.pollCreationInfo?.kind == PollType.UNDISCLOSED -> Undisclosed
    +        pollResponseSummary?.myVote?.isNotEmpty().orFalse() -> Voted(pollResponseSummary?.totalVotes ?: 0)
    +        else -> Ready
    +    }
    +
    +    private fun List.mapToOptions(
    +        pollState: PollState,
    +        informationData: MessageInformationData,
    +    ) = map { answer ->
    +        val pollResponseSummary = informationData.pollResponseAggregatedSummary
    +        val winnerVoteCount = pollResponseSummary?.winnerVoteCount
    +        val optionId = answer.id ?: ""
    +        val optionAnswer = answer.answer ?: ""
    +        val voteSummary = pollResponseSummary?.votes?.get(answer.id)
    +        val voteCount = voteSummary?.total ?: 0
    +        val votePercentage = voteSummary?.percentage ?: 0.0
    +        val isMyVote = pollResponseSummary?.myVote == answer.id
    +        val isWinner = winnerVoteCount != 0 && voteCount == winnerVoteCount
    +
    +        when (pollState) {
    +            Sending -> PollSending(optionId, optionAnswer)
    +            Ready -> PollReady(optionId, optionAnswer)
    +            is Voted -> PollVoted(optionId, optionAnswer, voteCount, votePercentage, isMyVote)
    +            Undisclosed -> PollUndisclosed(optionId, optionAnswer, isMyVote)
    +            Ended -> PollEnded(optionId, optionAnswer, voteCount, votePercentage, isWinner)
    +        }
    +    }
    +
    +    private fun createPollQuestion(
    +        informationData: MessageInformationData,
    +        question: String,
    +        callback: TimelineEventController.Callback?,
    +    ) = if (informationData.hasBeenEdited) {
    +        annotateWithEdited(question, callback, informationData)
    +    } else {
    +        question
    +    }.toEpoxyCharSequence()
    +
    +    private fun createTotalVotesText(
    +        pollState: PollState,
    +        pollResponseSummary: PollResponseData?,
    +    ): String {
    +        val votes = pollResponseSummary?.totalVotes ?: 0
    +        return when {
    +            pollState is Ended -> stringProvider.getQuantityString(R.plurals.poll_total_vote_count_after_ended, votes, votes)
    +            pollState is Undisclosed -> ""
    +            pollState is Voted -> stringProvider.getQuantityString(R.plurals.poll_total_vote_count_before_ended_and_voted, votes, votes)
    +            votes == 0 -> stringProvider.getString(R.string.poll_no_votes_cast)
    +            else -> stringProvider.getQuantityString(R.plurals.poll_total_vote_count_before_ended_and_not_voted, votes, votes)
    +        }
    +    }
    +
    +    private fun buildAudioMessageItem(
    +        messageContent: MessageAudioContent,
    +        @Suppress("UNUSED_PARAMETER")
    +        informationData: MessageInformationData,
    +        highlight: Boolean,
    +        attributes: AbsMessageItem.Attributes,
    +    ): MessageFileItem? {
             val fileUrl = messageContent.getFileUrl()?.let {
                 if (informationData.sentByMe && !informationData.sendState.isSent()) {
                     it
    @@ -325,29 +350,31 @@ class MessageItemFactory @Inject constructor(
                 }
             } ?: ""
             return MessageFileItem_()
    -                .attributes(attributes)
    -                .izLocalFile(localFilesHelper.isLocalFile(fileUrl))
    -                .izDownloaded(session.fileService().isFileInCache(
    -                        fileUrl,
    -                        messageContent.getFileName(),
    -                        messageContent.mimeType,
    -                        messageContent.encryptedFileInfo?.toElementToDecrypt())
    -                )
    -                .mxcUrl(fileUrl)
    -                .contentUploadStateTrackerBinder(contentUploadStateTrackerBinder)
    -                .contentDownloadStateTrackerBinder(contentDownloadStateTrackerBinder)
    -                .highlighted(highlight)
    -                .leftGuideline(avatarSizeProvider.leftGuideline)
    -                .filename(messageContent.body)
    -                .iconRes(R.drawable.ic_headphones)
    +            .attributes(attributes)
    +            .izLocalFile(localFilesHelper.isLocalFile(fileUrl))
    +            .izDownloaded(session.fileService().isFileInCache(
    +                fileUrl,
    +                messageContent.getFileName(),
    +                messageContent.mimeType,
    +                messageContent.encryptedFileInfo?.toElementToDecrypt())
    +            )
    +            .mxcUrl(fileUrl)
    +            .contentUploadStateTrackerBinder(contentUploadStateTrackerBinder)
    +            .contentDownloadStateTrackerBinder(contentDownloadStateTrackerBinder)
    +            .highlighted(highlight)
    +            .leftGuideline(avatarSizeProvider.leftGuideline)
    +            .filename(messageContent.body)
    +            .iconRes(R.drawable.ic_headphones)
         }
     
    -    private fun buildVoiceMessageItem(params: TimelineItemFactoryParams,
    -                                      messageContent: MessageAudioContent,
    -                                      @Suppress("UNUSED_PARAMETER")
    -                                      informationData: MessageInformationData,
    -                                      highlight: Boolean,
    -                                      attributes: AbsMessageItem.Attributes): MessageVoiceItem? {
    +    private fun buildVoiceMessageItem(
    +        params: TimelineItemFactoryParams,
    +        messageContent: MessageAudioContent,
    +        @Suppress("UNUSED_PARAMETER")
    +        informationData: MessageInformationData,
    +        highlight: Boolean,
    +        attributes: AbsMessageItem.Attributes,
    +    ): MessageVoiceItem? {
             val fileUrl = messageContent.getFileUrl()?.let {
                 if (informationData.sentByMe && !informationData.sendState.isSent()) {
                     it
    @@ -363,31 +390,33 @@ class MessageItemFactory @Inject constructor(
             }
     
             return MessageVoiceItem_()
    -                .attributes(attributes)
    -                .duration(messageContent.audioWaveformInfo?.duration ?: 0)
    -                .waveform(messageContent.audioWaveformInfo?.waveform?.toFft().orEmpty())
    -                .playbackControlButtonClickListener(playbackControlButtonClickListener)
    -                .voiceMessagePlaybackTracker(voiceMessagePlaybackTracker)
    -                .izLocalFile(localFilesHelper.isLocalFile(fileUrl))
    -                .izDownloaded(session.fileService().isFileInCache(
    -                        fileUrl,
    -                        messageContent.getFileName(),
    -                        messageContent.mimeType,
    -                        messageContent.encryptedFileInfo?.toElementToDecrypt())
    -                )
    -                .mxcUrl(fileUrl)
    -                .contentUploadStateTrackerBinder(contentUploadStateTrackerBinder)
    -                .contentDownloadStateTrackerBinder(contentDownloadStateTrackerBinder)
    -                .highlighted(highlight)
    -                .leftGuideline(avatarSizeProvider.leftGuideline)
    +            .attributes(attributes)
    +            .duration(messageContent.audioWaveformInfo?.duration ?: 0)
    +            .waveform(messageContent.audioWaveformInfo?.waveform?.toFft().orEmpty())
    +            .playbackControlButtonClickListener(playbackControlButtonClickListener)
    +            .voiceMessagePlaybackTracker(voiceMessagePlaybackTracker)
    +            .izLocalFile(localFilesHelper.isLocalFile(fileUrl))
    +            .izDownloaded(session.fileService().isFileInCache(
    +                fileUrl,
    +                messageContent.getFileName(),
    +                messageContent.mimeType,
    +                messageContent.encryptedFileInfo?.toElementToDecrypt())
    +            )
    +            .mxcUrl(fileUrl)
    +            .contentUploadStateTrackerBinder(contentUploadStateTrackerBinder)
    +            .contentDownloadStateTrackerBinder(contentDownloadStateTrackerBinder)
    +            .highlighted(highlight)
    +            .leftGuideline(avatarSizeProvider.leftGuideline)
         }
     
    -    private fun buildVerificationRequestMessageItem(messageContent: MessageVerificationRequestContent,
    -                                                    @Suppress("UNUSED_PARAMETER")
    -                                                    informationData: MessageInformationData,
    -                                                    highlight: Boolean,
    -                                                    callback: TimelineEventController.Callback?,
    -                                                    attributes: AbsMessageItem.Attributes): VerificationRequestItem? {
    +    private fun buildVerificationRequestMessageItem(
    +        messageContent: MessageVerificationRequestContent,
    +        @Suppress("UNUSED_PARAMETER")
    +        informationData: MessageInformationData,
    +        highlight: Boolean,
    +        callback: TimelineEventController.Callback?,
    +        attributes: AbsMessageItem.Attributes,
    +    ): VerificationRequestItem? {
             // If this request is not sent by me or sent to me, we should ignore it in timeline
             val myUserId = session.myUserId
             if (informationData.senderId != myUserId && messageContent.toUserId != myUserId) {
    @@ -401,136 +430,144 @@ class MessageItemFactory @Inject constructor(
                 informationData.memberName
             }
             return VerificationRequestItem_()
    -                .attributes(
    -                        VerificationRequestItem.Attributes(
    -                                otherUserId = otherUserId,
    -                                otherUserName = otherUserName.toString(),
    -                                referenceId = informationData.eventId,
    -                                informationData = informationData,
    -                                avatarRenderer = attributes.avatarRenderer,
    -                                messageColorProvider = attributes.messageColorProvider,
    -                                itemLongClickListener = attributes.itemLongClickListener,
    -                                itemClickListener = attributes.itemClickListener,
    -                                reactionPillCallback = attributes.reactionPillCallback,
    -                                readReceiptsCallback = attributes.readReceiptsCallback,
    -                                emojiTypeFace = attributes.emojiTypeFace
    -                        )
    +            .attributes(
    +                VerificationRequestItem.Attributes(
    +                    otherUserId = otherUserId,
    +                    otherUserName = otherUserName.toString(),
    +                    referenceId = informationData.eventId,
    +                    informationData = informationData,
    +                    avatarRenderer = attributes.avatarRenderer,
    +                    messageColorProvider = attributes.messageColorProvider,
    +                    itemLongClickListener = attributes.itemLongClickListener,
    +                    itemClickListener = attributes.itemClickListener,
    +                    reactionPillCallback = attributes.reactionPillCallback,
    +                    readReceiptsCallback = attributes.readReceiptsCallback,
    +                    emojiTypeFace = attributes.emojiTypeFace
                     )
    -                .callback(callback)
    -                .highlighted(highlight)
    -                .leftGuideline(avatarSizeProvider.leftGuideline)
    +            )
    +            .callback(callback)
    +            .highlighted(highlight)
    +            .leftGuideline(avatarSizeProvider.leftGuideline)
         }
     
    -    private fun buildFileMessageItem(messageContent: MessageFileContent,
    -//                                     informationData: MessageInformationData,
    -                                     highlight: Boolean,
    -//                                     callback: TimelineEventController.Callback?,
    -                                     attributes: AbsMessageItem.Attributes): MessageFileItem? {
    +    private fun buildFileMessageItem(
    +        messageContent: MessageFileContent,
    +        highlight: Boolean,
    +        attributes: AbsMessageItem.Attributes,
    +    ): MessageFileItem? {
             val mxcUrl = messageContent.getFileUrl() ?: ""
             return MessageFileItem_()
    -                .attributes(attributes)
    -                .leftGuideline(avatarSizeProvider.leftGuideline)
    -                .izLocalFile(localFilesHelper.isLocalFile(messageContent.getFileUrl()))
    -                .izDownloaded(session.fileService().isFileInCache(messageContent))
    -                .mxcUrl(mxcUrl)
    -                .contentUploadStateTrackerBinder(contentUploadStateTrackerBinder)
    -                .contentDownloadStateTrackerBinder(contentDownloadStateTrackerBinder)
    -                .highlighted(highlight)
    -                .filename(messageContent.body)
    -                .iconRes(R.drawable.ic_paperclip)
    +            .attributes(attributes)
    +            .leftGuideline(avatarSizeProvider.leftGuideline)
    +            .izLocalFile(localFilesHelper.isLocalFile(messageContent.getFileUrl()))
    +            .izDownloaded(session.fileService().isFileInCache(messageContent))
    +            .mxcUrl(mxcUrl)
    +            .contentUploadStateTrackerBinder(contentUploadStateTrackerBinder)
    +            .contentDownloadStateTrackerBinder(contentDownloadStateTrackerBinder)
    +            .highlighted(highlight)
    +            .filename(messageContent.body)
    +            .iconRes(R.drawable.ic_paperclip)
         }
     
    -    private fun buildNotHandledMessageItem(messageContent: MessageContent,
    -                                           informationData: MessageInformationData,
    -                                           highlight: Boolean,
    -                                           callback: TimelineEventController.Callback?,
    -                                           attributes: AbsMessageItem.Attributes): MessageTextItem? {
    +    private fun buildNotHandledMessageItem(
    +        messageContent: MessageContent,
    +        informationData: MessageInformationData,
    +        highlight: Boolean,
    +        callback: TimelineEventController.Callback?,
    +        attributes: AbsMessageItem.Attributes,
    +    ): MessageTextItem? {
             // For compatibility reason we should display the body
             return buildMessageTextItem(messageContent.body, false, informationData, highlight, callback, attributes)
         }
     
    -    private fun buildImageMessageItem(messageContent: MessageImageInfoContent,
    -                                      @Suppress("UNUSED_PARAMETER")
    -                                      informationData: MessageInformationData,
    -                                      highlight: Boolean,
    -                                      callback: TimelineEventController.Callback?,
    -                                      attributes: AbsMessageItem.Attributes): MessageImageVideoItem? {
    +    private fun buildImageMessageItem(
    +        messageContent: MessageImageInfoContent,
    +        @Suppress("UNUSED_PARAMETER")
    +        informationData: MessageInformationData,
    +        highlight: Boolean,
    +        callback: TimelineEventController.Callback?,
    +        attributes: AbsMessageItem.Attributes,
    +    ): MessageImageVideoItem? {
             val (maxWidth, maxHeight) = timelineMediaSizeProvider.getMaxSize()
             val data = ImageContentRenderer.Data(
    -                eventId = informationData.eventId,
    -                filename = messageContent.body,
    -                mimeType = messageContent.mimeType,
    -                url = messageContent.getFileUrl(),
    -                elementToDecrypt = messageContent.encryptedFileInfo?.toElementToDecrypt(),
    -                height = messageContent.info?.height,
    -                maxHeight = maxHeight,
    -                width = messageContent.info?.width,
    -                maxWidth = maxWidth,
    -                allowNonMxcUrls = informationData.sendState.isSending()
    +            eventId = informationData.eventId,
    +            filename = messageContent.body,
    +            mimeType = messageContent.mimeType,
    +            url = messageContent.getFileUrl(),
    +            elementToDecrypt = messageContent.encryptedFileInfo?.toElementToDecrypt(),
    +            height = messageContent.info?.height,
    +            maxHeight = maxHeight,
    +            width = messageContent.info?.width,
    +            maxWidth = maxWidth,
    +            allowNonMxcUrls = informationData.sendState.isSending()
             )
             return MessageImageVideoItem_()
    -                .attributes(attributes)
    -                .leftGuideline(avatarSizeProvider.leftGuideline)
    -                .imageContentRenderer(imageContentRenderer)
    -                .contentUploadStateTrackerBinder(contentUploadStateTrackerBinder)
    -                .playable(messageContent.mimeType == MimeTypes.Gif)
    -                .highlighted(highlight)
    -                .mediaData(data)
    -                .apply {
    -                    if (messageContent.msgType == MessageType.MSGTYPE_STICKER_LOCAL) {
    -                        mode(ImageContentRenderer.Mode.STICKER)
    -                    } else {
    -                        clickListener { view ->
    -                            callback?.onImageMessageClicked(messageContent, data, view)
    -                        }
    +            .attributes(attributes)
    +            .leftGuideline(avatarSizeProvider.leftGuideline)
    +            .imageContentRenderer(imageContentRenderer)
    +            .contentUploadStateTrackerBinder(contentUploadStateTrackerBinder)
    +            .playable(messageContent.mimeType == MimeTypes.Gif)
    +            .highlighted(highlight)
    +            .mediaData(data)
    +            .apply {
    +                if (messageContent.msgType == MessageType.MSGTYPE_STICKER_LOCAL) {
    +                    mode(ImageContentRenderer.Mode.STICKER)
    +                } else {
    +                    clickListener { view ->
    +                        callback?.onImageMessageClicked(messageContent, data, view)
                         }
                     }
    +            }
         }
     
    -    private fun buildVideoMessageItem(messageContent: MessageVideoContent,
    -                                      informationData: MessageInformationData,
    -                                      highlight: Boolean,
    -                                      callback: TimelineEventController.Callback?,
    -                                      attributes: AbsMessageItem.Attributes): MessageImageVideoItem? {
    +    private fun buildVideoMessageItem(
    +        messageContent: MessageVideoContent,
    +        informationData: MessageInformationData,
    +        highlight: Boolean,
    +        callback: TimelineEventController.Callback?,
    +        attributes: AbsMessageItem.Attributes,
    +    ): MessageImageVideoItem? {
             val (maxWidth, maxHeight) = timelineMediaSizeProvider.getMaxSize()
             val thumbnailData = ImageContentRenderer.Data(
    -                eventId = informationData.eventId,
    -                filename = messageContent.body,
    -                mimeType = messageContent.mimeType,
    -                url = messageContent.videoInfo?.getThumbnailUrl(),
    -                elementToDecrypt = messageContent.videoInfo?.thumbnailFile?.toElementToDecrypt(),
    -                height = messageContent.videoInfo?.height,
    -                maxHeight = maxHeight,
    -                width = messageContent.videoInfo?.width,
    -                maxWidth = maxWidth,
    -                allowNonMxcUrls = informationData.sendState.isSending()
    +            eventId = informationData.eventId,
    +            filename = messageContent.body,
    +            mimeType = messageContent.mimeType,
    +            url = messageContent.videoInfo?.getThumbnailUrl(),
    +            elementToDecrypt = messageContent.videoInfo?.thumbnailFile?.toElementToDecrypt(),
    +            height = messageContent.videoInfo?.height,
    +            maxHeight = maxHeight,
    +            width = messageContent.videoInfo?.width,
    +            maxWidth = maxWidth,
    +            allowNonMxcUrls = informationData.sendState.isSending()
             )
     
             val videoData = VideoContentRenderer.Data(
    -                eventId = informationData.eventId,
    -                filename = messageContent.body,
    -                mimeType = messageContent.mimeType,
    -                url = messageContent.getFileUrl(),
    -                elementToDecrypt = messageContent.encryptedFileInfo?.toElementToDecrypt(),
    -                thumbnailMediaData = thumbnailData
    +            eventId = informationData.eventId,
    +            filename = messageContent.body,
    +            mimeType = messageContent.mimeType,
    +            url = messageContent.getFileUrl(),
    +            elementToDecrypt = messageContent.encryptedFileInfo?.toElementToDecrypt(),
    +            thumbnailMediaData = thumbnailData
             )
     
             return MessageImageVideoItem_()
    -                .leftGuideline(avatarSizeProvider.leftGuideline)
    -                .attributes(attributes)
    -                .imageContentRenderer(imageContentRenderer)
    -                .contentUploadStateTrackerBinder(contentUploadStateTrackerBinder)
    -                .playable(true)
    -                .highlighted(highlight)
    -                .mediaData(thumbnailData)
    -                .clickListener { view -> callback?.onVideoMessageClicked(messageContent, videoData, view.findViewById(R.id.messageThumbnailView)) }
    +            .leftGuideline(avatarSizeProvider.leftGuideline)
    +            .attributes(attributes)
    +            .imageContentRenderer(imageContentRenderer)
    +            .contentUploadStateTrackerBinder(contentUploadStateTrackerBinder)
    +            .playable(true)
    +            .highlighted(highlight)
    +            .mediaData(thumbnailData)
    +            .clickListener { view -> callback?.onVideoMessageClicked(messageContent, videoData, view.findViewById(R.id.messageThumbnailView)) }
         }
     
    -    private fun buildItemForTextContent(messageContent: MessageTextContent,
    -                                        informationData: MessageInformationData,
    -                                        highlight: Boolean,
    -                                        callback: TimelineEventController.Callback?,
    -                                        attributes: AbsMessageItem.Attributes): VectorEpoxyModel<*>? {
    +    private fun buildItemForTextContent(
    +        messageContent: MessageTextContent,
    +        informationData: MessageInformationData,
    +        highlight: Boolean,
    +        callback: TimelineEventController.Callback?,
    +        attributes: AbsMessageItem.Attributes,
    +    ): VectorEpoxyModel<*>? {
             val matrixFormattedBody = messageContent.matrixFormattedBody
             return if (matrixFormattedBody != null) {
                 buildFormattedTextItem(matrixFormattedBody, informationData, highlight, callback, attributes)
    @@ -539,50 +576,56 @@ class MessageItemFactory @Inject constructor(
             }
         }
     
    -    private fun buildFormattedTextItem(matrixFormattedBody: String,
    -                                       informationData: MessageInformationData,
    -                                       highlight: Boolean,
    -                                       callback: TimelineEventController.Callback?,
    -                                       attributes: AbsMessageItem.Attributes): MessageTextItem? {
    +    private fun buildFormattedTextItem(
    +        matrixFormattedBody: String,
    +        informationData: MessageInformationData,
    +        highlight: Boolean,
    +        callback: TimelineEventController.Callback?,
    +        attributes: AbsMessageItem.Attributes,
    +    ): MessageTextItem? {
             val compressed = htmlCompressor.compress(matrixFormattedBody)
             val renderedFormattedBody = htmlRenderer.get().render(compressed, pillsPostProcessor) as Spanned
             return buildMessageTextItem(renderedFormattedBody, true, informationData, highlight, callback, attributes)
         }
     
    -    private fun buildMessageTextItem(body: CharSequence,
    -                                     isFormatted: Boolean,
    -                                     informationData: MessageInformationData,
    -                                     highlight: Boolean,
    -                                     callback: TimelineEventController.Callback?,
    -                                     attributes: AbsMessageItem.Attributes): MessageTextItem? {
    +    private fun buildMessageTextItem(
    +        body: CharSequence,
    +        isFormatted: Boolean,
    +        informationData: MessageInformationData,
    +        highlight: Boolean,
    +        callback: TimelineEventController.Callback?,
    +        attributes: AbsMessageItem.Attributes,
    +    ): MessageTextItem? {
             val renderedBody = textRenderer.render(body)
             val bindingOptions = spanUtils.getBindingOptions(renderedBody)
             val linkifiedBody = renderedBody.linkify(callback)
     
             return MessageTextItem_()
    -                .message(
    -                        if (informationData.hasBeenEdited) {
    -                            annotateWithEdited(linkifiedBody, callback, informationData)
    -                        } else {
    -                            linkifiedBody
    -                        }.toEpoxyCharSequence()
    -                )
    -                .useBigFont(linkifiedBody.length <= MAX_NUMBER_OF_EMOJI_FOR_BIG_FONT * 2 && containsOnlyEmojis(linkifiedBody.toString()))
    -                .bindingOptions(bindingOptions)
    -                .markwonPlugins(htmlRenderer.get().plugins)
    -                .searchForPills(isFormatted)
    -                .previewUrlRetriever(callback?.getPreviewUrlRetriever())
    -                .imageContentRenderer(imageContentRenderer)
    -                .previewUrlCallback(callback)
    -                .leftGuideline(avatarSizeProvider.leftGuideline)
    -                .attributes(attributes)
    -                .highlighted(highlight)
    -                .movementMethod(createLinkMovementMethod(callback))
    +            .message(
    +                if (informationData.hasBeenEdited) {
    +                    annotateWithEdited(linkifiedBody, callback, informationData)
    +                } else {
    +                    linkifiedBody
    +                }.toEpoxyCharSequence()
    +            )
    +            .useBigFont(linkifiedBody.length <= MAX_NUMBER_OF_EMOJI_FOR_BIG_FONT * 2 && containsOnlyEmojis(linkifiedBody.toString()))
    +            .bindingOptions(bindingOptions)
    +            .markwonPlugins(htmlRenderer.get().plugins)
    +            .searchForPills(isFormatted)
    +            .previewUrlRetriever(callback?.getPreviewUrlRetriever())
    +            .imageContentRenderer(imageContentRenderer)
    +            .previewUrlCallback(callback)
    +            .leftGuideline(avatarSizeProvider.leftGuideline)
    +            .attributes(attributes)
    +            .highlighted(highlight)
    +            .movementMethod(createLinkMovementMethod(callback))
         }
     
    -    private fun annotateWithEdited(linkifiedBody: CharSequence,
    -                                   callback: TimelineEventController.Callback?,
    -                                   informationData: MessageInformationData): Spannable {
    +    private fun annotateWithEdited(
    +        linkifiedBody: CharSequence,
    +        callback: TimelineEventController.Callback?,
    +        informationData: MessageInformationData,
    +    ): Spannable {
             val spannable = SpannableStringBuilder()
             spannable.append(linkifiedBody)
             val editedSuffix = stringProvider.getString(R.string.edited_suffix)
    @@ -591,17 +634,17 @@ class MessageItemFactory @Inject constructor(
             val editStart = spannable.lastIndexOf(editedSuffix)
             val editEnd = editStart + editedSuffix.length
             spannable.setSpan(
    -                ForegroundColorSpan(color),
    -                editStart,
    -                editEnd,
    -                Spanned.SPAN_INCLUSIVE_EXCLUSIVE)
    +            ForegroundColorSpan(color),
    +            editStart,
    +            editEnd,
    +            Spanned.SPAN_INCLUSIVE_EXCLUSIVE)
     
             // Note: text size is set to 14sp
             spannable.setSpan(
    -                AbsoluteSizeSpan(dimensionConverter.spToPx(13)),
    -                editStart,
    -                editEnd,
    -                Spanned.SPAN_INCLUSIVE_EXCLUSIVE)
    +            AbsoluteSizeSpan(dimensionConverter.spToPx(13)),
    +            editStart,
    +            editEnd,
    +            Spanned.SPAN_INCLUSIVE_EXCLUSIVE)
     
             spannable.setSpan(object : ClickableSpan() {
                 override fun onClick(widget: View) {
    @@ -612,18 +655,20 @@ class MessageItemFactory @Inject constructor(
                     // nop
                 }
             },
    -                editStart,
    -                editEnd,
    -                Spanned.SPAN_INCLUSIVE_EXCLUSIVE)
    +            editStart,
    +            editEnd,
    +            Spanned.SPAN_INCLUSIVE_EXCLUSIVE)
             return spannable
         }
     
    -    private fun buildNoticeMessageItem(messageContent: MessageNoticeContent,
    -                                       @Suppress("UNUSED_PARAMETER")
    -                                       informationData: MessageInformationData,
    -                                       highlight: Boolean,
    -                                       callback: TimelineEventController.Callback?,
    -                                       attributes: AbsMessageItem.Attributes): MessageTextItem? {
    +    private fun buildNoticeMessageItem(
    +        messageContent: MessageNoticeContent,
    +        @Suppress("UNUSED_PARAMETER")
    +        informationData: MessageInformationData,
    +        highlight: Boolean,
    +        callback: TimelineEventController.Callback?,
    +        attributes: AbsMessageItem.Attributes,
    +    ): MessageTextItem? {
             val htmlBody = messageContent.getHtmlBody()
             val formattedBody = span {
                 text = htmlBody
    @@ -635,22 +680,24 @@ class MessageItemFactory @Inject constructor(
             val message = formattedBody.linkify(callback)
     
             return MessageTextItem_()
    -                .leftGuideline(avatarSizeProvider.leftGuideline)
    -                .previewUrlRetriever(callback?.getPreviewUrlRetriever())
    -                .imageContentRenderer(imageContentRenderer)
    -                .previewUrlCallback(callback)
    -                .attributes(attributes)
    -                .message(message.toEpoxyCharSequence())
    -                .bindingOptions(bindingOptions)
    -                .highlighted(highlight)
    -                .movementMethod(createLinkMovementMethod(callback))
    +            .leftGuideline(avatarSizeProvider.leftGuideline)
    +            .previewUrlRetriever(callback?.getPreviewUrlRetriever())
    +            .imageContentRenderer(imageContentRenderer)
    +            .previewUrlCallback(callback)
    +            .attributes(attributes)
    +            .message(message.toEpoxyCharSequence())
    +            .bindingOptions(bindingOptions)
    +            .highlighted(highlight)
    +            .movementMethod(createLinkMovementMethod(callback))
         }
     
    -    private fun buildEmoteMessageItem(messageContent: MessageEmoteContent,
    -                                      informationData: MessageInformationData,
    -                                      highlight: Boolean,
    -                                      callback: TimelineEventController.Callback?,
    -                                      attributes: AbsMessageItem.Attributes): MessageTextItem? {
    +    private fun buildEmoteMessageItem(
    +        messageContent: MessageEmoteContent,
    +        informationData: MessageInformationData,
    +        highlight: Boolean,
    +        callback: TimelineEventController.Callback?,
    +        attributes: AbsMessageItem.Attributes,
    +    ): MessageTextItem? {
             val formattedBody = SpannableStringBuilder()
             formattedBody.append("* ${informationData.memberName} ")
             formattedBody.append(messageContent.getHtmlBody())
    @@ -658,46 +705,48 @@ class MessageItemFactory @Inject constructor(
             val message = formattedBody.linkify(callback)
     
             return MessageTextItem_()
    -                .message(
    -                        if (informationData.hasBeenEdited) {
    -                            annotateWithEdited(message, callback, informationData)
    -                        } else {
    -                            message
    -                        }.toEpoxyCharSequence()
    -                )
    -                .bindingOptions(bindingOptions)
    -                .leftGuideline(avatarSizeProvider.leftGuideline)
    -                .previewUrlRetriever(callback?.getPreviewUrlRetriever())
    -                .imageContentRenderer(imageContentRenderer)
    -                .previewUrlCallback(callback)
    -                .attributes(attributes)
    -                .highlighted(highlight)
    -                .movementMethod(createLinkMovementMethod(callback))
    +            .message(
    +                if (informationData.hasBeenEdited) {
    +                    annotateWithEdited(message, callback, informationData)
    +                } else {
    +                    message
    +                }.toEpoxyCharSequence()
    +            )
    +            .bindingOptions(bindingOptions)
    +            .leftGuideline(avatarSizeProvider.leftGuideline)
    +            .previewUrlRetriever(callback?.getPreviewUrlRetriever())
    +            .imageContentRenderer(imageContentRenderer)
    +            .previewUrlCallback(callback)
    +            .attributes(attributes)
    +            .highlighted(highlight)
    +            .movementMethod(createLinkMovementMethod(callback))
         }
     
         private fun MessageContentWithFormattedBody.getHtmlBody(): CharSequence {
             return matrixFormattedBody
    -                ?.let { htmlCompressor.compress(it) }
    -                ?.let { htmlRenderer.get().render(it, pillsPostProcessor) }
    +            ?.let { htmlCompressor.compress(it) }
    +            ?.let { htmlRenderer.get().render(it, pillsPostProcessor) }
                     ?: body
         }
     
    -    private fun buildRedactedItem(attributes: AbsMessageItem.Attributes,
    -                                  highlight: Boolean): RedactedMessageItem? {
    +    private fun buildRedactedItem(
    +        attributes: AbsMessageItem.Attributes,
    +        highlight: Boolean,
    +    ): RedactedMessageItem? {
             return RedactedMessageItem_()
    -                .layout(attributes.informationData.messageLayout.layoutRes)
    -                .leftGuideline(avatarSizeProvider.leftGuideline)
    -                .attributes(attributes)
    -                .highlighted(highlight)
    +            .layout(attributes.informationData.messageLayout.layoutRes)
    +            .leftGuideline(avatarSizeProvider.leftGuideline)
    +            .attributes(attributes)
    +            .highlighted(highlight)
         }
     
         private fun List?.toFft(): List? {
             return this
    -                ?.filterNotNull()
    -                ?.map {
    -                    // Value comes from AudioRecordView.maxReportableAmp, and 1024 is the max value in the Matrix spec
    -                    it * 22760 / 1024
    -                }
    +            ?.filterNotNull()
    +            ?.map {
    +                // Value comes from AudioRecordView.maxReportableAmp, and 1024 is the max value in the Matrix spec
    +                it * 22760 / 1024
    +            }
         }
     
         companion object {
    diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/PollItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/PollItem.kt
    index 0ce225b21c..273dd0299a 100644
    --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/PollItem.kt
    +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/PollItem.kt
    @@ -39,7 +39,7 @@ abstract class PollItem : AbsMessageItem() {
         var eventId: String? = null
     
         @EpoxyAttribute
    -    var pollSent: Boolean = false
    +    var canVote: Boolean = false
     
         @EpoxyAttribute
         var totalVotesText: String? = null
    @@ -80,13 +80,11 @@ abstract class PollItem : AbsMessageItem() {
         private fun onPollItemClick(optionViewState: PollOptionViewState) {
             val relatedEventId = eventId
     
    -        if (isPollActive(optionViewState) && relatedEventId != null) {
    +        if (canVote && relatedEventId != null) {
                 callback?.onTimelineItemAction(RoomDetailAction.VoteToPoll(relatedEventId, optionViewState.optionId))
             }
         }
     
    -    private fun isPollActive(optionViewState: PollOptionViewState) = optionViewState !is PollOptionViewState.PollEnded
    -
         class Holder : AbsMessageItem.Holder(STUB_ID) {
             val questionTextView by bind(R.id.questionTextView)
             val optionsContainer by bind(R.id.optionsContainer)
    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..41add7be7d 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
    @@ -77,7 +77,7 @@ import im.vector.app.features.pin.PinArgs
     import im.vector.app.features.pin.PinMode
     import im.vector.app.features.poll.create.CreatePollActivity
     import im.vector.app.features.poll.create.CreatePollArgs
    -import im.vector.app.features.poll.create.PollMode
    +import im.vector.app.features.poll.PollMode
     import im.vector.app.features.roomdirectory.RoomDirectoryActivity
     import im.vector.app.features.roomdirectory.RoomDirectoryData
     import im.vector.app.features.roomdirectory.createroom.CreateRoomActivity
    diff --git a/vector/src/main/java/im/vector/app/features/navigation/Navigator.kt b/vector/src/main/java/im/vector/app/features/navigation/Navigator.kt
    index a31dc8fb89..85826fad5b 100644
    --- a/vector/src/main/java/im/vector/app/features/navigation/Navigator.kt
    +++ b/vector/src/main/java/im/vector/app/features/navigation/Navigator.kt
    @@ -31,7 +31,7 @@ import im.vector.app.features.location.LocationSharingMode
     import im.vector.app.features.login.LoginConfig
     import im.vector.app.features.media.AttachmentData
     import im.vector.app.features.pin.PinMode
    -import im.vector.app.features.poll.create.PollMode
    +import im.vector.app.features.poll.PollMode
     import im.vector.app.features.roomdirectory.RoomDirectoryData
     import im.vector.app.features.roomdirectory.roompreview.RoomPreviewData
     import im.vector.app.features.settings.VectorSettingsActivity
    diff --git a/vector/src/main/java/im/vector/app/features/poll/create/PollMode.kt b/vector/src/main/java/im/vector/app/features/poll/PollMode.kt
    similarity index 93%
    rename from vector/src/main/java/im/vector/app/features/poll/create/PollMode.kt
    rename to vector/src/main/java/im/vector/app/features/poll/PollMode.kt
    index 0007589d10..47558a34a4 100644
    --- a/vector/src/main/java/im/vector/app/features/poll/create/PollMode.kt
    +++ b/vector/src/main/java/im/vector/app/features/poll/PollMode.kt
    @@ -14,7 +14,7 @@
      * limitations under the License.
      */
     
    -package im.vector.app.features.poll.create
    +package im.vector.app.features.poll
     
     enum class PollMode {
         CREATE,
    diff --git a/vector/src/main/java/im/vector/app/features/poll/PollState.kt b/vector/src/main/java/im/vector/app/features/poll/PollState.kt
    new file mode 100644
    index 0000000000..076fcad388
    --- /dev/null
    +++ b/vector/src/main/java/im/vector/app/features/poll/PollState.kt
    @@ -0,0 +1,27 @@
    +/*
    + * Copyright (c) 2022 New Vector Ltd
    + *
    + * Licensed under the Apache License, Version 2.0 (the "License");
    + * you may not use this file except in compliance with the License.
    + * You may obtain a copy of the License at
    + *
    + *     http://www.apache.org/licenses/LICENSE-2.0
    + *
    + * Unless required by applicable law or agreed to in writing, software
    + * distributed under the License is distributed on an "AS IS" BASIS,
    + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    + * See the License for the specific language governing permissions and
    + * limitations under the License.
    + */
    +
    +package im.vector.app.features.poll
    +
    +sealed class PollState {
    +    object Sending : PollState()
    +    object Ready : PollState()
    +    data class Voted(val votes: Int) : PollState()
    +    object Undisclosed : PollState()
    +    object Ended : PollState()
    +
    +    fun isVotable() = this !is Sending && this !is Ended
    +}
    \ No newline at end of file
    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..c00c1ece0c 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
    @@ -30,6 +30,7 @@ import im.vector.app.core.extensions.configureWith
     import im.vector.app.core.extensions.exhaustive
     import im.vector.app.core.platform.VectorBaseFragment
     import im.vector.app.databinding.FragmentCreatePollBinding
    +import im.vector.app.features.poll.PollMode
     import im.vector.app.features.poll.create.CreatePollViewModel.Companion.MAX_OPTIONS_COUNT
     import kotlinx.parcelize.Parcelize
     import org.matrix.android.sdk.api.session.room.model.message.PollType
    diff --git a/vector/src/main/java/im/vector/app/features/poll/create/CreatePollViewModel.kt b/vector/src/main/java/im/vector/app/features/poll/create/CreatePollViewModel.kt
    index 5c7ef72297..0c822f58fa 100644
    --- a/vector/src/main/java/im/vector/app/features/poll/create/CreatePollViewModel.kt
    +++ b/vector/src/main/java/im/vector/app/features/poll/create/CreatePollViewModel.kt
    @@ -23,6 +23,7 @@ import dagger.assisted.AssistedInject
     import im.vector.app.core.di.MavericksAssistedViewModelFactory
     import im.vector.app.core.di.hiltMavericksViewModelFactory
     import im.vector.app.core.platform.VectorViewModel
    +import im.vector.app.features.poll.PollMode
     import org.matrix.android.sdk.api.session.Session
     import org.matrix.android.sdk.api.session.room.model.message.MessagePollContent
     import org.matrix.android.sdk.api.session.room.model.message.PollType
    diff --git a/vector/src/main/java/im/vector/app/features/poll/create/CreatePollViewState.kt b/vector/src/main/java/im/vector/app/features/poll/create/CreatePollViewState.kt
    index 175d1b0116..75dbb9ba4d 100644
    --- a/vector/src/main/java/im/vector/app/features/poll/create/CreatePollViewState.kt
    +++ b/vector/src/main/java/im/vector/app/features/poll/create/CreatePollViewState.kt
    @@ -17,22 +17,16 @@
     package im.vector.app.features.poll.create
     
     import com.airbnb.mvrx.MavericksState
    +import im.vector.app.features.poll.PollMode
     import org.matrix.android.sdk.api.session.room.model.message.PollType
     
     data class CreatePollViewState(
    -        val roomId: String,
    -        val editedEventId: String?,
    -        val mode: PollMode,
    -        val question: String = "",
    -        val options: List = List(CreatePollViewModel.MIN_OPTIONS_COUNT) { "" },
    -        val canCreatePoll: Boolean = false,
    -        val canAddMoreOptions: Boolean = true,
    -        val pollType: PollType = PollType.DISCLOSED
    -) : MavericksState {
    -
    -    constructor(args: CreatePollArgs) : this(
    -            roomId = args.roomId,
    -            editedEventId = args.editedEventId,
    -            mode = args.mode
    -    )
    -}
    +    val roomId: String,
    +    val editedEventId: String?,
    +    val mode: PollMode,
    +    val question: String = "",
    +    val options: List = List(CreatePollViewModel.MIN_OPTIONS_COUNT) { "" },
    +    val canCreatePoll: Boolean = false,
    +    val canAddMoreOptions: Boolean = true,
    +    val pollType: PollType = PollType.DISCLOSED
    +) : MavericksState
    
    From 86765c9020f02497fa3975c1d91ef9c2e7235187 Mon Sep 17 00:00:00 2001
    From: ericdecanini 
    Date: Sun, 13 Mar 2022 20:22:03 +0100
    Subject: [PATCH 020/262] Adds new line at the end of PollState
    
    ---
     vector/src/main/java/im/vector/app/features/poll/PollState.kt | 2 +-
     1 file changed, 1 insertion(+), 1 deletion(-)
    
    diff --git a/vector/src/main/java/im/vector/app/features/poll/PollState.kt b/vector/src/main/java/im/vector/app/features/poll/PollState.kt
    index 076fcad388..3352c3ad05 100644
    --- a/vector/src/main/java/im/vector/app/features/poll/PollState.kt
    +++ b/vector/src/main/java/im/vector/app/features/poll/PollState.kt
    @@ -24,4 +24,4 @@ sealed class PollState {
         object Ended : PollState()
     
         fun isVotable() = this !is Sending && this !is Ended
    -}
    \ No newline at end of file
    +}
    
    From 06e0047c228b872658df6e8f268f37f66e41b348 Mon Sep 17 00:00:00 2001
    From: ericdecanini 
    Date: Sun, 13 Mar 2022 20:22:44 +0100
    Subject: [PATCH 021/262] Fixes import ordering in DefaultNavigator
    
    ---
     .../java/im/vector/app/features/navigation/DefaultNavigator.kt  | 2 +-
     1 file changed, 1 insertion(+), 1 deletion(-)
    
    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 41add7be7d..1dabdb5bf8 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
    @@ -75,9 +75,9 @@ import im.vector.app.features.onboarding.OnboardingActivity
     import im.vector.app.features.pin.PinActivity
     import im.vector.app.features.pin.PinArgs
     import im.vector.app.features.pin.PinMode
    +import im.vector.app.features.poll.PollMode
     import im.vector.app.features.poll.create.CreatePollActivity
     import im.vector.app.features.poll.create.CreatePollArgs
    -import im.vector.app.features.poll.PollMode
     import im.vector.app.features.roomdirectory.RoomDirectoryActivity
     import im.vector.app.features.roomdirectory.RoomDirectoryData
     import im.vector.app.features.roomdirectory.createroom.CreateRoomActivity
    
    From 84bd205014b3e9d46b4804f693d68af990705c69 Mon Sep 17 00:00:00 2001
    From: NIkita Fedrunov 
    Date: Mon, 14 Mar 2022 09:34:28 +0100
    Subject: [PATCH 022/262] "Add space" copy is replaced with "create space" in
     left sliding panel
    
    ---
     changelog.d/5516.misc                         | 1 +
     vector/src/main/res/layout/item_space_add.xml | 2 +-
     2 files changed, 2 insertions(+), 1 deletion(-)
     create mode 100644 changelog.d/5516.misc
    
    diff --git a/changelog.d/5516.misc b/changelog.d/5516.misc
    new file mode 100644
    index 0000000000..0b925fcdcd
    --- /dev/null
    +++ b/changelog.d/5516.misc
    @@ -0,0 +1 @@
    +"Add space" copy is replaced with "create space" in left sliding panel
    \ No newline at end of file
    diff --git a/vector/src/main/res/layout/item_space_add.xml b/vector/src/main/res/layout/item_space_add.xml
    index e723753adf..a329ac760b 100644
    --- a/vector/src/main/res/layout/item_space_add.xml
    +++ b/vector/src/main/res/layout/item_space_add.xml
    @@ -36,7 +36,7 @@
             android:layout_marginEnd="@dimen/layout_horizontal_margin"
             android:ellipsize="end"
             android:maxLines="1"
    -        android:text="@string/add_space"
    +        android:text="@string/create_space"
             android:textColor="?colorPrimary"
             app:layout_constraintBottom_toBottomOf="parent"
             app:layout_constraintEnd_toEndOf="parent"
    
    From 38f3b88395199317b434ca92f9a4ea59c59258f8 Mon Sep 17 00:00:00 2001
    From: ericdecanini 
    Date: Tue, 15 Mar 2022 15:30:02 +0100
    Subject: [PATCH 023/262] Adds debug test to check roomSummary for voice call
     button
    
    ---
     .../features/home/room/detail/RoomDetailViewState.kt  | 11 ++++++++++-
     1 file changed, 10 insertions(+), 1 deletion(-)
    
    diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewState.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewState.kt
    index e2b97b0900..4b3fdc4c43 100644
    --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewState.kt
    +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewState.kt
    @@ -31,6 +31,7 @@ import org.matrix.android.sdk.api.session.sync.SyncState
     import org.matrix.android.sdk.api.session.threads.ThreadNotificationBadgeState
     import org.matrix.android.sdk.api.session.widgets.model.Widget
     import org.matrix.android.sdk.api.session.widgets.model.WidgetType
    +import timber.log.Timber
     
     sealed class UnreadState {
         object Unknown : UnreadState()
    @@ -87,7 +88,15 @@ data class RoomDetailViewState(
                 rootThreadEventId = args.threadTimelineArgs?.rootThreadEventId
         )
     
    -    fun isWebRTCCallOptionAvailable() = (asyncRoomSummary.invoke()?.joinedMembersCount ?: 0) <= 2
    +    // TODO: Add condition here to check that room is DM-based group chat and not group room
    +    fun isWebRTCCallOptionAvailable() = asyncRoomSummary.invoke()?.let { roomSummary ->
    +        val joinedMembersCount = roomSummary.joinedMembersCount ?: 0
    +        val isDirect = roomSummary.isDirect
    +        Timber.d("WebRTCTest roomSummary: $roomSummary")
    +        Timber.d("WebRTCTest joined members: $joinedMembersCount")
    +        Timber.d("WebRTCTest isDirect: $isDirect")
    +        joinedMembersCount <= 2
    +    }
     
         fun isSearchAvailable() = asyncRoomSummary()?.isEncrypted == false
     
    
    From 98b7d194eb47208a788b682fd714fa81f5c77c60 Mon Sep 17 00:00:00 2001
    From: ericdecanini 
    Date: Tue, 15 Mar 2022 15:54:30 +0100
    Subject: [PATCH 024/262] Adds getBestX following merge from develop
    
    ---
     .../room/detail/timeline/factory/MessageItemFactory.kt   | 9 +++++----
     1 file changed, 5 insertions(+), 4 deletions(-)
    
    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 ad5aeb98d0..3698b45b19 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
    @@ -258,9 +258,10 @@ class MessageItemFactory @Inject constructor(
         ): PollItem {
             val pollResponseSummary = informationData.pollResponseAggregatedSummary
             val pollState = createPollState(informationData, pollResponseSummary, pollContent)
    -        val optionViewStates = pollContent.pollCreationInfo?.answers?.mapToOptions(pollState, informationData)
    -        val questionText = pollContent.pollCreationInfo?.question?.question.orEmpty()
    +        val pollCreationInfo = pollContent.getBestPollCreationInfo()
    +        val questionText = pollCreationInfo?.question?.getBestQuestion().orEmpty()
             val question = createPollQuestion(informationData, questionText, callback)
    +        val optionViewStates = pollCreationInfo?.answers?.mapToOptions(pollState, informationData)
             val totalVotesText = createTotalVotesText(pollState, pollResponseSummary)
     
             return PollItem_()
    @@ -283,7 +284,7 @@ class MessageItemFactory @Inject constructor(
         ): PollState = when {
             !informationData.sendState.isSent() -> Sending
             pollResponseSummary?.isClosed.orFalse() -> Ended
    -        pollContent.pollCreationInfo?.kind == PollType.UNDISCLOSED -> Undisclosed
    +        pollContent.getBestPollCreationInfo()?.kind == PollType.UNDISCLOSED -> Undisclosed
             pollResponseSummary?.myVote?.isNotEmpty().orFalse() -> Voted(pollResponseSummary?.totalVotes ?: 0)
             else -> Ready
         }
    @@ -295,7 +296,7 @@ class MessageItemFactory @Inject constructor(
             val pollResponseSummary = informationData.pollResponseAggregatedSummary
             val winnerVoteCount = pollResponseSummary?.winnerVoteCount
             val optionId = answer.id ?: ""
    -        val optionAnswer = answer.answer ?: ""
    +        val optionAnswer = answer.getBestAnswer() ?: ""
             val voteSummary = pollResponseSummary?.votes?.get(answer.id)
             val voteCount = voteSummary?.total ?: 0
             val votePercentage = voteSummary?.percentage ?: 0.0
    
    From a2d18d460a95c6030d010d345355a885f3d36fa6 Mon Sep 17 00:00:00 2001
    From: ericdecanini 
    Date: Tue, 15 Mar 2022 18:10:05 +0100
    Subject: [PATCH 025/262] Fixes PollMode import error in TimelineFragment
    
    ---
     .../im/vector/app/features/home/room/detail/TimelineFragment.kt | 2 +-
     1 file changed, 1 insertion(+), 1 deletion(-)
    
    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 c638666cd7..0f4c83003a 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
    @@ -182,7 +182,7 @@ import im.vector.app.features.notifications.NotificationDrawerManager
     import im.vector.app.features.notifications.NotificationUtils
     import im.vector.app.features.permalink.NavigationInterceptor
     import im.vector.app.features.permalink.PermalinkHandler
    -import im.vector.app.features.poll.create.PollMode
    +import im.vector.app.features.poll.PollMode
     import im.vector.app.features.reactions.EmojiReactionPickerActivity
     import im.vector.app.features.roomprofile.RoomProfileActivity
     import im.vector.app.features.session.coroutineScope
    
    From a46901ad6c2c939aab9507988da3f4bc80a0354c Mon Sep 17 00:00:00 2001
    From: ericdecanini 
    Date: Tue, 15 Mar 2022 18:10:47 +0100
    Subject: [PATCH 026/262] Makes PollState a sealed interface
    
    ---
     .../java/im/vector/app/features/poll/PollState.kt    | 12 ++++++------
     1 file changed, 6 insertions(+), 6 deletions(-)
    
    diff --git a/vector/src/main/java/im/vector/app/features/poll/PollState.kt b/vector/src/main/java/im/vector/app/features/poll/PollState.kt
    index 3352c3ad05..93cdb0ecbe 100644
    --- a/vector/src/main/java/im/vector/app/features/poll/PollState.kt
    +++ b/vector/src/main/java/im/vector/app/features/poll/PollState.kt
    @@ -16,12 +16,12 @@
     
     package im.vector.app.features.poll
     
    -sealed class PollState {
    -    object Sending : PollState()
    -    object Ready : PollState()
    -    data class Voted(val votes: Int) : PollState()
    -    object Undisclosed : PollState()
    -    object Ended : PollState()
    +sealed interface PollState {
    +    object Sending : PollState
    +    object Ready : PollState
    +    data class Voted(val votes: Int) : PollState
    +    object Undisclosed : PollState
    +    object Ended : PollState
     
         fun isVotable() = this !is Sending && this !is Ended
     }
    
    From d74d569f4b9bdccd837272c7cb8b58e2bcd5b6e3 Mon Sep 17 00:00:00 2001
    From: ericdecanini 
    Date: Tue, 15 Mar 2022 18:15:46 +0100
    Subject: [PATCH 027/262] Adds back default to true on
     isWebRTCCallOptionAvailable
    
    ---
     .../vector/app/features/home/room/detail/RoomDetailViewState.kt | 2 +-
     1 file changed, 1 insertion(+), 1 deletion(-)
    
    diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewState.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewState.kt
    index 4b3fdc4c43..b05cf294cd 100644
    --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewState.kt
    +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewState.kt
    @@ -96,7 +96,7 @@ data class RoomDetailViewState(
             Timber.d("WebRTCTest joined members: $joinedMembersCount")
             Timber.d("WebRTCTest isDirect: $isDirect")
             joinedMembersCount <= 2
    -    }
    +    } ?: true // if room summary cannot be found, assume call option should be available
     
         fun isSearchAvailable() = asyncRoomSummary()?.isEncrypted == false
     
    
    From 82ead4f3f5756fd7adc60405dab9f9e0db011310 Mon Sep 17 00:00:00 2001
    From: ericdecanini 
    Date: Tue, 15 Mar 2022 19:09:19 +0100
    Subject: [PATCH 028/262] Replaces call option condition with isDirect
    
    ---
     .../features/home/room/detail/RoomDetailViewState.kt   | 10 +---------
     1 file changed, 1 insertion(+), 9 deletions(-)
    
    diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewState.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewState.kt
    index b05cf294cd..87eae1bc2d 100644
    --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewState.kt
    +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewState.kt
    @@ -88,15 +88,7 @@ data class RoomDetailViewState(
                 rootThreadEventId = args.threadTimelineArgs?.rootThreadEventId
         )
     
    -    // TODO: Add condition here to check that room is DM-based group chat and not group room
    -    fun isWebRTCCallOptionAvailable() = asyncRoomSummary.invoke()?.let { roomSummary ->
    -        val joinedMembersCount = roomSummary.joinedMembersCount ?: 0
    -        val isDirect = roomSummary.isDirect
    -        Timber.d("WebRTCTest roomSummary: $roomSummary")
    -        Timber.d("WebRTCTest joined members: $joinedMembersCount")
    -        Timber.d("WebRTCTest isDirect: $isDirect")
    -        joinedMembersCount <= 2
    -    } ?: true // if room summary cannot be found, assume call option should be available
    +    fun isWebRTCCallOptionAvailable() = asyncRoomSummary.invoke()?.isDirect ?: true
     
         fun isSearchAvailable() = asyncRoomSummary()?.isEncrypted == false
     
    
    From 5d496d5f3d3ec1302e76827b98e13034805c7c42 Mon Sep 17 00:00:00 2001
    From: ericdecanini 
    Date: Tue, 15 Mar 2022 19:16:04 +0100
    Subject: [PATCH 029/262] Adds changelog file
    
    ---
     changelog.d/5548.bugfix | 1 +
     1 file changed, 1 insertion(+)
     create mode 100644 changelog.d/5548.bugfix
    
    diff --git a/changelog.d/5548.bugfix b/changelog.d/5548.bugfix
    new file mode 100644
    index 0000000000..ccf07a7ee3
    --- /dev/null
    +++ b/changelog.d/5548.bugfix
    @@ -0,0 +1 @@
    +Fixes voice call button disappearing in DM rooms with more than 2 members
    
    From f722b2eb85182ea9dffbaaaf2a735c453d0ad2c8 Mon Sep 17 00:00:00 2001
    From: ericdecanini 
    Date: Tue, 15 Mar 2022 19:16:22 +0100
    Subject: [PATCH 030/262] Removes unused import
    
    ---
     .../vector/app/features/home/room/detail/RoomDetailViewState.kt  | 1 -
     1 file changed, 1 deletion(-)
    
    diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewState.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewState.kt
    index 87eae1bc2d..84e618a8fe 100644
    --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewState.kt
    +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewState.kt
    @@ -31,7 +31,6 @@ import org.matrix.android.sdk.api.session.sync.SyncState
     import org.matrix.android.sdk.api.session.threads.ThreadNotificationBadgeState
     import org.matrix.android.sdk.api.session.widgets.model.Widget
     import org.matrix.android.sdk.api.session.widgets.model.WidgetType
    -import timber.log.Timber
     
     sealed class UnreadState {
         object Unknown : UnreadState()
    
    From d11fc060ee28f36333c1dc2bb27d3d9ade87687b Mon Sep 17 00:00:00 2001
    From: ericdecanini 
    Date: Thu, 17 Mar 2022 13:46:16 +0100
    Subject: [PATCH 031/262] Fixes crash due to empty constructor in
     CreatePollViewState
    
    ---
     .../app/features/poll/create/CreatePollViewState.kt      | 9 ++++++++-
     1 file changed, 8 insertions(+), 1 deletion(-)
    
    diff --git a/vector/src/main/java/im/vector/app/features/poll/create/CreatePollViewState.kt b/vector/src/main/java/im/vector/app/features/poll/create/CreatePollViewState.kt
    index 3045bc695a..3fd8ab605c 100644
    --- a/vector/src/main/java/im/vector/app/features/poll/create/CreatePollViewState.kt
    +++ b/vector/src/main/java/im/vector/app/features/poll/create/CreatePollViewState.kt
    @@ -29,4 +29,11 @@ data class CreatePollViewState(
         val canCreatePoll: Boolean = false,
         val canAddMoreOptions: Boolean = true,
         val pollType: PollType = PollType.DISCLOSED_UNSTABLE
    -) : MavericksState
    +) : MavericksState {
    +
    +    constructor(args: CreatePollArgs) : this(
    +            roomId = args.roomId,
    +            editedEventId = args.editedEventId,
    +            mode = args.mode
    +    )
    +}
    
    From fbb6f117d048757f3b1f83565cde8bd7f7ec0970 Mon Sep 17 00:00:00 2001
    From: ericdecanini 
    Date: Thu, 17 Mar 2022 13:47:57 +0100
    Subject: [PATCH 032/262] Fixes remote echo of end poll not processing
     correctly
    
    ---
     .../EventRelationsAggregationProcessor.kt     | 25 +++++++------------
     1 file changed, 9 insertions(+), 16 deletions(-)
    
    diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EventRelationsAggregationProcessor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EventRelationsAggregationProcessor.kt
    index bf01acaccd..8bbe3a9ac6 100644
    --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EventRelationsAggregationProcessor.kt
    +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EventRelationsAggregationProcessor.kt
    @@ -398,7 +398,7 @@ internal class EventRelationsAggregationProcessor @Inject constructor(
                     }
     
             val closedTime = existingPollSummary?.closedTime
    -        if (closedTime != null) {
    +        if (closedTime != null && eventTimestamp > closedTime) {
                 Timber.v("## POLL is closed ignore event poll:$targetEventId, event :${event.eventId}")
                 return
             }
    @@ -482,46 +482,39 @@ internal class EventRelationsAggregationProcessor @Inject constructor(
                                   roomId: String,
                                   isLocalEcho: Boolean) {
             val pollEventId = content.relatesTo?.eventId ?: return
    -
             val pollOwnerId = getPollEvent(roomId, pollEventId)?.root?.senderId
             val isPollOwner = pollOwnerId == event.senderId
    -
             val powerLevelsHelper = stateEventDataSource.getStateEvent(roomId, EventType.STATE_ROOM_POWER_LEVELS, QueryStringValue.NoCondition)
                     ?.content?.toModel()
                     ?.let { PowerLevelsHelper(it) }
    +
             if (!isPollOwner && !powerLevelsHelper?.isUserAbleToRedact(event.senderId ?: "").orFalse()) {
                 Timber.v("## Received poll.end event $pollEventId but user ${event.senderId} doesn't have enough power level in room $roomId")
                 return
             }
     
    -        var existing = EventAnnotationsSummaryEntity.where(realm, roomId, pollEventId).findFirst()
    -        if (existing == null) {
    +        var existingPoll = EventAnnotationsSummaryEntity.where(realm, roomId, pollEventId).findFirst()
    +        if (existingPoll == null) {
                 Timber.v("## POLL creating new relation summary for $pollEventId")
    -            existing = EventAnnotationsSummaryEntity.create(realm, roomId, pollEventId)
    +            existingPoll = EventAnnotationsSummaryEntity.create(realm, roomId, pollEventId)
             }
     
             // we have it
    -        val existingPollSummary = existing.pollResponseSummary
    +        val existingPollSummary = existingPoll.pollResponseSummary
                     ?: realm.createObject(PollResponseAggregatedSummaryEntity::class.java).also {
    -                    existing.pollResponseSummary = it
    +                    existingPoll.pollResponseSummary = it
                     }
     
    -        if (existingPollSummary.closedTime != null) {
    -            Timber.v("## Received poll.end event for already ended poll $pollEventId")
    -            return
    -        }
    -
             val txId = event.unsignedData?.transactionId
    +        existingPollSummary.closedTime = event.originServerTs
    +
             // is it a remote echo?
             if (!isLocalEcho && existingPollSummary.sourceLocalEchoEvents.contains(txId)) {
                 // ok it has already been managed
                 Timber.v("## POLL  Receiving remote echo of response eventId:$pollEventId")
                 existingPollSummary.sourceLocalEchoEvents.remove(txId)
                 existingPollSummary.sourceEvents.add(event.eventId)
    -            return
             }
    -
    -        existingPollSummary.closedTime = event.originServerTs
         }
     
         private fun getPollEvent(roomId: String, eventId: String): TimelineEvent? {
    
    From a1d27940cdceae5b9a0806b8d7c1db262d6201c4 Mon Sep 17 00:00:00 2001
    From: Onuray Sahin 
    Date: Fri, 18 Mar 2022 15:26:11 +0300
    Subject: [PATCH 033/262] Create a foreground service.
    
    ---
     vector/src/main/AndroidManifest.xml           |  8 +++++-
     .../location/LocationSharingService.kt        | 27 +++++++++++++++++++
     .../app/features/location/LocationTracker.kt  |  2 ++
     3 files changed, 36 insertions(+), 1 deletion(-)
     create mode 100644 vector/src/main/java/im/vector/app/features/location/LocationSharingService.kt
    
    diff --git a/vector/src/main/AndroidManifest.xml b/vector/src/main/AndroidManifest.xml
    index 1d99fba91a..71ea6aaf8f 100644
    --- a/vector/src/main/AndroidManifest.xml
    +++ b/vector/src/main/AndroidManifest.xml
    @@ -46,6 +46,7 @@
         
         
         
    +    
     
         
         
    @@ -84,8 +85,8 @@
             android:resizeableActivity="true"
             android:roundIcon="@mipmap/ic_launcher_round"
             android:supportsRtl="true"
    -        android:theme="@style/Theme.Vector.Light"
             android:taskAffinity="${applicationId}.${appTaskAffinitySuffix}"
    +        android:theme="@style/Theme.Vector.Light"
             tools:replace="android:allowBackup">
     
             
    @@ -369,6 +370,11 @@
                 
             
     
    +        
    +
             
     
             
    Date: Mon, 21 Mar 2022 14:27:15 +0300
    Subject: [PATCH 034/262] Start the foreground service when users start live
     location sharing.
    
    ---
     .../location/LocationSharingFragment.kt       | 20 ++++++++--
     .../location/LocationSharingService.kt        | 37 ++++++++++++++++++-
     .../location/LocationSharingViewEvents.kt     |  1 +
     .../location/LocationSharingViewModel.kt      |  6 ++-
     .../notifications/NotificationUtils.kt        | 12 ++++++
     vector/src/main/res/values/strings.xml        |  2 +
     6 files changed, 71 insertions(+), 7 deletions(-)
    
    diff --git a/vector/src/main/java/im/vector/app/features/location/LocationSharingFragment.kt b/vector/src/main/java/im/vector/app/features/location/LocationSharingFragment.kt
    index c4dccc1b73..15f91d1f47 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
    @@ -16,11 +16,13 @@
     
     package im.vector.app.features.location
     
    +import android.content.Intent
     import android.graphics.drawable.Drawable
     import android.os.Bundle
     import android.view.LayoutInflater
     import android.view.View
     import android.view.ViewGroup
    +import androidx.core.content.ContextCompat
     import androidx.core.view.isGone
     import androidx.lifecycle.lifecycleScope
     import com.airbnb.mvrx.fragmentViewModel
    @@ -83,9 +85,10 @@ class LocationSharingFragment @Inject constructor(
     
             viewModel.observeViewEvents {
                 when (it) {
    -                LocationSharingViewEvents.Close                     -> locationSharingNavigator.quit()
    -                LocationSharingViewEvents.LocationNotAvailableError -> handleLocationNotAvailableError()
    -                is LocationSharingViewEvents.ZoomToUserLocation     -> handleZoomToUserLocationEvent(it)
    +                LocationSharingViewEvents.Close                       -> locationSharingNavigator.quit()
    +                LocationSharingViewEvents.LocationNotAvailableError   -> handleLocationNotAvailableError()
    +                is LocationSharingViewEvents.ZoomToUserLocation       -> handleZoomToUserLocationEvent(it)
    +                is LocationSharingViewEvents.StartLiveLocationService -> handleStartLiveLocationService(it)
                 }.exhaustive
             }
         }
    @@ -177,6 +180,17 @@ class LocationSharingFragment @Inject constructor(
             views.mapView.zoomToLocation(event.userLocation.latitude, event.userLocation.longitude)
         }
     
    +    private fun handleStartLiveLocationService(event: LocationSharingViewEvents.StartLiveLocationService) {
    +        Intent(requireContext(), LocationSharingService::class.java)
    +                .apply {
    +                    putExtra(LocationSharingService.EXTRA_SESSION_ID, event.sessionId)
    +                    putExtra(LocationSharingService.EXTRA_ROOM_ID, event.roomId)
    +                }
    +                .also {
    +                    ContextCompat.startForegroundService(requireContext(), it)
    +                }
    +    }
    +
         private fun initOptionsPicker() {
             // set no option at start
             views.shareLocationOptionsPicker.render()
    diff --git a/vector/src/main/java/im/vector/app/features/location/LocationSharingService.kt b/vector/src/main/java/im/vector/app/features/location/LocationSharingService.kt
    index 4c565ac2bb..e0faebe528 100644
    --- a/vector/src/main/java/im/vector/app/features/location/LocationSharingService.kt
    +++ b/vector/src/main/java/im/vector/app/features/location/LocationSharingService.kt
    @@ -16,12 +16,45 @@
     
     package im.vector.app.features.location
     
    -import android.app.Service
     import android.content.Intent
     import android.os.IBinder
    +import dagger.hilt.android.AndroidEntryPoint
    +import im.vector.app.core.services.VectorService
    +import im.vector.app.features.notifications.NotificationUtils
    +import timber.log.Timber
    +import javax.inject.Inject
    +
    +@AndroidEntryPoint
    +class LocationSharingService : VectorService() {
    +
    +    @Inject lateinit var notificationUtils: NotificationUtils
    +
    +    private var sessionId: String? = null
    +    private var roomId: String? = null
    +
    +    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
    +        sessionId = intent?.getStringExtra(EXTRA_SESSION_ID)
    +        roomId = intent?.getStringExtra(EXTRA_ROOM_ID)
    +
    +        Timber.d("LocationSharingService $sessionId - $roomId")
    +
    +        if (sessionId == null || roomId == null) {
    +            stopForeground(true)
    +            stopSelf()
    +        }
    +
    +        val notification = notificationUtils.buildLiveLocationSharingNotification()
    +        startForeground(roomId!!.hashCode(), notification)
    +
    +        return START_STICKY
    +    }
     
    -class LocationSharingService : Service() {
         override fun onBind(intent: Intent?): IBinder? {
             return null
         }
    +
    +    companion object {
    +        const val EXTRA_SESSION_ID = "EXTRA_SESSION_ID"
    +        const val EXTRA_ROOM_ID = "EXTRA_ROOM_ID"
    +    }
     }
    diff --git a/vector/src/main/java/im/vector/app/features/location/LocationSharingViewEvents.kt b/vector/src/main/java/im/vector/app/features/location/LocationSharingViewEvents.kt
    index 8d31db1119..a7204f02c1 100644
    --- a/vector/src/main/java/im/vector/app/features/location/LocationSharingViewEvents.kt
    +++ b/vector/src/main/java/im/vector/app/features/location/LocationSharingViewEvents.kt
    @@ -22,4 +22,5 @@ sealed class LocationSharingViewEvents : VectorViewEvents {
         object Close : LocationSharingViewEvents()
         object LocationNotAvailableError : LocationSharingViewEvents()
         data class ZoomToUserLocation(val userLocation: LocationData) : LocationSharingViewEvents()
    +    data class StartLiveLocationService(val sessionId: String, val roomId: String) : LocationSharingViewEvents()
     }
    diff --git a/vector/src/main/java/im/vector/app/features/location/LocationSharingViewModel.kt b/vector/src/main/java/im/vector/app/features/location/LocationSharingViewModel.kt
    index 639666e63f..893eee6f70 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
    @@ -160,8 +160,10 @@ class LocationSharingViewModel @AssistedInject constructor(
         }
     
         private fun handleStartLiveLocationSharingAction() {
    -        // TODO start sharing live location and update view state
    -        Timber.d("live location sharing started")
    +        _viewEvents.post(LocationSharingViewEvents.StartLiveLocationService(
    +                sessionId = session.sessionId,
    +                roomId = room.roomId
    +        ))
         }
     
         override fun onLocationUpdate(locationData: LocationData) {
    diff --git a/vector/src/main/java/im/vector/app/features/notifications/NotificationUtils.kt b/vector/src/main/java/im/vector/app/features/notifications/NotificationUtils.kt
    index d39926f620..0366b160ee 100755
    --- a/vector/src/main/java/im/vector/app/features/notifications/NotificationUtils.kt
    +++ b/vector/src/main/java/im/vector/app/features/notifications/NotificationUtils.kt
    @@ -521,6 +521,18 @@ class NotificationUtils @Inject constructor(private val context: Context,
             return builder.build()
         }
     
    +    /**
    +     * Creates a notification that indicates the application is retrieving location even if it is in background or killed.
    +     */
    +    fun buildLiveLocationSharingNotification(): Notification {
    +        return NotificationCompat.Builder(context, SILENT_NOTIFICATION_CHANNEL_ID)
    +                .setContentTitle(stringProvider.getString(R.string.live_location_sharing_notification_title))
    +                .setContentText(stringProvider.getString(R.string.live_location_sharing_notification_description))
    +                .setSmallIcon(R.drawable.ic_location_pin)
    +                .setCategory(NotificationCompat.CATEGORY_LOCATION_SHARING)
    +                .build()
    +    }
    +
         fun buildDownloadFileNotification(uri: Uri, fileName: String, mimeType: String): Notification {
             return NotificationCompat.Builder(context, SILENT_NOTIFICATION_CHANNEL_ID)
                     .setGroup(stringProvider.getString(R.string.app_name))
    diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml
    index 428be3209f..9a30e89948 100644
    --- a/vector/src/main/res/values/strings.xml
    +++ b/vector/src/main/res/values/strings.xml
    @@ -2946,6 +2946,8 @@
         Once enabled you will be able to send your location to any room
         Render user locations in the timeline
         Failed to load map
    +    ${app_name} Live Location
    +    Location sharing is in progress
     
         Show Message bubbles
     
    
    From 0479049476d7a292dbcef2fcf10e7344b9ab6aaf Mon Sep 17 00:00:00 2001
    From: ariskotsomitopoulos 
    Date: Mon, 21 Mar 2022 15:16:57 +0200
    Subject: [PATCH 035/262] Permalink to a root thread message will navigate user
     within the thread timeline
    
    ---
     changelog.d/5567.misc                                    | 1 +
     .../im/vector/app/features/permalink/PermalinkHandler.kt | 9 ++++++++-
     2 files changed, 9 insertions(+), 1 deletion(-)
     create mode 100644 changelog.d/5567.misc
    
    diff --git a/changelog.d/5567.misc b/changelog.d/5567.misc
    new file mode 100644
    index 0000000000..4c0f5e0ffc
    --- /dev/null
    +++ b/changelog.d/5567.misc
    @@ -0,0 +1 @@
    +Permalinks to root thread messages will now navigate you within the thread timeline
    \ No newline at end of file
    diff --git a/vector/src/main/java/im/vector/app/features/permalink/PermalinkHandler.kt b/vector/src/main/java/im/vector/app/features/permalink/PermalinkHandler.kt
    index fa7b5aa7bc..f840e9ea6f 100644
    --- a/vector/src/main/java/im/vector/app/features/permalink/PermalinkHandler.kt
    +++ b/vector/src/main/java/im/vector/app/features/permalink/PermalinkHandler.kt
    @@ -38,6 +38,7 @@ import org.matrix.android.sdk.api.session.permalinks.PermalinkService
     import org.matrix.android.sdk.api.session.room.model.Membership
     import org.matrix.android.sdk.api.session.room.model.RoomSummary
     import org.matrix.android.sdk.api.session.room.model.RoomType
    +import org.matrix.android.sdk.api.session.room.timeline.isRootThread
     import javax.inject.Inject
     
     class PermalinkHandler @Inject constructor(private val activeSessionHolder: ActiveSessionHolder,
    @@ -89,7 +90,13 @@ class PermalinkHandler @Inject constructor(private val activeSessionHolder: Acti
     
                     val rootThreadEventId = permalinkData.eventId?.let { eventId ->
                         val room = roomId?.let { session?.getRoom(it) }
    -                    room?.getTimelineEvent(eventId)?.root?.getRootThreadEventId()
    +
    +                    val rootThreadEventId = room?.getTimelineEvent(eventId)?.root?.getRootThreadEventId()
    +                    rootThreadEventId ?: if (room?.getTimelineEvent(eventId)?.isRootThread() == true) {
    +                        eventId
    +                    } else {
    +                        null
    +                    }
                     }
                     openRoom(
                             navigationInterceptor,
    
    From 334368083e2fc2189c22ce9476f79f00f27b9e88 Mon Sep 17 00:00:00 2001
    From: Onuray Sahin 
    Date: Mon, 21 Mar 2022 16:46:40 +0300
    Subject: [PATCH 036/262] Track location in foreground service.
    
    ---
     .../location/LocationSharingService.kt        | 24 ++++++++++++--
     .../location/LocationSharingViewModel.kt      |  5 +--
     .../app/features/location/LocationTracker.kt  | 32 ++++++++++++++-----
     .../notifications/NotificationUtils.kt        |  2 +-
     4 files changed, 49 insertions(+), 14 deletions(-)
    
    diff --git a/vector/src/main/java/im/vector/app/features/location/LocationSharingService.kt b/vector/src/main/java/im/vector/app/features/location/LocationSharingService.kt
    index e0faebe528..1253891421 100644
    --- a/vector/src/main/java/im/vector/app/features/location/LocationSharingService.kt
    +++ b/vector/src/main/java/im/vector/app/features/location/LocationSharingService.kt
    @@ -25,9 +25,10 @@ import timber.log.Timber
     import javax.inject.Inject
     
     @AndroidEntryPoint
    -class LocationSharingService : VectorService() {
    +class LocationSharingService : VectorService(), LocationTracker.Callback {
     
         @Inject lateinit var notificationUtils: NotificationUtils
    +    @Inject lateinit var locationTracker: LocationTracker
     
         private var sessionId: String? = null
         private var roomId: String? = null
    @@ -36,19 +37,27 @@ class LocationSharingService : VectorService() {
             sessionId = intent?.getStringExtra(EXTRA_SESSION_ID)
             roomId = intent?.getStringExtra(EXTRA_ROOM_ID)
     
    -        Timber.d("LocationSharingService $sessionId - $roomId")
    -
             if (sessionId == null || roomId == null) {
                 stopForeground(true)
                 stopSelf()
             }
     
    +        // Show a sticky notification
             val notification = notificationUtils.buildLiveLocationSharingNotification()
             startForeground(roomId!!.hashCode(), notification)
     
    +        // Start tracking location
    +        locationTracker.addCallback(this)
    +        locationTracker.start()
    +
             return START_STICKY
         }
     
    +    override fun onDestroy() {
    +        super.onDestroy()
    +        locationTracker.removeCallback(this)
    +    }
    +
         override fun onBind(intent: Intent?): IBinder? {
             return null
         }
    @@ -57,4 +66,13 @@ class LocationSharingService : VectorService() {
             const val EXTRA_SESSION_ID = "EXTRA_SESSION_ID"
             const val EXTRA_ROOM_ID = "EXTRA_ROOM_ID"
         }
    +
    +    override fun onLocationUpdate(locationData: LocationData) {
    +        Timber.d("### LocationSharingService.onLocationUpdate: ${locationData.latitude} - ${locationData.longitude}")
    +    }
    +
    +    override fun onLocationProviderIsNotAvailable() {
    +        stopForeground(true)
    +        stopSelf()
    +    }
     }
    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 893eee6f70..b2bf6d1762 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
    @@ -65,7 +65,8 @@ class LocationSharingViewModel @AssistedInject constructor(
         companion object : MavericksViewModelFactory by hiltMavericksViewModelFactory()
     
         init {
    -        locationTracker.start(this)
    +        locationTracker.addCallback(this)
    +        locationTracker.start()
             setUserItem()
             updatePin()
             compareTargetAndUserLocation()
    @@ -112,7 +113,7 @@ class LocationSharingViewModel @AssistedInject constructor(
     
         override fun onCleared() {
             super.onCleared()
    -        locationTracker.stop()
    +        locationTracker.removeCallback(this)
         }
     
         override fun handle(action: LocationSharingAction) {
    diff --git a/vector/src/main/java/im/vector/app/features/location/LocationTracker.kt b/vector/src/main/java/im/vector/app/features/location/LocationTracker.kt
    index 0ee6395871..d5e298a14b 100644
    --- a/vector/src/main/java/im/vector/app/features/location/LocationTracker.kt
    +++ b/vector/src/main/java/im/vector/app/features/location/LocationTracker.kt
    @@ -40,18 +40,17 @@ class LocationTracker @Inject constructor(
             fun onLocationProviderIsNotAvailable()
         }
     
    -    private var callback: Callback? = null
    +    private var callbacks = mutableListOf()
     
         private var hasGpsProviderLiveLocation = false
     
         @RequiresPermission(anyOf = [Manifest.permission.ACCESS_COARSE_LOCATION, Manifest.permission.ACCESS_FINE_LOCATION])
    -    fun start(callback: Callback?) {
    +    fun start() {
             Timber.d("## LocationTracker. start()")
             hasGpsProviderLiveLocation = false
    -        this.callback = callback
     
             if (locationManager == null) {
    -            callback?.onLocationProviderIsNotAvailable()
    +            callbacks.forEach { it.onLocationProviderIsNotAvailable() }
                 Timber.v("## LocationTracker. LocationManager is not available")
                 return
             }
    @@ -81,7 +80,7 @@ class LocationTracker @Inject constructor(
                         )
                     }
                     ?: run {
    -                    callback?.onLocationProviderIsNotAvailable()
    +                    callbacks.forEach { it.onLocationProviderIsNotAvailable() }
                         Timber.v("## LocationTracker. There is no location provider available")
                     }
         }
    @@ -90,7 +89,24 @@ class LocationTracker @Inject constructor(
         fun stop() {
             Timber.d("## LocationTracker. stop()")
             locationManager?.removeUpdates(this)
    -        callback = null
    +        callbacks.clear()
    +    }
    +
    +    fun addCallback(callback: Callback) {
    +        synchronized(callbacks) {
    +            if (!callbacks.contains(callback)) {
    +                callbacks.add(callback)
    +            }
    +        }
    +    }
    +
    +    fun removeCallback(callback: Callback) {
    +        synchronized(callbacks) {
    +            callbacks.remove(callback)
    +            if (callbacks.size == 0) {
    +                stop()
    +            }
    +        }
         }
     
         override fun onLocationChanged(location: Location) {
    @@ -115,12 +131,12 @@ class LocationTracker @Inject constructor(
                     }
                 }
             }
    -        callback?.onLocationUpdate(location.toLocationData())
    +        callbacks.forEach { it.onLocationUpdate(location.toLocationData()) }
         }
     
         override fun onProviderDisabled(provider: String) {
             Timber.d("## LocationTracker. onProviderDisabled: $provider")
    -        callback?.onLocationProviderIsNotAvailable()
    +        callbacks.forEach { it.onLocationProviderIsNotAvailable() }
         }
     
         private fun Location.toLocationData(): LocationData {
    diff --git a/vector/src/main/java/im/vector/app/features/notifications/NotificationUtils.kt b/vector/src/main/java/im/vector/app/features/notifications/NotificationUtils.kt
    index 0366b160ee..9fa094075a 100755
    --- a/vector/src/main/java/im/vector/app/features/notifications/NotificationUtils.kt
    +++ b/vector/src/main/java/im/vector/app/features/notifications/NotificationUtils.kt
    @@ -528,7 +528,7 @@ class NotificationUtils @Inject constructor(private val context: Context,
             return NotificationCompat.Builder(context, SILENT_NOTIFICATION_CHANNEL_ID)
                     .setContentTitle(stringProvider.getString(R.string.live_location_sharing_notification_title))
                     .setContentText(stringProvider.getString(R.string.live_location_sharing_notification_description))
    -                .setSmallIcon(R.drawable.ic_location_pin)
    +                .setSmallIcon(R.drawable.ic_attachment_location_live_white)
                     .setCategory(NotificationCompat.CATEGORY_LOCATION_SHARING)
                     .build()
         }
    
    From 7e5c293ebc547585c3df07bb1612e1b32a867a1f Mon Sep 17 00:00:00 2001
    From: Onuray Sahin 
    Date: Mon, 21 Mar 2022 17:27:14 +0300
    Subject: [PATCH 037/262] Use primary color for location notification item.
    
    ---
     .../im/vector/app/features/notifications/NotificationUtils.kt    | 1 +
     1 file changed, 1 insertion(+)
    
    diff --git a/vector/src/main/java/im/vector/app/features/notifications/NotificationUtils.kt b/vector/src/main/java/im/vector/app/features/notifications/NotificationUtils.kt
    index 9fa094075a..8120134526 100755
    --- a/vector/src/main/java/im/vector/app/features/notifications/NotificationUtils.kt
    +++ b/vector/src/main/java/im/vector/app/features/notifications/NotificationUtils.kt
    @@ -529,6 +529,7 @@ class NotificationUtils @Inject constructor(private val context: Context,
                     .setContentTitle(stringProvider.getString(R.string.live_location_sharing_notification_title))
                     .setContentText(stringProvider.getString(R.string.live_location_sharing_notification_description))
                     .setSmallIcon(R.drawable.ic_attachment_location_live_white)
    +                .setColor(ThemeUtils.getColor(context, android.R.attr.colorPrimary))
                     .setCategory(NotificationCompat.CATEGORY_LOCATION_SHARING)
                     .build()
         }
    
    From 6fd207764dfbb1017de99d7a009aa998d2b5f03c Mon Sep 17 00:00:00 2001
    From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
    Date: Mon, 21 Mar 2022 23:03:30 +0000
    Subject: [PATCH 038/262] Bump actions/cache from 2 to 3
    
    Bumps [actions/cache](https://github.com/actions/cache) from 2 to 3.
    - [Release notes](https://github.com/actions/cache/releases)
    - [Commits](https://github.com/actions/cache/compare/v2...v3)
    
    ---
    updated-dependencies:
    - dependency-name: actions/cache
      dependency-type: direct:production
      update-type: version-update:semver-major
    ...
    
    Signed-off-by: dependabot[bot] 
    ---
     .github/workflows/build.yml   | 4 ++--
     .github/workflows/nightly.yml | 8 ++++----
     .github/workflows/quality.yml | 4 ++--
     .github/workflows/tests.yml   | 4 ++--
     4 files changed, 10 insertions(+), 10 deletions(-)
    
    diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
    index 4ff935fad1..14b5112818 100644
    --- a/.github/workflows/build.yml
    +++ b/.github/workflows/build.yml
    @@ -26,7 +26,7 @@ jobs:
           cancel-in-progress: true
         steps:
           - uses: actions/checkout@v3
    -      - uses: actions/cache@v2
    +      - uses: actions/cache@v3
             with:
               path: |
                 ~/.gradle/caches
    @@ -50,7 +50,7 @@ jobs:
         # Only runs on main, no concurrency.
         steps:
           - uses: actions/checkout@v3
    -      - uses: actions/cache@v2
    +      - uses: actions/cache@v3
             with:
               path: |
                 ~/.gradle/caches
    diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml
    index 455545aeef..9b225ce59b 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
    @@ -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
    @@ -273,7 +273,7 @@ jobs:
             with: 
               distribution: 'adopt'
               java-version: '11'
    -      - uses: actions/cache@v2
    +      - uses: actions/cache@v3
             with:
               path: |
                 ~/.gradle/caches
    @@ -302,7 +302,7 @@ jobs:
             with:
               distribution: 'adopt'
               java-version: '11'
    -      - uses: actions/cache@v2
    +      - uses: actions/cache@v3
             with:
               path: |
                 ~/.gradle/caches
    diff --git a/.github/workflows/quality.yml b/.github/workflows/quality.yml
    index a588b91449..d427d65b7f 100644
    --- a/.github/workflows/quality.yml
    +++ b/.github/workflows/quality.yml
    @@ -97,7 +97,7 @@ jobs:
           cancel-in-progress: true
         steps:
           - uses: actions/checkout@v3
    -      - uses: actions/cache@v2
    +      - uses: actions/cache@v3
             with:
               path: |
                 ~/.gradle/caches
    @@ -130,7 +130,7 @@ jobs:
           cancel-in-progress: true
         steps:
           - uses: actions/checkout@v3
    -      - uses: actions/cache@v2
    +      - uses: actions/cache@v3
             with:
               path: |
                 ~/.gradle/caches
    diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
    index 587bf14488..98e5f588ca 100644
    --- a/.github/workflows/tests.yml
    +++ b/.github/workflows/tests.yml
    @@ -25,7 +25,7 @@ jobs:
             with: 
               distribution: 'adopt' 
               java-version: 11 
    -      - uses: actions/cache@v2 
    +      - uses: actions/cache@v3 
             with: 
               path: | 
                 ~/.gradle/caches 
    @@ -45,7 +45,7 @@ jobs:
           cancel-in-progress: true
         steps:
           - uses: actions/checkout@v3
    -      - uses: actions/cache@v2
    +      - uses: actions/cache@v3
             with:
               path: |
                 ~/.gradle/caches
    
    From 13d0b2366356ac10d90ffe16ce24c7a861da0b01 Mon Sep 17 00:00:00 2001
    From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
    Date: Mon, 21 Mar 2022 23:03:32 +0000
    Subject: [PATCH 039/262] Bump michaelkaye/setup-matrix-synapse from 0.3.0 to
     0.4.0
    
    Bumps [michaelkaye/setup-matrix-synapse](https://github.com/michaelkaye/setup-matrix-synapse) from 0.3.0 to 0.4.0.
    - [Release notes](https://github.com/michaelkaye/setup-matrix-synapse/releases)
    - [Commits](https://github.com/michaelkaye/setup-matrix-synapse/compare/v0.3.0...v0.4.0)
    
    ---
    updated-dependencies:
    - dependency-name: michaelkaye/setup-matrix-synapse
      dependency-type: direct:production
      update-type: version-update:semver-minor
    ...
    
    Signed-off-by: dependabot[bot] 
    ---
     .github/workflows/nightly.yml | 4 ++--
     1 file changed, 2 insertions(+), 2 deletions(-)
    
    diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml
    index 455545aeef..4a1ea84107 100644
    --- a/.github/workflows/nightly.yml
    +++ b/.github/workflows/nightly.yml
    @@ -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
    @@ -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
    
    From 52699357dd709b7d89256ab254d71f5b52519ced Mon Sep 17 00:00:00 2001
    From: ariskotsomitopoulos 
    Date: Tue, 22 Mar 2022 12:27:11 +0200
    Subject: [PATCH 040/262] Change text constant
    
    ---
     vector/src/main/res/values/strings.xml | 2 +-
     1 file changed, 1 insertion(+), 1 deletion(-)
    
    diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml
    index aed977d687..6e5a2be47c 100644
    --- a/vector/src/main/res/values/strings.xml
    +++ b/vector/src/main/res/values/strings.xml
    @@ -1546,7 +1546,7 @@
     
         Edit
         Reply
    -    Reply In Thread
    +    Reply in thread
         View In Room
     
         Retry
    
    From 3c6dbd08437abc4e7cb0e896e6ae28bebf9847ad Mon Sep 17 00:00:00 2001
    From: ariskotsomitopoulos 
    Date: Tue, 22 Mar 2022 12:55:50 +0200
    Subject: [PATCH 041/262] Reduce timeline menu thread icon padding
    
    ---
     library/ui-styles/src/main/res/values/dimens.xml              | 1 +
     vector/src/main/res/layout/view_thread_notification_badge.xml | 2 +-
     2 files changed, 2 insertions(+), 1 deletion(-)
    
    diff --git a/library/ui-styles/src/main/res/values/dimens.xml b/library/ui-styles/src/main/res/values/dimens.xml
    index 600c73c878..8dc4edc07d 100644
    --- a/library/ui-styles/src/main/res/values/dimens.xml
    +++ b/library/ui-styles/src/main/res/values/dimens.xml
    @@ -40,6 +40,7 @@
         24dp
         48dp
         48dp
    +    38dp
     
         
         56dp
    diff --git a/vector/src/main/res/layout/view_thread_notification_badge.xml b/vector/src/main/res/layout/view_thread_notification_badge.xml
    index 81b3f7138e..00ba45ad08 100644
    --- a/vector/src/main/res/layout/view_thread_notification_badge.xml
    +++ b/vector/src/main/res/layout/view_thread_notification_badge.xml
    @@ -2,7 +2,7 @@
     
     
         
    Date: Tue, 22 Mar 2022 13:48:07 +0200
    Subject: [PATCH 042/262] Increase thread summary icon size
    
    ---
     vector/src/main/res/layout/view_thread_room_summary.xml | 4 ++--
     1 file changed, 2 insertions(+), 2 deletions(-)
    
    diff --git a/vector/src/main/res/layout/view_thread_room_summary.xml b/vector/src/main/res/layout/view_thread_room_summary.xml
    index 0f184edef3..6eeb62974d 100644
    --- a/vector/src/main/res/layout/view_thread_room_summary.xml
    +++ b/vector/src/main/res/layout/view_thread_room_summary.xml
    @@ -6,8 +6,8 @@
     
         
    Date: Tue, 22 Mar 2022 16:01:51 +0300
    Subject: [PATCH 043/262] Support sharing live location in multiple rooms.
    
    ---
     .../location/LocationSharingAction.kt         |  2 +-
     .../location/LocationSharingFragment.kt       |  8 +-
     .../location/LocationSharingService.kt        | 78 ++++++++++++++-----
     .../location/LocationSharingViewEvents.kt     |  2 +-
     .../location/LocationSharingViewModel.kt      | 15 ++--
     5 files changed, 75 insertions(+), 30 deletions(-)
    
    diff --git a/vector/src/main/java/im/vector/app/features/location/LocationSharingAction.kt b/vector/src/main/java/im/vector/app/features/location/LocationSharingAction.kt
    index d7d686ee60..4025fbefa8 100644
    --- a/vector/src/main/java/im/vector/app/features/location/LocationSharingAction.kt
    +++ b/vector/src/main/java/im/vector/app/features/location/LocationSharingAction.kt
    @@ -23,5 +23,5 @@ sealed class LocationSharingAction : VectorViewModelAction {
         data class PinnedLocationSharing(val locationData: LocationData?) : LocationSharingAction()
         data class LocationTargetChange(val locationData: LocationData) : LocationSharingAction()
         object ZoomToUserLocation : LocationSharingAction()
    -    object StartLiveLocationSharing : LocationSharingAction()
    +    data class StartLiveLocationSharing(val duration: Long) : LocationSharingAction()
     }
    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 15f91d1f47..84efff0e0d 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
    @@ -183,8 +183,8 @@ class LocationSharingFragment @Inject constructor(
         private fun handleStartLiveLocationService(event: LocationSharingViewEvents.StartLiveLocationService) {
             Intent(requireContext(), LocationSharingService::class.java)
                     .apply {
    -                    putExtra(LocationSharingService.EXTRA_SESSION_ID, event.sessionId)
    -                    putExtra(LocationSharingService.EXTRA_ROOM_ID, event.roomId)
    +                    putExtra(LocationSharingService.EXTRA_ROOM_ARGS,
    +                            LocationSharingService.RoomArgs(event.sessionId, event.roomId, event.duration))
                     }
                     .also {
                         ContextCompat.startForegroundService(requireContext(), it)
    @@ -236,7 +236,9 @@ class LocationSharingFragment @Inject constructor(
         }
     
         private fun startLiveLocationSharing() {
    -        viewModel.handle(LocationSharingAction.StartLiveLocationSharing)
    +        // TODO. Get duration from user
    +        val duration = 30 * 1000L
    +        viewModel.handle(LocationSharingAction.StartLiveLocationSharing(duration))
         }
     
         private fun updateMap(state: LocationSharingViewState) {
    diff --git a/vector/src/main/java/im/vector/app/features/location/LocationSharingService.kt b/vector/src/main/java/im/vector/app/features/location/LocationSharingService.kt
    index 1253891421..183d3ee2ab 100644
    --- a/vector/src/main/java/im/vector/app/features/location/LocationSharingService.kt
    +++ b/vector/src/main/java/im/vector/app/features/location/LocationSharingService.kt
    @@ -18,44 +18,87 @@ package im.vector.app.features.location
     
     import android.content.Intent
     import android.os.IBinder
    +import android.os.Parcelable
     import dagger.hilt.android.AndroidEntryPoint
     import im.vector.app.core.services.VectorService
     import im.vector.app.features.notifications.NotificationUtils
    +import kotlinx.parcelize.Parcelize
     import timber.log.Timber
    +import java.util.Timer
    +import java.util.TimerTask
     import javax.inject.Inject
     
     @AndroidEntryPoint
     class LocationSharingService : VectorService(), LocationTracker.Callback {
     
    +    @Parcelize
    +    data class RoomArgs(
    +            val sessionId: String,
    +            val roomId: String,
    +            val durationMillis: Long
    +    ) : Parcelable
    +
         @Inject lateinit var notificationUtils: NotificationUtils
         @Inject lateinit var locationTracker: LocationTracker
     
    -    private var sessionId: String? = null
    -    private var roomId: String? = null
    +    private var roomArgsList = mutableListOf()
     
    -    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
    -        sessionId = intent?.getStringExtra(EXTRA_SESSION_ID)
    -        roomId = intent?.getStringExtra(EXTRA_ROOM_ID)
    -
    -        if (sessionId == null || roomId == null) {
    -            stopForeground(true)
    -            stopSelf()
    -        }
    -
    -        // Show a sticky notification
    -        val notification = notificationUtils.buildLiveLocationSharingNotification()
    -        startForeground(roomId!!.hashCode(), notification)
    +    override fun onCreate() {
    +        super.onCreate()
    +        Timber.d("### LocationSharingService.onCreate")
     
             // Start tracking location
             locationTracker.addCallback(this)
             locationTracker.start()
    +    }
    +
    +    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
    +        val roomArgs = intent?.getParcelableExtra(EXTRA_ROOM_ARGS) as? RoomArgs
    +
    +        Timber.d("### LocationSharingService.onStartCommand. sessionId - roomId ${roomArgs?.sessionId} - ${roomArgs?.roomId}")
    +
    +        if (roomArgs != null) {
    +            roomArgsList.add(roomArgs)
    +
    +            // Show a sticky notification
    +            val notification = notificationUtils.buildLiveLocationSharingNotification()
    +            startForeground(roomArgs.roomId.hashCode(), notification)
    +
    +            // Schedule a timer to stop sharing
    +            scheduleTimer(roomArgs.roomId, roomArgs.durationMillis)
    +        }
     
             return START_STICKY
         }
     
    +    private fun scheduleTimer(roomId: String, durationMillis: Long) {
    +        Timer().schedule(object : TimerTask() {
    +            override fun run() {
    +                stopSharingLocation(roomId)
    +            }
    +        }, durationMillis)
    +    }
    +
    +    private fun stopSharingLocation(roomId: String) {
    +        Timber.d("### LocationSharingService.stopSharingLocation for $roomId")
    +        synchronized(roomArgsList) {
    +            roomArgsList.removeAll { it.roomId == roomId }
    +            if (roomArgsList.isEmpty()) {
    +                Timber.d("### LocationSharingService. Destroying self, time is up for all rooms")
    +                destroyMe()
    +            }
    +        }
    +    }
    +
    +    private fun destroyMe() {
    +        locationTracker.removeCallback(this)
    +        stopSelf()
    +    }
    +
         override fun onDestroy() {
             super.onDestroy()
    -        locationTracker.removeCallback(this)
    +        Timber.d("### LocationSharingService.onDestroy")
    +        destroyMe()
         }
     
         override fun onBind(intent: Intent?): IBinder? {
    @@ -63,12 +106,11 @@ class LocationSharingService : VectorService(), LocationTracker.Callback {
         }
     
         companion object {
    -        const val EXTRA_SESSION_ID = "EXTRA_SESSION_ID"
    -        const val EXTRA_ROOM_ID = "EXTRA_ROOM_ID"
    +        const val EXTRA_ROOM_ARGS = "EXTRA_ROOM_ARGS"
         }
     
         override fun onLocationUpdate(locationData: LocationData) {
    -        Timber.d("### LocationSharingService.onLocationUpdate: ${locationData.latitude} - ${locationData.longitude}")
    +        Timber.d("### LocationSharingService.onLocationUpdate. Lat - lon: ${locationData.latitude} - ${locationData.longitude}")
         }
     
         override fun onLocationProviderIsNotAvailable() {
    diff --git a/vector/src/main/java/im/vector/app/features/location/LocationSharingViewEvents.kt b/vector/src/main/java/im/vector/app/features/location/LocationSharingViewEvents.kt
    index a7204f02c1..b25a4988b0 100644
    --- a/vector/src/main/java/im/vector/app/features/location/LocationSharingViewEvents.kt
    +++ b/vector/src/main/java/im/vector/app/features/location/LocationSharingViewEvents.kt
    @@ -22,5 +22,5 @@ sealed class LocationSharingViewEvents : VectorViewEvents {
         object Close : LocationSharingViewEvents()
         object LocationNotAvailableError : LocationSharingViewEvents()
         data class ZoomToUserLocation(val userLocation: LocationData) : LocationSharingViewEvents()
    -    data class StartLiveLocationService(val sessionId: String, val roomId: String) : LocationSharingViewEvents()
    +    data class StartLiveLocationService(val sessionId: String, val roomId: String, val duration: Long) : LocationSharingViewEvents()
     }
    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 b2bf6d1762..8293ac6143 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
    @@ -118,11 +118,11 @@ class LocationSharingViewModel @AssistedInject constructor(
     
         override fun handle(action: LocationSharingAction) {
             when (action) {
    -            LocationSharingAction.CurrentUserLocationSharing -> handleCurrentUserLocationSharingAction()
    -            is LocationSharingAction.PinnedLocationSharing   -> handlePinnedLocationSharingAction(action)
    -            is LocationSharingAction.LocationTargetChange    -> handleLocationTargetChangeAction(action)
    -            LocationSharingAction.ZoomToUserLocation         -> handleZoomToUserLocationAction()
    -            LocationSharingAction.StartLiveLocationSharing   -> handleStartLiveLocationSharingAction()
    +            LocationSharingAction.CurrentUserLocationSharing  -> handleCurrentUserLocationSharingAction()
    +            is LocationSharingAction.PinnedLocationSharing    -> handlePinnedLocationSharingAction(action)
    +            is LocationSharingAction.LocationTargetChange     -> handleLocationTargetChangeAction(action)
    +            LocationSharingAction.ZoomToUserLocation          -> handleZoomToUserLocationAction()
    +            is LocationSharingAction.StartLiveLocationSharing -> handleStartLiveLocationSharingAction(action.duration)
             }.exhaustive
         }
     
    @@ -160,10 +160,11 @@ class LocationSharingViewModel @AssistedInject constructor(
             }
         }
     
    -    private fun handleStartLiveLocationSharingAction() {
    +    private fun handleStartLiveLocationSharingAction(duration: Long) {
             _viewEvents.post(LocationSharingViewEvents.StartLiveLocationService(
                     sessionId = session.sessionId,
    -                roomId = room.roomId
    +                roomId = room.roomId,
    +                duration = duration
             ))
         }
     
    
    From c4559129693b0e56d6cf3415e73abe64adaeae58 Mon Sep 17 00:00:00 2001
    From: ariskotsomitopoulos 
    Date: Tue, 22 Mar 2022 16:03:49 +0200
    Subject: [PATCH 044/262] Fix timeline thread summary padding
    
    ---
     vector/src/main/res/layout/item_timeline_event_base.xml | 4 ++--
     1 file changed, 2 insertions(+), 2 deletions(-)
    
    diff --git a/vector/src/main/res/layout/item_timeline_event_base.xml b/vector/src/main/res/layout/item_timeline_event_base.xml
    index bc02728f6e..7b491b2dfa 100644
    --- a/vector/src/main/res/layout/item_timeline_event_base.xml
    +++ b/vector/src/main/res/layout/item_timeline_event_base.xml
    @@ -154,8 +154,8 @@
             android:layout_below="@id/informationBottom"
             android:layout_marginEnd="32dp"
             android:layout_marginBottom="4dp"
    -        android:paddingStart="13dp"
    -        android:paddingEnd="13dp"
    +        android:paddingStart="12dp"
    +        android:paddingEnd="12dp"
             android:layout_toEndOf="@id/messageStartGuideline"
             android:background="@drawable/rounded_rect_shape_8"
             android:contentDescription="@string/room_threads_filter"
    
    From 24bdad3ae158cba4be1d4d39023ba87459d7f666 Mon Sep 17 00:00:00 2001
    From: Onuray Sahin 
    Date: Tue, 22 Mar 2022 17:04:35 +0300
    Subject: [PATCH 045/262] Code review fixes.
    
    ---
     .../detail/timeline/item/MessageVoiceItem.kt  | 74 ++++++++++---------
     1 file changed, 38 insertions(+), 36 deletions(-)
    
    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 722e0f620a..bbaab3959d 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
    @@ -24,6 +24,7 @@ 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
    @@ -85,31 +86,8 @@ abstract class MessageVoiceItem : AbsMessageItem() {
                 holder.progressLayout.isVisible = false
             }
     
    -        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.post {
    -            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
    -            }
    +        holder.voicePlaybackWaveform.doOnLayout {
    +            onWaveformViewReady(holder)
             }
     
             val backgroundTint = if (attributes.informationData.messageLayout is TimelineMessageLayout.Bubble) {
    @@ -119,19 +97,43 @@ abstract class MessageVoiceItem : AbsMessageItem() {
             }
             holder.voicePlaybackLayout.backgroundTintList = ColorStateList.valueOf(backgroundTint)
             holder.voicePlaybackControlButton.setOnClickListener { playbackControlButtonClickListener?.invoke(it) }
    +    }
     
    -        // Don't track and don't try to update UI before view is present
    -        holder.view.post {
    -            voiceMessagePlaybackTracker.track(attributes.informationData.eventId, object : VoiceMessagePlaybackTracker.Listener {
    -                override fun onUpdate(state: VoiceMessagePlaybackTracker.Listener.State) {
    -                    when (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)
    -                    }
    -                }
    -            })
    +    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, waveformColorIdle, waveformColorPlayed)
    +                    is VoiceMessagePlaybackTracker.Listener.State.Playing -> renderPlayingState(holder, state, waveformColorIdle, waveformColorPlayed)
    +                    is VoiceMessagePlaybackTracker.Listener.State.Paused  -> renderPausedState(holder, state, waveformColorIdle, waveformColorPlayed)
    +                }
    +            }
    +        })
         }
     
         private fun getTouchedPositionPercentage(motionEvent: MotionEvent, view: View) = (motionEvent.x / view.width).coerceIn(0f, 1f)
    
    From 1e3b859f48940a9251b9c21f115dec2895d481ff Mon Sep 17 00:00:00 2001
    From: ariskotsomitopoulos 
    Date: Tue, 22 Mar 2022 16:19:15 +0200
    Subject: [PATCH 046/262] Fix timeline thread summary width
    
    ---
     vector/src/main/res/layout/item_timeline_event_base.xml | 8 ++++----
     1 file changed, 4 insertions(+), 4 deletions(-)
    
    diff --git a/vector/src/main/res/layout/item_timeline_event_base.xml b/vector/src/main/res/layout/item_timeline_event_base.xml
    index 7b491b2dfa..722928a874 100644
    --- a/vector/src/main/res/layout/item_timeline_event_base.xml
    +++ b/vector/src/main/res/layout/item_timeline_event_base.xml
    @@ -34,11 +34,11 @@
             android:layout_marginStart="8dp"
             android:layout_marginTop="4dp"
             android:layout_marginEnd="4dp"
    -        android:textAlignment="viewStart"
             android:layout_toStartOf="@id/messageTimeView"
             android:layout_toEndOf="@id/messageStartGuideline"
             android:ellipsize="end"
             android:maxLines="1"
    +        android:textAlignment="viewStart"
             android:textColor="?vctr_content_primary"
             android:textStyle="bold"
             tools:text="@sample/users.json/data/displayName" />
    @@ -152,16 +152,16 @@
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
             android:layout_below="@id/informationBottom"
    -        android:layout_marginEnd="32dp"
    +        android:layout_marginEnd="40dp"
             android:layout_marginBottom="4dp"
    -        android:paddingStart="12dp"
    -        android:paddingEnd="12dp"
             android:layout_toEndOf="@id/messageStartGuideline"
             android:background="@drawable/rounded_rect_shape_8"
             android:contentDescription="@string/room_threads_filter"
             android:maxWidth="496dp"
             android:minWidth="144dp"
    +        android:paddingStart="12dp"
             android:paddingTop="8dp"
    +        android:paddingEnd="12dp"
             android:paddingBottom="8dp"
             android:visibility="gone"
             tools:visibility="visible">
    
    From bc9a785a591fe04da4ea8615b2a3524cf84e5f5d Mon Sep 17 00:00:00 2001
    From: ariskotsomitopoulos 
    Date: Tue, 22 Mar 2022 17:08:49 +0200
    Subject: [PATCH 047/262] Match timeline thread summary width with the actual
     text
    
    ---
     vector/src/main/res/layout/item_timeline_event_base.xml | 4 +++-
     1 file changed, 3 insertions(+), 1 deletion(-)
    
    diff --git a/vector/src/main/res/layout/item_timeline_event_base.xml b/vector/src/main/res/layout/item_timeline_event_base.xml
    index 722928a874..182e9c04a4 100644
    --- a/vector/src/main/res/layout/item_timeline_event_base.xml
    +++ b/vector/src/main/res/layout/item_timeline_event_base.xml
    @@ -152,8 +152,10 @@
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
             android:layout_below="@id/informationBottom"
    -        android:layout_marginEnd="40dp"
    +        android:layout_marginEnd="48dp"
             android:layout_marginBottom="4dp"
    +        android:layout_marginTop="4dp"
    +        android:layout_marginStart="8dp"
             android:layout_toEndOf="@id/messageStartGuideline"
             android:background="@drawable/rounded_rect_shape_8"
             android:contentDescription="@string/room_threads_filter"
    
    From 6f55a25a30c9f24ee130f290052822ae9334729b Mon Sep 17 00:00:00 2001
    From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
    Date: Tue, 16 Nov 2021 12:33:23 +0000
    Subject: [PATCH 048/262] Bump kotlin-gradle-plugin from 1.5.31 to 1.6.0
    
    Bumps [kotlin-gradle-plugin](https://github.com/JetBrains/kotlin) from 1.5.31 to 1.6.0.
    - [Release notes](https://github.com/JetBrains/kotlin/releases)
    - [Changelog](https://github.com/JetBrains/kotlin/blob/master/ChangeLog.md)
    - [Commits](https://github.com/JetBrains/kotlin/commits)
    
    ---
    updated-dependencies:
    - dependency-name: org.jetbrains.kotlin:kotlin-gradle-plugin
      dependency-type: direct:production
      update-type: version-update:semver-minor
    ...
    
    Signed-off-by: dependabot[bot] 
    ---
     dependencies.gradle | 2 +-
     1 file changed, 1 insertion(+), 1 deletion(-)
    
    diff --git a/dependencies.gradle b/dependencies.gradle
    index 1f2a08b6a6..4f06fbfa86 100644
    --- a/dependencies.gradle
    +++ b/dependencies.gradle
    @@ -9,7 +9,7 @@ ext.versions = [
     
     def gradle = "7.0.4"
     // Ref: https://kotlinlang.org/releases.html
    -def kotlin = "1.5.31"
    +def kotlin = "1.6.0"
     def kotlinCoroutines = "1.5.2"
     def dagger = "2.40.5"
     def retrofit = "2.9.0"
    
    From e169c81a2cfc77604da8760c7593117c61f163e1 Mon Sep 17 00:00:00 2001
    From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
    Date: Thu, 9 Dec 2021 23:17:20 +0000
    Subject: [PATCH 049/262] Bump moshi from 1.12.0 to 1.13.0
    
    Bumps `moshi` from 1.12.0 to 1.13.0.
    
    Updates `moshi-adapters` from 1.12.0 to 1.13.0
    - [Release notes](https://github.com/square/moshi/releases)
    - [Changelog](https://github.com/square/moshi/blob/master/CHANGELOG.md)
    - [Commits](https://github.com/square/moshi/compare/moshi-parent-1.12.0...moshi-parent-1.13.0)
    
    Updates `moshi-kotlin-codegen` from 1.12.0 to 1.13.0
    - [Release notes](https://github.com/square/moshi/releases)
    - [Changelog](https://github.com/square/moshi/blob/master/CHANGELOG.md)
    - [Commits](https://github.com/square/moshi/compare/moshi-parent-1.12.0...moshi-parent-1.13.0)
    
    ---
    updated-dependencies:
    - dependency-name: com.squareup.moshi:moshi-adapters
      dependency-type: direct:production
      update-type: version-update:semver-minor
    - dependency-name: com.squareup.moshi:moshi-kotlin-codegen
      dependency-type: direct:production
      update-type: version-update:semver-minor
    ...
    
    Signed-off-by: dependabot[bot] 
    ---
     dependencies.gradle | 2 +-
     1 file changed, 1 insertion(+), 1 deletion(-)
    
    diff --git a/dependencies.gradle b/dependencies.gradle
    index 4f06fbfa86..c0b9726f0f 100644
    --- a/dependencies.gradle
    +++ b/dependencies.gradle
    @@ -15,7 +15,7 @@ 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"
    
    From f07c9bf105509cc9eef6fbfa35245f5892f2c14b Mon Sep 17 00:00:00 2001
    From: Benoit Marty 
    Date: Tue, 22 Mar 2022 14:40:53 +0100
    Subject: [PATCH 050/262] Fix compilation warning (exhaustive when)
    
    ---
     .../billcarsonfr/jsonviewer/JSonViewerEpoxyController.kt   | 7 ++-----
     1 file changed, 2 insertions(+), 5 deletions(-)
    
    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..e3c4004fb5 100644
    --- a/library/jsonviewer/src/main/java/org/billcarsonfr/jsonviewer/JSonViewerEpoxyController.kt
    +++ b/library/jsonviewer/src/main/java/org/billcarsonfr/jsonviewer/JSonViewerEpoxyController.kt
    @@ -20,7 +20,6 @@ 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
    @@ -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, "")
                     }
                 }
    
    From ed62a2f1c98a4e5f58a56d0be56d3ba5c67c79bf Mon Sep 17 00:00:00 2001
    From: Benoit Marty 
    Date: Tue, 22 Mar 2022 14:42:05 +0100
    Subject: [PATCH 051/262] Format file (no other change)
    
    ---
     .../jsonviewer/JSonViewerEpoxyController.kt   | 182 +++++++++---------
     1 file changed, 91 insertions(+), 91 deletions(-)
    
    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 e3c4004fb5..9f8093f801 100644
    --- a/library/jsonviewer/src/main/java/org/billcarsonfr/jsonviewer/JSonViewerEpoxyController.kt
    +++ b/library/jsonviewer/src/main/java/org/billcarsonfr/jsonviewer/JSonViewerEpoxyController.kt
    @@ -25,7 +25,7 @@ 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)
     
    @@ -52,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}}"
    @@ -71,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 {
    @@ -110,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}\"") {
    @@ -119,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
    @@ -127,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)
                     }
    @@ -172,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
                     }
    @@ -187,7 +187,7 @@ internal class JSonViewerEpoxyController(private val context: Context) :
                         textColor = host.styleProvider.booleanColor
                     }
                 }
    -            JSONType.NULL -> {
    +            JSONType.NULL    -> {
                     span("null") {
                         textColor = host.styleProvider.booleanColor
                     }
    @@ -196,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) })
             }
    @@ -248,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()
                 )
             }
         }
    
    From 7502158ba90832f9b0c1861d57e8281c03da7f06 Mon Sep 17 00:00:00 2001
    From: Benoit Marty 
    Date: Tue, 22 Mar 2022 14:45:01 +0100
    Subject: [PATCH 052/262] Fix compilation warning (exhaustive when)
    
    ---
     .../verification/qrcode/DefaultQrCodeVerificationTransaction.kt  | 1 +
     .../sdk/internal/session/room/summary/RoomSummaryDataSource.kt   | 1 +
     2 files changed, 2 insertions(+)
    
    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..8bfbcfaef4 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
    @@ -272,6 +272,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/session/room/summary/RoomSummaryDataSource.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryDataSource.kt
    index ea4f102fa5..aa78bb8afc 100644
    --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryDataSource.kt
    +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryDataSource.kt
    @@ -314,6 +314,7 @@ internal class RoomSummaryDataSource @Inject constructor(
                 RoomCategoryFilter.ONLY_ROOMS              -> 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}")
    
    From 57bee40211bca7aa31be692307646c0e7e4ffa12 Mon Sep 17 00:00:00 2001
    From: Benoit Marty 
    Date: Tue, 22 Mar 2022 15:18:58 +0100
    Subject: [PATCH 053/262] Fix compilation warning (exhaustive when)
    
    ---
     .../app/core/preference/PushRulePreference.kt |  1 +
     .../app/core/ui/views/KeysBackupBanner.kt     |  9 +++------
     .../core/ui/views/PresenceStateImageView.kt   |  1 +
     .../app/core/ui/views/ShieldImageView.kt      | 13 +++++++------
     .../app/features/call/CallControlsView.kt     |  1 +
     .../app/features/call/VectorCallActivity.kt   |  3 +--
     .../createdirect/CreateDirectRoomActivity.kt  |  2 ++
     ...eysBackupSettingsRecyclerViewController.kt |  1 +
     .../quads/SharedSecureStorageActivity.kt      |  3 ++-
     .../quads/SharedSecuredStorageKeyFragment.kt  |  1 +
     .../SharedSecuredStoragePassphraseFragment.kt |  1 +
     .../verification/VerificationBottomSheet.kt   |  1 +
     .../emoji/VerificationEmojiCodeViewModel.kt   |  1 +
     .../request/VerificationRequestController.kt  |  2 ++
     .../discovery/DiscoverySettingsController.kt  | 17 +++++++++++------
     .../app/features/home/HomeDetailViewModel.kt  |  1 +
     .../home/room/detail/TimelineFragment.kt      |  3 +++
     .../voice/VoiceMessageRecorderView.kt         |  7 ++++---
     .../home/room/detail/search/SearchFragment.kt |  2 ++
     .../ViewEditHistoryEpoxyController.kt         | 10 ++++++----
     .../detail/timeline/item/MessageVoiceItem.kt  |  7 ++++---
     .../reactions/ViewReactionsEpoxyController.kt | 10 ++++++----
     .../app/features/login/LoginFragment.kt       |  4 +++-
     .../login/LoginResetPasswordFragment.kt       |  3 +--
     ...inResetPasswordMailConfirmationFragment.kt |  5 ++---
     .../app/features/login/LoginViewModel.kt      |  1 +
     .../app/features/login2/LoginViewModel2.kt    |  2 +-
     .../features/matrixto/MatrixToBottomSheet.kt  | 11 +++++------
     .../matrixto/MatrixToBottomSheetViewModel.kt  | 13 +++++++------
     .../features/navigation/DefaultNavigator.kt   |  2 ++
     .../notifications/NotificationRenderer.kt     | 16 ++++++----------
     .../onboarding/OnboardingViewModel.kt         |  1 +
     .../ftueauth/FtueAuthLoginFragment.kt         |  5 ++---
     .../ftueauth/FtueAuthResetPasswordFragment.kt |  3 +--
     ...thResetPasswordMailConfirmationFragment.kt |  5 ++---
     .../roomdirectory/RoomDirectoryActivity.kt    |  9 +++++----
     .../picker/RoomDirectoryPickerController.kt   |  8 ++++----
     .../RoomMemberProfileFragment.kt              | 10 ++++++----
     .../roomprofile/alias/RoomAliasController.kt  |  7 ++++---
     .../uploads/files/RoomUploadsFilesFragment.kt |  2 ++
     .../uploads/media/RoomUploadsMediaFragment.kt |  2 ++
     .../devtools/AccountDataEpoxyController.kt    |  2 ++
     .../settings/locale/LocalePickerController.kt | 19 +++++++++++++++----
     .../threepids/ThreePidsSettingsController.kt  |  2 ++
     .../signout/soft/SoftLogoutController.kt      | 10 ++++++----
     .../app/features/spaces/SpaceListFragment.kt  |  9 ++++++---
     .../spaces/manage/SpaceManageActivity.kt      |  1 +
     .../app/features/terms/TermsController.kt     | 12 +++++++-----
     .../usercode/UserCodeSharedViewModel.kt       | 19 ++++++++++---------
     .../app/features/widgets/WidgetActivity.kt    |  1 +
     .../app/features/widgets/WidgetFragment.kt    |  9 +++++----
     .../RoomWidgetPermissionViewModel.kt          |  1 +
     52 files changed, 175 insertions(+), 116 deletions(-)
    
    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/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/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/createdirect/CreateDirectRoomActivity.kt b/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomActivity.kt
    index 9df4f52d0f..111b872a15 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
    @@ -167,6 +168,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/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/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/verification/VerificationBottomSheet.kt b/vector/src/main/java/im/vector/app/features/crypto/verification/VerificationBottomSheet.kt
    index 65e3fbabba..8255e42d74 100644
    --- a/vector/src/main/java/im/vector/app/features/crypto/verification/VerificationBottomSheet.kt
    +++ b/vector/src/main/java/im/vector/app/features/crypto/verification/VerificationBottomSheet.kt
    @@ -252,6 +252,7 @@ class VerificationBottomSheet : VectorBaseBottomSheetDialogFragment Unit
                 }
     
                 return@withState
    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/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/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 f462128c6b..7137cbc4cf 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
    @@ -1782,6 +1782,7 @@ class TimelineFragment @Inject constructor(
                             transactionId = data.transactionId,
                     ).show(parentFragmentManager, "REQ")
                 }
    +            else                                          -> Unit
             }
         }
     
    @@ -2236,6 +2237,8 @@ class TimelineFragment @Inject constructor(
                 is EventSharedAction.EndPoll                    -> {
                     askConfirmationToEndPoll(action.eventId)
                 }
    +            is EventSharedAction.ReportContent              -> Unit /* Not clickable */
    +            EventSharedAction.Separator                     -> Unit /* Not clickable */
             }
         }
     
    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..ba39f40daf 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
    @@ -95,9 +95,10 @@ class VoiceMessageRecorderView @JvmOverloads constructor(
                 override fun onDeleteVoiceMessage() = callback.onDeleteVoiceMessage()
                 override fun onWaveformClicked() {
                     when (lastKnownState) {
    -                    RecordingUiState.Draft  -> callback.onVoicePlaybackButtonClicked()
    +                    RecordingUiState.Draft     -> callback.onVoicePlaybackButtonClicked()
                         is RecordingUiState.Recording,
                         is RecordingUiState.Locked -> callback.onRecordingWaveformClicked()
    +                    else                       -> Unit
                     }
                 }
     
    @@ -119,7 +120,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 +138,7 @@ class VoiceMessageRecorderView @JvmOverloads constructor(
                         voiceMessageViews.showRecordingLockedViews(recordingState)
                     }, 500)
                 }
    -            RecordingUiState.Draft   -> {
    +            RecordingUiState.Draft        -> {
                     stopRecordingTicker()
                     voiceMessageViews.showDraftViews()
                 }
    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/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/item/MessageVoiceItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceItem.kt
    index e9f728d976..98be75027e 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
    @@ -96,9 +96,10 @@ abstract class MessageVoiceItem : AbsMessageItem() {
             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)
    +                    is VoiceMessagePlaybackTracker.Listener.State.Playing   -> renderPlayingState(holder, state)
    +                    is VoiceMessagePlaybackTracker.Listener.State.Paused    -> renderPausedState(holder, state)
    +                    is VoiceMessagePlaybackTracker.Listener.State.Recording -> Unit
                     }
                 }
             })
    diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/reactions/ViewReactionsEpoxyController.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/reactions/ViewReactionsEpoxyController.kt
    index 10af3792d5..86e5c529ee 100644
    --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/reactions/ViewReactionsEpoxyController.kt
    +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/reactions/ViewReactionsEpoxyController.kt
    @@ -18,8 +18,9 @@ package im.vector.app.features.home.room.detail.timeline.reactions
     
     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.EmojiSpanify
     import im.vector.app.R
     import im.vector.app.core.resources.StringProvider
    @@ -41,18 +42,19 @@ class ViewReactionsEpoxyController @Inject constructor(
         override fun buildModels(state: DisplayReactionsViewState) {
             val host = this
             when (state.mapReactionKeyToMemberList) {
    -            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.mapReactionKeyToMemberList()?.forEach { reactionInfo ->
                         reactionInfoSimpleItem {
                             id(reactionInfo.eventId)
    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..61d32eadd8 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,6 +29,7 @@ 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
    @@ -269,6 +270,7 @@ class LoginFragment @Inject constructor() : AbstractSSOLoginFragment {
                     // Ensure password is hidden
                     views.passwordField.hidePassword()
    @@ -300,7 +302,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..4eba31994b 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
    @@ -173,6 +173,7 @@ class LoginViewModel @AssistedInject constructor(
                                     .withAllowedFingerPrints(listOf(action.fingerprint))
                                     .build()
                     )
    +            else                            -> Unit
             }
         }
     
    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..e2831e7d2d 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
    @@ -172,6 +171,7 @@ class LoginViewModel2 @AssistedInject constructor(
                     handleSetUserPassword(finalLastAction)
                 is LoginAction2.LoginWith        ->
                     handleLoginWith(finalLastAction)
    +            else                             -> Unit
             }
         }
     
    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..76391c6dec 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
    @@ -49,8 +49,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 +61,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)
    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/OnboardingViewModel.kt b/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewModel.kt
    index 6659058b4e..856b2ab567 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
    @@ -222,6 +222,7 @@ class OnboardingViewModel @AssistedInject constructor(
                                     .withAllowedFingerPrints(listOf(action.fingerprint))
                                     .build()
                     )
    +            else                                 -> Unit
             }
         }
     
    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..632625f4de 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,7 +28,6 @@ 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
    @@ -299,7 +298,7 @@ class FtueAuthLoginFragment @Inject constructor() : AbstractSSOFtueAuthFragment<
                     }
                 }
                 // Success is handled by the LoginActivity
    -            is Success -> Unit
    +            else       -> Unit
             }
     
             when (state.asyncRegistration) {
    @@ -308,7 +307,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/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/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/roommemberprofile/RoomMemberProfileFragment.kt b/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileFragment.kt
    index 7e919fb663..44e7405ee5 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
    @@ -198,18 +199,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/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/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/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/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/threepids/ThreePidsSettingsController.kt b/vector/src/main/java/im/vector/app/features/settings/threepids/ThreePidsSettingsController.kt
    index d374357396..7fd2292274 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,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.epoxy.noResultItem
    @@ -78,6 +79,7 @@ class ThreePidsSettingsController @Inject constructor(
             }
     
             when (data.threePids) {
    +            Uninitialized,
                 is Loading -> {
                     loadingItem {
                         id("loading")
    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..fb7786e3bd 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,8 +22,9 @@ 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
    @@ -121,8 +122,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/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/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/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/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
             }
         }
     
    
    From 97bb5a9abbd5d2a0f4f17d3892b02538cc8fea5d Mon Sep 17 00:00:00 2001
    From: Benoit Marty 
    Date: Tue, 22 Mar 2022 15:45:02 +0100
    Subject: [PATCH 054/262] Also upgrade the coroutine lib
    
    ---
     dependencies.gradle | 2 +-
     1 file changed, 1 insertion(+), 1 deletion(-)
    
    diff --git a/dependencies.gradle b/dependencies.gradle
    index c0b9726f0f..7666a3bf9f 100644
    --- a/dependencies.gradle
    +++ b/dependencies.gradle
    @@ -10,7 +10,7 @@ ext.versions = [
     def gradle = "7.0.4"
     // Ref: https://kotlinlang.org/releases.html
     def kotlin = "1.6.0"
    -def kotlinCoroutines = "1.5.2"
    +def kotlinCoroutines = "1.6.0"
     def dagger = "2.40.5"
     def retrofit = "2.9.0"
     def arrow = "0.8.2"
    
    From 03fee2a131a402baf2579adf3441df1880c86c38 Mon Sep 17 00:00:00 2001
    From: Benoit Marty 
    Date: Tue, 22 Mar 2022 15:56:06 +0100
    Subject: [PATCH 055/262] The `.exhaustive` trick is not needed anymore in
     Kotlin 1.6.0
     https://kotlinlang.org/docs/whatsnew16.html#stable-exhaustive-when-statements-for-enum-sealed-and-boolean-subjects
    
    ---
     .../DefaultQrCodeVerificationTransaction.kt   |  5 ++---
     .../android/sdk/internal/util/Exhaustive.kt   | 20 -------------------
     .../root/src/app_package/ViewModel.kt.ftl     |  3 +--
     .../analytics/DebugAnalyticsViewModel.kt      |  3 +--
     .../settings/DebugPrivateSettingsViewModel.kt |  3 +--
     .../vector/app/core/extensions/Exhaustive.kt  | 20 -------------------
     .../app/core/platform/VectorBaseActivity.kt   |  3 +--
     .../app/core/ui/views/NotificationAreaView.kt |  3 +--
     .../ui/consent/AnalyticsConsentViewModel.kt   |  3 +--
     .../ui/consent/AnalyticsOptInActivity.kt      |  3 +--
     .../preview/AttachmentsPreviewViewModel.kt    |  3 +--
     .../app/features/call/VectorCallViewModel.kt  |  5 ++---
     .../call/conference/JitsiCallViewModel.kt     |  3 +--
     .../call/conference/VectorJitsiActivity.kt    |  3 +--
     .../call/transfer/CallTransferActivity.kt     |  3 +--
     .../contactsbook/ContactsBookFragment.kt      |  3 +--
     .../contactsbook/ContactsBookViewModel.kt     |  3 +--
     .../createdirect/CreateDirectRoomActivity.kt  |  7 +++----
     .../createdirect/CreateDirectRoomViewModel.kt |  5 ++---
     .../quads/SharedSecureStorageViewModel.kt     |  3 +--
     .../crypto/recover/BootstrapBottomSheet.kt    |  3 +--
     .../recover/BootstrapSharedViewModel.kt       |  9 ++++-----
     .../verification/VerificationBottomSheet.kt   |  3 +--
     .../VerificationBottomSheetViewModel.kt       |  3 +--
     .../features/devtools/RoomDevToolActivity.kt  |  3 +--
     .../discovery/DiscoverySettingsFragment.kt    |  5 ++---
     .../discovery/DiscoverySettingsViewModel.kt   |  5 ++---
     .../SettingsTextButtonSingleLineItem.kt       |  5 ++---
     .../change/SetIdentityServerFragment.kt       |  3 +--
     .../change/SetIdentityServerViewModel.kt      |  3 +--
     .../vector/app/features/home/HomeActivity.kt  |  8 ++++----
     .../features/home/HomeActivityViewModel.kt    |  3 +--
     .../home/room/detail/TimelineFragment.kt      | 13 ++++++------
     .../home/room/detail/TimelineViewModel.kt     |  3 +--
     .../composer/MessageComposerViewModel.kt      |  5 ++---
     .../voice/VoiceMessageRecorderView.kt         |  3 +--
     .../room/detail/search/SearchViewModel.kt     |  3 +--
     .../helper/ContentUploadStateTrackerBinder.kt |  3 +--
     .../detail/timeline/item/PollOptionView.kt    |  3 +--
     .../timeline/item/VerificationRequestItem.kt  |  3 +--
     .../home/room/list/RoomListFragment.kt        |  5 ++---
     .../home/room/list/RoomListViewModel.kt       |  3 +--
     .../location/LocationSharingFragment.kt       |  3 +--
     .../location/LocationSharingViewModel.kt      |  3 +--
     .../features/login/AbstractLoginFragment.kt   |  3 +--
     .../app/features/login/LoginActivity.kt       |  7 +++----
     .../app/features/login/LoginFragment.kt       |  5 ++---
     .../app/features/login/LoginViewModel.kt      |  9 ++++-----
     .../features/login2/AbstractLoginFragment2.kt |  3 +--
     .../app/features/login2/LoginViewModel2.kt    |  9 ++++-----
     .../matrixto/MatrixToBottomSheetViewModel.kt  |  3 +--
     .../app/features/onboarding/Login2Variant.kt  |  3 +--
     .../onboarding/OnboardingViewModel.kt         |  9 ++++-----
     .../ftueauth/AbstractFtueAuthFragment.kt      |  3 +--
     .../ftueauth/FtueAuthLoginFragment.kt         |  5 ++---
     .../onboarding/ftueauth/FtueAuthVariant.kt    |  5 ++---
     .../poll/create/CreatePollFragment.kt         |  3 +--
     .../features/qrcode/QrCodeScannerActivity.kt  |  3 +--
     .../room/RequireActiveMembershipViewModel.kt  |  3 +--
     .../roomdirectory/PublicRoomsFragment.kt      |  3 +--
     .../createroom/CreateRoomFragment.kt          |  3 +--
     .../createroom/CreateRoomViewModel.kt         |  5 ++---
     .../picker/RoomDirectoryPickerViewModel.kt    |  3 +--
     .../roompreview/RoomPreviewViewModel.kt       |  3 +--
     .../RoomMemberProfileFragment.kt              |  3 +--
     .../RoomMemberProfileViewModel.kt             |  3 +--
     .../devices/DeviceListBottomSheet.kt          |  3 +--
     .../devices/DeviceListBottomSheetViewModel.kt |  3 +--
     .../roomprofile/RoomProfileActivity.kt        |  3 +--
     .../roomprofile/RoomProfileFragment.kt        |  3 +--
     .../roomprofile/RoomProfileViewModel.kt       |  3 +--
     .../roomprofile/alias/RoomAliasFragment.kt    |  3 +--
     .../roomprofile/alias/RoomAliasViewModel.kt   |  3 +--
     .../banned/RoomBannedMemberListViewModel.kt   |  3 +--
     .../members/RoomMemberListViewModel.kt        |  3 +--
     .../permissions/RoomPermissionsFragment.kt    |  3 +--
     .../permissions/RoomPermissionsViewModel.kt   |  3 +--
     .../settings/RoomSettingsFragment.kt          |  3 +--
     .../settings/RoomSettingsViewModel.kt         |  3 +--
     .../RoomJoinRuleChooseRestrictedViewModel.kt  |  3 +--
     .../uploads/RoomUploadsFragment.kt            |  3 +--
     .../uploads/RoomUploadsViewModel.kt           |  3 +--
     .../deactivation/DeactivateAccountFragment.kt |  3 +--
     .../DeactivateAccountViewModel.kt             |  3 +--
     .../CrossSigningSettingsFragment.kt           |  3 +--
     .../CrossSigningSettingsViewModel.kt          |  3 +--
     .../devices/VectorSettingsDevicesFragment.kt  |  3 +--
     .../settings/devtools/AccountDataViewModel.kt |  3 +--
     .../settings/devtools/KeyRequestViewModel.kt  |  3 +--
     .../settings/devtools/KeyRequestsFragment.kt  |  3 +--
     .../VectorSettingsIgnoredUsersFragment.kt     |  3 +--
     .../settings/legals/LegalsViewModel.kt        |  3 +--
     .../settings/locale/LocalePickerFragment.kt   |  3 +--
     .../settings/locale/LocalePickerViewModel.kt  |  3 +--
     .../settings/push/PushGatewaysFragment.kt     |  3 +--
     .../settings/push/PushGatewaysViewModel.kt    |  3 +--
     .../threepids/ThreePidsSettingsController.kt  |  5 ++---
     .../threepids/ThreePidsSettingsFragment.kt    |  3 +--
     .../threepids/ThreePidsSettingsViewModel.kt   |  3 +--
     .../features/share/IncomingShareFragment.kt   |  3 +--
     .../features/share/IncomingShareViewModel.kt  |  5 ++---
     .../app/features/spaces/SpaceListFragment.kt  |  3 +--
     .../spaces/create/CreateSpaceViewModel.kt     |  3 +--
     .../manage/SpaceManageSharedViewModel.kt      |  3 +--
     .../spaces/manage/SpaceSettingsFragment.kt    |  3 +--
     .../spaces/people/SpacePeopleViewModel.kt     |  3 +--
     .../app/features/terms/ReviewTermsActivity.kt |  3 +--
     .../app/features/terms/ReviewTermsFragment.kt |  3 +--
     .../features/terms/ReviewTermsViewModel.kt    |  3 +--
     .../app/features/usercode/UserCodeActivity.kt |  5 ++---
     .../userdirectory/UserListViewModel.kt        |  3 +--
     .../workers/signout/SignoutCheckViewModel.kt  |  3 +--
     112 files changed, 149 insertions(+), 298 deletions(-)
     delete mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/Exhaustive.kt
     delete mode 100644 vector/src/main/java/im/vector/app/core/extensions/Exhaustive.kt
    
    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 8bfbcfaef4..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
    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/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/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/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/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/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/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 111b872a15..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
    @@ -36,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
    @@ -85,7 +84,7 @@ class CreateDirectRoomActivity : SimpleFragmentActivity() {
                             is UserListSharedAction.OnMenuItemSelected -> onMenuItemSelected(action)
                             UserListSharedAction.OpenPhoneBook         -> openPhoneBook()
                             UserListSharedAction.AddByQrCode           -> openAddByQrCode()
    -                    }.exhaustive
    +                    }
                     }
                     .launchIn(lifecycleScope)
             if (isFirstCreation()) {
    @@ -112,7 +111,7 @@ class CreateDirectRoomActivity : SimpleFragmentActivity() {
                         Toast.makeText(this, R.string.cannot_dm_self, Toast.LENGTH_SHORT).show()
                         finish()
                     }
    -            }.exhaustive
    +            }
             }
     
             qrViewModel.observeViewEvents {
    @@ -125,7 +124,7 @@ class CreateDirectRoomActivity : SimpleFragmentActivity() {
                         finish()
                     }
                     else                               -> Unit
    -            }.exhaustive
    +            }
             }
         }
     
    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/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/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 showSnackbar(it.message)
    -            }.exhaustive
    +            }
             }
             supportFragmentManager.addOnBackStackChangedListener(this)
         }
    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 2e9ab0efcb..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
    @@ -107,6 +106,7 @@ class HomeActivity :
     
         @Suppress("UNUSED")
         private val analyticsAccountDataViewModel: AnalyticsAccountDataViewModel by viewModel()
    +
         @Suppress("UNUSED")
         private val userColorAccountDataViewModel: UserColorAccountDataViewModel by viewModel()
     
    @@ -232,7 +232,7 @@ class HomeActivity :
                             HomeActivitySharedAction.SendSpaceFeedBack    -> {
                                 bugReporter.openBugReportScreen(this, ReportType.SPACE_BETA_FEEDBACK)
                             }
    -                    }.exhaustive
    +                    }
                     }
                     .launchIn(lifecycleScope)
     
    @@ -256,7 +256,7 @@ class HomeActivity :
                     HomeActivityViewEvents.ShowAnalyticsOptIn               -> handleShowAnalyticsOptIn()
                     HomeActivityViewEvents.NotifyUserForThreadsMigration    -> handleNotifyUserForThreadsMigration()
                     is HomeActivityViewEvents.MigrateThreads                -> migrateThreadsIfNeeded(it.checkSession)
    -            }.exhaustive
    +            }
             }
             homeActivityViewModel.onEach { renderState(it) }
     
    @@ -374,7 +374,7 @@ class HomeActivity :
                     // Idle or Incremental sync status
                     views.waitingView.root.isVisible = false
                 }
    -        }.exhaustive
    +        }
         }
     
         private fun handleAskPasswordToInitCrossSigning(events: HomeActivityViewEvents.AskPasswordToInitCrossSigning) {
    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 b4af50c7ff..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
    @@ -306,6 +305,6 @@ class HomeActivityViewModel @AssistedInject constructor(
                 HomeActivityViewActions.ViewStarted               -> {
                     initialize()
                 }
    -        }.exhaustive
    +        }
         }
     }
    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 7137cbc4cf..a0844a9a96 100644
    --- a/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt
    +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt
    @@ -72,7 +72,6 @@ import im.vector.app.core.dialogs.ConfirmationDialogBuilder
     import im.vector.app.core.dialogs.GalleryOrCameraDialogHelper
     import im.vector.app.core.epoxy.LayoutManagerStateRestorer
     import im.vector.app.core.extensions.cleanup
    -import im.vector.app.core.extensions.exhaustive
     import im.vector.app.core.extensions.hideKeyboard
     import im.vector.app.core.extensions.registerStartForActivityResult
     import im.vector.app.core.extensions.setTextOrHide
    @@ -446,7 +445,7 @@ class TimelineFragment @Inject constructor(
                         }
                         showErrorInSnackbar(it.throwable)
                     }
    -            }.exhaustive
    +            }
             }
     
             timelineViewModel.observeViewEvents {
    @@ -483,7 +482,7 @@ class TimelineFragment @Inject constructor(
                     RoomDetailViewEvents.StopChatEffects                     -> handleStopChatEffects()
                     is RoomDetailViewEvents.DisplayAndAcceptCall             -> acceptIncomingCall(it)
                     RoomDetailViewEvents.RoomReplacementStarted              -> handleRoomReplacement()
    -            }.exhaustive
    +            }
             }
     
             if (savedInstanceState == null) {
    @@ -875,7 +874,7 @@ class TimelineFragment @Inject constructor(
                     onContentAttachmentsReady(sharedData.attachmentData)
                 }
                 null                      -> Timber.v("No share data to process")
    -        }.exhaustive
    +        }
         }
     
         private fun handleSpaceShare() {
    @@ -1241,7 +1240,7 @@ class TimelineFragment @Inject constructor(
                     insertUserDisplayNameInTextEditor(roomDetailPendingAction.userId)
                 is RoomDetailPendingAction.OpenRoom          ->
                     handleOpenRoom(RoomDetailViewEvents.OpenRoom(roomDetailPendingAction.roomId, roomDetailPendingAction.closeCurrentRoom))
    -        }.exhaustive
    +        }
         }
     
         override fun onPause() {
    @@ -1658,7 +1657,7 @@ class TimelineFragment @Inject constructor(
                 is MessageComposerViewEvents.SlashCommandNotSupportedInThreads -> {
                     displayCommandError(getString(R.string.command_not_supported_in_threads, sendMessageResult.command.command))
                 }
    -        } // .exhaustive
    +        } // 
     
             lockSendButton = false
         }
    @@ -2437,7 +2436,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/MessageComposerViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerViewModel.kt
    index 009d898940..8b34d9d9a9 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
    @@ -463,7 +462,7 @@ class MessageComposerViewModel @AssistedInject constructor(
                                 _viewEvents.post(MessageComposerViewEvents.SlashCommandResultOk())
                                 popDraft()
                             }
    -                    }.exhaustive
    +                    }
                     }
                     is SendMode.Edit    -> {
                         // is original event a reply?
    @@ -536,7 +535,7 @@ class MessageComposerViewModel @AssistedInject constructor(
                     is SendMode.Voice   -> {
                         // do nothing
                     }
    -            }.exhaustive
    +            }
             }
         }
     
    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 ba39f40daf..7cb8bbad95 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
    @@ -168,7 +167,7 @@ class VoiceMessageRecorderView @JvmOverloads constructor(
                 DraggingState.Ready         -> {
                     // do nothing
                 }
    -        }.exhaustive
    +        }
             dragState = newDragState
         }
     
    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/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/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 handleSelectRoom(it, it.isInviteAlreadyAccepted)
                     is RoomListViewEvents.Done                      -> Unit
                     is RoomListViewEvents.NavigateToMxToBottomSheet -> handleShowMxToLink(it.link)
    -            }.exhaustive
    +            }
             }
     
             views.createChatFabMenu.listener = this
    @@ -418,7 +417,7 @@ class RoomListFragment @Inject constructor(
                 is RoomListQuickActionsSharedAction.Leave                     -> {
                     promptLeaveRoom(quickAction.roomId)
                 }
    -        }.exhaustive
    +        }
         }
     
         private fun promptLeaveRoom(roomId: String) {
    diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListViewModel.kt
    index ec8b01876b..70974bc1f6 100644
    --- a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListViewModel.kt
    +++ b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListViewModel.kt
    @@ -29,7 +29,6 @@ import im.vector.app.AppStateHandler
     import im.vector.app.RoomGroupingMethod
     import im.vector.app.core.di.MavericksAssistedViewModelFactory
     import im.vector.app.core.di.hiltMavericksViewModelFactory
    -import im.vector.app.core.extensions.exhaustive
     import im.vector.app.core.platform.VectorViewModel
     import im.vector.app.core.resources.StringProvider
     import im.vector.app.features.analytics.AnalyticsTracker
    @@ -163,7 +162,7 @@ class RoomListViewModel @AssistedInject constructor(
                 is RoomListAction.ToggleSection               -> handleToggleSection(action.section)
                 is RoomListAction.JoinSuggestedRoom           -> handleJoinSuggestedRoom(action)
                 is RoomListAction.ShowRoomDetails             -> handleShowRoomDetails(action)
    -        }.exhaustive
    +        }
         }
     
         fun isPublicRoom(roomId: String): Boolean {
    diff --git a/vector/src/main/java/im/vector/app/features/location/LocationSharingFragment.kt b/vector/src/main/java/im/vector/app/features/location/LocationSharingFragment.kt
    index c4dccc1b73..d61d53ae51 100644
    --- a/vector/src/main/java/im/vector/app/features/location/LocationSharingFragment.kt
    +++ b/vector/src/main/java/im/vector/app/features/location/LocationSharingFragment.kt
    @@ -29,7 +29,6 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder
     import com.mapbox.mapboxsdk.maps.MapView
     import im.vector.app.BuildConfig
     import im.vector.app.R
    -import im.vector.app.core.extensions.exhaustive
     import im.vector.app.core.platform.VectorBaseFragment
     import im.vector.app.core.utils.PERMISSIONS_FOR_BACKGROUND_LOCATION_SHARING
     import im.vector.app.core.utils.PERMISSIONS_FOR_FOREGROUND_LOCATION_SHARING
    @@ -86,7 +85,7 @@ class LocationSharingFragment @Inject constructor(
                     LocationSharingViewEvents.Close                     -> locationSharingNavigator.quit()
                     LocationSharingViewEvents.LocationNotAvailableError -> handleLocationNotAvailableError()
                     is LocationSharingViewEvents.ZoomToUserLocation     -> handleZoomToUserLocationEvent(it)
    -            }.exhaustive
    +            }
             }
         }
     
    diff --git a/vector/src/main/java/im/vector/app/features/location/LocationSharingViewModel.kt b/vector/src/main/java/im/vector/app/features/location/LocationSharingViewModel.kt
    index 639666e63f..1d68247f2c 100644
    --- a/vector/src/main/java/im/vector/app/features/location/LocationSharingViewModel.kt
    +++ b/vector/src/main/java/im/vector/app/features/location/LocationSharingViewModel.kt
    @@ -23,7 +23,6 @@ import dagger.assisted.AssistedFactory
     import dagger.assisted.AssistedInject
     import im.vector.app.core.di.MavericksAssistedViewModelFactory
     import im.vector.app.core.di.hiltMavericksViewModelFactory
    -import im.vector.app.core.extensions.exhaustive
     import im.vector.app.core.platform.VectorViewModel
     import im.vector.app.features.home.room.detail.timeline.helper.LocationPinProvider
     import im.vector.app.features.location.domain.usecase.CompareLocationsUseCase
    @@ -122,7 +121,7 @@ class LocationSharingViewModel @AssistedInject constructor(
                 is LocationSharingAction.LocationTargetChange    -> handleLocationTargetChangeAction(action)
                 LocationSharingAction.ZoomToUserLocation         -> handleZoomToUserLocationAction()
                 LocationSharingAction.StartLiveLocationSharing   -> handleStartLiveLocationSharingAction()
    -        }.exhaustive
    +        }
         }
     
         private fun handleCurrentUserLocationSharingAction() = withState { state ->
    diff --git a/vector/src/main/java/im/vector/app/features/login/AbstractLoginFragment.kt b/vector/src/main/java/im/vector/app/features/login/AbstractLoginFragment.kt
    index 8b83873142..f5e48e84e7 100644
    --- a/vector/src/main/java/im/vector/app/features/login/AbstractLoginFragment.kt
    +++ b/vector/src/main/java/im/vector/app/features/login/AbstractLoginFragment.kt
    @@ -26,7 +26,6 @@ import com.airbnb.mvrx.withState
     import com.google.android.material.dialog.MaterialAlertDialogBuilder
     import im.vector.app.R
     import im.vector.app.core.dialogs.UnrecognizedCertificateDialog
    -import im.vector.app.core.extensions.exhaustive
     import im.vector.app.core.platform.OnBackPressed
     import im.vector.app.core.platform.VectorBaseFragment
     import kotlinx.coroutines.CancellationException
    @@ -69,7 +68,7 @@ abstract class AbstractLoginFragment : VectorBaseFragment(
                 else                       ->
                     // This is handled by the Activity
                     Unit
    -        }.exhaustive
    +        }
         }
     
         override fun showFailure(throwable: Throwable) {
    diff --git a/vector/src/main/java/im/vector/app/features/login/LoginActivity.kt b/vector/src/main/java/im/vector/app/features/login/LoginActivity.kt
    index a40f26acec..dec6fef040 100644
    --- a/vector/src/main/java/im/vector/app/features/login/LoginActivity.kt
    +++ b/vector/src/main/java/im/vector/app/features/login/LoginActivity.kt
    @@ -35,7 +35,6 @@ import im.vector.app.R
     import im.vector.app.core.extensions.POP_BACK_STACK_EXCLUSIVE
     import im.vector.app.core.extensions.addFragment
     import im.vector.app.core.extensions.addFragmentToBackstack
    -import im.vector.app.core.extensions.exhaustive
     import im.vector.app.core.extensions.validateBackPressed
     import im.vector.app.core.platform.VectorBaseActivity
     import im.vector.app.databinding.ActivityLoginBinding
    @@ -197,7 +196,7 @@ open class LoginActivity : VectorBaseActivity(), UnlockedA
                 is LoginViewEvents.Loading                                    ->
                     // This is handled by the Fragments
                     Unit
    -        }.exhaustive
    +        }
         }
     
         private fun updateWithState(loginViewState: LoginViewState) {
    @@ -260,13 +259,13 @@ open class LoginActivity : VectorBaseActivity(), UnlockedA
                                 tag = FRAGMENT_LOGIN_TAG,
                                 option = commonOption)
                         LoginMode.Unsupported -> onLoginModeNotSupported(state.loginModeSupportedTypes)
    -                }.exhaustive
    +                }
                 }
                 SignMode.SignInWithMatrixId -> addFragmentToBackstack(views.loginFragmentContainer,
                         LoginFragment::class.java,
                         tag = FRAGMENT_LOGIN_TAG,
                         option = commonOption)
    -        }.exhaustive
    +        }
         }
     
         /**
    diff --git a/vector/src/main/java/im/vector/app/features/login/LoginFragment.kt b/vector/src/main/java/im/vector/app/features/login/LoginFragment.kt
    index 61d32eadd8..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
    @@ -31,7 +31,6 @@ 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
    @@ -98,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() {
    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 4eba31994b..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) {
    @@ -448,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) {
    @@ -556,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?) {
    @@ -586,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 e2831e7d2d..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
    @@ -28,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
    @@ -136,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() {
    @@ -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/MatrixToBottomSheetViewModel.kt b/vector/src/main/java/im/vector/app/features/matrixto/MatrixToBottomSheetViewModel.kt
    index 76391c6dec..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
    @@ -264,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/onboarding/Login2Variant.kt b/vector/src/main/java/im/vector/app/features/onboarding/Login2Variant.kt
    index 107c08da5a..163af5d8d1 100644
    --- a/vector/src/main/java/im/vector/app/features/onboarding/Login2Variant.kt
    +++ b/vector/src/main/java/im/vector/app/features/onboarding/Login2Variant.kt
    @@ -30,7 +30,6 @@ import im.vector.app.R
     import im.vector.app.core.extensions.POP_BACK_STACK_EXCLUSIVE
     import im.vector.app.core.extensions.addFragment
     import im.vector.app.core.extensions.addFragmentToBackstack
    -import im.vector.app.core.extensions.exhaustive
     import im.vector.app.core.extensions.resetBackstack
     import im.vector.app.core.platform.VectorBaseActivity
     import im.vector.app.databinding.ActivityLoginBinding
    @@ -257,7 +256,7 @@ class Login2Variant(
                 is LoginViewEvents2.OnSessionCreated                           -> handleOnSessionCreated(event)
                 is LoginViewEvents2.Finish                                     -> terminate(true)
                 is LoginViewEvents2.CancelRegistration                         -> handleCancelRegistration()
    -        }.exhaustive
    +        }
         }
     
         private fun handleCancelRegistration() {
    diff --git a/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewModel.kt b/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewModel.kt
    index 856b2ab567..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
    @@ -166,7 +165,7 @@ class OnboardingViewModel @AssistedInject constructor(
                 OnboardingAction.SaveSelectedProfilePicture    -> updateProfilePicture()
                 is OnboardingAction.PostViewEvent              -> _viewEvents.post(action.viewEvent)
                 OnboardingAction.StopEmailValidationCheck      -> cancelWaitForEmailValidation()
    -        }.exhaustive
    +        }
         }
     
         private fun handleSplashAction(resetConfig: Boolean, onboardingFlow: OnboardingFlow) {
    @@ -404,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) {
    @@ -512,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?) {
    @@ -542,7 +541,7 @@ class OnboardingViewModel @AssistedInject constructor(
                     else                          -> {
                         onWellKnownError()
                     }
    -            }.exhaustive
    +            }
             }
         }
     
    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/FtueAuthLoginFragment.kt b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthLoginFragment.kt
    index 632625f4de..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
    @@ -29,7 +29,6 @@ import androidx.lifecycle.lifecycleScope
     import com.airbnb.mvrx.Fail
     import com.airbnb.mvrx.Loading
     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
    @@ -104,7 +103,7 @@ class FtueAuthLoginFragment @Inject constructor() : AbstractSSOFtueAuthFragment<
                         views.loginField.setAutofillHints(HintConstants.AUTOFILL_HINT_USERNAME)
                         views.passwordField.setAutofillHints(HintConstants.AUTOFILL_HINT_PASSWORD)
                     }
    -            }.exhaustive
    +            }
             }
         }
     
    @@ -114,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() {
    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/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/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/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 44e7405ee5..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
    @@ -39,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
    @@ -134,7 +133,7 @@ class RoomMemberProfileFragment @Inject constructor(
                     is RoomMemberProfileViewEvents.OnBanActionSuccess          -> Unit
                     is RoomMemberProfileViewEvents.OnIgnoreActionSuccess       -> Unit
                     is RoomMemberProfileViewEvents.OnInviteActionSuccess       -> Unit
    -            }.exhaustive
    +            }
             }
             setupLongClicks()
         }
    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/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/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/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/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 7fd2292274..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
    @@ -26,7 +26,6 @@ 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
    @@ -162,7 +161,7 @@ class ThreePidsSettingsController @Inject constructor(
                     }
                 }
                 is ThreePidsSettingsUiState.AddingPhoneNumber -> Unit
    -        }.exhaustive
    +        }
     
             settingsSectionTitleItem {
                 id("msisdn")
    @@ -225,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/spaces/SpaceListFragment.kt b/vector/src/main/java/im/vector/app/features/spaces/SpaceListFragment.kt
    index fb7786e3bd..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
    @@ -29,7 +29,6 @@ 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
    @@ -110,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
    +            }
             }
         }
     
    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/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/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/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/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) {
    
    From f791ddb7bb3feffa4ff322e694afdddd3a0fbf2b Mon Sep 17 00:00:00 2001
    From: Benoit Marty 
    Date: Tue, 22 Mar 2022 15:57:10 +0100
    Subject: [PATCH 056/262] Small cleanup
    
    ---
     .../im/vector/app/features/invite/InviteUsersToRoomActivity.kt | 3 +--
     1 file changed, 1 insertion(+), 2 deletions(-)
    
    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)
    
    From 86829008c31e4a650ccf3ab4b7f327f322fbecd5 Mon Sep 17 00:00:00 2001
    From: Benoit Marty 
    Date: Tue, 22 Mar 2022 16:33:31 +0100
    Subject: [PATCH 057/262] runBlockingTest -> runTest
     https://github.com/Kotlin/kotlinx.coroutines/blob/master/kotlinx-coroutines-test/MIGRATION.md
    
    ---
     .../space/DefaultResolveSpaceInfoTaskTest.kt  |   6 +-
     .../impl/DefaultVectorAnalyticsTest.kt        |  22 +--
     .../impl/LateInitUserPropertiesFactoryTest.kt |   8 +-
     .../quads/SharedSecureStorageViewModelTest.kt | 164 ++++++++----------
     .../usecase/CompareLocationsUseCaseTest.kt    |   6 +-
     .../usecase/DownloadMediaUseCaseTest.kt       |   6 +-
     .../onboarding/OnboardingViewModelTest.kt     |  36 ++--
     .../RegistrationActionHandlerTest.kt          |   4 +-
     8 files changed, 120 insertions(+), 132 deletions(-)
    
    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/vector/src/test/java/im/vector/app/features/analytics/impl/DefaultVectorAnalyticsTest.kt b/vector/src/test/java/im/vector/app/features/analytics/impl/DefaultVectorAnalyticsTest.kt
    index b17c1a8bba..543d517db1 100644
    --- a/vector/src/test/java/im/vector/app/features/analytics/impl/DefaultVectorAnalyticsTest.kt
    +++ b/vector/src/test/java/im/vector/app/features/analytics/impl/DefaultVectorAnalyticsTest.kt
    @@ -30,7 +30,7 @@ import im.vector.app.test.fixtures.aVectorAnalyticsScreen
     import kotlinx.coroutines.CoroutineScope
     import kotlinx.coroutines.Dispatchers
     import kotlinx.coroutines.ExperimentalCoroutinesApi
    -import kotlinx.coroutines.test.runBlockingTest
    +import kotlinx.coroutines.test.runTest
     import org.junit.Before
     import org.junit.Test
     
    @@ -60,35 +60,35 @@ class DefaultVectorAnalyticsTest {
         }
     
         @Test
    -    fun `when setting user consent then updates analytics store`() = runBlockingTest {
    +    fun `when setting user consent then updates analytics store`() = runTest {
             defaultVectorAnalytics.setUserConsent(true)
     
             fakeAnalyticsStore.verifyConsentUpdated(updatedValue = true)
         }
     
         @Test
    -    fun `when consenting to analytics then updates posthog opt out to false`() = runBlockingTest {
    +    fun `when consenting to analytics then updates posthog opt out to false`() = runTest {
             fakeAnalyticsStore.givenUserContent(consent = true)
     
             fakePostHog.verifyOptOutStatus(optedOut = false)
         }
     
         @Test
    -    fun `when revoking consent to analytics then updates posthog opt out to true`() = runBlockingTest {
    +    fun `when revoking consent to analytics then updates posthog opt out to true`() = runTest {
             fakeAnalyticsStore.givenUserContent(consent = false)
     
             fakePostHog.verifyOptOutStatus(optedOut = true)
         }
     
         @Test
    -    fun `when setting the analytics id then updates analytics store`() = runBlockingTest {
    +    fun `when setting the analytics id then updates analytics store`() = runTest {
             defaultVectorAnalytics.setAnalyticsId(AN_ANALYTICS_ID)
     
             fakeAnalyticsStore.verifyAnalyticsIdUpdated(updatedValue = AN_ANALYTICS_ID)
         }
     
         @Test
    -    fun `given lateinit user properties when valid analytics id updates then identify with lateinit properties`() = runBlockingTest {
    +    fun `given lateinit user properties when valid analytics id updates then identify with lateinit properties`() = runTest {
             fakeLateInitUserPropertiesFactory.givenCreatesProperties(A_LATE_INIT_USER_PROPERTIES)
     
             fakeAnalyticsStore.givenAnalyticsId(AN_ANALYTICS_ID)
    @@ -97,7 +97,7 @@ class DefaultVectorAnalyticsTest {
         }
     
         @Test
    -    fun `when signing out then resets posthog`() = runBlockingTest {
    +    fun `when signing out then resets posthog`() = runTest {
             fakeAnalyticsStore.allowSettingAnalyticsIdToCallBackingFlow()
     
             defaultVectorAnalytics.onSignOut()
    @@ -106,7 +106,7 @@ class DefaultVectorAnalyticsTest {
         }
     
         @Test
    -    fun `given user consent when tracking screen events then submits to posthog`() = runBlockingTest {
    +    fun `given user consent when tracking screen events then submits to posthog`() = runTest {
             fakeAnalyticsStore.givenUserContent(consent = true)
     
             defaultVectorAnalytics.screen(A_SCREEN_EVENT)
    @@ -115,7 +115,7 @@ class DefaultVectorAnalyticsTest {
         }
     
         @Test
    -    fun `given user has not consented when tracking screen events then does not track`() = runBlockingTest {
    +    fun `given user has not consented when tracking screen events then does not track`() = runTest {
             fakeAnalyticsStore.givenUserContent(consent = false)
     
             defaultVectorAnalytics.screen(A_SCREEN_EVENT)
    @@ -124,7 +124,7 @@ class DefaultVectorAnalyticsTest {
         }
     
         @Test
    -    fun `given user consent when tracking events then submits to posthog`() = runBlockingTest {
    +    fun `given user consent when tracking events then submits to posthog`() = runTest {
             fakeAnalyticsStore.givenUserContent(consent = true)
     
             defaultVectorAnalytics.capture(AN_EVENT)
    @@ -133,7 +133,7 @@ class DefaultVectorAnalyticsTest {
         }
     
         @Test
    -    fun `given user has not consented when tracking events then does not track`() = runBlockingTest {
    +    fun `given user has not consented when tracking events then does not track`() = runTest {
             fakeAnalyticsStore.givenUserContent(consent = false)
     
             defaultVectorAnalytics.capture(AN_EVENT)
    diff --git a/vector/src/test/java/im/vector/app/features/analytics/impl/LateInitUserPropertiesFactoryTest.kt b/vector/src/test/java/im/vector/app/features/analytics/impl/LateInitUserPropertiesFactoryTest.kt
    index c2fa50f789..2068099ab9 100644
    --- a/vector/src/test/java/im/vector/app/features/analytics/impl/LateInitUserPropertiesFactoryTest.kt
    +++ b/vector/src/test/java/im/vector/app/features/analytics/impl/LateInitUserPropertiesFactoryTest.kt
    @@ -23,7 +23,7 @@ import im.vector.app.test.fakes.FakeContext
     import im.vector.app.test.fakes.FakeSession
     import im.vector.app.test.fakes.FakeVectorStore
     import kotlinx.coroutines.ExperimentalCoroutinesApi
    -import kotlinx.coroutines.test.runBlockingTest
    +import kotlinx.coroutines.test.runTest
     import org.amshove.kluent.shouldBeEqualTo
     import org.junit.Test
     
    @@ -43,14 +43,14 @@ class LateInitUserPropertiesFactoryTest {
         )
     
         @Test
    -    fun `given no active session when creating properties then returns null`() = runBlockingTest {
    +    fun `given no active session when creating properties then returns null`() = runTest {
             val result = lateInitUserProperties.createUserProperties()
     
             result shouldBeEqualTo null
         }
     
         @Test
    -    fun `given no use case set on an active session when creating properties then returns null`() = runBlockingTest {
    +    fun `given no use case set on an active session when creating properties then returns null`() = runTest {
             fakeVectorStore.givenUseCase(null)
             fakeSession.givenVectorStore(fakeVectorStore.instance)
             fakeActiveSessionDataSource.setActiveSession(fakeSession)
    @@ -61,7 +61,7 @@ class LateInitUserPropertiesFactoryTest {
         }
     
         @Test
    -    fun `given use case set on an active session when creating properties then includes the use case`() = runBlockingTest {
    +    fun `given use case set on an active session when creating properties then includes the use case`() = runTest {
             fakeVectorStore.givenUseCase(FtueUseCase.TEAMS)
             fakeActiveSessionDataSource.setActiveSession(fakeSession)
             val result = lateInitUserProperties.createUserProperties()
    diff --git a/vector/src/test/java/im/vector/app/features/crypto/quads/SharedSecureStorageViewModelTest.kt b/vector/src/test/java/im/vector/app/features/crypto/quads/SharedSecureStorageViewModelTest.kt
    index fc4197e07a..cd80be0801 100644
    --- a/vector/src/test/java/im/vector/app/features/crypto/quads/SharedSecureStorageViewModelTest.kt
    +++ b/vector/src/test/java/im/vector/app/features/crypto/quads/SharedSecureStorageViewModelTest.kt
    @@ -21,7 +21,7 @@ import com.airbnb.mvrx.test.MvRxTestRule
     import im.vector.app.test.fakes.FakeSession
     import im.vector.app.test.fakes.FakeStringProvider
     import im.vector.app.test.test
    -import kotlinx.coroutines.test.runBlockingTest
    +import kotlinx.coroutines.test.runTest
     import org.junit.Rule
     import org.junit.Test
     import org.matrix.android.sdk.api.session.securestorage.IntegrityResult
    @@ -47,117 +47,105 @@ class SharedSecureStorageViewModelTest {
         val args = SharedSecureStorageActivity.Args(keyId = null, emptyList(), "alias")
     
         @Test
    -    fun `given a key info with passphrase when initialising then step is EnterPassphrase`() {
    -        runBlockingTest {
    -            givenKey(KEY_INFO_WITH_PASSPHRASE)
    -            val viewModel = createViewModel()
    -            viewModel
    -                    .test(this)
    -                    .assertState(aViewState(
    -                            hasPassphrase = true,
    -                            step = SharedSecureStorageViewState.Step.EnterPassphrase
    -                    ))
    -                    .finish()
    -        }
    +    fun `given a key info with passphrase when initialising then step is EnterPassphrase`() = runTest {
    +        givenKey(KEY_INFO_WITH_PASSPHRASE)
    +        val viewModel = createViewModel()
    +        viewModel
    +                .test(this)
    +                .assertState(aViewState(
    +                        hasPassphrase = true,
    +                        step = SharedSecureStorageViewState.Step.EnterPassphrase
    +                ))
    +                .finish()
         }
     
         @Test
    -    fun `given a key info without passphrase when initialising then step is EnterKey`() {
    -        runBlockingTest {
    -            givenKey(KEY_INFO_WITHOUT_PASSPHRASE)
    +    fun `given a key info without passphrase when initialising then step is EnterKey`() = runTest {
    +        givenKey(KEY_INFO_WITHOUT_PASSPHRASE)
     
    -            val viewModel = createViewModel()
    +        val viewModel = createViewModel()
     
    -            viewModel
    -                    .test(this)
    -                    .assertState(aViewState(
    -                            hasPassphrase = false,
    -                            step = SharedSecureStorageViewState.Step.EnterKey
    -                    ))
    -                    .finish()
    -        }
    +        viewModel
    +                .test(this)
    +                .assertState(aViewState(
    +                        hasPassphrase = false,
    +                        step = SharedSecureStorageViewState.Step.EnterKey
    +                ))
    +                .finish()
         }
     
         @Test
    -    fun `given on EnterKey step when going back then dismisses`() {
    -        runBlockingTest {
    -            givenKey(KEY_INFO_WITHOUT_PASSPHRASE)
    +    fun `given on EnterKey step when going back then dismisses`() = runTest {
    +        givenKey(KEY_INFO_WITHOUT_PASSPHRASE)
     
    -            val viewModel = createViewModel()
    -            val test = viewModel.test(this)
    -            viewModel.handle(SharedSecureStorageAction.Back)
    -            test
    -                    .assertEvents(SharedSecureStorageViewEvent.Dismiss)
    -                    .finish()
    -        }
    +        val viewModel = createViewModel()
    +        val test = viewModel.test(this)
    +        viewModel.handle(SharedSecureStorageAction.Back)
    +        test
    +                .assertEvents(SharedSecureStorageViewEvent.Dismiss)
    +                .finish()
         }
     
         @Test
    -    fun `given on passphrase step when using key then step is EnterKey`() {
    -        runBlockingTest {
    -            givenKey(KEY_INFO_WITH_PASSPHRASE)
    -            val viewModel = createViewModel()
    -            val test = viewModel.test(this)
    +    fun `given on passphrase step when using key then step is EnterKey`() = runTest {
    +        givenKey(KEY_INFO_WITH_PASSPHRASE)
    +        val viewModel = createViewModel()
    +        val test = viewModel.test(this)
     
    -            viewModel.handle(SharedSecureStorageAction.UseKey)
    +        viewModel.handle(SharedSecureStorageAction.UseKey)
     
    -            test
    -                    .assertStates(
    -                            aViewState(
    -                                    hasPassphrase = true,
    -                                    step = SharedSecureStorageViewState.Step.EnterPassphrase
    -                            ),
    -                            aViewState(
    -                                    hasPassphrase = true,
    -                                    step = SharedSecureStorageViewState.Step.EnterKey
    -                            )
    -                    )
    -                    .finish()
    -        }
    +        test
    +                .assertStates(
    +                        aViewState(
    +                                hasPassphrase = true,
    +                                step = SharedSecureStorageViewState.Step.EnterPassphrase
    +                        ),
    +                        aViewState(
    +                                hasPassphrase = true,
    +                                step = SharedSecureStorageViewState.Step.EnterKey
    +                        )
    +                )
    +                .finish()
         }
     
         @Test
    -    fun `given a key info with passphrase and on EnterKey step when going back then step is EnterPassphrase`() {
    -        runBlockingTest {
    -            givenKey(KEY_INFO_WITH_PASSPHRASE)
    -            val viewModel = createViewModel()
    -            val test = viewModel.test(this)
    +    fun `given a key info with passphrase and on EnterKey step when going back then step is EnterPassphrase`() = runTest {
    +        givenKey(KEY_INFO_WITH_PASSPHRASE)
    +        val viewModel = createViewModel()
    +        val test = viewModel.test(this)
     
    -            viewModel.handle(SharedSecureStorageAction.UseKey)
    -            viewModel.handle(SharedSecureStorageAction.Back)
    +        viewModel.handle(SharedSecureStorageAction.UseKey)
    +        viewModel.handle(SharedSecureStorageAction.Back)
     
    -            test
    -                    .assertStates(
    -                            aViewState(
    -                                    hasPassphrase = true,
    -                                    step = SharedSecureStorageViewState.Step.EnterPassphrase
    -                            ),
    -                            aViewState(
    -                                    hasPassphrase = true,
    -                                    step = SharedSecureStorageViewState.Step.EnterKey
    -                            ),
    -                            aViewState(
    -                                    hasPassphrase = true,
    -                                    step = SharedSecureStorageViewState.Step.EnterPassphrase
    -                            )
    -                    )
    -                    .finish()
    -        }
    +        test
    +                .assertStates(
    +                        aViewState(
    +                                hasPassphrase = true,
    +                                step = SharedSecureStorageViewState.Step.EnterPassphrase
    +                        ),
    +                        aViewState(
    +                                hasPassphrase = true,
    +                                step = SharedSecureStorageViewState.Step.EnterKey
    +                        ),
    +                        aViewState(
    +                                hasPassphrase = true,
    +                                step = SharedSecureStorageViewState.Step.EnterPassphrase
    +                        )
    +                )
    +                .finish()
         }
     
         @Test
    -    fun `given on passphrase step when going back then dismisses`() {
    -        runBlockingTest {
    -            givenKey(KEY_INFO_WITH_PASSPHRASE)
    -            val viewModel = createViewModel()
    -            val test = viewModel.test(this)
    +    fun `given on passphrase step when going back then dismisses`() = runTest {
    +        givenKey(KEY_INFO_WITH_PASSPHRASE)
    +        val viewModel = createViewModel()
    +        val test = viewModel.test(this)
     
    -            viewModel.handle(SharedSecureStorageAction.Back)
    +        viewModel.handle(SharedSecureStorageAction.Back)
     
    -            test
    -                    .assertEvents(SharedSecureStorageViewEvent.Dismiss)
    -                    .finish()
    -        }
    +        test
    +                .assertEvents(SharedSecureStorageViewEvent.Dismiss)
    +                .finish()
         }
     
         private fun createViewModel(): SharedSecureStorageViewModel {
    diff --git a/vector/src/test/java/im/vector/app/features/location/domain/usecase/CompareLocationsUseCaseTest.kt b/vector/src/test/java/im/vector/app/features/location/domain/usecase/CompareLocationsUseCaseTest.kt
    index 015a27b0c8..7a80cbe87e 100644
    --- a/vector/src/test/java/im/vector/app/features/location/domain/usecase/CompareLocationsUseCaseTest.kt
    +++ b/vector/src/test/java/im/vector/app/features/location/domain/usecase/CompareLocationsUseCaseTest.kt
    @@ -21,7 +21,7 @@ import im.vector.app.features.location.LocationData
     import im.vector.app.test.fakes.FakeSession
     import io.mockk.MockKAnnotations
     import io.mockk.impl.annotations.OverrideMockKs
    -import kotlinx.coroutines.test.runBlockingTest
    +import kotlinx.coroutines.test.runTest
     import org.junit.Before
     import org.junit.Rule
     import org.junit.Test
    @@ -42,7 +42,7 @@ class CompareLocationsUseCaseTest {
         }
     
         @Test
    -    fun `given 2 very near locations when calling execute then these locations are considered as equal`() = runBlockingTest {
    +    fun `given 2 very near locations when calling execute then these locations are considered as equal`() = runTest {
             // Given
             val location1 = LocationData(
                     latitude = 48.858269,
    @@ -62,7 +62,7 @@ class CompareLocationsUseCaseTest {
         }
     
         @Test
    -    fun `given 2 far away locations when calling execute then these locations are considered as not equal`() = runBlockingTest {
    +    fun `given 2 far away locations when calling execute then these locations are considered as not equal`() = runTest {
             // Given
             val location1 = LocationData(
                     latitude = 48.858269,
    diff --git a/vector/src/test/java/im/vector/app/features/media/domain/usecase/DownloadMediaUseCaseTest.kt b/vector/src/test/java/im/vector/app/features/media/domain/usecase/DownloadMediaUseCaseTest.kt
    index 2fa8c7d5f7..bb05357cb2 100644
    --- a/vector/src/test/java/im/vector/app/features/media/domain/usecase/DownloadMediaUseCaseTest.kt
    +++ b/vector/src/test/java/im/vector/app/features/media/domain/usecase/DownloadMediaUseCaseTest.kt
    @@ -38,7 +38,7 @@ import io.mockk.runs
     import io.mockk.unmockkStatic
     import io.mockk.verify
     import io.mockk.verifyAll
    -import kotlinx.coroutines.test.runBlockingTest
    +import kotlinx.coroutines.test.runTest
     import org.junit.After
     import org.junit.Before
     import org.junit.Rule
    @@ -77,7 +77,7 @@ class DownloadMediaUseCaseTest {
         }
     
         @Test
    -    fun `given a file when calling execute then save the file in local with success`() = runBlockingTest {
    +    fun `given a file when calling execute then save the file in local with success`() = runTest {
             // Given
             val uri = mockk()
             val mimeType = "mimeType"
    @@ -105,7 +105,7 @@ class DownloadMediaUseCaseTest {
         }
     
         @Test
    -    fun `given a file when calling execute then save the file in local with error`() = runBlockingTest {
    +    fun `given a file when calling execute then save the file in local with error`() = runTest {
             // Given
             val uri = mockk()
             val mimeType = "mimeType"
    diff --git a/vector/src/test/java/im/vector/app/features/onboarding/OnboardingViewModelTest.kt b/vector/src/test/java/im/vector/app/features/onboarding/OnboardingViewModelTest.kt
    index f6c322af40..a9028f1147 100644
    --- a/vector/src/test/java/im/vector/app/features/onboarding/OnboardingViewModelTest.kt
    +++ b/vector/src/test/java/im/vector/app/features/onboarding/OnboardingViewModelTest.kt
    @@ -40,7 +40,7 @@ import im.vector.app.test.fakes.FakeVectorFeatures
     import im.vector.app.test.fakes.FakeVectorOverrides
     import im.vector.app.test.fixtures.aHomeServerCapabilities
     import im.vector.app.test.test
    -import kotlinx.coroutines.test.runBlockingTest
    +import kotlinx.coroutines.test.runTest
     import org.junit.Before
     import org.junit.Rule
     import org.junit.Test
    @@ -82,7 +82,7 @@ class OnboardingViewModelTest {
         }
     
         @Test
    -    fun `when handling PostViewEvent, then emits contents as view event`() = runBlockingTest {
    +    fun `when handling PostViewEvent, then emits contents as view event`() = runTest {
             val test = viewModel.test(this)
     
             viewModel.handle(OnboardingAction.PostViewEvent(OnboardingViewEvents.OnTakeMeHome))
    @@ -93,7 +93,7 @@ class OnboardingViewModelTest {
         }
     
         @Test
    -    fun `given supports changing display name, when handling PersonalizeProfile, then emits contents choose display name`() = runBlockingTest {
    +    fun `given supports changing display name, when handling PersonalizeProfile, then emits contents choose display name`() = runTest {
             val initialState = initialState.copy(personalizationState = PersonalizationState(supportsChangingDisplayName = true, supportsChangingProfilePicture = false))
             viewModel = createViewModel(initialState)
             val test = viewModel.test(this)
    @@ -106,7 +106,7 @@ class OnboardingViewModelTest {
         }
     
         @Test
    -    fun `given only supports changing profile picture, when handling PersonalizeProfile, then emits contents choose profile picture`() = runBlockingTest {
    +    fun `given only supports changing profile picture, when handling PersonalizeProfile, then emits contents choose profile picture`() = runTest {
             val initialState = initialState.copy(personalizationState = PersonalizationState(supportsChangingDisplayName = false, supportsChangingProfilePicture = true))
             viewModel = createViewModel(initialState)
             val test = viewModel.test(this)
    @@ -119,7 +119,7 @@ class OnboardingViewModelTest {
         }
     
         @Test
    -    fun `when handling SignUp then sets sign mode to sign up and starts registration`() = runBlockingTest {
    +    fun `when handling SignUp then sets sign mode to sign up and starts registration`() = runTest {
             givenRegistrationResultFor(RegisterAction.StartRegistration, ANY_CONTINUING_REGISTRATION_RESULT)
             val test = viewModel.test(this)
     
    @@ -137,7 +137,7 @@ class OnboardingViewModelTest {
         }
     
         @Test
    -    fun `given register action requires more steps, when handling action, then posts next steps`() = runBlockingTest {
    +    fun `given register action requires more steps, when handling action, then posts next steps`() = runTest {
             val test = viewModel.test(this)
             givenRegistrationResultFor(A_LOADABLE_REGISTER_ACTION, ANY_CONTINUING_REGISTRATION_RESULT)
     
    @@ -154,7 +154,7 @@ class OnboardingViewModelTest {
         }
     
         @Test
    -    fun `given register action is non loadable, when handling action, then posts next steps without loading`() = runBlockingTest {
    +    fun `given register action is non loadable, when handling action, then posts next steps without loading`() = runTest {
             val test = viewModel.test(this)
             givenRegistrationResultFor(A_NON_LOADABLE_REGISTER_ACTION, ANY_CONTINUING_REGISTRATION_RESULT)
     
    @@ -167,7 +167,7 @@ class OnboardingViewModelTest {
         }
     
         @Test
    -    fun `given register action ignores result, when handling action, then does nothing on success`() = runBlockingTest {
    +    fun `given register action ignores result, when handling action, then does nothing on success`() = runTest {
             val test = viewModel.test(this)
             givenRegistrationResultFor(A_RESULT_IGNORED_REGISTER_ACTION, RegistrationResult.FlowResponse(AN_IGNORED_FLOW_RESULT))
     
    @@ -184,7 +184,7 @@ class OnboardingViewModelTest {
         }
     
         @Test
    -    fun `when registering account, then updates state and emits account created event`() = runBlockingTest {
    +    fun `when registering account, then updates state and emits account created event`() = runTest {
             givenRegistrationResultFor(A_LOADABLE_REGISTER_ACTION, RegistrationResult.Success(fakeSession))
             givenSuccessfullyCreatesAccount(A_HOMESERVER_CAPABILITIES)
             val test = viewModel.test(this)
    @@ -203,7 +203,7 @@ class OnboardingViewModelTest {
         }
     
         @Test
    -    fun `given registration has started and has dummy step to do, when handling action, then ignores other steps and executes dummy`() = runBlockingTest {
    +    fun `given registration has started and has dummy step to do, when handling action, then ignores other steps and executes dummy`() = runTest {
             givenSuccessfulRegistrationForStartAndDummySteps(missingStages = listOf(Stage.Dummy(mandatory = true)))
             val test = viewModel.test(this)
     
    @@ -221,7 +221,7 @@ class OnboardingViewModelTest {
         }
     
         @Test
    -    fun `given changing profile picture is supported, when updating display name, then updates upstream user display name and moves to choose profile picture`() = runBlockingTest {
    +    fun `given changing profile picture is supported, when updating display name, then updates upstream user display name and moves to choose profile picture`() = runTest {
             val personalisedInitialState = initialState.copy(personalizationState = PersonalizationState(supportsChangingProfilePicture = true))
             viewModel = createViewModel(personalisedInitialState)
             val test = viewModel.test(this)
    @@ -236,7 +236,7 @@ class OnboardingViewModelTest {
         }
     
         @Test
    -    fun `given changing profile picture is not supported, when updating display name, then updates upstream user display name and completes personalization`() = runBlockingTest {
    +    fun `given changing profile picture is not supported, when updating display name, then updates upstream user display name and completes personalization`() = runTest {
             val personalisedInitialState = initialState.copy(personalizationState = PersonalizationState(supportsChangingProfilePicture = false))
             viewModel = createViewModel(personalisedInitialState)
             val test = viewModel.test(this)
    @@ -251,7 +251,7 @@ class OnboardingViewModelTest {
         }
     
         @Test
    -    fun `given upstream failure, when handling display name update, then emits failure event`() = runBlockingTest {
    +    fun `given upstream failure, when handling display name update, then emits failure event`() = runTest {
             val test = viewModel.test(this)
             fakeSession.fakeProfileService.givenSetDisplayNameErrors(AN_ERROR)
     
    @@ -268,7 +268,7 @@ class OnboardingViewModelTest {
         }
     
         @Test
    -    fun `when handling profile picture selected, then updates selected picture state`() = runBlockingTest {
    +    fun `when handling profile picture selected, then updates selected picture state`() = runTest {
             val test = viewModel.test(this)
     
             viewModel.handle(OnboardingAction.ProfilePictureSelected(fakeUri.instance))
    @@ -283,7 +283,7 @@ class OnboardingViewModelTest {
         }
     
         @Test
    -    fun `given a selected picture, when handling save selected profile picture, then updates upstream avatar and completes personalization`() = runBlockingTest {
    +    fun `given a selected picture, when handling save selected profile picture, then updates upstream avatar and completes personalization`() = runTest {
             val initialStateWithPicture = givenPictureSelected(fakeUri.instance, A_PICTURE_FILENAME)
             viewModel = createViewModel(initialStateWithPicture)
             val test = viewModel.test(this)
    @@ -298,7 +298,7 @@ class OnboardingViewModelTest {
         }
     
         @Test
    -    fun `given upstream update avatar fails, when saving selected profile picture, then emits failure event`() = runBlockingTest {
    +    fun `given upstream update avatar fails, when saving selected profile picture, then emits failure event`() = runTest {
             fakeSession.fakeProfileService.givenUpdateAvatarErrors(AN_ERROR)
             val initialStateWithPicture = givenPictureSelected(fakeUri.instance, A_PICTURE_FILENAME)
             viewModel = createViewModel(initialStateWithPicture)
    @@ -313,7 +313,7 @@ class OnboardingViewModelTest {
         }
     
         @Test
    -    fun `given no selected picture, when saving selected profile picture, then emits failure event`() = runBlockingTest {
    +    fun `given no selected picture, when saving selected profile picture, then emits failure event`() = runTest {
             val test = viewModel.test(this)
     
             viewModel.handle(OnboardingAction.SaveSelectedProfilePicture)
    @@ -325,7 +325,7 @@ class OnboardingViewModelTest {
         }
     
         @Test
    -    fun `when handling profile skipped, then completes personalization`() = runBlockingTest {
    +    fun `when handling profile skipped, then completes personalization`() = runTest {
             val test = viewModel.test(this)
     
             viewModel.handle(OnboardingAction.UpdateProfilePictureSkipped)
    diff --git a/vector/src/test/java/im/vector/app/features/onboarding/RegistrationActionHandlerTest.kt b/vector/src/test/java/im/vector/app/features/onboarding/RegistrationActionHandlerTest.kt
    index 2ca9aaef07..a7fa2a6331 100644
    --- a/vector/src/test/java/im/vector/app/features/onboarding/RegistrationActionHandlerTest.kt
    +++ b/vector/src/test/java/im/vector/app/features/onboarding/RegistrationActionHandlerTest.kt
    @@ -19,7 +19,7 @@ package im.vector.app.features.onboarding
     import im.vector.app.test.fakes.FakeRegistrationWizard
     import im.vector.app.test.fakes.FakeSession
     import io.mockk.coVerifyAll
    -import kotlinx.coroutines.test.runBlockingTest
    +import kotlinx.coroutines.test.runTest
     import org.amshove.kluent.shouldBeEqualTo
     import org.junit.Test
     import org.matrix.android.sdk.api.auth.registration.RegisterThreePid
    @@ -39,7 +39,7 @@ private val A_PID_TO_REGISTER = RegisterThreePid.Email("an email")
     class RegistrationActionHandlerTest {
     
         @Test
    -    fun `when handling register action then delegates to wizard`() = runBlockingTest {
    +    fun `when handling register action then delegates to wizard`() = runTest {
             val cases = listOf(
                     case(RegisterAction.StartRegistration) { getRegistrationFlow() },
                     case(RegisterAction.CaptchaDone(A_CAPTCHA_RESPONSE)) { performReCaptcha(A_CAPTCHA_RESPONSE) },
    
    From 012cdf4b4d588aff1e92724305ffc47a29f50091 Mon Sep 17 00:00:00 2001
    From: Benoit Marty 
    Date: Tue, 22 Mar 2022 16:52:18 +0100
    Subject: [PATCH 058/262] runBlocking -> runTest
     https://github.com/Kotlin/kotlinx.coroutines/blob/master/kotlinx-coroutines-test/MIGRATION.md
    
    ---
     .../session/pushers/DefaultAddPusherTaskTest.kt      | 12 +++++++-----
     .../sdk/internal/task/CoroutineSequencersTest.kt     |  8 ++++----
     .../app/features/crypto/keys/KeysExporterTest.kt     | 10 +++++-----
     3 files changed, 16 insertions(+), 14 deletions(-)
    
    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/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/vector/src/test/java/im/vector/app/features/crypto/keys/KeysExporterTest.kt b/vector/src/test/java/im/vector/app/features/crypto/keys/KeysExporterTest.kt
    index 57ad2a52ab..5d0317592d 100644
    --- a/vector/src/test/java/im/vector/app/features/crypto/keys/KeysExporterTest.kt
    +++ b/vector/src/test/java/im/vector/app/features/crypto/keys/KeysExporterTest.kt
    @@ -26,7 +26,7 @@ import io.mockk.every
     import io.mockk.mockk
     import io.mockk.verify
     import kotlinx.coroutines.Dispatchers
    -import kotlinx.coroutines.runBlocking
    +import kotlinx.coroutines.test.runTest
     import org.amshove.kluent.internal.assertFailsWith
     import org.junit.Before
     import org.junit.Test
    @@ -55,7 +55,7 @@ class KeysExporterTest {
             givenFileDescriptorWithSize(size = A_ROOM_KEYS_EXPORT.size.toLong())
             val outputStream = context.givenOutputStreamFor(A_URI)
     
    -        runBlocking { keysExporter.export(A_PASSWORD, A_URI) }
    +        runTest { keysExporter.export(A_PASSWORD, A_URI) }
     
             verify { outputStream.write(A_ROOM_KEYS_EXPORT) }
         }
    @@ -66,7 +66,7 @@ class KeysExporterTest {
             context.givenOutputStreamFor(A_URI)
     
             assertFailsWith {
    -            runBlocking { keysExporter.export(A_PASSWORD, A_URI) }
    +            runTest { keysExporter.export(A_PASSWORD, A_URI) }
             }
         }
     
    @@ -75,7 +75,7 @@ class KeysExporterTest {
             context.givenMissingOutputStreamFor(A_URI)
     
             assertFailsWith(message = "Unable to open file for writing") {
    -            runBlocking { keysExporter.export(A_PASSWORD, A_URI) }
    +            runTest { keysExporter.export(A_PASSWORD, A_URI) }
             }
         }
     
    @@ -85,7 +85,7 @@ class KeysExporterTest {
             context.givenOutputStreamFor(A_URI)
     
             assertFailsWith(message = "Exported file not found") {
    -            runBlocking { keysExporter.export(A_PASSWORD, A_URI) }
    +            runTest { keysExporter.export(A_PASSWORD, A_URI) }
             }
         }
     
    
    From b1c4ca7816124a22f4c896fed9d91d2435a9fcd4 Mon Sep 17 00:00:00 2001
    From: ariskotsomitopoulos 
    Date: Tue, 22 Mar 2022 18:03:49 +0200
    Subject: [PATCH 059/262] Simplify thread timeline toolbar menu more
    
    ---
     .../home/room/detail/TimelineViewModel.kt     |  6 ++-
     vector/src/main/res/menu/menu_timeline.xml    | 41 +++++++------------
     2 files changed, 19 insertions(+), 28 deletions(-)
    
    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..cba11b4ed7 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
    @@ -706,8 +706,10 @@ class TimelineViewModel @AssistedInject constructor(
     
             if (initialState.isThreadTimeline()) {
                 when (itemId) {
    -                R.id.menu_thread_timeline_more -> true
    -                else                           -> false
    +                R.id.menu_thread_timeline_view_in_room,
    +                R.id.menu_thread_timeline_copy_link,
    +                R.id.menu_thread_timeline_share -> true
    +                else                            -> false
                 }
             } else {
                 when (itemId) {
    diff --git a/vector/src/main/res/menu/menu_timeline.xml b/vector/src/main/res/menu/menu_timeline.xml
    index 962c505e4e..d035ce38eb 100644
    --- a/vector/src/main/res/menu/menu_timeline.xml
    +++ b/vector/src/main/res/menu/menu_timeline.xml
    @@ -66,35 +66,24 @@
             tools:visible="true" />
     
         
    +        app:showAsAction="never" />
     
    -        
    -            
    -
    -            
    -
    -            
    -        
    -    
    +    
     
    +    
     
     
    \ No newline at end of file
    
    From f8e7ba7355e70b2448ae38e3c9d5125def07a065 Mon Sep 17 00:00:00 2001
    From: ariskotsomitopoulos 
    Date: Tue, 22 Mar 2022 18:43:01 +0200
    Subject: [PATCH 060/262] Format menu_timeline
    
    ---
     .../features/home/room/detail/TimelineFragment.kt    |  5 +++++
     vector/src/main/res/menu/menu_timeline.xml           | 12 ++++++------
     2 files changed, 11 insertions(+), 6 deletions(-)
    
    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 b66897d8d1..3c271710dd 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
    @@ -40,6 +40,7 @@ import android.widget.TextView
     import android.widget.Toast
     import androidx.annotation.DrawableRes
     import androidx.annotation.StringRes
    +import androidx.appcompat.view.menu.MenuBuilder
     import androidx.core.content.ContextCompat
     import androidx.core.graphics.drawable.DrawableCompat
     import androidx.core.net.toUri
    @@ -982,7 +983,11 @@ class TimelineFragment @Inject constructor(
             }
         }
     
    +    @SuppressLint("RestrictedApi")
         override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
    +        if (isThreadTimeLine()) {
    +            if (menu is MenuBuilder) menu.setOptionalIconsVisible(true)
    +        }
             super.onCreateOptionsMenu(menu, inflater)
             // We use a custom layout for this menu item, so we need to set a ClickListener
             menu.findItem(R.id.open_matrix_apps)?.let { menuItem ->
    diff --git a/vector/src/main/res/menu/menu_timeline.xml b/vector/src/main/res/menu/menu_timeline.xml
    index d035ce38eb..e362ec7483 100644
    --- a/vector/src/main/res/menu/menu_timeline.xml
    +++ b/vector/src/main/res/menu/menu_timeline.xml
    @@ -41,12 +41,13 @@
             android:id="@+id/menu_timeline_thread_list"
             android:title="@string/action_view_threads"
             android:visible="false"
    -        app:iconTint="?colorPrimary"
             app:actionLayout="@layout/view_thread_notification_badge"
    +        app:iconTint="?colorPrimary"
             app:showAsAction="always"
             tools:visible="true" />
     
    -    
    @@ -70,20 +71,19 @@
             android:icon="@drawable/ic_thread_view_in_room_menu_item"
             android:title="@string/action_thread_view_in_room"
             app:iconTint="?vctr_content_secondary"
    -        app:showAsAction="never" />
    +        app:showAsAction="withText" />
     
         
    +        app:showAsAction="withText" />
     
         
    -
    +        app:showAsAction="withText" />
     
    \ No newline at end of file
    
    From 72bc613f347c4c6fe13b839725f6b5870b261705 Mon Sep 17 00:00:00 2001
    From: ariskotsomitopoulos 
    Date: Tue, 22 Mar 2022 18:54:59 +0200
    Subject: [PATCH 061/262] Reduce thread toolbar avatar size
    
    ---
     .../src/main/res/layout/view_room_detail_thread_toolbar.xml   | 4 ++--
     1 file changed, 2 insertions(+), 2 deletions(-)
    
    diff --git a/vector/src/main/res/layout/view_room_detail_thread_toolbar.xml b/vector/src/main/res/layout/view_room_detail_thread_toolbar.xml
    index e16912246e..c58a8cd837 100644
    --- a/vector/src/main/res/layout/view_room_detail_thread_toolbar.xml
    +++ b/vector/src/main/res/layout/view_room_detail_thread_toolbar.xml
    @@ -26,8 +26,8 @@
     
         
    Date: Tue, 22 Mar 2022 17:56:07 +0100
    Subject: [PATCH 062/262] Renames call option to be more agnostic
    
    ---
     .../app/features/call/conference/RemoveJitsiWidgetView.kt   | 2 +-
     .../app/features/home/room/detail/RoomDetailViewState.kt    | 2 +-
     .../app/features/home/room/detail/TimelineViewModel.kt      | 6 +++---
     vector/src/main/res/menu/menu_timeline.xml                  | 6 +++---
     4 files changed, 8 insertions(+), 8 deletions(-)
    
    diff --git a/vector/src/main/java/im/vector/app/features/call/conference/RemoveJitsiWidgetView.kt b/vector/src/main/java/im/vector/app/features/call/conference/RemoveJitsiWidgetView.kt
    index fd7fc31e6d..e7659fb3e6 100644
    --- a/vector/src/main/java/im/vector/app/features/call/conference/RemoveJitsiWidgetView.kt
    +++ b/vector/src/main/java/im/vector/app/features/call/conference/RemoveJitsiWidgetView.kt
    @@ -88,7 +88,7 @@ import org.matrix.android.sdk.api.session.room.model.Membership
         fun render(roomDetailViewState: RoomDetailViewState) {
             val summary = roomDetailViewState.asyncRoomSummary()
             val newState = if (summary?.membership != Membership.JOIN ||
    -                roomDetailViewState.isWebRTCCallOptionAvailable() ||
    +                roomDetailViewState.isCallOptionAvailable() ||
                     !roomDetailViewState.isAllowedToManageWidgets ||
                     roomDetailViewState.jitsiState.widgetId == null) {
                 State.Unmount
    diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewState.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewState.kt
    index 84e618a8fe..09b5211246 100644
    --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewState.kt
    +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewState.kt
    @@ -87,7 +87,7 @@ data class RoomDetailViewState(
                 rootThreadEventId = args.threadTimelineArgs?.rootThreadEventId
         )
     
    -    fun isWebRTCCallOptionAvailable() = asyncRoomSummary.invoke()?.isDirect ?: true
    +    fun isCallOptionAvailable() = asyncRoomSummary.invoke()?.isDirect ?: true
     
         fun isSearchAvailable() = asyncRoomSummary()?.isEncrypted == false
     
    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..a726aea61f 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
    @@ -714,10 +714,10 @@ class TimelineViewModel @AssistedInject constructor(
                     R.id.timeline_setting          -> true
                     R.id.invite                    -> state.canInvite
                     R.id.open_matrix_apps          -> true
    -                R.id.voice_call                -> state.isWebRTCCallOptionAvailable()
    -                R.id.video_call                -> state.isWebRTCCallOptionAvailable() || state.jitsiState.confId == null || state.jitsiState.hasJoined
    +                R.id.voice_call                -> state.isCallOptionAvailable()
    +                R.id.video_call                -> state.isCallOptionAvailable() || state.jitsiState.confId == null || state.jitsiState.hasJoined
                     // Show Join conference button only if there is an active conf id not joined. Otherwise fallback to default video disabled. ^
    -                R.id.join_conference           -> !state.isWebRTCCallOptionAvailable() && state.jitsiState.confId != null && !state.jitsiState.hasJoined
    +                R.id.join_conference           -> !state.isCallOptionAvailable() && state.jitsiState.confId != null && !state.jitsiState.hasJoined
                     R.id.search                    -> state.isSearchAvailable()
                     R.id.menu_timeline_thread_list -> vectorPreferences.areThreadMessagesEnabled()
                     R.id.dev_tools                 -> vectorPreferences.developerMode()
    diff --git a/vector/src/main/res/menu/menu_timeline.xml b/vector/src/main/res/menu/menu_timeline.xml
    index 962c505e4e..98d1beece3 100644
    --- a/vector/src/main/res/menu/menu_timeline.xml
    +++ b/vector/src/main/res/menu/menu_timeline.xml
    @@ -25,7 +25,7 @@
             android:title="@string/action_video_call"
             android:visible="false"
             app:iconTint="?colorPrimary"
    -        app:showAsAction="always"
    +        app:showAsAction="ifRoom"
             tools:visible="true" />
     
         
     
         
     
         
    Date: Tue, 22 Mar 2022 17:01:34 +0000
    Subject: [PATCH 063/262] fixing view model tests not collecting flow results -
     the switch from runBlockingTest to runTest means we need to provide a
     separate scope from the test in order to asynchronously collect the flow
     results
    
    ---
     .../quads/SharedSecureStorageViewModelTest.kt | 12 +++----
     .../onboarding/OnboardingViewModelTest.kt     | 34 +++++++++----------
     .../java/im/vector/app/test/Extensions.kt     |  8 +++--
     3 files changed, 28 insertions(+), 26 deletions(-)
    
    diff --git a/vector/src/test/java/im/vector/app/features/crypto/quads/SharedSecureStorageViewModelTest.kt b/vector/src/test/java/im/vector/app/features/crypto/quads/SharedSecureStorageViewModelTest.kt
    index cd80be0801..7562dfdf14 100644
    --- a/vector/src/test/java/im/vector/app/features/crypto/quads/SharedSecureStorageViewModelTest.kt
    +++ b/vector/src/test/java/im/vector/app/features/crypto/quads/SharedSecureStorageViewModelTest.kt
    @@ -51,7 +51,7 @@ class SharedSecureStorageViewModelTest {
             givenKey(KEY_INFO_WITH_PASSPHRASE)
             val viewModel = createViewModel()
             viewModel
    -                .test(this)
    +                .test()
                     .assertState(aViewState(
                             hasPassphrase = true,
                             step = SharedSecureStorageViewState.Step.EnterPassphrase
    @@ -66,7 +66,7 @@ class SharedSecureStorageViewModelTest {
             val viewModel = createViewModel()
     
             viewModel
    -                .test(this)
    +                .test()
                     .assertState(aViewState(
                             hasPassphrase = false,
                             step = SharedSecureStorageViewState.Step.EnterKey
    @@ -79,7 +79,7 @@ class SharedSecureStorageViewModelTest {
             givenKey(KEY_INFO_WITHOUT_PASSPHRASE)
     
             val viewModel = createViewModel()
    -        val test = viewModel.test(this)
    +        val test = viewModel.test()
             viewModel.handle(SharedSecureStorageAction.Back)
             test
                     .assertEvents(SharedSecureStorageViewEvent.Dismiss)
    @@ -90,7 +90,7 @@ class SharedSecureStorageViewModelTest {
         fun `given on passphrase step when using key then step is EnterKey`() = runTest {
             givenKey(KEY_INFO_WITH_PASSPHRASE)
             val viewModel = createViewModel()
    -        val test = viewModel.test(this)
    +        val test = viewModel.test()
     
             viewModel.handle(SharedSecureStorageAction.UseKey)
     
    @@ -112,7 +112,7 @@ class SharedSecureStorageViewModelTest {
         fun `given a key info with passphrase and on EnterKey step when going back then step is EnterPassphrase`() = runTest {
             givenKey(KEY_INFO_WITH_PASSPHRASE)
             val viewModel = createViewModel()
    -        val test = viewModel.test(this)
    +        val test = viewModel.test()
     
             viewModel.handle(SharedSecureStorageAction.UseKey)
             viewModel.handle(SharedSecureStorageAction.Back)
    @@ -139,7 +139,7 @@ class SharedSecureStorageViewModelTest {
         fun `given on passphrase step when going back then dismisses`() = runTest {
             givenKey(KEY_INFO_WITH_PASSPHRASE)
             val viewModel = createViewModel()
    -        val test = viewModel.test(this)
    +        val test = viewModel.test()
     
             viewModel.handle(SharedSecureStorageAction.Back)
     
    diff --git a/vector/src/test/java/im/vector/app/features/onboarding/OnboardingViewModelTest.kt b/vector/src/test/java/im/vector/app/features/onboarding/OnboardingViewModelTest.kt
    index a9028f1147..4fd079611d 100644
    --- a/vector/src/test/java/im/vector/app/features/onboarding/OnboardingViewModelTest.kt
    +++ b/vector/src/test/java/im/vector/app/features/onboarding/OnboardingViewModelTest.kt
    @@ -83,7 +83,7 @@ class OnboardingViewModelTest {
     
         @Test
         fun `when handling PostViewEvent, then emits contents as view event`() = runTest {
    -        val test = viewModel.test(this)
    +        val test = viewModel.test()
     
             viewModel.handle(OnboardingAction.PostViewEvent(OnboardingViewEvents.OnTakeMeHome))
     
    @@ -96,7 +96,7 @@ class OnboardingViewModelTest {
         fun `given supports changing display name, when handling PersonalizeProfile, then emits contents choose display name`() = runTest {
             val initialState = initialState.copy(personalizationState = PersonalizationState(supportsChangingDisplayName = true, supportsChangingProfilePicture = false))
             viewModel = createViewModel(initialState)
    -        val test = viewModel.test(this)
    +        val test = viewModel.test()
     
             viewModel.handle(OnboardingAction.PersonalizeProfile)
     
    @@ -109,7 +109,7 @@ class OnboardingViewModelTest {
         fun `given only supports changing profile picture, when handling PersonalizeProfile, then emits contents choose profile picture`() = runTest {
             val initialState = initialState.copy(personalizationState = PersonalizationState(supportsChangingDisplayName = false, supportsChangingProfilePicture = true))
             viewModel = createViewModel(initialState)
    -        val test = viewModel.test(this)
    +        val test = viewModel.test()
     
             viewModel.handle(OnboardingAction.PersonalizeProfile)
     
    @@ -121,7 +121,7 @@ class OnboardingViewModelTest {
         @Test
         fun `when handling SignUp then sets sign mode to sign up and starts registration`() = runTest {
             givenRegistrationResultFor(RegisterAction.StartRegistration, ANY_CONTINUING_REGISTRATION_RESULT)
    -        val test = viewModel.test(this)
    +        val test = viewModel.test()
     
             viewModel.handle(OnboardingAction.UpdateSignMode(SignMode.SignUp))
     
    @@ -138,7 +138,7 @@ class OnboardingViewModelTest {
     
         @Test
         fun `given register action requires more steps, when handling action, then posts next steps`() = runTest {
    -        val test = viewModel.test(this)
    +        val test = viewModel.test()
             givenRegistrationResultFor(A_LOADABLE_REGISTER_ACTION, ANY_CONTINUING_REGISTRATION_RESULT)
     
             viewModel.handle(OnboardingAction.PostRegisterAction(A_LOADABLE_REGISTER_ACTION))
    @@ -155,7 +155,7 @@ class OnboardingViewModelTest {
     
         @Test
         fun `given register action is non loadable, when handling action, then posts next steps without loading`() = runTest {
    -        val test = viewModel.test(this)
    +        val test = viewModel.test()
             givenRegistrationResultFor(A_NON_LOADABLE_REGISTER_ACTION, ANY_CONTINUING_REGISTRATION_RESULT)
     
             viewModel.handle(OnboardingAction.PostRegisterAction(A_NON_LOADABLE_REGISTER_ACTION))
    @@ -168,7 +168,7 @@ class OnboardingViewModelTest {
     
         @Test
         fun `given register action ignores result, when handling action, then does nothing on success`() = runTest {
    -        val test = viewModel.test(this)
    +        val test = viewModel.test()
             givenRegistrationResultFor(A_RESULT_IGNORED_REGISTER_ACTION, RegistrationResult.FlowResponse(AN_IGNORED_FLOW_RESULT))
     
             viewModel.handle(OnboardingAction.PostRegisterAction(A_RESULT_IGNORED_REGISTER_ACTION))
    @@ -187,7 +187,7 @@ class OnboardingViewModelTest {
         fun `when registering account, then updates state and emits account created event`() = runTest {
             givenRegistrationResultFor(A_LOADABLE_REGISTER_ACTION, RegistrationResult.Success(fakeSession))
             givenSuccessfullyCreatesAccount(A_HOMESERVER_CAPABILITIES)
    -        val test = viewModel.test(this)
    +        val test = viewModel.test()
     
             viewModel.handle(OnboardingAction.PostRegisterAction(A_LOADABLE_REGISTER_ACTION))
     
    @@ -205,7 +205,7 @@ class OnboardingViewModelTest {
         @Test
         fun `given registration has started and has dummy step to do, when handling action, then ignores other steps and executes dummy`() = runTest {
             givenSuccessfulRegistrationForStartAndDummySteps(missingStages = listOf(Stage.Dummy(mandatory = true)))
    -        val test = viewModel.test(this)
    +        val test = viewModel.test()
     
             viewModel.handle(OnboardingAction.PostRegisterAction(A_LOADABLE_REGISTER_ACTION))
     
    @@ -224,7 +224,7 @@ class OnboardingViewModelTest {
         fun `given changing profile picture is supported, when updating display name, then updates upstream user display name and moves to choose profile picture`() = runTest {
             val personalisedInitialState = initialState.copy(personalizationState = PersonalizationState(supportsChangingProfilePicture = true))
             viewModel = createViewModel(personalisedInitialState)
    -        val test = viewModel.test(this)
    +        val test = viewModel.test()
     
             viewModel.handle(OnboardingAction.UpdateDisplayName(A_DISPLAY_NAME))
     
    @@ -239,7 +239,7 @@ class OnboardingViewModelTest {
         fun `given changing profile picture is not supported, when updating display name, then updates upstream user display name and completes personalization`() = runTest {
             val personalisedInitialState = initialState.copy(personalizationState = PersonalizationState(supportsChangingProfilePicture = false))
             viewModel = createViewModel(personalisedInitialState)
    -        val test = viewModel.test(this)
    +        val test = viewModel.test()
     
             viewModel.handle(OnboardingAction.UpdateDisplayName(A_DISPLAY_NAME))
     
    @@ -252,7 +252,7 @@ class OnboardingViewModelTest {
     
         @Test
         fun `given upstream failure, when handling display name update, then emits failure event`() = runTest {
    -        val test = viewModel.test(this)
    +        val test = viewModel.test()
             fakeSession.fakeProfileService.givenSetDisplayNameErrors(AN_ERROR)
     
             viewModel.handle(OnboardingAction.UpdateDisplayName(A_DISPLAY_NAME))
    @@ -269,7 +269,7 @@ class OnboardingViewModelTest {
     
         @Test
         fun `when handling profile picture selected, then updates selected picture state`() = runTest {
    -        val test = viewModel.test(this)
    +        val test = viewModel.test()
     
             viewModel.handle(OnboardingAction.ProfilePictureSelected(fakeUri.instance))
     
    @@ -286,7 +286,7 @@ class OnboardingViewModelTest {
         fun `given a selected picture, when handling save selected profile picture, then updates upstream avatar and completes personalization`() = runTest {
             val initialStateWithPicture = givenPictureSelected(fakeUri.instance, A_PICTURE_FILENAME)
             viewModel = createViewModel(initialStateWithPicture)
    -        val test = viewModel.test(this)
    +        val test = viewModel.test()
     
             viewModel.handle(OnboardingAction.SaveSelectedProfilePicture)
     
    @@ -302,7 +302,7 @@ class OnboardingViewModelTest {
             fakeSession.fakeProfileService.givenUpdateAvatarErrors(AN_ERROR)
             val initialStateWithPicture = givenPictureSelected(fakeUri.instance, A_PICTURE_FILENAME)
             viewModel = createViewModel(initialStateWithPicture)
    -        val test = viewModel.test(this)
    +        val test = viewModel.test()
     
             viewModel.handle(OnboardingAction.SaveSelectedProfilePicture)
     
    @@ -314,7 +314,7 @@ class OnboardingViewModelTest {
     
         @Test
         fun `given no selected picture, when saving selected profile picture, then emits failure event`() = runTest {
    -        val test = viewModel.test(this)
    +        val test = viewModel.test()
     
             viewModel.handle(OnboardingAction.SaveSelectedProfilePicture)
     
    @@ -326,7 +326,7 @@ class OnboardingViewModelTest {
     
         @Test
         fun `when handling profile skipped, then completes personalization`() = runTest {
    -        val test = viewModel.test(this)
    +        val test = viewModel.test()
     
             viewModel.handle(OnboardingAction.UpdateProfilePictureSkipped)
     
    diff --git a/vector/src/test/java/im/vector/app/test/Extensions.kt b/vector/src/test/java/im/vector/app/test/Extensions.kt
    index 67eff7ca11..b9521298e2 100644
    --- a/vector/src/test/java/im/vector/app/test/Extensions.kt
    +++ b/vector/src/test/java/im/vector/app/test/Extensions.kt
    @@ -21,12 +21,14 @@ import im.vector.app.core.platform.VectorViewEvents
     import im.vector.app.core.platform.VectorViewModel
     import im.vector.app.core.platform.VectorViewModelAction
     import kotlinx.coroutines.CoroutineScope
    +import kotlinx.coroutines.Dispatchers
     
     fun String.trimIndentOneLine() = trimIndent().replace("\n", "")
     
    -fun  VectorViewModel.test(coroutineScope: CoroutineScope): ViewModelTest {
    -    val state = stateFlow.test(coroutineScope)
    -    val viewEvents = viewEvents.stream().test(coroutineScope)
    +fun  VectorViewModel.test(): ViewModelTest {
    +    val testResultCollectingScope = CoroutineScope(Dispatchers.Unconfined)
    +    val state = stateFlow.test(testResultCollectingScope)
    +    val viewEvents = viewEvents.stream().test(testResultCollectingScope)
         return ViewModelTest(state, viewEvents)
     }
     
    
    From 040180478d79e54d22802a6dfe268dafbec6dc31 Mon Sep 17 00:00:00 2001
    From: waclaw66 
    Date: Tue, 22 Mar 2022 16:42:45 +0000
    Subject: [PATCH 064/262] Translated using Weblate (Czech)
    
    Currently translated at 100.0% (52 of 52 strings)
    
    Translation: Element Android/Element Android Store
    Translate-URL: https://translate.element.io/projects/element-android/element-store/cs/
    ---
     fastlane/metadata/android/cs-CZ/changelogs/40104040.txt | 2 ++
     1 file changed, 2 insertions(+)
     create mode 100644 fastlane/metadata/android/cs-CZ/changelogs/40104040.txt
    
    diff --git a/fastlane/metadata/android/cs-CZ/changelogs/40104040.txt b/fastlane/metadata/android/cs-CZ/changelogs/40104040.txt
    new file mode 100644
    index 0000000000..38a29f5a5d
    --- /dev/null
    +++ b/fastlane/metadata/android/cs-CZ/changelogs/40104040.txt
    @@ -0,0 +1,2 @@
    +Hlavní změny v této verzi: vylepšení indikátoru psaní. Opravy různých chyb a vylepšení stability.
    +Úplný seznam změn: https://github.com/vector-im/element-android/releases/tag/v1.4.4
    
    From 41de155fb586b9d6c74c8b7acbfcb6cec7530cd8 Mon Sep 17 00:00:00 2001
    From: libexus 
    Date: Tue, 22 Mar 2022 17:03:18 +0000
    Subject: [PATCH 065/262] Translated using Weblate (German)
    
    Currently translated at 100.0% (52 of 52 strings)
    
    Translation: Element Android/Element Android Store
    Translate-URL: https://translate.element.io/projects/element-android/element-store/de/
    ---
     fastlane/metadata/android/de-DE/changelogs/40104040.txt | 2 ++
     1 file changed, 2 insertions(+)
     create mode 100644 fastlane/metadata/android/de-DE/changelogs/40104040.txt
    
    diff --git a/fastlane/metadata/android/de-DE/changelogs/40104040.txt b/fastlane/metadata/android/de-DE/changelogs/40104040.txt
    new file mode 100644
    index 0000000000..8813f7f3c3
    --- /dev/null
    +++ b/fastlane/metadata/android/de-DE/changelogs/40104040.txt
    @@ -0,0 +1,2 @@
    +Neuer Tippindikator und Bugfixes
    +Alle Änderungen: https://github.com/vector-im/element-android/releases/tag/v1.4.4
    
    From 720d830f5fc24caa2f30e1fdc058b5576e553cfa Mon Sep 17 00:00:00 2001
    From: lvre <7uu3qrbvm@relay.firefox.com>
    Date: Tue, 22 Mar 2022 17:26:01 +0000
    Subject: [PATCH 066/262] Translated using Weblate (Portuguese (Brazil))
    
    Currently translated at 100.0% (52 of 52 strings)
    
    Translation: Element Android/Element Android Store
    Translate-URL: https://translate.element.io/projects/element-android/element-store/pt_BR/
    ---
     fastlane/metadata/android/pt-BR/changelogs/40104040.txt | 2 ++
     1 file changed, 2 insertions(+)
     create mode 100644 fastlane/metadata/android/pt-BR/changelogs/40104040.txt
    
    diff --git a/fastlane/metadata/android/pt-BR/changelogs/40104040.txt b/fastlane/metadata/android/pt-BR/changelogs/40104040.txt
    new file mode 100644
    index 0000000000..87a61ec102
    --- /dev/null
    +++ b/fastlane/metadata/android/pt-BR/changelogs/40104040.txt
    @@ -0,0 +1,2 @@
    +Principais mudanças nesta versão: atualizações de UI de indicador de digitação. Vários consertos de bugs e melhorias de estabilidade.
    +Changelog completo: https://github.com/vector-im/element-android/releases/tag/v1.4.4
    
    From 17d4e6c74502111c8e638276f4c9ed3daac493d9 Mon Sep 17 00:00:00 2001
    From: Denys Nykula 
    Date: Tue, 22 Mar 2022 16:39:03 +0000
    Subject: [PATCH 067/262] Translated using Weblate (Ukrainian)
    
    Currently translated at 100.0% (52 of 52 strings)
    
    Translation: Element Android/Element Android Store
    Translate-URL: https://translate.element.io/projects/element-android/element-store/uk/
    ---
     fastlane/metadata/android/uk/changelogs/40104040.txt | 2 ++
     1 file changed, 2 insertions(+)
     create mode 100644 fastlane/metadata/android/uk/changelogs/40104040.txt
    
    diff --git a/fastlane/metadata/android/uk/changelogs/40104040.txt b/fastlane/metadata/android/uk/changelogs/40104040.txt
    new file mode 100644
    index 0000000000..313eb56045
    --- /dev/null
    +++ b/fastlane/metadata/android/uk/changelogs/40104040.txt
    @@ -0,0 +1,2 @@
    +Основні зміни в цій версії: оновлено індикатор набору. Виправлено різні вади й удосконалено стабільність.
    +Вичерпний журнал змін: https://github.com/vector-im/element-android/releases/tag/v1.4.4
    
    From a8969b6dfa0617309c11d917aa0732fcbad49889 Mon Sep 17 00:00:00 2001
    From: Denys Nykula 
    Date: Tue, 22 Mar 2022 20:25:23 +0000
    Subject: [PATCH 068/262] Translated using Weblate (Ukrainian)
    
    Currently translated at 100.0% (2171 of 2171 strings)
    
    Translation: Element Android/Element Android App
    Translate-URL: https://translate.element.io/projects/element-android/element-app/uk/
    ---
     vector/src/main/res/values-uk/strings.xml | 23 +++++++++++++++++++++--
     1 file changed, 21 insertions(+), 2 deletions(-)
    
    diff --git a/vector/src/main/res/values-uk/strings.xml b/vector/src/main/res/values-uk/strings.xml
    index 2f34fc66fb..9e46fdebce 100644
    --- a/vector/src/main/res/values-uk/strings.xml
    +++ b/vector/src/main/res/values-uk/strings.xml
    @@ -424,7 +424,7 @@
             %d змін членства
             
         
    -    Список учасників
    +    Учасники
         Усі повідомлення
         Додати ярлик на головний екран
         Надіслати наліпку
    @@ -1496,7 +1496,7 @@
         Запросити електронним листом
         Проведіть пальцем, щоб скасувати
         Записати голосове повідомлення
    -    Вийти з простору
    +    Вийти
         Керувати кімнатами
         %s запрошує вас
         Вас запрошено
    @@ -2511,4 +2511,23 @@
         Згорнути
         %1$s, %2$s та інші
         %1$s і %2$s
    +    Зупинити
    +    Місцеперебування поширюється наживо
    +    Якщо бажаєте поширювати місцеперебування наживо, ${app_name} потребує доступу до місцеперебування протягом усієї фонової роботи застосунку.
    +\nМи отримуватимемо ваше місцеперебування лише протягом обраного вами терміну.
    +    Дозвольте доступ
    +    Поширити ці координати
    +    Поширити ці координати
    +    Маркер обраних координат на карті
    +    Поширити місцеперебування наживо
    +    Поширити місцеперебування наживо
    +    Поширити моє поточне місцеперебування
    +    Поширити моє поточне місцеперебування
    +    Перейти до поточного місцеперебування
    +    Наближаємось до випуску тредів у загальнодоступну бета-версію.
    +\n
    +\nГотуючись, маємо дещо змінити: треди, створені досі, буде показано як звичайні відповіді.
    +\n
    +\nЦе буде одноразовий перехід, бо треди — тепер частина специфікації Matrix.
    +    Незабаром бета-версія тредів 🎉
     
    \ No newline at end of file
    
    From 5bbc9dc1023e59cbeeac1fbc79206531c0c9f741 Mon Sep 17 00:00:00 2001
    From: Adam Brown 
    Date: Wed, 23 Mar 2022 09:02:33 +0000
    Subject: [PATCH 069/262] fixing the onboarding sanity test failing - adds
     tapping the new take me home button within the sanity test
    
    ---
     .../androidTest/java/im/vector/app/ui/robot/OnboardingRobot.kt | 3 +++
     1 file changed, 3 insertions(+)
    
    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") {
    
    From 8a1d008b3c00965c255242886100f85a8fcd09b5 Mon Sep 17 00:00:00 2001
    From: ariskotsomitopoulos 
    Date: Wed, 23 Mar 2022 11:29:06 +0200
    Subject: [PATCH 070/262] Show keyboard when user first reply in a thread
    
    ---
     .../vector/app/features/home/room/detail/TimelineFragment.kt  | 4 ++++
     1 file changed, 4 insertions(+)
    
    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 3c271710dd..09d82840bd 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
    @@ -1463,6 +1463,10 @@ class TimelineFragment @Inject constructor(
     
             views.composerLayout.views.composerEmojiButton.isVisible = vectorPreferences.showEmojiKeyboard()
     
    +        if(isThreadTimeLine() && timelineArgs.threadTimelineArgs?.startsThread == true){
    +            // Show keyboard when the user started a thread
    +            views.composerLayout.views.composerEditText.showKeyboard(andRequestFocus = true)
    +        }
             views.composerLayout.callback = object : MessageComposerView.Callback {
                 override fun onAddAttachment() {
                     if (!::attachmentTypeSelector.isInitialized) {
    
    From 6568091f298cc993b3f6355b7c41ce94bc88947d Mon Sep 17 00:00:00 2001
    From: ariskotsomitopoulos 
    Date: Wed, 23 Mar 2022 12:29:07 +0200
    Subject: [PATCH 071/262] Improve thread list item UI
    
    ---
     .../home/room/threads/list/model/ThreadListItem.kt     |  3 +++
     vector/src/main/res/layout/item_thread.xml             | 10 +++++-----
     2 files changed, 8 insertions(+), 5 deletions(-)
    
    diff --git a/vector/src/main/java/im/vector/app/features/home/room/threads/list/model/ThreadListItem.kt b/vector/src/main/java/im/vector/app/features/home/room/threads/list/model/ThreadListItem.kt
    index 2364e86166..385bb226a1 100644
    --- a/vector/src/main/java/im/vector/app/features/home/room/threads/list/model/ThreadListItem.kt
    +++ b/vector/src/main/java/im/vector/app/features/home/room/threads/list/model/ThreadListItem.kt
    @@ -33,6 +33,7 @@ import im.vector.app.core.extensions.setLeftDrawable
     import im.vector.app.core.utils.DimensionConverter
     import im.vector.app.features.displayname.getBestName
     import im.vector.app.features.home.AvatarRenderer
    +import im.vector.app.features.themes.ThemeUtils
     import org.matrix.android.sdk.api.session.threads.ThreadNotificationState
     import org.matrix.android.sdk.api.util.MatrixItem
     
    @@ -60,9 +61,11 @@ abstract class ThreadListItem : VectorEpoxyModel() {
             holder.dateTextView.text = date
             if (rootMessageDeleted) {
                 holder.rootMessageTextView.text = holder.view.context.getString(R.string.event_redacted)
    +            holder.rootMessageTextView.setTextColor(ThemeUtils.getColor(holder.view.context, R.attr.vctr_content_secondary))
                 holder.rootMessageTextView.setLeftDrawable(R.drawable.ic_trash_16, R.attr.vctr_content_tertiary)
                 holder.rootMessageTextView.compoundDrawablePadding = DimensionConverter(holder.view.context.resources).dpToPx(10)
             } else {
    +            holder.rootMessageTextView.setTextColor(ThemeUtils.getColor(holder.view.context, R.attr.vctr_content_primary))
                 holder.rootMessageTextView.text = rootMessage
                 holder.rootMessageTextView.clearDrawables()
             }
    diff --git a/vector/src/main/res/layout/item_thread.xml b/vector/src/main/res/layout/item_thread.xml
    index 37186f031c..921f0663b1 100644
    --- a/vector/src/main/res/layout/item_thread.xml
    +++ b/vector/src/main/res/layout/item_thread.xml
    @@ -73,8 +73,8 @@
             style="@style/Widget.Vector.TextView.Body"
             android:layout_width="0dp"
             android:layout_height="wrap_content"
    -        android:layout_marginTop="3dp"
    -        android:layout_marginEnd="25dp"
    +        android:layout_marginTop="2dp"
    +        android:layout_marginEnd="28dp"
             android:ellipsize="end"
             android:maxLines="2"
             android:textColor="?vctr_content_primary"
    @@ -87,12 +87,12 @@
             android:id="@+id/threadSummaryConstraintLayout"
             android:layout_width="0dp"
             android:layout_height="wrap_content"
    -        android:layout_marginEnd="25dp"
    +        android:layout_marginEnd="28dp"
             android:contentDescription="@string/room_threads_filter"
             android:maxWidth="496dp"
             android:minWidth="144dp"
    -        android:paddingTop="10dp"
    -        android:paddingBottom="12dp"
    +        android:paddingTop="8dp"
    +        android:paddingBottom="8dp"
             app:layout_constraintEnd_toEndOf="parent"
             app:layout_constraintStart_toStartOf="@id/threadSummaryTitleTextView"
             app:layout_constraintTop_toBottomOf="@id/threadSummaryRootMessageTextView"
    
    From 7ead3f93f45b81b9f42cae7454f7516c458e1550 Mon Sep 17 00:00:00 2001
    From: Onuray Sahin 
    Date: Wed, 23 Mar 2022 13:52:53 +0300
    Subject: [PATCH 072/262] Remove exhaustive.
    
    ---
     .../home/room/detail/composer/MessageComposerViewModel.kt       | 2 +-
     1 file changed, 1 insertion(+), 1 deletion(-)
    
    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 a9b9a1d302..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
    @@ -110,7 +110,7 @@ class MessageComposerViewModel @AssistedInject constructor(
                 is MessageComposerAction.OnEntersBackground             -> handleEntersBackground(action.composerText)
                 is MessageComposerAction.VoiceWaveformTouchedUp         -> handleVoiceWaveformTouchedUp(action)
                 is MessageComposerAction.VoiceWaveformMovedTo           -> handleVoiceWaveformMovedTo(action)
    -        }.exhaustive
    +        }
         }
     
         private fun handleOnVoiceRecordingUiStateChanged(action: MessageComposerAction.OnVoiceRecordingUiStateChanged) = setState {
    
    From d232c49d65706855e220fddd52f78fef3faf017b Mon Sep 17 00:00:00 2001
    From: ariskotsomitopoulos 
    Date: Wed, 23 Mar 2022 13:33:55 +0200
    Subject: [PATCH 073/262] Remove filter toolbar on thread list while there no
     threads to display
    
    ---
     .../app/features/home/room/detail/TimelineFragment.kt |  2 +-
     .../room/threads/list/views/ThreadListFragment.kt     | 11 +++++++++++
     2 files changed, 12 insertions(+), 1 deletion(-)
    
    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 09d82840bd..872205d95a 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
    @@ -1463,7 +1463,7 @@ class TimelineFragment @Inject constructor(
     
             views.composerLayout.views.composerEmojiButton.isVisible = vectorPreferences.showEmojiKeyboard()
     
    -        if(isThreadTimeLine() && timelineArgs.threadTimelineArgs?.startsThread == true){
    +        if (isThreadTimeLine() && timelineArgs.threadTimelineArgs?.startsThread == true) {
                 // Show keyboard when the user started a thread
                 views.composerLayout.views.composerEditText.showKeyboard(andRequestFocus = true)
             }
    diff --git a/vector/src/main/java/im/vector/app/features/home/room/threads/list/views/ThreadListFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/threads/list/views/ThreadListFragment.kt
    index d5659efa49..d00fcc9eb7 100644
    --- a/vector/src/main/java/im/vector/app/features/home/room/threads/list/views/ThreadListFragment.kt
    +++ b/vector/src/main/java/im/vector/app/features/home/room/threads/list/views/ThreadListFragment.kt
    @@ -18,6 +18,7 @@ package im.vector.app.features.home.room.threads.list.views
     
     import android.os.Bundle
     import android.view.LayoutInflater
    +import android.view.Menu
     import android.view.MenuItem
     import android.view.View
     import android.view.ViewGroup
    @@ -76,6 +77,15 @@ class ThreadListFragment @Inject constructor(
             }
         }
     
    +    override fun onPrepareOptionsMenu(menu: Menu) {
    +        withState(threadListViewModel) { state ->
    +            when (threadListViewModel.canHomeserverUseThreading()) {
    +                true  -> menu.findItem(R.id.menu_thread_list_filter).isVisible = !state.threadSummaryList.invoke().isNullOrEmpty()
    +                false -> menu.findItem(R.id.menu_thread_list_filter).isVisible = !state.rootThreadEventList.invoke().isNullOrEmpty()
    +            }
    +        }
    +    }
    +
         override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
             super.onViewCreated(view, savedInstanceState)
             initToolbar()
    @@ -102,6 +112,7 @@ class ThreadListFragment @Inject constructor(
         }
     
         override fun invalidate() = withState(threadListViewModel) { state ->
    +        invalidateOptionsMenu()
             renderEmptyStateIfNeeded(state)
             threadListController.update(state)
         }
    
    From 82a6ea9d857d752804a909afb882c599ac6dd2cf Mon Sep 17 00:00:00 2001
    From: ariskotsomitopoulos 
    Date: Wed, 23 Mar 2022 13:40:32 +0200
    Subject: [PATCH 074/262] Change thread list filtering radio buttons color
    
    ---
     .../home/room/threads/list/views/ThreadListBottomSheet.kt     | 4 ++--
     1 file changed, 2 insertions(+), 2 deletions(-)
    
    diff --git a/vector/src/main/java/im/vector/app/features/home/room/threads/list/views/ThreadListBottomSheet.kt b/vector/src/main/java/im/vector/app/features/home/room/threads/list/views/ThreadListBottomSheet.kt
    index 7ad4804e5b..07684a796e 100644
    --- a/vector/src/main/java/im/vector/app/features/home/room/threads/list/views/ThreadListBottomSheet.kt
    +++ b/vector/src/main/java/im/vector/app/features/home/room/threads/list/views/ThreadListBottomSheet.kt
    @@ -61,11 +61,11 @@ class ThreadListBottomSheet : VectorBaseBottomSheetDialogFragment
    Date: Wed, 23 Mar 2022 15:37:44 +0300
    Subject: [PATCH 075/262] Navigate to room list when user clicks to the sticky
     notification.
    
    ---
     .../app/features/location/LocationSharingService.kt  | 12 ++++++------
     .../app/features/notifications/NotificationUtils.kt  |  1 +
     2 files changed, 7 insertions(+), 6 deletions(-)
    
    diff --git a/vector/src/main/java/im/vector/app/features/location/LocationSharingService.kt b/vector/src/main/java/im/vector/app/features/location/LocationSharingService.kt
    index 183d3ee2ab..f62ad6465f 100644
    --- a/vector/src/main/java/im/vector/app/features/location/LocationSharingService.kt
    +++ b/vector/src/main/java/im/vector/app/features/location/LocationSharingService.kt
    @@ -45,7 +45,7 @@ class LocationSharingService : VectorService(), LocationTracker.Callback {
     
         override fun onCreate() {
             super.onCreate()
    -        Timber.d("### LocationSharingService.onCreate")
    +        Timber.i("### LocationSharingService.onCreate")
     
             // Start tracking location
             locationTracker.addCallback(this)
    @@ -55,7 +55,7 @@ class LocationSharingService : VectorService(), LocationTracker.Callback {
         override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
             val roomArgs = intent?.getParcelableExtra(EXTRA_ROOM_ARGS) as? RoomArgs
     
    -        Timber.d("### LocationSharingService.onStartCommand. sessionId - roomId ${roomArgs?.sessionId} - ${roomArgs?.roomId}")
    +        Timber.i("### LocationSharingService.onStartCommand. sessionId - roomId ${roomArgs?.sessionId} - ${roomArgs?.roomId}")
     
             if (roomArgs != null) {
                 roomArgsList.add(roomArgs)
    @@ -80,11 +80,11 @@ class LocationSharingService : VectorService(), LocationTracker.Callback {
         }
     
         private fun stopSharingLocation(roomId: String) {
    -        Timber.d("### LocationSharingService.stopSharingLocation for $roomId")
    +        Timber.i("### LocationSharingService.stopSharingLocation for $roomId")
             synchronized(roomArgsList) {
                 roomArgsList.removeAll { it.roomId == roomId }
                 if (roomArgsList.isEmpty()) {
    -                Timber.d("### LocationSharingService. Destroying self, time is up for all rooms")
    +                Timber.i("### LocationSharingService. Destroying self, time is up for all rooms")
                     destroyMe()
                 }
             }
    @@ -97,7 +97,7 @@ class LocationSharingService : VectorService(), LocationTracker.Callback {
     
         override fun onDestroy() {
             super.onDestroy()
    -        Timber.d("### LocationSharingService.onDestroy")
    +        Timber.i("### LocationSharingService.onDestroy")
             destroyMe()
         }
     
    @@ -110,7 +110,7 @@ class LocationSharingService : VectorService(), LocationTracker.Callback {
         }
     
         override fun onLocationUpdate(locationData: LocationData) {
    -        Timber.d("### LocationSharingService.onLocationUpdate. Lat - lon: ${locationData.latitude} - ${locationData.longitude}")
    +        Timber.i("### LocationSharingService.onLocationUpdate. Uncertainty: ${locationData.uncertainty}")
         }
     
         override fun onLocationProviderIsNotAvailable() {
    diff --git a/vector/src/main/java/im/vector/app/features/notifications/NotificationUtils.kt b/vector/src/main/java/im/vector/app/features/notifications/NotificationUtils.kt
    index 8120134526..161b58d53d 100755
    --- a/vector/src/main/java/im/vector/app/features/notifications/NotificationUtils.kt
    +++ b/vector/src/main/java/im/vector/app/features/notifications/NotificationUtils.kt
    @@ -531,6 +531,7 @@ class NotificationUtils @Inject constructor(private val context: Context,
                     .setSmallIcon(R.drawable.ic_attachment_location_live_white)
                     .setColor(ThemeUtils.getColor(context, android.R.attr.colorPrimary))
                     .setCategory(NotificationCompat.CATEGORY_LOCATION_SHARING)
    +                .setContentIntent(buildOpenHomePendingIntentForSummary())
                     .build()
         }
     
    
    From 96a2bc9ce47fc807e5f5f60174ced5bfa13246bd Mon Sep 17 00:00:00 2001
    From: Onuray Sahin 
    Date: Wed, 23 Mar 2022 15:47:40 +0300
    Subject: [PATCH 076/262] Changelog added.
    
    ---
     changelog.d/5595.feature | 1 +
     1 file changed, 1 insertion(+)
     create mode 100644 changelog.d/5595.feature
    
    diff --git a/changelog.d/5595.feature b/changelog.d/5595.feature
    new file mode 100644
    index 0000000000..02ae703bdd
    --- /dev/null
    +++ b/changelog.d/5595.feature
    @@ -0,0 +1 @@
    +Live Location Sharing - Foreground Service
    \ No newline at end of file
    
    From 3b9cade1a7150852b29052d149e957e4aa07d841 Mon Sep 17 00:00:00 2001
    From: ariskotsomitopoulos 
    Date: Wed, 23 Mar 2022 15:13:15 +0200
    Subject: [PATCH 077/262] Thread list filtering minor UI changes
    
    ---
     vector/src/main/res/layout/bottom_sheet_thread_list.xml | 6 +++---
     1 file changed, 3 insertions(+), 3 deletions(-)
    
    diff --git a/vector/src/main/res/layout/bottom_sheet_thread_list.xml b/vector/src/main/res/layout/bottom_sheet_thread_list.xml
    index e736f30edc..4d1f1eb043 100644
    --- a/vector/src/main/res/layout/bottom_sheet_thread_list.xml
    +++ b/vector/src/main/res/layout/bottom_sheet_thread_list.xml
    @@ -5,7 +5,7 @@
         android:layout_height="match_parent"
         android:background="?colorSurface"
         android:orientation="vertical"
    -    android:padding="8dp">
    +    android:padding="16dp">
     
         
    Date: Wed, 23 Mar 2022 16:34:45 +0300
    Subject: [PATCH 078/262] Reorder functions.
    
    ---
     .../location/LocationSharingService.kt         | 18 +++++++++---------
     1 file changed, 9 insertions(+), 9 deletions(-)
    
    diff --git a/vector/src/main/java/im/vector/app/features/location/LocationSharingService.kt b/vector/src/main/java/im/vector/app/features/location/LocationSharingService.kt
    index f62ad6465f..b11ddf93e2 100644
    --- a/vector/src/main/java/im/vector/app/features/location/LocationSharingService.kt
    +++ b/vector/src/main/java/im/vector/app/features/location/LocationSharingService.kt
    @@ -90,6 +90,15 @@ class LocationSharingService : VectorService(), LocationTracker.Callback {
             }
         }
     
    +    override fun onLocationUpdate(locationData: LocationData) {
    +        Timber.i("### LocationSharingService.onLocationUpdate. Uncertainty: ${locationData.uncertainty}")
    +    }
    +
    +    override fun onLocationProviderIsNotAvailable() {
    +        stopForeground(true)
    +        stopSelf()
    +    }
    +
         private fun destroyMe() {
             locationTracker.removeCallback(this)
             stopSelf()
    @@ -108,13 +117,4 @@ class LocationSharingService : VectorService(), LocationTracker.Callback {
         companion object {
             const val EXTRA_ROOM_ARGS = "EXTRA_ROOM_ARGS"
         }
    -
    -    override fun onLocationUpdate(locationData: LocationData) {
    -        Timber.i("### LocationSharingService.onLocationUpdate. Uncertainty: ${locationData.uncertainty}")
    -    }
    -
    -    override fun onLocationProviderIsNotAvailable() {
    -        stopForeground(true)
    -        stopSelf()
    -    }
     }
    
    From 669d00c02a6874c7830a5836cbcfa3dbb713604e Mon Sep 17 00:00:00 2001
    From: Michael Kaye <1917473+michaelkaye@users.noreply.github.com>
    Date: Wed, 23 Mar 2022 15:15:30 +0000
    Subject: [PATCH 079/262] Only run sonar on nightly runs
    
    ---
     .github/workflows/nightly.yml | 4 ++--
     1 file changed, 2 insertions(+), 2 deletions(-)
    
    diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml
    index 7f789b4610..4affffe143 100644
    --- a/.github/workflows/nightly.yml
    +++ b/.github/workflows/nightly.yml
    @@ -293,7 +293,7 @@ jobs:
       sonarqube:
         name: Sonarqube upload
         runs-on: macos-latest
    -    if: always()
    +    if: always() && github.event_name == "schedule"
         needs:
           - codecov-units
         steps:
    @@ -319,7 +319,7 @@ jobs:
             env:
               ORG_GRADLE_PROJECT_SONAR_LOGIN: ${{ secrets.SONAR_TOKEN }}
     
    -# Notify the channel about scheduled runs, do not notify for manually triggered runs
    +# Notify the channel about scheduled runs, or pushes to the release branches, do not notify for manually triggered runs
       notify:
         name: Notify matrix
         runs-on: ubuntu-latest
    
    From f7f115e4dc03d466d57d49c9ff957e66b6881790 Mon Sep 17 00:00:00 2001
    From: Michael Kaye <1917473+michaelkaye@users.noreply.github.com>
    Date: Wed, 23 Mar 2022 15:19:30 +0000
    Subject: [PATCH 080/262] Be clearer on which test run we're running & don't
     run sonarqube on release branches.
    
    ---
     .github/workflows/nightly.yml | 4 ++--
     1 file changed, 2 insertions(+), 2 deletions(-)
    
    diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml
    index 4affffe143..59f4169452 100644
    --- a/.github/workflows/nightly.yml
    +++ b/.github/workflows/nightly.yml
    @@ -335,5 +335,5 @@ jobs:
               github_token: ${{ secrets.GITHUB_TOKEN }}
               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 }}
    {{icon conclusion}} {{name}} {{conclusion}} at {{completed_at}} [details]{{/if}}{{/with}}{{/each}}" + text_template: "{{#if ${{ github.event_name }} == 'schedule' }}Nightly test run{{else}}Test run (on ${{ github.ref }}){{/if }}: {{#each job_statuses }}{{#with this }}{{#if completed }} {{name}} {{conclusion}} at {{completed_at}}, {{/if}}{{/with}}{{/each}}" + html_template: "{{#if ${{ github.event_name }} == 'schedule' }}Nightly test run{{else}}Test run (on ${{ github.ref }}){{/if}}: {{#each job_statuses }}{{#with this }}{{#if completed }}
    {{icon conclusion}} {{name}} {{conclusion}} at {{completed_at}} [details]{{/if}}{{/with}}{{/each}}" From adf2c642da32a63c40e4f29908b421045753a109 Mon Sep 17 00:00:00 2001 From: ganfra Date: Wed, 23 Mar 2022 16:27:36 +0100 Subject: [PATCH 081/262] Update versions to 1.4.8 --- matrix-sdk-android/build.gradle | 2 +- vector/build.gradle | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/matrix-sdk-android/build.gradle b/matrix-sdk-android/build.gradle index 2b2c38e22a..1e2eda166f 100644 --- a/matrix-sdk-android/build.gradle +++ b/matrix-sdk-android/build.gradle @@ -31,7 +31,7 @@ android { // that the app's state is completely cleared between tests. testInstrumentationRunnerArguments clearPackageData: 'true' - buildConfigField "String", "SDK_VERSION", "\"1.4.6\"" + buildConfigField "String", "SDK_VERSION", "\"1.4.8\"" buildConfigField "String", "GIT_SDK_REVISION", "\"${gitRevision()}\"" buildConfigField "String", "GIT_SDK_REVISION_UNIX_DATE", "\"${gitRevisionUnixDate()}\"" diff --git a/vector/build.gradle b/vector/build.gradle index 04b20599d4..9f8471bc18 100644 --- a/vector/build.gradle +++ b/vector/build.gradle @@ -18,7 +18,7 @@ ext.versionMinor = 4 // Note: even values are reserved for regular release, odd values for hotfix release. // When creating a hotfix, you should decrease the value, since the current value // is the value for the next regular release. -ext.versionPatch = 6 +ext.versionPatch = 8 static def getGitTimestamp() { def cmd = 'git show -s --format=%ct' From e3df9c4ceff2ad888ee1c2a14399929bb37f3b53 Mon Sep 17 00:00:00 2001 From: Adam Brown Date: Tue, 8 Mar 2022 11:06:35 +0000 Subject: [PATCH 082/262] using isLoading boolean instead of stateless async result for the display name and profile picture updates --- .../onboarding/OnboardingViewModel.kt | 12 +++++------ .../onboarding/OnboardingViewState.kt | 9 +++----- .../onboarding/ftueauth/FtueAuthVariant.kt | 2 +- .../onboarding/OnboardingViewModelTest.kt | 21 +++++++++---------- 4 files changed, 20 insertions(+), 24 deletions(-) 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 e7302cb1e2..6ebbffa6e3 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 @@ -828,20 +828,20 @@ class OnboardingViewModel @AssistedInject constructor( } private fun updateDisplayName(displayName: String) { - setState { copy(asyncDisplayName = Loading()) } + setState { copy(isLoading = true) } viewModelScope.launch { val activeSession = activeSessionHolder.getActiveSession() try { activeSession.setDisplayName(activeSession.myUserId, displayName) setState { copy( - asyncDisplayName = Success(Unit), + isLoading = false, personalizationState = personalizationState.copy(displayName = displayName) ) } handleDisplayNameStepComplete() } catch (error: Throwable) { - setState { copy(asyncDisplayName = Fail(error)) } + setState { copy(isLoading = false) } _viewEvents.post(OnboardingViewEvents.Failure(error)) } } @@ -883,7 +883,7 @@ class OnboardingViewModel @AssistedInject constructor( when (val pictureUri = state.personalizationState.selectedPictureUri) { null -> _viewEvents.post(OnboardingViewEvents.Failure(NullPointerException("picture uri is missing from state"))) else -> { - setState { copy(asyncProfilePicture = Loading()) } + setState { copy(isLoading = true) } viewModelScope.launch { val activeSession = activeSessionHolder.getActiveSession() try { @@ -894,12 +894,12 @@ class OnboardingViewModel @AssistedInject constructor( ) setState { copy( - asyncProfilePicture = Success(Unit), + isLoading = false, ) } onProfilePictureSaved() } catch (error: Throwable) { - setState { copy(asyncProfilePicture = Fail(error)) } + setState { copy(isLoading = false) } _viewEvents.post(OnboardingViewEvents.Failure(error)) } } diff --git a/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewState.kt b/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewState.kt index 8747de6da8..cdb68cfdac 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewState.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewState.kt @@ -34,8 +34,7 @@ data class OnboardingViewState( val asyncResetPassword: Async = Uninitialized, val asyncResetMailConfirmed: Async = Uninitialized, val asyncRegistration: Async = Uninitialized, - val asyncDisplayName: Async = Uninitialized, - val asyncProfilePicture: Async = Uninitialized, + val isLoading: Boolean = false, @PersistState val onboardingFlow: OnboardingFlow? = null, @@ -73,14 +72,12 @@ data class OnboardingViewState( val personalizationState: PersonalizationState = PersonalizationState() ) : MavericksState { - fun isLoading(): Boolean { + fun legacyIsLoading(): Boolean { return asyncLoginAction is Loading || asyncHomeServerLoginFlowRequest is Loading || asyncResetPassword is Loading || asyncResetMailConfirmed is Loading || - asyncRegistration is Loading || - asyncDisplayName is Loading || - asyncProfilePicture is Loading + asyncRegistration is Loading } } 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 13b5f61010..b8319c9f3a 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 @@ -121,7 +121,7 @@ class FtueAuthVariant( private fun updateWithState(viewState: OnboardingViewState) { isForceLoginFallbackEnabled = viewState.isForceLoginFallbackEnabled - views.loginLoading.isVisible = viewState.isLoading() + views.loginLoading.isVisible = viewState.isLoading || viewState.legacyIsLoading() } override fun setIsLoading(isLoading: Boolean) = Unit diff --git a/vector/src/test/java/im/vector/app/features/onboarding/OnboardingViewModelTest.kt b/vector/src/test/java/im/vector/app/features/onboarding/OnboardingViewModelTest.kt index 4fd079611d..20e6ceb55d 100644 --- a/vector/src/test/java/im/vector/app/features/onboarding/OnboardingViewModelTest.kt +++ b/vector/src/test/java/im/vector/app/features/onboarding/OnboardingViewModelTest.kt @@ -17,7 +17,6 @@ package im.vector.app.features.onboarding import android.net.Uri -import com.airbnb.mvrx.Fail import com.airbnb.mvrx.Loading import com.airbnb.mvrx.Success import com.airbnb.mvrx.Uninitialized @@ -260,8 +259,8 @@ class OnboardingViewModelTest { test .assertStatesChanges( initialState, - { copy(asyncDisplayName = Loading()) }, - { copy(asyncDisplayName = Fail(AN_ERROR)) }, + { copy(isLoading = true) }, + { copy(isLoading = false) }, ) .assertEvents(OnboardingViewEvents.Failure(AN_ERROR)) .finish() @@ -307,7 +306,7 @@ class OnboardingViewModelTest { viewModel.handle(OnboardingAction.SaveSelectedProfilePicture) test - .assertStates(expectedProfilePictureFailureStates(initialStateWithPicture, AN_ERROR)) + .assertStates(expectedProfilePictureFailureStates(initialStateWithPicture)) .assertEvents(OnboardingViewEvents.Failure(AN_ERROR)) .finish() } @@ -362,20 +361,20 @@ class OnboardingViewModelTest { private fun expectedProfilePictureSuccessStates(state: OnboardingViewState) = listOf( state, - state.copy(asyncProfilePicture = Loading()), - state.copy(asyncProfilePicture = Success(Unit)) + state.copy(isLoading = true), + state.copy(isLoading = false) ) - private fun expectedProfilePictureFailureStates(state: OnboardingViewState, cause: Exception) = listOf( + private fun expectedProfilePictureFailureStates(state: OnboardingViewState) = listOf( state, - state.copy(asyncProfilePicture = Loading()), - state.copy(asyncProfilePicture = Fail(cause)) + state.copy(isLoading = true), + state.copy(isLoading = false) ) private fun expectedSuccessfulDisplayNameUpdateStates(): List OnboardingViewState> { return listOf( - { copy(asyncDisplayName = Loading()) }, - { copy(asyncDisplayName = Success(Unit), personalizationState = personalizationState.copy(displayName = A_DISPLAY_NAME)) } + { copy(isLoading = true) }, + { copy(isLoading = false, personalizationState = personalizationState.copy(displayName = A_DISPLAY_NAME)) } ) } From 2227df479c3d46cf21369a99df4e63901d297375 Mon Sep 17 00:00:00 2001 From: Adam Brown Date: Tue, 8 Mar 2022 11:41:32 +0000 Subject: [PATCH 083/262] replacing async login/register state with separate failure view event and shared isLoading --- .../onboarding/OnboardingViewModel.kt | 101 +++++------------- .../onboarding/OnboardingViewState.kt | 8 +- .../ftueauth/FtueAuthLoginFragment.kt | 65 ++++++----- .../onboarding/OnboardingViewModelTest.kt | 22 ++-- 4 files changed, 67 insertions(+), 129 deletions(-) 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 6ebbffa6e3..d6c0daa26e 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 @@ -239,31 +239,19 @@ class OnboardingViewModel @AssistedInject constructor( val safeLoginWizard = loginWizard if (safeLoginWizard == null) { - setState { - copy( - asyncLoginAction = Fail(Throwable("Bad configuration")) - ) - } + setState { copy(isLoading = false) } + _viewEvents.post(OnboardingViewEvents.Failure(Throwable("Bad configuration"))) } else { - setState { - copy( - asyncLoginAction = Loading() - ) - } + setState { copy(isLoading = true) } currentJob = viewModelScope.launch { try { - safeLoginWizard.loginWithToken(action.loginToken) + val result = safeLoginWizard.loginWithToken(action.loginToken) + onSessionCreated(result, isAccountCreated = false) } catch (failure: Throwable) { _viewEvents.post(OnboardingViewEvents.Failure(failure)) - setState { - copy( - asyncLoginAction = Fail(failure) - ) - } - null + setState { copy(isLoading = false) } } - ?.let { onSessionCreated(it, isAccountCreated = false) } } } } @@ -271,7 +259,7 @@ class OnboardingViewModel @AssistedInject constructor( private fun handleRegisterAction(action: RegisterAction) { currentJob = viewModelScope.launch { if (action.hasLoadingState()) { - setState { copy(asyncRegistration = Loading()) } + setState { copy(isLoading = true) } } runCatching { registrationActionHandler.handleRegisterAction(registrationWizard, action) } .fold( @@ -292,7 +280,7 @@ class OnboardingViewModel @AssistedInject constructor( } } ) - setState { copy(asyncRegistration = Uninitialized) } + setState { copy(isLoading = false) } } } @@ -345,12 +333,7 @@ class OnboardingViewModel @AssistedInject constructor( OnboardingAction.ResetLogin -> { viewModelScope.launch { authenticationService.cancelPendingLoginOrRegistration() - setState { - copy( - asyncLoginAction = Uninitialized, - asyncRegistration = Uninitialized - ) - } + setState { copy(isLoading = false) } } } OnboardingAction.ResetResetPassword -> { @@ -515,11 +498,7 @@ class OnboardingViewModel @AssistedInject constructor( } private fun handleDirectLogin(action: OnboardingAction.LoginOrRegister, homeServerConnectionConfig: HomeServerConnectionConfig?) { - setState { - copy( - asyncLoginAction = Loading() - ) - } + setState { copy(isLoading = true) } currentJob = viewModelScope.launch { val data = try { @@ -546,11 +525,7 @@ class OnboardingViewModel @AssistedInject constructor( } private fun onWellKnownError() { - setState { - copy( - asyncLoginAction = Uninitialized - ) - } + setState { copy(isLoading = false) } _viewEvents.post(OnboardingViewEvents.Failure(Exception(stringProvider.getString(R.string.autodiscover_well_known_error)))) } @@ -587,18 +562,10 @@ class OnboardingViewModel @AssistedInject constructor( is Failure.UnrecognizedCertificateFailure -> { // Display this error in a dialog _viewEvents.post(OnboardingViewEvents.Failure(failure)) - setState { - copy( - asyncLoginAction = Uninitialized - ) - } + setState { copy(isLoading = false) } } else -> { - setState { - copy( - asyncLoginAction = Fail(failure) - ) - } + setState { copy(isLoading = false) } } } } @@ -607,37 +574,22 @@ class OnboardingViewModel @AssistedInject constructor( val safeLoginWizard = loginWizard if (safeLoginWizard == null) { - setState { - copy( - asyncLoginAction = Fail(Throwable("Bad configuration")) - ) - } + setState { copy(isLoading = false) } + _viewEvents.post(OnboardingViewEvents.Failure(Throwable("Bad configuration"))) } else { - setState { - copy( - asyncLoginAction = Loading() - ) - } - + setState { copy(isLoading = true) } currentJob = viewModelScope.launch { try { - safeLoginWizard.login( + val result = safeLoginWizard.login( action.username, action.password, action.initialDeviceName ) + reAuthHelper.data = action.password + onSessionCreated(result, isAccountCreated = false) } catch (failure: Throwable) { - setState { - copy( - asyncLoginAction = Fail(failure) - ) - } - null + setState { copy(isLoading = false) } } - ?.let { - reAuthHelper.data = action.password - onSessionCreated(it, isAccountCreated = false) - } } } } @@ -678,12 +630,12 @@ class OnboardingViewModel @AssistedInject constructor( true -> { val personalizationState = createPersonalizationState(session, state) setState { - copy(asyncLoginAction = Success(Unit), personalizationState = personalizationState) + copy(isLoading = false, personalizationState = personalizationState) } _viewEvents.post(OnboardingViewEvents.OnAccountCreated) } false -> { - setState { copy(asyncLoginAction = Success(Unit)) } + setState { copy(isLoading = false) } _viewEvents.post(OnboardingViewEvents.OnAccountSignedIn) } } @@ -712,14 +664,11 @@ class OnboardingViewModel @AssistedInject constructor( } else { currentJob = viewModelScope.launch { try { - authenticationService.createSessionFromSso(homeServerConnectionConfigFinal, action.credentials) + val result = authenticationService.createSessionFromSso(homeServerConnectionConfigFinal, action.credentials) + onSessionCreated(result, isAccountCreated = false) } catch (failure: Throwable) { - setState { - copy(asyncLoginAction = Fail(failure)) - } - null + setState { copy(isLoading = false) } } - ?.let { onSessionCreated(it, isAccountCreated = false) } } } } diff --git a/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewState.kt b/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewState.kt index cdb68cfdac..063c651dfd 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewState.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewState.kt @@ -29,11 +29,9 @@ import im.vector.app.features.login.SignMode import kotlinx.parcelize.Parcelize data class OnboardingViewState( - val asyncLoginAction: Async = Uninitialized, val asyncHomeServerLoginFlowRequest: Async = Uninitialized, val asyncResetPassword: Async = Uninitialized, val asyncResetMailConfirmed: Async = Uninitialized, - val asyncRegistration: Async = Uninitialized, val isLoading: Boolean = false, @PersistState @@ -73,11 +71,9 @@ data class OnboardingViewState( ) : MavericksState { fun legacyIsLoading(): Boolean { - return asyncLoginAction is Loading || - asyncHomeServerLoginFlowRequest is Loading || + return asyncHomeServerLoginFlowRequest is Loading || asyncResetPassword is Loading || - asyncResetMailConfirmed is Loading || - asyncRegistration is Loading + asyncResetMailConfirmed is Loading } } 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 dacd8feab3..a5536b1dcd 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthLoginFragment.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthLoginFragment.kt @@ -26,8 +26,6 @@ import androidx.autofill.HintConstants import androidx.core.text.isDigitsOnly import androidx.core.view.isVisible import androidx.lifecycle.lifecycleScope -import com.airbnb.mvrx.Fail -import com.airbnb.mvrx.Loading import im.vector.app.R import im.vector.app.core.extensions.hideKeyboard import im.vector.app.core.extensions.hidePassword @@ -74,6 +72,33 @@ class FtueAuthLoginFragment @Inject constructor() : AbstractSSOFtueAuthFragment< override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) + viewModel.viewEvents + .stream() + .onEach { + when (it) { + is OnboardingViewEvents.Failure -> { + if (it.throwable is Failure.ServerError && + it.throwable.error.code == MatrixError.M_FORBIDDEN && + it.throwable.error.message.isEmpty()) { + // Login with email, but email unknown + views.loginFieldTil.error = getString(R.string.login_login_with_email_error) + } else { + // Trick to display the error without text. + views.loginFieldTil.error = " " + if (it.throwable.isInvalidPassword() && spaceInPassword()) { + views.passwordFieldTil.error = getString(R.string.auth_invalid_login_param_space_in_password) + } else { + views.passwordFieldTil.error = errorFormatter.toHumanReadable(it.throwable) + } + } + } + else -> { + // do nothing + } + } + } + .launchIn(lifecycleScope) + setupSubmitButton() setupForgottenPasswordButton() @@ -274,39 +299,9 @@ class FtueAuthLoginFragment @Inject constructor() : AbstractSSOFtueAuthFragment< setupSocialLoginButtons(state) setupButtons(state) - when (state.asyncLoginAction) { - is Loading -> { - // Ensure password is hidden - views.passwordField.hidePassword() - } - is Fail -> { - val error = state.asyncLoginAction.error - if (error is Failure.ServerError && - error.error.code == MatrixError.M_FORBIDDEN && - error.error.message.isEmpty()) { - // Login with email, but email unknown - views.loginFieldTil.error = getString(R.string.login_login_with_email_error) - } else { - // Trick to display the error without text. - views.loginFieldTil.error = " " - if (error.isInvalidPassword() && spaceInPassword()) { - views.passwordFieldTil.error = getString(R.string.auth_invalid_login_param_space_in_password) - } else { - views.passwordFieldTil.error = errorFormatter.toHumanReadable(error) - } - } - } - // Success is handled by the LoginActivity - else -> Unit - } - - when (state.asyncRegistration) { - is Loading -> { - // Ensure password is hidden - views.passwordField.hidePassword() - } - // Success is handled by the LoginActivity - else -> Unit + if (state.isLoading) { + // Ensure password is hidden + views.passwordField.hidePassword() } } diff --git a/vector/src/test/java/im/vector/app/features/onboarding/OnboardingViewModelTest.kt b/vector/src/test/java/im/vector/app/features/onboarding/OnboardingViewModelTest.kt index 20e6ceb55d..09cefc5ff2 100644 --- a/vector/src/test/java/im/vector/app/features/onboarding/OnboardingViewModelTest.kt +++ b/vector/src/test/java/im/vector/app/features/onboarding/OnboardingViewModelTest.kt @@ -128,8 +128,8 @@ class OnboardingViewModelTest { .assertStatesChanges( initialState, { copy(signMode = SignMode.SignUp) }, - { copy(asyncRegistration = Loading()) }, - { copy(asyncRegistration = Uninitialized) } + { copy(isLoading = true) }, + { copy(isLoading = false) } ) .assertEvents(OnboardingViewEvents.RegistrationFlowResult(ANY_CONTINUING_REGISTRATION_RESULT.flowResult, isRegistrationStarted = true)) .finish() @@ -145,8 +145,8 @@ class OnboardingViewModelTest { test .assertStatesChanges( initialState, - { copy(asyncRegistration = Loading()) }, - { copy(asyncRegistration = Uninitialized) } + { copy(isLoading = true) }, + { copy(isLoading = false) } ) .assertEvents(OnboardingViewEvents.RegistrationFlowResult(ANY_CONTINUING_REGISTRATION_RESULT.flowResult, isRegistrationStarted = true)) .finish() @@ -175,8 +175,8 @@ class OnboardingViewModelTest { test .assertStatesChanges( initialState, - { copy(asyncRegistration = Loading()) }, - { copy(asyncRegistration = Uninitialized) } + { copy(isLoading = true) }, + { copy(isLoading = false) } ) .assertNoEvents() .finish() @@ -193,9 +193,8 @@ class OnboardingViewModelTest { test .assertStatesChanges( initialState, - { copy(asyncRegistration = Loading()) }, - { copy(asyncLoginAction = Success(Unit), personalizationState = A_HOMESERVER_CAPABILITIES.toPersonalisationState()) }, - { copy(asyncLoginAction = Success(Unit), asyncRegistration = Uninitialized) } + { copy(isLoading = true) }, + { copy(isLoading = false, personalizationState = A_HOMESERVER_CAPABILITIES.toPersonalisationState()) } ) .assertEvents(OnboardingViewEvents.OnAccountCreated) .finish() @@ -211,9 +210,8 @@ class OnboardingViewModelTest { test .assertStatesChanges( initialState, - { copy(asyncRegistration = Loading()) }, - { copy(asyncLoginAction = Success(Unit), personalizationState = A_HOMESERVER_CAPABILITIES.toPersonalisationState()) }, - { copy(asyncRegistration = Uninitialized) } + { copy(isLoading = true) }, + { copy(isLoading = false, personalizationState = A_HOMESERVER_CAPABILITIES.toPersonalisationState()) } ) .assertEvents(OnboardingViewEvents.OnAccountCreated) .finish() From 7d80cfed0b2199d63d445d330ac18252b00cf6b2 Mon Sep 17 00:00:00 2001 From: Adam Brown Date: Tue, 8 Mar 2022 12:00:42 +0000 Subject: [PATCH 084/262] replacing async reset password,mail and homeserver requests with shared isLoading with error view events --- .../onboarding/OnboardingViewModel.kt | 72 ++++++------------- .../onboarding/OnboardingViewState.kt | 15 +--- .../ftueauth/FtueAuthResetPasswordFragment.kt | 20 ++---- ...thResetPasswordMailConfirmationFragment.kt | 30 ++++---- .../onboarding/ftueauth/FtueAuthVariant.kt | 2 +- 5 files changed, 44 insertions(+), 95 deletions(-) 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 d6c0daa26e..a18b9e62e0 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewModel.kt @@ -18,11 +18,7 @@ package im.vector.app.features.onboarding import android.content.Context import android.net.Uri -import com.airbnb.mvrx.Fail -import com.airbnb.mvrx.Loading import com.airbnb.mvrx.MavericksViewModelFactory -import com.airbnb.mvrx.Success -import com.airbnb.mvrx.Uninitialized import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject @@ -249,8 +245,8 @@ class OnboardingViewModel @AssistedInject constructor( val result = safeLoginWizard.loginWithToken(action.loginToken) onSessionCreated(result, isAccountCreated = false) } catch (failure: Throwable) { - _viewEvents.post(OnboardingViewEvents.Failure(failure)) setState { copy(isLoading = false) } + _viewEvents.post(OnboardingViewEvents.Failure(failure)) } } } @@ -310,7 +306,7 @@ class OnboardingViewModel @AssistedInject constructor( authenticationService.reset() setState { copy( - asyncHomeServerLoginFlowRequest = Uninitialized, + isLoading = false, homeServerUrlFromUser = null, homeServerUrl = null, loginMode = LoginMode.Unknown, @@ -323,7 +319,7 @@ class OnboardingViewModel @AssistedInject constructor( OnboardingAction.ResetSignMode -> { setState { copy( - asyncHomeServerLoginFlowRequest = Uninitialized, + isLoading = false, signMode = SignMode.Unknown, loginMode = LoginMode.Unknown, loginModeSupportedTypes = emptyList() @@ -339,8 +335,7 @@ class OnboardingViewModel @AssistedInject constructor( OnboardingAction.ResetResetPassword -> { setState { copy( - asyncResetPassword = Uninitialized, - asyncResetMailConfirmed = Uninitialized, + isLoading = false, resetPasswordEmail = null ) } @@ -409,35 +404,23 @@ class OnboardingViewModel @AssistedInject constructor( val safeLoginWizard = loginWizard if (safeLoginWizard == null) { - setState { - copy( - asyncResetPassword = Fail(Throwable("Bad configuration")), - asyncResetMailConfirmed = Uninitialized - ) - } + setState { copy(isLoading = false) } + _viewEvents.post(OnboardingViewEvents.Failure(Throwable("Bad configuration"))) } else { - setState { - copy( - asyncResetPassword = Loading(), - asyncResetMailConfirmed = Uninitialized - ) - } + setState { copy(isLoading = true) } currentJob = viewModelScope.launch { try { safeLoginWizard.resetPassword(action.email, action.newPassword) } catch (failure: Throwable) { - setState { - copy( - asyncResetPassword = Fail(failure) - ) - } + setState { copy(isLoading = false) } + _viewEvents.post(OnboardingViewEvents.Failure(failure)) return@launch } setState { copy( - asyncResetPassword = Success(Unit), + isLoading = false, resetPasswordEmail = action.email ) } @@ -451,34 +434,22 @@ class OnboardingViewModel @AssistedInject constructor( val safeLoginWizard = loginWizard if (safeLoginWizard == null) { - setState { - copy( - asyncResetPassword = Uninitialized, - asyncResetMailConfirmed = Fail(Throwable("Bad configuration")) - ) - } + setState { copy(isLoading = false) } + _viewEvents.post(OnboardingViewEvents.Failure(Throwable("Bad configuration"))) } else { - setState { - copy( - asyncResetPassword = Uninitialized, - asyncResetMailConfirmed = Loading() - ) - } + setState { copy(isLoading = false) } currentJob = viewModelScope.launch { try { safeLoginWizard.resetPasswordMailConfirmed() } catch (failure: Throwable) { - setState { - copy( - asyncResetMailConfirmed = Fail(failure) - ) - } + setState { copy(isLoading = false) } + _viewEvents.post(OnboardingViewEvents.Failure(failure)) return@launch } setState { copy( - asyncResetMailConfirmed = Success(Unit), + isLoading = false, resetPasswordEmail = null ) } @@ -560,9 +531,9 @@ class OnboardingViewModel @AssistedInject constructor( when (failure) { is MatrixIdFailure.InvalidMatrixId, is Failure.UnrecognizedCertificateFailure -> { + setState { copy(isLoading = false) } // Display this error in a dialog _viewEvents.post(OnboardingViewEvents.Failure(failure)) - setState { copy(isLoading = false) } } else -> { setState { copy(isLoading = false) } @@ -589,6 +560,7 @@ class OnboardingViewModel @AssistedInject constructor( onSessionCreated(result, isAccountCreated = false) } catch (failure: Throwable) { setState { copy(isLoading = false) } + _viewEvents.post(OnboardingViewEvents.Failure(failure)) } } } @@ -692,7 +664,7 @@ class OnboardingViewModel @AssistedInject constructor( setState { copy( - asyncHomeServerLoginFlowRequest = Loading(), + isLoading = true, // If user has entered https://matrix.org, ensure that server type is ServerType.MatrixOrg // It is also useful to set the value again in the case of a certificate error on matrix.org serverType = if (homeServerConnectionConfig.homeServerUri.toString() == matrixOrgUrl) { @@ -706,14 +678,14 @@ class OnboardingViewModel @AssistedInject constructor( val data = try { authenticationService.getLoginFlow(homeServerConnectionConfig) } catch (failure: Throwable) { - _viewEvents.post(OnboardingViewEvents.Failure(failure)) setState { copy( - asyncHomeServerLoginFlowRequest = Uninitialized, + isLoading = false, // If we were trying to retrieve matrix.org login flow, also reset the serverType serverType = if (serverType == ServerType.MatrixOrg) ServerType.Unknown else serverType ) } + _viewEvents.post(OnboardingViewEvents.Failure(failure)) null } @@ -734,7 +706,7 @@ class OnboardingViewModel @AssistedInject constructor( setState { copy( - asyncHomeServerLoginFlowRequest = Uninitialized, + isLoading = false, homeServerUrlFromUser = homeServerConnectionConfig.homeServerUri.toString(), homeServerUrl = data.homeServerUrl, loginMode = loginMode, diff --git a/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewState.kt b/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewState.kt index 063c651dfd..b98e811679 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewState.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewState.kt @@ -18,20 +18,14 @@ package im.vector.app.features.onboarding import android.net.Uri import android.os.Parcelable -import com.airbnb.mvrx.Async -import com.airbnb.mvrx.Loading import com.airbnb.mvrx.MavericksState import com.airbnb.mvrx.PersistState -import com.airbnb.mvrx.Uninitialized import im.vector.app.features.login.LoginMode import im.vector.app.features.login.ServerType import im.vector.app.features.login.SignMode import kotlinx.parcelize.Parcelize data class OnboardingViewState( - val asyncHomeServerLoginFlowRequest: Async = Uninitialized, - val asyncResetPassword: Async = Uninitialized, - val asyncResetMailConfirmed: Async = Uninitialized, val isLoading: Boolean = false, @PersistState @@ -68,14 +62,7 @@ data class OnboardingViewState( @PersistState val personalizationState: PersonalizationState = PersonalizationState() -) : MavericksState { - - fun legacyIsLoading(): Boolean { - return asyncHomeServerLoginFlowRequest is Loading || - asyncResetPassword is Loading || - asyncResetMailConfirmed is Loading - } -} +) : MavericksState enum class OnboardingFlow { SignIn, 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 073801c920..c91dcbaf8e 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthResetPasswordFragment.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthResetPasswordFragment.kt @@ -21,8 +21,6 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.lifecycle.lifecycleScope -import com.airbnb.mvrx.Fail -import com.airbnb.mvrx.Loading import com.google.android.material.dialog.MaterialAlertDialogBuilder import im.vector.app.R import im.vector.app.core.extensions.hideKeyboard @@ -53,10 +51,13 @@ class FtueAuthResetPasswordFragment @Inject constructor() : AbstractFtueAuthFrag override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - setupSubmitButton() } + override fun showFailure(throwable: Throwable) { + views.resetPasswordEmailTil.error = errorFormatter.toHumanReadable(throwable) + } + private fun setupUi(state: OnboardingViewState) { views.resetPasswordTitle.text = getString(R.string.login_reset_password_on, state.homeServerUrlFromUser.toReducedUrl()) } @@ -115,16 +116,9 @@ class FtueAuthResetPasswordFragment @Inject constructor() : AbstractFtueAuthFrag override fun updateWithState(state: OnboardingViewState) { setupUi(state) - - when (state.asyncResetPassword) { - is Loading -> { - // Ensure new password is hidden - views.passwordField.hidePassword() - } - is Fail -> { - views.resetPasswordEmailTil.error = errorFormatter.toHumanReadable(state.asyncResetPassword.error) - } - else -> Unit + if (state.isLoading) { + // Ensure new password is hidden + views.passwordField.hidePassword() } } } diff --git a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthResetPasswordMailConfirmationFragment.kt b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthResetPasswordMailConfirmationFragment.kt index f8b3266d37..2b72c88d89 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthResetPasswordMailConfirmationFragment.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthResetPasswordMailConfirmationFragment.kt @@ -20,7 +20,6 @@ import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup -import com.airbnb.mvrx.Fail import com.google.android.material.dialog.MaterialAlertDialogBuilder import im.vector.app.R import im.vector.app.databinding.FragmentLoginResetPasswordMailConfirmationBinding @@ -58,23 +57,20 @@ class FtueAuthResetPasswordMailConfirmationFragment @Inject constructor() : Abst override fun updateWithState(state: OnboardingViewState) { setupUi(state) + } - when (state.asyncResetMailConfirmed) { - is Fail -> { - // Link in email not yet clicked ? - val message = if (state.asyncResetMailConfirmed.error.is401()) { - getString(R.string.auth_reset_password_error_unauthorized) - } else { - errorFormatter.toHumanReadable(state.asyncResetMailConfirmed.error) - } - - MaterialAlertDialogBuilder(requireActivity()) - .setTitle(R.string.dialog_title_error) - .setMessage(message) - .setPositiveButton(R.string.ok, null) - .show() - } - else -> Unit + override fun showFailure(throwable: Throwable) { + // Link in email not yet clicked ? + val message = if (throwable.is401()) { + getString(R.string.auth_reset_password_error_unauthorized) + } else { + errorFormatter.toHumanReadable(throwable) } + + MaterialAlertDialogBuilder(requireActivity()) + .setTitle(R.string.dialog_title_error) + .setMessage(message) + .setPositiveButton(R.string.ok, null) + .show() } } diff --git a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthVariant.kt b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthVariant.kt index b8319c9f3a..2b3a43df5d 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthVariant.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthVariant.kt @@ -121,7 +121,7 @@ class FtueAuthVariant( private fun updateWithState(viewState: OnboardingViewState) { isForceLoginFallbackEnabled = viewState.isForceLoginFallbackEnabled - views.loginLoading.isVisible = viewState.isLoading || viewState.legacyIsLoading() + views.loginLoading.isVisible = viewState.isLoading } override fun setIsLoading(isLoading: Boolean) = Unit From 11cc284bccbf1d370f4e5efdbffe96ea5246b210 Mon Sep 17 00:00:00 2001 From: Adam Brown Date: Tue, 8 Mar 2022 12:02:21 +0000 Subject: [PATCH 085/262] reusing showFailure from the base fragment instead of resubscribing to the view events --- .../ftueauth/FtueAuthLoginFragment.kt | 44 +++++++------------ 1 file changed, 17 insertions(+), 27 deletions(-) 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 a5536b1dcd..60912f754e 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 @@ -72,33 +72,6 @@ class FtueAuthLoginFragment @Inject constructor() : AbstractSSOFtueAuthFragment< override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - viewModel.viewEvents - .stream() - .onEach { - when (it) { - is OnboardingViewEvents.Failure -> { - if (it.throwable is Failure.ServerError && - it.throwable.error.code == MatrixError.M_FORBIDDEN && - it.throwable.error.message.isEmpty()) { - // Login with email, but email unknown - views.loginFieldTil.error = getString(R.string.login_login_with_email_error) - } else { - // Trick to display the error without text. - views.loginFieldTil.error = " " - if (it.throwable.isInvalidPassword() && spaceInPassword()) { - views.passwordFieldTil.error = getString(R.string.auth_invalid_login_param_space_in_password) - } else { - views.passwordFieldTil.error = errorFormatter.toHumanReadable(it.throwable) - } - } - } - else -> { - // do nothing - } - } - } - .launchIn(lifecycleScope) - setupSubmitButton() setupForgottenPasswordButton() @@ -305,6 +278,23 @@ class FtueAuthLoginFragment @Inject constructor() : AbstractSSOFtueAuthFragment< } } + override fun showFailure(throwable: Throwable) { + if (throwable is Failure.ServerError && + throwable.error.code == MatrixError.M_FORBIDDEN && + throwable.error.message.isEmpty()) { + // Login with email, but email unknown + views.loginFieldTil.error = getString(R.string.login_login_with_email_error) + } else { + // Trick to display the error without text. + views.loginFieldTil.error = " " + if (throwable.isInvalidPassword() && spaceInPassword()) { + views.passwordFieldTil.error = getString(R.string.auth_invalid_login_param_space_in_password) + } else { + views.passwordFieldTil.error = errorFormatter.toHumanReadable(throwable) + } + } + } + /** * Detect if password ends or starts with spaces */ From 373385b29fbeeefd8afb510f4799b8506620739b Mon Sep 17 00:00:00 2001 From: Adam Brown Date: Thu, 10 Mar 2022 14:37:17 +0000 Subject: [PATCH 086/262] moving the not accepting registration error handling to the login fragment --- .../android/sdk/api/failure/Extensions.kt | 9 ++- .../ftueauth/AbstractFtueAuthFragment.kt | 19 +----- .../ftueauth/FtueAuthLoginFragment.kt | 58 +++++++++++-------- .../ftueauth/FtueAuthResetPasswordFragment.kt | 2 +- ...thResetPasswordMailConfirmationFragment.kt | 2 +- .../onboarding/OnboardingViewModelTest.kt | 3 - 6 files changed, 46 insertions(+), 47 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/failure/Extensions.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/failure/Extensions.kt index aabe6e0d06..5dc8965b6f 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/failure/Extensions.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/failure/Extensions.kt @@ -64,6 +64,11 @@ fun Throwable.isInvalidPassword(): Boolean { error.message == "Invalid password" } +fun Throwable.isRegistrationDisabled(): Boolean { + return this is Failure.ServerError && error.code == MatrixError.M_FORBIDDEN && + httpCode == HttpsURLConnection.HTTP_FORBIDDEN +} + fun Throwable.isInvalidUIAAuth(): Boolean { return this is Failure.ServerError && error.code == MatrixError.M_FORBIDDEN && @@ -104,8 +109,8 @@ fun Throwable.isRegistrationAvailabilityError(): Boolean { return this is Failure.ServerError && httpCode == HttpsURLConnection.HTTP_BAD_REQUEST && /* 400 */ (error.code == MatrixError.M_USER_IN_USE || - error.code == MatrixError.M_INVALID_USERNAME || - error.code == MatrixError.M_EXCLUSIVE) + error.code == MatrixError.M_INVALID_USERNAME || + error.code == MatrixError.M_EXCLUSIVE) } /** diff --git a/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 f8f6f6cefa..64e29766c5 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/AbstractFtueAuthFragment.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/AbstractFtueAuthFragment.kt @@ -34,8 +34,6 @@ import im.vector.app.features.onboarding.OnboardingViewModel import im.vector.app.features.onboarding.OnboardingViewState import kotlinx.coroutines.CancellationException import org.matrix.android.sdk.api.failure.Failure -import org.matrix.android.sdk.api.failure.MatrixError -import javax.net.ssl.HttpsURLConnection /** * Parent Fragment for all the login/registration screens @@ -85,21 +83,8 @@ abstract class AbstractFtueAuthFragment : VectorBaseFragment /* Ignore this error, user has cancelled the action */ Unit - is Failure.ServerError -> - if (throwable.error.code == MatrixError.M_FORBIDDEN && - throwable.httpCode == HttpsURLConnection.HTTP_FORBIDDEN /* 403 */) { - MaterialAlertDialogBuilder(requireActivity()) - .setTitle(R.string.dialog_title_error) - .setMessage(getString(R.string.login_registration_disabled)) - .setPositiveButton(R.string.ok, null) - .show() - } else { - onError(throwable) - } - is Failure.UnrecognizedCertificateFailure -> - showUnrecognizedCertificateFailure(throwable) - else -> - onError(throwable) + is Failure.UnrecognizedCertificateFailure -> showUnrecognizedCertificateFailure(throwable) + else -> onError(throwable) } } diff --git a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthLoginFragment.kt b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthLoginFragment.kt index 60912f754e..664346953d 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthLoginFragment.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthLoginFragment.kt @@ -26,6 +26,7 @@ import androidx.autofill.HintConstants import androidx.core.text.isDigitsOnly import androidx.core.view.isVisible import androidx.lifecycle.lifecycleScope +import com.google.android.material.dialog.MaterialAlertDialogBuilder import im.vector.app.R import im.vector.app.core.extensions.hideKeyboard import im.vector.app.core.extensions.hidePassword @@ -46,6 +47,7 @@ import kotlinx.coroutines.flow.onEach import org.matrix.android.sdk.api.failure.Failure import org.matrix.android.sdk.api.failure.MatrixError import org.matrix.android.sdk.api.failure.isInvalidPassword +import org.matrix.android.sdk.api.failure.isRegistrationDisabled import reactivecircus.flowbinding.android.widget.textChanges import javax.inject.Inject @@ -254,12 +256,39 @@ class FtueAuthLoginFragment @Inject constructor() : AbstractSSOFtueAuthFragment< } override fun onError(throwable: Throwable) { - // Show M_WEAK_PASSWORD error in the password field - if (throwable is Failure.ServerError && - throwable.error.code == MatrixError.M_WEAK_PASSWORD) { - views.passwordFieldTil.error = errorFormatter.toHumanReadable(throwable) - } else { - views.loginFieldTil.error = errorFormatter.toHumanReadable(throwable) + // Trick to display the error without text. + views.loginFieldTil.error = " " + when { + throwable is Failure.ServerError && + throwable.error.code == MatrixError.M_FORBIDDEN && + throwable.error.message.isEmpty() -> { + // Login with email, but email unknown + views.loginFieldTil.error = getString(R.string.login_login_with_email_error) + } + + throwable is Failure.ServerError && throwable.error.code == MatrixError.M_WEAK_PASSWORD -> { + views.passwordFieldTil.error = errorFormatter.toHumanReadable(throwable) + } + + throwable.isInvalidPassword() && spaceInPassword() -> { + views.passwordFieldTil.error = getString(R.string.auth_invalid_login_param_space_in_password) + } + + throwable.isInvalidPassword() -> { + views.passwordFieldTil.error = errorFormatter.toHumanReadable(throwable) + } + + throwable.isRegistrationDisabled() -> { + MaterialAlertDialogBuilder(requireActivity()) + .setTitle(R.string.dialog_title_error) + .setMessage(getString(R.string.login_registration_disabled)) + .setPositiveButton(R.string.ok, null) + .show() + } + + else -> { + super.onError(throwable) + } } } @@ -278,23 +307,6 @@ class FtueAuthLoginFragment @Inject constructor() : AbstractSSOFtueAuthFragment< } } - override fun showFailure(throwable: Throwable) { - if (throwable is Failure.ServerError && - throwable.error.code == MatrixError.M_FORBIDDEN && - throwable.error.message.isEmpty()) { - // Login with email, but email unknown - views.loginFieldTil.error = getString(R.string.login_login_with_email_error) - } else { - // Trick to display the error without text. - views.loginFieldTil.error = " " - if (throwable.isInvalidPassword() && spaceInPassword()) { - views.passwordFieldTil.error = getString(R.string.auth_invalid_login_param_space_in_password) - } else { - views.passwordFieldTil.error = errorFormatter.toHumanReadable(throwable) - } - } - } - /** * Detect if password ends or starts with spaces */ 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 c91dcbaf8e..b612ec34b5 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthResetPasswordFragment.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthResetPasswordFragment.kt @@ -54,7 +54,7 @@ class FtueAuthResetPasswordFragment @Inject constructor() : AbstractFtueAuthFrag setupSubmitButton() } - override fun showFailure(throwable: Throwable) { + override fun onError(throwable: Throwable) { views.resetPasswordEmailTil.error = errorFormatter.toHumanReadable(throwable) } 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 2b72c88d89..f6141a4900 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthResetPasswordMailConfirmationFragment.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthResetPasswordMailConfirmationFragment.kt @@ -59,7 +59,7 @@ class FtueAuthResetPasswordMailConfirmationFragment @Inject constructor() : Abst setupUi(state) } - override fun showFailure(throwable: Throwable) { + override fun onError(throwable: Throwable) { // Link in email not yet clicked ? val message = if (throwable.is401()) { getString(R.string.auth_reset_password_error_unauthorized) diff --git a/vector/src/test/java/im/vector/app/features/onboarding/OnboardingViewModelTest.kt b/vector/src/test/java/im/vector/app/features/onboarding/OnboardingViewModelTest.kt index 09cefc5ff2..df4e0de65e 100644 --- a/vector/src/test/java/im/vector/app/features/onboarding/OnboardingViewModelTest.kt +++ b/vector/src/test/java/im/vector/app/features/onboarding/OnboardingViewModelTest.kt @@ -17,9 +17,6 @@ package im.vector.app.features.onboarding import android.net.Uri -import com.airbnb.mvrx.Loading -import com.airbnb.mvrx.Success -import com.airbnb.mvrx.Uninitialized import com.airbnb.mvrx.test.MvRxTestRule import im.vector.app.features.login.ReAuthHelper import im.vector.app.features.login.SignMode From 5aa35899bc302b3854df6cff0a58a0af67ace3a5 Mon Sep 17 00:00:00 2001 From: Adam Brown Date: Fri, 11 Mar 2022 12:52:09 +0000 Subject: [PATCH 087/262] extracting error cases to extensions and handles invalid username as a specific case --- .../android/sdk/api/failure/Extensions.kt | 15 ++++++++++ .../ftueauth/FtueAuthLoginFragment.kt | 29 +++++++------------ 2 files changed, 26 insertions(+), 18 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/failure/Extensions.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/failure/Extensions.kt index 5dc8965b6f..cfae056774 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/failure/Extensions.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/failure/Extensions.kt @@ -58,6 +58,11 @@ fun Throwable.getRetryDelay(defaultValue: Long): Long { ?: defaultValue } +fun Throwable.isInvalidUsername(): Boolean { + return this is Failure.ServerError && + error.code == MatrixError.M_INVALID_USERNAME +} + fun Throwable.isInvalidPassword(): Boolean { return this is Failure.ServerError && error.code == MatrixError.M_FORBIDDEN && @@ -69,6 +74,16 @@ fun Throwable.isRegistrationDisabled(): Boolean { httpCode == HttpsURLConnection.HTTP_FORBIDDEN } +fun Throwable.isWeakPassword(): Boolean { + return this is Failure.ServerError && error.code == MatrixError.M_WEAK_PASSWORD +} + +fun Throwable.isLoginEmailUnknown(): Boolean { + return this is Failure.ServerError && + error.code == MatrixError.M_FORBIDDEN && + error.message.isEmpty() +} + fun Throwable.isInvalidUIAAuth(): Boolean { return this is Failure.ServerError && error.code == MatrixError.M_FORBIDDEN && 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 664346953d..192eb4d5f4 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 @@ -44,10 +44,11 @@ import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach -import org.matrix.android.sdk.api.failure.Failure -import org.matrix.android.sdk.api.failure.MatrixError import org.matrix.android.sdk.api.failure.isInvalidPassword +import org.matrix.android.sdk.api.failure.isInvalidUsername +import org.matrix.android.sdk.api.failure.isLoginEmailUnknown import org.matrix.android.sdk.api.failure.isRegistrationDisabled +import org.matrix.android.sdk.api.failure.isWeakPassword import reactivecircus.flowbinding.android.widget.textChanges import javax.inject.Inject @@ -259,34 +260,26 @@ class FtueAuthLoginFragment @Inject constructor() : AbstractSSOFtueAuthFragment< // Trick to display the error without text. views.loginFieldTil.error = " " when { - throwable is Failure.ServerError && - throwable.error.code == MatrixError.M_FORBIDDEN && - throwable.error.message.isEmpty() -> { - // Login with email, but email unknown + throwable.isInvalidUsername() -> { + views.loginFieldTil.error = errorFormatter.toHumanReadable(throwable) + } + throwable.isLoginEmailUnknown() -> { views.loginFieldTil.error = getString(R.string.login_login_with_email_error) } - - throwable is Failure.ServerError && throwable.error.code == MatrixError.M_WEAK_PASSWORD -> { - views.passwordFieldTil.error = errorFormatter.toHumanReadable(throwable) - } - - throwable.isInvalidPassword() && spaceInPassword() -> { + throwable.isInvalidPassword() && spaceInPassword() -> { views.passwordFieldTil.error = getString(R.string.auth_invalid_login_param_space_in_password) } - - throwable.isInvalidPassword() -> { + throwable.isWeakPassword() || throwable.isInvalidPassword() -> { views.passwordFieldTil.error = errorFormatter.toHumanReadable(throwable) } - - throwable.isRegistrationDisabled() -> { + throwable.isRegistrationDisabled() -> { MaterialAlertDialogBuilder(requireActivity()) .setTitle(R.string.dialog_title_error) .setMessage(getString(R.string.login_registration_disabled)) .setPositiveButton(R.string.ok, null) .show() } - - else -> { + else -> { super.onError(throwable) } } From e7a1c20132c25b6629b0395a8d4a088a419b56df Mon Sep 17 00:00:00 2001 From: Adam Brown Date: Fri, 11 Mar 2022 13:22:03 +0000 Subject: [PATCH 088/262] handling the username in use as a username field error --- .../matrix/android/sdk/api/failure/Extensions.kt | 4 ++++ .../onboarding/ftueauth/FtueAuthLoginFragment.kt | 13 +++++++------ 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/failure/Extensions.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/failure/Extensions.kt index cfae056774..89b4a343dd 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/failure/Extensions.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/failure/Extensions.kt @@ -58,6 +58,10 @@ fun Throwable.getRetryDelay(defaultValue: Long): Long { ?: defaultValue } +fun Throwable.isUsernameInUse(): Boolean { + return this is Failure.ServerError && error.code == MatrixError.M_USER_IN_USE +} + fun Throwable.isInvalidUsername(): Boolean { return this is Failure.ServerError && error.code == MatrixError.M_INVALID_USERNAME 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 192eb4d5f4..53c9fd4fcc 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthLoginFragment.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthLoginFragment.kt @@ -48,6 +48,7 @@ import org.matrix.android.sdk.api.failure.isInvalidPassword import org.matrix.android.sdk.api.failure.isInvalidUsername import org.matrix.android.sdk.api.failure.isLoginEmailUnknown import org.matrix.android.sdk.api.failure.isRegistrationDisabled +import org.matrix.android.sdk.api.failure.isUsernameInUse import org.matrix.android.sdk.api.failure.isWeakPassword import reactivecircus.flowbinding.android.widget.textChanges import javax.inject.Inject @@ -260,26 +261,26 @@ class FtueAuthLoginFragment @Inject constructor() : AbstractSSOFtueAuthFragment< // Trick to display the error without text. views.loginFieldTil.error = " " when { - throwable.isInvalidUsername() -> { + throwable.isUsernameInUse() || throwable.isInvalidUsername() -> { views.loginFieldTil.error = errorFormatter.toHumanReadable(throwable) } - throwable.isLoginEmailUnknown() -> { + throwable.isLoginEmailUnknown() -> { views.loginFieldTil.error = getString(R.string.login_login_with_email_error) } - throwable.isInvalidPassword() && spaceInPassword() -> { + throwable.isInvalidPassword() && spaceInPassword() -> { views.passwordFieldTil.error = getString(R.string.auth_invalid_login_param_space_in_password) } - throwable.isWeakPassword() || throwable.isInvalidPassword() -> { + throwable.isWeakPassword() || throwable.isInvalidPassword() -> { views.passwordFieldTil.error = errorFormatter.toHumanReadable(throwable) } - throwable.isRegistrationDisabled() -> { + throwable.isRegistrationDisabled() -> { MaterialAlertDialogBuilder(requireActivity()) .setTitle(R.string.dialog_title_error) .setMessage(getString(R.string.login_registration_disabled)) .setPositiveButton(R.string.ok, null) .show() } - else -> { + else -> { super.onError(throwable) } } From af90adadf3d7ea8d1fafe24e93c2bff1d17fb214 Mon Sep 17 00:00:00 2001 From: Adam Brown Date: Fri, 11 Mar 2022 13:23:34 +0000 Subject: [PATCH 089/262] adding changelog entry --- changelog.d/5517.misc | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/5517.misc diff --git a/changelog.d/5517.misc b/changelog.d/5517.misc new file mode 100644 index 0000000000..18269afcc6 --- /dev/null +++ b/changelog.d/5517.misc @@ -0,0 +1 @@ +Flattening the asynchronous onboarding state and passing all errors through the same pipeline \ No newline at end of file From 7a575ed062f0d7576d11837738fd06310a10cfd1 Mon Sep 17 00:00:00 2001 From: Onuray Sahin Date: Thu, 24 Mar 2022 13:10:06 +0300 Subject: [PATCH 090/262] Lint fixes. --- .../im/vector/app/features/location/LocationSharingViewModel.kt | 1 - 1 file changed, 1 deletion(-) 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 347aad7db9..dfa936dcaa 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 @@ -37,7 +37,6 @@ import kotlinx.coroutines.launch import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.util.toMatrixItem -import timber.log.Timber /** * Sampling period to compare target location and user location. From 481274088ca6b028ea66fb8259f94c761c0dafd3 Mon Sep 17 00:00:00 2001 From: ravit Date: Thu, 24 Mar 2022 13:16:12 +0000 Subject: [PATCH 091/262] Translated using Weblate (Hebrew) Currently translated at 95.5% (2075 of 2171 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/he/ --- vector/src/main/res/values-iw/strings.xml | 62 +++++++++++++++++++++-- 1 file changed, 59 insertions(+), 3 deletions(-) diff --git a/vector/src/main/res/values-iw/strings.xml b/vector/src/main/res/values-iw/strings.xml index b70034888f..d5e8bf021e 100644 --- a/vector/src/main/res/values-iw/strings.xml +++ b/vector/src/main/res/values-iw/strings.xml @@ -1521,7 +1521,7 @@ זוהי ההתחלה של היסטוריית ההודעות הישירות שלך עם%s. זו תחילתה של השיחה הזו. זו ההתחלה ש ל%s. - הצתרפתם. + הצטרפתם. %s הצטרף. יצרת ותגדרת את החדר. %s יצר את החדר והגדיר אותו. @@ -2361,9 +2361,9 @@ \n ניתן לקרוא את כל התנאים %s.
    התוצאה הסופית מתבססת על הצבעה %1$d - + התוצאה הסופית מתבססת על הצבעות %1$d - + בחר אילו מרחבים יכולים לגשת לחדר זה. בבחירת מרחב החברים יוכלו למצוא ולהצטרף לחדר. חברים במרחב %s יכולים למצוא , לצפות ולהצטרף. @@ -2417,4 +2417,60 @@ חדר@ הודעות קבוצתיות הודעות ישירות מוצפנות + ההזמנה למרחב זה נשלחה מ%s אשר אינו משוייך לחשבונך + ההזמנה לחדר זה נשלחה מ%s אשר אינו משוייך לחשבונך + הודעה קולית (%1$s) + לא ניתן להשיב או לערוך במהלך הודעה קולית + לא ניתן להקליט את ההודעה הקולית + לא ניתן לנגן את ההודעה הקולית + לחץ על הקלטה כדי לעצור או להאזין + נשארו עוד %1$d שניות + החזק להקלטה, שחרר לשיחה + נגן הודעה קולית + השהה הודעה קולית + מחיקת הקלטה + מקליט הודעה קולית + עצור הקלטה + החלק לביטול + הקלט שיחה קולית + סליחה , אירעה שגיאה בזמן נסיון ההצטרפות לחדר:%s + שדרג לגרסת החדר המומלצת + חדר זה נמצא בשדרוג גרסה %s , לכן הוא לא יציב . + נדרשת הרשאה לשדרוג החדר + אחראי המרחב מעודכן אוטומטית + הזמן משתמשים אוטומטית + תשדרג את החדר מ%1$s ל%2$s. + שדרג חדר פרטי + שדרג חדר ציבורי + נדרש שדרוג + שדרוג + אנא המתן, זה יכול לקחת קצת זמן. + הצטרפו לחדר שהוחלף + + כניסה %d + שתי כניסות %d + %d כניסות + %d כניסות + + ההצפנה מוגדרת בצורה שגויה + שרשורים מתקרבים לבטא 🎉 + שלח את המדיה בגודלה המקורי + + שלח סרט וידאו בגודל המקורי + שלח שני סרטי וידאו בגודל המקורי + שלח סרטי וידאו בגודלם המקורי + שלח סרט וידאו בגודל המקורי + + לא יציב + יציב + גרסת ברירת מחדל + גרסאות חדר 👓 + המגבלה אינה ידועה. + שרת הבית שלך מקבל קבצים מצורפים (קבצים, מדיה וכו\') בגודל של עד %s. + מגבלת העלאת קובץ לשרת + גרסת שרת + שם שרת + ביטול צבע + שחזור הצפנה + אנא צור קשר עם מנהל המערכת לשחזור ההצפנה למצב תקין . \ No newline at end of file From 516e548fcd85c2419f4ba0257f1e34e41821523d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=AD=D0=B4=D1=83=D0=B0=D1=80=D0=B4=20=D0=93=D0=B5=D1=80?= =?UTF-8?q?=D0=B0?= Date: Thu, 24 Mar 2022 08:25:18 +0000 Subject: [PATCH 092/262] Translated using Weblate (Hebrew) Currently translated at 95.5% (2075 of 2171 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/he/ --- vector/src/main/res/values-iw/strings.xml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/vector/src/main/res/values-iw/strings.xml b/vector/src/main/res/values-iw/strings.xml index d5e8bf021e..da6f15ff34 100644 --- a/vector/src/main/res/values-iw/strings.xml +++ b/vector/src/main/res/values-iw/strings.xml @@ -2473,4 +2473,8 @@ ביטול צבע שחזור הצפנה אנא צור קשר עם מנהל המערכת לשחזור ההצפנה למצב תקין . + חדר ללא שם + חלק מהחדרים עשויים להיות מוסתרים כי הם פרטיים ואתה זקוק להזמנה. + חלק מהחדרים עשויים להיות מוסתרים כי הם פרטיים ואתה זקוק להזמנה. +\nאין לך הרשאה להוסיף חדרים. \ No newline at end of file From 3525d82733a76ee0c04392074f37538e89451ca6 Mon Sep 17 00:00:00 2001 From: Michael Kaye <1917473+michaelkaye@users.noreply.github.com> Date: Wed, 23 Mar 2022 15:23:12 +0000 Subject: [PATCH 093/262] Make test use quoted strings --- .github/workflows/nightly.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index 59f4169452..b8f9f83f8e 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -335,5 +335,5 @@ jobs: github_token: ${{ secrets.GITHUB_TOKEN }} matrix_access_token: ${{ secrets.ELEMENT_ANDROID_NOTIFICATION_ACCESS_TOKEN }} matrix_room_id: ${{ secrets.ELEMENT_ANDROID_INTERNAL_ROOM_ID }} - text_template: "{{#if ${{ github.event_name }} == 'schedule' }}Nightly test run{{else}}Test run (on ${{ github.ref }}){{/if }}: {{#each job_statuses }}{{#with this }}{{#if completed }} {{name}} {{conclusion}} at {{completed_at}}, {{/if}}{{/with}}{{/each}}" - html_template: "{{#if ${{ github.event_name }} == 'schedule' }}Nightly test run{{else}}Test run (on ${{ github.ref }}){{/if}}: {{#each job_statuses }}{{#with this }}{{#if completed }}
    {{icon conclusion}} {{name}} {{conclusion}} at {{completed_at}} [details]{{/if}}{{/with}}{{/each}}" + text_template: "{{#if '${{ github.event_name }}' == 'schedule' }}Nightly test run{{else}}Test run (on ${{ github.ref }}){{/if }}: {{#each job_statuses }}{{#with this }}{{#if completed }} {{name}} {{conclusion}} at {{completed_at}}, {{/if}}{{/with}}{{/each}}" + html_template: "{{#if '${{ github.event_name }}' == 'schedule' }}Nightly test run{{else}}Test run (on ${{ github.ref }}){{/if}}: {{#each job_statuses }}{{#with this }}{{#if completed }}
    {{icon conclusion}} {{name}} {{conclusion}} at {{completed_at}} [details]{{/if}}{{/with}}{{/each}}" From 164ac0ee80cebea2fbc86cf5721eccb5bcd1d928 Mon Sep 17 00:00:00 2001 From: Michael Kaye <1917473+michaelkaye@users.noreply.github.com> Date: Thu, 24 Mar 2022 13:40:52 +0000 Subject: [PATCH 094/262] Update nightly.yml Clean up formatting of if --- .github/workflows/nightly.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index b8f9f83f8e..4efca05d36 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -336,4 +336,4 @@ jobs: matrix_access_token: ${{ secrets.ELEMENT_ANDROID_NOTIFICATION_ACCESS_TOKEN }} matrix_room_id: ${{ secrets.ELEMENT_ANDROID_INTERNAL_ROOM_ID }} text_template: "{{#if '${{ github.event_name }}' == 'schedule' }}Nightly test run{{else}}Test run (on ${{ github.ref }}){{/if }}: {{#each job_statuses }}{{#with this }}{{#if completed }} {{name}} {{conclusion}} at {{completed_at}}, {{/if}}{{/with}}{{/each}}" - html_template: "{{#if '${{ github.event_name }}' == 'schedule' }}Nightly test run{{else}}Test run (on ${{ github.ref }}){{/if}}: {{#each job_statuses }}{{#with this }}{{#if completed }}
    {{icon conclusion}} {{name}} {{conclusion}} at {{completed_at}} [details]{{/if}}{{/with}}{{/each}}" + html_template: "{{#if '${{ github.event_name }}' == 'schedule' }}Nightly test run{{else}}Test run (on ${{ github.ref }}){{/if }}: {{#each job_statuses }}{{#with this }}{{#if completed }}
    {{icon conclusion}} {{name}} {{conclusion}} at {{completed_at}} [details]{{/if}}{{/with}}{{/each}}" From 3182c60d132c69e7876ad8a36f262fb03ed2599f Mon Sep 17 00:00:00 2001 From: Michael Kaye <1917473+michaelkaye@users.noreply.github.com> Date: Thu, 24 Mar 2022 13:42:25 +0000 Subject: [PATCH 095/262] Quoe 'schedule' correctly. --- .github/workflows/nightly.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index 4efca05d36..f4bcef2d4e 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -293,7 +293,7 @@ jobs: sonarqube: name: Sonarqube upload runs-on: macos-latest - if: always() && github.event_name == "schedule" + if: always() && github.event_name == 'schedule' needs: - codecov-units steps: From 3c7495bd605da1893f9a2055e511373ac3fdfb0a Mon Sep 17 00:00:00 2001 From: ariskotsomitopoulos Date: Thu, 24 Mar 2022 13:50:07 +0200 Subject: [PATCH 096/262] Thread redaction will now update the thread summary counter Root threads with 0 threads replies will become normal messages and removed from thread summaries --- .../api/session/events/model/UnsignedData.kt | 2 + .../database/helper/ThreadEventsHelper.kt | 51 +++++++++++++++---- .../room/prune/RedactionEventProcessor.kt | 38 ++++++++++++++ 3 files changed, 82 insertions(+), 9 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/UnsignedData.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/UnsignedData.kt index dfe1db7b1c..630a2fb91a 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/UnsignedData.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/UnsignedData.kt @@ -46,3 +46,5 @@ data class UnsignedData( @Json(name = "replaces_state") val replacesState: String? = null ) + +fun UnsignedData?.isRedacted() = this?.redactedEvent != null diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/ThreadEventsHelper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/ThreadEventsHelper.kt index ee3008d40b..0e85057f23 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/ThreadEventsHelper.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/ThreadEventsHelper.kt @@ -16,9 +16,12 @@ package org.matrix.android.sdk.internal.database.helper +import com.squareup.moshi.JsonDataException import io.realm.Realm import io.realm.RealmQuery import io.realm.Sort +import org.matrix.android.sdk.api.session.events.model.UnsignedData +import org.matrix.android.sdk.api.session.events.model.isRedacted import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent import org.matrix.android.sdk.api.session.threads.ThreadNotificationState import org.matrix.android.sdk.internal.database.mapper.asDomain @@ -33,6 +36,8 @@ import org.matrix.android.sdk.internal.database.query.findIncludingEvent import org.matrix.android.sdk.internal.database.query.findLastForwardChunkOfRoom import org.matrix.android.sdk.internal.database.query.where import org.matrix.android.sdk.internal.database.query.whereRoomId +import org.matrix.android.sdk.internal.di.MoshiProvider +import timber.log.Timber private typealias Summary = Pair? @@ -90,19 +95,18 @@ internal fun EventEntity.markEventAsRoot( /** * Count the number of threads for the provided root thread eventId, and finds the latest event message + * note: Redactions are handled by RedactionEventProcessor * @param rootThreadEventId The root eventId that will find the number of threads * @return A ThreadSummary containing the counted threads and the latest event message */ internal fun EventEntity.threadSummaryInThread(realm: Realm, rootThreadEventId: String, chunkEntity: ChunkEntity?): Summary { - // Number of messages - val messages = TimelineEventEntity - .whereRoomId(realm, roomId = roomId) - .equalTo(TimelineEventEntityFields.ROOT.ROOT_THREAD_EVENT_ID, rootThreadEventId) - .distinct(TimelineEventEntityFields.ROOT.EVENT_ID) - .count() - .toInt() + val numberOfThread = countThreads( + realm = realm, + roomId = roomId, + rootThreadEventId = rootThreadEventId + ) ?: return null - if (messages <= 0) return null + if (numberOfThread <= 0) return null // Find latest thread event, we know it exists var chunk = ChunkEntity.findLastForwardChunkOfRoom(realm, roomId) ?: chunkEntity ?: return null @@ -124,9 +128,38 @@ internal fun EventEntity.threadSummaryInThread(realm: Realm, rootThreadEventId: result ?: return null - return Summary(messages, result) + return Summary(numberOfThread, result) } +/** + * Counts the number of threads in the main timeline thread summary, + * with respect to redactions. + */ +internal fun countThreads(realm: Realm, roomId: String, rootThreadEventId: String): Int? = + TimelineEventEntity + .whereRoomId(realm, roomId = roomId) + .equalTo(TimelineEventEntityFields.ROOT.ROOT_THREAD_EVENT_ID, rootThreadEventId) + .distinct(TimelineEventEntityFields.ROOT.EVENT_ID) + .findAll() + ?.filterNot { timelineEvent -> + timelineEvent.root + ?.unsignedData + ?.takeIf { it.isNotBlank() } + ?.toUnsignedData() + .isRedacted() + }?.size + +/** + * Mapping string to UnsignedData using Moshi + */ +private fun String.toUnsignedData(): UnsignedData? = + try { + MoshiProvider.providesMoshi().adapter(UnsignedData::class.java).fromJson(this) + } catch (ex: JsonDataException) { + Timber.e(ex, "Failed to parse UnsignedData") + null + } + /** * Lets compare them in case user is moving forward in the timeline and we cannot know the * exact chunk sequence while currentChunk is not yet committed in the DB diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/prune/RedactionEventProcessor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/prune/RedactionEventProcessor.kt index 4753e12157..4fcc47a8d4 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/prune/RedactionEventProcessor.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/prune/RedactionEventProcessor.kt @@ -21,11 +21,14 @@ import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.events.model.LocalEcho import org.matrix.android.sdk.api.session.events.model.UnsignedData +import org.matrix.android.sdk.internal.database.helper.countThreads +import org.matrix.android.sdk.internal.database.helper.findRootThreadEvent import org.matrix.android.sdk.internal.database.mapper.ContentMapper import org.matrix.android.sdk.internal.database.mapper.EventMapper import org.matrix.android.sdk.internal.database.model.EventEntity import org.matrix.android.sdk.internal.database.model.EventInsertType import org.matrix.android.sdk.internal.database.model.TimelineEventEntity +import org.matrix.android.sdk.internal.database.model.threads.ThreadSummaryEntity import org.matrix.android.sdk.internal.database.query.findWithSenderMembershipEvent import org.matrix.android.sdk.internal.database.query.where import org.matrix.android.sdk.internal.di.MoshiProvider @@ -89,6 +92,8 @@ internal class RedactionEventProcessor @Inject constructor() : EventInsertLivePr eventToPrune.unsignedData = MoshiProvider.providesMoshi().adapter(UnsignedData::class.java).toJson(modified) eventToPrune.decryptionResultJson = null eventToPrune.decryptionErrorCode = null + + handleTimelineThreadSummaryIfNeeded(realm, eventToPrune, isLocalEcho) } // EventType.REACTION -> { // eventRelationsAggregationUpdater.handleReactionRedact(eventToPrune, realm, userId) @@ -104,6 +109,39 @@ internal class RedactionEventProcessor @Inject constructor() : EventInsertLivePr } } + /** + * Invalidates the number of threads in the main timeline thread summary, + * with respect to redactions. + */ + private fun handleTimelineThreadSummaryIfNeeded( + realm: Realm, + eventToPrune: EventEntity, + isLocalEcho: Boolean, + ) { + if (eventToPrune.isThread() && !isLocalEcho) { + val roomId = eventToPrune.roomId + val rootThreadEvent = eventToPrune.findRootThreadEvent() ?: return + val rootThreadEventId = eventToPrune.rootThreadEventId ?: return + + val numberOfThreads = countThreads( + realm = realm, + roomId = roomId, + rootThreadEventId = rootThreadEventId + ) ?: return + + rootThreadEvent.numberOfThreads = numberOfThreads + if (numberOfThreads == 0) { + // We should also clear the thread summary list + rootThreadEvent.isRootThread = false + rootThreadEvent.threadSummaryLatestMessage = null + ThreadSummaryEntity + .where(realm, roomId = roomId, rootThreadEventId) + .findFirst() + ?.deleteFromRealm() + } + } + } + private fun computeAllowedKeys(type: String): List { // Add filtered content, allowed keys in content depends on the event type return when (type) { From 47415a8ef11f304111b5f9a9b628fcf6b8979d72 Mon Sep 17 00:00:00 2001 From: random Date: Wed, 23 Mar 2022 15:17:14 +0000 Subject: [PATCH 097/262] Translated using Weblate (Italian) Currently translated at 100.0% (2171 of 2171 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/it/ --- vector/src/main/res/values-it/strings.xml | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/vector/src/main/res/values-it/strings.xml b/vector/src/main/res/values-it/strings.xml index 492d8af526..d14aa3c671 100644 --- a/vector/src/main/res/values-it/strings.xml +++ b/vector/src/main/res/values-it/strings.xml @@ -494,7 +494,7 @@ Per segnalare un errore agita il dispositivo con rabbia Sicuro di voler fare una chiamata audio\? Sicuro di voler fare una videochiamata\? - Elenco dei membri + Membri %d utente %d utenti @@ -1948,7 +1948,7 @@ Sei stato invitato Gli Spazi sono un nuovo modo per raggruppare stanze e contatti. Aggiungi stanze e Spazi esistenti - Esci dallo Spazio + Esci Aggiungi stanze Guarda le stanze @@ -2410,4 +2410,23 @@ %1$s e %2$s %1$s, %2$s e altri + Ferma + Posizione in tempo reale attivata + Se vuoi condividere la tua posizione in tempo reale, ${app_name} richiede l\'accesso alla posizione costantemente quando l\'app è in secondo piano. +\nAccederemo alla tua posizione solo per la durata che scegli. + Permetti accesso + Condividi questa posizione + Condividi questa posizione + Condividi posizione in tempo reale + Condividi posizione in tempo reale + Condividi la mia posizione attuale + Condividi la mia posizione attuale + Ingrandisci alla posizione attuale + Puntina della posizione selezionata sulla mappa + Siamo vicini ad iniziare una beta pubblica delle conversazioni. +\n +\nMentre ci prepariamo, dobbiamo fare alcune modifiche: le conversazioni create prima di questo momento saranno mostrate come normali risposte. +\n +\nSarà una transizione una-tantum, dato che le conversazioni ora sono parte delle specifiche di Matrix. + I messaggi in conversazioni entrano in beta 🎉 \ No newline at end of file From ce28da3ae4d27cf58835445b6184a4349e412fc3 Mon Sep 17 00:00:00 2001 From: LinAGKar Date: Wed, 23 Mar 2022 21:24:30 +0000 Subject: [PATCH 098/262] Translated using Weblate (Swedish) Currently translated at 99.3% (2157 of 2171 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/sv/ --- vector/src/main/res/values-sv/strings.xml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/vector/src/main/res/values-sv/strings.xml b/vector/src/main/res/values-sv/strings.xml index 69c25ebba3..11c38830f6 100644 --- a/vector/src/main/res/values-sv/strings.xml +++ b/vector/src/main/res/values-sv/strings.xml @@ -378,7 +378,7 @@ Ta bort Gå med Avslå - Lista medlemmar + Medlemmar Hoppa till oläst %d medlem @@ -2419,4 +2419,5 @@ %d server-ACL-ändring %d server-ACL-ändringar + Trådar närmar sig beta 🎉 \ No newline at end of file From f3deb3e16048da774b970493b8c0154f52db3a1c Mon Sep 17 00:00:00 2001 From: random Date: Wed, 23 Mar 2022 15:19:31 +0000 Subject: [PATCH 099/262] Translated using Weblate (Italian) Currently translated at 100.0% (52 of 52 strings) Translation: Element Android/Element Android Store Translate-URL: https://translate.element.io/projects/element-android/element-store/it/ --- fastlane/metadata/android/it-IT/changelogs/40104040.txt | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 fastlane/metadata/android/it-IT/changelogs/40104040.txt diff --git a/fastlane/metadata/android/it-IT/changelogs/40104040.txt b/fastlane/metadata/android/it-IT/changelogs/40104040.txt new file mode 100644 index 0000000000..ef63e614cb --- /dev/null +++ b/fastlane/metadata/android/it-IT/changelogs/40104040.txt @@ -0,0 +1,2 @@ +Modifiche principali in questa versione: agg.mento indicatore scrittura. Correzioni errori e miglioramenti stabilità. +Cronologia completa: https://github.com/vector-im/element-android/releases/tag/v1.4.4 From 7352faa2d975010c80e931ca04f5fb84cef2c381 Mon Sep 17 00:00:00 2001 From: lvre <7uu3qrbvm@relay.firefox.com> Date: Tue, 22 Mar 2022 20:58:01 +0000 Subject: [PATCH 100/262] Translated using Weblate (Portuguese (Brazil)) Currently translated at 100.0% (2171 of 2171 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/pt_BR/ --- vector/src/main/res/values-pt-rBR/strings.xml | 23 +++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/vector/src/main/res/values-pt-rBR/strings.xml b/vector/src/main/res/values-pt-rBR/strings.xml index f2eaf8d993..d06937fb64 100644 --- a/vector/src/main/res/values-pt-rBR/strings.xml +++ b/vector/src/main/res/values-pt-rBR/strings.xml @@ -529,7 +529,7 @@ %d mudança de filiação %d mudanças de filiação - Listar membros + Membros %d mensagem notificada não-lida %d mensagens notificadas não-lidas @@ -2016,7 +2016,7 @@ Você é a/o única(o) admin deste espaço. Sair dele vai significar que ninguém tem controle sobre ele. Você não vai ser capaz de se rejuntar a menos que você seja re-convidada(o). Você é a única pessoa aqui. Se você sair, ninguém vai ser capaz de se juntar no futuro, incluindo você. - Sair de Espaço + Sair Adicionar salas Explorar salas @@ -2419,4 +2419,23 @@ %1$s, %2$s e outras(os) %1$s e %2$s + Localização ao vivo habilitada + Se você gostaria de compartilhar sua localização Ao Vivo, ${app_name} precisa de acesso de localização todo o tempo quando o app está no background. +\nNós vamos somente acessar sua localização pela duração que você escolher. + Compartilhar esta localização + Compartilhar esta localização + Compartilhar localização ao vivo + Compartilhar localização ao vivo + Compartilhar minha localização atual + Compartilhar minha localização atual + Zoom para localização atual + Pin de localização selecionada em mapa + Parar + Permitir acesso + Nós estamos ficando mais perto de lançar uma Beta pública para Threads. +\n +\nEnquanto nos preparamos para isso, nós precisamos fazer algumas mudanças: threads criadas antes deste ponto vão ser exibidas como respostas regulares. +\n +\nIsto vai ser uma transição única visto que Threads são agora parte da especificação Matrix. + Threads Aproximando-Se a Beta 🎉 \ No newline at end of file From 56fd8983c9c680783b197e082f50af0f74691ae2 Mon Sep 17 00:00:00 2001 From: Jozef Gaal Date: Tue, 22 Mar 2022 20:03:07 +0000 Subject: [PATCH 101/262] Translated using Weblate (Slovak) Currently translated at 100.0% (52 of 52 strings) Translation: Element Android/Element Android Store Translate-URL: https://translate.element.io/projects/element-android/element-store/sk/ --- fastlane/metadata/android/sk/changelogs/40104040.txt | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 fastlane/metadata/android/sk/changelogs/40104040.txt diff --git a/fastlane/metadata/android/sk/changelogs/40104040.txt b/fastlane/metadata/android/sk/changelogs/40104040.txt new file mode 100644 index 0000000000..67f04df995 --- /dev/null +++ b/fastlane/metadata/android/sk/changelogs/40104040.txt @@ -0,0 +1,2 @@ +Hlavné zmeny v tejto verzii: aktualizácie používateľského rozhrania indikátora písania. Rôzne opravy chýb a vylepšenia stability. +Úplný zoznam zmien: https://github.com/vector-im/element-android/releases/tag/v1.4.4 From b9d10c9d6e03b7ad00e8e6dd601bc73969138146 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=AD=D0=B4=D1=83=D0=B0=D1=80=D0=B4=20=D0=93=D0=B5=D1=80?= =?UTF-8?q?=D0=B0?= Date: Thu, 24 Mar 2022 13:42:13 +0000 Subject: [PATCH 102/262] Translated using Weblate (Hebrew) Currently translated at 96.4% (2095 of 2171 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/he/ --- vector/src/main/res/values-iw/strings.xml | 47 +++++++++++++++++++++-- 1 file changed, 44 insertions(+), 3 deletions(-) diff --git a/vector/src/main/res/values-iw/strings.xml b/vector/src/main/res/values-iw/strings.xml index da6f15ff34..fcc956ba35 100644 --- a/vector/src/main/res/values-iw/strings.xml +++ b/vector/src/main/res/values-iw/strings.xml @@ -2361,9 +2361,9 @@ \n ניתן לקרוא את כל התנאים %s. התוצאה הסופית מתבססת על הצבעה %1$d - - התוצאה הסופית מתבססת על הצבעות %1$d - + התוצאות הסופיות מתבססות על %1$d הצבעות + התוצאות הסופיות מתבססות על %1$d הצבעות + התוצאות הסופיות מתבססות על %1$d הצבעות בחר אילו מרחבים יכולים לגשת לחדר זה. בבחירת מרחב החברים יוכלו למצוא ולהצטרף לחדר. חברים במרחב %s יכולים למצוא , לצפות ולהצטרף. @@ -2477,4 +2477,45 @@ חלק מהחדרים עשויים להיות מוסתרים כי הם פרטיים ואתה זקוק להזמנה. חלק מהחדרים עשויים להיות מוסתרים כי הם פרטיים ואתה זקוק להזמנה. \nאין לך הרשאה להוסיף חדרים. + + הצבעה %1$d. הצביעו כדי לראות את התוצאות + הצבעות %1$d. הצביעו כדי לראות את התוצאות + הצבעות %1$d. הצביעו כדי לראות את התוצאות + הצבעות %1$d. הצביעו כדי לראות את התוצאות + + אין הצבעות + + מבוסס על %1$d הצבעה + מבוסס על %1$d הצבעות + מבוסס על %1$d הצבעות + מבוסס על %1$d הצבעות + + + %1$d הצבעה + %1$d הצבעות + %1$d הצבעות + %1$d הצבעות + + + נדרשת אפשרות %1$s לפחות + נדרשות %1$s אפשרויות לפחות + נדרשות %1$s אפשרויות לפחות + נדרשות %1$s אפשרויות לפחות + + %s בהגדרות כדי לקבל הזמנות ישירות ב-${app_name}. + אם תרצה לשתף את המיקום החי שלך, ${app_name} זקוק לגישה למיקום כל הזמן כשהאפליקציה נמצאת ברקע. +\nאנו ניגשים למיקום שלך רק למשך הזמן שתבחר. + אפשר גישה + שתף מיקום זה + שתף מיקום זה + שתף מיקום בזמן אמת + שתף מיקום בזמן אמת + שתף את המיקום הנוכחי + שתף את המיקום הנוכחי + זום למיקום הנוכחי + קיבוע של המיקום שנבחר במפה + עצור + מיקום בזמן אמת מופעל + שדרוג חדר הוא פעולה מתקדמת ומומלץ לרוב כאשר החדר אינו יציב עקב באגים, תכונות חסרות או פרצות אבטחה. +\nזה בדרך כלל משפיע רק על אופן עיבוד החדר בשרת. \ No newline at end of file From 604f38bfa3c024c3900a2b9bd32bc59e37ee7fbc Mon Sep 17 00:00:00 2001 From: ravit Date: Thu, 24 Mar 2022 13:23:39 +0000 Subject: [PATCH 103/262] Translated using Weblate (Hebrew) Currently translated at 96.4% (2095 of 2171 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/he/ --- vector/src/main/res/values-iw/strings.xml | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/vector/src/main/res/values-iw/strings.xml b/vector/src/main/res/values-iw/strings.xml index fcc956ba35..639175efa8 100644 --- a/vector/src/main/res/values-iw/strings.xml +++ b/vector/src/main/res/values-iw/strings.xml @@ -2158,10 +2158,10 @@ צור משאל הוסף אפשרות אפשרות %1$d - צור אפשריות + צור אפשרויות שאלה או נושא - שאלה או נושא המשאל - צור משאל + שאלה או נושא הסקר + צור סקר אתחל את הישומון כדי שהשינויים יכנסו לתוקף. הצטרף בכל מקרה הצטרף למרחב @@ -2518,4 +2518,7 @@ מיקום בזמן אמת מופעל שדרוג חדר הוא פעולה מתקדמת ומומלץ לרוב כאשר החדר אינו יציב עקב באגים, תכונות חסרות או פרצות אבטחה. \nזה בדרך כלל משפיע רק על אופן עיבוד החדר בשרת. + כל מי שנמצא ב%s יוכל למצוא או להצטרף לחדר- אין צורך להזמין משתתפים. ניתן לשנות את הגדרת החדר בכל זמן. + אפשר LaTeX mathematics + שייך את הדואר האלקטרוני לחשבונך \ No newline at end of file From b2896dd2486943d73a7ea15b65eed3dc6b14eee8 Mon Sep 17 00:00:00 2001 From: Besnik Bleta Date: Tue, 22 Mar 2022 19:19:04 +0000 Subject: [PATCH 104/262] Translated using Weblate (Albanian) Currently translated at 100.0% (52 of 52 strings) Translation: Element Android/Element Android Store Translate-URL: https://translate.element.io/projects/element-android/element-store/sq/ --- fastlane/metadata/android/sq/changelogs/40104040.txt | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 fastlane/metadata/android/sq/changelogs/40104040.txt diff --git a/fastlane/metadata/android/sq/changelogs/40104040.txt b/fastlane/metadata/android/sq/changelogs/40104040.txt new file mode 100644 index 0000000000..5cece335e3 --- /dev/null +++ b/fastlane/metadata/android/sq/changelogs/40104040.txt @@ -0,0 +1,2 @@ +Ndryshimet kryesore në këtë version: përditësime UI treguesi shtypjeje. Ndreqje të metash të ndryshme dhe përmirësime qëndrueshmërie. +Regjistër i plotë ndryshimesh: https://github.com/vector-im/element-android/releases/tag/v1.4.4 From 6537f7f69a93c7cc1f1335479937f66c3a425174 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Priit=20J=C3=B5er=C3=BC=C3=BCt?= Date: Tue, 22 Mar 2022 20:40:00 +0000 Subject: [PATCH 105/262] Translated using Weblate (Estonian) Currently translated at 100.0% (52 of 52 strings) Translation: Element Android/Element Android Store Translate-URL: https://translate.element.io/projects/element-android/element-store/et/ --- fastlane/metadata/android/et/changelogs/40104040.txt | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 fastlane/metadata/android/et/changelogs/40104040.txt diff --git a/fastlane/metadata/android/et/changelogs/40104040.txt b/fastlane/metadata/android/et/changelogs/40104040.txt new file mode 100644 index 0000000000..b4a0e059e3 --- /dev/null +++ b/fastlane/metadata/android/et/changelogs/40104040.txt @@ -0,0 +1,2 @@ +Põhilised muutused selles versioonis: kirjutusteatiste liidese uuendused ning pisiparandused ja stabiilsust parandavad kohendused. +Kogu ingliskeelne muudatuste logi: https://github.com/vector-im/element-android/releases/tag/v1.4.4 From 237bb879552fe775a0c94875cae64d7e75f24530 Mon Sep 17 00:00:00 2001 From: Danial Behzadi Date: Wed, 23 Mar 2022 14:16:56 +0000 Subject: [PATCH 106/262] Translated using Weblate (Persian) Currently translated at 99.8% (2168 of 2171 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/fa/ --- vector/src/main/res/values-fa/strings.xml | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/vector/src/main/res/values-fa/strings.xml b/vector/src/main/res/values-fa/strings.xml index a3ee61013e..ab6bac5c1b 100644 --- a/vector/src/main/res/values-fa/strings.xml +++ b/vector/src/main/res/values-fa/strings.xml @@ -282,7 +282,7 @@ برداشتن پیوستن رد کردن - فهرست اعضا + اعضا در حال برقراری تماس… تماس پایان یافت تماس ویدئویی ورودی @@ -1957,7 +1957,7 @@ دعوت شده‌اید فضاها شیوه‌ای جدید برای گروه‌بندی اتاق‌ها و افراد است. افزودن فضا و اتاق‌های موجود - ترک فضا + ترک افزودن اتاق کاوش در اتاق‌ها @@ -2419,4 +2419,15 @@ نمایش کم‌تر %1$s، %2$s و دیگران %1$s و %2$s + توقّف + مکان زنده به کار افتاد + اجازهٔ دسترسی + هم‌رسانی این مکان + هم‌رسانی این مکان + هم‌رسانی مکان زنده + هم‌رسانی مکان زنده + هم‌رسانی مکان کنونیم + هم‌رسانی مکان کنونیم + بزرگ‌نمایی به مکان کنونی + سنجاق مکان گزیده روی نقشه \ No newline at end of file From 5e79c9367bcd1faee47dd8e634208510d7e0489a Mon Sep 17 00:00:00 2001 From: Linerly Date: Wed, 23 Mar 2022 11:16:08 +0000 Subject: [PATCH 107/262] Translated using Weblate (Indonesian) Currently translated at 100.0% (2171 of 2171 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/id/ --- vector/src/main/res/values-in/strings.xml | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/vector/src/main/res/values-in/strings.xml b/vector/src/main/res/values-in/strings.xml index 719b49fc4b..29831c1377 100644 --- a/vector/src/main/res/values-in/strings.xml +++ b/vector/src/main/res/values-in/strings.xml @@ -134,7 +134,7 @@ %d perubahan keanggotaan Panggilan - Daftar Anggota + Anggota Arahkan ke pesan yang belum dibaca %d anggota @@ -1727,7 +1727,7 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan.
    Anda tidak akan dapat bergabung lagi kecuali jika Anda diundang lagi. Anda orang satu-satunya di sini. Jika Anda tinggalkan, siapa saja tidak dapat bergabung di masa depan, termasuk Anda. Apakah Anda yakin untuk meninggalkan %s\? - Tinggalkan Space + Tinggalkan Tambahkan ruangan Jelajah ruangan @@ -2374,4 +2374,23 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan.
    %1$s, %2$s dan lainnya %1$s dan %2$s + Berhenti + Lokasi langsung diaktifkan + Jika Anda ingin membagikan lokasi langsung Anda, ${app_name} membutuhkan akses ke lokasi selama aplikasinya di latar belakang. +\nKami hanya akan mengakses lokasi Anda untuk durasi yang Anda pilih. + Perbolehkan akses + Bagikan lokasi ini + Bagikan lokasi ini + Bagikan lokasi langsung + Bagikan lokasi langsung + Bagikan lokasi saya saat ini + Bagikan lokasi saya saat ini + Perbesar ke lokasi saat ini + Pin dari lokasi yang terpilih pada peta + Kami hampir dekat untuk meriliskan sebuah Beta publik untuk Utasan. +\n +\nSaat kami mempersiapkan, kami harus membuat beberapa perubahan: utasan dibuat sebelum titik ini akan ditampilkan sebagai balasan biasa. +\n +\nIni hanya transisi sekali, utasan sekarang sebagai bagian dari spesifikasi Matrix. + Utasan Mencapai Beta 🎉 \ No newline at end of file From 9317d42c49b14259f2066a3c970d18011e444fd0 Mon Sep 17 00:00:00 2001 From: Ihor Hordiichuk Date: Tue, 22 Mar 2022 22:35:40 +0000 Subject: [PATCH 108/262] Translated using Weblate (Ukrainian) Currently translated at 100.0% (2171 of 2171 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/uk/ --- vector/src/main/res/values-uk/strings.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/vector/src/main/res/values-uk/strings.xml b/vector/src/main/res/values-uk/strings.xml index 9e46fdebce..26d090e3ef 100644 --- a/vector/src/main/res/values-uk/strings.xml +++ b/vector/src/main/res/values-uk/strings.xml @@ -2526,8 +2526,8 @@ Перейти до поточного місцеперебування Наближаємось до випуску тредів у загальнодоступну бета-версію. \n -\nГотуючись, маємо дещо змінити: треди, створені досі, буде показано як звичайні відповіді. +\nГотуючись, маємо дещо змінити: раніше створені треди буде показано як звичайні відповіді. \n -\nЦе буде одноразовий перехід, бо треди — тепер частина специфікації Matrix. +\nЦе буде одноразовий перехід, оскільки треди — тепер частина специфікації Matrix.
    Незабаром бета-версія тредів 🎉 \ No newline at end of file From 0e91511db1d0e051e6c6a8f3e620544962177c63 Mon Sep 17 00:00:00 2001 From: Besnik Bleta Date: Tue, 22 Mar 2022 19:26:01 +0000 Subject: [PATCH 109/262] Translated using Weblate (Albanian) Currently translated at 99.3% (2157 of 2171 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/sq/ --- vector/src/main/res/values-sq/strings.xml | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/vector/src/main/res/values-sq/strings.xml b/vector/src/main/res/values-sq/strings.xml index 0fc5c7f6fb..fb7fb1448e 100644 --- a/vector/src/main/res/values-sq/strings.xml +++ b/vector/src/main/res/values-sq/strings.xml @@ -312,7 +312,7 @@ Hiqe Hyni Hidheni tej - Anëtarë liste + Anëtarë Hidhu te të palexuarit Dilni nga dhoma Jeni i sigurt se doni të dilni nga dhoma? @@ -1946,7 +1946,7 @@ Hapësirat janë mënyra për të grupuar dhoma dhe persona. Jeni ftuar Shtoni dhoma ekzistuese dhe hapësira - Braktiseni Hapësirën + Braktiseni Shtoni dhoma Eksploroni dhoma @@ -2408,4 +2408,21 @@ Njoftim dhome Shfaq më pak + Ndale + Vendndodhje Drejtpërsëdrejti e aktivizuar + Nëse do të donit të tregonit Vendndodhjen tuaj Drejtpërsëdrejti, ${app_name} lyp njohje të vendndodhjes gjithë kohën, kur aplikacioni gjenden në prapaskenë. +\nDo ta njohim vendndodhjen tuaj vetëm për kohëzgjatjen që zgjidhni ju. + Lejoni hyrje + Tregoje këtë vendndodhje + Tregoje këtë vendndodhje + Tregoje vendndodhjen time të tanishme + Tregoje vendndodhjen time të tanishme + “Zoom” te vendndodhja e tanishme + Piketë e vendndodhjes së përzgjedhur në hartë + Po i afrohemi hedhjes publike në qarkullim Beta për Rrjedha. +\n +\nTeksa përgatitemi për të, na duhet të bëjmë disa ndryshime: rrjedha të krijuara para kësaj pike do të shfaqen si përgjigje të rregullta. +\n +\nKy do të jetë tranzicion vetëm për një herë, meqë tanimë Rrjedhat janë pjesë e protokollit Matrix. + Rrjedhat Po i Afrohen Beta-s 🎉 \ No newline at end of file From df1cc6a14440af494c1f45d54af11c4fc1e52ca6 Mon Sep 17 00:00:00 2001 From: Jeff Huang Date: Wed, 23 Mar 2022 03:09:14 +0000 Subject: [PATCH 110/262] Translated using Weblate (Chinese (Traditional)) Currently translated at 100.0% (52 of 52 strings) Translation: Element Android/Element Android Store Translate-URL: https://translate.element.io/projects/element-android/element-store/zh_Hant/ --- fastlane/metadata/android/zh-TW/changelogs/40104040.txt | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 fastlane/metadata/android/zh-TW/changelogs/40104040.txt diff --git a/fastlane/metadata/android/zh-TW/changelogs/40104040.txt b/fastlane/metadata/android/zh-TW/changelogs/40104040.txt new file mode 100644 index 0000000000..8949ec3486 --- /dev/null +++ b/fastlane/metadata/android/zh-TW/changelogs/40104040.txt @@ -0,0 +1,2 @@ +此版本中的主要變動:輸入指示器使用者介面更新。許多臭蟲修復與穩定性改善。 +完整的變更紀錄:https://github.com/vector-im/element-android/releases/tag/v1.4.4 From c042e5f3cfff84f87c697bd376f432d8972e6832 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Priit=20J=C3=B5er=C3=BC=C3=BCt?= Date: Wed, 23 Mar 2022 06:16:12 +0000 Subject: [PATCH 111/262] Translated using Weblate (Estonian) Currently translated at 99.9% (2169 of 2171 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/et/ --- vector/src/main/res/values-et/strings.xml | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/vector/src/main/res/values-et/strings.xml b/vector/src/main/res/values-et/strings.xml index 44025005d3..e320550323 100644 --- a/vector/src/main/res/values-et/strings.xml +++ b/vector/src/main/res/values-et/strings.xml @@ -682,7 +682,7 @@ SSL\'i viga: teise osapoole identiteeti ei õnnestu kontrollida. Väldi juhuslikke kõnesid Enne kõne algatamist küsi kinnitust - Osalejate loend + Osalejad %d osaleja %d osalejat @@ -1957,7 +1957,7 @@ Sa oled saanud kutse Kogukonnakeskused on uus võimalus siduda jututubasid ja inimesi. Lisa olemasolevaid jututubasid ja kogukonnakeskuseid - Lahku kogukonnakeskusest + Lahku kogukonnast Lisa jututuba Tutvu jututubadega @@ -2419,4 +2419,23 @@ %1$s, %2$s ning teised kasutajad %1$s ja %2$s + Lõpeta asukoha jagamine + Reaalajas asukoha jagamine on kasutusel + Kui sa soovid reaalajas oma asukohta jagada, siis ${app_name} vajab alati õigus asukohta tuvastada. Seda ka siis kui rakendus töötab taustal. +\nLigipääs asukohale on saadaval vaid sinu valitud ajavahemiku vältel. + Luba ligipääs asukohale + Jaga seda asukohta + Jaga seda asukohta + Jaga asukohta reaalajas + Jaga asukohta reaalajas + Jaga minu praegust asukohta + Jaga minu praegust asukohta + Suumi praeguse asukohani + Valitud asukoha marker kaardil + Me oleme valmis avaldama jutulõngade funktsionaalsuse beetaversiooni. +\n +\nValmistudes järgnevaks, me peame tegema natuke muudatusi: enne seda hetke loodud jutulõngad kuvatakse tavaliste vastustena. +\n +\nKuna jutulõngad on nüüd osa Matrix\'i spetsifikatsioonist, siis see on ühekordne muudatus. + Jutulõngad on peaaegu valmis 🎉 \ No newline at end of file From d526724eb11018eb8b4a34aaca46f65e7cf54ed5 Mon Sep 17 00:00:00 2001 From: Linerly Date: Wed, 23 Mar 2022 11:08:47 +0000 Subject: [PATCH 112/262] Translated using Weblate (Indonesian) Currently translated at 100.0% (52 of 52 strings) Translation: Element Android/Element Android Store Translate-URL: https://translate.element.io/projects/element-android/element-store/id/ --- fastlane/metadata/android/id/changelogs/40104040.txt | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 fastlane/metadata/android/id/changelogs/40104040.txt diff --git a/fastlane/metadata/android/id/changelogs/40104040.txt b/fastlane/metadata/android/id/changelogs/40104040.txt new file mode 100644 index 0000000000..4f3ef77ed6 --- /dev/null +++ b/fastlane/metadata/android/id/changelogs/40104040.txt @@ -0,0 +1,2 @@ +Perubahan utama dalam versi ini: pembaruan UI indikator pengetikan. Beberapa perbaikan kutu dan perbaikan stabilitas. +Catatan perubahan lanjutan: https://github.com/vector-im/element-android/releases/tag/v1.4.4 From 37b17932ca7a92359e9848193689ecd1b8014895 Mon Sep 17 00:00:00 2001 From: LinAGKar Date: Wed, 23 Mar 2022 21:23:43 +0000 Subject: [PATCH 113/262] Translated using Weblate (Swedish) Currently translated at 100.0% (52 of 52 strings) Translation: Element Android/Element Android Store Translate-URL: https://translate.element.io/projects/element-android/element-store/sv/ --- fastlane/metadata/android/sv-SE/changelogs/40104040.txt | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 fastlane/metadata/android/sv-SE/changelogs/40104040.txt diff --git a/fastlane/metadata/android/sv-SE/changelogs/40104040.txt b/fastlane/metadata/android/sv-SE/changelogs/40104040.txt new file mode 100644 index 0000000000..0601b26873 --- /dev/null +++ b/fastlane/metadata/android/sv-SE/changelogs/40104040.txt @@ -0,0 +1,2 @@ +Huvudsakliga ändringar i den här versionen: gränssnittsuppdateringar för skrivindikator. Diverse buggfixar och stabilitetsförbättringar. +Full ändringslogg: https://github.com/vector-im/element-android/releases/tag/v1.4.4 From 345ce171ff99404f471dc87cc92d226a19ef87fc Mon Sep 17 00:00:00 2001 From: waclaw66 Date: Tue, 22 Mar 2022 18:55:58 +0000 Subject: [PATCH 114/262] Translated using Weblate (Czech) Currently translated at 100.0% (2171 of 2171 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/cs/ --- vector/src/main/res/values-cs/strings.xml | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/vector/src/main/res/values-cs/strings.xml b/vector/src/main/res/values-cs/strings.xml index c7e0f96433..55ead1f8fc 100644 --- a/vector/src/main/res/values-cs/strings.xml +++ b/vector/src/main/res/values-cs/strings.xml @@ -363,7 +363,7 @@ Vstoupit Prosím, spusťte ${app_name} na jiném zařízení, které může dešifrovat zprávu, aby poslalo klíče této relaci. Odmítnout - Zobrazit členy + Členové Přejít na nepřečtené %d člen @@ -1998,7 +1998,7 @@ Prostory Jste zváni Přidat existující místnosti a prostor - Opustit prostor + Opustit Přidat místnosti Prozkoumat místnosti @@ -2466,4 +2466,23 @@ Zobrazit méně %1$s, %2$s a další %1$s a %2$s + Zastavit + Poloha živě povolena + Pokud chcete sdílet svou polohu živě, ${název_aplikace} potřebuje přístup k poloze po celou dobu, kdy je aplikace na pozadí. +\nK vaší poloze budeme mít přístup pouze po dobu, kterou si zvolíte. + Povolit přístup + Sdílet tuto polohu + Sdílet tuto polohu + Sdílet polohu živě + Sdílet polohu živě + Sdílet moji současnou polohu + Sdílet moji současnou polohu + Přiblížit na současnou polohu + Připnout vybranou pozici na mapě + Blížíme se k vydání veřejné betaverze vláken. +\n +\nV rámci příprav na ni musíme provést několik změn: vlákna vytvořená před tímto okamžikem se budou zobrazovat jako běžné odpovědi. +\n +\nPůjde o jednorázový přechod, protože vlákna jsou nyní součástí Matrix specifikace. + Vlákna se blíží k betaverzi 🎉 \ No newline at end of file From 44f064c484101b2f91b853171ae18e4394047c30 Mon Sep 17 00:00:00 2001 From: Ultimator14 Date: Wed, 23 Mar 2022 23:12:34 +0000 Subject: [PATCH 115/262] Translated using Weblate (German) Currently translated at 99.2% (2154 of 2171 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/de/ --- vector/src/main/res/values-de/strings.xml | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/vector/src/main/res/values-de/strings.xml b/vector/src/main/res/values-de/strings.xml index 4c5131d452..866f05b3a6 100644 --- a/vector/src/main/res/values-de/strings.xml +++ b/vector/src/main/res/values-de/strings.xml @@ -123,8 +123,8 @@ Du hast das %1$s Widget entfernt %1$s hat das %2$s Widget modifiziert Du hast das %1$s Widget modifiziert - Admin - Moderation + Administrator + Moderator Standard Benutzerdefiniert (%1$d) Benutzerdefiniert @@ -1261,8 +1261,8 @@ Benutzerdefiniert Eingeladen Nutzer - Admin in %1$s - Moderation in %1$s + Administrator in %1$s + Moderator in %1$s Springen und als gelesen markieren ${app_name} kann keine Ereignisse vom Typ \'%1$s\' ${app_name} ist beim Verarbeiten des Ereignisinhalts mit der ID \'%1$s\' auf ein Problem gestoßen @@ -2418,4 +2418,14 @@ %d Server-ACL geändert %d Server-ACLs geändert + Beenden + Live-Standort aktiviert + Zugriff erlauben + Standort teilen + Standort teilen + Meinen Standort teilen + Meinen Standort teilen + Live-Standort teilen + Live-Standort teilen + Threads nähern sich der Beta 🎉 \ No newline at end of file From 9004d23f38cac4f9423b204769c3c6c761a4a470 Mon Sep 17 00:00:00 2001 From: Jozef Gaal Date: Tue, 22 Mar 2022 20:13:03 +0000 Subject: [PATCH 116/262] Translated using Weblate (Slovak) Currently translated at 100.0% (2171 of 2171 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/sk/ --- vector/src/main/res/values-sk/strings.xml | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/vector/src/main/res/values-sk/strings.xml b/vector/src/main/res/values-sk/strings.xml index 6650890ac3..bd3e86e40c 100644 --- a/vector/src/main/res/values-sk/strings.xml +++ b/vector/src/main/res/values-sk/strings.xml @@ -472,7 +472,7 @@ %d zmeny členstva %d zmien členstva - Zobraziť členov + Členovia 1 člen %d členovia @@ -1316,7 +1316,7 @@ Verejná miestnosť Kompresia obrázku… Vždy sa opýtať - Opustiť priestor + Opustiť Pridať miestnosti Preskúmať miestnosti Pripojiť sa k priestoru @@ -2466,4 +2466,23 @@ Opustiť miestnosť s daným id (alebo aktuálnu miestnosť, ak je prázdna) Varovná úroveň dôveryhodnosti Pripojiť + Zastaviť + Poloha v reálnom čase zapnutá + Ak chcete zdieľať svoju polohu v reálnom čase, ${app_name} potrebuje prístup k polohe po celý čas, kým je aplikácia na pozadí. +\nPrístup k vašej polohe bude len počas obdobia, ktoré si zvolíte. + Povoliť prístup + Zdieľať túto polohu + Zdieľať túto polohu + Zdieľať polohu v reálnom čase + Zdieľať polohu v reálnom čase + Zdieľať moju aktuálnu polohu + Zdieľať moju aktuálnu polohu + Priblížiť k aktuálnej polohe + Označiť vybranú lokalitu na mape + Blížime sa k vydaniu verejnej beta verzie pre vlákna. +\n +\nV rámci príprav na ňu musíme urobiť niekoľko zmien: vlákna vytvorené pred týmto bodom sa budú zobrazovať ako bežné odpovede. +\n +\nPôjde o jednorazový prechod, keďže vlákna sú teraz súčasťou špecifikácie Matrix. + Vlákna sa blížia k beta verzii 🎉 \ No newline at end of file From 2602c7afb541f1c524427bef0ab589d655e89717 Mon Sep 17 00:00:00 2001 From: Danial Behzadi Date: Wed, 23 Mar 2022 14:13:35 +0000 Subject: [PATCH 117/262] Translated using Weblate (Persian) Currently translated at 100.0% (52 of 52 strings) Translation: Element Android/Element Android Store Translate-URL: https://translate.element.io/projects/element-android/element-store/fa/ --- fastlane/metadata/android/fa/changelogs/40104040.txt | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 fastlane/metadata/android/fa/changelogs/40104040.txt diff --git a/fastlane/metadata/android/fa/changelogs/40104040.txt b/fastlane/metadata/android/fa/changelogs/40104040.txt new file mode 100644 index 0000000000..6e8f4911ea --- /dev/null +++ b/fastlane/metadata/android/fa/changelogs/40104040.txt @@ -0,0 +1,2 @@ +تغییرات عمده در این نگارش: به‌روز رسانی‌های رابط کاربری نشانگر نوشتن. چندین رفع اشکال و بهبودهای پایداری‌. +گزارش دگرگونی کامل: https://github.com/vector-im/element-android/releases/tag/v1.4.4 From 57ad36175068e2b891f591f86a347fef20b5bf89 Mon Sep 17 00:00:00 2001 From: Jeff Huang Date: Wed, 23 Mar 2022 03:16:13 +0000 Subject: [PATCH 118/262] Translated using Weblate (Chinese (Traditional)) Currently translated at 100.0% (2171 of 2171 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/zh_Hant/ --- vector/src/main/res/values-zh-rTW/strings.xml | 23 +++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/vector/src/main/res/values-zh-rTW/strings.xml b/vector/src/main/res/values-zh-rTW/strings.xml index 564a4ccf8a..781591ace7 100644 --- a/vector/src/main/res/values-zh-rTW/strings.xml +++ b/vector/src/main/res/values-zh-rTW/strings.xml @@ -326,7 +326,7 @@ 移除 加入 拒絕 - 列出成員 + 成員 跳到未讀 %d 個成員 @@ -1922,7 +1922,7 @@ 您被邀請了 空間是將聊天室與人們分組的新方式。 新增既有的聊天室與空間 - 離開空間 + 離開 新增聊天室 探索聊天室 @@ -2372,4 +2372,23 @@ %1$s 與 %2$s %1$s、%2$s 與其他人 + 停止 + 即時位置已啟用 + 若您想要分享您的即時位置,${app_name} 需要當應用程式在背景時隨時存取位置的權限。 +\n我們將只會在您選擇的期間內存取您的位置。 + 允許存取 + 分享此位置 + 分享此位置 + 分享即時位置 + 分享即時位置 + 分享我目前的位置 + 分享我目前的位置 + 縮放至目前位置 + 地圖上選定位置的圖釘 + 我們愈來愈接近將討論串釋出為公開測試版。 +\n +\n在我們為此做準備時,我們需要做出一些變動:先前建立的討論串將會顯示為一般回覆。 +\n +\n這會是一次性的過渡,因為討論串現在是 Matrix 規範的一部分了。 + 討論串接近測試版了 🎉 \ No newline at end of file From 129c6aa7fb193a091134fd81611ce82cb40f0370 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sveinn=20=C3=AD=20Felli?= Date: Wed, 23 Mar 2022 15:10:55 +0000 Subject: [PATCH 119/262] Translated using Weblate (Icelandic) Currently translated at 75.1% (1632 of 2171 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/is/ --- vector/src/main/res/values-is/strings.xml | 280 ++++++++++++++++++++-- 1 file changed, 257 insertions(+), 23 deletions(-) diff --git a/vector/src/main/res/values-is/strings.xml b/vector/src/main/res/values-is/strings.xml index ee34c9e314..6deeb7fac5 100644 --- a/vector/src/main/res/values-is/strings.xml +++ b/vector/src/main/res/values-is/strings.xml @@ -86,7 +86,7 @@ Lýstu vandamálinu þínu hér Taka þátt í spjallrás Notandanafn - Útskráning + Skrá út Leita Hefja raddsamtal Hefja myndsamtal @@ -107,7 +107,7 @@ Stórt Miðlungs Lítið - Samtal + Símtal Innhringing myndsamtals Innhringing raddsamtals Samtal í gangi… @@ -118,7 +118,7 @@ Fjarlægja Taka þátt Hafna - Listi yfir meðlimi + Meðlimir %d meðlimur %d meðlimir @@ -160,7 +160,7 @@ Tilkynningar Hunsaðir notendur Annað - Ítarlegt + Nánar Dulritun Tengiliðir á tæki Greiningar @@ -175,7 +175,7 @@ Tungumál notandaviðmóts Veldu tungumál Breyta lykilorði - eldra lykilorð + Núverandi lykilorð Nýtt lykilorð Mistókst að uppfæra lykilorð Lykilorðið þitt hefur verið uppfært @@ -191,12 +191,12 @@ Hver getur lesið ferilskráningu? Hver sem er Bannaðir notendur - Ítarlegt + Nánar Þema Afkóðunarvilla Heiti tækis Auðkenni setu - Dulritunarlykill tækis + Dulritunarlykill setu Flytja út Settu inn lykilsetningu Staðfestu lykilsetningu @@ -230,7 +230,7 @@ Hávært Dulrituð skilaboð Búa til - Heim + Upphafssíða Spjallrásir Ástæða: %1$s Auðkennismynd @@ -246,7 +246,7 @@ Inniheldur ekki gilt JSON Of margar beiðnir hafa verið sendar Samtal tengist… - Samtali lokið + Símtali lokið Boð um samtal Nota innbyggða myndavél Minnst á @@ -273,8 +273,8 @@ Fara í ólesið Banna Afbanna - Fela öll skilaboð frá þessum notanda - Sýna öll skilaboð frá þessum notanda + Hunsa + Hætta að hunsa Þú hefur ekki heimild til að senda skilaboð á þessa spjallrás. Gat ekki sannreynt auðkenni fjartengds þjóns. Bæta við á upphafsskjá @@ -298,7 +298,7 @@ Einungis meðlimir (síðan þeir skráðu sig) Innra auðkenni þessarar spjallrásar Veldu skrá yfir spjallrásir - Heiti heimaþjóns + Heiti þjóns %d ólesið tilkynnt skilaboð %d ólesin tilkynnt skilaboð @@ -521,7 +521,7 @@ %1$s tók %2$s úr banni. Ástæða: %3$s Þú fjarlægðir %1$s. Ástæða: %2$s %1$s fjarlægði %2$s. Ástæða: %3$s - Þú hafnaðir boðinu. Ástæða: %1$s + Þú hafnaðir boðinu. Ástæða: %2$s %1$s hafnaði boðinu. Ástæða: %2$s Þú hættir. Ástæða: %1$s %1$s hætti. Ástæða: %2$s @@ -562,7 +562,7 @@ %1$s fjarlægði %2$s viðmótshluta Þú bættir við %1$s viðmótshluta %1$s bætti við %2$s viðmótshluta - Þú samþykktir boð um að taka þátt í %1$s + Þú samþykktir boð um að taka þátt í %$s Þú afturkallaðir boðið til %1$s %1$s afturkallaði boðið til %2$s Þú afturkallaðir boð til %1$s um þátttöku í spjallrásinni @@ -746,7 +746,7 @@ Svæði eru ný leið til að hópa fólk og spjallrásir. Bæta við fyrirliggjandi svæðum Bæta við fyrirliggjandi spjallrásum - Yfirgefa svæði + Yfirgefa Bæta við spjallrásum Kanna spjallrásir Búa til svæði @@ -762,8 +762,8 @@ Almenningsspjallrás Eyða auðkennismynd Það kom upp villa við að fletta upp símanúmerinu - Sendir skilaboðið með snjókomu - Sendir skilaboðið með skrauti + Sendir skilaboðin með snjókomu + Sendir skilaboðin með skrauti Uppfærsla dulritunar tiltæk Sendir skilaboð sem óbreyttur texti án þess að túlka það sem markdown Dulritun ekki virk @@ -899,7 +899,7 @@ Notaðu lykilsetningu endurheimtu eða dulritunarlykil Sýsla með í öryggisafriti dulritunarlykla Nota öryggisafrit af lykli - Verja öryggisafrit + Varið öryggisafrit Eyða öryggisafriti Athuga ástand öryggisafrits Eyði öryggisafriti… @@ -972,8 +972,8 @@ Hver sem er getur fundið svæðið og tekið þátt Hver sem er getur fundið spjallrásina og tekið þátt Birta falda atburði í tímalínu - Hjálp og um - Rödd og myndband + Hjálp og um hugbúnaðinn + Tal og myndmerki Stillingar spjallrásar Umfjöllunarefni spjallrásar (valkvætt) Skipta um netkerfi @@ -1105,7 +1105,7 @@ NÁÐI ÞVÍ Stilla auðkennismynd Umfjöllunarefni - Nafn spjallrásar + Heiti spjallrásar Setja upp Ræsa myndavélina Stöðva myndavélina @@ -1385,13 +1385,13 @@ Taka notanda úr banni Banna notanda Fjarlægja notanda - Lækka niður um stig + Lækka í tign Ekkert svar Bíða Halda áfram Símtöl Alltaf spyrja - Aftan + Til baka Fram Heyrnartól Hátalari @@ -1540,4 +1540,238 @@ Engin breyting. %1$s gekk í hópinn Boðið þitt + Settu aftur inn öryggisfrasann þinn til að staðfesta hann. + Öryggisfrasi + Setja öryggisfrasa + Vista öryggislykilinn þinn + Nota öryggisfrasa + Nota öryggislykil + Yfirfarðu þennan tengil + Ef þú frumstillir allt + Næstum því búið! Bíð eftir staðfestingu… + Bæta við umræðuefni + Sannprófa þessa innskráningu + Skrá út úr þessari setu + Skilaboð við þennan notanda eru enda-í-enda dulrituð þannig að enginn annar getur lesið þau. + Skanna með þessu tæki + Skannaðu kóðann hinna + Skrá inn með Matrix-auðkenni + Skrá inn með Matrix-auðkenni + Samþykktu skilmálana til að halda áfram + Velja sérsniðinn heimaþjón + Velja Element Matrix þjónustur + Velja matrix.org + Þú gerðir þetta einungis aðgengilegt gegn boði. + %1$s gerði þetta einungis aðgengilegt gegn boði. + Þú gerðir spjallrás einungis aðgengilega gegn boði. + %1$s gerði spjallrás einungis aðgengilega gegn boði. + Þú gerðir spjallrásina opinbera fyrir hverja þá sem þekkja slóðina á hana. + %1$s gerði spjallrásina opinbera fyrir hverja þá sem þekkja slóðina á hana. + Ýttu lengi á spjallrás til að sjá fleiri valkosti + Útbúa nýtt beint samtal + Loka valmyndinni til að útbúa spjallrás… + Stöðva + Staðsetning í rauntíma virkjuð + Leyfa aðgang + Deila þessari staðsetningu + Deila þessari staðsetningu + Deila staðsetningu í rauntíma + Deila staðsetningu í rauntíma + Deila núverandi staðsetningu minni + Deila núverandi staðsetningu minni + Renna að núverandi staðsetningu + Pinni með valinni staðsetningu á landakorti + Get ekki tekið upp talskilaboð + Get ekki spilað þessi talskilaboð + Bjóða notendum sjálfvirkt + Merkja sem ekki-tillögu + Merkja sem tillögu + Þér er boðið + Uppgötvun (%s) + Aðeins á þessa spjallrás + Bjóða í %s + Bjóða með notandanafni eða tölvupóstfangi + Bjóða í %s + Þú ert ekki að hunsa neina notendur + Skrá teikn + Talskilaboð (%1$s) + Tek upp talskilaboð + Setja talskilaboð í bið + Renna til að hætta við + Taka upp talskilaboð + %s býður þér + Útbý svæði… + Þú getur breytt þessu síðar ef þarf + Mistókst að senda skilaboð + Efni atburðar + Breyta efni + Flytja lykil inn úr skrá + Ýti-tilkynningar eru óvirkar + Tókst ekki að taka notanda úr banni + Bannaður af %1$s + Afturkalla boð til %1$s\? + Afturkalla boð + Vista endurheimtulykil í + Riot heitir núna Element! + Varið öryggisafrit + Setja upp nýtt lykilorð notandaaðgangs… + Kross-undirritun + Virkja enda-í-enda dulritun… + Deildi staðsetningu sinni + Þú ert skráð/ur út + Þú ert skráð/ur út + Þetta notandanafn er þegar í notkun + Nýskrá inn í %1$s + Senda aftur + Settu hér inn símanúmer + Til baka í innskráningu + Staðfestingarpóstur var sendur til %1$s. + Athugaðu pósthólfið þitt + Þetta tölvupóstfang er ekki tengt við neinn notandaaðgang + Endurstilla lykilorð á %1$s + Þetta tölvupóstfang er ekki tengt við neinn notandaaðgang. + Premium-hýsing fyrir samtök/fyrirtæki + Halda áfram með SSO + Tengjast sérsniðnum þjóni + Tengjast Element Matrix þjónustum + Sérsniðnar og ítarlegar stillingar + Premium-hýsing fyrir samtök/fyrirtæki + Veldu netþjón + Þú ert við stjórnvölinn. + Stríðni + Þú gerðir engar breytingar + %1$s gerði engar breytingar + Fara út úr spjallrásinni + Fjarlægja úr litlum forgangi + Bæta í lítinn forgang + HUNSA NOTANDA + Tókst ekki að meðhöndla deiligögn + Villa kom upp þegar reynt var að sækja viðhengið. + Skráin er of stór til að senda hana inn. + + %d notandi las + %d notendur lásu + + %s las + %1$s og %2$s lásu + %1$s, %2$s og %3$s lásu + + %1$s, %2$s og %3$d til viðbótar lásu + %1$s, %2$s og %3$d til viðbótar lásu + + Hoppa neðst + Senda viðhengi + Auðkennisþjónninn er ekki með neina þjónustuskilmála + Settu inn slóð á auðkennisþjón + Samþykkir þú að senda þessar upplýsingar\? + Senda tölvupóstföng og símanúmer til %s + Gefa samþykki + Afturkalla samþykki mitt + Ef þú aftengist frá auðkennisþjóninum þínum, munu aðrir notendur ekki geta fundið þig og þú munt ekki geta boðið öðrum með símanúmeri eða tölvupósti. + Fela reglur fyrir auðkenningarþjón + Sýna reglur fyrir auðkenningarþjón + Skipta um auðkennisþjón + Opna uppgötvunarstillingar + Stilla auðkennisþjón + Vertu finnanlegur fyrir aðra + Þekktir notendur + Bý til spjallrás… + Bæta við með QR-kóða + Finnurðu ekki það sem þú leitar að\? + Sía samtöl… + Skráin %1$s hefur verið sótt! + Sendi smámynd (%1$s / %2$s) + Dulrita smámynd… + Lýstu tillögunni þinni hér + Skrifaðu tillöguna þína hér. + Settu inn tillögu + Fáðu aðstoð við að nota ${app_name} + Lagaleg atriði + session_name: + app_display_name: + push_key: + app_id: + Þú ert nú þegar að skoða þennan spjallþráð! + Þú ert nú þegar að skoða þessa spjallrás! + Útgáfa Matrix SDK + Þú ert ekki að nota neinn auðkennisþjón + %s vill sannreyna setuna þína + Beiðni um sannvottun + Setja upp varið öryggisafrit + Tapaðu aldrei dulrituðum skilaboðum + Verið er að öryggisafrita dulritunarlyklana þína. + Flytja út dulritunarlykla handvirkt + Tekur bann af notanda með uppgefið auðkenni + Þessi þjónn gefur ekki upp neina stefnu. + Stefna fyrir auðkenningarþjóninn þinn + Stefna fyrir heimaþjóninn þinn + Reglur ${app_name} + Við deilum ekki upplýsingum með utanaðkomandi aðilum + Við skráum ekki eða búum til snið með gögnum notendaaðganga + Veldu lit á LED, hljóð, titring… + Stilla þöglar tilkynningar + Stilla tilkynningar símtala + Stilla háværar tilkynningar + Hunsa bestun + ${app_name} er ekki háð bestun fyrir rafhlöðuendingu. + Gera takmarkanir óvirkar + Virkja keyrslu í ræsingu + Skráning á aðgangsteikni + Mistókst að ná FCM-teikni: +\n%1$s + Tókst að ná FCM-teikni: +\n%1$s + Firebase-teikn + Laga Play-þjónustur + ${app_name} notar Google Play þjónustur til að afhenda ýtitilkynningar en það lítur út fyrir að vera rangt stillt: +\n%1$s + Google Play Services APK er tiltækt og af nýjustu gerð. + Athugun á Play-þjónustum + Uppfæra einkaspjallrás + Uppfæra almenningsspjallrás + Yfirgefa allar spjallrásir og svæði + Bjóða með tölvupósti + Einkasvæði til að skipuleggja spjallrásirnar þínar + Hverjum ertu að vinna með\? + Efni atburðar + Ósvarað myndsímtal + Ósvarað símtal + Virkt myndsamtal + Virkt raddsamtal + Þú hafnaðir þessu símtali + QR-kóði var ekki skannaður! + Ógildur QR-kóði (ógild slóð)! + Deila með textaskilaboðum + Endursetja PIN-númer + Hleð inn tiltækum tungumálum… + Kóðinn minn + Deila kóðanum mínum + Skanna QR-kóða + Býð notendum… + Bæta við meðlimum + Sannreyna handvirkt með textaskilaboðum + Dulritað meðf ósannreyndu tæki + sendir snjókomu ❄️ + sendir skraut 🎉 + ${app_name} fyrir iOS +\n${app_name} fyrir Android + ${app_name} fyrir vefinn +\n${app_name} fyrir vinnutölvur + Nota skrá + Þú gekkst í hópinn. + Þú bjóst til og stilltir spjallrásina. + Haltu þessu öruggu + Skilaboð hér eru ekki enda-í-enda dulrituð. + Þú samþykktir + Þú hættir við + Forritarahamur + Hreinsa persónuleg gögn + Taktu þátt ókeypis ásamt milljónum annarra á stærsta almenningsþjóninum + sleppt þessari spurningu + Örugg skilaboð. + Gat ekki tengst við auðkennisþjón + Dulritunarlyklarnir þínir eru ekki öryggisafritaðir úr þessari setu. + Tekur stjórnunarréttindi af notanda með uppgefið auðkenni + Hættir að hunsa notanda, birtir skilaboð viðkomandi héðan í frá + Setja upp varið öryggisafrit \ No newline at end of file From 79afdf724f23725fd82eb73046f76e9aa070353b Mon Sep 17 00:00:00 2001 From: Onuray Sahin Date: Fri, 25 Mar 2022 13:48:20 +0300 Subject: [PATCH 120/262] Code review fixes. --- vector/src/main/AndroidManifest.xml | 1 - .../app/features/location/LocationSharingFragment.kt | 7 +++---- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/vector/src/main/AndroidManifest.xml b/vector/src/main/AndroidManifest.xml index 71ea6aaf8f..d52e95b382 100644 --- a/vector/src/main/AndroidManifest.xml +++ b/vector/src/main/AndroidManifest.xml @@ -46,7 +46,6 @@ - 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 d4582d98b9..b779b50c8b 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 @@ -180,11 +180,10 @@ class LocationSharingFragment @Inject constructor( } private fun handleStartLiveLocationService(event: LocationSharingViewEvents.StartLiveLocationService) { + val args = LocationSharingService.RoomArgs(event.sessionId, event.roomId, event.duration) + Intent(requireContext(), LocationSharingService::class.java) - .apply { - putExtra(LocationSharingService.EXTRA_ROOM_ARGS, - LocationSharingService.RoomArgs(event.sessionId, event.roomId, event.duration)) - } + .putExtra(LocationSharingService.EXTRA_ROOM_ARGS, args) .also { ContextCompat.startForegroundService(requireContext(), it) } From 7285bc6889aacafe09ccf6640a58d438fd58f54f Mon Sep 17 00:00:00 2001 From: Onuray Sahin Date: Fri, 25 Mar 2022 13:58:48 +0300 Subject: [PATCH 121/262] Code review fixes. --- .../app/features/location/LocationTracker.kt | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/location/LocationTracker.kt b/vector/src/main/java/im/vector/app/features/location/LocationTracker.kt index d5e298a14b..b7006370a6 100644 --- a/vector/src/main/java/im/vector/app/features/location/LocationTracker.kt +++ b/vector/src/main/java/im/vector/app/features/location/LocationTracker.kt @@ -93,19 +93,15 @@ class LocationTracker @Inject constructor( } fun addCallback(callback: Callback) { - synchronized(callbacks) { - if (!callbacks.contains(callback)) { - callbacks.add(callback) - } + if (!callbacks.contains(callback)) { + callbacks.add(callback) } } fun removeCallback(callback: Callback) { - synchronized(callbacks) { - callbacks.remove(callback) - if (callbacks.size == 0) { - stop() - } + callbacks.remove(callback) + if (callbacks.size == 0) { + stop() } } From 88197991e1210f839bc0df0bd17ca853bff21cd6 Mon Sep 17 00:00:00 2001 From: Adam Brown Date: Thu, 24 Mar 2022 12:44:57 +0000 Subject: [PATCH 122/262] extracting the direct login logic to its own use case along with viewmodel test case - will ensure we emit account sign in when going via direct login flow --- .../features/onboarding/DirectLoginUseCase.kt | 91 +++++++++++++++++++ .../onboarding/OnboardingViewModel.kt | 78 ++-------------- .../onboarding/OnboardingViewModelTest.kt | 31 ++++++- .../app/test/fakes/FakeDirectLoginUseCase.kt | 31 +++++++ 4 files changed, 159 insertions(+), 72 deletions(-) create mode 100644 vector/src/main/java/im/vector/app/features/onboarding/DirectLoginUseCase.kt create mode 100644 vector/src/test/java/im/vector/app/test/fakes/FakeDirectLoginUseCase.kt diff --git a/vector/src/main/java/im/vector/app/features/onboarding/DirectLoginUseCase.kt b/vector/src/main/java/im/vector/app/features/onboarding/DirectLoginUseCase.kt new file mode 100644 index 0000000000..54ee1d3a52 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/onboarding/DirectLoginUseCase.kt @@ -0,0 +1,91 @@ +/* + * 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 android.net.Uri +import im.vector.app.R +import im.vector.app.core.resources.StringProvider +import im.vector.app.features.onboarding.OnboardingAction.LoginOrRegister +import org.matrix.android.sdk.api.MatrixPatterns.getDomain +import org.matrix.android.sdk.api.auth.AuthenticationService +import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig +import org.matrix.android.sdk.api.auth.wellknown.WellknownResult +import org.matrix.android.sdk.api.session.Session +import javax.inject.Inject + +class DirectLoginUseCase @Inject constructor( + private val authenticationService: AuthenticationService, + private val stringProvider: StringProvider, +) { + + suspend fun execute(action: LoginOrRegister, homeServerConnectionConfig: HomeServerConnectionConfig?): Result { + return fetchWellKnown(action.username, homeServerConnectionConfig) + .andThen { wellKnown -> createSessionFor(wellKnown, action, homeServerConnectionConfig) } + } + + private suspend fun fetchWellKnown(matrixId: String, config: HomeServerConnectionConfig?) = runCatching { + authenticationService.getWellKnownData(matrixId, config) + } + + private suspend fun createSessionFor(data: WellknownResult, action: LoginOrRegister, config: HomeServerConnectionConfig?) = when (data) { + is WellknownResult.Prompt -> loginDirect(action, data, config) + is WellknownResult.FailPrompt -> handleFailPrompt(data, action, config) + else -> onWellKnownError() + } + + private suspend fun handleFailPrompt(data: WellknownResult.FailPrompt, action: LoginOrRegister, config: HomeServerConnectionConfig?): Result { + // Relax on IS discovery if homeserver is valid + val isMissingInformationToLogin = data.homeServerUrl == null || data.wellKnown == null + return when { + isMissingInformationToLogin -> onWellKnownError() + else -> loginDirect(action, WellknownResult.Prompt(data.homeServerUrl!!, null, data.wellKnown!!), config) + } + } + + private suspend fun loginDirect(action: LoginOrRegister, wellKnownPrompt: WellknownResult.Prompt, config: HomeServerConnectionConfig?): Result { + val alteredHomeServerConnectionConfig = config?.updateWith(wellKnownPrompt) ?: fallbackConfig(action, wellKnownPrompt) + return runCatching { + authenticationService.directAuthentication( + alteredHomeServerConnectionConfig, + action.username, + action.password, + action.initialDeviceName + ) + } + } + + private fun HomeServerConnectionConfig.updateWith(wellKnownPrompt: WellknownResult.Prompt) = copy( + homeServerUriBase = Uri.parse(wellKnownPrompt.homeServerUrl), + identityServerUri = wellKnownPrompt.identityServerUrl?.let { Uri.parse(it) } + ) + + private fun fallbackConfig(action: LoginOrRegister, wellKnownPrompt: WellknownResult.Prompt) = HomeServerConnectionConfig( + homeServerUri = Uri.parse("https://${action.username.getDomain()}"), + homeServerUriBase = Uri.parse(wellKnownPrompt.homeServerUrl), + identityServerUri = wellKnownPrompt.identityServerUrl?.let { Uri.parse(it) } + ) + + private fun onWellKnownError() = Result.failure(Exception(stringProvider.getString(R.string.autodiscover_well_known_error))) +} + +@Suppress("UNCHECKED_CAST") // We're casting null failure results to R +private inline fun Result.andThen(block: (T) -> Result): Result { + return when (val result = getOrNull()) { + null -> this as Result + else -> block(result) + } +} 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 3fb52619da..6d959ef124 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 @@ -17,7 +17,6 @@ package im.vector.app.features.onboarding import android.content.Context -import android.net.Uri import com.airbnb.mvrx.MavericksViewModelFactory import dagger.assisted.Assisted import dagger.assisted.AssistedFactory @@ -45,7 +44,6 @@ import im.vector.app.features.login.SignMode import kotlinx.coroutines.Job import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.launch -import org.matrix.android.sdk.api.MatrixPatterns.getDomain import org.matrix.android.sdk.api.auth.AuthenticationService import org.matrix.android.sdk.api.auth.HomeServerHistoryService import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig @@ -55,9 +53,6 @@ import org.matrix.android.sdk.api.auth.registration.FlowResult import org.matrix.android.sdk.api.auth.registration.RegistrationResult import org.matrix.android.sdk.api.auth.registration.RegistrationWizard import org.matrix.android.sdk.api.auth.registration.Stage -import org.matrix.android.sdk.api.auth.wellknown.WellknownResult -import org.matrix.android.sdk.api.failure.Failure -import org.matrix.android.sdk.api.failure.MatrixIdFailure import org.matrix.android.sdk.api.session.Session import timber.log.Timber import java.util.UUID @@ -79,6 +74,7 @@ class OnboardingViewModel @AssistedInject constructor( private val analyticsTracker: AnalyticsTracker, private val uriFilenameResolver: UriFilenameResolver, private val registrationActionHandler: RegistrationActionHandler, + private val directLoginUseCase: DirectLoginUseCase, private val vectorOverrides: VectorOverrides ) : VectorViewModel(initialState) { @@ -470,74 +466,14 @@ class OnboardingViewModel @AssistedInject constructor( private fun handleDirectLogin(action: OnboardingAction.LoginOrRegister, homeServerConnectionConfig: HomeServerConnectionConfig?) { setState { copy(isLoading = true) } - currentJob = viewModelScope.launch { - val data = try { - authenticationService.getWellKnownData(action.username, homeServerConnectionConfig) - } catch (failure: Throwable) { - onDirectLoginError(failure) - return@launch - } - when (data) { - is WellknownResult.Prompt -> - directLoginOnWellknownSuccess(action, data, homeServerConnectionConfig) - is WellknownResult.FailPrompt -> - // Relax on IS discovery if homeserver is valid - if (data.homeServerUrl != null && data.wellKnown != null) { - directLoginOnWellknownSuccess(action, WellknownResult.Prompt(data.homeServerUrl!!, null, data.wellKnown!!), homeServerConnectionConfig) - } else { - onWellKnownError() + directLoginUseCase.execute(action, homeServerConnectionConfig).fold( + onSuccess = { onSessionCreated(it, isAccountCreated = false) }, + onFailure = { + setState { copy(isLoading = false) } + _viewEvents.post(OnboardingViewEvents.Failure(it)) } - else -> { - onWellKnownError() - } - } - } - } - - private fun onWellKnownError() { - setState { copy(isLoading = false) } - _viewEvents.post(OnboardingViewEvents.Failure(Exception(stringProvider.getString(R.string.autodiscover_well_known_error)))) - } - - private suspend fun directLoginOnWellknownSuccess(action: OnboardingAction.LoginOrRegister, - wellKnownPrompt: WellknownResult.Prompt, - homeServerConnectionConfig: HomeServerConnectionConfig?) { - val alteredHomeServerConnectionConfig = homeServerConnectionConfig - ?.copy( - homeServerUriBase = Uri.parse(wellKnownPrompt.homeServerUrl), - identityServerUri = wellKnownPrompt.identityServerUrl?.let { Uri.parse(it) } - ) - ?: HomeServerConnectionConfig( - homeServerUri = Uri.parse("https://${action.username.getDomain()}"), - homeServerUriBase = Uri.parse(wellKnownPrompt.homeServerUrl), - identityServerUri = wellKnownPrompt.identityServerUrl?.let { Uri.parse(it) } - ) - - val data = try { - authenticationService.directAuthentication( - alteredHomeServerConnectionConfig, - action.username, - action.password, - action.initialDeviceName) - } catch (failure: Throwable) { - onDirectLoginError(failure) - return - } - onSessionCreated(data, isAccountCreated = false) - } - - private fun onDirectLoginError(failure: Throwable) { - when (failure) { - is MatrixIdFailure.InvalidMatrixId, - is Failure.UnrecognizedCertificateFailure -> { - setState { copy(isLoading = false) } - // Display this error in a dialog - _viewEvents.post(OnboardingViewEvents.Failure(failure)) - } - else -> { - setState { copy(isLoading = false) } - } + ) } } diff --git a/vector/src/test/java/im/vector/app/features/onboarding/OnboardingViewModelTest.kt b/vector/src/test/java/im/vector/app/features/onboarding/OnboardingViewModelTest.kt index df4e0de65e..8d4ee50f2f 100644 --- a/vector/src/test/java/im/vector/app/features/onboarding/OnboardingViewModelTest.kt +++ b/vector/src/test/java/im/vector/app/features/onboarding/OnboardingViewModelTest.kt @@ -24,6 +24,7 @@ import im.vector.app.test.fakes.FakeActiveSessionHolder import im.vector.app.test.fakes.FakeAnalyticsTracker import im.vector.app.test.fakes.FakeAuthenticationService import im.vector.app.test.fakes.FakeContext +import im.vector.app.test.fakes.FakeDirectLoginUseCase import im.vector.app.test.fakes.FakeHomeServerConnectionConfigFactory import im.vector.app.test.fakes.FakeHomeServerHistoryService import im.vector.app.test.fakes.FakeRegisterActionHandler @@ -44,6 +45,7 @@ import org.matrix.android.sdk.api.auth.registration.FlowResult 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.Stage +import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilities private const val A_DISPLAY_NAME = "a display name" @@ -55,6 +57,7 @@ private val A_RESULT_IGNORED_REGISTER_ACTION = RegisterAction.AddThreePid(Regist private val A_HOMESERVER_CAPABILITIES = aHomeServerCapabilities(canChangeDisplayName = true, canChangeAvatar = true) private val AN_IGNORED_FLOW_RESULT = FlowResult(missingStages = emptyList(), completedStages = emptyList()) private val ANY_CONTINUING_REGISTRATION_RESULT = RegistrationResult.FlowResponse(AN_IGNORED_FLOW_RESULT) +private val A_LOGIN_OR_REGISTER_ACTION = OnboardingAction.LoginOrRegister("@a-user:id.org", "a-password", "a-device-name") class OnboardingViewModelTest { @@ -69,6 +72,7 @@ class OnboardingViewModelTest { private val fakeActiveSessionHolder = FakeActiveSessionHolder(fakeSession) private val fakeAuthenticationService = FakeAuthenticationService() private val fakeRegisterActionHandler = FakeRegisterActionHandler() + private val fakeDirectLoginUseCase = FakeDirectLoginUseCase() lateinit var viewModel: OnboardingViewModel @@ -114,6 +118,26 @@ class OnboardingViewModelTest { .finish() } + @Test + fun `given has sign in with matrix id sign mode, when handling login or register action, then logs in directly`() = runTest { + val initialState = initialState.copy(signMode = SignMode.SignInWithMatrixId) + viewModel = createViewModel(initialState) + fakeDirectLoginUseCase.givenSuccessResult(A_LOGIN_OR_REGISTER_ACTION, config = null, result = fakeSession) + givenInitialisesSession(fakeSession) + val test = viewModel.test() + + viewModel.handle(A_LOGIN_OR_REGISTER_ACTION) + + test + .assertStatesChanges( + initialState, + { copy(isLoading = true) }, + { copy(isLoading = false) } + ) + .assertEvents(OnboardingViewEvents.OnAccountSignedIn) + .finish() + } + @Test fun `when handling SignUp then sets sign mode to sign up and starts registration`() = runTest { givenRegistrationResultFor(RegisterAction.StartRegistration, ANY_CONTINUING_REGISTRATION_RESULT) @@ -344,6 +368,7 @@ class OnboardingViewModelTest { FakeAnalyticsTracker(), fakeUriFilenameResolver.instance, fakeRegisterActionHandler.instance, + fakeDirectLoginUseCase.instance, FakeVectorOverrides() ) } @@ -384,7 +409,11 @@ class OnboardingViewModelTest { private fun givenSuccessfullyCreatesAccount(homeServerCapabilities: HomeServerCapabilities) { fakeSession.fakeHomeServerCapabilitiesService.givenCapabilities(homeServerCapabilities) - fakeActiveSessionHolder.expectSetsActiveSession(fakeSession) + givenInitialisesSession(fakeSession) + } + + private fun givenInitialisesSession(session: Session) { + fakeActiveSessionHolder.expectSetsActiveSession(session) fakeAuthenticationService.expectReset() fakeSession.expectStartsSyncing() } diff --git a/vector/src/test/java/im/vector/app/test/fakes/FakeDirectLoginUseCase.kt b/vector/src/test/java/im/vector/app/test/fakes/FakeDirectLoginUseCase.kt new file mode 100644 index 0000000000..b3fba51354 --- /dev/null +++ b/vector/src/test/java/im/vector/app/test/fakes/FakeDirectLoginUseCase.kt @@ -0,0 +1,31 @@ +/* + * 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.test.fakes + +import im.vector.app.features.onboarding.DirectLoginUseCase +import im.vector.app.features.onboarding.OnboardingAction +import io.mockk.coEvery +import io.mockk.mockk +import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig + +class FakeDirectLoginUseCase { + val instance = mockk() + + fun givenSuccessResult(action: OnboardingAction.LoginOrRegister, config: HomeServerConnectionConfig?, result: FakeSession) { + coEvery { instance.execute(action, config) } returns Result.success(result) + } +} From 230c37597c67c33403d173ba1685928cbaa97af3 Mon Sep 17 00:00:00 2001 From: Adam Brown Date: Thu, 24 Mar 2022 15:41:53 +0000 Subject: [PATCH 123/262] adding happy path tests for the direct login use case --- .../android/sdk/internal/extensions/Result.kt | 8 +++ .../features/onboarding/DirectLoginUseCase.kt | 21 ++---- .../app/features/onboarding/UriFactory.kt | 27 +++++++ .../onboarding/DirectLoginUseCaseTest.kt | 71 +++++++++++++++++++ .../test/fakes/FakeAuthenticationService.kt | 11 +++ .../java/im/vector/app/test/fakes/FakeUri.kt | 15 +++- .../vector/app/test/fakes/FakeUriFactory.kt | 31 ++++++++ 7 files changed, 169 insertions(+), 15 deletions(-) create mode 100644 vector/src/main/java/im/vector/app/features/onboarding/UriFactory.kt create mode 100644 vector/src/test/java/im/vector/app/features/onboarding/DirectLoginUseCaseTest.kt create mode 100644 vector/src/test/java/im/vector/app/test/fakes/FakeUriFactory.kt diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/extensions/Result.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/extensions/Result.kt index 3734c5dc1d..12adf16cbc 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/extensions/Result.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/extensions/Result.kt @@ -21,3 +21,11 @@ fun Result.foldToCallback(callback: MatrixCallback): Unit = fold( { callback.onSuccess(it) }, { callback.onFailure(it) } ) + +@Suppress("UNCHECKED_CAST") // We're casting null failure results to R +inline fun Result.andThen(block: (T) -> Result): Result { + return when (val result = getOrNull()) { + null -> this as Result + else -> block(result) + } +} diff --git a/vector/src/main/java/im/vector/app/features/onboarding/DirectLoginUseCase.kt b/vector/src/main/java/im/vector/app/features/onboarding/DirectLoginUseCase.kt index 54ee1d3a52..7ef4dfb609 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/DirectLoginUseCase.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/DirectLoginUseCase.kt @@ -16,7 +16,6 @@ package im.vector.app.features.onboarding -import android.net.Uri import im.vector.app.R import im.vector.app.core.resources.StringProvider import im.vector.app.features.onboarding.OnboardingAction.LoginOrRegister @@ -25,11 +24,13 @@ import org.matrix.android.sdk.api.auth.AuthenticationService import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig import org.matrix.android.sdk.api.auth.wellknown.WellknownResult import org.matrix.android.sdk.api.session.Session +import org.matrix.android.sdk.internal.extensions.andThen import javax.inject.Inject class DirectLoginUseCase @Inject constructor( private val authenticationService: AuthenticationService, private val stringProvider: StringProvider, + private val uriFactory: UriFactory ) { suspend fun execute(action: LoginOrRegister, homeServerConnectionConfig: HomeServerConnectionConfig?): Result { @@ -69,23 +70,15 @@ class DirectLoginUseCase @Inject constructor( } private fun HomeServerConnectionConfig.updateWith(wellKnownPrompt: WellknownResult.Prompt) = copy( - homeServerUriBase = Uri.parse(wellKnownPrompt.homeServerUrl), - identityServerUri = wellKnownPrompt.identityServerUrl?.let { Uri.parse(it) } + homeServerUriBase = uriFactory.parse(wellKnownPrompt.homeServerUrl), + identityServerUri = wellKnownPrompt.identityServerUrl?.let { uriFactory.parse(it) } ) private fun fallbackConfig(action: LoginOrRegister, wellKnownPrompt: WellknownResult.Prompt) = HomeServerConnectionConfig( - homeServerUri = Uri.parse("https://${action.username.getDomain()}"), - homeServerUriBase = Uri.parse(wellKnownPrompt.homeServerUrl), - identityServerUri = wellKnownPrompt.identityServerUrl?.let { Uri.parse(it) } + homeServerUri = uriFactory.parse("https://${action.username.getDomain()}"), + homeServerUriBase = uriFactory.parse(wellKnownPrompt.homeServerUrl), + identityServerUri = wellKnownPrompt.identityServerUrl?.let { uriFactory.parse(it) } ) private fun onWellKnownError() = Result.failure(Exception(stringProvider.getString(R.string.autodiscover_well_known_error))) } - -@Suppress("UNCHECKED_CAST") // We're casting null failure results to R -private inline fun Result.andThen(block: (T) -> Result): Result { - return when (val result = getOrNull()) { - null -> this as Result - else -> block(result) - } -} diff --git a/vector/src/main/java/im/vector/app/features/onboarding/UriFactory.kt b/vector/src/main/java/im/vector/app/features/onboarding/UriFactory.kt new file mode 100644 index 0000000000..f9e7a3458c --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/onboarding/UriFactory.kt @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2022 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.onboarding + +import android.net.Uri +import javax.inject.Inject + +class UriFactory @Inject constructor() { + + fun parse(value: String): Uri { + return Uri.parse(value) + } +} diff --git a/vector/src/test/java/im/vector/app/features/onboarding/DirectLoginUseCaseTest.kt b/vector/src/test/java/im/vector/app/features/onboarding/DirectLoginUseCaseTest.kt new file mode 100644 index 0000000000..c7bfc9b73d --- /dev/null +++ b/vector/src/test/java/im/vector/app/features/onboarding/DirectLoginUseCaseTest.kt @@ -0,0 +1,71 @@ +/* + * 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 im.vector.app.test.fakes.FakeAuthenticationService +import im.vector.app.test.fakes.FakeSession +import im.vector.app.test.fakes.FakeStringProvider +import im.vector.app.test.fakes.FakeUri +import im.vector.app.test.fakes.FakeUriFactory +import kotlinx.coroutines.test.runTest +import org.amshove.kluent.shouldBeEqualTo +import org.junit.Test +import org.matrix.android.sdk.api.MatrixPatterns.getDomain +import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig +import org.matrix.android.sdk.api.auth.data.WellKnown +import org.matrix.android.sdk.api.auth.wellknown.WellknownResult + +private val A_LOGIN_OR_REGISTER_ACTION = OnboardingAction.LoginOrRegister("@a-user:id.org", "a-password", "a-device-name") +private val A_WELLKNOWN_SUCCESS_RESULT = WellknownResult.Prompt("https://homeserverurl.com", identityServerUrl = null, WellKnown()) +private val A_WELLKNOWN_FAILED_WITH_CONTENT_RESULT = WellknownResult.FailPrompt("https://homeserverurl.com", WellKnown()) +private val NO_HOMESERVER_CONFIG: HomeServerConnectionConfig? = null +private val A_FALLBACK_CONFIG: HomeServerConnectionConfig = HomeServerConnectionConfig( + homeServerUri = FakeUri("https://${A_LOGIN_OR_REGISTER_ACTION.username.getDomain()}").instance, + homeServerUriBase = FakeUri(A_WELLKNOWN_SUCCESS_RESULT.homeServerUrl).instance, + identityServerUri = null +) + +class DirectLoginUseCaseTest { + + private val fakeAuthenticationService = FakeAuthenticationService() + private val fakeStringProvider = FakeStringProvider() + private val fakeSession = FakeSession() + + private val useCase = DirectLoginUseCase(fakeAuthenticationService, fakeStringProvider.instance, FakeUriFactory().instance) + + @Test + fun `when logging in directly, then returns success with direct session result`() = runTest { + fakeAuthenticationService.givenWellKnown(A_LOGIN_OR_REGISTER_ACTION.username, config = NO_HOMESERVER_CONFIG, result = A_WELLKNOWN_SUCCESS_RESULT) + val (username, password, initialDeviceName) = A_LOGIN_OR_REGISTER_ACTION + fakeAuthenticationService.givenDirectAuthentication(A_FALLBACK_CONFIG, username, password, initialDeviceName, result = fakeSession) + + val result = useCase.execute(A_LOGIN_OR_REGISTER_ACTION, homeServerConnectionConfig = NO_HOMESERVER_CONFIG) + + result shouldBeEqualTo Result.success(fakeSession) + } + + @Test + fun `given wellknown fails but has content, when logging in directly, then returns success with direct session result`() = runTest { + fakeAuthenticationService.givenWellKnown(A_LOGIN_OR_REGISTER_ACTION.username, config = NO_HOMESERVER_CONFIG, result = A_WELLKNOWN_FAILED_WITH_CONTENT_RESULT) + val (username, password, initialDeviceName) = A_LOGIN_OR_REGISTER_ACTION + fakeAuthenticationService.givenDirectAuthentication(A_FALLBACK_CONFIG, username, password, initialDeviceName, result = fakeSession) + + val result = useCase.execute(A_LOGIN_OR_REGISTER_ACTION, homeServerConnectionConfig = NO_HOMESERVER_CONFIG) + + result shouldBeEqualTo Result.success(fakeSession) + } +} diff --git a/vector/src/test/java/im/vector/app/test/fakes/FakeAuthenticationService.kt b/vector/src/test/java/im/vector/app/test/fakes/FakeAuthenticationService.kt index 10daf5de1e..a59116a737 100644 --- a/vector/src/test/java/im/vector/app/test/fakes/FakeAuthenticationService.kt +++ b/vector/src/test/java/im/vector/app/test/fakes/FakeAuthenticationService.kt @@ -16,11 +16,14 @@ package im.vector.app.test.fakes +import io.mockk.coEvery import io.mockk.coJustRun import io.mockk.every import io.mockk.mockk import org.matrix.android.sdk.api.auth.AuthenticationService +import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig import org.matrix.android.sdk.api.auth.registration.RegistrationWizard +import org.matrix.android.sdk.api.auth.wellknown.WellknownResult class FakeAuthenticationService : AuthenticationService by mockk() { @@ -35,4 +38,12 @@ class FakeAuthenticationService : AuthenticationService by mockk() { fun expectReset() { coJustRun { reset() } } + + fun givenWellKnown(matrixId: String, config: HomeServerConnectionConfig?, result: WellknownResult) { + coEvery { getWellKnownData(matrixId, config) } returns result + } + + fun givenDirectAuthentication(config: HomeServerConnectionConfig, matrixId: String, password: String, deviceName: String, result: FakeSession) { + coEvery { directAuthentication(config, matrixId, password, deviceName) } returns result + } } diff --git a/vector/src/test/java/im/vector/app/test/fakes/FakeUri.kt b/vector/src/test/java/im/vector/app/test/fakes/FakeUri.kt index 99f2cf39aa..675401d72f 100644 --- a/vector/src/test/java/im/vector/app/test/fakes/FakeUri.kt +++ b/vector/src/test/java/im/vector/app/test/fakes/FakeUri.kt @@ -20,9 +20,14 @@ import android.net.Uri import io.mockk.every import io.mockk.mockk -class FakeUri { +class FakeUri(contentEquals: String? = null) { + val instance = mockk() + init { + contentEquals?.let { givenEquals(it) } + } + fun givenNonHierarchical() { givenContent(schema = "mail", path = null) } @@ -31,4 +36,12 @@ class FakeUri { every { instance.scheme } returns schema every { instance.path } returns path } + + @Suppress("ReplaceCallWithBinaryOperator") + fun givenEquals(content: String) { + every { instance.equals(any()) } answers { + it.invocation.args.first() == content + } + every { instance.hashCode() } answers { content.hashCode() } + } } diff --git a/vector/src/test/java/im/vector/app/test/fakes/FakeUriFactory.kt b/vector/src/test/java/im/vector/app/test/fakes/FakeUriFactory.kt new file mode 100644 index 0000000000..90b615cb7c --- /dev/null +++ b/vector/src/test/java/im/vector/app/test/fakes/FakeUriFactory.kt @@ -0,0 +1,31 @@ +/* + * 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.test.fakes + +import im.vector.app.features.onboarding.UriFactory +import io.mockk.every +import io.mockk.mockk + +class FakeUriFactory { + + val instance = mockk().also { + every { it.parse(any()) } answers { + val input = it.invocation.args.first() as String + FakeUri().also { it.givenEquals(input) }.instance + } + } +} From cfb3aa8a221a370b9c7ba0526bd2e06b70060917 Mon Sep 17 00:00:00 2001 From: Adam Brown Date: Thu, 24 Mar 2022 16:23:03 +0000 Subject: [PATCH 124/262] adding direct login error path tests --- .../onboarding/DirectLoginUseCaseTest.kt | 40 ++++++++++++++++++- .../onboarding/OnboardingViewModelTest.kt | 20 ++++++++++ .../test/fakes/FakeAuthenticationService.kt | 8 ++++ .../app/test/fakes/FakeDirectLoginUseCase.kt | 4 ++ .../app/test/fakes/FakeStringProvider.kt | 2 + 5 files changed, 73 insertions(+), 1 deletion(-) diff --git a/vector/src/test/java/im/vector/app/features/onboarding/DirectLoginUseCaseTest.kt b/vector/src/test/java/im/vector/app/features/onboarding/DirectLoginUseCaseTest.kt index c7bfc9b73d..5a3c323316 100644 --- a/vector/src/test/java/im/vector/app/features/onboarding/DirectLoginUseCaseTest.kt +++ b/vector/src/test/java/im/vector/app/features/onboarding/DirectLoginUseCaseTest.kt @@ -16,12 +16,15 @@ package im.vector.app.features.onboarding +import im.vector.app.R import im.vector.app.test.fakes.FakeAuthenticationService import im.vector.app.test.fakes.FakeSession import im.vector.app.test.fakes.FakeStringProvider import im.vector.app.test.fakes.FakeUri import im.vector.app.test.fakes.FakeUriFactory +import im.vector.app.test.fakes.toTestString import kotlinx.coroutines.test.runTest +import org.amshove.kluent.should import org.amshove.kluent.shouldBeEqualTo import org.junit.Test import org.matrix.android.sdk.api.MatrixPatterns.getDomain @@ -32,12 +35,14 @@ import org.matrix.android.sdk.api.auth.wellknown.WellknownResult private val A_LOGIN_OR_REGISTER_ACTION = OnboardingAction.LoginOrRegister("@a-user:id.org", "a-password", "a-device-name") private val A_WELLKNOWN_SUCCESS_RESULT = WellknownResult.Prompt("https://homeserverurl.com", identityServerUrl = null, WellKnown()) private val A_WELLKNOWN_FAILED_WITH_CONTENT_RESULT = WellknownResult.FailPrompt("https://homeserverurl.com", WellKnown()) +private val A_WELLKNOWN_FAILED_WITHOUT_CONTENT_RESULT = WellknownResult.FailPrompt(null, null) private val NO_HOMESERVER_CONFIG: HomeServerConnectionConfig? = null private val A_FALLBACK_CONFIG: HomeServerConnectionConfig = HomeServerConnectionConfig( homeServerUri = FakeUri("https://${A_LOGIN_OR_REGISTER_ACTION.username.getDomain()}").instance, homeServerUriBase = FakeUri(A_WELLKNOWN_SUCCESS_RESULT.homeServerUrl).instance, identityServerUri = null ) +private val AN_ERROR = RuntimeException() class DirectLoginUseCaseTest { @@ -59,7 +64,7 @@ class DirectLoginUseCaseTest { } @Test - fun `given wellknown fails but has content, when logging in directly, then returns success with direct session result`() = runTest { + fun `given wellknown fails with content, when logging in directly, then returns success with direct session result`() = runTest { fakeAuthenticationService.givenWellKnown(A_LOGIN_OR_REGISTER_ACTION.username, config = NO_HOMESERVER_CONFIG, result = A_WELLKNOWN_FAILED_WITH_CONTENT_RESULT) val (username, password, initialDeviceName) = A_LOGIN_OR_REGISTER_ACTION fakeAuthenticationService.givenDirectAuthentication(A_FALLBACK_CONFIG, username, password, initialDeviceName, result = fakeSession) @@ -68,4 +73,37 @@ class DirectLoginUseCaseTest { result shouldBeEqualTo Result.success(fakeSession) } + + @Test + fun `given wellknown fails without content, when logging in directly, then returns well known error`() = runTest { + fakeAuthenticationService.givenWellKnown(A_LOGIN_OR_REGISTER_ACTION.username, config = NO_HOMESERVER_CONFIG, result = A_WELLKNOWN_FAILED_WITHOUT_CONTENT_RESULT) + val (username, password, initialDeviceName) = A_LOGIN_OR_REGISTER_ACTION + fakeAuthenticationService.givenDirectAuthentication(A_FALLBACK_CONFIG, username, password, initialDeviceName, result = fakeSession) + + val result = useCase.execute(A_LOGIN_OR_REGISTER_ACTION, homeServerConnectionConfig = NO_HOMESERVER_CONFIG) + + result should { this.isFailure } + result should { this.exceptionOrNull() is Exception } + result should { this.exceptionOrNull()?.message == R.string.autodiscover_well_known_error.toTestString() } + } + + @Test + fun `given wellknown throws, when logging in directly, then returns failure result with original cause`() = runTest { + fakeAuthenticationService.givenWellKnownThrows(A_LOGIN_OR_REGISTER_ACTION.username, config = NO_HOMESERVER_CONFIG, cause = AN_ERROR) + + val result = useCase.execute(A_LOGIN_OR_REGISTER_ACTION, homeServerConnectionConfig = NO_HOMESERVER_CONFIG) + + result shouldBeEqualTo Result.failure(AN_ERROR) + } + + @Test + fun `given direct authentication throws, when logging in directly, then returns failure result with original cause`() = runTest { + fakeAuthenticationService.givenWellKnown(A_LOGIN_OR_REGISTER_ACTION.username, config = NO_HOMESERVER_CONFIG, result = A_WELLKNOWN_SUCCESS_RESULT) + val (username, password, initialDeviceName) = A_LOGIN_OR_REGISTER_ACTION + fakeAuthenticationService.givenDirectAuthenticationThrows(A_FALLBACK_CONFIG, username, password, initialDeviceName, cause = AN_ERROR) + + val result = useCase.execute(A_LOGIN_OR_REGISTER_ACTION, homeServerConnectionConfig = NO_HOMESERVER_CONFIG) + + result shouldBeEqualTo Result.failure(AN_ERROR) + } } diff --git a/vector/src/test/java/im/vector/app/features/onboarding/OnboardingViewModelTest.kt b/vector/src/test/java/im/vector/app/features/onboarding/OnboardingViewModelTest.kt index 8d4ee50f2f..118bf689d2 100644 --- a/vector/src/test/java/im/vector/app/features/onboarding/OnboardingViewModelTest.kt +++ b/vector/src/test/java/im/vector/app/features/onboarding/OnboardingViewModelTest.kt @@ -138,6 +138,26 @@ class OnboardingViewModelTest { .finish() } + @Test + fun `given has sign in with matrix id sign mode, when handling login or register action fails, then emits error`() = runTest { + val initialState = initialState.copy(signMode = SignMode.SignInWithMatrixId) + viewModel = createViewModel(initialState) + fakeDirectLoginUseCase.givenFailureResult(A_LOGIN_OR_REGISTER_ACTION, config = null, cause = AN_ERROR) + givenInitialisesSession(fakeSession) + val test = viewModel.test() + + viewModel.handle(A_LOGIN_OR_REGISTER_ACTION) + + test + .assertStatesChanges( + initialState, + { copy(isLoading = true) }, + { copy(isLoading = false) } + ) + .assertEvents(OnboardingViewEvents.Failure(AN_ERROR)) + .finish() + } + @Test fun `when handling SignUp then sets sign mode to sign up and starts registration`() = runTest { givenRegistrationResultFor(RegisterAction.StartRegistration, ANY_CONTINUING_REGISTRATION_RESULT) diff --git a/vector/src/test/java/im/vector/app/test/fakes/FakeAuthenticationService.kt b/vector/src/test/java/im/vector/app/test/fakes/FakeAuthenticationService.kt index a59116a737..9175fd3750 100644 --- a/vector/src/test/java/im/vector/app/test/fakes/FakeAuthenticationService.kt +++ b/vector/src/test/java/im/vector/app/test/fakes/FakeAuthenticationService.kt @@ -43,7 +43,15 @@ class FakeAuthenticationService : AuthenticationService by mockk() { coEvery { getWellKnownData(matrixId, config) } returns result } + fun givenWellKnownThrows(matrixId: String, config: HomeServerConnectionConfig?, cause: Throwable) { + coEvery { getWellKnownData(matrixId, config) } throws cause + } + fun givenDirectAuthentication(config: HomeServerConnectionConfig, matrixId: String, password: String, deviceName: String, result: FakeSession) { coEvery { directAuthentication(config, matrixId, password, deviceName) } returns result } + + fun givenDirectAuthenticationThrows(config: HomeServerConnectionConfig, matrixId: String, password: String, deviceName: String, cause: Throwable) { + coEvery { directAuthentication(config, matrixId, password, deviceName) } throws cause + } } diff --git a/vector/src/test/java/im/vector/app/test/fakes/FakeDirectLoginUseCase.kt b/vector/src/test/java/im/vector/app/test/fakes/FakeDirectLoginUseCase.kt index b3fba51354..8a5c6b1cee 100644 --- a/vector/src/test/java/im/vector/app/test/fakes/FakeDirectLoginUseCase.kt +++ b/vector/src/test/java/im/vector/app/test/fakes/FakeDirectLoginUseCase.kt @@ -28,4 +28,8 @@ class FakeDirectLoginUseCase { fun givenSuccessResult(action: OnboardingAction.LoginOrRegister, config: HomeServerConnectionConfig?, result: FakeSession) { coEvery { instance.execute(action, config) } returns Result.success(result) } + + fun givenFailureResult(action: OnboardingAction.LoginOrRegister, config: HomeServerConnectionConfig?, cause: Throwable) { + coEvery { instance.execute(action, config) } returns Result.failure(cause) + } } diff --git a/vector/src/test/java/im/vector/app/test/fakes/FakeStringProvider.kt b/vector/src/test/java/im/vector/app/test/fakes/FakeStringProvider.kt index f9001e3f8a..1a4f5cb85b 100644 --- a/vector/src/test/java/im/vector/app/test/fakes/FakeStringProvider.kt +++ b/vector/src/test/java/im/vector/app/test/fakes/FakeStringProvider.kt @@ -30,3 +30,5 @@ class FakeStringProvider { } } } + +fun Int.toTestString() = "test-$this" From 776cf2451621b6fb2beee7538544c08ede66f980 Mon Sep 17 00:00:00 2001 From: Adam Brown Date: Thu, 24 Mar 2022 16:24:17 +0000 Subject: [PATCH 125/262] adding changelog entry --- changelog.d/5628.misc | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/5628.misc diff --git a/changelog.d/5628.misc b/changelog.d/5628.misc new file mode 100644 index 0000000000..9c4894c164 --- /dev/null +++ b/changelog.d/5628.misc @@ -0,0 +1 @@ +Adds unit tests around the login with matrix id flow \ No newline at end of file From bdbdfe52cdbd3f2a1e8560b1ce5ee7d54ae41b29 Mon Sep 17 00:00:00 2001 From: Onuray Sahin Date: Fri, 25 Mar 2022 14:24:42 +0300 Subject: [PATCH 126/262] Cancel timers when service is destroyed. --- .../location/LocationSharingService.kt | 20 ++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/location/LocationSharingService.kt b/vector/src/main/java/im/vector/app/features/location/LocationSharingService.kt index b11ddf93e2..a2a68e4188 100644 --- a/vector/src/main/java/im/vector/app/features/location/LocationSharingService.kt +++ b/vector/src/main/java/im/vector/app/features/location/LocationSharingService.kt @@ -42,6 +42,7 @@ class LocationSharingService : VectorService(), LocationTracker.Callback { @Inject lateinit var locationTracker: LocationTracker private var roomArgsList = mutableListOf() + private var timers = mutableListOf() override fun onCreate() { super.onCreate() @@ -72,11 +73,18 @@ class LocationSharingService : VectorService(), LocationTracker.Callback { } private fun scheduleTimer(roomId: String, durationMillis: Long) { - Timer().schedule(object : TimerTask() { - override fun run() { - stopSharingLocation(roomId) - } - }, durationMillis) + Timer() + .apply { + schedule(object : TimerTask() { + override fun run() { + stopSharingLocation(roomId) + timers.remove(this@apply) + } + }, durationMillis) + } + .also { + timers.add(it) + } } private fun stopSharingLocation(roomId: String) { @@ -101,6 +109,8 @@ class LocationSharingService : VectorService(), LocationTracker.Callback { private fun destroyMe() { locationTracker.removeCallback(this) + timers.forEach { it.cancel() } + timers.clear() stopSelf() } From 883c1816bc4787b1671a2582771d3b961062c40e Mon Sep 17 00:00:00 2001 From: LinAGKar Date: Sat, 26 Mar 2022 20:24:56 +0000 Subject: [PATCH 127/262] Translated using Weblate (Swedish) Currently translated at 100.0% (2171 of 2171 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/sv/ --- vector/src/main/res/values-sv/strings.xml | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/vector/src/main/res/values-sv/strings.xml b/vector/src/main/res/values-sv/strings.xml index 11c38830f6..2b4e3502f8 100644 --- a/vector/src/main/res/values-sv/strings.xml +++ b/vector/src/main/res/values-sv/strings.xml @@ -2012,7 +2012,7 @@ Du är inbjuden Utrymmen är ett nytt sätt att gruppera rum och personer. Lägg till existerande rum och utrymme - Lämna utrymme + Lämna Lägg till rum Utforska rum @@ -2420,4 +2420,22 @@ %d server-ACL-ändringar Trådar närmar sig beta 🎉 + Stoppa + Kontinuerligt plats aktiverad + Om du vill dela din plats kontinuerligt så behöver ${app_name} åtkomst till din plats hela tiden när appen är i bakgrunden. +\nVi kommer bara använda din plats under tiden du väljer. + Tillåt åtkomst + Dela den här platsen + Dela den här platsen + Dela plats kontinuerligt + Dela plats kontinuerligt + Dela min nuvarande plats + Dela min nuvarande plats + Zooma in till nuvarande plats + Nål för vald plats på kartan + Vi kommer närmare att släppa en offentlig beta för trådar. +\n +\nMedan vi förbereder den så behöver vi göra några ändringar: trådar skapade innan den här tidpunkten kommer att visas som vanliga svar. +\n +\nDet kommer att vara en engångshändelse, eftersom trådar nu är en del av Matrixspecifikationen. \ No newline at end of file From cc8fd0c9a73aab2f4062fd694ce1d81e34394bd3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sveinn=20=C3=AD=20Felli?= Date: Sat, 26 Mar 2022 16:22:02 +0000 Subject: [PATCH 128/262] Translated using Weblate (Icelandic) Currently translated at 82.5% (1793 of 2171 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/is/ --- vector/src/main/res/values-is/strings.xml | 234 +++++++++++++++++++--- 1 file changed, 205 insertions(+), 29 deletions(-) diff --git a/vector/src/main/res/values-is/strings.xml b/vector/src/main/res/values-is/strings.xml index 6deeb7fac5..f48aa28e62 100644 --- a/vector/src/main/res/values-is/strings.xml +++ b/vector/src/main/res/values-is/strings.xml @@ -26,8 +26,8 @@ %1$s fjarlægði birtingarnafn sitt (sem var %2$s) %1$s breytti umræðuefninu í: %2$s %1$s breytti heiti spjallrásarinnar í: %2$s - %s hringdi myndsamtal. - %s hringdi raddsamtal. + %s hringdi myndsímtal. + %s hringdi raddsímtal. %s svaraði símtalinu. %s lauk símtalinu. %1$s fjarlægði heiti spjallrásar @@ -63,8 +63,8 @@ eða Bjóða Skrá út - Raddsamtal - Myndsamtal + Raddsímtal + Myndsímtal Merkja allt sem lesið Opna Loka @@ -88,8 +88,8 @@ Notandanafn Skrá út Leita - Hefja raddsamtal - Hefja myndsamtal + Hefja raddsímtal + Hefja myndsímtal Senda skrár Taka ljósmynd eða myndskeið Taka ljósmynd @@ -108,9 +108,9 @@ Miðlungs Lítið Símtal - Innhringing myndsamtals - Innhringing raddsamtals - Samtal í gangi… + Innhringing myndsímtals + Innhringing raddsímtals + Símtal í gangi… Upplýsingar NEI @@ -245,9 +245,9 @@ Skráðu inn gilda URL-lóð Inniheldur ekki gilt JSON Of margar beiðnir hafa verið sendar - Samtal tengist… + Símtal tengist… Símtali lokið - Boð um samtal + Boð um símtöl Nota innbyggða myndavél Minnst á Titra þegar minnst er á @@ -268,8 +268,8 @@ Tilraunir Kæra efni Slóð á heimaþjón - Ertu viss að þú viljir byrja raddsamtal? - Ertu viss að þú viljir byrja myndsamtal? + Ertu viss að þú viljir byrja raddsímtal\? + Ertu viss að þú viljir byrja myndsímtal\? Fara í ólesið Banna Afbanna @@ -430,8 +430,8 @@ Þú settir símtalið í bið %s setti símtalið í bið Raddsímtal við %s - Myndsamtal við %s - Myndsamtal í gangi… + Myndsímtal við %s + Myndsímtal í gangi… Ósvarað myndsímtal %d ósvöruð myndsímtöl @@ -521,7 +521,7 @@ %1$s tók %2$s úr banni. Ástæða: %3$s Þú fjarlægðir %1$s. Ástæða: %2$s %1$s fjarlægði %2$s. Ástæða: %3$s - Þú hafnaðir boðinu. Ástæða: %2$s + Þú hafnaðir boðinu. Ástæða: %1$s %1$s hafnaði boðinu. Ástæða: %2$s Þú hættir. Ástæða: %1$s %1$s hætti. Ástæða: %2$s @@ -562,7 +562,7 @@ %1$s fjarlægði %2$s viðmótshluta Þú bættir við %1$s viðmótshluta %1$s bætti við %2$s viðmótshluta - Þú samþykktir boð um að taka þátt í %$s + Þú samþykktir boð um að taka þátt í %1$s Þú afturkallaðir boðið til %1$s %1$s afturkallaði boðið til %2$s Þú afturkallaðir boð til %1$s um þátttöku í spjallrásinni @@ -602,10 +602,10 @@ Þú gerðir ferilskrá spjallrásar héðan í frá sýnilega fyrir %1$s Þú laukst símtalinu. Þú svaraðir símtalinu. - Þú sendir gögn til að setja upp samtalið. - %s sendi gögn til að setja upp samtalið. - Þú hringdir raddsamtal. - Þú hringdir myndsamtal. + Þú sendir gögn til að setja upp símtalið. + %s sendi gögn til að setja upp símtalið. + Þú hringdir raddsímtal. + Þú hringdir myndsímtal. Þú breyttir heiti spjallrásarinnar í: %1$s Þú breyttir auðkennismynd spjallrásarinnar %1$s breytti auðkennismynd spjallrásarinnar @@ -623,7 +623,7 @@ Opna emoji-tánmyndaval Skipta um auðkennismynd Opna viðmótshluta - Virkt samtal (%1$s) + Virkt símtal (%1$s) Talnaborð Nýtt PIN-númer Opna notkunarskilmála %s @@ -1080,14 +1080,14 @@ Notendur Flutningur Tengjast - Virkt samtal (%1$s) · + Virkt símtal (%1$s) · - Virkt samtal · - %1$d virk samtöl · + Virkt símtal · + %1$d virk símtöl · Ekkert svar - Innhringing myndsamtals - Innhringing raddsamtals + Innhringing myndsímtals + Innhringing raddsímtals Hringja til baka Þessu símtali er lokið Henda breytingum @@ -1736,8 +1736,8 @@ Efni atburðar Ósvarað myndsímtal Ósvarað símtal - Virkt myndsamtal - Virkt raddsamtal + Virkt myndsímtal + Virkt raddsímtal Þú hafnaðir þessu símtali QR-kóði var ekki skannaður! Ógildur QR-kóði (ógild slóð)! @@ -1774,4 +1774,180 @@ Tekur stjórnunarréttindi af notanda með uppgefið auðkenni Hættir að hunsa notanda, birtir skilaboð viðkomandi héðan í frá Setja upp varið öryggisafrit + Virkja að strjúka á tímalínu til að svara + Birta allan ferilinn í dulrituðum spjallrásum + Mistókst að senda umsögnina (%s) + Það tókst að senda umsögnina + Mistókst að senda tillöguna (%s) + Það tókst að senda tillöguna + Engar skráðar ýtigáttir + Engar ýtireglur skilgreindar + Ýtireglur (push rules) + Flytja e2e-lykla inn úr skránni \"%1$s\". + Hver sem er getur tekið þátt í þessari spjallrás + Ekki er ennþá búið að útbúa spjallrásina. Hætta við að búa hana til\? + Bjóddu með tölvupósti, finndu tengiliði og ýmislegt fleira… + Ljúka við að setja upp uppgötvun. + Þú ert núna ekki að nota neinn auðkennisþjón. Til að uppgötva og vera finnanleg/ur fyrir félaga þína í teyminu, skaltu bæta við auðkennisþjóni hér fyrir neðan. + ${app_name} krefst þess að þú setjir inn auðkennin þín til að framkvæma þessa aðgerð. + Endurauðkenning er nauðsynleg + Yfirfarðu hvar þú sért skráð/ur inn + Nota endurheimtulykil + Athuga öryggisafritunarlykil + Þetta er ekki gildur endurheimtulykill + Settu inn %s þinn til að halda áfram + + %d aðili sem þú þekkir hefur þegar tekið þátt + %d aðilar sem þú þekkir hafa þegar tekið þátt + + Tengillinn %1$s fer með þig yfir á annað vefsvæði: %2$s. +\n +\nErtu viss um að þú viljir halda áfram\? + Tengillinn er ekki rétt formaður + Finn ekki þessa spjallrás. Gakktu úr skugga um að hún sé til. + Get ekki opnað spjallrás þar sem þú ert í banni. + Krafist er PIN-númers í hvert skipti sem þú opnar ${app_name}. + Krafist er PIN-númers ef þú notar ekki ${app_name} í 2 mínútur. + Krefjast PIN-númers eftir 2 mínútur + Aðeins birta fjölda ólesinna skilaboða í einfaldri tilkynningu. + Birta nánari upplýsingar eins og heiti spjallrása og efni skilaboða. + PIN-númer er eina leiðin til að aflæsa ${app_name}. + Virkjaðu sérstök lífkenni tækisins, eins og fingrafaraskönnun og andlitakennsl. + Virkja lífkenni + Ef þú vilt endurstilla PIN-númerið þitt, geturðu ýtt á \'Gleymt PIN-númer\?\' og endurstillt það. + Verðu aðganginn með PIN-númeri og lífkennum. + Til að endurstilla PIN-númerið, þarftu að skrá þig inn aftur og útbúa nýtt. + Mistókst að fullgilda PIN-númer, ýttu á annað nýtt. + Veldu PIN-númer í öryggisskyni + Of margar villur, þú hefur verið skráð/ur út + Aðvörun! Síðasta tilraunin sem eftir er áður en útskráning fer fram! + + Rangur kóði, %d tilraun eftir + Rangur kóði, %d tilraunir eftir + + Yfirfarðu stillingarnar þínar til að virkja ýtitilkynningar + Leita að tengiliðum á Matrix + Tengiliðaskráin þín er tóm + Næ í tengiliðina þína… + Við iðum í skinninu eftir að tilkynbna að við höfum skipt um nafn! Forritið er að fullu uppfært og þú ert skráð/ur aftur inn á aðganginn þinn. + Bíð eftir ferli dulritunar + Þú hefur ekki aðgang að þessum skilaboðum því sendandinn hefur viljandi ekki sent dulritunarlyklana + Þú hefur ekki aðgang að þessum skilaboðum því setunni þinni er ekki treyst af sendandanum + Þú hefur ekki aðgang að þessum skilaboðum því sendandinn hefur lokað á þig + Vegna enda-í-enda dulritunar, gætirðu þurft að bíða eftir skilaboðum frá einhverjum þar sem þér hafa ekki verið sendir dulritunarlyklar á réttan hátt. + Bíð eftir þessum skilaboðum, þetta getur tekið smá tíma + Þú hefur ekki aðgang að þessum skilaboðum + Þér tókst að breyta stillingum spjallrásarinnar + Settu inn öryggisfrasa sem aðeins þú þekkir, þetta er notað til að verja leyndarmálin sem þú geymir á netþjóninum þínum. + Geymdu öryggislykilinn þinn á öruggum stað, eins og í lykilorðastýringu eða jafnvel í peningaskáp. + Settu inn leynilegan frasa eða setningu sem aðeins þú þekkir, og útbúðu lykil fyrir öryggisafrit. + Útbúðu öryggislykil til að geyma á öruggum stað, eins og í lykilorðastýringu eða jafnvel í peningaskáp. + Tryggðu þig gegn því að missa aðgang að dulrituðum skilaboðum og gögnum með því að taka öryggisafrit af dulritunarlyklunum á netþjóninum þinum. + Settu inn slóðina á auðkennisþjón + Aftengjast frá auðkennisþjóninum %s \? + + Boð voru send til %1$s og eins til viðbótar + Boð voru send til %1$s og %2$d til viðbótar + + Þetta er ekki gildur QR-kóði á Matrix + Boð voru send til %1$s og %2$s + Boð var sent til %1$s + Halló, talaðu við mig á ${app_name}: %s + Ekki tókst að setja upp kross-undirritun + Sannprófa gagnvirkt með táknmyndum + Sannprófa innskráningu + Það lítur út fyrir að heimaþjónninn þinn styðji ekki ennþá við notkun svæða + Villa kom upp þegar við áframsendingu símtals + %1$s Ýttu til að fara til baka + sinnum hafnað + Raddsímtali hafnað + Mynddsímtali lauk • %1$s + Raddsímtali lauk • %1$s + %1$s hafnaði þessu símtali + Það eru óvistaðar breytingar. Viltu henda þeim\? + Getur ekki sent sjálfum þér bein skilaboð! + Breyta fyrirliggjandi PIN-númeri þínu + Birta efni í tilkynningum + Stilla varnir + Verja aðgang + Búum til spjallrás fyrir hvern og einn þeirra. Þú getur bætt fleirum við síðar, þar með töldum þeim sem fyrir eru þegar. + Búum til spjallrás fyrir þá. Þú getur bætt fleirum við síðar. + Bættu við nánari atriðum svo fólk eigi auðveldara með að þekkja þetta. Þú getur breytt þessu hvenær sem er. + Bættu við nánari atriðum til að aðgreina þetta frá öðru. Þú getur breytt þessu hvenær sem er. + Ertu viss um að þú viljir eyða öllum ósendum skilaboðum úr þessari spjallrás\? + Viltu hætta við að senda skilaboðin\? + Eyða öllum misförnum skilaboðum + Stöðuatburður sendur! + Rangt sniðinn atburður + Vantar gerð skilaboða + Senda sérsniðinn stöðuatburð + Stöðuatburðir + Senda stöðuatburð + Forritunartól + Skoða leskvittanir + Skilaboð voru ekki send vegna villu + Loka emoji-tánmyndavali + Stig trausts er treyst + Stig trausts er aðvarandi + Sjálfgefið stig trausts + er með ósend drög + Sum skilaboð hafa ekki verið send + Kross-undirritun er virk +\nLyklum er ekki treyst + Kross-undirritun er virk +\nLyklum er treyst. +\nEinkalyklar eru ekki þekktir + Vantreyst innskráning + Villa kom upp við að sækja upplýsingar um traust + Taka samt þátt + Taka þátt í svæði + Taktu þátt í svæðinu mínu %1$s %2$s + Þau munu ekki vera hluti af %s + Þau munu geta kannað %s + Í augnablikinu ert þetta bara þú. %s verður enn betri með fleirum. + Bjóddu fólki inn á svæðið þitt + Í hvaða málum ertu að vinna\? + Gakktu úr skugga um að rétta fólkið hafi aðgang að %s. Þú getur boðið fleira fólki síðar. + Hverjir eru félagar í teyminu þínu\? + Hverjar eru umræðurnar sem þú vilt hafa í %s\? + Gefðu því nafn til að halda áfram. + Gakktu úr skugga um að rétta fólkið hafi aðgang að %s. + Til að ganga til liðs við fyrirliggjandi svæði þarftu boð. + Hvaða tegund af svæði viltu búa til\? + Ertu viss um að þú viljir ljúka þessari könnun\? Þú munt ekki geta endurheimt hana ef hún hefur einu sinni verið fjarlægð. + Þetta mun birta lokaniðurstöður könnunarinnar og koma í veg fyrir að fólk geti kosið. + %s í stillingunum til að fá boð beint í ${app_name}. + Hver sem er í yfirsvæði mun geta fundið og tekið þátt í þessari spjallrás - ekki er þörf á að bjóða öllum handvirkt. Þú munt geta breytt þessu í stillingum spjallrásarinnar hvenær sem er. + Hver sem er í %s mun geta fundið og tekið þátt í þessari spjallrás - ekki er þörf á að bjóða öllum handvirkt. Þú munt geta breytt þessu í stillingum spjallrásarinnar hvenær sem er. + Get ekki svarað eða breytt á meðan talskilaboð eru virk + Ýttu á upptökuna þína til að stöðva eða hlusta + Haltu niðri til að taka upp, slepptu til að senda + Því miður, villa kom upp við að reyna að taka þátt: %s + Uppfæra í þá útgáfu spjallrásar sem mælt er með + Uppfæra yfirsvæði sjálfvirkt + Þú munt uppfæra þessa spjallrás úr %1$s upp í %2$s. + Þetta svæði er ekki með neinar spjallrásir + Hafðu samband við stjórnanda heimaþjónsins þíns til að fá frekari upplýsingar + Ertu til í tilraunastarfsemi\? +\nÞú getur bætt fyrirliggjandi svæðum í annað svæði. + Allar spjallrásir sem þú ert í munu birtast á forsíðu. + Leitarðu að einhverjum sem ekki er í %s\? + Athugaðu: forritið verður endurræst + Virkja spjallþræði fyrir skilaboð + Tilkynna afkóðunarvillur sjálfvirkt. + Bættu svæði við eitthvað svæði sem þú stýrir. + Bæta við fyrirliggjandi spjallrásum og svæði + Veldu það sem á að yfirgefa + Yfirgefa tilteknar spjallrásir og svæði… + Ekki yfirgefa neinar spjallrásir eða svæði + Ertu viss um að þú viljir yfirgefa %s\? + Einkasvæði fyrir þig og félaga í teyminu þínu + Bæta við í uppgefið svæði + Gögn notandaaðgangs + Virkar setur + Óheimilt, vantar gild auðkenni sannvottunar + SSL-villa: auðkenni jafningjans hefur ekki verið sannreynt. + Næ ekki að tengjast heimaþjóni á slóðinni %s. Athugaðu slóðina eða veldu heimaþjón handvirkt.. + Yfirfarðu reglur þessa heimaþjóns: + Senda feril beiðna um deilingu lykla \ No newline at end of file From ee9c8d862674a1e413bead1e7f39bf148e25b227 Mon Sep 17 00:00:00 2001 From: bmarty Date: Mon, 28 Mar 2022 00:03:23 +0000 Subject: [PATCH 129/262] Sync analytics plan --- .../app/features/analytics/plan/ViewRoom.kt | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/vector/src/main/java/im/vector/app/features/analytics/plan/ViewRoom.kt b/vector/src/main/java/im/vector/app/features/analytics/plan/ViewRoom.kt index a73ca5a9b3..5ad1beaee5 100644 --- a/vector/src/main/java/im/vector/app/features/analytics/plan/ViewRoom.kt +++ b/vector/src/main/java/im/vector/app/features/analytics/plan/ViewRoom.kt @@ -63,6 +63,49 @@ data class ViewRoom( */ MessageUser, + /** + * Room switched due to user interacting with a file search result. + */ + MobileFileSearch, + + /** + * Room accessed via interacting with the incall screen. + */ + MobileInCall, + + /** + * Room accessed via interacting with direct chat item in the room + * contact detail screen. + */ + MobileRoomMemberDetail, + + /** + * Room switched due to user interacting with a room search result. + */ + MobileRoomSearch, + + /** + * Room accessed via interacting with direct chat item in the search + * contact detail screen. + */ + MobileSearchContactDetail, + + /** + * Room accessed via interacting with direct chat item in the space + * contact detail screen. + */ + MobileSpaceMemberDetail, + + /** + * Space accessed via interacting with the space menu. + */ + MobileSpaceMenu, + + /** + * Space accessed via interacting with a space settings menu item. + */ + MobileSpaceSettings, + /** * Room accessed via a push/desktop notification. */ From a9b87de7a3fe2406c3dc546744f38bb75e38e571 Mon Sep 17 00:00:00 2001 From: ClaireG Date: Mon, 28 Mar 2022 10:34:22 +0200 Subject: [PATCH 130/262] [Subscribing] Blank display name --- changelog.d/5497.bugfix | 1 + .../sdk/api/session/profile/ProfileService.kt | 14 ++++++++++++++ .../android/sdk/internal/session/sync/SyncTask.kt | 7 ++++++- .../RoomMemberProfileViewModel.kt | 7 +++---- .../features/userdirectory/UserListViewModel.kt | 15 +++++---------- 5 files changed, 29 insertions(+), 15 deletions(-) create mode 100644 changelog.d/5497.bugfix diff --git a/changelog.d/5497.bugfix b/changelog.d/5497.bugfix new file mode 100644 index 0000000000..4228ebaafb --- /dev/null +++ b/changelog.d/5497.bugfix @@ -0,0 +1 @@ +[Subscribing] Blank display name \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/profile/ProfileService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/profile/ProfileService.kt index 05fa24946a..d2c677bb31 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/profile/ProfileService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/profile/ProfileService.kt @@ -21,6 +21,7 @@ import android.net.Uri import androidx.lifecycle.LiveData import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor import org.matrix.android.sdk.api.session.identity.ThreePid +import org.matrix.android.sdk.api.session.user.model.User import org.matrix.android.sdk.api.util.JsonDict import org.matrix.android.sdk.api.util.Optional @@ -118,4 +119,17 @@ interface ProfileService { * Remove a 3Pid from the Matrix account. */ suspend fun deleteThreePid(threePid: ThreePid) + + /** + * Return a User object from a userId + */ + suspend fun getProfileAsUser(userId: String): User { + return getProfile(userId).let { dict -> + User( + userId = userId, + displayName = dict[DISPLAY_NAME_KEY] as? String, + avatarUrl = dict[AVATAR_URL_KEY] as? String + ) + } + } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncTask.kt index b4da1a02cd..2136259f22 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncTask.kt @@ -18,6 +18,7 @@ package org.matrix.android.sdk.internal.session.sync import android.os.SystemClock import okhttp3.ResponseBody +import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.api.logger.LoggerTag import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.initsync.InitSyncStep @@ -104,7 +105,11 @@ internal class DefaultSyncTask @Inject constructor( val isInitialSync = token == null if (isInitialSync) { // We might want to get the user information in parallel too - userStore.createOrUpdate(userId) + val user = tryOrNull { session.getProfileAsUser(userId) } + userStore.createOrUpdate( + userId = userId, + displayName = user?.displayName, + avatarUrl = user?.avatarUrl) defaultSyncStatusService.startRoot(InitSyncStep.ImportingAccount, 100) } // Maybe refresh the homeserver capabilities data we know 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 db54f27910..1f23fec327 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 @@ -48,7 +48,6 @@ import org.matrix.android.sdk.api.session.accountdata.UserAccountDataTypes import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.events.model.toContent import org.matrix.android.sdk.api.session.events.model.toModel -import org.matrix.android.sdk.api.session.profile.ProfileService import org.matrix.android.sdk.api.session.room.Room import org.matrix.android.sdk.api.session.room.members.roomMemberQueryParams import org.matrix.android.sdk.api.session.room.model.Membership @@ -328,12 +327,12 @@ class RoomMemberProfileViewModel @AssistedInject constructor( private suspend fun fetchProfileInfo() { val result = runCatchingToAsync { - session.getProfile(initialState.userId) + session.getProfileAsUser(initialState.userId) .let { MatrixItem.UserItem( id = initialState.userId, - displayName = it[ProfileService.DISPLAY_NAME_KEY] as? String, - avatarUrl = it[ProfileService.AVATAR_URL_KEY] as? String + displayName = it.displayName, + avatarUrl = it.avatarUrl ) } } 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 039c7041b0..783fddf8ad 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 @@ -45,7 +45,6 @@ import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.identity.IdentityServiceError import org.matrix.android.sdk.api.session.identity.IdentityServiceListener import org.matrix.android.sdk.api.session.identity.ThreePid -import org.matrix.android.sdk.api.session.profile.ProfileService import org.matrix.android.sdk.api.session.user.model.User import org.matrix.android.sdk.api.util.toMatrixItem @@ -213,14 +212,10 @@ class UserListViewModel @AssistedInject constructor( ThreePidUser(email = search, user = null) } else { try { - val json = session.getProfile(foundThreePid.matrixId) + val user = tryOrNull { session.getProfileAsUser(foundThreePid.matrixId) } ?: User(foundThreePid.matrixId) ThreePidUser( email = search, - user = User( - userId = foundThreePid.matrixId, - displayName = json[ProfileService.DISPLAY_NAME_KEY] as? String, - avatarUrl = json[ProfileService.AVATAR_URL_KEY] as? String - ) + user = user ) } catch (failure: Throwable) { ThreePidUser(email = search, user = User(foundThreePid.matrixId)) @@ -240,11 +235,11 @@ class UserListViewModel @AssistedInject constructor( .searchUsersDirectory(search, 50, state.excludedUserIds.orEmpty()) .sortedBy { it.toMatrixItem().firstLetterOfDisplayName() } val userProfile = if (MatrixPatterns.isUserId(search)) { - val json = tryOrNull { session.getProfile(search) } + val user = tryOrNull { session.getProfileAsUser(search) } User( userId = search, - displayName = json?.get(ProfileService.DISPLAY_NAME_KEY) as? String, - avatarUrl = json?.get(ProfileService.AVATAR_URL_KEY) as? String + displayName = user?.displayName, + avatarUrl = user?.avatarUrl ) } else { null From bb19987314c5794b0fa13e691facbeac7cc970b2 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 28 Mar 2022 11:09:15 +0200 Subject: [PATCH 131/262] `is Loading` has been replaced by `Uninitialized, is Loading` in `when` statements, which is not strictly equivalent This commit revert those changes. --- .../app/features/createdirect/CreateDirectRoomActivity.kt | 3 +-- .../app/features/discovery/DiscoverySettingsController.kt | 4 ++-- .../app/features/home/room/detail/search/SearchFragment.kt | 3 +-- .../main/java/im/vector/app/features/login/LoginFragment.kt | 5 +---- .../roomprofile/uploads/files/RoomUploadsFilesFragment.kt | 3 +-- .../roomprofile/uploads/media/RoomUploadsMediaFragment.kt | 3 +-- .../features/settings/devtools/AccountDataEpoxyController.kt | 3 +-- .../settings/threepids/ThreePidsSettingsController.kt | 3 +-- 8 files changed, 9 insertions(+), 18 deletions(-) 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 0d36c7c7cc..0053ab0fc6 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,7 +28,6 @@ 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 @@ -167,10 +166,10 @@ class CreateDirectRoomActivity : SimpleFragmentActivity() { private fun renderCreateAndInviteState(state: Async) { when (state) { - Uninitialized, is Loading -> renderCreationLoading() is Success -> renderCreationSuccess(state()) is Fail -> renderCreationFailure(state.error) + else -> Unit } } 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 b338f367e3..e7d74e3d38 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,7 +50,6 @@ class DiscoverySettingsController @Inject constructor( override fun buildModels(data: DiscoverySettingsState) { when (data.identityServer) { - Uninitialized, is Loading -> { loadingItem { id("identityServerLoading") @@ -71,6 +70,7 @@ class DiscoverySettingsController @Inject constructor( buildMsisdnSection(data.phoneNumbersList) } } + else -> Unit } } @@ -356,7 +356,6 @@ class DiscoverySettingsController @Inject constructor( colorProvider(host.colorProvider) stringProvider(host.stringProvider) when (pidInfo.isShared) { - Uninitialized, is Loading -> { buttonIndeterminate(true) } @@ -390,6 +389,7 @@ class DiscoverySettingsController @Inject constructor( } null -> Unit } + else -> Unit } } } 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 fbcf29d863..b3543ae579 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,7 +26,6 @@ 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 @@ -89,7 +88,6 @@ 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 } @@ -101,6 +99,7 @@ class SearchFragment @Inject constructor( title = getString(R.string.search_no_results), image = ContextCompat.getDrawable(requireContext(), R.drawable.ic_search_no_results)) } + else -> Unit } } else { controller.setData(state) 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 22f8792078..df613eeae2 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 @@ -28,8 +28,6 @@ import androidx.core.view.isVisible import androidx.lifecycle.lifecycleScope import com.airbnb.mvrx.Fail import com.airbnb.mvrx.Loading -import com.airbnb.mvrx.Success -import com.airbnb.mvrx.Uninitialized import im.vector.app.R import im.vector.app.core.extensions.hideKeyboard import im.vector.app.core.extensions.hidePassword @@ -269,7 +267,6 @@ class LoginFragment @Inject constructor() : AbstractSSOLoginFragment { // Ensure password is hidden views.passwordField.hidePassword() @@ -292,7 +289,7 @@ class LoginFragment @Inject constructor() : AbstractSSOLoginFragment Unit + else -> Unit } when (state.asyncRegistration) { 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 953838aecd..4b5d44e886 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,7 +24,6 @@ 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 @@ -92,7 +91,6 @@ 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 } @@ -110,6 +108,7 @@ class RoomUploadsFilesFragment @Inject constructor( ) } } + else -> Unit } } else { views.genericStateViewListStateView.state = StateView.State.Content 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 2f33f8403c..59b66603df 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,7 +29,6 @@ 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 @@ -189,7 +188,6 @@ 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 } @@ -207,6 +205,7 @@ class RoomUploadsMediaFragment @Inject constructor( ) } } + else -> Unit } } else { views.genericStateViewListStateView.state = StateView.State.Content 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 4748aeb45e..e840ab2266 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,7 +21,6 @@ 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 @@ -46,7 +45,6 @@ class AccountDataEpoxyController @Inject constructor( if (data == null) return val host = this when (data.accountData) { - Uninitialized, is Loading -> { loadingItem { id("loading") @@ -82,6 +80,7 @@ class AccountDataEpoxyController @Inject constructor( } } } + else -> Unit } } } 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 61d93b6f5f..7a4033fb82 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,7 +21,6 @@ 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 @@ -78,7 +77,6 @@ class ThreePidsSettingsController @Inject constructor( } when (data.threePids) { - Uninitialized, is Loading -> { loadingItem { id("loading") @@ -95,6 +93,7 @@ class ThreePidsSettingsController @Inject constructor( val dataList = data.threePids.invoke() buildThreePids(dataList, data) } + else -> Unit } } From 9b271e4ffde152b872922ec5fa0c08a92779d591 Mon Sep 17 00:00:00 2001 From: Onuray Sahin Date: Mon, 28 Mar 2022 12:11:22 +0300 Subject: [PATCH 132/262] Trigger github actions. --- changelog.d/5595.feature | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/changelog.d/5595.feature b/changelog.d/5595.feature index 02ae703bdd..8fd4d4b144 100644 --- a/changelog.d/5595.feature +++ b/changelog.d/5595.feature @@ -1 +1 @@ -Live Location Sharing - Foreground Service \ No newline at end of file +Live Location Sharing - Foreground Service and Notification \ No newline at end of file From 58cc3931b91cd107ea54b426eace75ebd19ecb31 Mon Sep 17 00:00:00 2001 From: ariskotsomitopoulos Date: Mon, 28 Mar 2022 13:53:44 +0300 Subject: [PATCH 133/262] Add beta feedback for threads --- .../main/res/drawable/bg_shadow_divider.xml | 9 +++ .../threads/list/views/ThreadListFragment.kt | 11 ++++ .../features/rageshake/BugReportActivity.kt | 9 +++ .../app/features/rageshake/BugReporter.kt | 10 +-- .../app/features/rageshake/ReportType.kt | 3 +- .../main/res/layout/fragment_thread_list.xml | 66 +++++++++++++++++-- vector/src/main/res/values/strings.xml | 3 + 7 files changed, 100 insertions(+), 11 deletions(-) create mode 100644 library/ui-styles/src/main/res/drawable/bg_shadow_divider.xml diff --git a/library/ui-styles/src/main/res/drawable/bg_shadow_divider.xml b/library/ui-styles/src/main/res/drawable/bg_shadow_divider.xml new file mode 100644 index 0000000000..9d0ef632ec --- /dev/null +++ b/library/ui-styles/src/main/res/drawable/bg_shadow_divider.xml @@ -0,0 +1,9 @@ + + + + \ No newline at end of file diff --git a/vector/src/main/java/im/vector/app/features/home/room/threads/list/views/ThreadListFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/threads/list/views/ThreadListFragment.kt index d5659efa49..dc16680334 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/threads/list/views/ThreadListFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/threads/list/views/ThreadListFragment.kt @@ -39,6 +39,8 @@ import im.vector.app.features.home.room.threads.arguments.ThreadTimelineArgs import im.vector.app.features.home.room.threads.list.viewmodel.ThreadListController import im.vector.app.features.home.room.threads.list.viewmodel.ThreadListViewModel import im.vector.app.features.home.room.threads.list.viewmodel.ThreadListViewState +import im.vector.app.features.rageshake.BugReporter +import im.vector.app.features.rageshake.ReportType import org.matrix.android.sdk.api.session.room.threads.model.ThreadSummary import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent import org.matrix.android.sdk.api.util.MatrixItem @@ -46,6 +48,7 @@ import javax.inject.Inject class ThreadListFragment @Inject constructor( private val avatarRenderer: AvatarRenderer, + private val bugReporter: BugReporter, private val threadListController: ThreadListController, val threadListViewModelFactory: ThreadListViewModel.Factory ) : VectorBaseFragment(), @@ -80,6 +83,7 @@ class ThreadListFragment @Inject constructor( super.onViewCreated(view, savedInstanceState) initToolbar() initTextConstants() + initBetaFeedback() views.threadListRecyclerView.configureWith(threadListController, TimelineItemAnimator(), hasFixedSize = false) threadListController.listener = this } @@ -101,6 +105,13 @@ class ThreadListFragment @Inject constructor( resources.getString(R.string.reply_in_thread)) } + private fun initBetaFeedback() { + views.threadsFeedBackConstraintLayout.isVisible = resources.getBoolean(R.bool.feature_threads_beta_feedback_enabled) + views.threadFeedbackDivider.isVisible = resources.getBoolean(R.bool.feature_threads_beta_feedback_enabled) + views.threadsFeedBackConstraintLayout.debouncedClicks { + bugReporter.openBugReportScreen(requireActivity(), reportType = ReportType.THREADS_BETA_FEEDBACK) + } + } override fun invalidate() = withState(threadListViewModel) { state -> renderEmptyStateIfNeeded(state) threadListController.update(state) diff --git a/vector/src/main/java/im/vector/app/features/rageshake/BugReportActivity.kt b/vector/src/main/java/im/vector/app/features/rageshake/BugReportActivity.kt index 2d4bc704a4..701d04f3c9 100755 --- a/vector/src/main/java/im/vector/app/features/rageshake/BugReportActivity.kt +++ b/vector/src/main/java/im/vector/app/features/rageshake/BugReportActivity.kt @@ -85,6 +85,15 @@ class BugReportActivity : VectorBaseActivity() { hideBugReportOptions() } + ReportType.THREADS_BETA_FEEDBACK -> { + supportActionBar?.setTitle(R.string.send_feedback_threads_title) + + views.bugReportFirstText.setText(R.string.send_feedback_threads_info) + views.bugReportTextInputLayout.hint = getString(R.string.feedback) + views.bugReportButtonContactMe.isVisible = true + + hideBugReportOptions() + } else -> { // other types not supported here } diff --git a/vector/src/main/java/im/vector/app/features/rageshake/BugReporter.kt b/vector/src/main/java/im/vector/app/features/rageshake/BugReporter.kt index 6434ba60f2..4a62d62e34 100755 --- a/vector/src/main/java/im/vector/app/features/rageshake/BugReporter.kt +++ b/vector/src/main/java/im/vector/app/features/rageshake/BugReporter.kt @@ -255,11 +255,12 @@ class BugReporter @Inject constructor( if (!mIsCancelled) { val text = when (reportType) { - ReportType.BUG_REPORT -> "[Element] $bugDescription" - ReportType.SUGGESTION -> "[Element] [Suggestion] $bugDescription" - ReportType.SPACE_BETA_FEEDBACK -> "[Element] [spaces-feedback] $bugDescription" + ReportType.BUG_REPORT -> "[Element] $bugDescription" + ReportType.SUGGESTION -> "[Element] [Suggestion] $bugDescription" + ReportType.SPACE_BETA_FEEDBACK -> "[Element] [spaces-feedback] $bugDescription" ReportType.AUTO_UISI_SENDER, - ReportType.AUTO_UISI -> bugDescription + ReportType.AUTO_UISI -> bugDescription + ReportType.THREADS_BETA_FEEDBACK -> "[Element] [threads-feedback] $bugDescription" } // build the multi part request @@ -350,6 +351,7 @@ class BugReporter @Inject constructor( builder.addFormDataPart("label", "android") builder.addFormDataPart("label", "uisi-sender") } + ReportType.THREADS_BETA_FEEDBACK -> builder.addFormDataPart("label", "threads-feedback") } if (getCrashFile().exists()) { diff --git a/vector/src/main/java/im/vector/app/features/rageshake/ReportType.kt b/vector/src/main/java/im/vector/app/features/rageshake/ReportType.kt index f9dc628914..f75420ea55 100644 --- a/vector/src/main/java/im/vector/app/features/rageshake/ReportType.kt +++ b/vector/src/main/java/im/vector/app/features/rageshake/ReportType.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021 New Vector Ltd + * 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. @@ -20,6 +20,7 @@ enum class ReportType { BUG_REPORT, SUGGESTION, SPACE_BETA_FEEDBACK, + THREADS_BETA_FEEDBACK, AUTO_UISI, AUTO_UISI_SENDER, } diff --git a/vector/src/main/res/layout/fragment_thread_list.xml b/vector/src/main/res/layout/fragment_thread_list.xml index 7e7c79f8c3..026432035f 100644 --- a/vector/src/main/res/layout/fragment_thread_list.xml +++ b/vector/src/main/res/layout/fragment_thread_list.xml @@ -30,7 +30,7 @@ android:layout_width="0dp" android:layout_height="0dp" android:background="?android:colorBackground" - app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintBottom_toTopOf="@id/threadsFeedBackConstraintLayout" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/threadListAppBarLayout" @@ -75,13 +75,13 @@ android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginBottom="20dp" + android:gravity="center" + android:text="@string/thread_list_empty_title" android:textColor="?vctr_content_primary" app:layout_constraintBottom_toTopOf="@id/threadListEmptySubtitleTextView" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" - android:gravity="center" - app:layout_constraintTop_toBottomOf="@id/threadListEmptyImageView" - android:text="@string/thread_list_empty_title" /> + app:layout_constraintTop_toBottomOf="@id/threadListEmptyImageView" /> + app:layout_constraintTop_toBottomOf="@id/threadListEmptyTitleTextView" /> + + + + + + + + \ No newline at end of file diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml index 551e5961ec..d227326ceb 100644 --- a/vector/src/main/res/values/strings.xml +++ b/vector/src/main/res/values/strings.xml @@ -1648,6 +1648,9 @@ Thanks, your feedback has been successfully sent The feedback failed to be sent (%s) Give Feedback + Give Feedback on threads + Threads Beta feedback + Threads are a work in progress with new, exciting upcoming features, such as improved notifications. We’d love to hear your feedback! Show hidden events in timeline "Show complete history in encrypted rooms" From a2e2cdc2f38b099e23a0537448c74241337a089c Mon Sep 17 00:00:00 2001 From: ariskotsomitopoulos Date: Mon, 28 Mar 2022 13:54:31 +0300 Subject: [PATCH 134/262] Add feature specific configurations --- vector-config/src/main/res/values/config-features.xml | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100755 vector-config/src/main/res/values/config-features.xml diff --git a/vector-config/src/main/res/values/config-features.xml b/vector-config/src/main/res/values/config-features.xml new file mode 100755 index 0000000000..0a6025b37d --- /dev/null +++ b/vector-config/src/main/res/values/config-features.xml @@ -0,0 +1,11 @@ + + + + + + false + + From 9eccb9eaa084b1a59d4922277dcef50df3f96dfe Mon Sep 17 00:00:00 2001 From: ariskotsomitopoulos Date: Mon, 28 Mar 2022 13:59:59 +0300 Subject: [PATCH 135/262] Enable threads beta feedback --- vector-config/src/main/res/values/config-features.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vector-config/src/main/res/values/config-features.xml b/vector-config/src/main/res/values/config-features.xml index 0a6025b37d..1a1a62446f 100755 --- a/vector-config/src/main/res/values/config-features.xml +++ b/vector-config/src/main/res/values/config-features.xml @@ -6,6 +6,6 @@ to the users. --> - false + true From b996e0eac0e3d136c44eab9464ef146b9f3a7cc7 Mon Sep 17 00:00:00 2001 From: ariskotsomitopoulos Date: Mon, 28 Mar 2022 14:09:45 +0300 Subject: [PATCH 136/262] Add changelog --- changelog.d/5647.feature | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/5647.feature diff --git a/changelog.d/5647.feature b/changelog.d/5647.feature new file mode 100644 index 0000000000..e4192300a1 --- /dev/null +++ b/changelog.d/5647.feature @@ -0,0 +1 @@ +Users will be able to provide feedback for threads \ No newline at end of file From 3ba2419e9b8eb7cfc809a6f2144d93cd0525b46e Mon Sep 17 00:00:00 2001 From: ariskotsomitopoulos Date: Mon, 28 Mar 2022 14:15:15 +0300 Subject: [PATCH 137/262] Replace hardcoded string --- vector/src/main/res/layout/fragment_thread_list.xml | 2 +- vector/src/main/res/values/strings.xml | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/vector/src/main/res/layout/fragment_thread_list.xml b/vector/src/main/res/layout/fragment_thread_list.xml index 026432035f..c0041c0d75 100644 --- a/vector/src/main/res/layout/fragment_thread_list.xml +++ b/vector/src/main/res/layout/fragment_thread_list.xml @@ -144,7 +144,7 @@ android:paddingTop="3dp" android:paddingEnd="10dp" android:paddingBottom="3dp" - android:text="BETA" + android:text="@string/beta" android:textColor="@color/palette_white" app:layout_constraintBottom_toBottomOf="@id/threadsBetaFeedbackButton" app:layout_constraintEnd_toStartOf="@id/threadsBetaFeedbackButton" diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml index d227326ceb..3b5f1e6aca 100644 --- a/vector/src/main/res/values/strings.xml +++ b/vector/src/main/res/values/strings.xml @@ -1651,6 +1651,7 @@ Give Feedback on threads Threads Beta feedback Threads are a work in progress with new, exciting upcoming features, such as improved notifications. We’d love to hear your feedback! + BETA Show hidden events in timeline "Show complete history in encrypted rooms" From 32cf3feab8d0303c8662c2fc28e85f670052e36f Mon Sep 17 00:00:00 2001 From: Onuray Sahin Date: Mon, 28 Mar 2022 14:45:40 +0300 Subject: [PATCH 138/262] Create beacon content class. --- .../sdk/api/session/room/model/BeaconInfo.kt | 33 +++++++++++++++++ .../room/model/LiveLocationBeaconContent.kt | 37 +++++++++++++++++++ 2 files changed, 70 insertions(+) create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/BeaconInfo.kt create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/LiveLocationBeaconContent.kt diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/BeaconInfo.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/BeaconInfo.kt new file mode 100644 index 0000000000..2f9eeacd65 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/BeaconInfo.kt @@ -0,0 +1,33 @@ +/* + * 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.api.session.room.model + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + +@JsonClass(generateAdapter = true) +data class BeaconInfo( + @Json(name = "description") val description: String? = null, + /** + * Beacon should be considered as inactive after this timeout as milliseconds. + */ + @Json(name = "timeout") val timeout: Long? = null, + /** + * Should be set true to start sharing beacon. + */ + @Json(name = "live") val isLive: Boolean? = null +) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/LiveLocationBeaconContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/LiveLocationBeaconContent.kt new file mode 100644 index 0000000000..b21296b4aa --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/LiveLocationBeaconContent.kt @@ -0,0 +1,37 @@ +/* + * 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.api.session.room.model + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass +import org.matrix.android.sdk.api.session.room.model.message.LocationAsset + +@JsonClass(generateAdapter = true) +data class LiveLocationBeaconContent( + /** + * Indicates user's intent to share ephemeral location. + */ + @Json(name = "m.beacon_info") val beaconInfo: BeaconInfo? = null, + /** + * Beacon creation timestamp. + */ + @Json(name = "m.ts") val ts: Long? = null, + /** + * Live location asset type. + */ + @Json(name = "m.asset") val locationAsset: LocationAsset = LocationAsset(type = "m.self.live") +) From 152c92101750d7e2fd5b8ec16d8b863d9dd9372c Mon Sep 17 00:00:00 2001 From: Onuray Sahin Date: Mon, 28 Mar 2022 16:23:51 +0300 Subject: [PATCH 139/262] Send beacon info. --- .../sdk/api/session/events/model/EventType.kt | 1 + .../location/LocationSharingService.kt | 33 +++++++++++++++++++ 2 files changed, 34 insertions(+) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/EventType.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/EventType.kt index 22fb9bcbe2..460b9ae1d2 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/EventType.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/EventType.kt @@ -49,6 +49,7 @@ object EventType { const val STATE_ROOM_JOIN_RULES = "m.room.join_rules" const val STATE_ROOM_GUEST_ACCESS = "m.room.guest_access" const val STATE_ROOM_POWER_LEVELS = "m.room.power_levels" + const val STATE_ROOM_BEACON_INFO = "m.beacon_info" const val STATE_SPACE_CHILD = "m.space.child" diff --git a/vector/src/main/java/im/vector/app/features/location/LocationSharingService.kt b/vector/src/main/java/im/vector/app/features/location/LocationSharingService.kt index a2a68e4188..347443dc4e 100644 --- a/vector/src/main/java/im/vector/app/features/location/LocationSharingService.kt +++ b/vector/src/main/java/im/vector/app/features/location/LocationSharingService.kt @@ -22,7 +22,14 @@ import android.os.Parcelable import dagger.hilt.android.AndroidEntryPoint import im.vector.app.core.services.VectorService import im.vector.app.features.notifications.NotificationUtils +import im.vector.app.features.session.coroutineScope +import kotlinx.coroutines.launch import kotlinx.parcelize.Parcelize +import org.matrix.android.sdk.api.session.Session +import org.matrix.android.sdk.api.session.events.model.EventType +import org.matrix.android.sdk.api.session.events.model.toContent +import org.matrix.android.sdk.api.session.room.model.BeaconInfo +import org.matrix.android.sdk.api.session.room.model.LiveLocationBeaconContent import timber.log.Timber import java.util.Timer import java.util.TimerTask @@ -40,6 +47,7 @@ class LocationSharingService : VectorService(), LocationTracker.Callback { @Inject lateinit var notificationUtils: NotificationUtils @Inject lateinit var locationTracker: LocationTracker + @Inject lateinit var session: Session private var roomArgsList = mutableListOf() private var timers = mutableListOf() @@ -67,11 +75,36 @@ class LocationSharingService : VectorService(), LocationTracker.Callback { // Schedule a timer to stop sharing scheduleTimer(roomArgs.roomId, roomArgs.durationMillis) + + // Send beacon info state event + session.coroutineScope.launch { + sendBeaconInfo(roomArgs) + } } return START_STICKY } + private suspend fun sendBeaconInfo(roomArgs: RoomArgs) { + val beaconContent = LiveLocationBeaconContent( + beaconInfo = BeaconInfo( + timeout = roomArgs.durationMillis, + isLive = true + ), + ts = System.currentTimeMillis() + ).toContent() + + // This format is not yet finalized + val stateKey = session.myUserId + session + .getRoom(roomArgs.roomId) + ?.sendStateEvent( + eventType = EventType.STATE_ROOM_BEACON_INFO, + stateKey = stateKey, + body = beaconContent + ) + } + private fun scheduleTimer(roomId: String, durationMillis: Long) { Timer() .apply { From 6952552e5ebc7e46a7c8a8870c562da33ce6a749 Mon Sep 17 00:00:00 2001 From: Onuray Sahin Date: Mon, 28 Mar 2022 16:29:24 +0300 Subject: [PATCH 140/262] Changelog added. --- changelog.d/5651.feature | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/5651.feature diff --git a/changelog.d/5651.feature b/changelog.d/5651.feature new file mode 100644 index 0000000000..c633c01944 --- /dev/null +++ b/changelog.d/5651.feature @@ -0,0 +1 @@ +Send beacon info state event when live location sharing started \ No newline at end of file From 5499854ec0ff883ed3ee8594e32350d76c83c6ea Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 22 Mar 2022 14:05:33 +0100 Subject: [PATCH 141/262] Set up org.owasp.dependencycheck See https://github.com/jeremylong/DependencyCheck --- build.gradle | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 31416a0440..fee8af8822 100644 --- a/build.gradle +++ b/build.gradle @@ -21,7 +21,7 @@ buildscript { classpath 'org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:3.3' classpath 'com.google.android.gms:oss-licenses-plugin:0.10.5' classpath "com.likethesalad.android:stem-plugin:2.0.0" - + classpath 'org.owasp:dependency-check-gradle:7.0.0' // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files } @@ -32,6 +32,9 @@ plugins { id "org.jlleitschuh.gradle.ktlint" version "10.2.1" } +// https://github.com/jeremylong/DependencyCheck +apply plugin: 'org.owasp.dependencycheck' + allprojects { apply plugin: "org.jlleitschuh.gradle.ktlint" From 2b3951fe0483f1ad43e6357e6ca039129fe7f994 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 28 Mar 2022 16:17:27 +0200 Subject: [PATCH 142/262] Force ktlint to version 0.45.1. The ktlint plugin is using 0.42.1 --- build.gradle | 2 ++ dependencies_groups.gradle | 2 ++ 2 files changed, 4 insertions(+) diff --git a/build.gradle b/build.gradle index fee8af8822..b9792511d6 100644 --- a/build.gradle +++ b/build.gradle @@ -90,6 +90,8 @@ allprojects { // See https://github.com/JLLeitschuh/ktlint-gradle#configuration ktlint { + // See https://github.com/pinterest/ktlint/releases/ + version = "0.45.1" android = true ignoreFailures = false enableExperimentalRules = true diff --git a/dependencies_groups.gradle b/dependencies_groups.gradle index 45883f506d..6f155a0149 100644 --- a/dependencies_groups.gradle +++ b/dependencies_groups.gradle @@ -39,6 +39,7 @@ ext.groups = [ regex: [ ], group: [ + 'ch.qos.logback', 'com.adevinta.android', 'com.airbnb.android', 'com.almworks.sqlite4java', @@ -113,6 +114,7 @@ ext.groups = [ 'info.picocli', 'io.arrow-kt', 'io.github.detekt.sarif4k', + 'io.github.microutils', 'io.github.reactivecircus.flowbinding', 'io.grpc', 'io.jsonwebtoken', From 4c406158716173308c4a8b662515ec6bef51b35e Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 28 Mar 2022 16:31:30 +0200 Subject: [PATCH 143/262] Remove Flair Fragment (not used, and part of group, which will be removed) --- .../features/settings/VectorPreferences.kt | 3 - .../settings/VectorSettingsFlairFragment.kt | 170 ------------------ vector/src/main/res/values/strings.xml | 3 +- .../main/res/xml/vector_settings_flair.xml | 8 - 4 files changed, 2 insertions(+), 182 deletions(-) delete mode 100644 vector/src/main/java/im/vector/app/features/settings/VectorSettingsFlairFragment.kt delete mode 100644 vector/src/main/res/xml/vector_settings_flair.xml 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 8d93edc0ec..8fce936428 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 @@ -111,9 +111,6 @@ class VectorPreferences @Inject constructor(private val context: Context) { private const val SETTINGS_PIN_UNREAD_MESSAGES_PREFERENCE_KEY = "SETTINGS_PIN_UNREAD_MESSAGES_PREFERENCE_KEY" private const val SETTINGS_PIN_MISSED_NOTIFICATIONS_PREFERENCE_KEY = "SETTINGS_PIN_MISSED_NOTIFICATIONS_PREFERENCE_KEY" - // flair - const val SETTINGS_GROUPS_FLAIR_KEY = "SETTINGS_GROUPS_FLAIR_KEY" - // notifications const val SETTINGS_ENABLE_ALL_NOTIF_PREFERENCE_KEY = "SETTINGS_ENABLE_ALL_NOTIF_PREFERENCE_KEY" const val SETTINGS_ENABLE_THIS_DEVICE_PREFERENCE_KEY = "SETTINGS_ENABLE_THIS_DEVICE_PREFERENCE_KEY" diff --git a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsFlairFragment.kt b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsFlairFragment.kt deleted file mode 100644 index ec65e7d004..0000000000 --- a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsFlairFragment.kt +++ /dev/null @@ -1,170 +0,0 @@ -/* - * Copyright 2019 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.settings - -import androidx.preference.PreferenceCategory -import im.vector.app.R -import im.vector.app.core.preference.ProgressBarPreference - -class VectorSettingsFlairFragment : VectorSettingsBaseFragment() { - - override var titleRes = R.string.settings_flair - override val preferenceXmlRes = R.xml.vector_settings_flair - - // current publicised group list - private var mPublicisedGroups: MutableSet? = null - - // Group Flairs - private val mGroupsFlairCategory by lazy { - findPreference(VectorPreferences.SETTINGS_GROUPS_FLAIR_KEY)!! - } - - override fun bindPref() { - // Flair - refreshGroupFlairsList() - } - - // ============================================================================================================== - // Group flairs management - // ============================================================================================================== - - /** - * Force the refresh of the devices list.

    - * The devices list is the list of the devices where the user as looged in. - * It can be any mobile device, as any browser. - */ - private fun refreshGroupFlairsList() { - // display a spinner while refreshing - if (0 == mGroupsFlairCategory.preferenceCount) { - activity?.let { - val preference = ProgressBarPreference(it) - mGroupsFlairCategory.addPreference(preference) - } - } - - /* - TODO - session.groupsManager.getUserPublicisedGroups(session.myUserId, true, object : MatrixCallback> { - override fun onSuccess(publicisedGroups: Set) { - // clear everything - mGroupsFlairCategory.removeAll() - - if (publicisedGroups.isEmpty()) { - val vectorGroupPreference = Preference(activity) - vectorGroupPreference.title = resources.getString(R.string.settings_without_flair) - mGroupsFlairCategory.addPreference(vectorGroupPreference) - } else { - buildGroupsList(publicisedGroups) - } - } - - override fun onNetworkError(e: Exception) { - // NOP - } - - override fun onMatrixError(e: MatrixError) { - // NOP - } - - override fun onUnexpectedError(e: Exception) { - // NOP - } - }) - */ - } - - /** - * Build the groups list. - * - * @param publicisedGroups the publicised groups list. - */ - private fun buildGroupsList(publicisedGroups: Set) { - var isNewList = true - - mPublicisedGroups?.let { - if (it.size == publicisedGroups.size) { - isNewList = !it.containsAll(publicisedGroups) - } - } - - if (isNewList) { - /* - TODO - val joinedGroups = ArrayList(session.groupsManager.joinedGroups) - Collections.sort(joinedGroups, Group.mGroupsComparator) - - mPublicisedGroups = publicisedGroups.toMutableSet() - - for ((prefIndex, group) in joinedGroups.withIndex()) { - val vectorGroupPreference = VectorGroupPreference(activity!!) - vectorGroupPreference.key = DEVICES_PREFERENCE_KEY_BASE + prefIndex - - vectorGroupPreference.setGroup(group, session) - vectorGroupPreference.title = group.displayName - vectorGroupPreference.summary = group.groupId - - vectorGroupPreference.isChecked = publicisedGroups.contains(group.groupId) - mGroupsFlairCategory.addPreference(vectorGroupPreference) - - vectorGroupPreference.onPreferenceChangeListener = Preference.OnPreferenceChangeListener { _, newValue -> - if (newValue is Boolean) { - /* - * if mPublicisedGroup is null somehow, then - * we cant check it contains groupId or not - * so set isFlaired to false - */ - val isFlaired = mPublicisedGroups?.contains(group.groupId) ?: false - - if (newValue != isFlaired) { - displayLoadingView() - session.groupsManager.updateGroupPublicity(group.groupId, newValue, object : MatrixCallback { - override fun onSuccess(info: Void?) { - hideLoadingView() - if (newValue) { - mPublicisedGroups?.add(group.groupId) - } else { - mPublicisedGroups?.remove(group.groupId) - } - } - - private fun onError() { - hideLoadingView() - // restore default value - vectorGroupPreference.isChecked = publicisedGroups.contains(group.groupId) - } - - override fun onNetworkError(e: Exception) { - onError() - } - - override fun onMatrixError(e: MatrixError) { - onError() - } - - override fun onUnexpectedError(e: Exception) { - onError() - } - }) - } - } - true - } - } - */ - } - } -} diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml index a276e07b1e..e444a3b0bb 100644 --- a/vector/src/main/res/values/strings.xml +++ b/vector/src/main/res/values/strings.xml @@ -1041,7 +1041,8 @@ Choose Play shutter sound - Flair + + Flair 3 days diff --git a/vector/src/main/res/xml/vector_settings_flair.xml b/vector/src/main/res/xml/vector_settings_flair.xml deleted file mode 100644 index ac7ae83d24..0000000000 --- a/vector/src/main/res/xml/vector_settings_flair.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - \ No newline at end of file From ebee66cfafd6e9a0b31b5e992d22011f30271309 Mon Sep 17 00:00:00 2001 From: ganfra Date: Mon, 28 Mar 2022 16:37:24 +0200 Subject: [PATCH 144/262] Update versions to 1.4.10 --- matrix-sdk-android/build.gradle | 2 +- vector/build.gradle | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/matrix-sdk-android/build.gradle b/matrix-sdk-android/build.gradle index 1e2eda166f..25cfaeb45e 100644 --- a/matrix-sdk-android/build.gradle +++ b/matrix-sdk-android/build.gradle @@ -31,7 +31,7 @@ android { // that the app's state is completely cleared between tests. testInstrumentationRunnerArguments clearPackageData: 'true' - buildConfigField "String", "SDK_VERSION", "\"1.4.8\"" + buildConfigField "String", "SDK_VERSION", "\"1.4.10\"" buildConfigField "String", "GIT_SDK_REVISION", "\"${gitRevision()}\"" buildConfigField "String", "GIT_SDK_REVISION_UNIX_DATE", "\"${gitRevisionUnixDate()}\"" diff --git a/vector/build.gradle b/vector/build.gradle index 9f8471bc18..f5984ff0c6 100644 --- a/vector/build.gradle +++ b/vector/build.gradle @@ -18,7 +18,7 @@ ext.versionMinor = 4 // Note: even values are reserved for regular release, odd values for hotfix release. // When creating a hotfix, you should decrease the value, since the current value // is the value for the next regular release. -ext.versionPatch = 8 +ext.versionPatch = 10 static def getGitTimestamp() { def cmd = 'git show -s --format=%ct' From ff1fb63bf69d0dfb27a45bad54ce49774bdf745d Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 28 Mar 2022 16:35:03 +0200 Subject: [PATCH 145/262] Fix some ktlint issues, ignore some others --- build.gradle | 11 +++++++++- .../crypto/gossiping/WithHeldTests.kt | 2 +- .../sdk/internal/crypto/CryptoModule.kt | 3 +-- .../crypto/model/MXUsersDevicesMap.kt | 2 +- .../sdk/internal/crypto/tools/HkdfSha256.kt | 2 +- .../DefaultVerificationService.kt | 16 +++++++-------- .../session/cache/DefaultCacheService.kt | 6 +++--- .../user/accountdata/DirectChatsHelper.kt | 5 +++-- .../widgets/DefaultWidgetPostAPIMediator.kt | 8 ++++---- .../app/features/debug/TestLinkifyActivity.kt | 4 ++-- .../vector/app/features/badge/BadgeProxy.kt | 4 ++-- .../createdirect/CreateDirectRoomViewModel.kt | 9 +++++---- .../restore/KeysBackupRestoreActivity.kt | 2 +- .../home/room/detail/TimelineFragment.kt | 2 +- .../action/MessageActionsViewModel.kt | 20 +++++++++---------- .../reactions/ViewReactionsViewModel.kt | 8 ++++---- .../invite/InviteUsersToRoomViewModel.kt | 11 +++++----- .../app/features/login/LoginViewModel.kt | 2 +- .../app/features/login2/LoginViewModel2.kt | 2 +- .../NotificationBroadcastReceiver.kt | 2 +- .../RoomPreviewNoPreviewFragment.kt | 16 +++++---------- .../settings/VectorSettingsGeneralFragment.kt | 2 +- .../VectorSettingsPreferencesFragment.kt | 2 +- 23 files changed, 72 insertions(+), 69 deletions(-) diff --git a/build.gradle b/build.gradle index b9792511d6..8ac546ac54 100644 --- a/build.gradle +++ b/build.gradle @@ -101,7 +101,16 @@ allprojects { "spacing-between-declarations-with-comments", "no-multi-spaces", "experimental:spacing-between-declarations-with-annotations", - "experimental:annotation" + "experimental:annotation", + // - Missing newline after "(" + // - Missing newline before ")" + "wrapping", + // - Unnecessary trailing comma before ")" + "experimental:trailing-comma", + // - A block comment in between other elements on the same line is disallowed + "experimental:comment-wrapping", + // - A KDoc comment after any other element on the same line must be separated by a new line + "experimental:kdoc-wrapping", ] } } diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/gossiping/WithHeldTests.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/gossiping/WithHeldTests.kt index 65c65660b5..e8f6eea460 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/gossiping/WithHeldTests.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/gossiping/WithHeldTests.kt @@ -138,7 +138,7 @@ class WithHeldTests : InstrumentedTest { @Test @Ignore("This test will be ignored until it is fixed") - fun test_WithHeldNoOlm() { + fun test_WithHeldNoOlm() { val testData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom() val aliceSession = testData.firstSession val bobSession = testData.secondSession!! diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/CryptoModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/CryptoModule.kt index 3130a6382f..2265526484 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/CryptoModule.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/CryptoModule.kt @@ -137,8 +137,7 @@ internal abstract class CryptoModule { @JvmStatic @Provides @CryptoDatabase - fun providesClearCacheTask(@CryptoDatabase - realmConfiguration: RealmConfiguration): ClearCacheTask { + fun providesClearCacheTask(@CryptoDatabase realmConfiguration: RealmConfiguration): ClearCacheTask { return RealmClearCacheTask(realmConfiguration) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/MXUsersDevicesMap.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/MXUsersDevicesMap.kt index 662541428e..bdb00dce8e 100755 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/MXUsersDevicesMap.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/MXUsersDevicesMap.kt @@ -130,7 +130,7 @@ inline fun MXUsersDevicesMap.forEach(action: (String, String, T) -> Unit) } } -internal fun MXUsersDevicesMap.toDebugString() = +internal fun MXUsersDevicesMap.toDebugString() = map.entries.joinToString { "${it.key} [${it.value.keys.joinToString { it }}]" } internal fun MXUsersDevicesMap.toDebugCount() = diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tools/HkdfSha256.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tools/HkdfSha256.kt index 6839ccd326..04ce0d8500 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tools/HkdfSha256.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tools/HkdfSha256.kt @@ -70,7 +70,7 @@ object HkdfSha256 { T(2) = HMAC-Hash(PRK, T(1) | info | 0x02) T(3) = HMAC-Hash(PRK, T(2) | info | 0x03) ... - */ + */ val n = ceil(outputLength.toDouble() / HASH_LEN.toDouble()).toInt() var stepHash = ByteArray(0) // T(0) empty string (zero length) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/DefaultVerificationService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/DefaultVerificationService.kt index 388ecb9659..bd623575fa 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/DefaultVerificationService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/DefaultVerificationService.kt @@ -364,14 +364,14 @@ internal class DefaultVerificationService @Inject constructor( dispatchRequestAdded(pendingVerificationRequest) /* - * After the m.key.verification.ready event is sent, either party can send an m.key.verification.start event - * to begin the verification. - * If both parties send an m.key.verification.start event, and they both specify the same verification method, - * then the event sent by the user whose user ID is the smallest is used, and the other m.key.verification.start - * event is ignored. - * In the case of a single user verifying two of their devices, the device ID is compared instead. - * If both parties send an m.key.verification.start event, but they specify different verification methods, - * the verification should be cancelled with a code of m.unexpected_message. + * After the m.key.verification.ready event is sent, either party can send an m.key.verification.start event + * to begin the verification. + * If both parties send an m.key.verification.start event, and they both specify the same verification method, + * then the event sent by the user whose user ID is the smallest is used, and the other m.key.verification.start + * event is ignored. + * In the case of a single user verifying two of their devices, the device ID is compared instead. + * If both parties send an m.key.verification.start event, but they specify different verification methods, + * the verification should be cancelled with a code of m.unexpected_message. */ } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/cache/DefaultCacheService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/cache/DefaultCacheService.kt index 6d0cd37e1f..93b0dba13e 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/cache/DefaultCacheService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/cache/DefaultCacheService.kt @@ -21,9 +21,9 @@ import org.matrix.android.sdk.internal.di.SessionDatabase import org.matrix.android.sdk.internal.task.TaskExecutor import javax.inject.Inject -internal class DefaultCacheService @Inject constructor(@SessionDatabase - private val clearCacheTask: ClearCacheTask, - private val taskExecutor: TaskExecutor +internal class DefaultCacheService @Inject constructor( + @SessionDatabase private val clearCacheTask: ClearCacheTask, + private val taskExecutor: TaskExecutor ) : CacheService { override suspend fun clearCache() { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/DirectChatsHelper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/DirectChatsHelper.kt index c7b125b5d6..c4fbdc75ab 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/DirectChatsHelper.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/DirectChatsHelper.kt @@ -24,8 +24,9 @@ import org.matrix.android.sdk.internal.di.SessionDatabase import org.matrix.android.sdk.internal.session.sync.model.accountdata.DirectMessagesContent import javax.inject.Inject -internal class DirectChatsHelper @Inject constructor(@SessionDatabase - private val realmConfiguration: RealmConfiguration) { +internal class DirectChatsHelper @Inject constructor( + @SessionDatabase private val realmConfiguration: RealmConfiguration +) { /** * @return a map of userId <-> list of roomId diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/DefaultWidgetPostAPIMediator.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/DefaultWidgetPostAPIMediator.kt index 07f7c7cb86..1fa5e5f771 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/DefaultWidgetPostAPIMediator.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/DefaultWidgetPostAPIMediator.kt @@ -88,10 +88,10 @@ internal class DefaultWidgetPostAPIMediator @Inject constructor(private val mosh } /* - * ********************************************************************************************* - * Message sending methods - * ********************************************************************************************* - */ + * ********************************************************************************************* + * Message sending methods + * ********************************************************************************************* + */ /** * Send a boolean response diff --git a/vector/src/debug/java/im/vector/app/features/debug/TestLinkifyActivity.kt b/vector/src/debug/java/im/vector/app/features/debug/TestLinkifyActivity.kt index 88e55d6760..59c60e0e15 100644 --- a/vector/src/debug/java/im/vector/app/features/debug/TestLinkifyActivity.kt +++ b/vector/src/debug/java/im/vector/app/features/debug/TestLinkifyActivity.kt @@ -93,7 +93,7 @@ class TestLinkifyActivity : AppCompatActivity() { .show() } }) - */ + */ } subViews.testLinkifyCustomText.apply { @@ -108,7 +108,7 @@ class TestLinkifyActivity : AppCompatActivity() { .show() } }) - */ + */ // TODO Call VectorLinkify.addLinks(text) } diff --git a/vector/src/main/java/im/vector/app/features/badge/BadgeProxy.kt b/vector/src/main/java/im/vector/app/features/badge/BadgeProxy.kt index 3df33b0c9b..fb597d1ef9 100644 --- a/vector/src/main/java/im/vector/app/features/badge/BadgeProxy.kt +++ b/vector/src/main/java/im/vector/app/features/badge/BadgeProxy.kt @@ -90,7 +90,7 @@ object BadgeProxy { } } } - */ + */ } /** @@ -124,6 +124,6 @@ object BadgeProxy { Timber.v("## updateBadgeCount(): badge update count=$unreadRoomsCount") updateBadgeCount(aContext, unreadRoomsCount) } - */ + */ } } 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 d3011496d2..9ce8e68dab 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 @@ -38,10 +38,11 @@ import org.matrix.android.sdk.api.session.permalinks.PermalinkParser import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams import org.matrix.android.sdk.api.session.user.model.User -class CreateDirectRoomViewModel @AssistedInject constructor(@Assisted - initialState: CreateDirectRoomViewState, - private val rawService: RawService, - val session: Session) : +class CreateDirectRoomViewModel @AssistedInject constructor( + @Assisted initialState: CreateDirectRoomViewState, + private val rawService: RawService, + val session: Session +) : VectorViewModel(initialState) { @AssistedFactory diff --git a/vector/src/main/java/im/vector/app/features/crypto/keysbackup/restore/KeysBackupRestoreActivity.kt b/vector/src/main/java/im/vector/app/features/crypto/keysbackup/restore/KeysBackupRestoreActivity.kt index 6e5d7f5fab..a4f6587be4 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/keysbackup/restore/KeysBackupRestoreActivity.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/keysbackup/restore/KeysBackupRestoreActivity.kt @@ -52,7 +52,7 @@ class KeysBackupRestoreActivity : SimpleFragmentActivity() { super.onBackPressed() } - @Inject lateinit var activeSessionHolder: ActiveSessionHolder + @Inject lateinit var activeSessionHolder: ActiveSessionHolder override fun initUiAndData() { super.initUiAndData() 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 9c754e042c..81b037e016 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 @@ -1669,7 +1669,7 @@ class TimelineFragment @Inject constructor( is MessageComposerViewEvents.SlashCommandNotSupportedInThreads -> { displayCommandError(getString(R.string.command_not_supported_in_threads, sendMessageResult.command.command)) } - } // + } lockSendButton = false } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionsViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionsViewModel.kt index bd4e93b25d..aaaecb0a13 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionsViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionsViewModel.kt @@ -69,16 +69,16 @@ import org.matrix.android.sdk.flow.unwrap /** * Information related to an event and used to display preview in contextual bottom sheet. */ -class MessageActionsViewModel @AssistedInject constructor(@Assisted - private val initialState: MessageActionState, - private val eventHtmlRenderer: Lazy, - private val htmlCompressor: VectorHtmlCompressor, - private val session: Session, - private val noticeEventFormatter: NoticeEventFormatter, - private val errorFormatter: ErrorFormatter, - private val stringProvider: StringProvider, - private val pillsPostProcessorFactory: PillsPostProcessor.Factory, - private val vectorPreferences: VectorPreferences +class MessageActionsViewModel @AssistedInject constructor( + @Assisted private val initialState: MessageActionState, + private val eventHtmlRenderer: Lazy, + private val htmlCompressor: VectorHtmlCompressor, + private val session: Session, + private val noticeEventFormatter: NoticeEventFormatter, + private val errorFormatter: ErrorFormatter, + private val stringProvider: StringProvider, + private val pillsPostProcessorFactory: PillsPostProcessor.Factory, + private val vectorPreferences: VectorPreferences ) : VectorViewModel(initialState) { private val informationData = initialState.informationData diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/reactions/ViewReactionsViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/reactions/ViewReactionsViewModel.kt index 25d6f907b5..29b8c207df 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/reactions/ViewReactionsViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/reactions/ViewReactionsViewModel.kt @@ -56,10 +56,10 @@ data class ReactionInfo( /** * Used to display the list of members that reacted to a given event */ -class ViewReactionsViewModel @AssistedInject constructor(@Assisted - initialState: DisplayReactionsViewState, - session: Session, - private val dateFormatter: VectorDateFormatter +class ViewReactionsViewModel @AssistedInject constructor( + @Assisted initialState: DisplayReactionsViewState, + session: Session, + private val dateFormatter: VectorDateFormatter ) : VectorViewModel(initialState) { private val roomId = initialState.roomId diff --git a/vector/src/main/java/im/vector/app/features/invite/InviteUsersToRoomViewModel.kt b/vector/src/main/java/im/vector/app/features/invite/InviteUsersToRoomViewModel.kt index 891194040e..42f08c334e 100644 --- a/vector/src/main/java/im/vector/app/features/invite/InviteUsersToRoomViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/invite/InviteUsersToRoomViewModel.kt @@ -28,16 +28,15 @@ import im.vector.app.core.resources.StringProvider import im.vector.app.features.userdirectory.PendingSelection import kotlinx.coroutines.flow.asFlow import kotlinx.coroutines.flow.catch -import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch import org.matrix.android.sdk.api.session.Session -class InviteUsersToRoomViewModel @AssistedInject constructor(@Assisted - initialState: InviteUsersToRoomViewState, - session: Session, - val stringProvider: StringProvider) : - VectorViewModel(initialState) { +class InviteUsersToRoomViewModel @AssistedInject constructor( + @Assisted initialState: InviteUsersToRoomViewState, + session: Session, + val stringProvider: StringProvider +) : VectorViewModel(initialState) { private val room = session.getRoom(initialState.roomId)!! 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 246c3ad464..73f5c064e7 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 @@ -275,7 +275,7 @@ class LoginViewModel @AssistedInject constructor( code = MatrixError.FORBIDDEN, message = "Registration is disabled" ), 403)) - */ + */ } catch (failure: Throwable) { if (failure !is CancellationException) { _viewEvents.post(LoginViewEvents.Failure(failure)) 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 8125c6e089..62f0007104 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 @@ -771,7 +771,7 @@ class LoginViewModel2 @AssistedInject constructor( ), httpCode = 403 ) - */ + */ LoginViewEvents2.OpenSignUpChooseUsernameScreen } catch (throwable: Throwable) { diff --git a/vector/src/main/java/im/vector/app/features/notifications/NotificationBroadcastReceiver.kt b/vector/src/main/java/im/vector/app/features/notifications/NotificationBroadcastReceiver.kt index 01c1117ab2..505f4cc4a0 100644 --- a/vector/src/main/java/im/vector/app/features/notifications/NotificationBroadcastReceiver.kt +++ b/vector/src/main/java/im/vector/app/features/notifications/NotificationBroadcastReceiver.kt @@ -202,7 +202,7 @@ class NotificationBroadcastReceiver : BroadcastReceiver() { VectorApp.getInstance().notificationDrawerManager.refreshNotificationDrawer(null) } }) - */ + */ } private fun getReplyMessage(intent: Intent?): String? { diff --git a/vector/src/main/java/im/vector/app/features/roomdirectory/roompreview/RoomPreviewNoPreviewFragment.kt b/vector/src/main/java/im/vector/app/features/roomdirectory/roompreview/RoomPreviewNoPreviewFragment.kt index 6d0195fae3..90f1a4785d 100644 --- a/vector/src/main/java/im/vector/app/features/roomdirectory/roompreview/RoomPreviewNoPreviewFragment.kt +++ b/vector/src/main/java/im/vector/app/features/roomdirectory/roompreview/RoomPreviewNoPreviewFragment.kt @@ -110,8 +110,7 @@ class RoomPreviewNoPreviewFragment @Inject constructor( PeekingState.FOUND -> { // show join buttons views.roomPreviewNoPreviewJoin.isVisible = true - renderState(bestName, state.matrixItem(), state.roomTopic - /**, state.roomType*/) + renderState(bestName, state.matrixItem(), state.roomTopic) if (state.fromEmailInvite != null && !state.isEmailBoundToAccount) { views.roomPreviewNoPreviewLabel.text = span { @@ -152,15 +151,13 @@ class RoomPreviewNoPreviewFragment @Inject constructor( views.roomPreviewNoPreviewJoin.isVisible = true views.roomPreviewNoPreviewLabel.isVisible = true views.roomPreviewNoPreviewLabel.setText(R.string.room_preview_no_preview_join) - renderState(bestName, state.matrixItem().takeIf { state.roomAlias != null }, state.roomTopic - /**, state.roomType*/) + renderState(bestName, state.matrixItem().takeIf { state.roomAlias != null }, state.roomTopic) } else -> { views.roomPreviewNoPreviewJoin.isVisible = false views.roomPreviewNoPreviewLabel.isVisible = true views.roomPreviewNoPreviewLabel.setText(R.string.room_preview_not_found) - renderState(bestName, null, state.roomTopic - /**, state.roomType*/) + renderState(bestName, null, state.roomTopic) } } } @@ -168,16 +165,13 @@ class RoomPreviewNoPreviewFragment @Inject constructor( // Render with initial state, no peeking views.roomPreviewPeekingProgress.isVisible = false views.roomPreviewNoPreviewJoin.isVisible = true - renderState(bestName, state.matrixItem(), state.roomTopic - /**, state.roomType*/) + renderState(bestName, state.matrixItem(), state.roomTopic) views.roomPreviewNoPreviewLabel.isVisible = false } } } - private fun renderState(roomName: String, matrixItem: MatrixItem?, topic: String? - /**, roomType: String?*/ - ) { + private fun renderState(roomName: String, matrixItem: MatrixItem?, topic: String?) { // Toolbar if (matrixItem != null) { views.roomPreviewNoPreviewToolbarAvatar.isVisible = true diff --git a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsGeneralFragment.kt b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsGeneralFragment.kt index ffb9fc4af4..db6b4002a0 100644 --- a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsGeneralFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsGeneralFragment.kt @@ -359,7 +359,7 @@ class VectorSettingsGeneralFragment @Inject constructor( startActivityForResult(intent, REQUEST_PHONEBOOK_COUNTRY) true } - */ + */ } // ============================================================================================================== diff --git a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsPreferencesFragment.kt b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsPreferencesFragment.kt index 50e32ae453..fa020c8d26 100644 --- a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsPreferencesFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsPreferencesFragment.kt @@ -117,7 +117,7 @@ class VectorSettingsPreferencesFragment @Inject constructor( false } } - */ + */ // update keep medias period findPreference(VectorPreferences.SETTINGS_MEDIA_SAVING_PERIOD_KEY)!!.let { From a7cd03d578087159758787a55f2f63df24801c6f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 28 Mar 2022 23:04:32 +0000 Subject: [PATCH 146/262] Bump peter-evans/create-pull-request from 3 to 4 Bumps [peter-evans/create-pull-request](https://github.com/peter-evans/create-pull-request) from 3 to 4. - [Release notes](https://github.com/peter-evans/create-pull-request/releases) - [Commits](https://github.com/peter-evans/create-pull-request/compare/v3...v4) --- updated-dependencies: - dependency-name: peter-evans/create-pull-request dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/sync-from-external-sources.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/sync-from-external-sources.yml b/.github/workflows/sync-from-external-sources.yml index d390c47696..796d915ea6 100644 --- a/.github/workflows/sync-from-external-sources.yml +++ b/.github/workflows/sync-from-external-sources.yml @@ -23,7 +23,7 @@ jobs: - name: Run Emoji script run: ./tools/import_emojis.py - name: Create Pull Request for Emojis - uses: peter-evans/create-pull-request@v3 + uses: peter-evans/create-pull-request@v4 with: commit-message: Sync Emojis title: Sync Emojis @@ -49,7 +49,7 @@ jobs: - name: Run SAS String script run: ./tools/import_sas_strings.py - name: Create Pull Request for SAS Strings - uses: peter-evans/create-pull-request@v3 + uses: peter-evans/create-pull-request@v4 with: commit-message: Sync SAS Strings title: Sync SAS Strings @@ -68,7 +68,7 @@ jobs: - name: Run analytics import script run: ./tools/import_analytic_plan.sh - name: Create Pull Request for analytics plan - uses: peter-evans/create-pull-request@v3 + uses: peter-evans/create-pull-request@v4 with: commit-message: Sync analytics plan title: Sync analytics plan From 01bb49d9632ab1ad21c335288f979140f81dc123 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 28 Mar 2022 23:04:35 +0000 Subject: [PATCH 147/262] Bump peter-evans/find-comment from 1 to 2 Bumps [peter-evans/find-comment](https://github.com/peter-evans/find-comment) from 1 to 2. - [Release notes](https://github.com/peter-evans/find-comment/releases) - [Commits](https://github.com/peter-evans/find-comment/compare/v1...v2) --- updated-dependencies: - dependency-name: peter-evans/find-comment dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/nightly.yml | 2 +- .github/workflows/quality.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index 7f789b4610..f035244810 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -174,7 +174,7 @@ jobs: # package: class PermalinkParserTest - name: Find Comment if: always() && github.event_name == 'pull_request' - uses: peter-evans/find-comment@v1 + uses: peter-evans/find-comment@v2 id: fc with: issue-number: ${{ github.event.pull_request.number }} diff --git a/.github/workflows/quality.yml b/.github/workflows/quality.yml index d427d65b7f..b08d92ae56 100644 --- a/.github/workflows/quality.yml +++ b/.github/workflows/quality.yml @@ -59,7 +59,7 @@ jobs: fi - name: Find Comment if: always() && github.event_name == 'pull_request' - uses: peter-evans/find-comment@v1 + uses: peter-evans/find-comment@v2 id: fc with: issue-number: ${{ github.event.pull_request.number }} From 6269a3357b7e36702650dbe470d38ae36eaa36cc Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 28 Mar 2022 23:04:38 +0000 Subject: [PATCH 148/262] Bump peter-evans/create-or-update-comment from 1 to 2 Bumps [peter-evans/create-or-update-comment](https://github.com/peter-evans/create-or-update-comment) from 1 to 2. - [Release notes](https://github.com/peter-evans/create-or-update-comment/releases) - [Commits](https://github.com/peter-evans/create-or-update-comment/compare/v1...v2) --- updated-dependencies: - dependency-name: peter-evans/create-or-update-comment dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/nightly.yml | 2 +- .github/workflows/quality.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index 7f789b4610..9ce77edd0e 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -182,7 +182,7 @@ jobs: body-includes: Integration Tests Results - name: Publish results to PR if: always() && github.event_name == 'pull_request' - uses: peter-evans/create-or-update-comment@v1 + uses: peter-evans/create-or-update-comment@v2 with: comment-id: ${{ steps.fc.outputs.comment-id }} issue-number: ${{ github.event.pull_request.number }} diff --git a/.github/workflows/quality.yml b/.github/workflows/quality.yml index d427d65b7f..00db85b01d 100644 --- a/.github/workflows/quality.yml +++ b/.github/workflows/quality.yml @@ -67,7 +67,7 @@ jobs: body-includes: Ktlint Results - name: Add comment if needed if: always() && github.event_name == 'pull_request' && steps.ktlint-results.outputs.add_comment == 'true' - uses: peter-evans/create-or-update-comment@v1 + uses: peter-evans/create-or-update-comment@v2 with: comment-id: ${{ steps.fc.outputs.comment-id }} issue-number: ${{ github.event.pull_request.number }} From 2a4182ea8421e6179b75acfb20133ef096b416e5 Mon Sep 17 00:00:00 2001 From: Onuray Sahin Date: Tue, 29 Mar 2022 11:53:44 +0300 Subject: [PATCH 149/262] Code review fixes. --- .../room/model/LiveLocationBeaconContent.kt | 2 +- .../location/LocationSharingService.kt | 19 +++++++++++++------ 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/LiveLocationBeaconContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/LiveLocationBeaconContent.kt index b21296b4aa..1c80984d2d 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/LiveLocationBeaconContent.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/LiveLocationBeaconContent.kt @@ -29,7 +29,7 @@ data class LiveLocationBeaconContent( /** * Beacon creation timestamp. */ - @Json(name = "m.ts") val ts: Long? = null, + @Json(name = "m.ts") val timestampAsMillisecond: Long? = null, /** * Live location asset type. */ diff --git a/vector/src/main/java/im/vector/app/features/location/LocationSharingService.kt b/vector/src/main/java/im/vector/app/features/location/LocationSharingService.kt index 347443dc4e..0a330b497b 100644 --- a/vector/src/main/java/im/vector/app/features/location/LocationSharingService.kt +++ b/vector/src/main/java/im/vector/app/features/location/LocationSharingService.kt @@ -20,7 +20,9 @@ import android.content.Intent import android.os.IBinder import android.os.Parcelable import dagger.hilt.android.AndroidEntryPoint +import im.vector.app.core.di.ActiveSessionHolder import im.vector.app.core.services.VectorService +import im.vector.app.core.time.Clock import im.vector.app.features.notifications.NotificationUtils import im.vector.app.features.session.coroutineScope import kotlinx.coroutines.launch @@ -47,7 +49,8 @@ class LocationSharingService : VectorService(), LocationTracker.Callback { @Inject lateinit var notificationUtils: NotificationUtils @Inject lateinit var locationTracker: LocationTracker - @Inject lateinit var session: Session + @Inject lateinit var activeSessionHolder: ActiveSessionHolder + @Inject lateinit var clock: Clock private var roomArgsList = mutableListOf() private var timers = mutableListOf() @@ -77,21 +80,25 @@ class LocationSharingService : VectorService(), LocationTracker.Callback { scheduleTimer(roomArgs.roomId, roomArgs.durationMillis) // Send beacon info state event - session.coroutineScope.launch { - sendBeaconInfo(roomArgs) - } + activeSessionHolder + .getSafeActiveSession() + ?.let { session -> + session.coroutineScope.launch { + sendBeaconInfo(session, roomArgs) + } + } } return START_STICKY } - private suspend fun sendBeaconInfo(roomArgs: RoomArgs) { + private suspend fun sendBeaconInfo(session: Session, roomArgs: RoomArgs) { val beaconContent = LiveLocationBeaconContent( beaconInfo = BeaconInfo( timeout = roomArgs.durationMillis, isLive = true ), - ts = System.currentTimeMillis() + timestampAsMillisecond = clock.epochMillis() ).toContent() // This format is not yet finalized From 2938fa92c0a6359c94e738d1842d6bbcf7271056 Mon Sep 17 00:00:00 2001 From: ariskotsomitopoulos Date: Tue, 29 Mar 2022 11:58:11 +0300 Subject: [PATCH 150/262] Rename countThreads method --- .../sdk/internal/database/helper/ThreadEventsHelper.kt | 4 ++-- .../internal/session/room/prune/RedactionEventProcessor.kt | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/ThreadEventsHelper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/ThreadEventsHelper.kt index 0e85057f23..811c520b8b 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/ThreadEventsHelper.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/ThreadEventsHelper.kt @@ -100,7 +100,7 @@ internal fun EventEntity.markEventAsRoot( * @return A ThreadSummary containing the counted threads and the latest event message */ internal fun EventEntity.threadSummaryInThread(realm: Realm, rootThreadEventId: String, chunkEntity: ChunkEntity?): Summary { - val numberOfThread = countThreads( + val numberOfThread = countThreadReplies( realm = realm, roomId = roomId, rootThreadEventId = rootThreadEventId @@ -135,7 +135,7 @@ internal fun EventEntity.threadSummaryInThread(realm: Realm, rootThreadEventId: * Counts the number of threads in the main timeline thread summary, * with respect to redactions. */ -internal fun countThreads(realm: Realm, roomId: String, rootThreadEventId: String): Int? = +internal fun countThreadReplies(realm: Realm, roomId: String, rootThreadEventId: String): Int? = TimelineEventEntity .whereRoomId(realm, roomId = roomId) .equalTo(TimelineEventEntityFields.ROOT.ROOT_THREAD_EVENT_ID, rootThreadEventId) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/prune/RedactionEventProcessor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/prune/RedactionEventProcessor.kt index 4fcc47a8d4..6558a138b0 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/prune/RedactionEventProcessor.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/prune/RedactionEventProcessor.kt @@ -21,7 +21,7 @@ import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.events.model.LocalEcho import org.matrix.android.sdk.api.session.events.model.UnsignedData -import org.matrix.android.sdk.internal.database.helper.countThreads +import org.matrix.android.sdk.internal.database.helper.countThreadReplies import org.matrix.android.sdk.internal.database.helper.findRootThreadEvent import org.matrix.android.sdk.internal.database.mapper.ContentMapper import org.matrix.android.sdk.internal.database.mapper.EventMapper @@ -123,7 +123,7 @@ internal class RedactionEventProcessor @Inject constructor() : EventInsertLivePr val rootThreadEvent = eventToPrune.findRootThreadEvent() ?: return val rootThreadEventId = eventToPrune.rootThreadEventId ?: return - val numberOfThreads = countThreads( + val numberOfThreads = countThreadReplies( realm = realm, roomId = roomId, rootThreadEventId = rootThreadEventId From f58f3ad6d9c563322940ee3352273a204b2df603 Mon Sep 17 00:00:00 2001 From: ariskotsomitopoulos Date: Tue, 29 Mar 2022 14:28:57 +0300 Subject: [PATCH 151/262] Refactoring --- .../database/helper/ThreadEventsHelper.kt | 24 +++++++++---------- .../internal/database/model/EventEntity.kt | 1 + .../room/prune/RedactionEventProcessor.kt | 10 ++++---- 3 files changed, 18 insertions(+), 17 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/ThreadEventsHelper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/ThreadEventsHelper.kt index 811c520b8b..04cf5b78af 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/ThreadEventsHelper.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/ThreadEventsHelper.kt @@ -53,14 +53,14 @@ internal fun Map.updateThreadSummaryIfNeeded( for ((rootThreadEventId, eventEntity) in this) { eventEntity.threadSummaryInThread(eventEntity.realm, rootThreadEventId, chunkEntity)?.let { threadSummary -> - val numberOfMessages = threadSummary.first + val inThreadMessages = threadSummary.first val latestEventInThread = threadSummary.second // If this is a thread message, find its root event if exists val rootThreadEvent = if (eventEntity.isThread()) eventEntity.findRootThreadEvent() else eventEntity rootThreadEvent?.markEventAsRoot( - threadsCounted = numberOfMessages, + inThreadMessages = inThreadMessages, latestMessageTimelineEventEntity = latestEventInThread ) } @@ -86,10 +86,10 @@ internal fun EventEntity.findRootThreadEvent(): EventEntity? = * Mark or update the current event a root thread event */ internal fun EventEntity.markEventAsRoot( - threadsCounted: Int, + inThreadMessages: Int, latestMessageTimelineEventEntity: TimelineEventEntity?) { isRootThread = true - numberOfThreads = threadsCounted + numberOfThreads = inThreadMessages threadSummaryLatestMessage = latestMessageTimelineEventEntity } @@ -100,13 +100,13 @@ internal fun EventEntity.markEventAsRoot( * @return A ThreadSummary containing the counted threads and the latest event message */ internal fun EventEntity.threadSummaryInThread(realm: Realm, rootThreadEventId: String, chunkEntity: ChunkEntity?): Summary { - val numberOfThread = countThreadReplies( + val inThreadMessages = countInThreadMessages( realm = realm, roomId = roomId, rootThreadEventId = rootThreadEventId - ) ?: return null + ) - if (numberOfThread <= 0) return null + if (inThreadMessages <= 0) return null // Find latest thread event, we know it exists var chunk = ChunkEntity.findLastForwardChunkOfRoom(realm, roomId) ?: chunkEntity ?: return null @@ -128,26 +128,26 @@ internal fun EventEntity.threadSummaryInThread(realm: Realm, rootThreadEventId: result ?: return null - return Summary(numberOfThread, result) + return Summary(inThreadMessages, result) } /** - * Counts the number of threads in the main timeline thread summary, + * Counts the number of thread replies in the main timeline thread summary, * with respect to redactions. */ -internal fun countThreadReplies(realm: Realm, roomId: String, rootThreadEventId: String): Int? = +internal fun countInThreadMessages(realm: Realm, roomId: String, rootThreadEventId: String): Int = TimelineEventEntity .whereRoomId(realm, roomId = roomId) .equalTo(TimelineEventEntityFields.ROOT.ROOT_THREAD_EVENT_ID, rootThreadEventId) .distinct(TimelineEventEntityFields.ROOT.EVENT_ID) .findAll() - ?.filterNot { timelineEvent -> + .filterNot { timelineEvent -> timelineEvent.root ?.unsignedData ?.takeIf { it.isNotBlank() } ?.toUnsignedData() .isRedacted() - }?.size + }.size /** * Mapping string to UnsignedData using Moshi diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/EventEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/EventEntity.kt index b7158ba9cd..09be98aa96 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/EventEntity.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/EventEntity.kt @@ -44,6 +44,7 @@ internal open class EventEntity(@Index var eventId: String = "", // Thread related, no need to create a new Entity for performance @Index var isRootThread: Boolean = false, @Index var rootThreadEventId: String? = null, + // Number messages within the thread var numberOfThreads: Int = 0, var threadSummaryLatestMessage: TimelineEventEntity? = null ) : RealmObject() { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/prune/RedactionEventProcessor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/prune/RedactionEventProcessor.kt index 6558a138b0..b19b8d4a6b 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/prune/RedactionEventProcessor.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/prune/RedactionEventProcessor.kt @@ -21,7 +21,7 @@ import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.events.model.LocalEcho import org.matrix.android.sdk.api.session.events.model.UnsignedData -import org.matrix.android.sdk.internal.database.helper.countThreadReplies +import org.matrix.android.sdk.internal.database.helper.countInThreadMessages import org.matrix.android.sdk.internal.database.helper.findRootThreadEvent import org.matrix.android.sdk.internal.database.mapper.ContentMapper import org.matrix.android.sdk.internal.database.mapper.EventMapper @@ -123,14 +123,14 @@ internal class RedactionEventProcessor @Inject constructor() : EventInsertLivePr val rootThreadEvent = eventToPrune.findRootThreadEvent() ?: return val rootThreadEventId = eventToPrune.rootThreadEventId ?: return - val numberOfThreads = countThreadReplies( + val inThreadMessages = countInThreadMessages( realm = realm, roomId = roomId, rootThreadEventId = rootThreadEventId - ) ?: return + ) - rootThreadEvent.numberOfThreads = numberOfThreads - if (numberOfThreads == 0) { + rootThreadEvent.numberOfThreads = inThreadMessages + if (inThreadMessages == 0) { // We should also clear the thread summary list rootThreadEvent.isRootThread = false rootThreadEvent.threadSummaryLatestMessage = null From 33d197a4298f0f4e2345363abca13b47fb6c896d Mon Sep 17 00:00:00 2001 From: Szimszon Date: Mon, 28 Mar 2022 06:16:58 +0000 Subject: [PATCH 152/262] Translated using Weblate (Hungarian) Currently translated at 100.0% (2171 of 2171 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/hu/ --- vector/src/main/res/values-hu/strings.xml | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/vector/src/main/res/values-hu/strings.xml b/vector/src/main/res/values-hu/strings.xml index f189a4198f..f13a97d216 100644 --- a/vector/src/main/res/values-hu/strings.xml +++ b/vector/src/main/res/values-hu/strings.xml @@ -457,7 +457,7 @@ Vedd figyelembe, hogy az alkalmazás újraindul ami sok időt vehet igénybe."< %d tagság változás %d tagság változás
    - Tagok listázása + Tagok %d tag %d tag @@ -1785,7 +1785,7 @@ A Visszaállítási Kulcsot tartsd biztonságos helyen, mint pl. egy jelszókeze Ön az egyetlen adminisztrátora a térnek. Ha kilép, senki nem tudja irányítani. Amíg nem hívnak meg újra nem tudsz újracsatlakozni. Csak te van itt. Ha kilépsz, akkor a jövőben senki nem tud majd ide belépni, beleértve téged is. - Tér elhagyása + Elhagyás Szobák hozzáadása Szobák felderítése @@ -2419,4 +2419,23 @@ A Visszaállítási Kulcsot tartsd biztonságos helyen, mint pl. egy jelszókeze %1$s, %2$s és mások %1$s és %2$s + Ha folyamatosan meg akarod osztani a földrajzi pozíciódat ahhoz a(z) ${app_name} alkalmazásnak hozzá kell férnie a helyadatokhoz akkor is ha a háttérben fut. +\nCsak az általad megadott idő intervallumban férünk hozzá a helyadatokhoz. + Egyre közelebb kerülünk ahhoz, hogy publikus béta állapotba kerüljenek az üzenetszálak. +\n +\nAz előkészületekkel be kell vezetnünk pár változtatást: az eddig a pontig készített üzenetszálak hagyományos válaszokként fognak megjelenni. +\n +\nEz egy egyszeri változtatás ahogy az üzenetszálak ezentúl a Matrix specifikáció részét képezik. + Állj + Folyamatos pozíció megosztás engedélyezve + Hozzáférés engedélyezése + Ennek a földrajzi helynek a megosztása + Ennek a földrajzi helynek a megosztása + Pozícióm folyamatos megosztása + Pozícióm folyamatos megosztása + Jelenlegi pozícióm megosztása + Jelenlegi pozícióm megosztása + Ugrás a jelenlegi pozícióra + Kiválasztott hely rögzítése a térképen + Üzenetszálak lassan béta állapotba kerülnek 🎉 \ No newline at end of file From ea96718af533484652c85d2a2f765c36c2cc3193 Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Mon, 28 Mar 2022 11:40:21 +0000 Subject: [PATCH 153/262] Translated using Weblate (French) Currently translated at 99.2% (2155 of 2171 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/fr/ --- vector/src/main/res/values-fr/strings.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/vector/src/main/res/values-fr/strings.xml b/vector/src/main/res/values-fr/strings.xml index e1b3b0043c..c9230ec49e 100644 --- a/vector/src/main/res/values-fr/strings.xml +++ b/vector/src/main/res/values-fr/strings.xml @@ -1580,8 +1580,8 @@ Appel échoué Les messages de ce salon sont chiffrés de bout en bout. Vous avez créé et configuré ce salon. - Le code est requis à l’ouverture d’${app_name}. - Le code est demandé après 2 minutes d\'inutilisation d’${app_name}. + Le code est requis à l’ouverture de ${app_name}. + Le code est demandé après 2 minutes d\'inutilisation de ${app_name}. Demander le code après 2 minutes Afficher uniquement le numéro de messages non-lus dans une simple notification. Afficher les détails comme les noms des salons et le contenu du message. From 7999bd75233d22e73bab2d9dd5c40b056c7e0aaf Mon Sep 17 00:00:00 2001 From: Onuray Sahin Date: Tue, 29 Mar 2022 15:34:50 +0300 Subject: [PATCH 154/262] Show a banner in timeline while location sharing service is running. --- .../home/room/detail/RoomDetailViewEvents.kt | 3 + .../home/room/detail/TimelineFragment.kt | 10 +++ .../home/room/detail/TimelineViewModel.kt | 16 ++++- .../location/LocationSharingService.kt | 11 +++- .../LocationSharingServiceConnection.kt | 63 +++++++++++++++++++ 5 files changed, 100 insertions(+), 3 deletions(-) create mode 100644 vector/src/main/java/im/vector/app/features/location/LocationSharingServiceConnection.kt diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewEvents.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewEvents.kt index d08a27324c..a7819d3ddf 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewEvents.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewEvents.kt @@ -82,4 +82,7 @@ sealed class RoomDetailViewEvents : VectorViewEvents { data class StartChatEffect(val type: ChatEffect) : RoomDetailViewEvents() object StopChatEffects : RoomDetailViewEvents() object RoomReplacementStarted : RoomDetailViewEvents() + + object ShowLocationSharingIndicator : RoomDetailViewEvents() + object HideLocationSharingIndicator : RoomDetailViewEvents() } 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 9c754e042c..00ddd8dcbd 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 @@ -482,6 +482,8 @@ class TimelineFragment @Inject constructor( RoomDetailViewEvents.StopChatEffects -> handleStopChatEffects() is RoomDetailViewEvents.DisplayAndAcceptCall -> acceptIncomingCall(it) RoomDetailViewEvents.RoomReplacementStarted -> handleRoomReplacement() + RoomDetailViewEvents.ShowLocationSharingIndicator -> handleShowLocationSharingIndicator() + RoomDetailViewEvents.HideLocationSharingIndicator -> handleHideLocationSharingIndicator() } } @@ -616,6 +618,14 @@ class TimelineFragment @Inject constructor( ) } + private fun handleShowLocationSharingIndicator() { + views.locationLiveStatusIndicator.isVisible = true + } + + private fun handleHideLocationSharingIndicator() { + views.locationLiveStatusIndicator.isVisible = false + } + private fun displayErrorMessage(error: RoomDetailViewEvents.Failure) { if (error.showInDialog) displayErrorDialog(error.throwable) else showErrorInSnackbar(error.throwable) } 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 6933adc758..024c42e503 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 @@ -52,6 +52,7 @@ import im.vector.app.features.home.room.detail.sticker.StickerPickerActionHandle import im.vector.app.features.home.room.detail.timeline.factory.TimelineFactory import im.vector.app.features.home.room.detail.timeline.url.PreviewUrlRetriever import im.vector.app.features.home.room.typing.TypingHelper +import im.vector.app.features.location.LocationSharingServiceConnection import im.vector.app.features.notifications.NotificationDrawerManager import im.vector.app.features.powerlevel.PowerLevelsFlowFactory import im.vector.app.features.session.coroutineScope @@ -124,10 +125,11 @@ class TimelineViewModel @AssistedInject constructor( private val activeConferenceHolder: JitsiActiveConferenceHolder, private val decryptionFailureTracker: DecryptionFailureTracker, private val notificationDrawerManager: NotificationDrawerManager, + private val locationSharingServiceConnection: LocationSharingServiceConnection, timelineFactory: TimelineFactory, appStateHandler: AppStateHandler ) : VectorViewModel(initialState), - Timeline.Listener, ChatEffectManager.Delegate, CallProtocolsChecker.Listener { + Timeline.Listener, ChatEffectManager.Delegate, CallProtocolsChecker.Listener, LocationSharingServiceConnection.Callback { private val room = session.getRoom(initialState.roomId)!! private val eventId = initialState.eventId @@ -219,6 +221,9 @@ class TimelineViewModel @AssistedInject constructor( // Threads initThreads() + + // Observe location service lifecycle to be able to warn the user + locationSharingServiceConnection.bind(this) } /** @@ -1218,6 +1223,14 @@ class TimelineViewModel @AssistedInject constructor( _viewEvents.post(RoomDetailViewEvents.OnNewTimelineEvents(eventIds)) } + override fun onLocationServiceRunning() { + _viewEvents.post(RoomDetailViewEvents.ShowLocationSharingIndicator) + } + + override fun onLocationServiceStopped() { + _viewEvents.post(RoomDetailViewEvents.HideLocationSharingIndicator) + } + override fun onCleared() { timeline.dispose() timeline.removeAllListeners() @@ -1231,6 +1244,7 @@ class TimelineViewModel @AssistedInject constructor( // we should also mark it as read here, for the scenario that the user // is already in the thread timeline markThreadTimelineAsReadLocal() + locationSharingServiceConnection.unbind() super.onCleared() } } diff --git a/vector/src/main/java/im/vector/app/features/location/LocationSharingService.kt b/vector/src/main/java/im/vector/app/features/location/LocationSharingService.kt index a2a68e4188..bcbda9ecf4 100644 --- a/vector/src/main/java/im/vector/app/features/location/LocationSharingService.kt +++ b/vector/src/main/java/im/vector/app/features/location/LocationSharingService.kt @@ -17,6 +17,7 @@ package im.vector.app.features.location import android.content.Intent +import android.os.Binder import android.os.IBinder import android.os.Parcelable import dagger.hilt.android.AndroidEntryPoint @@ -41,6 +42,8 @@ class LocationSharingService : VectorService(), LocationTracker.Callback { @Inject lateinit var notificationUtils: NotificationUtils @Inject lateinit var locationTracker: LocationTracker + private val binder = LocalBinder() + private var roomArgsList = mutableListOf() private var timers = mutableListOf() @@ -120,8 +123,12 @@ class LocationSharingService : VectorService(), LocationTracker.Callback { destroyMe() } - override fun onBind(intent: Intent?): IBinder? { - return null + override fun onBind(intent: Intent?): IBinder { + return binder + } + + inner class LocalBinder : Binder() { + fun getService(): LocationSharingService = this@LocationSharingService } companion object { diff --git a/vector/src/main/java/im/vector/app/features/location/LocationSharingServiceConnection.kt b/vector/src/main/java/im/vector/app/features/location/LocationSharingServiceConnection.kt new file mode 100644 index 0000000000..9af6b1539a --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/location/LocationSharingServiceConnection.kt @@ -0,0 +1,63 @@ +/* + * 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 + +import android.content.ComponentName +import android.content.Context +import android.content.Intent +import android.content.ServiceConnection +import android.os.IBinder +import javax.inject.Inject + +class LocationSharingServiceConnection @Inject constructor( + private val context: Context +) : ServiceConnection { + + interface Callback { + fun onLocationServiceRunning() + fun onLocationServiceStopped() + } + + private var callback: Callback? = null + private var isBound = false + + fun bind(callback: Callback) { + this.callback = callback + + if (isBound) { + callback.onLocationServiceRunning() + } else { + Intent(context, LocationSharingService::class.java).also { intent -> + context.bindService(intent, this, 0) + } + } + } + + fun unbind() { + callback = null + } + + override fun onServiceConnected(className: ComponentName, binder: IBinder) { + isBound = true + callback?.onLocationServiceRunning() + } + + override fun onServiceDisconnected(className: ComponentName) { + isBound = false + callback?.onLocationServiceStopped() + } +} From 0f7d6a19469cd364e4539f72b5c05929c514c84c Mon Sep 17 00:00:00 2001 From: ariskotsomitopoulos Date: Tue, 29 Mar 2022 16:26:47 +0300 Subject: [PATCH 155/262] Add loader to thread list --- .../list/viewmodel/ThreadListViewModel.kt | 8 ++++++ .../list/viewmodel/ThreadListViewState.kt | 1 + .../threads/list/views/ThreadListFragment.kt | 5 ++++ .../main/res/layout/fragment_thread_list.xml | 26 ++++++++++++++----- 4 files changed, 34 insertions(+), 6 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/home/room/threads/list/viewmodel/ThreadListViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/threads/list/viewmodel/ThreadListViewModel.kt index 7f18d172e4..da09683b63 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/threads/list/viewmodel/ThreadListViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/threads/list/viewmodel/ThreadListViewModel.kt @@ -113,7 +113,15 @@ class ThreadListViewModel @AssistedInject constructor(@Assisted val initialState private fun fetchThreadList() { viewModelScope.launch { + isLoading(true) room?.fetchThreadSummaries() + isLoading(false) + } + } + + private fun isLoading(show: Boolean) { + setState { + copy(isLoading = show) } } diff --git a/vector/src/main/java/im/vector/app/features/home/room/threads/list/viewmodel/ThreadListViewState.kt b/vector/src/main/java/im/vector/app/features/home/room/threads/list/viewmodel/ThreadListViewState.kt index e08f70030b..2328da0b8a 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/threads/list/viewmodel/ThreadListViewState.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/threads/list/viewmodel/ThreadListViewState.kt @@ -27,6 +27,7 @@ data class ThreadListViewState( val threadSummaryList: Async> = Uninitialized, val rootThreadEventList: Async> = Uninitialized, val shouldFilterThreads: Boolean = false, + val isLoading: Boolean = false, val roomId: String ) : MavericksState { constructor(args: ThreadListArgs) : this(roomId = args.roomId) diff --git a/vector/src/main/java/im/vector/app/features/home/room/threads/list/views/ThreadListFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/threads/list/views/ThreadListFragment.kt index d5659efa49..ab9ca4d2f2 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/threads/list/views/ThreadListFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/threads/list/views/ThreadListFragment.kt @@ -104,6 +104,11 @@ class ThreadListFragment @Inject constructor( override fun invalidate() = withState(threadListViewModel) { state -> renderEmptyStateIfNeeded(state) threadListController.update(state) + renderLoaderIfNeeded(state) + } + + private fun renderLoaderIfNeeded(state: ThreadListViewState) { + views.threadListProgressBar.isVisible = state.isLoading } private fun renderToolbar() { diff --git a/vector/src/main/res/layout/fragment_thread_list.xml b/vector/src/main/res/layout/fragment_thread_list.xml index 7e7c79f8c3..f0f7dff611 100644 --- a/vector/src/main/res/layout/fragment_thread_list.xml +++ b/vector/src/main/res/layout/fragment_thread_list.xml @@ -37,6 +37,21 @@ tools:listitem="@layout/item_thread" tools:visibility="gone" /> + + + + app:layout_constraintTop_toBottomOf="@id/threadListEmptyImageView" /> + app:layout_constraintTop_toBottomOf="@id/threadListEmptyTitleTextView" /> Date: Tue, 29 Mar 2022 13:26:41 +0200 Subject: [PATCH 156/262] Add link to the changelog --- docs/jitsi.md | 2 ++ tools/jitsi/build_jisti_libs.sh | 2 ++ 2 files changed, 4 insertions(+) diff --git a/docs/jitsi.md b/docs/jitsi.md index 55cedaedb1..1b4e0a37b4 100644 --- a/docs/jitsi.md +++ b/docs/jitsi.md @@ -18,6 +18,8 @@ The generated maven repository is then host in the project https://github.com/ve Update the script `./tools/jitsi/build_jisti_libs.sh` with the tag of the project `https://github.com/jitsi/jitsi-meet`. +Latest tag can be found from this page: https://github.com/jitsi/jitsi-meet-release-notes/blob/master/CHANGELOG-MOBILE-SDKS.md + Currently we are building the version with the tag `android-sdk-3.10.0`. ### Run the build script diff --git a/tools/jitsi/build_jisti_libs.sh b/tools/jitsi/build_jisti_libs.sh index e352575775..25002fd379 100755 --- a/tools/jitsi/build_jisti_libs.sh +++ b/tools/jitsi/build_jisti_libs.sh @@ -25,6 +25,8 @@ cd jitsi-meet # This is commit after version 2.2.2, which does not compile # git checkout 5a934c071a5cbe64de275a25d0ed62d8193cdd03 +# Changelog: https://github.com/jitsi/jitsi-meet-release-notes/blob/master/CHANGELOG-MOBILE-SDKS.md + # Version android-sdk-3.10.0, commit 99e56e229dfa3c490096e37c3e5b76d2a3f23e32 git checkout android-sdk-3.10.0 From 0d2d1339e09da8dc9c9b4e414aa415368f0b0f15 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 29 Mar 2022 16:01:46 +0200 Subject: [PATCH 157/262] Bump Jitsi lib from 3.10.0 to 5.0.1 --- build.gradle | 2 +- dependencies_groups.gradle | 3 +++ tools/jitsi/build_jisti_libs.sh | 3 +-- vector/build.gradle | 4 ++-- vector/src/main/AndroidManifest.xml | 2 +- 5 files changed, 8 insertions(+), 6 deletions(-) diff --git a/build.gradle b/build.gradle index 8ac546ac54..8b527ccf83 100644 --- a/build.gradle +++ b/build.gradle @@ -54,7 +54,7 @@ allprojects { } // Jitsi repo maven { - url "https://github.com/vector-im/jitsi_libre_maven/raw/main/android-sdk-3.10.0" + url "https://github.com/vector-im/jitsi_libre_maven/raw/main/android-sdk-5.0.1" // Note: to test Jitsi release you can use a local file like this: // url "file:///Users/bmarty/workspaces/jitsi_libre_maven/android-sdk-3.10.0" content { diff --git a/dependencies_groups.gradle b/dependencies_groups.gradle index 6f155a0149..9e70a1de42 100644 --- a/dependencies_groups.gradle +++ b/dependencies_groups.gradle @@ -7,6 +7,7 @@ ext.groups = [ 'com.github.chrisbanes', 'com.github.hyuwah', 'com.github.jetradarmobile', + 'com.github.MatrixFrog', 'com.github.tapadoo', 'com.github.vector-im', 'com.github.yalantis', @@ -49,10 +50,12 @@ ext.groups = [ 'com.beust', 'com.davemorrissey.labs', 'com.dropbox.core', + 'com.facebook.fbjni', 'com.facebook.fresco', 'com.facebook.infer.annotation', 'com.facebook.soloader', 'com.facebook.stetho', + 'com.facebook.yoga', 'com.fasterxml', 'com.fasterxml.jackson', 'com.fasterxml.jackson.core', diff --git a/tools/jitsi/build_jisti_libs.sh b/tools/jitsi/build_jisti_libs.sh index 25002fd379..4220dd1551 100755 --- a/tools/jitsi/build_jisti_libs.sh +++ b/tools/jitsi/build_jisti_libs.sh @@ -27,8 +27,7 @@ cd jitsi-meet # Changelog: https://github.com/jitsi/jitsi-meet-release-notes/blob/master/CHANGELOG-MOBILE-SDKS.md -# Version android-sdk-3.10.0, commit 99e56e229dfa3c490096e37c3e5b76d2a3f23e32 -git checkout android-sdk-3.10.0 +git checkout android-sdk-5.0.1 echo echo "##################################################" diff --git a/vector/build.gradle b/vector/build.gradle index 9f8471bc18..1ab9dd8251 100644 --- a/vector/build.gradle +++ b/vector/build.gradle @@ -470,10 +470,10 @@ dependencies { // WebRTC // org.webrtc:google-webrtc is for development purposes only // implementation 'org.webrtc:google-webrtc:1.0.+' - implementation('com.facebook.react:react-native-webrtc:1.92.1-jitsi-9093212@aar') + implementation('com.facebook.react:react-native-webrtc:1.94.2-jitsi-10226524@aar') // Jitsi - implementation('org.jitsi.react:jitsi-meet-sdk:3.10.0') { + implementation('org.jitsi.react:jitsi-meet-sdk:5.0.1') { exclude group: 'com.google.firebase' exclude group: 'com.google.android.gms' exclude group: 'com.android.installreferrer' diff --git a/vector/src/main/AndroidManifest.xml b/vector/src/main/AndroidManifest.xml index 1ffe8a2be8..eada664216 100644 --- a/vector/src/main/AndroidManifest.xml +++ b/vector/src/main/AndroidManifest.xml @@ -49,7 +49,7 @@ - + From 587948c1b97337370523b3d358116ed07d0ae0b2 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 29 Mar 2022 18:24:44 +0200 Subject: [PATCH 158/262] Bump Jitsi lib from 5.0.1 to 5.0.2 https://github.com/jitsi/jitsi-meet-release-notes/blob/master/CHANGELOG-MOBILE-SDKS.md#502-2022-03-29 --- build.gradle | 2 +- tools/jitsi/build_jisti_libs.sh | 5 ++++- vector/build.gradle | 4 ++-- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/build.gradle b/build.gradle index 8b527ccf83..6ad0663c6d 100644 --- a/build.gradle +++ b/build.gradle @@ -54,7 +54,7 @@ allprojects { } // Jitsi repo maven { - url "https://github.com/vector-im/jitsi_libre_maven/raw/main/android-sdk-5.0.1" + url "https://github.com/vector-im/jitsi_libre_maven/raw/main/android-sdk-5.0.2" // Note: to test Jitsi release you can use a local file like this: // url "file:///Users/bmarty/workspaces/jitsi_libre_maven/android-sdk-3.10.0" content { diff --git a/tools/jitsi/build_jisti_libs.sh b/tools/jitsi/build_jisti_libs.sh index 4220dd1551..445dc5e0fe 100755 --- a/tools/jitsi/build_jisti_libs.sh +++ b/tools/jitsi/build_jisti_libs.sh @@ -17,6 +17,9 @@ cd .. rm -rf jitsi-meet git clone https://github.com/jitsi/jitsi-meet +# Android SDK +export ANDROID_SDK_ROOT=~/Library/Android/sdk + # We want a libre build! export LIBRE_BUILD=true @@ -27,7 +30,7 @@ cd jitsi-meet # Changelog: https://github.com/jitsi/jitsi-meet-release-notes/blob/master/CHANGELOG-MOBILE-SDKS.md -git checkout android-sdk-5.0.1 +git checkout android-sdk-5.0.2 echo echo "##################################################" diff --git a/vector/build.gradle b/vector/build.gradle index 1ab9dd8251..8ccec79902 100644 --- a/vector/build.gradle +++ b/vector/build.gradle @@ -470,10 +470,10 @@ dependencies { // WebRTC // org.webrtc:google-webrtc is for development purposes only // implementation 'org.webrtc:google-webrtc:1.0.+' - implementation('com.facebook.react:react-native-webrtc:1.94.2-jitsi-10226524@aar') + implementation('com.facebook.react:react-native-webrtc:1.94.2-jitsi-10227332@aar') // Jitsi - implementation('org.jitsi.react:jitsi-meet-sdk:5.0.1') { + implementation('org.jitsi.react:jitsi-meet-sdk:5.0.2') { exclude group: 'com.google.firebase' exclude group: 'com.google.android.gms' exclude group: 'com.android.installreferrer' From b9b5cab7722a8b648b80b62fffc19cf27b92c375 Mon Sep 17 00:00:00 2001 From: cketti Date: Tue, 29 Mar 2022 16:12:12 +0200 Subject: [PATCH 159/262] Use truncate mode to replace the contents of existing files `ContentResolver.openOutputStream(Uri)` does not truncate existing files. If the amount of data written is smaller than the file size, you end up with new data at the beginning of the file followed by old data at the end of the file. --- changelog.d/5663.bugfix | 1 + .../im/vector/app/features/crypto/keys/KeysExporter.kt | 2 +- .../keysbackup/setup/KeysBackupSetupStep3Fragment.kt | 2 +- .../crypto/recover/BootstrapSaveRecoveryKeyFragment.kt | 2 +- .../app/features/settings/devtools/KeyRequestsFragment.kt | 2 +- .../vector/app/features/crypto/keys/KeysExporterTest.kt | 8 ++++---- .../src/test/java/im/vector/app/test/fakes/FakeContext.kt | 8 ++++---- 7 files changed, 13 insertions(+), 12 deletions(-) create mode 100644 changelog.d/5663.bugfix diff --git a/changelog.d/5663.bugfix b/changelog.d/5663.bugfix new file mode 100644 index 0000000000..5086407e8e --- /dev/null +++ b/changelog.d/5663.bugfix @@ -0,0 +1 @@ +Fixed key export when overwriting existing files diff --git a/vector/src/main/java/im/vector/app/features/crypto/keys/KeysExporter.kt b/vector/src/main/java/im/vector/app/features/crypto/keys/KeysExporter.kt index 3db67df8e1..27d8d4842a 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/keys/KeysExporter.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/keys/KeysExporter.kt @@ -34,7 +34,7 @@ class KeysExporter @Inject constructor( suspend fun export(password: String, uri: Uri) { withContext(dispatchers.io) { val data = session.cryptoService().exportRoomKeys(password) - context.contentResolver.openOutputStream(uri) + context.contentResolver.openOutputStream(uri, "wt") ?.use { it.write(data) } ?: throw IllegalStateException("Unable to open file for writing") verifyExportedKeysOutputFileSize(uri, expectedSize = data.size.toLong()) diff --git a/vector/src/main/java/im/vector/app/features/crypto/keysbackup/setup/KeysBackupSetupStep3Fragment.kt b/vector/src/main/java/im/vector/app/features/crypto/keysbackup/setup/KeysBackupSetupStep3Fragment.kt index c1cd87b4c8..42ff4ac183 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/keysbackup/setup/KeysBackupSetupStep3Fragment.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/keysbackup/setup/KeysBackupSetupStep3Fragment.kt @@ -165,7 +165,7 @@ class KeysBackupSetupStep3Fragment @Inject constructor() : VectorBaseFragment os.write(data.toByteArray()) os.flush() diff --git a/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapSaveRecoveryKeyFragment.kt b/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapSaveRecoveryKeyFragment.kt index 8a41c7ce4d..746ed48c94 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapSaveRecoveryKeyFragment.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapSaveRecoveryKeyFragment.kt @@ -81,7 +81,7 @@ class BootstrapSaveRecoveryKeyFragment @Inject constructor( val uri = activityResult.data?.data ?: return@registerStartForActivityResult lifecycleScope.launch(Dispatchers.IO) { try { - sharedViewModel.handle(BootstrapActions.SaveKeyToUri(requireContext().contentResolver!!.openOutputStream(uri)!!)) + sharedViewModel.handle(BootstrapActions.SaveKeyToUri(requireContext().contentResolver!!.openOutputStream(uri, "wt")!!)) } catch (failure: Throwable) { sharedViewModel.handle(BootstrapActions.SaveReqFailed) } 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 db2d07feef..6748fec1bc 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 @@ -106,7 +106,7 @@ class KeyRequestsFragment @Inject constructor() : VectorBaseFragment { tryOrNull { - requireContext().contentResolver?.openOutputStream(it.uri) + requireContext().contentResolver?.openOutputStream(it.uri, "wt") ?.use { os -> os.write(it.raw.toByteArray()) } } } diff --git a/vector/src/test/java/im/vector/app/features/crypto/keys/KeysExporterTest.kt b/vector/src/test/java/im/vector/app/features/crypto/keys/KeysExporterTest.kt index 5d0317592d..3bec3fad88 100644 --- a/vector/src/test/java/im/vector/app/features/crypto/keys/KeysExporterTest.kt +++ b/vector/src/test/java/im/vector/app/features/crypto/keys/KeysExporterTest.kt @@ -53,7 +53,7 @@ class KeysExporterTest { @Test fun `when exporting then writes exported keys to context output stream`() { givenFileDescriptorWithSize(size = A_ROOM_KEYS_EXPORT.size.toLong()) - val outputStream = context.givenOutputStreamFor(A_URI) + val outputStream = context.givenOutputStreamFor(A_URI, mode = "wt") runTest { keysExporter.export(A_PASSWORD, A_URI) } @@ -63,7 +63,7 @@ class KeysExporterTest { @Test fun `given different file size returned for export when exporting then throws UnexpectedExportKeysFileSizeException`() { givenFileDescriptorWithSize(size = 110) - context.givenOutputStreamFor(A_URI) + context.givenOutputStreamFor(A_URI, mode = "wt") assertFailsWith { runTest { keysExporter.export(A_PASSWORD, A_URI) } @@ -72,7 +72,7 @@ class KeysExporterTest { @Test fun `given output stream is unavailable for exporting to when exporting then throws IllegalStateException`() { - context.givenMissingOutputStreamFor(A_URI) + context.givenMissingOutputStreamFor(A_URI, mode = "wt") assertFailsWith(message = "Unable to open file for writing") { runTest { keysExporter.export(A_PASSWORD, A_URI) } @@ -82,7 +82,7 @@ class KeysExporterTest { @Test fun `given exported file is missing after export when exporting then throws IllegalStateException`() { context.givenFileDescriptor(A_URI, mode = "r") { null } - context.givenOutputStreamFor(A_URI) + context.givenOutputStreamFor(A_URI, mode = "wt") assertFailsWith(message = "Exported file not found") { runTest { keysExporter.export(A_PASSWORD, A_URI) } diff --git a/vector/src/test/java/im/vector/app/test/fakes/FakeContext.kt b/vector/src/test/java/im/vector/app/test/fakes/FakeContext.kt index 8382e54253..de1a7956b8 100644 --- a/vector/src/test/java/im/vector/app/test/fakes/FakeContext.kt +++ b/vector/src/test/java/im/vector/app/test/fakes/FakeContext.kt @@ -39,13 +39,13 @@ class FakeContext( every { contentResolver.openFileDescriptor(uri, mode, null) } returns fileDescriptor } - fun givenOutputStreamFor(uri: Uri): OutputStream { + fun givenOutputStreamFor(uri: Uri, mode: String): OutputStream { val outputStream = mockk(relaxed = true) - every { contentResolver.openOutputStream(uri) } returns outputStream + every { contentResolver.openOutputStream(uri, mode) } returns outputStream return outputStream } - fun givenMissingOutputStreamFor(uri: Uri) { - every { contentResolver.openOutputStream(uri) } returns null + fun givenMissingOutputStreamFor(uri: Uri, mode: String) { + every { contentResolver.openOutputStream(uri, mode) } returns null } } From 963b2dfa572e384ee510ea9add03e651452224c6 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 29 Mar 2022 19:38:10 +0200 Subject: [PATCH 160/262] Ignore false positive on static analysis tools Until 2023-01-01Z ! --- build.gradle | 7 +++++++ tools/dependencycheck/suppressions.xml | 17 +++++++++++++++++ 2 files changed, 24 insertions(+) create mode 100644 tools/dependencycheck/suppressions.xml diff --git a/build.gradle b/build.gradle index 6ad0663c6d..a5e1242afa 100644 --- a/build.gradle +++ b/build.gradle @@ -35,6 +35,13 @@ plugins { // https://github.com/jeremylong/DependencyCheck apply plugin: 'org.owasp.dependencycheck' +dependencyCheck { + // See https://jeremylong.github.io/DependencyCheck/general/suppression.html + suppressionFiles = [ + "./tools/dependencycheck/suppressions.xml" + ] +} + allprojects { apply plugin: "org.jlleitschuh.gradle.ktlint" diff --git a/tools/dependencycheck/suppressions.xml b/tools/dependencycheck/suppressions.xml new file mode 100644 index 0000000000..758b1a87f3 --- /dev/null +++ b/tools/dependencycheck/suppressions.xml @@ -0,0 +1,17 @@ + + + + + ^pkg:maven/com\.pinterest\.ktlint/ktlint\-reporter\-checkstyle@.*$ + CVE-2019-10782 + + + + ^pkg:maven/com\.pinterest\.ktlint/ktlint\-reporter\-checkstyle@.*$ + CVE-2019-9658 + + From f4f5beff152ebcfe623aa84ae9155bf16c733cf9 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 30 Mar 2022 09:54:02 +0200 Subject: [PATCH 161/262] Add changelogs --- changelog.d/5654.feature | 1 + changelog.d/5654.misc | 1 + 2 files changed, 2 insertions(+) create mode 100644 changelog.d/5654.feature create mode 100644 changelog.d/5654.misc diff --git a/changelog.d/5654.feature b/changelog.d/5654.feature new file mode 100644 index 0000000000..52a41ef37a --- /dev/null +++ b/changelog.d/5654.feature @@ -0,0 +1 @@ +Update Jitsi lib from 3.10.0 to 5.0.2 \ No newline at end of file diff --git a/changelog.d/5654.misc b/changelog.d/5654.misc new file mode 100644 index 0000000000..26e2ed5a1c --- /dev/null +++ b/changelog.d/5654.misc @@ -0,0 +1 @@ +Setup the plugin org.owasp.dependencycheck \ No newline at end of file From 9d3d574d2833afc4cb9f7dc6b564fbb3b49cd5a0 Mon Sep 17 00:00:00 2001 From: Adam Brown Date: Wed, 30 Mar 2022 09:09:46 +0100 Subject: [PATCH 162/262] FTUE - Finalising personalisation journey (#5519) * enabling the personalisation flow and promoting the strings for translation * delegating the fake vector features to the static defaults, the fake exists for allowing overrides when needed * incorporating the personalisation screens into the UI test signup * adding changelog entry * removing unused import * putting the personalisation UI test flow behind the feature flag so that we can keep it disabled * disabling the personalisation flow, we'll batch enable with other parts of the FTUE flow * enabling the personalisation feature for registration unit tests which expect it to be enabled --- changelog.d/5519.wip | 1 + .../im/vector/app/ui/robot/OnboardingRobot.kt | 17 ++++++++++++++++- vector/src/main/res/values/donottranslate.xml | 18 ------------------ vector/src/main/res/values/strings.xml | 19 +++++++++++++++++++ .../onboarding/OnboardingViewModelTest.kt | 9 ++++++--- .../app/test/fakes/FakeVectorFeatures.kt | 14 ++++++++------ 6 files changed, 50 insertions(+), 28 deletions(-) create mode 100644 changelog.d/5519.wip diff --git a/changelog.d/5519.wip b/changelog.d/5519.wip new file mode 100644 index 0000000000..c5a6112ad9 --- /dev/null +++ b/changelog.d/5519.wip @@ -0,0 +1 @@ +Finalising FTUE onboarding account creation personalization steps but keeping feature disabled until other parts are complete \ No newline at end of file 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 d051488ad7..97e3b281c0 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 @@ -29,6 +29,7 @@ import com.adevinta.android.barista.interaction.BaristaClickInteractions.clickOn import com.adevinta.android.barista.interaction.BaristaEditTextInteractions.writeTo import im.vector.app.R import im.vector.app.espresso.tools.waitUntilViewVisible +import im.vector.app.features.DefaultVectorFeatures import im.vector.app.waitForView class OnboardingRobot { @@ -57,7 +58,21 @@ 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) + if (DefaultVectorFeatures().isOnboardingPersonalizeEnabled()) { + clickOn(R.string.ftue_account_created_personalize) + + waitUntilViewVisible(withText(R.string.ftue_display_name_title)) + writeTo(R.id.displayNameInput, "UI automation") + clickOn(R.string.ftue_personalize_submit) + + waitUntilViewVisible(withText(R.string.ftue_profile_picture_title)) + clickOn(R.string.ftue_personalize_skip_this_step) + + waitUntilViewVisible(withText(R.string.ftue_personalize_complete_title)) + clickOn(R.string.ftue_personalize_lets_go) + } else { + 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/main/res/values/donottranslate.xml b/vector/src/main/res/values/donottranslate.xml index d8e06459c8..2895d72a98 100755 --- a/vector/src/main/res/values/donottranslate.xml +++ b/vector/src/main/res/values/donottranslate.xml @@ -10,22 +10,4 @@ Cut the slack from teams. - Personalize profile - Take me home - Congratulations! - Your account %s has been created. - - Choose a display name - This will be shown when you send messages. - Display Name - You can change this later - - Add a profile picture - You can change this anytime. - Let\'s go - You\'re all set! - Your preferences have been saved. - - Save and continue - Skip this step diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml index a276e07b1e..59eb6e2911 100644 --- a/vector/src/main/res/values/strings.xml +++ b/vector/src/main/res/values/strings.xml @@ -1863,6 +1863,25 @@ Looking to join an existing server? Connect to server + Personalize profile + Take me home + Congratulations! + Your account %s has been created. + + Choose a display name + This will be shown when you send messages. + Display Name + You can change this later + + Add a profile picture + You can change this anytime. + Let\'s go + You\'re all set! + Your preferences have been saved. + + Save and continue + Skip this step + It\'s your conversation. Own it. Chat with people directly or in groups Keep conversations private with encryption diff --git a/vector/src/test/java/im/vector/app/features/onboarding/OnboardingViewModelTest.kt b/vector/src/test/java/im/vector/app/features/onboarding/OnboardingViewModelTest.kt index 118bf689d2..a682d025b8 100644 --- a/vector/src/test/java/im/vector/app/features/onboarding/OnboardingViewModelTest.kt +++ b/vector/src/test/java/im/vector/app/features/onboarding/OnboardingViewModelTest.kt @@ -73,6 +73,7 @@ class OnboardingViewModelTest { private val fakeAuthenticationService = FakeAuthenticationService() private val fakeRegisterActionHandler = FakeRegisterActionHandler() private val fakeDirectLoginUseCase = FakeDirectLoginUseCase() + private val fakeVectorFeatures = FakeVectorFeatures() lateinit var viewModel: OnboardingViewModel @@ -224,7 +225,8 @@ class OnboardingViewModelTest { } @Test - fun `when registering account, then updates state and emits account created event`() = runTest { + fun `given personalisation enabled, when registering account, then updates state and emits account created event`() = runTest { + fakeVectorFeatures.givenPersonalisationEnabled() givenRegistrationResultFor(A_LOADABLE_REGISTER_ACTION, RegistrationResult.Success(fakeSession)) givenSuccessfullyCreatesAccount(A_HOMESERVER_CAPABILITIES) val test = viewModel.test() @@ -242,7 +244,8 @@ class OnboardingViewModelTest { } @Test - fun `given registration has started and has dummy step to do, when handling action, then ignores other steps and executes dummy`() = runTest { + fun `given personalisation enabled and registration has started and has dummy step to do, when handling action, then ignores other steps and executes dummy`() = runTest { + fakeVectorFeatures.givenPersonalisationEnabled() givenSuccessfulRegistrationForStartAndDummySteps(missingStages = listOf(Stage.Dummy(mandatory = true))) val test = viewModel.test() @@ -384,7 +387,7 @@ class OnboardingViewModelTest { ReAuthHelper(), FakeStringProvider().instance, FakeHomeServerHistoryService(), - FakeVectorFeatures(), + fakeVectorFeatures, FakeAnalyticsTracker(), fakeUriFilenameResolver.instance, fakeRegisterActionHandler.instance, diff --git a/vector/src/test/java/im/vector/app/test/fakes/FakeVectorFeatures.kt b/vector/src/test/java/im/vector/app/test/fakes/FakeVectorFeatures.kt index b6e06bcdda..aeabcce7cd 100644 --- a/vector/src/test/java/im/vector/app/test/fakes/FakeVectorFeatures.kt +++ b/vector/src/test/java/im/vector/app/test/fakes/FakeVectorFeatures.kt @@ -16,12 +16,14 @@ package im.vector.app.test.fakes +import im.vector.app.features.DefaultVectorFeatures import im.vector.app.features.VectorFeatures +import io.mockk.every +import io.mockk.spyk -class FakeVectorFeatures : VectorFeatures { - override fun onboardingVariant() = VectorFeatures.OnboardingVariant.FTUE_AUTH - override fun isOnboardingAlreadyHaveAccountSplashEnabled() = true - override fun isOnboardingSplashCarouselEnabled() = true - override fun isOnboardingUseCaseEnabled() = true - override fun isOnboardingPersonalizeEnabled() = true +class FakeVectorFeatures : VectorFeatures by spyk() { + + fun givenPersonalisationEnabled() { + every { isOnboardingPersonalizeEnabled() } returns true + } } From 93876737e7569b1ee3942a72b5d97a845e4f380e Mon Sep 17 00:00:00 2001 From: Maxime Naturel Date: Fri, 18 Mar 2022 16:57:43 +0100 Subject: [PATCH 163/262] Adding forceEnableLiveLocationSharing field in VectorOverride interface --- .../features/debug/features/DebugVectorOverrides.kt | 11 +++++++++++ .../im/vector/app/features/DefaultVectorOverrides.kt | 2 ++ 2 files changed, 13 insertions(+) diff --git a/vector/src/debug/java/im/vector/app/features/debug/features/DebugVectorOverrides.kt b/vector/src/debug/java/im/vector/app/features/debug/features/DebugVectorOverrides.kt index 5e16182f3c..4bd19d9740 100644 --- a/vector/src/debug/java/im/vector/app/features/debug/features/DebugVectorOverrides.kt +++ b/vector/src/debug/java/im/vector/app/features/debug/features/DebugVectorOverrides.kt @@ -33,6 +33,7 @@ private val keyForceDialPadDisplay = booleanPreferencesKey("force_dial_pad_displ private val keyForceLoginFallback = booleanPreferencesKey("force_login_fallback") private val forceCanChangeDisplayName = booleanPreferencesKey("force_can_change_display_name") private val forceCanChangeAvatar = booleanPreferencesKey("force_can_change_avatar") +private val keyForceEnableLiveLocationSharing = booleanPreferencesKey("force_enable_live_location_sharing") class DebugVectorOverrides(private val context: Context) : VectorOverrides { @@ -51,6 +52,10 @@ class DebugVectorOverrides(private val context: Context) : VectorOverrides { ) } + override val forceEnableLiveLocationSharing = context.dataStore.data.map { preferences -> + preferences[keyForceEnableLiveLocationSharing].orFalse() + } + suspend fun setForceDialPadDisplay(force: Boolean) { context.dataStore.edit { settings -> settings[keyForceDialPadDisplay] = force @@ -76,4 +81,10 @@ class DebugVectorOverrides(private val context: Context) : VectorOverrides { } } } + + suspend fun setForceEnableLiveLocationSharing(force: Boolean) { + context.dataStore.edit { settings -> + settings[keyForceEnableLiveLocationSharing] = force + } + } } diff --git a/vector/src/main/java/im/vector/app/features/DefaultVectorOverrides.kt b/vector/src/main/java/im/vector/app/features/DefaultVectorOverrides.kt index daa0d9e0bd..d055244148 100644 --- a/vector/src/main/java/im/vector/app/features/DefaultVectorOverrides.kt +++ b/vector/src/main/java/im/vector/app/features/DefaultVectorOverrides.kt @@ -23,6 +23,7 @@ interface VectorOverrides { val forceDialPad: Flow val forceLoginFallback: Flow val forceHomeserverCapabilities: Flow? + val forceEnableLiveLocationSharing: Flow } data class HomeserverCapabilitiesOverride( @@ -34,4 +35,5 @@ class DefaultVectorOverrides : VectorOverrides { override val forceDialPad = flowOf(false) override val forceLoginFallback = flowOf(false) override val forceHomeserverCapabilities: Flow? = null + override val forceEnableLiveLocationSharing = flowOf(false) } From 424f70bc58f8fcc3d4dfa6708c166c24c042d605 Mon Sep 17 00:00:00 2001 From: Maxime Naturel Date: Fri, 18 Mar 2022 17:27:47 +0100 Subject: [PATCH 164/262] Adding new override setting in the debug private settings --- .../debug/settings/DebugPrivateSettingsFragment.kt | 4 ++++ .../settings/DebugPrivateSettingsViewActions.kt | 1 + .../settings/DebugPrivateSettingsViewModel.kt | 14 ++++++++++++-- .../settings/DebugPrivateSettingsViewState.kt | 3 ++- .../res/layout/fragment_debug_private_settings.xml | 6 ++++++ 5 files changed, 25 insertions(+), 3 deletions(-) diff --git a/vector/src/debug/java/im/vector/app/features/debug/settings/DebugPrivateSettingsFragment.kt b/vector/src/debug/java/im/vector/app/features/debug/settings/DebugPrivateSettingsFragment.kt index 38253fe7c2..735b4079c0 100644 --- a/vector/src/debug/java/im/vector/app/features/debug/settings/DebugPrivateSettingsFragment.kt +++ b/vector/src/debug/java/im/vector/app/features/debug/settings/DebugPrivateSettingsFragment.kt @@ -46,6 +46,9 @@ class DebugPrivateSettingsFragment : VectorBaseFragment viewModel.handle(DebugPrivateSettingsViewActions.SetForceLoginFallbackEnabled(isChecked)) } + views.forceEnableLiveLocationSharing.setOnCheckedChangeListener { _, isChecked -> + viewModel.handle(DebugPrivateSettingsViewActions.SetEnableLiveLocationSharing(isChecked)) + } } override fun invalidate() = withState(viewModel) { @@ -57,5 +60,6 @@ class DebugPrivateSettingsFragment : VectorBaseFragment handleSetForceLoginFallbackEnabled(action) is SetDisplayNameCapabilityOverride -> handSetDisplayNameCapabilityOverride(action) is SetAvatarCapabilityOverride -> handSetAvatarCapabilityOverride(action) + is DebugPrivateSettingsViewActions.SetEnableLiveLocationSharing -> handleSetEnableLiveLocationSharingOverride(action) } } @@ -85,17 +89,23 @@ class DebugPrivateSettingsViewModel @AssistedInject constructor( } } - private fun handSetDisplayNameCapabilityOverride(action: SetDisplayNameCapabilityOverride) { + private fun handleSetDisplayNameCapabilityOverride(action: SetDisplayNameCapabilityOverride) { viewModelScope.launch { val forceDisplayName = action.option.toBoolean() debugVectorOverrides.setHomeserverCapabilities { copy(canChangeDisplayName = forceDisplayName) } } } - private fun handSetAvatarCapabilityOverride(action: SetAvatarCapabilityOverride) { + private fun handleSetAvatarCapabilityOverride(action: SetAvatarCapabilityOverride) { viewModelScope.launch { val forceAvatar = action.option.toBoolean() debugVectorOverrides.setHomeserverCapabilities { copy(canChangeAvatar = forceAvatar) } } } + + private fun handleSetEnableLiveLocationSharingOverride(action: DebugPrivateSettingsViewActions.SetEnableLiveLocationSharing) { + viewModelScope.launch { + debugVectorOverrides.setForceEnableLiveLocationSharing(action.force) + } + } } diff --git a/vector/src/debug/java/im/vector/app/features/debug/settings/DebugPrivateSettingsViewState.kt b/vector/src/debug/java/im/vector/app/features/debug/settings/DebugPrivateSettingsViewState.kt index 749b11a744..2eff1575c7 100644 --- a/vector/src/debug/java/im/vector/app/features/debug/settings/DebugPrivateSettingsViewState.kt +++ b/vector/src/debug/java/im/vector/app/features/debug/settings/DebugPrivateSettingsViewState.kt @@ -22,7 +22,8 @@ import im.vector.app.features.debug.settings.OverrideDropdownView.OverrideDropdo data class DebugPrivateSettingsViewState( val dialPadVisible: Boolean = false, val forceLoginFallback: Boolean = false, - val homeserverCapabilityOverrides: HomeserverCapabilityOverrides = HomeserverCapabilityOverrides() + val homeserverCapabilityOverrides: HomeserverCapabilityOverrides = HomeserverCapabilityOverrides(), + val forceEnableLiveLocationSharing: Boolean = false ) : MavericksState data class HomeserverCapabilityOverrides( diff --git a/vector/src/debug/res/layout/fragment_debug_private_settings.xml b/vector/src/debug/res/layout/fragment_debug_private_settings.xml index c42ad68dce..3e89ac9120 100644 --- a/vector/src/debug/res/layout/fragment_debug_private_settings.xml +++ b/vector/src/debug/res/layout/fragment_debug_private_settings.xml @@ -49,6 +49,12 @@ android:layout_marginEnd="16dp" android:layout_marginBottom="4dp" /> + +
    From cfce144b61fd64fa57de91e80cf1f894606a93ab Mon Sep 17 00:00:00 2001 From: Maxime Naturel Date: Fri, 18 Mar 2022 17:28:18 +0100 Subject: [PATCH 165/262] Using the override setting in the LocationSharing screen --- .../app/features/location/LocationSharingFragment.kt | 3 +-- .../features/location/LocationSharingViewModel.kt | 12 +++++++++++- .../features/location/LocationSharingViewState.kt | 5 ++++- 3 files changed, 16 insertions(+), 4 deletions(-) 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 b779b50c8b..ea350b00c3 100644 --- a/vector/src/main/java/im/vector/app/features/location/LocationSharingFragment.kt +++ b/vector/src/main/java/im/vector/app/features/location/LocationSharingFragment.kt @@ -29,7 +29,6 @@ import com.airbnb.mvrx.fragmentViewModel import com.airbnb.mvrx.withState 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.platform.VectorBaseFragment import im.vector.app.core.utils.PERMISSIONS_FOR_BACKGROUND_LOCATION_SHARING @@ -243,7 +242,7 @@ class LocationSharingFragment @Inject constructor( // first, update the options view val options: Set = when (state.areTargetAndUserLocationEqual) { true -> { - if (BuildConfig.ENABLE_LIVE_LOCATION_SHARING) { + if (state.isLiveLocationSharingEnabled) { setOf(LocationSharingOption.USER_CURRENT, LocationSharingOption.USER_LIVE) } else { setOf(LocationSharingOption.USER_CURRENT) 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 dfa936dcaa..186c5c46b3 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 @@ -21,9 +21,11 @@ import com.airbnb.mvrx.MavericksViewModelFactory import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject +import im.vector.app.BuildConfig import im.vector.app.core.di.MavericksAssistedViewModelFactory import im.vector.app.core.di.hiltMavericksViewModelFactory import im.vector.app.core.platform.VectorViewModel +import im.vector.app.features.VectorOverrides import im.vector.app.features.home.room.detail.timeline.helper.LocationPinProvider import im.vector.app.features.location.domain.usecase.CompareLocationsUseCase import kotlinx.coroutines.flow.MutableSharedFlow @@ -48,7 +50,8 @@ class LocationSharingViewModel @AssistedInject constructor( private val locationTracker: LocationTracker, private val locationPinProvider: LocationPinProvider, private val session: Session, - private val compareLocationsUseCase: CompareLocationsUseCase + private val compareLocationsUseCase: CompareLocationsUseCase, + private val vectorOverrides: VectorOverrides ) : VectorViewModel(initialState), LocationTracker.Callback { private val room = session.getRoom(initialState.roomId)!! @@ -68,6 +71,7 @@ class LocationSharingViewModel @AssistedInject constructor( setUserItem() updatePin() compareTargetAndUserLocation() + observeVectorOverrides() } private fun setUserItem() { @@ -109,6 +113,12 @@ class LocationSharingViewModel @AssistedInject constructor( ?.let { userLocation -> compareLocationsUseCase.execute(userLocation, targetLocation) } } + private fun observeVectorOverrides() { + vectorOverrides.forceEnableLiveLocationSharing.setOnEach { forceLiveLocation -> + copy(isLiveLocationSharingEnabled = forceLiveLocation || BuildConfig.ENABLE_LIVE_LOCATION_SHARING) + } + } + override fun onCleared() { super.onCleared() locationTracker.removeCallback(this) diff --git a/vector/src/main/java/im/vector/app/features/location/LocationSharingViewState.kt b/vector/src/main/java/im/vector/app/features/location/LocationSharingViewState.kt index ee5ba402e2..64039b00c4 100644 --- a/vector/src/main/java/im/vector/app/features/location/LocationSharingViewState.kt +++ b/vector/src/main/java/im/vector/app/features/location/LocationSharingViewState.kt @@ -19,6 +19,7 @@ package im.vector.app.features.location import android.graphics.drawable.Drawable import androidx.annotation.StringRes import com.airbnb.mvrx.MavericksState +import im.vector.app.BuildConfig import im.vector.app.R import org.matrix.android.sdk.api.extensions.orTrue import org.matrix.android.sdk.api.util.MatrixItem @@ -31,6 +32,7 @@ enum class LocationSharingMode(@StringRes val titleRes: Int) { data class LocationSharingViewState( val roomId: String, val mode: LocationSharingMode, + val isLiveLocationSharingEnabled: Boolean, val userItem: MatrixItem.UserItem? = null, val areTargetAndUserLocationEqual: Boolean? = null, val lastKnownUserLocation: LocationData? = null, @@ -39,7 +41,8 @@ data class LocationSharingViewState( constructor(locationSharingArgs: LocationSharingArgs) : this( roomId = locationSharingArgs.roomId, - mode = locationSharingArgs.mode + mode = locationSharingArgs.mode, + isLiveLocationSharingEnabled = BuildConfig.ENABLE_LIVE_LOCATION_SHARING ) } From e92a05abe7774792101bb551b434e0294b571eef Mon Sep 17 00:00:00 2001 From: Maxime Naturel Date: Fri, 18 Mar 2022 17:30:11 +0100 Subject: [PATCH 166/262] Adding changelog entry --- changelog.d/5581.misc | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/5581.misc diff --git a/changelog.d/5581.misc b/changelog.d/5581.misc new file mode 100644 index 0000000000..3191c5eae8 --- /dev/null +++ b/changelog.d/5581.misc @@ -0,0 +1 @@ +Live location sharing: adding way to override feature activation in debug From 78b2ccb2b50dfcdf8a350f7a8d5626577ff56aac Mon Sep 17 00:00:00 2001 From: Maxime Naturel Date: Mon, 28 Mar 2022 17:57:04 +0200 Subject: [PATCH 167/262] Using VectorFeatures instead of VectorOverrides --- .../debug/features/DebugFeaturesStateFactory.kt | 5 +++++ .../features/debug/features/DebugVectorFeatures.kt | 4 ++++ .../java/im/vector/app/features/VectorFeatures.kt | 2 ++ .../features/location/LocationSharingViewModel.kt | 13 ++++++------- 4 files changed, 17 insertions(+), 7 deletions(-) diff --git a/vector/src/debug/java/im/vector/app/features/debug/features/DebugFeaturesStateFactory.kt b/vector/src/debug/java/im/vector/app/features/debug/features/DebugFeaturesStateFactory.kt index 8702c8d966..60024d4c19 100644 --- a/vector/src/debug/java/im/vector/app/features/debug/features/DebugFeaturesStateFactory.kt +++ b/vector/src/debug/java/im/vector/app/features/debug/features/DebugFeaturesStateFactory.kt @@ -54,6 +54,11 @@ class DebugFeaturesStateFactory @Inject constructor( key = DebugFeatureKeys.onboardingPersonalize, factory = VectorFeatures::isOnboardingPersonalizeEnabled ), + createBooleanFeature( + label = "Live location sharing", + key = DebugFeatureKeys.liveLocationSharing, + factory = VectorFeatures::isLiveLocationEnabled + ), )) } diff --git a/vector/src/debug/java/im/vector/app/features/debug/features/DebugVectorFeatures.kt b/vector/src/debug/java/im/vector/app/features/debug/features/DebugVectorFeatures.kt index f93e3d96fb..ec3de1408a 100644 --- a/vector/src/debug/java/im/vector/app/features/debug/features/DebugVectorFeatures.kt +++ b/vector/src/debug/java/im/vector/app/features/debug/features/DebugVectorFeatures.kt @@ -54,6 +54,9 @@ class DebugVectorFeatures( override fun isOnboardingPersonalizeEnabled(): Boolean = read(DebugFeatureKeys.onboardingPersonalize) ?: vectorFeatures.isOnboardingPersonalizeEnabled() + override fun isLiveLocationEnabled(): Boolean = read(DebugFeatureKeys.liveLocationSharing) + ?: vectorFeatures.isLiveLocationEnabled() + fun override(value: T?, key: Preferences.Key) = updatePreferences { if (value == null) { it.remove(key) @@ -106,4 +109,5 @@ object DebugFeatureKeys { val onboardingSplashCarousel = booleanPreferencesKey("onboarding-splash-carousel") val onboardingUseCase = booleanPreferencesKey("onbboarding-splash-carousel") val onboardingPersonalize = booleanPreferencesKey("onbboarding-personalize") + val liveLocationSharing = booleanPreferencesKey("live-location-sharing") } diff --git a/vector/src/main/java/im/vector/app/features/VectorFeatures.kt b/vector/src/main/java/im/vector/app/features/VectorFeatures.kt index a19b3d9026..52ad9242f4 100644 --- a/vector/src/main/java/im/vector/app/features/VectorFeatures.kt +++ b/vector/src/main/java/im/vector/app/features/VectorFeatures.kt @@ -25,6 +25,7 @@ interface VectorFeatures { fun isOnboardingSplashCarouselEnabled(): Boolean fun isOnboardingUseCaseEnabled(): Boolean fun isOnboardingPersonalizeEnabled(): Boolean + fun isLiveLocationEnabled(): Boolean enum class OnboardingVariant { LEGACY, @@ -39,4 +40,5 @@ class DefaultVectorFeatures : VectorFeatures { override fun isOnboardingSplashCarouselEnabled() = true override fun isOnboardingUseCaseEnabled() = true override fun isOnboardingPersonalizeEnabled() = false + override fun isLiveLocationEnabled(): Boolean = BuildConfig.ENABLE_LIVE_LOCATION_SHARING } 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 186c5c46b3..37ccd344b3 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 @@ -21,11 +21,10 @@ import com.airbnb.mvrx.MavericksViewModelFactory import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject -import im.vector.app.BuildConfig import im.vector.app.core.di.MavericksAssistedViewModelFactory import im.vector.app.core.di.hiltMavericksViewModelFactory import im.vector.app.core.platform.VectorViewModel -import im.vector.app.features.VectorOverrides +import im.vector.app.features.VectorFeatures import im.vector.app.features.home.room.detail.timeline.helper.LocationPinProvider import im.vector.app.features.location.domain.usecase.CompareLocationsUseCase import kotlinx.coroutines.flow.MutableSharedFlow @@ -51,7 +50,7 @@ class LocationSharingViewModel @AssistedInject constructor( private val locationPinProvider: LocationPinProvider, private val session: Session, private val compareLocationsUseCase: CompareLocationsUseCase, - private val vectorOverrides: VectorOverrides + private val vectorFeatures: VectorFeatures, ) : VectorViewModel(initialState), LocationTracker.Callback { private val room = session.getRoom(initialState.roomId)!! @@ -71,7 +70,7 @@ class LocationSharingViewModel @AssistedInject constructor( setUserItem() updatePin() compareTargetAndUserLocation() - observeVectorOverrides() + checkVectorFeatures() } private fun setUserItem() { @@ -113,9 +112,9 @@ class LocationSharingViewModel @AssistedInject constructor( ?.let { userLocation -> compareLocationsUseCase.execute(userLocation, targetLocation) } } - private fun observeVectorOverrides() { - vectorOverrides.forceEnableLiveLocationSharing.setOnEach { forceLiveLocation -> - copy(isLiveLocationSharingEnabled = forceLiveLocation || BuildConfig.ENABLE_LIVE_LOCATION_SHARING) + private fun checkVectorFeatures() { + setState { + copy(isLiveLocationSharingEnabled = vectorFeatures.isLiveLocationEnabled()) } } From 90c53b9dd57d195ac13c759c542b81f74b9fafff Mon Sep 17 00:00:00 2001 From: Maxime Naturel Date: Mon, 28 Mar 2022 18:02:26 +0200 Subject: [PATCH 168/262] Remove non necessary DebugOverrides --- .../features/debug/features/DebugVectorOverrides.kt | 11 ----------- .../debug/settings/DebugPrivateSettingsFragment.kt | 4 ---- .../debug/settings/DebugPrivateSettingsViewActions.kt | 1 - .../debug/settings/DebugPrivateSettingsViewModel.kt | 10 ---------- .../debug/settings/DebugPrivateSettingsViewState.kt | 3 +-- .../res/layout/fragment_debug_private_settings.xml | 6 ------ .../im/vector/app/features/DefaultVectorOverrides.kt | 2 -- 7 files changed, 1 insertion(+), 36 deletions(-) diff --git a/vector/src/debug/java/im/vector/app/features/debug/features/DebugVectorOverrides.kt b/vector/src/debug/java/im/vector/app/features/debug/features/DebugVectorOverrides.kt index 4bd19d9740..5e16182f3c 100644 --- a/vector/src/debug/java/im/vector/app/features/debug/features/DebugVectorOverrides.kt +++ b/vector/src/debug/java/im/vector/app/features/debug/features/DebugVectorOverrides.kt @@ -33,7 +33,6 @@ private val keyForceDialPadDisplay = booleanPreferencesKey("force_dial_pad_displ private val keyForceLoginFallback = booleanPreferencesKey("force_login_fallback") private val forceCanChangeDisplayName = booleanPreferencesKey("force_can_change_display_name") private val forceCanChangeAvatar = booleanPreferencesKey("force_can_change_avatar") -private val keyForceEnableLiveLocationSharing = booleanPreferencesKey("force_enable_live_location_sharing") class DebugVectorOverrides(private val context: Context) : VectorOverrides { @@ -52,10 +51,6 @@ class DebugVectorOverrides(private val context: Context) : VectorOverrides { ) } - override val forceEnableLiveLocationSharing = context.dataStore.data.map { preferences -> - preferences[keyForceEnableLiveLocationSharing].orFalse() - } - suspend fun setForceDialPadDisplay(force: Boolean) { context.dataStore.edit { settings -> settings[keyForceDialPadDisplay] = force @@ -81,10 +76,4 @@ class DebugVectorOverrides(private val context: Context) : VectorOverrides { } } } - - suspend fun setForceEnableLiveLocationSharing(force: Boolean) { - context.dataStore.edit { settings -> - settings[keyForceEnableLiveLocationSharing] = force - } - } } diff --git a/vector/src/debug/java/im/vector/app/features/debug/settings/DebugPrivateSettingsFragment.kt b/vector/src/debug/java/im/vector/app/features/debug/settings/DebugPrivateSettingsFragment.kt index 735b4079c0..38253fe7c2 100644 --- a/vector/src/debug/java/im/vector/app/features/debug/settings/DebugPrivateSettingsFragment.kt +++ b/vector/src/debug/java/im/vector/app/features/debug/settings/DebugPrivateSettingsFragment.kt @@ -46,9 +46,6 @@ class DebugPrivateSettingsFragment : VectorBaseFragment viewModel.handle(DebugPrivateSettingsViewActions.SetForceLoginFallbackEnabled(isChecked)) } - views.forceEnableLiveLocationSharing.setOnCheckedChangeListener { _, isChecked -> - viewModel.handle(DebugPrivateSettingsViewActions.SetEnableLiveLocationSharing(isChecked)) - } } override fun invalidate() = withState(viewModel) { @@ -60,6 +57,5 @@ class DebugPrivateSettingsFragment : VectorBaseFragment handleSetForceLoginFallbackEnabled(action) is SetDisplayNameCapabilityOverride -> handSetDisplayNameCapabilityOverride(action) is SetAvatarCapabilityOverride -> handSetAvatarCapabilityOverride(action) - is DebugPrivateSettingsViewActions.SetEnableLiveLocationSharing -> handleSetEnableLiveLocationSharingOverride(action) } } @@ -102,10 +98,4 @@ class DebugPrivateSettingsViewModel @AssistedInject constructor( debugVectorOverrides.setHomeserverCapabilities { copy(canChangeAvatar = forceAvatar) } } } - - private fun handleSetEnableLiveLocationSharingOverride(action: DebugPrivateSettingsViewActions.SetEnableLiveLocationSharing) { - viewModelScope.launch { - debugVectorOverrides.setForceEnableLiveLocationSharing(action.force) - } - } } diff --git a/vector/src/debug/java/im/vector/app/features/debug/settings/DebugPrivateSettingsViewState.kt b/vector/src/debug/java/im/vector/app/features/debug/settings/DebugPrivateSettingsViewState.kt index 2eff1575c7..749b11a744 100644 --- a/vector/src/debug/java/im/vector/app/features/debug/settings/DebugPrivateSettingsViewState.kt +++ b/vector/src/debug/java/im/vector/app/features/debug/settings/DebugPrivateSettingsViewState.kt @@ -22,8 +22,7 @@ import im.vector.app.features.debug.settings.OverrideDropdownView.OverrideDropdo data class DebugPrivateSettingsViewState( val dialPadVisible: Boolean = false, val forceLoginFallback: Boolean = false, - val homeserverCapabilityOverrides: HomeserverCapabilityOverrides = HomeserverCapabilityOverrides(), - val forceEnableLiveLocationSharing: Boolean = false + val homeserverCapabilityOverrides: HomeserverCapabilityOverrides = HomeserverCapabilityOverrides() ) : MavericksState data class HomeserverCapabilityOverrides( diff --git a/vector/src/debug/res/layout/fragment_debug_private_settings.xml b/vector/src/debug/res/layout/fragment_debug_private_settings.xml index 3e89ac9120..c42ad68dce 100644 --- a/vector/src/debug/res/layout/fragment_debug_private_settings.xml +++ b/vector/src/debug/res/layout/fragment_debug_private_settings.xml @@ -49,12 +49,6 @@ android:layout_marginEnd="16dp" android:layout_marginBottom="4dp" /> - -
    diff --git a/vector/src/main/java/im/vector/app/features/DefaultVectorOverrides.kt b/vector/src/main/java/im/vector/app/features/DefaultVectorOverrides.kt index d055244148..daa0d9e0bd 100644 --- a/vector/src/main/java/im/vector/app/features/DefaultVectorOverrides.kt +++ b/vector/src/main/java/im/vector/app/features/DefaultVectorOverrides.kt @@ -23,7 +23,6 @@ interface VectorOverrides { val forceDialPad: Flow val forceLoginFallback: Flow val forceHomeserverCapabilities: Flow? - val forceEnableLiveLocationSharing: Flow } data class HomeserverCapabilitiesOverride( @@ -35,5 +34,4 @@ class DefaultVectorOverrides : VectorOverrides { override val forceDialPad = flowOf(false) override val forceLoginFallback = flowOf(false) override val forceHomeserverCapabilities: Flow? = null - override val forceEnableLiveLocationSharing = flowOf(false) } From 2b41096518269db2f90f60d2c35aaccb8789bf16 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL Date: Tue, 29 Mar 2022 16:16:53 +0200 Subject: [PATCH 169/262] Fixing wrong method name calls --- .../features/debug/settings/DebugPrivateSettingsViewModel.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 8db030c13d..1d77d031af 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 @@ -68,8 +68,8 @@ class DebugPrivateSettingsViewModel @AssistedInject constructor( when (action) { is DebugPrivateSettingsViewActions.SetDialPadVisibility -> handleSetDialPadVisibility(action) is DebugPrivateSettingsViewActions.SetForceLoginFallbackEnabled -> handleSetForceLoginFallbackEnabled(action) - is SetDisplayNameCapabilityOverride -> handSetDisplayNameCapabilityOverride(action) - is SetAvatarCapabilityOverride -> handSetAvatarCapabilityOverride(action) + is SetDisplayNameCapabilityOverride -> handleSetDisplayNameCapabilityOverride(action) + is SetAvatarCapabilityOverride -> handleSetAvatarCapabilityOverride(action) } } From f4ef4c2e617ac0b6e2cba813955a5695428570bd Mon Sep 17 00:00:00 2001 From: Maxime NATUREL Date: Wed, 30 Mar 2022 09:33:43 +0200 Subject: [PATCH 170/262] Fixing attempt of unit tests --- .../vector/app/features/onboarding/OnboardingViewModelTest.kt | 2 ++ .../test/java/im/vector/app/test/fakes/FakeVectorFeatures.kt | 4 ++++ 2 files changed, 6 insertions(+) diff --git a/vector/src/test/java/im/vector/app/features/onboarding/OnboardingViewModelTest.kt b/vector/src/test/java/im/vector/app/features/onboarding/OnboardingViewModelTest.kt index a682d025b8..a4983ba54a 100644 --- a/vector/src/test/java/im/vector/app/features/onboarding/OnboardingViewModelTest.kt +++ b/vector/src/test/java/im/vector/app/features/onboarding/OnboardingViewModelTest.kt @@ -227,6 +227,7 @@ class OnboardingViewModelTest { @Test fun `given personalisation enabled, when registering account, then updates state and emits account created event`() = runTest { fakeVectorFeatures.givenPersonalisationEnabled() + fakeVectorFeatures.givenLiveLocationSharingEnabled() givenRegistrationResultFor(A_LOADABLE_REGISTER_ACTION, RegistrationResult.Success(fakeSession)) givenSuccessfullyCreatesAccount(A_HOMESERVER_CAPABILITIES) val test = viewModel.test() @@ -246,6 +247,7 @@ class OnboardingViewModelTest { @Test fun `given personalisation enabled and registration has started and has dummy step to do, when handling action, then ignores other steps and executes dummy`() = runTest { fakeVectorFeatures.givenPersonalisationEnabled() + fakeVectorFeatures.givenLiveLocationSharingEnabled() givenSuccessfulRegistrationForStartAndDummySteps(missingStages = listOf(Stage.Dummy(mandatory = true))) val test = viewModel.test() diff --git a/vector/src/test/java/im/vector/app/test/fakes/FakeVectorFeatures.kt b/vector/src/test/java/im/vector/app/test/fakes/FakeVectorFeatures.kt index aeabcce7cd..680dc520ee 100644 --- a/vector/src/test/java/im/vector/app/test/fakes/FakeVectorFeatures.kt +++ b/vector/src/test/java/im/vector/app/test/fakes/FakeVectorFeatures.kt @@ -26,4 +26,8 @@ class FakeVectorFeatures : VectorFeatures by spyk() { fun givenPersonalisationEnabled() { every { isOnboardingPersonalizeEnabled() } returns true } + + fun givenLiveLocationSharingEnabled() { + every { isLiveLocationEnabled() } returns true + } } From 9e3dc4c10f489f8d2406396a70796e71fd1994fd Mon Sep 17 00:00:00 2001 From: Maxime NATUREL Date: Wed, 30 Mar 2022 10:59:47 +0200 Subject: [PATCH 171/262] Fixing unit tests --- .../vector/app/features/onboarding/OnboardingViewModelTest.kt | 2 -- .../test/java/im/vector/app/test/fakes/FakeVectorFeatures.kt | 4 ---- 2 files changed, 6 deletions(-) diff --git a/vector/src/test/java/im/vector/app/features/onboarding/OnboardingViewModelTest.kt b/vector/src/test/java/im/vector/app/features/onboarding/OnboardingViewModelTest.kt index a4983ba54a..a682d025b8 100644 --- a/vector/src/test/java/im/vector/app/features/onboarding/OnboardingViewModelTest.kt +++ b/vector/src/test/java/im/vector/app/features/onboarding/OnboardingViewModelTest.kt @@ -227,7 +227,6 @@ class OnboardingViewModelTest { @Test fun `given personalisation enabled, when registering account, then updates state and emits account created event`() = runTest { fakeVectorFeatures.givenPersonalisationEnabled() - fakeVectorFeatures.givenLiveLocationSharingEnabled() givenRegistrationResultFor(A_LOADABLE_REGISTER_ACTION, RegistrationResult.Success(fakeSession)) givenSuccessfullyCreatesAccount(A_HOMESERVER_CAPABILITIES) val test = viewModel.test() @@ -247,7 +246,6 @@ class OnboardingViewModelTest { @Test fun `given personalisation enabled and registration has started and has dummy step to do, when handling action, then ignores other steps and executes dummy`() = runTest { fakeVectorFeatures.givenPersonalisationEnabled() - fakeVectorFeatures.givenLiveLocationSharingEnabled() givenSuccessfulRegistrationForStartAndDummySteps(missingStages = listOf(Stage.Dummy(mandatory = true))) val test = viewModel.test() diff --git a/vector/src/test/java/im/vector/app/test/fakes/FakeVectorFeatures.kt b/vector/src/test/java/im/vector/app/test/fakes/FakeVectorFeatures.kt index 680dc520ee..aeabcce7cd 100644 --- a/vector/src/test/java/im/vector/app/test/fakes/FakeVectorFeatures.kt +++ b/vector/src/test/java/im/vector/app/test/fakes/FakeVectorFeatures.kt @@ -26,8 +26,4 @@ class FakeVectorFeatures : VectorFeatures by spyk() { fun givenPersonalisationEnabled() { every { isOnboardingPersonalizeEnabled() } returns true } - - fun givenLiveLocationSharingEnabled() { - every { isLiveLocationEnabled() } returns true - } } From 0d59a317882c4ebf3166b7a64b966966ff29be16 Mon Sep 17 00:00:00 2001 From: ariskotsomitopoulos Date: Wed, 30 Mar 2022 12:32:08 +0300 Subject: [PATCH 172/262] Add changelog --- changelog.d/5562.bugfix | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/5562.bugfix diff --git a/changelog.d/5562.bugfix b/changelog.d/5562.bugfix new file mode 100644 index 0000000000..02148e58fa --- /dev/null +++ b/changelog.d/5562.bugfix @@ -0,0 +1 @@ +Add loader in thread list \ No newline at end of file From 29c7ea11bd799ab1021131c88048bbcb654e5de3 Mon Sep 17 00:00:00 2001 From: cketti Date: Wed, 30 Mar 2022 15:38:40 +0200 Subject: [PATCH 173/262] Create extension function `Context.safeOpenOutputStream` --- .../main/java/im/vector/app/core/extensions/Context.kt | 9 +++++++++ .../im/vector/app/features/crypto/keys/KeysExporter.kt | 3 ++- .../keysbackup/setup/KeysBackupSetupStep3Fragment.kt | 3 ++- .../crypto/recover/BootstrapSaveRecoveryKeyFragment.kt | 3 ++- .../features/settings/devtools/KeyRequestsFragment.kt | 3 ++- .../vector/app/features/crypto/keys/KeysExporterTest.kt | 8 ++++---- .../test/java/im/vector/app/test/fakes/FakeContext.kt | 8 ++++---- 7 files changed, 25 insertions(+), 12 deletions(-) diff --git a/vector/src/main/java/im/vector/app/core/extensions/Context.kt b/vector/src/main/java/im/vector/app/core/extensions/Context.kt index b1e24c9502..0f785e43a3 100644 --- a/vector/src/main/java/im/vector/app/core/extensions/Context.kt +++ b/vector/src/main/java/im/vector/app/core/extensions/Context.kt @@ -18,6 +18,7 @@ package im.vector.app.core.extensions import android.content.Context import android.graphics.drawable.Drawable +import android.net.Uri import android.text.Spannable import android.text.SpannableString import android.text.style.ImageSpan @@ -31,6 +32,7 @@ import androidx.datastore.preferences.core.Preferences import dagger.hilt.EntryPoints import im.vector.app.core.datastore.dataStoreProvider import im.vector.app.core.di.SingletonEntryPoint +import java.io.OutputStream import kotlin.math.roundToInt fun Context.singletonEntryPoint(): SingletonEntryPoint { @@ -68,3 +70,10 @@ private fun Float.toAndroidAlpha(): Int { } val Context.dataStoreProvider: (String) -> DataStore by dataStoreProvider() + +/** + * Open Uri in truncate mode to make sure we don't partially overwrite content when we get passed a Uri to an existing file. + */ +fun Context.safeOpenOutputStream(uri: Uri): OutputStream? { + return contentResolver.openOutputStream(uri, "wt") +} diff --git a/vector/src/main/java/im/vector/app/features/crypto/keys/KeysExporter.kt b/vector/src/main/java/im/vector/app/features/crypto/keys/KeysExporter.kt index 27d8d4842a..f40f126d2c 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/keys/KeysExporter.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/keys/KeysExporter.kt @@ -19,6 +19,7 @@ package im.vector.app.features.crypto.keys import android.content.Context import android.net.Uri import im.vector.app.core.dispatchers.CoroutineDispatchers +import im.vector.app.core.extensions.safeOpenOutputStream import kotlinx.coroutines.withContext import org.matrix.android.sdk.api.session.Session import javax.inject.Inject @@ -34,7 +35,7 @@ class KeysExporter @Inject constructor( suspend fun export(password: String, uri: Uri) { withContext(dispatchers.io) { val data = session.cryptoService().exportRoomKeys(password) - context.contentResolver.openOutputStream(uri, "wt") + context.safeOpenOutputStream(uri) ?.use { it.write(data) } ?: throw IllegalStateException("Unable to open file for writing") verifyExportedKeysOutputFileSize(uri, expectedSize = data.size.toLong()) diff --git a/vector/src/main/java/im/vector/app/features/crypto/keysbackup/setup/KeysBackupSetupStep3Fragment.kt b/vector/src/main/java/im/vector/app/features/crypto/keysbackup/setup/KeysBackupSetupStep3Fragment.kt index 42ff4ac183..e5d7ade3ce 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/keysbackup/setup/KeysBackupSetupStep3Fragment.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/keysbackup/setup/KeysBackupSetupStep3Fragment.kt @@ -30,6 +30,7 @@ import com.google.android.material.bottomsheet.BottomSheetDialog import com.google.android.material.dialog.MaterialAlertDialogBuilder import im.vector.app.R import im.vector.app.core.extensions.registerStartForActivityResult +import im.vector.app.core.extensions.safeOpenOutputStream import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.core.utils.LiveEvent import im.vector.app.core.utils.copyToClipboard @@ -165,7 +166,7 @@ class KeysBackupSetupStep3Fragment @Inject constructor() : VectorBaseFragment os.write(data.toByteArray()) os.flush() diff --git a/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapSaveRecoveryKeyFragment.kt b/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapSaveRecoveryKeyFragment.kt index 746ed48c94..efabae2f3a 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapSaveRecoveryKeyFragment.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapSaveRecoveryKeyFragment.kt @@ -29,6 +29,7 @@ import com.airbnb.mvrx.parentFragmentViewModel import com.airbnb.mvrx.withState import im.vector.app.R import im.vector.app.core.extensions.registerStartForActivityResult +import im.vector.app.core.extensions.safeOpenOutputStream import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.core.resources.ColorProvider import im.vector.app.core.utils.startSharePlainTextIntent @@ -81,7 +82,7 @@ class BootstrapSaveRecoveryKeyFragment @Inject constructor( val uri = activityResult.data?.data ?: return@registerStartForActivityResult lifecycleScope.launch(Dispatchers.IO) { try { - sharedViewModel.handle(BootstrapActions.SaveKeyToUri(requireContext().contentResolver!!.openOutputStream(uri, "wt")!!)) + sharedViewModel.handle(BootstrapActions.SaveKeyToUri(requireContext().safeOpenOutputStream(uri)!!)) } catch (failure: Throwable) { sharedViewModel.handle(BootstrapActions.SaveReqFailed) } 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 6748fec1bc..cef68c01c1 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 @@ -34,6 +34,7 @@ import com.airbnb.mvrx.withState import com.google.android.material.tabs.TabLayoutMediator import im.vector.app.R import im.vector.app.core.extensions.registerStartForActivityResult +import im.vector.app.core.extensions.safeOpenOutputStream import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.core.utils.selectTxtFileToWrite import im.vector.app.databinding.FragmentDevtoolKeyrequestsBinding @@ -106,7 +107,7 @@ class KeyRequestsFragment @Inject constructor() : VectorBaseFragment { tryOrNull { - requireContext().contentResolver?.openOutputStream(it.uri, "wt") + requireContext().safeOpenOutputStream(it.uri) ?.use { os -> os.write(it.raw.toByteArray()) } } } diff --git a/vector/src/test/java/im/vector/app/features/crypto/keys/KeysExporterTest.kt b/vector/src/test/java/im/vector/app/features/crypto/keys/KeysExporterTest.kt index 3bec3fad88..3cd797a7b1 100644 --- a/vector/src/test/java/im/vector/app/features/crypto/keys/KeysExporterTest.kt +++ b/vector/src/test/java/im/vector/app/features/crypto/keys/KeysExporterTest.kt @@ -53,7 +53,7 @@ class KeysExporterTest { @Test fun `when exporting then writes exported keys to context output stream`() { givenFileDescriptorWithSize(size = A_ROOM_KEYS_EXPORT.size.toLong()) - val outputStream = context.givenOutputStreamFor(A_URI, mode = "wt") + val outputStream = context.givenSafeOutputStreamFor(A_URI) runTest { keysExporter.export(A_PASSWORD, A_URI) } @@ -63,7 +63,7 @@ class KeysExporterTest { @Test fun `given different file size returned for export when exporting then throws UnexpectedExportKeysFileSizeException`() { givenFileDescriptorWithSize(size = 110) - context.givenOutputStreamFor(A_URI, mode = "wt") + context.givenSafeOutputStreamFor(A_URI) assertFailsWith { runTest { keysExporter.export(A_PASSWORD, A_URI) } @@ -72,7 +72,7 @@ class KeysExporterTest { @Test fun `given output stream is unavailable for exporting to when exporting then throws IllegalStateException`() { - context.givenMissingOutputStreamFor(A_URI, mode = "wt") + context.givenMissingSafeOutputStreamFor(A_URI) assertFailsWith(message = "Unable to open file for writing") { runTest { keysExporter.export(A_PASSWORD, A_URI) } @@ -82,7 +82,7 @@ class KeysExporterTest { @Test fun `given exported file is missing after export when exporting then throws IllegalStateException`() { context.givenFileDescriptor(A_URI, mode = "r") { null } - context.givenOutputStreamFor(A_URI, mode = "wt") + context.givenSafeOutputStreamFor(A_URI) assertFailsWith(message = "Exported file not found") { runTest { keysExporter.export(A_PASSWORD, A_URI) } diff --git a/vector/src/test/java/im/vector/app/test/fakes/FakeContext.kt b/vector/src/test/java/im/vector/app/test/fakes/FakeContext.kt index de1a7956b8..2a50c34ca3 100644 --- a/vector/src/test/java/im/vector/app/test/fakes/FakeContext.kt +++ b/vector/src/test/java/im/vector/app/test/fakes/FakeContext.kt @@ -39,13 +39,13 @@ class FakeContext( every { contentResolver.openFileDescriptor(uri, mode, null) } returns fileDescriptor } - fun givenOutputStreamFor(uri: Uri, mode: String): OutputStream { + fun givenSafeOutputStreamFor(uri: Uri): OutputStream { val outputStream = mockk(relaxed = true) - every { contentResolver.openOutputStream(uri, mode) } returns outputStream + every { contentResolver.openOutputStream(uri, "wt") } returns outputStream return outputStream } - fun givenMissingOutputStreamFor(uri: Uri, mode: String) { - every { contentResolver.openOutputStream(uri, mode) } returns null + fun givenMissingSafeOutputStreamFor(uri: Uri) { + every { contentResolver.openOutputStream(uri, "wt") } returns null } } From 558a72e58fbb80dd3cab00824189858bf6adb55a Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 31 Mar 2022 09:58:15 +0200 Subject: [PATCH 174/262] Create a documentation about PR (#5401) Create documentation about our PR process --- CONTRIBUTING.md | 4 +- docs/pull_request.md | 236 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 239 insertions(+), 1 deletion(-) create mode 100644 docs/pull_request.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 2512052953..f3739be08d 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -2,7 +2,7 @@ Please read https://github.com/matrix-org/synapse/blob/master/CONTRIBUTING.md -Android support can be found in this [![Element Android Matrix room #element-android:matrix.org](https://img.shields.io/matrix/element-android:matrix.org.svg?label=%23element-android:matrix.org&logo=matrix&server_fqdn=matrix.org)](https://matrix.to/#/#element-android:matrix.org) room. +Element Android support can be found in this room: [![Element Android Matrix room #element-android:matrix.org](https://img.shields.io/matrix/element-android:matrix.org.svg?label=%23element-android:matrix.org&logo=matrix&server_fqdn=matrix.org)](https://matrix.to/#/#element-android:matrix.org). # Specific rules for Matrix Android projects @@ -44,6 +44,8 @@ If you want to fix an issue in other languages, or add a missing translation, or ## I want to submit a PR to fix an issue +Please have a look in the [dedicated documentation](./docs/pull_request.md) about pull request. + Please check if a corresponding issue exists. If yes, please let us know in a comment that you're working on it. If an issue does not exist yet, it may be relevant to open a new issue and let us know that you're implementing it. diff --git a/docs/pull_request.md b/docs/pull_request.md new file mode 100644 index 0000000000..b4dd0bd209 --- /dev/null +++ b/docs/pull_request.md @@ -0,0 +1,236 @@ +# Pull requests + +## Introduction + +This document gives some clue about how to efficiently manage Pull Requests (PR). This document is a first draft and may be improved later. + +## Who should read this document? + +Every pull request reviewers, but also probably every ones who submit PRs. + +## Submitting PR + +### Who can submit pull requests? + +Basically every one who wants to contribute to the project! But there are some rules to follow. + +#### Humans + +People with write access to the project can directly clone the project, push their branches and create PR. + +External contributors must first fork the project and create PR to the mainline from there. + +##### Draft PR? + +Draft PR can be created when the submitter does not expect the PR to be reviewed and merged yet. It can be useful to publicly show the work, or to do a self-review first. + +Draft PR can also be created when it depends on other un-merged PR. + +In any case, it is better to explicitly declare in the description why the PR is a draft PR. + +Also, draft PR should not stay indefinitely in this state. It may be removed if it is the case and the submitter does not update it after a few days. + +##### PR Review Assignment + +We use automatic assignment for PR reviews. A PR is automatically routed by GitHub to a team member using the round robin algorithm. The process is the following: + +- The PR creator assigns the [element-android](https://github.com/orgs/vector-im/teams/element-android) team as a reviewer. They can skip this process and assign directly a specific member if they think they should take a look at it. +- GitHub automatically assigns one reviewer. If the chosen reviewer is not available (holiday, etc.), remove them and set again the team, GitHub will select another reviewer. +- The reviewer gets a notification to make the review: they review the code following the good practice (see the rest of this document). +- After making their own review, if they feel not confident enough, they can ask another person for a full review, or they can tag someone within a PR comment to check specific lines. + +For PRs coming from the community, the issue wrangler can assign either the team [element-android](https://github.com/orgs/vector-im/teams/element-android) or any member directly. + +##### PR review time + +As a PR submitter, you deserve a quick review. As a reviewer, you should do your best to unblock others. + +Some tips to achieve it: + +- Set up your GH notifications correctly +- Check your pulls page: [https://github.com/pulls](https://github.com/pulls) +- Check your pending assigned PRs before starting or resuming your day to day tasks + +It is hard to define a deadline for a review. It depends on the PR size and the complexity. Let's start with a goal of 24h (working day!) for a PR smaller than 500 lines. If bigger, the submitter and the reviewer should discuss. + +After this time, the submitter can ping the reviewer to get a status of the review. + +##### Re-request PR review + +Once all the remarks have been handled, it's possible to re-request a review from the (same) reviewer to let them know that the PR has been updated the PR is ready to be reviewed again. Use the double arrow next to the reviewer name to do that. + +##### When create split PR? + +To implement big new feature, it may be efficient to split the work into several smaller and scoped PRs. They will be easier to review, and they can be merged on `develop` faster. + +Big PR can take time, and there is a risk of future merge conflict. + +Feature flag can be used to avoid half implemented feature to be available in the application. + +That said, splitting into several PRs should not have the side effect to have more review to do, for instance if some code is added, then finally removed. + +##### Avoid fixing other unrelated issue in a big PR + +Each PR should focus on a single task. If other issues may be fixed when working in the area of it, it's preferable to open a dedicated PR. + +It will have the advantage to be reviewed and merged faster, and not interfere with the main PR. + +It's also applicable for code rework (such as renaming for instance), or code formatting. Sometimes, it is more efficient to extract that work to a dedicated PR, and rebase your branch once this "rework" PR has been merged. + +#### Bots + +Some bots can create PR, but they still have to be reviewed by the team + +##### Dependabot + +Dependabot is a tool which maintain all our external dependencies up to date. A dedicated PR is created for each new available release for one of our external dependency.Dependabot + +To review such PR, you have to + - **IMPORTANT** check the diff files (as always). + - Check the release note. Some existing bugs in Element project may be fixed by the upgrade + - Make sure that the CI is happy + - If the code does not compile (API break for instance), you have to checkout the branch and push new commits + - Do some smoke test, depending of the library which has been upgraded + +For some reason dependabot sometimes does not upgrade some dependencies. In this case, and when detected, the upgrade has to be done manually. + +##### Gradle wrapper + +`Update Gradle Wrapper` is a tool which can create PR to upgrade our gradle.properties file. +Review such PR is the same recipe than for PR from Dependabot + +##### Sync analytics plan + +This tools imports any update in the analytics plan. See instruction in the PR itself to handle it. +More info can be found in the file [analytics.md] + +## Reviewing PR + +### Who can review pull requests? + +As an open source project, every one can review each others PR. Of course an approval from internal developer is mandatory for a PR to be merged. +But comment in PR from the community are always appreciated! + +### What to have in mind when reviewing a PR + +1. User experience: is the UX and UI correct? You will probably be the second person to test the new thing, the first one is the developer. +2. Developer experience: does the code look nice and decoupled? No big functions, new classes added to the right module, etc. +3. Code maintenance. A bit similar to point 2. Tricky code must be documented for instance +4. Fork consideration. Will configuration of forks be easy? Some documentation may help in some cases. +5. We are building long term products. "Quick and dirty" code must be avoided. +6. The PR includes new tests for the added code, updated test for the existing code +7. All PRs from external contributors **MUST** include a sign-off. It's in the checklist, and sometimes it's checked by the submitter, but there is actually no sign-off. In this case, ask nicely for a sign-off and request changes (do not approve the PR, even if everything else is fine). + +### Rules + +#### Check the form + +##### PR title + +PR title should describe in one line what's brought by the PR. Reviewer can edit the title if it's not clear enough, or to add suffix like `[BLOCKED]` or similar. Fixing typo is also a good practice, since GitHub search is quite not efficient, so the words must be spelled without any issue. Adding suffix will help when viewing the PR list. + +It's free form, but prefix tags could also be used to help understand what's in the PR. + +Examples of prefixes: +- `[Refacto]` +- `[Feature]` +- `[Bugfix]` +- etc. + +Also, it's still possible to add labels to the PRs, such as `A-` or `T-` labels, even if this is not a string requirement. We prefer to spend time to add labels on issues. + +##### PR description + +PR description should follow the PR template, and at least provide some context about the code change. + +##### File change + +1. Code should follow the guidelines +2. Code should be formatted correctly +3. XML attribute must be sorted +4. New code is added at the correct location +5. New classes are added to the correct location +6. Naming is correct. Naming is really important, it's considered part of the documentation +7. Architecture is followed. For instance, the logic is in the ViewModel and not in the Fragment +8. There is at least one file for the changelog. Exception if the PR fixes something which has not been released yet. Changelog content should target their audience: `.sdk` extension are mainly targeted for developers, other extensions are targeted for users and forks maintainers. It should generally describe visual change rather than give technical details. More details can be found [here](../CONTRIBUTING.md#changelog). +9. PR includes tests. allScreensTest when applicable, and unit tests +10. Avoid over complicating things. Keep it simple (KISS)! +11. PR contains only the expected change. Sometimes, the diff is showing changes that are already on `develop`. This is not good, submitter has to fix that up. + +##### Check the commit + +Commit message must be short, one line and valuable. "WIP" is not a good commit message. Commit message can contain issue number, starting with `#`. GitHub will add some link between the issue and such commit, which can be useful. It's possible to change a commit message at any time (may require a force push). + +Commit messages can contain extra lines with more details, links, etc. But keep in mind that those lines are quite less visible than the first line. + +Also commit history should be nice. Having commits like "Adding temporary code" then later "Removing temporary code" is not good. The branch has to be rebased and those commit have to be dropped. + +PR merger could decide to squash and merge if commit history is not good. + +Commit like "Code review fixes" is good when reviewing the PR, since new changes can be reviewed easily, but is less valuable when looking at git history. To avoid this, PR submitter should always push new commits after a review (no commit amend with force push), and when the PR is approved decide to interactive rebase the PR to improve the git history and reduce noise. + +##### Check the substance + +1. Test the changes! +2. Test the nominal case and the edge cases +3. Run the sanity test for critical PR + +##### Make a dedicated meeting to review the PR + +Sometimes a big PR can be hard to review. Setting up a call with the PR submitter can speed up the communication, rather than putting comments and questions in GitHub comments. It has the inconvenience of making the discussion non-public, consider including a summary of the main points of the "offline" conversation in the PR. + +### What happen to the issue(s)? + +The issue(s) should be referenced in the PR description using keywords like `Closes` of `Fixes` followed by the issue number. + +Example: +> Closes #1 + +Note that you have to repeat the keyword in case of a list of issue + +> Closes #1, Closes #2, etc. + +When PR will be merged, such referenced issue will be automatically closed. +It is up to the person who has merged the PR to go to the (closed) issue(s) and to add a comment to inform in which version the issue fix will be available. Use the current version of `develop` branch. + +> Closed in Element Android v1.x.y + +### Merge conflict + +It's up to the submitter to handle merge conflict. Sometimes, they can be fixed directly from GitHub, sometimes this is not possible. The branch can be rebased on `develop`, or the `develop` branch can be merged on the branch, it's up to the submitter to decide what is best. +Keep in mind that Github Actions are not run in case of conflict. + +### When and who can merge PR + +PR can be merged by the submitter, if and only if at least one approval from another developer is done. Approval from all people added as reviewer is also a good thing to have. Approval from design team may be mandatory, but is not sufficient to merge a PR. + +PR can also be merged by the reviewer, to reduce the time the PR is open. But only if the PR is not in draft and the change are quite small, or behind a feature flag. + +Dangerous PR should not be merged just before a release. Dangerous PR are PR that could break the app. Update of Realm library, rework in the chunk of Events management in the SDK, etc. + +We prefer to merge such PR after a release so that it can be tested during several days by the team before behind included in a release candidate. + +PR from bots will always be merged by the reviewer, right after approving the changes, or in case of critical changes, right after a release. + +#### Merge type + +Generally we use "Create a merge commit", which has the advantage to keep the branch visible. + +If git history is noisy (code added, then removed, etc.), it's possible to use "Squash and merge". But the branch will not be visible anymore, a commit will be added on top of develop. Git commit message can (and probably must) be edited from the GitHub web app. It's better if the submitter do the work to cleanup the git history by using a git interactive rebase of their branch. + +### Resolve conversation + +Generally we do not close conversation added during PR review and update by clicking on "Resolve conversation" +If the submitter or the reviewer do so, it will more difficult for further readers to see again the content. They will have to open the conversation to see it again. it's a waste of time. + +When remarks are handled, a small comment like "done" is enough, commit hash can also be added to the conversation. + +Exception: for big PRs with lots of conversations, using "Resolve conversation" may help to see the remaining remarks. + +Also "Resolve conversation" should probably be hit by the creator of the conversation. + +## Responsibility + +PR submitter is responsible of the incoming change. PR reviewers who approved the PR take a part of responsibility on the code which will land to develop, and then be used by our users, and the user of our forks. + +That said, bug may still be merged on `develop`, this is still acceptable of course. In this case, please make sure an issue is created and correctly labelled. Ideally, such issues should be fixed before the next release candidate, i.e. with a higher priority. But as we release the application every 10 working days, it can be hard to fix every bugs. That's why PR should be fully tested and reviewed before being merge and we should never comment code review remark with "will be handled later", or similar comments. \ No newline at end of file From 167fcb53664c284507d9373312ad42ef4e491523 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL Date: Thu, 31 Mar 2022 11:25:30 +0200 Subject: [PATCH 175/262] Directly use VectorFeatures inside Fragment --- .../app/features/location/LocationSharingFragment.kt | 6 ++++-- .../app/features/location/LocationSharingViewModel.kt | 9 --------- .../app/features/location/LocationSharingViewState.kt | 3 --- 3 files changed, 4 insertions(+), 14 deletions(-) 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 ea350b00c3..ab3bf9b933 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 @@ -36,6 +36,7 @@ import im.vector.app.core.utils.PERMISSIONS_FOR_FOREGROUND_LOCATION_SHARING import im.vector.app.core.utils.checkPermissions import im.vector.app.core.utils.registerForPermissionsResult import im.vector.app.databinding.FragmentLocationSharingBinding +import im.vector.app.features.VectorFeatures import im.vector.app.features.home.AvatarRenderer import im.vector.app.features.home.room.detail.timeline.helper.MatrixItemColorProvider import im.vector.app.features.location.option.LocationSharingOption @@ -49,7 +50,8 @@ import javax.inject.Inject class LocationSharingFragment @Inject constructor( private val urlMapProvider: UrlMapProvider, private val avatarRenderer: AvatarRenderer, - private val matrixItemColorProvider: MatrixItemColorProvider + private val matrixItemColorProvider: MatrixItemColorProvider, + private val vectorFeatures: VectorFeatures, ) : VectorBaseFragment(), LocationTargetChangeListener { private val viewModel: LocationSharingViewModel by fragmentViewModel() @@ -242,7 +244,7 @@ class LocationSharingFragment @Inject constructor( // first, update the options view val options: Set = when (state.areTargetAndUserLocationEqual) { true -> { - if (state.isLiveLocationSharingEnabled) { + if (vectorFeatures.isLiveLocationEnabled()) { setOf(LocationSharingOption.USER_CURRENT, LocationSharingOption.USER_LIVE) } else { setOf(LocationSharingOption.USER_CURRENT) 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 37ccd344b3..5f538dad67 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 @@ -24,7 +24,6 @@ import dagger.assisted.AssistedInject import im.vector.app.core.di.MavericksAssistedViewModelFactory import im.vector.app.core.di.hiltMavericksViewModelFactory import im.vector.app.core.platform.VectorViewModel -import im.vector.app.features.VectorFeatures import im.vector.app.features.home.room.detail.timeline.helper.LocationPinProvider import im.vector.app.features.location.domain.usecase.CompareLocationsUseCase import kotlinx.coroutines.flow.MutableSharedFlow @@ -50,7 +49,6 @@ class LocationSharingViewModel @AssistedInject constructor( private val locationPinProvider: LocationPinProvider, private val session: Session, private val compareLocationsUseCase: CompareLocationsUseCase, - private val vectorFeatures: VectorFeatures, ) : VectorViewModel(initialState), LocationTracker.Callback { private val room = session.getRoom(initialState.roomId)!! @@ -70,7 +68,6 @@ class LocationSharingViewModel @AssistedInject constructor( setUserItem() updatePin() compareTargetAndUserLocation() - checkVectorFeatures() } private fun setUserItem() { @@ -112,12 +109,6 @@ class LocationSharingViewModel @AssistedInject constructor( ?.let { userLocation -> compareLocationsUseCase.execute(userLocation, targetLocation) } } - private fun checkVectorFeatures() { - setState { - copy(isLiveLocationSharingEnabled = vectorFeatures.isLiveLocationEnabled()) - } - } - override fun onCleared() { super.onCleared() locationTracker.removeCallback(this) diff --git a/vector/src/main/java/im/vector/app/features/location/LocationSharingViewState.kt b/vector/src/main/java/im/vector/app/features/location/LocationSharingViewState.kt index 64039b00c4..64f324bc1b 100644 --- a/vector/src/main/java/im/vector/app/features/location/LocationSharingViewState.kt +++ b/vector/src/main/java/im/vector/app/features/location/LocationSharingViewState.kt @@ -19,7 +19,6 @@ package im.vector.app.features.location import android.graphics.drawable.Drawable import androidx.annotation.StringRes import com.airbnb.mvrx.MavericksState -import im.vector.app.BuildConfig import im.vector.app.R import org.matrix.android.sdk.api.extensions.orTrue import org.matrix.android.sdk.api.util.MatrixItem @@ -32,7 +31,6 @@ enum class LocationSharingMode(@StringRes val titleRes: Int) { data class LocationSharingViewState( val roomId: String, val mode: LocationSharingMode, - val isLiveLocationSharingEnabled: Boolean, val userItem: MatrixItem.UserItem? = null, val areTargetAndUserLocationEqual: Boolean? = null, val lastKnownUserLocation: LocationData? = null, @@ -42,7 +40,6 @@ data class LocationSharingViewState( constructor(locationSharingArgs: LocationSharingArgs) : this( roomId = locationSharingArgs.roomId, mode = locationSharingArgs.mode, - isLiveLocationSharingEnabled = BuildConfig.ENABLE_LIVE_LOCATION_SHARING ) } From fbbadc81385efb9bdaf734bef55d6185454a3f93 Mon Sep 17 00:00:00 2001 From: Onuray Sahin Date: Thu, 31 Mar 2022 13:45:46 +0300 Subject: [PATCH 176/262] Rebind location service when the previous sharing is stopped. --- .../vector/app/features/home/room/detail/TimelineViewModel.kt | 2 ++ 1 file changed, 2 insertions(+) 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 024c42e503..dc1df9a96e 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 @@ -1229,6 +1229,8 @@ class TimelineViewModel @AssistedInject constructor( override fun onLocationServiceStopped() { _viewEvents.post(RoomDetailViewEvents.HideLocationSharingIndicator) + // Bind again in case user decides to share live location without leaving the room + locationSharingServiceConnection.bind(this) } override fun onCleared() { From 20cdf2bef1a66eeb184b5ca8823fb09aa46d2c39 Mon Sep 17 00:00:00 2001 From: Onuray Sahin Date: Thu, 31 Mar 2022 13:47:18 +0300 Subject: [PATCH 177/262] Changelog added. --- changelog.d/5660.feature | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/5660.feature diff --git a/changelog.d/5660.feature b/changelog.d/5660.feature new file mode 100644 index 0000000000..a914762d50 --- /dev/null +++ b/changelog.d/5660.feature @@ -0,0 +1 @@ +Show a banner in timeline while location sharing service is running \ No newline at end of file From f8a909b014ba92f9073fa2e2391cfeb217315cb0 Mon Sep 17 00:00:00 2001 From: ariskotsomitopoulos Date: Thu, 31 Mar 2022 14:07:49 +0300 Subject: [PATCH 178/262] Enhance naming --- .../room/threads/list/viewmodel/ThreadListViewModel.kt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/home/room/threads/list/viewmodel/ThreadListViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/threads/list/viewmodel/ThreadListViewModel.kt index da09683b63..8840131f38 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/threads/list/viewmodel/ThreadListViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/threads/list/viewmodel/ThreadListViewModel.kt @@ -113,15 +113,15 @@ class ThreadListViewModel @AssistedInject constructor(@Assisted val initialState private fun fetchThreadList() { viewModelScope.launch { - isLoading(true) + setLoading(true) room?.fetchThreadSummaries() - isLoading(false) + setLoading(false) } } - private fun isLoading(show: Boolean) { + private fun setLoading(isLoading: Boolean) { setState { - copy(isLoading = show) + copy(isLoading = isLoading) } } From 21541642ba8eaa748bbd6273ac9789c9251a0560 Mon Sep 17 00:00:00 2001 From: ariskotsomitopoulos Date: Thu, 31 Mar 2022 14:35:37 +0300 Subject: [PATCH 179/262] Exclude NegativeMargin from linter --- vector/src/main/res/layout/fragment_thread_list.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/vector/src/main/res/layout/fragment_thread_list.xml b/vector/src/main/res/layout/fragment_thread_list.xml index f0f7dff611..7918aaf178 100644 --- a/vector/src/main/res/layout/fragment_thread_list.xml +++ b/vector/src/main/res/layout/fragment_thread_list.xml @@ -46,6 +46,7 @@ android:layout_gravity="center_vertical" android:indeterminate="true" android:visibility="gone" + tools:ignore="NegativeMargin" android:layout_marginTop="-6dp" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" From d448c62caddf7bcdb045e8e75a6c7516857379f5 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL Date: Wed, 30 Mar 2022 13:37:24 +0200 Subject: [PATCH 180/262] Adding changelog entry --- changelog.d/5667.feature | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/5667.feature diff --git a/changelog.d/5667.feature b/changelog.d/5667.feature new file mode 100644 index 0000000000..38e750112e --- /dev/null +++ b/changelog.d/5667.feature @@ -0,0 +1 @@ +Location sharing: adding possibility to choose duration of live sharing \ No newline at end of file From f34225506a64ba354766cf9c032f20900495712f Mon Sep 17 00:00:00 2001 From: Maxime NATUREL Date: Wed, 30 Mar 2022 13:46:08 +0200 Subject: [PATCH 181/262] Adding strings resources --- vector/src/main/res/values/strings.xml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml index 96c9541da7..be143e99bd 100644 --- a/vector/src/main/res/values/strings.xml +++ b/vector/src/main/res/values/strings.xml @@ -2959,6 +2959,10 @@ Share live location Share this location Share this location + Share your live location for + 15 minutes + 1 hour + 8 hours Allow access If you’d like to share your Live location, ${app_name} needs location access all the time when the app is in the background.\nWe will only access your location for the duration that you choose. ${app_name} could not access your location From d0a255819a2b5bca3ffa1638a08cba00f50a6fae Mon Sep 17 00:00:00 2001 From: Maxime NATUREL Date: Wed, 30 Mar 2022 14:34:56 +0200 Subject: [PATCH 182/262] Creating BottomSheet to choose the live duration --- .../location/LocationSharingFragment.kt | 7 ++- .../duration/ChooseLiveDurationBottomSheet.kt | 48 +++++++++++++++++++ ...et_choose_live_location_share_duration.xml | 18 +++++++ 3 files changed, 71 insertions(+), 2 deletions(-) create mode 100644 vector/src/main/java/im/vector/app/features/location/live/duration/ChooseLiveDurationBottomSheet.kt create mode 100644 vector/src/main/res/layout/bottom_sheet_choose_live_location_share_duration.xml 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 ab3bf9b933..62104aa8eb 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 @@ -39,6 +39,7 @@ import im.vector.app.databinding.FragmentLocationSharingBinding import im.vector.app.features.VectorFeatures import im.vector.app.features.home.AvatarRenderer import im.vector.app.features.home.room.detail.timeline.helper.MatrixItemColorProvider +import im.vector.app.features.location.live.duration.ChooseLiveDurationBottomSheet import im.vector.app.features.location.option.LocationSharingOption import org.matrix.android.sdk.api.util.MatrixItem import java.lang.ref.WeakReference @@ -236,8 +237,10 @@ class LocationSharingFragment @Inject constructor( private fun startLiveLocationSharing() { // TODO. Get duration from user - val duration = 30 * 1000L - viewModel.handle(LocationSharingAction.StartLiveLocationSharing(duration)) + ChooseLiveDurationBottomSheet.newInstance() + .show(requireActivity().supportFragmentManager, "DISPLAY_CHOOSE_DURATION_OPTIONS") + //val duration = 30 * 1000L + //viewModel.handle(LocationSharingAction.StartLiveLocationSharing(duration)) } private fun updateMap(state: LocationSharingViewState) { diff --git a/vector/src/main/java/im/vector/app/features/location/live/duration/ChooseLiveDurationBottomSheet.kt b/vector/src/main/java/im/vector/app/features/location/live/duration/ChooseLiveDurationBottomSheet.kt new file mode 100644 index 0000000000..2801d5f0a6 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/location/live/duration/ChooseLiveDurationBottomSheet.kt @@ -0,0 +1,48 @@ +/* + * Copyright 2019 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.duration + +import android.view.LayoutInflater +import android.view.ViewGroup +import dagger.hilt.android.AndroidEntryPoint +import im.vector.app.core.platform.VectorBaseBottomSheetDialogFragment +import im.vector.app.databinding.BottomSheetChooseLiveLocationShareDurationBinding +import im.vector.app.features.home.room.detail.timeline.action.MessageSharedActionViewModel + +/** + * Bottom sheet displaying list of options to choose the duration of the live sharing. + */ +@AndroidEntryPoint +class ChooseLiveDurationBottomSheet : + VectorBaseBottomSheetDialogFragment() { + + // TODO create interface callback to set the chosen duration + // TODO show same UI as in Figma + // TODO handle choice of user + + override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): BottomSheetChooseLiveLocationShareDurationBinding { + return BottomSheetChooseLiveLocationShareDurationBinding.inflate(inflater, container, false) + } + + // we are not using state for this one as it's static, so no need to override invalidate() + + companion object { + fun newInstance(): ChooseLiveDurationBottomSheet { + return ChooseLiveDurationBottomSheet() + } + } +} diff --git a/vector/src/main/res/layout/bottom_sheet_choose_live_location_share_duration.xml b/vector/src/main/res/layout/bottom_sheet_choose_live_location_share_duration.xml new file mode 100644 index 0000000000..675a63144f --- /dev/null +++ b/vector/src/main/res/layout/bottom_sheet_choose_live_location_share_duration.xml @@ -0,0 +1,18 @@ + + + + + + \ No newline at end of file From 4da11bbdc0ae0beceede01a1010728e796628d28 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL Date: Wed, 30 Mar 2022 14:41:12 +0200 Subject: [PATCH 183/262] Renaming duration parameter to precise the time unit --- .../vector/app/features/location/LocationSharingAction.kt | 2 +- .../vector/app/features/location/LocationSharingFragment.kt | 2 +- .../app/features/location/LocationSharingViewEvents.kt | 2 +- .../app/features/location/LocationSharingViewModel.kt | 6 +++--- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/location/LocationSharingAction.kt b/vector/src/main/java/im/vector/app/features/location/LocationSharingAction.kt index 4025fbefa8..d86d97e6b0 100644 --- a/vector/src/main/java/im/vector/app/features/location/LocationSharingAction.kt +++ b/vector/src/main/java/im/vector/app/features/location/LocationSharingAction.kt @@ -23,5 +23,5 @@ sealed class LocationSharingAction : VectorViewModelAction { data class PinnedLocationSharing(val locationData: LocationData?) : LocationSharingAction() data class LocationTargetChange(val locationData: LocationData) : LocationSharingAction() object ZoomToUserLocation : LocationSharingAction() - data class StartLiveLocationSharing(val duration: Long) : LocationSharingAction() + data class StartLiveLocationSharing(val durationMillis: Long) : LocationSharingAction() } 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 62104aa8eb..c4574d69db 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 @@ -182,7 +182,7 @@ class LocationSharingFragment @Inject constructor( } private fun handleStartLiveLocationService(event: LocationSharingViewEvents.StartLiveLocationService) { - val args = LocationSharingService.RoomArgs(event.sessionId, event.roomId, event.duration) + val args = LocationSharingService.RoomArgs(event.sessionId, event.roomId, event.durationMillis) Intent(requireContext(), LocationSharingService::class.java) .putExtra(LocationSharingService.EXTRA_ROOM_ARGS, args) diff --git a/vector/src/main/java/im/vector/app/features/location/LocationSharingViewEvents.kt b/vector/src/main/java/im/vector/app/features/location/LocationSharingViewEvents.kt index b25a4988b0..1116003e41 100644 --- a/vector/src/main/java/im/vector/app/features/location/LocationSharingViewEvents.kt +++ b/vector/src/main/java/im/vector/app/features/location/LocationSharingViewEvents.kt @@ -22,5 +22,5 @@ sealed class LocationSharingViewEvents : VectorViewEvents { object Close : LocationSharingViewEvents() object LocationNotAvailableError : LocationSharingViewEvents() data class ZoomToUserLocation(val userLocation: LocationData) : LocationSharingViewEvents() - data class StartLiveLocationService(val sessionId: String, val roomId: String, val duration: Long) : LocationSharingViewEvents() + data class StartLiveLocationService(val sessionId: String, val roomId: String, val durationMillis: Long) : LocationSharingViewEvents() } 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 5f538dad67..e67d5d0abb 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 @@ -120,7 +120,7 @@ class LocationSharingViewModel @AssistedInject constructor( is LocationSharingAction.PinnedLocationSharing -> handlePinnedLocationSharingAction(action) is LocationSharingAction.LocationTargetChange -> handleLocationTargetChangeAction(action) LocationSharingAction.ZoomToUserLocation -> handleZoomToUserLocationAction() - is LocationSharingAction.StartLiveLocationSharing -> handleStartLiveLocationSharingAction(action.duration) + is LocationSharingAction.StartLiveLocationSharing -> handleStartLiveLocationSharingAction(action.durationMillis) } } @@ -158,11 +158,11 @@ class LocationSharingViewModel @AssistedInject constructor( } } - private fun handleStartLiveLocationSharingAction(duration: Long) { + private fun handleStartLiveLocationSharingAction(durationMillis: Long) { _viewEvents.post(LocationSharingViewEvents.StartLiveLocationService( sessionId = session.sessionId, roomId = room.roomId, - duration = duration + durationMillis = durationMillis )) } From 5abc1965364d4e3cc8240fbe4dce30b924a4c106 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL Date: Wed, 30 Mar 2022 14:53:32 +0200 Subject: [PATCH 184/262] Callback interface for the choice of the duration --- .../location/LocationSharingFragment.kt | 13 ++++++++----- .../duration/ChooseLiveDurationBottomSheet.kt | 18 +++++++++++++++--- 2 files changed, 23 insertions(+), 8 deletions(-) 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 c4574d69db..3fdae43312 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 @@ -53,7 +53,9 @@ class LocationSharingFragment @Inject constructor( private val avatarRenderer: AvatarRenderer, private val matrixItemColorProvider: MatrixItemColorProvider, private val vectorFeatures: VectorFeatures, -) : VectorBaseFragment(), LocationTargetChangeListener { +) : VectorBaseFragment(), + LocationTargetChangeListener, + ChooseLiveDurationBottomSheet.DurationChoiceListener { private val viewModel: LocationSharingViewModel by fragmentViewModel() @@ -236,11 +238,12 @@ class LocationSharingFragment @Inject constructor( } private fun startLiveLocationSharing() { - // TODO. Get duration from user - ChooseLiveDurationBottomSheet.newInstance() + ChooseLiveDurationBottomSheet.newInstance(this) .show(requireActivity().supportFragmentManager, "DISPLAY_CHOOSE_DURATION_OPTIONS") - //val duration = 30 * 1000L - //viewModel.handle(LocationSharingAction.StartLiveLocationSharing(duration)) + } + + override fun onDurationChoice(durationMillis: Long) { + viewModel.handle(LocationSharingAction.StartLiveLocationSharing(durationMillis)) } private fun updateMap(state: LocationSharingViewState) { diff --git a/vector/src/main/java/im/vector/app/features/location/live/duration/ChooseLiveDurationBottomSheet.kt b/vector/src/main/java/im/vector/app/features/location/live/duration/ChooseLiveDurationBottomSheet.kt index 2801d5f0a6..59c9faddda 100644 --- a/vector/src/main/java/im/vector/app/features/location/live/duration/ChooseLiveDurationBottomSheet.kt +++ b/vector/src/main/java/im/vector/app/features/location/live/duration/ChooseLiveDurationBottomSheet.kt @@ -30,19 +30,31 @@ import im.vector.app.features.home.room.detail.timeline.action.MessageSharedActi class ChooseLiveDurationBottomSheet : VectorBaseBottomSheetDialogFragment() { - // TODO create interface callback to set the chosen duration // TODO show same UI as in Figma // TODO handle choice of user + var durationChoiceListener: DurationChoiceListener? = null + override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): BottomSheetChooseLiveLocationShareDurationBinding { return BottomSheetChooseLiveLocationShareDurationBinding.inflate(inflater, container, false) } + override fun onDestroyView() { + durationChoiceListener = null + super.onDestroyView() + } + // we are not using state for this one as it's static, so no need to override invalidate() companion object { - fun newInstance(): ChooseLiveDurationBottomSheet { - return ChooseLiveDurationBottomSheet() + fun newInstance(durationChoiceListener: DurationChoiceListener): ChooseLiveDurationBottomSheet { + val bottomSheet = ChooseLiveDurationBottomSheet() + bottomSheet.durationChoiceListener = durationChoiceListener + return bottomSheet } } + + interface DurationChoiceListener { + fun onDurationChoice(durationMillis: Long) + } } From c18a9230e552ce7f480e59ae02f31785672c8adb Mon Sep 17 00:00:00 2001 From: Maxime NATUREL Date: Wed, 30 Mar 2022 16:11:25 +0200 Subject: [PATCH 185/262] UI to select the duration --- .../duration/ChooseLiveDurationBottomSheet.kt | 44 ++++++++++++++- .../drawable/divider_horizontal_system.xml | 5 ++ ...et_choose_live_location_share_duration.xml | 56 +++++++++++++++++-- 3 files changed, 96 insertions(+), 9 deletions(-) create mode 100644 vector/src/main/res/drawable/divider_horizontal_system.xml diff --git a/vector/src/main/java/im/vector/app/features/location/live/duration/ChooseLiveDurationBottomSheet.kt b/vector/src/main/java/im/vector/app/features/location/live/duration/ChooseLiveDurationBottomSheet.kt index 59c9faddda..f0bee558cc 100644 --- a/vector/src/main/java/im/vector/app/features/location/live/duration/ChooseLiveDurationBottomSheet.kt +++ b/vector/src/main/java/im/vector/app/features/location/live/duration/ChooseLiveDurationBottomSheet.kt @@ -16,12 +16,29 @@ package im.vector.app.features.location.live.duration +import android.os.Bundle import android.view.LayoutInflater +import android.view.View import android.view.ViewGroup import dagger.hilt.android.AndroidEntryPoint +import im.vector.app.R import im.vector.app.core.platform.VectorBaseBottomSheetDialogFragment import im.vector.app.databinding.BottomSheetChooseLiveLocationShareDurationBinding -import im.vector.app.features.home.room.detail.timeline.action.MessageSharedActionViewModel + +/** + * 15 minutes. + */ +private const val DURATION_IN_MS_OPTION_1 = 15 * 60_000L + +/** + * 1 hour. + */ +private const val DURATION_IN_MS_OPTION_2 = 60 * 60_000L + +/** + * 8 hours. + */ +private const val DURATION_IN_MS_OPTION_3 = 8 * 60 * 60_000L /** * Bottom sheet displaying list of options to choose the duration of the live sharing. @@ -30,8 +47,7 @@ import im.vector.app.features.home.room.detail.timeline.action.MessageSharedActi class ChooseLiveDurationBottomSheet : VectorBaseBottomSheetDialogFragment() { - // TODO show same UI as in Figma - // TODO handle choice of user + // TODO fix text color problem of button in dqrk mode var durationChoiceListener: DurationChoiceListener? = null @@ -39,6 +55,11 @@ class ChooseLiveDurationBottomSheet : return BottomSheetChooseLiveLocationShareDurationBinding.inflate(inflater, container, false) } + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + initConfirmButton() + } + override fun onDestroyView() { durationChoiceListener = null super.onDestroyView() @@ -46,6 +67,23 @@ class ChooseLiveDurationBottomSheet : // we are not using state for this one as it's static, so no need to override invalidate() + private fun initConfirmButton() { + views.liveLocShareChooseDurationConfirm.setOnClickListener { + val currentChoice = getCurrentChoice() + durationChoiceListener?.onDurationChoice(currentChoice) + dismiss() + } + } + + private fun getCurrentChoice(): Long { + return when (views.liveLocShareChooseDurationOptions.checkedRadioButtonId) { + R.id.liveLocShareChooseDurationOption1 -> DURATION_IN_MS_OPTION_1 + R.id.liveLocShareChooseDurationOption2 -> DURATION_IN_MS_OPTION_2 + R.id.liveLocShareChooseDurationOption3 -> DURATION_IN_MS_OPTION_3 + else -> DURATION_IN_MS_OPTION_1 + } + } + companion object { fun newInstance(durationChoiceListener: DurationChoiceListener): ChooseLiveDurationBottomSheet { val bottomSheet = ChooseLiveDurationBottomSheet() diff --git a/vector/src/main/res/drawable/divider_horizontal_system.xml b/vector/src/main/res/drawable/divider_horizontal_system.xml new file mode 100644 index 0000000000..df847a4feb --- /dev/null +++ b/vector/src/main/res/drawable/divider_horizontal_system.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/vector/src/main/res/layout/bottom_sheet_choose_live_location_share_duration.xml b/vector/src/main/res/layout/bottom_sheet_choose_live_location_share_duration.xml index 675a63144f..3336c0bac0 100644 --- a/vector/src/main/res/layout/bottom_sheet_choose_live_location_share_duration.xml +++ b/vector/src/main/res/layout/bottom_sheet_choose_live_location_share_duration.xml @@ -2,17 +2,61 @@ + android:paddingHorizontal="15dp" + android:paddingVertical="24dp" + android:text="@string/location_share_live_select_duration_title" + android:textColor="?vctr_content_primary" /> + + + + + + + + + + +