From 6f103101b69c518527ecac25f95f23e180e1a533 Mon Sep 17 00:00:00 2001 From: Valere Date: Tue, 21 May 2019 14:12:18 +0200 Subject: [PATCH] Show edited annotation in timeline + simple edit history --- .../session/room/EventRelationExtractor.kt | 19 ++++++++++- .../core/resources/ColorProvider.kt | 15 ++++++++ .../riotredesign/features/home/HomeModule.kt | 4 ++- .../home/room/detail/RoomDetailActions.kt | 2 ++ .../home/room/detail/RoomDetailFragment.kt | 19 +++++++++++ .../home/room/detail/RoomDetailViewModel.kt | 25 ++++++++++++++ .../timeline/TimelineEventController.kt | 2 ++ .../action/MessageActionsViewModel.kt | 3 +- .../timeline/factory/MessageItemFactory.kt | 34 +++++++++++++++++-- vector/src/main/res/values/strings_riotX.xml | 1 + 10 files changed, 118 insertions(+), 6 deletions(-) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/EventRelationExtractor.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/EventRelationExtractor.kt index 6fc1cb86..e52f5e09 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/EventRelationExtractor.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/EventRelationExtractor.kt @@ -1,3 +1,18 @@ +/* + * 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.matrix.android.internal.session.room import im.vector.matrix.android.api.session.room.model.EventAnnotationsSummary @@ -7,7 +22,9 @@ import im.vector.matrix.android.internal.database.model.EventEntity import im.vector.matrix.android.internal.database.query.where import io.realm.Realm - +/** + * Fetches annotations (reactions, edits...) associated to a given eventEntity from the data layer. + */ internal class EventRelationExtractor { fun extractFrom(event: EventEntity, realm: Realm = event.realm): EventAnnotationsSummary? { diff --git a/vector/src/main/java/im/vector/riotredesign/core/resources/ColorProvider.kt b/vector/src/main/java/im/vector/riotredesign/core/resources/ColorProvider.kt index 4fd37212..cfc48891 100644 --- a/vector/src/main/java/im/vector/riotredesign/core/resources/ColorProvider.kt +++ b/vector/src/main/java/im/vector/riotredesign/core/resources/ColorProvider.kt @@ -19,8 +19,11 @@ package im.vector.riotredesign.core.resources import android.content.Context +import androidx.annotation.AttrRes +import androidx.annotation.ColorInt import androidx.annotation.ColorRes import androidx.core.content.ContextCompat +import im.vector.riotredesign.features.themes.ThemeUtils class ColorProvider(private val context: Context) { @@ -28,4 +31,16 @@ class ColorProvider(private val context: Context) { return ContextCompat.getColor(context, colorRes) } + /** + * Translates color attributes to colors + * + * @param c Context + * @param colorAttribute Color Attribute + * @return Requested Color + */ + @ColorInt + fun getColorFromAttribute(@AttrRes colorAttribute: Int): Int { + return ThemeUtils.getColor(context, colorAttribute) + } + } \ No newline at end of file diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/HomeModule.kt b/vector/src/main/java/im/vector/riotredesign/features/home/HomeModule.kt index 98666053..1474d2da 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/home/HomeModule.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/home/HomeModule.kt @@ -18,6 +18,7 @@ package im.vector.riotredesign.features.home import androidx.fragment.app.Fragment import im.vector.riotredesign.core.glide.GlideApp +import im.vector.riotredesign.core.resources.ColorProvider import im.vector.riotredesign.features.autocomplete.command.AutocompleteCommandController import im.vector.riotredesign.features.autocomplete.command.AutocompleteCommandPresenter import im.vector.riotredesign.features.autocomplete.user.AutocompleteUserController @@ -58,7 +59,8 @@ class HomeModule { val eventHtmlRenderer = EventHtmlRenderer(GlideApp.with(fragment), fragment.requireContext(), get()) val timelineDateFormatter = TimelineDateFormatter(get()) val timelineMediaSizeProvider = TimelineMediaSizeProvider() - val messageItemFactory = MessageItemFactory(get(), timelineMediaSizeProvider, timelineDateFormatter, eventHtmlRenderer) + val colorProvider = ColorProvider(fragment.requireContext()) + val messageItemFactory = MessageItemFactory(colorProvider, timelineMediaSizeProvider, timelineDateFormatter, eventHtmlRenderer) val timelineItemFactory = TimelineItemFactory(messageItemFactory = messageItemFactory, roomNameItemFactory = RoomNameItemFactory(get()), diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailActions.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailActions.kt index 10552231..7d6ffc04 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailActions.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailActions.kt @@ -17,6 +17,7 @@ package im.vector.riotredesign.features.home.room.detail import com.jaiselrahman.filepicker.model.MediaFile +import im.vector.matrix.android.api.session.room.model.EditAggregatedSummary import im.vector.matrix.android.api.session.room.timeline.Timeline import im.vector.matrix.android.api.session.room.timeline.TimelineEvent @@ -31,6 +32,7 @@ sealed class RoomDetailActions { data class RedactAction(val targetEventId: String, val reason: String? = "") : RoomDetailActions() data class UndoReaction(val targetEventId: String, val key: String, val reason: String? = "") : RoomDetailActions() data class UpdateQuickReactAction(val targetEventId: String,val selectedReaction: String,val opposite: String) : RoomDetailActions() + data class ShowEditHistoryAction(val event: String,val editAggregatedSummary: EditAggregatedSummary) : RoomDetailActions() object AcceptInvite : RoomDetailActions() object RejectInvite : RoomDetailActions() 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 72141e8a..0278607a 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 @@ -53,6 +53,7 @@ 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.EditAggregatedSummary import im.vector.matrix.android.api.session.room.model.Membership import im.vector.matrix.android.api.session.room.model.message.* import im.vector.matrix.android.api.session.room.timeline.TimelineEvent @@ -177,6 +178,12 @@ class RoomDetailFragment : textComposerViewModel.subscribe { renderTextComposerState(it) } roomDetailViewModel.sendMessageResultLiveData.observeEvent(this) { renderSendMessageResult(it) } + roomDetailViewModel.nonBlockingPopAlert.observe(this, Observer { liveEvent -> + liveEvent.getContentIfNotHandled()?.let { + val message = requireContext().getString(it.first, *it.second.toTypedArray()) + showSnackWithMessage(message, Snackbar.LENGTH_LONG) + } + }) actionViewModel.actionCommandEvent.observe(this, Observer { handleActions(it) }) @@ -514,6 +521,12 @@ class RoomDetailFragment : } } + override fun onEditedDecorationClicked(informationData: MessageInformationData, editAggregatedSummary: EditAggregatedSummary?) { + editAggregatedSummary?.also { + roomDetailViewModel.process(RoomDetailActions.ShowEditHistoryAction(informationData.eventId, it)) + } + + } // AutocompleteUserPresenter.Callback override fun onQueryUsers(query: CharSequence?) { @@ -641,6 +654,12 @@ class RoomDetailFragment : } } + fun showSnackWithMessage(message: String, duration: Int = Snackbar.LENGTH_SHORT) { + val snack = Snackbar.make(view!!, message, Snackbar.LENGTH_SHORT) + snack.view.setBackgroundColor(ContextCompat.getColor(requireContext(), R.color.notification_accent_color)) + snack.show() + } + // VectorInviteView.Callback override fun onAcceptInvite() { diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailViewModel.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailViewModel.kt index 1c185add..44c2aa8f 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailViewModel.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailViewModel.kt @@ -28,6 +28,7 @@ import im.vector.matrix.android.api.session.content.ContentAttachmentData import im.vector.matrix.android.api.session.room.model.Membership import im.vector.matrix.android.api.session.room.model.message.MessageType import im.vector.matrix.rx.rx +import im.vector.riotredesign.R import im.vector.riotredesign.core.platform.VectorViewModel import im.vector.riotredesign.core.utils.LiveEvent import im.vector.riotredesign.features.command.CommandParser @@ -36,6 +37,8 @@ import im.vector.riotredesign.features.home.room.VisibleRoomStore import im.vector.riotredesign.features.home.room.detail.timeline.helper.TimelineDisplayableEvents import io.reactivex.rxkotlin.subscribeBy import org.koin.android.ext.android.get +import java.text.SimpleDateFormat +import java.util.* import java.util.concurrent.TimeUnit class RoomDetailViewModel(initialState: RoomDetailViewState, @@ -83,10 +86,16 @@ class RoomDetailViewModel(initialState: RoomDetailViewState, is RoomDetailActions.RedactAction -> handleRedactEvent(action) is RoomDetailActions.UndoReaction -> handleUndoReact(action) is RoomDetailActions.UpdateQuickReactAction -> handleUpdateQuickReaction(action) + is RoomDetailActions.ShowEditHistoryAction -> handleShowEditHistoryReaction(action) } } + private val _nonBlockingPopAlert = MutableLiveData>>>() + val nonBlockingPopAlert: LiveData>>> + get() = _nonBlockingPopAlert + + private val _sendMessageResultLiveData = MutableLiveData>() val sendMessageResultLiveData: LiveData> get() = _sendMessageResultLiveData @@ -161,6 +170,22 @@ class RoomDetailViewModel(initialState: RoomDetailViewState, } } + private fun handleShowEditHistoryReaction(action: RoomDetailActions.ShowEditHistoryAction) { + //TODO temporary implementation + val lastReplace = action.editAggregatedSummary.sourceEvents.lastOrNull()?.let { + room.getTimeLineEvent(it) + } ?: return + + val dateFormat = SimpleDateFormat("EEE, d MMM yyyy HH:mm", Locale.getDefault()) + _nonBlockingPopAlert.postValue(LiveEvent( + Pair(R.string.last_edited_info_message, listOf( + lastReplace.senderName ?: "?", + dateFormat.format(Date(lastReplace.root.originServerTs ?: 0))) + )) + ) + } + + private fun handleChangeTopicSlashCommand(changeTopic: ParsedCommand.ChangeTopic) { _sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.SlashCommandHandled)) 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 4674edd4..c14b863a 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,6 +25,7 @@ import androidx.recyclerview.widget.RecyclerView import com.airbnb.epoxy.EpoxyController import com.airbnb.epoxy.EpoxyModel import im.vector.matrix.android.api.session.events.model.toModel +import im.vector.matrix.android.api.session.room.model.EditAggregatedSummary import im.vector.matrix.android.api.session.room.model.RoomMember import im.vector.matrix.android.api.session.room.model.message.* import im.vector.matrix.android.api.session.room.timeline.Timeline @@ -58,6 +59,7 @@ class TimelineEventController(private val dateFormatter: TimelineDateFormatter, fun onEventLongClicked(informationData: MessageInformationData, messageContent: MessageContent, view: View): Boolean fun onAvatarClicked(informationData: MessageInformationData) fun onMemberNameClicked(informationData: MessageInformationData) + fun onEditedDecorationClicked(informationData: MessageInformationData, editAggregatedSummary: EditAggregatedSummary?) } interface ReactionPillCallback { diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/MessageActionsViewModel.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/MessageActionsViewModel.kt index 74582fcf..a35199a0 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/MessageActionsViewModel.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/MessageActionsViewModel.kt @@ -51,7 +51,8 @@ class MessageActionsViewModel(initialState: MessageActionState) : VectorViewMode val event = currentSession.getRoom(parcel.roomId)?.getTimeLineEvent(parcel.eventId) return if (event != null) { - val messageContent: MessageContent? = event.root.content.toModel() + val messageContent: MessageContent? = event.annotations?.editSummary?.aggregatedContent?.toModel() + ?: event.root.content.toModel() val originTs = event.root.originServerTs MessageActionState( event.root.sender ?: "", 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 03a4f768..8b5ecf89 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 @@ -16,10 +16,12 @@ package im.vector.riotredesign.features.home.room.detail.timeline.factory -import android.graphics.Color import android.text.SpannableStringBuilder import android.text.Spanned +import android.text.TextPaint +import android.text.style.ClickableSpan import android.text.style.ForegroundColorSpan +import android.text.style.RelativeSizeSpan import android.view.View import androidx.annotation.ColorRes import im.vector.matrix.android.api.permalinks.MatrixLinkify @@ -27,6 +29,7 @@ import im.vector.matrix.android.api.permalinks.MatrixPermalinkSpan import im.vector.matrix.android.api.session.events.model.EventType import im.vector.matrix.android.api.session.events.model.RelationType import im.vector.matrix.android.api.session.events.model.toModel +import im.vector.matrix.android.api.session.room.model.EditAggregatedSummary import im.vector.matrix.android.api.session.room.model.message.* import im.vector.matrix.android.api.session.room.send.SendState import im.vector.matrix.android.api.session.room.timeline.TimelineEvent @@ -108,7 +111,10 @@ class MessageItemFactory(private val colorProvider: ColorProvider, // val ev = all.toModel() return when (messageContent) { is MessageEmoteContent -> buildEmoteMessageItem(messageContent, informationData, callback) - is MessageTextContent -> buildTextMessageItem(event.sendState, messageContent, informationData, hasBeenEdited, callback) + is MessageTextContent -> buildTextMessageItem(event.sendState, messageContent, informationData, hasBeenEdited, + event.annotations?.editSummary, + callback + ) is MessageImageContent -> buildImageMessageItem(messageContent, informationData, callback) is MessageNoticeContent -> buildNoticeMessageItem(messageContent, informationData, callback) is MessageVideoContent -> buildVideoMessageItem(messageContent, informationData, callback) @@ -269,6 +275,7 @@ class MessageItemFactory(private val colorProvider: ColorProvider, private fun buildTextMessageItem(sendState: SendState, messageContent: MessageTextContent, informationData: MessageInformationData, hasBeenEdited: Boolean, + editSummary: EditAggregatedSummary?, callback: TimelineEventController.Callback?): MessageTextItem? { val bodyToUse = messageContent.formattedBody?.let { @@ -284,7 +291,28 @@ class MessageItemFactory(private val colorProvider: ColorProvider, spannable.append(linkifiedBody) val editedSuffix = "(edited)" spannable.append(" ").append(editedSuffix) - spannable.setSpan(ForegroundColorSpan(Color.LTGRAY), spannable.indexOf(editedSuffix), spannable.indexOf(editedSuffix) + editedSuffix.length, Spanned.SPAN_INCLUSIVE_EXCLUSIVE) + val color = colorProvider.getColorFromAttribute(R.attr.vctr_list_header_secondary_text_color) + val editStart = spannable.indexOf(editedSuffix) + val editEnd = editStart + editedSuffix.length + spannable.setSpan( + ForegroundColorSpan(color), + editStart, + editEnd, + Spanned.SPAN_INCLUSIVE_EXCLUSIVE) + + spannable.setSpan(RelativeSizeSpan(.9f), editStart, editEnd, Spanned.SPAN_INCLUSIVE_EXCLUSIVE) + spannable.setSpan(object : ClickableSpan() { + override fun onClick(widget: View?) { + callback?.onEditedDecorationClicked(informationData, editSummary) + } + + override fun updateDrawState(ds: TextPaint?) { + //nop + } + }, + editStart, + editEnd, + Spanned.SPAN_INCLUSIVE_EXCLUSIVE) message(spannable) } else { message(linkifiedBody) diff --git a/vector/src/main/res/values/strings_riotX.xml b/vector/src/main/res/values/strings_riotX.xml index 8a9e11d9..556bd50c 100644 --- a/vector/src/main/res/values/strings_riotX.xml +++ b/vector/src/main/res/values/strings_riotX.xml @@ -15,5 +15,6 @@ Event deleted by user Event moderated by room admin + Last edited by %s on %s \ No newline at end of file