// _____ _ // |_ _| |_ _ _ ___ ___ _ __ __ _ // | | | ' \| '_/ -_) -_) ' \/ _` |_ // |_| |_||_|_| \___\___|_|_|_\__,_(_) // // Threema iOS Client // Copyright (c) 2012-2020 Threema GmbH // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License, version 3, // as published by the Free Software Foundation. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. // // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . #import #import "ConversationCell.h" #import "Conversation.h" #import "Contact.h" #import "SystemMessage.h" #import "Utils.h" #import "AvatarMaker.h" #import "UIImage+ColoredImage.h" #import "BaseMessage+Accessibility.h" #import "BundleUtil.h" #import "UILabel+Markup.h" #import "MessageDraftStore.h" #import "ConversationUtils.h" #import "ChatCallMessageCell.h" #import "UserSettings.h" #import "LicenseStore.h" #import "TextStyleUtils.h" #import "PushSetting.h" #import "GroupProxy.h" @interface ConversationCell () @property BOOL showStatusIcon; @property PushSetting *pushSetting; @end @implementation ConversationCell @synthesize conversation; - (void)dealloc { [self removeObservers]; [[NSNotificationCenter defaultCenter] removeObserver: self]; } - (void)awakeFromNib { [super awakeFromNib]; /* very minor adjustment to make base lines of name, date and draft align on Retina displays */ if ([UIScreen mainScreen].scale == 2.0) { self.dateLabel.frame = CGRectOffset(self.dateLabel.frame, 0, -0.5); self.draftLabel.frame = CGRectOffset(self.draftLabel.frame, 0, -0.5); } self.draftLabel.text = self.draftLabel.text.uppercaseString; _threemaTypeIcon.image = [Utils threemaTypeIcon]; self.messagePreviewLabel.userInteractionEnabled = NO; self.statusIcon.contentMode = UIViewContentModeScaleAspectFit; [self setupColors]; } - (void)setupColors { [Colors updateTableViewCellBackground:self]; if (conversation.groupId == nil && (conversation.contact.state.intValue == kStateInactive || conversation.contact.state.intValue == kStateInvalid)) { self.nameLabel.textColor = [Colors fontLight]; self.nameLabel.highlightedTextColor = [Colors fontLight]; } else { self.nameLabel.textColor = [Colors fontNormal]; self.nameLabel.highlightedTextColor = [Colors fontNormal]; } [self.messagePreviewLabel setTextColor:[Colors fontLight]]; self.messagePreviewLabel.highlightedTextColor = [Colors fontLight]; [self.dateLabel setTextColor:[Colors fontLight]]; self.dateLabel.highlightedTextColor = [Colors fontLight]; self.markedView.backgroundColor = [Colors markTag]; if (@available(iOS 11.0, *)) { _contactImage.accessibilityIgnoresInvertColors = true; _threemaTypeIcon.image = [Utils threemaTypeIcon]; } _typingIndicator.image = [UIImage imageNamed:@"Typing" inColor:[Colors fontLight]]; } - (void)removeObservers { [conversation removeObserver:self forKeyPath:@"typing"]; [conversation removeObserver:self forKeyPath:@"lastMessage"]; [conversation removeObserver:self forKeyPath:@"unreadMessageCount"]; [conversation removeObserver:self forKeyPath:@"lastMessage.reverseGeocodingResult"]; [conversation removeObserver:self forKeyPath:@"lastMessage.userack"]; [conversation removeObserver:self forKeyPath:@"lastMessage.read"]; [conversation removeObserver:self forKeyPath:@"lastMessage.delivered"]; [conversation removeObserver:self forKeyPath:@"lastMessage.sendfailed"]; [conversation removeObserver:self forKeyPath:@"lastMessage.sent"]; [conversation removeObserver:self forKeyPath:@"groupName"]; [conversation removeObserver:self forKeyPath:@"members"]; [conversation removeObserver:self forKeyPath:@"contact.displayName"]; [conversation removeObserver:self forKeyPath:@"contact.imageData"]; [conversation removeObserver:self forKeyPath:@"groupImage"]; [conversation removeObserver:self forKeyPath:@"contact.contactImage"]; [conversation removeObserver:self forKeyPath:@"tags"]; [[NSNotificationCenter defaultCenter] removeObserver: self]; } - (void)setConversation:(Conversation *)newConversation { if (conversation == newConversation) { [self setupColors]; [self updateDateLabel]; [self updateContactImage]; [self updateLastMessagePreview]; [self updateThreemaTypeIcon]; [self updateName]; [self updateTagsView]; [self updateNotificationIcon]; return; } [self removeObservers]; conversation = newConversation; /* observe this conversation as the last message text could change */ [conversation addObserver:self forKeyPath:@"typing" options:0 context:nil]; [conversation addObserver:self forKeyPath:@"lastMessage" options:0 context:nil]; [conversation addObserver:self forKeyPath:@"unreadMessageCount" options:0 context:nil]; [conversation addObserver:self forKeyPath:@"lastMessage.reverseGeocodingResult" options:0 context:nil]; [conversation addObserver:self forKeyPath:@"lastMessage.userack" options:0 context:nil]; [conversation addObserver:self forKeyPath:@"lastMessage.read" options:0 context:nil]; [conversation addObserver:self forKeyPath:@"lastMessage.delivered" options:0 context:nil]; [conversation addObserver:self forKeyPath:@"lastMessage.sendfailed" options:0 context:nil]; [conversation addObserver:self forKeyPath:@"lastMessage.sent" options:0 context:nil]; [conversation addObserver:self forKeyPath:@"groupName" options:0 context:nil]; [conversation addObserver:self forKeyPath:@"members" options:0 context:nil]; [conversation addObserver:self forKeyPath:@"contact.displayName" options:0 context:nil]; [conversation addObserver:self forKeyPath:@"contact.imageData" options:0 context:nil]; [conversation addObserver:self forKeyPath:@"groupImage" options:0 context:nil]; [conversation addObserver:self forKeyPath:@"contact.contactImage" options:0 context:nil]; [conversation addObserver:self forKeyPath:@"tags" options:0 context:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(avatarChanged:) name:kNotificationIdentityAvatarChanged object:nil]; [self updateName]; [self updateLastMessagePreview]; [self updateDateLabel]; [self updateBadgeView]; [self updateContactImage]; [self updateTypingIndicator]; [self setupColors]; [self updateThreemaTypeIcon]; [self updateTagsView]; [self updateNotificationIcon]; } - (void)updateLastMessagePreview { BaseMessage *lastMessage = conversation.lastMessage; NSString *messageTextForPreview = [lastMessage previewText]; if (messageTextForPreview == nil) { messageTextForPreview = @""; } if (conversation.groupId != nil) { self.statusIcon.image = [UIImage imageNamed:@"MessageStatus_group" inColor:[Colors fontLight]]; self.statusIcon.highlightedImage = self.statusIcon.image; self.statusIcon.alpha = 1.0; self.statusIcon.hidden = NO; } else { NSString *iconName; UIColor *color = [Colors fontLight]; if ([self isSystemCallMessage:lastMessage]) { iconName = @"call"; } else { if (lastMessage.isOwn.boolValue && conversation.contact.isGatewayId == NO) { if (lastMessage.userackDate) { if (lastMessage.userack.boolValue) { iconName = @"thumb_up"; color = [Colors green]; } else if (lastMessage.userack.boolValue == NO) { iconName = @"thumb_down"; color = [Colors orange]; } } else if (lastMessage.read.boolValue) { iconName = @"read"; } else if (lastMessage.delivered.boolValue) { iconName = @"delivered"; } else if (lastMessage.sendFailed.boolValue) { iconName = @"sendfailed"; color = [Colors red]; } else { if (lastMessage.sent.boolValue) { iconName = @"sent"; } else { iconName = @"sending"; } } } else if (lastMessage == nil) { iconName = nil; } else if (!lastMessage.isOwn.boolValue && conversation.contact.isGatewayId == NO) { iconName = @"reply"; if (lastMessage.userackDate) { if (lastMessage.userack.boolValue) { color = [Colors green]; } else if (lastMessage.userack.boolValue == NO) { color = [Colors orange]; } } } } if ((iconName && [self isSystemCallMessage:lastMessage]) || (iconName && ![lastMessage isKindOfClass:[SystemMessage class]])) { if ([iconName isEqualToString:@"sendfailed"]) { self.statusIcon.image = [UIImage imageNamed:[NSString stringWithFormat:@"MessageStatus_%@", iconName]]; } else if ([iconName isEqualToString:@"call"]) { self.statusIcon.image = [UIImage imageNamed:@"ThreemaPhone" inColor:color]; } else { self.statusIcon.image = [UIImage imageNamed:[NSString stringWithFormat:@"MessageStatus_%@", iconName] inColor:color]; } self.statusIcon.highlightedImage = self.statusIcon.image; self.statusIcon.alpha = 1.0; self.statusIcon.hidden = NO; } else { self.statusIcon.hidden = YES; } } NSString *draftMessage = [MessageDraftStore loadDraftForConversation:self.conversation]; NSAttributedString *attributedString; if (draftMessage) { self.dateLabel.hidden = YES; self.draftLabel.hidden = NO; NSAttributedString *draftAttributed = [TextStyleUtils makeAttributedStringFromString:draftMessage withFont:self.messagePreviewLabel.font textColor:[Colors fontLight] isOwn:true application:[UIApplication sharedApplication]]; NSMutableAttributedString *formattedAttributeString = [[NSMutableAttributedString alloc] initWithAttributedString:[self.messagePreviewLabel applyMarkupFor:draftAttributed]]; attributedString = [TextStyleUtils makeMentionsAttributedStringForAttributedString:formattedAttributeString textFont:self.messagePreviewLabel.font atColor:[[Colors fontLight] colorWithAlphaComponent:0.6] messageInfo:TextStyleUtilsMessageInfoOverview application:[UIApplication sharedApplication]]; self.messagePreviewLabel.attributedText = attributedString; if (conversation.groupId == nil) { self.statusIcon.hidden = YES; } } else { self.dateLabel.hidden = NO; self.draftLabel.hidden = YES; NSString *editedString; if ([self isSystemCallMessage:lastMessage]) { NSString *spaces = @""; for (int i = 0; i < (self.messagePreviewLabel.font.pointSize / 2.2); i++) { spaces = [NSString stringWithFormat:@"%@ ", spaces]; } editedString = [NSString stringWithFormat:@"%@%@",spaces, messageTextForPreview]; } else { editedString = messageTextForPreview; } NSMutableAttributedString *formattedAttributeString; NSString *contactString; NSMutableAttributedString *messageAttributeString; if (conversation.groupId != nil) { if (lastMessage != nil && ![lastMessage isKindOfClass:[SystemMessage class]]) { if (lastMessage.sender == nil) { contactString = [[NSString alloc] initWithFormat:@"%@: ", [BundleUtil localizedStringForKey:@"me"]]; } else { contactString = [[NSString alloc] initWithFormat:@"%@: ", lastMessage.sender.displayName]; } } NSAttributedString *attributed = [TextStyleUtils makeAttributedStringFromString:editedString withFont:self.messagePreviewLabel.font textColor:[Colors fontLight] isOwn:true application:[UIApplication sharedApplication]]; messageAttributeString = [[NSMutableAttributedString alloc] initWithAttributedString:[self.messagePreviewLabel applyMarkupFor:attributed]]; NSAttributedString *attributedContact = [TextStyleUtils makeAttributedStringFromString:contactString withFont:self.messagePreviewLabel.font textColor:[Colors fontLight] isOwn:true application:[UIApplication sharedApplication]]; formattedAttributeString = [[NSMutableAttributedString alloc] initWithAttributedString:attributedContact]; [formattedAttributeString appendAttributedString:messageAttributeString]; } else { NSAttributedString *attributed = [TextStyleUtils makeAttributedStringFromString:editedString withFont:self.messagePreviewLabel.font textColor:[Colors fontLight] isOwn:true application:[UIApplication sharedApplication]]; formattedAttributeString = [[NSMutableAttributedString alloc] initWithAttributedString:[self.messagePreviewLabel applyMarkupFor:attributed]]; } self.messagePreviewLabel.attributedText = [TextStyleUtils makeMentionsAttributedStringForAttributedString:formattedAttributeString textFont:self.messagePreviewLabel.font atColor:[[Colors fontLight] colorWithAlphaComponent:0.6] messageInfo:TextStyleUtilsMessageInfoOverview application:[UIApplication sharedApplication]]; } if ([self isSystemCallMessage:lastMessage] && !draftMessage) { switch ([((SystemMessage *)lastMessage).type integerValue]) { case kSystemMessageCallEnded: if (((SystemMessage *)lastMessage).haveCallTime) { if (!((SystemMessage *)lastMessage).isOwn.boolValue) { _callImageView.image = [UIImage imageNamed:@"CallDownGreen" inColor:[Colors green]]; } else { _callImageView.image = [UIImage imageNamed:@"CallUpGreen" inColor:[Colors green]]; } } else { if (!((SystemMessage *)lastMessage).isOwn.boolValue) { _callImageView.image = [UIImage imageNamed:@"CallLeftRed"]; } else { _callImageView.image = [UIImage imageNamed:@"CallUpRed"]; } } break; case kSystemMessageCallRejected: if (!((SystemMessage *)lastMessage).isOwn.boolValue) { _callImageView.image = [UIImage imageNamed:@"CallLeftOrange"]; } else { _callImageView.image = [UIImage imageNamed:@"CallRightRed"]; } break; case kSystemMessageCallRejectedBusy: if (!((SystemMessage *)lastMessage).isOwn.boolValue) { _callImageView.image = [UIImage imageNamed:@"CallLeftRed"]; } else { _callImageView.image = [UIImage imageNamed:@"CallRightRed"]; } break; case kSystemMessageCallRejectedTimeout: if (!((SystemMessage *)lastMessage).isOwn.boolValue) { _callImageView.image = [UIImage imageNamed:@"CallLeftRed"]; } else { _callImageView.image = [UIImage imageNamed:@"CallRightRed"]; } break; case kSystemMessageCallRejectedDisabled: _callImageView.image = [UIImage imageNamed:@"CallRightRed"]; break; case kSystemMessageCallMissed: _callImageView.image = [UIImage imageNamed:@"CallLeftRed"]; break; default: _callImageView.image = [UIImage imageNamed:@"CallUpGreen" inColor:[Colors green]]; break; } _callImageHeight.constant = self.messagePreviewLabel.font.pointSize * 1.2; _callImageView.hidden = NO; } else { _callImageView.hidden = YES; } _showStatusIcon = self.statusIcon.hidden == NO; // make sure typing indicator is in sync [self updateTypingIndicator]; } - (void)updateDateLabel { BaseMessage *lastMessage = conversation.lastMessage; if (lastMessage.userackDate && lastMessage.isOwn.boolValue) { self.dateLabel.text = [Utils formatShortLastMessageDate:conversation.lastMessage.userackDate]; } else if (lastMessage.read.boolValue && lastMessage.isOwn.boolValue) { self.dateLabel.text = [Utils formatShortLastMessageDate:conversation.lastMessage.readDate]; } else if (lastMessage.delivered.boolValue && lastMessage.isOwn.boolValue) { self.dateLabel.text = [Utils formatShortLastMessageDate:conversation.lastMessage.deliveryDate]; } else { self.dateLabel.text = [Utils formatShortLastMessageDate:conversation.lastMessage.remoteSentDate]; } } - (void)updateContactImage { dispatch_async(dispatch_get_main_queue(), ^{ self.contactImage.image = [BundleUtil imageNamed:@"Unknown"]; }); [[AvatarMaker sharedAvatarMaker] avatarForConversation:conversation size:56.0f masked:YES onCompletion:^(UIImage *avatarImage) { dispatch_async(dispatch_get_main_queue(), ^{ self.contactImage.image = avatarImage; }); }]; } - (void)updateBadgeView { if (conversation.unreadMessageCount.intValue > 0) { self.badgeView.alignment = NSTextAlignmentCenter; self.badgeView.font = [UIFont systemFontOfSize:13]; self.badgeView.shadowOffset = CGSizeMake(1,1); self.badgeView.value = conversation.unreadMessageCount.intValue; self.badgeView.alpha = 1.0; self.badgeView.hidden = NO; } else { self.badgeView.alpha = 0.0; self.badgeView.hidden = YES; } } - (void)updateTypingIndicator { if (conversation.typing.boolValue) { self.typingIndicator.alpha = 0.0; self.typingIndicator.hidden = NO; self.typingIndicator.alpha = 1.0; self.statusIcon.alpha = 0.0; self.statusIcon.hidden = YES; } else { if (_showStatusIcon) { self.statusIcon.hidden = NO; } self.typingIndicator.alpha = 0.0; self.statusIcon.alpha = 1.0; self.typingIndicator.hidden = YES; } } - (void)updateName { if (conversation.displayName) { if (conversation.groupId == nil && conversation.contact.state.intValue == kStateInvalid) { NSDictionary *strikethroughDict = @{NSStrikethroughStyleAttributeName: @(NSUnderlineStyleThick)}; NSAttributedString *str = [[NSAttributedString alloc] initWithString:conversation.displayName attributes:strikethroughDict]; if ([NSThread isMainThread]) { self.nameLabel.attributedText = str; } else { dispatch_async(dispatch_get_main_queue(), ^{ self.nameLabel.attributedText = str; }); } } else { NSMutableAttributedString *str = [[NSMutableAttributedString alloc] init]; if ([[UserSettings sharedUserSettings].blacklist containsObject:conversation.contact.identity]) { [str appendAttributedString:[[NSAttributedString alloc] initWithString:@"🚫 " attributes:nil]]; } [str appendAttributedString:[[NSAttributedString alloc] initWithString:conversation.displayName attributes:nil]]; if ([NSThread isMainThread]) { self.nameLabel.attributedText = str; } else { dispatch_async(dispatch_get_main_queue(), ^{ self.nameLabel.attributedText = str; }); } } } UIFontDescriptor *fontDescriptor = [UIFontDescriptor preferredFontDescriptorWithTextStyle:UIFontTextStyleHeadline]; CGFloat size = fontDescriptor.pointSize; dispatch_async(dispatch_get_main_queue(), ^{ self.nameLabel.font = [UIFont boldSystemFontOfSize:size]; }); } - (void)updateThreemaTypeIcon { if (self.conversation.isGroup) { _threemaTypeIcon.hidden = YES; } else { _threemaTypeIcon.hidden = [Utils hideThreemaTypeIconForContact:self.conversation.contact]; } } - (void)updateTagsView { self.markedView.alpha = [conversation.marked isEqualToNumber:[NSNumber numberWithBool:YES]] ? 1.0 : 0.0; } - (void)updateNotificationIcon { _pushSetting = [PushSetting findPushSettingForConversation:conversation]; UIImage *pushSettingIcon = nil; if (_pushSetting) { pushSettingIcon = [_pushSetting imageForEditedPushSetting]; } if (pushSettingIcon != nil) { _notificationIcon.image = [pushSettingIcon imageWithTint:[Colors fontLight]]; _notificationIcon.hidden = false; } else { _notificationIcon.image = nil; _notificationIcon.hidden = true; } } - (NSString *)accessibilityLabel { NSMutableString *text = [NSMutableString stringWithFormat:@"%@. ", self.nameLabel.text]; if (_pushSetting) { if (_pushSetting.type == kPushSettingTypeOn && _pushSetting.silent) { [text appendFormat:@"%@ %@. ", NSLocalizedString(@"notification_sound_header", nil), NSLocalizedString(@"doNotDisturb_off", nil)]; } else if (_pushSetting.type == kPushSettingTypeOff && !_pushSetting.mentions) { [text appendFormat:@"%@ %@. ", NSLocalizedString(@"doNotDisturb_title", nil), NSLocalizedString(@"doNotDisturb_on", nil)]; } else if (_pushSetting.type == kPushSettingTypeOff && _pushSetting.mentions) { [text appendFormat:@"%@ %@, %@. ", NSLocalizedString(@"doNotDisturb_title", nil), NSLocalizedString(@"doNotDisturb_on", nil), NSLocalizedString(@"doNotDisturb_mention", @"")]; } else if (_pushSetting.type == kPushSettingTypeOffPeriod && !_pushSetting.mentions) { [text appendFormat:@"%@ %@ %@. ", NSLocalizedString(@"doNotDisturb_title", nil), NSLocalizedString(@"doNotDisturb_onPeriod_time", nil), [DateFormatter getFullDateFor:_pushSetting.periodOffTillDate]]; } else if (_pushSetting.type == kPushSettingTypeOffPeriod && _pushSetting.mentions) { [text appendFormat:@"%@ %@ %@, %@. ", NSLocalizedString(@"doNotDisturb_title", nil), NSLocalizedString(@"doNotDisturb_onPeriod_time", nil), [DateFormatter getFullDateFor:_pushSetting.periodOffTillDate], NSLocalizedString(@"doNotDisturb_mention", @"")]; } } NSString *messagePreview = [conversation.lastMessage previewText]; NSString *draftPreview = [MessageDraftStore loadDraftForConversation:self.conversation]; if (draftPreview.length > 0) { [text appendFormat:@"%@. ", [BundleUtil localizedStringForKey:@"draft"]]; if (conversation.unreadMessageCount.intValue > 0) { [text appendFormat:@"%@. ", NSLocalizedString(@"unread", nil)]; } [text appendFormat:@"%@ ", [BundleUtil localizedStringForKey:@"from"]]; [text appendFormat:@"%@. ", [BundleUtil localizedStringForKey:@"me"]]; [text appendFormat:@"%@. ", draftPreview]; [text appendFormat:@"%@. ", [conversation.lastMessage accessibilityMessageShortStatus]]; return text; } [text appendFormat:@"%@. ", [conversation.lastMessage accessibilityMessageDate]]; if (messagePreview.length > 0) { if (conversation.unreadMessageCount.intValue > 0) { [text appendFormat:@"%@. ", NSLocalizedString(@"unread", nil)]; } [text appendFormat:@"%@ ", [BundleUtil localizedStringForKey:@"from"]]; [text appendFormat:@"%@. ", [conversation.lastMessage accessibilityMessageSender]]; [text appendFormat:@"%@. ", messagePreview]; [text appendFormat:@"%@. ", [conversation.lastMessage accessibilityMessageShortStatus]]; } if ([conversation.marked isEqualToNumber:[NSNumber numberWithBool:YES]]) { [text appendFormat:@"%@. ", NSLocalizedString(@"pinned_conversation", nil)]; } return text; } - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { dispatch_async(dispatch_get_main_queue(), ^{ if (object == conversation) { if ([keyPath isEqualToString:@"lastMessage"]) { [self updateLastMessagePreview]; [self updateDateLabel]; } else if ([keyPath isEqualToString:@"contact.displayName"] || [keyPath isEqualToString:@"groupName"] || [keyPath isEqualToString:@"members"]) { [self updateName]; } else if ([keyPath isEqualToString:@"contact.imageData"] || [keyPath isEqualToString:@"groupImage"] || [keyPath isEqualToString:@"contact.contactImage"]) { [self updateContactImage]; } else if ([keyPath isEqualToString:@"typing"]) { [self updateTypingIndicator]; } else if ([keyPath isEqualToString:@"unreadMessageCount"]) { [self updateBadgeView]; } else if ([keyPath hasPrefix:@"lastMessage."]) { [self updateLastMessagePreview]; [self updateDateLabel]; } else if ([keyPath hasPrefix:@"tags"]) { [self updateTagsView]; } } }); } - (BOOL)isSystemCallMessage:(BaseMessage *)message { if ([message isKindOfClass:[SystemMessage class]]) { switch ([((SystemMessage *)message).type integerValue]) { case kSystemMessageCallEnded: return YES; case kSystemMessageCallRejected: return YES; case kSystemMessageCallRejectedBusy: return YES; case kSystemMessageCallRejectedTimeout: return YES; case kSystemMessageCallRejectedDisabled: return YES; case kSystemMessageCallMissed: return YES; case kSystemMessageCallRejectedUnknown: return YES; default: return NO; } } return NO; } - (void)setHighlighted:(BOOL)highlighted animated:(BOOL)animated { [super setHighlighted:highlighted animated:animated]; [_messagePreviewLabel setHighlighted:NO]; } - (void)voiceOverDeleteConversation { if ([_conversationCellDelegate respondsToSelector:@selector(voiceOverDeleteConversation:)]) { [_conversationCellDelegate voiceOverDeleteConversation:self]; } } - (void)voiceOverLeaveGroup { if ([_conversationCellDelegate respondsToSelector:@selector(voiceOverLeaveGroup:)]) { [_conversationCellDelegate voiceOverLeaveGroup:self]; } } - (void)voiceOverMarkConversation:(id)sender { [ConversationUtils markConversation:conversation]; } - (void)voiceOverUnmarkConversation:(id)sender { [ConversationUtils unmarkConversation:conversation]; } - (void)voiceOverReadMessage:(id)sender { [ConversationUtils unreadConversation:conversation]; } #pragma mark - Accessibility - (NSArray *)accessibilityCustomActions { UIAccessibilityCustomAction *readAction; UIAccessibilityCustomAction *markAction; NSMutableArray *actionArray = [NSMutableArray new]; if (conversation.unreadMessageCount.intValue > 0) { readAction = [[UIAccessibilityCustomAction alloc] initWithName:NSLocalizedString(@"read", @"") target:self selector:@selector(voiceOverReadMessage:)]; [actionArray addObject:readAction]; } if (conversation.marked.boolValue) { markAction = [[UIAccessibilityCustomAction alloc] initWithName:NSLocalizedString(@"unpin", @"") target:self selector:@selector(voiceOverUnmarkConversation:)]; } else { markAction = [[UIAccessibilityCustomAction alloc] initWithName:NSLocalizedString(@"pin", @"") target:self selector:@selector(voiceOverMarkConversation:)]; } [actionArray addObject:markAction]; if (conversation.isGroup) { GroupProxy *group = [GroupProxy groupProxyForConversation:conversation]; if ([group isSelfMember]) { UIAccessibilityCustomAction *leaveAction = [[UIAccessibilityCustomAction alloc] initWithName:[BundleUtil localizedStringForKey:@"leave_group"] target:self selector:@selector(voiceOverLeaveGroup)]; [actionArray addObject:leaveAction]; } } UIAccessibilityCustomAction *deleteAction = [[UIAccessibilityCustomAction alloc] initWithName:NSLocalizedString(@"delete", @"") target:self selector:@selector(voiceOverDeleteConversation)]; [actionArray addObject:deleteAction]; return actionArray; } #pragma mark - Notification - (void)avatarChanged:(NSNotification*)notification { if (notification.object && [conversation.contact.identity isEqualToString:notification.object] ) { [self updateContactImage]; } } @end