Merge pull request #407 from vector-im/feature/pending_edits_ux

Feature/pending edits ux
This commit is contained in:
Valere 2019-07-22 23:53:26 +02:00 committed by GitHub
commit 6176520805
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 64 additions and 23 deletions

View file

@ -5,6 +5,7 @@ Features:
- -
Improvements: Improvements:
- UI for pending edits (#193)
- UX image preview screen transition (#393) - UX image preview screen transition (#393)
Other changes: Other changes:

View file

@ -83,21 +83,21 @@ internal class DefaultEventRelationsAggregationTask @Inject constructor(
if (event.unsignedData?.relations?.annotations != null) { if (event.unsignedData?.relations?.annotations != null) {
Timber.v("###REACTION Agreggation in room $roomId for event ${event.eventId}") Timber.v("###REACTION Agreggation in room $roomId for event ${event.eventId}")
handleInitialAggregatedRelations(event, roomId, event.unsignedData.relations.annotations, realm) handleInitialAggregatedRelations(event, roomId, event.unsignedData.relations.annotations, realm)
} else {
val content: MessageContent? = event.content.toModel() EventAnnotationsSummaryEntity.where(realm, event.eventId
if (content?.relatesTo?.type == RelationType.REPLACE) { ?: "").findFirst()?.let {
Timber.v("###REPLACE in room $roomId for event ${event.eventId}") TimelineEventEntity.where(realm, eventId = event.eventId
//A replace! ?: "").findFirst()?.let { tet ->
handleReplace(realm, event, content, roomId, isLocalEcho) tet.annotations = it
}
} }
} }
EventAnnotationsSummaryEntity.where(realm, event.eventId val content: MessageContent? = event.content.toModel()
?: "").findFirst()?.let { if (content?.relatesTo?.type == RelationType.REPLACE) {
TimelineEventEntity.where(realm, eventId = event.eventId Timber.v("###REPLACE in room $roomId for event ${event.eventId}")
?: "").findFirst()?.let { tet -> //A replace!
tet.annotations = it handleReplace(realm, event, content, roomId, isLocalEcho)
}
} }
@ -178,11 +178,12 @@ internal class DefaultEventRelationsAggregationTask @Inject constructor(
Timber.v("###REPLACE new edit summary for ${targetEventId}, creating one (localEcho:$isLocalEcho)") Timber.v("###REPLACE new edit summary for ${targetEventId}, creating one (localEcho:$isLocalEcho)")
//create the edit summary //create the edit summary
val editSummary = realm.createObject(EditAggregatedSummaryEntity::class.java) val editSummary = realm.createObject(EditAggregatedSummaryEntity::class.java)
editSummary.lastEditTs = event.originServerTs ?: System.currentTimeMillis()
editSummary.aggregatedContent = ContentMapper.map(newContent) editSummary.aggregatedContent = ContentMapper.map(newContent)
if (isLocalEcho) { if (isLocalEcho) {
editSummary.lastEditTs = 0
editSummary.sourceLocalEchoEvents.add(eventId) editSummary.sourceLocalEchoEvents.add(eventId)
} else { } else {
editSummary.lastEditTs = event.originServerTs ?: 0
editSummary.sourceEvents.add(eventId) editSummary.sourceEvents.add(eventId)
} }
@ -200,13 +201,26 @@ internal class DefaultEventRelationsAggregationTask @Inject constructor(
Timber.v("###REPLACE Receiving remote echo of edit (edit already done)") Timber.v("###REPLACE Receiving remote echo of edit (edit already done)")
existingSummary.sourceLocalEchoEvents.remove(txId) existingSummary.sourceLocalEchoEvents.remove(txId)
existingSummary.sourceEvents.add(event.eventId) existingSummary.sourceEvents.add(event.eventId)
} else if (event.originServerTs ?: 0 > existingSummary.lastEditTs) { } else if (
isLocalEcho // do not rely on ts for local echo, take it
|| event.originServerTs ?: 0 >= existingSummary.lastEditTs
) {
Timber.v("###REPLACE Computing aggregated edit summary (isLocalEcho:$isLocalEcho)") Timber.v("###REPLACE Computing aggregated edit summary (isLocalEcho:$isLocalEcho)")
existingSummary.lastEditTs = event.originServerTs ?: System.currentTimeMillis() if (!isLocalEcho) {
//Do not take local echo originServerTs here, could mess up ordering (keep old ts)
existingSummary.lastEditTs = event.originServerTs ?: System.currentTimeMillis()
}
existingSummary.aggregatedContent = ContentMapper.map(newContent) existingSummary.aggregatedContent = ContentMapper.map(newContent)
existingSummary.sourceEvents.add(eventId) if (isLocalEcho) {
existingSummary.sourceLocalEchoEvents.add(eventId)
} else {
existingSummary.sourceEvents.add(eventId)
}
} else { } else {
//ignore this event for the summary //ignore this event for the summary (back paginate)
if (!isLocalEcho) {
existingSummary.sourceEvents.add(eventId)
}
Timber.v("###REPLACE ignoring event for summary, it's to old $eventId") Timber.v("###REPLACE ignoring event for summary, it's to old $eventId")
} }
} }

View file

@ -41,11 +41,13 @@ import im.vector.riotx.core.epoxy.VectorEpoxyModel
import im.vector.riotx.core.linkify.VectorLinkify import im.vector.riotx.core.linkify.VectorLinkify
import im.vector.riotx.core.resources.ColorProvider import im.vector.riotx.core.resources.ColorProvider
import im.vector.riotx.core.resources.StringProvider import im.vector.riotx.core.resources.StringProvider
import im.vector.riotx.core.resources.UserPreferencesProvider
import im.vector.riotx.core.utils.DebouncedClickListener import im.vector.riotx.core.utils.DebouncedClickListener
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
import im.vector.riotx.features.home.room.detail.timeline.helper.ContentUploadStateTrackerBinder import im.vector.riotx.features.home.room.detail.timeline.helper.ContentUploadStateTrackerBinder
import im.vector.riotx.features.home.room.detail.timeline.helper.TimelineMediaSizeProvider import im.vector.riotx.features.home.room.detail.timeline.helper.TimelineMediaSizeProvider
import im.vector.riotx.features.home.room.detail.timeline.helper.senderAvatar
import im.vector.riotx.features.home.room.detail.timeline.item.* import im.vector.riotx.features.home.room.detail.timeline.item.*
import im.vector.riotx.features.home.room.detail.timeline.util.MessageInformationDataFactory import im.vector.riotx.features.home.room.detail.timeline.util.MessageInformationDataFactory
import im.vector.riotx.features.html.EventHtmlRenderer import im.vector.riotx.features.html.EventHtmlRenderer
@ -63,7 +65,8 @@ class MessageItemFactory @Inject constructor(
private val emojiCompatFontProvider: EmojiCompatFontProvider, private val emojiCompatFontProvider: EmojiCompatFontProvider,
private val imageContentRenderer: ImageContentRenderer, private val imageContentRenderer: ImageContentRenderer,
private val messageInformationDataFactory: MessageInformationDataFactory, private val messageInformationDataFactory: MessageInformationDataFactory,
private val contentUploadStateTrackerBinder: ContentUploadStateTrackerBinder) { private val contentUploadStateTrackerBinder: ContentUploadStateTrackerBinder,
private val userPreferencesProvider: UserPreferencesProvider) {
fun create(event: TimelineEvent, fun create(event: TimelineEvent,
@ -89,7 +92,26 @@ class MessageItemFactory @Inject constructor(
|| event.isEncrypted() && event.root.content.toModel<EncryptedEventContent>()?.relatesTo?.type == RelationType.REPLACE || event.isEncrypted() && event.root.content.toModel<EncryptedEventContent>()?.relatesTo?.type == RelationType.REPLACE
) { ) {
// ignore replace event, the targeted id is already edited // ignore replace event, the targeted id is already edited
return BlankItem_() if (userPreferencesProvider.shouldShowHiddenEvents()) {
//These are just for debug to display hidden event, they should be filtered out in normal mode
val informationData = MessageInformationData(
eventId = event.root.eventId ?: "?",
senderId = event.root.senderId ?: "",
sendState = event.sendState,
time = "",
avatarUrl = event.senderAvatar(),
memberName = "",
showInformation = false
)
return NoticeItem_()
.avatarRenderer(avatarRenderer)
.informationData(informationData)
.noticeText("{ \"type\": ${event.root.getClearType()} }")
.highlighted(highlight)
.baseCallback(callback)
} else {
return BlankItem_()
}
} }
// val all = event.root.toContent() // val all = event.root.toContent()
// val ev = all.toModel<Event>() // val ev = all.toModel<Event>()

View file

@ -29,6 +29,7 @@ import androidx.core.view.children
import androidx.core.view.isGone import androidx.core.view.isGone
import androidx.core.view.isVisible import androidx.core.view.isVisible
import com.airbnb.epoxy.EpoxyAttribute import com.airbnb.epoxy.EpoxyAttribute
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.utils.DebouncedClickListener import im.vector.riotx.core.utils.DebouncedClickListener
@ -163,7 +164,8 @@ abstract class AbsMessageItem<H : AbsMessageItem.Holder> : BaseEventItem<H>() {
protected fun renderSendState(root: View, textView: TextView?) { protected fun renderSendState(root: View, textView: TextView?) {
root.isClickable = informationData.sendState.isSent() root.isClickable = informationData.sendState.isSent()
textView?.setTextColor(colorProvider.getMessageTextColor(informationData.sendState)) val state = if (informationData.hasPendingEdits) SendState.UNSENT else informationData.sendState
textView?.setTextColor(colorProvider.getMessageTextColor(state))
} }
abstract class Holder(@IdRes stubId: Int) : BaseHolder(stubId) { abstract class Holder(@IdRes stubId: Int) : BaseHolder(stubId) {

View file

@ -30,8 +30,9 @@ data class MessageInformationData(
val memberName: CharSequence? = null, val memberName: CharSequence? = null,
val showInformation: Boolean = true, val showInformation: Boolean = true,
/*List of reactions (emoji,count,isSelected)*/ /*List of reactions (emoji,count,isSelected)*/
var orderedReactionList: List<ReactionInfoData>? = null, val orderedReactionList: List<ReactionInfoData>? = null,
var hasBeenEdited: Boolean = false val hasBeenEdited: Boolean = false,
val hasPendingEdits: Boolean = false
) : Parcelable ) : Parcelable

View file

@ -74,7 +74,8 @@ class MessageInformationDataFactory @Inject constructor(private val timelineDate
?.map { ?.map {
ReactionInfoData(it.key, it.count, it.addedByMe, it.localEchoEvents.isEmpty()) ReactionInfoData(it.key, it.count, it.addedByMe, it.localEchoEvents.isEmpty())
}, },
hasBeenEdited = hasBeenEdited hasBeenEdited = hasBeenEdited,
hasPendingEdits = event.annotations?.editSummary?.localEchos?.any() ?: false
) )
} }
} }