diff --git a/MatrixSDK/Room/Polls/PollAggregator.swift b/MatrixSDK/Room/Polls/PollAggregator.swift index be77d13985..5d6324e4e8 100644 --- a/MatrixSDK/Room/Polls/PollAggregator.swift +++ b/MatrixSDK/Room/Polls/PollAggregator.swift @@ -45,6 +45,7 @@ public class PollAggregator { private let pollStartEventId: String private let pollBuilder: PollBuilder + private var pollStartedEvent: MXEvent! private var pollStartEventContent: MXEventContentPollStart! private var referenceEventsListener: Any? @@ -59,7 +60,7 @@ public class PollAggregator { } } - public var delegate: PollAggregatorDelegate? + public weak var delegate: PollAggregatorDelegate? deinit { if let referenceEventsListener = referenceEventsListener { @@ -71,7 +72,7 @@ public class PollAggregator { } } - public convenience init(session: MXSession, room: MXRoom, pollEvent: MXEvent) throws { + public convenience init(session: MXSession, room: MXRoom, pollEvent: MXEvent, delegate: PollAggregatorDelegate? = nil) throws { var pollStartEventId: String? switch pollEvent.eventType { @@ -87,14 +88,15 @@ public class PollAggregator { throw PollAggregatorError.invalidPollStartEvent } - try self.init(session: session, room: room, pollStartEventId: pollStartEventId) + try self.init(session: session, room: room, pollStartEventId: pollStartEventId, delegate: delegate) } - public init(session: MXSession, room: MXRoom, pollStartEventId: String) throws { + public init(session: MXSession, room: MXRoom, pollStartEventId: String, delegate: PollAggregatorDelegate? = nil) throws { self.session = session self.room = room self.pollStartEventId = pollStartEventId self.pollBuilder = PollBuilder() + self.delegate = delegate NotificationCenter.default.addObserver(self, selector: #selector(handleRoomDataFlush), name: .mxRoomDidFlushData, object: self.room) setupEditListener() @@ -125,11 +127,13 @@ public class PollAggregator { throw PollAggregatorError.invalidPollStartEvent } + pollStartedEvent = event pollStartEventContent = eventContent hasBeenEdited = (event.unsignedData.relations?.replace != nil) poll = pollBuilder.build(pollStartEventContent: eventContent, + pollStartEvent: pollStartedEvent, events: events, currentUserIdentifier: session.myUserId, hasBeenEdited: hasBeenEdited) @@ -159,21 +163,25 @@ public class PollAggregator { let eventTypes = [kMXEventTypeStringPollResponse, kMXEventTypeStringPollResponseMSC3381, kMXEventTypeStringPollEnd, kMXEventTypeStringPollEndMSC3381] self.referenceEventsListener = self.room.listen(toEventsOfTypes: eventTypes) { [weak self] event, direction, state in - guard let self = self, - let relatedEventId = event.relatesTo?.eventId, - relatedEventId == self.pollStartEventId else { + guard + let self = self, + let relatedEventId = event.relatesTo?.eventId, + relatedEventId == self.pollStartEventId + else { return } self.events.append(event) self.poll = self.pollBuilder.build(pollStartEventContent: self.pollStartEventContent, + pollStartEvent: self.pollStartedEvent, events: self.events, currentUserIdentifier: self.session.myUserId, hasBeenEdited: self.hasBeenEdited) } as Any self.poll = self.pollBuilder.build(pollStartEventContent: self.pollStartEventContent, + pollStartEvent: self.pollStartedEvent, events: self.events, currentUserIdentifier: self.session.myUserId, hasBeenEdited: self.hasBeenEdited) diff --git a/MatrixSDK/Room/Polls/PollBuilder.swift b/MatrixSDK/Room/Polls/PollBuilder.swift index 44d17127f1..af5fc5e613 100644 --- a/MatrixSDK/Room/Polls/PollBuilder.swift +++ b/MatrixSDK/Room/Polls/PollBuilder.swift @@ -22,9 +22,15 @@ struct PollBuilder { static let maxAnswerOptionCount = 20 } - func build(pollStartEventContent: MXEventContentPollStart, events: [MXEvent], currentUserIdentifier: String, hasBeenEdited: Bool = false) -> PollProtocol { + func build(pollStartEventContent: MXEventContentPollStart, + pollStartEvent: MXEvent, + events: [MXEvent], + currentUserIdentifier: String, + hasBeenEdited: Bool = false) -> PollProtocol { let poll = Poll() + poll.id = pollStartEvent.eventId + poll.startDate = Date(timeIntervalSince1970: Double(pollStartEvent.originServerTs) / 1000) poll.hasBeenEdited = hasBeenEdited poll.hasDecryptionError = events.contains(where: { $0.isEncrypted && $0.clear == nil }) @@ -55,9 +61,11 @@ struct PollBuilder { var filteredEvents = events.filter { event in guard - let eventContent = event.content, event.eventType == __MXEventType.pollResponse, + let eventContent = event.content, + event.eventType == .pollResponse, let response = pollResponseFromEventContent(eventContent), - let _ = response[kMXMessageContentKeyExtensiblePollAnswers] else { + let _ = response[kMXMessageContentKeyExtensiblePollAnswers] + else { return false } diff --git a/MatrixSDK/Room/Polls/PollModels.swift b/MatrixSDK/Room/Polls/PollModels.swift index f8f565cca7..8c007b0fe7 100644 --- a/MatrixSDK/Room/Polls/PollModels.swift +++ b/MatrixSDK/Room/Polls/PollModels.swift @@ -30,9 +30,11 @@ public protocol PollAnswerOptionProtocol { } public protocol PollProtocol { + var id: String { get } var text: String { get } var answerOptions: [PollAnswerOptionProtocol] { get } var kind: PollKind { get } + var startDate: Date { get } var maxAllowedSelections: UInt { get } var isClosed: Bool { get } var totalAnswerCount: UInt { get } @@ -43,23 +45,23 @@ public protocol PollProtocol { class PollAnswerOption: PollAnswerOptionProtocol { var id: String = "" var text: String = "" - var count: UInt = 0 var isWinner: Bool = false var isCurrentUserSelection: Bool = false } class Poll: PollProtocol { + var id: String = "" var text: String = "" var answerOptions: [PollAnswerOptionProtocol] = [] - var kind: PollKind = .disclosed + var startDate: Date = .distantPast var maxAllowedSelections: UInt = 1 var isClosed: Bool = false var hasBeenEdited: Bool = false var hasDecryptionError: Bool = false var totalAnswerCount: UInt { - return self.answerOptions.reduce(0) { $0 + $1.count} + answerOptions.reduce(0) { $0 + $1.count } } } diff --git a/MatrixSDKTests/MXPollAggregatorTests.swift b/MatrixSDKTests/MXPollAggregatorTests.swift index da236d8b26..9862243eda 100644 --- a/MatrixSDKTests/MXPollAggregatorTests.swift +++ b/MatrixSDKTests/MXPollAggregatorTests.swift @@ -18,27 +18,35 @@ import Foundation class MXPollAggregatorTest: XCTestCase { private var matrixSDKTestsData: MatrixSDKTestsData! - private var matrixSDKTestsE2EData: MatrixSDKTestsE2EData! - private var pollAggregator: PollAggregator! + private var delegate: PollAggregatorBlockWrapper! + private var isFirstDelegateUpdate: Bool = true override func setUp() { super.setUp() matrixSDKTestsData = MatrixSDKTestsData() matrixSDKTestsE2EData = MatrixSDKTestsE2EData(matrixSDKTestsData: matrixSDKTestsData) + isFirstDelegateUpdate = true } override func tearDown() { matrixSDKTestsData = nil matrixSDKTestsE2EData = nil + delegate = nil super.tearDown() } func testAggregations() { self.createScenarioForBobAndAlice { bobSession, aliceSession, bobRoom, aliceRoom, pollStartEvent, expectation in - self.pollAggregator = try! PollAggregator(session: bobSession, room: bobRoom, pollStartEventId: pollStartEvent.eventId) + self.delegate = PollAggregatorBlockWrapper(dataUpdateCallback: { pollAggregator in + XCTAssertEqual(self.pollAggregator.poll.answerOptions.first!.count, 2) + XCTAssertEqual(self.pollAggregator.poll.answerOptions.last!.count, 0) + expectation.fulfill() + }) + self.pollAggregator = try! PollAggregator(session: bobSession, room: bobRoom, pollStartEventId: pollStartEvent.eventId) + let dispatchGroup = DispatchGroup() for _ in 1...5 { @@ -58,18 +66,18 @@ class MXPollAggregatorTest: XCTestCase { } dispatchGroup.notify(queue: .main) { - self.pollAggregator.delegate = PollAggregatorBlockWrapper(dataUpdateCallback: { - XCTAssertEqual(self.pollAggregator.poll.answerOptions.first!.count, 2) - XCTAssertEqual(self.pollAggregator.poll.answerOptions.last!.count, 0) - - expectation.fulfill() - }) + self.pollAggregator.delegate = self.delegate } } } func testSessionPausing() { self.createScenarioForBobAndAlice { bobSession, aliceSession, bobRoom, aliceRoom, pollStartEvent, expectation in + let delegate = PollAggregatorBlockWrapper(dataUpdateCallback: { aggregator in + XCTAssertEqual(aggregator.poll.answerOptions.first!.count, 2) + XCTAssertEqual(aggregator.poll.answerOptions.last!.count, 0) + }) + self.pollAggregator = try! PollAggregator(session: bobSession, room: bobRoom, pollStartEventId: pollStartEvent.eventId) XCTAssertEqual(self.pollAggregator.poll.answerOptions.first!.count, 1) // One from Alice @@ -77,10 +85,7 @@ class MXPollAggregatorTest: XCTestCase { bobSession.pause() - self.pollAggregator.delegate = PollAggregatorBlockWrapper(dataUpdateCallback: { - XCTAssertEqual(self.pollAggregator.poll.answerOptions.first!.count, 2) - XCTAssertEqual(self.pollAggregator.poll.answerOptions.last!.count, 0) - }) + self.pollAggregator.delegate = delegate bobRoom.sendPollResponse(for: pollStartEvent, withAnswerIdentifiers: ["1"], threadId:nil, localEcho: nil) { _ in bobSession.resume { @@ -96,6 +101,12 @@ class MXPollAggregatorTest: XCTestCase { self.createScenarioForBobAndAlice { bobSession, aliceSession, bobRoom, aliceRoom, pollStartEvent, expectation in self.pollAggregator = try! PollAggregator(session: bobSession, room: bobRoom, pollStartEventId: pollStartEvent.eventId) + self.delegate = PollAggregatorBlockWrapper(dataUpdateCallback: { aggregator in + XCTAssertEqual(aggregator.poll.answerOptions.first!.count, 2) // One from Bob and one from Alice + XCTAssertEqual(aggregator.poll.answerOptions.last!.count, 1) // One from Alice + expectation.fulfill() + }) + XCTAssertEqual(self.pollAggregator.poll.answerOptions.first!.count, 1) // One from Alice XCTAssertEqual(self.pollAggregator.poll.answerOptions.last!.count, 0) @@ -105,12 +116,7 @@ class MXPollAggregatorTest: XCTestCase { bobRoom.sendPollResponse(for: pollStartEvent, withAnswerIdentifiers: ["1"], threadId:nil, localEcho: nil) { _ in aliceRoom.sendPollResponse(for: pollStartEvent, withAnswerIdentifiers: ["1", "2"], threadId:nil, localEcho: nil) { _ in self.matrixSDKTestsData.for(aliceSession.matrixRestClient, andRoom: aliceRoom.roomId, sendMessages: 50, testCase: self) { - - self.pollAggregator.delegate = PollAggregatorBlockWrapper(dataUpdateCallback: { - XCTAssertEqual(self.pollAggregator.poll.answerOptions.first!.count, 2) // One from Bob and one from Alice - XCTAssertEqual(self.pollAggregator.poll.answerOptions.last!.count, 1) // One from Alice - expectation.fulfill() - }) + self.pollAggregator.delegate = self.delegate bobSession.resume { @@ -131,24 +137,28 @@ class MXPollAggregatorTest: XCTestCase { self.createScenarioForBobAndAlice { bobSession, aliceSession, bobRoom, aliceRoom, pollStartEvent, expectation in self.pollAggregator = try! PollAggregator(session: bobSession, room: bobRoom, pollStartEventId: pollStartEvent.eventId) + self.delegate = PollAggregatorBlockWrapper(dataUpdateCallback: { aggregator in + defer { + self.isFirstDelegateUpdate = false + } + guard self.isFirstDelegateUpdate else { + return + } + XCTAssertEqual(aggregator.poll.text, "Some other question") + XCTAssertEqual(aggregator.poll.answerOptions.count, 2) + XCTAssertTrue(aggregator.poll.hasBeenEdited) + expectation.fulfill() + }) + let oldContent = MXEventContentPollStart(fromJSON: pollStartEvent.content)! let newContent = MXEventContentPollStart(question: "Some other question", kind: oldContent.kind, maxSelections: oldContent.maxSelections, - answerOptions: []) - - self.pollAggregator.delegate = PollAggregatorBlockWrapper(dataUpdateCallback: { - - XCTAssertEqual(self.pollAggregator.poll.text, "Some other question") - XCTAssertEqual(self.pollAggregator.poll.answerOptions.count, 0) - XCTAssertTrue(self.pollAggregator.poll.hasBeenEdited) - - expectation.fulfill() - self.pollAggregator.delegate = nil - }) + answerOptions: oldContent.answerOptions) + bobRoom.sendPollUpdate(for: pollStartEvent, oldContent: oldContent, newContent: newContent, localEcho: nil) { result in - + self.pollAggregator.delegate = self.delegate } failure: { error in XCTFail("The operation should not fail - NSError: \(String(describing: error))") } @@ -200,10 +210,9 @@ class MXPollAggregatorTest: XCTestCase { } private class PollAggregatorBlockWrapper: PollAggregatorDelegate { + let dataUpdateCallback: (PollAggregator) -> (Void) - let dataUpdateCallback: ()->(Void) - - internal init(dataUpdateCallback: @escaping () -> (Void)) { + internal init(dataUpdateCallback: @escaping (PollAggregator) -> (Void)) { self.dataUpdateCallback = dataUpdateCallback } @@ -220,6 +229,6 @@ private class PollAggregatorBlockWrapper: PollAggregatorDelegate { } func pollAggregatorDidUpdateData(_ aggregator: PollAggregator) { - dataUpdateCallback() + dataUpdateCallback(aggregator) } } diff --git a/MatrixSDKTests/MXPollBuilderTests.swift b/MatrixSDKTests/MXPollBuilderTests.swift index 14b63cd819..7bb5ccc1ba 100644 --- a/MatrixSDKTests/MXPollBuilderTests.swift +++ b/MatrixSDKTests/MXPollBuilderTests.swift @@ -25,7 +25,7 @@ class MXPollBuilderTest: XCTestCase { events.append(MXEvent(fromJSON: pollResponseEventWithSender("Alice", answerIdentifiers: ["1"]))!) events.append(MXEvent(fromJSON: pollResponseEventWithSender("Bob", answerIdentifiers: ["1"]))!) - let poll = builder.build(pollStartEventContent: pollStartEventContent(maxSelections: 7), events: events, currentUserIdentifier: "") + let poll = builder.build(pollStartEventContent: pollStartEventContent(maxSelections: 7), pollStartEvent: pollStartedEvent(), events: events, currentUserIdentifier: "") XCTAssertEqual(poll.maxAllowedSelections, 7) XCTAssertEqual(poll.text, "Question") XCTAssertEqual(poll.kind, .disclosed) @@ -37,6 +37,9 @@ class MXPollBuilderTest: XCTestCase { XCTAssertEqual(poll.answerOptions.last?.id, "2") XCTAssertEqual(poll.answerOptions.last?.text, "Second answer") XCTAssertEqual(poll.answerOptions.last?.count, 0) + + XCTAssertEqual(poll.id, "$eventId") + XCTAssertEqual(poll.startDate, Date(timeIntervalSince1970: 0)) } func testSpoiledResponseEmpty() { @@ -44,7 +47,7 @@ class MXPollBuilderTest: XCTestCase { events.append(MXEvent(fromJSON: pollResponseEventWithSender("Bob", timestamp: 0, answerIdentifiers: ["1"]))!) events.append(MXEvent(fromJSON: pollResponseEventWithSender("Bob", timestamp: 1, answerIdentifiers: []))!) - let poll = builder.build(pollStartEventContent: pollStartEventContent(), events: events, currentUserIdentifier: "") + let poll = builder.build(pollStartEventContent: pollStartEventContent(), pollStartEvent: pollStartedEvent(), events: events, currentUserIdentifier: "") XCTAssertEqual(poll.answerOptions.first?.count, 0) XCTAssertEqual(poll.answerOptions.last?.count, 0) } @@ -54,7 +57,7 @@ class MXPollBuilderTest: XCTestCase { events.append(MXEvent(fromJSON: pollResponseEventWithSender("Bob", timestamp: 0, answerIdentifiers: ["1"]))!) events.append(MXEvent(fromJSON: pollResponseEventWithSender("Bob", timestamp: 1, answerIdentifiers: ["1", "2"]))!) - let poll = builder.build(pollStartEventContent: pollStartEventContent(), events: events, currentUserIdentifier: "") + let poll = builder.build(pollStartEventContent: pollStartEventContent(), pollStartEvent: pollStartedEvent(), events: events, currentUserIdentifier: "") XCTAssertEqual(poll.answerOptions.first?.count, 0) XCTAssertEqual(poll.answerOptions.last?.count, 0) } @@ -64,7 +67,7 @@ class MXPollBuilderTest: XCTestCase { events.append(MXEvent(fromJSON: pollResponseEventWithSender("Bob", timestamp: 0, answerIdentifiers: ["1"]))!) events.append(MXEvent(fromJSON: pollResponseEventWithSender("Bob", timestamp: 1, answerIdentifiers: ["1", "2", "3"]))!) - let poll = builder.build(pollStartEventContent: pollStartEventContent(), events: events, currentUserIdentifier: "") + let poll = builder.build(pollStartEventContent: pollStartEventContent(), pollStartEvent: pollStartedEvent(), events: events, currentUserIdentifier: "") XCTAssertEqual(poll.answerOptions.first?.count, 0) XCTAssertEqual(poll.answerOptions.last?.count, 0) } @@ -73,7 +76,7 @@ class MXPollBuilderTest: XCTestCase { var events = [MXEvent]() events.append(MXEvent(fromJSON: pollResponseEventWithSender("Bob", answerIdentifiers: ["1", "1", "1"]))!) - let poll = builder.build(pollStartEventContent: pollStartEventContent(maxSelections: 100), events: events, currentUserIdentifier: "") + let poll = builder.build(pollStartEventContent: pollStartEventContent(maxSelections: 100), pollStartEvent: pollStartedEvent(), events: events, currentUserIdentifier: "") XCTAssertEqual(poll.answerOptions.first?.count, 1) XCTAssertEqual(poll.answerOptions.last?.count, 0) } @@ -82,7 +85,7 @@ class MXPollBuilderTest: XCTestCase { var events = [MXEvent]() events.append(MXEvent(fromJSON: pollResponseEventWithSender("Bob", answerIdentifiers: ["1", "1", "2", "1", "2", "2", "1", "2"]))!) - let poll = builder.build(pollStartEventContent: pollStartEventContent(maxSelections: 100), events: events, currentUserIdentifier: "") + let poll = builder.build(pollStartEventContent: pollStartEventContent(maxSelections: 100), pollStartEvent: pollStartedEvent(), events: events, currentUserIdentifier: "") XCTAssertEqual(poll.answerOptions.first?.count, 1) XCTAssertEqual(poll.answerOptions.last?.count, 1) } @@ -95,7 +98,7 @@ class MXPollBuilderTest: XCTestCase { events.append(MXEvent(fromJSON: pollResponseEventWithSender("Bob", timestamp: 1, answerIdentifiers: []))!) // Too few events.append(MXEvent(fromJSON: pollResponseEventWithSender("Bob", timestamp: 3, answerIdentifiers: ["1", "2"]))!) // Too many - let poll = builder.build(pollStartEventContent: pollStartEventContent(), events: events, currentUserIdentifier: "") + let poll = builder.build(pollStartEventContent: pollStartEventContent(), pollStartEvent: pollStartedEvent(), events: events, currentUserIdentifier: "") XCTAssertEqual(poll.answerOptions.first?.count, 0) XCTAssertEqual(poll.answerOptions.last?.count, 1) } @@ -111,7 +114,7 @@ class MXPollBuilderTest: XCTestCase { events.append(MXEvent(fromJSON: pollResponseEventWithSender("Bob", timestamp: 10, answerIdentifiers: []))!) events.append(MXEvent(fromJSON: pollResponseEventWithSender("Alice", timestamp:10, answerIdentifiers: ["1", "2"]))!) - let poll = builder.build(pollStartEventContent: pollStartEventContent(maxSelections: 10), events: events, currentUserIdentifier: "") + let poll = builder.build(pollStartEventContent: pollStartEventContent(maxSelections: 10), pollStartEvent: pollStartedEvent(), events: events, currentUserIdentifier: "") XCTAssert(poll.isClosed) @@ -126,7 +129,7 @@ class MXPollBuilderTest: XCTestCase { var events = [MXEvent]() events.append(MXEvent(fromJSON: pollResponseEventWithSender("Bob", answerIdentifiers: ["1"]))!) - let poll = builder.build(pollStartEventContent: pollStartEventContent(maxSelections: 7), events: events, currentUserIdentifier: "Bob") + let poll = builder.build(pollStartEventContent: pollStartEventContent(maxSelections: 7), pollStartEvent: pollStartedEvent(), events: events, currentUserIdentifier: "Bob") XCTAssertEqual(poll.answerOptions.first?.isCurrentUserSelection, true) XCTAssertEqual(poll.answerOptions.last?.isCurrentUserSelection, false) } @@ -136,7 +139,7 @@ class MXPollBuilderTest: XCTestCase { events.append(MXEvent(fromJSON: pollResponseEventWithSender("Bob", timestamp: 0, answerIdentifiers: ["1"]))!) events.append(MXEvent(fromJSON: pollResponseEventWithSender("Bob", timestamp: 1, answerIdentifiers: ["2", "1"]))!) - let poll = builder.build(pollStartEventContent: pollStartEventContent(maxSelections: 7), events: events, currentUserIdentifier: "Bob") + let poll = builder.build(pollStartEventContent: pollStartEventContent(maxSelections: 7), pollStartEvent: pollStartedEvent(), events: events, currentUserIdentifier: "Bob") XCTAssertEqual(poll.answerOptions.first?.isCurrentUserSelection, true) XCTAssertEqual(poll.answerOptions.last?.isCurrentUserSelection, true) } @@ -153,6 +156,10 @@ class MXPollBuilderTest: XCTestCase { answerOptions: answerOptions) } + private func pollStartedEvent() -> MXEvent { + .init(fromJSON: pollResponseEventWithSender("Bob", answerIdentifiers: ["1", "2"])) + } + private func pollResponseEventWithSender(_ sender: String, timestamp: Int = 0, answerIdentifiers:[String]) -> [String: Any] { return [ "event_id": "$eventId", diff --git a/changelog.d/pr-1691.change b/changelog.d/pr-1691.change new file mode 100644 index 0000000000..053da27a47 --- /dev/null +++ b/changelog.d/pr-1691.change @@ -0,0 +1 @@ +Polls: add more information in PollProtocol for poll history.