From a072cc752493725b28aba4dd5df676a26cf47480 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 23 Jul 2024 14:48:18 +0000 Subject: [PATCH 1/6] Update dependency org.matrix.rustcomponents:sdk-android to v0.2.34 --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 97f6585b34..a1fede3e54 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -163,7 +163,7 @@ jsoup = "org.jsoup:jsoup:1.18.1" appyx_core = { module = "com.bumble.appyx:core", version.ref = "appyx" } molecule-runtime = "app.cash.molecule:molecule-runtime:2.0.0" timber = "com.jakewharton.timber:timber:5.0.1" -matrix_sdk = "org.matrix.rustcomponents:sdk-android:0.2.33" +matrix_sdk = "org.matrix.rustcomponents:sdk-android:0.2.34" matrix_richtexteditor = { module = "io.element.android:wysiwyg", version.ref = "wysiwyg" } matrix_richtexteditor_compose = { module = "io.element.android:wysiwyg-compose", version.ref = "wysiwyg" } sqldelight-driver-android = { module = "app.cash.sqldelight:android-driver", version.ref = "sqldelight" } From f5d215ba0f718b59416003447ff56577b356310f Mon Sep 17 00:00:00 2001 From: ganfra Date: Wed, 24 Jul 2024 11:49:16 +0200 Subject: [PATCH 2/6] Edit : fallback to room.edit when timeline item is not found. --- .../MessageComposerPresenter.kt | 8 +++ .../MessageComposerPresenterTest.kt | 62 ++++++++++++++++ .../libraries/matrix/api/room/MatrixRoom.kt | 2 + .../matrix/api/timeline/TimelineException.kt | 1 + .../matrix/impl/room/RustMatrixRoom.kt | 9 +++ .../matrix/impl/timeline/RustTimeline.kt | 72 +++++++------------ .../matrix/impl/util/MessageEventContent.kt | 36 ++++++++++ .../matrix/test/room/FakeMatrixRoom.kt | 5 ++ 8 files changed, 148 insertions(+), 47 deletions(-) create mode 100644 libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/util/MessageEventContent.kt diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerPresenter.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerPresenter.kt index 7bd6bd1867..d0e50d114c 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerPresenter.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerPresenter.kt @@ -60,6 +60,7 @@ import io.element.android.libraries.matrix.api.room.Mention import io.element.android.libraries.matrix.api.room.draft.ComposerDraft import io.element.android.libraries.matrix.api.room.draft.ComposerDraftType import io.element.android.libraries.matrix.api.room.isDm +import io.element.android.libraries.matrix.api.timeline.TimelineException import io.element.android.libraries.matrix.ui.messages.RoomMemberProfilesCache import io.element.android.libraries.matrix.ui.messages.reply.InReplyToDetails import io.element.android.libraries.matrix.ui.messages.reply.map @@ -436,7 +437,14 @@ class MessageComposerPresenter @Inject constructor( val eventId = capturedMode.eventId val transactionId = capturedMode.transactionId timelineController.invokeOnCurrentTimeline { + // First try to edit the message in the current timeline editMessage(eventId, transactionId, message.markdown, message.html, message.mentions) + .onFailure { cause -> + if (cause is TimelineException.EventNotFound && eventId != null) { + // if the event is not found in the timeline, try to edit the message directly + room.editMessage(eventId, message.markdown, message.html, message.mentions) + } + } } } diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/textcomposer/MessageComposerPresenterTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/textcomposer/MessageComposerPresenterTest.kt index b944ea3d95..dcb09fb7c8 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/textcomposer/MessageComposerPresenterTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/textcomposer/MessageComposerPresenterTest.kt @@ -56,6 +56,7 @@ import io.element.android.libraries.matrix.api.room.Mention import io.element.android.libraries.matrix.api.room.RoomMembershipState import io.element.android.libraries.matrix.api.room.draft.ComposerDraft import io.element.android.libraries.matrix.api.room.draft.ComposerDraftType +import io.element.android.libraries.matrix.api.timeline.TimelineException import io.element.android.libraries.matrix.api.timeline.item.event.InReplyTo import io.element.android.libraries.matrix.test.ANOTHER_MESSAGE import io.element.android.libraries.matrix.test.AN_EVENT_ID @@ -417,6 +418,67 @@ class MessageComposerPresenterTest { } } + @Test + fun `present - edit sent message event not found`() = runTest { + val timelineEditMessageLambda = lambdaRecorder { _: EventId?, _: TransactionId?, _: String, _: String?, _: List -> + Result.failure(TimelineException.EventNotFound) + } + val timeline = FakeTimeline().apply { + this.editMessageLambda = timelineEditMessageLambda + } + val roomEditMessageLambda = lambdaRecorder { _: EventId?, _: String, _: String?, _: List -> + Result.success(Unit) + } + val fakeMatrixRoom = FakeMatrixRoom( + liveTimeline = timeline, + typingNoticeResult = { Result.success(Unit) } + ).apply { + this.editMessageLambda = roomEditMessageLambda + } + val presenter = createPresenter( + this, + fakeMatrixRoom, + ) + moleculeFlow(RecompositionMode.Immediate) { + val state = presenter.present() + remember(state, state.textEditorState.messageHtml()) { state } + }.test { + val initialState = awaitFirstItem() + assertThat(initialState.textEditorState.messageHtml()).isEqualTo("") + val mode = anEditMode() + initialState.eventSink.invoke(MessageComposerEvents.SetMode(mode)) + val withMessageState = awaitItem() + assertThat(withMessageState.mode).isEqualTo(mode) + assertThat(withMessageState.textEditorState.messageHtml()).isEqualTo(A_MESSAGE) + withMessageState.textEditorState.setHtml(ANOTHER_MESSAGE) + val withEditedMessageState = awaitItem() + assertThat(withEditedMessageState.textEditorState.messageHtml()).isEqualTo(ANOTHER_MESSAGE) + withEditedMessageState.eventSink.invoke(MessageComposerEvents.SendMessage) + skipItems(1) + val messageSentState = awaitItem() + assertThat(messageSentState.textEditorState.messageHtml()).isEqualTo("") + + advanceUntilIdle() + + assert(timelineEditMessageLambda) + .isCalledOnce() + .with(value(AN_EVENT_ID), value(null), value(ANOTHER_MESSAGE), value(ANOTHER_MESSAGE), any()) + + assert(roomEditMessageLambda) + .isCalledOnce() + .with(value(AN_EVENT_ID), value(ANOTHER_MESSAGE), value(ANOTHER_MESSAGE), any()) + + assertThat(analyticsService.capturedEvents).containsExactly( + Composer( + inThread = false, + isEditing = true, + isReply = false, + messageType = Composer.MessageType.Text, + ) + ) + } + } + @Test fun `present - edit not sent message`() = runTest { val editMessageLambda = lambdaRecorder { _: EventId?, _: TransactionId?, _: String, _: String?, _: List -> diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MatrixRoom.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MatrixRoom.kt index 926df6ae21..378ada5b16 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MatrixRoom.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MatrixRoom.kt @@ -126,6 +126,8 @@ interface MatrixRoom : Closeable { suspend fun sendMessage(body: String, htmlBody: String?, mentions: List): Result + suspend fun editMessage(eventId: EventId, body: String, htmlBody: String?, mentions: List): Result + suspend fun sendImage( file: File, thumbnailFile: File?, diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/TimelineException.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/TimelineException.kt index e3970619cd..ba44de0ba3 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/TimelineException.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/TimelineException.kt @@ -18,4 +18,5 @@ package io.element.android.libraries.matrix.api.timeline sealed class TimelineException : Exception() { data object CannotPaginate : TimelineException() + data object EventNotFound : TimelineException() } diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustMatrixRoom.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustMatrixRoom.kt index 0a09ea005e..b594bba5e4 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustMatrixRoom.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustMatrixRoom.kt @@ -56,6 +56,7 @@ import io.element.android.libraries.matrix.impl.room.member.RoomMemberMapper import io.element.android.libraries.matrix.impl.room.powerlevels.RoomPowerLevelsMapper import io.element.android.libraries.matrix.impl.timeline.RustTimeline import io.element.android.libraries.matrix.impl.timeline.toRustReceiptType +import io.element.android.libraries.matrix.impl.util.MessageEventContent import io.element.android.libraries.matrix.impl.util.mxCallbackFlow import io.element.android.libraries.matrix.impl.widget.RustWidgetDriver import io.element.android.libraries.matrix.impl.widget.generateWidgetWebViewUrl @@ -324,6 +325,14 @@ class RustMatrixRoom( } } + override suspend fun editMessage(eventId: EventId, body: String, htmlBody: String?, mentions: List): Result = withContext(roomDispatcher) { + runCatching { + MessageEventContent.from(body, htmlBody, mentions).use { newContent -> + innerRoom.edit(eventId.value, newContent) + } + } + } + override suspend fun sendMessage(body: String, htmlBody: String?, mentions: List): Result { return liveTimeline.sendMessage(body, htmlBody, mentions) } diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/RustTimeline.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/RustTimeline.kt index 00c79bf687..b57efc5603 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/RustTimeline.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/RustTimeline.kt @@ -42,7 +42,6 @@ import io.element.android.libraries.matrix.impl.media.toMSC3246range import io.element.android.libraries.matrix.impl.poll.toInner import io.element.android.libraries.matrix.impl.room.RoomContentForwarder import io.element.android.libraries.matrix.impl.room.location.toInner -import io.element.android.libraries.matrix.impl.room.map import io.element.android.libraries.matrix.impl.timeline.item.event.EventTimelineItemMapper import io.element.android.libraries.matrix.impl.timeline.item.event.TimelineEventContentMapper import io.element.android.libraries.matrix.impl.timeline.item.virtual.VirtualTimelineItemMapper @@ -51,6 +50,7 @@ import io.element.android.libraries.matrix.impl.timeline.postprocessor.LoadingIn import io.element.android.libraries.matrix.impl.timeline.postprocessor.RoomBeginningPostProcessor import io.element.android.libraries.matrix.impl.timeline.postprocessor.TimelineEncryptedHistoryPostProcessor import io.element.android.libraries.matrix.impl.timeline.reply.InReplyToMapper +import io.element.android.libraries.matrix.impl.util.MessageEventContent import io.element.android.services.toolbox.api.systemclock.SystemClock import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.CoroutineDispatcher @@ -70,12 +70,10 @@ import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onStart import kotlinx.coroutines.launch import kotlinx.coroutines.withContext +import org.matrix.rustcomponents.sdk.EventTimelineItem import org.matrix.rustcomponents.sdk.FormattedBody import org.matrix.rustcomponents.sdk.MessageFormat -import org.matrix.rustcomponents.sdk.RoomMessageEventContentWithoutRelation import org.matrix.rustcomponents.sdk.SendAttachmentJoinHandle -import org.matrix.rustcomponents.sdk.messageEventContentFromHtml -import org.matrix.rustcomponents.sdk.messageEventContentFromMarkdown import org.matrix.rustcomponents.sdk.use import timber.log.Timber import uniffi.matrix_sdk_ui.LiveBackPaginationStatus @@ -266,7 +264,7 @@ class RustTimeline( } override suspend fun sendMessage(body: String, htmlBody: String?, mentions: List): Result = withContext(dispatcher) { - messageEventContentFromParts(body, htmlBody).withMentions(mentions.map()).use { content -> + MessageEventContent.from(body, htmlBody, mentions).use { content -> runCatching { inner.send(content) } @@ -275,20 +273,8 @@ class RustTimeline( override suspend fun redactEvent(eventId: EventId?, transactionId: TransactionId?, reason: String?): Result = withContext(dispatcher) { runCatching { - when { - eventId != null -> { - inner.getEventTimelineItemByEventId(eventId.value).use { - inner.redactEvent(item = it, reason = reason) - } - } - transactionId != null -> { - inner.getEventTimelineItemByTransactionId(transactionId.value).use { - inner.redactEvent(item = it, reason = reason) - } - } - else -> { - error("Either eventId or transactionId must be non-null") - } + getEventTimelineItem(eventId, transactionId).use { item -> + inner.redactEvent(item = item, reason = reason) } } } @@ -302,26 +288,11 @@ class RustTimeline( ): Result = withContext(dispatcher) { runCatching { - when { - originalEventId != null -> { - inner.getEventTimelineItemByEventId(originalEventId.value).use { - inner.edit( - newContent = messageEventContentFromParts(body, htmlBody).withMentions(mentions.map()), - item = it, - ) - } - } - transactionId != null -> { - inner.getEventTimelineItemByTransactionId(transactionId.value).use { - inner.edit( - newContent = messageEventContentFromParts(body, htmlBody).withMentions(mentions.map()), - item = it, - ) - } - } - else -> { - error("Either originalEventId or transactionId must be non null") - } + getEventTimelineItem(originalEventId, transactionId).use { item -> + inner.edit( + newContent = MessageEventContent.from(body, htmlBody, mentions), + item = item, + ) } } } @@ -334,7 +305,7 @@ class RustTimeline( fromNotification: Boolean, ): Result = withContext(dispatcher) { runCatching { - val msg = messageEventContentFromParts(body, htmlBody).withMentions(mentions.map()) + val msg = MessageEventContent.from(body, htmlBody, mentions) inner.sendReply(msg, eventId.value) } } @@ -361,6 +332,20 @@ class RustTimeline( } } + @Throws + private suspend fun getEventTimelineItem(eventId: EventId?, transactionId: TransactionId?): EventTimelineItem { + return try { + when { + eventId != null -> inner.getEventTimelineItemByEventId(eventId.value) + transactionId != null -> inner.getEventTimelineItemByTransactionId(transactionId.value) + else -> error("Either eventId or transactionId must be non-null") + } + } catch (e: Exception) { + Timber.e(e, "Failed to get event timeline item") + throw TimelineException.EventNotFound + } + } + override suspend fun sendVideo( file: File, thumbnailFile: File?, @@ -517,13 +502,6 @@ class RustTimeline( ) } - private fun messageEventContentFromParts(body: String, htmlBody: String?): RoomMessageEventContentWithoutRelation = - if (htmlBody != null) { - messageEventContentFromHtml(body, htmlBody) - } else { - messageEventContentFromMarkdown(body) - } - private fun sendAttachment(files: List, handle: () -> SendAttachmentJoinHandle): Result { return runCatching { MediaUploadHandlerImpl(files, handle()) diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/util/MessageEventContent.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/util/MessageEventContent.kt new file mode 100644 index 0000000000..e1728bb528 --- /dev/null +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/util/MessageEventContent.kt @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.libraries.matrix.impl.util + +import io.element.android.libraries.matrix.api.room.Mention +import io.element.android.libraries.matrix.impl.room.map +import org.matrix.rustcomponents.sdk.RoomMessageEventContentWithoutRelation +import org.matrix.rustcomponents.sdk.messageEventContentFromHtml +import org.matrix.rustcomponents.sdk.messageEventContentFromMarkdown + +/** + * Creates a [RoomMessageEventContentWithoutRelation] from a body, an html body and a list of mentions. + */ +object MessageEventContent { + fun from(body: String, htmlBody: String?, mentions: List): RoomMessageEventContentWithoutRelation { + return if (htmlBody != null) { + messageEventContentFromHtml(body, htmlBody) + } else { + messageEventContentFromMarkdown(body) + }.withMentions(mentions.map()) + } +} diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/FakeMatrixRoom.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/FakeMatrixRoom.kt index 21c50e51a4..cfd0267516 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/FakeMatrixRoom.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/FakeMatrixRoom.kt @@ -212,6 +212,11 @@ class FakeMatrixRoom( return updateUserRoleResult() } + var editMessageLambda: (EventId, String, String?, List) -> Result = { _, _, _, _ -> lambdaError() } + override suspend fun editMessage(eventId: EventId, body: String, htmlBody: String?, mentions: List): Result { + return editMessageLambda(eventId, body, htmlBody, mentions) + } + override suspend fun sendMessage(body: String, htmlBody: String?, mentions: List) = simulateLongTask { sendMessageResult(body, htmlBody, mentions) } From 472c864a381d452bc6521f0f78696519d9adefc1 Mon Sep 17 00:00:00 2001 From: ganfra Date: Wed, 24 Jul 2024 14:34:12 +0200 Subject: [PATCH 3/6] Setting version for the release 0.5.0 --- plugins/src/main/kotlin/Versions.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/src/main/kotlin/Versions.kt b/plugins/src/main/kotlin/Versions.kt index df1e90a378..9fb6df696a 100644 --- a/plugins/src/main/kotlin/Versions.kt +++ b/plugins/src/main/kotlin/Versions.kt @@ -51,12 +51,12 @@ import org.gradle.jvm.toolchain.JavaLanguageVersion // Note: 2 digits max for each value private const val versionMajor = 0 -private const val versionMinor = 4 +private const val versionMinor = 5 // Note: even values are reserved for regular release, odd values for hotfix release. // When creating a hotfix, you should decrease the value, since the current value // is the value for the next regular release. -private const val versionPatch = 17 +private const val versionPatch = 0 object Versions { val versionCode = 4_000_000 + versionMajor * 1_00_00 + versionMinor * 1_00 + versionPatch From 6f8d01331c1781ec39d79246db2ae489ef50c1eb Mon Sep 17 00:00:00 2001 From: ganfra Date: Wed, 24 Jul 2024 14:35:47 +0200 Subject: [PATCH 4/6] Adding fastlane file for version 0.5.0 --- fastlane/metadata/android/en-US/changelogs/40005000.txt | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 fastlane/metadata/android/en-US/changelogs/40005000.txt diff --git a/fastlane/metadata/android/en-US/changelogs/40005000.txt b/fastlane/metadata/android/en-US/changelogs/40005000.txt new file mode 100644 index 0000000000..dd8c30a1b9 --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/40005000.txt @@ -0,0 +1,2 @@ +Main changes in this version: mostly bug fixes and performance improvements. +Full changelog: https://github.com/element-hq/element-x-android/releases \ No newline at end of file From 1e7da96f3615fbc0f3f5f48086e3de2dcfd2d8e0 Mon Sep 17 00:00:00 2001 From: ganfra Date: Wed, 24 Jul 2024 14:38:34 +0200 Subject: [PATCH 5/6] version++ --- plugins/src/main/kotlin/Versions.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/src/main/kotlin/Versions.kt b/plugins/src/main/kotlin/Versions.kt index 9fb6df696a..e5df6fe90c 100644 --- a/plugins/src/main/kotlin/Versions.kt +++ b/plugins/src/main/kotlin/Versions.kt @@ -56,7 +56,7 @@ private const val versionMinor = 5 // Note: even values are reserved for regular release, odd values for hotfix release. // When creating a hotfix, you should decrease the value, since the current value // is the value for the next regular release. -private const val versionPatch = 0 +private const val versionPatch = 1 object Versions { val versionCode = 4_000_000 + versionMajor * 1_00_00 + versionMinor * 1_00 + versionPatch From 9f94ce8b313ed98647b37e3dad2b3696137d20ff Mon Sep 17 00:00:00 2001 From: ganfra Date: Wed, 24 Jul 2024 16:29:08 +0200 Subject: [PATCH 6/6] Changelog for version 0.5.0 --- CHANGES.md | 77 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 77 insertions(+) diff --git a/CHANGES.md b/CHANGES.md index c2c2641b34..2661597461 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,3 +1,80 @@ +Changes in Element X v0.5.0 (2024-07-24) +========================================= + +### 🙌 Improvements +* Add icon for "Mark as read" and "Mark as unread" actions. by @bmarty in https://github.com/element-hq/element-x-android/pull/3144 +* Add support for Picture In Picture for Element Call by @bmarty in https://github.com/element-hq/element-x-android/pull/3159 +* Set pin grace period to 2 minutes by @bmarty in https://github.com/element-hq/element-x-android/pull/3172 +* Unify the way we decide whether a room is a DM or a group room by @jmartinesp in https://github.com/element-hq/element-x-android/pull/3100 +* Subscribe to `RoomListItems` in the visible range by @jmartinesp in https://github.com/element-hq/element-x-android/pull/3169 +* Improve pip and add feature flag. by @bmarty in https://github.com/element-hq/element-x-android/pull/3199 +* Open Source licenses: add color for links. by @bmarty in https://github.com/element-hq/element-x-android/pull/3215 +* Cancel ringing call notification on call cancellation by @jmartinesp in https://github.com/element-hq/element-x-android/pull/3047 + +### 🐛 Bugfixes +* Fix `MainActionButton` layout for long texts by @jmartinesp in https://github.com/element-hq/element-x-android/pull/3158 +* Always follow the desired theme for Pin, Incoming Call and Element Call screens by @bmarty in https://github.com/element-hq/element-x-android/pull/3165 +* Fix empty screen issue after clearing the cache by @bmarty in https://github.com/element-hq/element-x-android/pull/3163 +* Restore intentional mentions in the markdown/plain text editor by @jmartinesp in https://github.com/element-hq/element-x-android/pull/3193 +* Fix crash in the room list after a forced log out in background by @jmartinesp in https://github.com/element-hq/element-x-android/pull/3180 +* Clear existing notification when a room is marked as read by @bmarty in https://github.com/element-hq/element-x-android/pull/3203 +* Fix crash when Pin code screen is displayed by @bmarty in https://github.com/element-hq/element-x-android/pull/3205 +* Fix pillification not working for non formatted message bodies by @jmartinesp in https://github.com/element-hq/element-x-android/pull/3201 +* Update grammar on Matrix Ids to be more spec compliant and render error instead of infinite loading in room member list screen by @bmarty in https://github.com/element-hq/element-x-android/pull/3206 +* Reduce the risk of text truncation in buttons. by @bmarty in https://github.com/element-hq/element-x-android/pull/3209 +* Ensure that the manual dark theme is rendering correctly regarding -night resource and keyboard by @bmarty in https://github.com/element-hq/element-x-android/pull/3216 +* Fix rendering issue of SunsetPage in dark mode by @bmarty in https://github.com/element-hq/element-x-android/pull/3217 +* Fix linkification not working for `Spanned` strings in text messages by @jmartinesp in https://github.com/element-hq/element-x-android/pull/3233 +* Edit : fallback to room.edit when timeline item is not found. by @ganfra in https://github.com/element-hq/element-x-android/pull/3239 + +### 🗣 Translations +* Sync Strings by @ElementBot in https://github.com/element-hq/element-x-android/pull/3156 +* Sync Strings by @ElementBot in https://github.com/element-hq/element-x-android/pull/3192 +* Sync Strings by @ElementBot in https://github.com/element-hq/element-x-android/pull/3232 + +### 🧱 Build +* Remove Showkase processor not found warning from Danger by @jmartinesp in https://github.com/element-hq/element-x-android/pull/3148 +* Set targetSDK to 34 by @bmarty in https://github.com/element-hq/element-x-android/pull/3149 +* Add a local copy of `inplace-fix.py` and `fix-pg-map-id.py` by @bmarty in https://github.com/element-hq/element-x-android/pull/3167 +* Only add private SSH keys and clone submodules in the original repo by @jmartinesp in https://github.com/element-hq/element-x-android/pull/3225 +* Fix CI for forks by @jmartinesp in https://github.com/element-hq/element-x-android/pull/3226 + +### Dependency upgrades +* Update dependency io.element.android:compound-android to v0.0.7 by @renovate in https://github.com/element-hq/element-x-android/pull/3143 +* Update dependency org.matrix.rustcomponents:sdk-android to v0.2.31 by @renovate in https://github.com/element-hq/element-x-android/pull/3145 +* Update dependency com.squareup:kotlinpoet to v1.18.0 by @renovate in https://github.com/element-hq/element-x-android/pull/3150 +* Update dependency org.robolectric:robolectric to v4.13 by @renovate in https://github.com/element-hq/element-x-android/pull/3157 +* Update plugin dependencycheck to v10.0.2 by @renovate in https://github.com/element-hq/element-x-android/pull/3154 +* Update wysiwyg to v2.37.5 by @renovate in https://github.com/element-hq/element-x-android/pull/3162 +* Update plugin sonarqube to v5.1.0.4882 by @renovate in https://github.com/element-hq/element-x-android/pull/3139 +* Update dependency org.jsoup:jsoup to v1.18.1 by @renovate in https://github.com/element-hq/element-x-android/pull/3171 +* Update dependency com.google.firebase:firebase-bom to v33.1.2 by @renovate in https://github.com/element-hq/element-x-android/pull/3178 +* Update telephoto to v0.12.0 by @renovate in https://github.com/element-hq/element-x-android/pull/3191 +* Update dependency com.google.truth:truth to v1.4.4 by @renovate in https://github.com/element-hq/element-x-android/pull/3187 +* Update dependency com.squareup:kotlinpoet to v1.18.1 by @renovate in https://github.com/element-hq/element-x-android/pull/3194 +* Update dependency io.mockk:mockk to v1.13.12 by @renovate in https://github.com/element-hq/element-x-android/pull/3198 +* Update dependency io.sentry:sentry-android to v7.12.0 by @renovate in https://github.com/element-hq/element-x-android/pull/3200 +* Update plugin dependencycheck to v10.0.3 by @renovate in https://github.com/element-hq/element-x-android/pull/3204 +* Update dependency gradle to v8.9 by @renovate in https://github.com/element-hq/element-x-android/pull/3177 +* Update dependency org.matrix.rustcomponents:sdk-android to v0.2.32 by @renovate in https://github.com/element-hq/element-x-android/pull/3202 +* Update coil to v2.7.0 by @renovate in https://github.com/element-hq/element-x-android/pull/3210 +* Update dependency org.matrix.rustcomponents:sdk-android to v0.2.33 by @renovate in https://github.com/element-hq/element-x-android/pull/3220 +* Update wysiwyg to v2.37.7 by @renovate in https://github.com/element-hq/element-x-android/pull/3218 +* Update telephoto to v0.12.1 by @renovate in https://github.com/element-hq/element-x-android/pull/3230 +* Update dependency org.matrix.rustcomponents:sdk-android to v0.2.34 by @renovate in https://github.com/element-hq/element-x-android/pull/3237 + +### Others +* Reduce delay when selecting room list filters by @jmartinesp in https://github.com/element-hq/element-x-android/pull/3160 +* Add `--alignment-preserved true` when signing APK for F-Droid. by @bmarty in https://github.com/element-hq/element-x-android/pull/3161 +* Ensure that all callback plugins are invoked. by @bmarty in https://github.com/element-hq/element-x-android/pull/3146 +* Add generated screen to show open source licenses in Gplay variant by @bmarty in https://github.com/element-hq/element-x-android/pull/3207 +* Performance : improve time to open a room. by @ganfra in https://github.com/element-hq/element-x-android/pull/3186 +* Add logging to help debug forced logout issues by @jmartinesp in https://github.com/element-hq/element-x-android/pull/3208 +* Use the right filename for log files so they're sorted in rageshakes by @jmartinesp in https://github.com/element-hq/element-x-android/pull/3219 +* Compose : add immutability to some Reaction classes by @ganfra in https://github.com/element-hq/element-x-android/pull/3224 +* Fix stickers display text on room summary by @surakin in https://github.com/element-hq/element-x-android/pull/3221 +* Rework FakeMatrixRoom so that it contains only lambdas. by @bmarty in https://github.com/element-hq/element-x-android/pull/3229 + Changes in Element X v0.4.16 (2024-07-05) =========================================