Fix impure reducer and use live event

This commit is contained in:
Valere 2019-06-28 19:28:38 +02:00
parent 4d7f1b4fee
commit a734c699ad
11 changed files with 145 additions and 82 deletions

View file

@ -19,6 +19,7 @@ package im.vector.matrix.rx
import im.vector.matrix.android.api.session.room.Room import im.vector.matrix.android.api.session.room.Room
import im.vector.matrix.android.api.session.room.model.EventAnnotationsSummary import im.vector.matrix.android.api.session.room.model.EventAnnotationsSummary
import im.vector.matrix.android.api.session.room.model.RoomSummary import im.vector.matrix.android.api.session.room.model.RoomSummary
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
import io.reactivex.Observable import io.reactivex.Observable
class RxRoom(private val room: Room) { class RxRoom(private val room: Room) {
@ -31,10 +32,14 @@ class RxRoom(private val room: Room) {
return room.getRoomMemberIdsLive().asObservable() return room.getRoomMemberIdsLive().asObservable()
} }
fun liveAnnotationSummary(eventId: String): Observable<List<EventAnnotationsSummary>> { fun liveAnnotationSummary(eventId: String): Observable<EventAnnotationsSummary> {
return room.getEventSummaryLive(eventId).asObservable() return room.getEventSummaryLive(eventId).asObservable()
} }
fun liveTimelineEvent(eventId: String): Observable<TimelineEvent> {
return room.liveTimeLineEvent(eventId).asObservable()
}
} }
fun Room.rx(): RxRoom { fun Room.rx(): RxRoom {

View file

@ -80,5 +80,5 @@ interface RelationService {
*/ */
fun replyToMessage(eventReplied: Event, replyText: String): Cancelable? fun replyToMessage(eventReplied: Event, replyText: String): Cancelable?
fun getEventSummaryLive(eventId: String): LiveData<List<EventAnnotationsSummary>> fun getEventSummaryLive(eventId: String): LiveData<EventAnnotationsSummary>
} }

View file

@ -16,6 +16,8 @@
package im.vector.matrix.android.api.session.room.timeline package im.vector.matrix.android.api.session.room.timeline
import androidx.lifecycle.LiveData
/** /**
* This interface defines methods to interact with the timeline. It's implemented at the room level. * This interface defines methods to interact with the timeline. It's implemented at the room level.
*/ */
@ -32,4 +34,7 @@ interface TimelineService {
fun getTimeLineEvent(eventId: String): TimelineEvent? fun getTimeLineEvent(eventId: String): TimelineEvent?
fun liveTimeLineEvent(eventId: String): LiveData<TimelineEvent>
} }

View file

@ -17,6 +17,7 @@ package im.vector.matrix.android.internal.session.room.relation
import android.content.Context import android.content.Context
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import androidx.lifecycle.Transformations
import androidx.work.OneTimeWorkRequest import androidx.work.OneTimeWorkRequest
import com.zhuinden.monarchy.Monarchy import com.zhuinden.monarchy.Monarchy
import im.vector.matrix.android.api.MatrixCallback import im.vector.matrix.android.api.MatrixCallback
@ -27,6 +28,7 @@ import im.vector.matrix.android.api.session.room.model.EventAnnotationsSummary
import im.vector.matrix.android.api.session.room.model.message.MessageType import im.vector.matrix.android.api.session.room.model.message.MessageType
import im.vector.matrix.android.api.session.room.model.relation.RelationService import im.vector.matrix.android.api.session.room.model.relation.RelationService
import im.vector.matrix.android.api.util.Cancelable import im.vector.matrix.android.api.util.Cancelable
import im.vector.matrix.android.internal.database.RealmLiveData
import im.vector.matrix.android.internal.database.helper.addSendingEvent import im.vector.matrix.android.internal.database.helper.addSendingEvent
import im.vector.matrix.android.internal.database.mapper.asDomain import im.vector.matrix.android.internal.database.mapper.asDomain
import im.vector.matrix.android.internal.database.model.ChunkEntity import im.vector.matrix.android.internal.database.model.ChunkEntity
@ -155,15 +157,13 @@ internal class DefaultRelationService @Inject constructor(private val context: C
return workRequest return workRequest
} }
override fun getEventSummaryLive(eventId: String): LiveData<List<EventAnnotationsSummary>> { override fun getEventSummaryLive(eventId: String): LiveData<EventAnnotationsSummary> {
return monarchy.findAllMappedWithChanges( val liveEntity = RealmLiveData(monarchy.realmConfiguration) { realm ->
{ realm ->
EventAnnotationsSummaryEntity.where(realm, eventId) EventAnnotationsSummaryEntity.where(realm, eventId)
},
{
it.asDomain()
} }
) return Transformations.map(liveEntity) { realmResults ->
realmResults.firstOrNull()?.asDomain() ?: EventAnnotationsSummary(eventId, emptyList(), null)
}
} }
/** /**

View file

@ -16,10 +16,14 @@
package im.vector.matrix.android.internal.session.room.timeline package im.vector.matrix.android.internal.session.room.timeline
import androidx.lifecycle.LiveData
import androidx.lifecycle.MediatorLiveData
import com.zhuinden.monarchy.Monarchy import com.zhuinden.monarchy.Monarchy
import im.vector.matrix.android.api.session.room.timeline.Timeline import im.vector.matrix.android.api.session.room.timeline.Timeline
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
import im.vector.matrix.android.api.session.room.timeline.TimelineService import im.vector.matrix.android.api.session.room.timeline.TimelineService
import im.vector.matrix.android.internal.database.RealmLiveData
import im.vector.matrix.android.internal.database.model.EventAnnotationsSummaryEntity
import im.vector.matrix.android.internal.database.model.EventEntity import im.vector.matrix.android.internal.database.model.EventEntity
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.TaskExecutor import im.vector.matrix.android.internal.task.TaskExecutor
@ -47,5 +51,27 @@ internal class DefaultTimelineService @Inject constructor(private val roomId: St
}) })
} }
override fun liveTimeLineEvent(eventId: String): LiveData<TimelineEvent> {
val liveEventEntity = RealmLiveData(monarchy.realmConfiguration) { realm ->
EventEntity.where(realm, eventId = eventId)
}
val liveAnnotationsEntity = RealmLiveData(monarchy.realmConfiguration) { realm ->
EventAnnotationsSummaryEntity.where(realm, eventId = eventId)
}
val result = MediatorLiveData<TimelineEvent>()
result.addSource(liveEventEntity) { realmResults ->
result.value = realmResults.firstOrNull()?.let { timelineEventFactory.create(it) }
}
result.addSource(liveAnnotationsEntity) {
liveEventEntity.value?.let {
result.value = liveEventEntity.value?.let { realmResults ->
//recreate the timeline event
realmResults.firstOrNull()?.let { timelineEventFactory.create(it) }
}
}
}
return result
}
} }

View file

@ -15,10 +15,7 @@
*/ */
package im.vector.riotredesign.features.home.room.detail.timeline.action package im.vector.riotredesign.features.home.room.detail.timeline.action
import com.airbnb.mvrx.FragmentViewModelContext import com.airbnb.mvrx.*
import com.airbnb.mvrx.MvRxState
import com.airbnb.mvrx.MvRxViewModelFactory
import com.airbnb.mvrx.ViewModelContext
import com.squareup.inject.assisted.Assisted import com.squareup.inject.assisted.Assisted
import com.squareup.inject.assisted.AssistedInject import com.squareup.inject.assisted.AssistedInject
import im.vector.matrix.android.api.session.Session import im.vector.matrix.android.api.session.Session
@ -28,7 +25,7 @@ import im.vector.matrix.android.api.session.room.model.message.MessageContent
import im.vector.matrix.android.api.session.room.model.message.MessageTextContent import im.vector.matrix.android.api.session.room.model.message.MessageTextContent
import im.vector.matrix.android.api.session.room.model.message.MessageType import im.vector.matrix.android.api.session.room.model.message.MessageType
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.di.HasScreenInjector import im.vector.matrix.rx.RxRoom
import im.vector.riotredesign.core.platform.VectorViewModel import im.vector.riotredesign.core.platform.VectorViewModel
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.item.MessageInformationData import im.vector.riotredesign.features.home.room.detail.timeline.item.MessageInformationData
@ -37,27 +34,30 @@ import java.text.SimpleDateFormat
import java.util.* import java.util.*
val dateFormat = SimpleDateFormat("EEE, d MMM yyyy HH:mm", Locale.getDefault())
data class MessageActionState( data class MessageActionState(
val roomId: String, val roomId: String,
val eventId: String, val eventId: String,
val informationData: MessageInformationData, val informationData: MessageInformationData,
val timelineEvent: TimelineEvent? val timelineEvent: Async<TimelineEvent> = Uninitialized
) : MvRxState { ) : MvRxState {
constructor(args: TimelineEventFragmentArgs) : this(roomId = args.roomId, eventId = args.eventId, informationData = args.informationData)
private val dateFormat = SimpleDateFormat("EEE, d MMM yyyy HH:mm", Locale.getDefault())
fun senderName(): String = informationData.memberName?.toString() ?: "" fun senderName(): String = informationData.memberName?.toString() ?: ""
fun time(): String? = timelineEvent?.root?.originServerTs?.let { dateFormat.format(Date(it)) } fun time(): String? = timelineEvent()?.root?.originServerTs?.let { dateFormat.format(Date(it)) }
?: "" ?: ""
fun canReact(): Boolean = timelineEvent?.root?.type == EventType.MESSAGE && timelineEvent.sendState.isSent() fun canReact(): Boolean = timelineEvent()?.root?.type == EventType.MESSAGE && timelineEvent()?.sendState?.isSent() == true
fun messageBody(eventHtmlRenderer: EventHtmlRenderer?, noticeEventFormatter: NoticeEventFormatter?): CharSequence? { fun messageBody(eventHtmlRenderer: EventHtmlRenderer?, noticeEventFormatter: NoticeEventFormatter?): CharSequence? {
return when (timelineEvent?.root?.getClearType()) { return when (timelineEvent()?.root?.getClearType()) {
EventType.MESSAGE -> { EventType.MESSAGE -> {
val messageContent: MessageContent? = timelineEvent.annotations?.editSummary?.aggregatedContent?.toModel() val messageContent: MessageContent? = timelineEvent()?.annotations?.editSummary?.aggregatedContent?.toModel()
?: timelineEvent.root.getClearContent().toModel() ?: timelineEvent()?.root?.getClearContent().toModel()
if (messageContent is MessageTextContent && messageContent.format == MessageType.FORMAT_MATRIX_HTML) { if (messageContent is MessageTextContent && messageContent.format == MessageType.FORMAT_MATRIX_HTML) {
eventHtmlRenderer?.render(messageContent.formattedBody eventHtmlRenderer?.render(messageContent.formattedBody
?: messageContent.body) ?: messageContent.body)
@ -72,7 +72,7 @@ data class MessageActionState(
EventType.CALL_INVITE, EventType.CALL_INVITE,
EventType.CALL_HANGUP, EventType.CALL_HANGUP,
EventType.CALL_ANSWER -> { EventType.CALL_ANSWER -> {
noticeEventFormatter?.format(timelineEvent) timelineEvent()?.let { noticeEventFormatter?.format(it) }
} }
else -> null else -> null
} }
@ -85,10 +85,14 @@ data class MessageActionState(
class MessageActionsViewModel @AssistedInject constructor(@Assisted class MessageActionsViewModel @AssistedInject constructor(@Assisted
initialState: MessageActionState, initialState: MessageActionState,
private val eventHtmlRenderer: EventHtmlRenderer, private val eventHtmlRenderer: EventHtmlRenderer,
private val session: Session, session: Session,
private val noticeEventFormatter: NoticeEventFormatter private val noticeEventFormatter: NoticeEventFormatter
) : VectorViewModel<MessageActionState>(initialState) { ) : VectorViewModel<MessageActionState>(initialState) {
private val eventId = initialState.eventId
private val room = session.getRoom(initialState.roomId)
@AssistedInject.Factory @AssistedInject.Factory
interface Factory { interface Factory {
fun create(initialState: MessageActionState): MessageActionsViewModel fun create(initialState: MessageActionState): MessageActionsViewModel
@ -101,18 +105,19 @@ class MessageActionsViewModel @AssistedInject constructor(@Assisted
return fragment.messageActionViewModelFactory.create(state) return fragment.messageActionViewModelFactory.create(state)
} }
override fun initialState(viewModelContext: ViewModelContext): MessageActionState? {
val session = (viewModelContext.activity as HasScreenInjector).injector().session()
val args: TimelineEventFragmentArgs = viewModelContext.args()
val event = session.getRoom(args.roomId)?.getTimeLineEvent(args.eventId)
return MessageActionState(
args.roomId,
args.eventId,
args.informationData,
event
)
} }
init {
observeEvent()
}
private fun observeEvent() {
if (room == null) return
RxRoom(room)
.liveTimelineEvent(eventId)
.execute {
copy(timelineEvent = it)
}
} }
fun resolveBody(state: MessageActionState): CharSequence? { fun resolveBody(state: MessageActionState): CharSequence? {

View file

@ -55,7 +55,8 @@ class MessageMenuFragment : VectorBaseFragment() {
val inflater = LayoutInflater.from(linearLayout.context) val inflater = LayoutInflater.from(linearLayout.context)
linearLayout.removeAllViews() linearLayout.removeAllViews()
var insertIndex = 0 var insertIndex = 0
state.actions.forEachIndexed { index, action -> val actions = state.actions()
actions?.forEachIndexed { index, action ->
inflateActionView(action, inflater, linearLayout)?.let { inflateActionView(action, inflater, linearLayout)?.let {
it.setOnClickListener { it.setOnClickListener {
interactionListener?.didSelectMenuAction(action) interactionListener?.didSelectMenuAction(action)
@ -63,7 +64,7 @@ class MessageMenuFragment : VectorBaseFragment() {
linearLayout.addView(it, insertIndex) linearLayout.addView(it, insertIndex)
insertIndex++ insertIndex++
if (addSeparators) { if (addSeparators) {
if (index < state.actions.size - 1) { if (index < actions.size - 1) {
linearLayout.addView(inflateSeparatorView(), insertIndex) linearLayout.addView(inflateSeparatorView(), insertIndex)
insertIndex++ insertIndex++
} }

View file

@ -15,10 +15,7 @@
*/ */
package im.vector.riotredesign.features.home.room.detail.timeline.action package im.vector.riotredesign.features.home.room.detail.timeline.action
import com.airbnb.mvrx.FragmentViewModelContext import com.airbnb.mvrx.*
import com.airbnb.mvrx.MvRxState
import com.airbnb.mvrx.MvRxViewModelFactory
import com.airbnb.mvrx.ViewModelContext
import com.squareup.inject.assisted.Assisted import com.squareup.inject.assisted.Assisted
import com.squareup.inject.assisted.AssistedInject import com.squareup.inject.assisted.AssistedInject
import im.vector.matrix.android.api.session.Session import im.vector.matrix.android.api.session.Session
@ -30,6 +27,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.model.message.MessageType
import im.vector.matrix.android.api.session.room.send.SendState 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.TimelineEvent
import im.vector.matrix.rx.RxRoom
import im.vector.riotredesign.R import im.vector.riotredesign.R
import im.vector.riotredesign.core.platform.VectorViewModel import im.vector.riotredesign.core.platform.VectorViewModel
import im.vector.riotredesign.core.resources.StringProvider import im.vector.riotredesign.core.resources.StringProvider
@ -44,7 +42,7 @@ data class MessageMenuState(
val roomId: String, val roomId: String,
val eventId: String, val eventId: String,
val informationData: MessageInformationData, val informationData: MessageInformationData,
val actions: List<SimpleAction> = emptyList() val actions: Async<List<SimpleAction>> = Uninitialized
) : MvRxState { ) : MvRxState {
constructor(args: TimelineEventFragmentArgs) : this(roomId = args.roomId, eventId = args.eventId, informationData = args.informationData) constructor(args: TimelineEventFragmentArgs) : this(roomId = args.roomId, eventId = args.eventId, informationData = args.informationData)
@ -63,6 +61,12 @@ class MessageMenuViewModel @AssistedInject constructor(@Assisted initialState: M
fun create(initialState: MessageMenuState): MessageMenuViewModel fun create(initialState: MessageMenuState): MessageMenuViewModel
} }
private val room = session.getRoom(initialState.roomId)
?: throw IllegalStateException("Shouldn't use this ViewModel without a room")
private val eventId = initialState.eventId
private val informationData: MessageInformationData = initialState.informationData
companion object : MvRxViewModelFactory<MessageMenuViewModel, MessageMenuState> { companion object : MvRxViewModelFactory<MessageMenuViewModel, MessageMenuState> {
const val ACTION_ADD_REACTION = "add_reaction" const val ACTION_ADD_REACTION = "add_reaction"
@ -87,13 +91,23 @@ class MessageMenuViewModel @AssistedInject constructor(@Assisted initialState: M
} }
init { init {
setState { reduceState(this) } observeEvent()
} }
private fun reduceState(state: MessageMenuState): MessageMenuState { private fun observeEvent() {
val event = session.getRoom(state.roomId)?.getTimeLineEvent(state.eventId) ?: return state RxRoom(room)
.liveTimelineEvent(eventId)
?.map {
actionsForEvent(it)
}
?.execute {
copy(actions = it)
}
}
val messageContent: MessageContent? = event.annotations?.editSummary?.aggregatedContent?.toModel() private fun actionsForEvent(event: TimelineEvent): List<SimpleAction> {
val messageContent: MessageContent? = event.annotations?.editSummary?.aggregatedContent.toModel()
?: event.root.getClearContent().toModel() ?: event.root.getClearContent().toModel()
val type = messageContent?.type val type = messageContent?.type
@ -114,7 +128,7 @@ class MessageMenuViewModel @AssistedInject constructor(@Assisted initialState: M
//TODO is downloading attachement? //TODO is downloading attachement?
if (canReact(event, messageContent)) { if (canReact(event, messageContent)) {
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, eventId))
} }
if (canCopy(type)) { if (canCopy(type)) {
//TODO copy images? html? see ClipBoard //TODO copy images? html? see ClipBoard
@ -122,23 +136,23 @@ class MessageMenuViewModel @AssistedInject constructor(@Assisted initialState: M
} }
if (canReply(event, messageContent)) { if (canReply(event, messageContent)) {
this.add(SimpleAction(ACTION_REPLY, R.string.reply, R.drawable.ic_reply, event.root.eventId)) this.add(SimpleAction(ACTION_REPLY, R.string.reply, R.drawable.ic_reply, eventId))
} }
if (canEdit(event, session.sessionParams.credentials.userId)) { if (canEdit(event, session.sessionParams.credentials.userId)) {
this.add(SimpleAction(ACTION_EDIT, R.string.edit, R.drawable.ic_edit, event.root.eventId)) this.add(SimpleAction(ACTION_EDIT, R.string.edit, R.drawable.ic_edit, eventId))
} }
if (canRedact(event, session.sessionParams.credentials.userId)) { if (canRedact(event, session.sessionParams.credentials.userId)) {
this.add(SimpleAction(ACTION_DELETE, R.string.delete, R.drawable.ic_delete, event.root.eventId)) this.add(SimpleAction(ACTION_DELETE, R.string.delete, R.drawable.ic_delete, eventId))
} }
if (canQuote(event, messageContent)) { if (canQuote(event, messageContent)) {
this.add(SimpleAction(ACTION_QUOTE, R.string.quote, R.drawable.ic_quote, state.eventId)) this.add(SimpleAction(ACTION_QUOTE, R.string.quote, R.drawable.ic_quote, eventId))
} }
if (canViewReactions(event)) { if (canViewReactions(event)) {
this.add(SimpleAction(ACTION_VIEW_REACTIONS, R.string.message_view_reaction, R.drawable.ic_view_reactions, state.informationData)) this.add(SimpleAction(ACTION_VIEW_REACTIONS, R.string.message_view_reaction, R.drawable.ic_view_reactions, informationData))
} }
if (canShare(type)) { if (canShare(type)) {
@ -160,22 +174,22 @@ class MessageMenuViewModel @AssistedInject constructor(@Assisted initialState: M
//TODO sent by me or sufficient power level //TODO sent by me or sufficient power level
} }
this.add(SimpleAction(VIEW_SOURCE, R.string.view_source, R.drawable.ic_view_source, JSONObject(event.root.toContent()).toString(4))) this.add(SimpleAction(VIEW_SOURCE, R.string.view_source, R.drawable.ic_view_source, JSONObject(event.root.content.toContent()).toString(4)))
if (event.isEncrypted()) { if (event.isEncrypted()) {
val decryptedContent = event.root.mClearEvent?.toContent()?.let { val decryptedContent = event.root.mClearEvent?.content?.toContent().let {
JSONObject(it).toString(4) JSONObject(it).toString(4)
} ?: stringProvider.getString(R.string.encryption_information_decryption_error) } ?: stringProvider.getString(R.string.encryption_information_decryption_error)
this.add(SimpleAction(VIEW_DECRYPTED_SOURCE, R.string.view_decrypted_source, R.drawable.ic_view_source, decryptedContent)) this.add(SimpleAction(VIEW_DECRYPTED_SOURCE, R.string.view_decrypted_source, R.drawable.ic_view_source, decryptedContent))
} }
this.add(SimpleAction(ACTION_COPY_PERMALINK, R.string.permalink, R.drawable.ic_permalink, state.eventId)) this.add(SimpleAction(ACTION_COPY_PERMALINK, R.string.permalink, R.drawable.ic_permalink, event.root.eventId))
if (session.sessionParams.credentials.userId != event.root.senderId && event.root.getClearType() == EventType.MESSAGE) { if (session.sessionParams.credentials.userId != event.root.senderId && event.root.getClearType() == EventType.MESSAGE) {
//not sent by me //not sent by me
this.add(SimpleAction(ACTION_FLAG, R.string.report_content, R.drawable.ic_flag, state.eventId)) this.add(SimpleAction(ACTION_FLAG, R.string.report_content, R.drawable.ic_flag, event.root.eventId))
} }
} }
} }
return state.copy(actions = actions) return actions
} }

View file

@ -47,7 +47,7 @@ class QuickReactionFragment : VectorBaseFragment() {
injector.inject(this) injector.inject(this)
} }
lateinit var textViews: List<TextView> private lateinit var textViews: List<TextView>
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
@ -63,7 +63,8 @@ class QuickReactionFragment : VectorBaseFragment() {
} }
override fun invalidate() = withState(viewModel) { override fun invalidate() = withState(viewModel) {
it.quickStates.forEachIndexed { index, qs -> val quickReactionsStates = it.quickStates() ?: return@withState
quickReactionsStates.forEachIndexed { index, qs ->
textViews[index].text = qs.reaction textViews[index].text = qs.reaction
textViews[index].alpha = if (qs.isSelected) 0.2f else 1f textViews[index].alpha = if (qs.isSelected) 0.2f else 1f
} }

View file

@ -15,13 +15,11 @@
*/ */
package im.vector.riotredesign.features.home.room.detail.timeline.action package im.vector.riotredesign.features.home.room.detail.timeline.action
import com.airbnb.mvrx.FragmentViewModelContext import com.airbnb.mvrx.*
import com.airbnb.mvrx.MvRxState
import com.airbnb.mvrx.MvRxViewModelFactory
import com.airbnb.mvrx.ViewModelContext
import com.squareup.inject.assisted.Assisted import com.squareup.inject.assisted.Assisted
import com.squareup.inject.assisted.AssistedInject import com.squareup.inject.assisted.AssistedInject
import im.vector.matrix.android.api.session.Session import im.vector.matrix.android.api.session.Session
import im.vector.matrix.rx.RxRoom
import im.vector.riotredesign.core.platform.VectorViewModel import im.vector.riotredesign.core.platform.VectorViewModel
import im.vector.riotredesign.features.home.room.detail.timeline.item.MessageInformationData import im.vector.riotredesign.features.home.room.detail.timeline.item.MessageInformationData
@ -37,7 +35,7 @@ data class QuickReactionState(
val roomId: String, val roomId: String,
val eventId: String, val eventId: String,
val informationData: MessageInformationData, val informationData: MessageInformationData,
val quickStates: List<ToggleState> = emptyList(), val quickStates: Async<List<ToggleState>> = Uninitialized,
val result: ToggleState? = null val result: ToggleState? = null
/** Pair of 'clickedOn' and current toggles state*/ /** Pair of 'clickedOn' and current toggles state*/
) : MvRxState { ) : MvRxState {
@ -56,6 +54,9 @@ class QuickReactionViewModel @AssistedInject constructor(@Assisted initialState:
fun create(initialState: QuickReactionState): QuickReactionViewModel fun create(initialState: QuickReactionState): QuickReactionViewModel
} }
private val room = session.getRoom(initialState.roomId)
private val eventId = initialState.eventId
companion object : MvRxViewModelFactory<QuickReactionViewModel, QuickReactionState> { companion object : MvRxViewModelFactory<QuickReactionViewModel, QuickReactionState> {
val quickEmojis = listOf("👍", "👎", "😄", "🎉", "😕", "❤️", "🚀", "👀") val quickEmojis = listOf("👍", "👎", "😄", "🎉", "😕", "❤️", "🚀", "👀")
@ -67,22 +68,30 @@ class QuickReactionViewModel @AssistedInject constructor(@Assisted initialState:
} }
init { init {
setState { reduceState(this) } observeReactions()
} }
private fun reduceState(state: QuickReactionState): QuickReactionState { private fun observeReactions() {
val event = session.getRoom(state.roomId)?.getTimeLineEvent(state.eventId) ?: return state if (room == null) return
val summary = event.annotations?.reactionsSummary RxRoom(room)
val quickReactions = quickEmojis.map { emoji -> .liveAnnotationSummary(eventId)
ToggleState(emoji, summary?.firstOrNull { it.key == emoji }?.addedByMe ?: false) .map { annotations ->
quickEmojis.map { emoji ->
ToggleState(emoji, annotations.reactionsSummary.firstOrNull { it.key == emoji }?.addedByMe
?: false)
} }
return state.copy(quickStates = quickReactions)
} }
.execute {
copy(quickStates = it)
}
}
fun didSelect(index: Int) = withState { fun didSelect(index: Int) = withState {
val isSelected = it.quickStates[index].isSelected val selectedReaction = it.quickStates()?.get(index) ?: return@withState
val isSelected = selectedReaction.isSelected
setState { setState {
copy(result = ToggleState(it.quickStates[index].reaction, !isSelected)) copy(result = ToggleState(selectedReaction.reaction, !isSelected))
} }
} }

View file

@ -85,10 +85,8 @@ class ViewReactionViewModel @AssistedInject constructor(@Assisted
.liveAnnotationSummary(eventId) .liveAnnotationSummary(eventId)
.flatMapSingle { summaries -> .flatMapSingle { summaries ->
Observable Observable
.fromIterable(summaries) .fromIterable(summaries.reactionsSummary)
.flatMapIterable { it.reactionsSummary
.filter { reactionAggregatedSummary -> isSingleEmoji(reactionAggregatedSummary.key) } .filter { reactionAggregatedSummary -> isSingleEmoji(reactionAggregatedSummary.key) }
}
.toReactionInfoList() .toReactionInfoList()
} }
.execute { .execute {
@ -112,7 +110,6 @@ class ViewReactionViewModel @AssistedInject constructor(@Assisted
timelineDateFormatter.formatMessageHour(localDate) timelineDateFormatter.formatMessageHour(localDate)
) )
} }
} }.toList()
.toList()
} }
} }