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

feat: conversation list pagination, pt1 - queries [WPB-9433] #3055

Merged
merged 2 commits into from
Oct 14, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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?
@@ -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
Original file line number Diff line number Diff line change
@@ -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
@@ -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
@@ -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>
@@ -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(
@@ -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 -> {
@@ -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) }
@@ -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)

Original file line number Diff line number Diff line change
@@ -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
@@ -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

@@ -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,
@@ -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(),
@@ -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)
@@ -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())
@@ -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()
Original file line number Diff line number Diff line change
@@ -128,7 +128,8 @@ internal object MapperProvider {
AvailabilityStatusMapperImpl(),
DomainUserTypeMapperImpl(),
ConnectionStatusMapperImpl(),
ConversationRoleMapperImpl()
ConversationRoleMapperImpl(),
MessageMapperImpl(selfUserId),
)

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

private val conversationGroupRepository: ConversationGroupRepository
Original file line number Diff line number Diff line change
@@ -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
@@ -174,10 +173,8 @@ class CallRepositoryTest {
ConversationDetails.Group(
Arrangement.groupConversation,
false,
lastMessage = null,
isSelfUserMember = true,
isSelfUserCreator = true,
unreadEventCount = emptyMap(),
selfRole = Conversation.Member.Role.Member
)
)
@@ -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
)
)
@@ -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
)
)
@@ -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
)
)
@@ -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
)
)
@@ -1819,8 +1808,6 @@ class CallRepositoryTest {
conversation = oneOnOneConversation,
otherUser = TestUser.OTHER,
userType = UserType.INTERNAL,
lastMessage = null,
unreadEventCount = emptyMap()
)

val mlsProtocolInfo = Conversation.ProtocolInfo.MLS(
Loading