Added reply to chat message feature

This commit is contained in:
Sylvain Berfini 2021-06-04 17:03:55 +02:00
parent 80fa401d5b
commit f7710e2ae2
34 changed files with 528 additions and 16 deletions

View file

@ -29,7 +29,7 @@ if (crashlyticsEnabled()) {
def gitBranch = new ByteArrayOutputStream() def gitBranch = new ByteArrayOutputStream()
task getGitVersion() { task getGitVersion() {
def gitVersion = "4.5.0" def gitVersion = "4.6.0"
def gitVersionStream = new ByteArrayOutputStream() def gitVersionStream = new ByteArrayOutputStream()
def gitCommitsCount = new ByteArrayOutputStream() def gitCommitsCount = new ByteArrayOutputStream()
def gitCommitHash = new ByteArrayOutputStream() def gitCommitHash = new ByteArrayOutputStream()
@ -269,7 +269,7 @@ dependencies {
implementation 'com.google.firebase:firebase-messaging' implementation 'com.google.firebase:firebase-messaging'
} }
implementation 'org.linphone:linphone-sdk-android:5.0+' implementation 'org.linphone:linphone-sdk-android:5.1+'
// Only enable leak canary prior to release // Only enable leak canary prior to release
//debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.4' //debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.4'

View file

@ -69,6 +69,10 @@ class ChatMessagesListAdapter(
MutableLiveData<Event<ChatMessage>>() MutableLiveData<Event<ChatMessage>>()
} }
val replyMessageEvent: MutableLiveData<Event<ChatMessage>> by lazy {
MutableLiveData<Event<ChatMessage>>()
}
val showImdnForMessageEvent: MutableLiveData<Event<ChatMessage>> by lazy { val showImdnForMessageEvent: MutableLiveData<Event<ChatMessage>> by lazy {
MutableLiveData<Event<ChatMessage>>() MutableLiveData<Event<ChatMessage>>()
} }
@ -81,6 +85,10 @@ class ChatMessagesListAdapter(
MutableLiveData<Event<Content>>() MutableLiveData<Event<Content>>()
} }
val scrollToChatMessageEvent: MutableLiveData<Event<ChatMessage>> by lazy {
MutableLiveData<Event<ChatMessage>>()
}
private val contentClickedListener = object : OnContentClickedListener { private val contentClickedListener = object : OnContentClickedListener {
override fun onContentClicked(content: Content) { override fun onContentClicked(content: Content) {
openContentEvent.value = Event(content) openContentEvent.value = Event(content)
@ -155,6 +163,13 @@ class ChatMessagesListAdapter(
} }
} }
setReplyClickListener {
val reply = chatMessageViewModel.replyData.value?.chatMessage
if (reply != null) {
scrollToChatMessageEvent.value = Event(reply)
}
}
// Grouping // Grouping
var hasPrevious = false var hasPrevious = false
var hasNext = false var hasNext = false
@ -196,7 +211,7 @@ class ChatMessagesListAdapter(
) )
val itemSize = AppUtils.getDimension(R.dimen.chat_message_popup_item_height).toInt() val itemSize = AppUtils.getDimension(R.dimen.chat_message_popup_item_height).toInt()
var totalSize = itemSize * 6 var totalSize = itemSize * 7
if (chatMessage.chatRoom.hasCapability(ChatRoomCapabilities.OneToOne.toInt()) || if (chatMessage.chatRoom.hasCapability(ChatRoomCapabilities.OneToOne.toInt()) ||
chatMessage.state == ChatMessage.State.NotDelivered) { // No message id chatMessage.state == ChatMessage.State.NotDelivered) { // No message id
popupView.imdnHidden = true popupView.imdnHidden = true
@ -237,6 +252,10 @@ class ChatMessagesListAdapter(
forwardMessage() forwardMessage()
popupWindow.dismiss() popupWindow.dismiss()
} }
popupView.setReplyClickListener {
replyMessage()
popupWindow.dismiss()
}
popupView.setImdnClickListener { popupView.setImdnClickListener {
showImdnDeliveryFragment() showImdnDeliveryFragment()
popupWindow.dismiss() popupWindow.dismiss()
@ -287,6 +306,13 @@ class ChatMessagesListAdapter(
} }
} }
private fun replyMessage() {
val chatMessage = binding.data?.chatMessage
if (chatMessage != null) {
replyMessageEvent.value = Event(chatMessage)
}
}
private fun showImdnDeliveryFragment() { private fun showImdnDeliveryFragment() {
val chatMessage = binding.data?.chatMessage val chatMessage = binding.data?.chatMessage
if (chatMessage != null) { if (chatMessage != null) {

View file

@ -28,6 +28,7 @@ import org.linphone.R
import org.linphone.contact.GenericContactData import org.linphone.contact.GenericContactData
import org.linphone.core.ChatMessage import org.linphone.core.ChatMessage
import org.linphone.core.ChatMessageListenerStub import org.linphone.core.ChatMessageListenerStub
import org.linphone.core.tools.Log
import org.linphone.utils.AppUtils import org.linphone.utils.AppUtils
import org.linphone.utils.TimestampUtils import org.linphone.utils.TimestampUtils
@ -56,6 +57,8 @@ class ChatMessageData(val chatMessage: ChatMessage) : GenericContactData(chatMes
val text = MutableLiveData<Spannable>() val text = MutableLiveData<Spannable>()
val replyData = MutableLiveData<ChatMessageData>()
private var countDownTimer: CountDownTimer? = null private var countDownTimer: CountDownTimer? = null
private val listener = object : ChatMessageListenerStub() { private val listener = object : ChatMessageListenerStub() {
@ -74,6 +77,15 @@ class ChatMessageData(val chatMessage: ChatMessage) : GenericContactData(chatMes
backgroundRes.value = if (chatMessage.isOutgoing) R.drawable.chat_bubble_outgoing_full else R.drawable.chat_bubble_incoming_full backgroundRes.value = if (chatMessage.isOutgoing) R.drawable.chat_bubble_outgoing_full else R.drawable.chat_bubble_incoming_full
hideAvatar.value = false hideAvatar.value = false
if (chatMessage.isReply) {
val reply = chatMessage.replyMessage
if (reply != null) {
Log.i("[Chat Message Data] Message is a reply of message id [${chatMessage.replyMessageId}] sent by [${chatMessage.replyMessageSenderAddress?.asStringUriOnly()}]")
replyData.value = ChatMessageData(reply)
}
}
time.value = TimestampUtils.toString(chatMessage.time) time.value = TimestampUtils.toString(chatMessage.time)
updateEphemeralTimer() updateEphemeralTimer()
@ -84,6 +96,10 @@ class ChatMessageData(val chatMessage: ChatMessage) : GenericContactData(chatMes
override fun destroy() { override fun destroy() {
super.destroy() super.destroy()
if (chatMessage.isReply) {
replyData.value?.destroy()
}
contents.value.orEmpty().forEach(ChatMessageContentData::destroy) contents.value.orEmpty().forEach(ChatMessageContentData::destroy)
chatMessage.removeListener(listener) chatMessage.removeListener(listener)
contentListener = null contentListener = null

View file

@ -39,6 +39,7 @@ import androidx.navigation.fragment.findNavController
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import java.io.File import java.io.File
import java.lang.IllegalArgumentException
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.linphone.LinphoneApplication.Companion.coreContext import org.linphone.LinphoneApplication.Companion.coreContext
import org.linphone.LinphoneApplication.Companion.corePreferences import org.linphone.LinphoneApplication.Companion.corePreferences
@ -47,6 +48,7 @@ import org.linphone.activities.*
import org.linphone.activities.main.MainActivity import org.linphone.activities.main.MainActivity
import org.linphone.activities.main.chat.ChatScrollListener import org.linphone.activities.main.chat.ChatScrollListener
import org.linphone.activities.main.chat.adapters.ChatMessagesListAdapter import org.linphone.activities.main.chat.adapters.ChatMessagesListAdapter
import org.linphone.activities.main.chat.data.ChatMessageData
import org.linphone.activities.main.chat.data.EventLogData import org.linphone.activities.main.chat.data.EventLogData
import org.linphone.activities.main.chat.viewmodels.* import org.linphone.activities.main.chat.viewmodels.*
import org.linphone.activities.main.fragments.MasterFragment import org.linphone.activities.main.fragments.MasterFragment
@ -166,6 +168,28 @@ class DetailChatRoomFragment : MasterFragment<ChatRoomDetailFragmentBinding, Cha
layoutManager.stackFromEnd = true layoutManager.stackFromEnd = true
binding.chatMessagesList.layoutManager = layoutManager binding.chatMessagesList.layoutManager = layoutManager
// Swipe action
/*val swipeConfiguration = RecyclerViewSwipeConfiguration()
swipeConfiguration.leftToRightAction = RecyclerViewSwipeConfiguration.Action(icon = R.drawable.menu_reply_default)
val swipeListener = object : RecyclerViewSwipeListener {
override fun onLeftToRightSwipe(viewHolder: RecyclerView.ViewHolder) {
adapter.notifyItemChanged(viewHolder.adapterPosition)
val chatMessageEventLog = adapter.currentList[viewHolder.adapterPosition]
val chatMessage = chatMessageEventLog.chatMessage
if (chatMessage != null) {
chatSendingViewModel.pendingChatMessageToReplyTo.value?.destroy()
chatSendingViewModel.pendingChatMessageToReplyTo.value =
ChatMessageData(chatMessage)
chatSendingViewModel.isPendingAnswer.value = true
}
}
override fun onRightToLeftSwipe(viewHolder: RecyclerView.ViewHolder) {}
}
RecyclerViewSwipeUtils(ItemTouchHelper.RIGHT, swipeConfiguration, swipeListener)
.attachToRecyclerView(binding.chatMessagesList)*/
val chatScrollListener: ChatScrollListener = object : ChatScrollListener(layoutManager) { val chatScrollListener: ChatScrollListener = object : ChatScrollListener(layoutManager) {
override fun onLoadMore(totalItemsCount: Int) { override fun onLoadMore(totalItemsCount: Int) {
listViewModel.loadMoreData(totalItemsCount) listViewModel.loadMoreData(totalItemsCount)
@ -216,6 +240,14 @@ class DetailChatRoomFragment : MasterFragment<ChatRoomDetailFragmentBinding, Cha
} }
}) })
adapter.replyMessageEvent.observe(viewLifecycleOwner, {
it.consume { chatMessage ->
chatSendingViewModel.pendingChatMessageToReplyTo.value?.destroy()
chatSendingViewModel.pendingChatMessageToReplyTo.value = ChatMessageData(chatMessage)
chatSendingViewModel.isPendingAnswer.value = true
}
})
adapter.showImdnForMessageEvent.observe(viewLifecycleOwner, { adapter.showImdnForMessageEvent.observe(viewLifecycleOwner, {
it.consume { chatMessage -> it.consume { chatMessage ->
val args = Bundle() val args = Bundle()
@ -278,6 +310,23 @@ class DetailChatRoomFragment : MasterFragment<ChatRoomDetailFragmentBinding, Cha
} }
}) })
adapter.scrollToChatMessageEvent.observe(viewLifecycleOwner, {
it.consume { chatMessage ->
val events = listViewModel.events.value.orEmpty()
val eventLog = events.find { eventLog ->
if (eventLog.eventLog.type == EventLog.Type.ConferenceChatMessage) {
(eventLog.data as ChatMessageData).chatMessage.messageId == chatMessage.messageId
} else false
}
val index = events.indexOf(eventLog)
try {
binding.chatMessagesList.smoothScrollToPosition(index)
} catch (iae: IllegalArgumentException) {
Log.e("[Chat Room] Can't scroll to position $index")
}
}
})
binding.setBackClickListener { binding.setBackClickListener {
goBack() goBack()
} }

View file

@ -25,6 +25,7 @@ import androidx.lifecycle.ViewModelProvider
import java.io.File import java.io.File
import org.linphone.LinphoneApplication.Companion.corePreferences import org.linphone.LinphoneApplication.Companion.corePreferences
import org.linphone.activities.main.chat.data.ChatMessageAttachmentData import org.linphone.activities.main.chat.data.ChatMessageAttachmentData
import org.linphone.activities.main.chat.data.ChatMessageData
import org.linphone.core.ChatMessage import org.linphone.core.ChatMessage
import org.linphone.core.ChatRoom import org.linphone.core.ChatRoom
import org.linphone.core.ChatRoomCapabilities import org.linphone.core.ChatRoomCapabilities
@ -53,6 +54,10 @@ class ChatMessageSendingViewModel(private val chatRoom: ChatRoom) : ViewModel()
var textToSend = MutableLiveData<String>() var textToSend = MutableLiveData<String>()
val isPendingAnswer = MutableLiveData<Boolean>()
var pendingChatMessageToReplyTo = MutableLiveData<ChatMessageData>()
init { init {
attachments.value = arrayListOf() attachments.value = arrayListOf()
@ -108,14 +113,18 @@ class ChatMessageSendingViewModel(private val chatRoom: ChatRoom) : ViewModel()
} }
fun sendMessage() { fun sendMessage() {
val isBasicChatRoom: Boolean = chatRoom.hasCapability(ChatRoomCapabilities.Basic.toInt()) val pendingMessageToReplyTo = pendingChatMessageToReplyTo.value
val message: ChatMessage = chatRoom.createEmptyMessage() val message: ChatMessage = if (isPendingAnswer.value == true && pendingMessageToReplyTo != null)
chatRoom.createReplyMessage(pendingMessageToReplyTo.chatMessage)
else
chatRoom.createEmptyMessage()
val toSend = textToSend.value val toSend = textToSend.value
if (toSend != null && toSend.isNotEmpty()) { if (toSend != null && toSend.isNotEmpty()) {
message.addUtf8TextContent(toSend.trim()) message.addUtf8TextContent(toSend.trim())
} }
val isBasicChatRoom: Boolean = chatRoom.hasCapability(ChatRoomCapabilities.Basic.toInt())
var fileContent = false var fileContent = false
for (attachment in attachments.value.orEmpty()) { for (attachment in attachments.value.orEmpty()) {
val content = Factory.instance().createContent() val content = Factory.instance().createContent()
@ -144,6 +153,7 @@ class ChatMessageSendingViewModel(private val chatRoom: ChatRoom) : ViewModel()
message.send() message.send()
} }
cancelReply()
attachments.value.orEmpty().forEach(ChatMessageAttachmentData::destroy) attachments.value.orEmpty().forEach(ChatMessageAttachmentData::destroy)
attachments.value = arrayListOf() attachments.value = arrayListOf()
} }
@ -152,4 +162,9 @@ class ChatMessageSendingViewModel(private val chatRoom: ChatRoom) : ViewModel()
val message = chatRoom.createForwardMessage(chatMessage) val message = chatRoom.createForwardMessage(chatMessage)
message.send() message.send()
} }
fun cancelReply() {
pendingChatMessageToReplyTo.value?.destroy()
isPendingAnswer.value = false
}
} }

View file

@ -41,6 +41,15 @@ class LinphoneUtils {
private const val RECORDING_DATE_PATTERN = "dd-MM-yyyy-HH-mm-ss" private const val RECORDING_DATE_PATTERN = "dd-MM-yyyy-HH-mm-ss"
fun getDisplayName(address: Address): String { fun getDisplayName(address: Address): String {
if (address.displayName == null) {
val account = coreContext.core.accountList.find { account ->
account.params.identityAddress?.asStringUriOnly() == address.asStringUriOnly()
}
val localDisplayName = account?.params?.identityAddress?.displayName
if (localDisplayName != null) {
return localDisplayName
}
}
return address.displayName ?: address.username ?: "" return address.displayName ?: address.username ?: ""
} }

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

View file

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle"> <shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
<solid android:color="@color/chat_bubble_incoming_color"/> <solid android:color="@color/chat_bubble_incoming_color"/>
<corners android:radius="6.7dp"/> <corners android:radius="@dimen/chat_message_round_corner_radius"/>
</shape> </shape>

View file

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle"> <shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
<solid android:color="@color/chat_bubble_incoming_color"/> <solid android:color="@color/chat_bubble_incoming_color"/>
<corners android:radius="6.7dp" android:bottomLeftRadius="0dp"/> <corners android:radius="@dimen/chat_message_round_corner_radius" android:bottomLeftRadius="0dp"/>
</shape> </shape>

View file

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle"> <shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
<solid android:color="@color/chat_bubble_incoming_color"/> <solid android:color="@color/chat_bubble_incoming_color"/>
<corners android:radius="6.7dp" android:topLeftRadius="0dp" android:bottomLeftRadius="0dp"/> <corners android:radius="@dimen/chat_message_round_corner_radius" android:topLeftRadius="0dp" android:bottomLeftRadius="0dp"/>
</shape> </shape>

View file

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle"> <shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
<solid android:color="@color/chat_bubble_incoming_color"/> <solid android:color="@color/chat_bubble_incoming_color"/>
<corners android:radius="6.7dp" android:topLeftRadius="0dp"/> <corners android:radius="@dimen/chat_message_round_corner_radius" android:topLeftRadius="0dp"/>
</shape> </shape>

View file

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle"> <shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
<solid android:color="@color/chat_bubble_outgoing_color"/> <solid android:color="@color/chat_bubble_outgoing_color"/>
<corners android:radius="6.7dp"/> <corners android:radius="@dimen/chat_message_round_corner_radius"/>
</shape> </shape>

View file

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle"> <shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
<solid android:color="@color/chat_bubble_outgoing_color"/> <solid android:color="@color/chat_bubble_outgoing_color"/>
<corners android:radius="6.7dp" android:bottomRightRadius="0dp"/> <corners android:radius="@dimen/chat_message_round_corner_radius" android:bottomRightRadius="0dp"/>
</shape> </shape>

View file

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle"> <shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
<solid android:color="@color/chat_bubble_outgoing_color"/> <solid android:color="@color/chat_bubble_outgoing_color"/>
<corners android:radius="6.7dp" android:topRightRadius="0dp" android:bottomRightRadius="0dp"/> <corners android:radius="@dimen/chat_message_round_corner_radius" android:topRightRadius="0dp" android:bottomRightRadius="0dp"/>
</shape> </shape>

View file

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle"> <shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
<solid android:color="@color/chat_bubble_outgoing_color"/> <solid android:color="@color/chat_bubble_outgoing_color"/>
<corners android:radius="6.7dp" android:topRightRadius="0dp"/> <corners android:radius="@dimen/chat_message_round_corner_radius" android:topRightRadius="0dp"/>
</shape> </shape>

View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="?attr/backgroundColor"/>
<corners android:radius="@dimen/chat_message_round_corner_radius"/>
<padding android:left="0dp" android:top="0dp" android:right="0dp" android:bottom="0dp" />
</shape>

View file

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="@color/chat_bubble_file_background_color"/>
<stroke android:color="@color/chat_bubble_file_background_border_color" android:width="1dp" />
<corners android:radius="@dimen/chat_message_round_corner_radius"/>
<padding android:left="0dp" android:top="0dp" android:right="0dp" android:bottom="0dp" />
</shape>

View file

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
<solid android:color="@color/chat_bubble_incoming_color_dark"/>
<corners android:radius="@dimen/chat_message_round_corner_radius" android:topRightRadius="0dp" android:bottomRightRadius="0dp"/>
</shape>

View file

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
<solid android:color="@color/chat_bubble_outgoing_color_dark"/>
<corners android:radius="@dimen/chat_message_round_corner_radius" android:topRightRadius="0dp" android:bottomRightRadius="0dp"/>
</shape>

View file

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<bitmap android:src="@drawable/menu_reply_default"
android:tint="?attr/drawableTintColor"/>
</item>
</selector>

View file

@ -13,6 +13,9 @@
<variable <variable
name="clickListener" name="clickListener"
type="android.view.View.OnClickListener" /> type="android.view.View.OnClickListener" />
<variable
name="replyClickListener"
type="android.view.View.OnClickListener" />
<variable <variable
name="position" name="position"
type="Integer"/> type="Integer"/>
@ -90,7 +93,6 @@
android:layout_marginBottom="1dp" android:layout_marginBottom="1dp"
android:layout_marginRight="@{data.chatMessage.outgoing ? @dimen/outgoing_chat_message_bubble_right_margin : @dimen/incoming_chat_message_bubble_right_margin}" android:layout_marginRight="@{data.chatMessage.outgoing ? @dimen/outgoing_chat_message_bubble_right_margin : @dimen/incoming_chat_message_bubble_right_margin}"
android:layout_marginLeft="@{selectionListViewModel.isEditionEnabled ? @dimen/edit_chat_message_bubble_left_margin : !data.chatMessage.outgoing ? @dimen/incoming_chat_message_bubble_left_margin : @dimen/outgoing_chat_message_bubble_left_margin}" android:layout_marginLeft="@{selectionListViewModel.isEditionEnabled ? @dimen/edit_chat_message_bubble_left_margin : !data.chatMessage.outgoing ? @dimen/incoming_chat_message_bubble_left_margin : @dimen/outgoing_chat_message_bubble_left_margin}"
android:paddingTop="5dp"
android:paddingBottom="5dp"> android:paddingBottom="5dp">
<LinearLayout <LinearLayout
@ -98,7 +100,8 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="horizontal" android:orientation="horizontal"
android:gravity="center_vertical|right" android:gravity="center_vertical"
android:layout_marginTop="5dp"
android:layout_marginRight="5dp" android:layout_marginRight="5dp"
android:layout_marginLeft="5dp"> android:layout_marginLeft="5dp">
@ -123,9 +126,52 @@
</LinearLayout> </LinearLayout>
<LinearLayout
android:visibility="@{data.chatMessage.reply ? View.VISIBLE : View.GONE, default=gone}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_vertical"
android:layout_marginTop="5dp"
android:layout_marginRight="5dp"
android:layout_marginLeft="5dp">
<ImageView
android:layout_width="15dp"
android:layout_height="wrap_content"
android:adjustViewBounds="true"
android:src="@drawable/replied_message_default"
android:contentDescription="@string/content_description_replied_message"
android:layout_marginRight="3dp"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@color/chat_bubble_text_color"
android:textSize="10sp"
android:fontFamily="sans-serif"
android:textStyle="normal"
android:lineSpacingExtra="3.3sp"
android:text="@string/chat_message_replied"
tools:ignore="SmallSp" />
</LinearLayout>
<include layout="@layout/chat_message_reply_bubble"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="10dp"
android:layout_marginRight="10dp"
android:layout_marginTop="5dp"
android:layout_gravity="@{data.chatMessage.outgoing ? Gravity.RIGHT : Gravity.LEFT}"
app:data="@{data.replyData}"
app:clickListener="@{replyClickListener}"
android:visibility="@{data.chatMessage.reply ? View.VISIBLE : View.GONE, default=gone}" />
<com.google.android.flexbox.FlexboxLayout <com.google.android.flexbox.FlexboxLayout
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="5dp"
android:layout_marginLeft="5dp" android:layout_marginLeft="5dp"
android:layout_marginRight="5dp" android:layout_marginRight="5dp"
android:longClickable="true" android:longClickable="true"

View file

@ -12,6 +12,9 @@
<variable <variable
name="forwardClickListener" name="forwardClickListener"
type="View.OnClickListener" /> type="View.OnClickListener" />
<variable
name="replyClickListener"
type="View.OnClickListener" />
<variable <variable
name="imdnClickListener" name="imdnClickListener"
type="View.OnClickListener" /> type="View.OnClickListener" />
@ -70,6 +73,15 @@
style="@style/popup_item_font" style="@style/popup_item_font"
android:text="@string/chat_message_context_menu_forward" /> android:text="@string/chat_message_context_menu_forward" />
<TextView
android:layout_width="match_parent"
android:layout_height="@dimen/chat_message_popup_item_height"
android:background="@drawable/menu_background"
android:onClick="@{replyClickListener}"
android:drawableRight="@drawable/menu_reply"
style="@style/popup_item_font"
android:text="@string/chat_message_context_menu_reply" />
<TextView <TextView
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="@dimen/chat_message_popup_item_height" android:layout_height="@dimen/chat_message_popup_item_height"

View file

@ -0,0 +1,74 @@
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<data>
<import type="android.view.View"/>
<variable
name="cancelClickListener"
type="android.view.View.OnClickListener" />
<variable
name="data"
type="org.linphone.activities.main.chat.data.ChatMessageData" />
</data>
<RelativeLayout
android:background="@{data.chatMessage.isOutgoing ? @color/chat_bubble_outgoing_color : @color/chat_bubble_incoming_color, default=@color/chat_bubble_incoming_color}"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="10dp"
android:paddingBottom="10dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="10dp"
android:orientation="vertical"
android:layout_toLeftOf="@id/clear_reply">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
style="@style/chat_message_reply_sender_font"
android:text="@{data.contact.fullName ?? data.displayName, default=Tintin}"/>
<HorizontalScrollView
android:visibility="@{data.contents.size() == 0 ? View.GONE : View.VISIBLE}"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="5dp">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
app:entries="@{data.contents}"
app:layout="@{@layout/chat_message_reply_preview_content_cell}"/>
</HorizontalScrollView>
<TextView
android:visibility="@{data.text.length() == 0 ? View.GONE : View.VISIBLE}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="5dp"
style="@style/chat_message_reply_font"
android:text="@{data.text, default=`Lorem Ipsum`}"/>
</LinearLayout>
<ImageView
android:id="@+id/clear_reply"
android:onClick="@{cancelClickListener}"
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
android:layout_marginLeft="5dp"
android:layout_marginRight="10dp"
android:padding="5dp"
android:src="@drawable/field_clean" />
</RelativeLayout>
</layout>

View file

@ -0,0 +1,79 @@
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<data>
<import type="android.view.View"/>
<variable
name="clickListener"
type="android.view.View.OnClickListener" />
<variable
name="data"
type="org.linphone.activities.main.chat.data.ChatMessageData" />
</data>
<LinearLayout
android:background="@drawable/chat_bubble_reply_background"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="10dp"
android:layout_marginRight="10dp"
android:layout_marginTop="5dp"
android:orientation="horizontal"
android:onClick="@{clickListener}">
<View
android:id="@+id/color"
android:layout_width="10dp"
android:layout_height="match_parent"
android:background="@{data.chatMessage.isOutgoing ? @drawable/chat_bubble_reply_outgoing_indicator : @drawable/chat_bubble_reply_incoming_indicator, default=@drawable/chat_bubble_reply_outgoing_indicator}" />
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="5dp"
android:visibility="@{data.contents.size() > 0 ? View.VISIBLE : View.GONE}">
<com.google.android.flexbox.FlexboxLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintWidth_max="120dp"
app:entries="@{data.contents}"
app:layout="@{@layout/chat_message_reply_content_cell}"
app:flexWrap="wrap"
app:alignItems="center"
app:justifyContent="flex_start"/>
</androidx.constraintlayout.widget.ConstraintLayout>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="5dp"
android:layout_marginRight="10dp"
android:paddingTop="10dp"
android:paddingBottom="10dp"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
style="@style/chat_message_reply_sender_font"
android:text="@{data.contact.fullName ?? data.displayName, default=Tintin}"/>
<TextView
android:visibility="@{data.text.length() == 0 ? View.GONE : View.VISIBLE}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:singleLine="false"
android:layout_marginTop="5dp"
style="@style/chat_message_reply_font"
android:text="@{data.text, default=`Lorem ipsum`}"/>
</LinearLayout>
</LinearLayout>
</layout>

View file

@ -0,0 +1,61 @@
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<data>
<import type="android.view.View"/>
<import type="android.widget.ImageView.ScaleType"/>
<variable
name="data"
type="org.linphone.activities.main.chat.data.ChatMessageContentData" />
</data>
<RelativeLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="5dp">
<ImageView
android:contentDescription="@string/content_description_downloaded_file_transfer"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:maxHeight="@dimen/chat_message_bubble_image_height_small"
android:layout_size="@{data.alone ? 0f : @dimen/chat_message_small_bubble_file_size}"
app:glidePath="@{data.filePath}"
android:visibility="@{data.image ? View.VISIBLE : View.GONE}"
android:scaleType="@{ScaleType.CENTER_CROP}"
android:adjustViewBounds="true" />
<ImageView
android:contentDescription="@string/content_description_downloaded_file_transfer"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:maxHeight="@dimen/chat_message_bubble_image_height_small"
android:layout_size="@{data.alone ? 0f : @dimen/chat_message_small_bubble_file_size}"
android:src="@{data.videoPreview}"
android:visibility="@{data.video ? View.VISIBLE : View.GONE}"
android:scaleType="@{ScaleType.CENTER_CROP}"
android:adjustViewBounds="true" />
<ImageView
android:visibility="@{data.video ? View.VISIBLE : View.GONE}"
android:layout_width="20dp"
android:layout_height="20dp"
android:src="@drawable/recording_play_pause"
android:contentDescription="@string/content_description_chat_message_video_attachment"
android:layout_centerInParent="true"/>
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:maxHeight="@dimen/chat_message_bubble_image_height_small"
android:layout_size="@{data.alone ? 0f : @dimen/chat_message_small_bubble_file_size}"
android:background="@drawable/chat_bubble_reply_file_background"
android:padding="10dp"
android:contentDescription="@{data.fileName}"
android:visibility="@{data.downloadable || data.pdf || data.audio || data.genericFile ? View.VISIBLE : View.GONE}"
android:src="@{data.video ? @drawable/file_video : (data.image ? @drawable/file_picture : (data.pdf ? @drawable/file_pdf : (data.audio ? @drawable/file_audio : @drawable/file))), default=@drawable/file}" />
</RelativeLayout>
</layout>

View file

@ -0,0 +1,55 @@
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<data>
<import type="android.view.View"/>
<import type="android.widget.ImageView.ScaleType"/>
<variable
name="data"
type="org.linphone.activities.main.chat.data.ChatMessageContentData" />
</data>
<RelativeLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="5dp">
<ImageView
android:contentDescription="@string/content_description_downloaded_file_transfer"
android:layout_width="@dimen/chat_message_small_bubble_file_size"
android:layout_height="@dimen/chat_message_small_bubble_file_size"
app:glidePath="@{data.filePath}"
android:visibility="@{data.image ? View.VISIBLE : View.GONE}"
android:scaleType="@{ScaleType.CENTER_CROP}"
android:adjustViewBounds="true" />
<ImageView
android:contentDescription="@string/content_description_downloaded_file_transfer"
android:layout_width="@dimen/chat_message_small_bubble_file_size"
android:layout_height="@dimen/chat_message_small_bubble_file_size"
android:src="@{data.videoPreview}"
android:visibility="@{data.video ? View.VISIBLE : View.GONE}"
android:scaleType="@{ScaleType.CENTER_CROP}"
android:adjustViewBounds="true" />
<ImageView
android:visibility="@{data.video ? View.VISIBLE : View.GONE}"
android:layout_width="20dp"
android:layout_height="20dp"
android:src="@drawable/recording_play_pause"
android:contentDescription="@string/content_description_chat_message_video_attachment"
android:layout_centerInParent="true"/>
<ImageView
android:layout_width="@dimen/chat_message_small_bubble_file_size"
android:layout_height="@dimen/chat_message_small_bubble_file_size"
android:background="@drawable/chat_bubble_reply_file_background"
android:padding="10dp"
android:contentDescription="@{data.fileName}"
android:visibility="@{data.downloadable || data.pdf || data.audio || data.genericFile ? View.VISIBLE : View.GONE}"
android:src="@{data.video ? @drawable/file_video : (data.image ? @drawable/file_picture : (data.pdf ? @drawable/file_pdf : (data.audio ? @drawable/file_audio : @drawable/file))), default=@drawable/file}" />
</RelativeLayout>
</layout>

View file

@ -147,6 +147,12 @@
android:background="?attr/lightToolbarBackgroundColor" android:background="?attr/lightToolbarBackgroundColor"
android:orientation="vertical"> android:orientation="vertical">
<include
android:visibility="@{chatSendingViewModel.isPendingAnswer ? View.VISIBLE : View.GONE, default=gone}"
app:data="@{chatSendingViewModel.pendingChatMessageToReplyTo}"
app:cancelClickListener="@{() -> chatSendingViewModel.cancelReply()}"
layout="@layout/chat_message_reply" />
<HorizontalScrollView <HorizontalScrollView
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
@ -166,7 +172,9 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:gravity="center_vertical" android:gravity="center_vertical"
android:background="?attr/backgroundColor" android:background="@color/toolbar_color"
android:layout_marginTop="5dp"
android:layout_marginBottom="5dp"
android:orientation="horizontal"> android:orientation="horizontal">
<ImageView <ImageView
@ -182,6 +190,7 @@
android:id="@+id/message" android:id="@+id/message"
android:enabled="@{!chatSendingViewModel.isReadOnly}" android:enabled="@{!chatSendingViewModel.isReadOnly}"
android:text="@={chatSendingViewModel.textToSend}" android:text="@={chatSendingViewModel.textToSend}"
android:hint="@{chatSendingViewModel.isPendingAnswer ? @string/chat_room_sending_reply_hint : @string/chat_room_sending_message_hint}"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="center_vertical" android:layout_gravity="center_vertical"

View file

@ -605,4 +605,8 @@
<string name="chat_room_choose_conversation_for_file_sharing">Sélectionnez ou créez une conversation pour partager le(s) fichier(s)</string> <string name="chat_room_choose_conversation_for_file_sharing">Sélectionnez ou créez une conversation pour partager le(s) fichier(s)</string>
<string name="chat_room_choose_conversation_for_text_sharing">Sélectionnez ou créez une conversation pour partager le texte</string> <string name="chat_room_choose_conversation_for_text_sharing">Sélectionnez ou créez une conversation pour partager le texte</string>
<string name="chat_settins_enable_ephemeral_messages_beta_title">Messages éphémères (bêta)</string> <string name="chat_settins_enable_ephemeral_messages_beta_title">Messages éphémères (bêta)</string>
<string name="chat_message_context_menu_reply">Répondre</string>
<string name="chat_room_sending_reply_hint">Réponse</string>
<string name="chat_room_sending_message_hint">Message</string>
<string name="chat_message_replied">Réponse</string>
</resources> </resources>

View file

@ -20,13 +20,18 @@
<color name="toolbar_color">#e1e1e1</color> <color name="toolbar_color">#e1e1e1</color>
<color name="header_background_color">#f3f3f3</color> <color name="header_background_color">#f3f3f3</color>
<color name="security_gray_color">#c2c2c2</color> <color name="security_gray_color">#c2c2c2</color>
<color name="chat_bubble_text_color">#a1a1a1</color> <color name="chat_bubble_text_color">#a1a1a1</color>
<color name="chat_bubble_incoming_color">#f3f3f3</color> <color name="chat_bubble_incoming_color">#f3f3f3</color>
<color name="chat_bubble_incoming_color_dark">#9b9b9b</color>
<!-- Do not use a color with transparency here, it will look bad when in dark mode! <!-- Do not use a color with transparency here, it will look bad when in dark mode!
We use a solid color representation of the primary color but with 10% transparency. We use a solid color representation of the primary color but with 10% transparency.
For each RGB value of the color, the math to get is: For each RGB value of the color, the math to get is:
C' = C * (A / 255) + 255 * (1 - (A / 255)) so for 10% transparency: C' = C * 0.1 + 229.5 --> C' = C * (A / 255) + 255 * (1 - (A / 255)) so for 10% transparency: C' = C * 0.1 + 229.5 -->
<color name="chat_bubble_outgoing_color">#ffeee5</color> <color name="chat_bubble_outgoing_color">#ffeee5</color>
<color name="chat_bubble_outgoing_color_dark">#ff9e67</color>
<color name="chat_bubble_file_background_color">#f4f4f4</color>
<color name="chat_bubble_file_background_border_color">#dedede</color>
<color name="disabled_color">#808080</color> <color name="disabled_color">#808080</color>
<color name="contact_disabled_color">#99ffffff</color> <color name="contact_disabled_color">#99ffffff</color>

View file

@ -9,6 +9,7 @@
<dimen name="chat_message_bubble_image_height_small">100dp</dimen> <dimen name="chat_message_bubble_image_height_small">100dp</dimen>
<dimen name="chat_message_bubble_file_size">120dp</dimen> <dimen name="chat_message_bubble_file_size">120dp</dimen>
<dimen name="chat_message_bubble_file_icon_size">30dp</dimen> <dimen name="chat_message_bubble_file_icon_size">30dp</dimen>
<dimen name="chat_message_small_bubble_file_size">50dp</dimen>
<dimen name="chat_message_bubble_desired_height">600dp</dimen> <dimen name="chat_message_bubble_desired_height">600dp</dimen>
<dimen name="video_preview_max_size">200dp</dimen> <dimen name="video_preview_max_size">200dp</dimen>
<dimen name="video_preview_pip_max_size">45dp</dimen> <dimen name="video_preview_pip_max_size">45dp</dimen>
@ -26,4 +27,5 @@
<dimen name="call_overlay_size">60dp</dimen> <dimen name="call_overlay_size">60dp</dimen>
<dimen name="chat_message_popup_width">250dp</dimen> <dimen name="chat_message_popup_width">250dp</dimen>
<dimen name="chat_message_popup_item_height">50dp</dimen> <dimen name="chat_message_popup_item_height">50dp</dimen>
<dimen name="chat_message_round_corner_radius">6.7dp</dimen>
</resources> </resources>

View file

@ -168,9 +168,11 @@
<string name="chat_message_imdn_undelivered">Undelivered</string> <string name="chat_message_imdn_undelivered">Undelivered</string>
<string name="chat_message_imdn_sent">Sent</string> <string name="chat_message_imdn_sent">Sent</string>
<string name="chat_message_forwarded">Forwarded</string> <string name="chat_message_forwarded">Forwarded</string>
<string name="chat_message_replied">Reply</string>
<string name="chat_message_context_menu_resend">Resend</string> <string name="chat_message_context_menu_resend">Resend</string>
<string name="chat_message_context_menu_copy_text">Copy text</string> <string name="chat_message_context_menu_copy_text">Copy text</string>
<string name="chat_message_context_menu_forward">Forward</string> <string name="chat_message_context_menu_forward">Forward</string>
<string name="chat_message_context_menu_reply">Reply</string>
<string name="chat_message_context_menu_imdn_info">Delivery status</string> <string name="chat_message_context_menu_imdn_info">Delivery status</string>
<string name="chat_message_context_menu_delete">Delete</string> <string name="chat_message_context_menu_delete">Delete</string>
<string name="chat_message_context_menu_add_to_contacts">Add to contacts</string> <string name="chat_message_context_menu_add_to_contacts">Add to contacts</string>
@ -214,6 +216,8 @@
<string name="chat_message_cant_open_file_in_app_dialog_message">Would you like to open it as text or export it (unencrypted) to a third party app if available?</string> <string name="chat_message_cant_open_file_in_app_dialog_message">Would you like to open it as text or export it (unencrypted) to a third party app if available?</string>
<string name="chat_message_cant_open_file_in_app_dialog_export_button">Export</string> <string name="chat_message_cant_open_file_in_app_dialog_export_button">Export</string>
<string name="chat_message_cant_open_file_in_app_dialog_open_as_text_button">Open as text</string> <string name="chat_message_cant_open_file_in_app_dialog_open_as_text_button">Open as text</string>
<string name="chat_room_sending_reply_hint">Reply</string>
<string name="chat_room_sending_message_hint">Message</string>
<!-- Recordings --> <!-- Recordings -->
<string name="recordings_empty_list">No recordings</string> <string name="recordings_empty_list">No recordings</string>
@ -622,6 +626,7 @@
<string name="content_description_downloaded_file_transfer">Attached file</string> <string name="content_description_downloaded_file_transfer">Attached file</string>
<string name="content_description_delivery_status">Message delivery status</string> <string name="content_description_delivery_status">Message delivery status</string>
<string name="content_description_forwarded_message">Message has been forwarded</string> <string name="content_description_forwarded_message">Message has been forwarded</string>
<string name="content_description_replied_message">Message is a reply</string>
<string name="content_description_forward_message">Forward message in this conversation</string> <string name="content_description_forward_message">Forward message in this conversation</string>
<string name="content_description_ephemeral_message">Message is ephemeral</string> <string name="content_description_ephemeral_message">Message is ephemeral</string>
<string name="content_description_linphone_user">Contact is a &appName; user</string> <string name="content_description_linphone_user">Contact is a &appName; user</string>
@ -636,6 +641,7 @@
<string name="content_description_show_chat_room_menu">Show chat room menu</string> <string name="content_description_show_chat_room_menu">Show chat room menu</string>
<string name="content_description_enter_edition_mode">Enter edition mode</string> <string name="content_description_enter_edition_mode">Enter edition mode</string>
<string name="content_description_attach_file">Attach a file to the message</string> <string name="content_description_attach_file">Attach a file to the message</string>
<string name="content_description_attached_file">File attached to the message</string>
<string name="content_description_send_message">Send message</string> <string name="content_description_send_message">Send message</string>
<string name="content_description_toggle_participant_devices_list">Show or hide the participant devices</string> <string name="content_description_toggle_participant_devices_list">Show or hide the participant devices</string>
<string name="content_description_ephemeral_duration_selected">Ephemeral duration selected</string> <string name="content_description_ephemeral_duration_selected">Ephemeral duration selected</string>

View file

@ -111,6 +111,20 @@
<item name="android:textSize">13sp</item> <item name="android:textSize">13sp</item>
</style> </style>
<!-- Chat related -->
<style name="chat_message_reply_sender_font">
<item name="android:textColor">?attr/primaryTextColor</item>
<item name="android:textSize">13sp</item>
<item name="android:fontFamily">sans-serif-medium</item>
</style>
<style name="chat_message_reply_font">
<item name="android:textColor">?attr/primaryTextColor</item>
<item name="android:textSize">15sp</item>
<item name="android:fontFamily">sans-serif</item>
</style>
<!-- Call stats --> <!-- Call stats -->
<style name="call_stats_title_font" parent="@android:style/TextAppearance.Medium"> <style name="call_stats_title_font" parent="@android:style/TextAppearance.Medium">