element-ios/Riot/Modules/MatrixKit/Models/RoomList/MXKInterleavedRecentsDataSo...

432 lines
18 KiB
Objective-C

/*
Copyright 2024 New Vector Ltd.
Copyright 2017 Vector Creations Ltd
Copyright 2015 OpenMarket Ltd
SPDX-License-Identifier: AGPL-3.0-only
Please see LICENSE in the repository root for full details.
*/
#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 inTableView:(UITableView*)tableView
{
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