diff --git a/ElementX/Sources/FlowCoordinators/RoomFlowCoordinator.swift b/ElementX/Sources/FlowCoordinators/RoomFlowCoordinator.swift index c7c4393fb..5742a49fc 100644 --- a/ElementX/Sources/FlowCoordinators/RoomFlowCoordinator.swift +++ b/ElementX/Sources/FlowCoordinators/RoomFlowCoordinator.swift @@ -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) diff --git a/ElementX/Sources/Mocks/Generated/GeneratedMocks.swift b/ElementX/Sources/Mocks/Generated/GeneratedMocks.swift index 310e2dec2..bb95e02de 100644 --- a/ElementX/Sources/Mocks/Generated/GeneratedMocks.swift +++ b/ElementX/Sources/Mocks/Generated/GeneratedMocks.swift @@ -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! - var endPollPollStartIDTextReturnValue: Result! { - get { - if Thread.isMainThread { - return endPollPollStartIDTextUnderlyingReturnValue - } else { - var returnValue: Result? = 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)? - - func endPoll(pollStartID: String, text: String) async -> Result { - 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! + var endPollPollStartIDTextReturnValue: Result! { + get { + if Thread.isMainThread { + return endPollPollStartIDTextUnderlyingReturnValue + } else { + var returnValue: Result? = 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)? + + func endPoll(pollStartID: String, text: String) async -> Result { + 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 diff --git a/ElementX/Sources/Screens/Timeline/TimelineInteractionHandler.swift b/ElementX/Sources/Screens/Timeline/TimelineInteractionHandler.swift index 70a0d6a31..f1b3f6a63 100644 --- a/ElementX/Sources/Screens/Timeline/TimelineInteractionHandler.swift +++ b/ElementX/Sources/Screens/Timeline/TimelineInteractionHandler.swift @@ -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 diff --git a/ElementX/Sources/Services/Polls/PollInteractionHandler.swift b/ElementX/Sources/Services/Polls/PollInteractionHandler.swift index adc540f10..504d2c4e9 100644 --- a/ElementX/Sources/Services/Polls/PollInteractionHandler.swift +++ b/ElementX/Sources/Services/Polls/PollInteractionHandler.swift @@ -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 { - 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 { - 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 } diff --git a/ElementX/Sources/Services/Timeline/TimelineController/MockTimelineController.swift b/ElementX/Sources/Services/Timeline/TimelineController/MockTimelineController.swift index 9779bdd56..210ceb968 100644 --- a/ElementX/Sources/Services/Timeline/TimelineController/MockTimelineController.swift +++ b/ElementX/Sources/Services/Timeline/TimelineController/MockTimelineController.swift @@ -244,6 +244,24 @@ class MockTimelineController: TimelineControllerProtocol { return .success(()) } + + // MARK: - Polls + + func createPoll(question: String, answers: [String], pollKind: Poll.Kind) async -> Result { + .success(()) + } + + func editPoll(original eventID: String, question: String, answers: [String], pollKind: Poll.Kind) async -> Result { + .success(()) + } + + func sendPollResponse(pollStartID: String, answers: [String]) async -> Result { + .success(()) + } + + func endPoll(pollStartID: String, text: String) async -> Result { + .success(()) + } // MARK: - UI Test signalling diff --git a/ElementX/Sources/Services/Timeline/TimelineController/TimelineController.swift b/ElementX/Sources/Services/Timeline/TimelineController/TimelineController.swift index a57de03d6..a75346c4b 100644 --- a/ElementX/Sources/Services/Timeline/TimelineController/TimelineController.swift +++ b/ElementX/Sources/Services/Timeline/TimelineController/TimelineController.swift @@ -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 { + 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 { + await activeTimeline.editPoll(original: eventID, question: question, answers: answers, pollKind: pollKind).mapError(TimelineControllerError.timelineProxyError) + } + + func sendPollResponse(pollStartID: String, answers: [String]) async -> Result { + await activeTimeline.sendPollResponse(pollStartID: pollStartID, answers: answers).mapError(TimelineControllerError.timelineProxyError) + } + + func endPoll(pollStartID: String, text: String) async -> Result { + await activeTimeline.endPoll(pollStartID: pollStartID, text: text).mapError(TimelineControllerError.timelineProxyError) + } + // MARK: - Private /// The cancellable used to update the timeline items. diff --git a/ElementX/Sources/Services/Timeline/TimelineController/TimelineControllerProtocol.swift b/ElementX/Sources/Services/Timeline/TimelineController/TimelineControllerProtocol.swift index 8696a0518..5972ffc7a 100644 --- a/ElementX/Sources/Services/Timeline/TimelineController/TimelineControllerProtocol.swift +++ b/ElementX/Sources/Services/Timeline/TimelineController/TimelineControllerProtocol.swift @@ -131,4 +131,17 @@ protocol TimelineControllerProtocol { audioInfo: AudioInfo, waveform: [UInt16], requestHandle: @MainActor (SendAttachmentJoinHandleProtocol) -> Void) async -> Result + + // MARK: - Poll + + func createPoll(question: String, answers: [String], pollKind: Poll.Kind) async -> Result + + func editPoll(original eventID: String, + question: String, + answers: [String], + pollKind: Poll.Kind) async -> Result + + func sendPollResponse(pollStartID: String, answers: [String]) async -> Result + + func endPoll(pollStartID: String, text: String) async -> Result } diff --git a/ElementX/Sources/Services/Timeline/TimelineProxyProtocol.swift b/ElementX/Sources/Services/Timeline/TimelineProxyProtocol.swift index 408bec4a7..d4e6fd127 100644 --- a/ElementX/Sources/Services/Timeline/TimelineProxyProtocol.swift +++ b/ElementX/Sources/Services/Timeline/TimelineProxyProtocol.swift @@ -131,10 +131,10 @@ protocol TimelineProxyProtocol { answers: [String], pollKind: Poll.Kind) async -> Result - func endPoll(pollStartID: String, text: String) async -> Result - func sendPollResponse(pollStartID: String, answers: [String]) async -> Result + func endPoll(pollStartID: String, text: String) async -> Result + func getLoadedReplyDetails(eventID: String) async -> Result func buildMessageContentFor(_ message: String,