Skip to content

Commit cc9c7b1

Browse files
authored
Merge branch 'develop' into feature/valere/support_verification_violation_banner
2 parents a145c15 + d1fc963 commit cc9c7b1

File tree

625 files changed

+6271
-2289
lines changed

Some content is hidden

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

625 files changed

+6271
-2289
lines changed

.github/workflows/danger.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ jobs:
2020
- run: |
2121
npm install --save-dev @babel/plugin-transform-flow-strip-types
2222
- name: Danger
23-
uses: danger/[email protected].3
23+
uses: danger/[email protected].4
2424
with:
2525
args: "--dangerfile ./tools/danger/dangerfile.js"
2626
env:

.github/workflows/quality.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -294,7 +294,7 @@ jobs:
294294
yarn add danger-plugin-lint-report --dev
295295
- name: Danger lint
296296
if: always()
297-
uses: danger/[email protected].3
297+
uses: danger/[email protected].4
298298
with:
299299
args: "--dangerfile ./tools/danger/dangerfile-lint.js"
300300
env:

app/src/main/kotlin/io/element/android/x/MainActivity.kt

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ import androidx.activity.enableEdgeToEdge
1414
import androidx.compose.foundation.background
1515
import androidx.compose.foundation.layout.Box
1616
import androidx.compose.foundation.layout.fillMaxSize
17-
import androidx.compose.material3.MaterialTheme
1817
import androidx.compose.runtime.Composable
1918
import androidx.compose.runtime.CompositionLocalProvider
2019
import androidx.compose.ui.Modifier
@@ -26,6 +25,7 @@ import androidx.lifecycle.repeatOnLifecycle
2625
import com.bumble.appyx.core.integration.NodeHost
2726
import com.bumble.appyx.core.integrationpoint.NodeActivity
2827
import com.bumble.appyx.core.plugin.NodeReadyObserver
28+
import io.element.android.compound.theme.ElementTheme
2929
import io.element.android.features.lockscreen.api.LockScreenEntryPoint
3030
import io.element.android.features.lockscreen.api.LockScreenLockState
3131
import io.element.android.features.lockscreen.api.LockScreenService
@@ -61,16 +61,19 @@ class MainActivity : NodeActivity() {
6161
@Composable
6262
private fun MainContent(appBindings: AppBindings) {
6363
val migrationState = appBindings.migrationEntryPoint().present()
64-
ElementThemeApp(appBindings.preferencesStore()) {
64+
ElementThemeApp(
65+
appPreferencesStore = appBindings.preferencesStore(),
66+
enterpriseService = appBindings.enterpriseService(),
67+
) {
6568
CompositionLocalProvider(
6669
LocalSnackbarDispatcher provides appBindings.snackbarDispatcher(),
6770
LocalUriHandler provides SafeUriHandler(this),
6871
LocalAnalyticsService provides appBindings.analyticsService(),
6972
) {
7073
Box(
7174
modifier = Modifier
72-
.fillMaxSize()
73-
.background(MaterialTheme.colorScheme.background),
75+
.fillMaxSize()
76+
.background(ElementTheme.colors.bgCanvasDefault),
7477
) {
7578
if (migrationState.migrationAction.isSuccess()) {
7679
MainNodeHost()

app/src/main/kotlin/io/element/android/x/di/AppBindings.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ package io.element.android.x.di
99

1010
import com.squareup.anvil.annotations.ContributesTo
1111
import io.element.android.features.api.MigrationEntryPoint
12+
import io.element.android.features.enterprise.api.EnterpriseService
1213
import io.element.android.features.lockscreen.api.LockScreenEntryPoint
1314
import io.element.android.features.lockscreen.api.LockScreenService
1415
import io.element.android.features.rageshake.api.reporter.BugReporter
@@ -35,4 +36,6 @@ interface AppBindings {
3536
fun lockScreenEntryPoint(): LockScreenEntryPoint
3637

3738
fun analyticsService(): AnalyticsService
39+
40+
fun enterpriseService(): EnterpriseService
3841
}

app/src/main/res/xml/locales_config.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
<locale android:name="ru"/>
2626
<locale android:name="sk"/>
2727
<locale android:name="sv"/>
28+
<locale android:name="tr"/>
2829
<locale android:name="uk"/>
2930
<locale android:name="uz"/>
3031
<locale android:name="zh-CN"/>

app/src/main/res/xml/network_security_config.xml

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,15 @@
11
<?xml version="1.0" encoding="utf-8"?>
2-
<network-security-config>
2+
<network-security-config xmlns:tools="http://schemas.android.com/tools">
33
<!-- Ref: https://developer.android.com/training/articles/security-config.html -->
44
<!-- By default, do not allow clearText traffic -->
5-
<base-config cleartextTrafficPermitted="false" />
5+
<base-config cleartextTrafficPermitted="false">
6+
<trust-anchors>
7+
<certificates src="system" />
8+
<certificates
9+
src="user"
10+
tools:ignore="AcceptsUserCertificates" />
11+
</trust-anchors>
12+
</base-config>
613

714
<!-- Allow clearText traffic on some specified host -->
815
<domain-config cleartextTrafficPermitted="true">
@@ -24,12 +31,4 @@
2431
<domain includeSubdomains="true">lan</domain>
2532
<domain includeSubdomains="true">localdomain</domain>
2633
</domain-config>
27-
28-
<debug-overrides>
29-
<trust-anchors>
30-
<certificates src="system" />
31-
<certificates src="user" />
32-
</trust-anchors>
33-
</debug-overrides>
34-
3534
</network-security-config>

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

Lines changed: 1 addition & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,7 @@ import androidx.compose.runtime.Composable
1414
import androidx.compose.runtime.collectAsState
1515
import androidx.compose.runtime.getValue
1616
import androidx.compose.ui.Modifier
17-
import androidx.lifecycle.Lifecycle
1817
import androidx.lifecycle.lifecycleScope
19-
import androidx.lifecycle.repeatOnLifecycle
2018
import com.bumble.appyx.core.composable.PermanentChild
2119
import com.bumble.appyx.core.lifecycle.subscribe
2220
import com.bumble.appyx.core.modality.BuildContext
@@ -52,8 +50,6 @@ import io.element.android.features.ftue.api.FtueEntryPoint
5250
import io.element.android.features.ftue.api.state.FtueService
5351
import io.element.android.features.ftue.api.state.FtueState
5452
import io.element.android.features.logout.api.LogoutEntryPoint
55-
import io.element.android.features.networkmonitor.api.NetworkMonitor
56-
import io.element.android.features.networkmonitor.api.NetworkStatus
5753
import io.element.android.features.preferences.api.PreferencesEntryPoint
5854
import io.element.android.features.roomdirectory.api.RoomDescription
5955
import io.element.android.features.roomdirectory.api.RoomDirectoryEntryPoint
@@ -77,18 +73,13 @@ import io.element.android.libraries.matrix.api.core.RoomIdOrAlias
7773
import io.element.android.libraries.matrix.api.core.UserId
7874
import io.element.android.libraries.matrix.api.core.toRoomIdOrAlias
7975
import io.element.android.libraries.matrix.api.permalink.PermalinkData
80-
import io.element.android.libraries.matrix.api.sync.SyncState
8176
import io.element.android.libraries.matrix.api.verification.SessionVerificationRequestDetails
8277
import io.element.android.libraries.matrix.api.verification.SessionVerificationServiceListener
8378
import io.element.android.libraries.preferences.api.store.EnableNativeSlidingSyncUseCase
8479
import io.element.android.services.appnavstate.api.AppNavigationStateService
8580
import kotlinx.coroutines.CoroutineScope
86-
import kotlinx.coroutines.FlowPreview
87-
import kotlinx.coroutines.flow.combine
88-
import kotlinx.coroutines.flow.debounce
8981
import kotlinx.coroutines.flow.launchIn
9082
import kotlinx.coroutines.flow.onEach
91-
import kotlinx.coroutines.flow.onStart
9283
import kotlinx.coroutines.launch
9384
import kotlinx.parcelize.Parcelize
9485
import timber.log.Timber
@@ -107,7 +98,6 @@ class LoggedInFlowNode @AssistedInject constructor(
10798
private val userProfileEntryPoint: UserProfileEntryPoint,
10899
private val ftueEntryPoint: FtueEntryPoint,
109100
private val coroutineScope: CoroutineScope,
110-
private val networkMonitor: NetworkMonitor,
111101
private val ftueService: FtueService,
112102
private val roomDirectoryEntryPoint: RoomDirectoryEntryPoint,
113103
private val shareEntryPoint: ShareEntryPoint,
@@ -133,7 +123,6 @@ class LoggedInFlowNode @AssistedInject constructor(
133123
fun onOpenBugReport()
134124
}
135125

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

148137
override fun onBuilt() {
149138
super.onBuilt()
139+
150140
lifecycle.subscribe(
151141
onCreate = {
152142
appNavigationStateService.onNavigateToSession(id, matrixClient.sessionId)
@@ -165,52 +155,20 @@ class LoggedInFlowNode @AssistedInject constructor(
165155
}
166156
.launchIn(lifecycleScope)
167157
},
168-
onStop = {
169-
coroutineScope.launch {
170-
// Counterpart startSync is done in observeSyncStateAndNetworkStatus method.
171-
syncService.stopSync()
172-
}
173-
},
174158
onDestroy = {
175159
appNavigationStateService.onLeavingSpace(id)
176160
appNavigationStateService.onLeavingSession(id)
177161
loggedInFlowProcessor.stopObserving()
178162
matrixClient.sessionVerificationService().setListener(null)
179163
}
180164
)
181-
observeSyncStateAndNetworkStatus()
182165
setupSendingQueue()
183166
}
184167

185168
private fun setupSendingQueue() {
186169
sendingQueue.launchIn(lifecycleScope)
187170
}
188171

189-
@OptIn(FlowPreview::class)
190-
private fun observeSyncStateAndNetworkStatus() {
191-
lifecycleScope.launch {
192-
repeatOnLifecycle(Lifecycle.State.STARTED) {
193-
combine(
194-
// small debounce to avoid spamming startSync when the state is changing quickly in case of error.
195-
syncService.syncState.debounce(100),
196-
networkMonitor.connectivity
197-
) { syncState, networkStatus ->
198-
Pair(syncState, networkStatus)
199-
}
200-
.onStart {
201-
// Temporary fix to ensure that the sync is started even if the networkStatus is offline.
202-
syncService.startSync()
203-
}
204-
.collect { (syncState, networkStatus) ->
205-
Timber.d("Sync state: $syncState, network status: $networkStatus")
206-
if (syncState != SyncState.Running && networkStatus == NetworkStatus.Online) {
207-
syncService.startSync()
208-
}
209-
}
210-
}
211-
}
212-
}
213-
214172
sealed interface NavTarget : Parcelable {
215173
@Parcelize
216174
data object Placeholder : NavTarget

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

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ import dagger.assisted.Assisted
2727
import dagger.assisted.AssistedInject
2828
import im.vector.app.features.analytics.plan.JoinedRoom
2929
import io.element.android.anvilannotations.ContributesNode
30-
import io.element.android.appnav.di.MatrixClientsHolder
30+
import io.element.android.appnav.di.MatrixSessionCache
3131
import io.element.android.appnav.intent.IntentResolver
3232
import io.element.android.appnav.intent.ResolvedIntent
3333
import io.element.android.appnav.root.RootNavStateFlowFactory
@@ -62,7 +62,7 @@ class RootFlowNode @AssistedInject constructor(
6262
@Assisted plugins: List<Plugin>,
6363
private val authenticationService: MatrixAuthenticationService,
6464
private val navStateFlowFactory: RootNavStateFlowFactory,
65-
private val matrixClientsHolder: MatrixClientsHolder,
65+
private val matrixSessionCache: MatrixSessionCache,
6666
private val presenter: RootPresenter,
6767
private val bugReportEntryPoint: BugReportEntryPoint,
6868
private val viewFolderEntryPoint: ViewFolderEntryPoint,
@@ -78,14 +78,14 @@ class RootFlowNode @AssistedInject constructor(
7878
plugins = plugins
7979
) {
8080
override fun onBuilt() {
81-
matrixClientsHolder.restoreWithSavedState(buildContext.savedStateMap)
81+
matrixSessionCache.restoreWithSavedState(buildContext.savedStateMap)
8282
super.onBuilt()
8383
observeNavState()
8484
}
8585

8686
override fun onSaveInstanceState(state: MutableSavedStateMap) {
8787
super.onSaveInstanceState(state)
88-
matrixClientsHolder.saveIntoSavedState(state)
88+
matrixSessionCache.saveIntoSavedState(state)
8989
navStateFlowFactory.saveIntoSavedState(state)
9090
}
9191

@@ -118,7 +118,7 @@ class RootFlowNode @AssistedInject constructor(
118118
}
119119

120120
private fun switchToNotLoggedInFlow() {
121-
matrixClientsHolder.removeAll()
121+
matrixSessionCache.removeAll()
122122
backstack.safeRoot(NavTarget.NotLoggedInFlow)
123123
}
124124

@@ -131,7 +131,7 @@ class RootFlowNode @AssistedInject constructor(
131131
onFailure: () -> Unit,
132132
onSuccess: (SessionId) -> Unit,
133133
) {
134-
matrixClientsHolder.getOrRestore(sessionId)
134+
matrixSessionCache.getOrRestore(sessionId)
135135
.onSuccess {
136136
Timber.v("Succeed to restore session $sessionId")
137137
onSuccess(sessionId)
@@ -200,7 +200,7 @@ class RootFlowNode @AssistedInject constructor(
200200
override fun resolve(navTarget: NavTarget, buildContext: BuildContext): Node {
201201
return when (navTarget) {
202202
is NavTarget.LoggedInFlow -> {
203-
val matrixClient = matrixClientsHolder.getOrNull(navTarget.sessionId) ?: return splashNode(buildContext).also {
203+
val matrixClient = matrixSessionCache.getOrNull(navTarget.sessionId) ?: return splashNode(buildContext).also {
204204
Timber.w("Couldn't find any session, go through SplashScreen")
205205
}
206206
val inputs = LoggedInAppScopeFlowNode.Inputs(matrixClient)

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

Lines changed: 38 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
package io.element.android.appnav.di
99

10+
import androidx.annotation.VisibleForTesting
1011
import com.bumble.appyx.core.state.MutableSavedStateMap
1112
import com.bumble.appyx.core.state.SavedStateMap
1213
import com.squareup.anvil.annotations.ContributesBinding
@@ -25,45 +26,61 @@ import javax.inject.Inject
2526

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

29+
/**
30+
* In-memory cache for logged in Matrix sessions.
31+
*
32+
* This component contains both the [MatrixClient] and the [SyncOrchestrator] for each session.
33+
*/
2834
@SingleIn(AppScope::class)
2935
@ContributesBinding(AppScope::class)
30-
class MatrixClientsHolder @Inject constructor(
36+
class MatrixSessionCache @Inject constructor(
3137
private val authenticationService: MatrixAuthenticationService,
38+
private val syncOrchestratorFactory: SyncOrchestrator.Factory,
3239
) : MatrixClientProvider {
33-
private val sessionIdsToMatrixClient = ConcurrentHashMap<SessionId, MatrixClient>()
40+
private val sessionIdsToMatrixSession = ConcurrentHashMap<SessionId, InMemoryMatrixSession>()
3441
private val restoreMutex = Mutex()
3542

3643
init {
3744
authenticationService.listenToNewMatrixClients { matrixClient ->
38-
sessionIdsToMatrixClient[matrixClient.sessionId] = matrixClient
45+
val syncOrchestrator = syncOrchestratorFactory.create(matrixClient)
46+
sessionIdsToMatrixSession[matrixClient.sessionId] = InMemoryMatrixSession(
47+
matrixClient = matrixClient,
48+
syncOrchestrator = syncOrchestrator,
49+
)
50+
syncOrchestrator.start()
3951
}
4052
}
4153

4254
fun removeAll() {
43-
sessionIdsToMatrixClient.clear()
55+
sessionIdsToMatrixSession.clear()
4456
}
4557

4658
fun remove(sessionId: SessionId) {
47-
sessionIdsToMatrixClient.remove(sessionId)
59+
sessionIdsToMatrixSession.remove(sessionId)
4860
}
4961

5062
override fun getOrNull(sessionId: SessionId): MatrixClient? {
51-
return sessionIdsToMatrixClient[sessionId]
63+
return sessionIdsToMatrixSession[sessionId]?.matrixClient
5264
}
5365

5466
override suspend fun getOrRestore(sessionId: SessionId): Result<MatrixClient> {
5567
return restoreMutex.withLock {
56-
when (val matrixClient = getOrNull(sessionId)) {
68+
when (val cached = getOrNull(sessionId)) {
5769
null -> restore(sessionId)
58-
else -> Result.success(matrixClient)
70+
else -> Result.success(cached)
5971
}
6072
}
6173
}
6274

75+
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
76+
internal fun getSyncOrchestrator(sessionId: SessionId): SyncOrchestrator? {
77+
return sessionIdsToMatrixSession[sessionId]?.syncOrchestrator
78+
}
79+
6380
@Suppress("UNCHECKED_CAST")
6481
fun restoreWithSavedState(state: SavedStateMap?) {
6582
Timber.d("Restore state")
66-
if (state == null || sessionIdsToMatrixClient.isNotEmpty()) {
83+
if (state == null || sessionIdsToMatrixSession.isNotEmpty()) {
6784
Timber.w("Restore with non-empty map")
6885
return
6986
}
@@ -79,7 +96,7 @@ class MatrixClientsHolder @Inject constructor(
7996
}
8097

8198
fun saveIntoSavedState(state: MutableSavedStateMap) {
82-
val sessionKeys = sessionIdsToMatrixClient.keys.toTypedArray()
99+
val sessionKeys = sessionIdsToMatrixSession.keys.toTypedArray()
83100
Timber.d("Save matrix session keys = ${sessionKeys.map { it.value }}")
84101
state[SAVE_INSTANCE_KEY] = sessionKeys
85102
}
@@ -88,10 +105,20 @@ class MatrixClientsHolder @Inject constructor(
88105
Timber.d("Restore matrix session: $sessionId")
89106
return authenticationService.restoreSession(sessionId)
90107
.onSuccess { matrixClient ->
91-
sessionIdsToMatrixClient[matrixClient.sessionId] = matrixClient
108+
val syncOrchestrator = syncOrchestratorFactory.create(matrixClient)
109+
sessionIdsToMatrixSession[matrixClient.sessionId] = InMemoryMatrixSession(
110+
matrixClient = matrixClient,
111+
syncOrchestrator = syncOrchestrator,
112+
)
113+
syncOrchestrator.start()
92114
}
93115
.onFailure {
94116
Timber.e(it, "Fail to restore session")
95117
}
96118
}
97119
}
120+
121+
private data class InMemoryMatrixSession(
122+
val matrixClient: MatrixClient,
123+
val syncOrchestrator: SyncOrchestrator,
124+
)

0 commit comments

Comments
 (0)