Skip to content

Commit 0a5c178

Browse files
Add thread decoration with latest event details (#5355)
* Add thread decoration with latest event details * Update screenshots --------- Co-authored-by: ElementBot <[email protected]>
1 parent 5cadd37 commit 0a5c178

File tree

106 files changed

+554
-282
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

106 files changed

+554
-282
lines changed

features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesPresenter.kt

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ import io.element.android.features.messages.impl.timeline.components.customreact
4343
import io.element.android.features.messages.impl.timeline.components.reactionsummary.ReactionSummaryState
4444
import io.element.android.features.messages.impl.timeline.components.receipt.bottomsheet.ReadReceiptBottomSheetState
4545
import io.element.android.features.messages.impl.timeline.model.TimelineItem
46+
import io.element.android.features.messages.impl.timeline.model.TimelineItemThreadInfo
4647
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemEventContentWithAttachment
4748
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemPollContent
4849
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemStateContent
@@ -328,7 +329,10 @@ class MessagesPresenter(
328329
val displayThreads = featureFlagService.isFeatureEnabled(FeatureFlags.Threads)
329330
if (displayThreads) {
330331
// Get either the thread id this event is in, or the event id if it's not in a thread so we can start one
331-
val threadId = targetEvent.threadInfo.threadRootId ?: targetEvent.eventId!!.toThreadId()
332+
val threadId = when (targetEvent.threadInfo) {
333+
is TimelineItemThreadInfo.ThreadResponse -> targetEvent.threadInfo.threadRootId
334+
is TimelineItemThreadInfo.ThreadRoot, null -> targetEvent.eventId?.toThreadId()
335+
} ?: return@launch
332336
navigator.onOpenThread(threadId, null)
333337
} else {
334338
handleActionReply(targetEvent, composerState, timelineProtectionState)

features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListPresenter.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import io.element.android.features.messages.impl.actionlist.model.TimelineItemAc
2525
import io.element.android.features.messages.impl.crypto.sendfailure.VerifiedUserSendFailure
2626
import io.element.android.features.messages.impl.crypto.sendfailure.VerifiedUserSendFailureFactory
2727
import io.element.android.features.messages.impl.timeline.model.TimelineItem
28+
import io.element.android.features.messages.impl.timeline.model.TimelineItemThreadInfo
2829
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemEventContent
2930
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemEventContentWithAttachment
3031
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemLegacyCallInviteContent
@@ -174,7 +175,7 @@ class DefaultActionListPresenter(
174175
add(TimelineItemAction.ReplyInThread)
175176
add(TimelineItemAction.Reply)
176177
} else {
177-
if (!isThreadsEnabled && timelineItem.threadInfo.threadRootId != null) {
178+
if (!isThreadsEnabled && timelineItem.threadInfo is TimelineItemThreadInfo.ThreadResponse) {
178179
// If threads are not enabled, we can reply in a thread if the item is already in the thread
179180
add(TimelineItemAction.ReplyInThread)
180181
} else {

features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineStateProvider.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import io.element.android.features.messages.impl.timeline.model.TimelineItem
1616
import io.element.android.features.messages.impl.timeline.model.TimelineItemGroupPosition
1717
import io.element.android.features.messages.impl.timeline.model.TimelineItemReactions
1818
import io.element.android.features.messages.impl.timeline.model.TimelineItemReadReceipts
19+
import io.element.android.features.messages.impl.timeline.model.TimelineItemThreadInfo
1920
import io.element.android.features.messages.impl.timeline.model.anAggregatedReaction
2021
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemEventContent
2122
import io.element.android.features.messages.impl.timeline.model.event.aTimelineItemStateEventContent
@@ -32,7 +33,6 @@ import io.element.android.libraries.matrix.api.core.UniqueId
3233
import io.element.android.libraries.matrix.api.core.UserId
3334
import io.element.android.libraries.matrix.api.room.tombstone.PredecessorRoom
3435
import io.element.android.libraries.matrix.api.timeline.Timeline
35-
import io.element.android.libraries.matrix.api.timeline.item.EventThreadInfo
3636
import io.element.android.libraries.matrix.api.timeline.item.TimelineItemDebugInfo
3737
import io.element.android.libraries.matrix.api.timeline.item.event.LocalEventSendState
3838
import io.element.android.libraries.matrix.api.timeline.item.event.MessageShield
@@ -146,7 +146,7 @@ internal fun aTimelineItemEvent(
146146
groupPosition: TimelineItemGroupPosition = TimelineItemGroupPosition.None,
147147
sendState: LocalEventSendState? = null,
148148
inReplyTo: InReplyToDetails? = null,
149-
threadInfo: EventThreadInfo = EventThreadInfo(threadRootId = null, threadSummary = null),
149+
threadInfo: TimelineItemThreadInfo? = null,
150150
debugInfo: TimelineItemDebugInfo = aTimelineItemDebugInfo(),
151151
timelineItemReactions: TimelineItemReactions = aTimelineItemReactions(),
152152
readReceiptState: TimelineItemReadReceipts = aTimelineItemReadReceipts(),

features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/MessageEventBubble.kt

Lines changed: 45 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -53,8 +53,6 @@ import io.element.android.libraries.ui.utils.time.isTalkbackActive
5353
private val BUBBLE_RADIUS = 12.dp
5454
private val avatarRadius = AvatarSize.TimelineSender.dp / 2
5555

56-
// Design says: The maximum width of a bubble is still 3/4 of the screen width. But try with 78% now.
57-
private const val BUBBLE_WIDTH_RATIO = 0.78f
5856
private val MIN_BUBBLE_WIDTH = 80.dp
5957

6058
@Composable
@@ -66,34 +64,6 @@ fun MessageEventBubble(
6664
modifier: Modifier = Modifier,
6765
content: @Composable BoxScope.() -> Unit = {},
6866
) {
69-
fun bubbleShape(): Shape {
70-
val topLeftCorner = if (state.cutTopStart) 0.dp else BUBBLE_RADIUS
71-
return when (state.groupPosition) {
72-
TimelineItemGroupPosition.First -> if (state.isMine) {
73-
RoundedCornerShape(BUBBLE_RADIUS, BUBBLE_RADIUS, 0.dp, BUBBLE_RADIUS)
74-
} else {
75-
RoundedCornerShape(topLeftCorner, BUBBLE_RADIUS, BUBBLE_RADIUS, 0.dp)
76-
}
77-
TimelineItemGroupPosition.Middle -> if (state.isMine) {
78-
RoundedCornerShape(BUBBLE_RADIUS, 0.dp, 0.dp, BUBBLE_RADIUS)
79-
} else {
80-
RoundedCornerShape(0.dp, BUBBLE_RADIUS, BUBBLE_RADIUS, 0.dp)
81-
}
82-
TimelineItemGroupPosition.Last -> if (state.isMine) {
83-
RoundedCornerShape(BUBBLE_RADIUS, 0.dp, BUBBLE_RADIUS, BUBBLE_RADIUS)
84-
} else {
85-
RoundedCornerShape(0.dp, BUBBLE_RADIUS, BUBBLE_RADIUS, BUBBLE_RADIUS)
86-
}
87-
TimelineItemGroupPosition.None ->
88-
RoundedCornerShape(
89-
topLeftCorner,
90-
BUBBLE_RADIUS,
91-
BUBBLE_RADIUS,
92-
BUBBLE_RADIUS
93-
)
94-
}
95-
}
96-
9767
val clickableModifier = if (isTalkbackActive()) {
9868
Modifier
9969
} else {
@@ -108,11 +78,8 @@ fun MessageEventBubble(
10878
}
10979

11080
// Ignore state.isHighlighted for now, we need a design decision on it.
111-
val backgroundBubbleColor = when {
112-
state.isMine -> ElementTheme.colors.messageFromMeBackground
113-
else -> ElementTheme.colors.messageFromOtherBackground
114-
}
115-
val bubbleShape = bubbleShape()
81+
val backgroundBubbleColor = MessageEventBubbleDefaults.backgroundBubbleColor(state.isMine)
82+
val bubbleShape = remember(state) { MessageEventBubbleDefaults.shape(state.cutTopStart, state.groupPosition, state.isMine) }
11683
val radiusPx = (avatarRadius + SENDER_AVATAR_BORDER_WIDTH).toPx()
11784
val yOffsetPx = -(NEGATIVE_MARGIN_FOR_BUBBLE + avatarRadius).toPx()
11885
val isRtl = LocalLayoutDirection.current == LayoutDirection.Rtl
@@ -147,7 +114,7 @@ fun MessageEventBubble(
147114
.testTag(TestTags.messageBubble)
148115
.widthIn(
149116
min = MIN_BUBBLE_WIDTH,
150-
max = (constraints.maxWidth * BUBBLE_WIDTH_RATIO)
117+
max = (constraints.maxWidth * MessageEventBubbleDefaults.BUBBLE_WIDTH_RATIO)
151118
.toInt()
152119
.toDp()
153120
)
@@ -157,6 +124,48 @@ fun MessageEventBubble(
157124
}
158125
}
159126

127+
object MessageEventBubbleDefaults {
128+
fun shape(cutTopStart: Boolean, groupPosition: TimelineItemGroupPosition, isMine: Boolean): Shape {
129+
val topLeftCorner = if (cutTopStart) 0.dp else BUBBLE_RADIUS
130+
return when (groupPosition) {
131+
TimelineItemGroupPosition.First -> if (isMine) {
132+
RoundedCornerShape(BUBBLE_RADIUS, BUBBLE_RADIUS, 0.dp, BUBBLE_RADIUS)
133+
} else {
134+
RoundedCornerShape(topLeftCorner, BUBBLE_RADIUS, BUBBLE_RADIUS, 0.dp)
135+
}
136+
TimelineItemGroupPosition.Middle -> if (isMine) {
137+
RoundedCornerShape(BUBBLE_RADIUS, 0.dp, 0.dp, BUBBLE_RADIUS)
138+
} else {
139+
RoundedCornerShape(0.dp, BUBBLE_RADIUS, BUBBLE_RADIUS, 0.dp)
140+
}
141+
TimelineItemGroupPosition.Last -> if (isMine) {
142+
RoundedCornerShape(BUBBLE_RADIUS, 0.dp, BUBBLE_RADIUS, BUBBLE_RADIUS)
143+
} else {
144+
RoundedCornerShape(0.dp, BUBBLE_RADIUS, BUBBLE_RADIUS, BUBBLE_RADIUS)
145+
}
146+
TimelineItemGroupPosition.None ->
147+
RoundedCornerShape(
148+
topLeftCorner,
149+
BUBBLE_RADIUS,
150+
BUBBLE_RADIUS,
151+
BUBBLE_RADIUS
152+
)
153+
}
154+
}
155+
156+
@Composable
157+
fun backgroundBubbleColor(isMine: Boolean): Color {
158+
return if (isMine) {
159+
ElementTheme.colors.messageFromMeBackground
160+
} else {
161+
ElementTheme.colors.messageFromOtherBackground
162+
}
163+
}
164+
165+
// Design says: The maximum width of a bubble is still 3/4 of the screen width. But try with 78% now.
166+
const val BUBBLE_WIDTH_RATIO = 0.78f
167+
}
168+
160169
@PreviewsDayNight
161170
@Composable
162171
internal fun MessageEventBubblePreview(@PreviewParameter(BubbleStateProvider::class) state: BubbleState) = ElementPreview {

0 commit comments

Comments
 (0)