Skip to content

Commit 3b41166

Browse files
committed
Update message grouping to take time difference into account
1 parent 0bdf04c commit 3b41166

2 files changed

Lines changed: 101 additions & 24 deletions

File tree

stream-chat-android-ui-common/src/main/kotlin/io/getstream/chat/android/ui/common/feature/messages/list/MessagePositionHandler.kt

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616

1717
package io.getstream.chat.android.ui.common.feature.messages.list
1818

19+
import io.getstream.chat.android.client.extensions.getCreatedAtOrDefault
20+
import io.getstream.chat.android.client.extensions.internal.NEVER
1921
import io.getstream.chat.android.client.utils.message.isError
2022
import io.getstream.chat.android.client.utils.message.isSystem
2123
import io.getstream.chat.android.core.internal.InternalStreamChatApi
@@ -65,8 +67,12 @@ public fun interface MessagePositionHandler {
6567
isBeforeDateSeparator: Boolean,
6668
_: Boolean,
6769
->
68-
val isGroupedWithPrevious = !isAfterDateSeparator && previous.isValidMessageFromUser(message.user)
69-
val isGroupedWithNext = !isBeforeDateSeparator && next.isValidMessageFromUser(message.user)
70+
val isGroupedWithPrevious = !isAfterDateSeparator &&
71+
previous.isValidMessageFromUser(message.user) &&
72+
!message.exceedsMaxTimeDifference(previous)
73+
val isGroupedWithNext = !isBeforeDateSeparator &&
74+
next.isValidMessageFromUser(message.user) &&
75+
!next.exceedsMaxTimeDifference(message)
7076

7177
when {
7278
isGroupedWithNext && !isGroupedWithPrevious -> MessagePosition.TOP
@@ -80,5 +86,14 @@ public fun interface MessagePositionHandler {
8086
private fun Message?.isValidMessageFromUser(user: User): Boolean {
8187
return this != null && !this.isSystem() && !this.isError() && this.user.id == user.id
8288
}
89+
90+
private fun Message?.exceedsMaxTimeDifference(other: Message?): Boolean {
91+
if (this == null || other == null) return false
92+
val thisTime = this.getCreatedAtOrDefault(NEVER).time
93+
val otherTime = other.getCreatedAtOrDefault(NEVER).time
94+
return kotlin.math.abs(thisTime - otherTime) > MAX_TIME_BETWEEN_GROUPED_MESSAGES_MILLIS
95+
}
96+
97+
private const val MAX_TIME_BETWEEN_GROUPED_MESSAGES_MILLIS: Long = 60 * 1000L
8398
}
8499
}

stream-chat-android-ui-common/src/test/kotlin/io/getstream/chat/android/ui/common/feature/messages/list/MessagePositionHandlerTest.kt

Lines changed: 84 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import io.getstream.chat.android.randomMessage
2323
import io.getstream.chat.android.ui.common.state.messages.list.MessagePosition
2424
import org.amshove.kluent.`should be equal to`
2525
import org.junit.jupiter.api.Test
26+
import java.util.Date
2627

2728
internal class MessagePositionHandlerTest {
2829

@@ -31,9 +32,16 @@ internal class MessagePositionHandlerTest {
3132
private val user1 = User(id = "user1")
3233
private val user2 = User(id = "user2")
3334

34-
private fun createMessage(user: User, isSystem: Boolean = false, isError: Boolean = false): Message {
35+
private fun createMessage(
36+
user: User,
37+
isSystem: Boolean = false,
38+
isError: Boolean = false,
39+
timeOffset: Long = 0L,
40+
): Message {
3541
return randomMessage(
3642
user = user,
43+
createdAt = Date(timeOffset),
44+
createdLocallyAt = Date(timeOffset),
3745
type = when {
3846
isSystem -> MessageType.SYSTEM
3947
isError -> MessageType.ERROR
@@ -44,9 +52,9 @@ internal class MessagePositionHandlerTest {
4452

4553
@Test
4654
fun `Message should be TOP when it starts a new user sequence`() {
47-
val previousMessage = createMessage(user2)
48-
val currentMessage = createMessage(user1)
49-
val nextMessage = createMessage(user1)
55+
val previousMessage = createMessage(user2, timeOffset = 0L)
56+
val currentMessage = createMessage(user1, timeOffset = 1_000L)
57+
val nextMessage = createMessage(user1, timeOffset = 2_000L)
5058

5159
val result = defaultHandler.handleMessagePosition(
5260
previousMessage,
@@ -62,9 +70,9 @@ internal class MessagePositionHandlerTest {
6270

6371
@Test
6472
fun `Message should be MIDDLE when it's between two messages from the same user`() {
65-
val previousMessage = createMessage(user1)
66-
val currentMessage = createMessage(user1)
67-
val nextMessage = createMessage(user1)
73+
val previousMessage = createMessage(user1, timeOffset = 0L)
74+
val currentMessage = createMessage(user1, timeOffset = 1_000L)
75+
val nextMessage = createMessage(user1, timeOffset = 2_000L)
6876

6977
val result = defaultHandler.handleMessagePosition(
7078
previousMessage,
@@ -80,9 +88,9 @@ internal class MessagePositionHandlerTest {
8088

8189
@Test
8290
fun `Message should be BOTTOM when it's the last message in a user sequence`() {
83-
val previousMessage = createMessage(user1)
84-
val currentMessage = createMessage(user1)
85-
val nextMessage = createMessage(user2)
91+
val previousMessage = createMessage(user1, timeOffset = 0L)
92+
val currentMessage = createMessage(user1, timeOffset = 1_000L)
93+
val nextMessage = createMessage(user2, timeOffset = 2_000L)
8694

8795
val result = defaultHandler.handleMessagePosition(
8896
previousMessage,
@@ -98,9 +106,9 @@ internal class MessagePositionHandlerTest {
98106

99107
@Test
100108
fun `Message should be NONE when it's a single isolated message`() {
101-
val previousMessage = createMessage(user2)
102-
val currentMessage = createMessage(user1)
103-
val nextMessage = createMessage(user2)
109+
val previousMessage = createMessage(user2, timeOffset = 0L)
110+
val currentMessage = createMessage(user1, timeOffset = 1_000L)
111+
val nextMessage = createMessage(user2, timeOffset = 2_000L)
104112

105113
val result = defaultHandler.handleMessagePosition(
106114
previousMessage,
@@ -116,9 +124,9 @@ internal class MessagePositionHandlerTest {
116124

117125
@Test
118126
fun `Message should be NONE when it doesn't match any position`() {
119-
val previousMessage = createMessage(user1, isSystem = true)
120-
val currentMessage = createMessage(user1, isSystem = true)
121-
val nextMessage = createMessage(user1, isSystem = true)
127+
val previousMessage = createMessage(user1, isSystem = true, timeOffset = 0L)
128+
val currentMessage = createMessage(user1, isSystem = true, timeOffset = 1_000L)
129+
val nextMessage = createMessage(user1, isSystem = true, timeOffset = 2_000L)
122130

123131
val result = defaultHandler.handleMessagePosition(
124132
previousMessage,
@@ -134,9 +142,9 @@ internal class MessagePositionHandlerTest {
134142

135143
@Test
136144
fun `Message should be TOP when there's a date separator before it`() {
137-
val previousMessage = createMessage(user1)
138-
val currentMessage = createMessage(user1)
139-
val nextMessage = createMessage(user1)
145+
val previousMessage = createMessage(user1, timeOffset = 0L)
146+
val currentMessage = createMessage(user1, timeOffset = 1_000L)
147+
val nextMessage = createMessage(user1, timeOffset = 2_000L)
140148

141149
val result = defaultHandler.handleMessagePosition(
142150
previousMessage,
@@ -152,9 +160,9 @@ internal class MessagePositionHandlerTest {
152160

153161
@Test
154162
fun `Message should be BOTTOM when there's a date separator after it`() {
155-
val previousMessage = createMessage(user1)
156-
val currentMessage = createMessage(user1)
157-
val nextMessage = createMessage(user1)
163+
val previousMessage = createMessage(user1, timeOffset = 0L)
164+
val currentMessage = createMessage(user1, timeOffset = 1_000L)
165+
val nextMessage = createMessage(user1, timeOffset = 2_000L)
158166

159167
val result = defaultHandler.handleMessagePosition(
160168
previousMessage,
@@ -167,4 +175,58 @@ internal class MessagePositionHandlerTest {
167175

168176
result `should be equal to` MessagePosition.BOTTOM
169177
}
178+
179+
@Test
180+
fun `Message should be NONE when time difference with previous and next exceeds 60 seconds`() {
181+
val previousMessage = createMessage(user1, timeOffset = 0L)
182+
val currentMessage = createMessage(user1, timeOffset = 120_000L)
183+
val nextMessage = createMessage(user1, timeOffset = 240_000L)
184+
185+
val result = defaultHandler.handleMessagePosition(
186+
previousMessage,
187+
currentMessage,
188+
nextMessage,
189+
isAfterDateSeparator = false,
190+
isBeforeDateSeparator = false,
191+
isInThread = false,
192+
)
193+
194+
result `should be equal to` MessagePosition.NONE
195+
}
196+
197+
@Test
198+
fun `Message should be TOP when time difference with previous exceeds 60 seconds`() {
199+
val previousMessage = createMessage(user1, timeOffset = 0L)
200+
val currentMessage = createMessage(user1, timeOffset = 120_000L)
201+
val nextMessage = createMessage(user1, timeOffset = 121_000L)
202+
203+
val result = defaultHandler.handleMessagePosition(
204+
previousMessage,
205+
currentMessage,
206+
nextMessage,
207+
isAfterDateSeparator = false,
208+
isBeforeDateSeparator = false,
209+
isInThread = false,
210+
)
211+
212+
result `should be equal to` MessagePosition.TOP
213+
}
214+
215+
@Test
216+
fun `Message should be BOTTOM when time difference with next exceeds 60 seconds`() {
217+
val previousMessage = createMessage(user1, timeOffset = 0L)
218+
val currentMessage = createMessage(user1, timeOffset = 1_000L)
219+
val nextMessage = createMessage(user1, timeOffset = 120_000L)
220+
221+
val result = defaultHandler.handleMessagePosition(
222+
previousMessage,
223+
currentMessage,
224+
nextMessage,
225+
isAfterDateSeparator = false,
226+
isBeforeDateSeparator = false,
227+
isInThread = false,
228+
)
229+
230+
result `should be equal to` MessagePosition.BOTTOM
231+
}
170232
}

0 commit comments

Comments
 (0)