ganfra
1 year ago
9 changed files with 255 additions and 172 deletions
@ -0,0 +1,22 @@
@@ -0,0 +1,22 @@
|
||||
/* |
||||
* Copyright (c) 2023 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.root |
||||
|
||||
data class RootNavState( |
||||
val cacheIndex: Int, |
||||
val isLoggedIn: Boolean |
||||
) |
@ -0,0 +1,90 @@
@@ -0,0 +1,90 @@
|
||||
/* |
||||
* Copyright (c) 2023 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.root |
||||
|
||||
import com.bumble.appyx.core.state.MutableSavedStateMap |
||||
import com.bumble.appyx.core.state.SavedStateMap |
||||
import io.element.android.appnav.di.MatrixClientsHolder |
||||
import io.element.android.features.login.api.LoginUserStory |
||||
import io.element.android.features.preferences.api.CacheService |
||||
import io.element.android.libraries.matrix.api.auth.MatrixAuthenticationService |
||||
import kotlinx.coroutines.flow.Flow |
||||
import kotlinx.coroutines.flow.combine |
||||
import kotlinx.coroutines.flow.distinctUntilChanged |
||||
import kotlinx.coroutines.flow.flow |
||||
import kotlinx.coroutines.flow.onEach |
||||
import javax.inject.Inject |
||||
|
||||
private const val SAVE_INSTANCE_KEY = "io.element.android.x.RootNavStateFlowFactory.SAVE_INSTANCE_KEY" |
||||
|
||||
class RootNavStateFlowFactory @Inject constructor( |
||||
private val authenticationService: MatrixAuthenticationService, |
||||
private val cacheService: CacheService, |
||||
private val matrixClientsHolder: MatrixClientsHolder, |
||||
private val loginUserStory: LoginUserStory, |
||||
) { |
||||
|
||||
private var currentCacheIndex = 0 |
||||
|
||||
fun create(savedStateMap: SavedStateMap?): Flow<RootNavState> { |
||||
/** |
||||
* A flow of integer, where each time a clear cache is done, we have a new incremented value. |
||||
*/ |
||||
val initialCacheIndex = savedStateMap.getCacheIndexOrDefault() |
||||
val cacheIndexFlow = cacheService.clearedCacheEventFlow |
||||
.onEach { sessionId -> |
||||
matrixClientsHolder.remove(sessionId) |
||||
} |
||||
.toIndexFlow(initialCacheIndex) |
||||
.onEach { cacheIndex -> |
||||
currentCacheIndex = cacheIndex |
||||
} |
||||
|
||||
return combine( |
||||
cacheIndexFlow, |
||||
isUserLoggedInFlow(), |
||||
) { navId, isLoggedIn -> |
||||
RootNavState(navId, isLoggedIn) |
||||
} |
||||
} |
||||
|
||||
fun saveIntoSavedState(stateMap: MutableSavedStateMap) { |
||||
stateMap[SAVE_INSTANCE_KEY] = currentCacheIndex |
||||
} |
||||
|
||||
private fun isUserLoggedInFlow(): Flow<Boolean> { |
||||
return combine( |
||||
authenticationService.isLoggedIn(), |
||||
loginUserStory.loginFlowIsDone |
||||
) { isLoggedIn, loginFlowIsDone -> |
||||
isLoggedIn && loginFlowIsDone |
||||
} |
||||
.distinctUntilChanged() |
||||
} |
||||
|
||||
private fun Flow<Any>.toIndexFlow(initialValue: Int): Flow<Int> = flow { |
||||
var index = initialValue |
||||
emit(initialValue) |
||||
collect { |
||||
emit(++index) |
||||
} |
||||
} |
||||
|
||||
private fun SavedStateMap?.getCacheIndexOrDefault(): Int { |
||||
return this?.get(SAVE_INSTANCE_KEY) as? Int ?: 0 |
||||
} |
||||
} |
@ -0,0 +1,28 @@
@@ -0,0 +1,28 @@
|
||||
/* |
||||
* Copyright (c) 2023 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.libraries.matrix.api |
||||
|
||||
import io.element.android.libraries.matrix.api.core.SessionId |
||||
|
||||
interface MatrixClientProvider { |
||||
/** |
||||
* Can be used to get or restore a MatrixClient with the given [SessionId]. |
||||
* If a [MatrixClient] is already in memory, it'll return it. Otherwise it'll try to restore one. |
||||
* Most of the time you want to use injected constructor instead of retrieving a MatrixClient with this provider. |
||||
*/ |
||||
suspend fun getOrRestore(sessionId: SessionId): Result<MatrixClient> |
||||
} |
@ -1,106 +0,0 @@
@@ -1,106 +0,0 @@
|
||||
/* |
||||
* Copyright (c) 2023 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.libraries.matrix.ui.di |
||||
|
||||
import com.bumble.appyx.core.state.MutableSavedStateMap |
||||
import com.bumble.appyx.core.state.SavedStateMap |
||||
import io.element.android.libraries.di.AppScope |
||||
import io.element.android.libraries.di.SingleIn |
||||
import io.element.android.libraries.matrix.api.MatrixClient |
||||
import io.element.android.libraries.matrix.api.auth.AuthenticationException |
||||
import io.element.android.libraries.matrix.api.auth.MatrixAuthenticationService |
||||
import io.element.android.libraries.matrix.api.core.SessionId |
||||
import kotlinx.coroutines.runBlocking |
||||
import kotlinx.coroutines.sync.Mutex |
||||
import kotlinx.coroutines.sync.withLock |
||||
import timber.log.Timber |
||||
import java.util.concurrent.ConcurrentHashMap |
||||
import javax.inject.Inject |
||||
import kotlin.jvm.Throws |
||||
|
||||
private const val SAVE_INSTANCE_KEY = "io.element.android.x.di.MatrixClientsHolder.SaveInstanceKey" |
||||
|
||||
@SingleIn(AppScope::class) |
||||
class MatrixClientsHolder @Inject constructor(private val authenticationService: MatrixAuthenticationService) { |
||||
|
||||
private val sessionIdsToMatrixClient = ConcurrentHashMap<SessionId, MatrixClient>() |
||||
private val restoreMutex = Mutex() |
||||
|
||||
private fun add(matrixClient: MatrixClient) { |
||||
sessionIdsToMatrixClient[matrixClient.sessionId] = matrixClient |
||||
} |
||||
|
||||
fun removeAll() { |
||||
sessionIdsToMatrixClient.clear() |
||||
} |
||||
|
||||
fun remove(sessionId: SessionId) { |
||||
sessionIdsToMatrixClient.remove(sessionId) |
||||
} |
||||
|
||||
fun isEmpty(): Boolean = sessionIdsToMatrixClient.isEmpty() |
||||
|
||||
fun knowSession(sessionId: SessionId): Boolean = sessionIdsToMatrixClient.containsKey(sessionId) |
||||
|
||||
fun getOrNull(sessionId: SessionId): MatrixClient? { |
||||
return sessionIdsToMatrixClient[sessionId] |
||||
} |
||||
|
||||
@Throws(AuthenticationException::class) |
||||
suspend fun requireSession(sessionId: SessionId): MatrixClient { |
||||
return restoreMutex.withLock { |
||||
when (val matrixClient = sessionIdsToMatrixClient[sessionId]) { |
||||
null -> restore(sessionId).getOrThrow() |
||||
else -> matrixClient |
||||
} |
||||
} |
||||
} |
||||
|
||||
private suspend fun restore(sessionId: SessionId): Result<MatrixClient> { |
||||
Timber.d("Restore matrix session: $sessionId") |
||||
return authenticationService.restoreSession(sessionId) |
||||
.onSuccess { matrixClient -> |
||||
add(matrixClient) |
||||
} |
||||
.onFailure { |
||||
Timber.e("Fail to restore session") |
||||
} |
||||
} |
||||
|
||||
@Suppress("UNCHECKED_CAST") |
||||
fun restore(state: SavedStateMap?) { |
||||
Timber.d("Restore state") |
||||
if (state == null || sessionIdsToMatrixClient.isNotEmpty()) return Unit.also { |
||||
Timber.w("Restore with non-empty map") |
||||
} |
||||
val sessionIds = state[SAVE_INSTANCE_KEY] as? Array<SessionId> |
||||
Timber.d("Restore matrix session keys = ${sessionIds?.map { it.value }}") |
||||
if (sessionIds.isNullOrEmpty()) return |
||||
// Not ideal but should only happens in case of process recreation. This ensure we restore all the active sessions before restoring the node graphs. |
||||
runBlocking { |
||||
sessionIds.forEach { sessionId -> |
||||
restore(sessionId) |
||||
} |
||||
} |
||||
} |
||||
|
||||
fun save(state: MutableSavedStateMap) { |
||||
val sessionKeys = sessionIdsToMatrixClient.keys.toTypedArray() |
||||
Timber.d("Save matrix session keys = ${sessionKeys.map { it.value }}") |
||||
state[SAVE_INSTANCE_KEY] = sessionKeys |
||||
} |
||||
} |
Loading…
Reference in new issue