Interact with polls through the TimelineController as well (creation still disabled on threaded timeline as it's not yet supported by the SDK)

This commit is contained in:
Stefan Ceriu
2025-07-22 17:00:09 +03:00
committed by Stefan Ceriu
parent 324a203086
commit ffe2f726e7
8 changed files with 162 additions and 100 deletions
@@ -321,7 +321,10 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
presentMapNavigator(interactionMode: mode, timelineController: timelineController, animated: animated)
case (_, .presentPollForm(let mode), .pollForm):
presentPollForm(mode: mode)
guard let timelineController = (context.userInfo as? EventUserInfo)?.timelineController else {
fatalError()
}
presentPollForm(mode: mode, timelineController: timelineController)
case (_, .presentResolveSendFailure(let failure, let sendHandle), .resolveSendFailure):
presentResolveSendFailure(failure: failure, sendHandle: sendHandle)
@@ -343,7 +346,7 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
presentRoomMembersList()
case (.roomDetails, .presentPollsHistory, .pollsHistory):
presentPollsHistory()
Task { await self.presentRoomPollsHistory(animated: animated) }
case (.roomDetails, .presentPinnedEventsTimeline, .pinnedEventsTimeline):
startPinnedEventsTimelineFlow()
@@ -392,7 +395,10 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
replaceRoomMemberDetailsWithUserProfile(userID: userID)
case (.pollsHistory, .presentPollForm(let mode), .pollsHistoryForm):
presentPollForm(mode: mode)
guard let timelineController = (context.userInfo as? EventUserInfo)?.timelineController else {
fatalError()
}
presentPollForm(mode: mode, timelineController: timelineController)
case (_, .presentMediaUploadPreview, .mediaUploadPreview(let fileURL, _)):
guard let timelineController = (context.userInfo as? EventUserInfo)?.timelineController else {
@@ -557,7 +563,8 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
stateMachine.tryEvent(.presentMapNavigator(interactionMode: .picker),
userInfo: EventUserInfo(animated: animated, timelineController: timelineController))
case .presentPollForm(let mode):
stateMachine.tryEvent(.presentPollForm(mode: mode))
stateMachine.tryEvent(.presentPollForm(mode: mode),
userInfo: EventUserInfo(animated: animated, timelineController: timelineController))
case .presentLocationViewer(_, let geoURI, let description):
stateMachine.tryEvent(.presentMapNavigator(interactionMode: .viewOnly(geoURI: geoURI, description: description)),
userInfo: EventUserInfo(animated: animated, timelineController: timelineController))
@@ -642,7 +649,8 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
stateMachine.tryEvent(.presentMapNavigator(interactionMode: .picker),
userInfo: EventUserInfo(animated: animated, timelineController: timelineController))
case .presentPollForm(let mode):
stateMachine.tryEvent(.presentPollForm(mode: mode))
stateMachine.tryEvent(.presentPollForm(mode: mode),
userInfo: EventUserInfo(animated: animated, timelineController: timelineController))
case .presentLocationViewer(_, let geoURI, let description):
stateMachine.tryEvent(.presentMapNavigator(interactionMode: .viewOnly(geoURI: geoURI,
description: description)),
@@ -1034,7 +1042,7 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
}
}
private func presentPollForm(mode: PollFormMode) {
private func presentPollForm(mode: PollFormMode, timelineController: TimelineControllerProtocol) {
let stackCoordinator = NavigationStackCoordinator()
let coordinator = PollFormScreenCoordinator(parameters: .init(mode: mode))
stackCoordinator.setRootCoordinator(coordinator)
@@ -1055,9 +1063,16 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
case let .submit(question, options, pollKind):
switch mode {
case .new:
createPoll(question: question, options: options, pollKind: pollKind)
createPoll(question: question,
options: options,
pollKind: pollKind,
timelineController: timelineController)
case .edit(let eventID, _):
editPoll(pollStartID: eventID, question: question, options: options, pollKind: pollKind)
editPoll(pollStartID: eventID,
question: question,
options: options,
pollKind: pollKind,
timelineController: timelineController)
}
}
}
@@ -1068,9 +1083,9 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
}
}
private func createPoll(question: String, options: [String], pollKind: Poll.Kind) {
private func createPoll(question: String, options: [String], pollKind: Poll.Kind, timelineController: TimelineControllerProtocol) {
Task {
let result = await roomProxy.timeline.createPoll(question: question, answers: options, pollKind: pollKind)
let result = await timelineController.createPoll(question: question, answers: options, pollKind: pollKind)
self.analytics.trackComposer(inThread: false,
isEditing: false,
@@ -1089,9 +1104,9 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
}
}
private func editPoll(pollStartID: String, question: String, options: [String], pollKind: Poll.Kind) {
private func editPoll(pollStartID: String, question: String, options: [String], pollKind: Poll.Kind, timelineController: TimelineControllerProtocol) {
Task {
let result = await roomProxy.timeline.editPoll(original: pollStartID, question: question, answers: options, pollKind: pollKind)
let result = await timelineController.editPoll(original: pollStartID, question: question, answers: options, pollKind: pollKind)
switch result {
case .success:
@@ -1120,13 +1135,7 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
}
}
private func presentPollsHistory() {
Task {
await asyncPresentRoomPollsHistory()
}
}
private func asyncPresentRoomPollsHistory() async {
private func presentRoomPollsHistory(animated: Bool) async {
let userID = userSession.clientProxy.userID
let timelineItemFactory = RoomTimelineItemFactory(userID: userID,
@@ -1138,7 +1147,8 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
timelineItemFactory: timelineItemFactory,
mediaProvider: userSession.mediaProvider)
let parameters = RoomPollsHistoryScreenCoordinatorParameters(pollInteractionHandler: PollInteractionHandler(analyticsService: analytics, roomProxy: roomProxy),
let parameters = RoomPollsHistoryScreenCoordinatorParameters(pollInteractionHandler: PollInteractionHandler(analyticsService: analytics,
timelineController: timelineController),
timelineController: timelineController)
let coordinator = RoomPollsHistoryScreenCoordinator(parameters: parameters)
coordinator.actions
@@ -1147,7 +1157,8 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
switch action {
case .editPoll(let pollStartID, let poll):
stateMachine.tryEvent(.presentPollForm(mode: .edit(eventID: pollStartID, poll: poll)))
stateMachine.tryEvent(.presentPollForm(mode: .edit(eventID: pollStartID, poll: poll)),
userInfo: EventUserInfo(animated: animated, timelineController: timelineController))
}
}
.store(in: &cancellables)
@@ -17752,76 +17752,6 @@ class TimelineProxyMock: TimelineProxyProtocol, @unchecked Sendable {
return editPollOriginalQuestionAnswersPollKindReturnValue
}
}
//MARK: - endPoll
var endPollPollStartIDTextUnderlyingCallsCount = 0
var endPollPollStartIDTextCallsCount: Int {
get {
if Thread.isMainThread {
return endPollPollStartIDTextUnderlyingCallsCount
} else {
var returnValue: Int? = nil
DispatchQueue.main.sync {
returnValue = endPollPollStartIDTextUnderlyingCallsCount
}
return returnValue!
}
}
set {
if Thread.isMainThread {
endPollPollStartIDTextUnderlyingCallsCount = newValue
} else {
DispatchQueue.main.sync {
endPollPollStartIDTextUnderlyingCallsCount = newValue
}
}
}
}
var endPollPollStartIDTextCalled: Bool {
return endPollPollStartIDTextCallsCount > 0
}
var endPollPollStartIDTextReceivedArguments: (pollStartID: String, text: String)?
var endPollPollStartIDTextReceivedInvocations: [(pollStartID: String, text: String)] = []
var endPollPollStartIDTextUnderlyingReturnValue: Result<Void, TimelineProxyError>!
var endPollPollStartIDTextReturnValue: Result<Void, TimelineProxyError>! {
get {
if Thread.isMainThread {
return endPollPollStartIDTextUnderlyingReturnValue
} else {
var returnValue: Result<Void, TimelineProxyError>? = nil
DispatchQueue.main.sync {
returnValue = endPollPollStartIDTextUnderlyingReturnValue
}
return returnValue!
}
}
set {
if Thread.isMainThread {
endPollPollStartIDTextUnderlyingReturnValue = newValue
} else {
DispatchQueue.main.sync {
endPollPollStartIDTextUnderlyingReturnValue = newValue
}
}
}
}
var endPollPollStartIDTextClosure: ((String, String) async -> Result<Void, TimelineProxyError>)?
func endPoll(pollStartID: String, text: String) async -> Result<Void, TimelineProxyError> {
endPollPollStartIDTextCallsCount += 1
endPollPollStartIDTextReceivedArguments = (pollStartID: pollStartID, text: text)
DispatchQueue.main.async {
self.endPollPollStartIDTextReceivedInvocations.append((pollStartID: pollStartID, text: text))
}
if let endPollPollStartIDTextClosure = endPollPollStartIDTextClosure {
return await endPollPollStartIDTextClosure(pollStartID, text)
} else {
return endPollPollStartIDTextReturnValue
}
}
//MARK: - sendPollResponse
var sendPollResponsePollStartIDAnswersUnderlyingCallsCount = 0
@@ -17892,6 +17822,76 @@ class TimelineProxyMock: TimelineProxyProtocol, @unchecked Sendable {
return sendPollResponsePollStartIDAnswersReturnValue
}
}
//MARK: - endPoll
var endPollPollStartIDTextUnderlyingCallsCount = 0
var endPollPollStartIDTextCallsCount: Int {
get {
if Thread.isMainThread {
return endPollPollStartIDTextUnderlyingCallsCount
} else {
var returnValue: Int? = nil
DispatchQueue.main.sync {
returnValue = endPollPollStartIDTextUnderlyingCallsCount
}
return returnValue!
}
}
set {
if Thread.isMainThread {
endPollPollStartIDTextUnderlyingCallsCount = newValue
} else {
DispatchQueue.main.sync {
endPollPollStartIDTextUnderlyingCallsCount = newValue
}
}
}
}
var endPollPollStartIDTextCalled: Bool {
return endPollPollStartIDTextCallsCount > 0
}
var endPollPollStartIDTextReceivedArguments: (pollStartID: String, text: String)?
var endPollPollStartIDTextReceivedInvocations: [(pollStartID: String, text: String)] = []
var endPollPollStartIDTextUnderlyingReturnValue: Result<Void, TimelineProxyError>!
var endPollPollStartIDTextReturnValue: Result<Void, TimelineProxyError>! {
get {
if Thread.isMainThread {
return endPollPollStartIDTextUnderlyingReturnValue
} else {
var returnValue: Result<Void, TimelineProxyError>? = nil
DispatchQueue.main.sync {
returnValue = endPollPollStartIDTextUnderlyingReturnValue
}
return returnValue!
}
}
set {
if Thread.isMainThread {
endPollPollStartIDTextUnderlyingReturnValue = newValue
} else {
DispatchQueue.main.sync {
endPollPollStartIDTextUnderlyingReturnValue = newValue
}
}
}
}
var endPollPollStartIDTextClosure: ((String, String) async -> Result<Void, TimelineProxyError>)?
func endPoll(pollStartID: String, text: String) async -> Result<Void, TimelineProxyError> {
endPollPollStartIDTextCallsCount += 1
endPollPollStartIDTextReceivedArguments = (pollStartID: pollStartID, text: text)
DispatchQueue.main.async {
self.endPollPollStartIDTextReceivedInvocations.append((pollStartID: pollStartID, text: text))
}
if let endPollPollStartIDTextClosure = endPollPollStartIDTextClosure {
return await endPollPollStartIDTextClosure(pollStartID, text)
} else {
return endPollPollStartIDTextReturnValue
}
}
//MARK: - getLoadedReplyDetails
var getLoadedReplyDetailsEventIDUnderlyingCallsCount = 0
@@ -85,7 +85,9 @@ class TimelineInteractionHandler {
self.emojiProvider = emojiProvider
self.timelineControllerFactory = timelineControllerFactory
self.clientProxy = clientProxy
pollInteractionHandler = PollInteractionHandler(analyticsService: analyticsService, roomProxy: roomProxy)
pollInteractionHandler = PollInteractionHandler(analyticsService: analyticsService,
timelineController: timelineController)
}
// MARK: Timeline Item Action Menu
@@ -9,22 +9,22 @@ import Foundation
class PollInteractionHandler: PollInteractionHandlerProtocol {
let analyticsService: AnalyticsService
let roomProxy: JoinedRoomProxyProtocol
let timelineController: TimelineControllerProtocol
init(analyticsService: AnalyticsService, roomProxy: JoinedRoomProxyProtocol) {
init(analyticsService: AnalyticsService, timelineController: TimelineControllerProtocol) {
self.analyticsService = analyticsService
self.roomProxy = roomProxy
self.timelineController = timelineController
}
func sendPollResponse(pollStartID: String, optionID: String) async -> Result<Void, Error> {
let sendPollResponseResult = await roomProxy.timeline.sendPollResponse(pollStartID: pollStartID, answers: [optionID])
let sendPollResponseResult = await timelineController.sendPollResponse(pollStartID: pollStartID, answers: [optionID])
analyticsService.trackPollVote()
return sendPollResponseResult.mapError { $0 }
}
func endPoll(pollStartID: String) async -> Result<Void, Error> {
let endPollResult = await roomProxy.timeline.endPoll(pollStartID: pollStartID,
let endPollResult = await timelineController.endPoll(pollStartID: pollStartID,
text: "The poll with event id: \(pollStartID) has ended")
analyticsService.trackPollEnd()
return endPollResult.mapError { $0 }
@@ -244,6 +244,24 @@ class MockTimelineController: TimelineControllerProtocol {
return .success(())
}
// MARK: - Polls
func createPoll(question: String, answers: [String], pollKind: Poll.Kind) async -> Result<Void, TimelineControllerError> {
.success(())
}
func editPoll(original eventID: String, question: String, answers: [String], pollKind: Poll.Kind) async -> Result<Void, TimelineControllerError> {
.success(())
}
func sendPollResponse(pollStartID: String, answers: [String]) async -> Result<Void, TimelineControllerError> {
.success(())
}
func endPoll(pollStartID: String, text: String) async -> Result<Void, TimelineControllerError> {
.success(())
}
// MARK: - UI Test signalling
@@ -362,6 +362,24 @@ class TimelineController: TimelineControllerProtocol {
waveform: waveform, requestHandle: requestHandle).mapError(TimelineControllerError.timelineProxyError)
}
// MARK: - Polls
func createPoll(question: String, answers: [String], pollKind: Poll.Kind) async -> Result<Void, TimelineControllerError> {
await activeTimeline.createPoll(question: question, answers: answers, pollKind: pollKind).mapError(TimelineControllerError.timelineProxyError)
}
func editPoll(original eventID: String, question: String, answers: [String], pollKind: Poll.Kind) async -> Result<Void, TimelineControllerError> {
await activeTimeline.editPoll(original: eventID, question: question, answers: answers, pollKind: pollKind).mapError(TimelineControllerError.timelineProxyError)
}
func sendPollResponse(pollStartID: String, answers: [String]) async -> Result<Void, TimelineControllerError> {
await activeTimeline.sendPollResponse(pollStartID: pollStartID, answers: answers).mapError(TimelineControllerError.timelineProxyError)
}
func endPoll(pollStartID: String, text: String) async -> Result<Void, TimelineControllerError> {
await activeTimeline.endPoll(pollStartID: pollStartID, text: text).mapError(TimelineControllerError.timelineProxyError)
}
// MARK: - Private
/// The cancellable used to update the timeline items.
@@ -131,4 +131,17 @@ protocol TimelineControllerProtocol {
audioInfo: AudioInfo,
waveform: [UInt16],
requestHandle: @MainActor (SendAttachmentJoinHandleProtocol) -> Void) async -> Result<Void, TimelineControllerError>
// MARK: - Poll
func createPoll(question: String, answers: [String], pollKind: Poll.Kind) async -> Result<Void, TimelineControllerError>
func editPoll(original eventID: String,
question: String,
answers: [String],
pollKind: Poll.Kind) async -> Result<Void, TimelineControllerError>
func sendPollResponse(pollStartID: String, answers: [String]) async -> Result<Void, TimelineControllerError>
func endPoll(pollStartID: String, text: String) async -> Result<Void, TimelineControllerError>
}
@@ -131,10 +131,10 @@ protocol TimelineProxyProtocol {
answers: [String],
pollKind: Poll.Kind) async -> Result<Void, TimelineProxyError>
func endPoll(pollStartID: String, text: String) async -> Result<Void, TimelineProxyError>
func sendPollResponse(pollStartID: String, answers: [String]) async -> Result<Void, TimelineProxyError>
func endPoll(pollStartID: String, text: String) async -> Result<Void, TimelineProxyError>
func getLoadedReplyDetails(eventID: String) async -> Result<InReplyToDetails, TimelineProxyError>
func buildMessageContentFor(_ message: String,