mirror of
https://github.com/oxen-io/session-android.git
synced 2025-02-19 20:08:27 +00:00
Merge remote-tracking branch 'upstream/ui' into ui
This commit is contained in:
commit
313fe2f62c
@ -6,11 +6,13 @@ import android.content.res.Resources
|
||||
import android.database.Cursor
|
||||
import android.graphics.Rect
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import android.view.*
|
||||
import android.widget.RelativeLayout
|
||||
import androidx.loader.app.LoaderManager
|
||||
import androidx.loader.content.Loader
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import kotlinx.android.synthetic.main.activity_conversation_v2.*
|
||||
import kotlinx.android.synthetic.main.activity_conversation_v2.view.*
|
||||
import kotlinx.android.synthetic.main.activity_conversation_v2_action_bar.*
|
||||
@ -35,11 +37,12 @@ import org.thoughtcrime.securesms.database.DraftDatabase.Drafts
|
||||
import org.thoughtcrime.securesms.database.model.MessageRecord
|
||||
import org.thoughtcrime.securesms.loki.utilities.toPx
|
||||
import org.thoughtcrime.securesms.mms.GlideApp
|
||||
import kotlin.math.abs
|
||||
import kotlin.math.roundToInt
|
||||
import kotlin.math.sqrt
|
||||
import kotlin.math.*
|
||||
|
||||
class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDelegate, InputBarRecordingViewDelegate {
|
||||
class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDelegate,
|
||||
InputBarRecordingViewDelegate, ConversationRecyclerViewDelegate {
|
||||
private val scrollButtonFullVisibilityThreshold by lazy { toPx(120.0f, resources) }
|
||||
private val scrollButtonNoVisibilityThreshold by lazy { toPx(20.0f, resources) }
|
||||
private val screenWidth = Resources.getSystem().displayMetrics.widthPixels
|
||||
private var threadID: Long = -1
|
||||
private var actionMode: ActionMode? = null
|
||||
@ -113,6 +116,7 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
|
||||
conversationRecyclerView.adapter = adapter
|
||||
val layoutManager = LinearLayoutManager(this, LinearLayoutManager.VERTICAL, true)
|
||||
conversationRecyclerView.layoutManager = layoutManager
|
||||
conversationRecyclerView.delegate = this
|
||||
// Workaround for the fact that CursorRecyclerViewAdapter doesn't auto-update automatically (even though it says it will)
|
||||
LoaderManager.getInstance(this).restartLoader(0, null, object : LoaderManager.LoaderCallbacks<Cursor> {
|
||||
|
||||
@ -171,7 +175,7 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
|
||||
if (!isOxenHostedOpenGroup) { return }
|
||||
openGroupGuidelinesView.visibility = View.VISIBLE
|
||||
val recyclerViewLayoutParams = conversationRecyclerView.layoutParams as RelativeLayout.LayoutParams
|
||||
recyclerViewLayoutParams.topMargin = toPx(57, resources)
|
||||
recyclerViewLayoutParams.topMargin = toPx(57, resources) // The height of the open group guidelines view is hardcoded to this
|
||||
conversationRecyclerView.layoutParams = recyclerViewLayoutParams
|
||||
}
|
||||
|
||||
@ -315,6 +319,13 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
|
||||
}
|
||||
animation.start()
|
||||
}
|
||||
|
||||
override fun handleConversationRecyclerViewBottomOffsetChanged(bottomOffset: Int) {
|
||||
val rawAlpha = (bottomOffset.toFloat() - scrollButtonNoVisibilityThreshold) /
|
||||
(scrollButtonFullVisibilityThreshold - scrollButtonNoVisibilityThreshold)
|
||||
val alpha = max(min(rawAlpha, 1.0f), 0.0f)
|
||||
Log.d("Test", "$alpha")
|
||||
}
|
||||
// endregion
|
||||
|
||||
// region Interaction
|
||||
@ -408,6 +419,8 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
|
||||
}
|
||||
|
||||
private fun isValidLockViewLocation(x: Int, y: Int): Boolean {
|
||||
// We can be anywhere above the lock view and a bit to the side of it (at most `lockViewHitMargin`
|
||||
// to the side)
|
||||
val lockViewLocation = IntArray(2) { 0 }
|
||||
lockView.getLocationOnScreen(lockViewLocation)
|
||||
val hitRect = Rect(lockViewLocation[0] - lockViewHitMargin, 0,
|
||||
|
@ -2,17 +2,21 @@ package org.thoughtcrime.securesms.conversation.v2
|
||||
|
||||
import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
import android.util.Log
|
||||
import android.view.MotionEvent
|
||||
import android.view.VelocityTracker
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import kotlinx.android.synthetic.main.activity_conversation_v2.*
|
||||
import org.thoughtcrime.securesms.loki.utilities.disableClipping
|
||||
import org.thoughtcrime.securesms.loki.utilities.toPx
|
||||
import kotlin.math.abs
|
||||
import kotlin.math.max
|
||||
|
||||
class ConversationRecyclerView : RecyclerView {
|
||||
private val maxLongPressVelocityY = toPx(10, resources)
|
||||
private val minSwipeVelocityX = toPx(10, resources)
|
||||
private var velocityTracker: VelocityTracker? = null
|
||||
var delegate: ConversationRecyclerViewDelegate? = null
|
||||
|
||||
constructor(context: Context) : super(context) { initialize() }
|
||||
constructor(context: Context, attrs: AttributeSet) : super(context, attrs) { initialize() }
|
||||
@ -20,6 +24,20 @@ class ConversationRecyclerView : RecyclerView {
|
||||
|
||||
private fun initialize() {
|
||||
disableClipping()
|
||||
addOnScrollListener(object : RecyclerView.OnScrollListener() {
|
||||
private var maxScrollOffset = 0
|
||||
|
||||
override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
|
||||
val scrollOffset = recyclerView.computeVerticalScrollOffset()
|
||||
maxScrollOffset = max(maxScrollOffset, scrollOffset)
|
||||
val bottomOffset = (maxScrollOffset - scrollOffset)
|
||||
delegate?.handleConversationRecyclerViewBottomOffsetChanged(bottomOffset)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
override fun onInterceptTouchEvent(e: MotionEvent): Boolean {
|
||||
@ -48,4 +66,9 @@ class ConversationRecyclerView : RecyclerView {
|
||||
velocityTracker?.addMovement(e)
|
||||
return super.dispatchTouchEvent(e)
|
||||
}
|
||||
}
|
||||
|
||||
interface ConversationRecyclerViewDelegate {
|
||||
|
||||
fun handleConversationRecyclerViewBottomOffsetChanged(bottomOffset: Int)
|
||||
}
|
@ -26,6 +26,7 @@ import kotlin.math.roundToInt
|
||||
class InputBar : RelativeLayout, InputBarEditTextDelegate, QuoteViewDelegate {
|
||||
private val screenWidth = Resources.getSystem().displayMetrics.widthPixels
|
||||
private val vMargin by lazy { toDp(4, resources) }
|
||||
private val minHeight by lazy { toPx(56, resources) }
|
||||
var delegate: InputBarDelegate? = null
|
||||
var additionalContentHeight = 0
|
||||
|
||||
@ -82,7 +83,7 @@ class InputBar : RelativeLayout, InputBarEditTextDelegate, QuoteViewDelegate {
|
||||
}
|
||||
|
||||
override fun inputBarEditTextHeightChanged(newValue: Int) {
|
||||
val newHeight = max(newValue + 2 * vMargin, toPx(56, resources)) + inputBarAdditionalContentContainer.height
|
||||
val newHeight = max(newValue + 2 * vMargin, minHeight) + inputBarAdditionalContentContainer.height
|
||||
setHeight(newHeight)
|
||||
}
|
||||
|
||||
@ -100,18 +101,23 @@ class InputBar : RelativeLayout, InputBarEditTextDelegate, QuoteViewDelegate {
|
||||
quoteView.delegate = this
|
||||
inputBarAdditionalContentContainer.addView(quoteView)
|
||||
val attachments = (message as? MmsMessageRecord)?.slideDeck
|
||||
// The max content width is the screen width - 2 times the horizontal input bar padding - the
|
||||
// quote view content area's start and end margins. This unfortunately has to be calculated manually
|
||||
// here to get the layout right.
|
||||
val maxContentWidth = (screenWidth - 2 * resources.getDimension(R.dimen.medium_spacing) - toPx(16, resources) - toPx(30, resources)).roundToInt()
|
||||
quoteView.bind(message.individualRecipient.address.toString(), message.body, attachments,
|
||||
message.recipient, true, maxContentWidth, message.isOpenGroupInvitation)
|
||||
// The 6 DP below is the padding the quote view applies to itself, which isn't included in the
|
||||
// intrinsic height calculation.
|
||||
val quoteViewIntrinsicHeight = quoteView.getIntrinsicHeight(maxContentWidth) + toPx(6, resources)
|
||||
val newHeight = max(inputBarEditText.height + 2 * vMargin, toPx(56, resources)) + quoteViewIntrinsicHeight
|
||||
val newHeight = max(inputBarEditText.height + 2 * vMargin, minHeight) + quoteViewIntrinsicHeight
|
||||
additionalContentHeight = quoteViewIntrinsicHeight
|
||||
setHeight(newHeight)
|
||||
}
|
||||
|
||||
override fun cancelQuoteDraft() {
|
||||
inputBarAdditionalContentContainer.removeAllViews()
|
||||
val newHeight = max(inputBarEditText.height + 2 * vMargin, toPx(56, resources))
|
||||
val newHeight = max(inputBarEditText.height + 2 * vMargin, minHeight)
|
||||
additionalContentHeight = 0
|
||||
setHeight(newHeight)
|
||||
}
|
||||
|
@ -29,7 +29,8 @@ class InputBarEditText : AppCompatEditText {
|
||||
super.onTextChanged(text, start, lengthBefore, lengthAfter)
|
||||
delegate?.inputBarEditTextContentChanged(text)
|
||||
// Calculate the width manually to get it right even before layout has happened (i.e.
|
||||
// when restoring a draft)
|
||||
// when restoring a draft). The 64 DP is the horizontal margin around the input bar
|
||||
// edit text.
|
||||
val width = (screenWidth - 2 * toPx(64.0f, resources)).roundToInt()
|
||||
if (width < 0) { return } // screenWidth initially evaluates to 0
|
||||
val height = TextUtilities.getIntrinsicHeight(text, paint, width).toFloat()
|
||||
|
@ -52,7 +52,8 @@ class LinkPreviewView : LinearLayout {
|
||||
}
|
||||
titleTextView.setTextColor(ResourcesCompat.getColor(resources, textColorID, context.theme))
|
||||
// Body
|
||||
mainLinkPreviewContainer.addView(VisibleMessageContentView.getBodyTextView(context, message))
|
||||
val bodyTextView = VisibleMessageContentView.getBodyTextView(context, message)
|
||||
mainLinkPreviewContainer.addView(bodyTextView)
|
||||
}
|
||||
|
||||
fun recycle() {
|
||||
|
@ -29,6 +29,13 @@ import kotlin.math.max
|
||||
import kotlin.math.min
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
// There's quite some calculation going on here. It's a bit complex so don't make changes
|
||||
// if you don't need to. If you do then test:
|
||||
// • Quoted text in both private chats and group chats
|
||||
// • Quoted images and videos in both private chats and group chats
|
||||
// • Quoted voice messages and documents in both private chats and group chats
|
||||
// • All of the above in both dark mode and light mode
|
||||
|
||||
class QuoteView : LinearLayout {
|
||||
private lateinit var mode: Mode
|
||||
private val vPadding by lazy { toPx(6, resources) }
|
||||
@ -44,6 +51,8 @@ class QuoteView : LinearLayout {
|
||||
constructor(context: Context, mode: Mode) : super(context) {
|
||||
this.mode = mode
|
||||
LayoutInflater.from(context).inflate(R.layout.view_quote, this)
|
||||
// Add padding here (not on mainQuoteViewContainer) to get a bit of a top inset while avoiding
|
||||
// the clipping issue described in getIntrinsicHeight(maxContentWidth:).
|
||||
setPadding(0, toPx(6, resources), 0, 0)
|
||||
when (mode) {
|
||||
Mode.Draft -> quoteViewCancelButton.setOnClickListener { delegate?.cancelQuoteDraft() }
|
||||
@ -51,6 +60,7 @@ class QuoteView : LinearLayout {
|
||||
quoteViewCancelButton.isVisible = false
|
||||
mainQuoteViewContainer.setBackgroundColor(ResourcesCompat.getColor(resources, R.color.transparent, context.theme))
|
||||
val quoteViewMainContentContainerLayoutParams = quoteViewMainContentContainer.layoutParams as RelativeLayout.LayoutParams
|
||||
// Since we're not showing the cancel button we can shorten the end margin
|
||||
quoteViewMainContentContainerLayoutParams.marginEnd = resources.getDimension(R.dimen.medium_spacing).roundToInt()
|
||||
quoteViewMainContentContainer.layoutParams = quoteViewMainContentContainerLayoutParams
|
||||
}
|
||||
@ -60,6 +70,7 @@ class QuoteView : LinearLayout {
|
||||
|
||||
// region General
|
||||
fun getIntrinsicContentHeight(maxContentWidth: Int): Int {
|
||||
// If we're showing an attachment thumbnail, just constrain to the height of that
|
||||
if (quoteViewAttachmentPreviewContainer.isVisible) { return toPx(40, resources) }
|
||||
var result = 0
|
||||
var authorTextViewIntrinsicHeight = 0
|
||||
@ -72,16 +83,24 @@ class QuoteView : LinearLayout {
|
||||
val bodyTextViewIntrinsicHeight = TextUtilities.getIntrinsicHeight(body, quoteViewBodyTextView.paint, maxContentWidth)
|
||||
result += bodyTextViewIntrinsicHeight
|
||||
if (!quoteViewAuthorTextView.isVisible) {
|
||||
return min(max(result, toPx(32, resources)), toPx(60, resources))
|
||||
// We want to at least be as high as the cancel button, and no higher than 56 DP (that's
|
||||
// approximately the height of 3 lines.
|
||||
return min(max(result, toPx(32, resources)), toPx(56, resources))
|
||||
} else {
|
||||
return min(result, toPx(60, resources) + authorTextViewIntrinsicHeight)
|
||||
// Because we're showing the author text view, we should have a height of at least 32 DP
|
||||
// anyway, so there's no need to constrain to that. We constrain to a max height of 56 DP
|
||||
// because that's approximately the height of the author text view + 2 lines of the body
|
||||
// text view.
|
||||
return min(result, toPx(56, resources))
|
||||
}
|
||||
}
|
||||
|
||||
fun getIntrinsicHeight(maxContentWidth: Int): Int {
|
||||
var result = getIntrinsicContentHeight(maxContentWidth)
|
||||
result += 2 * vPadding
|
||||
return result
|
||||
// The way all this works is that we just calculate the total height the quote view should be
|
||||
// and then center everything inside vertically. This effectively means we're applying padding.
|
||||
// Applying padding the regular way results in a clipping issue though due to a bug in
|
||||
// RelativeLayout.
|
||||
return getIntrinsicContentHeight(maxContentWidth) + 2 * vPadding
|
||||
}
|
||||
// endregion
|
||||
|
||||
@ -89,6 +108,10 @@ class QuoteView : LinearLayout {
|
||||
fun bind(authorPublicKey: String, body: String?, attachments: SlideDeck?, thread: Recipient,
|
||||
isOutgoingMessage: Boolean, maxContentWidth: Int, isOpenGroupInvitation: Boolean) {
|
||||
val contactDB = DatabaseFactory.getSessionContactDatabase(context)
|
||||
// Reduce the max body text view line count to 2 if this is a group thread because
|
||||
// we'll be showing the author text view and we don't want the overall quote view height
|
||||
// to get too big.
|
||||
quoteViewBodyTextView.maxLines = if (thread.isGroupRecipient) 2 else 3
|
||||
// Author
|
||||
if (thread.isGroupRecipient) {
|
||||
val author = contactDB.getContactWithSessionID(authorPublicKey)
|
||||
@ -106,7 +129,7 @@ class QuoteView : LinearLayout {
|
||||
quoteViewAttachmentPreviewContainer.isVisible = hasAttachments
|
||||
if (!hasAttachments) {
|
||||
val accentLineLayoutParams = quoteViewAccentLine.layoutParams as RelativeLayout.LayoutParams
|
||||
accentLineLayoutParams.height = getIntrinsicContentHeight(maxContentWidth)
|
||||
accentLineLayoutParams.height = getIntrinsicContentHeight(maxContentWidth) // Match the intrinsic * content * height
|
||||
quoteViewAccentLine.layoutParams = accentLineLayoutParams
|
||||
quoteViewAccentLine.setBackgroundColor(getLineColor(isOutgoingMessage))
|
||||
} else {
|
||||
@ -127,6 +150,7 @@ class QuoteView : LinearLayout {
|
||||
}
|
||||
mainQuoteViewContainer.layoutParams = LayoutParams(LayoutParams.WRAP_CONTENT, getIntrinsicHeight(maxContentWidth))
|
||||
val quoteViewMainContentContainerLayoutParams = quoteViewMainContentContainer.layoutParams as RelativeLayout.LayoutParams
|
||||
// The start margin is different if we just show the accent line vs if we show an attachment thumbnail
|
||||
quoteViewMainContentContainerLayoutParams.marginStart = if (!hasAttachments) toPx(16, resources) else toPx(48, resources)
|
||||
quoteViewMainContentContainer.layoutParams = quoteViewMainContentContainerLayoutParams
|
||||
}
|
||||
|
@ -1,8 +1,11 @@
|
||||
package org.thoughtcrime.securesms.conversation.v2.messages
|
||||
|
||||
import android.content.Context
|
||||
import android.content.res.ColorStateList
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.text.util.Linkify
|
||||
import android.util.AttributeSet
|
||||
import android.util.Log
|
||||
import android.util.TypedValue
|
||||
import android.view.LayoutInflater
|
||||
import android.widget.LinearLayout
|
||||
@ -18,6 +21,7 @@ import org.session.libsession.messaging.utilities.UpdateMessageData
|
||||
import org.session.libsession.messaging.utilities.UpdateMessageData.Companion.fromJSON
|
||||
import org.session.libsession.utilities.ThemeUtil
|
||||
import org.session.libsession.utilities.ViewUtil
|
||||
import org.session.libsession.utilities.recipients.Recipient
|
||||
import org.thoughtcrime.securesms.database.model.MessageRecord
|
||||
import org.thoughtcrime.securesms.database.model.MmsMessageRecord
|
||||
import org.thoughtcrime.securesms.loki.utilities.UiMode
|
||||
@ -43,7 +47,8 @@ class VisibleMessageContentView : LinearLayout {
|
||||
// endregion
|
||||
|
||||
// region Updating
|
||||
fun bind(message: MessageRecord, isStartOfMessageCluster: Boolean, isEndOfMessageCluster: Boolean, glide: GlideRequests, maxWidth: Int) {
|
||||
fun bind(message: MessageRecord, isStartOfMessageCluster: Boolean, isEndOfMessageCluster: Boolean,
|
||||
glide: GlideRequests, maxWidth: Int, thread: Recipient) {
|
||||
// Background
|
||||
val background = getBackground(message.isOutgoing, isStartOfMessageCluster, isEndOfMessageCluster)
|
||||
val colorID = if (message.isOutgoing) R.attr.message_sent_background_color else R.attr.message_received_background_color
|
||||
@ -62,8 +67,11 @@ class VisibleMessageContentView : LinearLayout {
|
||||
} else if (message is MmsMessageRecord && message.quote != null) {
|
||||
val quote = message.quote!!
|
||||
val quoteView = QuoteView(context, QuoteView.Mode.Regular)
|
||||
// The max content width is the max message bubble size - 2 times the horizontal padding - the
|
||||
// quote view content area's start margin. This unfortunately has to be calculated manually
|
||||
// here to get the layout right.
|
||||
val maxContentWidth = (maxWidth - 2 * resources.getDimension(R.dimen.medium_spacing) - toPx(16, resources)).roundToInt()
|
||||
quoteView.bind(quote.author.toString(), quote.text, quote.attachment, message.recipient,
|
||||
quoteView.bind(quote.author.toString(), quote.text, quote.attachment, thread,
|
||||
message.isOutgoing, maxContentWidth, message.isOpenGroupInvitation)
|
||||
mainContainer.addView(quoteView)
|
||||
val bodyTextView = VisibleMessageContentView.getBodyTextView(context, message)
|
||||
@ -73,6 +81,8 @@ class VisibleMessageContentView : LinearLayout {
|
||||
val voiceMessageView = VoiceMessageView(context)
|
||||
voiceMessageView.bind(message, background)
|
||||
mainContainer.addView(voiceMessageView)
|
||||
// We have to use onContentClick (rather than a click listener directly on the voice
|
||||
// message view) so as to not interfere with all the other gestures.
|
||||
onContentClick = { voiceMessageView.togglePlayback() }
|
||||
} else if (message is MmsMessageRecord && message.slideDeck.documentSlide != null) {
|
||||
val documentView = DocumentView(context)
|
||||
@ -124,6 +134,8 @@ class VisibleMessageContentView : LinearLayout {
|
||||
result.setTextSize(TypedValue.COMPLEX_UNIT_PX, context.resources.getDimension(R.dimen.small_font_size))
|
||||
val color = getTextColor(context, message)
|
||||
result.setTextColor(color)
|
||||
result.setLinkTextColor(color)
|
||||
Linkify.addLinks(result, Linkify.WEB_URLS)
|
||||
return result
|
||||
}
|
||||
|
||||
|
@ -79,15 +79,14 @@ class VisibleMessageView : LinearLayout {
|
||||
val senderSessionID = sender.address.serialize()
|
||||
val threadID = message.threadId
|
||||
val threadDB = DatabaseFactory.getThreadDatabase(context)
|
||||
val thread = threadDB.getRecipientForThreadId(threadID)
|
||||
val thread = threadDB.getRecipientForThreadId(threadID)!!
|
||||
val contactDB = DatabaseFactory.getSessionContactDatabase(context)
|
||||
val isGroupThread = (thread?.isGroupRecipient == true)
|
||||
val isGroupThread = thread.isGroupRecipient
|
||||
val isStartOfMessageCluster = isStartOfMessageCluster(message, previous, isGroupThread)
|
||||
val isEndOfMessageCluster = isEndOfMessageCluster(message, next, isGroupThread)
|
||||
// Show profile picture and sender name if this is a group thread AND
|
||||
// the message is incoming
|
||||
if (isGroupThread && !message.isOutgoing) {
|
||||
thread!!
|
||||
profilePictureContainer.visibility = if (isEndOfMessageCluster) View.VISIBLE else View.INVISIBLE
|
||||
profilePictureView.publicKey = senderSessionID
|
||||
profilePictureView.glide = glide
|
||||
@ -139,12 +138,11 @@ class VisibleMessageView : LinearLayout {
|
||||
} else {
|
||||
messageStatusImageView.isVisible = false
|
||||
}
|
||||
// Populate content view
|
||||
// Calculate max message bubble width
|
||||
var maxWidth = screenWidth - messageContentContainerLayoutParams.leftMargin - messageContentContainerLayoutParams.rightMargin
|
||||
if (profilePictureContainer.visibility != View.GONE) {
|
||||
maxWidth -= profilePictureContainer.width
|
||||
}
|
||||
messageContentView.bind(message, isStartOfMessageCluster, isEndOfMessageCluster, glide, maxWidth)
|
||||
if (profilePictureContainer.visibility != View.GONE) { maxWidth -= profilePictureContainer.width }
|
||||
// Populate content view
|
||||
messageContentView.bind(message, isStartOfMessageCluster, isEndOfMessageCluster, glide, maxWidth, thread)
|
||||
}
|
||||
|
||||
private fun setMessageSpacing(isStartOfMessageCluster: Boolean, isEndOfMessageCluster: Boolean) {
|
||||
|
Loading…
x
Reference in New Issue
Block a user