Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[PBE-4089] Store Poll in our local db #5283

Merged
merged 14 commits into from
Aug 12, 2024
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
### ⬆️ Improved

### ✅ Added
- Store poll info on local data base. [#5283](https://github.com/GetStream/stream-chat-android/pull/5283)

### ⚠️ Changed

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<Message>

/**
* Inserts many messages.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ internal object NoOpMessageRepository : MessageRepository {
override suspend fun deleteChannelMessage(message: Message) { /* No-Op */ }
override suspend fun selectMessageIdsBySyncState(syncStatus: SyncStatus): List<String> = emptyList()
override suspend fun selectMessageBySyncState(syncStatus: SyncStatus): List<Message> = emptyList()
override suspend fun selectMessagesWithPoll(pollId: String): List<Message> = emptyList()
override suspend fun evictMessages() { /* No-Op */ }
override suspend fun evictMessage(messageId: String) { /* No-Op */ }

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -92,12 +93,20 @@ 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;
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 <init> (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 <init> (Landroidx/room/RoomDatabase;)V
public fun delete (Lio/getstream/chat/android/offline/repository/domain/message/internal/ReplyMessageInnerEntity;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
Expand Down
Original file line number Diff line number Diff line change
@@ -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<OptionEntity>()

@OptIn(ExperimentalStdlibApi::class)
private val entityListAdapter = moshi.adapter<List<OptionEntity>>()

@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<OptionEntity>? {
if (data.isNullOrEmpty() || data == "null") {
return emptyList()
}
return entityListAdapter.fromJson(data)
}

@TypeConverter
fun optionListToString(entities: List<OptionEntity>?): String? {
return entities?.let {
entityListAdapter.toJson(it)
}
}
}
Original file line number Diff line number Diff line change
@@ -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<VoteEntity>()

@OptIn(ExperimentalStdlibApi::class)
private val entityListAdapter = moshi.adapter<List<VoteEntity>>()

@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<VoteEntity>? {
if (data.isNullOrEmpty() || data == "null") {
return emptyList()
}
return entityListAdapter.fromJson(data)
}

@TypeConverter
fun voteListToString(entities: List<VoteEntity>?): String? {
return entities?.let {
entityListAdapter.toJson(it)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +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.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
Expand All @@ -44,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
Expand All @@ -68,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(
Expand All @@ -85,6 +90,8 @@ import io.getstream.chat.android.offline.repository.domain.user.internal.UserEnt
ModerationDetailsConverter::class,
ReactionGroupConverter::class,
PrivacySettingsConverter::class,
OptionConverter::class,
VoteConverter::class,
)
internal abstract class ChatDatabase : RoomDatabase() {
abstract fun queryChannelsDao(): QueryChannelsDao
Expand All @@ -96,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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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,
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -182,9 +189,12 @@ internal class DatabaseMessageRepository(
* @param syncStatus [SyncStatus]
*/
override suspend fun selectMessageBySyncState(syncStatus: SyncStatus): List<Message> {
return messageDao.selectBySyncStatus(syncStatus).map { it.toModel(getUser, ::selectRepliedMessage) }
return messageDao.selectBySyncStatus(syncStatus).map { it.toMessage() }
}

override suspend fun selectMessagesWithPoll(pollId: String): List<Message> =
messageDao.selectMessagesWithPoll(pollId).map { it.toMessage() }

override suspend fun evictMessage(messageId: String) {
messageCache.remove(messageId)
replyMessageCache.remove(messageId)
Expand Down Expand Up @@ -252,7 +262,10 @@ internal class DatabaseMessageRepository(
}

private suspend fun MessageEntity.toMessage(): Message =
this.toModel(getUser, ::selectRepliedMessage).filterReactions()
this.toModel(getUser, ::selectRepliedMessage, ::getPoll).filterReactions()

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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,9 @@ internal interface MessageDao {
)
suspend fun selectIdsBySyncStatus(syncStatus: SyncStatus, limit: Int = NO_LIMIT): List<String>

@Query("SELECT * FROM $MESSAGE_ENTITY_TABLE_NAME WHERE pollId = :pollId")
suspend fun selectMessagesWithPoll(pollId: String): List<MessageEntity>

@Query("DELETE FROM $MESSAGE_ENTITY_TABLE_NAME")
suspend fun deleteAll()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Loading
Loading