From 5c7761e85162907419edc3708b3b2084b6e14fcc Mon Sep 17 00:00:00 2001 From: Stefan Ceriu Date: Tue, 22 Mar 2022 10:09:46 +0200 Subject: [PATCH] Added html body to text messages and rendering through Apple's default NSAttributedString.DocumentType.html parser --- .../HomeScreen/HomeScreenViewModel.swift | 2 +- .../Room/Messages/ImageRoomMessage.swift | 4 +- .../Room/Messages/RoomMessageProtocol.swift | 2 +- .../Room/Messages/TextRoomMessage.swift | 8 +++- .../Sources/Services/Room/RoomProxy.swift | 46 ++++++++++--------- .../Timeline/MockRoomTimelineController.swift | 6 +-- .../EventBasedTimelineItemProtocol.swift | 2 +- .../Items/ImageRoomTimelineItem.swift | 2 +- .../Items/TextRoomTimelineItem.swift | 3 +- .../RoomTimelineItemFactory.swift | 5 +- .../Views/ImageRoomTimelineView.swift | 8 ++-- .../Views/TextRoomTimelineView.swift | 34 +++++++++++--- 12 files changed, 77 insertions(+), 45 deletions(-) diff --git a/ElementX/Sources/Screens/HomeScreen/HomeScreenViewModel.swift b/ElementX/Sources/Screens/HomeScreen/HomeScreenViewModel.swift index 739a077fe..89d0c3d79 100644 --- a/ElementX/Sources/Screens/HomeScreen/HomeScreenViewModel.swift +++ b/ElementX/Sources/Screens/HomeScreen/HomeScreenViewModel.swift @@ -159,7 +159,7 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol return } - self.updateLastMessage(lastMessage.content, forRoomWithIdentifier: roomIdentifier) + self.updateLastMessage(lastMessage.body, forRoomWithIdentifier: roomIdentifier) default: break } diff --git a/ElementX/Sources/Services/Room/Messages/ImageRoomMessage.swift b/ElementX/Sources/Services/Room/Messages/ImageRoomMessage.swift index 67c218136..afe17008a 100644 --- a/ElementX/Sources/Services/Room/Messages/ImageRoomMessage.swift +++ b/ElementX/Sources/Services/Room/Messages/ImageRoomMessage.swift @@ -20,8 +20,8 @@ struct ImageRoomMessage: RoomMessageProtocol { message.baseMessage().id() } - var content: String { - message.baseMessage().content() + var body: String { + message.baseMessage().body() } var sender: String { diff --git a/ElementX/Sources/Services/Room/Messages/RoomMessageProtocol.swift b/ElementX/Sources/Services/Room/Messages/RoomMessageProtocol.swift index 13c97b5ad..879e7213c 100644 --- a/ElementX/Sources/Services/Room/Messages/RoomMessageProtocol.swift +++ b/ElementX/Sources/Services/Room/Messages/RoomMessageProtocol.swift @@ -10,7 +10,7 @@ import Foundation protocol RoomMessageProtocol { var id: String { get } - var content: String { get } + var body: String { get } var sender: String { get } var originServerTs: Date { get } } diff --git a/ElementX/Sources/Services/Room/Messages/TextRoomMessage.swift b/ElementX/Sources/Services/Room/Messages/TextRoomMessage.swift index 0622860d7..22b9bd57b 100644 --- a/ElementX/Sources/Services/Room/Messages/TextRoomMessage.swift +++ b/ElementX/Sources/Services/Room/Messages/TextRoomMessage.swift @@ -21,8 +21,12 @@ struct TextRoomMessage: RoomMessageProtocol { message.baseMessage().id() } - var content: String { - message.baseMessage().content() + var body: String { + message.baseMessage().body() + } + + var htmlBody: String? { + message.htmlBody() } var sender: String { diff --git a/ElementX/Sources/Services/Room/RoomProxy.swift b/ElementX/Sources/Services/Room/RoomProxy.swift index dbd105a9d..6c3a386d9 100644 --- a/ElementX/Sources/Services/Room/RoomProxy.swift +++ b/ElementX/Sources/Services/Room/RoomProxy.swift @@ -49,31 +49,35 @@ class RoomProxy: RoomProxyProtocol, Equatable { } var id: String { - return 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() + room.id() } var name: String? { - return room.name() + room.name() } 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? { @@ -154,7 +158,7 @@ class RoomProxy: RoomProxyProtocol, Equatable { DispatchQueue.main.async { callback?(.success(messages)) 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) { let message = self.messageFactory.buildRoomMessageFrom(message) - lastMessage = message.content + lastMessage = message.body callbacks.send(.addedMessage(message)) } diff --git a/ElementX/Sources/Services/Timeline/MockRoomTimelineController.swift b/ElementX/Sources/Services/Timeline/MockRoomTimelineController.swift index 45e7d443e..b0cebcbf6 100644 --- a/ElementX/Sources/Services/Timeline/MockRoomTimelineController.swift +++ b/ElementX/Sources/Services/Timeline/MockRoomTimelineController.swift @@ -14,10 +14,10 @@ class MockRoomTimelineController: RoomTimelineControllerProtocol { let callbacks = PassthroughSubject() 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, text: "You also rule!", timestamp: "10:11 AM", shouldShowSenderDetails: false, senderId: "Alice"), + TextRoomTimelineItem(id: UUID().uuidString, body: "You rock!", timestamp: "10:10 AM", shouldShowSenderDetails: true, senderId: "Alice"), + TextRoomTimelineItem(id: UUID().uuidString, body: "You also rule!", timestamp: "10:11 AM", shouldShowSenderDetails: false, senderId: "Alice"), 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)) { callbacks.send(.updatedTimelineItems) diff --git a/ElementX/Sources/Services/Timeline/TimelineItems/EventBasedTimelineItemProtocol.swift b/ElementX/Sources/Services/Timeline/TimelineItems/EventBasedTimelineItemProtocol.swift index 891f0f002..ba6aed4ca 100644 --- a/ElementX/Sources/Services/Timeline/TimelineItems/EventBasedTimelineItemProtocol.swift +++ b/ElementX/Sources/Services/Timeline/TimelineItems/EventBasedTimelineItemProtocol.swift @@ -10,7 +10,7 @@ import Foundation import UIKit protocol EventBasedTimelineItemProtocol: RoomTimelineItemProtocol { - var text: String { get } + var body: String { get } var timestamp: String { get } var shouldShowSenderDetails: Bool { get } diff --git a/ElementX/Sources/Services/Timeline/TimelineItems/Items/ImageRoomTimelineItem.swift b/ElementX/Sources/Services/Timeline/TimelineItems/Items/ImageRoomTimelineItem.swift index b375acbf1..409678796 100644 --- a/ElementX/Sources/Services/Timeline/TimelineItems/Items/ImageRoomTimelineItem.swift +++ b/ElementX/Sources/Services/Timeline/TimelineItems/Items/ImageRoomTimelineItem.swift @@ -11,7 +11,7 @@ import UIKit struct ImageRoomTimelineItem: EventBasedTimelineItemProtocol, Identifiable, Equatable { let id: String - let text: String + let body: String let timestamp: String let shouldShowSenderDetails: Bool diff --git a/ElementX/Sources/Services/Timeline/TimelineItems/Items/TextRoomTimelineItem.swift b/ElementX/Sources/Services/Timeline/TimelineItems/Items/TextRoomTimelineItem.swift index 4c13fc8ec..224091673 100644 --- a/ElementX/Sources/Services/Timeline/TimelineItems/Items/TextRoomTimelineItem.swift +++ b/ElementX/Sources/Services/Timeline/TimelineItems/Items/TextRoomTimelineItem.swift @@ -11,7 +11,8 @@ import UIKit struct TextRoomTimelineItem: EventBasedTimelineItemProtocol, Identifiable, Equatable { let id: String - let text: String + let body: String + var htmlBody: String? let timestamp: String let shouldShowSenderDetails: Bool diff --git a/ElementX/Sources/Services/Timeline/TimelineItems/RoomTimelineItemFactory.swift b/ElementX/Sources/Services/Timeline/TimelineItems/RoomTimelineItemFactory.swift index c63e6ec96..c9924d3de 100644 --- a/ElementX/Sources/Services/Timeline/TimelineItems/RoomTimelineItemFactory.swift +++ b/ElementX/Sources/Services/Timeline/TimelineItems/RoomTimelineItemFactory.swift @@ -28,7 +28,8 @@ struct RoomTimelineItemFactory { switch roomMessage { case let message as TextRoomMessage: return TextRoomTimelineItem(id: message.id, - text: message.content, + body: message.body, + htmlBody: message.htmlBody, timestamp: message.originServerTs.formatted(date: .omitted, time: .shortened), shouldShowSenderDetails: showSenderDetails, senderId: message.sender, @@ -36,7 +37,7 @@ struct RoomTimelineItemFactory { senderAvatar: avatarImage) case let message as ImageRoomMessage: return ImageRoomTimelineItem(id: message.id, - text: message.content, + body: message.body, timestamp: message.originServerTs.formatted(date: .omitted, time: .shortened), shouldShowSenderDetails: showSenderDetails, senderId: message.sender, diff --git a/ElementX/Sources/Services/Timeline/TimelineItems/Views/ImageRoomTimelineView.swift b/ElementX/Sources/Services/Timeline/TimelineItems/Views/ImageRoomTimelineView.swift index 57627a59f..0f4a14aff 100644 --- a/ElementX/Sources/Services/Timeline/TimelineItems/Views/ImageRoomTimelineView.swift +++ b/ElementX/Sources/Services/Timeline/TimelineItems/Views/ImageRoomTimelineView.swift @@ -16,7 +16,7 @@ struct ImageRoomTimelineView: View { if let image = timelineItem.image { VStack(alignment: .leading) { EventBasedTimelineView(timelineItem: timelineItem) - Text(timelineItem.text) + Text(timelineItem.body) Image(uiImage: image) .resizable() .scaledToFit() @@ -24,7 +24,7 @@ struct ImageRoomTimelineView: View { } else { VStack(alignment: .center) { HStack { - Text(timelineItem.text) + Text(timelineItem.body) Spacer() } ProgressView("Loading") @@ -37,7 +37,7 @@ struct ImageRoomTimelineView_Previews: PreviewProvider { static var previews: some View { VStack { let timelineItem = ImageRoomTimelineItem(id: UUID().uuidString, - text: "Some image", + body: "Some image", timestamp: "Now", shouldShowSenderDetails: false, senderId: "Bob", @@ -46,7 +46,7 @@ struct ImageRoomTimelineView_Previews: PreviewProvider { ImageRoomTimelineView(timelineItem: timelineItem) let timelineItem = ImageRoomTimelineItem(id: UUID().uuidString, - text: "Some other image", + body: "Some other image", timestamp: "Now", shouldShowSenderDetails: false, senderId: "Bob", diff --git a/ElementX/Sources/Services/Timeline/TimelineItems/Views/TextRoomTimelineView.swift b/ElementX/Sources/Services/Timeline/TimelineItems/Views/TextRoomTimelineView.swift index 969922a8e..efb5c4163 100644 --- a/ElementX/Sources/Services/Timeline/TimelineItems/Views/TextRoomTimelineView.swift +++ b/ElementX/Sources/Services/Timeline/TimelineItems/Views/TextRoomTimelineView.swift @@ -15,30 +15,52 @@ struct TextRoomTimelineView: View { var body: some View { VStack(alignment: .leading) { 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) } else { - Text(timelineItem.text) - .fixedSize(horizontal: false, vertical: true) + if let attributedString = try? AttributedString(markdown: timelineItem.body) { + Text(attributedString) + .fixedSize(horizontal: false, vertical: true) + } else { + Text(timelineItem.body) + .fixedSize(horizontal: false, vertical: true) + } } } .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 { static var previews: some View { VStack(spacing: 20.0) { 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", shouldShowSenderDetails: true, senderId: "Bob") TextRoomTimelineView(timelineItem: timelineItem) let timelineItem = TextRoomTimelineItem(id: UUID().uuidString, - text: "Some other text", + body: "Some other text", timestamp: "Later", shouldShowSenderDetails: true, senderId: "Anne")