Added support for emotes and notices.

This commit is contained in:
Stefan Ceriu
2022-03-29 12:13:41 +03:00
parent 1c780507fb
commit 2d786b49fe
11 changed files with 347 additions and 30 deletions

View File

@@ -20,6 +20,12 @@
1850257127B6A135002E6B18 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 1850256927B6A135002E6B18 /* LaunchScreen.storyboard */; };
1863A3FC27BA5A9100B52E4D /* KeychainAccess in Frameworks */ = {isa = PBXBuildFile; productRef = 1863A3FB27BA5A9100B52E4D /* KeychainAccess */; };
1863A40627BA6DFC00B52E4D /* SwiftyBeaver in Frameworks */ = {isa = PBXBuildFile; productRef = 1863A40527BA6DFC00B52E4D /* SwiftyBeaver */; };
18920C0E27F233FF00A717B5 /* NoticeRoomMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18920C0C27F233FF00A717B5 /* NoticeRoomMessage.swift */; };
18920C0F27F233FF00A717B5 /* EmoteRoomMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18920C0D27F233FF00A717B5 /* EmoteRoomMessage.swift */; };
18920C1227F2347600A717B5 /* NoticeRoomTimelineItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18920C1027F2347600A717B5 /* NoticeRoomTimelineItem.swift */; };
18920C1327F2347600A717B5 /* EmoteRoomTimelineItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18920C1127F2347600A717B5 /* EmoteRoomTimelineItem.swift */; };
18920C1627F2E3F400A717B5 /* NoticeRoomTimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18920C1427F2E3F400A717B5 /* NoticeRoomTimelineView.swift */; };
18920C1727F2E3F400A717B5 /* EmoteRoomTimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18920C1527F2E3F400A717B5 /* EmoteRoomTimelineView.swift */; };
18ADC7D527E4B20300A8C953 /* PlaceholderAvatarImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18ADC7D427E4B20300A8C953 /* PlaceholderAvatarImage.swift */; };
18ADC7D827E4B63C00A8C953 /* MatrixRustSDK in Frameworks */ = {isa = PBXBuildFile; productRef = 18ADC7D727E4B63C00A8C953 /* MatrixRustSDK */; };
18ADC7FA27EB02D900A8C953 /* AttributedStringBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18ADC7F927EB02D900A8C953 /* AttributedStringBuilder.swift */; };
@@ -145,6 +151,13 @@
1850256727B6A135002E6B18 /* ElementX.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = ElementX.entitlements; sourceTree = "<group>"; };
1850256827B6A135002E6B18 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
1850256A27B6A135002E6B18 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
18920C0B27F2335000A717B5 /* matrix-rust-components-swift */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = "matrix-rust-components-swift"; path = "../matrix-rust-components-swift"; sourceTree = "<group>"; };
18920C0C27F233FF00A717B5 /* NoticeRoomMessage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NoticeRoomMessage.swift; sourceTree = "<group>"; };
18920C0D27F233FF00A717B5 /* EmoteRoomMessage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EmoteRoomMessage.swift; sourceTree = "<group>"; };
18920C1027F2347600A717B5 /* NoticeRoomTimelineItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NoticeRoomTimelineItem.swift; sourceTree = "<group>"; };
18920C1127F2347600A717B5 /* EmoteRoomTimelineItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EmoteRoomTimelineItem.swift; sourceTree = "<group>"; };
18920C1427F2E3F400A717B5 /* NoticeRoomTimelineView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NoticeRoomTimelineView.swift; sourceTree = "<group>"; };
18920C1527F2E3F400A717B5 /* EmoteRoomTimelineView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EmoteRoomTimelineView.swift; sourceTree = "<group>"; };
18ADC7D427E4B20300A8C953 /* PlaceholderAvatarImage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PlaceholderAvatarImage.swift; sourceTree = "<group>"; };
18ADC7F927EB02D900A8C953 /* AttributedStringBuilder.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AttributedStringBuilder.swift; sourceTree = "<group>"; };
18ADC80427EB1ED100A8C953 /* UIFont+AttributedStringBuilder.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIFont+AttributedStringBuilder.m"; sourceTree = "<group>"; };
@@ -274,6 +287,7 @@
1850251B27B6918C002E6B18 = {
isa = PBXGroup;
children = (
18920C0B27F2335000A717B5 /* matrix-rust-components-swift */,
1850252627B6918C002E6B18 /* ElementX */,
1850253D27B6918D002E6B18 /* ElementXTests */,
1850254727B6918D002E6B18 /* ElementXUITests */,
@@ -378,10 +392,12 @@
18C5744927E1D84000D70937 /* Messages */ = {
isa = PBXGroup;
children = (
18920C0D27F233FF00A717B5 /* EmoteRoomMessage.swift */,
18C5745127E1D88600D70937 /* ImageRoomMessage.swift */,
18920C0C27F233FF00A717B5 /* NoticeRoomMessage.swift */,
18C5745527E1DCA800D70937 /* RoomMessageFactory.swift */,
18C5744F27E1D87800D70937 /* RoomMessageProtocol.swift */,
18C5745327E1D88E00D70937 /* TextRoomMessage.swift */,
18C5745127E1D88600D70937 /* ImageRoomMessage.swift */,
);
path = Messages;
sourceTree = "<group>";
@@ -414,8 +430,10 @@
18DF7C3927E4670600291672 /* Items */ = {
isa = PBXGroup;
children = (
18DF7C3A27E4670600291672 /* SeparatorRoomTimelineItem.swift */,
18920C1127F2347600A717B5 /* EmoteRoomTimelineItem.swift */,
18DF7C3B27E4670600291672 /* ImageRoomTimelineItem.swift */,
18920C1027F2347600A717B5 /* NoticeRoomTimelineItem.swift */,
18DF7C3A27E4670600291672 /* SeparatorRoomTimelineItem.swift */,
18DF7C3C27E4670600291672 /* TextRoomTimelineItem.swift */,
);
path = Items;
@@ -424,12 +442,14 @@
18DF7C3D27E4670600291672 /* Views */ = {
isa = PBXGroup;
children = (
18ADC7D427E4B20300A8C953 /* PlaceholderAvatarImage.swift */,
18920C1527F2E3F400A717B5 /* EmoteRoomTimelineView.swift */,
18DF7C4F27E46A7A00291672 /* EventBasedTimelineView.swift */,
18DF7C4027E4670600291672 /* TextRoomTimelineView.swift */,
18DF7C3E27E4670600291672 /* ImageRoomTimelineView.swift */,
18DF7C3F27E4670600291672 /* SeparatorRoomTimelineView.swift */,
18DDB72427EC784E000F1ABF /* FormattedBodyText.swift */,
18DF7C3E27E4670600291672 /* ImageRoomTimelineView.swift */,
18920C1427F2E3F400A717B5 /* NoticeRoomTimelineView.swift */,
18ADC7D427E4B20300A8C953 /* PlaceholderAvatarImage.swift */,
18DF7C3F27E4670600291672 /* SeparatorRoomTimelineView.swift */,
18DF7C4027E4670600291672 /* TextRoomTimelineView.swift */,
);
path = Views;
sourceTree = "<group>";
@@ -960,10 +980,14 @@
18C5744D27E1D84000D70937 /* RoomProxy.swift in Sources */,
18F2BB0027D25B4000DD1988 /* HomeScreen.swift in Sources */,
18F2BB2827D2647A00DD1988 /* MockRoomTimelineController.swift in Sources */,
18920C0F27F233FF00A717B5 /* EmoteRoomMessage.swift in Sources */,
18DF7C3127E3608100291672 /* MediaProviderProtocol.swift in Sources */,
18F2BB0127D25B4000DD1988 /* HomeScreenViewModel.swift in Sources */,
18F2BAF027D25B4000DD1988 /* ActivityDismissal.swift in Sources */,
18F2BADD27D25B4000DD1988 /* KeychainController.swift in Sources */,
18920C1227F2347600A717B5 /* NoticeRoomTimelineItem.swift in Sources */,
18920C1727F2E3F400A717B5 /* EmoteRoomTimelineView.swift in Sources */,
18920C0E27F233FF00A717B5 /* NoticeRoomMessage.swift in Sources */,
18F2BAFB27D25B4000DD1988 /* HomeScreenCoordinator.swift in Sources */,
18F2BB0C27D25B4000DD1988 /* RoomScreenCoordinator.swift in Sources */,
18DF7C5027E46A7A00291672 /* EventBasedTimelineView.swift in Sources */,
@@ -980,9 +1004,11 @@
18C5744C27E1D84000D70937 /* RoomProxyProtocol.swift in Sources */,
18C5744E27E1D84000D70937 /* MockRoomProxy.swift in Sources */,
18F2BADC27D25B4000DD1988 /* UserSession.swift in Sources */,
18920C1627F2E3F400A717B5 /* NoticeRoomTimelineView.swift in Sources */,
18F2BAEF27D25B4000DD1988 /* ActivityRequest.swift in Sources */,
18F2BAEE27D25B4000DD1988 /* Activity.swift in Sources */,
18F2BAEC27D25B4000DD1988 /* ToastActivityPresenter.swift in Sources */,
18920C1327F2347600A717B5 /* EmoteRoomTimelineItem.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};

View File

@@ -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()))
}
}

View File

@@ -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()))
}
}

View File

@@ -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")
}

View File

@@ -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?
}

View File

@@ -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?
}

View File

@@ -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)
}
}

View File

@@ -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")
}

View File

@@ -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)
}
}
}

View File

@@ -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()
}
}

View File

@@ -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()
}
}