Skip to content

Commit 612749b

Browse files
committed
Merge branch 'release/25.03.4'
2 parents 490e76c + 64f16d5 commit 612749b

File tree

12 files changed

+155
-85
lines changed

12 files changed

+155
-85
lines changed

CHANGES.md

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,34 @@
1+
Changes in Element X v25.03.3
2+
=============================
3+
4+
<!-- Release notes generated using configuration in .github/release.yml at v25.03.3 -->
5+
6+
## What's Changed
7+
### ✨ Features
8+
* Add 'unencrypted room' badges and labels by @jmartinesp in https://github.com/element-hq/element-x-android/pull/4445
9+
* Use embedded version of Element Call by @jmartinesp in https://github.com/element-hq/element-x-android/pull/4470
10+
### 🐛 Bugfixes
11+
* Fix 'unverified session' flow displayed when creating account by @jmartinesp in https://github.com/element-hq/element-x-android/pull/4467
12+
### 🗣 Translations
13+
* Sync Strings by @ElementBot in https://github.com/element-hq/element-x-android/pull/4461
14+
### 🧱 Build
15+
* Let element enterprise be able to configure id for mapTiler. by @bmarty in https://github.com/element-hq/element-x-android/pull/4446
16+
### Dependency upgrades
17+
* chore(deps): update rnkdsh/action-upload-diawi action to v1.5.8 by @renovate in https://github.com/element-hq/element-x-android/pull/4457
18+
* chore(deps): update plugin licensee to v1.13.0 by @renovate in https://github.com/element-hq/element-x-android/pull/4447
19+
* fix(deps): update dependency org.maplibre.gl:android-sdk to v11.8.4 by @renovate in https://github.com/element-hq/element-x-android/pull/4450
20+
* fix(deps): update dependency com.google.firebase:firebase-bom to v33.11.0 by @renovate in https://github.com/element-hq/element-x-android/pull/4448
21+
* fix(deps): update dependency org.matrix.rustcomponents:sdk-android to v25.3.24 by @renovate in https://github.com/element-hq/element-x-android/pull/4394
22+
* fix(deps): update dependencyanalysis to v2.13.1 by @renovate in https://github.com/element-hq/element-x-android/pull/4464
23+
* chore(deps): update plugin sonarqube to v6.1.0.5360 by @renovate in https://github.com/element-hq/element-x-android/pull/4468
24+
* fix(deps): update android.gradle.plugin to v8.9.1 by @renovate in https://github.com/element-hq/element-x-android/pull/4465
25+
### Others
26+
* Sync Strings - tweaks to identity change messages by @andybalaam in https://github.com/element-hq/element-x-android/pull/4454
27+
* Check link click by @bmarty in https://github.com/element-hq/element-x-android/pull/4463
28+
29+
30+
**Full Changelog**: https://github.com/element-hq/element-x-android/compare/v25.03.2...v25.03.3
31+
132
Changes in Element X v25.03.2
233
=============================
334

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Main changes in this version: added a fix for Element Call not being able to report issues.
2+
Full changelog: https://github.com/element-hq/element-x-android/releases

features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerPresenter.kt

Lines changed: 43 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import androidx.compose.runtime.rememberCoroutineScope
2323
import androidx.compose.runtime.rememberUpdatedState
2424
import androidx.compose.runtime.saveable.rememberSaveable
2525
import androidx.compose.runtime.setValue
26+
import androidx.compose.runtime.snapshots.SnapshotStateList
2627
import androidx.media3.common.MimeTypes
2728
import androidx.media3.common.util.UnstableApi
2829
import dagger.assisted.Assisted
@@ -84,11 +85,13 @@ import kotlinx.coroutines.CoroutineScope
8485
import kotlinx.coroutines.FlowPreview
8586
import kotlinx.coroutines.delay
8687
import kotlinx.coroutines.flow.MutableStateFlow
88+
import kotlinx.coroutines.flow.SharingStarted
8789
import kotlinx.coroutines.flow.collect
8890
import kotlinx.coroutines.flow.combine
8991
import kotlinx.coroutines.flow.debounce
9092
import kotlinx.coroutines.flow.filter
9193
import kotlinx.coroutines.flow.merge
94+
import kotlinx.coroutines.flow.stateIn
9295
import kotlinx.coroutines.launch
9396
import timber.log.Timber
9497
import kotlin.time.Duration.Companion.seconds
@@ -135,7 +138,6 @@ class MessageComposerPresenter @AssistedInject constructor(
135138
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
136139
internal var showTextFormatting: Boolean by mutableStateOf(false)
137140

138-
@OptIn(FlowPreview::class)
139141
@SuppressLint("UnsafeOptInUsageError")
140142
@Composable
141143
override fun present(): MessageComposerState {
@@ -148,12 +150,6 @@ class MessageComposerPresenter @AssistedInject constructor(
148150
richTextEditorState.isReadyToProcessActions = true
149151
}
150152
val markdownTextEditorState = rememberMarkdownTextEditorState(initialText = null, initialFocus = false)
151-
var isMentionsEnabled by remember { mutableStateOf(false) }
152-
var isRoomAliasSuggestionsEnabled by remember { mutableStateOf(false) }
153-
LaunchedEffect(Unit) {
154-
isMentionsEnabled = featureFlagService.isFeatureEnabled(FeatureFlags.Mentions)
155-
isRoomAliasSuggestionsEnabled = featureFlagService.isFeatureEnabled(FeatureFlags.RoomAliasSuggestions)
156-
}
157153

158154
val cameraPermissionState = cameraPermissionPresenter.present()
159155

@@ -187,8 +183,6 @@ class MessageComposerPresenter @AssistedInject constructor(
187183

188184
val sendTypingNotifications by sessionPreferencesStore.isSendTypingNotificationsEnabled().collectAsState(initial = true)
189185

190-
val roomAliasSuggestions by roomAliasSuggestionsDataSource.getAllRoomAliasSuggestions().collectAsState(initial = emptyList())
191-
192186
LaunchedEffect(cameraPermissionState.permissionGranted) {
193187
if (cameraPermissionState.permissionGranted) {
194188
when (pendingEvent) {
@@ -201,35 +195,7 @@ class MessageComposerPresenter @AssistedInject constructor(
201195
}
202196

203197
val suggestions = remember { mutableStateListOf<ResolvedSuggestion>() }
204-
LaunchedEffect(isMentionsEnabled) {
205-
if (!isMentionsEnabled) return@LaunchedEffect
206-
val currentUserId = room.sessionId
207-
208-
suspend fun canSendRoomMention(): Boolean {
209-
val userCanSendAtRoom = room.canUserTriggerRoomNotification(currentUserId).getOrDefault(false)
210-
return !room.isDm() && userCanSendAtRoom
211-
}
212-
213-
// This will trigger a search immediately when `@` is typed
214-
val mentionStartTrigger = suggestionSearchTrigger.filter { it?.text.isNullOrEmpty() }
215-
// This will start a search when the user changes the text after the `@` with a debounce to prevent too much wasted work
216-
val mentionCompletionTrigger = suggestionSearchTrigger.debounce(0.3.seconds).filter { !it?.text.isNullOrEmpty() }
217-
merge(mentionStartTrigger, mentionCompletionTrigger)
218-
.combine(room.membersStateFlow) { suggestion, roomMembersState ->
219-
suggestions.clear()
220-
val result = suggestionsProcessor.process(
221-
suggestion = suggestion,
222-
roomMembersState = roomMembersState,
223-
roomAliasSuggestions = if (isRoomAliasSuggestionsEnabled) roomAliasSuggestions else emptyList(),
224-
currentUserId = currentUserId,
225-
canSendRoomMention = ::canSendRoomMention,
226-
)
227-
if (result.isNotEmpty()) {
228-
suggestions.addAll(result)
229-
}
230-
}
231-
.collect()
232-
}
198+
ResolveSuggestionsEffect(suggestions)
233199

234200
DisposableEffect(Unit) {
235201
// Declare that the user is not typing anymore when the composer is disposed
@@ -409,6 +375,45 @@ class MessageComposerPresenter @AssistedInject constructor(
409375
)
410376
}
411377

378+
@OptIn(FlowPreview::class)
379+
@Composable
380+
private fun ResolveSuggestionsEffect(
381+
suggestions: SnapshotStateList<ResolvedSuggestion>,
382+
) {
383+
LaunchedEffect(Unit) {
384+
val currentUserId = room.sessionId
385+
386+
suspend fun canSendRoomMention(): Boolean {
387+
val userCanSendAtRoom = room.canUserTriggerRoomNotification(currentUserId).getOrDefault(false)
388+
return !room.isDm() && userCanSendAtRoom
389+
}
390+
391+
// This will trigger a search immediately when `@` is typed
392+
val mentionStartTrigger = suggestionSearchTrigger.filter { it?.text.isNullOrEmpty() }
393+
// This will start a search when the user changes the text after the `@` with a debounce to prevent too much wasted work
394+
val mentionCompletionTrigger = suggestionSearchTrigger.debounce(0.3.seconds).filter { !it?.text.isNullOrEmpty() }
395+
396+
val mentionTriggerFlow = merge(mentionStartTrigger, mentionCompletionTrigger)
397+
398+
val roomAliasSuggestionsFlow = roomAliasSuggestionsDataSource
399+
.getAllRoomAliasSuggestions()
400+
.stateIn(this, SharingStarted.Lazily, emptyList())
401+
402+
combine(mentionTriggerFlow, room.membersStateFlow, roomAliasSuggestionsFlow) { suggestion, roomMembersState, roomAliasSuggestions ->
403+
val result = suggestionsProcessor.process(
404+
suggestion = suggestion,
405+
roomMembersState = roomMembersState,
406+
roomAliasSuggestions = roomAliasSuggestions,
407+
currentUserId = currentUserId,
408+
canSendRoomMention = ::canSendRoomMention,
409+
)
410+
suggestions.clear()
411+
suggestions.addAll(result)
412+
}
413+
.collect()
414+
}
415+
}
416+
412417
private fun CoroutineScope.sendMessage(
413418
markdownTextEditorState: MarkdownTextEditorState,
414419
richTextEditorState: RichTextEditorState,

features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/suggestions/SuggestionsProcessor.kt

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,11 @@ class SuggestionsProcessor @Inject constructor() {
5353
}
5454
SuggestionType.Room -> {
5555
roomAliasSuggestions
56-
.filter { it.roomAlias.value.contains(suggestion.text, ignoreCase = true) }
56+
.filter { roomAliasSuggestion ->
57+
// Filter by either room alias or room name (if available)
58+
roomAliasSuggestion.roomAlias.value.contains(suggestion.text, ignoreCase = true) ||
59+
roomAliasSuggestion.roomName?.contains(suggestion.text, ignoreCase = true) == true
60+
}
5761
.map {
5862
ResolvedSuggestion.Alias(
5963
roomAlias = it.roomAlias,

features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerPresenterTest.kt

Lines changed: 11 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,6 @@ import io.element.android.libraries.matrix.test.A_USER_ID
6161
import io.element.android.libraries.matrix.test.A_USER_ID_2
6262
import io.element.android.libraries.matrix.test.A_USER_ID_3
6363
import io.element.android.libraries.matrix.test.A_USER_ID_4
64-
import io.element.android.libraries.matrix.test.core.aBuildMeta
6564
import io.element.android.libraries.matrix.test.permalink.FakePermalinkBuilder
6665
import io.element.android.libraries.matrix.test.permalink.FakePermalinkParser
6766
import io.element.android.libraries.matrix.test.room.FakeMatrixRoom
@@ -1011,16 +1010,10 @@ class MessageComposerPresenterTest {
10111010
)
10121011
givenRoomInfo(aRoomInfo(isDirect = false))
10131012
}
1014-
val flagsService = FakeFeatureFlagService(
1015-
mapOf(
1016-
FeatureFlags.Mentions.key to true,
1017-
)
1018-
)
1019-
val presenter = createPresenter(this, room, featureFlagService = flagsService)
1013+
val presenter = createPresenter(this, room)
10201014
moleculeFlow(RecompositionMode.Immediate) {
10211015
presenter.present()
10221016
}.test {
1023-
skipItems(1)
10241017
val initialState = awaitItem()
10251018

10261019
// A null suggestion (no suggestion was received) returns nothing
@@ -1078,16 +1071,10 @@ class MessageComposerPresenterTest {
10781071
)
10791072
)
10801073
}
1081-
val flagsService = FakeFeatureFlagService(
1082-
mapOf(
1083-
FeatureFlags.Mentions.key to true,
1084-
)
1085-
)
1086-
val presenter = createPresenter(this, room, featureFlagService = flagsService)
1074+
val presenter = createPresenter(this, room)
10871075
moleculeFlow(RecompositionMode.Immediate) {
10881076
presenter.present()
10891077
}.test {
1090-
skipItems(1)
10911078
val initialState = awaitItem()
10921079

10931080
// An empty suggestion returns the joined members that are not the current user, but not the room
@@ -1293,11 +1280,11 @@ class MessageComposerPresenterTest {
12931280
moleculeFlow(RecompositionMode.Immediate) {
12941281
presenter.present()
12951282
}.test {
1296-
awaitFirstItem().also { state ->
1283+
skipItems(2)
1284+
awaitItem().also { state ->
12971285
assertThat(state.textEditorState.messageMarkdown(permalinkBuilder)).isEqualTo(A_MESSAGE)
12981286
assertThat(state.textEditorState.messageHtml()).isNull()
12991287
}
1300-
13011288
assert(loadDraftLambda)
13021289
.isCalledOnce()
13031290
.with(value(A_ROOM_ID), value(false))
@@ -1327,7 +1314,8 @@ class MessageComposerPresenterTest {
13271314
moleculeFlow(RecompositionMode.Immediate) {
13281315
presenter.present()
13291316
}.test {
1330-
awaitFirstItem().also { state ->
1317+
skipItems(1)
1318+
awaitItem().also { state ->
13311319
assertThat(state.showTextFormatting).isTrue()
13321320
assertThat(state.textEditorState.messageMarkdown(permalinkBuilder)).isEqualTo(A_MESSAGE)
13331321
assertThat(state.textEditorState.messageHtml()).isEqualTo(A_MESSAGE)
@@ -1360,7 +1348,8 @@ class MessageComposerPresenterTest {
13601348
moleculeFlow(RecompositionMode.Immediate) {
13611349
presenter.present()
13621350
}.test {
1363-
awaitFirstItem().also { state ->
1351+
skipItems(2)
1352+
awaitItem().also { state ->
13641353
assertThat(state.showTextFormatting).isFalse()
13651354
assertThat(state.mode).isEqualTo(anEditMode())
13661355
assertThat(state.textEditorState.messageMarkdown(permalinkBuilder)).isEqualTo(A_MESSAGE)
@@ -1406,7 +1395,8 @@ class MessageComposerPresenterTest {
14061395
moleculeFlow(RecompositionMode.Immediate) {
14071396
presenter.present()
14081397
}.test {
1409-
awaitFirstItem().also { state ->
1398+
skipItems(2)
1399+
awaitItem().also { state ->
14101400
assertThat(state.showTextFormatting).isFalse()
14111401
assertThat(state.mode).isEqualTo(aReplyMode())
14121402
assertThat(state.textEditorState.messageMarkdown(permalinkBuilder)).isEqualTo(A_MESSAGE)
@@ -1580,8 +1570,7 @@ class MessageComposerPresenterTest {
15801570
}
15811571

15821572
private suspend fun <T> ReceiveTurbine<T>.awaitFirstItem(): T {
1583-
// Skip 2 item if Mentions feature is enabled, else 1
1584-
skipItems(if (FeatureFlags.Mentions.defaultValue(aBuildMeta())) 2 else 1)
1573+
skipItems(1)
15851574
return awaitItem()
15861575
}
15871576
}

features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/messagecomposer/suggestions/SuggestionsProcessorTest.kt

Lines changed: 51 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ class SuggestionsProcessorTest {
133133
}
134134

135135
@Test
136-
fun `processing Room suggestion with aliases will return a suggestion`() = runTest {
136+
fun `processing Room suggestion with aliases will return a suggestion when matching on alias`() = runTest {
137137
val aRoomSummary = aRoomSummary(canonicalAlias = A_ROOM_ALIAS)
138138
val result = suggestionsProcessor.process(
139139
suggestion = aRoomSuggestion("ali"),
@@ -171,7 +171,56 @@ class SuggestionsProcessorTest {
171171
RoomAliasSuggestion(
172172
roomAlias = A_ROOM_ALIAS,
173173
roomId = aRoomSummary.roomId,
174-
roomName = aRoomSummary.info.name,
174+
roomName = "Element",
175+
roomAvatarUrl = aRoomSummary.info.avatarUrl,
176+
)
177+
),
178+
currentUserId = A_USER_ID,
179+
canSendRoomMention = { true },
180+
)
181+
assertThat(result).isEmpty()
182+
}
183+
184+
@Test
185+
fun `processing Room suggestion will return a suggestion when matching on room name`() = runTest {
186+
val aRoomSummary = aRoomSummary(canonicalAlias = A_ROOM_ALIAS)
187+
val result = suggestionsProcessor.process(
188+
suggestion = aRoomSuggestion("lement"),
189+
roomMembersState = MatrixRoomMembersState.Ready(persistentListOf()),
190+
roomAliasSuggestions = listOf(
191+
RoomAliasSuggestion(
192+
roomAlias = A_ROOM_ALIAS,
193+
roomId = aRoomSummary.roomId,
194+
roomName = "Element",
195+
roomAvatarUrl = aRoomSummary.info.avatarUrl,
196+
)
197+
),
198+
currentUserId = A_USER_ID,
199+
canSendRoomMention = { true },
200+
)
201+
assertThat(result).isEqualTo(
202+
listOf(
203+
ResolvedSuggestion.Alias(
204+
roomAlias = A_ROOM_ALIAS,
205+
roomId = aRoomSummary.roomId,
206+
roomName = "Element",
207+
roomAvatarUrl = aRoomSummary.info.avatarUrl,
208+
)
209+
)
210+
)
211+
}
212+
213+
@Test
214+
fun `processing Room suggestion will not return a suggestion when room has no name`() = runTest {
215+
val aRoomSummary = aRoomSummary(canonicalAlias = A_ROOM_ALIAS)
216+
val result = suggestionsProcessor.process(
217+
suggestion = aRoomSuggestion("lement"),
218+
roomMembersState = MatrixRoomMembersState.Ready(persistentListOf()),
219+
roomAliasSuggestions = listOf(
220+
RoomAliasSuggestion(
221+
roomAlias = A_ROOM_ALIAS,
222+
roomId = aRoomSummary.roomId,
223+
roomName = null,
175224
roomAvatarUrl = aRoomSummary.info.avatarUrl,
176225
)
177226
),

features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsPresenterTest.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ class DeveloperSettingsPresenterTest {
8080
presenter.test {
8181
skipItems(2)
8282
awaitItem().also { state ->
83-
val feature = state.features.first()
83+
val feature = state.features.first { !it.isEnabled }
8484
state.eventSink(DeveloperSettingsEvents.UpdateEnabledFeature(feature, !feature.isEnabled))
8585
}
8686
awaitItem().also { state ->

features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/incoming/IncomingVerificationPresenter.kt

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,6 @@ import io.element.android.libraries.matrix.api.verification.SessionVerificationS
3030
import io.element.android.libraries.matrix.api.verification.VerificationFlowState
3131
import io.element.android.libraries.matrix.api.verification.VerificationRequest
3232
import kotlinx.coroutines.CoroutineScope
33-
import kotlinx.coroutines.Dispatchers
3433
import kotlinx.coroutines.ExperimentalCoroutinesApi
3534
import kotlinx.coroutines.flow.launchIn
3635
import kotlinx.coroutines.flow.onEach
@@ -57,7 +56,7 @@ class IncomingVerificationPresenter @AssistedInject constructor(
5756

5857
@Composable
5958
override fun present(): IncomingVerificationState {
60-
val coroutineScope = rememberCoroutineScope { Dispatchers.IO }
59+
val coroutineScope = rememberCoroutineScope()
6160

6261
val stateAndDispatch = stateMachine.rememberStateAndDispatch()
6362

features/verifysession/impl/src/test/kotlin/io/element/android/features/verifysession/impl/incoming/IncomingVerificationPresenterTest.kt

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,9 @@ class IncomingVerificationPresenterTest {
131131
isWaiting = false,
132132
)
133133
)
134+
135+
advanceTimeBy(1.seconds)
136+
134137
resetLambda.assertions().isCalledOnce().with(value(false))
135138
acknowledgeVerificationRequestLambda.assertions().isCalledOnce().with(value(anIncomingSessionVerificationRequest))
136139
acceptVerificationRequestLambda.assertions().isNeverCalled()
@@ -139,7 +142,9 @@ class IncomingVerificationPresenterTest {
139142
skipItems(1)
140143
val initialWaitingState = awaitItem()
141144
assertThat((initialWaitingState.step as IncomingVerificationState.Step.Initial).isWaiting).isTrue()
142-
advanceUntilIdle()
145+
146+
advanceTimeBy(1.seconds)
147+
143148
acceptVerificationRequestLambda.assertions().isCalledOnce()
144149
// Remote sent the data
145150
fakeSessionVerificationService.emitVerificationFlowState(VerificationFlowState.DidAcceptVerificationRequest)

0 commit comments

Comments
 (0)