mirror of
https://gitlab.opencode.de/bwi/bundesmessenger/clients/bundesmessenger-ios.git
synced 2026-04-19 16:13:42 +02:00
Merge MatrixKit develop with commit hash: b85b736313bec0592bd1cabc68035d97f5331137
This commit is contained in:
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
Copyright 2015 OpenMarket 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 "MXKRecentsDataSource.h"
|
||||
|
||||
/**
|
||||
'MXKInterleavedRecentsDataSource' class inherits from 'MXKRecentsDataSource'.
|
||||
|
||||
It interleaves the recents in case of multiple sessions to display first the most recent room.
|
||||
*/
|
||||
@interface MXKInterleavedRecentsDataSource : MXKRecentsDataSource
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,439 @@
|
||||
/*
|
||||
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 "MXKInterleavedRecentsDataSource.h"
|
||||
|
||||
#import "MXKInterleavedRecentTableViewCell.h"
|
||||
|
||||
#import "MXKAccountManager.h"
|
||||
|
||||
#import "NSBundle+MatrixKit.h"
|
||||
|
||||
@interface MXKInterleavedRecentsDataSource ()
|
||||
{
|
||||
/**
|
||||
The interleaved recents: cell data served by `MXKInterleavedRecentsDataSource`.
|
||||
*/
|
||||
NSMutableArray *interleavedCellDataArray;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation MXKInterleavedRecentsDataSource
|
||||
|
||||
- (instancetype)init
|
||||
{
|
||||
self = [super init];
|
||||
if (self)
|
||||
{
|
||||
interleavedCellDataArray = [NSMutableArray array];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
#pragma mark - Override MXKDataSource
|
||||
|
||||
- (void)destroy
|
||||
{
|
||||
interleavedCellDataArray = nil;
|
||||
|
||||
[super destroy];
|
||||
}
|
||||
|
||||
#pragma mark - Override MXKRecentsDataSource
|
||||
|
||||
- (UIView *)viewForHeaderInSection:(NSInteger)section withFrame:(CGRect)frame
|
||||
{
|
||||
UIView *sectionHeader = nil;
|
||||
|
||||
if (displayedRecentsDataSourceArray.count > 1 && section == 0)
|
||||
{
|
||||
sectionHeader = [[UIView alloc] initWithFrame:frame];
|
||||
sectionHeader.backgroundColor = [UIColor colorWithRed:0.9 green:0.9 blue:0.9 alpha:1.0];
|
||||
CGFloat btnWidth = frame.size.width / displayedRecentsDataSourceArray.count;
|
||||
UIButton *previousShrinkButton;
|
||||
|
||||
for (NSInteger index = 0; index < displayedRecentsDataSourceArray.count; index++)
|
||||
{
|
||||
MXKSessionRecentsDataSource *recentsDataSource = [displayedRecentsDataSourceArray objectAtIndex:index];
|
||||
NSString* btnTitle = recentsDataSource.mxSession.myUser.userId;
|
||||
|
||||
// Add shrink button
|
||||
UIButton *shrinkButton = [UIButton buttonWithType:UIButtonTypeCustom];
|
||||
CGRect btnFrame = CGRectMake(index * btnWidth, 0, btnWidth, sectionHeader.frame.size.height);
|
||||
shrinkButton.frame = btnFrame;
|
||||
shrinkButton.backgroundColor = [UIColor clearColor];
|
||||
|
||||
[shrinkButton addTarget:self action:@selector(onButtonPressed:) forControlEvents:UIControlEventTouchUpInside];
|
||||
shrinkButton.tag = index;
|
||||
[sectionHeader addSubview:shrinkButton];
|
||||
sectionHeader.userInteractionEnabled = YES;
|
||||
|
||||
// Set shrink button constraints
|
||||
NSLayoutConstraint *leftConstraint;
|
||||
NSLayoutConstraint *widthConstraint;
|
||||
shrinkButton.translatesAutoresizingMaskIntoConstraints = NO;
|
||||
if (!previousShrinkButton)
|
||||
{
|
||||
leftConstraint = [NSLayoutConstraint constraintWithItem:shrinkButton
|
||||
attribute:NSLayoutAttributeLeading
|
||||
relatedBy:NSLayoutRelationEqual
|
||||
toItem:sectionHeader
|
||||
attribute:NSLayoutAttributeLeading
|
||||
multiplier:1
|
||||
constant:0];
|
||||
widthConstraint = [NSLayoutConstraint constraintWithItem:shrinkButton
|
||||
attribute:NSLayoutAttributeWidth
|
||||
relatedBy:NSLayoutRelationEqual
|
||||
toItem:sectionHeader
|
||||
attribute:NSLayoutAttributeWidth
|
||||
multiplier:(1.0 /displayedRecentsDataSourceArray.count)
|
||||
constant:0];
|
||||
}
|
||||
else
|
||||
{
|
||||
leftConstraint = [NSLayoutConstraint constraintWithItem:shrinkButton
|
||||
attribute:NSLayoutAttributeLeading
|
||||
relatedBy:NSLayoutRelationEqual
|
||||
toItem:previousShrinkButton
|
||||
attribute:NSLayoutAttributeTrailing
|
||||
multiplier:1
|
||||
constant:0];
|
||||
widthConstraint = [NSLayoutConstraint constraintWithItem:shrinkButton
|
||||
attribute:NSLayoutAttributeWidth
|
||||
relatedBy:NSLayoutRelationEqual
|
||||
toItem:previousShrinkButton
|
||||
attribute:NSLayoutAttributeWidth
|
||||
multiplier:1
|
||||
constant:0];
|
||||
}
|
||||
[NSLayoutConstraint activateConstraints:@[leftConstraint, widthConstraint]];
|
||||
previousShrinkButton = shrinkButton;
|
||||
|
||||
// Add shrink icon
|
||||
UIImage *chevron;
|
||||
if ([shrinkedRecentsDataSourceArray indexOfObject:recentsDataSource] != NSNotFound)
|
||||
{
|
||||
chevron = [NSBundle mxk_imageFromMXKAssetsBundleWithName:@"disclosure"];
|
||||
}
|
||||
else
|
||||
{
|
||||
chevron = [NSBundle mxk_imageFromMXKAssetsBundleWithName:@"shrink"];
|
||||
}
|
||||
UIImageView *chevronView = [[UIImageView alloc] initWithImage:chevron];
|
||||
if ([shrinkedRecentsDataSourceArray indexOfObject:recentsDataSource] == NSNotFound)
|
||||
{
|
||||
// Display the tint color of the user
|
||||
MXKAccount *account = [[MXKAccountManager sharedManager] accountForUserId:recentsDataSource.mxSession.myUser.userId];
|
||||
if (account)
|
||||
{
|
||||
chevronView.backgroundColor = account.userTintColor;
|
||||
}
|
||||
else
|
||||
{
|
||||
chevronView.backgroundColor = [UIColor clearColor];
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
chevronView.backgroundColor = [UIColor lightGrayColor];
|
||||
}
|
||||
chevronView.contentMode = UIViewContentModeCenter;
|
||||
frame = chevronView.frame;
|
||||
frame.size.width = frame.size.height = shrinkButton.frame.size.height - 10;
|
||||
frame.origin.x = shrinkButton.frame.size.width - frame.size.width - 8;
|
||||
frame.origin.y = (shrinkButton.frame.size.height - frame.size.height) / 2;
|
||||
chevronView.frame = frame;
|
||||
[shrinkButton addSubview:chevronView];
|
||||
chevronView.autoresizingMask |= (UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin);
|
||||
|
||||
// Add label
|
||||
frame = shrinkButton.frame;
|
||||
frame.origin.x = 5;
|
||||
frame.origin.y = 5;
|
||||
frame.size.width = chevronView.frame.origin.x - 10;
|
||||
frame.size.height -= 10;
|
||||
UILabel *headerLabel = [[UILabel alloc] initWithFrame:frame];
|
||||
headerLabel.font = [UIFont boldSystemFontOfSize:16];
|
||||
headerLabel.backgroundColor = [UIColor clearColor];
|
||||
headerLabel.text = btnTitle;
|
||||
[shrinkButton addSubview:headerLabel];
|
||||
headerLabel.autoresizingMask |= (UIViewAutoresizingFlexibleWidth);
|
||||
}
|
||||
}
|
||||
|
||||
return sectionHeader;
|
||||
}
|
||||
|
||||
- (id<MXKRecentCellDataStoring>)cellDataAtIndexPath:(NSIndexPath *)indexPath
|
||||
{
|
||||
id<MXKRecentCellDataStoring> cellData = nil;
|
||||
|
||||
// Only one section is handled by this data source
|
||||
if (indexPath.section == 0)
|
||||
{
|
||||
// Consider first the case where there is only one data source (no interleaving).
|
||||
if (displayedRecentsDataSourceArray.count == 1)
|
||||
{
|
||||
MXKSessionRecentsDataSource *recentsDataSource = displayedRecentsDataSourceArray.firstObject;
|
||||
cellData = [recentsDataSource cellDataAtIndex:indexPath.row];
|
||||
}
|
||||
// Else all the cells have been interleaved.
|
||||
else if (indexPath.row < interleavedCellDataArray.count)
|
||||
{
|
||||
cellData = interleavedCellDataArray[indexPath.row];
|
||||
}
|
||||
}
|
||||
|
||||
return cellData;
|
||||
}
|
||||
|
||||
- (CGFloat)cellHeightAtIndexPath:(NSIndexPath *)indexPath
|
||||
{
|
||||
CGFloat height = 0;
|
||||
|
||||
// Only one section is handled by this data source
|
||||
if (indexPath.section == 0)
|
||||
{
|
||||
// Consider first the case where there is only one data source (no interleaving).
|
||||
if (displayedRecentsDataSourceArray.count == 1)
|
||||
{
|
||||
MXKSessionRecentsDataSource *recentsDataSource = displayedRecentsDataSourceArray.firstObject;
|
||||
height = [recentsDataSource cellHeightAtIndex:indexPath.row];
|
||||
}
|
||||
// Else all the cells have been interleaved.
|
||||
else if (indexPath.row < interleavedCellDataArray.count)
|
||||
{
|
||||
id<MXKRecentCellDataStoring> recentCellData = interleavedCellDataArray[indexPath.row];
|
||||
|
||||
// Select the related recent data source
|
||||
MXKDataSource *dataSource = recentCellData.dataSource;
|
||||
if ([dataSource isKindOfClass:[MXKSessionRecentsDataSource class]])
|
||||
{
|
||||
MXKSessionRecentsDataSource *recentsDataSource = (MXKSessionRecentsDataSource*)dataSource;
|
||||
// Count the index of this cell data in original data source array
|
||||
NSInteger rank = 0;
|
||||
for (NSInteger index = 0; index < indexPath.row; index++)
|
||||
{
|
||||
id<MXKRecentCellDataStoring> cellData = interleavedCellDataArray[index];
|
||||
if (cellData.roomSummary == recentCellData.roomSummary)
|
||||
{
|
||||
rank++;
|
||||
}
|
||||
}
|
||||
|
||||
height = [recentsDataSource cellHeightAtIndex:rank];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return height;
|
||||
}
|
||||
|
||||
- (NSIndexPath*)cellIndexPathWithRoomId:(NSString*)roomId andMatrixSession:(MXSession*)matrixSession
|
||||
{
|
||||
NSIndexPath *indexPath = nil;
|
||||
|
||||
// Consider first the case where there is only one data source (no interleaving).
|
||||
if (displayedRecentsDataSourceArray.count == 1)
|
||||
{
|
||||
MXKSessionRecentsDataSource *recentsDataSource = displayedRecentsDataSourceArray.firstObject;
|
||||
if (recentsDataSource.mxSession == matrixSession)
|
||||
{
|
||||
// Look for the cell
|
||||
for (NSInteger index = 0; index < recentsDataSource.numberOfCells; index ++)
|
||||
{
|
||||
id<MXKRecentCellDataStoring> recentCellData = [recentsDataSource cellDataAtIndex:index];
|
||||
if ([roomId isEqualToString:recentCellData.roomIdentifier])
|
||||
{
|
||||
// Got it
|
||||
indexPath = [NSIndexPath indexPathForRow:index inSection:0];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Look for the right data source
|
||||
for (MXKSessionRecentsDataSource *recentsDataSource in displayedRecentsDataSourceArray)
|
||||
{
|
||||
if (recentsDataSource.mxSession == matrixSession)
|
||||
{
|
||||
// Check whether the source is not shrinked
|
||||
if ([shrinkedRecentsDataSourceArray indexOfObject:recentsDataSource] == NSNotFound)
|
||||
{
|
||||
// Look for the cell
|
||||
for (NSInteger index = 0; index < interleavedCellDataArray.count; index ++)
|
||||
{
|
||||
id<MXKRecentCellDataStoring> recentCellData = interleavedCellDataArray[index];
|
||||
if ([roomId isEqualToString:recentCellData.roomIdentifier])
|
||||
{
|
||||
// Got it
|
||||
indexPath = [NSIndexPath indexPathForRow:index inSection:0];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return indexPath;
|
||||
}
|
||||
|
||||
#pragma mark - UITableViewDataSource
|
||||
|
||||
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
|
||||
{
|
||||
// Check whether all data sources are ready before rendering recents
|
||||
if (self.state == MXKDataSourceStateReady)
|
||||
{
|
||||
// Only one section is handled by this data source.
|
||||
return (displayedRecentsDataSourceArray.count ? 1 : 0);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
|
||||
{
|
||||
// Consider first the case where there is only one data source (no interleaving).
|
||||
if (displayedRecentsDataSourceArray.count == 1)
|
||||
{
|
||||
MXKSessionRecentsDataSource *recentsDataSource = displayedRecentsDataSourceArray.firstObject;
|
||||
return recentsDataSource.numberOfCells;
|
||||
}
|
||||
|
||||
return interleavedCellDataArray.count;
|
||||
}
|
||||
|
||||
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
|
||||
{
|
||||
id<MXKRecentCellDataStoring> roomData = [self cellDataAtIndexPath:indexPath];
|
||||
if (roomData && self.delegate)
|
||||
{
|
||||
NSString *cellIdentifier = [self.delegate cellReuseIdentifierForCellData:roomData];
|
||||
if (cellIdentifier)
|
||||
{
|
||||
UITableViewCell<MXKCellRendering> *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier forIndexPath:indexPath];
|
||||
|
||||
// Make sure we listen to user actions on the cell
|
||||
cell.delegate = self;
|
||||
|
||||
// Make the bubble display the data
|
||||
[cell render:roomData];
|
||||
|
||||
// Clear the user flag, if only one recents list is available
|
||||
if (displayedRecentsDataSourceArray.count == 1 && [cell isKindOfClass:[MXKInterleavedRecentTableViewCell class]])
|
||||
{
|
||||
((MXKInterleavedRecentTableViewCell*)cell).userFlag.backgroundColor = [UIColor clearColor];
|
||||
}
|
||||
|
||||
return cell;
|
||||
}
|
||||
}
|
||||
|
||||
// Return a fake cell to prevent app from crashing.
|
||||
return [[UITableViewCell alloc] init];
|
||||
}
|
||||
|
||||
#pragma mark - MXKDataSourceDelegate
|
||||
|
||||
- (void)dataSource:(MXKDataSource*)dataSource didCellChange:(id)changes
|
||||
{
|
||||
// Consider first the case where there is only one data source (no interleaving).
|
||||
if (displayedRecentsDataSourceArray.count == 1)
|
||||
{
|
||||
// Flush interleaved cells array, we will refer directly to the cell data of the unique data source.
|
||||
[interleavedCellDataArray removeAllObjects];
|
||||
}
|
||||
else
|
||||
{
|
||||
// Handle here the specific case where a second source is just added.
|
||||
// The empty interleaved cells array has to be prefilled with the cell data of the other source (except if this other source is shrinked).
|
||||
if (!interleavedCellDataArray.count && displayedRecentsDataSourceArray.count == 2)
|
||||
{
|
||||
// This is the first interleaving, look for the other source
|
||||
MXKSessionRecentsDataSource *recentsDataSource = displayedRecentsDataSourceArray.firstObject;
|
||||
if (recentsDataSource == dataSource)
|
||||
{
|
||||
recentsDataSource = displayedRecentsDataSourceArray.lastObject;
|
||||
}
|
||||
|
||||
if ([shrinkedRecentsDataSourceArray indexOfObject:recentsDataSource] == NSNotFound)
|
||||
{
|
||||
// Report all cell data
|
||||
for (NSInteger index = 0; index < recentsDataSource.numberOfCells; index ++)
|
||||
{
|
||||
[interleavedCellDataArray addObject:[recentsDataSource cellDataAtIndex:index]];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Update now interleaved cells array, TODO take into account 'changes' parameter
|
||||
MXKSessionRecentsDataSource *updateRecentsDataSource = (MXKSessionRecentsDataSource*)dataSource;
|
||||
NSInteger numberOfUpdatedCells = 0;
|
||||
// Check whether this dataSource is used
|
||||
if ([displayedRecentsDataSourceArray indexOfObject:dataSource] != NSNotFound && [shrinkedRecentsDataSourceArray indexOfObject:dataSource] == NSNotFound)
|
||||
{
|
||||
numberOfUpdatedCells = updateRecentsDataSource.numberOfCells;
|
||||
}
|
||||
|
||||
NSInteger currentCellIndex = 0;
|
||||
NSInteger updatedCellIndex = 0;
|
||||
id<MXKRecentCellDataStoring> updatedCellData = nil;
|
||||
|
||||
if (numberOfUpdatedCells)
|
||||
{
|
||||
updatedCellData = [updateRecentsDataSource cellDataAtIndex:updatedCellIndex++];
|
||||
}
|
||||
|
||||
// Review all cell data items of the current list
|
||||
while (currentCellIndex < interleavedCellDataArray.count)
|
||||
{
|
||||
id<MXKRecentCellDataStoring> currentCellData = interleavedCellDataArray[currentCellIndex];
|
||||
|
||||
// Remove existing cell data of the updated data source
|
||||
if (currentCellData.dataSource == dataSource)
|
||||
{
|
||||
[interleavedCellDataArray removeObjectAtIndex:currentCellIndex];
|
||||
}
|
||||
else
|
||||
{
|
||||
while (updatedCellData && (updatedCellData.roomSummary.lastMessage.originServerTs > currentCellData.roomSummary.lastMessage.originServerTs))
|
||||
{
|
||||
[interleavedCellDataArray insertObject:updatedCellData atIndex:currentCellIndex++];
|
||||
updatedCellData = [updateRecentsDataSource cellDataAtIndex:updatedCellIndex++];
|
||||
}
|
||||
|
||||
currentCellIndex++;
|
||||
}
|
||||
}
|
||||
|
||||
while (updatedCellData)
|
||||
{
|
||||
[interleavedCellDataArray addObject:updatedCellData];
|
||||
updatedCellData = [updateRecentsDataSource cellDataAtIndex:updatedCellIndex++];
|
||||
}
|
||||
}
|
||||
|
||||
// Call super to keep update readyRecentsDataSourceArray.
|
||||
[super dataSource:dataSource didCellChange:changes];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
Copyright 2015 OpenMarket 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 <Foundation/Foundation.h>
|
||||
|
||||
#import "MXKRecentCellDataStoring.h"
|
||||
|
||||
/**
|
||||
`MXKRecentCellData` modelised the data for a `MXKRecentTableViewCell` cell.
|
||||
*/
|
||||
@interface MXKRecentCellData : MXKCellData <MXKRecentCellDataStoring>
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,133 @@
|
||||
/*
|
||||
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 "MXKRecentCellData.h"
|
||||
|
||||
@import MatrixSDK;
|
||||
|
||||
#import "MXKDataSource.h"
|
||||
#import "MXEvent+MatrixKit.h"
|
||||
|
||||
#import "MXKSwiftHeader.h"
|
||||
|
||||
@implementation MXKRecentCellData
|
||||
@synthesize roomSummary, dataSource, lastEventDate;
|
||||
|
||||
- (instancetype)initWithRoomSummary:(id<MXRoomSummaryProtocol>)theRoomSummary
|
||||
dataSource:(MXKDataSource*)theDataSource;
|
||||
{
|
||||
self = [self init];
|
||||
if (self)
|
||||
{
|
||||
roomSummary = theRoomSummary;
|
||||
dataSource = theDataSource;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
roomSummary = nil;
|
||||
}
|
||||
|
||||
- (MXSession *)mxSession
|
||||
{
|
||||
return dataSource.mxSession;
|
||||
}
|
||||
|
||||
- (NSString*)lastEventDate
|
||||
{
|
||||
return (NSString*)roomSummary.lastMessage.others[@"lastEventDate"];
|
||||
}
|
||||
|
||||
- (BOOL)hasUnread
|
||||
{
|
||||
return (roomSummary.localUnreadEventCount != 0);
|
||||
}
|
||||
|
||||
- (NSString *)roomIdentifier
|
||||
{
|
||||
if (self.isSuggestedRoom)
|
||||
{
|
||||
return self.roomSummary.spaceChildInfo.childRoomId;
|
||||
}
|
||||
return roomSummary.roomId;
|
||||
}
|
||||
|
||||
- (NSString *)roomDisplayname
|
||||
{
|
||||
if (self.isSuggestedRoom)
|
||||
{
|
||||
return self.roomSummary.spaceChildInfo.displayName;
|
||||
}
|
||||
return roomSummary.displayname;
|
||||
}
|
||||
|
||||
- (NSString *)avatarUrl
|
||||
{
|
||||
if (self.isSuggestedRoom)
|
||||
{
|
||||
return self.roomSummary.spaceChildInfo.avatarUrl;
|
||||
}
|
||||
return roomSummary.avatar;
|
||||
}
|
||||
|
||||
- (NSString *)lastEventTextMessage
|
||||
{
|
||||
if (self.isSuggestedRoom)
|
||||
{
|
||||
return roomSummary.spaceChildInfo.topic;
|
||||
}
|
||||
return roomSummary.lastMessage.text;
|
||||
}
|
||||
|
||||
- (NSAttributedString *)lastEventAttributedTextMessage
|
||||
{
|
||||
if (self.isSuggestedRoom)
|
||||
{
|
||||
return nil;
|
||||
}
|
||||
return roomSummary.lastMessage.attributedText;
|
||||
}
|
||||
|
||||
- (NSUInteger)notificationCount
|
||||
{
|
||||
return roomSummary.notificationCount;
|
||||
}
|
||||
|
||||
- (NSUInteger)highlightCount
|
||||
{
|
||||
return roomSummary.highlightCount;
|
||||
}
|
||||
|
||||
- (NSString*)notificationCountStringValue
|
||||
{
|
||||
return [NSString stringWithFormat:@"%tu", self.notificationCount];
|
||||
}
|
||||
|
||||
- (NSString*)description
|
||||
{
|
||||
return [NSString stringWithFormat:@"%@ %@: %@ - %@", super.description, self.roomSummary.roomId, self.roomDisplayname, self.lastEventTextMessage];
|
||||
}
|
||||
|
||||
- (BOOL)isSuggestedRoom
|
||||
{
|
||||
// As off now, we only store MXSpaceChildInfo in case of suggested rooms
|
||||
return self.roomSummary.spaceChildInfo != nil;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
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 <Foundation/Foundation.h>
|
||||
#import <MatrixSDK/MatrixSDK.h>
|
||||
|
||||
#import "MXKCellData.h"
|
||||
|
||||
@class MXKDataSource;
|
||||
@class MXSpaceChildInfo;
|
||||
|
||||
/**
|
||||
`MXKRecentCellDataStoring` defines a protocol a class must conform in order to store recent cell data
|
||||
managed by `MXKSessionRecentsDataSource`.
|
||||
*/
|
||||
@protocol MXKRecentCellDataStoring <NSObject>
|
||||
|
||||
#pragma mark - Data displayed by a room recent cell
|
||||
|
||||
/**
|
||||
The original data source of the recent displayed by the cell.
|
||||
*/
|
||||
@property (nonatomic, weak, readonly) MXKDataSource *dataSource;
|
||||
|
||||
/**
|
||||
The `MXRoomSummaryProtocol` instance of the room for the recent displayed by the cell.
|
||||
*/
|
||||
@property (nonatomic, readonly) id<MXRoomSummaryProtocol> roomSummary;
|
||||
|
||||
@property (nonatomic, readonly) NSString *roomIdentifier;
|
||||
@property (nonatomic, readonly) NSString *roomDisplayname;
|
||||
@property (nonatomic, readonly) NSString *avatarUrl;
|
||||
@property (nonatomic, readonly) NSString *lastEventTextMessage;
|
||||
@property (nonatomic, readonly) NSString *lastEventDate;
|
||||
|
||||
@property (nonatomic, readonly) BOOL hasUnread;
|
||||
@property (nonatomic, readonly) NSUInteger notificationCount;
|
||||
@property (nonatomic, readonly) NSUInteger highlightCount;
|
||||
@property (nonatomic, readonly) NSString *notificationCountStringValue;
|
||||
@property (nonatomic, readonly) BOOL isSuggestedRoom;
|
||||
|
||||
@property (nonatomic, readonly) MXSession *mxSession;
|
||||
|
||||
#pragma mark - Public methods
|
||||
/**
|
||||
Create a new `MXKCellData` object for a new recent cell.
|
||||
|
||||
@param roomSummary the `id<MXRoomSummaryProtocol>` object that has data about the room.
|
||||
@param dataSource the `MXKDataSource` object that will use this instance.
|
||||
@return the newly created instance.
|
||||
*/
|
||||
- (instancetype)initWithRoomSummary:(id<MXRoomSummaryProtocol>)roomSummary
|
||||
dataSource:(MXKDataSource*)dataSource;
|
||||
|
||||
@optional
|
||||
/**
|
||||
The `lastEventTextMessage` with sets of attributes.
|
||||
*/
|
||||
@property (nonatomic, readonly) NSAttributedString *lastEventAttributedTextMessage;
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,140 @@
|
||||
/*
|
||||
Copyright 2015 OpenMarket 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"
|
||||
|
||||
/**
|
||||
'MXKRecentsDataSource' is a base class to handle recents from one or multiple matrix sessions.
|
||||
A 'MXKRecentsDataSource' instance provides the recents data source for `MXKRecentListViewController`.
|
||||
|
||||
By default, the recents list of different sessions are handled into separate sections.
|
||||
*/
|
||||
@interface MXKRecentsDataSource : MXKDataSource <UITableViewDataSource, MXKDataSourceDelegate>
|
||||
{
|
||||
@protected
|
||||
/**
|
||||
Array of `MXKSessionRecentsDataSource` instances. Only ready and non empty data source are listed here.
|
||||
(Note: a data source may be considered as empty during searching)
|
||||
*/
|
||||
NSMutableArray *displayedRecentsDataSourceArray;
|
||||
|
||||
/**
|
||||
Array of shrinked sources. Sub-list of displayedRecentsDataSourceArray.
|
||||
*/
|
||||
NSMutableArray *shrinkedRecentsDataSourceArray;
|
||||
}
|
||||
|
||||
/**
|
||||
List of associated matrix sessions.
|
||||
*/
|
||||
@property (nonatomic, readonly) NSArray* mxSessions;
|
||||
|
||||
/**
|
||||
The number of available recents data sources (This count may be different than mxSession.count because empty data sources are ignored).
|
||||
*/
|
||||
@property (nonatomic, readonly) NSUInteger displayedRecentsDataSourcesCount;
|
||||
|
||||
/**
|
||||
Tell whether there are some unread messages.
|
||||
*/
|
||||
@property (nonatomic, readonly) BOOL hasUnread;
|
||||
|
||||
/**
|
||||
The current search patterns list.
|
||||
*/
|
||||
@property (nonatomic, readonly) NSArray* searchPatternsList;
|
||||
|
||||
@property (nonatomic, strong) MXSpace *currentSpace;
|
||||
|
||||
#pragma mark - Configuration
|
||||
|
||||
/**
|
||||
Add recents data from a matrix session.
|
||||
|
||||
@param mxSession the Matrix session to retrieve contextual data.
|
||||
@return the new 'MXKSessionRecentsDataSource' instance created for this Matrix session.
|
||||
*/
|
||||
- (MXKSessionRecentsDataSource *)addMatrixSession:(MXSession*)mxSession;
|
||||
|
||||
/**
|
||||
Remove recents data related to a matrix session.
|
||||
|
||||
@param mxSession the session to remove.
|
||||
*/
|
||||
- (void)removeMatrixSession:(MXSession*)mxSession;
|
||||
|
||||
/**
|
||||
Filter the current recents list according to the provided patterns.
|
||||
|
||||
@param patternsList the list of patterns (`NSString` instances) to match with. Set nil to cancel search.
|
||||
*/
|
||||
- (void)searchWithPatterns:(NSArray*)patternsList;
|
||||
|
||||
/**
|
||||
Get the section header view.
|
||||
|
||||
@param section the section index
|
||||
@param frame the drawing area for the header of the specified section.
|
||||
@return the section header.
|
||||
*/
|
||||
- (UIView *)viewForHeaderInSection:(NSInteger)section withFrame:(CGRect)frame;
|
||||
|
||||
/**
|
||||
Get the data for the cell at the given index path.
|
||||
|
||||
@param indexPath the index of the cell
|
||||
@return the cell data
|
||||
*/
|
||||
- (id<MXKRecentCellDataStoring>)cellDataAtIndexPath:(NSIndexPath*)indexPath;
|
||||
|
||||
/**
|
||||
Get the height of the cell at the given index path.
|
||||
|
||||
@param indexPath the index of the cell
|
||||
@return the cell height
|
||||
*/
|
||||
- (CGFloat)cellHeightAtIndexPath:(NSIndexPath*)indexPath;
|
||||
|
||||
/**
|
||||
Get the index path of the cell related to the provided roomId and session.
|
||||
|
||||
@param roomId the room identifier.
|
||||
@param mxSession the matrix session in which the room should be available.
|
||||
@return indexPath the index of the cell (nil if not found or if the related section is shrinked).
|
||||
*/
|
||||
- (NSIndexPath*)cellIndexPathWithRoomId:(NSString*)roomId andMatrixSession:(MXSession*)mxSession;
|
||||
|
||||
/**
|
||||
Returns the room at the index path
|
||||
|
||||
@param indexPath the index of the cell
|
||||
@return the MXRoom if it exists
|
||||
*/
|
||||
- (MXRoom*)getRoomAtIndexPath:(NSIndexPath *)indexPath;
|
||||
|
||||
/**
|
||||
Leave the room at the index path
|
||||
|
||||
@param indexPath the index of the cell
|
||||
*/
|
||||
- (void)leaveRoomAtIndexPath:(NSIndexPath *)indexPath;
|
||||
|
||||
/**
|
||||
Action registered on buttons used to shrink/disclose recents sources.
|
||||
*/
|
||||
- (IBAction)onButtonPressed:(id)sender;
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,657 @@
|
||||
/*
|
||||
Copyright 2015 OpenMarket Ltd
|
||||
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
|
||||
|
||||
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 "MXKRecentsDataSource.h"
|
||||
|
||||
@import MatrixSDK.MXMediaManager;
|
||||
|
||||
#import "NSBundle+MatrixKit.h"
|
||||
|
||||
#import "MXKConstants.h"
|
||||
|
||||
@interface MXKRecentsDataSource ()
|
||||
{
|
||||
/**
|
||||
Array of `MXSession` instances.
|
||||
*/
|
||||
NSMutableArray *mxSessionArray;
|
||||
|
||||
/**
|
||||
Array of `MXKSessionRecentsDataSource` instances (one by matrix session).
|
||||
*/
|
||||
NSMutableArray *recentsDataSourceArray;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation MXKRecentsDataSource
|
||||
|
||||
- (instancetype)init
|
||||
{
|
||||
self = [super init];
|
||||
if (self)
|
||||
{
|
||||
mxSessionArray = [NSMutableArray array];
|
||||
recentsDataSourceArray = [NSMutableArray array];
|
||||
|
||||
displayedRecentsDataSourceArray = [NSMutableArray array];
|
||||
shrinkedRecentsDataSourceArray = [NSMutableArray array];
|
||||
|
||||
// Set default data and view classes
|
||||
[self registerCellDataClass:MXKRecentCellData.class forCellIdentifier:kMXKRecentCellIdentifier];
|
||||
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didMXSessionInviteRoomUpdate:) name:kMXSessionInvitedRoomsDidChangeNotification object:nil];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (instancetype)initWithMatrixSession:(MXSession *)matrixSession
|
||||
{
|
||||
self = [self init];
|
||||
if (self)
|
||||
{
|
||||
[self addMatrixSession:matrixSession];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (MXKSessionRecentsDataSource *)addMatrixSession:(MXSession *)matrixSession
|
||||
{
|
||||
MXKSessionRecentsDataSource *recentsDataSource = [[MXKSessionRecentsDataSource alloc] initWithMatrixSession:matrixSession];
|
||||
|
||||
if (recentsDataSource)
|
||||
{
|
||||
// Set the actual data and view classes
|
||||
[recentsDataSource registerCellDataClass:[self cellDataClassForCellIdentifier:kMXKRecentCellIdentifier] forCellIdentifier:kMXKRecentCellIdentifier];
|
||||
|
||||
[mxSessionArray addObject:matrixSession];
|
||||
|
||||
recentsDataSource.delegate = self;
|
||||
[recentsDataSourceArray addObject:recentsDataSource];
|
||||
|
||||
[recentsDataSource finalizeInitialization];
|
||||
|
||||
if (self.delegate && [self.delegate respondsToSelector:@selector(dataSource:didAddMatrixSession:)])
|
||||
{
|
||||
[self.delegate dataSource:self didAddMatrixSession:matrixSession];
|
||||
}
|
||||
|
||||
// Check the current state of the data source
|
||||
[self dataSource:recentsDataSource didStateChange:recentsDataSource.state];
|
||||
}
|
||||
|
||||
return recentsDataSource;
|
||||
}
|
||||
|
||||
- (void)removeMatrixSession:(MXSession*)matrixSession
|
||||
{
|
||||
for (NSUInteger index = 0; index < mxSessionArray.count; index++)
|
||||
{
|
||||
MXSession *mxSession = [mxSessionArray objectAtIndex:index];
|
||||
if (mxSession == matrixSession)
|
||||
{
|
||||
MXKSessionRecentsDataSource *recentsDataSource = [recentsDataSourceArray objectAtIndex:index];
|
||||
[recentsDataSource destroy];
|
||||
|
||||
[displayedRecentsDataSourceArray removeObject:recentsDataSource];
|
||||
|
||||
[recentsDataSourceArray removeObjectAtIndex:index];
|
||||
[mxSessionArray removeObjectAtIndex:index];
|
||||
|
||||
// Loop on 'didCellChange' method to let inherited 'MXKRecentsDataSource' class handle this removed data source.
|
||||
[self dataSource:recentsDataSource didCellChange:nil];
|
||||
|
||||
if (self.delegate && [self.delegate respondsToSelector:@selector(dataSource:didRemoveMatrixSession:)])
|
||||
{
|
||||
[self.delegate dataSource:self didRemoveMatrixSession:matrixSession];
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setCurrentSpace:(MXSpace *)currentSpace
|
||||
{
|
||||
_currentSpace = currentSpace;
|
||||
|
||||
for (MXKSessionRecentsDataSource *recentsDataSource in recentsDataSourceArray) {
|
||||
recentsDataSource.currentSpace = currentSpace;
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - MXKDataSource overridden
|
||||
|
||||
- (MXSession*)mxSession
|
||||
{
|
||||
if (mxSessionArray.count > 1)
|
||||
{
|
||||
MXLogDebug(@"[MXKRecentsDataSource] CAUTION: mxSession property is not relevant in case of multi-sessions (%tu)", mxSessionArray.count);
|
||||
}
|
||||
|
||||
// TODO: This property is not well adapted in case of multi-sessions
|
||||
// We consider by default the first added session as the main one...
|
||||
if (mxSessionArray.count)
|
||||
{
|
||||
return [mxSessionArray firstObject];
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (MXKDataSourceState)state
|
||||
{
|
||||
// Manage a global state based on the state of each internal data source.
|
||||
|
||||
MXKDataSourceState currentState = MXKDataSourceStateUnknown;
|
||||
MXKSessionRecentsDataSource *dataSource;
|
||||
|
||||
if (recentsDataSourceArray.count)
|
||||
{
|
||||
dataSource = [recentsDataSourceArray firstObject];
|
||||
currentState = dataSource.state;
|
||||
|
||||
// Deduce the current state according to the internal data sources
|
||||
for (NSUInteger index = 1; index < recentsDataSourceArray.count; index++)
|
||||
{
|
||||
dataSource = [recentsDataSourceArray objectAtIndex:index];
|
||||
|
||||
switch (dataSource.state)
|
||||
{
|
||||
case MXKDataSourceStateUnknown:
|
||||
break;
|
||||
case MXKDataSourceStatePreparing:
|
||||
currentState = MXKDataSourceStatePreparing;
|
||||
break;
|
||||
case MXKDataSourceStateFailed:
|
||||
if (currentState == MXKDataSourceStateUnknown)
|
||||
{
|
||||
currentState = MXKDataSourceStateFailed;
|
||||
}
|
||||
break;
|
||||
case MXKDataSourceStateReady:
|
||||
if (currentState == MXKDataSourceStateUnknown || currentState == MXKDataSourceStateFailed)
|
||||
{
|
||||
currentState = MXKDataSourceStateReady;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return currentState;
|
||||
}
|
||||
|
||||
- (void)destroy
|
||||
{
|
||||
for (MXKSessionRecentsDataSource *recentsDataSource in recentsDataSourceArray)
|
||||
{
|
||||
[recentsDataSource destroy];
|
||||
}
|
||||
displayedRecentsDataSourceArray = nil;
|
||||
recentsDataSourceArray = nil;
|
||||
shrinkedRecentsDataSourceArray = nil;
|
||||
mxSessionArray = nil;
|
||||
|
||||
_searchPatternsList = nil;
|
||||
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:self name:kMXSessionInvitedRoomsDidChangeNotification object:nil];
|
||||
|
||||
[super destroy];
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
|
||||
- (NSArray*)mxSessions
|
||||
{
|
||||
return [NSArray arrayWithArray:mxSessionArray];
|
||||
}
|
||||
|
||||
- (NSUInteger)displayedRecentsDataSourcesCount
|
||||
{
|
||||
return displayedRecentsDataSourceArray.count;
|
||||
}
|
||||
|
||||
- (BOOL)hasUnread
|
||||
{
|
||||
// Check hasUnread flag in all ready data sources
|
||||
for (MXKSessionRecentsDataSource *recentsDataSource in displayedRecentsDataSourceArray)
|
||||
{
|
||||
if (recentsDataSource.hasUnread)
|
||||
{
|
||||
return YES;
|
||||
}
|
||||
}
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (void)searchWithPatterns:(NSArray*)patternsList
|
||||
{
|
||||
_searchPatternsList = patternsList;
|
||||
|
||||
// CAUTION: Apply here the search pattern to all ready data sources (not only displayed ones).
|
||||
// Some data sources may have been removed from 'displayedRecentsDataSourceArray' during a previous search if no recent was matching.
|
||||
for (MXKSessionRecentsDataSource *recentsDataSource in recentsDataSourceArray)
|
||||
{
|
||||
if (recentsDataSource.state == MXKDataSourceStateReady)
|
||||
{
|
||||
[recentsDataSource searchWithPatterns:patternsList];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (UIView *)viewForHeaderInSection:(NSInteger)section withFrame:(CGRect)frame
|
||||
{
|
||||
UIView *sectionHeader = nil;
|
||||
|
||||
if (displayedRecentsDataSourceArray.count > 1 && section < displayedRecentsDataSourceArray.count)
|
||||
{
|
||||
MXKSessionRecentsDataSource *recentsDataSource = [displayedRecentsDataSourceArray objectAtIndex:section];
|
||||
|
||||
NSString* sectionTitle = recentsDataSource.mxSession.myUser.userId;
|
||||
|
||||
sectionHeader = [[UIView alloc] initWithFrame:frame];
|
||||
sectionHeader.backgroundColor = [UIColor colorWithRed:0.9 green:0.9 blue:0.9 alpha:1.0];
|
||||
|
||||
// Add shrink button
|
||||
UIButton *shrinkButton = [UIButton buttonWithType:UIButtonTypeCustom];
|
||||
frame.origin.x = frame.origin.y = 0;
|
||||
shrinkButton.frame = frame;
|
||||
shrinkButton.backgroundColor = [UIColor clearColor];
|
||||
[shrinkButton addTarget:self action:@selector(onButtonPressed:) forControlEvents:UIControlEventTouchUpInside];
|
||||
shrinkButton.tag = section;
|
||||
[sectionHeader addSubview:shrinkButton];
|
||||
sectionHeader.userInteractionEnabled = YES;
|
||||
|
||||
// Add shrink icon
|
||||
UIImage *chevron;
|
||||
if ([shrinkedRecentsDataSourceArray indexOfObject:recentsDataSource] != NSNotFound)
|
||||
{
|
||||
chevron = [NSBundle mxk_imageFromMXKAssetsBundleWithName:@"disclosure"];
|
||||
}
|
||||
else
|
||||
{
|
||||
chevron = [NSBundle mxk_imageFromMXKAssetsBundleWithName:@"shrink"];
|
||||
}
|
||||
UIImageView *chevronView = [[UIImageView alloc] initWithImage:chevron];
|
||||
chevronView.contentMode = UIViewContentModeCenter;
|
||||
frame = chevronView.frame;
|
||||
frame.origin.x = sectionHeader.frame.size.width - frame.size.width - 8;
|
||||
frame.origin.y = (sectionHeader.frame.size.height - frame.size.height) / 2;
|
||||
chevronView.frame = frame;
|
||||
[sectionHeader addSubview:chevronView];
|
||||
chevronView.autoresizingMask = (UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin);
|
||||
|
||||
// Add label
|
||||
frame = sectionHeader.frame;
|
||||
frame.origin.x = 5;
|
||||
frame.origin.y = 5;
|
||||
frame.size.width = chevronView.frame.origin.x - 10;
|
||||
frame.size.height -= 10;
|
||||
UILabel *headerLabel = [[UILabel alloc] initWithFrame:frame];
|
||||
headerLabel.font = [UIFont boldSystemFontOfSize:16];
|
||||
headerLabel.backgroundColor = [UIColor clearColor];
|
||||
headerLabel.text = sectionTitle;
|
||||
[sectionHeader addSubview:headerLabel];
|
||||
}
|
||||
|
||||
return sectionHeader;
|
||||
}
|
||||
|
||||
- (id<MXKRecentCellDataStoring>)cellDataAtIndexPath:(NSIndexPath *)indexPath
|
||||
{
|
||||
if (indexPath.section < displayedRecentsDataSourceArray.count)
|
||||
{
|
||||
MXKSessionRecentsDataSource *recentsDataSource = [displayedRecentsDataSourceArray objectAtIndex:indexPath.section];
|
||||
|
||||
return [recentsDataSource cellDataAtIndex:indexPath.row];
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (CGFloat)cellHeightAtIndexPath:(NSIndexPath *)indexPath
|
||||
{
|
||||
if (indexPath.section < displayedRecentsDataSourceArray.count)
|
||||
{
|
||||
MXKSessionRecentsDataSource *recentsDataSource = [displayedRecentsDataSourceArray objectAtIndex:indexPath.section];
|
||||
|
||||
return [recentsDataSource cellHeightAtIndex:indexPath.row];
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
- (NSIndexPath*)cellIndexPathWithRoomId:(NSString*)roomId andMatrixSession:(MXSession*)matrixSession
|
||||
{
|
||||
NSIndexPath *indexPath = nil;
|
||||
|
||||
// Look for the right data source
|
||||
for (NSInteger section = 0; section < displayedRecentsDataSourceArray.count; section++)
|
||||
{
|
||||
MXKSessionRecentsDataSource *recentsDataSource = displayedRecentsDataSourceArray[section];
|
||||
if (recentsDataSource.mxSession == matrixSession)
|
||||
{
|
||||
// Check whether the source is not shrinked
|
||||
if ([shrinkedRecentsDataSourceArray indexOfObject:recentsDataSource] == NSNotFound)
|
||||
{
|
||||
// Look for the cell
|
||||
for (NSInteger index = 0; index < recentsDataSource.numberOfCells; index ++)
|
||||
{
|
||||
id<MXKRecentCellDataStoring> recentCellData = [recentsDataSource cellDataAtIndex:index];
|
||||
if ([roomId isEqualToString:recentCellData.roomIdentifier])
|
||||
{
|
||||
// Got it
|
||||
indexPath = [NSIndexPath indexPathForRow:index inSection:section];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return indexPath;
|
||||
}
|
||||
|
||||
#pragma mark - UITableViewDataSource
|
||||
|
||||
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
|
||||
{
|
||||
// Check whether all data sources are ready before rendering recents
|
||||
if (self.state == MXKDataSourceStateReady)
|
||||
{
|
||||
return displayedRecentsDataSourceArray.count;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
|
||||
{
|
||||
if (section < displayedRecentsDataSourceArray.count)
|
||||
{
|
||||
MXKSessionRecentsDataSource *recentsDataSource = [displayedRecentsDataSourceArray objectAtIndex:section];
|
||||
|
||||
// Check whether the source is shrinked
|
||||
if ([shrinkedRecentsDataSourceArray indexOfObject:recentsDataSource] == NSNotFound)
|
||||
{
|
||||
return recentsDataSource.numberOfCells;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section
|
||||
{
|
||||
NSString* sectionTitle = nil;
|
||||
|
||||
if (displayedRecentsDataSourceArray.count > 1 && section < displayedRecentsDataSourceArray.count)
|
||||
{
|
||||
MXKSessionRecentsDataSource *recentsDataSource = [displayedRecentsDataSourceArray objectAtIndex:section];
|
||||
|
||||
sectionTitle = recentsDataSource.mxSession.myUser.userId;
|
||||
}
|
||||
|
||||
return sectionTitle;
|
||||
}
|
||||
|
||||
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
|
||||
{
|
||||
if (indexPath.section < displayedRecentsDataSourceArray.count && self.delegate)
|
||||
{
|
||||
MXKSessionRecentsDataSource *recentsDataSource = [displayedRecentsDataSourceArray objectAtIndex:indexPath.section];
|
||||
|
||||
id<MXKRecentCellDataStoring> roomData = [recentsDataSource cellDataAtIndex:indexPath.row];
|
||||
|
||||
NSString *cellIdentifier = [self.delegate cellReuseIdentifierForCellData:roomData];
|
||||
if (cellIdentifier)
|
||||
{
|
||||
UITableViewCell<MXKCellRendering> *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier forIndexPath:indexPath];
|
||||
|
||||
// Make sure we listen to user actions on the cell
|
||||
cell.delegate = self;
|
||||
|
||||
// Make the bubble display the data
|
||||
[cell render:roomData];
|
||||
|
||||
return cell;
|
||||
}
|
||||
}
|
||||
|
||||
// Return a fake cell to prevent app from crashing.
|
||||
return [[UITableViewCell alloc] init];
|
||||
}
|
||||
|
||||
- (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath
|
||||
{
|
||||
// Return NO if you do not want the specified item to be editable.
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath
|
||||
{
|
||||
if (editingStyle == UITableViewCellEditingStyleDelete)
|
||||
{
|
||||
[self leaveRoomAtIndexPath:indexPath];
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - MXKDataSourceDelegate
|
||||
|
||||
- (Class<MXKCellRendering>)cellViewClassForCellData:(MXKCellData*)cellData
|
||||
{
|
||||
// Retrieve the class from the delegate here
|
||||
if (self.delegate)
|
||||
{
|
||||
return [self.delegate cellViewClassForCellData:cellData];
|
||||
}
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (NSString *)cellReuseIdentifierForCellData:(MXKCellData*)cellData
|
||||
{
|
||||
// Retrieve the identifier from the delegate here
|
||||
if (self.delegate)
|
||||
{
|
||||
return [self.delegate cellReuseIdentifierForCellData:cellData];
|
||||
}
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (void)dataSource:(MXKDataSource*)dataSource didCellChange:(id)changes
|
||||
{
|
||||
// Keep update readyRecentsDataSourceArray by checking number of cells
|
||||
if (dataSource.state == MXKDataSourceStateReady)
|
||||
{
|
||||
MXKSessionRecentsDataSource *recentsDataSource = (MXKSessionRecentsDataSource*)dataSource;
|
||||
|
||||
if (recentsDataSource.numberOfCells)
|
||||
{
|
||||
// Check whether the data source must be added
|
||||
if ([displayedRecentsDataSourceArray indexOfObject:recentsDataSource] == NSNotFound)
|
||||
{
|
||||
// Add this data source first
|
||||
[self dataSource:dataSource didStateChange:dataSource.state];
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Check whether this data source must be removed
|
||||
if ([displayedRecentsDataSourceArray indexOfObject:recentsDataSource] != NSNotFound)
|
||||
{
|
||||
[displayedRecentsDataSourceArray removeObject:recentsDataSource];
|
||||
|
||||
// Loop on 'didCellChange' method to let inherited 'MXKRecentsDataSource' class handle this removed data source.
|
||||
[self dataSource:recentsDataSource didCellChange:nil];
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Notify delegate
|
||||
[self.delegate dataSource:self didCellChange:changes];
|
||||
}
|
||||
|
||||
- (void)dataSource:(MXKDataSource*)dataSource didStateChange:(MXKDataSourceState)state
|
||||
{
|
||||
// Update list of ready data sources
|
||||
MXKSessionRecentsDataSource *recentsDataSource = (MXKSessionRecentsDataSource*)dataSource;
|
||||
if (dataSource.state == MXKDataSourceStateReady && recentsDataSource.numberOfCells)
|
||||
{
|
||||
if ([displayedRecentsDataSourceArray indexOfObject:recentsDataSource] == NSNotFound)
|
||||
{
|
||||
// Add this new recents data source.
|
||||
if (!displayedRecentsDataSourceArray.count)
|
||||
{
|
||||
[displayedRecentsDataSourceArray addObject:recentsDataSource];
|
||||
}
|
||||
else
|
||||
{
|
||||
// To display multiple accounts in a consistent order, we sort the recents data source by considering the account user id (alphabetic order).
|
||||
NSUInteger index;
|
||||
for (index = 0; index < displayedRecentsDataSourceArray.count; index++)
|
||||
{
|
||||
MXKSessionRecentsDataSource *currentRecentsDataSource = displayedRecentsDataSourceArray[index];
|
||||
if ([currentRecentsDataSource.mxSession.myUser.userId compare:recentsDataSource.mxSession.myUser.userId] == NSOrderedDescending)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Insert this data source
|
||||
[displayedRecentsDataSourceArray insertObject:recentsDataSource atIndex:index];
|
||||
}
|
||||
|
||||
// Check whether a search session is in progress
|
||||
if (_searchPatternsList)
|
||||
{
|
||||
[recentsDataSource searchWithPatterns:_searchPatternsList];
|
||||
}
|
||||
else
|
||||
{
|
||||
// Loop on 'didCellChange' method to let inherited 'MXKRecentsDataSource' class handle this new added data source.
|
||||
[self dataSource:recentsDataSource didCellChange:nil];
|
||||
}
|
||||
}
|
||||
}
|
||||
else if ([displayedRecentsDataSourceArray indexOfObject:recentsDataSource] != NSNotFound)
|
||||
{
|
||||
[displayedRecentsDataSourceArray removeObject:recentsDataSource];
|
||||
|
||||
// Loop on 'didCellChange' method to let inherited 'MXKRecentsDataSource' class handle this removed data source.
|
||||
[self dataSource:recentsDataSource didCellChange:nil];
|
||||
}
|
||||
|
||||
if (self.delegate && [self.delegate respondsToSelector:@selector(dataSource:didStateChange:)])
|
||||
{
|
||||
[self.delegate dataSource:self didStateChange:self.state];
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - Action
|
||||
|
||||
- (IBAction)onButtonPressed:(id)sender
|
||||
{
|
||||
if ([sender isKindOfClass:[UIButton class]])
|
||||
{
|
||||
UIButton *shrinkButton = (UIButton*)sender;
|
||||
|
||||
if (shrinkButton.tag < displayedRecentsDataSourceArray.count)
|
||||
{
|
||||
MXKSessionRecentsDataSource *recentsDataSource = [displayedRecentsDataSourceArray objectAtIndex:shrinkButton.tag];
|
||||
|
||||
NSUInteger index = [shrinkedRecentsDataSourceArray indexOfObject:recentsDataSource];
|
||||
if (index != NSNotFound)
|
||||
{
|
||||
// Disclose the
|
||||
[shrinkedRecentsDataSourceArray removeObjectAtIndex:index];
|
||||
}
|
||||
else
|
||||
{
|
||||
// Shrink the recents from this session
|
||||
[shrinkedRecentsDataSourceArray addObject:recentsDataSource];
|
||||
}
|
||||
|
||||
// Loop on 'didCellChange' method to let inherited 'MXKRecentsDataSource' class handle change on this data source.
|
||||
[self dataSource:recentsDataSource didCellChange:nil];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - room actions
|
||||
- (MXRoom*)getRoomAtIndexPath:(NSIndexPath *)indexPath
|
||||
{
|
||||
// Leave the selected room
|
||||
id<MXKRecentCellDataStoring> recentCellData = [self cellDataAtIndexPath:indexPath];
|
||||
|
||||
if (recentCellData)
|
||||
{
|
||||
return [self.mxSession roomWithRoomId:recentCellData.roomIdentifier];
|
||||
}
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (void)leaveRoomAtIndexPath:(NSIndexPath *)indexPath
|
||||
{
|
||||
MXRoom* room = [self getRoomAtIndexPath:indexPath];
|
||||
|
||||
if (room)
|
||||
{
|
||||
// cancel pending uploads/downloads
|
||||
// they are useless by now
|
||||
[MXMediaManager cancelDownloadsInCacheFolder:room.roomId];
|
||||
|
||||
// TODO GFO cancel pending uploads related to this room
|
||||
|
||||
[room leave:^{
|
||||
|
||||
// Trigger recents table refresh
|
||||
if (self.delegate)
|
||||
{
|
||||
[self.delegate dataSource:self didCellChange:nil];
|
||||
}
|
||||
|
||||
} failure:^(NSError *error) {
|
||||
|
||||
MXLogDebug(@"[MXKRecentsDataSource] Failed to leave room (%@) failed", room.roomId);
|
||||
|
||||
// Notify MatrixKit user
|
||||
NSString *myUserId = room.mxSession.myUser.userId;
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:kMXKErrorNotification object:error userInfo:myUserId ? @{kMXKErrorUserIdKey: myUserId} : nil];
|
||||
|
||||
}];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)didMXSessionInviteRoomUpdate:(NSNotification *)notif
|
||||
{
|
||||
MXSession *mxSession = notif.object;
|
||||
if ([self.mxSessions indexOfObject:mxSession] != NSNotFound)
|
||||
{
|
||||
// do nothing by default
|
||||
// the inherited classes might require to perform a full or a particial refresh.
|
||||
//[self.delegate dataSource:self didCellChange:nil];
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,90 @@
|
||||
/*
|
||||
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 <UIKit/UIKit.h>
|
||||
|
||||
#import "MXKConstants.h"
|
||||
#import "MXKDataSource.h"
|
||||
#import "MXKRecentCellData.h"
|
||||
|
||||
@class MXSpace;
|
||||
|
||||
/**
|
||||
Identifier to use for cells that display a room in the recents list.
|
||||
*/
|
||||
extern NSString *const kMXKRecentCellIdentifier;
|
||||
|
||||
/**
|
||||
The recents data source based on a unique matrix session.
|
||||
*/
|
||||
MXK_DEPRECATED_ATTRIBUTE_WITH_MSG("See MXSession.roomListDataManager")
|
||||
@interface MXKSessionRecentsDataSource : MXKDataSource {
|
||||
|
||||
@protected
|
||||
|
||||
/**
|
||||
The data for the cells served by `MXKSessionRecentsDataSource`.
|
||||
*/
|
||||
NSMutableArray *cellDataArray;
|
||||
|
||||
/**
|
||||
The filtered recents: sub-list of `cellDataArray` defined by `searchWithPatterns:` call.
|
||||
*/
|
||||
NSMutableArray *filteredCellDataArray;
|
||||
}
|
||||
|
||||
/**
|
||||
The current number of cells.
|
||||
*/
|
||||
@property (nonatomic, readonly) NSInteger numberOfCells;
|
||||
|
||||
/**
|
||||
Tell whether there are some unread messages.
|
||||
*/
|
||||
@property (nonatomic, readonly) BOOL hasUnread;
|
||||
|
||||
@property (nonatomic, strong, nullable) MXSpace *currentSpace;
|
||||
|
||||
|
||||
#pragma mark - Life cycle
|
||||
|
||||
/**
|
||||
Filter the current recents list according to the provided patterns.
|
||||
When patterns are not empty, the search result is stored in `filteredCellDataArray`,
|
||||
this array provides then data for the cells served by `MXKRecentsDataSource`.
|
||||
|
||||
@param patternsList the list of patterns (`NSString` instances) to match with. Set nil to cancel search.
|
||||
*/
|
||||
- (void)searchWithPatterns:(NSArray*)patternsList;
|
||||
|
||||
/**
|
||||
Get the data for the cell at the given index.
|
||||
|
||||
@param index the index of the cell in the array
|
||||
@return the cell data
|
||||
*/
|
||||
- (id<MXKRecentCellDataStoring>)cellDataAtIndex:(NSInteger)index;
|
||||
|
||||
/**
|
||||
Get height of the cell at the given index.
|
||||
|
||||
@param index the index of the cell in the array
|
||||
@return the cell height
|
||||
*/
|
||||
- (CGFloat)cellHeightAtIndex:(NSInteger)index;
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,552 @@
|
||||
/*
|
||||
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
|
||||
Reference in New Issue
Block a user