mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-04 10:33:45 +00:00
Render the number of thread replies on the thread summary timeline view.
This commit is contained in:
committed by
Stefan Ceriu
parent
f66d654820
commit
5315f962c6
@@ -512,6 +512,10 @@ internal enum L10n {
|
||||
internal static var commonRecoveryKey: String { return L10n.tr("Localizable", "common_recovery_key") }
|
||||
/// Refreshing…
|
||||
internal static var commonRefreshing: String { return L10n.tr("Localizable", "common_refreshing") }
|
||||
/// %1$d replies
|
||||
internal static func commonReplies(_ p1: Int) -> String {
|
||||
return L10n.tr("Localizable", "common_replies", p1)
|
||||
}
|
||||
/// Replying to %1$@
|
||||
internal static func commonReplyingTo(_ p1: Any) -> String {
|
||||
return L10n.tr("Localizable", "common_replying_to", String(describing: p1))
|
||||
@@ -764,6 +768,8 @@ internal enum L10n {
|
||||
internal static var errorSomeMessagesHaveNotBeenSent: String { return L10n.tr("Localizable", "error_some_messages_have_not_been_sent") }
|
||||
/// Sorry, an error occurred
|
||||
internal static var errorUnknown: String { return L10n.tr("Localizable", "error_unknown") }
|
||||
/// The sender of the event does not match the owner of the device that sent it.
|
||||
internal static var eventShieldMismatchedSender: String { return L10n.tr("Localizable", "event_shield_mismatched_sender") }
|
||||
/// The authenticity of this encrypted message can't be guaranteed on this device.
|
||||
internal static var eventShieldReasonAuthenticityNotGuaranteed: String { return L10n.tr("Localizable", "event_shield_reason_authenticity_not_guaranteed") }
|
||||
/// Encrypted by a previously-verified user.
|
||||
|
||||
@@ -419,7 +419,8 @@ struct TimelineItemBubbledStylerView_Previews: PreviewProvider, TestablePreview
|
||||
ScrollView {
|
||||
let threadSummary = TimelineItemThreadSummary.loaded(senderID: "@alice:matrix.org",
|
||||
sender: .init(id: "@alice:matrix.org", displayName: "Alice"),
|
||||
latestEventContent: .message(.text(.init(body: "This is a very long, multi-lined, threaded message"))))
|
||||
latestEventContent: .message(.text(.init(body: "This is a very long, multi-lined, threaded message"))),
|
||||
numberOfReplies: 42)
|
||||
|
||||
MockTimelineContent(threadSummary: threadSummary)
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ struct TimelineThreadSummaryView: View {
|
||||
@ViewBuilder
|
||||
private var content: some View {
|
||||
switch threadSummary {
|
||||
case .loaded(let senderID, let sender, let latestEventContent):
|
||||
case .loaded(let senderID, let sender, let latestEventContent, let numberOfReplies):
|
||||
switch latestEventContent {
|
||||
case .message(let content):
|
||||
switch content {
|
||||
@@ -31,58 +31,69 @@ struct TimelineThreadSummaryView: View {
|
||||
ThreadView(senderID: senderID,
|
||||
sender: sender,
|
||||
plainBody: content.caption ?? content.filename,
|
||||
formattedBody: content.formattedCaption)
|
||||
formattedBody: content.formattedCaption,
|
||||
numberOfReplies: numberOfReplies)
|
||||
case .emote(let content):
|
||||
ThreadView(senderID: senderID,
|
||||
sender: sender,
|
||||
plainBody: content.body,
|
||||
formattedBody: content.formattedBody)
|
||||
formattedBody: content.formattedBody,
|
||||
numberOfReplies: numberOfReplies)
|
||||
case .file(let content):
|
||||
ThreadView(senderID: senderID,
|
||||
sender: sender,
|
||||
plainBody: content.caption ?? content.filename,
|
||||
formattedBody: content.formattedCaption)
|
||||
formattedBody: content.formattedCaption,
|
||||
numberOfReplies: numberOfReplies)
|
||||
case .image(let content):
|
||||
ThreadView(senderID: senderID,
|
||||
sender: sender,
|
||||
plainBody: content.caption ?? content.filename,
|
||||
formattedBody: content.formattedCaption)
|
||||
formattedBody: content.formattedCaption,
|
||||
numberOfReplies: numberOfReplies)
|
||||
case .notice(let content):
|
||||
ThreadView(senderID: senderID,
|
||||
sender: sender,
|
||||
plainBody: content.body,
|
||||
formattedBody: content.formattedBody)
|
||||
formattedBody: content.formattedBody,
|
||||
numberOfReplies: numberOfReplies)
|
||||
case .text(let content):
|
||||
ThreadView(senderID: senderID,
|
||||
sender: sender,
|
||||
plainBody: content.body,
|
||||
formattedBody: content.formattedBody)
|
||||
formattedBody: content.formattedBody,
|
||||
numberOfReplies: numberOfReplies)
|
||||
case .video(let content):
|
||||
ThreadView(senderID: senderID,
|
||||
sender: sender,
|
||||
plainBody: content.caption ?? content.filename,
|
||||
formattedBody: content.formattedCaption)
|
||||
formattedBody: content.formattedCaption,
|
||||
numberOfReplies: numberOfReplies)
|
||||
case .voice:
|
||||
ThreadView(senderID: senderID,
|
||||
sender: sender,
|
||||
plainBody: L10n.commonVoiceMessage,
|
||||
formattedBody: nil)
|
||||
formattedBody: nil,
|
||||
numberOfReplies: numberOfReplies)
|
||||
case .location:
|
||||
ThreadView(senderID: senderID,
|
||||
sender: sender,
|
||||
plainBody: L10n.commonSharedLocation,
|
||||
formattedBody: nil)
|
||||
formattedBody: nil,
|
||||
numberOfReplies: numberOfReplies)
|
||||
}
|
||||
case .poll(let question):
|
||||
ThreadView(senderID: senderID,
|
||||
sender: sender,
|
||||
plainBody: question,
|
||||
formattedBody: nil)
|
||||
formattedBody: nil,
|
||||
numberOfReplies: numberOfReplies)
|
||||
case .redacted:
|
||||
ThreadView(senderID: senderID,
|
||||
sender: sender,
|
||||
plainBody: L10n.commonMessageRemoved,
|
||||
formattedBody: nil)
|
||||
formattedBody: nil,
|
||||
numberOfReplies: numberOfReplies)
|
||||
}
|
||||
default:
|
||||
LoadingThreadView()
|
||||
@@ -91,7 +102,11 @@ struct TimelineThreadSummaryView: View {
|
||||
|
||||
private struct LoadingThreadView: View {
|
||||
var body: some View {
|
||||
ThreadView(senderID: "@alice:matrix.org", sender: nil, plainBody: "Hello world", formattedBody: nil)
|
||||
ThreadView(senderID: "@alice:matrix.org",
|
||||
sender: nil,
|
||||
plainBody: "Hello world",
|
||||
formattedBody: nil,
|
||||
numberOfReplies: 42)
|
||||
.redacted(reason: .placeholder)
|
||||
}
|
||||
}
|
||||
@@ -103,12 +118,17 @@ struct TimelineThreadSummaryView: View {
|
||||
let sender: TimelineItemSender?
|
||||
let plainBody: String
|
||||
let formattedBody: AttributedString?
|
||||
let numberOfReplies: Int
|
||||
|
||||
var body: some View {
|
||||
HStack(spacing: 8) {
|
||||
HStack(spacing: 4) {
|
||||
CompoundIcon(\.threads, size: .xSmall, relativeTo: .compound.bodyXS)
|
||||
.foregroundColor(.compound.iconSecondary)
|
||||
|
||||
Text(L10n.commonReplies(numberOfReplies))
|
||||
.font(.compound.bodyXSSemibold)
|
||||
.foregroundColor(.compound.textPrimary)
|
||||
|
||||
LoadableAvatarImage(url: sender?.avatarURL,
|
||||
name: sender?.displayName,
|
||||
contentID: sender?.id,
|
||||
@@ -121,13 +141,11 @@ struct TimelineThreadSummaryView: View {
|
||||
.accessibilityLabel(L10n.commonInReplyTo(sender?.disambiguatedDisplayName ?? senderID))
|
||||
|
||||
Text(context.viewState.buildMessagePreview(formattedBody: formattedBody, plainBody: plainBody))
|
||||
.multilineTextAlignment(.leading)
|
||||
.font(.compound.bodyXS)
|
||||
.foregroundColor(.compound.textSecondary)
|
||||
.tint(.compound.textLinkExternal)
|
||||
.lineLimit(2)
|
||||
}
|
||||
.padding(.vertical, 4.0)
|
||||
.lineLimit(1)
|
||||
.padding(.vertical, 7.0)
|
||||
.padding(.horizontal, 8.0)
|
||||
.background(Color.compound.bgSubtlePrimary)
|
||||
.cornerRadius(8)
|
||||
@@ -183,12 +201,14 @@ struct TimelineThreadSummaryView_Previews: PreviewProvider, TestablePreview {
|
||||
TimelineThreadSummaryView(threadSummary: .error(message: "Error")),
|
||||
|
||||
TimelineThreadSummaryView(threadSummary: .loaded(senderID: "@alice:matrix.org",
|
||||
sender: .init(id: "@alice:matrix.org", displayName: "Alice"),
|
||||
latestEventContent: .message(.text(.init(body: "This is a threaded message"))))),
|
||||
sender: .init(id: "@alice:matrix.org", displayName: "Alice McAliceFace"),
|
||||
latestEventContent: .message(.text(.init(body: "This is a very long, multi-lined, threaded message"))),
|
||||
numberOfReplies: 42)),
|
||||
|
||||
TimelineThreadSummaryView(threadSummary: .loaded(senderID: "@alice:matrix.org",
|
||||
sender: .init(id: "@alice:matrix.org", displayName: "Alice"),
|
||||
latestEventContent: .message(.notice(.init(body: "Hello world"))))),
|
||||
latestEventContent: .message(.notice(.init(body: "Hello world"))),
|
||||
numberOfReplies: 42)),
|
||||
|
||||
TimelineThreadSummaryView(threadSummary: .loaded(senderID: "@alice:matrix.org",
|
||||
sender: .init(id: "@alice:matrix.org", displayName: "Alice"),
|
||||
@@ -198,7 +218,8 @@ struct TimelineThreadSummaryView_Previews: PreviewProvider, TestablePreview {
|
||||
waveform: nil,
|
||||
source: nil,
|
||||
fileSize: nil,
|
||||
contentType: nil))))),
|
||||
contentType: nil))),
|
||||
numberOfReplies: 42)),
|
||||
|
||||
TimelineThreadSummaryView(threadSummary: .loaded(senderID: "@alice:matrix.org",
|
||||
sender: .init(id: "@alice:matrix.org", displayName: "Alice"),
|
||||
@@ -207,24 +228,28 @@ struct TimelineThreadSummaryView_Previews: PreviewProvider, TestablePreview {
|
||||
source: nil,
|
||||
fileSize: nil,
|
||||
thumbnailSource: nil,
|
||||
contentType: nil))))),
|
||||
contentType: nil))),
|
||||
numberOfReplies: 42)),
|
||||
|
||||
TimelineThreadSummaryView(threadSummary: .loaded(senderID: "@alice:matrix.org",
|
||||
sender: .init(id: "@alice:matrix.org", displayName: "Alice"),
|
||||
latestEventContent: .message(.image(.init(filename: "image.jpg",
|
||||
caption: "Some image",
|
||||
imageInfo: .mockImage,
|
||||
thumbnailInfo: .mockThumbnail))))),
|
||||
thumbnailInfo: .mockThumbnail))),
|
||||
numberOfReplies: 42)),
|
||||
|
||||
TimelineThreadSummaryView(threadSummary: .loaded(senderID: "@alice:matrix.org",
|
||||
sender: .init(id: "@alice:matrix.org", displayName: "Alice"),
|
||||
latestEventContent: .message(.video(.init(filename: "video.mp4",
|
||||
caption: "Some video",
|
||||
videoInfo: .mockVideo,
|
||||
thumbnailInfo: .mockVideoThumbnail))))),
|
||||
thumbnailInfo: .mockVideoThumbnail))),
|
||||
numberOfReplies: 42)),
|
||||
TimelineThreadSummaryView(threadSummary: .loaded(senderID: "@alice:matrix.org",
|
||||
sender: .init(id: "@alice:matrix.org", displayName: "Alice"),
|
||||
latestEventContent: .message(.location(.init(body: ""))))),
|
||||
latestEventContent: .message(.location(.init(body: ""))),
|
||||
numberOfReplies: 42)),
|
||||
|
||||
TimelineThreadSummaryView(threadSummary: .loaded(senderID: "@alice:matrix.org",
|
||||
sender: .init(id: "@alice:matrix.org", displayName: "Alice"),
|
||||
@@ -234,34 +259,43 @@ struct TimelineThreadSummaryView_Previews: PreviewProvider, TestablePreview {
|
||||
waveform: nil,
|
||||
source: nil,
|
||||
fileSize: nil,
|
||||
contentType: nil))))),
|
||||
contentType: nil))),
|
||||
numberOfReplies: 42)),
|
||||
|
||||
TimelineThreadSummaryView(threadSummary: .loaded(senderID: "@alice:matrix.org",
|
||||
sender: .init(id: "@alice:matrix.org", displayName: "Alice"),
|
||||
latestEventContent: .poll(question: "Do you like polls?"))),
|
||||
latestEventContent: .poll(question: "Do you like polls?"),
|
||||
numberOfReplies: 42)),
|
||||
|
||||
TimelineThreadSummaryView(threadSummary: .loaded(senderID: "@alice:matrix.org",
|
||||
sender: .init(id: "@alice:matrix.org", displayName: "Alice"),
|
||||
latestEventContent: .redacted)),
|
||||
latestEventContent: .redacted,
|
||||
numberOfReplies: 42)),
|
||||
|
||||
TimelineThreadSummaryView(threadSummary: .loaded(senderID: "@alice:matrix.org",
|
||||
sender: .init(id: "@alice:matrix.org", displayName: "Alice"),
|
||||
latestEventContent: .message(.notice(.init(body: "", formattedBody: attributedStringWithMention))))),
|
||||
latestEventContent: .message(.notice(.init(body: "", formattedBody: attributedStringWithMention))),
|
||||
numberOfReplies: 42)),
|
||||
TimelineThreadSummaryView(threadSummary: .loaded(senderID: "@alice:matrix.org",
|
||||
sender: .init(id: "@alice:matrix.org", displayName: "Alice"),
|
||||
latestEventContent: .message(.notice(.init(body: "", formattedBody: attributedStringWithAtRoomMention))))),
|
||||
latestEventContent: .message(.notice(.init(body: "", formattedBody: attributedStringWithAtRoomMention))),
|
||||
numberOfReplies: 42)),
|
||||
TimelineThreadSummaryView(threadSummary: .loaded(senderID: "@alice:matrix.org",
|
||||
sender: .init(id: "@alice:matrix.org", displayName: "Alice"),
|
||||
latestEventContent: .message(.notice(.init(body: "", formattedBody: attributedStringWithRoomAliasMention))))),
|
||||
latestEventContent: .message(.notice(.init(body: "", formattedBody: attributedStringWithRoomAliasMention))),
|
||||
numberOfReplies: 42)),
|
||||
TimelineThreadSummaryView(threadSummary: .loaded(senderID: "@alice:matrix.org",
|
||||
sender: .init(id: "@alice:matrix.org", displayName: "Alice"),
|
||||
latestEventContent: .message(.notice(.init(body: "", formattedBody: attributedStringWithRoomIDMention))))),
|
||||
latestEventContent: .message(.notice(.init(body: "", formattedBody: attributedStringWithRoomIDMention))),
|
||||
numberOfReplies: 42)),
|
||||
TimelineThreadSummaryView(threadSummary: .loaded(senderID: "@alice:matrix.org",
|
||||
sender: .init(id: "@alice:matrix.org", displayName: "Alice"),
|
||||
latestEventContent: .message(.notice(.init(body: "", formattedBody: attributedStringWithEventOnRoomIDMention))))),
|
||||
latestEventContent: .message(.notice(.init(body: "", formattedBody: attributedStringWithEventOnRoomIDMention))),
|
||||
numberOfReplies: 42)),
|
||||
TimelineThreadSummaryView(threadSummary: .loaded(senderID: "@alice:matrix.org",
|
||||
sender: .init(id: "@alice:matrix.org", displayName: "Alice"),
|
||||
latestEventContent: .message(.notice(.init(body: "", formattedBody: attributedStringWithEventOnRoomAliasMention)))))
|
||||
latestEventContent: .message(.notice(.init(body: "", formattedBody: attributedStringWithEventOnRoomAliasMention))),
|
||||
numberOfReplies: 42))
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@ enum EncryptionAuthenticity: Hashable {
|
||||
case unverifiedIdentity(color: Color)
|
||||
case verificationViolation(color: Color)
|
||||
case sentInClear(color: Color)
|
||||
case mismatchedSender(color: Color)
|
||||
|
||||
var message: String {
|
||||
switch self {
|
||||
@@ -36,6 +37,8 @@ enum EncryptionAuthenticity: Hashable {
|
||||
L10n.eventShieldReasonPreviouslyVerified
|
||||
case .sentInClear:
|
||||
L10n.eventShieldReasonSentInClear
|
||||
case .mismatchedSender:
|
||||
L10n.eventShieldMismatchedSender
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,7 +49,8 @@ enum EncryptionAuthenticity: Hashable {
|
||||
.unsignedDevice(let color),
|
||||
.unverifiedIdentity(let color),
|
||||
.verificationViolation(let color),
|
||||
.sentInClear(let color):
|
||||
.sentInClear(let color),
|
||||
.mismatchedSender(let color):
|
||||
color
|
||||
}
|
||||
}
|
||||
@@ -54,7 +58,7 @@ enum EncryptionAuthenticity: Hashable {
|
||||
var icon: KeyPath<CompoundIcons, Image> {
|
||||
switch self {
|
||||
case .notGuaranteed: \.info
|
||||
case .unknownDevice, .unsignedDevice, .unverifiedIdentity, .verificationViolation: \.helpSolid
|
||||
case .unknownDevice, .unsignedDevice, .unverifiedIdentity, .verificationViolation, .mismatchedSender: \.helpSolid
|
||||
case .sentInClear: \.lockOff
|
||||
}
|
||||
}
|
||||
@@ -86,6 +90,8 @@ extension EncryptionAuthenticity {
|
||||
self = .verificationViolation(color: color)
|
||||
case .sentInClear:
|
||||
self = .sentInClear(color: color)
|
||||
case .mismatchedSender:
|
||||
self = .mismatchedSender(color: color)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -10,6 +10,6 @@ import MatrixRustSDK
|
||||
enum TimelineItemThreadSummary: Hashable {
|
||||
case notLoaded
|
||||
case loading
|
||||
case loaded(senderID: String, sender: TimelineItemSender, latestEventContent: TimelineEventContent)
|
||||
case loaded(senderID: String, sender: TimelineItemSender, latestEventContent: TimelineEventContent, numberOfReplies: Int)
|
||||
case error(message: String)
|
||||
}
|
||||
|
||||
@@ -699,7 +699,8 @@ struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol {
|
||||
|
||||
return .loaded(senderID: senderID,
|
||||
sender: sender,
|
||||
latestEventContent: latestEventContent)
|
||||
latestEventContent: latestEventContent,
|
||||
numberOfReplies: Int(threadSummary.numReplies()))
|
||||
|
||||
case .error(let message):
|
||||
return .error(message: message)
|
||||
|
||||
Reference in New Issue
Block a user