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: composite messages #1997

Merged
merged 16 commits into from
Jul 27, 2023
Merged
Show file tree
Hide file tree
Changes from 12 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
9 changes: 9 additions & 0 deletions app/src/main/kotlin/com/wire/android/di/CoreLogicModule.kt
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ import com.wire.kalium.logic.feature.message.SendEditTextMessageUseCase
import com.wire.kalium.logic.feature.message.SendKnockUseCase
import com.wire.kalium.logic.feature.message.SendTextMessageUseCase
import com.wire.kalium.logic.feature.message.ToggleReactionUseCase
import com.wire.kalium.logic.feature.message.composite.SendButtonActionMessageUseCase
import com.wire.kalium.logic.feature.message.ephemeral.EnqueueMessageSelfDeletionUseCase
import com.wire.kalium.logic.feature.message.getPaginatedFlowOfMessagesByConversation
import com.wire.kalium.logic.feature.publicuser.GetAllContactsUseCase
Expand Down Expand Up @@ -1208,6 +1209,14 @@ class UseCaseModule {
): DeleteAccountUseCase =
coreLogic.getSessionScope(currentAccount).users.deleteAccount

@ViewModelScoped
@Provides
fun provideSendButtonActionMessageUseCase(
@KaliumCoreLogic coreLogic: CoreLogic,
@CurrentAccount currentAccount: UserId
): SendButtonActionMessageUseCase =
coreLogic.getSessionScope(currentAccount).messages.sendButtonActionMessage

@ViewModelScoped
@Provides
fun providePersistScreenshotCensoringConfigUseCase(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -247,10 +247,20 @@ fun MessagePreview.uiLastMessageContent(): UILastMessageContent {
is WithUser.TeamMemberRemoved -> UILastMessageContent.None // TODO
is WithUser.Text -> UILastMessageContent.SenderWithMessage(
sender = userUIText,
message = UIText.DynamicString((content as WithUser.Text).messageBody),
message = (content as WithUser.Text).messageBody.let { UIText.DynamicString(it) },
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

isn't this crash prone ?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no, since this code will only be executed when the content is Text

separator = ": "
)

is WithUser.Composite -> {
val text = (content as WithUser.Composite).messageBody?.let { UIText.DynamicString(it) }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

isn;t this crash prone when content is not WithUser.Composite and we try to cast it ?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it is impossible to crash this because of the when this code will run iff the content is Composite

?: UIText.StringResource(R.string.last_message_composite_with_missing_text)
UILastMessageContent.SenderWithMessage(
sender = userUIText,
message = text,
separator = ": "
)
}

is WithUser.MissedCall -> UILastMessageContent.TextMessage(
MessageBody(UIText.PluralResource(R.plurals.unread_event_call, 1, 1))
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import com.wire.android.model.ImageAsset
import com.wire.android.ui.home.conversations.findUser
import com.wire.android.ui.home.conversations.model.DeliveryStatusContent
import com.wire.android.ui.home.conversations.model.MessageBody
import com.wire.android.ui.home.conversations.model.MessageButton
import com.wire.android.ui.home.conversations.model.UIMessageContent
import com.wire.android.ui.home.conversations.model.UIQuotedMessage
import com.wire.android.util.time.ISOFormatter
Expand Down Expand Up @@ -91,6 +92,33 @@ class RegularMessageMapper @Inject constructor(
message.deliveryStatus
)

is MessageContent.Composite -> {
val text = content.textContent?.let { textContent ->
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can;t we map it inside the mapper ?

val quotedMessage = textContent.quotedMessageDetails?.let { mapQuoteData(message.conversationId, it) }
?: if (textContent.quotedMessageReference?.quotedMessageId != null) {
UIQuotedMessage.UnavailableData
} else {
null
}

MessageBody(
message = UIText.DynamicString(textContent.value, content.textContent?.mentions.orEmpty()),
quotedMessage = quotedMessage
)
}

UIMessageContent.Composite(
messageBody = text,
buttonList = content.buttonList.map {
MessageButton(
id = it.id,
text = it.text,
isSelected = it.isSelected
)
}
)
}

else -> toText(message.conversationId, content, userList, message.deliveryStatus)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ data class Device(
clientId = client.id,
registrationTime = client.registrationTime?.toIsoDateTimeString(),
lastActiveInWholeWeeks = client.lastActiveInWholeWeeks(),
isValid = client.isVerified,
isValid = client.isValid,
isVerified = client.isVerified
)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
/*
* Wire
* Copyright (C) 2023 Wire Swiss GmbH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see http://www.gnu.org/licenses/.
*/
package com.wire.android.ui.home.conversations

import androidx.annotation.VisibleForTesting
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.wire.android.navigation.EXTRA_CONVERSATION_ID
import com.wire.android.navigation.EXTRA_MESSAGE_ID
import com.wire.kalium.logic.data.id.MessageButtonId
import com.wire.kalium.logic.data.id.QualifiedID
import com.wire.kalium.logic.data.id.QualifiedIdMapper
import com.wire.kalium.logic.feature.message.composite.SendButtonActionMessageUseCase
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.launch
import javax.inject.Inject

@HiltViewModel
class CompositeMessageViewModel @Inject constructor(
private val sendButtonActionMessageUseCase: SendButtonActionMessageUseCase,
qualifiedIdMapper: QualifiedIdMapper,
savedStateHandle: SavedStateHandle,
) : ViewModel() {

val conversationId: QualifiedID = qualifiedIdMapper.fromStringToQualifiedID(
savedStateHandle.get<String>(EXTRA_CONVERSATION_ID)!!
)

private val messageId: String = savedStateHandle.get<String>(EXTRA_MESSAGE_ID)!!

var pendingButtonId: MessageButtonId? by mutableStateOf(null)
@VisibleForTesting
set

fun sendButtonActionMessage(buttonId: String) {
if (pendingButtonId != null) return

pendingButtonId = buttonId
viewModelScope.launch {
sendButtonActionMessageUseCase(conversationId, messageId, buttonId)
}.invokeOnCompletion {
pendingButtonId = null
}
}

companion object {
const val ARGS_KEY = "CompositeMessageViewModelKey"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ fun ConversationScreen(
conversationBannerViewModel: ConversationBannerViewModel = hiltSavedStateViewModel(backNavArgs = backNavArgs),
conversationCallViewModel: ConversationCallViewModel = hiltSavedStateViewModel(backNavArgs = backNavArgs),
conversationMessagesViewModel: ConversationMessagesViewModel = hiltSavedStateViewModel(backNavArgs = backNavArgs),
messageComposerViewModel: MessageComposerViewModel = hiltSavedStateViewModel(backNavArgs = backNavArgs)
messageComposerViewModel: MessageComposerViewModel = hiltSavedStateViewModel(backNavArgs = backNavArgs),
) {
val coroutineScope = rememberCoroutineScope()
val showDialog = remember { mutableStateOf(ConversationScreenDialogType.NONE) }
Expand Down Expand Up @@ -421,7 +421,7 @@ private fun ConversationScreen(
onClearMentionSearchResult = onClearMentionSearchResult,
tempWritableImageUri = tempWritableImageUri,
tempWritableVideoUri = tempWritableVideoUri,
snackBarHostState = conversationScreenState.snackBarHostState
snackBarHostState = conversationScreenState.snackBarHostState,
)
}
MenuModalSheetLayout(
Expand Down Expand Up @@ -491,7 +491,7 @@ private fun ConversationScreenContent(
onShowEditingOption = onShowEditingOptions,
conversationDetailsData = conversationDetailsData,
onFailedMessageCancelClicked = onFailedMessageCancelClicked,
onFailedMessageRetryClicked = onFailedMessageRetryClicked
onFailedMessageRetryClicked = onFailedMessageRetryClicked,
)
},
onChangeSelfDeletionClicked = onChangeSelfDeletionClicked,
Expand Down Expand Up @@ -571,7 +571,7 @@ fun MessageList(
onSelfDeletingMessageRead: (UIMessage) -> Unit,
conversationDetailsData: ConversationDetailsData,
onFailedMessageRetryClicked: (String) -> Unit,
onFailedMessageCancelClicked: (String) -> Unit
onFailedMessageCancelClicked: (String) -> Unit,
) {
val mostRecentMessage = lazyPagingMessages.itemCount.takeIf { it > 0 }?.let { lazyPagingMessages[0] }

Expand Down Expand Up @@ -627,7 +627,7 @@ fun MessageList(
onResetSessionClicked = onResetSessionClicked,
onSelfDeletingMessageRead = onSelfDeletingMessageRead,
onFailedMessageCancelClicked = onFailedMessageCancelClicked,
onFailedMessageRetryClicked = onFailedMessageRetryClicked
onFailedMessageRetryClicked = onFailedMessageRetryClicked,
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ fun MessageItem(
onResetSessionClicked: (senderUserId: UserId, clientId: String?) -> Unit,
onSelfDeletingMessageRead: (UIMessage) -> Unit,
onFailedMessageRetryClicked: (String) -> Unit = {},
onFailedMessageCancelClicked: (String) -> Unit = {}
onFailedMessageCancelClicked: (String) -> Unit = {},
) {
with(message) {
val selfDeletionTimerState = rememberSelfDeletionTimer(header.messageStatus.expirationStatus)
Expand Down Expand Up @@ -158,7 +158,7 @@ fun MessageItem(

val isProfileRedirectEnabled =
header.userId != null &&
!(header.isSenderDeleted || header.isSenderUnavailable)
!(header.isSenderDeleted || header.isSenderUnavailable)

if (showAuthor) {
val avatarClickable = remember {
Expand Down Expand Up @@ -229,7 +229,7 @@ fun MessageItem(
onAssetClick = currentOnAssetClicked,
onImageClick = currentOnImageClick,
onLongClick = onLongClick,
onOpenProfile = onOpenProfile
onOpenProfile = onOpenProfile,
)
}
if (isMyMessage) {
Expand Down Expand Up @@ -475,12 +475,35 @@ private fun MessageContent(
messageBody = messageContent.messageBody,
isAvailable = !message.isPending && message.isAvailable,
onLongClick = onLongClick,
onOpenProfile = onOpenProfile
onOpenProfile = onOpenProfile,
buttonList = null,
messageId = message.header.messageId
)
PartialDeliveryInformation(messageContent.deliveryStatus)
}
}

is UIMessageContent.Composite -> {
Column {
messageContent.messageBody?.quotedMessage?.let {
VerticalSpace.x4()
when (it) {
is UIQuotedMessage.UIQuotedData -> QuotedMessage(it)
UIQuotedMessage.UnavailableData -> QuotedUnavailable(QuotedMessageStyle.COMPLETE)
}
VerticalSpace.x4()
}
MessageBody(
messageBody = messageContent.messageBody,
isAvailable = !message.isPending && message.isAvailable,
onLongClick = onLongClick,
onOpenProfile = onOpenProfile,
buttonList = messageContent.buttonList,
messageId = message.header.messageId,
)
}
}

is UIMessageContent.AssetMessage -> {
MessageGenericAsset(
assetName = messageContent.assetName,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import com.wire.android.ui.common.bottomsheet.MenuItemIcon
import com.wire.android.ui.home.conversations.model.ExpirationStatus
import com.wire.android.ui.home.conversations.model.UIMessage
import com.wire.android.ui.home.conversations.model.UIMessageContent
import com.wire.android.util.Copyable
import com.wire.android.util.ui.UIText
import com.wire.kalium.logic.data.message.mention.MessageMention

Expand All @@ -49,39 +50,40 @@ fun EditMessageMenuItems(
): List<@Composable () -> Unit> {
val localContext = LocalContext.current

val onCopyItemClick = remember(message) {
{
hideEditMessageMenu {
onCopyClick(
(message.messageContent as UIMessageContent.TextMessage).messageBody.message.asString(
localContext.resources
)
)
val isComposite = remember(message.header.messageId) {
message.messageContent is UIMessageContent.Composite
}

val onCopyItemClick: (() -> Unit)? = remember(message.header.messageId) {
(message.messageContent as? Copyable)?.textToCopy(localContext.resources)?.let {
{
hideEditMessageMenu { onCopyClick(it) }
}
}
}
val onDeleteItemClick = remember(message) {

val onDeleteItemClick = remember(message.header.messageId) {
{
hideEditMessageMenu {
onDeleteClick(message.header.messageId, message.isMyMessage)
}
}
}
val onReactionItemClick = remember(message) {
val onReactionItemClick = remember(message.header.messageId) {
{ emoji: String ->
hideEditMessageMenu {
onReactionClick(message.header.messageId, emoji)
}
}
}
val onReplyItemClick = remember(message) {
val onReplyItemClick = remember(message.header.messageId) {
{
hideEditMessageMenu {
onReplyClick(message)
}
}
}
val onDetailsItemClick = remember(message) {
val onDetailsItemClick = remember(message.header.messageId) {
{
hideEditMessageMenu {
onDetailsClick(message.header.messageId, message.isMyMessage)
Expand Down Expand Up @@ -132,6 +134,7 @@ fun EditMessageMenuItems(
TextMessageEditMenuItems(
isEphemeral = message.header.messageStatus.expirationStatus is ExpirationStatus.Expirable,
isUploading = message.isPending,
isComposite = isComposite,
onDeleteClick = onDeleteItemClick,
onDetailsClick = onDetailsItemClick,
onReactionClick = onReactionItemClick,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,19 +27,20 @@ import com.wire.android.ui.edit.ReplyMessageOption
fun TextMessageEditMenuItems(
isEphemeral: Boolean,
isUploading: Boolean,
isComposite: Boolean,
onDeleteClick: () -> Unit,
onDetailsClick: () -> Unit,
onReplyClick: () -> Unit,
onCopyClick: () -> Unit,
onCopyClick: (() -> Unit)?,
onReactionClick: (String) -> Unit,
onEditClick: (() -> Unit)? = null
): List<@Composable () -> Unit> {
return buildList {
if (!isUploading) {
if (!isEphemeral) add { ReactionOption(onReactionClick) }
if (!isEphemeral && !isComposite) add { ReactionOption(onReactionClick) }
add { MessageDetailsMenuOption(onDetailsClick) }
if (!isEphemeral) add { CopyItemMenuOption(onCopyClick) }
if (!isEphemeral) add { ReplyMessageOption(onReplyClick) }
onCopyClick?.also { add { CopyItemMenuOption(it) } }
if (!isEphemeral && !isComposite) add { ReplyMessageOption(onReplyClick) }
if (!isEphemeral && onEditClick != null) add { EditMessageMenuOption(onEditClick) }
}
add { DeleteItemMenuOption(onDeleteClick) }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,13 +43,13 @@ import com.wire.android.navigation.getBackNavArg
import com.wire.android.ui.home.conversations.ConversationSnackbarMessages
import com.wire.android.ui.home.conversations.ConversationSnackbarMessages.OnResetSession
import com.wire.android.ui.home.conversations.model.AssetBundle
import com.wire.kalium.logic.data.asset.AttachmentType
import com.wire.android.ui.home.conversations.model.UIMessage
import com.wire.android.ui.home.conversations.usecase.GetMessagesForConversationUseCase
import com.wire.android.util.FileManager
import com.wire.android.util.dispatchers.DispatcherProvider
import com.wire.android.util.startFileShareIntent
import com.wire.android.util.ui.UIText
import com.wire.kalium.logic.data.asset.AttachmentType
import com.wire.kalium.logic.data.conversation.ClientId
import com.wire.kalium.logic.data.id.QualifiedID
import com.wire.kalium.logic.data.id.QualifiedIdMapper
Expand All @@ -68,6 +68,7 @@ import com.wire.kalium.logic.feature.sessionreset.ResetSessionUseCase
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
Expand Down Expand Up @@ -96,6 +97,7 @@ class ConversationMessagesViewModel @Inject constructor(
) : SavedStateViewModel(savedStateHandle) {

var conversationViewState by mutableStateOf(ConversationMessagesViewState())
private set

private val conversationId: QualifiedID = qualifiedIdMapper.fromStringToQualifiedID(
savedStateHandle.get<String>(EXTRA_CONVERSATION_ID)!!
Expand Down
Loading
Loading