From 9c9c09db2b1747137e7a0ed8356d02e08cecc2fc Mon Sep 17 00:00:00 2001 From: ganfra Date: Thu, 11 Apr 2019 19:19:52 +0200 Subject: [PATCH] Adjust colors for avatar and display names + start handling video in timeline --- .../user/AutocompleteUserController.kt | 1 + .../autocomplete/user/AutocompleteUserItem.kt | 13 ++- .../features/home/AvatarRenderer.kt | 37 ++++++--- .../home/group/GroupSummaryController.kt | 1 + .../features/home/group/GroupSummaryItem.kt | 3 +- .../home/room/detail/RoomDetailFragment.kt | 11 ++- .../timeline/TimelineEventController.kt | 13 ++- .../timeline/factory/MessageItemFactory.kt | 80 +++++++++++-------- .../helper/ContentUploadStateTrackerBinder.kt | 6 +- .../detail/timeline/item/AbsMessageItem.kt | 7 +- ...eImageItem.kt => MessageImageVideoItem.kt} | 25 +++--- .../timeline/item/MessageInformationData.kt | 5 ++ .../detail/timeline/item/MessageTextItem.kt | 1 + .../room/detail/timeline/item/NoticeItem.kt | 3 +- .../home/room/list/RoomSummaryController.kt | 1 + .../home/room/list/RoomSummaryItem.kt | 3 +- .../features/html/PillImageSpan.kt | 4 +- ...entRenderer.kt => ImageContentRenderer.kt} | 23 +++--- .../features/media/MediaViewerActivity.kt | 8 +- ...em_timeline_event_image_video_message.xml} | 41 ++++++++-- .../item_timeline_event_text_message.xml | 2 + 21 files changed, 188 insertions(+), 100 deletions(-) rename vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/item/{MessageImageItem.kt => MessageImageVideoItem.kt} (65%) rename vector/src/main/java/im/vector/riotredesign/features/media/{MediaContentRenderer.kt => ImageContentRenderer.kt} (88%) rename vector/src/main/res/layout/{item_timeline_event_image_message.xml => item_timeline_event_image_video_message.xml} (66%) diff --git a/vector/src/main/java/im/vector/riotredesign/features/autocomplete/user/AutocompleteUserController.kt b/vector/src/main/java/im/vector/riotredesign/features/autocomplete/user/AutocompleteUserController.kt index bec9adb022..6f174e0d88 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/autocomplete/user/AutocompleteUserController.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/autocomplete/user/AutocompleteUserController.kt @@ -31,6 +31,7 @@ class AutocompleteUserController : TypedEpoxyController>() { data.forEach { user -> autocompleteUserItem { id(user.userId) + userId(user.userId) name(user.displayName) avatarUrl(user.avatarUrl) clickListener { _ -> diff --git a/vector/src/main/java/im/vector/riotredesign/features/autocomplete/user/AutocompleteUserItem.kt b/vector/src/main/java/im/vector/riotredesign/features/autocomplete/user/AutocompleteUserItem.kt index 6678bff543..576947253e 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/autocomplete/user/AutocompleteUserItem.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/autocomplete/user/AutocompleteUserItem.kt @@ -29,18 +29,15 @@ import im.vector.riotredesign.features.home.AvatarRenderer @EpoxyModelClass(layout = R.layout.item_autocomplete_user) abstract class AutocompleteUserItem : VectorEpoxyModel() { - @EpoxyAttribute - var name: String? = null - @EpoxyAttribute - var avatarUrl: String? = null - @EpoxyAttribute - var clickListener: View.OnClickListener? = null + @EpoxyAttribute var name: String? = null + @EpoxyAttribute var userId: String = "" + @EpoxyAttribute var avatarUrl: String? = null + @EpoxyAttribute var clickListener: View.OnClickListener? = null override fun bind(holder: Holder) { holder.view.setOnClickListener(clickListener) - holder.nameView.text = name - AvatarRenderer.render(avatarUrl, name, holder.avatarImageView) + AvatarRenderer.render(avatarUrl, userId, name, holder.avatarImageView) } class Holder : VectorEpoxyHolder() { diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/AvatarRenderer.kt b/vector/src/main/java/im/vector/riotredesign/features/home/AvatarRenderer.kt index b64cadb18d..70974710db 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/home/AvatarRenderer.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/home/AvatarRenderer.kt @@ -29,7 +29,6 @@ import com.bumptech.glide.request.target.Target import im.vector.matrix.android.api.Matrix import im.vector.matrix.android.api.MatrixPatterns import im.vector.matrix.android.api.session.content.ContentUrlResolver -import im.vector.matrix.android.api.session.room.model.RoomMember import im.vector.matrix.android.api.session.room.model.RoomSummary import im.vector.riotredesign.R import im.vector.riotredesign.core.glide.GlideApp @@ -44,39 +43,41 @@ object AvatarRenderer { private const val THUMBNAIL_SIZE = 250 - @UiThread - fun render(roomMember: RoomMember, imageView: ImageView) { - render(roomMember.avatarUrl, roomMember.displayName, imageView) - } + private val AVATAR_COLOR_LIST = listOf( + R.color.avatar_color_1, + R.color.avatar_color_2, + R.color.avatar_color_3 + ) @UiThread fun render(roomSummary: RoomSummary, imageView: ImageView) { - render(roomSummary.avatarUrl, roomSummary.displayName, imageView) + render(roomSummary.avatarUrl, roomSummary.roomId, roomSummary.displayName, imageView) } @UiThread - fun render(avatarUrl: String?, name: String?, imageView: ImageView) { - render(imageView.context, GlideApp.with(imageView), avatarUrl, name, DrawableImageViewTarget(imageView)) + fun render(avatarUrl: String?, identifier: String, name: String?, imageView: ImageView) { + render(imageView.context, GlideApp.with(imageView), avatarUrl, identifier, name, DrawableImageViewTarget(imageView)) } @UiThread fun render(context: Context, glideRequest: GlideRequests, avatarUrl: String?, + identifier: String, name: String?, target: Target) { if (name.isNullOrEmpty()) { return } - val placeholder = getPlaceholderDrawable(context, name) + val placeholder = getPlaceholderDrawable(context, identifier, name) buildGlideRequest(glideRequest, avatarUrl) .placeholder(placeholder) .into(target) } @AnyThread - fun getPlaceholderDrawable(context: Context, text: String): Drawable { - val avatarColor = ContextCompat.getColor(context, R.color.pale_teal) + fun getPlaceholderDrawable(context: Context, identifier: String, text: String): Drawable { + val avatarColor = ContextCompat.getColor(context, getAvatarColor(identifier)) return if (text.isEmpty()) { TextDrawable.builder().buildRound("", avatarColor) } else { @@ -87,9 +88,21 @@ object AvatarRenderer { } } - // PRIVATE API ********************************************************************************* + + private fun getAvatarColor(text: String? = null): Int { + var colorIndex: Long = 0 + if (!text.isNullOrEmpty()) { + var sum: Long = 0 + for (i in 0 until text.length) { + sum += text[i].toLong() + } + colorIndex = sum % AVATAR_COLOR_LIST.size + } + return AVATAR_COLOR_LIST[colorIndex.toInt()] + } + private fun buildGlideRequest(glideRequest: GlideRequests, avatarUrl: String?): GlideRequest { val resolvedUrl = Matrix.getInstance().currentSession!!.contentUrlResolver() .resolveThumbnail(avatarUrl, THUMBNAIL_SIZE, THUMBNAIL_SIZE, ContentUrlResolver.ThumbnailMethod.SCALE) diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/group/GroupSummaryController.kt b/vector/src/main/java/im/vector/riotredesign/features/home/group/GroupSummaryController.kt index 0fc0c9d4d5..fe40b162c4 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/home/group/GroupSummaryController.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/home/group/GroupSummaryController.kt @@ -35,6 +35,7 @@ class GroupSummaryController : TypedEpoxyController() { val isSelected = groupSummary.groupId == selected?.groupId groupSummaryItem { id(groupSummary.groupId) + groupId(groupSummary.groupId) groupName(groupSummary.displayName) selected(isSelected) avatarUrl(groupSummary.avatarUrl) diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/group/GroupSummaryItem.kt b/vector/src/main/java/im/vector/riotredesign/features/home/group/GroupSummaryItem.kt index 3acf7f0a76..eef0e8659a 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/home/group/GroupSummaryItem.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/home/group/GroupSummaryItem.kt @@ -29,6 +29,7 @@ import im.vector.riotredesign.features.home.AvatarRenderer abstract class GroupSummaryItem : VectorEpoxyModel() { @EpoxyAttribute lateinit var groupName: CharSequence + @EpoxyAttribute lateinit var groupId: String @EpoxyAttribute var avatarUrl: String? = null @EpoxyAttribute var selected: Boolean = false @EpoxyAttribute var listener: (() -> Unit)? = null @@ -37,7 +38,7 @@ abstract class GroupSummaryItem : VectorEpoxyModel() { super.bind(holder) holder.rootView.isSelected = selected holder.rootView.setOnClickListener { listener?.invoke() } - AvatarRenderer.render(avatarUrl, groupName.toString(), holder.avatarImageView) + AvatarRenderer.render(avatarUrl, groupId, groupName.toString(), holder.avatarImageView) } class Holder : VectorEpoxyHolder() { diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailFragment.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailFragment.kt index 59cf99abd1..d3319b7476 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailFragment.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailFragment.kt @@ -37,6 +37,8 @@ import com.otaliastudios.autocomplete.Autocomplete import com.otaliastudios.autocomplete.AutocompleteCallback import com.otaliastudios.autocomplete.CharPolicy import im.vector.matrix.android.api.session.Session +import im.vector.matrix.android.api.session.room.model.message.MessageImageContent +import im.vector.matrix.android.api.session.room.model.message.MessageVideoContent import im.vector.matrix.android.api.session.room.timeline.TimelineEvent import im.vector.matrix.android.api.session.user.model.User import im.vector.riotredesign.R @@ -65,7 +67,7 @@ import im.vector.riotredesign.features.home.room.detail.composer.TextComposerVie import im.vector.riotredesign.features.home.room.detail.timeline.TimelineEventController import im.vector.riotredesign.features.home.room.detail.timeline.helper.EndlessRecyclerViewScrollListener import im.vector.riotredesign.features.html.PillImageSpan -import im.vector.riotredesign.features.media.MediaContentRenderer +import im.vector.riotredesign.features.media.ImageContentRenderer import im.vector.riotredesign.features.media.MediaViewerActivity import kotlinx.android.parcel.Parcelize import kotlinx.android.synthetic.main.fragment_room_detail.* @@ -379,11 +381,16 @@ class RoomDetailFragment : VectorBaseFragment(), TimelineEventController.Callbac roomDetailViewModel.process(RoomDetailActions.EventDisplayed(event)) } - override fun onMediaClicked(mediaData: MediaContentRenderer.Data, view: View) { + override fun onImageMessageClicked(messageImageContent: MessageImageContent, mediaData: ImageContentRenderer.Data, view: View) { val intent = MediaViewerActivity.newIntent(vectorBaseActivity, mediaData) startActivity(intent) } + override fun onVideoMessageClicked(messageVideoContent: MessageVideoContent, mediaData: ImageContentRenderer.Data, view: View) { + //TODO handle + } + + // AutocompleteUserPresenter.Callback override fun onQueryUsers(query: CharSequence?) { diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/TimelineEventController.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/TimelineEventController.kt index 618c15c210..b96ba1ddc8 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/TimelineEventController.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/TimelineEventController.kt @@ -25,15 +25,21 @@ import androidx.recyclerview.widget.RecyclerView import com.airbnb.epoxy.EpoxyController import com.airbnb.epoxy.EpoxyModel import com.airbnb.epoxy.VisibilityState +import im.vector.matrix.android.api.session.room.model.message.MessageImageContent +import im.vector.matrix.android.api.session.room.model.message.MessageVideoContent import im.vector.matrix.android.api.session.room.timeline.Timeline import im.vector.matrix.android.api.session.room.timeline.TimelineEvent import im.vector.riotredesign.core.epoxy.LoadingItemModel_ import im.vector.riotredesign.core.epoxy.VectorEpoxyModel import im.vector.riotredesign.core.extensions.localDateTime import im.vector.riotredesign.features.home.room.detail.timeline.factory.TimelineItemFactory -import im.vector.riotredesign.features.home.room.detail.timeline.helper.* +import im.vector.riotredesign.features.home.room.detail.timeline.helper.TimelineAsyncHelper +import im.vector.riotredesign.features.home.room.detail.timeline.helper.TimelineDateFormatter +import im.vector.riotredesign.features.home.room.detail.timeline.helper.TimelineEventDiffUtilCallback +import im.vector.riotredesign.features.home.room.detail.timeline.helper.TimelineMediaSizeProvider +import im.vector.riotredesign.features.home.room.detail.timeline.helper.nextDisplayableEvent import im.vector.riotredesign.features.home.room.detail.timeline.item.DaySeparatorItem_ -import im.vector.riotredesign.features.media.MediaContentRenderer +import im.vector.riotredesign.features.media.ImageContentRenderer class TimelineEventController(private val dateFormatter: TimelineDateFormatter, private val timelineItemFactory: TimelineItemFactory, @@ -44,7 +50,8 @@ class TimelineEventController(private val dateFormatter: TimelineDateFormatter, interface Callback { fun onEventVisible(event: TimelineEvent) fun onUrlClicked(url: String) - fun onMediaClicked(mediaData: MediaContentRenderer.Data, view: View) + fun onImageMessageClicked(messageImageContent: MessageImageContent, mediaData: ImageContentRenderer.Data, view: View) + fun onVideoMessageClicked(messageVideoContent: MessageVideoContent, mediaData: ImageContentRenderer.Data, view: View) } private val modelCache = arrayListOf>>() diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/factory/MessageItemFactory.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/factory/MessageItemFactory.kt index 9df93a5625..884e8c3416 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/factory/MessageItemFactory.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/factory/MessageItemFactory.kt @@ -28,7 +28,7 @@ import im.vector.matrix.android.api.session.room.model.message.MessageEmoteConte import im.vector.matrix.android.api.session.room.model.message.MessageImageContent import im.vector.matrix.android.api.session.room.model.message.MessageNoticeContent import im.vector.matrix.android.api.session.room.model.message.MessageTextContent -import im.vector.matrix.android.api.session.room.send.SendState +import im.vector.matrix.android.api.session.room.model.message.MessageVideoContent import im.vector.matrix.android.api.session.room.timeline.TimelineEvent import im.vector.riotredesign.R import im.vector.riotredesign.core.epoxy.VectorEpoxyModel @@ -40,13 +40,13 @@ import im.vector.riotredesign.features.home.room.detail.timeline.helper.Timeline import im.vector.riotredesign.features.home.room.detail.timeline.helper.TimelineMediaSizeProvider import im.vector.riotredesign.features.home.room.detail.timeline.item.DefaultItem import im.vector.riotredesign.features.home.room.detail.timeline.item.DefaultItem_ -import im.vector.riotredesign.features.home.room.detail.timeline.item.MessageImageItem -import im.vector.riotredesign.features.home.room.detail.timeline.item.MessageImageItem_ +import im.vector.riotredesign.features.home.room.detail.timeline.item.MessageImageVideoItem +import im.vector.riotredesign.features.home.room.detail.timeline.item.MessageImageVideoItem_ import im.vector.riotredesign.features.home.room.detail.timeline.item.MessageInformationData import im.vector.riotredesign.features.home.room.detail.timeline.item.MessageTextItem import im.vector.riotredesign.features.home.room.detail.timeline.item.MessageTextItem_ import im.vector.riotredesign.features.html.EventHtmlRenderer -import im.vector.riotredesign.features.media.MediaContentRenderer +import im.vector.riotredesign.features.media.ImageContentRenderer import me.gujun.android.span.span class MessageItemFactory(private val colorProvider: ColorProvider, @@ -79,15 +79,22 @@ class MessageItemFactory(private val colorProvider: ColorProvider, val avatarUrl = roomMember?.avatarUrl val memberName = roomMember?.displayName ?: event.root.sender ?: "" val formattedMemberName = span(memberName) { - textColor = colorProvider.getColor(colorIndexForSender(memberName)) + textColor = colorProvider.getColor(getColorFor(event.root.sender ?: "")) } - val informationData = MessageInformationData(time, avatarUrl, formattedMemberName, showInformation) + val informationData = MessageInformationData(eventId = eventId, + senderId = event.root.sender ?: "", + sendState = event.sendState, + time = time, + avatarUrl = avatarUrl, + memberName = formattedMemberName, + showInformation = showInformation) return when (messageContent) { is MessageEmoteContent -> buildEmoteMessageItem(messageContent, informationData, callback) - is MessageTextContent -> buildTextMessageItem(event.sendState, messageContent, informationData, callback) - is MessageImageContent -> buildImageMessageItem(eventId, messageContent, informationData, callback) + is MessageTextContent -> buildTextMessageItem(messageContent, informationData, callback) + is MessageImageContent -> buildImageMessageItem(messageContent, informationData, callback) is MessageNoticeContent -> buildNoticeMessageItem(messageContent, informationData, callback) + is MessageVideoContent -> buildVideoMessageItem(messageContent, informationData, callback) else -> buildNotHandledMessageItem(messageContent) } } @@ -97,31 +104,49 @@ class MessageItemFactory(private val colorProvider: ColorProvider, return DefaultItem_().text(text) } - private fun buildImageMessageItem(eventId: String, - messageContent: MessageImageContent, + private fun buildImageMessageItem(messageContent: MessageImageContent, informationData: MessageInformationData, - callback: TimelineEventController.Callback?): MessageImageItem? { + callback: TimelineEventController.Callback?): MessageImageVideoItem? { val (maxWidth, maxHeight) = timelineMediaSizeProvider.getMaxSize() - val data = MediaContentRenderer.Data( - messageContent.body, + val data = ImageContentRenderer.Data( + filename = messageContent.body, url = messageContent.url, height = messageContent.info?.height, maxHeight = maxHeight, width = messageContent.info?.width, maxWidth = maxWidth, - rotation = messageContent.info?.rotation, - orientation = messageContent.info?.orientation + orientation = messageContent.info?.orientation, + rotation = messageContent.info?.rotation ) - return MessageImageItem_() - .eventId(eventId) + return MessageImageVideoItem_() + .playable(messageContent.info?.mimeType == "image/gif") .informationData(informationData) .mediaData(data) - .clickListener { view -> callback?.onMediaClicked(data, view) } + .clickListener { view -> callback?.onImageMessageClicked(messageContent, data, view) } } - private fun buildTextMessageItem(sendState: SendState, - messageContent: MessageTextContent, + private fun buildVideoMessageItem(messageContent: MessageVideoContent, + informationData: MessageInformationData, + callback: TimelineEventController.Callback?): MessageImageVideoItem? { + + val (maxWidth, maxHeight) = timelineMediaSizeProvider.getMaxSize() + val data = ImageContentRenderer.Data( + filename = messageContent.body, + url = messageContent.info?.thumbnailUrl, + height = messageContent.info?.height, + maxHeight = maxHeight, + width = messageContent.info?.width, + maxWidth = maxWidth + ) + return MessageImageVideoItem_() + .playable(true) + .informationData(informationData) + .mediaData(data) + .clickListener { view -> callback?.onVideoMessageClicked(messageContent, data, view) } + } + + private fun buildTextMessageItem(messageContent: MessageTextContent, informationData: MessageInformationData, callback: TimelineEventController.Callback?): MessageTextItem? { @@ -129,15 +154,7 @@ class MessageItemFactory(private val colorProvider: ColorProvider, htmlRenderer.render(it) } ?: messageContent.body - val textColor = if (sendState.isSent()) { - R.color.dark_grey - } else { - R.color.brown_grey - } - val formattedBody = span(bodyToUse) { - this.textColor = colorProvider.getColor(textColor) - } - val linkifiedBody = linkifyBody(formattedBody, callback) + val linkifiedBody = linkifyBody(bodyToUse, callback) return MessageTextItem_() .message(linkifiedBody) .informationData(informationData) @@ -184,8 +201,9 @@ class MessageItemFactory(private val colorProvider: ColorProvider, return spannable } + //Based on riot-web implementation @ColorRes - private fun colorIndexForSender(sender: String): Int { + private fun getColorFor(sender: String): Int { var hash = 0 var i = 0 var chr: Char @@ -210,6 +228,4 @@ class MessageItemFactory(private val colorProvider: ColorProvider, else -> R.color.username_8 } } - - } \ No newline at end of file diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/helper/ContentUploadStateTrackerBinder.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/helper/ContentUploadStateTrackerBinder.kt index d0d899ef50..8d395668ce 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/helper/ContentUploadStateTrackerBinder.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/helper/ContentUploadStateTrackerBinder.kt @@ -25,7 +25,7 @@ import android.widget.TextView import im.vector.matrix.android.api.Matrix import im.vector.matrix.android.api.session.content.ContentUploadStateTracker import im.vector.riotredesign.R -import im.vector.riotredesign.features.media.MediaContentRenderer +import im.vector.riotredesign.features.media.ImageContentRenderer import java.io.File object ContentUploadStateTrackerBinder { @@ -33,7 +33,7 @@ object ContentUploadStateTrackerBinder { private val updateListeners = mutableMapOf() fun bind(eventId: String, - mediaData: MediaContentRenderer.Data, + mediaData: ImageContentRenderer.Data, progressLayout: ViewGroup) { Matrix.getInstance().currentSession?.also { session -> @@ -56,7 +56,7 @@ object ContentUploadStateTrackerBinder { } private class ContentMediaProgressUpdater(private val progressLayout: ViewGroup, - private val mediaData: MediaContentRenderer.Data) : ContentUploadStateTracker.UpdateListener { + private val mediaData: ImageContentRenderer.Data) : ContentUploadStateTracker.UpdateListener { override fun onUpdate(state: ContentUploadStateTracker.State) { when (state) { diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/item/AbsMessageItem.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/item/AbsMessageItem.kt index f4fe2e8dec..17c2ea4a91 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/item/AbsMessageItem.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/item/AbsMessageItem.kt @@ -35,7 +35,7 @@ abstract class AbsMessageItem : VectorEpoxyModel() holder.timeView.visibility = View.VISIBLE holder.timeView.text = informationData.time holder.memberNameView.text = informationData.memberName - AvatarRenderer.render(informationData.avatarUrl, informationData.memberName?.toString(), holder.avatarImageView) + AvatarRenderer.render(informationData.avatarUrl, informationData.senderId, informationData.memberName?.toString(), holder.avatarImageView) } else { holder.avatarImageView.visibility = View.GONE holder.memberNameView.visibility = View.GONE @@ -43,6 +43,11 @@ abstract class AbsMessageItem : VectorEpoxyModel() } } + protected fun View.renderSendState() { + isClickable = informationData.sendState.isSent() + alpha = if (informationData.sendState.isSent()) 1f else 0.5f + } + abstract class Holder : VectorEpoxyHolder() { abstract val avatarImageView: ImageView abstract val memberNameView: TextView diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/item/MessageImageItem.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/item/MessageImageVideoItem.kt similarity index 65% rename from vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/item/MessageImageItem.kt rename to vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/item/MessageImageVideoItem.kt index 15e3d5ff08..27e464b16f 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/item/MessageImageItem.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/item/MessageImageVideoItem.kt @@ -24,27 +24,27 @@ import com.airbnb.epoxy.EpoxyAttribute import com.airbnb.epoxy.EpoxyModelClass import im.vector.riotredesign.R import im.vector.riotredesign.features.home.room.detail.timeline.helper.ContentUploadStateTrackerBinder -import im.vector.riotredesign.features.media.MediaContentRenderer +import im.vector.riotredesign.features.media.ImageContentRenderer -@EpoxyModelClass(layout = R.layout.item_timeline_event_image_message) -abstract class MessageImageItem : AbsMessageItem() { +@EpoxyModelClass(layout = R.layout.item_timeline_event_image_video_message) +abstract class MessageImageVideoItem : AbsMessageItem() { - @EpoxyAttribute lateinit var mediaData: MediaContentRenderer.Data - @EpoxyAttribute lateinit var eventId: String + @EpoxyAttribute lateinit var mediaData: ImageContentRenderer.Data @EpoxyAttribute override lateinit var informationData: MessageInformationData + @EpoxyAttribute var playable: Boolean = false @EpoxyAttribute var clickListener: View.OnClickListener? = null override fun bind(holder: Holder) { super.bind(holder) - MediaContentRenderer.render(mediaData, MediaContentRenderer.Mode.THUMBNAIL, holder.imageView) - ContentUploadStateTrackerBinder.bind(eventId, mediaData, holder.progressLayout) + ImageContentRenderer.render(mediaData, ImageContentRenderer.Mode.THUMBNAIL, holder.imageView) + ContentUploadStateTrackerBinder.bind(informationData.eventId, mediaData, holder.progressLayout) holder.imageView.setOnClickListener(clickListener) - holder.imageView.isEnabled = !mediaData.isLocalFile() - holder.imageView.alpha = if (mediaData.isLocalFile()) 0.5f else 1f + holder.imageView.renderSendState() + holder.playContentView.visibility = if (playable) View.VISIBLE else View.GONE } override fun unbind(holder: Holder) { - ContentUploadStateTrackerBinder.unbind(eventId) + ContentUploadStateTrackerBinder.unbind(informationData.eventId) super.unbind(holder) } @@ -52,8 +52,9 @@ abstract class MessageImageItem : AbsMessageItem() { override val avatarImageView by bind(R.id.messageAvatarImageView) override val memberNameView by bind(R.id.messageMemberNameView) override val timeView by bind(R.id.messageTimeView) - val progressLayout by bind(R.id.messageImageUploadProgressLayout) - val imageView by bind(R.id.messageImageView) + val progressLayout by bind(R.id.messageMediaUploadProgressLayout) + val imageView by bind(R.id.messageThumbnailView) + val playContentView by bind(R.id.messageMediaPlayView) } } \ No newline at end of file diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/item/MessageInformationData.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/item/MessageInformationData.kt index f6ae60a93f..286f11a5b0 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/item/MessageInformationData.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/item/MessageInformationData.kt @@ -16,7 +16,12 @@ package im.vector.riotredesign.features.home.room.detail.timeline.item +import im.vector.matrix.android.api.session.room.send.SendState + data class MessageInformationData( + val eventId: String, + val senderId: String, + val sendState: SendState, val time: CharSequence? = null, val avatarUrl: String?, val memberName: CharSequence? = null, diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/item/MessageTextItem.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/item/MessageTextItem.kt index 5c5ebbf933..45ba0ec147 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/item/MessageTextItem.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/item/MessageTextItem.kt @@ -45,6 +45,7 @@ abstract class MessageTextItem : AbsMessageItem() { TextViewCompat.getTextMetricsParams(holder.messageView), null) holder.messageView.setTextFuture(textFuture) + holder.messageView.renderSendState() findPillsAndProcess { it.bind(holder.messageView) } } diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/item/NoticeItem.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/item/NoticeItem.kt index fe28892c83..821561531e 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/item/NoticeItem.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/item/NoticeItem.kt @@ -30,11 +30,12 @@ abstract class NoticeItem : VectorEpoxyModel() { @EpoxyAttribute var noticeText: CharSequence? = null @EpoxyAttribute var avatarUrl: String? = null + @EpoxyAttribute var userId: String = "" @EpoxyAttribute var memberName: CharSequence? = null override fun bind(holder: Holder) { holder.noticeTextView.text = noticeText - AvatarRenderer.render(avatarUrl, memberName?.toString(), holder.avatarImageView) + AvatarRenderer.render(avatarUrl, userId, memberName?.toString(), holder.avatarImageView) } class Holder : VectorEpoxyHolder() { diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/list/RoomSummaryController.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/list/RoomSummaryController.kt index 8c0b742d33..24641da234 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/home/room/list/RoomSummaryController.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/list/RoomSummaryController.kt @@ -79,6 +79,7 @@ class RoomSummaryController(private val stringProvider: StringProvider roomSummaryItem { id(roomSummary.roomId) + roomId(roomSummary.roomId) roomName(roomSummary.displayName) avatarUrl(roomSummary.avatarUrl) selected(isSelected) diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/list/RoomSummaryItem.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/list/RoomSummaryItem.kt index 02a240d9f3..e68926101d 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/home/room/list/RoomSummaryItem.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/list/RoomSummaryItem.kt @@ -31,6 +31,7 @@ import im.vector.riotredesign.features.home.AvatarRenderer abstract class RoomSummaryItem : VectorEpoxyModel() { @EpoxyAttribute lateinit var roomName: CharSequence + @EpoxyAttribute lateinit var roomId: String @EpoxyAttribute var avatarUrl: String? = null @EpoxyAttribute var selected: Boolean = false @EpoxyAttribute var unreadCount: Int = 0 @@ -44,7 +45,7 @@ abstract class RoomSummaryItem : VectorEpoxyModel() { holder.rootView.isChecked = selected holder.rootView.setOnClickListener { listener?.invoke() } holder.titleView.text = roomName - AvatarRenderer.render(avatarUrl, roomName.toString(), holder.avatarImageView) + AvatarRenderer.render(avatarUrl, roomId, roomName.toString(), holder.avatarImageView) } class Holder : VectorEpoxyHolder() { diff --git a/vector/src/main/java/im/vector/riotredesign/features/html/PillImageSpan.kt b/vector/src/main/java/im/vector/riotredesign/features/html/PillImageSpan.kt index 5ab1853109..e83e20e6b9 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/html/PillImageSpan.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/html/PillImageSpan.kt @@ -52,7 +52,7 @@ class PillImageSpan(private val glideRequests: GlideRequests, @UiThread fun bind(textView: TextView) { tv = WeakReference(textView) - AvatarRenderer.render(context, glideRequests, user?.avatarUrl, displayName, target) + AvatarRenderer.render(context, glideRequests, user?.avatarUrl, userId, displayName, target) } // ReplacementSpan ***************************************************************************** @@ -105,7 +105,7 @@ class PillImageSpan(private val glideRequests: GlideRequests, textStartPadding = textPadding setChipMinHeightResource(R.dimen.pill_min_height) setChipIconSizeResource(R.dimen.pill_avatar_size) - chipIcon = AvatarRenderer.getPlaceholderDrawable(context, displayName) + chipIcon = AvatarRenderer.getPlaceholderDrawable(context, userId, displayName) setBounds(0, 0, intrinsicWidth, intrinsicHeight) } } diff --git a/vector/src/main/java/im/vector/riotredesign/features/media/MediaContentRenderer.kt b/vector/src/main/java/im/vector/riotredesign/features/media/ImageContentRenderer.kt similarity index 88% rename from vector/src/main/java/im/vector/riotredesign/features/media/MediaContentRenderer.kt rename to vector/src/main/java/im/vector/riotredesign/features/media/ImageContentRenderer.kt index fda674189d..42c50e688b 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/media/MediaContentRenderer.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/media/ImageContentRenderer.kt @@ -27,7 +27,7 @@ import im.vector.riotredesign.core.glide.GlideApp import kotlinx.android.parcel.Parcelize import java.io.File -object MediaContentRenderer { +object ImageContentRenderer { @Parcelize data class Data( @@ -37,8 +37,8 @@ object MediaContentRenderer { val maxHeight: Int, val width: Int?, val maxWidth: Int, - val orientation: Int?, - val rotation: Int? + val orientation: Int? = null, + val rotation: Int? = null ) : Parcelable { fun isLocalFile(): Boolean { @@ -66,6 +66,7 @@ object MediaContentRenderer { GlideApp .with(imageView) .load(resolvedUrl) + .dontAnimate() .thumbnail(0.3f) .into(imageView) } @@ -73,16 +74,12 @@ object MediaContentRenderer { fun render(data: Data, imageView: BigImageView) { val (width, height) = processSize(data, Mode.THUMBNAIL) val contentUrlResolver = Matrix.getInstance().currentSession!!.contentUrlResolver() - if (data.isLocalFile()) { - imageView.showImage(Uri.parse(data.url)) - } else { - val fullSize = contentUrlResolver.resolveFullSize(data.url) - val thumbnail = contentUrlResolver.resolveThumbnail(data.url, width, height, ContentUrlResolver.ThumbnailMethod.SCALE) - imageView.showImage( - Uri.parse(thumbnail), - Uri.parse(fullSize) - ) - } + val fullSize = contentUrlResolver.resolveFullSize(data.url) + val thumbnail = contentUrlResolver.resolveThumbnail(data.url, width, height, ContentUrlResolver.ThumbnailMethod.SCALE) + imageView.showImage( + Uri.parse(thumbnail), + Uri.parse(fullSize) + ) } private fun processSize(data: Data, mode: Mode): Pair { diff --git a/vector/src/main/java/im/vector/riotredesign/features/media/MediaViewerActivity.kt b/vector/src/main/java/im/vector/riotredesign/features/media/MediaViewerActivity.kt index 1cbd16fe56..9859b94bb6 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/media/MediaViewerActivity.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/media/MediaViewerActivity.kt @@ -33,18 +33,18 @@ class MediaViewerActivity : VectorBaseActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(im.vector.riotredesign.R.layout.activity_media_viewer) - val mediaData = intent.getParcelableExtra(EXTRA_MEDIA_DATA) + val mediaData = intent.getParcelableExtra(EXTRA_MEDIA_DATA) if (mediaData.url.isNullOrEmpty()) { finish() } else { configureToolbar(mediaViewerToolbar, mediaData) mediaViewerImageView.setImageViewFactory(GlideImageViewFactory()) mediaViewerImageView.setProgressIndicator(ProgressPieIndicator()) - MediaContentRenderer.render(mediaData, mediaViewerImageView) + ImageContentRenderer.render(mediaData, mediaViewerImageView) } } - private fun configureToolbar(toolbar: Toolbar, mediaData: MediaContentRenderer.Data) { + private fun configureToolbar(toolbar: Toolbar, mediaData: ImageContentRenderer.Data) { setSupportActionBar(toolbar) supportActionBar?.apply { title = mediaData.filename @@ -57,7 +57,7 @@ class MediaViewerActivity : VectorBaseActivity() { private const val EXTRA_MEDIA_DATA = "EXTRA_MEDIA_DATA" - fun newIntent(context: Context, mediaData: MediaContentRenderer.Data): Intent { + fun newIntent(context: Context, mediaData: ImageContentRenderer.Data): Intent { return Intent(context, MediaViewerActivity::class.java).apply { putExtra(EXTRA_MEDIA_DATA, mediaData) } diff --git a/vector/src/main/res/layout/item_timeline_event_image_message.xml b/vector/src/main/res/layout/item_timeline_event_image_video_message.xml similarity index 66% rename from vector/src/main/res/layout/item_timeline_event_image_message.xml rename to vector/src/main/res/layout/item_timeline_event_image_video_message.xml index 21c7b12f9a..b1da588294 100644 --- a/vector/src/main/res/layout/item_timeline_event_image_message.xml +++ b/vector/src/main/res/layout/item_timeline_event_image_video_message.xml @@ -1,4 +1,19 @@ - + + + app:layout_constraintTop_toBottomOf="@+id/messageMemberNameView" + tools:layout_height="300dp" /> + + + diff --git a/vector/src/main/res/layout/item_timeline_event_text_message.xml b/vector/src/main/res/layout/item_timeline_event_text_message.xml index 5d3004427a..bd6f9f8970 100644 --- a/vector/src/main/res/layout/item_timeline_event_text_message.xml +++ b/vector/src/main/res/layout/item_timeline_event_text_message.xml @@ -44,6 +44,7 @@ android:layout_marginStart="8dp" android:layout_marginLeft="8dp" android:textColor="@color/brown_grey" + android:duplicateParentState="true" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintHorizontal_bias="0.0" app:layout_constraintTop_toTopOf="@id/messageMemberNameView" @@ -56,6 +57,7 @@ android:layout_marginStart="64dp" android:layout_marginLeft="64dp" android:layout_marginBottom="8dp" + android:duplicateParentState="true" android:textColor="@color/dark_grey" android:textSize="14sp" app:layout_constraintBottom_toBottomOf="parent"