diff --git a/Riot/Modules/MatrixKit/Models/Room/MXKRoomDataSource.m b/Riot/Modules/MatrixKit/Models/Room/MXKRoomDataSource.m index d7854a5cd..ffa461e27 100644 --- a/Riot/Modules/MatrixKit/Models/Room/MXKRoomDataSource.m +++ b/Riot/Modules/MatrixKit/Models/Room/MXKRoomDataSource.m @@ -201,6 +201,7 @@ typedef NS_ENUM (NSUInteger, MXKRoomDataSourceError) { @property (nonatomic, assign) BOOL shouldStopBackPagination; @property (nonatomic, readwrite) MXRoom *room; +@property (nonatomic, readwrite) MXThread *thread; @property (nonatomic, readwrite) MXRoom *secondaryRoom; @property (nonatomic, strong) id secondaryTimeline; @@ -258,9 +259,18 @@ typedef NS_ENUM (NSUInteger, MXKRoomDataSourceError) { // Asynchronously preload data here so that the data will be ready later // to synchronously respond to that request - [roomDataSource.room liveTimeline:^(id liveTimeline) { - onComplete(roomDataSource); - }]; + if (roomDataSource.threadId) + { + [roomDataSource.thread liveTimeline:^(id _Nonnull liveTimeline) { + onComplete(roomDataSource); + }]; + } + else + { + [roomDataSource.room liveTimeline:^(id liveTimeline) { + onComplete(roomDataSource); + }]; + } } } @@ -622,209 +632,311 @@ typedef NS_ENUM (NSUInteger, MXKRoomDataSourceError) { { if (MXSessionStateStoreDataReady <= self.mxSession.state) { - // Check whether the room is not already set - if (!_room) + if (_threadId) { - // Are we peeking into a random room or displaying a room the user is part of? - if (peekingRoom) + [self initializeTimelineForThread]; + } + else + { + [self initializeTimelineForRoom]; + } + } +} + +- (void)initializeTimelineForRoom +{ + // Check whether the room is not already set + if (!_room) + { + // Are we peeking into a random room or displaying a room the user is part of? + if (peekingRoom) + { + self.room = peekingRoom; + } + else + { + self.room = [self.mxSession roomWithRoomId:_roomId]; + } + + if (_room) + { + // This is the time to set up the timeline according to the called init method + if (_isLive) { - self.room = peekingRoom; + // LIVE + MXWeakify(self); + [_room liveTimeline:^(id liveTimeline) { + MXStrongifyAndReturnIfNil(self); + + self->_timeline = liveTimeline; + + // Only one pagination process can be done at a time by an MXRoom object. + // This assumption is satisfied by MatrixKit. Only MXRoomDataSource does it. + [self.timeline resetPagination]; + + // 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.mxSession == room.mxSession && ([self.roomId isEqualToString:room.roomId] || + ([self.secondaryRoomId isEqualToString:room.roomId]))) + { + // The existing room history has been flushed during server sync because a gap has been observed between local and server storage. + [self reload]; + } + + }]; + + // Add the event listeners, by considering all the event types (the event filtering is applying by the event formatter), + // except if only the events with a url key in their content must be handled. + [self refreshEventListeners:(self.filterMessagesWithURL ? @[kMXEventTypeStringRoomMessage] : [MXKAppSettings standardAppSettings].allEventTypesForMessages)]; + + // display typing notifications is optional + // the inherited class can manage them by its own. + if (self.showTypingNotifications) + { + // Register on typing notif + [self listenTypingNotifications]; + } + + // Manage unsent messages + [self handleUnsentMessages]; + + // Update here data source state if it is not already ready + if (!self->_secondaryRoomId) + { + [self setState:MXKDataSourceStateReady]; + } + + // Check user membership in this room + MXMembership membership = self.room.summary.membership; + if (membership == MXMembershipUnknown || membership == MXMembershipInvite) + { + // Here the initial sync is not ended or the room is a pending invitation. + // Note: In case of invitation, a full sync will be triggered if the user joins this room. + + // We have to observe here 'kMXRoomInitialSyncNotification' to reload room data when room sync is done. + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didMXRoomInitialSynced:) name:kMXRoomInitialSyncNotification object:self.room]; + } + }]; + + if (!_secondaryRoom && _secondaryRoomId) + { + _secondaryRoom = [self.mxSession roomWithRoomId:_secondaryRoomId]; + + if (_secondaryRoom) + { + MXWeakify(self); + [_secondaryRoom liveTimeline:^(id liveTimeline) { + MXStrongifyAndReturnIfNil(self); + + self->_secondaryTimeline = liveTimeline; + + // Only one pagination process can be done at a time by an MXRoom object. + // This assumption is satisfied by MatrixKit. Only MXRoomDataSource does it. + [self.secondaryTimeline resetPagination]; + + // Add the secondary event listeners, by considering the event types in self.secondaryRoomEventTypes + [self refreshSecondaryEventListeners:self.secondaryRoomEventTypes]; + + // Update here data source state if it is not already ready + [self setState:MXKDataSourceStateReady]; + + // Check user membership in the secondary room + MXMembership membership = self.secondaryRoom.summary.membership; + if (membership == MXMembershipUnknown || membership == MXMembershipInvite) + { + // Here the initial sync is not ended or the room is a pending invitation. + // Note: In case of invitation, a full sync will be triggered if the user joins this room. + + // We have to observe here 'kMXRoomInitialSyncNotification' to reload room data when room sync is done. + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didMXRoomInitialSynced:) name:kMXRoomInitialSyncNotification object:self.secondaryRoom]; + } + }]; + } + } } else { - self.room = [self.mxSession roomWithRoomId:_roomId]; + // Past timeline + // Less things need to configured + _timeline = [_room timelineOnEvent:initialEventId]; + + // Refresh the event listeners. Note: events for past timelines come only from pagination request + [self refreshEventListeners:nil]; + + MXWeakify(self); + + // Preload the state and some messages around the initial event + [_timeline resetPaginationAroundInitialEventWithLimit:_paginationLimitAroundInitialEvent success:^{ + + MXStrongifyAndReturnIfNil(self); + + // Do a "classic" reset. The room view controller will paginate + // from the events stored in the timeline store + [self.timeline resetPagination]; + + // Update here data source state if it is not already ready + [self setState:MXKDataSourceStateReady]; + + } failure:^(NSError *error) { + + MXStrongifyAndReturnIfNil(self); + + MXLogDebug(@"[MXKRoomDataSource][%p] Failed to resetPaginationAroundInitialEventWithLimit", self); + + // Notify the error + [[NSNotificationCenter defaultCenter] postNotificationName:kMXKRoomDataSourceTimelineError + object:self + userInfo:@{ + kMXKRoomDataSourceTimelineErrorErrorKey: error + }]; + }]; } - - if (_room) + } + else + { + MXLogDebug(@"[MXKRoomDataSource][%p] Warning: The user does not know the room %@", self, _roomId); + + // Update here data source state if it is not already ready + [self setState:MXKDataSourceStateFailed]; + } + } + + if (_room && MXSessionStateRunning == self.mxSession.state) + { + // Flair handling: observe the update in the publicised groups by users when the flair is enabled in the room. + [[NSNotificationCenter defaultCenter] removeObserver:self name:kMXSessionDidUpdatePublicisedGroupsForUsersNotification object:self.mxSession]; + [self.room state:^(MXRoomState *roomState) { + if (roomState.relatedGroups.count) { - // This is the time to set up the timeline according to the called init method - if (_isLive) + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didMXSessionUpdatePublicisedGroupsForUsers:) name:kMXSessionDidUpdatePublicisedGroupsForUsersNotification object:self.mxSession]; + + // Get a fresh profile for all the related groups. Trigger a table refresh when all requests are done. + __block NSUInteger count = roomState.relatedGroups.count; + for (NSString *groupId in roomState.relatedGroups) { - // LIVE - MXWeakify(self); - [_room liveTimeline:^(id liveTimeline) { - MXStrongifyAndReturnIfNil(self); - - self->_timeline = liveTimeline; - - // Only one pagination process can be done at a time by an MXRoom object. - // This assumption is satisfied by MatrixKit. Only MXRoomDataSource does it. - [self.timeline resetPagination]; - - // 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.mxSession == room.mxSession && ([self.roomId isEqualToString:room.roomId] || - ([self.secondaryRoomId isEqualToString:room.roomId]))) - { - // The existing room history has been flushed during server sync because a gap has been observed between local and server storage. - [self reload]; - } - - }]; - - // Add the event listeners, by considering all the event types (the event filtering is applying by the event formatter), - // except if only the events with a url key in their content must be handled. - [self refreshEventListeners:(self.filterMessagesWithURL ? @[kMXEventTypeStringRoomMessage] : [MXKAppSettings standardAppSettings].allEventTypesForMessages)]; - - // display typing notifications is optional - // the inherited class can manage them by its own. - if (self.showTypingNotifications) - { - // Register on typing notif - [self listenTypingNotifications]; - } - - // Manage unsent messages - [self handleUnsentMessages]; - - // Update here data source state if it is not already ready - if (!self->_secondaryRoomId) - { - [self setState:MXKDataSourceStateReady]; - } - - // Check user membership in this room - MXMembership membership = self.room.summary.membership; - if (membership == MXMembershipUnknown || membership == MXMembershipInvite) - { - // Here the initial sync is not ended or the room is a pending invitation. - // Note: In case of invitation, a full sync will be triggered if the user joins this room. - - // We have to observe here 'kMXRoomInitialSyncNotification' to reload room data when room sync is done. - [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didMXRoomInitialSynced:) name:kMXRoomInitialSyncNotification object:self.room]; - } - }]; - - if (!_secondaryRoom && _secondaryRoomId) + MXGroup *group = [self.mxSession groupWithGroupId:groupId]; + if (!group) { - _secondaryRoom = [self.mxSession roomWithRoomId:_secondaryRoomId]; - - if (_secondaryRoom) - { - MXWeakify(self); - [_secondaryRoom liveTimeline:^(id liveTimeline) { - MXStrongifyAndReturnIfNil(self); - - self->_secondaryTimeline = liveTimeline; - - // Only one pagination process can be done at a time by an MXRoom object. - // This assumption is satisfied by MatrixKit. Only MXRoomDataSource does it. - [self.secondaryTimeline resetPagination]; - - // Add the secondary event listeners, by considering the event types in self.secondaryRoomEventTypes - [self refreshSecondaryEventListeners:self.secondaryRoomEventTypes]; - - // Update here data source state if it is not already ready - [self setState:MXKDataSourceStateReady]; - - // Check user membership in the secondary room - MXMembership membership = self.secondaryRoom.summary.membership; - if (membership == MXMembershipUnknown || membership == MXMembershipInvite) - { - // Here the initial sync is not ended or the room is a pending invitation. - // Note: In case of invitation, a full sync will be triggered if the user joins this room. - - // We have to observe here 'kMXRoomInitialSyncNotification' to reload room data when room sync is done. - [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didMXRoomInitialSynced:) name:kMXRoomInitialSyncNotification object:self.secondaryRoom]; - } - }]; - } + // Create a group instance for the groups that the current user did not join. + group = [[MXGroup alloc] initWithGroupId:groupId]; + [self->externalRelatedGroups setObject:group forKey:groupId]; } - } - else - { - // Past timeline - // Less things need to configured - _timeline = [_room timelineOnEvent:initialEventId]; - // Refresh the event listeners. Note: events for past timelines come only from pagination request - [self refreshEventListeners:nil]; - - MXWeakify(self); + // Refresh the group profile from server. + [self.mxSession updateGroupProfile:group success:^{ - // Preload the state and some messages around the initial event - [_timeline resetPaginationAroundInitialEventWithLimit:_paginationLimitAroundInitialEvent success:^{ - - MXStrongifyAndReturnIfNil(self); - - // Do a "classic" reset. The room view controller will paginate - // from the events stored in the timeline store - [self.timeline resetPagination]; - - // Update here data source state if it is not already ready - [self setState:MXKDataSourceStateReady]; + if (self.delegate && !(--count)) + { + // All the requests have been done. + [self.delegate dataSource:self didCellChange:nil]; + } } failure:^(NSError *error) { - - MXStrongifyAndReturnIfNil(self); - MXLogDebug(@"[MXKRoomDataSource][%p] Failed to resetPaginationAroundInitialEventWithLimit", self); + MXLogDebug(@"[MXKRoomDataSource][%p] group profile update failed %@", self, groupId); + + if (self.delegate && !(--count)) + { + // All the requests have been done. + [self.delegate dataSource:self didCellChange:nil]; + } - // Notify the error - [[NSNotificationCenter defaultCenter] postNotificationName:kMXKRoomDataSourceTimelineError - object:self - userInfo:@{ - kMXKRoomDataSourceTimelineErrorErrorKey: error - }]; }]; } } - else - { - MXLogDebug(@"[MXKRoomDataSource][%p] Warning: The user does not know the room %@", self, _roomId); - - // Update here data source state if it is not already ready - [self setState:MXKDataSourceStateFailed]; - } - } - - if (_room && MXSessionStateRunning == self.mxSession.state) + }]; + } +} + +- (void)initializeTimelineForThread +{ + // Check whether the thread is not already set + if (_thread && self.state == MXKDataSourceStateReady) + { + return; + } + + _thread = [self.mxSession.threadingService threadWithId:_threadId]; + + if (!_thread) + { + // there is not a thread yet available, this will be a new thread + _thread = [self.mxSession.threadingService createTempThreadWithId:_threadId roomId:_roomId]; + } + + if (!_room) + { + // also hold a reference to the room + _room = [self.mxSession roomWithRoomId:_roomId]; + } + + if (_thread) + { + if (_isLive) { - // Flair handling: observe the update in the publicised groups by users when the flair is enabled in the room. - [[NSNotificationCenter defaultCenter] removeObserver:self name:kMXSessionDidUpdatePublicisedGroupsForUsersNotification object:self.mxSession]; - [self.room state:^(MXRoomState *roomState) { - if (roomState.relatedGroups.count) - { - [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didMXSessionUpdatePublicisedGroupsForUsers:) name:kMXSessionDidUpdatePublicisedGroupsForUsersNotification object:self.mxSession]; - - // Get a fresh profile for all the related groups. Trigger a table refresh when all requests are done. - __block NSUInteger count = roomState.relatedGroups.count; - for (NSString *groupId in roomState.relatedGroups) - { - MXGroup *group = [self.mxSession groupWithGroupId:groupId]; - if (!group) - { - // Create a group instance for the groups that the current user did not join. - group = [[MXGroup alloc] initWithGroupId:groupId]; - [self->externalRelatedGroups setObject:group forKey:groupId]; - } - - // Refresh the group profile from server. - [self.mxSession updateGroupProfile:group success:^{ - - if (self.delegate && !(--count)) - { - // All the requests have been done. - [self.delegate dataSource:self didCellChange:nil]; - } - - } failure:^(NSError *error) { - - MXLogDebug(@"[MXKRoomDataSource][%p] group profile update failed %@", self, groupId); - - if (self.delegate && !(--count)) - { - // All the requests have been done. - [self.delegate dataSource:self didCellChange:nil]; - } - - }]; - } - } + [_thread liveTimeline:^(id _Nonnull liveTimeline) { + self->_timeline = liveTimeline; + + // Only one pagination process can be done at a time by an MXThread object. + // This assumption is satisfied by MXRoomDataSource. + [self.timeline resetPagination]; + + // Add the event listeners, by considering all the event types (the event filtering is applying by the event formatter), + // except if only the events with a url key in their content must be handled. + [self refreshEventListeners:(self.filterMessagesWithURL ? @[kMXEventTypeStringRoomMessage] : [MXKAppSettings standardAppSettings].allEventTypesForMessages)]; + + // Manage unsent messages + [self handleUnsentMessages]; + + [self setState:MXKDataSourceStateReady]; }]; } + else + { + // Past timeline + // Less things need to configured + _timeline = [_thread timelineOnEvent:initialEventId]; + + // Refresh the event listeners. Note: events for past timelines come only from pagination request + [self refreshEventListeners:nil]; + + MXWeakify(self); + + // Preload the state and some messages around the initial event + [_timeline resetPaginationAroundInitialEventWithLimit:_paginationLimitAroundInitialEvent success:^{ + + MXStrongifyAndReturnIfNil(self); + + // Do a "classic" reset. The room view controller will paginate + // from the events stored in the timeline store + [self.timeline resetPagination]; + + // Update here data source state if it is not already ready + [self setState:MXKDataSourceStateReady]; + + } failure:^(NSError *error) { + + MXStrongifyAndReturnIfNil(self); + + MXLogDebug(@"[MXKRoomDataSource][%p] Failed to resetPaginationAroundInitialEventWithLimit", self); + + // Notify the error + [[NSNotificationCenter defaultCenter] postNotificationName:kMXKRoomDataSourceTimelineError + object:self + userInfo:@{ + kMXKRoomDataSourceTimelineErrorErrorKey: error + }]; + }]; + } + } + else + { + MXLogDebug(@"[MXKRoomDataSource][%p] Warning: The user does not know the thread %@", self, _threadId); + + // Update here data source state if it is not already ready + [self setState:MXKDataSourceStateFailed]; } } diff --git a/Riot/Modules/Threads/Thread/ThreadViewController.swift b/Riot/Modules/Threads/Thread/ThreadViewController.swift index 6c08c5e59..bee392509 100644 --- a/Riot/Modules/Threads/Thread/ThreadViewController.swift +++ b/Riot/Modules/Threads/Thread/ThreadViewController.swift @@ -49,7 +49,7 @@ class ThreadViewController: RoomViewController { super.viewDidAppear(animated) guard let threadId = threadId else { return } - mainSession.threadingService.markThreadAsRead(threadId) + mainSession?.threadingService.markThreadAsRead(threadId) } override func setRoomTitleViewClass(_ roomTitleViewClass: AnyClass!) {