element-ios/Riot/Modules/MatrixKit/Models/RoomList/MXKInterleavedRecentsDataSource.m
2022-02-10 03:19:00 +03:00

439 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 "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