element-ios/Vector/Categories/MXRoom+Vector.m

556 lines
19 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 "MXRoom+Vector.h"
#import "AvatarGenerator.h"
#import <objc/runtime.h>
@implementation MXRoom (Vector)
#pragma mark - User power level
- (BOOL)isModerator
{
// Check whether the user has enough power to rename the room or update the avatar
MXRoomPowerLevels *powerLevels = [self.state powerLevels];
NSInteger userPowerLevel = [powerLevels powerLevelOfUserWithUserID:self.mxSession.myUser.userId];
return (userPowerLevel >= [powerLevels minimumPowerLevelForSendingEventAsStateEvent:kMXEventTypeStringRoomName]);
}
#pragma mark - Room avatar
- (void)setRoomAvatarImageIn:(MXKImageView*)mxkImageView
{
NSString* roomAvatarUrl = self.state.avatar;
// detect if it is a room with no more than 2 members (i.e. an alone or a 1:1 chat)
if (!roomAvatarUrl)
{
NSString* myUserId = self.mxSession.myUser.userId;
NSArray* members = self.state.members;
if (members.count < 3)
{
// use the member avatar only it is an active member
for (MXRoomMember *roomMember in members)
{
if ((MXMembershipJoin == roomMember.membership) && ((members.count == 1) || ![roomMember.userId isEqualToString:myUserId]))
{
roomAvatarUrl = roomMember.avatarUrl;
break;
}
}
}
}
UIImage* avatarImage = [AvatarGenerator generateRoomAvatar:self.state.roomId andDisplayName:self.vectorDisplayname];
if (roomAvatarUrl)
{
mxkImageView.enableInMemoryCache = YES;
[mxkImageView setImageURL:[self.mxSession.matrixRestClient urlOfContentThumbnail:roomAvatarUrl toFitViewSize:mxkImageView.frame.size withMethod:MXThumbnailingMethodCrop] withType:nil andImageOrientation:UIImageOrientationUp previewImage:avatarImage];
}
else
{
mxkImageView.image = avatarImage;
}
mxkImageView.contentMode = UIViewContentModeScaleAspectFill;
}
#pragma mark - Room display name
- (NSString *)vectorDisplayname
{
// this algo is the one defined in
// https://github.com/matrix-org/matrix-js-sdk/blob/develop/lib/models/room.js#L617
// calculateRoomName(room, userId)
MXRoomState* roomState = self.state;
if (roomState.name.length > 0)
{
return roomState.name;
}
NSString *alias = roomState.canonicalAlias;
if (!alias)
{
// For rooms where canonical alias is not defined, we use the 1st alias as a workaround
NSArray *aliases = roomState.aliases;
if (aliases.count)
{
alias = [aliases[0] copy];
}
}
// check if there is non empty alias.
if ([alias length] > 0)
{
return alias;
}
NSString* myUserId = self.mxSession.myUser.userId;
NSArray* members = roomState.members;
NSMutableArray* othersActiveMembers = [[NSMutableArray alloc] init];
NSMutableArray* activeMembers = [[NSMutableArray alloc] init];
for(MXRoomMember* member in members)
{
if (member.membership != MXMembershipLeave)
{
if (![member.userId isEqualToString:myUserId])
{
[othersActiveMembers addObject:member];
}
[activeMembers addObject:member];
}
}
// sort the members by their creation (oldest first)
othersActiveMembers = [[othersActiveMembers sortedArrayUsingComparator:^NSComparisonResult(id obj1, id obj2) {
uint64_t originServerTs1 = 0;
uint64_t originServerTs2 = 0;
MXRoomMember* member1 = (MXRoomMember*)obj1;
MXRoomMember* member2 = (MXRoomMember*)obj2;
if (member1.originalEvent)
{
originServerTs1 = member1.originalEvent.originServerTs;
}
if (member2.originalEvent)
{
originServerTs2 = member2.originalEvent.originServerTs;
}
if (originServerTs1 == originServerTs2)
{
return NSOrderedSame;
}
else
{
return originServerTs1 > originServerTs2 ? NSOrderedDescending : NSOrderedAscending;
}
}] mutableCopy];
NSString* displayName = @"";
if (othersActiveMembers.count == 0)
{
if (activeMembers.count == 1)
{
MXRoomMember* member = [activeMembers objectAtIndex:0];
if (member.membership == MXMembershipInvite)
{
if (member.originalEvent.sender)
{
// extract who invited us to the room
displayName = [NSString stringWithFormat:NSLocalizedStringFromTable(@"room_displayname_invite_from", @"Vector", nil), [roomState memberName:member.originalEvent.sender]];
}
else
{
displayName = NSLocalizedStringFromTable(@"room_displayname_room_invite", @"Vector", nil);
}
}
}
}
else if (othersActiveMembers.count == 1)
{
MXRoomMember* member = [othersActiveMembers objectAtIndex:0];
displayName = [roomState memberName:member.userId];
}
else if (othersActiveMembers.count == 2)
{
MXRoomMember* member1 = [othersActiveMembers objectAtIndex:0];
MXRoomMember* member2 = [othersActiveMembers objectAtIndex:1];
displayName = [NSString stringWithFormat:NSLocalizedStringFromTable(@"room_displayname_two_members", @"Vector", nil), [roomState memberName:member1.userId], [roomState memberName:member2.userId]];
}
else
{
MXRoomMember* member = [othersActiveMembers objectAtIndex:0];
displayName = [NSString stringWithFormat:NSLocalizedStringFromTable(@"room_displayname_more_than_two_members", @"Vector", nil), [roomState memberName:member.userId], othersActiveMembers.count - 1];
}
return displayName;
}
#pragma mark - Room tags
- (void)setRoomTag:(NSString*)tag completion:(void (^)())completion
{
NSString* oldTag = nil;
if (self.accountData.tags && self.accountData.tags.count)
{
oldTag = [self.accountData.tags.allKeys objectAtIndex:0];
}
// support only kMXRoomTagFavourite or kMXRoomTagLowPriority tags by now
if (![tag isEqualToString:kMXRoomTagFavourite] && ![tag isEqualToString:kMXRoomTagLowPriority])
{
tag = nil;
}
NSString* tagOrder = [self.mxSession tagOrderToBeAtIndex:0 from:NSNotFound withTag:tag];
NSLog(@"[MXRoom+Vector] Update the room %@ tag from %@ to %@ with tag order %@", self.state.roomId, oldTag, tag, tagOrder);
[self replaceTag:oldTag
byTag:tag
withOrder:tagOrder
success: ^{
if (completion)
{
completion();
}
} failure:^(NSError *error) {
NSLog(@"[MXRoom+Vector] Failed to update the tag %@ of room (%@) failed: %@", tag, self.state.roomId, error);
// Notify user
[[NSNotificationCenter defaultCenter] postNotificationName:kMXKErrorNotification object:error];
if (completion)
{
completion();
}
}];
}
#pragma mark - Room notification mode
- (BOOL)isMute
{
MXPushRule* rule = [self getRoomPushRule];
if (rule)
{
for (MXPushRuleAction *ruleAction in rule.actions)
{
if (ruleAction.actionType == MXPushRuleActionTypeDontNotify)
{
return rule.enabled;
}
}
}
return NO;
}
- (void)setMute:(BOOL)mute completion:(void (^)())completion
{
// Check the current notification mode
if (self.isMute == mute)
{
if (completion)
{
completion();
}
return;
}
MXPushRule* rule = [self getRoomPushRule];
if (!mute)
{
// let the other notification rules manage the pushes.
[self removePushRule:rule completion:completion];
}
else
{
// User does not want to have push
// Check if no rule is already defined.
if (!rule)
{
// Add a new one
[self addPushRuleToDisableNotification:completion];
}
else
{
// Check whether there is no pending update for this room
if (self.notificationCenterDidUpdateObserver)
{
NSLog(@"[MXRoom+Vector] Request in progress: ignore push rule update");
if (completion)
{
completion();
}
return;
}
// check if the user did not define one
BOOL hasDontNotifyRule = NO;
for (MXPushRuleAction *ruleAction in rule.actions)
{
if (ruleAction.actionType == MXPushRuleActionTypeDontNotify)
{
hasDontNotifyRule = YES;
break;
}
}
// if the user defined one, use it
if (hasDontNotifyRule)
{
[self enablePushRule:rule completion:completion];
}
else
{
// If the user has defined a room rule, the rule is deleted before adding new one.
[self removePushRule:rule completion:^{
// Add new rule to disable notification
[self addPushRuleToDisableNotification:completion];
}];
}
}
}
}
#pragma mark -
- (MXPushRule*)getRoomPushRule
{
NSArray* rules = self.mxSession.notificationCenter.rules.global.room;
// sanity checks
if (rules)
{
for(MXPushRule* rule in rules)
{
// the rule id is the room Id
// it is the server trick to avoid duplicated rule on the same room.
if ([rule.ruleId isEqualToString:self.state.roomId])
{
return rule;
}
}
}
return nil;
}
- (void)addPushRuleToDisableNotification:(void (^)())completion
{
MXNotificationCenter* notificationCenter = self.mxSession.notificationCenter;
// Define notificationCenter observers if a completion block is defined.
if (completion)
{
__weak typeof(self) weakSelf = self;
self.notificationCenterDidUpdateObserver = [[NSNotificationCenter defaultCenter] addObserverForName:kMXNotificationCenterDidUpdateRules object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) {
// Check whether the rule has been added
BOOL isAdded = ([self getRoomPushRule] != nil);
if (isAdded)
{
__strong __typeof(weakSelf)strongSelf = weakSelf;
if (strongSelf.notificationCenterDidUpdateObserver)
{
[[NSNotificationCenter defaultCenter] removeObserver:strongSelf.notificationCenterDidUpdateObserver];
strongSelf.notificationCenterDidUpdateObserver = nil;
}
if (strongSelf.notificationCenterDidFailObserver)
{
[[NSNotificationCenter defaultCenter] removeObserver:strongSelf.notificationCenterDidFailObserver];
strongSelf.notificationCenterDidFailObserver = nil;
}
completion();
}
}];
self.notificationCenterDidFailObserver = [[NSNotificationCenter defaultCenter] addObserverForName:kMXNotificationCenterDidFailRulesUpdate object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) {
__strong __typeof(weakSelf)strongSelf = weakSelf;
if (strongSelf.notificationCenterDidUpdateObserver)
{
[[NSNotificationCenter defaultCenter] removeObserver:strongSelf.notificationCenterDidUpdateObserver];
strongSelf.notificationCenterDidUpdateObserver = nil;
}
if (strongSelf.notificationCenterDidFailObserver)
{
[[NSNotificationCenter defaultCenter] removeObserver:strongSelf.notificationCenterDidFailObserver];
strongSelf.notificationCenterDidFailObserver = nil;
}
completion();
}];
}
[notificationCenter addRoomRule:self.state.roomId
notify:NO
sound:NO
highlight:NO];
}
- (void)removePushRule:(MXPushRule *)rule completion:(void (^)())completion
{
MXNotificationCenter* notificationCenter = self.mxSession.notificationCenter;
// Define notificationCenter observers if a completion block is defined.
if (completion)
{
__weak typeof(self) weakSelf = self;
self.notificationCenterDidUpdateObserver = [[NSNotificationCenter defaultCenter] addObserverForName:kMXNotificationCenterDidUpdateRules object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) {
// Check whether the rule has been removed
BOOL isRemoved = ([notificationCenter ruleById:rule.ruleId] == nil);
if (isRemoved)
{
__strong __typeof(weakSelf)strongSelf = weakSelf;
if (strongSelf.notificationCenterDidUpdateObserver)
{
[[NSNotificationCenter defaultCenter] removeObserver:strongSelf.notificationCenterDidUpdateObserver];
strongSelf.notificationCenterDidUpdateObserver = nil;
}
if (strongSelf.notificationCenterDidFailObserver)
{
[[NSNotificationCenter defaultCenter] removeObserver:strongSelf.notificationCenterDidFailObserver];
strongSelf.notificationCenterDidFailObserver = nil;
}
completion();
}
}];
self.notificationCenterDidFailObserver = [[NSNotificationCenter defaultCenter] addObserverForName:kMXNotificationCenterDidFailRulesUpdate object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) {
__strong __typeof(weakSelf)strongSelf = weakSelf;
if (strongSelf.notificationCenterDidUpdateObserver)
{
[[NSNotificationCenter defaultCenter] removeObserver:strongSelf.notificationCenterDidUpdateObserver];
strongSelf.notificationCenterDidUpdateObserver = nil;
}
if (strongSelf.notificationCenterDidFailObserver)
{
[[NSNotificationCenter defaultCenter] removeObserver:strongSelf.notificationCenterDidFailObserver];
strongSelf.notificationCenterDidFailObserver = nil;
}
completion();
}];
}
[notificationCenter removeRule:rule];
}
- (void)enablePushRule:(MXPushRule *)rule completion:(void (^)())completion
{
MXNotificationCenter* notificationCenter = self.mxSession.notificationCenter;
// Define notificationCenter observers if a completion block is defined.
if (completion)
{
__weak typeof(self) weakSelf = self;
self.notificationCenterDidUpdateObserver = [[NSNotificationCenter defaultCenter] addObserverForName:kMXNotificationCenterDidUpdateRules object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) {
// No way to check whether this notification concerns the push rule. Consider the change is applied.
__strong __typeof(weakSelf)strongSelf = weakSelf;
if (strongSelf.notificationCenterDidUpdateObserver)
{
[[NSNotificationCenter defaultCenter] removeObserver:strongSelf.notificationCenterDidUpdateObserver];
strongSelf.notificationCenterDidUpdateObserver = nil;
}
if (strongSelf.notificationCenterDidFailObserver)
{
[[NSNotificationCenter defaultCenter] removeObserver:strongSelf.notificationCenterDidFailObserver];
strongSelf.notificationCenterDidFailObserver = nil;
}
completion();
}];
self.notificationCenterDidFailObserver = [[NSNotificationCenter defaultCenter] addObserverForName:kMXNotificationCenterDidFailRulesUpdate object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) {
__strong __typeof(weakSelf)strongSelf = weakSelf;
if (strongSelf.notificationCenterDidUpdateObserver)
{
[[NSNotificationCenter defaultCenter] removeObserver:strongSelf.notificationCenterDidUpdateObserver];
strongSelf.notificationCenterDidUpdateObserver = nil;
}
if (strongSelf.notificationCenterDidFailObserver)
{
[[NSNotificationCenter defaultCenter] removeObserver:strongSelf.notificationCenterDidFailObserver];
strongSelf.notificationCenterDidFailObserver = nil;
}
completion();
}];
}
[notificationCenter enableRule:rule isEnabled:YES];
}
- (void)setNotificationCenterDidFailObserver:(id)anObserver
{
objc_setAssociatedObject(self, @selector(notificationCenterDidFailObserver), anObserver, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (id)notificationCenterDidFailObserver
{
return objc_getAssociatedObject(self, @selector(notificationCenterDidFailObserver));
}
- (void)setNotificationCenterDidUpdateObserver:(id)anObserver
{
objc_setAssociatedObject(self, @selector(notificationCenterDidUpdateObserver), anObserver, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (id)notificationCenterDidUpdateObserver
{
return objc_getAssociatedObject(self, @selector(notificationCenterDidUpdateObserver));
}
@end