mirror of
https://gitlab.opencode.de/bwi/bundesmessenger/clients/bundesmessenger-ios.git
synced 2026-04-18 15:38:28 +02:00
553 lines
18 KiB
Objective-C
553 lines
18 KiB
Objective-C
/*
|
|
Copyright 2015 OpenMarket Ltd
|
|
Copyright 2017 Vector Creations 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
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
See the License for the specific language governing permissions and
|
|
limitations under the License.
|
|
*/
|
|
|
|
#import "MXKSessionRecentsDataSource.h"
|
|
|
|
@import MatrixSDK;
|
|
|
|
#import "MXKRoomDataSourceManager.h"
|
|
|
|
#import "MXKSwiftHeader.h"
|
|
|
|
#pragma mark - Constant definitions
|
|
NSString *const kMXKRecentCellIdentifier = @"kMXKRecentCellIdentifier";
|
|
static NSTimeInterval const roomSummaryChangeThrottlerDelay = .5;
|
|
|
|
|
|
@interface MXKSessionRecentsDataSource ()
|
|
{
|
|
MXKRoomDataSourceManager *roomDataSourceManager;
|
|
|
|
/**
|
|
Internal array used to regulate change notifications.
|
|
Cell data changes are stored instantly in this array.
|
|
These changes are reported to the delegate only if no server sync is in progress.
|
|
*/
|
|
NSMutableArray *internalCellDataArray;
|
|
|
|
/**
|
|
Store the current search patterns list.
|
|
*/
|
|
NSArray* searchPatternsList;
|
|
|
|
/**
|
|
Do not react on every summary change
|
|
*/
|
|
MXThrottler *roomSummaryChangeThrottler;
|
|
|
|
/**
|
|
Last received suggested rooms per space ID
|
|
*/
|
|
NSMutableDictionary<NSString*, NSArray<MXSpaceChildInfo *> *> *lastSuggestedRooms;
|
|
|
|
/**
|
|
Event listener of the current space used to update the UI if an event occurs.
|
|
*/
|
|
id spaceEventsListener;
|
|
|
|
/**
|
|
Observer used to reload data when the space service is initialised
|
|
*/
|
|
id spaceServiceDidInitialiseObserver;
|
|
}
|
|
|
|
/**
|
|
Additional suggestedRooms related to the current selected Space
|
|
*/
|
|
@property (nonatomic, strong) NSArray<MXSpaceChildInfo *> *suggestedRooms;
|
|
|
|
@end
|
|
|
|
@implementation MXKSessionRecentsDataSource
|
|
|
|
- (instancetype)initWithMatrixSession:(MXSession *)matrixSession
|
|
{
|
|
self = [super initWithMatrixSession:matrixSession];
|
|
if (self)
|
|
{
|
|
roomDataSourceManager = [MXKRoomDataSourceManager sharedManagerForMatrixSession:self.mxSession];
|
|
|
|
internalCellDataArray = [NSMutableArray array];
|
|
filteredCellDataArray = nil;
|
|
|
|
lastSuggestedRooms = [NSMutableDictionary new];
|
|
|
|
// Set default data and view classes
|
|
[self registerCellDataClass:MXKRecentCellData.class forCellIdentifier:kMXKRecentCellIdentifier];
|
|
|
|
roomSummaryChangeThrottler = [[MXThrottler alloc] initWithMinimumDelay:roomSummaryChangeThrottlerDelay];
|
|
|
|
[[MXKAppSettings standardAppSettings] addObserver:self forKeyPath:@"showAllRoomsInHomeSpace" options:0 context:nil];
|
|
}
|
|
return self;
|
|
}
|
|
|
|
- (void)destroy
|
|
{
|
|
[[NSNotificationCenter defaultCenter] removeObserver:self name:kMXRoomSummaryDidChangeNotification object:nil];
|
|
[[NSNotificationCenter defaultCenter] removeObserver:self name:kMXKRoomDataSourceSyncStatusChanged object:nil];
|
|
[[NSNotificationCenter defaultCenter] removeObserver:self name:kMXSessionNewRoomNotification object:nil];
|
|
[[NSNotificationCenter defaultCenter] removeObserver:self name:kMXSessionDidLeaveRoomNotification object:nil];
|
|
[[NSNotificationCenter defaultCenter] removeObserver:self name:kMXSessionDirectRoomsDidChangeNotification object:nil];
|
|
|
|
if (spaceServiceDidInitialiseObserver) {
|
|
[[NSNotificationCenter defaultCenter] removeObserver:spaceServiceDidInitialiseObserver];
|
|
}
|
|
|
|
[roomSummaryChangeThrottler cancelAll];
|
|
roomSummaryChangeThrottler = nil;
|
|
|
|
cellDataArray = nil;
|
|
internalCellDataArray = nil;
|
|
filteredCellDataArray = nil;
|
|
lastSuggestedRooms = nil;
|
|
|
|
searchPatternsList = nil;
|
|
|
|
[[MXKAppSettings standardAppSettings] removeObserver:self forKeyPath:@"showAllRoomsInHomeSpace" context:nil];
|
|
|
|
[super destroy];
|
|
}
|
|
|
|
- (void)didMXSessionStateChange
|
|
{
|
|
if (MXSessionStateStoreDataReady <= self.mxSession.state)
|
|
{
|
|
// Check whether some data have been already load
|
|
if (0 == internalCellDataArray.count)
|
|
{
|
|
[self loadData];
|
|
}
|
|
else if (!roomDataSourceManager.isServerSyncInProgress)
|
|
{
|
|
// Sort cell data and notify the delegate
|
|
[self sortCellDataAndNotifyChanges];
|
|
}
|
|
}
|
|
}
|
|
|
|
- (void)setCurrentSpace:(MXSpace *)currentSpace
|
|
{
|
|
if (_currentSpace == currentSpace)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (_currentSpace && spaceEventsListener)
|
|
{
|
|
[_currentSpace.room removeListener:spaceEventsListener];
|
|
}
|
|
|
|
_currentSpace = currentSpace;
|
|
|
|
self.suggestedRooms = _currentSpace ? lastSuggestedRooms[_currentSpace.spaceId] : nil;
|
|
[self updateSuggestedRooms];
|
|
|
|
MXWeakify(self);
|
|
spaceEventsListener = [self.currentSpace.room listenToEvents:^(MXEvent *event, MXTimelineDirection direction, MXRoomState *roomState) {
|
|
MXStrongifyAndReturnIfNil(self);
|
|
[self updateSuggestedRooms];
|
|
}];
|
|
}
|
|
|
|
-(void)setSuggestedRooms:(NSArray<MXSpaceChildInfo *> *)suggestedRooms
|
|
{
|
|
_suggestedRooms = suggestedRooms;
|
|
[self loadData];
|
|
}
|
|
|
|
-(void)updateSuggestedRooms
|
|
{
|
|
if (self.currentSpace)
|
|
{
|
|
NSString *currentSpaceId = self.currentSpace.spaceId;
|
|
MXWeakify(self);
|
|
[self.mxSession.spaceService getSpaceChildrenForSpaceWithId:currentSpaceId suggestedOnly:YES limit:5 maxDepth:1 paginationToken:nil success:^(MXSpaceChildrenSummary * _Nonnull childrenSummary) {
|
|
MXLogDebug(@"[MXKSessionRecentsDataSource] getSpaceChildrenForSpaceWithId %@: %ld found", self.currentSpace.spaceId, childrenSummary.childInfos.count);
|
|
MXStrongifyAndReturnIfNil(self);
|
|
self->lastSuggestedRooms[currentSpaceId] = childrenSummary.childInfos;
|
|
if ([self.currentSpace.spaceId isEqual:currentSpaceId]) {
|
|
self.suggestedRooms = childrenSummary.childInfos;
|
|
}
|
|
} failure:^(NSError * _Nonnull error) {
|
|
MXLogError(@"[MXKSessionRecentsDataSource] getSpaceChildrenForSpaceWithId failed with error: %@", error);
|
|
}];
|
|
}
|
|
}
|
|
|
|
#pragma mark -
|
|
|
|
- (NSInteger)numberOfCells
|
|
{
|
|
if (filteredCellDataArray)
|
|
{
|
|
return filteredCellDataArray.count;
|
|
}
|
|
return cellDataArray.count;
|
|
}
|
|
|
|
- (BOOL)hasUnread
|
|
{
|
|
// Check all current cells
|
|
// Use numberOfRowsInSection methods so that we take benefit of the filtering
|
|
for (NSUInteger i = 0; i < self.numberOfCells; i++)
|
|
{
|
|
id<MXKRecentCellDataStoring> cellData = [self cellDataAtIndex:i];
|
|
if (cellData.hasUnread)
|
|
{
|
|
return YES;
|
|
}
|
|
}
|
|
return NO;
|
|
}
|
|
|
|
- (void)searchWithPatterns:(NSArray*)patternsList
|
|
{
|
|
if (patternsList.count)
|
|
{
|
|
searchPatternsList = patternsList;
|
|
|
|
if (filteredCellDataArray)
|
|
{
|
|
[filteredCellDataArray removeAllObjects];
|
|
}
|
|
else
|
|
{
|
|
filteredCellDataArray = [NSMutableArray arrayWithCapacity:cellDataArray.count];
|
|
}
|
|
|
|
for (id<MXKRecentCellDataStoring> cellData in cellDataArray)
|
|
{
|
|
for (NSString* pattern in patternsList)
|
|
{
|
|
if (cellData.roomDisplayname && [cellData.roomDisplayname rangeOfString:pattern options:NSCaseInsensitiveSearch].location != NSNotFound)
|
|
{
|
|
[filteredCellDataArray addObject:cellData];
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
filteredCellDataArray = nil;
|
|
searchPatternsList = nil;
|
|
}
|
|
|
|
[self.delegate dataSource:self didCellChange:nil];
|
|
}
|
|
|
|
- (id<MXKRecentCellDataStoring>)cellDataAtIndex:(NSInteger)index
|
|
{
|
|
if (filteredCellDataArray)
|
|
{
|
|
if (index < filteredCellDataArray.count)
|
|
{
|
|
return filteredCellDataArray[index];
|
|
}
|
|
}
|
|
else if (index < cellDataArray.count)
|
|
{
|
|
return cellDataArray[index];
|
|
}
|
|
|
|
return nil;
|
|
}
|
|
|
|
- (CGFloat)cellHeightAtIndex:(NSInteger)index
|
|
{
|
|
if (self.delegate)
|
|
{
|
|
id<MXKRecentCellDataStoring> cellData = [self cellDataAtIndex:index];
|
|
|
|
Class<MXKCellRendering> class = [self.delegate cellViewClassForCellData:cellData];
|
|
return [class heightForCellData:cellData withMaximumWidth:0];
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
#pragma mark - Events processing
|
|
|
|
/**
|
|
Filtering in this method won't have any effect anymore. This class is not maintained.
|
|
*/
|
|
- (void)loadData
|
|
{
|
|
[[NSNotificationCenter defaultCenter] removeObserver:self name:kMXRoomSummaryDidChangeNotification object:nil];
|
|
[[NSNotificationCenter defaultCenter] removeObserver:self name:kMXKRoomDataSourceSyncStatusChanged object:nil];
|
|
[[NSNotificationCenter defaultCenter] removeObserver:self name:kMXSessionNewRoomNotification object:nil];
|
|
[[NSNotificationCenter defaultCenter] removeObserver:self name:kMXSessionDidLeaveRoomNotification object:nil];
|
|
[[NSNotificationCenter defaultCenter] removeObserver:self name:kMXSessionDirectRoomsDidChangeNotification object:nil];
|
|
|
|
if (!self.mxSession.spaceService.isInitialised && !spaceServiceDidInitialiseObserver) {
|
|
MXWeakify(self);
|
|
spaceServiceDidInitialiseObserver = [[NSNotificationCenter defaultCenter] addObserverForName:MXSpaceService.didInitialise object:self.mxSession.spaceService queue:nil usingBlock:^(NSNotification * _Nonnull note) {
|
|
MXStrongifyAndReturnIfNil(self);
|
|
[self loadData];
|
|
}];
|
|
}
|
|
|
|
// Reset the table
|
|
[internalCellDataArray removeAllObjects];
|
|
|
|
// Retrieve the MXKCellData class to manage the data
|
|
Class class = [self cellDataClassForCellIdentifier:kMXKRecentCellIdentifier];
|
|
NSAssert([class conformsToProtocol:@protocol(MXKRecentCellDataStoring)], @"MXKSessionRecentsDataSource only manages MXKCellData that conforms to MXKRecentCellDataStoring protocol");
|
|
|
|
NSDate *startDate = [NSDate date];
|
|
|
|
for (MXRoomSummary *roomSummary in self.mxSession.roomsSummaries)
|
|
{
|
|
// Filter out private rooms with conference users
|
|
if (!roomSummary.isConferenceUserRoom // @TODO Abstract this condition with roomSummary.hiddenFromUser
|
|
&& !roomSummary.hiddenFromUser)
|
|
{
|
|
id<MXKRecentCellDataStoring> cellData = [[class alloc] initWithRoomSummary:roomSummary dataSource:self];
|
|
if (cellData)
|
|
{
|
|
[internalCellDataArray addObject:cellData];
|
|
}
|
|
}
|
|
}
|
|
|
|
for (MXSpaceChildInfo *childInfo in _suggestedRooms)
|
|
{
|
|
id<MXRoomSummaryProtocol> summary = [[MXRoomSummary alloc] initWithSpaceChildInfo:childInfo];
|
|
id<MXKRecentCellDataStoring> cellData = [[class alloc] initWithRoomSummary:summary
|
|
dataSource:self];
|
|
if (cellData)
|
|
{
|
|
[internalCellDataArray addObject:cellData];
|
|
}
|
|
}
|
|
|
|
MXLogDebug(@"[MXKSessionRecentsDataSource] Loaded %tu recents in %.3fms", self.mxSession.rooms.count, [[NSDate date] timeIntervalSinceDate:startDate] * 1000);
|
|
|
|
// Make sure all rooms have a last message
|
|
[self.mxSession fixRoomsSummariesLastMessage];
|
|
|
|
// Report loaded array except if sync is in progress
|
|
if (!roomDataSourceManager.isServerSyncInProgress)
|
|
{
|
|
[self sortCellDataAndNotifyChanges];
|
|
}
|
|
|
|
// Listen to MXSession rooms count changes
|
|
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didMXSessionHaveNewRoom:) name:kMXSessionNewRoomNotification object:nil];
|
|
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didMXSessionDidLeaveRoom:) name:kMXSessionDidLeaveRoomNotification object:nil];
|
|
|
|
// Listen to the direct rooms list
|
|
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didDirectRoomsChange:) name:kMXSessionDirectRoomsDidChangeNotification object:nil];
|
|
|
|
// Listen to MXRoomSummary
|
|
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didRoomSummaryChanged:) name:kMXRoomSummaryDidChangeNotification object:nil];
|
|
|
|
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didMXSessionStateChange) name:kMXKRoomDataSourceSyncStatusChanged object:nil];
|
|
}
|
|
|
|
- (void)didDirectRoomsChange:(NSNotification *)notif
|
|
{
|
|
// Inform the delegate about the update
|
|
[self.delegate dataSource:self didCellChange:nil];
|
|
}
|
|
|
|
- (void)didRoomSummaryChanged:(NSNotification *)notif
|
|
{
|
|
[roomSummaryChangeThrottler throttle:^{
|
|
[self didRoomSummaryChanged2:notif];
|
|
}];
|
|
}
|
|
|
|
- (void)didRoomSummaryChanged2:(NSNotification *)notif
|
|
{
|
|
MXRoomSummary *roomSummary = notif.object;
|
|
if (roomSummary.mxSession == self.mxSession && internalCellDataArray.count)
|
|
{
|
|
// Find the index of the related cell data
|
|
NSInteger index = NSNotFound;
|
|
for (index = 0; index < internalCellDataArray.count; index++)
|
|
{
|
|
id<MXKRecentCellDataStoring> theRoomData = [internalCellDataArray objectAtIndex:index];
|
|
if (theRoomData.roomSummary == roomSummary)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (index < internalCellDataArray.count)
|
|
{
|
|
if (roomSummary.hiddenFromUser)
|
|
{
|
|
[internalCellDataArray removeObjectAtIndex:index];
|
|
}
|
|
else
|
|
{
|
|
// Create a new instance to not modify the content of 'cellDataArray' (the copy is not a deep copy).
|
|
Class class = [self cellDataClassForCellIdentifier:kMXKRecentCellIdentifier];
|
|
id<MXKRecentCellDataStoring> cellData = [[class alloc] initWithRoomSummary:roomSummary dataSource:self];
|
|
if (cellData)
|
|
{
|
|
[internalCellDataArray replaceObjectAtIndex:index withObject:cellData];
|
|
}
|
|
}
|
|
|
|
// Report change except if sync is in progress
|
|
if (!roomDataSourceManager.isServerSyncInProgress)
|
|
{
|
|
[self sortCellDataAndNotifyChanges];
|
|
}
|
|
}
|
|
else
|
|
{
|
|
MXLogDebug(@"[MXKSessionRecentsDataSource] didRoomLastMessageChanged: Cannot find the changed room summary for %@ (%@). It is probably not managed by this recents data source", roomSummary.roomId, roomSummary);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Inform the delegate that all the room summaries have been updated.
|
|
[self.delegate dataSource:self didCellChange:nil];
|
|
}
|
|
}
|
|
|
|
- (void)didMXSessionHaveNewRoom:(NSNotification *)notif
|
|
{
|
|
MXSession *mxSession = notif.object;
|
|
if (mxSession == self.mxSession)
|
|
{
|
|
NSString *roomId = notif.userInfo[kMXSessionNotificationRoomIdKey];
|
|
|
|
// Add the room if there is not yet a cell for it
|
|
id<MXKRecentCellDataStoring> roomData = [self cellDataWithRoomId:roomId];
|
|
if (nil == roomData)
|
|
{
|
|
MXLogDebug(@"MXKSessionRecentsDataSource] Add newly joined room: %@", roomId);
|
|
|
|
// Retrieve the MXKCellData class to manage the data
|
|
Class class = [self cellDataClassForCellIdentifier:kMXKRecentCellIdentifier];
|
|
|
|
MXRoomSummary *roomSummary = [mxSession roomSummaryWithRoomId:roomId];
|
|
id<MXKRecentCellDataStoring> cellData = [[class alloc] initWithRoomSummary:roomSummary dataSource:self];
|
|
if (cellData)
|
|
{
|
|
[internalCellDataArray addObject:cellData];
|
|
|
|
// Report change except if sync is in progress
|
|
if (!roomDataSourceManager.isServerSyncInProgress)
|
|
{
|
|
[self sortCellDataAndNotifyChanges];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
- (void)didMXSessionDidLeaveRoom:(NSNotification *)notif
|
|
{
|
|
MXSession *mxSession = notif.object;
|
|
if (mxSession == self.mxSession)
|
|
{
|
|
NSString *roomId = notif.userInfo[kMXSessionNotificationRoomIdKey];
|
|
id<MXKRecentCellDataStoring> roomData = [self cellDataWithRoomId:roomId];
|
|
|
|
if (roomData)
|
|
{
|
|
MXLogDebug(@"MXKSessionRecentsDataSource] Remove left room: %@", roomId);
|
|
|
|
[internalCellDataArray removeObject:roomData];
|
|
|
|
// Report change except if sync is in progress
|
|
if (!roomDataSourceManager.isServerSyncInProgress)
|
|
{
|
|
[self sortCellDataAndNotifyChanges];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Order cells
|
|
- (void)sortCellDataAndNotifyChanges
|
|
{
|
|
// Order them by origin_server_ts
|
|
[internalCellDataArray sortUsingComparator:^NSComparisonResult(id<MXKRecentCellDataStoring> cellData1, id<MXKRecentCellDataStoring> cellData2)
|
|
{
|
|
return [cellData1.roomSummary.lastMessage compareOriginServerTs:cellData2.roomSummary.lastMessage];
|
|
}];
|
|
|
|
// Snapshot the cell data array
|
|
cellDataArray = [internalCellDataArray copy];
|
|
|
|
// Update search result if any
|
|
if (searchPatternsList)
|
|
{
|
|
[self searchWithPatterns:searchPatternsList];
|
|
}
|
|
|
|
// Update here data source state
|
|
if (state != MXKDataSourceStateReady)
|
|
{
|
|
state = MXKDataSourceStateReady;
|
|
if (self.delegate && [self.delegate respondsToSelector:@selector(dataSource:didStateChange:)])
|
|
{
|
|
[self.delegate dataSource:self didStateChange:state];
|
|
}
|
|
}
|
|
|
|
// And inform the delegate about the update
|
|
[self.delegate dataSource:self didCellChange:nil];
|
|
}
|
|
|
|
// Find the cell data that stores information about the given room id
|
|
- (id<MXKRecentCellDataStoring>)cellDataWithRoomId:(NSString*)roomId
|
|
{
|
|
id<MXKRecentCellDataStoring> theRoomData;
|
|
|
|
NSMutableArray *dataArray = internalCellDataArray;
|
|
if (!roomDataSourceManager.isServerSyncInProgress)
|
|
{
|
|
dataArray = cellDataArray;
|
|
}
|
|
|
|
for (id<MXKRecentCellDataStoring> roomData in dataArray)
|
|
{
|
|
if ([roomData.roomSummary.roomId isEqualToString:roomId])
|
|
{
|
|
theRoomData = roomData;
|
|
break;
|
|
}
|
|
}
|
|
return theRoomData;
|
|
}
|
|
|
|
#pragma mark - KVO
|
|
|
|
- (void)observeValueForKeyPath:(NSString *)keyPath
|
|
ofObject:(id)object
|
|
change:(NSDictionary *)change
|
|
context:(void *)context
|
|
{
|
|
if (object == [MXKAppSettings standardAppSettings] && [keyPath isEqualToString:@"showAllRoomsInHomeSpace"])
|
|
{
|
|
if (self.currentSpace == nil)
|
|
{
|
|
[self loadData];
|
|
}
|
|
}
|
|
}
|
|
|
|
@end
|