forked from GitHub-Mirror/riotX-android
Read receipts: create custom view to use it wherever we want easily
This commit is contained in:
parent
825463d9cd
commit
1dbb02a80d
@ -0,0 +1,77 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019 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.riotx.core.ui.views
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.util.AttributeSet
|
||||||
|
import android.widget.ImageView
|
||||||
|
import android.widget.LinearLayout
|
||||||
|
import androidx.core.view.isVisible
|
||||||
|
import butterknife.ButterKnife
|
||||||
|
import im.vector.riotx.R
|
||||||
|
import im.vector.riotx.features.home.AvatarRenderer
|
||||||
|
import im.vector.riotx.features.home.room.detail.timeline.item.ReadReceiptData
|
||||||
|
import kotlinx.android.synthetic.main.view_read_receipts.view.*
|
||||||
|
|
||||||
|
private const val MAX_RECEIPT_DISPLAYED = 5
|
||||||
|
|
||||||
|
class ReadReceiptsView @JvmOverloads constructor(
|
||||||
|
context: Context,
|
||||||
|
attrs: AttributeSet? = null,
|
||||||
|
defStyleAttr: Int = 0
|
||||||
|
) : LinearLayout(context, attrs, defStyleAttr) {
|
||||||
|
|
||||||
|
private val receiptAvatars: List<ImageView> by lazy {
|
||||||
|
listOf(receiptAvatar1, receiptAvatar2, receiptAvatar3, receiptAvatar4, receiptAvatar5)
|
||||||
|
}
|
||||||
|
|
||||||
|
init {
|
||||||
|
setupView()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setupView() {
|
||||||
|
inflate(context, R.layout.view_read_receipts, this)
|
||||||
|
ButterKnife.bind(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun render(readReceipts: List<ReadReceiptData>, avatarRenderer: AvatarRenderer) {
|
||||||
|
if (readReceipts.isNotEmpty()) {
|
||||||
|
isVisible = true
|
||||||
|
for (index in 0 until MAX_RECEIPT_DISPLAYED) {
|
||||||
|
val receiptData = readReceipts.getOrNull(index)
|
||||||
|
if (receiptData == null) {
|
||||||
|
receiptAvatars[index].isVisible = false
|
||||||
|
} else {
|
||||||
|
receiptAvatars[index].isVisible = true
|
||||||
|
avatarRenderer.render(receiptData.avatarUrl, receiptData.userId, receiptData.displayName, receiptAvatars[index])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (readReceipts.size > MAX_RECEIPT_DISPLAYED) {
|
||||||
|
receiptMore.isVisible = true
|
||||||
|
receiptMore.text = context.getString(
|
||||||
|
R.string.x_plus, readReceipts.size - MAX_RECEIPT_DISPLAYED
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
receiptMore.isVisible = false
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
isVisible = false
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -25,23 +25,18 @@ import im.vector.riotx.features.home.room.detail.timeline.helper.senderName
|
|||||||
import im.vector.riotx.features.home.room.detail.timeline.item.MessageInformationData
|
import im.vector.riotx.features.home.room.detail.timeline.item.MessageInformationData
|
||||||
import im.vector.riotx.features.home.room.detail.timeline.item.NoticeItem
|
import im.vector.riotx.features.home.room.detail.timeline.item.NoticeItem
|
||||||
import im.vector.riotx.features.home.room.detail.timeline.item.NoticeItem_
|
import im.vector.riotx.features.home.room.detail.timeline.item.NoticeItem_
|
||||||
|
import im.vector.riotx.features.home.room.detail.timeline.util.MessageInformationDataFactory
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class NoticeItemFactory @Inject constructor(private val eventFormatter: NoticeEventFormatter,
|
class NoticeItemFactory @Inject constructor(private val eventFormatter: NoticeEventFormatter,
|
||||||
private val avatarRenderer: AvatarRenderer) {
|
private val avatarRenderer: AvatarRenderer,
|
||||||
|
private val informationDataFactory: MessageInformationDataFactory) {
|
||||||
|
|
||||||
fun create(event: TimelineEvent,
|
fun create(event: TimelineEvent,
|
||||||
highlight: Boolean,
|
highlight: Boolean,
|
||||||
callback: TimelineEventController.Callback?): NoticeItem? {
|
callback: TimelineEventController.Callback?): NoticeItem? {
|
||||||
val formattedText = eventFormatter.format(event) ?: return null
|
val formattedText = eventFormatter.format(event) ?: return null
|
||||||
val informationData = MessageInformationData(
|
val informationData = informationDataFactory.create(event, null)
|
||||||
eventId = event.root.eventId ?: "?",
|
|
||||||
senderId = event.root.senderId ?: "",
|
|
||||||
sendState = event.root.sendState,
|
|
||||||
avatarUrl = event.senderAvatar(),
|
|
||||||
memberName = event.senderName(),
|
|
||||||
showInformation = false
|
|
||||||
)
|
|
||||||
|
|
||||||
return NoticeItem_()
|
return NoticeItem_()
|
||||||
.avatarRenderer(avatarRenderer)
|
.avatarRenderer(avatarRenderer)
|
||||||
|
@ -33,6 +33,7 @@ import com.airbnb.epoxy.EpoxyAttribute
|
|||||||
import im.vector.matrix.android.api.session.room.send.SendState
|
import im.vector.matrix.android.api.session.room.send.SendState
|
||||||
import im.vector.riotx.R
|
import im.vector.riotx.R
|
||||||
import im.vector.riotx.core.resources.ColorProvider
|
import im.vector.riotx.core.resources.ColorProvider
|
||||||
|
import im.vector.riotx.core.ui.views.ReadReceiptsView
|
||||||
import im.vector.riotx.core.utils.DebouncedClickListener
|
import im.vector.riotx.core.utils.DebouncedClickListener
|
||||||
import im.vector.riotx.core.utils.DimensionUtils.dpToPx
|
import im.vector.riotx.core.utils.DimensionUtils.dpToPx
|
||||||
import im.vector.riotx.features.home.AvatarRenderer
|
import im.vector.riotx.features.home.AvatarRenderer
|
||||||
@ -40,8 +41,6 @@ import im.vector.riotx.features.home.room.detail.timeline.TimelineEventControlle
|
|||||||
import im.vector.riotx.features.reactions.widget.ReactionButton
|
import im.vector.riotx.features.reactions.widget.ReactionButton
|
||||||
import im.vector.riotx.features.ui.getMessageTextColor
|
import im.vector.riotx.features.ui.getMessageTextColor
|
||||||
|
|
||||||
private const val MAX_RECEIPT_DISPLAYED = 5
|
|
||||||
|
|
||||||
abstract class AbsMessageItem<H : AbsMessageItem.Holder> : BaseEventItem<H>() {
|
abstract class AbsMessageItem<H : AbsMessageItem.Holder> : BaseEventItem<H>() {
|
||||||
|
|
||||||
@EpoxyAttribute
|
@EpoxyAttribute
|
||||||
@ -125,28 +124,7 @@ abstract class AbsMessageItem<H : AbsMessageItem.Holder> : BaseEventItem<H>() {
|
|||||||
holder.memberNameView.setOnLongClickListener(null)
|
holder.memberNameView.setOnLongClickListener(null)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (informationData.readReceipts.isNotEmpty()) {
|
holder.readReceiptsView.render(informationData.readReceipts, avatarRenderer)
|
||||||
holder.readReceiptsView.isVisible = true
|
|
||||||
for (index in 0 until MAX_RECEIPT_DISPLAYED) {
|
|
||||||
val receiptData = informationData.readReceipts.getOrNull(index)
|
|
||||||
if (receiptData == null) {
|
|
||||||
holder.receiptAvatars[index].isVisible = false
|
|
||||||
} else {
|
|
||||||
holder.receiptAvatars[index].isVisible = true
|
|
||||||
avatarRenderer.render(receiptData.avatarUrl, receiptData.userId, receiptData.displayName, holder.receiptAvatars[index])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (informationData.readReceipts.size > MAX_RECEIPT_DISPLAYED) {
|
|
||||||
holder.receiptMoreView.isVisible = true
|
|
||||||
holder.receiptMoreView.text = holder.view.context.getString(
|
|
||||||
R.string.x_plus, informationData.readReceipts.size - MAX_RECEIPT_DISPLAYED
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
holder.receiptMoreView.isVisible = false
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
holder.readReceiptsView.isVisible = false
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!shouldShowReactionAtBottom() || informationData.orderedReactionList.isNullOrEmpty()) {
|
if (!shouldShowReactionAtBottom() || informationData.orderedReactionList.isNullOrEmpty()) {
|
||||||
holder.reactionWrapper?.isVisible = false
|
holder.reactionWrapper?.isVisible = false
|
||||||
@ -198,17 +176,7 @@ abstract class AbsMessageItem<H : AbsMessageItem.Holder> : BaseEventItem<H>() {
|
|||||||
val avatarImageView by bind<ImageView>(R.id.messageAvatarImageView)
|
val avatarImageView by bind<ImageView>(R.id.messageAvatarImageView)
|
||||||
val memberNameView by bind<TextView>(R.id.messageMemberNameView)
|
val memberNameView by bind<TextView>(R.id.messageMemberNameView)
|
||||||
val timeView by bind<TextView>(R.id.messageTimeView)
|
val timeView by bind<TextView>(R.id.messageTimeView)
|
||||||
val readReceiptsView by bind<ViewGroup>(R.id.readReceiptsView)
|
val readReceiptsView by bind<ReadReceiptsView>(R.id.readReceiptsView)
|
||||||
val receiptAvatar1 by bind<ImageView>(R.id.message_avatar_receipt_1)
|
|
||||||
val receiptAvatar2 by bind<ImageView>(R.id.message_avatar_receipt_2)
|
|
||||||
val receiptAvatar3 by bind<ImageView>(R.id.message_avatar_receipt_3)
|
|
||||||
val receiptAvatar4 by bind<ImageView>(R.id.message_avatar_receipt_4)
|
|
||||||
val receiptAvatar5 by bind<ImageView>(R.id.message_avatar_receipt_5)
|
|
||||||
val receiptMoreView by bind<TextView>(R.id.message_more_than_expected)
|
|
||||||
val receiptAvatars: List<ImageView> by lazy {
|
|
||||||
listOf(receiptAvatar1, receiptAvatar2, receiptAvatar3, receiptAvatar4, receiptAvatar5)
|
|
||||||
}
|
|
||||||
|
|
||||||
var reactionWrapper: ViewGroup? = null
|
var reactionWrapper: ViewGroup? = null
|
||||||
var reactionFlowHelper: Flow? = null
|
var reactionFlowHelper: Flow? = null
|
||||||
}
|
}
|
||||||
|
@ -22,6 +22,7 @@ import android.widget.TextView
|
|||||||
import com.airbnb.epoxy.EpoxyAttribute
|
import com.airbnb.epoxy.EpoxyAttribute
|
||||||
import com.airbnb.epoxy.EpoxyModelClass
|
import com.airbnb.epoxy.EpoxyModelClass
|
||||||
import im.vector.riotx.R
|
import im.vector.riotx.R
|
||||||
|
import im.vector.riotx.core.ui.views.ReadReceiptsView
|
||||||
import im.vector.riotx.features.home.AvatarRenderer
|
import im.vector.riotx.features.home.AvatarRenderer
|
||||||
import im.vector.riotx.features.home.room.detail.timeline.TimelineEventController
|
import im.vector.riotx.features.home.room.detail.timeline.TimelineEventController
|
||||||
|
|
||||||
@ -55,6 +56,7 @@ abstract class NoticeItem : BaseEventItem<NoticeItem.Holder>() {
|
|||||||
holder.avatarImageView
|
holder.avatarImageView
|
||||||
)
|
)
|
||||||
holder.view.setOnLongClickListener(longClickListener)
|
holder.view.setOnLongClickListener(longClickListener)
|
||||||
|
holder.readReceiptsView.render(informationData.readReceipts, avatarRenderer)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getViewType() = STUB_ID
|
override fun getViewType() = STUB_ID
|
||||||
@ -62,6 +64,7 @@ abstract class NoticeItem : BaseEventItem<NoticeItem.Holder>() {
|
|||||||
class Holder : BaseHolder(STUB_ID) {
|
class Holder : BaseHolder(STUB_ID) {
|
||||||
val avatarImageView by bind<ImageView>(R.id.itemNoticeAvatarView)
|
val avatarImageView by bind<ImageView>(R.id.itemNoticeAvatarView)
|
||||||
val noticeTextView by bind<TextView>(R.id.itemNoticeTextView)
|
val noticeTextView by bind<TextView>(R.id.itemNoticeTextView)
|
||||||
|
val readReceiptsView by bind<ReadReceiptsView>(R.id.readReceiptsView)
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
@ -123,65 +123,14 @@
|
|||||||
</ViewStub>
|
</ViewStub>
|
||||||
|
|
||||||
|
|
||||||
<LinearLayout
|
<im.vector.riotx.core.ui.views.ReadReceiptsView
|
||||||
android:id="@+id/readReceiptsView"
|
android:id="@+id/readReceiptsView"
|
||||||
android:layout_width="0dp"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginEnd="8dp"
|
android:layout_marginEnd="8dp"
|
||||||
android:orientation="horizontal"
|
android:layout_marginBottom="4dp"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
app:layout_constraintEnd_toEndOf="parent">
|
app:layout_constraintEnd_toEndOf="parent" />
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/message_more_than_expected"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:layout_marginEnd="8dp"
|
|
||||||
android:gravity="center"
|
|
||||||
android:textSize="12sp"
|
|
||||||
tools:text="999+" />
|
|
||||||
|
|
||||||
<ImageView
|
|
||||||
android:id="@+id/message_avatar_receipt_5"
|
|
||||||
android:layout_width="16dp"
|
|
||||||
android:layout_height="16dp"
|
|
||||||
android:adjustViewBounds="true"
|
|
||||||
android:scaleType="centerCrop"
|
|
||||||
tools:src="@tools:sample/avatars" />
|
|
||||||
|
|
||||||
<ImageView
|
|
||||||
android:id="@+id/message_avatar_receipt_4"
|
|
||||||
android:layout_width="16dp"
|
|
||||||
android:layout_height="16dp"
|
|
||||||
android:adjustViewBounds="true"
|
|
||||||
android:scaleType="centerCrop"
|
|
||||||
tools:src="@tools:sample/avatars" />
|
|
||||||
|
|
||||||
<ImageView
|
|
||||||
android:id="@+id/message_avatar_receipt_3"
|
|
||||||
android:layout_width="16dp"
|
|
||||||
android:layout_height="16dp"
|
|
||||||
android:adjustViewBounds="true"
|
|
||||||
android:scaleType="centerCrop"
|
|
||||||
tools:src="@tools:sample/avatars" />
|
|
||||||
|
|
||||||
<ImageView
|
|
||||||
android:id="@+id/message_avatar_receipt_2"
|
|
||||||
android:layout_width="16dp"
|
|
||||||
android:layout_height="16dp"
|
|
||||||
android:adjustViewBounds="true"
|
|
||||||
android:scaleType="centerCrop"
|
|
||||||
tools:src="@tools:sample/avatars" />
|
|
||||||
|
|
||||||
<ImageView
|
|
||||||
android:id="@+id/message_avatar_receipt_1"
|
|
||||||
android:layout_width="16dp"
|
|
||||||
android:layout_height="16dp"
|
|
||||||
android:adjustViewBounds="true"
|
|
||||||
android:scaleType="centerCrop"
|
|
||||||
tools:src="@tools:sample/avatars" />
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
@ -52,5 +52,14 @@
|
|||||||
android:layout="@layout/item_timeline_event_merged_header_stub"
|
android:layout="@layout/item_timeline_event_merged_header_stub"
|
||||||
tools:ignore="MissingConstraints" />
|
tools:ignore="MissingConstraints" />
|
||||||
|
|
||||||
|
<im.vector.riotx.core.ui.views.ReadReceiptsView
|
||||||
|
android:id="@+id/readReceiptsView"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginEnd="8dp"
|
||||||
|
android:layout_marginBottom="4dp"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent" />
|
||||||
|
|
||||||
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
@ -1,11 +1,58 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
|
||||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<merge xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:id="@+id/readReceiptsView"
|
android:layout_width="wrap_content"
|
||||||
android:layout_width="match_parent"
|
android:layout_height="wrap_content"
|
||||||
android:layout_height="wrap_content">
|
tools:parentTag="android.widget.LinearLayout">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/receiptMore"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_marginEnd="8dp"
|
||||||
|
android:gravity="center"
|
||||||
|
android:textSize="12sp"
|
||||||
|
tools:text="999+" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/receiptAvatar5"
|
||||||
|
android:layout_width="16dp"
|
||||||
|
android:layout_height="16dp"
|
||||||
|
android:adjustViewBounds="true"
|
||||||
|
android:scaleType="centerCrop"
|
||||||
|
tools:src="@tools:sample/avatars" />
|
||||||
|
|
||||||
</RelativeLayout>
|
<ImageView
|
||||||
|
android:id="@+id/receiptAvatar4"
|
||||||
|
android:layout_width="16dp"
|
||||||
|
android:layout_height="16dp"
|
||||||
|
android:adjustViewBounds="true"
|
||||||
|
android:scaleType="centerCrop"
|
||||||
|
tools:src="@tools:sample/avatars" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/receiptAvatar3"
|
||||||
|
android:layout_width="16dp"
|
||||||
|
android:layout_height="16dp"
|
||||||
|
android:adjustViewBounds="true"
|
||||||
|
android:scaleType="centerCrop"
|
||||||
|
tools:src="@tools:sample/avatars" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/receiptAvatar2"
|
||||||
|
android:layout_width="16dp"
|
||||||
|
android:layout_height="16dp"
|
||||||
|
android:adjustViewBounds="true"
|
||||||
|
android:scaleType="centerCrop"
|
||||||
|
tools:src="@tools:sample/avatars" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/receiptAvatar1"
|
||||||
|
android:layout_width="16dp"
|
||||||
|
android:layout_height="16dp"
|
||||||
|
android:adjustViewBounds="true"
|
||||||
|
android:scaleType="centerCrop"
|
||||||
|
tools:src="@tools:sample/avatars" />
|
||||||
|
|
||||||
|
</merge>
|
||||||
|
@ -297,6 +297,7 @@
|
|||||||
|
|
||||||
<style name="TimelineContentStubNoInfoLayoutParams" parent="TimelineContentStubBaseParams">
|
<style name="TimelineContentStubNoInfoLayoutParams" parent="TimelineContentStubBaseParams">
|
||||||
<item name="layout_constraintTop_toTopOf">parent</item>
|
<item name="layout_constraintTop_toTopOf">parent</item>
|
||||||
|
<item name="layout_constraintBottom_toTopOf">@id/readReceiptsView</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user