mirror of
https://github.com/element-hq/element-android
synced 2024-11-28 13:38:49 +03:00
Timeline: handle failure when navigating to an unknown event (+ clean some files)
This commit is contained in:
parent
7bb8cb0682
commit
a6afd2e904
13 changed files with 102 additions and 29 deletions
|
@ -66,7 +66,7 @@ internal class TimelineTest : InstrumentedTest {
|
|||
// val latch = CountDownLatch(2)
|
||||
// var timelineEvents: List<TimelineEvent> = emptyList()
|
||||
// timeline.listener = object : Timeline.Listener {
|
||||
// override fun onUpdated(snapshot: List<TimelineEvent>) {
|
||||
// override fun onTimelineUpdated(snapshot: List<TimelineEvent>) {
|
||||
// if (snapshot.isNotEmpty()) {
|
||||
// if (initialLoad == 0) {
|
||||
// initialLoad = snapshot.size
|
||||
|
|
|
@ -65,7 +65,7 @@ interface Timeline {
|
|||
|
||||
/**
|
||||
* This is the main method to enrich the timeline with new data.
|
||||
* It will call the onUpdated method from [Listener] when the data will be processed.
|
||||
* It will call the onTimelineUpdated method from [Listener] when the data will be processed.
|
||||
* It also ensures only one pagination by direction is launched at a time, so you can safely call this multiple time in a row.
|
||||
*/
|
||||
fun paginate(direction: Direction, count: Int)
|
||||
|
@ -106,7 +106,12 @@ interface Timeline {
|
|||
* Call when the timeline has been updated through pagination or sync.
|
||||
* @param snapshot the most up to date snapshot
|
||||
*/
|
||||
fun onUpdated(snapshot: List<TimelineEvent>)
|
||||
fun onTimelineUpdated(snapshot: List<TimelineEvent>)
|
||||
|
||||
/**
|
||||
* Called whenever an error we can't recover from occurred
|
||||
*/
|
||||
fun onTimelineFailure(throwable: Throwable)
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -21,12 +21,7 @@ import im.vector.matrix.android.api.session.events.model.EventType
|
|||
import im.vector.matrix.android.api.session.room.send.SendState
|
||||
import im.vector.matrix.android.internal.database.mapper.asDomain
|
||||
import im.vector.matrix.android.internal.database.mapper.toEntity
|
||||
import im.vector.matrix.android.internal.database.model.ChunkEntity
|
||||
import im.vector.matrix.android.internal.database.model.EventAnnotationsSummaryEntity
|
||||
import im.vector.matrix.android.internal.database.model.ReadReceiptEntity
|
||||
import im.vector.matrix.android.internal.database.model.ReadReceiptsSummaryEntity
|
||||
import im.vector.matrix.android.internal.database.model.TimelineEventEntity
|
||||
import im.vector.matrix.android.internal.database.model.TimelineEventEntityFields
|
||||
import im.vector.matrix.android.internal.database.model.*
|
||||
import im.vector.matrix.android.internal.database.query.find
|
||||
import im.vector.matrix.android.internal.database.query.getOrCreate
|
||||
import im.vector.matrix.android.internal.database.query.where
|
||||
|
|
|
@ -28,6 +28,7 @@ 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.model.RoomSummaryEntity
|
||||
import im.vector.matrix.android.internal.database.model.TimelineEventEntity
|
||||
import im.vector.matrix.android.internal.database.query.*
|
||||
import im.vector.matrix.android.internal.database.query.isEventRead
|
||||
import im.vector.matrix.android.internal.database.query.latestEvent
|
||||
import im.vector.matrix.android.internal.database.query.prev
|
||||
|
@ -38,7 +39,6 @@ import im.vector.matrix.android.internal.session.room.membership.RoomMembers
|
|||
import im.vector.matrix.android.internal.session.sync.model.RoomSyncSummary
|
||||
import im.vector.matrix.android.internal.session.sync.model.RoomSyncUnreadNotifications
|
||||
import io.realm.Realm
|
||||
import io.realm.kotlin.createObject
|
||||
import javax.inject.Inject
|
||||
|
||||
internal class RoomSummaryUpdater @Inject constructor(@UserId private val userId: String,
|
||||
|
@ -69,9 +69,7 @@ internal class RoomSummaryUpdater @Inject constructor(@UserId private val userId
|
|||
roomSummary: RoomSyncSummary? = null,
|
||||
unreadNotifications: RoomSyncUnreadNotifications? = null,
|
||||
updateMembers: Boolean = false) {
|
||||
val roomSummaryEntity = RoomSummaryEntity.where(realm, roomId).findFirst()
|
||||
?: realm.createObject(roomId)
|
||||
|
||||
val roomSummaryEntity = RoomSummaryEntity.getOrCreate(realm, roomId)
|
||||
if (roomSummary != null) {
|
||||
if (roomSummary.heroes.isNotEmpty()) {
|
||||
roomSummaryEntity.heroes.clear()
|
||||
|
|
|
@ -638,7 +638,14 @@ internal class DefaultTimeline(
|
|||
|
||||
private fun fetchEvent(eventId: String) {
|
||||
val params = GetContextOfEventTask.Params(roomId, eventId, settings.initialSize)
|
||||
cancelableBag += contextOfEventTask.configureWith(params).executeBy(taskExecutor)
|
||||
cancelableBag += contextOfEventTask.configureWith(params) {
|
||||
callback = object : MatrixCallback<TokenChunkEventPersistor.Result> {
|
||||
override fun onFailure(failure: Throwable) {
|
||||
postFailure(failure)
|
||||
}
|
||||
}
|
||||
}
|
||||
.executeBy(taskExecutor)
|
||||
}
|
||||
|
||||
private fun postSnapshot() {
|
||||
|
@ -651,7 +658,7 @@ internal class DefaultTimeline(
|
|||
val runnable = Runnable {
|
||||
synchronized(listeners) {
|
||||
listeners.forEach {
|
||||
it.onUpdated(snapshot)
|
||||
it.onTimelineUpdated(snapshot)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -659,6 +666,20 @@ internal class DefaultTimeline(
|
|||
}
|
||||
}
|
||||
|
||||
private fun postFailure(throwable: Throwable) {
|
||||
if (isReady.get().not()) {
|
||||
return
|
||||
}
|
||||
val runnable = Runnable {
|
||||
synchronized(listeners) {
|
||||
listeners.forEach {
|
||||
it.onTimelineFailure(throwable)
|
||||
}
|
||||
}
|
||||
}
|
||||
mainHandler.post(runnable)
|
||||
}
|
||||
|
||||
private fun clearAllValues() {
|
||||
prevDisplayIndex = null
|
||||
nextDisplayIndex = null
|
||||
|
|
|
@ -18,6 +18,10 @@ package im.vector.matrix.android.internal.session.room.timeline
|
|||
|
||||
import com.zhuinden.monarchy.Monarchy
|
||||
import im.vector.matrix.android.internal.database.helper.*
|
||||
import im.vector.matrix.android.internal.database.helper.add
|
||||
import im.vector.matrix.android.internal.database.helper.addOrUpdate
|
||||
import im.vector.matrix.android.internal.database.helper.addStateEvent
|
||||
import im.vector.matrix.android.internal.database.helper.deleteOnCascade
|
||||
import im.vector.matrix.android.internal.database.model.ChunkEntity
|
||||
import im.vector.matrix.android.internal.database.model.RoomEntity
|
||||
import im.vector.matrix.android.internal.database.query.create
|
||||
|
@ -112,7 +116,7 @@ internal class TokenChunkEventPersistor @Inject constructor(private val monarchy
|
|||
Timber.v("Start persisting ${receivedChunk.events.size} events in $roomId towards $direction")
|
||||
|
||||
val roomEntity = RoomEntity.where(realm, roomId).findFirst()
|
||||
?: realm.createObject(roomId)
|
||||
?: realm.createObject(roomId)
|
||||
|
||||
val nextToken: String?
|
||||
val prevToken: String?
|
||||
|
@ -125,7 +129,7 @@ internal class TokenChunkEventPersistor @Inject constructor(private val monarchy
|
|||
}
|
||||
|
||||
val shouldSkip = ChunkEntity.find(realm, roomId, nextToken = nextToken) != null
|
||||
|| ChunkEntity.find(realm, roomId, prevToken = prevToken) != null
|
||||
|| ChunkEntity.find(realm, roomId, prevToken = prevToken) != null
|
||||
|
||||
val prevChunk = ChunkEntity.find(realm, roomId, nextToken = prevToken)
|
||||
val nextChunk = ChunkEntity.find(realm, roomId, prevToken = nextToken)
|
||||
|
@ -139,7 +143,7 @@ internal class TokenChunkEventPersistor @Inject constructor(private val monarchy
|
|||
} else {
|
||||
nextChunk?.apply { this.prevToken = prevToken }
|
||||
}
|
||||
?: ChunkEntity.create(realm, prevToken, nextToken)
|
||||
?: ChunkEntity.create(realm, prevToken, nextToken)
|
||||
|
||||
if (receivedChunk.events.isEmpty() && receivedChunk.end == receivedChunk.start) {
|
||||
Timber.v("Reach end of $roomId")
|
||||
|
|
|
@ -17,7 +17,6 @@
|
|||
package im.vector.matrix.android.internal.session.sync
|
||||
|
||||
import im.vector.matrix.android.R
|
||||
import im.vector.matrix.android.internal.auth.SessionParamsStore
|
||||
import im.vector.matrix.android.internal.di.UserId
|
||||
import im.vector.matrix.android.internal.network.executeRequest
|
||||
import im.vector.matrix.android.internal.session.DefaultInitialSyncProgressService
|
||||
|
|
|
@ -31,6 +31,7 @@ import butterknife.Unbinder
|
|||
import com.airbnb.mvrx.BaseMvRxFragment
|
||||
import com.airbnb.mvrx.MvRx
|
||||
import com.bumptech.glide.util.Util.assertMainThread
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import im.vector.riotx.core.di.DaggerScreenComponent
|
||||
import im.vector.riotx.core.di.HasScreenInjector
|
||||
import im.vector.riotx.core.di.ScreenComponent
|
||||
|
@ -167,6 +168,13 @@ abstract class VectorBaseFragment : BaseMvRxFragment(), HasScreenInjector {
|
|||
return this
|
||||
}
|
||||
|
||||
protected fun showErrorInSnackbar(throwable: Throwable) {
|
||||
vectorBaseActivity.coordinatorLayout?.let {
|
||||
Snackbar.make(it, errorFormatter.toHumanReadable(throwable), Snackbar.LENGTH_SHORT)
|
||||
.show()
|
||||
}
|
||||
}
|
||||
|
||||
/* ==========================================================================================
|
||||
* Toolbar
|
||||
* ========================================================================================== */
|
||||
|
|
|
@ -274,6 +274,16 @@ class RoomDetailFragment @Inject constructor(
|
|||
roomDetailViewModel.requestLiveData.observeEvent(this) {
|
||||
displayRoomDetailActionResult(it)
|
||||
}
|
||||
|
||||
roomDetailViewModel.viewEvents
|
||||
.observe()
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe {
|
||||
when (it) {
|
||||
is RoomDetailViewEvents.Failure -> showErrorInSnackbar(it.throwable)
|
||||
}
|
||||
}
|
||||
.disposeOnDestroyView()
|
||||
}
|
||||
|
||||
override fun onActivityCreated(savedInstanceState: Bundle?) {
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
/*
|
||||
* 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.riotx.features.home.room.detail
|
||||
|
||||
/**
|
||||
* Transient events for RoomDetail
|
||||
*/
|
||||
sealed class RoomDetailViewEvents {
|
||||
data class Failure(val throwable: Throwable) : RoomDetailViewEvents()
|
||||
}
|
|
@ -27,6 +27,7 @@ import com.squareup.inject.assisted.Assisted
|
|||
import com.squareup.inject.assisted.AssistedInject
|
||||
import im.vector.matrix.android.api.MatrixCallback
|
||||
import im.vector.matrix.android.api.MatrixPatterns
|
||||
import im.vector.matrix.android.api.failure.Failure
|
||||
import im.vector.matrix.android.api.session.Session
|
||||
import im.vector.matrix.android.api.session.events.model.EventType
|
||||
import im.vector.matrix.android.api.session.events.model.isImageMessage
|
||||
|
@ -56,7 +57,9 @@ import im.vector.riotx.core.extensions.postLiveEvent
|
|||
import im.vector.riotx.core.platform.VectorViewModel
|
||||
import im.vector.riotx.core.resources.StringProvider
|
||||
import im.vector.riotx.core.resources.UserPreferencesProvider
|
||||
import im.vector.riotx.core.utils.DataSource
|
||||
import im.vector.riotx.core.utils.LiveEvent
|
||||
import im.vector.riotx.core.utils.PublishDataSource
|
||||
import im.vector.riotx.core.utils.subscribeLogError
|
||||
import im.vector.riotx.features.command.CommandParser
|
||||
import im.vector.riotx.features.command.ParsedCommand
|
||||
|
@ -101,6 +104,9 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
|
|||
private var timelineEvents = PublishRelay.create<List<TimelineEvent>>()
|
||||
private var timeline = room.createTimeline(eventId, timelineSettings)
|
||||
|
||||
private val _viewEvents = PublishDataSource<RoomDetailViewEvents>()
|
||||
val viewEvents: DataSource<RoomDetailViewEvents> = _viewEvents
|
||||
|
||||
// Can be used for several actions, for a one shot result
|
||||
private val _requestLiveData = MutableLiveData<LiveEvent<Async<RoomDetailAction>>>()
|
||||
val requestLiveData: LiveData<LiveEvent<Async<RoomDetailAction>>>
|
||||
|
@ -862,10 +868,16 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
|
|||
}
|
||||
}
|
||||
|
||||
override fun onUpdated(snapshot: List<TimelineEvent>) {
|
||||
override fun onTimelineUpdated(snapshot: List<TimelineEvent>) {
|
||||
timelineEvents.accept(snapshot)
|
||||
}
|
||||
|
||||
override fun onTimelineFailure(throwable: Throwable) {
|
||||
// If we have a critical timeline issue, we get back to live.
|
||||
timeline.restartWithEventId(null)
|
||||
_viewEvents.post(RoomDetailViewEvents.Failure(throwable))
|
||||
}
|
||||
|
||||
override fun onCleared() {
|
||||
timeline.dispose()
|
||||
timeline.removeListener(this)
|
||||
|
|
|
@ -236,10 +236,14 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec
|
|||
|
||||
// Timeline.LISTENER ***************************************************************************
|
||||
|
||||
override fun onUpdated(snapshot: List<TimelineEvent>) {
|
||||
override fun onTimelineUpdated(snapshot: List<TimelineEvent>) {
|
||||
submitSnapshot(snapshot)
|
||||
}
|
||||
|
||||
override fun onTimelineFailure(throwable: Throwable) {
|
||||
// no-op, already handled
|
||||
}
|
||||
|
||||
private fun submitSnapshot(newSnapshot: List<TimelineEvent>) {
|
||||
backgroundHandler.post {
|
||||
inSubmitList = true
|
||||
|
|
|
@ -104,7 +104,7 @@ class RoomListFragment @Inject constructor(
|
|||
.subscribe {
|
||||
when (it) {
|
||||
is RoomListViewEvents.SelectRoom -> openSelectedRoom(it)
|
||||
is RoomListViewEvents.Failure -> showError(it)
|
||||
is RoomListViewEvents.Failure -> showErrorInSnackbar(it.throwable)
|
||||
}
|
||||
}
|
||||
.disposeOnDestroyView()
|
||||
|
@ -135,13 +135,6 @@ class RoomListFragment @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private fun showError(event: RoomListViewEvents.Failure) {
|
||||
vectorBaseActivity.coordinatorLayout?.let {
|
||||
Snackbar.make(it, errorFormatter.toHumanReadable(event.throwable), Snackbar.LENGTH_SHORT)
|
||||
.show()
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupCreateRoomButton() {
|
||||
when (roomListParams.displayMode) {
|
||||
RoomListDisplayMode.HOME -> createChatFabMenu.isVisible = true
|
||||
|
|
Loading…
Reference in a new issue