Skip to content

Commit

Permalink
Merge branch 'develop' into feature/valere/support_verification_viola…
Browse files Browse the repository at this point in the history
…tion_banner
  • Loading branch information
bmarty authored Feb 18, 2025
2 parents a145c15 + d1fc963 commit cc9c7b1
Show file tree
Hide file tree
Showing 625 changed files with 6,271 additions and 2,289 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/danger.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ jobs:
- run: |
npm install --save-dev @babel/plugin-transform-flow-strip-types
- name: Danger
uses: danger/danger-js@12.3.3
uses: danger/danger-js@12.3.4
with:
args: "--dangerfile ./tools/danger/dangerfile.js"
env:
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/quality.yml
Original file line number Diff line number Diff line change
Expand Up @@ -294,7 +294,7 @@ jobs:
yarn add danger-plugin-lint-report --dev
- name: Danger lint
if: always()
uses: danger/danger-js@12.3.3
uses: danger/danger-js@12.3.4
with:
args: "--dangerfile ./tools/danger/dangerfile-lint.js"
env:
Expand Down
11 changes: 7 additions & 4 deletions app/src/main/kotlin/io/element/android/x/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ import androidx.activity.enableEdgeToEdge
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.ui.Modifier
Expand All @@ -26,6 +25,7 @@ import androidx.lifecycle.repeatOnLifecycle
import com.bumble.appyx.core.integration.NodeHost
import com.bumble.appyx.core.integrationpoint.NodeActivity
import com.bumble.appyx.core.plugin.NodeReadyObserver
import io.element.android.compound.theme.ElementTheme
import io.element.android.features.lockscreen.api.LockScreenEntryPoint
import io.element.android.features.lockscreen.api.LockScreenLockState
import io.element.android.features.lockscreen.api.LockScreenService
Expand Down Expand Up @@ -61,16 +61,19 @@ class MainActivity : NodeActivity() {
@Composable
private fun MainContent(appBindings: AppBindings) {
val migrationState = appBindings.migrationEntryPoint().present()
ElementThemeApp(appBindings.preferencesStore()) {
ElementThemeApp(
appPreferencesStore = appBindings.preferencesStore(),
enterpriseService = appBindings.enterpriseService(),
) {
CompositionLocalProvider(
LocalSnackbarDispatcher provides appBindings.snackbarDispatcher(),
LocalUriHandler provides SafeUriHandler(this),
LocalAnalyticsService provides appBindings.analyticsService(),
) {
Box(
modifier = Modifier
.fillMaxSize()
.background(MaterialTheme.colorScheme.background),
.fillMaxSize()
.background(ElementTheme.colors.bgCanvasDefault),
) {
if (migrationState.migrationAction.isSuccess()) {
MainNodeHost()
Expand Down
3 changes: 3 additions & 0 deletions app/src/main/kotlin/io/element/android/x/di/AppBindings.kt
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ package io.element.android.x.di

import com.squareup.anvil.annotations.ContributesTo
import io.element.android.features.api.MigrationEntryPoint
import io.element.android.features.enterprise.api.EnterpriseService
import io.element.android.features.lockscreen.api.LockScreenEntryPoint
import io.element.android.features.lockscreen.api.LockScreenService
import io.element.android.features.rageshake.api.reporter.BugReporter
Expand All @@ -35,4 +36,6 @@ interface AppBindings {
fun lockScreenEntryPoint(): LockScreenEntryPoint

fun analyticsService(): AnalyticsService

fun enterpriseService(): EnterpriseService
}
1 change: 1 addition & 0 deletions app/src/main/res/xml/locales_config.xml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
<locale android:name="ru"/>
<locale android:name="sk"/>
<locale android:name="sv"/>
<locale android:name="tr"/>
<locale android:name="uk"/>
<locale android:name="uz"/>
<locale android:name="zh-CN"/>
Expand Down
19 changes: 9 additions & 10 deletions app/src/main/res/xml/network_security_config.xml
Original file line number Diff line number Diff line change
@@ -1,8 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<network-security-config xmlns:tools="http://schemas.android.com/tools">
<!-- Ref: https://developer.android.com/training/articles/security-config.html -->
<!-- By default, do not allow clearText traffic -->
<base-config cleartextTrafficPermitted="false" />
<base-config cleartextTrafficPermitted="false">
<trust-anchors>
<certificates src="system" />
<certificates
src="user"
tools:ignore="AcceptsUserCertificates" />
</trust-anchors>
</base-config>

<!-- Allow clearText traffic on some specified host -->
<domain-config cleartextTrafficPermitted="true">
Expand All @@ -24,12 +31,4 @@
<domain includeSubdomains="true">lan</domain>
<domain includeSubdomains="true">localdomain</domain>
</domain-config>

<debug-overrides>
<trust-anchors>
<certificates src="system" />
<certificates src="user" />
</trust-anchors>
</debug-overrides>

</network-security-config>
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,7 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import com.bumble.appyx.core.composable.PermanentChild
import com.bumble.appyx.core.lifecycle.subscribe
import com.bumble.appyx.core.modality.BuildContext
Expand Down Expand Up @@ -52,8 +50,6 @@ import io.element.android.features.ftue.api.FtueEntryPoint
import io.element.android.features.ftue.api.state.FtueService
import io.element.android.features.ftue.api.state.FtueState
import io.element.android.features.logout.api.LogoutEntryPoint
import io.element.android.features.networkmonitor.api.NetworkMonitor
import io.element.android.features.networkmonitor.api.NetworkStatus
import io.element.android.features.preferences.api.PreferencesEntryPoint
import io.element.android.features.roomdirectory.api.RoomDescription
import io.element.android.features.roomdirectory.api.RoomDirectoryEntryPoint
Expand All @@ -77,18 +73,13 @@ import io.element.android.libraries.matrix.api.core.RoomIdOrAlias
import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.api.core.toRoomIdOrAlias
import io.element.android.libraries.matrix.api.permalink.PermalinkData
import io.element.android.libraries.matrix.api.sync.SyncState
import io.element.android.libraries.matrix.api.verification.SessionVerificationRequestDetails
import io.element.android.libraries.matrix.api.verification.SessionVerificationServiceListener
import io.element.android.libraries.preferences.api.store.EnableNativeSlidingSyncUseCase
import io.element.android.services.appnavstate.api.AppNavigationStateService
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.FlowPreview
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.debounce
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.launch
import kotlinx.parcelize.Parcelize
import timber.log.Timber
Expand All @@ -107,7 +98,6 @@ class LoggedInFlowNode @AssistedInject constructor(
private val userProfileEntryPoint: UserProfileEntryPoint,
private val ftueEntryPoint: FtueEntryPoint,
private val coroutineScope: CoroutineScope,
private val networkMonitor: NetworkMonitor,
private val ftueService: FtueService,
private val roomDirectoryEntryPoint: RoomDirectoryEntryPoint,
private val shareEntryPoint: ShareEntryPoint,
Expand All @@ -133,7 +123,6 @@ class LoggedInFlowNode @AssistedInject constructor(
fun onOpenBugReport()
}

private val syncService = matrixClient.syncService()
private val loggedInFlowProcessor = LoggedInEventProcessor(
snackbarDispatcher,
matrixClient.roomMembershipObserver(),
Expand All @@ -147,6 +136,7 @@ class LoggedInFlowNode @AssistedInject constructor(

override fun onBuilt() {
super.onBuilt()

lifecycle.subscribe(
onCreate = {
appNavigationStateService.onNavigateToSession(id, matrixClient.sessionId)
Expand All @@ -165,52 +155,20 @@ class LoggedInFlowNode @AssistedInject constructor(
}
.launchIn(lifecycleScope)
},
onStop = {
coroutineScope.launch {
// Counterpart startSync is done in observeSyncStateAndNetworkStatus method.
syncService.stopSync()
}
},
onDestroy = {
appNavigationStateService.onLeavingSpace(id)
appNavigationStateService.onLeavingSession(id)
loggedInFlowProcessor.stopObserving()
matrixClient.sessionVerificationService().setListener(null)
}
)
observeSyncStateAndNetworkStatus()
setupSendingQueue()
}

private fun setupSendingQueue() {
sendingQueue.launchIn(lifecycleScope)
}

@OptIn(FlowPreview::class)
private fun observeSyncStateAndNetworkStatus() {
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
combine(
// small debounce to avoid spamming startSync when the state is changing quickly in case of error.
syncService.syncState.debounce(100),
networkMonitor.connectivity
) { syncState, networkStatus ->
Pair(syncState, networkStatus)
}
.onStart {
// Temporary fix to ensure that the sync is started even if the networkStatus is offline.
syncService.startSync()
}
.collect { (syncState, networkStatus) ->
Timber.d("Sync state: $syncState, network status: $networkStatus")
if (syncState != SyncState.Running && networkStatus == NetworkStatus.Online) {
syncService.startSync()
}
}
}
}
}

sealed interface NavTarget : Parcelable {
@Parcelize
data object Placeholder : NavTarget
Expand Down
14 changes: 7 additions & 7 deletions appnav/src/main/kotlin/io/element/android/appnav/RootFlowNode.kt
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ import dagger.assisted.Assisted
import dagger.assisted.AssistedInject
import im.vector.app.features.analytics.plan.JoinedRoom
import io.element.android.anvilannotations.ContributesNode
import io.element.android.appnav.di.MatrixClientsHolder
import io.element.android.appnav.di.MatrixSessionCache
import io.element.android.appnav.intent.IntentResolver
import io.element.android.appnav.intent.ResolvedIntent
import io.element.android.appnav.root.RootNavStateFlowFactory
Expand Down Expand Up @@ -62,7 +62,7 @@ class RootFlowNode @AssistedInject constructor(
@Assisted plugins: List<Plugin>,
private val authenticationService: MatrixAuthenticationService,
private val navStateFlowFactory: RootNavStateFlowFactory,
private val matrixClientsHolder: MatrixClientsHolder,
private val matrixSessionCache: MatrixSessionCache,
private val presenter: RootPresenter,
private val bugReportEntryPoint: BugReportEntryPoint,
private val viewFolderEntryPoint: ViewFolderEntryPoint,
Expand All @@ -78,14 +78,14 @@ class RootFlowNode @AssistedInject constructor(
plugins = plugins
) {
override fun onBuilt() {
matrixClientsHolder.restoreWithSavedState(buildContext.savedStateMap)
matrixSessionCache.restoreWithSavedState(buildContext.savedStateMap)
super.onBuilt()
observeNavState()
}

override fun onSaveInstanceState(state: MutableSavedStateMap) {
super.onSaveInstanceState(state)
matrixClientsHolder.saveIntoSavedState(state)
matrixSessionCache.saveIntoSavedState(state)
navStateFlowFactory.saveIntoSavedState(state)
}

Expand Down Expand Up @@ -118,7 +118,7 @@ class RootFlowNode @AssistedInject constructor(
}

private fun switchToNotLoggedInFlow() {
matrixClientsHolder.removeAll()
matrixSessionCache.removeAll()
backstack.safeRoot(NavTarget.NotLoggedInFlow)
}

Expand All @@ -131,7 +131,7 @@ class RootFlowNode @AssistedInject constructor(
onFailure: () -> Unit,
onSuccess: (SessionId) -> Unit,
) {
matrixClientsHolder.getOrRestore(sessionId)
matrixSessionCache.getOrRestore(sessionId)
.onSuccess {
Timber.v("Succeed to restore session $sessionId")
onSuccess(sessionId)
Expand Down Expand Up @@ -200,7 +200,7 @@ class RootFlowNode @AssistedInject constructor(
override fun resolve(navTarget: NavTarget, buildContext: BuildContext): Node {
return when (navTarget) {
is NavTarget.LoggedInFlow -> {
val matrixClient = matrixClientsHolder.getOrNull(navTarget.sessionId) ?: return splashNode(buildContext).also {
val matrixClient = matrixSessionCache.getOrNull(navTarget.sessionId) ?: return splashNode(buildContext).also {
Timber.w("Couldn't find any session, go through SplashScreen")
}
val inputs = LoggedInAppScopeFlowNode.Inputs(matrixClient)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

package io.element.android.appnav.di

import androidx.annotation.VisibleForTesting
import com.bumble.appyx.core.state.MutableSavedStateMap
import com.bumble.appyx.core.state.SavedStateMap
import com.squareup.anvil.annotations.ContributesBinding
Expand All @@ -25,45 +26,61 @@ import javax.inject.Inject

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

/**
* In-memory cache for logged in Matrix sessions.
*
* This component contains both the [MatrixClient] and the [SyncOrchestrator] for each session.
*/
@SingleIn(AppScope::class)
@ContributesBinding(AppScope::class)
class MatrixClientsHolder @Inject constructor(
class MatrixSessionCache @Inject constructor(
private val authenticationService: MatrixAuthenticationService,
private val syncOrchestratorFactory: SyncOrchestrator.Factory,
) : MatrixClientProvider {
private val sessionIdsToMatrixClient = ConcurrentHashMap<SessionId, MatrixClient>()
private val sessionIdsToMatrixSession = ConcurrentHashMap<SessionId, InMemoryMatrixSession>()
private val restoreMutex = Mutex()

init {
authenticationService.listenToNewMatrixClients { matrixClient ->
sessionIdsToMatrixClient[matrixClient.sessionId] = matrixClient
val syncOrchestrator = syncOrchestratorFactory.create(matrixClient)
sessionIdsToMatrixSession[matrixClient.sessionId] = InMemoryMatrixSession(
matrixClient = matrixClient,
syncOrchestrator = syncOrchestrator,
)
syncOrchestrator.start()
}
}

fun removeAll() {
sessionIdsToMatrixClient.clear()
sessionIdsToMatrixSession.clear()
}

fun remove(sessionId: SessionId) {
sessionIdsToMatrixClient.remove(sessionId)
sessionIdsToMatrixSession.remove(sessionId)
}

override fun getOrNull(sessionId: SessionId): MatrixClient? {
return sessionIdsToMatrixClient[sessionId]
return sessionIdsToMatrixSession[sessionId]?.matrixClient
}

override suspend fun getOrRestore(sessionId: SessionId): Result<MatrixClient> {
return restoreMutex.withLock {
when (val matrixClient = getOrNull(sessionId)) {
when (val cached = getOrNull(sessionId)) {
null -> restore(sessionId)
else -> Result.success(matrixClient)
else -> Result.success(cached)
}
}
}

@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
internal fun getSyncOrchestrator(sessionId: SessionId): SyncOrchestrator? {
return sessionIdsToMatrixSession[sessionId]?.syncOrchestrator
}

@Suppress("UNCHECKED_CAST")
fun restoreWithSavedState(state: SavedStateMap?) {
Timber.d("Restore state")
if (state == null || sessionIdsToMatrixClient.isNotEmpty()) {
if (state == null || sessionIdsToMatrixSession.isNotEmpty()) {
Timber.w("Restore with non-empty map")
return
}
Expand All @@ -79,7 +96,7 @@ class MatrixClientsHolder @Inject constructor(
}

fun saveIntoSavedState(state: MutableSavedStateMap) {
val sessionKeys = sessionIdsToMatrixClient.keys.toTypedArray()
val sessionKeys = sessionIdsToMatrixSession.keys.toTypedArray()
Timber.d("Save matrix session keys = ${sessionKeys.map { it.value }}")
state[SAVE_INSTANCE_KEY] = sessionKeys
}
Expand All @@ -88,10 +105,20 @@ class MatrixClientsHolder @Inject constructor(
Timber.d("Restore matrix session: $sessionId")
return authenticationService.restoreSession(sessionId)
.onSuccess { matrixClient ->
sessionIdsToMatrixClient[matrixClient.sessionId] = matrixClient
val syncOrchestrator = syncOrchestratorFactory.create(matrixClient)
sessionIdsToMatrixSession[matrixClient.sessionId] = InMemoryMatrixSession(
matrixClient = matrixClient,
syncOrchestrator = syncOrchestrator,
)
syncOrchestrator.start()
}
.onFailure {
Timber.e(it, "Fail to restore session")
}
}
}

private data class InMemoryMatrixSession(
val matrixClient: MatrixClient,
val syncOrchestrator: SyncOrchestrator,
)
Loading

0 comments on commit cc9c7b1

Please sign in to comment.