mirror of
https://github.com/element-hq/element-android
synced 2024-11-23 09:55:40 +03:00
Timeline : send read-receipt when scrolling. Still need to handle read marker.
This commit is contained in:
parent
bfaab52b41
commit
6113bba703
17 changed files with 198 additions and 49 deletions
|
@ -16,10 +16,11 @@
|
||||||
|
|
||||||
package im.vector.riotredesign.core.epoxy
|
package im.vector.riotredesign.core.epoxy
|
||||||
|
|
||||||
|
import android.view.View
|
||||||
import androidx.annotation.IdRes
|
import androidx.annotation.IdRes
|
||||||
import androidx.annotation.LayoutRes
|
import androidx.annotation.LayoutRes
|
||||||
import android.view.View
|
|
||||||
import com.airbnb.epoxy.EpoxyModel
|
import com.airbnb.epoxy.EpoxyModel
|
||||||
|
import com.airbnb.epoxy.OnModelVisibilityStateChangedListener
|
||||||
import kotlin.properties.ReadOnlyProperty
|
import kotlin.properties.ReadOnlyProperty
|
||||||
import kotlin.reflect.KProperty
|
import kotlin.reflect.KProperty
|
||||||
|
|
||||||
|
@ -29,6 +30,7 @@ abstract class KotlinModel(
|
||||||
|
|
||||||
private var view: View? = null
|
private var view: View? = null
|
||||||
private var onBindCallback: (() -> Unit)? = null
|
private var onBindCallback: (() -> Unit)? = null
|
||||||
|
private var onModelVisibilityStateChangedListener: OnModelVisibilityStateChangedListener<KotlinModel, View>? = null
|
||||||
|
|
||||||
abstract fun bind()
|
abstract fun bind()
|
||||||
|
|
||||||
|
@ -47,6 +49,16 @@ abstract class KotlinModel(
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onVisibilityStateChanged(visibilityState: Int, view: View) {
|
||||||
|
onModelVisibilityStateChangedListener?.onVisibilityStateChanged(this, view, visibilityState)
|
||||||
|
super.onVisibilityStateChanged(visibilityState, view)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setOnVisibilityStateChanged(listener: OnModelVisibilityStateChangedListener<KotlinModel, View>): KotlinModel {
|
||||||
|
this.onModelVisibilityStateChangedListener = listener
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
override fun getDefaultLayout() = layoutRes
|
override fun getDefaultLayout() = layoutRes
|
||||||
|
|
||||||
protected fun <V : View> bind(@IdRes id: Int) = object : ReadOnlyProperty<KotlinModel, V> {
|
protected fun <V : View> bind(@IdRes id: Int) = object : ReadOnlyProperty<KotlinModel, V> {
|
||||||
|
@ -56,7 +68,7 @@ abstract class KotlinModel(
|
||||||
// be optimized with a map
|
// be optimized with a map
|
||||||
@Suppress("UNCHECKED_CAST")
|
@Suppress("UNCHECKED_CAST")
|
||||||
return view?.findViewById(id) as V?
|
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.")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -16,9 +16,12 @@
|
||||||
|
|
||||||
package im.vector.riotredesign.features.home.room.detail
|
package im.vector.riotredesign.features.home.room.detail
|
||||||
|
|
||||||
|
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
|
||||||
|
|
||||||
sealed class RoomDetailActions {
|
sealed class RoomDetailActions {
|
||||||
|
|
||||||
data class SendMessage(val text: String) : RoomDetailActions()
|
data class SendMessage(val text: String) : RoomDetailActions()
|
||||||
object IsDisplayed : RoomDetailActions()
|
object IsDisplayed : RoomDetailActions()
|
||||||
|
data class EventDisplayed(val event: TimelineEvent, val index: Int) : RoomDetailActions()
|
||||||
|
|
||||||
}
|
}
|
|
@ -23,9 +23,11 @@ import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import com.airbnb.epoxy.EpoxyVisibilityTracker
|
||||||
import com.airbnb.mvrx.Success
|
import com.airbnb.mvrx.Success
|
||||||
import com.airbnb.mvrx.args
|
import com.airbnb.mvrx.args
|
||||||
import com.airbnb.mvrx.fragmentViewModel
|
import com.airbnb.mvrx.fragmentViewModel
|
||||||
|
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
|
||||||
import im.vector.riotredesign.R
|
import im.vector.riotredesign.R
|
||||||
import im.vector.riotredesign.core.platform.RiotFragment
|
import im.vector.riotredesign.core.platform.RiotFragment
|
||||||
import im.vector.riotredesign.core.platform.ToolbarConfigurable
|
import im.vector.riotredesign.core.platform.ToolbarConfigurable
|
||||||
|
@ -75,7 +77,7 @@ class RoomDetailFragment : RiotFragment(), TimelineEventController.Callback {
|
||||||
|
|
||||||
override fun onResume() {
|
override fun onResume() {
|
||||||
super.onResume()
|
super.onResume()
|
||||||
roomDetailViewModel.accept(RoomDetailActions.IsDisplayed)
|
roomDetailViewModel.process(RoomDetailActions.IsDisplayed)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setupToolbar() {
|
private fun setupToolbar() {
|
||||||
|
@ -86,6 +88,8 @@ class RoomDetailFragment : RiotFragment(), TimelineEventController.Callback {
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setupRecyclerView() {
|
private fun setupRecyclerView() {
|
||||||
|
val epoxyVisibilityTracker = EpoxyVisibilityTracker()
|
||||||
|
epoxyVisibilityTracker.attach(recyclerView)
|
||||||
val layoutManager = LinearLayoutManager(context, RecyclerView.VERTICAL, true)
|
val layoutManager = LinearLayoutManager(context, RecyclerView.VERTICAL, true)
|
||||||
scrollOnNewMessageCallback = ScrollOnNewMessageCallback(layoutManager)
|
scrollOnNewMessageCallback = ScrollOnNewMessageCallback(layoutManager)
|
||||||
recyclerView.layoutManager = layoutManager
|
recyclerView.layoutManager = layoutManager
|
||||||
|
@ -100,7 +104,7 @@ class RoomDetailFragment : RiotFragment(), TimelineEventController.Callback {
|
||||||
val textMessage = composerEditText.text.toString()
|
val textMessage = composerEditText.text.toString()
|
||||||
if (textMessage.isNotBlank()) {
|
if (textMessage.isNotBlank()) {
|
||||||
composerEditText.text = null
|
composerEditText.text = null
|
||||||
roomDetailViewModel.accept(RoomDetailActions.SendMessage(textMessage))
|
roomDetailViewModel.process(RoomDetailActions.SendMessage(textMessage))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -143,4 +147,8 @@ class RoomDetailFragment : RiotFragment(), TimelineEventController.Callback {
|
||||||
homePermalinkHandler.launch(url)
|
homePermalinkHandler.launch(url)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onEventVisible(event: TimelineEvent, index: Int) {
|
||||||
|
roomDetailViewModel.process(RoomDetailActions.EventDisplayed(event, index))
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,6 +18,7 @@ package im.vector.riotredesign.features.home.room.detail
|
||||||
|
|
||||||
import com.airbnb.mvrx.MvRxViewModelFactory
|
import com.airbnb.mvrx.MvRxViewModelFactory
|
||||||
import com.airbnb.mvrx.ViewModelContext
|
import com.airbnb.mvrx.ViewModelContext
|
||||||
|
import com.jakewharton.rxrelay2.BehaviorRelay
|
||||||
import im.vector.matrix.android.api.Matrix
|
import im.vector.matrix.android.api.Matrix
|
||||||
import im.vector.matrix.android.api.MatrixCallback
|
import im.vector.matrix.android.api.MatrixCallback
|
||||||
import im.vector.matrix.android.api.session.Session
|
import im.vector.matrix.android.api.session.Session
|
||||||
|
@ -25,7 +26,9 @@ import im.vector.matrix.android.api.session.events.model.Event
|
||||||
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.features.home.room.VisibleRoomHolder
|
import im.vector.riotredesign.features.home.room.VisibleRoomHolder
|
||||||
|
import io.reactivex.rxkotlin.subscribeBy
|
||||||
import org.koin.android.ext.android.get
|
import org.koin.android.ext.android.get
|
||||||
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
class RoomDetailViewModel(initialState: RoomDetailViewState,
|
class RoomDetailViewModel(initialState: RoomDetailViewState,
|
||||||
private val session: Session,
|
private val session: Session,
|
||||||
|
@ -36,6 +39,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>()
|
||||||
|
|
||||||
companion object : MvRxViewModelFactory<RoomDetailViewModel, RoomDetailViewState> {
|
companion object : MvRxViewModelFactory<RoomDetailViewModel, RoomDetailViewState> {
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
|
@ -49,14 +54,15 @@ class RoomDetailViewModel(initialState: RoomDetailViewState,
|
||||||
init {
|
init {
|
||||||
observeRoomSummary()
|
observeRoomSummary()
|
||||||
observeTimeline()
|
observeTimeline()
|
||||||
|
observeDisplayedEvents()
|
||||||
room.loadRoomMembersIfNeeded()
|
room.loadRoomMembersIfNeeded()
|
||||||
room.markLatestAsRead(callback = object : MatrixCallback<Void> {})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun accept(action: RoomDetailActions) {
|
fun process(action: RoomDetailActions) {
|
||||||
when (action) {
|
when (action) {
|
||||||
is RoomDetailActions.SendMessage -> handleSendMessage(action)
|
is RoomDetailActions.SendMessage -> handleSendMessage(action)
|
||||||
is RoomDetailActions.IsDisplayed -> visibleRoomHolder.setVisibleRoom(roomId)
|
is RoomDetailActions.IsDisplayed -> handleIsDisplayed()
|
||||||
|
is RoomDetailActions.EventDisplayed -> handleEventDisplayed(action)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -66,6 +72,29 @@ class RoomDetailViewModel(initialState: RoomDetailViewState,
|
||||||
room.sendTextMessage(action.text, callback = object : MatrixCallback<Event> {})
|
room.sendTextMessage(action.text, callback = object : MatrixCallback<Event> {})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun handleEventDisplayed(action: RoomDetailActions.EventDisplayed) {
|
||||||
|
displayedEventsObservable.accept(action)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun handleIsDisplayed() {
|
||||||
|
visibleRoomHolder.setVisibleRoom(roomId)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun observeDisplayedEvents() {
|
||||||
|
// We are buffering scroll events for one second
|
||||||
|
// and keep the most recent one to set the read receipt on.
|
||||||
|
displayedEventsObservable.hide()
|
||||||
|
.buffer(1, TimeUnit.SECONDS)
|
||||||
|
.filter { it.isNotEmpty() }
|
||||||
|
.subscribeBy { actions ->
|
||||||
|
val mostRecentEvent = actions.minBy { it.index }
|
||||||
|
mostRecentEvent?.event?.root?.eventId?.let { eventId ->
|
||||||
|
room.setReadReceipt(eventId, callback = object : MatrixCallback<Void> {})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.disposeOnClear()
|
||||||
|
}
|
||||||
|
|
||||||
private fun observeRoomSummary() {
|
private fun observeRoomSummary() {
|
||||||
room.rx().liveRoomSummary()
|
room.rx().liveRoomSummary()
|
||||||
.execute { async ->
|
.execute { async ->
|
||||||
|
|
|
@ -16,12 +16,16 @@
|
||||||
|
|
||||||
package im.vector.riotredesign.features.home.room.detail.timeline
|
package im.vector.riotredesign.features.home.room.detail.timeline
|
||||||
|
|
||||||
|
import android.view.View
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import com.airbnb.epoxy.EpoxyAsyncUtil
|
import com.airbnb.epoxy.EpoxyAsyncUtil
|
||||||
import com.airbnb.epoxy.EpoxyModel
|
import com.airbnb.epoxy.EpoxyModel
|
||||||
|
import com.airbnb.epoxy.OnModelVisibilityStateChangedListener
|
||||||
|
import com.airbnb.epoxy.VisibilityState
|
||||||
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.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.TimelineEvent
|
||||||
|
import im.vector.riotredesign.core.epoxy.KotlinModel
|
||||||
import im.vector.riotredesign.core.extensions.localDateTime
|
import im.vector.riotredesign.core.extensions.localDateTime
|
||||||
import im.vector.riotredesign.features.home.LoadingItemModel_
|
import im.vector.riotredesign.features.home.LoadingItemModel_
|
||||||
import im.vector.riotredesign.features.home.room.detail.timeline.helper.TimelineMediaSizeProvider
|
import im.vector.riotredesign.features.home.room.detail.timeline.helper.TimelineMediaSizeProvider
|
||||||
|
@ -74,6 +78,11 @@ class TimelineEventController(private val roomId: String,
|
||||||
|
|
||||||
timelineItemFactory.create(event, nextEvent, callback)?.also {
|
timelineItemFactory.create(event, nextEvent, callback)?.also {
|
||||||
it.id(event.localId)
|
it.id(event.localId)
|
||||||
|
it.setOnVisibilityStateChanged(OnModelVisibilityStateChangedListener<KotlinModel, View> { model, view, visibilityState ->
|
||||||
|
if (visibilityState == VisibilityState.VISIBLE) {
|
||||||
|
callback?.onEventVisible(event, currentPosition)
|
||||||
|
}
|
||||||
|
})
|
||||||
epoxyModels.add(it)
|
epoxyModels.add(it)
|
||||||
}
|
}
|
||||||
if (addDaySeparator) {
|
if (addDaySeparator) {
|
||||||
|
@ -98,6 +107,7 @@ class TimelineEventController(private val roomId: String,
|
||||||
|
|
||||||
|
|
||||||
interface Callback {
|
interface Callback {
|
||||||
|
fun onEventVisible(event: TimelineEvent, index: Int)
|
||||||
fun onUrlClicked(url: String)
|
fun onUrlClicked(url: String)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -16,9 +16,9 @@
|
||||||
|
|
||||||
package im.vector.riotredesign.features.home.room.detail.timeline
|
package im.vector.riotredesign.features.home.room.detail.timeline
|
||||||
|
|
||||||
import com.airbnb.epoxy.EpoxyModel
|
|
||||||
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.TimelineEvent
|
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
|
||||||
|
import im.vector.riotredesign.core.epoxy.KotlinModel
|
||||||
|
|
||||||
class TimelineItemFactory(private val messageItemFactory: MessageItemFactory,
|
class TimelineItemFactory(private val messageItemFactory: MessageItemFactory,
|
||||||
private val roomNameItemFactory: RoomNameItemFactory,
|
private val roomNameItemFactory: RoomNameItemFactory,
|
||||||
|
@ -28,14 +28,14 @@ class TimelineItemFactory(private val messageItemFactory: MessageItemFactory,
|
||||||
|
|
||||||
fun create(event: TimelineEvent,
|
fun create(event: TimelineEvent,
|
||||||
nextEvent: TimelineEvent?,
|
nextEvent: TimelineEvent?,
|
||||||
callback: TimelineEventController.Callback?): EpoxyModel<*>? {
|
callback: TimelineEventController.Callback?): KotlinModel? {
|
||||||
|
|
||||||
return when (event.root.type) {
|
return when (event.root.type) {
|
||||||
EventType.MESSAGE -> messageItemFactory.create(event, nextEvent, callback)
|
EventType.MESSAGE -> messageItemFactory.create(event, nextEvent, callback)
|
||||||
EventType.STATE_ROOM_NAME -> roomNameItemFactory.create(event)
|
EventType.STATE_ROOM_NAME -> roomNameItemFactory.create(event)
|
||||||
EventType.STATE_ROOM_TOPIC -> roomTopicItemFactory.create(event)
|
EventType.STATE_ROOM_TOPIC -> roomTopicItemFactory.create(event)
|
||||||
EventType.STATE_ROOM_MEMBER -> roomMemberItemFactory.create(event)
|
EventType.STATE_ROOM_MEMBER -> roomMemberItemFactory.create(event)
|
||||||
else -> defaultItemFactory.create(event)
|
else -> defaultItemFactory.create(event)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -18,14 +18,24 @@ package im.vector.matrix.android.api.session.room.read
|
||||||
|
|
||||||
import im.vector.matrix.android.api.MatrixCallback
|
import im.vector.matrix.android.api.MatrixCallback
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This interface defines methods to handle read receipts and read marker in a room. It's implemented at the room level.
|
||||||
|
*/
|
||||||
interface ReadService {
|
interface ReadService {
|
||||||
|
|
||||||
fun markLatestAsRead(callback: MatrixCallback<Void>)
|
/**
|
||||||
|
* Force the read marker to be set on the latest event.
|
||||||
|
*/
|
||||||
fun markAllAsRead(callback: MatrixCallback<Void>)
|
fun markAllAsRead(callback: MatrixCallback<Void>)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the read receipt on the event with provided eventId.
|
||||||
|
*/
|
||||||
fun setReadReceipt(eventId: String, callback: MatrixCallback<Void>)
|
fun setReadReceipt(eventId: String, callback: MatrixCallback<Void>)
|
||||||
|
|
||||||
fun setReadMarkers(fullyReadEventId: String, readReceiptEventId: String?, callback: MatrixCallback<Void>)
|
/**
|
||||||
|
* Set the read marker on the event with provided eventId.
|
||||||
|
*/
|
||||||
|
fun setReadMarker(fullyReadEventId: String, callback: MatrixCallback<Void>)
|
||||||
|
|
||||||
}
|
}
|
|
@ -18,11 +18,11 @@ package im.vector.matrix.android.internal.di
|
||||||
|
|
||||||
import im.vector.matrix.android.internal.network.AccessTokenInterceptor
|
import im.vector.matrix.android.internal.network.AccessTokenInterceptor
|
||||||
import im.vector.matrix.android.internal.network.NetworkConnectivityChecker
|
import im.vector.matrix.android.internal.network.NetworkConnectivityChecker
|
||||||
|
import im.vector.matrix.android.internal.network.UnitConverterFactory
|
||||||
import okhttp3.OkHttpClient
|
import okhttp3.OkHttpClient
|
||||||
import okhttp3.logging.HttpLoggingInterceptor
|
import okhttp3.logging.HttpLoggingInterceptor
|
||||||
import okreplay.OkReplayInterceptor
|
import okreplay.OkReplayInterceptor
|
||||||
import org.koin.dsl.module.module
|
import org.koin.dsl.module.module
|
||||||
import retrofit2.Converter
|
|
||||||
import retrofit2.Retrofit
|
import retrofit2.Retrofit
|
||||||
import retrofit2.converter.moshi.MoshiConverterFactory
|
import retrofit2.converter.moshi.MoshiConverterFactory
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
@ -62,10 +62,6 @@ class NetworkModule {
|
||||||
MoshiProvider.providesMoshi()
|
MoshiProvider.providesMoshi()
|
||||||
}
|
}
|
||||||
|
|
||||||
single {
|
|
||||||
MoshiConverterFactory.create(get()) as Converter.Factory
|
|
||||||
}
|
|
||||||
|
|
||||||
single {
|
single {
|
||||||
NetworkConnectivityChecker(get())
|
NetworkConnectivityChecker(get())
|
||||||
}
|
}
|
||||||
|
@ -73,7 +69,8 @@ class NetworkModule {
|
||||||
factory {
|
factory {
|
||||||
Retrofit.Builder()
|
Retrofit.Builder()
|
||||||
.client(get())
|
.client(get())
|
||||||
.addConverterFactory(get())
|
.addConverterFactory(UnitConverterFactory)
|
||||||
|
.addConverterFactory(MoshiConverterFactory.create(get()))
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,35 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2019 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.matrix.android.internal.network
|
||||||
|
|
||||||
|
import okhttp3.ResponseBody
|
||||||
|
import retrofit2.Converter
|
||||||
|
import retrofit2.Retrofit
|
||||||
|
import java.lang.reflect.Type
|
||||||
|
|
||||||
|
object UnitConverterFactory : Converter.Factory() {
|
||||||
|
override fun responseBodyConverter(type: Type, annotations: Array<out Annotation>,
|
||||||
|
retrofit: Retrofit): Converter<ResponseBody, *>? {
|
||||||
|
return if (type == Unit::class.java) UnitConverter else null
|
||||||
|
}
|
||||||
|
|
||||||
|
private object UnitConverter : Converter<ResponseBody, Unit> {
|
||||||
|
override fun convert(value: ResponseBody) {
|
||||||
|
value.close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -47,6 +47,10 @@ internal class SessionModule(private val sessionParams: SessionParams) {
|
||||||
sessionParams
|
sessionParams
|
||||||
}
|
}
|
||||||
|
|
||||||
|
scope(DefaultSession.SCOPE) {
|
||||||
|
sessionParams.credentials
|
||||||
|
}
|
||||||
|
|
||||||
scope(DefaultSession.SCOPE) {
|
scope(DefaultSession.SCOPE) {
|
||||||
val context = get<Context>()
|
val context = get<Context>()
|
||||||
val childPath = sessionParams.credentials.userId.md5()
|
val childPath = sessionParams.credentials.userId.md5()
|
||||||
|
|
|
@ -26,13 +26,13 @@ import im.vector.matrix.android.internal.database.model.RoomEntity
|
||||||
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.util.fetchManaged
|
import im.vector.matrix.android.internal.util.fetchCopied
|
||||||
|
|
||||||
internal class DefaultRoomService(private val monarchy: Monarchy,
|
internal class DefaultRoomService(private val monarchy: Monarchy,
|
||||||
private val roomFactory: RoomFactory) : RoomService {
|
private val roomFactory: RoomFactory) : RoomService {
|
||||||
|
|
||||||
override fun getRoom(roomId: String): Room? {
|
override fun getRoom(roomId: String): Room? {
|
||||||
monarchy.fetchManaged { RoomEntity.where(it, roomId).findFirst() } ?: return null
|
monarchy.fetchCopied { RoomEntity.where(it, roomId).findFirst() } ?: return null
|
||||||
return roomFactory.instantiate(roomId)
|
return roomFactory.instantiate(roomId)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -112,7 +112,7 @@ internal interface RoomAPI {
|
||||||
* @param markers the read markers
|
* @param markers the read markers
|
||||||
*/
|
*/
|
||||||
@POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/read_markers")
|
@POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/read_markers")
|
||||||
fun sendReadMarker(@Path("roomId") roomId: String, @Body markers: Map<String, String>): Call<Void>
|
fun sendReadMarker(@Path("roomId") roomId: String, @Body markers: Map<String, String>): Call<Unit>
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
|
@ -17,6 +17,7 @@
|
||||||
package im.vector.matrix.android.internal.session.room
|
package im.vector.matrix.android.internal.session.room
|
||||||
|
|
||||||
import com.zhuinden.monarchy.Monarchy
|
import com.zhuinden.monarchy.Monarchy
|
||||||
|
import im.vector.matrix.android.api.auth.data.Credentials
|
||||||
import im.vector.matrix.android.api.session.room.Room
|
import im.vector.matrix.android.api.session.room.Room
|
||||||
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.session.room.members.RoomMemberExtractor
|
import im.vector.matrix.android.internal.session.room.members.RoomMemberExtractor
|
||||||
|
@ -34,6 +35,7 @@ import java.util.concurrent.Executors
|
||||||
|
|
||||||
internal class RoomFactory(private val loadRoomMembersTask: LoadRoomMembersTask,
|
internal class RoomFactory(private val loadRoomMembersTask: LoadRoomMembersTask,
|
||||||
private val monarchy: Monarchy,
|
private val monarchy: Monarchy,
|
||||||
|
private val credentials: Credentials,
|
||||||
private val paginationTask: PaginationTask,
|
private val paginationTask: PaginationTask,
|
||||||
private val contextOfEventTask: GetContextOfEventTask,
|
private val contextOfEventTask: GetContextOfEventTask,
|
||||||
private val setReadMarkersTask: SetReadMarkersTask,
|
private val setReadMarkersTask: SetReadMarkersTask,
|
||||||
|
|
|
@ -16,7 +16,6 @@
|
||||||
|
|
||||||
package im.vector.matrix.android.internal.session.room
|
package im.vector.matrix.android.internal.session.room
|
||||||
|
|
||||||
import im.vector.matrix.android.api.auth.data.SessionParams
|
|
||||||
import im.vector.matrix.android.internal.session.DefaultSession
|
import im.vector.matrix.android.internal.session.DefaultSession
|
||||||
import im.vector.matrix.android.internal.session.room.members.DefaultLoadRoomMembersTask
|
import im.vector.matrix.android.internal.session.room.members.DefaultLoadRoomMembersTask
|
||||||
import im.vector.matrix.android.internal.session.room.members.LoadRoomMembersTask
|
import im.vector.matrix.android.internal.session.room.members.LoadRoomMembersTask
|
||||||
|
@ -58,16 +57,15 @@ class RoomModule {
|
||||||
}
|
}
|
||||||
|
|
||||||
scope(DefaultSession.SCOPE) {
|
scope(DefaultSession.SCOPE) {
|
||||||
DefaultSetReadMarkersTask(get()) as SetReadMarkersTask
|
DefaultSetReadMarkersTask(get(), get(),get()) as SetReadMarkersTask
|
||||||
}
|
}
|
||||||
|
|
||||||
scope(DefaultSession.SCOPE) {
|
scope(DefaultSession.SCOPE) {
|
||||||
val sessionParams = get<SessionParams>()
|
EventFactory(get())
|
||||||
EventFactory(sessionParams.credentials)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
scope(DefaultSession.SCOPE) {
|
scope(DefaultSession.SCOPE) {
|
||||||
RoomFactory(get(), get(), get(), get(), get(), get(), get())
|
RoomFactory(get(), get(), get(), get(), get(), get(), get(), get())
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,22 +23,16 @@ import im.vector.matrix.android.internal.database.model.EventEntity
|
||||||
import im.vector.matrix.android.internal.database.query.latestEvent
|
import im.vector.matrix.android.internal.database.query.latestEvent
|
||||||
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
|
||||||
import im.vector.matrix.android.internal.util.fetchManaged
|
import im.vector.matrix.android.internal.util.fetchCopied
|
||||||
|
|
||||||
internal class DefaultReadService(private val roomId: String,
|
internal class DefaultReadService(private val roomId: String,
|
||||||
private val monarchy: Monarchy,
|
private val monarchy: Monarchy,
|
||||||
private val setReadMarkersTask: SetReadMarkersTask,
|
private val setReadMarkersTask: SetReadMarkersTask,
|
||||||
private val taskExecutor: TaskExecutor) : ReadService {
|
private val taskExecutor: TaskExecutor) : ReadService {
|
||||||
|
|
||||||
override fun markLatestAsRead(callback: MatrixCallback<Void>) {
|
|
||||||
val lastEvent = getLatestEvent()
|
|
||||||
val params = SetReadMarkersTask.Params(roomId, fullyReadEventId = null, readReceiptEventId = lastEvent?.eventId)
|
|
||||||
setReadMarkersTask.configureWith(params).executeBy(taskExecutor)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun markAllAsRead(callback: MatrixCallback<Void>) {
|
override fun markAllAsRead(callback: MatrixCallback<Void>) {
|
||||||
val lastEvent = getLatestEvent()
|
val latestEvent = getLatestEvent()
|
||||||
val params = SetReadMarkersTask.Params(roomId, fullyReadEventId = lastEvent?.eventId, readReceiptEventId = null)
|
val params = SetReadMarkersTask.Params(roomId, fullyReadEventId = latestEvent?.eventId, readReceiptEventId = latestEvent?.eventId)
|
||||||
setReadMarkersTask.configureWith(params).executeBy(taskExecutor)
|
setReadMarkersTask.configureWith(params).executeBy(taskExecutor)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -47,13 +41,14 @@ internal class DefaultReadService(private val roomId: String,
|
||||||
setReadMarkersTask.configureWith(params).executeBy(taskExecutor)
|
setReadMarkersTask.configureWith(params).executeBy(taskExecutor)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun setReadMarkers(fullyReadEventId: String, readReceiptEventId: String?, callback: MatrixCallback<Void>) {
|
override fun setReadMarker(fullyReadEventId: String, callback: MatrixCallback<Void>) {
|
||||||
val params = SetReadMarkersTask.Params(roomId, fullyReadEventId = fullyReadEventId, readReceiptEventId = readReceiptEventId)
|
val params = SetReadMarkersTask.Params(roomId, fullyReadEventId = fullyReadEventId, readReceiptEventId = null)
|
||||||
setReadMarkersTask.configureWith(params).executeBy(taskExecutor)
|
setReadMarkersTask.configureWith(params).executeBy(taskExecutor)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getLatestEvent(): EventEntity? {
|
private fun getLatestEvent(): EventEntity? {
|
||||||
return monarchy.fetchManaged { EventEntity.latestEvent(it, roomId) }
|
return monarchy.fetchCopied { EventEntity.latestEvent(it, roomId) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
|
@ -17,11 +17,22 @@
|
||||||
package im.vector.matrix.android.internal.session.room.read
|
package im.vector.matrix.android.internal.session.room.read
|
||||||
|
|
||||||
import arrow.core.Try
|
import arrow.core.Try
|
||||||
|
import com.zhuinden.monarchy.Monarchy
|
||||||
|
import im.vector.matrix.android.api.auth.data.Credentials
|
||||||
|
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.ReadReceiptEntity
|
||||||
|
import im.vector.matrix.android.internal.database.model.RoomSummaryEntity
|
||||||
|
import im.vector.matrix.android.internal.database.query.find
|
||||||
|
import im.vector.matrix.android.internal.database.query.findLastLiveChunkFromRoom
|
||||||
|
import im.vector.matrix.android.internal.database.query.latestEvent
|
||||||
|
import im.vector.matrix.android.internal.database.query.where
|
||||||
import im.vector.matrix.android.internal.network.executeRequest
|
import im.vector.matrix.android.internal.network.executeRequest
|
||||||
import im.vector.matrix.android.internal.session.room.RoomAPI
|
import im.vector.matrix.android.internal.session.room.RoomAPI
|
||||||
import im.vector.matrix.android.internal.task.Task
|
import im.vector.matrix.android.internal.task.Task
|
||||||
|
import im.vector.matrix.android.internal.util.tryTransactionAsync
|
||||||
|
|
||||||
internal interface SetReadMarkersTask : Task<SetReadMarkersTask.Params, Void> {
|
internal interface SetReadMarkersTask : Task<SetReadMarkersTask.Params, Unit> {
|
||||||
|
|
||||||
data class Params(
|
data class Params(
|
||||||
val roomId: String,
|
val roomId: String,
|
||||||
|
@ -33,19 +44,54 @@ internal interface SetReadMarkersTask : Task<SetReadMarkersTask.Params, Void> {
|
||||||
private const val READ_MARKER = "m.fully_read"
|
private const val READ_MARKER = "m.fully_read"
|
||||||
private const val READ_RECEIPT = "m.read"
|
private const val READ_RECEIPT = "m.read"
|
||||||
|
|
||||||
internal class DefaultSetReadMarkersTask(private val roomAPI: RoomAPI
|
internal class DefaultSetReadMarkersTask(private val roomAPI: RoomAPI,
|
||||||
|
private val credentials: Credentials,
|
||||||
|
private val monarchy: Monarchy
|
||||||
) : SetReadMarkersTask {
|
) : SetReadMarkersTask {
|
||||||
|
|
||||||
override fun execute(params: SetReadMarkersTask.Params): Try<Void> {
|
override fun execute(params: SetReadMarkersTask.Params): Try<Unit> {
|
||||||
val markers = HashMap<String, String>()
|
val markers = HashMap<String, String>()
|
||||||
if (params.fullyReadEventId?.isNotEmpty() == true) {
|
if (params.fullyReadEventId?.isNotEmpty() == true) {
|
||||||
markers[READ_MARKER] = params.fullyReadEventId
|
markers[READ_MARKER] = params.fullyReadEventId
|
||||||
}
|
}
|
||||||
if (params.readReceiptEventId?.isNotEmpty() == true) {
|
if (params.readReceiptEventId?.isNotEmpty() == true && !isEventRead(params.roomId, params.readReceiptEventId)) {
|
||||||
|
updateNotificationCountIfNecessary(params.roomId, params.readReceiptEventId)
|
||||||
markers[READ_RECEIPT] = params.readReceiptEventId
|
markers[READ_RECEIPT] = params.readReceiptEventId
|
||||||
}
|
}
|
||||||
return executeRequest {
|
return if (markers.isEmpty()) {
|
||||||
apiCall = roomAPI.sendReadMarker(params.roomId, markers)
|
Try.just(Unit)
|
||||||
|
} else {
|
||||||
|
executeRequest {
|
||||||
|
apiCall = roomAPI.sendReadMarker(params.roomId, markers)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun updateNotificationCountIfNecessary(roomId: String, eventId: String) {
|
||||||
|
monarchy.tryTransactionAsync { realm ->
|
||||||
|
val isLatestReceived = EventEntity.latestEvent(realm, eventId)?.eventId == eventId
|
||||||
|
if (isLatestReceived) {
|
||||||
|
val roomSummary = RoomSummaryEntity.where(realm, roomId).findFirst()
|
||||||
|
?: return@tryTransactionAsync
|
||||||
|
roomSummary.notificationCount = 0
|
||||||
|
roomSummary.highlightCount = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun isEventRead(roomId: String, eventId: String): Boolean {
|
||||||
|
var isEventRead = false
|
||||||
|
monarchy.doWithRealm {
|
||||||
|
val readReceipt = ReadReceiptEntity.where(it, roomId, credentials.userId).findFirst()
|
||||||
|
?: return@doWithRealm
|
||||||
|
val liveChunk = ChunkEntity.findLastLiveChunkFromRoom(it, roomId)
|
||||||
|
?: return@doWithRealm
|
||||||
|
val readReceiptIndex = liveChunk.events.find(readReceipt.eventId)?.displayIndex
|
||||||
|
?: -1
|
||||||
|
val eventToCheckIndex = liveChunk.events.find(eventId)?.displayIndex ?: -1
|
||||||
|
isEventRead = eventToCheckIndex >= readReceiptIndex
|
||||||
|
}
|
||||||
|
return isEventRead
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -34,10 +34,10 @@ internal fun Monarchy.tryTransactionAsync(transaction: (realm: Realm) -> Unit):
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun <T : RealmModel> Monarchy.fetchManaged(query: (Realm) -> T?): T? {
|
fun <T : RealmModel> Monarchy.fetchCopied(query: (Realm) -> T?): T? {
|
||||||
val ref = AtomicReference<T>()
|
val ref = AtomicReference<T>()
|
||||||
doWithRealm { realm ->
|
doWithRealm { realm ->
|
||||||
val result = query.invoke(realm)
|
val result = query.invoke(realm)?.let { realm.copyFromRealm(it) }
|
||||||
ref.set(result)
|
ref.set(result)
|
||||||
}
|
}
|
||||||
return ref.get()
|
return ref.get()
|
||||||
|
|
Loading…
Reference in a new issue