From 4e95c04d90f3cae36039943e686aa4023bde8c08 Mon Sep 17 00:00:00 2001 From: Gian <47775302+gpunto@users.noreply.github.com> Date: Fri, 29 May 2026 11:38:52 +0200 Subject: [PATCH 1/2] Wire enhanced-mention fields through Message --- .../client/api2/mapping/DomainMapping.kt | 29 +++++++- .../android/client/api2/mapping/DtoMapping.kt | 5 ++ .../client/api2/model/dto/ConfigDto.kt | 1 + .../client/api2/model/dto/MessageDtos.kt | 8 ++ .../client/api2/model/dto/UserGroupDtos.kt | 40 ++++++++++ .../converter/internal/UserGroupConverter.kt | 36 +++++++++ .../database/internal/ChatDatabase.kt | 4 +- .../internal/ChannelConfigEntity.kt | 1 + .../internal/ChannelConfigMapper.kt | 2 + .../domain/message/internal/MessageEntity.kt | 9 +++ .../domain/message/internal/MessageMapper.kt | 16 ++++ .../message/internal/ReplyMessageEntity.kt | 9 +++ .../getstream/chat/android/client/Mother.kt | 30 ++++++++ .../client/api2/mapping/DomainMappingTest.kt | 25 +++++++ .../client/api2/mapping/DtoMappingTest.kt | 5 ++ .../android/client/internal/offline/Mother.kt | 10 +++ .../message/internal/MessageMapperTest.kt | 8 ++ .../parser2/testdata/MessageDtoTestData.kt | 23 ++++++ .../api/stream-chat-android-core.api | 74 +++++++++++++++++-- .../getstream/chat/android/models/Config.kt | 6 ++ .../getstream/chat/android/models/Message.kt | 46 ++++++++++++ .../chat/android/models/UserGroup.kt | 60 +++++++++++++++ .../io/getstream/chat/android/Mother.kt | 42 +++++++++++ 23 files changed, 479 insertions(+), 10 deletions(-) create mode 100644 stream-chat-android-client/src/main/java/io/getstream/chat/android/client/api2/model/dto/UserGroupDtos.kt create mode 100644 stream-chat-android-client/src/main/java/io/getstream/chat/android/client/internal/offline/repository/database/converter/internal/UserGroupConverter.kt create mode 100644 stream-chat-android-core/src/main/java/io/getstream/chat/android/models/UserGroup.kt diff --git a/stream-chat-android-client/src/main/java/io/getstream/chat/android/client/api2/mapping/DomainMapping.kt b/stream-chat-android-client/src/main/java/io/getstream/chat/android/client/api2/mapping/DomainMapping.kt index 9fa70b61a0d..856653e7ba2 100644 --- a/stream-chat-android-client/src/main/java/io/getstream/chat/android/client/api2/mapping/DomainMapping.kt +++ b/stream-chat-android-client/src/main/java/io/getstream/chat/android/client/api2/mapping/DomainMapping.kt @@ -50,6 +50,8 @@ import io.getstream.chat.android.client.api2.model.dto.DownstreamThreadInfoDto import io.getstream.chat.android.client.api2.model.dto.DownstreamThreadParticipantDto import io.getstream.chat.android.client.api2.model.dto.DownstreamUserBlockDto import io.getstream.chat.android.client.api2.model.dto.DownstreamUserDto +import io.getstream.chat.android.client.api2.model.dto.DownstreamUserGroupDto +import io.getstream.chat.android.client.api2.model.dto.DownstreamUserGroupMemberDto import io.getstream.chat.android.client.api2.model.dto.DownstreamVoteDto import io.getstream.chat.android.client.api2.model.dto.PrivacySettingsDto import io.getstream.chat.android.client.api2.model.dto.ReadReceiptsDto @@ -121,6 +123,8 @@ import io.getstream.chat.android.models.UnreadCounts import io.getstream.chat.android.models.UnreadThread import io.getstream.chat.android.models.User import io.getstream.chat.android.models.UserBlock +import io.getstream.chat.android.models.UserGroup +import io.getstream.chat.android.models.UserGroupMember import io.getstream.chat.android.models.UserId import io.getstream.chat.android.models.UserTransformer import io.getstream.chat.android.models.Vote @@ -130,7 +134,7 @@ import io.getstream.chat.android.models.querysort.QuerySorter import io.getstream.chat.android.models.querysort.SortDirection import java.util.Date -@Suppress("TooManyFunctions") +@Suppress("TooManyFunctions", "LargeClass") internal class DomainMapping( val currentUserIdProvider: () -> UserId?, private val channelTransformer: ChannelTransformer, @@ -225,6 +229,10 @@ internal class DomainMapping( messageId = id, ), mentionedUsers = mentioned_users.map { it.toDomain() }, + mentionedHere = mentioned_here ?: false, + mentionedChannel = mentioned_channel ?: false, + mentionedGroups = mentioned_groups.map { it.toDomain() }, + mentionedRoles = mentioned_roles, ownReactions = own_reactions.toDomain( messageId = id, ), @@ -672,6 +680,7 @@ internal class DomainMapping( messageRemindersEnabled = user_message_reminders ?: false, sharedLocationsEnabled = shared_locations ?: false, markMessagesPending = mark_messages_pending, + pushLevel = push_level, ) /** @@ -930,6 +939,24 @@ internal class DomainMapping( return QuerySortByField().desc(field) } + internal fun DownstreamUserGroupDto.toDomain(): UserGroup = UserGroup( + id = id, + name = name, + description = description, + team = team_id.orEmpty(), + members = members.map { it.toDomain() }, + createdBy = created_by, + createdAt = created_at, + updatedAt = updated_at, + ) + + internal fun DownstreamUserGroupMemberDto.toDomain(): UserGroupMember = UserGroupMember( + groupId = group_id, + userId = user_id, + isAdmin = is_admin, + createdAt = created_at, + ) + private companion object { private const val FIELD_LAST_MESSAGE_AT = "last_message_at" private const val FIELD_LAST_UPDATED = "last_updated" diff --git a/stream-chat-android-client/src/main/java/io/getstream/chat/android/client/api2/mapping/DtoMapping.kt b/stream-chat-android-client/src/main/java/io/getstream/chat/android/client/api2/mapping/DtoMapping.kt index 05bfc6f1639..1f86c282f4d 100644 --- a/stream-chat-android-client/src/main/java/io/getstream/chat/android/client/api2/mapping/DtoMapping.kt +++ b/stream-chat-android-client/src/main/java/io/getstream/chat/android/client/api2/mapping/DtoMapping.kt @@ -47,6 +47,7 @@ import io.getstream.chat.android.models.MessageType import io.getstream.chat.android.models.Mute import io.getstream.chat.android.models.Reaction import io.getstream.chat.android.models.User +import io.getstream.chat.android.models.UserGroup import io.getstream.chat.android.models.UserTransformer internal class DtoMapping( @@ -134,6 +135,10 @@ internal class DtoMapping( id = id, type = upstreamType, mentioned_users = mentionedUsersIds, + mentioned_here = mentionedHere, + mentioned_channel = mentionedChannel, + mentioned_group_ids = mentionedGroups.map(UserGroup::id), + mentioned_roles = mentionedRoles, parent_id = parentId, pin_expires = pinExpires, pinned = pinned, diff --git a/stream-chat-android-client/src/main/java/io/getstream/chat/android/client/api2/model/dto/ConfigDto.kt b/stream-chat-android-client/src/main/java/io/getstream/chat/android/client/api2/model/dto/ConfigDto.kt index 24d337b71b4..fca89283b14 100644 --- a/stream-chat-android-client/src/main/java/io/getstream/chat/android/client/api2/model/dto/ConfigDto.kt +++ b/stream-chat-android-client/src/main/java/io/getstream/chat/android/client/api2/model/dto/ConfigDto.kt @@ -47,4 +47,5 @@ internal data class ConfigDto( val user_message_reminders: Boolean?, val shared_locations: Boolean?, val mark_messages_pending: Boolean, + val push_level: String? = null, ) diff --git a/stream-chat-android-client/src/main/java/io/getstream/chat/android/client/api2/model/dto/MessageDtos.kt b/stream-chat-android-client/src/main/java/io/getstream/chat/android/client/api2/model/dto/MessageDtos.kt index e2450bcfcab..050a49cc56c 100644 --- a/stream-chat-android-client/src/main/java/io/getstream/chat/android/client/api2/model/dto/MessageDtos.kt +++ b/stream-chat-android-client/src/main/java/io/getstream/chat/android/client/api2/model/dto/MessageDtos.kt @@ -38,6 +38,10 @@ internal data class UpstreamMessageDto( val id: String, val type: String, val mentioned_users: List, + val mentioned_here: Boolean = false, + val mentioned_channel: Boolean = false, + val mentioned_group_ids: List = emptyList(), + val mentioned_roles: List = emptyList(), val parent_id: String?, val pin_expires: Date?, val pinned: Boolean?, @@ -75,6 +79,10 @@ internal data class DownstreamMessageDto( val id: String, val latest_reactions: List, val mentioned_users: List, + val mentioned_here: Boolean? = null, + val mentioned_channel: Boolean? = null, + val mentioned_groups: List = emptyList(), + val mentioned_roles: List = emptyList(), val own_reactions: List, val parent_id: String?, val pin_expires: Date?, diff --git a/stream-chat-android-client/src/main/java/io/getstream/chat/android/client/api2/model/dto/UserGroupDtos.kt b/stream-chat-android-client/src/main/java/io/getstream/chat/android/client/api2/model/dto/UserGroupDtos.kt new file mode 100644 index 00000000000..96e9c8b04eb --- /dev/null +++ b/stream-chat-android-client/src/main/java/io/getstream/chat/android/client/api2/model/dto/UserGroupDtos.kt @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2014-2026 Stream.io Inc. All rights reserved. + * + * Licensed under the Stream License; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://github.com/GetStream/stream-chat-android/blob/main/LICENSE + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.getstream.chat.android.client.api2.model.dto + +import com.squareup.moshi.JsonClass +import java.util.Date + +@JsonClass(generateAdapter = true) +internal data class DownstreamUserGroupDto( + val id: String, + val name: String, + val description: String? = null, + val team_id: String? = null, + val members: List = emptyList(), + val created_by: String? = null, + val created_at: Date? = null, + val updated_at: Date? = null, +) + +@JsonClass(generateAdapter = true) +internal data class DownstreamUserGroupMemberDto( + val group_id: String, + val user_id: String, + val is_admin: Boolean = false, + val created_at: Date? = null, +) diff --git a/stream-chat-android-client/src/main/java/io/getstream/chat/android/client/internal/offline/repository/database/converter/internal/UserGroupConverter.kt b/stream-chat-android-client/src/main/java/io/getstream/chat/android/client/internal/offline/repository/database/converter/internal/UserGroupConverter.kt new file mode 100644 index 00000000000..6619ee5da83 --- /dev/null +++ b/stream-chat-android-client/src/main/java/io/getstream/chat/android/client/internal/offline/repository/database/converter/internal/UserGroupConverter.kt @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2014-2026 Stream.io Inc. All rights reserved. + * + * Licensed under the Stream License; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://github.com/GetStream/stream-chat-android/blob/main/LICENSE + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.getstream.chat.android.client.internal.offline.repository.database.converter.internal + +import androidx.room.TypeConverter +import com.squareup.moshi.adapter +import io.getstream.chat.android.models.UserGroup + +internal class UserGroupConverter { + + @OptIn(ExperimentalStdlibApi::class) + private val listAdapter = moshi.adapter>() + + @TypeConverter + fun userGroupListToString(groups: List?): String? = groups?.let(listAdapter::toJson) + + @TypeConverter + fun stringToUserGroupList(data: String?): List? { + if (data.isNullOrEmpty() || data == "null") return emptyList() + return listAdapter.fromJson(data) + } +} diff --git a/stream-chat-android-client/src/main/java/io/getstream/chat/android/client/internal/offline/repository/database/internal/ChatDatabase.kt b/stream-chat-android-client/src/main/java/io/getstream/chat/android/client/internal/offline/repository/database/internal/ChatDatabase.kt index 74cdada44b3..2dc60669b1d 100644 --- a/stream-chat-android-client/src/main/java/io/getstream/chat/android/client/internal/offline/repository/database/internal/ChatDatabase.kt +++ b/stream-chat-android-client/src/main/java/io/getstream/chat/android/client/internal/offline/repository/database/internal/ChatDatabase.kt @@ -40,6 +40,7 @@ import io.getstream.chat.android.client.internal.offline.repository.database.con import io.getstream.chat.android.client.internal.offline.repository.database.converter.internal.ReminderInfoConverter import io.getstream.chat.android.client.internal.offline.repository.database.converter.internal.SetConverter import io.getstream.chat.android.client.internal.offline.repository.database.converter.internal.SyncStatusConverter +import io.getstream.chat.android.client.internal.offline.repository.database.converter.internal.UserGroupConverter import io.getstream.chat.android.client.internal.offline.repository.database.converter.internal.UserMuteConverter import io.getstream.chat.android.client.internal.offline.repository.database.converter.internal.VoteConverter import io.getstream.chat.android.client.internal.offline.repository.domain.channel.internal.ChannelDao @@ -88,7 +89,7 @@ import io.getstream.chat.android.client.internal.offline.repository.domain.user. ThreadOrderEntity::class, DraftMessageEntity::class, ], - version = 200, + version = 201, exportSchema = false, ) @TypeConverters( @@ -112,6 +113,7 @@ import io.getstream.chat.android.client.internal.offline.repository.domain.user. LocationConverter::class, PushPreferenceConverter::class, UserMuteConverter::class, + UserGroupConverter::class, ) internal abstract class ChatDatabase : RoomDatabase() { abstract fun queryChannelsDao(): QueryChannelsDao diff --git a/stream-chat-android-client/src/main/java/io/getstream/chat/android/client/internal/offline/repository/domain/channelconfig/internal/ChannelConfigEntity.kt b/stream-chat-android-client/src/main/java/io/getstream/chat/android/client/internal/offline/repository/domain/channelconfig/internal/ChannelConfigEntity.kt index c16d94240ac..e5ab3548098 100644 --- a/stream-chat-android-client/src/main/java/io/getstream/chat/android/client/internal/offline/repository/domain/channelconfig/internal/ChannelConfigEntity.kt +++ b/stream-chat-android-client/src/main/java/io/getstream/chat/android/client/internal/offline/repository/domain/channelconfig/internal/ChannelConfigEntity.kt @@ -50,6 +50,7 @@ internal data class ChannelConfigInnerEntity( val blocklistBehavior: String, val messageRemindersEnabled: Boolean, val markMessagesPending: Boolean, + val pushLevel: String? = null, ) @Entity( diff --git a/stream-chat-android-client/src/main/java/io/getstream/chat/android/client/internal/offline/repository/domain/channelconfig/internal/ChannelConfigMapper.kt b/stream-chat-android-client/src/main/java/io/getstream/chat/android/client/internal/offline/repository/domain/channelconfig/internal/ChannelConfigMapper.kt index f4bcdeb5a28..6cf3b9dc6bd 100644 --- a/stream-chat-android-client/src/main/java/io/getstream/chat/android/client/internal/offline/repository/domain/channelconfig/internal/ChannelConfigMapper.kt +++ b/stream-chat-android-client/src/main/java/io/getstream/chat/android/client/internal/offline/repository/domain/channelconfig/internal/ChannelConfigMapper.kt @@ -46,6 +46,7 @@ internal fun ChannelConfig.toEntity(): ChannelConfigEntity = ChannelConfigEntity blocklistBehavior = blocklistBehavior, messageRemindersEnabled = messageRemindersEnabled, markMessagesPending = markMessagesPending, + pushLevel = pushLevel, ) }, commands = config.commands.map { it.toEntity(type) }, @@ -78,6 +79,7 @@ internal fun ChannelConfigEntity.toModel(): ChannelConfig = ChannelConfig( commands = commands.map(CommandInnerEntity::toModel), messageRemindersEnabled = messageRemindersEnabled, markMessagesPending = markMessagesPending, + pushLevel = pushLevel, ) }, ) diff --git a/stream-chat-android-client/src/main/java/io/getstream/chat/android/client/internal/offline/repository/domain/message/internal/MessageEntity.kt b/stream-chat-android-client/src/main/java/io/getstream/chat/android/client/internal/offline/repository/domain/message/internal/MessageEntity.kt index 883b264dcbb..6b8404145f0 100644 --- a/stream-chat-android-client/src/main/java/io/getstream/chat/android/client/internal/offline/repository/domain/message/internal/MessageEntity.kt +++ b/stream-chat-android-client/src/main/java/io/getstream/chat/android/client/internal/offline/repository/domain/message/internal/MessageEntity.kt @@ -25,6 +25,7 @@ import io.getstream.chat.android.client.internal.offline.repository.domain.messa import io.getstream.chat.android.client.internal.offline.repository.domain.message.channelinfo.internal.ChannelInfoEntity import io.getstream.chat.android.client.internal.offline.repository.domain.reaction.internal.ReactionEntity import io.getstream.chat.android.models.SyncStatus +import io.getstream.chat.android.models.UserGroup import java.util.Date internal data class MessageEntity( @@ -77,6 +78,14 @@ internal data class MessageInnerEntity( val remoteMentionedUserIds: List = emptyList(), /** the users to be mentioned in this message */ val mentionedUsersId: List = emptyList(), + /** whether this message includes an `@here` mention */ + val mentionedHere: Boolean = false, + /** whether this message includes an `@channel` mention */ + val mentionedChannel: Boolean = false, + /** the user groups mentioned in this message */ + val mentionedGroups: List = emptyList(), + /** the roles mentioned in this message */ + val mentionedRoles: List = emptyList(), /** a mapping between reaction type and the count, ie like:10, heart:4 */ val reactionCounts: Map = emptyMap(), /** a mapping between reaction type and the reaction score, ie like:10, heart:4 */ diff --git a/stream-chat-android-client/src/main/java/io/getstream/chat/android/client/internal/offline/repository/domain/message/internal/MessageMapper.kt b/stream-chat-android-client/src/main/java/io/getstream/chat/android/client/internal/offline/repository/domain/message/internal/MessageMapper.kt index 42402697d86..a003601d9ce 100644 --- a/stream-chat-android-client/src/main/java/io/getstream/chat/android/client/internal/offline/repository/domain/message/internal/MessageMapper.kt +++ b/stream-chat-android-client/src/main/java/io/getstream/chat/android/client/internal/offline/repository/domain/message/internal/MessageMapper.kt @@ -64,6 +64,10 @@ internal suspend fun MessageEntity.toModel( ownReactions = (ownReactions.map { it.toModel(getUser) }), mentionedUsers = remoteMentionedUserIds.map { getUser(it) }, mentionedUsersIds = mentionedUsersId, + mentionedHere = mentionedHere, + mentionedChannel = mentionedChannel, + mentionedGroups = mentionedGroups, + mentionedRoles = mentionedRoles, replyTo = replyToId?.let { getReply(it) }, replyMessageId = replyToId, threadParticipants = threadParticipantsIds.map { getUser(it) }, @@ -114,6 +118,10 @@ internal fun Message.toEntity(): MessageEntity = MessageEntity( i18n = i18n, remoteMentionedUserIds = mentionedUsers.map(User::id), mentionedUsersId = mentionedUsersIds, + mentionedHere = mentionedHere, + mentionedChannel = mentionedChannel, + mentionedGroups = mentionedGroups, + mentionedRoles = mentionedRoles, replyToId = replyTo?.id ?: replyMessageId, threadParticipantsIds = threadParticipants.map(User::id), showInChannel = showInChannel, @@ -170,6 +178,10 @@ internal suspend fun ReplyMessageEntity.toModel( ownReactions = mutableListOf(), mentionedUsers = remoteMentionedUserIds.map { getUser(it) }, mentionedUsersIds = mentionedUsersId, + mentionedHere = mentionedHere, + mentionedChannel = mentionedChannel, + mentionedGroups = mentionedGroups, + mentionedRoles = mentionedRoles, replyTo = null, replyMessageId = null, threadParticipants = threadParticipantsIds.map { getUser(it) }, @@ -213,6 +225,10 @@ internal fun Message.toReplyEntity(): ReplyMessageEntity = i18n = i18n, remoteMentionedUserIds = mentionedUsers.map(User::id), mentionedUsersId = mentionedUsersIds, + mentionedHere = mentionedHere, + mentionedChannel = mentionedChannel, + mentionedGroups = mentionedGroups, + mentionedRoles = mentionedRoles, threadParticipantsIds = threadParticipants.map(User::id), showInChannel = showInChannel, silent = silent, diff --git a/stream-chat-android-client/src/main/java/io/getstream/chat/android/client/internal/offline/repository/domain/message/internal/ReplyMessageEntity.kt b/stream-chat-android-client/src/main/java/io/getstream/chat/android/client/internal/offline/repository/domain/message/internal/ReplyMessageEntity.kt index 65238794e48..4f6c032bcbe 100644 --- a/stream-chat-android-client/src/main/java/io/getstream/chat/android/client/internal/offline/repository/domain/message/internal/ReplyMessageEntity.kt +++ b/stream-chat-android-client/src/main/java/io/getstream/chat/android/client/internal/offline/repository/domain/message/internal/ReplyMessageEntity.kt @@ -24,6 +24,7 @@ import androidx.room.Relation import io.getstream.chat.android.client.internal.offline.repository.domain.message.attachment.internal.ReplyAttachmentEntity import io.getstream.chat.android.client.internal.offline.repository.domain.message.channelinfo.internal.ChannelInfoEntity import io.getstream.chat.android.models.SyncStatus +import io.getstream.chat.android.models.UserGroup import java.util.Date internal data class ReplyMessageEntity( @@ -70,6 +71,14 @@ internal data class ReplyMessageInnerEntity( val remoteMentionedUserIds: List = emptyList(), /** the users to be mentioned in this message */ val mentionedUsersId: List = emptyList(), + /** whether this message includes an `@here` mention */ + val mentionedHere: Boolean = false, + /** whether this message includes an `@channel` mention */ + val mentionedChannel: Boolean = false, + /** the user groups mentioned in this message */ + val mentionedGroups: List = emptyList(), + /** the roles mentioned in this message */ + val mentionedRoles: List = emptyList(), /** parent id, used for threads */ val parentId: String? = null, /** slash command like /giphy etc */ diff --git a/stream-chat-android-client/src/test/java/io/getstream/chat/android/client/Mother.kt b/stream-chat-android-client/src/test/java/io/getstream/chat/android/client/Mother.kt index 40a1b270d3b..5c775294646 100644 --- a/stream-chat-android-client/src/test/java/io/getstream/chat/android/client/Mother.kt +++ b/stream-chat-android-client/src/test/java/io/getstream/chat/android/client/Mother.kt @@ -53,6 +53,8 @@ import io.getstream.chat.android.client.api2.model.dto.DownstreamThreadInfoDto import io.getstream.chat.android.client.api2.model.dto.DownstreamThreadParticipantDto import io.getstream.chat.android.client.api2.model.dto.DownstreamUserBlockDto import io.getstream.chat.android.client.api2.model.dto.DownstreamUserDto +import io.getstream.chat.android.client.api2.model.dto.DownstreamUserGroupDto +import io.getstream.chat.android.client.api2.model.dto.DownstreamUserGroupMemberDto import io.getstream.chat.android.client.api2.model.dto.DownstreamVoteDto import io.getstream.chat.android.client.api2.model.dto.ErrorDetailDto import io.getstream.chat.android.client.api2.model.dto.ErrorDto @@ -290,6 +292,10 @@ internal object Mother { id: String = randomString(), latest_reactions: List = emptyList(), mentioned_users: List = emptyList(), + mentioned_here: Boolean? = null, + mentioned_channel: Boolean? = null, + mentioned_groups: List = emptyList(), + mentioned_roles: List = emptyList(), own_reactions: List = emptyList(), parent_id: String? = randomString(), pin_expires: Date? = randomDateOrNull(), @@ -331,6 +337,10 @@ internal object Mother { id = id, latest_reactions = latest_reactions, mentioned_users = mentioned_users, + mentioned_here = mentioned_here, + mentioned_channel = mentioned_channel, + mentioned_groups = mentioned_groups, + mentioned_roles = mentioned_roles, own_reactions = own_reactions, parent_id = parent_id, pin_expires = pin_expires, @@ -1374,6 +1384,26 @@ internal object Mother { chat_level = chatLevel, disabled_until = disabledUntil, ) + + fun randomDownstreamUserGroupDto( + id: String = randomString(), + name: String = randomString(), + description: String? = randomString(), + teamId: String? = randomString(), + members: List = emptyList(), + createdBy: String? = randomString(), + createdAt: Date? = randomDate(), + updatedAt: Date? = randomDate(), + ): DownstreamUserGroupDto = DownstreamUserGroupDto( + id = id, + name = name, + description = description, + team_id = teamId, + members = members, + created_by = createdBy, + created_at = createdAt, + updated_at = updatedAt, + ) } internal fun randomPushMessage( diff --git a/stream-chat-android-client/src/test/java/io/getstream/chat/android/client/api2/mapping/DomainMappingTest.kt b/stream-chat-android-client/src/test/java/io/getstream/chat/android/client/api2/mapping/DomainMappingTest.kt index d6ec745788a..15c58c24e3c 100644 --- a/stream-chat-android-client/src/test/java/io/getstream/chat/android/client/api2/mapping/DomainMappingTest.kt +++ b/stream-chat-android-client/src/test/java/io/getstream/chat/android/client/api2/mapping/DomainMappingTest.kt @@ -62,6 +62,7 @@ import io.getstream.chat.android.client.Mother.randomUnreadCountByTeamDto import io.getstream.chat.android.client.Mother.randomUnreadDto import io.getstream.chat.android.client.Mother.randomUnreadThreadDto import io.getstream.chat.android.client.api2.mapping.DomainMappingTest.Companion.toSortDomainArguments +import io.getstream.chat.android.client.api2.model.dto.DownstreamUserGroupDto import io.getstream.chat.android.client.api2.model.response.MessageResponse import io.getstream.chat.android.client.extensions.internal.sortedByLastReply import io.getstream.chat.android.models.Answer @@ -108,6 +109,7 @@ import io.getstream.chat.android.models.UnreadChannelByType import io.getstream.chat.android.models.UnreadCounts import io.getstream.chat.android.models.UnreadThread import io.getstream.chat.android.models.UserBlock +import io.getstream.chat.android.models.UserGroup import io.getstream.chat.android.models.UserId import io.getstream.chat.android.models.UserTransformer import io.getstream.chat.android.models.Vote @@ -123,6 +125,7 @@ import io.getstream.chat.android.randomPendingMessageMetadata import io.getstream.chat.android.randomString import io.getstream.chat.android.randomUser import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertTrue import org.junit.jupiter.api.Test import org.junit.jupiter.api.assertThrows import org.junit.jupiter.params.ParameterizedTest @@ -172,6 +175,28 @@ internal class DomainMappingTest { assertEquals(transformedMessage, result) } + @Test + fun `Mention fields propagate from DownstreamMessageDto to Message`() { + val sut = Fixture().get() + val dto = randomDownstreamMessageDto( + mentioned_here = true, + mentioned_channel = true, + mentioned_groups = listOf( + DownstreamUserGroupDto(id = "g1", name = "platform"), + DownstreamUserGroupDto(id = "g2", name = "support"), + ), + mentioned_roles = listOf("admin", "moderator"), + ) + + val result = with(sut) { dto.toDomain() } + + assertTrue(result.mentionedHere) + assertTrue(result.mentionedChannel) + assertEquals(listOf("admin", "moderator"), result.mentionedRoles) + assertEquals(listOf("g1", "g2"), result.mentionedGroups.map(UserGroup::id)) + assertEquals(listOf("platform", "support"), result.mentionedGroups.map(UserGroup::name)) + } + @Test fun `DownstreamDraftDto is correctly mapped to DraftMessage`() { val draftMessageResponse = randomDownstreamDraftDto( diff --git a/stream-chat-android-client/src/test/java/io/getstream/chat/android/client/api2/mapping/DtoMappingTest.kt b/stream-chat-android-client/src/test/java/io/getstream/chat/android/client/api2/mapping/DtoMappingTest.kt index cc155118c90..2236331c9e6 100644 --- a/stream-chat-android-client/src/test/java/io/getstream/chat/android/client/api2/mapping/DtoMappingTest.kt +++ b/stream-chat-android-client/src/test/java/io/getstream/chat/android/client/api2/mapping/DtoMappingTest.kt @@ -38,6 +38,7 @@ import io.getstream.chat.android.models.MessageTransformer import io.getstream.chat.android.models.MessageType import io.getstream.chat.android.models.NoOpMessageTransformer import io.getstream.chat.android.models.NoOpUserTransformer +import io.getstream.chat.android.models.UserGroup import io.getstream.chat.android.models.UserTransformer import io.getstream.chat.android.randomAttachment import io.getstream.chat.android.randomDevice @@ -186,6 +187,10 @@ internal class DtoMappingTest { id = message.id, type = message.type, mentioned_users = message.mentionedUsersIds, + mentioned_here = message.mentionedHere, + mentioned_channel = message.mentionedChannel, + mentioned_roles = message.mentionedRoles, + mentioned_group_ids = message.mentionedGroups.map(UserGroup::id), parent_id = message.parentId, pin_expires = message.pinExpires, pinned = message.pinned, diff --git a/stream-chat-android-client/src/test/java/io/getstream/chat/android/client/internal/offline/Mother.kt b/stream-chat-android-client/src/test/java/io/getstream/chat/android/client/internal/offline/Mother.kt index eed604c26fe..406373be919 100644 --- a/stream-chat-android-client/src/test/java/io/getstream/chat/android/client/internal/offline/Mother.kt +++ b/stream-chat-android-client/src/test/java/io/getstream/chat/android/client/internal/offline/Mother.kt @@ -42,6 +42,7 @@ import io.getstream.chat.android.models.Channel import io.getstream.chat.android.models.FilterObject import io.getstream.chat.android.models.NeutralFilterObject import io.getstream.chat.android.models.SyncStatus +import io.getstream.chat.android.models.UserGroup import io.getstream.chat.android.models.querysort.QuerySortByField import io.getstream.chat.android.models.querysort.QuerySorter import io.getstream.chat.android.randomBoolean @@ -52,6 +53,7 @@ import io.getstream.chat.android.randomDouble import io.getstream.chat.android.randomInt import io.getstream.chat.android.randomString import io.getstream.chat.android.randomStringOrNull +import io.getstream.chat.android.randomUserGroup import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.asExecutor import java.util.Date @@ -133,6 +135,10 @@ internal fun randomMessageEntity( latestReactions: List = emptyList(), ownReactions: List = emptyList(), mentionedUsersId: List = emptyList(), + mentionedHere: Boolean = randomBoolean(), + mentionedChannel: Boolean = randomBoolean(), + mentionedRoles: List = listOf(randomString()), + mentionedGroups: List = listOf(randomUserGroup()), reactionCounts: Map = emptyMap(), reactionScores: Map = emptyMap(), reactionGroups: Map = emptyMap(), @@ -167,6 +173,10 @@ internal fun randomMessageEntity( updatedLocallyAt = updatedLocallyAt, deletedAt = deletedAt, mentionedUsersId = mentionedUsersId, + mentionedHere = mentionedHere, + mentionedChannel = mentionedChannel, + mentionedRoles = mentionedRoles, + mentionedGroups = mentionedGroups, reactionCounts = reactionCounts, reactionScores = reactionScores, reactionGroups = reactionGroups, diff --git a/stream-chat-android-client/src/test/java/io/getstream/chat/android/client/internal/offline/repository/domain/message/internal/MessageMapperTest.kt b/stream-chat-android-client/src/test/java/io/getstream/chat/android/client/internal/offline/repository/domain/message/internal/MessageMapperTest.kt index 34fed2d23e6..568dc3409a2 100644 --- a/stream-chat-android-client/src/test/java/io/getstream/chat/android/client/internal/offline/repository/domain/message/internal/MessageMapperTest.kt +++ b/stream-chat-android-client/src/test/java/io/getstream/chat/android/client/internal/offline/repository/domain/message/internal/MessageMapperTest.kt @@ -76,6 +76,10 @@ internal class MessageMapperTest { ownReactions = emptyList(), mentionedUsers = emptyList(), mentionedUsersIds = messageEntity.messageInnerEntity.mentionedUsersId, + mentionedHere = messageEntity.messageInnerEntity.mentionedHere, + mentionedChannel = messageEntity.messageInnerEntity.mentionedChannel, + mentionedRoles = messageEntity.messageInnerEntity.mentionedRoles, + mentionedGroups = messageEntity.messageInnerEntity.mentionedGroups, replyTo = null, replyMessageId = messageEntity.messageInnerEntity.replyToId, threadParticipants = emptyList(), @@ -150,6 +154,10 @@ internal class MessageMapperTest { i18n = message.i18n, remoteMentionedUserIds = message.mentionedUsers.map(User::id), mentionedUsersId = message.mentionedUsersIds, + mentionedHere = message.mentionedHere, + mentionedChannel = message.mentionedChannel, + mentionedRoles = message.mentionedRoles, + mentionedGroups = message.mentionedGroups, replyToId = message.replyTo?.id, threadParticipantsIds = message.threadParticipants.map(User::id), showInChannel = message.showInChannel, diff --git a/stream-chat-android-client/src/test/java/io/getstream/chat/android/client/parser2/testdata/MessageDtoTestData.kt b/stream-chat-android-client/src/test/java/io/getstream/chat/android/client/parser2/testdata/MessageDtoTestData.kt index 9402cf83219..f48d8892181 100644 --- a/stream-chat-android-client/src/test/java/io/getstream/chat/android/client/parser2/testdata/MessageDtoTestData.kt +++ b/stream-chat-android-client/src/test/java/io/getstream/chat/android/client/parser2/testdata/MessageDtoTestData.kt @@ -19,6 +19,7 @@ package io.getstream.chat.android.client.parser2.testdata import io.getstream.chat.android.client.api2.model.dto.DownstreamMessageDto import io.getstream.chat.android.client.api2.model.dto.DownstreamModerationDetailsDto import io.getstream.chat.android.client.api2.model.dto.DownstreamReactionGroupDto +import io.getstream.chat.android.client.api2.model.dto.DownstreamUserGroupDto import io.getstream.chat.android.client.api2.model.dto.UpstreamMessageDto import org.intellij.lang.annotations.Language import java.util.Date @@ -38,6 +39,16 @@ internal object MessageDtoTestData { "id": "8584452-6d711169-0224-41c2-b9aa-1adbe624521b", "latest_reactions": [${ReactionDtoTestData.downstreamJsonWithoutExtraData}], "mentioned_users": [${UserDtoTestData.downstreamJsonWithoutExtraData}], + "mentioned_here": true, + "mentioned_channel": true, + "mentioned_groups": [ + { + "id": "g1", + "name": "platform", + "members": [] + } + ], + "mentioned_roles": ["admin", "moderator"], "own_reactions": [], "reaction_counts": {"like": 2}, "reaction_scores": {"like": 10}, @@ -151,6 +162,10 @@ internal object MessageDtoTestData { own_reactions = emptyList(), show_in_channel = false, mentioned_users = listOf(UserDtoTestData.downstreamUserWithoutExtraData), + mentioned_here = true, + mentioned_channel = true, + mentioned_groups = listOf(DownstreamUserGroupDto(id = "g1", name = "platform")), + mentioned_roles = listOf("admin", "moderator"), i18n = emptyMap(), thread_participants = emptyList(), attachments = listOf(AttachmentDtoTestData.attachment), @@ -385,6 +400,10 @@ internal object MessageDtoTestData { "id": "8584452-6d711169-0224-41c2-b9aa-1adbe624521b", "type": "regular", "mentioned_users": [], + "mentioned_here": false, + "mentioned_channel": false, + "mentioned_group_ids": [], + "mentioned_roles": [], "pinned": true, "shadowed": false, "show_in_channel": false, @@ -456,6 +475,10 @@ internal object MessageDtoTestData { "id": "8584452-6d711169-0224-41c2-b9aa-1adbe624521b", "type": "regular", "mentioned_users": [], + "mentioned_here": false, + "mentioned_channel": false, + "mentioned_group_ids": [], + "mentioned_roles": [], "pinned": false, "shadowed": false, "show_in_channel": false, diff --git a/stream-chat-android-core/api/stream-chat-android-core.api b/stream-chat-android-core/api/stream-chat-android-core.api index 48545cdbfc4..2f46b0283f7 100644 --- a/stream-chat-android-core/api/stream-chat-android-core.api +++ b/stream-chat-android-core/api/stream-chat-android-core.api @@ -675,8 +675,8 @@ public final class io/getstream/chat/android/models/Command { public final class io/getstream/chat/android/models/Config { public fun ()V - public fun (Ljava/util/Date;Ljava/util/Date;Ljava/lang/String;ZZZZZZZZZZZZZZLjava/lang/String;ILjava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/List;ZZZ)V - public synthetic fun (Ljava/util/Date;Ljava/util/Date;Ljava/lang/String;ZZZZZZZZZZZZZZLjava/lang/String;ILjava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/List;ZZZILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun (Ljava/util/Date;Ljava/util/Date;Ljava/lang/String;ZZZZZZZZZZZZZZLjava/lang/String;ILjava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/List;ZZZLjava/lang/String;)V + public synthetic fun (Ljava/util/Date;Ljava/util/Date;Ljava/lang/String;ZZZZZZZZZZZZZZLjava/lang/String;ILjava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/List;ZZZLjava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun component1 ()Ljava/util/Date; public final fun component10 ()Z public final fun component11 ()Z @@ -696,6 +696,7 @@ public final class io/getstream/chat/android/models/Config { public final fun component24 ()Z public final fun component25 ()Z public final fun component26 ()Z + public final fun component27 ()Ljava/lang/String; public final fun component3 ()Ljava/lang/String; public final fun component4 ()Z public final fun component5 ()Z @@ -703,8 +704,8 @@ public final class io/getstream/chat/android/models/Config { public final fun component7 ()Z public final fun component8 ()Z public final fun component9 ()Z - public final fun copy (Ljava/util/Date;Ljava/util/Date;Ljava/lang/String;ZZZZZZZZZZZZZZLjava/lang/String;ILjava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/List;ZZZ)Lio/getstream/chat/android/models/Config; - public static synthetic fun copy$default (Lio/getstream/chat/android/models/Config;Ljava/util/Date;Ljava/util/Date;Ljava/lang/String;ZZZZZZZZZZZZZZLjava/lang/String;ILjava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/List;ZZZILjava/lang/Object;)Lio/getstream/chat/android/models/Config; + public final fun copy (Ljava/util/Date;Ljava/util/Date;Ljava/lang/String;ZZZZZZZZZZZZZZLjava/lang/String;ILjava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/List;ZZZLjava/lang/String;)Lio/getstream/chat/android/models/Config; + public static synthetic fun copy$default (Lio/getstream/chat/android/models/Config;Ljava/util/Date;Ljava/util/Date;Ljava/lang/String;ZZZZZZZZZZZZZZLjava/lang/String;ILjava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/List;ZZZLjava/lang/String;ILjava/lang/Object;)Lio/getstream/chat/android/models/Config; public fun equals (Ljava/lang/Object;)Z public final fun getAutomod ()Ljava/lang/String; public final fun getAutomodBehavior ()Ljava/lang/String; @@ -721,6 +722,7 @@ public final class io/getstream/chat/android/models/Config { public final fun getMuteEnabled ()Z public final fun getName ()Ljava/lang/String; public final fun getPollsEnabled ()Z + public final fun getPushLevel ()Ljava/lang/String; public final fun getPushNotificationsEnabled ()Z public final fun getReadEventsEnabled ()Z public final fun getSearchEnabled ()Z @@ -1252,8 +1254,8 @@ public final class io/getstream/chat/android/models/Message : io/getstream/chat/ public static final field TYPE_ERROR Ljava/lang/String; public static final field TYPE_REGULAR Ljava/lang/String; public fun ()V - public fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/List;Ljava/util/List;Ljava/util/List;IILjava/util/Map;Ljava/util/Map;Ljava/util/Map;Lio/getstream/chat/android/models/SyncStatus;Ljava/lang/String;Ljava/util/List;Ljava/util/List;Ljava/util/Date;Ljava/util/Date;Ljava/util/Date;Ljava/util/Date;Ljava/util/Date;Lio/getstream/chat/android/models/User;Ljava/util/Map;ZZLjava/util/Map;ZLio/getstream/chat/android/models/ChannelInfo;Lio/getstream/chat/android/models/Message;Ljava/lang/String;ZLjava/util/Date;Ljava/util/Date;Lio/getstream/chat/android/models/User;Ljava/util/List;ZZLio/getstream/chat/android/models/MessageModerationDetails;Lio/getstream/chat/android/models/Moderation;Ljava/util/Date;Lio/getstream/chat/android/models/Poll;Ljava/util/List;Lio/getstream/chat/android/models/MessageReminderInfo;Lio/getstream/chat/android/models/Location;Ljava/lang/String;Z)V - public synthetic fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/List;Ljava/util/List;Ljava/util/List;IILjava/util/Map;Ljava/util/Map;Ljava/util/Map;Lio/getstream/chat/android/models/SyncStatus;Ljava/lang/String;Ljava/util/List;Ljava/util/List;Ljava/util/Date;Ljava/util/Date;Ljava/util/Date;Ljava/util/Date;Ljava/util/Date;Lio/getstream/chat/android/models/User;Ljava/util/Map;ZZLjava/util/Map;ZLio/getstream/chat/android/models/ChannelInfo;Lio/getstream/chat/android/models/Message;Ljava/lang/String;ZLjava/util/Date;Ljava/util/Date;Lio/getstream/chat/android/models/User;Ljava/util/List;ZZLio/getstream/chat/android/models/MessageModerationDetails;Lio/getstream/chat/android/models/Moderation;Ljava/util/Date;Lio/getstream/chat/android/models/Poll;Ljava/util/List;Lio/getstream/chat/android/models/MessageReminderInfo;Lio/getstream/chat/android/models/Location;Ljava/lang/String;ZIILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/List;Ljava/util/List;Ljava/util/List;IILjava/util/Map;Ljava/util/Map;Ljava/util/Map;Lio/getstream/chat/android/models/SyncStatus;Ljava/lang/String;Ljava/util/List;Ljava/util/List;Ljava/util/Date;Ljava/util/Date;Ljava/util/Date;Ljava/util/Date;Ljava/util/Date;Lio/getstream/chat/android/models/User;Ljava/util/Map;ZZLjava/util/Map;ZLio/getstream/chat/android/models/ChannelInfo;Lio/getstream/chat/android/models/Message;Ljava/lang/String;ZLjava/util/Date;Ljava/util/Date;Lio/getstream/chat/android/models/User;Ljava/util/List;ZZLio/getstream/chat/android/models/MessageModerationDetails;Lio/getstream/chat/android/models/Moderation;Ljava/util/Date;Lio/getstream/chat/android/models/Poll;Ljava/util/List;Lio/getstream/chat/android/models/MessageReminderInfo;Lio/getstream/chat/android/models/Location;Ljava/lang/String;ZZZLjava/util/List;Ljava/util/List;)V + public synthetic fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/List;Ljava/util/List;Ljava/util/List;IILjava/util/Map;Ljava/util/Map;Ljava/util/Map;Lio/getstream/chat/android/models/SyncStatus;Ljava/lang/String;Ljava/util/List;Ljava/util/List;Ljava/util/Date;Ljava/util/Date;Ljava/util/Date;Ljava/util/Date;Ljava/util/Date;Lio/getstream/chat/android/models/User;Ljava/util/Map;ZZLjava/util/Map;ZLio/getstream/chat/android/models/ChannelInfo;Lio/getstream/chat/android/models/Message;Ljava/lang/String;ZLjava/util/Date;Ljava/util/Date;Lio/getstream/chat/android/models/User;Ljava/util/List;ZZLio/getstream/chat/android/models/MessageModerationDetails;Lio/getstream/chat/android/models/Moderation;Ljava/util/Date;Lio/getstream/chat/android/models/Poll;Ljava/util/List;Lio/getstream/chat/android/models/MessageReminderInfo;Lio/getstream/chat/android/models/Location;Ljava/lang/String;ZZZLjava/util/List;Ljava/util/List;IILkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun component1 ()Ljava/lang/String; public final fun component10 ()I public final fun component11 ()I @@ -1297,13 +1299,17 @@ public final class io/getstream/chat/android/models/Message : io/getstream/chat/ public final fun component46 ()Lio/getstream/chat/android/models/Location; public final fun component47 ()Ljava/lang/String; public final fun component48 ()Z + public final fun component49 ()Z public final fun component5 ()Ljava/lang/String; + public final fun component50 ()Z + public final fun component51 ()Ljava/util/List; + public final fun component52 ()Ljava/util/List; public final fun component6 ()Ljava/lang/String; public final fun component7 ()Ljava/util/List; public final fun component8 ()Ljava/util/List; public final fun component9 ()Ljava/util/List; - public final fun copy (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/List;Ljava/util/List;Ljava/util/List;IILjava/util/Map;Ljava/util/Map;Ljava/util/Map;Lio/getstream/chat/android/models/SyncStatus;Ljava/lang/String;Ljava/util/List;Ljava/util/List;Ljava/util/Date;Ljava/util/Date;Ljava/util/Date;Ljava/util/Date;Ljava/util/Date;Lio/getstream/chat/android/models/User;Ljava/util/Map;ZZLjava/util/Map;ZLio/getstream/chat/android/models/ChannelInfo;Lio/getstream/chat/android/models/Message;Ljava/lang/String;ZLjava/util/Date;Ljava/util/Date;Lio/getstream/chat/android/models/User;Ljava/util/List;ZZLio/getstream/chat/android/models/MessageModerationDetails;Lio/getstream/chat/android/models/Moderation;Ljava/util/Date;Lio/getstream/chat/android/models/Poll;Ljava/util/List;Lio/getstream/chat/android/models/MessageReminderInfo;Lio/getstream/chat/android/models/Location;Ljava/lang/String;Z)Lio/getstream/chat/android/models/Message; - public static synthetic fun copy$default (Lio/getstream/chat/android/models/Message;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/List;Ljava/util/List;Ljava/util/List;IILjava/util/Map;Ljava/util/Map;Ljava/util/Map;Lio/getstream/chat/android/models/SyncStatus;Ljava/lang/String;Ljava/util/List;Ljava/util/List;Ljava/util/Date;Ljava/util/Date;Ljava/util/Date;Ljava/util/Date;Ljava/util/Date;Lio/getstream/chat/android/models/User;Ljava/util/Map;ZZLjava/util/Map;ZLio/getstream/chat/android/models/ChannelInfo;Lio/getstream/chat/android/models/Message;Ljava/lang/String;ZLjava/util/Date;Ljava/util/Date;Lio/getstream/chat/android/models/User;Ljava/util/List;ZZLio/getstream/chat/android/models/MessageModerationDetails;Lio/getstream/chat/android/models/Moderation;Ljava/util/Date;Lio/getstream/chat/android/models/Poll;Ljava/util/List;Lio/getstream/chat/android/models/MessageReminderInfo;Lio/getstream/chat/android/models/Location;Ljava/lang/String;ZIILjava/lang/Object;)Lio/getstream/chat/android/models/Message; + public final fun copy (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/List;Ljava/util/List;Ljava/util/List;IILjava/util/Map;Ljava/util/Map;Ljava/util/Map;Lio/getstream/chat/android/models/SyncStatus;Ljava/lang/String;Ljava/util/List;Ljava/util/List;Ljava/util/Date;Ljava/util/Date;Ljava/util/Date;Ljava/util/Date;Ljava/util/Date;Lio/getstream/chat/android/models/User;Ljava/util/Map;ZZLjava/util/Map;ZLio/getstream/chat/android/models/ChannelInfo;Lio/getstream/chat/android/models/Message;Ljava/lang/String;ZLjava/util/Date;Ljava/util/Date;Lio/getstream/chat/android/models/User;Ljava/util/List;ZZLio/getstream/chat/android/models/MessageModerationDetails;Lio/getstream/chat/android/models/Moderation;Ljava/util/Date;Lio/getstream/chat/android/models/Poll;Ljava/util/List;Lio/getstream/chat/android/models/MessageReminderInfo;Lio/getstream/chat/android/models/Location;Ljava/lang/String;ZZZLjava/util/List;Ljava/util/List;)Lio/getstream/chat/android/models/Message; + public static synthetic fun copy$default (Lio/getstream/chat/android/models/Message;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/List;Ljava/util/List;Ljava/util/List;IILjava/util/Map;Ljava/util/Map;Ljava/util/Map;Lio/getstream/chat/android/models/SyncStatus;Ljava/lang/String;Ljava/util/List;Ljava/util/List;Ljava/util/Date;Ljava/util/Date;Ljava/util/Date;Ljava/util/Date;Ljava/util/Date;Lio/getstream/chat/android/models/User;Ljava/util/Map;ZZLjava/util/Map;ZLio/getstream/chat/android/models/ChannelInfo;Lio/getstream/chat/android/models/Message;Ljava/lang/String;ZLjava/util/Date;Ljava/util/Date;Lio/getstream/chat/android/models/User;Ljava/util/List;ZZLio/getstream/chat/android/models/MessageModerationDetails;Lio/getstream/chat/android/models/Moderation;Ljava/util/Date;Lio/getstream/chat/android/models/Poll;Ljava/util/List;Lio/getstream/chat/android/models/MessageReminderInfo;Lio/getstream/chat/android/models/Location;Ljava/lang/String;ZZZLjava/util/List;Ljava/util/List;IILjava/lang/Object;)Lio/getstream/chat/android/models/Message; public fun equals (Ljava/lang/Object;)Z public final fun getAttachments ()Ljava/util/List; public final fun getChannelInfo ()Lio/getstream/chat/android/models/ChannelInfo; @@ -1322,6 +1328,10 @@ public final class io/getstream/chat/android/models/Message : io/getstream/chat/ public final fun getI18n ()Ljava/util/Map; public final fun getId ()Ljava/lang/String; public final fun getLatestReactions ()Ljava/util/List; + public final fun getMentionedChannel ()Z + public final fun getMentionedGroups ()Ljava/util/List; + public final fun getMentionedHere ()Z + public final fun getMentionedRoles ()Ljava/util/List; public final fun getMentionedUsers ()Ljava/util/List; public final fun getMentionedUsersIds ()Ljava/util/List; public final fun getMessageTextUpdatedAt ()Ljava/util/Date; @@ -1383,6 +1393,10 @@ public final class io/getstream/chat/android/models/Message$Builder { public final fun withI18n (Ljava/util/Map;)Lio/getstream/chat/android/models/Message$Builder; public final fun withId (Ljava/lang/String;)Lio/getstream/chat/android/models/Message$Builder; public final fun withLatestReactions (Ljava/util/List;)Lio/getstream/chat/android/models/Message$Builder; + public final fun withMentionedChannel (Z)Lio/getstream/chat/android/models/Message$Builder; + public final fun withMentionedGroups (Ljava/util/List;)Lio/getstream/chat/android/models/Message$Builder; + public final fun withMentionedHere (Z)Lio/getstream/chat/android/models/Message$Builder; + public final fun withMentionedRoles (Ljava/util/List;)Lio/getstream/chat/android/models/Message$Builder; public final fun withMentionedUsers (Ljava/util/List;)Lio/getstream/chat/android/models/Message$Builder; public final fun withMentionedUsersIds (Ljava/util/List;)Lio/getstream/chat/android/models/Message$Builder; public final fun withMessageTextUpdatedAt (Ljava/util/Date;)Lio/getstream/chat/android/models/Message$Builder; @@ -2491,6 +2505,50 @@ public final class io/getstream/chat/android/models/UserEntity$DefaultImpls { public static fun getUserId (Lio/getstream/chat/android/models/UserEntity;)Ljava/lang/String; } +public final class io/getstream/chat/android/models/UserGroup { + public fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/List;Ljava/lang/String;Ljava/util/Date;Ljava/util/Date;)V + public synthetic fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/List;Ljava/lang/String;Ljava/util/Date;Ljava/util/Date;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Ljava/lang/String; + public final fun component3 ()Ljava/lang/String; + public final fun component4 ()Ljava/lang/String; + public final fun component5 ()Ljava/util/List; + public final fun component6 ()Ljava/lang/String; + public final fun component7 ()Ljava/util/Date; + public final fun component8 ()Ljava/util/Date; + public final fun copy (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/List;Ljava/lang/String;Ljava/util/Date;Ljava/util/Date;)Lio/getstream/chat/android/models/UserGroup; + public static synthetic fun copy$default (Lio/getstream/chat/android/models/UserGroup;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/List;Ljava/lang/String;Ljava/util/Date;Ljava/util/Date;ILjava/lang/Object;)Lio/getstream/chat/android/models/UserGroup; + public fun equals (Ljava/lang/Object;)Z + public final fun getCreatedAt ()Ljava/util/Date; + public final fun getCreatedBy ()Ljava/lang/String; + public final fun getDescription ()Ljava/lang/String; + public final fun getId ()Ljava/lang/String; + public final fun getMembers ()Ljava/util/List; + public final fun getName ()Ljava/lang/String; + public final fun getTeam ()Ljava/lang/String; + public final fun getUpdatedAt ()Ljava/util/Date; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class io/getstream/chat/android/models/UserGroupMember { + public fun (Ljava/lang/String;Ljava/lang/String;ZLjava/util/Date;)V + public synthetic fun (Ljava/lang/String;Ljava/lang/String;ZLjava/util/Date;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Ljava/lang/String; + public final fun component3 ()Z + public final fun component4 ()Ljava/util/Date; + public final fun copy (Ljava/lang/String;Ljava/lang/String;ZLjava/util/Date;)Lio/getstream/chat/android/models/UserGroupMember; + public static synthetic fun copy$default (Lio/getstream/chat/android/models/UserGroupMember;Ljava/lang/String;Ljava/lang/String;ZLjava/util/Date;ILjava/lang/Object;)Lio/getstream/chat/android/models/UserGroupMember; + public fun equals (Ljava/lang/Object;)Z + public final fun getCreatedAt ()Ljava/util/Date; + public final fun getGroupId ()Ljava/lang/String; + public final fun getUserId ()Ljava/lang/String; + public fun hashCode ()I + public final fun isAdmin ()Z + public fun toString ()Ljava/lang/String; +} + public abstract interface class io/getstream/chat/android/models/UserTransformer { public abstract fun transform (Lio/getstream/chat/android/models/User;)Lio/getstream/chat/android/models/User; } diff --git a/stream-chat-android-core/src/main/java/io/getstream/chat/android/models/Config.kt b/stream-chat-android-core/src/main/java/io/getstream/chat/android/models/Config.kt index c24b544a5aa..237e915c281 100644 --- a/stream-chat-android-core/src/main/java/io/getstream/chat/android/models/Config.kt +++ b/stream-chat-android-core/src/main/java/io/getstream/chat/android/models/Config.kt @@ -154,4 +154,10 @@ public data class Config( * If true, the messages sent in the channel will be marked as pending. */ val markMessagesPending: Boolean = false, + + /** + * The default push notification level for the channel type. One of `"all"`, `"all_mentions"`, + * `"direct_mentions"`, `"none"`, or deprecated `"mentions"`. + */ + val pushLevel: String? = null, ) diff --git a/stream-chat-android-core/src/main/java/io/getstream/chat/android/models/Message.kt b/stream-chat-android-core/src/main/java/io/getstream/chat/android/models/Message.kt index 30236eb3b3d..fbb96a94d02 100644 --- a/stream-chat-android-core/src/main/java/io/getstream/chat/android/models/Message.kt +++ b/stream-chat-android-core/src/main/java/io/getstream/chat/android/models/Message.kt @@ -274,6 +274,26 @@ public data class Message( */ val deletedForMe: Boolean = false, + /** + * Whether the message includes an `@here` mention notifying online channel members. + */ + val mentionedHere: Boolean = false, + + /** + * Whether the message includes an `@channel` mention notifying all channel members. + */ + val mentionedChannel: Boolean = false, + + /** + * The user groups mentioned in the message. + */ + val mentionedGroups: List = emptyList(), + + /** + * The roles mentioned in the message (e.g. `["admin", "moderator"]`). + */ + val mentionedRoles: List = emptyList(), + ) : CustomObject, ComparableFieldProvider { public companion object { /** @@ -394,6 +414,10 @@ public data class Message( if (poll != null) append(", poll=").append(poll) if (channelRole != null) append(", channelRole=").append(channelRole) append(", deletedForMe=").append(deletedForMe) + if (mentionedHere) append(", mentionedHere=true") + if (mentionedChannel) append(", mentionedChannel=true") + if (mentionedGroups.isNotEmpty()) append(", mentionedGroups=").append(mentionedGroups) + if (mentionedRoles.isNotEmpty()) append(", mentionedRoles=").append(mentionedRoles) if (extraData.isNotEmpty()) append(", extraData=").append(extraData) append(")") }.toString() @@ -452,6 +476,10 @@ public data class Message( private var sharedLocation: Location? = null private var channelRole: String? = null private var deletedForMe: Boolean = false + private var mentionedHere: Boolean = false + private var mentionedChannel: Boolean = false + private var mentionedGroups: List = emptyList() + private var mentionedRoles: List = emptyList() public constructor(message: Message) : this() { id = message.id @@ -502,6 +530,10 @@ public data class Message( sharedLocation = message.sharedLocation channelRole = message.channelRole deletedForMe = message.deletedForMe + mentionedHere = message.mentionedHere + mentionedChannel = message.mentionedChannel + mentionedGroups = message.mentionedGroups + mentionedRoles = message.mentionedRoles } public fun withId(id: String): Builder = apply { this.id = id } @@ -584,6 +616,16 @@ public data class Message( } public fun withChannelRole(channelRole: String?): Builder = apply { this.channelRole = channelRole } public fun withDeletedForMe(deletedForMe: Boolean): Builder = apply { this.deletedForMe = deletedForMe } + public fun withMentionedHere(mentionedHere: Boolean): Builder = apply { this.mentionedHere = mentionedHere } + public fun withMentionedChannel(mentionedChannel: Boolean): Builder = apply { + this.mentionedChannel = mentionedChannel + } + public fun withMentionedGroups(mentionedGroups: List): Builder = apply { + this.mentionedGroups = mentionedGroups + } + public fun withMentionedRoles(mentionedRoles: List): Builder = apply { + this.mentionedRoles = mentionedRoles + } public fun build(): Message { return Message( @@ -635,6 +677,10 @@ public data class Message( sharedLocation = sharedLocation, channelRole = channelRole, deletedForMe = deletedForMe, + mentionedHere = mentionedHere, + mentionedChannel = mentionedChannel, + mentionedGroups = mentionedGroups, + mentionedRoles = mentionedRoles, ) } } diff --git a/stream-chat-android-core/src/main/java/io/getstream/chat/android/models/UserGroup.kt b/stream-chat-android-core/src/main/java/io/getstream/chat/android/models/UserGroup.kt new file mode 100644 index 00000000000..c1104e07f48 --- /dev/null +++ b/stream-chat-android-core/src/main/java/io/getstream/chat/android/models/UserGroup.kt @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2014-2026 Stream.io Inc. All rights reserved. + * + * Licensed under the Stream License; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://github.com/GetStream/stream-chat-android/blob/main/LICENSE + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.getstream.chat.android.models + +import androidx.compose.runtime.Immutable +import java.util.Date + +/** + * Named collection of users that can be mentioned together with `@`. + * + * @param id Unique identifier of the group within an app (or team, when multi-tenancy is enabled). + * @param name Display name; the literal that follows `@` in message text. + * @param description Optional description. + * @param team Team this group is scoped to, when multi-tenancy is enabled. Empty when not. + * @param members Group members. May be empty when the group object only carries metadata. + * @param createdBy User ID of the creator, when known. + * @param createdAt When the group was created. + * @param updatedAt Last time the group's metadata changed. + */ +@Immutable +public data class UserGroup( + val id: String, + val name: String, + val description: String? = null, + val team: String = "", + val members: List = emptyList(), + val createdBy: String? = null, + val createdAt: Date? = null, + val updatedAt: Date? = null, +) + +/** + * Membership of a [UserGroup]. + * + * @param groupId The group this membership belongs to. + * @param userId The user that is a member of the group. + * @param isAdmin Whether the user is an admin of the group. + * @param createdAt When the user was added to the group. + */ +@Immutable +public data class UserGroupMember( + val groupId: String, + val userId: String, + val isAdmin: Boolean = false, + val createdAt: Date? = null, +) diff --git a/stream-chat-android-core/src/testFixtures/kotlin/io/getstream/chat/android/Mother.kt b/stream-chat-android-core/src/testFixtures/kotlin/io/getstream/chat/android/Mother.kt index a2fb459d835..92b83625422 100644 --- a/stream-chat-android-core/src/testFixtures/kotlin/io/getstream/chat/android/Mother.kt +++ b/stream-chat-android-core/src/testFixtures/kotlin/io/getstream/chat/android/Mother.kt @@ -68,6 +68,8 @@ import io.getstream.chat.android.models.UnreadThread import io.getstream.chat.android.models.UploadedFile import io.getstream.chat.android.models.User import io.getstream.chat.android.models.UserBlock +import io.getstream.chat.android.models.UserGroup +import io.getstream.chat.android.models.UserGroupMember import io.getstream.chat.android.models.Vote import io.getstream.chat.android.models.VotingVisibility import io.getstream.result.Error @@ -304,6 +306,38 @@ public fun randomQueryDraftsResult( next = next, ) +public fun randomUserGroup( + id: String = randomString(), + name: String = randomString(), + description: String? = randomString(), + team: String = randomString(), + members: List = emptyList(), + createdBy: String? = randomString(), + createdAt: Date? = randomDate(), + updatedAt: Date? = randomDate(), +): UserGroup = UserGroup( + id = id, + name = name, + description = description, + team = team, + members = members, + createdBy = createdBy, + createdAt = createdAt, + updatedAt = updatedAt, +) + +public fun randomUserGroupMember( + groupId: String = randomString(), + userId: String = randomString(), + isAdmin: Boolean = randomBoolean(), + createdAt: Date? = randomDate(), +): UserGroupMember = UserGroupMember( + groupId = groupId, + userId = userId, + isAdmin = isAdmin, + createdAt = createdAt, +) + public fun randomMessage( id: String = randomString(), cid: String = randomCID(), @@ -354,6 +388,10 @@ public fun randomMessage( reminder: MessageReminderInfo? = randomMessageReminderInfo(), channelRole: String? = null, deletedForMe: Boolean = randomBoolean(), + mentionedHere: Boolean = randomBoolean(), + mentionedChannel: Boolean = randomBoolean(), + mentionedRoles: List = emptyList(), + mentionedGroups: List = emptyList(), ): Message = Message( id = id, cid = cid, @@ -401,6 +439,10 @@ public fun randomMessage( reminder = reminder, channelRole = channelRole, deletedForMe = deletedForMe, + mentionedHere = mentionedHere, + mentionedChannel = mentionedChannel, + mentionedRoles = mentionedRoles, + mentionedGroups = mentionedGroups, ) public fun randomPendingMessage( From b8eae3829fefc8947362226b0db9bb1b8b7a3e30 Mon Sep 17 00:00:00 2001 From: Gian <47775302+gpunto@users.noreply.github.com> Date: Mon, 8 Jun 2026 16:34:17 +0200 Subject: [PATCH 2/2] Improve tests --- .../getstream/chat/android/client/Mother.kt | 2 + .../client/api2/mapping/DomainMappingTest.kt | 1 + .../converter/UserGroupConverterTest.kt | 52 +++++++++++++++++++ .../io/getstream/chat/android/Mother.kt | 2 + 4 files changed, 57 insertions(+) create mode 100644 stream-chat-android-client/src/test/java/io/getstream/chat/android/client/internal/offline/repository/database/converter/UserGroupConverterTest.kt diff --git a/stream-chat-android-client/src/test/java/io/getstream/chat/android/client/Mother.kt b/stream-chat-android-client/src/test/java/io/getstream/chat/android/client/Mother.kt index 5c775294646..bd366bed244 100644 --- a/stream-chat-android-client/src/test/java/io/getstream/chat/android/client/Mother.kt +++ b/stream-chat-android-client/src/test/java/io/getstream/chat/android/client/Mother.kt @@ -525,6 +525,7 @@ internal object Mother { user_message_reminders: Boolean? = randomBoolean(), shared_locations: Boolean = randomBoolean(), mark_messages_pending: Boolean = randomBoolean(), + push_level: String? = randomString(), ): ConfigDto = ConfigDto( created_at = created_at, updated_at = updated_at, @@ -552,6 +553,7 @@ internal object Mother { user_message_reminders = user_message_reminders, shared_locations = shared_locations, mark_messages_pending = mark_messages_pending, + push_level = push_level, ) /** diff --git a/stream-chat-android-client/src/test/java/io/getstream/chat/android/client/api2/mapping/DomainMappingTest.kt b/stream-chat-android-client/src/test/java/io/getstream/chat/android/client/api2/mapping/DomainMappingTest.kt index 15c58c24e3c..1f4797bd69c 100644 --- a/stream-chat-android-client/src/test/java/io/getstream/chat/android/client/api2/mapping/DomainMappingTest.kt +++ b/stream-chat-android-client/src/test/java/io/getstream/chat/android/client/api2/mapping/DomainMappingTest.kt @@ -698,6 +698,7 @@ internal class DomainMappingTest { messageRemindersEnabled = configDto.user_message_reminders ?: false, sharedLocationsEnabled = configDto.shared_locations ?: false, markMessagesPending = configDto.mark_messages_pending, + pushLevel = configDto.push_level, ) assertEquals(expected, config) } diff --git a/stream-chat-android-client/src/test/java/io/getstream/chat/android/client/internal/offline/repository/database/converter/UserGroupConverterTest.kt b/stream-chat-android-client/src/test/java/io/getstream/chat/android/client/internal/offline/repository/database/converter/UserGroupConverterTest.kt new file mode 100644 index 00000000000..af2de550fcd --- /dev/null +++ b/stream-chat-android-client/src/test/java/io/getstream/chat/android/client/internal/offline/repository/database/converter/UserGroupConverterTest.kt @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2014-2026 Stream.io Inc. All rights reserved. + * + * Licensed under the Stream License; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://github.com/GetStream/stream-chat-android/blob/main/LICENSE + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.getstream.chat.android.client.internal.offline.repository.database.converter + +import io.getstream.chat.android.client.internal.offline.repository.database.converter.internal.UserGroupConverter +import io.getstream.chat.android.randomUserGroup +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertNull +import org.junit.jupiter.api.Test +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.NullSource +import org.junit.jupiter.params.provider.ValueSource + +internal class UserGroupConverterTest { + private val sut = UserGroupConverter() + + @Test + fun `userGroupListToString with null returns null`() { + assertNull(sut.userGroupListToString(null)) + } + + @ParameterizedTest + @NullSource + @ValueSource(strings = ["", "null"]) + fun `stringToUserGroupList with blank input returns empty list`(input: String?) { + assertEquals(emptyList(), sut.stringToUserGroupList(input)) + } + + @Test + fun `round-trip preserves user groups including dates`() { + val groups = listOf(randomUserGroup(), randomUserGroup()) + + val json = sut.userGroupListToString(groups) + val converted = sut.stringToUserGroupList(json) + + assertEquals(groups, converted) + } +} diff --git a/stream-chat-android-core/src/testFixtures/kotlin/io/getstream/chat/android/Mother.kt b/stream-chat-android-core/src/testFixtures/kotlin/io/getstream/chat/android/Mother.kt index 92b83625422..25ef8e85504 100644 --- a/stream-chat-android-core/src/testFixtures/kotlin/io/getstream/chat/android/Mother.kt +++ b/stream-chat-android-core/src/testFixtures/kotlin/io/getstream/chat/android/Mother.kt @@ -605,6 +605,7 @@ public fun randomConfig( blocklistBehavior: String = randomString(), commands: List = emptyList(), pollsEnabled: Boolean = randomBoolean(), + pushLevel: String? = randomString(), ): Config = Config( createdAt = createdAt, updatedAt = updatedAt, @@ -628,6 +629,7 @@ public fun randomConfig( blocklistBehavior = blocklistBehavior, commands = commands, pollsEnabled = pollsEnabled, + pushLevel = pushLevel, ) public fun randomChannelConfig(type: String = randomString(), config: Config = randomConfig()): ChannelConfig =