Browse Source

Add pusher status in the state.

It improve the tests and we may want to render errors in the View at some point.
pull/3035/head
Benoit Marty 3 months ago committed by Benoit Marty
parent
commit
21ce1c40b3
  1. 29
      appnav/src/main/kotlin/io/element/android/appnav/loggedin/LoggedInPresenter.kt
  2. 3
      appnav/src/main/kotlin/io/element/android/appnav/loggedin/LoggedInState.kt
  3. 2
      appnav/src/main/kotlin/io/element/android/appnav/loggedin/LoggedInStateProvider.kt
  4. 24
      appnav/src/main/kotlin/io/element/android/appnav/loggedin/PusherRegistrationFailure.kt
  5. 34
      appnav/src/test/kotlin/io/element/android/appnav/loggedin/LoggedInPresenterTest.kt

29
appnav/src/main/kotlin/io/element/android/appnav/loggedin/LoggedInPresenter.kt

@ -18,14 +18,17 @@ package io.element.android.appnav.loggedin
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.collectAsState import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import im.vector.app.features.analytics.plan.CryptoSessionStateChange import im.vector.app.features.analytics.plan.CryptoSessionStateChange
import im.vector.app.features.analytics.plan.UserProperties import im.vector.app.features.analytics.plan.UserProperties
import io.element.android.features.networkmonitor.api.NetworkMonitor import io.element.android.features.networkmonitor.api.NetworkMonitor
import io.element.android.features.networkmonitor.api.NetworkStatus import io.element.android.features.networkmonitor.api.NetworkStatus
import io.element.android.libraries.architecture.AsyncData
import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.architecture.Presenter
import io.element.android.libraries.core.log.logger.LoggerTag import io.element.android.libraries.core.log.logger.LoggerTag
import io.element.android.libraries.matrix.api.MatrixClient import io.element.android.libraries.matrix.api.MatrixClient
@ -55,13 +58,16 @@ class LoggedInPresenter @Inject constructor(
val isVerified by remember { val isVerified by remember {
sessionVerificationService.sessionVerifiedStatus.map { it == SessionVerifiedStatus.Verified } sessionVerificationService.sessionVerifiedStatus.map { it == SessionVerifiedStatus.Verified }
}.collectAsState(initial = false) }.collectAsState(initial = false)
val pusherRegistrationState = remember<MutableState<AsyncData<Unit>>> { mutableStateOf(AsyncData.Uninitialized) }
if (isVerified) { if (isVerified) {
LaunchedEffect(Unit) { LaunchedEffect(Unit) {
ensurePusherIsRegistered() ensurePusherIsRegistered(pusherRegistrationState)
}
} else {
LaunchedEffect(Unit) {
pusherRegistrationState.value = AsyncData.Failure(PusherRegistrationFailure.AccountNotVerified())
} }
} }
val syncIndicator by matrixClient.roomListService.syncIndicator.collectAsState() val syncIndicator by matrixClient.roomListService.syncIndicator.collectAsState()
val networkStatus by networkMonitor.connectivity.collectAsState() val networkStatus by networkMonitor.connectivity.collectAsState()
val showSyncSpinner by remember { val showSyncSpinner by remember {
@ -77,25 +83,32 @@ class LoggedInPresenter @Inject constructor(
return LoggedInState( return LoggedInState(
showSyncSpinner = showSyncSpinner, showSyncSpinner = showSyncSpinner,
pusherRegistrationState = pusherRegistrationState.value,
) )
} }
private suspend fun ensurePusherIsRegistered() { private suspend fun ensurePusherIsRegistered(pusherRegistrationState: MutableState<AsyncData<Unit>>) {
Timber.tag(pusherTag.value).d("Ensure pusher is registered") Timber.tag(pusherTag.value).d("Ensure pusher is registered")
val currentPushProvider = pushService.getCurrentPushProvider() val currentPushProvider = pushService.getCurrentPushProvider()
val result = if (currentPushProvider == null) { val result = if (currentPushProvider == null) {
Timber.tag(pusherTag.value).d("Register with the first available push provider") Timber.tag(pusherTag.value).d("Register with the first available push provider")
val pushProvider = pushService.getAvailablePushProviders().firstOrNull() val pushProvider = pushService.getAvailablePushProviders().firstOrNull()
?: return Unit.also { Timber.tag(pusherTag.value).w("No push provider available") } ?: return Unit
.also { Timber.tag(pusherTag.value).w("No push providers available") }
.also { pusherRegistrationState.value = AsyncData.Failure(PusherRegistrationFailure.NoProvidersAvailable()) }
val distributor = pushProvider.getDistributors().firstOrNull() val distributor = pushProvider.getDistributors().firstOrNull()
?: return Unit.also { Timber.tag(pusherTag.value).w("No distributor available") } ?: return Unit
.also { Timber.tag(pusherTag.value).w("No distributors available") }
.also { pusherRegistrationState.value = AsyncData.Failure(PusherRegistrationFailure.NoDistributorsAvailable()) }
pushService.registerWith(matrixClient, pushProvider, distributor) pushService.registerWith(matrixClient, pushProvider, distributor)
} else { } else {
val currentPushDistributor = currentPushProvider.getCurrentDistributor(matrixClient) val currentPushDistributor = currentPushProvider.getCurrentDistributor(matrixClient)
if (currentPushDistributor == null) { if (currentPushDistributor == null) {
Timber.tag(pusherTag.value).d("Register with the first available distributor") Timber.tag(pusherTag.value).d("Register with the first available distributor")
val distributor = currentPushProvider.getDistributors().firstOrNull() val distributor = currentPushProvider.getDistributors().firstOrNull()
?: return Unit.also { Timber.tag(pusherTag.value).w("No distributor available") } ?: return Unit
.also { Timber.tag(pusherTag.value).w("No distributors available") }
.also { pusherRegistrationState.value = AsyncData.Failure(PusherRegistrationFailure.NoDistributorsAvailable()) }
pushService.registerWith(matrixClient, currentPushProvider, distributor) pushService.registerWith(matrixClient, currentPushProvider, distributor)
} else { } else {
Timber.tag(pusherTag.value).d("Re-register with the current distributor") Timber.tag(pusherTag.value).d("Re-register with the current distributor")
@ -105,9 +118,11 @@ class LoggedInPresenter @Inject constructor(
result.fold( result.fold(
onSuccess = { onSuccess = {
Timber.tag(pusherTag.value).d("Pusher registered") Timber.tag(pusherTag.value).d("Pusher registered")
pusherRegistrationState.value = AsyncData.Success(Unit)
}, },
onFailure = { onFailure = {
Timber.tag(pusherTag.value).e(it, "Failed to register pusher") Timber.tag(pusherTag.value).e(it, "Failed to register pusher")
pusherRegistrationState.value = AsyncData.Failure(PusherRegistrationFailure.RegistrationFailure(it))
} }
) )
} }

3
appnav/src/main/kotlin/io/element/android/appnav/loggedin/LoggedInState.kt

@ -16,6 +16,9 @@
package io.element.android.appnav.loggedin package io.element.android.appnav.loggedin
import io.element.android.libraries.architecture.AsyncData
data class LoggedInState( data class LoggedInState(
val showSyncSpinner: Boolean, val showSyncSpinner: Boolean,
val pusherRegistrationState: AsyncData<Unit>,
) )

2
appnav/src/main/kotlin/io/element/android/appnav/loggedin/LoggedInStateProvider.kt

@ -17,6 +17,7 @@
package io.element.android.appnav.loggedin package io.element.android.appnav.loggedin
import androidx.compose.ui.tooling.preview.PreviewParameterProvider import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import io.element.android.libraries.architecture.AsyncData
open class LoggedInStateProvider : PreviewParameterProvider<LoggedInState> { open class LoggedInStateProvider : PreviewParameterProvider<LoggedInState> {
override val values: Sequence<LoggedInState> override val values: Sequence<LoggedInState>
@ -31,4 +32,5 @@ fun aLoggedInState(
showSyncSpinner: Boolean = true, showSyncSpinner: Boolean = true,
) = LoggedInState( ) = LoggedInState(
showSyncSpinner = showSyncSpinner, showSyncSpinner = showSyncSpinner,
pusherRegistrationState = AsyncData.Uninitialized,
) )

24
appnav/src/main/kotlin/io/element/android/appnav/loggedin/PusherRegistrationFailure.kt

@ -0,0 +1,24 @@
/*
* Copyright (c) 2024 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 io.element.android.appnav.loggedin
sealed class PusherRegistrationFailure : Exception() {
class AccountNotVerified : PusherRegistrationFailure()
class NoProvidersAvailable : PusherRegistrationFailure()
class NoDistributorsAvailable : PusherRegistrationFailure()
class RegistrationFailure(val failure: Throwable) : PusherRegistrationFailure()
}

34
appnav/src/test/kotlin/io/element/android/appnav/loggedin/LoggedInPresenterTest.kt

@ -47,13 +47,10 @@ import io.element.android.tests.testutils.consumeItemsUntilPredicate
import io.element.android.tests.testutils.lambda.any import io.element.android.tests.testutils.lambda.any
import io.element.android.tests.testutils.lambda.lambdaRecorder import io.element.android.tests.testutils.lambda.lambdaRecorder
import io.element.android.tests.testutils.lambda.value import io.element.android.tests.testutils.lambda.value
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.advanceUntilIdle
import kotlinx.coroutines.test.runTest import kotlinx.coroutines.test.runTest
import org.junit.Rule import org.junit.Rule
import org.junit.Test import org.junit.Test
@OptIn(ExperimentalCoroutinesApi::class)
class LoggedInPresenterTest { class LoggedInPresenterTest {
@get:Rule @get:Rule
val warmUpRule = WarmUpRule() val warmUpRule = WarmUpRule()
@ -66,6 +63,8 @@ class LoggedInPresenterTest {
}.test { }.test {
val initialState = awaitItem() val initialState = awaitItem()
assertThat(initialState.showSyncSpinner).isFalse() assertThat(initialState.showSyncSpinner).isFalse()
assertThat(initialState.pusherRegistrationState.isUninitialized()).isTrue()
skipItems(1)
} }
} }
@ -106,7 +105,7 @@ class LoggedInPresenterTest {
encryptionService.emitRecoveryState(RecoveryState.INCOMPLETE) encryptionService.emitRecoveryState(RecoveryState.INCOMPLETE)
verificationService.emitVerifiedStatus(SessionVerifiedStatus.Verified) verificationService.emitVerifiedStatus(SessionVerifiedStatus.Verified)
skipItems(4) skipItems(6)
assertThat(analyticsService.capturedEvents.size).isEqualTo(1) assertThat(analyticsService.capturedEvents.size).isEqualTo(1)
assertThat(analyticsService.capturedEvents[0]).isInstanceOf(CryptoSessionStateChange::class.java) assertThat(analyticsService.capturedEvents[0]).isInstanceOf(CryptoSessionStateChange::class.java)
@ -133,6 +132,9 @@ class LoggedInPresenterTest {
presenter.present() presenter.present()
}.test { }.test {
skipItems(1) skipItems(1)
val finalState = awaitItem()
assertThat(finalState.pusherRegistrationState.errorOrNull())
.isInstanceOf(PusherRegistrationFailure.AccountNotVerified::class.java)
lambda.assertions() lambda.assertions()
.isNeverCalled() .isNeverCalled()
} }
@ -156,7 +158,8 @@ class LoggedInPresenterTest {
presenter.present() presenter.present()
}.test { }.test {
skipItems(2) skipItems(2)
advanceUntilIdle() val finalState = awaitItem()
assertThat(finalState.pusherRegistrationState.isSuccess()).isTrue()
lambda.assertions() lambda.assertions()
.isCalledOnce() .isCalledOnce()
.with( .with(
@ -188,7 +191,8 @@ class LoggedInPresenterTest {
presenter.present() presenter.present()
}.test { }.test {
skipItems(2) skipItems(2)
advanceUntilIdle() val finalState = awaitItem()
assertThat(finalState.pusherRegistrationState.isFailure()).isTrue()
lambda.assertions() lambda.assertions()
.isCalledOnce() .isCalledOnce()
.with( .with(
@ -233,7 +237,8 @@ class LoggedInPresenterTest {
presenter.present() presenter.present()
}.test { }.test {
skipItems(2) skipItems(2)
advanceUntilIdle() val finalState = awaitItem()
assertThat(finalState.pusherRegistrationState.isSuccess()).isTrue()
lambda.assertions() lambda.assertions()
.isCalledOnce() .isCalledOnce()
.with( .with(
@ -277,7 +282,8 @@ class LoggedInPresenterTest {
presenter.present() presenter.present()
}.test { }.test {
skipItems(2) skipItems(2)
advanceUntilIdle() val finalState = awaitItem()
assertThat(finalState.pusherRegistrationState.isSuccess()).isTrue()
lambda.assertions() lambda.assertions()
.isCalledOnce() .isCalledOnce()
.with( .with(
@ -317,7 +323,9 @@ class LoggedInPresenterTest {
presenter.present() presenter.present()
}.test { }.test {
skipItems(2) skipItems(2)
advanceUntilIdle() val finalState = awaitItem()
assertThat(finalState.pusherRegistrationState.errorOrNull())
.isInstanceOf(PusherRegistrationFailure.NoDistributorsAvailable::class.java)
lambda.assertions() lambda.assertions()
.isNeverCalled() .isNeverCalled()
} }
@ -343,7 +351,9 @@ class LoggedInPresenterTest {
presenter.present() presenter.present()
}.test { }.test {
skipItems(2) skipItems(2)
advanceUntilIdle() val finalState = awaitItem()
assertThat(finalState.pusherRegistrationState.errorOrNull())
.isInstanceOf(PusherRegistrationFailure.NoProvidersAvailable::class.java)
lambda.assertions() lambda.assertions()
.isNeverCalled() .isNeverCalled()
} }
@ -374,7 +384,9 @@ class LoggedInPresenterTest {
presenter.present() presenter.present()
}.test { }.test {
skipItems(2) skipItems(2)
advanceUntilIdle() val finalState = awaitItem()
assertThat(finalState.pusherRegistrationState.errorOrNull())
.isInstanceOf(PusherRegistrationFailure.NoDistributorsAvailable::class.java)
lambda.assertions() lambda.assertions()
.isNeverCalled() .isNeverCalled()
} }

Loading…
Cancel
Save