mirror of
https://gitlab.linphone.org/BC/public/linphone-android.git
synced 2026-01-17 11:28:06 +00:00
Started grouping chat messages from same person in a short interval of time
This commit is contained in:
parent
395dc379ed
commit
c952178749
7 changed files with 136 additions and 15 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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<ChatMessage.State>()
|
||||
|
||||
val text = MutableLiveData<String>()
|
||||
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))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -12,14 +12,17 @@
|
|||
<variable
|
||||
name="model"
|
||||
type="org.linphone.ui.main.chat.model.ChatMessageModel" />
|
||||
<variable
|
||||
name="isGroupedWithPreviousOne"
|
||||
type="Boolean" />
|
||||
</data>
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:onLongClick="@{onLongClickListener}"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="20dp"
|
||||
android:layout_marginStart="20dp">
|
||||
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">
|
||||
|
||||
<io.getstream.avatarview.AvatarView
|
||||
android:id="@+id/avatar"
|
||||
|
|
@ -27,6 +30,7 @@
|
|||
android:layout_height="@dimen/avatar_list_cell_size"
|
||||
android:adjustViewBounds="true"
|
||||
android:background="@drawable/shape_circle_light_blue_background"
|
||||
android:visibility="@{isGroupedWithPreviousOne ? View.INVISIBLE : View.VISIBLE}"
|
||||
contactAvatar="@{model.avatarModel}"
|
||||
app:avatarViewPlaceholder="@drawable/user_circle"
|
||||
app:avatarViewInitialsBackgroundColor="@color/gray_main2_200"
|
||||
|
|
@ -50,17 +54,27 @@
|
|||
app:layout_constraintEnd_toEndOf="@id/avatar"
|
||||
app:layout_constraintBottom_toBottomOf="@id/avatar"/>
|
||||
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
<ImageView
|
||||
android:id="@+id/background"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:src="@{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="parent"
|
||||
app:layout_constraintStart_toStartOf="@id/text_message"
|
||||
app:layout_constraintEnd_toEndOf="@id/text_message"/>
|
||||
|
||||
<org.linphone.ui.main.chat.view.ChatBubbleTextView
|
||||
style="@style/default_text_style"
|
||||
android:id="@+id/text_message"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="10dp"
|
||||
android:padding="16dp"
|
||||
android:text="@{model.text, default=`Lorem ipsum dolor sit amet`}"
|
||||
android:textSize="14sp"
|
||||
android:textColor="@color/gray_main2_700"
|
||||
android:gravity="center_vertical|start"
|
||||
android:padding="16dp"
|
||||
android:background="@drawable/shape_chat_bubble_incoming_first"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@id/avatar"/>
|
||||
|
|
|
|||
|
|
@ -11,16 +11,19 @@
|
|||
<variable
|
||||
name="model"
|
||||
type="org.linphone.ui.main.chat.model.ChatMessageModel" />
|
||||
<variable
|
||||
name="isGroupedWithPreviousOne"
|
||||
type="Boolean" />
|
||||
</data>
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:onLongClick="@{onLongClickListener}"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="20dp"
|
||||
android:layout_marginEnd="20dp">
|
||||
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">
|
||||
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
<org.linphone.ui.main.chat.view.ChatBubbleTextView
|
||||
style="@style/default_text_style"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
|
|
@ -32,7 +35,7 @@
|
|||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
android:background="@drawable/shape_chat_bubble_outgoing_first"/>
|
||||
android:background="@{isGroupedWithPreviousOne ? @drawable/shape_chat_bubble_outgoing_full : @drawable/shape_chat_bubble_outgoing_first, default=@drawable/shape_chat_bubble_outgoing_first}"/>
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
|
|
|
|||
|
|
@ -52,4 +52,7 @@
|
|||
<dimen name="text_input_max_width">400dp</dimen>
|
||||
|
||||
<dimen name="meeting_margin">8dp</dimen>
|
||||
|
||||
<dimen name="chat_bubble_grouped_top_margin">4dp</dimen>
|
||||
<dimen name="chat_bubble_top_margin">16dp</dimen>
|
||||
</resources>
|
||||
Loading…
Add table
Reference in a new issue