Merge pull request #6603 from element-hq/valere/call/fix_join_button_on_several_items

cleaning: Remove join button from call notify timelineItemView
This commit is contained in:
Valere Fedronic 2026-04-21 11:56:38 +02:00 committed by GitHub
commit 7c81df6816
18 changed files with 50 additions and 56 deletions

View file

@ -282,7 +282,6 @@ fun MessagesView(
state.eventSink(MessagesEvent.HandleAction(TimelineItemAction.Reply, targetEvent)) state.eventSink(MessagesEvent.HandleAction(TimelineItemAction.Reply, targetEvent))
}, },
forceJumpToBottomVisibility = forceJumpToBottomVisibility, forceJumpToBottomVisibility = forceJumpToBottomVisibility,
onJoinCallClick = onJoinCallClick,
onViewAllPinnedMessagesClick = onViewAllPinnedMessagesClick, onViewAllPinnedMessagesClick = onViewAllPinnedMessagesClick,
knockRequestsBannerView = knockRequestsBannerView, knockRequestsBannerView = knockRequestsBannerView,
) )
@ -460,7 +459,6 @@ private fun MessagesViewContent(
onMessageLongClick: (TimelineItem.Event) -> Unit, onMessageLongClick: (TimelineItem.Event) -> Unit,
onSendLocationClick: () -> Unit, onSendLocationClick: () -> Unit,
onCreatePollClick: () -> Unit, onCreatePollClick: () -> Unit,
onJoinCallClick: (isAudioCall: Boolean) -> Unit,
onViewAllPinnedMessagesClick: () -> Unit, onViewAllPinnedMessagesClick: () -> Unit,
forceJumpToBottomVisibility: Boolean, forceJumpToBottomVisibility: Boolean,
onSwipeToReply: (TimelineItem.Event) -> Unit, onSwipeToReply: (TimelineItem.Event) -> Unit,
@ -517,7 +515,6 @@ private fun MessagesViewContent(
onMoreReactionsClick = onMoreReactionsClick, onMoreReactionsClick = onMoreReactionsClick,
onReadReceiptClick = onReadReceiptClick, onReadReceiptClick = onReadReceiptClick,
forceJumpToBottomVisibility = forceJumpToBottomVisibility, forceJumpToBottomVisibility = forceJumpToBottomVisibility,
onJoinCallClick = onJoinCallClick,
nestedScrollConnection = scrollBehavior.nestedScrollConnection, nestedScrollConnection = scrollBehavior.nestedScrollConnection,
floatingDateTopOffset = pinnedBannerHeightDp, floatingDateTopOffset = pinnedBannerHeightDp,
) )

View file

@ -235,7 +235,6 @@ private fun PinnedMessagesListLoaded(
onMoreReactionsClick = {}, onMoreReactionsClick = {},
onReadReceiptClick = {}, onReadReceiptClick = {},
onSwipeToReply = {}, onSwipeToReply = {},
onJoinCallClick = {},
eventSink = { timelineItemEvent -> eventSink = { timelineItemEvent ->
when (timelineItemEvent) { when (timelineItemEvent) {
is TimelineEvent.OpenThread -> state.eventSink(PinnedMessagesListEvent.OpenThread(timelineItemEvent.threadRootEventId)) is TimelineEvent.OpenThread -> state.eventSink(PinnedMessagesListEvent.OpenThread(timelineItemEvent.threadRootEventId))

View file

@ -103,7 +103,6 @@ fun TimelineView(
onReactionLongClick: (emoji: String, TimelineItem.Event) -> Unit, onReactionLongClick: (emoji: String, TimelineItem.Event) -> Unit,
onMoreReactionsClick: (TimelineItem.Event) -> Unit, onMoreReactionsClick: (TimelineItem.Event) -> Unit,
onReadReceiptClick: (TimelineItem.Event) -> Unit, onReadReceiptClick: (TimelineItem.Event) -> Unit,
onJoinCallClick: (isAudioCall: Boolean) -> Unit,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
lazyListState: LazyListState = rememberLazyListState(), lazyListState: LazyListState = rememberLazyListState(),
forceJumpToBottomVisibility: Boolean = false, forceJumpToBottomVisibility: Boolean = false,
@ -187,7 +186,6 @@ fun TimelineView(
onMoreReactionsClick = onMoreReactionsClick, onMoreReactionsClick = onMoreReactionsClick,
onReadReceiptClick = onReadReceiptClick, onReadReceiptClick = onReadReceiptClick,
onSwipeToReply = onSwipeToReply, onSwipeToReply = onSwipeToReply,
onJoinCallClick = onJoinCallClick,
eventSink = state.eventSink, eventSink = state.eventSink,
) )
} }
@ -431,7 +429,6 @@ internal fun TimelineViewPreview(
onReactionLongClick = { _, _ -> }, onReactionLongClick = { _, _ -> },
onMoreReactionsClick = {}, onMoreReactionsClick = {},
onReadReceiptClick = {}, onReadReceiptClick = {},
onJoinCallClick = {},
forceJumpToBottomVisibility = true, forceJumpToBottomVisibility = true,
) )
} }

View file

@ -49,7 +49,6 @@ internal fun TimelineViewMessageShieldPreview() = ElementPreview {
onReactionLongClick = { _, _ -> }, onReactionLongClick = { _, _ -> },
onMoreReactionsClick = {}, onMoreReactionsClick = {},
onReadReceiptClick = {}, onReadReceiptClick = {},
onJoinCallClick = {},
forceJumpToBottomVisibility = true, forceJumpToBottomVisibility = true,
) )
} }

View file

@ -31,22 +31,20 @@ import io.element.android.compound.tokens.generated.CompoundIcons
import io.element.android.features.messages.impl.timeline.aTimelineItemEvent import io.element.android.features.messages.impl.timeline.aTimelineItemEvent
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.TimelineItemRtcNotificationContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemRtcNotificationContent
import io.element.android.features.roomcall.api.RoomCallState
import io.element.android.features.roomcall.api.RoomCallStateProvider
import io.element.android.libraries.designsystem.components.avatar.Avatar import io.element.android.libraries.designsystem.components.avatar.Avatar
import io.element.android.libraries.designsystem.components.avatar.AvatarType import io.element.android.libraries.designsystem.components.avatar.AvatarType
import io.element.android.libraries.designsystem.modifiers.onKeyboardContextMenuAction import io.element.android.libraries.designsystem.modifiers.onKeyboardContextMenuAction
import io.element.android.libraries.designsystem.preview.ElementPreview 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.designsystem.text.toDp import io.element.android.libraries.designsystem.text.toDp
import io.element.android.libraries.matrix.api.notification.CallIntent
import io.element.android.libraries.ui.strings.CommonStrings import io.element.android.libraries.ui.strings.CommonStrings
@Composable @Composable
internal fun TimelineItemCallNotifyView( internal fun TimelineItemCallNotifyView(
event: TimelineItem.Event, event: TimelineItem.Event,
roomCallState: RoomCallState, content: TimelineItemRtcNotificationContent,
onLongClick: (TimelineItem.Event) -> Unit, onLongClick: (TimelineItem.Event) -> Unit,
onJoinCallClick: (isAudioCall: Boolean) -> Unit,
modifier: Modifier = Modifier modifier: Modifier = Modifier
) { ) {
Row( Row(
@ -81,7 +79,8 @@ internal fun TimelineItemCallNotifyView(
) { ) {
Icon( Icon(
modifier = Modifier.size(20.sp.toDp()), modifier = Modifier.size(20.sp.toDp()),
imageVector = CompoundIcons.VideoCallSolid(), imageVector =
if (content.callIntent == CallIntent.AUDIO) CompoundIcons.VoiceCallSolid() else CompoundIcons.VideoCallSolid(),
contentDescription = null, contentDescription = null,
tint = ElementTheme.colors.iconSecondary, tint = ElementTheme.colors.iconSecondary,
) )
@ -94,20 +93,13 @@ internal fun TimelineItemCallNotifyView(
) )
} }
} }
if (roomCallState is RoomCallState.OnGoing) { Text(
CallMenuItem( text = event.sentTime,
roomCallState = roomCallState, style = ElementTheme.typography.fontBodyMdRegular,
onJoinCallClick = onJoinCallClick, color = ElementTheme.colors.textSecondary,
) maxLines = 1,
} else { overflow = TextOverflow.Ellipsis,
Text( )
text = event.sentTime,
style = ElementTheme.typography.fontBodyMdRegular,
color = ElementTheme.colors.textSecondary,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
}
} }
} }
@ -115,16 +107,15 @@ internal fun TimelineItemCallNotifyView(
@Composable @Composable
internal fun TimelineItemCallNotifyViewPreview() = ElementPreview { internal fun TimelineItemCallNotifyViewPreview() = ElementPreview {
Column(modifier = Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(16.dp)) { Column(modifier = Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(16.dp)) {
RoomCallStateProvider() listOf(
.values TimelineItemRtcNotificationContent(CallIntent.AUDIO),
.filter { it !is RoomCallState.Unavailable } TimelineItemRtcNotificationContent(CallIntent.VIDEO),
.forEach { roomCallState -> ).forEach { content ->
TimelineItemCallNotifyView( TimelineItemCallNotifyView(
event = aTimelineItemEvent(content = TimelineItemRtcNotificationContent()), event = aTimelineItemEvent(content = content),
roomCallState = roomCallState, content = content,
onLongClick = {}, onLongClick = {},
onJoinCallClick = {}, )
) }
}
} }
} }

View file

@ -188,7 +188,6 @@ private fun TimelineItemGroupedEventsRowContent(
onMoreReactionsClick = onMoreReactionsClick, onMoreReactionsClick = onMoreReactionsClick,
onReadReceiptClick = onReadReceiptClick, onReadReceiptClick = onReadReceiptClick,
onSwipeToReply = {}, onSwipeToReply = {},
onJoinCallClick = {},
eventSink = eventSink, eventSink = eventSink,
eventContentView = eventContentView, eventContentView = eventContentView,
) )

View file

@ -72,7 +72,6 @@ internal fun TimelineItemRow(
onMoreReactionsClick: (TimelineItem.Event) -> Unit, onMoreReactionsClick: (TimelineItem.Event) -> Unit,
onReadReceiptClick: (TimelineItem.Event) -> Unit, onReadReceiptClick: (TimelineItem.Event) -> Unit,
onSwipeToReply: (TimelineItem.Event) -> Unit, onSwipeToReply: (TimelineItem.Event) -> Unit,
onJoinCallClick: (isAudioCall: Boolean) -> Unit,
eventSink: (TimelineEvent.TimelineItemEvent) -> Unit, eventSink: (TimelineEvent.TimelineItemEvent) -> Unit,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
eventContentView: @Composable (TimelineItem.Event, Modifier, (ContentAvoidingLayoutData) -> Unit) -> Unit = eventContentView: @Composable (TimelineItem.Event, Modifier, (ContentAvoidingLayoutData) -> Unit) -> Unit =
@ -127,9 +126,8 @@ internal fun TimelineItemRow(
TimelineItemCallNotifyView( TimelineItemCallNotifyView(
modifier = Modifier.padding(start = 16.dp, end = 16.dp, top = 16.dp), modifier = Modifier.padding(start = 16.dp, end = 16.dp, top = 16.dp),
event = timelineItem, event = timelineItem,
roomCallState = timelineRoomInfo.roomCallState, content = timelineItem.content,
onLongClick = onLongClick, onLongClick = onLongClick,
onJoinCallClick = onJoinCallClick,
) )
} }
else -> { else -> {

View file

@ -103,7 +103,9 @@ class TimelineItemContentFactory(
is StickerContent -> stickerFactory.create(itemContent) is StickerContent -> stickerFactory.create(itemContent)
is PollContent -> pollFactory.create(eventId, isEditable, isOutgoing, itemContent) is PollContent -> pollFactory.create(eventId, isEditable, isOutgoing, itemContent)
is UnableToDecryptContent -> utdFactory.create(itemContent) is UnableToDecryptContent -> utdFactory.create(itemContent)
is CallNotifyContent -> TimelineItemRtcNotificationContent() is CallNotifyContent -> TimelineItemRtcNotificationContent(
itemContent.callIntent
)
is UnknownContent -> TimelineItemUnknownContent is UnknownContent -> TimelineItemUnknownContent
is LiveLocationContent -> { is LiveLocationContent -> {
val lastKnownLocation = itemContent.locations.mapNotNull { beacon -> val lastKnownLocation = itemContent.locations.mapNotNull { beacon ->

View file

@ -90,7 +90,7 @@ internal fun MatrixTimelineItem.Event.canBeDisplayedInBubbleBlock(): Boolean {
is RoomMembershipContent, is RoomMembershipContent,
UnknownContent, UnknownContent,
is LegacyCallInviteContent, is LegacyCallInviteContent,
CallNotifyContent, is CallNotifyContent,
is StateContent -> false is StateContent -> false
} }
} }

View file

@ -8,6 +8,8 @@
package io.element.android.features.messages.impl.timeline.model.event package io.element.android.features.messages.impl.timeline.model.event
class TimelineItemRtcNotificationContent : TimelineItemEventContent { import io.element.android.libraries.matrix.api.notification.CallIntent
class TimelineItemRtcNotificationContent(val callIntent: CallIntent) : TimelineItemEventContent {
override val type: String = "org.matrix.msc4075.rtc.notification" override val type: String = "org.matrix.msc4075.rtc.notification"
} }

View file

@ -28,6 +28,7 @@ import io.element.android.features.poll.api.pollcontent.aPollAnswerItemList
import io.element.android.libraries.dateformatter.test.FakeDateFormatter import io.element.android.libraries.dateformatter.test.FakeDateFormatter
import io.element.android.libraries.featureflag.api.FeatureFlags import io.element.android.libraries.featureflag.api.FeatureFlags
import io.element.android.libraries.featureflag.test.FakeFeatureFlagService import io.element.android.libraries.featureflag.test.FakeFeatureFlagService
import io.element.android.libraries.matrix.api.notification.CallIntent
import io.element.android.libraries.matrix.api.room.BaseRoom import io.element.android.libraries.matrix.api.room.BaseRoom
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.LocalEventSendState import io.element.android.libraries.matrix.api.timeline.item.event.LocalEventSendState
@ -1168,7 +1169,7 @@ class ActionListPresenterTest {
val initialState = awaitItem() val initialState = awaitItem()
val messageEvent = aMessageEvent( val messageEvent = aMessageEvent(
isMine = true, isMine = true,
content = TimelineItemRtcNotificationContent(), content = TimelineItemRtcNotificationContent(callIntent = CallIntent.VIDEO),
) )
initialState.eventSink.invoke( initialState.eventSink.invoke(
ActionListEvent.ComputeForMessage( ActionListEvent.ComputeForMessage(

View file

@ -219,7 +219,6 @@ private fun <R : TestRule> AndroidComposeTestRule<R, ComponentActivity>.setTimel
onReactionLongClick: (emoji: String, TimelineItem.Event) -> Unit = EnsureNeverCalledWithTwoParams(), onReactionLongClick: (emoji: String, TimelineItem.Event) -> Unit = EnsureNeverCalledWithTwoParams(),
onMoreReactionsClick: (TimelineItem.Event) -> Unit = EnsureNeverCalledWithParam(), onMoreReactionsClick: (TimelineItem.Event) -> Unit = EnsureNeverCalledWithParam(),
onReadReceiptClick: (TimelineItem.Event) -> Unit = EnsureNeverCalledWithParam(), onReadReceiptClick: (TimelineItem.Event) -> Unit = EnsureNeverCalledWithParam(),
onJoinCallClick: (Boolean) -> Unit = EnsureNeverCalledWithParam(),
forceJumpToBottomVisibility: Boolean = false, forceJumpToBottomVisibility: Boolean = false,
) { ) {
setSafeContent(clearAndroidUiDispatcher = true) { setSafeContent(clearAndroidUiDispatcher = true) {
@ -235,7 +234,6 @@ private fun <R : TestRule> AndroidComposeTestRule<R, ComponentActivity>.setTimel
onReactionLongClick = onReactionLongClick, onReactionLongClick = onReactionLongClick,
onMoreReactionsClick = onMoreReactionsClick, onMoreReactionsClick = onMoreReactionsClick,
onReadReceiptClick = onReadReceiptClick, onReadReceiptClick = onReadReceiptClick,
onJoinCallClick = onJoinCallClick,
forceJumpToBottomVisibility = forceJumpToBottomVisibility, forceJumpToBottomVisibility = forceJumpToBottomVisibility,
) )
} }

View file

@ -12,6 +12,7 @@ import androidx.compose.runtime.Immutable
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.media.ImageInfo import io.element.android.libraries.matrix.api.media.ImageInfo
import io.element.android.libraries.matrix.api.media.MediaSource import io.element.android.libraries.matrix.api.media.MediaSource
import io.element.android.libraries.matrix.api.notification.CallIntent
import io.element.android.libraries.matrix.api.poll.PollAnswer import io.element.android.libraries.matrix.api.poll.PollAnswer
import io.element.android.libraries.matrix.api.poll.PollKind import io.element.android.libraries.matrix.api.poll.PollKind
import io.element.android.libraries.matrix.api.room.location.AssetType import io.element.android.libraries.matrix.api.room.location.AssetType
@ -117,6 +118,8 @@ data class LiveLocationContent(
data object LegacyCallInviteContent : EventContent data object LegacyCallInviteContent : EventContent
data object CallNotifyContent : EventContent data class CallNotifyContent(
val callIntent: CallIntent
) : EventContent
data object UnknownContent : EventContent data object UnknownContent : EventContent

View file

@ -11,6 +11,7 @@ package io.element.android.libraries.matrix.impl.timeline.item.event
import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.architecture.AsyncData
import io.element.android.libraries.matrix.api.core.ThreadId import io.element.android.libraries.matrix.api.core.ThreadId
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.notification.CallIntent
import io.element.android.libraries.matrix.api.room.location.LiveLocationInfo import io.element.android.libraries.matrix.api.room.location.LiveLocationInfo
import io.element.android.libraries.matrix.api.timeline.item.EmbeddedEventInfo import io.element.android.libraries.matrix.api.timeline.item.EmbeddedEventInfo
import io.element.android.libraries.matrix.api.timeline.item.EventThreadInfo import io.element.android.libraries.matrix.api.timeline.item.EventThreadInfo
@ -147,7 +148,13 @@ class TimelineEventContentMapper(
) )
} }
is TimelineItemContent.CallInvite -> LegacyCallInviteContent is TimelineItemContent.CallInvite -> LegacyCallInviteContent
is TimelineItemContent.RtcNotification -> CallNotifyContent is TimelineItemContent.RtcNotification -> CallNotifyContent(
callIntent = if (it.callIntent == "audio") {
CallIntent.AUDIO
} else {
CallIntent.VIDEO
}
)
} }
} }
} }

View file

@ -65,7 +65,7 @@ class EventItemFactory(
mode = DateFormatterMode.Full, mode = DateFormatterMode.Full,
) )
return when (val content = event.content) { return when (val content = event.content) {
CallNotifyContent, is CallNotifyContent,
is FailedToParseMessageLikeContent, is FailedToParseMessageLikeContent,
is FailedToParseStateContent, is FailedToParseStateContent,
LegacyCallInviteContent, LegacyCallInviteContent,

View file

@ -18,6 +18,7 @@ import io.element.android.libraries.matrix.api.media.FileInfo
import io.element.android.libraries.matrix.api.media.ImageInfo import io.element.android.libraries.matrix.api.media.ImageInfo
import io.element.android.libraries.matrix.api.media.MediaSource import io.element.android.libraries.matrix.api.media.MediaSource
import io.element.android.libraries.matrix.api.media.VideoInfo import io.element.android.libraries.matrix.api.media.VideoInfo
import io.element.android.libraries.matrix.api.notification.CallIntent
import io.element.android.libraries.matrix.api.timeline.MatrixTimelineItem import io.element.android.libraries.matrix.api.timeline.MatrixTimelineItem
import io.element.android.libraries.matrix.api.timeline.item.event.AudioMessageType import io.element.android.libraries.matrix.api.timeline.item.event.AudioMessageType
import io.element.android.libraries.matrix.api.timeline.item.event.CallNotifyContent import io.element.android.libraries.matrix.api.timeline.item.event.CallNotifyContent
@ -61,7 +62,7 @@ class DefaultEventItemFactoryTest {
fun `create check all null cases`() { fun `create check all null cases`() {
val factory = createEventItemFactory() val factory = createEventItemFactory()
val contents = listOf( val contents = listOf(
CallNotifyContent, CallNotifyContent(callIntent = CallIntent.VIDEO),
FailedToParseMessageLikeContent("", ""), FailedToParseMessageLikeContent("", ""),
FailedToParseStateContent("", "", ""), FailedToParseStateContent("", "", ""),
LegacyCallInviteContent, LegacyCallInviteContent,

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:155ad78cfadaab78089293eca38ab8c404f227e38c451dddbbe3c59cccb82bc5 oid sha256:3624fe8448ae4af2481e2023a978ffab2d69f2784b7cef41e5ae2e2dbe8fdbd5
size 51391 size 17804

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:8b54d16054565d3ba0280ff704c350227a03db0fad93750ad6d41f6e67f605f3 oid sha256:6bd02d39619efbcaa6d96f1e75a0d14a572c43e60bad0c3f84d6a5a48b6fbda1
size 51582 size 17395