mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-03 16:10:20 +00:00
Reply to a poll (#2087)
* Refactor TimelineItemReplyDetails for polls * Fix reply for polls * Update reply details factory * Revert Poll Hashable conformance * PR comments
This commit is contained in:
@@ -32,7 +32,7 @@ struct MessageComposer: View {
|
|||||||
let editCancellationAction: () -> Void
|
let editCancellationAction: () -> Void
|
||||||
let onAppearAction: () -> Void
|
let onAppearAction: () -> Void
|
||||||
@FocusState private var focused: Bool
|
@FocusState private var focused: Bool
|
||||||
|
|
||||||
@State private var composerTranslation: CGFloat = 0
|
@State private var composerTranslation: CGFloat = 0
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
@@ -40,7 +40,7 @@ struct MessageComposer: View {
|
|||||||
if showResizeGrabber {
|
if showResizeGrabber {
|
||||||
resizeGrabber
|
resizeGrabber
|
||||||
}
|
}
|
||||||
|
|
||||||
let borderRadius: CGFloat = 21
|
let borderRadius: CGFloat = 21
|
||||||
mainContent
|
mainContent
|
||||||
.padding(.horizontal, 12.0)
|
.padding(.horizontal, 12.0)
|
||||||
@@ -60,11 +60,11 @@ struct MessageComposer: View {
|
|||||||
}
|
}
|
||||||
.gesture(showResizeGrabber ? dragGesture : nil)
|
.gesture(showResizeGrabber ? dragGesture : nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Private
|
// MARK: - Private
|
||||||
|
|
||||||
@State private var composerFrame = CGRect.zero
|
@State private var composerFrame = CGRect.zero
|
||||||
|
|
||||||
private var mainContent: some View {
|
private var mainContent: some View {
|
||||||
VStack(alignment: .leading, spacing: -6) {
|
VStack(alignment: .leading, spacing: -6) {
|
||||||
header
|
header
|
||||||
@@ -187,34 +187,34 @@ struct MessageComposer_Previews: PreviewProvider, TestablePreview {
|
|||||||
static let viewModel = RoomScreenViewModel.mock
|
static let viewModel = RoomScreenViewModel.mock
|
||||||
|
|
||||||
static let replyTypes: [TimelineItemReplyDetails] = [
|
static let replyTypes: [TimelineItemReplyDetails] = [
|
||||||
.loaded(sender: .init(id: "Dave"), contentType: .audio(.init(body: "Audio: Ride the lightning", duration: 100, waveform: nil, source: nil, contentType: nil))),
|
.loaded(sender: .init(id: "Dave"), eventContent: .message(.audio(.init(body: "Audio: Ride the lightning", duration: 100, waveform: nil, source: nil, contentType: nil)))),
|
||||||
.loaded(sender: .init(id: "James"), contentType: .emote(.init(body: "Emote: James thinks he's the phantom lord"))),
|
.loaded(sender: .init(id: "James"), eventContent: .message(.emote(.init(body: "Emote: James thinks he's the phantom lord")))),
|
||||||
.loaded(sender: .init(id: "Robert"), contentType: .file(.init(body: "File: Crash course in brain surgery.pdf", source: nil, thumbnailSource: nil, contentType: nil))),
|
.loaded(sender: .init(id: "Robert"), eventContent: .message(.file(.init(body: "File: Crash course in brain surgery.pdf", source: nil, thumbnailSource: nil, contentType: nil)))),
|
||||||
.loaded(sender: .init(id: "Cliff"), contentType: .image(.init(body: "Image: Pushead",
|
.loaded(sender: .init(id: "Cliff"), eventContent: .message(.image(.init(body: "Image: Pushead",
|
||||||
source: .init(url: .picturesDirectory, mimeType: nil),
|
source: .init(url: .picturesDirectory, mimeType: nil),
|
||||||
thumbnailSource: .init(url: .picturesDirectory, mimeType: nil)))),
|
thumbnailSource: .init(url: .picturesDirectory, mimeType: nil))))),
|
||||||
.loaded(sender: .init(id: "Jason"), contentType: .notice(.init(body: "Notice: Too far gone?"))),
|
.loaded(sender: .init(id: "Jason"), eventContent: .message(.notice(.init(body: "Notice: Too far gone?")))),
|
||||||
.loaded(sender: .init(id: "Kirk"), contentType: .text(.init(body: "Text: Where the wild things are"))),
|
.loaded(sender: .init(id: "Kirk"), eventContent: .message(.text(.init(body: "Text: Where the wild things are")))),
|
||||||
.loaded(sender: .init(id: "Lars"), contentType: .video(.init(body: "Video: Through the never",
|
.loaded(sender: .init(id: "Lars"), eventContent: .message(.video(.init(body: "Video: Through the never",
|
||||||
duration: 100,
|
duration: 100,
|
||||||
source: nil,
|
source: nil,
|
||||||
thumbnailSource: .init(url: .picturesDirectory, mimeType: nil)))),
|
thumbnailSource: .init(url: .picturesDirectory, mimeType: nil))))),
|
||||||
.loading(eventID: "")
|
.loading(eventID: "")
|
||||||
]
|
]
|
||||||
|
|
||||||
static func messageComposer(_ content: String = "",
|
static func messageComposer(_ content: String = "",
|
||||||
sendingDisabled: Bool = false,
|
sendingDisabled: Bool = false,
|
||||||
mode: RoomScreenComposerMode = .default) -> MessageComposer {
|
mode: RoomScreenComposerMode = .default) -> MessageComposer {
|
||||||
let viewModel = WysiwygComposerViewModel(minHeight: 22,
|
let viewModel = WysiwygComposerViewModel(minHeight: 22,
|
||||||
maxExpandedHeight: 250)
|
maxExpandedHeight: 250)
|
||||||
viewModel.setMarkdownContent(content)
|
viewModel.setMarkdownContent(content)
|
||||||
|
|
||||||
let composerView = WysiwygComposerView(placeholder: L10n.richTextEditorComposerPlaceholder,
|
let composerView = WysiwygComposerView(placeholder: L10n.richTextEditorComposerPlaceholder,
|
||||||
viewModel: viewModel,
|
viewModel: viewModel,
|
||||||
itemProviderHelper: nil,
|
itemProviderHelper: nil,
|
||||||
keyCommandHandler: nil,
|
keyCommandHandler: nil,
|
||||||
pasteHandler: nil)
|
pasteHandler: nil)
|
||||||
|
|
||||||
return MessageComposer(composerView: composerView,
|
return MessageComposer(composerView: composerView,
|
||||||
mode: mode,
|
mode: mode,
|
||||||
showResizeGrabber: false,
|
showResizeGrabber: false,
|
||||||
@@ -225,20 +225,21 @@ struct MessageComposer_Previews: PreviewProvider, TestablePreview {
|
|||||||
editCancellationAction: { },
|
editCancellationAction: { },
|
||||||
onAppearAction: { viewModel.setup() })
|
onAppearAction: { viewModel.setup() })
|
||||||
}
|
}
|
||||||
|
|
||||||
static var previews: some View {
|
static var previews: some View {
|
||||||
VStack(spacing: 8) {
|
VStack(spacing: 8) {
|
||||||
messageComposer(sendingDisabled: true)
|
messageComposer(sendingDisabled: true)
|
||||||
|
|
||||||
messageComposer("Some message",
|
messageComposer("Some message",
|
||||||
mode: .edit(originalItemId: .random))
|
mode: .edit(originalItemId: .random))
|
||||||
|
|
||||||
messageComposer(mode: .reply(itemID: .random,
|
messageComposer(mode: .reply(itemID: .random,
|
||||||
replyDetails: .loaded(sender: .init(id: "Kirk"),
|
replyDetails: .loaded(sender: .init(id: "Kirk"),
|
||||||
contentType: .text(.init(body: "Text: Where the wild things are"))), isThread: false))
|
eventContent: .message(.text(.init(body: "Text: Where the wild things are")))),
|
||||||
|
isThread: false))
|
||||||
}
|
}
|
||||||
.padding(.horizontal)
|
.padding(.horizontal)
|
||||||
|
|
||||||
ScrollView {
|
ScrollView {
|
||||||
VStack(spacing: 8) {
|
VStack(spacing: 8) {
|
||||||
ForEach(replyTypes, id: \.self) { replyDetails in
|
ForEach(replyTypes, id: \.self) { replyDetails in
|
||||||
|
|||||||
@@ -232,8 +232,8 @@ class RoomScreenInteractionHandler {
|
|||||||
}
|
}
|
||||||
case .reply:
|
case .reply:
|
||||||
let replyInfo = buildReplyInfo(for: eventTimelineItem)
|
let replyInfo = buildReplyInfo(for: eventTimelineItem)
|
||||||
let replyDetails = TimelineItemReplyDetails.loaded(sender: eventTimelineItem.sender, contentType: replyInfo.type)
|
let replyDetails = TimelineItemReplyDetails.loaded(sender: eventTimelineItem.sender, eventContent: replyInfo.type)
|
||||||
|
|
||||||
actionsSubject.send(.composer(action: .setMode(mode: .reply(itemID: eventTimelineItem.id, replyDetails: replyDetails, isThread: replyInfo.isThread))))
|
actionsSubject.send(.composer(action: .setMode(mode: .reply(itemID: eventTimelineItem.id, replyDetails: replyDetails, isThread: replyInfo.isThread))))
|
||||||
case .forward(let itemID):
|
case .forward(let itemID):
|
||||||
actionsSubject.send(.displayMessageForwarding(itemID: itemID))
|
actionsSubject.send(.displayMessageForwarding(itemID: itemID))
|
||||||
@@ -596,11 +596,14 @@ class RoomScreenInteractionHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func buildReplyInfo(for item: EventBasedTimelineItemProtocol) -> ReplyInfo {
|
private func buildReplyInfo(for item: EventBasedTimelineItemProtocol) -> ReplyInfo {
|
||||||
guard let messageItem = item as? EventBasedMessageTimelineItemProtocol else {
|
switch item {
|
||||||
return .init(type: .text(.init(body: item.body)), isThread: false)
|
case let messageItem as EventBasedMessageTimelineItemProtocol:
|
||||||
|
return .init(type: .message(messageItem.contentType), isThread: messageItem.isThreaded)
|
||||||
|
case let pollItem as PollRoomTimelineItem:
|
||||||
|
return .init(type: .poll(question: pollItem.poll.question), isThread: false)
|
||||||
|
default:
|
||||||
|
return .init(type: .message(.text(.init(body: item.body))), isThread: false)
|
||||||
}
|
}
|
||||||
|
|
||||||
return .init(type: messageItem.contentType, isThread: messageItem.isThreaded)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private func openSystemSettings() {
|
private func openSystemSettings() {
|
||||||
@@ -611,7 +614,7 @@ class RoomScreenInteractionHandler {
|
|||||||
private func displayMediaActionIfPossible(timelineItem: RoomTimelineItemProtocol) async -> RoomTimelineControllerAction {
|
private func displayMediaActionIfPossible(timelineItem: RoomTimelineItemProtocol) async -> RoomTimelineControllerAction {
|
||||||
var source: MediaSourceProxy?
|
var source: MediaSourceProxy?
|
||||||
var body: String
|
var body: String
|
||||||
|
|
||||||
switch timelineItem {
|
switch timelineItem {
|
||||||
case let item as ImageRoomTimelineItem:
|
case let item as ImageRoomTimelineItem:
|
||||||
source = item.content.source
|
source = item.content.source
|
||||||
@@ -657,6 +660,6 @@ class RoomScreenInteractionHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private struct ReplyInfo {
|
private struct ReplyInfo {
|
||||||
let type: EventBasedMessageTimelineItemContentType
|
let type: TimelineEventContent
|
||||||
let isThread: Bool
|
let isThread: Bool
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,48 +31,56 @@ struct TimelineReplyView: View {
|
|||||||
switch timelineItemReplyDetails {
|
switch timelineItemReplyDetails {
|
||||||
case .loaded(let sender, let content):
|
case .loaded(let sender, let content):
|
||||||
switch content {
|
switch content {
|
||||||
case .audio(let content):
|
case .message(let content):
|
||||||
|
switch content {
|
||||||
|
case .audio(let content):
|
||||||
|
ReplyView(sender: sender,
|
||||||
|
plainBody: content.body,
|
||||||
|
formattedBody: nil,
|
||||||
|
icon: .init(kind: .systemIcon("waveform"), cornerRadii: iconCornerRadii))
|
||||||
|
case .emote(let content):
|
||||||
|
ReplyView(sender: sender,
|
||||||
|
plainBody: content.body,
|
||||||
|
formattedBody: content.formattedBody)
|
||||||
|
case .file(let content):
|
||||||
|
ReplyView(sender: sender,
|
||||||
|
plainBody: content.body,
|
||||||
|
formattedBody: nil,
|
||||||
|
icon: .init(kind: .iconAsset(Asset.Images.document), cornerRadii: iconCornerRadii))
|
||||||
|
case .image(let content):
|
||||||
|
ReplyView(sender: sender,
|
||||||
|
plainBody: content.body,
|
||||||
|
formattedBody: nil,
|
||||||
|
icon: .init(kind: .mediaSource(content.thumbnailSource ?? content.source), cornerRadii: iconCornerRadii))
|
||||||
|
case .notice(let content):
|
||||||
|
ReplyView(sender: sender,
|
||||||
|
plainBody: content.body,
|
||||||
|
formattedBody: content.formattedBody)
|
||||||
|
case .text(let content):
|
||||||
|
ReplyView(sender: sender,
|
||||||
|
plainBody: content.body,
|
||||||
|
formattedBody: content.formattedBody)
|
||||||
|
case .video(let content):
|
||||||
|
ReplyView(sender: sender,
|
||||||
|
plainBody: content.body,
|
||||||
|
formattedBody: nil,
|
||||||
|
icon: content.thumbnailSource.map { .init(kind: .mediaSource($0), cornerRadii: iconCornerRadii) })
|
||||||
|
case .voice:
|
||||||
|
ReplyView(sender: sender,
|
||||||
|
plainBody: L10n.commonVoiceMessage,
|
||||||
|
formattedBody: nil,
|
||||||
|
icon: .init(kind: .icon(\.micOnOutline), cornerRadii: iconCornerRadii))
|
||||||
|
case .location:
|
||||||
|
ReplyView(sender: sender,
|
||||||
|
plainBody: L10n.commonSharedLocation,
|
||||||
|
formattedBody: nil,
|
||||||
|
icon: .init(kind: .iconAsset(Asset.Images.addLocation), cornerRadii: iconCornerRadii))
|
||||||
|
}
|
||||||
|
case .poll(let question):
|
||||||
ReplyView(sender: sender,
|
ReplyView(sender: sender,
|
||||||
plainBody: content.body,
|
plainBody: question,
|
||||||
formattedBody: nil,
|
formattedBody: nil,
|
||||||
icon: .init(kind: .systemIcon("waveform"), cornerRadii: iconCornerRadii))
|
icon: .init(kind: .iconAsset(Asset.Images.timelinePoll), cornerRadii: iconCornerRadii))
|
||||||
case .emote(let content):
|
|
||||||
ReplyView(sender: sender,
|
|
||||||
plainBody: content.body,
|
|
||||||
formattedBody: content.formattedBody)
|
|
||||||
case .file(let content):
|
|
||||||
ReplyView(sender: sender,
|
|
||||||
plainBody: content.body,
|
|
||||||
formattedBody: nil,
|
|
||||||
icon: .init(kind: .iconAsset(Asset.Images.document), cornerRadii: iconCornerRadii))
|
|
||||||
case .image(let content):
|
|
||||||
ReplyView(sender: sender,
|
|
||||||
plainBody: content.body,
|
|
||||||
formattedBody: nil,
|
|
||||||
icon: .init(kind: .mediaSource(content.thumbnailSource ?? content.source), cornerRadii: iconCornerRadii))
|
|
||||||
case .notice(let content):
|
|
||||||
ReplyView(sender: sender,
|
|
||||||
plainBody: content.body,
|
|
||||||
formattedBody: content.formattedBody)
|
|
||||||
case .text(let content):
|
|
||||||
ReplyView(sender: sender,
|
|
||||||
plainBody: content.body,
|
|
||||||
formattedBody: content.formattedBody)
|
|
||||||
case .video(let content):
|
|
||||||
ReplyView(sender: sender,
|
|
||||||
plainBody: content.body,
|
|
||||||
formattedBody: nil,
|
|
||||||
icon: content.thumbnailSource.map { .init(kind: .mediaSource($0), cornerRadii: iconCornerRadii) })
|
|
||||||
case .voice:
|
|
||||||
ReplyView(sender: sender,
|
|
||||||
plainBody: L10n.commonVoiceMessage,
|
|
||||||
formattedBody: nil,
|
|
||||||
icon: .init(kind: .icon(\.micOnOutline), cornerRadii: iconCornerRadii))
|
|
||||||
case .location:
|
|
||||||
ReplyView(sender: sender,
|
|
||||||
plainBody: L10n.commonSharedLocation,
|
|
||||||
formattedBody: nil,
|
|
||||||
icon: .init(kind: .iconAsset(Asset.Images.addLocation), cornerRadii: iconCornerRadii))
|
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
LoadingReplyView()
|
LoadingReplyView()
|
||||||
@@ -223,7 +231,7 @@ struct TimelineReplyView_Previews: PreviewProvider, TestablePreview {
|
|||||||
|
|
||||||
static var previewItems: [TimelineReplyView] {
|
static var previewItems: [TimelineReplyView] {
|
||||||
let imageSource = MediaSourceProxy(url: "https://mock.com", mimeType: "image/png")
|
let imageSource = MediaSourceProxy(url: "https://mock.com", mimeType: "image/png")
|
||||||
|
|
||||||
return [
|
return [
|
||||||
TimelineReplyView(placement: .timeline, timelineItemReplyDetails: .notLoaded(eventID: "")),
|
TimelineReplyView(placement: .timeline, timelineItemReplyDetails: .notLoaded(eventID: "")),
|
||||||
|
|
||||||
@@ -231,60 +239,60 @@ struct TimelineReplyView_Previews: PreviewProvider, TestablePreview {
|
|||||||
|
|
||||||
TimelineReplyView(placement: .timeline,
|
TimelineReplyView(placement: .timeline,
|
||||||
timelineItemReplyDetails: .loaded(sender: .init(id: "", displayName: "Alice"),
|
timelineItemReplyDetails: .loaded(sender: .init(id: "", displayName: "Alice"),
|
||||||
contentType: .text(.init(body: "This is a reply")))),
|
eventContent: .message(.text(.init(body: "This is a reply"))))),
|
||||||
|
|
||||||
TimelineReplyView(placement: .timeline,
|
TimelineReplyView(placement: .timeline,
|
||||||
timelineItemReplyDetails: .loaded(sender: .init(id: "", displayName: "Alice"),
|
timelineItemReplyDetails: .loaded(sender: .init(id: "", displayName: "Alice"),
|
||||||
contentType: .emote(.init(body: "says hello")))),
|
eventContent: .message(.emote(.init(body: "says hello"))))),
|
||||||
|
|
||||||
TimelineReplyView(placement: .timeline,
|
TimelineReplyView(placement: .timeline,
|
||||||
timelineItemReplyDetails: .loaded(sender: .init(id: "", displayName: "Bob"),
|
timelineItemReplyDetails: .loaded(sender: .init(id: "", displayName: "Bob"),
|
||||||
contentType: .notice(.init(body: "Hello world")))),
|
eventContent: .message(.notice(.init(body: "Hello world"))))),
|
||||||
|
|
||||||
TimelineReplyView(placement: .timeline,
|
TimelineReplyView(placement: .timeline,
|
||||||
timelineItemReplyDetails: .loaded(sender: .init(id: "", displayName: "Alice"),
|
timelineItemReplyDetails: .loaded(sender: .init(id: "", displayName: "Alice"),
|
||||||
contentType: .audio(.init(body: "Some audio",
|
eventContent: .message(.audio(.init(body: "Some audio",
|
||||||
duration: 0,
|
duration: 0,
|
||||||
waveform: nil,
|
waveform: nil,
|
||||||
source: nil,
|
source: nil,
|
||||||
contentType: nil)))),
|
contentType: nil))))),
|
||||||
|
|
||||||
TimelineReplyView(placement: .timeline,
|
TimelineReplyView(placement: .timeline,
|
||||||
timelineItemReplyDetails: .loaded(sender: .init(id: "", displayName: "Alice"),
|
timelineItemReplyDetails: .loaded(sender: .init(id: "", displayName: "Alice"),
|
||||||
contentType: .file(.init(body: "Some file",
|
eventContent: .message(.file(.init(body: "Some file",
|
||||||
source: nil,
|
source: nil,
|
||||||
thumbnailSource: nil,
|
thumbnailSource: nil,
|
||||||
contentType: nil)))),
|
contentType: nil))))),
|
||||||
|
|
||||||
TimelineReplyView(placement: .timeline,
|
TimelineReplyView(placement: .timeline,
|
||||||
timelineItemReplyDetails: .loaded(sender: .init(id: "", displayName: "Alice"),
|
timelineItemReplyDetails: .loaded(sender: .init(id: "", displayName: "Alice"),
|
||||||
contentType: .image(.init(body: "Some image",
|
eventContent: .message(.image(.init(body: "Some image",
|
||||||
source: imageSource,
|
source: imageSource,
|
||||||
thumbnailSource: imageSource)))),
|
thumbnailSource: imageSource))))),
|
||||||
|
|
||||||
TimelineReplyView(placement: .timeline,
|
TimelineReplyView(placement: .timeline,
|
||||||
timelineItemReplyDetails: .loaded(sender: .init(id: "", displayName: "Alice"),
|
timelineItemReplyDetails: .loaded(sender: .init(id: "", displayName: "Alice"),
|
||||||
contentType: .video(.init(body: "Some video",
|
eventContent: .message(.video(.init(body: "Some video",
|
||||||
duration: 0,
|
duration: 0,
|
||||||
source: nil,
|
source: nil,
|
||||||
thumbnailSource: imageSource)))),
|
thumbnailSource: imageSource))))),
|
||||||
TimelineReplyView(placement: .timeline,
|
TimelineReplyView(placement: .timeline,
|
||||||
timelineItemReplyDetails: .loaded(sender: .init(id: "", displayName: "Alice"),
|
timelineItemReplyDetails: .loaded(sender: .init(id: "", displayName: "Alice"),
|
||||||
contentType: .location(.init(body: "")))),
|
eventContent: .message(.location(.init(body: ""))))),
|
||||||
|
|
||||||
TimelineReplyView(placement: .timeline,
|
TimelineReplyView(placement: .timeline,
|
||||||
timelineItemReplyDetails: .loaded(sender: .init(id: "", displayName: "Alice"),
|
timelineItemReplyDetails: .loaded(sender: .init(id: "", displayName: "Alice"),
|
||||||
contentType: .voice(.init(body: "Some voice message",
|
eventContent: .message(.voice(.init(body: "Some voice message",
|
||||||
duration: 0,
|
duration: 0,
|
||||||
waveform: nil,
|
waveform: nil,
|
||||||
source: nil,
|
source: nil,
|
||||||
contentType: nil)))),
|
contentType: nil))))),
|
||||||
TimelineReplyView(placement: .timeline,
|
TimelineReplyView(placement: .timeline,
|
||||||
timelineItemReplyDetails: .loaded(sender: .init(id: "", displayName: "Bob"),
|
timelineItemReplyDetails: .loaded(sender: .init(id: "", displayName: "Bob"),
|
||||||
contentType: .notice(.init(body: "", formattedBody: attributedStringWithMention)))),
|
eventContent: .message(.notice(.init(body: "", formattedBody: attributedStringWithMention))))),
|
||||||
TimelineReplyView(placement: .timeline,
|
TimelineReplyView(placement: .timeline,
|
||||||
timelineItemReplyDetails: .loaded(sender: .init(id: "", displayName: "Bob"),
|
timelineItemReplyDetails: .loaded(sender: .init(id: "", displayName: "Bob"),
|
||||||
contentType: .notice(.init(body: "", formattedBody: attributedStringWithAtRoomMention))))
|
eventContent: .message(.notice(.init(body: "", formattedBody: attributedStringWithAtRoomMention)))))
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -421,7 +421,8 @@ struct TimelineItemBubbledStylerView_Previews: PreviewProvider, TestablePreview
|
|||||||
sender: .init(id: "whoever"),
|
sender: .init(id: "whoever"),
|
||||||
content: .init(body: "A long message that should be on multiple lines."),
|
content: .init(body: "A long message that should be on multiple lines."),
|
||||||
replyDetails: .loaded(sender: .init(id: "", displayName: "Alice"),
|
replyDetails: .loaded(sender: .init(id: "", displayName: "Alice"),
|
||||||
contentType: .text(.init(body: "Short")))), groupStyle: .single))
|
eventContent: .message(.text(.init(body: "Short"))))),
|
||||||
|
groupStyle: .single))
|
||||||
|
|
||||||
AudioRoomTimelineView(timelineItem: .init(id: .init(timelineID: ""),
|
AudioRoomTimelineView(timelineItem: .init(id: .init(timelineID: ""),
|
||||||
timestamp: "10:42",
|
timestamp: "10:42",
|
||||||
@@ -436,7 +437,7 @@ struct TimelineItemBubbledStylerView_Previews: PreviewProvider, TestablePreview
|
|||||||
source: nil,
|
source: nil,
|
||||||
contentType: nil),
|
contentType: nil),
|
||||||
replyDetails: .loaded(sender: .init(id: "", displayName: "Alice"),
|
replyDetails: .loaded(sender: .init(id: "", displayName: "Alice"),
|
||||||
contentType: .text(.init(body: "Short")))))
|
eventContent: .message(.text(.init(body: "Short"))))))
|
||||||
|
|
||||||
FileRoomTimelineView(timelineItem: .init(id: .init(timelineID: ""),
|
FileRoomTimelineView(timelineItem: .init(id: .init(timelineID: ""),
|
||||||
timestamp: "10:42",
|
timestamp: "10:42",
|
||||||
@@ -450,7 +451,7 @@ struct TimelineItemBubbledStylerView_Previews: PreviewProvider, TestablePreview
|
|||||||
thumbnailSource: nil,
|
thumbnailSource: nil,
|
||||||
contentType: nil),
|
contentType: nil),
|
||||||
replyDetails: .loaded(sender: .init(id: "", displayName: "Alice"),
|
replyDetails: .loaded(sender: .init(id: "", displayName: "Alice"),
|
||||||
contentType: .text(.init(body: "Short")))))
|
eventContent: .message(.text(.init(body: "Short"))))))
|
||||||
ImageRoomTimelineView(timelineItem: .init(id: .init(timelineID: ""),
|
ImageRoomTimelineView(timelineItem: .init(id: .init(timelineID: ""),
|
||||||
timestamp: "10:42",
|
timestamp: "10:42",
|
||||||
isOutgoing: true,
|
isOutgoing: true,
|
||||||
@@ -460,7 +461,7 @@ struct TimelineItemBubbledStylerView_Previews: PreviewProvider, TestablePreview
|
|||||||
sender: .init(id: ""),
|
sender: .init(id: ""),
|
||||||
content: .init(body: "Some image", source: MediaSourceProxy(url: .picturesDirectory, mimeType: "image/png"), thumbnailSource: nil),
|
content: .init(body: "Some image", source: MediaSourceProxy(url: .picturesDirectory, mimeType: "image/png"), thumbnailSource: nil),
|
||||||
replyDetails: .loaded(sender: .init(id: "", displayName: "Alice"),
|
replyDetails: .loaded(sender: .init(id: "", displayName: "Alice"),
|
||||||
contentType: .text(.init(body: "Short")))))
|
eventContent: .message(.text(.init(body: "Short"))))))
|
||||||
LocationRoomTimelineView(timelineItem: .init(id: .random,
|
LocationRoomTimelineView(timelineItem: .init(id: .random,
|
||||||
timestamp: "Now",
|
timestamp: "Now",
|
||||||
isOutgoing: false,
|
isOutgoing: false,
|
||||||
@@ -473,7 +474,7 @@ struct TimelineItemBubbledStylerView_Previews: PreviewProvider, TestablePreview
|
|||||||
longitude: 12.496366),
|
longitude: 12.496366),
|
||||||
description: "Location description description description description description description description description"),
|
description: "Location description description description description description description description description"),
|
||||||
replyDetails: .loaded(sender: .init(id: "", displayName: "Alice"),
|
replyDetails: .loaded(sender: .init(id: "", displayName: "Alice"),
|
||||||
contentType: .text(.init(body: "Short")))))
|
eventContent: .message(.text(.init(body: "Short"))))))
|
||||||
LocationRoomTimelineView(timelineItem: .init(id: .random,
|
LocationRoomTimelineView(timelineItem: .init(id: .random,
|
||||||
timestamp: "Now",
|
timestamp: "Now",
|
||||||
isOutgoing: false,
|
isOutgoing: false,
|
||||||
@@ -484,7 +485,7 @@ struct TimelineItemBubbledStylerView_Previews: PreviewProvider, TestablePreview
|
|||||||
content: .init(body: "Fallback geo uri description",
|
content: .init(body: "Fallback geo uri description",
|
||||||
geoURI: .init(latitude: 41.902782, longitude: 12.496366), description: nil),
|
geoURI: .init(latitude: 41.902782, longitude: 12.496366), description: nil),
|
||||||
replyDetails: .loaded(sender: .init(id: "", displayName: "Alice"),
|
replyDetails: .loaded(sender: .init(id: "", displayName: "Alice"),
|
||||||
contentType: .text(.init(body: "Short")))))
|
eventContent: .message(.text(.init(body: "Short"))))))
|
||||||
|
|
||||||
VoiceMessageRoomTimelineView(timelineItem: .init(id: .init(timelineID: ""),
|
VoiceMessageRoomTimelineView(timelineItem: .init(id: .init(timelineID: ""),
|
||||||
timestamp: "10:42",
|
timestamp: "10:42",
|
||||||
@@ -499,7 +500,7 @@ struct TimelineItemBubbledStylerView_Previews: PreviewProvider, TestablePreview
|
|||||||
source: nil,
|
source: nil,
|
||||||
contentType: nil),
|
contentType: nil),
|
||||||
replyDetails: .loaded(sender: .init(id: "", displayName: "Alice"),
|
replyDetails: .loaded(sender: .init(id: "", displayName: "Alice"),
|
||||||
contentType: .text(.init(body: "Short")))),
|
eventContent: .message(.text(.init(body: "Short"))))),
|
||||||
playerState: AudioPlayerState(id: .timelineItemIdentifier(.random), duration: 10, waveform: EstimatedWaveform.mockWaveform))
|
playerState: AudioPlayerState(id: .timelineItemIdentifier(.random), duration: 10, waveform: EstimatedWaveform.mockWaveform))
|
||||||
}
|
}
|
||||||
.environmentObject(viewModel.context)
|
.environmentObject(viewModel.context)
|
||||||
@@ -530,7 +531,8 @@ struct TimelineItemBubbledStylerView_Previews: PreviewProvider, TestablePreview
|
|||||||
sender: .init(id: "whoever"),
|
sender: .init(id: "whoever"),
|
||||||
content: .init(body: "A long message that should be on multiple lines."),
|
content: .init(body: "A long message that should be on multiple lines."),
|
||||||
replyDetails: .loaded(sender: .init(id: "", displayName: "Alice"),
|
replyDetails: .loaded(sender: .init(id: "", displayName: "Alice"),
|
||||||
contentType: .text(.init(body: "Short")))), groupStyle: .single))
|
eventContent: .message(.text(.init(body: "Short"))))),
|
||||||
|
groupStyle: .single))
|
||||||
|
|
||||||
RoomTimelineItemView(viewState: .init(item: TextRoomTimelineItem(id: .init(timelineID: ""),
|
RoomTimelineItemView(viewState: .init(item: TextRoomTimelineItem(id: .init(timelineID: ""),
|
||||||
timestamp: "10:42",
|
timestamp: "10:42",
|
||||||
@@ -541,7 +543,8 @@ struct TimelineItemBubbledStylerView_Previews: PreviewProvider, TestablePreview
|
|||||||
sender: .init(id: "whoever"),
|
sender: .init(id: "whoever"),
|
||||||
content: .init(body: "Short message"),
|
content: .init(body: "Short message"),
|
||||||
replyDetails: .loaded(sender: .init(id: "", displayName: "Alice"),
|
replyDetails: .loaded(sender: .init(id: "", displayName: "Alice"),
|
||||||
contentType: .text(.init(body: "A long message that should be on more than 2 lines and so will be clipped by the layout.")))), groupStyle: .single))
|
eventContent: .message(.text(.init(body: "A long message that should be on more than 2 lines and so will be clipped by the layout."))))),
|
||||||
|
groupStyle: .single))
|
||||||
}
|
}
|
||||||
.environmentObject(viewModel.context)
|
.environmentObject(viewModel.context)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -156,8 +156,9 @@ struct TimelineItemPlainStylerView_Previews: PreviewProvider, TestablePreview {
|
|||||||
sender: .init(id: "whoever"),
|
sender: .init(id: "whoever"),
|
||||||
content: .init(body: "A long message that should be on multiple lines."),
|
content: .init(body: "A long message that should be on multiple lines."),
|
||||||
replyDetails: .loaded(sender: .init(id: "", displayName: "Alice"),
|
replyDetails: .loaded(sender: .init(id: "", displayName: "Alice"),
|
||||||
contentType: .text(.init(body: "Short")))), groupStyle: .single))
|
eventContent: .message(.text(.init(body: "Short"))))),
|
||||||
|
groupStyle: .single))
|
||||||
|
|
||||||
AudioRoomTimelineView(timelineItem: .init(id: .init(timelineID: ""),
|
AudioRoomTimelineView(timelineItem: .init(id: .init(timelineID: ""),
|
||||||
timestamp: "10:42",
|
timestamp: "10:42",
|
||||||
isOutgoing: true,
|
isOutgoing: true,
|
||||||
@@ -171,7 +172,7 @@ struct TimelineItemPlainStylerView_Previews: PreviewProvider, TestablePreview {
|
|||||||
source: nil,
|
source: nil,
|
||||||
contentType: nil),
|
contentType: nil),
|
||||||
replyDetails: .loaded(sender: .init(id: "", displayName: "Alice"),
|
replyDetails: .loaded(sender: .init(id: "", displayName: "Alice"),
|
||||||
contentType: .text(.init(body: "Short")))))
|
eventContent: .message(.text(.init(body: "Short"))))))
|
||||||
FileRoomTimelineView(timelineItem: .init(id: .init(timelineID: ""),
|
FileRoomTimelineView(timelineItem: .init(id: .init(timelineID: ""),
|
||||||
timestamp: "10:42",
|
timestamp: "10:42",
|
||||||
isOutgoing: false,
|
isOutgoing: false,
|
||||||
@@ -184,7 +185,7 @@ struct TimelineItemPlainStylerView_Previews: PreviewProvider, TestablePreview {
|
|||||||
thumbnailSource: nil,
|
thumbnailSource: nil,
|
||||||
contentType: nil),
|
contentType: nil),
|
||||||
replyDetails: .loaded(sender: .init(id: "", displayName: "Alice"),
|
replyDetails: .loaded(sender: .init(id: "", displayName: "Alice"),
|
||||||
contentType: .text(.init(body: "Short")))))
|
eventContent: .message(.text(.init(body: "Short"))))))
|
||||||
ImageRoomTimelineView(timelineItem: .init(id: .init(timelineID: ""),
|
ImageRoomTimelineView(timelineItem: .init(id: .init(timelineID: ""),
|
||||||
timestamp: "10:42",
|
timestamp: "10:42",
|
||||||
isOutgoing: true,
|
isOutgoing: true,
|
||||||
@@ -194,7 +195,7 @@ struct TimelineItemPlainStylerView_Previews: PreviewProvider, TestablePreview {
|
|||||||
sender: .init(id: ""),
|
sender: .init(id: ""),
|
||||||
content: .init(body: "Some image", source: MediaSourceProxy(url: .picturesDirectory, mimeType: "image/png"), thumbnailSource: nil),
|
content: .init(body: "Some image", source: MediaSourceProxy(url: .picturesDirectory, mimeType: "image/png"), thumbnailSource: nil),
|
||||||
replyDetails: .loaded(sender: .init(id: "", displayName: "Alice"),
|
replyDetails: .loaded(sender: .init(id: "", displayName: "Alice"),
|
||||||
contentType: .text(.init(body: "Short")))))
|
eventContent: .message(.text(.init(body: "Short"))))))
|
||||||
LocationRoomTimelineView(timelineItem: .init(id: .random,
|
LocationRoomTimelineView(timelineItem: .init(id: .random,
|
||||||
timestamp: "Now",
|
timestamp: "Now",
|
||||||
isOutgoing: false,
|
isOutgoing: false,
|
||||||
@@ -207,7 +208,7 @@ struct TimelineItemPlainStylerView_Previews: PreviewProvider, TestablePreview {
|
|||||||
longitude: 12.496366),
|
longitude: 12.496366),
|
||||||
description: "Location description description description description description description description description"),
|
description: "Location description description description description description description description description"),
|
||||||
replyDetails: .loaded(sender: .init(id: "", displayName: "Alice"),
|
replyDetails: .loaded(sender: .init(id: "", displayName: "Alice"),
|
||||||
contentType: .text(.init(body: "Short")))))
|
eventContent: .message(.text(.init(body: "Short"))))))
|
||||||
LocationRoomTimelineView(timelineItem: .init(id: .random,
|
LocationRoomTimelineView(timelineItem: .init(id: .random,
|
||||||
timestamp: "Now",
|
timestamp: "Now",
|
||||||
isOutgoing: false,
|
isOutgoing: false,
|
||||||
@@ -218,7 +219,7 @@ struct TimelineItemPlainStylerView_Previews: PreviewProvider, TestablePreview {
|
|||||||
content: .init(body: "Fallback geo uri description",
|
content: .init(body: "Fallback geo uri description",
|
||||||
geoURI: .init(latitude: 41.902782, longitude: 12.496366), description: nil),
|
geoURI: .init(latitude: 41.902782, longitude: 12.496366), description: nil),
|
||||||
replyDetails: .loaded(sender: .init(id: "", displayName: "Alice"),
|
replyDetails: .loaded(sender: .init(id: "", displayName: "Alice"),
|
||||||
contentType: .text(.init(body: "Short")))))
|
eventContent: .message(.text(.init(body: "Short"))))))
|
||||||
VoiceMessageRoomTimelineView(timelineItem: .init(id: .init(timelineID: ""),
|
VoiceMessageRoomTimelineView(timelineItem: .init(id: .init(timelineID: ""),
|
||||||
timestamp: "10:42",
|
timestamp: "10:42",
|
||||||
isOutgoing: true,
|
isOutgoing: true,
|
||||||
@@ -232,12 +233,12 @@ struct TimelineItemPlainStylerView_Previews: PreviewProvider, TestablePreview {
|
|||||||
source: nil,
|
source: nil,
|
||||||
contentType: nil),
|
contentType: nil),
|
||||||
replyDetails: .loaded(sender: .init(id: "", displayName: "Alice"),
|
replyDetails: .loaded(sender: .init(id: "", displayName: "Alice"),
|
||||||
contentType: .text(.init(body: "Short")))),
|
eventContent: .message(.text(.init(body: "Short"))))),
|
||||||
playerState: AudioPlayerState(id: .timelineItemIdentifier(.init(timelineID: "")), duration: 10, waveform: EstimatedWaveform.mockWaveform))
|
playerState: AudioPlayerState(id: .timelineItemIdentifier(.init(timelineID: "")), duration: 10, waveform: EstimatedWaveform.mockWaveform))
|
||||||
}
|
}
|
||||||
.environmentObject(viewModel.context)
|
.environmentObject(viewModel.context)
|
||||||
}
|
}
|
||||||
|
|
||||||
static var previews: some View {
|
static var previews: some View {
|
||||||
VStack(alignment: .leading, spacing: 0) {
|
VStack(alignment: .leading, spacing: 0) {
|
||||||
ForEach(1..<MockRoomTimelineController().timelineItems.count, id: \.self) { index in
|
ForEach(1..<MockRoomTimelineController().timelineItems.count, id: \.self) { index in
|
||||||
|
|||||||
@@ -136,6 +136,6 @@ struct LocationRoomTimelineView_Previews: PreviewProvider, TestablePreview {
|
|||||||
content: .init(body: "Fallback geo uri description",
|
content: .init(body: "Fallback geo uri description",
|
||||||
geoURI: .init(latitude: 41.902782, longitude: 12.496366), description: "Location description description description description description description description description"),
|
geoURI: .init(latitude: 41.902782, longitude: 12.496366), description: "Location description description description description description description description description"),
|
||||||
replyDetails: .loaded(sender: .init(id: "Someone"),
|
replyDetails: .loaded(sender: .init(id: "Someone"),
|
||||||
contentType: .text(.init(body: "The thread content goes 'ere.")))))
|
eventContent: .message(.text(.init(body: "The thread content goes 'ere."))))))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+6
-1
@@ -19,6 +19,11 @@ import Foundation
|
|||||||
enum TimelineItemReplyDetails: Hashable {
|
enum TimelineItemReplyDetails: Hashable {
|
||||||
case notLoaded(eventID: String)
|
case notLoaded(eventID: String)
|
||||||
case loading(eventID: String)
|
case loading(eventID: String)
|
||||||
case loaded(sender: TimelineItemSender, contentType: EventBasedMessageTimelineItemContentType)
|
case loaded(sender: TimelineItemSender, eventContent: TimelineEventContent)
|
||||||
case error(eventID: String, message: String)
|
case error(eventID: String, message: String)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum TimelineEventContent: Hashable {
|
||||||
|
case message(EventBasedMessageTimelineItemContentType)
|
||||||
|
case poll(question: String)
|
||||||
|
}
|
||||||
|
|||||||
@@ -628,47 +628,55 @@ struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol {
|
|||||||
avatarURL: nil)
|
avatarURL: nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
let replyContent: EventBasedMessageTimelineItemContentType
|
let replyContent: TimelineEventContent
|
||||||
|
|
||||||
switch timelineItem.kind() {
|
switch timelineItem.kind() {
|
||||||
case .message:
|
case .message:
|
||||||
switch timelineItem.asMessage()?.msgtype() {
|
return timelineItemReplyDetails(for: timelineItem.asMessage()?.msgtype(), sender: sender)
|
||||||
case .audio(let content):
|
|
||||||
if content.voice != nil {
|
|
||||||
replyContent = .voice(buildAudioTimelineItemContent(content))
|
|
||||||
} else {
|
|
||||||
replyContent = .audio(buildAudioTimelineItemContent(content))
|
|
||||||
}
|
|
||||||
case .emote(let content):
|
|
||||||
replyContent = .emote(buildEmoteTimelineItemContent(senderDisplayName: sender.displayName, senderID: sender.id, messageContent: content))
|
|
||||||
case .file(let content):
|
|
||||||
replyContent = .file(buildFileTimelineItemContent(content))
|
|
||||||
case .image(let content):
|
|
||||||
replyContent = .image(buildImageTimelineItemContent(content))
|
|
||||||
case .notice(let content):
|
|
||||||
replyContent = .notice(buildNoticeTimelineItemContent(content))
|
|
||||||
case .text(let content):
|
|
||||||
replyContent = .text(buildTextTimelineItemContent(content))
|
|
||||||
case .video(let content):
|
|
||||||
replyContent = .video(buildVideoTimelineItemContent(content))
|
|
||||||
case .location(let content):
|
|
||||||
replyContent = .location(buildLocationTimelineItemContent(content))
|
|
||||||
case .other, .none:
|
|
||||||
replyContent = .text(.init(body: L10n.commonUnsupportedEvent))
|
|
||||||
}
|
|
||||||
case .poll(let question, _, _, _, _, _):
|
case .poll(let question, _, _, _, _, _):
|
||||||
replyContent = .text(.init(body: question))
|
replyContent = .poll(question: question)
|
||||||
case .sticker(let body, _, _):
|
case .sticker(let body, _, _):
|
||||||
replyContent = .text(.init(body: body))
|
replyContent = .message(.text(.init(body: body)))
|
||||||
default:
|
default:
|
||||||
replyContent = .text(.init(body: L10n.commonUnsupportedEvent))
|
replyContent = .message(.text(.init(body: L10n.commonUnsupportedEvent)))
|
||||||
}
|
}
|
||||||
|
|
||||||
return .loaded(sender: sender, contentType: replyContent)
|
return .loaded(sender: sender, eventContent: replyContent)
|
||||||
case let .error(message):
|
case let .error(message):
|
||||||
return .error(eventID: details.eventId, message: message)
|
return .error(eventID: details.eventId, message: message)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func timelineItemReplyDetails(for messageType: MessageType?, sender: TimelineItemSender) -> TimelineItemReplyDetails {
|
||||||
|
let replyContent: EventBasedMessageTimelineItemContentType
|
||||||
|
|
||||||
|
switch messageType {
|
||||||
|
case .audio(let content):
|
||||||
|
if content.voice != nil {
|
||||||
|
replyContent = .voice(buildAudioTimelineItemContent(content))
|
||||||
|
} else {
|
||||||
|
replyContent = .audio(buildAudioTimelineItemContent(content))
|
||||||
|
}
|
||||||
|
case .emote(let content):
|
||||||
|
replyContent = .emote(buildEmoteTimelineItemContent(senderDisplayName: sender.displayName, senderID: sender.id, messageContent: content))
|
||||||
|
case .file(let content):
|
||||||
|
replyContent = .file(buildFileTimelineItemContent(content))
|
||||||
|
case .image(let content):
|
||||||
|
replyContent = .image(buildImageTimelineItemContent(content))
|
||||||
|
case .notice(let content):
|
||||||
|
replyContent = .notice(buildNoticeTimelineItemContent(content))
|
||||||
|
case .text(let content):
|
||||||
|
replyContent = .text(buildTextTimelineItemContent(content))
|
||||||
|
case .video(let content):
|
||||||
|
replyContent = .video(buildVideoTimelineItemContent(content))
|
||||||
|
case .location(let content):
|
||||||
|
replyContent = .location(buildLocationTimelineItemContent(content))
|
||||||
|
case .other, .none:
|
||||||
|
replyContent = .text(.init(body: L10n.commonUnsupportedEvent))
|
||||||
|
}
|
||||||
|
|
||||||
|
return .loaded(sender: sender, eventContent: .message(replyContent))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private extension LocationRoomTimelineItemContent.AssetType {
|
private extension LocationRoomTimelineItemContent.AssetType {
|
||||||
|
|||||||
Reference in New Issue
Block a user