Skip to content

Commit

Permalink
feat: conversation list pagination, pt1 - queries [WPB-9433]
Browse files Browse the repository at this point in the history
  • Loading branch information
saleniuk committed Oct 9, 2024
1 parent 8e0f4a6 commit c471f61
Show file tree
Hide file tree
Showing 26 changed files with 1,190 additions and 238 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -293,15 +293,11 @@ sealed class ConversationDetails(open val conversation: Conversation) {
override val conversation: Conversation,
val otherUser: OtherUser,
val userType: UserType,
val unreadEventCount: UnreadEventCount,
val lastMessage: MessagePreview?
) : ConversationDetails(conversation)

data class Group(
override val conversation: Conversation,
val hasOngoingCall: Boolean = false,
val unreadEventCount: UnreadEventCount,
val lastMessage: MessagePreview?,
val isSelfUserMember: Boolean,
val isSelfUserCreator: Boolean,
val selfRole: Conversation.Member.Role?
Expand Down Expand Up @@ -344,6 +340,13 @@ sealed class ConversationDetails(open val conversation: Conversation) {
)
}

data class ConversationDetailsWithEvents(
val conversationDetails: ConversationDetails,
val unreadEventCount: UnreadEventCount = emptyMap(),
val lastMessage: MessagePreview? = null,
val hasNewActivitiesToShow: Boolean = false,
)

fun ConversationDetails.interactionAvailability(): InteractionAvailability {
val availability = when (this) {
is ConversationDetails.Connection -> InteractionAvailability.DISABLED
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ import com.wire.kalium.logic.data.id.TeamId
import com.wire.kalium.logic.data.id.toApi
import com.wire.kalium.logic.data.id.toDao
import com.wire.kalium.logic.data.id.toModel
import com.wire.kalium.logic.data.message.MessagePreview
import com.wire.kalium.logic.data.message.MessageMapper
import com.wire.kalium.logic.data.message.UnreadEventType
import com.wire.kalium.logic.data.mls.MLSPublicKeys
import com.wire.kalium.logic.data.user.AvailabilityStatusMapper
import com.wire.kalium.logic.data.user.BotService
Expand All @@ -45,12 +46,14 @@ import com.wire.kalium.network.api.authenticated.conversation.ReceiptMode
import com.wire.kalium.network.api.authenticated.serverpublickey.MLSPublicKeysDTO
import com.wire.kalium.network.api.model.ConversationAccessDTO
import com.wire.kalium.network.api.model.ConversationAccessRoleDTO
import com.wire.kalium.persistence.dao.conversation.ConversationDetailsWithEventsEntity
import com.wire.kalium.persistence.dao.conversation.ConversationEntity
import com.wire.kalium.persistence.dao.conversation.ConversationEntity.GroupState
import com.wire.kalium.persistence.dao.conversation.ConversationEntity.Protocol
import com.wire.kalium.persistence.dao.conversation.ConversationEntity.ProtocolInfo
import com.wire.kalium.persistence.dao.conversation.ConversationViewEntity
import com.wire.kalium.persistence.dao.conversation.ProposalTimerEntity
import com.wire.kalium.persistence.dao.unread.UnreadEventTypeEntity
import com.wire.kalium.persistence.util.requireField
import com.wire.kalium.util.DateTimeUtil
import com.wire.kalium.util.time.UNIX_FIRST_DATE
Expand All @@ -64,12 +67,8 @@ interface ConversationMapper {
fun fromApiModel(mlsPublicKeysDTO: MLSPublicKeysDTO?): MLSPublicKeys?
fun fromDaoModel(daoModel: ConversationViewEntity): Conversation
fun fromDaoModel(daoModel: ConversationEntity): Conversation
fun fromDaoModelToDetails(
daoModel: ConversationViewEntity,
lastMessage: MessagePreview?,
unreadEventCount: UnreadEventCount?
): ConversationDetails

fun fromDaoModelToDetails(daoModel: ConversationViewEntity): ConversationDetails
fun fromDaoModelToDetailsWithEvents(daoModel: ConversationDetailsWithEventsEntity): ConversationDetailsWithEvents
fun fromDaoModel(daoModel: ProposalTimerEntity): ProposalTimer
fun toDAOAccess(accessList: Set<ConversationAccessDTO>): List<ConversationEntity.Access>
fun toDAOAccessRole(accessRoleList: Set<ConversationAccessRoleDTO>): List<ConversationEntity.AccessRole>
Expand Down Expand Up @@ -105,7 +104,8 @@ internal class ConversationMapperImpl(
private val domainUserTypeMapper: DomainUserTypeMapper,
private val connectionStatusMapper: ConnectionStatusMapper,
private val conversationRoleMapper: ConversationRoleMapper,
private val receiptModeMapper: ReceiptModeMapper = MapperProvider.receiptModeMapper()
private val messageMapper: MessageMapper,
private val receiptModeMapper: ReceiptModeMapper = MapperProvider.receiptModeMapper(),
) : ConversationMapper {

override fun fromApiModelToDaoModel(
Expand Down Expand Up @@ -232,11 +232,7 @@ internal class ConversationMapperImpl(
}

@Suppress("ComplexMethod", "LongMethod")
override fun fromDaoModelToDetails(
daoModel: ConversationViewEntity,
lastMessage: MessagePreview?,
unreadEventCount: UnreadEventCount?
): ConversationDetails =
override fun fromDaoModelToDetails(daoModel: ConversationViewEntity): ConversationDetails =
with(daoModel) {
when (type) {
ConversationEntity.Type.SELF -> {
Expand Down Expand Up @@ -266,17 +262,13 @@ internal class ConversationMapperImpl(
activeOneOnOneConversationId = userActiveOneOnOneConversationId?.toModel()
),
userType = domainUserTypeMapper.fromUserTypeEntity(userType),
unreadEventCount = unreadEventCount ?: mapOf(),
lastMessage = lastMessage
)
}

ConversationEntity.Type.GROUP -> {
ConversationDetails.Group(
conversation = fromConversationViewToEntity(daoModel),
hasOngoingCall = callStatus != null, // todo: we can do better!
unreadEventCount = unreadEventCount ?: mapOf(),
lastMessage = lastMessage,
isSelfUserMember = isMember,
isSelfUserCreator = isCreator == 1L,
selfRole = selfRole?.let { conversationRoleMapper.fromDAO(it) }
Expand Down Expand Up @@ -325,6 +317,26 @@ internal class ConversationMapperImpl(
}
}

override fun fromDaoModelToDetailsWithEvents(daoModel: ConversationDetailsWithEventsEntity): ConversationDetailsWithEvents =
ConversationDetailsWithEvents(
conversationDetails = fromDaoModelToDetails(daoModel.conversationViewEntity),
unreadEventCount = daoModel.unreadEvents.unreadEvents.mapKeys {
when (it.key) {
UnreadEventTypeEntity.KNOCK -> UnreadEventType.KNOCK
UnreadEventTypeEntity.MISSED_CALL -> UnreadEventType.MISSED_CALL
UnreadEventTypeEntity.MENTION -> UnreadEventType.MENTION
UnreadEventTypeEntity.REPLY -> UnreadEventType.REPLY
UnreadEventTypeEntity.MESSAGE -> UnreadEventType.MESSAGE
}
},
lastMessage = when {
daoModel.conversationViewEntity.archived -> null // no last message in archived conversations
daoModel.messageDraft != null -> messageMapper.fromDraftToMessagePreview(daoModel.messageDraft!!)
daoModel.lastMessage != null -> messageMapper.fromEntityToMessagePreview(daoModel.lastMessage!!)
else -> null
},
)

override fun fromDaoModel(daoModel: ProposalTimerEntity): ProposalTimer =
ProposalTimer(idMapper.fromGroupIDEntity(daoModel.groupID), daoModel.firingDate)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,6 @@ import com.wire.kalium.logic.data.id.toDao
import com.wire.kalium.logic.data.id.toModel
import com.wire.kalium.logic.data.message.MessageMapper
import com.wire.kalium.logic.data.message.SelfDeletionTimer
import com.wire.kalium.logic.data.message.UnreadEventType
import com.wire.kalium.logic.data.user.UserId
import com.wire.kalium.logic.di.MapperProvider
import com.wire.kalium.logic.functional.Either
Expand Down Expand Up @@ -75,16 +74,11 @@ import com.wire.kalium.persistence.dao.conversation.ConversationEntity
import com.wire.kalium.persistence.dao.conversation.ConversationMetaDataDAO
import com.wire.kalium.persistence.dao.member.MemberDAO
import com.wire.kalium.persistence.dao.message.MessageDAO
import com.wire.kalium.persistence.dao.message.draft.MessageDraftDAO
import com.wire.kalium.persistence.dao.unread.UnreadEventTypeEntity
import com.wire.kalium.util.DelicateKaliumApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.firstOrNull
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
import kotlinx.datetime.Instant

Expand Down Expand Up @@ -131,6 +125,11 @@ interface ConversationRepository {
suspend fun getConversationList(): Either<StorageFailure, Flow<List<Conversation>>>
suspend fun observeConversationList(): Flow<List<Conversation>>
suspend fun observeConversationListDetails(fromArchive: Boolean): Flow<List<ConversationDetails>>
suspend fun observeConversationListDetailsWithEvents(
fromArchive: Boolean = false,
onlyInteractionsEnabled: Boolean = false,
newActivitiesOnTop: Boolean = false,
): Flow<List<ConversationDetailsWithEvents>>
suspend fun getConversationIds(
type: Conversation.Type,
protocol: Conversation.Protocol,
Expand Down Expand Up @@ -319,7 +318,6 @@ internal class ConversationDataSource internal constructor(
private val clientDAO: ClientDAO,
private val clientApi: ClientApi,
private val conversationMetaDataDAO: ConversationMetaDataDAO,
private val messageDraftDAO: MessageDraftDAO,
private val idMapper: IdMapper = MapperProvider.idMapper(),
private val conversationMapper: ConversationMapper = MapperProvider.conversationMapper(selfUserId),
private val memberMapper: MemberMapper = MapperProvider.memberMapper(),
Expand Down Expand Up @@ -352,11 +350,10 @@ internal class ConversationDataSource internal constructor(
override suspend fun observeConversationDetailsById(conversationID: ConversationId): Flow<Either<StorageFailure, ConversationDetails>> =
conversationDAO.observeConversationDetailsById(conversationID.toDao())
.wrapStorageRequest()
// TODO we don't need last message and unread count here, we should discuss to divide model for list and for details
.map { eitherConversationView ->
eitherConversationView.flatMap {
try {
Either.Right(conversationMapper.fromDaoModelToDetails(it, null, mapOf()))
Either.Right(conversationMapper.fromDaoModelToDetails(it))
} catch (error: IllegalArgumentException) {
kaliumLogger.e("require field in conversation Details", error)
Either.Left(StorageFailure.DataNotFound)
Expand Down Expand Up @@ -515,33 +512,22 @@ internal class ConversationDataSource internal constructor(
}

override suspend fun observeConversationListDetails(fromArchive: Boolean): Flow<List<ConversationDetails>> =
combine(
conversationDAO.getAllConversationDetails(fromArchive),
if (fromArchive) flowOf(listOf()) else messageDAO.observeLastMessages(),
messageDAO.observeConversationsUnreadEvents(),
messageDraftDAO.observeMessageDrafts()
) { conversationList, lastMessageList, unreadEvents, drafts ->
val lastMessageMap = lastMessageList.associateBy { it.conversationId }
val messageDraftMap = drafts.filter { it.text.isNotBlank() }.associateBy { it.conversationId }

conversationList.map { conversation ->
conversationMapper.fromDaoModelToDetails(
conversation,
lastMessage = messageDraftMap[conversation.id]?.let { messageMapper.fromDraftToMessagePreview(it) }
?: lastMessageMap[conversation.id]?.let { messageMapper.fromEntityToMessagePreview(it) },
unreadEventCount = unreadEvents.firstOrNull { it.conversationId == conversation.id }?.unreadEvents?.mapKeys {
when (it.key) {
UnreadEventTypeEntity.KNOCK -> UnreadEventType.KNOCK
UnreadEventTypeEntity.MISSED_CALL -> UnreadEventType.MISSED_CALL
UnreadEventTypeEntity.MENTION -> UnreadEventType.MENTION
UnreadEventTypeEntity.REPLY -> UnreadEventType.REPLY
UnreadEventTypeEntity.MESSAGE -> UnreadEventType.MESSAGE
}
}
)
}
conversationDAO.getAllConversationDetails(fromArchive).map { conversationViewEntityList ->
conversationViewEntityList.map { conversationViewEntity -> conversationMapper.fromDaoModelToDetails(conversationViewEntity) }
}

override suspend fun observeConversationListDetailsWithEvents(
fromArchive: Boolean,
onlyInteractionsEnabled: Boolean,
newActivitiesOnTop: Boolean,
): Flow<List<ConversationDetailsWithEvents>> =
conversationDAO.getAllConversationDetailsWithEvents(fromArchive, onlyInteractionsEnabled, newActivitiesOnTop)
.map { conversationDetailsWithEventsViewEntityList ->
conversationDetailsWithEventsViewEntityList.map { conversationDetailsWithEventsViewEntity ->
conversationMapper.fromDaoModelToDetailsWithEvents(conversationDetailsWithEventsViewEntity)
}
}

override suspend fun fetchMlsOneToOneConversation(userId: UserId): Either<CoreFailure, Conversation> =
wrapApiRequest {
conversationApi.fetchMlsOneToOneConversation(userId.toApi())
Expand Down Expand Up @@ -994,7 +980,7 @@ internal class ConversationDataSource internal constructor(

override suspend fun getConversationDetailsByMLSGroupId(mlsGroupId: GroupID): Either<CoreFailure, ConversationDetails> =
wrapStorageRequest { conversationDAO.getConversationByGroupID(mlsGroupId.value) }
.map { conversationMapper.fromDaoModelToDetails(it, null, mapOf()) }
.map { conversationMapper.fromDaoModelToDetails(it) }

override suspend fun observeUnreadArchivedConversationsCount(): Flow<Long> =
conversationDAO.observeUnreadArchivedConversationsCount()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,8 @@ internal object MapperProvider {
AvailabilityStatusMapperImpl(),
DomainUserTypeMapperImpl(),
ConnectionStatusMapperImpl(),
ConversationRoleMapperImpl()
ConversationRoleMapperImpl(),
MessageMapperImpl(selfUserId),
)

fun conversationRoleMapper(): ConversationRoleMapper = ConversationRoleMapperImpl()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -711,7 +711,6 @@ class UserSessionScope internal constructor(
userStorage.database.clientDAO,
authenticatedNetworkContainer.clientApi,
userStorage.database.conversationMetaDataDAO,
userStorage.database.messageDraftDAO
)

private val conversationGroupRepository: ConversationGroupRepository
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,6 @@ import kotlinx.coroutines.test.runTest
import kotlinx.coroutines.yield
import kotlinx.datetime.Clock
import kotlin.test.Test
import kotlin.test.assertContains
import kotlin.test.assertEquals
import kotlin.test.assertFalse
import kotlin.test.assertTrue
Expand Down Expand Up @@ -174,10 +173,8 @@ class CallRepositoryTest {
ConversationDetails.Group(
Arrangement.groupConversation,
false,
lastMessage = null,
isSelfUserMember = true,
isSelfUserCreator = true,
unreadEventCount = emptyMap(),
selfRole = Conversation.Member.Role.Member
)
)
Expand Down Expand Up @@ -214,10 +211,8 @@ class CallRepositoryTest {
Either.Right(
ConversationDetails.Group(
Arrangement.groupConversation,
lastMessage = null,
isSelfUserMember = true,
isSelfUserCreator = true,
unreadEventCount = emptyMap(),
selfRole = Conversation.Member.Role.Member
)
)
Expand Down Expand Up @@ -270,10 +265,8 @@ class CallRepositoryTest {
Either.Right(
ConversationDetails.Group(
Arrangement.groupConversation,
lastMessage = null,
isSelfUserMember = true,
isSelfUserCreator = true,
unreadEventCount = emptyMap(),
selfRole = Conversation.Member.Role.Member
)
)
Expand Down Expand Up @@ -315,10 +308,8 @@ class CallRepositoryTest {
Either.Right(
ConversationDetails.Group(
Arrangement.groupConversation,
lastMessage = null,
isSelfUserMember = true,
isSelfUserCreator = true,
unreadEventCount = emptyMap(),
selfRole = Conversation.Member.Role.Member
)
)
Expand Down Expand Up @@ -374,10 +365,8 @@ class CallRepositoryTest {
Either.Right(
ConversationDetails.Group(
Arrangement.groupConversation,
lastMessage = null,
isSelfUserMember = true,
isSelfUserCreator = true,
unreadEventCount = emptyMap(),
selfRole = Conversation.Member.Role.Member
)
)
Expand Down Expand Up @@ -1819,8 +1808,6 @@ class CallRepositoryTest {
conversation = oneOnOneConversation,
otherUser = TestUser.OTHER,
userType = UserType.INTERNAL,
lastMessage = null,
unreadEventCount = emptyMap()
)

val mlsProtocolInfo = Conversation.ProtocolInfo.MLS(
Expand Down
Loading

0 comments on commit c471f61

Please sign in to comment.