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,39 @@
//
// EmoteRoomMessage.swift
// ElementX
//
// Created by Stefan Ceriu on 16/03/2022.
// Copyright © 2022 Element. All rights reserved.
//
import Foundation
import UIKit
import MatrixRustSDK
struct EmoteRoomMessage: RoomMessageProtocol {
private let message: MatrixRustSDK.EmoteMessage
init(message: MatrixRustSDK.EmoteMessage) {
self.message = message
}
var id: String {
message.baseMessage().id()
}
var body: String {
message.baseMessage().body()
}
var htmlBody: String? {
message.htmlBody()
}
var sender: String {
message.baseMessage().sender()
}
var originServerTs: Date {
Date(timeIntervalSince1970: TimeInterval(message.baseMessage().originServerTs()))
}
}
@@ -0,0 +1,39 @@
//
// NoticeRoomMessage.swift
// ElementX
//
// Created by Stefan Ceriu on 16/03/2022.
// Copyright © 2022 Element. All rights reserved.
//
import Foundation
import UIKit
import MatrixRustSDK
struct NoticeRoomMessage: RoomMessageProtocol {
private let message: MatrixRustSDK.NoticeMessage
init(message: MatrixRustSDK.NoticeMessage) {
self.message = message
}
var id: String {
message.baseMessage().id()
}
var body: String {
message.baseMessage().body()
}
var htmlBody: String? {
message.htmlBody()
}
var sender: String {
message.baseMessage().sender()
}
var originServerTs: Date {
Date(timeIntervalSince1970: TimeInterval(message.baseMessage().originServerTs()))
}
}
@@ -11,10 +11,14 @@ import MatrixRustSDK
struct RoomMessageFactory {
func buildRoomMessageFrom(_ message: AnyMessage) -> RoomMessageProtocol {
if let textMessage = message.text() {
if let textMessage = message.textMessage() {
return TextRoomMessage(message: textMessage)
} else if let imageMessage = message.image() {
} else if let imageMessage = message.imageMessage() {
return ImageRoomMessage(message: imageMessage)
} else if let noticeMessage = message.noticeMessage() {
return NoticeRoomMessage(message: noticeMessage)
} else if let emoteMessage = message.emoteMessage() {
return EmoteRoomMessage(message: emoteMessage)
} else {
fatalError("One of these must exist")
}
@@ -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()
}
}