From 3dd161d65a7139ec8a19944fe1bf3ac35ec10300 Mon Sep 17 00:00:00 2001 From: ganfra Date: Tue, 11 Jun 2019 16:54:44 +0200 Subject: [PATCH] Sync: add progress indicator for sync, need UI inputs. --- .../java/im/vector/matrix/rx/RxSession.kt | 5 ++ .../matrix/android/api/session/Session.kt | 9 +++ .../android/api/session/sync/SyncState.kt | 27 +++++++ .../internal/session/DefaultSession.kt | 5 ++ .../internal/session/sync/job/SyncThread.kt | 73 ++++++++++--------- .../features/home/HomeDetailFragment.kt | 6 ++ .../features/home/HomeDetailViewModel.kt | 18 ++++- .../features/home/HomeDetailViewState.kt | 4 +- .../main/res/layout/fragment_home_detail.xml | 15 +++- 9 files changed, 123 insertions(+), 39 deletions(-) create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/sync/SyncState.kt diff --git a/matrix-sdk-android-rx/src/main/java/im/vector/matrix/rx/RxSession.kt b/matrix-sdk-android-rx/src/main/java/im/vector/matrix/rx/RxSession.kt index 96cddf7d..4205b049 100644 --- a/matrix-sdk-android-rx/src/main/java/im/vector/matrix/rx/RxSession.kt +++ b/matrix-sdk-android-rx/src/main/java/im/vector/matrix/rx/RxSession.kt @@ -19,6 +19,7 @@ package im.vector.matrix.rx import im.vector.matrix.android.api.session.Session import im.vector.matrix.android.api.session.group.model.GroupSummary import im.vector.matrix.android.api.session.room.model.RoomSummary +import im.vector.matrix.android.api.session.sync.SyncState import io.reactivex.Observable class RxSession(private val session: Session) { @@ -31,6 +32,10 @@ class RxSession(private val session: Session) { return session.liveGroupSummaries().asObservable() } + fun liveSyncState(): Observable { + return session.syncState().asObservable() + } + } fun Session.rx(): RxSession { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/Session.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/Session.kt index 82264306..6f8d745e 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/Session.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/Session.kt @@ -17,6 +17,7 @@ package im.vector.matrix.android.api.session import androidx.annotation.MainThread +import androidx.lifecycle.LiveData import im.vector.matrix.android.api.auth.data.SessionParams import im.vector.matrix.android.api.session.cache.CacheService import im.vector.matrix.android.api.session.content.ContentUploadStateTracker @@ -27,6 +28,7 @@ import im.vector.matrix.android.api.session.room.RoomDirectoryService import im.vector.matrix.android.api.session.room.RoomService import im.vector.matrix.android.api.session.signout.SignOutService import im.vector.matrix.android.api.session.sync.FilterService +import im.vector.matrix.android.api.session.sync.SyncState import im.vector.matrix.android.api.session.user.UserService /** @@ -66,6 +68,12 @@ interface Session : @MainThread fun stopSync() + /** + * This method allows to listen the sync state. + * @return a [LiveData] of [SyncState]. + */ + fun syncState(): LiveData + /** * This method allow to close a session. It does stop some services. */ @@ -102,6 +110,7 @@ interface Session : * The access token is not valid anymore */ fun onInvalidToken() + } } \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/sync/SyncState.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/sync/SyncState.kt new file mode 100644 index 00000000..eedb8086 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/sync/SyncState.kt @@ -0,0 +1,27 @@ +/* + * + * * 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.api.session.sync + +sealed class SyncState { + object IDLE : SyncState() + data class RUNNING(val catchingUp: Boolean) : SyncState() + object PAUSED : SyncState() + object KILLING : SyncState() + object KILLED : SyncState() +} \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/DefaultSession.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/DefaultSession.kt index 2b43c15c..e3eb6db1 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/DefaultSession.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/DefaultSession.kt @@ -39,6 +39,7 @@ import im.vector.matrix.android.api.session.room.model.roomdirectory.PublicRooms import im.vector.matrix.android.api.session.room.model.thirdparty.ThirdPartyProtocol import im.vector.matrix.android.api.session.signout.SignOutService import im.vector.matrix.android.api.session.sync.FilterService +import im.vector.matrix.android.api.session.sync.SyncState import im.vector.matrix.android.api.session.user.UserService import im.vector.matrix.android.api.session.user.model.User import im.vector.matrix.android.api.util.Cancelable @@ -124,6 +125,10 @@ internal class DefaultSession(override val sessionParams: SessionParams) : Sessi isOpen = false } + override fun syncState(): LiveData { + return syncThread.liveState() + } + @MainThread override fun signOut(callback: MatrixCallback) { assert(isOpen) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/job/SyncThread.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/job/SyncThread.kt index 929b67d6..1c6d0a0f 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/job/SyncThread.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/job/SyncThread.kt @@ -16,10 +16,13 @@ package im.vector.matrix.android.internal.session.sync.job +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData import com.squareup.moshi.JsonEncodingException import im.vector.matrix.android.api.MatrixCallback import im.vector.matrix.android.api.failure.Failure import im.vector.matrix.android.api.failure.MatrixError +import im.vector.matrix.android.api.session.sync.SyncState import im.vector.matrix.android.api.util.Cancelable import im.vector.matrix.android.internal.network.NetworkConnectivityChecker import im.vector.matrix.android.internal.session.sync.SyncTask @@ -42,61 +45,52 @@ internal class SyncThread(private val syncTask: SyncTask, private val taskExecutor: TaskExecutor ) : Thread(), NetworkConnectivityChecker.Listener, BackgroundDetectionObserver.Listener { - enum class State { - IDLE, - RUNNING, - PAUSED, - KILLING, - KILLED - } - - private var state: State = State.IDLE + private var state: SyncState = SyncState.IDLE + private var liveState = MutableLiveData() private val lock = Object() private var nextBatch = syncTokenStore.getLastToken() private var cancelableTask: Cancelable? = null - fun restart() { - synchronized(lock) { - if (state != State.PAUSED) { - return@synchronized - } - Timber.v("Resume sync...") + init { + updateStateTo(SyncState.IDLE) + } + fun restart() = synchronized(lock) { + if (state is SyncState.PAUSED) { + Timber.v("Resume sync...") // Retrieve the last token, it may have been deleted in case of a clear cache nextBatch = syncTokenStore.getLastToken() - - state = State.RUNNING + updateStateTo(SyncState.RUNNING(catchingUp = true)) lock.notify() } } - fun pause() { - synchronized(lock) { - if (state != State.RUNNING) { - return@synchronized - } + fun pause() = synchronized(lock) { + if (state is SyncState.RUNNING) { Timber.v("Pause sync...") - state = State.PAUSED + updateStateTo(SyncState.PAUSED) } } - fun kill() { - synchronized(lock) { - Timber.v("Kill sync...") - state = State.KILLING - cancelableTask?.cancel() - lock.notify() - } + fun kill() = synchronized(lock) { + Timber.v("Kill sync...") + updateStateTo(SyncState.KILLING) + cancelableTask?.cancel() + lock.notify() } + fun liveState(): LiveData { + return liveState + } override fun run() { Timber.v("Start syncing...") networkConnectivityChecker.register(this) backgroundDetectionObserver.register(this) - state = State.RUNNING - while (state != State.KILLING) { - if (!networkConnectivityChecker.isConnected() || state == State.PAUSED) { + updateStateTo(SyncState.RUNNING(catchingUp = true)) + + while (state != SyncState.KILLING) { + if (!networkConnectivityChecker.isConnected() || state == SyncState.PAUSED) { Timber.v("Waiting...") synchronized(lock) { lock.wait() @@ -133,7 +127,7 @@ internal class SyncThread(private val syncTask: SyncTask, if (failure is Failure.ServerError && (failure.error.code == MatrixError.UNKNOWN_TOKEN || failure.error.code == MatrixError.MISSING_TOKEN)) { // No token or invalid token, stop the thread - state = State.KILLING + updateStateTo(SyncState.KILLING) } latch.countDown() @@ -141,16 +135,23 @@ internal class SyncThread(private val syncTask: SyncTask, }) .executeBy(taskExecutor) - latch.await() + if (state is SyncState.RUNNING) { + updateStateTo(SyncState.RUNNING(catchingUp = false)) + } } } Timber.v("Sync killed") - state = State.KILLED + updateStateTo(SyncState.KILLED) backgroundDetectionObserver.unregister(this) networkConnectivityChecker.unregister(this) } + private fun updateStateTo(newState: SyncState) { + state = newState + liveState.postValue(newState) + } + override fun onConnect() { synchronized(lock) { lock.notify() diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/HomeDetailFragment.kt b/vector/src/main/java/im/vector/riotredesign/features/home/HomeDetailFragment.kt index cd47df2d..32adfbd2 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/home/HomeDetailFragment.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/home/HomeDetailFragment.kt @@ -19,6 +19,7 @@ package im.vector.riotredesign.features.home import android.os.Bundle import android.os.Parcelable import android.view.LayoutInflater +import android.view.View import androidx.core.view.forEachIndexed import androidx.lifecycle.ViewModelProviders import com.airbnb.mvrx.args @@ -26,6 +27,7 @@ import com.airbnb.mvrx.fragmentViewModel import com.airbnb.mvrx.withState import com.google.android.material.bottomnavigation.BottomNavigationItemView import com.google.android.material.bottomnavigation.BottomNavigationMenuView +import im.vector.matrix.android.api.session.sync.SyncState import im.vector.riotredesign.R import im.vector.riotredesign.core.platform.ToolbarConfigurable import im.vector.riotredesign.core.platform.VectorBaseFragment @@ -145,6 +147,10 @@ class HomeDetailFragment : VectorBaseFragment() { unreadCounterBadgeViews[INDEX_CATCHUP].render(UnreadCounterBadgeView.State(it.notificationCountCatchup, it.notificationHighlightCatchup)) unreadCounterBadgeViews[INDEX_PEOPLE].render(UnreadCounterBadgeView.State(it.notificationCountPeople, it.notificationHighlightPeople)) unreadCounterBadgeViews[INDEX_ROOMS].render(UnreadCounterBadgeView.State(it.notificationCountRooms, it.notificationHighlightRooms)) + syncProgressBar.visibility = when (it.syncState) { + is SyncState.RUNNING -> if (it.syncState.catchingUp) View.VISIBLE else View.GONE + else -> View.GONE + } } companion object { diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/HomeDetailViewModel.kt b/vector/src/main/java/im/vector/riotredesign/features/home/HomeDetailViewModel.kt index 81a8f790..7e2ebb7f 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/home/HomeDetailViewModel.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/home/HomeDetailViewModel.kt @@ -18,6 +18,8 @@ package im.vector.riotredesign.features.home import com.airbnb.mvrx.MvRxViewModelFactory import com.airbnb.mvrx.ViewModelContext +import im.vector.matrix.android.api.session.Session +import im.vector.matrix.rx.rx import im.vector.riotredesign.core.platform.VectorViewModel import org.koin.android.ext.android.get @@ -25,6 +27,7 @@ import org.koin.android.ext.android.get * View model used to update the home bottom bar notification counts */ class HomeDetailViewModel(initialState: HomeDetailViewState, + private val session: Session, private val homeRoomListStore: HomeRoomListObservableStore) : VectorViewModel(initialState) { @@ -33,16 +36,29 @@ class HomeDetailViewModel(initialState: HomeDetailViewState, @JvmStatic override fun create(viewModelContext: ViewModelContext, state: HomeDetailViewState): HomeDetailViewModel? { val homeRoomListStore = viewModelContext.activity.get() - return HomeDetailViewModel(state, homeRoomListStore) + val session = viewModelContext.activity.get() + return HomeDetailViewModel(state, session, homeRoomListStore) } } init { + observeSyncState() observeRoomSummaries() } // PRIVATE METHODS ***************************************************************************** + private fun observeSyncState() { + session.rx() + .liveSyncState() + .subscribe { syncState -> + setState { + copy(syncState = syncState) + } + } + .disposeOnClear() + } + private fun observeRoomSummaries() { homeRoomListStore .observe() diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/HomeDetailViewState.kt b/vector/src/main/java/im/vector/riotredesign/features/home/HomeDetailViewState.kt index b85b7990..f201ea79 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/home/HomeDetailViewState.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/home/HomeDetailViewState.kt @@ -17,6 +17,7 @@ package im.vector.riotredesign.features.home import com.airbnb.mvrx.MvRxState +import im.vector.matrix.android.api.session.sync.SyncState data class HomeDetailViewState( val notificationCountCatchup: Int = 0, @@ -24,5 +25,6 @@ data class HomeDetailViewState( val notificationCountPeople: Int = 0, val notificationHighlightPeople: Boolean = false, val notificationCountRooms: Int = 0, - val notificationHighlightRooms: Boolean = false + val notificationHighlightRooms: Boolean = false, + val syncState: SyncState = SyncState.IDLE ) : MvRxState \ No newline at end of file diff --git a/vector/src/main/res/layout/fragment_home_detail.xml b/vector/src/main/res/layout/fragment_home_detail.xml index a54fa61e..0c4519fd 100644 --- a/vector/src/main/res/layout/fragment_home_detail.xml +++ b/vector/src/main/res/layout/fragment_home_detail.xml @@ -44,13 +44,26 @@ + + + app:layout_constraintTop_toBottomOf="@+id/syncProgressBar" />