Bubble: introduce CornersRadius
This commit is contained in:
parent
fd99d6d7d8
commit
820bc644b6
@ -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))
|
||||
}
|
||||
|
@ -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()
|
||||
|
@ -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
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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()
|
||||
}
|
@ -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
|
||||
|
@ -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) {
|
||||
|
Loading…
x
Reference in New Issue
Block a user