From 52c57d4d8e61343f0c7fb91a0f0dff475d38d500 Mon Sep 17 00:00:00 2001 From: Valere Date: Wed, 8 Jan 2025 10:38:55 +0100 Subject: [PATCH 01/19] feature(crypto): verification violation handling and block sending --- .../crypto/identity/IdentityChangeEvent.kt | 3 +- .../identity/IdentityChangeStatePresenter.kt | 18 +++- .../identity/IdentityChangeStateProvider.kt | 2 +- .../identity/IdentityChangeStateView.kt | 40 ++++++-- .../messagecomposer/DisabledComposerView.kt | 97 +++++++++++++++++++ .../MessageComposerPresenter.kt | 47 +++++++++ .../messagecomposer/MessageComposerState.kt | 2 + .../MessageComposerStateProvider.kt | 3 + .../messagecomposer/MessageComposerView.kt | 10 ++ .../atomic/molecules/ComposerAlertMolecule.kt | 6 +- .../api/encryption/EncryptionService.kt | 1 + .../impl/encryption/RustEncryptionService.kt | 4 + .../test/encryption/FakeEncryptionService.kt | 5 + 13 files changed, 224 insertions(+), 14 deletions(-) create mode 100644 features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/DisabledComposerView.kt diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/crypto/identity/IdentityChangeEvent.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/crypto/identity/IdentityChangeEvent.kt index e74004c1d02..bc11f29df35 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/crypto/identity/IdentityChangeEvent.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/crypto/identity/IdentityChangeEvent.kt @@ -10,5 +10,6 @@ package io.element.android.features.messages.impl.crypto.identity import io.element.android.libraries.matrix.api.core.UserId sealed interface IdentityChangeEvent { - data class Submit(val userId: UserId) : IdentityChangeEvent + data class PinViolation(val userId: UserId) : IdentityChangeEvent + data class VerificationViolation(val userId: UserId) : IdentityChangeEvent } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/crypto/identity/IdentityChangeStatePresenter.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/crypto/identity/IdentityChangeStatePresenter.kt index adc39fbc746..2b410911689 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/crypto/identity/IdentityChangeStatePresenter.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/crypto/identity/IdentityChangeStatePresenter.kt @@ -49,7 +49,12 @@ class IdentityChangeStatePresenter @Inject constructor( fun handleEvent(event: IdentityChangeEvent) { when (event) { - is IdentityChangeEvent.Submit -> coroutineScope.pinUserIdentity(event.userId) + is IdentityChangeEvent.VerificationViolation -> { + coroutineScope.withdrawVerificationRequirement(event.userId) + } + is IdentityChangeEvent.PinViolation -> { + coroutineScope.pinUserIdentity(event.userId) + } } } @@ -94,15 +99,22 @@ class IdentityChangeStatePresenter @Inject constructor( Timber.e(it, "Failed to pin identity for user $userId") } } + + private fun CoroutineScope.withdrawVerificationRequirement(userId: UserId) = launch { + encryptionService.withdrawVerificationRequirement(userId) + .onFailure { + Timber.e(it, "Failed to withdraw verification for user $userId") + } + } } -private fun RoomMember.toIdentityRoomMember() = IdentityRoomMember( +fun RoomMember.toIdentityRoomMember() = IdentityRoomMember( userId = userId, displayNameOrDefault = displayNameOrDefault, avatarData = getAvatarData(AvatarSize.ComposerAlert), ) -private fun createDefaultRoomMemberForIdentityChange(userId: UserId) = IdentityRoomMember( +fun createDefaultRoomMemberForIdentityChange(userId: UserId) = IdentityRoomMember( userId = userId, displayNameOrDefault = userId.extractedDisplayName, avatarData = AvatarData( diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/crypto/identity/IdentityChangeStateProvider.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/crypto/identity/IdentityChangeStateProvider.kt index 5ec38588091..d381d17de90 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/crypto/identity/IdentityChangeStateProvider.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/crypto/identity/IdentityChangeStateProvider.kt @@ -30,7 +30,7 @@ class IdentityChangeStateProvider : PreviewParameterProvider>.observeRoomMemberIdentityStateChange() { + room.syncUpdateFlow + .filter { + // Room cannot become unencrypted, so we can just apply a filter here. + room.isEncrypted + } + .distinctUntilChanged() + .flatMapLatest { + combine(room.identityStateChangesFlow, room.membersStateFlow) { identityStateChanges, membersState -> + identityStateChanges.map { identityStateChange -> + val member = membersState.roomMembers() + ?.firstOrNull { roomMember -> roomMember.userId == identityStateChange.userId } + ?.toIdentityRoomMember() + ?: createDefaultRoomMemberForIdentityChange(identityStateChange.userId) + RoomMemberIdentityStateChange( + identityRoomMember = member, + identityState = identityStateChange.identityState, + ) + } + } + .distinctUntilChanged() + .onEach { roomMemberIdentityStateChanges -> + value = roomMemberIdentityStateChanges.toPersistentList() + } + } + .launchIn(this) + } } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerState.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerState.kt index d86ba8d98c4..4bb0f7f6fd6 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerState.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerState.kt @@ -8,6 +8,7 @@ package io.element.android.features.messages.impl.messagecomposer import androidx.compose.runtime.Stable +import io.element.android.features.messages.impl.crypto.identity.RoomMemberIdentityStateChange import io.element.android.libraries.textcomposer.mentions.ResolvedSuggestion import io.element.android.libraries.textcomposer.model.MessageComposerMode import io.element.android.libraries.textcomposer.model.TextEditorState @@ -24,6 +25,7 @@ data class MessageComposerState( val canShareLocation: Boolean, val canCreatePoll: Boolean, val suggestions: ImmutableList, + val roomMemberIdentityStateChanges: ImmutableList, val resolveMentionDisplay: (String, String) -> TextDisplay, val eventSink: (MessageComposerEvents) -> Unit, ) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerStateProvider.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerStateProvider.kt index c0ea895618e..4e0a6df5c9b 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerStateProvider.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerStateProvider.kt @@ -8,6 +8,7 @@ package io.element.android.features.messages.impl.messagecomposer import androidx.compose.ui.tooling.preview.PreviewParameterProvider +import io.element.android.features.messages.impl.crypto.identity.RoomMemberIdentityStateChange import io.element.android.libraries.textcomposer.mentions.ResolvedSuggestion import io.element.android.libraries.textcomposer.model.MessageComposerMode import io.element.android.libraries.textcomposer.model.TextEditorState @@ -32,6 +33,7 @@ fun aMessageComposerState( canShareLocation: Boolean = true, canCreatePoll: Boolean = true, suggestions: ImmutableList = persistentListOf(), + identityStates: ImmutableList = persistentListOf(), eventSink: (MessageComposerEvents) -> Unit = {}, ) = MessageComposerState( textEditorState = textEditorState, @@ -42,6 +44,7 @@ fun aMessageComposerState( canShareLocation = canShareLocation, canCreatePoll = canCreatePoll, suggestions = suggestions, + roomMemberIdentityStateChanges = identityStates, resolveMentionDisplay = { _, _ -> TextDisplay.Plain }, eventSink = eventSink, ) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerView.kt index 86e30a22c04..09289305b25 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerView.kt @@ -23,6 +23,7 @@ import io.element.android.features.messages.impl.voicemessages.composer.VoiceMes import io.element.android.features.messages.impl.voicemessages.composer.aVoiceMessageComposerState import io.element.android.libraries.designsystem.preview.ElementPreview import io.element.android.libraries.designsystem.preview.PreviewsDayNight +import io.element.android.libraries.matrix.api.encryption.identity.IdentityState import io.element.android.libraries.textcomposer.TextComposer import io.element.android.libraries.textcomposer.model.Suggestion import io.element.android.libraries.textcomposer.model.VoiceMessagePlayerEvent @@ -37,6 +38,14 @@ internal fun MessageComposerView( enableVoiceMessages: Boolean, modifier: Modifier = Modifier, ) { + val verificationViolation = state.roomMemberIdentityStateChanges.firstOrNull { + it.identityState == IdentityState.VerificationViolation + } + if (verificationViolation != null) { + DisabledComposerView(modifier = modifier) + return + } + val view = LocalView.current fun sendMessage() { state.eventSink(MessageComposerEvents.SendMessage) @@ -139,6 +148,7 @@ internal fun MessageComposerViewPreview( enableVoiceMessages = true, subcomposing = false, ) + DisabledComposerView() } } diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/molecules/ComposerAlertMolecule.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/molecules/ComposerAlertMolecule.kt index 30944ead690..b85e31c5140 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/molecules/ComposerAlertMolecule.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/molecules/ComposerAlertMolecule.kt @@ -78,7 +78,11 @@ fun ComposerAlertMolecule( text = content, modifier = Modifier.weight(1f), style = ElementTheme.typography.fontBodyMdRegular, - color = ElementTheme.colors.textPrimary, + color = if (isCritical) { + ElementTheme.colors.textCriticalPrimary + } else { + ElementTheme.colors.textPrimary + }, textAlign = TextAlign.Start, ) } diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/encryption/EncryptionService.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/encryption/EncryptionService.kt index bc009d3ee89..5b2703bf255 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/encryption/EncryptionService.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/encryption/EncryptionService.kt @@ -66,6 +66,7 @@ interface EncryptionService { * Remember this identity, ensuring it does not result in a pin violation. */ suspend fun pinUserIdentity(userId: UserId): Result + suspend fun withdrawVerificationRequirement(userId: UserId): Result } /** diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/encryption/RustEncryptionService.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/encryption/RustEncryptionService.kt index 3e0e6571d4c..3c3228ceb9f 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/encryption/RustEncryptionService.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/encryption/RustEncryptionService.kt @@ -209,6 +209,10 @@ internal class RustEncryptionService( getUserIdentity(userId).pin() } + override suspend fun withdrawVerificationRequirement(userId: UserId): Result = runCatching { + getUserIdentity(userId).withdrawVerification() + } + private suspend fun getUserIdentity(userId: UserId): UserIdentity { return service.userIdentity( userId = userId.value, diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/encryption/FakeEncryptionService.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/encryption/FakeEncryptionService.kt index ee126e2f92d..0be0434d381 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/encryption/FakeEncryptionService.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/encryption/FakeEncryptionService.kt @@ -24,6 +24,7 @@ class FakeEncryptionService( var startIdentityResetLambda: () -> Result = { lambdaError() }, private val pinUserIdentityResult: (UserId) -> Result = { lambdaError() }, private val isUserVerifiedResult: (UserId) -> Result = { lambdaError() }, + private val withdrawVerificationResult: (UserId) -> Result = { lambdaError() }, ) : EncryptionService { private var disableRecoveryFailure: Exception? = null override val backupStateStateFlow: MutableStateFlow = MutableStateFlow(BackupState.UNKNOWN) @@ -124,6 +125,10 @@ class FakeEncryptionService( return pinUserIdentityResult(userId) } + override suspend fun withdrawVerificationRequirement(userId: UserId): Result { + return withdrawVerificationResult(userId) + } + override suspend fun isUserVerified(userId: UserId): Result = simulateLongTask { isUserVerifiedResult(userId) } From e5c99e778bd501e3821b69a1c08b977516274bcb Mon Sep 17 00:00:00 2001 From: Valere Date: Mon, 20 Jan 2025 08:56:49 +0100 Subject: [PATCH 02/19] Quick test for verification violation event --- .../IdentityChangeStatePresenterTest.kt | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/crypto/identity/IdentityChangeStatePresenterTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/crypto/identity/IdentityChangeStatePresenterTest.kt index 115e14c82c2..04153cd8276 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/crypto/identity/IdentityChangeStatePresenterTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/crypto/identity/IdentityChangeStatePresenterTest.kt @@ -143,7 +143,22 @@ class IdentityChangeStatePresenterTest { val presenter = createIdentityChangeStatePresenter(encryptionService = encryptionService) presenter.test { val initialState = awaitItem() - initialState.eventSink(IdentityChangeEvent.Submit(A_USER_ID)) + initialState.eventSink(IdentityChangeEvent.PinViolation(A_USER_ID)) + lambda.assertions().isCalledOnce().with(value(A_USER_ID)) + } + } + + @Test + fun `present - when the user withdraw the identity, the presenter invokes the encryption service api`() = + runTest { + val lambda = lambdaRecorder> { Result.success(Unit) } + val encryptionService = FakeEncryptionService( + withdrawVerificationResult = lambda, + ) + val presenter = createIdentityChangeStatePresenter(encryptionService = encryptionService) + presenter.test { + val initialState = awaitItem() + initialState.eventSink(IdentityChangeEvent.VerificationViolation(A_USER_ID)) lambda.assertions().isCalledOnce().with(value(A_USER_ID)) } } From afd74af9a2a42d5d1917321360d7550476998986 Mon Sep 17 00:00:00 2001 From: ElementBot Date: Mon, 20 Jan 2025 09:05:19 +0000 Subject: [PATCH 03/19] Update screenshots --- ....impl.crypto.identity_IdentityChangeStateView_Day_2_en.png | 4 ++-- ...mpl.crypto.identity_IdentityChangeStateView_Night_2_en.png | 4 ++-- ...rypto.identity_MessagesViewWithIdentityChange_Day_2_en.png | 4 ++-- ...pto.identity_MessagesViewWithIdentityChange_Night_2_en.png | 4 ++-- ...sagecomposer_DisabledMessageComposerViewVoice_Day_0_en.png | 3 +++ ...gecomposer_DisabledMessageComposerViewVoice_Night_0_en.png | 3 +++ ...ages.impl.messagecomposer_MessageComposerView_Day_0_en.png | 4 ++-- ...es.impl.messagecomposer_MessageComposerView_Night_0_en.png | 4 ++-- ...system.atomic.molecules_ComposerAlertMolecule_Day_0_en.png | 4 ++-- ...stem.atomic.molecules_ComposerAlertMolecule_Night_0_en.png | 4 ++-- 10 files changed, 22 insertions(+), 16 deletions(-) create mode 100644 tests/uitests/src/test/snapshots/images/features.messages.impl.messagecomposer_DisabledMessageComposerViewVoice_Day_0_en.png create mode 100644 tests/uitests/src/test/snapshots/images/features.messages.impl.messagecomposer_DisabledMessageComposerViewVoice_Night_0_en.png diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.crypto.identity_IdentityChangeStateView_Day_2_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.crypto.identity_IdentityChangeStateView_Day_2_en.png index 02192807520..d2e968638ae 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.crypto.identity_IdentityChangeStateView_Day_2_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.crypto.identity_IdentityChangeStateView_Day_2_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3ffda5a15b4c2a56009940901b0f5b102536efbc77a540c9814c4e644618509e -size 25186 +oid sha256:0bceb89945fbdc84bfd2778d5061b61b5602a147c8c4325f1893b9a2e5dd411e +size 27588 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.crypto.identity_IdentityChangeStateView_Night_2_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.crypto.identity_IdentityChangeStateView_Night_2_en.png index 234e7f99d2a..aa095fb225c 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.crypto.identity_IdentityChangeStateView_Night_2_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.crypto.identity_IdentityChangeStateView_Night_2_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9370c42c42cbb9878244c4a1318d7e59725afc90ab1bca818a2a2619a41a6479 -size 27931 +oid sha256:b098eebc72bc5388453bc7a8ba46e62fb64b994b5240dda4bf1f59161362e97a +size 29408 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.crypto.identity_MessagesViewWithIdentityChange_Day_2_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.crypto.identity_MessagesViewWithIdentityChange_Day_2_en.png index 73b3cec7113..857344e8d9d 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.crypto.identity_MessagesViewWithIdentityChange_Day_2_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.crypto.identity_MessagesViewWithIdentityChange_Day_2_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ab8c08410091863015fdf8e9cfeaf4259f470b500d7901d3dffff83483fb53a3 -size 65168 +oid sha256:62baf6570e7b118a943efd1aa7482d865a3984646cc41afa28a8dccd6246b580 +size 68313 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.crypto.identity_MessagesViewWithIdentityChange_Night_2_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.crypto.identity_MessagesViewWithIdentityChange_Night_2_en.png index 6c06c9b772f..d87569a76c7 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.crypto.identity_MessagesViewWithIdentityChange_Night_2_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.crypto.identity_MessagesViewWithIdentityChange_Night_2_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ee7cdf903b25290a383c19ffa8f55759449469019f4b387366a0c538d59cc4b8 -size 69210 +oid sha256:66343e5a2a84a1caf4d4606ec3985866be85ee4d4a0b02d8fd374364c561f411 +size 70575 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.messagecomposer_DisabledMessageComposerViewVoice_Day_0_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.messagecomposer_DisabledMessageComposerViewVoice_Day_0_en.png new file mode 100644 index 00000000000..803daa3cfd1 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.messagecomposer_DisabledMessageComposerViewVoice_Day_0_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1c3e38a6fa5e7434327e0877e6d65258086ac1db5dc50bb2db8d3284d307112e +size 6623 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.messagecomposer_DisabledMessageComposerViewVoice_Night_0_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.messagecomposer_DisabledMessageComposerViewVoice_Night_0_en.png new file mode 100644 index 00000000000..1bbbb4439f7 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.messagecomposer_DisabledMessageComposerViewVoice_Night_0_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:295c2fcc181add02418415981ba2cab1aa36142b83d6a1b7e7610dcf8c3c6dee +size 6339 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.messagecomposer_MessageComposerView_Day_0_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.messagecomposer_MessageComposerView_Day_0_en.png index 37713151e1a..b9c1c8d3881 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.messagecomposer_MessageComposerView_Day_0_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.messagecomposer_MessageComposerView_Day_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:409936daa72c7e70334f2ff5d62e84e41b5b79277fefb064b4a9183bd3925549 -size 16357 +oid sha256:8ec042b23b502e5becc57c6b804a809bf374dfc82c4638c29d8db6f3f0280d58 +size 19404 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.messagecomposer_MessageComposerView_Night_0_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.messagecomposer_MessageComposerView_Night_0_en.png index dc1fac59118..207ea5363d2 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.messagecomposer_MessageComposerView_Night_0_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.messagecomposer_MessageComposerView_Night_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:99c74f986295bd66f52346723a562a8d87b4eca9bda021220e8e916fecc86784 -size 15106 +oid sha256:2e2ee0b51352553c414211af666823dda94497c0bd229c9439f729070f1ec1d5 +size 17878 diff --git a/tests/uitests/src/test/snapshots/images/libraries.designsystem.atomic.molecules_ComposerAlertMolecule_Day_0_en.png b/tests/uitests/src/test/snapshots/images/libraries.designsystem.atomic.molecules_ComposerAlertMolecule_Day_0_en.png index efa4a70bf38..157f3e59e23 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.designsystem.atomic.molecules_ComposerAlertMolecule_Day_0_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.designsystem.atomic.molecules_ComposerAlertMolecule_Day_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:267bd9c09da1732222fc833e302b06c2107a2964524e8f98f5fdd7d2884543a5 -size 20808 +oid sha256:ff4fbcc142dac5a501eabb228009663eed497a2d30d8e29e0fe68b462a82997d +size 20369 diff --git a/tests/uitests/src/test/snapshots/images/libraries.designsystem.atomic.molecules_ComposerAlertMolecule_Night_0_en.png b/tests/uitests/src/test/snapshots/images/libraries.designsystem.atomic.molecules_ComposerAlertMolecule_Night_0_en.png index 311ce5e56b9..5338f9a2de3 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.designsystem.atomic.molecules_ComposerAlertMolecule_Night_0_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.designsystem.atomic.molecules_ComposerAlertMolecule_Night_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:35499aea0d8741002ee546553ac0ec8fee64abeb93db8913e585804ed28539fc -size 23451 +oid sha256:570d33be676b5a603f13c1beb364d41e8c19a6d1c501c3082cc197cd26344d4e +size 22546 From d3a6ab696a2c519c09a19264363c7549ca3ab001 Mon Sep 17 00:00:00 2001 From: Valere Date: Mon, 20 Jan 2025 12:07:32 +0100 Subject: [PATCH 04/19] Fix konsist warnings --- .../messages/impl/messagecomposer/DisabledComposerView.kt | 6 +++--- ...essagecomposer_DisabledComposerViewPreview_Day_0_en.png} | 0 ...sagecomposer_DisabledComposerViewPreview_Night_0_en.png} | 0 3 files changed, 3 insertions(+), 3 deletions(-) rename tests/uitests/src/test/snapshots/images/{features.messages.impl.messagecomposer_DisabledMessageComposerViewVoice_Day_0_en.png => features.messages.impl.messagecomposer_DisabledComposerViewPreview_Day_0_en.png} (100%) rename tests/uitests/src/test/snapshots/images/{features.messages.impl.messagecomposer_DisabledMessageComposerViewVoice_Night_0_en.png => features.messages.impl.messagecomposer_DisabledComposerViewPreview_Night_0_en.png} (100%) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/DisabledComposerView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/DisabledComposerView.kt index fc4697e6b60..94ccf375e7b 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/DisabledComposerView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/DisabledComposerView.kt @@ -1,8 +1,8 @@ /* * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only - * Please see LICENSE in the repository root for full details. + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * Please see LICENSE files in the repository root for full details. */ package io.element.android.features.messages.impl.messagecomposer @@ -88,7 +88,7 @@ internal fun DisabledComposerView( @PreviewsDayNight @Composable -internal fun DisabledMessageComposerViewVoicePreview() = ElementPreview { +internal fun DisabledComposerViewPreview() = ElementPreview { Column { DisabledComposerView( modifier = Modifier.height(IntrinsicSize.Min), diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.messagecomposer_DisabledMessageComposerViewVoice_Day_0_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.messagecomposer_DisabledComposerViewPreview_Day_0_en.png similarity index 100% rename from tests/uitests/src/test/snapshots/images/features.messages.impl.messagecomposer_DisabledMessageComposerViewVoice_Day_0_en.png rename to tests/uitests/src/test/snapshots/images/features.messages.impl.messagecomposer_DisabledComposerViewPreview_Day_0_en.png diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.messagecomposer_DisabledMessageComposerViewVoice_Night_0_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.messagecomposer_DisabledComposerViewPreview_Night_0_en.png similarity index 100% rename from tests/uitests/src/test/snapshots/images/features.messages.impl.messagecomposer_DisabledMessageComposerViewVoice_Night_0_en.png rename to tests/uitests/src/test/snapshots/images/features.messages.impl.messagecomposer_DisabledComposerViewPreview_Night_0_en.png From eb1375279ceeab45d6658334e8b01fc1d9291954 Mon Sep 17 00:00:00 2001 From: Valere Date: Mon, 20 Jan 2025 14:54:13 +0100 Subject: [PATCH 05/19] test: Add new IdentityChangeStateViewTest --- .../identity/IdentityChangeStateProvider.kt | 3 +- .../identity/IdentityChangeStateViewTest.kt | 107 ++++++++++++++++++ 2 files changed, 109 insertions(+), 1 deletion(-) create mode 100644 features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/crypto/identity/IdentityChangeStateViewTest.kt diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/crypto/identity/IdentityChangeStateProvider.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/crypto/identity/IdentityChangeStateProvider.kt index d381d17de90..50ca0d7e3ab 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/crypto/identity/IdentityChangeStateProvider.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/crypto/identity/IdentityChangeStateProvider.kt @@ -47,9 +47,10 @@ internal fun aRoomMemberIdentityStateChange( internal fun anIdentityChangeState( roomMemberIdentityStateChanges: List = emptyList(), + eventSink: (IdentityChangeEvent) -> Unit = {} ) = IdentityChangeState( roomMemberIdentityStateChanges = roomMemberIdentityStateChanges.toImmutableList(), - eventSink = {}, + eventSink, ) internal fun anIdentityRoomMember( diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/crypto/identity/IdentityChangeStateViewTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/crypto/identity/IdentityChangeStateViewTest.kt new file mode 100644 index 00000000000..3642fcd3c9d --- /dev/null +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/crypto/identity/IdentityChangeStateViewTest.kt @@ -0,0 +1,107 @@ +/* + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.features.messages.impl.crypto.identity + +import androidx.activity.ComponentActivity +import androidx.compose.ui.test.junit4.AndroidComposeTestRule +import androidx.compose.ui.test.junit4.createAndroidComposeRule +import androidx.compose.ui.test.onNodeWithText +import androidx.test.ext.junit.runners.AndroidJUnit4 +import io.element.android.libraries.designsystem.components.avatar.anAvatarData +import io.element.android.libraries.matrix.api.core.UserId +import io.element.android.libraries.matrix.api.encryption.identity.IdentityState +import io.element.android.libraries.ui.strings.CommonStrings +import io.element.android.tests.testutils.EventsRecorder +import io.element.android.tests.testutils.clickOn +import org.junit.Rule +import org.junit.Test +import org.junit.rules.TestRule +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class IdentityChangeStateViewTest { + @get:Rule val rule = createAndroidComposeRule() + + @Test + fun `show and resolve pin violation`() { + val eventsRecorder = EventsRecorder() + rule.setIdentityChangeStateView( + state = anIdentityChangeState( + listOf( + RoomMemberIdentityStateChange( + identityRoomMember = IdentityRoomMember(UserId("@alice:localhost"), "Alice", anAvatarData()), + identityState = IdentityState.PinViolation + ) + ), + eventsRecorder + ), + ) + + rule.onNodeWithText("identity appears to have changed", substring = true).assertExists("should display pin violation warning") + rule.onNodeWithText("@alice:localhost", substring = true).assertExists("should display user mxid") + rule.onNodeWithText("Alice", substring = true).assertExists("should display user displayname") + + rule.clickOn(res = CommonStrings.action_ok) + eventsRecorder.assertSingle(IdentityChangeEvent.PinViolation(UserId("@alice:localhost"))) + } + + @Test + fun `show and resolve verification violation`() { + val eventsRecorder = EventsRecorder() + rule.setIdentityChangeStateView( + state = anIdentityChangeState( + listOf( + RoomMemberIdentityStateChange( + identityRoomMember = IdentityRoomMember(UserId("@alice:localhost"), "Alice", anAvatarData()), + identityState = IdentityState.VerificationViolation + ) + ), + eventsRecorder + ), + ) + + rule.onNodeWithText("verified identity has changed", substring = true).assertExists("should display verification violation warning") + rule.onNodeWithText("@alice:localhost", substring = true).assertExists("should display user mxid") + rule.onNodeWithText("Alice", substring = true).assertExists("should display user displayname") + + rule.clickOn(res = CommonStrings.crypto_identity_change_withdraw_verification_action) + eventsRecorder.assertSingle(IdentityChangeEvent.VerificationViolation(UserId("@alice:localhost"))) + } + + @Test + fun `Should not show any banner if no violations`() { + rule.setIdentityChangeStateView( + state = anIdentityChangeState( + listOf( + RoomMemberIdentityStateChange( + identityRoomMember = IdentityRoomMember(UserId("@alice:localhost"), "Alice", anAvatarData()), + identityState = IdentityState.Verified + ), + RoomMemberIdentityStateChange( + identityRoomMember = IdentityRoomMember(UserId("@bob:localhost"), "Bob", anAvatarData()), + identityState = IdentityState.Pinned + ) + ), + ), + ) + + rule.onNodeWithText("identity appears to have changed", substring = true).assertDoesNotExist() + rule.onNodeWithText("verified identity has changed", substring = true).assertDoesNotExist() + } + + private fun AndroidComposeTestRule.setIdentityChangeStateView( + state: IdentityChangeState, + ) { + setContent { + IdentityChangeStateView( + state = state, + onLinkClick = {}, + ) + } + } +} From 31ab66985e56391c8cab2c618cb97af67b5c49ea Mon Sep 17 00:00:00 2001 From: ElementBot Date: Mon, 20 Jan 2025 12:06:19 +0000 Subject: [PATCH 06/19] Update screenshots --- ...ssages.impl.messagecomposer_DisabledComposerView_Day_0_en.png} | 0 ...ages.impl.messagecomposer_DisabledComposerView_Night_0_en.png} | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename tests/uitests/src/test/snapshots/images/{features.messages.impl.messagecomposer_DisabledComposerViewPreview_Day_0_en.png => features.messages.impl.messagecomposer_DisabledComposerView_Day_0_en.png} (100%) rename tests/uitests/src/test/snapshots/images/{features.messages.impl.messagecomposer_DisabledComposerViewPreview_Night_0_en.png => features.messages.impl.messagecomposer_DisabledComposerView_Night_0_en.png} (100%) diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.messagecomposer_DisabledComposerViewPreview_Day_0_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.messagecomposer_DisabledComposerView_Day_0_en.png similarity index 100% rename from tests/uitests/src/test/snapshots/images/features.messages.impl.messagecomposer_DisabledComposerViewPreview_Day_0_en.png rename to tests/uitests/src/test/snapshots/images/features.messages.impl.messagecomposer_DisabledComposerView_Day_0_en.png diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.messagecomposer_DisabledComposerViewPreview_Night_0_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.messagecomposer_DisabledComposerView_Night_0_en.png similarity index 100% rename from tests/uitests/src/test/snapshots/images/features.messages.impl.messagecomposer_DisabledComposerViewPreview_Night_0_en.png rename to tests/uitests/src/test/snapshots/images/features.messages.impl.messagecomposer_DisabledComposerView_Night_0_en.png From 3f1543eb51bf16689a35467a59df66ca4d1e4402 Mon Sep 17 00:00:00 2001 From: Valere Date: Fri, 24 Jan 2025 12:00:16 +0100 Subject: [PATCH 07/19] code review: renaming, comments, extract common code --- .../crypto/identity/IdentityChangeEvent.kt | 4 +- .../identity/IdentityChangeStatePresenter.kt | 53 +++---------------- .../identity/IdentityChangeStateView.kt | 25 +++++---- .../MessageComposerPresenter.kt | 2 +- .../messagecomposer/MessageComposerUtils.kt | 53 +++++++++++++++++++ .../IdentityChangeStatePresenterTest.kt | 4 +- .../identity/IdentityChangeStateViewTest.kt | 4 +- .../api/encryption/EncryptionService.kt | 10 +++- .../impl/encryption/RustEncryptionService.kt | 2 +- .../test/encryption/FakeEncryptionService.kt | 2 +- 10 files changed, 90 insertions(+), 69 deletions(-) create mode 100644 features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerUtils.kt diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/crypto/identity/IdentityChangeEvent.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/crypto/identity/IdentityChangeEvent.kt index bc11f29df35..149789d97d4 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/crypto/identity/IdentityChangeEvent.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/crypto/identity/IdentityChangeEvent.kt @@ -10,6 +10,6 @@ package io.element.android.features.messages.impl.crypto.identity import io.element.android.libraries.matrix.api.core.UserId sealed interface IdentityChangeEvent { - data class PinViolation(val userId: UserId) : IdentityChangeEvent - data class VerificationViolation(val userId: UserId) : IdentityChangeEvent + data class PinIdentity(val userId: UserId) : IdentityChangeEvent + data class WithdrawVerification(val userId: UserId) : IdentityChangeEvent } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/crypto/identity/IdentityChangeStatePresenter.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/crypto/identity/IdentityChangeStatePresenter.kt index 2b410911689..745343d4e2f 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/crypto/identity/IdentityChangeStatePresenter.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/crypto/identity/IdentityChangeStatePresenter.kt @@ -8,10 +8,10 @@ package io.element.android.features.messages.impl.crypto.identity import androidx.compose.runtime.Composable -import androidx.compose.runtime.ProduceStateScope import androidx.compose.runtime.getValue import androidx.compose.runtime.produceState import androidx.compose.runtime.rememberCoroutineScope +import io.element.android.features.messages.impl.messagecomposer.observeRoomMemberIdentityStateChange import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.designsystem.components.avatar.AvatarData import io.element.android.libraries.designsystem.components.avatar.AvatarSize @@ -19,19 +19,9 @@ import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.api.encryption.EncryptionService import io.element.android.libraries.matrix.api.room.MatrixRoom import io.element.android.libraries.matrix.api.room.RoomMember -import io.element.android.libraries.matrix.api.room.roomMembers import io.element.android.libraries.matrix.ui.model.getAvatarData -import kotlinx.collections.immutable.PersistentList import kotlinx.collections.immutable.persistentListOf -import kotlinx.collections.immutable.toPersistentList import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.flow.combine -import kotlinx.coroutines.flow.distinctUntilChanged -import kotlinx.coroutines.flow.filter -import kotlinx.coroutines.flow.flatMapLatest -import kotlinx.coroutines.flow.launchIn -import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch import timber.log.Timber import javax.inject.Inject @@ -44,15 +34,15 @@ class IdentityChangeStatePresenter @Inject constructor( override fun present(): IdentityChangeState { val coroutineScope = rememberCoroutineScope() val roomMemberIdentityStateChange by produceState(persistentListOf()) { - observeRoomMemberIdentityStateChange() + observeRoomMemberIdentityStateChange(room) } fun handleEvent(event: IdentityChangeEvent) { when (event) { - is IdentityChangeEvent.VerificationViolation -> { - coroutineScope.withdrawVerificationRequirement(event.userId) + is IdentityChangeEvent.WithdrawVerification -> { + coroutineScope.withdrawVerification(event.userId) } - is IdentityChangeEvent.PinViolation -> { + is IdentityChangeEvent.PinIdentity -> { coroutineScope.pinUserIdentity(event.userId) } } @@ -64,35 +54,6 @@ class IdentityChangeStatePresenter @Inject constructor( ) } - @OptIn(ExperimentalCoroutinesApi::class) - private fun ProduceStateScope>.observeRoomMemberIdentityStateChange() { - room.syncUpdateFlow - .filter { - // Room cannot become unencrypted, so we can just apply a filter here. - room.isEncrypted - } - .distinctUntilChanged() - .flatMapLatest { - combine(room.identityStateChangesFlow, room.membersStateFlow) { identityStateChanges, membersState -> - identityStateChanges.map { identityStateChange -> - val member = membersState.roomMembers() - ?.firstOrNull { roomMember -> roomMember.userId == identityStateChange.userId } - ?.toIdentityRoomMember() - ?: createDefaultRoomMemberForIdentityChange(identityStateChange.userId) - RoomMemberIdentityStateChange( - identityRoomMember = member, - identityState = identityStateChange.identityState, - ) - } - } - .distinctUntilChanged() - .onEach { roomMemberIdentityStateChanges -> - value = roomMemberIdentityStateChanges.toPersistentList() - } - } - .launchIn(this) - } - private fun CoroutineScope.pinUserIdentity(userId: UserId) = launch { encryptionService.pinUserIdentity(userId) .onFailure { @@ -100,8 +61,8 @@ class IdentityChangeStatePresenter @Inject constructor( } } - private fun CoroutineScope.withdrawVerificationRequirement(userId: UserId) = launch { - encryptionService.withdrawVerificationRequirement(userId) + private fun CoroutineScope.withdrawVerification(userId: UserId) = launch { + encryptionService.withdrawVerification(userId) .onFailure { Timber.e(it, "Failed to withdraw verification for user $userId") } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/crypto/identity/IdentityChangeStateView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/crypto/identity/IdentityChangeStateView.kt index 600f4709268..31c017d22a6 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/crypto/identity/IdentityChangeStateView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/crypto/identity/IdentityChangeStateView.kt @@ -30,25 +30,24 @@ fun IdentityChangeStateView( onLinkClick: (String, Boolean) -> Unit, modifier: Modifier = Modifier, ) { - // Pick the first identity change to PinViolation - val pinViolationIdentityChange = state.roomMemberIdentityStateChanges.firstOrNull { - // For now only render PinViolation + // Pick the first identity change that is in Pin or Verification violation + val maybeIdentityChangeViolation = state.roomMemberIdentityStateChanges.firstOrNull { it.identityState == IdentityState.PinViolation || it.identityState == IdentityState.VerificationViolation } - if (pinViolationIdentityChange != null) { + if (maybeIdentityChangeViolation != null) { ComposerAlertMolecule( modifier = modifier, - avatar = pinViolationIdentityChange.identityRoomMember.avatarData, + avatar = maybeIdentityChangeViolation.identityRoomMember.avatarData, content = buildAnnotatedString { val learnMoreStr = stringResource(CommonStrings.action_learn_more) - val displayName = pinViolationIdentityChange.identityRoomMember.displayNameOrDefault + val displayName = maybeIdentityChangeViolation.identityRoomMember.displayNameOrDefault val userIdStr = stringResource( CommonStrings.crypto_identity_change_pin_violation_new_user_id, - pinViolationIdentityChange.identityRoomMember.userId, + maybeIdentityChangeViolation.identityRoomMember.userId, ) - val fullText = if (pinViolationIdentityChange.identityState == IdentityState.PinViolation) { + val fullText = if (maybeIdentityChangeViolation.identityState == IdentityState.PinViolation) { stringResource( id = CommonStrings.crypto_identity_change_pin_violation_new, displayName, @@ -93,19 +92,19 @@ fun IdentityChangeStateView( end = learnMoreStartIndex + learnMoreStr.length, ) }, - submitText = if (pinViolationIdentityChange.identityState == IdentityState.VerificationViolation) { + submitText = if (maybeIdentityChangeViolation.identityState == IdentityState.VerificationViolation) { stringResource(CommonStrings.crypto_identity_change_withdraw_verification_action) } else { stringResource(CommonStrings.action_ok) }, onSubmitClick = { - if (pinViolationIdentityChange.identityState == IdentityState.VerificationViolation) { - state.eventSink(IdentityChangeEvent.VerificationViolation(pinViolationIdentityChange.identityRoomMember.userId)) + if (maybeIdentityChangeViolation.identityState == IdentityState.VerificationViolation) { + state.eventSink(IdentityChangeEvent.WithdrawVerification(maybeIdentityChangeViolation.identityRoomMember.userId)) } else { - state.eventSink(IdentityChangeEvent.PinViolation(pinViolationIdentityChange.identityRoomMember.userId)) + state.eventSink(IdentityChangeEvent.PinIdentity(maybeIdentityChangeViolation.identityRoomMember.userId)) } }, - isCritical = pinViolationIdentityChange.identityState == IdentityState.VerificationViolation, + isCritical = maybeIdentityChangeViolation.identityState == IdentityState.VerificationViolation, ) } } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerPresenter.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerPresenter.kt index 7945cc6bb1f..93ad8f59ba8 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerPresenter.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerPresenter.kt @@ -403,7 +403,7 @@ class MessageComposerPresenter @AssistedInject constructor( } val roomMemberIdentityStateChange by produceState(persistentListOf()) { - observeRoomMemberIdentityStateChange() + observeRoomMemberIdentityStateChange(room) } return MessageComposerState( diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerUtils.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerUtils.kt new file mode 100644 index 00000000000..f1ec4f61565 --- /dev/null +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerUtils.kt @@ -0,0 +1,53 @@ +/* + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.features.messages.impl.messagecomposer + +import androidx.compose.runtime.ProduceStateScope +import io.element.android.features.messages.impl.crypto.identity.RoomMemberIdentityStateChange +import io.element.android.features.messages.impl.crypto.identity.createDefaultRoomMemberForIdentityChange +import io.element.android.features.messages.impl.crypto.identity.toIdentityRoomMember +import io.element.android.libraries.matrix.api.room.MatrixRoom +import io.element.android.libraries.matrix.api.room.roomMembers +import kotlinx.collections.immutable.PersistentList +import kotlinx.collections.immutable.toPersistentList +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach + +@OptIn(ExperimentalCoroutinesApi::class) +fun ProduceStateScope>.observeRoomMemberIdentityStateChange(room: MatrixRoom) { + room.syncUpdateFlow + .filter { + // Room cannot become unencrypted, so we can just apply a filter here. + room.isEncrypted + } + .distinctUntilChanged() + .flatMapLatest { + combine(room.identityStateChangesFlow, room.membersStateFlow) { identityStateChanges, membersState -> + identityStateChanges.map { identityStateChange -> + val member = membersState.roomMembers() + ?.firstOrNull { roomMember -> roomMember.userId == identityStateChange.userId } + ?.toIdentityRoomMember() + ?: createDefaultRoomMemberForIdentityChange(identityStateChange.userId) + RoomMemberIdentityStateChange( + identityRoomMember = member, + identityState = identityStateChange.identityState, + ) + } + } + .distinctUntilChanged() + .onEach { roomMemberIdentityStateChanges -> + value = roomMemberIdentityStateChanges.toPersistentList() + } + } + .launchIn(this) +} diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/crypto/identity/IdentityChangeStatePresenterTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/crypto/identity/IdentityChangeStatePresenterTest.kt index 04153cd8276..934bff7f49b 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/crypto/identity/IdentityChangeStatePresenterTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/crypto/identity/IdentityChangeStatePresenterTest.kt @@ -143,7 +143,7 @@ class IdentityChangeStatePresenterTest { val presenter = createIdentityChangeStatePresenter(encryptionService = encryptionService) presenter.test { val initialState = awaitItem() - initialState.eventSink(IdentityChangeEvent.PinViolation(A_USER_ID)) + initialState.eventSink(IdentityChangeEvent.PinIdentity(A_USER_ID)) lambda.assertions().isCalledOnce().with(value(A_USER_ID)) } } @@ -158,7 +158,7 @@ class IdentityChangeStatePresenterTest { val presenter = createIdentityChangeStatePresenter(encryptionService = encryptionService) presenter.test { val initialState = awaitItem() - initialState.eventSink(IdentityChangeEvent.VerificationViolation(A_USER_ID)) + initialState.eventSink(IdentityChangeEvent.WithdrawVerification(A_USER_ID)) lambda.assertions().isCalledOnce().with(value(A_USER_ID)) } } diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/crypto/identity/IdentityChangeStateViewTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/crypto/identity/IdentityChangeStateViewTest.kt index 3642fcd3c9d..67ee5689e12 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/crypto/identity/IdentityChangeStateViewTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/crypto/identity/IdentityChangeStateViewTest.kt @@ -47,7 +47,7 @@ class IdentityChangeStateViewTest { rule.onNodeWithText("Alice", substring = true).assertExists("should display user displayname") rule.clickOn(res = CommonStrings.action_ok) - eventsRecorder.assertSingle(IdentityChangeEvent.PinViolation(UserId("@alice:localhost"))) + eventsRecorder.assertSingle(IdentityChangeEvent.PinIdentity(UserId("@alice:localhost"))) } @Test @@ -70,7 +70,7 @@ class IdentityChangeStateViewTest { rule.onNodeWithText("Alice", substring = true).assertExists("should display user displayname") rule.clickOn(res = CommonStrings.crypto_identity_change_withdraw_verification_action) - eventsRecorder.assertSingle(IdentityChangeEvent.VerificationViolation(UserId("@alice:localhost"))) + eventsRecorder.assertSingle(IdentityChangeEvent.WithdrawVerification(UserId("@alice:localhost"))) } @Test diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/encryption/EncryptionService.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/encryption/EncryptionService.kt index 5b2703bf255..a1a4eb9b1cc 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/encryption/EncryptionService.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/encryption/EncryptionService.kt @@ -66,7 +66,15 @@ interface EncryptionService { * Remember this identity, ensuring it does not result in a pin violation. */ suspend fun pinUserIdentity(userId: UserId): Result - suspend fun withdrawVerificationRequirement(userId: UserId): Result + + /** + * Withdraw the verification for that user (also pin the identity). + * + * Useful when a user that was verified is not anymore, but it is not + * possible to re-verify immediately. This allows to restore communication by reverting the + * user trust from verified to TOFU verified. + */ + suspend fun withdrawVerification(userId: UserId): Result } /** diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/encryption/RustEncryptionService.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/encryption/RustEncryptionService.kt index 3c3228ceb9f..2730d93c878 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/encryption/RustEncryptionService.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/encryption/RustEncryptionService.kt @@ -209,7 +209,7 @@ internal class RustEncryptionService( getUserIdentity(userId).pin() } - override suspend fun withdrawVerificationRequirement(userId: UserId): Result = runCatching { + override suspend fun withdrawVerification(userId: UserId): Result = runCatching { getUserIdentity(userId).withdrawVerification() } diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/encryption/FakeEncryptionService.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/encryption/FakeEncryptionService.kt index 0be0434d381..7d56ab3d7ea 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/encryption/FakeEncryptionService.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/encryption/FakeEncryptionService.kt @@ -125,7 +125,7 @@ class FakeEncryptionService( return pinUserIdentityResult(userId) } - override suspend fun withdrawVerificationRequirement(userId: UserId): Result { + override suspend fun withdrawVerification(userId: UserId): Result { return withdrawVerificationResult(userId) } From b25361907d50434b7b48a18dcd0273f4780ca5cb Mon Sep 17 00:00:00 2001 From: Valere Date: Fri, 24 Jan 2025 12:00:48 +0100 Subject: [PATCH 08/19] fix disabled composer padding/margin diff with composer --- .../messages/impl/messagecomposer/DisabledComposerView.kt | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/DisabledComposerView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/DisabledComposerView.kt index 94ccf375e7b..508239383c7 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/DisabledComposerView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/DisabledComposerView.kt @@ -13,11 +13,13 @@ import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.IntrinsicSize import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.requiredHeightIn import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment @@ -39,7 +41,7 @@ internal fun DisabledComposerView( modifier: Modifier = Modifier, ) { Row( - modifier = modifier.padding(4.dp) + modifier = modifier.padding(3.dp) .fillMaxWidth(), verticalAlignment = Alignment.CenterVertically, ) { @@ -70,8 +72,10 @@ internal fun DisabledComposerView( .weight(1f), ) + Spacer(modifier = Modifier.width(8.dp)) IconButton( modifier = Modifier + .padding(start= 2.dp) .size(48.dp), enabled = false, onClick = {}, From a3ec13340ed4c7c5e80baeef65af6447e758a18c Mon Sep 17 00:00:00 2001 From: Valere Date: Fri, 24 Jan 2025 12:10:15 +0100 Subject: [PATCH 09/19] fixup: ktlint --- .../messages/impl/messagecomposer/DisabledComposerView.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/DisabledComposerView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/DisabledComposerView.kt index 508239383c7..19e1758be8e 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/DisabledComposerView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/DisabledComposerView.kt @@ -75,7 +75,7 @@ internal fun DisabledComposerView( Spacer(modifier = Modifier.width(8.dp)) IconButton( modifier = Modifier - .padding(start= 2.dp) + .padding(start = 2.dp) .size(48.dp), enabled = false, onClick = {}, From 690c6cfc6d9dcbf8378305880440becd332fd7ba Mon Sep 17 00:00:00 2001 From: ElementBot Date: Fri, 24 Jan 2025 12:47:28 +0000 Subject: [PATCH 10/19] Update screenshots --- ...ges.impl.messagecomposer_DisabledComposerView_Day_0_en.png | 4 ++-- ...s.impl.messagecomposer_DisabledComposerView_Night_0_en.png | 4 ++-- ...ages.impl.messagecomposer_MessageComposerView_Day_0_en.png | 4 ++-- ...es.impl.messagecomposer_MessageComposerView_Night_0_en.png | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.messagecomposer_DisabledComposerView_Day_0_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.messagecomposer_DisabledComposerView_Day_0_en.png index 803daa3cfd1..205952091dd 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.messagecomposer_DisabledComposerView_Day_0_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.messagecomposer_DisabledComposerView_Day_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1c3e38a6fa5e7434327e0877e6d65258086ac1db5dc50bb2db8d3284d307112e -size 6623 +oid sha256:0d866ee561b4109bf1bb4d1cc3b34d32ad0c67ae51f7599088898acd4310f961 +size 6725 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.messagecomposer_DisabledComposerView_Night_0_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.messagecomposer_DisabledComposerView_Night_0_en.png index 1bbbb4439f7..a5be35d80b1 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.messagecomposer_DisabledComposerView_Night_0_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.messagecomposer_DisabledComposerView_Night_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:295c2fcc181add02418415981ba2cab1aa36142b83d6a1b7e7610dcf8c3c6dee -size 6339 +oid sha256:3fcb7d90743e35ebf03f09129e02f4df7d0b3b883c451fb45f57f70669b5d4c5 +size 6382 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.messagecomposer_MessageComposerView_Day_0_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.messagecomposer_MessageComposerView_Day_0_en.png index b9c1c8d3881..8bf1f125bc5 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.messagecomposer_MessageComposerView_Day_0_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.messagecomposer_MessageComposerView_Day_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8ec042b23b502e5becc57c6b804a809bf374dfc82c4638c29d8db6f3f0280d58 -size 19404 +oid sha256:ceb6210817e79599a968157aebb7fd1d4e80ddbb8f3e57aabfb58d8f6066b9d4 +size 19455 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.messagecomposer_MessageComposerView_Night_0_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.messagecomposer_MessageComposerView_Night_0_en.png index 207ea5363d2..7743b1f55aa 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.messagecomposer_MessageComposerView_Night_0_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.messagecomposer_MessageComposerView_Night_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2e2ee0b51352553c414211af666823dda94497c0bd229c9439f729070f1ec1d5 -size 17878 +oid sha256:6394543bdd107f70a09bdcb164163984112a25f90eed93850cb525572dd53045 +size 17888 From e65a436625420501b4dd35d36ac202ff8be59485 Mon Sep 17 00:00:00 2001 From: Valere Date: Fri, 24 Jan 2025 15:19:31 +0100 Subject: [PATCH 11/19] fixup: remove dead code (refactored method) --- .../MessageComposerPresenter.kt | 40 ------------------- 1 file changed, 40 deletions(-) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerPresenter.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerPresenter.kt index 93ad8f59ba8..16b908a7b53 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerPresenter.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerPresenter.kt @@ -14,7 +14,6 @@ import androidx.annotation.VisibleForTesting import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.ProduceStateScope import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateListOf @@ -35,9 +34,6 @@ import im.vector.app.features.analytics.plan.Interaction import io.element.android.features.messages.impl.MessagesNavigator import io.element.android.features.messages.impl.attachments.Attachment import io.element.android.features.messages.impl.attachments.preview.error.sendAttachmentError -import io.element.android.features.messages.impl.crypto.identity.RoomMemberIdentityStateChange -import io.element.android.features.messages.impl.crypto.identity.createDefaultRoomMemberForIdentityChange -import io.element.android.features.messages.impl.crypto.identity.toIdentityRoomMember import io.element.android.features.messages.impl.draft.ComposerDraftService import io.element.android.features.messages.impl.messagecomposer.suggestions.RoomAliasSuggestionsDataSource import io.element.android.features.messages.impl.messagecomposer.suggestions.SuggestionsProcessor @@ -57,7 +53,6 @@ import io.element.android.libraries.matrix.api.room.MatrixRoom import io.element.android.libraries.matrix.api.room.draft.ComposerDraft import io.element.android.libraries.matrix.api.room.draft.ComposerDraftType import io.element.android.libraries.matrix.api.room.isDm -import io.element.android.libraries.matrix.api.room.roomMembers import io.element.android.libraries.matrix.api.timeline.TimelineException import io.element.android.libraries.matrix.api.timeline.item.event.toEventOrTransactionId import io.element.android.libraries.matrix.ui.messages.RoomMemberProfilesCache @@ -82,24 +77,18 @@ import io.element.android.services.analytics.api.AnalyticsService import io.element.android.services.analyticsproviders.api.trackers.captureInteraction import io.element.android.wysiwyg.compose.RichTextEditorState import io.element.android.wysiwyg.display.TextDisplay -import kotlinx.collections.immutable.PersistentList import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.toPersistentList import kotlinx.coroutines.CancellationException import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.FlowPreview import kotlinx.coroutines.delay import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.debounce -import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.filter -import kotlinx.coroutines.flow.flatMapLatest -import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.merge -import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch import timber.log.Timber import kotlin.time.Duration.Companion.seconds @@ -723,33 +712,4 @@ class MessageComposerPresenter @AssistedInject constructor( } } } - - @OptIn(ExperimentalCoroutinesApi::class) - private fun ProduceStateScope>.observeRoomMemberIdentityStateChange() { - room.syncUpdateFlow - .filter { - // Room cannot become unencrypted, so we can just apply a filter here. - room.isEncrypted - } - .distinctUntilChanged() - .flatMapLatest { - combine(room.identityStateChangesFlow, room.membersStateFlow) { identityStateChanges, membersState -> - identityStateChanges.map { identityStateChange -> - val member = membersState.roomMembers() - ?.firstOrNull { roomMember -> roomMember.userId == identityStateChange.userId } - ?.toIdentityRoomMember() - ?: createDefaultRoomMemberForIdentityChange(identityStateChange.userId) - RoomMemberIdentityStateChange( - identityRoomMember = member, - identityState = identityStateChange.identityState, - ) - } - } - .distinctUntilChanged() - .onEach { roomMemberIdentityStateChanges -> - value = roomMemberIdentityStateChanges.toPersistentList() - } - } - .launchIn(this) - } } From 22c0d08e7ce33baacc5f7f934d6114c0fc58758b Mon Sep 17 00:00:00 2001 From: Valere Date: Thu, 6 Feb 2025 17:16:09 +0100 Subject: [PATCH 12/19] post rebase: Fix test --- .../impl/crypto/identity/IdentityChangeStateViewTest.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/crypto/identity/IdentityChangeStateViewTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/crypto/identity/IdentityChangeStateViewTest.kt index 67ee5689e12..ca07cc93cdf 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/crypto/identity/IdentityChangeStateViewTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/crypto/identity/IdentityChangeStateViewTest.kt @@ -100,7 +100,7 @@ class IdentityChangeStateViewTest { setContent { IdentityChangeStateView( state = state, - onLinkClick = {}, + onLinkClick = { _, _ -> }, ) } } From 1b9e4bdd9e00366e49478c3487d5a66a295da589 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 18 Feb 2025 13:36:05 +0100 Subject: [PATCH 13/19] Move fun and add `private` modifier. --- .../identity/IdentityChangeStatePresenter.kt | 21 ---------------- .../messagecomposer/MessageComposerUtils.kt | 25 +++++++++++++++++-- 2 files changed, 23 insertions(+), 23 deletions(-) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/crypto/identity/IdentityChangeStatePresenter.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/crypto/identity/IdentityChangeStatePresenter.kt index 745343d4e2f..4aba5dc4403 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/crypto/identity/IdentityChangeStatePresenter.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/crypto/identity/IdentityChangeStatePresenter.kt @@ -13,13 +13,9 @@ import androidx.compose.runtime.produceState import androidx.compose.runtime.rememberCoroutineScope import io.element.android.features.messages.impl.messagecomposer.observeRoomMemberIdentityStateChange import io.element.android.libraries.architecture.Presenter -import io.element.android.libraries.designsystem.components.avatar.AvatarData -import io.element.android.libraries.designsystem.components.avatar.AvatarSize import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.api.encryption.EncryptionService import io.element.android.libraries.matrix.api.room.MatrixRoom -import io.element.android.libraries.matrix.api.room.RoomMember -import io.element.android.libraries.matrix.ui.model.getAvatarData import kotlinx.collections.immutable.persistentListOf import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch @@ -68,20 +64,3 @@ class IdentityChangeStatePresenter @Inject constructor( } } } - -fun RoomMember.toIdentityRoomMember() = IdentityRoomMember( - userId = userId, - displayNameOrDefault = displayNameOrDefault, - avatarData = getAvatarData(AvatarSize.ComposerAlert), -) - -fun createDefaultRoomMemberForIdentityChange(userId: UserId) = IdentityRoomMember( - userId = userId, - displayNameOrDefault = userId.extractedDisplayName, - avatarData = AvatarData( - id = userId.value, - name = null, - url = null, - size = AvatarSize.ComposerAlert, - ), -) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerUtils.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerUtils.kt index f1ec4f61565..48230840b1f 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerUtils.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerUtils.kt @@ -8,11 +8,15 @@ package io.element.android.features.messages.impl.messagecomposer import androidx.compose.runtime.ProduceStateScope +import io.element.android.features.messages.impl.crypto.identity.IdentityRoomMember import io.element.android.features.messages.impl.crypto.identity.RoomMemberIdentityStateChange -import io.element.android.features.messages.impl.crypto.identity.createDefaultRoomMemberForIdentityChange -import io.element.android.features.messages.impl.crypto.identity.toIdentityRoomMember +import io.element.android.libraries.designsystem.components.avatar.AvatarData +import io.element.android.libraries.designsystem.components.avatar.AvatarSize +import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.api.room.MatrixRoom +import io.element.android.libraries.matrix.api.room.RoomMember import io.element.android.libraries.matrix.api.room.roomMembers +import io.element.android.libraries.matrix.ui.model.getAvatarData import kotlinx.collections.immutable.PersistentList import kotlinx.collections.immutable.toPersistentList import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -51,3 +55,20 @@ fun ProduceStateScope>.observeRoom } .launchIn(this) } + +private fun RoomMember.toIdentityRoomMember() = IdentityRoomMember( + userId = userId, + displayNameOrDefault = displayNameOrDefault, + avatarData = getAvatarData(AvatarSize.ComposerAlert), +) + +private fun createDefaultRoomMemberForIdentityChange(userId: UserId) = IdentityRoomMember( + userId = userId, + displayNameOrDefault = userId.extractedDisplayName, + avatarData = AvatarData( + id = userId.value, + name = null, + url = null, + size = AvatarSize.ComposerAlert, + ), +) From 20bac66dd430a2c4106713347564afa70e45e42e Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 18 Feb 2025 13:36:54 +0100 Subject: [PATCH 14/19] Trailing comma and named param. --- .../impl/crypto/identity/IdentityChangeStateProvider.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/crypto/identity/IdentityChangeStateProvider.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/crypto/identity/IdentityChangeStateProvider.kt index 50ca0d7e3ab..f64f505f0f7 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/crypto/identity/IdentityChangeStateProvider.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/crypto/identity/IdentityChangeStateProvider.kt @@ -47,10 +47,10 @@ internal fun aRoomMemberIdentityStateChange( internal fun anIdentityChangeState( roomMemberIdentityStateChanges: List = emptyList(), - eventSink: (IdentityChangeEvent) -> Unit = {} + eventSink: (IdentityChangeEvent) -> Unit = {}, ) = IdentityChangeState( roomMemberIdentityStateChanges = roomMemberIdentityStateChanges.toImmutableList(), - eventSink, + eventSink = eventSink, ) internal fun anIdentityRoomMember( From 53e55b690ee70d8956195c13207ecde349c0f87d Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 18 Feb 2025 13:42:56 +0100 Subject: [PATCH 15/19] Cleanup code. --- .../identity/IdentityChangeStateView.kt | 29 +++++++++---------- .../api/encryption/identity/IdentityState.kt | 2 ++ 2 files changed, 16 insertions(+), 15 deletions(-) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/crypto/identity/IdentityChangeStateView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/crypto/identity/IdentityChangeStateView.kt index 31c017d22a6..0dd0b00d705 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/crypto/identity/IdentityChangeStateView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/crypto/identity/IdentityChangeStateView.kt @@ -22,6 +22,7 @@ import io.element.android.libraries.designsystem.atomic.molecules.ComposerAlertM import io.element.android.libraries.designsystem.preview.ElementPreview import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.matrix.api.encryption.identity.IdentityState +import io.element.android.libraries.matrix.api.encryption.identity.isAViolation import io.element.android.libraries.ui.strings.CommonStrings @Composable @@ -30,24 +31,22 @@ fun IdentityChangeStateView( onLinkClick: (String, Boolean) -> Unit, modifier: Modifier = Modifier, ) { - // Pick the first identity change that is in Pin or Verification violation - val maybeIdentityChangeViolation = state.roomMemberIdentityStateChanges.firstOrNull { - it.identityState == IdentityState.PinViolation || - it.identityState == IdentityState.VerificationViolation + // Pick the first identity change that is a violation + val identityChangeViolation = state.roomMemberIdentityStateChanges.firstOrNull { + it.identityState.isAViolation() } - if (maybeIdentityChangeViolation != null) { + if (identityChangeViolation != null) { ComposerAlertMolecule( modifier = modifier, - avatar = maybeIdentityChangeViolation.identityRoomMember.avatarData, + avatar = identityChangeViolation.identityRoomMember.avatarData, content = buildAnnotatedString { val learnMoreStr = stringResource(CommonStrings.action_learn_more) - val displayName = maybeIdentityChangeViolation.identityRoomMember.displayNameOrDefault + val displayName = identityChangeViolation.identityRoomMember.displayNameOrDefault val userIdStr = stringResource( CommonStrings.crypto_identity_change_pin_violation_new_user_id, - maybeIdentityChangeViolation.identityRoomMember.userId, + identityChangeViolation.identityRoomMember.userId, ) - - val fullText = if (maybeIdentityChangeViolation.identityState == IdentityState.PinViolation) { + val fullText = if (identityChangeViolation.identityState == IdentityState.PinViolation) { stringResource( id = CommonStrings.crypto_identity_change_pin_violation_new, displayName, @@ -92,19 +91,19 @@ fun IdentityChangeStateView( end = learnMoreStartIndex + learnMoreStr.length, ) }, - submitText = if (maybeIdentityChangeViolation.identityState == IdentityState.VerificationViolation) { + submitText = if (identityChangeViolation.identityState == IdentityState.VerificationViolation) { stringResource(CommonStrings.crypto_identity_change_withdraw_verification_action) } else { stringResource(CommonStrings.action_ok) }, onSubmitClick = { - if (maybeIdentityChangeViolation.identityState == IdentityState.VerificationViolation) { - state.eventSink(IdentityChangeEvent.WithdrawVerification(maybeIdentityChangeViolation.identityRoomMember.userId)) + if (identityChangeViolation.identityState == IdentityState.VerificationViolation) { + state.eventSink(IdentityChangeEvent.WithdrawVerification(identityChangeViolation.identityRoomMember.userId)) } else { - state.eventSink(IdentityChangeEvent.PinIdentity(maybeIdentityChangeViolation.identityRoomMember.userId)) + state.eventSink(IdentityChangeEvent.PinIdentity(identityChangeViolation.identityRoomMember.userId)) } }, - isCritical = maybeIdentityChangeViolation.identityState == IdentityState.VerificationViolation, + isCritical = identityChangeViolation.identityState == IdentityState.VerificationViolation, ) } } diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/encryption/identity/IdentityState.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/encryption/identity/IdentityState.kt index d05b8618e32..d565c473360 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/encryption/identity/IdentityState.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/encryption/identity/IdentityState.kt @@ -32,3 +32,5 @@ enum class IdentityState { */ VerificationViolation, } + +fun IdentityState.isAViolation() = this == IdentityState.PinViolation || this == IdentityState.VerificationViolation From 81a4aa018d4f5fc5eca9522c9efc851cc57a0f90 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 18 Feb 2025 14:01:44 +0100 Subject: [PATCH 16/19] Cleanup code. --- .../identity/IdentityChangeStateView.kt | 144 +++++++++--------- 1 file changed, 76 insertions(+), 68 deletions(-) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/crypto/identity/IdentityChangeStateView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/crypto/identity/IdentityChangeStateView.kt index 0dd0b00d705..8362515b6a3 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/crypto/identity/IdentityChangeStateView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/crypto/identity/IdentityChangeStateView.kt @@ -7,6 +7,7 @@ package io.element.android.features.messages.impl.crypto.identity +import androidx.annotation.StringRes import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource @@ -35,79 +36,86 @@ fun IdentityChangeStateView( val identityChangeViolation = state.roomMemberIdentityStateChanges.firstOrNull { it.identityState.isAViolation() } - if (identityChangeViolation != null) { - ComposerAlertMolecule( + when (identityChangeViolation?.identityState) { + IdentityState.PinViolation -> ViolationAlert( + identityChangeViolation = identityChangeViolation, + onLinkClick = onLinkClick, + textId = CommonStrings.crypto_identity_change_pin_violation_new, + isCritical = false, + submitTextId = CommonStrings.action_ok, + onSubmitClick = { state.eventSink(IdentityChangeEvent.PinIdentity(identityChangeViolation.identityRoomMember.userId)) }, modifier = modifier, - avatar = identityChangeViolation.identityRoomMember.avatarData, - content = buildAnnotatedString { - val learnMoreStr = stringResource(CommonStrings.action_learn_more) - val displayName = identityChangeViolation.identityRoomMember.displayNameOrDefault - val userIdStr = stringResource( - CommonStrings.crypto_identity_change_pin_violation_new_user_id, - identityChangeViolation.identityRoomMember.userId, - ) - val fullText = if (identityChangeViolation.identityState == IdentityState.PinViolation) { - stringResource( - id = CommonStrings.crypto_identity_change_pin_violation_new, - displayName, - userIdStr, - learnMoreStr, - ) - } else { - stringResource( - id = CommonStrings.crypto_identity_change_verification_violation_new, - displayName, - userIdStr, - learnMoreStr, - ) - } - append(fullText) - val userIdStartIndex = fullText.indexOf(userIdStr) - addStyle( - style = SpanStyle( - fontWeight = FontWeight.Bold, - ), - start = userIdStartIndex, - end = userIdStartIndex + userIdStr.length, - ) - val learnMoreStartIndex = fullText.lastIndexOf(learnMoreStr) - addStyle( - style = SpanStyle( - textDecoration = TextDecoration.Underline, - fontWeight = FontWeight.Bold, - color = ElementTheme.colors.textPrimary - ), - start = learnMoreStartIndex, - end = learnMoreStartIndex + learnMoreStr.length, - ) - addLink( - url = LinkAnnotation.Url( - url = LearnMoreConfig.IDENTITY_CHANGE_URL, - linkInteractionListener = { - onLinkClick(LearnMoreConfig.IDENTITY_CHANGE_URL, true) - } - ), - start = learnMoreStartIndex, - end = learnMoreStartIndex + learnMoreStr.length, - ) - }, - submitText = if (identityChangeViolation.identityState == IdentityState.VerificationViolation) { - stringResource(CommonStrings.crypto_identity_change_withdraw_verification_action) - } else { - stringResource(CommonStrings.action_ok) - }, - onSubmitClick = { - if (identityChangeViolation.identityState == IdentityState.VerificationViolation) { - state.eventSink(IdentityChangeEvent.WithdrawVerification(identityChangeViolation.identityRoomMember.userId)) - } else { - state.eventSink(IdentityChangeEvent.PinIdentity(identityChangeViolation.identityRoomMember.userId)) - } - }, - isCritical = identityChangeViolation.identityState == IdentityState.VerificationViolation, ) + IdentityState.VerificationViolation -> ViolationAlert( + identityChangeViolation = identityChangeViolation, + onLinkClick = onLinkClick, + textId = CommonStrings.crypto_identity_change_verification_violation_new, + isCritical = true, + submitTextId = CommonStrings.crypto_identity_change_withdraw_verification_action, + onSubmitClick = { state.eventSink(IdentityChangeEvent.WithdrawVerification(identityChangeViolation.identityRoomMember.userId)) }, + modifier = modifier, + ) + else -> Unit } } +@Composable +private fun ViolationAlert( + identityChangeViolation: RoomMemberIdentityStateChange, + onLinkClick: (String, Boolean) -> Unit, + @StringRes textId: Int, + isCritical: Boolean, + @StringRes submitTextId: Int, + onSubmitClick: () -> Unit, + modifier: Modifier = Modifier, +) { + ComposerAlertMolecule( + modifier = modifier, + avatar = identityChangeViolation.identityRoomMember.avatarData, + content = buildAnnotatedString { + val learnMoreStr = stringResource(CommonStrings.action_learn_more) + val displayName = identityChangeViolation.identityRoomMember.displayNameOrDefault + val userIdStr = stringResource( + CommonStrings.crypto_identity_change_pin_violation_new_user_id, + identityChangeViolation.identityRoomMember.userId, + ) + val fullText = stringResource(textId, displayName, userIdStr, learnMoreStr) + append(fullText) + val userIdStartIndex = fullText.indexOf(userIdStr) + addStyle( + style = SpanStyle( + fontWeight = FontWeight.Bold, + ), + start = userIdStartIndex, + end = userIdStartIndex + userIdStr.length, + ) + val learnMoreStartIndex = fullText.lastIndexOf(learnMoreStr) + addStyle( + style = SpanStyle( + textDecoration = TextDecoration.Underline, + fontWeight = FontWeight.Bold, + color = ElementTheme.colors.textPrimary + ), + start = learnMoreStartIndex, + end = learnMoreStartIndex + learnMoreStr.length, + ) + addLink( + url = LinkAnnotation.Url( + url = LearnMoreConfig.IDENTITY_CHANGE_URL, + linkInteractionListener = { + onLinkClick(LearnMoreConfig.IDENTITY_CHANGE_URL, true) + } + ), + start = learnMoreStartIndex, + end = learnMoreStartIndex + learnMoreStr.length, + ) + }, + submitText = stringResource(submitTextId), + onSubmitClick = onSubmitClick, + isCritical = isCritical, + ) +} + @PreviewsDayNight @Composable internal fun IdentityChangeStateViewPreview( From 6de04daddfcf879f5541449c0fbc721f05df110f Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 18 Feb 2025 14:12:22 +0100 Subject: [PATCH 17/19] Move DisabledComposer to MessageView. --- .../features/messages/impl/MessagesView.kt | 57 +++++++++++-------- .../messagecomposer/MessageComposerView.kt | 9 --- 2 files changed, 33 insertions(+), 33 deletions(-) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesView.kt index 381d2dca41d..26f9d268a74 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesView.kt @@ -55,6 +55,7 @@ import io.element.android.features.messages.impl.actionlist.ActionListView import io.element.android.features.messages.impl.actionlist.model.TimelineItemAction import io.element.android.features.messages.impl.crypto.identity.IdentityChangeStateView import io.element.android.features.messages.impl.messagecomposer.AttachmentsBottomSheet +import io.element.android.features.messages.impl.messagecomposer.DisabledComposerView import io.element.android.features.messages.impl.messagecomposer.MessageComposerEvents import io.element.android.features.messages.impl.messagecomposer.MessageComposerView import io.element.android.features.messages.impl.messagecomposer.suggestions.SuggestionsPickerView @@ -97,6 +98,7 @@ import io.element.android.libraries.designsystem.utils.snackbar.SnackbarHost import io.element.android.libraries.designsystem.utils.snackbar.rememberSnackbarHostState import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.core.UserId +import io.element.android.libraries.matrix.api.encryption.identity.IdentityState import io.element.android.libraries.textcomposer.model.TextEditorState import io.element.android.libraries.ui.strings.CommonStrings import kotlinx.collections.immutable.ImmutableList @@ -196,8 +198,8 @@ fun MessagesView( MessagesViewContent( state = state, modifier = Modifier - .padding(padding) - .consumeWindowInsets(padding), + .padding(padding) + .consumeWindowInsets(padding), onContentClick = ::onContentClick, onMessageLongClick = ::onMessageLongClick, onUserDataClick = { hidingKeyboard { onUserDataClick(it) } }, @@ -290,9 +292,9 @@ private fun MessagesViewContent( ) { Box( modifier = modifier - .fillMaxSize() - .navigationBarsPadding() - .imePadding(), + .fillMaxSize() + .navigationBarsPadding() + .imePadding(), ) { AttachmentsBottomSheet( state = state.composerState, @@ -402,13 +404,13 @@ private fun MessagesViewComposerBottomSheetContents( Column(modifier = Modifier.fillMaxWidth()) { SuggestionsPickerView( modifier = Modifier - .heightIn(max = 230.dp) - // Consume all scrolling, preventing the bottom sheet from being dragged when interacting with the list of suggestions - .nestedScroll(object : NestedScrollConnection { - override fun onPostScroll(consumed: Offset, available: Offset, source: NestedScrollSource): Offset { - return available - } - }), + .heightIn(max = 230.dp) + // Consume all scrolling, preventing the bottom sheet from being dragged when interacting with the list of suggestions + .nestedScroll(object : NestedScrollConnection { + override fun onPostScroll(consumed: Offset, available: Offset, source: NestedScrollSource): Offset { + return available + } + }), roomId = state.roomId, roomName = state.roomName.dataOrNull(), roomAvatarData = state.roomAvatar.dataOrNull(), @@ -425,13 +427,20 @@ private fun MessagesViewComposerBottomSheetContents( onLinkClick = onLinkClick, ) } - MessageComposerView( - state = state.composerState, - voiceMessageState = state.voiceMessageComposerState, - subcomposing = subcomposing, - enableVoiceMessages = state.enableVoiceMessages, - modifier = Modifier.fillMaxWidth(), - ) + val verificationViolation = state.composerState.roomMemberIdentityStateChanges.firstOrNull { + it.identityState == IdentityState.VerificationViolation + } + if (verificationViolation != null) { + DisabledComposerView(modifier = Modifier.fillMaxWidth()) + } else { + MessageComposerView( + state = state.composerState, + voiceMessageState = state.voiceMessageComposerState, + subcomposing = subcomposing, + enableVoiceMessages = state.enableVoiceMessages, + modifier = Modifier.fillMaxWidth(), + ) + } } } else { CantSendMessageBanner() @@ -456,8 +465,8 @@ private fun MessagesViewTopBar( title = { val roundedCornerShape = RoundedCornerShape(8.dp) val titleModifier = Modifier - .clip(roundedCornerShape) - .clickable { onRoomDetailsClick() } + .clip(roundedCornerShape) + .clickable { onRoomDetailsClick() } if (roomName != null && roomAvatar != null) { RoomAvatarAndNameRow( roomName = roomName, @@ -512,9 +521,9 @@ private fun RoomAvatarAndNameRow( private fun CantSendMessageBanner() { Row( modifier = Modifier - .fillMaxWidth() - .background(MaterialTheme.colorScheme.secondary) - .padding(16.dp), + .fillMaxWidth() + .background(MaterialTheme.colorScheme.secondary) + .padding(16.dp), verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.Center ) { diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerView.kt index 09289305b25..bfb51f4a879 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerView.kt @@ -23,7 +23,6 @@ import io.element.android.features.messages.impl.voicemessages.composer.VoiceMes import io.element.android.features.messages.impl.voicemessages.composer.aVoiceMessageComposerState import io.element.android.libraries.designsystem.preview.ElementPreview import io.element.android.libraries.designsystem.preview.PreviewsDayNight -import io.element.android.libraries.matrix.api.encryption.identity.IdentityState import io.element.android.libraries.textcomposer.TextComposer import io.element.android.libraries.textcomposer.model.Suggestion import io.element.android.libraries.textcomposer.model.VoiceMessagePlayerEvent @@ -38,14 +37,6 @@ internal fun MessageComposerView( enableVoiceMessages: Boolean, modifier: Modifier = Modifier, ) { - val verificationViolation = state.roomMemberIdentityStateChanges.firstOrNull { - it.identityState == IdentityState.VerificationViolation - } - if (verificationViolation != null) { - DisabledComposerView(modifier = modifier) - return - } - val view = LocalView.current fun sendMessage() { state.eventSink(MessageComposerEvents.SendMessage) From 4fc23dcc194d7abc9344970ebd9f9c5b6b5671ef Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 18 Feb 2025 14:17:21 +0100 Subject: [PATCH 18/19] Move roomMemberIdentityStateChanges from ComposerState to MessagesState. --- .../android/features/messages/impl/MessagesPresenter.kt | 7 ++++++- .../android/features/messages/impl/MessagesState.kt | 2 ++ .../features/messages/impl/MessagesStateProvider.kt | 4 ++++ .../element/android/features/messages/impl/MessagesView.kt | 2 +- .../impl/messagecomposer/MessageComposerPresenter.kt | 6 ------ .../messages/impl/messagecomposer/MessageComposerState.kt | 2 -- .../impl/messagecomposer/MessageComposerStateProvider.kt | 3 --- 7 files changed, 13 insertions(+), 13 deletions(-) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesPresenter.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesPresenter.kt index 547f5f6161d..6b1f50c41d5 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesPresenter.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesPresenter.kt @@ -33,6 +33,7 @@ import io.element.android.features.messages.impl.actionlist.model.TimelineItemAc import io.element.android.features.messages.impl.crypto.identity.IdentityChangeState import io.element.android.features.messages.impl.messagecomposer.MessageComposerEvents import io.element.android.features.messages.impl.messagecomposer.MessageComposerState +import io.element.android.features.messages.impl.messagecomposer.observeRoomMemberIdentityStateChange import io.element.android.features.messages.impl.pinned.banner.PinnedMessagesBannerState import io.element.android.features.messages.impl.timeline.TimelineController import io.element.android.features.messages.impl.timeline.TimelineEvents @@ -78,6 +79,7 @@ import io.element.android.libraries.matrix.ui.model.getAvatarData import io.element.android.libraries.textcomposer.model.MessageComposerMode import io.element.android.libraries.ui.strings.CommonStrings import io.element.android.services.analytics.api.AnalyticsService +import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.toPersistentList import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch @@ -154,7 +156,9 @@ class MessagesPresenter @AssistedInject constructor( var hasDismissedInviteDialog by rememberSaveable { mutableStateOf(false) } - + val roomMemberIdentityStateChanges by produceState(persistentListOf()) { + observeRoomMemberIdentityStateChange(room) + } LaunchedEffect(Unit) { // Remove the unread flag on entering but don't send read receipts // as those will be handled by the timeline. @@ -211,6 +215,7 @@ class MessagesPresenter @AssistedInject constructor( roomAvatar = roomAvatar, heroes = heroes, composerState = composerState, + roomMemberIdentityStateChanges = roomMemberIdentityStateChanges, userEventPermissions = userEventPermissions, voiceMessageComposerState = voiceMessageComposerState, timelineState = timelineState, diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesState.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesState.kt index 5db91b30459..61f092eaf93 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesState.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesState.kt @@ -10,6 +10,7 @@ package io.element.android.features.messages.impl import androidx.compose.runtime.Immutable import io.element.android.features.messages.impl.actionlist.ActionListState import io.element.android.features.messages.impl.crypto.identity.IdentityChangeState +import io.element.android.features.messages.impl.crypto.identity.RoomMemberIdentityStateChange import io.element.android.features.messages.impl.messagecomposer.MessageComposerState import io.element.android.features.messages.impl.pinned.banner.PinnedMessagesBannerState import io.element.android.features.messages.impl.timeline.TimelineState @@ -33,6 +34,7 @@ data class MessagesState( val heroes: ImmutableList, val userEventPermissions: UserEventPermissions, val composerState: MessageComposerState, + val roomMemberIdentityStateChanges: ImmutableList, val voiceMessageComposerState: VoiceMessageComposerState, val timelineState: TimelineState, val timelineProtectionState: TimelineProtectionState, diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesStateProvider.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesStateProvider.kt index 308efd8eac0..a73a4c15658 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesStateProvider.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesStateProvider.kt @@ -11,6 +11,7 @@ import androidx.compose.ui.tooling.preview.PreviewParameterProvider import io.element.android.features.messages.impl.actionlist.ActionListState import io.element.android.features.messages.impl.actionlist.anActionListState import io.element.android.features.messages.impl.crypto.identity.IdentityChangeState +import io.element.android.features.messages.impl.crypto.identity.RoomMemberIdentityStateChange import io.element.android.features.messages.impl.crypto.identity.anIdentityChangeState import io.element.android.features.messages.impl.messagecomposer.MessageComposerState import io.element.android.features.messages.impl.messagecomposer.aMessageComposerState @@ -41,6 +42,7 @@ import io.element.android.libraries.designsystem.components.avatar.AvatarSize import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.textcomposer.model.MessageComposerMode import io.element.android.libraries.textcomposer.model.aTextEditorStateRich +import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.persistentSetOf @@ -92,6 +94,7 @@ fun aMessagesState( isFullScreen = false, mode = MessageComposerMode.Normal, ), + roomMemberIdentityStateChanges: ImmutableList = persistentListOf(), voiceMessageComposerState: VoiceMessageComposerState = aVoiceMessageComposerState(), timelineState: TimelineState = aTimelineState( timelineItems = aTimelineItemList(aTimelineItemTextContent()), @@ -117,6 +120,7 @@ fun aMessagesState( heroes = persistentListOf(), userEventPermissions = userEventPermissions, composerState = composerState, + roomMemberIdentityStateChanges = roomMemberIdentityStateChanges, voiceMessageComposerState = voiceMessageComposerState, timelineProtectionState = timelineProtectionState, identityChangeState = identityChangeState, diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesView.kt index 26f9d268a74..1a31ddd9a01 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesView.kt @@ -427,7 +427,7 @@ private fun MessagesViewComposerBottomSheetContents( onLinkClick = onLinkClick, ) } - val verificationViolation = state.composerState.roomMemberIdentityStateChanges.firstOrNull { + val verificationViolation = state.roomMemberIdentityStateChanges.firstOrNull { it.identityState == IdentityState.VerificationViolation } if (verificationViolation != null) { diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerPresenter.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerPresenter.kt index 16b908a7b53..f3a44559bc1 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerPresenter.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerPresenter.kt @@ -18,7 +18,6 @@ import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateListOf import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.produceState import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.rememberUpdatedState @@ -391,10 +390,6 @@ class MessageComposerPresenter @AssistedInject constructor( } } - val roomMemberIdentityStateChange by produceState(persistentListOf()) { - observeRoomMemberIdentityStateChange(room) - } - return MessageComposerState( textEditorState = textEditorState, isFullScreen = isFullScreen.value, @@ -404,7 +399,6 @@ class MessageComposerPresenter @AssistedInject constructor( canShareLocation = canShareLocation.value, canCreatePoll = canCreatePoll.value, suggestions = suggestions.toPersistentList(), - roomMemberIdentityStateChanges = roomMemberIdentityStateChange, resolveMentionDisplay = resolveMentionDisplay, eventSink = { handleEvents(it) }, ) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerState.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerState.kt index 4bb0f7f6fd6..d86ba8d98c4 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerState.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerState.kt @@ -8,7 +8,6 @@ package io.element.android.features.messages.impl.messagecomposer import androidx.compose.runtime.Stable -import io.element.android.features.messages.impl.crypto.identity.RoomMemberIdentityStateChange import io.element.android.libraries.textcomposer.mentions.ResolvedSuggestion import io.element.android.libraries.textcomposer.model.MessageComposerMode import io.element.android.libraries.textcomposer.model.TextEditorState @@ -25,7 +24,6 @@ data class MessageComposerState( val canShareLocation: Boolean, val canCreatePoll: Boolean, val suggestions: ImmutableList, - val roomMemberIdentityStateChanges: ImmutableList, val resolveMentionDisplay: (String, String) -> TextDisplay, val eventSink: (MessageComposerEvents) -> Unit, ) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerStateProvider.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerStateProvider.kt index 4e0a6df5c9b..c0ea895618e 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerStateProvider.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerStateProvider.kt @@ -8,7 +8,6 @@ package io.element.android.features.messages.impl.messagecomposer import androidx.compose.ui.tooling.preview.PreviewParameterProvider -import io.element.android.features.messages.impl.crypto.identity.RoomMemberIdentityStateChange import io.element.android.libraries.textcomposer.mentions.ResolvedSuggestion import io.element.android.libraries.textcomposer.model.MessageComposerMode import io.element.android.libraries.textcomposer.model.TextEditorState @@ -33,7 +32,6 @@ fun aMessageComposerState( canShareLocation: Boolean = true, canCreatePoll: Boolean = true, suggestions: ImmutableList = persistentListOf(), - identityStates: ImmutableList = persistentListOf(), eventSink: (MessageComposerEvents) -> Unit = {}, ) = MessageComposerState( textEditorState = textEditorState, @@ -44,7 +42,6 @@ fun aMessageComposerState( canShareLocation = canShareLocation, canCreatePoll = canCreatePoll, suggestions = suggestions, - roomMemberIdentityStateChanges = identityStates, resolveMentionDisplay = { _, _ -> TextDisplay.Plain }, eventSink = eventSink, ) From a145c155479174a0ffd92720d7f4d4e947c768dc Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 18 Feb 2025 14:18:24 +0100 Subject: [PATCH 19/19] typo --- .../impl/crypto/identity/IdentityChangeStatePresenterTest.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/crypto/identity/IdentityChangeStatePresenterTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/crypto/identity/IdentityChangeStatePresenterTest.kt index 934bff7f49b..29f64b831f6 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/crypto/identity/IdentityChangeStatePresenterTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/crypto/identity/IdentityChangeStatePresenterTest.kt @@ -149,7 +149,7 @@ class IdentityChangeStatePresenterTest { } @Test - fun `present - when the user withdraw the identity, the presenter invokes the encryption service api`() = + fun `present - when the user withdraws the identity, the presenter invokes the encryption service api`() = runTest { val lambda = lambdaRecorder> { Result.success(Unit) } val encryptionService = FakeEncryptionService(