Skip to content

Commit 7cefff8

Browse files
committed
Merge branch 'develop' into feature/fga/update-rust-sdk-0.1.32
2 parents 9b96bd4 + 4b124e9 commit 7cefff8

File tree

69 files changed

+713
-304
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

69 files changed

+713
-304
lines changed

appnav/src/main/kotlin/io/element/android/appnav/RootFlowNode.kt

Lines changed: 19 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -40,12 +40,11 @@ import io.element.android.anvilannotations.ContributesNode
4040
import io.element.android.appnav.di.MatrixClientsHolder
4141
import io.element.android.appnav.intent.IntentResolver
4242
import io.element.android.appnav.intent.ResolvedIntent
43+
import io.element.android.appnav.root.RootNavStateFlowFactory
4344
import io.element.android.appnav.root.RootPresenter
4445
import io.element.android.appnav.root.RootView
45-
import io.element.android.features.login.api.LoginUserStory
4646
import io.element.android.features.login.api.oidc.OidcAction
4747
import io.element.android.features.login.api.oidc.OidcActionFlow
48-
import io.element.android.features.preferences.api.CacheService
4948
import io.element.android.features.rageshake.api.bugreport.BugReportEntryPoint
5049
import io.element.android.libraries.architecture.BackstackNode
5150
import io.element.android.libraries.architecture.animation.rememberDefaultTransitionHandler
@@ -57,29 +56,22 @@ import io.element.android.libraries.di.AppScope
5756
import io.element.android.libraries.matrix.api.auth.MatrixAuthenticationService
5857
import io.element.android.libraries.matrix.api.core.SessionId
5958
import kotlinx.coroutines.flow.distinctUntilChanged
60-
61-
import kotlinx.coroutines.flow.Flow
62-
import kotlinx.coroutines.flow.combine
6359
import kotlinx.coroutines.flow.launchIn
64-
import kotlinx.coroutines.flow.map
6560
import kotlinx.coroutines.flow.onEach
66-
import kotlinx.coroutines.flow.onStart
6761
import kotlinx.parcelize.Parcelize
6862
import timber.log.Timber
69-
import java.util.UUID
7063

7164
@ContributesNode(AppScope::class)
7265
class RootFlowNode @AssistedInject constructor(
7366
@Assisted val buildContext: BuildContext,
7467
@Assisted plugins: List<Plugin>,
7568
private val authenticationService: MatrixAuthenticationService,
76-
private val cacheService: CacheService,
69+
private val navStateFlowFactory: RootNavStateFlowFactory,
7770
private val matrixClientsHolder: MatrixClientsHolder,
7871
private val presenter: RootPresenter,
7972
private val bugReportEntryPoint: BugReportEntryPoint,
8073
private val intentResolver: IntentResolver,
8174
private val oidcActionFlow: OidcActionFlow,
82-
private val loginUserStory: LoginUserStory,
8375
) :
8476
BackstackNode<RootFlowNode.NavTarget>(
8577
backstack = BackStack(
@@ -91,26 +83,25 @@ class RootFlowNode @AssistedInject constructor(
9183
) {
9284

9385
override fun onBuilt() {
94-
matrixClientsHolder.restore(buildContext.savedStateMap)
86+
matrixClientsHolder.restoreWithSavedState(buildContext.savedStateMap)
9587
super.onBuilt()
96-
observeLoggedInState()
88+
observeNavState()
9789
}
9890

9991
override fun onSaveInstanceState(state: MutableSavedStateMap) {
10092
super.onSaveInstanceState(state)
101-
matrixClientsHolder.save(state)
93+
matrixClientsHolder.saveIntoSavedState(state)
94+
navStateFlowFactory.saveIntoSavedState(state)
10295
}
10396

104-
private fun observeLoggedInState() {
105-
combine(
106-
cacheService.onClearedCacheEventFlow(),
107-
isUserLoggedInFlow(),
108-
) { _, isLoggedIn -> isLoggedIn }
109-
.onEach { isLoggedIn ->
110-
Timber.v("isLoggedIn=$isLoggedIn")
111-
if (isLoggedIn) {
97+
private fun observeNavState() {
98+
navStateFlowFactory.create(buildContext.savedStateMap)
99+
.distinctUntilChanged()
100+
.onEach { navState ->
101+
Timber.v("navState=$navState")
102+
if (navState.isLoggedIn) {
112103
tryToRestoreLatestSession(
113-
onSuccess = { switchToLoggedInFlow(it) },
104+
onSuccess = { sessionId -> switchToLoggedInFlow(sessionId, navState.cacheIndex) },
114105
onFailure = { switchToNotLoggedInFlow() }
115106
)
116107
} else {
@@ -120,19 +111,8 @@ class RootFlowNode @AssistedInject constructor(
120111
.launchIn(lifecycleScope)
121112
}
122113

123-
124-
private fun switchToLoggedInFlow(sessionId: SessionId) {
125-
backstack.safeRoot(NavTarget.LoggedInFlow(sessionId))
126-
}
127-
128-
private fun isUserLoggedInFlow(): Flow<Boolean> {
129-
return combine(
130-
authenticationService.isLoggedIn(),
131-
loginUserStory.loginFlowIsDone
132-
) { isLoggedIn, loginFlowIsDone ->
133-
isLoggedIn && loginFlowIsDone
134-
}
135-
.distinctUntilChanged()
114+
private fun switchToLoggedInFlow(sessionId: SessionId, navId: Int) {
115+
backstack.safeRoot(NavTarget.LoggedInFlow(sessionId, navId))
136116
}
137117

138118
private fun switchToNotLoggedInFlow() {
@@ -145,14 +125,8 @@ class RootFlowNode @AssistedInject constructor(
145125
onFailure: () -> Unit = {},
146126
onSuccess: (SessionId) -> Unit = {},
147127
) {
148-
// If the session is already known it'll be restored by the node hierarchy
149-
if (matrixClientsHolder.knowSession(sessionId)) {
150-
Timber.v("Session $sessionId already alive, no need to restore.")
151-
return
152-
}
153-
authenticationService.restoreSession(sessionId)
154-
.onSuccess { matrixClient ->
155-
matrixClientsHolder.add(matrixClient)
128+
matrixClientsHolder.getOrRestore(sessionId)
129+
.onSuccess {
156130
Timber.v("Succeed to restore session $sessionId")
157131
onSuccess(sessionId)
158132
}
@@ -204,7 +178,7 @@ class RootFlowNode @AssistedInject constructor(
204178
@Parcelize
205179
data class LoggedInFlow(
206180
val sessionId: SessionId,
207-
val navId: UUID = UUID.randomUUID(),
181+
val navId: Int
208182
) : NavTarget
209183

210184
@Parcelize
@@ -278,11 +252,5 @@ class RootFlowNode @AssistedInject constructor(
278252
navTarget is NavTarget.LoggedInFlow && navTarget.sessionId == sessionId
279253
}
280254
}
281-
282-
private fun CacheService.onClearedCacheEventFlow(): Flow<Unit> {
283-
return clearedCacheEventFlow
284-
.onEach { sessionId -> matrixClientsHolder.remove(sessionId) }
285-
.map { }
286-
.onStart { emit((Unit)) }
287-
}
288255
}
256+

appnav/src/main/kotlin/io/element/android/appnav/di/MatrixClientsHolder.kt

Lines changed: 33 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -18,23 +18,28 @@ package io.element.android.appnav.di
1818

1919
import com.bumble.appyx.core.state.MutableSavedStateMap
2020
import com.bumble.appyx.core.state.SavedStateMap
21+
import com.squareup.anvil.annotations.ContributesBinding
22+
import io.element.android.libraries.di.AppScope
23+
import io.element.android.libraries.di.SingleIn
2124
import io.element.android.libraries.matrix.api.MatrixClient
25+
import io.element.android.libraries.matrix.api.MatrixClientProvider
2226
import io.element.android.libraries.matrix.api.auth.MatrixAuthenticationService
2327
import io.element.android.libraries.matrix.api.core.SessionId
2428
import kotlinx.coroutines.runBlocking
29+
import kotlinx.coroutines.sync.Mutex
30+
import kotlinx.coroutines.sync.withLock
2531
import timber.log.Timber
2632
import java.util.concurrent.ConcurrentHashMap
2733
import javax.inject.Inject
2834

2935
private const val SAVE_INSTANCE_KEY = "io.element.android.x.di.MatrixClientsHolder.SaveInstanceKey"
3036

31-
class MatrixClientsHolder @Inject constructor(private val authenticationService: MatrixAuthenticationService) {
37+
@SingleIn(AppScope::class)
38+
@ContributesBinding(AppScope::class)
39+
class MatrixClientsHolder @Inject constructor(private val authenticationService: MatrixAuthenticationService) : MatrixClientProvider {
3240

3341
private val sessionIdsToMatrixClient = ConcurrentHashMap<SessionId, MatrixClient>()
34-
35-
fun add(matrixClient: MatrixClient) {
36-
sessionIdsToMatrixClient[matrixClient.sessionId] = matrixClient
37-
}
42+
private val restoreMutex = Mutex()
3843

3944
fun removeAll() {
4045
sessionIdsToMatrixClient.clear()
@@ -44,16 +49,21 @@ class MatrixClientsHolder @Inject constructor(private val authenticationService:
4449
sessionIdsToMatrixClient.remove(sessionId)
4550
}
4651

47-
fun isEmpty(): Boolean = sessionIdsToMatrixClient.isEmpty()
48-
49-
fun knowSession(sessionId: SessionId): Boolean = sessionIdsToMatrixClient.containsKey(sessionId)
50-
5152
fun getOrNull(sessionId: SessionId): MatrixClient? {
5253
return sessionIdsToMatrixClient[sessionId]
5354
}
5455

56+
override suspend fun getOrRestore(sessionId: SessionId): Result<MatrixClient> {
57+
return restoreMutex.withLock {
58+
when (val matrixClient = getOrNull(sessionId)) {
59+
null -> restore(sessionId)
60+
else -> Result.success(matrixClient)
61+
}
62+
}
63+
}
64+
5565
@Suppress("UNCHECKED_CAST")
56-
fun restore(state: SavedStateMap?) {
66+
fun restoreWithSavedState(state: SavedStateMap?) {
5767
Timber.d("Restore state")
5868
if (state == null || sessionIdsToMatrixClient.isNotEmpty()) return Unit.also {
5969
Timber.w("Restore with non-empty map")
@@ -64,21 +74,25 @@ class MatrixClientsHolder @Inject constructor(private val authenticationService:
6474
// Not ideal but should only happens in case of process recreation. This ensure we restore all the active sessions before restoring the node graphs.
6575
runBlocking {
6676
sessionIds.forEach { sessionId ->
67-
Timber.d("Restore matrix session: $sessionId")
68-
authenticationService.restoreSession(sessionId)
69-
.onSuccess { matrixClient ->
70-
add(matrixClient)
71-
}
72-
.onFailure {
73-
Timber.e("Fail to restore session")
74-
}
77+
restore(sessionId)
7578
}
7679
}
7780
}
7881

79-
fun save(state: MutableSavedStateMap) {
82+
fun saveIntoSavedState(state: MutableSavedStateMap) {
8083
val sessionKeys = sessionIdsToMatrixClient.keys.toTypedArray()
8184
Timber.d("Save matrix session keys = ${sessionKeys.map { it.value }}")
8285
state[SAVE_INSTANCE_KEY] = sessionKeys
8386
}
87+
88+
private suspend fun restore(sessionId: SessionId): Result<MatrixClient> {
89+
Timber.d("Restore matrix session: $sessionId")
90+
return authenticationService.restoreSession(sessionId)
91+
.onSuccess { matrixClient ->
92+
sessionIdsToMatrixClient[matrixClient.sessionId] = matrixClient
93+
}
94+
.onFailure {
95+
Timber.e("Fail to restore session")
96+
}
97+
}
8498
}

appnav/src/main/kotlin/io/element/android/appnav/room/RoomFlowNode.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,11 +32,11 @@ import com.bumble.appyx.core.node.node
3232
import com.bumble.appyx.core.plugin.Plugin
3333
import com.bumble.appyx.core.plugin.plugins
3434
import com.bumble.appyx.navmodel.backstack.BackStack
35+
import com.bumble.appyx.navmodel.backstack.operation.newRoot
3536
import dagger.assisted.Assisted
3637
import dagger.assisted.AssistedInject
3738
import io.element.android.anvilannotations.ContributesNode
3839
import io.element.android.appnav.NodeLifecycleCallback
39-
import io.element.android.appnav.safeRoot
4040
import io.element.android.features.networkmonitor.api.NetworkMonitor
4141
import io.element.android.features.networkmonitor.api.NetworkStatus
4242
import io.element.android.libraries.architecture.BackstackNode
@@ -92,9 +92,9 @@ class RoomFlowNode @AssistedInject constructor(
9292
.distinctUntilChanged()
9393
.onEach { isLoaded ->
9494
if (isLoaded) {
95-
backstack.safeRoot(NavTarget.Loaded)
95+
backstack.newRoot(NavTarget.Loaded)
9696
} else {
97-
backstack.safeRoot(NavTarget.Loading)
97+
backstack.newRoot(NavTarget.Loading)
9898
}
9999
}.launchIn(lifecycleScope)
100100
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
/*
2+
* Copyright (c) 2023 New Vector Ltd
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package io.element.android.appnav.root
18+
19+
/**
20+
* [RootNavState] produced by [RootNavStateFlowFactory].
21+
*/
22+
data class RootNavState(
23+
/**
24+
* This value is incremented when a clear cache is done.
25+
* Can be useful to track to force ui state to re-render
26+
*/
27+
val cacheIndex: Int,
28+
/**
29+
* true if we are currently loggedIn.
30+
*/
31+
val isLoggedIn: Boolean
32+
)
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
/*
2+
* Copyright (c) 2023 New Vector Ltd
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package io.element.android.appnav.root
18+
19+
import com.bumble.appyx.core.state.MutableSavedStateMap
20+
import com.bumble.appyx.core.state.SavedStateMap
21+
import io.element.android.appnav.di.MatrixClientsHolder
22+
import io.element.android.features.login.api.LoginUserStory
23+
import io.element.android.features.preferences.api.CacheService
24+
import io.element.android.libraries.matrix.api.auth.MatrixAuthenticationService
25+
import kotlinx.coroutines.flow.Flow
26+
import kotlinx.coroutines.flow.combine
27+
import kotlinx.coroutines.flow.distinctUntilChanged
28+
import kotlinx.coroutines.flow.flow
29+
import kotlinx.coroutines.flow.onEach
30+
import javax.inject.Inject
31+
32+
private const val SAVE_INSTANCE_KEY = "io.element.android.x.RootNavStateFlowFactory.SAVE_INSTANCE_KEY"
33+
34+
/**
35+
* This class is responsible for creating a flow of [RootNavState].
36+
* It gathers data from multiple datasource and creates a unique one.
37+
*/
38+
class RootNavStateFlowFactory @Inject constructor(
39+
private val authenticationService: MatrixAuthenticationService,
40+
private val cacheService: CacheService,
41+
private val matrixClientsHolder: MatrixClientsHolder,
42+
private val loginUserStory: LoginUserStory,
43+
) {
44+
45+
private var currentCacheIndex = 0
46+
47+
fun create(savedStateMap: SavedStateMap?): Flow<RootNavState> {
48+
return combine(
49+
cacheIndexFlow(savedStateMap),
50+
isUserLoggedInFlow(),
51+
) { cacheIndex, isLoggedIn ->
52+
RootNavState(cacheIndex = cacheIndex, isLoggedIn = isLoggedIn)
53+
}
54+
}
55+
56+
fun saveIntoSavedState(stateMap: MutableSavedStateMap) {
57+
stateMap[SAVE_INSTANCE_KEY] = currentCacheIndex
58+
}
59+
60+
/**
61+
* @return a flow of integer, where each time a clear cache is done, we have a new incremented value.
62+
*/
63+
private fun cacheIndexFlow(savedStateMap: SavedStateMap?): Flow<Int> {
64+
val initialCacheIndex = savedStateMap.getCacheIndexOrDefault()
65+
return cacheService.clearedCacheEventFlow
66+
.onEach { sessionId ->
67+
matrixClientsHolder.remove(sessionId)
68+
}
69+
.toIndexFlow(initialCacheIndex)
70+
.onEach { cacheIndex ->
71+
currentCacheIndex = cacheIndex
72+
}
73+
}
74+
75+
private fun isUserLoggedInFlow(): Flow<Boolean> {
76+
return combine(
77+
authenticationService.isLoggedIn(),
78+
loginUserStory.loginFlowIsDone
79+
) { isLoggedIn, loginFlowIsDone ->
80+
isLoggedIn && loginFlowIsDone
81+
}
82+
.distinctUntilChanged()
83+
}
84+
85+
/**
86+
* @return a flow of integer that increments the value by one each time a new element is emitted upstream.
87+
*/
88+
private fun Flow<Any>.toIndexFlow(initialValue: Int): Flow<Int> = flow {
89+
var index = initialValue
90+
emit(initialValue)
91+
collect {
92+
emit(++index)
93+
}
94+
}
95+
96+
private fun SavedStateMap?.getCacheIndexOrDefault(): Int {
97+
return this?.get(SAVE_INSTANCE_KEY) as? Int ?: 0
98+
}
99+
}

0 commit comments

Comments
 (0)