ConversationCell.m 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666
  1. // _____ _
  2. // |_ _| |_ _ _ ___ ___ _ __ __ _
  3. // | | | ' \| '_/ -_) -_) ' \/ _` |_
  4. // |_| |_||_|_| \___\___|_|_|_\__,_(_)
  5. //
  6. // Threema iOS Client
  7. // Copyright (c) 2012-2020 Threema GmbH
  8. //
  9. // This program is free software: you can redistribute it and/or modify
  10. // it under the terms of the GNU Affero General Public License, version 3,
  11. // as published by the Free Software Foundation.
  12. //
  13. // This program is distributed in the hope that it will be useful,
  14. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  15. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  16. // GNU Affero General Public License for more details.
  17. //
  18. // You should have received a copy of the GNU Affero General Public License
  19. // along with this program. If not, see <https://www.gnu.org/licenses/>.
  20. #import <QuartzCore/QuartzCore.h>
  21. #import "ConversationCell.h"
  22. #import "Conversation.h"
  23. #import "Contact.h"
  24. #import "SystemMessage.h"
  25. #import "Utils.h"
  26. #import "AvatarMaker.h"
  27. #import "UIImage+ColoredImage.h"
  28. #import "BaseMessage+Accessibility.h"
  29. #import "BundleUtil.h"
  30. #import "UILabel+Markup.h"
  31. #import "MessageDraftStore.h"
  32. #import "ConversationUtils.h"
  33. #import "ChatCallMessageCell.h"
  34. #import "UserSettings.h"
  35. #import "LicenseStore.h"
  36. #import "TextStyleUtils.h"
  37. #import "PushSetting.h"
  38. #import "GroupProxy.h"
  39. @interface ConversationCell ()
  40. @property BOOL showStatusIcon;
  41. @property PushSetting *pushSetting;
  42. @end
  43. @implementation ConversationCell
  44. @synthesize conversation;
  45. - (void)dealloc {
  46. [self removeObservers];
  47. [[NSNotificationCenter defaultCenter] removeObserver: self];
  48. }
  49. - (void)awakeFromNib {
  50. [super awakeFromNib];
  51. /* very minor adjustment to make base lines of name, date and draft align on Retina displays */
  52. if ([UIScreen mainScreen].scale == 2.0) {
  53. self.dateLabel.frame = CGRectOffset(self.dateLabel.frame, 0, -0.5);
  54. self.draftLabel.frame = CGRectOffset(self.draftLabel.frame, 0, -0.5);
  55. }
  56. self.draftLabel.text = self.draftLabel.text.uppercaseString;
  57. _threemaTypeIcon.image = [Utils threemaTypeIcon];
  58. self.messagePreviewLabel.userInteractionEnabled = NO;
  59. self.statusIcon.contentMode = UIViewContentModeScaleAspectFit;
  60. [self setupColors];
  61. }
  62. - (void)setupColors {
  63. [Colors updateTableViewCellBackground:self];
  64. if (conversation.groupId == nil && (conversation.contact.state.intValue == kStateInactive || conversation.contact.state.intValue == kStateInvalid)) {
  65. self.nameLabel.textColor = [Colors fontLight];
  66. self.nameLabel.highlightedTextColor = [Colors fontLight];
  67. } else {
  68. self.nameLabel.textColor = [Colors fontNormal];
  69. self.nameLabel.highlightedTextColor = [Colors fontNormal];
  70. }
  71. [self.messagePreviewLabel setTextColor:[Colors fontLight]];
  72. self.messagePreviewLabel.highlightedTextColor = [Colors fontLight];
  73. [self.dateLabel setTextColor:[Colors fontLight]];
  74. self.dateLabel.highlightedTextColor = [Colors fontLight];
  75. self.markedView.backgroundColor = [Colors markTag];
  76. if (@available(iOS 11.0, *)) {
  77. _contactImage.accessibilityIgnoresInvertColors = true;
  78. _threemaTypeIcon.image = [Utils threemaTypeIcon];
  79. }
  80. _typingIndicator.image = [UIImage imageNamed:@"Typing" inColor:[Colors fontLight]];
  81. }
  82. - (void)removeObservers {
  83. [conversation removeObserver:self forKeyPath:@"typing"];
  84. [conversation removeObserver:self forKeyPath:@"lastMessage"];
  85. [conversation removeObserver:self forKeyPath:@"unreadMessageCount"];
  86. [conversation removeObserver:self forKeyPath:@"lastMessage.reverseGeocodingResult"];
  87. [conversation removeObserver:self forKeyPath:@"lastMessage.userack"];
  88. [conversation removeObserver:self forKeyPath:@"lastMessage.read"];
  89. [conversation removeObserver:self forKeyPath:@"lastMessage.delivered"];
  90. [conversation removeObserver:self forKeyPath:@"lastMessage.sendfailed"];
  91. [conversation removeObserver:self forKeyPath:@"lastMessage.sent"];
  92. [conversation removeObserver:self forKeyPath:@"groupName"];
  93. [conversation removeObserver:self forKeyPath:@"members"];
  94. [conversation removeObserver:self forKeyPath:@"contact.displayName"];
  95. [conversation removeObserver:self forKeyPath:@"contact.imageData"];
  96. [conversation removeObserver:self forKeyPath:@"groupImage"];
  97. [conversation removeObserver:self forKeyPath:@"contact.contactImage"];
  98. [conversation removeObserver:self forKeyPath:@"tags"];
  99. [[NSNotificationCenter defaultCenter] removeObserver: self];
  100. }
  101. - (void)setConversation:(Conversation *)newConversation {
  102. if (conversation == newConversation) {
  103. [self setupColors];
  104. [self updateDateLabel];
  105. [self updateContactImage];
  106. [self updateLastMessagePreview];
  107. [self updateThreemaTypeIcon];
  108. [self updateName];
  109. [self updateTagsView];
  110. [self updateNotificationIcon];
  111. return;
  112. }
  113. [self removeObservers];
  114. conversation = newConversation;
  115. /* observe this conversation as the last message text could change */
  116. [conversation addObserver:self forKeyPath:@"typing" options:0 context:nil];
  117. [conversation addObserver:self forKeyPath:@"lastMessage" options:0 context:nil];
  118. [conversation addObserver:self forKeyPath:@"unreadMessageCount" options:0 context:nil];
  119. [conversation addObserver:self forKeyPath:@"lastMessage.reverseGeocodingResult" options:0 context:nil];
  120. [conversation addObserver:self forKeyPath:@"lastMessage.userack" options:0 context:nil];
  121. [conversation addObserver:self forKeyPath:@"lastMessage.read" options:0 context:nil];
  122. [conversation addObserver:self forKeyPath:@"lastMessage.delivered" options:0 context:nil];
  123. [conversation addObserver:self forKeyPath:@"lastMessage.sendfailed" options:0 context:nil];
  124. [conversation addObserver:self forKeyPath:@"lastMessage.sent" options:0 context:nil];
  125. [conversation addObserver:self forKeyPath:@"groupName" options:0 context:nil];
  126. [conversation addObserver:self forKeyPath:@"members" options:0 context:nil];
  127. [conversation addObserver:self forKeyPath:@"contact.displayName" options:0 context:nil];
  128. [conversation addObserver:self forKeyPath:@"contact.imageData" options:0 context:nil];
  129. [conversation addObserver:self forKeyPath:@"groupImage" options:0 context:nil];
  130. [conversation addObserver:self forKeyPath:@"contact.contactImage" options:0 context:nil];
  131. [conversation addObserver:self forKeyPath:@"tags" options:0 context:nil];
  132. [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(avatarChanged:) name:kNotificationIdentityAvatarChanged object:nil];
  133. [self updateName];
  134. [self updateLastMessagePreview];
  135. [self updateDateLabel];
  136. [self updateBadgeView];
  137. [self updateContactImage];
  138. [self updateTypingIndicator];
  139. [self setupColors];
  140. [self updateThreemaTypeIcon];
  141. [self updateTagsView];
  142. [self updateNotificationIcon];
  143. }
  144. - (void)updateLastMessagePreview {
  145. BaseMessage *lastMessage = conversation.lastMessage;
  146. NSString *messageTextForPreview = [lastMessage previewText];
  147. if (messageTextForPreview == nil) {
  148. messageTextForPreview = @"";
  149. }
  150. if (conversation.groupId != nil) {
  151. self.statusIcon.image = [UIImage imageNamed:@"MessageStatus_group" inColor:[Colors fontLight]];
  152. self.statusIcon.highlightedImage = self.statusIcon.image;
  153. self.statusIcon.alpha = 1.0;
  154. self.statusIcon.hidden = NO;
  155. } else {
  156. NSString *iconName;
  157. UIColor *color = [Colors fontLight];
  158. if ([self isSystemCallMessage:lastMessage]) {
  159. iconName = @"call";
  160. }
  161. else {
  162. if (lastMessage.isOwn.boolValue && conversation.contact.isGatewayId == NO) {
  163. if (lastMessage.userackDate) {
  164. if (lastMessage.userack.boolValue) {
  165. iconName = @"thumb_up";
  166. color = [Colors green];
  167. } else if (lastMessage.userack.boolValue == NO) {
  168. iconName = @"thumb_down";
  169. color = [Colors orange];
  170. }
  171. } else if (lastMessage.read.boolValue) {
  172. iconName = @"read";
  173. } else if (lastMessage.delivered.boolValue) {
  174. iconName = @"delivered";
  175. } else if (lastMessage.sendFailed.boolValue) {
  176. iconName = @"sendfailed";
  177. color = [Colors red];
  178. } else {
  179. if (lastMessage.sent.boolValue) {
  180. iconName = @"sent";
  181. } else {
  182. iconName = @"sending";
  183. }
  184. }
  185. }
  186. else if (lastMessage == nil) {
  187. iconName = nil;
  188. }
  189. else if (!lastMessage.isOwn.boolValue && conversation.contact.isGatewayId == NO) {
  190. iconName = @"reply";
  191. if (lastMessage.userackDate) {
  192. if (lastMessage.userack.boolValue) {
  193. color = [Colors green];
  194. } else if (lastMessage.userack.boolValue == NO) {
  195. color = [Colors orange];
  196. }
  197. }
  198. }
  199. }
  200. if ((iconName && [self isSystemCallMessage:lastMessage]) || (iconName && ![lastMessage isKindOfClass:[SystemMessage class]])) {
  201. if ([iconName isEqualToString:@"sendfailed"]) {
  202. self.statusIcon.image = [UIImage imageNamed:[NSString stringWithFormat:@"MessageStatus_%@", iconName]];
  203. }
  204. else if ([iconName isEqualToString:@"call"]) {
  205. self.statusIcon.image = [UIImage imageNamed:@"ThreemaPhone" inColor:color];
  206. }
  207. else {
  208. self.statusIcon.image = [UIImage imageNamed:[NSString stringWithFormat:@"MessageStatus_%@", iconName] inColor:color];
  209. }
  210. self.statusIcon.highlightedImage = self.statusIcon.image;
  211. self.statusIcon.alpha = 1.0;
  212. self.statusIcon.hidden = NO;
  213. } else {
  214. self.statusIcon.hidden = YES;
  215. }
  216. }
  217. NSString *draftMessage = [MessageDraftStore loadDraftForConversation:self.conversation];
  218. NSAttributedString *attributedString;
  219. if (draftMessage) {
  220. self.dateLabel.hidden = YES;
  221. self.draftLabel.hidden = NO;
  222. NSAttributedString *draftAttributed = [TextStyleUtils makeAttributedStringFromString:draftMessage withFont:self.messagePreviewLabel.font textColor:[Colors fontLight] isOwn:true application:[UIApplication sharedApplication]];
  223. NSMutableAttributedString *formattedAttributeString = [[NSMutableAttributedString alloc] initWithAttributedString:[self.messagePreviewLabel applyMarkupFor:draftAttributed]];
  224. attributedString = [TextStyleUtils makeMentionsAttributedStringForAttributedString:formattedAttributeString textFont:self.messagePreviewLabel.font atColor:[[Colors fontLight] colorWithAlphaComponent:0.6] messageInfo:TextStyleUtilsMessageInfoOverview application:[UIApplication sharedApplication]];
  225. self.messagePreviewLabel.attributedText = attributedString;
  226. if (conversation.groupId == nil) {
  227. self.statusIcon.hidden = YES;
  228. }
  229. } else {
  230. self.dateLabel.hidden = NO;
  231. self.draftLabel.hidden = YES;
  232. NSString *editedString;
  233. if ([self isSystemCallMessage:lastMessage]) {
  234. NSString *spaces = @"";
  235. for (int i = 0; i < (self.messagePreviewLabel.font.pointSize / 2.2); i++) {
  236. spaces = [NSString stringWithFormat:@"%@ ", spaces];
  237. }
  238. editedString = [NSString stringWithFormat:@"%@%@",spaces, messageTextForPreview];
  239. } else {
  240. editedString = messageTextForPreview;
  241. }
  242. NSMutableAttributedString *formattedAttributeString;
  243. NSString *contactString;
  244. NSMutableAttributedString *messageAttributeString;
  245. if (conversation.groupId != nil) {
  246. if (lastMessage != nil && ![lastMessage isKindOfClass:[SystemMessage class]]) {
  247. if (lastMessage.sender == nil) {
  248. contactString = [[NSString alloc] initWithFormat:@"%@: ", [BundleUtil localizedStringForKey:@"me"]];
  249. } else {
  250. contactString = [[NSString alloc] initWithFormat:@"%@: ", lastMessage.sender.displayName];
  251. }
  252. }
  253. NSAttributedString *attributed = [TextStyleUtils makeAttributedStringFromString:editedString withFont:self.messagePreviewLabel.font textColor:[Colors fontLight] isOwn:true application:[UIApplication sharedApplication]];
  254. messageAttributeString = [[NSMutableAttributedString alloc] initWithAttributedString:[self.messagePreviewLabel applyMarkupFor:attributed]];
  255. NSAttributedString *attributedContact = [TextStyleUtils makeAttributedStringFromString:contactString withFont:self.messagePreviewLabel.font textColor:[Colors fontLight] isOwn:true application:[UIApplication sharedApplication]];
  256. formattedAttributeString = [[NSMutableAttributedString alloc] initWithAttributedString:attributedContact];
  257. [formattedAttributeString appendAttributedString:messageAttributeString];
  258. } else {
  259. NSAttributedString *attributed = [TextStyleUtils makeAttributedStringFromString:editedString withFont:self.messagePreviewLabel.font textColor:[Colors fontLight] isOwn:true application:[UIApplication sharedApplication]];
  260. formattedAttributeString = [[NSMutableAttributedString alloc] initWithAttributedString:[self.messagePreviewLabel applyMarkupFor:attributed]];
  261. }
  262. self.messagePreviewLabel.attributedText = [TextStyleUtils makeMentionsAttributedStringForAttributedString:formattedAttributeString textFont:self.messagePreviewLabel.font atColor:[[Colors fontLight] colorWithAlphaComponent:0.6] messageInfo:TextStyleUtilsMessageInfoOverview application:[UIApplication sharedApplication]];
  263. }
  264. if ([self isSystemCallMessage:lastMessage] && !draftMessage) {
  265. switch ([((SystemMessage *)lastMessage).type integerValue]) {
  266. case kSystemMessageCallEnded:
  267. if (((SystemMessage *)lastMessage).haveCallTime) {
  268. if (!((SystemMessage *)lastMessage).isOwn.boolValue) {
  269. _callImageView.image = [UIImage imageNamed:@"CallDownGreen" inColor:[Colors green]];
  270. } else {
  271. _callImageView.image = [UIImage imageNamed:@"CallUpGreen" inColor:[Colors green]];
  272. }
  273. } else {
  274. if (!((SystemMessage *)lastMessage).isOwn.boolValue) {
  275. _callImageView.image = [UIImage imageNamed:@"CallLeftRed"];
  276. } else {
  277. _callImageView.image = [UIImage imageNamed:@"CallUpRed"];
  278. }
  279. }
  280. break;
  281. case kSystemMessageCallRejected:
  282. if (!((SystemMessage *)lastMessage).isOwn.boolValue) {
  283. _callImageView.image = [UIImage imageNamed:@"CallLeftOrange"];
  284. } else {
  285. _callImageView.image = [UIImage imageNamed:@"CallRightRed"];
  286. }
  287. break;
  288. case kSystemMessageCallRejectedBusy:
  289. if (!((SystemMessage *)lastMessage).isOwn.boolValue) {
  290. _callImageView.image = [UIImage imageNamed:@"CallLeftRed"];
  291. } else {
  292. _callImageView.image = [UIImage imageNamed:@"CallRightRed"];
  293. }
  294. break;
  295. case kSystemMessageCallRejectedTimeout:
  296. if (!((SystemMessage *)lastMessage).isOwn.boolValue) {
  297. _callImageView.image = [UIImage imageNamed:@"CallLeftRed"];
  298. } else {
  299. _callImageView.image = [UIImage imageNamed:@"CallRightRed"];
  300. }
  301. break;
  302. case kSystemMessageCallRejectedDisabled:
  303. _callImageView.image = [UIImage imageNamed:@"CallRightRed"];
  304. break;
  305. case kSystemMessageCallMissed:
  306. _callImageView.image = [UIImage imageNamed:@"CallLeftRed"];
  307. break;
  308. default:
  309. _callImageView.image = [UIImage imageNamed:@"CallUpGreen" inColor:[Colors green]];
  310. break;
  311. }
  312. _callImageHeight.constant = self.messagePreviewLabel.font.pointSize * 1.2;
  313. _callImageView.hidden = NO;
  314. } else {
  315. _callImageView.hidden = YES;
  316. }
  317. _showStatusIcon = self.statusIcon.hidden == NO;
  318. // make sure typing indicator is in sync
  319. [self updateTypingIndicator];
  320. }
  321. - (void)updateDateLabel {
  322. BaseMessage *lastMessage = conversation.lastMessage;
  323. if (lastMessage.userackDate && lastMessage.isOwn.boolValue) {
  324. self.dateLabel.text = [Utils formatShortLastMessageDate:conversation.lastMessage.userackDate];
  325. } else if (lastMessage.read.boolValue && lastMessage.isOwn.boolValue) {
  326. self.dateLabel.text = [Utils formatShortLastMessageDate:conversation.lastMessage.readDate];
  327. } else if (lastMessage.delivered.boolValue && lastMessage.isOwn.boolValue) {
  328. self.dateLabel.text = [Utils formatShortLastMessageDate:conversation.lastMessage.deliveryDate];
  329. } else {
  330. self.dateLabel.text = [Utils formatShortLastMessageDate:conversation.lastMessage.remoteSentDate];
  331. }
  332. }
  333. - (void)updateContactImage {
  334. dispatch_async(dispatch_get_main_queue(), ^{
  335. self.contactImage.image = [BundleUtil imageNamed:@"Unknown"];
  336. });
  337. [[AvatarMaker sharedAvatarMaker] avatarForConversation:conversation size:56.0f masked:YES onCompletion:^(UIImage *avatarImage) {
  338. dispatch_async(dispatch_get_main_queue(), ^{
  339. self.contactImage.image = avatarImage;
  340. });
  341. }];
  342. }
  343. - (void)updateBadgeView {
  344. if (conversation.unreadMessageCount.intValue > 0) {
  345. self.badgeView.alignment = NSTextAlignmentCenter;
  346. self.badgeView.font = [UIFont systemFontOfSize:13];
  347. self.badgeView.shadowOffset = CGSizeMake(1,1);
  348. self.badgeView.value = conversation.unreadMessageCount.intValue;
  349. self.badgeView.alpha = 1.0;
  350. self.badgeView.hidden = NO;
  351. } else {
  352. self.badgeView.alpha = 0.0;
  353. self.badgeView.hidden = YES;
  354. }
  355. }
  356. - (void)updateTypingIndicator {
  357. if (conversation.typing.boolValue) {
  358. self.typingIndicator.alpha = 0.0;
  359. self.typingIndicator.hidden = NO;
  360. self.typingIndicator.alpha = 1.0;
  361. self.statusIcon.alpha = 0.0;
  362. self.statusIcon.hidden = YES;
  363. } else {
  364. if (_showStatusIcon) {
  365. self.statusIcon.hidden = NO;
  366. }
  367. self.typingIndicator.alpha = 0.0;
  368. self.statusIcon.alpha = 1.0;
  369. self.typingIndicator.hidden = YES;
  370. }
  371. }
  372. - (void)updateName {
  373. if (conversation.displayName) {
  374. if (conversation.groupId == nil && conversation.contact.state.intValue == kStateInvalid) {
  375. NSDictionary *strikethroughDict = @{NSStrikethroughStyleAttributeName: @(NSUnderlineStyleThick)};
  376. NSAttributedString *str = [[NSAttributedString alloc] initWithString:conversation.displayName attributes:strikethroughDict];
  377. if ([NSThread isMainThread]) {
  378. self.nameLabel.attributedText = str;
  379. } else {
  380. dispatch_async(dispatch_get_main_queue(), ^{
  381. self.nameLabel.attributedText = str;
  382. });
  383. }
  384. } else {
  385. NSMutableAttributedString *str = [[NSMutableAttributedString alloc] init];
  386. if ([[UserSettings sharedUserSettings].blacklist containsObject:conversation.contact.identity]) {
  387. [str appendAttributedString:[[NSAttributedString alloc] initWithString:@"🚫 " attributes:nil]];
  388. }
  389. [str appendAttributedString:[[NSAttributedString alloc] initWithString:conversation.displayName attributes:nil]];
  390. if ([NSThread isMainThread]) {
  391. self.nameLabel.attributedText = str;
  392. } else {
  393. dispatch_async(dispatch_get_main_queue(), ^{
  394. self.nameLabel.attributedText = str;
  395. });
  396. }
  397. }
  398. }
  399. UIFontDescriptor *fontDescriptor = [UIFontDescriptor preferredFontDescriptorWithTextStyle:UIFontTextStyleHeadline];
  400. CGFloat size = fontDescriptor.pointSize;
  401. dispatch_async(dispatch_get_main_queue(), ^{
  402. self.nameLabel.font = [UIFont boldSystemFontOfSize:size];
  403. });
  404. }
  405. - (void)updateThreemaTypeIcon {
  406. if (self.conversation.isGroup) {
  407. _threemaTypeIcon.hidden = YES;
  408. } else {
  409. _threemaTypeIcon.hidden = [Utils hideThreemaTypeIconForContact:self.conversation.contact];
  410. }
  411. }
  412. - (void)updateTagsView {
  413. self.markedView.alpha = [conversation.marked isEqualToNumber:[NSNumber numberWithBool:YES]] ? 1.0 : 0.0;
  414. }
  415. - (void)updateNotificationIcon {
  416. _pushSetting = [PushSetting findPushSettingForConversation:conversation];
  417. UIImage *pushSettingIcon = nil;
  418. if (_pushSetting) {
  419. pushSettingIcon = [_pushSetting imageForEditedPushSetting];
  420. }
  421. if (pushSettingIcon != nil) {
  422. _notificationIcon.image = [pushSettingIcon imageWithTint:[Colors fontLight]];
  423. _notificationIcon.hidden = false;
  424. } else {
  425. _notificationIcon.image = nil;
  426. _notificationIcon.hidden = true;
  427. }
  428. }
  429. - (NSString *)accessibilityLabel {
  430. NSMutableString *text = [NSMutableString stringWithFormat:@"%@. ", self.nameLabel.text];
  431. if (_pushSetting) {
  432. if (_pushSetting.type == kPushSettingTypeOn && _pushSetting.silent) {
  433. [text appendFormat:@"%@ %@. ", NSLocalizedString(@"notification_sound_header", nil), NSLocalizedString(@"doNotDisturb_off", nil)];
  434. }
  435. else if (_pushSetting.type == kPushSettingTypeOff && !_pushSetting.mentions) {
  436. [text appendFormat:@"%@ %@. ", NSLocalizedString(@"doNotDisturb_title", nil), NSLocalizedString(@"doNotDisturb_on", nil)];
  437. }
  438. else if (_pushSetting.type == kPushSettingTypeOff && _pushSetting.mentions) {
  439. [text appendFormat:@"%@ %@, %@. ", NSLocalizedString(@"doNotDisturb_title", nil), NSLocalizedString(@"doNotDisturb_on", nil), NSLocalizedString(@"doNotDisturb_mention", @"")];
  440. }
  441. else if (_pushSetting.type == kPushSettingTypeOffPeriod && !_pushSetting.mentions) {
  442. [text appendFormat:@"%@ %@ %@. ", NSLocalizedString(@"doNotDisturb_title", nil), NSLocalizedString(@"doNotDisturb_onPeriod_time", nil), [DateFormatter getFullDateFor:_pushSetting.periodOffTillDate]];
  443. }
  444. else if (_pushSetting.type == kPushSettingTypeOffPeriod && _pushSetting.mentions) {
  445. [text appendFormat:@"%@ %@ %@, %@. ", NSLocalizedString(@"doNotDisturb_title", nil), NSLocalizedString(@"doNotDisturb_onPeriod_time", nil), [DateFormatter getFullDateFor:_pushSetting.periodOffTillDate], NSLocalizedString(@"doNotDisturb_mention", @"")];
  446. }
  447. }
  448. NSString *messagePreview = [conversation.lastMessage previewText];
  449. NSString *draftPreview = [MessageDraftStore loadDraftForConversation:self.conversation];
  450. if (draftPreview.length > 0) {
  451. [text appendFormat:@"%@. ", [BundleUtil localizedStringForKey:@"draft"]];
  452. if (conversation.unreadMessageCount.intValue > 0) {
  453. [text appendFormat:@"%@. ", NSLocalizedString(@"unread", nil)];
  454. }
  455. [text appendFormat:@"%@ ", [BundleUtil localizedStringForKey:@"from"]];
  456. [text appendFormat:@"%@. ", [BundleUtil localizedStringForKey:@"me"]];
  457. [text appendFormat:@"%@. ", draftPreview];
  458. [text appendFormat:@"%@. ", [conversation.lastMessage accessibilityMessageShortStatus]];
  459. return text;
  460. }
  461. [text appendFormat:@"%@. ", [conversation.lastMessage accessibilityMessageDate]];
  462. if (messagePreview.length > 0) {
  463. if (conversation.unreadMessageCount.intValue > 0) {
  464. [text appendFormat:@"%@. ", NSLocalizedString(@"unread", nil)];
  465. }
  466. [text appendFormat:@"%@ ", [BundleUtil localizedStringForKey:@"from"]];
  467. [text appendFormat:@"%@. ", [conversation.lastMessage accessibilityMessageSender]];
  468. [text appendFormat:@"%@. ", messagePreview];
  469. [text appendFormat:@"%@. ", [conversation.lastMessage accessibilityMessageShortStatus]];
  470. }
  471. if ([conversation.marked isEqualToNumber:[NSNumber numberWithBool:YES]]) {
  472. [text appendFormat:@"%@. ", NSLocalizedString(@"pinned_conversation", nil)];
  473. }
  474. return text;
  475. }
  476. - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
  477. dispatch_async(dispatch_get_main_queue(), ^{
  478. if (object == conversation) {
  479. if ([keyPath isEqualToString:@"lastMessage"]) {
  480. [self updateLastMessagePreview];
  481. [self updateDateLabel];
  482. } else if ([keyPath isEqualToString:@"contact.displayName"] || [keyPath isEqualToString:@"groupName"] || [keyPath isEqualToString:@"members"]) {
  483. [self updateName];
  484. } else if ([keyPath isEqualToString:@"contact.imageData"] || [keyPath isEqualToString:@"groupImage"] || [keyPath isEqualToString:@"contact.contactImage"]) {
  485. [self updateContactImage];
  486. } else if ([keyPath isEqualToString:@"typing"]) {
  487. [self updateTypingIndicator];
  488. } else if ([keyPath isEqualToString:@"unreadMessageCount"]) {
  489. [self updateBadgeView];
  490. } else if ([keyPath hasPrefix:@"lastMessage."]) {
  491. [self updateLastMessagePreview];
  492. [self updateDateLabel];
  493. } else if ([keyPath hasPrefix:@"tags"]) {
  494. [self updateTagsView];
  495. }
  496. }
  497. });
  498. }
  499. - (BOOL)isSystemCallMessage:(BaseMessage *)message {
  500. if ([message isKindOfClass:[SystemMessage class]]) {
  501. switch ([((SystemMessage *)message).type integerValue]) {
  502. case kSystemMessageCallEnded:
  503. return YES;
  504. case kSystemMessageCallRejected:
  505. return YES;
  506. case kSystemMessageCallRejectedBusy:
  507. return YES;
  508. case kSystemMessageCallRejectedTimeout:
  509. return YES;
  510. case kSystemMessageCallRejectedDisabled:
  511. return YES;
  512. case kSystemMessageCallMissed:
  513. return YES;
  514. case kSystemMessageCallRejectedUnknown:
  515. return YES;
  516. default:
  517. return NO;
  518. }
  519. }
  520. return NO;
  521. }
  522. - (void)setHighlighted:(BOOL)highlighted animated:(BOOL)animated {
  523. [super setHighlighted:highlighted animated:animated];
  524. [_messagePreviewLabel setHighlighted:NO];
  525. }
  526. - (void)voiceOverDeleteConversation {
  527. if ([_conversationCellDelegate respondsToSelector:@selector(voiceOverDeleteConversation:)]) {
  528. [_conversationCellDelegate voiceOverDeleteConversation:self];
  529. }
  530. }
  531. - (void)voiceOverLeaveGroup {
  532. if ([_conversationCellDelegate respondsToSelector:@selector(voiceOverLeaveGroup:)]) {
  533. [_conversationCellDelegate voiceOverLeaveGroup:self];
  534. }
  535. }
  536. - (void)voiceOverMarkConversation:(id)sender {
  537. [ConversationUtils markConversation:conversation];
  538. }
  539. - (void)voiceOverUnmarkConversation:(id)sender {
  540. [ConversationUtils unmarkConversation:conversation];
  541. }
  542. - (void)voiceOverReadMessage:(id)sender {
  543. [ConversationUtils unreadConversation:conversation];
  544. }
  545. #pragma mark - Accessibility
  546. - (NSArray *)accessibilityCustomActions {
  547. UIAccessibilityCustomAction *readAction;
  548. UIAccessibilityCustomAction *markAction;
  549. NSMutableArray *actionArray = [NSMutableArray new];
  550. if (conversation.unreadMessageCount.intValue > 0) {
  551. readAction = [[UIAccessibilityCustomAction alloc] initWithName:NSLocalizedString(@"read", @"") target:self selector:@selector(voiceOverReadMessage:)];
  552. [actionArray addObject:readAction];
  553. }
  554. if (conversation.marked.boolValue) {
  555. markAction = [[UIAccessibilityCustomAction alloc] initWithName:NSLocalizedString(@"unpin", @"") target:self selector:@selector(voiceOverUnmarkConversation:)];
  556. } else {
  557. markAction = [[UIAccessibilityCustomAction alloc] initWithName:NSLocalizedString(@"pin", @"") target:self selector:@selector(voiceOverMarkConversation:)];
  558. }
  559. [actionArray addObject:markAction];
  560. if (conversation.isGroup) {
  561. GroupProxy *group = [GroupProxy groupProxyForConversation:conversation];
  562. if ([group isSelfMember]) {
  563. UIAccessibilityCustomAction *leaveAction = [[UIAccessibilityCustomAction alloc] initWithName:[BundleUtil localizedStringForKey:@"leave_group"] target:self selector:@selector(voiceOverLeaveGroup)];
  564. [actionArray addObject:leaveAction];
  565. }
  566. }
  567. UIAccessibilityCustomAction *deleteAction = [[UIAccessibilityCustomAction alloc] initWithName:NSLocalizedString(@"delete", @"") target:self selector:@selector(voiceOverDeleteConversation)];
  568. [actionArray addObject:deleteAction];
  569. return actionArray;
  570. }
  571. #pragma mark - Notification
  572. - (void)avatarChanged:(NSNotification*)notification
  573. {
  574. if (notification.object && [conversation.contact.identity isEqualToString:notification.object] ) {
  575. [self updateContactImage];
  576. }
  577. }
  578. @end