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:
Stefan Ceriu
2023-02-15 17:14:50 +02:00
committed by Stefan Ceriu
parent 64902190d4
commit a3c7c47a69
58 changed files with 679 additions and 458 deletions
@@ -22,6 +22,10 @@ struct DeveloperOptionsScreenViewState: BindableState {
var bindings: DeveloperOptionsScreenViewStateBindings
}
struct DeveloperOptionsScreenViewStateBindings { }
struct DeveloperOptionsScreenViewStateBindings {
var shouldCollapseRoomStateEvents: Bool
}
enum DeveloperOptionsScreenViewAction { }
enum DeveloperOptionsScreenViewAction {
case changedShouldCollapseRoomStateEvents
}
@@ -20,8 +20,19 @@ typealias DeveloperOptionsScreenViewModelType = StateStoreViewModel<DeveloperOpt
class DeveloperOptionsScreenViewModel: DeveloperOptionsScreenViewModelType, DeveloperOptionsScreenViewModelProtocol {
var callback: ((DeveloperOptionsScreenViewModelAction) -> Void)?
init() {
super.init(initialViewState: DeveloperOptionsScreenViewState(bindings: DeveloperOptionsScreenViewStateBindings()))
super.init(initialViewState: DeveloperOptionsScreenViewState(bindings: DeveloperOptionsScreenViewStateBindings(shouldCollapseRoomStateEvents: ServiceLocator.shared.settings.shouldCollapseRoomStateEvents)))
ServiceLocator.shared.settings.$shouldCollapseRoomStateEvents
.weakAssign(to: \.state.bindings.shouldCollapseRoomStateEvents, on: self)
.store(in: &cancellables)
}
override func process(viewAction: DeveloperOptionsScreenViewAction) async {
switch viewAction {
case .changedShouldCollapseRoomStateEvents:
ServiceLocator.shared.settings.shouldCollapseRoomStateEvents = state.bindings.shouldCollapseRoomStateEvents
}
}
}
@@ -22,6 +22,15 @@ struct DeveloperOptionsScreenScreen: View {
var body: some View {
Form {
Section {
Toggle(isOn: $context.shouldCollapseRoomStateEvents) {
Text("Collapse room state events")
}
.onChange(of: context.shouldCollapseRoomStateEvents) { _ in
context.send(viewAction: .changedShouldCollapseRoomStateEvents)
}
}
Section {
Button {
showConfetti = true
@@ -36,7 +36,6 @@ final class RoomScreenCoordinator: CoordinatorProtocol {
self.parameters = parameters
viewModel = RoomScreenViewModel(timelineController: parameters.timelineController,
timelineViewFactory: RoomTimelineViewFactory(),
mediaProvider: parameters.mediaProvider,
roomName: parameters.roomProxy.displayName ?? parameters.roomProxy.name,
roomAvatarUrl: parameters.roomProxy.avatarURL)
@@ -14,6 +14,7 @@
// limitations under the License.
//
import Algorithms
import Combine
import SwiftUI
@@ -27,15 +28,12 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol
}
private let timelineController: RoomTimelineControllerProtocol
private let timelineViewFactory: RoomTimelineViewFactoryProtocol
init(timelineController: RoomTimelineControllerProtocol,
timelineViewFactory: RoomTimelineViewFactoryProtocol,
mediaProvider: MediaProviderProtocol,
roomName: String?,
roomAvatarUrl: URL? = nil) {
self.timelineController = timelineController
self.timelineViewFactory = timelineViewFactory
super.init(initialViewState: RoomScreenViewState(roomId: timelineController.roomID,
roomTitle: roomName ?? "Unknown room 💥",
@@ -52,13 +50,6 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol
switch callback {
case .updatedTimelineItems:
self.buildTimelineViews()
case .updatedTimelineItem(let itemId):
guard let timelineItem = self.timelineController.timelineItems.first(where: { $0.id == itemId }),
let viewIndex = self.state.items.firstIndex(where: { $0.id == itemId }) else {
return
}
self.state.items[viewIndex] = timelineViewFactory.buildTimelineViewFor(timelineItem: timelineItem)
case .canBackPaginate(let canBackPaginate):
if self.state.canBackPaginate != canBackPaginate {
self.state.canBackPaginate = canBackPaginate
@@ -161,13 +152,57 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol
}
private func buildTimelineViews() {
let stateItems = timelineController.timelineItems.map { item in
timelineViewFactory.buildTimelineViewFor(timelineItem: item)
var timelineViews = [RoomTimelineViewProvider]()
let itemsGroupedByTimelineDisplayStyle = timelineController.timelineItems.chunked { current, next in
canGroupItem(timelineItem: current, with: next)
}
state.items = stateItems
for itemGroup in itemsGroupedByTimelineDisplayStyle {
guard !itemGroup.isEmpty else {
MXLog.error("Found empty item group")
continue
}
if itemGroup.count == 1 {
if let firstItem = itemGroup.first {
timelineViews.append(RoomTimelineViewProvider(timelineItem: firstItem, grouping: .single))
}
} else {
for (index, item) in itemGroup.enumerated() {
if index == 0 {
timelineViews.append(RoomTimelineViewProvider(timelineItem: item, grouping: .first))
} else if index == itemGroup.count - 1 {
timelineViews.append(RoomTimelineViewProvider(timelineItem: item, grouping: .last))
} else {
timelineViews.append(RoomTimelineViewProvider(timelineItem: item, grouping: .middle))
}
}
}
}
state.items = timelineViews
}
private func canGroupItem(timelineItem: RoomTimelineItemProtocol, with otherTimelineItem: RoomTimelineItemProtocol) -> Bool {
if timelineItem is CollapsibleTimelineItem || otherTimelineItem is CollapsibleTimelineItem {
return false
}
guard let eventTimelineItem = timelineItem as? EventBasedTimelineItemProtocol,
let otherEventTimelineItem = otherTimelineItem as? EventBasedTimelineItemProtocol else {
return false
}
// State events aren't rendered as messages so shouldn't be grouped.
if eventTimelineItem is StateRoomTimelineItem || otherEventTimelineItem is StateRoomTimelineItem {
return false
}
// can be improved by adding a date threshold
return otherEventTimelineItem.properties.reactions.isEmpty && eventTimelineItem.sender == otherEventTimelineItem.sender
}
private func sendCurrentMessage() async {
guard !state.bindings.composerText.isEmpty else {
fatalError("This message should never be empty")
@@ -298,7 +333,6 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol
extension RoomScreenViewModel {
static let mock = RoomScreenViewModel(timelineController: MockRoomTimelineController(),
timelineViewFactory: RoomTimelineViewFactory(),
mediaProvider: MockMediaProvider(),
roomName: "Preview room")
}
@@ -58,7 +58,6 @@ struct RoomHeaderView_Previews: PreviewProvider {
@ViewBuilder
static var bodyPlain: some View {
let viewModel = RoomScreenViewModel(timelineController: MockRoomTimelineController(),
timelineViewFactory: RoomTimelineViewFactory(),
mediaProvider: MockMediaProvider(),
roomName: "Some Room name",
roomAvatarUrl: URL.picturesDirectory)
@@ -67,11 +66,10 @@ struct RoomHeaderView_Previews: PreviewProvider {
.previewLayout(.sizeThatFits)
.padding()
}
@ViewBuilder
static var bodyEncrypted: some View {
let viewModel = RoomScreenViewModel(timelineController: MockRoomTimelineController(),
timelineViewFactory: RoomTimelineViewFactory(),
mediaProvider: MockMediaProvider(),
roomName: "Some Room name",
roomAvatarUrl: nil)
@@ -125,7 +125,6 @@ struct RoomScreen: View {
struct RoomScreen_Previews: PreviewProvider {
static var previews: some View {
let viewModel = RoomScreenViewModel(timelineController: MockRoomTimelineController(),
timelineViewFactory: RoomTimelineViewFactory(),
mediaProvider: MockMediaProvider(),
roomName: "Preview room")
@@ -19,11 +19,11 @@ import SwiftUI
struct TimelineItemBubbledStylerView<Content: View>: View {
@EnvironmentObject private var context: RoomScreenViewModel.Context
@Environment(\.timelineGroupStyle) private var timelineStyleGrouping
let timelineItem: EventBasedTimelineItemProtocol
@ViewBuilder let content: () -> Content
@Environment(\.colorScheme) private var colorScheme
@ScaledMetric private var senderNameVerticalPadding = 3
private let cornerRadius: CGFloat = 12
@@ -54,7 +54,7 @@ struct TimelineItemBubbledStylerView<Content: View>: View {
@ViewBuilder
private var header: some View {
if timelineItem.shouldShowSenderDetails {
if shouldShowSenderDetails {
VStack {
Spacer()
.frame(height: 8)
@@ -90,7 +90,7 @@ struct TimelineItemBubbledStylerView<Content: View>: View {
var messageBubble: some View {
styledContent
.contentShape(.contextMenuPreview, RoundedCornerShape(radius: cornerRadius, corners: timelineItem.roundedCorners)) // Rounded corners for the context menu animation.
.contentShape(.contextMenuPreview, RoundedCornerShape(radius: cornerRadius, corners: roundedCorners)) // Rounded corners for the context menu animation.
.contextMenu {
context.viewState.contextMenuActionProvider?(timelineItem.id).map { actions in
TimelineItemContextMenu(itemID: timelineItem.id, contextMenuActions: actions)
@@ -105,7 +105,7 @@ struct TimelineItemBubbledStylerView<Content: View>: View {
content()
.bubbleStyle(inset: false,
cornerRadius: cornerRadius,
corners: timelineItem.roundedCorners)
corners: roundedCorners)
} else {
VStack(alignment: .trailing, spacing: 4) {
content()
@@ -119,13 +119,13 @@ struct TimelineItemBubbledStylerView<Content: View>: View {
.bubbleStyle(inset: true,
color: timelineItem.isOutgoing ? .element.bubblesYou : .element.bubblesNotYou,
cornerRadius: cornerRadius,
corners: timelineItem.roundedCorners)
corners: roundedCorners)
}
}
private var messageBubbleTopPadding: CGFloat {
guard timelineItem.isOutgoing else { return 0 }
return timelineItem.groupState == .single || timelineItem.groupState == .beginning ? 8 : 0
return timelineStyleGrouping == .single || timelineStyleGrouping == .first ? 8 : 0
}
private var shouldAvoidBubbling: Bool {
@@ -135,6 +135,31 @@ struct TimelineItemBubbledStylerView<Content: View>: View {
private var alignment: HorizontalAlignment {
timelineItem.isOutgoing ? .trailing : .leading
}
private var roundedCorners: UIRectCorner {
switch timelineStyleGrouping {
case .single:
return .allCorners
case .first:
if timelineItem.isOutgoing {
return [.topLeft, .topRight, .bottomLeft]
} else {
return [.topLeft, .topRight, .bottomRight]
}
case .middle:
return timelineItem.isOutgoing ? [.topLeft, .bottomLeft] : [.topRight, .bottomRight]
case .last:
if timelineItem.isOutgoing {
return [.topLeft, .bottomLeft, .bottomRight]
} else {
return [.topRight, .bottomLeft, .bottomRight]
}
}
}
private var shouldShowSenderDetails: Bool {
timelineStyleGrouping.shouldShowSenderDetails
}
}
private extension View {
@@ -152,7 +177,7 @@ struct TimelineItemBubbledStylerView_Previews: PreviewProvider {
VStack(alignment: .leading, spacing: 0) {
ForEach(1..<MockRoomTimelineController().timelineItems.count, id: \.self) { index in
let item = MockRoomTimelineController().timelineItems[index]
RoomTimelineViewFactory().buildTimelineViewFor(timelineItem: item)
RoomTimelineViewProvider(timelineItem: item, grouping: .single)
.padding(TimelineStyle.bubbles.rowInsets) // Insets added in the table view cells
}
}
@@ -19,6 +19,7 @@ import SwiftUI
struct TimelineItemPlainStylerView<Content: View>: View {
@EnvironmentObject private var context: RoomScreenViewModel.Context
@Environment(\.timelineGroupStyle) private var timelineStyleGrouping
let timelineItem: EventBasedTimelineItemProtocol
@ViewBuilder let content: () -> Content
@@ -44,7 +45,7 @@ struct TimelineItemPlainStylerView<Content: View>: View {
@ViewBuilder
private var header: some View {
if timelineItem.shouldShowSenderDetails {
if shouldShowSenderDetails {
HStack {
TimelineSenderAvatarView(timelineItem: timelineItem)
Text(timelineItem.sender.displayName ?? timelineItem.sender.id)
@@ -78,6 +79,10 @@ struct TimelineItemPlainStylerView<Content: View>: View {
}
}
}
private var shouldShowSenderDetails: Bool {
timelineStyleGrouping.shouldShowSenderDetails
}
}
struct TimelineItemPlainStylerView_Previews: PreviewProvider {
@@ -87,7 +92,7 @@ struct TimelineItemPlainStylerView_Previews: PreviewProvider {
VStack(alignment: .leading, spacing: 0) {
ForEach(1..<MockRoomTimelineController().timelineItems.count, id: \.self) { index in
let item = MockRoomTimelineController().timelineItems[index]
RoomTimelineViewFactory().buildTimelineViewFor(timelineItem: item)
RoomTimelineViewProvider(timelineItem: item, grouping: .single)
.padding(TimelineStyle.plain.rowInsets) // Insets added in the table view cells
}
}
@@ -32,21 +32,50 @@ enum TimelineStyle: String, CaseIterable {
}
}
enum TimelineGroupStyle: Hashable {
case single
case first
case middle
case last
var shouldShowSenderDetails: Bool {
switch self {
case .single, .first:
return true
default:
return false
}
}
}
// MARK: - Environment
private struct TimelineStyleKey: EnvironmentKey {
static let defaultValue = TimelineStyle.bubbles
}
private struct TimelineGroupStyleKey: EnvironmentKey {
static let defaultValue = TimelineGroupStyle.single
}
extension EnvironmentValues {
var timelineStyle: TimelineStyle {
get { self[TimelineStyleKey.self] }
set { self[TimelineStyleKey.self] = newValue }
}
var timelineGroupStyle: TimelineGroupStyle {
get { self[TimelineGroupStyleKey.self] }
set { self[TimelineGroupStyleKey.self] = newValue }
}
}
extension View {
func timelineStyle(_ style: TimelineStyle) -> some View {
environment(\.timelineStyle, style)
}
func timelineStyleGrouping(_ grouping: TimelineGroupStyle) -> some View {
environment(\.timelineGroupStyle, grouping)
}
}
@@ -45,7 +45,6 @@ struct AudioRoomTimelineView_Previews: PreviewProvider {
AudioRoomTimelineView(timelineItem: AudioRoomTimelineItem(id: UUID().uuidString,
body: "audio.ogg",
timestamp: "Now",
groupState: .single,
isOutgoing: false,
isEditable: false,
sender: .init(id: "Bob"),
@@ -0,0 +1,77 @@
//
// 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 SwiftUI
struct CollapsibleRoomTimelineView: View {
private let timelineItem: CollapsibleTimelineItem
private let timelineViews: [RoomTimelineViewProvider]
@State private var isExpanded = false
init(timelineItem: CollapsibleTimelineItem) {
self.timelineItem = timelineItem
timelineViews = timelineItem.items.map { RoomTimelineViewProvider(timelineItem: $0, grouping: .single) }
}
var body: some View {
DisclosureGroup(ElementL10n.roomTimelineStateChanges(timelineItem.items.count),
isExpanded: $isExpanded) {
Group {
ForEach(timelineViews) { timelineView in
timelineView.body
}
}.transition(.opacity.animation(.elementDefault))
}
.disclosureGroupStyle(CollapsibleRoomTimelineItemDisclosureGroupStyle())
.transaction { transaction in
transaction.animation = .noAnimation // Fixes weird animations on the disclosure indicator
}
}
}
struct CollapsibleRoomTimelineView_Previews: PreviewProvider {
static var previews: some View {
let item = CollapsibleTimelineItem(items: [SeparatorRoomTimelineItem(text: "This is a separator"),
SeparatorRoomTimelineItem(text: "This is another separator")])
CollapsibleRoomTimelineView(timelineItem: item)
}
}
private struct CollapsibleRoomTimelineItemDisclosureGroupStyle: DisclosureGroupStyle {
func makeBody(configuration: Configuration) -> some View {
VStack {
HStack(alignment: .center) {
configuration.label
Text(Image(systemName: "chevron.forward"))
.rotationEffect(.degrees(configuration.isExpanded ? 90 : 0))
.animation(.elementDefault, value: configuration.isExpanded)
}
.frame(maxWidth: .infinity)
.font(.element.footnote)
.foregroundColor(.element.secondaryContent)
.padding(.horizontal, 36)
.padding(.vertical, 8)
.onTapGesture {
configuration.isExpanded.toggle()
}
if configuration.isExpanded {
configuration.content
}
}
}
}
@@ -55,7 +55,6 @@ struct EmoteRoomTimelineView_Previews: PreviewProvider {
EmoteRoomTimelineItem(id: UUID().uuidString,
body: text,
timestamp: timestamp,
groupState: .single,
isOutgoing: false,
isEditable: false,
sender: .init(id: senderId))
@@ -69,7 +69,6 @@ struct EncryptedRoomTimelineView_Previews: PreviewProvider {
body: text,
encryptionType: .unknown,
timestamp: timestamp,
groupState: .single,
isOutgoing: isOutgoing,
isEditable: false,
sender: .init(id: senderId))
@@ -46,7 +46,6 @@ struct FileRoomTimelineView_Previews: PreviewProvider {
FileRoomTimelineView(timelineItem: FileRoomTimelineItem(id: UUID().uuidString,
body: "document.pdf",
timestamp: "Now",
groupState: .single,
isOutgoing: false,
isEditable: false,
sender: .init(id: "Bob"),
@@ -56,7 +55,6 @@ struct FileRoomTimelineView_Previews: PreviewProvider {
FileRoomTimelineView(timelineItem: FileRoomTimelineItem(id: UUID().uuidString,
body: "document.docx",
timestamp: "Now",
groupState: .single,
isOutgoing: false,
isEditable: false,
sender: .init(id: "Bob"),
@@ -66,7 +64,6 @@ struct FileRoomTimelineView_Previews: PreviewProvider {
FileRoomTimelineView(timelineItem: FileRoomTimelineItem(id: UUID().uuidString,
body: "document.txt",
timestamp: "Now",
groupState: .single,
isOutgoing: false,
isEditable: false,
sender: .init(id: "Bob"),
@@ -74,7 +74,6 @@ struct ImageRoomTimelineView_Previews: PreviewProvider {
ImageRoomTimelineView(timelineItem: ImageRoomTimelineItem(id: UUID().uuidString,
body: "Some image",
timestamp: "Now",
groupState: .single,
isOutgoing: false,
isEditable: false,
sender: .init(id: "Bob"),
@@ -83,7 +82,6 @@ struct ImageRoomTimelineView_Previews: PreviewProvider {
ImageRoomTimelineView(timelineItem: ImageRoomTimelineItem(id: UUID().uuidString,
body: "Some other image",
timestamp: "Now",
groupState: .single,
isOutgoing: false,
isEditable: false,
sender: .init(id: "Bob"),
@@ -92,7 +90,6 @@ struct ImageRoomTimelineView_Previews: PreviewProvider {
ImageRoomTimelineView(timelineItem: ImageRoomTimelineItem(id: UUID().uuidString,
body: "Blurhashed image",
timestamp: "Now",
groupState: .single,
isOutgoing: false,
isEditable: false,
sender: .init(id: "Bob"),
@@ -66,7 +66,6 @@ struct NoticeRoomTimelineView_Previews: PreviewProvider {
NoticeRoomTimelineItem(id: UUID().uuidString,
body: text,
timestamp: timestamp,
groupState: .single,
isOutgoing: false,
isEditable: false,
sender: .init(id: senderId))
@@ -41,25 +41,23 @@ struct ReadMarkerRoomTimelineView_Previews: PreviewProvider {
static let item = ReadMarkerRoomTimelineItem()
static var previews: some View {
VStack(alignment: .leading, spacing: 0) {
RoomTimelineViewProvider.separator(.init(text: "Today"))
RoomTimelineViewProvider.separator(.init(text: "Today"), .single)
RoomTimelineViewProvider.text(.init(id: "",
body: "This is another message",
timestamp: "",
groupState: .single,
isOutgoing: true,
isEditable: false,
sender: .init(id: "1", displayName: "Bob")))
sender: .init(id: "1", displayName: "Bob")), .single)
ReadMarkerRoomTimelineView(timelineItem: item)
RoomTimelineViewProvider.separator(.init(text: "Today"))
RoomTimelineViewProvider.separator(.init(text: "Today"), .single)
RoomTimelineViewProvider.text(.init(id: "",
body: "This is a message",
timestamp: "",
groupState: .single,
isOutgoing: false,
isEditable: false,
sender: .init(id: "", displayName: "Alice")))
sender: .init(id: "", displayName: "Alice")), .single)
}
.padding(.horizontal, 8)
.environmentObject(viewModel.context)
@@ -45,7 +45,6 @@ struct RedactedRoomTimelineView_Previews: PreviewProvider {
RedactedRoomTimelineItem(id: UUID().uuidString,
body: text,
timestamp: timestamp,
groupState: .single,
isOutgoing: false,
isEditable: false,
sender: .init(id: senderId))
@@ -43,7 +43,6 @@ struct StateRoomTimelineView_Previews: PreviewProvider {
static let item = StateRoomTimelineItem(id: UUID().uuidString,
body: "Alice joined",
timestamp: "Now",
groupState: .beginning,
isOutgoing: false,
isEditable: false,
sender: .init(id: ""))
@@ -59,7 +59,6 @@ struct StickerRoomTimelineView_Previews: PreviewProvider {
StickerRoomTimelineView(timelineItem: StickerRoomTimelineItem(id: UUID().uuidString,
body: "Some image",
timestamp: "Now",
groupState: .single,
isOutgoing: false,
isEditable: false,
sender: .init(id: "Bob"),
@@ -68,7 +67,6 @@ struct StickerRoomTimelineView_Previews: PreviewProvider {
StickerRoomTimelineView(timelineItem: StickerRoomTimelineItem(id: UUID().uuidString,
body: "Some other image",
timestamp: "Now",
groupState: .single,
isOutgoing: false,
isEditable: false,
sender: .init(id: "Bob"),
@@ -77,7 +75,6 @@ struct StickerRoomTimelineView_Previews: PreviewProvider {
StickerRoomTimelineView(timelineItem: StickerRoomTimelineItem(id: UUID().uuidString,
body: "Blurhashed image",
timestamp: "Now",
groupState: .single,
isOutgoing: false,
isEditable: false,
sender: .init(id: "Bob"),
@@ -67,7 +67,6 @@ struct TextRoomTimelineView_Previews: PreviewProvider {
TextRoomTimelineItem(id: UUID().uuidString,
body: text,
timestamp: timestamp,
groupState: .single,
isOutgoing: isOutgoing,
isEditable: isOutgoing,
sender: .init(id: senderId))
@@ -66,7 +66,6 @@ struct UnsupportedRoomTimelineView_Previews: PreviewProvider {
eventType: "Some Event Type",
error: "Something went wrong",
timestamp: timestamp,
groupState: .single,
isOutgoing: isOutgoing,
isEditable: false,
sender: .init(id: senderId))
@@ -69,7 +69,6 @@ struct VideoRoomTimelineView_Previews: PreviewProvider {
VideoRoomTimelineView(timelineItem: VideoRoomTimelineItem(id: UUID().uuidString,
body: "Some video",
timestamp: "Now",
groupState: .single,
isOutgoing: false,
isEditable: false,
sender: .init(id: "Bob"),
@@ -80,7 +79,6 @@ struct VideoRoomTimelineView_Previews: PreviewProvider {
VideoRoomTimelineView(timelineItem: VideoRoomTimelineItem(id: UUID().uuidString,
body: "Some other video",
timestamp: "Now",
groupState: .single,
isOutgoing: false,
isEditable: false,
sender: .init(id: "Bob"),
@@ -91,7 +89,6 @@ struct VideoRoomTimelineView_Previews: PreviewProvider {
VideoRoomTimelineView(timelineItem: VideoRoomTimelineItem(id: UUID().uuidString,
body: "Blurhashed video",
timestamp: "Now",
groupState: .single,
isOutgoing: false,
isEditable: false,
sender: .init(id: "Bob"),
@@ -83,7 +83,6 @@ struct TimelineView: UIViewControllerRepresentable {
struct TimelineTableView_Previews: PreviewProvider {
static let viewModel = RoomScreenViewModel(timelineController: MockRoomTimelineController(),
timelineViewFactory: RoomTimelineViewFactory(),
mediaProvider: MockMediaProvider(),
roomName: "Preview room")