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"