Clean & add documentation

This commit is contained in:
Niels Andriesse 2021-06-23 13:57:13 +10:00
parent ca98a24839
commit c181dfec81
6 changed files with 50 additions and 13 deletions

View File

@ -160,7 +160,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
} }
@ -397,6 +397,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

@ -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

@ -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) {
// 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)) return min(max(result, toPx(32, resources)), toPx(56, resources))
} else { } else {
// 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)) 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,14 +108,16 @@ 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)
quoteViewBodyTextView.maxLines = 3 // 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)
val authorDisplayName = author?.displayName(Contact.contextForRecipient(thread)) ?: authorPublicKey val authorDisplayName = author?.displayName(Contact.contextForRecipient(thread)) ?: authorPublicKey
quoteViewAuthorTextView.text = authorDisplayName quoteViewAuthorTextView.text = authorDisplayName
quoteViewAuthorTextView.setTextColor(getTextColor(isOutgoingMessage)) quoteViewAuthorTextView.setTextColor(getTextColor(isOutgoingMessage))
quoteViewBodyTextView.maxLines = 2
} }
quoteViewAuthorTextView.isVisible = thread.isGroupRecipient quoteViewAuthorTextView.isVisible = thread.isGroupRecipient
// Body // Body
@ -108,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 {
@ -129,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

@ -65,6 +65,9 @@ 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, thread, quoteView.bind(quote.author.toString(), quote.text, quote.attachment, thread,
message.isOutgoing, maxContentWidth, message.isOpenGroupInvitation) message.isOutgoing, maxContentWidth, message.isOpenGroupInvitation)
@ -76,6 +79,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)

View File

@ -81,7 +81,7 @@ class VisibleMessageView : LinearLayout {
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
@ -138,9 +138,10 @@ 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) { maxWidth -= profilePictureContainer.width } if (profilePictureContainer.visibility != View.GONE) { maxWidth -= profilePictureContainer.width }
// Populate content view
messageContentView.bind(message, isStartOfMessageCluster, isEndOfMessageCluster, glide, maxWidth, thread) messageContentView.bind(message, isStartOfMessageCluster, isEndOfMessageCluster, glide, maxWidth, thread)
} }