Merge branch 'release/26.06.1'

This commit is contained in:
Jorge Martín
2026-06-04 16:13:51 +02:00
11 changed files with 135 additions and 19 deletions
@@ -0,0 +1,2 @@
Main changes in this version: fixed an issue with devices not supporting Vulkan, added image editing before sending and an option for custom notification sounds, improved start times, several other bug fixes and improvements.
Full changelog: https://github.com/element-hq/element-x-android/releases
@@ -75,10 +75,10 @@ internal fun RoomSummaryRow(
room: RoomListRoomSummary,
hideInviteAvatars: Boolean,
isInviteSeen: Boolean,
showUnreadCount: Boolean = false,
onClick: (RoomListRoomSummary) -> Unit,
eventSink: (RoomListEvent) -> Unit,
modifier: Modifier = Modifier,
showUnreadCount: Boolean = false,
eventSink: (RoomListEvent) -> Unit,
) {
Box(modifier = modifier) {
when (room.displayType) {
@@ -0,0 +1,30 @@
/*
* Copyright (c) 2026 Element Creations Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
* Please see LICENSE files in the repository root for full details.
*/
package io.element.android.features.location.api
import androidx.compose.runtime.Composable
import androidx.compose.ui.res.stringResource
import io.element.android.libraries.designsystem.components.dialogs.ErrorDialog
import io.element.android.libraries.designsystem.preview.ElementPreview
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
import io.element.android.libraries.ui.strings.CommonStrings
@Composable
fun RenderingMapsNotSupportedDialog(onSubmit: () -> Unit) {
ErrorDialog(
title = stringResource(CommonStrings.vulkan_not_supported_dialog_title_android),
content = stringResource(CommonStrings.vulkan_not_supported_dialog_content_android),
onSubmit = onSubmit,
)
}
@PreviewsDayNight
@Composable
internal fun RenderingMapsNotSupportedDialogPreview() = ElementPreview {
RenderingMapsNotSupportedDialog(onSubmit = {})
}
@@ -5,13 +5,21 @@
~ SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
~ Please see LICENSE files in the repository root for full details.
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<manifest
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" >
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_LOCATION" />
<!-- Declare Vulkan as an optional feature to prevent installation issues on devices that do not support it. -->
<uses-feature
android:name="android.hardware.vulkan.version"
android:required="false"
tools:replace="android:required" />
<application>
<service
android:name=".live.service.LiveLocationSharingService"
@@ -11,6 +11,9 @@ package io.element.android.features.messages.impl
import android.os.Parcelable
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.lifecycle.lifecycleScope
import com.bumble.appyx.core.lifecycle.subscribe
@@ -29,6 +32,7 @@ import io.element.android.features.call.api.ElementCallEntryPoint
import io.element.android.features.forward.api.ForwardEntryPoint
import io.element.android.features.knockrequests.api.list.KnockRequestsListEntryPoint
import io.element.android.features.location.api.LocationService
import io.element.android.features.location.api.RenderingMapsNotSupportedDialog
import io.element.android.features.location.api.ShareLocationEntryPoint
import io.element.android.features.location.api.ShowLocationEntryPoint
import io.element.android.features.location.api.ShowLocationMode
@@ -53,6 +57,7 @@ import io.element.android.features.messages.impl.timeline.model.event.TimelineIt
import io.element.android.features.messages.impl.timeline.model.event.duration
import io.element.android.features.poll.api.create.CreatePollEntryPoint
import io.element.android.features.poll.api.create.CreatePollMode
import io.element.android.libraries.androidutils.system.DeviceHasVulkanSupport
import io.element.android.libraries.architecture.BackstackWithOverlayBox
import io.element.android.libraries.architecture.BaseFlowNode
import io.element.android.libraries.architecture.callback
@@ -121,6 +126,7 @@ class MessagesFlowNode(
private val knockRequestsListEntryPoint: KnockRequestsListEntryPoint,
private val dateFormatter: DateFormatter,
private val coroutineDispatchers: CoroutineDispatchers,
private val hasVulkanSupport: DeviceHasVulkanSupport,
) : BaseFlowNode<MessagesFlowNode.NavTarget>(
backstack = BackStack(
initialElement = plugins.filterIsInstance<MessagesEntryPoint.Params>().first().initialTarget.toNavTarget(),
@@ -188,6 +194,8 @@ class MessagesFlowNode(
private val callback: MessagesEntryPoint.Callback = callback()
private var displayVulkanNotSupportedError by mutableStateOf(false)
override fun onBuilt() {
super.onBuilt()
lifecycle.subscribe(
@@ -267,7 +275,11 @@ class MessagesFlowNode(
}
override fun navigateToSendLocation() {
backstack.push(NavTarget.SendLocation(Timeline.Mode.Live))
if (hasVulkanSupport()) {
backstack.push(NavTarget.SendLocation(Timeline.Mode.Live))
} else {
displayVulkanNotSupportedError = true
}
}
override fun navigateToCreatePoll() {
@@ -279,7 +291,11 @@ class MessagesFlowNode(
}
override fun navigateToCurrentLiveLocation() {
backstack.push(NavTarget.LocationViewer(ShowLocationMode.Live(senderId = sessionId)))
if (hasVulkanSupport()) {
backstack.push(NavTarget.LocationViewer(ShowLocationMode.Live(senderId = sessionId)))
} else {
displayVulkanNotSupportedError = true
}
}
override fun navigateToRoomCall(roomId: RoomId, isAudioCall: Boolean) {
@@ -506,7 +522,11 @@ class MessagesFlowNode(
}
override fun navigateToSendLocation() {
backstack.push(NavTarget.SendLocation(Timeline.Mode.Thread(navTarget.threadRootId)))
if (hasVulkanSupport()) {
backstack.push(NavTarget.SendLocation(Timeline.Mode.Thread(navTarget.threadRootId)))
} else {
displayVulkanNotSupportedError = true
}
}
override fun navigateToCreatePoll() {
@@ -518,7 +538,11 @@ class MessagesFlowNode(
}
override fun navigateToCurrentLiveLocation() {
backstack.push(NavTarget.LocationViewer(ShowLocationMode.Live(senderId = sessionId)))
if (hasVulkanSupport()) {
backstack.push(NavTarget.LocationViewer(ShowLocationMode.Live(senderId = sessionId)))
} else {
displayVulkanNotSupportedError = true
}
}
override fun navigateToRoomCall(roomId: RoomId, isAudioCall: Boolean) {
@@ -607,18 +631,23 @@ class MessagesFlowNode(
)
}
is TimelineItemLocationContent -> {
val mode = when (event.content.mode) {
is TimelineItemLocationContent.Mode.Live -> ShowLocationMode.Live(event.senderId)
is TimelineItemLocationContent.Mode.Static -> ShowLocationMode.Static(
location = event.content.mode.location,
senderName = event.safeSenderName,
senderId = event.senderId,
senderAvatarUrl = event.senderAvatar.url,
timestamp = event.sentTimeMillis,
assetType = event.content.assetType,
)
if (hasVulkanSupport()) {
val mode = when (event.content.mode) {
is TimelineItemLocationContent.Mode.Live -> ShowLocationMode.Live(event.senderId)
is TimelineItemLocationContent.Mode.Static -> ShowLocationMode.Static(
location = event.content.mode.location,
senderName = event.safeSenderName,
senderId = event.senderId,
senderAvatarUrl = event.senderAvatar.url,
timestamp = event.sentTimeMillis,
assetType = event.content.assetType,
)
}
NavTarget.LocationViewer(mode = mode).takeIf { locationService.isServiceAvailable() }
} else {
displayVulkanNotSupportedError = true
null
}
NavTarget.LocationViewer(mode = mode).takeIf { locationService.isServiceAvailable() }
}
else -> null
}
@@ -691,6 +720,11 @@ class MessagesFlowNode(
@Composable
override fun View(modifier: Modifier) {
mentionSpanTheme.updateStyles()
if (displayVulkanNotSupportedError) {
RenderingMapsNotSupportedDialog { displayVulkanNotSupportedError = false }
}
CompositionLocalProvider(
LocalMentionSpanUpdater provides mentionSpanUpdater
) {
@@ -23,6 +23,7 @@ import io.element.android.features.messages.api.MessagesEntryPoint
import io.element.android.features.messages.impl.pinned.banner.createPinnedEventsTimelineProvider
import io.element.android.features.messages.impl.timeline.createTimelineController
import io.element.android.features.poll.test.create.FakeCreatePollEntryPoint
import io.element.android.libraries.androidutils.system.DeviceHasVulkanSupport
import io.element.android.libraries.dateformatter.test.FakeDateFormatter
import io.element.android.libraries.matrix.api.core.EventId
import io.element.android.libraries.matrix.api.core.RoomId
@@ -42,6 +43,7 @@ import io.element.android.services.analytics.test.FakeAnalyticsService
import io.element.android.tests.testutils.lambda.lambdaError
import io.element.android.tests.testutils.node.TestParentNode
import io.element.android.tests.testutils.testCoroutineDispatchers
import io.mockk.mockk
import kotlinx.coroutines.test.runTest
import org.junit.Rule
import org.junit.Test
@@ -85,6 +87,7 @@ class DefaultMessagesEntryPointTest {
knockRequestsListEntryPoint = FakeKnockRequestsListEntryPoint(),
dateFormatter = FakeDateFormatter(),
coroutineDispatchers = testCoroutineDispatchers(),
hasVulkanSupport = DeviceHasVulkanSupport(mockk(relaxed = true))
)
}
val callback = object : MessagesEntryPoint.Callback {
@@ -0,0 +1,31 @@
/*
* Copyright (c) 2026 Element Creations Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
* Please see LICENSE files in the repository root for full details.
*/
package io.element.android.libraries.androidutils.system
import android.content.Context
import dev.zacsweers.metro.Inject
import io.element.android.libraries.di.annotations.ApplicationContext
private const val VULKAN_VERSION_1_0 = 0x400003
/**
* Checks if the device supports Vulkan 1.0.
*
* This is needed for the location screens that contain maps using MapLibre UI components.
*
* Needed until https://github.com/maplibre/maplibre-native/issues/3079 is resolved and we can automatically choose between OpenGL and Vulkan renderers,
* or no devices support OpenGL anymore.
*/
@Inject
class DeviceHasVulkanSupport(
@ApplicationContext private val context: Context,
) {
operator fun invoke(): Boolean {
return context.packageManager.hasSystemFeature("android.hardware.vulkan.version", VULKAN_VERSION_1_0)
}
}
@@ -546,4 +546,6 @@ Are you sure you want to continue?"</string>
<string name="timeline_decryption_failure_historical_event_user_not_joined">"You don\'t have access to this message"</string>
<string name="timeline_decryption_failure_unable_to_decrypt">"Unable to decrypt message"</string>
<string name="timeline_decryption_failure_withheld_unverified">"This message was blocked either because you did not verify your device or because the sender needs to verify your digital identity."</string>
<string name="vulkan_not_supported_dialog_content_android">"Your device is too old, a device with Android 8 or newer is required."</string>
<string name="vulkan_not_supported_dialog_title_android">"Rendering maps is not supported"</string>
</resources>
+1 -1
View File
@@ -45,7 +45,7 @@ private const val versionMonth = 6
* Release number in the month. Value must be in [0,99].
* Do not update this value. it is updated by the release script.
*/
private const val versionReleaseNumber = 0
private const val versionReleaseNumber = 1
object Versions {
/**
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:4f64455657f0f8a5ac7eec71ee206721a79b25dacf470d105a0e6497e570204a
size 23954
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:43bd5f04f170372b120fa2223c632d6a969c45b6d4fe61b161a7109318f9941f
size 22658