Skip to content

Commit

Permalink
Merge pull request #16 from tchapgouv/yostyle/rebase_20241125
Browse files Browse the repository at this point in the history
Rebase against on Element X develop
  • Loading branch information
yostyle authored Nov 25, 2024
2 parents 9d3a2d0 + 5e07900 commit 184b078
Show file tree
Hide file tree
Showing 2,755 changed files with 22,013 additions and 12,013 deletions.
5 changes: 1 addition & 4 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
variant: [debug, release, nightly, samples]
variant: [debug, release, nightly]
fail-fast: false
# Allow all jobs on develop. Just one per PR.
concurrency:
Expand Down Expand Up @@ -82,6 +82,3 @@ jobs:
- name: Compile nightly sources
if: ${{ matrix.variant == 'nightly' }}
run: ./gradlew compileGplayNightlySources -PallWarningsAsErrors=true $CI_GRADLE_ARG_PROPERTIES
- name: Compile samples minimal
if: ${{ matrix.variant == 'samples' }}
run: ./gradlew :samples:minimal:assemble $CI_GRADLE_ARG_PROPERTIES
2 changes: 1 addition & 1 deletion .github/workflows/maestro.yml
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ jobs:
uses: actions/download-artifact@v4
with:
name: elementx-apk-maestro
- uses: mobile-dev-inc/[email protected].1
- uses: mobile-dev-inc/[email protected].6
if: (github.event_name == 'pull_request' && github.event.pull_request.fork == null) || github.event_name == 'workflow_dispatch'
with:
api-key: ${{ secrets.MAESTRO_CLOUD_API_KEY }}
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ jobs:
# https://github.com/codecov/codecov-action
- name: ☂️ Upload coverage reports to codecov
uses: codecov/codecov-action@v4
uses: codecov/codecov-action@v5
# Skip in forks
if: ${{ github.repository == 'element-hq/element-x-android' && ('pull_request' != github.event_name || github.event.pull_request.head.repo.full_name == github.repository) }}
with:
Expand Down
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,9 @@ captures/
.idea/shelf
.idea/sonarlint

# .kotlin folder
.kotlin

# Keystore files
# Uncomment the following lines if you do not want to check your keystore files in.
#*.jks
Expand Down
2 changes: 1 addition & 1 deletion .idea/kotlinc.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

291 changes: 291 additions & 0 deletions CHANGES.md

Large diffs are not rendered by default.

2 changes: 0 additions & 2 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,6 @@ Please ensure that you're using the project formatting rules (which are in the p

This project should compile without any special action. Just clone it and open it with Android Studio, or compile from command line using `gradlew`.

Note: please make sure that the configuration is `app` and not `samples.minimal`.

## Strings

The strings of the project are managed externally using [https://localazy.com](https://localazy.com) and shared with Element X iOS.
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

# Element X Android

Element X Android is a [Matrix](https://matrix.org/) Android Client provided by [element.io](https://element.io/). This app is currently in a pre-alpha release stage with only basic functionalities.
Element X Android is a [Matrix](https://matrix.org/) Android Client provided by [element.io](https://element.io/).

The application is a total rewrite of [Element-Android](https://github.com/element-hq/element-android) using the [Matrix Rust SDK](https://github.com/matrix-org/matrix-rust-sdk) underneath and targeting devices running Android 7+. The UI layer is written using [Jetpack Compose](https://developer.android.com/jetpack/compose), and the navigation is managed using [Appyx](https://github.com/bumble-tech/appyx).

Expand Down Expand Up @@ -71,7 +71,7 @@ We're doing this as a way to share code between platforms and while we've seen p

## Status

This project is in work in progress. The app does not cover yet all functionalities we expect. The list of supported features can be found in [this issue](https://github.com/element-hq/element-x-android/issues/911).
This project is in an early rollout and migration phase.

## Contributing

Expand Down
6 changes: 3 additions & 3 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,9 @@ android {
} else {
"fr.gouv.tchap.android.x"
}
targetSdk = Versions.targetSdk
versionCode = Versions.versionCode
versionName = Versions.versionName
targetSdk = Versions.TARGET_SDK
versionCode = Versions.VERSION_CODE
versionName = Versions.VERSION_NAME

// Keep abiFilter for the universalApk
ndk {
Expand Down
3 changes: 1 addition & 2 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,11 @@
<!-- To be able to install APK from the application -->
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />

<!-- Do not enable enableOnBackInvokedCallback until https://issuetracker.google.com/issues/271303558 is fixed -->
<application
android:name=".ElementXApplication"
android:allowBackup="false"
android:dataExtractionRules="@xml/data_extraction_rules"
android:enableOnBackInvokedCallback="false"
android:enableOnBackInvokedCallback="true"
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
Expand Down
2 changes: 1 addition & 1 deletion app/src/main/kotlin/io/element/android/x/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ class MainActivity : NodeActivity() {

@Composable
private fun MainNodeHost() {
NodeHost(integrationPoint = appyxIntegrationPoint) {
NodeHost(integrationPoint = appyxV1IntegrationPoint) {
MainNode(
it,
plugins = listOf(
Expand Down
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 @@ -9,6 +9,7 @@
<locale android:name="es"/>
<locale android:name="et"/>
<locale android:name="fa"/>
<locale android:name="fi"/>
<locale android:name="fr"/>
<locale android:name="hu"/>
<locale android:name="in"/>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/*
* Copyright 2023, 2024 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only
* Please see LICENSE in the repository root for full details.
*/

package io.element.android.appconfig

object LearnMoreConfig {
const val ENCRYPTION_URL: String = "https://element.io/help#encryption"
const val SECURE_BACKUP_URL: String = "https://element.io/help#encryption5"
const val IDENTITY_CHANGE_URL: String = "https://element.io/help#encryption18"
}

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,10 @@ 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.pop
import com.bumble.appyx.navmodel.backstack.operation.push
import com.bumble.appyx.navmodel.backstack.operation.replace
import com.bumble.appyx.navmodel.backstack.operation.singleTop
import dagger.assisted.Assisted
import dagger.assisted.AssistedInject
import im.vector.app.features.analytics.plan.JoinedRoom
Expand All @@ -50,6 +52,7 @@ import io.element.android.features.roomlist.api.RoomListEntryPoint
import io.element.android.features.securebackup.api.SecureBackupEntryPoint
import io.element.android.features.share.api.ShareEntryPoint
import io.element.android.features.userprofile.api.UserProfileEntryPoint
import io.element.android.features.verifysession.api.IncomingVerificationEntryPoint
import io.element.android.libraries.architecture.BackstackView
import io.element.android.libraries.architecture.BaseFlowNode
import io.element.android.libraries.architecture.createNode
Expand All @@ -66,6 +69,8 @@ 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
Expand Down Expand Up @@ -99,6 +104,7 @@ class LoggedInFlowNode @AssistedInject constructor(
private val matrixClient: MatrixClient,
private val sendingQueue: SendQueues,
private val logoutEntryPoint: LogoutEntryPoint,
private val incomingVerificationEntryPoint: IncomingVerificationEntryPoint,
private val enableNativeSlidingSyncUseCase: EnableNativeSlidingSyncUseCase,
snackbarDispatcher: SnackbarDispatcher,
) : BaseFlowNode<LoggedInFlowNode.NavTarget>(
Expand All @@ -123,6 +129,12 @@ class LoggedInFlowNode @AssistedInject constructor(
matrixClient.roomMembershipObserver(),
)

private val verificationListener = object : SessionVerificationServiceListener {
override fun onIncomingSessionRequest(sessionVerificationRequestDetails: SessionVerificationRequestDetails) {
backstack.singleTop(NavTarget.IncomingVerificationRequest(sessionVerificationRequestDetails))
}
}

override fun onBuilt() {
super.onBuilt()
lifecycle.subscribe(
Expand All @@ -131,6 +143,7 @@ class LoggedInFlowNode @AssistedInject constructor(
// TODO We do not support Space yet, so directly navigate to main space
appNavigationStateService.onNavigateToSpace(id, MAIN_SPACE)
loggedInFlowProcessor.observeEvents(coroutineScope)
matrixClient.sessionVerificationService().setListener(verificationListener)

ftueService.state
.onEach { ftueState ->
Expand All @@ -152,6 +165,7 @@ class LoggedInFlowNode @AssistedInject constructor(
appNavigationStateService.onLeavingSpace(id)
appNavigationStateService.onLeavingSession(id)
loggedInFlowProcessor.stopObserving()
matrixClient.sessionVerificationService().setListener(null)
}
)
observeSyncStateAndNetworkStatus()
Expand Down Expand Up @@ -232,6 +246,9 @@ class LoggedInFlowNode @AssistedInject constructor(

@Parcelize
data object LogoutForNativeSlidingSyncMigrationNeeded : NavTarget

@Parcelize
data class IncomingVerificationRequest(val data: SessionVerificationRequestDetails) : NavTarget
}

override fun resolve(navTarget: NavTarget, buildContext: BuildContext): Node {
Expand Down Expand Up @@ -260,7 +277,7 @@ class LoggedInFlowNode @AssistedInject constructor(
}

override fun onSetUpRecoveryClick() {
backstack.push(NavTarget.SecureBackup(initialElement = SecureBackupEntryPoint.InitialTarget.SetUpRecovery))
backstack.push(NavTarget.SecureBackup(initialElement = SecureBackupEntryPoint.InitialTarget.Root))
}

override fun onSessionConfirmRecoveryKeyClick() {
Expand Down Expand Up @@ -432,6 +449,16 @@ class LoggedInFlowNode @AssistedInject constructor(
.callback(callback)
.build()
}
is NavTarget.IncomingVerificationRequest -> {
incomingVerificationEntryPoint.nodeBuilder(this, buildContext)
.params(IncomingVerificationEntryPoint.Params(navTarget.data))
.callback(object : IncomingVerificationEntryPoint.Callback {
override fun onDone() {
backstack.pop()
}
})
.build()
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,18 @@ private const val SAVE_INSTANCE_KEY = "io.element.android.x.di.MatrixClientsHold

@SingleIn(AppScope::class)
@ContributesBinding(AppScope::class)
class MatrixClientsHolder @Inject constructor(private val authenticationService: MatrixAuthenticationService) : MatrixClientProvider {
class MatrixClientsHolder @Inject constructor(
private val authenticationService: MatrixAuthenticationService,
) : MatrixClientProvider {
private val sessionIdsToMatrixClient = ConcurrentHashMap<SessionId, MatrixClient>()
private val restoreMutex = Mutex()

init {
authenticationService.listenToNewMatrixClients { matrixClient ->
sessionIdsToMatrixClient[matrixClient.sessionId] = matrixClient
}
}

fun removeAll() {
sessionIdsToMatrixClient.clear()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import io.element.android.libraries.matrix.api.MatrixClient
import io.element.android.libraries.matrix.api.encryption.EncryptionService
import io.element.android.libraries.matrix.api.encryption.RecoveryState
import io.element.android.libraries.matrix.api.roomlist.RoomListService
import io.element.android.libraries.matrix.api.sync.SlidingSyncVersion
import io.element.android.libraries.matrix.api.verification.SessionVerificationService
import io.element.android.libraries.matrix.api.verification.SessionVerifiedStatus
import io.element.android.libraries.preferences.api.store.EnableNativeSlidingSyncUseCase
Expand Down Expand Up @@ -102,10 +103,7 @@ class LoggedInPresenter @Inject constructor(
}
}
LoggedInEvents.CheckSlidingSyncProxyAvailability -> coroutineScope.launch {
// Force the user to log out if they were using the proxy sliding sync and it's no longer available, but native sliding sync is.
forceNativeSlidingSyncMigration = !matrixClient.isUsingNativeSlidingSync() &&
matrixClient.isNativeSlidingSyncSupported() &&
!matrixClient.isSlidingSyncProxySupported()
forceNativeSlidingSyncMigration = matrixClient.forceNativeSlidingSyncMigration().getOrDefault(false)
}
LoggedInEvents.LogoutAndMigrateToNativeSlidingSync -> coroutineScope.launch {
// Enable native sliding sync if it wasn't already the case
Expand All @@ -125,6 +123,18 @@ class LoggedInPresenter @Inject constructor(
)
}

// Force the user to log out if they were using the proxy sliding sync and it's no longer available, but native sliding sync is.
private suspend fun MatrixClient.forceNativeSlidingSyncMigration(): Result<Boolean> = runCatching {
val currentSlidingSyncVersion = currentSlidingSyncVersion().getOrThrow()
if (currentSlidingSyncVersion == SlidingSyncVersion.Proxy) {
val availableSlidingSyncVersions = availableSlidingSyncVersions().getOrThrow()
availableSlidingSyncVersions.contains(SlidingSyncVersion.Native) &&
!availableSlidingSyncVersions.contains(SlidingSyncVersion.Proxy)
} else {
false
}
}

private suspend fun ensurePusherIsRegistered(pusherRegistrationState: MutableState<AsyncData<Unit>>) {
Timber.tag(pusherTag.value).d("Ensure pusher is registered")
val currentPushProvider = pushService.getCurrentPushProvider()
Expand All @@ -142,12 +152,12 @@ class LoggedInPresenter @Inject constructor(
.also { Timber.tag(pusherTag.value).w("No distributors available") }
.also {
// In this case, consider the push provider is chosen.
pushService.selectPushProvider(matrixClient, pushProvider)
pushService.selectPushProvider(matrixClient.sessionId, pushProvider)
}
.also { pusherRegistrationState.value = AsyncData.Failure(PusherRegistrationFailure.NoDistributorsAvailable()) }
pushService.registerWith(matrixClient, pushProvider, distributor)
} else {
val currentPushDistributor = currentPushProvider.getCurrentDistributor(matrixClient)
val currentPushDistributor = currentPushProvider.getCurrentDistributor(matrixClient.sessionId)
if (currentPushDistributor == null) {
Timber.tag(pusherTag.value).d("Register with the first available distributor")
val distributor = currentPushProvider.getDistributors().firstOrNull()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,10 +45,14 @@ import io.element.android.libraries.matrix.api.MatrixClient
import io.element.android.libraries.matrix.api.core.RoomAlias
import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.matrix.api.core.RoomIdOrAlias
import io.element.android.libraries.matrix.api.core.toRoomIdOrAlias
import io.element.android.libraries.matrix.api.getRoomInfoFlow
import io.element.android.libraries.matrix.api.room.CurrentUserMembership
import io.element.android.libraries.matrix.api.room.RoomMembershipObserver
import io.element.android.libraries.matrix.api.room.alias.ResolvedRoomAlias
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch
Expand All @@ -65,6 +69,7 @@ class RoomFlowNode @AssistedInject constructor(
private val joinRoomEntryPoint: JoinRoomEntryPoint,
private val roomAliasResolverEntryPoint: RoomAliasResolverEntryPoint,
private val networkMonitor: NetworkMonitor,
private val membershipObserver: RoomMembershipObserver,
) : BaseFlowNode<RoomFlowNode.NavTarget>(
backstack = BackStack(
initialElement = NavTarget.Loading,
Expand Down Expand Up @@ -120,12 +125,9 @@ class RoomFlowNode @AssistedInject constructor(
}

private fun subscribeToRoomInfoFlow(roomId: RoomId, serverNames: List<String>) {
val roomInfoFlow = client.getRoomInfoFlow(
roomId = roomId
).map { it.getOrNull() }

val isSpaceFlow = roomInfoFlow.map { it?.isSpace.orFalse() }.distinctUntilChanged()
val currentMembershipFlow = roomInfoFlow.map { it?.currentUserMembership }.distinctUntilChanged()
val roomInfoFlow = client.getRoomInfoFlow(roomIdOrAlias = roomId.toRoomIdOrAlias())
val isSpaceFlow = roomInfoFlow.map { it.getOrNull()?.isSpace.orFalse() }.distinctUntilChanged()
val currentMembershipFlow = roomInfoFlow.map { it.getOrNull()?.currentUserMembership }.distinctUntilChanged()
combine(currentMembershipFlow, isSpaceFlow) { membership, isSpace ->
Timber.d("Room membership: $membership")
when (membership) {
Expand All @@ -146,10 +148,6 @@ class RoomFlowNode @AssistedInject constructor(
backstack.newRoot(NavTarget.JoinedRoom(roomId))
}
}
CurrentUserMembership.LEFT -> {
// Left the room, navigate out of this flow
navigateUp()
}
else -> {
// Was invited or the room is not known, display the join room screen
backstack.newRoot(
Expand All @@ -162,6 +160,15 @@ class RoomFlowNode @AssistedInject constructor(
}
}
}.launchIn(lifecycleScope)

// If the user leaves the room from this client, close the room flow.
lifecycleScope.launch {
membershipObserver.updates
.first { it.roomId == roomId && !it.isUserInRoom }
.run {
navigateUp()
}
}
}

override fun resolve(navTarget: NavTarget, buildContext: BuildContext): Node {
Expand Down
2 changes: 1 addition & 1 deletion appnav/src/main/res/values-el/translations.xml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="banner_migrate_to_native_sliding_sync_action">"Αποσύνδεση &amp;amp; Αναβάθμιση"</string>
<string name="banner_migrate_to_native_sliding_sync_action">"Αποσύνδεση &amp; Αναβάθμιση"</string>
<string name="banner_migrate_to_native_sliding_sync_force_logout_title">"Ο οικιακός διακομιστής σου δεν υποστηρίζει πλέον το παλιό πρωτόκολλο. Αποσυνδέσου και συνδέσου ξανά για να συνεχίσεις να χρησιμοποιείς την εφαρμογή."</string>
</resources>
Loading

0 comments on commit 184b078

Please sign in to comment.