Merge branch 'develop' into feature/fga/waiting_ss_room
This commit is contained in:
commit
10c2859fac
249 changed files with 3147 additions and 677 deletions
|
|
@ -18,6 +18,7 @@ package io.element.android.appnav
|
|||
|
||||
import com.bumble.appyx.navmodel.backstack.BackStack
|
||||
import com.bumble.appyx.navmodel.backstack.operation.NewRoot
|
||||
import com.bumble.appyx.navmodel.backstack.operation.Remove
|
||||
|
||||
/**
|
||||
* Don't process NewRoot if the nav target already exists in the stack.
|
||||
|
|
@ -29,3 +30,14 @@ fun <T : Any> BackStack<T>.safeRoot(element: T) {
|
|||
if (containsRoot) return
|
||||
accept(NewRoot(element))
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the last element on the backstack equals to the given one.
|
||||
*/
|
||||
fun <T : Any> BackStack<T>.removeLast(element: T) {
|
||||
val lastExpectedNavElement = elements.value.lastOrNull {
|
||||
it.key.navTarget == element
|
||||
} ?: return
|
||||
accept(Remove(lastExpectedNavElement.key))
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -58,7 +58,6 @@ import io.element.android.libraries.architecture.animation.rememberDefaultTransi
|
|||
import io.element.android.libraries.architecture.bindings
|
||||
import io.element.android.libraries.architecture.createNode
|
||||
import io.element.android.libraries.architecture.inputs
|
||||
import io.element.android.libraries.designsystem.theme.components.CircularProgressIndicator
|
||||
import io.element.android.libraries.designsystem.utils.SnackbarDispatcher
|
||||
import io.element.android.libraries.di.AppScope
|
||||
import io.element.android.libraries.matrix.api.MatrixClient
|
||||
|
|
@ -93,7 +92,7 @@ class LoggedInFlowNode @AssistedInject constructor(
|
|||
snackbarDispatcher: SnackbarDispatcher,
|
||||
) : BackstackNode<LoggedInFlowNode.NavTarget>(
|
||||
backstack = BackStack(
|
||||
initialElement = NavTarget.SplashScreen,
|
||||
initialElement = NavTarget.RoomList,
|
||||
savedStateMap = buildContext.savedStateMap,
|
||||
),
|
||||
buildContext = buildContext,
|
||||
|
|
@ -105,22 +104,14 @@ class LoggedInFlowNode @AssistedInject constructor(
|
|||
.distinctUntilChanged()
|
||||
.onEach { isConsentAsked ->
|
||||
if (isConsentAsked) {
|
||||
switchToRoomList()
|
||||
backstack.removeLast(NavTarget.AnalyticsOptIn)
|
||||
} else {
|
||||
switchToAnalytics()
|
||||
backstack.push(NavTarget.AnalyticsOptIn)
|
||||
}
|
||||
}
|
||||
.launchIn(lifecycleScope)
|
||||
}
|
||||
|
||||
private fun switchToRoomList() {
|
||||
backstack.safeRoot(NavTarget.RoomList)
|
||||
}
|
||||
|
||||
private fun switchToAnalytics() {
|
||||
backstack.safeRoot(NavTarget.AnalyticsSettings)
|
||||
}
|
||||
|
||||
interface Callback : Plugin {
|
||||
fun onOpenBugReport() = Unit
|
||||
}
|
||||
|
|
@ -196,9 +187,6 @@ class LoggedInFlowNode @AssistedInject constructor(
|
|||
}
|
||||
|
||||
sealed interface NavTarget : Parcelable {
|
||||
@Parcelize
|
||||
object SplashScreen : NavTarget
|
||||
|
||||
@Parcelize
|
||||
object Permanent : NavTarget
|
||||
|
||||
|
|
@ -224,12 +212,11 @@ class LoggedInFlowNode @AssistedInject constructor(
|
|||
object InviteList : NavTarget
|
||||
|
||||
@Parcelize
|
||||
object AnalyticsSettings : NavTarget
|
||||
object AnalyticsOptIn : NavTarget
|
||||
}
|
||||
|
||||
override fun resolve(navTarget: NavTarget, buildContext: BuildContext): Node {
|
||||
return when (navTarget) {
|
||||
NavTarget.SplashScreen -> splashNode(buildContext)
|
||||
NavTarget.Permanent -> {
|
||||
createNode<LoggedInNode>(buildContext)
|
||||
}
|
||||
|
|
@ -322,7 +309,7 @@ class LoggedInFlowNode @AssistedInject constructor(
|
|||
.callback(callback)
|
||||
.build()
|
||||
}
|
||||
NavTarget.AnalyticsSettings -> {
|
||||
NavTarget.AnalyticsOptIn -> {
|
||||
analyticsOptInEntryPoint.createNode(this, buildContext)
|
||||
}
|
||||
}
|
||||
|
|
@ -341,12 +328,6 @@ class LoggedInFlowNode @AssistedInject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private fun splashNode(buildContext: BuildContext) = node(buildContext) {
|
||||
Box(modifier = it.fillMaxSize(), contentAlignment = Alignment.Center) {
|
||||
CircularProgressIndicator()
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
override fun View(modifier: Modifier) {
|
||||
Box(modifier = modifier) {
|
||||
|
|
|
|||
|
|
@ -31,7 +31,6 @@ import com.bumble.appyx.core.node.node
|
|||
import com.bumble.appyx.core.plugin.Plugin
|
||||
import com.bumble.appyx.core.plugin.plugins
|
||||
import com.bumble.appyx.navmodel.backstack.BackStack
|
||||
import com.bumble.appyx.navmodel.backstack.operation.newRoot
|
||||
import com.bumble.appyx.navmodel.backstack.operation.pop
|
||||
import com.bumble.appyx.navmodel.backstack.operation.push
|
||||
import dagger.assisted.Assisted
|
||||
|
|
@ -42,6 +41,7 @@ import io.element.android.appnav.intent.IntentResolver
|
|||
import io.element.android.appnav.intent.ResolvedIntent
|
||||
import io.element.android.appnav.root.RootPresenter
|
||||
import io.element.android.appnav.root.RootView
|
||||
import io.element.android.features.login.api.LoginUserStory
|
||||
import io.element.android.features.login.api.oidc.OidcAction
|
||||
import io.element.android.features.login.api.oidc.OidcActionFlow
|
||||
import io.element.android.features.preferences.api.CacheService
|
||||
|
|
@ -49,19 +49,23 @@ import io.element.android.features.rageshake.api.bugreport.BugReportEntryPoint
|
|||
import io.element.android.libraries.architecture.BackstackNode
|
||||
import io.element.android.libraries.architecture.animation.rememberDefaultTransitionHandler
|
||||
import io.element.android.libraries.architecture.createNode
|
||||
import io.element.android.libraries.architecture.waitForChildAttached
|
||||
import io.element.android.libraries.deeplink.DeeplinkData
|
||||
import io.element.android.libraries.designsystem.theme.components.CircularProgressIndicator
|
||||
import io.element.android.libraries.di.AppScope
|
||||
import io.element.android.libraries.matrix.api.auth.MatrixAuthenticationService
|
||||
import io.element.android.libraries.matrix.api.core.SessionId
|
||||
import io.element.android.libraries.matrix.api.core.UserId
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.first
|
||||
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.flow.onStart
|
||||
import kotlinx.parcelize.Parcelize
|
||||
import timber.log.Timber
|
||||
import java.util.UUID
|
||||
|
||||
@ContributesNode(AppScope::class)
|
||||
class RootFlowNode @AssistedInject constructor(
|
||||
|
|
@ -74,6 +78,7 @@ class RootFlowNode @AssistedInject constructor(
|
|||
private val bugReportEntryPoint: BugReportEntryPoint,
|
||||
private val intentResolver: IntentResolver,
|
||||
private val oidcActionFlow: OidcActionFlow,
|
||||
private val loginUserStory: LoginUserStory,
|
||||
) :
|
||||
BackstackNode<RootFlowNode.NavTarget>(
|
||||
backstack = BackStack(
|
||||
|
|
@ -90,21 +95,15 @@ class RootFlowNode @AssistedInject constructor(
|
|||
}
|
||||
|
||||
private fun observeLoggedInState() {
|
||||
authenticationService.isLoggedIn()
|
||||
.distinctUntilChanged()
|
||||
.combine(
|
||||
cacheService.cacheIndex().onEach {
|
||||
Timber.v("cacheIndex=$it")
|
||||
matrixClientsHolder.removeAll()
|
||||
}
|
||||
) { isLoggedIn, cacheIdx -> isLoggedIn to cacheIdx }
|
||||
.onEach { pair ->
|
||||
val isLoggedIn = pair.first
|
||||
val cacheIndex = pair.second
|
||||
Timber.v("isLoggedIn=$isLoggedIn, cacheIndex=$cacheIndex")
|
||||
combine(
|
||||
cacheService.onClearedCacheEventFlow(),
|
||||
isUserLoggedInFlow(),
|
||||
) { _, isLoggedIn -> isLoggedIn }
|
||||
.onEach { isLoggedIn ->
|
||||
Timber.v("isLoggedIn=$isLoggedIn")
|
||||
if (isLoggedIn) {
|
||||
tryToRestoreLatestSession(
|
||||
onSuccess = { switchToLoggedInFlow(it, cacheIndex) },
|
||||
onSuccess = { switchToLoggedInFlow(it) },
|
||||
onFailure = { switchToNotLoggedInFlow() }
|
||||
)
|
||||
} else {
|
||||
|
|
@ -114,8 +113,19 @@ class RootFlowNode @AssistedInject constructor(
|
|||
.launchIn(lifecycleScope)
|
||||
}
|
||||
|
||||
private fun switchToLoggedInFlow(sessionId: SessionId, cacheIndex: Int) {
|
||||
backstack.safeRoot(NavTarget.LoggedInFlow(sessionId, cacheIndex))
|
||||
|
||||
private fun switchToLoggedInFlow(sessionId: SessionId) {
|
||||
backstack.safeRoot(NavTarget.LoggedInFlow(sessionId))
|
||||
}
|
||||
|
||||
private fun isUserLoggedInFlow(): Flow<Boolean> {
|
||||
return combine(
|
||||
authenticationService.isLoggedIn(),
|
||||
loginUserStory.loginFlowIsDone
|
||||
) { isLoggedIn, loginFlowIsDone ->
|
||||
isLoggedIn && loginFlowIsDone
|
||||
}
|
||||
.distinctUntilChanged()
|
||||
}
|
||||
|
||||
private fun switchToNotLoggedInFlow() {
|
||||
|
|
@ -123,28 +133,38 @@ class RootFlowNode @AssistedInject constructor(
|
|||
backstack.safeRoot(NavTarget.NotLoggedInFlow)
|
||||
}
|
||||
|
||||
private suspend fun restoreSessionIfNeeded(
|
||||
sessionId: SessionId,
|
||||
onFailure: () -> Unit = {},
|
||||
onSuccess: (SessionId) -> Unit = {},
|
||||
) {
|
||||
// If the session is already known it'll be restored by the node hierarchy
|
||||
if (matrixClientsHolder.knowSession(sessionId)) {
|
||||
Timber.v("Session $sessionId already alive, no need to restore.")
|
||||
return
|
||||
}
|
||||
authenticationService.restoreSession(sessionId)
|
||||
.onSuccess { matrixClient ->
|
||||
matrixClientsHolder.add(matrixClient)
|
||||
Timber.v("Succeed to restore session $sessionId")
|
||||
onSuccess(sessionId)
|
||||
}
|
||||
.onFailure {
|
||||
Timber.v("Failed to restore session $sessionId")
|
||||
onFailure()
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun tryToRestoreLatestSession(
|
||||
onSuccess: (UserId) -> Unit = {},
|
||||
onSuccess: (SessionId) -> Unit = {},
|
||||
onFailure: () -> Unit = {}
|
||||
) {
|
||||
val latestKnownUserId = authenticationService.getLatestSessionId()
|
||||
if (latestKnownUserId == null) {
|
||||
val latestSessionId = authenticationService.getLatestSessionId()
|
||||
if (latestSessionId == null) {
|
||||
onFailure()
|
||||
return
|
||||
}
|
||||
if (matrixClientsHolder.knowSession(latestKnownUserId)) {
|
||||
onSuccess(latestKnownUserId)
|
||||
return
|
||||
}
|
||||
authenticationService.restoreSession(UserId(latestKnownUserId.value))
|
||||
.onSuccess { matrixClient ->
|
||||
matrixClientsHolder.add(matrixClient)
|
||||
onSuccess(matrixClient.sessionId)
|
||||
}
|
||||
.onFailure {
|
||||
Timber.v("Failed to restore session...")
|
||||
onFailure()
|
||||
}
|
||||
restoreSessionIfNeeded(latestSessionId, onFailure, onSuccess)
|
||||
}
|
||||
|
||||
private fun onOpenBugReport() {
|
||||
|
|
@ -175,7 +195,10 @@ class RootFlowNode @AssistedInject constructor(
|
|||
object NotLoggedInFlow : NavTarget
|
||||
|
||||
@Parcelize
|
||||
data class LoggedInFlow(val sessionId: SessionId, val cacheIndex: Int) : NavTarget
|
||||
data class LoggedInFlow(
|
||||
val sessionId: SessionId,
|
||||
val navId: UUID = UUID.randomUUID(),
|
||||
) : NavTarget
|
||||
|
||||
@Parcelize
|
||||
object BugReport : NavTarget
|
||||
|
|
@ -186,7 +209,6 @@ class RootFlowNode @AssistedInject constructor(
|
|||
is NavTarget.LoggedInFlow -> {
|
||||
val matrixClient = matrixClientsHolder.getOrNull(navTarget.sessionId) ?: return splashNode(buildContext).also {
|
||||
Timber.w("Couldn't find any session, go through SplashScreen")
|
||||
backstack.newRoot(NavTarget.SplashScreen)
|
||||
}
|
||||
val inputs = LoggedInFlowNode.Inputs(matrixClient)
|
||||
val callback = object : LoggedInFlowNode.Callback {
|
||||
|
|
@ -247,9 +269,16 @@ class RootFlowNode @AssistedInject constructor(
|
|||
}
|
||||
|
||||
private suspend fun attachSession(sessionId: SessionId): LoggedInFlowNode {
|
||||
val cacheIndex = cacheService.cacheIndex().first()
|
||||
return attachChild {
|
||||
backstack.newRoot(NavTarget.LoggedInFlow(sessionId, cacheIndex))
|
||||
//TODO handle multi-session
|
||||
return waitForChildAttached { navTarget ->
|
||||
navTarget is NavTarget.LoggedInFlow && navTarget.sessionId == sessionId
|
||||
}
|
||||
}
|
||||
|
||||
private fun CacheService.onClearedCacheEventFlow(): Flow<Unit> {
|
||||
return clearedCacheEventFlow
|
||||
.onEach { sessionId -> matrixClientsHolder.remove(sessionId) }
|
||||
.map { }
|
||||
.onStart { emit((Unit)) }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue