Fix some timeline issues and rename EnrichedEvent to TimelineEvent as it's only used in this context.

This commit is contained in:
ganfra 2019-01-14 16:18:39 +01:00
parent 34e08705dd
commit b2cdeb87f4
31 changed files with 170 additions and 179 deletions

View File

@ -3,7 +3,7 @@ package im.vector.riotredesign.core.extensions
import android.arch.lifecycle.LifecycleOwner import android.arch.lifecycle.LifecycleOwner
import android.arch.lifecycle.LiveData import android.arch.lifecycle.LiveData
import android.arch.lifecycle.Observer import android.arch.lifecycle.Observer
import im.vector.riotredesign.core.utils.Event import im.vector.riotredesign.core.utils.LiveEvent
import im.vector.riotredesign.core.utils.EventObserver import im.vector.riotredesign.core.utils.EventObserver


inline fun <T> LiveData<T>.observeK(owner: LifecycleOwner, crossinline observer: (T?) -> Unit) { inline fun <T> LiveData<T>.observeK(owner: LifecycleOwner, crossinline observer: (T?) -> Unit) {
@ -14,6 +14,6 @@ inline fun <T> LiveData<T>.observeNotNull(owner: LifecycleOwner, crossinline obs
this.observe(owner, Observer { it?.run(observer) }) this.observe(owner, Observer { it?.run(observer) })
} }


inline fun <T> LiveData<Event<T>>.observeEvent(owner: LifecycleOwner, crossinline observer: (T) -> Unit) { inline fun <T> LiveData<LiveEvent<T>>.observeEvent(owner: LifecycleOwner, crossinline observer: (T) -> Unit) {
this.observe(owner, EventObserver { it.run(observer) }) this.observe(owner, EventObserver { it.run(observer) })
} }

View File

@ -1,6 +0,0 @@
package im.vector.riotredesign.core.utils

object Constants {


}

View File

@ -1,63 +0,0 @@
package im.vector.riotredesign.core.utils

import android.os.Binder
import android.os.Bundle
import android.support.v4.app.BundleCompat
import android.support.v4.app.Fragment
import kotlin.reflect.KProperty

class FragmentArgumentDelegate<T : Any> : kotlin.properties.ReadWriteProperty<Fragment, T?> {

var value: T? = null

override operator fun getValue(thisRef: android.support.v4.app.Fragment, property: kotlin.reflect.KProperty<*>): T? {
if (value == null) {
val args = thisRef.arguments
@Suppress("UNCHECKED_CAST")
value = args?.get(property.name) as T?
}
return value
}

override operator fun setValue(thisRef: Fragment, property: KProperty<*>, value: T?) {
if (value == null) return

if (thisRef.arguments == null) {
thisRef.arguments = Bundle()
}
val args = thisRef.arguments!!
val key = property.name

when (value) {
is String -> args.putString(key, value)
is Int -> args.putInt(key, value)
is Short -> args.putShort(key, value)
is Long -> args.putLong(key, value)
is Byte -> args.putByte(key, value)
is ByteArray -> args.putByteArray(key, value)
is Char -> args.putChar(key, value)
is CharArray -> args.putCharArray(key, value)
is CharSequence -> args.putCharSequence(key, value)
is Float -> args.putFloat(key, value)
is Bundle -> args.putBundle(key, value)
is Binder -> BundleCompat.putBinder(args, key, value)
is android.os.Parcelable -> args.putParcelable(key, value)
is java.io.Serializable -> args.putSerializable(key, value)
else -> throw IllegalStateException("Type ${value.javaClass.name} of property ${property.name} is not supported")
}
}
}

class UnsafeFragmentArgumentDelegate<T : Any> : kotlin.properties.ReadWriteProperty<Fragment, T> {

private val innerDelegate = FragmentArgumentDelegate<T>()

override fun setValue(thisRef: Fragment, property: KProperty<*>, value: T) {
innerDelegate.setValue(thisRef, property, value)
}

override fun getValue(thisRef: Fragment, property: KProperty<*>): T {
return innerDelegate.getValue(thisRef, property)!!
}

}

View File

@ -2,7 +2,7 @@ package im.vector.riotredesign.core.utils


import android.arch.lifecycle.Observer import android.arch.lifecycle.Observer


open class Event<out T>(private val content: T) { open class LiveEvent<out T>(private val content: T) {


var hasBeenHandled = false var hasBeenHandled = false
private set // Allow external read but not write private set // Allow external read but not write
@ -26,13 +26,13 @@ open class Event<out T>(private val content: T) {
} }


/** /**
* An [Observer] for [Event]s, simplifying the pattern of checking if the [Event]'s content has * An [Observer] for [LiveEvent]s, simplifying the pattern of checking if the [LiveEvent]'s content has
* already been handled. * already been handled.
* *
* [onEventUnhandledContent] is *only* called if the [Event]'s contents has not been handled. * [onEventUnhandledContent] is *only* called if the [LiveEvent]'s contents has not been handled.
*/ */
class EventObserver<T>(private val onEventUnhandledContent: (T) -> Unit) : Observer<Event<T>> { class EventObserver<T>(private val onEventUnhandledContent: (T) -> Unit) : Observer<LiveEvent<T>> {
override fun onChanged(event: Event<T>?) { override fun onChanged(event: LiveEvent<T>?) {
event?.getContentIfNotHandled()?.let { value -> event?.getContentIfNotHandled()?.let { value ->
onEventUnhandledContent(value) onEventUnhandledContent(value)
} }

View File

@ -9,7 +9,7 @@ import im.vector.matrix.android.api.Matrix
import im.vector.matrix.android.api.session.Session import im.vector.matrix.android.api.session.Session
import im.vector.matrix.rx.rx import im.vector.matrix.rx.rx
import im.vector.riotredesign.core.platform.RiotViewModel import im.vector.riotredesign.core.platform.RiotViewModel
import im.vector.riotredesign.core.utils.Event import im.vector.riotredesign.core.utils.LiveEvent
import im.vector.riotredesign.features.home.room.list.RoomSelectionRepository import im.vector.riotredesign.features.home.room.list.RoomSelectionRepository
import io.reactivex.rxkotlin.subscribeBy import io.reactivex.rxkotlin.subscribeBy
import org.koin.android.ext.android.get import org.koin.android.ext.android.get
@ -31,8 +31,8 @@ class HomeActivityViewModel(state: EmptyState,
} }
} }


private val _openRoomLiveData = MutableLiveData<Event<String>>() private val _openRoomLiveData = MutableLiveData<LiveEvent<String>>()
val openRoomLiveData: LiveData<Event<String>> val openRoomLiveData: LiveData<LiveEvent<String>>
get() = _openRoomLiveData get() = _openRoomLiveData


init { init {
@ -40,7 +40,7 @@ class HomeActivityViewModel(state: EmptyState,
if (lastSelectedRoom == null) { if (lastSelectedRoom == null) {
getTheFirstRoomWhenAvailable() getTheFirstRoomWhenAvailable()
} else { } else {
_openRoomLiveData.postValue(Event(lastSelectedRoom)) _openRoomLiveData.postValue(LiveEvent(lastSelectedRoom))
} }
} }


@ -51,7 +51,7 @@ class HomeActivityViewModel(state: EmptyState,
.subscribeBy { .subscribeBy {
val firstRoom = it.firstOrNull() val firstRoom = it.firstOrNull()
if (firstRoom != null) { if (firstRoom != null) {
_openRoomLiveData.postValue(Event(firstRoom.roomId)) _openRoomLiveData.postValue(LiveEvent(firstRoom.roomId))
} }
} }
.disposeOnClear() .disposeOnClear()

View File

@ -23,7 +23,6 @@ class HomeNavigator {
if (!addToBackstack && isRoomOpened(roomId)) { if (!addToBackstack && isRoomOpened(roomId)) {
return return
} }
currentRoomId = roomId
activity?.let { activity?.let {
val args = RoomDetailArgs(roomId, eventId) val args = RoomDetailArgs(roomId, eventId)
val roomDetailFragment = RoomDetailFragment.newInstance(args) val roomDetailFragment = RoomDetailFragment.newInstance(args)
@ -31,6 +30,7 @@ class HomeNavigator {
if (addToBackstack) { if (addToBackstack) {
it.addFragmentToBackstack(roomDetailFragment, R.id.homeDetailFragmentContainer, roomId) it.addFragmentToBackstack(roomDetailFragment, R.id.homeDetailFragmentContainer, roomId)
} else { } else {
currentRoomId = roomId
clearBackStack(it.supportFragmentManager) clearBackStack(it.supportFragmentManager)
it.replaceFragment(roomDetailFragment, R.id.homeDetailFragmentContainer) it.replaceFragment(roomDetailFragment, R.id.homeDetailFragmentContainer)
} }

View File

@ -73,7 +73,7 @@ class RoomDetailFragment : RiotFragment(), TimelineEventController.Callback {
scrollOnNewMessageCallback = ScrollOnNewMessageCallback(layoutManager) scrollOnNewMessageCallback = ScrollOnNewMessageCallback(layoutManager)
recyclerView.layoutManager = layoutManager recyclerView.layoutManager = layoutManager
recyclerView.setHasFixedSize(true) recyclerView.setHasFixedSize(true)
//timelineEventController.addModelBuildListener { it.dispatchTo(scrollOnNewMessageCallback) } timelineEventController.addModelBuildListener { it.dispatchTo(scrollOnNewMessageCallback) }
recyclerView.setController(timelineEventController) recyclerView.setController(timelineEventController)
timelineEventController.callback = this timelineEventController.callback = this
} }
@ -95,8 +95,15 @@ class RoomDetailFragment : RiotFragment(), TimelineEventController.Callback {


private fun renderTimeline(state: RoomDetailViewState) { private fun renderTimeline(state: RoomDetailViewState) {
when (state.asyncTimelineData) { when (state.asyncTimelineData) {
is Success -> timelineEventController.update(state.asyncTimelineData()) is Success -> {
val timelineData = state.asyncTimelineData()
val lockAutoScroll = timelineData?.let {
it.events == timelineEventController.currentList && it.isLoadingForward
} ?: true


scrollOnNewMessageCallback.isLocked.set(lockAutoScroll)
timelineEventController.update(timelineData)
}
} }
} }



View File

@ -6,10 +6,10 @@ import java.util.concurrent.atomic.AtomicBoolean


class ScrollOnNewMessageCallback(private val layoutManager: LinearLayoutManager) : DefaultListUpdateCallback { class ScrollOnNewMessageCallback(private val layoutManager: LinearLayoutManager) : DefaultListUpdateCallback {


val hasBeenUpdated = AtomicBoolean(false) var isLocked = AtomicBoolean(true)


override fun onInserted(position: Int, count: Int) { override fun onInserted(position: Int, count: Int) {
if (hasBeenUpdated.compareAndSet(true, false) && position == 0 && layoutManager.findFirstVisibleItemPosition() == 0) { if (isLocked.compareAndSet(false, true) && position == 0 && layoutManager.findFirstVisibleItemPosition() == 0) {
layoutManager.scrollToPosition(0) layoutManager.scrollToPosition(0)
} }
} }

View File

@ -4,7 +4,7 @@ import android.text.SpannableStringBuilder
import android.text.util.Linkify import android.text.util.Linkify
import im.vector.matrix.android.api.permalinks.MatrixLinkify import im.vector.matrix.android.api.permalinks.MatrixLinkify
import im.vector.matrix.android.api.permalinks.MatrixPermalinkSpan import im.vector.matrix.android.api.permalinks.MatrixPermalinkSpan
import im.vector.matrix.android.api.session.events.model.EnrichedEvent import im.vector.matrix.android.api.session.events.model.TimelineEvent
import im.vector.matrix.android.api.session.events.model.toModel import im.vector.matrix.android.api.session.events.model.toModel
import im.vector.matrix.android.api.session.room.model.MessageContent import im.vector.matrix.android.api.session.room.model.MessageContent
import org.threeten.bp.LocalDateTime import org.threeten.bp.LocalDateTime
@ -13,8 +13,8 @@ class MessageItemFactory(private val timelineDateFormatter: TimelineDateFormatte


private val messagesDisplayedWithInformation = HashSet<String?>() private val messagesDisplayedWithInformation = HashSet<String?>()


fun create(event: EnrichedEvent, fun create(event: TimelineEvent,
nextEvent: EnrichedEvent?, nextEvent: TimelineEvent?,
addDaySeparator: Boolean, addDaySeparator: Boolean,
date: LocalDateTime, date: LocalDateTime,
callback: TimelineEventController.Callback? callback: TimelineEventController.Callback?

View File

@ -1,10 +1,10 @@
package im.vector.riotredesign.features.home.room.detail.timeline package im.vector.riotredesign.features.home.room.detail.timeline


import im.vector.matrix.android.api.session.events.model.EnrichedEvent import im.vector.matrix.android.api.session.events.model.TimelineEvent


class TextItemFactory { class TextItemFactory {


fun create(event: EnrichedEvent): TextItem? { fun create(event: TimelineEvent): TextItem? {
val text = "${event.root.type} events are not yet handled" val text = "${event.root.type} events are not yet handled"
return TextItem(text = text) return TextItem(text = text)
} }

View File

@ -2,7 +2,7 @@ package im.vector.riotredesign.features.home.room.detail.timeline


import com.airbnb.epoxy.EpoxyAsyncUtil import com.airbnb.epoxy.EpoxyAsyncUtil
import com.airbnb.epoxy.EpoxyModel import com.airbnb.epoxy.EpoxyModel
import im.vector.matrix.android.api.session.events.model.EnrichedEvent import im.vector.matrix.android.api.session.events.model.TimelineEvent
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.room.timeline.TimelineData import im.vector.matrix.android.api.session.room.timeline.TimelineData
import im.vector.riotredesign.core.extensions.localDateTime import im.vector.riotredesign.core.extensions.localDateTime
@ -13,7 +13,7 @@ class TimelineEventController(private val roomId: String,
private val messageItemFactory: MessageItemFactory, private val messageItemFactory: MessageItemFactory,
private val textItemFactory: TextItemFactory, private val textItemFactory: TextItemFactory,
private val dateFormatter: TimelineDateFormatter private val dateFormatter: TimelineDateFormatter
) : PagedListEpoxyController<EnrichedEvent>( ) : PagedListEpoxyController<TimelineEvent>(
EpoxyAsyncUtil.getAsyncBackgroundHandler(), EpoxyAsyncUtil.getAsyncBackgroundHandler(),
EpoxyAsyncUtil.getAsyncBackgroundHandler() EpoxyAsyncUtil.getAsyncBackgroundHandler()
) { ) {
@ -38,7 +38,7 @@ class TimelineEventController(private val roomId: String,
} }




override fun buildItemModels(currentPosition: Int, items: List<EnrichedEvent?>): List<EpoxyModel<*>> { override fun buildItemModels(currentPosition: Int, items: List<TimelineEvent?>): List<EpoxyModel<*>> {
if (items.isNullOrEmpty()) { if (items.isNullOrEmpty()) {
return emptyList() return emptyList()
} }

View File

@ -70,6 +70,9 @@ abstract class PagedListEpoxyController<T>(
modelBuildingHandler = modelBuildingHandler modelBuildingHandler = modelBuildingHandler
) )


var currentList: PagedList<T>? = null
private set

final override fun buildModels() { final override fun buildModels() {
addModels(modelCache.getModels()) addModels(modelCache.getModels())
} }
@ -107,6 +110,7 @@ abstract class PagedListEpoxyController<T>(
* to [buildItemModel] with items from the previous list. * to [buildItemModel] with items from the previous list.
*/ */
fun submitList(newList: PagedList<T>?) { fun submitList(newList: PagedList<T>?) {
currentList = newList
modelCache.submitList(newList) modelCache.submitList(newList)
} }



View File

@ -7,7 +7,7 @@ import im.vector.matrix.android.InstrumentedTest
import im.vector.matrix.android.LiveDataTestObserver import im.vector.matrix.android.LiveDataTestObserver
import im.vector.matrix.android.api.thread.MainThreadExecutor import im.vector.matrix.android.api.thread.MainThreadExecutor
import im.vector.matrix.android.internal.session.room.members.RoomMemberExtractor import im.vector.matrix.android.internal.session.room.members.RoomMemberExtractor
import im.vector.matrix.android.internal.session.room.timeline.DefaultTimelineHolder import im.vector.matrix.android.internal.session.room.timeline.DefaultTimelineService
import im.vector.matrix.android.internal.session.room.timeline.TimelineBoundaryCallback import im.vector.matrix.android.internal.session.room.timeline.TimelineBoundaryCallback
import im.vector.matrix.android.internal.session.room.timeline.TokenChunkEventPersistor import im.vector.matrix.android.internal.session.room.timeline.TokenChunkEventPersistor
import im.vector.matrix.android.internal.task.TaskExecutor import im.vector.matrix.android.internal.task.TaskExecutor
@ -44,17 +44,17 @@ internal class TimelineHolderTest : InstrumentedTest {
val boundaryCallback = TimelineBoundaryCallback(roomId, taskExecutor, paginationTask, monarchy, PagingRequestHelper(MainThreadExecutor())) val boundaryCallback = TimelineBoundaryCallback(roomId, taskExecutor, paginationTask, monarchy, PagingRequestHelper(MainThreadExecutor()))


RoomDataHelper.fakeInitialSync(monarchy, roomId) RoomDataHelper.fakeInitialSync(monarchy, roomId)
val timelineHolder = DefaultTimelineHolder(roomId, monarchy, taskExecutor, boundaryCallback, getContextOfEventTask, RoomMemberExtractor(monarchy, roomId)) val timelineHolder = DefaultTimelineService(roomId, monarchy, taskExecutor, boundaryCallback, getContextOfEventTask, RoomMemberExtractor(monarchy, roomId))
val timelineObserver = LiveDataTestObserver.test(timelineHolder.timeline()) val timelineObserver = LiveDataTestObserver.test(timelineHolder.timeline())
timelineObserver.awaitNextValue().assertHasValue() timelineObserver.awaitNextValue().assertHasValue()
var pagedList = timelineObserver.value() var timelineData = timelineObserver.value()
pagedList.size shouldEqual 30 timelineData.events.size shouldEqual 30
(0 until pagedList.size).map { (0 until timelineData.events.size).map {
pagedList.loadAround(it) timelineData.events.loadAround(it)
} }
timelineObserver.awaitNextValue().assertHasValue() timelineObserver.awaitNextValue().assertHasValue()
pagedList = timelineObserver.value() timelineData = timelineObserver.value()
pagedList.size shouldEqual 60 timelineData.events.size shouldEqual 60
} }





View File

@ -15,4 +15,12 @@ interface Session : RoomService, GroupService {
@MainThread @MainThread
fun close() fun close()


fun addListener(listener: Listener)

fun removeListener(listener: Listener)

// Not used at the moment
interface Listener


} }

View File

@ -1,12 +0,0 @@
package im.vector.matrix.android.api.session.events.interceptor

import im.vector.matrix.android.api.session.events.model.EnrichedEvent

interface EnrichedEventInterceptor {

fun canEnrich(event: EnrichedEvent): Boolean

fun enrich(event: EnrichedEvent)

}

View File

@ -0,0 +1,12 @@
package im.vector.matrix.android.api.session.events.interceptor

import im.vector.matrix.android.api.session.events.model.TimelineEvent

interface TimelineEventInterceptor {

fun canEnrich(event: TimelineEvent): Boolean

fun enrich(event: TimelineEvent)

}

View File

@ -2,7 +2,7 @@ package im.vector.matrix.android.api.session.events.model


import im.vector.matrix.android.api.session.room.model.RoomMember import im.vector.matrix.android.api.session.room.model.RoomMember


data class EnrichedEvent( data class TimelineEvent(
val root: Event, val root: Event,
val localId: String, val localId: String,
val roomMember: RoomMember? val roomMember: RoomMember?

View File

@ -1,10 +1,10 @@
package im.vector.matrix.android.api.session.room.timeline package im.vector.matrix.android.api.session.room.timeline


import android.arch.paging.PagedList import android.arch.paging.PagedList
import im.vector.matrix.android.api.session.events.model.EnrichedEvent import im.vector.matrix.android.api.session.events.model.TimelineEvent


data class TimelineData( data class TimelineData(
val events: PagedList<EnrichedEvent>, val events: PagedList<TimelineEvent>,
val isLoadingForward: Boolean = false, val isLoadingForward: Boolean = false,
val isLoadingBackward: Boolean = false val isLoadingBackward: Boolean = false
) )

View File

@ -4,7 +4,6 @@ import android.arch.lifecycle.LiveData
import android.arch.lifecycle.Observer import android.arch.lifecycle.Observer
import com.zhuinden.monarchy.Monarchy import com.zhuinden.monarchy.Monarchy
import io.realm.RealmObject import io.realm.RealmObject
import io.realm.RealmResults
import java.util.concurrent.atomic.AtomicBoolean import java.util.concurrent.atomic.AtomicBoolean


internal interface LiveEntityObserver { internal interface LiveEntityObserver {
@ -39,11 +38,15 @@ internal abstract class RealmLiveEntityObserver<T : RealmObject>(protected val m
if (changeSet == null) { if (changeSet == null) {
return return
} }
val updateIndexes = changeSet.orderedCollectionChangeSet.changes + changeSet.orderedCollectionChangeSet.insertions val insertionIndexes = changeSet.orderedCollectionChangeSet.insertions
val updateIndexes = changeSet.orderedCollectionChangeSet.changes
val deletionIndexes = changeSet.orderedCollectionChangeSet.deletions val deletionIndexes = changeSet.orderedCollectionChangeSet.deletions
process(changeSet.realmResults, updateIndexes, deletionIndexes) val inserted = changeSet.realmResults.filterIndexed { index, _ -> insertionIndexes.contains(index) }
val updated = changeSet.realmResults.filterIndexed { index, _ -> updateIndexes.contains(index) }
val deleted = changeSet.realmResults.filterIndexed { index, _ -> deletionIndexes.contains(index) }
process(inserted, updated, deleted)
} }


abstract fun process(results: RealmResults<T>, updateIndexes: IntArray, deletionIndexes: IntArray) abstract fun process(inserted: List<T>, updated: List<T>, deleted: List<T>)


} }

View File

@ -26,12 +26,13 @@ internal fun EventEntity.Companion.where(realm: Realm,
query.equalTo(EventEntityFields.TYPE, type) query.equalTo(EventEntityFields.TYPE, type)
} }
return when (linkFilterMode) { return when (linkFilterMode) {
LINKED_ONLY -> query.equalTo(EventEntityFields.IS_UNLINKED, false) LINKED_ONLY -> query.equalTo(EventEntityFields.IS_UNLINKED, false)
UNLINKED_ONLY -> query.equalTo(EventEntityFields.IS_UNLINKED, true) UNLINKED_ONLY -> query.equalTo(EventEntityFields.IS_UNLINKED, true)
BOTH -> query BOTH -> query
} }
} }



internal fun RealmQuery<EventEntity>.next(from: Int? = null, strict: Boolean = true): EventEntity? { internal fun RealmQuery<EventEntity>.next(from: Int? = null, strict: Boolean = true): EventEntity? {
if (from != null) { if (from != null) {
if (strict) { if (strict) {

View File

@ -31,6 +31,7 @@ internal class DefaultSession(override val sessionParams: SessionParams) : Sessi
private lateinit var scope: Scope private lateinit var scope: Scope


private val liveEntityUpdaters by inject<List<LiveEntityObserver>>() private val liveEntityUpdaters by inject<List<LiveEntityObserver>>()
private val sessionListeners by inject<SessionListeners>()
private val roomService by inject<RoomService>() private val roomService by inject<RoomService>()
private val groupService by inject<GroupService>() private val groupService by inject<GroupService>()
private val syncThread by inject<SyncThread>() private val syncThread by inject<SyncThread>()
@ -62,6 +63,14 @@ internal class DefaultSession(override val sessionParams: SessionParams) : Sessi
isOpen = false isOpen = false
} }


override fun addListener(listener: Session.Listener) {
sessionListeners.addListener(listener)
}

override fun removeListener(listener: Session.Listener) {
sessionListeners.removeListener(listener)
}

// ROOM SERVICE // ROOM SERVICE


override fun getRoom(roomId: String): Room? { override fun getRoom(roomId: String): Room? {

View File

@ -0,0 +1,17 @@
package im.vector.matrix.android.internal.session

import im.vector.matrix.android.api.session.Session

internal class SessionListeners {

private val listeners = ArrayList<Session.Listener>()

fun addListener(listener: Session.Listener) {
listeners.add(listener)
}

fun removeListener(listener: Session.Listener) {
listeners.remove(listener)
}

}

View File

@ -6,7 +6,6 @@ import im.vector.matrix.android.api.auth.data.SessionParams
import im.vector.matrix.android.api.session.group.GroupService import im.vector.matrix.android.api.session.group.GroupService
import im.vector.matrix.android.api.session.room.RoomService import im.vector.matrix.android.api.session.room.RoomService
import im.vector.matrix.android.internal.database.LiveEntityObserver import im.vector.matrix.android.internal.database.LiveEntityObserver
import im.vector.matrix.android.internal.session.room.prune.EventsPruner
import im.vector.matrix.android.internal.session.group.DefaultGroupService import im.vector.matrix.android.internal.session.group.DefaultGroupService
import im.vector.matrix.android.internal.session.group.GroupSummaryUpdater import im.vector.matrix.android.internal.session.group.GroupSummaryUpdater
import im.vector.matrix.android.internal.session.room.DefaultRoomService import im.vector.matrix.android.internal.session.room.DefaultRoomService
@ -14,6 +13,7 @@ import im.vector.matrix.android.internal.session.room.RoomAvatarResolver
import im.vector.matrix.android.internal.session.room.RoomSummaryUpdater import im.vector.matrix.android.internal.session.room.RoomSummaryUpdater
import im.vector.matrix.android.internal.session.room.members.RoomDisplayNameResolver import im.vector.matrix.android.internal.session.room.members.RoomDisplayNameResolver
import im.vector.matrix.android.internal.session.room.members.RoomMemberDisplayNameResolver import im.vector.matrix.android.internal.session.room.members.RoomMemberDisplayNameResolver
import im.vector.matrix.android.internal.session.room.prune.EventsPruner
import im.vector.matrix.android.internal.util.md5 import im.vector.matrix.android.internal.util.md5
import io.realm.RealmConfiguration import io.realm.RealmConfiguration
import org.koin.dsl.module.module import org.koin.dsl.module.module
@ -75,7 +75,10 @@ internal class SessionModule(private val sessionParams: SessionParams) {
} }


scope(DefaultSession.SCOPE) { scope(DefaultSession.SCOPE) {
SessionListeners()
}


scope(DefaultSession.SCOPE) {
val roomSummaryUpdater = RoomSummaryUpdater(get(), get(), get(), get(), sessionParams.credentials) val roomSummaryUpdater = RoomSummaryUpdater(get(), get(), get(), get(), sessionParams.credentials)
val groupSummaryUpdater = GroupSummaryUpdater(get()) val groupSummaryUpdater = GroupSummaryUpdater(get())
val eventsPruner = EventsPruner(get()) val eventsPruner = EventsPruner(get())

View File

@ -15,9 +15,7 @@ internal class GetGroupDataWorker(context: Context,


@JsonClass(generateAdapter = true) @JsonClass(generateAdapter = true)
internal data class Params( internal data class Params(
val groupIds: List<String>, val groupIds: List<String>
val updateIndexes: List<Int>,
val deletionIndexes: List<Int>
) )


private val getGroupDataTask by inject<GetGroupDataTask>() private val getGroupDataTask by inject<GetGroupDataTask>()
@ -26,8 +24,7 @@ internal class GetGroupDataWorker(context: Context,
val params = WorkerParamsFactory.fromData<Params>(inputData) val params = WorkerParamsFactory.fromData<Params>(inputData)
?: return Result.failure() ?: return Result.failure()


val results = params.updateIndexes.map { index -> val results = params.groupIds.map { groupId ->
val groupId = params.groupIds[index]
fetchGroupData(groupId) fetchGroupData(groupId)
} }
val isSuccessful = results.none { it.isFailure() } val isSuccessful = results.none { it.isFailure() }

View File

@ -1,12 +1,15 @@
package im.vector.matrix.android.internal.session.group package im.vector.matrix.android.internal.session.group


import androidx.work.* import androidx.work.Constraints
import androidx.work.ExistingWorkPolicy
import androidx.work.NetworkType
import androidx.work.OneTimeWorkRequestBuilder
import androidx.work.WorkManager
import com.zhuinden.monarchy.Monarchy import com.zhuinden.monarchy.Monarchy
import im.vector.matrix.android.internal.database.RealmLiveEntityObserver import im.vector.matrix.android.internal.database.RealmLiveEntityObserver
import im.vector.matrix.android.internal.database.model.GroupEntity import im.vector.matrix.android.internal.database.model.GroupEntity
import im.vector.matrix.android.internal.database.query.where import im.vector.matrix.android.internal.database.query.where
import im.vector.matrix.android.internal.util.WorkerParamsFactory import im.vector.matrix.android.internal.util.WorkerParamsFactory
import io.realm.RealmResults


private const val GET_GROUP_DATA_WORKER = "GET_GROUP_DATA_WORKER" private const val GET_GROUP_DATA_WORKER = "GET_GROUP_DATA_WORKER"


@ -19,9 +22,9 @@ internal class GroupSummaryUpdater(monarchy: Monarchy
.setRequiredNetworkType(NetworkType.CONNECTED) .setRequiredNetworkType(NetworkType.CONNECTED)
.build() .build()


override fun process(results: RealmResults<GroupEntity>, updateIndexes: IntArray, deletionIndexes: IntArray) { override fun process(inserted: List<GroupEntity>, updated: List<GroupEntity>, deleted: List<GroupEntity>) {
val groupIds = results.map { it.groupId } val newGroupIds = inserted.map { it.groupId }
val getGroupDataWorkerParams = GetGroupDataWorker.Params(groupIds, updateIndexes.toList(), deletionIndexes.toList()) val getGroupDataWorkerParams = GetGroupDataWorker.Params(newGroupIds)
val workData = WorkerParamsFactory.toData(getGroupDataWorkerParams) val workData = WorkerParamsFactory.toData(getGroupDataWorkerParams)


val sendWork = OneTimeWorkRequestBuilder<GetGroupDataWorker>() val sendWork = OneTimeWorkRequestBuilder<GetGroupDataWorker>()

View File

@ -2,24 +2,23 @@ package im.vector.matrix.android.internal.session.room


import android.arch.lifecycle.LiveData import android.arch.lifecycle.LiveData
import android.arch.lifecycle.Transformations import android.arch.lifecycle.Transformations
import android.arch.paging.PagedList
import com.zhuinden.monarchy.Monarchy import com.zhuinden.monarchy.Monarchy
import im.vector.matrix.android.api.MatrixCallback import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.session.events.model.EnrichedEvent
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.room.Room import im.vector.matrix.android.api.session.room.Room
import im.vector.matrix.android.api.session.room.SendService import im.vector.matrix.android.api.session.room.SendService
import im.vector.matrix.android.api.session.room.timeline.TimelineService
import im.vector.matrix.android.api.session.room.model.Membership import im.vector.matrix.android.api.session.room.model.Membership
import im.vector.matrix.android.api.session.room.model.MyMembership import im.vector.matrix.android.api.session.room.model.MyMembership
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.TimelineData import im.vector.matrix.android.api.session.room.timeline.TimelineData
import im.vector.matrix.android.api.session.room.timeline.TimelineService
import im.vector.matrix.android.api.util.Cancelable import im.vector.matrix.android.api.util.Cancelable
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.RoomSummaryEntity import im.vector.matrix.android.internal.database.model.RoomSummaryEntity
import im.vector.matrix.android.internal.database.model.RoomSummaryEntityFields import im.vector.matrix.android.internal.database.model.RoomSummaryEntityFields
import im.vector.matrix.android.internal.database.query.where import im.vector.matrix.android.internal.database.query.where
import im.vector.matrix.android.internal.di.MatrixKoinComponent import im.vector.matrix.android.internal.di.MatrixKoinComponent
import im.vector.matrix.android.internal.session.SessionListeners
import im.vector.matrix.android.internal.session.room.members.LoadRoomMembersTask import im.vector.matrix.android.internal.session.room.members.LoadRoomMembersTask
import im.vector.matrix.android.internal.task.TaskExecutor import im.vector.matrix.android.internal.task.TaskExecutor
import im.vector.matrix.android.internal.task.configureWith import im.vector.matrix.android.internal.task.configureWith
@ -62,4 +61,5 @@ internal data class DefaultRoom(
return sendService.sendTextMessage(text, callback) return sendService.sendTextMessage(text, callback)
} }



} }

View File

@ -17,7 +17,6 @@ import im.vector.matrix.android.internal.database.query.where
import im.vector.matrix.android.internal.session.room.members.RoomDisplayNameResolver import im.vector.matrix.android.internal.session.room.members.RoomDisplayNameResolver
import im.vector.matrix.android.internal.session.room.members.RoomMembers import im.vector.matrix.android.internal.session.room.members.RoomMembers
import io.realm.Realm import io.realm.Realm
import io.realm.RealmResults
import io.realm.kotlin.createObject import io.realm.kotlin.createObject


internal class RoomSummaryUpdater(monarchy: Monarchy, internal class RoomSummaryUpdater(monarchy: Monarchy,
@ -29,13 +28,10 @@ internal class RoomSummaryUpdater(monarchy: Monarchy,


override val query = Monarchy.Query<RoomEntity> { RoomEntity.where(it) } override val query = Monarchy.Query<RoomEntity> { RoomEntity.where(it) }


override fun process(results: RealmResults<RoomEntity>, updateIndexes: IntArray, deletionIndexes: IntArray) { override fun process(inserted: List<RoomEntity>, updated: List<RoomEntity>, deleted: List<RoomEntity>) {
val rooms = results.map { it.asDomain() } val rooms = (inserted + updated).map { it.asDomain() }
monarchy.writeAsync { realm -> monarchy.writeAsync { realm ->
updateIndexes.forEach { index -> rooms.forEach { updateRoom(realm, it) }
val data = rooms[index]
updateRoom(realm, data)
}
} }
} }



View File

@ -10,7 +10,6 @@ import im.vector.matrix.android.internal.database.mapper.asDomain
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.util.WorkerParamsFactory import im.vector.matrix.android.internal.util.WorkerParamsFactory
import io.realm.RealmResults


private const val PRUNE_EVENT_WORKER = "PRUNE_EVENT_WORKER" private const val PRUNE_EVENT_WORKER = "PRUNE_EVENT_WORKER"


@ -19,9 +18,9 @@ internal class EventsPruner(monarchy: Monarchy) :


override val query = Monarchy.Query<EventEntity> { EventEntity.where(it, type = EventType.REDACTION) } override val query = Monarchy.Query<EventEntity> { EventEntity.where(it, type = EventType.REDACTION) }


override fun process(results: RealmResults<EventEntity>, updateIndexes: IntArray, deletionIndexes: IntArray) { override fun process(inserted: List<EventEntity>, updated: List<EventEntity>, deleted: List<EventEntity>) {
val redactionEvents = results.map { it.asDomain() } val redactionEvents = inserted.map { it.asDomain() }
val pruneEventWorkerParams = PruneEventWorker.Params(redactionEvents, updateIndexes.toList(), deletionIndexes.toList()) val pruneEventWorkerParams = PruneEventWorker.Params(redactionEvents)
val workData = WorkerParamsFactory.toData(pruneEventWorkerParams) val workData = WorkerParamsFactory.toData(pruneEventWorkerParams)


val sendWork = OneTimeWorkRequestBuilder<PruneEventWorker>() val sendWork = OneTimeWorkRequestBuilder<PruneEventWorker>()

View File

@ -13,6 +13,7 @@ import im.vector.matrix.android.internal.database.query.where
import im.vector.matrix.android.internal.di.MatrixKoinComponent import im.vector.matrix.android.internal.di.MatrixKoinComponent
import im.vector.matrix.android.internal.util.WorkerParamsFactory import im.vector.matrix.android.internal.util.WorkerParamsFactory
import im.vector.matrix.android.internal.util.tryTransactionAsync import im.vector.matrix.android.internal.util.tryTransactionAsync
import im.vector.matrix.android.internal.util.tryTransactionSync
import io.realm.Realm import io.realm.Realm
import org.koin.standalone.inject import org.koin.standalone.inject


@ -22,9 +23,7 @@ internal class PruneEventWorker(context: Context,


@JsonClass(generateAdapter = true) @JsonClass(generateAdapter = true)
internal data class Params( internal data class Params(
val redactionEvents: List<Event>, val redactionEvents: List<Event>
val updateIndexes: List<Int>,
val deletionIndexes: List<Int>
) )


private val monarchy by inject<Monarchy>() private val monarchy by inject<Monarchy>()
@ -33,10 +32,9 @@ internal class PruneEventWorker(context: Context,
val params = WorkerParamsFactory.fromData<Params>(inputData) val params = WorkerParamsFactory.fromData<Params>(inputData)
?: return Result.failure() ?: return Result.failure()


val result = monarchy.tryTransactionAsync { realm -> val result = monarchy.tryTransactionSync { realm ->
params.updateIndexes.forEach { index -> params.redactionEvents.forEach { event ->
val data = params.redactionEvents[index] pruneEvent(realm, event)
pruneEvent(realm, data)
} }
} }
return result.fold({ Result.retry() }, { Result.success() }) return result.fold({ Result.retry() }, { Result.success() })

View File

@ -4,8 +4,8 @@ import android.arch.lifecycle.LiveData
import android.arch.paging.LivePagedListBuilder import android.arch.paging.LivePagedListBuilder
import android.arch.paging.PagedList import android.arch.paging.PagedList
import com.zhuinden.monarchy.Monarchy import com.zhuinden.monarchy.Monarchy
import im.vector.matrix.android.api.session.events.interceptor.EnrichedEventInterceptor import im.vector.matrix.android.api.session.events.interceptor.TimelineEventInterceptor
import im.vector.matrix.android.api.session.events.model.EnrichedEvent import im.vector.matrix.android.api.session.events.model.TimelineEvent
import im.vector.matrix.android.api.session.room.timeline.TimelineData import im.vector.matrix.android.api.session.room.timeline.TimelineData
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.mapper.asDomain import im.vector.matrix.android.internal.database.mapper.asDomain
@ -34,35 +34,21 @@ internal class DefaultTimelineService(private val roomId: String,
private val roomMemberExtractor: RoomMemberExtractor private val roomMemberExtractor: RoomMemberExtractor
) : TimelineService { ) : TimelineService {


private val eventInterceptors = ArrayList<EnrichedEventInterceptor>() private val eventInterceptors = ArrayList<TimelineEventInterceptor>()


override fun timeline(eventId: String?): LiveData<TimelineData> { override fun timeline(eventId: String?): LiveData<TimelineData> {
clearUnlinkedEvents() clearUnlinkedEvents()
var initialLoadKey = 0 val initialLoadKey = getInitialLoadKey(eventId)
if (eventId != null) {
val indexOfEvent = indexOfEvent(eventId)
if (indexOfEvent == EVENT_NOT_FOUND_INDEX) {
val params = GetContextOfEventTask.Params(roomId, eventId)
contextOfEventTask.configureWith(params).executeBy(taskExecutor)
} else {
initialLoadKey = indexOfEvent
}
}
val realmDataSourceFactory = monarchy.createDataSourceFactory { val realmDataSourceFactory = monarchy.createDataSourceFactory {
buildDataSourceFactoryQuery(it, eventId) buildDataSourceFactoryQuery(it, eventId)
} }
val domainSourceFactory = realmDataSourceFactory val domainSourceFactory = realmDataSourceFactory
.map { eventEntity -> .map { eventEntity ->
val roomMember = roomMemberExtractor.extractFrom(eventEntity) val roomMember = roomMemberExtractor.extractFrom(eventEntity)
EnrichedEvent(eventEntity.asDomain(), eventEntity.localId, roomMember) TimelineEvent(eventEntity.asDomain(), eventEntity.localId, roomMember)
} }


val pagedListConfig = PagedList.Config.Builder() val pagedListConfig = buildPagedListConfig()
.setEnablePlaceholders(false)
.setPageSize(PAGE_SIZE)
.setInitialLoadSizeHint(2 * PAGE_SIZE)
.setPrefetchDistance(PREFETCH_DISTANCE)
.build()


val livePagedListBuilder = LivePagedListBuilder(domainSourceFactory, pagedListConfig) val livePagedListBuilder = LivePagedListBuilder(domainSourceFactory, pagedListConfig)
.setBoundaryCallback(boundaryCallback) .setBoundaryCallback(boundaryCallback)
@ -77,6 +63,35 @@ internal class DefaultTimelineService(private val roomId: String,
} }
} }


// PRIVATE FUNCTIONS ***************************************************************************

private fun getInitialLoadKey(eventId: String?): Int {
var initialLoadKey = 0
if (eventId != null) {
val indexOfEvent = indexOfEvent(eventId)
if (indexOfEvent == EVENT_NOT_FOUND_INDEX) {
fetchEvent(eventId)
} else {
initialLoadKey = indexOfEvent
}
}
return initialLoadKey
}


private fun fetchEvent(eventId: String) {
val params = GetContextOfEventTask.Params(roomId, eventId)
contextOfEventTask.configureWith(params).executeBy(taskExecutor)
}

private fun buildPagedListConfig(): PagedList.Config {
return PagedList.Config.Builder()
.setEnablePlaceholders(false)
.setPageSize(PAGE_SIZE)
.setInitialLoadSizeHint(2 * PAGE_SIZE)
.setPrefetchDistance(PREFETCH_DISTANCE)
.build()
}


private fun clearUnlinkedEvents() { private fun clearUnlinkedEvents() {
monarchy.tryTransactionAsync { realm -> monarchy.tryTransactionAsync { realm ->

View File

@ -4,7 +4,7 @@ import android.arch.lifecycle.LiveData
import android.arch.paging.PagedList import android.arch.paging.PagedList
import com.zhuinden.monarchy.Monarchy import com.zhuinden.monarchy.Monarchy
import im.vector.matrix.android.api.MatrixCallback import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.session.events.model.EnrichedEvent import im.vector.matrix.android.api.session.events.model.TimelineEvent
import im.vector.matrix.android.internal.database.model.ChunkEntity import im.vector.matrix.android.internal.database.model.ChunkEntity
import im.vector.matrix.android.internal.database.query.findIncludingEvent import im.vector.matrix.android.internal.database.query.findIncludingEvent
import im.vector.matrix.android.internal.task.TaskExecutor import im.vector.matrix.android.internal.task.TaskExecutor
@ -16,7 +16,7 @@ internal class TimelineBoundaryCallback(private val roomId: String,
private val paginationTask: PaginationTask, private val paginationTask: PaginationTask,
private val monarchy: Monarchy, private val monarchy: Monarchy,
private val helper: PagingRequestHelper private val helper: PagingRequestHelper
) : PagedList.BoundaryCallback<EnrichedEvent>() { ) : PagedList.BoundaryCallback<TimelineEvent>() {


var limit = 30 var limit = 30


@ -41,7 +41,7 @@ internal class TimelineBoundaryCallback(private val roomId: String,
// actually, it's not possible // actually, it's not possible
} }


override fun onItemAtEndLoaded(itemAtEnd: EnrichedEvent) { override fun onItemAtEndLoaded(itemAtEnd: TimelineEvent) {
val token = itemAtEnd.root.eventId?.let { getToken(it, PaginationDirection.BACKWARDS) } val token = itemAtEnd.root.eventId?.let { getToken(it, PaginationDirection.BACKWARDS) }
?: return ?: return


@ -50,7 +50,7 @@ internal class TimelineBoundaryCallback(private val roomId: String,
} }
} }


override fun onItemAtFrontLoaded(itemAtFront: EnrichedEvent) { override fun onItemAtFrontLoaded(itemAtFront: TimelineEvent) {
val token = itemAtFront.root.eventId?.let { getToken(it, PaginationDirection.FORWARDS) } val token = itemAtFront.root.eventId?.let { getToken(it, PaginationDirection.FORWARDS) }
?: return ?: return