From 7dae283874293d06eacd94589788a2e3fe4ba366 Mon Sep 17 00:00:00 2001 From: Richard van der Hoff <1389908+richvdh@users.noreply.github.com> Date: Tue, 27 Jan 2026 11:31:01 +0000 Subject: [PATCH] Show an icon in the room header for shared history (#6090) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add a decoration to the header for encrypted rooms with `history_visibility: {shared|public}`. Fixes: #6070 --------- Co-authored-by: ElementBot Co-authored-by: Jorge Martín --- .../messages/impl/MessagesPresenter.kt | 12 ++++++++++ .../features/messages/impl/MessagesState.kt | 2 ++ .../messages/impl/MessagesStateProvider.kt | 2 ++ .../features/messages/impl/MessagesView.kt | 1 + .../impl/topbars/MessagesViewTopBar.kt | 17 ++++++++++++++ .../messages/impl/MessagesPresenterTest.kt | 22 +++++++++++++++++++ .../src/main/res/values/localazy.xml | 1 + ...pl.topbars_MessagesViewTopBar_Day_0_en.png | 4 ++-- ....topbars_MessagesViewTopBar_Night_0_en.png | 4 ++-- 9 files changed, 61 insertions(+), 4 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 c1f8effd64..93f7746858 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 @@ -75,6 +75,7 @@ import io.element.android.libraries.matrix.api.permalink.PermalinkParser import io.element.android.libraries.matrix.api.room.JoinedRoom import io.element.android.libraries.matrix.api.room.RoomInfo import io.element.android.libraries.matrix.api.room.RoomMembersState +import io.element.android.libraries.matrix.api.room.history.RoomHistoryVisibility import io.element.android.libraries.matrix.api.room.isDm import io.element.android.libraries.matrix.api.room.powerlevels.permissionsAsState import io.element.android.libraries.matrix.api.timeline.item.event.EventOrTransactionId @@ -207,6 +208,16 @@ class MessagesPresenter( val dmRoomMember by room.getDirectRoomMember(membersState) val roomMemberIdentityStateChanges = identityChangeState.roomMemberIdentityStateChanges + val isKeyShareOnInviteEnabled by featureFlagService.isFeatureEnabledFlow(FeatureFlags.EnableKeyShareOnInvite).collectAsState(initial = false) + // The top bar should show a "history" icon if: + // * History sharing is enabled, + // * The room is encrypted, and: + // * The room's history_visibility allows future users to see content. + val showSharedHistoryIcon = isKeyShareOnInviteEnabled && + roomInfo.isEncrypted == true && + (roomInfo.historyVisibility == RoomHistoryVisibility.Shared || + roomInfo.historyVisibility == RoomHistoryVisibility.WorldReadable) + LifecycleResumeEffect(dmRoomMember, roomInfo.isEncrypted) { if (roomInfo.isEncrypted == true) { val dmRoomMemberId = dmRoomMember?.userId @@ -290,6 +301,7 @@ class MessagesPresenter( pinnedMessagesBannerState = pinnedMessagesBannerState, dmUserVerificationState = dmUserVerificationState, roomMemberModerationState = roomMemberModerationState, + showSharedHistoryIcon = showSharedHistoryIcon, successorRoom = roomInfo.successorRoom, eventSink = ::handleEvent, ) 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 f7868806c1..18d3c17a81 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 @@ -56,6 +56,8 @@ data class MessagesState( val pinnedMessagesBannerState: PinnedMessagesBannerState, val dmUserVerificationState: IdentityState?, val roomMemberModerationState: RoomMemberModerationState, + /** Should the top bar include the "history" icon? */ + val showSharedHistoryIcon: Boolean, val successorRoom: SuccessorRoom?, val eventSink: (MessagesEvent) -> Unit ) { 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 edf61d90cd..592aab6d35 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 @@ -132,6 +132,7 @@ fun aMessagesState( pinnedMessagesBannerState: PinnedMessagesBannerState = aLoadedPinnedMessagesBannerState(), dmUserVerificationState: IdentityState? = null, roomMemberModerationState: RoomMemberModerationState = aRoomMemberModerationState(), + showSharedHistoryIcon: Boolean = false, successorRoom: SuccessorRoom? = null, eventSink: (MessagesEvent) -> Unit = {}, ) = MessagesState( @@ -160,6 +161,7 @@ fun aMessagesState( pinnedMessagesBannerState = pinnedMessagesBannerState, dmUserVerificationState = dmUserVerificationState, roomMemberModerationState = roomMemberModerationState, + showSharedHistoryIcon = showSharedHistoryIcon, successorRoom = successorRoom, eventSink = eventSink, ) 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 437e652cff..95f7333845 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 @@ -226,6 +226,7 @@ fun MessagesView( heroes = state.heroes, roomCallState = state.roomCallState, dmUserIdentityState = state.dmUserVerificationState, + showSharedHistoryIcon = state.showSharedHistoryIcon, onBackClick = { hidingKeyboard { onBackClick() } }, onRoomDetailsClick = { hidingKeyboard { onRoomDetailsClick() } }, onJoinCallClick = onJoinCallClick, diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/topbars/MessagesViewTopBar.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/topbars/MessagesViewTopBar.kt index 7727b28398..eb756a7369 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/topbars/MessagesViewTopBar.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/topbars/MessagesViewTopBar.kt @@ -63,6 +63,7 @@ internal fun MessagesViewTopBar( heroes: ImmutableList, roomCallState: RoomCallState, dmUserIdentityState: IdentityState?, + showSharedHistoryIcon: Boolean, onRoomDetailsClick: () -> Unit, onJoinCallClick: () -> Unit, onBackClick: () -> Unit, @@ -108,6 +109,14 @@ internal fun MessagesViewTopBar( } else -> Unit } + + if (showSharedHistoryIcon) { + Icon( + imageVector = CompoundIcons.History(), + tint = ElementTheme.colors.iconInfoPrimary, + contentDescription = stringResource(CommonStrings.common_shared_history), + ) + } } }, actions = { @@ -169,6 +178,7 @@ internal fun MessagesViewTopBarPreview() = ElementPreview { heroes: ImmutableList = persistentListOf(), roomCallState: RoomCallState = RoomCallState.Unavailable, dmUserIdentityState: IdentityState? = null, + showSharedHistoryIcon: Boolean = false, ) = MessagesViewTopBar( roomName = roomName, roomAvatar = roomAvatar, @@ -176,6 +186,7 @@ internal fun MessagesViewTopBarPreview() = ElementPreview { heroes = heroes, roomCallState = roomCallState, dmUserIdentityState = dmUserIdentityState, + showSharedHistoryIcon = showSharedHistoryIcon, onRoomDetailsClick = {}, onJoinCallClick = {}, onBackClick = {}, @@ -208,5 +219,11 @@ internal fun MessagesViewTopBarPreview() = ElementPreview { isTombstoned = true, dmUserIdentityState = IdentityState.VerificationViolation ) + HorizontalDivider() + AMessagesViewTopBar( + roomName = "A DM with shared history", + dmUserIdentityState = IdentityState.Verified, + showSharedHistoryIcon = true, + ) } } diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/MessagesPresenterTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/MessagesPresenterTest.kt index 15331f2ba5..8a4f5aad58 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/MessagesPresenterTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/MessagesPresenterTest.kt @@ -65,6 +65,7 @@ import io.element.android.libraries.matrix.api.room.MessageEventType import io.element.android.libraries.matrix.api.room.RoomMembersState import io.element.android.libraries.matrix.api.room.RoomMembershipState import io.element.android.libraries.matrix.api.room.StateEventType +import io.element.android.libraries.matrix.api.room.history.RoomHistoryVisibility import io.element.android.libraries.matrix.api.room.tombstone.SuccessorRoom import io.element.android.libraries.matrix.api.timeline.Timeline import io.element.android.libraries.matrix.api.timeline.item.TimelineItemDebugInfo @@ -1216,6 +1217,27 @@ class MessagesPresenterTest { } } + @Test + fun `present - shows a "history" icon if the room is encrypted and history is shared`() = runTest { + val presenter = createMessagesPresenter( + joinedRoom = FakeJoinedRoom( + baseRoom = FakeBaseRoom( + roomPermissions = roomPermissions(), + initialRoomInfo = aRoomInfo(isEncrypted = true, historyVisibility = RoomHistoryVisibility.Shared), + ), + ), + featureFlagService = FakeFeatureFlagService( + initialState = mapOf(FeatureFlags.EnableKeyShareOnInvite.key to true) + ) + ) + presenter.testWithLifecycleOwner { + awaitItem() + runCurrent() + val state = awaitItem() + assertThat(state.showSharedHistoryIcon).isTrue() + } + } + private fun roomPermissions( canStartCall: Boolean = true, canRedactOther: Boolean = true, diff --git a/libraries/ui-strings/src/main/res/values/localazy.xml b/libraries/ui-strings/src/main/res/values/localazy.xml index 9c4a01207b..e0e1af7bf3 100644 --- a/libraries/ui-strings/src/main/res/values/localazy.xml +++ b/libraries/ui-strings/src/main/res/values/localazy.xml @@ -334,6 +334,7 @@ Reason: %1$s." "Server URL" "Settings" "Share space" + "New members see history" "Shared location" "Shared space" "Signing out" diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.topbars_MessagesViewTopBar_Day_0_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.topbars_MessagesViewTopBar_Day_0_en.png index 899bf4ad75..4f36513ee3 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.topbars_MessagesViewTopBar_Day_0_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.topbars_MessagesViewTopBar_Day_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:dca37712b4df739fea42e8070761c431da4163732caa1d005bf50fd98b79764c -size 39730 +oid sha256:2ca04a8092b40b50f3724bca9213ac2be4742c6b754ef949566fd23b052ab808 +size 46501 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.topbars_MessagesViewTopBar_Night_0_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.topbars_MessagesViewTopBar_Night_0_en.png index de5049cae0..84dcdeb6a6 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.topbars_MessagesViewTopBar_Night_0_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.topbars_MessagesViewTopBar_Night_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e1bea37c40c43b5887cc87e0ed95552a5e42cf0b776a1342d66b2f0b2dd7836c -size 38283 +oid sha256:cbca529b51092d41afbd772cb2ed207709423c5590588a128a7c48bfad64917b +size 45275