From 80591449ab029f5e805b7008ed414560ed870266 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 28 Sep 2026 07:30:15 +0000 Subject: [PATCH 01/20] Update zizmorcore/zizmor-action action to v0.6.4 --- .github/workflows/quality.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/quality.yml b/.github/workflows/quality.yml index 1f6289a8ea..59a5d63b4d 100644 --- a/.github/workflows/quality.yml +++ b/.github/workflows/quality.yml @@ -336,7 +336,7 @@ jobs: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false - - uses: zizmorcore/zizmor-action@70fb788f84895a7701f5643d103d587e460b5c99 # v0.6.3 + - uses: zizmorcore/zizmor-action@cc914d7f3750a2d13d75c7f184a1060aa0e9d482 # v0.6.4 upload_reports: name: Project Check Suite From a30730462b5982002900f3a4c8f804923b3ec2a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20Mart=C3=ADn?= Date: Tue, 29 Sep 2026 16:31:01 +0200 Subject: [PATCH 02/20] Revert "Fix unread notification count in room list (#7749)" This reverts commit b80a24e0aac71d1d69a7287a32d4f7fefd80675b. --- .../home/impl/components/RoomSummaryRow.kt | 18 ++-- .../model/RoomListRoomSummaryPreviewParam.kt | 18 ---- .../designsystem/atomic/atoms/CounterAtom.kt | 90 ++++++++----------- .../atomic/atoms/UnreadIndicatorAtom.kt | 54 ++++++----- .../components/HorizontalFloatingToolbar.kt | 87 ++++++------------ .../libraries/matrix/ui/model/InviteSender.kt | 2 - ...omponents_RoomListContentView_Day_0_en.png | 4 +- ...omponents_RoomListContentView_Day_5_en.png | 4 +- ...ponents_RoomListContentView_Night_0_en.png | 4 +- ...ponents_RoomListContentView_Night_5_en.png | 4 +- ...pl.components_RoomSummaryRow_Day_10_en.png | 4 +- ...pl.components_RoomSummaryRow_Day_11_en.png | 4 +- ...pl.components_RoomSummaryRow_Day_12_en.png | 4 +- ...pl.components_RoomSummaryRow_Day_13_en.png | 4 +- ...pl.components_RoomSummaryRow_Day_14_en.png | 4 +- ...pl.components_RoomSummaryRow_Day_15_en.png | 4 +- ...pl.components_RoomSummaryRow_Day_16_en.png | 4 +- ...pl.components_RoomSummaryRow_Day_17_en.png | 4 +- ...pl.components_RoomSummaryRow_Day_18_en.png | 4 +- ...pl.components_RoomSummaryRow_Day_19_en.png | 4 +- ...pl.components_RoomSummaryRow_Day_20_en.png | 4 +- ...pl.components_RoomSummaryRow_Day_21_en.png | 4 +- ...pl.components_RoomSummaryRow_Day_22_en.png | 4 +- ...pl.components_RoomSummaryRow_Day_23_en.png | 4 +- ...pl.components_RoomSummaryRow_Day_24_en.png | 4 +- ...pl.components_RoomSummaryRow_Day_25_en.png | 4 +- ...pl.components_RoomSummaryRow_Day_26_en.png | 4 +- ...pl.components_RoomSummaryRow_Day_27_en.png | 4 +- ...pl.components_RoomSummaryRow_Day_28_en.png | 4 +- ...pl.components_RoomSummaryRow_Day_29_en.png | 4 +- ...pl.components_RoomSummaryRow_Day_30_en.png | 4 +- ...pl.components_RoomSummaryRow_Day_31_en.png | 4 +- ...pl.components_RoomSummaryRow_Day_32_en.png | 4 +- ...pl.components_RoomSummaryRow_Day_33_en.png | 4 +- ...pl.components_RoomSummaryRow_Day_34_en.png | 4 +- ...pl.components_RoomSummaryRow_Day_35_en.png | 4 +- ...pl.components_RoomSummaryRow_Day_36_en.png | 4 +- ...pl.components_RoomSummaryRow_Day_37_en.png | 4 +- ...pl.components_RoomSummaryRow_Day_38_en.png | 4 +- ...pl.components_RoomSummaryRow_Day_39_en.png | 3 - ...pl.components_RoomSummaryRow_Day_40_en.png | 3 - ...pl.components_RoomSummaryRow_Day_41_en.png | 3 - ...pl.components_RoomSummaryRow_Day_42_en.png | 3 - ...pl.components_RoomSummaryRow_Day_43_en.png | 3 - ...pl.components_RoomSummaryRow_Day_44_en.png | 3 - ...pl.components_RoomSummaryRow_Day_45_en.png | 3 - ...pl.components_RoomSummaryRow_Day_46_en.png | 3 - ...pl.components_RoomSummaryRow_Day_47_en.png | 3 - ...pl.components_RoomSummaryRow_Day_48_en.png | 3 - ...pl.components_RoomSummaryRow_Day_49_en.png | 3 - ...mpl.components_RoomSummaryRow_Day_4_en.png | 4 +- ...pl.components_RoomSummaryRow_Day_50_en.png | 3 - ...mpl.components_RoomSummaryRow_Day_6_en.png | 2 +- ...mpl.components_RoomSummaryRow_Day_7_en.png | 4 +- ...mpl.components_RoomSummaryRow_Day_8_en.png | 4 +- ...mpl.components_RoomSummaryRow_Day_9_en.png | 4 +- ....components_RoomSummaryRow_Night_10_en.png | 4 +- ....components_RoomSummaryRow_Night_11_en.png | 4 +- ....components_RoomSummaryRow_Night_12_en.png | 4 +- ....components_RoomSummaryRow_Night_13_en.png | 4 +- ....components_RoomSummaryRow_Night_14_en.png | 4 +- ....components_RoomSummaryRow_Night_15_en.png | 4 +- ....components_RoomSummaryRow_Night_16_en.png | 4 +- ....components_RoomSummaryRow_Night_17_en.png | 4 +- ....components_RoomSummaryRow_Night_18_en.png | 4 +- ....components_RoomSummaryRow_Night_19_en.png | 4 +- ....components_RoomSummaryRow_Night_20_en.png | 4 +- ....components_RoomSummaryRow_Night_21_en.png | 4 +- ....components_RoomSummaryRow_Night_22_en.png | 4 +- ....components_RoomSummaryRow_Night_23_en.png | 4 +- ....components_RoomSummaryRow_Night_24_en.png | 4 +- ....components_RoomSummaryRow_Night_25_en.png | 4 +- ....components_RoomSummaryRow_Night_26_en.png | 4 +- ....components_RoomSummaryRow_Night_27_en.png | 4 +- ....components_RoomSummaryRow_Night_28_en.png | 4 +- ....components_RoomSummaryRow_Night_29_en.png | 4 +- ....components_RoomSummaryRow_Night_30_en.png | 4 +- ....components_RoomSummaryRow_Night_31_en.png | 4 +- ....components_RoomSummaryRow_Night_32_en.png | 4 +- ....components_RoomSummaryRow_Night_33_en.png | 4 +- ....components_RoomSummaryRow_Night_34_en.png | 4 +- ....components_RoomSummaryRow_Night_35_en.png | 4 +- ....components_RoomSummaryRow_Night_36_en.png | 4 +- ....components_RoomSummaryRow_Night_37_en.png | 4 +- ....components_RoomSummaryRow_Night_38_en.png | 4 +- ....components_RoomSummaryRow_Night_39_en.png | 3 - ....components_RoomSummaryRow_Night_40_en.png | 3 - ....components_RoomSummaryRow_Night_41_en.png | 3 - ....components_RoomSummaryRow_Night_42_en.png | 3 - ....components_RoomSummaryRow_Night_43_en.png | 3 - ....components_RoomSummaryRow_Night_44_en.png | 3 - ....components_RoomSummaryRow_Night_45_en.png | 3 - ....components_RoomSummaryRow_Night_46_en.png | 3 - ....components_RoomSummaryRow_Night_47_en.png | 3 - ....components_RoomSummaryRow_Night_48_en.png | 3 - ....components_RoomSummaryRow_Night_49_en.png | 3 - ...l.components_RoomSummaryRow_Night_4_en.png | 4 +- ....components_RoomSummaryRow_Night_50_en.png | 3 - ...l.components_RoomSummaryRow_Night_6_en.png | 4 +- ...l.components_RoomSummaryRow_Night_7_en.png | 4 +- ...l.components_RoomSummaryRow_Night_8_en.png | 2 +- ...l.components_RoomSummaryRow_Night_9_en.png | 4 +- ....search_RoomListSearchContent_Day_1_en.png | 4 +- ...earch_RoomListSearchContent_Night_1_en.png | 4 +- ...me.impl.spaces_HomeSpacesView_Day_0_en.png | 4 +- ....impl.spaces_HomeSpacesView_Night_0_en.png | 4 +- .../features.home.impl_HomeViewA11y_en.png | 4 +- .../features.home.impl_HomeView_Day_0_en.png | 4 +- .../features.home.impl_HomeView_Day_10_en.png | 4 +- .../features.home.impl_HomeView_Day_11_en.png | 4 +- .../features.home.impl_HomeView_Day_13_en.png | 4 +- .../features.home.impl_HomeView_Day_14_en.png | 4 +- .../features.home.impl_HomeView_Day_15_en.png | 4 +- .../features.home.impl_HomeView_Day_16_en.png | 4 +- .../features.home.impl_HomeView_Day_1_en.png | 4 +- .../features.home.impl_HomeView_Day_2_en.png | 4 +- .../features.home.impl_HomeView_Day_3_en.png | 4 +- .../features.home.impl_HomeView_Day_4_en.png | 4 +- .../features.home.impl_HomeView_Day_5_en.png | 4 +- .../features.home.impl_HomeView_Day_6_en.png | 4 +- .../features.home.impl_HomeView_Day_7_en.png | 4 +- .../features.home.impl_HomeView_Day_8_en.png | 4 +- .../features.home.impl_HomeView_Day_9_en.png | 4 +- ...features.home.impl_HomeView_Night_0_en.png | 4 +- ...eatures.home.impl_HomeView_Night_10_en.png | 4 +- ...eatures.home.impl_HomeView_Night_11_en.png | 4 +- ...eatures.home.impl_HomeView_Night_13_en.png | 4 +- ...eatures.home.impl_HomeView_Night_14_en.png | 4 +- ...eatures.home.impl_HomeView_Night_15_en.png | 4 +- ...eatures.home.impl_HomeView_Night_16_en.png | 4 +- ...features.home.impl_HomeView_Night_1_en.png | 4 +- ...features.home.impl_HomeView_Night_2_en.png | 4 +- ...features.home.impl_HomeView_Night_3_en.png | 4 +- ...features.home.impl_HomeView_Night_4_en.png | 4 +- ...features.home.impl_HomeView_Night_5_en.png | 4 +- ...features.home.impl_HomeView_Night_6_en.png | 4 +- ...features.home.impl_HomeView_Night_7_en.png | 4 +- ...features.home.impl_HomeView_Night_8_en.png | 4 +- ...features.home.impl_HomeView_Night_9_en.png | 4 +- ...wWithReadMarkerBothIndicators_Day_0_en.png | 4 +- ...ithReadMarkerBothIndicators_Night_0_en.png | 4 +- ...rkerJumpToUnreadIndicatorOnly_Day_0_en.png | 4 +- ...erJumpToUnreadIndicatorOnly_Night_0_en.png | 4 +- ...roomdetails.impl_RoomDetailsDark_15_en.png | 4 +- ...res.roomdetails.impl_RoomDetails_15_en.png | 4 +- ...res.space.impl.root_SpaceView_Day_3_en.png | 4 +- ...res.space.impl.root_SpaceView_Day_4_en.png | 4 +- ...s.space.impl.root_SpaceView_Night_3_en.png | 4 +- ...s.space.impl.root_SpaceView_Night_4_en.png | 4 +- ...stem.atomic.atoms_CounterAtom_Day_0_en.png | 4 +- ...em.atomic.atoms_CounterAtom_Night_0_en.png | 4 +- ...mic.atoms_UnreadIndicatorAtom_Day_0_en.png | 4 +- ...c.atoms_UnreadIndicatorAtom_Night_0_en.png | 4 +- ...orizontalFloatingToolbarNoFab_Day_0_en.png | 4 +- ...izontalFloatingToolbarNoFab_Night_0_en.png | 4 +- ...nts_HorizontalFloatingToolbar_Day_0_en.png | 4 +- ...s_HorizontalFloatingToolbar_Night_0_en.png | 4 +- ...e.components_NavigationBar_App_Bars_en.png | 4 +- ....components_SpaceRoomItemView_Day_3_en.png | 4 +- ....components_SpaceRoomItemView_Day_4_en.png | 4 +- ....components_SpaceRoomItemView_Day_7_en.png | 4 +- ...omponents_SpaceRoomItemView_Night_3_en.png | 4 +- ...omponents_SpaceRoomItemView_Night_4_en.png | 4 +- ...omponents_SpaceRoomItemView_Night_7_en.png | 4 +- 164 files changed, 367 insertions(+), 506 deletions(-) delete mode 100644 tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Day_39_en.png delete mode 100644 tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Day_40_en.png delete mode 100644 tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Day_41_en.png delete mode 100644 tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Day_42_en.png delete mode 100644 tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Day_43_en.png delete mode 100644 tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Day_44_en.png delete mode 100644 tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Day_45_en.png delete mode 100644 tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Day_46_en.png delete mode 100644 tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Day_47_en.png delete mode 100644 tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Day_48_en.png delete mode 100644 tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Day_49_en.png delete mode 100644 tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Day_50_en.png delete mode 100644 tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Night_39_en.png delete mode 100644 tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Night_40_en.png delete mode 100644 tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Night_41_en.png delete mode 100644 tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Night_42_en.png delete mode 100644 tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Night_43_en.png delete mode 100644 tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Night_44_en.png delete mode 100644 tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Night_45_en.png delete mode 100644 tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Night_46_en.png delete mode 100644 tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Night_47_en.png delete mode 100644 tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Night_48_en.png delete mode 100644 tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Night_49_en.png delete mode 100644 tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Night_50_en.png diff --git a/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/components/RoomSummaryRow.kt b/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/components/RoomSummaryRow.kt index 4b60e9e684..883d8aff76 100644 --- a/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/components/RoomSummaryRow.kt +++ b/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/components/RoomSummaryRow.kt @@ -345,7 +345,7 @@ private fun MessagePreviewAndIndicatorRow( // Call and unread Row( modifier = Modifier - .heightIn(min = 16.dp) + .height(16.dp) // Used to force this line to be read aloud earlier than the latest event when using Talkback .zIndex(-1f), horizontalArrangement = Arrangement.spacedBy(8.dp), @@ -358,18 +358,21 @@ private fun MessagePreviewAndIndicatorRow( isAudio = room.activeCallIntent == CallIntent.AUDIO ) } - val isMuted = room.userDefinedNotificationMode == RoomNotificationMode.MUTE - if (isMuted) { + if (room.userDefinedNotificationMode == RoomNotificationMode.MUTE) { NotificationOffIndicatorAtom() } else if (room.numberOfUnreadMentions > 0) { MentionIndicatorAtom() } if (room.hasNewContent) { val contentDescription = stringResource(CommonStrings.a11y_notifications_new_messages) - val count = when { - showUnreadCount && !isMuted && room.numberOfUnreadNotifications > 0 -> room.numberOfUnreadNotifications - !showUnreadCount -> 0 - else -> null + val count = if (showUnreadCount) { + if (room.userDefinedNotificationMode == RoomNotificationMode.MUTE) { + room.numberOfUnreadMessages + } else { + room.numberOfUnreadNotifications + } + } else { + null } UnreadIndicatorAtom( color = tint, @@ -454,6 +457,5 @@ internal fun RoomSummaryRowPreview(@PreviewParameter(RoomListRoomSummaryPreviewP isInviteSeen = data.name == "Bob", onClick = {}, eventSink = {}, - showUnreadCount = data.numberOfUnreadNotifications > 0, ) } diff --git a/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/model/RoomListRoomSummaryPreviewParam.kt b/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/model/RoomListRoomSummaryPreviewParam.kt index 17b35c3b42..c04c3e446b 100644 --- a/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/model/RoomListRoomSummaryPreviewParam.kt +++ b/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/model/RoomListRoomSummaryPreviewParam.kt @@ -82,24 +82,6 @@ open class RoomListRoomSummaryPreviewParam : PreviewParameterProvider null - in 1..MAX_COUNT -> count.toString() + in 0..MAX_COUNT -> count.toString() else -> MAX_COUNT_STRING } - - val minSingleDigitSize = if (countAsText?.length == 1) { - val textMeasurer = rememberTextMeasurer() - // Measure the maximum count string size - val textLayoutResult = textMeasurer.measure( - text = countAsText, - style = textStyle - ) - val textSize = textLayoutResult.size - maxOf(textSize.width, textSize.height) - } else { - 0 - } - - androidx.compose.material3.Badge( - modifier = modifier.then( - if (countAsText == null) Modifier.sizeIn(minWidth = dotSize, minHeight = dotSize) else Modifier - ), - containerColor = containerColor ?: if (isCritical) ElementTheme.colors.iconCriticalPrimary else ElementTheme.colors.iconAccentPrimary, - contentColor = contentColor ?: ElementTheme.colors.textOnSolidPrimary, - content = countAsText?.let { - @Composable { - Text( - modifier = Modifier.sizeIn( - minWidth = (minSingleDigitSize.toDp() - 8.dp).coerceAtLeast(0.dp), - minHeight = minSingleDigitSize.toDp() - ).padding(contentPadding), - text = countAsText, - style = textStyle, - color = contentColor ?: ElementTheme.colors.textOnSolidPrimary, - textAlign = TextAlign.Center, - ) - } - } + val textMeasurer = rememberTextMeasurer() + // Measure the maximum count string size + val textLayoutResult = textMeasurer.measure( + text = MAX_COUNT_STRING, + style = textStyle ) + val textSize = textLayoutResult.size + val squareSize = maxOf(textSize.width, textSize.height) + Box( + modifier = modifier + .size(squareSize.toDp() + 1.dp) + .clip(CircleShape) + .background( + containerColor ?: if (isCritical) { + ElementTheme.colors.iconCriticalPrimary + } else { + ElementTheme.colors.iconAccentPrimary + } + ) + ) { + Text( + modifier = Modifier.align(Alignment.Center), + text = countAsText, + style = textStyle, + color = contentColor ?: ElementTheme.colors.textOnSolidPrimary, + ) + } } object CounterAtomDefaults { @@ -109,7 +95,7 @@ object CounterAtomDefaults { @PreviewsDayNight @Composable internal fun CounterAtomPreview() = ElementPreview { - Row(horizontalArrangement = spacedBy(2.dp), verticalAlignment = Alignment.CenterVertically) { + Column(verticalArrangement = spacedBy(2.dp)) { CounterAtom(count = 0) CounterAtom(count = 4) CounterAtom(count = 99) diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/atoms/UnreadIndicatorAtom.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/atoms/UnreadIndicatorAtom.kt index 332621bba5..bdf039edb8 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/atoms/UnreadIndicatorAtom.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/atoms/UnreadIndicatorAtom.kt @@ -9,16 +9,15 @@ package io.element.android.libraries.designsystem.atomic.atoms import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.background import androidx.compose.foundation.border -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.PaddingValues -import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.size -import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.shape.CircleShape import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color import androidx.compose.ui.semantics.contentDescription import androidx.compose.ui.semantics.semantics @@ -38,36 +37,35 @@ fun UnreadIndicatorAtom( isVisible: Boolean = true, contentDescription: String? = null, border: BorderStroke? = null, - contentPadding: PaddingValues = PaddingValues.Zero, ) { when { !isVisible -> Spacer(modifier = modifier.size(size)) - else -> count?.let { - CounterAtom( - count = count.toInt(), - modifier = modifier - .semantics { - contentDescription?.let { this.contentDescription = it } - } - .then(if (border != null) Modifier.border(border, RoundedCornerShape(percent = 50)) else Modifier), - containerColor = color, - contentColor = ElementTheme.colors.bgCanvasDefault, - textStyle = ElementTheme.typography.fontBodySmMedium, - contentPadding = contentPadding, - ) - } + count != null && count >= 1 -> CounterAtom( + count = count.toInt(), + modifier = modifier + .semantics { + contentDescription?.let { this.contentDescription = it } + } + .then(if (border != null) Modifier.border(border, CircleShape) else Modifier), + containerColor = color, + contentColor = ElementTheme.colors.bgCanvasDefault, + textStyle = ElementTheme.typography.fontBodySmMedium, + ) + else -> Box( + modifier = modifier + .semantics { + contentDescription?.let { this.contentDescription = it } + } + .size(size) + .clip(CircleShape) + .background(color) + .then(if (border != null) Modifier.border(border, CircleShape) else Modifier), + ) } } @PreviewsDayNight @Composable internal fun UnreadIndicatorAtomPreview() = ElementPreview { - Row(horizontalArrangement = Arrangement.spacedBy(2.dp), verticalAlignment = Alignment.CenterVertically) { - UnreadIndicatorAtom(count = null) - UnreadIndicatorAtom(count = 0) - UnreadIndicatorAtom(count = 1) - UnreadIndicatorAtom(count = 10) - UnreadIndicatorAtom(count = 99) - UnreadIndicatorAtom(count = 999) - } + UnreadIndicatorAtom() } diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/HorizontalFloatingToolbar.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/HorizontalFloatingToolbar.kt index 74cdc33f17..8cdb320d30 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/HorizontalFloatingToolbar.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/HorizontalFloatingToolbar.kt @@ -8,18 +8,17 @@ package io.element.android.libraries.designsystem.theme.components import androidx.compose.animation.core.FiniteAnimationSpec -import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.RowScope import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.offset import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.widthIn -import androidx.compose.material3.BadgedBox import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi +import androidx.compose.material3.FilledIconButton import androidx.compose.material3.FloatingToolbarColors import androidx.compose.material3.FloatingToolbarDefaults import androidx.compose.material3.FloatingToolbarHorizontalFabPosition @@ -29,6 +28,7 @@ import androidx.compose.material3.TooltipAnchorPosition import androidx.compose.material3.TooltipDefaults import androidx.compose.material3.rememberTooltipState import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Shape @@ -139,20 +139,8 @@ fun HorizontalFloatingToolbarItem( contentColor = ElementTheme.colors.iconSecondary, ) } - BadgedBox( - badge = { - counter?.let { - CounterAtom( - count = it, - // Tweak the offset so it's centered on the icon - modifier = Modifier.offset(x = (-16).dp, y = 10.dp), - contentPadding = PaddingValues(vertical = 0.5.dp), - textStyle = ElementTheme.typography.fontBodySmMedium, - ) - } - } - ) { - IconButton( + Box { + FilledIconButton( modifier = Modifier.widthIn(min = 56.dp), colors = colors, onClick = onClick, @@ -163,6 +151,15 @@ fun HorizontalFloatingToolbarItem( contentDescription = tooltipLabel, ) } + if (counter != null) { + CounterAtom( + modifier = Modifier + .align(Alignment.TopEnd) + .padding(top = 6.dp, end = 3.dp), + count = counter, + textStyle = ElementTheme.typography.fontBodyXsMedium, + ) + } } } } @@ -175,47 +172,18 @@ fun HorizontalFloatingToolbarSeparator(modifier: Modifier = Modifier) { @PreviewsDayNight @Composable internal fun HorizontalFloatingToolbarPreview() = ElementPreview { - Column { - ContentToPreview( - count = 1, - floatingActionButton = { - FloatingActionButton( - onClick = {}, - ) { - Icon( - imageVector = CompoundIcons.Plus(), - contentDescription = null, - ) - } + ContentToPreview( + floatingActionButton = { + FloatingActionButton( + onClick = {}, + ) { + Icon( + imageVector = CompoundIcons.Plus(), + contentDescription = null, + ) } - ) - ContentToPreview( - count = 99, - floatingActionButton = { - FloatingActionButton( - onClick = {}, - ) { - Icon( - imageVector = CompoundIcons.Plus(), - contentDescription = null, - ) - } - } - ) - ContentToPreview( - count = 999, - floatingActionButton = { - FloatingActionButton( - onClick = {}, - ) { - Icon( - imageVector = CompoundIcons.Plus(), - contentDescription = null, - ) - } - } - ) - } + } + ) } @PreviewsDayNight @@ -229,11 +197,10 @@ internal fun HorizontalFloatingToolbarNoFabPreview() = ElementPreview { @OptIn(ExperimentalMaterial3ExpressiveApi::class, ExperimentalMaterial3Api::class) @Composable private fun ContentToPreview( - count: Int? = 6, floatingActionButton: (@Composable () -> Unit)?, ) { HorizontalFloatingToolbar( - modifier = Modifier.padding(horizontal = 28.dp, vertical = 16.dp), + modifier = Modifier.padding(28.dp), floatingActionButton = floatingActionButton, ) { listOf( @@ -247,7 +214,7 @@ private fun ContentToPreview( icon = icon, tooltipLabel = "Label", isSelected = index == 0, - counter = if (index == 0) count else null, + counter = if (index == 0) 6 else null, forceRenderingTooltip = true, onClick = { }, ) diff --git a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/model/InviteSender.kt b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/model/InviteSender.kt index a6798a377b..dd8655af26 100644 --- a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/model/InviteSender.kt +++ b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/model/InviteSender.kt @@ -9,7 +9,6 @@ package io.element.android.libraries.matrix.ui.model import androidx.compose.runtime.Composable -import androidx.compose.runtime.Immutable import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.text.SpanStyle @@ -21,7 +20,6 @@ import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.api.room.RoomMember import io.element.android.libraries.matrix.ui.R -@Immutable data class InviteSender( val userId: UserId, val displayName: String, diff --git a/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomListContentView_Day_0_en.png b/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomListContentView_Day_0_en.png index c0c294db16..c6103fdd16 100644 --- a/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomListContentView_Day_0_en.png +++ b/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomListContentView_Day_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:58f1c38a10296a805aceb39915422defa97db88865be2f131433ba1c0115a487 -size 41012 +oid sha256:1da296f6409e018bf6e36d4bf8d0183327e2da1730008721476397a3398a2e13 +size 41382 diff --git a/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomListContentView_Day_5_en.png b/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomListContentView_Day_5_en.png index 4be85a8039..1f93f01826 100644 --- a/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomListContentView_Day_5_en.png +++ b/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomListContentView_Day_5_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f2d58c06e6ce7b699efde5ad9eb34a4bb60a2fb6885f627332067e117bbd3ea6 -size 59287 +oid sha256:7456163ece98c73691a5147e3c7b183189a545389699bb9fe33a8ce68bb125e3 +size 59685 diff --git a/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomListContentView_Night_0_en.png b/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomListContentView_Night_0_en.png index f7df2499a3..73c8da0c71 100644 --- a/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomListContentView_Night_0_en.png +++ b/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomListContentView_Night_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a6ef632e6e57853a4b5b4c0c2c783ae3324a00924adf9ff3ebbdfe54b971c19e -size 40769 +oid sha256:bbfb4004f0c9b017a86096d9a45c1793ef32d5117f6a17c59a41920264e95399 +size 41099 diff --git a/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomListContentView_Night_5_en.png b/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomListContentView_Night_5_en.png index 1fea73a64c..f293dd7cfd 100644 --- a/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomListContentView_Night_5_en.png +++ b/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomListContentView_Night_5_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3b04ac3279b631f5f7f580f0f440c1b88626e776a2f20ec6946cd6c5e97a4a48 -size 58395 +oid sha256:683568f509c83c24f506924e7a715f6ff9530d3f6ae28e041436288a510785cf +size 58742 diff --git a/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Day_10_en.png b/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Day_10_en.png index f1a300b27c..98126a3b63 100644 --- a/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Day_10_en.png +++ b/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Day_10_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3606e61a4b4b03aa2b46217ecd8dfa32c6b54c54f832c1682be00e63af6bee7e -size 14273 +oid sha256:cdc9960e9a2da12863e717ca0ea20b33e8253b858e80cf8115a4c90cc866cde7 +size 15062 diff --git a/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Day_11_en.png b/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Day_11_en.png index 63526122e0..342a26ff41 100644 --- a/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Day_11_en.png +++ b/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Day_11_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:970c808914b817fd6becac41d758e6313f9700b9440d4ef7ef81186e023c93ec -size 13852 +oid sha256:ca14149a3811b23abe145aca4697d4f662a3e7415fce50fd1d2c578430fe8a30 +size 17353 diff --git a/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Day_12_en.png b/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Day_12_en.png index 5fa92d809f..8a469007af 100644 --- a/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Day_12_en.png +++ b/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Day_12_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:86e395874c15a1a263da0d00f407f900d8dc91e6ca60f6e9421182338a74d540 -size 15062 +oid sha256:df7ba7ed437a7fc2fdf5a3e430b3947ef1b053d617781edafd9efe3847825def +size 15546 diff --git a/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Day_13_en.png b/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Day_13_en.png index c4935a9f83..52d3e7a59f 100644 --- a/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Day_13_en.png +++ b/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Day_13_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5648db8c7d1b8b62ed1044c8f315c3b7512ba09f63c54dda8c4dfc7152c95341 -size 17341 +oid sha256:5720d3452f9478e99ab2e5a5d3e3e5da6a7991bbf11a18d4b40f1a6eb279e510 +size 10289 diff --git a/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Day_14_en.png b/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Day_14_en.png index 31ffb2bbff..eac5a2c759 100644 --- a/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Day_14_en.png +++ b/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Day_14_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5e48babc8700db5a7f05d2f7bad7e1c7ad6e8feb2b37d7e14548c2e57960a737 -size 15534 +oid sha256:ce2ccd0f741b7a749c56bb307be5eb46b376466164e280d373488fb6fed30496 +size 11508 diff --git a/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Day_15_en.png b/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Day_15_en.png index 3644b2540f..043790eb8e 100644 --- a/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Day_15_en.png +++ b/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Day_15_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6414057df055980cd6ea73e917a2762b3f29dd73f5bac40fb5c455864abc5acb -size 15589 +oid sha256:f568e9fda7f57758e59e609ac402172f151fd2b7856d758675ea93a11e00109e +size 12850 diff --git a/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Day_16_en.png b/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Day_16_en.png index 2701193ab9..ed4e6f7f72 100644 --- a/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Day_16_en.png +++ b/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Day_16_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a018d57f919ee5fc27e2e76647c043f7c2f8a07b87cffaecf78efb7c755eaa7a -size 16210 +oid sha256:a915292d390b3acc3a495dae8a32fed39eb0e89cc26b73ce22f4b0dce08ccf4d +size 11006 diff --git a/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Day_17_en.png b/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Day_17_en.png index 52d3e7a59f..989a1d8875 100644 --- a/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Day_17_en.png +++ b/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Day_17_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5720d3452f9478e99ab2e5a5d3e3e5da6a7991bbf11a18d4b40f1a6eb279e510 -size 10289 +oid sha256:7e395a758d445ba4860c1811f32fcc6a670bbf161e6d31e5dcf691a46ef57442 +size 12699 diff --git a/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Day_18_en.png b/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Day_18_en.png index 320c868bf8..7c7972a951 100644 --- a/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Day_18_en.png +++ b/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Day_18_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7daaa440739b575097d4edd2a168cfe413fa8324bb4dba4594c598340f0cabe1 -size 11505 +oid sha256:8f989885d0d13fbf03bfbb61986ddea592033de482ba76d170a451a95ed2d7e4 +size 13891 diff --git a/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Day_19_en.png b/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Day_19_en.png index c297872d6d..3e150577c1 100644 --- a/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Day_19_en.png +++ b/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Day_19_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b2cd1de3bf744dd653076a195101a1adbf3b5bb633a3fbc832ddcc406fdac596 -size 12850 +oid sha256:233eaf06f35fc2cd55dd1c70f0b406065998d8ab372b590765cf6543c33285a9 +size 16337 diff --git a/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Day_20_en.png b/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Day_20_en.png index 438a6af0e4..69012a57fa 100644 --- a/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Day_20_en.png +++ b/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Day_20_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:39adc1297064622fa7b4a419a2b12586bbb4961648e34865945e53a8097e6669 -size 11001 +oid sha256:ae971022be9049bc136c1e3cc659792874b5f3aa61e9848a01b8473e295308e3 +size 14338 diff --git a/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Day_21_en.png b/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Day_21_en.png index 2ebe459661..d4c862fe89 100644 --- a/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Day_21_en.png +++ b/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Day_21_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0a34911009ea0a3457f58d2c26a1f33ebe2ba9fea86c03ab9199437104439012 -size 11331 +oid sha256:e754a2a6528f13311639b5013dd059c8e61500e9855eab898eeebff488607587 +size 14617 diff --git a/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Day_22_en.png b/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Day_22_en.png index 2ebe459661..bfad2f6cd6 100644 --- a/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Day_22_en.png +++ b/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Day_22_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0a34911009ea0a3457f58d2c26a1f33ebe2ba9fea86c03ab9199437104439012 -size 11331 +oid sha256:bbe51985786dddc65428c1b4b964ad36588f0a2c03cb88432b78f61ed0e51f72 +size 15818 diff --git a/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Day_23_en.png b/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Day_23_en.png index 989a1d8875..731b386f1b 100644 --- a/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Day_23_en.png +++ b/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Day_23_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7e395a758d445ba4860c1811f32fcc6a670bbf161e6d31e5dcf691a46ef57442 -size 12699 +oid sha256:4b79da8707db7b6903ce4bdbe9788d6b78ed3ac410bbd92c7350e13f0d55cc7b +size 18269 diff --git a/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Day_24_en.png b/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Day_24_en.png index 4ce6942b7a..4c394049bf 100644 --- a/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Day_24_en.png +++ b/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Day_24_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b224c14220ded85c0a3df8d68f7375a833579e1d7b9994c1255a145259d964fc -size 13890 +oid sha256:c81e31b63ac3549e0278159538de9107537385d17ce15d40120a50b987454e85 +size 16275 diff --git a/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Day_25_en.png b/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Day_25_en.png index 49a1a56122..3a85d80fa4 100644 --- a/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Day_25_en.png +++ b/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Day_25_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:984931bd0b2c850300f9537ae0b390726e744828598671d40aecb991fd3dbc13 -size 16332 +oid sha256:174e53280a24009753daf594e55c955c9b4192f25dd13d33bee4cfa9f32e489c +size 11057 diff --git a/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Day_26_en.png b/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Day_26_en.png index 072cc63aa0..b58323fdff 100644 --- a/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Day_26_en.png +++ b/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Day_26_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:effe16be8ec62fadf366bc2a85c1bc18fec59acade13adda688a3ad6e4a60fd0 -size 14329 +oid sha256:91d6c9fd61f26b446938dfd0a0c0d38f6f6d2f50449ae4af4190f03b3b40b613 +size 12261 diff --git a/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Day_27_en.png b/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Day_27_en.png index 4c10d32640..31a38a1ee5 100644 --- a/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Day_27_en.png +++ b/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Day_27_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ecf99cc048e5c70b92b5c8793b7ee7c9c2b92422cd2fe02fe61ede3c09d52138 -size 14390 +oid sha256:93de545bd3d959f7117cd6b37e3e77dc2933dc13a4aca25c7b2fcfc37ff9329a +size 13736 diff --git a/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Day_28_en.png b/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Day_28_en.png index 05823eb5eb..6e810d42c5 100644 --- a/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Day_28_en.png +++ b/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Day_28_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8effb8a470b82d9eb1629aab346403d6168fb33421f4bee9ed0a8f1179c67da2 -size 15005 +oid sha256:0887b5f8d083b7fc7e8698c088714aba6581f970b04e042a3bb6ae2f8e5fa9b2 +size 11746 diff --git a/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Day_29_en.png b/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Day_29_en.png index d4c862fe89..402f14d1f8 100644 --- a/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Day_29_en.png +++ b/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Day_29_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e754a2a6528f13311639b5013dd059c8e61500e9855eab898eeebff488607587 -size 14617 +oid sha256:0dec41829991e163917c3cc662a579bd71ac6be6207fdc1f1ac7eefeae3c116f +size 20689 diff --git a/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Day_30_en.png b/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Day_30_en.png index b1a83b1fb3..f409a80eac 100644 --- a/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Day_30_en.png +++ b/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Day_30_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9de543c2e12aa1346ac681d33a939a95019c4bf8afb4ac127d691ade1632b419 -size 15814 +oid sha256:44f889ba419a3ae98345fa18bd3aafa72a6c54c91a2840a6ee7fc57c281f4aed +size 15712 diff --git a/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Day_31_en.png b/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Day_31_en.png index 7f903cc541..db23cfdcf0 100644 --- a/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Day_31_en.png +++ b/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Day_31_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a80673fea3d0853ba17887863bd4c206479bb1d7a890df99ccea44c2f717c50c -size 18259 +oid sha256:60af1e883967dbcb2be2a6771cda32e6d6378f4743929a6ae71dee762fb6a4d8 +size 21825 diff --git a/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Day_32_en.png b/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Day_32_en.png index 0b88a5fa50..63d9190839 100644 --- a/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Day_32_en.png +++ b/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Day_32_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c208d1e4b56eb30e78110edb2153001328883f28a22a93ca1ef987852afc5e19 -size 16268 +oid sha256:b0a9a043b5ac3fd18f3c258c1a99a7debc3b7a53e1710c54362206f9edea2c78 +size 21873 diff --git a/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Day_33_en.png b/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Day_33_en.png index 96dd00c634..4b25a93c51 100644 --- a/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Day_33_en.png +++ b/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Day_33_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d7836f6e073d389af17d2df908750ceb5feaeee5f7ce8457da2044d2d20eb47c -size 16323 +oid sha256:dc0a9bd29caced8bd28f152485eecfaf8bfce8993bd04b8c1cf59cbaf699cd46 +size 11848 diff --git a/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Day_34_en.png b/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Day_34_en.png index 5058ec9885..04c6f7d8d3 100644 --- a/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Day_34_en.png +++ b/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Day_34_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:acad0ba3537899c700d59bfb2eb530cf373e76cb63aa91e05005aef3c6f6eb48 -size 16932 +oid sha256:bbbe8903182497155e87eff344956f2fdb348acdb78c8be6fad8f0e1718ff516 +size 17219 diff --git a/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Day_35_en.png b/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Day_35_en.png index 3a85d80fa4..af409e4b90 100644 --- a/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Day_35_en.png +++ b/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Day_35_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:174e53280a24009753daf594e55c955c9b4192f25dd13d33bee4cfa9f32e489c -size 11057 +oid sha256:f8414dc0f6de01cddc3971dad05778461c6e8220eb2c90016d3b5ef7902c6f7d +size 14846 diff --git a/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Day_36_en.png b/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Day_36_en.png index 5110e03a2a..078ce02360 100644 --- a/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Day_36_en.png +++ b/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Day_36_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:fc800c08455487eac5c9dc7a4ebb8119a8a48f33a2288f6b31ad8f345b284255 -size 12262 +oid sha256:20be0dd6e56278f168138622fdbf38d15e7161431812d0c2c4c9b3564bf81b7e +size 12991 diff --git a/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Day_37_en.png b/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Day_37_en.png index a9f31f2567..cd60ad3148 100644 --- a/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Day_37_en.png +++ b/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Day_37_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:88ae8b45c7b1dd48e2b4a8ff07e07faed1549ae38f56d78b9a250778c848f9cc -size 13737 +oid sha256:9d2dd354988f252db4877f598614ffe50b2e5f5e33c0d6c1420b07b1ecf877a8 +size 13473 diff --git a/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Day_38_en.png b/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Day_38_en.png index 10c78cb9ba..473fd3f275 100644 --- a/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Day_38_en.png +++ b/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Day_38_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2eb84e96ac418502ec0b355ae6c9eede10deb024c2007e7cf5008def5ef3bd29 -size 11748 +oid sha256:26071a6b16f4446526f6abf28274580eb0f385bd0e74f4f2b0479da5f5d4f6f2 +size 12914 diff --git a/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Day_39_en.png b/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Day_39_en.png deleted file mode 100644 index 657f9fd25d..0000000000 --- a/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Day_39_en.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:0cd3c10fea665b6542adaa722d17e05f7600000b498d580e59bed87467102c18 -size 12036 diff --git a/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Day_40_en.png b/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Day_40_en.png deleted file mode 100644 index 657f9fd25d..0000000000 --- a/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Day_40_en.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:0cd3c10fea665b6542adaa722d17e05f7600000b498d580e59bed87467102c18 -size 12036 diff --git a/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Day_41_en.png b/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Day_41_en.png deleted file mode 100644 index 1c41d22fe0..0000000000 --- a/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Day_41_en.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:4b5d23313fe6cd8f62f2413fdd9aeac7826c93624bd7c11f2bb6bd1e3345cca6 -size 20325 diff --git a/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Day_42_en.png b/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Day_42_en.png deleted file mode 100644 index f409a80eac..0000000000 --- a/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Day_42_en.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:44f889ba419a3ae98345fa18bd3aafa72a6c54c91a2840a6ee7fc57c281f4aed -size 15712 diff --git a/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Day_43_en.png b/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Day_43_en.png deleted file mode 100644 index bb91f62629..0000000000 --- a/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Day_43_en.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:de5f496dad2300441714005a69230487a62c2808e9bd44d71965d21a7939a744 -size 21459 diff --git a/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Day_44_en.png b/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Day_44_en.png deleted file mode 100644 index 46a43bb8ac..0000000000 --- a/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Day_44_en.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:e668ebebf756f6053864bdd23688722a158f58c3a234edeacfd81a9281e152ae -size 21515 diff --git a/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Day_45_en.png b/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Day_45_en.png deleted file mode 100644 index 4b25a93c51..0000000000 --- a/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Day_45_en.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:dc0a9bd29caced8bd28f152485eecfaf8bfce8993bd04b8c1cf59cbaf699cd46 -size 11848 diff --git a/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Day_46_en.png b/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Day_46_en.png deleted file mode 100644 index 04c6f7d8d3..0000000000 --- a/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Day_46_en.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:bbbe8903182497155e87eff344956f2fdb348acdb78c8be6fad8f0e1718ff516 -size 17219 diff --git a/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Day_47_en.png b/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Day_47_en.png deleted file mode 100644 index af409e4b90..0000000000 --- a/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Day_47_en.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:f8414dc0f6de01cddc3971dad05778461c6e8220eb2c90016d3b5ef7902c6f7d -size 14846 diff --git a/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Day_48_en.png b/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Day_48_en.png deleted file mode 100644 index 078ce02360..0000000000 --- a/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Day_48_en.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:20be0dd6e56278f168138622fdbf38d15e7161431812d0c2c4c9b3564bf81b7e -size 12991 diff --git a/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Day_49_en.png b/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Day_49_en.png deleted file mode 100644 index cd60ad3148..0000000000 --- a/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Day_49_en.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:9d2dd354988f252db4877f598614ffe50b2e5f5e33c0d6c1420b07b1ecf877a8 -size 13473 diff --git a/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Day_4_en.png b/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Day_4_en.png index 64cb4cf640..d25a021051 100644 --- a/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Day_4_en.png +++ b/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Day_4_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d0c5e7e9fc0aa24c6d17b6273d4f37175c1140ceaab70ff29b0a692175bfaa7f -size 20924 +oid sha256:dd10586365453b4353b430623a96e61caa8f1648a00b5f17bdc213a9604892c4 +size 20921 diff --git a/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Day_50_en.png b/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Day_50_en.png deleted file mode 100644 index 473fd3f275..0000000000 --- a/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Day_50_en.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:26071a6b16f4446526f6abf28274580eb0f385bd0e74f4f2b0479da5f5d4f6f2 -size 12914 diff --git a/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Day_6_en.png b/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Day_6_en.png index c317fecfac..cd0a15bcd9 100644 --- a/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Day_6_en.png +++ b/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Day_6_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:08c960c0596528eba8fb83ba7f71fa53c95b90d0f00f6068f85c826d0e24382c +oid sha256:ee56db9dbd7090e11245a0653bd91c6cf10f9bf4c5c07525f32560c2e3af17d8 size 13132 diff --git a/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Day_7_en.png b/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Day_7_en.png index 1bb7a7b5b1..a657a31e50 100644 --- a/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Day_7_en.png +++ b/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Day_7_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6c6ade4b765289b471fc2689ebb7da8ab738cd8fa3168e36cd08342ee425c8ac -size 15411 +oid sha256:d084ee537b20c9139a8213b3fa04384718da12be2759b2b6f19dd8016748a7f8 +size 15419 diff --git a/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Day_8_en.png b/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Day_8_en.png index d538a631a4..bb3f227793 100644 --- a/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Day_8_en.png +++ b/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Day_8_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:61e3e8eeacadaa281328a538499f89c89946bd3e11ff5f576cbaf568f2f723a3 -size 13589 +oid sha256:0aea61f4d5e013c83c244e10dad718cc12ffd15ed5ada336a9ea709cff5840e4 +size 13599 diff --git a/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Day_9_en.png b/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Day_9_en.png index 988f36e62e..63526122e0 100644 --- a/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Day_9_en.png +++ b/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Day_9_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0a3c3c152aeff7984717e0dd542604e119e592d198e24c9903f6a1cb4ce1f964 -size 13646 +oid sha256:970c808914b817fd6becac41d758e6313f9700b9440d4ef7ef81186e023c93ec +size 13852 diff --git a/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Night_10_en.png b/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Night_10_en.png index f788809889..8acab35fb0 100644 --- a/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Night_10_en.png +++ b/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Night_10_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3667009d409e5a8dd57e3d52c483f454f76a04ee2b4fe60def2a8e3f14249516 -size 13918 +oid sha256:f8c89084badb91481d83e94caceca7049bd724f8ba33f2890d63bb1a8401d77b +size 14840 diff --git a/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Night_11_en.png b/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Night_11_en.png index 3cda9fe8ae..d33fbddd58 100644 --- a/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Night_11_en.png +++ b/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Night_11_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:91c6ae370458d59210b64d222c607099346b5a6b1eb9d0793e5e775dc34b9916 -size 13747 +oid sha256:0235e9f8b3e5eab5d9e382b11130a62ebe554cb8991f1d79a4c48947c21d639b +size 16814 diff --git a/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Night_12_en.png b/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Night_12_en.png index 4ddb3472de..957208b555 100644 --- a/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Night_12_en.png +++ b/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Night_12_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:89cd08a79e9b979b68e74b763e0deb766d374a1e04c72650e52cb05b32a3a93a -size 14832 +oid sha256:dec0d0da741ad4153af86ba1a8289f421a9392f4ccb6a134ee6b1dcb6d6be3db +size 15122 diff --git a/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Night_13_en.png b/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Night_13_en.png index f3f269c765..7dc7775d95 100644 --- a/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Night_13_en.png +++ b/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Night_13_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0d83c718c35954b5eaced71e428e03ddbb86e0ed6297f71221705b18740e4ab8 -size 16815 +oid sha256:26239d9021572c9ac184675b5c6d8683749d57ef44f33078938550f9d7072c09 +size 10325 diff --git a/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Night_14_en.png b/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Night_14_en.png index e2f1446fea..712d4d453c 100644 --- a/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Night_14_en.png +++ b/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Night_14_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:495051c67d72104f2624ea73f12f9a86571e2e1acd61f1a49a01b253aa9d33b1 -size 15124 +oid sha256:6d5a40aee82efbe857da7a121f0334168a230762c25b4f9ba62cb84a1b20dd90 +size 11398 diff --git a/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Night_15_en.png b/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Night_15_en.png index cd0475825f..0d2f59b472 100644 --- a/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Night_15_en.png +++ b/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Night_15_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0d62fb18995b549a238e77cc70d46e22c8edf32e4d658673e259647e1bef997c -size 15184 +oid sha256:03d0ded21f64013bedb73be7570087255ad4ce7dc256691947c0a4131afcfe4a +size 12688 diff --git a/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Night_16_en.png b/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Night_16_en.png index a03d1da7fb..4dbc05878f 100644 --- a/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Night_16_en.png +++ b/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Night_16_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0d3da68927fc86e5d71ebd0adb2f3cf49eaf94f5418019a72339208f4495bff1 -size 15720 +oid sha256:6c3b74815169cec4c5d1550f96b0a685b03506525ff4587095a3c9b7893cfcbf +size 10988 diff --git a/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Night_17_en.png b/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Night_17_en.png index 7dc7775d95..135e645ddd 100644 --- a/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Night_17_en.png +++ b/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Night_17_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:26239d9021572c9ac184675b5c6d8683749d57ef44f33078938550f9d7072c09 -size 10325 +oid sha256:88d9e55e6cab4697780fde38ac51a7f20cc3d3c173a4af8761e260a02253cbaf +size 12650 diff --git a/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Night_18_en.png b/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Night_18_en.png index fec3afc632..df67864a44 100644 --- a/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Night_18_en.png +++ b/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Night_18_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:85dd620057e64147e1ac409e9a3023efc795e06426cfd5deb94b14f08f528980 -size 11397 +oid sha256:3356143e9b6a4e142c97e673f9e530fc4944e96fd2b3e1dc33a391aa95f2abe1 +size 13773 diff --git a/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Night_19_en.png b/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Night_19_en.png index 19c583e781..0db6cd5f06 100644 --- a/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Night_19_en.png +++ b/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Night_19_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:661c824676939245b6666f1af006f61c855e9baab13cdfe942b72eac5238a962 -size 12682 +oid sha256:e5ff5b619aea5f071c4ed2b23a005fbc14c65cc5042da9d93a8b56af94c4ac4f +size 15903 diff --git a/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Night_20_en.png b/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Night_20_en.png index 3457379745..62cf2ce554 100644 --- a/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Night_20_en.png +++ b/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Night_20_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:94ddbe773a40bd985b327f8b01cb33ea172c865dad303015630220cf09f77880 -size 10981 +oid sha256:6f630c17f3397054a2796ba80327b341edec5442f1d51f73c57104a328aa26a6 +size 14034 diff --git a/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Night_21_en.png b/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Night_21_en.png index 1a1a9ffb7f..23e9185b0c 100644 --- a/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Night_21_en.png +++ b/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Night_21_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c3ff569950f899d8bcc9229211cbbafd237404db3cf160d4e0f8cd555c97a740 -size 11293 +oid sha256:9e6cc774f4b4f35cc47319b983118b76ab75361f98112fe1fe18c87ee7e1e6d4 +size 14460 diff --git a/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Night_22_en.png b/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Night_22_en.png index 1a1a9ffb7f..67340637a9 100644 --- a/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Night_22_en.png +++ b/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Night_22_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c3ff569950f899d8bcc9229211cbbafd237404db3cf160d4e0f8cd555c97a740 -size 11293 +oid sha256:7d20e5ee28d2e00686c5490402b878748a07f02eb0ec62a3f1de0b4c205eb7b1 +size 15571 diff --git a/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Night_23_en.png b/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Night_23_en.png index 135e645ddd..d3875536c7 100644 --- a/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Night_23_en.png +++ b/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Night_23_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:88d9e55e6cab4697780fde38ac51a7f20cc3d3c173a4af8761e260a02253cbaf -size 12650 +oid sha256:7269157bdee33761c05e94497a43f194387dfd0c4c041201a700b51dec90b0df +size 17698 diff --git a/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Night_24_en.png b/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Night_24_en.png index d3c12d4fa3..85515dab4d 100644 --- a/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Night_24_en.png +++ b/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Night_24_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1bcde2f97cd976d138caf3fb265a57e4fcdf0f27d1bcd9fed196bdcaded31a2c -size 13776 +oid sha256:8034899aa95ccd789a4315809719bfcb67914ea931a18d0ea788c4858fb16e97 +size 15837 diff --git a/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Night_25_en.png b/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Night_25_en.png index 4862702754..d00ac32e3c 100644 --- a/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Night_25_en.png +++ b/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Night_25_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:718458db1fb9ce0895a626abba0f274e2aee8426a99483b3e8eb6dbc7445aa7a -size 15904 +oid sha256:3aff10fe78c73358c672341ef183830950162cdacb9838adb3c7b0f45ede34b5 +size 11047 diff --git a/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Night_26_en.png b/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Night_26_en.png index 60b98dea18..9b29057a46 100644 --- a/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Night_26_en.png +++ b/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Night_26_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2b577fff3aed9822067f5067c7b222cc7ecbe6f5bcf3b0e17c87aa253b985bec -size 14030 +oid sha256:708a3135086a08ef5c7b20bbaa95f18d687abdfe5a80748f06c8691625d9930f +size 12133 diff --git a/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Night_27_en.png b/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Night_27_en.png index 0281dcdd9c..7db77c3fff 100644 --- a/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Night_27_en.png +++ b/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Night_27_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:14460f19a44b5674ee55c4f9f5abcab76e5c816f321f93bb7e5cfe946706d2ec -size 14086 +oid sha256:31b308470d3fccf45d59fc8e4bef05286470b98916e458e16966fd46182e88b2 +size 13554 diff --git a/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Night_28_en.png b/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Night_28_en.png index ff4ab49f89..e78b47c6ce 100644 --- a/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Night_28_en.png +++ b/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Night_28_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4a7ccaf461ec73526a1fc1ac0e7e6a1fd7067c49cafc8b465a086a93b951c68f -size 14613 +oid sha256:352ce5517d371a6ccacceac4c93698b91eb7f94ba4bd46a1f3bb0f8cdc698ca0 +size 11703 diff --git a/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Night_29_en.png b/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Night_29_en.png index 23e9185b0c..5abab16cd3 100644 --- a/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Night_29_en.png +++ b/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Night_29_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9e6cc774f4b4f35cc47319b983118b76ab75361f98112fe1fe18c87ee7e1e6d4 -size 14460 +oid sha256:599bfa351c894812582239be7d5c945b5eb7e4c432bc59e276a9f133fac6e530 +size 20234 diff --git a/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Night_30_en.png b/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Night_30_en.png index 8ae8ddd318..c4f8ad25cf 100644 --- a/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Night_30_en.png +++ b/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Night_30_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6adc4890b406cee4c678e5eb62603be7f038f8a8efd8eea8f11413715cea7da6 -size 15565 +oid sha256:8632f1a875c675158c2ac617e319020230f2f796cf5df20a480f2f1027d90e19 +size 15341 diff --git a/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Night_31_en.png b/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Night_31_en.png index 9e8aa36f5b..e6dd276416 100644 --- a/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Night_31_en.png +++ b/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Night_31_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a8c43e6c0bc920d74cae04d1911c82bcf9a8793e0976cd8659cd4e9f06374c8b -size 17698 +oid sha256:829f697cfa0f409bc836e07d25dc713e54091e4a3861ac3741e8ec2f8e2ff311 +size 21121 diff --git a/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Night_32_en.png b/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Night_32_en.png index c4a2ccaebf..4ca6fa81c7 100644 --- a/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Night_32_en.png +++ b/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Night_32_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:30b3911802698b203241dbfef35fd107d6e781713c5cd413090247cf27e9b97e -size 15837 +oid sha256:29e0a6c7b5e5fbe6c240fca3f822e8ebf15ad10c4de02eb5fc78dc2f3eb2e3d5 +size 20743 diff --git a/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Night_33_en.png b/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Night_33_en.png index 63714660a2..025d903212 100644 --- a/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Night_33_en.png +++ b/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Night_33_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:82193b43a6d29c19f5b5928dc44dcc9016922ad98630c1707edd3d11f7ea9935 -size 15872 +oid sha256:35ec154bb4a105ad33f78898c6c72d7b8be670d7188a5e7498f743fcd2273452 +size 11856 diff --git a/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Night_34_en.png b/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Night_34_en.png index 35916ecf4d..2acaa85e7b 100644 --- a/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Night_34_en.png +++ b/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Night_34_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f00c788f206afc0757723e15d2cfa454943329801bc0eb6e3e682a8da230b413 -size 16410 +oid sha256:90e186637324c021e2815a3750c8e84f2d0a53cefb19256154f485e8ebbdf45c +size 17124 diff --git a/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Night_35_en.png b/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Night_35_en.png index d00ac32e3c..bf4090e112 100644 --- a/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Night_35_en.png +++ b/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Night_35_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3aff10fe78c73358c672341ef183830950162cdacb9838adb3c7b0f45ede34b5 -size 11047 +oid sha256:94783aac31bd2993c26ce86ecc17e5d4ec37ebe8364b3f37fd96b1c571c4d2a6 +size 14052 diff --git a/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Night_36_en.png b/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Night_36_en.png index f278e0a0b6..9d7ee6696a 100644 --- a/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Night_36_en.png +++ b/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Night_36_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:986cd737a9fd4e9e7f41541c5df3f4800738a5febe6d5d0603c634fa3580ac20 -size 12128 +oid sha256:11e826f01b6b6b5b2fca6a33abb8dbdebd2658baab5dcf5d6b9b7439ee0c1f9c +size 12920 diff --git a/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Night_37_en.png b/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Night_37_en.png index f3232c3fe8..210e8d26cb 100644 --- a/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Night_37_en.png +++ b/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Night_37_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a8d7be0a60c676901589ea43e74c03ef221499989b07c55c472c03c75aef2096 -size 13544 +oid sha256:eb77faa7d7fef837af991a8de23684b9ed53defa1035513b8973db36a843ad58 +size 13387 diff --git a/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Night_38_en.png b/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Night_38_en.png index 762116566f..14c08f0b62 100644 --- a/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Night_38_en.png +++ b/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Night_38_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:62659c02ed16edada557c0c13693bbeb5eda5b42a5819f37535195f229a8b4a8 -size 11701 +oid sha256:2ddef7c50d6a791c6a32bae8506a999b198bc601b70b6ffb5e5e7a8d5a2f1543 +size 12869 diff --git a/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Night_39_en.png b/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Night_39_en.png deleted file mode 100644 index c0f1f1d1fd..0000000000 --- a/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Night_39_en.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:3df8aa7b84c2676a84f473d09cd29667403afbb1adf31722c67d816fcf2baa16 -size 11968 diff --git a/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Night_40_en.png b/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Night_40_en.png deleted file mode 100644 index c0f1f1d1fd..0000000000 --- a/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Night_40_en.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:3df8aa7b84c2676a84f473d09cd29667403afbb1adf31722c67d816fcf2baa16 -size 11968 diff --git a/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Night_41_en.png b/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Night_41_en.png deleted file mode 100644 index d2dd4936ba..0000000000 --- a/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Night_41_en.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:0265b4de17d78805f0ae9b3f4075aee56e4d53483b04f28fc3f66c44d31650c7 -size 19897 diff --git a/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Night_42_en.png b/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Night_42_en.png deleted file mode 100644 index c4f8ad25cf..0000000000 --- a/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Night_42_en.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:8632f1a875c675158c2ac617e319020230f2f796cf5df20a480f2f1027d90e19 -size 15341 diff --git a/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Night_43_en.png b/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Night_43_en.png deleted file mode 100644 index 7e86d1de71..0000000000 --- a/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Night_43_en.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:563c72eb9611714afaf8b126b7735691d34c62a01b89b1045a3b59f996d4be1e -size 20787 diff --git a/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Night_44_en.png b/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Night_44_en.png deleted file mode 100644 index 734b5edf9e..0000000000 --- a/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Night_44_en.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:9b7a3de3ac091a2a07607074d610ad9bd37a052701d6f64b81e875fb4818ff19 -size 20414 diff --git a/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Night_45_en.png b/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Night_45_en.png deleted file mode 100644 index 025d903212..0000000000 --- a/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Night_45_en.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:35ec154bb4a105ad33f78898c6c72d7b8be670d7188a5e7498f743fcd2273452 -size 11856 diff --git a/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Night_46_en.png b/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Night_46_en.png deleted file mode 100644 index 2acaa85e7b..0000000000 --- a/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Night_46_en.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:90e186637324c021e2815a3750c8e84f2d0a53cefb19256154f485e8ebbdf45c -size 17124 diff --git a/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Night_47_en.png b/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Night_47_en.png deleted file mode 100644 index bf4090e112..0000000000 --- a/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Night_47_en.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:94783aac31bd2993c26ce86ecc17e5d4ec37ebe8364b3f37fd96b1c571c4d2a6 -size 14052 diff --git a/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Night_48_en.png b/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Night_48_en.png deleted file mode 100644 index 9d7ee6696a..0000000000 --- a/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Night_48_en.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:11e826f01b6b6b5b2fca6a33abb8dbdebd2658baab5dcf5d6b9b7439ee0c1f9c -size 12920 diff --git a/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Night_49_en.png b/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Night_49_en.png deleted file mode 100644 index 210e8d26cb..0000000000 --- a/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Night_49_en.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:eb77faa7d7fef837af991a8de23684b9ed53defa1035513b8973db36a843ad58 -size 13387 diff --git a/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Night_4_en.png b/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Night_4_en.png index 9bf8e46207..d173392035 100644 --- a/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Night_4_en.png +++ b/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Night_4_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:375fcdb12c76213423fe50656b30127659d6dd4c80d5aa842d198c38e3ad8486 -size 20521 +oid sha256:714864a3455e6797f4eb9be66dcd99bfb931fea2c8c3a7610e2962b181309686 +size 20533 diff --git a/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Night_50_en.png b/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Night_50_en.png deleted file mode 100644 index 14c08f0b62..0000000000 --- a/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Night_50_en.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:2ddef7c50d6a791c6a32bae8506a999b198bc601b70b6ffb5e5e7a8d5a2f1543 -size 12869 diff --git a/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Night_6_en.png b/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Night_6_en.png index 0d101f0a13..0d46f8056c 100644 --- a/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Night_6_en.png +++ b/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Night_6_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:61ffd011086b5c45cd4928e9737264c94897de25868e6f5be236af93c2d3d7ad -size 13035 +oid sha256:5ccb4c62dc282307727575827e22f69d083e7f3f9e201320f4dc7e95b73a3c3a +size 13040 diff --git a/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Night_7_en.png b/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Night_7_en.png index 1639b9f704..bc498107f9 100644 --- a/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Night_7_en.png +++ b/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Night_7_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f0f9ee8023fe05706663f1f336acd07c711ed32abb83b7bbf8a7ccbe1330b98a -size 15031 +oid sha256:744c0f0b121c0df805ab5c5d3a6de6a6ce866d57cd78fcd1a00b8e53bc4b2385 +size 15026 diff --git a/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Night_8_en.png b/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Night_8_en.png index 1ce328cb25..0b508e0b86 100644 --- a/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Night_8_en.png +++ b/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Night_8_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c664437d85aa8678ca10273e599e38ae16e52e4ccfcc4fb7285eb5cbb8344d5a +oid sha256:4fce9852b7d9fb17ef003d1ed31b29d9c9dc6c0dc20dc8084613c4979cc725dc size 13313 diff --git a/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Night_9_en.png b/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Night_9_en.png index b139c2dece..3cda9fe8ae 100644 --- a/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Night_9_en.png +++ b/tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Night_9_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2b8868624e1a0c91e1f3c1677c14567f72a50374d8d6a3923b2d513c07cc01c8 -size 13378 +oid sha256:91c6ae370458d59210b64d222c607099346b5a6b1eb9d0793e5e775dc34b9916 +size 13747 diff --git a/tests/uitests/src/test/snapshots/images/features.home.impl.search_RoomListSearchContent_Day_1_en.png b/tests/uitests/src/test/snapshots/images/features.home.impl.search_RoomListSearchContent_Day_1_en.png index a270674e69..fd17d9f320 100644 --- a/tests/uitests/src/test/snapshots/images/features.home.impl.search_RoomListSearchContent_Day_1_en.png +++ b/tests/uitests/src/test/snapshots/images/features.home.impl.search_RoomListSearchContent_Day_1_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:447fe29130c68e1506f9a49e26229f419306a89c003040d099681ba8cc27e89e -size 43475 +oid sha256:471ac85d6c18b5186058067b4ce23b1c6cdc44c31e0f35907c65f8580b7c57ed +size 43808 diff --git a/tests/uitests/src/test/snapshots/images/features.home.impl.search_RoomListSearchContent_Night_1_en.png b/tests/uitests/src/test/snapshots/images/features.home.impl.search_RoomListSearchContent_Night_1_en.png index 8fa3637330..69443c148a 100644 --- a/tests/uitests/src/test/snapshots/images/features.home.impl.search_RoomListSearchContent_Night_1_en.png +++ b/tests/uitests/src/test/snapshots/images/features.home.impl.search_RoomListSearchContent_Night_1_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:35fe66a9ef2bf4657abbe75625abc36c4d1473db39dd487046338281edef9ae0 -size 43068 +oid sha256:2f2971c76d9dc2a32b94f90d513eff37f3cb85272047be5358bc3e2c8092025a +size 43413 diff --git a/tests/uitests/src/test/snapshots/images/features.home.impl.spaces_HomeSpacesView_Day_0_en.png b/tests/uitests/src/test/snapshots/images/features.home.impl.spaces_HomeSpacesView_Day_0_en.png index d5f3ddc7bf..fb9ed3f101 100644 --- a/tests/uitests/src/test/snapshots/images/features.home.impl.spaces_HomeSpacesView_Day_0_en.png +++ b/tests/uitests/src/test/snapshots/images/features.home.impl.spaces_HomeSpacesView_Day_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6c34e6ad761112ab3ad36de81eaa23e84667f9259ce2718c9db1b22f60f866b6 -size 84244 +oid sha256:43b2800a5fb8014557801f35210a98752529c3c71ec285e95be1d405d2910327 +size 84602 diff --git a/tests/uitests/src/test/snapshots/images/features.home.impl.spaces_HomeSpacesView_Night_0_en.png b/tests/uitests/src/test/snapshots/images/features.home.impl.spaces_HomeSpacesView_Night_0_en.png index 0e68328732..f1a441a0b3 100644 --- a/tests/uitests/src/test/snapshots/images/features.home.impl.spaces_HomeSpacesView_Night_0_en.png +++ b/tests/uitests/src/test/snapshots/images/features.home.impl.spaces_HomeSpacesView_Night_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:23fbf0567b03332593a7cede27b247d3da8ba9ff16d066f5372e0f597f264026 -size 82587 +oid sha256:330103af450379bc1ea3bd9815779324384d6025a68ba8e3bb9a76b2565fa2dc +size 82909 diff --git a/tests/uitests/src/test/snapshots/images/features.home.impl_HomeViewA11y_en.png b/tests/uitests/src/test/snapshots/images/features.home.impl_HomeViewA11y_en.png index cfde8f7313..560b337d7d 100644 --- a/tests/uitests/src/test/snapshots/images/features.home.impl_HomeViewA11y_en.png +++ b/tests/uitests/src/test/snapshots/images/features.home.impl_HomeViewA11y_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4743a05888b1109bb4a87bb3f26e526d1d0fa41528d4a9776dbb715761481bd3 -size 143568 +oid sha256:a77b3fd89bdf26f15b44d070ee60af56a885e5c8d087780630e1dc5303de077c +size 143941 diff --git a/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Day_0_en.png b/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Day_0_en.png index 6c280379d3..5b1808def1 100644 --- a/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Day_0_en.png +++ b/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Day_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6522e7b2a874145b2bc3e105e1d75115324110b42eca8f19b4f5c8fffdbdf9f3 -size 67986 +oid sha256:905febd17389965c962b51c0225a3019d0b5b49ed1189e15e8be631989effb85 +size 68367 diff --git a/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Day_10_en.png b/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Day_10_en.png index 702f675b64..e2b3622d86 100644 --- a/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Day_10_en.png +++ b/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Day_10_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:21371e86345894c9c940cacbecaa00ad7bfba3385ecc2fac4d5950e94e30c04a -size 36843 +oid sha256:3c3e85ad3d267db2fa0964b4d28ef9fd50ce4989259646d5d07bdea7e24c7a4c +size 36872 diff --git a/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Day_11_en.png b/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Day_11_en.png index e18220ad3c..48b80d3c0c 100644 --- a/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Day_11_en.png +++ b/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Day_11_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a0138600e742cd3981037948974787e3e137def232d12dd47885d228d089abc9 -size 32626 +oid sha256:3c3580427877cca8f59538bccd47ee19f57b4bdce28d60135da7de185e17f8f1 +size 32636 diff --git a/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Day_13_en.png b/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Day_13_en.png index a88fa07eb1..a6942d9c35 100644 --- a/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Day_13_en.png +++ b/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Day_13_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e14b4474acb3e3d663b6df1beffc67b053b75fd2b34bfb09e6bbf634f2ed15ab -size 94965 +oid sha256:7d07d37556793e63eb09f0ea7e71a2a8bca73098d7372dea35b1ad916a8ebebe +size 95328 diff --git a/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Day_14_en.png b/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Day_14_en.png index 5e751f99d3..4cf6e8dd31 100644 --- a/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Day_14_en.png +++ b/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Day_14_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e479935da0c1e403f80451ff5ed7403c341f6b5d840c5bf3861de9ade9251777 -size 87548 +oid sha256:545e5dc4c7602e6535109e34816922a76d58c0ae19e7d86627b9515c1c4645c3 +size 87943 diff --git a/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Day_15_en.png b/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Day_15_en.png index 1e36871727..0f310c78a8 100644 --- a/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Day_15_en.png +++ b/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Day_15_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:179cdc42442d764de150ee7738e78e580c942b379cdd5079903d33d5f5318b12 -size 55863 +oid sha256:0f2280adb5d99a20fbe98b8d097e97c2de0c05a083382a905075bfb5795a2290 +size 55890 diff --git a/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Day_16_en.png b/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Day_16_en.png index 1cfbe24537..17ff86bf54 100644 --- a/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Day_16_en.png +++ b/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Day_16_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2249d4b82b98d0770410d066d3b918b6faecdcf15453f7cbd69d093c16a1cc61 -size 37408 +oid sha256:2e2d1b5923dd2941bd7e60a9019a7770ed3383529ad9a8f3572c9e3d450310e0 +size 37461 diff --git a/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Day_1_en.png b/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Day_1_en.png index 6c280379d3..5b1808def1 100644 --- a/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Day_1_en.png +++ b/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Day_1_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6522e7b2a874145b2bc3e105e1d75115324110b42eca8f19b4f5c8fffdbdf9f3 -size 67986 +oid sha256:905febd17389965c962b51c0225a3019d0b5b49ed1189e15e8be631989effb85 +size 68367 diff --git a/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Day_2_en.png b/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Day_2_en.png index 6c280379d3..5b1808def1 100644 --- a/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Day_2_en.png +++ b/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Day_2_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6522e7b2a874145b2bc3e105e1d75115324110b42eca8f19b4f5c8fffdbdf9f3 -size 67986 +oid sha256:905febd17389965c962b51c0225a3019d0b5b49ed1189e15e8be631989effb85 +size 68367 diff --git a/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Day_3_en.png b/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Day_3_en.png index 2e85657a95..cfd1827f50 100644 --- a/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Day_3_en.png +++ b/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Day_3_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0672d02683ac8fa43f85d089f944993c546e913c6b4b8057bf414a248110ad85 -size 65297 +oid sha256:6618a3f641c4919fd009f8bab432cd0d03ee933accf5b33815202eed721c78bf +size 65320 diff --git a/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Day_4_en.png b/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Day_4_en.png index ef2ab7b8d1..9eb0a6a43d 100644 --- a/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Day_4_en.png +++ b/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Day_4_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:756ca818c4f0edc848dd963526ac6d533bd3fa3062c95555c20b0af115ea55f4 -size 54283 +oid sha256:0227ce3fa0664ff4a99649532683837835733d1bb26dcd05b7f6277964b5115c +size 54342 diff --git a/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Day_5_en.png b/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Day_5_en.png index 6c280379d3..5b1808def1 100644 --- a/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Day_5_en.png +++ b/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Day_5_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6522e7b2a874145b2bc3e105e1d75115324110b42eca8f19b4f5c8fffdbdf9f3 -size 67986 +oid sha256:905febd17389965c962b51c0225a3019d0b5b49ed1189e15e8be631989effb85 +size 68367 diff --git a/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Day_6_en.png b/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Day_6_en.png index 12c5485223..516089368e 100644 --- a/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Day_6_en.png +++ b/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Day_6_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:29617f74f80d296d24b8bcd66d8837f3b9b5c4a278c8baca5d7af9c204953f34 -size 54990 +oid sha256:fd658e684c89fc1b201a4a4090037d83c8882774b2a2593b12b30bd011971ecf +size 55324 diff --git a/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Day_7_en.png b/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Day_7_en.png index 23bfc078ec..f4bb27c8b4 100644 --- a/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Day_7_en.png +++ b/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Day_7_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3afe56e1e5b582c8acd4f7b33bc725c7930cc3d4f3863cc0ee386cc7e5677968 -size 54795 +oid sha256:4c5798c49f07cd4a564c6b48c18dc0a67b5437e403da63e154c6bdbcee9fb060 +size 55128 diff --git a/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Day_8_en.png b/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Day_8_en.png index aae78d67af..b43498911a 100644 --- a/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Day_8_en.png +++ b/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Day_8_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:366cec0595cb8a30d677287598de8326850e4bde28ffc8b398bdbdc83fe6b1a5 -size 53028 +oid sha256:4c8042cfa5d850e0322da8a6cb1fb019da4d3675c469d29aec84fe9d6a8aa147 +size 53368 diff --git a/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Day_9_en.png b/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Day_9_en.png index 10f74710ba..c25fce6fdf 100644 --- a/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Day_9_en.png +++ b/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Day_9_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c517e3c1595945e27e5b76f7fac96cf744bb1bcf263adef50f8f1ae8e2da4eae -size 87417 +oid sha256:4441594ce0a35e4df5cc0a1d7dff028fd6842e229e0ec10a1dca05a84749010c +size 87798 diff --git a/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Night_0_en.png b/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Night_0_en.png index 71cc7f0311..af72b7ef67 100644 --- a/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Night_0_en.png +++ b/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Night_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2ac8b408f8c524cf07eb5572b1014d8759a6424d7f457f4e566afa7d412f2c49 -size 62640 +oid sha256:4e9677b8e987ca3d709a60fe92ec0125ef559ea765daeff4f58ea335796731aa +size 62919 diff --git a/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Night_10_en.png b/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Night_10_en.png index 3682ec4637..fc6b74a2f6 100644 --- a/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Night_10_en.png +++ b/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Night_10_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:05d3024cbc8a4a32618c5fc1d359f1152a03d3e304803a86d6780b5529159b3d -size 31647 +oid sha256:588859e742ca76818510abb9326969da5c227d0946ea6aac9de216e1a62a7d6a +size 31657 diff --git a/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Night_11_en.png b/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Night_11_en.png index 3c7aaacfcc..6f2a17627d 100644 --- a/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Night_11_en.png +++ b/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Night_11_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e53c44f711122bdacc3d8044c8061fad3e6dbb3a75f37b7812e4f50ccc05095f -size 27250 +oid sha256:827036f5c685629037b5bb7ae3265b3261a52ab3d8b912344cd06137839512be +size 27266 diff --git a/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Night_13_en.png b/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Night_13_en.png index 18fdca6b14..2c40439113 100644 --- a/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Night_13_en.png +++ b/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Night_13_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:eebe003dbf4c8ee0bedfdd2428dbfbfb75756da2d5dc42fafeacb03a63b9be77 -size 88607 +oid sha256:d760c522a5e69fb609740180b4c90a8046f0a516fc2271b14ac6b337a0bba3b4 +size 88924 diff --git a/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Night_14_en.png b/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Night_14_en.png index 2e1fa6593f..7eaf2627d0 100644 --- a/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Night_14_en.png +++ b/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Night_14_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:fda62f52097c6972b746babeb8bbfa7152fe11865c664f76c6bef3b1fa6a2d76 -size 81273 +oid sha256:c19fc8a6cfb56d95433cc6e5cc193f11c6b6b3c57d6b2f49b0e21f226ef8a2fe +size 81593 diff --git a/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Night_15_en.png b/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Night_15_en.png index 24b402b7ec..2e8b7314f6 100644 --- a/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Night_15_en.png +++ b/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Night_15_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e7015bbe32c70d7a94de85a80495e16aa807b49074e0ae75331c051296997fb0 -size 50432 +oid sha256:422c10006716ea99ae5d3222334f4fe74a002e42fa2ab8b7890dff31a60ca8f7 +size 50444 diff --git a/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Night_16_en.png b/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Night_16_en.png index 8b2e84c525..a26f43f289 100644 --- a/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Night_16_en.png +++ b/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Night_16_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d48383d688df552d456c32fd3ae2c0abc883957740b46125c509a7e027b4125f -size 32514 +oid sha256:858ed6993ba8d322e9acc40ca3e759372e19cd3aae9854709f374c0c8d1c4429 +size 32549 diff --git a/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Night_1_en.png b/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Night_1_en.png index 71cc7f0311..af72b7ef67 100644 --- a/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Night_1_en.png +++ b/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Night_1_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2ac8b408f8c524cf07eb5572b1014d8759a6424d7f457f4e566afa7d412f2c49 -size 62640 +oid sha256:4e9677b8e987ca3d709a60fe92ec0125ef559ea765daeff4f58ea335796731aa +size 62919 diff --git a/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Night_2_en.png b/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Night_2_en.png index 71cc7f0311..af72b7ef67 100644 --- a/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Night_2_en.png +++ b/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Night_2_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2ac8b408f8c524cf07eb5572b1014d8759a6424d7f457f4e566afa7d412f2c49 -size 62640 +oid sha256:4e9677b8e987ca3d709a60fe92ec0125ef559ea765daeff4f58ea335796731aa +size 62919 diff --git a/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Night_3_en.png b/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Night_3_en.png index 3b920ab162..a1d6128daa 100644 --- a/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Night_3_en.png +++ b/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Night_3_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4076933fb4724fc51000b75b1fcbef45cab3fac1cbd54b8a10324365a4363abd -size 60520 +oid sha256:89e25edf690c8c416699e82781a4dd01f4ab370e8ca868b7bd4ce1a0e5a1124e +size 60532 diff --git a/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Night_4_en.png b/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Night_4_en.png index 5011db41f4..95f3da33c4 100644 --- a/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Night_4_en.png +++ b/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Night_4_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f827f6313bc5958daebc719797bdd8b50796b0e766ac931cf90f4ad31c979a08 -size 49192 +oid sha256:a04f8865034c36f32f0f7dd098fd6bf3e80f96869036f965c755a7a48286a071 +size 49232 diff --git a/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Night_5_en.png b/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Night_5_en.png index 71cc7f0311..af72b7ef67 100644 --- a/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Night_5_en.png +++ b/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Night_5_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2ac8b408f8c524cf07eb5572b1014d8759a6424d7f457f4e566afa7d412f2c49 -size 62640 +oid sha256:4e9677b8e987ca3d709a60fe92ec0125ef559ea765daeff4f58ea335796731aa +size 62919 diff --git a/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Night_6_en.png b/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Night_6_en.png index 5f85c83794..14dc407833 100644 --- a/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Night_6_en.png +++ b/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Night_6_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f66d640d03876ab52239f1946c60abca4fb41badbdf85b9f52f74ce016a760fa -size 51898 +oid sha256:784de072b6803569aaba3fddfeb1301ace32f51d2a3b78d823ccaaa77fd5d52e +size 52222 diff --git a/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Night_7_en.png b/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Night_7_en.png index df6ce558ab..d4f6602825 100644 --- a/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Night_7_en.png +++ b/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Night_7_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:16c3d5fb4eaa27c15bf22865ad97807a7e5b9cbc73194b67d3c8578c73b91de2 -size 51636 +oid sha256:fdec8293d351e9b879bafdff048a9ae3bdeaf34d1e47125fe94386415ef80ac4 +size 51941 diff --git a/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Night_8_en.png b/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Night_8_en.png index 966fdbf88e..2dcffff9f8 100644 --- a/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Night_8_en.png +++ b/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Night_8_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:79893b80ce51fc98742c9c0e31605f691953add4c6e9f55d0c3957cc8a723dc2 -size 49909 +oid sha256:f0599ae5bfbd84c1087b06b1e50352fd21b0d784e62536868f012f8a3ab8da95 +size 50257 diff --git a/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Night_9_en.png b/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Night_9_en.png index a33dcb8f4e..042afef2d1 100644 --- a/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Night_9_en.png +++ b/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Night_9_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:be43a15e95d9d073c95de0b876613bc4fb9c3f815ee32486cbbeeb8edbe8a553 -size 81131 +oid sha256:3af77b779731125d04585399023ac996fbdd4c42ee634d3c090153972a458212 +size 81441 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline_TimelineViewWithReadMarkerBothIndicators_Day_0_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline_TimelineViewWithReadMarkerBothIndicators_Day_0_en.png index 6a851f755e..4a73754a8e 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline_TimelineViewWithReadMarkerBothIndicators_Day_0_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline_TimelineViewWithReadMarkerBothIndicators_Day_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b55275e3538a08895a7eefe15e35afef1ce83975f6fb37c541d9f800d9f2d2a6 -size 47213 +oid sha256:2ff35c533fef9ca6ddd0e0f31ae4f4d5673e7e90f6d52d9636bfa62a045d8ebf +size 47952 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline_TimelineViewWithReadMarkerBothIndicators_Night_0_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline_TimelineViewWithReadMarkerBothIndicators_Night_0_en.png index 7eaee8802c..6df8acbfd3 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline_TimelineViewWithReadMarkerBothIndicators_Night_0_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline_TimelineViewWithReadMarkerBothIndicators_Night_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:934acae5403f1523092331d067b797585265026f03081c2875180e961f0957cf -size 50413 +oid sha256:cbcfc8e7f8fa55d007014f39be61a2d8a5a895ecb51f06d83b4f65c13302ebdd +size 51087 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline_TimelineViewWithReadMarkerJumpToUnreadIndicatorOnly_Day_0_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline_TimelineViewWithReadMarkerJumpToUnreadIndicatorOnly_Day_0_en.png index 6a851f755e..e5528b303c 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline_TimelineViewWithReadMarkerJumpToUnreadIndicatorOnly_Day_0_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline_TimelineViewWithReadMarkerJumpToUnreadIndicatorOnly_Day_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b55275e3538a08895a7eefe15e35afef1ce83975f6fb37c541d9f800d9f2d2a6 -size 47213 +oid sha256:990da337f293792e43768e9aa507581df18fac8b2b9dd01d7ad980e4239bec53 +size 47571 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline_TimelineViewWithReadMarkerJumpToUnreadIndicatorOnly_Night_0_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline_TimelineViewWithReadMarkerJumpToUnreadIndicatorOnly_Night_0_en.png index 7eaee8802c..d6b7840b7b 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline_TimelineViewWithReadMarkerJumpToUnreadIndicatorOnly_Night_0_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline_TimelineViewWithReadMarkerJumpToUnreadIndicatorOnly_Night_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:934acae5403f1523092331d067b797585265026f03081c2875180e961f0957cf -size 50413 +oid sha256:092e8d3ae1d219d48e3f696244cc83a53aa6402096ec6b7d817d3a115c6a28ac +size 50750 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetailsDark_15_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetailsDark_15_en.png index 87531bbb94..20b8a5e619 100644 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetailsDark_15_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetailsDark_15_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5ae035447be0df2583e4934e2f9b56812d11affa24535adf38f9e8ca2d71ca21 -size 40340 +oid sha256:4ab43948e7b58860945365c7a7d8bbf61f1edf7d6463296d57b94591b3c829a4 +size 40389 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetails_15_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetails_15_en.png index 9a8ec4e896..12eb9af146 100644 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetails_15_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetails_15_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c7379ec1ccc4eb1ca8647d4c1ff139af7d337104b02691a89de3e5582b3e3f37 -size 41604 +oid sha256:7e97d75c02a333e0ba2b66abb364bdbf70db08ed92614d10c7f0c9ad38b20600 +size 41668 diff --git a/tests/uitests/src/test/snapshots/images/features.space.impl.root_SpaceView_Day_3_en.png b/tests/uitests/src/test/snapshots/images/features.space.impl.root_SpaceView_Day_3_en.png index 4cd66fda9f..8b83d0555c 100644 --- a/tests/uitests/src/test/snapshots/images/features.space.impl.root_SpaceView_Day_3_en.png +++ b/tests/uitests/src/test/snapshots/images/features.space.impl.root_SpaceView_Day_3_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:41aec793fd2618601a96c199cb668a2225f0ba6d76a7926e788247660c43accd -size 59560 +oid sha256:373cfbe1fbd3d9e06bc2079b7ceadccce999efd0cbb1d43f7e791450f3882cad +size 59876 diff --git a/tests/uitests/src/test/snapshots/images/features.space.impl.root_SpaceView_Day_4_en.png b/tests/uitests/src/test/snapshots/images/features.space.impl.root_SpaceView_Day_4_en.png index 6b67239002..6febc55310 100644 --- a/tests/uitests/src/test/snapshots/images/features.space.impl.root_SpaceView_Day_4_en.png +++ b/tests/uitests/src/test/snapshots/images/features.space.impl.root_SpaceView_Day_4_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b7ad98eb663979992303b0b07af55f5a602538f8a3568797b5f876fc9673a843 -size 60234 +oid sha256:5192794371b4b3d10d5e8035f5ccf5dad66b04f16abb686cb5820d4647f678ef +size 60550 diff --git a/tests/uitests/src/test/snapshots/images/features.space.impl.root_SpaceView_Night_3_en.png b/tests/uitests/src/test/snapshots/images/features.space.impl.root_SpaceView_Night_3_en.png index 86d270af61..8d9d32385a 100644 --- a/tests/uitests/src/test/snapshots/images/features.space.impl.root_SpaceView_Night_3_en.png +++ b/tests/uitests/src/test/snapshots/images/features.space.impl.root_SpaceView_Night_3_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:56ef681d933d0e0647f3002a847aaa299f3d36db71d6d03cc63e73e9d5f33d64 -size 57916 +oid sha256:1ee744055473f7b652d874ff024ece68434e88ef085571dbab6466bcd3557c23 +size 58226 diff --git a/tests/uitests/src/test/snapshots/images/features.space.impl.root_SpaceView_Night_4_en.png b/tests/uitests/src/test/snapshots/images/features.space.impl.root_SpaceView_Night_4_en.png index 33bd00cbc2..2c24a43118 100644 --- a/tests/uitests/src/test/snapshots/images/features.space.impl.root_SpaceView_Night_4_en.png +++ b/tests/uitests/src/test/snapshots/images/features.space.impl.root_SpaceView_Night_4_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5dc6cd1814d57156e9644990d634c9e6f995b1e35150acf84256a84049c6005a -size 58480 +oid sha256:e3c991f23daeb792f580afc18f3f528649cc92f27555ed05cc1abedad8accc27 +size 58781 diff --git a/tests/uitests/src/test/snapshots/images/libraries.designsystem.atomic.atoms_CounterAtom_Day_0_en.png b/tests/uitests/src/test/snapshots/images/libraries.designsystem.atomic.atoms_CounterAtom_Day_0_en.png index a4b5cc493f..b707ae4e42 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.designsystem.atomic.atoms_CounterAtom_Day_0_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.designsystem.atomic.atoms_CounterAtom_Day_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4183ed1526f154b7044cff9b02a1e34c9c0a5fdf93232c9a5a1dc980fe0f1388 -size 7935 +oid sha256:ea3b6e3821ae1a9bc142d831c1359b030f8ca555b2bcf0824723db0603776631 +size 8903 diff --git a/tests/uitests/src/test/snapshots/images/libraries.designsystem.atomic.atoms_CounterAtom_Night_0_en.png b/tests/uitests/src/test/snapshots/images/libraries.designsystem.atomic.atoms_CounterAtom_Night_0_en.png index 860f6cef22..ad60d0287e 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.designsystem.atomic.atoms_CounterAtom_Night_0_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.designsystem.atomic.atoms_CounterAtom_Night_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:fa39f384f766a79316d8f930ba682b5b08876533e067bbd350b3261d36297888 -size 7424 +oid sha256:8bb14c46d84c653a0429012b41e7acfa5205b3d6d464bea98612cbdb3eca5f27 +size 8276 diff --git a/tests/uitests/src/test/snapshots/images/libraries.designsystem.atomic.atoms_UnreadIndicatorAtom_Day_0_en.png b/tests/uitests/src/test/snapshots/images/libraries.designsystem.atomic.atoms_UnreadIndicatorAtom_Day_0_en.png index a17548b7f6..4f41fb6a88 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.designsystem.atomic.atoms_UnreadIndicatorAtom_Day_0_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.designsystem.atomic.atoms_UnreadIndicatorAtom_Day_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a8cbffff27d8e2284a91afcab63419ad77b4b72e2583d5f5f5fe9f3b86c4497a -size 7267 +oid sha256:f2d4fa0d01fecbd961c781159af8249a400e4be7fa7383dab75f95fb9241608e +size 4086 diff --git a/tests/uitests/src/test/snapshots/images/libraries.designsystem.atomic.atoms_UnreadIndicatorAtom_Night_0_en.png b/tests/uitests/src/test/snapshots/images/libraries.designsystem.atomic.atoms_UnreadIndicatorAtom_Night_0_en.png index d380a94d3e..0e0bcdbb72 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.designsystem.atomic.atoms_UnreadIndicatorAtom_Night_0_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.designsystem.atomic.atoms_UnreadIndicatorAtom_Night_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ed6a76df3601c8740d636503aea1173ed711da91fae12935ddd925483b4197ca -size 6642 +oid sha256:a73349d1bbd4afedaf49ca91d4037a659b17fa6e4605a3e9384ffa556a063f25 +size 4035 diff --git a/tests/uitests/src/test/snapshots/images/libraries.designsystem.theme.components_HorizontalFloatingToolbarNoFab_Day_0_en.png b/tests/uitests/src/test/snapshots/images/libraries.designsystem.theme.components_HorizontalFloatingToolbarNoFab_Day_0_en.png index f5e4be23f4..58546b8522 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.designsystem.theme.components_HorizontalFloatingToolbarNoFab_Day_0_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.designsystem.theme.components_HorizontalFloatingToolbarNoFab_Day_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:23b332e5fae5381f4ed742d7ac43479e5ac4847e857bbee9af379f44b9a8e1c9 -size 11646 +oid sha256:30357bf18792db5ab20085010251170a724ffe8c00313facd04ee704e806af64 +size 10982 diff --git a/tests/uitests/src/test/snapshots/images/libraries.designsystem.theme.components_HorizontalFloatingToolbarNoFab_Night_0_en.png b/tests/uitests/src/test/snapshots/images/libraries.designsystem.theme.components_HorizontalFloatingToolbarNoFab_Night_0_en.png index 7dd36ce469..fedead0fa8 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.designsystem.theme.components_HorizontalFloatingToolbarNoFab_Night_0_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.designsystem.theme.components_HorizontalFloatingToolbarNoFab_Night_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d17a8f10926076fe6249e335f7f1d6c1a3fca70d0a95221e7eb8cb1f0559fa51 -size 8589 +oid sha256:4811667dec0f32accc5e2c8a5dd5c0b34017fcff8d1e0376ee7a4708426bca0f +size 9838 diff --git a/tests/uitests/src/test/snapshots/images/libraries.designsystem.theme.components_HorizontalFloatingToolbar_Day_0_en.png b/tests/uitests/src/test/snapshots/images/libraries.designsystem.theme.components_HorizontalFloatingToolbar_Day_0_en.png index e13284edca..83302d6ba4 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.designsystem.theme.components_HorizontalFloatingToolbar_Day_0_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.designsystem.theme.components_HorizontalFloatingToolbar_Day_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c6461cb039a2a99b7531fcf1ba0d7cd2972727c2aa63f645e914c99f14fac723 -size 32629 +oid sha256:16dee1dfe746cd40dac9fe8f0af3ecb42cd14bab5cc490917b589beed065ed1b +size 14686 diff --git a/tests/uitests/src/test/snapshots/images/libraries.designsystem.theme.components_HorizontalFloatingToolbar_Night_0_en.png b/tests/uitests/src/test/snapshots/images/libraries.designsystem.theme.components_HorizontalFloatingToolbar_Night_0_en.png index d985007797..4da2a98aca 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.designsystem.theme.components_HorizontalFloatingToolbar_Night_0_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.designsystem.theme.components_HorizontalFloatingToolbar_Night_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7b7942c49a8d33123b0dbb056daacdc525c29c05d7807fb3764cb4a5150455cb -size 24634 +oid sha256:ef0dc4d53daeb986f378b6e4d85928b4b6729336c89cf21e642c88c53688a3ab +size 10292 diff --git a/tests/uitests/src/test/snapshots/images/libraries.designsystem.theme.components_NavigationBar_App_Bars_en.png b/tests/uitests/src/test/snapshots/images/libraries.designsystem.theme.components_NavigationBar_App_Bars_en.png index be17322a4d..34384b3ba4 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.designsystem.theme.components_NavigationBar_App_Bars_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.designsystem.theme.components_NavigationBar_App_Bars_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3a45c52567b03fd01fc725e7466a853a65399d116b8840bdee1ab703603487fa -size 15819 +oid sha256:121b01a2411c4e12782b72a7bba1210c434b67c8281247b4bc080c8a8bb4e097 +size 15690 diff --git a/tests/uitests/src/test/snapshots/images/libraries.matrix.ui.components_SpaceRoomItemView_Day_3_en.png b/tests/uitests/src/test/snapshots/images/libraries.matrix.ui.components_SpaceRoomItemView_Day_3_en.png index d368a2980f..eb21a4e93b 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.matrix.ui.components_SpaceRoomItemView_Day_3_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.matrix.ui.components_SpaceRoomItemView_Day_3_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9569da9b4f797a32c432426c0713322dbe87d89c7c926e9f1df1d835c815960f -size 19075 +oid sha256:e56d17fe464fbb261056dd0ac7b8c87efae96f7187f71bd6c9c5b3aeb7c358d6 +size 19424 diff --git a/tests/uitests/src/test/snapshots/images/libraries.matrix.ui.components_SpaceRoomItemView_Day_4_en.png b/tests/uitests/src/test/snapshots/images/libraries.matrix.ui.components_SpaceRoomItemView_Day_4_en.png index c75b08e44b..b3906c8856 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.matrix.ui.components_SpaceRoomItemView_Day_4_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.matrix.ui.components_SpaceRoomItemView_Day_4_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:346455332fc343ce172eb523c426cd663db535512b8eabfa68d45ce312ca9306 -size 18756 +oid sha256:bb1f5c4a72adcdff1556a88190658fa05452952954502ad0fc9105b6dc802d04 +size 19114 diff --git a/tests/uitests/src/test/snapshots/images/libraries.matrix.ui.components_SpaceRoomItemView_Day_7_en.png b/tests/uitests/src/test/snapshots/images/libraries.matrix.ui.components_SpaceRoomItemView_Day_7_en.png index f89102d3ea..9886d9c865 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.matrix.ui.components_SpaceRoomItemView_Day_7_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.matrix.ui.components_SpaceRoomItemView_Day_7_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1bf98a8db438787d6cf5a3c22e617c0a0e3d6069880352bb2c2ee1e1d8fbacd9 -size 37121 +oid sha256:fd9a020a4b4a2d9fe1a720e1ef1ea6d444d568730ca10a27764a6a11ae93ca9e +size 37428 diff --git a/tests/uitests/src/test/snapshots/images/libraries.matrix.ui.components_SpaceRoomItemView_Night_3_en.png b/tests/uitests/src/test/snapshots/images/libraries.matrix.ui.components_SpaceRoomItemView_Night_3_en.png index 7407952240..147081568d 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.matrix.ui.components_SpaceRoomItemView_Night_3_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.matrix.ui.components_SpaceRoomItemView_Night_3_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:507882f8fbac05f38ab0a76e37bfe8de1e213f28870346d600adb1ca13487c9e -size 18363 +oid sha256:5bd1e4629aad697d49657dcaee6dec1cae46980d9246f0a48d22cdfed6823ce4 +size 18692 diff --git a/tests/uitests/src/test/snapshots/images/libraries.matrix.ui.components_SpaceRoomItemView_Night_4_en.png b/tests/uitests/src/test/snapshots/images/libraries.matrix.ui.components_SpaceRoomItemView_Night_4_en.png index b9aa819216..d8a36be2bc 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.matrix.ui.components_SpaceRoomItemView_Night_4_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.matrix.ui.components_SpaceRoomItemView_Night_4_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3523780d1f7526194ffd898ab0b817ae6158983308c4116f5f894964b7504042 -size 18057 +oid sha256:f4411decc4e53e10e4debd884aa5e1cc51f6fa057dfcbe9828e7cd87959411e0 +size 18397 diff --git a/tests/uitests/src/test/snapshots/images/libraries.matrix.ui.components_SpaceRoomItemView_Night_7_en.png b/tests/uitests/src/test/snapshots/images/libraries.matrix.ui.components_SpaceRoomItemView_Night_7_en.png index 388e9ba9b2..eda3c1422a 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.matrix.ui.components_SpaceRoomItemView_Night_7_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.matrix.ui.components_SpaceRoomItemView_Night_7_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8708e565a0811df16e20648a64938934ced44e598160c4a5230cbf03f3826c5d -size 36056 +oid sha256:65f65fba9f6fc97edceb825090112245e17b8f302382dc5053eb0fd4af2c5bc8 +size 36324 From 7cbd60448df6eea9313d0997a49d064864cde952 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20Mart=C3=ADn?= Date: Tue, 29 Sep 2026 16:36:06 +0200 Subject: [PATCH 03/20] Setting version for the release 26.09.4 --- 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 7a28130702..1f80aa1be2 100644 --- a/plugins/src/main/kotlin/Versions.kt +++ b/plugins/src/main/kotlin/Versions.kt @@ -45,7 +45,7 @@ private const val versionMonth = 9 * 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 = 3 +private const val versionReleaseNumber = 4 object Versions { /** From 5b84bd4340b70f4dbbe36ffde1cbc941c4c176a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20Mart=C3=ADn?= Date: Tue, 29 Sep 2026 16:37:05 +0200 Subject: [PATCH 04/20] Adding fastlane file for version 26.09.4 --- fastlane/metadata/android/en-US/changelogs/202609040.txt | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 fastlane/metadata/android/en-US/changelogs/202609040.txt diff --git a/fastlane/metadata/android/en-US/changelogs/202609040.txt b/fastlane/metadata/android/en-US/changelogs/202609040.txt new file mode 100644 index 0000000000..f89659a6f0 --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/202609040.txt @@ -0,0 +1,2 @@ +Main changes in this version: fix an issue with the room list not displaying all activity from unread messages. +Full changelog: https://github.com/element-hq/element-x-android/releases From ed80c5c53746373907ed69b5de5a8f20c820dc4f Mon Sep 17 00:00:00 2001 From: Jorge Martin Espinosa Date: Tue, 29 Sep 2026 18:35:36 +0200 Subject: [PATCH 05/20] Remove Firebase push integration from Posthog (#7810) This causes issues with F-Droid, since now Posthog unconditionally references some Firebase classes, which are detected by their checks. Note these don't do anything if Firebase messaging isn't included, which is already the case for the F-droid variant of the app. The R8 changes should remove any code referencing Firebase in Posthog, making the offending classes unused and removing them from the final APKs. This is not the ideal fix, that would be Posthog actually extracting these classes to an optional dependency, but it's a workaround to be able to publish F-droid versions. --- app/proguard/foss/default-proguard-rules.keep | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/app/proguard/foss/default-proguard-rules.keep b/app/proguard/foss/default-proguard-rules.keep index a6dce2f0ce..604e1c55fd 100644 --- a/app/proguard/foss/default-proguard-rules.keep +++ b/app/proguard/foss/default-proguard-rules.keep @@ -1,2 +1,9 @@ # Don't obfuscate anything for non-enterprise builds -dontobfuscate + +# Strip PostHog's Firebase push token integration. We don't use it and it breaks F-Droid. +# Removing the contents of this method will mark the Firebase-related classes as unused, +# which will allow R8 to remove them from the final build. +-assumevalues class com.posthog.android.PostHogAndroidConfig { + boolean getCapturePushNotificationSubscriptions() return false; +} From 93b08e3508d52dda2652ffa881bacfeaf4829064 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 29 Sep 2026 18:37:32 +0200 Subject: [PATCH 06/20] Update dependency io.sentry:sentry-android to v8.57.0 (#7814) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- 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 7de1894015..1788edd14e 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -223,7 +223,7 @@ color_picker = "io.mhssn:colorpicker:1.0.0" # Analytics posthog = "com.posthog:posthog-android:3.60.7" -sentry = "io.sentry:sentry-android:8.55.0" +sentry = "io.sentry:sentry-android:8.57.0" # main branch can be tested replacing the version with main-SNAPSHOT matrix_analytics_events = "com.github.matrix-org:matrix-analytics-events:0.37.0" From 0059c72a32e6d2df380228174af6db3748cd2441 Mon Sep 17 00:00:00 2001 From: Valere Date: Mon, 14 Sep 2026 19:04:13 +0200 Subject: [PATCH 07/20] Expose the write sides of the current-call and ringing state `CurrentCallService` and `ActiveCallManager` are read by the whole app but written only by whoever is actually running a call. Writing has so far been reachable only from inside `features/call/impl`, because that is where both implementations live. Adds `CurrentCallTracker` and `RingingCallTracker` to `features/call/api`, so that a call implementation in another module can report the call it is running and answer a ring, without depending on `features/call/impl`. No behaviour change: both interfaces are implemented by the existing classes, and their methods are aliases of methods that already existed. Co-Authored-By: Claude Opus 5 (1M context) --- .../features/call/api/CurrentCallTracker.kt | 23 +++++++++++++++ .../features/call/api/RingingCallTracker.kt | 28 +++++++++++++++++++ .../call/impl/utils/ActiveCallManager.kt | 16 +++++++++-- .../impl/utils/DefaultCurrentCallService.kt | 11 +++++--- .../call/test/FakeCurrentCallTracker.kt | 24 ++++++++++++++++ .../call/test/FakeRingingCallTracker.kt | 24 ++++++++++++++++ 6 files changed, 120 insertions(+), 6 deletions(-) create mode 100644 features/call/api/src/main/kotlin/io/element/android/features/call/api/CurrentCallTracker.kt create mode 100644 features/call/api/src/main/kotlin/io/element/android/features/call/api/RingingCallTracker.kt create mode 100644 features/call/test/src/main/kotlin/io/element/android/features/call/test/FakeCurrentCallTracker.kt create mode 100644 features/call/test/src/main/kotlin/io/element/android/features/call/test/FakeRingingCallTracker.kt diff --git a/features/call/api/src/main/kotlin/io/element/android/features/call/api/CurrentCallTracker.kt b/features/call/api/src/main/kotlin/io/element/android/features/call/api/CurrentCallTracker.kt new file mode 100644 index 0000000000..42190316c3 --- /dev/null +++ b/features/call/api/src/main/kotlin/io/element/android/features/call/api/CurrentCallTracker.kt @@ -0,0 +1,23 @@ +/* + * 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.call.api + +/** + * The write side of [CurrentCallService], for whoever is actually running a call. + * + * Separate from [CurrentCallService] because the two have opposite audiences: everything in the app + * that wants to know whether we are in a call reads the service, while only a call implementation + * writes. The WebView path reaches the implementation directly through `ActiveCallManager`, both + * being in `features/call/impl`; the native path is in another module and would otherwise have to + * depend on that impl to report a call it is running itself. + */ +interface CurrentCallTracker { + fun onCallStarted(call: CurrentCall) + + fun onCallEnded() +} diff --git a/features/call/api/src/main/kotlin/io/element/android/features/call/api/RingingCallTracker.kt b/features/call/api/src/main/kotlin/io/element/android/features/call/api/RingingCallTracker.kt new file mode 100644 index 0000000000..aaec286072 --- /dev/null +++ b/features/call/api/src/main/kotlin/io/element/android/features/call/api/RingingCallTracker.kt @@ -0,0 +1,28 @@ +/* + * 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.call.api + +/** + * Tells the ringing machinery what became of the call it is ringing for. + * + * There is one ring per incoming call, and it is owned by `ActiveCallManager` in + * `features/call/impl` - but *whichever* call implementation answers has to stop it, cancel the + * missed-call timeout and release the wake lock. The WebView path reaches the manager directly, + * being in the same module; the native path is in another one, and this is the narrow slice of it + * that anyone answering a call needs. + */ +interface RingingCallTracker { + /** + * The user is now in the call: stop ringing, take down the incoming-call UI, and cancel the + * timeout that would otherwise post a missed call. + */ + suspend fun onCallJoined(callData: CallData) + + /** The call is over. Clears the active call, and stops a ring that is still in progress. */ + suspend fun onCallEnded(callData: CallData) +} diff --git a/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/utils/ActiveCallManager.kt b/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/utils/ActiveCallManager.kt index 685fc932fe..c0f01fe0ac 100644 --- a/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/utils/ActiveCallManager.kt +++ b/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/utils/ActiveCallManager.kt @@ -19,9 +19,11 @@ import coil3.annotation.DelicateCoilApi import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesBinding import dev.zacsweers.metro.SingleIn +import dev.zacsweers.metro.binding import io.element.android.appconfig.ElementCallConfig import io.element.android.features.call.api.CallData import io.element.android.features.call.api.CurrentCall +import io.element.android.features.call.api.RingingCallTracker import io.element.android.features.call.impl.notifications.CallNotificationData import io.element.android.features.call.impl.notifications.RingingCallNotificationCreator import io.element.android.libraries.core.extensions.runCatchingExceptions @@ -90,7 +92,8 @@ interface ActiveCallManager { } @SingleIn(AppScope::class) -@ContributesBinding(AppScope::class) +@ContributesBinding(AppScope::class, binding = binding()) +@ContributesBinding(AppScope::class, binding = binding()) class DefaultActiveCallManager( @ApplicationContext context: Context, @AppCoroutineScope @@ -103,7 +106,16 @@ class DefaultActiveCallManager( private val appForegroundStateService: AppForegroundStateService, private val imageLoaderHolder: ImageLoaderHolder, private val systemClock: SystemClock, -) : ActiveCallManager { +) : ActiveCallManager, RingingCallTracker { + /** + * The two [RingingCallTracker] entry points are the existing methods under the names a caller + * outside this module can reach. Deliberately not new behaviour: answering has always had to do + * exactly this, and the native path simply could not say so from another module. + */ + override suspend fun onCallJoined(callData: CallData) = joinedCall(callData) + + override suspend fun onCallEnded(callData: CallData) = hangUpCall(callData) + private val tag = "ActiveCallManager" private var timedOutCallJob: Job? = null diff --git a/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/utils/DefaultCurrentCallService.kt b/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/utils/DefaultCurrentCallService.kt index 1c8c0b342b..bf29ccd404 100644 --- a/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/utils/DefaultCurrentCallService.kt +++ b/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/utils/DefaultCurrentCallService.kt @@ -11,20 +11,23 @@ package io.element.android.features.call.impl.utils import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesBinding import dev.zacsweers.metro.SingleIn +import dev.zacsweers.metro.binding import io.element.android.features.call.api.CurrentCall import io.element.android.features.call.api.CurrentCallService +import io.element.android.features.call.api.CurrentCallTracker import kotlinx.coroutines.flow.MutableStateFlow @SingleIn(AppScope::class) -@ContributesBinding(AppScope::class) -class DefaultCurrentCallService : CurrentCallService { +@ContributesBinding(AppScope::class, binding = binding()) +@ContributesBinding(AppScope::class, binding = binding()) +class DefaultCurrentCallService : CurrentCallService, CurrentCallTracker { override val currentCall = MutableStateFlow(CurrentCall.None) - fun onCallStarted(call: CurrentCall) { + override fun onCallStarted(call: CurrentCall) { currentCall.value = call } - fun onCallEnded() { + override fun onCallEnded() { currentCall.value = CurrentCall.None } } diff --git a/features/call/test/src/main/kotlin/io/element/android/features/call/test/FakeCurrentCallTracker.kt b/features/call/test/src/main/kotlin/io/element/android/features/call/test/FakeCurrentCallTracker.kt new file mode 100644 index 0000000000..b6e31f8882 --- /dev/null +++ b/features/call/test/src/main/kotlin/io/element/android/features/call/test/FakeCurrentCallTracker.kt @@ -0,0 +1,24 @@ +/* + * 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.call.test + +import io.element.android.features.call.api.CurrentCall +import io.element.android.features.call.api.CurrentCallTracker + +class FakeCurrentCallTracker : CurrentCallTracker { + /** Every transition in order, so a test can assert the call was reported before it was cleared. */ + val calls = mutableListOf() + + override fun onCallStarted(call: CurrentCall) { + calls += call + } + + override fun onCallEnded() { + calls += CurrentCall.None + } +} diff --git a/features/call/test/src/main/kotlin/io/element/android/features/call/test/FakeRingingCallTracker.kt b/features/call/test/src/main/kotlin/io/element/android/features/call/test/FakeRingingCallTracker.kt new file mode 100644 index 0000000000..915641a0a4 --- /dev/null +++ b/features/call/test/src/main/kotlin/io/element/android/features/call/test/FakeRingingCallTracker.kt @@ -0,0 +1,24 @@ +/* + * 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.call.test + +import io.element.android.features.call.api.CallData +import io.element.android.features.call.api.RingingCallTracker + +class FakeRingingCallTracker : RingingCallTracker { + val joined = mutableListOf() + val ended = mutableListOf() + + override suspend fun onCallJoined(callData: CallData) { + joined += callData + } + + override suspend fun onCallEnded(callData: CallData) { + ended += callData + } +} From 66d6d365d7dda5b1c98f08ad97274a0839410069 Mon Sep 17 00:00:00 2001 From: Valere Date: Mon, 14 Sep 2026 19:05:18 +0200 Subject: [PATCH 08/20] Route calls through an entry point the native stack can implement Adds the seam a native MatrixRTC call stack plugs into, with nothing behind it yet, so that the stack itself lands as a self-contained change. `FeatureFlags.NativeCall` is off by default and unfinished. `features/callnative/api` declares `NativeCallEntryPoint`, and `features/callnative/impl` provides a default that logs and does nothing: turning the flag on today gets no call and a line in the log. That is the honest outcome, where falling back to the WebView would make the flag look broken rather than unimplemented. `DefaultElementCallEntryPoint` forks on the flag. The read is asynchronous because the flag is storage-backed, and caching it in an eagerly started StateFlow would race a call placed just after app start onto the default value. Co-Authored-By: Claude Opus 5 (1M context) --- features/call/impl/build.gradle.kts | 1 + .../call/impl/DefaultElementCallEntryPoint.kt | 23 ++++++++++++- .../call/DefaultElementCallEntryPointTest.kt | 34 +++++++++++++++++++ .../call/utils/FakeNativeCallEntryPoint.kt | 17 ++++++++++ features/callnative/api/build.gradle.kts | 21 ++++++++++++ .../callnative/api/NativeCallEntryPoint.kt | 24 +++++++++++++ features/callnative/impl/build.gradle.kts | 29 ++++++++++++++++ .../impl/DefaultNativeCallEntryPoint.kt | 29 ++++++++++++++++ .../libraries/featureflag/api/FeatureFlags.kt | 7 ++++ 9 files changed, 184 insertions(+), 1 deletion(-) create mode 100644 features/call/impl/src/test/kotlin/io/element/android/features/call/utils/FakeNativeCallEntryPoint.kt create mode 100644 features/callnative/api/build.gradle.kts create mode 100644 features/callnative/api/src/main/kotlin/io/element/android/features/callnative/api/NativeCallEntryPoint.kt create mode 100644 features/callnative/impl/build.gradle.kts create mode 100644 features/callnative/impl/src/main/kotlin/io/element/android/features/callnative/impl/DefaultNativeCallEntryPoint.kt diff --git a/features/call/impl/build.gradle.kts b/features/call/impl/build.gradle.kts index d01e31451e..e872678b63 100644 --- a/features/call/impl/build.gradle.kts +++ b/features/call/impl/build.gradle.kts @@ -86,6 +86,7 @@ dependencies { implementation(libs.coil.compose) implementation(libs.serialization.json) implementation(libs.element.call.embedded) + implementation(projects.features.callnative.api) api(projects.features.call.api) testCommonDependencies(libs, true) diff --git a/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/DefaultElementCallEntryPoint.kt b/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/DefaultElementCallEntryPoint.kt index 532db3305d..074ea87551 100644 --- a/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/DefaultElementCallEntryPoint.kt +++ b/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/DefaultElementCallEntryPoint.kt @@ -16,14 +16,24 @@ import io.element.android.features.call.api.ElementCallEntryPoint import io.element.android.features.call.impl.notifications.CallNotificationData import io.element.android.features.call.impl.utils.ActiveCallManager import io.element.android.features.call.impl.utils.IntentProvider +import io.element.android.features.callnative.api.NativeCallEntryPoint +import io.element.android.libraries.di.annotations.AppCoroutineScope import io.element.android.libraries.di.annotations.ApplicationContext +import io.element.android.libraries.featureflag.api.FeatureFlagService +import io.element.android.libraries.featureflag.api.FeatureFlags import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.core.UserId +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch +import timber.log.Timber @ContributesBinding(AppScope::class) class DefaultElementCallEntryPoint( @ApplicationContext private val context: Context, private val activeCallManager: ActiveCallManager, + private val nativeCallEntryPoint: NativeCallEntryPoint, + private val featureFlagService: FeatureFlagService, + @AppCoroutineScope private val appCoroutineScope: CoroutineScope, ) : ElementCallEntryPoint { companion object { const val EXTRA_CALL_TYPE = "EXTRA_CALL_TYPE" @@ -31,7 +41,18 @@ class DefaultElementCallEntryPoint( } override fun startCall(callData: CallData) { - context.startActivity(IntentProvider.createIntent(context, callData)) + // The flag is backed by storage, so it has to be read asynchronously. Caching it in an + // eagerly started StateFlow would race: a call placed just after app start could be routed + // on the default value before the real one arrived. + appCoroutineScope.launch { + val useNativeCall = featureFlagService.isFeatureEnabled(FeatureFlags.NativeCall) + Timber.i("startCall: roomId=${callData.roomId}, nativeCall=$useNativeCall") + if (useNativeCall) { + nativeCallEntryPoint.startCall(callData) + } else { + context.startActivity(IntentProvider.createIntent(context, callData)) + } + } } override suspend fun handleIncomingCall( diff --git a/features/call/impl/src/test/kotlin/io/element/android/features/call/DefaultElementCallEntryPointTest.kt b/features/call/impl/src/test/kotlin/io/element/android/features/call/DefaultElementCallEntryPointTest.kt index 9b2ee3d923..82c28209f7 100644 --- a/features/call/impl/src/test/kotlin/io/element/android/features/call/DefaultElementCallEntryPointTest.kt +++ b/features/call/impl/src/test/kotlin/io/element/android/features/call/DefaultElementCallEntryPointTest.kt @@ -16,15 +16,21 @@ import io.element.android.features.call.impl.DefaultElementCallEntryPoint import io.element.android.features.call.impl.notifications.CallNotificationData import io.element.android.features.call.impl.ui.ElementCallActivity import io.element.android.features.call.utils.FakeActiveCallManager +import io.element.android.features.call.utils.FakeNativeCallEntryPoint +import io.element.android.features.callnative.api.NativeCallEntryPoint +import io.element.android.libraries.featureflag.api.FeatureFlags +import io.element.android.libraries.featureflag.test.FakeFeatureFlagService import io.element.android.libraries.matrix.test.AN_EVENT_ID import io.element.android.libraries.matrix.test.A_ROOM_ID import io.element.android.libraries.matrix.test.A_SESSION_ID import io.element.android.libraries.matrix.test.A_USER_ID_2 import io.element.android.tests.testutils.lambda.lambdaRecorder +import io.element.android.tests.testutils.lambda.value import io.element.android.tests.testutils.robolectric.RobolectricTest import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.advanceTimeBy +import kotlinx.coroutines.test.advanceUntilIdle import kotlinx.coroutines.test.runTest import org.junit.Test import org.robolectric.RuntimeEnvironment @@ -32,10 +38,13 @@ import org.robolectric.Shadows.shadowOf import kotlin.time.Duration.Companion.seconds class DefaultElementCallEntryPointTest : RobolectricTest() { + @OptIn(ExperimentalCoroutinesApi::class) @Test fun `startCall - starts ElementCallActivity setup with the needed extras`() = runTest { val entryPoint = createEntryPoint() entryPoint.startCall(CallData(A_SESSION_ID, A_ROOM_ID, isAudioCall = false)) + // The flag is read asynchronously before routing. + advanceUntilIdle() val expectedIntent = Intent(InstrumentationRegistry.getInstrumentation().targetContext, ElementCallActivity::class.java) val intent = shadowOf(RuntimeEnvironment.getApplication()).nextStartedActivity @@ -43,6 +52,24 @@ class DefaultElementCallEntryPointTest : RobolectricTest() { assertThat(intent.extras?.containsKey("EXTRA_CALL_TYPE")).isTrue() } + @OptIn(ExperimentalCoroutinesApi::class) + @Test + fun `startCall - routes to the native call stack when the feature flag is on`() = runTest { + val startNativeCallLambda = lambdaRecorder {} + val entryPoint = createEntryPoint( + nativeCallEntryPoint = FakeNativeCallEntryPoint(startNativeCallLambda), + isNativeCallEnabled = true, + ) + val callData = CallData(A_SESSION_ID, A_ROOM_ID, isAudioCall = true) + + entryPoint.startCall(callData) + advanceUntilIdle() + + startNativeCallLambda.assertions().isCalledOnce().with(value(callData)) + // The WebView Activity must not also be launched. + assertThat(shadowOf(RuntimeEnvironment.getApplication()).nextStartedActivity).isNull() + } + @OptIn(ExperimentalCoroutinesApi::class) @Test fun `handleIncomingCall - registers the incoming call using ActiveCallManager`() = runTest { @@ -70,8 +97,15 @@ class DefaultElementCallEntryPointTest : RobolectricTest() { private fun TestScope.createEntryPoint( activeCallManager: FakeActiveCallManager = FakeActiveCallManager(), + nativeCallEntryPoint: NativeCallEntryPoint = FakeNativeCallEntryPoint(), + isNativeCallEnabled: Boolean = false, ) = DefaultElementCallEntryPoint( context = InstrumentationRegistry.getInstrumentation().targetContext, activeCallManager = activeCallManager, + nativeCallEntryPoint = nativeCallEntryPoint, + featureFlagService = FakeFeatureFlagService( + initialState = mapOf(FeatureFlags.NativeCall.key to isNativeCallEnabled), + ), + appCoroutineScope = this, ) } diff --git a/features/call/impl/src/test/kotlin/io/element/android/features/call/utils/FakeNativeCallEntryPoint.kt b/features/call/impl/src/test/kotlin/io/element/android/features/call/utils/FakeNativeCallEntryPoint.kt new file mode 100644 index 0000000000..742ba55bdc --- /dev/null +++ b/features/call/impl/src/test/kotlin/io/element/android/features/call/utils/FakeNativeCallEntryPoint.kt @@ -0,0 +1,17 @@ +/* + * 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.call.utils + +import io.element.android.features.call.api.CallData +import io.element.android.features.callnative.api.NativeCallEntryPoint + +class FakeNativeCallEntryPoint( + private val startCallLambda: (CallData) -> Unit = {}, +) : NativeCallEntryPoint { + override fun startCall(callData: CallData) = startCallLambda(callData) +} diff --git a/features/callnative/api/build.gradle.kts b/features/callnative/api/build.gradle.kts new file mode 100644 index 0000000000..73c478ccb6 --- /dev/null +++ b/features/callnative/api/build.gradle.kts @@ -0,0 +1,21 @@ +/* + * 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. + */ + +plugins { + id("io.element.android-library") +} + +android { + namespace = "io.element.android.features.callnative.api" +} + +dependencies { + // api, not implementation: CallData is part of NativeCallEntryPoint's signature. + api(projects.features.call.api) + implementation(projects.libraries.architecture) + implementation(projects.libraries.matrix.api) +} diff --git a/features/callnative/api/src/main/kotlin/io/element/android/features/callnative/api/NativeCallEntryPoint.kt b/features/callnative/api/src/main/kotlin/io/element/android/features/callnative/api/NativeCallEntryPoint.kt new file mode 100644 index 0000000000..a964c1502a --- /dev/null +++ b/features/callnative/api/src/main/kotlin/io/element/android/features/callnative/api/NativeCallEntryPoint.kt @@ -0,0 +1,24 @@ +/* + * 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.callnative.api + +import io.element.android.features.call.api.CallData + +/** + * Starts a call on the native MatrixRTC stack instead of the Element Call WebView. + * + * Experimental: gated behind `FeatureFlags.NativeCall` and reached only through + * [io.element.android.features.call.api.ElementCallEntryPoint], so nothing else in the app has to know + * it exists. + * + * There is nothing behind this seam yet - the default implementation does nothing and says so. The + * native stack that implements it is a separate library, added in a follow-up. + */ +interface NativeCallEntryPoint { + fun startCall(callData: CallData) +} diff --git a/features/callnative/impl/build.gradle.kts b/features/callnative/impl/build.gradle.kts new file mode 100644 index 0000000000..26c9eabae3 --- /dev/null +++ b/features/callnative/impl/build.gradle.kts @@ -0,0 +1,29 @@ +import extension.setupDependencyInjection +import extension.testCommonDependencies + +/* + * 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. + */ +plugins { + id("io.element.android-library") +} + +android { + namespace = "io.element.android.features.callnative.impl" +} + +setupDependencyInjection() + +dependencies { + api(projects.features.callnative.api) + + implementation(projects.features.call.api) + implementation(projects.libraries.architecture) + implementation(projects.libraries.di) + implementation(projects.libraries.matrix.api) + + testCommonDependencies(libs) +} diff --git a/features/callnative/impl/src/main/kotlin/io/element/android/features/callnative/impl/DefaultNativeCallEntryPoint.kt b/features/callnative/impl/src/main/kotlin/io/element/android/features/callnative/impl/DefaultNativeCallEntryPoint.kt new file mode 100644 index 0000000000..ecfdafd76f --- /dev/null +++ b/features/callnative/impl/src/main/kotlin/io/element/android/features/callnative/impl/DefaultNativeCallEntryPoint.kt @@ -0,0 +1,29 @@ +/* + * 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.callnative.impl + +import dev.zacsweers.metro.AppScope +import dev.zacsweers.metro.ContributesBinding +import io.element.android.features.call.api.CallData +import io.element.android.features.callnative.api.NativeCallEntryPoint +import timber.log.Timber + +/** + * What answers [NativeCallEntryPoint] until the native MatrixRTC stack is wired in: nothing. + * + * The seam exists so that `DefaultElementCallEntryPoint` can fork on `FeatureFlags.NativeCall` today, + * and so that adding the stack later is a change to this module alone. A user who turns the flag on + * before then gets no call and a line in the log, which is the honest outcome - falling back to the + * WebView would make the flag look broken rather than unimplemented. + */ +@ContributesBinding(AppScope::class) +class DefaultNativeCallEntryPoint : NativeCallEntryPoint { + override fun startCall(callData: CallData) { + Timber.w("Native call requested for ${callData.roomId} but no native call stack is present in this build") + } +} diff --git a/libraries/featureflag/api/src/main/kotlin/io/element/android/libraries/featureflag/api/FeatureFlags.kt b/libraries/featureflag/api/src/main/kotlin/io/element/android/libraries/featureflag/api/FeatureFlags.kt index 98bb55fecf..1b7aad30b3 100644 --- a/libraries/featureflag/api/src/main/kotlin/io/element/android/libraries/featureflag/api/FeatureFlags.kt +++ b/libraries/featureflag/api/src/main/kotlin/io/element/android/libraries/featureflag/api/FeatureFlags.kt @@ -162,4 +162,11 @@ enum class FeatureFlags( isFinished = false, isInLabs = false, ), + NativeCall( + key = "feature.native_call", + title = "Native calls (experimental)", + description = "Place calls with the native MatrixRTC stack instead of the Element Call WebView.", + defaultValue = { false }, + isFinished = false, + ), } From 0d36f70c964c54878d464f8fad4d28feda4fd884 Mon Sep 17 00:00:00 2001 From: Valere Date: Tue, 22 Sep 2026 21:04:03 +0200 Subject: [PATCH 09/20] Resolve element-call-android from its GitHub releases The component and the matrix-rust-rtc core publish as GitHub release assets rather than to Maven, so two filtered ivy repositories resolve them; both become plain coordinates the day they reach Maven Central. Co-Authored-By: Claude Opus 5 (1M context) --- gradle/libs.versions.toml | 15 +++++++++++++- settings.gradle.kts | 42 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 56 insertions(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 1788edd14e..905db875ae 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -64,6 +64,8 @@ detekt_compose_rules = "0.4.28" ktlint = "1.8.0" androidx-test-ext-junit = "1.3.0" kover = "0.9.9" +# The native MatrixRTC call component, https://github.com/element-hq/element-call-android +element_call = "0.1.0-rc.2" [libraries] # Project @@ -182,7 +184,11 @@ test_detekt_test = { module = "io.gitlab.arturbosch.detekt:detekt-test", version # https://github.com/matrix-org/matrix-rust-components-kotlin/commits/main/sdk/sdk-android/src/main/kotlin/org/matrix/rustcomponents/sdk/matrix_sdk_ffi.kt # All new features should not be implemented in the pull request that upgrades the version, developers should # only fix API breaks and may add some TODOs. -matrix_sdk = "org.matrix.rustcomponents:sdk-android:26.09.28" +# `strictly` because element-call-matrix is a published AAR compiled against one SDK version and declares +# it as a plain `require`: without this, a library built against a newer SDK would silently drag this app +# onto it. With it, this pin always wins, and a library that needs something newer fails at configuration +# time naming both versions instead of at runtime with NoSuchMethodError. +matrix_sdk = { module = "org.matrix.rustcomponents:sdk-android", version = { strictly = "26.09.28" } } rustls_platform_verifier = "org.rustls:rustls-platform-verifier:0.2.0" # Others @@ -236,6 +242,13 @@ metro_runtime = { module = "dev.zacsweers.metro:runtime", version.ref = "metro" # Element Call element_call_embedded = "io.element.android:element-call-embedded:0.26.0" +# The native MatrixRTC call component. Versions come from the BOM, so every artifact moves together. +element_call_bom = { module = "io.element.android:element-call-bom", version.ref = "element_call" } +element_call_api = { module = "io.element.android:element-call-api" } +element_call_impl = { module = "io.element.android:element-call" } +element_call_ui = { module = "io.element.android:element-call-ui" } +element_call_matrix = { module = "io.element.android:element-call-matrix" } +element_call_test = { module = "io.element.android:element-call-test" } # Auto services google_autoservice = { module = "com.google.auto.service:auto-service", version.ref = "autoservice" } diff --git a/settings.gradle.kts b/settings.gradle.kts index e46d80f4f2..db3aa099a4 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -57,6 +57,48 @@ dependencyResolutionManagement { flatDir { dirs("libraries/matrix/libs") } + // Temporary: the native call component and the matrix-rust-rtc core publish as GitHub release + // assets, which carry full Maven metadata. Both blocks go once they reach Maven Central. + ivy { + url = uri("https://github.com/element-hq/element-call-android/releases/download") + patternLayout { artifact("v[revision]/[artifact]-[revision](-[classifier])(.[ext])") } + metadataSources { gradleMetadata() } + content { + // Per module: `element-call.*` would also catch element-call-embedded, the WebView call. + includeModule("io.element.android", "element-call-bom") + includeModule("io.element.android", "element-call-api") + includeModule("io.element.android", "element-call") + includeModule("io.element.android", "element-call-ui") + includeModule("io.element.android", "element-call-matrix") + includeModule("io.element.android", "element-call-test") + } + } + ivy { + url = uri("https://github.com/element-hq/matrix-rust-rtc/releases/download") + patternLayout { artifact("v[revision]/[artifact]-[revision](-[classifier])(.[ext])") } + // The core ships an AAR and nothing else; element-call's module metadata names it with an + // explicit aar artifact selector, so it needs no POM. + metadataSources { artifact() } + content { includeModule("io.element.android", "matrix-rtc-android") } + } + // Also temporary: testing an unreleased element-call-android build. Publish it there with + // `publishToMavenLocal -PVERSION_NAME=0.1.0-local`, then build here with + // `-PelementCallLocalVersion=0.1.0-local`. Absent without the property, so that everything + // resolves for a contributor who has never built the library. + providers.gradleProperty("elementCallLocalVersion").orNull?.let { elementCallVersion -> + logger.lifecycle("Note: resolving io.element.android:element-call-* $elementCallVersion from mavenLocal") + mavenLocal { + content { + // Per module: compound-android and element-call-embedded stay on Maven Central. + includeModule("io.element.android", "element-call-bom") + includeModule("io.element.android", "element-call-api") + includeModule("io.element.android", "element-call") + includeModule("io.element.android", "element-call-ui") + includeModule("io.element.android", "element-call-matrix") + includeModule("io.element.android", "element-call-test") + } + } + } } } From 43a13646717b408d7bf2edd6545b2ded5396a933 Mon Sep 17 00:00:00 2001 From: Valere Date: Tue, 22 Sep 2026 21:04:04 +0200 Subject: [PATCH 10/20] Give the native call component its view of Matrix The factory lives in libraries/matrix/impl because that is the only module compiled against the Rust SDK, and is app-scoped because a call is started for whichever account it belongs to rather than the one on screen. Co-Authored-By: Claude Opus 5 (1M context) --- features/callnative/api/build.gradle.kts | 12 +++++- .../api/ElementCallTransportFactory.kt | 26 +++++++++++++ libraries/matrix/impl/build.gradle.kts | 35 ++++++++++++++++- .../di/DefaultElementCallTransportFactory.kt | 38 +++++++++++++++++++ 4 files changed, 108 insertions(+), 3 deletions(-) create mode 100644 features/callnative/api/src/main/kotlin/io/element/android/features/callnative/api/ElementCallTransportFactory.kt create mode 100644 libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/di/DefaultElementCallTransportFactory.kt diff --git a/features/callnative/api/build.gradle.kts b/features/callnative/api/build.gradle.kts index 73c478ccb6..81e5976627 100644 --- a/features/callnative/api/build.gradle.kts +++ b/features/callnative/api/build.gradle.kts @@ -13,8 +13,18 @@ android { namespace = "io.element.android.features.callnative.api" } +// Temporary: `-PelementCallLocalVersion` points this at a locally published build of +// element-call-android. Goes with the repositories in settings.gradle.kts. +val elementCallVersion: String = providers.gradleProperty("elementCallLocalVersion") + .getOrElse(libs.versions.element.call.get()) + dependencies { - // api, not implementation: CallData is part of NativeCallEntryPoint's signature. + // api, not implementation: ElementCallMatrixTransport is part of ElementCallTransportFactory's + // signature, and CallData of NativeCallEntryPoint's. + // api, so that consumers of this module inherit the versions the BOM pins - the artifact + // below is declared without one. + api(platform("io.element.android:element-call-bom:$elementCallVersion")) + api(libs.element.call.api) api(projects.features.call.api) implementation(projects.libraries.architecture) implementation(projects.libraries.matrix.api) diff --git a/features/callnative/api/src/main/kotlin/io/element/android/features/callnative/api/ElementCallTransportFactory.kt b/features/callnative/api/src/main/kotlin/io/element/android/features/callnative/api/ElementCallTransportFactory.kt new file mode 100644 index 0000000000..bda31738f0 --- /dev/null +++ b/features/callnative/api/src/main/kotlin/io/element/android/features/callnative/api/ElementCallTransportFactory.kt @@ -0,0 +1,26 @@ +/* + * 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.callnative.api + +import io.element.android.call.api.matrix.ElementCallMatrixTransport +import io.element.android.libraries.matrix.api.MatrixClient + +/** + * Makes the native call's view of Matrix for a session. + * + * The component's turnkey transport is built over a raw Rust SDK client, and only + * `libraries/matrix/impl` is compiled against the SDK. This is the seam that keeps it that way: the + * implementation lives there, everything else takes a [MatrixClient] and asks. + * + * A factory rather than a binding for the transport itself, because the call is started from app scope + * - a call can be placed for an account that is not the one on screen - and the session it belongs to + * is known only as an id until a client is fetched for it. + */ +fun interface ElementCallTransportFactory { + fun create(client: MatrixClient): ElementCallMatrixTransport +} diff --git a/libraries/matrix/impl/build.gradle.kts b/libraries/matrix/impl/build.gradle.kts index 13920980a2..6ef7891e10 100644 --- a/libraries/matrix/impl/build.gradle.kts +++ b/libraries/matrix/impl/build.gradle.kts @@ -20,16 +20,47 @@ android { setupDependencyInjection() +// Temporary: `-PelementCallLocalVersion` points this at a locally published build of +// element-call-android, because the library has no remote repository yet. Goes with the mavenLocal +// block in settings.gradle.kts. Only the BOM carries a version; every other artifact follows it. +val elementCallVersion: String = providers.gradleProperty("elementCallLocalVersion") + .getOrElse(libs.versions.element.call.get()) + +val usesLocalRustSdk = file("${rootDir.path}/libraries/rustsdk/matrix-rust-sdk.aar").exists() + +if (usesLocalRustSdk) { + // element-call-matrix declares the Rust SDK as an ordinary Maven dependency, so with the local + // AAR in place a debug classpath would carry two copies of org.matrix.rustcomponents.sdk. + // Point the library's dependency at the same local AAR the app is using. + configurations.matching { it.name.startsWith("debug") }.configureEach { + resolutionStrategy.dependencySubstitution { + substitute(module("org.matrix.rustcomponents:sdk-android")) + .using(project(":libraries:rustsdk")) + .because("the local Rust SDK AAR replaces the published one on debug builds") + } + } +} + dependencies { releaseImplementation(libs.matrix.sdk) - if (file("${rootDir.path}/libraries/rustsdk/matrix-rust-sdk.aar").exists()) { - println("\nNote: Using local binary of the Rust SDK.\n") + if (usesLocalRustSdk) { + println( + "\nNote: Using local binary of the Rust SDK." + + "\n element-call-matrix is substituted onto it as well, so the native call runs" + + "\n against this SDK rather than the one it was published against.\n" + ) debugImplementation(projects.libraries.rustsdk) } else { debugImplementation(libs.matrix.sdk) } implementation(variantOf(libs.rustls.platform.verifier) { artifactType("aar") }) + // The native call's Matrix transport. Only this module may hand it a raw SDK client. + implementation(platform("io.element.android:element-call-bom:$elementCallVersion")) + implementation(libs.element.call.api) + implementation(projects.features.callnative.api) + implementation(libs.element.call.matrix) + implementation(projects.appconfig) implementation(projects.features.enterprise.api) implementation(projects.libraries.androidutils) diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/di/DefaultElementCallTransportFactory.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/di/DefaultElementCallTransportFactory.kt new file mode 100644 index 0000000000..14b59b14e6 --- /dev/null +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/di/DefaultElementCallTransportFactory.kt @@ -0,0 +1,38 @@ +/* + * 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.matrix.impl.di + +import dev.zacsweers.metro.AppScope +import dev.zacsweers.metro.ContributesBinding +import io.element.android.call.api.matrix.ElementCallMatrixTransport +import io.element.android.call.matrix.ElementCallSdkTransport +import io.element.android.features.callnative.api.ElementCallTransportFactory +import io.element.android.libraries.matrix.api.MatrixClient +import io.element.android.libraries.matrix.impl.RustMatrixClient + +/** + * Gives the native call component its view of Matrix. + * + * Here because this is the only module in the app compiled against the Rust SDK, and the component's + * turnkey transport needs a raw SDK client. No feature module ever sees the SDK, the rule that no + * feature depends on `libraries/matrix/impl` still holds, and the SDK version contract is checked where + * the app's own SDK pin lives. + * + * The transport is built on the client's own coroutine scope, which is what ties it to the session: the + * component has no shutdown of its own, so cancelling that scope at logout is the only thing that stops + * the RTC core and its to-device subscription. + */ +@ContributesBinding(AppScope::class) +class DefaultElementCallTransportFactory : ElementCallTransportFactory { + override fun create(client: MatrixClient): ElementCallMatrixTransport { + return ElementCallSdkTransport( + client = (client as RustMatrixClient).innerClient, + sessionScope = client.sessionCoroutineScope, + ) + } +} From 40cb43ae875678ee80712375260a6812d000dc4c Mon Sep 17 00:00:00 2001 From: Valere Date: Tue, 22 Sep 2026 21:04:05 +0200 Subject: [PATCH 11/20] Build the native call stack on demand for each session Keyed by session id in app scope rather than session-scoped, because answering a call notification after a cold start has no session graph to reach a session-scoped stack from. Co-Authored-By: Claude Opus 5 (1M context) --- features/callnative/impl/build.gradle.kts | 23 +++ .../callnative/impl/ElementCallControllers.kt | 164 ++++++++++++++++++ .../impl/ElementXCallLifecycleListener.kt | 62 +++++++ .../impl/ElementXRoomContextProvider.kt | 74 ++++++++ .../impl/ElementXCallLifecycleListenerTest.kt | 84 +++++++++ 5 files changed, 407 insertions(+) create mode 100644 features/callnative/impl/src/main/kotlin/io/element/android/features/callnative/impl/ElementCallControllers.kt create mode 100644 features/callnative/impl/src/main/kotlin/io/element/android/features/callnative/impl/ElementXCallLifecycleListener.kt create mode 100644 features/callnative/impl/src/main/kotlin/io/element/android/features/callnative/impl/ElementXRoomContextProvider.kt create mode 100644 features/callnative/impl/src/test/kotlin/io/element/android/features/callnative/impl/ElementXCallLifecycleListenerTest.kt diff --git a/features/callnative/impl/build.gradle.kts b/features/callnative/impl/build.gradle.kts index 26c9eabae3..8cfee12e0b 100644 --- a/features/callnative/impl/build.gradle.kts +++ b/features/callnative/impl/build.gradle.kts @@ -17,13 +17,36 @@ android { setupDependencyInjection() +// Temporary: `-PelementCallLocalVersion` points this at a locally published build of +// element-call-android, because the library has no remote repository yet. Goes with the mavenLocal +// block in settings.gradle.kts. Only the BOM carries a version; every other artifact follows it. +val elementCallVersion: String = providers.gradleProperty("elementCallLocalVersion") + .getOrElse(libs.versions.element.call.get()) + dependencies { api(projects.features.callnative.api) + implementation(platform("io.element.android:element-call-bom:$elementCallVersion")) + implementation(libs.element.call.api) + implementation(libs.element.call.impl) + implementation(projects.features.call.api) implementation(projects.libraries.architecture) implementation(projects.libraries.di) + implementation(projects.libraries.featureflag.api) implementation(projects.libraries.matrix.api) + implementation(projects.libraries.preferences.api) + // For NotificationIdProvider: the ongoing-call notification id is shared with the WebView call's + // foreground service, so that the two paths cannot leave two call notifications in the shade. + implementation(projects.libraries.push.api) + implementation(projects.services.appnavstate.api) + implementation(libs.androidx.corektx) testCommonDependencies(libs) + testImplementation(libs.element.call.test) + testImplementation(projects.features.call.test) + testImplementation(projects.libraries.featureflag.test) + testImplementation(projects.libraries.matrix.test) + testImplementation(projects.libraries.preferences.test) + testImplementation(projects.services.appnavstate.test) } diff --git a/features/callnative/impl/src/main/kotlin/io/element/android/features/callnative/impl/ElementCallControllers.kt b/features/callnative/impl/src/main/kotlin/io/element/android/features/callnative/impl/ElementCallControllers.kt new file mode 100644 index 0000000000..0cb50d2038 --- /dev/null +++ b/features/callnative/impl/src/main/kotlin/io/element/android/features/callnative/impl/ElementCallControllers.kt @@ -0,0 +1,164 @@ +/* + * 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.callnative.impl + +import android.content.Context +import dev.zacsweers.metro.AppScope +import dev.zacsweers.metro.ContributesBinding +import dev.zacsweers.metro.SingleIn +import io.element.android.call.api.ElementCallController +import io.element.android.call.api.ElementCallNotificationConfig +import io.element.android.call.api.ElementCallOptions +import io.element.android.call.api.rtc.MatrixRtcLogLevel +import io.element.android.call.api.rtc.MatrixRtcLoggingConfiguration +import io.element.android.call.impl.ElementCallStack +import io.element.android.features.call.api.CurrentCallTracker +import io.element.android.features.call.api.RingingCallTracker +import io.element.android.features.callnative.api.ElementCallTransportFactory +import io.element.android.libraries.di.annotations.ApplicationContext +import io.element.android.libraries.featureflag.api.FeatureFlagService +import io.element.android.libraries.featureflag.api.FeatureFlags +import io.element.android.libraries.matrix.api.MatrixClient +import io.element.android.libraries.matrix.api.MatrixClientProvider +import io.element.android.libraries.matrix.api.core.SessionId +import io.element.android.libraries.matrix.api.tracing.LogLevel +import io.element.android.libraries.preferences.api.store.AppPreferencesStore +import io.element.android.libraries.push.api.notifications.ForegroundServiceType +import io.element.android.libraries.push.api.notifications.NotificationIdProvider +import io.element.android.services.appnavstate.api.AppForegroundStateService +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.job +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock +import timber.log.Timber +import java.util.concurrent.ConcurrentHashMap + +/** + * The native call controller of a session, built on demand. + * + * An interface so that what starts calls can be tested without the real stack, which loads the RTC + * native library. + */ +interface ElementCallControllers { + /** + * The session's controller, building its call stack on first use. Null when there is no such + * session - logged out, or a session id that cannot be restored. + */ + suspend fun getOrBuild(sessionId: SessionId): ElementCallController? + + /** + * The controller of whichever session currently has a call, or null when none does. + * + * For the Activity-level seams, which are app-scoped and have no session of their own to ask. Only + * sessions whose stack is already built are considered, so asking cannot start one. + */ + fun withRunningCall(): ElementCallController? +} + +/** + * Builds one call stack per session, from a Matrix client, and keeps it for the session's life. + * + * App-scoped and keyed by session id rather than session-scoped, because everything that starts a call + * is app-scoped: the push path that resolves a ringing call, the notification it posts, and the Activity + * that answers it all run on a [MatrixClient] fetched by id, and none of them has a session graph. A + * session-scoped stack could only be reached after the user had navigated into the session, so answering + * a call notification after a cold start would find nothing to answer with. + * + * One stack serves every call in a session; each call opens and closes its own room underneath, which + * the component does itself. + */ +@SingleIn(AppScope::class) +@ContributesBinding(AppScope::class) +class DefaultElementCallControllers( + @ApplicationContext private val context: Context, + private val matrixClientProvider: MatrixClientProvider, + private val transportFactory: ElementCallTransportFactory, + private val currentCallTracker: CurrentCallTracker, + private val ringingCallTracker: RingingCallTracker, + private val appForegroundStateService: AppForegroundStateService, + private val appPreferencesStore: AppPreferencesStore, + private val featureFlagService: FeatureFlagService, +) : ElementCallControllers { + private val stacks = ConcurrentHashMap() + + // One at a time across every session: two calls placed at once must not race into two stacks for + // the same session, because only one of them would ever be closed. + private val mutex = Mutex() + + override suspend fun getOrBuild(sessionId: SessionId): ElementCallController? = mutex.withLock { + stacks[sessionId]?.let { return@withLock it.controller } + val client = matrixClientProvider.getOrRestore(sessionId).getOrNull() + if (client == null) { + Timber.w("NativeCall: no client for $sessionId, cannot start a call") + return@withLock null + } + build(client).also { stack -> + stacks[sessionId] = stack + // The client's scope is the stack's lifetime: the component has no shutdown of its own, so + // cancelling this at logout is what stops the RTC core. Closing here only releases the + // component's own reference, so a dead stack is not left behind it. + client.sessionCoroutineScope.coroutineContext.job.invokeOnCompletion { + stacks.remove(sessionId, stack) + stack.close() + } + }.controller + } + + override fun withRunningCall(): ElementCallController? = + stacks.values.firstNotNullOfOrNull { stack -> + stack.controller.takeIf { it.state.value != null } + } + + private suspend fun build(client: MatrixClient): ElementCallStack = + ElementCallStack.Builder(context, transportFactory.create(client)) + .lifecycleListener( + ElementXCallLifecycleListener( + sessionId = client.sessionId, + currentCallTracker = currentCallTracker, + ringingCallTracker = ringingCallTracker, + sessionCoroutineScope = client.sessionCoroutineScope, + appForegroundStateService = appForegroundStateService, + ) + ) + .roomContext(ElementXRoomContextProvider(client)) + .options(options()) + .build(scope = client.sessionCoroutineScope) + + private suspend fun options() = ElementCallOptions( + logging = loggingConfiguration(), + notification = ElementCallNotificationConfig( + // The same id the WebView call's foreground service uses, so that the two paths cannot + // leave two ongoing-call notifications in the shade. + notificationId = NotificationIdProvider.getForegroundServiceNotificationId(ForegroundServiceType.ONGOING_CALL), + ), + ) + + /** + * Read here rather than in `PlatformInitializer`, where the Rust SDK's tracing is configured: the + * core applies this when it first loads, which is when a call is first placed, and this is the first + * place where both the preference and the flag are readable. + */ + private suspend fun loggingConfiguration(): MatrixRtcLoggingConfiguration { + val logLevel = appPreferencesStore.getTracingLogLevelFlow().first() + return MatrixRtcLoggingConfiguration( + // Floored at debug: turning the SDK level down to quiet the timeline and event-cache + // traces is exactly what you do while working on calls, and it must not also silence the + // RTC core. The core's own filter holds down the parts that log per frame. + logLevel = maxOf(logLevel, LogLevel.DEBUG).toMatrixRtcLogLevel(), + writesToLogcat = featureFlagService.isFeatureEnabled(FeatureFlags.PrintLogsToLogcat), + ) + } + + private fun LogLevel.toMatrixRtcLogLevel(): MatrixRtcLogLevel = when (this) { + LogLevel.ERROR -> MatrixRtcLogLevel.ERROR + LogLevel.WARN -> MatrixRtcLogLevel.WARN + LogLevel.INFO -> MatrixRtcLogLevel.INFO + LogLevel.DEBUG -> MatrixRtcLogLevel.DEBUG + LogLevel.TRACE -> MatrixRtcLogLevel.TRACE + } +} diff --git a/features/callnative/impl/src/main/kotlin/io/element/android/features/callnative/impl/ElementXCallLifecycleListener.kt b/features/callnative/impl/src/main/kotlin/io/element/android/features/callnative/impl/ElementXCallLifecycleListener.kt new file mode 100644 index 0000000000..83ec22b392 --- /dev/null +++ b/features/callnative/impl/src/main/kotlin/io/element/android/features/callnative/impl/ElementXCallLifecycleListener.kt @@ -0,0 +1,62 @@ +/* + * 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.callnative.impl + +import io.element.android.call.api.ElementCallData +import io.element.android.call.api.ElementCallLifecycleListener +import io.element.android.features.call.api.CallData +import io.element.android.features.call.api.CurrentCall +import io.element.android.features.call.api.CurrentCallTracker +import io.element.android.features.call.api.RingingCallTracker +import io.element.android.libraries.matrix.api.core.RoomId +import io.element.android.libraries.matrix.api.core.SessionId +import io.element.android.services.appnavstate.api.AppForegroundStateService +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.launch + +/** + * Reports a native call back to the parts of Element X that already track calls. + * + * The library knows it is in a call; Element X's room list, timeline and ringing machinery each + * already know how to react to one. This joins the two, so that a native call looks like any other + * call to the rest of the app rather than needing every reader to learn about it. + */ +class ElementXCallLifecycleListener( + private val sessionId: SessionId, + private val currentCallTracker: CurrentCallTracker, + private val ringingCallTracker: RingingCallTracker, + private val sessionCoroutineScope: CoroutineScope, + appForegroundStateService: AppForegroundStateService, +) : ElementCallLifecycleListener { + override val isAppInForeground: Flow = appForegroundStateService.isInForeground + + override fun onCallStarted(callData: ElementCallData) { + currentCallTracker.onCallStarted(CurrentCall.RoomCall(callData.roomId())) + } + + override fun onCallJoined(callData: ElementCallData) { + // The ringing side is suspending and these callbacks are not, so the work is handed to the + // session scope rather than blocking the controller's. It outlives the call on purpose: a + // ring still has to be taken down even if the call ends while this is in flight. + sessionCoroutineScope.launch { ringingCallTracker.onCallJoined(callData.toCallData()) } + } + + override fun onCallEnded(callData: ElementCallData?) { + currentCallTracker.onCallEnded() + callData?.let { sessionCoroutineScope.launch { ringingCallTracker.onCallEnded(it.toCallData()) } } + } + + private fun ElementCallData.roomId() = RoomId(roomId.value) + + private fun ElementCallData.toCallData() = CallData( + sessionId = sessionId, + roomId = roomId(), + isAudioCall = isAudioCall, + ) +} diff --git a/features/callnative/impl/src/main/kotlin/io/element/android/features/callnative/impl/ElementXRoomContextProvider.kt b/features/callnative/impl/src/main/kotlin/io/element/android/features/callnative/impl/ElementXRoomContextProvider.kt new file mode 100644 index 0000000000..aed2f97725 --- /dev/null +++ b/features/callnative/impl/src/main/kotlin/io/element/android/features/callnative/impl/ElementXRoomContextProvider.kt @@ -0,0 +1,74 @@ +/* + * 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.callnative.impl + +import io.element.android.call.api.ElementCallRoomContext +import io.element.android.call.api.ElementCallRoomContextProvider +import io.element.android.call.api.ElementCallRoomMember +import io.element.android.libraries.matrix.api.MatrixClient +import io.element.android.libraries.matrix.api.room.RoomMembersState +import io.element.android.libraries.matrix.api.room.roomMembers +import kotlinx.coroutines.coroutineScope +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.emitAll +import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.launch +import timber.log.Timber +import io.element.android.call.api.rtc.id.RoomId as ElementCallRoomId +import io.element.android.call.api.rtc.id.UserId as ElementCallUserId +import io.element.android.libraries.matrix.api.core.RoomId as MatrixRoomId + +/** + * Tells the call what the room is called and who is in it. + * + * The RTC layer knows a participant only as a member id and a user id - it has no reason to know + * what anyone is called or what they look like. Element X does, so the join happens here rather than + * in the call UI, and it goes through Element X's room cache so that names and avatars are the same + * ones the rest of the app shows. The library ships a provider of its own that reads the SDK + * directly; it is not used, because it would miss exactly that cache. + */ +class ElementXRoomContextProvider( + private val matrixClient: MatrixClient, +) : ElementCallRoomContextProvider { + override fun roomContext(roomId: ElementCallRoomId): Flow = flow { + val room = matrixClient.getRoom(MatrixRoomId(roomId.value)) + if (room == null) { + // Emits nothing rather than guessing: the call then shows user ids, which is better than + // a name we made up. + Timber.w("NativeCall: no room for ${roomId.value}, the call will show user ids") + return@flow + } + room.use { + coroutineScope { + // The member list is loaded lazily, and a call is exactly the moment it is needed: + // without this the tiles would show user ids until something else happened to ask. + // Launched rather than awaited so the room name still arrives immediately. + launch { room.updateMembers() } + emitAll( + combine(room.roomInfoFlow, room.membersStateFlow) { info, membersState -> + ElementCallRoomContext( + displayName = info.name, + isDm = info.isDm, + members = membersState.toElementCallMembers(), + ) + } + ) + } + } + } + + private fun RoomMembersState.toElementCallMembers(): Map = + roomMembers().orEmpty().associate { member -> + ElementCallUserId(member.userId.value) to ElementCallRoomMember( + userId = ElementCallUserId(member.userId.value), + displayName = member.displayName, + avatarUrl = member.avatarUrl, + ) + } +} diff --git a/features/callnative/impl/src/test/kotlin/io/element/android/features/callnative/impl/ElementXCallLifecycleListenerTest.kt b/features/callnative/impl/src/test/kotlin/io/element/android/features/callnative/impl/ElementXCallLifecycleListenerTest.kt new file mode 100644 index 0000000000..6ef057e512 --- /dev/null +++ b/features/callnative/impl/src/test/kotlin/io/element/android/features/callnative/impl/ElementXCallLifecycleListenerTest.kt @@ -0,0 +1,84 @@ +/* + * 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.callnative.impl + +import com.google.common.truth.Truth.assertThat +import io.element.android.call.api.ElementCallData +import io.element.android.call.api.rtc.id.RoomId +import io.element.android.features.call.api.CurrentCall +import io.element.android.features.call.test.FakeCurrentCallTracker +import io.element.android.features.call.test.FakeRingingCallTracker +import io.element.android.libraries.matrix.test.A_ROOM_ID +import io.element.android.libraries.matrix.test.A_SESSION_ID +import io.element.android.services.appnavstate.test.FakeAppForegroundStateService +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.runCurrent +import kotlinx.coroutines.test.runTest +import org.junit.Test + +@OptIn(ExperimentalCoroutinesApi::class) +class ElementXCallLifecycleListenerTest { + @Test + fun `a started call is reported as the current call, and cleared when it ends`() = runTest { + val currentCallTracker = FakeCurrentCallTracker() + val listener = createListener(currentCallTracker = currentCallTracker) + + listener.onCallStarted(A_CALL) + listener.onCallEnded(A_CALL) + + assertThat(currentCallTracker.calls).containsExactly( + CurrentCall.RoomCall(A_ROOM_ID), + CurrentCall.None, + ).inOrder() + } + + @Test + fun `joining a call stops the ring`() = runTest { + val ringingCallTracker = FakeRingingCallTracker() + val listener = createListener(ringingCallTracker = ringingCallTracker) + + listener.onCallJoined(A_CALL) + runCurrent() + + assertThat(ringingCallTracker.joined.map { it.roomId }).containsExactly(A_ROOM_ID) + assertThat(ringingCallTracker.ended).isEmpty() + } + + @Test + fun `ending a call tells the ringing side, and survives a call that never had data`() = runTest { + val ringingCallTracker = FakeRingingCallTracker() + val currentCallTracker = FakeCurrentCallTracker() + val listener = createListener( + currentCallTracker = currentCallTracker, + ringingCallTracker = ringingCallTracker, + ) + + listener.onCallEnded(null) + runCurrent() + + // The current call is always cleared; only the ring needs to know which call it was. + assertThat(currentCallTracker.calls).containsExactly(CurrentCall.None) + assertThat(ringingCallTracker.ended).isEmpty() + } + + private fun TestScope.createListener( + currentCallTracker: FakeCurrentCallTracker = FakeCurrentCallTracker(), + ringingCallTracker: FakeRingingCallTracker = FakeRingingCallTracker(), + ) = ElementXCallLifecycleListener( + sessionId = A_SESSION_ID, + currentCallTracker = currentCallTracker, + ringingCallTracker = ringingCallTracker, + sessionCoroutineScope = this, + appForegroundStateService = FakeAppForegroundStateService(), + ) + + private companion object { + val A_CALL = ElementCallData(roomId = RoomId(A_ROOM_ID.value), isAudioCall = true) + } +} From 75605f19328532fd5c2ded37031f3118280d6fd5 Mon Sep 17 00:00:00 2001 From: Valere Date: Tue, 22 Sep 2026 21:04:06 +0200 Subject: [PATCH 12/20] Make a call with no UI actually run, and cover it with tests Found on a device: with no UI to answer the microphone permission or unset the maximized snapshot, the call parks forever and blanks the screen through the proximity wake lock. Co-Authored-By: Claude Opus 5 (1M context) --- .../callnative/api/NativeCallEntryPoint.kt | 2 +- .../impl/DefaultNativeCallEntryPoint.kt | 54 ++++++++-- .../impl/DefaultNativeCallEntryPointTest.kt | 101 ++++++++++++++++++ .../impl/FakeElementCallControllers.kt | 29 +++++ 4 files changed, 178 insertions(+), 8 deletions(-) create mode 100644 features/callnative/impl/src/test/kotlin/io/element/android/features/callnative/impl/DefaultNativeCallEntryPointTest.kt create mode 100644 features/callnative/impl/src/test/kotlin/io/element/android/features/callnative/impl/FakeElementCallControllers.kt diff --git a/features/callnative/api/src/main/kotlin/io/element/android/features/callnative/api/NativeCallEntryPoint.kt b/features/callnative/api/src/main/kotlin/io/element/android/features/callnative/api/NativeCallEntryPoint.kt index a964c1502a..486a0b14c1 100644 --- a/features/callnative/api/src/main/kotlin/io/element/android/features/callnative/api/NativeCallEntryPoint.kt +++ b/features/callnative/api/src/main/kotlin/io/element/android/features/callnative/api/NativeCallEntryPoint.kt @@ -19,6 +19,6 @@ import io.element.android.features.call.api.CallData * There is nothing behind this seam yet - the default implementation does nothing and says so. The * native stack that implements it is a separate library, added in a follow-up. */ -interface NativeCallEntryPoint { +fun interface NativeCallEntryPoint { fun startCall(callData: CallData) } diff --git a/features/callnative/impl/src/main/kotlin/io/element/android/features/callnative/impl/DefaultNativeCallEntryPoint.kt b/features/callnative/impl/src/main/kotlin/io/element/android/features/callnative/impl/DefaultNativeCallEntryPoint.kt index ecfdafd76f..4eb5b69418 100644 --- a/features/callnative/impl/src/main/kotlin/io/element/android/features/callnative/impl/DefaultNativeCallEntryPoint.kt +++ b/features/callnative/impl/src/main/kotlin/io/element/android/features/callnative/impl/DefaultNativeCallEntryPoint.kt @@ -7,23 +7,63 @@ package io.element.android.features.callnative.impl +import android.Manifest +import android.content.Context +import android.content.pm.PackageManager +import androidx.core.content.ContextCompat import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesBinding +import io.element.android.call.api.ElementCallData +import io.element.android.call.api.rtc.id.RoomId import io.element.android.features.call.api.CallData import io.element.android.features.callnative.api.NativeCallEntryPoint +import io.element.android.libraries.di.annotations.AppCoroutineScope +import io.element.android.libraries.di.annotations.ApplicationContext +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch import timber.log.Timber /** - * What answers [NativeCallEntryPoint] until the native MatrixRTC stack is wired in: nothing. + * Starts a call on the native MatrixRTC stack. * - * The seam exists so that `DefaultElementCallEntryPoint` can fork on `FeatureFlags.NativeCall` today, - * and so that adding the stack later is a change to this module alone. A user who turns the flag on - * before then gets no call and a line in the log, which is the honest outcome - falling back to the - * WebView would make the flag look broken rather than unimplemented. + * App-scoped, while the stack it drives is per session: the session comes from the call rather than + * from whatever is on screen, so answering a notification for a second account reaches that account's + * stack. */ @ContributesBinding(AppScope::class) -class DefaultNativeCallEntryPoint : NativeCallEntryPoint { +class DefaultNativeCallEntryPoint( + @ApplicationContext private val context: Context, + private val controllers: ElementCallControllers, + @AppCoroutineScope private val appCoroutineScope: CoroutineScope, +) : NativeCallEntryPoint { override fun startCall(callData: CallData) { - Timber.w("Native call requested for ${callData.roomId} but no native call stack is present in this build") + // The call does nothing past "requested" until the microphone permission is answered, and + // only an Activity can ask for one. Until the call has a UI of its own, the only answer + // available is the permission the app already holds - so check it before starting rather + // than after. Starting a call nobody can answer leaves it running and refusing every later + // attempt for the rest of the session, with nothing on screen to hang it up. + if (!hasMicrophonePermission()) { + Timber.w("NativeCall: RECORD_AUDIO is not granted and there is no call UI to ask for it, not starting the call") + return + } + appCoroutineScope.launch { + val controller = controllers.getOrBuild(callData.sessionId) ?: return@launch + controller.startCall( + ElementCallData( + roomId = RoomId(callData.roomId.value), + isAudioCall = callData.isAudioCall, + ) + ) + controller.setMicrophonePermissionGranted(true) + // A call with no UI is never the screen the user is looking at, and the snapshot assumes + // it is. Saying so matters for more than tidiness: the component holds a proximity wake + // lock while a maximized audio call is on screen, so leaving the default in place blanks + // the display whenever a hand goes near the top of the phone - including when reaching + // for the notification shade, which is the only way to hang this call up. + controller.setMaximized(false) + } } + + private fun hasMicrophonePermission(): Boolean = + ContextCompat.checkSelfPermission(context, Manifest.permission.RECORD_AUDIO) == PackageManager.PERMISSION_GRANTED } diff --git a/features/callnative/impl/src/test/kotlin/io/element/android/features/callnative/impl/DefaultNativeCallEntryPointTest.kt b/features/callnative/impl/src/test/kotlin/io/element/android/features/callnative/impl/DefaultNativeCallEntryPointTest.kt new file mode 100644 index 0000000000..5afbda0e5b --- /dev/null +++ b/features/callnative/impl/src/test/kotlin/io/element/android/features/callnative/impl/DefaultNativeCallEntryPointTest.kt @@ -0,0 +1,101 @@ +/* + * 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.callnative.impl + +import android.Manifest +import android.app.Application +import androidx.test.platform.app.InstrumentationRegistry +import com.google.common.truth.Truth.assertThat +import io.element.android.call.test.FakeElementCallController +import io.element.android.features.call.api.CallData +import io.element.android.libraries.matrix.test.A_ROOM_ID +import io.element.android.libraries.matrix.test.A_SESSION_ID +import io.element.android.tests.testutils.robolectric.RobolectricTest +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.runCurrent +import kotlinx.coroutines.test.runTest +import org.junit.Test +import org.robolectric.RuntimeEnvironment +import org.robolectric.Shadows.shadowOf + +@OptIn(ExperimentalCoroutinesApi::class) +class DefaultNativeCallEntryPointTest : RobolectricTest() { + @Test + fun `starting a call answers the microphone permission the app already holds`() = runTest { + grantMicrophonePermission() + val controller = FakeElementCallController() + val entryPoint = createEntryPoint(controller) + + entryPoint.startCall(A_CALL_DATA) + runCurrent() + + assertThat(controller.startedCalls.map { it.roomId.value }).containsExactly(A_ROOM_ID.value) + // Without this the call parks at RequestingPermission forever, and because a call is then + // "running" every later attempt is refused with nothing on screen to hang it up. + assertThat(controller.microphonePermissionAnswers).containsExactly(true) + } + + @Test + fun `a call with no UI is not maximized`() = runTest { + grantMicrophonePermission() + val controller = FakeElementCallController() + val entryPoint = createEntryPoint(controller) + + entryPoint.startCall(A_CALL_DATA) + runCurrent() + + // The snapshot assumes a call is on screen. While there is no call UI it never is, and the + // component holds a proximity wake lock for a maximized audio call - which blanks the display + // whenever a hand goes near the top of the phone, including reaching for the shade. + assertThat(controller.maximizedCalls).containsExactly(false) + } + + @Test + fun `no call is started when the microphone permission is missing`() = runTest { + shadowOf(RuntimeEnvironment.getApplication()).denyPermissions(Manifest.permission.RECORD_AUDIO) + val controller = FakeElementCallController() + val entryPoint = createEntryPoint(controller) + + entryPoint.startCall(A_CALL_DATA) + runCurrent() + + // Nothing to ask with, so nothing is started: a call left unanswered would wedge the + // controller for the rest of the session. + assertThat(controller.startedCalls).isEmpty() + } + + @Test + fun `nothing happens for a session that cannot be restored`() = runTest { + grantMicrophonePermission() + val controller = FakeElementCallController() + val entryPoint = createEntryPoint(controller, sessionCanBeRestored = false) + + entryPoint.startCall(A_CALL_DATA) + runCurrent() + + assertThat(controller.startedCalls).isEmpty() + } + + private fun grantMicrophonePermission() { + shadowOf(RuntimeEnvironment.getApplication()).grantPermissions(Manifest.permission.RECORD_AUDIO) + } + + private fun TestScope.createEntryPoint( + controller: FakeElementCallController, + sessionCanBeRestored: Boolean = true, + ) = DefaultNativeCallEntryPoint( + context = InstrumentationRegistry.getInstrumentation().targetContext as Application, + controllers = FakeElementCallControllers(controller.takeIf { sessionCanBeRestored }), + appCoroutineScope = this, + ) + + private companion object { + val A_CALL_DATA = CallData(sessionId = A_SESSION_ID, roomId = A_ROOM_ID, isAudioCall = true) + } +} diff --git a/features/callnative/impl/src/test/kotlin/io/element/android/features/callnative/impl/FakeElementCallControllers.kt b/features/callnative/impl/src/test/kotlin/io/element/android/features/callnative/impl/FakeElementCallControllers.kt new file mode 100644 index 0000000000..64cf425af6 --- /dev/null +++ b/features/callnative/impl/src/test/kotlin/io/element/android/features/callnative/impl/FakeElementCallControllers.kt @@ -0,0 +1,29 @@ +/* + * 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.callnative.impl + +import io.element.android.call.api.ElementCallController +import io.element.android.libraries.matrix.api.core.SessionId + +/** + * @param controller what [getOrBuild] answers with, or null to stand for a session that cannot be + * restored - logged out, or an id from a stale notification. + */ +class FakeElementCallControllers( + private val controller: ElementCallController? = null, +) : ElementCallControllers { + val requestedSessions = mutableListOf() + + override suspend fun getOrBuild(sessionId: SessionId): ElementCallController? { + requestedSessions += sessionId + return controller + } + + override fun withRunningCall(): ElementCallController? = + controller?.takeIf { it.state.value != null } +} From e35b522a9a9c7b48236082cd272c7c6f7ce6fa2f Mon Sep 17 00:00:00 2001 From: Valere Date: Tue, 22 Sep 2026 21:04:06 +0200 Subject: [PATCH 13/20] Answer a ringing call through the call entry point The notification's Answer action named ElementCallActivity outright, so answering always opened the WebView whatever FeatureFlags.NativeCall was set to. Co-Authored-By: Claude Opus 5 (1M context) --- .../RingingCallNotificationCreator.kt | 34 ++++++++++----- .../call/impl/ui/IncomingCallActivity.kt | 42 +++++++++++++++++++ .../RingingCallNotificationCreatorTest.kt | 31 ++++++++++++++ 3 files changed, 97 insertions(+), 10 deletions(-) diff --git a/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/notifications/RingingCallNotificationCreator.kt b/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/notifications/RingingCallNotificationCreator.kt index 647f5ebcfc..78b08e4468 100644 --- a/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/notifications/RingingCallNotificationCreator.kt +++ b/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/notifications/RingingCallNotificationCreator.kt @@ -18,10 +18,8 @@ import androidx.core.app.PendingIntentCompat import androidx.core.app.Person import dev.zacsweers.metro.Inject import io.element.android.appconfig.ElementCallConfig -import io.element.android.features.call.api.CallData import io.element.android.features.call.impl.receivers.DeclineCallBroadcastReceiver import io.element.android.features.call.impl.ui.IncomingCallActivity -import io.element.android.features.call.impl.utils.IntentProvider import io.element.android.libraries.designsystem.components.avatar.AvatarData import io.element.android.libraries.designsystem.components.avatar.AvatarSize import io.element.android.libraries.designsystem.utils.CommonDrawables @@ -55,6 +53,15 @@ class RingingCallNotificationCreator( * Request code for the full screen intent. */ const val FULL_SCREEN_INTENT_REQUEST_CODE = 2 + + /** + * Request code for the answer action. + * + * Distinct from [FULL_SCREEN_INTENT_REQUEST_CODE] even though both now target + * `IncomingCallActivity`: they carry different extras, and a shared request code would let + * `FLAG_CANCEL_CURRENT` on one replace the other. + */ + const val ANSWER_REQUEST_CODE = 3 } suspend fun createNotification( @@ -89,14 +96,6 @@ class RingingCallNotificationCreator( .setImportant(true) .build() - val answerIntent = IntentProvider.getPendingIntent( - context, - CallData( - sessionId = sessionId, - roomId = roomId, - isAudioCall = audioOnly, - ), - ) val notificationData = CallNotificationData( sessionId = sessionId, roomId = roomId, @@ -112,6 +111,21 @@ class RingingCallNotificationCreator( audioOnly = audioOnly, ) + // Through IncomingCallActivity rather than straight to ElementCallActivity, so that + // answering goes through ElementCallEntryPoint and lands in whichever call stack the + // NativeCall flag selects. Naming the WebView activity here meant the notification's Answer + // button always opened the WebView, even with native calls turned on. + val answerIntent = PendingIntentCompat.getActivity( + context, + ANSWER_REQUEST_CODE, + Intent(context, IncomingCallActivity::class.java).apply { + putExtra(IncomingCallActivity.EXTRA_NOTIFICATION_DATA, notificationData) + putExtra(IncomingCallActivity.EXTRA_ANSWER_IMMEDIATELY, true) + }, + PendingIntent.FLAG_CANCEL_CURRENT, + false, + )!! + val declineIntent = PendingIntentCompat.getBroadcast( context, DECLINE_REQUEST_CODE, diff --git a/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/ui/IncomingCallActivity.kt b/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/ui/IncomingCallActivity.kt index 1d6989fb3c..a4bb9eef31 100644 --- a/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/ui/IncomingCallActivity.kt +++ b/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/ui/IncomingCallActivity.kt @@ -8,6 +8,7 @@ package io.element.android.features.call.impl.ui +import android.content.Intent import android.os.Bundle import android.view.WindowManager import androidx.activity.compose.setContent @@ -47,6 +48,15 @@ class IncomingCallActivity : AppCompatActivity() { * Extra key for the notification data. */ const val EXTRA_NOTIFICATION_DATA = "EXTRA_NOTIFICATION_DATA" + + /** + * Answer straight away rather than showing the incoming-call screen. + * + * Set by the notification's Answer action, which has already asked the user - showing them + * the same question again would be the bug. The screen is for the full-screen intent, where + * the user has not answered anything yet. + */ + const val EXTRA_ANSWER_IMMEDIATELY = "EXTRA_ANSWER_IMMEDIATELY" } @Inject @@ -85,6 +95,7 @@ class IncomingCallActivity : AppCompatActivity() { ) val notificationData = intent?.let { IntentCompat.getParcelableExtra(it, EXTRA_NOTIFICATION_DATA, CallNotificationData::class.java) } + if (answerImmediatelyIfAsked(intent)) return if (notificationData != null) { setContent { val colors by remember { @@ -116,6 +127,37 @@ class IncomingCallActivity : AppCompatActivity() { .launchIn(lifecycleScope) } + /** + * The Answer action can arrive while this activity is already showing the ringing screen, because + * the full screen intent put it there and it is `singleTask`. That delivers a new intent instead + * of recreating the activity, so the answer has to be handled here as well as in [onCreate] - and + * [setIntent] has to run first, or everything reading `intent` afterwards would still see the one + * the activity was created with. + */ + override fun onNewIntent(intent: Intent) { + super.onNewIntent(intent) + setIntent(intent) + answerImmediatelyIfAsked(intent) + } + + /** + * Answers and finishes when the intent is the notification's Answer action, and reports whether + * it did. + * + * Answering goes through this activity rather than straight to a call screen so that it goes + * through `ElementCallEntryPoint`, which is what decides between the WebView and the native + * stack. The notification action used to name `ElementCallActivity` outright, so answering from + * the notification always opened the WebView however the flag was set. + */ + private fun answerImmediatelyIfAsked(intent: Intent?): Boolean { + if (intent == null || !intent.getBooleanExtra(EXTRA_ANSWER_IMMEDIATELY, false)) return false + val notificationData = IntentCompat.getParcelableExtra(intent, EXTRA_NOTIFICATION_DATA, CallNotificationData::class.java) + ?: return false + onAnswer(notificationData) + finish() + return true + } + private fun onAnswer(notificationData: CallNotificationData) { elementCallEntryPoint.startCall( CallData( diff --git a/features/call/impl/src/test/kotlin/io/element/android/features/call/notifications/RingingCallNotificationCreatorTest.kt b/features/call/impl/src/test/kotlin/io/element/android/features/call/notifications/RingingCallNotificationCreatorTest.kt index 3f5c1c4e50..3c072c5958 100644 --- a/features/call/impl/src/test/kotlin/io/element/android/features/call/notifications/RingingCallNotificationCreatorTest.kt +++ b/features/call/impl/src/test/kotlin/io/element/android/features/call/notifications/RingingCallNotificationCreatorTest.kt @@ -13,6 +13,7 @@ import androidx.test.platform.app.InstrumentationRegistry import coil3.ImageLoader import com.google.common.truth.Truth.assertThat import io.element.android.features.call.impl.notifications.RingingCallNotificationCreator +import io.element.android.features.call.impl.ui.IncomingCallActivity import io.element.android.libraries.designsystem.components.avatar.AvatarData import io.element.android.libraries.matrix.test.AN_EVENT_ID import io.element.android.libraries.matrix.test.A_ROOM_ID @@ -26,6 +27,7 @@ import io.element.android.tests.testutils.lambda.lambdaRecorder import io.element.android.tests.testutils.robolectric.RobolectricTest import kotlinx.coroutines.test.runTest import org.junit.Test +import org.robolectric.Shadows.shadowOf class RingingCallNotificationCreatorTest : RobolectricTest() { @Test @@ -89,6 +91,35 @@ class RingingCallNotificationCreatorTest : RobolectricTest() { assertThat(acceptAction?.title?.toString()).isEqualTo("Answer") } + @Test + fun `createNotification - the answer action goes through IncomingCallActivity, asking it to answer straight away`() = runTest { + val notificationCreator = createRingingCallNotificationCreator( + matrixClientProvider = FakeMatrixClientProvider(getClient = { Result.success(FakeMatrixClient()) }), + ) + + val notification = notificationCreator.createTestNotification() + + // Not ElementCallActivity: naming the WebView activity here made the Answer button open the + // WebView call whatever FeatureFlags.NativeCall was set to. + val answerIntent = shadowOf(notification?.actions?.get(1)?.actionIntent).savedIntent + assertThat(answerIntent.component?.className).isEqualTo(IncomingCallActivity::class.java.name) + assertThat(answerIntent.getBooleanExtra(IncomingCallActivity.EXTRA_ANSWER_IMMEDIATELY, false)).isTrue() + assertThat(answerIntent.hasExtra(IncomingCallActivity.EXTRA_NOTIFICATION_DATA)).isTrue() + } + + @Test + fun `createNotification - tapping the notification body answers too`() = runTest { + val notificationCreator = createRingingCallNotificationCreator( + matrixClientProvider = FakeMatrixClientProvider(getClient = { Result.success(FakeMatrixClient()) }), + ) + + val notification = notificationCreator.createTestNotification() + + val contentIntent = shadowOf(notification?.contentIntent).savedIntent + assertThat(contentIntent.component?.className).isEqualTo(IncomingCallActivity::class.java.name) + assertThat(contentIntent.getBooleanExtra(IncomingCallActivity.EXTRA_ANSWER_IMMEDIATELY, false)).isTrue() + } + private suspend fun RingingCallNotificationCreator.createTestNotification(audioOnly: Boolean = false) = createNotification( sessionId = A_SESSION_ID, roomId = A_ROOM_ID, From 0d5a79f3f23e7d974af51a3dc127bf7929371a6c Mon Sep 17 00:00:00 2001 From: Valere Date: Thu, 24 Sep 2026 14:57:09 +0200 Subject: [PATCH 14/20] Add the camera foreground service permission to the manifest snapshot --- tools/manifest/gplay/release/aaptDump.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/tools/manifest/gplay/release/aaptDump.txt b/tools/manifest/gplay/release/aaptDump.txt index 97e389bd7c..258e8a032e 100644 --- a/tools/manifest/gplay/release/aaptDump.txt +++ b/tools/manifest/gplay/release/aaptDump.txt @@ -18,6 +18,7 @@ uses-permission: name='android.permission.USE_FULL_SCREEN_INTENT' uses-permission: name='android.permission.ACCESS_NETWORK_STATE' uses-permission: name='android.permission.VIBRATE' uses-permission: name='android.permission.ACCESS_LOCAL_NETWORK' +uses-permission: name='android.permission.FOREGROUND_SERVICE_CAMERA' uses-permission: name='android.permission.USE_BIOMETRIC' uses-permission: name='android.permission.USE_FINGERPRINT' uses-permission: name='com.google.android.c2dm.permission.RECEIVE' From 2ca33296b51a82e2155858edc8b95eb7b4a2f30f Mon Sep 17 00:00:00 2001 From: Valere Date: Tue, 29 Sep 2026 15:07:08 +0200 Subject: [PATCH 15/20] Update element-call-android to 0.1.0-rc.6 Co-Authored-By: Claude Opus 5.5 --- 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 905db875ae..ac20a11a83 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -65,7 +65,7 @@ ktlint = "1.8.0" androidx-test-ext-junit = "1.3.0" kover = "0.9.9" # The native MatrixRTC call component, https://github.com/element-hq/element-call-android -element_call = "0.1.0-rc.2" +element_call = "0.1.0-rc.6" [libraries] # Project From e786e2d22ff4d4db92d3d7a88f014a90954721d2 Mon Sep 17 00:00:00 2001 From: Valere Date: Tue, 22 Sep 2026 21:31:02 +0200 Subject: [PATCH 16/20] Draw the native call in the app A wrapper around the logged-in content rather than an overlay, because a minimized call takes a strip of height above it while a maximized one covers it. Co-Authored-By: Claude Opus 5 (1M context) --- features/callnative/api/build.gradle.kts | 3 +- .../features/callnative/api/NativeCallHost.kt | 26 +++++++ .../features/callnative/api/NativeCallPip.kt | 26 +++++++ features/callnative/impl/build.gradle.kts | 4 +- .../impl/DefaultNativeCallEntryPoint.kt | 25 +++---- .../callnative/impl/DefaultNativeCallHost.kt | 71 +++++++++++++++++++ .../callnative/impl/DefaultNativeCallPip.kt | 31 ++++++++ .../callnative/impl/ElementCallControllers.kt | 28 ++++++-- .../callnative/impl/ElementXCallStyle.kt | 57 +++++++++++++++ .../impl/DefaultNativeCallEntryPointTest.kt | 24 ++++--- .../impl/FakeElementCallControllers.kt | 4 ++ 11 files changed, 263 insertions(+), 36 deletions(-) create mode 100644 features/callnative/api/src/main/kotlin/io/element/android/features/callnative/api/NativeCallHost.kt create mode 100644 features/callnative/api/src/main/kotlin/io/element/android/features/callnative/api/NativeCallPip.kt create mode 100644 features/callnative/impl/src/main/kotlin/io/element/android/features/callnative/impl/DefaultNativeCallHost.kt create mode 100644 features/callnative/impl/src/main/kotlin/io/element/android/features/callnative/impl/DefaultNativeCallPip.kt create mode 100644 features/callnative/impl/src/main/kotlin/io/element/android/features/callnative/impl/ElementXCallStyle.kt diff --git a/features/callnative/api/build.gradle.kts b/features/callnative/api/build.gradle.kts index 81e5976627..f5f3c25f4b 100644 --- a/features/callnative/api/build.gradle.kts +++ b/features/callnative/api/build.gradle.kts @@ -6,7 +6,8 @@ */ plugins { - id("io.element.android-library") + // NativeCallHost renders the call, so this module carries a composable. + id("io.element.android-compose-library") } android { diff --git a/features/callnative/api/src/main/kotlin/io/element/android/features/callnative/api/NativeCallHost.kt b/features/callnative/api/src/main/kotlin/io/element/android/features/callnative/api/NativeCallHost.kt new file mode 100644 index 0000000000..c90ffe1e29 --- /dev/null +++ b/features/callnative/api/src/main/kotlin/io/element/android/features/callnative/api/NativeCallHost.kt @@ -0,0 +1,26 @@ +/* + * 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.callnative.api + +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier + +/** + * Where a native call is drawn, wrapped around the logged-in content. + * + * A wrapper rather than an overlay, because the two states of a call need different things from the + * layout: minimized, the call takes a strip of height above the content; maximized, it covers it. The + * [Modifier] handed back to [content] has consumed whatever window insets the call has already taken, + * so the screens underneath do not pad for the status bar a second time. + * + * With no call running this renders [content] and nothing else. + */ +interface NativeCallHost { + @Composable + fun Render(modifier: Modifier, content: @Composable (Modifier) -> Unit) +} diff --git a/features/callnative/api/src/main/kotlin/io/element/android/features/callnative/api/NativeCallPip.kt b/features/callnative/api/src/main/kotlin/io/element/android/features/callnative/api/NativeCallPip.kt new file mode 100644 index 0000000000..0cd8ea9110 --- /dev/null +++ b/features/callnative/api/src/main/kotlin/io/element/android/features/callnative/api/NativeCallPip.kt @@ -0,0 +1,26 @@ +/* + * 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.callnative.api + +import android.app.Activity + +/** + * The one part of picture-in-picture that an Activity has to do itself. + * + * Everything else is installed where the call is drawn, which is inside the logged-in content and so + * knows which session's call it is. This does not: `onUserLeaveHint` is an Activity override, there is + * no session in scope at that point, and it applies to whichever call happens to be running. + * + * Only Android 11 and below reach this. From Android 12 the system is told up front that the Activity + * would like to shrink, which covers every way out of the app including the home gesture - the one + * people actually use, and the one this hook never sees. + */ +interface NativeCallPip { + /** Call from the host Activity's `onUserLeaveHint()` override. */ + fun onUserLeaveHint(activity: Activity) +} diff --git a/features/callnative/impl/build.gradle.kts b/features/callnative/impl/build.gradle.kts index 8cfee12e0b..a135f46ad3 100644 --- a/features/callnative/impl/build.gradle.kts +++ b/features/callnative/impl/build.gradle.kts @@ -8,7 +8,7 @@ import extension.testCommonDependencies * Please see LICENSE files in the repository root for full details. */ plugins { - id("io.element.android-library") + id("io.element.android-compose-library") } android { @@ -29,8 +29,10 @@ dependencies { implementation(platform("io.element.android:element-call-bom:$elementCallVersion")) implementation(libs.element.call.api) implementation(libs.element.call.impl) + implementation(libs.element.call.ui) implementation(projects.features.call.api) + implementation(projects.libraries.designsystem) implementation(projects.libraries.architecture) implementation(projects.libraries.di) implementation(projects.libraries.featureflag.api) diff --git a/features/callnative/impl/src/main/kotlin/io/element/android/features/callnative/impl/DefaultNativeCallEntryPoint.kt b/features/callnative/impl/src/main/kotlin/io/element/android/features/callnative/impl/DefaultNativeCallEntryPoint.kt index 4eb5b69418..2210663376 100644 --- a/features/callnative/impl/src/main/kotlin/io/element/android/features/callnative/impl/DefaultNativeCallEntryPoint.kt +++ b/features/callnative/impl/src/main/kotlin/io/element/android/features/callnative/impl/DefaultNativeCallEntryPoint.kt @@ -21,7 +21,6 @@ import io.element.android.libraries.di.annotations.AppCoroutineScope import io.element.android.libraries.di.annotations.ApplicationContext import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch -import timber.log.Timber /** * Starts a call on the native MatrixRTC stack. @@ -37,15 +36,6 @@ class DefaultNativeCallEntryPoint( @AppCoroutineScope private val appCoroutineScope: CoroutineScope, ) : NativeCallEntryPoint { override fun startCall(callData: CallData) { - // The call does nothing past "requested" until the microphone permission is answered, and - // only an Activity can ask for one. Until the call has a UI of its own, the only answer - // available is the permission the app already holds - so check it before starting rather - // than after. Starting a call nobody can answer leaves it running and refusing every later - // attempt for the rest of the session, with nothing on screen to hang it up. - if (!hasMicrophonePermission()) { - Timber.w("NativeCall: RECORD_AUDIO is not granted and there is no call UI to ask for it, not starting the call") - return - } appCoroutineScope.launch { val controller = controllers.getOrBuild(callData.sessionId) ?: return@launch controller.startCall( @@ -54,13 +44,14 @@ class DefaultNativeCallEntryPoint( isAudioCall = callData.isAudioCall, ) ) - controller.setMicrophonePermissionGranted(true) - // A call with no UI is never the screen the user is looking at, and the snapshot assumes - // it is. Saying so matters for more than tidiness: the component holds a proximity wake - // lock while a maximized audio call is on screen, so leaving the default in place blanks - // the display whenever a hand goes near the top of the phone - including when reaching - // for the notification shade, which is the only way to hang this call up. - controller.setMaximized(false) + // The call screen asks for the microphone itself, and is where the answer normally comes + // from. This covers the case it cannot reach: a call answered from a notification while + // the app is in the background has no call screen composed yet, and a call waiting on a + // permission nobody is there to grant would sit unanswered. Reporting a permission we + // already hold is idempotent, so the screen asking again later costs nothing. + if (hasMicrophonePermission()) { + controller.setMicrophonePermissionGranted(true) + } } } diff --git a/features/callnative/impl/src/main/kotlin/io/element/android/features/callnative/impl/DefaultNativeCallHost.kt b/features/callnative/impl/src/main/kotlin/io/element/android/features/callnative/impl/DefaultNativeCallHost.kt new file mode 100644 index 0000000000..80c232e799 --- /dev/null +++ b/features/callnative/impl/src/main/kotlin/io/element/android/features/callnative/impl/DefaultNativeCallHost.kt @@ -0,0 +1,71 @@ +/* + * 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.callnative.impl + +import androidx.activity.ComponentActivity +import androidx.compose.runtime.Composable +import androidx.compose.runtime.DisposableEffect +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext +import dev.zacsweers.metro.ContributesBinding +import io.element.android.call.impl.ElementCallPictureInPicture +import io.element.android.call.ui.ElementCallOverlay +import io.element.android.features.callnative.api.NativeCallHost +import io.element.android.libraries.core.extensions.runCatchingExceptions +import io.element.android.libraries.di.SessionScope +import io.element.android.libraries.matrix.api.MatrixClient +import timber.log.Timber + +/** + * Draws the session's native call around the logged-in content. + * + * Session-scoped, unlike the entry point: this renders inside the logged-in navigation, so the + * session is already in scope and there is no need to go looking for one. The stack it draws is not, + * so this asks the app-scoped registry for its own session's controller. + * + * That controller only exists once a call has been placed or answered. Until then this is a + * pass-through and nothing about the call is on screen. + */ +@ContributesBinding(SessionScope::class) +class DefaultNativeCallHost( + private val client: MatrixClient, + private val controllers: ElementCallControllers, +) : NativeCallHost { + @Composable + override fun Render(modifier: Modifier, content: @Composable (Modifier) -> Unit) { + val controller by remember(client.sessionId) { controllers.controller(client.sessionId) } + .collectAsState(initial = null) + val current = controller + if (current == null) { + content(modifier) + return + } + + // Installed here rather than in the Activity because this is the only place that has both the + // Activity and the session whose call it is. The Activity keeps `onUserLeaveHint`, which it + // cannot delegate: see NativeCallPip. + val activity = LocalContext.current as? ComponentActivity + DisposableEffect(activity, current) { + if (activity != null) { + runCatchingExceptions { ElementCallPictureInPicture.attach(activity, current) } + .onFailure { Timber.w(it, "NativeCall: cannot attach picture-in-picture") } + } + onDispose { } + } + + ElementCallOverlay( + controller = current, + modifier = modifier, + style = rememberElementXCallStyle(), + content = content, + ) + } +} diff --git a/features/callnative/impl/src/main/kotlin/io/element/android/features/callnative/impl/DefaultNativeCallPip.kt b/features/callnative/impl/src/main/kotlin/io/element/android/features/callnative/impl/DefaultNativeCallPip.kt new file mode 100644 index 0000000000..b94785a931 --- /dev/null +++ b/features/callnative/impl/src/main/kotlin/io/element/android/features/callnative/impl/DefaultNativeCallPip.kt @@ -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.features.callnative.impl + +import android.app.Activity +import dev.zacsweers.metro.AppScope +import dev.zacsweers.metro.ContributesBinding +import io.element.android.call.impl.ElementCallPictureInPicture +import io.element.android.features.callnative.api.NativeCallPip + +/** + * Forwards the Activity's leave hint to whichever session has a call. + * + * App-scoped because `onUserLeaveHint` is, and it asks for the running call rather than a particular + * session's: only one call runs at a time, and the Activity has no session in scope to name. Sessions + * whose stack was never built are skipped, so asking cannot start one. + */ +@ContributesBinding(AppScope::class) +class DefaultNativeCallPip( + private val controllers: ElementCallControllers, +) : NativeCallPip { + override fun onUserLeaveHint(activity: Activity) { + val controller = controllers.withRunningCall() ?: return + ElementCallPictureInPicture.onUserLeaveHint(activity, controller) + } +} diff --git a/features/callnative/impl/src/main/kotlin/io/element/android/features/callnative/impl/ElementCallControllers.kt b/features/callnative/impl/src/main/kotlin/io/element/android/features/callnative/impl/ElementCallControllers.kt index 0cb50d2038..5792da9a93 100644 --- a/features/callnative/impl/src/main/kotlin/io/element/android/features/callnative/impl/ElementCallControllers.kt +++ b/features/callnative/impl/src/main/kotlin/io/element/android/features/callnative/impl/ElementCallControllers.kt @@ -31,12 +31,16 @@ import io.element.android.libraries.preferences.api.store.AppPreferencesStore import io.element.android.libraries.push.api.notifications.ForegroundServiceType import io.element.android.libraries.push.api.notifications.NotificationIdProvider import io.element.android.services.appnavstate.api.AppForegroundStateService +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.first +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.update import kotlinx.coroutines.job import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock import timber.log.Timber -import java.util.concurrent.ConcurrentHashMap /** * The native call controller of a session, built on demand. @@ -51,6 +55,14 @@ interface ElementCallControllers { */ suspend fun getOrBuild(sessionId: SessionId): ElementCallController? + /** + * The session's controller once its stack exists, and null until then. Never builds one. + * + * A flow rather than a getter because the stack appears part-way through the session's life, when + * a call is first placed or answered, and whatever draws the call has to notice when it does. + */ + fun controller(sessionId: SessionId): Flow + /** * The controller of whichever session currently has a call, or null when none does. * @@ -84,33 +96,37 @@ class DefaultElementCallControllers( private val appPreferencesStore: AppPreferencesStore, private val featureFlagService: FeatureFlagService, ) : ElementCallControllers { - private val stacks = ConcurrentHashMap() + // A flow rather than a plain map because what draws the call has to see the stack appear. + private val stacks = MutableStateFlow>(emptyMap()) // One at a time across every session: two calls placed at once must not race into two stacks for // the same session, because only one of them would ever be closed. private val mutex = Mutex() override suspend fun getOrBuild(sessionId: SessionId): ElementCallController? = mutex.withLock { - stacks[sessionId]?.let { return@withLock it.controller } + stacks.value[sessionId]?.let { return@withLock it.controller } val client = matrixClientProvider.getOrRestore(sessionId).getOrNull() if (client == null) { Timber.w("NativeCall: no client for $sessionId, cannot start a call") return@withLock null } build(client).also { stack -> - stacks[sessionId] = stack + stacks.update { it + (sessionId to stack) } // The client's scope is the stack's lifetime: the component has no shutdown of its own, so // cancelling this at logout is what stops the RTC core. Closing here only releases the // component's own reference, so a dead stack is not left behind it. client.sessionCoroutineScope.coroutineContext.job.invokeOnCompletion { - stacks.remove(sessionId, stack) + stacks.update { if (it[sessionId] === stack) it - sessionId else it } stack.close() } }.controller } + override fun controller(sessionId: SessionId): Flow = + stacks.map { it[sessionId]?.controller }.distinctUntilChanged() + override fun withRunningCall(): ElementCallController? = - stacks.values.firstNotNullOfOrNull { stack -> + stacks.value.values.firstNotNullOfOrNull { stack -> stack.controller.takeIf { it.state.value != null } } diff --git a/features/callnative/impl/src/main/kotlin/io/element/android/features/callnative/impl/ElementXCallStyle.kt b/features/callnative/impl/src/main/kotlin/io/element/android/features/callnative/impl/ElementXCallStyle.kt new file mode 100644 index 0000000000..751679b298 --- /dev/null +++ b/features/callnative/impl/src/main/kotlin/io/element/android/features/callnative/impl/ElementXCallStyle.kt @@ -0,0 +1,57 @@ +/* + * 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.callnative.impl + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import io.element.android.call.ui.theme.ElementCallColors +import io.element.android.call.ui.theme.ElementCallIcons +import io.element.android.call.ui.theme.ElementCallStyle +import io.element.android.call.ui.theme.ElementCallTypography +import io.element.android.libraries.designsystem.components.avatar.Avatar +import io.element.android.libraries.designsystem.components.avatar.AvatarData +import io.element.android.libraries.designsystem.components.avatar.AvatarSize +import io.element.android.libraries.designsystem.components.avatar.AvatarType + +/** + * How Element X dresses the native call. + * + * Deliberately almost nothing. The component's own defaults are already Compound - its colours are + * Compound's dark palette and its glyphs are Compound vectors - so re-deriving them from + * `ElementTheme` would buy no visual change and a lot of mapping to keep in step. The call also stays + * dark in a light-themed app on purpose: it is a video surface, as it is in Element Call on the web. + * + * What the component genuinely cannot do is turn an `mxc://` URL into a picture, because that needs a + * Matrix media loader. So the avatar is the one thing overridden here, and it is what makes faces + * appear on the tiles rather than initials. + */ +@Composable +fun rememberElementXCallStyle(): ElementCallStyle { + val icons = ElementCallIcons.default() + return remember(icons) { + ElementCallStyle( + colors = ElementCallColors.Dark, + typography = ElementCallTypography.Default, + icons = icons, + avatar = { data, size -> + Avatar( + avatarData = AvatarData( + id = data.id, + name = data.name, + url = data.url, + // Unused: forcedAvatarSize below is what decides the size, and the component + // asks in dp because it lays the tiles out itself. + size = AvatarSize.ActiveCallItem, + ), + avatarType = AvatarType.User, + forcedAvatarSize = size, + ) + }, + ) + } +} diff --git a/features/callnative/impl/src/test/kotlin/io/element/android/features/callnative/impl/DefaultNativeCallEntryPointTest.kt b/features/callnative/impl/src/test/kotlin/io/element/android/features/callnative/impl/DefaultNativeCallEntryPointTest.kt index 5afbda0e5b..cd209069e7 100644 --- a/features/callnative/impl/src/test/kotlin/io/element/android/features/callnative/impl/DefaultNativeCallEntryPointTest.kt +++ b/features/callnative/impl/src/test/kotlin/io/element/android/features/callnative/impl/DefaultNativeCallEntryPointTest.kt @@ -36,13 +36,14 @@ class DefaultNativeCallEntryPointTest : RobolectricTest() { runCurrent() assertThat(controller.startedCalls.map { it.roomId.value }).containsExactly(A_ROOM_ID.value) - // Without this the call parks at RequestingPermission forever, and because a call is then - // "running" every later attempt is refused with nothing on screen to hang it up. + // The call screen normally answers this, but it is not composed when a call is answered from + // a notification with the app in the background - and a call waiting on a permission nobody + // is there to grant sits unanswered and refuses every later attempt. assertThat(controller.microphonePermissionAnswers).containsExactly(true) } @Test - fun `a call with no UI is not maximized`() = runTest { + fun `a call the user placed opens on the call screen, not in the minimized bar`() = runTest { grantMicrophonePermission() val controller = FakeElementCallController() val entryPoint = createEntryPoint(controller) @@ -50,14 +51,14 @@ class DefaultNativeCallEntryPointTest : RobolectricTest() { entryPoint.startCall(A_CALL_DATA) runCurrent() - // The snapshot assumes a call is on screen. While there is no call UI it never is, and the - // component holds a proximity wake lock for a maximized audio call - which blanks the display - // whenever a hand goes near the top of the phone, including reaching for the shade. - assertThat(controller.maximizedCalls).containsExactly(false) + // A snapshot starts maximized, which is what the user asked for by pressing call. Saying + // otherwise here is what a UI-less build had to do, and it would now dock a freshly placed + // call into the bar. + assertThat(controller.maximizedCalls).isEmpty() } @Test - fun `no call is started when the microphone permission is missing`() = runTest { + fun `the call still starts without the microphone permission, so the call screen can ask`() = runTest { shadowOf(RuntimeEnvironment.getApplication()).denyPermissions(Manifest.permission.RECORD_AUDIO) val controller = FakeElementCallController() val entryPoint = createEntryPoint(controller) @@ -65,9 +66,10 @@ class DefaultNativeCallEntryPointTest : RobolectricTest() { entryPoint.startCall(A_CALL_DATA) runCurrent() - // Nothing to ask with, so nothing is started: a call left unanswered would wedge the - // controller for the rest of the session. - assertThat(controller.startedCalls).isEmpty() + assertThat(controller.startedCalls).hasSize(1) + // Nothing to report, and nothing to report it from: only an Activity can ask, and the call + // screen does exactly that once it appears. + assertThat(controller.microphonePermissionAnswers).isEmpty() } @Test diff --git a/features/callnative/impl/src/test/kotlin/io/element/android/features/callnative/impl/FakeElementCallControllers.kt b/features/callnative/impl/src/test/kotlin/io/element/android/features/callnative/impl/FakeElementCallControllers.kt index 64cf425af6..fb87f494d5 100644 --- a/features/callnative/impl/src/test/kotlin/io/element/android/features/callnative/impl/FakeElementCallControllers.kt +++ b/features/callnative/impl/src/test/kotlin/io/element/android/features/callnative/impl/FakeElementCallControllers.kt @@ -9,6 +9,8 @@ package io.element.android.features.callnative.impl import io.element.android.call.api.ElementCallController import io.element.android.libraries.matrix.api.core.SessionId +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flowOf /** * @param controller what [getOrBuild] answers with, or null to stand for a session that cannot be @@ -24,6 +26,8 @@ class FakeElementCallControllers( return controller } + override fun controller(sessionId: SessionId): Flow = flowOf(controller) + override fun withRunningCall(): ElementCallController? = controller?.takeIf { it.state.value != null } } From f052f755b645a91ecd300044f451b6083e128309 Mon Sep 17 00:00:00 2001 From: Valere Date: Tue, 22 Sep 2026 21:31:03 +0200 Subject: [PATCH 17/20] Follow a native call into picture-in-picture from the app supportsPictureInPicture and smallestScreenSize have to be declared here, because a library cannot change a host Activity's manifest entry. Co-Authored-By: Claude Opus 5 (1M context) --- app/src/main/AndroidManifest.xml | 3 ++- .../io/element/android/x/MainActivity.kt | 13 ++++++++++ .../io/element/android/x/di/AppBindings.kt | 8 +++++++ appnav/build.gradle.kts | 1 + .../android/appnav/LoggedInFlowNode.kt | 24 ++++++++++++------- 5 files changed, 39 insertions(+), 10 deletions(-) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index d5f08e0795..9193c6de71 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -53,9 +53,10 @@ --> diff --git a/app/src/main/kotlin/io/element/android/x/MainActivity.kt b/app/src/main/kotlin/io/element/android/x/MainActivity.kt index c5551f11aa..cc058d0f8f 100644 --- a/app/src/main/kotlin/io/element/android/x/MainActivity.kt +++ b/app/src/main/kotlin/io/element/android/x/MainActivity.kt @@ -70,6 +70,19 @@ class MainActivity : NodeActivity() { } } + /** + * The pre-Android 12 way into picture-in-picture for a native call. + * + * Everything else about it is installed where the call is drawn, which knows whose call it is. + * This cannot be: an Activity override has no session in scope. From Android 12 the system is told + * up front that this Activity would like to shrink, and the call handles it without ever coming + * through here. + */ + override fun onUserLeaveHint() { + super.onUserLeaveHint() + appBindings.nativeCallPip().onUserLeaveHint(this) + } + @Composable private fun MainContent(appBindings: AppBindings) { val migrationState = appBindings.migrationEntryPoint().present() diff --git a/app/src/main/kotlin/io/element/android/x/di/AppBindings.kt b/app/src/main/kotlin/io/element/android/x/di/AppBindings.kt index 638ba0e38c..abdb6b1394 100644 --- a/app/src/main/kotlin/io/element/android/x/di/AppBindings.kt +++ b/app/src/main/kotlin/io/element/android/x/di/AppBindings.kt @@ -12,6 +12,7 @@ import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesTo import dev.zacsweers.metro.Multibinds import io.element.android.features.api.MigrationEntryPoint +import io.element.android.features.callnative.api.NativeCallPip import io.element.android.features.enterprise.api.AppStartupHook import io.element.android.features.enterprise.api.EnterpriseService import io.element.android.features.lockscreen.api.LockScreenEntryPoint @@ -52,6 +53,13 @@ interface AppBindings { fun buildMeta(): BuildMeta + /** + * For picture-in-picture. Only an Activity is told it is being left, and only the call knows + * whether that is worth shrinking for, so `MainActivity` needs this much of the native call and + * no more. + */ + fun nativeCallPip(): NativeCallPip + fun sentrySdkDsn(): SentrySdkDsn? @Multibinds(allowEmpty = true) diff --git a/appnav/build.gradle.kts b/appnav/build.gradle.kts index 12b95d9a09..f65692feb4 100644 --- a/appnav/build.gradle.kts +++ b/appnav/build.gradle.kts @@ -50,6 +50,7 @@ dependencies { implementation(libs.coil) implementation(projects.features.announcement.api) + implementation(projects.features.callnative.api) implementation(projects.features.ftue.api) implementation(projects.features.linknewdevice.api) implementation(projects.features.share.api) diff --git a/appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt b/appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt index a0bcf3b489..2f0988281e 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt @@ -48,6 +48,7 @@ import io.element.android.appnav.room.RoomFlowNode import io.element.android.appnav.room.RoomNavigationTarget import io.element.android.appnav.room.joined.JoinedRoomLoadedFlowNode import io.element.android.compound.colors.SemanticColorsLightDark +import io.element.android.features.callnative.api.NativeCallHost import io.element.android.features.createroom.api.CreateRoomEntryPoint import io.element.android.features.enterprise.api.EnterpriseService import io.element.android.features.enterprise.api.SessionEnterpriseService @@ -144,6 +145,7 @@ class LoggedInFlowNode( private val shareEntryPoint: ShareEntryPoint, private val matrixClient: MatrixClient, private val sendingQueue: SendQueues, + private val nativeCallHost: NativeCallHost, private val incomingVerificationEntryPoint: IncomingVerificationEntryPoint, private val mediaPreviewConfigMigration: MediaPreviewConfigMigration, private val sessionEnterpriseService: SessionEnterpriseService, @@ -701,15 +703,19 @@ class LoggedInFlowNode( ) { CompositionLocalProvider(LocalMapTilerConfig provides updatedMapTilerConfig) { val isOnline by syncService.isOnline.collectAsState() - ConnectivityIndicatorContainer( - isOnline = isOnline, - modifier = modifier, - ) { contentModifier -> - Box(modifier = contentModifier) { - val ftueState by ftueService.state.collectAsState() - BackstackView(transitionHandler = rememberLoggedInFlowTransitionHandler(backstack)) - if (ftueState is FtueState.Complete) { - PermanentChild(permanentNavModel = permanentNavModel, navTarget = NavTarget.LoggedInPermanent) + // Outside the connectivity indicator so the two strips stack rather than fight: an + // offline banner during a call belongs under the call bar, not over it. + nativeCallHost.Render(modifier = modifier) { callContentModifier -> + ConnectivityIndicatorContainer( + isOnline = isOnline, + modifier = callContentModifier, + ) { contentModifier -> + Box(modifier = contentModifier) { + val ftueState by ftueService.state.collectAsState() + BackstackView(transitionHandler = rememberLoggedInFlowTransitionHandler(backstack)) + if (ftueState is FtueState.Complete) { + PermanentChild(permanentNavModel = permanentNavModel, navTarget = NavTarget.LoggedInPermanent) + } } } } From a2be854cf9c56a95918fdad564e8da27376e34c7 Mon Sep 17 00:00:00 2001 From: Valere Date: Wed, 23 Sep 2026 18:06:47 +0200 Subject: [PATCH 18/20] fix: linter LocalContext should not be cast to Activity --- .../android/features/callnative/impl/DefaultNativeCallHost.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/features/callnative/impl/src/main/kotlin/io/element/android/features/callnative/impl/DefaultNativeCallHost.kt b/features/callnative/impl/src/main/kotlin/io/element/android/features/callnative/impl/DefaultNativeCallHost.kt index 80c232e799..bc37649f58 100644 --- a/features/callnative/impl/src/main/kotlin/io/element/android/features/callnative/impl/DefaultNativeCallHost.kt +++ b/features/callnative/impl/src/main/kotlin/io/element/android/features/callnative/impl/DefaultNativeCallHost.kt @@ -8,13 +8,13 @@ package io.element.android.features.callnative.impl import androidx.activity.ComponentActivity +import androidx.activity.compose.LocalActivity import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.LocalContext import dev.zacsweers.metro.ContributesBinding import io.element.android.call.impl.ElementCallPictureInPicture import io.element.android.call.ui.ElementCallOverlay @@ -52,7 +52,7 @@ class DefaultNativeCallHost( // Installed here rather than in the Activity because this is the only place that has both the // Activity and the session whose call it is. The Activity keeps `onUserLeaveHint`, which it // cannot delegate: see NativeCallPip. - val activity = LocalContext.current as? ComponentActivity + val activity = LocalActivity.current as? ComponentActivity DisposableEffect(activity, current) { if (activity != null) { runCatchingExceptions { ElementCallPictureInPicture.attach(activity, current) } From 01bc7dd070b3e119473afecedf65b182b845f89e Mon Sep 17 00:00:00 2001 From: Valere Date: Tue, 29 Sep 2026 15:08:55 +0200 Subject: [PATCH 19/20] Keep the app content composed when a native call starts The overlay now draws with no controller too, so starting a call no longer resets the content's state, such as the composer draft. Co-Authored-By: Claude Opus 5.5 --- .../features/callnative/impl/DefaultNativeCallHost.kt | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/features/callnative/impl/src/main/kotlin/io/element/android/features/callnative/impl/DefaultNativeCallHost.kt b/features/callnative/impl/src/main/kotlin/io/element/android/features/callnative/impl/DefaultNativeCallHost.kt index bc37649f58..332f28f7d8 100644 --- a/features/callnative/impl/src/main/kotlin/io/element/android/features/callnative/impl/DefaultNativeCallHost.kt +++ b/features/callnative/impl/src/main/kotlin/io/element/android/features/callnative/impl/DefaultNativeCallHost.kt @@ -31,8 +31,8 @@ import timber.log.Timber * session is already in scope and there is no need to go looking for one. The stack it draws is not, * so this asks the app-scoped registry for its own session's controller. * - * That controller only exists once a call has been placed or answered. Until then this is a - * pass-through and nothing about the call is on screen. + * That controller only exists once a call has been placed or answered. Until then the overlay draws + * the content alone. */ @ContributesBinding(SessionScope::class) class DefaultNativeCallHost( @@ -44,23 +44,20 @@ class DefaultNativeCallHost( val controller by remember(client.sessionId) { controllers.controller(client.sessionId) } .collectAsState(initial = null) val current = controller - if (current == null) { - content(modifier) - return - } // Installed here rather than in the Activity because this is the only place that has both the // Activity and the session whose call it is. The Activity keeps `onUserLeaveHint`, which it // cannot delegate: see NativeCallPip. val activity = LocalActivity.current as? ComponentActivity DisposableEffect(activity, current) { - if (activity != null) { + if (activity != null && current != null) { runCatchingExceptions { ElementCallPictureInPicture.attach(activity, current) } .onFailure { Timber.w(it, "NativeCall: cannot attach picture-in-picture") } } onDispose { } } + // Composed even with no controller yet, so the content never moves in the tree and keeps its state. ElementCallOverlay( controller = current, modifier = modifier, From 3985d4b712dc78f1576c2f85266eb8f285104ba1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20Mart=C3=ADn?= Date: Tue, 29 Sep 2026 19:06:33 +0200 Subject: [PATCH 20/20] Add missing parts of the `26.09.4` release to `develop`: - Update CHANGES.md. - Update aaptDump.txt. --- CHANGES.md | 9 +++++++++ tools/manifest/gplay/release/aaptDump.txt | 20 ++++++++++---------- 2 files changed, 19 insertions(+), 10 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index cbf9bc0652..74621c3eea 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,3 +1,12 @@ +Changes in Element X v26.09.4 +============================= + +## Hotifx version: + +Fixes an issue with the room list not displaying all activity from unread messages. + +**Full Changelog**: https://github.com/element-hq/element-x-android/compare/v26.09.3...v26.09.4 + Changes in Element X v26.09.3 ============================= diff --git a/tools/manifest/gplay/release/aaptDump.txt b/tools/manifest/gplay/release/aaptDump.txt index 258e8a032e..9c2dca205a 100644 --- a/tools/manifest/gplay/release/aaptDump.txt +++ b/tools/manifest/gplay/release/aaptDump.txt @@ -1,5 +1,5 @@ -package: name='io.element.android.x' versionCode='202609030' versionName='26.09.3' platformBuildVersionName='17' platformBuildVersionCode='37' compileSdkVersion='37' compileSdkVersionCodename='17' -sdkVersion:'24' +package: name='io.element.android.x' versionCode='202609040' versionName='26.09.4' platformBuildVersionName='17' platformBuildVersionCode='37' compileSdkVersion='37' compileSdkVersionCodename='17' +sdkVersion:'33' targetSdkVersion:'37' uses-permission: name='android.permission.POST_NOTIFICATIONS' uses-permission: name='android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS' @@ -66,14 +66,14 @@ application-label-vi:'Element X' application-label-zh:'Element X' application-label-zh-CN:'Element X' application-label-zh-TW:'Element X' -application-icon-120:'res/BW.xml' -application-icon-160:'res/BW.xml' -application-icon-240:'res/BW.xml' -application-icon-320:'res/BW.xml' -application-icon-480:'res/BW.xml' -application-icon-640:'res/BW.xml' -application-icon-65534:'res/BW.xml' -application: label='Element X' icon='res/BW.xml' +application-icon-120:'res/E4.xml' +application-icon-160:'res/E4.xml' +application-icon-240:'res/E4.xml' +application-icon-320:'res/E4.xml' +application-icon-480:'res/E4.xml' +application-icon-640:'res/E4.xml' +application-icon-65534:'res/E4.xml' +application: label='Element X' icon='res/E4.xml' launchable-activity: name='io.element.android.x.MainActivity' label='' icon='' uses-library-not-required:'androidx.window.extensions' uses-library-not-required:'androidx.window.sidecar'