diff --git a/Riot/AppDelegate.m b/Riot/AppDelegate.m index b30f8bbe0..7c7ece2b4 100644 --- a/Riot/AppDelegate.m +++ b/Riot/AppDelegate.m @@ -1108,37 +1108,42 @@ NSString *const kAppDelegateNetworkStatusDidChangeNotification = @"kAppDelegateN { NSArray* mxAccounts = [MXKAccountManager sharedManager].activeAccounts; MXKRoomDataSource* roomDataSource = nil; + MXKRoomDataSourceManager* manager; for (MXKAccount* account in mxAccounts) { MXRoom* room = [account.mxSession roomWithRoomId:roomId]; if (room) { - MXKRoomDataSourceManager* manager = [MXKRoomDataSourceManager sharedManagerForMatrixSession:account.mxSession]; + manager = [MXKRoomDataSourceManager sharedManagerForMatrixSession:account.mxSession]; if (manager) { - roomDataSource = [manager roomDataSourceForRoom:roomId create:YES]; + break; } - break; } } - if (roomDataSource == nil) + if (manager == nil) { NSLog(@"[AppDelegate][Push] handleActionWithIdentifier: room with id %@ not found", roomId); } else { - NSString* responseText = [responseInfo objectForKey:UIUserNotificationActionResponseTypedTextKey]; - if (responseText != nil && responseText.length != 0) - { - NSLog(@"[AppDelegate][Push] handleActionWithIdentifier: sending message to room: %@", roomId); - [roomDataSource sendTextMessage:responseText success:^(NSString* eventId) {} failure:^(NSError* error) { - UILocalNotification* failureNotification = [[UILocalNotification alloc] init]; - failureNotification.alertBody = NSLocalizedStringFromTable(@"room_event_failed_to_send", @"Vector", nil); - failureNotification.userInfo = notification.userInfo; - [[UIApplication sharedApplication] scheduleLocalNotification: failureNotification]; - NSLog(@"[AppDelegate][Push] handleActionWithIdentifier: error sending text message: %@", error); - }]; - } + [manager roomDataSourceForRoom:roomId create:YES onComplete:^(MXKRoomDataSource *roomDataSource) { + NSString* responseText = [responseInfo objectForKey:UIUserNotificationActionResponseTypedTextKey]; + if (responseText != nil && responseText.length != 0) + { + NSLog(@"[AppDelegate][Push] handleActionWithIdentifier: sending message to room: %@", roomId); + [roomDataSource sendTextMessage:responseText success:^(NSString* eventId) {} failure:^(NSError* error) { + UILocalNotification* failureNotification = [[UILocalNotification alloc] init]; + failureNotification.alertBody = NSLocalizedStringFromTable(@"room_event_failed_to_send", @"Vector", nil); + failureNotification.userInfo = notification.userInfo; + [[UIApplication sharedApplication] scheduleLocalNotification: failureNotification]; + NSLog(@"[AppDelegate][Push] handleActionWithIdentifier: error sending text message: %@", error); + }]; + } + + completionHandler(); + }]; + return; } } } @@ -1308,7 +1313,7 @@ NSString *const kAppDelegateNetworkStatusDidChangeNotification = @"kAppDelegateN NSLog(@"[AppDelegate][Push] handleLocalNotificationsForAccount: eventsToNotify: %@", eventsToNotify[@(account.mxSession.hash)]); NSLog(@"[AppDelegate][Push] handleLocalNotificationsForAccount: incomingPushEventIds: %@", self.incomingPushEventIds[@(account.mxSession.hash)]); - NSUInteger scheduledNotifications = 0; + __block NSUInteger scheduledNotifications = 0; // The call invite are handled here only when the callkit is not active. BOOL isCallKitActive = [MXCallKitAdapter callKitAvailable] && [MXKAppSettings standardAppSettings].isCallKitEnabled; @@ -1385,54 +1390,56 @@ NSString *const kAppDelegateNetworkStatusDidChangeNotification = @"kAppDelegateN // Prepare the local notification MXPushRule *rule = eventDict[@"push_rule"]; - - NSString *notificationBody = [self notificationBodyForEvent:event pushRule:rule inAccount:account]; - if (notificationBody) - { - // Printf style escape characters are stripped from the string prior to display; - // to include a percent symbol (%) in the message, use two percent symbols (%%). - notificationBody = [notificationBody stringByReplacingOccurrencesOfString:@"%" withString:@"%%"]; - - UILocalNotification *eventNotification = [[UILocalNotification alloc] init]; - eventNotification.alertBody = notificationBody; - eventNotification.userInfo = @{ - @"type": @"full", - @"room_id": event.roomId, - @"event_id": event.eventId, - @"user_id": account.mxCredentials.userId - }; - - BOOL isNotificationContentShown = !event.isEncrypted || RiotSettings.shared.showDecryptedContentInNotifications; - - if ((event.eventType == MXEventTypeRoomMessage || event.eventType == MXEventTypeRoomEncrypted) && isNotificationContentShown) - { - eventNotification.category = @"QUICK_REPLY"; - } - // Set sound name based on the value provided in action of MXPushRule - for (MXPushRuleAction *action in rule.actions) + [self notificationBodyForEvent:event pushRule:rule inAccount:account onComplete:^(NSString * _Nullable notificationBody) { + + if (notificationBody) { - if (action.actionType == MXPushRuleActionTypeSetTweak) + // Printf style escape characters are stripped from the string prior to display; + // to include a percent symbol (%) in the message, use two percent symbols (%%). + notificationBody = [notificationBody stringByReplacingOccurrencesOfString:@"%" withString:@"%%"]; + + UILocalNotification *eventNotification = [[UILocalNotification alloc] init]; + eventNotification.alertBody = notificationBody; + eventNotification.userInfo = @{ + @"type": @"full", + @"room_id": event.roomId, + @"event_id": event.eventId, + @"user_id": account.mxCredentials.userId + }; + + BOOL isNotificationContentShown = !event.isEncrypted || RiotSettings.shared.showDecryptedContentInNotifications; + + if ((event.eventType == MXEventTypeRoomMessage || event.eventType == MXEventTypeRoomEncrypted) && isNotificationContentShown) { - if ([action.parameters[@"set_tweak"] isEqualToString:@"sound"]) + eventNotification.category = @"QUICK_REPLY"; + } + + // Set sound name based on the value provided in action of MXPushRule + for (MXPushRuleAction *action in rule.actions) + { + if (action.actionType == MXPushRuleActionTypeSetTweak) { - NSString *soundName = action.parameters[@"value"]; - if ([soundName isEqualToString:@"default"]) - soundName = @"message.mp3"; - - eventNotification.soundName = soundName; + if ([action.parameters[@"set_tweak"] isEqualToString:@"sound"]) + { + NSString *soundName = action.parameters[@"value"]; + if ([soundName isEqualToString:@"default"]) + soundName = @"message.mp3"; + + eventNotification.soundName = soundName; + } } } - } - NSLog(@"[AppDelegate][Push] handleLocalNotificationsForAccount: Display notification for event %@", event.eventId); - [[UIApplication sharedApplication] scheduleLocalNotification:eventNotification]; - scheduledNotifications++; - } - else - { - NSLog(@"[AppDelegate][Push] handleLocalNotificationsForAccount: Skip event with empty generated notificationBody. Event id: %@", event.eventId); - } + NSLog(@"[AppDelegate][Push] handleLocalNotificationsForAccount: Display notification for event %@", event.eventId); + [[UIApplication sharedApplication] scheduleLocalNotification:eventNotification]; + scheduledNotifications++; + } + else + { + NSLog(@"[AppDelegate][Push] handleLocalNotificationsForAccount: Skip event with empty generated notificationBody. Event id: %@", event.eventId); + } + }]; } } @@ -1441,120 +1448,130 @@ NSString *const kAppDelegateNetworkStatusDidChangeNotification = @"kAppDelegateN [eventsArray removeAllObjects]; } -- (nullable NSString *)notificationBodyForEvent:(MXEvent *)event pushRule:(MXPushRule*)rule inAccount:(MXKAccount*)account +- (void)notificationBodyForEvent:(MXEvent *)event pushRule:(MXPushRule*)rule inAccount:(MXKAccount*)account onComplete:(void (^)(NSString * _Nullable notificationBody))onComplete; { if (!event.content || !event.content.count) { NSLog(@"[AppDelegate][Push] notificationBodyForEvent: empty event content"); - return nil; + onComplete (nil); + return; } MXRoom *room = [account.mxSession roomWithRoomId:event.roomId]; - MXRoomState *roomState = room.state; - - NSString *notificationBody; - NSString *eventSenderName = [roomState.members memberName:event.sender]; - - if (event.eventType == MXEventTypeRoomMessage || event.eventType == MXEventTypeRoomEncrypted) + if (!room) { - if (room.isMentionsOnly) + NSLog(@"[AppDelegate][Push] notificationBodyForEvent: Unknown room"); + onComplete (nil); + return; + } + + [room members:^(MXRoomMembers *roomMembers) { + + NSString *notificationBody; + NSString *eventSenderName = [roomMembers memberName:event.sender]; + + if (event.eventType == MXEventTypeRoomMessage || event.eventType == MXEventTypeRoomEncrypted) { - // A local notification will be displayed only for highlighted notification. - BOOL isHighlighted = NO; - - // Check whether is there an highlight tweak on it - for (MXPushRuleAction *ruleAction in rule.actions) + if (room.isMentionsOnly) { - if (ruleAction.actionType == MXPushRuleActionTypeSetTweak) + // A local notification will be displayed only for highlighted notification. + BOOL isHighlighted = NO; + + // Check whether is there an highlight tweak on it + for (MXPushRuleAction *ruleAction in rule.actions) { - if ([ruleAction.parameters[@"set_tweak"] isEqualToString:@"highlight"]) + if (ruleAction.actionType == MXPushRuleActionTypeSetTweak) { - // Check the highlight tweak "value" - // If not present, highlight. Else check its value before highlighting - if (nil == ruleAction.parameters[@"value"] || YES == [ruleAction.parameters[@"value"] boolValue]) + if ([ruleAction.parameters[@"set_tweak"] isEqualToString:@"highlight"]) { - isHighlighted = YES; - break; + // Check the highlight tweak "value" + // If not present, highlight. Else check its value before highlighting + if (nil == ruleAction.parameters[@"value"] || YES == [ruleAction.parameters[@"value"] boolValue]) + { + isHighlighted = YES; + break; + } } } } + + if (!isHighlighted) + { + // Ignore this notif. + NSLog(@"[AppDelegate][Push] notificationBodyForEvent: Ignore non highlighted notif in mentions only room"); + onComplete(nil); + return; + } } - - if (!isHighlighted) + + NSString *msgType = event.content[@"msgtype"]; + NSString *content = event.content[@"body"]; + + if (event.isEncrypted && !RiotSettings.shared.showDecryptedContentInNotifications) { - // Ignore this notif. - NSLog(@"[AppDelegate][Push] notificationBodyForEvent: Ignore non highlighted notif in mentions only room"); - return nil; + // Hide the content + msgType = nil; + } + + NSString *roomDisplayName = room.summary.displayname; + + // Display the room name only if it is different than the sender name + if (roomDisplayName.length && ![roomDisplayName isEqualToString:eventSenderName]) + { + if ([msgType isEqualToString:@"m.text"]) + notificationBody = [NSString stringWithFormat:NSLocalizedString(@"MSG_FROM_USER_IN_ROOM_WITH_CONTENT", nil), eventSenderName,roomDisplayName, content]; + else if ([msgType isEqualToString:@"m.emote"]) + notificationBody = [NSString stringWithFormat:NSLocalizedString(@"ACTION_FROM_USER_IN_ROOM", nil), roomDisplayName, eventSenderName, content]; + else if ([msgType isEqualToString:@"m.image"]) + notificationBody = [NSString stringWithFormat:NSLocalizedString(@"IMAGE_FROM_USER_IN_ROOM", nil), eventSenderName, content, roomDisplayName]; + else + // Encrypted messages falls here + notificationBody = [NSString stringWithFormat:NSLocalizedString(@"MSG_FROM_USER_IN_ROOM", nil), eventSenderName, roomDisplayName]; + } + else + { + if ([msgType isEqualToString:@"m.text"]) + notificationBody = [NSString stringWithFormat:NSLocalizedString(@"MSG_FROM_USER_WITH_CONTENT", nil), eventSenderName, content]; + else if ([msgType isEqualToString:@"m.emote"]) + notificationBody = [NSString stringWithFormat:NSLocalizedString(@"ACTION_FROM_USER", nil), eventSenderName, content]; + else if ([msgType isEqualToString:@"m.image"]) + notificationBody = [NSString stringWithFormat:NSLocalizedString(@"IMAGE_FROM_USER", nil), eventSenderName, content]; + else + // Encrypted messages falls here + notificationBody = [NSString stringWithFormat:NSLocalizedString(@"MSG_FROM_USER", nil), eventSenderName]; } } - - NSString *msgType = event.content[@"msgtype"]; - NSString *content = event.content[@"body"]; - - if (event.isEncrypted && !RiotSettings.shared.showDecryptedContentInNotifications) + else if (event.eventType == MXEventTypeCallInvite) { - // Hide the content - msgType = nil; - } - - NSString *roomDisplayName = room.summary.displayname; - - // Display the room name only if it is different than the sender name - if (roomDisplayName.length && ![roomDisplayName isEqualToString:eventSenderName]) - { - if ([msgType isEqualToString:@"m.text"]) - notificationBody = [NSString stringWithFormat:NSLocalizedString(@"MSG_FROM_USER_IN_ROOM_WITH_CONTENT", nil), eventSenderName,roomDisplayName, content]; - else if ([msgType isEqualToString:@"m.emote"]) - notificationBody = [NSString stringWithFormat:NSLocalizedString(@"ACTION_FROM_USER_IN_ROOM", nil), roomDisplayName, eventSenderName, content]; - else if ([msgType isEqualToString:@"m.image"]) - notificationBody = [NSString stringWithFormat:NSLocalizedString(@"IMAGE_FROM_USER_IN_ROOM", nil), eventSenderName, content, roomDisplayName]; + NSString *sdp = event.content[@"offer"][@"sdp"]; + BOOL isVideoCall = [sdp rangeOfString:@"m=video"].location != NSNotFound; + + if (!isVideoCall) + notificationBody = [NSString stringWithFormat:NSLocalizedString(@"VOICE_CALL_FROM_USER", nil), eventSenderName]; else - // Encrypted messages falls here + notificationBody = [NSString stringWithFormat:NSLocalizedString(@"VIDEO_CALL_FROM_USER", nil), eventSenderName]; + } + else if (event.eventType == MXEventTypeRoomMember) + { + NSString *roomDisplayName = room.summary.displayname; + + if (roomDisplayName.length && ![roomDisplayName isEqualToString:eventSenderName]) + notificationBody = [NSString stringWithFormat:NSLocalizedString(@"USER_INVITE_TO_NAMED_ROOM", nil), eventSenderName, roomDisplayName]; + else + notificationBody = [NSString stringWithFormat:NSLocalizedString(@"USER_INVITE_TO_CHAT", nil), eventSenderName]; + } + else if (event.eventType == MXEventTypeSticker) + { + NSString *roomDisplayName = room.summary.displayname; + + if (roomDisplayName.length && ![roomDisplayName isEqualToString:eventSenderName]) notificationBody = [NSString stringWithFormat:NSLocalizedString(@"MSG_FROM_USER_IN_ROOM", nil), eventSenderName, roomDisplayName]; - } - else - { - if ([msgType isEqualToString:@"m.text"]) - notificationBody = [NSString stringWithFormat:NSLocalizedString(@"MSG_FROM_USER_WITH_CONTENT", nil), eventSenderName, content]; - else if ([msgType isEqualToString:@"m.emote"]) - notificationBody = [NSString stringWithFormat:NSLocalizedString(@"ACTION_FROM_USER", nil), eventSenderName, content]; - else if ([msgType isEqualToString:@"m.image"]) - notificationBody = [NSString stringWithFormat:NSLocalizedString(@"IMAGE_FROM_USER", nil), eventSenderName, content]; else - // Encrypted messages falls here notificationBody = [NSString stringWithFormat:NSLocalizedString(@"MSG_FROM_USER", nil), eventSenderName]; } - } - else if (event.eventType == MXEventTypeCallInvite) - { - NSString *sdp = event.content[@"offer"][@"sdp"]; - BOOL isVideoCall = [sdp rangeOfString:@"m=video"].location != NSNotFound; - - if (!isVideoCall) - notificationBody = [NSString stringWithFormat:NSLocalizedString(@"VOICE_CALL_FROM_USER", nil), eventSenderName]; - else - notificationBody = [NSString stringWithFormat:NSLocalizedString(@"VIDEO_CALL_FROM_USER", nil), eventSenderName]; - } - else if (event.eventType == MXEventTypeRoomMember) - { - NSString *roomDisplayName = room.summary.displayname; - - if (roomDisplayName.length && ![roomDisplayName isEqualToString:eventSenderName]) - notificationBody = [NSString stringWithFormat:NSLocalizedString(@"USER_INVITE_TO_NAMED_ROOM", nil), eventSenderName, roomDisplayName]; - else - notificationBody = [NSString stringWithFormat:NSLocalizedString(@"USER_INVITE_TO_CHAT", nil), eventSenderName]; - } - else if (event.eventType == MXEventTypeSticker) - { - NSString *roomDisplayName = room.summary.displayname; - - if (roomDisplayName.length && ![roomDisplayName isEqualToString:eventSenderName]) - notificationBody = [NSString stringWithFormat:NSLocalizedString(@"MSG_FROM_USER_IN_ROOM", nil), eventSenderName, roomDisplayName]; - else - notificationBody = [NSString stringWithFormat:NSLocalizedString(@"MSG_FROM_USER", nil), eventSenderName]; - } - - return notificationBody; + + onComplete(notificationBody); + }]; } /** @@ -2888,7 +2905,9 @@ NSString *const kAppDelegateNetworkStatusDidChangeNotification = @"kAppDelegateN - (void)enableLocalNotificationsFromMatrixSession:(MXSession*)mxSession { // Prepare listener block. + MXWeakify(self); MXOnNotification notificationListenerBlock = ^(MXEvent *event, MXRoomState *roomState, MXPushRule *rule) { + MXStrongifyAndReturnIfNil(self); // Ignore this event if the app is not running in background. if ([[UIApplication sharedApplication] applicationState] != UIApplicationStateBackground) @@ -2910,7 +2929,11 @@ NSString *const kAppDelegateNetworkStatusDidChangeNotification = @"kAppDelegateN } // Add it to the list of the events to notify. - [eventsToNotify[@(mxSession.hash)] addObject:@{@"event_id": event.eventId, @"room_id": event.roomId, @"push_rule": rule}]; + [self->eventsToNotify[@(mxSession.hash)] addObject:@{ + @"event_id": event.eventId, + @"room_id": event.roomId, + @"push_rule": rule + }]; } else { diff --git a/Riot/Managers/Widgets/WidgetManager.h b/Riot/Managers/Widgets/WidgetManager.h index 315e363e8..a95ba83e1 100644 --- a/Riot/Managers/Widgets/WidgetManager.h +++ b/Riot/Managers/Widgets/WidgetManager.h @@ -74,7 +74,7 @@ WidgetManagerErrorCode; @param room the room to check. @return a list of widgets. */ -- (NSArray *)widgetsInRoom:(MXRoom*)room; +- (NSArray *)widgetsInRoom:(MXRoom*)room withRoomState:(MXRoomState*)roomState; /** List all active widgets of a given type in a room. @@ -83,7 +83,7 @@ WidgetManagerErrorCode; @param room the room to check. @return a list of widgets. */ -- (NSArray *)widgetsOfTypes:(NSArray*)widgetTypes inRoom:(MXRoom*)room; +- (NSArray *)widgetsOfTypes:(NSArray*)widgetTypes inRoom:(MXRoom*)room withRoomState:(MXRoomState*)roomState; /** List all active widgets of a given type in a room, excluding some types. @@ -92,7 +92,7 @@ WidgetManagerErrorCode; @param room the room to check. @return a list of widgets. */ -- (NSArray *)widgetsNotOfTypes:(NSArray*)notWidgetTypes inRoom:(MXRoom*)room; +- (NSArray *)widgetsNotOfTypes:(NSArray*)notWidgetTypes inRoom:(MXRoom*)room withRoomState:(MXRoomState*)roomState; /** List all widgets of an account. @@ -157,7 +157,7 @@ WidgetManagerErrorCode; @return a MXHTTPOperation instance. */ - (MXHTTPOperation *)closeWidget:(NSString*)widgetId inRoom:(MXRoom*)room - success:(void (^)())success + success:(void (^)(void))success failure:(void (^)(NSError *error))failure; diff --git a/Riot/Managers/Widgets/WidgetManager.m b/Riot/Managers/Widgets/WidgetManager.m index cdbfb9835..360a359da 100644 --- a/Riot/Managers/Widgets/WidgetManager.m +++ b/Riot/Managers/Widgets/WidgetManager.m @@ -84,29 +84,29 @@ NSString *const WidgetManagerErrorDomain = @"WidgetManagerErrorDomain"; return self; } -- (NSArray *)widgetsInRoom:(MXRoom *)room +- (NSArray *)widgetsInRoom:(MXRoom*)room withRoomState:(MXRoomState*)roomState { - return [self widgetsOfTypes:nil inRoom:room]; + return [self widgetsOfTypes:nil inRoom:room withRoomState:roomState]; } -- (NSArray *)widgetsOfTypes:(NSArray*)widgetTypes inRoom:(MXRoom*)room; +- (NSArray *)widgetsOfTypes:(NSArray*)widgetTypes inRoom:(MXRoom*)room withRoomState:(MXRoomState*)roomState { - return [self widgetsOfTypes:widgetTypes butNotTypesOf:nil inRoom:room]; + return [self widgetsOfTypes:widgetTypes butNotTypesOf:nil inRoom:room withRoomState:roomState]; } -- (NSArray *)widgetsNotOfTypes:(NSArray*)notWidgetTypes inRoom:(MXRoom*)room +- (NSArray *)widgetsNotOfTypes:(NSArray*)notWidgetTypes inRoom:(MXRoom*)room withRoomState:(MXRoomState*)roomState; { - return [self widgetsOfTypes:nil butNotTypesOf:notWidgetTypes inRoom:room]; + return [self widgetsOfTypes:nil butNotTypesOf:notWidgetTypes inRoom:room withRoomState:roomState]; } -- (NSArray *)widgetsOfTypes:(NSArray*)widgetTypes butNotTypesOf:(NSArray*)notWidgetTypes inRoom:(MXRoom*)room; +- (NSArray *)widgetsOfTypes:(NSArray*)widgetTypes butNotTypesOf:(NSArray*)notWidgetTypes inRoom:(MXRoom*)room withRoomState:(MXRoomState*)roomState; { // Widget id -> widget NSMutableDictionary *widgets = [NSMutableDictionary dictionary]; // Get all widgets state events in the room - NSMutableArray *widgetEvents = [NSMutableArray arrayWithArray:[room.state stateEventsWithType:kWidgetMatrixEventTypeString]]; - [widgetEvents addObjectsFromArray:[room.state stateEventsWithType:kWidgetModularEventTypeString]]; + NSMutableArray *widgetEvents = [NSMutableArray arrayWithArray:[roomState stateEventsWithType:kWidgetMatrixEventTypeString]]; + [widgetEvents addObjectsFromArray:[roomState stateEventsWithType:kWidgetModularEventTypeString]]; // There can be several widgets state events for a same widget but // only the last one must be considered. @@ -221,27 +221,38 @@ NSString *const WidgetManagerErrorDomain = @"WidgetManagerErrorDomain"; success:(void (^)(Widget *widget))success failure:(void (^)(NSError *error))failure { - NSError *permissionError = [self checkWidgetPermissionInRoom:room]; - if (permissionError) - { + // Create an empty operation that will be mutated later + MXHTTPOperation *operation = [[MXHTTPOperation alloc] init]; + + MXWeakify(self); + [self checkWidgetPermissionInRoom:room success:^{ + MXStrongifyAndReturnIfNil(self); + + NSString *hash = [NSString stringWithFormat:@"%p", room.mxSession]; + self->successBlockForWidgetCreation[hash][widgetId] = success; + self->failureBlockForWidgetCreation[hash][widgetId] = failure; + + // Send a state event with the widget data + // TODO: This API will be shortly replaced by a pure modular API + // TODO: Move to kWidgetMatrixEventTypeString ("m.widget") type but when? + MXHTTPOperation *operation2 = [room sendStateEventOfType:kWidgetModularEventTypeString + content:widgetContent + stateKey:widgetId + success:nil failure:failure]; + + if (operation2) + { + [operation mutateTo:operation2]; + } + + } failure:^(NSError *error) { if (failure) { - failure(permissionError); + failure(error); } - return nil; - } + }]; - NSString *hash = [NSString stringWithFormat:@"%p", room.mxSession]; - successBlockForWidgetCreation[hash][widgetId] = success; - failureBlockForWidgetCreation[hash][widgetId] = failure; - - // Send a state event with the widget data - // TODO: This API will be shortly replaced by a pure modular API - // TODO: Move to kWidgetMatrixEventTypeString ("m.widget") type but when? - return [room sendStateEventOfType:kWidgetModularEventTypeString - content:widgetContent - stateKey:widgetId - success:nil failure:failure]; + return operation; } @@ -279,31 +290,40 @@ NSString *const WidgetManagerErrorDomain = @"WidgetManagerErrorDomain"; failure:failure]; } -- (MXHTTPOperation *)closeWidget:(NSString *)widgetId inRoom:(MXRoom *)room success:(void (^)())success failure:(void (^)(NSError *))failure +- (MXHTTPOperation *)closeWidget:(NSString *)widgetId inRoom:(MXRoom *)room success:(void (^)(void))success failure:(void (^)(NSError *))failure { - NSError *permissionError = [self checkWidgetPermissionInRoom:room]; - if (permissionError) - { + // Create an empty operation that will be mutated later + MXHTTPOperation *operation = [[MXHTTPOperation alloc] init]; + + [self checkWidgetPermissionInRoom:room success:^{ + + // Send a state event with an empty content to disable the widget + // TODO: This API will be shortly replaced by a pure modular API + // TODO: Move to kWidgetMatrixEventTypeString ("m.widget") type but when? + MXHTTPOperation *operation2 = [room sendStateEventOfType:kWidgetModularEventTypeString + content:@{} + stateKey:widgetId + success:^(NSString *eventId) + { + if (success) + { + success(); + } + } failure:failure]; + + if (operation2) + { + [operation mutateTo:operation2]; + } + + } failure:^(NSError *error) { if (failure) { - failure(permissionError); + failure(error); } - return nil; - } + }]; - // Send a state event with an empty content to disable the widget - // TODO: This API will be shortly replaced by a pure modular API - // TODO: Move to kWidgetMatrixEventTypeString ("m.widget") type but when? - return [room sendStateEventOfType:kWidgetModularEventTypeString - content:@{} - stateKey:widgetId - success:^(NSString *eventId) - { - if (success) - { - success(); - } - } failure:failure]; + return operation; } /** @@ -312,25 +332,35 @@ NSString *const WidgetManagerErrorDomain = @"WidgetManagerErrorDomain"; @param room the room to check. @return an NSError if the user cannot act on widgets in this room. Else, nil. */ -- (NSError *)checkWidgetPermissionInRoom:(MXRoom *)room +- (void)checkWidgetPermissionInRoom:(MXRoom *)room success:(dispatch_block_t)success failure:(void (^)(NSError *))failure { - NSError *error; + [room state:^(MXRoomState *roomState) { - // Check user's power in the room - MXRoomPowerLevels *powerLevels = room.state.powerLevels; - NSInteger oneSelfPowerLevel = [powerLevels powerLevelOfUserWithUserID:room.mxSession.myUser.userId]; + NSError *error; - // The user must be able to send state events to manage widgets - if (oneSelfPowerLevel < powerLevels.stateDefault) - { - error = [NSError errorWithDomain:WidgetManagerErrorDomain - code:WidgetManagerErrorCodeNotEnoughPower - userInfo:@{ - NSLocalizedDescriptionKey: NSLocalizedStringFromTable(@"widget_no_power_to_manage", @"Vector", nil) - }]; - } + // Check user's power in the room + MXRoomPowerLevels *powerLevels = roomState.powerLevels; + NSInteger oneSelfPowerLevel = [powerLevels powerLevelOfUserWithUserID:room.mxSession.myUser.userId]; - return error; + // The user must be able to send state events to manage widgets + if (oneSelfPowerLevel < powerLevels.stateDefault) + { + error = [NSError errorWithDomain:WidgetManagerErrorDomain + code:WidgetManagerErrorCodeNotEnoughPower + userInfo:@{ + NSLocalizedDescriptionKey: NSLocalizedStringFromTable(@"widget_no_power_to_manage", @"Vector", nil) + }]; + } + + if (error) + { + failure(error); + } + else + { + success(); + } + }]; } - (void)addMatrixSession:(MXSession *)mxSession diff --git a/Riot/Model/Room/RoomPreviewData.m b/Riot/Model/Room/RoomPreviewData.m index 051e2f234..6c482e9bb 100644 --- a/Riot/Model/Room/RoomPreviewData.m +++ b/Riot/Model/Room/RoomPreviewData.m @@ -79,37 +79,45 @@ - (void)peekInRoom:(void (^)(BOOL succeeded))completion { + MXWeakify(self); [_mxSession peekInRoomWithRoomId:_roomId success:^(MXPeekingRoom *peekingRoom) { + MXStrongifyAndReturnIfNil(self); // Create the room data source - _roomDataSource = [[RoomDataSource alloc] initWithPeekingRoom:peekingRoom andInitialEventId:_eventId]; - [_roomDataSource finalizeInitialization]; - _roomDataSource.markTimelineInitialEvent = YES; + MXWeakify(self); + [RoomDataSource loadRoomDataSourceWithPeekingRoom:peekingRoom andInitialEventId:self.eventId onComplete:^(id roomDataSource) { + MXStrongifyAndReturnIfNil(self); - _roomName = peekingRoom.summary.displayname; - _roomAvatarUrl = peekingRoom.summary.avatar; - - _roomTopic = [MXTools stripNewlineCharacters:peekingRoom.summary.topic];; - _roomAliases = peekingRoom.state.aliases; - - // Room members count - // Note that room members presence/activity is not available - _numJoinedMembers = 0; - for (MXRoomMember *mxMember in peekingRoom.state.members.members) - { - if (mxMember.membership == MXMembershipJoin) + self->_roomDataSource = roomDataSource; + + [self.roomDataSource finalizeInitialization]; + self.roomDataSource.markTimelineInitialEvent = YES; + + self->_roomName = peekingRoom.summary.displayname; + self->_roomAvatarUrl = peekingRoom.summary.avatar; + + self->_roomTopic = [MXTools stripNewlineCharacters:peekingRoom.summary.topic];; + self->_roomAliases = self.roomDataSource.roomState.aliases; + + // Room members count + // Note that room members presence/activity is not available + self->_numJoinedMembers = 0; + for (MXRoomMember *mxMember in self.roomDataSource.roomState.members.members) { - _numJoinedMembers ++; + if (mxMember.membership == MXMembershipJoin) + { + self->_numJoinedMembers ++; + } } - } - completion(YES); + completion(YES); + }]; } failure:^(NSError *error) { + MXStrongifyAndReturnIfNil(self); - _roomName = _roomId; + self->_roomName = self->_roomId; completion(NO); - }]; } diff --git a/Riot/Modules/Communities/Home/GroupHomeViewController.m b/Riot/Modules/Communities/Home/GroupHomeViewController.m index 88ab6533b..62397db77 100644 --- a/Riot/Modules/Communities/Home/GroupHomeViewController.m +++ b/Riot/Modules/Communities/Home/GroupHomeViewController.m @@ -569,13 +569,13 @@ if ([self.mxSession roomWithRoomId:roomId]) { MXKRoomDataSourceManager *roomDataSourceManager = [MXKRoomDataSourceManager sharedManagerForMatrixSession:self.mxSession]; - MXKRoomDataSource *roomDataSource = [roomDataSourceManager roomDataSourceForRoom:roomId create:YES]; - - // Open this room - RoomViewController *roomViewController = [RoomViewController roomViewController]; - roomViewController.showMissedDiscussionsBadge = NO; - [roomViewController displayRoom:roomDataSource]; - [self pushViewController:roomViewController]; + [roomDataSourceManager roomDataSourceForRoom:roomId create:YES onComplete:^(MXKRoomDataSource *roomDataSource) { + // Open this room + RoomViewController *roomViewController = [RoomViewController roomViewController]; + roomViewController.showMissedDiscussionsBadge = NO; + [roomViewController displayRoom:roomDataSource]; + [self pushViewController:roomViewController]; + }]; } else { diff --git a/Riot/Modules/Communities/Rooms/GroupRoomsViewController.m b/Riot/Modules/Communities/Rooms/GroupRoomsViewController.m index 9d2e0bfe1..dfaaf1243 100644 --- a/Riot/Modules/Communities/Rooms/GroupRoomsViewController.m +++ b/Riot/Modules/Communities/Rooms/GroupRoomsViewController.m @@ -534,14 +534,15 @@ // Check first if the user already joined this room. if ([self.mxSession roomWithRoomId:room.roomId]) { - MXKRoomDataSourceManager *roomDataSourceManager = [MXKRoomDataSourceManager sharedManagerForMatrixSession:self.mxSession]; - MXKRoomDataSource *roomDataSource = [roomDataSourceManager roomDataSourceForRoom:room.roomId create:YES]; - // Open this room - RoomViewController *roomViewController = [RoomViewController roomViewController]; - roomViewController.showMissedDiscussionsBadge = NO; - [roomViewController displayRoom:roomDataSource]; - [self pushViewController:roomViewController]; + MXKRoomDataSourceManager *roomDataSourceManager = [MXKRoomDataSourceManager sharedManagerForMatrixSession:self.mxSession]; + [roomDataSourceManager roomDataSourceForRoom:room.roomId create:YES onComplete:^(MXKRoomDataSource *roomDataSource) { + + RoomViewController *roomViewController = [RoomViewController roomViewController]; + roomViewController.showMissedDiscussionsBadge = NO; + [roomViewController displayRoom:roomDataSource]; + [self pushViewController:roomViewController]; + }]; } else { diff --git a/Riot/Modules/GlobalSearch/Files/CellData/FilesSearchCellData.m b/Riot/Modules/GlobalSearch/Files/CellData/FilesSearchCellData.m index bdfd26ef5..2fd863cdb 100644 --- a/Riot/Modules/GlobalSearch/Files/CellData/FilesSearchCellData.m +++ b/Riot/Modules/GlobalSearch/Files/CellData/FilesSearchCellData.m @@ -55,21 +55,38 @@ } date = [searchDataSource.eventFormatter dateStringFromEvent:event withTime:NO]; - + } + return self; +} + ++ (void)cellDataWithSearchResult:(MXSearchResult *)searchResult andSearchDataSource:(MXKSearchDataSource *)searchDataSource onComplete:(void (^)(id))onComplete +{ + FilesSearchCellData *cellData = [[self alloc] initWithSearchResult:searchResult andSearchDataSource:searchDataSource]; + if (cellData) + { // Retrieve the sender display name from the current room state - MXRoom *room = [searchDataSource.mxSession roomWithRoomId:roomId]; + MXRoom *room = [searchDataSource.mxSession roomWithRoomId:cellData.roomId]; if (room) { - senderDisplayName = [room.state.members memberName:event.sender]; + [room state:^(MXRoomState *roomState) { + cellData->senderDisplayName = [roomState.members memberName:searchResult.result.sender]; + cellData->message = cellData->senderDisplayName; + + onComplete(cellData); + }]; } else { - senderDisplayName = event.sender; + cellData->senderDisplayName = searchResult.result.sender; + cellData->message = cellData->senderDisplayName; + + onComplete(cellData); } - - message = senderDisplayName; } - return self; + else + { + onComplete(nil); + } } - (void)setShouldShowRoomDisplayName:(BOOL)shouldShowRoomDisplayName2 diff --git a/Riot/Modules/GlobalSearch/Messages/DataSources/HomeMessagesSearchDataSource.m b/Riot/Modules/GlobalSearch/Messages/DataSources/HomeMessagesSearchDataSource.m index 0d2787c60..5a332ed5f 100644 --- a/Riot/Modules/GlobalSearch/Messages/DataSources/HomeMessagesSearchDataSource.m +++ b/Riot/Modules/GlobalSearch/Messages/DataSources/HomeMessagesSearchDataSource.m @@ -33,9 +33,11 @@ [super destroy]; } -- (void)convertHomeserverResultsIntoCells:(MXSearchRoomEventResults *)roomEventResults +- (void)convertHomeserverResultsIntoCells:(MXSearchRoomEventResults *)roomEventResults onComplete:(dispatch_block_t)onComplete { MXKRoomDataSourceManager *roomDataSourceManager = [MXKRoomDataSourceManager sharedManagerForMatrixSession:self.mxSession]; + + dispatch_group_t group = dispatch_group_create(); // Convert the HS results into `RoomViewController` cells for (MXSearchResult *result in roomEventResults.results) @@ -43,48 +45,57 @@ // Retrieve the local room data source thanks to the room identifier // Note: if no local room data source exist the result is ignored. NSString *roomId = result.result.roomId; - UIFont *patternFont = nil; - MXKRoomDataSource *roomDataSource; if (roomId) { + dispatch_group_enter(group); + // Check whether the user knows this room to create the room data source if it doesn't exist. - roomDataSource = [roomDataSourceManager roomDataSourceForRoom:roomId create:([self.mxSession roomWithRoomId:roomId])]; - if (roomDataSource) - { - // Prepare text font used to highlight the search pattern. - patternFont = [roomDataSource.eventFormatter bingTextFont]; - - // Let the `RoomViewController` ecosystem do the job - // The search result contains only room message events, no state events. - // Thus, passing the current room state is not a huge problem. Only - // the user display name and his avatar may be wrong. - RoomBubbleCellData *cellData = [[RoomBubbleCellData alloc] initWithEvent:result.result andRoomState:roomDataSource.room.state andRoomDataSource:roomDataSource]; - if (cellData) + [roomDataSourceManager roomDataSourceForRoom:roomId create:[self.mxSession roomWithRoomId:roomId] onComplete:^(MXKRoomDataSource *roomDataSource) { + + if (roomDataSource) { - // Highlight the search pattern - [cellData highlightPatternInTextMessage:self.searchText withForegroundColor:kRiotColorGreen andFont:patternFont]; - - [cellDataArray insertObject:cellData atIndex:0]; + // Prepare text font used to highlight the search pattern. + UIFont *patternFont = [roomDataSource.eventFormatter bingTextFont]; + + // Let the `RoomViewController` ecosystem do the job + // The search result contains only room message events, no state events. + // Thus, passing the current room state is not a huge problem. Only + // the user display name and his avatar may be wrong. + RoomBubbleCellData *cellData = [[RoomBubbleCellData alloc] initWithEvent:result.result andRoomState:roomDataSource.roomState andRoomDataSource:roomDataSource]; + if (cellData) + { + // Highlight the search pattern + [cellData highlightPatternInTextMessage:self.searchText withForegroundColor:kRiotColorGreen andFont:patternFont]; + + [self->cellDataArray insertObject:cellData atIndex:0]; + } } + + dispatch_group_leave(group); + }]; + } + } + + dispatch_group_notify(group, dispatch_get_main_queue(), ^{ + + // In case of successive messages from the same room, + // we use the pagination flag to display the room name only on the first message. + NSString *currentRoomId; + for (RoomBubbleCellData *cellData in self->cellDataArray) + { + if (currentRoomId && [currentRoomId isEqualToString:cellData.roomId]) + { + cellData.isPaginationFirstBubble = NO; + } + else + { + cellData.isPaginationFirstBubble = YES; + currentRoomId = cellData.roomId; } } - } - - // In case of successive messages from the same room, - // we use the pagination flag to display the room name only on the first message. - NSString *currentRoomId; - for (RoomBubbleCellData *cellData in cellDataArray) - { - if (currentRoomId && [currentRoomId isEqualToString:cellData.roomId]) - { - cellData.isPaginationFirstBubble = NO; - } - else - { - cellData.isPaginationFirstBubble = YES; - currentRoomId = cellData.roomId; - } - } + + onComplete(); + }); } - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath diff --git a/Riot/Modules/Integrations/IntegrationManagerViewController.m b/Riot/Modules/Integrations/IntegrationManagerViewController.m index 692ba910f..99897bf7b 100644 --- a/Riot/Modules/Integrations/IntegrationManagerViewController.m +++ b/Riot/Modules/Integrations/IntegrationManagerViewController.m @@ -69,34 +69,24 @@ NSString *const kIntegrationManagerAddIntegrationScreen = @"add_integ"; if (!self.URL && !operation) { - __weak __typeof__(self) weakSelf = self; - [self startActivityIndicator]; // Make sure we have a scalar token + MXWeakify(self); operation = [[WidgetManager sharedManager] getScalarTokenForMXSession:mxSession success:^(NSString *theScalarToken) { + MXStrongifyAndReturnIfNil(self); - typeof(self) self = weakSelf; + self->operation = nil; + self->scalarToken = theScalarToken; - if (self) - { - self->operation = nil; - - scalarToken = theScalarToken; - - // Launch the webview on the right modular webapp page - self.URL = [self interfaceUrl]; - } + // Launch the webview on the right modular webapp page + self.URL = [self interfaceUrl]; } failure:^(NSError *error) { + MXStrongifyAndReturnIfNil(self); - typeof(self) self = weakSelf; - - if (self) - { - self->operation = nil; - [self stopActivityIndicator]; - } + self->operation = nil; + [self stopActivityIndicator]; }]; } } @@ -253,26 +243,28 @@ NSString *const kIntegrationManagerAddIntegrationScreen = @"add_integ"; #pragma mark - Private methods -- (MXRoom *)roomCheckForRequest:(NSString*)requestId data:(NSDictionary*)requestData +- (void)roomCheckForRequest:(NSString*)requestId data:(NSDictionary*)requestData onComplete:(void (^)(MXRoom *room, MXRoomState *roomState))onComplete { MXRoom *room = [mxSession roomWithRoomId:roomId]; - if (!room) + if (room) + { + [room state:^(MXRoomState *roomState) { + onComplete(room, roomState); + }]; + } + else { [self sendLocalisedError:@"widget_integration_room_not_recognised" toRequest:requestId]; } - - return room; } - (void)inviteUser:(NSString*)userId request:(NSString*)requestId data:(NSDictionary*)requestData { NSLog(@"[IntegrationManagerVC] Received request to invite %@ into room %@.", userId, roomId); - MXRoom *room = [self roomCheckForRequest:requestId data:requestData]; - - if (room) - { - MXRoomMember *member = [room.state.members memberWithUserId:userId]; + [self roomCheckForRequest:requestId data:requestData onComplete:^(MXRoom *room, MXRoomState *roomState) { + + MXRoomMember *member = [roomState.members memberWithUserId:userId]; if (member && member.membership == MXMembershipJoin) { [self sendNSObjectResponse:@{ @@ -282,29 +274,22 @@ NSString *const kIntegrationManagerAddIntegrationScreen = @"add_integ"; } else { - __weak __typeof__(self) weakSelf = self; - + MXWeakify(self); [room inviteUser:userId success:^{ + MXStrongifyAndReturnIfNil(self); - typeof(self) self = weakSelf; - if (self) - { - [self sendNSObjectResponse:@{ - @"success": @(YES) - } - toRequest:requestId]; - } + [self sendNSObjectResponse:@{ + @"success": @(YES) + } + toRequest:requestId]; } failure:^(NSError *error) { + MXStrongifyAndReturnIfNil(self); - typeof(self) self = weakSelf; - if (self) - { - [self sendLocalisedError:@"widget_integration_need_to_be_able_to_invite" toRequest:requestId]; - } + [self sendLocalisedError:@"widget_integration_need_to_be_able_to_invite" toRequest:requestId]; }]; } - } + }]; } - (void)setWidget:(NSString*)requestId data:(NSDictionary*)requestData @@ -396,9 +381,8 @@ NSString *const kIntegrationManagerAddIntegrationScreen = @"add_integ"; else { // Room widget - MXRoom *room = [self roomCheckForRequest:requestId data:requestData]; - if (room) - { + [self roomCheckForRequest:requestId data:requestData onComplete:^(MXRoom *room, MXRoomState *roomState) { + // TODO: Move to kWidgetMatrixEventTypeString ("m.widget") type but when? [room sendStateEventOfType:kWidgetModularEventTypeString content:widgetEventContent @@ -422,51 +406,50 @@ NSString *const kIntegrationManagerAddIntegrationScreen = @"add_integ"; [self sendLocalisedError:@"widget_integration_failed_to_send_request" toRequest:requestId]; } }]; - } + }]; } } - (void)getWidgets:(NSString*)requestId data:(NSDictionary*)requestData { - MXRoom *room = [self roomCheckForRequest:requestId data:requestData]; - NSMutableArray *widgetStateEvents = [NSMutableArray array]; + MXWeakify(self); + [self roomCheckForRequest:requestId data:requestData onComplete:^(MXRoom *room, MXRoomState *roomState) { + MXStrongifyAndReturnIfNil(self); - if (room) - { - NSArray *widgets = [[WidgetManager sharedManager] widgetsInRoom:room]; + NSMutableArray *widgetStateEvents = [NSMutableArray array]; + + NSArray *widgets = [[WidgetManager sharedManager] widgetsInRoom:room withRoomState:roomState]; for (Widget *widget in widgets) { [widgetStateEvents addObject:widget.widgetEvent.JSONDictionary]; } - } - // Add user widgets (not linked to a specific room) - for (Widget *widget in [[WidgetManager sharedManager] userWidgets:mxSession]) - { - [widgetStateEvents addObject:widget.widgetEvent.JSONDictionary]; - } + // Add user widgets (not linked to a specific room) + for (Widget *widget in [[WidgetManager sharedManager] userWidgets:self->mxSession]) + { + [widgetStateEvents addObject:widget.widgetEvent.JSONDictionary]; + } - [self sendNSObjectResponse:widgetStateEvents toRequest:requestId]; + [self sendNSObjectResponse:widgetStateEvents toRequest:requestId]; + }]; } - (void)getRoomEncState:(NSString*)requestId data:(NSDictionary*)requestData { - MXRoom *room = [self roomCheckForRequest:requestId data:requestData]; - if (room) - { + [self roomCheckForRequest:requestId data:requestData onComplete:^(MXRoom *room, MXRoomState *roomState) { [self sendBoolResponse:room.summary.isEncrypted toRequest:requestId]; - } + }]; } - (void)canSendEvent:(NSString*)requestId data:(NSDictionary*)requestData { - NSString *eventType; - BOOL isState = NO; + MXWeakify(self); + [self roomCheckForRequest:requestId data:requestData onComplete:^(MXRoom *room, MXRoomState *roomState) { + MXStrongifyAndReturnIfNil(self); - MXRoom *room = [self roomCheckForRequest:requestId data:requestData]; + NSString *eventType; + BOOL isState = NO; - if (room) - { if (room.summary.membership != MXMembershipJoin) { [self sendLocalisedError:@"widget_integration_must_be_in_room" toRequest:requestId]; @@ -476,8 +459,8 @@ NSString *const kIntegrationManagerAddIntegrationScreen = @"add_integ"; MXJSONModelSetString(eventType, requestData[@"event_type"]); MXJSONModelSetBoolean(isState, requestData[@"is_state"]); - MXRoomPowerLevels *powerLevels = room.state.powerLevels; - NSInteger userPowerLevel = [powerLevels powerLevelOfUserWithUserID:mxSession.myUser.userId]; + MXRoomPowerLevels *powerLevels = roomState.powerLevels; + NSInteger userPowerLevel = [powerLevels powerLevelOfUserWithUserID:self->mxSession.myUser.userId]; BOOL canSend = NO; @@ -498,47 +481,42 @@ NSString *const kIntegrationManagerAddIntegrationScreen = @"add_integ"; { [self sendLocalisedError:@"widget_integration_no_permission_in_room" toRequest:requestId]; } - } + + }]; } - (void)getMembershipState:(NSString*)userId request:(NSString*)requestId data:(NSDictionary*)requestData { NSLog(@"[IntegrationManagerVC] membership_state of %@ in room %@ requested.", userId, roomId); - MXRoom *room = [self roomCheckForRequest:requestId data:requestData]; - if (room) - { - MXRoomMember *member = [room.state.members memberWithUserId:userId]; + [self roomCheckForRequest:requestId data:requestData onComplete:^(MXRoom *room, MXRoomState *roomState) { + MXRoomMember *member = [roomState.members memberWithUserId:userId]; [self sendNSObjectResponse:member.originalEvent.content toRequest:requestId]; - } + }]; } - (void)getJoinRules:(NSString*)requestId data:(NSDictionary*)requestData { NSLog(@"[IntegrationManagerVC] join_rules of %@ requested.", roomId); - MXRoom *room = [self roomCheckForRequest:requestId data:requestData]; - if (room) - { - MXEvent *event = [room.state stateEventsWithType:kMXEventTypeStringRoomJoinRules].lastObject; + [self roomCheckForRequest:requestId data:requestData onComplete:^(MXRoom *room, MXRoomState *roomState) { + MXEvent *event = [roomState stateEventsWithType:kMXEventTypeStringRoomJoinRules].lastObject; [self sendNSObjectResponse:event.JSONDictionary toRequest:requestId]; - } + }]; } - (void)setPlumbingState:(NSString*)requestId data:(NSDictionary*)requestData { NSLog(@"[IntegrationManagerVC] Received request to set plumbing state to status %@ in room %@.", requestData[@"status"], roomId); - MXRoom *room = [self roomCheckForRequest:requestId data:requestData]; - if (room) - { + [self roomCheckForRequest:requestId data:requestData onComplete:^(MXRoom *room, MXRoomState *roomState) { NSString *status; MXJSONModelSetString(status, requestData[@"status"]); if (status) { - __weak __typeof__(self) weakSelf = self; - + __weak __typeof__(self) weakSelf = self; + [room sendStateEventOfType:kMXEventTypeStringRoomPlumbing content:@{ @"status": status @@ -552,7 +530,7 @@ NSString *const kIntegrationManagerAddIntegrationScreen = @"add_integ"; [self sendNSObjectResponse:@{ @"success": @(YES) } - toRequest:requestId]; + toRequest:requestId]; } } failure:^(NSError *error) { @@ -568,19 +546,18 @@ NSString *const kIntegrationManagerAddIntegrationScreen = @"add_integ"; { NSLog(@"[IntegrationManagerVC] setPlumbingState. Error: Plumbing state status should be a string."); } - } + + }]; } - (void)getBotOptions:(NSString*)userId request:(NSString*)requestId data:(NSDictionary*)requestData { NSLog(@"[IntegrationManagerVC] Received request to get options for bot %@ in room %@", userId, roomId); - MXRoom *room = [self roomCheckForRequest:requestId data:requestData]; - if (room) - { + [self roomCheckForRequest:requestId data:requestData onComplete:^(MXRoom *room, MXRoomState *roomState) { NSString *stateKey = [NSString stringWithFormat:@"_%@", userId]; - NSArray *stateEvents = [room.state stateEventsWithType:kMXEventTypeStringRoomBotOptions]; + NSArray *stateEvents = [roomState stateEventsWithType:kMXEventTypeStringRoomBotOptions]; MXEvent *botOptionsEvent; @@ -590,22 +567,21 @@ NSString *const kIntegrationManagerAddIntegrationScreen = @"add_integ"; { if (!botOptionsEvent || stateEvent.ageLocalTs > botOptionsEvent.ageLocalTs) { - botOptionsEvent = stateEvent; + botOptionsEvent = stateEvent; } } } [self sendNSObjectResponse:botOptionsEvent.JSONDictionary toRequest:requestId]; - } + + }]; } - (void)setBotOptions:(NSString*)userId request:(NSString*)requestId data:(NSDictionary*)requestData { NSLog(@"[IntegrationManagerVC] Received request to set options for bot %@ in room %@", userId, roomId); - MXRoom *room = [self roomCheckForRequest:requestId data:requestData]; - if (room) - { + [self roomCheckForRequest:requestId data:requestData onComplete:^(MXRoom *room, MXRoomState *roomState) { NSDictionary *content; MXJSONModelSetDictionary(content, requestData[@"content"]); @@ -626,7 +602,7 @@ NSString *const kIntegrationManagerAddIntegrationScreen = @"add_integ"; [self sendNSObjectResponse:@{ @"success": @(YES) } - toRequest:requestId]; + toRequest:requestId]; } } failure:^(NSError *error) { @@ -642,16 +618,14 @@ NSString *const kIntegrationManagerAddIntegrationScreen = @"add_integ"; { NSLog(@"[IntegrationManagerVC] setBotOptions. Error: options should be a dict."); } - } + }]; } - (void)setBotPower:(NSString*)userId request:(NSString*)requestId data:(NSDictionary*)requestData { NSLog(@"[IntegrationManagerVC] Received request to set power level to %@ for bot %@ in room %@.", requestData[@"level"], userId, roomId); - MXRoom *room = [self roomCheckForRequest:requestId data:requestData]; - if (room) - { + [self roomCheckForRequest:requestId data:requestData onComplete:^(MXRoom *room, MXRoomState *roomState) { NSInteger level = -1; MXJSONModelSetInteger(level, requestData[@"level"]); @@ -667,7 +641,7 @@ NSString *const kIntegrationManagerAddIntegrationScreen = @"add_integ"; [self sendNSObjectResponse:@{ @"success": @(YES) } - toRequest:requestId]; + toRequest:requestId]; } } failure:^(NSError *error) { @@ -684,17 +658,15 @@ NSString *const kIntegrationManagerAddIntegrationScreen = @"add_integ"; NSLog(@"[IntegrationManagerVC] setBotPower. Power level must be positive integer."); [self sendLocalisedError:@"widget_integration_positive_power_level" toRequest:requestId]; } - } + }]; } - (void)getMembershipCount:(NSString*)requestId data:(NSDictionary*)requestData { - MXRoom *room = [self roomCheckForRequest:requestId data:requestData]; - if (room) - { + [self roomCheckForRequest:requestId data:requestData onComplete:^(MXRoom *room, MXRoomState *roomState) { NSUInteger membershipCount = room.summary.membersCount.joined; [self sendIntegerResponse:membershipCount toRequest:requestId]; - } + }]; } @end diff --git a/Riot/Modules/Integrations/WidgetPicker/WidgetPickerViewController.m b/Riot/Modules/Integrations/WidgetPicker/WidgetPickerViewController.m index fb663c1b0..4287370a3 100644 --- a/Riot/Modules/Integrations/WidgetPicker/WidgetPickerViewController.m +++ b/Riot/Modules/Integrations/WidgetPicker/WidgetPickerViewController.m @@ -49,64 +49,66 @@ - (void)showInViewController:(MXKViewController *)mxkViewController { - UIAlertAction *alertAction; + MXKRoomDataSourceManager *roomDataSourceManager = [MXKRoomDataSourceManager sharedManagerForMatrixSession:mxSession]; + [roomDataSourceManager roomDataSourceForRoom:roomId create:NO onComplete:^(MXKRoomDataSource *roomDataSource) { - MXRoom *room = [mxSession roomWithRoomId:roomId]; + UIAlertAction *alertAction; - NSArray *widgets = [[WidgetManager sharedManager] widgetsNotOfTypes:@[kWidgetTypeJitsi] - inRoom:room]; + NSArray *widgets = [[WidgetManager sharedManager] widgetsNotOfTypes:@[kWidgetTypeJitsi] + inRoom:roomDataSource.room + withRoomState:roomDataSource.roomState]; - // List widgets - for (Widget *widget in widgets) - { - alertAction = [UIAlertAction actionWithTitle:widget.name ? widget.name : widget.type + // List widgets + for (Widget *widget in widgets) + { + alertAction = [UIAlertAction actionWithTitle:widget.name ? widget.name : widget.type + style:UIAlertActionStyleDefault + handler:^(UIAlertAction * _Nonnull action) + { + // Hide back button title + mxkViewController.navigationItem.backBarButtonItem =[[UIBarButtonItem alloc] initWithTitle:@"" style:UIBarButtonItemStylePlain target:nil action:nil]; + + // Display the widget + [widget widgetUrl:^(NSString * _Nonnull widgetUrl) { + + WidgetViewController *widgetVC = [[WidgetViewController alloc] initWithUrl:widgetUrl forWidget:widget]; + + widgetVC.roomDataSource = roomDataSource; + + [mxkViewController.navigationController pushViewController:widgetVC animated:YES]; + + } failure:^(NSError * _Nonnull error) { + + NSLog(@"[WidgetPickerVC] Cannot display widget %@", widget); + [[AppDelegate theDelegate] showErrorAsAlert:error]; + }]; + }]; + [self.alertController addAction:alertAction]; + } + + // Link to the integration manager + alertAction = [UIAlertAction actionWithTitle:@"Manage integrations..." style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) { - // Hide back button title - mxkViewController.navigationItem.backBarButtonItem =[[UIBarButtonItem alloc] initWithTitle:@"" style:UIBarButtonItemStylePlain target:nil action:nil]; + IntegrationManagerViewController *modularVC = [[IntegrationManagerViewController alloc] initForMXSession:self->mxSession + inRoom:self->roomId + screen:kIntegrationManagerMainScreen + widgetId:nil]; - // Display the widget - [widget widgetUrl:^(NSString * _Nonnull widgetUrl) { - - WidgetViewController *widgetVC = [[WidgetViewController alloc] initWithUrl:widgetUrl forWidget:widget]; - - MXKRoomDataSourceManager *roomDataSourceManager = [MXKRoomDataSourceManager sharedManagerForMatrixSession:mxSession]; - widgetVC.roomDataSource = [roomDataSourceManager roomDataSourceForRoom:roomId create:NO]; - - [mxkViewController.navigationController pushViewController:widgetVC animated:YES]; - - } failure:^(NSError * _Nonnull error) { - - NSLog(@"[WidgetPickerVC] Cannot display widget %@", widget); - [[AppDelegate theDelegate] showErrorAsAlert:error]; - }]; + [mxkViewController presentViewController:modularVC animated:NO completion:nil]; }]; - [_alertController addAction:alertAction]; - } + [self.alertController addAction:alertAction]; - // Link to the integration manager - alertAction = [UIAlertAction actionWithTitle:@"Manage integrations..." - style:UIAlertActionStyleDefault - handler:^(UIAlertAction * _Nonnull action) - { - IntegrationManagerViewController *modularVC = [[IntegrationManagerViewController alloc] initForMXSession:self->mxSession - inRoom:self->roomId - screen:kIntegrationManagerMainScreen - widgetId:nil]; + // Cancel + alertAction = [UIAlertAction actionWithTitle:[NSBundle mxk_localizedStringForKey:@"cancel"] + style:UIAlertActionStyleCancel + handler:nil]; + [self.alertController addAction:alertAction]; - [mxkViewController presentViewController:modularVC animated:NO completion:nil]; - }]; - [_alertController addAction:alertAction]; - - // Cancel - alertAction = [UIAlertAction actionWithTitle:[NSBundle mxk_localizedStringForKey:@"cancel"] - style:UIAlertActionStyleCancel - handler:nil]; - [_alertController addAction:alertAction]; - - // And show it - [mxkViewController presentViewController:_alertController animated:YES completion:nil]; -} + // And show it + [mxkViewController presentViewController:_alertController animated:YES completion:nil]; + }]; + } @end diff --git a/Riot/Modules/Room/DataSources/RoomDataSource.m b/Riot/Modules/Room/DataSources/RoomDataSource.m index f400d22f2..7abcc6cb9 100644 --- a/Riot/Modules/Room/DataSources/RoomDataSource.m +++ b/Riot/Modules/Room/DataSources/RoomDataSource.m @@ -271,7 +271,7 @@ for (MXReceiptData* data in receipts) { - MXRoomMember * roomMember = [self.room.state.members memberWithUserId:data.userId]; + MXRoomMember * roomMember = [self.roomState.members memberWithUserId:data.userId]; if (roomMember) { [roomMembers addObject:roomMember]; @@ -495,7 +495,7 @@ Widget *jitsiWidget; // Note: Manage only one jitsi widget at a time for the moment - jitsiWidget = [[WidgetManager sharedManager] widgetsOfTypes:@[kWidgetTypeJitsi] inRoom:self.room].firstObject; + jitsiWidget = [[WidgetManager sharedManager] widgetsOfTypes:@[kWidgetTypeJitsi] inRoom:self.room withRoomState:self.roomState].firstObject; return jitsiWidget; } diff --git a/Riot/Modules/Room/Members/Detail/RoomMemberDetailsViewController.m b/Riot/Modules/Room/Members/Detail/RoomMemberDetailsViewController.m index f0c78e61f..aedc8c202 100644 --- a/Riot/Modules/Room/Members/Detail/RoomMemberDetailsViewController.m +++ b/Riot/Modules/Room/Members/Detail/RoomMemberDetailsViewController.m @@ -367,22 +367,27 @@ self.roomMemberNameLabel.text = self.mxRoomMember.displayname ? self.mxRoomMember.displayname : self.mxRoomMember.userId; // Update member badge - MXRoomPowerLevels *powerLevels = [self.mxRoom.state powerLevels]; - NSInteger powerLevel = [powerLevels powerLevelOfUserWithUserID:self.mxRoomMember.userId]; - if (powerLevel >= kRiotRoomAdminLevel) - { - memberTitleView.memberBadge.image = [UIImage imageNamed:@"admin_icon"]; - memberTitleView.memberBadge.hidden = NO; - } - else if (powerLevel >= kRiotRoomModeratorLevel) - { - memberTitleView.memberBadge.image = [UIImage imageNamed:@"mod_icon"]; - memberTitleView.memberBadge.hidden = NO; - } - else - { - memberTitleView.memberBadge.hidden = YES; - } + MXWeakify(self); + [self.mxRoom state:^(MXRoomState *roomState) { + MXStrongifyAndReturnIfNil(self); + + MXRoomPowerLevels *powerLevels = [roomState powerLevels]; + NSInteger powerLevel = [powerLevels powerLevelOfUserWithUserID:self.mxRoomMember.userId]; + if (powerLevel >= kRiotRoomAdminLevel) + { + self->memberTitleView.memberBadge.image = [UIImage imageNamed:@"admin_icon"]; + self->memberTitleView.memberBadge.hidden = NO; + } + else if (powerLevel >= kRiotRoomModeratorLevel) + { + self->memberTitleView.memberBadge.image = [UIImage imageNamed:@"mod_icon"]; + self->memberTitleView.memberBadge.hidden = NO; + } + else + { + self->memberTitleView.memberBadge.hidden = YES; + } + }]; NSString* presenceText; @@ -477,7 +482,7 @@ BOOL isOneself = NO; // Check user's power level before allowing an action (kick, ban, ...) - MXRoomPowerLevels *powerLevels = [self.mxRoom.state powerLevels]; + MXRoomPowerLevels *powerLevels = [self.mxRoom.dangerousSyncState powerLevels]; NSInteger memberPowerLevel = [powerLevels powerLevelOfUserWithUserID:self.mxRoomMember.userId]; NSInteger oneSelfPowerLevel = [powerLevels powerLevelOfUserWithUserID:self.mainSession.myUser.userId]; @@ -930,7 +935,9 @@ { case MXKRoomMemberDetailsActionSetDefaultPowerLevel: { - [self setPowerLevel:self.mxRoom.state.powerLevels.usersDefault promptUser:YES]; + [self.mxRoom state:^(MXRoomState *roomState) { + [self setPowerLevel:roomState.powerLevels.usersDefault promptUser:YES]; + }]; break; } case MXKRoomMemberDetailsActionSetModerator: diff --git a/Riot/Modules/Room/Members/RoomParticipantsViewController.m b/Riot/Modules/Room/Members/RoomParticipantsViewController.m index f543fa449..fa850031d 100644 --- a/Riot/Modules/Room/Members/RoomParticipantsViewController.m +++ b/Riot/Modules/Room/Members/RoomParticipantsViewController.m @@ -216,8 +216,13 @@ if (membersListener) { - [self.mxRoom.liveTimeline removeListener:membersListener]; - membersListener = nil; + MXWeakify(self); + [self.mxRoom liveTimeline:^(MXEventTimeline *liveTimeline) { + MXStrongifyAndReturnIfNil(self); + + [liveTimeline removeListener:self->membersListener]; + self->membersListener = nil; + }]; } if (currentAlert) @@ -330,142 +335,159 @@ { // Cancel any pending search [self searchBarCancelButtonClicked:_searchBarView]; - - // Remove previous room registration (if any). - if (_mxRoom) - { - // Remove the previous listener - if (leaveRoomNotificationObserver) + + // Make sure we can access synchronously to self.mxRoom and mxRoom data + // to avoid race conditions + MXWeakify(self); + [_mxRoom.mxSession preloadRoomsData:@[_mxRoom.roomId, mxRoom.roomId] onComplete:^{ + MXStrongifyAndReturnIfNil(self); + + // Remove previous room registration (if any). + if (self.mxRoom) { - [[NSNotificationCenter defaultCenter] removeObserver:leaveRoomNotificationObserver]; - leaveRoomNotificationObserver = nil; - } - if (roomDidFlushDataNotificationObserver) - { - [[NSNotificationCenter defaultCenter] removeObserver:roomDidFlushDataNotificationObserver]; - roomDidFlushDataNotificationObserver = nil; - } - if (membersListener) - { - [_mxRoom.liveTimeline removeListener:membersListener]; - membersListener = nil; - } - - [self removeMatrixSession:_mxRoom.mxSession]; - } - - _mxRoom = mxRoom; - - if (_mxRoom) - { - _searchBarHeader.hidden = NO; - - // Update the current matrix session. - [self addMatrixSession:_mxRoom.mxSession]; - - // Observe kMXSessionWillLeaveRoomNotification to be notified if the user leaves the current room. - leaveRoomNotificationObserver = [[NSNotificationCenter defaultCenter] addObserverForName:kMXSessionWillLeaveRoomNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notif) { - - // Check whether the user will leave the room related to the displayed participants - if (notif.object == _mxRoom.mxSession) + // Remove the previous listener + if (self->leaveRoomNotificationObserver) { - NSString *roomId = notif.userInfo[kMXSessionNotificationRoomIdKey]; - if (roomId && [roomId isEqualToString:_mxRoom.roomId]) - { - // We remove the current view controller. - [self withdrawViewControllerAnimated:YES completion:nil]; - } + [[NSNotificationCenter defaultCenter] removeObserver:self->leaveRoomNotificationObserver]; + self->leaveRoomNotificationObserver = nil; } - }]; - - // Observe room history flush (sync with limited timeline, or state event redaction) - roomDidFlushDataNotificationObserver = [[NSNotificationCenter defaultCenter] addObserverForName:kMXRoomDidFlushDataNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notif) { - - MXRoom *room = notif.object; - if (_mxRoom.mxSession == room.mxSession && [_mxRoom.roomId isEqualToString:room.roomId]) + if (self->roomDidFlushDataNotificationObserver) { - // The existing room history has been flushed during server sync. Take into account the updated room members list. - [self refreshParticipantsFromRoomMembers]; - - [self refreshTableView]; + [[NSNotificationCenter defaultCenter] removeObserver:self->roomDidFlushDataNotificationObserver]; + self->roomDidFlushDataNotificationObserver = nil; } - - }]; - - // Register a listener for events that concern room members - NSArray *mxMembersEvents = @[kMXEventTypeStringRoomMember, kMXEventTypeStringRoomThirdPartyInvite, kMXEventTypeStringRoomPowerLevels]; - membersListener = [_mxRoom.liveTimeline listenToEventsOfTypes:mxMembersEvents onEvent:^(MXEvent *event, MXTimelineDirection direction, id customObject) { - - // Consider only live event - if (direction == MXTimelineDirectionForwards) + if (self->membersListener) { - switch (event.eventType) + MXWeakify(self); + [self.mxRoom liveTimeline:^(MXEventTimeline *liveTimeline) { + MXStrongifyAndReturnIfNil(self); + + [liveTimeline removeListener:self->membersListener]; + self->membersListener = nil; + }]; + } + + [self removeMatrixSession:self.mxRoom.mxSession]; + } + + self->_mxRoom = mxRoom; + + if (self.mxRoom) + { + self.searchBarHeader.hidden = NO; + + // Update the current matrix session. + [self addMatrixSession:self.mxRoom.mxSession]; + + // Observe kMXSessionWillLeaveRoomNotification to be notified if the user leaves the current room. + self->leaveRoomNotificationObserver = [[NSNotificationCenter defaultCenter] addObserverForName:kMXSessionWillLeaveRoomNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notif) { + + // Check whether the user will leave the room related to the displayed participants + if (notif.object == self.mxRoom.mxSession) { - case MXEventTypeRoomMember: + NSString *roomId = notif.userInfo[kMXSessionNotificationRoomIdKey]; + if (roomId && [roomId isEqualToString:self.mxRoom.roomId]) { - // Take into account updated member - // Ignore here change related to the current user (this change is handled by leaveRoomNotificationObserver) - if ([event.stateKey isEqualToString:self.mxRoom.mxSession.myUser.userId] == NO) + // We remove the current view controller. + [self withdrawViewControllerAnimated:YES completion:nil]; + } + } + }]; + + // Observe room history flush (sync with limited timeline, or state event redaction) + self->roomDidFlushDataNotificationObserver = [[NSNotificationCenter defaultCenter] addObserverForName:kMXRoomDidFlushDataNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notif) { + + MXRoom *room = notif.object; + if (self.mxRoom.mxSession == room.mxSession && [self.mxRoom.roomId isEqualToString:room.roomId]) + { + // The existing room history has been flushed during server sync. Take into account the updated room members list. + [self refreshParticipantsFromRoomMembers]; + + [self refreshTableView]; + } + + }]; + + // Register a listener for events that concern room members + NSArray *mxMembersEvents = @[kMXEventTypeStringRoomMember, kMXEventTypeStringRoomThirdPartyInvite, kMXEventTypeStringRoomPowerLevels]; + + MXWeakify(self); + [self.mxRoom liveTimeline:^(MXEventTimeline *liveTimeline) { + MXStrongifyAndReturnIfNil(self); + + self->membersListener = [liveTimeline listenToEventsOfTypes:mxMembersEvents onEvent:^(MXEvent *event, MXTimelineDirection direction, id customObject) { + + // Consider only live event + if (direction == MXTimelineDirectionForwards) + { + switch (event.eventType) { - MXRoomMember *mxMember = [self.mxRoom.state.members memberWithUserId:event.stateKey]; - if (mxMember) + case MXEventTypeRoomMember: { - // Remove previous occurrence of this member (if any) - [self removeParticipantByKey:mxMember.userId]; - - // If any, remove 3pid invite corresponding to this room member - if (mxMember.thirdPartyInviteToken) + // Take into account updated member + // Ignore here change related to the current user (this change is handled by leaveRoomNotificationObserver) + if ([event.stateKey isEqualToString:self.mxRoom.mxSession.myUser.userId] == NO) { - [self removeParticipantByKey:mxMember.thirdPartyInviteToken]; + MXRoomMember *mxMember = [liveTimeline.state.members memberWithUserId:event.stateKey]; + if (mxMember) + { + // Remove previous occurrence of this member (if any) + [self removeParticipantByKey:mxMember.userId]; + + // If any, remove 3pid invite corresponding to this room member + if (mxMember.thirdPartyInviteToken) + { + [self removeParticipantByKey:mxMember.thirdPartyInviteToken]; + } + + [self handleRoomMember:mxMember]; + + [self finalizeParticipantsList:liveTimeline.state]; + + [self refreshTableView]; + } } - - [self handleRoomMember:mxMember]; - - [self finalizeParticipantsList]; - - [self refreshTableView]; + + break; } + case MXEventTypeRoomThirdPartyInvite: + { + MXRoomThirdPartyInvite *thirdPartyInvite = [liveTimeline.state thirdPartyInviteWithToken:event.stateKey]; + if (thirdPartyInvite) + { + [self addRoomThirdPartyInviteToParticipants:thirdPartyInvite roomState:liveTimeline.state]; + + [self finalizeParticipantsList:liveTimeline.state]; + + [self refreshTableView]; + } + break; + } + case MXEventTypeRoomPowerLevels: + { + [self refreshParticipantsFromRoomMembers]; + + [self refreshTableView]; + break; + } + default: + break; } - - break; } - case MXEventTypeRoomThirdPartyInvite: - { - MXRoomThirdPartyInvite *thirdPartyInvite = [self.mxRoom.state thirdPartyInviteWithToken:event.stateKey]; - if (thirdPartyInvite) - { - [self addRoomThirdPartyInviteToParticipants:thirdPartyInvite]; - - [self finalizeParticipantsList]; - - [self refreshTableView]; - } - break; - } - case MXEventTypeRoomPowerLevels: - { - [self refreshParticipantsFromRoomMembers]; - - [self refreshTableView]; - break; - } - default: - break; - } - } - - }]; - } - else - { - // Search bar header is hidden when no room is provided - _searchBarHeader.hidden = YES; - } - - // Refresh the members list. - [self refreshParticipantsFromRoomMembers]; - - [self refreshTableView]; + }]; + }]; + } + else + { + // Search bar header is hidden when no room is provided + self.searchBarHeader.hidden = YES; + } + + // Refresh the members list. + [self refreshParticipantsFromRoomMembers]; + + [self refreshTableView]; + }]; } - (void)setEnableMention:(BOOL)enableMention @@ -686,36 +708,41 @@ if (self.mxRoom) { // Retrieve the current members from the room state - NSArray *members = [self.mxRoom.state.members membersWithoutConferenceUser]; - NSString *userId = self.mxRoom.mxSession.myUser.userId; - NSArray *roomThirdPartyInvites = self.mxRoom.state.thirdPartyInvites; - - for (MXRoomMember *mxMember in members) - { - // Update the current participants list - if ([mxMember.userId isEqualToString:userId]) + MXWeakify(self); + [self.mxRoom state:^(MXRoomState *roomState) { + MXStrongifyAndReturnIfNil(self); + + NSArray *members = [roomState.members membersWithoutConferenceUser]; + NSString *userId = self.mxRoom.mxSession.myUser.userId; + NSArray *roomThirdPartyInvites = roomState.thirdPartyInvites; + + for (MXRoomMember *mxMember in members) { - if (mxMember.membership == MXMembershipJoin || mxMember.membership == MXMembershipInvite) + // Update the current participants list + if ([mxMember.userId isEqualToString:userId]) { - // The user is in this room - NSString *displayName = NSLocalizedStringFromTable(@"you", @"Vector", nil); - - userParticipant = [[Contact alloc] initMatrixContactWithDisplayName:displayName andMatrixID:userId]; - userParticipant.mxMember = [self.mxRoom.state.members memberWithUserId:userId]; + if (mxMember.membership == MXMembershipJoin || mxMember.membership == MXMembershipInvite) + { + // The user is in this room + NSString *displayName = NSLocalizedStringFromTable(@"you", @"Vector", nil); + + self->userParticipant = [[Contact alloc] initMatrixContactWithDisplayName:displayName andMatrixID:userId]; + self->userParticipant.mxMember = [roomState.members memberWithUserId:userId]; + } + } + else + { + [self handleRoomMember:mxMember]; } } - else + + for (MXRoomThirdPartyInvite *roomThirdPartyInvite in roomThirdPartyInvites) { - [self handleRoomMember:mxMember]; + [self addRoomThirdPartyInviteToParticipants:roomThirdPartyInvite roomState:roomState]; } - } - - for (MXRoomThirdPartyInvite *roomThirdPartyInvite in roomThirdPartyInvites) - { - [self addRoomThirdPartyInviteToParticipants:roomThirdPartyInvite]; - } - - [self finalizeParticipantsList]; + + [self finalizeParticipantsList:roomState]; + }]; } } @@ -766,10 +793,10 @@ } } -- (void)addRoomThirdPartyInviteToParticipants:(MXRoomThirdPartyInvite*)roomThirdPartyInvite +- (void)addRoomThirdPartyInviteToParticipants:(MXRoomThirdPartyInvite*)roomThirdPartyInvite roomState:(MXRoomState*)roomState { // If the homeserver has converted the 3pid invite into a room member, do no show it - if (![self.mxRoom.state memberWithThirdPartyInviteToken:roomThirdPartyInvite.token]) + if (![roomState memberWithThirdPartyInviteToken:roomThirdPartyInvite.token]) { Contact *contact = [[Contact alloc] initMatrixContactWithDisplayName:roomThirdPartyInvite.displayname andMatrixID:nil]; contact.isThirdPartyInvite = YES; @@ -819,7 +846,7 @@ } } -- (void)finalizeParticipantsList +- (void)finalizeParticipantsList:(MXRoomState*)roomState { // Sort contacts by last active, with "active now" first. // ...and then by power @@ -846,7 +873,7 @@ if (userA.currentlyActive && userB.currentlyActive) { // Order first by power levels (admins then moderators then others) - MXRoomPowerLevels *powerLevels = [self.mxRoom.state powerLevels]; + MXRoomPowerLevels *powerLevels = [roomState powerLevels]; NSInteger powerLevelA = [powerLevels powerLevelOfUserWithUserID:contactA.mxMember.userId]; NSInteger powerLevelB = [powerLevels powerLevelOfUserWithUserID:contactB.mxMember.userId]; @@ -1094,8 +1121,10 @@ if (contact.mxMember) { + MXRoomState *roomState = self.mxRoom.dangerousSyncState; + // Update member badge - MXRoomPowerLevels *powerLevels = [self.mxRoom.state powerLevels]; + MXRoomPowerLevels *powerLevels = [roomState powerLevels]; NSInteger powerLevel = [powerLevels powerLevelOfUserWithUserID:contact.mxMember.userId]; if (powerLevel >= kRiotRoomAdminLevel) { @@ -1111,7 +1140,7 @@ // Update the contact display name by considering the current room state. if (contact.mxMember.userId) { - participantCell.contactDisplayNameLabel.text = [self.mxRoom.state.members memberName:contact.mxMember.userId]; + participantCell.contactDisplayNameLabel.text = [roomState.members memberName:contact.mxMember.userId]; } } } diff --git a/Riot/Modules/Room/RoomViewController.m b/Riot/Modules/Room/RoomViewController.m index 5f7e72ec0..f5f1e47a3 100644 --- a/Riot/Modules/Room/RoomViewController.m +++ b/Riot/Modules/Room/RoomViewController.m @@ -545,9 +545,13 @@ // Observe missed notifications mxRoomSummaryDidChangeObserver = [[NSNotificationCenter defaultCenter] addObserverForName:kMXRoomSummaryDidChangeNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notif) { - - [self refreshMissedDiscussionsCount:NO]; - + + MXRoomSummary *roomSummary = notif.object; + + if ([roomSummary.roomId isEqualToString:self.roomDataSource.roomId]) + { + [self refreshMissedDiscussionsCount:NO]; + } }]; [self refreshMissedDiscussionsCount:YES]; @@ -931,9 +935,9 @@ Class roomInputToolbarViewClass = RoomInputToolbarView.class; // Check the user has enough power to post message - if (self.roomDataSource.room.state) + if (self.roomDataSource.roomState) { - MXRoomPowerLevels *powerLevels = self.roomDataSource.room.state.powerLevels; + MXRoomPowerLevels *powerLevels = self.roomDataSource.roomState.powerLevels; NSInteger userPowerLevel = [powerLevels powerLevelOfUserWithUserID:self.mainSession.myUser.userId]; BOOL canSend = (userPowerLevel >= [powerLevels minimumPowerLevelForSendingEventAsMessage:kMXEventTypeStringRoomMessage]); @@ -1894,7 +1898,7 @@ { if ([actionIdentifier isEqualToString:kMXKRoomBubbleCellTapOnAvatarView]) { - selectedRoomMember = [self.roomDataSource.room.state.members memberWithUserId:userInfo[kMXKRoomBubbleCellUserIdKey]]; + selectedRoomMember = [self.roomDataSource.roomState.members memberWithUserId:userInfo[kMXKRoomBubbleCellUserIdKey]]; if (selectedRoomMember) { [self performSegueWithIdentifier:@"showMemberDetails" sender:self]; @@ -1903,7 +1907,7 @@ else if ([actionIdentifier isEqualToString:kMXKRoomBubbleCellLongPressOnAvatarView]) { // Add the member display name in text input - MXRoomMember *roomMember = [self.roomDataSource.room.state.members memberWithUserId:userInfo[kMXKRoomBubbleCellUserIdKey]]; + MXRoomMember *roomMember = [self.roomDataSource.roomState.members memberWithUserId:userInfo[kMXKRoomBubbleCellUserIdKey]]; if (roomMember) { [self mention:roomMember]; @@ -2656,7 +2660,7 @@ NSString *userId = absoluteURLString; - MXRoomMember* member = [self.roomDataSource.room.state.members memberWithUserId:userId]; + MXRoomMember* member = [self.roomDataSource.roomState.members memberWithUserId:userId]; if (member) { // Use the room member detail VC for room members @@ -2765,7 +2769,11 @@ // Files tab [titles addObject: NSLocalizedStringFromTable(@"room_details_files", @"Vector", nil)]; RoomFilesViewController *roomFilesViewController = [RoomFilesViewController roomViewController]; - MXKRoomDataSource *roomFilesDataSource = [[MXKRoomDataSource alloc] initWithRoomId:roomId andMatrixSession:session]; + // @TODO (async-state): This call should be synchronous. Every thing will be fine + __block MXKRoomDataSource *roomFilesDataSource; + [MXKRoomDataSource loadRoomDataSourceWithRoomId:roomId andMatrixSession:session onComplete:^(id roomDataSource) { + roomFilesDataSource = roomDataSource; + }]; roomFilesDataSource.filterMessagesWithURL = YES; [roomFilesDataSource finalizeInitialization]; // Give the data source ownership to the room files view controller. @@ -2858,25 +2866,18 @@ contactsDataSource.contactCellAccessoryImage = [UIImage imageNamed:@"plus_icon"]; // List all the participants matrix user id to ignore them during the contacts search. - MXSession* session = self.roomDataSource.mxSession; - NSString* roomId = self.roomDataSource.roomId; - MXRoom *room = [session roomWithRoomId:roomId]; - if (room) + NSArray *members = [self.roomDataSource.roomState.members membersWithoutConferenceUser]; + for (MXRoomMember *mxMember in members) { - NSArray *members = [room.state.members membersWithoutConferenceUser]; - - for (MXRoomMember *mxMember in members) + // Check his status + if (mxMember.membership == MXMembershipJoin || mxMember.membership == MXMembershipInvite) { - // Check his status - if (mxMember.membership == MXMembershipJoin || mxMember.membership == MXMembershipInvite) - { - // Create the contact related to this member - MXKContact *contact = [[MXKContact alloc] initMatrixContactWithDisplayName:mxMember.displayname andMatrixID:mxMember.userId]; - [contactsDataSource.ignoredContactsByMatrixId setObject:contact forKey:mxMember.userId]; - } + // Create the contact related to this member + MXKContact *contact = [[MXKContact alloc] initMatrixContactWithDisplayName:mxMember.displayname andMatrixID:mxMember.userId]; + [contactsDataSource.ignoredContactsByMatrixId setObject:contact forKey:mxMember.userId]; } } - + [contactsPickerViewController showSearch:YES]; contactsPickerViewController.searchBar.placeholder = NSLocalizedStringFromTable(@"room_participants_invite_another_user", @"Vector", nil); @@ -3067,7 +3068,7 @@ // In case of conference call, check that the user has enough power level else if (self.roomDataSource.room.summary.membersCount.joined > 2 && - ![MXCallManager canPlaceConferenceCallInRoom:self.roomDataSource.room]) + ![MXCallManager canPlaceConferenceCallInRoom:self.roomDataSource.room roomState:self.roomDataSource.roomState]) { [currentAlert dismissViewControllerAnimated:NO completion:nil]; @@ -3195,18 +3196,21 @@ [self showExpandedHeader:NO]; // Dismiss potential keyboard. [self dismissKeyboard]; - - MXKRoomDataSource *roomDataSource; + // Jump to the last unread event by using a temporary room data source initialized with the last unread event id. - roomDataSource = [[RoomDataSource alloc] initWithRoomId:self.roomDataSource.roomId initialEventId:self.roomDataSource.room.accountData.readMarkerEventId andMatrixSession:self.mainSession]; - [roomDataSource finalizeInitialization]; - - // Center the bubbles table content on the bottom of the read marker event in order to display correctly the read marker view. - self.centerBubblesTableViewContentOnTheInitialEventBottom = YES; - [self displayRoom:roomDataSource]; - - // Give the data source ownership to the room view controller. - self.hasRoomDataSourceOwnership = YES; + MXWeakify(self); + [RoomDataSource loadRoomDataSourceWithRoomId:self.roomDataSource.roomId initialEventId:self.roomDataSource.room.accountData.readMarkerEventId andMatrixSession:self.mainSession onComplete:^(id roomDataSource) { + MXStrongifyAndReturnIfNil(self); + + [roomDataSource finalizeInitialization]; + + // Center the bubbles table content on the bottom of the read marker event in order to display correctly the read marker view. + self.centerBubblesTableViewContentOnTheInitialEventBottom = YES; + [self displayRoom:roomDataSource]; + + // Give the data source ownership to the room view controller. + self.hasRoomDataSourceOwnership = YES; + }]; } else if (sender == self.resetReadMarkerButton) { @@ -3474,13 +3478,17 @@ // If an event was specified, replace the datasource by a non live datasource showing the event if (eventId) { - RoomDataSource *roomDataSource = [[RoomDataSource alloc] initWithRoomId:self.roomDataSource.roomId initialEventId:eventId andMatrixSession:self.mainSession]; - [roomDataSource finalizeInitialization]; - roomDataSource.markTimelineInitialEvent = YES; - - [self displayRoom:roomDataSource]; - - self.hasRoomDataSourceOwnership = YES; + MXWeakify(self); + [RoomDataSource loadRoomDataSourceWithRoomId:self.roomDataSource.roomId initialEventId:eventId andMatrixSession:self.mainSession onComplete:^(id roomDataSource) { + MXStrongifyAndReturnIfNil(self); + + [roomDataSource finalizeInitialization]; + ((RoomDataSource*)roomDataSource).markTimelineInitialEvent = YES; + + [self displayRoom:roomDataSource]; + + self.hasRoomDataSourceOwnership = YES; + }]; } else { @@ -3555,8 +3563,13 @@ // Remove the previous live listener if (typingNotifListener) { - [self.roomDataSource.room.liveTimeline removeListener:typingNotifListener]; - typingNotifListener = nil; + MXWeakify(self); + [self.roomDataSource.room liveTimeline:^(MXEventTimeline *liveTimeline) { + MXStrongifyAndReturnIfNil(self); + + [liveTimeline removeListener:self->typingNotifListener]; + self->typingNotifListener = nil; + }]; } } @@ -3568,8 +3581,10 @@ if (self.roomDataSource) { // Add typing notification listener - typingNotifListener = [self.roomDataSource.room.liveTimeline listenToEventsOfTypes:@[kMXEventTypeStringTypingNotification] onEvent:^(MXEvent *event, MXTimelineDirection direction, MXRoomState *roomState) { - + MXWeakify(self); + self->typingNotifListener = [self.roomDataSource.room listenToEventsOfTypes:@[kMXEventTypeStringTypingNotification] onEvent:^(MXEvent *event, MXTimelineDirection direction, MXRoomState *roomState) { + MXStrongifyAndReturnIfNil(self); + // Handle only live events if (direction == MXTimelineDirectionForwards) { @@ -3581,17 +3596,16 @@ { [typingUsers removeObjectAtIndex:index]; } - + // Ignore this notification if both arrays are empty - if (currentTypingUsers.count || typingUsers.count) + if (self->currentTypingUsers.count || typingUsers.count) { - currentTypingUsers = typingUsers; + self->currentTypingUsers = typingUsers; [self refreshActivitiesViewDisplay]; } } - }]; - + // Retrieve the current typing users list NSMutableArray *typingUsers = [NSMutableArray arrayWithArray:self.roomDataSource.room.typingUsers]; // Remove typing info for the current user @@ -3621,7 +3635,7 @@ { NSString* name = [currentTypingUsers objectAtIndex:i]; - MXRoomMember* member = [self.roomDataSource.room.state.members memberWithUserId:name]; + MXRoomMember* member = [self.roomDataSource.roomState.members memberWithUserId:name]; if (member && member.displayname.length) { @@ -3754,7 +3768,8 @@ - (NSUInteger)widgetsCount:(BOOL)includeUserWidgets { NSUInteger widgetsCount = [[WidgetManager sharedManager] widgetsNotOfTypes:@[kWidgetTypeJitsi] - inRoom:self.roomDataSource.room].count; + inRoom:self.roomDataSource.room + withRoomState:self.roomDataSource.roomState].count; if (includeUserWidgets) { widgetsCount += [[WidgetManager sharedManager] userWidgets:self.roomDataSource.room.mxSession].count; @@ -3783,7 +3798,7 @@ { [roomActivitiesView displayNetworkErrorNotification:NSLocalizedStringFromTable(@"room_offline_notification", @"Vector", nil)]; } - else if (customizedRoomDataSource.room.state.isOngoingConferenceCall) + else if (customizedRoomDataSource.roomState.isOngoingConferenceCall) { // Show the "Ongoing conference call" banner only if the user is not in the conference MXCall *callInRoom = [self.roomDataSource.mxSession.callManager callInRoom:self.roomDataSource.roomId]; @@ -3928,24 +3943,28 @@ { // Switch back to the room live timeline managed by MXKRoomDataSourceManager MXKRoomDataSourceManager *roomDataSourceManager = [MXKRoomDataSourceManager sharedManagerForMatrixSession:self.mainSession]; - MXKRoomDataSource *roomDataSource = [roomDataSourceManager roomDataSourceForRoom:self.roomDataSource.roomId create:YES]; - - // Scroll to bottom the bubble history on the display refresh. - shouldScrollToBottomOnTableRefresh = YES; - - [self displayRoom:roomDataSource]; - - // The room view controller do not have here the data source ownership. - self.hasRoomDataSourceOwnership = NO; - - [self refreshActivitiesViewDisplay]; - [self refreshJumpToLastUnreadBannerDisplay]; - - if (self.saveProgressTextInput) - { - // Restore the potential message partially typed before jump to last unread messages. - self.inputToolbarView.textMessage = roomDataSource.partialTextMessage; - } + + MXWeakify(self); + [roomDataSourceManager roomDataSourceForRoom:self.roomDataSource.roomId create:YES onComplete:^(MXKRoomDataSource *roomDataSource) { + MXStrongifyAndReturnIfNil(self); + + // Scroll to bottom the bubble history on the display refresh. + self->shouldScrollToBottomOnTableRefresh = YES; + + [self displayRoom:roomDataSource]; + + // The room view controller do not have here the data source ownership. + self.hasRoomDataSourceOwnership = NO; + + [self refreshActivitiesViewDisplay]; + [self refreshJumpToLastUnreadBannerDisplay]; + + if (self.saveProgressTextInput) + { + // Restore the potential message partially typed before jump to last unread messages. + self.inputToolbarView.textMessage = roomDataSource.partialTextMessage; + } + }]; } } diff --git a/Riot/Modules/Room/Search/DataSources/RoomSearchDataSource.m b/Riot/Modules/Room/Search/DataSources/RoomSearchDataSource.m index eb136ed34..a375117db 100644 --- a/Riot/Modules/Room/Search/DataSources/RoomSearchDataSource.m +++ b/Riot/Modules/Room/Search/DataSources/RoomSearchDataSource.m @@ -52,7 +52,7 @@ [super destroy]; } -- (void)convertHomeserverResultsIntoCells:(MXSearchRoomEventResults *)roomEventResults +- (void)convertHomeserverResultsIntoCells:(MXSearchRoomEventResults *)roomEventResults onComplete:(dispatch_block_t)onComplete { // Prepare text font used to highlight the search pattern. UIFont *patternFont = [roomDataSource.eventFormatter bingTextFont]; @@ -64,7 +64,7 @@ // The search result contains only room message events, no state events. // Thus, passing the current room state is not a huge problem. Only // the user display name and his avatar may be wrong. - RoomBubbleCellData *cellData = [[RoomBubbleCellData alloc] initWithEvent:result.result andRoomState:roomDataSource.room.state andRoomDataSource:roomDataSource]; + RoomBubbleCellData *cellData = [[RoomBubbleCellData alloc] initWithEvent:result.result andRoomState:roomDataSource.roomState andRoomDataSource:roomDataSource]; if (cellData) { // Highlight the search pattern @@ -73,6 +73,8 @@ [cellDataArray insertObject:cellData atIndex:0]; } } + + onComplete(); } - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath diff --git a/Riot/Modules/Room/Search/RoomSearchViewController.m b/Riot/Modules/Room/Search/RoomSearchViewController.m index 19d50a700..95f1e2015 100644 --- a/Riot/Modules/Room/Search/RoomSearchViewController.m +++ b/Riot/Modules/Room/Search/RoomSearchViewController.m @@ -290,14 +290,19 @@ if (selectedSearchEvent) { RoomViewController *roomViewController = segue.destinationViewController; - RoomDataSource *roomDataSource = [[RoomDataSource alloc] initWithRoomId:selectedSearchEvent.roomId initialEventId:selectedSearchEvent.eventId andMatrixSession:selectedSearchEventSession]; - [roomDataSource finalizeInitialization]; - roomDataSource.markTimelineInitialEvent = YES; - - [roomViewController displayRoom:roomDataSource]; - roomViewController.hasRoomDataSourceOwnership = YES; - - roomViewController.navigationItem.leftItemsSupplementBackButton = YES; + + [RoomDataSource loadRoomDataSourceWithRoomId:selectedSearchEvent.roomId + initialEventId:selectedSearchEvent.eventId + andMatrixSession:selectedSearchEventSession onComplete:^(RoomDataSource *roomDataSource) { + + [roomDataSource finalizeInitialization]; + roomDataSource.markTimelineInitialEvent = YES; + + [roomViewController displayRoom:roomDataSource]; + roomViewController.hasRoomDataSourceOwnership = YES; + + roomViewController.navigationItem.leftItemsSupplementBackButton = YES; + }]; } // Hide back button title diff --git a/Riot/Modules/Room/Settings/RoomSettingsViewController.m b/Riot/Modules/Room/Settings/RoomSettingsViewController.m index 4355bf9b4..7d1605f6a 100644 --- a/Riot/Modules/Room/Settings/RoomSettingsViewController.m +++ b/Riot/Modules/Room/Settings/RoomSettingsViewController.m @@ -190,8 +190,8 @@ NSString *const kRoomSettingsAdvancedE2eEnabledCellViewIdentifier = @"kRoomSetti [super initWithSession:session andRoomId:roomId]; // Add an additional listener to update banned users - extraEventsListener = [mxRoom.liveTimeline listenToEventsOfTypes:@[kMXEventTypeStringRoomMember] onEvent:^(MXEvent *event, MXTimelineDirection direction, MXRoomState *roomState) { - + self->extraEventsListener = [mxRoom listenToEventsOfTypes:@[kMXEventTypeStringRoomMember] onEvent:^(MXEvent *event, MXTimelineDirection direction, MXRoomState *roomState) { + if (direction == MXTimelineDirectionForwards) { [self updateRoomState:roomState]; @@ -399,8 +399,13 @@ NSString *const kRoomSettingsAdvancedE2eEnabledCellViewIdentifier = @"kRoomSetti if (extraEventsListener) { - [mxRoom.liveTimeline removeListener:extraEventsListener]; - extraEventsListener = nil; + MXWeakify(self); + [mxRoom liveTimeline:^(MXEventTimeline *liveTimeline) { + MXStrongifyAndReturnIfNil(self); + + [liveTimeline removeListener:self->extraEventsListener]; + self->extraEventsListener = nil; + }]; } [super destroy]; @@ -467,7 +472,7 @@ NSString *const kRoomSettingsAdvancedE2eEnabledCellViewIdentifier = @"kRoomSetti if (!eventTypeForSelectedField) return; - MXRoomPowerLevels *powerLevels = [mxRoom.state powerLevels]; + MXRoomPowerLevels *powerLevels = [mxRoomState powerLevels]; NSInteger oneSelfPowerLevel = [powerLevels powerLevelOfUserWithUserID:self.mainSession.myUser.userId]; if (oneSelfPowerLevel < [powerLevels minimumPowerLevelForSendingEventAsStateEvent:eventTypeForSelectedField]) @@ -1963,7 +1968,7 @@ NSString *const kRoomSettingsAdvancedE2eEnabledCellViewIdentifier = @"kRoomSetti if (self.mainSession) { // Check user's power level to know whether the user is allowed to add room alias - MXRoomPowerLevels *powerLevels = [mxRoom.state powerLevels]; + MXRoomPowerLevels *powerLevels = [mxRoomState powerLevels]; NSInteger oneSelfPowerLevel = [powerLevels powerLevelOfUserWithUserID:self.mainSession.myUser.userId]; if (oneSelfPowerLevel >= [powerLevels minimumPowerLevelForSendingEventAsStateEvent:kMXEventTypeStringRoomAliases]) @@ -1981,7 +1986,7 @@ NSString *const kRoomSettingsAdvancedE2eEnabledCellViewIdentifier = @"kRoomSetti if (self.mainSession) { // Check user's power level to know whether the user is allowed to add communities to this room - MXRoomPowerLevels *powerLevels = [mxRoom.state powerLevels]; + MXRoomPowerLevels *powerLevels = [mxRoomState powerLevels]; NSInteger oneSelfPowerLevel = [powerLevels powerLevelOfUserWithUserID:self.mainSession.myUser.userId]; if (oneSelfPowerLevel >= [powerLevels minimumPowerLevelForSendingEventAsStateEvent:kMXEventTypeStringRoomRelatedGroups]) @@ -2118,7 +2123,7 @@ NSString *const kRoomSettingsAdvancedE2eEnabledCellViewIdentifier = @"kRoomSetti UITableViewCell* cell; // Check user's power level to know which settings are editable. - MXRoomPowerLevels *powerLevels = [mxRoom.state powerLevels]; + MXRoomPowerLevels *powerLevels = [mxRoomState powerLevels]; NSInteger oneSelfPowerLevel = [powerLevels powerLevelOfUserWithUserID:self.mainSession.myUser.userId]; // general settings @@ -2732,7 +2737,7 @@ NSString *const kRoomSettingsAdvancedE2eEnabledCellViewIdentifier = @"kRoomSetti else { // Check user's power level to know whether the user is allowed to turn on the encryption mode - MXRoomPowerLevels *powerLevels = [mxRoom.state powerLevels]; + MXRoomPowerLevels *powerLevels = [mxRoomState powerLevels]; NSInteger oneSelfPowerLevel = [powerLevels powerLevelOfUserWithUserID:self.mainSession.myUser.userId]; if (oneSelfPowerLevel >= [powerLevels minimumPowerLevelForSendingEventAsStateEvent:kMXEventTypeStringRoomEncryption]) diff --git a/Riot/Modules/Room/Views/Title/Expanded/ExpandedRoomTitleView.m b/Riot/Modules/Room/Views/Title/Expanded/ExpandedRoomTitleView.m index fe7de2297..f09a0990f 100644 --- a/Riot/Modules/Room/Views/Title/Expanded/ExpandedRoomTitleView.m +++ b/Riot/Modules/Room/Views/Title/Expanded/ExpandedRoomTitleView.m @@ -65,46 +65,51 @@ self.roomTopic.text = [MXTools stripNewlineCharacters:self.mxRoom.summary.topic]; // Compute active members count - NSArray *members = [self.mxRoom.state.members membersWithMembership:MXMembershipJoin includeConferenceUser:NO]; - NSUInteger activeCount = 0; - NSUInteger memberCount = 0; - for (MXRoomMember *mxMember in members) - { - memberCount ++; + MXWeakify(self); + [self.mxRoom members:^(MXRoomMembers *roomMembers) { + MXStrongifyAndReturnIfNil(self); - // Get the user that corresponds to this member - MXUser *user = [self.mxRoom.mxSession userWithUserId:mxMember.userId]; - // existing user ? - if (user && user.presence == MXPresenceOnline) + NSArray *members = [roomMembers membersWithMembership:MXMembershipJoin includeConferenceUser:NO]; + NSUInteger activeCount = 0; + NSUInteger memberCount = 0; + for (MXRoomMember *mxMember in members) { - activeCount ++; - } - } + memberCount ++; - if (memberCount) - { - // Check whether the logged in user is alone in this room - if (memberCount == 1 && self.mxRoom.summary.membership == MXMembershipJoin) - { - self.roomMembers.text = NSLocalizedStringFromTable(@"room_title_invite_members", @"Vector", nil); - } - else - { - if (activeCount > 1) + // Get the user that corresponds to this member + MXUser *user = [self.mxRoom.mxSession userWithUserId:mxMember.userId]; + // existing user ? + if (user && user.presence == MXPresenceOnline) { - self.roomMembers.text = [NSString stringWithFormat:NSLocalizedStringFromTable(@"room_title_multiple_active_members", @"Vector", nil), @(activeCount), @(memberCount)]; + activeCount ++; + } + } + + if (memberCount) + { + // Check whether the logged in user is alone in this room + if (memberCount == 1 && self.mxRoom.summary.membership == MXMembershipJoin) + { + self.roomMembers.text = NSLocalizedStringFromTable(@"room_title_invite_members", @"Vector", nil); } else { - self.roomMembers.text = [NSString stringWithFormat:NSLocalizedStringFromTable(@"room_title_one_active_member", @"Vector", nil), @(activeCount), @(memberCount)]; + if (activeCount > 1) + { + self.roomMembers.text = [NSString stringWithFormat:NSLocalizedStringFromTable(@"room_title_multiple_active_members", @"Vector", nil), @(activeCount), @(memberCount)]; + } + else + { + self.roomMembers.text = [NSString stringWithFormat:NSLocalizedStringFromTable(@"room_title_one_active_member", @"Vector", nil), @(activeCount), @(memberCount)]; + } } } - } - else - { - // Should not happen - self.roomMembers.text = nil; - } + else + { + // Should not happen + self.roomMembers.text = nil; + } + }]; } else { diff --git a/Riot/Modules/Room/Views/Title/Preview/PreviewRoomTitleView.m b/Riot/Modules/Room/Views/Title/Preview/PreviewRoomTitleView.m index 6d63b7e0e..6e305091c 100644 --- a/Riot/Modules/Room/Views/Title/Preview/PreviewRoomTitleView.m +++ b/Riot/Modules/Room/Views/Title/Preview/PreviewRoomTitleView.m @@ -182,51 +182,56 @@ self.roomTopic.text = [MXTools stripNewlineCharacters:self.mxRoom.summary.topic]; // Compute active members count, and look for the inviter - NSArray *members = self.mxRoom.state.members.members; - NSUInteger activeCount = 0; - NSUInteger memberCount = 0; - NSString *inviter = nil; - - for (MXRoomMember *mxMember in members) - { - if (mxMember.membership == MXMembershipJoin) + MXWeakify(self); + [self.mxRoom members:^(MXRoomMembers *roomMembers) { + MXStrongifyAndReturnIfNil(self); + + NSArray *members = roomMembers.members; + NSUInteger activeCount = 0; + NSUInteger memberCount = 0; + NSString *inviter = nil; + + for (MXRoomMember *mxMember in members) { - memberCount ++; - - // Get the user that corresponds to this member - MXUser *user = [self.mxRoom.mxSession userWithUserId:mxMember.userId]; - // existing user ? - if (user && user.presence == MXPresenceOnline) + if (mxMember.membership == MXMembershipJoin) { - activeCount ++; + memberCount ++; + + // Get the user that corresponds to this member + MXUser *user = [self.mxRoom.mxSession userWithUserId:mxMember.userId]; + // existing user ? + if (user && user.presence == MXPresenceOnline) + { + activeCount ++; + } + + // Presently only one member is available from invited room data + // This is the inviter + inviter = mxMember.displayname.length ? mxMember.displayname : mxMember.userId; } - - // Presently only one member is available from invited room data - // This is the inviter - inviter = mxMember.displayname.length ? mxMember.displayname : mxMember.userId; } - } - - // FIXME: Display members status when it will be available - self.roomMembers.text = nil; -// if (memberCount) -// { -// if (activeCount > 1) -// { -// self.roomMembers.text = [NSString stringWithFormat:NSLocalizedStringFromTable(@"room_title_multiple_active_members", @"Vector", nil), @(activeCount), @(memberCount)]; -// } -// else -// { -// self.roomMembers.text = [NSString stringWithFormat:NSLocalizedStringFromTable(@"room_title_one_active_member", @"Vector", nil), @(activeCount), @(memberCount)]; -// } -// } -// else -// { -// // Should not happen -// self.roomMembers.text = nil; -// } - - self.previewLabel.text = [NSString stringWithFormat:NSLocalizedStringFromTable(@"room_preview_invitation_format", @"Vector", nil), inviter]; + + // FIXME: Display members status when it will be available + self.roomMembers.text = nil; +// if (memberCount) +// { +// if (activeCount > 1) +// { +// self.roomMembers.text = [NSString stringWithFormat:NSLocalizedStringFromTable(@"room_title_multiple_active_members", @"Vector", nil), @(activeCount), @(memberCount)]; +// } +// else +// { +// self.roomMembers.text = [NSString stringWithFormat:NSLocalizedStringFromTable(@"room_title_one_active_member", @"Vector", nil), @(activeCount), @(memberCount)]; +// } +// } +// else +// { +// // Should not happen +// self.roomMembers.text = nil; +// } + + self.previewLabel.text = [NSString stringWithFormat:NSLocalizedStringFromTable(@"room_preview_invitation_format", @"Vector", nil), inviter]; + }]; } else { diff --git a/Riot/Modules/Settings/SettingsViewController.m b/Riot/Modules/Settings/SettingsViewController.m index c8c0e4da7..a4367d1a1 100644 --- a/Riot/Modules/Settings/SettingsViewController.m +++ b/Riot/Modules/Settings/SettingsViewController.m @@ -2975,8 +2975,9 @@ typedef void (^blockSettingsViewController_onReadyToDestroy)(); { if (room.summary.isEncrypted) { - MXKRoomDataSource *roomDataSource = [roomDataSourceManager roomDataSourceForRoom:room.roomId create:NO]; - [roomDataSource reload]; + [roomDataSourceManager roomDataSourceForRoom:room.roomId create:NO onComplete:^(MXKRoomDataSource *roomDataSource) { + [roomDataSource reload]; + }]; } } diff --git a/Riot/Modules/TabBar/MasterTabBarController.m b/Riot/Modules/TabBar/MasterTabBarController.m index 25da2d2ab..dd4e2e756 100644 --- a/Riot/Modules/TabBar/MasterTabBarController.m +++ b/Riot/Modules/TabBar/MasterTabBarController.m @@ -1,6 +1,7 @@ /* Copyright 2017 Vector Creations Ltd - + Copyright 2018 New Vector Ltd + Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at @@ -510,59 +511,37 @@ if ([[segue identifier] isEqualToString:@"showRoomDetails"]) { - // Replace the rootviewcontroller with a room view controller - // Get the RoomViewController from the storyboard - UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@"Main" bundle:[NSBundle mainBundle]]; - _currentRoomViewController = [storyboard instantiateViewControllerWithIdentifier:@"RoomViewControllerStoryboardId"]; - - navigationController.viewControllers = @[_currentRoomViewController]; - if (!_selectedRoomPreviewData) { - MXKRoomDataSource *roomDataSource; - - // Check whether an event has been selected from messages or files search tab. - MXEvent *selectedSearchEvent = unifiedSearchViewController.selectedSearchEvent; - MXSession *selectedSearchEventSession = unifiedSearchViewController.selectedSearchEventSession; - - if (!selectedSearchEvent) - { - if (!_selectedEventId) - { - // LIVE: Show the room live timeline managed by MXKRoomDataSourceManager - MXKRoomDataSourceManager *roomDataSourceManager = [MXKRoomDataSourceManager sharedManagerForMatrixSession:_selectedRoomSession]; - roomDataSource = [roomDataSourceManager roomDataSourceForRoom:_selectedRoomId create:YES]; - } - else - { - // Open the room on the requested event - roomDataSource = [[RoomDataSource alloc] initWithRoomId:_selectedRoomId initialEventId:_selectedEventId andMatrixSession:_selectedRoomSession]; - [roomDataSource finalizeInitialization]; - - ((RoomDataSource*)roomDataSource).markTimelineInitialEvent = YES; - - // Give the data source ownership to the room view controller. - _currentRoomViewController.hasRoomDataSourceOwnership = YES; - } - } - else - { - // Search result: Create a temp timeline from the selected event - roomDataSource = [[RoomDataSource alloc] initWithRoomId:selectedSearchEvent.roomId initialEventId:selectedSearchEvent.eventId andMatrixSession:selectedSearchEventSession]; - [roomDataSource finalizeInitialization]; - - ((RoomDataSource*)roomDataSource).markTimelineInitialEvent = YES; - - // Give the data source ownership to the room view controller. - _currentRoomViewController.hasRoomDataSourceOwnership = YES; - } - - [_currentRoomViewController displayRoom:roomDataSource]; + MXWeakify(self); + [self dataSourceOfRoomToDisplay:^(MXKRoomDataSource *roomDataSource) { + MXStrongifyAndReturnIfNil(self); + + // Replace the rootviewcontroller with a room view controller + // Get the RoomViewController from the storyboard + UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@"Main" bundle:[NSBundle mainBundle]]; + self->_currentRoomViewController = [storyboard instantiateViewControllerWithIdentifier:@"RoomViewControllerStoryboardId"]; + + navigationController.viewControllers = @[self.currentRoomViewController]; + + [self.currentRoomViewController displayRoom:roomDataSource]; + + [self setupLeftBarButtonItem]; + }]; } else { + // Replace the rootviewcontroller with a room view controller + // Get the RoomViewController from the storyboard + UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@"Main" bundle:[NSBundle mainBundle]]; + _currentRoomViewController = [storyboard instantiateViewControllerWithIdentifier:@"RoomViewControllerStoryboardId"]; + + navigationController.viewControllers = @[_currentRoomViewController]; + [_currentRoomViewController displayRoomPreview:_selectedRoomPreviewData]; _selectedRoomPreviewData = nil; + + [self setupLeftBarButtonItem]; } } else if ([[segue identifier] isEqualToString:@"showContactDetails"]) @@ -573,6 +552,8 @@ _currentContactDetailViewController.contact = _selectedContact; navigationController.viewControllers = @[_currentContactDetailViewController]; + + [self setupLeftBarButtonItem]; } else { @@ -581,28 +562,8 @@ [_currentGroupDetailViewController setGroup:_selectedGroup withMatrixSession:_selectedGroupSession]; navigationController.viewControllers = @[_currentGroupDetailViewController]; - } - - if (self.splitViewController) - { - // Refresh selected cell without scrolling the selected cell (We suppose it's visible here) - [self refreshCurrentSelectedCell:NO]; - - if (_currentRoomViewController) - { - _currentRoomViewController.navigationItem.leftBarButtonItem = self.splitViewController.displayModeButtonItem; - _currentRoomViewController.navigationItem.leftItemsSupplementBackButton = YES; - } - else if (_currentContactDetailViewController) - { - _currentContactDetailViewController.navigationItem.leftBarButtonItem = self.splitViewController.displayModeButtonItem; - _currentContactDetailViewController.navigationItem.leftItemsSupplementBackButton = YES; - } - else if (_currentGroupDetailViewController) - { - _currentGroupDetailViewController.navigationItem.leftBarButtonItem = self.splitViewController.displayModeButtonItem; - _currentGroupDetailViewController.navigationItem.leftItemsSupplementBackButton = YES; - } + + [self setupLeftBarButtonItem]; } } else @@ -647,6 +608,84 @@ self.navigationController.topViewController.navigationItem.backBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:@"" style:UIBarButtonItemStylePlain target:nil action:nil]; } +/** + Load the data source of the room to open. + + @param onComplete a block providing the loaded room data source. + */ +- (void)dataSourceOfRoomToDisplay:(void (^)(MXKRoomDataSource *roomDataSource))onComplete +{ + // Check whether an event has been selected from messages or files search tab. + MXEvent *selectedSearchEvent = unifiedSearchViewController.selectedSearchEvent; + MXSession *selectedSearchEventSession = unifiedSearchViewController.selectedSearchEventSession; + + if (!selectedSearchEvent) + { + if (!_selectedEventId) + { + // LIVE: Show the room live timeline managed by MXKRoomDataSourceManager + MXKRoomDataSourceManager *roomDataSourceManager = [MXKRoomDataSourceManager sharedManagerForMatrixSession:_selectedRoomSession]; + + [roomDataSourceManager roomDataSourceForRoom:_selectedRoomId create:YES onComplete:^(MXKRoomDataSource *roomDataSource) { + onComplete(roomDataSource); + }]; + } + else + { + // Open the room on the requested event + [RoomDataSource loadRoomDataSourceWithRoomId:_selectedRoomId initialEventId:_selectedEventId andMatrixSession:_selectedRoomSession onComplete:^(id roomDataSource) { + + ((RoomDataSource*)roomDataSource).markTimelineInitialEvent = YES; + + // Give the data source ownership to the room view controller. + self.currentRoomViewController.hasRoomDataSourceOwnership = YES; + + onComplete(roomDataSource); + }]; + } + } + else + { + // Search result: Create a temp timeline from the selected event + [RoomDataSource loadRoomDataSourceWithRoomId:selectedSearchEvent.roomId initialEventId:selectedSearchEvent.eventId andMatrixSession:selectedSearchEventSession onComplete:^(id roomDataSource) { + + [roomDataSource finalizeInitialization]; + + ((RoomDataSource*)roomDataSource).markTimelineInitialEvent = YES; + + // Give the data source ownership to the room view controller. + self.currentRoomViewController.hasRoomDataSourceOwnership = YES; + + onComplete(roomDataSource); + }]; + } +} + +- (void)setupLeftBarButtonItem +{ + if (self.splitViewController) + { + // Refresh selected cell without scrolling the selected cell (We suppose it's visible here) + [self refreshCurrentSelectedCell:NO]; + + if (_currentRoomViewController) + { + _currentRoomViewController.navigationItem.leftBarButtonItem = self.splitViewController.displayModeButtonItem; + _currentRoomViewController.navigationItem.leftItemsSupplementBackButton = YES; + } + else if (_currentContactDetailViewController) + { + _currentContactDetailViewController.navigationItem.leftBarButtonItem = self.splitViewController.displayModeButtonItem; + _currentContactDetailViewController.navigationItem.leftItemsSupplementBackButton = YES; + } + else if (_currentGroupDetailViewController) + { + _currentGroupDetailViewController.navigationItem.leftBarButtonItem = self.splitViewController.displayModeButtonItem; + _currentGroupDetailViewController.navigationItem.leftItemsSupplementBackButton = YES; + } + } +} + - (void)presentViewController:(UIViewController *)viewControllerToPresent animated:(BOOL)flag completion:(void (^)(void))completion { // Keep ref on presented view controller diff --git a/Riot/Utils/EventFormatter.m b/Riot/Utils/EventFormatter.m index 2015a9bbc..122241393 100644 --- a/Riot/Utils/EventFormatter.m +++ b/Riot/Utils/EventFormatter.m @@ -262,9 +262,9 @@ NSString *const kEventFormatterOnReRequestKeysLinkActionSeparator = @"/"; #pragma mark - MXRoomSummaryUpdating -- (BOOL)session:(MXSession *)session updateRoomSummary:(MXRoomSummary *)summary withStateEvents:(NSArray *)stateEvents +- (BOOL)session:(MXSession *)session updateRoomSummary:(MXRoomSummary *)summary withStateEvents:(NSArray *)stateEvents roomState:(MXRoomState *)roomState { - BOOL ret = [super session:session updateRoomSummary:summary withStateEvents:stateEvents]; + BOOL ret = [super session:session updateRoomSummary:summary withStateEvents:stateEvents roomState:roomState]; // Check whether the room display name and/or the room avatar url should be updated at Riot level. BOOL refreshRiotRoomDisplayName = NO; @@ -303,7 +303,7 @@ NSString *const kEventFormatterOnReRequestKeysLinkActionSeparator = @"/"; if (refreshRiotRoomDisplayName) { - NSString *riotRoomDisplayName = [self riotRoomDisplayNameFromRoomState:summary.room.state]; + NSString *riotRoomDisplayName = [self riotRoomDisplayNameFromRoomState:roomState]; if (riotRoomDisplayName.length && ![summary.displayname isEqualToString:riotRoomDisplayName]) { @@ -313,7 +313,7 @@ NSString *const kEventFormatterOnReRequestKeysLinkActionSeparator = @"/"; } if (refreshRiotRoomAvatarURL) { - NSString *riotRoomAvatarURL = [self riotRoomAvatarURLFromRoomState:summary.room.state]; + NSString *riotRoomAvatarURL = [self riotRoomAvatarURLFromRoomState:roomState]; if (riotRoomAvatarURL.length && ![summary.avatar isEqualToString:riotRoomAvatarURL]) {