diff --git a/CHANGES.md b/CHANGES.md
index f4455d4fc1..f747e9ded9 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -1,3 +1,45 @@
+## What's Changed
+### ✨ Features
+* Enable support for Android Auto. by @bmarty in https://github.com/element-hq/element-x-android/pull/4818
+* Element Call: Add audio output selector handled by Android by @jmartinesp in https://github.com/element-hq/element-x-android/pull/4663
+### 🙌 Improvements
+* Oidc: Fallback to external browser instead of using Webview by @bmarty in https://github.com/element-hq/element-x-android/pull/4808
+* change (room member moderation) : update icon to match figma by @ganfra in https://github.com/element-hq/element-x-android/pull/4837
+### 🐛 Bugfixes
+* Fix login flow by @bmarty in https://github.com/element-hq/element-x-android/pull/4813
+* fix: When sending media as files use the `octet-stream` type by @jmartinesp in https://github.com/element-hq/element-x-android/pull/4815
+* fix: Make `Client.findDM` return a `Result` by @jmartinesp in https://github.com/element-hq/element-x-android/pull/4816
+* Mark room as fully read when user goes back to the room list. by @bmarty in https://github.com/element-hq/element-x-android/pull/2687
+* fix (identity change) : RoomMemberIdentityStateChange in non encrypted room by @ganfra in https://github.com/element-hq/element-x-android/pull/4824
+* Fix room and user avatar downloaded with a `.bin` extension. by @bmarty in https://github.com/element-hq/element-x-android/pull/4830
+* Log the push resolving failure reason if available by @jmartinesp in https://github.com/element-hq/element-x-android/pull/4835
+### 🧱 Build
+* Update Gradle Wrapper from 8.14.1 to 8.14.2 by @ElementBot in https://github.com/element-hq/element-x-android/pull/4831
+### Dependency upgrades
+* fix(deps): update dependency androidx.compose:compose-bom to v2025.04.01 by @renovate in https://github.com/element-hq/element-x-android/pull/4631
+* fix(deps): update dependency androidx.compose:compose-bom to v2025.05.01 by @renovate in https://github.com/element-hq/element-x-android/pull/4814
+* fix(deps): update dependency io.sentry:sentry-android to v8.13.2 by @renovate in https://github.com/element-hq/element-x-android/pull/4780
+* fix(deps): update appyx to v1.7.1 by @renovate in https://github.com/element-hq/element-x-android/pull/4672
+* fix(deps): update telephoto to v0.16.0 by @renovate in https://github.com/element-hq/element-x-android/pull/4749
+* fix(deps): update coil to v3.2.0 by @renovate in https://github.com/element-hq/element-x-android/pull/4712
+* fix(deps): update dependency androidx.webkit:webkit to v1.14.0 by @renovate in https://github.com/element-hq/element-x-android/pull/4823
+* fix(deps): update dependency com.posthog:posthog-android to v3.17.0 by @renovate in https://github.com/element-hq/element-x-android/pull/4827
+* fix(deps): update dependency io.element.android:element-call-embedded to v0.12.2 by @renovate in https://github.com/element-hq/element-x-android/pull/4832
+* fix(deps): update dependency com.google.firebase:firebase-bom to v33.15.0 by @renovate in https://github.com/element-hq/element-x-android/pull/4833
+* fix(deps): update dependency org.maplibre.gl:android-sdk to v11.10.1 by @renovate in https://github.com/element-hq/element-x-android/pull/4825
+* fix(deps): update lifecycle to v2.9.1 by @renovate in https://github.com/element-hq/element-x-android/pull/4822
+* fix(deps): update dependency org.matrix.rustcomponents:sdk-android to v25.6.6 by @renovate in https://github.com/element-hq/element-x-android/pull/4834
+* fix(deps): update dependency io.element.android:opusencoder to v1.2.0 by @renovate in https://github.com/element-hq/element-x-android/pull/4836
+### Others
+* Add `catchingExceptions` method to replace `runCatching` by @jmartinesp in https://github.com/element-hq/element-x-android/pull/4797
+* Rename classes overriding classes from the FFI layer. by @bmarty in https://github.com/element-hq/element-x-android/pull/4817
+* Fix coroutine scope by @bmarty in https://github.com/element-hq/element-x-android/pull/4820
+* Add extra logs the 'send call notification' flow by @jmartinesp in https://github.com/element-hq/element-x-android/pull/4819
+* misc (matrix) : use innerClient.subscribeToRoomInfo sdk method by @ganfra in https://github.com/element-hq/element-x-android/pull/4838
+
+
+**Full Changelog**: https://github.com/element-hq/element-x-android/compare/v25.06.0...v25.06.1
+
Changes in Element X v25.06.0
=============================
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index adc2b178f8..1fdc07d72c 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -122,6 +122,17 @@
+
+
+
+
+
+
+
+
+
diff --git a/fastlane/metadata/android/en-US/changelogs/202506020.txt b/fastlane/metadata/android/en-US/changelogs/202506020.txt
new file mode 100644
index 0000000000..8955ade680
--- /dev/null
+++ b/fastlane/metadata/android/en-US/changelogs/202506020.txt
@@ -0,0 +1,2 @@
+Main changes in this version: bug fixes and improvements.
+Full changelog: https://github.com/element-hq/element-x-android/releases
diff --git a/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/utils/WebViewAudioManager.kt b/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/utils/WebViewAudioManager.kt
index d29843a70e..dc55ff2ac4 100644
--- a/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/utils/WebViewAudioManager.kt
+++ b/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/utils/WebViewAudioManager.kt
@@ -15,9 +15,11 @@ import android.os.Build
import android.os.PowerManager
import android.webkit.JavascriptInterface
import android.webkit.WebView
+import androidx.annotation.RequiresApi
import androidx.core.content.getSystemService
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.serialization.Serializable
import kotlinx.serialization.Transient
@@ -25,6 +27,7 @@ import kotlinx.serialization.json.Json
import timber.log.Timber
import java.util.concurrent.Executors
import java.util.concurrent.atomic.AtomicBoolean
+import kotlin.time.Duration.Companion.milliseconds
/**
* This class manages the audio devices for a WebView.
@@ -66,23 +69,26 @@ class WebViewAudioManager(
/**
* This listener tracks the current communication device and updates the WebView when it changes.
*/
- private val commsDeviceChangedListener = AudioManager.OnCommunicationDeviceChangedListener { device ->
- if (device != null && device.id == expectedNewCommunicationDeviceId) {
- expectedNewCommunicationDeviceId = null
- Timber.d("Audio device changed, type: ${device.type}")
- updateSelectedAudioDeviceInWebView(device.id.toString())
- } else if (device != null && device.id != expectedNewCommunicationDeviceId) {
- // We were expecting a device change but it didn't happen, so we should retry
- val expectedDeviceId = expectedNewCommunicationDeviceId
- if (expectedDeviceId != null) {
- // Remove the expected id so we only retry once
+ @get:RequiresApi(Build.VERSION_CODES.S)
+ private val commsDeviceChangedListener by lazy {
+ AudioManager.OnCommunicationDeviceChangedListener { device ->
+ if (device != null && device.id == expectedNewCommunicationDeviceId) {
expectedNewCommunicationDeviceId = null
- audioManager.selectAudioDevice(expectedDeviceId.toString())
+ Timber.d("Audio device changed, type: ${device.type}")
+ updateSelectedAudioDeviceInWebView(device.id.toString())
+ } else if (device != null && device.id != expectedNewCommunicationDeviceId) {
+ // We were expecting a device change but it didn't happen, so we should retry
+ val expectedDeviceId = expectedNewCommunicationDeviceId
+ if (expectedDeviceId != null) {
+ // Remove the expected id so we only retry once
+ expectedNewCommunicationDeviceId = null
+ audioManager.selectAudioDevice(expectedDeviceId.toString())
+ }
+ } else {
+ Timber.d("Audio device cleared")
+ expectedNewCommunicationDeviceId = null
+ audioManager.selectAudioDevice(null)
}
- } else {
- Timber.d("Audio device cleared")
- expectedNewCommunicationDeviceId = null
- audioManager.selectAudioDevice(null)
}
}
@@ -217,6 +223,10 @@ class WebViewAudioManager(
},
onAudioPlaybackStarted = {
coroutineScope.launch(Dispatchers.Main) {
+ // Even with the callback, it seems like starting the audio takes a bit on the webview side,
+ // so we add an extra delay here to make sure it's ready
+ delay(500.milliseconds)
+
// Calling this ahead of time makes the default audio device to not use the right audio stream
setAvailableAudioDevices()
diff --git a/features/call/impl/src/test/kotlin/io/element/android/features/call/ui/CallScreenPresenterTest.kt b/features/call/impl/src/test/kotlin/io/element/android/features/call/ui/CallScreenPresenterTest.kt
index ef571e12b3..23253a2033 100644
--- a/features/call/impl/src/test/kotlin/io/element/android/features/call/ui/CallScreenPresenterTest.kt
+++ b/features/call/impl/src/test/kotlin/io/element/android/features/call/ui/CallScreenPresenterTest.kt
@@ -83,7 +83,7 @@ import kotlin.time.Duration.Companion.seconds
@Test
fun `present - with CallType RoomCall sets call as active, loads URL, runs WidgetDriver and notifies the other clients a call started`() = runTest {
- val sendCallNotificationIfNeededLambda = lambdaRecorder> { Result.success(Unit) }
+ val sendCallNotificationIfNeededLambda = lambdaRecorder> { Result.success(true) }
val syncService = FakeSyncService(SyncState.Running)
val fakeRoom = FakeJoinedRoom(sendCallNotificationIfNeededResult = sendCallNotificationIfNeededLambda)
val client = FakeMatrixClient(syncService = syncService).apply {
diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/ExpandableBottomSheetScaffold.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/ExpandableBottomSheetScaffold.kt
index ca70ca55de..28908498ff 100644
--- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/ExpandableBottomSheetScaffold.kt
+++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/ExpandableBottomSheetScaffold.kt
@@ -10,7 +10,10 @@
package io.element.android.features.messages.impl
import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.fillMaxHeight
+import androidx.compose.foundation.layout.ime
+import androidx.compose.foundation.layout.windowInsetsPadding
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.SheetValue
import androidx.compose.material3.rememberBottomSheetScaffoldState
@@ -112,7 +115,7 @@ internal fun ExpandableBottomSheetScaffold(
}
SubcomposeLayout(
- modifier = modifier,
+ modifier = modifier.windowInsetsPadding(WindowInsets.ime),
measurePolicy = { constraints: Constraints ->
val sheetContentSub = subcompose(Slot.SheetContent(sheetContentKey)) { sheetContent(true) }.map {
it.measure(Constraints(maxWidth = constraints.maxWidth))
@@ -123,7 +126,7 @@ internal fun ExpandableBottomSheetScaffold(
val dragHandleHeight = dragHandleSub?.height?.toDp() ?: 0.dp
val maxHeight = constraints.maxHeight.toDp()
- val contentHeight = sheetContentSub.height.toDp() + dragHandleHeight
+ val contentHeight = sheetContentSub.measuredHeight.toDp() + dragHandleHeight
contentOverflows = contentHeight > maxHeight
@@ -140,7 +143,7 @@ internal fun ExpandableBottomSheetScaffold(
measurePolicy = { measurables, constraints ->
val constraintHeight = constraints.maxHeight
val offset = tryOrNull { scaffoldState.bottomSheetState.requireOffset() } ?: 0f
- val height = Integer.max(0, constraintHeight - offset.roundToInt())
+ val height = Integer.max(peekHeight.roundToPx(), constraintHeight - offset.roundToInt())
val top = measurables[0].measure(
constraints.copy(
minHeight = height,
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index cd95972e97..d4db6807c4 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -172,7 +172,7 @@ jsoup = "org.jsoup:jsoup:1.20.1"
appyx_core = { module = "com.bumble.appyx:core", version.ref = "appyx" }
molecule-runtime = "app.cash.molecule:molecule-runtime:2.1.0"
timber = "com.jakewharton.timber:timber:5.0.1"
-matrix_sdk = "org.matrix.rustcomponents:sdk-android:25.6.6"
+matrix_sdk = "org.matrix.rustcomponents:sdk-android:25.6.10"
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" }
@@ -237,7 +237,7 @@ anvil = { id = "dev.zacsweers.anvil", version.ref = "anvil" }
detekt = { id = "io.gitlab.arturbosch.detekt", version.ref = "detekt" }
ktlint = "org.jlleitschuh.gradle.ktlint:12.3.0"
dependencygraph = "com.savvasdalkitsis.module-dependency-graph:0.12"
-dependencycheck = "org.owasp.dependencycheck:12.1.1"
+dependencycheck = "org.owasp.dependencycheck:12.1.2"
dependencyanalysis = { id = "com.autonomousapps.dependency-analysis", version.ref = "dependencyAnalysis" }
paparazzi = "app.cash.paparazzi:1.3.5"
sqldelight = { id = "app.cash.sqldelight", version.ref = "sqldelight" }
diff --git a/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/ui/View.kt b/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/ui/View.kt
index 1081b6a3fb..cead1384c9 100644
--- a/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/ui/View.kt
+++ b/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/ui/View.kt
@@ -27,6 +27,11 @@ fun View.showKeyboard(andRequestFocus: Boolean = false) {
imm?.showSoftInput(this, InputMethodManager.SHOW_IMPLICIT)
}
+fun View.isKeyboardVisible(): Boolean {
+ val imm = context?.getSystemService()
+ return imm?.isAcceptingText == true
+}
+
suspend fun View.awaitWindowFocus() = suspendCancellableCoroutine { continuation ->
if (hasWindowFocus()) {
continuation.resume(Unit)
diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/permalink/PermalinkParser.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/permalink/PermalinkParser.kt
index 6057656091..f51856db33 100644
--- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/permalink/PermalinkParser.kt
+++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/permalink/PermalinkParser.kt
@@ -12,6 +12,7 @@ package io.element.android.libraries.matrix.api.permalink
* element-based domains (e.g. https://app.element.io/#/user/@chagai95:matrix.org) permalinks
* or matrix.to permalinks (e.g. https://matrix.to/#/@chagai95:matrix.org)
* or client permalinks (e.g. user/@chagai95:matrix.org)
+ * or matrix: permalinks (e.g. matrix:u/chagai95:matrix.org)
*/
interface PermalinkParser {
/**
diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/JoinedRoom.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/JoinedRoom.kt
index 4373721b70..4528c75b1d 100644
--- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/JoinedRoom.kt
+++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/JoinedRoom.kt
@@ -159,7 +159,7 @@ interface JoinedRoom : BaseRoom {
/**
* Send an Element Call started notification if needed.
*/
- suspend fun sendCallNotificationIfNeeded(): Result
+ suspend fun sendCallNotificationIfNeeded(): Result
suspend fun setSendQueueEnabled(enabled: Boolean)
diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/permalink/DefaultPermalinkParser.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/permalink/DefaultPermalinkParser.kt
index 0bf3e908b6..974c88825d 100644
--- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/permalink/DefaultPermalinkParser.kt
+++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/permalink/DefaultPermalinkParser.kt
@@ -29,6 +29,7 @@ import javax.inject.Inject
* element-based domains (e.g. https://app.element.io/#/user/@chagai95:matrix.org) permalinks
* or matrix.to permalinks (e.g. https://matrix.to/#/@chagai95:matrix.org)
* or client permalinks (e.g. user/@chagai95:matrix.org)
+ * or matrix: permalinks (e.g. matrix:u/chagai95:matrix.org)
*/
@ContributesBinding(AppScope::class)
class DefaultPermalinkParser @Inject constructor(
@@ -40,10 +41,15 @@ class DefaultPermalinkParser @Inject constructor(
*/
override fun parse(uriString: String): PermalinkData {
val uri = uriString.toUri()
- // the client or element-based domain permalinks (e.g. https://app.element.io/#/user/@chagai95:matrix.org) don't have the
- // mxid in the first param (like matrix.to does - https://matrix.to/#/@chagai95:matrix.org) but rather in the second after /user/ so /user/mxid
- // so convert URI to matrix.to to simplify parsing process
- val matrixToUri = matrixToConverter.convert(uri) ?: return PermalinkData.FallbackLink(uri)
+ val matrixToUri = if (uri.scheme == "matrix") {
+ // take matrix: URI as is to [parseMatrixEntityFrom]
+ uri
+ } else {
+ // the client or element-based domain permalinks (e.g. https://app.element.io/#/user/@chagai95:matrix.org) don't have the
+ // mxid in the first param (like matrix.to does - https://matrix.to/#/@chagai95:matrix.org) but rather in the second after /user/ so /user/mxid
+ // so convert URI to matrix.to to simplify parsing process
+ matrixToConverter.convert(uri) ?: return PermalinkData.FallbackLink(uri)
+ }
val result = runCatchingExceptions {
parseMatrixEntityFrom(matrixToUri.toString())
diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/JoinedRustRoom.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/JoinedRustRoom.kt
index 18e6571a27..34ff4042cc 100644
--- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/JoinedRustRoom.kt
+++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/JoinedRustRoom.kt
@@ -161,14 +161,16 @@ class JoinedRustRoom(
maxEventsToLoad = 100u,
maxConcurrentRequests = 10u,
)
- is CreateTimelineParams.MediaOnly -> TimelineFocus.Live
+ is CreateTimelineParams.MediaOnly -> TimelineFocus.Live(hideThreadedEvents = false)
is CreateTimelineParams.Focused -> TimelineFocus.Event(
eventId = createTimelineParams.focusedEventId.value,
numContextEvents = 50u,
+ hideThreadedEvents = false,
)
is CreateTimelineParams.MediaOnlyFocused -> TimelineFocus.Event(
eventId = createTimelineParams.focusedEventId.value,
numContextEvents = 50u,
+ hideThreadedEvents = false,
)
}
@@ -427,7 +429,7 @@ class JoinedRustRoom(
}
}
- override suspend fun sendCallNotificationIfNeeded(): Result = withContext(roomDispatcher) {
+ override suspend fun sendCallNotificationIfNeeded(): Result = withContext(roomDispatcher) {
runCatchingExceptions {
innerRoom.sendCallNotificationIfNeeded()
}
diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustRoomFactory.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustRoomFactory.kt
index 4ea1611b38..c842c25876 100644
--- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustRoomFactory.kt
+++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustRoomFactory.kt
@@ -110,7 +110,7 @@ class RustRoomFactory(
// Init the live timeline in the SDK from the Room
val timeline = sdkRoom.timelineWithConfiguration(
TimelineConfiguration(
- focus = TimelineFocus.Live,
+ focus = TimelineFocus.Live(hideThreadedEvents = false),
filter = eventFilters?.let(TimelineFilter::EventTypeFilter) ?: TimelineFilter.All,
internalIdPrefix = "live",
dateDividerMode = DateDividerMode.DAILY,
diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/FakeJoinedRoom.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/FakeJoinedRoom.kt
index 64d0a800d6..ff7a1ddc26 100644
--- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/FakeJoinedRoom.kt
+++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/FakeJoinedRoom.kt
@@ -55,7 +55,7 @@ class FakeJoinedRoom(
private val roomNotificationSettingsService: FakeNotificationSettingsService = FakeNotificationSettingsService(),
private var createTimelineResult: (CreateTimelineParams) -> Result = { lambdaError() },
private val editMessageLambda: (EventId, String, String?, List) -> Result = { _, _, _, _ -> lambdaError() },
- private val sendCallNotificationIfNeededResult: () -> Result = { lambdaError() },
+ private val sendCallNotificationIfNeededResult: () -> Result = { lambdaError() },
private val progressCallbackValues: List> = emptyList(),
private val generateWidgetWebViewUrlResult: (MatrixWidgetSettings, String, String?, String?) -> Result = { _, _, _, _ -> lambdaError() },
private val getWidgetDriverResult: (MatrixWidgetSettings) -> Result = { lambdaError() },
@@ -207,7 +207,7 @@ class FakeJoinedRoom(
return getWidgetDriverResult(widgetSettings)
}
- override suspend fun sendCallNotificationIfNeeded(): Result = simulateLongTask {
+ override suspend fun sendCallNotificationIfNeeded(): Result = simulateLongTask {
sendCallNotificationIfNeededResult()
}
diff --git a/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/SoftKeyboardEffect.kt b/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/SoftKeyboardEffect.kt
index 108e969b04..8f6ca0530d 100644
--- a/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/SoftKeyboardEffect.kt
+++ b/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/SoftKeyboardEffect.kt
@@ -7,6 +7,8 @@
package io.element.android.libraries.textcomposer
+import android.os.Build
+import android.view.WindowInsets
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
@@ -14,6 +16,7 @@ import androidx.compose.runtime.rememberUpdatedState
import androidx.compose.ui.platform.LocalView
import androidx.compose.ui.viewinterop.AndroidView
import io.element.android.libraries.androidutils.ui.awaitWindowFocus
+import io.element.android.libraries.androidutils.ui.isKeyboardVisible
import io.element.android.libraries.androidutils.ui.showKeyboard
/**
@@ -40,11 +43,17 @@ internal fun SoftKeyboardEffect(
// Await window focus in case returning from a dialog
view.awaitWindowFocus()
- // Show the keyboard, temporarily using the root view for focus
- view.showKeyboard(andRequestFocus = true)
+ if (!view.isKeyboardVisible()) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
+ view.windowInsetsController?.show(WindowInsets.Type.ime())
+ } else {
+ // Show the keyboard, temporarily using the root view for focus
+ view.showKeyboard(andRequestFocus = true)
+ }
- // Refocus to the correct view
- latestOnRequestFocus()
+ // Refocus to the correct view
+ latestOnRequestFocus()
+ }
}
}
}
diff --git a/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/TextComposer.kt b/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/TextComposer.kt
index 83d51612d2..29a5f22449 100644
--- a/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/TextComposer.kt
+++ b/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/TextComposer.kt
@@ -18,12 +18,14 @@ import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.IntrinsicSize
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.requiredHeightIn
import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.layout.systemBars
import androidx.compose.foundation.layout.width
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
@@ -35,6 +37,7 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
+import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
@@ -432,8 +435,9 @@ private fun TextFormattingLayout(
sendButton: @Composable () -> Unit,
modifier: Modifier = Modifier
) {
+ val bottomPadding = with(LocalDensity.current) { WindowInsets.systemBars.getBottom(this).toDp() + 8.dp }
Column(
- modifier = modifier.padding(vertical = 4.dp),
+ modifier = modifier.padding(vertical = 4.dp).padding(bottom = bottomPadding),
verticalArrangement = Arrangement.spacedBy(4.dp),
) {
if (isRoomEncrypted == false) {
diff --git a/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/components/markdown/MarkdownEditText.kt b/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/components/markdown/MarkdownEditText.kt
index 7d7a2c00e9..4e66e6c79b 100644
--- a/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/components/markdown/MarkdownEditText.kt
+++ b/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/components/markdown/MarkdownEditText.kt
@@ -8,7 +8,6 @@
package io.element.android.libraries.textcomposer.components.markdown
import android.content.Context
-import android.view.View
import androidx.appcompat.widget.AppCompatEditText
internal class MarkdownEditText(
@@ -37,8 +36,4 @@ internal class MarkdownEditText(
onSelectionChangeListener?.invoke(selStart, selEnd)
}
}
-
- override fun focusSearch(direction: Int): View? {
- return null
- }
}
diff --git a/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/components/markdown/MarkdownTextInput.kt b/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/components/markdown/MarkdownTextInput.kt
index 81b2aab287..58cd9c29d2 100644
--- a/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/components/markdown/MarkdownTextInput.kt
+++ b/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/components/markdown/MarkdownTextInput.kt
@@ -118,6 +118,11 @@ fun MarkdownTextInput(
)
}
state.requestFocusAction = { this.requestFocus() }
+ } else {
+ isEnabled = false
+ isFocusable = false
+ isFocusableInTouchMode = false
+ isClickable = false
}
}
},
diff --git a/plugins/src/main/kotlin/Versions.kt b/plugins/src/main/kotlin/Versions.kt
index cd0dcac4c3..ef5abdf881 100644
--- a/plugins/src/main/kotlin/Versions.kt
+++ b/plugins/src/main/kotlin/Versions.kt
@@ -32,7 +32,7 @@ private const val versionYear = 25
private const val versionMonth = 6
// Note: must be in [0,99]
-private const val versionReleaseNumber = 1
+private const val versionReleaseNumber = 2
object Versions {
const val VERSION_CODE = (2000 + versionYear) * 10_000 + versionMonth * 100 + versionReleaseNumber
diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Day_6_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Day_6_en.png
index 1526024ad2..57f5891983 100644
--- a/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Day_6_en.png
+++ b/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Day_6_en.png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:390f7f3c699ecd843eb79adcefc64a66e6bbb6740dcd3bccf0f90c2f2b70e7e2
-size 54255
+oid sha256:70ab6c6c738a96fd2db7901482cf4110dfcc69a6289e1db734a753751690747a
+size 51622
diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Night_6_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Night_6_en.png
index 1a2a97e582..72dbaca2b9 100644
--- a/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Night_6_en.png
+++ b/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Night_6_en.png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:c6465d7f2e552193d51dd1701d6b4f5726cb616e35cdcd3e219ebf31c22941b9
-size 53750
+oid sha256:237ee8f45cfd01821287669abc4fe7125c0d2828ff60224706eef855910821f0
+size 51048
diff --git a/tests/uitests/src/test/snapshots/images/libraries.textcomposer_TextComposerFormattingNotEncrypted_Day_0_en.png b/tests/uitests/src/test/snapshots/images/libraries.textcomposer_TextComposerFormattingNotEncrypted_Day_0_en.png
index bdf24a86a5..a13eab8827 100644
--- a/tests/uitests/src/test/snapshots/images/libraries.textcomposer_TextComposerFormattingNotEncrypted_Day_0_en.png
+++ b/tests/uitests/src/test/snapshots/images/libraries.textcomposer_TextComposerFormattingNotEncrypted_Day_0_en.png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:c86b3656e5913338c2513af8a56dc725a63d4fda9c5478f429ec684203c68115
-size 64550
+oid sha256:17f126215961643b6b7c3b41fcc5ea970923229b76bb7c918db191cb5c0ef8b9
+size 64672
diff --git a/tests/uitests/src/test/snapshots/images/libraries.textcomposer_TextComposerFormattingNotEncrypted_Night_0_en.png b/tests/uitests/src/test/snapshots/images/libraries.textcomposer_TextComposerFormattingNotEncrypted_Night_0_en.png
index f3a93f5a27..ea9a142676 100644
--- a/tests/uitests/src/test/snapshots/images/libraries.textcomposer_TextComposerFormattingNotEncrypted_Night_0_en.png
+++ b/tests/uitests/src/test/snapshots/images/libraries.textcomposer_TextComposerFormattingNotEncrypted_Night_0_en.png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:1b491d682990192c54ebdae58045caab1ff653d3d6c89ca5f15e93bbb45c6fc0
-size 62052
+oid sha256:f72fdf44bd35b6d62b5e75ec1bf2206429e0861da684d370259bea684eb2a76e
+size 62158
diff --git a/tests/uitests/src/test/snapshots/images/libraries.textcomposer_TextComposerFormatting_Day_0_en.png b/tests/uitests/src/test/snapshots/images/libraries.textcomposer_TextComposerFormatting_Day_0_en.png
index 29a5c83fc0..7b011af46e 100644
--- a/tests/uitests/src/test/snapshots/images/libraries.textcomposer_TextComposerFormatting_Day_0_en.png
+++ b/tests/uitests/src/test/snapshots/images/libraries.textcomposer_TextComposerFormatting_Day_0_en.png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:d2bc5c13d2a1e100bdcd0142b7f03b458ba80d0d6ded82de1ebb1b6aa055a344
-size 53893
+oid sha256:d5226d7599af16015858523607a829d9cbbc0ffa21174f9c528e725673e2e31c
+size 53982
diff --git a/tests/uitests/src/test/snapshots/images/libraries.textcomposer_TextComposerFormatting_Night_0_en.png b/tests/uitests/src/test/snapshots/images/libraries.textcomposer_TextComposerFormatting_Night_0_en.png
index bc91fe1061..ccb1438c05 100644
--- a/tests/uitests/src/test/snapshots/images/libraries.textcomposer_TextComposerFormatting_Night_0_en.png
+++ b/tests/uitests/src/test/snapshots/images/libraries.textcomposer_TextComposerFormatting_Night_0_en.png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:fb2210aa87440b2e87217ff53448091078bdff658a6b36fad12cb1a57caf59e2
-size 51187
+oid sha256:31b2d698e78fa11dc6af9dbc20b07533afd16c26b8f9969756057f07ea4785d0
+size 51371
diff --git a/tools/adb/deeplink_matrix.sh b/tools/adb/deeplink_matrix.sh
new file mode 100755
index 0000000000..f11d810ec9
--- /dev/null
+++ b/tools/adb/deeplink_matrix.sh
@@ -0,0 +1,9 @@
+#! /bin/bash
+
+# Copyright 2025 New Vector Ltd.
+#
+# SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
+# Please see LICENSE files in the repository root for full details.
+
+adb shell am start -a android.intent.action.VIEW \
+ -d "matrix:r/element-android:matrix.org"