"View only" polls in the timeline (#1399)

* Add stub methods in RoomTimelineItemFactory

* Add PollRoomTimelineView and PollEndRoomTimelineView

* Add timeline skeleton

* Start poll mapping

* Add comment

* Refine poll UI

* Add feature flags for polls

* Cleanup

* Delete poll end event

* Refine poll mapping

* Refine poll layout

* Reuse FormRowAccessory

* Refine poll bubble layout

* Localise strings

* Update project

* Refine poll preview

* Map poll properties
This commit is contained in:
Alfonso Grillo
2023-07-27 15:34:27 +02:00
committed by GitHub
parent 8f2904701b
commit 0e7a48d961
19 changed files with 483 additions and 43 deletions
@@ -57,28 +57,7 @@ struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol {
case .failedToParseState(let eventType, _, let error):
return buildUnsupportedTimelineItem(eventItemProxy, eventType, error, isOutgoing)
case .message:
guard let messageTimelineItem = eventItemProxy.content.asMessage() else { fatalError("Invalid message timeline item: \(eventItemProxy)") }
switch messageTimelineItem.msgtype() {
case .text(content: let content):
return buildTextTimelineItem(for: eventItemProxy, messageTimelineItem, content, isOutgoing)
case .image(content: let content):
return buildImageTimelineItem(for: eventItemProxy, messageTimelineItem, content, isOutgoing)
case .video(let content):
return buildVideoTimelineItem(for: eventItemProxy, messageTimelineItem, content, isOutgoing)
case .file(let content):
return buildFileTimelineItem(for: eventItemProxy, messageTimelineItem, content, isOutgoing)
case .notice(content: let content):
return buildNoticeTimelineItem(for: eventItemProxy, messageTimelineItem, content, isOutgoing)
case .emote(content: let content):
return buildEmoteTimelineItem(for: eventItemProxy, messageTimelineItem, content, isOutgoing)
case .audio(let content):
return buildAudioTimelineItem(for: eventItemProxy, messageTimelineItem, content, isOutgoing)
case .location(let content):
return buildLocationTimelineItem(for: eventItemProxy, messageTimelineItem, content, isOutgoing)
case .none:
return nil
}
return buildMessageTimelineItem(eventItemProxy, isOutgoing)
case .state(let stateKey, let content):
return buildStateTimelineItem(for: eventItemProxy, state: content, stateKey: stateKey, isOutgoing: isOutgoing)
case .roomMembership(userId: let userID, change: let change):
@@ -90,12 +69,44 @@ struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol {
avatarURLString: avatarUrl,
previousAvatarURLString: prevAvatarUrl,
isOutgoing: isOutgoing)
case .poll, .pollEnd:
case .poll(question: let question, kind: let kind, maxSelections: let maxSelections, answers: let answers, votes: let votes, endTime: let endTime):
guard ServiceLocator.shared.settings.pollsInTimelineEnabled else {
return nil
}
return buildPollTimelineItem(question, kind, maxSelections, answers, votes, endTime, eventItemProxy, isOutgoing)
case .pollEnd:
return nil
}
}
// MARK: - Message Events
private func buildMessageTimelineItem(_ eventItemProxy: EventTimelineItemProxy, _ isOutgoing: Bool) -> RoomTimelineItemProtocol? {
guard let messageTimelineItem = eventItemProxy.content.asMessage() else {
fatalError("Invalid message timeline item: \(eventItemProxy)")
}
switch messageTimelineItem.msgtype() {
case .text(content: let content):
return buildTextTimelineItem(for: eventItemProxy, messageTimelineItem, content, isOutgoing)
case .image(content: let content):
return buildImageTimelineItem(for: eventItemProxy, messageTimelineItem, content, isOutgoing)
case .video(let content):
return buildVideoTimelineItem(for: eventItemProxy, messageTimelineItem, content, isOutgoing)
case .file(let content):
return buildFileTimelineItem(for: eventItemProxy, messageTimelineItem, content, isOutgoing)
case .notice(content: let content):
return buildNoticeTimelineItem(for: eventItemProxy, messageTimelineItem, content, isOutgoing)
case .emote(content: let content):
return buildEmoteTimelineItem(for: eventItemProxy, messageTimelineItem, content, isOutgoing)
case .audio(let content):
return buildAudioTimelineItem(for: eventItemProxy, messageTimelineItem, content, isOutgoing)
case .location(let content):
return buildLocationTimelineItem(for: eventItemProxy, messageTimelineItem, content, isOutgoing)
case .none:
return nil
}
}
private func buildUnsupportedTimelineItem(_ eventItemProxy: EventTimelineItemProxy,
_ eventType: String,
@@ -309,6 +320,47 @@ struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol {
deliveryStatus: eventItemProxy.deliveryStatus,
orderedReadReceipts: orderReadReceipts(eventItemProxy.readReceipts)))
}
// swiftlint:disable:next function_parameter_count
private func buildPollTimelineItem(_ question: String,
_ pollKind: PollKind,
_ maxSelections: UInt64,
_ answers: [PollAnswer],
_ votes: [String: [String]],
_ endTime: UInt64?,
_ eventItemProxy: EventTimelineItemProxy,
_ isOutgoing: Bool) -> RoomTimelineItemProtocol {
let allVotes = votes.reduce(0) { count, pair in
count + pair.value.count
}
let options = answers.map { answer in
Poll.Option(id: answer.id,
text: answer.text,
votes: votes[answer.id]?.count ?? 0,
allVotes: allVotes,
isSelected: votes[answer.id]?.contains(userID) ?? false)
}
let poll = Poll(question: question,
pollKind: .init(pollKind: pollKind),
maxSelections: Int(maxSelections),
options: options,
votes: votes,
endDate: endTime.map { Date(timeIntervalSince1970: TimeInterval($0 / 1000)) })
return PollRoomTimelineItem(id: eventItemProxy.id,
poll: poll,
body: poll.question,
timestamp: eventItemProxy.timestamp.formatted(date: .omitted, time: .shortened),
isOutgoing: isOutgoing,
isEditable: eventItemProxy.isEditable,
sender: eventItemProxy.sender,
properties: RoomTimelineItemProperties(isEdited: false,
reactions: aggregateReactions(eventItemProxy.reactions),
deliveryStatus: eventItemProxy.deliveryStatus,
orderedReadReceipts: orderReadReceipts(eventItemProxy.readReceipts)))
}
private func aggregateReactions(_ reactions: [Reaction]) -> [AggregatedReaction] {
reactions.map { reaction in
@@ -552,3 +604,14 @@ private extension LocationRoomTimelineItemContent.AssetType {
}
}
}
private extension Poll.Kind {
init(pollKind: MatrixRustSDK.PollKind) {
switch pollKind {
case .disclosed:
self = .disclosed
case .undisclosed:
self = .undisclosed
}
}
}