Merge remote-tracking branch 'upstream/ui' into ui

This commit is contained in:
jubb 2021-06-23 14:59:46 +10:00
commit 313fe2f62c
8 changed files with 104 additions and 26 deletions

View File

@ -6,11 +6,13 @@ import android.content.res.Resources
import android.database.Cursor import android.database.Cursor
import android.graphics.Rect import android.graphics.Rect
import android.os.Bundle import android.os.Bundle
import android.util.Log
import android.view.* import android.view.*
import android.widget.RelativeLayout import android.widget.RelativeLayout
import androidx.loader.app.LoaderManager import androidx.loader.app.LoaderManager
import androidx.loader.content.Loader import androidx.loader.content.Loader
import androidx.recyclerview.widget.LinearLayoutManager 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.*
import kotlinx.android.synthetic.main.activity_conversation_v2.view.* import kotlinx.android.synthetic.main.activity_conversation_v2.view.*
import kotlinx.android.synthetic.main.activity_conversation_v2_action_bar.* 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.database.model.MessageRecord
import org.thoughtcrime.securesms.loki.utilities.toPx import org.thoughtcrime.securesms.loki.utilities.toPx
import org.thoughtcrime.securesms.mms.GlideApp import org.thoughtcrime.securesms.mms.GlideApp
import kotlin.math.abs import kotlin.math.*
import kotlin.math.roundToInt
import kotlin.math.sqrt
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 val screenWidth = Resources.getSystem().displayMetrics.widthPixels
private var threadID: Long = -1 private var threadID: Long = -1
private var actionMode: ActionMode? = null private var actionMode: ActionMode? = null
@ -113,6 +116,7 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
conversationRecyclerView.adapter = adapter conversationRecyclerView.adapter = adapter
val layoutManager = LinearLayoutManager(this, LinearLayoutManager.VERTICAL, true) val layoutManager = LinearLayoutManager(this, LinearLayoutManager.VERTICAL, true)
conversationRecyclerView.layoutManager = layoutManager conversationRecyclerView.layoutManager = layoutManager
conversationRecyclerView.delegate = this
// Workaround for the fact that CursorRecyclerViewAdapter doesn't auto-update automatically (even though it says it will) // 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> { LoaderManager.getInstance(this).restartLoader(0, null, object : LoaderManager.LoaderCallbacks<Cursor> {
@ -171,7 +175,7 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
if (!isOxenHostedOpenGroup) { return } if (!isOxenHostedOpenGroup) { return }
openGroupGuidelinesView.visibility = View.VISIBLE openGroupGuidelinesView.visibility = View.VISIBLE
val recyclerViewLayoutParams = conversationRecyclerView.layoutParams as RelativeLayout.LayoutParams 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 conversationRecyclerView.layoutParams = recyclerViewLayoutParams
} }
@ -315,6 +319,13 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
} }
animation.start() 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 // endregion
// region Interaction // region Interaction
@ -408,6 +419,8 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
} }
private fun isValidLockViewLocation(x: Int, y: Int): Boolean { 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 } val lockViewLocation = IntArray(2) { 0 }
lockView.getLocationOnScreen(lockViewLocation) lockView.getLocationOnScreen(lockViewLocation)
val hitRect = Rect(lockViewLocation[0] - lockViewHitMargin, 0, val hitRect = Rect(lockViewLocation[0] - lockViewHitMargin, 0,

View File

@ -2,17 +2,21 @@ package org.thoughtcrime.securesms.conversation.v2
import android.content.Context import android.content.Context
import android.util.AttributeSet import android.util.AttributeSet
import android.util.Log
import android.view.MotionEvent import android.view.MotionEvent
import android.view.VelocityTracker import android.view.VelocityTracker
import androidx.recyclerview.widget.RecyclerView 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.disableClipping
import org.thoughtcrime.securesms.loki.utilities.toPx import org.thoughtcrime.securesms.loki.utilities.toPx
import kotlin.math.abs import kotlin.math.abs
import kotlin.math.max
class ConversationRecyclerView : RecyclerView { class ConversationRecyclerView : RecyclerView {
private val maxLongPressVelocityY = toPx(10, resources) private val maxLongPressVelocityY = toPx(10, resources)
private val minSwipeVelocityX = toPx(10, resources) private val minSwipeVelocityX = toPx(10, resources)
private var velocityTracker: VelocityTracker? = null private var velocityTracker: VelocityTracker? = null
var delegate: ConversationRecyclerViewDelegate? = null
constructor(context: Context) : super(context) { initialize() } constructor(context: Context) : super(context) { initialize() }
constructor(context: Context, attrs: AttributeSet) : super(context, attrs) { initialize() } constructor(context: Context, attrs: AttributeSet) : super(context, attrs) { initialize() }
@ -20,6 +24,20 @@ class ConversationRecyclerView : RecyclerView {
private fun initialize() { private fun initialize() {
disableClipping() 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 { override fun onInterceptTouchEvent(e: MotionEvent): Boolean {
@ -49,3 +67,8 @@ class ConversationRecyclerView : RecyclerView {
return super.dispatchTouchEvent(e) return super.dispatchTouchEvent(e)
} }
} }
interface ConversationRecyclerViewDelegate {
fun handleConversationRecyclerViewBottomOffsetChanged(bottomOffset: Int)
}

View File

@ -26,6 +26,7 @@ import kotlin.math.roundToInt
class InputBar : RelativeLayout, InputBarEditTextDelegate, QuoteViewDelegate { class InputBar : RelativeLayout, InputBarEditTextDelegate, QuoteViewDelegate {
private val screenWidth = Resources.getSystem().displayMetrics.widthPixels private val screenWidth = Resources.getSystem().displayMetrics.widthPixels
private val vMargin by lazy { toDp(4, resources) } private val vMargin by lazy { toDp(4, resources) }
private val minHeight by lazy { toPx(56, resources) }
var delegate: InputBarDelegate? = null var delegate: InputBarDelegate? = null
var additionalContentHeight = 0 var additionalContentHeight = 0
@ -82,7 +83,7 @@ class InputBar : RelativeLayout, InputBarEditTextDelegate, QuoteViewDelegate {
} }
override fun inputBarEditTextHeightChanged(newValue: Int) { 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) setHeight(newHeight)
} }
@ -100,18 +101,23 @@ class InputBar : RelativeLayout, InputBarEditTextDelegate, QuoteViewDelegate {
quoteView.delegate = this quoteView.delegate = this
inputBarAdditionalContentContainer.addView(quoteView) inputBarAdditionalContentContainer.addView(quoteView)
val attachments = (message as? MmsMessageRecord)?.slideDeck 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() 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, quoteView.bind(message.individualRecipient.address.toString(), message.body, attachments,
message.recipient, true, maxContentWidth, message.isOpenGroupInvitation) 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 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 additionalContentHeight = quoteViewIntrinsicHeight
setHeight(newHeight) setHeight(newHeight)
} }
override fun cancelQuoteDraft() { override fun cancelQuoteDraft() {
inputBarAdditionalContentContainer.removeAllViews() inputBarAdditionalContentContainer.removeAllViews()
val newHeight = max(inputBarEditText.height + 2 * vMargin, toPx(56, resources)) val newHeight = max(inputBarEditText.height + 2 * vMargin, minHeight)
additionalContentHeight = 0 additionalContentHeight = 0
setHeight(newHeight) setHeight(newHeight)
} }

View File

@ -29,7 +29,8 @@ class InputBarEditText : AppCompatEditText {
super.onTextChanged(text, start, lengthBefore, lengthAfter) super.onTextChanged(text, start, lengthBefore, lengthAfter)
delegate?.inputBarEditTextContentChanged(text) delegate?.inputBarEditTextContentChanged(text)
// Calculate the width manually to get it right even before layout has happened (i.e. // 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() val width = (screenWidth - 2 * toPx(64.0f, resources)).roundToInt()
if (width < 0) { return } // screenWidth initially evaluates to 0 if (width < 0) { return } // screenWidth initially evaluates to 0
val height = TextUtilities.getIntrinsicHeight(text, paint, width).toFloat() val height = TextUtilities.getIntrinsicHeight(text, paint, width).toFloat()

View File

@ -52,7 +52,8 @@ class LinkPreviewView : LinearLayout {
} }
titleTextView.setTextColor(ResourcesCompat.getColor(resources, textColorID, context.theme)) titleTextView.setTextColor(ResourcesCompat.getColor(resources, textColorID, context.theme))
// Body // Body
mainLinkPreviewContainer.addView(VisibleMessageContentView.getBodyTextView(context, message)) val bodyTextView = VisibleMessageContentView.getBodyTextView(context, message)
mainLinkPreviewContainer.addView(bodyTextView)
} }
fun recycle() { fun recycle() {

View File

@ -29,6 +29,13 @@ import kotlin.math.max
import kotlin.math.min import kotlin.math.min
import kotlin.math.roundToInt 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 { class QuoteView : LinearLayout {
private lateinit var mode: Mode private lateinit var mode: Mode
private val vPadding by lazy { toPx(6, resources) } private val vPadding by lazy { toPx(6, resources) }
@ -44,6 +51,8 @@ class QuoteView : LinearLayout {
constructor(context: Context, mode: Mode) : super(context) { constructor(context: Context, mode: Mode) : super(context) {
this.mode = mode this.mode = mode
LayoutInflater.from(context).inflate(R.layout.view_quote, this) 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) setPadding(0, toPx(6, resources), 0, 0)
when (mode) { when (mode) {
Mode.Draft -> quoteViewCancelButton.setOnClickListener { delegate?.cancelQuoteDraft() } Mode.Draft -> quoteViewCancelButton.setOnClickListener { delegate?.cancelQuoteDraft() }
@ -51,6 +60,7 @@ class QuoteView : LinearLayout {
quoteViewCancelButton.isVisible = false quoteViewCancelButton.isVisible = false
mainQuoteViewContainer.setBackgroundColor(ResourcesCompat.getColor(resources, R.color.transparent, context.theme)) mainQuoteViewContainer.setBackgroundColor(ResourcesCompat.getColor(resources, R.color.transparent, context.theme))
val quoteViewMainContentContainerLayoutParams = quoteViewMainContentContainer.layoutParams as RelativeLayout.LayoutParams 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() quoteViewMainContentContainerLayoutParams.marginEnd = resources.getDimension(R.dimen.medium_spacing).roundToInt()
quoteViewMainContentContainer.layoutParams = quoteViewMainContentContainerLayoutParams quoteViewMainContentContainer.layoutParams = quoteViewMainContentContainerLayoutParams
} }
@ -60,6 +70,7 @@ class QuoteView : LinearLayout {
// region General // region General
fun getIntrinsicContentHeight(maxContentWidth: Int): Int { 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) } if (quoteViewAttachmentPreviewContainer.isVisible) { return toPx(40, resources) }
var result = 0 var result = 0
var authorTextViewIntrinsicHeight = 0 var authorTextViewIntrinsicHeight = 0
@ -72,16 +83,24 @@ class QuoteView : LinearLayout {
val bodyTextViewIntrinsicHeight = TextUtilities.getIntrinsicHeight(body, quoteViewBodyTextView.paint, maxContentWidth) val bodyTextViewIntrinsicHeight = TextUtilities.getIntrinsicHeight(body, quoteViewBodyTextView.paint, maxContentWidth)
result += bodyTextViewIntrinsicHeight result += bodyTextViewIntrinsicHeight
if (!quoteViewAuthorTextView.isVisible) { 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 { } 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 { fun getIntrinsicHeight(maxContentWidth: Int): Int {
var result = getIntrinsicContentHeight(maxContentWidth) // The way all this works is that we just calculate the total height the quote view should be
result += 2 * vPadding // and then center everything inside vertically. This effectively means we're applying padding.
return result // Applying padding the regular way results in a clipping issue though due to a bug in
// RelativeLayout.
return getIntrinsicContentHeight(maxContentWidth) + 2 * vPadding
} }
// endregion // endregion
@ -89,6 +108,10 @@ class QuoteView : LinearLayout {
fun bind(authorPublicKey: String, body: String?, attachments: SlideDeck?, thread: Recipient, fun bind(authorPublicKey: String, body: String?, attachments: SlideDeck?, thread: Recipient,
isOutgoingMessage: Boolean, maxContentWidth: Int, isOpenGroupInvitation: Boolean) { isOutgoingMessage: Boolean, maxContentWidth: Int, isOpenGroupInvitation: Boolean) {
val contactDB = DatabaseFactory.getSessionContactDatabase(context) 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 // Author
if (thread.isGroupRecipient) { if (thread.isGroupRecipient) {
val author = contactDB.getContactWithSessionID(authorPublicKey) val author = contactDB.getContactWithSessionID(authorPublicKey)
@ -106,7 +129,7 @@ class QuoteView : LinearLayout {
quoteViewAttachmentPreviewContainer.isVisible = hasAttachments quoteViewAttachmentPreviewContainer.isVisible = hasAttachments
if (!hasAttachments) { if (!hasAttachments) {
val accentLineLayoutParams = quoteViewAccentLine.layoutParams as RelativeLayout.LayoutParams val accentLineLayoutParams = quoteViewAccentLine.layoutParams as RelativeLayout.LayoutParams
accentLineLayoutParams.height = getIntrinsicContentHeight(maxContentWidth) accentLineLayoutParams.height = getIntrinsicContentHeight(maxContentWidth) // Match the intrinsic * content * height
quoteViewAccentLine.layoutParams = accentLineLayoutParams quoteViewAccentLine.layoutParams = accentLineLayoutParams
quoteViewAccentLine.setBackgroundColor(getLineColor(isOutgoingMessage)) quoteViewAccentLine.setBackgroundColor(getLineColor(isOutgoingMessage))
} else { } else {
@ -127,6 +150,7 @@ class QuoteView : LinearLayout {
} }
mainQuoteViewContainer.layoutParams = LayoutParams(LayoutParams.WRAP_CONTENT, getIntrinsicHeight(maxContentWidth)) mainQuoteViewContainer.layoutParams = LayoutParams(LayoutParams.WRAP_CONTENT, getIntrinsicHeight(maxContentWidth))
val quoteViewMainContentContainerLayoutParams = quoteViewMainContentContainer.layoutParams as RelativeLayout.LayoutParams 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) quoteViewMainContentContainerLayoutParams.marginStart = if (!hasAttachments) toPx(16, resources) else toPx(48, resources)
quoteViewMainContentContainer.layoutParams = quoteViewMainContentContainerLayoutParams quoteViewMainContentContainer.layoutParams = quoteViewMainContentContainerLayoutParams
} }

View File

@ -1,8 +1,11 @@
package org.thoughtcrime.securesms.conversation.v2.messages package org.thoughtcrime.securesms.conversation.v2.messages
import android.content.Context import android.content.Context
import android.content.res.ColorStateList
import android.graphics.drawable.Drawable import android.graphics.drawable.Drawable
import android.text.util.Linkify
import android.util.AttributeSet import android.util.AttributeSet
import android.util.Log
import android.util.TypedValue import android.util.TypedValue
import android.view.LayoutInflater import android.view.LayoutInflater
import android.widget.LinearLayout 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.messaging.utilities.UpdateMessageData.Companion.fromJSON
import org.session.libsession.utilities.ThemeUtil import org.session.libsession.utilities.ThemeUtil
import org.session.libsession.utilities.ViewUtil 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.MessageRecord
import org.thoughtcrime.securesms.database.model.MmsMessageRecord import org.thoughtcrime.securesms.database.model.MmsMessageRecord
import org.thoughtcrime.securesms.loki.utilities.UiMode import org.thoughtcrime.securesms.loki.utilities.UiMode
@ -43,7 +47,8 @@ class VisibleMessageContentView : LinearLayout {
// endregion // endregion
// region Updating // 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 // Background
val background = getBackground(message.isOutgoing, isStartOfMessageCluster, isEndOfMessageCluster) 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 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) { } else if (message is MmsMessageRecord && message.quote != null) {
val quote = message.quote!! val quote = message.quote!!
val quoteView = QuoteView(context, QuoteView.Mode.Regular) 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() 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) message.isOutgoing, maxContentWidth, message.isOpenGroupInvitation)
mainContainer.addView(quoteView) mainContainer.addView(quoteView)
val bodyTextView = VisibleMessageContentView.getBodyTextView(context, message) val bodyTextView = VisibleMessageContentView.getBodyTextView(context, message)
@ -73,6 +81,8 @@ class VisibleMessageContentView : LinearLayout {
val voiceMessageView = VoiceMessageView(context) val voiceMessageView = VoiceMessageView(context)
voiceMessageView.bind(message, background) voiceMessageView.bind(message, background)
mainContainer.addView(voiceMessageView) 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() } onContentClick = { voiceMessageView.togglePlayback() }
} else if (message is MmsMessageRecord && message.slideDeck.documentSlide != null) { } else if (message is MmsMessageRecord && message.slideDeck.documentSlide != null) {
val documentView = DocumentView(context) 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)) result.setTextSize(TypedValue.COMPLEX_UNIT_PX, context.resources.getDimension(R.dimen.small_font_size))
val color = getTextColor(context, message) val color = getTextColor(context, message)
result.setTextColor(color) result.setTextColor(color)
result.setLinkTextColor(color)
Linkify.addLinks(result, Linkify.WEB_URLS)
return result return result
} }

View File

@ -79,15 +79,14 @@ class VisibleMessageView : LinearLayout {
val senderSessionID = sender.address.serialize() val senderSessionID = sender.address.serialize()
val threadID = message.threadId val threadID = message.threadId
val threadDB = DatabaseFactory.getThreadDatabase(context) val threadDB = DatabaseFactory.getThreadDatabase(context)
val thread = threadDB.getRecipientForThreadId(threadID) val thread = threadDB.getRecipientForThreadId(threadID)!!
val contactDB = DatabaseFactory.getSessionContactDatabase(context) val contactDB = DatabaseFactory.getSessionContactDatabase(context)
val isGroupThread = (thread?.isGroupRecipient == true) val isGroupThread = thread.isGroupRecipient
val isStartOfMessageCluster = isStartOfMessageCluster(message, previous, isGroupThread) val isStartOfMessageCluster = isStartOfMessageCluster(message, previous, isGroupThread)
val isEndOfMessageCluster = isEndOfMessageCluster(message, next, isGroupThread) val isEndOfMessageCluster = isEndOfMessageCluster(message, next, isGroupThread)
// Show profile picture and sender name if this is a group thread AND // Show profile picture and sender name if this is a group thread AND
// the message is incoming // the message is incoming
if (isGroupThread && !message.isOutgoing) { if (isGroupThread && !message.isOutgoing) {
thread!!
profilePictureContainer.visibility = if (isEndOfMessageCluster) View.VISIBLE else View.INVISIBLE profilePictureContainer.visibility = if (isEndOfMessageCluster) View.VISIBLE else View.INVISIBLE
profilePictureView.publicKey = senderSessionID profilePictureView.publicKey = senderSessionID
profilePictureView.glide = glide profilePictureView.glide = glide
@ -139,12 +138,11 @@ class VisibleMessageView : LinearLayout {
} else { } else {
messageStatusImageView.isVisible = false messageStatusImageView.isVisible = false
} }
// Populate content view // Calculate max message bubble width
var maxWidth = screenWidth - messageContentContainerLayoutParams.leftMargin - messageContentContainerLayoutParams.rightMargin var maxWidth = screenWidth - messageContentContainerLayoutParams.leftMargin - messageContentContainerLayoutParams.rightMargin
if (profilePictureContainer.visibility != View.GONE) { if (profilePictureContainer.visibility != View.GONE) { maxWidth -= profilePictureContainer.width }
maxWidth -= profilePictureContainer.width // Populate content view
} messageContentView.bind(message, isStartOfMessageCluster, isEndOfMessageCluster, glide, maxWidth, thread)
messageContentView.bind(message, isStartOfMessageCluster, isEndOfMessageCluster, glide, maxWidth)
} }
private fun setMessageSpacing(isStartOfMessageCluster: Boolean, isEndOfMessageCluster: Boolean) { private fun setMessageSpacing(isStartOfMessageCluster: Boolean, isEndOfMessageCluster: Boolean) {