Added custom reply views for all message types as well as a loading state

This commit is contained in:
Stefan Ceriu
2023-05-15 17:04:37 +03:00
committed by Stefan Ceriu
parent 1b4ef0b35c
commit 0608c68aee
3 changed files with 88 additions and 30 deletions
@@ -25,58 +25,116 @@ struct TimelineReplyView: View {
case .loaded(let sender, let content): case .loaded(let sender, let content):
switch content { switch content {
case .audio(let content): case .audio(let content):
TimelineTextReplyView(sender: sender, plainBody: content.body, formattedBody: nil) ReplyView(sender: sender, plainBody: content.body, formattedBody: nil, systemIconName: "waveform")
case .emote(let content): case .emote(let content):
TimelineTextReplyView(sender: sender, plainBody: content.body, formattedBody: content.formattedBody) ReplyView(sender: sender, plainBody: content.body, formattedBody: content.formattedBody)
case .file(let content): case .file(let content):
TimelineTextReplyView(sender: sender, plainBody: content.body, formattedBody: nil) ReplyView(sender: sender, plainBody: content.body, formattedBody: nil, systemIconName: "doc.text.fill")
case .image(let content): case .image(let content):
TimelineTextReplyView(sender: sender, plainBody: content.body, formattedBody: nil) ReplyView(sender: sender, plainBody: content.body, formattedBody: nil, mediaSource: content.thumbnailSource ?? content.source)
case .notice(let content): case .notice(let content):
TimelineTextReplyView(sender: sender, plainBody: content.body, formattedBody: content.formattedBody) ReplyView(sender: sender, plainBody: content.body, formattedBody: content.formattedBody)
case .text(let content): case .text(let content):
TimelineTextReplyView(sender: sender, plainBody: content.body, formattedBody: content.formattedBody) ReplyView(sender: sender, plainBody: content.body, formattedBody: content.formattedBody)
case .video(let content): case .video(let content):
TimelineTextReplyView(sender: sender, plainBody: content.body, formattedBody: nil) ReplyView(sender: sender, plainBody: content.body, formattedBody: nil, mediaSource: content.thumbnailSource)
} }
default: default:
Text("Missing in-reply-to details") LoadingReplyView()
.font(.compound.bodyMD)
.foregroundColor(.element.secondaryContent)
.padding()
} }
} }
} }
private struct TimelineTextReplyView: View { private struct LoadingReplyView: View {
var body: some View {
ReplyView(sender: .init(id: "@alice:matrix.org"), plainBody: "Hello world", formattedBody: nil)
.redacted(reason: .placeholder)
}
}
private struct ReplyView: View {
@EnvironmentObject private var context: RoomScreenViewModel.Context
@ScaledMetric private var imageContainerSize = 36.0
let sender: TimelineItemSender let sender: TimelineItemSender
let plainBody: String let plainBody: String
let formattedBody: AttributedString? let formattedBody: AttributedString?
var systemIconName: String?
var mediaSource: MediaSourceProxy?
var body: some View { var body: some View {
VStack(alignment: .leading) { HStack {
Text(sender.displayName ?? sender.id) icon
.font(.compound.bodySMSemibold) .frame(width: imageContainerSize, height: imageContainerSize)
.foregroundColor(.compound.textPrimary) .foregroundColor(.element.primaryContent)
.background(Color.compound.bgSubtlePrimary)
.cornerRadius(9.0, corners: .allCorners)
Text(formattedBody ?? AttributedString(plainBody)) VStack(alignment: .leading) {
.font(.compound.bodyMD) Text(sender.displayName ?? sender.id)
.foregroundColor(.compound.textPlaceholder) .font(.compound.bodySMSemibold)
.tint(.element.links) .foregroundColor(.compound.textPrimary)
.lineLimit(2)
Text(formattedBody ?? AttributedString(plainBody))
.font(.compound.bodyMD)
.foregroundColor(.compound.textPlaceholder)
.tint(.element.links)
.lineLimit(2)
}
}
}
@ViewBuilder
private var icon: some View {
if let mediaSource {
LoadableImage(mediaSource: mediaSource, size: .init(width: imageContainerSize, height: imageContainerSize), imageProvider: context.imageProvider) {
Image(systemName: "photo")
.padding(4.0)
}
.aspectRatio(contentMode: .fill)
}
if let systemIconName {
Image(systemName: systemIconName)
.resizable()
.aspectRatio(contentMode: .fit)
.padding(4.0)
} }
} }
} }
} }
struct TimelineReplyView_Previews: PreviewProvider { struct TimelineReplyView_Previews: PreviewProvider {
static let viewModel = RoomScreenViewModel.mock
static var previews: some View { static var previews: some View {
TimelineReplyView(timelineItemReplyDetails: .loaded(sender: .init(id: "", displayName: "Alice"), VStack(alignment: .leading) {
content: .text(.init(body: "This is a reply")))) TimelineReplyView(timelineItemReplyDetails: .notLoaded(eventID: ""))
.background(Color.element.background)
.cornerRadius(8) TimelineReplyView(timelineItemReplyDetails: .loading(eventID: ""))
.padding(8)
.background(Color.element.bubblesYou) TimelineReplyView(timelineItemReplyDetails: .loaded(sender: .init(id: "", displayName: "Alice"),
.cornerRadius(12) content: .text(.init(body: "This is a reply"))))
TimelineReplyView(timelineItemReplyDetails: .loaded(sender: .init(id: "", displayName: "Alice"),
content: .emote(.init(body: "says hello"))))
TimelineReplyView(timelineItemReplyDetails: .loaded(sender: .init(id: "", displayName: "Bot"),
content: .notice(.init(body: "Hello world"))))
TimelineReplyView(timelineItemReplyDetails: .loaded(sender: .init(id: "", displayName: "Alice"),
content: .audio(.init(body: "Some audio", duration: 0, source: nil, contentType: nil))))
TimelineReplyView(timelineItemReplyDetails: .loaded(sender: .init(id: "", displayName: "Alice"),
content: .file(.init(body: "Some file", source: nil, thumbnailSource: nil, contentType: nil))))
let imageSource = MediaSourceProxy(url: .init(staticString: "https://mock.com"), mimeType: "image/png")
TimelineReplyView(timelineItemReplyDetails: .loaded(sender: .init(id: "", displayName: "Alice"), content: .image(.init(body: "Some image", source: imageSource, thumbnailSource: imageSource))))
TimelineReplyView(timelineItemReplyDetails: .loaded(sender: .init(id: "", displayName: "Alice"), content: .video(.init(body: "Some video", duration: 0, source: nil, thumbnailSource: imageSource))))
}
.environmentObject(viewModel.context)
} }
} }
@@ -76,6 +76,6 @@ extension View {
func timelineQuoteBubbleFormatting() -> some View { func timelineQuoteBubbleFormatting() -> some View {
foregroundColor(.compound.textPlaceholder) foregroundColor(.compound.textPlaceholder)
.fixedSize(horizontal: false, vertical: true) .fixedSize(horizontal: false, vertical: true)
.padding(EdgeInsets(top: 4, leading: 12, bottom: 4, trailing: 12)) .padding(4.0)
} }
} }
@@ -50,7 +50,7 @@ struct TimelineItemPlainStylerView<Content: View>: View {
let replyDetails = messageTimelineItem.replyDetails { let replyDetails = messageTimelineItem.replyDetails {
HStack(spacing: 4.0) { HStack(spacing: 4.0) {
Rectangle() Rectangle()
.foregroundColor(.element.accent) .foregroundColor(.global.melon)
.frame(width: 4.0) .frame(width: 4.0)
TimelineReplyView(timelineItemReplyDetails: replyDetails) TimelineReplyView(timelineItemReplyDetails: replyDetails)
} }