From 93669baf4c2bddfe826fd70b2840e7c89b3603cd Mon Sep 17 00:00:00 2001 From: Mohamad Jaara Date: Thu, 13 Jul 2023 12:11:19 +0200 Subject: [PATCH 1/5] feat: add timer to all message types (#1920) --- .../com/wire/android/mapper/MessageMapper.kt | 4 +- .../home/conversations/ConversationScreen.kt | 9 +-- .../conversations/MessageComposerViewModel.kt | 2 +- .../home/conversations/MessageExpiration.kt | 58 +++++++++---------- .../ui/home/conversations/MessageItem.kt | 2 +- .../home/conversations/SystemMessageItem.kt | 15 ++++- .../ui/home/conversations/mock/Mock.kt | 44 ++++++++++---- .../model/MessageTypesPreview.kt | 19 ++++-- .../ui/home/conversations/model/UIMessage.kt | 33 ++++++----- .../com/wire/android/framework/TestMessage.kt | 18 ++++-- .../wire/android/mapper/MessageMapperTest.kt | 34 ++++++++--- .../MessageComposerViewModelArrangement.kt | 6 +- kalium | 2 +- 13 files changed, 163 insertions(+), 83 deletions(-) diff --git a/app/src/main/kotlin/com/wire/android/mapper/MessageMapper.kt b/app/src/main/kotlin/com/wire/android/mapper/MessageMapper.kt index 8db02804823..dd02a8b3cf1 100644 --- a/app/src/main/kotlin/com/wire/android/mapper/MessageMapper.kt +++ b/app/src/main/kotlin/com/wire/android/mapper/MessageMapper.kt @@ -145,8 +145,8 @@ class MessageMapper @Inject constructor( } } - private fun provideExpirationData(message: Message.Standalone): ExpirationStatus { - val expirationData = (message as? Message.Regular)?.expirationData + private fun provideExpirationData(message: Message): ExpirationStatus { + val expirationData = message.expirationData return if (expirationData == null) { ExpirationStatus.NotExpirable } else { diff --git a/app/src/main/kotlin/com/wire/android/ui/home/conversations/ConversationScreen.kt b/app/src/main/kotlin/com/wire/android/ui/home/conversations/ConversationScreen.kt index c1249404a5e..d3289bf9052 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/conversations/ConversationScreen.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/conversations/ConversationScreen.kt @@ -304,7 +304,7 @@ private fun ConversationScreen( composerMessages: SharedFlow, conversationMessages: SharedFlow, conversationMessagesViewModel: ConversationMessagesViewModel, - onSelfDeletingMessageRead: (UIMessage.Regular) -> Unit, + onSelfDeletingMessageRead: (UIMessage) -> Unit, onNewSelfDeletingMessagesStatus: (SelfDeletionTimer) -> Unit, tempWritableImageUri: Uri?, tempWritableVideoUri: Uri?, @@ -451,7 +451,7 @@ private fun ConversationScreenContent( onOpenProfile: (String) -> Unit, onUpdateConversationReadDate: (String) -> Unit, onShowEditingOptions: (UIMessage.Regular) -> Unit, - onSelfDeletingMessageRead: (UIMessage.Regular) -> Unit, + onSelfDeletingMessageRead: (UIMessage) -> Unit, conversationDetailsData: ConversationDetailsData, onFailedMessageRetryClicked: (String) -> Unit, onFailedMessageCancelClicked: (String) -> Unit, @@ -564,7 +564,7 @@ fun MessageList( onReactionClicked: (String, String) -> Unit, onResetSessionClicked: (senderUserId: UserId, clientId: String?) -> Unit, onShowEditingOption: (UIMessage.Regular) -> Unit, - onSelfDeletingMessageRead: (UIMessage.Regular) -> Unit, + onSelfDeletingMessageRead: (UIMessage) -> Unit, conversationDetailsData: ConversationDetailsData, onFailedMessageRetryClicked: (String) -> Unit, onFailedMessageCancelClicked: (String) -> Unit @@ -630,7 +630,8 @@ fun MessageList( is UIMessage.System -> SystemMessageItem( message = message, onFailedMessageCancelClicked = onFailedMessageCancelClicked, - onFailedMessageRetryClicked = onFailedMessageRetryClicked + onFailedMessageRetryClicked = onFailedMessageRetryClicked, + onSelfDeletingMessageRead = onSelfDeletingMessageRead ) } } diff --git a/app/src/main/kotlin/com/wire/android/ui/home/conversations/MessageComposerViewModel.kt b/app/src/main/kotlin/com/wire/android/ui/home/conversations/MessageComposerViewModel.kt index 9be6de959bf..a9bded1a831 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/conversations/MessageComposerViewModel.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/conversations/MessageComposerViewModel.kt @@ -431,7 +431,7 @@ class MessageComposerViewModel @Inject constructor( } } - fun startSelfDeletion(uiMessage: UIMessage.Regular) { + fun startSelfDeletion(uiMessage: UIMessage) { enqueueMessageSelfDeletion(conversationId, uiMessage.header.messageId) } diff --git a/app/src/main/kotlin/com/wire/android/ui/home/conversations/MessageExpiration.kt b/app/src/main/kotlin/com/wire/android/ui/home/conversations/MessageExpiration.kt index 5a1a74fc74c..d389975aabb 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/conversations/MessageExpiration.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/conversations/MessageExpiration.kt @@ -257,45 +257,41 @@ class SelfDeletionTimerHelper(private val context: Context) { @Composable fun startDeletionTimer( - message: UIMessage.Regular, + message: UIMessage, expirableTimer: SelfDeletionTimerHelper.SelfDeletionTimerState.Expirable, - onStartMessageSelfDeletion: (UIMessage.Regular) -> Unit + onStartMessageSelfDeletion: (UIMessage) -> Unit ) { - message.messageContent?.let { - when (val messageContent = message.messageContent) { - is UIMessageContent.AssetMessage -> startAssetDeletion( - expirableTimer = expirableTimer, - onSelfDeletingMessageRead = { onStartMessageSelfDeletion(message) }, - downloadStatus = messageContent.downloadStatus - ) + when (val messageContent = message.messageContent) { + is UIMessageContent.AssetMessage -> startAssetDeletion( + expirableTimer = expirableTimer, + onSelfDeletingMessageRead = { onStartMessageSelfDeletion(message) }, + downloadStatus = messageContent.downloadStatus + ) - is UIMessageContent.AudioAssetMessage -> startAssetDeletion( - expirableTimer = expirableTimer, - onSelfDeletingMessageRead = { onStartMessageSelfDeletion(message) }, - downloadStatus = messageContent.downloadStatus - ) + is UIMessageContent.AudioAssetMessage -> startAssetDeletion( + expirableTimer = expirableTimer, + onSelfDeletingMessageRead = { onStartMessageSelfDeletion(message) }, + downloadStatus = messageContent.downloadStatus + ) - is UIMessageContent.ImageMessage -> startAssetDeletion( - expirableTimer = expirableTimer, - onSelfDeletingMessageRead = { onStartMessageSelfDeletion(message) }, - downloadStatus = messageContent.downloadStatus - ) + is UIMessageContent.ImageMessage -> startAssetDeletion( + expirableTimer = expirableTimer, + onSelfDeletingMessageRead = { onStartMessageSelfDeletion(message) }, + downloadStatus = messageContent.downloadStatus + ) - is UIMessageContent.TextMessage -> { - LaunchedEffect(Unit) { - onStartMessageSelfDeletion(message) - } - LaunchedEffect(expirableTimer.timeLeft) { - with(expirableTimer) { - if (timeLeft != ZERO) { - delay(updateInterval()) - decreaseTimeLeft(updateInterval()) - } + else -> { + LaunchedEffect(Unit) { + onStartMessageSelfDeletion(message) + } + LaunchedEffect(expirableTimer.timeLeft) { + with(expirableTimer) { + if (timeLeft != ZERO) { + delay(updateInterval()) + decreaseTimeLeft(updateInterval()) } } } - - else -> {} } } } diff --git a/app/src/main/kotlin/com/wire/android/ui/home/conversations/MessageItem.kt b/app/src/main/kotlin/com/wire/android/ui/home/conversations/MessageItem.kt index e90a41910e3..b74bcb32799 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/conversations/MessageItem.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/conversations/MessageItem.kt @@ -101,7 +101,7 @@ fun MessageItem( onOpenProfile: (String) -> Unit, onReactionClicked: (String, String) -> Unit, onResetSessionClicked: (senderUserId: UserId, clientId: String?) -> Unit, - onSelfDeletingMessageRead: (UIMessage.Regular) -> Unit, + onSelfDeletingMessageRead: (UIMessage) -> Unit, onFailedMessageRetryClicked: (String) -> Unit = {}, onFailedMessageCancelClicked: (String) -> Unit = {} ) { diff --git a/app/src/main/kotlin/com/wire/android/ui/home/conversations/SystemMessageItem.kt b/app/src/main/kotlin/com/wire/android/ui/home/conversations/SystemMessageItem.kt index df85e3d2b1f..ce5aa4edf11 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/conversations/SystemMessageItem.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/conversations/SystemMessageItem.kt @@ -78,8 +78,21 @@ import com.wire.android.util.ui.toUIText fun SystemMessageItem( message: UIMessage.System, onFailedMessageRetryClicked: (String) -> Unit = {}, - onFailedMessageCancelClicked: (String) -> Unit = {} + onFailedMessageCancelClicked: (String) -> Unit = {}, + onSelfDeletingMessageRead: (UIMessage) -> Unit = {} ) { + val selfDeletionTimerState = rememberSelfDeletionTimer(message.header.messageStatus.expirationStatus) + if ( + selfDeletionTimerState is SelfDeletionTimerHelper.SelfDeletionTimerState.Expirable && + !message.isPending && + !message.sendingFailed + ) { + startDeletionTimer( + message = message, + expirableTimer = selfDeletionTimerState, + onStartMessageSelfDeletion = onSelfDeletingMessageRead + ) + } val fullAvatarOuterPadding = dimensions().userAvatarClickablePadding + dimensions().userAvatarStatusBorderSize Row( Modifier diff --git a/app/src/main/kotlin/com/wire/android/ui/home/conversations/mock/Mock.kt b/app/src/main/kotlin/com/wire/android/ui/home/conversations/mock/Mock.kt index 8ae530903d1..c53d47abf11 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/conversations/mock/Mock.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/conversations/mock/Mock.kt @@ -31,6 +31,7 @@ import coil.request.ImageResult import com.wire.android.model.ImageAsset import com.wire.android.model.ImageAsset.UserAvatarAsset import com.wire.android.model.UserAvatarData +import com.wire.android.ui.home.conversations.model.ExpirationStatus import com.wire.android.ui.home.conversations.model.MessageBody import com.wire.android.ui.home.conversations.model.MessageEditStatus import com.wire.android.ui.home.conversations.model.MessageFlowStatus @@ -62,7 +63,10 @@ val mockHeader = MessageHeader( membership = Membership.Guest, isLegalHold = true, messageTime = MessageTime("12.23pm"), - messageStatus = MessageStatus(flowStatus = MessageFlowStatus.Sent), + messageStatus = MessageStatus( + flowStatus = MessageFlowStatus.Sent, + expirationStatus = ExpirationStatus.NotExpirable + ), messageId = "", connectionState = ConnectionState.ACCEPTED, isSenderDeleted = false, @@ -117,7 +121,10 @@ fun mockAssetMessage(uploadStatus: Message.UploadStatus = Message.UploadStatus.U membership = Membership.Guest, isLegalHold = true, messageTime = MessageTime("12.23pm"), - messageStatus = MessageStatus(flowStatus = MessageFlowStatus.Sent), + messageStatus = MessageStatus( + flowStatus = MessageFlowStatus.Sent, + expirationStatus = ExpirationStatus.NotExpirable + ), messageId = "", connectionState = ConnectionState.ACCEPTED, isSenderDeleted = false, @@ -148,7 +155,10 @@ fun mockedImg( @Suppress("MagicNumber") fun mockedImageUIMessage( uploadStatus: Message.UploadStatus = Message.UploadStatus.UPLOADED, - messageStatus: MessageStatus = MessageStatus(flowStatus = MessageFlowStatus.Sent), + messageStatus: MessageStatus = MessageStatus( + flowStatus = MessageFlowStatus.Sent, + expirationStatus = ExpirationStatus.NotExpirable + ) ) = UIMessage.Regular( userAvatarData = UserAvatarData(null, UserAvailabilityStatus.AVAILABLE), header = MessageHeader( @@ -176,7 +186,10 @@ fun getMockedMessages(): List = listOf( membership = Membership.Guest, isLegalHold = true, messageTime = MessageTime("12.23pm"), - messageStatus = MessageStatus(flowStatus = MessageFlowStatus.Sent), + messageStatus = MessageStatus( + flowStatus = MessageFlowStatus.Sent, + expirationStatus = ExpirationStatus.NotExpirable + ), messageId = "1", connectionState = ConnectionState.ACCEPTED, isSenderDeleted = false, @@ -202,7 +215,10 @@ fun getMockedMessages(): List = listOf( membership = Membership.Guest, isLegalHold = true, messageTime = MessageTime("12.23pm"), - messageStatus = MessageStatus(flowStatus = MessageFlowStatus.Delivered, isDeleted = true), + messageStatus = MessageStatus( + flowStatus = MessageFlowStatus.Delivered, isDeleted = true, + expirationStatus = ExpirationStatus.NotExpirable + ), messageId = "2", connectionState = ConnectionState.ACCEPTED, isSenderDeleted = false, @@ -221,7 +237,8 @@ fun getMockedMessages(): List = listOf( messageTime = MessageTime("12.23pm"), messageStatus = MessageStatus( flowStatus = MessageFlowStatus.Sent, - editStatus = MessageEditStatus.Edited("May 31, 2022 12.24pm") + editStatus = MessageEditStatus.Edited("May 31, 2022 12.24pm"), + expirationStatus = ExpirationStatus.NotExpirable ), messageId = "3", connectionState = ConnectionState.ACCEPTED, @@ -241,7 +258,8 @@ fun getMockedMessages(): List = listOf( messageTime = MessageTime("12.23pm"), messageStatus = MessageStatus( flowStatus = MessageFlowStatus.Sent, - editStatus = MessageEditStatus.Edited("May 31, 2022 12.24pm") + editStatus = MessageEditStatus.Edited("May 31, 2022 12.24pm"), + expirationStatus = ExpirationStatus.NotExpirable ), messageId = "4", connectionState = ConnectionState.ACCEPTED, @@ -259,7 +277,11 @@ fun getMockedMessages(): List = listOf( membership = Membership.External, isLegalHold = false, messageTime = MessageTime("12.23pm"), - messageStatus = MessageStatus(flowStatus = MessageFlowStatus.Delivered, isDeleted = true), + messageStatus = MessageStatus( + flowStatus = MessageFlowStatus.Delivered, + isDeleted = true, + expirationStatus = ExpirationStatus.NotExpirable + ), messageId = "5", connectionState = ConnectionState.ACCEPTED, isSenderDeleted = false, @@ -287,7 +309,8 @@ fun getMockedMessages(): List = listOf( messageTime = MessageTime("12.23pm"), messageStatus = MessageStatus( flowStatus = MessageFlowStatus.Sent, - editStatus = MessageEditStatus.Edited("May 31, 2022 12.24pm") + editStatus = MessageEditStatus.Edited("May 31, 2022 12.24pm"), + expirationStatus = ExpirationStatus.NotExpirable ), messageId = "6", connectionState = ConnectionState.ACCEPTED, @@ -307,7 +330,8 @@ fun getMockedMessages(): List = listOf( messageTime = MessageTime("12.23pm"), messageStatus = MessageStatus( flowStatus = MessageFlowStatus.Sent, - editStatus = MessageEditStatus.Edited("May 31, 2022 12.24pm") + editStatus = MessageEditStatus.Edited("May 31, 2022 12.24pm"), + expirationStatus = ExpirationStatus.NotExpirable ), messageId = "7", connectionState = ConnectionState.ACCEPTED, diff --git a/app/src/main/kotlin/com/wire/android/ui/home/conversations/model/MessageTypesPreview.kt b/app/src/main/kotlin/com/wire/android/ui/home/conversations/model/MessageTypesPreview.kt index f7ac3927b07..1adc4cc8f4c 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/conversations/model/MessageTypesPreview.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/conversations/model/MessageTypesPreview.kt @@ -149,7 +149,8 @@ fun PreviewDeletedMessage() { it.copy( header = it.header.copy( messageStatus = MessageStatus( - flowStatus = MessageFlowStatus.Delivered, isDeleted = true + flowStatus = MessageFlowStatus.Delivered, isDeleted = true, + expirationStatus = ExpirationStatus.NotExpirable ) ) ) @@ -178,7 +179,8 @@ fun PreviewFailedSendMessage() { it.copy( header = it.header.copy( messageStatus = MessageStatus( - flowStatus = MessageFlowStatus.Failure.Send.Locally(false) + flowStatus = MessageFlowStatus.Failure.Send.Locally(false), + expirationStatus = ExpirationStatus.NotExpirable ) ), messageFooter = mockFooter.copy(reactions = emptyMap(), ownReactions = emptySet()) @@ -208,7 +210,8 @@ fun PreviewFailedDecryptionMessage() { it.copy( header = it.header.copy( messageStatus = MessageStatus( - flowStatus = MessageFlowStatus.Failure.Decryption(false) + flowStatus = MessageFlowStatus.Failure.Decryption(false), + expirationStatus = ExpirationStatus.NotExpirable ) ), messageFooter = mockFooter.copy(reactions = emptyMap(), ownReactions = emptySet()) @@ -367,7 +370,10 @@ fun PreviewImageMessageFailedUpload() { MessageItem( message = mockedImageUIMessage( uploadStatus = Message.UploadStatus.FAILED_UPLOAD, - messageStatus = MessageStatus(flowStatus = MessageFlowStatus.Failure.Send.Locally(false)) + messageStatus = MessageStatus( + flowStatus = MessageFlowStatus.Failure.Send.Locally(false), + expirationStatus = ExpirationStatus.NotExpirable + ) ), audioMessagesState = emptyMap(), onLongClicked = {}, @@ -494,7 +500,10 @@ fun PreviewAggregatedMessagesWithErrorMessage() { MessageItem( message = mockMessageWithText.copy( header = mockHeader.copy( - messageStatus = MessageStatus(flowStatus = MessageFlowStatus.Failure.Send.Locally(false)) + messageStatus = MessageStatus( + flowStatus = MessageFlowStatus.Failure.Send.Locally(false), + expirationStatus = ExpirationStatus.NotExpirable + ) ) ), showAuthor = false, diff --git a/app/src/main/kotlin/com/wire/android/ui/home/conversations/model/UIMessage.kt b/app/src/main/kotlin/com/wire/android/ui/home/conversations/model/UIMessage.kt index 68e3f61b5de..7f1fb105b35 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/conversations/model/UIMessage.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/conversations/model/UIMessage.kt @@ -43,23 +43,26 @@ import kotlinx.collections.immutable.persistentMapOf import kotlinx.collections.immutable.toImmutableList import kotlin.time.Duration -sealed class UIMessage( - open val header: MessageHeader, - open val source: MessageSource -) { +sealed interface UIMessage { + val header: MessageHeader + val source: MessageSource + val messageContent: UIMessageContent? + val sendingFailed: Boolean + val decryptionFailed: Boolean + val isPending: Boolean data class Regular( override val header: MessageHeader, override val source: MessageSource, val userAvatarData: UserAvatarData, - val messageContent: UIMessageContent.Regular?, + override val messageContent: UIMessageContent.Regular?, val messageFooter: MessageFooter, - ) : UIMessage(header, source) { + ) : UIMessage { val isDeleted: Boolean = header.messageStatus.isDeleted - val sendingFailed: Boolean = header.messageStatus.flowStatus is MessageFlowStatus.Failure.Send - val decryptionFailed: Boolean = header.messageStatus.flowStatus is MessageFlowStatus.Failure.Decryption + override val sendingFailed: Boolean = header.messageStatus.flowStatus is MessageFlowStatus.Failure.Send + override val decryptionFailed: Boolean = header.messageStatus.flowStatus is MessageFlowStatus.Failure.Decryption val isAvailable: Boolean = !isDeleted && !sendingFailed && !decryptionFailed - val isPending: Boolean = header.messageStatus.flowStatus == MessageFlowStatus.Sending + override val isPending: Boolean = header.messageStatus.flowStatus == MessageFlowStatus.Sending val isMyMessage = source == MessageSource.Self val isAssetMessage = messageContent is UIMessageContent.AssetMessage || messageContent is UIMessageContent.ImageMessage @@ -70,10 +73,11 @@ sealed class UIMessage( data class System( override val header: MessageHeader, override val source: MessageSource, - val messageContent: UIMessageContent.SystemMessage - ) : UIMessage(header, source) { - val sendingFailed: Boolean = header.messageStatus.flowStatus is MessageFlowStatus.Failure.Send - val decryptionFailed: Boolean = header.messageStatus.flowStatus is MessageFlowStatus.Failure.Decryption + override val messageContent: UIMessageContent.SystemMessage + ) : UIMessage { + override val sendingFailed: Boolean = header.messageStatus.flowStatus is MessageFlowStatus.Failure.Send + override val decryptionFailed: Boolean = header.messageStatus.flowStatus is MessageFlowStatus.Failure.Decryption + override val isPending: Boolean = header.messageStatus.flowStatus == MessageFlowStatus.Sending val addingFailed: Boolean = messageContent is UIMessageContent.SystemMessage.MemberFailedToAdd val singleUserAddFailed: Boolean = messageContent is UIMessageContent.SystemMessage.MemberFailedToAdd && messageContent.usersCount == 1 @@ -155,10 +159,11 @@ sealed class MessageFlowStatus { data class Read(val count: Int) : MessageFlowStatus() } +@Stable data class MessageStatus( val flowStatus: MessageFlowStatus, + val expirationStatus: ExpirationStatus, val editStatus: MessageEditStatus = MessageEditStatus.NonEdited, - val expirationStatus: ExpirationStatus = ExpirationStatus.NotExpirable, val isDeleted: Boolean = false ) { diff --git a/app/src/test/kotlin/com/wire/android/framework/TestMessage.kt b/app/src/test/kotlin/com/wire/android/framework/TestMessage.kt index 30ff5be7981..ba2fde75f69 100644 --- a/app/src/test/kotlin/com/wire/android/framework/TestMessage.kt +++ b/app/src/test/kotlin/com/wire/android/framework/TestMessage.kt @@ -22,6 +22,7 @@ package com.wire.android.framework import com.wire.android.mapper.AssetMessageContentMetadata import com.wire.android.model.UserAvatarData +import com.wire.android.ui.home.conversations.model.ExpirationStatus import com.wire.android.ui.home.conversations.model.MessageBody import com.wire.android.ui.home.conversations.model.MessageFlowStatus import com.wire.android.ui.home.conversations.model.MessageFooter @@ -140,7 +141,8 @@ object TestMessage { conversationId = ConversationId("convo-id", "convo.domain"), date = "some-date", senderUserId = UserId("user-id", "domain"), - status = Message.Status.SENT + status = Message.Status.SENT, + expirationData = null ) val MEMBER_REMOVED_MESSAGE = Message.System( @@ -149,7 +151,8 @@ object TestMessage { conversationId = ConversationId("convo-id", "convo.domain"), date = "some-date", senderUserId = UserId("user-id", "domain"), - status = Message.Status.SENT + status = Message.Status.SENT, + expirationData = null ) val IMAGE_ASSET_MESSAGE_DATA_TEST = AssetMessageContentMetadata( AssetContent( @@ -167,7 +170,10 @@ object TestMessage { membership = Membership.Guest, isLegalHold = true, messageTime = MessageTime("12.23pm"), - messageStatus = MessageStatus(flowStatus = MessageFlowStatus.Sent), + messageStatus = MessageStatus( + flowStatus = MessageFlowStatus.Sent, + expirationStatus = ExpirationStatus.NotExpirable + ), messageId = "messageID", connectionState = null, isSenderDeleted = false, @@ -187,7 +193,8 @@ object TestMessage { conversationId = ConversationId("convo-id", "convo.domain"), date = "some-date", senderUserId = UserId("user-id", "domain"), - status = Message.Status.SENT + status = Message.Status.SENT, + expirationData = null ) val CONVERSATION_CREATED_MESSAGE = Message.System( @@ -196,7 +203,8 @@ object TestMessage { conversationId = ConversationId("convo-id", "convo.domain"), date = "some-date", senderUserId = UserId("user-id", "domain"), - status = Message.Status.SENT + status = Message.Status.SENT, + expirationData = null ) val PREVIEW = MessagePreview( diff --git a/app/src/test/kotlin/com/wire/android/mapper/MessageMapperTest.kt b/app/src/test/kotlin/com/wire/android/mapper/MessageMapperTest.kt index ff51c880d24..575f7e97b54 100644 --- a/app/src/test/kotlin/com/wire/android/mapper/MessageMapperTest.kt +++ b/app/src/test/kotlin/com/wire/android/mapper/MessageMapperTest.kt @@ -23,6 +23,7 @@ package com.wire.android.mapper import com.wire.android.config.CoroutineTestExtension import com.wire.android.framework.TestMessage import com.wire.android.framework.TestUser +import com.wire.android.ui.home.conversations.model.ExpirationStatus import com.wire.android.ui.home.conversations.model.MessageBody import com.wire.android.ui.home.conversations.model.MessageEditStatus import com.wire.android.ui.home.conversations.model.MessageFlowStatus @@ -123,7 +124,10 @@ class MessageMapperTest { time = message2.date.uiMessageDateTime(), source = MessageSource.OtherUser, membership = Membership.Guest, - status = MessageStatus(flowStatus = MessageFlowStatus.Failure.Send.Locally(false)) + status = MessageStatus( + flowStatus = MessageFlowStatus.Failure.Send.Locally(false), + expirationStatus = ExpirationStatus.NotExpirable + ) ) ) assert( @@ -132,7 +136,8 @@ class MessageMapperTest { time = message3.date.uiMessageDateTime(), status = MessageStatus( flowStatus = MessageFlowStatus.Sent, - editStatus = MessageEditStatus.Edited(now.uiMessageDateTime() ?: "") + editStatus = MessageEditStatus.Edited(now.uiMessageDateTime() ?: ""), + expirationStatus = ExpirationStatus.NotExpirable ) ) ) @@ -140,7 +145,11 @@ class MessageMapperTest { checkMessageData( uiMessage = uiMessage4, time = message4.date.uiMessageDateTime(), - status = MessageStatus(flowStatus = MessageFlowStatus.Sent, isDeleted = true) + status = MessageStatus( + flowStatus = MessageFlowStatus.Sent, + isDeleted = true, + expirationStatus = ExpirationStatus.NotExpirable + ) ) ) @@ -148,7 +157,11 @@ class MessageMapperTest { checkMessageData( uiMessage = uiMessage5, time = message5.date.uiMessageDateTime(), - status = MessageStatus(flowStatus = MessageFlowStatus.Failure.Decryption(false), isDeleted = false) + status = MessageStatus( + flowStatus = MessageFlowStatus.Failure.Decryption(false), + isDeleted = false, + expirationStatus = ExpirationStatus.NotExpirable + ) ) ) @@ -156,7 +169,11 @@ class MessageMapperTest { checkMessageData( uiMessage = uiMessage6, time = message6.date.uiMessageDateTime(), - status = MessageStatus(flowStatus = MessageFlowStatus.Failure.Decryption(true), isDeleted = false) + status = MessageStatus( + flowStatus = MessageFlowStatus.Failure.Decryption(true), + isDeleted = false, + expirationStatus = ExpirationStatus.NotExpirable + ) ) ) } @@ -166,7 +183,10 @@ class MessageMapperTest { time: String?, source: MessageSource = MessageSource.Self, membership: Membership = Membership.None, - status: MessageStatus = MessageStatus(flowStatus = MessageFlowStatus.Sent) + status: MessageStatus = MessageStatus( + flowStatus = MessageFlowStatus.Sent, + expirationStatus = ExpirationStatus.NotExpirable + ) ): Boolean { return (uiMessage?.source == source && uiMessage.header.membership == membership && uiMessage.header.messageTime.formattedDate == time @@ -229,4 +249,4 @@ private fun Message.Regular.failureToDecrypt(isDecryptionResolved: Boolean) = senderUserId = this.senderUserId, isDecryptionResolved = isDecryptionResolved ) - ) + ) diff --git a/app/src/test/kotlin/com/wire/android/ui/home/conversations/MessageComposerViewModelArrangement.kt b/app/src/test/kotlin/com/wire/android/ui/home/conversations/MessageComposerViewModelArrangement.kt index f24baecb141..57f85a74ea6 100644 --- a/app/src/test/kotlin/com/wire/android/ui/home/conversations/MessageComposerViewModelArrangement.kt +++ b/app/src/test/kotlin/com/wire/android/ui/home/conversations/MessageComposerViewModelArrangement.kt @@ -31,6 +31,7 @@ import com.wire.android.media.PingRinger import com.wire.android.model.UserAvatarData import com.wire.android.navigation.NavigationManager import com.wire.android.ui.home.conversations.model.AssetBundle +import com.wire.android.ui.home.conversations.model.ExpirationStatus import com.wire.android.ui.home.conversations.model.MessageFlowStatus import com.wire.android.ui.home.conversations.model.MessageHeader import com.wire.android.ui.home.conversations.model.MessageSource @@ -338,7 +339,10 @@ internal fun mockUITextMessage(id: String = "someId", userName: String = "mockUs every { it.username } returns UIText.DynamicString(userName) every { it.isLegalHold } returns false every { it.messageTime } returns MessageTime("") - every { it.messageStatus } returns MessageStatus(flowStatus = MessageFlowStatus.Sent) + every { it.messageStatus } returns MessageStatus( + flowStatus = MessageFlowStatus.Sent, + expirationStatus = ExpirationStatus.NotExpirable + ) } every { it.messageContent } returns null } diff --git a/kalium b/kalium index 3282688ab87..1a3db5fd180 160000 --- a/kalium +++ b/kalium @@ -1 +1 @@ -Subproject commit 3282688ab87f23c393be198764661f227776695a +Subproject commit 1a3db5fd180ac50d80baaad19168d71aa3ff95bc From 9365f1ee5d907a2fb332ff147d36c03d10b309a5 Mon Sep 17 00:00:00 2001 From: Mohamad Jaara Date: Thu, 13 Jul 2023 12:33:39 +0200 Subject: [PATCH 2/5] feat: send self deleting pings and clean up (#1929) --- .../EditSelfDeletingMessagesViewModel.kt | 4 ++-- .../details/options/GroupConversationOptions.kt | 2 +- .../home/conversations/selfdeletion/SelfDeletionMapper.kt | 2 +- .../common/SelectParticipantsButtonsRow.kt | 2 +- .../com/wire/android/ui/sharing/ImportMediaScreen.kt | 8 ++++---- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/app/src/main/kotlin/com/wire/android/ui/home/conversations/details/editselfdeletingmessages/EditSelfDeletingMessagesViewModel.kt b/app/src/main/kotlin/com/wire/android/ui/home/conversations/details/editselfdeletingmessages/EditSelfDeletingMessagesViewModel.kt index d39756a7239..6ce9cb86145 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/conversations/details/editselfdeletingmessages/EditSelfDeletingMessagesViewModel.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/conversations/details/editselfdeletingmessages/EditSelfDeletingMessagesViewModel.kt @@ -84,8 +84,8 @@ class EditSelfDeletingMessagesViewModel @Inject constructor( state = state.copy( isLoading = selfDeletingMessages.isEnforcedByTeam || !isSelfAnAdmin, isEnabled = selfDeletingMessages.isEnforcedByGroup, - remotelySelected = selfDeletingMessages.toDuration().toSelfDeletionDuration(), - locallySelected = selfDeletingMessages.toDuration().toSelfDeletionDuration() + remotelySelected = selfDeletingMessages.duration?.toSelfDeletionDuration(), + locallySelected = selfDeletingMessages.duration?.toSelfDeletionDuration() ) } } diff --git a/app/src/main/kotlin/com/wire/android/ui/home/conversations/details/options/GroupConversationOptions.kt b/app/src/main/kotlin/com/wire/android/ui/home/conversations/details/options/GroupConversationOptions.kt index f85fc3b78a1..04b2b48a27b 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/conversations/details/options/GroupConversationOptions.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/conversations/details/options/GroupConversationOptions.kt @@ -127,7 +127,7 @@ fun GroupConversationSettings( title = stringResource(id = R.string.conversation_options_self_deleting_messages_label), subtitle = stringResource(id = R.string.conversation_options_self_deleting_messages_description), trailingOnText = if (state.selfDeletionTimer.isEnforced) { - "(${state.selfDeletionTimer.toDuration().toSelfDeletionDuration().shortLabel.asString()})" + "(${state.selfDeletionTimer.duration.toSelfDeletionDuration().shortLabel.asString()})" } else { null }, diff --git a/app/src/main/kotlin/com/wire/android/ui/home/conversations/selfdeletion/SelfDeletionMapper.kt b/app/src/main/kotlin/com/wire/android/ui/home/conversations/selfdeletion/SelfDeletionMapper.kt index 1f802a99ba4..e015ce68c18 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/conversations/selfdeletion/SelfDeletionMapper.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/conversations/selfdeletion/SelfDeletionMapper.kt @@ -21,7 +21,7 @@ import com.wire.android.ui.home.messagecomposer.SelfDeletionDuration import kotlin.time.Duration object SelfDeletionMapper { - fun Duration.toSelfDeletionDuration(): SelfDeletionDuration = when (this) { + fun Duration?.toSelfDeletionDuration(): SelfDeletionDuration = when (this) { SelfDeletionDuration.TenSeconds.value -> SelfDeletionDuration.TenSeconds SelfDeletionDuration.OneMinute.value -> SelfDeletionDuration.OneMinute SelfDeletionDuration.FiveMinutes.value -> SelfDeletionDuration.FiveMinutes diff --git a/app/src/main/kotlin/com/wire/android/ui/home/newconversation/common/SelectParticipantsButtonsRow.kt b/app/src/main/kotlin/com/wire/android/ui/home/newconversation/common/SelectParticipantsButtonsRow.kt index 3781cc32c79..8d2e94aa26d 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/newconversation/common/SelectParticipantsButtonsRow.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/newconversation/common/SelectParticipantsButtonsRow.kt @@ -169,7 +169,7 @@ fun SelfDeletionTimerButton( isDisabled: Boolean, onSelfDeletionTimerClicked: () -> Unit ) { - val isSelected = selfDeletionTimer is SelfDeletionTimer.Enabled && selfDeletionTimer.userDuration != ZERO + val isSelected = selfDeletionTimer is SelfDeletionTimer.Enabled && selfDeletionTimer.duration != null Box( modifier = modifier .padding(start = dimensions().spacing16x) diff --git a/app/src/main/kotlin/com/wire/android/ui/sharing/ImportMediaScreen.kt b/app/src/main/kotlin/com/wire/android/ui/sharing/ImportMediaScreen.kt index b542ef7782b..6ddce85582c 100644 --- a/app/src/main/kotlin/com/wire/android/ui/sharing/ImportMediaScreen.kt +++ b/app/src/main/kotlin/com/wire/android/ui/sharing/ImportMediaScreen.kt @@ -62,9 +62,9 @@ import com.wire.android.util.CustomTabsHelper import com.wire.android.util.extension.getActivity import com.wire.android.util.ui.LinkText import com.wire.android.util.ui.LinkTextData +import com.wire.kalium.logic.util.isPositiveNotNull import kotlinx.collections.immutable.persistentMapOf import kotlinx.coroutines.flow.SharedFlow -import kotlin.time.Duration @Composable fun ImportMediaScreen( @@ -169,7 +169,7 @@ fun ImportMediaRegularContent(authorizedViewModel: ImportMediaAuthenticatedViewM ) MenuModalSheetLayout( menuItems = SelfDeletionMenuItems( - currentlySelected = authorizedViewModel.importMediaState.selfDeletingTimer.toDuration().toSelfDeletionDuration(), + currentlySelected = authorizedViewModel.importMediaState.selfDeletingTimer.duration.toSelfDeletionDuration(), hideEditMessageMenu = importMediaScreenState::hideBottomSheetMenu, onSelfDeletionDurationChanged = authorizedViewModel::onNewSelfDeletionTimerPicked, ), @@ -261,8 +261,8 @@ private fun ImportMediaBottomBar( importMediaScreenState: ImportMediaScreenState ) { val selfDeletionTimer = importMediaViewModel.importMediaState.selfDeletingTimer - val shortDurationLabel = selfDeletionTimer.toDuration().toSelfDeletionDuration().shortLabel - val mainButtonText = if (selfDeletionTimer.toDuration() > Duration.ZERO) { + val shortDurationLabel = selfDeletionTimer.duration.toSelfDeletionDuration().shortLabel + val mainButtonText = if (selfDeletionTimer.duration.isPositiveNotNull()) { "${stringResource(id = R.string.self_deleting_message_label)} (${shortDurationLabel.asString()})" } else { stringResource(id = R.string.import_media_send_button_title) From 6fd4b06e2dba058c87d60aadff50b996c28ac086 Mon Sep 17 00:00:00 2001 From: Mohamad Jaara Date: Thu, 13 Jul 2023 14:53:44 +0200 Subject: [PATCH 3/5] fix merge conflicts --- .../android/ui/home/conversations/ConversationScreen.kt | 2 +- .../android/ui/home/messagecomposer/MessageActions.kt | 2 +- .../state/MessageCompositionInputStateHolder.kt | 4 ++-- .../home/conversations/MessageComposerViewModelTest.kt | 9 +++++---- 4 files changed, 9 insertions(+), 8 deletions(-) diff --git a/app/src/main/kotlin/com/wire/android/ui/home/conversations/ConversationScreen.kt b/app/src/main/kotlin/com/wire/android/ui/home/conversations/ConversationScreen.kt index d3289bf9052..278ed26b087 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/conversations/ConversationScreen.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/conversations/ConversationScreen.kt @@ -361,7 +361,7 @@ private fun ConversationScreen( is ConversationScreenState.BottomSheetMenuType.SelfDeletion -> { SelfDeletionMenuItems( hideEditMessageMenu = conversationScreenState::hideContextMenu, - currentlySelected = messageComposerViewState.value.selfDeletionTimer.toDuration().toSelfDeletionDuration(), + currentlySelected = messageComposerViewState.value.selfDeletionTimer.duration.toSelfDeletionDuration(), onSelfDeletionDurationChanged = { newTimer -> onNewSelfDeletingMessagesStatus(SelfDeletionTimer.Enabled(newTimer.value)) } diff --git a/app/src/main/kotlin/com/wire/android/ui/home/messagecomposer/MessageActions.kt b/app/src/main/kotlin/com/wire/android/ui/home/messagecomposer/MessageActions.kt index b6b74efe35a..a750fb1c487 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/messagecomposer/MessageActions.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/messagecomposer/MessageActions.kt @@ -72,7 +72,7 @@ fun SelfDeletingActions( ) { Row(verticalAlignment = Alignment.CenterVertically) { Text( - text = selfDeletionTimer.toDuration().toSelfDeletionDuration().shortLabel.asString(), + text = selfDeletionTimer.duration.toSelfDeletionDuration().shortLabel.asString(), style = typography().label02, color = colorsScheme().primary, modifier = Modifier diff --git a/app/src/main/kotlin/com/wire/android/ui/home/messagecomposer/state/MessageCompositionInputStateHolder.kt b/app/src/main/kotlin/com/wire/android/ui/home/messagecomposer/state/MessageCompositionInputStateHolder.kt index 12df3858bbf..7f35b3236d6 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/messagecomposer/state/MessageCompositionInputStateHolder.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/messagecomposer/state/MessageCompositionInputStateHolder.kt @@ -29,7 +29,7 @@ import com.wire.android.ui.common.colorsScheme import com.wire.android.ui.common.textfield.WireTextFieldColors import com.wire.android.ui.common.textfield.wireTextFieldColors import com.wire.kalium.logic.feature.selfDeletingMessages.SelfDeletionTimer -import kotlin.time.Duration +import com.wire.kalium.logic.util.isPositiveNotNull class MessageCompositionInputStateHolder( private val messageComposition: MutableState, @@ -39,7 +39,7 @@ class MessageCompositionInputStateHolder( private set private val messageType = derivedStateOf { - if (selfDeletionTimer.value.toDuration() > Duration.ZERO) { + if (selfDeletionTimer.value.duration.isPositiveNotNull()) { MessageType.SelfDeleting(selfDeletionTimer.value) } else { MessageType.Normal diff --git a/app/src/test/kotlin/com/wire/android/ui/home/conversations/MessageComposerViewModelTest.kt b/app/src/test/kotlin/com/wire/android/ui/home/conversations/MessageComposerViewModelTest.kt index 6e6fbb85e72..26d959b2748 100644 --- a/app/src/test/kotlin/com/wire/android/ui/home/conversations/MessageComposerViewModelTest.kt +++ b/app/src/test/kotlin/com/wire/android/ui/home/conversations/MessageComposerViewModelTest.kt @@ -39,6 +39,7 @@ import kotlinx.coroutines.test.runTest import okio.Path.Companion.toPath import org.amshove.kluent.internal.assertEquals import org.amshove.kluent.shouldBeEqualTo +import org.junit.jupiter.api.Assertions.assertInstanceOf import org.junit.jupiter.api.Test import org.junit.jupiter.api.extension.ExtendWith import kotlin.time.DurationUnit @@ -497,8 +498,8 @@ class MessageComposerViewModelTest { expectedTimer ) } - assert(viewModel.messageComposerViewState.value.selfDeletionTimer is SelfDeletionTimer.Enabled) - assert(viewModel.messageComposerViewState.value.selfDeletionTimer.toDuration() == expectedDuration) + assertInstanceOf(SelfDeletionTimer.Enabled::class.java, viewModel.messageComposerViewState.value.selfDeletionTimer) + assertEquals(expectedDuration, viewModel.messageComposerViewState.value.selfDeletionTimer.duration) } @Test @@ -521,7 +522,7 @@ class MessageComposerViewModelTest { true ) } - assert(viewModel.messageComposerViewState.value.selfDeletionTimer is SelfDeletionTimer.Enabled) - assert(viewModel.messageComposerViewState.value.selfDeletionTimer.toDuration() == expectedDuration) + assertInstanceOf(SelfDeletionTimer.Enabled::class.java, viewModel.messageComposerViewState.value.selfDeletionTimer) + assertEquals(expectedDuration, viewModel.messageComposerViewState.value.selfDeletionTimer.duration) } } From 3791080ca3a4a662be8da5c90492c2e5c3290635 Mon Sep 17 00:00:00 2001 From: Mohamad Jaara Date: Thu, 13 Jul 2023 15:33:10 +0200 Subject: [PATCH 4/5] trigger CI From d34138c9ffad6c845a18e20d00b2fedda2e863fe Mon Sep 17 00:00:00 2001 From: Mohamad Jaara Date: Thu, 13 Jul 2023 15:33:18 +0200 Subject: [PATCH 5/5] update kalium --- kalium | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kalium b/kalium index 1a3db5fd180..088d5e4f28a 160000 --- a/kalium +++ b/kalium @@ -1 +1 @@ -Subproject commit 1a3db5fd180ac50d80baaad19168d71aa3ff95bc +Subproject commit 088d5e4f28a224796c909a48ab64e71d9bc43de0