From 3c8ceb09e61f25ad91771f95f49b8fe4da63c223 Mon Sep 17 00:00:00 2001 From: Yamil Medina Date: Fri, 8 Dec 2023 13:22:41 +0100 Subject: [PATCH] feat: queries, storage and mapping for receiving location messages (WPB-5170) (#2285) * feat: parse location messages persistence, queries and mappers * feat: location migration added * feat: location migration added, query insertion * feat: map message preview and notification * feat: map message preview and notification, map temp sqm * feat: map notifications and second line * feat: fix tests --- .../wire/kalium/logic/data/message/Message.kt | 4 + .../logic/data/message/MessageContent.kt | 9 + .../logic/data/message/MessageMapper.kt | 21 +++ .../data/message/PersistMessageUseCase.kt | 1 + .../logic/data/message/ProtoContentMapper.kt | 30 ++- .../data/notification/LocalNotification.kt | 2 +- .../message/PersistMigratedMessagesUseCase.kt | 1 + .../message/ApplicationMessageHandler.kt | 2 + .../kalium/persistence/MessageDetailsView.sq | 7 +- .../wire/kalium/persistence/MessagePreview.sq | 2 +- .../com/wire/kalium/persistence/Messages.sq | 19 +- .../wire/kalium/persistence/Notification.sq | 2 +- .../src/commonMain/db_user/migrations/68.sqm | 171 ++++++++++++++++++ .../persistence/dao/message/MessageEntity.kt | 9 +- .../dao/message/MessageInsertExtension.kt | 11 ++ .../persistence/dao/message/MessageMapper.kt | 14 +- .../wire/kalium/persistence/db/TableMapper.kt | 11 +- .../persistence/db/UserDatabaseBuilder.kt | 3 +- .../dao/message/MessageMapperTest.kt | 12 +- 19 files changed, 317 insertions(+), 14 deletions(-) create mode 100644 persistence/src/commonMain/db_user/migrations/68.sqm diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/message/Message.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/message/Message.kt index 287b614e4e7..4a6a01d61b7 100644 --- a/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/message/Message.kt +++ b/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/message/Message.kt @@ -134,6 +134,10 @@ sealed interface Message { is MessageContent.Composite -> mutableMapOf( typeKey to "composite" ) + + is MessageContent.Location -> mutableMapOf( + typeKey to "location", + ) } val standardProperties = mapOf( diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/message/MessageContent.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/message/MessageContent.kt index 253fadd4de0..b410290bdc9 100644 --- a/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/message/MessageContent.kt +++ b/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/message/MessageContent.kt @@ -288,6 +288,13 @@ sealed class MessageContent { val clientId: ClientId? = null ) : Regular() + data class Location( + val latitude: Float, + val longitude: Float, + val name: String? = null, + val zoom: Int? = null, + ) : Regular() + data object MLSWrongEpochWarning : System() data object ClientAction : Signaling() @@ -359,6 +366,7 @@ fun MessageContent?.getType() = when (this) { MessageContent.ConversationVerifiedMLS -> "ConversationVerification.Verified.MLS" MessageContent.ConversationVerifiedProteus -> "ConversationVerification.Verified.Proteus" is MessageContent.ConversationStartedUnverifiedWarning -> "ConversationStartedUnverifiedWarning" + is MessageContent.Location -> "Location" null -> "null" } @@ -378,6 +386,7 @@ sealed interface MessagePreviewContent { data class QuotedSelf(override val username: String?) : WithUser data class Knock(override val username: String?) : WithUser + data class Location(override val username: String?) : WithUser data class MemberLeft(override val username: String?) : WithUser diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/message/MessageMapper.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/message/MessageMapper.kt index 7cd26092215..2fac50341ea 100644 --- a/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/message/MessageMapper.kt +++ b/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/message/MessageMapper.kt @@ -264,6 +264,12 @@ class MessageMapperImpl( ) } + MessageEntity.ContentType.LOCATION -> LocalNotificationMessage.Comment( + message.id, + sender, + message.date, + LocalNotificationCommentType.LOCATION + ) MessageEntity.ContentType.MEMBER_CHANGE -> null MessageEntity.ContentType.RESTRICTED_ASSET -> null MessageEntity.ContentType.CONVERSATION_RENAMED -> null @@ -358,6 +364,13 @@ class MessageMapperImpl( ) }, ) + + is MessageContent.Location -> MessageEntityContent.Location( + latitude = regularMessage.latitude, + longitude = regularMessage.longitude, + name = regularMessage.name, + zoom = regularMessage.zoom + ) } private fun toTextEntity(textContent: MessageContent.Text): MessageEntityContent.Text = MessageEntityContent.Text( @@ -466,6 +479,7 @@ private fun MessagePreviewEntityContent.toMessageContent(): MessagePreviewConten is MessagePreviewEntityContent.ConversationVerifiedProteus -> MessagePreviewContent.VerificationChanged.VerifiedProteus is MessagePreviewEntityContent.ConversationVerificationDegradedMls -> MessagePreviewContent.VerificationChanged.DegradedMls is MessagePreviewEntityContent.ConversationVerificationDegradedProteus -> MessagePreviewContent.VerificationChanged.DegradedProteus + is MessagePreviewEntityContent.Location -> MessagePreviewContent.WithUser.Location(username = senderName) } fun AssetTypeEntity.toModel(): AssetType = when (this) { @@ -551,6 +565,13 @@ fun MessageEntityContent.Regular.toMessageContent(hidden: Boolean, selfUserId: U ) } ) + + is MessageEntityContent.Location -> MessageContent.Location( + latitude = this.latitude, + longitude = this.longitude, + name = this.name, + zoom = this.zoom + ) } private fun quotedContentFromEntity(it: MessageEntityContent.Text.QuotedMessage) = when { diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/message/PersistMessageUseCase.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/message/PersistMessageUseCase.kt index c54c562beaa..455a89af91e 100644 --- a/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/message/PersistMessageUseCase.kt +++ b/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/message/PersistMessageUseCase.kt @@ -110,5 +110,6 @@ internal class PersistMessageUseCaseImpl( is MessageContent.FederationStopped.Removed -> false is MessageContent.ConversationProtocolChanged -> false is MessageContent.ConversationStartedUnverifiedWarning -> false + is MessageContent.Location -> true } } diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/message/ProtoContentMapper.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/message/ProtoContentMapper.kt index 6045a3c5eca..40f4c6404f2 100644 --- a/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/message/ProtoContentMapper.kt +++ b/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/message/ProtoContentMapper.kt @@ -45,6 +45,7 @@ import com.wire.kalium.protobuf.messages.GenericMessage import com.wire.kalium.protobuf.messages.Knock import com.wire.kalium.protobuf.messages.LastRead import com.wire.kalium.protobuf.messages.LegalHoldStatus +import com.wire.kalium.protobuf.messages.Location import com.wire.kalium.protobuf.messages.MessageDelete import com.wire.kalium.protobuf.messages.MessageEdit import com.wire.kalium.protobuf.messages.MessageHide @@ -132,6 +133,7 @@ class ProtoContentMapperImpl( is MessageContent.ButtonAction -> packButtonAction(readableContent) is MessageContent.ButtonActionConfirmation -> TODO() + is MessageContent.Location -> TODO("todo, when implementing send location") } } @@ -200,6 +202,10 @@ class ProtoContentMapperImpl( ) } + is MessageContent.Location -> { + TODO("todo, when implementing send location") + } + is MessageContent.FailedDecryption, is MessageContent.RestrictedAsset, is MessageContent.Unknown, @@ -319,7 +325,7 @@ class ProtoContentMapperImpl( is GenericMessage.Content.Hidden -> unpackHidden(genericMessage, protoContent) is GenericMessage.Content.Knock -> MessageContent.Knock(protoContent.value.hotKnock) is GenericMessage.Content.LastRead -> unpackLastRead(genericMessage, protoContent) - is GenericMessage.Content.Location -> MessageContent.Unknown(typeName, encodedContent.data) + is GenericMessage.Content.Location -> unpackLocation(protoContent) is GenericMessage.Content.Reaction -> unpackReaction(protoContent) is GenericMessage.Content.External -> { @@ -335,6 +341,15 @@ class ProtoContentMapperImpl( return readableContent } + private fun unpackLocation( + protoContent: GenericMessage.Content.Location + ): MessageContent.FromProto = MessageContent.Location( + latitude = protoContent.value.latitude, + longitude = protoContent.value.longitude, + name = protoContent.value.name, + zoom = protoContent.value.zoom + ) + private fun packReceipt( receiptContent: MessageContent.Receipt ): GenericMessage.Content.Confirmation { @@ -621,9 +636,20 @@ class ProtoContentMapperImpl( ) } + is Ephemeral.Content.Location -> { + val location = GenericMessage.Content.Location( + Location( + latitude = ephemeralContent.value.latitude, + longitude = ephemeralContent.value.longitude, + name = ephemeralContent.value.name, + zoom = ephemeralContent.value.zoom + ) + ) + unpackLocation(location) + } + // Handle self-deleting Location messages when they are implemented is Ephemeral.Content.Image, - is Ephemeral.Content.Location, null -> { MessageContent.Ignored } diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/notification/LocalNotification.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/notification/LocalNotification.kt index 74cc7274c29..35a2b377c72 100644 --- a/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/notification/LocalNotification.kt +++ b/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/notification/LocalNotification.kt @@ -102,5 +102,5 @@ sealed class LocalNotificationMessage( data class LocalNotificationMessageAuthor(val name: String, val imageUri: UserAssetId?) enum class LocalNotificationCommentType { - PICTURE, FILE, REACTION, MISSED_CALL, NOT_SUPPORTED_YET + PICTURE, FILE, REACTION, MISSED_CALL, LOCATION, NOT_SUPPORTED_YET } diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/message/PersistMigratedMessagesUseCase.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/message/PersistMigratedMessagesUseCase.kt index a4c2e168e5d..62e39f18059 100644 --- a/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/message/PersistMigratedMessagesUseCase.kt +++ b/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/message/PersistMigratedMessagesUseCase.kt @@ -198,5 +198,6 @@ internal class PersistMigratedMessagesUseCaseImpl( is MessageContent.Composite -> MessageEntity.Visibility.VISIBLE is MessageContent.ButtonAction -> MessageEntity.Visibility.HIDDEN is MessageContent.ButtonActionConfirmation -> MessageEntity.Visibility.HIDDEN + is MessageContent.Location -> MessageEntity.Visibility.VISIBLE } } diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/sync/receiver/conversation/message/ApplicationMessageHandler.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/sync/receiver/conversation/message/ApplicationMessageHandler.kt index 390ed6c8c42..4980d8e5799 100644 --- a/logic/src/commonMain/kotlin/com/wire/kalium/logic/sync/receiver/conversation/message/ApplicationMessageHandler.kt +++ b/logic/src/commonMain/kotlin/com/wire/kalium/logic/sync/receiver/conversation/message/ApplicationMessageHandler.kt @@ -114,6 +114,7 @@ internal class ApplicationMessageHandlerImpl( is MessageContent.LastRead -> Message.Visibility.HIDDEN is MessageContent.Cleared -> Message.Visibility.HIDDEN is MessageContent.Composite -> Message.Visibility.VISIBLE + is MessageContent.Location -> Message.Visibility.VISIBLE } val message = Message.Regular( id = content.messageUid, @@ -233,6 +234,7 @@ internal class ApplicationMessageHandlerImpl( } is MessageContent.Composite -> persistMessage(message) + is MessageContent.Location -> persistMessage(message) } } diff --git a/persistence/src/commonMain/db_user/com/wire/kalium/persistence/MessageDetailsView.sq b/persistence/src/commonMain/db_user/com/wire/kalium/persistence/MessageDetailsView.sq index 167a50796d3..0f2e3409f0f 100644 --- a/persistence/src/commonMain/db_user/com/wire/kalium/persistence/MessageDetailsView.sq +++ b/persistence/src/commonMain/db_user/com/wire/kalium/persistence/MessageDetailsView.sq @@ -129,7 +129,11 @@ IFNULL( ) AS buttonsJson, FederationTerminatedContent.domain_list AS federationDomainList, FederationTerminatedContent.federation_type AS federationType, -ConversationProtocolChangedContent.protocol AS conversationProtocolChanged +ConversationProtocolChangedContent.protocol AS conversationProtocolChanged, +ConversationLocationContent.latitude AS latitude, +ConversationLocationContent.longitude AS longitude, +ConversationLocationContent.name AS locationName, +ConversationLocationContent.zoom AS locationZoom FROM Message JOIN User ON Message.sender_user_id = User.qualified_id @@ -155,5 +159,6 @@ LEFT JOIN MessageConversationReceiptModeChangedContent AS ConversationReceiptMod LEFT JOIN MessageConversationTimerChangedContent AS ConversationTimerChangedContent ON Message.id = ConversationTimerChangedContent.message_id AND Message.conversation_id = ConversationTimerChangedContent.conversation_id LEFT JOIN MessageFederationTerminatedContent AS FederationTerminatedContent ON Message.id = FederationTerminatedContent.message_id AND Message.conversation_id = FederationTerminatedContent.conversation_id LEFT JOIN MessageConversationProtocolChangedContent AS ConversationProtocolChangedContent ON Message.id = ConversationProtocolChangedContent.message_id AND Message.conversation_id = ConversationProtocolChangedContent.conversation_id +LEFT JOIN MessageConversationLocationContent AS ConversationLocationContent ON Message.id = ConversationLocationContent.message_id AND Message.conversation_id = ConversationLocationContent.conversation_id LEFT JOIN SelfUser; -- TODO: Remove IFNULL functions above if we can force SQLDelight to not unpack as notnull diff --git a/persistence/src/commonMain/db_user/com/wire/kalium/persistence/MessagePreview.sq b/persistence/src/commonMain/db_user/com/wire/kalium/persistence/MessagePreview.sq index b47aa12f031..95972d92701 100644 --- a/persistence/src/commonMain/db_user/com/wire/kalium/persistence/MessagePreview.sq +++ b/persistence/src/commonMain/db_user/com/wire/kalium/persistence/MessagePreview.sq @@ -51,7 +51,7 @@ WHERE id IN ( SELECT id FROM Message WHERE Message.visibility = 'VISIBLE' AND - Message.content_type IN ('TEXT', 'ASSET', 'KNOCK', 'MISSED_CALL', 'CONVERSATION_RENAMED', 'MEMBER_CHANGE', 'COMPOSITE', 'CONVERSATION_DEGRADED_MLS', 'CONVERSATION_DEGRADED_PROTEUS', 'CONVERSATION_VERIFIED_MLS', 'CONVERSATION_VERIFIED_PROTEUS') + Message.content_type IN ('TEXT', 'ASSET', 'KNOCK', 'MISSED_CALL', 'CONVERSATION_RENAMED', 'MEMBER_CHANGE', 'COMPOSITE', 'CONVERSATION_DEGRADED_MLS', 'CONVERSATION_DEGRADED_PROTEUS', 'CONVERSATION_VERIFIED_MLS', 'CONVERSATION_VERIFIED_PROTEUS', 'LOCATION') GROUP BY Message.conversation_id HAVING Message.creation_date = MAX(Message.creation_date) ); diff --git a/persistence/src/commonMain/db_user/com/wire/kalium/persistence/Messages.sq b/persistence/src/commonMain/db_user/com/wire/kalium/persistence/Messages.sq index 3a509fccd5a..c6656c73eb5 100644 --- a/persistence/src/commonMain/db_user/com/wire/kalium/persistence/Messages.sq +++ b/persistence/src/commonMain/db_user/com/wire/kalium/persistence/Messages.sq @@ -1,11 +1,12 @@ -import com.wire.kalium.persistence.dao.conversation.ConversationEntity; import com.wire.kalium.persistence.dao.QualifiedIDEntity; +import com.wire.kalium.persistence.dao.conversation.ConversationEntity; import com.wire.kalium.persistence.dao.message.MessageEntity.ContentType; import com.wire.kalium.persistence.dao.message.MessageEntity.FederationType; import com.wire.kalium.persistence.dao.message.MessageEntity.MemberChangeType; import com.wire.kalium.persistence.dao.message.MessageEntity; import com.wire.kalium.persistence.dao.message.RecipientFailureTypeEntity; import kotlin.Boolean; +import kotlin.Float; import kotlin.Int; import kotlin.String; import kotlin.collections.List; @@ -221,6 +222,18 @@ CREATE TABLE MessageConversationProtocolChangedContent ( PRIMARY KEY (message_id, conversation_id) ); +CREATE TABLE MessageConversationLocationContent ( + message_id TEXT NOT NULL, + conversation_id TEXT AS QualifiedIDEntity NOT NULL, + latitude REAL AS Float NOT NULL, + longitude REAL AS Float NOT NULL, + name TEXT, + zoom INTEGER AS Int, + + FOREIGN KEY (message_id, conversation_id) REFERENCES Message(id, conversation_id) ON DELETE CASCADE ON UPDATE CASCADE, + PRIMARY KEY (message_id, conversation_id) +); + needsToBeNotified: WITH targetMessage(isSelfMessage, isMentioningSelfUser, isQuotingSelfUser, mutedStatus) AS ( SELECT isSelfMessage, @@ -383,6 +396,10 @@ insertConversationProtocolChanged: INSERT OR IGNORE INTO MessageConversationProtocolChangedContent(message_id, conversation_id, protocol) VALUES(?, ?, ?); +insertLocationMessageContent: +INSERT OR IGNORE INTO MessageConversationLocationContent(message_id, conversation_id, latitude, longitude, name, zoom) +VALUES(?, ?, ?, ?, ?, ?); + updateMessageStatus: UPDATE Message SET status = ? diff --git a/persistence/src/commonMain/db_user/com/wire/kalium/persistence/Notification.sq b/persistence/src/commonMain/db_user/com/wire/kalium/persistence/Notification.sq index 363128645a0..4dd4061e256 100644 --- a/persistence/src/commonMain/db_user/com/wire/kalium/persistence/Notification.sq +++ b/persistence/src/commonMain/db_user/com/wire/kalium/persistence/Notification.sq @@ -16,7 +16,7 @@ SELECT Conversation.type AS conversationType FROM Message LEFT JOIN SelfUser -JOIN User ON Message.sender_user_id = User.qualified_id AND Message.content_type IN ('TEXT', 'RESTRICTED_ASSET', 'ASSET', 'KNOCK', 'MISSED_CALL') +JOIN User ON Message.sender_user_id = User.qualified_id AND Message.content_type IN ('TEXT', 'RESTRICTED_ASSET', 'ASSET', 'KNOCK', 'MISSED_CALL', 'LOCATION') JOIN Conversation AS Conversation ON Message.conversation_id == Conversation.qualified_id AND (Message.creation_date > IFNULL(Conversation.last_notified_date, 0)) LEFT JOIN MessageAssetContent AS AssetContent ON Message.id = AssetContent.message_id AND Message.conversation_id = AssetContent.conversation_id LEFT JOIN MessageTextContent AS TextContent ON Message.id = TextContent.message_id AND Message.conversation_id = TextContent.conversation_id diff --git a/persistence/src/commonMain/db_user/migrations/68.sqm b/persistence/src/commonMain/db_user/migrations/68.sqm new file mode 100644 index 00000000000..f635b3c6dd7 --- /dev/null +++ b/persistence/src/commonMain/db_user/migrations/68.sqm @@ -0,0 +1,171 @@ +import com.wire.kalium.persistence.dao.QualifiedIDEntity; +import kotlin.Float; +import kotlin.Int; + +CREATE TABLE MessageConversationLocationContent ( + message_id TEXT NOT NULL, + conversation_id TEXT AS QualifiedIDEntity NOT NULL, + latitude REAL AS Float NOT NULL, + longitude REAL AS Float NOT NULL, + name TEXT, + zoom INTEGER AS Int, + + FOREIGN KEY (message_id, conversation_id) REFERENCES Message(id, conversation_id) ON DELETE CASCADE ON UPDATE CASCADE, + PRIMARY KEY (message_id, conversation_id) +); + +DROP VIEW IF EXISTS MessageDetailsView; + +CREATE VIEW IF NOT EXISTS MessageDetailsView +AS SELECT +Message.id AS id, +Message.conversation_id AS conversationId, +Message.content_type AS contentType, +Message.creation_date AS date, +Message.sender_user_id AS senderUserId, +Message.sender_client_id AS senderClientId, +Message.status AS status, +Message.last_edit_date AS lastEditTimestamp, +Message.visibility AS visibility, +Message.expects_read_confirmation AS expectsReadConfirmation, +Message.expire_after_millis AS expireAfterMillis, +Message.self_deletion_start_date AS selfDeletionStartDate, +IFNULL ((SELECT COUNT (*) FROM Receipt WHERE message_id = Message.id AND type = "READ"), 0) AS readCount, +User.name AS senderName, +User.handle AS senderHandle, +User.email AS senderEmail, +User.phone AS senderPhone, +User.accent_id AS senderAccentId, +User.team AS senderTeamId, +User.connection_status AS senderConnectionStatus, +User.preview_asset_id AS senderPreviewAssetId, +User.complete_asset_id AS senderCompleteAssetId, +User.user_availability_status AS senderAvailabilityStatus, +User.user_type AS senderUserType, +User.bot_service AS senderBotService, +User.deleted AS senderIsDeleted, +(Message.sender_user_id == SelfUser.id) AS isSelfMessage, +TextContent.text_body AS text, +TextContent.is_quoting_self AS isQuotingSelfUser, +AssetContent.asset_size AS assetSize, +AssetContent.asset_name AS assetName, +AssetContent.asset_mime_type AS assetMimeType, +AssetContent.asset_upload_status AS assetUploadStatus, +AssetContent.asset_download_status AS assetDownloadStatus, +AssetContent.asset_otr_key AS assetOtrKey, +AssetContent.asset_sha256 AS assetSha256, +AssetContent.asset_id AS assetId, +AssetContent.asset_token AS assetToken, +AssetContent.asset_domain AS assetDomain, +AssetContent.asset_encryption_algorithm AS assetEncryptionAlgorithm, +AssetContent.asset_width AS assetWidth, +AssetContent.asset_height AS assetHeight, +AssetContent.asset_duration_ms AS assetDuration, +AssetContent.asset_normalized_loudness AS assetNormalizedLoudness, +MissedCallContent.caller_id AS callerId, +MemberChangeContent.member_change_list AS memberChangeList, +MemberChangeContent.member_change_type AS memberChangeType, +UnknownContent.unknown_type_name AS unknownContentTypeName, +UnknownContent.unknown_encoded_data AS unknownContentData, +RestrictedAssetContent.asset_mime_type AS restrictedAssetMimeType, +RestrictedAssetContent.asset_size AS restrictedAssetSize, +RestrictedAssetContent.asset_name AS restrictedAssetName, +FailedToDecryptContent.unknown_encoded_data AS failedToDecryptData, +FailedToDecryptContent.is_decryption_resolved AS isDecryptionResolved, +ConversationNameChangedContent.conversation_name AS conversationName, +'{' || IFNULL( + (SELECT GROUP_CONCAT('"' || emoji || '":' || count) + FROM ( + SELECT COUNT(*) count, Reaction.emoji emoji + FROM Reaction + WHERE Reaction.message_id = Message.id + AND Reaction.conversation_id = Message.conversation_id + GROUP BY Reaction.emoji + )), + '') +|| '}' AS allReactionsJson, +IFNULL( + (SELECT '[' || GROUP_CONCAT('"' || Reaction.emoji || '"') || ']' + FROM Reaction + WHERE Reaction.message_id = Message.id + AND Reaction.conversation_id = Message.conversation_id + AND Reaction.sender_id = SelfUser.id + ), + '[]' +) AS selfReactionsJson, +IFNULL( + (SELECT '[' || GROUP_CONCAT( + '{"start":' || start || ', "length":' || length || + ', "userId":{"value":"' || replace(substr(user_id, 0, instr(user_id, '@')), '@', '') || '"' || + ',"domain":"' || replace(substr(user_id, instr(user_id, '@')+1, length(user_id)), '@', '') || '"' || + '}' || '}') || ']' + FROM MessageMention + WHERE MessageMention.message_id = Message.id + AND MessageMention.conversation_id = Message.conversation_id + ), + '[]' +) AS mentions, +TextContent.quoted_message_id AS quotedMessageId, +QuotedMessage.sender_user_id AS quotedSenderId, +TextContent.is_quote_verified AS isQuoteVerified, +QuotedSender.name AS quotedSenderName, +QuotedMessage.creation_date AS quotedMessageDateTime, +QuotedMessage.last_edit_date AS quotedMessageEditTimestamp, +QuotedMessage.visibility AS quotedMessageVisibility, +QuotedMessage.content_type AS quotedMessageContentType, +QuotedTextContent.text_body AS quotedTextBody, +QuotedAssetContent.asset_mime_type AS quotedAssetMimeType, +QuotedAssetContent.asset_name AS quotedAssetName, + +NewConversationReceiptMode.receipt_mode AS newConversationReceiptMode, + +ConversationReceiptModeChanged.receipt_mode AS conversationReceiptModeChanged, +ConversationTimerChangedContent.message_timer AS messageTimerChanged, +FailedRecipientsWithNoClients.recipient_failure_list AS recipientsFailedWithNoClientsList, +FailedRecipientsDeliveryFailed.recipient_failure_list AS recipientsFailedDeliveryList, + +IFNULL( + (SELECT '[' || + GROUP_CONCAT('{"text":"' || text || '", "id":"' || id || '""is_selected":' || is_selected || '}') + || ']' + FROM ButtonContent + WHERE ButtonContent.message_id = Message.id + AND ButtonContent.conversation_id = Message.conversation_id + ), + '[]' +) AS buttonsJson, +FederationTerminatedContent.domain_list AS federationDomainList, +FederationTerminatedContent.federation_type AS federationType, +ConversationProtocolChangedContent.protocol AS conversationProtocolChanged, +ConversationLocationContent.latitude AS latitude, +ConversationLocationContent.longitude AS longitude, +ConversationLocationContent.name AS locationName, +ConversationLocationContent.zoom AS locationZoom + +FROM Message +JOIN User ON Message.sender_user_id = User.qualified_id +LEFT JOIN MessageTextContent AS TextContent ON Message.id = TextContent.message_id AND Message.conversation_id = TextContent.conversation_id +LEFT JOIN MessageAssetContent AS AssetContent ON Message.id = AssetContent.message_id AND Message.conversation_id = AssetContent.conversation_id +LEFT JOIN MessageMissedCallContent AS MissedCallContent ON Message.id = MissedCallContent.message_id AND Message.conversation_id = MissedCallContent.conversation_id +LEFT JOIN MessageMemberChangeContent AS MemberChangeContent ON Message.id = MemberChangeContent.message_id AND Message.conversation_id = MemberChangeContent.conversation_id +LEFT JOIN MessageUnknownContent AS UnknownContent ON Message.id = UnknownContent.message_id AND Message.conversation_id = UnknownContent.conversation_id +LEFT JOIN MessageRestrictedAssetContent AS RestrictedAssetContent ON Message.id = RestrictedAssetContent.message_id AND RestrictedAssetContent.conversation_id = RestrictedAssetContent.conversation_id +LEFT JOIN MessageFailedToDecryptContent AS FailedToDecryptContent ON Message.id = FailedToDecryptContent.message_id AND Message.conversation_id = FailedToDecryptContent.conversation_id +LEFT JOIN MessageConversationChangedContent AS ConversationNameChangedContent ON Message.id = ConversationNameChangedContent.message_id AND Message.conversation_id = ConversationNameChangedContent.conversation_id +LEFT JOIN MessageRecipientFailure AS FailedRecipientsWithNoClients ON Message.id = FailedRecipientsWithNoClients.message_id AND Message.conversation_id = FailedRecipientsWithNoClients.conversation_id AND FailedRecipientsWithNoClients.recipient_failure_type = 'NO_CLIENTS_TO_DELIVER' +LEFT JOIN MessageRecipientFailure AS FailedRecipientsDeliveryFailed ON Message.id = FailedRecipientsDeliveryFailed.message_id AND Message.conversation_id = FailedRecipientsDeliveryFailed.conversation_id AND FailedRecipientsDeliveryFailed.recipient_failure_type = 'MESSAGE_DELIVERY_FAILED' + +-- joins for quoted messages +LEFT JOIN Message AS QuotedMessage ON QuotedMessage.id = TextContent.quoted_message_id AND QuotedMessage.conversation_id = TextContent.conversation_id +LEFT JOIN User AS QuotedSender ON QuotedMessage.sender_user_id = QuotedSender.qualified_id +LEFT JOIN MessageTextContent AS QuotedTextContent ON QuotedTextContent.message_id = QuotedMessage.id AND QuotedMessage.conversation_id = TextContent.conversation_id +LEFT JOIN MessageAssetContent AS QuotedAssetContent ON QuotedAssetContent.message_id = QuotedMessage.id AND QuotedMessage.conversation_id = TextContent.conversation_id +-- end joins for quoted messages +LEFT JOIN MessageNewConversationReceiptModeContent AS NewConversationReceiptMode ON Message.id = NewConversationReceiptMode.message_id AND Message.conversation_id = NewConversationReceiptMode.conversation_id +LEFT JOIN MessageConversationReceiptModeChangedContent AS ConversationReceiptModeChanged ON Message.id = ConversationReceiptModeChanged.message_id AND Message.conversation_id = ConversationReceiptModeChanged.conversation_id +LEFT JOIN MessageConversationTimerChangedContent AS ConversationTimerChangedContent ON Message.id = ConversationTimerChangedContent.message_id AND Message.conversation_id = ConversationTimerChangedContent.conversation_id +LEFT JOIN MessageFederationTerminatedContent AS FederationTerminatedContent ON Message.id = FederationTerminatedContent.message_id AND Message.conversation_id = FederationTerminatedContent.conversation_id +LEFT JOIN MessageConversationProtocolChangedContent AS ConversationProtocolChangedContent ON Message.id = ConversationProtocolChangedContent.message_id AND Message.conversation_id = ConversationProtocolChangedContent.conversation_id +LEFT JOIN MessageConversationLocationContent AS ConversationLocationContent ON Message.id = ConversationLocationContent.message_id AND Message.conversation_id = ConversationLocationContent.conversation_id +LEFT JOIN SelfUser; +-- TODO: Remove IFNULL functions above if we can force SQLDelight to not unpack as notnull diff --git a/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/dao/message/MessageEntity.kt b/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/dao/message/MessageEntity.kt index 5d779add717..aded576d03d 100644 --- a/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/dao/message/MessageEntity.kt +++ b/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/dao/message/MessageEntity.kt @@ -198,7 +198,7 @@ sealed interface MessageEntity { NEW_CONVERSATION_RECEIPT_MODE, CONVERSATION_RECEIPT_MODE_CHANGED, HISTORY_LOST, HISTORY_LOST_PROTOCOL_CHANGED, CONVERSATION_MESSAGE_TIMER_CHANGED, CONVERSATION_CREATED, MLS_WRONG_EPOCH_WARNING, CONVERSATION_DEGRADED_MLS, CONVERSATION_DEGRADED_PROTEUS, CONVERSATION_VERIFIED_MLS, CONVERSATION_VERIFIED_PROTEUS, COMPOSITE, FEDERATION, - CONVERSATION_PROTOCOL_CHANGED, CONVERSATION_STARTED_UNVERIFIED_WARNING + CONVERSATION_PROTOCOL_CHANGED, CONVERSATION_STARTED_UNVERIFIED_WARNING, LOCATION } enum class MemberChangeType { @@ -293,6 +293,12 @@ sealed class MessageEntityContent { ) : Regular() data class Knock(val hotKnock: Boolean) : Regular() + data class Location( + val latitude: Float, + val longitude: Float, + val name: String? = null, + val zoom: Int? = null, + ) : Regular() data class Unknown( val typeName: String? = null, @@ -393,6 +399,7 @@ sealed class MessagePreviewEntityContent { data class MissedCall(val senderName: String?) : MessagePreviewEntityContent() data class Knock(val senderName: String?) : MessagePreviewEntityContent() + data class Location(val senderName: String?) : MessagePreviewEntityContent() data class MembersAdded( val senderName: String?, diff --git a/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/dao/message/MessageInsertExtension.kt b/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/dao/message/MessageInsertExtension.kt index 9d333835d63..254dddb4caa 100644 --- a/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/dao/message/MessageInsertExtension.kt +++ b/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/dao/message/MessageInsertExtension.kt @@ -265,6 +265,15 @@ internal class MessageInsertExtensionImpl( is MessageEntityContent.ConversationStartedUnverifiedWarning -> { /* no-op */ } + + is MessageEntityContent.Location -> messagesQueries.insertLocationMessageContent( + message_id = message.id, + conversation_id = message.conversationId, + latitude = content.latitude, + longitude = content.longitude, + name = content.name, + zoom = content.zoom + ) } } @@ -289,6 +298,7 @@ internal class MessageInsertExtensionImpl( is MessageEntityContent.Asset, is MessageEntityContent.RestrictedAsset, is MessageEntityContent.Composite, + is MessageEntityContent.Location, is MessageEntityContent.FailedDecryption -> unreadEventsQueries.insertEvent( message.id, UnreadEventTypeEntity.MESSAGE, @@ -414,5 +424,6 @@ internal class MessageInsertExtensionImpl( MessageEntityContent.ConversationVerifiedProteus -> MessageEntity.ContentType.CONVERSATION_VERIFIED_PROTEUS is MessageEntityContent.ConversationProtocolChanged -> MessageEntity.ContentType.CONVERSATION_PROTOCOL_CHANGED is MessageEntityContent.ConversationStartedUnverifiedWarning -> MessageEntity.ContentType.CONVERSATION_STARTED_UNVERIFIED_WARNING + is MessageEntityContent.Location -> MessageEntity.ContentType.LOCATION } } diff --git a/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/dao/message/MessageMapper.kt b/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/dao/message/MessageMapper.kt index c62974fcd26..1c9463b2d59 100644 --- a/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/dao/message/MessageMapper.kt +++ b/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/dao/message/MessageMapper.kt @@ -189,7 +189,7 @@ object MessageMapper { ) MessageEntity.ContentType.REMOVED_FROM_TEAM -> MessagePreviewEntityContent.TeamMemberRemoved(userName = senderName) - + MessageEntity.ContentType.LOCATION -> MessagePreviewEntityContent.Location(senderName = senderName) MessageEntity.ContentType.FEDERATION -> MessagePreviewEntityContent.Unknown MessageEntity.ContentType.NEW_CONVERSATION_RECEIPT_MODE -> MessagePreviewEntityContent.Unknown MessageEntity.ContentType.CONVERSATION_RECEIPT_MODE_CHANGED -> MessagePreviewEntityContent.Unknown @@ -477,7 +477,11 @@ object MessageMapper { buttonsJson: String, federationDomainList: List?, federationType: MessageEntity.FederationType?, - conversationProtocolChanged: ConversationEntity.Protocol? + conversationProtocolChanged: ConversationEntity.Protocol?, + latitude: Float?, + longitude: Float?, + locationName: String?, + locationZoom: Int? ): MessageEntity { // If message hsa been deleted, we don't care about the content. Also most of their internal content is null anyways val content = if (visibility == MessageEntity.Visibility.DELETED) { @@ -612,6 +616,12 @@ object MessageMapper { ) MessageEntity.ContentType.CONVERSATION_STARTED_UNVERIFIED_WARNING -> MessageEntityContent.ConversationStartedUnverifiedWarning + MessageEntity.ContentType.LOCATION -> MessageEntityContent.Location( + latitude = latitude.requireField("latitude"), + longitude = longitude.requireField("longitude"), + locationName, + locationZoom + ) } return createMessageEntity( diff --git a/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/db/TableMapper.kt b/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/db/TableMapper.kt index 7f39ed84953..a924e0b571a 100644 --- a/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/db/TableMapper.kt +++ b/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/db/TableMapper.kt @@ -19,6 +19,7 @@ package com.wire.kalium.persistence.db import app.cash.sqldelight.EnumColumnAdapter +import app.cash.sqldelight.adapter.primitive.FloatColumnAdapter import app.cash.sqldelight.adapter.primitive.IntColumnAdapter import com.wire.kalium.persistence.Call import com.wire.kalium.persistence.Client @@ -28,6 +29,7 @@ import com.wire.kalium.persistence.Member import com.wire.kalium.persistence.Message import com.wire.kalium.persistence.MessageAssetContent import com.wire.kalium.persistence.MessageConversationChangedContent +import com.wire.kalium.persistence.MessageConversationLocationContent import com.wire.kalium.persistence.MessageConversationProtocolChangedContent import com.wire.kalium.persistence.MessageConversationReceiptModeChangedContent import com.wire.kalium.persistence.MessageConversationTimerChangedContent @@ -58,9 +60,9 @@ import com.wire.kalium.persistence.adapter.MemberRoleAdapter import com.wire.kalium.persistence.adapter.QualifiedIDAdapter import com.wire.kalium.persistence.adapter.QualifiedIDListAdapter import com.wire.kalium.persistence.adapter.ServiceTagListAdapter -import com.wire.kalium.persistence.content.ButtonContent import com.wire.kalium.persistence.adapter.StringListAdapter import com.wire.kalium.persistence.adapter.SupportedProtocolSetAdapter +import com.wire.kalium.persistence.content.ButtonContent internal object TableMapper { val callAdapter = Call.Adapter( @@ -224,4 +226,11 @@ internal object TableMapper { val buttonContentAdapter = ButtonContent.Adapter( conversation_idAdapter = QualifiedIDAdapter ) + + val messageConversationLocationContentAdapter = MessageConversationLocationContent.Adapter( + conversation_idAdapter = QualifiedIDAdapter, + latitudeAdapter = FloatColumnAdapter, + longitudeAdapter = FloatColumnAdapter, + zoomAdapter = IntColumnAdapter + ) } diff --git a/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/db/UserDatabaseBuilder.kt b/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/db/UserDatabaseBuilder.kt index 268d441731e..74360200a87 100644 --- a/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/db/UserDatabaseBuilder.kt +++ b/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/db/UserDatabaseBuilder.kt @@ -153,7 +153,8 @@ class UserDatabaseBuilder internal constructor( MessageRecipientFailureAdapter = TableMapper.messageRecipientFailureAdapter, ButtonContentAdapter = TableMapper.buttonContentAdapter, MessageFederationTerminatedContentAdapter = TableMapper.messageFederationTerminatedContentAdapter, - MessageConversationProtocolChangedContentAdapter = TableMapper.messageConversationProtocolChangedContentAdapter + MessageConversationProtocolChangedContentAdapter = TableMapper.messageConversationProtocolChangedContentAdapter, + MessageConversationLocationContentAdapter = TableMapper.messageConversationLocationContentAdapter, ) init { diff --git a/persistence/src/commonTest/kotlin/com/wire/kalium/persistence/dao/message/MessageMapperTest.kt b/persistence/src/commonTest/kotlin/com/wire/kalium/persistence/dao/message/MessageMapperTest.kt index 982682e8f77..742e75fe810 100644 --- a/persistence/src/commonTest/kotlin/com/wire/kalium/persistence/dao/message/MessageMapperTest.kt +++ b/persistence/src/commonTest/kotlin/com/wire/kalium/persistence/dao/message/MessageMapperTest.kt @@ -160,7 +160,11 @@ class MessageMapperTest { buttonsJson: String = "[]", federationDomainList: List? = null, federationType: MessageEntity.FederationType? = null, - conversationProtocolChanged: ConversationEntity.Protocol? = null + conversationProtocolChanged: ConversationEntity.Protocol? = null, + latitude: Float? = null, + longitude: Float? = null, + locationName: String? = null, + locationZoom: Int? = null ): MessageEntity { return MessageMapper.toEntityMessageFromView( id, @@ -240,7 +244,11 @@ class MessageMapperTest { buttonsJson, federationDomainList, federationType, - conversationProtocolChanged + conversationProtocolChanged, + latitude, + longitude, + locationName, + locationZoom ) }