From c9521787494c71f61b166c2abf8d445a41fd6bc4 Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Mon, 9 Oct 2023 17:53:17 +0200 Subject: [PATCH] Started grouping chat messages from same person in a short interval of time --- .../chat/adapter/ConversationEventAdapter.kt | 35 +++++++++++ .../ui/main/chat/model/ChatMessageModel.kt | 10 ++- .../ui/main/chat/view/ChatBubbleTextView.kt | 61 +++++++++++++++++++ .../org/linphone/utils/DataBindingUtils.kt | 5 +- .../main/res/layout/chat_bubble_incoming.xml | 26 ++++++-- .../main/res/layout/chat_bubble_outgoing.xml | 11 ++-- app/src/main/res/values/dimen.xml | 3 + 7 files changed, 136 insertions(+), 15 deletions(-) create mode 100644 app/src/main/java/org/linphone/ui/main/chat/view/ChatBubbleTextView.kt diff --git a/app/src/main/java/org/linphone/ui/main/chat/adapter/ConversationEventAdapter.kt b/app/src/main/java/org/linphone/ui/main/chat/adapter/ConversationEventAdapter.kt index aae82319e..ad4984fd1 100644 --- a/app/src/main/java/org/linphone/ui/main/chat/adapter/ConversationEventAdapter.kt +++ b/app/src/main/java/org/linphone/ui/main/chat/adapter/ConversationEventAdapter.kt @@ -26,6 +26,7 @@ import androidx.lifecycle.LifecycleOwner import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.ListAdapter import androidx.recyclerview.widget.RecyclerView +import java.lang.Math.abs import org.linphone.R import org.linphone.core.ChatMessage import org.linphone.databinding.ChatBubbleIncomingBinding @@ -42,6 +43,8 @@ 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 } var selectedAdapterPosition = -1 @@ -116,6 +119,22 @@ class ConversationEventAdapter( with(binding) { model = chatMessageData + isGroupedWithPreviousOne = if (bindingAdapterPosition == 0) { + false + } else { + val previous = bindingAdapterPosition - 1 + if (getItemViewType(previous) == INCOMING_CHAT_MESSAGE) { + val previousItem = getItem(previous).data as ChatMessageModel + if (kotlin.math.abs(chatMessageData.timestamp - previousItem.timestamp) < MAX_TIME_TO_GROUP_MESSAGES) { + previousItem.fromSipUri == chatMessageData.fromSipUri + } else { + false + } + } else { + false + } + } + binding.setOnLongClickListener { selectedAdapterPosition = bindingAdapterPosition binding.root.isSelected = true @@ -135,6 +154,22 @@ class ConversationEventAdapter( with(binding) { model = chatMessageData + isGroupedWithPreviousOne = if (bindingAdapterPosition == 0) { + false + } else { + val previous = bindingAdapterPosition - 1 + if (getItemViewType(previous) == OUTGOING_CHAT_MESSAGE) { + val previousItem = getItem(previous).data as ChatMessageModel + if (kotlin.math.abs(chatMessageData.timestamp - previousItem.timestamp) < MAX_TIME_TO_GROUP_MESSAGES) { + previousItem.fromSipUri == chatMessageData.fromSipUri + } else { + false + } + } else { + false + } + } + binding.setOnLongClickListener { selectedAdapterPosition = bindingAdapterPosition binding.root.isSelected = true diff --git a/app/src/main/java/org/linphone/ui/main/chat/model/ChatMessageModel.kt b/app/src/main/java/org/linphone/ui/main/chat/model/ChatMessageModel.kt index 2135c2fc9..ced04eb91 100644 --- a/app/src/main/java/org/linphone/ui/main/chat/model/ChatMessageModel.kt +++ b/app/src/main/java/org/linphone/ui/main/chat/model/ChatMessageModel.kt @@ -24,6 +24,7 @@ import androidx.lifecycle.MutableLiveData import org.linphone.core.ChatMessage import org.linphone.ui.main.contacts.model.ContactAvatarModel import org.linphone.utils.LinphoneUtils +import org.linphone.utils.TimestampUtils class ChatMessageModel @WorkerThread constructor( chatMessage: ChatMessage, @@ -35,10 +36,15 @@ class ChatMessageModel @WorkerThread constructor( val state = MutableLiveData() - val text = MutableLiveData() + val text = LinphoneUtils.getTextDescribingMessage(chatMessage) + + val fromSipUri = chatMessage.fromAddress.asStringUriOnly() + + val timestamp = chatMessage.time + + val time = TimestampUtils.toString(timestamp) init { state.postValue(chatMessage.state) - text.postValue(LinphoneUtils.getTextDescribingMessage(chatMessage)) } } diff --git a/app/src/main/java/org/linphone/ui/main/chat/view/ChatBubbleTextView.kt b/app/src/main/java/org/linphone/ui/main/chat/view/ChatBubbleTextView.kt new file mode 100644 index 000000000..610de22f1 --- /dev/null +++ b/app/src/main/java/org/linphone/ui/main/chat/view/ChatBubbleTextView.kt @@ -0,0 +1,61 @@ +/* + * 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 . + */ +package org.linphone.ui.main.chat.view + +import android.content.Context +import android.text.method.LinkMovementMethod +import android.util.AttributeSet +import android.view.View.MeasureSpec.* +import androidx.appcompat.widget.AppCompatTextView +import kotlin.math.ceil + +class ChatBubbleTextView : AppCompatTextView { + constructor(context: Context) : super(context) + + constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) + + constructor( + context: Context, + attrs: AttributeSet?, + defStyleAttr: Int + ) : super(context, attrs, defStyleAttr) + + override fun setText(text: CharSequence?, type: BufferType?) { + super.setText(text, type) + // Required for PatternClickableSpan + movementMethod = LinkMovementMethod.getInstance() + } + + override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec) + + val lines = layout.lineCount + if (lines > 1 && getMode(widthMeasureSpec) != EXACTLY) { + val textWidth = (0 until lines).maxOf(layout::getLineWidth) + val padding = compoundPaddingLeft + compoundPaddingRight + val w = ceil(textWidth).toInt() + padding + + if (w < measuredWidth) { + val newWidthMeasureSpec = makeMeasureSpec(w, AT_MOST) + super.onMeasure(newWidthMeasureSpec, heightMeasureSpec) + } + } + } +} diff --git a/app/src/main/java/org/linphone/utils/DataBindingUtils.kt b/app/src/main/java/org/linphone/utils/DataBindingUtils.kt index 054b8b289..bcb82b3d3 100644 --- a/app/src/main/java/org/linphone/utils/DataBindingUtils.kt +++ b/app/src/main/java/org/linphone/utils/DataBindingUtils.kt @@ -34,7 +34,6 @@ import androidx.annotation.ColorRes import androidx.annotation.UiThread import androidx.appcompat.widget.AppCompatEditText import androidx.appcompat.widget.AppCompatTextView -import androidx.constraintlayout.widget.ConstraintLayout import androidx.core.view.ViewCompat import androidx.core.view.WindowInsetsCompat import androidx.core.view.doOnLayout @@ -333,7 +332,7 @@ fun AppCompatEditText.editTextSetting(lambda: () -> Unit) { @BindingAdapter("android:layout_marginBottom") fun setConstraintLayoutBottomMargin(view: View, margins: Float) { - val params = view.layoutParams as ConstraintLayout.LayoutParams + val params = view.layoutParams as ViewGroup.MarginLayoutParams val m = margins.toInt() params.setMargins(params.leftMargin, params.topMargin, params.rightMargin, m) view.layoutParams = params @@ -341,7 +340,7 @@ fun setConstraintLayoutBottomMargin(view: View, margins: Float) { @BindingAdapter("android:layout_marginTop") fun setConstraintLayoutTopMargin(view: View, margins: Float) { - val params = view.layoutParams as ConstraintLayout.LayoutParams + val params = view.layoutParams as ViewGroup.MarginLayoutParams val m = margins.toInt() params.setMargins(params.leftMargin, m, params.rightMargin, params.bottomMargin) view.layoutParams = params diff --git a/app/src/main/res/layout/chat_bubble_incoming.xml b/app/src/main/res/layout/chat_bubble_incoming.xml index 0c7f84179..35d61b6b5 100644 --- a/app/src/main/res/layout/chat_bubble_incoming.xml +++ b/app/src/main/res/layout/chat_bubble_incoming.xml @@ -12,14 +12,17 @@ + + android:layout_marginTop="@{isGroupedWithPreviousOne ? @dimen/chat_bubble_grouped_top_margin : @dimen/chat_bubble_top_margin, default=@dimen/chat_bubble_top_margin}" + android:layout_marginStart="16dp"> - + + diff --git a/app/src/main/res/layout/chat_bubble_outgoing.xml b/app/src/main/res/layout/chat_bubble_outgoing.xml index 0144dfe78..96897fdd6 100644 --- a/app/src/main/res/layout/chat_bubble_outgoing.xml +++ b/app/src/main/res/layout/chat_bubble_outgoing.xml @@ -11,16 +11,19 @@ + + android:layout_marginTop="@{isGroupedWithPreviousOne ? @dimen/chat_bubble_grouped_top_margin : @dimen/chat_bubble_top_margin, default=@dimen/chat_bubble_top_margin}" + android:layout_marginEnd="16dp"> - + android:background="@{isGroupedWithPreviousOne ? @drawable/shape_chat_bubble_outgoing_full : @drawable/shape_chat_bubble_outgoing_first, default=@drawable/shape_chat_bubble_outgoing_first}"/> diff --git a/app/src/main/res/values/dimen.xml b/app/src/main/res/values/dimen.xml index 81f19e451..90c001aec 100644 --- a/app/src/main/res/values/dimen.xml +++ b/app/src/main/res/values/dimen.xml @@ -52,4 +52,7 @@ 400dp 8dp + + 4dp + 16dp \ No newline at end of file