From dda9569cf6eac10580e54e317dffabeb0db24c1c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jc=20Mi=C3=B1arro?= Date: Thu, 13 Jun 2024 13:08:42 +0200 Subject: [PATCH 01/14] Include new VoteRemovedEvent From 1a49ca846461bec9c818acdcfb0ca832898fed91 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jc=20Mi=C3=B1arro?= Date: Thu, 13 Jun 2024 15:56:28 +0200 Subject: [PATCH 02/14] Add poll vote to events --- .../getstream/chat/android/client/api2/mapping/EventMapping.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/stream-chat-android-client/src/main/java/io/getstream/chat/android/client/api2/mapping/EventMapping.kt b/stream-chat-android-client/src/main/java/io/getstream/chat/android/client/api2/mapping/EventMapping.kt index 4d1f3a27079..7b66fc65963 100644 --- a/stream-chat-android-client/src/main/java/io/getstream/chat/android/client/api2/mapping/EventMapping.kt +++ b/stream-chat-android-client/src/main/java/io/getstream/chat/android/client/api2/mapping/EventMapping.kt @@ -815,6 +815,7 @@ private fun VoteChangedEventDto.toDomain(currentUserId: UserId?): VoteChangedEve newVote = pollVote, ) } + private fun VoteRemovedEventDto.toDomain(currentUserId: UserId?): VoteRemovedEvent { val removedVote = poll_vote.toDomain(currentUserId) val (channelType, channelId) = cid.cidToTypeAndId() From 7296a8aded0b84018b3faa320821fbf23c8877f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jc=20Mi=C3=B1arro?= Date: Thu, 13 Jun 2024 18:40:08 +0200 Subject: [PATCH 03/14] Create new PollEntity classes --- .../domain/message/internal/Poll.kt | 56 +++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 stream-chat-android-offline/src/main/java/io/getstream/chat/android/offline/repository/domain/message/internal/Poll.kt diff --git a/stream-chat-android-offline/src/main/java/io/getstream/chat/android/offline/repository/domain/message/internal/Poll.kt b/stream-chat-android-offline/src/main/java/io/getstream/chat/android/offline/repository/domain/message/internal/Poll.kt new file mode 100644 index 00000000000..ee463c7e5a0 --- /dev/null +++ b/stream-chat-android-offline/src/main/java/io/getstream/chat/android/offline/repository/domain/message/internal/Poll.kt @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2014-2024 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.offline.repository.domain.message.internal + +import com.squareup.moshi.JsonClass +import java.util.Date + +@JsonClass(generateAdapter = true) +@Suppress("LongParameterList") +internal class PollEntity( + val id: String, + val name: String, + val description: String, + val options: List, + val votingVisibility: String, + val enforceUniqueVote: Boolean, + val maxVotesAllowed: Int, + val allowUserSuggestedOptions: Boolean, + val allowAnswers: Boolean, + val voteCountsByOption: Map, + val votes: List, + val ownVotes: List, + val createdAt: Date, + val updatedAt: Date, + val closed: Boolean, +) + +@JsonClass(generateAdapter = true) +internal class OptionEntity( + val id: String, + val text: String, +) + +@JsonClass(generateAdapter = true) +internal class VoteEntity( + val id: String, + val pollId: String, + val optionId: String, + val createdAt: Date, + val updatedAt: Date, + val userId: String?, +) From 46011f6b8a09da05f9e5a10f7cd0a290bc811280 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jc=20Mi=C3=B1arro?= Date: Thu, 13 Jun 2024 18:40:37 +0200 Subject: [PATCH 04/14] Create a TypeConverter for PollEntity --- .../converter/internal/PollConverter.kt | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 stream-chat-android-offline/src/main/java/io/getstream/chat/android/offline/repository/database/converter/internal/PollConverter.kt diff --git a/stream-chat-android-offline/src/main/java/io/getstream/chat/android/offline/repository/database/converter/internal/PollConverter.kt b/stream-chat-android-offline/src/main/java/io/getstream/chat/android/offline/repository/database/converter/internal/PollConverter.kt new file mode 100644 index 00000000000..4faec411022 --- /dev/null +++ b/stream-chat-android-offline/src/main/java/io/getstream/chat/android/offline/repository/database/converter/internal/PollConverter.kt @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2014-2022 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.offline.repository.database.converter.internal + +import androidx.room.TypeConverter +import com.squareup.moshi.adapter +import io.getstream.chat.android.offline.repository.domain.message.internal.PollEntity + +internal class PollConverter { + + @OptIn(ExperimentalStdlibApi::class) + private val entityAdapter = moshi.adapter() + + @TypeConverter + fun stringToPrivacySettings(data: String?): PollEntity? { + return data?.let { + entityAdapter.fromJson(it) + } + } + + @TypeConverter + fun privacySettingsToString(entity: PollEntity?): String? { + return entity?.let { + entityAdapter.toJson(it) + } + } +} From 7074aa494e784f2c58a562999163250a01ccfbca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jc=20Mi=C3=B1arro?= Date: Thu, 13 Jun 2024 18:41:31 +0200 Subject: [PATCH 05/14] Use PollConveter --- .../offline/repository/database/internal/ChatDatabase.kt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/stream-chat-android-offline/src/main/java/io/getstream/chat/android/offline/repository/database/internal/ChatDatabase.kt b/stream-chat-android-offline/src/main/java/io/getstream/chat/android/offline/repository/database/internal/ChatDatabase.kt index 18a71a8918b..a652b7e3cb5 100644 --- a/stream-chat-android-offline/src/main/java/io/getstream/chat/android/offline/repository/database/internal/ChatDatabase.kt +++ b/stream-chat-android-offline/src/main/java/io/getstream/chat/android/offline/repository/database/internal/ChatDatabase.kt @@ -29,6 +29,7 @@ import io.getstream.chat.android.offline.repository.database.converter.internal. import io.getstream.chat.android.offline.repository.database.converter.internal.MapConverter import io.getstream.chat.android.offline.repository.database.converter.internal.MemberConverter import io.getstream.chat.android.offline.repository.database.converter.internal.ModerationDetailsConverter +import io.getstream.chat.android.offline.repository.database.converter.internal.PollConverter import io.getstream.chat.android.offline.repository.database.converter.internal.PrivacySettingsConverter import io.getstream.chat.android.offline.repository.database.converter.internal.QuerySortConverter import io.getstream.chat.android.offline.repository.database.converter.internal.ReactionGroupConverter @@ -85,6 +86,7 @@ import io.getstream.chat.android.offline.repository.domain.user.internal.UserEnt ModerationDetailsConverter::class, ReactionGroupConverter::class, PrivacySettingsConverter::class, + PollConverter::class, ) internal abstract class ChatDatabase : RoomDatabase() { abstract fun queryChannelsDao(): QueryChannelsDao From ebbed5c48b9d64ccf8197a7febad1fc98db50599 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jc=20Mi=C3=B1arro?= Date: Thu, 13 Jun 2024 18:42:44 +0200 Subject: [PATCH 06/14] Create new mappers for Poll Entities --- .../domain/message/internal/MessageMapper.kt | 83 +++++++++++++++++++ 1 file changed, 83 insertions(+) diff --git a/stream-chat-android-offline/src/main/java/io/getstream/chat/android/offline/repository/domain/message/internal/MessageMapper.kt b/stream-chat-android-offline/src/main/java/io/getstream/chat/android/offline/repository/domain/message/internal/MessageMapper.kt index 665df00d95d..eb8c8e72b48 100644 --- a/stream-chat-android-offline/src/main/java/io/getstream/chat/android/offline/repository/domain/message/internal/MessageMapper.kt +++ b/stream-chat-android-offline/src/main/java/io/getstream/chat/android/offline/repository/domain/message/internal/MessageMapper.kt @@ -17,8 +17,12 @@ package io.getstream.chat.android.offline.repository.domain.message.internal import io.getstream.chat.android.models.Message +import io.getstream.chat.android.models.Option +import io.getstream.chat.android.models.Poll import io.getstream.chat.android.models.Reaction import io.getstream.chat.android.models.User +import io.getstream.chat.android.models.Vote +import io.getstream.chat.android.models.VotingVisibility import io.getstream.chat.android.offline.repository.domain.message.attachment.internal.AttachmentEntity import io.getstream.chat.android.offline.repository.domain.message.attachment.internal.toEntity import io.getstream.chat.android.offline.repository.domain.message.attachment.internal.toModel @@ -198,3 +202,82 @@ internal fun Message.toReplyEntity(): ReplyMessageEntity = ), attachments = attachments.mapIndexed { index, attachment -> attachment.toReplyEntity(id, index) }, ) + +internal fun Poll.toEntity(): PollEntity = PollEntity( + id = id, + name = name, + description = description, + options = options.map { it.toEntity() }, + votes = votes.map { it.toEntity() }, + createdAt = createdAt, + updatedAt = updatedAt, + votingVisibility = votingVisibility.toEntity(), + enforceUniqueVote = enforceUniqueVote, + maxVotesAllowed = maxVotesAllowed, + allowUserSuggestedOptions = allowUserSuggestedOptions, + allowAnswers = allowAnswers, + voteCountsByOption = voteCountsByOption, + ownVotes = ownVotes.map { it.toEntity() }, + closed = closed, +) + +internal fun Option.toEntity(): OptionEntity = OptionEntity( + id = id, + text = text, +) + +internal fun Vote.toEntity(): VoteEntity = VoteEntity( + id = id, + optionId = optionId, + pollId = pollId, + createdAt = createdAt, + updatedAt = updatedAt, + userId = user?.id, +) + +private fun VotingVisibility.toEntity(): String = when (this) { + VotingVisibility.ANONYMOUS -> "anonymous" + VotingVisibility.PUBLIC -> "public" +} + +private suspend fun PollEntity.toModel( + getUser: suspend (userId: String) -> User, +): Poll = Poll( + id = id, + name = name, + description = description, + options = options.map(OptionEntity::toModel), + votes = votes.map { it.toModel(getUser) }, + createdAt = createdAt, + updatedAt = updatedAt, + votingVisibility = votingVisibility.toVotingVisibility(), + enforceUniqueVote = enforceUniqueVote, + maxVotesAllowed = maxVotesAllowed, + allowUserSuggestedOptions = allowUserSuggestedOptions, + allowAnswers = allowAnswers, + voteCountsByOption = voteCountsByOption, + ownVotes = ownVotes.map { it.toModel(getUser) }, + closed = closed, +) + +private fun OptionEntity.toModel(): Option = Option( + id = id, + text = text, +) + +private suspend fun VoteEntity.toModel( + getUser: suspend (userId: String) -> User, +): Vote = Vote( + id = id, + optionId = optionId, + pollId = pollId, + createdAt = createdAt, + updatedAt = updatedAt, + user = userId?.let { getUser(it) }, +) + +private fun String.toVotingVisibility(): VotingVisibility = when (this) { + "public" -> VotingVisibility.PUBLIC + "anonymous" -> VotingVisibility.ANONYMOUS + else -> throw IllegalArgumentException("Unknown voting visibility: $this") +} From 345dc2eaa89d61a6c14d3632de63f7e02eea4dfc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jc=20Mi=C3=B1arro?= Date: Thu, 8 Aug 2024 09:54:38 +0200 Subject: [PATCH 07/14] Create converter for Option --- .../converter/internal/OptionConverter.kt | 59 +++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 stream-chat-android-offline/src/main/java/io/getstream/chat/android/offline/repository/database/converter/internal/OptionConverter.kt diff --git a/stream-chat-android-offline/src/main/java/io/getstream/chat/android/offline/repository/database/converter/internal/OptionConverter.kt b/stream-chat-android-offline/src/main/java/io/getstream/chat/android/offline/repository/database/converter/internal/OptionConverter.kt new file mode 100644 index 00000000000..2ea6602e414 --- /dev/null +++ b/stream-chat-android-offline/src/main/java/io/getstream/chat/android/offline/repository/database/converter/internal/OptionConverter.kt @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2014-2022 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.offline.repository.database.converter.internal + +import androidx.room.TypeConverter +import com.squareup.moshi.adapter +import io.getstream.chat.android.offline.repository.domain.message.internal.OptionEntity + +internal class OptionConverter { + + @OptIn(ExperimentalStdlibApi::class) + private val entityAdapter = moshi.adapter() + + @OptIn(ExperimentalStdlibApi::class) + private val entityListAdapter = moshi.adapter>() + + @TypeConverter + fun stringToOption(data: String?): OptionEntity? { + return data?.let { + entityAdapter.fromJson(it) + } + } + + @TypeConverter + fun optionToString(entity: OptionEntity?): String? { + return entity?.let { + entityAdapter.toJson(it) + } + } + + @TypeConverter + fun stringToOptionList(data: String?): List? { + if (data.isNullOrEmpty() || data == "null") { + return emptyList() + } + return entityListAdapter.fromJson(data) + } + + @TypeConverter + fun optionListToString(entities: List?): String? { + return entities?.let { + entityListAdapter.toJson(it) + } + } +} From 39f1d1606674243a394f9bcd558e53c0c5071a0a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jc=20Mi=C3=B1arro?= Date: Thu, 8 Aug 2024 09:55:00 +0200 Subject: [PATCH 08/14] Create converter for Vote --- .../converter/internal/VoteConverter.kt | 59 +++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 stream-chat-android-offline/src/main/java/io/getstream/chat/android/offline/repository/database/converter/internal/VoteConverter.kt diff --git a/stream-chat-android-offline/src/main/java/io/getstream/chat/android/offline/repository/database/converter/internal/VoteConverter.kt b/stream-chat-android-offline/src/main/java/io/getstream/chat/android/offline/repository/database/converter/internal/VoteConverter.kt new file mode 100644 index 00000000000..7a981cb427e --- /dev/null +++ b/stream-chat-android-offline/src/main/java/io/getstream/chat/android/offline/repository/database/converter/internal/VoteConverter.kt @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2014-2022 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.offline.repository.database.converter.internal + +import androidx.room.TypeConverter +import com.squareup.moshi.adapter +import io.getstream.chat.android.offline.repository.domain.message.internal.VoteEntity + +internal class VoteConverter { + + @OptIn(ExperimentalStdlibApi::class) + private val entityAdapter = moshi.adapter() + + @OptIn(ExperimentalStdlibApi::class) + private val entityListAdapter = moshi.adapter>() + + @TypeConverter + fun stringToVote(data: String?): VoteEntity? { + return data?.let { + entityAdapter.fromJson(it) + } + } + + @TypeConverter + fun voteToString(entity: VoteEntity?): String? { + return entity?.let { + entityAdapter.toJson(it) + } + } + + @TypeConverter + fun stringToVoteList(data: String?): List? { + if (data.isNullOrEmpty() || data == "null") { + return emptyList() + } + return entityListAdapter.fromJson(data) + } + + @TypeConverter + fun voteListToString(entities: List?): String? { + return entities?.let { + entityListAdapter.toJson(it) + } + } +} From 0c0393acf420855371cb65668ccdc941cf25bf0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jc=20Mi=C3=B1arro?= Date: Thu, 8 Aug 2024 10:01:50 +0200 Subject: [PATCH 09/14] Create PollDao --- .../api/stream-chat-android-offline.api | 8 ++++ .../converter/internal/PollConverter.kt | 41 ------------------- .../database/internal/ChatDatabase.kt | 12 ++++-- .../domain/message/internal/Poll.kt | 7 +++- .../domain/message/internal/PollDao.kt | 31 ++++++++++++++ 5 files changed, 54 insertions(+), 45 deletions(-) delete mode 100644 stream-chat-android-offline/src/main/java/io/getstream/chat/android/offline/repository/database/converter/internal/PollConverter.kt create mode 100644 stream-chat-android-offline/src/main/java/io/getstream/chat/android/offline/repository/domain/message/internal/PollDao.kt diff --git a/stream-chat-android-offline/api/stream-chat-android-offline.api b/stream-chat-android-offline/api/stream-chat-android-offline.api index 053f0510b0d..30cfb82959c 100644 --- a/stream-chat-android-offline/api/stream-chat-android-offline.api +++ b/stream-chat-android-offline/api/stream-chat-android-offline.api @@ -21,6 +21,7 @@ public final class io/getstream/chat/android/offline/repository/database/interna public fun getAutoMigrations (Ljava/util/Map;)Ljava/util/List; public fun getRequiredAutoMigrationSpecs ()Ljava/util/Set; public fun messageDao ()Lio/getstream/chat/android/offline/repository/domain/message/internal/MessageDao; + public fun pollDao ()Lio/getstream/chat/android/offline/repository/domain/message/internal/PollDao; public fun queryChannelsDao ()Lio/getstream/chat/android/offline/repository/domain/queryChannels/internal/QueryChannelsDao; public fun reactionDao ()Lio/getstream/chat/android/offline/repository/domain/reaction/internal/ReactionDao; public fun replyMessageDao ()Lio/getstream/chat/android/offline/repository/domain/message/internal/ReplyMessageDao; @@ -98,6 +99,13 @@ public final class io/getstream/chat/android/offline/repository/domain/message/i public fun upsertMessageInnerEntity (Lio/getstream/chat/android/offline/repository/domain/message/internal/MessageInnerEntity;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; } +public final class io/getstream/chat/android/offline/repository/domain/message/internal/PollDao_Impl : io/getstream/chat/android/offline/repository/domain/message/internal/PollDao { + public fun (Landroidx/room/RoomDatabase;)V + public fun getPoll (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static fun getRequiredConverters ()Ljava/util/List; + public fun insertPolls (Ljava/util/List;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +} + public final class io/getstream/chat/android/offline/repository/domain/message/internal/ReplyMessageDao_Impl : io/getstream/chat/android/offline/repository/domain/message/internal/ReplyMessageDao { public fun (Landroidx/room/RoomDatabase;)V public fun delete (Lio/getstream/chat/android/offline/repository/domain/message/internal/ReplyMessageInnerEntity;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; diff --git a/stream-chat-android-offline/src/main/java/io/getstream/chat/android/offline/repository/database/converter/internal/PollConverter.kt b/stream-chat-android-offline/src/main/java/io/getstream/chat/android/offline/repository/database/converter/internal/PollConverter.kt deleted file mode 100644 index 4faec411022..00000000000 --- a/stream-chat-android-offline/src/main/java/io/getstream/chat/android/offline/repository/database/converter/internal/PollConverter.kt +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright (c) 2014-2022 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.offline.repository.database.converter.internal - -import androidx.room.TypeConverter -import com.squareup.moshi.adapter -import io.getstream.chat.android.offline.repository.domain.message.internal.PollEntity - -internal class PollConverter { - - @OptIn(ExperimentalStdlibApi::class) - private val entityAdapter = moshi.adapter() - - @TypeConverter - fun stringToPrivacySettings(data: String?): PollEntity? { - return data?.let { - entityAdapter.fromJson(it) - } - } - - @TypeConverter - fun privacySettingsToString(entity: PollEntity?): String? { - return entity?.let { - entityAdapter.toJson(it) - } - } -} diff --git a/stream-chat-android-offline/src/main/java/io/getstream/chat/android/offline/repository/database/internal/ChatDatabase.kt b/stream-chat-android-offline/src/main/java/io/getstream/chat/android/offline/repository/database/internal/ChatDatabase.kt index a652b7e3cb5..906af415e06 100644 --- a/stream-chat-android-offline/src/main/java/io/getstream/chat/android/offline/repository/database/internal/ChatDatabase.kt +++ b/stream-chat-android-offline/src/main/java/io/getstream/chat/android/offline/repository/database/internal/ChatDatabase.kt @@ -29,12 +29,13 @@ import io.getstream.chat.android.offline.repository.database.converter.internal. import io.getstream.chat.android.offline.repository.database.converter.internal.MapConverter import io.getstream.chat.android.offline.repository.database.converter.internal.MemberConverter import io.getstream.chat.android.offline.repository.database.converter.internal.ModerationDetailsConverter -import io.getstream.chat.android.offline.repository.database.converter.internal.PollConverter +import io.getstream.chat.android.offline.repository.database.converter.internal.OptionConverter import io.getstream.chat.android.offline.repository.database.converter.internal.PrivacySettingsConverter import io.getstream.chat.android.offline.repository.database.converter.internal.QuerySortConverter import io.getstream.chat.android.offline.repository.database.converter.internal.ReactionGroupConverter import io.getstream.chat.android.offline.repository.database.converter.internal.SetConverter import io.getstream.chat.android.offline.repository.database.converter.internal.SyncStatusConverter +import io.getstream.chat.android.offline.repository.database.converter.internal.VoteConverter import io.getstream.chat.android.offline.repository.domain.channel.internal.ChannelDao import io.getstream.chat.android.offline.repository.domain.channel.internal.ChannelEntity import io.getstream.chat.android.offline.repository.domain.channelconfig.internal.ChannelConfigDao @@ -45,6 +46,8 @@ import io.getstream.chat.android.offline.repository.domain.message.attachment.in import io.getstream.chat.android.offline.repository.domain.message.attachment.internal.ReplyAttachmentEntity import io.getstream.chat.android.offline.repository.domain.message.internal.MessageDao import io.getstream.chat.android.offline.repository.domain.message.internal.MessageInnerEntity +import io.getstream.chat.android.offline.repository.domain.message.internal.PollDao +import io.getstream.chat.android.offline.repository.domain.message.internal.PollEntity import io.getstream.chat.android.offline.repository.domain.message.internal.ReplyMessageDao import io.getstream.chat.android.offline.repository.domain.message.internal.ReplyMessageInnerEntity import io.getstream.chat.android.offline.repository.domain.queryChannels.internal.QueryChannelsDao @@ -69,8 +72,9 @@ import io.getstream.chat.android.offline.repository.domain.user.internal.UserEnt ChannelConfigInnerEntity::class, CommandInnerEntity::class, SyncStateEntity::class, + PollEntity::class, ], - version = 76, + version = 77, exportSchema = false, ) @TypeConverters( @@ -86,7 +90,8 @@ import io.getstream.chat.android.offline.repository.domain.user.internal.UserEnt ModerationDetailsConverter::class, ReactionGroupConverter::class, PrivacySettingsConverter::class, - PollConverter::class, + OptionConverter::class, + VoteConverter::class, ) internal abstract class ChatDatabase : RoomDatabase() { abstract fun queryChannelsDao(): QueryChannelsDao @@ -98,6 +103,7 @@ internal abstract class ChatDatabase : RoomDatabase() { abstract fun channelConfigDao(): ChannelConfigDao abstract fun syncStateDao(): SyncStateDao abstract fun attachmentDao(): AttachmentDao + abstract fun pollDao(): PollDao companion object { @Volatile diff --git a/stream-chat-android-offline/src/main/java/io/getstream/chat/android/offline/repository/domain/message/internal/Poll.kt b/stream-chat-android-offline/src/main/java/io/getstream/chat/android/offline/repository/domain/message/internal/Poll.kt index ee463c7e5a0..bcde794495d 100644 --- a/stream-chat-android-offline/src/main/java/io/getstream/chat/android/offline/repository/domain/message/internal/Poll.kt +++ b/stream-chat-android-offline/src/main/java/io/getstream/chat/android/offline/repository/domain/message/internal/Poll.kt @@ -16,12 +16,15 @@ package io.getstream.chat.android.offline.repository.domain.message.internal +import androidx.room.Entity +import androidx.room.PrimaryKey import com.squareup.moshi.JsonClass import java.util.Date -@JsonClass(generateAdapter = true) @Suppress("LongParameterList") +@Entity(tableName = POLL_ENTITY_TABLE_NAME) internal class PollEntity( + @PrimaryKey val id: String, val name: String, val description: String, @@ -54,3 +57,5 @@ internal class VoteEntity( val updatedAt: Date, val userId: String?, ) + +internal const val POLL_ENTITY_TABLE_NAME = "stream_chat_poll" diff --git a/stream-chat-android-offline/src/main/java/io/getstream/chat/android/offline/repository/domain/message/internal/PollDao.kt b/stream-chat-android-offline/src/main/java/io/getstream/chat/android/offline/repository/domain/message/internal/PollDao.kt new file mode 100644 index 00000000000..626854e2e76 --- /dev/null +++ b/stream-chat-android-offline/src/main/java/io/getstream/chat/android/offline/repository/domain/message/internal/PollDao.kt @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2014-2024 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.offline.repository.domain.message.internal + +import androidx.room.Dao +import androidx.room.Insert +import androidx.room.OnConflictStrategy +import androidx.room.Query + +@Dao +internal interface PollDao { + @Insert(onConflict = OnConflictStrategy.REPLACE) + suspend fun insertPolls(polls: List) + + @Query("SELECT * From $POLL_ENTITY_TABLE_NAME WHERE id = :pollId") + suspend fun getPoll(pollId: String): PollEntity? +} From cf7442b2bc28d05ef6f8a87b165fdff0bf2a3424 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jc=20Mi=C3=B1arro?= Date: Thu, 8 Aug 2024 10:04:43 +0200 Subject: [PATCH 10/14] Add PollEntity to MessageEntity and implement mapper logic --- .../internal/DatabaseMessageRepository.kt | 17 ++++++++++++++--- .../domain/message/internal/MessageEntity.kt | 2 ++ .../domain/message/internal/MessageMapper.kt | 8 +++++++- .../message/internal/ReplyMessageEntity.kt | 2 ++ .../internal/DatabaseRepositoryFactory.kt | 1 + .../io/getstream/chat/android/offline/Mother.kt | 2 ++ .../repository/MessageRepositoryTests.kt | 12 +++++++++++- 7 files changed, 39 insertions(+), 5 deletions(-) diff --git a/stream-chat-android-offline/src/main/java/io/getstream/chat/android/offline/repository/domain/message/internal/DatabaseMessageRepository.kt b/stream-chat-android-offline/src/main/java/io/getstream/chat/android/offline/repository/domain/message/internal/DatabaseMessageRepository.kt index 07fdfce0eb9..d5a3378f808 100644 --- a/stream-chat-android-offline/src/main/java/io/getstream/chat/android/offline/repository/domain/message/internal/DatabaseMessageRepository.kt +++ b/stream-chat-android-offline/src/main/java/io/getstream/chat/android/offline/repository/domain/message/internal/DatabaseMessageRepository.kt @@ -21,6 +21,7 @@ import io.getstream.chat.android.client.api.models.Pagination import io.getstream.chat.android.client.persistance.repository.MessageRepository import io.getstream.chat.android.client.query.pagination.AnyChannelPaginationRequest import io.getstream.chat.android.models.Message +import io.getstream.chat.android.models.Poll import io.getstream.chat.android.models.SyncStatus import io.getstream.chat.android.models.User import io.getstream.chat.android.offline.extensions.launchWithMutex @@ -33,6 +34,7 @@ internal class DatabaseMessageRepository( private val scope: CoroutineScope, private val messageDao: MessageDao, private val replyMessageDao: ReplyMessageDao, + private val pollDao: PollDao, private val getUser: suspend (userId: String) -> User, private val currentUser: User, cacheSize: Int = 1000, @@ -68,7 +70,7 @@ internal class DatabaseMessageRepository( .map { it.toMessage() } private suspend fun selectRepliedMessage(messageId: String): Message? = - replyMessageCache[messageId] ?: replyMessageDao.selectById(messageId)?.toModel(getUser) + replyMessageCache[messageId] ?: replyMessageDao.selectById(messageId)?.toModel(getUser, ::getPoll) /** * Selects messages by IDs. @@ -119,6 +121,11 @@ internal class DatabaseMessageRepository( .filter { replyMessageCache.get(it.id) != it } .map(Message::toReplyEntity) + (replyMessages + messages) + .mapNotNull { it.poll?.toEntity() } + .takeUnless { it.isEmpty() } + ?.let { pollDao.insertPolls(it) } + replyMessages.forEach { replyMessageCache.put(it.id, it) } validMessages.forEach { messageCache.put(it.id, it) } scope.launchWithMutex(dbMutex) { @@ -182,7 +189,7 @@ internal class DatabaseMessageRepository( * @param syncStatus [SyncStatus] */ override suspend fun selectMessageBySyncState(syncStatus: SyncStatus): List { - return messageDao.selectBySyncStatus(syncStatus).map { it.toModel(getUser, ::selectRepliedMessage) } + return messageDao.selectBySyncStatus(syncStatus).map { it.toModel(getUser, ::selectRepliedMessage, ::getPoll) } } override suspend fun evictMessage(messageId: String) { @@ -252,7 +259,11 @@ internal class DatabaseMessageRepository( } private suspend fun MessageEntity.toMessage(): Message = - this.toModel(getUser, ::selectRepliedMessage).filterReactions() + this.toModel(getUser, ::selectRepliedMessage, ::getPoll).filterReactions() + .also { if (this.messageInnerEntity.pollId != null) println("JcLog: Poll within message: ${it.poll}") } + + private suspend fun getPoll(pollId: String): Poll? = + pollDao.getPoll(pollId)?.toModel(getUser) /** * Workaround to remove reactions which should not be displayed in the UI. This filtering diff --git a/stream-chat-android-offline/src/main/java/io/getstream/chat/android/offline/repository/domain/message/internal/MessageEntity.kt b/stream-chat-android-offline/src/main/java/io/getstream/chat/android/offline/repository/domain/message/internal/MessageEntity.kt index f17f3e74694..505e8f75620 100644 --- a/stream-chat-android-offline/src/main/java/io/getstream/chat/android/offline/repository/domain/message/internal/MessageEntity.kt +++ b/stream-chat-android-offline/src/main/java/io/getstream/chat/android/offline/repository/domain/message/internal/MessageEntity.kt @@ -127,6 +127,8 @@ internal data class MessageInnerEntity( val moderationDetails: ModerationDetailsEntity? = null, /** When the message text was updated */ val messageTextUpdatedAt: Date? = null, + /** The ID of the poll **/ + val pollId: String?, ) internal const val MESSAGE_ENTITY_TABLE_NAME = "stream_chat_message" diff --git a/stream-chat-android-offline/src/main/java/io/getstream/chat/android/offline/repository/domain/message/internal/MessageMapper.kt b/stream-chat-android-offline/src/main/java/io/getstream/chat/android/offline/repository/domain/message/internal/MessageMapper.kt index eb8c8e72b48..2cdc399297b 100644 --- a/stream-chat-android-offline/src/main/java/io/getstream/chat/android/offline/repository/domain/message/internal/MessageMapper.kt +++ b/stream-chat-android-offline/src/main/java/io/getstream/chat/android/offline/repository/domain/message/internal/MessageMapper.kt @@ -35,6 +35,7 @@ import io.getstream.chat.android.offline.repository.domain.reaction.internal.toM internal suspend fun MessageEntity.toModel( getUser: suspend (userId: String) -> User, getReply: suspend (messageId: String) -> Message?, + getPoll: suspend (pollId: String) -> Poll?, ): Message = with(messageInnerEntity) { Message( id = id, @@ -77,6 +78,7 @@ internal suspend fun MessageEntity.toModel( skipPushNotification = skipPushNotification, moderationDetails = moderationDetails?.toModel(), messageTextUpdatedAt = messageTextUpdatedAt, + poll = pollId?.let { getPoll(it) }, ) } @@ -118,6 +120,7 @@ internal fun Message.toEntity(): MessageEntity = MessageEntity( skipEnrichUrl = skipEnrichUrl, moderationDetails = moderationDetails?.toEntity(), messageTextUpdatedAt = messageTextUpdatedAt, + pollId = poll?.id, ), attachments = attachments.mapIndexed { index, attachment -> attachment.toEntity(id, index) }, latestReactions = latestReactions.map(Reaction::toEntity), @@ -126,6 +129,7 @@ internal fun Message.toEntity(): MessageEntity = MessageEntity( internal suspend fun ReplyMessageEntity.toModel( getUser: suspend (userId: String) -> User, + getPoll: suspend (pollId: String) -> Poll?, ): Message { val entity = this return this.replyMessageInnerEntity.run { @@ -164,6 +168,7 @@ internal suspend fun ReplyMessageEntity.toModel( pinnedBy = pinnedByUserId?.let { getUser(it) }, moderationDetails = moderationDetails?.toModel(), messageTextUpdatedAt = messageTextUpdatedAt, + poll = pollId?.let { getPoll(it) }, ) } } @@ -199,6 +204,7 @@ internal fun Message.toReplyEntity(): ReplyMessageEntity = pinExpires = pinExpires, pinnedByUserId = pinnedBy?.id, moderationDetails = moderationDetails?.toEntity(), + pollId = poll?.id, ), attachments = attachments.mapIndexed { index, attachment -> attachment.toReplyEntity(id, index) }, ) @@ -240,7 +246,7 @@ private fun VotingVisibility.toEntity(): String = when (this) { VotingVisibility.PUBLIC -> "public" } -private suspend fun PollEntity.toModel( +internal suspend fun PollEntity.toModel( getUser: suspend (userId: String) -> User, ): Poll = Poll( id = id, diff --git a/stream-chat-android-offline/src/main/java/io/getstream/chat/android/offline/repository/domain/message/internal/ReplyMessageEntity.kt b/stream-chat-android-offline/src/main/java/io/getstream/chat/android/offline/repository/domain/message/internal/ReplyMessageEntity.kt index 9b13507a567..3d674e34c3f 100644 --- a/stream-chat-android-offline/src/main/java/io/getstream/chat/android/offline/repository/domain/message/internal/ReplyMessageEntity.kt +++ b/stream-chat-android-offline/src/main/java/io/getstream/chat/android/offline/repository/domain/message/internal/ReplyMessageEntity.kt @@ -100,6 +100,8 @@ internal data class ReplyMessageInnerEntity( val moderationDetails: ModerationDetailsEntity? = null, /** Date when the message text was updated **/ val messageTextUpdatedAt: Date? = null, + /** The ID of the poll **/ + val pollId: String?, ) internal const val REPLY_MESSAGE_ENTITY_TABLE_NAME = "stream_chat_reply_message" diff --git a/stream-chat-android-offline/src/main/java/io/getstream/chat/android/offline/repository/factory/internal/DatabaseRepositoryFactory.kt b/stream-chat-android-offline/src/main/java/io/getstream/chat/android/offline/repository/factory/internal/DatabaseRepositoryFactory.kt index 2a39c2a2cc2..c38fbd805af 100644 --- a/stream-chat-android-offline/src/main/java/io/getstream/chat/android/offline/repository/factory/internal/DatabaseRepositoryFactory.kt +++ b/stream-chat-android-offline/src/main/java/io/getstream/chat/android/offline/repository/factory/internal/DatabaseRepositoryFactory.kt @@ -103,6 +103,7 @@ internal class DatabaseRepositoryFactory( scope, database.messageDao(), database.replyMessageDao(), + database.pollDao(), getUser, currentUser, DEFAULT_CACHE_SIZE, diff --git a/stream-chat-android-offline/src/test/java/io/getstream/chat/android/offline/Mother.kt b/stream-chat-android-offline/src/test/java/io/getstream/chat/android/offline/Mother.kt index c6437271f4c..91ddcb5733c 100644 --- a/stream-chat-android-offline/src/test/java/io/getstream/chat/android/offline/Mother.kt +++ b/stream-chat-android-offline/src/test/java/io/getstream/chat/android/offline/Mother.kt @@ -91,6 +91,7 @@ internal fun randomMessageEntity( pinExpires: Date? = randomDate(), pinnedByUserId: String? = randomString(), threadParticipantsIds: List = emptyList(), + pollId: String? = null, ) = MessageEntity( messageInnerEntity = MessageInnerEntity( id = id, @@ -119,6 +120,7 @@ internal fun randomMessageEntity( pinExpires = pinExpires, pinnedByUserId = pinnedByUserId, threadParticipantsIds = threadParticipantsIds, + pollId = pollId, ), attachments = attachments, latestReactions = latestReactions, diff --git a/stream-chat-android-offline/src/test/java/io/getstream/chat/android/offline/repository/MessageRepositoryTests.kt b/stream-chat-android-offline/src/test/java/io/getstream/chat/android/offline/repository/MessageRepositoryTests.kt index d3618ddf81d..b6da7bba285 100644 --- a/stream-chat-android-offline/src/test/java/io/getstream/chat/android/offline/repository/MessageRepositoryTests.kt +++ b/stream-chat-android-offline/src/test/java/io/getstream/chat/android/offline/repository/MessageRepositoryTests.kt @@ -22,6 +22,7 @@ import io.getstream.chat.android.client.query.pagination.AnyChannelPaginationReq import io.getstream.chat.android.offline.randomMessageEntity import io.getstream.chat.android.offline.repository.domain.message.internal.DatabaseMessageRepository import io.getstream.chat.android.offline.repository.domain.message.internal.MessageDao +import io.getstream.chat.android.offline.repository.domain.message.internal.PollDao import io.getstream.chat.android.offline.repository.domain.message.internal.ReplyMessageDao import io.getstream.chat.android.randomString import io.getstream.chat.android.randomUser @@ -41,11 +42,20 @@ internal class MessageRepositoryTests { private val messageDao: MessageDao = mock() private val replyMessageDao: ReplyMessageDao = mock() + private val pollDao: PollDao = mock() private lateinit var sut: MessageRepository @Test fun `when selecting messages for channel, correct messages should be requested to DAO`() = runTest { - val sut = DatabaseMessageRepository(this, messageDao, replyMessageDao, ::randomUser, randomUser(id = "currentUserId"), 100) + val sut = DatabaseMessageRepository( + this, + messageDao, + replyMessageDao, + pollDao, + ::randomUser, + randomUser(id = "currentUserId"), + 100, + ) val createdAt = Date() val cid = randomString() val messageEntity = randomMessageEntity(createdAt = createdAt) From 49d9dcb8730b4213804dfa805a87ce8dc5107fc2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jc=20Mi=C3=B1arro?= Date: Thu, 8 Aug 2024 10:23:50 +0200 Subject: [PATCH 11/14] Provide a method to obtain a list of messages that contains an specific poll --- .../api/stream-chat-android-client.api | 1 + .../client/persistance/repository/MessageRepository.kt | 9 +++++++++ .../persistance/repository/noop/NoOpMessageRepository.kt | 1 + .../api/stream-chat-android-offline.api | 1 + .../domain/message/internal/DatabaseMessageRepository.kt | 5 ++++- .../repository/domain/message/internal/MessageDao.kt | 3 +++ .../attachment/UploadAttachmentsIntegrationTests.kt | 4 ++++ 7 files changed, 23 insertions(+), 1 deletion(-) diff --git a/stream-chat-android-client/api/stream-chat-android-client.api b/stream-chat-android-client/api/stream-chat-android-client.api index d7cf5cd7629..772c16212cd 100644 --- a/stream-chat-android-client/api/stream-chat-android-client.api +++ b/stream-chat-android-client/api/stream-chat-android-client.api @@ -2417,6 +2417,7 @@ public abstract interface class io/getstream/chat/android/client/persistance/rep public abstract fun selectMessages (Ljava/util/List;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public abstract fun selectMessagesForChannel (Ljava/lang/String;Lio/getstream/chat/android/client/query/pagination/AnyChannelPaginationRequest;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public abstract fun selectMessagesForThread (Ljava/lang/String;ILkotlin/coroutines/Continuation;)Ljava/lang/Object; + public abstract fun selectMessagesWithPoll (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; } public abstract interface class io/getstream/chat/android/client/persistance/repository/QueryChannelsRepository { diff --git a/stream-chat-android-client/src/main/java/io/getstream/chat/android/client/persistance/repository/MessageRepository.kt b/stream-chat-android-client/src/main/java/io/getstream/chat/android/client/persistance/repository/MessageRepository.kt index 8b71e1346c6..d0902149c48 100644 --- a/stream-chat-android-client/src/main/java/io/getstream/chat/android/client/persistance/repository/MessageRepository.kt +++ b/stream-chat-android-client/src/main/java/io/getstream/chat/android/client/persistance/repository/MessageRepository.kt @@ -68,6 +68,15 @@ public interface MessageRepository { */ public suspend fun selectMessage(messageId: String): Message? + /** + * Selects all messages with a poll with the passed ID. + * + * @param pollId The ID of the poll. + * + * @return A list of messages with the poll. + */ + public suspend fun selectMessagesWithPoll(pollId: String): List + /** * Inserts many messages. * diff --git a/stream-chat-android-client/src/main/java/io/getstream/chat/android/client/persistance/repository/noop/NoOpMessageRepository.kt b/stream-chat-android-client/src/main/java/io/getstream/chat/android/client/persistance/repository/noop/NoOpMessageRepository.kt index b44f3f23176..083371ae12d 100644 --- a/stream-chat-android-client/src/main/java/io/getstream/chat/android/client/persistance/repository/noop/NoOpMessageRepository.kt +++ b/stream-chat-android-client/src/main/java/io/getstream/chat/android/client/persistance/repository/noop/NoOpMessageRepository.kt @@ -37,6 +37,7 @@ internal object NoOpMessageRepository : MessageRepository { override suspend fun deleteChannelMessage(message: Message) { /* No-Op */ } override suspend fun selectMessageIdsBySyncState(syncStatus: SyncStatus): List = emptyList() override suspend fun selectMessageBySyncState(syncStatus: SyncStatus): List = emptyList() + override suspend fun selectMessagesWithPoll(pollId: String): List = emptyList() override suspend fun evictMessages() { /* No-Op */ } override suspend fun evictMessage(messageId: String) { /* No-Op */ } diff --git a/stream-chat-android-offline/api/stream-chat-android-offline.api b/stream-chat-android-offline/api/stream-chat-android-offline.api index 30cfb82959c..49c09a33f69 100644 --- a/stream-chat-android-offline/api/stream-chat-android-offline.api +++ b/stream-chat-android-offline/api/stream-chat-android-offline.api @@ -93,6 +93,7 @@ public final class io/getstream/chat/android/offline/repository/domain/message/i public fun selectBySyncStatus (Lio/getstream/chat/android/models/SyncStatus;ILkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun selectChunked (Ljava/util/List;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun selectIdsBySyncStatus (Lio/getstream/chat/android/models/SyncStatus;ILkotlin/coroutines/Continuation;)Ljava/lang/Object; + public fun selectMessagesWithPoll (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun selectWaitForAttachments (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun updateMessageInnerEntity (Lio/getstream/chat/android/offline/repository/domain/message/internal/MessageInnerEntity;)V public fun upsertMessageInnerEntities (Ljava/util/List;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; diff --git a/stream-chat-android-offline/src/main/java/io/getstream/chat/android/offline/repository/domain/message/internal/DatabaseMessageRepository.kt b/stream-chat-android-offline/src/main/java/io/getstream/chat/android/offline/repository/domain/message/internal/DatabaseMessageRepository.kt index d5a3378f808..5604a1f95be 100644 --- a/stream-chat-android-offline/src/main/java/io/getstream/chat/android/offline/repository/domain/message/internal/DatabaseMessageRepository.kt +++ b/stream-chat-android-offline/src/main/java/io/getstream/chat/android/offline/repository/domain/message/internal/DatabaseMessageRepository.kt @@ -189,9 +189,12 @@ internal class DatabaseMessageRepository( * @param syncStatus [SyncStatus] */ override suspend fun selectMessageBySyncState(syncStatus: SyncStatus): List { - return messageDao.selectBySyncStatus(syncStatus).map { it.toModel(getUser, ::selectRepliedMessage, ::getPoll) } + return messageDao.selectBySyncStatus(syncStatus).map { it.toMessage() } } + override suspend fun selectMessagesWithPoll(pollId: String): List = + messageDao.selectMessagesWithPoll(pollId).map { it.toMessage() } + override suspend fun evictMessage(messageId: String) { messageCache.remove(messageId) replyMessageCache.remove(messageId) diff --git a/stream-chat-android-offline/src/main/java/io/getstream/chat/android/offline/repository/domain/message/internal/MessageDao.kt b/stream-chat-android-offline/src/main/java/io/getstream/chat/android/offline/repository/domain/message/internal/MessageDao.kt index fb46f37bce8..72cae4726ed 100644 --- a/stream-chat-android-offline/src/main/java/io/getstream/chat/android/offline/repository/domain/message/internal/MessageDao.kt +++ b/stream-chat-android-offline/src/main/java/io/getstream/chat/android/offline/repository/domain/message/internal/MessageDao.kt @@ -223,6 +223,9 @@ internal interface MessageDao { ) suspend fun selectIdsBySyncStatus(syncStatus: SyncStatus, limit: Int = NO_LIMIT): List + @Query("SELECT * FROM $MESSAGE_ENTITY_TABLE_NAME WHERE pollId = :pollId") + suspend fun selectMessagesWithPoll(pollId: String): List + @Query("DELETE FROM $MESSAGE_ENTITY_TABLE_NAME") suspend fun deleteAll() diff --git a/stream-chat-android-state/src/test/java/io/getstream/chat/android/state/channel/controller/attachment/UploadAttachmentsIntegrationTests.kt b/stream-chat-android-state/src/test/java/io/getstream/chat/android/state/channel/controller/attachment/UploadAttachmentsIntegrationTests.kt index f9eca2e3b21..7d079704c17 100644 --- a/stream-chat-android-state/src/test/java/io/getstream/chat/android/state/channel/controller/attachment/UploadAttachmentsIntegrationTests.kt +++ b/stream-chat-android-state/src/test/java/io/getstream/chat/android/state/channel/controller/attachment/UploadAttachmentsIntegrationTests.kt @@ -262,6 +262,10 @@ internal class MockMessageRepository : MessageRepository { TODO("Not yet implemented") } + override suspend fun selectMessagesWithPoll(pollId: String): List { + TODO("Not yet implemented") + } + override suspend fun evictMessages() { TODO("Not yet implemented") } From 1d29171ea4bd41542ab798f4e7076e506513a6e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jc=20Mi=C3=B1arro?= Date: Thu, 8 Aug 2024 12:07:06 +0200 Subject: [PATCH 12/14] Handle Poll Events and update offline data --- .../handler/internal/EventBatchUpdate.kt | 25 +++++++++- .../internal/EventHandlerSequential.kt | 48 ++++++++++++++++++- 2 files changed, 71 insertions(+), 2 deletions(-) diff --git a/stream-chat-android-state/src/main/java/io/getstream/chat/android/state/event/handler/internal/EventBatchUpdate.kt b/stream-chat-android-state/src/main/java/io/getstream/chat/android/state/event/handler/internal/EventBatchUpdate.kt index 02e9c515a8f..e61dc5017b7 100644 --- a/stream-chat-android-state/src/main/java/io/getstream/chat/android/state/event/handler/internal/EventBatchUpdate.kt +++ b/stream-chat-android-state/src/main/java/io/getstream/chat/android/state/event/handler/internal/EventBatchUpdate.kt @@ -23,6 +23,7 @@ import io.getstream.chat.android.client.persistance.repository.RepositoryFacade import io.getstream.chat.android.client.utils.message.latestOrNull import io.getstream.chat.android.models.Channel import io.getstream.chat.android.models.Message +import io.getstream.chat.android.models.Poll import io.getstream.chat.android.models.User import io.getstream.chat.android.state.plugin.state.global.GlobalState import io.getstream.log.StreamLog @@ -105,6 +106,18 @@ internal class EventBatchUpdate private constructor( userMap += (newUser.id to newUser) } + fun getPoll(pollId: String): Poll? = + messageMap.values + .asSequence() + .mapNotNull { it.poll } + .firstOrNull { it.id == pollId } + + fun addPoll(poll: Poll) { + messageMap.values + .filter { it.poll?.id == poll.id } + .forEach { addMessage(it.copy(poll = poll)) } + } + suspend fun execute() { // actually insert the data currentUserId?.let { userMap -= it } @@ -147,6 +160,7 @@ internal class EventBatchUpdate private constructor( private val channelsToRemove = mutableSetOf() private val messagesToFetch = mutableSetOf() private val users = mutableSetOf() + private val pollsToFetch = mutableSetOf() fun addToFetchChannels(cIds: List) { channelsToFetch += cIds @@ -172,6 +186,10 @@ internal class EventBatchUpdate private constructor( users += usersToAdd } + fun addPollToFetch(pollId: String) { + pollsToFetch += pollId + } + suspend fun build( globalState: GlobalState, repos: RepositoryFacade, @@ -181,13 +199,18 @@ internal class EventBatchUpdate private constructor( // Update users in DB in order to fetch channels and messages with sync data. repos.insertUsers(users) val messageMap: Map = - repos.selectMessages(messagesToFetch.toList()).associateBy(Message::id) + ( + repos.selectMessages(messagesToFetch.toList()) + + pollsToFetch.flatMap { repos.selectMessagesWithPoll(it) } + ).associateBy(Message::id) + val channelMap: Map = repos.selectChannels(channelsToFetch.toList()).associateBy(Channel::cid) StreamLog.v(TAG) { "[builder.build] id: $id, messageMap.size: ${messageMap.size}" + ", channelMap.size: ${channelMap.size}" } + return EventBatchUpdate( id, currentUserId, diff --git a/stream-chat-android-state/src/main/java/io/getstream/chat/android/state/event/handler/internal/EventHandlerSequential.kt b/stream-chat-android-state/src/main/java/io/getstream/chat/android/state/event/handler/internal/EventHandlerSequential.kt index e3bea59bb30..1483ba4df60 100644 --- a/stream-chat-android-state/src/main/java/io/getstream/chat/android/state/event/handler/internal/EventHandlerSequential.kt +++ b/stream-chat-android-state/src/main/java/io/getstream/chat/android/state/event/handler/internal/EventHandlerSequential.kt @@ -33,6 +33,7 @@ import io.getstream.chat.android.client.events.GlobalUserBannedEvent import io.getstream.chat.android.client.events.GlobalUserUnbannedEvent import io.getstream.chat.android.client.events.HasMessage import io.getstream.chat.android.client.events.HasOwnUser +import io.getstream.chat.android.client.events.HasPoll import io.getstream.chat.android.client.events.HasUnreadCounts import io.getstream.chat.android.client.events.MarkAllReadEvent import io.getstream.chat.android.client.events.MemberAddedEvent @@ -54,6 +55,9 @@ import io.getstream.chat.android.client.events.NotificationMarkUnreadEvent import io.getstream.chat.android.client.events.NotificationMessageNewEvent import io.getstream.chat.android.client.events.NotificationMutesUpdatedEvent import io.getstream.chat.android.client.events.NotificationRemovedFromChannelEvent +import io.getstream.chat.android.client.events.PollClosedEvent +import io.getstream.chat.android.client.events.PollDeletedEvent +import io.getstream.chat.android.client.events.PollUpdatedEvent import io.getstream.chat.android.client.events.ReactionDeletedEvent import io.getstream.chat.android.client.events.ReactionNewEvent import io.getstream.chat.android.client.events.ReactionUpdateEvent @@ -62,6 +66,9 @@ import io.getstream.chat.android.client.events.UserPresenceChangedEvent import io.getstream.chat.android.client.events.UserStartWatchingEvent import io.getstream.chat.android.client.events.UserStopWatchingEvent import io.getstream.chat.android.client.events.UserUpdatedEvent +import io.getstream.chat.android.client.events.VoteCastedEvent +import io.getstream.chat.android.client.events.VoteChangedEvent +import io.getstream.chat.android.client.events.VoteRemovedEvent import io.getstream.chat.android.client.extensions.cidToTypeAndId import io.getstream.chat.android.client.extensions.internal.addMember import io.getstream.chat.android.client.extensions.internal.addMembership @@ -121,7 +128,7 @@ private const val TAG_SOCKET = "Chat:SocketEvent" * Processes events sequentially. That means a new event will not be processed * until the previous event processing is not completed. */ -@Suppress("LongParameterList", "TooManyFunctions") +@Suppress("LongParameterList", "TooManyFunctions", "LargeClass") internal class EventHandlerSequential( private val currentUserId: UserId, private val subscribeForEvents: (ChatEventListener) -> Disposable, @@ -428,6 +435,7 @@ internal class EventHandlerSequential( val events = batchEvent.sortedEvents.map { it.enrichIfNeeded() } val batchBuilder = EventBatchUpdate.Builder(batchEvent.id) val cidEvents = events.filterIsInstance() + val pollEvents = events.filterIsInstance() batchBuilder.addToFetchChannels( cidEvents .filterNot { it is ChannelDeletedEvent || it is NotificationChannelDeletedEvent } @@ -439,6 +447,7 @@ internal class EventHandlerSequential( .filter { it is ChannelDeletedEvent || it is NotificationChannelDeletedEvent } .map { it.cid }, ) + pollEvents.forEach { batchBuilder.addPollToFetch(it.poll.id) } val users: List = events.filterIsInstance().map { it.user } + events.filterIsInstance().map { it.me } @@ -666,6 +675,43 @@ internal class EventHandlerSequential( is UserUpdatedEvent -> if (event.user.id == currentUserId) { repos.insertCurrentUser(event.user) } + is PollClosedEvent -> batch.addPoll(event.poll) + is PollDeletedEvent -> batch.addPoll(event.poll) + is PollUpdatedEvent -> batch.addPoll(event.poll) + is VoteCastedEvent -> { + val ownVotes = + ( + batch.getPoll(event.poll.id)?.ownVotes?.associateBy { it.id } + ?: emptyMap() + ) + + listOfNotNull(event.newVote.takeIf { it.user?.id == currentUserId }).associateBy { it.id } + batch.addPoll( + event.poll.copy( + ownVotes = ownVotes.values.toList(), + ), + ) + } + is VoteChangedEvent -> { + val ownVotes = event.newVote.takeIf { it.user?.id == currentUserId }?.let { listOf(it) } + ?: batch.getPoll(event.poll.id)?.ownVotes + batch.addPoll( + event.poll.copy( + ownVotes = ownVotes ?: emptyList(), + ), + ) + } + is VoteRemovedEvent -> { + val ownVotes = + ( + batch.getPoll(event.poll.id)?.ownVotes?.associateBy { it.id } + ?: emptyMap() + ) - event.removedVote.id + batch.addPoll( + event.poll.copy( + ownVotes = ownVotes.values.toList(), + ), + ) + } else -> Unit } } From 1aa6b67db6e508e52d37a550cc050954e38d836a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jc=20Mi=C3=B1arro?= Date: Thu, 8 Aug 2024 12:26:56 +0200 Subject: [PATCH 13/14] Remove unneeded logs --- .../domain/message/internal/DatabaseMessageRepository.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/stream-chat-android-offline/src/main/java/io/getstream/chat/android/offline/repository/domain/message/internal/DatabaseMessageRepository.kt b/stream-chat-android-offline/src/main/java/io/getstream/chat/android/offline/repository/domain/message/internal/DatabaseMessageRepository.kt index 5604a1f95be..11dac7cab1f 100644 --- a/stream-chat-android-offline/src/main/java/io/getstream/chat/android/offline/repository/domain/message/internal/DatabaseMessageRepository.kt +++ b/stream-chat-android-offline/src/main/java/io/getstream/chat/android/offline/repository/domain/message/internal/DatabaseMessageRepository.kt @@ -263,7 +263,6 @@ internal class DatabaseMessageRepository( private suspend fun MessageEntity.toMessage(): Message = this.toModel(getUser, ::selectRepliedMessage, ::getPoll).filterReactions() - .also { if (this.messageInnerEntity.pollId != null) println("JcLog: Poll within message: ${it.poll}") } private suspend fun getPoll(pollId: String): Poll? = pollDao.getPoll(pollId)?.toModel(getUser) From 7342314a5410a451e9ee88f5422881dfe2e40fe4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jc=20Mi=C3=B1arro?= Date: Mon, 12 Aug 2024 12:13:16 +0200 Subject: [PATCH 14/14] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 814010ef6d6..6915d5252e4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,6 +27,7 @@ ### ⬆️ Improved ### ✅ Added +- Store poll info on local data base. [#5283](https://github.com/GetStream/stream-chat-android/pull/5283) ### ⚠️ Changed