Bubble: introduce CornersRadius

This commit is contained in:
ganfra 2022-01-31 19:18:42 +01:00
parent fd99d6d7d8
commit 820bc644b6
7 changed files with 103 additions and 84 deletions

View file

@ -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<MessageImageVideoItem.Hold
val messageLayout = baseAttributes.informationData.messageLayout
val dimensionConverter = DimensionConverter(holder.view.resources)
val imageCornerTransformation = if (messageLayout is TimelineMessageLayout.Bubble) {
val cornerRadius = holder.view.resources.getDimensionPixelSize(R.dimen.chat_bubble_corner_radius).toFloat()
val topRadius = if (messageLayout.isFirstFromThisSender) cornerRadius else 0f
val bottomRadius = if (messageLayout.isLastFromThisSender) cornerRadius else 0f
if (messageLayout.isIncoming) {
GranularRoundedCorners(topRadius, cornerRadius, cornerRadius, bottomRadius)
} else {
GranularRoundedCorners(cornerRadius, topRadius, bottomRadius, cornerRadius)
}
messageLayout.cornersRadius.granularRoundedCorners()
} else {
RoundedCorners(dimensionConverter.dpToPx(8))
}

View file

@ -55,10 +55,6 @@ abstract class MessageLocationItem : AbsMessageItem<MessageLocationItem.Holder>(
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()

View file

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

View file

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

View file

@ -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()
}

View file

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

View file

@ -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<LayoutParams> {
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) {