mirror of
https://github.com/SchildiChat/SchildiChat-android.git
synced 2024-11-22 09:25:49 +03:00
Bubbles: still R&D. Not sure how to handle every event types.
This commit is contained in:
parent
bde1df0322
commit
ad63d3de1c
11 changed files with 121 additions and 25 deletions
|
@ -3,6 +3,7 @@
|
|||
|
||||
<declare-styleable name="MessageBubble">
|
||||
<attr name="incoming_style" format="boolean" />
|
||||
<attr name="show_background" format="boolean" />
|
||||
<attr name="is_first" format="boolean" />
|
||||
<attr name="is_last" format="boolean" />
|
||||
</declare-styleable>
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
)
|
||||
|
|
|
@ -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<H : AbsBaseMessageItem.Holder> : 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) {
|
||||
|
|
|
@ -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<MessageImageVideoItem.Hold
|
|||
holder.mediaContentView.onClick(attributes.itemClickListener)
|
||||
holder.mediaContentView.setOnLongClickListener(attributes.itemLongClickListener)
|
||||
holder.playContentView.visibility = if (playable) View.VISIBLE else View.GONE
|
||||
(holder.view as? MessageViewConfiguration)?.displayBorder = false
|
||||
}
|
||||
|
||||
override fun unbind(holder: Holder) {
|
||||
|
|
|
@ -42,7 +42,9 @@ data class MessageInformationData(
|
|||
val referencesInfoData: ReferencesInfoData? = null,
|
||||
val sentByMe: Boolean,
|
||||
val e2eDecoration: E2EDecoration = E2EDecoration.NONE,
|
||||
val sendStateDecoration: SendStateDecoration = SendStateDecoration.NONE
|
||||
val sendStateDecoration: SendStateDecoration = SendStateDecoration.NONE,
|
||||
val isFirstFromThisSender: Boolean = false,
|
||||
val isLastFromThisSender: Boolean = false
|
||||
) : Parcelable {
|
||||
|
||||
val matrixItem: MatrixItem
|
||||
|
|
|
@ -20,9 +20,10 @@ 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.constraintlayout.widget.ConstraintLayout
|
||||
import androidx.constraintlayout.widget.ConstraintSet
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.content.withStyledAttributes
|
||||
import androidx.core.view.updateLayoutParams
|
||||
|
@ -33,34 +34,61 @@ 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) {
|
||||
defStyleAttr: Int = 0)
|
||||
: RelativeLayout(context, attrs, defStyleAttr), MessageViewConfiguration {
|
||||
|
||||
var incoming: Boolean = false
|
||||
var isFirst: Boolean = false
|
||||
var isLast: Boolean = false
|
||||
var cornerRadius = DimensionConverter(resources).dpToPx(12).toFloat()
|
||||
override var isIncoming: Boolean = false
|
||||
set(value) {
|
||||
field = value
|
||||
render()
|
||||
}
|
||||
|
||||
override var isFirstFromSender: Boolean = false
|
||||
set(value) {
|
||||
field = value
|
||||
render()
|
||||
}
|
||||
override var isLastFromSender: Boolean = false
|
||||
set(value) {
|
||||
field = value
|
||||
render()
|
||||
}
|
||||
|
||||
override var displayBorder: Boolean = true
|
||||
set(value) {
|
||||
field = value
|
||||
render()
|
||||
}
|
||||
|
||||
private val 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)
|
||||
isIncoming = getBoolean(R.styleable.MessageBubble_incoming_style, false)
|
||||
displayBorder = getBoolean(R.styleable.MessageBubble_show_background, true)
|
||||
isFirstFromSender = getBoolean(R.styleable.MessageBubble_is_first, false)
|
||||
isLastFromSender = getBoolean(R.styleable.MessageBubble_is_last, false)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onFinishInflate() {
|
||||
super.onFinishInflate()
|
||||
render()
|
||||
}
|
||||
|
||||
private fun render() {
|
||||
val currentLayoutDirection = layoutDirection
|
||||
findViewById<ViewGroup>(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<View>(R.id.informationBottom).layoutDirection = currentLayoutDirection
|
||||
findViewById<View>(R.id.bubbleWrapper).layoutDirection = currentLayoutDirection
|
||||
findViewById<View>(R.id.bubbleView).layoutDirection = currentLayoutDirection
|
||||
bubbleView.layoutDirection = currentLayoutDirection
|
||||
findViewById<View>(R.id.messageEndGuideline).updateLayoutParams<LayoutParams> {
|
||||
marginEnd = resources.getDimensionPixelSize(R.dimen.chat_bubble_margin_end)
|
||||
}
|
||||
|
@ -73,21 +101,37 @@ class MessageBubbleView @JvmOverloads constructor(context: Context, attrs: Attri
|
|||
|
||||
findViewById<View>(R.id.informationBottom).layoutDirection = oppositeLayoutDirection
|
||||
findViewById<View>(R.id.bubbleWrapper).layoutDirection = oppositeLayoutDirection
|
||||
findViewById<View>(R.id.bubbleView).layoutDirection = currentLayoutDirection
|
||||
bubbleView.layoutDirection = currentLayoutDirection
|
||||
findViewById<View>(R.id.messageEndGuideline).updateLayoutParams<LayoutParams> {
|
||||
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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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">
|
||||
|
||||
<ImageView
|
||||
|
|
|
@ -103,7 +103,7 @@
|
|||
android:layout_height="wrap_content"
|
||||
android:addStatesFromChildren="true"
|
||||
app:layout_constrainedWidth="true"
|
||||
app:layout_constraintEnd_toStartOf="@id/messageTimeView"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintWidth_max="300dp" />
|
||||
|
|
Loading…
Reference in a new issue