Skip to content

Commit

Permalink
element-hq/element-ios/issues/5114 - Allow editing poll start events.
Browse files Browse the repository at this point in the history
  • Loading branch information
stefanceriu committed Jan 18, 2022
1 parent 156b2b7 commit ce0c6d8
Show file tree
Hide file tree
Showing 8 changed files with 114 additions and 29 deletions.
1 change: 1 addition & 0 deletions MatrixSDK/Aggregations/MXAggregations.m
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,7 @@ - (void)registerListener
[self.mxSession listenToEvents:^(MXEvent *event, MXTimelineDirection direction, id customObject) {

switch (event.eventType) {
case MXEventTypePollStart:
case MXEventTypeRoomMessage:
if (direction == MXTimelineDirectionForwards
&& [event.relatesTo.relationType isEqualToString:MXEventRelationTypeReplace])
Expand Down
7 changes: 7 additions & 0 deletions MatrixSDK/Data/MXRoom.h
Original file line number Diff line number Diff line change
Expand Up @@ -950,6 +950,13 @@ FOUNDATION_EXPORT NSInteger const kMXRoomAlreadyJoinedErrorCode;
success:(void (^)(NSString *eventId))success
failure:(void (^)(NSError *error))failure;

- (MXHTTPOperation *)sendPollUpdateForEvent:(MXEvent *)pollStartEvent
oldContent:(MXEventContentPollStart *)oldContent
newContent:(MXEventContentPollStart *)newContent
localEcho:(MXEvent **)localEcho
success:(void (^)(NSString *))success
failure:(void (^)(NSError *))failure;

#pragma mark - Location sharing

- (MXHTTPOperation *)sendLocationWithLatitude:(double)latitude
Expand Down
24 changes: 24 additions & 0 deletions MatrixSDK/Data/MXRoom.m
Original file line number Diff line number Diff line change
Expand Up @@ -2382,6 +2382,30 @@ - (MXHTTPOperation *)sendPollEndForEvent:(MXEvent *)pollStartEvent
return [self sendEventOfType:[MXTools eventTypeString:MXEventTypePollEnd] content:content localEcho:localEcho success:success failure:failure];
}

- (MXHTTPOperation *)sendPollUpdateForEvent:(MXEvent *)pollStartEvent
oldContent:(MXEventContentPollStart *)oldContent
newContent:(MXEventContentPollStart *)newContent
localEcho:(MXEvent **)localEcho
success:(void (^)(NSString *))success
failure:(void (^)(NSError *))failure
{
NSParameterAssert(oldContent);
NSParameterAssert(newContent);

NSMutableDictionary *content = [NSMutableDictionary dictionary];

[content addEntriesFromDictionary:oldContent.JSONDictionary];

MXEventContentRelatesTo *relatesTo = [[MXEventContentRelatesTo alloc] initWithRelationType:MXEventRelationTypeReplace
eventId:pollStartEvent.eventId];

[content setObject:relatesTo.JSONDictionary forKey:kMXEventRelationRelatesToKey];

[content setObject:newContent.JSONDictionary forKey:kMXMessageContentKeyNewContent];

return [self sendEventOfType:[MXTools eventTypeString:MXEventTypePollStart] content:content localEcho:localEcho success:success failure:failure];
}

#pragma mark - Location sharing

- (MXHTTPOperation *)sendLocationWithLatitude:(double)latitude
Expand Down
1 change: 1 addition & 0 deletions MatrixSDK/JSONModels/MXEvent.h
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,7 @@ FOUNDATION_EXPORT NSString *const kMXEventRelationRelatesToKey;
FOUNDATION_EXPORT NSString *const MXEventRelationTypeAnnotation; // Reactions
FOUNDATION_EXPORT NSString *const MXEventRelationTypeReference; // Reply
FOUNDATION_EXPORT NSString *const MXEventRelationTypeReplace; // Edition
FOUNDATION_EXPORT NSString *const kMXMessageContentKeyNewContent; // Edited content key
FOUNDATION_EXPORT NSString *const MXEventRelationTypeThread; // Thread

/**
Expand Down
17 changes: 9 additions & 8 deletions MatrixSDK/JSONModels/MXEvent.m
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@
NSString *const MXEventRelationTypeAnnotation = @"m.annotation";
NSString *const MXEventRelationTypeReference = @"m.reference";
NSString *const MXEventRelationTypeReplace = @"m.replace";
NSString *const kMXMessageContentKeyNewContent = @"m.new_content";
// TODO: Replace when the MSC merged
// https://github.com/matrix-org/matrix-doc/pull/3440
NSString *const MXEventRelationTypeThread = @"io.element.thread";
Expand Down Expand Up @@ -476,7 +477,7 @@ - (BOOL)isMediaAttachment

- (BOOL)isEditEvent
{
return self.eventType == MXEventTypeRoomMessage && [self.relatesTo.relationType isEqualToString:MXEventRelationTypeReplace];
return [self.relatesTo.relationType isEqualToString:MXEventRelationTypeReplace];
}

- (BOOL)isReplyEvent
Expand Down Expand Up @@ -649,6 +650,10 @@ - (MXEvent*)prune

- (MXEvent*)editedEventFromReplacementEvent:(MXEvent*)replaceEvent
{
if ([self.eventId isEqualToString:replaceEvent.eventId]) {
return nil;
}

MXEvent *editedEvent;
MXEvent *event = self;
NSDictionary *newContentDict;
Expand All @@ -668,20 +673,16 @@ - (MXEvent*)editedEventFromReplacementEvent:(MXEvent*)replaceEvent
// Reuse its decryption data
replaceEventDecryptionResult = [replaceEvent decryptionResult];
}
else if (event.content[kMXMessageBodyKey] && newContentDict && [newContentDict[kMXMessageTypeKey] isEqualToString:event.content[kMXMessageTypeKey]])
else if (newContentDict)
{
editedEventDict = [event.JSONDictionary mutableCopy];
NSMutableDictionary *editedEventContentDict = [editedEventDict[@"content"] mutableCopy];
editedEventContentDict[kMXMessageBodyKey] = newContentDict[kMXMessageBodyKey];
editedEventContentDict[@"formatted_body"] = newContentDict[@"formatted_body"];
editedEventContentDict[@"format"] = newContentDict[@"format"];
editedEventDict[@"content"] = editedEventContentDict;
editedEventDict[@"content"] = newContentDict;
}

if (editedEventDict)
{
// Use the same type as the replace event
// This is useful for local echoes in e2e room as local echoes are always non encryted/
// This is useful for local echoes in e2e room as local echoes are always non encrypted/
// So, there are switching between "m.room.encrypted" and "m.room.message"
editedEventDict[@"type"] = replaceEvent.isEncrypted ? @"m.room.encrypted" : replaceEvent.type;

Expand Down
59 changes: 41 additions & 18 deletions MatrixSDK/Room/Polls/PollAggregator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,14 @@ public class PollAggregator {

private let session: MXSession
private let room: MXRoom
private let pollStartEvent: MXEvent
private let pollStartEventContent: MXEventContentPollStart
private let pollStartEventId: String
private let pollBuilder: PollBuilder

private var eventListener: Any!
private var pollStartEventContent: MXEventContentPollStart!

private var referenceEventsListener: Any!
private var editEventsListener: Any!

private var events: [MXEvent] = []

public private(set) var poll: PollProtocol! {
Expand All @@ -54,40 +57,60 @@ public class PollAggregator {
public var delegate: PollAggregatorDelegate?

deinit {
room.removeListener(eventListener)
room.removeListener(referenceEventsListener)
session.aggregations.removeListener(editEventsListener as Any)
}

public init(session: MXSession, room: MXRoom, pollStartEvent: MXEvent) throws {
public init(session: MXSession, room: MXRoom, pollStartEventId: String) throws {
self.session = session
self.room = room
self.pollStartEvent = pollStartEvent
self.pollStartEventId = pollStartEventId
self.pollBuilder = PollBuilder()

NotificationCenter.default.addObserver(self, selector: #selector(handleRoomDataFlush), name: NSNotification.Name.mxRoomDidFlushData, object: self.room)

editEventsListener = session.aggregations.listenToEditsUpdate(inRoom: self.room.roomId) { [weak self] event in
guard let self = self,
self.pollStartEventId == event.relatesTo.eventId
else {
return
}

do {
try self.buildPollStartContent()
} catch {
self.delegate?.pollAggregator(self, didFailWithError: PollAggregatorError.invalidPollStartEvent)
}
}

guard let pollStartEventContent = MXEventContentPollStart(fromJSON: pollStartEvent.content) else {
try buildPollStartContent()
}

private func buildPollStartContent() throws {
guard let event = self.session.store.event(withEventId: self.pollStartEventId, inRoom: room.roomId),
let pollStartEventContent = MXEventContentPollStart(fromJSON: event.content) else {
throw PollAggregatorError.invalidPollStartEvent
}

self.pollStartEventContent = pollStartEventContent

pollBuilder = PollBuilder()
self.poll = self.pollBuilder.build(pollStartEventContent: self.pollStartEventContent, events: self.events, currentUserIdentifier: self.session.myUserId)

poll = pollBuilder.build(pollStartEventContent: self.pollStartEventContent, events: self.events, currentUserIdentifier: session.myUserId)

reloadData()

NotificationCenter.default.addObserver(self, selector: #selector(handleRoomDataFlush), name: NSNotification.Name.mxRoomDidFlushData, object: self.room)
self.reloadPollData()
}

@objc private func handleRoomDataFlush(sender: Notification) {
guard let room = sender.object as? MXRoom, room == self.room else {
return
}

reloadData()
reloadPollData()
}

private func reloadData() {
private func reloadPollData() {
delegate?.pollAggregatorDidStartLoading(self)
session.aggregations.referenceEvents(forEvent: pollStartEvent.eventId, inRoom: room.roomId, from: nil, limit: -1) { [weak self] response in

session.aggregations.referenceEvents(forEvent: pollStartEventId, inRoom: room.roomId, from: nil, limit: -1) { [weak self] response in
guard let self = self else {
return
}
Expand All @@ -97,11 +120,11 @@ public class PollAggregator {
self.events.append(contentsOf: response.chunk)

let eventTypes = [kMXEventTypeStringPollResponse, kMXEventTypeStringPollResponseMSC3381, kMXEventTypeStringPollEnd, kMXEventTypeStringPollEndMSC3381]
self.eventListener = self.room.listen(toEventsOfTypes: eventTypes) { [weak self] event, direction, state in
self.referenceEventsListener = self.room.listen(toEventsOfTypes: eventTypes) { [weak self] event, direction, state in
guard let self = self,
let event = event,
let relatedEventId = event.relatesTo?.eventId,
relatedEventId == self.pollStartEvent.eventId else {
relatedEventId == self.pollStartEventId else {
return
}

Expand Down
33 changes: 30 additions & 3 deletions MatrixSDKTests/MXPollAggregatorTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ class MXPollAggregatorTest: XCTestCase {

func testAggregations() {
self.createScenarioForBobAndAlice { bobSession, aliceSession, bobRoom, aliceRoom, pollStartEvent, expectation in
self.pollAggregator = try! PollAggregator(session: bobSession, room: bobRoom, pollStartEvent: pollStartEvent)
self.pollAggregator = try! PollAggregator(session: bobSession, room: bobRoom, pollStartEventId: pollStartEvent.eventId)

let dispatchGroup = DispatchGroup()

Expand Down Expand Up @@ -68,7 +68,7 @@ class MXPollAggregatorTest: XCTestCase {

func testSessionPausing() {
self.createScenarioForBobAndAlice { bobSession, aliceSession, bobRoom, aliceRoom, pollStartEvent, expectation in
self.pollAggregator = try! PollAggregator(session: bobSession, room: bobRoom, pollStartEvent: pollStartEvent)
self.pollAggregator = try! PollAggregator(session: bobSession, room: bobRoom, pollStartEventId: pollStartEvent.eventId)

XCTAssertEqual(self.pollAggregator.poll.answerOptions.first!.count, 1) // One from Alice
XCTAssertEqual(self.pollAggregator.poll.answerOptions.last!.count, 0)
Expand All @@ -92,7 +92,7 @@ class MXPollAggregatorTest: XCTestCase {

func testGappySync() {
self.createScenarioForBobAndAlice { bobSession, aliceSession, bobRoom, aliceRoom, pollStartEvent, expectation in
self.pollAggregator = try! PollAggregator(session: bobSession, room: bobRoom, pollStartEvent: pollStartEvent)
self.pollAggregator = try! PollAggregator(session: bobSession, room: bobRoom, pollStartEventId: pollStartEvent.eventId)

XCTAssertEqual(self.pollAggregator.poll.answerOptions.first!.count, 1) // One from Alice
XCTAssertEqual(self.pollAggregator.poll.answerOptions.last!.count, 0)
Expand Down Expand Up @@ -125,6 +125,33 @@ class MXPollAggregatorTest: XCTestCase {
}
}

func testEditing() {
self.createScenarioForBobAndAlice { bobSession, aliceSession, bobRoom, aliceRoom, pollStartEvent, expectation in
self.pollAggregator = try! PollAggregator(session: bobSession, room: bobRoom, pollStartEventId: pollStartEvent.eventId)

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)

expectation.fulfill()
self.pollAggregator.delegate = nil
})

bobRoom.sendPollUpdate(for: pollStartEvent, oldContent: oldContent, newContent: newContent, localEcho: nil) { result in

} failure: { error in
XCTFail("The operation should not fail - NSError: \(String(describing: error))")
}
}
}

// MARK: - Private

func createScenarioForBobAndAlice(_ readyToTest: @escaping (MXSession, MXSession, MXRoom, MXRoom, MXEvent, XCTestExpectation) -> Void) {
Expand Down
1 change: 1 addition & 0 deletions changelog.d/5114.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Allow editing poll start events.

0 comments on commit ce0c6d8

Please sign in to comment.