mirror of
https://gitlab.linphone.org/BC/public/linphone-android.git
synced 2026-01-24 23:28:10 +00:00
Reworked algorithm that groups chat messages together
This commit is contained in:
parent
c9db3df251
commit
888c8c453a
7 changed files with 134 additions and 109 deletions
|
|
@ -28,7 +28,6 @@ import androidx.recyclerview.widget.DiffUtil
|
|||
import androidx.recyclerview.widget.ListAdapter
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import org.linphone.R
|
||||
import org.linphone.core.ChatMessage
|
||||
import org.linphone.databinding.ChatBubbleIncomingBinding
|
||||
import org.linphone.databinding.ChatBubbleOutgoingBinding
|
||||
import org.linphone.databinding.ChatEventBinding
|
||||
|
|
@ -44,8 +43,6 @@ class ConversationEventAdapter(
|
|||
const val INCOMING_CHAT_MESSAGE = 1
|
||||
const val OUTGOING_CHAT_MESSAGE = 2
|
||||
const val EVENT = 3
|
||||
|
||||
const val MAX_TIME_TO_GROUP_MESSAGES = 60 // 1 minute
|
||||
}
|
||||
|
||||
val chatMessageLongPressEvent = MutableLiveData<Event<Pair<ChatMessageModel, Int>>>()
|
||||
|
|
@ -107,42 +104,6 @@ class ConversationEventAdapter(
|
|||
}
|
||||
}
|
||||
|
||||
fun groupPreviousItem(item: ChatMessageModel, position: Int): Boolean {
|
||||
return if (position == 0) {
|
||||
false
|
||||
} else {
|
||||
val previous = position - 1
|
||||
if (getItemViewType(position) == getItemViewType(previous)) {
|
||||
val previousItem = getItem(previous).model as ChatMessageModel
|
||||
if (kotlin.math.abs(item.timestamp - previousItem.timestamp) < MAX_TIME_TO_GROUP_MESSAGES) {
|
||||
previousItem.fromSipUri == item.fromSipUri
|
||||
} else {
|
||||
false
|
||||
}
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun isLastItemOfGroup(item: ChatMessageModel, position: Int): Boolean {
|
||||
return if (position == itemCount - 1) {
|
||||
true
|
||||
} else {
|
||||
val next = position + 1
|
||||
if (getItemViewType(next) == getItemViewType(position)) {
|
||||
val nextItem = getItem(next).model as ChatMessageModel
|
||||
if (kotlin.math.abs(item.timestamp - nextItem.timestamp) < MAX_TIME_TO_GROUP_MESSAGES) {
|
||||
nextItem.fromSipUri != item.fromSipUri
|
||||
} else {
|
||||
true
|
||||
}
|
||||
} else {
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
inner class IncomingBubbleViewHolder(
|
||||
val binding: ChatBubbleIncomingBinding
|
||||
) : RecyclerView.ViewHolder(binding.root) {
|
||||
|
|
@ -150,10 +111,6 @@ class ConversationEventAdapter(
|
|||
with(binding) {
|
||||
model = message
|
||||
|
||||
val position = bindingAdapterPosition
|
||||
isGroupedWithPreviousOne = groupPreviousItem(message, position)
|
||||
isLastOneOfGroup = isLastItemOfGroup(message, position)
|
||||
|
||||
setOnLongClickListener {
|
||||
val screen = IntArray(2)
|
||||
root.getLocationOnScreen(screen)
|
||||
|
|
@ -175,10 +132,6 @@ class ConversationEventAdapter(
|
|||
with(binding) {
|
||||
model = message
|
||||
|
||||
val position = bindingAdapterPosition
|
||||
isGroupedWithPreviousOne = groupPreviousItem(message, position)
|
||||
isLastOneOfGroup = isLastItemOfGroup(message, position)
|
||||
|
||||
lifecycleOwner = viewLifecycleOwner
|
||||
executePendingBindings()
|
||||
}
|
||||
|
|
@ -214,8 +167,9 @@ class ConversationEventAdapter(
|
|||
return if (oldItem.isEvent && newItem.isEvent) {
|
||||
true
|
||||
} else {
|
||||
val newData = (newItem.model as ChatMessageModel)
|
||||
newData.state.value == ChatMessage.State.Displayed
|
||||
val oldModel = (newItem.model as ChatMessageModel)
|
||||
val newModel = newItem.model
|
||||
oldModel.statusIcon.value == newModel.statusIcon.value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ import androidx.annotation.UiThread
|
|||
import androidx.annotation.WorkerThread
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import org.linphone.LinphoneApplication.Companion.coreContext
|
||||
import org.linphone.R
|
||||
import org.linphone.core.Address
|
||||
import org.linphone.core.ChatMessage
|
||||
import org.linphone.core.ChatMessageListenerStub
|
||||
|
|
@ -35,7 +36,10 @@ import org.linphone.utils.TimestampUtils
|
|||
|
||||
class ChatMessageModel @WorkerThread constructor(
|
||||
val chatMessage: ChatMessage,
|
||||
val avatarModel: ContactAvatarModel
|
||||
val avatarModel: ContactAvatarModel,
|
||||
val isFromGroup: Boolean,
|
||||
val isGroupedWithPreviousOne: Boolean,
|
||||
val isGroupedWithNextOne: Boolean
|
||||
) {
|
||||
companion object {
|
||||
private const val TAG = "[Chat Message Model]"
|
||||
|
|
@ -45,15 +49,11 @@ class ChatMessageModel @WorkerThread constructor(
|
|||
|
||||
val isOutgoing = chatMessage.isOutgoing
|
||||
|
||||
val state = MutableLiveData<ChatMessage.State>()
|
||||
val statusIcon = MutableLiveData<Int>()
|
||||
|
||||
val text = LinphoneUtils.getTextDescribingMessage(chatMessage)
|
||||
|
||||
val fromSipUri = chatMessage.fromAddress.asStringUriOnly()
|
||||
|
||||
val timestamp = chatMessage.time
|
||||
|
||||
val time = TimestampUtils.toString(timestamp)
|
||||
val time = TimestampUtils.toString(chatMessage.time)
|
||||
|
||||
val dismissLongPressMenuEvent: MutableLiveData<Event<Boolean>> by lazy {
|
||||
MutableLiveData<Event<Boolean>>()
|
||||
|
|
@ -62,7 +62,7 @@ class ChatMessageModel @WorkerThread constructor(
|
|||
private val chatMessageListener = object : ChatMessageListenerStub() {
|
||||
@WorkerThread
|
||||
override fun onMsgStateChanged(message: ChatMessage, messageState: ChatMessage.State?) {
|
||||
state.postValue(chatMessage.state)
|
||||
computeStatusIcon(chatMessage.state)
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
|
|
@ -80,7 +80,7 @@ class ChatMessageModel @WorkerThread constructor(
|
|||
|
||||
init {
|
||||
chatMessage.addListener(chatMessageListener)
|
||||
state.postValue(chatMessage.state)
|
||||
computeStatusIcon(chatMessage.state)
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
|
|
@ -101,4 +101,26 @@ class ChatMessageModel @WorkerThread constructor(
|
|||
@UiThread
|
||||
fun showDeliveryInfo() {
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
private fun computeStatusIcon(state: ChatMessage.State) {
|
||||
val icon = when (state) {
|
||||
ChatMessage.State.Displayed -> {
|
||||
R.drawable.checks
|
||||
}
|
||||
ChatMessage.State.DeliveredToUser -> {
|
||||
R.drawable.check
|
||||
}
|
||||
ChatMessage.State.Delivered -> {
|
||||
R.drawable.envelope_simple
|
||||
}
|
||||
ChatMessage.State.NotDelivered -> {
|
||||
R.drawable.warning_circle
|
||||
}
|
||||
else -> {
|
||||
R.drawable.in_progress
|
||||
}
|
||||
}
|
||||
statusIcon.postValue(icon)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,7 +23,13 @@ import androidx.annotation.WorkerThread
|
|||
import org.linphone.core.EventLog
|
||||
import org.linphone.ui.main.contacts.model.ContactAvatarModel
|
||||
|
||||
class EventLogModel @WorkerThread constructor(eventLog: EventLog, avatarModel: ContactAvatarModel) {
|
||||
class EventLogModel @WorkerThread constructor(
|
||||
val eventLog: EventLog,
|
||||
avatarModel: ContactAvatarModel,
|
||||
isFromGroup: Boolean,
|
||||
isGroupedWithPreviousOne: Boolean,
|
||||
isGroupedWithNextOne: Boolean
|
||||
) {
|
||||
val type: EventLog.Type = eventLog.type
|
||||
|
||||
val isEvent = type != EventLog.Type.ConferenceChatMessage
|
||||
|
|
@ -31,7 +37,13 @@ class EventLogModel @WorkerThread constructor(eventLog: EventLog, avatarModel: C
|
|||
val model = if (isEvent) {
|
||||
EventModel(eventLog)
|
||||
} else {
|
||||
ChatMessageModel(eventLog.chatMessage!!, avatarModel)
|
||||
ChatMessageModel(
|
||||
eventLog.chatMessage!!,
|
||||
avatarModel,
|
||||
isFromGroup,
|
||||
isGroupedWithPreviousOne,
|
||||
isGroupedWithNextOne
|
||||
)
|
||||
}
|
||||
|
||||
val notifyId = eventLog.notifyId
|
||||
|
|
|
|||
|
|
@ -43,6 +43,8 @@ import org.linphone.utils.LinphoneUtils
|
|||
class ConversationViewModel @UiThread constructor() : ViewModel() {
|
||||
companion object {
|
||||
private const val TAG = "[Conversation ViewModel]"
|
||||
|
||||
const val MAX_TIME_TO_GROUP_MESSAGES = 60 // 1 minute
|
||||
}
|
||||
|
||||
val showBackButton = MutableLiveData<Boolean>()
|
||||
|
|
@ -79,7 +81,13 @@ class ConversationViewModel @UiThread constructor() : ViewModel() {
|
|||
list.addAll(events.value.orEmpty())
|
||||
|
||||
val avatarModel = getAvatarModelForAddress(message?.localAddress)
|
||||
list.add(EventLogModel(eventLog, avatarModel))
|
||||
val lastEvent = events.value.orEmpty().lastOrNull()
|
||||
val group = if (lastEvent != null) {
|
||||
shouldWeGroupTwoEvents(eventLog, lastEvent.eventLog)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
list.add(EventLogModel(eventLog, avatarModel, isGroup.value == true, group, true))
|
||||
|
||||
events.postValue(list)
|
||||
}
|
||||
|
|
@ -103,7 +111,7 @@ class ConversationViewModel @UiThread constructor() : ViewModel() {
|
|||
}
|
||||
|
||||
@WorkerThread
|
||||
override fun onChatMessagesReceived(chatRoom: ChatRoom, eventLogs: Array<out EventLog>) {
|
||||
override fun onChatMessagesReceived(chatRoom: ChatRoom, eventLogs: Array<EventLog>) {
|
||||
Log.i("$TAG Received [${eventLogs.size}] new message(s)")
|
||||
chatRoom.markAsRead()
|
||||
computeComposingLabel()
|
||||
|
|
@ -111,15 +119,13 @@ class ConversationViewModel @UiThread constructor() : ViewModel() {
|
|||
val list = arrayListOf<EventLogModel>()
|
||||
list.addAll(events.value.orEmpty())
|
||||
|
||||
for (eventLog in eventLogs) {
|
||||
val address = if (eventLog.type == EventLog.Type.ConferenceChatMessage) {
|
||||
eventLog.chatMessage?.fromAddress
|
||||
} else {
|
||||
eventLog.participantAddress
|
||||
}
|
||||
val avatarModel = getAvatarModelForAddress(address)
|
||||
list.add(EventLogModel(eventLog, avatarModel))
|
||||
}
|
||||
val newList = getEventsListFromHistory(
|
||||
eventLogs,
|
||||
isGroupChatRoom = isGroup.value == true
|
||||
)
|
||||
list.addAll(newList)
|
||||
|
||||
// TODO: handle case when first one of the newly received messages should be grouped with last one of the current list
|
||||
|
||||
events.postValue(list)
|
||||
}
|
||||
|
|
@ -211,11 +217,9 @@ class ConversationViewModel @UiThread constructor() : ViewModel() {
|
|||
private fun configureChatRoom() {
|
||||
computeComposingLabel()
|
||||
|
||||
isGroup.postValue(
|
||||
!chatRoom.hasCapability(ChatRoom.Capabilities.OneToOne.toInt()) && chatRoom.hasCapability(
|
||||
ChatRoom.Capabilities.Conference.toInt()
|
||||
)
|
||||
)
|
||||
val isGroupChatRoom = !chatRoom.hasCapability(ChatRoom.Capabilities.OneToOne.toInt()) &&
|
||||
chatRoom.hasCapability(ChatRoom.Capabilities.Conference.toInt())
|
||||
isGroup.postValue(isGroupChatRoom)
|
||||
|
||||
val empty = chatRoom.hasCapability(ChatRoom.Capabilities.Conference.toInt()) && chatRoom.participants.isEmpty()
|
||||
val readOnly = chatRoom.isReadOnly || empty
|
||||
|
|
@ -245,19 +249,66 @@ class ConversationViewModel @UiThread constructor() : ViewModel() {
|
|||
val groupAvatar = GroupAvatarModel(friends)
|
||||
groupAvatarModel.postValue(groupAvatar)
|
||||
|
||||
val eventsList = arrayListOf<EventLogModel>()
|
||||
|
||||
val history = chatRoom.getHistoryEvents(0)
|
||||
for (event in history) {
|
||||
val avatar = getAvatarModelForAddress(event.chatMessage?.fromAddress)
|
||||
val model = EventLogModel(event, avatar)
|
||||
eventsList.add(model)
|
||||
}
|
||||
val eventsList = getEventsListFromHistory(history, isGroupChatRoom)
|
||||
|
||||
events.postValue(eventsList)
|
||||
chatRoom.markAsRead()
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
private fun getEventsListFromHistory(history: Array<EventLog>, isGroupChatRoom: Boolean): ArrayList<EventLogModel> {
|
||||
val eventsList = arrayListOf<EventLogModel>()
|
||||
val groupedEventLogs = arrayListOf<EventLog>()
|
||||
for (event in history) {
|
||||
if (groupedEventLogs.isEmpty()) {
|
||||
groupedEventLogs.add(event)
|
||||
continue
|
||||
}
|
||||
|
||||
val previousGroupEvent = groupedEventLogs.last()
|
||||
val groupEvents = shouldWeGroupTwoEvents(event, previousGroupEvent)
|
||||
|
||||
if (!groupEvents) {
|
||||
// Handle all events in group, then re-start a new group with current item
|
||||
var index = 0
|
||||
for (groupedEvent in groupedEventLogs) {
|
||||
val avatar = getAvatarModelForAddress(groupedEvent.chatMessage?.fromAddress)
|
||||
val model = EventLogModel(
|
||||
groupedEvent,
|
||||
avatar,
|
||||
isGroupChatRoom,
|
||||
index > 0,
|
||||
index == groupedEventLogs.size - 1
|
||||
)
|
||||
eventsList.add(model)
|
||||
|
||||
index += 1
|
||||
}
|
||||
|
||||
groupedEventLogs.clear()
|
||||
}
|
||||
|
||||
groupedEventLogs.add(event)
|
||||
}
|
||||
return eventsList
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
private fun shouldWeGroupTwoEvents(event: EventLog, previousGroupEvent: EventLog): Boolean {
|
||||
return if (previousGroupEvent.type == EventLog.Type.ConferenceChatMessage && event.type == EventLog.Type.ConferenceChatMessage) {
|
||||
val previousChatMessage = previousGroupEvent.chatMessage!!
|
||||
val chatMessage = event.chatMessage!!
|
||||
|
||||
// If they have the same direction, the same from address and were sent in a short timelapse, group them
|
||||
chatMessage.isOutgoing == previousChatMessage.isOutgoing &&
|
||||
chatMessage.fromAddress.weakEqual(previousChatMessage.fromAddress) &&
|
||||
kotlin.math.abs(chatMessage.time - previousChatMessage.time) < MAX_TIME_TO_GROUP_MESSAGES
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
private fun getAvatarModelForAddress(address: Address?): ContactAvatarModel {
|
||||
Log.i("Looking for avatar model with address [${address?.asStringUriOnly()}]")
|
||||
|
|
|
|||
|
|
@ -13,19 +13,13 @@
|
|||
<variable
|
||||
name="model"
|
||||
type="org.linphone.ui.main.chat.model.ChatMessageModel" />
|
||||
<variable
|
||||
name="isGroupedWithPreviousOne"
|
||||
type="Boolean" />
|
||||
<variable
|
||||
name="isLastOneOfGroup"
|
||||
type="Boolean" />
|
||||
</data>
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:onLongClick="@{onLongClickListener}"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@{isGroupedWithPreviousOne ? @dimen/chat_bubble_grouped_top_margin : @dimen/chat_bubble_top_margin, default=@dimen/chat_bubble_top_margin}"
|
||||
android:layout_marginTop="@{model.isGroupedWithPreviousOne ? @dimen/chat_bubble_grouped_top_margin : @dimen/chat_bubble_top_margin, default=@dimen/chat_bubble_top_margin}"
|
||||
android:layout_marginStart="16dp">
|
||||
|
||||
<io.getstream.avatarview.AvatarView
|
||||
|
|
@ -34,7 +28,7 @@
|
|||
android:layout_height="@dimen/avatar_bubble_size"
|
||||
android:adjustViewBounds="true"
|
||||
android:background="@drawable/shape_circle_light_blue_background"
|
||||
android:visibility="@{isGroupedWithPreviousOne ? View.INVISIBLE : View.VISIBLE}"
|
||||
android:visibility="@{!model.isFromGroup ? View.GONE: model.isGroupedWithPreviousOne ? View.INVISIBLE : View.VISIBLE}"
|
||||
contactAvatar="@{model.avatarModel}"
|
||||
app:avatarViewPlaceholder="@drawable/user_circle"
|
||||
app:avatarViewInitialsBackgroundColor="@color/gray_main2_200"
|
||||
|
|
@ -54,7 +48,7 @@
|
|||
android:background="@drawable/led_background"
|
||||
android:padding="1dp"
|
||||
app:presenceIcon="@{model.avatarModel.presenceStatus}"
|
||||
android:visibility="@{isGroupedWithPreviousOne || model.avatarModel.presenceStatus == ConsolidatedPresence.Offline ? View.GONE : View.VISIBLE}"
|
||||
android:visibility="@{model.isGroupedWithPreviousOne || !model.isFromGroup || model.avatarModel.presenceStatus == ConsolidatedPresence.Offline ? View.GONE : View.VISIBLE}"
|
||||
app:layout_constraintEnd_toEndOf="@id/avatar"
|
||||
app:layout_constraintBottom_toBottomOf="@id/avatar"/>
|
||||
|
||||
|
|
@ -63,7 +57,7 @@
|
|||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_marginStart="10dp"
|
||||
android:src="@{isGroupedWithPreviousOne ? @drawable/shape_chat_bubble_incoming_full : @drawable/shape_chat_bubble_incoming_first, default=@drawable/shape_chat_bubble_incoming_first}"
|
||||
android:src="@{model.isGroupedWithPreviousOne ? @drawable/shape_chat_bubble_incoming_full : @drawable/shape_chat_bubble_incoming_first, default=@drawable/shape_chat_bubble_incoming_first}"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintBottom_toBottomOf="@id/bubble_bottom_barrier"
|
||||
app:layout_constraintStart_toEndOf="@id/avatar"
|
||||
|
|
@ -127,7 +121,7 @@
|
|||
android:layout_marginBottom="5dp"
|
||||
android:text="@{model.time, default=`13:40`}"
|
||||
android:textSize="12sp"
|
||||
android:visibility="@{isLastOneOfGroup ? View.VISIBLE : View.GONE}"
|
||||
android:visibility="@{model.isGroupedWithNextOne ? View.VISIBLE : View.GONE}"
|
||||
app:layout_constraintTop_toBottomOf="@id/text_message"
|
||||
app:layout_constraintStart_toStartOf="@id/text_message"
|
||||
app:layout_constraintBottom_toBottomOf="@id/background"/>
|
||||
|
|
@ -140,8 +134,8 @@
|
|||
android:layout_height="@dimen/small_icon_size"
|
||||
android:layout_marginStart="5dp"
|
||||
android:layout_marginTop="2dp"
|
||||
android:src="@{model.state == State.Displayed ? @drawable/checks : model.state == State.DeliveredToUser ? @drawable/check : model.state == State.Delivered ? @drawable/envelope_simple : model.state == State.NotDelivered ? @drawable/warning_circle : @drawable/in_progress, default=@drawable/in_progress}"
|
||||
android:visibility="@{isLastOneOfGroup ? View.VISIBLE : View.GONE}"
|
||||
android:src="@{model.statusIcon, default=@drawable/checks}"
|
||||
android:visibility="@{model.isGroupedWithNextOne ? View.VISIBLE : View.GONE}"
|
||||
app:layout_constraintTop_toTopOf="@id/date_time"
|
||||
app:layout_constraintStart_toEndOf="@id/date_time"
|
||||
app:layout_constraintBottom_toBottomOf="@id/date_time"
|
||||
|
|
|
|||
|
|
@ -12,26 +12,20 @@
|
|||
<variable
|
||||
name="model"
|
||||
type="org.linphone.ui.main.chat.model.ChatMessageModel" />
|
||||
<variable
|
||||
name="isGroupedWithPreviousOne"
|
||||
type="Boolean" />
|
||||
<variable
|
||||
name="isLastOneOfGroup"
|
||||
type="Boolean" />
|
||||
</data>
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:onLongClick="@{onLongClickListener}"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@{isGroupedWithPreviousOne ? @dimen/chat_bubble_grouped_top_margin : @dimen/chat_bubble_top_margin, default=@dimen/chat_bubble_top_margin}"
|
||||
android:layout_marginTop="@{model.isGroupedWithPreviousOne ? @dimen/chat_bubble_grouped_top_margin : @dimen/chat_bubble_top_margin, default=@dimen/chat_bubble_top_margin}"
|
||||
android:layout_marginEnd="16dp">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/background"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:src="@{isGroupedWithPreviousOne ? @drawable/shape_chat_bubble_outgoing_full : @drawable/shape_chat_bubble_outgoing_first, default=@drawable/shape_chat_bubble_outgoing_first}"
|
||||
android:src="@{model.isGroupedWithPreviousOne ? @drawable/shape_chat_bubble_outgoing_full : @drawable/shape_chat_bubble_outgoing_first, default=@drawable/shape_chat_bubble_outgoing_first}"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintBottom_toBottomOf="@id/bubble_bottom_barrier"
|
||||
app:layout_constraintStart_toStartOf="@id/bubble_start_barrier"
|
||||
|
|
@ -89,7 +83,7 @@
|
|||
android:layout_marginEnd="5dp"
|
||||
android:text="@{model.time, default=`13:40`}"
|
||||
android:textSize="12sp"
|
||||
android:visibility="@{isLastOneOfGroup ? View.VISIBLE : View.GONE}"
|
||||
android:visibility="@{model.isGroupedWithNextOne ? View.VISIBLE : View.GONE}"
|
||||
app:layout_constraintTop_toBottomOf="@id/text_message"
|
||||
app:layout_constraintEnd_toStartOf="@id/delivery_status"
|
||||
app:layout_constraintBottom_toBottomOf="@id/background"/>
|
||||
|
|
@ -102,8 +96,8 @@
|
|||
android:layout_height="@dimen/small_icon_size"
|
||||
android:layout_marginEnd="18dp"
|
||||
android:layout_marginTop="2dp"
|
||||
android:src="@{model.state == State.Displayed ? @drawable/checks : model.state == State.DeliveredToUser ? @drawable/check : model.state == State.Delivered ? @drawable/envelope_simple : model.state == State.NotDelivered ? @drawable/warning_circle : @drawable/in_progress, default=@drawable/in_progress}"
|
||||
android:visibility="@{isLastOneOfGroup ? View.VISIBLE : View.GONE}"
|
||||
android:src="@{model.statusIcon, default=@drawable/checks}"
|
||||
android:visibility="@{model.isGroupedWithNextOne ? View.VISIBLE : View.GONE}"
|
||||
app:layout_constraintTop_toTopOf="@id/date_time"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintBottom_toBottomOf="@id/date_time"
|
||||
|
|
|
|||
|
|
@ -133,8 +133,6 @@
|
|||
app:layout_constraintTop_toBottomOf="@id/emojis_background"
|
||||
app:layout_constraintBottom_toTopOf="@id/reply"
|
||||
model="@{model}"
|
||||
isGroupedWithPreviousOne="@{false}"
|
||||
isLastOneOfGroup="@{true}"
|
||||
layout="@layout/chat_bubble_incoming"/>
|
||||
|
||||
<View
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue