Merge branch 'develop' into feature/fga/space_list_sdk_api

This commit is contained in:
ganfra
2025-09-24 15:21:40 +02:00
committed by GitHub
42 changed files with 477 additions and 315 deletions

View File

@@ -1,3 +1,73 @@
Changes in Element X v25.09.2
=============================
## What's Changed
### ✨ Features
* Show progress dialog while we are sending invites in a room by @richvdh in https://github.com/element-hq/element-x-android/pull/5342
* Call: RTC decline event support by @BillCarsonFr in https://github.com/element-hq/element-x-android/pull/5305
* Add room info to the thread's top app bar by @jmartinesp in https://github.com/element-hq/element-x-android/pull/5374
### 🙌 Improvements
* Use the new RtcNotification event instead of the now deprecated CallNotify by @BillCarsonFr in https://github.com/element-hq/element-x-android/pull/5357
### 🐛 Bugfixes
* Increase Element Call audio init delay ensuring the right audio device is used by @jmartinesp in https://github.com/element-hq/element-x-android/pull/5315
* Do not center the dialog title text for dialogs with no icon by @jmartinesp in https://github.com/element-hq/element-x-android/pull/5332
* Media viewer: release the `ExoPlayers` when the hosting composables are disposed by @jmartinesp in https://github.com/element-hq/element-x-android/pull/5351
* Make PushData.clientSecret mandatory. by @bmarty in https://github.com/element-hq/element-x-android/pull/5369
* Cleanup ftue code and ensure verification confirmation is displayed by @bmarty in https://github.com/element-hq/element-x-android/pull/5379
* Change in clear cache behavior by @bmarty in https://github.com/element-hq/element-x-android/pull/5388
* fix (room navigation) : fix navigation when leaving room/space by @ganfra in https://github.com/element-hq/element-x-android/pull/5376
* fix (timeline) : forward pagination regression by @ganfra in https://github.com/element-hq/element-x-android/pull/5389
* When joining a call, wait for the `content_loaded` action by @jmartinesp in https://github.com/element-hq/element-x-android/pull/5399
* Ensure the thread summary sender's display name won't wrap to the next line by @jmartinesp in https://github.com/element-hq/element-x-android/pull/5403
### 🗣 Translations
* Sync Strings by @ElementBot in https://github.com/element-hq/element-x-android/pull/5349
* Sync Strings by @ElementBot in https://github.com/element-hq/element-x-android/pull/5385
### 🧱 Build
* Improve release script and the file Versions.kt by @bmarty in https://github.com/element-hq/element-x-android/pull/5318
* Dependency: extract the Matrix SDK and add instructions for upgrading the library by @bmarty in https://github.com/element-hq/element-x-android/pull/5363
* Add test on DefaultSpaceEntryPoint by @bmarty in https://github.com/element-hq/element-x-android/pull/5343
### 🚧 In development 🚧
* Space list by @bmarty in https://github.com/element-hq/element-x-android/pull/5320
* Feature : Join Space (WIP) by @ganfra in https://github.com/element-hq/element-x-android/pull/5378
### Dependency upgrades
* Update activity to v1.11.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/5324
* Update dependency com.google.truth:truth to v1.4.5 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/5322
* Update dependency io.sentry:sentry-android to v8.21.1 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/5310
* Update dependency org.matrix.rustcomponents:sdk-android to v25.9.10 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/5323
* Update dependency androidx.sqlite:sqlite-ktx to v2.6.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/5337
* Update camera to v1.5.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/5336
* Update dependency com.posthog:posthog-android to v3.21.2 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/5333
* Update dependency com.google.testparameterinjector:test-parameter-injector to v1.19 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/5341
* Upgrade Rust SDK bindings to v25.09.15 by @jmartinesp in https://github.com/element-hq/element-x-android/pull/5353
* Update dependency org.matrix.rustcomponents:sdk-android to v25.9.16 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/5359
* Update dependency org.matrix.rustcomponents:sdk-android to v25.9.18 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/5365
* Update telephoto to v0.17.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/5350
* Update dependency org.matrix.rustcomponents:sdk-android to v25.9.19 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/5377
* Update dependency com.google.firebase:firebase-bom to v34.3.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/5367
* Upgrade Element Call embedded dependency to `v0.16.0-rc.4` by @jmartinesp in https://github.com/element-hq/element-x-android/pull/5391
* Update dependencyAnalysis to v3 (major) by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/5194
* Update dependency org.maplibre.gl:android-sdk to v11.13.5 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/5381
* Update dependency org.matrix.rustcomponents:sdk-android to v25.9.23 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/5396
* Update plugin dependencycheck to v12.1.5 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/5382
* Update dependency io.sentry:sentry-android to v8.22.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/5397
### Others
* Cleanup nodes by @bmarty in https://github.com/element-hq/element-x-android/pull/5358
* Complete test on MediaGalleryPresenter by @bmarty in https://github.com/element-hq/element-x-android/pull/5361
* Remove dead code by @bmarty in https://github.com/element-hq/element-x-android/pull/5306
* Introduce BugReportFlowNode, and remove NavTarget.ViewLogs from RootFlowNode by @bmarty in https://github.com/element-hq/element-x-android/pull/5370
* When logging out from Pin code screen, logout from all the sessions. by @bmarty in https://github.com/element-hq/element-x-android/pull/5372
* Clean MatrixAuthenticationService and SessionStore API by @bmarty in https://github.com/element-hq/element-x-android/pull/5371
* Add logs to detect duplicates in the room list by @jmartinesp in https://github.com/element-hq/element-x-android/pull/5364
* Add troubleshoot notification test about blocked users by @bmarty in https://github.com/element-hq/element-x-android/pull/5394
* Add thread decoration with latest event details by @jmartinesp in https://github.com/element-hq/element-x-android/pull/5355
* Rework on messages view top bars by @bmarty in https://github.com/element-hq/element-x-android/pull/5401
* Put developer settings at the end of the view by @p1gp1g in https://github.com/element-hq/element-x-android/pull/5387
## New Contributors
* @p1gp1g made their first contribution in https://github.com/element-hq/element-x-android/pull/5387
**Full Changelog**: https://github.com/element-hq/element-x-android/compare/v25.09.1...v25.09.2
Changes in Element X v25.09.1
=============================

View File

@@ -0,0 +1,2 @@
Main changes in this version: bug fixes and improvements.
Full changelog: https://github.com/element-hq/element-x-android/releases

View File

@@ -36,7 +36,6 @@ import io.element.android.features.messages.impl.timeline.protection.TimelinePro
import io.element.android.features.messages.impl.timeline.protection.aTimelineProtectionState
import io.element.android.features.roomcall.api.RoomCallState
import io.element.android.features.roomcall.api.aStandByCallState
import io.element.android.features.roomcall.api.anOngoingCallState
import io.element.android.features.roommembermoderation.api.RoomMemberModerationEvents
import io.element.android.features.roommembermoderation.api.RoomMemberModerationState
import io.element.android.libraries.architecture.AsyncData
@@ -60,36 +59,29 @@ open class MessagesStateProvider : PreviewParameterProvider<MessagesState> {
aMessagesState(composerState = aMessageComposerState(showAttachmentSourcePicker = true)),
aMessagesState(userEventPermissions = aUserEventPermissions(canSendMessage = false)),
aMessagesState(showReinvitePrompt = true),
aMessagesState(roomName = null),
aMessagesState(composerState = aMessageComposerState(showTextFormatting = true)),
aMessagesState(
voiceMessageComposerState = aVoiceMessageComposerState(showPermissionRationaleDialog = true),
),
aMessagesState(
roomCallState = anOngoingCallState(),
),
aMessagesState(
voiceMessageComposerState = aVoiceMessageComposerState(
voiceMessageState = aVoiceMessagePreviewState(),
showSendFailureDialog = true
),
),
aMessagesState(
roomCallState = aStandByCallState(canStartCall = false),
),
aMessagesState(
pinnedMessagesBannerState = aLoadedPinnedMessagesBannerState(
knownPinnedMessagesCount = 4,
currentPinnedMessageIndex = 0,
),
),
aMessagesState(roomName = "A DM with a very looong name", dmUserVerificationState = IdentityState.Verified),
aMessagesState(roomName = "A DM with a very looong name", dmUserVerificationState = IdentityState.VerificationViolation),
aMessagesState(successorRoom = SuccessorRoom(RoomId("!id:domain"), null)),
aMessagesState(timelineState = aTimelineState(
timelineMode = Timeline.Mode.Thread(threadRootId = ThreadId("\$a-thread-id")),
timelineItems = aTimelineItemList(aTimelineItemTextContent()),
)),
aMessagesState(
timelineState = aTimelineState(
timelineMode = Timeline.Mode.Thread(threadRootId = ThreadId("\$a-thread-id")),
timelineItems = aTimelineItemList(aTimelineItemTextContent()),
)
),
)
}

View File

@@ -11,12 +11,10 @@ import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.expandVertically
import androidx.compose.animation.shrinkVertically
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.consumeWindowInsets
import androidx.compose.foundation.layout.fillMaxSize
@@ -27,30 +25,23 @@ import androidx.compose.foundation.layout.navigationBarsPadding
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.statusBars
import androidx.compose.foundation.layout.systemBarsPadding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.MaterialTheme
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.draw.shadow
import androidx.compose.ui.graphics.RectangleShape
import androidx.compose.ui.platform.LocalView
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.semantics.Role
import androidx.compose.ui.semantics.heading
import androidx.compose.ui.semantics.onClick
import androidx.compose.ui.semantics.role
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.text.font.FontStyle
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.unit.dp
import io.element.android.compound.theme.ElementTheme
import io.element.android.compound.tokens.generated.CompoundIcons
import io.element.android.features.messages.api.timeline.voicemessages.composer.VoiceMessageComposerEvents
import io.element.android.features.messages.impl.actionlist.ActionListEvents
import io.element.android.features.messages.impl.actionlist.ActionListView
@@ -69,7 +60,6 @@ import io.element.android.features.messages.impl.pinned.banner.PinnedMessagesBan
import io.element.android.features.messages.impl.timeline.FOCUS_ON_PINNED_EVENT_DEBOUNCE_DURATION_IN_MILLIS
import io.element.android.features.messages.impl.timeline.TimelineEvents
import io.element.android.features.messages.impl.timeline.TimelineView
import io.element.android.features.messages.impl.timeline.components.CallMenuItem
import io.element.android.features.messages.impl.timeline.components.customreaction.CustomReactionBottomSheet
import io.element.android.features.messages.impl.timeline.components.customreaction.CustomReactionEvents
import io.element.android.features.messages.impl.timeline.components.reactionsummary.ReactionSummaryEvents
@@ -77,30 +67,23 @@ import io.element.android.features.messages.impl.timeline.components.reactionsum
import io.element.android.features.messages.impl.timeline.components.receipt.bottomsheet.ReadReceiptBottomSheet
import io.element.android.features.messages.impl.timeline.components.receipt.bottomsheet.ReadReceiptBottomSheetEvents
import io.element.android.features.messages.impl.timeline.model.TimelineItem
import io.element.android.features.messages.impl.topbars.MessagesViewTopBar
import io.element.android.features.messages.impl.topbars.ThreadTopBar
import io.element.android.features.messages.impl.voicemessages.composer.VoiceMessagePermissionRationaleDialog
import io.element.android.features.messages.impl.voicemessages.composer.VoiceMessageSendingFailedDialog
import io.element.android.features.networkmonitor.api.ui.ConnectivityIndicatorView
import io.element.android.features.roomcall.api.RoomCallState
import io.element.android.libraries.androidutils.ui.hideKeyboard
import io.element.android.libraries.designsystem.atomic.molecules.ComposerAlertMolecule
import io.element.android.libraries.designsystem.components.ExpandableBottomSheetLayout
import io.element.android.libraries.designsystem.components.ExpandableBottomSheetLayoutState
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
import io.element.android.libraries.designsystem.components.button.BackButton
import io.element.android.libraries.designsystem.components.dialogs.ConfirmationDialog
import io.element.android.libraries.designsystem.components.rememberExpandableBottomSheetLayoutState
import io.element.android.libraries.designsystem.preview.ElementPreview
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
import io.element.android.libraries.designsystem.text.toAnnotatedString
import io.element.android.libraries.designsystem.theme.components.BottomSheetDragHandle
import io.element.android.libraries.designsystem.theme.components.HorizontalDivider
import io.element.android.libraries.designsystem.theme.components.Icon
import io.element.android.libraries.designsystem.theme.components.Scaffold
import io.element.android.libraries.designsystem.theme.components.Text
import io.element.android.libraries.designsystem.theme.components.TopAppBar
import io.element.android.libraries.designsystem.utils.HideKeyboardWhenDisposed
import io.element.android.libraries.designsystem.utils.KeepScreenOn
import io.element.android.libraries.designsystem.utils.OnLifecycleEvent
@@ -113,14 +96,9 @@ import io.element.android.libraries.matrix.api.encryption.identity.IdentityState
import io.element.android.libraries.matrix.api.room.tombstone.SuccessorRoom
import io.element.android.libraries.matrix.api.timeline.Timeline
import io.element.android.libraries.matrix.api.user.MatrixUser
import io.element.android.libraries.matrix.ui.components.aMatrixUserList
import io.element.android.libraries.matrix.ui.model.getAvatarData
import io.element.android.libraries.textcomposer.model.TextEditorState
import io.element.android.libraries.ui.strings.CommonStrings
import io.element.android.wysiwyg.link.Link
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.toImmutableList
import timber.log.Timber
import kotlin.time.Duration.Companion.milliseconds
@@ -517,154 +495,6 @@ private fun MessagesViewComposerBottomSheetContents(
}
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
private fun MessagesViewTopBar(
roomName: String?,
roomAvatar: AvatarData,
isTombstoned: Boolean,
heroes: ImmutableList<AvatarData>,
roomCallState: RoomCallState,
dmUserIdentityState: IdentityState?,
onRoomDetailsClick: () -> Unit,
onJoinCallClick: () -> Unit,
onBackClick: () -> Unit,
) {
TopAppBar(
navigationIcon = {
BackButton(onClick = onBackClick)
},
title = {
val roundedCornerShape = RoundedCornerShape(8.dp)
Row(
modifier = Modifier
.clip(roundedCornerShape)
.clickable { onRoomDetailsClick() },
horizontalArrangement = Arrangement.spacedBy(4.dp),
verticalAlignment = Alignment.CenterVertically,
) {
val titleModifier = Modifier.weight(1f, fill = false)
RoomAvatarAndNameRow(
roomName = roomName,
roomAvatar = roomAvatar,
isTombstoned = isTombstoned,
heroes = heroes,
modifier = titleModifier
)
when (dmUserIdentityState) {
IdentityState.Verified -> {
Icon(
imageVector = CompoundIcons.Verified(),
tint = ElementTheme.colors.iconSuccessPrimary,
contentDescription = null,
)
}
IdentityState.VerificationViolation -> {
Icon(
imageVector = CompoundIcons.ErrorSolid(),
tint = ElementTheme.colors.iconCriticalPrimary,
contentDescription = null,
)
}
else -> Unit
}
}
},
actions = {
CallMenuItem(
roomCallState = roomCallState,
onJoinCallClick = onJoinCallClick,
)
Spacer(Modifier.width(8.dp))
},
windowInsets = WindowInsets(0.dp)
)
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
private fun ThreadTopBar(
roomName: String?,
roomAvatarData: AvatarData,
heroes: ImmutableList<AvatarData>,
isTombstoned: Boolean,
onBackClick: () -> Unit,
modifier: Modifier = Modifier,
) {
TopAppBar(
modifier = modifier,
navigationIcon = {
BackButton(onClick = onBackClick)
},
title = {
Row(verticalAlignment = Alignment.CenterVertically) {
Avatar(
avatarData = roomAvatarData,
avatarType = AvatarType.Room(
heroes = heroes,
isTombstoned = isTombstoned,
),
)
Column(
modifier = Modifier.fillMaxWidth()
.padding(horizontal = 8.dp)
.semantics {
heading()
},
) {
Text(
text = stringResource(CommonStrings.common_thread),
style = ElementTheme.typography.fontBodyLgMedium,
)
Text(
text = roomName ?: stringResource(CommonStrings.common_no_room_name),
style = ElementTheme.typography.fontBodySmRegular,
fontStyle = FontStyle.Italic.takeIf { roomName == null },
color = ElementTheme.colors.textSecondary,
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
}
}
}
)
}
@Composable
private fun RoomAvatarAndNameRow(
roomName: String?,
roomAvatar: AvatarData,
heroes: ImmutableList<AvatarData>,
isTombstoned: Boolean,
modifier: Modifier = Modifier
) {
Row(
modifier = modifier,
verticalAlignment = Alignment.CenterVertically
) {
Avatar(
avatarData = roomAvatar,
avatarType = AvatarType.Room(
heroes = heroes,
isTombstoned = isTombstoned,
),
)
Text(
modifier = Modifier
.padding(horizontal = 8.dp)
.semantics {
heading()
},
text = roomName ?: stringResource(CommonStrings.common_no_room_name),
style = ElementTheme.typography.fontBodyLgMedium,
fontStyle = FontStyle.Italic.takeIf { roomName == null },
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
}
}
@Composable
private fun CantSendMessageBanner() {
Row(
@@ -719,58 +549,3 @@ internal fun MessagesViewPreview(@PreviewParameter(MessagesStateProvider::class)
knockRequestsBannerView = {},
)
}
@PreviewsDayNight
@Composable
internal fun ThreadTopBarPreview() {
ElementPreview {
val name = "Room name"
val initialsAvatarData = AvatarData(
id = "id",
name = name,
url = null,
size = AvatarSize.TimelineRoom,
)
Column {
ThreadTopBar(
roomName = name,
roomAvatarData = initialsAvatarData,
heroes = persistentListOf(),
isTombstoned = false,
onBackClick = {},
)
HorizontalDivider()
ThreadTopBar(
roomName = name,
roomAvatarData = initialsAvatarData,
heroes = aMatrixUserList().map { it.getAvatarData(AvatarSize.TimelineRoom) }.toImmutableList(),
isTombstoned = false,
onBackClick = {},
)
HorizontalDivider()
ThreadTopBar(
roomName = null,
roomAvatarData = initialsAvatarData,
heroes = persistentListOf(),
isTombstoned = false,
onBackClick = {},
)
HorizontalDivider()
ThreadTopBar(
roomName = name,
roomAvatarData = initialsAvatarData.copy(url = "https://some-avatar.jpg"),
heroes = persistentListOf(),
isTombstoned = false,
onBackClick = {},
)
HorizontalDivider()
ThreadTopBar(
roomName = name,
roomAvatarData = initialsAvatarData,
heroes = persistentListOf(),
isTombstoned = true,
onBackClick = {},
)
}
}
}

View File

@@ -0,0 +1,211 @@
/*
* Copyright 2025 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
* Please see LICENSE files in the repository root for full details.
*/
package io.element.android.features.messages.impl.topbars
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.ExperimentalMaterial3Api
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.res.stringResource
import androidx.compose.ui.semantics.heading
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.text.font.FontStyle
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import io.element.android.compound.theme.ElementTheme
import io.element.android.compound.tokens.generated.CompoundIcons
import io.element.android.features.messages.impl.timeline.components.CallMenuItem
import io.element.android.features.roomcall.api.RoomCallState
import io.element.android.features.roomcall.api.aStandByCallState
import io.element.android.features.roomcall.api.anOngoingCallState
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
import io.element.android.libraries.designsystem.components.avatar.anAvatarData
import io.element.android.libraries.designsystem.components.button.BackButton
import io.element.android.libraries.designsystem.preview.ElementPreview
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
import io.element.android.libraries.designsystem.theme.components.HorizontalDivider
import io.element.android.libraries.designsystem.theme.components.Icon
import io.element.android.libraries.designsystem.theme.components.Text
import io.element.android.libraries.designsystem.theme.components.TopAppBar
import io.element.android.libraries.matrix.api.encryption.identity.IdentityState
import io.element.android.libraries.matrix.ui.components.aMatrixUserList
import io.element.android.libraries.matrix.ui.model.getAvatarData
import io.element.android.libraries.ui.strings.CommonStrings
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.toImmutableList
@OptIn(ExperimentalMaterial3Api::class)
@Composable
internal fun MessagesViewTopBar(
roomName: String?,
roomAvatar: AvatarData,
isTombstoned: Boolean,
heroes: ImmutableList<AvatarData>,
roomCallState: RoomCallState,
dmUserIdentityState: IdentityState?,
onRoomDetailsClick: () -> Unit,
onJoinCallClick: () -> Unit,
onBackClick: () -> Unit,
modifier: Modifier = Modifier,
) {
TopAppBar(
modifier = modifier,
navigationIcon = {
BackButton(onClick = onBackClick)
},
title = {
val roundedCornerShape = RoundedCornerShape(8.dp)
Row(
modifier = Modifier
.clip(roundedCornerShape)
.clickable { onRoomDetailsClick() },
horizontalArrangement = Arrangement.spacedBy(4.dp),
verticalAlignment = Alignment.CenterVertically,
) {
val titleModifier = Modifier.weight(1f, fill = false)
RoomAvatarAndNameRow(
roomName = roomName,
roomAvatar = roomAvatar,
isTombstoned = isTombstoned,
heroes = heroes,
modifier = titleModifier
)
when (dmUserIdentityState) {
IdentityState.Verified -> {
Icon(
imageVector = CompoundIcons.Verified(),
tint = ElementTheme.colors.iconSuccessPrimary,
contentDescription = null,
)
}
IdentityState.VerificationViolation -> {
Icon(
imageVector = CompoundIcons.ErrorSolid(),
tint = ElementTheme.colors.iconCriticalPrimary,
contentDescription = null,
)
}
else -> Unit
}
}
},
actions = {
CallMenuItem(
roomCallState = roomCallState,
onJoinCallClick = onJoinCallClick,
)
Spacer(Modifier.width(8.dp))
},
windowInsets = WindowInsets(0.dp)
)
}
@Composable
private fun RoomAvatarAndNameRow(
roomName: String?,
roomAvatar: AvatarData,
heroes: ImmutableList<AvatarData>,
isTombstoned: Boolean,
modifier: Modifier = Modifier
) {
Row(
modifier = modifier,
verticalAlignment = Alignment.CenterVertically
) {
Avatar(
avatarData = roomAvatar,
avatarType = AvatarType.Room(
heroes = heroes,
isTombstoned = isTombstoned,
),
)
Text(
modifier = Modifier
.padding(horizontal = 8.dp)
.semantics {
heading()
},
text = roomName ?: stringResource(CommonStrings.common_no_room_name),
style = ElementTheme.typography.fontBodyLgMedium,
fontStyle = FontStyle.Italic.takeIf { roomName == null },
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
}
}
@PreviewsDayNight
@Composable
internal fun MessagesViewTopBarPreview() = ElementPreview {
@Composable
fun AMessagesViewTopBar(
roomName: String? = "Room name",
roomAvatar: AvatarData = anAvatarData(
name = "Room name",
size = AvatarSize.TimelineRoom,
),
isTombstoned: Boolean = false,
heroes: ImmutableList<AvatarData> = persistentListOf(),
roomCallState: RoomCallState = RoomCallState.Unavailable,
dmUserIdentityState: IdentityState? = null,
) = MessagesViewTopBar(
roomName = roomName,
roomAvatar = roomAvatar,
isTombstoned = isTombstoned,
heroes = heroes,
roomCallState = roomCallState,
dmUserIdentityState = dmUserIdentityState,
onRoomDetailsClick = {},
onJoinCallClick = {},
onBackClick = {},
)
Column {
AMessagesViewTopBar()
HorizontalDivider()
AMessagesViewTopBar(
heroes = aMatrixUserList().map { it.getAvatarData(AvatarSize.TimelineRoom) }.toImmutableList(),
roomCallState = anOngoingCallState(),
)
HorizontalDivider()
AMessagesViewTopBar(
roomName = null,
roomCallState = anOngoingCallState(canJoinCall = false),
)
HorizontalDivider()
AMessagesViewTopBar(
roomName = "A DM with a very very very long name",
roomAvatar = anAvatarData(
size = AvatarSize.TimelineRoom,
url = "https://some-avatar.jpg"
),
roomCallState = aStandByCallState(canStartCall = false),
dmUserIdentityState = IdentityState.Verified
)
HorizontalDivider()
AMessagesViewTopBar(
roomName = "A DM with a very very very long name",
isTombstoned = true,
dmUserIdentityState = IdentityState.VerificationViolation
)
}
}

View File

@@ -0,0 +1,135 @@
/*
* Copyright 2025 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
* Please see LICENSE files in the repository root for full details.
*/
package io.element.android.features.messages.impl.topbars
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.semantics.heading
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.text.font.FontStyle
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import io.element.android.compound.theme.ElementTheme
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
import io.element.android.libraries.designsystem.components.avatar.anAvatarData
import io.element.android.libraries.designsystem.components.button.BackButton
import io.element.android.libraries.designsystem.preview.ElementPreview
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
import io.element.android.libraries.designsystem.theme.components.HorizontalDivider
import io.element.android.libraries.designsystem.theme.components.Text
import io.element.android.libraries.designsystem.theme.components.TopAppBar
import io.element.android.libraries.matrix.ui.components.aMatrixUserList
import io.element.android.libraries.matrix.ui.model.getAvatarData
import io.element.android.libraries.ui.strings.CommonStrings
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.toImmutableList
@OptIn(ExperimentalMaterial3Api::class)
@Composable
internal fun ThreadTopBar(
roomName: String?,
roomAvatarData: AvatarData,
heroes: ImmutableList<AvatarData>,
isTombstoned: Boolean,
onBackClick: () -> Unit,
modifier: Modifier = Modifier,
) {
TopAppBar(
modifier = modifier,
navigationIcon = {
BackButton(onClick = onBackClick)
},
title = {
Row(verticalAlignment = Alignment.CenterVertically) {
Avatar(
avatarData = roomAvatarData,
avatarType = AvatarType.Room(
heroes = heroes,
isTombstoned = isTombstoned,
),
)
Column(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 8.dp)
.semantics {
heading()
},
) {
Text(
text = stringResource(CommonStrings.common_thread),
style = ElementTheme.typography.fontBodyLgMedium,
)
Text(
text = roomName ?: stringResource(CommonStrings.common_no_room_name),
style = ElementTheme.typography.fontBodySmRegular,
fontStyle = FontStyle.Italic.takeIf { roomName == null },
color = ElementTheme.colors.textSecondary,
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
}
}
}
)
}
@PreviewsDayNight
@Composable
internal fun ThreadTopBarPreview() = ElementPreview {
@Composable
fun AThreadTopBar(
roomName: String? = "Room name",
roomAvatarData: AvatarData = anAvatarData(
name = "Room name",
size = AvatarSize.TimelineRoom,
),
isTombstoned: Boolean = false,
heroes: ImmutableList<AvatarData> = persistentListOf(),
) = ThreadTopBar(
roomName = roomName,
roomAvatarData = roomAvatarData,
isTombstoned = isTombstoned,
heroes = heroes,
onBackClick = {},
)
Column {
AThreadTopBar()
HorizontalDivider()
AThreadTopBar(
heroes = aMatrixUserList().map { it.getAvatarData(AvatarSize.TimelineRoom) }.toImmutableList(),
)
HorizontalDivider()
AThreadTopBar(
roomName = null,
)
HorizontalDivider()
AThreadTopBar(
roomAvatarData = anAvatarData(
name = "Room name",
url = "https://some-avatar.jpg",
size = AvatarSize.TimelineRoom,
),
)
HorizontalDivider()
AThreadTopBar(
isTombstoned = true,
)
}
}

View File

@@ -214,9 +214,6 @@ private fun ColumnScope.GeneralSection(
leadingContent = ListItemContent.Icon(IconSource.Vector(CompoundIcons.Settings())),
onClick = onOpenAdvancedSettings,
)
if (state.showDeveloperSettings) {
DeveloperPreferencesView(onOpenDeveloperSettings)
}
ListItem(
headlineContent = { Text(stringResource(id = CommonStrings.action_signout)) },
leadingContent = ListItemContent.Icon(IconSource.Vector(CompoundIcons.SignOut())),
@@ -231,6 +228,10 @@ private fun ColumnScope.GeneralSection(
onClick = onDeactivateClick,
)
}
// Put developer settings at the end, so nothing bad happens if the user clicks 8 times to enable the entry
if (state.showDeveloperSettings) {
DeveloperPreferencesView(onOpenDeveloperSettings)
}
}
@Composable

View File

@@ -206,7 +206,7 @@ haze_materials = { module = "dev.chrisbanes.haze:haze-materials", version.ref =
# Analytics
posthog = "com.posthog:posthog-android:3.21.2"
sentry = "io.sentry:sentry-android:8.21.1"
sentry = "io.sentry:sentry-android:8.22.0"
# main branch can be tested replacing the version with main-SNAPSHOT
matrix_analytics_events = "com.github.matrix-org:matrix-analytics-events:0.28.0"
@@ -241,7 +241,7 @@ metro = { id = "dev.zacsweers.metro", version.ref = "metro" }
detekt = { id = "io.gitlab.arturbosch.detekt", version.ref = "detekt" }
ktlint = "org.jlleitschuh.gradle.ktlint:13.1.0"
dependencygraph = "com.savvasdalkitsis.module-dependency-graph:0.12"
dependencycheck = "org.owasp.dependencycheck:12.1.3"
dependencycheck = "org.owasp.dependencycheck:12.1.5"
dependencyanalysis = { id = "com.autonomousapps.dependency-analysis", version.ref = "dependencyAnalysis" }
paparazzi = "app.cash.paparazzi:2.0.0-alpha02"
sqldelight = { id = "app.cash.sqldelight", version.ref = "sqldelight" }

View File

@@ -44,7 +44,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 = 1
private const val versionReleaseNumber = 2
object Versions {
/**

View File

@@ -146,7 +146,7 @@ printf "Creating fastlane file...\n"
printf -v versionReleaseNumber2Digits "%02d" "${versionReleaseNumber}"
fastlaneFile="20${versionYear}${versionMonth}${versionReleaseNumber2Digits}0.txt"
fastlanePathFile="./fastlane/metadata/android/en-US/changelogs/${fastlaneFile}"
printf "Main changes in this version: TODO.\nFull changelog: https://github.com/element-hq/element-x-android/releases" > "${fastlanePathFile}"
printf "Main changes in this version: bug fixes and improvements.\nFull changelog: https://github.com/element-hq/element-x-android/releases" > "${fastlanePathFile}"
read -r -p "I have created the file ${fastlanePathFile}, please edit it and press enter to continue. "
git add "${fastlanePathFile}"