Added support for emotes and notices.

This commit is contained in:
Stefan Ceriu
2022-03-29 12:13:41 +03:00
parent 2fcbd33158
commit ab25689e38
11 changed files with 347 additions and 30 deletions
@@ -0,0 +1,22 @@
//
// EmoteRoomTimelineItem.swift
// ElementX
//
// Created by Stefan Ceriu on 11/03/2022.
// Copyright © 2022 Element. All rights reserved.
//
import Foundation
import UIKit
struct EmoteRoomTimelineItem: EventBasedTimelineItemProtocol, Identifiable, Equatable {
let id: String
let text: String
var attributedComponents: [AttributedStringBuilderComponent]?
let timestamp: String
let shouldShowSenderDetails: Bool
let senderId: String
var senderDisplayName: String?
var senderAvatar: UIImage?
}
@@ -0,0 +1,22 @@
//
// NoticeRoomTimelineItem.swift
// ElementX
//
// Created by Stefan Ceriu on 11/03/2022.
// Copyright © 2022 Element. All rights reserved.
//
import Foundation
import UIKit
struct NoticeRoomTimelineItem: EventBasedTimelineItemProtocol, Identifiable, Equatable {
let id: String
let text: String
var attributedComponents: [AttributedStringBuilderComponent]?
let timestamp: String
let shouldShowSenderDetails: Bool
let senderId: String
var senderDisplayName: String?
var senderAvatar: UIImage?
}
@@ -23,37 +23,88 @@ struct RoomTimelineItemFactory {
}
func buildTimelineItemFor(_ roomMessage: RoomMessageProtocol, showSenderDetails: Bool) -> RoomTimelineItemProtocol {
let displayName = memberDetailsProvider.displayNameForUserId(roomMessage.sender)
let avatarURL = memberDetailsProvider.avatarURLForUserId(roomMessage.sender)
let avatarImage = mediaProvider.imageForURL(avatarURL)
switch roomMessage {
case let message as TextRoomMessage:
let attributedText = (message.htmlBody != nil ? attributedStringBuilder.fromHTML(message.htmlBody) : attributedStringBuilder.fromPlain(message.body))
let attributedComponents = attributedStringBuilder.blockquoteCoalescedComponentsFrom(attributedText)
return TextRoomTimelineItem(id: message.id,
text: message.body,
attributedComponents: attributedComponents,
timestamp: message.originServerTs.formatted(date: .omitted, time: .shortened),
shouldShowSenderDetails: showSenderDetails,
senderId: message.sender,
senderDisplayName: displayName,
senderAvatar: avatarImage)
return buildTextTimelineItemFromMessage(message, showSenderDetails, displayName, avatarImage)
case let message as ImageRoomMessage:
return ImageRoomTimelineItem(id: message.id,
text: message.body,
timestamp: message.originServerTs.formatted(date: .omitted, time: .shortened),
shouldShowSenderDetails: showSenderDetails,
senderId: message.sender,
senderDisplayName: displayName,
senderAvatar: avatarImage,
url: message.url,
image: mediaProvider.imageForURL(message.url))
return buildImageTimelineItemFromMessage(message, showSenderDetails, displayName, avatarImage)
case let message as NoticeRoomMessage:
return buildNoticeTimelineItemFromMessage(message, showSenderDetails, displayName, avatarImage)
case let message as EmoteRoomMessage:
return buildEmoteTimelineItemFromMessage(message, showSenderDetails, displayName, avatarImage)
default:
fatalError("Unknown room message.")
}
}
// MARK: - Private
private func buildTextTimelineItemFromMessage(_ message: TextRoomMessage,
_ showSenderDetails: Bool,
_ displayName: String?,
_ avatarImage: UIImage?) -> RoomTimelineItemProtocol {
let attributedText = (message.htmlBody != nil ? attributedStringBuilder.fromHTML(message.htmlBody) : attributedStringBuilder.fromPlain(message.body))
let attributedComponents = attributedStringBuilder.blockquoteCoalescedComponentsFrom(attributedText)
return TextRoomTimelineItem(id: message.id,
text: message.body,
attributedComponents: attributedComponents,
timestamp: message.originServerTs.formatted(date: .omitted, time: .shortened),
shouldShowSenderDetails: showSenderDetails,
senderId: message.sender,
senderDisplayName: displayName,
senderAvatar: avatarImage)
}
private func buildImageTimelineItemFromMessage(_ message: ImageRoomMessage,
_ showSenderDetails: Bool,
_ displayName: String?,
_ avatarImage: UIImage?) -> RoomTimelineItemProtocol {
return ImageRoomTimelineItem(id: message.id,
text: message.body,
timestamp: message.originServerTs.formatted(date: .omitted, time: .shortened),
shouldShowSenderDetails: showSenderDetails,
senderId: message.sender,
senderDisplayName: displayName,
senderAvatar: avatarImage,
url: message.url,
image: mediaProvider.imageForURL(message.url))
}
private func buildNoticeTimelineItemFromMessage(_ message: NoticeRoomMessage,
_ showSenderDetails: Bool,
_ displayName: String?,
_ avatarImage: UIImage?) -> RoomTimelineItemProtocol {
let attributedText = (message.htmlBody != nil ? attributedStringBuilder.fromHTML(message.htmlBody) : attributedStringBuilder.fromPlain(message.body))
let attributedComponents = attributedStringBuilder.blockquoteCoalescedComponentsFrom(attributedText)
return NoticeRoomTimelineItem(id: message.id,
text: message.body,
attributedComponents: attributedComponents,
timestamp: message.originServerTs.formatted(date: .omitted, time: .shortened),
shouldShowSenderDetails: showSenderDetails,
senderId: message.sender,
senderDisplayName: displayName,
senderAvatar: avatarImage)
}
private func buildEmoteTimelineItemFromMessage(_ message: EmoteRoomMessage,
_ showSenderDetails: Bool,
_ displayName: String?,
_ avatarImage: UIImage?) -> RoomTimelineItemProtocol {
let attributedText = (message.htmlBody != nil ? attributedStringBuilder.fromHTML(message.htmlBody) : attributedStringBuilder.fromPlain(message.body))
let attributedComponents = attributedStringBuilder.blockquoteCoalescedComponentsFrom(attributedText)
return EmoteRoomTimelineItem(id: message.id,
text: message.body,
attributedComponents: attributedComponents,
timestamp: message.originServerTs.formatted(date: .omitted, time: .shortened),
shouldShowSenderDetails: showSenderDetails,
senderId: message.sender,
senderDisplayName: displayName,
senderAvatar: avatarImage)
}
}
@@ -17,6 +17,10 @@ struct RoomTimelineViewFactory {
return .image(item)
case let item as SeparatorRoomTimelineItem:
return .separator(item)
case let item as NoticeRoomTimelineItem:
return .notice(item)
case let item as EmoteRoomTimelineItem:
return .emote(item)
default:
fatalError("Unknown timeline item")
}
@@ -13,6 +13,8 @@ enum RoomTimelineViewProvider: Identifiable, Equatable {
case text(TextRoomTimelineItem)
case separator(SeparatorRoomTimelineItem)
case image(ImageRoomTimelineItem)
case emote(EmoteRoomTimelineItem)
case notice(NoticeRoomTimelineItem)
var id: String {
switch self {
@@ -22,6 +24,10 @@ enum RoomTimelineViewProvider: Identifiable, Equatable {
return item.id
case .image(let item):
return item.id
case .emote(let item):
return item.id
case .notice(let item):
return item.id
}
}
}
@@ -35,6 +41,10 @@ extension RoomTimelineViewProvider: View {
SeparatorRoomTimelineView(timelineItem: item)
case .image(let item):
ImageRoomTimelineView(timelineItem: item)
case .emote(let item):
EmoteRoomTimelineView(timelineItem: item)
case .notice(let item):
NoticeRoomTimelineView(timelineItem: item)
}
}
}
@@ -0,0 +1,50 @@
//
// EmoteRoomTimelineView.swift
// ElementX
//
// Created by Stefan Ceriu on 11/03/2022.
// Copyright © 2022 Element. All rights reserved.
//
import Foundation
import SwiftUI
struct EmoteRoomTimelineView: View {
let timelineItem: EmoteRoomTimelineItem
var body: some View {
VStack(alignment: .leading) {
EventBasedTimelineView(timelineItem: timelineItem)
HStack(alignment: .top) {
Image(systemName: "face.dashed").padding(.top, 1.0)
if let attributedComponents = timelineItem.attributedComponents {
FormattedBodyText(attributedComponents: attributedComponents)
} else {
Text(timelineItem.text)
}
}
}
.id(timelineItem.id)
}
}
struct EmoteRoomTimelineView_Previews: PreviewProvider {
static var previews: some View {
VStack(spacing: 20.0) {
let timelineItem = EmoteRoomTimelineItem(id: UUID().uuidString,
text: "Short loin ground round tongue hamburger, fatback salami shoulder. Beef turkey sausage kielbasa strip steak. Alcatra capicola pig tail pancetta chislic.",
timestamp: "Now",
shouldShowSenderDetails: true,
senderId: "Bob")
EmoteRoomTimelineView(timelineItem: timelineItem)
let timelineItem = EmoteRoomTimelineItem(id: UUID().uuidString,
text: "Some other text",
timestamp: "Later",
shouldShowSenderDetails: true,
senderId: "Anne")
EmoteRoomTimelineView(timelineItem: timelineItem)
}
.padding()
}
}
@@ -0,0 +1,50 @@
//
// NoticeRoomTimelineView.swift
// ElementX
//
// Created by Stefan Ceriu on 11/03/2022.
// Copyright © 2022 Element. All rights reserved.
//
import Foundation
import SwiftUI
struct NoticeRoomTimelineView: View {
let timelineItem: NoticeRoomTimelineItem
var body: some View {
VStack(alignment: .leading) {
EventBasedTimelineView(timelineItem: timelineItem)
HStack(alignment: .top) {
Image(systemName: "exclamationmark.bubble").padding(.top, 2.0)
if let attributedComponents = timelineItem.attributedComponents {
FormattedBodyText(attributedComponents: attributedComponents)
} else {
Text(timelineItem.text)
}
}
}
.id(timelineItem.id)
}
}
struct NoticeRoomTimelineView_Previews: PreviewProvider {
static var previews: some View {
VStack(spacing: 20.0) {
let timelineItem = NoticeRoomTimelineItem(id: UUID().uuidString,
text: "Short loin ground round tongue hamburger, fatback salami shoulder. Beef turkey sausage kielbasa strip steak. Alcatra capicola pig tail pancetta chislic.",
timestamp: "Now",
shouldShowSenderDetails: true,
senderId: "Bob")
NoticeRoomTimelineView(timelineItem: timelineItem)
let timelineItem = NoticeRoomTimelineItem(id: UUID().uuidString,
text: "Some other text",
timestamp: "Later",
shouldShowSenderDetails: true,
senderId: "Anne")
NoticeRoomTimelineView(timelineItem: timelineItem)
}
.padding()
}
}