From 7414dfaa4d0fdbd0ce36cd6e6ac6a3edefc3e1ce Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 26 Dec 2023 15:27:08 +0100 Subject: [PATCH] Hide add more reaction button if user do not have permission to send reaction #2093 Also: - move `userHasPermissionToSendMessage` to `TimelineRoomInfo` - remove `canReply` parameter which can be computed from other params. --- .../impl/timeline/TimelinePresenter.kt | 15 +++++++--- .../messages/impl/timeline/TimelineState.kt | 3 +- .../impl/timeline/TimelineStateProvider.kt | 3 +- .../messages/impl/timeline/TimelineView.kt | 1 - .../components/ATimelineItemEventRow.kt | 1 - .../components/TimelineItemEventRow.kt | 4 ++- .../TimelineItemGroupedEventsRow.kt | 1 - .../components/TimelineItemReactionsLayout.kt | 19 ++++++------ .../components/TimelineItemReactionsView.kt | 29 +++++++++++++------ .../timeline/components/TimelineItemRow.kt | 5 +--- 10 files changed, 48 insertions(+), 33 deletions(-) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelinePresenter.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelinePresenter.kt index 8613f2404e..37d2d7d2a6 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelinePresenter.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelinePresenter.kt @@ -98,6 +98,7 @@ class TimelinePresenter @AssistedInject constructor( val paginationState by timeline.paginationState.collectAsState() val syncUpdateFlow = room.syncUpdateFlow.collectAsState() val userHasPermissionToSendMessage by room.canSendMessageAsState(type = MessageEventType.ROOM_MESSAGE, updateKey = syncUpdateFlow.value) + val userHasPermissionToSendReaction by room.canSendMessageAsState(type = MessageEventType.REACTION_SENT, updateKey = syncUpdateFlow.value) val prevMostRecentItemId = rememberSaveable { mutableStateOf(null) } val newItemState = remember { mutableStateOf(NewEventState.None) } @@ -175,12 +176,18 @@ class TimelinePresenter @AssistedInject constructor( .launchIn(this) } + val timelineRoomInfo by remember { + derivedStateOf { + TimelineRoomInfo( + isDirect = room.isDirect, + userHasPermissionToSendMessage = userHasPermissionToSendMessage, + userHasPermissionToSendReaction = userHasPermissionToSendReaction, + ) + } + } return TimelineState( - timelineRoomInfo = TimelineRoomInfo( - isDirect = room.isDirect - ), + timelineRoomInfo = timelineRoomInfo, highlightedEventId = highlightedEventId.value, - userHasPermissionToSendMessage = userHasPermissionToSendMessage, paginationState = paginationState, timelineItems = timelineItems, showReadReceipts = readReceiptsEnabled, diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineState.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineState.kt index d334aebf8c..abf7e626a3 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineState.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineState.kt @@ -30,7 +30,6 @@ data class TimelineState( val timelineRoomInfo: TimelineRoomInfo, val showReadReceipts: Boolean, val highlightedEventId: EventId?, - val userHasPermissionToSendMessage: Boolean, val paginationState: MatrixTimeline.PaginationState, val newEventState: NewEventState, val sessionState: SessionState, @@ -40,4 +39,6 @@ data class TimelineState( @Immutable data class TimelineRoomInfo( val isDirect: Boolean, + val userHasPermissionToSendMessage: Boolean, + val userHasPermissionToSendReaction: Boolean, ) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineStateProvider.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineStateProvider.kt index 63d8f0ed01..80d98b070d 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineStateProvider.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineStateProvider.kt @@ -55,7 +55,6 @@ fun aTimelineState(timelineItems: ImmutableList = persistentListOf beginningOfRoomReached = false, ), highlightedEventId = null, - userHasPermissionToSendMessage = true, newEventState = NewEventState.None, sessionState = aSessionState( isSessionVerified = true, @@ -218,4 +217,6 @@ internal fun aTimelineRoomInfo( isDirect: Boolean = false, ) = TimelineRoomInfo( isDirect = isDirect, + userHasPermissionToSendMessage = true, + userHasPermissionToSendReaction = true, ) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineView.kt index bc285b0bc5..137d18d8bc 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineView.kt @@ -123,7 +123,6 @@ fun TimelineView( isLastOutgoingMessage = (timelineItem as? TimelineItem.Event)?.isMine == true && state.timelineItems.first().identifier() == timelineItem.identifier(), highlightedItem = state.highlightedEventId?.value, - userHasPermissionToSendMessage = state.userHasPermissionToSendMessage, onClick = onMessageClicked, onLongClick = onMessageLongClicked, onUserDataClick = onUserDataClicked, diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/ATimelineItemEventRow.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/ATimelineItemEventRow.kt index a2a0ae5f4e..6c65ecf828 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/ATimelineItemEventRow.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/ATimelineItemEventRow.kt @@ -35,7 +35,6 @@ internal fun ATimelineItemEventRow( showReadReceipts = showReadReceipts, isLastOutgoingMessage = isLastOutgoingMessage, isHighlighted = isHighlighted, - canReply = true, onClick = {}, onLongClick = {}, onUserDataClick = {}, diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRow.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRow.kt index d22cfedae7..19fc7dff0d 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRow.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRow.kt @@ -81,6 +81,7 @@ import io.element.android.features.messages.impl.timeline.model.event.TimelineIt import io.element.android.features.messages.impl.timeline.model.event.TimelineItemVideoContent import io.element.android.features.messages.impl.timeline.model.event.aTimelineItemImageContent import io.element.android.features.messages.impl.timeline.model.event.aTimelineItemTextContent +import io.element.android.features.messages.impl.timeline.model.event.canBeRepliedTo import io.element.android.features.messages.impl.timeline.model.metadata import io.element.android.libraries.androidutils.system.openUrlInExternalApp import io.element.android.libraries.designsystem.colors.AvatarColorsProvider @@ -112,7 +113,6 @@ fun TimelineItemEventRow( showReadReceipts: Boolean, isLastOutgoingMessage: Boolean, isHighlighted: Boolean, - canReply: Boolean, onClick: () -> Unit, onLongClick: () -> Unit, onUserDataClick: (UserId) -> Unit, @@ -151,6 +151,7 @@ fun TimelineItemEventRow( } else { Spacer(modifier = Modifier.height(2.dp)) } + val canReply = timelineRoomInfo.userHasPermissionToSendMessage && event.content.canBeRepliedTo() if (canReply) { val state: SwipeableActionsState = rememberSwipeableActionsState() val offset = state.offset.floatValue @@ -335,6 +336,7 @@ private fun TimelineItemEventRowContent( if (event.reactionsState.reactions.isNotEmpty()) { TimelineItemReactionsView( reactionsState = event.reactionsState, + userCanSendReaction = timelineRoomInfo.userHasPermissionToSendReaction, isOutgoing = event.isMine, onReactionClicked = onReactionClicked, onReactionLongClicked = onReactionLongClicked, diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemGroupedEventsRow.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemGroupedEventsRow.kt index d52795c052..76462e35a9 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemGroupedEventsRow.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemGroupedEventsRow.kt @@ -131,7 +131,6 @@ private fun TimelineItemGroupedEventsRowContent( isLastOutgoingMessage = isLastOutgoingMessage, highlightedItem = highlightedItem, sessionState = sessionState, - userHasPermissionToSendMessage = false, onClick = onClick, onLongClick = onLongClick, inReplyToClick = inReplyToClick, diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemReactionsLayout.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemReactionsLayout.kt index d30a60c7a4..60fda07d35 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemReactionsLayout.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemReactionsLayout.kt @@ -26,8 +26,8 @@ import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.IntOffset import androidx.compose.ui.unit.dp import io.element.android.features.messages.impl.R -import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.designsystem.preview.ElementPreview +import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.designsystem.utils.CommonDrawables /** @@ -46,7 +46,7 @@ import io.element.android.libraries.designsystem.utils.CommonDrawables @Composable fun TimelineItemReactionsLayout( expandButton: @Composable () -> Unit, - addMoreButton: @Composable () -> Unit, + addMoreButton: (@Composable () -> Unit)?, modifier: Modifier = Modifier, itemSpacing: Dp = 0.dp, rowSpacing: Dp = 0.dp, @@ -82,21 +82,21 @@ fun TimelineItemReactionsLayout( // Used to render the collapsed state, this takes the rows inputted and adds the extra button to the last row, // removing only as many trailing reactions as needed to make space for it. - fun replaceTrailingItemsWithButtons(rowsIn: List>, expandButton: Placeable, addMoreButton: Placeable): List> { + fun replaceTrailingItemsWithButtons(rowsIn: List>, expandButton: Placeable, addMoreButton: Placeable?): List> { val rows = rowsIn.toMutableList() val lastRow = rows.last() - val buttonsWidth = expandButton.width + itemSpacing.toPx().toInt() + addMoreButton.width + val buttonsWidth = expandButton.width + itemSpacing.toPx().toInt() + (addMoreButton?.width ?: 0) var rowX = 0 lastRow.forEachIndexed { i, placeable -> val horizontalSpacing = if (i == 0) 0 else itemSpacing.toPx().toInt() rowX += placeable.width + horizontalSpacing if (rowX > constraints.maxWidth - (buttonsWidth + horizontalSpacing)) { - val lastRowWithButton = lastRow.take(i) + listOf(expandButton, addMoreButton) + val lastRowWithButton = lastRow.take(i) + listOfNotNull(expandButton, addMoreButton) rows[rows.size - 1] = lastRowWithButton return rows } } - val lastRowWithButton = lastRow + listOf(expandButton, addMoreButton) + val lastRowWithButton = lastRow + listOfNotNull(expandButton, addMoreButton) rows[rows.size - 1] = lastRowWithButton return rows } @@ -155,16 +155,15 @@ fun TimelineItemReactionsLayout( val newConstrains = constraints.copy(minHeight = maxHeight) reactionsPlaceables = subcompose(2, reactions).map { it.measure(newConstrains) } expandPlaceable = subcompose(3, expandButton).first().measure(newConstrains) - val addMorePlaceable = subcompose(4, addMoreButton).first().measure(newConstrains) - + val addMorePlaceable = addMoreButton?.let { subcompose(4, addMoreButton).first().measure(newConstrains) } // Calculate the layout of the rows with the reactions button and add more button - val reactionsAndAddMore = calculateRows(reactionsPlaceables + listOf(addMorePlaceable)) + val reactionsAndAddMore = calculateRows(reactionsPlaceables + listOfNotNull(addMorePlaceable)) // If we have extended beyond the defined number of rows we are showing the expand/collapse ui if (rowsBeforeCollapsible?.let { reactionsAndAddMore.size > it } == true) { if (expanded) { // Show all subviews with the add more button at the end - var reactionsAndButtons = calculateRows(reactionsPlaceables + listOf(expandPlaceable, addMorePlaceable)) + var reactionsAndButtons = calculateRows(reactionsPlaceables + listOfNotNull(expandPlaceable, addMorePlaceable)) reactionsAndButtons = ensureCollapseAndAddMoreButtonsAreOnTheSameRow(reactionsAndButtons) layoutRows(reactionsAndButtons) } else { diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemReactionsView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemReactionsView.kt index 764b4cdea4..0f5026f0a2 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemReactionsView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemReactionsView.kt @@ -31,8 +31,8 @@ import io.element.android.features.messages.impl.R import io.element.android.features.messages.impl.timeline.aTimelineItemReactions import io.element.android.features.messages.impl.timeline.model.AggregatedReaction import io.element.android.features.messages.impl.timeline.model.TimelineItemReactions -import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.designsystem.preview.ElementPreview +import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.designsystem.utils.CommonDrawables import kotlinx.collections.immutable.ImmutableList @@ -40,6 +40,7 @@ import kotlinx.collections.immutable.ImmutableList fun TimelineItemReactionsView( reactionsState: TimelineItemReactions, isOutgoing: Boolean, + userCanSendReaction: Boolean, onReactionClicked: (emoji: String) -> Unit, onReactionLongClicked: (emoji: String) -> Unit, onMoreReactionsClicked: () -> Unit, @@ -49,6 +50,7 @@ fun TimelineItemReactionsView( TimelineItemReactionsView( modifier = modifier, reactions = reactionsState.reactions, + userCanSendReaction = userCanSendReaction, expanded = expanded, isOutgoing = isOutgoing, onReactionClick = onReactionClicked, @@ -61,6 +63,7 @@ fun TimelineItemReactionsView( @Composable private fun TimelineItemReactionsView( reactions: ImmutableList, + userCanSendReaction: Boolean, isOutgoing: Boolean, expanded: Boolean, onReactionClick: (emoji: String) -> Unit, @@ -93,19 +96,26 @@ private fun TimelineItemReactionsView( onLongClick = {} ) }, - addMoreButton = { - MessagesReactionButton( - content = MessagesReactionsButtonContent.Icon(CommonDrawables.ic_add_reaction), - onClick = onMoreReactionsClick, - onLongClick = {} - ) - }, + addMoreButton = if (userCanSendReaction) { + { + MessagesReactionButton( + content = MessagesReactionsButtonContent.Icon(CommonDrawables.ic_add_reaction), + onClick = onMoreReactionsClick, + onLongClick = {} + ) + } + } else null, reactions = { reactions.forEach { reaction -> CompositionLocalProvider(LocalLayoutDirection provides currentLayout) { MessagesReactionButton( content = MessagesReactionsButtonContent.Reaction(reaction = reaction), - onClick = { onReactionClick(reaction.key) }, + onClick = { + // Always allow user to redact their own reactions + if (reaction.isHighlighted || userCanSendReaction) { + onReactionClick(reaction.key) + } + }, onLongClick = { onReactionLongClick(reaction.key) } ) } @@ -157,6 +167,7 @@ private fun ContentToPreview( reactionsState = TimelineItemReactions( reactions ), + userCanSendReaction = true, isOutgoing = isOutgoing, onReactionClicked = {}, onReactionLongClicked = {}, diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemRow.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemRow.kt index 92dbbe46b6..b7ab65e69e 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemRow.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemRow.kt @@ -18,11 +18,10 @@ package io.element.android.features.messages.impl.timeline.components import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier -import io.element.android.features.messages.impl.timeline.TimelineRoomInfo import io.element.android.features.messages.impl.timeline.TimelineEvents +import io.element.android.features.messages.impl.timeline.TimelineRoomInfo import io.element.android.features.messages.impl.timeline.model.TimelineItem import io.element.android.features.messages.impl.timeline.model.event.TimelineItemStateContent -import io.element.android.features.messages.impl.timeline.model.event.canBeRepliedTo import io.element.android.features.messages.impl.timeline.session.SessionState import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.core.UserId @@ -34,7 +33,6 @@ internal fun TimelineItemRow( showReadReceipts: Boolean, isLastOutgoingMessage: Boolean, highlightedItem: String?, - userHasPermissionToSendMessage: Boolean, sessionState: SessionState, onUserDataClick: (UserId) -> Unit, onClick: (TimelineItem.Event) -> Unit, @@ -77,7 +75,6 @@ internal fun TimelineItemRow( showReadReceipts = showReadReceipts, isLastOutgoingMessage = isLastOutgoingMessage, isHighlighted = highlightedItem == timelineItem.identifier(), - canReply = userHasPermissionToSendMessage && timelineItem.content.canBeRepliedTo(), onClick = { onClick(timelineItem) }, onLongClick = { onLongClick(timelineItem) }, onUserDataClick = onUserDataClick,