Skip to content

Commit 183ffd2

Browse files
committed
Merge branch 'release/0.7.3' into main
2 parents d7282dd + e2275cb commit 183ffd2

File tree

987 files changed

+8406
-4241
lines changed

Some content is hidden

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

987 files changed

+8406
-4241
lines changed

.github/workflows/maestro.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ jobs:
7979
uses: actions/download-artifact@v4
8080
with:
8181
name: elementx-apk-maestro
82-
- uses: mobile-dev-inc/[email protected].2
82+
- uses: mobile-dev-inc/[email protected].4
8383
if: (github.event_name == 'pull_request' && github.event.pull_request.fork == null) || github.event_name == 'workflow_dispatch'
8484
with:
8585
api-key: ${{ secrets.MAESTRO_CLOUD_API_KEY }}

.idea/kotlinc.xml

+1-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

CHANGES.md

+16
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,19 @@
1+
Changes in Element X v0.7.2 (2024-10-29)
2+
========================================
3+
4+
## What's Changed
5+
### 🙌 Improvements
6+
* Add setting to compress image and video by @bmarty in https://github.com/element-hq/element-x-android/pull/3744
7+
### 🗣 Translations
8+
* Sync Strings by @ElementBot in https://github.com/element-hq/element-x-android/pull/3743
9+
### 🧱 Build
10+
* Release script improvement by @bmarty in https://github.com/element-hq/element-x-android/pull/3741
11+
### Dependency upgrades
12+
* Update dependency org.maplibre.gl:android-sdk to v11.5.2 by @renovate in https://github.com/element-hq/element-x-android/pull/3720
13+
* Update dependency io.sentry:sentry-android to v7.16.0 by @renovate in https://github.com/element-hq/element-x-android/pull/3726
14+
* Update dependencyAnalysis to v2.3.0 by @renovate in https://github.com/element-hq/element-x-android/pull/3740
15+
* Update dependency org.matrix.rustcomponents:sdk-android to v0.2.58 by @renovate in https://github.com/element-hq/element-x-android/pull/3749
16+
117
Changes in Element X v0.7.1 (2024-10-25)
218
========================================
319

README.md

+2-2
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
# Element X Android
1010

11-
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.
11+
Element X Android is a [Matrix](https://matrix.org/) Android Client provided by [element.io](https://element.io/).
1212

1313
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).
1414

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

7272
## Status
7373

74-
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).
74+
This project is in an early rollout and migration phase.
7575

7676
## Contributing
7777

app/build.gradle.kts

+3-3
Original file line numberDiff line numberDiff line change
@@ -48,9 +48,9 @@ android {
4848
} else {
4949
"io.element.android.x"
5050
}
51-
targetSdk = Versions.targetSdk
52-
versionCode = Versions.versionCode
53-
versionName = Versions.versionName
51+
targetSdk = Versions.TARGET_SDK
52+
versionCode = Versions.VERSION_CODE
53+
versionName = Versions.VERSION_NAME
5454

5555
// Keep abiFilter for the universalApk
5656
ndk {

app/src/main/AndroidManifest.xml

+1-2
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,11 @@
1010
<!-- To be able to install APK from the application -->
1111
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
1212

13-
<!-- Do not enable enableOnBackInvokedCallback until https://issuetracker.google.com/issues/271303558 is fixed -->
1413
<application
1514
android:name=".ElementXApplication"
1615
android:allowBackup="false"
1716
android:dataExtractionRules="@xml/data_extraction_rules"
18-
android:enableOnBackInvokedCallback="false"
17+
android:enableOnBackInvokedCallback="true"
1918
android:fullBackupContent="@xml/backup_rules"
2019
android:icon="@mipmap/ic_launcher"
2120
android:label="@string/app_name"

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

+28-1
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,10 @@ import com.bumble.appyx.core.node.Node
2525
import com.bumble.appyx.core.plugin.Plugin
2626
import com.bumble.appyx.core.plugin.plugins
2727
import com.bumble.appyx.navmodel.backstack.BackStack
28+
import com.bumble.appyx.navmodel.backstack.operation.pop
2829
import com.bumble.appyx.navmodel.backstack.operation.push
2930
import com.bumble.appyx.navmodel.backstack.operation.replace
31+
import com.bumble.appyx.navmodel.backstack.operation.singleTop
3032
import dagger.assisted.Assisted
3133
import dagger.assisted.AssistedInject
3234
import im.vector.app.features.analytics.plan.JoinedRoom
@@ -50,6 +52,7 @@ import io.element.android.features.roomlist.api.RoomListEntryPoint
5052
import io.element.android.features.securebackup.api.SecureBackupEntryPoint
5153
import io.element.android.features.share.api.ShareEntryPoint
5254
import io.element.android.features.userprofile.api.UserProfileEntryPoint
55+
import io.element.android.features.verifysession.api.IncomingVerificationEntryPoint
5356
import io.element.android.libraries.architecture.BackstackView
5457
import io.element.android.libraries.architecture.BaseFlowNode
5558
import io.element.android.libraries.architecture.createNode
@@ -66,6 +69,8 @@ import io.element.android.libraries.matrix.api.core.UserId
6669
import io.element.android.libraries.matrix.api.core.toRoomIdOrAlias
6770
import io.element.android.libraries.matrix.api.permalink.PermalinkData
6871
import io.element.android.libraries.matrix.api.sync.SyncState
72+
import io.element.android.libraries.matrix.api.verification.SessionVerificationRequestDetails
73+
import io.element.android.libraries.matrix.api.verification.SessionVerificationServiceListener
6974
import io.element.android.libraries.preferences.api.store.EnableNativeSlidingSyncUseCase
7075
import io.element.android.services.appnavstate.api.AppNavigationStateService
7176
import kotlinx.coroutines.CoroutineScope
@@ -99,6 +104,7 @@ class LoggedInFlowNode @AssistedInject constructor(
99104
private val matrixClient: MatrixClient,
100105
private val sendingQueue: SendQueues,
101106
private val logoutEntryPoint: LogoutEntryPoint,
107+
private val incomingVerificationEntryPoint: IncomingVerificationEntryPoint,
102108
private val enableNativeSlidingSyncUseCase: EnableNativeSlidingSyncUseCase,
103109
snackbarDispatcher: SnackbarDispatcher,
104110
) : BaseFlowNode<LoggedInFlowNode.NavTarget>(
@@ -123,6 +129,12 @@ class LoggedInFlowNode @AssistedInject constructor(
123129
matrixClient.roomMembershipObserver(),
124130
)
125131

132+
private val verificationListener = object : SessionVerificationServiceListener {
133+
override fun onIncomingSessionRequest(sessionVerificationRequestDetails: SessionVerificationRequestDetails) {
134+
backstack.singleTop(NavTarget.IncomingVerificationRequest(sessionVerificationRequestDetails))
135+
}
136+
}
137+
126138
override fun onBuilt() {
127139
super.onBuilt()
128140
lifecycle.subscribe(
@@ -131,6 +143,7 @@ class LoggedInFlowNode @AssistedInject constructor(
131143
// TODO We do not support Space yet, so directly navigate to main space
132144
appNavigationStateService.onNavigateToSpace(id, MAIN_SPACE)
133145
loggedInFlowProcessor.observeEvents(coroutineScope)
146+
matrixClient.sessionVerificationService().setListener(verificationListener)
134147

135148
ftueService.state
136149
.onEach { ftueState ->
@@ -152,6 +165,7 @@ class LoggedInFlowNode @AssistedInject constructor(
152165
appNavigationStateService.onLeavingSpace(id)
153166
appNavigationStateService.onLeavingSession(id)
154167
loggedInFlowProcessor.stopObserving()
168+
matrixClient.sessionVerificationService().setListener(null)
155169
}
156170
)
157171
observeSyncStateAndNetworkStatus()
@@ -232,6 +246,9 @@ class LoggedInFlowNode @AssistedInject constructor(
232246

233247
@Parcelize
234248
data object LogoutForNativeSlidingSyncMigrationNeeded : NavTarget
249+
250+
@Parcelize
251+
data class IncomingVerificationRequest(val data: SessionVerificationRequestDetails) : NavTarget
235252
}
236253

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

262279
override fun onSetUpRecoveryClick() {
263-
backstack.push(NavTarget.SecureBackup(initialElement = SecureBackupEntryPoint.InitialTarget.SetUpRecovery))
280+
backstack.push(NavTarget.SecureBackup(initialElement = SecureBackupEntryPoint.InitialTarget.Root))
264281
}
265282

266283
override fun onSessionConfirmRecoveryKeyClick() {
@@ -432,6 +449,16 @@ class LoggedInFlowNode @AssistedInject constructor(
432449
.callback(callback)
433450
.build()
434451
}
452+
is NavTarget.IncomingVerificationRequest -> {
453+
incomingVerificationEntryPoint.nodeBuilder(this, buildContext)
454+
.params(IncomingVerificationEntryPoint.Params(navTarget.data))
455+
.callback(object : IncomingVerificationEntryPoint.Callback {
456+
override fun onDone() {
457+
backstack.pop()
458+
}
459+
})
460+
.build()
461+
}
435462
}
436463
}
437464

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

+9-1
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,18 @@ private const val SAVE_INSTANCE_KEY = "io.element.android.x.di.MatrixClientsHold
2727

2828
@SingleIn(AppScope::class)
2929
@ContributesBinding(AppScope::class)
30-
class MatrixClientsHolder @Inject constructor(private val authenticationService: MatrixAuthenticationService) : MatrixClientProvider {
30+
class MatrixClientsHolder @Inject constructor(
31+
private val authenticationService: MatrixAuthenticationService,
32+
) : MatrixClientProvider {
3133
private val sessionIdsToMatrixClient = ConcurrentHashMap<SessionId, MatrixClient>()
3234
private val restoreMutex = Mutex()
3335

36+
init {
37+
authenticationService.listenToNewMatrixClients { matrixClient ->
38+
sessionIdsToMatrixClient[matrixClient.sessionId] = matrixClient
39+
}
40+
}
41+
3442
fun removeAll() {
3543
sessionIdsToMatrixClient.clear()
3644
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
3+
<string name="banner_migrate_to_native_sliding_sync_action">"Uitloggen &amp; Upgraden"</string>
4+
<string name="banner_migrate_to_native_sliding_sync_force_logout_title">"Je homeserver ondersteunt het oude protocol niet meer. Log uit en log opnieuw in om de app te blijven gebruiken."</string>
5+
</resources>

appnav/src/test/kotlin/io/element/android/appnav/di/MatrixClientsHolderTest.kt

+13
Original file line numberDiff line numberDiff line change
@@ -81,4 +81,17 @@ class MatrixClientsHolderTest {
8181
matrixClientsHolder.restoreWithSavedState(savedStateMap)
8282
assertThat(matrixClientsHolder.getOrNull(A_SESSION_ID)).isEqualTo(fakeMatrixClient)
8383
}
84+
85+
@Test
86+
fun `test AuthenticationService listenToNewMatrixClients emits a Client value and we save it`() = runTest {
87+
val fakeAuthenticationService = FakeMatrixAuthenticationService()
88+
val matrixClientsHolder = MatrixClientsHolder(fakeAuthenticationService)
89+
assertThat(matrixClientsHolder.getOrNull(A_SESSION_ID)).isNull()
90+
91+
fakeAuthenticationService.givenMatrixClient(FakeMatrixClient(sessionId = A_SESSION_ID))
92+
val loginSucceeded = fakeAuthenticationService.login("user", "pass")
93+
94+
assertThat(loginSucceeded.isSuccess).isTrue()
95+
assertThat(matrixClientsHolder.getOrNull(A_SESSION_ID)).isNotNull()
96+
}
8497
}

build.gradle.kts

+1-1
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ allprojects {
4949
config.from(files("$rootDir/tools/detekt/detekt.yml"))
5050
}
5151
dependencies {
52-
detektPlugins("io.nlopez.compose.rules:detekt:0.4.16")
52+
detektPlugins("io.nlopez.compose.rules:detekt:0.4.17")
5353
}
5454

5555
// KtLint
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Main changes in this version: TODO.
2+
Full changelog: https://github.com/element-hq/element-x-android/releases
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
/*
2+
* Copyright 2024 New Vector Ltd.
3+
*
4+
* SPDX-License-Identifier: AGPL-3.0-only
5+
* Please see LICENSE in the repository root for full details.
6+
*/
7+
8+
package io.element.android.features.call.api
9+
10+
import io.element.android.libraries.matrix.api.core.RoomId
11+
12+
/**
13+
* Value for the local current call.
14+
*/
15+
sealed interface CurrentCall {
16+
data object None : CurrentCall
17+
18+
data class RoomCall(
19+
val roomId: RoomId,
20+
) : CurrentCall
21+
22+
data class ExternalUrl(
23+
val url: String,
24+
) : CurrentCall
25+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
/*
2+
* Copyright 2024 New Vector Ltd.
3+
*
4+
* SPDX-License-Identifier: AGPL-3.0-only
5+
* Please see LICENSE in the repository root for full details.
6+
*/
7+
8+
package io.element.android.features.call.api
9+
10+
import kotlinx.coroutines.flow.StateFlow
11+
12+
interface CurrentCallService {
13+
/**
14+
* The current call state flow, which will be updated when the active call changes.
15+
* This value reflect the local state of the call. It is not updated if the user answers
16+
* a call from another session.
17+
*/
18+
val currentCall: StateFlow<CurrentCall>
19+
}

features/call/impl/src/main/kotlin/io/element/android/features/call/impl/ui/CallScreenView.kt

+1
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,7 @@ private fun WebView.setup(
183183
allowFileAccess = true
184184
domStorageEnabled = true
185185
mediaPlaybackRequiresUserGesture = false
186+
@Suppress("DEPRECATION")
186187
databaseEnabled = true
187188
loadsImagesAutomatically = true
188189
userAgentString = userAgent

features/call/impl/src/main/kotlin/io/element/android/features/call/impl/ui/ElementCallActivity.kt

+22-18
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ import androidx.core.content.IntentCompat
3535
import androidx.core.util.Consumer
3636
import androidx.lifecycle.Lifecycle
3737
import io.element.android.features.call.api.CallType
38+
import io.element.android.features.call.api.CallType.ExternalUrl
3839
import io.element.android.features.call.impl.DefaultElementCallEntryPoint
3940
import io.element.android.features.call.impl.di.CallBindings
4041
import io.element.android.features.call.impl.pip.PictureInPictureEvents
@@ -44,11 +45,14 @@ import io.element.android.features.call.impl.pip.PipView
4445
import io.element.android.features.call.impl.services.CallForegroundService
4546
import io.element.android.features.call.impl.utils.CallIntentDataParser
4647
import io.element.android.libraries.architecture.bindings
48+
import io.element.android.libraries.core.log.logger.LoggerTag
4749
import io.element.android.libraries.designsystem.theme.ElementThemeApp
4850
import io.element.android.libraries.preferences.api.store.AppPreferencesStore
4951
import timber.log.Timber
5052
import javax.inject.Inject
5153

54+
private val loggerTag = LoggerTag("ElementCallActivity")
55+
5256
class ElementCallActivity :
5357
AppCompatActivity(),
5458
CallScreenNavigator,
@@ -132,7 +136,7 @@ class ElementCallActivity :
132136
DisposableEffect(Unit) {
133137
val listener = Runnable {
134138
if (requestPermissionCallback != null) {
135-
Timber.w("Ignoring onUserLeaveHint event because user is asked to grant permissions")
139+
Timber.tag(loggerTag.value).w("Ignoring onUserLeaveHint event because user is asked to grant permissions")
136140
} else {
137141
pipEventSink(PictureInPictureEvents.EnterPictureInPicture)
138142
}
@@ -146,7 +150,7 @@ class ElementCallActivity :
146150
val onPictureInPictureModeChangedListener = Consumer { _: PictureInPictureModeChangedInfo ->
147151
pipEventSink(PictureInPictureEvents.OnPictureInPictureModeChanged(isInPictureInPictureMode))
148152
if (!isInPictureInPictureMode && !lifecycle.currentState.isAtLeast(Lifecycle.State.STARTED)) {
149-
Timber.d("Exiting PiP mode: Hangup the call")
153+
Timber.tag(loggerTag.value).d("Exiting PiP mode: Hangup the call")
150154
eventSink?.invoke(CallScreenEvents.Hangup)
151155
}
152156
}
@@ -185,23 +189,23 @@ class ElementCallActivity :
185189

186190
private fun setCallType(intent: Intent?) {
187191
val callType = intent?.let {
188-
IntentCompat.getParcelableExtra(it, DefaultElementCallEntryPoint.EXTRA_CALL_TYPE, CallType::class.java)
192+
IntentCompat.getParcelableExtra(intent, DefaultElementCallEntryPoint.EXTRA_CALL_TYPE, CallType::class.java)
193+
?: intent.dataString?.let(::parseUrl)?.let(::ExternalUrl)
189194
}
190-
val intentUrl = intent?.dataString?.let(::parseUrl)
191-
when {
192-
// Re-opened the activity but we have no url to load or a cached one, finish the activity
193-
intent?.dataString == null && callType == null && webViewTarget.value == null -> finish()
194-
callType != null -> {
195-
webViewTarget.value = callType
196-
presenter = presenterFactory.create(callType, this)
197-
}
198-
intentUrl != null -> {
199-
val fallbackInputs = CallType.ExternalUrl(intentUrl)
200-
webViewTarget.value = fallbackInputs
201-
presenter = presenterFactory.create(fallbackInputs, this)
202-
}
203-
// Coming back from notification, do nothing
204-
else -> return
195+
val currentCallType = webViewTarget.value
196+
if (currentCallType == null && callType == null) {
197+
Timber.tag(loggerTag.value).d("Re-opened the activity but we have no url to load or a cached one, finish the activity")
198+
finish()
199+
} else if (currentCallType == null) {
200+
Timber.tag(loggerTag.value).d("Set the call type and create the presenter")
201+
webViewTarget.value = callType
202+
presenter = presenterFactory.create(callType!!, this)
203+
} else if (callType != currentCallType) {
204+
Timber.tag(loggerTag.value).d("User starts another call, restart the Activity")
205+
setIntent(intent)
206+
recreate()
207+
} else {
208+
Timber.tag(loggerTag.value).d("Coming back from notification, do nothing")
205209
}
206210
}
207211

0 commit comments

Comments
 (0)