Show an icon in the room header for shared history (#6090)

Add a decoration to the header for encrypted rooms with `history_visibility:
{shared|public}`.

Fixes: #6070

---------

Co-authored-by: ElementBot <android@element.io>
Co-authored-by: Jorge Martín <jorgem@element.io>
This commit is contained in:
Richard van der Hoff
2026-01-27 11:31:01 +00:00
committed by GitHub
parent 5cfb69f0fa
commit 7dae283874
9 changed files with 61 additions and 4 deletions

View File

@@ -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,
)

View File

@@ -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
) {

View File

@@ -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,
)

View File

@@ -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,

View File

@@ -63,6 +63,7 @@ internal fun MessagesViewTopBar(
heroes: ImmutableList<AvatarData>,
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<AvatarData> = 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,
)
}
}

View File

@@ -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,

View File

@@ -334,6 +334,7 @@ Reason: %1$s."</string>
<string name="common_server_url">"Server URL"</string>
<string name="common_settings">"Settings"</string>
<string name="common_share_space">"Share space"</string>
<string name="common_shared_history">"New members see history"</string>
<string name="common_shared_location">"Shared location"</string>
<string name="common_shared_space">"Shared space"</string>
<string name="common_signing_out">"Signing out"</string>