From 10bf3e56fc2dba67598827f632aba9cdaf8e7cfb Mon Sep 17 00:00:00 2001 From: ganfra Date: Thu, 17 Feb 2022 19:09:51 +0100 Subject: [PATCH] Bubble: better handling of timestamp position for text messages --- .../view/MessageBubbleContentLayout.kt | 186 ++++++++++++++++++ .../item_timeline_event_text_message_stub.xml | 1 + .../main/res/layout/view_message_bubble.xml | 4 +- 3 files changed, 189 insertions(+), 2 deletions(-) create mode 100644 vector/src/main/java/im/vector/app/features/home/room/detail/timeline/view/MessageBubbleContentLayout.kt diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/view/MessageBubbleContentLayout.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/view/MessageBubbleContentLayout.kt new file mode 100644 index 0000000000..6afd3f141d --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/view/MessageBubbleContentLayout.kt @@ -0,0 +1,186 @@ +/* + * Copyright (c) 2022 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.home.room.detail.timeline.view + +import android.content.Context +import android.util.AttributeSet +import android.view.View +import android.view.ViewStub +import android.widget.TextView +import androidx.constraintlayout.widget.ConstraintLayout +import androidx.core.view.marginBottom +import androidx.core.view.marginEnd +import androidx.core.view.marginStart +import androidx.core.view.marginTop +import im.vector.app.R +import im.vector.app.core.resources.LocaleProvider +import im.vector.app.core.resources.getLayoutDirectionFromCurrentLocale + +class MessageBubbleContentLayout @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0) : + ConstraintLayout(context, attrs, defStyleAttr) { + + private var messageTextView: TextView? = null + + private lateinit var contentContainerView: View + private lateinit var timeView: View + private lateinit var contentOverlayView: View + private var localeLayoutDirection: Int = View.LAYOUT_DIRECTION_LOCALE + + private val timeViewMeasuredWidthWithMargins: Int + get() = timeView.measuredWidth + timeView.marginStart + timeView.marginEnd + + override fun onFinishInflate() { + super.onFinishInflate() + val textViewStub: ViewStub = findViewById(R.id.messageContentTextStub) + contentContainerView = findViewById(R.id.viewStubContainer) + contentOverlayView = findViewById(R.id.messageOverlayView) + timeView = findViewById(R.id.messageTimeView) + textViewStub.setOnInflateListener { _, inflated -> + textViewStub.setOnInflateListener(null) + messageTextView = inflated.findViewById(R.id.messageTextView) + } + localeLayoutDirection = LocaleProvider(resources).getLayoutDirectionFromCurrentLocale() + } + + override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { + // Let the ConstraintLayouts measure children + super.onMeasure(widthMeasureSpec, heightMeasureSpec) + val messageTextView = this.messageTextView + // Then if we have a text message layout, we can resize ourself so we can position the timeView without losing space. + if (messageTextView != null) { + val width: Int + val height: Int + val textLineCount = messageTextView.lineCount + val maxContentWidth = (contentContainerView.layoutParams as LayoutParams).matchConstraintMaxWidth + val lastLineWidth = if (textLineCount != 0) messageTextView.layout.getLineWidth(textLineCount - 1) else 0f + if (textLineCount == 1 && contentContainerView.measuredWidth + timeViewMeasuredWidthWithMargins < maxContentWidth) { + width = contentContainerView.measuredWidth + timeViewMeasuredWidthWithMargins + height = contentContainerView.measuredHeight + } else if (textLineCount > 1 && lastLineWidth + timeViewMeasuredWidthWithMargins < contentContainerView.measuredWidth - contentContainerView.paddingEnd) { + width = contentContainerView.measuredWidth + height = contentContainerView.measuredHeight + } else { + width = contentContainerView.measuredWidth + height = contentContainerView.measuredHeight + timeView.measuredHeight + } + setMeasuredDimension(width, height) + } + } + + override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) { + // If we have a text message layout, we want to render it manually, so we don't call super.onLayout + if (messageTextView != null) { + val parentLeft: Int = paddingLeft + val parentRight: Int = right - left - paddingRight + val parentTop: Int = paddingTop + val parentBottom: Int = bottom - top - paddingBottom + if (localeLayoutDirection == LAYOUT_DIRECTION_RTL) { + val contentLeft = parentRight - contentContainerView.measuredWidth - contentContainerView.marginEnd + contentContainerView.layout( + contentLeft, + parentTop + contentContainerView.marginTop, + parentRight - contentContainerView.marginEnd, + parentTop + contentContainerView.marginTop + contentContainerView.measuredHeight + ) + timeView.layout( + parentLeft + timeView.marginEnd , + parentBottom - timeView.measuredHeight - timeView.marginBottom, + parentLeft + timeView.measuredWidth + timeView.marginEnd, + parentBottom - timeView.marginBottom + ) + } else { + contentContainerView.layout( + parentLeft, + parentTop, + parentLeft + contentContainerView.measuredWidth, + parentTop + contentContainerView.measuredHeight + ) + timeView.layout( + parentRight - timeView.measuredWidth - timeView.marginEnd, + parentBottom - timeView.measuredHeight - timeView.marginBottom, + parentRight - timeView.marginEnd, + parentBottom - timeView.marginBottom + ) + } + } else { + super.onLayout(changed, left, top, right, bottom) + } + } + + /* + override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { + val widthConstraints = paddingStart + paddingEnd + val heightConstraints = paddingTop + paddingBottom + + val measuredWidth = MeasureSpec.getSize(widthMeasureSpec) + val width: Int + val height: Int + + val maxAvailableWidth = min(measuredWidth, maxWidth) + measureChild(timeView, widthMeasureSpec, heightMeasureSpec) + + val messageTextView = this.messageTextView + if (messageTextView != null) { + val newWidthMeasureSpec = MeasureSpec.makeMeasureSpec(maxAvailableWidth, MeasureSpec.AT_MOST) + measureChild(contentContainerView, newWidthMeasureSpec, heightMeasureSpec) + val textLineCount = messageTextView.lineCount + val lastLineWidth = if (textLineCount != 0) messageTextView.layout.getLineWidth(textLineCount - 1) else 0f + if (textLineCount == 1 && contentContainerView.measuredWidth + widthConstraints + timeViewMeasuredWidthWithMargins < measuredWidth) { + width = contentContainerView.measuredWidth + timeViewMeasuredWidthWithMargins + widthConstraints + height = contentContainerView.measuredHeight + heightConstraints + } else if (textLineCount > 1 && lastLineWidth + widthConstraints + timeViewMeasuredWidthWithMargins < contentContainerView.measuredWidth - contentContainerView.paddingEnd) { + width = contentContainerView.measuredWidth + widthConstraints + height = contentContainerView.measuredHeight + heightConstraints + } else { + width = contentContainerView.measuredWidth + widthConstraints + height = contentContainerView.measuredHeight + heightConstraints + timeView.measuredHeight + } + } else { + if (contentOverlayView.isVisible) { + val newWidthMeasureSpec = MeasureSpec.makeMeasureSpec(maxAvailableWidth, MeasureSpec.AT_MOST) + measureChild(contentContainerView, newWidthMeasureSpec, heightMeasureSpec) + val overlayWidthSpec = MeasureSpec.makeMeasureSpec(contentContainerView.measuredWidth, MeasureSpec.EXACTLY) + val overlayHeightSpec = MeasureSpec.makeMeasureSpec(contentContainerView.measuredHeight, MeasureSpec.EXACTLY) + contentOverlayView.measure(overlayWidthSpec, overlayHeightSpec) + width = contentContainerView.measuredWidth + height = contentContainerView.measuredHeight + } else { + val newWidthMeasureSpec = MeasureSpec.makeMeasureSpec(maxAvailableWidth, MeasureSpec.AT_MOST) + measureChild(contentContainerView, newWidthMeasureSpec, heightMeasureSpec) + width = contentContainerView.measuredWidth + height = contentContainerView.measuredHeight + timeView.measuredHeight + } + } + setMeasuredDimension(width, height) + } + + override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) { + val parentLeft: Int = paddingLeft + val parentRight: Int = right - left - paddingRight + val parentTop: Int = paddingTop + val parentBottom: Int = bottom - top - paddingBottom + val contentRight = parentLeft + contentContainerView.measuredWidth + val contentBottom = parentTop + contentContainerView.measuredHeight + contentContainerView.layout(parentLeft, parentTop, contentRight, contentBottom) + if (contentOverlayView.isVisible) { + contentOverlayView.layout(parentLeft, parentTop, contentRight, contentBottom) + } + timeView.layout(parentRight - timeView.measuredWidth - timeViewMargins, parentBottom - timeView.measuredHeight - timeViewMargins, parentRight, parentBottom) + } + + */ +} diff --git a/vector/src/main/res/layout/item_timeline_event_text_message_stub.xml b/vector/src/main/res/layout/item_timeline_event_text_message_stub.xml index e2a11c7926..5c5280ad4e 100644 --- a/vector/src/main/res/layout/item_timeline_event_text_message_stub.xml +++ b/vector/src/main/res/layout/item_timeline_event_text_message_stub.xml @@ -12,6 +12,7 @@ style="@style/Widget.Vector.TextView.Body" android:layout_width="match_parent" android:layout_height="wrap_content" + android:textAlignment="viewStart" android:textColor="?vctr_content_primary" tools:text="@sample/messages.json/data/message" /> diff --git a/vector/src/main/res/layout/view_message_bubble.xml b/vector/src/main/res/layout/view_message_bubble.xml index e6f24e43c5..5ae5afc329 100644 --- a/vector/src/main/res/layout/view_message_bubble.xml +++ b/vector/src/main/res/layout/view_message_bubble.xml @@ -84,7 +84,7 @@ android:clipChildren="false" android:clipToPadding="false"> - - +