mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-03 16:10:20 +00:00
Made room state event collapsing configurable, expose it in the developer menu. Allow the UserSetting property wrappers to only store values in memory (+11 squashed commits)
Squashed commits: [42e45fc] Even more tweaks following code review [5dcd5be] Add swift-algoritms and switch bubbling detection to its `chunked` method [4ac70ed] Move the groupBy implementation to Collection instead of Array [6aeffc3] Tweaks following code review [0ca5ac2] Bubbles working again, grouping computed closer to the UI level [3a66030] Refactor how timeline items are built in the RoomTimelineController [57d51e9] Remove grouping from timeline items [8608950] Remove the RoomTimelineViewFactory, update the GroupRoomTimelineView [9e52e61] Add array grouping extension, unit tests and switched the timeline controller to it [7d213a1] First attempt [90bb1d7] Remove now unused `RoomTimelineController` `updatedTimelineItem` calback
This commit is contained in:
committed by
Stefan Ceriu
parent
64902190d4
commit
a3c7c47a69
@@ -23,7 +23,6 @@ enum RoomTimelineItemFixtures {
|
||||
TextRoomTimelineItem(id: UUID().uuidString,
|
||||
body: "That looks so good!",
|
||||
timestamp: "10:10 AM",
|
||||
groupState: .single,
|
||||
isOutgoing: false,
|
||||
isEditable: false,
|
||||
sender: .init(id: "", displayName: "Jacob"),
|
||||
@@ -31,7 +30,6 @@ enum RoomTimelineItemFixtures {
|
||||
TextRoomTimelineItem(id: UUID().uuidString,
|
||||
body: "Let’s get lunch soon! New salad place opened up 🥗. When are y’all free? 🤗",
|
||||
timestamp: "10:11 AM",
|
||||
groupState: .beginning,
|
||||
isOutgoing: false,
|
||||
isEditable: false,
|
||||
sender: .init(id: "", displayName: "Helena"),
|
||||
@@ -41,7 +39,6 @@ enum RoomTimelineItemFixtures {
|
||||
TextRoomTimelineItem(id: UUID().uuidString,
|
||||
body: "I can be around on Wednesday. How about some 🌮 instead? Like https://www.tortilla.co.uk/",
|
||||
timestamp: "10:11 AM",
|
||||
groupState: .end,
|
||||
isOutgoing: false,
|
||||
isEditable: false,
|
||||
sender: .init(id: "", displayName: "Helena"),
|
||||
@@ -53,21 +50,18 @@ enum RoomTimelineItemFixtures {
|
||||
TextRoomTimelineItem(id: UUID().uuidString,
|
||||
body: "Wow, cool. Ok, lets go the usual place tomorrow?! Is that too soon? Here’s the menu, let me know what you want it’s on me!",
|
||||
timestamp: "5 PM",
|
||||
groupState: .single,
|
||||
isOutgoing: false,
|
||||
isEditable: false,
|
||||
sender: .init(id: "", displayName: "Helena")),
|
||||
TextRoomTimelineItem(id: UUID().uuidString,
|
||||
body: "And John's speech was amazing!",
|
||||
timestamp: "5 PM",
|
||||
groupState: .beginning,
|
||||
isOutgoing: true,
|
||||
isEditable: true,
|
||||
sender: .init(id: "", displayName: "Bob")),
|
||||
TextRoomTimelineItem(id: UUID().uuidString,
|
||||
body: "New home office set up!",
|
||||
timestamp: "5 PM",
|
||||
groupState: .end,
|
||||
isOutgoing: true,
|
||||
isEditable: true,
|
||||
sender: .init(id: "", displayName: "Bob"),
|
||||
@@ -79,7 +73,6 @@ enum RoomTimelineItemFixtures {
|
||||
body: "",
|
||||
formattedBody: AttributedStringBuilder().fromHTML("Hol' up <blockquote>New home office set up!</blockquote>That's amazing! Congrats 🥳"),
|
||||
timestamp: "5 PM",
|
||||
groupState: .single,
|
||||
isOutgoing: false,
|
||||
isEditable: false,
|
||||
sender: .init(id: "", displayName: "Helena"))
|
||||
@@ -88,24 +81,20 @@ enum RoomTimelineItemFixtures {
|
||||
/// A small chunk of events, containing 2 text items.
|
||||
static var smallChunk: [RoomTimelineItemProtocol] {
|
||||
[TextRoomTimelineItem(text: "Hey there 👋",
|
||||
groupState: .beginning,
|
||||
senderDisplayName: "Alice"),
|
||||
TextRoomTimelineItem(text: "How are you?",
|
||||
groupState: .end,
|
||||
senderDisplayName: "Alice")]
|
||||
}
|
||||
|
||||
/// A chunk of events that contains a single text item.
|
||||
static var singleMessageChunk: [RoomTimelineItemProtocol] {
|
||||
[TextRoomTimelineItem(text: "Tap tap tap 🎙️. Is this thing on?",
|
||||
groupState: .single,
|
||||
senderDisplayName: "Helena")]
|
||||
}
|
||||
|
||||
/// A single text item.
|
||||
static var incomingMessage: RoomTimelineItemProtocol {
|
||||
TextRoomTimelineItem(text: "Hello, World!",
|
||||
groupState: .single,
|
||||
senderDisplayName: "Bob")
|
||||
}
|
||||
|
||||
@@ -196,11 +185,10 @@ enum RoomTimelineItemFixtures {
|
||||
}
|
||||
|
||||
private extension TextRoomTimelineItem {
|
||||
init(text: String, groupState: TimelineItemGroupState = .single, senderDisplayName: String) {
|
||||
init(text: String, senderDisplayName: String) {
|
||||
self.init(id: UUID().uuidString,
|
||||
body: text,
|
||||
timestamp: "10:47 am",
|
||||
groupState: groupState,
|
||||
isOutgoing: senderDisplayName == "Alice",
|
||||
isEditable: false,
|
||||
sender: .init(id: "", displayName: senderDisplayName))
|
||||
|
||||
+69
-86
@@ -230,61 +230,30 @@ class RoomTimelineController: RoomTimelineControllerProtocol {
|
||||
updateTimelineItems()
|
||||
}
|
||||
|
||||
// swiftlint:disable:next cyclomatic_complexity
|
||||
private func updateTimelineItems() {
|
||||
var newTimelineItems = [RoomTimelineItemProtocol]()
|
||||
var canBackPaginate = true
|
||||
var isBackPaginating = false
|
||||
|
||||
var createdIdentifiers = [String: Bool]()
|
||||
|
||||
for (index, itemProxy) in timelineProvider.itemsPublisher.value.enumerated() {
|
||||
if Task.isCancelled {
|
||||
return
|
||||
}
|
||||
|
||||
let previousItemProxy = timelineProvider.itemsPublisher.value[safe: index - 1]
|
||||
let nextItemProxy = timelineProvider.itemsPublisher.value[safe: index + 1]
|
||||
|
||||
let groupState = computeGroupState(for: itemProxy, previousItemProxy: previousItemProxy, nextItemProxy: nextItemProxy)
|
||||
|
||||
switch itemProxy {
|
||||
case .event(let eventItemProxy):
|
||||
if let timelineItem = timelineItemFactory.buildTimelineItemFor(eventItemProxy: eventItemProxy, groupState: groupState) {
|
||||
#warning("This works around duplicated items coming out of the SDK, remove once fixed")
|
||||
if createdIdentifiers[timelineItem.id] == nil {
|
||||
newTimelineItems.append(timelineItem)
|
||||
createdIdentifiers[timelineItem.id] = true
|
||||
} else {
|
||||
MXLog.error("Found duplicated timeline item, ignoring")
|
||||
}
|
||||
}
|
||||
case .virtual(let virtualItem):
|
||||
switch virtualItem {
|
||||
case .dayDivider(let timestamp):
|
||||
// These components will be replaced by a timestamp in upcoming releases
|
||||
let date = Date(timeIntervalSince1970: TimeInterval(timestamp / 1000))
|
||||
let dateString = date.formatted(date: .complete, time: .omitted)
|
||||
newTimelineItems.append(SeparatorRoomTimelineItem(text: dateString))
|
||||
case .readMarker:
|
||||
// Don't show the read marker if it's the last item in the timeline
|
||||
if index != timelineProvider.itemsPublisher.value.indices.last {
|
||||
newTimelineItems.append(ReadMarkerRoomTimelineItem())
|
||||
}
|
||||
case .loadingIndicator:
|
||||
newTimelineItems.append(PaginationIndicatorRoomTimelineItem())
|
||||
isBackPaginating = true
|
||||
case .timelineStart:
|
||||
newTimelineItems.append(TimelineStartRoomTimelineItem(name: roomProxy.displayName ?? roomProxy.name))
|
||||
canBackPaginate = false
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if Task.isCancelled {
|
||||
return
|
||||
let collapsibleChunks = timelineProvider.itemsPublisher.value.groupBy { isItemCollapsible($0) }
|
||||
|
||||
for (index, itemGroup) in collapsibleChunks.enumerated() {
|
||||
if itemGroup.count == 1, let itemProxy = itemGroup.first {
|
||||
let isLastItem = index == collapsibleChunks.indices.last
|
||||
if let item = buildTimelineItemFor(itemProxy: itemProxy, isLastItem: isLastItem, createdIdentifiers: &createdIdentifiers, isBackPaginating: &isBackPaginating, canBackPaginate: &canBackPaginate) {
|
||||
newTimelineItems.append(item)
|
||||
}
|
||||
} else {
|
||||
let items = itemGroup.compactMap { itemProxy in
|
||||
buildTimelineItemFor(itemProxy: itemProxy, isLastItem: false, createdIdentifiers: &createdIdentifiers, isBackPaginating: &isBackPaginating, canBackPaginate: &canBackPaginate)
|
||||
}
|
||||
|
||||
if !items.isEmpty {
|
||||
newTimelineItems.append(CollapsibleTimelineItem(items: items))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
timelineItems = newTimelineItems
|
||||
@@ -294,49 +263,63 @@ class RoomTimelineController: RoomTimelineControllerProtocol {
|
||||
callbacks.send(.isBackPaginating(isBackPaginating))
|
||||
}
|
||||
|
||||
private func computeGroupState(for itemProxy: TimelineItemProxy,
|
||||
previousItemProxy: TimelineItemProxy?,
|
||||
nextItemProxy: TimelineItemProxy?) -> TimelineItemGroupState {
|
||||
guard let previousItem = previousItemProxy else {
|
||||
// no previous item, check next item
|
||||
guard let nextItem = nextItemProxy else {
|
||||
// no next item neither, this is single
|
||||
return .single
|
||||
private func buildTimelineItemFor(itemProxy: TimelineItemProxy,
|
||||
isLastItem: Bool,
|
||||
createdIdentifiers: inout [String: Bool],
|
||||
isBackPaginating: inout Bool,
|
||||
canBackPaginate: inout Bool) -> RoomTimelineItemProtocol? {
|
||||
switch itemProxy {
|
||||
case .event(let eventItemProxy):
|
||||
if let timelineItem = timelineItemFactory.buildTimelineItemFor(eventItemProxy: eventItemProxy) {
|
||||
#warning("This works around duplicated items coming out of the SDK, remove once fixed")
|
||||
if createdIdentifiers[timelineItem.id] == nil {
|
||||
createdIdentifiers[timelineItem.id] = true
|
||||
return timelineItem
|
||||
} else {
|
||||
MXLog.error("Found duplicated timeline item, ignoring")
|
||||
}
|
||||
}
|
||||
guard nextItem.canBeGrouped(with: itemProxy) else {
|
||||
// there is a next item but can't be grouped, this is single
|
||||
return .single
|
||||
case .virtual(let virtualItem):
|
||||
switch virtualItem {
|
||||
case .dayDivider(let timestamp):
|
||||
// These components will be replaced by a timestamp in upcoming releases
|
||||
let date = Date(timeIntervalSince1970: TimeInterval(timestamp / 1000))
|
||||
let dateString = date.formatted(date: .complete, time: .omitted)
|
||||
return SeparatorRoomTimelineItem(text: dateString)
|
||||
case .readMarker:
|
||||
// Don't show the read marker if it's the last item in the timeline
|
||||
if !isLastItem {
|
||||
return ReadMarkerRoomTimelineItem()
|
||||
}
|
||||
case .loadingIndicator:
|
||||
isBackPaginating = true
|
||||
return PaginationIndicatorRoomTimelineItem()
|
||||
case .timelineStart:
|
||||
canBackPaginate = false
|
||||
return TimelineStartRoomTimelineItem(name: roomProxy.displayName ?? roomProxy.name)
|
||||
}
|
||||
// next will be grouped with this one, this is the start
|
||||
return .beginning
|
||||
case .unknown:
|
||||
return nil
|
||||
}
|
||||
|
||||
guard let nextItem = nextItemProxy else {
|
||||
// no next item
|
||||
guard itemProxy.canBeGrouped(with: previousItem) else {
|
||||
// there is a previous item but can't be grouped, this is single
|
||||
return .single
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
private func isItemCollapsible(_ item: TimelineItemProxy) -> Bool {
|
||||
if !ServiceLocator.shared.settings.shouldCollapseRoomStateEvents {
|
||||
return false
|
||||
}
|
||||
|
||||
if case let .event(eventItem) = item {
|
||||
switch eventItem.content.kind() {
|
||||
case .profileChange, .roomMembership, .state:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
// will be grouped with previous, this is the end
|
||||
return .end
|
||||
}
|
||||
|
||||
guard itemProxy.canBeGrouped(with: previousItem) else {
|
||||
guard nextItem.canBeGrouped(with: itemProxy) else {
|
||||
// there is a next item but can't be grouped, this is single
|
||||
return .single
|
||||
}
|
||||
// next will be grouped with this one, this is the start
|
||||
return .beginning
|
||||
}
|
||||
|
||||
guard nextItem.canBeGrouped(with: itemProxy) else {
|
||||
// there is a next item but can't be grouped, this is the end
|
||||
return .end
|
||||
}
|
||||
|
||||
// next will be grouped with this one, this is the start
|
||||
return .middle
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
private func loadVideoForTimelineItem(_ timelineItem: VideoRoomTimelineItem) async {
|
||||
|
||||
-1
@@ -20,7 +20,6 @@ import UIKit
|
||||
|
||||
enum RoomTimelineControllerCallback {
|
||||
case updatedTimelineItems
|
||||
case updatedTimelineItem(_ itemId: String)
|
||||
case canBackPaginate(Bool)
|
||||
case isBackPaginating(Bool)
|
||||
}
|
||||
|
||||
@@ -32,20 +32,6 @@ enum TimelineItemProxy {
|
||||
self = .unknown(item)
|
||||
}
|
||||
}
|
||||
|
||||
func canBeGrouped(with previousItemProxy: TimelineItemProxy) -> Bool {
|
||||
guard case let .event(selfEventItemProxy) = self, case let .event(previousEventItemProxy) = previousItemProxy else {
|
||||
return false
|
||||
}
|
||||
|
||||
// State events aren't rendered as messages so shouldn't be grouped.
|
||||
if selfEventItemProxy.isRoomState || previousEventItemProxy.isRoomState {
|
||||
return false
|
||||
}
|
||||
|
||||
// can be improved by adding a date threshold
|
||||
return previousEventItemProxy.reactions.isEmpty && selfEventItemProxy.sender == previousEventItemProxy.sender
|
||||
}
|
||||
}
|
||||
|
||||
/// The delivery status for the item.
|
||||
|
||||
@@ -17,27 +17,9 @@
|
||||
import Foundation
|
||||
import UIKit
|
||||
|
||||
enum TimelineItemGroupState: Hashable {
|
||||
case single
|
||||
case beginning
|
||||
case middle
|
||||
case end
|
||||
|
||||
var shouldShowSenderDetails: Bool {
|
||||
switch self {
|
||||
case .single, .beginning:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protocol EventBasedTimelineItemProtocol: RoomTimelineItemProtocol, CustomStringConvertible {
|
||||
var body: String { get }
|
||||
var timestamp: String { get }
|
||||
var shouldShowSenderDetails: Bool { get }
|
||||
var groupState: TimelineItemGroupState { get }
|
||||
var isOutgoing: Bool { get }
|
||||
var isEditable: Bool { get }
|
||||
|
||||
@@ -47,31 +29,6 @@ protocol EventBasedTimelineItemProtocol: RoomTimelineItemProtocol, CustomStringC
|
||||
}
|
||||
|
||||
extension EventBasedTimelineItemProtocol {
|
||||
var shouldShowSenderDetails: Bool {
|
||||
groupState.shouldShowSenderDetails
|
||||
}
|
||||
|
||||
var roundedCorners: UIRectCorner {
|
||||
switch groupState {
|
||||
case .single:
|
||||
return .allCorners
|
||||
case .beginning:
|
||||
if isOutgoing {
|
||||
return [.topLeft, .topRight, .bottomLeft]
|
||||
} else {
|
||||
return [.topLeft, .topRight, .bottomRight]
|
||||
}
|
||||
case .middle:
|
||||
return isOutgoing ? [.topLeft, .bottomLeft] : [.topRight, .bottomRight]
|
||||
case .end:
|
||||
if isOutgoing {
|
||||
return [.topLeft, .bottomLeft, .bottomRight]
|
||||
} else {
|
||||
return [.topRight, .bottomLeft, .bottomRight]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var description: String {
|
||||
"\(String(describing: Self.self)): id: \(id), timestamp: \(timestamp), isOutgoing: \(isOutgoing), properties: \(properties)"
|
||||
}
|
||||
|
||||
-1
@@ -20,7 +20,6 @@ struct AudioRoomTimelineItem: EventBasedTimelineItemProtocol, Identifiable, Hash
|
||||
let id: String
|
||||
let body: String
|
||||
let timestamp: String
|
||||
let groupState: TimelineItemGroupState
|
||||
let isOutgoing: Bool
|
||||
let isEditable: Bool
|
||||
let sender: TimelineItemSender
|
||||
|
||||
-1
@@ -21,7 +21,6 @@ struct EmoteRoomTimelineItem: EventBasedTimelineItemProtocol, Identifiable, Hash
|
||||
let body: String
|
||||
var formattedBody: AttributedString?
|
||||
let timestamp: String
|
||||
let groupState: TimelineItemGroupState
|
||||
let isOutgoing: Bool
|
||||
let isEditable: Bool
|
||||
|
||||
|
||||
-1
@@ -20,7 +20,6 @@ struct FileRoomTimelineItem: EventBasedTimelineItemProtocol, Identifiable, Hasha
|
||||
let id: String
|
||||
let body: String
|
||||
let timestamp: String
|
||||
let groupState: TimelineItemGroupState
|
||||
let isOutgoing: Bool
|
||||
let isEditable: Bool
|
||||
|
||||
|
||||
-1
@@ -21,7 +21,6 @@ struct ImageRoomTimelineItem: EventBasedTimelineItemProtocol, Identifiable, Hash
|
||||
let id: String
|
||||
let body: String
|
||||
let timestamp: String
|
||||
let groupState: TimelineItemGroupState
|
||||
let isOutgoing: Bool
|
||||
let isEditable: Bool
|
||||
|
||||
|
||||
-1
@@ -21,7 +21,6 @@ struct NoticeRoomTimelineItem: EventBasedTimelineItemProtocol, Identifiable, Has
|
||||
let body: String
|
||||
var formattedBody: AttributedString?
|
||||
let timestamp: String
|
||||
let groupState: TimelineItemGroupState
|
||||
let isOutgoing: Bool
|
||||
let isEditable: Bool
|
||||
|
||||
|
||||
-1
@@ -21,7 +21,6 @@ struct TextRoomTimelineItem: EventBasedTimelineItemProtocol, Identifiable, Hasha
|
||||
let body: String
|
||||
var formattedBody: AttributedString?
|
||||
let timestamp: String
|
||||
let groupState: TimelineItemGroupState
|
||||
let isOutgoing: Bool
|
||||
let isEditable: Bool
|
||||
|
||||
|
||||
-1
@@ -20,7 +20,6 @@ struct VideoRoomTimelineItem: EventBasedTimelineItemProtocol, Identifiable, Hash
|
||||
let id: String
|
||||
let body: String
|
||||
let timestamp: String
|
||||
let groupState: TimelineItemGroupState
|
||||
let isOutgoing: Bool
|
||||
let isEditable: Bool
|
||||
|
||||
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
//
|
||||
// Copyright 2023 New Vector Ltd
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
struct CollapsibleTimelineItem: RoomTimelineItemProtocol, Identifiable, Hashable {
|
||||
let id: String
|
||||
let items: [RoomTimelineItemProtocol]
|
||||
|
||||
init(items: [RoomTimelineItemProtocol]) {
|
||||
guard let firstItem = items.first else {
|
||||
fatalError()
|
||||
}
|
||||
|
||||
self.items = items
|
||||
id = firstItem.id
|
||||
}
|
||||
|
||||
// MARK: - Equatable
|
||||
|
||||
static func == (lhs: CollapsibleTimelineItem, rhs: CollapsibleTimelineItem) -> Bool {
|
||||
lhs.id == rhs.id
|
||||
}
|
||||
|
||||
// MARK: - Hashable
|
||||
|
||||
func hash(into hasher: inout Hasher) {
|
||||
hasher.combine(id)
|
||||
}
|
||||
}
|
||||
-1
@@ -27,7 +27,6 @@ struct EncryptedRoomTimelineItem: EventBasedTimelineItemProtocol, Identifiable,
|
||||
let body: String
|
||||
let encryptionType: EncryptionType
|
||||
let timestamp: String
|
||||
let groupState: TimelineItemGroupState
|
||||
let isOutgoing: Bool
|
||||
let isEditable: Bool
|
||||
|
||||
|
||||
-1
@@ -21,7 +21,6 @@ struct RedactedRoomTimelineItem: EventBasedTimelineItemProtocol, Identifiable, H
|
||||
let id: String
|
||||
let body: String
|
||||
let timestamp: String
|
||||
let groupState: TimelineItemGroupState
|
||||
let isOutgoing: Bool
|
||||
let isEditable: Bool
|
||||
|
||||
|
||||
-1
@@ -20,7 +20,6 @@ struct StateRoomTimelineItem: EventBasedTimelineItemProtocol, Identifiable, Hash
|
||||
let id: String
|
||||
let body: String
|
||||
let timestamp: String
|
||||
let groupState: TimelineItemGroupState
|
||||
let isOutgoing: Bool
|
||||
let isEditable: Bool
|
||||
|
||||
|
||||
-1
@@ -20,7 +20,6 @@ struct StickerRoomTimelineItem: EventBasedTimelineItemProtocol, Identifiable, Ha
|
||||
let id: String
|
||||
let body: String
|
||||
let timestamp: String
|
||||
let groupState: TimelineItemGroupState
|
||||
let isOutgoing: Bool
|
||||
let isEditable: Bool
|
||||
|
||||
|
||||
-1
@@ -24,7 +24,6 @@ struct UnsupportedRoomTimelineItem: EventBasedTimelineItemProtocol, Identifiable
|
||||
let error: String
|
||||
|
||||
let timestamp: String
|
||||
let groupState: TimelineItemGroupState
|
||||
let isOutgoing: Bool
|
||||
let isEditable: Bool
|
||||
|
||||
|
||||
@@ -36,53 +36,52 @@ struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol {
|
||||
}
|
||||
|
||||
// swiftlint:disable:next cyclomatic_complexity
|
||||
func buildTimelineItemFor(eventItemProxy: EventTimelineItemProxy,
|
||||
groupState: TimelineItemGroupState) -> RoomTimelineItemProtocol? {
|
||||
func buildTimelineItemFor(eventItemProxy: EventTimelineItemProxy) -> RoomTimelineItemProtocol? {
|
||||
let isOutgoing = eventItemProxy.isOwn
|
||||
|
||||
switch eventItemProxy.content.kind() {
|
||||
case .unableToDecrypt(let encryptedMessage):
|
||||
return buildEncryptedTimelineItem(eventItemProxy, encryptedMessage, isOutgoing, groupState)
|
||||
return buildEncryptedTimelineItem(eventItemProxy, encryptedMessage, isOutgoing)
|
||||
case .redactedMessage:
|
||||
return buildRedactedTimelineItem(eventItemProxy, isOutgoing, groupState)
|
||||
return buildRedactedTimelineItem(eventItemProxy, isOutgoing)
|
||||
case .sticker(let body, let imageInfo, let urlString):
|
||||
guard let url = URL(string: urlString) else {
|
||||
MXLog.error("Invalid sticker url string: \(urlString)")
|
||||
return buildUnsupportedTimelineItem(eventItemProxy, "m.sticker", "Invalid Sticker URL", isOutgoing, groupState)
|
||||
return buildUnsupportedTimelineItem(eventItemProxy, "m.sticker", "Invalid Sticker URL", isOutgoing)
|
||||
}
|
||||
|
||||
return buildStickerTimelineItem(eventItemProxy, body, imageInfo, url, isOutgoing, groupState)
|
||||
return buildStickerTimelineItem(eventItemProxy, body, imageInfo, url, isOutgoing)
|
||||
case .failedToParseMessageLike(let eventType, let error):
|
||||
return buildUnsupportedTimelineItem(eventItemProxy, eventType, error, isOutgoing, groupState)
|
||||
return buildUnsupportedTimelineItem(eventItemProxy, eventType, error, isOutgoing)
|
||||
case .failedToParseState(let eventType, _, let error):
|
||||
return buildUnsupportedTimelineItem(eventItemProxy, eventType, error, isOutgoing, groupState)
|
||||
return buildUnsupportedTimelineItem(eventItemProxy, eventType, error, isOutgoing)
|
||||
case .message:
|
||||
guard let messageContent = eventItemProxy.content.asMessage() else { fatalError("Invalid message timeline item: \(eventItemProxy)") }
|
||||
|
||||
switch messageContent.msgtype() {
|
||||
case .text(content: let content):
|
||||
let message = MessageTimelineItem(item: eventItemProxy.item, content: content)
|
||||
return buildTextTimelineItemFromMessage(eventItemProxy, message, isOutgoing, groupState)
|
||||
return buildTextTimelineItemFromMessage(eventItemProxy, message, isOutgoing)
|
||||
case .image(content: let content):
|
||||
let message = MessageTimelineItem(item: eventItemProxy.item, content: content)
|
||||
return buildImageTimelineItemFromMessage(eventItemProxy, message, isOutgoing, groupState)
|
||||
return buildImageTimelineItemFromMessage(eventItemProxy, message, isOutgoing)
|
||||
case .video(let content):
|
||||
let message = MessageTimelineItem(item: eventItemProxy.item, content: content)
|
||||
return buildVideoTimelineItemFromMessage(eventItemProxy, message, isOutgoing, groupState)
|
||||
return buildVideoTimelineItemFromMessage(eventItemProxy, message, isOutgoing)
|
||||
case .file(let content):
|
||||
let message = MessageTimelineItem(item: eventItemProxy.item, content: content)
|
||||
return buildFileTimelineItemFromMessage(eventItemProxy, message, isOutgoing, groupState)
|
||||
case .notice(let content):
|
||||
return buildFileTimelineItemFromMessage(eventItemProxy, message, isOutgoing)
|
||||
case .notice(content: let content):
|
||||
let message = MessageTimelineItem(item: eventItemProxy.item, content: content)
|
||||
return buildNoticeTimelineItemFromMessage(eventItemProxy, message, isOutgoing, groupState)
|
||||
case .emote(let content):
|
||||
return buildNoticeTimelineItemFromMessage(eventItemProxy, message, isOutgoing)
|
||||
case .emote(content: let content):
|
||||
let message = MessageTimelineItem(item: eventItemProxy.item, content: content)
|
||||
return buildEmoteTimelineItemFromMessage(eventItemProxy, message, isOutgoing, groupState)
|
||||
return buildEmoteTimelineItemFromMessage(eventItemProxy, message, isOutgoing)
|
||||
case .audio(let content):
|
||||
let message = MessageTimelineItem(item: eventItemProxy.item, content: content)
|
||||
return buildAudioTimelineItem(eventItemProxy, message, isOutgoing, groupState)
|
||||
return buildAudioTimelineItem(eventItemProxy, message, isOutgoing)
|
||||
case .none:
|
||||
return buildFallbackTimelineItem(eventItemProxy, isOutgoing, groupState)
|
||||
return buildFallbackTimelineItem(eventItemProxy, isOutgoing)
|
||||
}
|
||||
case .state(let stateKey, let content):
|
||||
return buildStateTimelineItemFor(eventItemProxy: eventItemProxy, state: content, stateKey: stateKey, isOutgoing: isOutgoing)
|
||||
@@ -98,27 +97,23 @@ struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol {
|
||||
private func buildUnsupportedTimelineItem(_ eventItemProxy: EventTimelineItemProxy,
|
||||
_ eventType: String,
|
||||
_ error: String,
|
||||
_ isOutgoing: Bool,
|
||||
_ groupState: TimelineItemGroupState) -> RoomTimelineItemProtocol {
|
||||
_ isOutgoing: Bool) -> RoomTimelineItemProtocol {
|
||||
UnsupportedRoomTimelineItem(id: eventItemProxy.id,
|
||||
body: ElementL10n.roomTimelineItemUnsupported,
|
||||
eventType: eventType,
|
||||
error: error,
|
||||
timestamp: eventItemProxy.timestamp.formatted(date: .omitted, time: .shortened),
|
||||
groupState: groupState,
|
||||
isOutgoing: isOutgoing,
|
||||
isEditable: eventItemProxy.isEditable,
|
||||
sender: eventItemProxy.sender,
|
||||
properties: RoomTimelineItemProperties())
|
||||
}
|
||||
|
||||
// swiftlint:disable:next function_parameter_count
|
||||
private func buildStickerTimelineItem(_ eventItemProxy: EventTimelineItemProxy,
|
||||
_ body: String,
|
||||
_ imageInfo: ImageInfo,
|
||||
_ imageURL: URL,
|
||||
_ isOutgoing: Bool,
|
||||
_ groupState: TimelineItemGroupState) -> RoomTimelineItemProtocol {
|
||||
_ isOutgoing: Bool) -> RoomTimelineItemProtocol {
|
||||
var aspectRatio: CGFloat?
|
||||
let width = imageInfo.width.map(CGFloat.init)
|
||||
let height = imageInfo.height.map(CGFloat.init)
|
||||
@@ -129,7 +124,6 @@ struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol {
|
||||
return StickerRoomTimelineItem(id: eventItemProxy.id,
|
||||
body: body,
|
||||
timestamp: eventItemProxy.timestamp.formatted(date: .omitted, time: .shortened),
|
||||
groupState: groupState,
|
||||
isOutgoing: isOutgoing,
|
||||
isEditable: eventItemProxy.isEditable,
|
||||
sender: eventItemProxy.sender,
|
||||
@@ -144,8 +138,7 @@ struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol {
|
||||
|
||||
private func buildEncryptedTimelineItem(_ eventItemProxy: EventTimelineItemProxy,
|
||||
_ encryptedMessage: EncryptedMessage,
|
||||
_ isOutgoing: Bool,
|
||||
_ groupState: TimelineItemGroupState) -> RoomTimelineItemProtocol {
|
||||
_ isOutgoing: Bool) -> RoomTimelineItemProtocol {
|
||||
var encryptionType = EncryptedRoomTimelineItem.EncryptionType.unknown
|
||||
switch encryptedMessage {
|
||||
case .megolmV1AesSha2(let sessionId):
|
||||
@@ -160,7 +153,6 @@ struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol {
|
||||
body: ElementL10n.roomTimelineUnableToDecrypt,
|
||||
encryptionType: encryptionType,
|
||||
timestamp: eventItemProxy.timestamp.formatted(date: .omitted, time: .shortened),
|
||||
groupState: groupState,
|
||||
isOutgoing: isOutgoing,
|
||||
isEditable: eventItemProxy.isEditable,
|
||||
sender: eventItemProxy.sender,
|
||||
@@ -168,12 +160,10 @@ struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol {
|
||||
}
|
||||
|
||||
private func buildRedactedTimelineItem(_ eventItemProxy: EventTimelineItemProxy,
|
||||
_ isOutgoing: Bool,
|
||||
_ groupState: TimelineItemGroupState) -> RoomTimelineItemProtocol {
|
||||
_ isOutgoing: Bool) -> RoomTimelineItemProtocol {
|
||||
RedactedRoomTimelineItem(id: eventItemProxy.id,
|
||||
body: ElementL10n.eventRedacted,
|
||||
timestamp: eventItemProxy.timestamp.formatted(date: .omitted, time: .shortened),
|
||||
groupState: groupState,
|
||||
isOutgoing: isOutgoing,
|
||||
isEditable: eventItemProxy.isEditable,
|
||||
sender: eventItemProxy.sender,
|
||||
@@ -181,15 +171,13 @@ struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol {
|
||||
}
|
||||
|
||||
private func buildFallbackTimelineItem(_ eventItemProxy: EventTimelineItemProxy,
|
||||
_ isOutgoing: Bool,
|
||||
_ groupState: TimelineItemGroupState) -> RoomTimelineItemProtocol {
|
||||
_ isOutgoing: Bool) -> RoomTimelineItemProtocol {
|
||||
let formattedBody = attributedStringBuilder.fromPlain(eventItemProxy.body)
|
||||
|
||||
return TextRoomTimelineItem(id: eventItemProxy.id,
|
||||
body: eventItemProxy.body ?? "",
|
||||
formattedBody: formattedBody,
|
||||
timestamp: eventItemProxy.timestamp.formatted(date: .omitted, time: .shortened),
|
||||
groupState: groupState,
|
||||
isOutgoing: isOutgoing,
|
||||
isEditable: eventItemProxy.isEditable,
|
||||
sender: eventItemProxy.sender,
|
||||
@@ -199,15 +187,13 @@ struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol {
|
||||
|
||||
private func buildTextTimelineItemFromMessage(_ eventItemProxy: EventTimelineItemProxy,
|
||||
_ message: MessageTimelineItem<TextMessageContent>,
|
||||
_ isOutgoing: Bool,
|
||||
_ groupState: TimelineItemGroupState) -> RoomTimelineItemProtocol {
|
||||
_ isOutgoing: Bool) -> RoomTimelineItemProtocol {
|
||||
let formattedBody = (message.htmlBody != nil ? attributedStringBuilder.fromHTML(message.htmlBody) : attributedStringBuilder.fromPlain(message.body))
|
||||
|
||||
return TextRoomTimelineItem(id: message.id,
|
||||
body: message.body,
|
||||
formattedBody: formattedBody,
|
||||
timestamp: message.timestamp.formatted(date: .omitted, time: .shortened),
|
||||
groupState: groupState,
|
||||
isOutgoing: isOutgoing,
|
||||
isEditable: eventItemProxy.isEditable,
|
||||
sender: eventItemProxy.sender,
|
||||
@@ -218,8 +204,7 @@ struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol {
|
||||
|
||||
private func buildImageTimelineItemFromMessage(_ eventItemProxy: EventTimelineItemProxy,
|
||||
_ message: MessageTimelineItem<ImageMessageContent>,
|
||||
_ isOutgoing: Bool,
|
||||
_ groupState: TimelineItemGroupState) -> RoomTimelineItemProtocol {
|
||||
_ isOutgoing: Bool) -> RoomTimelineItemProtocol {
|
||||
var aspectRatio: CGFloat?
|
||||
if let width = message.width,
|
||||
let height = message.height {
|
||||
@@ -229,7 +214,6 @@ struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol {
|
||||
return ImageRoomTimelineItem(id: message.id,
|
||||
body: message.body,
|
||||
timestamp: message.timestamp.formatted(date: .omitted, time: .shortened),
|
||||
groupState: groupState,
|
||||
isOutgoing: isOutgoing,
|
||||
isEditable: eventItemProxy.isEditable,
|
||||
sender: eventItemProxy.sender,
|
||||
@@ -246,8 +230,7 @@ struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol {
|
||||
|
||||
private func buildVideoTimelineItemFromMessage(_ eventItemProxy: EventTimelineItemProxy,
|
||||
_ message: MessageTimelineItem<VideoMessageContent>,
|
||||
_ isOutgoing: Bool,
|
||||
_ groupState: TimelineItemGroupState) -> RoomTimelineItemProtocol {
|
||||
_ isOutgoing: Bool) -> RoomTimelineItemProtocol {
|
||||
var aspectRatio: CGFloat?
|
||||
if let width = message.width,
|
||||
let height = message.height {
|
||||
@@ -257,7 +240,6 @@ struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol {
|
||||
return VideoRoomTimelineItem(id: message.id,
|
||||
body: message.body,
|
||||
timestamp: message.timestamp.formatted(date: .omitted, time: .shortened),
|
||||
groupState: groupState,
|
||||
isOutgoing: isOutgoing,
|
||||
isEditable: eventItemProxy.isEditable,
|
||||
sender: eventItemProxy.sender,
|
||||
@@ -275,12 +257,10 @@ struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol {
|
||||
|
||||
private func buildAudioTimelineItem(_ eventItemProxy: EventTimelineItemProxy,
|
||||
_ message: MessageTimelineItem<AudioMessageContent>,
|
||||
_ isOutgoing: Bool,
|
||||
_ groupState: TimelineItemGroupState) -> RoomTimelineItemProtocol {
|
||||
_ isOutgoing: Bool) -> RoomTimelineItemProtocol {
|
||||
AudioRoomTimelineItem(id: eventItemProxy.id,
|
||||
body: message.body,
|
||||
timestamp: message.timestamp.formatted(date: .omitted, time: .shortened),
|
||||
groupState: groupState,
|
||||
isOutgoing: isOutgoing,
|
||||
isEditable: eventItemProxy.isEditable,
|
||||
sender: eventItemProxy.sender,
|
||||
@@ -290,12 +270,10 @@ struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol {
|
||||
|
||||
private func buildFileTimelineItemFromMessage(_ eventItemProxy: EventTimelineItemProxy,
|
||||
_ message: MessageTimelineItem<FileMessageContent>,
|
||||
_ isOutgoing: Bool,
|
||||
_ groupState: TimelineItemGroupState) -> RoomTimelineItemProtocol {
|
||||
_ isOutgoing: Bool) -> RoomTimelineItemProtocol {
|
||||
FileRoomTimelineItem(id: message.id,
|
||||
body: message.body,
|
||||
timestamp: message.timestamp.formatted(date: .omitted, time: .shortened),
|
||||
groupState: groupState,
|
||||
isOutgoing: isOutgoing,
|
||||
isEditable: eventItemProxy.isEditable,
|
||||
sender: eventItemProxy.sender,
|
||||
@@ -308,15 +286,13 @@ struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol {
|
||||
|
||||
private func buildNoticeTimelineItemFromMessage(_ eventItemProxy: EventTimelineItemProxy,
|
||||
_ message: MessageTimelineItem<NoticeMessageContent>,
|
||||
_ isOutgoing: Bool,
|
||||
_ groupState: TimelineItemGroupState) -> RoomTimelineItemProtocol {
|
||||
_ isOutgoing: Bool) -> RoomTimelineItemProtocol {
|
||||
let formattedBody = (message.htmlBody != nil ? attributedStringBuilder.fromHTML(message.htmlBody) : attributedStringBuilder.fromPlain(message.body))
|
||||
|
||||
return NoticeRoomTimelineItem(id: message.id,
|
||||
body: message.body,
|
||||
formattedBody: formattedBody,
|
||||
timestamp: message.timestamp.formatted(date: .omitted, time: .shortened),
|
||||
groupState: groupState,
|
||||
isOutgoing: isOutgoing,
|
||||
isEditable: eventItemProxy.isEditable,
|
||||
sender: eventItemProxy.sender,
|
||||
@@ -327,8 +303,7 @@ struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol {
|
||||
|
||||
private func buildEmoteTimelineItemFromMessage(_ eventItemProxy: EventTimelineItemProxy,
|
||||
_ message: MessageTimelineItem<EmoteMessageContent>,
|
||||
_ isOutgoing: Bool,
|
||||
_ groupState: TimelineItemGroupState) -> RoomTimelineItemProtocol {
|
||||
_ isOutgoing: Bool) -> RoomTimelineItemProtocol {
|
||||
let name = eventItemProxy.sender.displayName ?? eventItemProxy.sender.id
|
||||
|
||||
var formattedBody: AttributedString?
|
||||
@@ -342,7 +317,6 @@ struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol {
|
||||
body: message.body,
|
||||
formattedBody: formattedBody,
|
||||
timestamp: message.timestamp.formatted(date: .omitted, time: .shortened),
|
||||
groupState: groupState,
|
||||
isOutgoing: isOutgoing,
|
||||
isEditable: eventItemProxy.isEditable,
|
||||
sender: eventItemProxy.sender,
|
||||
@@ -396,7 +370,6 @@ struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol {
|
||||
StateRoomTimelineItem(id: eventItemProxy.id,
|
||||
body: text,
|
||||
timestamp: eventItemProxy.timestamp.formatted(date: .omitted, time: .shortened),
|
||||
groupState: .single,
|
||||
isOutgoing: isOutgoing,
|
||||
isEditable: false,
|
||||
sender: eventItemProxy.sender)
|
||||
|
||||
+1
-2
@@ -18,6 +18,5 @@ import Foundation
|
||||
|
||||
@MainActor
|
||||
protocol RoomTimelineItemFactoryProtocol {
|
||||
func buildTimelineItemFor(eventItemProxy: EventTimelineItemProxy,
|
||||
groupState: TimelineItemGroupState) -> RoomTimelineItemProtocol?
|
||||
func buildTimelineItemFor(eventItemProxy: EventTimelineItemProxy) -> RoomTimelineItemProtocol?
|
||||
}
|
||||
|
||||
@@ -1,59 +0,0 @@
|
||||
//
|
||||
// Copyright 2022 New Vector Ltd
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
struct RoomTimelineViewFactory: RoomTimelineViewFactoryProtocol {
|
||||
// swiftlint:disable:next cyclomatic_complexity
|
||||
func buildTimelineViewFor(timelineItem: RoomTimelineItemProtocol) -> RoomTimelineViewProvider {
|
||||
switch timelineItem {
|
||||
case let item as TextRoomTimelineItem:
|
||||
return .text(item)
|
||||
case let item as ImageRoomTimelineItem:
|
||||
return .image(item)
|
||||
case let item as VideoRoomTimelineItem:
|
||||
return .video(item)
|
||||
case let item as AudioRoomTimelineItem:
|
||||
return .audio(item)
|
||||
case let item as FileRoomTimelineItem:
|
||||
return .file(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)
|
||||
case let item as RedactedRoomTimelineItem:
|
||||
return .redacted(item)
|
||||
case let item as EncryptedRoomTimelineItem:
|
||||
return .encrypted(item)
|
||||
case let item as ReadMarkerRoomTimelineItem:
|
||||
return .readMarker(item)
|
||||
case let item as PaginationIndicatorRoomTimelineItem:
|
||||
return .paginationIndicator(item)
|
||||
case let item as StickerRoomTimelineItem:
|
||||
return .sticker(item)
|
||||
case let item as UnsupportedRoomTimelineItem:
|
||||
return .unsupported(item)
|
||||
case let item as TimelineStartRoomTimelineItem:
|
||||
return .timelineStart(item)
|
||||
case let item as StateRoomTimelineItem:
|
||||
return .state(item)
|
||||
default:
|
||||
fatalError("Unknown timeline item")
|
||||
}
|
||||
}
|
||||
}
|
||||
-22
@@ -1,22 +0,0 @@
|
||||
//
|
||||
// Copyright 2022 New Vector Ltd
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
@MainActor
|
||||
protocol RoomTimelineViewFactoryProtocol {
|
||||
func buildTimelineViewFor(timelineItem: RoomTimelineItemProtocol) -> RoomTimelineViewProvider
|
||||
}
|
||||
@@ -18,56 +18,85 @@ import Foundation
|
||||
import SwiftUI
|
||||
|
||||
enum RoomTimelineViewProvider: Identifiable, Hashable {
|
||||
case text(TextRoomTimelineItem)
|
||||
case separator(SeparatorRoomTimelineItem)
|
||||
case image(ImageRoomTimelineItem)
|
||||
case video(VideoRoomTimelineItem)
|
||||
case audio(AudioRoomTimelineItem)
|
||||
case file(FileRoomTimelineItem)
|
||||
case emote(EmoteRoomTimelineItem)
|
||||
case notice(NoticeRoomTimelineItem)
|
||||
case redacted(RedactedRoomTimelineItem)
|
||||
case encrypted(EncryptedRoomTimelineItem)
|
||||
case readMarker(ReadMarkerRoomTimelineItem)
|
||||
case paginationIndicator(PaginationIndicatorRoomTimelineItem)
|
||||
case sticker(StickerRoomTimelineItem)
|
||||
case unsupported(UnsupportedRoomTimelineItem)
|
||||
case timelineStart(TimelineStartRoomTimelineItem)
|
||||
case state(StateRoomTimelineItem)
|
||||
case text(TextRoomTimelineItem, TimelineGroupStyle)
|
||||
case separator(SeparatorRoomTimelineItem, TimelineGroupStyle)
|
||||
case image(ImageRoomTimelineItem, TimelineGroupStyle)
|
||||
case video(VideoRoomTimelineItem, TimelineGroupStyle)
|
||||
case audio(AudioRoomTimelineItem, TimelineGroupStyle)
|
||||
case file(FileRoomTimelineItem, TimelineGroupStyle)
|
||||
case emote(EmoteRoomTimelineItem, TimelineGroupStyle)
|
||||
case notice(NoticeRoomTimelineItem, TimelineGroupStyle)
|
||||
case redacted(RedactedRoomTimelineItem, TimelineGroupStyle)
|
||||
case encrypted(EncryptedRoomTimelineItem, TimelineGroupStyle)
|
||||
case readMarker(ReadMarkerRoomTimelineItem, TimelineGroupStyle)
|
||||
case paginationIndicator(PaginationIndicatorRoomTimelineItem, TimelineGroupStyle)
|
||||
case sticker(StickerRoomTimelineItem, TimelineGroupStyle)
|
||||
case unsupported(UnsupportedRoomTimelineItem, TimelineGroupStyle)
|
||||
case timelineStart(TimelineStartRoomTimelineItem, TimelineGroupStyle)
|
||||
case state(StateRoomTimelineItem, TimelineGroupStyle)
|
||||
case group(CollapsibleTimelineItem, TimelineGroupStyle)
|
||||
|
||||
// swiftlint:disable:next cyclomatic_complexity
|
||||
init(timelineItem: RoomTimelineItemProtocol, grouping: TimelineGroupStyle) {
|
||||
switch timelineItem {
|
||||
case let item as TextRoomTimelineItem:
|
||||
self = .text(item, grouping)
|
||||
case let item as ImageRoomTimelineItem:
|
||||
self = .image(item, grouping)
|
||||
case let item as VideoRoomTimelineItem:
|
||||
self = .video(item, grouping)
|
||||
case let item as AudioRoomTimelineItem:
|
||||
self = .audio(item, grouping)
|
||||
case let item as FileRoomTimelineItem:
|
||||
self = .file(item, grouping)
|
||||
case let item as SeparatorRoomTimelineItem:
|
||||
self = .separator(item, grouping)
|
||||
case let item as NoticeRoomTimelineItem:
|
||||
self = .notice(item, grouping)
|
||||
case let item as EmoteRoomTimelineItem:
|
||||
self = .emote(item, grouping)
|
||||
case let item as RedactedRoomTimelineItem:
|
||||
self = .redacted(item, grouping)
|
||||
case let item as EncryptedRoomTimelineItem:
|
||||
self = .encrypted(item, grouping)
|
||||
case let item as ReadMarkerRoomTimelineItem:
|
||||
self = .readMarker(item, grouping)
|
||||
case let item as PaginationIndicatorRoomTimelineItem:
|
||||
self = .paginationIndicator(item, grouping)
|
||||
case let item as StickerRoomTimelineItem:
|
||||
self = .sticker(item, grouping)
|
||||
case let item as UnsupportedRoomTimelineItem:
|
||||
self = .unsupported(item, grouping)
|
||||
case let item as TimelineStartRoomTimelineItem:
|
||||
self = .timelineStart(item, grouping)
|
||||
case let item as StateRoomTimelineItem:
|
||||
self = .state(item, grouping)
|
||||
case let item as CollapsibleTimelineItem:
|
||||
self = .group(item, grouping)
|
||||
default:
|
||||
fatalError("Unknown timeline item")
|
||||
}
|
||||
}
|
||||
|
||||
var id: String {
|
||||
switch self {
|
||||
case .text(let item):
|
||||
return item.id
|
||||
case .separator(let item):
|
||||
return item.id
|
||||
case .image(let item):
|
||||
return item.id
|
||||
case .video(let item):
|
||||
return item.id
|
||||
case .audio(let item):
|
||||
return item.id
|
||||
case .file(let item):
|
||||
return item.id
|
||||
case .emote(let item):
|
||||
return item.id
|
||||
case .notice(let item):
|
||||
return item.id
|
||||
case .redacted(let item):
|
||||
return item.id
|
||||
case .encrypted(let item):
|
||||
return item.id
|
||||
case .readMarker(let item):
|
||||
return item.id
|
||||
case .paginationIndicator(let item):
|
||||
return item.id
|
||||
case .sticker(let item):
|
||||
return item.id
|
||||
case .unsupported(let item):
|
||||
return item.id
|
||||
case .timelineStart(let item):
|
||||
return item.id
|
||||
case .state(let item):
|
||||
case .text(let item as RoomTimelineItemProtocol, _),
|
||||
.separator(let item as RoomTimelineItemProtocol, _),
|
||||
.image(let item as RoomTimelineItemProtocol, _),
|
||||
.video(let item as RoomTimelineItemProtocol, _),
|
||||
.audio(let item as RoomTimelineItemProtocol, _),
|
||||
.file(let item as RoomTimelineItemProtocol, _),
|
||||
.emote(let item as RoomTimelineItemProtocol, _),
|
||||
.notice(let item as RoomTimelineItemProtocol, _),
|
||||
.redacted(let item as RoomTimelineItemProtocol, _),
|
||||
.encrypted(let item as RoomTimelineItemProtocol, _),
|
||||
.readMarker(let item as RoomTimelineItemProtocol, _),
|
||||
.paginationIndicator(let item as RoomTimelineItemProtocol, _),
|
||||
.sticker(let item as RoomTimelineItemProtocol, _),
|
||||
.unsupported(let item as RoomTimelineItemProtocol, _),
|
||||
.timelineStart(let item as RoomTimelineItemProtocol, _),
|
||||
.state(let item as RoomTimelineItemProtocol, _),
|
||||
.group(let item as RoomTimelineItemProtocol, _):
|
||||
return item.id
|
||||
}
|
||||
}
|
||||
@@ -81,45 +110,77 @@ enum RoomTimelineViewProvider: Identifiable, Hashable {
|
||||
return false
|
||||
case .timelineStart, .separator, .readMarker, .paginationIndicator: // Virtual items are never reactable
|
||||
return false
|
||||
case .group:
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension RoomTimelineViewProvider: View {
|
||||
@ViewBuilder var body: some View {
|
||||
var body: some View {
|
||||
timelineView
|
||||
.environment(\.timelineGroupStyle, timelineStyleGrouping)
|
||||
}
|
||||
|
||||
@ViewBuilder private var timelineView: some View {
|
||||
switch self {
|
||||
case .text(let item):
|
||||
case .text(let item, _):
|
||||
TextRoomTimelineView(timelineItem: item)
|
||||
case .separator(let item):
|
||||
case .separator(let item, _):
|
||||
SeparatorRoomTimelineView(timelineItem: item)
|
||||
case .image(let item):
|
||||
case .image(let item, _):
|
||||
ImageRoomTimelineView(timelineItem: item)
|
||||
case .video(let item):
|
||||
case .video(let item, _):
|
||||
VideoRoomTimelineView(timelineItem: item)
|
||||
case .audio(let item):
|
||||
case .audio(let item, _):
|
||||
AudioRoomTimelineView(timelineItem: item)
|
||||
case .file(let item):
|
||||
case .file(let item, _):
|
||||
FileRoomTimelineView(timelineItem: item)
|
||||
case .emote(let item):
|
||||
case .emote(let item, _):
|
||||
EmoteRoomTimelineView(timelineItem: item)
|
||||
case .notice(let item):
|
||||
case .notice(let item, _):
|
||||
NoticeRoomTimelineView(timelineItem: item)
|
||||
case .redacted(let item):
|
||||
case .redacted(let item, _):
|
||||
RedactedRoomTimelineView(timelineItem: item)
|
||||
case .encrypted(let item):
|
||||
case .encrypted(let item, _):
|
||||
EncryptedRoomTimelineView(timelineItem: item)
|
||||
case .readMarker(let item):
|
||||
case .readMarker(let item, _):
|
||||
ReadMarkerRoomTimelineView(timelineItem: item)
|
||||
case .paginationIndicator(let item):
|
||||
case .paginationIndicator(let item, _):
|
||||
PaginationIndicatorRoomTimelineView(timelineItem: item)
|
||||
case .sticker(let item):
|
||||
case .sticker(let item, _):
|
||||
StickerRoomTimelineView(timelineItem: item)
|
||||
case .unsupported(let item):
|
||||
case .unsupported(let item, _):
|
||||
UnsupportedRoomTimelineView(timelineItem: item)
|
||||
case .timelineStart(let item):
|
||||
case .timelineStart(let item, _):
|
||||
TimelineStartRoomTimelineView(timelineItem: item)
|
||||
case .state(let item):
|
||||
case .state(let item, _):
|
||||
StateRoomTimelineView(timelineItem: item)
|
||||
case .group(let item, _):
|
||||
CollapsibleRoomTimelineView(timelineItem: item)
|
||||
}
|
||||
}
|
||||
|
||||
private var timelineStyleGrouping: TimelineGroupStyle {
|
||||
switch self {
|
||||
case .text(_, let grouping),
|
||||
.separator(_, let grouping),
|
||||
.image(_, let grouping),
|
||||
.video(_, let grouping),
|
||||
.audio(_, let grouping),
|
||||
.file(_, let grouping),
|
||||
.emote(_, let grouping),
|
||||
.notice(_, let grouping),
|
||||
.redacted(_, let grouping),
|
||||
.encrypted(_, let grouping),
|
||||
.readMarker(_, let grouping),
|
||||
.paginationIndicator(_, let grouping),
|
||||
.sticker(_, let grouping),
|
||||
.unsupported(_, let grouping),
|
||||
.timelineStart(_, let grouping),
|
||||
.state(_, let grouping),
|
||||
.group(_, let grouping):
|
||||
return grouping
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user