Made chat message delivery bottom sheet generic to also use it to display emoji reactions lists

This commit is contained in:
Sylvain Berfini 2023-10-23 16:10:21 +02:00
parent 18f0d9109e
commit f855426a9f
13 changed files with 269 additions and 168 deletions

View file

@ -9,19 +9,19 @@ import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import org.linphone.R
import org.linphone.databinding.ChatMessageDeliveryListCellBinding
import org.linphone.ui.main.chat.model.ChatMessageParticipantDeliveryModel
import org.linphone.databinding.ChatMessageBottomSheetListCellBinding
import org.linphone.ui.main.chat.model.ChatMessageBottomSheetParticipantModel
class ChatMessageDeliveryAdapter(
class ChatMessageBottomSheetAdapter(
private val viewLifecycleOwner: LifecycleOwner
) : ListAdapter<ChatMessageParticipantDeliveryModel, RecyclerView.ViewHolder>(
ChatDeliveryDiffCallback()
) : ListAdapter<ChatMessageBottomSheetParticipantModel, RecyclerView.ViewHolder>(
ParticipantDiffCallback()
) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
val binding: ChatMessageDeliveryListCellBinding = DataBindingUtil.inflate(
val binding: ChatMessageBottomSheetListCellBinding = DataBindingUtil.inflate(
LayoutInflater.from(parent.context),
R.layout.chat_message_delivery_list_cell,
R.layout.chat_message_bottom_sheet_list_cell,
parent,
false
)
@ -33,12 +33,12 @@ class ChatMessageDeliveryAdapter(
}
inner class ViewHolder(
val binding: ChatMessageDeliveryListCellBinding
val binding: ChatMessageBottomSheetListCellBinding
) : RecyclerView.ViewHolder(binding.root) {
@UiThread
fun bind(deliveryModel: ChatMessageParticipantDeliveryModel) {
fun bind(bottomSheetModel: ChatMessageBottomSheetParticipantModel) {
with(binding) {
model = deliveryModel
model = bottomSheetModel
lifecycleOwner = viewLifecycleOwner
@ -47,19 +47,19 @@ class ChatMessageDeliveryAdapter(
}
}
private class ChatDeliveryDiffCallback : DiffUtil.ItemCallback<ChatMessageParticipantDeliveryModel>() {
private class ParticipantDiffCallback : DiffUtil.ItemCallback<ChatMessageBottomSheetParticipantModel>() {
override fun areItemsTheSame(
oldItem: ChatMessageParticipantDeliveryModel,
newItem: ChatMessageParticipantDeliveryModel
oldItem: ChatMessageBottomSheetParticipantModel,
newItem: ChatMessageBottomSheetParticipantModel
): Boolean {
return oldItem.sipUri == newItem.sipUri
}
override fun areContentsTheSame(
oldItem: ChatMessageParticipantDeliveryModel,
newItem: ChatMessageParticipantDeliveryModel
oldItem: ChatMessageBottomSheetParticipantModel,
newItem: ChatMessageBottomSheetParticipantModel
): Boolean {
return oldItem.time == newItem.time
return oldItem.value == newItem.value
}
}
}

View file

@ -50,6 +50,9 @@ class ConversationEventAdapter(
val showDeliveryForChatMessageModelEvent: MutableLiveData<Event<ChatMessageModel>> by lazy {
MutableLiveData<Event<ChatMessageModel>>()
}
val showReactionForChatMessageModelEvent: MutableLiveData<Event<ChatMessageModel>> by lazy {
MutableLiveData<Event<ChatMessageModel>>()
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
return when (viewType) {
@ -123,6 +126,9 @@ class ConversationEventAdapter(
setShowDeliveryInfoClickListener {
showDeliveryForChatMessageModelEvent.value = Event(message)
}
setShowReactionInfoClickListener {
showReactionForChatMessageModelEvent.value = Event(message)
}
lifecycleOwner = viewLifecycleOwner
executePendingBindings()
@ -140,6 +146,9 @@ class ConversationEventAdapter(
setShowDeliveryInfoClickListener {
showDeliveryForChatMessageModelEvent.value = Event(message)
}
setShowReactionInfoClickListener {
showReactionForChatMessageModelEvent.value = Event(message)
}
lifecycleOwner = viewLifecycleOwner
executePendingBindings()

View file

@ -56,10 +56,11 @@ import org.linphone.core.ChatMessage
import org.linphone.core.tools.Log
import org.linphone.databinding.ChatConversationFragmentBinding
import org.linphone.databinding.ChatConversationLongPressMenuBinding
import org.linphone.ui.main.chat.adapter.ChatMessageDeliveryAdapter
import org.linphone.ui.main.chat.adapter.ChatMessageBottomSheetAdapter
import org.linphone.ui.main.chat.adapter.ConversationEventAdapter
import org.linphone.ui.main.chat.model.ChatMessageDeliveryModel
import org.linphone.ui.main.chat.model.ChatMessageModel
import org.linphone.ui.main.chat.model.ChatMessageReactionsModel
import org.linphone.ui.main.chat.viewmodel.ConversationViewModel
import org.linphone.ui.main.fragment.GenericFragment
import org.linphone.utils.AppUtils
@ -83,7 +84,7 @@ class ConversationFragment : GenericFragment() {
private lateinit var adapter: ConversationEventAdapter
private lateinit var deliveryAdapter: ChatMessageDeliveryAdapter
private lateinit var bottomSheetAdapter: ChatMessageBottomSheetAdapter
private val pickMedia = registerForActivityResult(
ActivityResultContracts.PickMultipleVisualMedia()
@ -162,15 +163,15 @@ class ConversationFragment : GenericFragment() {
binding.eventsList.setHasFixedSize(true)
binding.eventsList.adapter = adapter
deliveryAdapter = ChatMessageDeliveryAdapter(viewLifecycleOwner)
binding.messageDelivery.deliveryList.setHasFixedSize(true)
binding.messageDelivery.deliveryList.adapter = deliveryAdapter
val layoutManager = LinearLayoutManager(requireContext())
binding.eventsList.layoutManager = layoutManager
val deliveryLayoutManager = LinearLayoutManager(requireContext())
binding.messageDelivery.deliveryList.layoutManager = deliveryLayoutManager
bottomSheetAdapter = ChatMessageBottomSheetAdapter(viewLifecycleOwner)
binding.messageBottomSheet.bottomSheetList.setHasFixedSize(true)
binding.messageBottomSheet.bottomSheetList.adapter = bottomSheetAdapter
val bottomSheetLayoutManager = LinearLayoutManager(requireContext())
binding.messageBottomSheet.bottomSheetList.layoutManager = bottomSheetLayoutManager
adapter.chatMessageLongPressEvent.observe(viewLifecycleOwner) {
it.consume { model ->
@ -195,13 +196,23 @@ class ConversationFragment : GenericFragment() {
adapter.showDeliveryForChatMessageModelEvent.observe(viewLifecycleOwner) {
it.consume { model ->
if (viewModel.isGroup.value == true) {
showDeliveryBottomSheetDialog(model)
showDeliveryBottomSheetDialog(model, showDelivery = true)
} else {
Log.w("$TAG Conversation is not a group, not showing delivery bottom sheet")
}
}
}
adapter.showReactionForChatMessageModelEvent.observe(viewLifecycleOwner) {
it.consume { model ->
if (viewModel.isGroup.value == true) {
showDeliveryBottomSheetDialog(model, showReactions = true)
} else {
Log.w("$TAG Conversation is not a group, not showing reactions bottom sheet")
}
}
}
binding.setOpenFilePickerClickListener {
Log.i("$TAG Opening media picker")
pickMedia.launch(
@ -320,74 +331,141 @@ class ConversationFragment : GenericFragment() {
}
@UiThread
private fun showDeliveryBottomSheetDialog(chatMessageModel: ChatMessageModel) {
val deliveryBottomSheetBehavior = BottomSheetBehavior.from(binding.messageDelivery.root)
private fun showDeliveryBottomSheetDialog(
chatMessageModel: ChatMessageModel,
showDelivery: Boolean = false,
showReactions: Boolean = false
) {
val deliveryBottomSheetBehavior = BottomSheetBehavior.from(binding.messageBottomSheet.root)
deliveryBottomSheetBehavior.state = BottomSheetBehavior.STATE_COLLAPSED
binding.messageDelivery.setHandleClickedListener {
binding.messageBottomSheet.setHandleClickedListener {
deliveryBottomSheetBehavior.state = BottomSheetBehavior.STATE_COLLAPSED
}
lifecycleScope.launch {
withContext(Dispatchers.IO) {
// Wait for previous bottom sheet to go away
delay(200)
withContext(Dispatchers.Main) {
coreContext.postOnCoreThread {
val model = ChatMessageDeliveryModel(chatMessageModel.chatMessage)
coreContext.postOnMainThread {
model.deliveryModels.observe(viewLifecycleOwner) {
deliveryAdapter.submitList(it)
}
binding.messageDelivery.tabs.removeAllTabs()
binding.messageDelivery.tabs.addTab(
binding.messageDelivery.tabs.newTab().setText(model.readLabel.value).setId(
ChatMessage.State.Displayed.toInt()
)
)
binding.messageDelivery.tabs.addTab(
binding.messageDelivery.tabs.newTab().setText(
model.receivedLabel.value
).setId(
ChatMessage.State.DeliveredToUser.toInt()
)
)
binding.messageDelivery.tabs.addTab(
binding.messageDelivery.tabs.newTab().setText(model.sentLabel.value).setId(
ChatMessage.State.Delivered.toInt()
)
)
binding.messageDelivery.tabs.addTab(
binding.messageDelivery.tabs.newTab().setText(
model.errorLabel.value
).setId(
ChatMessage.State.NotDelivered.toInt()
)
)
binding.messageDelivery.tabs.setOnTabSelectedListener(object : OnTabSelectedListener {
override fun onTabSelected(tab: TabLayout.Tab?) {
val state = tab?.id ?: ChatMessage.State.Displayed.toInt()
model.computeListForState(ChatMessage.State.fromInt(state))
}
override fun onTabUnselected(tab: TabLayout.Tab?) {
}
override fun onTabReselected(tab: TabLayout.Tab?) {
}
})
binding.messageDelivery.model = model
binding.messageDelivery.root.visibility = View.VISIBLE
deliveryBottomSheetBehavior.state = BottomSheetBehavior.STATE_EXPANDED
}
if (showDelivery) {
prepareBottomSheetForDeliveryStatus(chatMessageModel)
} else if (showReactions) {
prepareBottomSheetForReactions(chatMessageModel)
}
binding.messageBottomSheet.root.visibility = View.VISIBLE
deliveryBottomSheetBehavior.state = BottomSheetBehavior.STATE_EXPANDED
}
}
}
}
@UiThread
private fun prepareBottomSheetForDeliveryStatus(chatMessageModel: ChatMessageModel) {
coreContext.postOnCoreThread {
val model = ChatMessageDeliveryModel(chatMessageModel.chatMessage)
coreContext.postOnMainThread {
val tabs = binding.messageBottomSheet.tabs
tabs.removeAllTabs()
tabs.addTab(
tabs.newTab().setText(model.readLabel.value).setId(
ChatMessage.State.Displayed.toInt()
)
)
tabs.addTab(
tabs.newTab().setText(
model.receivedLabel.value
).setId(
ChatMessage.State.DeliveredToUser.toInt()
)
)
tabs.addTab(
tabs.newTab().setText(model.sentLabel.value).setId(
ChatMessage.State.Delivered.toInt()
)
)
tabs.addTab(
tabs.newTab().setText(
model.errorLabel.value
).setId(
ChatMessage.State.NotDelivered.toInt()
)
)
tabs.setOnTabSelectedListener(object : OnTabSelectedListener {
override fun onTabSelected(tab: TabLayout.Tab?) {
val state = tab?.id ?: ChatMessage.State.Displayed.toInt()
bottomSheetAdapter.submitList(
model.computeListForState(ChatMessage.State.fromInt(state))
)
}
override fun onTabUnselected(tab: TabLayout.Tab?) {
}
override fun onTabReselected(tab: TabLayout.Tab?) {
}
})
val initialList = model.displayedModels
bottomSheetAdapter.submitList(initialList)
Log.i("$TAG Submitted [${initialList.size}] items for default delivery status list")
}
}
}
@UiThread
private fun prepareBottomSheetForReactions(chatMessageModel: ChatMessageModel) {
coreContext.postOnCoreThread {
val model = ChatMessageReactionsModel(chatMessageModel.chatMessage)
val totalCount = model.allReactions.size
val label = getString(R.string.message_reactions_info_all_title, totalCount.toString())
coreContext.postOnMainThread {
val tabs = binding.messageBottomSheet.tabs
tabs.removeAllTabs()
tabs.addTab(
tabs.newTab().setText(label).setId(0).setTag("")
)
var index = 1
for (reaction in model.differentReactions.value.orEmpty()) {
val count = model.reactionsMap[reaction]
val tabLabel = getString(
R.string.message_reactions_info_emoji_title,
reaction,
count.toString()
)
tabs.addTab(
tabs.newTab().setText(tabLabel).setId(index).setTag(reaction)
)
index += 1
}
tabs.setOnTabSelectedListener(object : OnTabSelectedListener {
override fun onTabSelected(tab: TabLayout.Tab?) {
val filter = tab?.tag.toString()
if (filter.isEmpty()) {
bottomSheetAdapter.submitList(model.allReactions)
} else {
bottomSheetAdapter.submitList(model.filterReactions(filter))
}
}
override fun onTabUnselected(tab: TabLayout.Tab?) {
}
override fun onTabReselected(tab: TabLayout.Tab?) {
}
})
val initialList = model.allReactions
bottomSheetAdapter.submitList(initialList)
Log.i("$TAG Submitted [${initialList.size}] items for default reactions list")
}
}
}
}

View file

@ -1,51 +0,0 @@
/*
* Copyright (c) 2010-2023 Belledonne Communications SARL.
*
* This file is part of linphone-android
* (see https://www.linphone.org).
*
* 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 org.linphone.ui.main.chat.fragment
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.annotation.UiThread
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
import org.linphone.databinding.ChatMessageDeliveryBottomSheetBinding
import org.linphone.ui.main.chat.model.ChatMessageDeliveryModel
import org.linphone.ui.main.chat.model.ChatMessageModel
@UiThread
class MessageDeliveryDialogFragment(
chatMessageModel: ChatMessageModel
) : BottomSheetDialogFragment() {
companion object {
const val TAG = "MessageDeliveryDialogFragment"
}
val model = ChatMessageDeliveryModel(chatMessageModel.chatMessage)
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
val view = ChatMessageDeliveryBottomSheetBinding.inflate(layoutInflater)
view.model = model
return view.root
}
}

View file

@ -3,15 +3,12 @@ package org.linphone.ui.main.chat.model
import androidx.annotation.WorkerThread
import org.linphone.LinphoneApplication.Companion.coreContext
import org.linphone.core.Address
import org.linphone.utils.TimestampUtils
class ChatMessageParticipantDeliveryModel @WorkerThread constructor(
class ChatMessageBottomSheetParticipantModel @WorkerThread constructor(
address: Address,
timestamp: Long
val value: String
) {
val sipUri = address.asStringUriOnly()
val avatarModel = coreContext.contactsManager.getContactAvatarModelForAddress(address)
val time = TimestampUtils.toString(timestamp)
}

View file

@ -8,6 +8,7 @@ import org.linphone.core.ChatMessage
import org.linphone.core.ChatMessage.State
import org.linphone.core.tools.Log
import org.linphone.utils.AppUtils
import org.linphone.utils.TimestampUtils
class ChatMessageDeliveryModel @WorkerThread constructor(
private val chatMessage: ChatMessage
@ -24,34 +25,34 @@ class ChatMessageDeliveryModel @WorkerThread constructor(
val errorLabel = MutableLiveData<String>()
val deliveryModels = MutableLiveData<ArrayList<ChatMessageParticipantDeliveryModel>>()
val displayedModels = arrayListOf<ChatMessageBottomSheetParticipantModel>()
private val displayedModels = arrayListOf<ChatMessageParticipantDeliveryModel>()
private val deliveredModels = arrayListOf<ChatMessageBottomSheetParticipantModel>()
private val deliveredModels = arrayListOf<ChatMessageParticipantDeliveryModel>()
private val sentModels = arrayListOf<ChatMessageBottomSheetParticipantModel>()
private val sentModels = arrayListOf<ChatMessageParticipantDeliveryModel>()
private val errorModels = arrayListOf<ChatMessageParticipantDeliveryModel>()
private val errorModels = arrayListOf<ChatMessageBottomSheetParticipantModel>()
init {
computeDeliveryStatus()
// TODO: add listener to update in real time the lists
}
@UiThread
fun computeListForState(state: State) {
when (state) {
fun computeListForState(state: State): ArrayList<ChatMessageBottomSheetParticipantModel> {
return when (state) {
State.DeliveredToUser -> {
deliveryModels.value = deliveredModels
deliveredModels
}
State.Delivered -> {
deliveryModels.value = sentModels
sentModels
}
State.NotDelivered -> {
deliveryModels.value = errorModels
errorModels
}
else -> {
deliveryModels.value = displayedModels
displayedModels
}
}
}
@ -60,17 +61,17 @@ class ChatMessageDeliveryModel @WorkerThread constructor(
private fun computeDeliveryStatus() {
for (participant in chatMessage.getParticipantsByImdnState(State.Displayed)) {
displayedModels.add(
ChatMessageParticipantDeliveryModel(
ChatMessageBottomSheetParticipantModel(
participant.participant.address,
participant.stateChangeTime
TimestampUtils.timeToString(participant.stateChangeTime)
)
)
}
// Always add ourselves to prevent empty list
displayedModels.add(
ChatMessageParticipantDeliveryModel(
ChatMessageBottomSheetParticipantModel(
chatMessage.localAddress,
chatMessage.time
TimestampUtils.timeToString(chatMessage.time)
)
)
val readCount = displayedModels.size.toString()
@ -83,9 +84,9 @@ class ChatMessageDeliveryModel @WorkerThread constructor(
for (participant in chatMessage.getParticipantsByImdnState(State.DeliveredToUser)) {
deliveredModels.add(
ChatMessageParticipantDeliveryModel(
ChatMessageBottomSheetParticipantModel(
participant.participant.address,
participant.stateChangeTime
TimestampUtils.timeToString(participant.stateChangeTime)
)
)
}
@ -99,9 +100,9 @@ class ChatMessageDeliveryModel @WorkerThread constructor(
for (participant in chatMessage.getParticipantsByImdnState(State.Delivered)) {
sentModels.add(
ChatMessageParticipantDeliveryModel(
ChatMessageBottomSheetParticipantModel(
participant.participant.address,
participant.stateChangeTime
TimestampUtils.timeToString(participant.stateChangeTime)
)
)
}
@ -115,9 +116,9 @@ class ChatMessageDeliveryModel @WorkerThread constructor(
for (participant in chatMessage.getParticipantsByImdnState(State.NotDelivered)) {
errorModels.add(
ChatMessageParticipantDeliveryModel(
ChatMessageBottomSheetParticipantModel(
participant.participant.address,
participant.stateChangeTime
TimestampUtils.timeToString(participant.stateChangeTime)
)
)
}
@ -129,7 +130,6 @@ class ChatMessageDeliveryModel @WorkerThread constructor(
)
)
deliveryModels.postValue(displayedModels)
Log.i("$TAG Message ID [${chatMessage.messageId}] is in state [${chatMessage.state}]")
Log.i(
"$TAG There are [$readCount] that have read this message, [$receivedCount] that have received it, [$sentCount] that haven't received it yet and [$errorCount] that probably won't receive it due to an error"

View file

@ -0,0 +1,61 @@
package org.linphone.ui.main.chat.model
import androidx.annotation.WorkerThread
import androidx.lifecycle.MutableLiveData
import org.linphone.core.ChatMessage
import org.linphone.core.tools.Log
class ChatMessageReactionsModel @WorkerThread constructor(
private val chatMessage: ChatMessage
) {
companion object {
private const val TAG = "[Chat Message Reactions Model]"
}
val allReactions = arrayListOf<ChatMessageBottomSheetParticipantModel>()
val differentReactions = MutableLiveData<ArrayList<String>>()
val reactionsMap = HashMap<String, Int>()
init {
computeReactions()
// TODO: add listener to update in real time the lists
}
fun filterReactions(emoji: String): ArrayList<ChatMessageBottomSheetParticipantModel> {
val filteredList = arrayListOf<ChatMessageBottomSheetParticipantModel>()
for (reaction in allReactions) {
if (reaction.value == emoji) {
filteredList.add(reaction)
}
}
return filteredList
}
@WorkerThread
private fun computeReactions() {
reactionsMap.clear()
allReactions.clear()
val differentReactionsList = arrayListOf<String>()
for (reaction in chatMessage.reactions) {
val body = reaction.body
val count = reactionsMap.getOrDefault(body, 0)
reactionsMap[body] = count + 1
allReactions.add(ChatMessageBottomSheetParticipantModel(reaction.fromAddress, body))
if (!differentReactionsList.contains(body)) {
differentReactionsList.add(body)
}
}
Log.i(
"$TAG [${differentReactionsList.size}] reactions found on a total of [${allReactions.size}]"
)
differentReactions.postValue(differentReactionsList)
}
}

View file

@ -13,6 +13,9 @@
<variable
name="showDeliveryInfoClickListener"
type="View.OnClickListener" />
<variable
name="showReactionInfoClickListener"
type="View.OnClickListener" />
<variable
name="model"
type="org.linphone.ui.main.chat.model.ChatMessageModel" />
@ -116,6 +119,7 @@
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/reactions"
android:onClick="@{showReactionInfoClickListener}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="2dp"

View file

@ -13,6 +13,9 @@
<variable
name="showDeliveryInfoClickListener"
type="View.OnClickListener" />
<variable
name="showReactionInfoClickListener"
type="View.OnClickListener" />
<variable
name="model"
type="org.linphone.ui.main.chat.model.ChatMessageModel" />
@ -93,6 +96,7 @@
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/reactions"
android:onClick="@{showReactionInfoClickListener}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="2dp"

View file

@ -235,9 +235,9 @@
layout="@layout/chat_conversation_send_area"/>
<include
android:id="@+id/message_delivery"
android:id="@+id/message_bottom_sheet"
android:visibility="gone"
layout="@layout/chat_message_delivery_bottom_sheet" />
layout="@layout/chat_message_bottom_sheet" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View file

@ -7,9 +7,6 @@
<variable
name="handleClickedListener"
type="View.OnClickListener" />
<variable
name="model"
type="org.linphone.ui.main.chat.model.ChatMessageDeliveryModel" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
@ -40,7 +37,7 @@
android:background="@color/transparent_color"
android:layout_marginBottom="16dp"
app:layout_constraintTop_toBottomOf="@id/handle"
app:layout_constraintBottom_toTopOf="@id/deliveryList"
app:layout_constraintBottom_toTopOf="@id/bottom_sheet_list"
app:tabMode="fixed"
app:tabUnboundedRipple="true"
app:tabRippleColor="@color/orange_main_100"
@ -52,7 +49,7 @@
app:tabSelectedTextColor="@color/gray_main2_600" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/deliveryList"
android:id="@+id/bottom_sheet_list"
android:layout_width="match_parent"
android:layout_height="250dp"
android:nestedScrollingEnabled="true"

View file

@ -9,7 +9,7 @@
<import type="org.linphone.core.ChatRoom.SecurityLevel" />
<variable
name="model"
type="org.linphone.ui.main.chat.model.ChatMessageParticipantDeliveryModel" />
type="org.linphone.ui.main.chat.model.ChatMessageBottomSheetParticipantModel" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
@ -72,7 +72,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="16dp"
android:text="@{model.time, default=`16:18`}"
android:text="@{model.value, default=`16:18`}"
android:textSize="12sp"
android:textColor="@color/gray_main2_600"
app:layout_constraintEnd_toEndOf="parent"

View file

@ -366,6 +366,8 @@
<string name="message_delivery_info_received_title">Received (%s)</string>
<string name="message_delivery_info_sent_title">Sent (%s)</string>
<string name="message_delivery_info_error_title">Error (%s)</string>
<string name="message_reactions_info_all_title">Reactions (%s)</string>
<string name="message_reactions_info_emoji_title">%s (%s)</string>
<string name="meetings_list_empty">No meeting for the moment…</string>
<string name="meeting_schedule_title">New meeting</string>