Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
### 🐞 Fixed

### ⬆️ Improved
- Prioritize `Message.createdLocallyAt` over `Message.createdAt` when sorting messages, to ensure the message order is consistent before the messages are synced with the server. [#5993](https://github.com/GetStream/stream-chat-android/pull/5993)

### ✅ Added

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import io.getstream.chat.android.client.extensions.getCreatedAtOrNull
import io.getstream.chat.android.client.utils.message.belongsToThread
import io.getstream.chat.android.compose.R
import io.getstream.chat.android.compose.state.DateFormatType
Expand Down Expand Up @@ -91,7 +92,7 @@ internal class AiMessageContentFactory : MessageContentFactory() {
}

val updatedAt = message.updatedAt
val createdAt = message.createdAt ?: message.createdLocallyAt
val createdAt = message.getCreatedAtOrNull()
val date = when {
createdAt == null -> updatedAt
updatedAt == null -> createdAt
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ public fun Message.getCreatedAtOrThrow(): Date {
* @return when the message was created or null.
*/
public fun Message.getCreatedAtOrNull(): Date? {
return createdAt ?: createdLocallyAt
return createdLocallyAt ?: createdAt
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@

package io.getstream.chat.android.client.extensions.internal

import io.getstream.chat.android.client.extensions.getCreatedAtOrDefault
import io.getstream.chat.android.client.extensions.getCreatedAtOrNull
import io.getstream.chat.android.client.extensions.syncUnreadCountWithReads
import io.getstream.chat.android.client.query.pagination.AnyChannelPaginationRequest
import io.getstream.chat.android.client.utils.message.isDeleted
Expand Down Expand Up @@ -53,7 +55,7 @@ public fun Channel.users(): List<User> {
public val Channel.lastMessage: Message?
get() = messages
.filterNot { it.isDeleted() }
.maxByOrNull { it.createdAt ?: it.createdLocallyAt ?: Date(0) }
.maxByOrNull { it.getCreatedAtOrDefault(NEVER) }

/**
* Updates the [Channel] with newest [Message].
Expand All @@ -68,7 +70,7 @@ public fun Channel.updateLastMessage(
message: Message,
currentUserId: String,
): Channel {
val createdAt = message.createdAt ?: message.createdLocallyAt
val createdAt = message.getCreatedAtOrNull()
checkNotNull(createdAt) { "created at cant be null, be sure to set message.createdAt" }

val newMessages = (
Expand All @@ -77,7 +79,7 @@ public fun Channel.updateLastMessage(
)
.values
.filterNot { it.isDeleted() }
.sortedBy { it.createdAt ?: it.createdLocallyAt }
.sortedBy { it.getCreatedAtOrNull() }

val newReads = read.map { read ->
read.takeUnless { it.user.id == currentUserId }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

package io.getstream.chat.android.client.extensions.internal

import io.getstream.chat.android.client.extensions.getCreatedAtOrDefault
import io.getstream.chat.android.core.internal.InternalStreamChatApi
import io.getstream.chat.android.models.Attachment
import io.getstream.chat.android.models.Channel
Expand Down Expand Up @@ -92,31 +93,31 @@ public val NEVER: Date = Date(0)
*/
@InternalStreamChatApi
public fun Message.wasCreatedAfterOrAt(date: Date?): Boolean {
return (createdAt ?: createdLocallyAt ?: NEVER) >= date
return getCreatedAtOrDefault(NEVER) >= date
}

/**
* Checks if the message was created after the given [date].
*/
@InternalStreamChatApi
public fun Message.wasCreatedAfter(date: Date?): Boolean {
return (createdAt ?: createdLocallyAt ?: NEVER) > date
return getCreatedAtOrDefault(NEVER) > date
}

/**
* Checks if the message was created before the given [date].
*/
@InternalStreamChatApi
public fun Message.wasCreatedBefore(date: Date?): Boolean {
return (createdAt ?: createdLocallyAt ?: NEVER) < date
return getCreatedAtOrDefault(NEVER) < date
}

/**
* Checks if the message was created before or at the given [date].
*/
@InternalStreamChatApi
public fun Message.wasCreatedBeforeOrAt(date: Date?): Boolean {
return (createdAt ?: createdLocallyAt ?: NEVER) <= date
return getCreatedAtOrDefault(NEVER) <= date
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

package io.getstream.chat.android.client.extensions.internal

import io.getstream.chat.android.client.extensions.getCreatedAtOrNull
import io.getstream.chat.android.core.internal.InternalStreamChatApi
import io.getstream.chat.android.models.ChannelUserRead
import io.getstream.chat.android.models.Message
Expand Down Expand Up @@ -64,11 +65,9 @@ public fun Thread.upsertReply(reply: Message): Thread {
val newReplies = upsertMessageInList(reply, this.latestReplies)
val isInsert = newReplies.size > this.latestReplies.size
val sortedNewReplies = newReplies.sortedBy {
it.createdAt ?: it.createdLocallyAt
}
val lastMessageAt = sortedNewReplies.lastOrNull()?.let { latestReply ->
latestReply.createdAt ?: latestReply.createdLocallyAt
it.getCreatedAtOrNull()
}
val lastMessageAt = sortedNewReplies.lastOrNull()?.getCreatedAtOrNull()
// The new message could be from a new thread participant
val threadParticipants = if (isInsert) {
upsertThreadParticipantInList(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ package io.getstream.chat.android.client.interceptor.message.internal
import io.getstream.chat.android.client.channel.state.ChannelStateLogicProvider
import io.getstream.chat.android.client.extensions.EXTRA_UPLOAD_ID
import io.getstream.chat.android.client.extensions.enrichWithCid
import io.getstream.chat.android.client.extensions.getCreatedAtOrDefault
import io.getstream.chat.android.client.extensions.internal.populateMentions
import io.getstream.chat.android.client.extensions.uploadId
import io.getstream.chat.android.client.interceptor.message.PrepareMessageLogic
Expand Down Expand Up @@ -66,7 +67,7 @@ internal class PrepareMessageLogicImpl(
user = user,
attachments = attachments,
type = getMessageType(message),
createdLocallyAt = message.createdAt ?: message.createdLocallyAt ?: Date(),
createdLocallyAt = message.getCreatedAtOrDefault(Date()),
syncStatus = when {
attachments.any { it.uploadState is Attachment.UploadState.Idle } -> SyncStatus.AWAITING_ATTACHMENTS
clientState.isNetworkAvailable -> SyncStatus.IN_PROGRESS
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import androidx.core.app.Person
import androidx.core.content.ContextCompat
import io.getstream.chat.android.client.ChatClient
import io.getstream.chat.android.client.R
import io.getstream.chat.android.client.extensions.getCreatedAtOrDefault
import io.getstream.chat.android.models.Channel
import io.getstream.chat.android.models.Message
import io.getstream.chat.android.models.User
Expand Down Expand Up @@ -176,7 +177,7 @@ internal class MessagingStyleNotificationFactory(
private suspend fun Message.person(context: Context): Person = user.toPerson(context)

private val Message.timestamp: Long
get() = (createdAt ?: createdLocallyAt ?: Date()).time
get() = getCreatedAtOrDefault(Date()).time

private suspend fun User.toPerson(context: Context): Person =
Person.Builder()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@

package io.getstream.chat.android.client.utils.message

import io.getstream.chat.android.client.extensions.getCreatedAtOrNull
import io.getstream.chat.android.core.internal.InternalStreamChatApi
import io.getstream.chat.android.core.utils.date.after
import io.getstream.chat.android.models.AttachmentType
Expand Down Expand Up @@ -55,8 +56,8 @@ public fun List<Message>.latestOrNull(): Message? = when (size >= ITEM_COUNT_OF_
*/
@InternalStreamChatApi
public fun Message.createdAfter(that: Message): Boolean {
val thisDate = this.createdAt ?: this.createdLocallyAt
val thatDate = that.createdAt ?: that.createdLocallyAt
val thisDate = this.getCreatedAtOrNull()
val thatDate = that.getCreatedAtOrNull()
return thisDate after thatDate
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -127,15 +127,18 @@ internal class ChannelExtensionsTests {
text = "Hello @${user.id}",
mentionedUsers = listOf(user),
createdAt = Date(lastReadDate.time - 1000),
createdLocallyAt = null,
),
randomMessage(
text = "Hi @${user.id}",
mentionedUsers = listOf(user),
createdAt = Date(lastReadDate.time + 1000),
createdLocallyAt = null,
),
randomMessage(
text = "No mention here",
createdAt = Date(lastReadDate.time + 2000),
createdLocallyAt = null,
),
)
val channelRead = randomChannelUserRead(user = user, lastRead = lastReadDate)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ internal class MessageExtensionsTests {
@Test
fun `getCreatedAtOrThrow should return createdAt if not null`() {
val date = randomDate()
val message = randomMessage(createdAt = date)
val message = randomMessage(createdAt = date, createdLocallyAt = null)
message.getCreatedAtOrThrow() shouldBeEqualTo date
}

Expand All @@ -86,8 +86,8 @@ internal class MessageExtensionsTests {
@Test
fun `getCreatedAtOrNull should return createdAt if not null`() {
val date = randomDate()
val message = randomMessage(createdAt = date)
message.getCreatedAtOrThrow() shouldBeEqualTo date
val message = randomMessage(createdAt = date, createdLocallyAt = null)
message.getCreatedAtOrNull() shouldBeEqualTo date
}

@Test
Expand All @@ -97,10 +97,24 @@ internal class MessageExtensionsTests {
message.getCreatedAtOrNull() shouldBeEqualTo date
}

@Test
fun `getCreatedAtOrNull should return createdLocallyAt if both createdAt and createdLocallyAt are not null`() {
val createdAtDate = randomDate()
val createdLocallyAtDate = randomDate()
val message = randomMessage(createdAt = createdAtDate, createdLocallyAt = createdLocallyAtDate)
message.getCreatedAtOrNull() shouldBeEqualTo createdLocallyAtDate
}

@Test
fun `getCreatedAtOrNull should return null if createdAt and createdLocallyAt are null`() {
val message = randomMessage(createdAt = null, createdLocallyAt = null)
message.getCreatedAtOrNull() shouldBeEqualTo null
}

@Test
fun `getCreatedAtOrDefault should return createdAt if not null`() {
val date = randomDate()
val message = randomMessage(createdAt = date)
val message = randomMessage(createdAt = date, createdLocallyAt = null)
message.getCreatedAtOrDefault(Date(0)) shouldBeEqualTo date
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,8 @@ internal class ChannelExtensionsTests {
@Test
fun `lastMessage should return the most recent non-deleted message`() {
// given
val oldestMsg = randomMessage(createdAt = Date(1000), deletedAt = null, deletedForMe = false)
val newestMsg = randomMessage(createdAt = Date(3000), deletedAt = null, deletedForMe = false)
val oldestMsg = randomMessage(createdAt = Date(1000), createdLocallyAt = null, deletedAt = null, deletedForMe = false)
val newestMsg = randomMessage(createdAt = Date(3000), createdLocallyAt = null, deletedAt = null, deletedForMe = false)
val middleMsg = randomMessage(
createdAt = null,
createdLocallyAt = Date(2000),
Expand Down Expand Up @@ -104,12 +104,14 @@ internal class ChannelExtensionsTests {
val existingMessage = randomMessage(
id = "existing",
createdAt = Date(1000),
createdLocallyAt = null,
deletedAt = null,
deletedForMe = false,
)
val newMessage = randomMessage(
id = "new",
createdAt = Date(2000),
createdLocallyAt = null,
deletedAt = null,
deletedForMe = false,
silent = false,
Expand Down Expand Up @@ -160,12 +162,14 @@ internal class ChannelExtensionsTests {
val existingMessage = randomMessage(
id = "existing",
createdAt = Date(1000),
createdLocallyAt = null,
deletedAt = null,
deletedForMe = false,
)
val newMessage = randomMessage(
id = "new",
createdAt = Date(2000),
createdLocallyAt = null,
deletedAt = null,
deletedForMe = false,
silent = true,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -329,6 +329,7 @@ internal class MessageExtensionsTests {
val message = randomMessage(
id = "message1",
createdAt = now,
createdLocallyAt = null,
)

// when/then
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,8 @@ internal class MessageUtilsTest {

@Test
fun `latestOrNull should return the latest message based on createdAt`() {
val message1 = randomMessage(createdAt = Date(1000))
val message2 = randomMessage(createdAt = Date(2000))
val message1 = randomMessage(createdAt = Date(1000), createdLocallyAt = null)
val message2 = randomMessage(createdAt = Date(2000), createdLocallyAt = null)
val messages = listOf(message1, message2)
messages.latestOrNull() shouldBeEqualTo message2
}
Expand All @@ -66,15 +66,15 @@ internal class MessageUtilsTest {

@Test
fun `createdAfter should return true if the message was created after the given message`() {
val message1 = randomMessage(createdAt = Date(1000))
val message2 = randomMessage(createdAt = Date(2000))
val message1 = randomMessage(createdAt = Date(1000), createdLocallyAt = null)
val message2 = randomMessage(createdAt = Date(2000), createdLocallyAt = null)
message2.createdAfter(message1) shouldBeEqualTo true
}

@Test
fun `createdAfter should return false if the message was created before the given message`() {
val message1 = randomMessage(createdAt = Date(2000))
val message2 = randomMessage(createdAt = Date(1000))
val message1 = randomMessage(createdAt = Date(2000), createdLocallyAt = null)
val message2 = randomMessage(createdAt = Date(1000), createdLocallyAt = null)
message2.createdAfter(message1) shouldBeEqualTo false
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ import androidx.compose.ui.text.withStyle
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import io.getstream.chat.android.client.extensions.getCreatedAtOrNull
import io.getstream.chat.android.client.utils.message.isDeleted
import io.getstream.chat.android.compose.R
import io.getstream.chat.android.compose.state.OnlineIndicatorAlignment
Expand Down Expand Up @@ -250,7 +251,7 @@ internal fun ThreadItemLatestReplyContent(
)
Timestamp(
modifier = Modifier.padding(start = 8.dp),
date = latestReply.updatedAt ?: latestReply.createdAt ?: latestReply.createdLocallyAt,
date = latestReply.updatedAt ?: latestReply.getCreatedAtOrNull(),
)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,5 +32,5 @@ public fun List<Message>.lastMessageAt(skipLastMsgUpdateForSystemMsgs: Boolean):
.filterNot { it.shadowed }
.filterNot { it.parentId != null && !it.showInChannel }
.filterNot { it.type == MessageType.SYSTEM && skipLastMsgUpdateForSystemMsgs }
.maxByOrNull { it.createdAt ?: it.createdLocallyAt ?: Date(0) }
?.let { it.createdAt ?: it.createdLocallyAt }
.maxByOrNull { it.createdLocallyAt ?: it.createdAt ?: Date(0) }
?.let { it.createdLocallyAt ?: it.createdAt }
Loading
Loading