Send failure verified user : handle click on failed send icon.

This commit is contained in:
ganfra 2024-09-13 16:32:29 +02:00
parent b38f016677
commit 84c8ddacf7
16 changed files with 76 additions and 82 deletions

View file

@ -30,7 +30,6 @@ import io.element.android.features.messages.impl.actionlist.ActionListEvents
import io.element.android.features.messages.impl.actionlist.ActionListPresenter import io.element.android.features.messages.impl.actionlist.ActionListPresenter
import io.element.android.features.messages.impl.actionlist.model.TimelineItemAction import io.element.android.features.messages.impl.actionlist.model.TimelineItemAction
import io.element.android.features.messages.impl.actionlist.model.TimelineItemActionPostProcessor import io.element.android.features.messages.impl.actionlist.model.TimelineItemActionPostProcessor
import io.element.android.features.messages.impl.crypto.sendfailure.resolve.ResolveVerifiedUserSendFailureState
import io.element.android.features.messages.impl.messagecomposer.MessageComposerEvents import io.element.android.features.messages.impl.messagecomposer.MessageComposerEvents
import io.element.android.features.messages.impl.messagecomposer.MessageComposerPresenter import io.element.android.features.messages.impl.messagecomposer.MessageComposerPresenter
import io.element.android.features.messages.impl.messagecomposer.MessageComposerState import io.element.android.features.messages.impl.messagecomposer.MessageComposerState
@ -96,7 +95,6 @@ class MessagesPresenter @AssistedInject constructor(
private val reactionSummaryPresenter: ReactionSummaryPresenter, private val reactionSummaryPresenter: ReactionSummaryPresenter,
private val readReceiptBottomSheetPresenter: ReadReceiptBottomSheetPresenter, private val readReceiptBottomSheetPresenter: ReadReceiptBottomSheetPresenter,
private val pinnedMessagesBannerPresenter: Presenter<PinnedMessagesBannerState>, private val pinnedMessagesBannerPresenter: Presenter<PinnedMessagesBannerState>,
private val resolveVerifiedUserSendFailurePresenter: Presenter<ResolveVerifiedUserSendFailureState>,
private val networkMonitor: NetworkMonitor, private val networkMonitor: NetworkMonitor,
private val snackbarDispatcher: SnackbarDispatcher, private val snackbarDispatcher: SnackbarDispatcher,
private val dispatchers: CoroutineDispatchers, private val dispatchers: CoroutineDispatchers,
@ -130,7 +128,6 @@ class MessagesPresenter @AssistedInject constructor(
val reactionSummaryState = reactionSummaryPresenter.present() val reactionSummaryState = reactionSummaryPresenter.present()
val readReceiptBottomSheetState = readReceiptBottomSheetPresenter.present() val readReceiptBottomSheetState = readReceiptBottomSheetPresenter.present()
val pinnedMessagesBannerState = pinnedMessagesBannerPresenter.present() val pinnedMessagesBannerState = pinnedMessagesBannerPresenter.present()
val resolveVerifiedUserSendFailureState = resolveVerifiedUserSendFailurePresenter.present()
val syncUpdateFlow = room.syncUpdateFlow.collectAsState() val syncUpdateFlow = room.syncUpdateFlow.collectAsState()
@ -230,7 +227,6 @@ class MessagesPresenter @AssistedInject constructor(
appName = buildMeta.applicationName, appName = buildMeta.applicationName,
callState = callState, callState = callState,
pinnedMessagesBannerState = pinnedMessagesBannerState, pinnedMessagesBannerState = pinnedMessagesBannerState,
resolveVerifiedUserSendFailureState = resolveVerifiedUserSendFailureState,
eventSink = { handleEvents(it) } eventSink = { handleEvents(it) }
) )
} }

View file

@ -9,7 +9,6 @@ package io.element.android.features.messages.impl
import androidx.compose.runtime.Immutable import androidx.compose.runtime.Immutable
import io.element.android.features.messages.impl.actionlist.ActionListState import io.element.android.features.messages.impl.actionlist.ActionListState
import io.element.android.features.messages.impl.crypto.sendfailure.resolve.ResolveVerifiedUserSendFailureState
import io.element.android.features.messages.impl.messagecomposer.MessageComposerState 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.pinned.banner.PinnedMessagesBannerState
import io.element.android.features.messages.impl.timeline.TimelineState import io.element.android.features.messages.impl.timeline.TimelineState
@ -48,7 +47,6 @@ data class MessagesState(
val callState: RoomCallState, val callState: RoomCallState,
val appName: String, val appName: String,
val pinnedMessagesBannerState: PinnedMessagesBannerState, val pinnedMessagesBannerState: PinnedMessagesBannerState,
val resolveVerifiedUserSendFailureState: ResolveVerifiedUserSendFailureState,
val eventSink: (MessagesEvents) -> Unit val eventSink: (MessagesEvents) -> Unit
) )

View file

@ -10,8 +10,6 @@ package io.element.android.features.messages.impl
import androidx.compose.ui.tooling.preview.PreviewParameterProvider import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import io.element.android.features.messages.impl.actionlist.ActionListState 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.actionlist.anActionListState
import io.element.android.features.messages.impl.crypto.sendfailure.resolve.ResolveVerifiedUserSendFailureState
import io.element.android.features.messages.impl.crypto.sendfailure.resolve.aResolveVerifiedUserSendFailureState
import io.element.android.features.messages.impl.messagecomposer.AttachmentsState import io.element.android.features.messages.impl.messagecomposer.AttachmentsState
import io.element.android.features.messages.impl.messagecomposer.MessageComposerState import io.element.android.features.messages.impl.messagecomposer.MessageComposerState
import io.element.android.features.messages.impl.messagecomposer.aMessageComposerState import io.element.android.features.messages.impl.messagecomposer.aMessageComposerState
@ -115,7 +113,6 @@ fun aMessagesState(
enableVoiceMessages: Boolean = true, enableVoiceMessages: Boolean = true,
callState: RoomCallState = RoomCallState.ENABLED, callState: RoomCallState = RoomCallState.ENABLED,
pinnedMessagesBannerState: PinnedMessagesBannerState = aLoadedPinnedMessagesBannerState(), pinnedMessagesBannerState: PinnedMessagesBannerState = aLoadedPinnedMessagesBannerState(),
resolveVerifiedUserSendFailureState: ResolveVerifiedUserSendFailureState = aResolveVerifiedUserSendFailureState(),
eventSink: (MessagesEvents) -> Unit = {}, eventSink: (MessagesEvents) -> Unit = {},
) = MessagesState( ) = MessagesState(
roomId = RoomId("!id:domain"), roomId = RoomId("!id:domain"),
@ -140,7 +137,6 @@ fun aMessagesState(
callState = callState, callState = callState,
appName = "Element", appName = "Element",
pinnedMessagesBannerState = pinnedMessagesBannerState, pinnedMessagesBannerState = pinnedMessagesBannerState,
resolveVerifiedUserSendFailureState = resolveVerifiedUserSendFailureState,
eventSink = eventSink, eventSink = eventSink,
) )

View file

@ -57,8 +57,6 @@ import io.element.android.features.messages.impl.actionlist.ActionListEvents
import io.element.android.features.messages.impl.actionlist.ActionListView 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.actionlist.model.TimelineItemAction
import io.element.android.features.messages.impl.attachments.Attachment import io.element.android.features.messages.impl.attachments.Attachment
import io.element.android.features.messages.impl.crypto.sendfailure.resolve.ResolveVerifiedUserSendFailureEvents
import io.element.android.features.messages.impl.crypto.sendfailure.resolve.ResolveVerifiedUserSendFailureView
import io.element.android.features.messages.impl.messagecomposer.AttachmentsBottomSheet import io.element.android.features.messages.impl.messagecomposer.AttachmentsBottomSheet
import io.element.android.features.messages.impl.messagecomposer.AttachmentsState import io.element.android.features.messages.impl.messagecomposer.AttachmentsState
import io.element.android.features.messages.impl.messagecomposer.MessageComposerEvents import io.element.android.features.messages.impl.messagecomposer.MessageComposerEvents
@ -244,14 +242,10 @@ fun MessagesView(
}, },
onEmojiReactionClick = ::onEmojiReactionClick, onEmojiReactionClick = ::onEmojiReactionClick,
onVerifiedUserSendFailureClick = { event -> onVerifiedUserSendFailureClick = { event ->
state.resolveVerifiedUserSendFailureState.eventSink(ResolveVerifiedUserSendFailureEvents.ComputeForMessage(event)) state.timelineState.eventSink(TimelineEvents.ComputeVerifiedUserSendFailure(event))
}, },
) )
ResolveVerifiedUserSendFailureView(
state = state.resolveVerifiedUserSendFailureState,
)
CustomReactionBottomSheet( CustomReactionBottomSheet(
state = state.customReactionState, state = state.customReactionState,
onSelectEmoji = { uniqueId, emoji -> onSelectEmoji = { uniqueId, emoji ->

View file

@ -43,7 +43,7 @@ fun ResolveVerifiedUserSendFailureView(
state: ResolveVerifiedUserSendFailureState, state: ResolveVerifiedUserSendFailureState,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
) { ) {
val sheetState = rememberModalBottomSheetState() val sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true)
var showSheet by remember { mutableStateOf(false) } var showSheet by remember { mutableStateOf(false) }
fun dismiss() { fun dismiss() {

View file

@ -200,19 +200,18 @@ private fun PinnedMessagesListLoaded(
renderReadReceipts = false, renderReadReceipts = false,
isLastOutgoingMessage = false, isLastOutgoingMessage = false,
focusedEventId = null, focusedEventId = null,
onClick = onEventClick,
onLongClick = ::onMessageLongClick,
onUserDataClick = onUserDataClick, onUserDataClick = onUserDataClick,
onLinkClick = onLinkClick, onLinkClick = onLinkClick,
onClick = onEventClick,
onLongClick = ::onMessageLongClick,
inReplyToClick = {}, inReplyToClick = {},
onReactionClick = { _, _ -> }, onReactionClick = { _, _ -> },
onReactionLongClick = { _, _ -> }, onReactionLongClick = { _, _ -> },
onMoreReactionsClick = {}, onMoreReactionsClick = {},
onReadReceiptClick = {}, onReadReceiptClick = {},
eventSink = {},
onSwipeToReply = {}, onSwipeToReply = {},
onJoinCallClick = {}, onJoinCallClick = {},
onShieldClick = {}, eventSink = {},
eventContentView = { event, contentModifier, onContentLayoutChange -> eventContentView = { event, contentModifier, onContentLayoutChange ->
TimelineItemEventContentViewWrapper( TimelineItemEventContentViewWrapper(
event = event, event = event,

View file

@ -7,6 +7,7 @@
package io.element.android.features.messages.impl.timeline package io.element.android.features.messages.impl.timeline
import io.element.android.features.messages.impl.timeline.model.TimelineItem
import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.core.EventId
import io.element.android.libraries.matrix.api.timeline.Timeline import io.element.android.libraries.matrix.api.timeline.Timeline
import io.element.android.libraries.matrix.api.timeline.item.event.MessageShield import io.element.android.libraries.matrix.api.timeline.item.event.MessageShield
@ -19,7 +20,6 @@ sealed interface TimelineEvents {
data object OnFocusEventRender : TimelineEvents data object OnFocusEventRender : TimelineEvents
data object JumpToLive : TimelineEvents data object JumpToLive : TimelineEvents
data class ShowShieldDialog(val messageShield: MessageShield) : TimelineEvents
data object HideShieldDialog : TimelineEvents data object HideShieldDialog : TimelineEvents
/** /**
@ -27,6 +27,8 @@ sealed interface TimelineEvents {
*/ */
sealed interface EventFromTimelineItem : TimelineEvents sealed interface EventFromTimelineItem : TimelineEvents
data class ComputeVerifiedUserSendFailure(val event: TimelineItem.Event) : EventFromTimelineItem
data class ShowShieldDialog(val messageShield: MessageShield) : EventFromTimelineItem
data class LoadMore(val direction: Timeline.PaginationDirection) : EventFromTimelineItem data class LoadMore(val direction: Timeline.PaginationDirection) : EventFromTimelineItem
/** /**

View file

@ -21,6 +21,8 @@ import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject import dagger.assisted.AssistedInject
import io.element.android.features.messages.impl.MessagesNavigator import io.element.android.features.messages.impl.MessagesNavigator
import io.element.android.features.messages.impl.crypto.sendfailure.resolve.ResolveVerifiedUserSendFailureEvents
import io.element.android.features.messages.impl.crypto.sendfailure.resolve.ResolveVerifiedUserSendFailureState
import io.element.android.features.messages.impl.timeline.factories.TimelineItemsFactory import io.element.android.features.messages.impl.timeline.factories.TimelineItemsFactory
import io.element.android.features.messages.impl.timeline.factories.TimelineItemsFactoryConfig import io.element.android.features.messages.impl.timeline.factories.TimelineItemsFactoryConfig
import io.element.android.features.messages.impl.timeline.model.NewEventState import io.element.android.features.messages.impl.timeline.model.NewEventState
@ -66,6 +68,7 @@ class TimelinePresenter @AssistedInject constructor(
private val endPollAction: EndPollAction, private val endPollAction: EndPollAction,
private val sessionPreferencesStore: SessionPreferencesStore, private val sessionPreferencesStore: SessionPreferencesStore,
private val timelineController: TimelineController, private val timelineController: TimelineController,
private val resolveVerifiedUserSendFailurePresenter: Presenter<ResolveVerifiedUserSendFailureState>,
) : Presenter<TimelineState> { ) : Presenter<TimelineState> {
@AssistedFactory @AssistedFactory
interface Factory { interface Factory {
@ -101,6 +104,7 @@ class TimelinePresenter @AssistedInject constructor(
val newEventState = remember { mutableStateOf(NewEventState.None) } val newEventState = remember { mutableStateOf(NewEventState.None) }
val messageShield: MutableState<MessageShield?> = remember { mutableStateOf(null) } val messageShield: MutableState<MessageShield?> = remember { mutableStateOf(null) }
val resolveVerifiedUserSendFailureState = resolveVerifiedUserSendFailurePresenter.present()
val isSendPublicReadReceiptsEnabled by sessionPreferencesStore.isSendPublicReadReceiptsEnabled().collectAsState(initial = true) val isSendPublicReadReceiptsEnabled by sessionPreferencesStore.isSendPublicReadReceiptsEnabled().collectAsState(initial = true)
val renderReadReceipts by sessionPreferencesStore.isRenderReadReceiptsEnabled().collectAsState(initial = true) val renderReadReceipts by sessionPreferencesStore.isRenderReadReceiptsEnabled().collectAsState(initial = true)
val isLive by timelineController.isLive().collectAsState(initial = true) val isLive by timelineController.isLive().collectAsState(initial = true)
@ -156,6 +160,9 @@ class TimelinePresenter @AssistedInject constructor(
} }
TimelineEvents.HideShieldDialog -> messageShield.value = null TimelineEvents.HideShieldDialog -> messageShield.value = null
is TimelineEvents.ShowShieldDialog -> messageShield.value = event.messageShield is TimelineEvents.ShowShieldDialog -> messageShield.value = event.messageShield
is TimelineEvents.ComputeVerifiedUserSendFailure -> {
resolveVerifiedUserSendFailureState.eventSink(ResolveVerifiedUserSendFailureEvents.ComputeForMessage(event.event))
}
} }
} }
@ -232,6 +239,7 @@ class TimelinePresenter @AssistedInject constructor(
isLive = isLive, isLive = isLive,
focusRequestState = focusRequestState.value, focusRequestState = focusRequestState.value,
messageShield = messageShield.value, messageShield = messageShield.value,
resolveVerifiedUserSendFailureState = resolveVerifiedUserSendFailureState,
eventSink = { handleEvents(it) } eventSink = { handleEvents(it) }
) )
} }

View file

@ -8,6 +8,7 @@
package io.element.android.features.messages.impl.timeline package io.element.android.features.messages.impl.timeline
import androidx.compose.runtime.Immutable import androidx.compose.runtime.Immutable
import io.element.android.features.messages.impl.crypto.sendfailure.resolve.ResolveVerifiedUserSendFailureState
import io.element.android.features.messages.impl.timeline.model.NewEventState import io.element.android.features.messages.impl.timeline.model.NewEventState
import io.element.android.features.messages.impl.timeline.model.TimelineItem import io.element.android.features.messages.impl.timeline.model.TimelineItem
import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.core.EventId
@ -25,6 +26,7 @@ data class TimelineState(
val focusRequestState: FocusRequestState, val focusRequestState: FocusRequestState,
// If not null, info will be rendered in a dialog // If not null, info will be rendered in a dialog
val messageShield: MessageShield?, val messageShield: MessageShield?,
val resolveVerifiedUserSendFailureState: ResolveVerifiedUserSendFailureState,
val eventSink: (TimelineEvents) -> Unit, val eventSink: (TimelineEvents) -> Unit,
) { ) {
val hasAnyEvent = timelineItems.any { it is TimelineItem.Event } val hasAnyEvent = timelineItems.any { it is TimelineItem.Event }

View file

@ -7,6 +7,8 @@
package io.element.android.features.messages.impl.timeline package io.element.android.features.messages.impl.timeline
import io.element.android.features.messages.impl.crypto.sendfailure.resolve.ResolveVerifiedUserSendFailureState
import io.element.android.features.messages.impl.crypto.sendfailure.resolve.aResolveVerifiedUserSendFailureState
import io.element.android.features.messages.impl.timeline.components.receipt.aReadReceiptData import io.element.android.features.messages.impl.timeline.components.receipt.aReadReceiptData
import io.element.android.features.messages.impl.timeline.model.NewEventState import io.element.android.features.messages.impl.timeline.model.NewEventState
import io.element.android.features.messages.impl.timeline.model.ReadReceiptData import io.element.android.features.messages.impl.timeline.model.ReadReceiptData
@ -44,6 +46,7 @@ fun aTimelineState(
focusedEventIndex: Int = -1, focusedEventIndex: Int = -1,
isLive: Boolean = true, isLive: Boolean = true,
messageShield: MessageShield? = null, messageShield: MessageShield? = null,
resolveVerifiedUserSendFailureState: ResolveVerifiedUserSendFailureState = aResolveVerifiedUserSendFailureState(),
eventSink: (TimelineEvents) -> Unit = {}, eventSink: (TimelineEvents) -> Unit = {},
): TimelineState { ): TimelineState {
val focusedEventId = timelineItems.filterIsInstance<TimelineItem.Event>().getOrNull(focusedEventIndex)?.eventId val focusedEventId = timelineItems.filterIsInstance<TimelineItem.Event>().getOrNull(focusedEventIndex)?.eventId
@ -60,6 +63,7 @@ fun aTimelineState(
isLive = isLive, isLive = isLive,
focusRequestState = focusRequestState, focusRequestState = focusRequestState,
messageShield = messageShield, messageShield = messageShield,
resolveVerifiedUserSendFailureState = resolveVerifiedUserSendFailureState,
eventSink = eventSink, eventSink = eventSink,
) )
} }

View file

@ -48,6 +48,7 @@ import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import io.element.android.compound.theme.ElementTheme import io.element.android.compound.theme.ElementTheme
import io.element.android.compound.tokens.generated.CompoundIcons import io.element.android.compound.tokens.generated.CompoundIcons
import io.element.android.features.messages.impl.crypto.sendfailure.resolve.ResolveVerifiedUserSendFailureView
import io.element.android.features.messages.impl.timeline.components.TimelineItemRow import io.element.android.features.messages.impl.timeline.components.TimelineItemRow
import io.element.android.features.messages.impl.timeline.components.toText import io.element.android.features.messages.impl.timeline.components.toText
import io.element.android.features.messages.impl.timeline.di.LocalTimelineItemPresenterFactories import io.element.android.features.messages.impl.timeline.di.LocalTimelineItemPresenterFactories
@ -127,8 +128,8 @@ fun TimelineView(
Box(modifier) { Box(modifier) {
LazyColumn( LazyColumn(
modifier = Modifier modifier = Modifier
.fillMaxSize() .fillMaxSize()
.nestedScroll(nestedScrollConnection), .nestedScroll(nestedScrollConnection),
state = lazyListState, state = lazyListState,
reverseLayout = useReverseLayout, reverseLayout = useReverseLayout,
contentPadding = PaddingValues(vertical = 8.dp), contentPadding = PaddingValues(vertical = 8.dp),
@ -150,19 +151,18 @@ fun TimelineView(
isLastOutgoingMessage = (timelineItem as? TimelineItem.Event)?.isMine == true && isLastOutgoingMessage = (timelineItem as? TimelineItem.Event)?.isMine == true &&
state.timelineItems.first().identifier() == timelineItem.identifier(), state.timelineItems.first().identifier() == timelineItem.identifier(),
focusedEventId = state.focusedEventId, focusedEventId = state.focusedEventId,
onClick = onMessageClick,
onLongClick = onMessageLongClick,
onShieldClick = ::onShieldClick,
onUserDataClick = onUserDataClick, onUserDataClick = onUserDataClick,
onLinkClick = onLinkClick, onLinkClick = onLinkClick,
onClick = onMessageClick,
onLongClick = onMessageLongClick,
inReplyToClick = ::inReplyToClick, inReplyToClick = ::inReplyToClick,
onReactionClick = onReactionClick, onReactionClick = onReactionClick,
onReactionLongClick = onReactionLongClick, onReactionLongClick = onReactionLongClick,
onMoreReactionsClick = onMoreReactionsClick, onMoreReactionsClick = onMoreReactionsClick,
onReadReceiptClick = onReadReceiptClick, onReadReceiptClick = onReadReceiptClick,
eventSink = state.eventSink,
onSwipeToReply = onSwipeToReply, onSwipeToReply = onSwipeToReply,
onJoinCallClick = onJoinCallClick, onJoinCallClick = onJoinCallClick,
eventSink = state.eventSink,
) )
} }
} }
@ -186,6 +186,8 @@ fun TimelineView(
} }
} }
ResolveVerifiedUserSendFailureView(state = state.resolveVerifiedUserSendFailureState)
MessageShieldDialog(state) MessageShieldDialog(state)
} }
@ -267,8 +269,8 @@ private fun BoxScope.TimelineScrollHelper(
// Use inverse of canAutoScroll otherwise we might briefly see the before the scroll animation is triggered // Use inverse of canAutoScroll otherwise we might briefly see the before the scroll animation is triggered
isVisible = !canAutoScroll || forceJumpToBottomVisibility || !isLive, isVisible = !canAutoScroll || forceJumpToBottomVisibility || !isLive,
modifier = Modifier modifier = Modifier
.align(Alignment.BottomEnd) .align(Alignment.BottomEnd)
.padding(end = 24.dp, bottom = 12.dp), .padding(end = 24.dp, bottom = 12.dp),
onClick = { jumpToBottom() }, onClick = { jumpToBottom() },
) )
} }
@ -295,8 +297,8 @@ private fun JumpToBottomButton(
) { ) {
Icon( Icon(
modifier = Modifier modifier = Modifier
.size(24.dp) .size(24.dp)
.rotate(90f), .rotate(90f),
imageVector = CompoundIcons.ArrowRight(), imageVector = CompoundIcons.ArrowRight(),
contentDescription = stringResource(id = CommonStrings.a11y_jump_to_bottom) contentDescription = stringResource(id = CommonStrings.a11y_jump_to_bottom)
) )

View file

@ -28,9 +28,8 @@ internal fun ATimelineItemEventRow(
isHighlighted = isHighlighted, isHighlighted = isHighlighted,
onClick = {}, onClick = {},
onLongClick = {}, onLongClick = {},
onShieldClick = {},
onUserDataClick = {},
onLinkClick = {}, onLinkClick = {},
onUserDataClick = {},
inReplyToClick = {}, inReplyToClick = {},
onReactionClick = { _, _ -> }, onReactionClick = { _, _ -> },
onReactionLongClick = { _, _ -> }, onReactionLongClick = { _, _ -> },

View file

@ -23,6 +23,7 @@ import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import io.element.android.compound.theme.ElementTheme import io.element.android.compound.theme.ElementTheme
import io.element.android.compound.tokens.generated.CompoundIcons import io.element.android.compound.tokens.generated.CompoundIcons
import io.element.android.features.messages.impl.timeline.TimelineEvents
import io.element.android.features.messages.impl.timeline.model.TimelineItem import io.element.android.features.messages.impl.timeline.model.TimelineItem
import io.element.android.features.messages.impl.timeline.model.event.isEdited import io.element.android.features.messages.impl.timeline.model.event.isEdited
import io.element.android.libraries.core.bool.orFalse import io.element.android.libraries.core.bool.orFalse
@ -31,14 +32,13 @@ import io.element.android.libraries.designsystem.preview.PreviewsDayNight
import io.element.android.libraries.designsystem.theme.components.Icon import io.element.android.libraries.designsystem.theme.components.Icon
import io.element.android.libraries.designsystem.theme.components.Text import io.element.android.libraries.designsystem.theme.components.Text
import io.element.android.libraries.matrix.api.timeline.item.event.LocalEventSendState import io.element.android.libraries.matrix.api.timeline.item.event.LocalEventSendState
import io.element.android.libraries.matrix.api.timeline.item.event.MessageShield
import io.element.android.libraries.matrix.api.timeline.item.event.isCritical import io.element.android.libraries.matrix.api.timeline.item.event.isCritical
import io.element.android.libraries.ui.strings.CommonStrings import io.element.android.libraries.ui.strings.CommonStrings
@Composable @Composable
fun TimelineEventTimestampView( fun TimelineEventTimestampView(
event: TimelineItem.Event, event: TimelineItem.Event,
onShieldClick: (MessageShield) -> Unit, eventSink: (TimelineEvents.EventFromTimelineItem) -> Unit,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
) { ) {
val formattedTime = event.sentTime val formattedTime = event.sentTime
@ -48,8 +48,8 @@ fun TimelineEventTimestampView(
val tint = if (hasError || hasEncryptionCritical) MaterialTheme.colorScheme.error else MaterialTheme.colorScheme.secondary val tint = if (hasError || hasEncryptionCritical) MaterialTheme.colorScheme.error else MaterialTheme.colorScheme.secondary
Row( Row(
modifier = Modifier modifier = Modifier
.padding(PaddingValues(start = TimelineEventTimestampViewDefaults.spacing)) .padding(PaddingValues(start = TimelineEventTimestampViewDefaults.spacing))
.then(modifier), .then(modifier),
verticalAlignment = Alignment.CenterVertically, verticalAlignment = Alignment.CenterVertically,
) { ) {
if (isMessageEdited) { if (isMessageEdited) {
@ -66,12 +66,17 @@ fun TimelineEventTimestampView(
color = tint, color = tint,
) )
if (hasError) { if (hasError) {
val isVerifiedUserSendFailure = event.localSendState is LocalEventSendState.Failed.VerifiedUser
Spacer(modifier = Modifier.width(2.dp)) Spacer(modifier = Modifier.width(2.dp))
Icon( Icon(
imageVector = CompoundIcons.Error(), imageVector = CompoundIcons.Error(),
contentDescription = stringResource(id = CommonStrings.common_sending_failed), contentDescription = stringResource(id = CommonStrings.common_sending_failed),
tint = tint, tint = tint,
modifier = Modifier.size(15.dp, 18.dp), modifier = Modifier
.size(15.dp, 18.dp)
.clickable(isVerifiedUserSendFailure) {
eventSink(TimelineEvents.ComputeVerifiedUserSendFailure(event))
},
) )
} }
event.messageShield?.let { shield -> event.messageShield?.let { shield ->
@ -80,8 +85,10 @@ fun TimelineEventTimestampView(
imageVector = shield.toIcon(), imageVector = shield.toIcon(),
contentDescription = shield.toText(), contentDescription = shield.toText(),
modifier = Modifier modifier = Modifier
.size(15.dp) .size(15.dp)
.clickable { onShieldClick(shield) }, .clickable {
eventSink(TimelineEvents.ShowShieldDialog(shield))
},
tint = shield.toIconColor(), tint = shield.toIconColor(),
) )
Spacer(modifier = Modifier.width(4.dp)) Spacer(modifier = Modifier.width(4.dp))
@ -94,7 +101,7 @@ fun TimelineEventTimestampView(
internal fun TimelineEventTimestampViewPreview(@PreviewParameter(TimelineItemEventForTimestampViewProvider::class) event: TimelineItem.Event) = ElementPreview { internal fun TimelineEventTimestampViewPreview(@PreviewParameter(TimelineItemEventForTimestampViewProvider::class) event: TimelineItem.Event) = ElementPreview {
TimelineEventTimestampView( TimelineEventTimestampView(
event = event, event = event,
onShieldClick = {}, eventSink = {},
) )
} }

View file

@ -81,7 +81,6 @@ import io.element.android.libraries.designsystem.theme.components.Icon
import io.element.android.libraries.designsystem.theme.components.Text import io.element.android.libraries.designsystem.theme.components.Text
import io.element.android.libraries.matrix.api.core.EventId 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.core.UserId
import io.element.android.libraries.matrix.api.timeline.item.event.MessageShield
import io.element.android.libraries.matrix.api.timeline.item.event.ProfileTimelineDetails import io.element.android.libraries.matrix.api.timeline.item.event.ProfileTimelineDetails
import io.element.android.libraries.matrix.ui.messages.reply.InReplyToDetails import io.element.android.libraries.matrix.ui.messages.reply.InReplyToDetails
import io.element.android.libraries.matrix.ui.messages.reply.InReplyToView import io.element.android.libraries.matrix.ui.messages.reply.InReplyToView
@ -110,7 +109,6 @@ fun TimelineItemEventRow(
isHighlighted: Boolean, isHighlighted: Boolean,
onClick: () -> Unit, onClick: () -> Unit,
onLongClick: () -> Unit, onLongClick: () -> Unit,
onShieldClick: (MessageShield) -> Unit,
onLinkClick: (String) -> Unit, onLinkClick: (String) -> Unit,
onUserDataClick: (UserId) -> Unit, onUserDataClick: (UserId) -> Unit,
inReplyToClick: (EventId) -> Unit, inReplyToClick: (EventId) -> Unit,
@ -161,6 +159,17 @@ fun TimelineItemEventRow(
ReplySwipeIndicator({ offset / 120 }) ReplySwipeIndicator({ offset / 120 })
} }
TimelineItemEventRowContent( TimelineItemEventRowContent(
event = event,
isHighlighted = isHighlighted,
timelineRoomInfo = timelineRoomInfo,
interactionSource = interactionSource,
onClick = onClick,
onLongClick = onLongClick,
inReplyToClick = ::inReplyToClick,
onUserDataClick = ::onUserDataClick,
onReactionClick = { emoji -> onReactionClick(emoji, event) },
onReactionLongClick = { emoji -> onReactionLongClick(emoji, event) },
onMoreReactionsClick = { onMoreReactionsClick(event) },
modifier = Modifier modifier = Modifier
.absoluteOffset { IntOffset(x = offset.roundToInt(), y = 0) } .absoluteOffset { IntOffset(x = offset.roundToInt(), y = 0) }
.draggable( .draggable(
@ -176,18 +185,7 @@ fun TimelineItemEventRow(
}, },
state = state.draggableState, state = state.draggableState,
), ),
event = event, eventSink = eventSink,
isHighlighted = isHighlighted,
timelineRoomInfo = timelineRoomInfo,
interactionSource = interactionSource,
onClick = onClick,
onLongClick = onLongClick,
onShieldClick = onShieldClick,
inReplyToClick = ::inReplyToClick,
onUserDataClick = ::onUserDataClick,
onReactionClick = { emoji -> onReactionClick(emoji, event) },
onReactionLongClick = { emoji -> onReactionLongClick(emoji, event) },
onMoreReactionsClick = { onMoreReactionsClick(event) },
eventContentView = eventContentView, eventContentView = eventContentView,
) )
} }
@ -200,12 +198,12 @@ fun TimelineItemEventRow(
interactionSource = interactionSource, interactionSource = interactionSource,
onClick = onClick, onClick = onClick,
onLongClick = onLongClick, onLongClick = onLongClick,
onShieldClick = onShieldClick,
inReplyToClick = ::inReplyToClick, inReplyToClick = ::inReplyToClick,
onUserDataClick = ::onUserDataClick, onUserDataClick = ::onUserDataClick,
onReactionClick = { emoji -> onReactionClick(emoji, event) }, onReactionClick = { emoji -> onReactionClick(emoji, event) },
onReactionLongClick = { emoji -> onReactionLongClick(emoji, event) }, onReactionLongClick = { emoji -> onReactionLongClick(emoji, event) },
onMoreReactionsClick = { onMoreReactionsClick(event) }, onMoreReactionsClick = { onMoreReactionsClick(event) },
eventSink = eventSink,
eventContentView = eventContentView, eventContentView = eventContentView,
) )
} }
@ -255,12 +253,12 @@ private fun TimelineItemEventRowContent(
interactionSource: MutableInteractionSource, interactionSource: MutableInteractionSource,
onClick: () -> Unit, onClick: () -> Unit,
onLongClick: () -> Unit, onLongClick: () -> Unit,
onShieldClick: (MessageShield) -> Unit,
inReplyToClick: () -> Unit, inReplyToClick: () -> Unit,
onUserDataClick: () -> Unit, onUserDataClick: () -> Unit,
onReactionClick: (emoji: String) -> Unit, onReactionClick: (emoji: String) -> Unit,
onReactionLongClick: (emoji: String) -> Unit, onReactionLongClick: (emoji: String) -> Unit,
onMoreReactionsClick: (event: TimelineItem.Event) -> Unit, onMoreReactionsClick: (event: TimelineItem.Event) -> Unit,
eventSink: (TimelineEvents.EventFromTimelineItem) -> Unit,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
eventContentView: @Composable (Modifier, (ContentAvoidingLayoutData) -> Unit) -> Unit, eventContentView: @Composable (Modifier, (ContentAvoidingLayoutData) -> Unit) -> Unit,
) { ) {
@ -322,9 +320,9 @@ private fun TimelineItemEventRowContent(
) { ) {
MessageEventBubbleContent( MessageEventBubbleContent(
event = event, event = event,
onShieldClick = onShieldClick,
onMessageLongClick = onLongClick, onMessageLongClick = onLongClick,
inReplyToClick = inReplyToClick, inReplyToClick = inReplyToClick,
eventSink = eventSink,
eventContentView = eventContentView, eventContentView = eventContentView,
) )
} }
@ -382,9 +380,9 @@ private fun MessageSenderInformation(
@Composable @Composable
private fun MessageEventBubbleContent( private fun MessageEventBubbleContent(
event: TimelineItem.Event, event: TimelineItem.Event,
onShieldClick: (MessageShield) -> Unit,
onMessageLongClick: () -> Unit, onMessageLongClick: () -> Unit,
inReplyToClick: () -> Unit, inReplyToClick: () -> Unit,
eventSink: (TimelineEvents.EventFromTimelineItem) -> Unit,
@SuppressLint("ModifierParameter") @SuppressLint("ModifierParameter")
// need to rename this modifier to prevent linter false positives // need to rename this modifier to prevent linter false positives
@Suppress("ModifierNaming") @Suppress("ModifierNaming")
@ -422,7 +420,7 @@ private fun MessageEventBubbleContent(
@Composable @Composable
fun WithTimestampLayout( fun WithTimestampLayout(
timestampPosition: TimestampPosition, timestampPosition: TimestampPosition,
onShieldClick: (MessageShield) -> Unit, eventSink: (TimelineEvents.EventFromTimelineItem) -> Unit,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
canShrinkContent: Boolean = false, canShrinkContent: Boolean = false,
content: @Composable (onContentLayoutChange: (ContentAvoidingLayoutData) -> Unit) -> Unit, content: @Composable (onContentLayoutChange: (ContentAvoidingLayoutData) -> Unit) -> Unit,
@ -433,7 +431,7 @@ private fun MessageEventBubbleContent(
content {} content {}
TimelineEventTimestampView( TimelineEventTimestampView(
event = event, event = event,
onShieldClick = onShieldClick, eventSink = eventSink,
modifier = Modifier modifier = Modifier
// Outer padding // Outer padding
.padding(horizontal = 4.dp, vertical = 4.dp) .padding(horizontal = 4.dp, vertical = 4.dp)
@ -454,7 +452,7 @@ private fun MessageEventBubbleContent(
overlay = { overlay = {
TimelineEventTimestampView( TimelineEventTimestampView(
event = event, event = event,
onShieldClick = onShieldClick, eventSink = eventSink,
modifier = Modifier modifier = Modifier
.padding(horizontal = 8.dp, vertical = 4.dp) .padding(horizontal = 8.dp, vertical = 4.dp)
) )
@ -465,7 +463,7 @@ private fun MessageEventBubbleContent(
content {} content {}
TimelineEventTimestampView( TimelineEventTimestampView(
event = event, event = event,
onShieldClick = onShieldClick, eventSink = eventSink,
modifier = Modifier modifier = Modifier
.align(Alignment.End) .align(Alignment.End)
.padding(horizontal = 8.dp, vertical = 4.dp) .padding(horizontal = 8.dp, vertical = 4.dp)
@ -513,7 +511,7 @@ private fun MessageEventBubbleContent(
val contentWithTimestamp = @Composable { val contentWithTimestamp = @Composable {
WithTimestampLayout( WithTimestampLayout(
timestampPosition = timestampPosition, timestampPosition = timestampPosition,
onShieldClick = onShieldClick, eventSink = eventSink,
canShrinkContent = canShrinkContent, canShrinkContent = canShrinkContent,
modifier = timestampLayoutModifier, modifier = timestampLayoutModifier,
content = { onContentLayoutChange -> content = { onContentLayoutChange ->

View file

@ -29,7 +29,6 @@ import io.element.android.libraries.designsystem.preview.ElementPreview
import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.designsystem.preview.PreviewsDayNight
import io.element.android.libraries.matrix.api.core.EventId 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.core.UserId
import io.element.android.libraries.matrix.api.timeline.item.event.MessageShield
@Composable @Composable
fun TimelineItemGroupedEventsRow( fun TimelineItemGroupedEventsRow(
@ -40,7 +39,6 @@ fun TimelineItemGroupedEventsRow(
focusedEventId: EventId?, focusedEventId: EventId?,
onClick: (TimelineItem.Event) -> Unit, onClick: (TimelineItem.Event) -> Unit,
onLongClick: (TimelineItem.Event) -> Unit, onLongClick: (TimelineItem.Event) -> Unit,
onShieldClick: (MessageShield) -> Unit,
inReplyToClick: (EventId) -> Unit, inReplyToClick: (EventId) -> Unit,
onUserDataClick: (UserId) -> Unit, onUserDataClick: (UserId) -> Unit,
onLinkClick: (String) -> Unit, onLinkClick: (String) -> Unit,
@ -77,7 +75,6 @@ fun TimelineItemGroupedEventsRow(
isLastOutgoingMessage = isLastOutgoingMessage, isLastOutgoingMessage = isLastOutgoingMessage,
onClick = onClick, onClick = onClick,
onLongClick = onLongClick, onLongClick = onLongClick,
onShieldClick = onShieldClick,
inReplyToClick = inReplyToClick, inReplyToClick = inReplyToClick,
onUserDataClick = onUserDataClick, onUserDataClick = onUserDataClick,
onLinkClick = onLinkClick, onLinkClick = onLinkClick,
@ -102,7 +99,6 @@ private fun TimelineItemGroupedEventsRowContent(
isLastOutgoingMessage: Boolean, isLastOutgoingMessage: Boolean,
onClick: (TimelineItem.Event) -> Unit, onClick: (TimelineItem.Event) -> Unit,
onLongClick: (TimelineItem.Event) -> Unit, onLongClick: (TimelineItem.Event) -> Unit,
onShieldClick: (MessageShield) -> Unit,
inReplyToClick: (EventId) -> Unit, inReplyToClick: (EventId) -> Unit,
onUserDataClick: (UserId) -> Unit, onUserDataClick: (UserId) -> Unit,
onLinkClick: (String) -> Unit, onLinkClick: (String) -> Unit,
@ -143,19 +139,18 @@ private fun TimelineItemGroupedEventsRowContent(
renderReadReceipts = renderReadReceipts, renderReadReceipts = renderReadReceipts,
isLastOutgoingMessage = isLastOutgoingMessage, isLastOutgoingMessage = isLastOutgoingMessage,
focusedEventId = focusedEventId, focusedEventId = focusedEventId,
onClick = onClick,
onLongClick = onLongClick,
onShieldClick = onShieldClick,
inReplyToClick = inReplyToClick,
onUserDataClick = onUserDataClick, onUserDataClick = onUserDataClick,
onLinkClick = onLinkClick, onLinkClick = onLinkClick,
onClick = onClick,
onLongClick = onLongClick,
inReplyToClick = inReplyToClick,
onReactionClick = onReactionClick, onReactionClick = onReactionClick,
onReactionLongClick = onReactionLongClick, onReactionLongClick = onReactionLongClick,
onMoreReactionsClick = onMoreReactionsClick, onMoreReactionsClick = onMoreReactionsClick,
onReadReceiptClick = onReadReceiptClick, onReadReceiptClick = onReadReceiptClick,
eventSink = eventSink,
onSwipeToReply = {}, onSwipeToReply = {},
onJoinCallClick = {}, onJoinCallClick = {},
eventSink = eventSink,
eventContentView = eventContentView, eventContentView = eventContentView,
) )
} }
@ -188,7 +183,6 @@ internal fun TimelineItemGroupedEventsRowContentExpandedPreview() = ElementPrevi
isLastOutgoingMessage = false, isLastOutgoingMessage = false,
onClick = {}, onClick = {},
onLongClick = {}, onLongClick = {},
onShieldClick = {},
inReplyToClick = {}, inReplyToClick = {},
onUserDataClick = {}, onUserDataClick = {},
onLinkClick = {}, onLinkClick = {},
@ -213,7 +207,6 @@ internal fun TimelineItemGroupedEventsRowContentCollapsePreview() = ElementPrevi
isLastOutgoingMessage = false, isLastOutgoingMessage = false,
onClick = {}, onClick = {},
onLongClick = {}, onLongClick = {},
onShieldClick = {},
inReplyToClick = {}, inReplyToClick = {},
onUserDataClick = {}, onUserDataClick = {},
onLinkClick = {}, onLinkClick = {},

View file

@ -30,7 +30,6 @@ import io.element.android.libraries.designsystem.text.toPx
import io.element.android.libraries.designsystem.theme.highlightedMessageBackgroundColor import io.element.android.libraries.designsystem.theme.highlightedMessageBackgroundColor
import io.element.android.libraries.matrix.api.core.EventId 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.core.UserId
import io.element.android.libraries.matrix.api.timeline.item.event.MessageShield
@Composable @Composable
internal fun TimelineItemRow( internal fun TimelineItemRow(
@ -43,7 +42,6 @@ internal fun TimelineItemRow(
onLinkClick: (String) -> Unit, onLinkClick: (String) -> Unit,
onClick: (TimelineItem.Event) -> Unit, onClick: (TimelineItem.Event) -> Unit,
onLongClick: (TimelineItem.Event) -> Unit, onLongClick: (TimelineItem.Event) -> Unit,
onShieldClick: (MessageShield) -> Unit,
inReplyToClick: (EventId) -> Unit, inReplyToClick: (EventId) -> Unit,
onReactionClick: (key: String, TimelineItem.Event) -> Unit, onReactionClick: (key: String, TimelineItem.Event) -> Unit,
onReactionLongClick: (key: String, TimelineItem.Event) -> Unit, onReactionLongClick: (key: String, TimelineItem.Event) -> Unit,
@ -115,9 +113,8 @@ internal fun TimelineItemRow(
isHighlighted = timelineItem.isEvent(focusedEventId), isHighlighted = timelineItem.isEvent(focusedEventId),
onClick = { onClick(timelineItem) }, onClick = { onClick(timelineItem) },
onLongClick = { onLongClick(timelineItem) }, onLongClick = { onLongClick(timelineItem) },
onShieldClick = onShieldClick,
onUserDataClick = onUserDataClick,
onLinkClick = onLinkClick, onLinkClick = onLinkClick,
onUserDataClick = onUserDataClick,
inReplyToClick = inReplyToClick, inReplyToClick = inReplyToClick,
onReactionClick = onReactionClick, onReactionClick = onReactionClick,
onReactionLongClick = onReactionLongClick, onReactionLongClick = onReactionLongClick,
@ -141,7 +138,6 @@ internal fun TimelineItemRow(
focusedEventId = focusedEventId, focusedEventId = focusedEventId,
onClick = onClick, onClick = onClick,
onLongClick = onLongClick, onLongClick = onLongClick,
onShieldClick = onShieldClick,
inReplyToClick = inReplyToClick, inReplyToClick = inReplyToClick,
onUserDataClick = onUserDataClick, onUserDataClick = onUserDataClick,
onLinkClick = onLinkClick, onLinkClick = onLinkClick,