forked from GitHub-Mirror/riotX-android
Merge pull request #473 from vector-im/feature/sync_room
Feature/sync room
This commit is contained in:
commit
5a9d88e791
@ -12,9 +12,10 @@ Improvements:
|
|||||||
- Enable proper cancellation of suspending functions (including db transaction)
|
- Enable proper cancellation of suspending functions (including db transaction)
|
||||||
- Enhances network connectivity checks in SDK
|
- Enhances network connectivity checks in SDK
|
||||||
- Add "View Edit History" item in the message bottom sheet (#401)
|
- Add "View Edit History" item in the message bottom sheet (#401)
|
||||||
|
- Cancel sync request on pause and timeout to 0 after pause (#404)
|
||||||
|
|
||||||
Other changes:
|
Other changes:
|
||||||
-
|
- Show sync progress also in room detail screen (#403)
|
||||||
|
|
||||||
Bugfix:
|
Bugfix:
|
||||||
- Edited message: link confusion when (edited) appears in body (#398)
|
- Edited message: link confusion when (edited) appears in body (#398)
|
||||||
|
@ -30,6 +30,7 @@ import im.vector.matrix.android.internal.task.TaskExecutor
|
|||||||
import im.vector.matrix.android.internal.task.TaskThread
|
import im.vector.matrix.android.internal.task.TaskThread
|
||||||
import im.vector.matrix.android.internal.task.configureWith
|
import im.vector.matrix.android.internal.task.configureWith
|
||||||
import im.vector.matrix.android.internal.util.BackgroundDetectionObserver
|
import im.vector.matrix.android.internal.util.BackgroundDetectionObserver
|
||||||
|
import kotlinx.coroutines.CancellationException
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import java.net.SocketTimeoutException
|
import java.net.SocketTimeoutException
|
||||||
import java.util.concurrent.CountDownLatch
|
import java.util.concurrent.CountDownLatch
|
||||||
@ -70,6 +71,8 @@ internal class SyncThread @Inject constructor(private val syncTask: SyncTask,
|
|||||||
if (state is SyncState.RUNNING) {
|
if (state is SyncState.RUNNING) {
|
||||||
Timber.v("Pause sync...")
|
Timber.v("Pause sync...")
|
||||||
updateStateTo(SyncState.PAUSED)
|
updateStateTo(SyncState.PAUSED)
|
||||||
|
cancelableTask?.cancel()
|
||||||
|
lock.notify()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -90,18 +93,25 @@ internal class SyncThread @Inject constructor(private val syncTask: SyncTask,
|
|||||||
backgroundDetectionObserver.register(this)
|
backgroundDetectionObserver.register(this)
|
||||||
|
|
||||||
while (state != SyncState.KILLING) {
|
while (state != SyncState.KILLING) {
|
||||||
|
Timber.v("Entering loop, state: $state")
|
||||||
|
|
||||||
if (!networkConnectivityChecker.isConnected() || state == SyncState.PAUSED) {
|
if (!networkConnectivityChecker.isConnected() || state == SyncState.PAUSED) {
|
||||||
Timber.v("Sync is Paused. Waiting...")
|
Timber.v("No network or sync is Paused. Waiting...")
|
||||||
synchronized(lock) {
|
synchronized(lock) {
|
||||||
lock.wait()
|
lock.wait()
|
||||||
}
|
}
|
||||||
|
Timber.v("...unlocked")
|
||||||
} else {
|
} else {
|
||||||
if (state !is SyncState.RUNNING) {
|
if (state !is SyncState.RUNNING) {
|
||||||
updateStateTo(SyncState.RUNNING(afterPause = true))
|
updateStateTo(SyncState.RUNNING(afterPause = true))
|
||||||
}
|
}
|
||||||
Timber.v("[$this] Execute sync request with timeout $DEFAULT_LONG_POOL_TIMEOUT")
|
|
||||||
|
// No timeout after a pause
|
||||||
|
val timeout = state.let { if (it is SyncState.RUNNING && it.afterPause) 0 else DEFAULT_LONG_POOL_TIMEOUT }
|
||||||
|
|
||||||
|
Timber.v("Execute sync request with timeout $timeout")
|
||||||
val latch = CountDownLatch(1)
|
val latch = CountDownLatch(1)
|
||||||
val params = SyncTask.Params(DEFAULT_LONG_POOL_TIMEOUT)
|
val params = SyncTask.Params(timeout)
|
||||||
|
|
||||||
cancelableTask = syncTask.configureWith(params) {
|
cancelableTask = syncTask.configureWith(params) {
|
||||||
this.callbackThread = TaskThread.SYNC
|
this.callbackThread = TaskThread.SYNC
|
||||||
@ -109,29 +119,31 @@ internal class SyncThread @Inject constructor(private val syncTask: SyncTask,
|
|||||||
this.callback = object : MatrixCallback<Unit> {
|
this.callback = object : MatrixCallback<Unit> {
|
||||||
|
|
||||||
override fun onSuccess(data: Unit) {
|
override fun onSuccess(data: Unit) {
|
||||||
|
Timber.v("onSuccess")
|
||||||
latch.countDown()
|
latch.countDown()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onFailure(failure: Throwable) {
|
override fun onFailure(failure: Throwable) {
|
||||||
if (failure is Failure.NetworkConnection
|
if (failure is Failure.NetworkConnection && failure.cause is SocketTimeoutException) {
|
||||||
&& failure.cause is SocketTimeoutException) {
|
|
||||||
// Timeout are not critical
|
// Timeout are not critical
|
||||||
Timber.v("Timeout")
|
Timber.v("Timeout")
|
||||||
} else {
|
} else if (failure is Failure.Unknown && failure.throwable is CancellationException) {
|
||||||
Timber.e(failure)
|
Timber.v("Cancelled")
|
||||||
}
|
} else if (failure is Failure.ServerError
|
||||||
|
|
||||||
if (failure !is Failure.NetworkConnection
|
|
||||||
|| failure.cause is JsonEncodingException) {
|
|
||||||
// Wait 10s before retrying
|
|
||||||
sleep(RETRY_WAIT_TIME_MS)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (failure is Failure.ServerError
|
|
||||||
&& (failure.error.code == MatrixError.UNKNOWN_TOKEN || failure.error.code == MatrixError.MISSING_TOKEN)) {
|
&& (failure.error.code == MatrixError.UNKNOWN_TOKEN || failure.error.code == MatrixError.MISSING_TOKEN)) {
|
||||||
// No token or invalid token, stop the thread
|
// No token or invalid token, stop the thread
|
||||||
|
Timber.w(failure)
|
||||||
updateStateTo(SyncState.KILLING)
|
updateStateTo(SyncState.KILLING)
|
||||||
|
} else {
|
||||||
|
Timber.e(failure)
|
||||||
|
|
||||||
|
if (failure !is Failure.NetworkConnection || failure.cause is JsonEncodingException) {
|
||||||
|
// Wait 10s before retrying
|
||||||
|
Timber.v("Wait 10s")
|
||||||
|
sleep(RETRY_WAIT_TIME_MS)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
latch.countDown()
|
latch.countDown()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -139,8 +151,10 @@ internal class SyncThread @Inject constructor(private val syncTask: SyncTask,
|
|||||||
.executeBy(taskExecutor)
|
.executeBy(taskExecutor)
|
||||||
|
|
||||||
latch.await()
|
latch.await()
|
||||||
if (state is SyncState.RUNNING) {
|
state.let {
|
||||||
updateStateTo(SyncState.RUNNING(afterPause = false))
|
if (it is SyncState.RUNNING && it.afterPause) {
|
||||||
|
updateStateTo(SyncState.RUNNING(afterPause = false))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Timber.v("...Continue")
|
Timber.v("...Continue")
|
||||||
|
@ -65,6 +65,7 @@ import im.vector.matrix.android.api.session.room.send.SendState
|
|||||||
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
|
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
|
||||||
import im.vector.matrix.android.api.session.room.timeline.getLastMessageContent
|
import im.vector.matrix.android.api.session.room.timeline.getLastMessageContent
|
||||||
import im.vector.matrix.android.api.session.room.timeline.getTextEditableContent
|
import im.vector.matrix.android.api.session.room.timeline.getTextEditableContent
|
||||||
|
import im.vector.matrix.android.api.session.sync.SyncState
|
||||||
import im.vector.matrix.android.api.session.user.model.User
|
import im.vector.matrix.android.api.session.user.model.User
|
||||||
import im.vector.riotx.R
|
import im.vector.riotx.R
|
||||||
import im.vector.riotx.core.di.ScreenComponent
|
import im.vector.riotx.core.di.ScreenComponent
|
||||||
@ -247,6 +248,14 @@ class RoomDetailFragment :
|
|||||||
is SendMode.REPLY -> enterSpecialMode(mode.timelineEvent, R.drawable.ic_reply, false)
|
is SendMode.REPLY -> enterSpecialMode(mode.timelineEvent, R.drawable.ic_reply, false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
roomDetailViewModel.selectSubscribe(RoomDetailViewState::syncState) { syncState ->
|
||||||
|
syncProgressBar.visibility = when (syncState) {
|
||||||
|
is SyncState.RUNNING -> if (syncState.afterPause) View.VISIBLE else View.GONE
|
||||||
|
else -> View.GONE
|
||||||
|
}
|
||||||
|
syncProgressBarWrap.visibility = syncProgressBar.visibility
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setupNotificationView() {
|
private fun setupNotificationView() {
|
||||||
|
@ -103,6 +103,7 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
|
|||||||
}
|
}
|
||||||
|
|
||||||
init {
|
init {
|
||||||
|
observeSyncState()
|
||||||
observeRoomSummary()
|
observeRoomSummary()
|
||||||
observeEventDisplayedActions()
|
observeEventDisplayedActions()
|
||||||
observeSummaryState()
|
observeSummaryState()
|
||||||
@ -631,6 +632,17 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
|
|||||||
.disposeOnClear()
|
.disposeOnClear()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun observeSyncState() {
|
||||||
|
session.rx()
|
||||||
|
.liveSyncState()
|
||||||
|
.subscribe { syncState ->
|
||||||
|
setState {
|
||||||
|
copy(syncState = syncState)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.disposeOnClear()
|
||||||
|
}
|
||||||
|
|
||||||
private fun observeRoomSummary() {
|
private fun observeRoomSummary() {
|
||||||
room.rx().liveRoomSummary()
|
room.rx().liveRoomSummary()
|
||||||
.execute { async ->
|
.execute { async ->
|
||||||
|
@ -21,9 +21,9 @@ import com.airbnb.mvrx.MvRxState
|
|||||||
import com.airbnb.mvrx.Uninitialized
|
import com.airbnb.mvrx.Uninitialized
|
||||||
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.model.RoomSummary
|
import im.vector.matrix.android.api.session.room.model.RoomSummary
|
||||||
import im.vector.matrix.android.api.session.room.model.tombstone.RoomTombstoneContent
|
|
||||||
import im.vector.matrix.android.api.session.room.timeline.Timeline
|
import im.vector.matrix.android.api.session.room.timeline.Timeline
|
||||||
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
|
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
|
||||||
|
import im.vector.matrix.android.api.session.sync.SyncState
|
||||||
import im.vector.matrix.android.api.session.user.model.User
|
import im.vector.matrix.android.api.session.user.model.User
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -50,7 +50,8 @@ data class RoomDetailViewState(
|
|||||||
val sendMode: SendMode = SendMode.REGULAR,
|
val sendMode: SendMode = SendMode.REGULAR,
|
||||||
val isEncrypted: Boolean = false,
|
val isEncrypted: Boolean = false,
|
||||||
val tombstoneEvent: Event? = null,
|
val tombstoneEvent: Event? = null,
|
||||||
val tombstoneEventHandling: Async<String> = Uninitialized
|
val tombstoneEventHandling: Async<String> = Uninitialized,
|
||||||
|
val syncState: SyncState = SyncState.IDLE
|
||||||
) : MvRxState {
|
) : MvRxState {
|
||||||
|
|
||||||
constructor(args: RoomDetailArgs) : this(roomId = args.roomId, eventId = args.eventId)
|
constructor(args: RoomDetailArgs) : this(roomId = args.roomId, eventId = args.eventId)
|
||||||
|
@ -11,8 +11,8 @@
|
|||||||
style="@style/VectorToolbarStyle"
|
style="@style/VectorToolbarStyle"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="?actionBarSize"
|
android:layout_height="?actionBarSize"
|
||||||
android:transitionName="toolbar"
|
|
||||||
android:elevation="4dp"
|
android:elevation="4dp"
|
||||||
|
android:transitionName="toolbar"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toTopOf="parent">
|
app:layout_constraintTop_toTopOf="parent">
|
||||||
@ -71,6 +71,29 @@
|
|||||||
|
|
||||||
</androidx.appcompat.widget.Toolbar>
|
</androidx.appcompat.widget.Toolbar>
|
||||||
|
|
||||||
|
<!-- Trick to remove surrounding padding (clip frome wrapping frame) -->
|
||||||
|
<FrameLayout
|
||||||
|
android:id="@+id/syncProgressBarWrap"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="3dp"
|
||||||
|
android:visibility="gone"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/roomToolbar"
|
||||||
|
tools:visibility="visible">
|
||||||
|
|
||||||
|
<ProgressBar
|
||||||
|
android:id="@+id/syncProgressBar"
|
||||||
|
style="@style/Widget.AppCompat.ProgressBar.Horizontal"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="14dp"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:background="?riotx_header_panel_background"
|
||||||
|
android:indeterminate="true"
|
||||||
|
android:visibility="gone"
|
||||||
|
tools:visibility="visible" />
|
||||||
|
</FrameLayout>
|
||||||
|
|
||||||
<com.airbnb.epoxy.EpoxyRecyclerView
|
<com.airbnb.epoxy.EpoxyRecyclerView
|
||||||
android:id="@+id/recyclerView"
|
android:id="@+id/recyclerView"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
@ -78,7 +101,7 @@
|
|||||||
app:layout_constraintBottom_toTopOf="@+id/recyclerViewBarrier"
|
app:layout_constraintBottom_toTopOf="@+id/recyclerViewBarrier"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toBottomOf="@id/roomToolbar"
|
app:layout_constraintTop_toBottomOf="@id/syncProgressBarWrap"
|
||||||
tools:listitem="@layout/item_timeline_event_base" />
|
tools:listitem="@layout/item_timeline_event_base" />
|
||||||
|
|
||||||
<androidx.constraintlayout.widget.Barrier
|
<androidx.constraintlayout.widget.Barrier
|
||||||
|
Loading…
Reference in New Issue
Block a user