diff --git a/app/src/main/java/org/linphone/ui/main/chat/adapter/ConversationEventAdapter.kt b/app/src/main/java/org/linphone/ui/main/chat/adapter/ConversationEventAdapter.kt index cd10602fa..9b1cc66c2 100644 --- a/app/src/main/java/org/linphone/ui/main/chat/adapter/ConversationEventAdapter.kt +++ b/app/src/main/java/org/linphone/ui/main/chat/adapter/ConversationEventAdapter.kt @@ -53,6 +53,9 @@ class ConversationEventAdapter( val showReactionForChatMessageModelEvent: MutableLiveData> by lazy { MutableLiveData>() } + val scrollToRepliedMessageEvent: MutableLiveData> by lazy { + MutableLiveData>() + } override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { return when (viewType) { @@ -129,6 +132,9 @@ class ConversationEventAdapter( setShowReactionInfoClickListener { showReactionForChatMessageModelEvent.value = Event(message) } + setScrollToRepliedMessageClickListener { + scrollToRepliedMessageEvent.value = Event(message) + } lifecycleOwner = viewLifecycleOwner executePendingBindings() @@ -149,6 +155,9 @@ class ConversationEventAdapter( setShowReactionInfoClickListener { showReactionForChatMessageModelEvent.value = Event(message) } + setScrollToRepliedMessageClickListener { + scrollToRepliedMessageEvent.value = Event(message) + } lifecycleOwner = viewLifecycleOwner executePendingBindings() diff --git a/app/src/main/java/org/linphone/ui/main/chat/fragment/ConversationFragment.kt b/app/src/main/java/org/linphone/ui/main/chat/fragment/ConversationFragment.kt index b6e03fb25..1a81bea00 100644 --- a/app/src/main/java/org/linphone/ui/main/chat/fragment/ConversationFragment.kt +++ b/app/src/main/java/org/linphone/ui/main/chat/fragment/ConversationFragment.kt @@ -203,6 +203,26 @@ class ConversationFragment : GenericFragment() { } } + adapter.scrollToRepliedMessageEvent.observe(viewLifecycleOwner) { + it.consume { model -> + val repliedMessageId = model.replyToMessageId + if (repliedMessageId.isNullOrEmpty()) { + Log.w("$TAG Chat message [${model.id}] doesn't have a reply to ID!") + } else { + val originalMessage = adapter.currentList.find { + !it.isEvent && (it.model as ChatMessageModel).id == repliedMessageId + } + if (originalMessage != null) { + val position = adapter.currentList.indexOf(originalMessage) + Log.i("$TAG Scrolling to position [$position]") + binding.eventsList.scrollToPosition(position) + } else { + Log.w("$TAG Failed to find matching message in adapter's items!") + } + } + } + } + binding.setOpenFilePickerClickListener { Log.i("$TAG Opening media picker") pickMedia.launch( @@ -280,11 +300,13 @@ class ConversationFragment : GenericFragment() { } layout.setDeleteClickListener { + Log.i("$TAG Deleting message") viewModel.deleteChatMessage(chatMessageModel) dialog.dismiss() } layout.setCopyClickListener { + Log.i("$TAG Copying message text into clipboard") val text = chatMessageModel.text val clipboard = requireContext().getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager val label = "Message" @@ -294,10 +316,17 @@ class ConversationFragment : GenericFragment() { } layout.setPickEmojiClickListener { + Log.i("$TAG Opening emoji-picker for reaction") val emojiSheetBehavior = BottomSheetBehavior.from(layout.emojiPickerBottomSheet.root) emojiSheetBehavior.state = BottomSheetBehavior.STATE_EXPANDED } + layout.setReplyClickListener { + Log.i("$TAG Updating sending area to reply to selected message") + viewModel.replyToMessage(chatMessageModel) + dialog.dismiss() + } + layout.model = chatMessageModel chatMessageModel.dismissLongPressMenuEvent.observe(viewLifecycleOwner) { dialog.dismiss() diff --git a/app/src/main/java/org/linphone/ui/main/chat/model/ChatMessageModel.kt b/app/src/main/java/org/linphone/ui/main/chat/model/ChatMessageModel.kt index fd0b3ab71..6aec7362a 100644 --- a/app/src/main/java/org/linphone/ui/main/chat/model/ChatMessageModel.kt +++ b/app/src/main/java/org/linphone/ui/main/chat/model/ChatMessageModel.kt @@ -39,6 +39,7 @@ class ChatMessageModel @WorkerThread constructor( val isFromGroup: Boolean, val isReply: Boolean, val replyText: String, + val replyToMessageId: String?, val isGroupedWithPreviousOne: Boolean, val isGroupedWithNextOne: Boolean ) { diff --git a/app/src/main/java/org/linphone/ui/main/chat/model/EventLogModel.kt b/app/src/main/java/org/linphone/ui/main/chat/model/EventLogModel.kt index 5b503665e..fe90a5e5e 100644 --- a/app/src/main/java/org/linphone/ui/main/chat/model/EventLogModel.kt +++ b/app/src/main/java/org/linphone/ui/main/chat/model/EventLogModel.kt @@ -64,6 +64,7 @@ class EventLogModel @WorkerThread constructor( isFromGroup, chatMessage.isReply, reply, + chatMessage.replyMessageId, isGroupedWithPreviousOne, isGroupedWithNextOne ) diff --git a/app/src/main/java/org/linphone/ui/main/chat/viewmodel/ConversationViewModel.kt b/app/src/main/java/org/linphone/ui/main/chat/viewmodel/ConversationViewModel.kt index 900d3769c..877d99c5a 100644 --- a/app/src/main/java/org/linphone/ui/main/chat/viewmodel/ConversationViewModel.kt +++ b/app/src/main/java/org/linphone/ui/main/chat/viewmodel/ConversationViewModel.kt @@ -26,6 +26,7 @@ import androidx.lifecycle.ViewModel import org.linphone.LinphoneApplication.Companion.coreContext import org.linphone.R import org.linphone.core.Address +import org.linphone.core.ChatMessage import org.linphone.core.ChatRoom import org.linphone.core.ChatRoomListenerStub import org.linphone.core.EventLog @@ -68,6 +69,12 @@ class ConversationViewModel @UiThread constructor() : ViewModel() { val isEmojiPickerOpen = MutableLiveData() + val isReplying = MutableLiveData() + + val isReplyingTo = MutableLiveData() + + val isReplyingToMessage = MutableLiveData() + val requestKeyboardHidingEvent: MutableLiveData> by lazy { MutableLiveData>() } @@ -80,6 +87,8 @@ class ConversationViewModel @UiThread constructor() : ViewModel() { private lateinit var chatRoom: ChatRoom + private var chatMessageToReplyTo: ChatMessage? = null + private val chatRoomListener = object : ChatRoomListenerStub() { @WorkerThread override fun onChatMessageSending(chatRoom: ChatRoom, eventLog: EventLog) { @@ -237,10 +246,35 @@ class ConversationViewModel @UiThread constructor() : ViewModel() { textToSend.value = "${textToSend.value.orEmpty()}$emoji" } + @UiThread + fun replyToMessage(model: ChatMessageModel) { + coreContext.postOnCoreThread { + val message = model.chatMessage + Log.i("$TAG Pending reply to chat message [${message.messageId}]") + chatMessageToReplyTo = message + isReplyingTo.postValue(model.avatarModel.friend.name) + isReplyingToMessage.postValue(LinphoneUtils.getTextDescribingMessage(message)) + isReplying.postValue(true) + } + } + + @UiThread + fun cancelReply() { + Log.i("$TAG Cancelling reply") + isReplying.value = false + chatMessageToReplyTo = null + } + @UiThread fun sendMessage() { - coreContext.postOnCoreThread { core -> - val message = chatRoom.createEmptyMessage() + coreContext.postOnCoreThread { + val messageToReplyTo = chatMessageToReplyTo + val message = if (messageToReplyTo != null) { + Log.i("$TAG Sending message as reply to [${messageToReplyTo.messageId}]") + chatRoom.createReplyMessage(messageToReplyTo) + } else { + chatRoom.createEmptyMessage() + } val toSend = textToSend.value.orEmpty().trim() if (toSend.isNotEmpty()) { @@ -251,7 +285,10 @@ class ConversationViewModel @UiThread constructor() : ViewModel() { Log.i("$TAG Sending message") message.send() } + + Log.i("$TAG Message sent, re-setting defaults") textToSend.postValue("") + cancelReply() } } diff --git a/app/src/main/res/layout/chat_bubble_incoming.xml b/app/src/main/res/layout/chat_bubble_incoming.xml index 97d41bfd3..659b9e8ed 100644 --- a/app/src/main/res/layout/chat_bubble_incoming.xml +++ b/app/src/main/res/layout/chat_bubble_incoming.xml @@ -13,6 +13,9 @@ + @@ -66,17 +69,32 @@ app:layout_constraintBottom_toTopOf="@id/reply" app:layout_constraintStart_toStartOf="@id/background" /> + + + @@ -37,6 +40,38 @@ app:barrierMargin="-10dp" app:constraint_referenced_ids="date_time, text_message" /> + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/chat_conversation_send_area.xml b/app/src/main/res/layout/chat_conversation_send_area.xml index e9a476b6b..7940ef212 100644 --- a/app/src/main/res/layout/chat_conversation_send_area.xml +++ b/app/src/main/res/layout/chat_conversation_send_area.xml @@ -19,13 +19,20 @@ app:layout_behavior="com.google.android.material.bottomsheet.BottomSheetBehavior"> + + + app:layout_constraintTop_toBottomOf="@id/reply_area"/> %s are composing… Add participants + Replying to: Group members Add participants