From 9a5934dd33ce66353e63d34206d16ec9a6cdba7f Mon Sep 17 00:00:00 2001 From: ganfra Date: Thu, 16 Dec 2021 20:57:05 +0100 Subject: [PATCH 01/36] Bubbles: R&D try to find the best way to provide dynamic layout --- .../res/values/stylable_message_bubble.xml | 8 + .../timeline/factory/MessageItemFactory.kt | 5 + .../timeline/helper/AvatarSizeProvider.kt | 5 +- .../helper/MessageInformationDataFactory.kt | 13 +- .../detail/timeline/item/AbsMessageItem.kt | 44 +++-- .../timeline/item/MessageInformationData.kt | 5 +- .../detail/timeline/item/MessageTextItem.kt | 2 +- .../detail/timeline/view/MessageBubbleView.kt | 58 ++++++ .../drawable/bg_timeline_incoming_message.xml | 5 + .../drawable/bg_timeline_outgoing_message.xml | 5 + .../res/layout/item_timeline_event_base.xml | 60 +------ ...em_timeline_event_bubble_incoming_base.xml | 8 + ...em_timeline_event_bubble_outgoing_base.xml | 8 + .../item_timeline_event_text_message_stub.xml | 2 +- ...em_timeline_event_view_stubs_container.xml | 61 +++++++ .../main/res/layout/view_message_bubble.xml | 170 ++++++++++++++++++ 16 files changed, 368 insertions(+), 91 deletions(-) create mode 100644 library/ui-styles/src/main/res/values/stylable_message_bubble.xml create mode 100644 vector/src/main/java/im/vector/app/features/home/room/detail/timeline/view/MessageBubbleView.kt create mode 100644 vector/src/main/res/drawable/bg_timeline_incoming_message.xml create mode 100644 vector/src/main/res/drawable/bg_timeline_outgoing_message.xml create mode 100644 vector/src/main/res/layout/item_timeline_event_bubble_incoming_base.xml create mode 100644 vector/src/main/res/layout/item_timeline_event_bubble_outgoing_base.xml create mode 100644 vector/src/main/res/layout/item_timeline_event_view_stubs_container.xml create mode 100644 vector/src/main/res/layout/view_message_bubble.xml diff --git a/library/ui-styles/src/main/res/values/stylable_message_bubble.xml b/library/ui-styles/src/main/res/values/stylable_message_bubble.xml new file mode 100644 index 0000000000..8da2df33e7 --- /dev/null +++ b/library/ui-styles/src/main/res/values/stylable_message_bubble.xml @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt index 98deaaf9c3..05973be8be 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt @@ -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) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/AvatarSizeProvider.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/AvatarSizeProvider.kt index 5fc5deb407..c2662e7ae6 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/AvatarSizeProvider.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/AvatarSizeProvider.kt @@ -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) } } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/MessageInformationDataFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/MessageInformationDataFactory.kt index 6385494fe1..eef174c0f3 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/MessageInformationDataFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/MessageInformationDataFactory.kt @@ -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 { diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/AbsMessageItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/AbsMessageItem.kt index b53495fdaf..a964af6f73 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/AbsMessageItem.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/AbsMessageItem.kt @@ -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 : AbsBaseMessageItem 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 diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageInformationData.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageInformationData.kt index 08aa301538..94c6b32b0a 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageInformationData.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageInformationData.kt @@ -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? = null, val pollResponseAggregatedSummary: PollResponseData? = null, diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageTextItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageTextItem.kt index 747183bce6..d950b7226a 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageTextItem.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageTextItem.kt @@ -113,7 +113,7 @@ abstract class MessageTextItem : AbsMessageItem() { 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(R.id.messageTextView) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/view/MessageBubbleView.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/view/MessageBubbleView.kt new file mode 100644 index 0000000000..66fb5cd998 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/view/MessageBubbleView.kt @@ -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(R.id.informationBottom).layoutDirection = currentLayoutDirection + findViewById(R.id.bubbleWrapper).layoutDirection = currentLayoutDirection + findViewById(R.id.bubbleView).layoutDirection = currentLayoutDirection + findViewById(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(R.id.informationBottom).layoutDirection = oppositeLayoutDirection + findViewById(R.id.bubbleWrapper).layoutDirection = oppositeLayoutDirection + findViewById(R.id.bubbleView).layoutDirection = currentLayoutDirection + findViewById(R.id.bubbleView).setBackgroundResource(R.drawable.bg_timeline_outgoing_message) + } + } +} diff --git a/vector/src/main/res/drawable/bg_timeline_incoming_message.xml b/vector/src/main/res/drawable/bg_timeline_incoming_message.xml new file mode 100644 index 0000000000..ad4a28a770 --- /dev/null +++ b/vector/src/main/res/drawable/bg_timeline_incoming_message.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/vector/src/main/res/drawable/bg_timeline_outgoing_message.xml b/vector/src/main/res/drawable/bg_timeline_outgoing_message.xml new file mode 100644 index 0000000000..1ab85d2352 --- /dev/null +++ b/vector/src/main/res/drawable/bg_timeline_outgoing_message.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/vector/src/main/res/layout/item_timeline_event_base.xml b/vector/src/main/res/layout/item_timeline_event_base.xml index 3316a651d7..debf578519 100644 --- a/vector/src/main/res/layout/item_timeline_event_base.xml +++ b/vector/src/main/res/layout/item_timeline_event_base.xml @@ -76,68 +76,16 @@ android:visibility="gone" tools:visibility="visible" /> - - - - - - - - - - - - - - - - - - - + android:addStatesFromChildren="true"/> + diff --git a/vector/src/main/res/layout/item_timeline_event_bubble_outgoing_base.xml b/vector/src/main/res/layout/item_timeline_event_bubble_outgoing_base.xml new file mode 100644 index 0000000000..42bf1b5f7a --- /dev/null +++ b/vector/src/main/res/layout/item_timeline_event_bubble_outgoing_base.xml @@ -0,0 +1,8 @@ + + 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 3f9feb93af..4bb612fedf 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 @@ -23,6 +23,6 @@ android:layout_marginBottom="4dp" android:foreground="?attr/selectableItemBackground" android:visibility="gone" - tools:visibility="visible" /> + tools:visibility="gone" /> diff --git a/vector/src/main/res/layout/item_timeline_event_view_stubs_container.xml b/vector/src/main/res/layout/item_timeline_event_view_stubs_container.xml new file mode 100644 index 0000000000..04613c665f --- /dev/null +++ b/vector/src/main/res/layout/item_timeline_event_view_stubs_container.xml @@ -0,0 +1,61 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/vector/src/main/res/layout/view_message_bubble.xml b/vector/src/main/res/layout/view_message_bubble.xml new file mode 100644 index 0000000000..36c5592f07 --- /dev/null +++ b/vector/src/main/res/layout/view_message_bubble.xml @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file From bde1df03224ac8cb5422467cb8347da973b687c2 Mon Sep 17 00:00:00 2001 From: ganfra Date: Wed, 5 Jan 2022 11:00:12 +0100 Subject: [PATCH 02/36] Bubbles: continue R&D on UI --- .../ui-styles/src/main/res/values/colors.xml | 4 ++ .../ui-styles/src/main/res/values/dimens.xml | 4 ++ .../res/values/stylable_message_bubble.xml | 2 + .../app/features/home/AvatarRenderer.kt | 1 + .../timeline/helper/AvatarSizeProvider.kt | 4 +- .../detail/timeline/view/MessageBubbleView.kt | 56 ++++++++++++++++++- .../main/res/drawable/bg_avatar_border.xml | 12 ++++ .../drawable/bg_timeline_incoming_message.xml | 1 - .../drawable/bg_timeline_outgoing_message.xml | 1 - .../main/res/layout/view_message_bubble.xml | 43 +++++++++----- 10 files changed, 109 insertions(+), 19 deletions(-) create mode 100644 vector/src/main/res/drawable/bg_avatar_border.xml diff --git a/library/ui-styles/src/main/res/values/colors.xml b/library/ui-styles/src/main/res/values/colors.xml index 9df2794a1a..e3ec542c89 100644 --- a/library/ui-styles/src/main/res/values/colors.xml +++ b/library/ui-styles/src/main/res/values/colors.xml @@ -13,6 +13,9 @@ #14368BD6 @color/palette_azure + #0F0DBD8B + @color/element_system_light + @color/palette_azure @color/palette_melon @@ -137,4 +140,5 @@ @color/palette_gray_100 @color/palette_gray_450 + diff --git a/library/ui-styles/src/main/res/values/dimens.xml b/library/ui-styles/src/main/res/values/dimens.xml index 9fbf8958da..6eff9295d0 100644 --- a/library/ui-styles/src/main/res/values/dimens.xml +++ b/library/ui-styles/src/main/res/values/dimens.xml @@ -47,4 +47,8 @@ 56dp 52dp 1dp + + 28dp + 62dp + \ No newline at end of file diff --git a/library/ui-styles/src/main/res/values/stylable_message_bubble.xml b/library/ui-styles/src/main/res/values/stylable_message_bubble.xml index 8da2df33e7..1f55d07486 100644 --- a/library/ui-styles/src/main/res/values/stylable_message_bubble.xml +++ b/library/ui-styles/src/main/res/values/stylable_message_bubble.xml @@ -3,6 +3,8 @@ + + diff --git a/vector/src/main/java/im/vector/app/features/home/AvatarRenderer.kt b/vector/src/main/java/im/vector/app/features/home/AvatarRenderer.kt index 2ee3233637..be689eb27a 100644 --- a/vector/src/main/java/im/vector/app/features/home/AvatarRenderer.kt +++ b/vector/src/main/java/im/vector/app/features/home/AvatarRenderer.kt @@ -145,6 +145,7 @@ class AvatarRenderer @Inject constructor(private val activeSessionHolder: Active } else -> { it.apply(RequestOptions.circleCropTransform()) + } } } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/AvatarSizeProvider.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/AvatarSizeProvider.kt index c2662e7ae6..00b02c2cf0 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/AvatarSizeProvider.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/AvatarSizeProvider.kt @@ -24,7 +24,7 @@ class AvatarSizeProvider @Inject constructor(private val dimensionConverter: Dim private val avatarStyle = AvatarStyle.X_SMALL val leftGuideline: Int by lazy { - dimensionConverter.dpToPx(avatarStyle.avatarSizeDP) + dimensionConverter.dpToPx(avatarStyle.avatarSizeDP + 4) } val avatarSize: Int by lazy { @@ -37,7 +37,7 @@ class AvatarSizeProvider @Inject constructor(private val dimensionConverter: Dim BIG(50), MEDIUM(40), SMALL(30), - X_SMALL(24), + X_SMALL(28), NONE(0) } } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/view/MessageBubbleView.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/view/MessageBubbleView.kt index 66fb5cd998..9ad7eb097c 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/view/MessageBubbleView.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/view/MessageBubbleView.kt @@ -17,42 +17,94 @@ package im.vector.app.features.home.room.detail.timeline.view import android.content.Context +import android.graphics.drawable.Drawable import android.util.AttributeSet import android.view.View +import android.view.ViewGroup +import android.view.ViewOutlineProvider import android.widget.RelativeLayout +import androidx.core.content.ContextCompat import androidx.core.content.withStyledAttributes +import androidx.core.view.updateLayoutParams +import com.google.android.material.shape.CornerFamily +import com.google.android.material.shape.MaterialShapeDrawable +import com.google.android.material.shape.ShapeAppearanceModel import im.vector.app.R +import im.vector.app.core.utils.DimensionConverter class MessageBubbleView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0) : RelativeLayout(context, attrs, defStyleAttr) { var incoming: Boolean = false + var isFirst: Boolean = false + var isLast: Boolean = false + var cornerRadius = DimensionConverter(resources).dpToPx(12).toFloat() init { inflate(context, R.layout.view_message_bubble, this) context.withStyledAttributes(attrs, R.styleable.MessageBubble) { incoming = getBoolean(R.styleable.MessageBubble_incoming_style, false) + isFirst = getBoolean(R.styleable.MessageBubble_is_first, false) + isLast = getBoolean(R.styleable.MessageBubble_is_last, false) } } override fun onFinishInflate() { super.onFinishInflate() val currentLayoutDirection = layoutDirection + findViewById(R.id.bubbleView).apply { + background = createBackgroundDrawable() + outlineProvider = ViewOutlineProvider.BACKGROUND + clipToOutline = true + } if (incoming) { findViewById(R.id.informationBottom).layoutDirection = currentLayoutDirection findViewById(R.id.bubbleWrapper).layoutDirection = currentLayoutDirection findViewById(R.id.bubbleView).layoutDirection = currentLayoutDirection - findViewById(R.id.bubbleView).setBackgroundResource(R.drawable.bg_timeline_incoming_message) + findViewById(R.id.messageEndGuideline).updateLayoutParams { + marginEnd = resources.getDimensionPixelSize(R.dimen.chat_bubble_margin_end) + } } else { val oppositeLayoutDirection = if (currentLayoutDirection == View.LAYOUT_DIRECTION_LTR) { View.LAYOUT_DIRECTION_RTL } else { View.LAYOUT_DIRECTION_LTR } + findViewById(R.id.informationBottom).layoutDirection = oppositeLayoutDirection findViewById(R.id.bubbleWrapper).layoutDirection = oppositeLayoutDirection findViewById(R.id.bubbleView).layoutDirection = currentLayoutDirection - findViewById(R.id.bubbleView).setBackgroundResource(R.drawable.bg_timeline_outgoing_message) + findViewById(R.id.messageEndGuideline).updateLayoutParams { + marginEnd = resources.getDimensionPixelSize(R.dimen.chat_bubble_margin_start) + } } } + + private fun createBackgroundDrawable(): Drawable { + val topCornerFamily = if (isFirst) CornerFamily.ROUNDED else CornerFamily.CUT + val bottomCornerFamily = if (isLast) CornerFamily.ROUNDED else CornerFamily.CUT + val topRadius = if (isFirst) cornerRadius else 0f + val bottomRadius = if (isLast) cornerRadius else 0f + val shapeAppearanceModelBuilder = ShapeAppearanceModel().toBuilder() + val backgroundColor: Int + if (incoming) { + backgroundColor = R.color.bubble_background_incoming + shapeAppearanceModelBuilder + .setTopRightCorner(CornerFamily.ROUNDED, cornerRadius) + .setBottomRightCorner(CornerFamily.ROUNDED, cornerRadius) + .setTopLeftCorner(topCornerFamily, topRadius) + .setBottomLeftCorner(bottomCornerFamily, bottomRadius) + } else { + backgroundColor = R.color.bubble_background_outgoing + shapeAppearanceModelBuilder + .setTopLeftCorner(CornerFamily.ROUNDED, cornerRadius) + .setBottomLeftCorner(CornerFamily.ROUNDED, cornerRadius) + .setTopRightCorner(topCornerFamily, topRadius) + .setBottomRightCorner(bottomCornerFamily, bottomRadius) + } + val shapeAppearanceModel = shapeAppearanceModelBuilder.build() + val shapeDrawable = MaterialShapeDrawable(shapeAppearanceModel) + shapeDrawable.fillColor = ContextCompat.getColorStateList(context, backgroundColor) + return shapeDrawable + } } diff --git a/vector/src/main/res/drawable/bg_avatar_border.xml b/vector/src/main/res/drawable/bg_avatar_border.xml new file mode 100644 index 0000000000..e22731c1a3 --- /dev/null +++ b/vector/src/main/res/drawable/bg_avatar_border.xml @@ -0,0 +1,12 @@ + + + + + + + + \ No newline at end of file diff --git a/vector/src/main/res/drawable/bg_timeline_incoming_message.xml b/vector/src/main/res/drawable/bg_timeline_incoming_message.xml index ad4a28a770..2cbca33702 100644 --- a/vector/src/main/res/drawable/bg_timeline_incoming_message.xml +++ b/vector/src/main/res/drawable/bg_timeline_incoming_message.xml @@ -1,5 +1,4 @@ - \ No newline at end of file diff --git a/vector/src/main/res/drawable/bg_timeline_outgoing_message.xml b/vector/src/main/res/drawable/bg_timeline_outgoing_message.xml index 1ab85d2352..0f75705a77 100644 --- a/vector/src/main/res/drawable/bg_timeline_outgoing_message.xml +++ b/vector/src/main/res/drawable/bg_timeline_outgoing_message.xml @@ -1,5 +1,4 @@ - \ No newline at end of file diff --git a/vector/src/main/res/layout/view_message_bubble.xml b/vector/src/main/res/layout/view_message_bubble.xml index 36c5592f07..12f16910f1 100644 --- a/vector/src/main/res/layout/view_message_bubble.xml +++ b/vector/src/main/res/layout/view_message_bubble.xml @@ -19,9 +19,12 @@ android:id="@+id/messageAvatarImageView" android:layout_width="44dp" android:layout_height="44dp" - android:layout_marginStart="8dp" + android:layout_marginStart="12dp" android:layout_marginTop="4dp" + android:padding="2dp" + android:background="@drawable/bg_avatar_border" android:contentDescription="@string/avatar" + android:elevation="2dp" tools:src="@sample/user_round_avatars" /> + + @@ -67,45 +78,49 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_below="@id/messageMemberNameView" - android:layout_toStartOf="@id/messageSendStateImageView" + android:layout_toStartOf="@id/messageEndGuideline" android:layout_toEndOf="@id/messageStartGuideline" android:addStatesFromChildren="true" android:clipChildren="false" android:clipToPadding="false"> - + android:paddingStart="4dp" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent"> + android:addStatesFromChildren="true" + app:layout_constrainedWidth="true" + app:layout_constraintEnd_toStartOf="@id/messageTimeView" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" + app:layout_constraintWidth_max="300dp" /> - + @@ -151,7 +169,6 @@ 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" From ad63d3de1c57cb8d9bf83741c7c59771fb7fc514 Mon Sep 17 00:00:00 2001 From: ganfra Date: Thu, 6 Jan 2022 19:07:28 +0100 Subject: [PATCH 03/36] Bubbles: still R&D. Not sure how to handle every event types. --- .../res/values/stylable_message_bubble.xml | 1 + .../timeline/TimelineEventController.kt | 8 +- .../timeline/factory/MessageItemFactory.kt | 7 ++ .../helper/MessageInformationDataFactory.kt | 5 ++ .../timeline/item/AbsBaseMessageItem.kt | 5 ++ .../timeline/item/MessageImageVideoItem.kt | 3 + .../timeline/item/MessageInformationData.kt | 4 +- .../detail/timeline/view/MessageBubbleView.kt | 86 +++++++++++++++---- .../timeline/view/MessageViewConfiguration.kt | 24 ++++++ ...item_timeline_event_media_message_stub.xml | 1 + .../main/res/layout/view_message_bubble.xml | 2 +- 11 files changed, 121 insertions(+), 25 deletions(-) create mode 100644 vector/src/main/java/im/vector/app/features/home/room/detail/timeline/view/MessageViewConfiguration.kt diff --git a/library/ui-styles/src/main/res/values/stylable_message_bubble.xml b/library/ui-styles/src/main/res/values/stylable_message_bubble.xml index 1f55d07486..32ed23c613 100644 --- a/library/ui-styles/src/main/res/values/stylable_message_bubble.xml +++ b/library/ui-styles/src/main/res/values/stylable_message_bubble.xml @@ -3,6 +3,7 @@ + diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/TimelineEventController.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/TimelineEventController.kt index 241ccb7428..dc5c76725b 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/TimelineEventController.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/TimelineEventController.kt @@ -355,12 +355,12 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec (0 until modelCache.size).forEach { position -> val event = currentSnapshot[position] val nextEvent = currentSnapshot.nextOrNull(position) - val prevEvent = currentSnapshot.prevOrNull(position) - val nextDisplayableEvent = currentSnapshot.subList(position + 1, currentSnapshot.size).firstOrNull { - timelineEventVisibilityHelper.shouldShowEvent(it, partialState.highlightedEventId) - } // Should be build if not cached or if model should be refreshed if (modelCache[position] == null || modelCache[position]?.isCacheable(partialState) == false) { + val prevEvent = currentSnapshot.prevOrNull(position) + val nextDisplayableEvent = currentSnapshot.subList(position + 1, currentSnapshot.size).firstOrNull { + timelineEventVisibilityHelper.shouldShowEvent(it, partialState.highlightedEventId) + } val timelineEventsGroup = timelineEventsGroups.getOrNull(event) val params = TimelineItemFactoryParams( event = event, diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt index 697c307d06..c42d50e924 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt @@ -389,6 +389,13 @@ class MessageItemFactory @Inject constructor( allowNonMxcUrls = informationData.sendState.isSending() ) return MessageImageVideoItem_() + .layout( + if (informationData.sentByMe) { + R.layout.item_timeline_event_bubble_outgoing_base + } else { + R.layout.item_timeline_event_bubble_incoming_base + } + ) .attributes(attributes) .leftGuideline(avatarSizeProvider.leftGuideline) .imageContentRenderer(imageContentRenderer) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/MessageInformationDataFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/MessageInformationDataFactory.kt index b203c23978..b9ef9ca558 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/MessageInformationDataFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/MessageInformationDataFactory.kt @@ -57,8 +57,11 @@ class MessageInformationDataFactory @Inject constructor(private val session: Ses fun create(params: TimelineItemFactoryParams): MessageInformationData { val event = params.event val nextDisplayableEvent = params.nextDisplayableEvent + val prevEvent = params.prevEvent val eventId = event.eventId val isSentByMe = event.root.senderId == session.myUserId + val isFirstFromThisSender = nextDisplayableEvent?.root?.senderId != event.root.senderId + val isLastFromThisSender = prevEvent?.root?.senderId != event.root.senderId val roomSummary = params.partialState.roomSummary val date = event.root.localDateTime() @@ -128,6 +131,8 @@ class MessageInformationDataFactory @Inject constructor(private val session: Ses ReferencesInfoData(verificationState) }, sentByMe = isSentByMe, + isFirstFromThisSender = isFirstFromThisSender, + isLastFromThisSender = isLastFromThisSender, e2eDecoration = e2eDecoration, sendStateDecoration = sendStateDecoration ) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/AbsBaseMessageItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/AbsBaseMessageItem.kt index 080b766258..3d9db4e827 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/AbsBaseMessageItem.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/AbsBaseMessageItem.kt @@ -29,6 +29,7 @@ import im.vector.app.core.ui.views.ShieldImageView import im.vector.app.features.home.AvatarRenderer import im.vector.app.features.home.room.detail.timeline.MessageColorProvider import im.vector.app.features.home.room.detail.timeline.TimelineEventController +import im.vector.app.features.home.room.detail.timeline.view.MessageViewConfiguration import im.vector.app.features.reactions.widget.ReactionButton import org.matrix.android.sdk.api.crypto.RoomEncryptionTrustLevel import org.matrix.android.sdk.api.session.room.send.SendState @@ -98,6 +99,10 @@ abstract class AbsBaseMessageItem : BaseEventItem holder.view.onClick(baseAttributes.itemClickListener) holder.view.setOnLongClickListener(baseAttributes.itemLongClickListener) + (holder.view as? MessageViewConfiguration)?.apply { + isFirstFromSender = baseAttributes.informationData.isFirstFromThisSender + isLastFromSender = baseAttributes.informationData.isLastFromThisSender + } } override fun unbind(holder: H) { diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageImageVideoItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageImageVideoItem.kt index 3ae91db97c..8e42297bc1 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageImageVideoItem.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageImageVideoItem.kt @@ -29,6 +29,8 @@ import im.vector.app.core.epoxy.onClick import im.vector.app.core.files.LocalFilesHelper import im.vector.app.core.glide.GlideApp import im.vector.app.features.home.room.detail.timeline.helper.ContentUploadStateTrackerBinder +import im.vector.app.features.home.room.detail.timeline.view.MessageBubbleView +import im.vector.app.features.home.room.detail.timeline.view.MessageViewConfiguration import im.vector.app.features.media.ImageContentRenderer @EpoxyModelClass(layout = R.layout.item_timeline_event_base) @@ -70,6 +72,7 @@ abstract class MessageImageVideoItem : AbsMessageItem(R.id.bubbleView).apply { + val bubbleView: ConstraintLayout = findViewById(R.id.bubbleView) + bubbleView.apply { background = createBackgroundDrawable() outlineProvider = ViewOutlineProvider.BACKGROUND clipToOutline = true } - if (incoming) { + if (isIncoming) { findViewById(R.id.informationBottom).layoutDirection = currentLayoutDirection findViewById(R.id.bubbleWrapper).layoutDirection = currentLayoutDirection - findViewById(R.id.bubbleView).layoutDirection = currentLayoutDirection + bubbleView.layoutDirection = currentLayoutDirection findViewById(R.id.messageEndGuideline).updateLayoutParams { marginEnd = resources.getDimensionPixelSize(R.dimen.chat_bubble_margin_end) } @@ -73,21 +101,37 @@ class MessageBubbleView @JvmOverloads constructor(context: Context, attrs: Attri findViewById(R.id.informationBottom).layoutDirection = oppositeLayoutDirection findViewById(R.id.bubbleWrapper).layoutDirection = oppositeLayoutDirection - findViewById(R.id.bubbleView).layoutDirection = currentLayoutDirection + bubbleView.layoutDirection = currentLayoutDirection findViewById(R.id.messageEndGuideline).updateLayoutParams { marginEnd = resources.getDimensionPixelSize(R.dimen.chat_bubble_margin_start) } } + ConstraintSet().apply { + clone(bubbleView) + clear(R.id.viewStubContainer, ConstraintSet.END) + if (displayBorder) { + connect(R.id.viewStubContainer, ConstraintSet.END, R.id.messageTimeView, ConstraintSet.START, 0) + } else { + connect(R.id.viewStubContainer, ConstraintSet.END, R.id.parent, ConstraintSet.END, 0) + } + applyTo(bubbleView) + } } private fun createBackgroundDrawable(): Drawable { - val topCornerFamily = if (isFirst) CornerFamily.ROUNDED else CornerFamily.CUT - val bottomCornerFamily = if (isLast) CornerFamily.ROUNDED else CornerFamily.CUT - val topRadius = if (isFirst) cornerRadius else 0f - val bottomRadius = if (isLast) cornerRadius else 0f + val (topCornerFamily, topRadius) = if (isFirstFromSender) { + Pair(CornerFamily.ROUNDED, cornerRadius) + } else { + Pair(CornerFamily.CUT, 0f) + } + val (bottomCornerFamily, bottomRadius) = if (isLastFromSender) { + Pair(CornerFamily.ROUNDED, cornerRadius) + } else { + Pair(CornerFamily.CUT, 0f) + } val shapeAppearanceModelBuilder = ShapeAppearanceModel().toBuilder() val backgroundColor: Int - if (incoming) { + if (isIncoming) { backgroundColor = R.color.bubble_background_incoming shapeAppearanceModelBuilder .setTopRightCorner(CornerFamily.ROUNDED, cornerRadius) @@ -104,7 +148,11 @@ class MessageBubbleView @JvmOverloads constructor(context: Context, attrs: Attri } val shapeAppearanceModel = shapeAppearanceModelBuilder.build() val shapeDrawable = MaterialShapeDrawable(shapeAppearanceModel) - shapeDrawable.fillColor = ContextCompat.getColorStateList(context, backgroundColor) + if (displayBorder) { + shapeDrawable.fillColor = ContextCompat.getColorStateList(context, backgroundColor) + } else { + shapeDrawable.fillColor = ContextCompat.getColorStateList(context, android.R.color.transparent) + } return shapeDrawable } } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/view/MessageViewConfiguration.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/view/MessageViewConfiguration.kt new file mode 100644 index 0000000000..f441005edf --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/view/MessageViewConfiguration.kt @@ -0,0 +1,24 @@ +/* + * 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 + +interface MessageViewConfiguration { + var isIncoming: Boolean + var isFirstFromSender: Boolean + var isLastFromSender: Boolean + var displayBorder: Boolean +} diff --git a/vector/src/main/res/layout/item_timeline_event_media_message_stub.xml b/vector/src/main/res/layout/item_timeline_event_media_message_stub.xml index 9e2a5ef3ed..988e2d8bb1 100644 --- a/vector/src/main/res/layout/item_timeline_event_media_message_stub.xml +++ b/vector/src/main/res/layout/item_timeline_event_media_message_stub.xml @@ -4,6 +4,7 @@ xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="wrap_content" + android:background="@color/palette_element_green" tools:viewBindingIgnore="true"> From f7c9b36cef33a037c0237f0c37f817ccdb542042 Mon Sep 17 00:00:00 2001 From: ganfra Date: Tue, 11 Jan 2022 11:57:35 +0100 Subject: [PATCH 04/36] Bubbles: continue exploration --- .../main/res/values/stylable_message_bubble.xml | 2 +- .../detail/timeline/TimelineEventController.kt | 4 ++++ .../timeline/factory/TimelineItemFactoryParams.kt | 1 + .../helper/MessageInformationDataFactory.kt | 10 ++++++---- .../detail/timeline/item/MessageImageVideoItem.kt | 3 +-- .../detail/timeline/view/MessageBubbleView.kt | 15 ++++++--------- .../timeline/view/MessageViewConfiguration.kt | 2 +- .../item_timeline_event_media_message_stub.xml | 2 -- 8 files changed, 20 insertions(+), 19 deletions(-) diff --git a/library/ui-styles/src/main/res/values/stylable_message_bubble.xml b/library/ui-styles/src/main/res/values/stylable_message_bubble.xml index 32ed23c613..f7a877e3ed 100644 --- a/library/ui-styles/src/main/res/values/stylable_message_bubble.xml +++ b/library/ui-styles/src/main/res/values/stylable_message_bubble.xml @@ -3,7 +3,7 @@ - + diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/TimelineEventController.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/TimelineEventController.kt index dc5c76725b..460c332812 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/TimelineEventController.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/TimelineEventController.kt @@ -358,6 +358,9 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec // Should be build if not cached or if model should be refreshed if (modelCache[position] == null || modelCache[position]?.isCacheable(partialState) == false) { val prevEvent = currentSnapshot.prevOrNull(position) + val prevDisplayableEvent = currentSnapshot.subList(0, position).lastOrNull { + timelineEventVisibilityHelper.shouldShowEvent(it, partialState.highlightedEventId) + } val nextDisplayableEvent = currentSnapshot.subList(position + 1, currentSnapshot.size).firstOrNull { timelineEventVisibilityHelper.shouldShowEvent(it, partialState.highlightedEventId) } @@ -365,6 +368,7 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec val params = TimelineItemFactoryParams( event = event, prevEvent = prevEvent, + prevDisplayableEvent = prevDisplayableEvent, nextEvent = nextEvent, nextDisplayableEvent = nextDisplayableEvent, partialState = partialState, diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/TimelineItemFactoryParams.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/TimelineItemFactoryParams.kt index cdfedb2925..fad558344c 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/TimelineItemFactoryParams.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/TimelineItemFactoryParams.kt @@ -23,6 +23,7 @@ import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent data class TimelineItemFactoryParams( val event: TimelineEvent, val prevEvent: TimelineEvent? = null, + val prevDisplayableEvent: TimelineEvent? = null, val nextEvent: TimelineEvent? = null, val nextDisplayableEvent: TimelineEvent? = null, val partialState: TimelineEventController.PartialState = TimelineEventController.PartialState(), diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/MessageInformationDataFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/MessageInformationDataFactory.kt index b9ef9ca558..b9ea78e0db 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/MessageInformationDataFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/MessageInformationDataFactory.kt @@ -57,19 +57,21 @@ class MessageInformationDataFactory @Inject constructor(private val session: Ses fun create(params: TimelineItemFactoryParams): MessageInformationData { val event = params.event val nextDisplayableEvent = params.nextDisplayableEvent - val prevEvent = params.prevEvent + val prevDisplayableEvent = params.prevDisplayableEvent val eventId = event.eventId val isSentByMe = event.root.senderId == session.myUserId - val isFirstFromThisSender = nextDisplayableEvent?.root?.senderId != event.root.senderId - val isLastFromThisSender = prevEvent?.root?.senderId != event.root.senderId val roomSummary = params.partialState.roomSummary val date = event.root.localDateTime() val nextDate = nextDisplayableEvent?.root?.localDateTime() val addDaySeparator = date.toLocalDate() != nextDate?.toLocalDate() + val isNextMessageReceivedMoreThanOneHourAgo = nextDate?.isBefore(date.minusMinutes(60)) ?: false + val isFirstFromThisSender = nextDisplayableEvent?.root?.senderId != event.root.senderId || addDaySeparator + val isLastFromThisSender = prevDisplayableEvent?.root?.senderId != event.root.senderId || prevDisplayableEvent?.root?.localDateTime()?.toLocalDate() != date.toLocalDate() + val showInformation = (addDaySeparator || event.senderInfo.avatarUrl != nextDisplayableEvent?.senderInfo?.avatarUrl || @@ -77,7 +79,7 @@ class MessageInformationDataFactory @Inject constructor(private val session: Ses nextDisplayableEvent.root.getClearType() !in listOf(EventType.MESSAGE, EventType.STICKER, EventType.ENCRYPTED) || isNextMessageReceivedMoreThanOneHourAgo || isTileTypeMessage(nextDisplayableEvent) || - nextDisplayableEvent.isEdition() ) && !isSentByMe + nextDisplayableEvent.isEdition()) && !isSentByMe val time = dateFormatter.format(event.root.originServerTs, DateFormatKind.MESSAGE_SIMPLE) val e2eDecoration = getE2EDecoration(roomSummary, event) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageImageVideoItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageImageVideoItem.kt index 8e42297bc1..e865354747 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageImageVideoItem.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageImageVideoItem.kt @@ -29,7 +29,6 @@ import im.vector.app.core.epoxy.onClick import im.vector.app.core.files.LocalFilesHelper import im.vector.app.core.glide.GlideApp import im.vector.app.features.home.room.detail.timeline.helper.ContentUploadStateTrackerBinder -import im.vector.app.features.home.room.detail.timeline.view.MessageBubbleView import im.vector.app.features.home.room.detail.timeline.view.MessageViewConfiguration import im.vector.app.features.media.ImageContentRenderer @@ -72,7 +71,7 @@ abstract class MessageImageVideoItem : AbsMessageItem Date: Tue, 11 Jan 2022 15:38:58 +0100 Subject: [PATCH 05/36] Bubbles: start adding "theming" mechanism --- .../timeline/factory/EncryptedItemFactory.kt | 1 + .../timeline/factory/MessageItemFactory.kt | 19 +--- .../timeline/helper/AvatarSizeProvider.kt | 26 ++++-- .../helper/MessageInformationDataFactory.kt | 24 ++--- .../detail/timeline/item/AbsMessageItem.kt | 6 +- .../timeline/item/MessageInformationData.kt | 5 +- .../timeline/style/TimelineLayoutSettings.kt | 22 +++++ .../style/TimelineLayoutSettingsProvider.kt | 26 ++++++ .../timeline/style/TimelineMessageLayout.kt | 44 ++++++++++ .../style/TimelineMessageLayoutFactory.kt | 88 +++++++++++++++++++ 10 files changed, 212 insertions(+), 49 deletions(-) create mode 100644 vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineLayoutSettings.kt create mode 100644 vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineLayoutSettingsProvider.kt create mode 100644 vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineMessageLayout.kt create mode 100644 vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineMessageLayoutFactory.kt diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/EncryptedItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/EncryptedItemFactory.kt index b8d7d96ecf..5112604dd3 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/EncryptedItemFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/EncryptedItemFactory.kt @@ -108,6 +108,7 @@ class EncryptedItemFactory @Inject constructor(private val messageInformationDat val informationData = messageInformationDataFactory.create(params) val attributes = attributesFactory.create(event.root.content.toModel(), informationData, params.callback) return MessageTextItem_() + .layout(informationData.messageLayout.layoutRes) .leftGuideline(avatarSizeProvider.leftGuideline) .highlighted(params.isHighlighted) .attributes(attributes) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt index c42d50e924..33d77fe16c 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt @@ -154,7 +154,7 @@ class MessageItemFactory @Inject constructor( // val all = event.root.toContent() // val ev = all.toModel() - return when (messageContent) { + val messageItem = when (messageContent) { is MessageEmoteContent -> buildEmoteMessageItem(messageContent, informationData, highlight, callback, attributes) is MessageTextContent -> buildItemForTextContent(messageContent, informationData, highlight, callback, attributes) is MessageImageInfoContent -> buildImageMessageItem(messageContent, informationData, highlight, callback, attributes) @@ -172,6 +172,9 @@ class MessageItemFactory @Inject constructor( is MessagePollContent -> buildPollContent(messageContent, informationData, highlight, callback, attributes) else -> buildNotHandledMessageItem(messageContent, informationData, highlight, callback, attributes) } + return messageItem?.apply { + layout(informationData.messageLayout.layoutRes) + } } private fun buildPollContent(pollContent: MessagePollContent, @@ -389,13 +392,6 @@ class MessageItemFactory @Inject constructor( allowNonMxcUrls = informationData.sendState.isSending() ) return MessageImageVideoItem_() - .layout( - if (informationData.sentByMe) { - R.layout.item_timeline_event_bubble_outgoing_base - } else { - R.layout.item_timeline_event_bubble_incoming_base - } - ) .attributes(attributes) .leftGuideline(avatarSizeProvider.leftGuideline) .imageContentRenderer(imageContentRenderer) @@ -517,13 +513,6 @@ class MessageItemFactory @Inject constructor( linkifiedBody }.toEpoxyCharSequence() ) - .layout( - if (informationData.sentByMe) { - R.layout.item_timeline_event_bubble_outgoing_base - } else { - R.layout.item_timeline_event_bubble_incoming_base - } - ) .useBigFont(linkifiedBody.length <= MAX_NUMBER_OF_EMOJI_FOR_BIG_FONT * 2 && containsOnlyEmojis(linkifiedBody.toString())) .bindingOptions(bindingOptions) .searchForPills(isFormatted) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/AvatarSizeProvider.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/AvatarSizeProvider.kt index 00b02c2cf0..a34c216fad 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/AvatarSizeProvider.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/AvatarSizeProvider.kt @@ -17,14 +17,22 @@ package im.vector.app.features.home.room.detail.timeline.helper import im.vector.app.core.utils.DimensionConverter +import im.vector.app.features.home.room.detail.timeline.style.TimelineLayoutSettings +import im.vector.app.features.home.room.detail.timeline.style.TimelineLayoutSettingsProvider import javax.inject.Inject -class AvatarSizeProvider @Inject constructor(private val dimensionConverter: DimensionConverter) { +class AvatarSizeProvider @Inject constructor(private val dimensionConverter: DimensionConverter, + private val layoutSettingsProvider: TimelineLayoutSettingsProvider) { - private val avatarStyle = AvatarStyle.X_SMALL + private val avatarStyle by lazy { + when (layoutSettingsProvider.getLayoutSettings()) { + TimelineLayoutSettings.MODERN -> AvatarStyle.SMALL + TimelineLayoutSettings.BUBBLE -> AvatarStyle.BUBBLE + } + } val leftGuideline: Int by lazy { - dimensionConverter.dpToPx(avatarStyle.avatarSizeDP + 4) + dimensionConverter.dpToPx(avatarStyle.avatarSizeDP + avatarStyle.marginDP) } val avatarSize: Int by lazy { @@ -33,12 +41,12 @@ class AvatarSizeProvider @Inject constructor(private val dimensionConverter: Dim companion object { - enum class AvatarStyle(val avatarSizeDP: Int) { - BIG(50), - MEDIUM(40), - SMALL(30), - X_SMALL(28), - NONE(0) + enum class AvatarStyle(val avatarSizeDP: Int, val marginDP: Int) { + BIG(50, 8), + MEDIUM(40, 8), + SMALL(30, 8), + BUBBLE(28, 4), + NONE(0, 8) } } } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/MessageInformationDataFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/MessageInformationDataFactory.kt index b9ea78e0db..2edab8af74 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/MessageInformationDataFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/MessageInformationDataFactory.kt @@ -27,7 +27,7 @@ import im.vector.app.features.home.room.detail.timeline.item.PollVoteSummaryData import im.vector.app.features.home.room.detail.timeline.item.ReactionInfoData import im.vector.app.features.home.room.detail.timeline.item.ReferencesInfoData import im.vector.app.features.home.room.detail.timeline.item.SendStateDecoration -import im.vector.app.features.settings.VectorPreferences +import im.vector.app.features.home.room.detail.timeline.style.TimelineMessageLayoutFactory import org.matrix.android.sdk.api.crypto.VerificationState import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.api.session.Session @@ -41,7 +41,6 @@ import org.matrix.android.sdk.api.session.room.send.SendState import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent import org.matrix.android.sdk.api.session.room.timeline.getLastMessageContent import org.matrix.android.sdk.api.session.room.timeline.hasBeenEdited -import org.matrix.android.sdk.api.session.room.timeline.isEdition import org.matrix.android.sdk.internal.crypto.model.event.EncryptedEventContent import javax.inject.Inject @@ -51,8 +50,7 @@ import javax.inject.Inject */ class MessageInformationDataFactory @Inject constructor(private val session: Session, private val dateFormatter: VectorDateFormatter, - private val visibilityHelper: TimelineEventVisibilityHelper, - private val vectorPreferences: VectorPreferences) { + private val messageLayoutFactory: TimelineMessageLayoutFactory) { fun create(params: TimelineItemFactoryParams): MessageInformationData { val event = params.event @@ -66,21 +64,9 @@ class MessageInformationDataFactory @Inject constructor(private val session: Ses val nextDate = nextDisplayableEvent?.root?.localDateTime() val addDaySeparator = date.toLocalDate() != nextDate?.toLocalDate() - val isNextMessageReceivedMoreThanOneHourAgo = nextDate?.isBefore(date.minusMinutes(60)) - ?: false - val isFirstFromThisSender = nextDisplayableEvent?.root?.senderId != event.root.senderId || addDaySeparator val isLastFromThisSender = prevDisplayableEvent?.root?.senderId != event.root.senderId || prevDisplayableEvent?.root?.localDateTime()?.toLocalDate() != date.toLocalDate() - val showInformation = - (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()) && !isSentByMe - val time = dateFormatter.format(event.root.originServerTs, DateFormatKind.MESSAGE_SIMPLE) val e2eDecoration = getE2EDecoration(roomSummary, event) @@ -95,6 +81,8 @@ class MessageInformationDataFactory @Inject constructor(private val session: Ses SendStateDecoration.NONE } + val messageLayout = messageLayoutFactory.create(params) + return MessageInformationData( eventId = eventId, senderId = event.root.senderId ?: "", @@ -103,9 +91,7 @@ class MessageInformationDataFactory @Inject constructor(private val session: Ses ageLocalTS = event.root.ageLocalTs, avatarUrl = event.senderInfo.avatarUrl, memberName = event.senderInfo.disambiguatedDisplayName, - showAvatar = showInformation, - showDisplayName = showInformation, - showTimestamp = true, + messageLayout = messageLayout, orderedReactionList = event.annotations?.reactionsSummary // ?.filter { isSingleEmoji(it.key) } ?.map { diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/AbsMessageItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/AbsMessageItem.kt index a964af6f73..9f3b2bddf2 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/AbsMessageItem.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/AbsMessageItem.kt @@ -62,7 +62,7 @@ abstract class AbsMessageItem : AbsBaseMessageItem override fun bind(holder: H) { super.bind(holder) - if (attributes.informationData.showAvatar) { + if (attributes.informationData.messageLayout.showAvatar) { holder.avatarImageView.layoutParams = holder.avatarImageView.layoutParams?.apply { height = attributes.avatarSize width = attributes.avatarSize @@ -76,7 +76,7 @@ abstract class AbsMessageItem : AbsBaseMessageItem holder.avatarImageView.setOnLongClickListener(null) holder.avatarImageView.isVisible = false } - if (attributes.informationData.showDisplayName) { + if (attributes.informationData.messageLayout.showDisplayName) { holder.memberNameView.isVisible = true holder.memberNameView.text = attributes.informationData.memberName holder.memberNameView.setTextColor(attributes.getMemberNameColor()) @@ -87,7 +87,7 @@ abstract class AbsMessageItem : AbsBaseMessageItem holder.memberNameView.setOnLongClickListener(null) holder.memberNameView.isVisible = false } - if (attributes.informationData.showTimestamp) { + if (attributes.informationData.messageLayout.showTimestamp) { holder.timeView.isVisible = true holder.timeView.text = attributes.informationData.time } else { diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageInformationData.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageInformationData.kt index 76fc9a5eff..629d20e898 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageInformationData.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageInformationData.kt @@ -17,6 +17,7 @@ package im.vector.app.features.home.room.detail.timeline.item import android.os.Parcelable +import im.vector.app.features.home.room.detail.timeline.style.TimelineMessageLayout import kotlinx.parcelize.Parcelize import org.matrix.android.sdk.api.crypto.VerificationState import org.matrix.android.sdk.api.session.room.send.SendState @@ -31,9 +32,7 @@ data class MessageInformationData( val ageLocalTS: Long?, val avatarUrl: String?, val memberName: CharSequence? = null, - val showAvatar: Boolean, - val showDisplayName: Boolean, - val showTimestamp: Boolean, + val messageLayout: TimelineMessageLayout, /*List of reactions (emoji,count,isSelected)*/ val orderedReactionList: List? = null, val pollResponseAggregatedSummary: PollResponseData? = null, diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineLayoutSettings.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineLayoutSettings.kt new file mode 100644 index 0000000000..873ef8c907 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineLayoutSettings.kt @@ -0,0 +1,22 @@ +/* + * 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.style + +enum class TimelineLayoutSettings { + MODERN, + BUBBLE +} diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineLayoutSettingsProvider.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineLayoutSettingsProvider.kt new file mode 100644 index 0000000000..a10c95befe --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineLayoutSettingsProvider.kt @@ -0,0 +1,26 @@ +/* + * 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.style + +import javax.inject.Inject + +class TimelineLayoutSettingsProvider @Inject constructor() { + + fun getLayoutSettings(): TimelineLayoutSettings { + return TimelineLayoutSettings.BUBBLE + } +} diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineMessageLayout.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineMessageLayout.kt new file mode 100644 index 0000000000..48dba0a58a --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineMessageLayout.kt @@ -0,0 +1,44 @@ +/* + * 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.style + +import android.os.Parcelable +import im.vector.app.R +import kotlinx.parcelize.Parcelize + +sealed interface TimelineMessageLayout : Parcelable { + val layoutRes: Int + val showAvatar: Boolean + val showDisplayName: Boolean + val showTimestamp: Boolean + + @Parcelize + data class Modern(override val showAvatar: Boolean, + override val showDisplayName: Boolean, + override val showTimestamp: Boolean, + override val layoutRes: Int = R.layout.item_timeline_event_base) : TimelineMessageLayout + + @Parcelize + data class Bubble(override val showAvatar: Boolean, + override val showDisplayName: Boolean, + override val showTimestamp: Boolean = true, + val isIncoming: Boolean, + val isFirstFromThisSender: Boolean, + val isLastFromThisSender: Boolean, + override val layoutRes: Int = if (isIncoming) R.layout.item_timeline_event_bubble_incoming_base else R.layout.item_timeline_event_bubble_outgoing_base, + ) : TimelineMessageLayout +} diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineMessageLayoutFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineMessageLayoutFactory.kt new file mode 100644 index 0000000000..18df8c133b --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineMessageLayoutFactory.kt @@ -0,0 +1,88 @@ +/* + * 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.style + +import im.vector.app.core.extensions.localDateTime +import im.vector.app.features.home.room.detail.timeline.factory.TimelineItemFactoryParams +import im.vector.app.features.settings.VectorPreferences +import org.matrix.android.sdk.api.session.Session +import org.matrix.android.sdk.api.session.events.model.EventType +import org.matrix.android.sdk.api.session.room.model.message.MessageVerificationRequestContent +import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent +import org.matrix.android.sdk.api.session.room.timeline.getLastMessageContent +import org.matrix.android.sdk.api.session.room.timeline.isEdition +import javax.inject.Inject + +class TimelineMessageLayoutFactory @Inject constructor(private val session: Session, + private val layoutSettingsProvider: TimelineLayoutSettingsProvider, + private val vectorPreferences: VectorPreferences) { + + fun create(params: TimelineItemFactoryParams): TimelineMessageLayout { + + val event = params.event + val nextDisplayableEvent = params.nextDisplayableEvent + val prevDisplayableEvent = params.prevDisplayableEvent + val isSentByMe = event.root.senderId == session.myUserId + + val date = event.root.localDateTime() + val nextDate = nextDisplayableEvent?.root?.localDateTime() + val addDaySeparator = date.toLocalDate() != nextDate?.toLocalDate() + + val isNextMessageReceivedMoreThanOneHourAgo = nextDate?.isBefore(date.minusMinutes(60)) + ?: false + + val showInformation = + (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()) && !isSentByMe + + val messageLayout = when (layoutSettingsProvider.getLayoutSettings()) { + TimelineLayoutSettings.MODERN -> TimelineMessageLayout.Modern(showInformation, showInformation, showInformation || vectorPreferences.alwaysShowTimeStamps()) + TimelineLayoutSettings.BUBBLE -> { + val isFirstFromThisSender = nextDisplayableEvent?.root?.senderId != event.root.senderId || addDaySeparator + val isLastFromThisSender = prevDisplayableEvent?.root?.senderId != event.root.senderId || prevDisplayableEvent?.root?.localDateTime()?.toLocalDate() != date.toLocalDate() + TimelineMessageLayout.Bubble( + showAvatar = showInformation, + showDisplayName = showInformation, + isIncoming = !isSentByMe, + isFirstFromThisSender = isFirstFromThisSender, + isLastFromThisSender = isLastFromThisSender + ) + } + } + return messageLayout + } + + /** + * Tiles type message never show the sender information (like verification request), so we should repeat it for next message + * even if same sender + */ + private fun isTileTypeMessage(event: TimelineEvent?): Boolean { + return when (event?.root?.getClearType()) { + EventType.KEY_VERIFICATION_DONE, + EventType.KEY_VERIFICATION_CANCEL -> true + EventType.MESSAGE -> { + event.getLastMessageContent() is MessageVerificationRequestContent + } + else -> false + } + } +} From f7df0b891efbf53d55f46f574d0da9a4de774a08 Mon Sep 17 00:00:00 2001 From: ganfra Date: Wed, 12 Jan 2022 18:45:40 +0100 Subject: [PATCH 06/36] Bubbles: fix recycling issue --- .../detail/timeline/factory/MessageItemFactory.kt | 4 +++- .../room/detail/timeline/item/BaseEventItem.kt | 15 +++++++++++++++ .../detail/timeline/item/CallTileTimelineItem.kt | 2 +- .../home/room/detail/timeline/item/DefaultItem.kt | 2 +- .../timeline/item/MergedMembershipEventsItem.kt | 2 +- .../timeline/item/MergedRoomCreationItem.kt | 2 +- .../detail/timeline/item/MessageBlockCodeItem.kt | 2 +- .../room/detail/timeline/item/MessageFileItem.kt | 2 +- .../detail/timeline/item/MessageImageVideoItem.kt | 2 +- .../room/detail/timeline/item/MessageTextItem.kt | 2 +- .../room/detail/timeline/item/MessageVoiceItem.kt | 2 +- .../home/room/detail/timeline/item/NoticeItem.kt | 2 +- .../home/room/detail/timeline/item/PollItem.kt | 2 ++ .../detail/timeline/item/RedactedMessageItem.kt | 2 +- .../timeline/item/StatusTileTimelineItem.kt | 2 +- .../timeline/item/VerificationRequestItem.kt | 2 +- .../timeline/item/WidgetTileTimelineItem.kt | 2 +- 17 files changed, 34 insertions(+), 15 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt index 33d77fe16c..80343fd909 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt @@ -172,7 +172,9 @@ class MessageItemFactory @Inject constructor( is MessagePollContent -> buildPollContent(messageContent, informationData, highlight, callback, attributes) else -> buildNotHandledMessageItem(messageContent, informationData, highlight, callback, attributes) } - return messageItem?.apply { + return messageItem?.takeIf { + it.layout == R.layout.item_timeline_event_base + }?.apply { layout(informationData.messageLayout.layoutRes) } } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/BaseEventItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/BaseEventItem.kt index 5dfbf5d8f6..3df6393c05 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/BaseEventItem.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/BaseEventItem.kt @@ -27,6 +27,7 @@ import im.vector.app.core.epoxy.VectorEpoxyHolder import im.vector.app.core.epoxy.VectorEpoxyModel import im.vector.app.core.platform.CheckableView import im.vector.app.core.utils.DimensionConverter +import timber.log.Timber /** * Children must override getViewType() @@ -43,6 +44,20 @@ abstract class BaseEventItem : VectorEpoxyModel @EpoxyAttribute(EpoxyAttribute.Option.DoNotHash) lateinit var dimensionConverter: DimensionConverter + final override fun getViewType(): Int { + // This makes sure we have a unique integer for the combination of layout and ViewStubId. + return pairingFunction(layout, getViewStubId()).also { + Timber.v("GetViewType: for ${javaClass.canonicalName} $it with layout:$layout and stubId:${getViewStubId()}") + } + } + + abstract fun getViewStubId(): Int + + // Szudzik function + private fun pairingFunction(a: Int, b: Int): Int { + return if (a >= b) a * a + a + b else a + b * b + } + @CallSuper override fun bind(holder: H) { super.bind(holder) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/CallTileTimelineItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/CallTileTimelineItem.kt index 5f8ac822da..218d648318 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/CallTileTimelineItem.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/CallTileTimelineItem.kt @@ -50,7 +50,7 @@ abstract class CallTileTimelineItem : AbsBaseMessageItem() { return listOf(attributes.informationData.eventId) } - override fun getViewType() = STUB_ID + override fun getViewStubId() = STUB_ID class Holder : BaseHolder(STUB_ID) { val avatarImageView by bind(R.id.itemDefaultAvatarView) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MergedMembershipEventsItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MergedMembershipEventsItem.kt index a52ddf8336..e19dc33fff 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MergedMembershipEventsItem.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MergedMembershipEventsItem.kt @@ -29,7 +29,7 @@ import im.vector.app.features.home.AvatarRenderer @EpoxyModelClass(layout = R.layout.item_timeline_event_base_noinfo) abstract class MergedMembershipEventsItem : BasedMergedItem() { - override fun getViewType() = STUB_ID + override fun getViewStubId() = STUB_ID @EpoxyAttribute override lateinit var attributes: Attributes diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MergedRoomCreationItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MergedRoomCreationItem.kt index 1e8e96426f..9f631f7a0e 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MergedRoomCreationItem.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MergedRoomCreationItem.kt @@ -51,7 +51,7 @@ abstract class MergedRoomCreationItem : BasedMergedItem(R.id.codeBlockTextView) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageFileItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageFileItem.kt index b15f909b79..bd35532eae 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageFileItem.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageFileItem.kt @@ -95,7 +95,7 @@ abstract class MessageFileItem : AbsMessageItem() { contentDownloadStateTrackerBinder.unbind(mxcUrl) } - override fun getViewType() = STUB_ID + override fun getViewStubId() = STUB_ID class Holder : AbsMessageItem.Holder(STUB_ID) { val progressLayout by bind(R.id.messageFileUploadProgressLayout) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageImageVideoItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageImageVideoItem.kt index e865354747..e20ad48b17 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageImageVideoItem.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageImageVideoItem.kt @@ -83,7 +83,7 @@ abstract class MessageImageVideoItem : AbsMessageItem(R.id.messageMediaUploadProgressLayout) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageTextItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageTextItem.kt index 6a438d3a06..e83611d6d8 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageTextItem.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageTextItem.kt @@ -115,7 +115,7 @@ abstract class MessageTextItem : AbsMessageItem() { previewUrlRetriever?.removeListener(attributes.informationData.eventId, previewUrlViewUpdater) } - override fun getViewType() = STUB_ID + layout + override fun getViewStubId() = STUB_ID class Holder : AbsMessageItem.Holder(STUB_ID) { val messageView by bind(R.id.messageTextView) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceItem.kt index f006c2aa35..b6fa684eca 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceItem.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceItem.kt @@ -120,7 +120,7 @@ abstract class MessageVoiceItem : AbsMessageItem() { voiceMessagePlaybackTracker.unTrack(attributes.informationData.eventId) } - override fun getViewType() = STUB_ID + override fun getViewStubId() = STUB_ID class Holder : AbsMessageItem.Holder(STUB_ID) { val voiceLayout by bind(R.id.voiceLayout) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/NoticeItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/NoticeItem.kt index 2851668df5..e998b7e58f 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/NoticeItem.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/NoticeItem.kt @@ -64,7 +64,7 @@ abstract class NoticeItem : BaseEventItem() { return listOf(attributes.informationData.eventId) } - override fun getViewType() = STUB_ID + override fun getViewStubId() = STUB_ID class Holder : BaseHolder(STUB_ID) { val avatarImageView by bind(R.id.itemNoticeAvatarView) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/PollItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/PollItem.kt index 1308fa49c8..784eec87fe 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/PollItem.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/PollItem.kt @@ -46,6 +46,8 @@ abstract class PollItem : AbsMessageItem() { @EpoxyAttribute lateinit var optionViewStates: List + override fun getViewStubId() = STUB_ID + override fun bind(holder: Holder) { super.bind(holder) val relatedEventId = eventId ?: return diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/RedactedMessageItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/RedactedMessageItem.kt index 282550daec..204bab2254 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/RedactedMessageItem.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/RedactedMessageItem.kt @@ -22,7 +22,7 @@ import im.vector.app.R @EpoxyModelClass(layout = R.layout.item_timeline_event_base) abstract class RedactedMessageItem : AbsMessageItem() { - override fun getViewType() = STUB_ID + override fun getViewStubId() = STUB_ID override fun shouldShowReactionAtBottom() = false diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/StatusTileTimelineItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/StatusTileTimelineItem.kt index 6531efb82d..66bf41ddac 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/StatusTileTimelineItem.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/StatusTileTimelineItem.kt @@ -40,7 +40,7 @@ abstract class StatusTileTimelineItem : AbsBaseMessageItem Date: Wed, 12 Jan 2022 19:01:13 +0100 Subject: [PATCH 07/36] Bubbles: add quick settings (temporary) --- .../timeline/style/TimelineLayoutSettingsProvider.kt | 9 +++++++-- .../vector/app/features/settings/VectorPreferences.kt | 10 ++++++++++ .../src/main/res/xml/vector_settings_preferences.xml | 5 +++++ 3 files changed, 22 insertions(+), 2 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineLayoutSettingsProvider.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineLayoutSettingsProvider.kt index a10c95befe..9e351a706d 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineLayoutSettingsProvider.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineLayoutSettingsProvider.kt @@ -16,11 +16,16 @@ package im.vector.app.features.home.room.detail.timeline.style +import im.vector.app.features.settings.VectorPreferences import javax.inject.Inject -class TimelineLayoutSettingsProvider @Inject constructor() { +class TimelineLayoutSettingsProvider @Inject constructor(private val vectorPreferences: VectorPreferences) { fun getLayoutSettings(): TimelineLayoutSettings { - return TimelineLayoutSettings.BUBBLE + return if (vectorPreferences.useMessageBubblesLayout()) { + TimelineLayoutSettings.BUBBLE + } else { + TimelineLayoutSettings.MODERN + } } } diff --git a/vector/src/main/java/im/vector/app/features/settings/VectorPreferences.kt b/vector/src/main/java/im/vector/app/features/settings/VectorPreferences.kt index 3436c20ce3..9c472a387c 100755 --- a/vector/src/main/java/im/vector/app/features/settings/VectorPreferences.kt +++ b/vector/src/main/java/im/vector/app/features/settings/VectorPreferences.kt @@ -83,6 +83,7 @@ class VectorPreferences @Inject constructor(private val context: Context) { // interface const val SETTINGS_INTERFACE_LANGUAGE_PREFERENCE_KEY = "SETTINGS_INTERFACE_LANGUAGE_PREFERENCE_KEY" const val SETTINGS_INTERFACE_TEXT_SIZE_KEY = "SETTINGS_INTERFACE_TEXT_SIZE_KEY" + const val SETTINGS_INTERFACE_BUBBLE_KEY = "SETTINGS_INTERFACE_BUBBLE_KEY" const val SETTINGS_SHOW_URL_PREVIEW_KEY = "SETTINGS_SHOW_URL_PREVIEW_KEY" private const val SETTINGS_SEND_TYPING_NOTIF_KEY = "SETTINGS_SEND_TYPING_NOTIF_KEY" private const val SETTINGS_ENABLE_MARKDOWN_KEY = "SETTINGS_ENABLE_MARKDOWN_KEY" @@ -852,6 +853,15 @@ class VectorPreferences @Inject constructor(private val context: Context) { return defaultPrefs.getBoolean(SETTINGS_SHOW_EMOJI_KEYBOARD, true) } + /** + * Tells if the emoji keyboard button should be visible or not. + * + * @return true to show emoji keyboard button. + */ + fun useMessageBubblesLayout(): Boolean { + return defaultPrefs.getBoolean(SETTINGS_INTERFACE_BUBBLE_KEY, false) + } + /** * Tells if the rage shake is used. * diff --git a/vector/src/main/res/xml/vector_settings_preferences.xml b/vector/src/main/res/xml/vector_settings_preferences.xml index 14c7dc7b80..ac8a48fb2e 100644 --- a/vector/src/main/res/xml/vector_settings_preferences.xml +++ b/vector/src/main/res/xml/vector_settings_preferences.xml @@ -22,6 +22,11 @@ android:title="@string/settings_theme" app:iconSpaceReserved="false" /> + + Date: Wed, 12 Jan 2022 19:22:14 +0100 Subject: [PATCH 08/36] Bubbles: fix avatar/name visibility in modern layout --- .../style/TimelineMessageLayoutFactory.kt | 27 +++++++++++-------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineMessageLayoutFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineMessageLayoutFactory.kt index 18df8c133b..bd37c2d66a 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineMessageLayoutFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineMessageLayoutFactory.kt @@ -45,23 +45,28 @@ class TimelineMessageLayoutFactory @Inject constructor(private val session: Sess val isNextMessageReceivedMoreThanOneHourAgo = nextDate?.isBefore(date.minusMinutes(60)) ?: false - val showInformation = - (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()) && !isSentByMe + val showInformation = 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() val messageLayout = when (layoutSettingsProvider.getLayoutSettings()) { - TimelineLayoutSettings.MODERN -> TimelineMessageLayout.Modern(showInformation, showInformation, showInformation || vectorPreferences.alwaysShowTimeStamps()) + TimelineLayoutSettings.MODERN -> { + TimelineMessageLayout.Modern( + showAvatar = showInformation, + showDisplayName = showInformation, + showTimestamp = showInformation || vectorPreferences.alwaysShowTimeStamps() + ) + } TimelineLayoutSettings.BUBBLE -> { val isFirstFromThisSender = nextDisplayableEvent?.root?.senderId != event.root.senderId || addDaySeparator val isLastFromThisSender = prevDisplayableEvent?.root?.senderId != event.root.senderId || prevDisplayableEvent?.root?.localDateTime()?.toLocalDate() != date.toLocalDate() TimelineMessageLayout.Bubble( - showAvatar = showInformation, - showDisplayName = showInformation, + showAvatar = showInformation && !isSentByMe, + showDisplayName = showInformation && !isSentByMe, isIncoming = !isSentByMe, isFirstFromThisSender = isFirstFromThisSender, isLastFromThisSender = isLastFromThisSender From b9cc7959967aa2098b99eb9977b97b13aa308d1f Mon Sep 17 00:00:00 2001 From: ganfra Date: Thu, 13 Jan 2022 12:33:36 +0100 Subject: [PATCH 09/36] Bubbles : fix background colors --- library/ui-styles/src/main/res/values/colors.xml | 3 --- .../room/detail/timeline/view/MessageBubbleView.kt | 12 ++++++++---- .../res/drawable/bg_timeline_incoming_message.xml | 4 ---- .../res/drawable/bg_timeline_outgoing_message.xml | 4 ---- vector/src/main/res/layout/view_message_bubble.xml | 1 - 5 files changed, 8 insertions(+), 16 deletions(-) delete mode 100644 vector/src/main/res/drawable/bg_timeline_incoming_message.xml delete mode 100644 vector/src/main/res/drawable/bg_timeline_outgoing_message.xml diff --git a/library/ui-styles/src/main/res/values/colors.xml b/library/ui-styles/src/main/res/values/colors.xml index e3ec542c89..ca6f6d3142 100644 --- a/library/ui-styles/src/main/res/values/colors.xml +++ b/library/ui-styles/src/main/res/values/colors.xml @@ -13,9 +13,6 @@ #14368BD6 @color/palette_azure - #0F0DBD8B - @color/element_system_light - @color/palette_azure @color/palette_melon diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/view/MessageBubbleView.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/view/MessageBubbleView.kt index 5bb732bdde..46e3edca44 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/view/MessageBubbleView.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/view/MessageBubbleView.kt @@ -17,6 +17,7 @@ package im.vector.app.features.home.room.detail.timeline.view import android.content.Context +import android.content.res.ColorStateList import android.graphics.drawable.Drawable import android.util.AttributeSet import android.view.View @@ -26,12 +27,14 @@ import androidx.constraintlayout.widget.ConstraintLayout import androidx.constraintlayout.widget.ConstraintSet import androidx.core.content.ContextCompat import androidx.core.content.withStyledAttributes +import androidx.core.graphics.ColorUtils import androidx.core.view.updateLayoutParams import com.google.android.material.shape.CornerFamily import com.google.android.material.shape.MaterialShapeDrawable import com.google.android.material.shape.ShapeAppearanceModel import im.vector.app.R import im.vector.app.core.utils.DimensionConverter +import im.vector.app.features.themes.ThemeUtils class MessageBubbleView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0) @@ -116,7 +119,6 @@ class MessageBubbleView @JvmOverloads constructor(context: Context, attrs: Attri } applyTo(bubbleView) } - } private fun createBackgroundDrawable(): Drawable { @@ -133,14 +135,16 @@ class MessageBubbleView @JvmOverloads constructor(context: Context, attrs: Attri val shapeAppearanceModelBuilder = ShapeAppearanceModel().toBuilder() val backgroundColor: Int if (isIncoming) { - backgroundColor = R.color.bubble_background_incoming + backgroundColor = ThemeUtils.getColor(context, R.attr.vctr_system) shapeAppearanceModelBuilder .setTopRightCorner(CornerFamily.ROUNDED, cornerRadius) .setBottomRightCorner(CornerFamily.ROUNDED, cornerRadius) .setTopLeftCorner(topCornerFamily, topRadius) .setBottomLeftCorner(bottomCornerFamily, bottomRadius) } else { - backgroundColor = R.color.bubble_background_outgoing + val resolvedColor = ContextCompat.getColor(context, R.color.palette_element_green) + val alpha = if (ThemeUtils.isLightTheme(context)) 0x0E else 0x26 + backgroundColor = ColorUtils.setAlphaComponent(resolvedColor, alpha) shapeAppearanceModelBuilder .setTopLeftCorner(CornerFamily.ROUNDED, cornerRadius) .setBottomLeftCorner(CornerFamily.ROUNDED, cornerRadius) @@ -149,7 +153,7 @@ class MessageBubbleView @JvmOverloads constructor(context: Context, attrs: Attri } val shapeAppearanceModel = shapeAppearanceModelBuilder.build() return MaterialShapeDrawable(shapeAppearanceModel).apply { - fillColor = ContextCompat.getColorStateList(context, backgroundColor) + fillColor = ColorStateList.valueOf(backgroundColor) } } } diff --git a/vector/src/main/res/drawable/bg_timeline_incoming_message.xml b/vector/src/main/res/drawable/bg_timeline_incoming_message.xml deleted file mode 100644 index 2cbca33702..0000000000 --- a/vector/src/main/res/drawable/bg_timeline_incoming_message.xml +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/vector/src/main/res/drawable/bg_timeline_outgoing_message.xml b/vector/src/main/res/drawable/bg_timeline_outgoing_message.xml deleted file mode 100644 index 0f75705a77..0000000000 --- a/vector/src/main/res/drawable/bg_timeline_outgoing_message.xml +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/vector/src/main/res/layout/view_message_bubble.xml b/vector/src/main/res/layout/view_message_bubble.xml index 8e9a95222a..8570a3dee2 100644 --- a/vector/src/main/res/layout/view_message_bubble.xml +++ b/vector/src/main/res/layout/view_message_bubble.xml @@ -91,7 +91,6 @@ android:layout_marginStart="0dp" android:layout_marginEnd="0dp" android:addStatesFromChildren="true" - android:background="@drawable/bg_timeline_incoming_message" android:paddingStart="4dp" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent"> From baee076e41c628ff3897b80097212f8b8fd40ae9 Mon Sep 17 00:00:00 2001 From: ganfra Date: Thu, 13 Jan 2022 12:33:58 +0100 Subject: [PATCH 10/36] Bubbles: fix types using wrong layout --- .../timeline/factory/MessageItemFactory.kt | 5 +- .../timeline/style/TimelineMessageLayout.kt | 9 +-- .../style/TimelineMessageLayoutFactory.kt | 56 ++++++++++++++----- 3 files changed, 49 insertions(+), 21 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt index 80343fd909..a80b948428 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt @@ -172,9 +172,7 @@ class MessageItemFactory @Inject constructor( is MessagePollContent -> buildPollContent(messageContent, informationData, highlight, callback, attributes) else -> buildNotHandledMessageItem(messageContent, informationData, highlight, callback, attributes) } - return messageItem?.takeIf { - it.layout == R.layout.item_timeline_event_base - }?.apply { + return messageItem?.apply { layout(informationData.messageLayout.layoutRes) } } @@ -650,6 +648,7 @@ class MessageItemFactory @Inject constructor( private fun buildRedactedItem(attributes: AbsMessageItem.Attributes, highlight: Boolean): RedactedMessageItem? { return RedactedMessageItem_() + .layout(attributes.informationData.messageLayout.layoutRes) .leftGuideline(avatarSizeProvider.leftGuideline) .attributes(attributes) .highlighted(highlight) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineMessageLayout.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineMessageLayout.kt index 48dba0a58a..50f4e95cee 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineMessageLayout.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineMessageLayout.kt @@ -27,10 +27,11 @@ sealed interface TimelineMessageLayout : Parcelable { val showTimestamp: Boolean @Parcelize - data class Modern(override val showAvatar: Boolean, - override val showDisplayName: Boolean, - override val showTimestamp: Boolean, - override val layoutRes: Int = R.layout.item_timeline_event_base) : TimelineMessageLayout + data class Default(override val showAvatar: Boolean, + override val showDisplayName: Boolean, + override val showTimestamp: Boolean, + // Keep defaultLayout generated on epoxy items + override val layoutRes: Int = 0) : TimelineMessageLayout @Parcelize data class Bubble(override val showAvatar: Boolean, diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineMessageLayoutFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineMessageLayoutFactory.kt index bd37c2d66a..9b2877f0f8 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineMessageLayoutFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineMessageLayoutFactory.kt @@ -21,6 +21,7 @@ import im.vector.app.features.home.room.detail.timeline.factory.TimelineItemFact import im.vector.app.features.settings.VectorPreferences import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.events.model.EventType +import org.matrix.android.sdk.api.session.room.model.message.MessageType import org.matrix.android.sdk.api.session.room.model.message.MessageVerificationRequestContent import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent import org.matrix.android.sdk.api.session.room.timeline.getLastMessageContent @@ -31,6 +32,18 @@ class TimelineMessageLayoutFactory @Inject constructor(private val session: Sess private val layoutSettingsProvider: TimelineLayoutSettingsProvider, private val vectorPreferences: VectorPreferences) { + companion object { + private val EVENT_TYPES_WITH_BUBBLE_LAYOUT = setOf( + EventType.MESSAGE, + EventType.ENCRYPTED, + EventType.STICKER + ) + private val MSG_TYPES_WITHOUT_BUBBLE_LAYOUT = setOf( + MessageType.MSGTYPE_POLL_START, + MessageType.MSGTYPE_VERIFICATION_REQUEST + ) + } + fun create(params: TimelineItemFactoryParams): TimelineMessageLayout { val event = params.event @@ -55,27 +68,42 @@ class TimelineMessageLayoutFactory @Inject constructor(private val session: Sess val messageLayout = when (layoutSettingsProvider.getLayoutSettings()) { TimelineLayoutSettings.MODERN -> { - TimelineMessageLayout.Modern( - showAvatar = showInformation, - showDisplayName = showInformation, - showTimestamp = showInformation || vectorPreferences.alwaysShowTimeStamps() - ) + buildModernLayout(showInformation) } TimelineLayoutSettings.BUBBLE -> { - val isFirstFromThisSender = nextDisplayableEvent?.root?.senderId != event.root.senderId || addDaySeparator - val isLastFromThisSender = prevDisplayableEvent?.root?.senderId != event.root.senderId || prevDisplayableEvent?.root?.localDateTime()?.toLocalDate() != date.toLocalDate() - TimelineMessageLayout.Bubble( - showAvatar = showInformation && !isSentByMe, - showDisplayName = showInformation && !isSentByMe, - isIncoming = !isSentByMe, - isFirstFromThisSender = isFirstFromThisSender, - isLastFromThisSender = isLastFromThisSender - ) + val type = event.root.getClearType() + if (type in EVENT_TYPES_WITH_BUBBLE_LAYOUT) { + val messageContent = if (type == EventType.MESSAGE) params.event.getLastMessageContent() else null + if (messageContent?.msgType in MSG_TYPES_WITHOUT_BUBBLE_LAYOUT) { + buildModernLayout(showInformation) + } + val isFirstFromThisSender = nextDisplayableEvent?.root?.senderId != event.root.senderId || addDaySeparator + val isLastFromThisSender = prevDisplayableEvent?.root?.senderId != event.root.senderId + || prevDisplayableEvent?.root?.localDateTime()?.toLocalDate() != date.toLocalDate() + + TimelineMessageLayout.Bubble( + showAvatar = showInformation && !isSentByMe, + showDisplayName = showInformation && !isSentByMe, + isIncoming = !isSentByMe, + isFirstFromThisSender = isFirstFromThisSender, + isLastFromThisSender = isLastFromThisSender + ) + } else { + buildModernLayout(showInformation) + } } } return messageLayout } + private fun buildModernLayout(showInformation: Boolean): TimelineMessageLayout.Default { + return TimelineMessageLayout.Default( + showAvatar = showInformation, + showDisplayName = showInformation, + showTimestamp = showInformation || vectorPreferences.alwaysShowTimeStamps() + ) + } + /** * Tiles type message never show the sender information (like verification request), so we should repeat it for next message * even if same sender From 5ac155285bcbf13992e3e2ff2d9352f30bbe0cb3 Mon Sep 17 00:00:00 2001 From: ganfra Date: Thu, 13 Jan 2022 13:14:37 +0100 Subject: [PATCH 11/36] Bubbles: some clean up --- tools/check/forbidden_strings_in_code.txt | 2 +- .../app/features/home/AvatarRenderer.kt | 1 - .../helper/MessageInformationDataFactory.kt | 3 ++- .../timeline/style/TimelineMessageLayout.kt | 21 ++++++++++++------- .../style/TimelineMessageLayoutFactory.kt | 5 ++--- .../detail/timeline/view/MessageBubbleView.kt | 4 ++-- vector/src/main/res/values/strings.xml | 1 + .../res/xml/vector_settings_preferences.xml | 2 +- 8 files changed, 22 insertions(+), 17 deletions(-) diff --git a/tools/check/forbidden_strings_in_code.txt b/tools/check/forbidden_strings_in_code.txt index a0c7c2f4c6..cbfaf20247 100644 --- a/tools/check/forbidden_strings_in_code.txt +++ b/tools/check/forbidden_strings_in_code.txt @@ -160,7 +160,7 @@ Formatter\.formatShortFileSize===1 # android\.text\.TextUtils ### This is not a rule, but a warning: the number of "enum class" has changed. For Json classes, it is mandatory that they have `@JsonClass(generateAdapter = false)`. If the enum is not used as a Json class, change the value in file forbidden_strings_in_code.txt -enum class===115 +enum class===116 ### Do not import temporary legacy classes import org.matrix.android.sdk.internal.legacy.riot===3 diff --git a/vector/src/main/java/im/vector/app/features/home/AvatarRenderer.kt b/vector/src/main/java/im/vector/app/features/home/AvatarRenderer.kt index be689eb27a..2ee3233637 100644 --- a/vector/src/main/java/im/vector/app/features/home/AvatarRenderer.kt +++ b/vector/src/main/java/im/vector/app/features/home/AvatarRenderer.kt @@ -145,7 +145,6 @@ class AvatarRenderer @Inject constructor(private val activeSessionHolder: Active } else -> { it.apply(RequestOptions.circleCropTransform()) - } } } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/MessageInformationDataFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/MessageInformationDataFactory.kt index 2edab8af74..276802e574 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/MessageInformationDataFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/MessageInformationDataFactory.kt @@ -65,7 +65,8 @@ class MessageInformationDataFactory @Inject constructor(private val session: Ses val addDaySeparator = date.toLocalDate() != nextDate?.toLocalDate() val isFirstFromThisSender = nextDisplayableEvent?.root?.senderId != event.root.senderId || addDaySeparator - val isLastFromThisSender = prevDisplayableEvent?.root?.senderId != event.root.senderId || prevDisplayableEvent?.root?.localDateTime()?.toLocalDate() != date.toLocalDate() + val isLastFromThisSender = prevDisplayableEvent?.root?.senderId != event.root.senderId || + prevDisplayableEvent?.root?.localDateTime()?.toLocalDate() != date.toLocalDate() val time = dateFormatter.format(event.root.originServerTs, DateFormatKind.MESSAGE_SIMPLE) val e2eDecoration = getE2EDecoration(roomSummary, event) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineMessageLayout.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineMessageLayout.kt index 50f4e95cee..bd4be10783 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineMessageLayout.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineMessageLayout.kt @@ -30,16 +30,21 @@ sealed interface TimelineMessageLayout : Parcelable { data class Default(override val showAvatar: Boolean, override val showDisplayName: Boolean, override val showTimestamp: Boolean, - // Keep defaultLayout generated on epoxy items + // Keep defaultLayout generated on epoxy items override val layoutRes: Int = 0) : TimelineMessageLayout @Parcelize - data class Bubble(override val showAvatar: Boolean, - override val showDisplayName: Boolean, - override val showTimestamp: Boolean = true, - val isIncoming: Boolean, - val isFirstFromThisSender: Boolean, - val isLastFromThisSender: Boolean, - override val layoutRes: Int = if (isIncoming) R.layout.item_timeline_event_bubble_incoming_base else R.layout.item_timeline_event_bubble_outgoing_base, + data class Bubble( + override val showAvatar: Boolean, + override val showDisplayName: Boolean, + override val showTimestamp: Boolean = true, + val isIncoming: Boolean, + val isFirstFromThisSender: Boolean, + val isLastFromThisSender: Boolean, + override val layoutRes: Int = if (isIncoming) { + R.layout.item_timeline_event_bubble_incoming_base + } else { + R.layout.item_timeline_event_bubble_outgoing_base + }, ) : TimelineMessageLayout } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineMessageLayoutFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineMessageLayoutFactory.kt index 9b2877f0f8..6c26679105 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineMessageLayoutFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineMessageLayoutFactory.kt @@ -45,7 +45,6 @@ class TimelineMessageLayoutFactory @Inject constructor(private val session: Sess } fun create(params: TimelineItemFactoryParams): TimelineMessageLayout { - val event = params.event val nextDisplayableEvent = params.nextDisplayableEvent val prevDisplayableEvent = params.prevDisplayableEvent @@ -78,8 +77,8 @@ class TimelineMessageLayoutFactory @Inject constructor(private val session: Sess buildModernLayout(showInformation) } val isFirstFromThisSender = nextDisplayableEvent?.root?.senderId != event.root.senderId || addDaySeparator - val isLastFromThisSender = prevDisplayableEvent?.root?.senderId != event.root.senderId - || prevDisplayableEvent?.root?.localDateTime()?.toLocalDate() != date.toLocalDate() + val isLastFromThisSender = prevDisplayableEvent?.root?.senderId != event.root.senderId || + prevDisplayableEvent?.root?.localDateTime()?.toLocalDate() != date.toLocalDate() TimelineMessageLayout.Bubble( showAvatar = showInformation && !isSentByMe, diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/view/MessageBubbleView.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/view/MessageBubbleView.kt index 46e3edca44..68e32861c3 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/view/MessageBubbleView.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/view/MessageBubbleView.kt @@ -37,8 +37,8 @@ import im.vector.app.core.utils.DimensionConverter import im.vector.app.features.themes.ThemeUtils class MessageBubbleView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, - defStyleAttr: Int = 0) - : RelativeLayout(context, attrs, defStyleAttr), MessageViewConfiguration { + defStyleAttr: Int = 0) : + RelativeLayout(context, attrs, defStyleAttr), MessageViewConfiguration { override var isIncoming: Boolean = false set(value) { diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml index 34ac5fcddc..4e927701d7 100644 --- a/vector/src/main/res/values/strings.xml +++ b/vector/src/main/res/values/strings.xml @@ -3701,4 +3701,5 @@ Poll ended Remove poll Are you sure you want to remove this poll? You won\'t be able to recover it once removed. + Message bubbles diff --git a/vector/src/main/res/xml/vector_settings_preferences.xml b/vector/src/main/res/xml/vector_settings_preferences.xml index ac8a48fb2e..a25562e3d2 100644 --- a/vector/src/main/res/xml/vector_settings_preferences.xml +++ b/vector/src/main/res/xml/vector_settings_preferences.xml @@ -25,7 +25,7 @@ + android:title="@string/message_bubbles" /> Date: Fri, 14 Jan 2022 19:19:23 +0100 Subject: [PATCH 12/36] Bubbles: make it works for file, voice and polls. Also add parity for "modern" layout. --- .../src/main/res/drawable/bg_media_pill.xml | 3 - .../main/res/drawable/file_progress_bar.xml | 8 +- .../ui-styles/src/main/res/values/dimens.xml | 3 + .../src/main/res/values/styles_progress.xml | 1 + .../src/main/res/values/styles_timeline.xml | 10 ++ .../ContentDownloadStateTrackerBinder.kt | 12 +-- .../detail/timeline/item/MessageFileItem.kt | 15 ++- .../timeline/item/MessageImageVideoItem.kt | 5 +- .../detail/timeline/item/MessageVoiceItem.kt | 13 +++ .../style/TimelineMessageLayoutFactory.kt | 27 ++--- .../detail/timeline/view/MessageBubbleView.kt | 23 +++- .../res/drawable/overlay_bubble_media.xml | 8 ++ .../res/layout/item_timeline_event_base.xml | 4 +- .../layout/item_timeline_event_file_stub.xml | 102 ++++++++---------- ...item_timeline_event_media_message_stub.xml | 12 +++ .../res/layout/item_timeline_event_poll.xml | 4 +- ...em_timeline_event_view_stubs_container.xml | 5 +- .../layout/item_timeline_event_voice_stub.xml | 29 ++--- vector/src/main/res/layout/view_file_icon.xml | 14 +-- .../main/res/layout/view_message_bubble.xml | 2 +- .../layout/view_voice_message_recorder.xml | 2 +- 21 files changed, 171 insertions(+), 131 deletions(-) rename vector/src/main/res/drawable/bg_voice_playback.xml => library/ui-styles/src/main/res/drawable/bg_media_pill.xml (82%) create mode 100644 vector/src/main/res/drawable/overlay_bubble_media.xml diff --git a/vector/src/main/res/drawable/bg_voice_playback.xml b/library/ui-styles/src/main/res/drawable/bg_media_pill.xml similarity index 82% rename from vector/src/main/res/drawable/bg_voice_playback.xml rename to library/ui-styles/src/main/res/drawable/bg_media_pill.xml index 4474c00345..2ad9ca9918 100644 --- a/vector/src/main/res/drawable/bg_voice_playback.xml +++ b/library/ui-styles/src/main/res/drawable/bg_media_pill.xml @@ -2,9 +2,6 @@ - - - - + + - - + diff --git a/library/ui-styles/src/main/res/values/dimens.xml b/library/ui-styles/src/main/res/values/dimens.xml index 6b6f1a3aef..3955bf44fd 100644 --- a/library/ui-styles/src/main/res/values/dimens.xml +++ b/library/ui-styles/src/main/res/values/dimens.xml @@ -15,6 +15,8 @@ 72dp 16dp + 32dp + 40dp 60dp @@ -51,6 +53,7 @@ 28dp 62dp + 300dp 0.05 diff --git a/library/ui-styles/src/main/res/values/styles_progress.xml b/library/ui-styles/src/main/res/values/styles_progress.xml index 712e7e98b6..04a0e01b58 100644 --- a/library/ui-styles/src/main/res/values/styles_progress.xml +++ b/library/ui-styles/src/main/res/values/styles_progress.xml @@ -6,6 +6,7 @@ diff --git a/library/ui-styles/src/main/res/values/styles_timeline.xml b/library/ui-styles/src/main/res/values/styles_timeline.xml index 7fd7eac0ec..ef2f694d3e 100644 --- a/library/ui-styles/src/main/res/values/styles_timeline.xml +++ b/library/ui-styles/src/main/res/values/styles_timeline.xml @@ -12,4 +12,14 @@ 4dp + + \ No newline at end of file diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/ContentDownloadStateTrackerBinder.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/ContentDownloadStateTrackerBinder.kt index caf0131144..e4405570a6 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/ContentDownloadStateTrackerBinder.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/ContentDownloadStateTrackerBinder.kt @@ -29,9 +29,7 @@ import org.matrix.android.sdk.api.session.file.ContentDownloadStateTracker import javax.inject.Inject @ActivityScoped -class ContentDownloadStateTrackerBinder @Inject constructor(private val activeSessionHolder: ActiveSessionHolder, - private val messageColorProvider: MessageColorProvider, - private val errorFormatter: ErrorFormatter) { +class ContentDownloadStateTrackerBinder @Inject constructor(private val activeSessionHolder: ActiveSessionHolder){ private val updateListeners = mutableMapOf() @@ -39,7 +37,7 @@ class ContentDownloadStateTrackerBinder @Inject constructor(private val activeSe holder: MessageFileItem.Holder) { activeSessionHolder.getSafeActiveSession()?.also { session -> val downloadStateTracker = session.contentDownloadProgressTracker() - val updateListener = ContentDownloadUpdater(holder, messageColorProvider, errorFormatter) + val updateListener = ContentDownloadUpdater(holder) updateListeners[mxcUrl] = updateListener downloadStateTracker.track(mxcUrl, updateListener) } @@ -62,9 +60,7 @@ class ContentDownloadStateTrackerBinder @Inject constructor(private val activeSe } } -private class ContentDownloadUpdater(private val holder: MessageFileItem.Holder, - private val messageColorProvider: MessageColorProvider, - private val errorFormatter: ErrorFormatter) : ContentDownloadStateTracker.UpdateListener { +private class ContentDownloadUpdater(private val holder: MessageFileItem.Holder) : ContentDownloadStateTracker.UpdateListener { override fun onDownloadStateUpdate(state: ContentDownloadStateTracker.State) { when (state) { @@ -124,7 +120,7 @@ private class ContentDownloadUpdater(private val holder: MessageFileItem.Holder, private fun handleSuccess() { stop() holder.fileDownloadProgress.isIndeterminate = false - holder.fileDownloadProgress.progress = 100 + holder.fileDownloadProgress.progress = 0 holder.fileImageView.setImageResource(R.drawable.ic_paperclip) } } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageFileItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageFileItem.kt index bd35532eae..e736c0e2da 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageFileItem.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageFileItem.kt @@ -16,6 +16,8 @@ package im.vector.app.features.home.room.detail.timeline.item +import android.content.res.ColorStateList +import android.graphics.Color import android.graphics.Paint import android.view.ViewGroup import android.widget.ImageView @@ -29,6 +31,8 @@ import im.vector.app.R import im.vector.app.core.epoxy.onClick import im.vector.app.features.home.room.detail.timeline.helper.ContentDownloadStateTrackerBinder import im.vector.app.features.home.room.detail.timeline.helper.ContentUploadStateTrackerBinder +import im.vector.app.features.home.room.detail.timeline.style.TimelineMessageLayout +import im.vector.app.features.themes.ThemeUtils @EpoxyModelClass(layout = R.layout.item_timeline_event_base) abstract class MessageFileItem : AbsMessageItem() { @@ -73,15 +77,19 @@ abstract class MessageFileItem : AbsMessageItem() { } else { if (izDownloaded) { holder.fileImageView.setImageResource(iconRes) - holder.fileDownloadProgress.progress = 100 + holder.fileDownloadProgress.progress = 0 } else { contentDownloadStateTrackerBinder.bind(mxcUrl, holder) holder.fileImageView.setImageResource(R.drawable.ic_download) - holder.fileDownloadProgress.progress = 0 } } // holder.view.setOnClickListener(clickListener) - + val backgroundTint = if(attributes.informationData.messageLayout is TimelineMessageLayout.Bubble){ + Color.TRANSPARENT + }else { + ThemeUtils.getColor(holder.view.context, R.attr.vctr_content_quinary) + } + holder.mainLayout.backgroundTintList = ColorStateList.valueOf(backgroundTint) holder.filenameView.onClick(attributes.itemClickListener) holder.filenameView.setOnLongClickListener(attributes.itemLongClickListener) holder.fileImageWrapper.onClick(attributes.itemClickListener) @@ -98,6 +106,7 @@ abstract class MessageFileItem : AbsMessageItem() { override fun getViewStubId() = STUB_ID class Holder : AbsMessageItem.Holder(STUB_ID) { + val mainLayout by bind(R.id.messageFileMainLayout) val progressLayout by bind(R.id.messageFileUploadProgressLayout) val fileLayout by bind(R.id.messageFileLayout) val fileImageView by bind(R.id.messageFileIconView) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageImageVideoItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageImageVideoItem.kt index e20ad48b17..799cb1a002 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageImageVideoItem.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageImageVideoItem.kt @@ -29,6 +29,7 @@ import im.vector.app.core.epoxy.onClick import im.vector.app.core.files.LocalFilesHelper import im.vector.app.core.glide.GlideApp import im.vector.app.features.home.room.detail.timeline.helper.ContentUploadStateTrackerBinder +import im.vector.app.features.home.room.detail.timeline.style.TimelineMessageLayout import im.vector.app.features.home.room.detail.timeline.view.MessageViewConfiguration import im.vector.app.features.media.ImageContentRenderer @@ -71,7 +72,8 @@ abstract class MessageImageVideoItem : AbsMessageItem(R.id.messageThumbnailView) val playContentView by bind(R.id.messageMediaPlayView) val mediaContentView by bind(R.id.messageContentMedia) + val overlayView by bind(R.id.messageMediaOverlayView) } companion object { diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceItem.kt index b6fa684eca..1058e3c1f9 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceItem.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceItem.kt @@ -16,10 +16,14 @@ package im.vector.app.features.home.room.detail.timeline.item +import android.content.res.ColorStateList +import android.graphics.Color import android.text.format.DateUtils +import android.view.View import android.view.ViewGroup import android.widget.ImageButton import android.widget.TextView +import androidx.core.content.ContextCompat import androidx.core.view.isVisible import com.airbnb.epoxy.EpoxyAttribute import com.airbnb.epoxy.EpoxyModelClass @@ -29,6 +33,8 @@ import im.vector.app.core.epoxy.ClickListener import im.vector.app.features.home.room.detail.timeline.helper.ContentDownloadStateTrackerBinder import im.vector.app.features.home.room.detail.timeline.helper.ContentUploadStateTrackerBinder import im.vector.app.features.home.room.detail.timeline.helper.VoiceMessagePlaybackTracker +import im.vector.app.features.home.room.detail.timeline.style.TimelineMessageLayout +import im.vector.app.features.themes.ThemeUtils @EpoxyModelClass(layout = R.layout.item_timeline_event_base) abstract class MessageVoiceItem : AbsMessageItem() { @@ -80,6 +86,12 @@ abstract class MessageVoiceItem : AbsMessageItem() { } } + val backgroundTint = if(attributes.informationData.messageLayout is TimelineMessageLayout.Bubble){ + Color.TRANSPARENT + }else { + ThemeUtils.getColor(holder.view.context, R.attr.vctr_content_quinary) + } + holder.voicePlaybackLayout.backgroundTintList = ColorStateList.valueOf(backgroundTint) holder.voicePlaybackControlButton.setOnClickListener { playbackControlButtonClickListener?.invoke(it) } voiceMessagePlaybackTracker.track(attributes.informationData.eventId, object : VoiceMessagePlaybackTracker.Listener { @@ -123,6 +135,7 @@ abstract class MessageVoiceItem : AbsMessageItem() { override fun getViewStubId() = STUB_ID class Holder : AbsMessageItem.Holder(STUB_ID) { + val voicePlaybackLayout by bind(R.id.voicePlaybackLayout) val voiceLayout by bind(R.id.voiceLayout) val voicePlaybackControlButton by bind(R.id.voicePlaybackControlButton) val voicePlaybackTime by bind(R.id.voicePlaybackTime) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineMessageLayoutFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineMessageLayoutFactory.kt index 6c26679105..f5ca97dc1d 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineMessageLayoutFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineMessageLayoutFactory.kt @@ -35,11 +35,11 @@ class TimelineMessageLayoutFactory @Inject constructor(private val session: Sess companion object { private val EVENT_TYPES_WITH_BUBBLE_LAYOUT = setOf( EventType.MESSAGE, + EventType.POLL_START, EventType.ENCRYPTED, EventType.STICKER ) private val MSG_TYPES_WITHOUT_BUBBLE_LAYOUT = setOf( - MessageType.MSGTYPE_POLL_START, MessageType.MSGTYPE_VERIFICATION_REQUEST ) } @@ -72,21 +72,22 @@ class TimelineMessageLayoutFactory @Inject constructor(private val session: Sess TimelineLayoutSettings.BUBBLE -> { val type = event.root.getClearType() if (type in EVENT_TYPES_WITH_BUBBLE_LAYOUT) { - val messageContent = if (type == EventType.MESSAGE) params.event.getLastMessageContent() else null + val messageContent = event.getLastMessageContent() if (messageContent?.msgType in MSG_TYPES_WITHOUT_BUBBLE_LAYOUT) { buildModernLayout(showInformation) - } - val isFirstFromThisSender = nextDisplayableEvent?.root?.senderId != event.root.senderId || addDaySeparator - val isLastFromThisSender = prevDisplayableEvent?.root?.senderId != event.root.senderId || - prevDisplayableEvent?.root?.localDateTime()?.toLocalDate() != date.toLocalDate() + } else { + val isFirstFromThisSender = nextDisplayableEvent?.root?.senderId != event.root.senderId || addDaySeparator + val isLastFromThisSender = prevDisplayableEvent?.root?.senderId != event.root.senderId || + prevDisplayableEvent?.root?.localDateTime()?.toLocalDate() != date.toLocalDate() - TimelineMessageLayout.Bubble( - showAvatar = showInformation && !isSentByMe, - showDisplayName = showInformation && !isSentByMe, - isIncoming = !isSentByMe, - isFirstFromThisSender = isFirstFromThisSender, - isLastFromThisSender = isLastFromThisSender - ) + TimelineMessageLayout.Bubble( + showAvatar = showInformation && !isSentByMe, + showDisplayName = showInformation && !isSentByMe, + isIncoming = !isSentByMe, + isFirstFromThisSender = isFirstFromThisSender, + isLastFromThisSender = isLastFromThisSender, + ) + } } else { buildModernLayout(showInformation) } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/view/MessageBubbleView.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/view/MessageBubbleView.kt index 68e32861c3..0a3746f8a4 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/view/MessageBubbleView.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/view/MessageBubbleView.kt @@ -23,6 +23,7 @@ import android.util.AttributeSet import android.view.View import android.view.ViewOutlineProvider import android.widget.RelativeLayout +import android.widget.TextView import androidx.constraintlayout.widget.ConstraintLayout import androidx.constraintlayout.widget.ConstraintSet import androidx.core.content.ContextCompat @@ -38,7 +39,7 @@ import im.vector.app.features.themes.ThemeUtils class MessageBubbleView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0) : - RelativeLayout(context, attrs, defStyleAttr), MessageViewConfiguration { + RelativeLayout(context, attrs, defStyleAttr), MessageViewConfiguration { override var isIncoming: Boolean = false set(value) { @@ -57,7 +58,7 @@ class MessageBubbleView @JvmOverloads constructor(context: Context, attrs: Attri render() } - override var showTimeAsOverlay: Boolean = true + override var showTimeAsOverlay: Boolean = false set(value) { field = value render() @@ -69,7 +70,7 @@ class MessageBubbleView @JvmOverloads constructor(context: Context, attrs: Attri inflate(context, R.layout.view_message_bubble, this) context.withStyledAttributes(attrs, R.styleable.MessageBubble) { isIncoming = getBoolean(R.styleable.MessageBubble_incoming_style, false) - showTimeAsOverlay = getBoolean(R.styleable.MessageBubble_show_time_overlay, true) + showTimeAsOverlay = getBoolean(R.styleable.MessageBubble_show_time_overlay, false) isFirstFromSender = getBoolean(R.styleable.MessageBubble_is_first, false) isLastFromSender = getBoolean(R.styleable.MessageBubble_is_last, false) } @@ -95,6 +96,9 @@ class MessageBubbleView @JvmOverloads constructor(context: Context, attrs: Attri findViewById(R.id.messageEndGuideline).updateLayoutParams { marginEnd = resources.getDimensionPixelSize(R.dimen.chat_bubble_margin_end) } + findViewById(R.id.messageStartGuideline).updateLayoutParams { + marginStart = resources.getDimensionPixelSize(R.dimen.chat_bubble_margin_start) + } } else { val oppositeLayoutDirection = if (currentLayoutDirection == View.LAYOUT_DIRECTION_LTR) { View.LAYOUT_DIRECTION_RTL @@ -108,14 +112,23 @@ class MessageBubbleView @JvmOverloads constructor(context: Context, attrs: Attri findViewById(R.id.messageEndGuideline).updateLayoutParams { marginEnd = resources.getDimensionPixelSize(R.dimen.chat_bubble_margin_start) } + findViewById(R.id.messageStartGuideline).updateLayoutParams { + marginStart = resources.getDimensionPixelSize(R.dimen.chat_bubble_margin_end) + } } ConstraintSet().apply { clone(bubbleView) clear(R.id.viewStubContainer, ConstraintSet.END) if (showTimeAsOverlay) { - connect(R.id.viewStubContainer, ConstraintSet.END, R.id.messageTimeView, ConstraintSet.START, 0) - } else { + val timeColor = ContextCompat.getColor(context, R.color.palette_white) + findViewById(R.id.messageTimeView).setTextColor(timeColor) connect(R.id.viewStubContainer, ConstraintSet.END, R.id.parent, ConstraintSet.END, 0) + val margin = resources.getDimensionPixelSize(R.dimen.layout_horizontal_margin) + setMargin(R.id.messageTimeView, ConstraintSet.END, margin) + } else { + val timeColor = ThemeUtils.getColor(context, R.attr.vctr_content_tertiary) + findViewById(R.id.messageTimeView).setTextColor(timeColor) + connect(R.id.viewStubContainer, ConstraintSet.END, R.id.messageTimeView, ConstraintSet.START, 0) } applyTo(bubbleView) } diff --git a/vector/src/main/res/drawable/overlay_bubble_media.xml b/vector/src/main/res/drawable/overlay_bubble_media.xml new file mode 100644 index 0000000000..ce34a39037 --- /dev/null +++ b/vector/src/main/res/drawable/overlay_bubble_media.xml @@ -0,0 +1,8 @@ + + + + \ No newline at end of file diff --git a/vector/src/main/res/layout/item_timeline_event_base.xml b/vector/src/main/res/layout/item_timeline_event_base.xml index debf578519..bc9ed68232 100644 --- a/vector/src/main/res/layout/item_timeline_event_base.xml +++ b/vector/src/main/res/layout/item_timeline_event_base.xml @@ -78,14 +78,14 @@ + android:addStatesFromChildren="true" /> - - - - - - - - - - - - - - - - - - - - + tools:viewBindingIgnore="true"> - + + + + + + + + + - + + \ No newline at end of file diff --git a/vector/src/main/res/layout/item_timeline_event_media_message_stub.xml b/vector/src/main/res/layout/item_timeline_event_media_message_stub.xml index 68abfbfda8..bdfa0c5164 100644 --- a/vector/src/main/res/layout/item_timeline_event_media_message_stub.xml +++ b/vector/src/main/res/layout/item_timeline_event_media_message_stub.xml @@ -18,12 +18,24 @@ tools:layout_height="300dp" tools:src="@tools:sample/backgrounds/scenic" /> + + + tools:visibility="gone" /> diff --git a/vector/src/main/res/layout/item_timeline_event_voice_stub.xml b/vector/src/main/res/layout/item_timeline_event_voice_stub.xml index 5cdd5a815a..a180afbf8e 100644 --- a/vector/src/main/res/layout/item_timeline_event_voice_stub.xml +++ b/vector/src/main/res/layout/item_timeline_event_voice_stub.xml @@ -1,31 +1,24 @@ - + style="@style/TimelineContentMediaPillStyle"> - + diff --git a/vector/src/main/res/layout/view_file_icon.xml b/vector/src/main/res/layout/view_file_icon.xml index 1c5268a50b..db88802ba1 100644 --- a/vector/src/main/res/layout/view_file_icon.xml +++ b/vector/src/main/res/layout/view_file_icon.xml @@ -2,22 +2,22 @@ + android:layout_width="32dp" + android:layout_height="32dp" + tools:parentTag="android.widget.FrameLayout"> + tools:progress="40" /> + app:layout_constraintWidth_max="@dimen/chat_bubble_fixed_size" /> Date: Tue, 18 Jan 2022 19:26:23 +0100 Subject: [PATCH 13/36] Fix url preview sizing --- ...ageViewConfiguration.kt => TimelineMessageLayoutRenderer.kt} | 2 ++ vector/src/main/res/layout/view_url_preview.xml | 2 ++ 2 files changed, 4 insertions(+) rename vector/src/main/java/im/vector/app/features/home/room/detail/timeline/view/{MessageViewConfiguration.kt => TimelineMessageLayoutRenderer.kt} (94%) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/view/MessageViewConfiguration.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/view/TimelineMessageLayoutRenderer.kt similarity index 94% rename from vector/src/main/java/im/vector/app/features/home/room/detail/timeline/view/MessageViewConfiguration.kt rename to vector/src/main/java/im/vector/app/features/home/room/detail/timeline/view/TimelineMessageLayoutRenderer.kt index bfb5884674..23dd9860f8 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/view/MessageViewConfiguration.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/view/TimelineMessageLayoutRenderer.kt @@ -21,4 +21,6 @@ interface MessageViewConfiguration { var isFirstFromSender: Boolean var isLastFromSender: Boolean var showTimeAsOverlay: Boolean + var showNoBubble: Boolean + fun render() } diff --git a/vector/src/main/res/layout/view_url_preview.xml b/vector/src/main/res/layout/view_url_preview.xml index 93ea4ea2bb..ff4ab1ed9a 100644 --- a/vector/src/main/res/layout/view_url_preview.xml +++ b/vector/src/main/res/layout/view_url_preview.xml @@ -9,6 +9,7 @@ From 5ee4984ec843e073630f659f2d54f29e86110e03 Mon Sep 17 00:00:00 2001 From: ganfra Date: Tue, 18 Jan 2022 19:27:12 +0100 Subject: [PATCH 14/36] Bubbles: handle images and make small refactoring --- .../ui-styles/src/main/res/values/dimens.xml | 1 + .../src/main/res/values/styles_timeline.xml | 6 - .../timeline/item/AbsBaseMessageItem.kt | 7 +- .../timeline/item/MessageImageVideoItem.kt | 24 ++- .../timeline/style/TimelineMessageLayout.kt | 3 +- .../style/TimelineMessageLayoutFactory.kt | 8 + .../detail/timeline/view/MessageBubbleView.kt | 148 +++++++++--------- .../view/TimelineMessageLayoutRenderer.kt | 11 +- .../features/media/ImageContentRenderer.kt | 6 +- .../res/drawable/overlay_bubble_media.xml | 1 + ...em_timeline_event_view_stubs_container.xml | 25 +-- .../main/res/layout/view_message_bubble.xml | 4 +- 12 files changed, 131 insertions(+), 113 deletions(-) diff --git a/library/ui-styles/src/main/res/values/dimens.xml b/library/ui-styles/src/main/res/values/dimens.xml index d89b3f477f..15e2fc2fbb 100644 --- a/library/ui-styles/src/main/res/values/dimens.xml +++ b/library/ui-styles/src/main/res/values/dimens.xml @@ -54,6 +54,7 @@ 28dp 62dp 300dp + 12dp 0.05 diff --git a/library/ui-styles/src/main/res/values/styles_timeline.xml b/library/ui-styles/src/main/res/values/styles_timeline.xml index ef2f694d3e..127d9f3dc7 100644 --- a/library/ui-styles/src/main/res/values/styles_timeline.xml +++ b/library/ui-styles/src/main/res/values/styles_timeline.xml @@ -4,12 +4,6 @@ + +