/************************************************************ * * Hyphenate CONFIDENTIAL * __________________ * Copyright (C) 2016 Hyphenate Inc. All rights reserved. * * NOTICE: All information contained herein is, and remains * the property of Hyphenate Inc. * Dissemination of this information or reproduction of this material * is strictly forbidden unless prior written permission is obtained * from Hyphenate Inc. */ #import "EaseMessageViewController.h" #import #import #import #import "NSDate+Category.h" #import "EaseUsersListViewController.h" #import "EaseMessageReadManager.h" #import "EaseEmotionManager.h" #import "EaseEmoji.h" #import "EaseEmotionEscape.h" #import "EaseCustomMessageCell.h" #import "UIImage+GIF.h" #import "EaseLocalDefine.h" #import "EaseSDKHelper.h" #define KHintAdjustY 50 #define IOS_VERSION [[UIDevice currentDevice] systemVersion]>=9.0 typedef enum : NSUInteger { EMRequestRecord, EMCanRecord, EMCanNotRecord, } EMRecordResponse; @implementation EaseAtTarget - (instancetype)initWithUserId:(NSString*)userId andNickname:(NSString*)nickname { if (self = [super init]) { _userId = [userId copy]; _nickname = [nickname copy]; } return self; } @end @interface EaseMessageViewController () { UIMenuItem *_copyMenuItem; UIMenuItem *_deleteMenuItem; UILongPressGestureRecognizer *_lpgr; NSMutableArray *_atTargets; dispatch_queue_t _messageQueue; BOOL _isRecording; } @property (strong, nonatomic) id playingVoiceModel; @property (nonatomic) BOOL isKicked; @property (nonatomic) BOOL isPlayingAudio; @property (nonatomic, strong) NSMutableArray *atTargets; @end @implementation EaseMessageViewController @synthesize conversation = _conversation; @synthesize deleteConversationIfNull = _deleteConversationIfNull; @synthesize messageCountOfPage = _messageCountOfPage; @synthesize timeCellHeight = _timeCellHeight; @synthesize messageTimeIntervalTag = _messageTimeIntervalTag; - (instancetype)initWithConversationChatter:(NSString *)conversationChatter conversationType:(EMConversationType)conversationType { if ([conversationChatter length] == 0) { return nil; } self = [super initWithStyle:UITableViewStylePlain]; if (self) { _conversation = [[EMClient sharedClient].chatManager getConversation:conversationChatter type:conversationType createIfNotExist:YES]; _messageCountOfPage = 10; _timeCellHeight = 30; _deleteConversationIfNull = YES; _scrollToBottomWhenAppear = YES; _messsagesSource = [NSMutableArray array]; [_conversation markAllMessagesAsRead:nil]; } return self; } - (void)viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view. self.view.backgroundColor = [UIColor colorWithRed:248 / 255.0 green:248 / 255.0 blue:248 / 255.0 alpha:1.0]; self.tableView.separatorStyle = UITableViewCellSeparatorStyleNone; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(hideImagePicker) name:@"hideImagePicker" object:nil]; //Initialization CGFloat chatbarHeight = [EaseChatToolbar defaultHeight]; EMChatToolbarType barType = self.conversation.type == EMConversationTypeChat ? EMChatToolbarTypeChat : EMChatToolbarTypeGroup; self.chatToolbar = [[EaseChatToolbar alloc] initWithFrame:CGRectMake(0, self.view.frame.size.height - chatbarHeight - iPhoneX_BOTTOM_HEIGHT, self.view.frame.size.width, chatbarHeight) type:barType]; self.chatToolbar.autoresizingMask = UIViewAutoresizingFlexibleTopMargin; //Initializa the gesture recognizer UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(keyBoardHidden:)]; [self.view addGestureRecognizer:tap]; _lpgr = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(handleLongPress:)]; _lpgr.minimumPressDuration = 0.5; [self.tableView addGestureRecognizer:_lpgr]; _messageQueue = dispatch_queue_create("hyphenate.com", NULL); //Register the delegate [EMCDDeviceManager sharedInstance].delegate = self; [[EMClient sharedClient].chatManager addDelegate:self delegateQueue:nil]; [[EMClient sharedClient].roomManager addDelegate:self delegateQueue:nil]; if (self.conversation.type == EMConversationTypeChatRoom) { [self joinChatroom:self.conversation.conversationId]; } [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didBecomeActive) name:UIApplicationDidBecomeActiveNotification object:nil]; [[EaseBaseMessageCell appearance] setSendBubbleBackgroundImage:[[UIImage easeImageNamed:@"EaseUIResource.bundle/chat_sender_bg"] stretchableImageWithLeftCapWidth:5 topCapHeight:35]]; [[EaseBaseMessageCell appearance] setRecvBubbleBackgroundImage:[[UIImage easeImageNamed:@"EaseUIResource.bundle/chat_receiver_bg"] stretchableImageWithLeftCapWidth:35 topCapHeight:35]]; [[EaseBaseMessageCell appearance] setSendMessageVoiceAnimationImages:@[[UIImage easeImageNamed:@"EaseUIResource.bundle/chat_sender_audio_playing_full"], [UIImage easeImageNamed:@"EaseUIResource.bundle/chat_sender_audio_playing_000"], [UIImage easeImageNamed:@"EaseUIResource.bundle/chat_sender_audio_playing_001"], [UIImage easeImageNamed:@"EaseUIResource.bundle/chat_sender_audio_playing_002"], [UIImage easeImageNamed:@"EaseUIResource.bundle/chat_sender_audio_playing_003"]]]; [[EaseBaseMessageCell appearance] setRecvMessageVoiceAnimationImages:@[[UIImage easeImageNamed:@"EaseUIResource.bundle/chat_receiver_audio_playing_full"],[UIImage easeImageNamed:@"EaseUIResource.bundle/chat_receiver_audio_playing000"], [UIImage easeImageNamed:@"EaseUIResource.bundle/chat_receiver_audio_playing001"], [UIImage easeImageNamed:@"EaseUIResource.bundle/chat_receiver_audio_playing002"], [UIImage easeImageNamed:@"EaseUIResource.bundle/chat_receiver_audio_playing003"]]]; [[EaseBaseMessageCell appearance] setAvatarSize:40.f]; [[EaseBaseMessageCell appearance] setAvatarCornerRadius:20.f]; [[EaseChatBarMoreView appearance] setMoreViewBackgroundColor:[UIColor colorWithRed:240 / 255.0 green:242 / 255.0 blue:247 / 255.0 alpha:1.0]]; [self tableViewDidTriggerHeaderRefresh]; [self setupEmotion]; self.tableView.estimatedRowHeight = 0; self.tableView.estimatedSectionHeaderHeight = 0; self.tableView.estimatedSectionFooterHeight = 0; } /*! @method @brief 设置表情 @discussion 加载默认表情,如果子类实现了dataSource的自定义表情回调,同时会加载自定义表情 @result */ - (void)setupEmotion { if ([self.dataSource respondsToSelector:@selector(emotionFormessageViewController:)]) { NSArray* emotionManagers = [self.dataSource emotionFormessageViewController:self]; [self.faceView setEmotionManagers:emotionManagers]; } else { NSMutableArray *emotions = [NSMutableArray array]; for (NSString *name in [EaseEmoji allEmoji]) { EaseEmotion *emotion = [[EaseEmotion alloc] initWithName:@"" emotionId:name emotionThumbnail:name emotionOriginal:name emotionOriginalURL:@"" emotionType:EMEmotionDefault]; [emotions addObject:emotion]; } EaseEmotion *emotion = [emotions objectAtIndex:0]; EaseEmotionManager *manager= [[EaseEmotionManager alloc] initWithType:EMEmotionDefault emotionRow:3 emotionCol:7 emotions:emotions tagImage:[UIImage imageNamed:emotion.emotionId]]; [self.faceView setEmotionManagers:@[manager]]; } } - (void)didReceiveMemoryWarning { [super didReceiveMemoryWarning]; // Dispose of any resources that can be recreated. } - (void)dealloc { [[NSNotificationCenter defaultCenter] removeObserver:self]; [[EMCDDeviceManager sharedInstance] stopPlaying]; [EMCDDeviceManager sharedInstance].delegate = nil; if (_imagePicker){ [_imagePicker dismissViewControllerAnimated:NO completion:nil]; _imagePicker = nil; } } - (void)viewWillAppear:(BOOL)animated { [super viewWillAppear:animated]; self.isViewDidAppear = YES; [[EaseSDKHelper shareHelper] setIsShowingimagePicker:NO]; if (self.scrollToBottomWhenAppear) { [self _scrollViewToBottom:NO]; } self.scrollToBottomWhenAppear = YES; } - (void)viewWillDisappear:(BOOL)animated { [super viewWillDisappear:animated]; self.isViewDidAppear = NO; [[EMCDDeviceManager sharedInstance] disableProximitySensor]; } #pragma mark - chatroom - (void)saveChatroom:(EMChatroom *)chatroom { NSString *chatroomName = chatroom.subject ? chatroom.subject : @""; NSUserDefaults *ud = [NSUserDefaults standardUserDefaults]; NSString *key = [NSString stringWithFormat:@"OnceJoinedChatrooms_%@", [[EMClient sharedClient] currentUsername]]; NSMutableDictionary *chatRooms = [NSMutableDictionary dictionaryWithDictionary:[ud objectForKey:key]]; if (![chatRooms objectForKey:chatroom.chatroomId]) { [chatRooms setObject:chatroomName forKey:chatroom.chatroomId]; [ud setObject:chatRooms forKey:key]; [ud synchronize]; } } /*! @method @brief 加入聊天室 @discussion @result */ - (void)joinChatroom:(NSString *)chatroomId { __weak typeof(self) weakSelf = self; [self showHudInView:self.view hint:NSEaseLocalizedString(@"chatroom.joining",@"Joining the chatroom")]; dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ EMError *error = nil; EMChatroom *chatroom = [[EMClient sharedClient].roomManager joinChatroom:chatroomId error:&error]; dispatch_async(dispatch_get_main_queue(), ^{ if (weakSelf) { EaseMessageViewController *strongSelf = weakSelf; [strongSelf hideHud]; if (error != nil) { [strongSelf showHint:[NSString stringWithFormat:NSEaseLocalizedString(@"chatroom.joinFailed",@"join chatroom \'%@\' failed"), chatroomId]]; } else { strongSelf.isJoinedChatroom = YES; [strongSelf saveChatroom:chatroom]; } } else { if (!error || (error.code == EMErrorChatroomAlreadyJoined)) { dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ EMError *leaveError; [[EMClient sharedClient].roomManager leaveChatroom:chatroomId error:&leaveError]; [[EMClient sharedClient].chatManager deleteConversation:chatroomId isDeleteMessages:YES completion:nil]; }); } } }); }); } #pragma mark - EMChatManagerChatroomDelegate - (void)didReceiveUserJoinedChatroom:(EMChatroom *)aChatroom username:(NSString *)aUsername { CGRect frame = self.chatToolbar.frame; [self showHint:[NSString stringWithFormat:NSEaseLocalizedString(@"chatroom.join", @"\'%@\'join chatroom\'%@\'"), aUsername, aChatroom.chatroomId] yOffset:-frame.size.height + KHintAdjustY]; } - (void)didReceiveUserLeavedChatroom:(EMChatroom *)aChatroom username:(NSString *)aUsername { CGRect frame = self.chatToolbar.frame; [self showHint:[NSString stringWithFormat:NSEaseLocalizedString(@"chatroom.leave.hint", @"\'%@\'leave chatroom\'%@\'"), aUsername, aChatroom.chatroomId] yOffset:-frame.size.height + KHintAdjustY]; } - (void)didReceiveKickedFromChatroom:(EMChatroom *)aChatroom reason:(EMChatroomBeKickedReason)aReason { if ([_conversation.conversationId isEqualToString:aChatroom.chatroomId]) { _isKicked = YES; CGRect frame = self.chatToolbar.frame; [self showHint:[NSString stringWithFormat:NSEaseLocalizedString(@"chatroom.remove", @"be removed from chatroom\'%@\'"), aChatroom.chatroomId] yOffset:-frame.size.height + KHintAdjustY]; [self.navigationController popToViewController:self animated:NO]; [self.navigationController popViewControllerAnimated:YES]; } } #pragma mark - getter - (UIImagePickerController *)imagePicker { if (_imagePicker == nil) { _imagePicker = [[UIImagePickerController alloc] init]; _imagePicker.modalPresentationStyle= UIModalPresentationOverFullScreen; _imagePicker.delegate = self; } return _imagePicker; } - (NSMutableArray*)atTargets { if (!_atTargets) { _atTargets = [NSMutableArray array]; } return _atTargets; } #pragma mark - setter - (void)setIsViewDidAppear:(BOOL)isViewDidAppear { _isViewDidAppear =isViewDidAppear; if (_isViewDidAppear) { NSMutableArray *unreadMessages = [NSMutableArray array]; for (EMMessage *message in self.messsagesSource) { if ([self shouldSendHasReadAckForMessage:message read:NO]) { [unreadMessages addObject:message]; } } if ([unreadMessages count]) { [self _sendHasReadResponseForMessages:unreadMessages isRead:YES]; } [_conversation markAllMessagesAsRead:nil]; } } - (void)setChatToolbar:(EaseChatToolbar *)chatToolbar { [_chatToolbar removeFromSuperview]; _chatToolbar = chatToolbar; if (_chatToolbar) { [self.view addSubview:_chatToolbar]; } CGRect tableFrame = self.tableView.frame; tableFrame.size.height = self.view.frame.size.height - _chatToolbar.frame.size.height - iPhoneX_BOTTOM_HEIGHT; self.tableView.frame = tableFrame; if ([chatToolbar isKindOfClass:[EaseChatToolbar class]]) { [(EaseChatToolbar *)self.chatToolbar setDelegate:self]; self.chatBarMoreView = (EaseChatBarMoreView*)[(EaseChatToolbar *)self.chatToolbar moreView]; self.faceView = (EaseFaceView*)[(EaseChatToolbar *)self.chatToolbar faceView]; self.recordView = (EaseRecordView*)[(EaseChatToolbar *)self.chatToolbar recordView]; } } - (void)setDataSource:(id)dataSource { _dataSource = dataSource; [self setupEmotion]; } - (void)setDelegate:(id)delegate { _delegate = delegate; } #pragma mark - private helper /*! @method @brief tableView滑动到底部 @discussion @result */ - (void)_scrollViewToBottom:(BOOL)animated { if (self.tableView.contentSize.height > self.tableView.frame.size.height) { CGPoint offset = CGPointMake(0, self.tableView.contentSize.height - self.tableView.frame.size.height); [self.tableView setContentOffset:offset animated:animated]; } } /*! @method @brief 当前设备是否可以录音 @discussion @param aCompletion 判断结果 @result */ - (void)_canRecordCompletion:(void(^)(EMRecordResponse))aCompletion { AVAuthorizationStatus videoAuthStatus = [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeAudio]; if (videoAuthStatus == AVAuthorizationStatusNotDetermined) { [[AVAudioSession sharedInstance] requestRecordPermission:^(BOOL granted) { if (aCompletion) { aCompletion(granted ? EMCanRecord : EMRequestRecord); } }]; } else if(videoAuthStatus == AVAuthorizationStatusRestricted || videoAuthStatus == AVAuthorizationStatusDenied) { aCompletion(EMCanNotRecord); } else{ aCompletion(EMCanRecord); } } - (void)showMenuViewController:(UIView *)showInView andIndexPath:(NSIndexPath *)indexPath messageType:(EMMessageBodyType)messageType { if (_menuController == nil) { _menuController = [UIMenuController sharedMenuController]; } if (_deleteMenuItem == nil) { _deleteMenuItem = [[UIMenuItem alloc] initWithTitle:NSEaseLocalizedString(@"delete", @"Delete") action:@selector(deleteMenuAction:)]; } if (_copyMenuItem == nil) { _copyMenuItem = [[UIMenuItem alloc] initWithTitle:NSEaseLocalizedString(@"copy", @"Copy") action:@selector(copyMenuAction:)]; } if (messageType == EMMessageBodyTypeText) { [_menuController setMenuItems:@[_copyMenuItem, _deleteMenuItem]]; } else { [_menuController setMenuItems:@[_deleteMenuItem]]; } [_menuController setTargetRect:showInView.frame inView:showInView.superview]; [_menuController setMenuVisible:YES animated:YES]; } - (void)_stopAudioPlayingWithChangeCategory:(BOOL)isChange { //停止音频播放及播放动画 [[EMCDDeviceManager sharedInstance] stopPlaying]; [[EMCDDeviceManager sharedInstance] disableProximitySensor]; [EMCDDeviceManager sharedInstance].delegate = nil; // MessageModel *playingModel = [self.EaseMessageReadManager stopMessageAudioModel]; // NSIndexPath *indexPath = nil; // if (playingModel) { // indexPath = [NSIndexPath indexPathForRow:[self.dataSource indexOfObject:playingModel] inSection:0]; // } // // if (indexPath) { // dispatch_async(dispatch_get_main_queue(), ^{ // [self.tableView beginUpdates]; // [self.tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationNone]; // [self.tableView endUpdates]; // }); // } } /*! @method @brief mov格式视频转换为MP4格式 @discussion @param movUrl mov视频路径 @result MP4格式视频路径 */ - (NSURL *)_convert2Mp4:(NSURL *)movUrl { NSURL *mp4Url = nil; AVURLAsset *avAsset = [AVURLAsset URLAssetWithURL:movUrl options:nil]; NSArray *compatiblePresets = [AVAssetExportSession exportPresetsCompatibleWithAsset:avAsset]; if ([compatiblePresets containsObject:AVAssetExportPresetHighestQuality]) { AVAssetExportSession *exportSession = [[AVAssetExportSession alloc]initWithAsset:avAsset presetName:AVAssetExportPresetHighestQuality]; NSString *mp4Path = [NSString stringWithFormat:@"%@/%d%d.mp4", [EMCDDeviceManager dataPath], (int)[[NSDate date] timeIntervalSince1970], arc4random() % 100000]; mp4Url = [NSURL fileURLWithPath:mp4Path]; exportSession.outputURL = mp4Url; exportSession.shouldOptimizeForNetworkUse = YES; exportSession.outputFileType = AVFileTypeMPEG4; dispatch_semaphore_t wait = dispatch_semaphore_create(0l); [exportSession exportAsynchronouslyWithCompletionHandler:^{ switch ([exportSession status]) { case AVAssetExportSessionStatusFailed: { NSLog(@"failed, error:%@.", exportSession.error); } break; case AVAssetExportSessionStatusCancelled: { NSLog(@"cancelled."); } break; case AVAssetExportSessionStatusCompleted: { NSLog(@"completed."); } break; default: { NSLog(@"others."); } break; } dispatch_semaphore_signal(wait); }]; long timeout = dispatch_semaphore_wait(wait, DISPATCH_TIME_FOREVER); if (timeout) { NSLog(@"timeout."); } if (wait) { //dispatch_release(wait); wait = nil; } } return mp4Url; } /*! @method @brief 通过当前会话类型,返回消息聊天类型 @discussion @result */ - (EMChatType)_messageTypeFromConversationType { EMChatType type = EMChatTypeChat; switch (self.conversation.type) { case EMConversationTypeChat: type = EMChatTypeChat; break; case EMConversationTypeGroupChat: type = EMChatTypeGroupChat; break; case EMConversationTypeChatRoom: type = EMChatTypeChatRoom; break; default: break; } return type; } - (void)_customDownloadMessageFile:(EMMessage *)aMessage { dispatch_async(dispatch_get_main_queue(), ^{ UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:nil message:NSLocalizedString(@"message.autoTransfer", @"Please customize the transfer attachment method") delegate:nil cancelButtonTitle:NSLocalizedString(@"sure", @"OK") otherButtonTitles:nil, nil]; [alertView show]; }); } /*! @method @brief 下载消息附件 @discussion @param message 待下载附件的消息 @result */ - (void)_downloadMessageAttachments:(EMMessage *)message { __weak typeof(self) weakSelf = self; void (^completion)(EMMessage *aMessage, EMError *error) = ^(EMMessage *aMessage, EMError *error) { if (!error) { [weakSelf _reloadTableViewDataWithMessage:message]; } else { [weakSelf showHint:NSEaseLocalizedString(@"message.thumImageFail", @"thumbnail for failure!")]; } }; BOOL isCustomDownload = !([EMClient sharedClient].options.isAutoTransferMessageAttachments); BOOL isAutoDownloadThumbnail = ([EMClient sharedClient].options.isAutoDownloadThumbnail); EMMessageBody *messageBody = message.body; if ([messageBody type] == EMMessageBodyTypeImage) { EMImageMessageBody *imageBody = (EMImageMessageBody *)messageBody; if (imageBody.thumbnailDownloadStatus > EMDownloadStatusSuccessed) { //download the message thumbnail if (isCustomDownload) { [self _customDownloadMessageFile:message]; } else { if (isAutoDownloadThumbnail) { [[[EMClient sharedClient] chatManager] downloadMessageThumbnail:message progress:nil completion:completion]; } } } } else if ([messageBody type] == EMMessageBodyTypeVideo) { EMVideoMessageBody *videoBody = (EMVideoMessageBody *)messageBody; if (videoBody.thumbnailDownloadStatus > EMDownloadStatusSuccessed) { //download the message thumbnail if (isCustomDownload) { [self _customDownloadMessageFile:message]; } else { if (isAutoDownloadThumbnail) { [[[EMClient sharedClient] chatManager] downloadMessageThumbnail:message progress:nil completion:completion]; } } } } else if ([messageBody type] == EMMessageBodyTypeVoice) { EMVoiceMessageBody *voiceBody = (EMVoiceMessageBody*)messageBody; if (voiceBody.downloadStatus > EMDownloadStatusSuccessed) { //download the message attachment if (isCustomDownload) { [self _customDownloadMessageFile:message]; } else { if (isAutoDownloadThumbnail) { [[EMClient sharedClient].chatManager downloadMessageAttachment:message progress:nil completion:^(EMMessage *message, EMError *error) { if (!error) { [weakSelf _reloadTableViewDataWithMessage:message]; } else { [weakSelf showHint:NSEaseLocalizedString(@"message.voiceFail", @"voice for failure!")]; } }]; } } } } } /*! @method @brief 传入消息是否需要发动已读回执 @discussion @param message 待判断的消息 @param read 消息是否已读 @result */ - (BOOL)shouldSendHasReadAckForMessage:(EMMessage *)message read:(BOOL)read { if (message.chatType != EMChatTypeChat || message.isReadAcked || message.direction == EMMessageDirectionSend || ([UIApplication sharedApplication].applicationState == UIApplicationStateBackground) || !self.isViewDidAppear) { return NO; } EMMessageBody *body = message.body; if (((body.type == EMMessageBodyTypeVideo) || (body.type == EMMessageBodyTypeVoice) || (body.type == EMMessageBodyTypeImage)) && !read) { return NO; } else { return YES; } } /*! @method @brief 为传入的消息发送已读回执 @discussion @param messages 待发送已读回执的消息数组 @param isRead 是否已读 @result */ - (void)_sendHasReadResponseForMessages:(NSArray*)messages isRead:(BOOL)isRead { NSMutableArray *unreadMessages = [NSMutableArray array]; for (NSInteger i = 0; i < [messages count]; i++) { EMMessage *message = messages[i]; BOOL isSend = YES; if (_dataSource && [_dataSource respondsToSelector:@selector(messageViewController:shouldSendHasReadAckForMessage:read:)]) { isSend = [_dataSource messageViewController:self shouldSendHasReadAckForMessage:message read:isRead]; } else{ isSend = [self shouldSendHasReadAckForMessage:message read:isRead]; } if (isSend) { [unreadMessages addObject:message]; } } if ([unreadMessages count]) { for (EMMessage *message in unreadMessages) { [[EMClient sharedClient].chatManager sendMessageReadAck:message completion:nil]; } } } - (BOOL)_shouldMarkMessageAsRead { BOOL isMark = YES; if (_dataSource && [_dataSource respondsToSelector:@selector(messageViewControllerShouldMarkMessagesAsRead:)]) { isMark = [_dataSource messageViewControllerShouldMarkMessagesAsRead:self]; } else{ if (([UIApplication sharedApplication].applicationState == UIApplicationStateBackground) || !self.isViewDidAppear) { isMark = NO; } } return isMark; } /*! @method @brief 位置消息被点击选择 @discussion @param model 消息model @result */ - (void)_locationMessageCellSelected:(id)model { _scrollToBottomWhenAppear = NO; EaseLocationViewController *locationController = [[EaseLocationViewController alloc] initWithLocation:CLLocationCoordinate2DMake(model.latitude, model.longitude)]; [self.navigationController pushViewController:locationController animated:YES]; } /*! @method @brief 视频消息被点击选择 @discussion @param model 消息model @result */ - (void)_videoMessageCellSelected:(id)model { _scrollToBottomWhenAppear = NO; EMVideoMessageBody *videoBody = (EMVideoMessageBody*)model.message.body; NSString *localPath = [model.fileLocalPath length] > 0 ? model.fileLocalPath : videoBody.localPath; if ([localPath length] == 0) { [self showHint:NSEaseLocalizedString(@"message.videoFail", @"video for failure!")]; return; } dispatch_block_t block = ^{ //send the acknowledgement [self _sendHasReadResponseForMessages:@[model.message] isRead:YES]; NSURL *videoURL = [NSURL fileURLWithPath:localPath]; MPMoviePlayerViewController *moviePlayerController = [[MPMoviePlayerViewController alloc] initWithContentURL:videoURL]; [moviePlayerController.moviePlayer prepareToPlay]; moviePlayerController.moviePlayer.movieSourceType = MPMovieSourceTypeFile; [self presentMoviePlayerViewControllerAnimated:moviePlayerController]; }; BOOL isCustomDownload = !([EMClient sharedClient].options.isAutoTransferMessageAttachments); __weak typeof(self) weakSelf = self; void (^completion)(EMMessage *aMessage, EMError *error) = ^(EMMessage *aMessage, EMError *error) { if (!error) { [weakSelf _reloadTableViewDataWithMessage:aMessage]; } else { [weakSelf showHint:NSEaseLocalizedString(@"message.thumImageFail", @"thumbnail for failure!")]; } }; if (videoBody.thumbnailDownloadStatus == EMDownloadStatusFailed || ![[NSFileManager defaultManager] fileExistsAtPath:videoBody.thumbnailLocalPath]) { [self showHint:@"begin downloading thumbnail image, click later"]; if (isCustomDownload) { [self _customDownloadMessageFile:model.message]; } else { [[EMClient sharedClient].chatManager downloadMessageThumbnail:model.message progress:nil completion:completion]; } return; } if (videoBody.downloadStatus == EMDownloadStatusSuccessed && [[NSFileManager defaultManager] fileExistsAtPath:localPath]) { block(); return; } [self showHudInView:self.view hint:NSEaseLocalizedString(@"message.downloadingVideo", @"downloading video...")]; if (isCustomDownload) { [self _customDownloadMessageFile:model.message]; } else { [[EMClient sharedClient].chatManager downloadMessageAttachment:model.message progress:nil completion:^(EMMessage *message, EMError *error) { [weakSelf hideHud]; if (!error) { block(); }else{ [weakSelf showHint:NSEaseLocalizedString(@"message.videoFail", @"video for failure!")]; } }]; } } /*! @method @brief 图片消息被点击选择 @discussion @param model 消息model @result */ - (void)_imageMessageCellSelected:(id)model { __weak EaseMessageViewController *weakSelf = self; EMImageMessageBody *imageBody = (EMImageMessageBody*)[model.message body]; BOOL isCustomDownload = !([EMClient sharedClient].options.isAutoTransferMessageAttachments); if ([imageBody type] == EMMessageBodyTypeImage) { if (imageBody.thumbnailDownloadStatus == EMDownloadStatusSuccessed) { if (imageBody.downloadStatus == EMDownloadStatusSuccessed) { //send the acknowledgement [weakSelf _sendHasReadResponseForMessages:@[model.message] isRead:YES]; NSString *localPath = model.message == nil ? model.fileLocalPath : [imageBody localPath]; if (localPath && localPath.length > 0) { UIImage *image = [UIImage imageWithContentsOfFile:localPath]; if (image) { [[EaseMessageReadManager defaultManager] showBrowserWithImages:@[image]]; return; } } } [weakSelf showHudInView:weakSelf.view hint:NSEaseLocalizedString(@"message.downloadingImage", @"downloading a image...")]; void (^completion)(EMMessage *aMessage, EMError *error) = ^(EMMessage *aMessage, EMError *error) { [weakSelf hideHud]; if (!error) { //send the acknowledgement [weakSelf _sendHasReadResponseForMessages:@[model.message] isRead:YES]; NSString *localPath = aMessage == nil ? model.fileLocalPath : [(EMImageMessageBody*)aMessage.body localPath]; if (localPath && localPath.length > 0) { UIImage *image = [UIImage imageWithContentsOfFile:localPath]; // weakSelf.isScrollToBottom = NO; if (image) { [[EaseMessageReadManager defaultManager] showBrowserWithImages:@[image]]; } else { NSLog(@"Read %@ failed!", localPath); } return ; } } [weakSelf showHint:NSEaseLocalizedString(@"message.imageFail", @"image for failure!")]; }; if (isCustomDownload) { [self _customDownloadMessageFile:model.message]; } else { [[EMClient sharedClient].chatManager downloadMessageAttachment:model.message progress:nil completion:completion]; } }else{ //get the message thumbnail if (isCustomDownload) { [self _customDownloadMessageFile:model.message]; } else { [[EMClient sharedClient].chatManager downloadMessageThumbnail:model.message progress:nil completion:^(EMMessage *message, EMError *error) { if (!error) { [weakSelf _reloadTableViewDataWithMessage:model.message]; }else{ [weakSelf showHint:NSEaseLocalizedString(@"message.thumImageFail", @"thumbnail for failure!")]; } }]; } } } } /*! @method @brief 语音消息被点击选择 @discussion @param model 消息model @result */ - (void)_audioMessageCellSelected:(id)model { _scrollToBottomWhenAppear = NO; EMVoiceMessageBody *body = (EMVoiceMessageBody*)model.message.body; EMDownloadStatus downloadStatus = [body downloadStatus]; if (downloadStatus == EMDownloadStatusDownloading) { [self showHint:NSEaseLocalizedString(@"message.downloadingAudio", @"downloading voice, click later")]; return; } else if (downloadStatus == EMDownloadStatusFailed || downloadStatus == EMDownloadStatusPending) { [self showHint:NSEaseLocalizedString(@"message.downloadingAudio", @"downloading voice, click later")]; BOOL isCustomDownload = !([EMClient sharedClient].options.isAutoTransferMessageAttachments); if (isCustomDownload) { [self _customDownloadMessageFile:model.message]; } else { [[EMClient sharedClient].chatManager downloadMessageAttachment:model.message progress:nil completion:nil]; } return; } // play the audio if (model.bodyType == EMMessageBodyTypeVoice) { //send the acknowledgement [self _sendHasReadResponseForMessages:@[model.message] isRead:YES]; __weak EaseMessageViewController *weakSelf = self; BOOL isPrepare = [[EaseMessageReadManager defaultManager] prepareMessageAudioModel:model updateViewCompletion:^(EaseMessageModel *prevAudioModel, EaseMessageModel *currentAudioModel) { if (prevAudioModel || currentAudioModel) { [weakSelf.tableView reloadData]; } }]; if (isPrepare) { _isPlayingAudio = YES; __weak EaseMessageViewController *weakSelf = self; [[EMCDDeviceManager sharedInstance] enableProximitySensor]; [[EMCDDeviceManager sharedInstance] asyncPlayingWithPath:model.fileLocalPath completion:^(NSError *error) { [[EaseMessageReadManager defaultManager] stopMessageAudioModel]; dispatch_async(dispatch_get_main_queue(), ^{ [weakSelf.tableView reloadData]; weakSelf.isPlayingAudio = NO; [[EMCDDeviceManager sharedInstance] disableProximitySensor]; }); }]; } else{ _isPlayingAudio = NO; } } } #pragma mark - pivate data /*! @method @brief 加载历史消息 @discussion @param messageId 参考消息的ID @param count 获取条数 @param isAppend 是否在dataArray直接添加 @result */ - (void)_loadMessagesBefore:(NSString*)messageId count:(NSInteger)count append:(BOOL)isAppend { __weak typeof(self) weakSelf = self; void (^refresh)(NSArray *messages) = ^(NSArray *messages) { dispatch_async(_messageQueue, ^{ //Format the message NSArray *formattedMessages = [weakSelf formatMessages:messages]; //Refresh the page dispatch_async(dispatch_get_main_queue(), ^{ EaseMessageViewController *strongSelf = weakSelf; if (strongSelf) { NSInteger scrollToIndex = 0; if (isAppend) { [strongSelf.messsagesSource insertObjects:messages atIndexes:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, [messages count])]]; //Combine the message id object = [strongSelf.dataArray firstObject]; if ([object isKindOfClass:[NSString class]]) { NSString *timestamp = object; [formattedMessages enumerateObjectsWithOptions:NSEnumerationReverse usingBlock:^(id model, NSUInteger idx, BOOL *stop) { if ([model isKindOfClass:[NSString class]] && [timestamp isEqualToString:model]) { [strongSelf.dataArray removeObjectAtIndex:0]; *stop = YES; } }]; } scrollToIndex = [strongSelf.dataArray count]; [strongSelf.dataArray insertObjects:formattedMessages atIndexes:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, [formattedMessages count])]]; } else { [strongSelf.messsagesSource removeAllObjects]; [strongSelf.messsagesSource addObjectsFromArray:messages]; [strongSelf.dataArray removeAllObjects]; [strongSelf.dataArray addObjectsFromArray:formattedMessages]; } EMMessage *latest = [strongSelf.messsagesSource lastObject]; strongSelf.messageTimeIntervalTag = latest.timestamp; [strongSelf.tableView reloadData]; [strongSelf.tableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:[self.dataArray count] - scrollToIndex - 1 inSection:0] atScrollPosition:UITableViewScrollPositionTop animated:NO]; } }); //re-download all messages that are not successfully downloaded for (EMMessage *message in messages) { [weakSelf _downloadMessageAttachments:message]; } //send the read acknoledgement [weakSelf _sendHasReadResponseForMessages:messages isRead:NO]; }); }; [self.conversation loadMessagesStartFromId:messageId count:(int)count searchDirection:EMMessageSearchDirectionUp completion:^(NSArray *aMessages, EMError *aError) { if (!aError && [aMessages count]) { refresh(aMessages); } }]; } #pragma mark - GestureRecognizer -(void)keyBoardHidden:(UITapGestureRecognizer *)tapRecognizer { if (tapRecognizer.state == UIGestureRecognizerStateEnded) { [self.chatToolbar endEditing:YES]; } } - (void)handleLongPress:(UILongPressGestureRecognizer *)recognizer { if (recognizer.state == UIGestureRecognizerStateBegan && [self.dataArray count] > 0) { CGPoint location = [recognizer locationInView:self.tableView]; NSIndexPath * indexPath = [self.tableView indexPathForRowAtPoint:location]; BOOL canLongPress = NO; if (_dataSource && [_dataSource respondsToSelector:@selector(messageViewController:canLongPressRowAtIndexPath:)]) { canLongPress = [_dataSource messageViewController:self canLongPressRowAtIndexPath:indexPath]; } if (!canLongPress) { return; } if (_dataSource && [_dataSource respondsToSelector:@selector(messageViewController:didLongPressRowAtIndexPath:)]) { [_dataSource messageViewController:self didLongPressRowAtIndexPath:indexPath]; } else{ id object = [self.dataArray objectAtIndex:indexPath.row]; if (![object isKindOfClass:[NSString class]]) { EaseMessageCell *cell = (EaseMessageCell *)[self.tableView cellForRowAtIndexPath:indexPath]; [cell becomeFirstResponder]; _menuIndexPath = indexPath; [self showMenuViewController:cell.bubbleView andIndexPath:indexPath messageType:cell.model.bodyType]; } } } } #pragma mark - Table view data source - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { // Return the number of sections. return 1; } - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { // Return the number of rows in the section. return [self.dataArray count]; } - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { id object = [self.dataArray objectAtIndex:indexPath.row]; //time cell if ([object isKindOfClass:[NSString class]]) { NSString *TimeCellIdentifier = [EaseMessageTimeCell cellIdentifier]; EaseMessageTimeCell *timeCell = (EaseMessageTimeCell *)[tableView dequeueReusableCellWithIdentifier:TimeCellIdentifier]; if (timeCell == nil) { timeCell = [[EaseMessageTimeCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:TimeCellIdentifier]; timeCell.selectionStyle = UITableViewCellSelectionStyleNone; } timeCell.title = object; return timeCell; } else{ id model = object; if (_delegate && [_delegate respondsToSelector:@selector(messageViewController:cellForMessageModel:)]) { UITableViewCell *cell = [_delegate messageViewController:tableView cellForMessageModel:model]; if (cell) { if ([cell isKindOfClass:[EaseMessageCell class]]) { EaseMessageCell *emcell= (EaseMessageCell*)cell; if (emcell.delegate == nil) { emcell.delegate = self; } } return cell; } } if (_dataSource && [_dataSource respondsToSelector:@selector(isEmotionMessageFormessageViewController:messageModel:)]) { BOOL flag = [_dataSource isEmotionMessageFormessageViewController:self messageModel:model]; if (flag) { NSString *CellIdentifier = [EaseCustomMessageCell cellIdentifierWithModel:model]; //send cell EaseCustomMessageCell *sendCell = (EaseCustomMessageCell *)[tableView dequeueReusableCellWithIdentifier:CellIdentifier]; // Configure the cell... if (sendCell == nil) { sendCell = [[EaseCustomMessageCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier model:model]; sendCell.selectionStyle = UITableViewCellSelectionStyleNone; } if (_dataSource && [_dataSource respondsToSelector:@selector(emotionURLFormessageViewController:messageModel:)]) { EaseEmotion *emotion = [_dataSource emotionURLFormessageViewController:self messageModel:model]; if (emotion) { model.image = [UIImage sd_animatedGIFNamed:emotion.emotionOriginal]; model.fileURLPath = emotion.emotionOriginalURL; } } sendCell.model = model; sendCell.delegate = self; return sendCell; } } NSString *CellIdentifier = [EaseMessageCell cellIdentifierWithModel:model]; EaseBaseMessageCell *sendCell = (EaseBaseMessageCell *)[tableView dequeueReusableCellWithIdentifier:CellIdentifier]; // Configure the cell... if (sendCell == nil) { sendCell = [[EaseBaseMessageCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier model:model]; sendCell.selectionStyle = UITableViewCellSelectionStyleNone; sendCell.delegate = self; } sendCell.model = model; return sendCell; } } #pragma mark - Table view delegate - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath { id object = [self.dataArray objectAtIndex:indexPath.row]; if ([object isKindOfClass:[NSString class]]) { return self.timeCellHeight; } else{ id model = object; if (_delegate && [_delegate respondsToSelector:@selector(messageViewController:heightForMessageModel:withCellWidth:)]) { CGFloat height = [_delegate messageViewController:self heightForMessageModel:model withCellWidth:tableView.frame.size.width]; if (height) { return height; } } if (_dataSource && [_dataSource respondsToSelector:@selector(isEmotionMessageFormessageViewController:messageModel:)]) { BOOL flag = [_dataSource isEmotionMessageFormessageViewController:self messageModel:model]; if (flag) { return [EaseCustomMessageCell cellHeightWithModel:model]; } } return [EaseBaseMessageCell cellHeightWithModel:model]; } } #pragma mark - UIImagePickerControllerDelegate - (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info { NSString *mediaType = info[UIImagePickerControllerMediaType]; if ([mediaType isEqualToString:(NSString *)kUTTypeMovie]) { NSURL *videoURL = info[UIImagePickerControllerMediaURL]; // video url: // file:///private/var/mobile/Applications/B3CDD0B2-2F19-432B-9CFA-158700F4DE8F/tmp/capture-T0x16e39100.tmp.9R8weF/capturedvideo.mp4 // we will convert it to mp4 format NSURL *mp4 = [self _convert2Mp4:videoURL]; NSFileManager *fileman = [NSFileManager defaultManager]; if ([fileman fileExistsAtPath:videoURL.path]) { NSError *error = nil; [fileman removeItemAtURL:videoURL error:&error]; if (error) { NSLog(@"failed to remove file, error:%@.", error); } } [self sendVideoMessageWithURL:mp4]; }else{ NSURL *url = info[UIImagePickerControllerReferenceURL]; if (url == nil) { UIImage *orgImage = info[UIImagePickerControllerOriginalImage]; [self sendImageMessage:orgImage]; } else { if ([[UIDevice currentDevice].systemVersion doubleValue] >= 9.0f) { PHFetchResult *result = [PHAsset fetchAssetsWithALAssetURLs:@[url] options:nil]; [result enumerateObjectsUsingBlock:^(PHAsset *asset , NSUInteger idx, BOOL *stop){ if (asset) { [[PHImageManager defaultManager] requestImageDataForAsset:asset options:nil resultHandler:^(NSData *data, NSString *uti, UIImageOrientation orientation, NSDictionary *dic){ if (data != nil) { [self sendImageMessageWithData:data]; } else { [self showHint:NSEaseLocalizedString(@"message.smallerImage", @"The image size is too large, please choose another one")]; } }]; } }]; } else { ALAssetsLibrary *alasset = [[ALAssetsLibrary alloc] init]; [alasset assetForURL:url resultBlock:^(ALAsset *asset) { if (asset) { ALAssetRepresentation* assetRepresentation = [asset defaultRepresentation]; Byte* buffer = (Byte*)malloc((size_t)[assetRepresentation size]); NSUInteger bufferSize = [assetRepresentation getBytes:buffer fromOffset:0.0 length:(NSUInteger)[assetRepresentation size] error:nil]; NSData* fileData = [NSData dataWithBytesNoCopy:buffer length:bufferSize freeWhenDone:YES]; [self sendImageMessageWithData:fileData]; } } failureBlock:NULL]; } } } [picker dismissViewControllerAnimated:YES completion:nil]; self.isViewDidAppear = YES; [[EaseSDKHelper shareHelper] setIsShowingimagePicker:NO]; } - (void)imagePickerControllerDidCancel:(UIImagePickerController *)picker { [self.imagePicker dismissViewControllerAnimated:YES completion:nil]; self.isViewDidAppear = YES; [[EaseSDKHelper shareHelper] setIsShowingimagePicker:NO]; } #pragma mark - EaseMessageCellDelegate - (void)messageCellSelected:(id)model { if (_delegate && [_delegate respondsToSelector:@selector(messageViewController:didSelectMessageModel:)]) { BOOL flag = [_delegate messageViewController:self didSelectMessageModel:model]; if (flag) { [self _sendHasReadResponseForMessages:@[model.message] isRead:YES]; return; } } switch (model.bodyType) { case EMMessageBodyTypeImage: { _scrollToBottomWhenAppear = NO; [self _imageMessageCellSelected:model]; } break; case EMMessageBodyTypeLocation: { [self _locationMessageCellSelected:model]; } break; case EMMessageBodyTypeVoice: { [self _audioMessageCellSelected:model]; } break; case EMMessageBodyTypeVideo: { [self _videoMessageCellSelected:model]; } break; case EMMessageBodyTypeFile: { _scrollToBottomWhenAppear = NO; [self showHint:@"Custom implementation!"]; } break; default: break; } } - (void)statusButtonSelcted:(id)model withMessageCell:(EaseMessageCell*)messageCell { if ((model.messageStatus != EMMessageStatusFailed) && (model.messageStatus != EMMessageStatusPending)) { return; } __weak typeof(self) weakself = self; [[[EMClient sharedClient] chatManager] resendMessage:model.message progress:nil completion:^(EMMessage *message, EMError *error) { if (!error) { [weakself _refreshAfterSentMessage:message]; } else { [weakself.tableView reloadData]; } }]; [self.tableView reloadData]; } - (void)avatarViewSelcted:(id)model { if (_delegate && [_delegate respondsToSelector:@selector(messageViewController:didSelectAvatarMessageModel:)]) { [_delegate messageViewController:self didSelectAvatarMessageModel:model]; return; } _scrollToBottomWhenAppear = NO; } #pragma mark - EMChatToolbarDelegate - (void)chatToolbarDidChangeFrameToHeight:(CGFloat)toHeight { [UIView animateWithDuration:0.3 animations:^{ CGRect rect = self.tableView.frame; rect.origin.y = 0; rect.size.height = self.view.frame.size.height - toHeight - iPhoneX_BOTTOM_HEIGHT; self.tableView.frame = rect; }]; [self _scrollViewToBottom:NO]; } - (void)inputTextViewWillBeginEditing:(EaseTextView *)inputTextView { if (_menuController == nil) { _menuController = [UIMenuController sharedMenuController]; } [_menuController setMenuItems:nil]; } - (void)didSendText:(NSString *)text { if (text && text.length > 0) { [self sendTextMessage:text]; [self.atTargets removeAllObjects]; } } - (BOOL)didInputAtInLocation:(NSUInteger)location { if ([self.delegate respondsToSelector:@selector(messageViewController:selectAtTarget:)] && self.conversation.type == EMConversationTypeGroupChat) { location += 1; __weak typeof(self) weakSelf = self; [self.delegate messageViewController:self selectAtTarget:^(EaseAtTarget *target) { __strong EaseMessageViewController *strongSelf = weakSelf; if (strongSelf && target) { if ([target.userId length] || [target.nickname length]) { [strongSelf.atTargets addObject:target]; NSString *insertStr = [NSString stringWithFormat:@"%@ ", target.nickname ? target.nickname : target.userId]; EaseChatToolbar *toolbar = (EaseChatToolbar*)strongSelf.chatToolbar; NSMutableString *originStr = [toolbar.inputTextView.text mutableCopy]; NSUInteger insertLocation = location > originStr.length ? originStr.length : location; [originStr insertString:insertStr atIndex:insertLocation]; toolbar.inputTextView.text = originStr; toolbar.inputTextView.selectedRange = NSMakeRange(insertLocation + insertStr.length, 0); [toolbar.inputTextView becomeFirstResponder]; } } else if (strongSelf) { EaseChatToolbar *toolbar = (EaseChatToolbar*)strongSelf.chatToolbar; [toolbar.inputTextView becomeFirstResponder]; } }]; EaseChatToolbar *toolbar = (EaseChatToolbar*)self.chatToolbar; toolbar.inputTextView.text = [NSString stringWithFormat:@"%@@", toolbar.inputTextView.text]; [toolbar.inputTextView resignFirstResponder]; return YES; } else { return NO; } } - (BOOL)didDeleteCharacterFromLocation:(NSUInteger)location { EaseChatToolbar *toolbar = (EaseChatToolbar*)self.chatToolbar; if ([toolbar.inputTextView.text length] == location + 1) { //delete last character NSString *inputText = toolbar.inputTextView.text; NSRange range = [inputText rangeOfString:@"@" options:NSBackwardsSearch]; if (range.location != NSNotFound) { if (location - range.location > 1) { NSString *sub = [inputText substringWithRange:NSMakeRange(range.location + 1, location - range.location - 1)]; for (EaseAtTarget *target in self.atTargets) { if ([sub isEqualToString:target.userId] || [sub isEqualToString:target.nickname]) { inputText = range.location > 0 ? [inputText substringToIndex:range.location] : @""; toolbar.inputTextView.text = inputText; toolbar.inputTextView.selectedRange = NSMakeRange(inputText.length, 0); [self.atTargets removeObject:target]; return YES; } } } } } return NO; } - (void)didSendText:(NSString *)text withExt:(NSDictionary*)ext { if ([ext objectForKey:EASEUI_EMOTION_DEFAULT_EXT]) { EaseEmotion *emotion = [ext objectForKey:EASEUI_EMOTION_DEFAULT_EXT]; if (self.dataSource && [self.dataSource respondsToSelector:@selector(emotionExtFormessageViewController:easeEmotion:)]) { NSDictionary *ext = [self.dataSource emotionExtFormessageViewController:self easeEmotion:emotion]; [self sendTextMessage:emotion.emotionTitle withExt:ext]; } else { [self sendTextMessage:emotion.emotionTitle withExt:@{MESSAGE_ATTR_EXPRESSION_ID:emotion.emotionId,MESSAGE_ATTR_IS_BIG_EXPRESSION:@(YES)}]; } return; } if (text && text.length > 0) { [self sendTextMessage:text withExt:ext]; } } - (void)didStartRecordingVoiceAction:(UIView *)recordView { if ([self.delegate respondsToSelector:@selector(messageViewController:didSelectRecordView:withEvenType:)]) { [self.delegate messageViewController:self didSelectRecordView:recordView withEvenType:EaseRecordViewTypeTouchDown]; } else { if ([self.recordView isKindOfClass:[EaseRecordView class]]) { [(EaseRecordView *)self.recordView recordButtonTouchDown]; } } [self _canRecordCompletion:^(EMRecordResponse recordResponse) { switch (recordResponse) { case EMRequestRecord: break; case EMCanRecord: { _isRecording = YES; EaseRecordView *tmpView = (EaseRecordView *)recordView; tmpView.center = self.view.center; [self.view addSubview:tmpView]; [self.view bringSubviewToFront:recordView]; int x = arc4random() % 100000; NSTimeInterval time = [[NSDate date] timeIntervalSince1970]; NSString *fileName = [NSString stringWithFormat:@"%d%d",(int)time,x]; [[EMCDDeviceManager sharedInstance] asyncStartRecordingWithFileName:fileName completion:^(NSError *error) { if (error) { NSLog(@"%@",NSEaseLocalizedString(@"message.startRecordFail", @"failure to start recording")); _isRecording = NO; } }]; } break; case EMCanNotRecord: { UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:NSLocalizedString(@"prompt", @"Prompt") message:NSLocalizedString(@"record.failToPermission", @"No recording permission") delegate:nil cancelButtonTitle:NSLocalizedString(@"ok", @"OK") otherButtonTitles:nil, nil]; [alertView show]; } break; default: break; } }]; } - (void)didCancelRecordingVoiceAction:(UIView *)recordView { if(_isRecording) { [[EMCDDeviceManager sharedInstance] cancelCurrentRecording]; if ([self.delegate respondsToSelector:@selector(messageViewController:didSelectRecordView:withEvenType:)]) { [self.delegate messageViewController:self didSelectRecordView:recordView withEvenType:EaseRecordViewTypeTouchUpOutside]; } else { if ([self.recordView isKindOfClass:[EaseRecordView class]]) { [(EaseRecordView *)self.recordView recordButtonTouchUpOutside]; } [self.recordView removeFromSuperview]; } _isRecording = NO; } } - (void)didFinishRecoingVoiceAction:(UIView *)recordView { if (_isRecording) { if ([self.delegate respondsToSelector:@selector(messageViewController:didSelectRecordView:withEvenType:)]) { [self.delegate messageViewController:self didSelectRecordView:recordView withEvenType:EaseRecordViewTypeTouchUpInside]; } else { if ([self.recordView isKindOfClass:[EaseRecordView class]]) { [(EaseRecordView *)self.recordView recordButtonTouchUpInside]; } [self.recordView removeFromSuperview]; } __weak typeof(self) weakSelf = self; [[EMCDDeviceManager sharedInstance] asyncStopRecordingWithCompletion:^(NSString *recordPath, NSInteger aDuration, NSError *error) { if (!error) { [weakSelf sendVoiceMessageWithLocalPath:recordPath duration:aDuration]; } else { [weakSelf showHudInView:self.view hint:error.domain]; dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ [weakSelf hideHud]; }); } }]; _isRecording = NO; } } - (void)didDragInsideAction:(UIView *)recordView { if ([self.delegate respondsToSelector:@selector(messageViewController:didSelectRecordView:withEvenType:)]) { [self.delegate messageViewController:self didSelectRecordView:recordView withEvenType:EaseRecordViewTypeDragInside]; } else { if ([self.recordView isKindOfClass:[EaseRecordView class]]) { [(EaseRecordView *)self.recordView recordButtonDragInside]; } } } - (void)didDragOutsideAction:(UIView *)recordView { if ([self.delegate respondsToSelector:@selector(messageViewController:didSelectRecordView:withEvenType:)]) { [self.delegate messageViewController:self didSelectRecordView:recordView withEvenType:EaseRecordViewTypeDragOutside]; } else { if ([self.recordView isKindOfClass:[EaseRecordView class]]) { [(EaseRecordView *)self.recordView recordButtonDragOutside]; } } } #pragma mark - EaseChatBarMoreViewDelegate - (void)moreView:(EaseChatBarMoreView *)moreView didItemInMoreViewAtIndex:(NSInteger)index { if ([self.delegate respondsToSelector:@selector(messageViewController:didSelectMoreView:AtIndex:)]) { [self.delegate messageViewController:self didSelectMoreView:moreView AtIndex:index]; return; } } - (void)moreViewPhotoAction:(EaseChatBarMoreView *)moreView { // Hide the keyboard [self.chatToolbar endEditing:YES]; // Pop image picker self.imagePicker.sourceType = UIImagePickerControllerSourceTypePhotoLibrary; self.imagePicker.mediaTypes = @[(NSString *)kUTTypeImage]; [self presentViewController:self.imagePicker animated:YES completion:NULL]; self.isViewDidAppear = NO; [[EaseSDKHelper shareHelper] setIsShowingimagePicker:YES]; } - (void)moreViewTakePicAction:(EaseChatBarMoreView *)moreView { // Hide the keyboard [self.chatToolbar endEditing:YES]; #if TARGET_IPHONE_SIMULATOR [self showHint:NSEaseLocalizedString(@"message.simulatorNotSupportCamera", @"simulator does not support taking picture")]; #elif TARGET_OS_IPHONE self.imagePicker.sourceType = UIImagePickerControllerSourceTypeCamera; self.imagePicker.mediaTypes = @[(NSString *)kUTTypeImage,(NSString *)kUTTypeMovie]; [self presentViewController:self.imagePicker animated:YES completion:NULL]; self.isViewDidAppear = NO; [[EaseSDKHelper shareHelper] setIsShowingimagePicker:YES]; #endif } - (void)moreViewLocationAction:(EaseChatBarMoreView *)moreView { // Hide the keyboard [self.chatToolbar endEditing:YES]; EaseLocationViewController *locationController = [[EaseLocationViewController alloc] init]; locationController.delegate = self; [self.navigationController pushViewController:locationController animated:YES]; } - (void)moreViewAudioCallAction:(EaseChatBarMoreView *)moreView { // Hide the keyboard [self.chatToolbar endEditing:YES]; [[NSNotificationCenter defaultCenter] postNotificationName:KNOTIFICATION_CALL object:@{@"chatter":self.conversation.conversationId, @"type":[NSNumber numberWithInt:0]}]; } - (void)moreViewVideoCallAction:(EaseChatBarMoreView *)moreView { // Hide the keyboard [self.chatToolbar endEditing:YES]; [[NSNotificationCenter defaultCenter] postNotificationName:KNOTIFICATION_CALL object:@{@"chatter":self.conversation.conversationId, @"type":[NSNumber numberWithInt:1]}]; } #pragma mark - EMLocationViewDelegate -(void)sendLocationLatitude:(double)latitude longitude:(double)longitude andAddress:(NSString *)address { [self sendLocationMessageLatitude:latitude longitude:longitude andAddress:address]; } #pragma mark - Hyphenate #pragma mark - EMChatManagerDelegate - (void)didReceiveMessages:(NSArray *)aMessages { for (EMMessage *message in aMessages) { if ([self.conversation.conversationId isEqualToString:message.conversationId]) { [self addMessageToDataSource:message progress:nil]; [self _sendHasReadResponseForMessages:@[message] isRead:NO]; if ([self _shouldMarkMessageAsRead]) { [self.conversation markMessageAsReadWithId:message.messageId error:nil]; } } } } - (void)didReceiveCmdMessages:(NSArray *)aCmdMessages { for (EMMessage *message in aCmdMessages) { if ([self.conversation.conversationId isEqualToString:message.conversationId]) { [self showHint:NSEaseLocalizedString(@"receiveCmd", @"receive cmd message")]; break; } } } - (void)didReceiveHasDeliveredAcks:(NSArray *)aMessages { for(EMMessage *message in aMessages){ [self _updateMessageStatus:message]; } } - (void)didReceiveHasReadAcks:(NSArray *)aMessages { for (EMMessage *message in aMessages) { if (![self.conversation.conversationId isEqualToString:message.conversationId]){ continue; } __block id model = nil; __block BOOL isHave = NO; [self.dataArray enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) { if ([obj conformsToProtocol:@protocol(IMessageModel)]) { model = (id)obj; if ([model.messageId isEqualToString:message.messageId]) { model.message.isReadAcked = YES; isHave = YES; *stop = YES; } } }]; if(!isHave){ return; } if (_delegate && [_delegate respondsToSelector:@selector(messageViewController:didReceiveHasReadAckForModel:)]) { [_delegate messageViewController:self didReceiveHasReadAckForModel:model]; } else{ [self.tableView reloadData]; } } } - (void)didMessageStatusChanged:(EMMessage *)aMessage error:(EMError *)aError; { [self _updateMessageStatus:aMessage]; } - (void)didMessageAttachmentsStatusChanged:(EMMessage *)message error:(EMError *)error{ if (!error) { EMFileMessageBody *fileBody = (EMFileMessageBody*)[message body]; if ([fileBody type] == EMMessageBodyTypeImage) { EMImageMessageBody *imageBody = (EMImageMessageBody *)fileBody; if ([imageBody thumbnailDownloadStatus] == EMDownloadStatusSuccessed) { [self _reloadTableViewDataWithMessage:message]; } }else if([fileBody type] == EMMessageBodyTypeVideo){ EMVideoMessageBody *videoBody = (EMVideoMessageBody *)fileBody; if ([videoBody thumbnailDownloadStatus] == EMDownloadStatusSuccessed) { [self _reloadTableViewDataWithMessage:message]; } }else if([fileBody type] == EMMessageBodyTypeVoice){ if ([fileBody downloadStatus] == EMDownloadStatusSuccessed) { [self _reloadTableViewDataWithMessage:message]; } } }else{ } } #pragma mark - EMCDDeviceManagerProximitySensorDelegate - (void)proximitySensorChanged:(BOOL)isCloseToUser { AVAudioSession *audioSession = [AVAudioSession sharedInstance]; if (isCloseToUser) { [audioSession setCategory:AVAudioSessionCategoryPlayAndRecord error:nil]; } else { [audioSession setCategory:AVAudioSessionCategoryPlayback error:nil]; if (self.playingVoiceModel == nil) { [[EMCDDeviceManager sharedInstance] disableProximitySensor]; } } [audioSession setActive:YES error:nil]; } #pragma mark - action - (void)copyMenuAction:(id)sender { UIPasteboard *pasteboard = [UIPasteboard generalPasteboard]; if (self.menuIndexPath && self.menuIndexPath.row > 0) { id model = [self.dataArray objectAtIndex:self.menuIndexPath.row]; pasteboard.string = model.text; } self.menuIndexPath = nil; } - (void)deleteMenuAction:(id)sender { if (self.menuIndexPath && self.menuIndexPath.row > 0) { id model = [self.dataArray objectAtIndex:self.menuIndexPath.row]; NSMutableIndexSet *indexs = [NSMutableIndexSet indexSetWithIndex:self.menuIndexPath.row]; NSMutableArray *indexPaths = [NSMutableArray arrayWithObjects:self.menuIndexPath, nil]; [self.conversation deleteMessageWithId:model.message.messageId error:nil]; [self.messsagesSource removeObject:model.message]; if (self.menuIndexPath.row - 1 >= 0) { id nextMessage = nil; id prevMessage = [self.dataArray objectAtIndex:(self.menuIndexPath.row - 1)]; if (self.menuIndexPath.row + 1 < [self.dataArray count]) { nextMessage = [self.dataArray objectAtIndex:(self.menuIndexPath.row + 1)]; } if ((!nextMessage || [nextMessage isKindOfClass:[NSString class]]) && [prevMessage isKindOfClass:[NSString class]]) { [indexs addIndex:self.menuIndexPath.row - 1]; [indexPaths addObject:[NSIndexPath indexPathForRow:(self.menuIndexPath.row - 1) inSection:0]]; } } [self.dataArray removeObjectsAtIndexes:indexs]; [self.tableView beginUpdates]; [self.tableView deleteRowsAtIndexPaths:indexPaths withRowAnimation:UITableViewRowAnimationFade]; [self.tableView endUpdates]; } self.menuIndexPath = nil; } #pragma mark - public - (NSArray *)formatMessages:(NSArray *)messages { NSMutableArray *formattedArray = [[NSMutableArray alloc] init]; if ([messages count] == 0) { return formattedArray; } for (EMMessage *message in messages) { //Calculate time interval CGFloat interval = (self.messageTimeIntervalTag - message.timestamp) / 1000; if (self.messageTimeIntervalTag < 0 || interval > 60 || interval < -60) { NSDate *messageDate = [NSDate dateWithTimeIntervalInMilliSecondSince1970:(NSTimeInterval)message.timestamp]; NSString *timeStr = @""; if (_dataSource && [_dataSource respondsToSelector:@selector(messageViewController:stringForDate:)]) { timeStr = [_dataSource messageViewController:self stringForDate:messageDate]; } else{ timeStr = [messageDate formattedTime]; } [formattedArray addObject:timeStr]; self.messageTimeIntervalTag = message.timestamp; } //Construct message model id model = nil; if (_dataSource && [_dataSource respondsToSelector:@selector(messageViewController:modelForMessage:)]) { model = [_dataSource messageViewController:self modelForMessage:message]; } else{ model = [[EaseMessageModel alloc] initWithMessage:message]; model.avatarImage = [UIImage easeImageNamed:@"EaseUIResource.bundle/user"]; model.failImageName = @"imageDownloadFail"; } if (model) { [formattedArray addObject:model]; } } return formattedArray; } -(void)addMessageToDataSource:(EMMessage *)message progress:(id)progress { [self.messsagesSource addObject:message]; __weak EaseMessageViewController *weakSelf = self; dispatch_async(_messageQueue, ^{ NSArray *messages = [weakSelf formatMessages:@[message]]; dispatch_async(dispatch_get_main_queue(), ^{ [weakSelf.dataArray addObjectsFromArray:messages]; [weakSelf.tableView reloadData]; [weakSelf.tableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:[weakSelf.dataArray count] - 1 inSection:0] atScrollPosition:UITableViewScrollPositionBottom animated:YES]; }); }); } #pragma mark - public - (void)tableViewDidTriggerHeaderRefresh { self.messageTimeIntervalTag = -1; NSString *messageId = nil; if ([self.messsagesSource count] > 0) { messageId = [(EMMessage *)self.messsagesSource.firstObject messageId]; } else { messageId = nil; } [self _loadMessagesBefore:messageId count:self.messageCountOfPage append:YES]; [self tableViewDidFinishTriggerHeader:YES reload:YES]; } #pragma mark - send message - (void)_refreshAfterSentMessage:(EMMessage*)aMessage { if ([self.messsagesSource count] && [EMClient sharedClient].options.sortMessageByServerTime) { NSString *msgId = aMessage.messageId; EMMessage *last = self.messsagesSource.lastObject; if ([last isKindOfClass:[EMMessage class]]) { __block NSUInteger index = NSNotFound; index = NSNotFound; [self.messsagesSource enumerateObjectsWithOptions:NSEnumerationReverse usingBlock:^(EMMessage *obj, NSUInteger idx, BOOL *stop) { if ([obj isKindOfClass:[EMMessage class]] && [obj.messageId isEqualToString:msgId]) { index = idx; *stop = YES; } }]; if (index != NSNotFound) { [self.messsagesSource removeObjectAtIndex:index]; [self.messsagesSource addObject:aMessage]; //格式化消息 self.messageTimeIntervalTag = -1; NSArray *formattedMessages = [self formatMessages:self.messsagesSource]; [self.dataArray removeAllObjects]; [self.dataArray addObjectsFromArray:formattedMessages]; [self.tableView reloadData]; [self.tableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:[self.dataArray count] - 1 inSection:0] atScrollPosition:UITableViewScrollPositionBottom animated:NO]; return; } } } [self.tableView reloadData]; } - (void)_sendMessage:(EMMessage *)message isNeedUploadFile:(BOOL)isUploadFile { if (self.conversation.type == EMConversationTypeGroupChat){ message.chatType = EMChatTypeGroupChat; } else if (self.conversation.type == EMConversationTypeChatRoom){ message.chatType = EMChatTypeChatRoom; } __weak typeof(self) weakself = self; if (!([EMClient sharedClient].options.isAutoTransferMessageAttachments) && isUploadFile) { UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:nil message:NSLocalizedString(@"message.autoTransfer", @"Please customize the transfer attachment method") delegate:nil cancelButtonTitle:NSLocalizedString(@"sure", @"OK") otherButtonTitles:nil, nil]; [alertView show]; } else { [self addMessageToDataSource:message progress:nil]; [[EMClient sharedClient].chatManager sendMessage:message progress:^(int progress) { if (weakself.dataSource && [weakself.dataSource respondsToSelector:@selector(messageViewController:updateProgress:messageModel:messageBody:)]) { [weakself.dataSource messageViewController:weakself updateProgress:progress messageModel:nil messageBody:message.body]; } } completion:^(EMMessage *aMessage, EMError *aError) { if (!aError) { [weakself _refreshAfterSentMessage:aMessage]; } else { [weakself.tableView reloadData]; } }]; } } - (void)sendTextMessage:(NSString *)text { NSDictionary *ext = nil; if (self.conversation.type == EMConversationTypeGroupChat) { NSArray *targets = [self _searchAtTargets:text]; if ([targets count]) { __block BOOL atAll = NO; [targets enumerateObjectsUsingBlock:^(NSString *target, NSUInteger idx, BOOL *stop) { if ([target compare:kGroupMessageAtAll options:NSCaseInsensitiveSearch] == NSOrderedSame) { atAll = YES; *stop = YES; } }]; if (atAll) { ext = @{kGroupMessageAtList: kGroupMessageAtAll}; } else { ext = @{kGroupMessageAtList: targets}; } } } [self sendTextMessage:text withExt:ext]; } - (void)sendTextMessage:(NSString *)text withExt:(NSDictionary*)ext { EMMessage *message = [EaseSDKHelper getTextMessage:text to:self.conversation.conversationId messageType:[self _messageTypeFromConversationType] messageExt:ext]; [self _sendMessage:message isNeedUploadFile:NO]; } - (void)sendLocationMessageLatitude:(double)latitude longitude:(double)longitude andAddress:(NSString *)address { EMMessage *message = [EaseSDKHelper getLocationMessageWithLatitude:latitude longitude:longitude address:address to:self.conversation.conversationId messageType:[self _messageTypeFromConversationType] messageExt:nil]; [self _sendMessage:message isNeedUploadFile:NO]; } - (void)sendImageMessageWithData:(NSData *)imageData { id progress = nil; if (_dataSource && [_dataSource respondsToSelector:@selector(messageViewController:progressDelegateForMessageBodyType:)]) { progress = [_dataSource messageViewController:self progressDelegateForMessageBodyType:EMMessageBodyTypeImage]; } else{ progress = self; } EMMessage *message = [EaseSDKHelper getImageMessageWithImageData:imageData to:self.conversation.conversationId messageType:[self _messageTypeFromConversationType] messageExt:nil]; [self _sendMessage:message isNeedUploadFile:YES]; } - (void)sendImageMessage:(UIImage *)image { id progress = nil; if (_dataSource && [_dataSource respondsToSelector:@selector(messageViewController:progressDelegateForMessageBodyType:)]) { progress = [_dataSource messageViewController:self progressDelegateForMessageBodyType:EMMessageBodyTypeImage]; } else{ progress = self; } EMMessage *message = [EaseSDKHelper getImageMessageWithImage:image to:self.conversation.conversationId messageType:[self _messageTypeFromConversationType] messageExt:nil]; [self _sendMessage:message isNeedUploadFile:YES]; } - (void)sendVoiceMessageWithLocalPath:(NSString *)localPath duration:(NSInteger)duration { id progress = nil; if (_dataSource && [_dataSource respondsToSelector:@selector(messageViewController:progressDelegateForMessageBodyType:)]) { progress = [_dataSource messageViewController:self progressDelegateForMessageBodyType:EMMessageBodyTypeVoice]; } else{ progress = self; } EMMessage *message = [EaseSDKHelper getVoiceMessageWithLocalPath:localPath duration:duration to:self.conversation.conversationId messageType:[self _messageTypeFromConversationType] messageExt:nil]; [self _sendMessage:message isNeedUploadFile:YES]; } - (void)sendVideoMessageWithURL:(NSURL *)url { id progress = nil; if (_dataSource && [_dataSource respondsToSelector:@selector(messageViewController:progressDelegateForMessageBodyType:)]) { progress = [_dataSource messageViewController:self progressDelegateForMessageBodyType:EMMessageBodyTypeVideo]; } else{ progress = self; } EMMessage *message = [EaseSDKHelper getVideoMessageWithURL:url to:self.conversation.conversationId messageType:[self _messageTypeFromConversationType] messageExt:nil]; [self _sendMessage:message isNeedUploadFile:YES]; } - (void)sendFileMessageWith:(EMMessage *)message { [self _sendMessage:message isNeedUploadFile:YES]; } #pragma mark - notifycation - (void)didBecomeActive { self.messageTimeIntervalTag = -1; self.dataArray = [[self formatMessages:self.messsagesSource] mutableCopy]; [self.tableView reloadData]; if (self.isViewDidAppear) { NSMutableArray *unreadMessages = [NSMutableArray array]; for (EMMessage *message in self.messsagesSource) { if ([self shouldSendHasReadAckForMessage:message read:NO]) { [unreadMessages addObject:message]; } } if ([unreadMessages count]) { [self _sendHasReadResponseForMessages:unreadMessages isRead:YES]; } [_conversation markAllMessagesAsRead:nil]; if (self.dataSource && [self.dataSource respondsToSelector:@selector(messageViewControllerMarkAllMessagesAsRead:)]) { [self.dataSource messageViewControllerMarkAllMessagesAsRead:self]; } } } - (void)hideImagePicker { if (_imagePicker && [EaseSDKHelper shareHelper].isShowingimagePicker) { [_imagePicker dismissViewControllerAnimated:NO completion:nil]; } } #pragma mark - private - (void)_reloadTableViewDataWithMessage:(EMMessage *)message { if ([self.conversation.conversationId isEqualToString:message.conversationId]) { for (int i = 0; i < self.dataArray.count; i ++) { id object = [self.dataArray objectAtIndex:i]; if ([object isKindOfClass:[EaseMessageModel class]]) { id model = object; if ([message.messageId isEqualToString:model.messageId]) { id model = nil; if (self.dataSource && [self.dataSource respondsToSelector:@selector(messageViewController:modelForMessage:)]) { model = [self.dataSource messageViewController:self modelForMessage:message]; } else{ model = [[EaseMessageModel alloc] initWithMessage:message]; model.avatarImage = [UIImage easeImageNamed:@"EaseUIResource.bundle/user"]; model.failImageName = @"imageDownloadFail"; } [self.tableView beginUpdates]; [self.dataArray replaceObjectAtIndex:i withObject:model]; [self.tableView reloadRowsAtIndexPaths:@[[NSIndexPath indexPathForRow:i inSection:0]] withRowAnimation:UITableViewRowAnimationNone]; [self.tableView endUpdates]; break; } } } } } - (void)_updateMessageStatus:(EMMessage *)aMessage { BOOL isChatting = [aMessage.conversationId isEqualToString:self.conversation.conversationId]; if (aMessage && isChatting) { id model = nil; if (_dataSource && [_dataSource respondsToSelector:@selector(messageViewController:modelForMessage:)]) { model = [_dataSource messageViewController:self modelForMessage:aMessage]; } else{ model = [[EaseMessageModel alloc] initWithMessage:aMessage]; model.avatarImage = [UIImage easeImageNamed:@"EaseUIResource.bundle/user"]; model.failImageName = @"imageDownloadFail"; } if (model) { __block NSUInteger index = NSNotFound; [self.dataArray enumerateObjectsUsingBlock:^(EaseMessageModel *model, NSUInteger idx, BOOL *stop){ if ([model conformsToProtocol:@protocol(IMessageModel)]) { if ([aMessage.messageId isEqualToString:model.message.messageId]) { index = idx; *stop = YES; } } }]; if (index != NSNotFound) { [self.dataArray replaceObjectAtIndex:index withObject:model]; [self.tableView beginUpdates]; [self.tableView reloadRowsAtIndexPaths:@[[NSIndexPath indexPathForRow:index inSection:0]] withRowAnimation:UITableViewRowAnimationNone]; [self.tableView endUpdates]; } } } } - (NSArray*)_searchAtTargets:(NSString*)text { NSMutableArray *targets = nil; if (text.length > 1) { targets = [NSMutableArray array]; NSArray *splits = [text componentsSeparatedByString:@"@"]; if ([splits count]) { for (NSString *split in splits) { if (split.length) { NSString *atALl = NSEaseLocalizedString(@"group.atAll", @"all"); if (split.length >= atALl.length && [split compare:atALl options:NSCaseInsensitiveSearch range:NSMakeRange(0, atALl.length)] == NSOrderedSame) { [targets removeAllObjects]; [targets addObject:kGroupMessageAtAll]; return targets; } for (EaseAtTarget *target in self.atTargets) { if ([target.userId length]) { if ([split hasPrefix:target.userId] || (target.nickname && [split hasPrefix:target.nickname])) { [targets addObject:target.userId]; [self.atTargets removeObject:target]; break; } } } } } } } return targets; } @end