mirror of
https://github.com/element-hq/element-x-ios.git
synced 2025-03-10 21:39:12 +00:00
Added html body to text messages and rendering through Apple's default NSAttributedString.DocumentType.html parser
This commit is contained in:
parent
d201ca1ad8
commit
5c7761e851
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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 {
|
||||||
|
@ -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 }
|
||||||
}
|
}
|
||||||
|
@ -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 {
|
||||||
|
@ -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))
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
|
@ -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 }
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -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,
|
||||||
|
@ -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",
|
||||||
|
@ -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) {
|
|
||||||
|
if let htmlString = buildHtmlString() {
|
||||||
|
Text(AttributedString(htmlString))
|
||||||
|
.fixedSize(horizontal: false, vertical: true)
|
||||||
|
} else {
|
||||||
|
if let attributedString = try? AttributedString(markdown: timelineItem.body) {
|
||||||
Text(attributedString)
|
Text(attributedString)
|
||||||
.fixedSize(horizontal: false, vertical: true)
|
.fixedSize(horizontal: false, vertical: true)
|
||||||
} else {
|
} else {
|
||||||
Text(timelineItem.text)
|
Text(timelineItem.body)
|
||||||
.fixedSize(horizontal: false, vertical: true)
|
.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")
|
||||||
|
Loading…
x
Reference in New Issue
Block a user