Bubbles: R&D try to find the best way to provide dynamic layout

This commit is contained in:
ganfra 2021-12-16 20:57:05 +01:00
parent 5ea7f3cbca
commit 9a5934dd33
16 changed files with 368 additions and 91 deletions

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="MessageBubble">
<attr name="incoming_style" format="boolean" />
</declare-styleable>
</resources>

View File

@ -482,6 +482,11 @@ class MessageItemFactory @Inject constructor(
} else {
message(linkifiedBody)
}
if (informationData.sentByMe) {
layout(R.layout.item_timeline_event_bubble_outgoing_base)
} else {
layout(R.layout.item_timeline_event_bubble_incoming_base)
}
}
.useBigFont(linkifiedBody.length <= MAX_NUMBER_OF_EMOJI_FOR_BIG_FONT * 2 && containsOnlyEmojis(linkifiedBody.toString()))
.canUseTextFuture(canUseTextFuture)

View File

@ -21,10 +21,10 @@ import javax.inject.Inject
class AvatarSizeProvider @Inject constructor(private val dimensionConverter: DimensionConverter) {
private val avatarStyle = AvatarStyle.SMALL
private val avatarStyle = AvatarStyle.X_SMALL
val leftGuideline: Int by lazy {
dimensionConverter.dpToPx(avatarStyle.avatarSizeDP + 8)
dimensionConverter.dpToPx(avatarStyle.avatarSizeDP)
}
val avatarSize: Int by lazy {
@ -37,6 +37,7 @@ class AvatarSizeProvider @Inject constructor(private val dimensionConverter: Dim
BIG(50),
MEDIUM(40),
SMALL(30),
X_SMALL(24),
NONE(0)
}
}

View File

@ -57,6 +57,8 @@ class MessageInformationDataFactory @Inject constructor(private val session: Ses
val event = params.event
val nextDisplayableEvent = params.nextDisplayableEvent
val eventId = event.eventId
val isSentByMe = event.root.senderId == session.myUserId
val roomSummary = params.partialState.roomSummary
val date = event.root.localDateTime()
val nextDate = nextDisplayableEvent?.root?.localDateTime()
@ -65,20 +67,18 @@ class MessageInformationDataFactory @Inject constructor(private val session: Ses
?: false
val showInformation =
addDaySeparator ||
(addDaySeparator ||
event.senderInfo.avatarUrl != nextDisplayableEvent?.senderInfo?.avatarUrl ||
event.senderInfo.disambiguatedDisplayName != nextDisplayableEvent?.senderInfo?.disambiguatedDisplayName ||
nextDisplayableEvent.root.getClearType() !in listOf(EventType.MESSAGE, EventType.STICKER, EventType.ENCRYPTED) ||
isNextMessageReceivedMoreThanOneHourAgo ||
isTileTypeMessage(nextDisplayableEvent) ||
nextDisplayableEvent.isEdition()
nextDisplayableEvent.isEdition() ) && !isSentByMe
val time = dateFormatter.format(event.root.originServerTs, DateFormatKind.MESSAGE_SIMPLE)
val roomSummary = params.partialState.roomSummary
val e2eDecoration = getE2EDecoration(roomSummary, event)
// SendState Decoration
val isSentByMe = event.root.senderId == session.myUserId
val sendStateDecoration = if (isSentByMe) {
getSendStateDecoration(
event = event,
@ -97,8 +97,9 @@ class MessageInformationDataFactory @Inject constructor(private val session: Ses
ageLocalTS = event.root.ageLocalTs,
avatarUrl = event.senderInfo.avatarUrl,
memberName = event.senderInfo.disambiguatedDisplayName,
showInformation = showInformation,
forceShowTimestamp = vectorPreferences.alwaysShowTimeStamps(),
showAvatar = showInformation,
showDisplayName = showInformation,
showTimestamp = true,
orderedReactionList = event.annotations?.reactionsSummary
// ?.filter { isSingleEmoji(it.key) }
?.map {

View File

@ -22,7 +22,6 @@ import android.widget.ImageView
import android.widget.ProgressBar
import android.widget.TextView
import androidx.annotation.IdRes
import androidx.core.view.isInvisible
import androidx.core.view.isVisible
import com.airbnb.epoxy.EpoxyAttribute
import im.vector.app.R
@ -63,38 +62,37 @@ abstract class AbsMessageItem<H : AbsMessageItem.Holder> : AbsBaseMessageItem<H>
override fun bind(holder: H) {
super.bind(holder)
if (attributes.informationData.showInformation) {
if (attributes.informationData.showAvatar) {
holder.avatarImageView.layoutParams = holder.avatarImageView.layoutParams?.apply {
height = attributes.avatarSize
width = attributes.avatarSize
}
holder.avatarImageView.visibility = View.VISIBLE
holder.avatarImageView.onClick(_avatarClickListener)
holder.memberNameView.visibility = View.VISIBLE
holder.memberNameView.onClick(_memberNameClickListener)
holder.timeView.visibility = View.VISIBLE
holder.timeView.text = attributes.informationData.time
holder.memberNameView.text = attributes.informationData.memberName
holder.memberNameView.setTextColor(attributes.getMemberNameColor())
attributes.avatarRenderer.render(attributes.informationData.matrixItem, holder.avatarImageView)
holder.avatarImageView.setOnLongClickListener(attributes.itemLongClickListener)
holder.memberNameView.setOnLongClickListener(attributes.itemLongClickListener)
holder.avatarImageView.isVisible = true
holder.avatarImageView.onClick(_avatarClickListener)
} else {
holder.avatarImageView.setOnClickListener(null)
holder.memberNameView.setOnClickListener(null)
holder.avatarImageView.visibility = View.GONE
if (attributes.informationData.forceShowTimestamp) {
holder.memberNameView.isInvisible = true
holder.timeView.isVisible = true
holder.timeView.text = attributes.informationData.time
} else {
holder.memberNameView.isVisible = false
holder.timeView.isVisible = false
}
holder.avatarImageView.setOnLongClickListener(null)
holder.memberNameView.setOnLongClickListener(null)
holder.avatarImageView.isVisible = false
}
if (attributes.informationData.showDisplayName) {
holder.memberNameView.isVisible = true
holder.memberNameView.text = attributes.informationData.memberName
holder.memberNameView.setTextColor(attributes.getMemberNameColor())
holder.memberNameView.onClick(_memberNameClickListener)
holder.memberNameView.setOnLongClickListener(attributes.itemLongClickListener)
} else {
holder.memberNameView.setOnClickListener(null)
holder.memberNameView.setOnLongClickListener(null)
holder.memberNameView.isVisible = false
}
if (attributes.informationData.showTimestamp) {
holder.timeView.isVisible = true
holder.timeView.text = attributes.informationData.time
} else {
holder.timeView.isVisible = false
}
// Render send state indicator
holder.sendStateImageView.render(attributes.informationData.sendStateDecoration)
holder.eventSendingIndicator.isVisible = attributes.informationData.sendStateDecoration == SendStateDecoration.SENDING_MEDIA

View File

@ -31,8 +31,9 @@ data class MessageInformationData(
val ageLocalTS: Long?,
val avatarUrl: String?,
val memberName: CharSequence? = null,
val showInformation: Boolean = true,
val forceShowTimestamp: Boolean = false,
val showAvatar: Boolean,
val showDisplayName: Boolean,
val showTimestamp: Boolean,
/*List of reactions (emoji,count,isSelected)*/
val orderedReactionList: List<ReactionInfoData>? = null,
val pollResponseAggregatedSummary: PollResponseData? = null,

View File

@ -113,7 +113,7 @@ abstract class MessageTextItem : AbsMessageItem<MessageTextItem.Holder>() {
previewUrlRetriever?.removeListener(attributes.informationData.eventId, previewUrlViewUpdater)
}
override fun getViewType() = STUB_ID
override fun getViewType() = STUB_ID + layout
class Holder : AbsMessageItem.Holder(STUB_ID) {
val messageView by bind<AppCompatTextView>(R.id.messageTextView)

View File

@ -0,0 +1,58 @@
/*
* Copyright (c) 2021 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.widget.RelativeLayout
import androidx.core.content.withStyledAttributes
import im.vector.app.R
class MessageBubbleView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null,
defStyleAttr: Int = 0) : RelativeLayout(context, attrs, defStyleAttr) {
var incoming: Boolean = false
init {
inflate(context, R.layout.view_message_bubble, this)
context.withStyledAttributes(attrs, R.styleable.MessageBubble) {
incoming = getBoolean(R.styleable.MessageBubble_incoming_style, false)
}
}
override fun onFinishInflate() {
super.onFinishInflate()
val currentLayoutDirection = layoutDirection
if (incoming) {
findViewById<View>(R.id.informationBottom).layoutDirection = currentLayoutDirection
findViewById<View>(R.id.bubbleWrapper).layoutDirection = currentLayoutDirection
findViewById<View>(R.id.bubbleView).layoutDirection = currentLayoutDirection
findViewById<RelativeLayout>(R.id.bubbleView).setBackgroundResource(R.drawable.bg_timeline_incoming_message)
} else {
val oppositeLayoutDirection = if (currentLayoutDirection == View.LAYOUT_DIRECTION_LTR) {
View.LAYOUT_DIRECTION_RTL
} else {
View.LAYOUT_DIRECTION_LTR
}
findViewById<View>(R.id.informationBottom).layoutDirection = oppositeLayoutDirection
findViewById<View>(R.id.bubbleWrapper).layoutDirection = oppositeLayoutDirection
findViewById<View>(R.id.bubbleView).layoutDirection = currentLayoutDirection
findViewById<RelativeLayout>(R.id.bubbleView).setBackgroundResource(R.drawable.bg_timeline_outgoing_message)
}
}
}

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="?attr/vctr_system" />
<corners android:radius="12dp"/>
</shape>

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="#0F0DBD8B" />
<corners android:radius="12dp"/>
</shape>

View File

@ -76,68 +76,16 @@
android:visibility="gone"
tools:visibility="visible" />
<FrameLayout
<include
android:id="@+id/viewStubContainer"
android:layout_width="match_parent"
android:layout_width="0dp"
android:layout_height="wrap_content"
layout="@layout/item_timeline_event_view_stubs_container"
android:layout_below="@id/messageMemberNameView"
android:layout_marginEnd="8dp"
android:layout_toStartOf="@id/messageSendStateImageView"
android:layout_toEndOf="@id/messageStartGuideline"
android:addStatesFromChildren="true">
<ViewStub
android:id="@+id/messageContentTextStub"
style="@style/TimelineContentStubBaseParams"
android:layout_height="wrap_content"
android:layout="@layout/item_timeline_event_text_message_stub"
tools:visibility="visible" />
<ViewStub
android:id="@+id/messageContentCodeBlockStub"
style="@style/TimelineContentStubBaseParams"
android:layout_height="wrap_content"
android:layout="@layout/item_timeline_event_code_block_stub"
tools:visibility="visible" />
<ViewStub
android:id="@+id/messageContentMediaStub"
style="@style/TimelineContentStubBaseParams"
android:layout_height="wrap_content"
android:inflatedId="@+id/messageContentMedia"
android:layout="@layout/item_timeline_event_media_message_stub" />
<ViewStub
android:id="@+id/messageContentFileStub"
style="@style/TimelineContentStubBaseParams"
android:layout_height="wrap_content"
android:layout="@layout/item_timeline_event_file_stub" />
<ViewStub
android:id="@+id/messageContentRedactedStub"
style="@style/TimelineContentStubBaseParams"
android:layout_height="wrap_content"
android:layout="@layout/item_timeline_event_redacted_stub" />
<ViewStub
android:id="@+id/messagePollStub"
style="@style/TimelineContentStubBaseParams"
android:layout_height="wrap_content"
android:layout="@layout/item_timeline_event_poll_stub" />
<ViewStub
android:id="@+id/messageOptionsStub"
style="@style/TimelineContentStubBaseParams"
android:layout_height="wrap_content"
android:layout="@layout/item_timeline_event_option_buttons_stub" />
<ViewStub
android:id="@+id/messageContentVoiceStub"
style="@style/TimelineContentStubBaseParams"
android:layout="@layout/item_timeline_event_voice_stub"
tools:visibility="visible" />
</FrameLayout>
android:addStatesFromChildren="true"/>
<im.vector.app.core.ui.views.SendStateImageView
android:id="@+id/messageSendStateImageView"

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<im.vector.app.features.home.room.detail.timeline.view.MessageBubbleView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:addStatesFromChildren="true"
android:background="?attr/selectableItemBackground"
app:incoming_style="true" />

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<im.vector.app.features.home.room.detail.timeline.view.MessageBubbleView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:addStatesFromChildren="true"
android:background="?attr/selectableItemBackground"
app:incoming_style="false" />

View File

@ -23,6 +23,6 @@
android:layout_marginBottom="4dp"
android:foreground="?attr/selectableItemBackground"
android:visibility="gone"
tools:visibility="visible" />
tools:visibility="gone" />
</LinearLayout>

View File

@ -0,0 +1,61 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:addStatesFromChildren="true">
<ViewStub
android:id="@+id/messageContentTextStub"
style="@style/TimelineContentStubBaseParams"
android:layout_height="wrap_content"
android:layout="@layout/item_timeline_event_text_message_stub"
tools:visibility="visible" />
<ViewStub
android:id="@+id/messageContentCodeBlockStub"
style="@style/TimelineContentStubBaseParams"
android:layout_height="wrap_content"
android:layout="@layout/item_timeline_event_code_block_stub"
tools:visibility="visible" />
<ViewStub
android:id="@+id/messageContentMediaStub"
style="@style/TimelineContentStubBaseParams"
android:layout_height="wrap_content"
android:inflatedId="@+id/messageContentMedia"
android:layout="@layout/item_timeline_event_media_message_stub" />
<ViewStub
android:id="@+id/messageContentFileStub"
style="@style/TimelineContentStubBaseParams"
android:layout_height="wrap_content"
android:layout="@layout/item_timeline_event_file_stub" />
<ViewStub
android:id="@+id/messageContentRedactedStub"
style="@style/TimelineContentStubBaseParams"
android:layout_height="wrap_content"
android:layout="@layout/item_timeline_event_redacted_stub" />
<ViewStub
android:id="@+id/messagePollStub"
style="@style/TimelineContentStubBaseParams"
android:layout_height="wrap_content"
android:layout="@layout/item_timeline_event_poll_stub" />
<ViewStub
android:id="@+id/messageOptionsStub"
style="@style/TimelineContentStubBaseParams"
android:layout_height="wrap_content"
android:layout="@layout/item_timeline_event_option_buttons_stub" />
<ViewStub
android:id="@+id/messageContentVoiceStub"
style="@style/TimelineContentStubBaseParams"
android:layout="@layout/item_timeline_event_voice_stub"
tools:visibility="visible" />
</FrameLayout>

View File

@ -0,0 +1,170 @@
<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
tools:parentTag="android.widget.RelativeLayout"
tools:viewBindingIgnore="true">
<im.vector.app.core.platform.CheckableView
android:id="@+id/messageSelectedBackground"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_alignBottom="@id/informationBottom"
android:layout_alignParentTop="true"
android:background="@drawable/highlighted_message_background" />
<ImageView
android:id="@+id/messageAvatarImageView"
android:layout_width="44dp"
android:layout_height="44dp"
android:layout_marginStart="8dp"
android:layout_marginTop="4dp"
android:contentDescription="@string/avatar"
tools:src="@sample/user_round_avatars" />
<TextView
android:id="@+id/messageMemberNameView"
style="@style/Widget.Vector.TextView.Subtitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_alignParentEnd="true"
android:layout_marginStart="8dp"
android:layout_marginEnd="4dp"
android:layout_toEndOf="@id/messageStartGuideline"
android:ellipsize="end"
android:maxLines="1"
android:textColor="?vctr_content_primary"
android:textStyle="bold"
tools:text="@sample/users.json/data/displayName" />
<View
android:id="@+id/messageStartGuideline"
android:layout_width="0dp"
android:layout_height="0dp"
tools:layout_marginStart="52dp" />
<Space
android:id="@+id/decorationSpace"
android:layout_width="4dp"
android:layout_height="8dp"
android:layout_toEndOf="@id/messageStartGuideline" />
<im.vector.app.core.ui.views.ShieldImageView
android:id="@+id/messageE2EDecoration"
android:layout_width="16dp"
android:layout_height="16dp"
android:layout_alignTop="@id/bubbleWrapper"
android:layout_alignEnd="@id/decorationSpace"
android:layout_marginTop="7dp"
android:visibility="gone"
tools:visibility="visible" />
<FrameLayout
android:id="@+id/bubbleWrapper"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/messageMemberNameView"
android:layout_toStartOf="@id/messageSendStateImageView"
android:layout_toEndOf="@id/messageStartGuideline"
android:addStatesFromChildren="true"
android:clipChildren="false"
android:clipToPadding="false">
<RelativeLayout
android:id="@+id/bubbleView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginVertical="4dp"
android:layout_marginStart="0dp"
android:layout_marginEnd="0dp"
android:addStatesFromChildren="true"
android:background="@drawable/bg_timeline_incoming_message"
android:clipChildren="false"
android:clipToPadding="false"
tools:ignore="UselessParent">
<include
android:id="@+id/viewStubContainer"
layout="@layout/item_timeline_event_view_stubs_container"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:addStatesFromChildren="true" />
<TextView
android:id="@+id/messageTimeView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignBottom="@id/viewStubContainer"
android:layout_marginEnd="8dp"
android:layout_marginBottom="4dp"
android:layout_toEndOf="@id/viewStubContainer"
android:textColor="?vctr_content_tertiary"
android:textSize="10sp"
tools:text="@tools:sample/date/hhmm" />
</RelativeLayout>
</FrameLayout>
<im.vector.app.core.ui.views.SendStateImageView
android:id="@+id/messageSendStateImageView"
android:layout_width="@dimen/item_event_message_state_size"
android:layout_height="@dimen/item_event_message_state_size"
android:layout_alignBottom="@id/bubbleWrapper"
android:layout_alignParentEnd="true"
android:layout_marginStart="8dp"
android:layout_marginEnd="8dp"
android:layout_marginBottom="4dp"
android:contentDescription="@string/event_status_a11y_sending"
android:src="@drawable/ic_sending_message"
android:visibility="invisible"
tools:tint="?vctr_content_tertiary"
tools:visibility="visible" />
<ProgressBar
android:id="@+id/eventSendingIndicator"
android:layout_width="@dimen/item_event_message_state_size"
android:layout_height="@dimen/item_event_message_state_size"
android:layout_alignBottom="@id/bubbleWrapper"
android:layout_alignParentEnd="true"
android:layout_marginStart="8dp"
android:layout_marginEnd="8dp"
android:layout_marginBottom="4dp"
android:indeterminateTint="?vctr_content_secondary"
android:visibility="gone"
app:tint="?vctr_content_tertiary"
tools:ignore="MissingPrefix"
tools:visibility="visible" />
<LinearLayout
android:id="@+id/informationBottom"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/bubbleWrapper"
android:layout_toEndOf="@id/messageStartGuideline"
android:addStatesFromChildren="true"
android:orientation="vertical">
<com.google.android.flexbox.FlexboxLayout
android:id="@+id/reactionsContainer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginBottom="4dp"
app:dividerDrawable="@drawable/reaction_divider"
app:flexWrap="wrap"
app:showDivider="middle"
tools:background="#F0E0F0"
tools:layout_height="40dp">
<!-- ReactionButtons will be added here in the code -->
<!--im.vector.app.features.reactions.widget.ReactionButton
android:layout_width="wrap_content"
android:layout_height="wrap_content" /-->
</com.google.android.flexbox.FlexboxLayout>
</LinearLayout>
</merge>