forked from GitHub-Mirror/riotX-android
Fix / Bug aggregation on initial sync
fix / All messages were not processed due to a test exiting the for loop + started adding context menu for non room messages
This commit is contained in:
parent
3f1bf00fdd
commit
7409003949
@ -48,7 +48,7 @@ android {
|
|||||||
buildConfigField "boolean", "LOG_PRIVATE_DATA", "false"
|
buildConfigField "boolean", "LOG_PRIVATE_DATA", "false"
|
||||||
|
|
||||||
// Set to BODY instead of NONE to enable logging
|
// Set to BODY instead of NONE to enable logging
|
||||||
buildConfigField "okhttp3.logging.HttpLoggingInterceptor.Level", "OKHTTP_LOGGING_LEVEL", "okhttp3.logging.HttpLoggingInterceptor.Level.BASIC"
|
buildConfigField "okhttp3.logging.HttpLoggingInterceptor.Level", "OKHTTP_LOGGING_LEVEL", "okhttp3.logging.HttpLoggingInterceptor.Level.NONE"
|
||||||
}
|
}
|
||||||
|
|
||||||
release {
|
release {
|
||||||
@ -91,7 +91,7 @@ dependencies {
|
|||||||
def moshi_version = '1.8.0'
|
def moshi_version = '1.8.0'
|
||||||
def lifecycle_version = '2.0.0'
|
def lifecycle_version = '2.0.0'
|
||||||
def coroutines_version = "1.0.1"
|
def coroutines_version = "1.0.1"
|
||||||
def markwon_version = '3.0.0-SNAPSHOT'
|
def markwon_version = '3.0.0'
|
||||||
|
|
||||||
implementation fileTree(dir: 'libs', include: ['*.aar'])
|
implementation fileTree(dir: 'libs', include: ['*.aar'])
|
||||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
||||||
|
@ -16,6 +16,7 @@
|
|||||||
|
|
||||||
package im.vector.matrix.android.internal.database.mapper
|
package im.vector.matrix.android.internal.database.mapper
|
||||||
|
|
||||||
|
import com.squareup.moshi.JsonDataException
|
||||||
import im.vector.matrix.android.api.session.events.model.Event
|
import im.vector.matrix.android.api.session.events.model.Event
|
||||||
import im.vector.matrix.android.api.session.events.model.UnsignedData
|
import im.vector.matrix.android.api.session.events.model.UnsignedData
|
||||||
import im.vector.matrix.android.internal.database.model.EventEntity
|
import im.vector.matrix.android.internal.database.model.EventEntity
|
||||||
@ -46,8 +47,16 @@ internal object EventMapper {
|
|||||||
|
|
||||||
fun map(eventEntity: EventEntity): Event {
|
fun map(eventEntity: EventEntity): Event {
|
||||||
//TODO proxy the event to only parse unsigned data when accessed?
|
//TODO proxy the event to only parse unsigned data when accessed?
|
||||||
var ud = if (eventEntity.unsignedData.isNullOrBlank()) null
|
val ud = if (eventEntity.unsignedData.isNullOrBlank()) {
|
||||||
else MoshiProvider.providesMoshi().adapter(UnsignedData::class.java).fromJson(eventEntity.unsignedData)
|
null
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
MoshiProvider.providesMoshi().adapter(UnsignedData::class.java).fromJson(eventEntity.unsignedData)
|
||||||
|
} catch (t: JsonDataException) {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
return Event(
|
return Event(
|
||||||
type = eventEntity.type,
|
type = eventEntity.type,
|
||||||
eventId = eventEntity.eventId,
|
eventId = eventEntity.eventId,
|
||||||
|
@ -27,7 +27,6 @@ import im.vector.matrix.android.internal.database.model.*
|
|||||||
import im.vector.matrix.android.internal.database.query.create
|
import im.vector.matrix.android.internal.database.query.create
|
||||||
import im.vector.matrix.android.internal.database.query.where
|
import im.vector.matrix.android.internal.database.query.where
|
||||||
import im.vector.matrix.android.internal.task.Task
|
import im.vector.matrix.android.internal.task.Task
|
||||||
import im.vector.matrix.android.internal.util.tryTransactionAsync
|
|
||||||
import im.vector.matrix.android.internal.util.tryTransactionSync
|
import im.vector.matrix.android.internal.util.tryTransactionSync
|
||||||
import io.realm.Realm
|
import io.realm.Realm
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
@ -49,21 +48,30 @@ internal class DefaultEventRelationsAggregationTask(private val monarchy: Monarc
|
|||||||
private val SHOULD_HANDLE_SERVER_AGREGGATION = false
|
private val SHOULD_HANDLE_SERVER_AGREGGATION = false
|
||||||
|
|
||||||
override fun execute(params: EventRelationsAggregationTask.Params): Try<Unit> {
|
override fun execute(params: EventRelationsAggregationTask.Params): Try<Unit> {
|
||||||
|
val events = params.events
|
||||||
|
val userId = params.userId
|
||||||
return monarchy.tryTransactionSync { realm ->
|
return monarchy.tryTransactionSync { realm ->
|
||||||
update(realm, params.events, params.userId)
|
Timber.v(">>> DefaultEventRelationsAggregationTask[${params.hashCode()}] called with ${events.size} events")
|
||||||
|
update(realm, events, userId)
|
||||||
|
Timber.v("<<< DefaultEventRelationsAggregationTask[${params.hashCode()}] finished")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun update(realm: Realm, events: List<Pair<Event, SendState>>, userId: String) {
|
private fun update(realm: Realm, events: List<Pair<Event, SendState>>, userId: String) {
|
||||||
events.forEach { pair ->
|
events.forEach { pair ->
|
||||||
val roomId = pair.first.roomId ?: return@forEach
|
try { //Temporary catch, should be removed
|
||||||
|
val roomId = pair.first.roomId
|
||||||
|
if (roomId == null) {
|
||||||
|
Timber.w("Event has no room id ${pair.first.eventId}")
|
||||||
|
return@forEach
|
||||||
|
}
|
||||||
val event = pair.first
|
val event = pair.first
|
||||||
val sendState = pair.second
|
val sendState = pair.second
|
||||||
val isLocalEcho = sendState == SendState.UNSENT
|
val isLocalEcho = sendState == SendState.UNSENT
|
||||||
when (event.type) {
|
when (event.type) {
|
||||||
EventType.REACTION -> {
|
EventType.REACTION -> {
|
||||||
//we got a reaction!!
|
//we got a reaction!!
|
||||||
Timber.v("###REACTION in room $roomId")
|
Timber.v("###REACTION in room $roomId , reaction eventID ${event.eventId}")
|
||||||
handleReaction(event, roomId, realm, userId, isLocalEcho)
|
handleReaction(event, roomId, realm, userId, isLocalEcho)
|
||||||
}
|
}
|
||||||
EventType.MESSAGE -> {
|
EventType.MESSAGE -> {
|
||||||
@ -82,7 +90,7 @@ internal class DefaultEventRelationsAggregationTask(private val monarchy: Monarc
|
|||||||
}
|
}
|
||||||
EventType.REDACTION -> {
|
EventType.REDACTION -> {
|
||||||
val eventToPrune = event.redacts?.let { EventEntity.where(realm, eventId = it).findFirst() }
|
val eventToPrune = event.redacts?.let { EventEntity.where(realm, eventId = it).findFirst() }
|
||||||
?: return
|
?: return@forEach
|
||||||
when (eventToPrune.type) {
|
when (eventToPrune.type) {
|
||||||
EventType.MESSAGE -> {
|
EventType.MESSAGE -> {
|
||||||
Timber.d("REDACTION for message ${eventToPrune.eventId}")
|
Timber.d("REDACTION for message ${eventToPrune.eventId}")
|
||||||
@ -101,8 +109,14 @@ internal class DefaultEventRelationsAggregationTask(private val monarchy: Monarc
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else -> Timber.v("UnHandled event ${event.eventId}")
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (t: Throwable) {
|
||||||
|
Timber.e(t, "## Should not happen ")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleReplace(realm: Realm, event: Event, content: MessageContent, roomId: String, isLocalEcho: Boolean) {
|
private fun handleReplace(realm: Realm, event: Event, content: MessageContent, roomId: String, isLocalEcho: Boolean) {
|
||||||
@ -112,7 +126,7 @@ internal class DefaultEventRelationsAggregationTask(private val monarchy: Monarc
|
|||||||
//ok, this is a replace
|
//ok, this is a replace
|
||||||
var existing = EventAnnotationsSummaryEntity.where(realm, targetEventId).findFirst()
|
var existing = EventAnnotationsSummaryEntity.where(realm, targetEventId).findFirst()
|
||||||
if (existing == null) {
|
if (existing == null) {
|
||||||
Timber.v("###REPLACE creating no relation summary for ${targetEventId}")
|
Timber.v("###REPLACE creating new relation summary for ${targetEventId}")
|
||||||
existing = EventAnnotationsSummaryEntity.create(realm, targetEventId)
|
existing = EventAnnotationsSummaryEntity.create(realm, targetEventId)
|
||||||
existing.roomId = roomId
|
existing.roomId = roomId
|
||||||
}
|
}
|
||||||
@ -120,7 +134,7 @@ internal class DefaultEventRelationsAggregationTask(private val monarchy: Monarc
|
|||||||
//we have it
|
//we have it
|
||||||
val existingSummary = existing.editSummary
|
val existingSummary = existing.editSummary
|
||||||
if (existingSummary == null) {
|
if (existingSummary == null) {
|
||||||
Timber.v("###REPLACE no 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.lastEditTs = event.originServerTs ?: System.currentTimeMillis()
|
||||||
@ -181,13 +195,19 @@ internal class DefaultEventRelationsAggregationTask(private val monarchy: Monarc
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun handleReaction(event: Event, roomId: String, realm: Realm, userId: String, isLocalEcho: Boolean) {
|
private fun handleReaction(event: Event, roomId: String, realm: Realm, userId: String, isLocalEcho: Boolean) {
|
||||||
event.content.toModel<ReactionContent>()?.let { content ->
|
val content = event.content.toModel<ReactionContent>()
|
||||||
|
if (content == null) {
|
||||||
|
Timber.e("Malformed reaction content ${event.content}")
|
||||||
|
return
|
||||||
|
}
|
||||||
//rel_type must be m.annotation
|
//rel_type must be m.annotation
|
||||||
if (RelationType.ANNOTATION == content.relatesTo?.type) {
|
if (RelationType.ANNOTATION == content.relatesTo?.type) {
|
||||||
val reaction = content.relatesTo.key
|
val reaction = content.relatesTo.key
|
||||||
val eventId = content.relatesTo.eventId
|
val relatedEventID = content.relatesTo.eventId
|
||||||
val eventSummary = EventAnnotationsSummaryEntity.where(realm, eventId).findFirst()
|
val reactionEventId = event.eventId
|
||||||
?: EventAnnotationsSummaryEntity.create(realm, eventId).apply { this.roomId = roomId }
|
Timber.v("Reaction $reactionEventId relates to $relatedEventID")
|
||||||
|
val eventSummary = EventAnnotationsSummaryEntity.where(realm, relatedEventID).findFirst()
|
||||||
|
?: EventAnnotationsSummaryEntity.create(realm, relatedEventID).apply { this.roomId = roomId }
|
||||||
|
|
||||||
var sum = eventSummary.reactionsSummary.find { it.key == reaction }
|
var sum = eventSummary.reactionsSummary.find { it.key == reaction }
|
||||||
val txId = event.unsignedData?.transactionId
|
val txId = event.unsignedData?.transactionId
|
||||||
@ -205,20 +225,20 @@ internal class DefaultEventRelationsAggregationTask(private val monarchy: Monarc
|
|||||||
} else {
|
} else {
|
||||||
Timber.v("Adding synced reaction $reaction")
|
Timber.v("Adding synced reaction $reaction")
|
||||||
sum.count = 1
|
sum.count = 1
|
||||||
sum.sourceEvents.add(event.eventId)
|
sum.sourceEvents.add(reactionEventId)
|
||||||
}
|
}
|
||||||
sum.addedByMe = sum.addedByMe || (userId == event.sender)
|
sum.addedByMe = sum.addedByMe || (userId == event.sender)
|
||||||
eventSummary.reactionsSummary.add(sum)
|
eventSummary.reactionsSummary.add(sum)
|
||||||
} else {
|
} else {
|
||||||
//is this a known event (is possible? pagination?)
|
//is this a known event (is possible? pagination?)
|
||||||
if (!sum.sourceEvents.contains(eventId)) {
|
if (!sum.sourceEvents.contains(reactionEventId)) {
|
||||||
|
|
||||||
//check if it's not the sync of a local echo
|
//check if it's not the sync of a local echo
|
||||||
if (!isLocalEcho && sum.sourceLocalEcho.contains(txId)) {
|
if (!isLocalEcho && sum.sourceLocalEcho.contains(txId)) {
|
||||||
//ok it has already been counted, just sync the list, do not touch count
|
//ok it has already been counted, just sync the list, do not touch count
|
||||||
Timber.v("Ignoring synced of local echo for reaction $reaction")
|
Timber.v("Ignoring synced of local echo for reaction $reaction")
|
||||||
sum.sourceLocalEcho.remove(txId)
|
sum.sourceLocalEcho.remove(txId)
|
||||||
sum.sourceEvents.add(event.eventId)
|
sum.sourceEvents.add(reactionEventId)
|
||||||
} else {
|
} else {
|
||||||
sum.count += 1
|
sum.count += 1
|
||||||
if (isLocalEcho) {
|
if (isLocalEcho) {
|
||||||
@ -226,7 +246,7 @@ internal class DefaultEventRelationsAggregationTask(private val monarchy: Monarc
|
|||||||
sum.sourceLocalEcho.add(txId)
|
sum.sourceLocalEcho.add(txId)
|
||||||
} else {
|
} else {
|
||||||
Timber.v("Adding synced reaction $reaction")
|
Timber.v("Adding synced reaction $reaction")
|
||||||
sum.sourceEvents.add(event.eventId)
|
sum.sourceEvents.add(reactionEventId)
|
||||||
}
|
}
|
||||||
|
|
||||||
sum.addedByMe = sum.addedByMe || (userId == event.sender)
|
sum.addedByMe = sum.addedByMe || (userId == event.sender)
|
||||||
@ -235,8 +255,10 @@ internal class DefaultEventRelationsAggregationTask(private val monarchy: Monarc
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
Timber.e("Unknwon relation type ${content.relatesTo?.type} for event ${event.eventId}")
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -46,11 +46,11 @@ internal class EventRelationsAggregationUpdater(monarchy: Monarchy,
|
|||||||
|
|
||||||
override fun processChanges(inserted: List<EventEntity>, updated: List<EventEntity>, deleted: List<EventEntity>) {
|
override fun processChanges(inserted: List<EventEntity>, updated: List<EventEntity>, deleted: List<EventEntity>) {
|
||||||
Timber.v("EventRelationsAggregationUpdater called with ${inserted.size} insertions")
|
Timber.v("EventRelationsAggregationUpdater called with ${inserted.size} insertions")
|
||||||
val inserted = inserted
|
val domainInserted = inserted
|
||||||
.mapNotNull { it.asDomain() to it.sendState }
|
.map { it.asDomain() to it.sendState }
|
||||||
|
|
||||||
val params = EventRelationsAggregationTask.Params(
|
val params = EventRelationsAggregationTask.Params(
|
||||||
inserted,
|
domainInserted,
|
||||||
credentials.userId
|
credentials.userId
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -38,7 +38,12 @@ internal class TaskExecutor(private val coroutineDispatchers: MatrixCoroutineDis
|
|||||||
task.execute(task.params)
|
task.execute(task.params)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
resultOrFailure.fold({ task.callback.onFailure(it) }, { task.callback.onSuccess(it) })
|
resultOrFailure.fold({
|
||||||
|
Timber.d(it, "Task failed")
|
||||||
|
task.callback.onFailure(it)
|
||||||
|
}, {
|
||||||
|
task.callback.onSuccess(it)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
return CancelableCoroutine(job)
|
return CancelableCoroutine(job)
|
||||||
}
|
}
|
||||||
|
@ -84,6 +84,7 @@ android {
|
|||||||
debug {
|
debug {
|
||||||
resValue "bool", "debug_mode", "true"
|
resValue "bool", "debug_mode", "true"
|
||||||
buildConfigField "boolean", "LOW_PRIVACY_LOG_ENABLE", "false"
|
buildConfigField "boolean", "LOW_PRIVACY_LOG_ENABLE", "false"
|
||||||
|
buildConfigField "boolean", "SHOW_HIDDEN_TIMELINE_EVENTS", "false"
|
||||||
|
|
||||||
signingConfig signingConfigs.debug
|
signingConfig signingConfigs.debug
|
||||||
}
|
}
|
||||||
@ -91,6 +92,7 @@ android {
|
|||||||
release {
|
release {
|
||||||
resValue "bool", "debug_mode", "false"
|
resValue "bool", "debug_mode", "false"
|
||||||
buildConfigField "boolean", "LOW_PRIVACY_LOG_ENABLE", "false"
|
buildConfigField "boolean", "LOW_PRIVACY_LOG_ENABLE", "false"
|
||||||
|
buildConfigField "boolean", "SHOW_HIDDEN_TIMELINE_EVENTS", "false"
|
||||||
|
|
||||||
minifyEnabled false
|
minifyEnabled false
|
||||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||||
@ -132,7 +134,7 @@ dependencies {
|
|||||||
def epoxy_version = "3.3.0"
|
def epoxy_version = "3.3.0"
|
||||||
def arrow_version = "0.8.2"
|
def arrow_version = "0.8.2"
|
||||||
def coroutines_version = "1.0.1"
|
def coroutines_version = "1.0.1"
|
||||||
def markwon_version = '3.0.0-SNAPSHOT'
|
def markwon_version = '3.0.0'
|
||||||
def big_image_viewer_version = '1.5.6'
|
def big_image_viewer_version = '1.5.6'
|
||||||
def glide_version = '4.9.0'
|
def glide_version = '4.9.0'
|
||||||
def moshi_version = '1.8.0'
|
def moshi_version = '1.8.0'
|
||||||
|
@ -563,11 +563,11 @@ class RoomDetailFragment :
|
|||||||
vectorBaseActivity.notImplemented()
|
vectorBaseActivity.notImplemented()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onEventCellClicked(informationData: MessageInformationData, messageContent: MessageContent, view: View) {
|
override fun onEventCellClicked(informationData: MessageInformationData, messageContent: MessageContent?, view: View) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onEventLongClicked(informationData: MessageInformationData, messageContent: MessageContent, view: View): Boolean {
|
override fun onEventLongClicked(informationData: MessageInformationData, messageContent: MessageContent?, view: View): Boolean {
|
||||||
view.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS)
|
view.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS)
|
||||||
val roomId = roomDetailArgs.roomId
|
val roomId = roomDetailArgs.roomId
|
||||||
|
|
||||||
|
@ -55,7 +55,8 @@ class RoomDetailViewModel(initialState: RoomDetailViewState,
|
|||||||
private val roomId = initialState.roomId
|
private val roomId = initialState.roomId
|
||||||
private val eventId = initialState.eventId
|
private val eventId = initialState.eventId
|
||||||
private val displayedEventsObservable = BehaviorRelay.create<RoomDetailActions.EventDisplayed>()
|
private val displayedEventsObservable = BehaviorRelay.create<RoomDetailActions.EventDisplayed>()
|
||||||
private val timeline = room.createTimeline(eventId, TimelineDisplayableEvents.DISPLAYABLE_TYPES)
|
private val timeline = room.createTimeline(eventId,
|
||||||
|
if (TimelineDisplayableEvents.DEBUG_HIDDEN_EVENT) TimelineDisplayableEvents.DEBUG_DISPLAYABLE_TYPES else TimelineDisplayableEvents.DISPLAYABLE_TYPES)
|
||||||
|
|
||||||
companion object : MvRxViewModelFactory<RoomDetailViewModel, RoomDetailViewState> {
|
companion object : MvRxViewModelFactory<RoomDetailViewModel, RoomDetailViewState> {
|
||||||
|
|
||||||
|
@ -46,15 +46,13 @@ class TimelineEventController(private val dateFormatter: TimelineDateFormatter,
|
|||||||
private val backgroundHandler: Handler = TimelineAsyncHelper.getBackgroundHandler()
|
private val backgroundHandler: Handler = TimelineAsyncHelper.getBackgroundHandler()
|
||||||
) : EpoxyController(backgroundHandler, backgroundHandler), Timeline.Listener {
|
) : EpoxyController(backgroundHandler, backgroundHandler), Timeline.Listener {
|
||||||
|
|
||||||
interface Callback : ReactionPillCallback, AvatarCallback {
|
interface Callback : ReactionPillCallback, AvatarCallback, BaseCallback {
|
||||||
fun onEventVisible(event: TimelineEvent)
|
fun onEventVisible(event: TimelineEvent)
|
||||||
fun onUrlClicked(url: String)
|
fun onUrlClicked(url: String)
|
||||||
fun onImageMessageClicked(messageImageContent: MessageImageContent, mediaData: ImageContentRenderer.Data, view: View)
|
fun onImageMessageClicked(messageImageContent: MessageImageContent, mediaData: ImageContentRenderer.Data, view: View)
|
||||||
fun onVideoMessageClicked(messageVideoContent: MessageVideoContent, mediaData: VideoContentRenderer.Data, view: View)
|
fun onVideoMessageClicked(messageVideoContent: MessageVideoContent, mediaData: VideoContentRenderer.Data, view: View)
|
||||||
fun onFileMessageClicked(messageFileContent: MessageFileContent)
|
fun onFileMessageClicked(messageFileContent: MessageFileContent)
|
||||||
fun onAudioMessageClicked(messageAudioContent: MessageAudioContent)
|
fun onAudioMessageClicked(messageAudioContent: MessageAudioContent)
|
||||||
fun onEventCellClicked(informationData: MessageInformationData, messageContent: MessageContent, view: View)
|
|
||||||
fun onEventLongClicked(informationData: MessageInformationData, messageContent: MessageContent, view: View): Boolean
|
|
||||||
fun onEditedDecorationClicked(informationData: MessageInformationData, editAggregatedSummary: EditAggregatedSummary?)
|
fun onEditedDecorationClicked(informationData: MessageInformationData, editAggregatedSummary: EditAggregatedSummary?)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -63,6 +61,11 @@ class TimelineEventController(private val dateFormatter: TimelineDateFormatter,
|
|||||||
fun onLongClickOnReactionPill(informationData: MessageInformationData, reaction: String)
|
fun onLongClickOnReactionPill(informationData: MessageInformationData, reaction: String)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface BaseCallback {
|
||||||
|
fun onEventCellClicked(informationData: MessageInformationData, messageContent: MessageContent?, view: View)
|
||||||
|
fun onEventLongClicked(informationData: MessageInformationData, messageContent: MessageContent?, view: View): Boolean
|
||||||
|
}
|
||||||
|
|
||||||
interface AvatarCallback {
|
interface AvatarCallback {
|
||||||
fun onAvatarClicked(informationData: MessageInformationData)
|
fun onAvatarClicked(informationData: MessageInformationData)
|
||||||
fun onMemberNameClicked(informationData: MessageInformationData)
|
fun onMemberNameClicked(informationData: MessageInformationData)
|
||||||
|
@ -34,7 +34,7 @@ import org.koin.android.ext.android.get
|
|||||||
|
|
||||||
data class SimpleAction(val uid: String, val titleRes: Int, val iconResId: Int?, val data: Any? = null)
|
data class SimpleAction(val uid: String, val titleRes: Int, val iconResId: Int?, val data: Any? = null)
|
||||||
|
|
||||||
data class MessageMenuState(val actions: List<SimpleAction>) : MvRxState
|
data class MessageMenuState(val actions: List<SimpleAction> = emptyList()) : MvRxState
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Manages list actions for a given message (copy / paste / forward...)
|
* Manages list actions for a given message (copy / paste / forward...)
|
||||||
@ -50,9 +50,9 @@ class MessageMenuViewModel(initialState: MessageMenuState) : VectorViewModel<Mes
|
|||||||
val event = currentSession.getRoom(parcel.roomId)?.getTimeLineEvent(parcel.eventId)
|
val event = currentSession.getRoom(parcel.roomId)?.getTimeLineEvent(parcel.eventId)
|
||||||
?: return null
|
?: return null
|
||||||
|
|
||||||
val messageContent: MessageContent = event.annotations?.editSummary?.aggregatedContent?.toModel()
|
val messageContent: MessageContent? = event.annotations?.editSummary?.aggregatedContent?.toModel()
|
||||||
?: event.root.content.toModel() ?: return null
|
?: event.root.content.toModel()
|
||||||
val type = messageContent.type
|
val type = messageContent?.type
|
||||||
|
|
||||||
if (event.sendState == SendState.UNSENT) {
|
if (event.sendState == SendState.UNSENT) {
|
||||||
//Resend and Delete
|
//Resend and Delete
|
||||||
@ -76,7 +76,7 @@ class MessageMenuViewModel(initialState: MessageMenuState) : VectorViewModel<Mes
|
|||||||
this.add(SimpleAction(ACTION_ADD_REACTION, R.string.message_add_reaction, R.drawable.ic_add_reaction, event.root.eventId))
|
this.add(SimpleAction(ACTION_ADD_REACTION, R.string.message_add_reaction, R.drawable.ic_add_reaction, event.root.eventId))
|
||||||
if (canCopy(type)) {
|
if (canCopy(type)) {
|
||||||
//TODO copy images? html? see ClipBoard
|
//TODO copy images? html? see ClipBoard
|
||||||
this.add(SimpleAction(ACTION_COPY, R.string.copy, R.drawable.ic_copy, messageContent.body))
|
this.add(SimpleAction(ACTION_COPY, R.string.copy, R.drawable.ic_copy, messageContent!!.body))
|
||||||
}
|
}
|
||||||
|
|
||||||
if (canReply(event, messageContent)) {
|
if (canReply(event, messageContent)) {
|
||||||
@ -134,10 +134,10 @@ class MessageMenuViewModel(initialState: MessageMenuState) : VectorViewModel<Mes
|
|||||||
return MessageMenuState(actions)
|
return MessageMenuState(actions)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun canReply(event: TimelineEvent, messageContent: MessageContent): Boolean {
|
private fun canReply(event: TimelineEvent, messageContent: MessageContent?): Boolean {
|
||||||
//Only event of type Event.EVENT_TYPE_MESSAGE are supported for the moment
|
//Only event of type Event.EVENT_TYPE_MESSAGE are supported for the moment
|
||||||
if (event.root.type != EventType.MESSAGE) return false
|
if (event.root.type != EventType.MESSAGE) return false
|
||||||
return when (messageContent.type) {
|
return when (messageContent?.type) {
|
||||||
MessageType.MSGTYPE_TEXT,
|
MessageType.MSGTYPE_TEXT,
|
||||||
MessageType.MSGTYPE_NOTICE,
|
MessageType.MSGTYPE_NOTICE,
|
||||||
MessageType.MSGTYPE_EMOTE,
|
MessageType.MSGTYPE_EMOTE,
|
||||||
@ -149,10 +149,10 @@ class MessageMenuViewModel(initialState: MessageMenuState) : VectorViewModel<Mes
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun canQuote(event: TimelineEvent, messageContent: MessageContent): Boolean {
|
private fun canQuote(event: TimelineEvent, messageContent: MessageContent?): Boolean {
|
||||||
//Only event of type Event.EVENT_TYPE_MESSAGE are supported for the moment
|
//Only event of type Event.EVENT_TYPE_MESSAGE are supported for the moment
|
||||||
if (event.root.type != EventType.MESSAGE) return false
|
if (event.root.type != EventType.MESSAGE) return false
|
||||||
return when (messageContent.type) {
|
return when (messageContent?.type) {
|
||||||
MessageType.MSGTYPE_TEXT,
|
MessageType.MSGTYPE_TEXT,
|
||||||
MessageType.MSGTYPE_NOTICE,
|
MessageType.MSGTYPE_NOTICE,
|
||||||
MessageType.MSGTYPE_EMOTE,
|
MessageType.MSGTYPE_EMOTE,
|
||||||
@ -190,7 +190,7 @@ class MessageMenuViewModel(initialState: MessageMenuState) : VectorViewModel<Mes
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private fun canCopy(type: String): Boolean {
|
private fun canCopy(type: String?): Boolean {
|
||||||
return when (type) {
|
return when (type) {
|
||||||
MessageType.MSGTYPE_TEXT,
|
MessageType.MSGTYPE_TEXT,
|
||||||
MessageType.MSGTYPE_NOTICE,
|
MessageType.MSGTYPE_NOTICE,
|
||||||
@ -204,7 +204,7 @@ class MessageMenuViewModel(initialState: MessageMenuState) : VectorViewModel<Mes
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private fun canShare(type: String): Boolean {
|
private fun canShare(type: String?): Boolean {
|
||||||
return when (type) {
|
return when (type) {
|
||||||
MessageType.MSGTYPE_IMAGE,
|
MessageType.MSGTYPE_IMAGE,
|
||||||
MessageType.MSGTYPE_AUDIO,
|
MessageType.MSGTYPE_AUDIO,
|
||||||
|
@ -17,23 +17,32 @@
|
|||||||
package im.vector.riotredesign.features.home.room.detail.timeline.factory
|
package im.vector.riotredesign.features.home.room.detail.timeline.factory
|
||||||
|
|
||||||
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
|
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
|
||||||
|
import im.vector.riotredesign.features.home.room.detail.timeline.TimelineEventController
|
||||||
import im.vector.riotredesign.features.home.room.detail.timeline.format.NoticeEventFormatter
|
import im.vector.riotredesign.features.home.room.detail.timeline.format.NoticeEventFormatter
|
||||||
import im.vector.riotredesign.features.home.room.detail.timeline.helper.senderAvatar
|
import im.vector.riotredesign.features.home.room.detail.timeline.helper.senderAvatar
|
||||||
import im.vector.riotredesign.features.home.room.detail.timeline.helper.senderName
|
import im.vector.riotredesign.features.home.room.detail.timeline.helper.senderName
|
||||||
|
import im.vector.riotredesign.features.home.room.detail.timeline.item.MessageInformationData
|
||||||
import im.vector.riotredesign.features.home.room.detail.timeline.item.NoticeItem
|
import im.vector.riotredesign.features.home.room.detail.timeline.item.NoticeItem
|
||||||
import im.vector.riotredesign.features.home.room.detail.timeline.item.NoticeItem_
|
import im.vector.riotredesign.features.home.room.detail.timeline.item.NoticeItem_
|
||||||
|
|
||||||
class NoticeItemFactory(private val eventFormatter: NoticeEventFormatter) {
|
class NoticeItemFactory(private val eventFormatter: NoticeEventFormatter) {
|
||||||
|
|
||||||
fun create(event: TimelineEvent): NoticeItem? {
|
fun create(event: TimelineEvent,
|
||||||
|
callback: TimelineEventController.Callback?): NoticeItem? {
|
||||||
val formattedText = eventFormatter.format(event) ?: return null
|
val formattedText = eventFormatter.format(event) ?: return null
|
||||||
val senderName = event.senderName()
|
val informationData = MessageInformationData(
|
||||||
val senderAvatar = event.senderAvatar()
|
eventId = event.root.eventId ?: "?",
|
||||||
|
senderId = event.root.sender ?: "",
|
||||||
|
sendState = event.sendState,
|
||||||
|
avatarUrl = event.senderAvatar(),
|
||||||
|
memberName = event.senderName(),
|
||||||
|
showInformation = false
|
||||||
|
)
|
||||||
|
|
||||||
return NoticeItem_()
|
return NoticeItem_()
|
||||||
.noticeText(formattedText)
|
.noticeText(formattedText)
|
||||||
.avatarUrl(senderAvatar)
|
.informationData(informationData)
|
||||||
.memberName(senderName)
|
.baseCallback(callback)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -17,10 +17,16 @@
|
|||||||
package im.vector.riotredesign.features.home.room.detail.timeline.factory
|
package im.vector.riotredesign.features.home.room.detail.timeline.factory
|
||||||
|
|
||||||
import im.vector.matrix.android.api.session.events.model.EventType
|
import im.vector.matrix.android.api.session.events.model.EventType
|
||||||
|
import im.vector.matrix.android.api.session.events.model.toModel
|
||||||
|
import im.vector.matrix.android.api.session.room.model.message.MessageContent
|
||||||
|
import im.vector.matrix.android.api.session.room.model.message.MessageDefaultContent
|
||||||
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
|
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
|
||||||
import im.vector.riotredesign.core.epoxy.EmptyItem_
|
import im.vector.riotredesign.core.epoxy.EmptyItem_
|
||||||
import im.vector.riotredesign.core.epoxy.VectorEpoxyModel
|
import im.vector.riotredesign.core.epoxy.VectorEpoxyModel
|
||||||
import im.vector.riotredesign.features.home.room.detail.timeline.TimelineEventController
|
import im.vector.riotredesign.features.home.room.detail.timeline.TimelineEventController
|
||||||
|
import im.vector.riotredesign.features.home.room.detail.timeline.helper.TimelineDisplayableEvents
|
||||||
|
import im.vector.riotredesign.features.home.room.detail.timeline.item.MessageInformationData
|
||||||
|
import im.vector.riotredesign.features.home.room.detail.timeline.item.MessageTextItem_
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
|
||||||
class TimelineItemFactory(private val messageItemFactory: MessageItemFactory,
|
class TimelineItemFactory(private val messageItemFactory: MessageItemFactory,
|
||||||
@ -43,7 +49,7 @@ class TimelineItemFactory(private val messageItemFactory: MessageItemFactory,
|
|||||||
EventType.STATE_HISTORY_VISIBILITY,
|
EventType.STATE_HISTORY_VISIBILITY,
|
||||||
EventType.CALL_INVITE,
|
EventType.CALL_INVITE,
|
||||||
EventType.CALL_HANGUP,
|
EventType.CALL_HANGUP,
|
||||||
EventType.CALL_ANSWER -> noticeItemFactory.create(event)
|
EventType.CALL_ANSWER -> noticeItemFactory.create(event, callback)
|
||||||
|
|
||||||
// Unhandled event types (yet)
|
// Unhandled event types (yet)
|
||||||
EventType.ENCRYPTED,
|
EventType.ENCRYPTED,
|
||||||
@ -51,11 +57,34 @@ class TimelineItemFactory(private val messageItemFactory: MessageItemFactory,
|
|||||||
EventType.STATE_ROOM_THIRD_PARTY_INVITE,
|
EventType.STATE_ROOM_THIRD_PARTY_INVITE,
|
||||||
EventType.STICKER,
|
EventType.STICKER,
|
||||||
EventType.STATE_ROOM_CREATE -> defaultItemFactory.create(event)
|
EventType.STATE_ROOM_CREATE -> defaultItemFactory.create(event)
|
||||||
|
|
||||||
else -> {
|
else -> {
|
||||||
|
//These are just for debug to display hidden event, they should be filtered out in normal mode
|
||||||
|
if (TimelineDisplayableEvents.DEBUG_HIDDEN_EVENT) {
|
||||||
|
val informationData = MessageInformationData(eventId = event.root.eventId
|
||||||
|
?: "?",
|
||||||
|
senderId = event.root.sender ?: "",
|
||||||
|
sendState = event.sendState,
|
||||||
|
time = "",
|
||||||
|
avatarUrl = null,
|
||||||
|
memberName = "",
|
||||||
|
showInformation = false
|
||||||
|
)
|
||||||
|
val messageContent = event.root.content.toModel<MessageContent>()
|
||||||
|
?: MessageDefaultContent("", "", null, null)
|
||||||
|
MessageTextItem_()
|
||||||
|
.informationData(informationData)
|
||||||
|
.message("{ \"type\": ${event.root.type} }")
|
||||||
|
.longClickListener { view ->
|
||||||
|
return@longClickListener callback?.onEventLongClicked(informationData, messageContent, view)
|
||||||
|
?: false
|
||||||
|
}
|
||||||
|
} else {
|
||||||
Timber.w("Ignored event (type: ${event.root.type}")
|
Timber.w("Ignored event (type: ${event.root.type}")
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
defaultItemFactory.create(event, e)
|
defaultItemFactory.create(event, e)
|
||||||
}
|
}
|
||||||
|
@ -22,10 +22,14 @@ import im.vector.matrix.android.api.session.events.model.toModel
|
|||||||
import im.vector.matrix.android.api.session.room.model.RoomMember
|
import im.vector.matrix.android.api.session.room.model.RoomMember
|
||||||
import im.vector.matrix.android.api.session.room.model.message.MessageContent
|
import im.vector.matrix.android.api.session.room.model.message.MessageContent
|
||||||
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
|
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
|
||||||
|
import im.vector.riotredesign.BuildConfig
|
||||||
import im.vector.riotredesign.core.extensions.localDateTime
|
import im.vector.riotredesign.core.extensions.localDateTime
|
||||||
|
|
||||||
object TimelineDisplayableEvents {
|
object TimelineDisplayableEvents {
|
||||||
|
|
||||||
|
//Debug helper, to show invisible items in time line (reaction, redacts)
|
||||||
|
val DEBUG_HIDDEN_EVENT = BuildConfig.SHOW_HIDDEN_TIMELINE_EVENTS
|
||||||
|
|
||||||
val DISPLAYABLE_TYPES = listOf(
|
val DISPLAYABLE_TYPES = listOf(
|
||||||
EventType.MESSAGE,
|
EventType.MESSAGE,
|
||||||
EventType.STATE_ROOM_NAME,
|
EventType.STATE_ROOM_NAME,
|
||||||
@ -41,6 +45,11 @@ object TimelineDisplayableEvents {
|
|||||||
EventType.STICKER,
|
EventType.STICKER,
|
||||||
EventType.STATE_ROOM_CREATE
|
EventType.STATE_ROOM_CREATE
|
||||||
)
|
)
|
||||||
|
|
||||||
|
val DEBUG_DISPLAYABLE_TYPES = DISPLAYABLE_TYPES + listOf(
|
||||||
|
EventType.REDACTION,
|
||||||
|
EventType.REACTION
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun TimelineEvent.isDisplayable(): Boolean {
|
fun TimelineEvent.isDisplayable(): Boolean {
|
||||||
|
@ -23,27 +23,37 @@ import com.airbnb.epoxy.EpoxyAttribute
|
|||||||
import com.airbnb.epoxy.EpoxyModelClass
|
import com.airbnb.epoxy.EpoxyModelClass
|
||||||
import im.vector.riotredesign.R
|
import im.vector.riotredesign.R
|
||||||
import im.vector.riotredesign.features.home.AvatarRenderer
|
import im.vector.riotredesign.features.home.AvatarRenderer
|
||||||
|
import im.vector.riotredesign.features.home.room.detail.timeline.TimelineEventController
|
||||||
|
|
||||||
@EpoxyModelClass(layout = R.layout.item_timeline_event_base_noinfo)
|
@EpoxyModelClass(layout = R.layout.item_timeline_event_base_noinfo)
|
||||||
abstract class NoticeItem : BaseEventItem<NoticeItem.Holder>() {
|
abstract class NoticeItem : BaseEventItem<NoticeItem.Holder>() {
|
||||||
|
|
||||||
@EpoxyAttribute
|
@EpoxyAttribute
|
||||||
var noticeText: CharSequence? = null
|
var noticeText: CharSequence? = null
|
||||||
@EpoxyAttribute
|
|
||||||
var avatarUrl: String? = null
|
|
||||||
@EpoxyAttribute
|
|
||||||
var userId: String = ""
|
|
||||||
@EpoxyAttribute
|
|
||||||
var memberName: CharSequence? = null
|
|
||||||
|
|
||||||
|
|
||||||
@EpoxyAttribute
|
@EpoxyAttribute
|
||||||
var longClickListener: View.OnLongClickListener? = null
|
lateinit var informationData: MessageInformationData
|
||||||
|
|
||||||
|
@EpoxyAttribute
|
||||||
|
var baseCallback: TimelineEventController.BaseCallback? = null
|
||||||
|
|
||||||
|
|
||||||
|
private var longClickListener = View.OnLongClickListener {
|
||||||
|
baseCallback?.onEventLongClicked(informationData, null, it)
|
||||||
|
baseCallback != null
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
override fun bind(holder: Holder) {
|
override fun bind(holder: Holder) {
|
||||||
super.bind(holder)
|
super.bind(holder)
|
||||||
holder.noticeTextView.text = noticeText
|
holder.noticeTextView.text = noticeText
|
||||||
AvatarRenderer.render(avatarUrl, userId, memberName?.toString(), holder.avatarImageView)
|
AvatarRenderer.render(
|
||||||
|
informationData.avatarUrl,
|
||||||
|
informationData.senderId,
|
||||||
|
informationData.memberName?.toString()
|
||||||
|
?: informationData.senderId,
|
||||||
|
holder.avatarImageView
|
||||||
|
)
|
||||||
holder.view.setOnLongClickListener(longClickListener)
|
holder.view.setOnLongClickListener(longClickListener)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -51,7 +61,6 @@ abstract class NoticeItem : BaseEventItem<NoticeItem.Holder>() {
|
|||||||
|
|
||||||
class Holder : BaseHolder() {
|
class Holder : BaseHolder() {
|
||||||
override fun getStubId(): Int = STUB_ID
|
override fun getStubId(): Int = 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)
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:addStatesFromChildren="true"
|
android:addStatesFromChildren="true"
|
||||||
|
android:background="?attr/selectableItemBackground"
|
||||||
android:paddingLeft="8dp"
|
android:paddingLeft="8dp"
|
||||||
android:paddingRight="8dp">
|
android:paddingRight="8dp">
|
||||||
|
|
||||||
@ -31,9 +32,9 @@
|
|||||||
<ViewStub
|
<ViewStub
|
||||||
android:id="@+id/messageContentBlankStub"
|
android:id="@+id/messageContentBlankStub"
|
||||||
style="@style/TimelineContentStubNoInfoLayoutParams"
|
style="@style/TimelineContentStubNoInfoLayoutParams"
|
||||||
android:layout="@layout/item_timeline_event_blank_stub"
|
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="0dp"
|
android:layout_height="0dp"
|
||||||
|
android:layout="@layout/item_timeline_event_blank_stub"
|
||||||
tools:ignore="MissingConstraints" />
|
tools:ignore="MissingConstraints" />
|
||||||
|
|
||||||
<ViewStub
|
<ViewStub
|
||||||
|
Loading…
Reference in New Issue
Block a user