mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-03 16:10:20 +00:00
Show the last message state in the room list.
This commit is contained in:
@@ -524,6 +524,8 @@ internal enum L10n {
|
||||
internal static var commonMessage: String { return L10n.tr("Localizable", "common_message") }
|
||||
/// Message actions
|
||||
internal static var commonMessageActions: String { return L10n.tr("Localizable", "common_message_actions") }
|
||||
/// Message failed to send
|
||||
internal static var commonMessageFailedToSend: String { return L10n.tr("Localizable", "common_message_failed_to_send") }
|
||||
/// Message layout
|
||||
internal static var commonMessageLayout: String { return L10n.tr("Localizable", "common_message_layout") }
|
||||
/// Message removed
|
||||
|
||||
@@ -83,6 +83,7 @@ extension RoomSummary {
|
||||
activeMembersCount: 0,
|
||||
lastMessage: AttributedString("I do not wish to take the trouble to understand mysticism"),
|
||||
lastMessageDate: .mock,
|
||||
lastMessageState: nil,
|
||||
unreadMessagesCount: 0,
|
||||
unreadMentionsCount: 0,
|
||||
unreadNotificationsCount: 0,
|
||||
@@ -109,6 +110,7 @@ extension Array where Element == RoomSummary {
|
||||
activeMembersCount: 0,
|
||||
lastMessage: AttributedString("I do not wish to take the trouble to understand mysticism"),
|
||||
lastMessageDate: .mock,
|
||||
lastMessageState: nil,
|
||||
unreadMessagesCount: 0,
|
||||
unreadMentionsCount: 0,
|
||||
unreadNotificationsCount: 0,
|
||||
@@ -130,6 +132,7 @@ extension Array where Element == RoomSummary {
|
||||
activeMembersCount: 0,
|
||||
lastMessage: AttributedString("How do you see the Emperor then? You think he keeps office hours?"),
|
||||
lastMessageDate: .mock,
|
||||
lastMessageState: nil,
|
||||
unreadMessagesCount: 2,
|
||||
unreadMentionsCount: 0,
|
||||
unreadNotificationsCount: 2,
|
||||
@@ -151,6 +154,7 @@ extension Array where Element == RoomSummary {
|
||||
activeMembersCount: 0,
|
||||
lastMessage: try? AttributedString(markdown: "He certainly seemed no *mental genius* to me"),
|
||||
lastMessageDate: .mock,
|
||||
lastMessageState: nil,
|
||||
unreadMessagesCount: 3,
|
||||
unreadMentionsCount: 0,
|
||||
unreadNotificationsCount: 0,
|
||||
@@ -172,6 +176,7 @@ extension Array where Element == RoomSummary {
|
||||
activeMembersCount: 0,
|
||||
lastMessage: AttributedString("There's an archaic measure of time that's called the month"),
|
||||
lastMessageDate: .mock,
|
||||
lastMessageState: nil,
|
||||
unreadMessagesCount: 2,
|
||||
unreadMentionsCount: 2,
|
||||
unreadNotificationsCount: 2,
|
||||
@@ -193,6 +198,7 @@ extension Array where Element == RoomSummary {
|
||||
activeMembersCount: 0,
|
||||
lastMessage: AttributedString("Clearly, if Earth is powerful enough to do that, it might also be capable of adjusting minds in order to force belief in its radioactivity"),
|
||||
lastMessageDate: .mock,
|
||||
lastMessageState: nil,
|
||||
unreadMessagesCount: 1,
|
||||
unreadMentionsCount: 1,
|
||||
unreadNotificationsCount: 1,
|
||||
@@ -214,6 +220,7 @@ extension Array where Element == RoomSummary {
|
||||
activeMembersCount: 0,
|
||||
lastMessage: AttributedString("Are you groping for the word 'paranoia'?"),
|
||||
lastMessageDate: .mock,
|
||||
lastMessageState: nil,
|
||||
unreadMessagesCount: 6,
|
||||
unreadMentionsCount: 0,
|
||||
unreadNotificationsCount: 0,
|
||||
@@ -235,6 +242,7 @@ extension Array where Element == RoomSummary {
|
||||
activeMembersCount: 0,
|
||||
lastMessage: nil,
|
||||
lastMessageDate: .mock,
|
||||
lastMessageState: nil,
|
||||
unreadMessagesCount: 1,
|
||||
unreadMentionsCount: 0,
|
||||
unreadNotificationsCount: 1,
|
||||
@@ -256,6 +264,7 @@ extension Array where Element == RoomSummary {
|
||||
activeMembersCount: 0,
|
||||
lastMessage: nil,
|
||||
lastMessageDate: nil,
|
||||
lastMessageState: nil,
|
||||
unreadMessagesCount: 0,
|
||||
unreadMentionsCount: 0,
|
||||
unreadNotificationsCount: 0,
|
||||
@@ -310,6 +319,7 @@ extension Array where Element == RoomSummary {
|
||||
activeMembersCount: 0,
|
||||
lastMessage: nil,
|
||||
lastMessageDate: nil,
|
||||
lastMessageState: nil,
|
||||
unreadMessagesCount: 0,
|
||||
unreadMentionsCount: 0,
|
||||
unreadNotificationsCount: 0,
|
||||
@@ -331,6 +341,7 @@ extension Array where Element == RoomSummary {
|
||||
activeMembersCount: 0,
|
||||
lastMessage: nil,
|
||||
lastMessageDate: nil,
|
||||
lastMessageState: nil,
|
||||
unreadMessagesCount: 0,
|
||||
unreadMentionsCount: 0,
|
||||
unreadNotificationsCount: 0,
|
||||
@@ -355,6 +366,7 @@ extension Array where Element == RoomSummary {
|
||||
activeMembersCount: 0,
|
||||
lastMessage: nil,
|
||||
lastMessageDate: nil,
|
||||
lastMessageState: nil,
|
||||
unreadMessagesCount: 0,
|
||||
unreadMentionsCount: 0,
|
||||
unreadNotificationsCount: 0,
|
||||
@@ -376,6 +388,7 @@ extension Array where Element == RoomSummary {
|
||||
activeMembersCount: 0,
|
||||
lastMessage: nil,
|
||||
lastMessageDate: nil,
|
||||
lastMessageState: nil,
|
||||
unreadMessagesCount: 0,
|
||||
unreadMentionsCount: 0,
|
||||
unreadNotificationsCount: 0,
|
||||
|
||||
@@ -197,6 +197,9 @@ struct HomeScreenRoom: Identifiable, Equatable {
|
||||
|
||||
let lastMessage: AttributedString?
|
||||
|
||||
enum LastMessageState { case sending, failed }
|
||||
let lastMessageState: LastMessageState?
|
||||
|
||||
let avatar: RoomAvatar
|
||||
|
||||
let canonicalAlias: String?
|
||||
@@ -204,11 +207,13 @@ struct HomeScreenRoom: Identifiable, Equatable {
|
||||
let isTombstoned: Bool
|
||||
|
||||
var displayedLastMessage: AttributedString? {
|
||||
// If the room is tombstoned, show a specific message, regardless of any last message.
|
||||
guard !isTombstoned else {
|
||||
return AttributedString(L10n.screenRoomlistTombstonedRoomDescription)
|
||||
if isTombstoned {
|
||||
AttributedString(L10n.screenRoomlistTombstonedRoomDescription)
|
||||
} else if lastMessageState == .failed {
|
||||
AttributedString(L10n.commonMessageFailedToSend)
|
||||
} else {
|
||||
lastMessage
|
||||
}
|
||||
return lastMessage
|
||||
}
|
||||
|
||||
static func placeholder() -> HomeScreenRoom {
|
||||
@@ -222,6 +227,7 @@ struct HomeScreenRoom: Identifiable, Equatable {
|
||||
isFavourite: false,
|
||||
timestamp: "Now",
|
||||
lastMessage: placeholderLastMessage,
|
||||
lastMessageState: nil,
|
||||
avatar: .room(id: "", name: "", avatarURL: nil),
|
||||
canonicalAlias: nil,
|
||||
isTombstoned: false)
|
||||
@@ -260,8 +266,23 @@ extension HomeScreenRoom {
|
||||
isFavourite: summary.isFavourite,
|
||||
timestamp: summary.lastMessageDate?.formattedMinimal(),
|
||||
lastMessage: summary.lastMessage,
|
||||
lastMessageState: summary.homeScreenLastMessageState,
|
||||
avatar: summary.avatar,
|
||||
canonicalAlias: summary.canonicalAlias,
|
||||
isTombstoned: summary.isTombstoned)
|
||||
}
|
||||
}
|
||||
|
||||
private extension RoomSummary {
|
||||
var homeScreenLastMessageState: HomeScreenRoom.LastMessageState? {
|
||||
if isTombstoned {
|
||||
nil
|
||||
} else {
|
||||
switch lastMessageState {
|
||||
case .sending: .sending
|
||||
case .failed: .failed
|
||||
case .none: .none
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -223,6 +223,7 @@ private extension HomeScreenRoom {
|
||||
activeMembersCount: 0,
|
||||
lastMessage: nil,
|
||||
lastMessageDate: nil,
|
||||
lastMessageState: nil,
|
||||
unreadMessagesCount: 0,
|
||||
unreadMentionsCount: 0,
|
||||
unreadNotificationsCount: 0,
|
||||
@@ -257,6 +258,7 @@ private extension HomeScreenRoom {
|
||||
activeMembersCount: 0,
|
||||
lastMessage: nil,
|
||||
lastMessageDate: nil,
|
||||
lastMessageState: nil,
|
||||
unreadMessagesCount: 0,
|
||||
unreadMentionsCount: 0,
|
||||
unreadNotificationsCount: 0,
|
||||
|
||||
@@ -156,6 +156,7 @@ private extension HomeScreenRoom {
|
||||
activeMembersCount: 0,
|
||||
lastMessage: nil,
|
||||
lastMessageDate: nil,
|
||||
lastMessageState: nil,
|
||||
unreadMessagesCount: 0,
|
||||
unreadMentionsCount: 0,
|
||||
unreadNotificationsCount: 0,
|
||||
@@ -187,6 +188,7 @@ private extension HomeScreenRoom {
|
||||
activeMembersCount: 0,
|
||||
lastMessage: nil,
|
||||
lastMessageDate: nil,
|
||||
lastMessageState: nil,
|
||||
unreadMessagesCount: 0,
|
||||
unreadMentionsCount: 0,
|
||||
unreadNotificationsCount: 0,
|
||||
|
||||
@@ -79,11 +79,24 @@ struct HomeScreenRoomCell: View {
|
||||
@ViewBuilder
|
||||
private var header: some View {
|
||||
HStack(alignment: .top, spacing: 16) {
|
||||
Text(room.name)
|
||||
.font(.compound.bodyLGSemibold)
|
||||
.foregroundColor(.compound.textPrimary)
|
||||
.lineLimit(1)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
HStack(spacing: 4) {
|
||||
Text(room.name)
|
||||
.font(.compound.bodyLGSemibold)
|
||||
.foregroundColor(.compound.textPrimary)
|
||||
.lineLimit(1)
|
||||
|
||||
switch room.lastMessageState {
|
||||
case .sending:
|
||||
CompoundIcon(\.time, size: .xSmall, relativeTo: .compound.bodyLGSemibold)
|
||||
.foregroundStyle(.compound.iconTertiary)
|
||||
case .failed:
|
||||
CompoundIcon(\.errorSolid, size: .xSmall, relativeTo: .compound.bodyLGSemibold)
|
||||
.foregroundStyle(.compound.iconCriticalPrimary)
|
||||
case .none:
|
||||
EmptyView()
|
||||
}
|
||||
}
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
|
||||
if let timestamp = room.timestamp {
|
||||
Text(timestamp)
|
||||
@@ -99,7 +112,7 @@ struct HomeScreenRoomCell: View {
|
||||
ZStack(alignment: .topLeading) {
|
||||
// Hidden text with 2 lines to maintain consistent height, scaling with dynamic text.
|
||||
Text(" \n ")
|
||||
.lastMessageFormatting()
|
||||
.lastMessageFormatting(hasFailed: false)
|
||||
.hidden()
|
||||
.environment(\.redactionReasons, []) // Always maintain consistent height
|
||||
|
||||
@@ -142,7 +155,7 @@ struct HomeScreenRoomCell: View {
|
||||
private var lastMessage: some View {
|
||||
if let displayedLastMessage = room.displayedLastMessage {
|
||||
Text(displayedLastMessage)
|
||||
.lastMessageFormatting()
|
||||
.lastMessageFormatting(hasFailed: room.lastMessageState == .failed)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -159,9 +172,9 @@ struct HomeScreenRoomCellButtonStyle: ButtonStyle {
|
||||
}
|
||||
|
||||
private extension View {
|
||||
func lastMessageFormatting() -> some View {
|
||||
func lastMessageFormatting(hasFailed: Bool) -> some View {
|
||||
font(.compound.bodyMD)
|
||||
.foregroundColor(.compound.textSecondary)
|
||||
.foregroundColor(hasFailed ? .compound.textCriticalPrimary : .compound.textSecondary)
|
||||
.lineLimit(2)
|
||||
.multilineTextAlignment(.leading)
|
||||
}
|
||||
@@ -174,6 +187,8 @@ struct HomeScreenRoomCell_Previews: PreviewProvider, TestablePreview {
|
||||
static let summaryProviderForNotificationsState = RoomSummaryProviderMock(.init(state: .loaded(.mockRoomsWithNotificationsState)))
|
||||
static let notificationsStateRooms = summaryProviderForNotificationsState.roomListPublisher.value.compactMap(mockRoom)
|
||||
|
||||
static let lastMessageStateRooms = [makeRoom(lastMessageState: .sending), makeRoom(lastMessageState: .failed)]
|
||||
|
||||
static var previews: some View {
|
||||
VStack(spacing: 0) {
|
||||
ForEach(genericRooms) { room in
|
||||
@@ -192,6 +207,14 @@ struct HomeScreenRoomCell_Previews: PreviewProvider, TestablePreview {
|
||||
}
|
||||
.previewLayout(.sizeThatFits)
|
||||
.previewDisplayName("Notifications State")
|
||||
|
||||
VStack(spacing: 0) {
|
||||
ForEach(lastMessageStateRooms) { room in
|
||||
HomeScreenRoomCell(room: room, isSelected: false, mediaProvider: MediaProviderMock(configuration: .init())) { _ in }
|
||||
}
|
||||
}
|
||||
.previewLayout(.sizeThatFits)
|
||||
.previewDisplayName("Last Message State")
|
||||
}
|
||||
|
||||
static func mockRoom(summary: RoomSummary) -> HomeScreenRoom? {
|
||||
@@ -208,4 +231,31 @@ struct HomeScreenRoomCell_Previews: PreviewProvider, TestablePreview {
|
||||
notificationManager: NotificationManagerMock(),
|
||||
userIndicatorController: ServiceLocator.shared.userIndicatorController)
|
||||
}
|
||||
|
||||
static func makeRoom(lastMessageState: RoomSummary.LastMessageState) -> HomeScreenRoom {
|
||||
let summary = RoomSummary(room: RoomSDKMock(),
|
||||
id: UUID().uuidString,
|
||||
joinRequestType: nil,
|
||||
name: "Foundation and Empire",
|
||||
isDirect: false,
|
||||
isSpace: false,
|
||||
avatarURL: .mockMXCAvatar,
|
||||
heroes: [],
|
||||
activeMembersCount: 0,
|
||||
lastMessage: AttributedString("How do you see the Emperor then? You think he keeps office hours?"),
|
||||
lastMessageDate: .mock,
|
||||
lastMessageState: lastMessageState,
|
||||
unreadMessagesCount: 2,
|
||||
unreadMentionsCount: 0,
|
||||
unreadNotificationsCount: 2,
|
||||
notificationMode: .mute,
|
||||
canonicalAlias: "#foundation-and-empire:matrix.org",
|
||||
alternativeAliases: [],
|
||||
hasOngoingCall: false,
|
||||
isMarkedUnread: false,
|
||||
isFavourite: false,
|
||||
isTombstoned: false)
|
||||
|
||||
return .init(summary: summary, hideUnreadMessagesBadge: false)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,6 +29,8 @@ struct RoomSummary {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum LastMessageState { case sending, failed }
|
||||
|
||||
let room: Room
|
||||
|
||||
@@ -46,6 +48,7 @@ struct RoomSummary {
|
||||
|
||||
let lastMessage: AttributedString?
|
||||
let lastMessageDate: Date?
|
||||
let lastMessageState: LastMessageState?
|
||||
let unreadMessagesCount: UInt
|
||||
let unreadMentionsCount: UInt
|
||||
let unreadNotificationsCount: UInt
|
||||
@@ -117,6 +120,7 @@ extension RoomSummary {
|
||||
|
||||
lastMessage = AttributedString(string)
|
||||
lastMessageDate = .mock
|
||||
lastMessageState = nil
|
||||
unreadMessagesCount = hasUnreadMessages ? 1 : 0
|
||||
unreadMentionsCount = hasUnreadMentions ? 1 : 0
|
||||
unreadNotificationsCount = hasUnreadNotifications ? 1 : 0
|
||||
|
||||
@@ -261,13 +261,15 @@ class RoomSummaryProvider: RoomSummaryProviderProtocol {
|
||||
|
||||
var attributedLastMessage: AttributedString?
|
||||
var lastMessageDate: Date?
|
||||
var lastMessageState: RoomSummary.LastMessageState?
|
||||
|
||||
if let latestRoomMessage = roomDetails.latestEvent {
|
||||
switch latestRoomMessage {
|
||||
case .local(let timestamp, let senderID, let profile, let content, _):
|
||||
case .local(let timestamp, let senderID, let profile, let content, let isSending):
|
||||
let sender = TimelineItemSender(senderID: senderID, senderProfile: profile)
|
||||
attributedLastMessage = eventStringBuilder.buildAttributedString(for: content, sender: sender, isOutgoing: true)
|
||||
lastMessageDate = Date(timeIntervalSince1970: TimeInterval(timestamp / 1000))
|
||||
lastMessageState = isSending ? .sending : .failed // No need to worry about sent for .local: https://github.com/matrix-org/matrix-rust-sdk/issues/3941
|
||||
case .remote(let timestamp, let senderID, let isOwn, let profile, let content):
|
||||
let sender = TimelineItemSender(senderID: senderID, senderProfile: profile)
|
||||
attributedLastMessage = eventStringBuilder.buildAttributedString(for: content, sender: sender, isOutgoing: isOwn)
|
||||
@@ -301,6 +303,7 @@ class RoomSummaryProvider: RoomSummaryProviderProtocol {
|
||||
activeMembersCount: UInt(roomInfo.activeMembersCount),
|
||||
lastMessage: attributedLastMessage,
|
||||
lastMessageDate: lastMessageDate,
|
||||
lastMessageState: lastMessageState,
|
||||
unreadMessagesCount: UInt(roomInfo.numUnreadMessages),
|
||||
unreadMentionsCount: UInt(roomInfo.numUnreadMentions),
|
||||
unreadNotificationsCount: UInt(roomInfo.numUnreadNotifications),
|
||||
|
||||
Reference in New Issue
Block a user