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 82b343b34..63e6d881e 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 @@ -278,6 +278,14 @@ class ConversationFragment : SlidingPaneChildFragment() { } } else { sendMessageViewModel.configureChatRoom(viewModel.chatRoom) + + // Wait for chat room to be ready before trying to forward a message in it + sharedViewModel.messageToForwardEvent.observe(viewLifecycleOwner) { + it.consume { toForward -> + Log.i("$TAG Found message to forward") + sendMessageViewModel.forwardMessage(toForward) + } + } } } } @@ -297,7 +305,7 @@ class ConversationFragment : SlidingPaneChildFragment() { viewModel.scrollToBottomEvent.observe(viewLifecycleOwner) { it.consume { - binding.eventsList.scrollToPosition(adapter.itemCount - 1) + scrollToBottom() } } @@ -516,7 +524,7 @@ class ConversationFragment : SlidingPaneChildFragment() { sendMessageViewModel.isEmojiPickerOpen.value = false // Scroll to bottom when keyboard is opened so latest message is visible - binding.eventsList.scrollToPosition(adapter.itemCount - 1) + scrollToBottom() } } } @@ -533,7 +541,7 @@ class ConversationFragment : SlidingPaneChildFragment() { if (viewModel.scrollingPosition != SCROLLING_POSITION_NOT_SET) { binding.eventsList.scrollToPosition(viewModel.scrollingPosition) } else { - binding.eventsList.scrollToPosition(adapter.itemCount - 1) + scrollToBottom() } try { @@ -573,34 +581,40 @@ class ConversationFragment : SlidingPaneChildFragment() { currentChatMessageModelForBottomSheet = null } - private fun scrollToFirstUnreadMessageOrBottom(smooth: Boolean): Boolean { - if (adapter.itemCount > 0) { - val recyclerView = binding.eventsList + private fun scrollToBottom() { + if (adapter.itemCount == 0) return - // Scroll to first unread message if any, unless we are already on it - val firstUnreadMessagePosition = adapter.getFirstUnreadMessagePosition() - val currentPosition = (recyclerView.layoutManager as LinearLayoutManager).findFirstCompletelyVisibleItemPosition() - val indexToScrollTo = if (firstUnreadMessagePosition != -1 && firstUnreadMessagePosition != currentPosition) { - firstUnreadMessagePosition - } else { - adapter.itemCount - 1 - } - - Log.i( - "$TAG Scrolling to position $indexToScrollTo, first unread message is at $firstUnreadMessagePosition" - ) - if (smooth) { - recyclerView.smoothScrollToPosition(indexToScrollTo) - } else { - recyclerView.scrollToPosition(indexToScrollTo) - } - - if (firstUnreadMessagePosition == 0) { - // Return true only if all unread messages don't fit in the recyclerview height - return recyclerView.computeVerticalScrollRange() > recyclerView.height + lifecycleScope.launch { + withContext(Dispatchers.IO) { + delay(100) + withContext(Dispatchers.Main) { + binding.eventsList.scrollToPosition(adapter.itemCount - 1) + } } } - return false + } + + private fun scrollToFirstUnreadMessageOrBottom(smooth: Boolean) { + if (adapter.itemCount == 0) return + + val recyclerView = binding.eventsList + // Scroll to first unread message if any, unless we are already on it + val firstUnreadMessagePosition = adapter.getFirstUnreadMessagePosition() + val currentPosition = (recyclerView.layoutManager as LinearLayoutManager).findFirstCompletelyVisibleItemPosition() + val indexToScrollTo = if (firstUnreadMessagePosition != -1 && firstUnreadMessagePosition != currentPosition) { + firstUnreadMessagePosition + } else { + adapter.itemCount - 1 + } + + Log.i( + "$TAG Scrolling to position $indexToScrollTo, first unread message is at $firstUnreadMessagePosition" + ) + if (smooth) { + recyclerView.smoothScrollToPosition(indexToScrollTo) + } else { + recyclerView.scrollToPosition(indexToScrollTo) + } } private fun showChatMessageLongPressMenu(chatMessageModel: MessageModel) { @@ -648,6 +662,16 @@ class ConversationFragment : SlidingPaneChildFragment() { dialog.dismiss() } + layout.setForwardClickListener { + Log.i("$TAG Forwarding message, going back to conversations list") + // Remove observer before setting the message to forward + // as we don't want to forward it in this chat room + sharedViewModel.messageToForwardEvent.removeObservers(viewLifecycleOwner) + sharedViewModel.messageToForwardEvent.postValue(Event(chatMessageModel)) + dialog.dismiss() + goBack() + } + layout.setReplyClickListener { Log.i("$TAG Updating sending area to reply to selected message") sendMessageViewModel.replyToMessage(chatMessageModel) diff --git a/app/src/main/java/org/linphone/ui/main/chat/fragment/ConversationsListFragment.kt b/app/src/main/java/org/linphone/ui/main/chat/fragment/ConversationsListFragment.kt index c54880677..f68c00769 100644 --- a/app/src/main/java/org/linphone/ui/main/chat/fragment/ConversationsListFragment.kt +++ b/app/src/main/java/org/linphone/ui/main/chat/fragment/ConversationsListFragment.kt @@ -273,6 +273,39 @@ class ConversationsListFragment : AbstractTopBarFragment() { } } + sharedViewModel.messageToForwardEvent.observe(viewLifecycleOwner) { event -> + if (!event.consumed()) { + // Do not consume it yet + val message = getString(R.string.toast_message_waiting_to_be_forwarded) + val icon = R.drawable.forward + (requireActivity() as MainActivity).showGreenToast(message, icon) + Log.i("$TAG Found a message waiting to be forwarded") + } + } + + sharedViewModel.filesToShareFromIntent.observe(viewLifecycleOwner) { filesToShare -> + val count = filesToShare.size + if (count > 0) { + val message = AppUtils.getStringWithPlural( + R.plurals.toast_files_waiting_to_be_shared, + count, + filesToShare.size.toString() + ) + val icon = R.drawable.file + (requireActivity() as MainActivity).showGreenToast(message, icon) + Log.i("$TAG Found [$count] files waiting to be shared") + } + } + + sharedViewModel.textToShareFromIntent.observe(viewLifecycleOwner) { textToShare -> + if (textToShare.isNotEmpty()) { + val message = getString(R.string.toast_text_waiting_to_be_shared) + val icon = R.drawable.file + (requireActivity() as MainActivity).showGreenToast(message, icon) + Log.i("$TAG Found text waiting to be shared") + } + } + // TopBarFragment related setViewModelAndTitle( @@ -311,19 +344,6 @@ class ConversationsListFragment : AbstractTopBarFragment() { Log.e("$TAG Failed to unregister data observer to adapter: $e") } - val filesToShare = sharedViewModel.filesToShareFromIntent.value.orEmpty() - if (filesToShare.isNotEmpty()) { - val count = filesToShare.size - val message = AppUtils.getStringWithPlural( - R.plurals.toast_files_waiting_to_be_shared, - count, - filesToShare.size.toString() - ) - val icon = R.drawable.file - (requireActivity() as MainActivity).showGreenToast(message, icon) - Log.i("$TAG Found [$count] files waiting to be shared") - } - // Scroll to top when fragment is resumed binding.conversationsList.scrollToPosition(0) } diff --git a/app/src/main/java/org/linphone/ui/main/chat/viewmodel/SendMessageInConversationViewModel.kt b/app/src/main/java/org/linphone/ui/main/chat/viewmodel/SendMessageInConversationViewModel.kt index 3008e1859..7fd2ba08f 100644 --- a/app/src/main/java/org/linphone/ui/main/chat/viewmodel/SendMessageInConversationViewModel.kt +++ b/app/src/main/java/org/linphone/ui/main/chat/viewmodel/SendMessageInConversationViewModel.kt @@ -348,6 +348,18 @@ class SendMessageInConversationViewModel @UiThread constructor() : ViewModel() { } } + @UiThread + fun forwardMessage(toForward: MessageModel) { + coreContext.postOnCoreThread { + if (::chatRoom.isInitialized) { + val messageToForward = toForward.chatMessage + val forwardedMessage = chatRoom.createForwardMessage(messageToForward) + Log.i("$TAG Sending forwarded message") + forwardedMessage.send() + } + } + } + @UiThread fun startVoiceMessageRecording() { if (ActivityCompat.checkSelfPermission( diff --git a/app/src/main/java/org/linphone/ui/main/viewmodel/SharedMainViewModel.kt b/app/src/main/java/org/linphone/ui/main/viewmodel/SharedMainViewModel.kt index b8f982011..216e21c8c 100644 --- a/app/src/main/java/org/linphone/ui/main/viewmodel/SharedMainViewModel.kt +++ b/app/src/main/java/org/linphone/ui/main/viewmodel/SharedMainViewModel.kt @@ -24,6 +24,7 @@ import androidx.annotation.UiThread import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import org.linphone.core.ChatRoom +import org.linphone.ui.main.chat.model.MessageModel import org.linphone.utils.Event class SharedMainViewModel @UiThread constructor() : ViewModel() { @@ -102,6 +103,8 @@ class SharedMainViewModel @UiThread constructor() : ViewModel() { val filesToShareFromIntent = MutableLiveData>() + val messageToForwardEvent = MutableLiveData>() + var displayedChatRoom: ChatRoom? = null // Prevents the need to go look for the chat room val showConversationEvent: MutableLiveData>> by lazy { MutableLiveData>>() diff --git a/app/src/main/java/org/linphone/utils/Event.kt b/app/src/main/java/org/linphone/utils/Event.kt index a903d24b4..5a08162b8 100644 --- a/app/src/main/java/org/linphone/utils/Event.kt +++ b/app/src/main/java/org/linphone/utils/Event.kt @@ -37,4 +37,9 @@ open class Event @AnyThread constructor(private val content: T) { handleContent(content) } } + + @UiThread + fun consumed(): Boolean { + return handled.get() + } } diff --git a/app/src/main/res/layout/chat_bubble_incoming.xml b/app/src/main/res/layout/chat_bubble_incoming.xml index 900b987dc..e87a44fa8 100644 --- a/app/src/main/res/layout/chat_bubble_incoming.xml +++ b/app/src/main/res/layout/chat_bubble_incoming.xml @@ -82,7 +82,7 @@ android:id="@+id/reply_name" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_marginStart="8dp" + android:layout_marginStart="5dp" android:text="@{model.replyTo, default=`John Doe`}" android:textSize="12sp" android:textColor="?attr/color_main2_500" @@ -135,7 +135,7 @@ android:id="@+id/forward_label" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_marginStart="8dp" + android:layout_marginStart="5dp" android:text="@string/message_forwarded_label" android:textSize="12sp" android:textColor="?attr/color_main2_500" @@ -187,11 +187,10 @@ android:onClick="@{() -> model.firstImageClicked()}" android:onLongClick="@{onLongClickListener}" android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:maxHeight="@dimen/chat_bubble_big_image_max_size" + android:layout_height="@dimen/chat_bubble_big_image_max_size" android:adjustViewBounds="true" android:scaleType="fitCenter" - android:visibility="@{model.filesList.size() == 1 && model.firstImagePath.length() >= 0 ? View.VISIBLE : View.GONE}" + android:visibility="@{model.filesList.size() == 1 && model.firstImagePath.length() >= 0 ? View.VISIBLE : View.GONE, default=gone}" coilBubble="@{model.firstImagePath}"/> %s file waiting to be shared %s files waiting to be shared + Text is waiting to be shared + A message is waiting to be forwarded Login Scan QR code