Fix navigation issue.
Ensure that the timeout has effect only in Idle state.
This commit is contained in:
parent
73e1a092d2
commit
f5e1cbef38
2 changed files with 59 additions and 57 deletions
|
|
@ -12,67 +12,60 @@ import io.element.android.features.login.impl.classic.ElementClassicConnection
|
||||||
import io.element.android.features.login.impl.classic.ElementClassicConnectionState
|
import io.element.android.features.login.impl.classic.ElementClassicConnectionState
|
||||||
import io.element.android.libraries.sessionstorage.api.SessionStore
|
import io.element.android.libraries.sessionstorage.api.SessionStore
|
||||||
import io.element.android.libraries.sessionstorage.api.toUserListFlow
|
import io.element.android.libraries.sessionstorage.api.toUserListFlow
|
||||||
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.combine
|
|
||||||
import kotlinx.coroutines.flow.distinctUntilChangedBy
|
import kotlinx.coroutines.flow.distinctUntilChangedBy
|
||||||
|
import kotlinx.coroutines.flow.first
|
||||||
|
import kotlinx.coroutines.flow.flatMapLatest
|
||||||
import kotlinx.coroutines.flow.flow
|
import kotlinx.coroutines.flow.flow
|
||||||
import kotlinx.coroutines.flow.take
|
import kotlinx.coroutines.flow.flowOf
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
class ClassicFlowNodeHelper(
|
class ClassicFlowNodeHelper(
|
||||||
private val elementClassicConnection: ElementClassicConnection,
|
private val elementClassicConnection: ElementClassicConnection,
|
||||||
private val sessionStore: SessionStore,
|
private val sessionStore: SessionStore,
|
||||||
) {
|
) {
|
||||||
// Ensure user is not stuck on the loading screen.
|
@OptIn(ExperimentalCoroutinesApi::class)
|
||||||
// If Element Classic is taking too long to communicate (or crashes), unblock the user after a few seconds.
|
|
||||||
private val timeoutFLow = flow {
|
|
||||||
emit(false)
|
|
||||||
delay(5_000)
|
|
||||||
emit(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun navigationEventFlow(): Flow<NavigationEvent> {
|
fun navigationEventFlow(): Flow<NavigationEvent> {
|
||||||
return combine(
|
return elementClassicConnection.stateFlow
|
||||||
timeoutFLow,
|
.distinctUntilChangedBy {
|
||||||
elementClassicConnection.stateFlow
|
// Ignore change on ElementClassicConnectionState.ElementClassicReady.avatar
|
||||||
.distinctUntilChangedBy {
|
if (it is ElementClassicConnectionState.ElementClassicReady) {
|
||||||
// Ignore change on ElementClassicConnectionState.ElementClassicReady.avatar
|
it.copy(avatar = null)
|
||||||
if (it is ElementClassicConnectionState.ElementClassicReady) {
|
} else {
|
||||||
it.copy(avatar = null)
|
it
|
||||||
} else {
|
|
||||||
it
|
|
||||||
}
|
|
||||||
},
|
|
||||||
sessionStore.sessionsFlow().toUserListFlow()
|
|
||||||
// Take only 1 emission of the sessions, else when the user actually logged in it will trigger a navigation to OnBoarding.
|
|
||||||
.take(1),
|
|
||||||
) { timeout, elementClassicConnectionState, existingSessions ->
|
|
||||||
when (elementClassicConnectionState) {
|
|
||||||
ElementClassicConnectionState.Idle -> {
|
|
||||||
if (timeout) {
|
|
||||||
NavigationEvent.NavigateToOnBoarding
|
|
||||||
} else {
|
|
||||||
NavigationEvent.Idle
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
ElementClassicConnectionState.ElementClassicNotFound,
|
}
|
||||||
ElementClassicConnectionState.ElementClassicReadyNoSession,
|
.flatMapLatest { elementClassicConnectionState ->
|
||||||
is ElementClassicConnectionState.Error -> {
|
when (elementClassicConnectionState) {
|
||||||
NavigationEvent.NavigateToOnBoarding
|
ElementClassicConnectionState.Idle -> {
|
||||||
}
|
// Ensure user is not stuck on the loading screen.
|
||||||
is ElementClassicConnectionState.ElementClassicReady -> {
|
// If Element Classic is taking too long to communicate (or crashes), unblock the user after a few seconds.
|
||||||
if (elementClassicConnectionState.elementClassicSession.userId.value in existingSessions) {
|
flow {
|
||||||
NavigationEvent.NavigateToOnBoarding
|
emit(NavigationEvent.Idle)
|
||||||
} else {
|
delay(5_000)
|
||||||
// 2 cases when this can be run:
|
emit(NavigationEvent.NavigateToOnBoarding)
|
||||||
// First time this screen will be displayed
|
}
|
||||||
// Missing key backup screen was displayed, but the data has changed (user set up the key backup on Classic),
|
}
|
||||||
// and the app is resuming.
|
ElementClassicConnectionState.ElementClassicNotFound,
|
||||||
NavigationEvent.NavigateToLoginWithClassic(elementClassicConnectionState.elementClassicSession.userId)
|
ElementClassicConnectionState.ElementClassicReadyNoSession,
|
||||||
|
is ElementClassicConnectionState.Error -> {
|
||||||
|
flowOf(NavigationEvent.NavigateToOnBoarding)
|
||||||
|
}
|
||||||
|
is ElementClassicConnectionState.ElementClassicReady -> {
|
||||||
|
val existingSessions = sessionStore.sessionsFlow().toUserListFlow().first()
|
||||||
|
if (elementClassicConnectionState.elementClassicSession.userId.value in existingSessions) {
|
||||||
|
flowOf(NavigationEvent.NavigateToOnBoarding)
|
||||||
|
} else {
|
||||||
|
// 2 cases when this can be run:
|
||||||
|
// First time this screen will be displayed
|
||||||
|
// Missing key backup screen was displayed, but the data has changed (user set up the key backup on Classic),
|
||||||
|
// and the app is resuming.
|
||||||
|
flowOf(NavigationEvent.NavigateToLoginWithClassic(elementClassicConnectionState.elementClassicSession.userId))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,7 @@ import io.element.android.libraries.sessionstorage.test.InMemorySessionStore
|
||||||
import io.element.android.libraries.sessionstorage.test.aSessionData
|
import io.element.android.libraries.sessionstorage.test.aSessionData
|
||||||
import io.element.android.tests.testutils.WarmUpRule
|
import io.element.android.tests.testutils.WarmUpRule
|
||||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
|
import kotlinx.coroutines.test.advanceTimeBy
|
||||||
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
|
||||||
|
|
@ -38,16 +39,6 @@ class ClassicFlowNodeHelperTest {
|
||||||
@get:Rule
|
@get:Rule
|
||||||
val warmUpRule = WarmUpRule()
|
val warmUpRule = WarmUpRule()
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `initial state`() = runTest {
|
|
||||||
createHelper()
|
|
||||||
.navigationEventFlow()
|
|
||||||
.test {
|
|
||||||
val initialState = awaitItem()
|
|
||||||
assertThat(initialState).isEqualTo(NavigationEvent.Idle)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `after a few seconds in Idle, NavigateToOnBoarding is emitted`() = runTest {
|
fun `after a few seconds in Idle, NavigateToOnBoarding is emitted`() = runTest {
|
||||||
createHelper()
|
createHelper()
|
||||||
|
|
@ -57,6 +48,8 @@ class ClassicFlowNodeHelperTest {
|
||||||
assertThat(initialState).isEqualTo(NavigationEvent.Idle)
|
assertThat(initialState).isEqualTo(NavigationEvent.Idle)
|
||||||
val finalState = awaitItem()
|
val finalState = awaitItem()
|
||||||
assertThat(finalState).isEqualTo(NavigationEvent.NavigateToOnBoarding)
|
assertThat(finalState).isEqualTo(NavigationEvent.NavigateToOnBoarding)
|
||||||
|
advanceTimeBy(10_000)
|
||||||
|
expectNoEvents()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -82,6 +75,8 @@ class ClassicFlowNodeHelperTest {
|
||||||
)
|
)
|
||||||
val finalState = awaitItem()
|
val finalState = awaitItem()
|
||||||
assertThat(finalState).isEqualTo(NavigationEvent.NavigateToOnBoarding)
|
assertThat(finalState).isEqualTo(NavigationEvent.NavigateToOnBoarding)
|
||||||
|
advanceTimeBy(10_000)
|
||||||
|
expectNoEvents()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -100,6 +95,8 @@ class ClassicFlowNodeHelperTest {
|
||||||
)
|
)
|
||||||
val finalState = awaitItem()
|
val finalState = awaitItem()
|
||||||
assertThat(finalState).isEqualTo(NavigationEvent.NavigateToOnBoarding)
|
assertThat(finalState).isEqualTo(NavigationEvent.NavigateToOnBoarding)
|
||||||
|
advanceTimeBy(10_000)
|
||||||
|
expectNoEvents()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -118,6 +115,8 @@ class ClassicFlowNodeHelperTest {
|
||||||
)
|
)
|
||||||
val finalState = awaitItem()
|
val finalState = awaitItem()
|
||||||
assertThat(finalState).isEqualTo(NavigationEvent.NavigateToOnBoarding)
|
assertThat(finalState).isEqualTo(NavigationEvent.NavigateToOnBoarding)
|
||||||
|
advanceTimeBy(10_000)
|
||||||
|
expectNoEvents()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -136,6 +135,8 @@ class ClassicFlowNodeHelperTest {
|
||||||
)
|
)
|
||||||
val finalState = awaitItem()
|
val finalState = awaitItem()
|
||||||
assertThat(finalState).isEqualTo(NavigationEvent.NavigateToOnBoarding)
|
assertThat(finalState).isEqualTo(NavigationEvent.NavigateToOnBoarding)
|
||||||
|
advanceTimeBy(10_000)
|
||||||
|
expectNoEvents()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -154,6 +155,8 @@ class ClassicFlowNodeHelperTest {
|
||||||
)
|
)
|
||||||
val finalState = awaitItem()
|
val finalState = awaitItem()
|
||||||
assertThat(finalState).isEqualTo(NavigationEvent.NavigateToLoginWithClassic(A_USER_ID))
|
assertThat(finalState).isEqualTo(NavigationEvent.NavigateToLoginWithClassic(A_USER_ID))
|
||||||
|
advanceTimeBy(10_000)
|
||||||
|
expectNoEvents()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -178,6 +181,7 @@ class ClassicFlowNodeHelperTest {
|
||||||
avatar = createBitmap(1, 1)
|
avatar = createBitmap(1, 1)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
advanceTimeBy(10_000)
|
||||||
expectNoEvents()
|
expectNoEvents()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -211,6 +215,8 @@ class ClassicFlowNodeHelperTest {
|
||||||
)
|
)
|
||||||
val finalState = awaitItem()
|
val finalState = awaitItem()
|
||||||
assertThat(finalState).isEqualTo(NavigationEvent.NavigateToLoginWithClassic(A_USER_ID))
|
assertThat(finalState).isEqualTo(NavigationEvent.NavigateToLoginWithClassic(A_USER_ID))
|
||||||
|
advanceTimeBy(10_000)
|
||||||
|
expectNoEvents()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -236,6 +242,8 @@ class ClassicFlowNodeHelperTest {
|
||||||
)
|
)
|
||||||
val finalState = awaitItem()
|
val finalState = awaitItem()
|
||||||
assertThat(finalState).isEqualTo(NavigationEvent.NavigateToLoginWithClassic(A_USER_ID))
|
assertThat(finalState).isEqualTo(NavigationEvent.NavigateToLoginWithClassic(A_USER_ID))
|
||||||
|
advanceTimeBy(10_000)
|
||||||
|
expectNoEvents()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -264,6 +272,7 @@ class ClassicFlowNodeHelperTest {
|
||||||
sessionId = A_USER_ID.value,
|
sessionId = A_USER_ID.value,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
advanceTimeBy(10_000)
|
||||||
expectNoEvents()
|
expectNoEvents()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue