Added html body to text messages and rendering through Apple's default NSAttributedString.DocumentType.html parser

This commit is contained in:
Stefan Ceriu 2022-03-22 10:09:46 +02:00
parent d201ca1ad8
commit 5c7761e851
12 changed files with 77 additions and 45 deletions

View File

@ -159,7 +159,7 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol
return return
} }
self.updateLastMessage(lastMessage.content, forRoomWithIdentifier: roomIdentifier) self.updateLastMessage(lastMessage.body, forRoomWithIdentifier: roomIdentifier)
default: default:
break break
} }

View File

@ -20,8 +20,8 @@ struct ImageRoomMessage: RoomMessageProtocol {
message.baseMessage().id() message.baseMessage().id()
} }
var content: String { var body: String {
message.baseMessage().content() message.baseMessage().body()
} }
var sender: String { var sender: String {

View File

@ -10,7 +10,7 @@ import Foundation
protocol RoomMessageProtocol { protocol RoomMessageProtocol {
var id: String { get } var id: String { get }
var content: String { get } var body: String { get }
var sender: String { get } var sender: String { get }
var originServerTs: Date { get } var originServerTs: Date { get }
} }

View File

@ -21,8 +21,12 @@ struct TextRoomMessage: RoomMessageProtocol {
message.baseMessage().id() message.baseMessage().id()
} }
var content: String { var body: String {
message.baseMessage().content() message.baseMessage().body()
}
var htmlBody: String? {
message.htmlBody()
} }
var sender: String { var sender: String {

View File

@ -49,31 +49,35 @@ class RoomProxy: RoomProxyProtocol, Equatable {
} }
var id: String { var id: String {
return room.id() room.id()
}
var isDirect: Bool {
return room.isDirect()
}
var isPublic: Bool {
return room.isPublic()
}
var isSpace: Bool {
return room.isSpace()
}
var isEncrypted: Bool {
return room.isEncrypted()
} }
var name: String? { var name: String? {
return room.name() room.name()
} }
var topic: String? { var topic: String? {
return room.topic() room.topic()
}
var isDirect: Bool {
room.isDirect()
}
var isPublic: Bool {
room.isPublic()
}
var isSpace: Bool {
room.isSpace()
}
var isEncrypted: Bool {
room.isEncrypted()
}
var isTombstoned: Bool {
room.isTombstoned()
} }
var lastMessage: String? { var lastMessage: String? {
@ -154,7 +158,7 @@ class RoomProxy: RoomProxyProtocol, Equatable {
DispatchQueue.main.async { DispatchQueue.main.async {
callback?(.success(messages)) callback?(.success(messages))
if self.lastMessage == nil { if self.lastMessage == nil {
self.lastMessage = messages.last?.content ?? "" self.lastMessage = messages.last?.body ?? ""
} }
} }
} }
@ -170,7 +174,7 @@ class RoomProxy: RoomProxyProtocol, Equatable {
fileprivate func appendMessage(_ message: AnyMessage) { fileprivate func appendMessage(_ message: AnyMessage) {
let message = self.messageFactory.buildRoomMessageFrom(message) let message = self.messageFactory.buildRoomMessageFrom(message)
lastMessage = message.content lastMessage = message.body
callbacks.send(.addedMessage(message)) callbacks.send(.addedMessage(message))
} }

View File

@ -14,10 +14,10 @@ class MockRoomTimelineController: RoomTimelineControllerProtocol {
let callbacks = PassthroughSubject<RoomTimelineControllerCallback, Never>() let callbacks = PassthroughSubject<RoomTimelineControllerCallback, Never>()
var timelineItems: [RoomTimelineItemProtocol] = [SeparatorRoomTimelineItem(id: UUID().uuidString, text: "Yesterday"), var timelineItems: [RoomTimelineItemProtocol] = [SeparatorRoomTimelineItem(id: UUID().uuidString, text: "Yesterday"),
TextRoomTimelineItem(id: UUID().uuidString, text: "You rock!", timestamp: "10:10 AM", shouldShowSenderDetails: true, senderId: "Alice"), TextRoomTimelineItem(id: UUID().uuidString, body: "You rock!", timestamp: "10:10 AM", shouldShowSenderDetails: true, senderId: "Alice"),
TextRoomTimelineItem(id: UUID().uuidString, text: "You also rule!", timestamp: "10:11 AM", shouldShowSenderDetails: false, senderId: "Alice"), TextRoomTimelineItem(id: UUID().uuidString, body: "You also rule!", timestamp: "10:11 AM", shouldShowSenderDetails: false, senderId: "Alice"),
SeparatorRoomTimelineItem(id: UUID().uuidString, text: "Today"), SeparatorRoomTimelineItem(id: UUID().uuidString, text: "Today"),
TextRoomTimelineItem(id: UUID().uuidString, text: "You too!", timestamp: "5 PM", shouldShowSenderDetails: true, senderId: "Bob")] TextRoomTimelineItem(id: UUID().uuidString, body: "You too!", timestamp: "5 PM", shouldShowSenderDetails: true, senderId: "Bob")]
func paginateBackwards(_ count: UInt, callback: ((Result<Void, RoomTimelineControllerError>) -> Void)) { func paginateBackwards(_ count: UInt, callback: ((Result<Void, RoomTimelineControllerError>) -> Void)) {
callbacks.send(.updatedTimelineItems) callbacks.send(.updatedTimelineItems)

View File

@ -10,7 +10,7 @@ import Foundation
import UIKit import UIKit
protocol EventBasedTimelineItemProtocol: RoomTimelineItemProtocol { protocol EventBasedTimelineItemProtocol: RoomTimelineItemProtocol {
var text: String { get } var body: String { get }
var timestamp: String { get } var timestamp: String { get }
var shouldShowSenderDetails: Bool { get } var shouldShowSenderDetails: Bool { get }

View File

@ -11,7 +11,7 @@ import UIKit
struct ImageRoomTimelineItem: EventBasedTimelineItemProtocol, Identifiable, Equatable { struct ImageRoomTimelineItem: EventBasedTimelineItemProtocol, Identifiable, Equatable {
let id: String let id: String
let text: String let body: String
let timestamp: String let timestamp: String
let shouldShowSenderDetails: Bool let shouldShowSenderDetails: Bool

View File

@ -11,7 +11,8 @@ import UIKit
struct TextRoomTimelineItem: EventBasedTimelineItemProtocol, Identifiable, Equatable { struct TextRoomTimelineItem: EventBasedTimelineItemProtocol, Identifiable, Equatable {
let id: String let id: String
let text: String let body: String
var htmlBody: String?
let timestamp: String let timestamp: String
let shouldShowSenderDetails: Bool let shouldShowSenderDetails: Bool

View File

@ -28,7 +28,8 @@ struct RoomTimelineItemFactory {
switch roomMessage { switch roomMessage {
case let message as TextRoomMessage: case let message as TextRoomMessage:
return TextRoomTimelineItem(id: message.id, return TextRoomTimelineItem(id: message.id,
text: message.content, body: message.body,
htmlBody: message.htmlBody,
timestamp: message.originServerTs.formatted(date: .omitted, time: .shortened), timestamp: message.originServerTs.formatted(date: .omitted, time: .shortened),
shouldShowSenderDetails: showSenderDetails, shouldShowSenderDetails: showSenderDetails,
senderId: message.sender, senderId: message.sender,
@ -36,7 +37,7 @@ struct RoomTimelineItemFactory {
senderAvatar: avatarImage) senderAvatar: avatarImage)
case let message as ImageRoomMessage: case let message as ImageRoomMessage:
return ImageRoomTimelineItem(id: message.id, return ImageRoomTimelineItem(id: message.id,
text: message.content, body: message.body,
timestamp: message.originServerTs.formatted(date: .omitted, time: .shortened), timestamp: message.originServerTs.formatted(date: .omitted, time: .shortened),
shouldShowSenderDetails: showSenderDetails, shouldShowSenderDetails: showSenderDetails,
senderId: message.sender, senderId: message.sender,

View File

@ -16,7 +16,7 @@ struct ImageRoomTimelineView: View {
if let image = timelineItem.image { if let image = timelineItem.image {
VStack(alignment: .leading) { VStack(alignment: .leading) {
EventBasedTimelineView(timelineItem: timelineItem) EventBasedTimelineView(timelineItem: timelineItem)
Text(timelineItem.text) Text(timelineItem.body)
Image(uiImage: image) Image(uiImage: image)
.resizable() .resizable()
.scaledToFit() .scaledToFit()
@ -24,7 +24,7 @@ struct ImageRoomTimelineView: View {
} else { } else {
VStack(alignment: .center) { VStack(alignment: .center) {
HStack { HStack {
Text(timelineItem.text) Text(timelineItem.body)
Spacer() Spacer()
} }
ProgressView("Loading") ProgressView("Loading")
@ -37,7 +37,7 @@ struct ImageRoomTimelineView_Previews: PreviewProvider {
static var previews: some View { static var previews: some View {
VStack { VStack {
let timelineItem = ImageRoomTimelineItem(id: UUID().uuidString, let timelineItem = ImageRoomTimelineItem(id: UUID().uuidString,
text: "Some image", body: "Some image",
timestamp: "Now", timestamp: "Now",
shouldShowSenderDetails: false, shouldShowSenderDetails: false,
senderId: "Bob", senderId: "Bob",
@ -46,7 +46,7 @@ struct ImageRoomTimelineView_Previews: PreviewProvider {
ImageRoomTimelineView(timelineItem: timelineItem) ImageRoomTimelineView(timelineItem: timelineItem)
let timelineItem = ImageRoomTimelineItem(id: UUID().uuidString, let timelineItem = ImageRoomTimelineItem(id: UUID().uuidString,
text: "Some other image", body: "Some other image",
timestamp: "Now", timestamp: "Now",
shouldShowSenderDetails: false, shouldShowSenderDetails: false,
senderId: "Bob", senderId: "Bob",

View File

@ -15,30 +15,52 @@ struct TextRoomTimelineView: View {
var body: some View { var body: some View {
VStack(alignment: .leading) { VStack(alignment: .leading) {
EventBasedTimelineView(timelineItem: timelineItem) EventBasedTimelineView(timelineItem: timelineItem)
if let attributedString = try? AttributedString(markdown: timelineItem.text) {
Text(attributedString) if let htmlString = buildHtmlString() {
Text(AttributedString(htmlString))
.fixedSize(horizontal: false, vertical: true) .fixedSize(horizontal: false, vertical: true)
} else { } else {
Text(timelineItem.text) if let attributedString = try? AttributedString(markdown: timelineItem.body) {
.fixedSize(horizontal: false, vertical: true) Text(attributedString)
.fixedSize(horizontal: false, vertical: true)
} else {
Text(timelineItem.body)
.fixedSize(horizontal: false, vertical: true)
}
} }
} }
.id(timelineItem.id) .id(timelineItem.id)
} }
private func buildHtmlString() -> NSAttributedString? {
guard let formattedText = timelineItem.htmlBody,
let encodedData = formattedText.data(using: String.Encoding.utf8) else {
return nil
}
do {
return try NSAttributedString(data: encodedData, options: [
NSAttributedString.DocumentReadingOptionKey.documentType: NSAttributedString.DocumentType.html,
NSAttributedString.DocumentReadingOptionKey.characterEncoding: NSNumber(value: String.Encoding.utf8.rawValue)
], documentAttributes: nil)
} catch {
return nil
}
}
} }
struct TextRoomTimelineView_Previews: PreviewProvider { struct TextRoomTimelineView_Previews: PreviewProvider {
static var previews: some View { static var previews: some View {
VStack(spacing: 20.0) { VStack(spacing: 20.0) {
let timelineItem = TextRoomTimelineItem(id: UUID().uuidString, let timelineItem = TextRoomTimelineItem(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.", body: "Short loin ground round tongue hamburger, fatback salami shoulder. Beef turkey sausage kielbasa strip steak. Alcatra capicola pig tail pancetta chislic.",
timestamp: "Now", timestamp: "Now",
shouldShowSenderDetails: true, shouldShowSenderDetails: true,
senderId: "Bob") senderId: "Bob")
TextRoomTimelineView(timelineItem: timelineItem) TextRoomTimelineView(timelineItem: timelineItem)
let timelineItem = TextRoomTimelineItem(id: UUID().uuidString, let timelineItem = TextRoomTimelineItem(id: UUID().uuidString,
text: "Some other text", body: "Some other text",
timestamp: "Later", timestamp: "Later",
shouldShowSenderDetails: true, shouldShowSenderDetails: true,
senderId: "Anne") senderId: "Anne")