element-ios/Riot/Modules/MatrixKit/Models/RoomMemberList/MXKRoomMemberListDataSource.m
2021-12-15 01:44:04 +03:00

464 lines
15 KiB
Objective-C

/*
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 "MXKRoomMemberListDataSource.h"
@import MatrixSDK.MXCallManager;
#import "MXKRoomMemberCellData.h"
#pragma mark - Constant definitions
NSString *const kMXKRoomMemberCellIdentifier = @"kMXKRoomMemberCellIdentifier";
@interface MXKRoomMemberListDataSource ()
{
/**
The room in which members are listed.
*/
MXRoom *mxRoom;
/**
Cache for loaded room state.
*/
MXRoomState *mxRoomState;
/**
The members events listener.
*/
id membersListener;
/**
The typing notification listener in the room.
*/
id typingNotifListener;
}
@end
@implementation MXKRoomMemberListDataSource
- (instancetype)initWithRoomId:(NSString*)roomId andMatrixSession:(MXSession*)mxSession
{
self = [super initWithMatrixSession:mxSession];
if (self)
{
_roomId = roomId;
cellDataArray = [NSMutableArray array];
filteredCellDataArray = nil;
// Consider the shared app settings by default
_settings = [MXKAppSettings standardAppSettings];
// Set default data class
[self registerCellDataClass:MXKRoomMemberCellData.class forCellIdentifier:kMXKRoomMemberCellIdentifier];
}
return self;
}
- (void)destroy
{
cellDataArray = nil;
filteredCellDataArray = nil;
if (membersListener)
{
[self.mxSession removeListener:membersListener];
membersListener = nil;
}
if (typingNotifListener)
{
MXWeakify(self);
[mxRoom liveTimeline:^(id<MXEventTimeline> liveTimeline) {
MXStrongifyAndReturnIfNil(self);
[liveTimeline removeListener:self->typingNotifListener];
self->typingNotifListener = nil;
}];
}
[super destroy];
}
- (void)didMXSessionStateChange
{
if (MXSessionStateStoreDataReady <= self.mxSession.state)
{
// Check whether the room is not already set
if (!mxRoom)
{
mxRoom = [self.mxSession roomWithRoomId:_roomId];
if (mxRoom)
{
MXWeakify(self);
[mxRoom state:^(MXRoomState *roomState) {
MXStrongifyAndReturnIfNil(self);
self->mxRoomState = roomState;
[self loadData];
// Register on typing notif
[self listenTypingNotifications];
// Register on members events
[self listenMembersEvents];
// Update here data source state
self->state = MXKDataSourceStateReady;
// Notify delegate
if (self.delegate)
{
if ([self.delegate respondsToSelector:@selector(dataSource:didStateChange:)])
{
[self.delegate dataSource:self didStateChange:self->state];
}
[self.delegate dataSource:self didCellChange:nil];
}
}];
}
else
{
MXLogDebug(@"[MXKRoomMemberDataSource] The user does not know the room %@", _roomId);
// Update here data source state
state = MXKDataSourceStateFailed;
// Notify delegate
if (self.delegate && [self.delegate respondsToSelector:@selector(dataSource:didStateChange:)])
{
[self.delegate dataSource:self didStateChange:state];
}
}
}
}
}
- (void)searchWithPatterns:(NSArray*)patternsList
{
if (patternsList.count)
{
if (filteredCellDataArray)
{
[filteredCellDataArray removeAllObjects];
}
else
{
filteredCellDataArray = [NSMutableArray arrayWithCapacity:cellDataArray.count];
}
for (id<MXKRoomMemberCellDataStoring> cellData in cellDataArray)
{
for (NSString* pattern in patternsList)
{
if ([[cellData memberDisplayName] rangeOfString:pattern options:NSCaseInsensitiveSearch].location != NSNotFound)
{
[filteredCellDataArray addObject:cellData];
break;
}
}
}
}
else
{
filteredCellDataArray = nil;
}
if (self.delegate)
{
[self.delegate dataSource:self didCellChange:nil];
}
}
- (id<MXKRoomMemberCellDataStoring>)cellDataAtIndex:(NSInteger)index
{
if (filteredCellDataArray)
{
return filteredCellDataArray[index];
}
return cellDataArray[index];
}
- (CGFloat)cellHeightAtIndex:(NSInteger)index
{
if (self.delegate)
{
id<MXKRoomMemberCellDataStoring> cellData = [self cellDataAtIndex:index];
Class<MXKCellRendering> class = [self.delegate cellViewClassForCellData:cellData];
return [class heightForCellData:cellData withMaximumWidth:0];
}
return 0;
}
#pragma mark - Members processing
- (void)loadData
{
NSArray* membersList = [mxRoomState.members membersWithoutConferenceUser];
if (!_settings.showLeftMembersInRoomMemberList)
{
NSMutableArray* filteredMembers = [[NSMutableArray alloc] init];
for (MXRoomMember* member in membersList)
{
// Filter out left users
if (member.membership != MXMembershipLeave)
{
[filteredMembers addObject:member];
}
}
membersList = filteredMembers;
}
[cellDataArray removeAllObjects];
// Retrieve the MXKCellData class to manage the data
Class class = [self cellDataClassForCellIdentifier:kMXKRoomMemberCellIdentifier];
NSAssert([class conformsToProtocol:@protocol(MXKRoomMemberCellDataStoring)], @"MXKRoomMemberListDataSource only manages MXKCellData that conforms to MXKRoomMemberCellDataStoring protocol");
for (MXRoomMember *member in membersList)
{
id<MXKRoomMemberCellDataStoring> cellData = [[class alloc] initWithRoomMember:member roomState:mxRoomState andRoomMemberListDataSource:self];
if (cellData)
{
[cellDataArray addObject:cellData];
}
}
[self sortMembers];
}
- (void)sortMembers
{
NSArray *sortedMembers = [cellDataArray sortedArrayUsingComparator:^NSComparisonResult(id<MXKRoomMemberCellDataStoring> member1, id<MXKRoomMemberCellDataStoring> member2)
{
// Move banned and left members at the end of the list
if (member1.roomMember.membership == MXMembershipLeave || member1.roomMember.membership == MXMembershipBan)
{
if (member2.roomMember.membership != MXMembershipLeave && member2.roomMember.membership != MXMembershipBan)
{
return NSOrderedDescending;
}
}
else if (member2.roomMember.membership == MXMembershipLeave || member2.roomMember.membership == MXMembershipBan)
{
return NSOrderedAscending;
}
// Move invited members just before left and banned members
if (member1.roomMember.membership == MXMembershipInvite)
{
if (member2.roomMember.membership != MXMembershipInvite)
{
return NSOrderedDescending;
}
}
else if (member2.roomMember.membership == MXMembershipInvite)
{
return NSOrderedAscending;
}
if (self->_settings.sortRoomMembersUsingLastSeenTime)
{
// Get the users that correspond to these members
MXUser *user1 = [self.mxSession userWithUserId:member1.roomMember.userId];
MXUser *user2 = [self.mxSession userWithUserId:member2.roomMember.userId];
// Move users who are not online or unavailable at the end (before invited users)
if ((user1.presence == MXPresenceOnline) || (user1.presence == MXPresenceUnavailable))
{
if ((user2.presence != MXPresenceOnline) && (user2.presence != MXPresenceUnavailable))
{
return NSOrderedAscending;
}
}
else if ((user2.presence == MXPresenceOnline) || (user2.presence == MXPresenceUnavailable))
{
return NSOrderedDescending;
}
else
{
// Here both users are neither online nor unavailable (the lastActive ago is useless)
// We will sort them according to their display, by keeping in front the offline users
if (user1.presence == MXPresenceOffline)
{
if (user2.presence != MXPresenceOffline)
{
return NSOrderedAscending;
}
}
else if (user2.presence == MXPresenceOffline)
{
return NSOrderedDescending;
}
return [[self->mxRoomState.members memberSortedName:member1.roomMember.userId] compare:[self->mxRoomState.members memberSortedName:member2.roomMember.userId] options:NSCaseInsensitiveSearch];
}
// Consider user's lastActive ago value
if (user1.lastActiveAgo < user2.lastActiveAgo)
{
return NSOrderedAscending;
}
else if (user1.lastActiveAgo == user2.lastActiveAgo)
{
return [[self->mxRoomState.members memberSortedName:member1.roomMember.userId] compare:[self->mxRoomState.members memberSortedName:member2.roomMember.userId] options:NSCaseInsensitiveSearch];
}
return NSOrderedDescending;
}
else
{
// Move user without display name at the end (before invited users)
if (member1.roomMember.displayname.length)
{
if (!member2.roomMember.displayname.length)
{
return NSOrderedAscending;
}
}
else if (member2.roomMember.displayname.length)
{
return NSOrderedDescending;
}
return [[self->mxRoomState.members memberSortedName:member1.roomMember.userId] compare:[self->mxRoomState.members memberSortedName:member2.roomMember.userId] options:NSCaseInsensitiveSearch];
}
}];
cellDataArray = [NSMutableArray arrayWithArray:sortedMembers];
}
- (void)listenMembersEvents
{
// Remove the previous live listener
if (membersListener)
{
[self.mxSession removeListener:membersListener];
}
// Register a listener for events that concern room members
NSArray *mxMembersEvents = @[
kMXEventTypeStringRoomMember,
kMXEventTypeStringRoomPowerLevels,
kMXEventTypeStringPresence
];
membersListener = [self.mxSession listenToEventsOfTypes:mxMembersEvents onEvent:^(MXEvent *event, MXTimelineDirection direction, id customObject)
{
// consider only live event
if (direction == MXTimelineDirectionForwards)
{
// Check the room Id (if any)
if (event.roomId && [event.roomId isEqualToString:self->mxRoom.roomId] == NO)
{
// This event does not concern the current room members
return;
}
// refresh the whole members list. TODO GFO refresh only the updated members.
[self loadData];
if (self.delegate)
{
[self.delegate dataSource:self didCellChange:nil];
}
}
}];
}
- (void)listenTypingNotifications
{
// Remove the previous live listener
if (self->typingNotifListener)
{
[mxRoom removeListener:self->typingNotifListener];
}
// Add typing notification listener
self->typingNotifListener = [mxRoom listenToEventsOfTypes:@[kMXEventTypeStringTypingNotification] onEvent:^(MXEvent *event, MXTimelineDirection direction, MXRoomState *roomState) {
// Handle only live events
if (direction == MXTimelineDirectionForwards)
{
// Retrieve typing users list
NSMutableArray *typingUsers = [NSMutableArray arrayWithArray:self->mxRoom.typingUsers];
// Remove typing info for the current user
NSUInteger index = [typingUsers indexOfObject:self.mxSession.myUser.userId];
if (index != NSNotFound)
{
[typingUsers removeObjectAtIndex:index];
}
for (id<MXKRoomMemberCellDataStoring> cellData in self->cellDataArray)
{
if ([typingUsers indexOfObject:cellData.roomMember.userId] == NSNotFound)
{
cellData.isTyping = NO;
}
else
{
cellData.isTyping = YES;
}
}
if (self.delegate)
{
[self.delegate dataSource:self didCellChange:nil];
}
}
}];
}
#pragma mark - UITableViewDataSource
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
if (filteredCellDataArray)
{
return filteredCellDataArray.count;
}
return cellDataArray.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
id<MXKRoomMemberCellDataStoring> roomData = [self cellDataAtIndex:indexPath.row];
if (roomData && self.delegate)
{
NSString *identifier = [self.delegate cellReuseIdentifierForCellData:roomData];
if (identifier)
{
UITableViewCell<MXKCellRendering> *cell = [tableView dequeueReusableCellWithIdentifier:identifier forIndexPath:indexPath];
// Make the bubble display the data
[cell render:roomData];
return cell;
}
}
// Return a fake cell to prevent app from crashing.
return [[UITableViewCell alloc] init];
}
@end