RoomList : invite row
This commit is contained in:
parent
2c6620a199
commit
36f0dfa694
12 changed files with 382 additions and 110 deletions
|
|
@ -214,7 +214,7 @@ class RoomListPresenter @Inject constructor(
|
||||||
val initialState = RoomListState.ContextMenu.Shown(
|
val initialState = RoomListState.ContextMenu.Shown(
|
||||||
roomId = event.roomListRoomSummary.roomId,
|
roomId = event.roomListRoomSummary.roomId,
|
||||||
roomName = event.roomListRoomSummary.name,
|
roomName = event.roomListRoomSummary.name,
|
||||||
isDm = event.roomListRoomSummary.isDm,
|
isDm = event.roomListRoomSummary.isDirect,
|
||||||
isFavorite = event.roomListRoomSummary.isFavorite,
|
isFavorite = event.roomListRoomSummary.isFavorite,
|
||||||
markAsUnreadFeatureFlagEnabled = featureFlagService.isFeatureEnabled(FeatureFlags.MarkAsUnread),
|
markAsUnreadFeatureFlagEnabled = featureFlagService.isFeatureEnabled(FeatureFlags.MarkAsUnread),
|
||||||
hasNewContent = event.roomListRoomSummary.hasNewContent
|
hasNewContent = event.roomListRoomSummary.hasNewContent
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@ import io.element.android.features.leaveroom.api.LeaveRoomState
|
||||||
import io.element.android.features.leaveroom.api.aLeaveRoomState
|
import io.element.android.features.leaveroom.api.aLeaveRoomState
|
||||||
import io.element.android.features.roomlist.impl.filters.RoomListFiltersState
|
import io.element.android.features.roomlist.impl.filters.RoomListFiltersState
|
||||||
import io.element.android.features.roomlist.impl.filters.aRoomListFiltersState
|
import io.element.android.features.roomlist.impl.filters.aRoomListFiltersState
|
||||||
|
import io.element.android.features.roomlist.impl.model.DisplayType
|
||||||
import io.element.android.features.roomlist.impl.model.RoomListRoomSummary
|
import io.element.android.features.roomlist.impl.model.RoomListRoomSummary
|
||||||
import io.element.android.features.roomlist.impl.model.aRoomListRoomSummary
|
import io.element.android.features.roomlist.impl.model.aRoomListRoomSummary
|
||||||
import io.element.android.features.roomlist.impl.search.RoomListSearchState
|
import io.element.android.features.roomlist.impl.search.RoomListSearchState
|
||||||
|
|
@ -98,11 +99,11 @@ internal fun aRoomListRoomSummaryList(): ImmutableList<RoomListRoomSummary> {
|
||||||
),
|
),
|
||||||
aRoomListRoomSummary(
|
aRoomListRoomSummary(
|
||||||
id = "!roomId3:domain",
|
id = "!roomId3:domain",
|
||||||
isPlaceholder = true,
|
displayType = DisplayType.PLACEHOLDER,
|
||||||
),
|
),
|
||||||
aRoomListRoomSummary(
|
aRoomListRoomSummary(
|
||||||
id = "!roomId4:domain",
|
id = "!roomId4:domain",
|
||||||
isPlaceholder = true,
|
displayType = DisplayType.PLACEHOLDER,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -180,7 +180,7 @@ private fun RoomListScaffold(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun RoomListRoomSummary.contentType() = isPlaceholder
|
internal fun RoomListRoomSummary.contentType() = type.ordinal
|
||||||
|
|
||||||
@PreviewsDayNight
|
@PreviewsDayNight
|
||||||
@Composable
|
@Composable
|
||||||
|
|
|
||||||
|
|
@ -20,15 +20,18 @@ import androidx.compose.foundation.ExperimentalFoundationApi
|
||||||
import androidx.compose.foundation.combinedClickable
|
import androidx.compose.foundation.combinedClickable
|
||||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Arrangement.Absolute.spacedBy
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.ColumnScope
|
||||||
import androidx.compose.foundation.layout.IntrinsicSize
|
import androidx.compose.foundation.layout.IntrinsicSize
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.RowScope
|
import androidx.compose.foundation.layout.Spacer
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.height
|
import androidx.compose.foundation.layout.height
|
||||||
import androidx.compose.foundation.layout.heightIn
|
import androidx.compose.foundation.layout.heightIn
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.size
|
import androidx.compose.foundation.layout.size
|
||||||
|
import androidx.compose.foundation.layout.width
|
||||||
import androidx.compose.material.ripple.rememberRipple
|
import androidx.compose.material.ripple.rememberRipple
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
|
@ -36,12 +39,15 @@ import androidx.compose.runtime.remember
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.text.AnnotatedString
|
import androidx.compose.ui.text.AnnotatedString
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import io.element.android.compound.theme.ElementTheme
|
import io.element.android.compound.theme.ElementTheme
|
||||||
import io.element.android.compound.tokens.generated.CompoundIcons
|
import io.element.android.compound.tokens.generated.CompoundIcons
|
||||||
|
import io.element.android.features.roomlist.impl.model.DisplayType
|
||||||
|
import io.element.android.features.roomlist.impl.model.InviteSender
|
||||||
import io.element.android.features.roomlist.impl.model.RoomListRoomSummary
|
import io.element.android.features.roomlist.impl.model.RoomListRoomSummary
|
||||||
import io.element.android.features.roomlist.impl.model.RoomListRoomSummaryProvider
|
import io.element.android.features.roomlist.impl.model.RoomListRoomSummaryProvider
|
||||||
import io.element.android.libraries.core.extensions.orEmpty
|
import io.element.android.libraries.core.extensions.orEmpty
|
||||||
|
|
@ -49,13 +55,17 @@ import io.element.android.libraries.designsystem.atomic.atoms.UnreadIndicatorAto
|
||||||
import io.element.android.libraries.designsystem.components.avatar.Avatar
|
import io.element.android.libraries.designsystem.components.avatar.Avatar
|
||||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||||
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||||
|
import io.element.android.libraries.designsystem.theme.components.Button
|
||||||
|
import io.element.android.libraries.designsystem.theme.components.ButtonSize
|
||||||
import io.element.android.libraries.designsystem.theme.components.Icon
|
import io.element.android.libraries.designsystem.theme.components.Icon
|
||||||
|
import io.element.android.libraries.designsystem.theme.components.OutlinedButton
|
||||||
import io.element.android.libraries.designsystem.theme.components.Text
|
import io.element.android.libraries.designsystem.theme.components.Text
|
||||||
import io.element.android.libraries.designsystem.theme.roomListRoomMessage
|
import io.element.android.libraries.designsystem.theme.roomListRoomMessage
|
||||||
import io.element.android.libraries.designsystem.theme.roomListRoomMessageDate
|
import io.element.android.libraries.designsystem.theme.roomListRoomMessageDate
|
||||||
import io.element.android.libraries.designsystem.theme.roomListRoomName
|
import io.element.android.libraries.designsystem.theme.roomListRoomName
|
||||||
import io.element.android.libraries.designsystem.theme.unreadIndicator
|
import io.element.android.libraries.designsystem.theme.unreadIndicator
|
||||||
import io.element.android.libraries.matrix.api.room.RoomNotificationMode
|
import io.element.android.libraries.matrix.api.room.RoomNotificationMode
|
||||||
|
import io.element.android.libraries.ui.strings.CommonStrings
|
||||||
|
|
||||||
internal val minHeight = 84.dp
|
internal val minHeight = 84.dp
|
||||||
|
|
||||||
|
|
@ -66,27 +76,53 @@ internal fun RoomSummaryRow(
|
||||||
onLongClick: (RoomListRoomSummary) -> Unit,
|
onLongClick: (RoomListRoomSummary) -> Unit,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
if (room.isPlaceholder) {
|
when (room.type) {
|
||||||
RoomSummaryPlaceholderRow(
|
DisplayType.PLACEHOLDER -> {
|
||||||
modifier = modifier,
|
RoomSummaryPlaceholderRow(modifier = modifier)
|
||||||
)
|
}
|
||||||
} else {
|
DisplayType.INVITE -> {
|
||||||
RoomSummaryRealRow(
|
RoomSummaryScaffoldRow(
|
||||||
room = room,
|
room = room,
|
||||||
onClick = onClick,
|
onClick = onClick,
|
||||||
onLongClick = onLongClick,
|
onLongClick = onLongClick,
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
)
|
) {
|
||||||
|
InviteNameAndIndicatorRow(name = room.name)
|
||||||
|
InviteSubtitle(isDirect = room.isDirect, inviteSender = room.inviteSender, canonicalAlias = room.canonicalAlias)
|
||||||
|
if (!room.isDirect && room.inviteSender != null) {
|
||||||
|
Spacer(modifier = Modifier.height(4.dp))
|
||||||
|
InviteSenderRow(sender = room.inviteSender)
|
||||||
|
}
|
||||||
|
Spacer(modifier = Modifier.height(12.dp))
|
||||||
|
InviteButtonsRow(onAcceptClicked = { }, onDeclineClicked = { })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DisplayType.ROOM -> {
|
||||||
|
RoomSummaryScaffoldRow(
|
||||||
|
room = room,
|
||||||
|
onClick = onClick,
|
||||||
|
onLongClick = onLongClick,
|
||||||
|
modifier = modifier
|
||||||
|
) {
|
||||||
|
NameAndTimestampRow(
|
||||||
|
name = room.name,
|
||||||
|
timestamp = room.timestamp,
|
||||||
|
isHighlighted = room.isHighlighted
|
||||||
|
)
|
||||||
|
LastMessageAndIndicatorRow(room = room)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@OptIn(ExperimentalFoundationApi::class)
|
@OptIn(ExperimentalFoundationApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
private fun RoomSummaryRealRow(
|
private fun RoomSummaryScaffoldRow(
|
||||||
room: RoomListRoomSummary,
|
room: RoomListRoomSummary,
|
||||||
onClick: (RoomListRoomSummary) -> Unit,
|
onClick: (RoomListRoomSummary) -> Unit,
|
||||||
onLongClick: (RoomListRoomSummary) -> Unit,
|
onLongClick: (RoomListRoomSummary) -> Unit,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
|
content: @Composable ColumnScope.() -> Unit
|
||||||
) {
|
) {
|
||||||
val clickModifier = Modifier.combinedClickable(
|
val clickModifier = Modifier.combinedClickable(
|
||||||
onClick = { onClick(room) },
|
onClick = { onClick(room) },
|
||||||
|
|
@ -97,97 +133,189 @@ private fun RoomSummaryRealRow(
|
||||||
|
|
||||||
Row(
|
Row(
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
.fillMaxWidth()
|
|
||||||
.heightIn(min = minHeight)
|
|
||||||
.then(clickModifier)
|
|
||||||
.fillMaxWidth()
|
|
||||||
.padding(horizontal = 16.dp, vertical = 11.dp)
|
|
||||||
.height(IntrinsicSize.Min),
|
|
||||||
) {
|
|
||||||
Avatar(
|
|
||||||
room
|
|
||||||
.avatarData,
|
|
||||||
modifier = Modifier
|
|
||||||
.align(Alignment.CenterVertically)
|
|
||||||
)
|
|
||||||
Column(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(start = 16.dp)
|
.heightIn(min = minHeight)
|
||||||
|
.then(clickModifier)
|
||||||
|
.padding(horizontal = 16.dp, vertical = 11.dp)
|
||||||
|
.height(IntrinsicSize.Min),
|
||||||
|
) {
|
||||||
|
Avatar(room.avatarData)
|
||||||
|
Spacer(modifier = Modifier.width(16.dp))
|
||||||
|
Column(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
content = content,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun NameAndTimestampRow(
|
||||||
|
name: String,
|
||||||
|
timestamp: String?,
|
||||||
|
isHighlighted: Boolean,
|
||||||
|
modifier: Modifier = Modifier
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
modifier = modifier.fillMaxWidth(),
|
||||||
|
horizontalArrangement = spacedBy(16.dp)
|
||||||
|
) {
|
||||||
|
// Name
|
||||||
|
Text(
|
||||||
|
modifier = Modifier.weight(1f),
|
||||||
|
style = ElementTheme.typography.fontBodyLgMedium,
|
||||||
|
text = name,
|
||||||
|
color = MaterialTheme.roomListRoomName(),
|
||||||
|
maxLines = 1,
|
||||||
|
overflow = TextOverflow.Ellipsis
|
||||||
|
)
|
||||||
|
// Timestamp
|
||||||
|
Text(
|
||||||
|
text = timestamp ?: "",
|
||||||
|
style = ElementTheme.typography.fontBodySmMedium,
|
||||||
|
color = if (isHighlighted) {
|
||||||
|
ElementTheme.colors.unreadIndicator
|
||||||
|
} else {
|
||||||
|
MaterialTheme.roomListRoomMessageDate()
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun InviteSubtitle(
|
||||||
|
isDirect: Boolean,
|
||||||
|
inviteSender: InviteSender?,
|
||||||
|
canonicalAlias: String?,
|
||||||
|
modifier: Modifier = Modifier
|
||||||
|
) {
|
||||||
|
val subtitle = if (isDirect) {
|
||||||
|
inviteSender?.userId?.value
|
||||||
|
} else {
|
||||||
|
canonicalAlias
|
||||||
|
}
|
||||||
|
if (subtitle != null) {
|
||||||
|
Text(
|
||||||
|
text = subtitle,
|
||||||
|
maxLines = 1,
|
||||||
|
overflow = TextOverflow.Ellipsis,
|
||||||
|
style = ElementTheme.typography.fontBodyMdRegular,
|
||||||
|
color = MaterialTheme.roomListRoomMessage(),
|
||||||
|
modifier = modifier,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun LastMessageAndIndicatorRow(
|
||||||
|
room: RoomListRoomSummary,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
modifier = modifier.fillMaxWidth(),
|
||||||
|
horizontalArrangement = spacedBy(28.dp)
|
||||||
|
) {
|
||||||
|
// Last Message
|
||||||
|
val attributedLastMessage = room.lastMessage as? AnnotatedString
|
||||||
|
?: AnnotatedString(room.lastMessage.orEmpty().toString())
|
||||||
|
Text(
|
||||||
|
modifier = Modifier.weight(1f),
|
||||||
|
text = attributedLastMessage,
|
||||||
|
color = MaterialTheme.roomListRoomMessage(),
|
||||||
|
style = ElementTheme.typography.fontBodyMdRegular,
|
||||||
|
minLines = 2,
|
||||||
|
maxLines = 2,
|
||||||
|
overflow = TextOverflow.Ellipsis
|
||||||
|
)
|
||||||
|
// Call and unread
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.height(16.dp),
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
) {
|
) {
|
||||||
Row(modifier = Modifier.fillMaxWidth()) {
|
val tint = if (room.isHighlighted) ElementTheme.colors.unreadIndicator else ElementTheme.colors.iconQuaternary
|
||||||
NameAndTimestampRow(room = room)
|
if (room.hasRoomCall) {
|
||||||
|
OnGoingCallIcon(
|
||||||
|
color = tint,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
Row(modifier = Modifier.fillMaxWidth()) {
|
if (room.userDefinedNotificationMode == RoomNotificationMode.MUTE) {
|
||||||
LastMessageAndIndicatorRow(room = room)
|
NotificationOffIndicatorAtom()
|
||||||
|
} else if (room.numberOfUnreadMentions > 0) {
|
||||||
|
MentionIndicatorAtom()
|
||||||
|
}
|
||||||
|
if (room.hasNewContent) {
|
||||||
|
UnreadIndicatorAtom(
|
||||||
|
color = tint
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun RowScope.NameAndTimestampRow(room: RoomListRoomSummary) {
|
private fun InviteNameAndIndicatorRow(
|
||||||
// Name
|
name: String,
|
||||||
Text(
|
modifier: Modifier = Modifier,
|
||||||
modifier = Modifier
|
) {
|
||||||
.weight(1f)
|
Row(
|
||||||
.padding(end = 16.dp),
|
modifier = modifier.fillMaxWidth(),
|
||||||
style = ElementTheme.typography.fontBodyLgMedium,
|
horizontalArrangement = spacedBy(16.dp),
|
||||||
text = room.name,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
color = MaterialTheme.roomListRoomName(),
|
) {
|
||||||
maxLines = 1,
|
Text(
|
||||||
overflow = TextOverflow.Ellipsis
|
modifier = Modifier.weight(1f),
|
||||||
)
|
style = ElementTheme.typography.fontBodyLgMedium,
|
||||||
// Timestamp
|
text = name,
|
||||||
Text(
|
color = MaterialTheme.roomListRoomName(),
|
||||||
text = room.timestamp ?: "",
|
maxLines = 1,
|
||||||
style = ElementTheme.typography.fontBodySmMedium,
|
overflow = TextOverflow.Ellipsis
|
||||||
color = if (room.isHighlighted) {
|
)
|
||||||
ElementTheme.colors.unreadIndicator
|
UnreadIndicatorAtom(
|
||||||
} else {
|
color = ElementTheme.colors.unreadIndicator
|
||||||
MaterialTheme.roomListRoomMessageDate()
|
)
|
||||||
},
|
}
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun RowScope.LastMessageAndIndicatorRow(room: RoomListRoomSummary) {
|
private fun InviteSenderRow(
|
||||||
// Last Message
|
sender: InviteSender,
|
||||||
val attributedLastMessage = room.lastMessage as? AnnotatedString
|
modifier: Modifier = Modifier
|
||||||
?: AnnotatedString(room.lastMessage.orEmpty().toString())
|
) {
|
||||||
Text(
|
|
||||||
modifier = Modifier
|
|
||||||
.weight(1f)
|
|
||||||
.padding(end = 28.dp),
|
|
||||||
text = attributedLastMessage,
|
|
||||||
color = MaterialTheme.roomListRoomMessage(),
|
|
||||||
style = ElementTheme.typography.fontBodyMdRegular,
|
|
||||||
minLines = 2,
|
|
||||||
maxLines = 2,
|
|
||||||
overflow = TextOverflow.Ellipsis
|
|
||||||
)
|
|
||||||
// Call and unread
|
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier.height(16.dp),
|
horizontalArrangement = Arrangement.spacedBy(4.dp),
|
||||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
modifier = modifier.fillMaxWidth(),
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
|
||||||
) {
|
) {
|
||||||
val tint = if (room.isHighlighted) ElementTheme.colors.unreadIndicator else ElementTheme.colors.iconQuaternary
|
Avatar(avatarData = sender.avatarData)
|
||||||
if (room.hasRoomCall) {
|
Text(
|
||||||
OnGoingCallIcon(
|
text = sender.annotatedString(),
|
||||||
color = tint,
|
style = ElementTheme.typography.fontBodyMdRegular,
|
||||||
)
|
color = MaterialTheme.colorScheme.secondary,
|
||||||
}
|
)
|
||||||
if (room.userDefinedNotificationMode == RoomNotificationMode.MUTE) {
|
}
|
||||||
NotificationOffIndicatorAtom()
|
}
|
||||||
} else if (room.numberOfUnreadMentions > 0) {
|
|
||||||
MentionIndicatorAtom()
|
@Composable
|
||||||
}
|
private fun InviteButtonsRow(
|
||||||
if (room.hasNewContent) {
|
onAcceptClicked: () -> Unit,
|
||||||
UnreadIndicatorAtom(
|
onDeclineClicked: () -> Unit,
|
||||||
color = tint
|
modifier: Modifier = Modifier
|
||||||
)
|
) {
|
||||||
}
|
Row(
|
||||||
|
modifier = modifier.padding(),
|
||||||
|
horizontalArrangement = spacedBy(12.dp)
|
||||||
|
) {
|
||||||
|
OutlinedButton(
|
||||||
|
text = stringResource(CommonStrings.action_decline),
|
||||||
|
onClick = onDeclineClicked,
|
||||||
|
size = ButtonSize.Medium,
|
||||||
|
modifier = Modifier.weight(1f),
|
||||||
|
)
|
||||||
|
Button(
|
||||||
|
text = stringResource(CommonStrings.action_accept),
|
||||||
|
onClick = onAcceptClicked,
|
||||||
|
size = ButtonSize.Medium,
|
||||||
|
modifier = Modifier.weight(1f),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -16,13 +16,16 @@
|
||||||
|
|
||||||
package io.element.android.features.roomlist.impl.datasource
|
package io.element.android.features.roomlist.impl.datasource
|
||||||
|
|
||||||
|
import io.element.android.features.roomlist.impl.model.InviteSender
|
||||||
import io.element.android.features.roomlist.impl.model.RoomListRoomSummary
|
import io.element.android.features.roomlist.impl.model.RoomListRoomSummary
|
||||||
|
import io.element.android.features.roomlist.impl.model.DisplayType
|
||||||
import io.element.android.libraries.core.extensions.orEmpty
|
import io.element.android.libraries.core.extensions.orEmpty
|
||||||
import io.element.android.libraries.dateformatter.api.LastMessageTimestampFormatter
|
import io.element.android.libraries.dateformatter.api.LastMessageTimestampFormatter
|
||||||
import io.element.android.libraries.designsystem.components.avatar.AvatarData
|
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.AvatarSize
|
||||||
import io.element.android.libraries.eventformatter.api.RoomLastMessageFormatter
|
import io.element.android.libraries.eventformatter.api.RoomLastMessageFormatter
|
||||||
import io.element.android.libraries.matrix.api.core.RoomId
|
import io.element.android.libraries.matrix.api.core.RoomId
|
||||||
|
import io.element.android.libraries.matrix.api.room.CurrentUserMembership
|
||||||
import io.element.android.libraries.matrix.api.roomlist.RoomSummary
|
import io.element.android.libraries.matrix.api.roomlist.RoomSummary
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
|
@ -35,7 +38,7 @@ class RoomListRoomSummaryFactory @Inject constructor(
|
||||||
return RoomListRoomSummary(
|
return RoomListRoomSummary(
|
||||||
id = id,
|
id = id,
|
||||||
roomId = RoomId(id),
|
roomId = RoomId(id),
|
||||||
isPlaceholder = true,
|
type = DisplayType.PLACEHOLDER,
|
||||||
name = "Short name",
|
name = "Short name",
|
||||||
timestamp = "hh:mm",
|
timestamp = "hh:mm",
|
||||||
lastMessage = "Last message for placeholder",
|
lastMessage = "Last message for placeholder",
|
||||||
|
|
@ -46,8 +49,10 @@ class RoomListRoomSummaryFactory @Inject constructor(
|
||||||
isMarkedUnread = false,
|
isMarkedUnread = false,
|
||||||
userDefinedNotificationMode = null,
|
userDefinedNotificationMode = null,
|
||||||
hasRoomCall = false,
|
hasRoomCall = false,
|
||||||
isDm = false,
|
isDirect = false,
|
||||||
isFavorite = false,
|
isFavorite = false,
|
||||||
|
inviteSender = null,
|
||||||
|
canonicalAlias = null,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -73,11 +78,28 @@ class RoomListRoomSummaryFactory @Inject constructor(
|
||||||
roomLastMessageFormatter.format(message.event, roomSummary.details.isDirect)
|
roomLastMessageFormatter.format(message.event, roomSummary.details.isDirect)
|
||||||
}.orEmpty(),
|
}.orEmpty(),
|
||||||
avatarData = avatarData,
|
avatarData = avatarData,
|
||||||
isPlaceholder = false,
|
|
||||||
userDefinedNotificationMode = roomSummary.details.userDefinedNotificationMode,
|
userDefinedNotificationMode = roomSummary.details.userDefinedNotificationMode,
|
||||||
hasRoomCall = roomSummary.details.hasRoomCall,
|
hasRoomCall = roomSummary.details.hasRoomCall,
|
||||||
isDm = roomSummary.details.isDm,
|
isDirect = roomSummary.details.isDirect,
|
||||||
isFavorite = roomSummary.details.isFavorite,
|
isFavorite = roomSummary.details.isFavorite,
|
||||||
|
inviteSender = roomSummary.details.inviter?.run {
|
||||||
|
InviteSender(
|
||||||
|
userId = userId,
|
||||||
|
displayName = displayName ?: "",
|
||||||
|
avatarData = AvatarData(
|
||||||
|
id = userId.value,
|
||||||
|
name = displayName,
|
||||||
|
url = avatarUrl,
|
||||||
|
size = AvatarSize.InviteSender,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
canonicalAlias = roomSummary.details.canonicalAlias,
|
||||||
|
type = if (roomSummary.details.currentUserMembership == CurrentUserMembership.INVITED) {
|
||||||
|
DisplayType.INVITE
|
||||||
|
} else {
|
||||||
|
DisplayType.ROOM
|
||||||
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,26 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2024 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.element.android.features.roomlist.impl.model
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents the type of display for a room list item.
|
||||||
|
*/
|
||||||
|
enum class DisplayType {
|
||||||
|
PLACEHOLDER,
|
||||||
|
ROOM,
|
||||||
|
INVITE
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,57 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2024 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.element.android.features.roomlist.impl.model
|
||||||
|
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.Immutable
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.text.AnnotatedString
|
||||||
|
import androidx.compose.ui.text.SpanStyle
|
||||||
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
|
import io.element.android.features.roomlist.impl.R
|
||||||
|
import io.element.android.libraries.designsystem.components.avatar.AvatarData
|
||||||
|
import io.element.android.libraries.matrix.api.core.UserId
|
||||||
|
|
||||||
|
@Immutable
|
||||||
|
data class InviteSender(
|
||||||
|
val userId: UserId,
|
||||||
|
val displayName: String,
|
||||||
|
val avatarData: AvatarData,
|
||||||
|
) {
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun annotatedString(): AnnotatedString {
|
||||||
|
return stringResource(R.string.screen_invites_invited_you, displayName, userId.value).let { text ->
|
||||||
|
val senderNameStart = LocalContext.current.getString(R.string.screen_invites_invited_you).indexOf("%1\$s")
|
||||||
|
AnnotatedString(
|
||||||
|
text = text,
|
||||||
|
spanStyles = listOf(
|
||||||
|
AnnotatedString.Range(
|
||||||
|
SpanStyle(
|
||||||
|
fontWeight = FontWeight.Medium,
|
||||||
|
color = MaterialTheme.colorScheme.primary
|
||||||
|
),
|
||||||
|
start = senderNameStart,
|
||||||
|
end = senderNameStart + displayName.length
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -24,8 +24,10 @@ import io.element.android.libraries.matrix.api.room.RoomNotificationMode
|
||||||
@Immutable
|
@Immutable
|
||||||
data class RoomListRoomSummary(
|
data class RoomListRoomSummary(
|
||||||
val id: String,
|
val id: String,
|
||||||
|
val type: DisplayType,
|
||||||
val roomId: RoomId,
|
val roomId: RoomId,
|
||||||
val name: String,
|
val name: String,
|
||||||
|
val canonicalAlias: String?,
|
||||||
val numberOfUnreadMessages: Int,
|
val numberOfUnreadMessages: Int,
|
||||||
val numberOfUnreadMentions: Int,
|
val numberOfUnreadMentions: Int,
|
||||||
val numberOfUnreadNotifications: Int,
|
val numberOfUnreadNotifications: Int,
|
||||||
|
|
@ -33,18 +35,21 @@ data class RoomListRoomSummary(
|
||||||
val timestamp: String?,
|
val timestamp: String?,
|
||||||
val lastMessage: CharSequence?,
|
val lastMessage: CharSequence?,
|
||||||
val avatarData: AvatarData,
|
val avatarData: AvatarData,
|
||||||
val isPlaceholder: Boolean,
|
|
||||||
val userDefinedNotificationMode: RoomNotificationMode?,
|
val userDefinedNotificationMode: RoomNotificationMode?,
|
||||||
val hasRoomCall: Boolean,
|
val hasRoomCall: Boolean,
|
||||||
val isDm: Boolean,
|
val isDirect: Boolean,
|
||||||
val isFavorite: Boolean,
|
val isFavorite: Boolean,
|
||||||
) {
|
val inviteSender: InviteSender?,
|
||||||
|
|
||||||
|
) {
|
||||||
val isHighlighted = userDefinedNotificationMode != RoomNotificationMode.MUTE &&
|
val isHighlighted = userDefinedNotificationMode != RoomNotificationMode.MUTE &&
|
||||||
(numberOfUnreadNotifications > 0 || numberOfUnreadMentions > 0) ||
|
(numberOfUnreadNotifications > 0 || numberOfUnreadMentions > 0) ||
|
||||||
isMarkedUnread
|
isMarkedUnread ||
|
||||||
|
type == DisplayType.INVITE
|
||||||
|
|
||||||
val hasNewContent = numberOfUnreadMessages > 0 ||
|
val hasNewContent = numberOfUnreadMessages > 0 ||
|
||||||
numberOfUnreadMentions > 0 ||
|
numberOfUnreadMentions > 0 ||
|
||||||
numberOfUnreadNotifications > 0 ||
|
numberOfUnreadNotifications > 0 ||
|
||||||
isMarkedUnread
|
isMarkedUnread ||
|
||||||
|
type == DisplayType.INVITE
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,13 +20,14 @@ import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||||
import io.element.android.libraries.designsystem.components.avatar.AvatarData
|
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.AvatarSize
|
||||||
import io.element.android.libraries.matrix.api.core.RoomId
|
import io.element.android.libraries.matrix.api.core.RoomId
|
||||||
|
import io.element.android.libraries.matrix.api.core.UserId
|
||||||
import io.element.android.libraries.matrix.api.room.RoomNotificationMode
|
import io.element.android.libraries.matrix.api.room.RoomNotificationMode
|
||||||
|
|
||||||
open class RoomListRoomSummaryProvider : PreviewParameterProvider<RoomListRoomSummary> {
|
open class RoomListRoomSummaryProvider : PreviewParameterProvider<RoomListRoomSummary> {
|
||||||
override val values: Sequence<RoomListRoomSummary>
|
override val values: Sequence<RoomListRoomSummary>
|
||||||
get() = sequenceOf(
|
get() = sequenceOf(
|
||||||
listOf(
|
listOf(
|
||||||
aRoomListRoomSummary(isPlaceholder = true),
|
aRoomListRoomSummary(displayType = DisplayType.PLACEHOLDER),
|
||||||
aRoomListRoomSummary(),
|
aRoomListRoomSummary(),
|
||||||
aRoomListRoomSummary(lastMessage = null),
|
aRoomListRoomSummary(lastMessage = null),
|
||||||
aRoomListRoomSummary(
|
aRoomListRoomSummary(
|
||||||
|
|
@ -80,6 +81,27 @@ open class RoomListRoomSummaryProvider : PreviewParameterProvider<RoomListRoomSu
|
||||||
)
|
)
|
||||||
}.flatten()
|
}.flatten()
|
||||||
}.flatten(),
|
}.flatten(),
|
||||||
|
listOf(
|
||||||
|
aRoomListRoomSummary(
|
||||||
|
displayType = DisplayType.INVITE,
|
||||||
|
inviteSender = InviteSender(
|
||||||
|
userId = UserId("@alice:matrix.org"),
|
||||||
|
displayName = "Alice",
|
||||||
|
avatarData = AvatarData("@alice:matrix.org", "Alice", size = AvatarSize.InviteSender),
|
||||||
|
),
|
||||||
|
canonicalAlias = "#alias:matrix.org",
|
||||||
|
),
|
||||||
|
aRoomListRoomSummary(
|
||||||
|
name = "Bob",
|
||||||
|
displayType = DisplayType.INVITE,
|
||||||
|
inviteSender = InviteSender(
|
||||||
|
userId = UserId("@bob:matrix.org"),
|
||||||
|
displayName = "Bob",
|
||||||
|
avatarData = AvatarData("@bob:matrix.org", "Bob", size = AvatarSize.InviteSender),
|
||||||
|
),
|
||||||
|
isDirect = true,
|
||||||
|
)
|
||||||
|
),
|
||||||
).flatten()
|
).flatten()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -92,12 +114,14 @@ internal fun aRoomListRoomSummary(
|
||||||
isMarkedUnread: Boolean = false,
|
isMarkedUnread: Boolean = false,
|
||||||
lastMessage: String? = "Last message",
|
lastMessage: String? = "Last message",
|
||||||
timestamp: String? = lastMessage?.let { "88:88" },
|
timestamp: String? = lastMessage?.let { "88:88" },
|
||||||
isPlaceholder: Boolean = false,
|
|
||||||
notificationMode: RoomNotificationMode? = null,
|
notificationMode: RoomNotificationMode? = null,
|
||||||
hasRoomCall: Boolean = false,
|
hasRoomCall: Boolean = false,
|
||||||
avatarData: AvatarData = AvatarData(id, name, size = AvatarSize.RoomListItem),
|
avatarData: AvatarData = AvatarData(id, name, size = AvatarSize.RoomListItem),
|
||||||
isDm: Boolean = false,
|
isDirect: Boolean = false,
|
||||||
isFavorite: Boolean = false,
|
isFavorite: Boolean = false,
|
||||||
|
inviteSender: InviteSender? = null,
|
||||||
|
displayType: DisplayType = DisplayType.ROOM,
|
||||||
|
canonicalAlias: String? = null,
|
||||||
) = RoomListRoomSummary(
|
) = RoomListRoomSummary(
|
||||||
id = id,
|
id = id,
|
||||||
roomId = RoomId(id),
|
roomId = RoomId(id),
|
||||||
|
|
@ -109,9 +133,11 @@ internal fun aRoomListRoomSummary(
|
||||||
timestamp = timestamp,
|
timestamp = timestamp,
|
||||||
lastMessage = lastMessage,
|
lastMessage = lastMessage,
|
||||||
avatarData = avatarData,
|
avatarData = avatarData,
|
||||||
isPlaceholder = isPlaceholder,
|
|
||||||
userDefinedNotificationMode = notificationMode,
|
userDefinedNotificationMode = notificationMode,
|
||||||
hasRoomCall = hasRoomCall,
|
hasRoomCall = hasRoomCall,
|
||||||
isDm = isDm,
|
isDirect = isDirect,
|
||||||
isFavorite = isFavorite,
|
isFavorite = isFavorite,
|
||||||
|
inviteSender = inviteSender,
|
||||||
|
type = displayType,
|
||||||
|
canonicalAlias = canonicalAlias,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,12 @@
|
||||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
<string name="confirm_recovery_key_banner_message">"Your chat backup is currently out of sync. You need to enter your recovery key to maintain access to your chat backup."</string>
|
<string name="confirm_recovery_key_banner_message">"Your chat backup is currently out of sync. You need to enter your recovery key to maintain access to your chat backup."</string>
|
||||||
<string name="confirm_recovery_key_banner_title">"Enter your recovery key"</string>
|
<string name="confirm_recovery_key_banner_title">"Enter your recovery key"</string>
|
||||||
|
<string name="screen_invites_decline_chat_message">"Are you sure you want to decline the invitation to join %1$s?"</string>
|
||||||
|
<string name="screen_invites_decline_chat_title">"Decline invite"</string>
|
||||||
|
<string name="screen_invites_decline_direct_chat_message">"Are you sure you want to decline this private chat with %1$s?"</string>
|
||||||
|
<string name="screen_invites_decline_direct_chat_title">"Decline chat"</string>
|
||||||
|
<string name="screen_invites_empty_list">"No Invites"</string>
|
||||||
|
<string name="screen_invites_invited_you">"%1$s (%2$s) invited you"</string>
|
||||||
<string name="screen_migration_message">"This is a one time process, thanks for waiting."</string>
|
<string name="screen_migration_message">"This is a one time process, thanks for waiting."</string>
|
||||||
<string name="screen_migration_title">"Setting up your account."</string>
|
<string name="screen_migration_title">"Setting up your account."</string>
|
||||||
<string name="screen_roomlist_a11y_create_message">"Create a new conversation or room"</string>
|
<string name="screen_roomlist_a11y_create_message">"Create a new conversation or room"</string>
|
||||||
|
|
|
||||||
|
|
@ -95,6 +95,6 @@ internal fun createRoomListRoomSummary(
|
||||||
isPlaceholder = false,
|
isPlaceholder = false,
|
||||||
userDefinedNotificationMode = userDefinedNotificationMode,
|
userDefinedNotificationMode = userDefinedNotificationMode,
|
||||||
hasRoomCall = false,
|
hasRoomCall = false,
|
||||||
isDm = false,
|
isDirect = false,
|
||||||
isFavorite = isFavorite,
|
isFavorite = isFavorite,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -130,7 +130,8 @@
|
||||||
"screen_roomlist_.*",
|
"screen_roomlist_.*",
|
||||||
"session_verification_banner_.*",
|
"session_verification_banner_.*",
|
||||||
"confirm_recovery_key_banner_.*",
|
"confirm_recovery_key_banner_.*",
|
||||||
"screen_migration_.*"
|
"screen_migration_.*",
|
||||||
|
"screen_invites_.*"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue