From 820bc644b6066de5ac89aa561d492b0ff3da9988 Mon Sep 17 00:00:00 2001 From: ganfra Date: Mon, 31 Jan 2022 19:18:42 +0100 Subject: [PATCH] Bubble: introduce CornersRadius --- .../timeline/item/MessageImageVideoItem.kt | 11 +--- .../timeline/item/MessageLocationItem.kt | 4 -- .../detail/timeline/style/CornersRadius.kt | 38 +++++++++++++ .../timeline/style/TimelineMessageLayout.kt | 14 ++++- .../style/TimelineMessageLayoutExt.kt | 48 ---------------- .../style/TimelineMessageLayoutFactory.kt | 55 ++++++++++++++++--- .../detail/timeline/view/MessageBubbleView.kt | 17 ++---- 7 files changed, 103 insertions(+), 84 deletions(-) create mode 100644 vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/CornersRadius.kt delete mode 100644 vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineMessageLayoutExt.kt 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 2ac84e6ff6..6f801d9eb7 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 @@ -33,7 +33,7 @@ import im.vector.app.core.glide.GlideApp import im.vector.app.core.utils.DimensionConverter 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.TimelineMessageLayoutRenderer +import im.vector.app.features.home.room.detail.timeline.style.granularRoundedCorners import im.vector.app.features.media.ImageContentRenderer @EpoxyModelClass(layout = R.layout.item_timeline_event_base) @@ -62,14 +62,7 @@ abstract class MessageImageVideoItem : AbsMessageItem( renderSendState(holder.mapViewContainer, null) val location = locationData ?: return val locationOwnerId = userId ?: return - val messageLayout = attributes.informationData.messageLayout - if (messageLayout is TimelineMessageLayout.Bubble) { - holder.mapCardView.shapeAppearanceModel = messageLayout.shapeAppearanceModel(12f) - } holder.clickableMapArea.onClick { callback?.onMapClicked() diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/CornersRadius.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/CornersRadius.kt new file mode 100644 index 0000000000..c10bebdcf4 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/CornersRadius.kt @@ -0,0 +1,38 @@ +/* + * 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 com.bumptech.glide.load.resource.bitmap.GranularRoundedCorners +import com.google.android.material.shape.CornerFamily +import com.google.android.material.shape.ShapeAppearanceModel + +fun TimelineMessageLayout.Bubble.CornersRadius.granularRoundedCorners(): GranularRoundedCorners { + return GranularRoundedCorners(topStartRadius, topEndRadius, bottomEndRadius, bottomStartRadius) +} + +fun TimelineMessageLayout.Bubble.CornersRadius.shapeAppearanceModel(): ShapeAppearanceModel { + return ShapeAppearanceModel().toBuilder() + .setTopRightCorner(topEndRadius.cornerFamily(), topEndRadius) + .setBottomRightCorner(bottomEndRadius.cornerFamily(), bottomEndRadius) + .setTopLeftCorner(topStartRadius.cornerFamily(), topStartRadius) + .setBottomLeftCorner(bottomStartRadius.cornerFamily(), bottomStartRadius) + .build() +} + +private fun Float.cornerFamily(): Int { + return if (this == 0F) CornerFamily.CUT else CornerFamily.ROUNDED +} 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 bdcbc52037..c87680de0a 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 @@ -39,14 +39,22 @@ sealed interface TimelineMessageLayout : Parcelable { override val showDisplayName: Boolean, override val showTimestamp: Boolean = true, val isIncoming: Boolean, - val isFirstFromThisSender: Boolean, - val isLastFromThisSender: Boolean, val isPseudoBubble: Boolean, + val cornersRadius: CornersRadius, val timestampAsOverlay: 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 + ) : TimelineMessageLayout { + + @Parcelize + data class CornersRadius( + val topStartRadius: Float, + val topEndRadius: Float, + val bottomStartRadius: Float, + val bottomEndRadius: Float + ) : Parcelable + } } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineMessageLayoutExt.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineMessageLayoutExt.kt deleted file mode 100644 index 503c9c7a6d..0000000000 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineMessageLayoutExt.kt +++ /dev/null @@ -1,48 +0,0 @@ -/* - * 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 com.google.android.material.shape.CornerFamily -import com.google.android.material.shape.ShapeAppearanceModel - -fun TimelineMessageLayout.Bubble.shapeAppearanceModel(cornerRadius: Float): ShapeAppearanceModel { - val (topCornerFamily, topRadius) = if (isFirstFromThisSender) { - Pair(CornerFamily.ROUNDED, cornerRadius) - } else { - Pair(CornerFamily.CUT, 0f) - } - val (bottomCornerFamily, bottomRadius) = if (isLastFromThisSender) { - Pair(CornerFamily.ROUNDED, cornerRadius) - } else { - Pair(CornerFamily.CUT, 0f) - } - val shapeAppearanceModelBuilder = ShapeAppearanceModel().toBuilder() - if (isIncoming) { - shapeAppearanceModelBuilder - .setTopRightCorner(CornerFamily.ROUNDED, cornerRadius) - .setBottomRightCorner(CornerFamily.ROUNDED, cornerRadius) - .setTopLeftCorner(topCornerFamily, topRadius) - .setBottomLeftCorner(bottomCornerFamily, bottomRadius) - } else { - shapeAppearanceModelBuilder - .setTopLeftCorner(CornerFamily.ROUNDED, cornerRadius) - .setBottomLeftCorner(CornerFamily.ROUNDED, cornerRadius) - .setTopRightCorner(topCornerFamily, topRadius) - .setBottomRightCorner(bottomCornerFamily, bottomRadius) - } - return shapeAppearanceModelBuilder.build() -} 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 2f6ec15fde..373e9cfa68 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 @@ -16,7 +16,12 @@ package im.vector.app.features.home.room.detail.timeline.style +import android.content.res.Resources +import android.text.TextUtils +import android.view.View +import im.vector.app.R import im.vector.app.core.extensions.localDateTime +import im.vector.app.core.resources.LocaleProvider 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 @@ -31,6 +36,8 @@ import javax.inject.Inject class TimelineMessageLayoutFactory @Inject constructor(private val session: Session, private val layoutSettingsProvider: TimelineLayoutSettingsProvider, + private val localeProvider: LocaleProvider, + private val resources: Resources, private val vectorPreferences: VectorPreferences) { companion object { @@ -59,6 +66,15 @@ class TimelineMessageLayoutFactory @Inject constructor(private val session: Sess ) } + private val cornerRadius: Float by lazy { + resources.getDimensionPixelSize(R.dimen.chat_bubble_corner_radius).toFloat() + } + + private val isRTL: Boolean by lazy { + val currentLocale = localeProvider.current() + TextUtils.getLayoutDirectionFromLocale(currentLocale) == View.LAYOUT_DIRECTION_RTL + } + fun create(params: TimelineItemFactoryParams): TimelineMessageLayout { val event = params.event val nextDisplayableEvent = params.nextDisplayableEvent @@ -94,13 +110,18 @@ class TimelineMessageLayoutFactory @Inject constructor(private val session: Sess prevDisplayableEvent.root.senderId != event.root.senderId || prevDisplayableEvent.root.localDateTime().toLocalDate() != date.toLocalDate() + val cornersRadius = buildCornersRadius( + isIncoming = !isSentByMe, + isFirstFromThisSender = isFirstFromThisSender, + isLastFromThisSender = isLastFromThisSender + ) + val messageContent = event.getLastMessageContent() TimelineMessageLayout.Bubble( showAvatar = showInformation && !isSentByMe, showDisplayName = showInformation && !isSentByMe, isIncoming = !isSentByMe, - isFirstFromThisSender = isFirstFromThisSender, - isLastFromThisSender = isLastFromThisSender, + cornersRadius = cornersRadius, isPseudoBubble = messageContent.isPseudoBubble(), timestampAsOverlay = messageContent.timestampAsOverlay() ) @@ -112,15 +133,15 @@ class TimelineMessageLayoutFactory @Inject constructor(private val session: Sess return messageLayout } - private fun MessageContent?.isPseudoBubble(): Boolean{ - if(this == null) return false - if(msgType == MessageType.MSGTYPE_LOCATION) return vectorPreferences.labsRenderLocationsInTimeline() + private fun MessageContent?.isPseudoBubble(): Boolean { + if (this == null) return false + if (msgType == MessageType.MSGTYPE_LOCATION) return vectorPreferences.labsRenderLocationsInTimeline() return this.msgType in MSG_TYPES_WITH_PSEUDO_BUBBLE_LAYOUT } - private fun MessageContent?.timestampAsOverlay(): Boolean{ - if(this == null) return false - if(msgType == MessageType.MSGTYPE_LOCATION) return vectorPreferences.labsRenderLocationsInTimeline() + private fun MessageContent?.timestampAsOverlay(): Boolean { + if (this == null) return false + if (msgType == MessageType.MSGTYPE_LOCATION) return vectorPreferences.labsRenderLocationsInTimeline() return this.msgType in MSG_TYPES_WITH_TIMESTAMP_AS_OVERLAY } @@ -141,6 +162,24 @@ class TimelineMessageLayoutFactory @Inject constructor(private val session: Sess ) } + private fun buildCornersRadius(isIncoming: Boolean, isFirstFromThisSender: Boolean, isLastFromThisSender: Boolean): TimelineMessageLayout.Bubble.CornersRadius { + return if ((isIncoming && !isRTL) || (!isIncoming && isRTL)) { + TimelineMessageLayout.Bubble.CornersRadius( + topStartRadius = if (isFirstFromThisSender) cornerRadius else 0f, + topEndRadius = cornerRadius, + bottomStartRadius = if (isLastFromThisSender) cornerRadius else 0f, + bottomEndRadius = cornerRadius + ) + } else { + TimelineMessageLayout.Bubble.CornersRadius( + topStartRadius = cornerRadius, + topEndRadius = if (isFirstFromThisSender) cornerRadius else 0f, + bottomStartRadius = cornerRadius, + bottomEndRadius = if (isLastFromThisSender) cornerRadius else 0f + ) + } + } + /** * Tiles type message never show the sender information (like verification request), so we should repeat it for next message * even if same sender 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 a843a408c9..7aea380123 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 @@ -48,7 +48,6 @@ class MessageBubbleView @JvmOverloads constructor(context: Context, attrs: Attri private var isIncoming: Boolean = false - private val cornerRadius = resources.getDimensionPixelSize(R.dimen.chat_bubble_corner_radius).toFloat() private val horizontalStubPadding = DimensionConverter(resources).dpToPx(12) private val verticalStubPadding = DimensionConverter(resources).dpToPx(4) @@ -116,7 +115,7 @@ class MessageBubbleView @JvmOverloads constructor(context: Context, attrs: Attri } if (messageLayout.timestampAsOverlay) { views.messageOverlayView.isVisible = true - (views.messageOverlayView.background as? GradientDrawable)?.cornerRadii = messageLayout.cornerRadii(cornerRadius) + (views.messageOverlayView.background as? GradientDrawable)?.cornerRadii = messageLayout.cornersRadius.toFloatArray() } else { views.messageOverlayView.isVisible = false } @@ -125,7 +124,7 @@ class MessageBubbleView @JvmOverloads constructor(context: Context, attrs: Attri } else { views.viewStubContainer.root.setPadding(horizontalStubPadding, verticalStubPadding, horizontalStubPadding, verticalStubPadding) } - if (messageLayout.isIncoming) { + if (isIncoming) { views.messageEndGuideline.updateLayoutParams { marginEnd = resources.getDimensionPixelSize(R.dimen.chat_bubble_margin_end) } @@ -142,18 +141,12 @@ class MessageBubbleView @JvmOverloads constructor(context: Context, attrs: Attri } } - private fun TimelineMessageLayout.Bubble.cornerRadii(cornerRadius: Float): FloatArray { - val topRadius = if (isFirstFromThisSender) cornerRadius else 0f - val bottomRadius = if (isLastFromThisSender) cornerRadius else 0f - return if (isIncoming) { - floatArrayOf(topRadius, topRadius, cornerRadius, cornerRadius, cornerRadius, cornerRadius, bottomRadius, bottomRadius) - } else { - floatArrayOf(cornerRadius, cornerRadius, topRadius, topRadius, bottomRadius, bottomRadius, cornerRadius, cornerRadius) - } + private fun TimelineMessageLayout.Bubble.CornersRadius.toFloatArray(): FloatArray { + return floatArrayOf(topStartRadius, topStartRadius, topEndRadius, topEndRadius, bottomEndRadius, bottomEndRadius, bottomStartRadius, bottomStartRadius) } private fun updateDrawables(messageLayout: TimelineMessageLayout.Bubble) { - val shapeAppearanceModel = messageLayout.shapeAppearanceModel(cornerRadius) + val shapeAppearanceModel = messageLayout.cornersRadius.shapeAppearanceModel() bubbleDrawable.apply { this.shapeAppearanceModel = shapeAppearanceModel this.fillColor = if (messageLayout.isPseudoBubble) {