diff --git a/CHANGES.md b/CHANGES.md index b42deef96b..7259f70a16 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -10,6 +10,7 @@ Improvements: - Basic support for resending failed messages (retry/remove) - Enable proper cancellation of suspending functions (including db transaction) - Enhances network connectivity checks in SDK + - Add "View Edit History" item in the message bottom sheet (#401) Other changes: - diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/timeline/TimelineEvent.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/timeline/TimelineEvent.kt index acab75df14..5d04d2f52e 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/timeline/TimelineEvent.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/timeline/TimelineEvent.kt @@ -84,6 +84,12 @@ data class TimelineEvent( } } + +/** + * Tells if the event has been edited + */ +fun TimelineEvent.hasBeenEdited() = annotations?.editSummary != null + /** * Get last MessageContent, after a possible edition */ @@ -100,4 +106,4 @@ fun TimelineEvent.getTextEditableContent(): String? { } else { lastContent?.body ?: "" } -} \ No newline at end of file +} diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt index 02e1edb6f7..4d691f50e7 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt @@ -59,7 +59,6 @@ import com.otaliastudios.autocomplete.CharPolicy import im.vector.matrix.android.api.permalinks.PermalinkFactory import im.vector.matrix.android.api.session.Session import im.vector.matrix.android.api.session.events.model.Event -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.send.SendState @@ -803,7 +802,7 @@ class RoomDetailFragment : .show(requireActivity().supportFragmentManager, "DISPLAY_REACTIONS") } - override fun onEditedDecorationClicked(informationData: MessageInformationData, editAggregatedSummary: EditAggregatedSummary?) { + override fun onEditedDecorationClicked(informationData: MessageInformationData) { ViewEditHistoryBottomSheet.newInstance(roomDetailArgs.roomId, informationData) .show(requireActivity().supportFragmentManager, "DISPLAY_EDITS") } @@ -869,6 +868,9 @@ class RoomDetailFragment : } ) } + is SimpleAction.ViewEditHistory -> { + onEditedDecorationClicked(action.messageInformationData) + } is SimpleAction.ViewSource -> { val view = LayoutInflater.from(requireContext()).inflate(R.layout.dialog_event_content, null) view.findViewById(R.id.event_content_text_view)?.let { diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/TimelineEventController.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/TimelineEventController.kt index e8268bace6..e895630910 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/TimelineEventController.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/TimelineEventController.kt @@ -24,7 +24,6 @@ import androidx.recyclerview.widget.ListUpdateCallback import androidx.recyclerview.widget.RecyclerView import com.airbnb.epoxy.EpoxyController import com.airbnb.epoxy.EpoxyModel -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.timeline.Timeline import im.vector.matrix.android.api.session.room.timeline.TimelineEvent @@ -60,7 +59,7 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Tim fun onVideoMessageClicked(messageVideoContent: MessageVideoContent, mediaData: VideoContentRenderer.Data, view: View) fun onFileMessageClicked(eventId: String, messageFileContent: MessageFileContent) fun onAudioMessageClicked(messageAudioContent: MessageAudioContent) - fun onEditedDecorationClicked(informationData: MessageInformationData, editAggregatedSummary: EditAggregatedSummary?) + fun onEditedDecorationClicked(informationData: MessageInformationData) } interface ReactionPillCallback { @@ -159,7 +158,7 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Tim synchronized(modelCache) { for (i in 0 until modelCache.size) { if (modelCache[i]?.eventId == eventIdToHighlight - || modelCache[i]?.eventId == this.eventIdToHighlight) { + || modelCache[i]?.eventId == this.eventIdToHighlight) { modelCache[i] = null } } @@ -220,8 +219,8 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Tim // Should be build if not cached or if cached but contains mergedHeader or formattedDay // We then are sure we always have items up to date. if (modelCache[position] == null - || modelCache[position]?.mergedHeaderModel != null - || modelCache[position]?.formattedDayModel != null) { + || modelCache[position]?.mergedHeaderModel != null + || modelCache[position]?.formattedDayModel != null) { modelCache[position] = buildItemModels(position, currentSnapshot) } } @@ -294,8 +293,7 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Tim // We try to find if one of the item id were used as mergeItemCollapseStates key // => handle case where paginating from mergeable events and we get more val previousCollapseStateKey = mergedEventIds.intersect(mergeItemCollapseStates.keys).firstOrNull() - val initialCollapseState = mergeItemCollapseStates.remove(previousCollapseStateKey) - ?: true + val initialCollapseState = mergeItemCollapseStates.remove(previousCollapseStateKey) ?: true val isCollapsed = mergeItemCollapseStates.getOrPut(event.localId) { initialCollapseState } if (isCollapsed) { collapsedEventIds.addAll(mergedEventIds) diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageMenuViewModel.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageMenuViewModel.kt index 0cc95af7d4..ae8803f5c8 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageMenuViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageMenuViewModel.kt @@ -29,6 +29,7 @@ import im.vector.matrix.android.api.session.room.model.message.MessageImageConte import im.vector.matrix.android.api.session.room.model.message.MessageType import im.vector.matrix.android.api.session.room.send.SendState import im.vector.matrix.android.api.session.room.timeline.TimelineEvent +import im.vector.matrix.android.api.session.room.timeline.hasBeenEdited import im.vector.matrix.rx.RxRoom import im.vector.riotx.R import im.vector.riotx.core.extensions.canReact @@ -55,6 +56,8 @@ sealed class SimpleAction(@StringRes val titleRes: Int, @DrawableRes val iconRes data class Flag(val eventId: String) : SimpleAction(R.string.report_content, R.drawable.ic_flag) data class QuickReact(val eventId: String, val clickedOn: String, val add: Boolean) : SimpleAction(0, 0) data class ViewReactions(val messageInformationData: MessageInformationData) : SimpleAction(R.string.message_view_reaction, R.drawable.ic_view_reactions) + data class ViewEditHistory(val messageInformationData: MessageInformationData) : + SimpleAction(R.string.message_view_edit_history, R.drawable.ic_view_edit_history) } data class MessageMenuState( @@ -155,6 +158,10 @@ class MessageMenuViewModel @AssistedInject constructor(@Assisted initialState: M add(SimpleAction.ViewReactions(informationData)) } + if (event.hasBeenEdited()) { + add(SimpleAction.ViewEditHistory(informationData)) + } + if (canShare(type)) { if (messageContent is MessageImageContent) { add(SimpleAction.Share(session.contentUrlResolver().resolveFullSize(messageContent.url))) diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/MessageItemFactory.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/MessageItemFactory.kt index c9da3ce6d0..f81ef9d333 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/MessageItemFactory.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/MessageItemFactory.kt @@ -28,7 +28,6 @@ import im.vector.matrix.android.api.permalinks.MatrixLinkify import im.vector.matrix.android.api.permalinks.MatrixPermalinkSpan 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 @@ -118,13 +117,11 @@ class MessageItemFactory @Inject constructor( return when (messageContent) { is MessageEmoteContent -> buildEmoteMessageItem(messageContent, informationData, - event.annotations?.editSummary, highlight, callback) is MessageTextContent -> buildTextMessageItem(event.root.sendState, messageContent, informationData, - event.annotations?.editSummary, highlight, callback ) @@ -298,7 +295,6 @@ class MessageItemFactory @Inject constructor( private fun buildTextMessageItem(sendState: SendState, messageContent: MessageTextContent, informationData: MessageInformationData, - editSummary: EditAggregatedSummary?, highlight: Boolean, callback: TimelineEventController.Callback?): MessageTextItem? { @@ -311,7 +307,7 @@ class MessageItemFactory @Inject constructor( return MessageTextItem_() .apply { if (informationData.hasBeenEdited) { - val spannable = annotateWithEdited(linkifiedBody, callback, informationData, editSummary) + val spannable = annotateWithEdited(linkifiedBody, callback, informationData) message(spannable) } else { message(linkifiedBody) @@ -338,8 +334,7 @@ class MessageItemFactory @Inject constructor( private fun annotateWithEdited(linkifiedBody: CharSequence, callback: TimelineEventController.Callback?, - informationData: MessageInformationData, - editSummary: EditAggregatedSummary?): SpannableStringBuilder { + informationData: MessageInformationData): SpannableStringBuilder { val spannable = SpannableStringBuilder() spannable.append(linkifiedBody) val editedSuffix = stringProvider.getString(R.string.edited_suffix) @@ -356,7 +351,7 @@ class MessageItemFactory @Inject constructor( spannable.setSpan(RelativeSizeSpan(.9f), editStart, editEnd, Spanned.SPAN_INCLUSIVE_EXCLUSIVE) spannable.setSpan(object : ClickableSpan() { override fun onClick(widget: View?) { - callback?.onEditedDecorationClicked(informationData, editSummary) + callback?.onEditedDecorationClicked(informationData) } override fun updateDrawState(ds: TextPaint?) { @@ -408,7 +403,6 @@ class MessageItemFactory @Inject constructor( private fun buildEmoteMessageItem(messageContent: MessageEmoteContent, informationData: MessageInformationData, - editSummary: EditAggregatedSummary?, highlight: Boolean, callback: TimelineEventController.Callback?): MessageTextItem? { @@ -419,7 +413,7 @@ class MessageItemFactory @Inject constructor( return MessageTextItem_() .apply { if (informationData.hasBeenEdited) { - val spannable = annotateWithEdited(message, callback, informationData, editSummary) + val spannable = annotateWithEdited(message, callback, informationData) message(spannable) } else { message(message) diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/util/MessageInformationDataFactory.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/util/MessageInformationDataFactory.kt index 5cd873ff10..fe15c5d281 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/util/MessageInformationDataFactory.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/util/MessageInformationDataFactory.kt @@ -18,6 +18,7 @@ package im.vector.riotx.features.home.room.detail.timeline.util import im.vector.matrix.android.api.session.events.model.EventType import im.vector.matrix.android.api.session.room.timeline.TimelineEvent +import im.vector.matrix.android.api.session.room.timeline.hasBeenEdited import im.vector.riotx.core.extensions.localDateTime import im.vector.riotx.core.resources.ColorProvider import im.vector.riotx.core.utils.isSingleEmoji @@ -59,8 +60,6 @@ class MessageInformationDataFactory @Inject constructor(private val timelineDate ?: "")) } - val hasBeenEdited = event.annotations?.editSummary != null - return MessageInformationData( eventId = eventId, senderId = event.root.senderId ?: "", @@ -74,7 +73,7 @@ class MessageInformationDataFactory @Inject constructor(private val timelineDate ?.map { ReactionInfoData(it.key, it.count, it.addedByMe, it.localEchoEvents.isEmpty()) }, - hasBeenEdited = hasBeenEdited, + hasBeenEdited = event.hasBeenEdited(), hasPendingEdits = event.annotations?.editSummary?.localEchos?.any() ?: false ) } diff --git a/vector/src/main/res/drawable/ic_view_edit_history.xml b/vector/src/main/res/drawable/ic_view_edit_history.xml new file mode 100644 index 0000000000..f94211d440 --- /dev/null +++ b/vector/src/main/res/drawable/ic_view_edit_history.xml @@ -0,0 +1,46 @@ + + + + + + + diff --git a/vector/src/main/res/values/strings_riotX.xml b/vector/src/main/res/values/strings_riotX.xml index 6bcec316dd..efa6d2cc57 100644 --- a/vector/src/main/res/values/strings_riotX.xml +++ b/vector/src/main/res/values/strings_riotX.xml @@ -9,4 +9,7 @@ "Filter by username or ID…" "Joining room…" + + View Edit History + \ No newline at end of file