Skip to content

Commit 039661b

Browse files
committed
Show the last message state in the room list.
1 parent 7be68fe commit 039661b

14 files changed

+125
-14
lines changed

ElementX/Resources/Localizations/en-US.lproj/Localizable.strings

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -234,6 +234,7 @@
234234
"common_loading_more" = "Loading more…";
235235
"common_message" = "Message";
236236
"common_message_actions" = "Message actions";
237+
"common_message_failed_to_send" = "Message failed to send";
237238
"common_message_layout" = "Message layout";
238239
"common_message_removed" = "Message removed";
239240
"common_modern" = "Modern";

ElementX/Resources/Localizations/en.lproj/Localizable.strings

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -234,6 +234,7 @@
234234
"common_loading_more" = "Loading more…";
235235
"common_message" = "Message";
236236
"common_message_actions" = "Message actions";
237+
"common_message_failed_to_send" = "Message failed to send";
237238
"common_message_layout" = "Message layout";
238239
"common_message_removed" = "Message removed";
239240
"common_modern" = "Modern";

ElementX/Sources/Generated/Strings.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -524,6 +524,8 @@ internal enum L10n {
524524
internal static var commonMessage: String { return L10n.tr("Localizable", "common_message") }
525525
/// Message actions
526526
internal static var commonMessageActions: String { return L10n.tr("Localizable", "common_message_actions") }
527+
/// Message failed to send
528+
internal static var commonMessageFailedToSend: String { return L10n.tr("Localizable", "common_message_failed_to_send") }
527529
/// Message layout
528530
internal static var commonMessageLayout: String { return L10n.tr("Localizable", "common_message_layout") }
529531
/// Message removed

ElementX/Sources/Mocks/RoomSummaryProviderMock.swift

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ extension RoomSummary {
8383
activeMembersCount: 0,
8484
lastMessage: AttributedString("I do not wish to take the trouble to understand mysticism"),
8585
lastMessageDate: .mock,
86+
lastMessageState: nil,
8687
unreadMessagesCount: 0,
8788
unreadMentionsCount: 0,
8889
unreadNotificationsCount: 0,
@@ -109,6 +110,7 @@ extension Array where Element == RoomSummary {
109110
activeMembersCount: 0,
110111
lastMessage: AttributedString("I do not wish to take the trouble to understand mysticism"),
111112
lastMessageDate: .mock,
113+
lastMessageState: nil,
112114
unreadMessagesCount: 0,
113115
unreadMentionsCount: 0,
114116
unreadNotificationsCount: 0,
@@ -130,6 +132,7 @@ extension Array where Element == RoomSummary {
130132
activeMembersCount: 0,
131133
lastMessage: AttributedString("How do you see the Emperor then? You think he keeps office hours?"),
132134
lastMessageDate: .mock,
135+
lastMessageState: nil,
133136
unreadMessagesCount: 2,
134137
unreadMentionsCount: 0,
135138
unreadNotificationsCount: 2,
@@ -151,6 +154,7 @@ extension Array where Element == RoomSummary {
151154
activeMembersCount: 0,
152155
lastMessage: try? AttributedString(markdown: "He certainly seemed no *mental genius* to me"),
153156
lastMessageDate: .mock,
157+
lastMessageState: nil,
154158
unreadMessagesCount: 3,
155159
unreadMentionsCount: 0,
156160
unreadNotificationsCount: 0,
@@ -172,6 +176,7 @@ extension Array where Element == RoomSummary {
172176
activeMembersCount: 0,
173177
lastMessage: AttributedString("There's an archaic measure of time that's called the month"),
174178
lastMessageDate: .mock,
179+
lastMessageState: nil,
175180
unreadMessagesCount: 2,
176181
unreadMentionsCount: 2,
177182
unreadNotificationsCount: 2,
@@ -193,6 +198,7 @@ extension Array where Element == RoomSummary {
193198
activeMembersCount: 0,
194199
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"),
195200
lastMessageDate: .mock,
201+
lastMessageState: nil,
196202
unreadMessagesCount: 1,
197203
unreadMentionsCount: 1,
198204
unreadNotificationsCount: 1,
@@ -214,6 +220,7 @@ extension Array where Element == RoomSummary {
214220
activeMembersCount: 0,
215221
lastMessage: AttributedString("Are you groping for the word 'paranoia'?"),
216222
lastMessageDate: .mock,
223+
lastMessageState: nil,
217224
unreadMessagesCount: 6,
218225
unreadMentionsCount: 0,
219226
unreadNotificationsCount: 0,
@@ -235,6 +242,7 @@ extension Array where Element == RoomSummary {
235242
activeMembersCount: 0,
236243
lastMessage: nil,
237244
lastMessageDate: .mock,
245+
lastMessageState: nil,
238246
unreadMessagesCount: 1,
239247
unreadMentionsCount: 0,
240248
unreadNotificationsCount: 1,
@@ -256,6 +264,7 @@ extension Array where Element == RoomSummary {
256264
activeMembersCount: 0,
257265
lastMessage: nil,
258266
lastMessageDate: nil,
267+
lastMessageState: nil,
259268
unreadMessagesCount: 0,
260269
unreadMentionsCount: 0,
261270
unreadNotificationsCount: 0,
@@ -310,6 +319,7 @@ extension Array where Element == RoomSummary {
310319
activeMembersCount: 0,
311320
lastMessage: nil,
312321
lastMessageDate: nil,
322+
lastMessageState: nil,
313323
unreadMessagesCount: 0,
314324
unreadMentionsCount: 0,
315325
unreadNotificationsCount: 0,
@@ -331,6 +341,7 @@ extension Array where Element == RoomSummary {
331341
activeMembersCount: 0,
332342
lastMessage: nil,
333343
lastMessageDate: nil,
344+
lastMessageState: nil,
334345
unreadMessagesCount: 0,
335346
unreadMentionsCount: 0,
336347
unreadNotificationsCount: 0,
@@ -355,6 +366,7 @@ extension Array where Element == RoomSummary {
355366
activeMembersCount: 0,
356367
lastMessage: nil,
357368
lastMessageDate: nil,
369+
lastMessageState: nil,
358370
unreadMessagesCount: 0,
359371
unreadMentionsCount: 0,
360372
unreadNotificationsCount: 0,
@@ -376,6 +388,7 @@ extension Array where Element == RoomSummary {
376388
activeMembersCount: 0,
377389
lastMessage: nil,
378390
lastMessageDate: nil,
391+
lastMessageState: nil,
379392
unreadMessagesCount: 0,
380393
unreadMentionsCount: 0,
381394
unreadNotificationsCount: 0,

ElementX/Sources/Screens/HomeScreen/HomeScreenModels.swift

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -197,18 +197,23 @@ struct HomeScreenRoom: Identifiable, Equatable {
197197

198198
let lastMessage: AttributedString?
199199

200+
enum LastMessageState { case sending, failed }
201+
let lastMessageState: LastMessageState?
202+
200203
let avatar: RoomAvatar
201204

202205
let canonicalAlias: String?
203206

204207
let isTombstoned: Bool
205208

206209
var displayedLastMessage: AttributedString? {
207-
// If the room is tombstoned, show a specific message, regardless of any last message.
208-
guard !isTombstoned else {
209-
return AttributedString(L10n.screenRoomlistTombstonedRoomDescription)
210+
if isTombstoned {
211+
AttributedString(L10n.screenRoomlistTombstonedRoomDescription)
212+
} else if lastMessageState == .failed {
213+
AttributedString(L10n.commonMessageFailedToSend)
214+
} else {
215+
lastMessage
210216
}
211-
return lastMessage
212217
}
213218

214219
static func placeholder() -> HomeScreenRoom {
@@ -222,6 +227,7 @@ struct HomeScreenRoom: Identifiable, Equatable {
222227
isFavourite: false,
223228
timestamp: "Now",
224229
lastMessage: placeholderLastMessage,
230+
lastMessageState: nil,
225231
avatar: .room(id: "", name: "", avatarURL: nil),
226232
canonicalAlias: nil,
227233
isTombstoned: false)
@@ -260,8 +266,23 @@ extension HomeScreenRoom {
260266
isFavourite: summary.isFavourite,
261267
timestamp: summary.lastMessageDate?.formattedMinimal(),
262268
lastMessage: summary.lastMessage,
269+
lastMessageState: summary.homeScreenLastMessageState,
263270
avatar: summary.avatar,
264271
canonicalAlias: summary.canonicalAlias,
265272
isTombstoned: summary.isTombstoned)
266273
}
267274
}
275+
276+
private extension RoomSummary {
277+
var homeScreenLastMessageState: HomeScreenRoom.LastMessageState? {
278+
if isTombstoned {
279+
nil
280+
} else {
281+
switch lastMessageState {
282+
case .sending: .sending
283+
case .failed: .failed
284+
case .none: .none
285+
}
286+
}
287+
}
288+
}

ElementX/Sources/Screens/HomeScreen/View/HomeScreenInviteCell.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,7 @@ private extension HomeScreenRoom {
223223
activeMembersCount: 0,
224224
lastMessage: nil,
225225
lastMessageDate: nil,
226+
lastMessageState: nil,
226227
unreadMessagesCount: 0,
227228
unreadMentionsCount: 0,
228229
unreadNotificationsCount: 0,
@@ -257,6 +258,7 @@ private extension HomeScreenRoom {
257258
activeMembersCount: 0,
258259
lastMessage: nil,
259260
lastMessageDate: nil,
261+
lastMessageState: nil,
260262
unreadMessagesCount: 0,
261263
unreadMentionsCount: 0,
262264
unreadNotificationsCount: 0,

ElementX/Sources/Screens/HomeScreen/View/HomeScreenKnockedCell.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,7 @@ private extension HomeScreenRoom {
156156
activeMembersCount: 0,
157157
lastMessage: nil,
158158
lastMessageDate: nil,
159+
lastMessageState: nil,
159160
unreadMessagesCount: 0,
160161
unreadMentionsCount: 0,
161162
unreadNotificationsCount: 0,
@@ -187,6 +188,7 @@ private extension HomeScreenRoom {
187188
activeMembersCount: 0,
188189
lastMessage: nil,
189190
lastMessageDate: nil,
191+
lastMessageState: nil,
190192
unreadMessagesCount: 0,
191193
unreadMentionsCount: 0,
192194
unreadNotificationsCount: 0,

ElementX/Sources/Screens/HomeScreen/View/HomeScreenRoomCell.swift

Lines changed: 59 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -79,11 +79,24 @@ struct HomeScreenRoomCell: View {
7979
@ViewBuilder
8080
private var header: some View {
8181
HStack(alignment: .top, spacing: 16) {
82-
Text(room.name)
83-
.font(.compound.bodyLGSemibold)
84-
.foregroundColor(.compound.textPrimary)
85-
.lineLimit(1)
86-
.frame(maxWidth: .infinity, alignment: .leading)
82+
HStack(spacing: 4) {
83+
Text(room.name)
84+
.font(.compound.bodyLGSemibold)
85+
.foregroundColor(.compound.textPrimary)
86+
.lineLimit(1)
87+
88+
switch room.lastMessageState {
89+
case .sending:
90+
CompoundIcon(\.time, size: .xSmall, relativeTo: .compound.bodyLGSemibold)
91+
.foregroundStyle(.compound.iconTertiary)
92+
case .failed:
93+
CompoundIcon(\.errorSolid, size: .xSmall, relativeTo: .compound.bodyLGSemibold)
94+
.foregroundStyle(.compound.iconCriticalPrimary)
95+
case .none:
96+
EmptyView()
97+
}
98+
}
99+
.frame(maxWidth: .infinity, alignment: .leading)
87100

88101
if let timestamp = room.timestamp {
89102
Text(timestamp)
@@ -99,7 +112,7 @@ struct HomeScreenRoomCell: View {
99112
ZStack(alignment: .topLeading) {
100113
// Hidden text with 2 lines to maintain consistent height, scaling with dynamic text.
101114
Text(" \n ")
102-
.lastMessageFormatting()
115+
.lastMessageFormatting(hasFailed: false)
103116
.hidden()
104117
.environment(\.redactionReasons, []) // Always maintain consistent height
105118

@@ -142,7 +155,7 @@ struct HomeScreenRoomCell: View {
142155
private var lastMessage: some View {
143156
if let displayedLastMessage = room.displayedLastMessage {
144157
Text(displayedLastMessage)
145-
.lastMessageFormatting()
158+
.lastMessageFormatting(hasFailed: room.lastMessageState == .failed)
146159
}
147160
}
148161
}
@@ -159,9 +172,9 @@ struct HomeScreenRoomCellButtonStyle: ButtonStyle {
159172
}
160173

161174
private extension View {
162-
func lastMessageFormatting() -> some View {
175+
func lastMessageFormatting(hasFailed: Bool) -> some View {
163176
font(.compound.bodyMD)
164-
.foregroundColor(.compound.textSecondary)
177+
.foregroundColor(hasFailed ? .compound.textCriticalPrimary : .compound.textSecondary)
165178
.lineLimit(2)
166179
.multilineTextAlignment(.leading)
167180
}
@@ -174,6 +187,8 @@ struct HomeScreenRoomCell_Previews: PreviewProvider, TestablePreview {
174187
static let summaryProviderForNotificationsState = RoomSummaryProviderMock(.init(state: .loaded(.mockRoomsWithNotificationsState)))
175188
static let notificationsStateRooms = summaryProviderForNotificationsState.roomListPublisher.value.compactMap(mockRoom)
176189

190+
static let lastMessageStateRooms = [makeRoom(lastMessageState: .sending), makeRoom(lastMessageState: .failed)]
191+
177192
static var previews: some View {
178193
VStack(spacing: 0) {
179194
ForEach(genericRooms) { room in
@@ -192,6 +207,14 @@ struct HomeScreenRoomCell_Previews: PreviewProvider, TestablePreview {
192207
}
193208
.previewLayout(.sizeThatFits)
194209
.previewDisplayName("Notifications State")
210+
211+
VStack(spacing: 0) {
212+
ForEach(lastMessageStateRooms) { room in
213+
HomeScreenRoomCell(room: room, isSelected: false, mediaProvider: MediaProviderMock(configuration: .init())) { _ in }
214+
}
215+
}
216+
.previewLayout(.sizeThatFits)
217+
.previewDisplayName("Last Message State")
195218
}
196219

197220
static func mockRoom(summary: RoomSummary) -> HomeScreenRoom? {
@@ -208,4 +231,31 @@ struct HomeScreenRoomCell_Previews: PreviewProvider, TestablePreview {
208231
notificationManager: NotificationManagerMock(),
209232
userIndicatorController: ServiceLocator.shared.userIndicatorController)
210233
}
234+
235+
static func makeRoom(lastMessageState: RoomSummary.LastMessageState) -> HomeScreenRoom {
236+
let summary = RoomSummary(room: RoomSDKMock(),
237+
id: UUID().uuidString,
238+
joinRequestType: nil,
239+
name: "Foundation and Empire",
240+
isDirect: false,
241+
isSpace: false,
242+
avatarURL: .mockMXCAvatar,
243+
heroes: [],
244+
activeMembersCount: 0,
245+
lastMessage: AttributedString("How do you see the Emperor then? You think he keeps office hours?"),
246+
lastMessageDate: .mock,
247+
lastMessageState: lastMessageState,
248+
unreadMessagesCount: 2,
249+
unreadMentionsCount: 0,
250+
unreadNotificationsCount: 2,
251+
notificationMode: .mute,
252+
canonicalAlias: "#foundation-and-empire:matrix.org",
253+
alternativeAliases: [],
254+
hasOngoingCall: false,
255+
isMarkedUnread: false,
256+
isFavourite: false,
257+
isTombstoned: false)
258+
259+
return .init(summary: summary, hideUnreadMessagesBadge: false)
260+
}
211261
}

ElementX/Sources/Services/Room/RoomSummary/RoomSummary.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ struct RoomSummary {
2929
}
3030
}
3131
}
32+
33+
enum LastMessageState { case sending, failed }
3234

3335
let room: Room
3436

@@ -46,6 +48,7 @@ struct RoomSummary {
4648

4749
let lastMessage: AttributedString?
4850
let lastMessageDate: Date?
51+
let lastMessageState: LastMessageState?
4952
let unreadMessagesCount: UInt
5053
let unreadMentionsCount: UInt
5154
let unreadNotificationsCount: UInt
@@ -117,6 +120,7 @@ extension RoomSummary {
117120

118121
lastMessage = AttributedString(string)
119122
lastMessageDate = .mock
123+
lastMessageState = nil
120124
unreadMessagesCount = hasUnreadMessages ? 1 : 0
121125
unreadMentionsCount = hasUnreadMentions ? 1 : 0
122126
unreadNotificationsCount = hasUnreadNotifications ? 1 : 0

ElementX/Sources/Services/Room/RoomSummary/RoomSummaryProvider.swift

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -261,13 +261,15 @@ class RoomSummaryProvider: RoomSummaryProviderProtocol {
261261

262262
var attributedLastMessage: AttributedString?
263263
var lastMessageDate: Date?
264+
var lastMessageState: RoomSummary.LastMessageState?
264265

265266
if let latestRoomMessage = roomDetails.latestEvent {
266267
switch latestRoomMessage {
267-
case .local(let timestamp, let senderID, let profile, let content, _):
268+
case .local(let timestamp, let senderID, let profile, let content, let isSending):
268269
let sender = TimelineItemSender(senderID: senderID, senderProfile: profile)
269270
attributedLastMessage = eventStringBuilder.buildAttributedString(for: content, sender: sender, isOutgoing: true)
270271
lastMessageDate = Date(timeIntervalSince1970: TimeInterval(timestamp / 1000))
272+
lastMessageState = isSending ? .sending : .failed // No need to worry about sent for .local: https://github.com/matrix-org/matrix-rust-sdk/issues/3941
271273
case .remote(let timestamp, let senderID, let isOwn, let profile, let content):
272274
let sender = TimelineItemSender(senderID: senderID, senderProfile: profile)
273275
attributedLastMessage = eventStringBuilder.buildAttributedString(for: content, sender: sender, isOutgoing: isOwn)
@@ -301,6 +303,7 @@ class RoomSummaryProvider: RoomSummaryProviderProtocol {
301303
activeMembersCount: UInt(roomInfo.activeMembersCount),
302304
lastMessage: attributedLastMessage,
303305
lastMessageDate: lastMessageDate,
306+
lastMessageState: lastMessageState,
304307
unreadMessagesCount: UInt(roomInfo.numUnreadMessages),
305308
unreadMentionsCount: UInt(roomInfo.numUnreadMentions),
306309
unreadNotificationsCount: UInt(roomInfo.numUnreadNotifications),

0 commit comments

Comments
 (0)