mirror of
https://github.com/SchildiChat/SchildiChat-android.git
synced 2025-03-30 01:39:00 +03:00
Bubbles: handle images and make small refactoring
This commit is contained in:
parent
a9e7c45074
commit
5ee4984ec8
12 changed files with 131 additions and 113 deletions
library/ui-styles/src/main/res/values
vector/src/main
java/im/vector/app/features
home/room/detail/timeline
item
style
view
media
res
|
@ -54,6 +54,7 @@
|
||||||
<dimen name="chat_bubble_margin_start">28dp</dimen>
|
<dimen name="chat_bubble_margin_start">28dp</dimen>
|
||||||
<dimen name="chat_bubble_margin_end">62dp</dimen>
|
<dimen name="chat_bubble_margin_end">62dp</dimen>
|
||||||
<dimen name="chat_bubble_fixed_size">300dp</dimen>
|
<dimen name="chat_bubble_fixed_size">300dp</dimen>
|
||||||
|
<dimen name="chat_bubble_corner_radius">12dp</dimen>
|
||||||
|
|
||||||
<!-- Onboarding -->
|
<!-- Onboarding -->
|
||||||
<item name="ftue_auth_gutter_start_percent" format="float" type="dimen">0.05</item>
|
<item name="ftue_auth_gutter_start_percent" format="float" type="dimen">0.05</item>
|
||||||
|
|
|
@ -4,12 +4,6 @@
|
||||||
<style name="TimelineContentStubBaseParams">
|
<style name="TimelineContentStubBaseParams">
|
||||||
<item name="android:layout_width">match_parent</item>
|
<item name="android:layout_width">match_parent</item>
|
||||||
<item name="android:layout_height">wrap_content</item>
|
<item name="android:layout_height">wrap_content</item>
|
||||||
<item name="android:layout_marginStart">8dp</item>
|
|
||||||
<item name="android:layout_marginLeft">8dp</item>
|
|
||||||
<item name="android:layout_marginEnd">8dp</item>
|
|
||||||
<item name="android:layout_marginRight">8dp</item>
|
|
||||||
<item name="android:layout_marginBottom">4dp</item>
|
|
||||||
<item name="android:layout_marginTop">4dp</item>
|
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style name="TimelineContentMediaPillStyle">
|
<style name="TimelineContentMediaPillStyle">
|
||||||
|
|
|
@ -29,7 +29,7 @@ import im.vector.app.core.ui.views.ShieldImageView
|
||||||
import im.vector.app.features.home.AvatarRenderer
|
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.MessageColorProvider
|
||||||
import im.vector.app.features.home.room.detail.timeline.TimelineEventController
|
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.home.room.detail.timeline.view.TimelineMessageLayoutRenderer
|
||||||
import im.vector.app.features.reactions.widget.ReactionButton
|
import im.vector.app.features.reactions.widget.ReactionButton
|
||||||
import org.matrix.android.sdk.api.crypto.RoomEncryptionTrustLevel
|
import org.matrix.android.sdk.api.crypto.RoomEncryptionTrustLevel
|
||||||
import org.matrix.android.sdk.api.session.room.send.SendState
|
import org.matrix.android.sdk.api.session.room.send.SendState
|
||||||
|
@ -99,10 +99,7 @@ abstract class AbsBaseMessageItem<H : AbsBaseMessageItem.Holder> : BaseEventItem
|
||||||
|
|
||||||
holder.view.onClick(baseAttributes.itemClickListener)
|
holder.view.onClick(baseAttributes.itemClickListener)
|
||||||
holder.view.setOnLongClickListener(baseAttributes.itemLongClickListener)
|
holder.view.setOnLongClickListener(baseAttributes.itemLongClickListener)
|
||||||
(holder.view as? MessageViewConfiguration)?.apply {
|
(holder.view as? TimelineMessageLayoutRenderer)?.render(baseAttributes.informationData.messageLayout)
|
||||||
isFirstFromSender = baseAttributes.informationData.isFirstFromThisSender
|
|
||||||
isLastFromSender = baseAttributes.informationData.isLastFromThisSender
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun unbind(holder: H) {
|
override fun unbind(holder: H) {
|
||||||
|
|
|
@ -23,14 +23,17 @@ import androidx.core.view.ViewCompat
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
import com.airbnb.epoxy.EpoxyAttribute
|
import com.airbnb.epoxy.EpoxyAttribute
|
||||||
import com.airbnb.epoxy.EpoxyModelClass
|
import com.airbnb.epoxy.EpoxyModelClass
|
||||||
|
import com.bumptech.glide.load.resource.bitmap.GranularRoundedCorners
|
||||||
|
import com.bumptech.glide.load.resource.bitmap.RoundedCorners
|
||||||
import im.vector.app.R
|
import im.vector.app.R
|
||||||
import im.vector.app.core.epoxy.ClickListener
|
import im.vector.app.core.epoxy.ClickListener
|
||||||
import im.vector.app.core.epoxy.onClick
|
import im.vector.app.core.epoxy.onClick
|
||||||
import im.vector.app.core.files.LocalFilesHelper
|
import im.vector.app.core.files.LocalFilesHelper
|
||||||
import im.vector.app.core.glide.GlideApp
|
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.helper.ContentUploadStateTrackerBinder
|
||||||
import im.vector.app.features.home.room.detail.timeline.style.TimelineMessageLayout
|
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.home.room.detail.timeline.view.TimelineMessageLayoutRenderer
|
||||||
import im.vector.app.features.media.ImageContentRenderer
|
import im.vector.app.features.media.ImageContentRenderer
|
||||||
|
|
||||||
@EpoxyModelClass(layout = R.layout.item_timeline_event_base)
|
@EpoxyModelClass(layout = R.layout.item_timeline_event_base)
|
||||||
|
@ -56,7 +59,21 @@ abstract class MessageImageVideoItem : AbsMessageItem<MessageImageVideoItem.Hold
|
||||||
|
|
||||||
override fun bind(holder: Holder) {
|
override fun bind(holder: Holder) {
|
||||||
super.bind(holder)
|
super.bind(holder)
|
||||||
imageContentRenderer.render(mediaData, mode, holder.imageView)
|
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)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
RoundedCorners(dimensionConverter.dpToPx(8))
|
||||||
|
}
|
||||||
|
imageContentRenderer.render(mediaData, mode, holder.imageView, imageCornerTransformation)
|
||||||
if (!attributes.informationData.sendState.hasFailed()) {
|
if (!attributes.informationData.sendState.hasFailed()) {
|
||||||
contentUploadStateTrackerBinder.bind(
|
contentUploadStateTrackerBinder.bind(
|
||||||
attributes.informationData.eventId,
|
attributes.informationData.eventId,
|
||||||
|
@ -72,8 +89,7 @@ abstract class MessageImageVideoItem : AbsMessageItem<MessageImageVideoItem.Hold
|
||||||
holder.mediaContentView.onClick(attributes.itemClickListener)
|
holder.mediaContentView.onClick(attributes.itemClickListener)
|
||||||
holder.mediaContentView.setOnLongClickListener(attributes.itemLongClickListener)
|
holder.mediaContentView.setOnLongClickListener(attributes.itemLongClickListener)
|
||||||
holder.playContentView.visibility = if (playable) View.VISIBLE else View.GONE
|
holder.playContentView.visibility = if (playable) View.VISIBLE else View.GONE
|
||||||
(holder.view as? MessageViewConfiguration)?.showTimeAsOverlay = true
|
holder.overlayView.isVisible = messageLayout is TimelineMessageLayout.Bubble
|
||||||
holder.overlayView.isVisible = baseAttributes.informationData.messageLayout is TimelineMessageLayout.Bubble
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun unbind(holder: Holder) {
|
override fun unbind(holder: Holder) {
|
||||||
|
|
|
@ -41,10 +41,11 @@ sealed interface TimelineMessageLayout : Parcelable {
|
||||||
val isIncoming: Boolean,
|
val isIncoming: Boolean,
|
||||||
val isFirstFromThisSender: Boolean,
|
val isFirstFromThisSender: Boolean,
|
||||||
val isLastFromThisSender: Boolean,
|
val isLastFromThisSender: Boolean,
|
||||||
|
val isPseudoBubble: Boolean,
|
||||||
override val layoutRes: Int = if (isIncoming) {
|
override val layoutRes: Int = if (isIncoming) {
|
||||||
R.layout.item_timeline_event_bubble_incoming_base
|
R.layout.item_timeline_event_bubble_incoming_base
|
||||||
} else {
|
} else {
|
||||||
R.layout.item_timeline_event_bubble_outgoing_base
|
R.layout.item_timeline_event_bubble_outgoing_base
|
||||||
},
|
}
|
||||||
) : TimelineMessageLayout
|
) : TimelineMessageLayout
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,15 +33,22 @@ class TimelineMessageLayoutFactory @Inject constructor(private val session: Sess
|
||||||
private val vectorPreferences: VectorPreferences) {
|
private val vectorPreferences: VectorPreferences) {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
// Can't be rendered in bubbles, so get back to default layout
|
||||||
private val EVENT_TYPES_WITH_BUBBLE_LAYOUT = setOf(
|
private val EVENT_TYPES_WITH_BUBBLE_LAYOUT = setOf(
|
||||||
EventType.MESSAGE,
|
EventType.MESSAGE,
|
||||||
EventType.POLL_START,
|
EventType.POLL_START,
|
||||||
EventType.ENCRYPTED,
|
EventType.ENCRYPTED,
|
||||||
EventType.STICKER
|
EventType.STICKER
|
||||||
)
|
)
|
||||||
|
// Can't be rendered in bubbles, so get back to default layout
|
||||||
private val MSG_TYPES_WITHOUT_BUBBLE_LAYOUT = setOf(
|
private val MSG_TYPES_WITHOUT_BUBBLE_LAYOUT = setOf(
|
||||||
MessageType.MSGTYPE_VERIFICATION_REQUEST
|
MessageType.MSGTYPE_VERIFICATION_REQUEST
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Use the bubble layout but without borders
|
||||||
|
private val MSG_TYPES_WITH_PSEUDO_BUBBLE_LAYOUT = setOf(
|
||||||
|
MessageType.MSGTYPE_IMAGE, MessageType.MSGTYPE_VIDEO,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun create(params: TimelineItemFactoryParams): TimelineMessageLayout {
|
fun create(params: TimelineItemFactoryParams): TimelineMessageLayout {
|
||||||
|
@ -86,6 +93,7 @@ class TimelineMessageLayoutFactory @Inject constructor(private val session: Sess
|
||||||
isIncoming = !isSentByMe,
|
isIncoming = !isSentByMe,
|
||||||
isFirstFromThisSender = isFirstFromThisSender,
|
isFirstFromThisSender = isFirstFromThisSender,
|
||||||
isLastFromThisSender = isLastFromThisSender,
|
isLastFromThisSender = isLastFromThisSender,
|
||||||
|
isPseudoBubble = messageContent?.msgType in MSG_TYPES_WITH_PSEUDO_BUBBLE_LAYOUT
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -18,13 +18,12 @@ package im.vector.app.features.home.room.detail.timeline.view
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.res.ColorStateList
|
import android.content.res.ColorStateList
|
||||||
|
import android.graphics.Color
|
||||||
import android.graphics.drawable.Drawable
|
import android.graphics.drawable.Drawable
|
||||||
import android.util.AttributeSet
|
import android.util.AttributeSet
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewOutlineProvider
|
import android.view.ViewOutlineProvider
|
||||||
import android.widget.RelativeLayout
|
import android.widget.RelativeLayout
|
||||||
import android.widget.TextView
|
|
||||||
import androidx.constraintlayout.widget.ConstraintLayout
|
|
||||||
import androidx.constraintlayout.widget.ConstraintSet
|
import androidx.constraintlayout.widget.ConstraintSet
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.core.content.withStyledAttributes
|
import androidx.core.content.withStyledAttributes
|
||||||
|
@ -35,70 +34,38 @@ import com.google.android.material.shape.MaterialShapeDrawable
|
||||||
import com.google.android.material.shape.ShapeAppearanceModel
|
import com.google.android.material.shape.ShapeAppearanceModel
|
||||||
import im.vector.app.R
|
import im.vector.app.R
|
||||||
import im.vector.app.core.utils.DimensionConverter
|
import im.vector.app.core.utils.DimensionConverter
|
||||||
|
import im.vector.app.databinding.ViewMessageBubbleBinding
|
||||||
|
import im.vector.app.features.home.room.detail.timeline.style.TimelineMessageLayout
|
||||||
import im.vector.app.features.themes.ThemeUtils
|
import im.vector.app.features.themes.ThemeUtils
|
||||||
|
import timber.log.Timber
|
||||||
|
|
||||||
class MessageBubbleView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null,
|
class MessageBubbleView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null,
|
||||||
defStyleAttr: Int = 0) :
|
defStyleAttr: Int = 0) :
|
||||||
RelativeLayout(context, attrs, defStyleAttr), MessageViewConfiguration {
|
RelativeLayout(context, attrs, defStyleAttr), TimelineMessageLayoutRenderer {
|
||||||
|
|
||||||
override var isIncoming: Boolean = false
|
private var isIncoming: Boolean = false
|
||||||
set(value) {
|
|
||||||
field = value
|
|
||||||
render()
|
|
||||||
}
|
|
||||||
|
|
||||||
override var isFirstFromSender: Boolean = false
|
private val cornerRadius = resources.getDimensionPixelSize(R.dimen.chat_bubble_corner_radius).toFloat()
|
||||||
set(value) {
|
private val horizontalStubPadding = DimensionConverter(resources).dpToPx(12)
|
||||||
field = value
|
private val verticalStubPadding = DimensionConverter(resources).dpToPx(4)
|
||||||
render()
|
|
||||||
}
|
|
||||||
override var isLastFromSender: Boolean = false
|
|
||||||
set(value) {
|
|
||||||
field = value
|
|
||||||
render()
|
|
||||||
}
|
|
||||||
|
|
||||||
override var showTimeAsOverlay: Boolean = false
|
private lateinit var views: ViewMessageBubbleBinding
|
||||||
set(value) {
|
|
||||||
field = value
|
|
||||||
render()
|
|
||||||
}
|
|
||||||
|
|
||||||
private val cornerRadius = DimensionConverter(resources).dpToPx(12).toFloat()
|
|
||||||
|
|
||||||
init {
|
init {
|
||||||
inflate(context, R.layout.view_message_bubble, this)
|
inflate(context, R.layout.view_message_bubble, this)
|
||||||
context.withStyledAttributes(attrs, R.styleable.MessageBubble) {
|
context.withStyledAttributes(attrs, R.styleable.MessageBubble) {
|
||||||
isIncoming = getBoolean(R.styleable.MessageBubble_incoming_style, false)
|
isIncoming = getBoolean(R.styleable.MessageBubble_incoming_style, false)
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onFinishInflate() {
|
override fun onFinishInflate() {
|
||||||
super.onFinishInflate()
|
super.onFinishInflate()
|
||||||
render()
|
views = ViewMessageBubbleBinding.bind(this)
|
||||||
}
|
|
||||||
|
|
||||||
private fun render() {
|
|
||||||
val currentLayoutDirection = layoutDirection
|
val currentLayoutDirection = layoutDirection
|
||||||
val bubbleView: ConstraintLayout = findViewById(R.id.bubbleView)
|
|
||||||
bubbleView.apply {
|
|
||||||
background = createBackgroundDrawable()
|
|
||||||
outlineProvider = ViewOutlineProvider.BACKGROUND
|
|
||||||
clipToOutline = true
|
|
||||||
}
|
|
||||||
if (isIncoming) {
|
if (isIncoming) {
|
||||||
findViewById<View>(R.id.informationBottom).layoutDirection = currentLayoutDirection
|
views.informationBottom.layoutDirection = currentLayoutDirection
|
||||||
findViewById<View>(R.id.bubbleWrapper).layoutDirection = currentLayoutDirection
|
views.bubbleWrapper.layoutDirection = currentLayoutDirection
|
||||||
bubbleView.layoutDirection = currentLayoutDirection
|
views.bubbleView.layoutDirection = currentLayoutDirection
|
||||||
findViewById<View>(R.id.messageEndGuideline).updateLayoutParams<LayoutParams> {
|
|
||||||
marginEnd = resources.getDimensionPixelSize(R.dimen.chat_bubble_margin_end)
|
|
||||||
}
|
|
||||||
findViewById<View>(R.id.messageStartGuideline).updateLayoutParams<LayoutParams> {
|
|
||||||
marginStart = resources.getDimensionPixelSize(R.dimen.chat_bubble_margin_start)
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
val oppositeLayoutDirection = if (currentLayoutDirection == View.LAYOUT_DIRECTION_LTR) {
|
val oppositeLayoutDirection = if (currentLayoutDirection == View.LAYOUT_DIRECTION_LTR) {
|
||||||
View.LAYOUT_DIRECTION_RTL
|
View.LAYOUT_DIRECTION_RTL
|
||||||
|
@ -106,41 +73,66 @@ class MessageBubbleView @JvmOverloads constructor(context: Context, attrs: Attri
|
||||||
View.LAYOUT_DIRECTION_LTR
|
View.LAYOUT_DIRECTION_LTR
|
||||||
}
|
}
|
||||||
|
|
||||||
findViewById<View>(R.id.informationBottom).layoutDirection = oppositeLayoutDirection
|
views.informationBottom.layoutDirection = oppositeLayoutDirection
|
||||||
findViewById<View>(R.id.bubbleWrapper).layoutDirection = oppositeLayoutDirection
|
views.bubbleWrapper.layoutDirection = oppositeLayoutDirection
|
||||||
bubbleView.layoutDirection = currentLayoutDirection
|
views.bubbleView.layoutDirection = currentLayoutDirection
|
||||||
findViewById<View>(R.id.messageEndGuideline).updateLayoutParams<LayoutParams> {
|
|
||||||
marginEnd = resources.getDimensionPixelSize(R.dimen.chat_bubble_margin_start)
|
|
||||||
}
|
|
||||||
findViewById<View>(R.id.messageStartGuideline).updateLayoutParams<LayoutParams> {
|
|
||||||
marginStart = resources.getDimensionPixelSize(R.dimen.chat_bubble_margin_end)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ConstraintSet().apply {
|
|
||||||
clone(bubbleView)
|
|
||||||
clear(R.id.viewStubContainer, ConstraintSet.END)
|
|
||||||
if (showTimeAsOverlay) {
|
|
||||||
val timeColor = ContextCompat.getColor(context, R.color.palette_white)
|
|
||||||
findViewById<TextView>(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<TextView>(R.id.messageTimeView).setTextColor(timeColor)
|
|
||||||
connect(R.id.viewStubContainer, ConstraintSet.END, R.id.messageTimeView, ConstraintSet.START, 0)
|
|
||||||
}
|
|
||||||
applyTo(bubbleView)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun createBackgroundDrawable(): Drawable {
|
override fun render(messageLayout: TimelineMessageLayout) {
|
||||||
val (topCornerFamily, topRadius) = if (isFirstFromSender) {
|
if (messageLayout !is TimelineMessageLayout.Bubble) {
|
||||||
|
Timber.v("Can't render messageLayout $messageLayout")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
views.bubbleView.apply {
|
||||||
|
background = createBackgroundDrawable(messageLayout)
|
||||||
|
outlineProvider = ViewOutlineProvider.BACKGROUND
|
||||||
|
clipToOutline = true
|
||||||
|
}
|
||||||
|
ConstraintSet().apply {
|
||||||
|
clone(views.bubbleView)
|
||||||
|
clear(R.id.viewStubContainer, ConstraintSet.END)
|
||||||
|
val showTimeAsOverlay = messageLayout.isPseudoBubble
|
||||||
|
if (showTimeAsOverlay) {
|
||||||
|
val timeColor = ContextCompat.getColor(context, R.color.palette_white)
|
||||||
|
views.messageTimeView.setTextColor(timeColor)
|
||||||
|
connect(R.id.viewStubContainer, ConstraintSet.END, R.id.parent, ConstraintSet.END, 0)
|
||||||
|
} else {
|
||||||
|
val timeColor = ThemeUtils.getColor(context, R.attr.vctr_content_tertiary)
|
||||||
|
views.messageTimeView.setTextColor(timeColor)
|
||||||
|
connect(R.id.viewStubContainer, ConstraintSet.END, R.id.messageTimeView, ConstraintSet.START, 0)
|
||||||
|
}
|
||||||
|
applyTo(views.bubbleView)
|
||||||
|
}
|
||||||
|
if (messageLayout.isPseudoBubble) {
|
||||||
|
views.viewStubContainer.root.setPadding(0, 0, 0, 0)
|
||||||
|
} else {
|
||||||
|
views.viewStubContainer.root.setPadding(horizontalStubPadding, verticalStubPadding, horizontalStubPadding, verticalStubPadding)
|
||||||
|
}
|
||||||
|
if (messageLayout.isIncoming) {
|
||||||
|
views.messageEndGuideline.updateLayoutParams<LayoutParams> {
|
||||||
|
marginEnd = resources.getDimensionPixelSize(R.dimen.chat_bubble_margin_end)
|
||||||
|
}
|
||||||
|
views.messageStartGuideline.updateLayoutParams<LayoutParams> {
|
||||||
|
marginStart = resources.getDimensionPixelSize(R.dimen.chat_bubble_margin_start)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
views.messageEndGuideline.updateLayoutParams<LayoutParams> {
|
||||||
|
marginEnd = resources.getDimensionPixelSize(R.dimen.chat_bubble_margin_start)
|
||||||
|
}
|
||||||
|
views.messageStartGuideline.updateLayoutParams<LayoutParams> {
|
||||||
|
marginStart = resources.getDimensionPixelSize(R.dimen.chat_bubble_margin_end)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createBackgroundDrawable(messageLayout: TimelineMessageLayout.Bubble): Drawable {
|
||||||
|
val (topCornerFamily, topRadius) = if (messageLayout.isFirstFromThisSender) {
|
||||||
Pair(CornerFamily.ROUNDED, cornerRadius)
|
Pair(CornerFamily.ROUNDED, cornerRadius)
|
||||||
} else {
|
} else {
|
||||||
Pair(CornerFamily.CUT, 0f)
|
Pair(CornerFamily.CUT, 0f)
|
||||||
}
|
}
|
||||||
val (bottomCornerFamily, bottomRadius) = if (isLastFromSender) {
|
val (bottomCornerFamily, bottomRadius) = if (messageLayout.isLastFromThisSender) {
|
||||||
Pair(CornerFamily.ROUNDED, cornerRadius)
|
Pair(CornerFamily.ROUNDED, cornerRadius)
|
||||||
} else {
|
} else {
|
||||||
Pair(CornerFamily.CUT, 0f)
|
Pair(CornerFamily.CUT, 0f)
|
||||||
|
@ -166,7 +158,11 @@ class MessageBubbleView @JvmOverloads constructor(context: Context, attrs: Attri
|
||||||
}
|
}
|
||||||
val shapeAppearanceModel = shapeAppearanceModelBuilder.build()
|
val shapeAppearanceModel = shapeAppearanceModelBuilder.build()
|
||||||
return MaterialShapeDrawable(shapeAppearanceModel).apply {
|
return MaterialShapeDrawable(shapeAppearanceModel).apply {
|
||||||
fillColor = ColorStateList.valueOf(backgroundColor)
|
fillColor = if (messageLayout.isPseudoBubble) {
|
||||||
|
ColorStateList.valueOf(Color.TRANSPARENT)
|
||||||
|
} else {
|
||||||
|
ColorStateList.valueOf(backgroundColor)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,11 +16,8 @@
|
||||||
|
|
||||||
package im.vector.app.features.home.room.detail.timeline.view
|
package im.vector.app.features.home.room.detail.timeline.view
|
||||||
|
|
||||||
interface MessageViewConfiguration {
|
import im.vector.app.features.home.room.detail.timeline.style.TimelineMessageLayout
|
||||||
var isIncoming: Boolean
|
|
||||||
var isFirstFromSender: Boolean
|
interface TimelineMessageLayoutRenderer {
|
||||||
var isLastFromSender: Boolean
|
fun render(messageLayout: TimelineMessageLayout)
|
||||||
var showTimeAsOverlay: Boolean
|
|
||||||
var showNoBubble: Boolean
|
|
||||||
fun render()
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
|
|
||||||
package im.vector.app.features.media
|
package im.vector.app.features.media
|
||||||
|
|
||||||
|
import android.graphics.Bitmap
|
||||||
import android.graphics.drawable.Drawable
|
import android.graphics.drawable.Drawable
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Parcelable
|
import android.os.Parcelable
|
||||||
|
@ -23,6 +24,7 @@ import android.view.View
|
||||||
import android.widget.ImageView
|
import android.widget.ImageView
|
||||||
import androidx.core.view.updateLayoutParams
|
import androidx.core.view.updateLayoutParams
|
||||||
import com.bumptech.glide.load.DataSource
|
import com.bumptech.glide.load.DataSource
|
||||||
|
import com.bumptech.glide.load.Transformation
|
||||||
import com.bumptech.glide.load.engine.DiskCacheStrategy
|
import com.bumptech.glide.load.engine.DiskCacheStrategy
|
||||||
import com.bumptech.glide.load.engine.GlideException
|
import com.bumptech.glide.load.engine.GlideException
|
||||||
import com.bumptech.glide.load.resource.bitmap.RoundedCorners
|
import com.bumptech.glide.load.resource.bitmap.RoundedCorners
|
||||||
|
@ -109,7 +111,7 @@ class ImageContentRenderer @Inject constructor(private val localFilesHelper: Loc
|
||||||
.into(imageView)
|
.into(imageView)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun render(data: Data, mode: Mode, imageView: ImageView) {
|
fun render(data: Data, mode: Mode, imageView: ImageView, cornerTransformation: Transformation<Bitmap> = RoundedCorners(dimensionConverter.dpToPx(8))) {
|
||||||
val size = processSize(data, mode)
|
val size = processSize(data, mode)
|
||||||
imageView.updateLayoutParams {
|
imageView.updateLayoutParams {
|
||||||
width = size.width
|
width = size.width
|
||||||
|
@ -120,7 +122,7 @@ class ImageContentRenderer @Inject constructor(private val localFilesHelper: Loc
|
||||||
|
|
||||||
createGlideRequest(data, mode, imageView, size)
|
createGlideRequest(data, mode, imageView, size)
|
||||||
.dontAnimate()
|
.dontAnimate()
|
||||||
.transform(RoundedCorners(dimensionConverter.dpToPx(8)))
|
.transform(cornerTransformation)
|
||||||
// .thumbnail(0.3f)
|
// .thumbnail(0.3f)
|
||||||
.into(imageView)
|
.into(imageView)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<corners android:radius="@dimen/chat_bubble_corner_radius"/>
|
||||||
<gradient
|
<gradient
|
||||||
android:type="linear"
|
android:type="linear"
|
||||||
android:angle="270"
|
android:angle="270"
|
||||||
|
|
|
@ -3,18 +3,24 @@
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:addStatesFromChildren="true">
|
android:addStatesFromChildren="true"
|
||||||
|
android:paddingStart="8dp"
|
||||||
|
android:paddingLeft="8dp"
|
||||||
|
android:paddingTop="4dp"
|
||||||
|
android:paddingEnd="8dp"
|
||||||
|
android:paddingRight="8dp"
|
||||||
|
android:paddingBottom="4dp">
|
||||||
|
|
||||||
<ViewStub
|
<ViewStub
|
||||||
android:id="@+id/messageContentTextStub"
|
android:id="@+id/messageContentTextStub"
|
||||||
style="@style/TimelineContentStubBaseParams"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout="@layout/item_timeline_event_text_message_stub"
|
android:layout="@layout/item_timeline_event_text_message_stub"
|
||||||
tools:visibility="visible" />
|
tools:visibility="visible" />
|
||||||
|
|
||||||
<ViewStub
|
<ViewStub
|
||||||
android:id="@+id/messageContentCodeBlockStub"
|
android:id="@+id/messageContentCodeBlockStub"
|
||||||
style="@style/TimelineContentStubBaseParams"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout="@layout/item_timeline_event_code_block_stub"
|
android:layout="@layout/item_timeline_event_code_block_stub"
|
||||||
tools:visibility="visible" />
|
tools:visibility="visible" />
|
||||||
|
@ -22,33 +28,34 @@
|
||||||
<ViewStub
|
<ViewStub
|
||||||
android:id="@+id/messageContentMediaStub"
|
android:id="@+id/messageContentMediaStub"
|
||||||
style="@style/TimelineContentStubBaseParams"
|
style="@style/TimelineContentStubBaseParams"
|
||||||
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:inflatedId="@+id/messageContentMedia"
|
android:inflatedId="@+id/messageContentMedia"
|
||||||
android:layout="@layout/item_timeline_event_media_message_stub" />
|
android:layout="@layout/item_timeline_event_media_message_stub" />
|
||||||
|
|
||||||
<ViewStub
|
<ViewStub
|
||||||
android:id="@+id/messageContentFileStub"
|
android:id="@+id/messageContentFileStub"
|
||||||
style="@style/TimelineContentStubBaseParams"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
tools:visibility="gone"
|
android:layout="@layout/item_timeline_event_file_stub"
|
||||||
android:layout="@layout/item_timeline_event_file_stub" />
|
tools:visibility="gone" />
|
||||||
|
|
||||||
<ViewStub
|
<ViewStub
|
||||||
android:id="@+id/messageContentRedactedStub"
|
android:id="@+id/messageContentRedactedStub"
|
||||||
style="@style/TimelineContentStubBaseParams"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout="@layout/item_timeline_event_redacted_stub" />
|
android:layout="@layout/item_timeline_event_redacted_stub" />
|
||||||
|
|
||||||
<ViewStub
|
<ViewStub
|
||||||
android:id="@+id/messageContentVoiceStub"
|
android:id="@+id/messageContentVoiceStub"
|
||||||
style="@style/TimelineContentStubBaseParams"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout="@layout/item_timeline_event_voice_stub"
|
android:layout="@layout/item_timeline_event_voice_stub"
|
||||||
tools:visibility="gone" />
|
tools:visibility="gone" />
|
||||||
|
|
||||||
<ViewStub
|
<ViewStub
|
||||||
android:id="@+id/messageContentPollStub"
|
android:id="@+id/messageContentPollStub"
|
||||||
style="@style/TimelineContentStubBaseParams"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout="@layout/item_timeline_event_poll" />
|
android:layout="@layout/item_timeline_event_poll" />
|
||||||
|
|
||||||
|
|
|
@ -4,8 +4,7 @@
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
tools:parentTag="android.widget.RelativeLayout"
|
tools:parentTag="android.widget.RelativeLayout">
|
||||||
tools:viewBindingIgnore="true">
|
|
||||||
|
|
||||||
<im.vector.app.core.platform.CheckableView
|
<im.vector.app.core.platform.CheckableView
|
||||||
android:id="@+id/messageSelectedBackground"
|
android:id="@+id/messageSelectedBackground"
|
||||||
|
@ -91,7 +90,6 @@
|
||||||
android:layout_marginStart="0dp"
|
android:layout_marginStart="0dp"
|
||||||
android:layout_marginEnd="0dp"
|
android:layout_marginEnd="0dp"
|
||||||
android:addStatesFromChildren="true"
|
android:addStatesFromChildren="true"
|
||||||
android:paddingStart="4dp"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toTopOf="parent">
|
app:layout_constraintTop_toTopOf="parent">
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue