forked from GitHub-Mirror/riotX-android
Handle epoxy timeline manually : get off the cache at the moment, will need to be improved
This commit is contained in:
parent
bc8055c3cd
commit
ecf728aef8
@ -12,11 +12,13 @@ abstract class KotlinModel(
|
||||
) : EpoxyModel<View>() {
|
||||
|
||||
private var view: View? = null
|
||||
private var onBindCallback: (() -> Unit)? = null
|
||||
|
||||
abstract fun bind()
|
||||
|
||||
override fun bind(view: View) {
|
||||
this.view = view
|
||||
onBindCallback?.invoke()
|
||||
bind()
|
||||
}
|
||||
|
||||
@ -24,6 +26,11 @@ abstract class KotlinModel(
|
||||
this.view = null
|
||||
}
|
||||
|
||||
fun onBind(lambda: (() -> Unit)?): KotlinModel {
|
||||
onBindCallback = lambda
|
||||
return this
|
||||
}
|
||||
|
||||
override fun getDefaultLayout() = layoutRes
|
||||
|
||||
protected fun <V : View> bind(@IdRes id: Int) = object : ReadOnlyProperty<KotlinModel, V> {
|
||||
@ -33,7 +40,7 @@ abstract class KotlinModel(
|
||||
// be optimized with a map
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
return view?.findViewById(id) as V?
|
||||
?: throw IllegalStateException("View ID $id for '${property.name}' not found.")
|
||||
?: throw IllegalStateException("View ID $id for '${property.name}' not found.")
|
||||
}
|
||||
}
|
||||
}
|
@ -4,15 +4,14 @@ import android.support.v7.util.DiffUtil
|
||||
import im.vector.matrix.android.api.session.events.model.EnrichedEvent
|
||||
|
||||
class EventDiffUtilCallback : DiffUtil.ItemCallback<EnrichedEvent>() {
|
||||
|
||||
override fun areItemsTheSame(p0: EnrichedEvent, p1: EnrichedEvent): Boolean {
|
||||
return p0.root.eventId == p1.root.eventId
|
||||
}
|
||||
|
||||
override fun areContentsTheSame(p0: EnrichedEvent, p1: EnrichedEvent): Boolean {
|
||||
return p0.root == p1.root
|
||||
&& p0.getMetaEvents()
|
||||
.zip(p1.getMetaEvents()) { a, b ->
|
||||
a.eventId == b.eventId
|
||||
}.none { !it }
|
||||
&& p0.metadata == p1.metadata
|
||||
}
|
||||
|
||||
}
|
@ -4,7 +4,6 @@ import android.arch.lifecycle.Observer
|
||||
import android.arch.paging.PagedList
|
||||
import android.os.Bundle
|
||||
import android.support.v7.widget.LinearLayoutManager
|
||||
import android.support.v7.widget.RecyclerView
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
@ -20,7 +19,7 @@ import im.vector.riotredesign.features.home.RoomSummaryViewHelper
|
||||
import kotlinx.android.synthetic.main.fragment_room_detail.*
|
||||
import org.koin.android.ext.android.inject
|
||||
|
||||
class RoomDetailFragment : RiotFragment(), TimelineEventAdapter.Callback {
|
||||
class RoomDetailFragment : RiotFragment() {
|
||||
|
||||
companion object {
|
||||
|
||||
@ -34,7 +33,6 @@ class RoomDetailFragment : RiotFragment(), TimelineEventAdapter.Callback {
|
||||
private val matrix by inject<Matrix>()
|
||||
private val currentSession = matrix.currentSession!!
|
||||
private var roomId by FragmentArgumentDelegate<String>()
|
||||
private val timelineAdapter = TimelineEventAdapter(this)
|
||||
private val timelineEventController = TimelineEventController()
|
||||
private lateinit var room: Room
|
||||
|
||||
@ -61,18 +59,8 @@ class RoomDetailFragment : RiotFragment(), TimelineEventAdapter.Callback {
|
||||
|
||||
private fun setupRecyclerView() {
|
||||
val layoutManager = LinearLayoutManager(context, LinearLayoutManager.VERTICAL, true)
|
||||
layoutManager.stackFromEnd = true
|
||||
timelineAdapter.registerAdapterDataObserver(object : RecyclerView.AdapterDataObserver() {
|
||||
override fun onItemRangeInserted(positionStart: Int, itemCount: Int) {
|
||||
if (layoutManager.findFirstVisibleItemPosition() == 0) {
|
||||
layoutManager.scrollToPosition(0)
|
||||
}
|
||||
|
||||
}
|
||||
})
|
||||
recyclerView.layoutManager = layoutManager
|
||||
recyclerView.adapter = timelineAdapter
|
||||
//recyclerView.setController(timelineEventController)
|
||||
recyclerView.setController(timelineEventController)
|
||||
}
|
||||
|
||||
private fun renderRoomSummary(roomSummary: RoomSummary?) {
|
||||
@ -90,16 +78,8 @@ class RoomDetailFragment : RiotFragment(), TimelineEventAdapter.Callback {
|
||||
}
|
||||
|
||||
private fun renderEvents(events: PagedList<EnrichedEvent>?) {
|
||||
timelineAdapter.submitList(events)
|
||||
timelineEventController.timeline = events
|
||||
timelineEventController.requestModelBuild()
|
||||
}
|
||||
|
||||
|
||||
override
|
||||
fun onEventsListChanged(oldList: List<EnrichedEvent>?, newList: List<EnrichedEvent>?) {
|
||||
if (oldList == null && newList != null) {
|
||||
recyclerView.scrollToPosition(0)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -1,68 +0,0 @@
|
||||
package im.vector.riotredesign.features.home.room.detail
|
||||
|
||||
import android.arch.paging.PagedList
|
||||
import android.arch.paging.PagedListAdapter
|
||||
import android.support.v7.widget.RecyclerView
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.TextView
|
||||
import im.vector.matrix.android.api.session.events.model.EnrichedEvent
|
||||
import im.vector.matrix.android.api.session.events.model.EventType
|
||||
import im.vector.matrix.android.api.session.room.model.MessageContent
|
||||
import im.vector.matrix.android.api.session.room.model.RoomMember
|
||||
import im.vector.riotredesign.R
|
||||
|
||||
/**
|
||||
* Created by francois on 14/05/2018.
|
||||
*/
|
||||
|
||||
class TimelineEventAdapter(private val callback: Callback? = null)
|
||||
: PagedListAdapter<EnrichedEvent, TimelineEventAdapter.ViewHolder>(EventDiffUtilCallback()) {
|
||||
|
||||
|
||||
private var currentList: List<EnrichedEvent>? = null
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, position: Int): ViewHolder {
|
||||
val view = LayoutInflater.from(parent.context).inflate(R.layout.item_event, parent, false)
|
||||
return ViewHolder(view)
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(viewHolder: ViewHolder, position: Int) {
|
||||
val event = getItem(position)
|
||||
viewHolder.bind(event)
|
||||
}
|
||||
|
||||
class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
|
||||
|
||||
val titleView = view.findViewById<TextView>(R.id.titleView)!!
|
||||
|
||||
fun bind(event: EnrichedEvent?) {
|
||||
if (event == null) {
|
||||
titleView.text = null
|
||||
} else if (event.root.type == EventType.MESSAGE) {
|
||||
val messageContent = event.root.content<MessageContent>()
|
||||
val roomMember = event.getMetaEvents(EventType.STATE_ROOM_MEMBER).firstOrNull()?.content<RoomMember>()
|
||||
if (messageContent == null || roomMember == null) {
|
||||
titleView.text = null
|
||||
} else {
|
||||
val text = "${roomMember.displayName} : ${messageContent.body}"
|
||||
titleView.text = text
|
||||
}
|
||||
} else {
|
||||
titleView.text = event.root.toString()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCurrentListChanged(newList: PagedList<EnrichedEvent>?) {
|
||||
callback?.onEventsListChanged(currentList, newList)
|
||||
currentList = newList
|
||||
}
|
||||
|
||||
interface Callback {
|
||||
fun onEventsListChanged(oldList: List<EnrichedEvent>?, newList: List<EnrichedEvent>?)
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,29 +1,71 @@
|
||||
package im.vector.riotredesign.features.home.room.detail
|
||||
|
||||
import android.arch.paging.PagedList
|
||||
import com.airbnb.epoxy.EpoxyAsyncUtil
|
||||
import com.airbnb.epoxy.EpoxyModel
|
||||
import com.airbnb.epoxy.paging.PagedListEpoxyController
|
||||
import com.airbnb.epoxy.EpoxyController
|
||||
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.EventType
|
||||
import im.vector.matrix.android.api.session.room.model.MessageContent
|
||||
import im.vector.matrix.android.api.session.room.model.RoomMember
|
||||
import im.vector.riotredesign.features.home.LoadingItemModel_
|
||||
|
||||
class TimelineEventController : PagedListEpoxyController<Event>(
|
||||
diffingHandler = EpoxyAsyncUtil.getAsyncBackgroundHandler()
|
||||
private const val PREFETCH_DISTANCE = 5
|
||||
|
||||
class TimelineEventController : EpoxyController(
|
||||
EpoxyAsyncUtil.getAsyncBackgroundHandler(),
|
||||
EpoxyAsyncUtil.getAsyncBackgroundHandler()
|
||||
) {
|
||||
|
||||
override fun buildItemModel(currentPosition: Int, item: Event?): EpoxyModel<*> {
|
||||
return if (item == null) {
|
||||
LoadingItemModel_().id(-currentPosition)
|
||||
} else {
|
||||
TimelineEventItem(item.toString()).id(item.eventId)
|
||||
private val pagedListCallback = object : PagedList.Callback() {
|
||||
override fun onChanged(position: Int, count: Int) {
|
||||
requestModelBuild()
|
||||
}
|
||||
|
||||
override fun onInserted(position: Int, count: Int) {
|
||||
requestModelBuild()
|
||||
}
|
||||
|
||||
override fun onRemoved(position: Int, count: Int) {
|
||||
requestModelBuild()
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
isDebugLoggingEnabled = true
|
||||
var timeline: PagedList<EnrichedEvent>? = null
|
||||
set(value) {
|
||||
field?.removeWeakCallback(pagedListCallback)
|
||||
field = value
|
||||
field?.addWeakCallback(null, pagedListCallback)
|
||||
}
|
||||
|
||||
|
||||
override fun buildModels() {
|
||||
buildModels(timeline)
|
||||
}
|
||||
|
||||
override fun onExceptionSwallowed(exception: RuntimeException) {
|
||||
throw exception
|
||||
private fun buildModels(data: List<EnrichedEvent>?) {
|
||||
if (data.isNullOrEmpty()) {
|
||||
return
|
||||
}
|
||||
data.forEachIndexed { index, enrichedEvent ->
|
||||
val item = if (enrichedEvent.root.type == EventType.MESSAGE) {
|
||||
val messageContent = enrichedEvent.root.content<MessageContent>()
|
||||
val roomMember = enrichedEvent.getMetadata<Event>(EventType.STATE_ROOM_MEMBER)?.content<RoomMember>()
|
||||
val title = "${roomMember?.displayName} : ${messageContent?.body}"
|
||||
TimelineEventItem(title = title)
|
||||
} else {
|
||||
TimelineEventItem(title = enrichedEvent.toString())
|
||||
}
|
||||
item
|
||||
.onBind { timeline?.loadAround(index) }
|
||||
.id(enrichedEvent.root.eventId)
|
||||
.addTo(this)
|
||||
}
|
||||
|
||||
val isLastEvent = data.last().getMetadata<Boolean>(EnrichedEvent.IS_LAST_EVENT) ?: false
|
||||
LoadingItemModel_()
|
||||
.id("backward_loading_item")
|
||||
.addIf(!isLastEvent, this)
|
||||
}
|
||||
|
||||
}
|
@ -5,14 +5,12 @@ import im.vector.riotredesign.R
|
||||
import im.vector.riotredesign.core.epoxy.KotlinModel
|
||||
|
||||
data class TimelineEventItem(
|
||||
val title: String,
|
||||
val listener: (() -> Unit)? = null
|
||||
val title: String
|
||||
) : KotlinModel(R.layout.item_event) {
|
||||
|
||||
val titleView by bind<TextView>(R.id.titleView)
|
||||
|
||||
override fun bind() {
|
||||
titleView.setOnClickListener { listener?.invoke() }
|
||||
titleView.text = title
|
||||
}
|
||||
}
|
@ -2,7 +2,7 @@ package im.vector.matrix.android.api.session.events.model
|
||||
|
||||
data class EnrichedEvent(val root: Event) {
|
||||
|
||||
private val metaEventsByType = HashMap<String, ArrayList<Event>>()
|
||||
val metadata = HashMap<String, Any>()
|
||||
|
||||
fun enrichWith(events: List<Event>) {
|
||||
events.forEach { enrichWith(it) }
|
||||
@ -12,24 +12,22 @@ data class EnrichedEvent(val root: Event) {
|
||||
if (event == null) {
|
||||
return
|
||||
}
|
||||
var currentEventsForType = metaEventsByType[event.type]
|
||||
if (currentEventsForType == null) {
|
||||
currentEventsForType = ArrayList()
|
||||
metaEventsByType[event.type] = currentEventsForType
|
||||
enrichWith(event.type, event)
|
||||
}
|
||||
|
||||
fun enrichWith(key: String, data: Any) {
|
||||
if (!metadata.containsKey(key)) {
|
||||
metadata[key] = data
|
||||
}
|
||||
currentEventsForType.add(event)
|
||||
}
|
||||
|
||||
fun getMetaEvents(type: String): List<Event> {
|
||||
return metaEventsByType[type] ?: emptyList()
|
||||
inline fun <reified T> getMetadata(key: String): T? {
|
||||
return metadata[key] as T?
|
||||
}
|
||||
|
||||
fun getMetaEvents(): List<Event> {
|
||||
return metaEventsByType.values.flatten()
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
return super.toString()
|
||||
companion object {
|
||||
const val IS_LAST_EVENT = "IS_LAST_EVENT"
|
||||
const val READ_RECEIPTS = "READ_RECEIPTS"
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,37 @@
|
||||
package im.vector.matrix.android.internal.session.events.interceptor
|
||||
|
||||
import com.zhuinden.monarchy.Monarchy
|
||||
import im.vector.matrix.android.api.session.events.interceptor.EnrichedEventInterceptor
|
||||
import im.vector.matrix.android.api.session.events.model.EnrichedEvent
|
||||
import im.vector.matrix.android.internal.database.model.ChunkEntity
|
||||
import im.vector.matrix.android.internal.database.model.EventEntity
|
||||
import im.vector.matrix.android.internal.database.model.EventEntityFields
|
||||
import im.vector.matrix.android.internal.database.query.findAllIncludingEvents
|
||||
import im.vector.matrix.android.internal.database.query.where
|
||||
import io.realm.Sort
|
||||
import java.util.*
|
||||
|
||||
|
||||
class IsLastEventInterceptor(val monarchy: Monarchy) : EnrichedEventInterceptor {
|
||||
|
||||
override fun canEnrich(event: EnrichedEvent): Boolean {
|
||||
return true
|
||||
}
|
||||
|
||||
override fun enrich(roomId: String, event: EnrichedEvent) {
|
||||
monarchy.doWithRealm { realm ->
|
||||
if (event.root.eventId == null) {
|
||||
return@doWithRealm
|
||||
}
|
||||
val eventEntity = EventEntity.where(realm, event.root.eventId).findFirst()
|
||||
val chunkEntity = ChunkEntity.findAllIncludingEvents(realm, Collections.singletonList(event.root.eventId)).firstOrNull()
|
||||
if (eventEntity == null || chunkEntity == null) {
|
||||
return@doWithRealm
|
||||
}
|
||||
val sortedChunkEvents = chunkEntity.events.where().sort(EventEntityFields.ORIGIN_SERVER_TS, Sort.ASCENDING).findAll()
|
||||
val isLastEvent = chunkEntity.prevToken == null && sortedChunkEvents?.indexOf(eventEntity) == 0
|
||||
event.enrichWith(EnrichedEvent.IS_LAST_EVENT, isLastEvent)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -10,6 +10,7 @@ import im.vector.matrix.android.internal.database.model.EventEntityFields
|
||||
import im.vector.matrix.android.internal.database.query.last
|
||||
import im.vector.matrix.android.internal.database.query.where
|
||||
|
||||
|
||||
class MessageEventInterceptor(val monarchy: Monarchy) : EnrichedEventInterceptor {
|
||||
|
||||
override fun canEnrich(event: EnrichedEvent): Boolean {
|
||||
|
@ -11,6 +11,7 @@ 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.EventEntityFields
|
||||
import im.vector.matrix.android.internal.database.query.where
|
||||
import im.vector.matrix.android.internal.session.events.interceptor.IsLastEventInterceptor
|
||||
import im.vector.matrix.android.internal.session.events.interceptor.MessageEventInterceptor
|
||||
import io.realm.Sort
|
||||
|
||||
@ -23,6 +24,7 @@ class DefaultTimelineHolder(private val roomId: String,
|
||||
|
||||
init {
|
||||
eventInterceptors.add(MessageEventInterceptor(monarchy))
|
||||
eventInterceptors.add(IsLastEventInterceptor(monarchy))
|
||||
}
|
||||
|
||||
override fun liveTimeline(): LiveData<PagedList<EnrichedEvent>> {
|
||||
|
Loading…
Reference in New Issue
Block a user