Console: support m.room.redaction event in live event stream.

TODO: add the option to redact event from iOS console.
This commit is contained in:
giomfo 2015-01-30 18:47:43 +01:00
parent 614b5bb7c8
commit f4187bf6d6
10 changed files with 274 additions and 46 deletions

View file

@ -126,8 +126,7 @@ static MatrixSDKHandler *sharedHandler = nil;
kMXEventTypeStringRoomName,
kMXEventTypeStringRoomTopic,
kMXEventTypeStringRoomMember,
kMXEventTypeStringRoomMessage,
kMXEventTypeStringRoomRedaction
kMXEventTypeStringRoomMessage
];
}
@ -689,7 +688,9 @@ static MatrixSDKHandler *sharedHandler = nil;
if (!isSubtitle && ![AppSettings sharedSettings].hideRedactedInformation) {
redactedInfo = @"<redacted>";
if ([event.redactedBecause isKindOfClass:[NSDictionary class]]) {
NSString *redactedBy = [roomState memberName:event.redactedBecause[@"user_id"]];
// Consider live room state to resolve redactor name if no roomState is provided
MXRoomState *aRoomState = roomState ? roomState : [self.mxSession roomWithRoomId:event.roomId].state;
NSString *redactedBy = [aRoomState memberName:event.redactedBecause[@"user_id"]];
NSString *redactedReason = event.redactedBecause[@"reason"];
if (redactedReason.length) {
if (redactedBy.length) {
@ -711,10 +712,10 @@ static MatrixSDKHandler *sharedHandler = nil;
// Prepare returned description
NSString *displayText = nil;
// Prepare display name for concerned users
NSString *senderDisplayName = [self senderDisplayNameForEvent:event withRoomState:roomState];
NSString *senderDisplayName = roomState ? [self senderDisplayNameForEvent:event withRoomState:roomState] : event.userId;
NSString *targetDisplayName = nil;
if (event.stateKey) {
targetDisplayName = [roomState memberName:event.stateKey];
targetDisplayName = roomState ? [roomState memberName:event.stateKey] : event.stateKey;
}
switch (event.eventType) {
@ -845,7 +846,7 @@ static MatrixSDKHandler *sharedHandler = nil;
case MXEventTypeRoomCreate: {
NSString *creatorId = event.content[@"creator"];
if (creatorId) {
displayText = [NSString stringWithFormat:@"%@ created the room", [roomState memberName:creatorId]];
displayText = [NSString stringWithFormat:@"%@ created the room", (roomState ? [roomState memberName:creatorId] : creatorId)];
// Append redacted info if any
if (redactedInfo) {
displayText = [NSString stringWithFormat:@"%@ %@", displayText, redactedInfo];
@ -996,7 +997,7 @@ static MatrixSDKHandler *sharedHandler = nil;
break;
}
case MXEventTypeRoomRedaction: {
if ([AppSettings sharedSettings].displayAllEvents) {
if ([self.eventsFilterForMessages indexOfObject:kMXEventTypeStringRoomRedaction] != NSNotFound) {
NSString *eventId = event.redacts;
displayText = [NSString stringWithFormat:@"%@ redacted an event (id: %@)", senderDisplayName, eventId];
} else {

View file

@ -23,6 +23,9 @@ NSString *const kRecentRoomUpdatedByBackPagination = @"kRecentRoomUpdatedByBackP
MXRoom *mxRoom;
id backPaginationListener;
NSOperation *backPaginationOperation;
// Keep reference on last event (used in case of redaction)
MXEvent *lastEvent;
}
@end
@ -36,24 +39,12 @@ NSString *const kRecentRoomUpdatedByBackPagination = @"kRecentRoomUpdatedByBackP
_lastEventOriginServerTs = event.originServerTs;
_unreadCount = isUnread ? 1 : 0;
// Keep ref on event
lastEvent = event;
if (!_lastEventDescription.length) {
// Trigger back pagination to get an event with a non empty description
mxRoom = [mxHandler.mxSession roomWithRoomId:event.roomId];
if (mxRoom) {
backPaginationListener = [mxRoom listenToEventsOfTypes:mxHandler.eventsFilterForMessages onEvent:^(MXEvent *event, MXEventDirection direction, MXRoomState *roomState) {
// Handle only backward events (Sanity check: be sure that the description has not been set by an other way)
if (direction == MXEventDirectionBackwards && !_lastEventDescription.length) {
if ([self updateWithLastEvent:event andRoomState:roomState markAsUnread:NO]) {
// Force recents refresh
[[NSNotificationCenter defaultCenter] postNotificationName:kRecentRoomUpdatedByBackPagination object:nil];
}
}
}];
// Trigger a back pagination by reseting first backState to get room history from live
[mxRoom resetBackState];
[self triggerBackPagination];
}
[self triggerBackPagination];
}
}
return self;
@ -65,12 +56,35 @@ NSString *const kRecentRoomUpdatedByBackPagination = @"kRecentRoomUpdatedByBackP
if (description.length) {
[self cancelBackPagination];
// Update current last event
lastEvent = event;
_lastEventDescription = description;
_lastEventOriginServerTs = event.originServerTs;
if (isUnread) {
_unreadCount ++;
}
return YES;
} else if (_lastEventDescription.length) {
// Here we tried to update the last event with a new live one, but the description of this new one is empty.
// Consider the specific case of redaction event
if (event.eventType == MXEventTypeRoomRedaction) {
// Check whether the redacted event is the current last event
if ([event.redacts isEqualToString:lastEvent.eventId]) {
// Update last event description
MXEvent *redactedEvent = [lastEvent prune];
redactedEvent.redactedBecause = event.originalDictionary;
_lastEventDescription = [[MatrixSDKHandler sharedHandler] displayTextForEvent:redactedEvent withRoomState:nil inSubtitleMode:YES];
if (!_lastEventDescription.length) {
// The current last event must be removed, decrement the unread count (if not null)
if (_unreadCount) {
_unreadCount--;
}
// Trigger back pagination to get an event with a non empty description
[self triggerBackPagination];
}
return YES;
}
}
}
return NO;
}
@ -81,10 +95,33 @@ NSString *const kRecentRoomUpdatedByBackPagination = @"kRecentRoomUpdatedByBackP
- (void)dealloc {
[self cancelBackPagination];
lastEvent = nil;
_lastEventDescription = nil;
}
- (void)triggerBackPagination {
// Add listener if it is not already done
if (!backPaginationListener) {
MatrixSDKHandler *mxHandler = [MatrixSDKHandler sharedHandler];
mxRoom = [mxHandler.mxSession roomWithRoomId:_roomId];
if (mxRoom) {
backPaginationListener = [mxRoom listenToEventsOfTypes:mxHandler.eventsFilterForMessages onEvent:^(MXEvent *event, MXEventDirection direction, MXRoomState *roomState) {
// Handle only backward events (Sanity check: be sure that the description has not been set by an other way)
if (direction == MXEventDirectionBackwards && !_lastEventDescription.length) {
if ([self updateWithLastEvent:event andRoomState:roomState markAsUnread:NO]) {
// Force recents refresh
[[NSNotificationCenter defaultCenter] postNotificationName:kRecentRoomUpdatedByBackPagination object:_roomId];
}
}
}];
// Trigger a back pagination by reseting first backState to get room history from live
[mxRoom resetBackState];
} else {
return;
}
}
if (mxRoom.canPaginate) {
backPaginationOperation = [mxRoom paginateBackMessages:10 complete:^{
backPaginationOperation = nil;
@ -98,6 +135,8 @@ NSString *const kRecentRoomUpdatedByBackPagination = @"kRecentRoomUpdatedByBackP
[self cancelBackPagination];
}];
} else {
// Force recents refresh
[[NSNotificationCenter defaultCenter] postNotificationName:kRecentRoomUpdatedByBackPagination object:_roomId];
[self cancelBackPagination];
}
}

View file

@ -77,6 +77,10 @@ typedef enum : NSUInteger {
// Remove the item defined with this event id
// Return false if the event id is not found
- (BOOL)removeEvent:(NSString*)eventId;
// Looks for the item defined with the same id than the provided event, then updates it with the redacted event.
// Return false if the event id is not found
- (BOOL)updateRedactedEvent:(MXEvent*)redactedEvent;
// Returns the component from the eventId
- (RoomMessageComponent*)componentWithEventId:(NSString *)eventId;
// Return true if the event id is one of the message items

View file

@ -217,6 +217,101 @@ static NSAttributedString *messageSeparator = nil;
}
}
// here the provided eventId has not been found
} else {
// Consider here message with no more than one element
if (messageComponents.count) {
RoomMessageComponent *msgComponent = [messageComponents firstObject];
if ([msgComponent.eventId isEqualToString:eventId]) {
[messageComponents removeObjectAtIndex:0];
// Reset content size
_contentSize = CGSizeZero;
return YES;
}
}
}
return NO;
}
- (BOOL)updateRedactedEvent:(MXEvent*)redactedEvent {
// Check whether the provided event is a redacted one
if (!redactedEvent.redactedBecause) {
return NO;
}
if (_messageType == RoomMessageTypeText) {
NSUInteger index = messageComponents.count;
while (index--) {
RoomMessageComponent* msgComponent = [messageComponents objectAtIndex:index];
if ([msgComponent.eventId isEqualToString:redactedEvent.eventId]) {
// Update component with redacted event, remove it if the resulting string is empty
[msgComponent updateWithRedactedEvent:redactedEvent];
if (!msgComponent.textMessage.length) {
[self removeEvent:redactedEvent.eventId];
} else {
// Compute the SIGNED difference of length (old length - new length)
NSInteger diffLength = msgComponent.range.length - msgComponent.textMessage.length;
// Refresh global attributed string (if any)
if (currentAttributedTextMsg) {
// Replace the component string
NSAttributedString *attributedString = [[NSAttributedString alloc] initWithString:msgComponent.textMessage attributes:[msgComponent stringAttributes]];
[currentAttributedTextMsg replaceCharactersInRange:msgComponent.range withAttributedString:attributedString];
// Update the component range
NSRange updatedRange = msgComponent.range;
updatedRange.length = msgComponent.textMessage.length;
msgComponent.range = updatedRange;
// Reset content size
_contentSize = CGSizeZero;
} // else let the getter "attributedTextMessage" build it
// Adjust range for components displayed after this updated component
for (index++; index < messageComponents.count; index++) {
msgComponent = [messageComponents objectAtIndex:index];
NSRange range = msgComponent.range;
NSAssert((diffLength < 0) || (range.location >= diffLength), @"RoomMessage: the ranges of msg components are corrupted");
range.location -= diffLength;
msgComponent.range = range;
}
// Height of each components should be updated
shouldUpdateComponentsHeight = YES;
}
return YES;
}
}
} else {
// Consider here message related to attachment (This message has no more than one element)
if (messageComponents.count) {
RoomMessageComponent *msgComponent = [messageComponents firstObject];
if ([msgComponent.eventId isEqualToString:redactedEvent.eventId]) {
// Redaction removes the attachment information, the message becomes a text message
_messageType = RoomMessageTypeText;
_attachmentURL = nil;
_attachmentInfo = nil;
_thumbnailURL = nil;
_thumbnailInfo = nil;
_previewURL = nil;
_uploadId = nil;
_uploadProgress = -1;
[msgComponent updateWithRedactedEvent:redactedEvent];
if (!msgComponent.textMessage.length) {
[self removeEvent:redactedEvent.eventId];
} else {
// Set text range
msgComponent.range = NSMakeRange(0, msgComponent.textMessage.length);
// Compute the height of the text component
msgComponent.height = [self rawTextHeight:self.attributedTextMessage];
shouldUpdateComponentsHeight = NO;
// Reset content size
_contentSize = CGSizeZero;
}
return YES;
}
}
}
return NO;
}

View file

@ -37,6 +37,9 @@ typedef enum : NSUInteger {
@property (nonatomic) BOOL isRedactedEvent;
@property (nonatomic) BOOL isIncomingMsg;
// Keep a reference on related event (used in case of redaction)
@property (nonatomic, readonly) MXEvent *event;
// The following properties are defined to store information on component.
// They must be handled by the object which creates the RoomMessageComponent instance.
@property (nonatomic) CGFloat height;
@ -48,4 +51,6 @@ typedef enum : NSUInteger {
- (id)initWithEvent:(MXEvent*)event andRoomState:(MXRoomState*)roomState;
- (NSDictionary *)stringAttributes;
- (void)updateWithRedactedEvent:(MXEvent*)redactedEvent;
@end

View file

@ -48,6 +48,8 @@ NSString *const kFailedEventIdPrefix = @"failedEventId-";
// Set eventId -> set the component style
self.eventId = event.eventId;
// Keep ref on event (used in case of redaction)
_event = event;
} else {
// Ignore this event
self = nil;
@ -56,6 +58,15 @@ NSString *const kFailedEventIdPrefix = @"failedEventId-";
return self;
}
- (void)updateWithRedactedEvent:(MXEvent*)redactedEvent {
MatrixSDKHandler *mxHandler = [MatrixSDKHandler sharedHandler];
// Build text component related to this event (Note: we don't have valid room state here, userId will be used as display name)
_textMessage = [mxHandler displayTextForEvent:redactedEvent withRoomState:nil inSubtitleMode:NO];
_isRedactedEvent = YES;
_event = redactedEvent;
}
- (void)setEventId:(NSString *)eventId {
_eventId = eventId;
@ -72,6 +83,9 @@ NSString *const kFailedEventIdPrefix = @"failedEventId-";
} else {
_style = RoomMessageComponentStyleDefault;
}
// Update stored event id (Note: Only local event Ids are supposed to change)
_event.eventId = eventId;
}
- (NSDictionary*)stringAttributes {

View file

@ -250,8 +250,16 @@
}
unreadCount = 0;
// Check whether redaction event belongs to the listened events list
NSArray *listenedEventTypes = mxHandler.eventsFilterForMessages;
BOOL hideRedactionEvent = ([listenedEventTypes indexOfObject:kMXEventTypeStringRoomRedaction] == NSNotFound);
if (hideRedactionEvent) {
// Add redaction event to the listened events list in order to take into account redaction of the last event in recents.
// (See [RecentRoom updateWithLastEvent:...] for more details)
listenedEventTypes = [listenedEventTypes arrayByAddingObject:kMXEventTypeStringRoomRedaction];
}
// Register recent listener
recentsListener = [mxHandler.mxSession listenToEventsOfTypes:mxHandler.eventsFilterForMessages onEvent:^(MXEvent *event, MXEventDirection direction, MXRoomState *roomState) {
recentsListener = [mxHandler.mxSession listenToEventsOfTypes:listenedEventTypes onEvent:^(MXEvent *event, MXEventDirection direction, MXRoomState *roomState) {
// Consider first live event
if (direction == MXEventDirectionForwards) {
// Check user's membership in live room state (We will remove left rooms from recents)
@ -268,6 +276,9 @@
RecentRoom *recentRoom = [recents objectAtIndex:index];
if ([event.roomId isEqualToString:recentRoom.roomId]) {
isFound = YES;
// Decrement here unreads count for this recent (we will add later the refreshed count)
unreadCount -= recentRoom.unreadCount;
if (isLeft) {
// Remove left room
[recents removeObjectAtIndex:index];
@ -292,13 +303,13 @@
[filteredRecents insertObject:recentRoom atIndex:0];
}
}
if (isUnread) {
unreadCount++;
[self updateTitleView];
}
}
// Refresh global unreads count
unreadCount += recentRoom.unreadCount;
}
// Refresh title
[self updateTitleView];
break;
}
}
@ -357,7 +368,7 @@
if (recents) {
// Add observer to force refresh when a recent last description is updated thanks to back pagination
// (This happens when the current last event description is blank, a back pagination is triggered to display non empty description)
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onRecentRoomUpdatedByBackPagination) name:kRecentRoomUpdatedByBackPagination object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onRecentRoomUpdatedByBackPagination:) name:kRecentRoomUpdatedByBackPagination object:nil];
} else {
// Remove potential listener
if (recentsListener && mxHandler.mxSession) {
@ -369,8 +380,20 @@
[self updateTitleView];
}
- (void)onRecentRoomUpdatedByBackPagination {
- (void)onRecentRoomUpdatedByBackPagination:(NSNotification *)notif{
[self.tableView reloadData];
[self updateTitleView];
if ([notif.object isKindOfClass:[NSString class]]) {
NSString* roomId = notif.object;
// Check whether this room is currently displayed in RoomViewController
if ([[AppDelegate theDelegate].masterTabBarController.visibleRoomId isEqualToString:roomId]) {
// For sanity reason, we have to force a full refresh in order to restore back state of the room
dispatch_async(dispatch_get_main_queue(), ^{
[currentRoomViewController forceRefresh];
});
}
}
}
- (void)updateTitleView {

View file

@ -22,5 +22,7 @@
@property (strong, nonatomic) NSString *roomId;
- (void)forceRefresh;
@end

View file

@ -137,10 +137,11 @@ NSString *const kCmdResetUserPowerLevel = @"/deop";
// Messages
@property (strong, nonatomic)NSMutableArray *messages;
@property (strong, nonatomic)id messagesListener;
@property (strong, nonatomic)id redactionListener;
@end
@implementation RoomViewController
@synthesize messages, messagesListener;
@synthesize messages;
- (void)viewDidLoad {
[super viewDidLoad];
@ -211,9 +212,11 @@ NSString *const kCmdResetUserPowerLevel = @"/deop";
[self hideAttachmentView];
messages = nil;
if (messagesListener) {
[self.mxRoom removeListener:messagesListener];
messagesListener = nil;
if (_messagesListener) {
[self.mxRoom removeListener:_messagesListener];
_messagesListener = nil;
[self.mxRoom removeListener:_redactionListener];
_redactionListener = nil;
[[AppSettings sharedSettings] removeObserver:self forKeyPath:@"hideRedactedInformation"];
[[AppSettings sharedSettings] removeObserver:self forKeyPath:@"hideUnsupportedEvents"];
[[MatrixSDKHandler sharedHandler] removeObserver:self forKeyPath:@"status"];
@ -439,18 +442,22 @@ NSString *const kCmdResetUserPowerLevel = @"/deop";
}
}
#pragma mark - room ID
#pragma mark -
- (void)setRoomId:(NSString *)roomId {
if ([self.roomId isEqualToString:roomId] == NO) {
_roomId = roomId;
// Reload room data here
[self configureView];
// Update UI
[self updateUI];
[self forceRefresh];
}
}
- (void)forceRefresh {
// Reload room data here
[self configureView];
// Update UI
[self updateUI];
}
#pragma mark - UIGestureRecognizer delegate
- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer {
@ -543,9 +550,11 @@ NSString *const kCmdResetUserPowerLevel = @"/deop";
if (self.mxRoom) {
// Remove potential listener
if (messagesListener){
[self.mxRoom removeListener:messagesListener];
messagesListener = nil;
if (_messagesListener){
[self.mxRoom removeListener:_messagesListener];
_messagesListener = nil;
[self.mxRoom removeListener:_redactionListener];
_redactionListener = nil;
[[AppSettings sharedSettings] removeObserver:self forKeyPath:@"hideRedactedInformation"];
[[AppSettings sharedSettings] removeObserver:self forKeyPath:@"hideUnsupportedEvents"];
[[MatrixSDKHandler sharedHandler] removeObserver:self forKeyPath:@"status"];
@ -603,7 +612,7 @@ NSString *const kCmdResetUserPowerLevel = @"/deop";
[mxHandler addObserver:self forKeyPath:@"status" options:0 context:nil];
[mxHandler addObserver:self forKeyPath:@"isResumeDone" options:0 context:nil];
// Register a listener to handle messages
messagesListener = [self.mxRoom listenToEventsOfTypes:mxHandler.eventsFilterForMessages onEvent:^(MXEvent *event, MXEventDirection direction, MXRoomState *roomState) {
_messagesListener = [self.mxRoom listenToEventsOfTypes:mxHandler.eventsFilterForMessages onEvent:^(MXEvent *event, MXEventDirection direction, MXRoomState *roomState) {
// Handle first live events
if (direction == MXEventDirectionForwards) {
// Check user's membership in live room state (Indeed we have to go back on recents when user leaves, or is kicked/banned)
@ -758,6 +767,42 @@ NSString *const kCmdResetUserPowerLevel = @"/deop";
}
}];
// Register a listener to handle redaction in live stream
_redactionListener = [self.mxRoom listenToEventsOfTypes:@[kMXEventTypeStringRoomRedaction] onEvent:^(MXEvent *redactionEvent, MXEventDirection direction, MXRoomState *roomState) {
// Consider only live redaction events
if (direction == MXEventDirectionForwards) {
// Update Table on processing queue
dispatch_async(mxHandler.processingQueue, ^{
// Check whether a message contains the redacted event
RoomMessage *message = [self messageWithEventId:redactionEvent.redacts];
if (message) {
// Retrieve the original to redact it
MXEvent *originalEvent = [message componentWithEventId:redactionEvent.redacts].event;
MXEvent *redactedEvent = [originalEvent prune];
redactedEvent.redactedBecause = redactionEvent.originalDictionary;
if (redactedEvent.isState) {
// FIXME: The room state must be refreshed here since this redacted event.
NSLog(@"CAUTION: a state event has been redacted, room state may not be up to date");
}
// We replace the event with the redacted one
[message updateRedactedEvent:redactedEvent];
if (!message.components.count) {
[self removeMessage:message];
}
}
// Refresh table display except if a back pagination is in progress
if (!isBackPaginationInProgress) {
dispatch_async(dispatch_get_main_queue(), ^{
[self.messagesTableView reloadData];
});
}
});
}
}];
// Add typing notification listener
typingNotifListener = [self.mxRoom listenToEventsOfTypes:@[kMXEventTypeStringTypingNotification] onEvent:^(MXEvent *event, MXEventDirection direction, MXRoomState *roomState) {
// Handle only live events
@ -2356,7 +2401,7 @@ NSString *const kCmdResetUserPowerLevel = @"/deop";
if (cellView) {
RoomMessage* roomMessage = ((RoomMessageTableCell*)cellView).message;
RoomMessageComponent* component =[roomMessage componentWithEventId:eventID];
RoomMessageComponent* component = [roomMessage componentWithEventId:eventID];
// sanity check
if (component) {

View file

@ -993,7 +993,7 @@ NSString* const kCommandsDescriptionText = @"The following commands are availabl
notificationsCell.settingLabel.text = @"Enable In-App notifications";
notificationsCell.settingSwitch.on = [[AppSettings sharedSettings] enableInAppNotifications];
inAppNotificationsSwitch = notificationsCell.settingSwitch;
} else /* SETTINGS_SECTION_NOTIFICATIONS_PUSH_NOTIFICATION_INDEX */{
} else /* enablePushNotifRowIndex */{
notificationsCell.settingLabel.text = @"Enable push notifications";
notificationsCell.settingSwitch.on = [[APNSHandler sharedHandler] isActive];
apnsNotificationsSwitch = notificationsCell.settingSwitch;