WFCUMessageListViewController.m 61 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403
  1. //
  2. // MessageListViewController.m
  3. // WFChat UIKit
  4. //
  5. // Created by WF Chat on 2017/8/31.
  6. // Copyright © 2017年 WildFireChat. All rights reserved.
  7. //
  8. #import "WFCUMessageListViewController.h"
  9. #import <AVFoundation/AVFoundation.h>
  10. #import "WFCUImagePreviewViewController.h"
  11. #import "WFCUVoiceRecordView.h"
  12. #import "WFCUImageCell.h"
  13. #import "WFCUTextCell.h"
  14. #import "WFCUVoiceCell.h"
  15. #import "WFCULocationCell.h"
  16. #import "WFCUFileCell.h"
  17. #import "WFCUInformationCell.h"
  18. #import "WFCUCallSummaryCell.h"
  19. #import "WFCUStickerCell.h"
  20. #import "WFCUVideoCell.h"
  21. #import "WFCUBrowserViewController.h"
  22. #import <WFChatClient/WFCChatClient.h>
  23. #import "WFCUProfileTableViewController.h"
  24. #import "WFCUChatInputBar.h"
  25. #import "WFCUConversationSettingViewController.h"
  26. #import "SDPhotoBrowser.h"
  27. #import "WFCULocationViewController.h"
  28. #import "WFCULocationPoint.h"
  29. #import "WFCUVideoViewController.h"
  30. #import "WFCUContactListViewController.h"
  31. #import "WFCUBrowserViewController.h"
  32. #import "MBProgressHUD.h"
  33. #import "WFCUMediaMessageDownloader.h"
  34. #import "VideoPlayerSampleViewController.h"
  35. #import "VideoPlayerKit.h"
  36. #import "WFCUForwardViewController.h"
  37. #import <WFChatClient/WFCChatClient.h>
  38. #if WFCU_SUPPORT_VOIP
  39. #import <WFAVEngineKit/WFAVEngineKit.h>
  40. #endif
  41. @interface WFCUMessageListViewController () <UITextFieldDelegate, UICollectionViewDelegateFlowLayout, UICollectionViewDataSource, UINavigationControllerDelegate, WFCUMessageCellDelegate, AVAudioPlayerDelegate, WFCUChatInputBarDelegate, SDPhotoBrowserDelegate, UIGestureRecognizerDelegate>
  42. @property (nonatomic, strong)NSMutableArray<WFCUMessageModel *> *modelList;
  43. @property (nonatomic, strong)NSMutableDictionary<NSNumber *, Class> *cellContentDict;
  44. @property(nonatomic) AVAudioPlayer *player;
  45. @property(nonatomic) NSTimer *playTimer;
  46. @property(nonatomic, assign)long playingMessageId;
  47. @property(nonatomic, assign)BOOL loadingMore;
  48. @property(nonatomic, assign)BOOL hasMoreOld;
  49. @property(nonatomic, strong)WFCCUserInfo *targetUser;
  50. @property(nonatomic, strong)WFCCGroupInfo *targetGroup;
  51. @property(nonatomic, strong)WFCCChannelInfo *targetChannel;
  52. @property(nonatomic, strong)WFCCChatroomInfo *targetChatroom;
  53. @property(nonatomic, strong)WFCUChatInputBar *chatInputBar;
  54. @property(nonatomic, strong)VideoPlayerKit *videoPlayerViewController;
  55. @property (strong, nonatomic) UICollectionView *collectionView;
  56. @property (strong, nonatomic)NSArray<WFCCMessage *> *imageMsgs;
  57. @property (strong, nonatomic)NSString *orignalDraft;
  58. @property (nonatomic, strong)id<UIGestureRecognizerDelegate> scrollBackDelegate;
  59. @property (nonatomic, strong)UIView *backgroundView;
  60. @property (nonatomic, assign)BOOL showAlias;
  61. @property (nonatomic, strong)WFCUMessageCellBase *cell4Menu;
  62. @property (nonatomic, assign)BOOL firstAppear;
  63. @property (nonatomic, assign)BOOL hasNewMessage;
  64. @property (nonatomic, assign)BOOL loadingNew;
  65. @property (nonatomic, strong)UICollectionReusableView *headerView;
  66. @property (nonatomic, strong)UICollectionReusableView *footerView;
  67. @property (nonatomic, strong)UIActivityIndicatorView *headerActivityView;
  68. @property (nonatomic, strong)UIActivityIndicatorView *footerActivityView;
  69. @property (nonatomic, strong)NSTimer *showTypingTimer;
  70. @end
  71. @implementation WFCUMessageListViewController
  72. - (void)viewDidLoad {
  73. [super viewDidLoad];
  74. self.cellContentDict = [[NSMutableDictionary alloc] init];
  75. [self initializedSubViews];
  76. self.firstAppear = YES;
  77. self.hasMoreOld = YES;
  78. UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(onResetKeyboard:)];
  79. [self.collectionView addGestureRecognizer:tap];
  80. [self reloadMessageList];
  81. [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onReceiveMessages:) name:kReceiveMessages object:nil];
  82. [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onRecallMessages:) name:kRecallMessages object:nil];
  83. [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onSendingMessage:) name:kSendingMessageStatusUpdated object:nil];
  84. [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onMessageListChanged:) name:kMessageListChanged object:self.conversation];
  85. [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onMenuHidden:) name:UIMenuControllerDidHideMenuNotification object:nil];
  86. if(self.conversation.type == Single_Type) {
  87. [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onUserInfoUpdated:) name:kUserInfoUpdated object:self.conversation.target];
  88. self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithImage:[UIImage imageNamed:@"nav_chat_single"] style:UIBarButtonItemStyleDone target:self action:@selector(onRightBarBtn:)];
  89. } else if(self.conversation.type == Group_Type) {
  90. [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onGroupInfoUpdated:) name:kGroupInfoUpdated object:self.conversation.target];
  91. self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithImage:[UIImage imageNamed:@"nav_chat_group"] style:UIBarButtonItemStyleDone target:self action:@selector(onRightBarBtn:)];
  92. } else if(self.conversation.type == Channel_Type) {
  93. [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onChannelInfoUpdated:) name:kChannelInfoUpdated object:self.conversation.target];
  94. self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithImage:[UIImage imageNamed:@"nav_chat_channel"] style:UIBarButtonItemStyleDone target:self action:@selector(onRightBarBtn:)];
  95. }
  96. self.chatInputBar = [[WFCUChatInputBar alloc] initWithParentView:self.backgroundView conversation:self.conversation delegate:self];
  97. self.orignalDraft = [[WFCCIMService sharedWFCIMService] getConversationInfo:self.conversation].draft;
  98. if (self.conversation.type == Chatroom_Type) {
  99. __weak typeof(self) ws = self;
  100. __block MBProgressHUD *hud = [MBProgressHUD showHUDAddedTo:ws.view animated:YES];
  101. hud.label.text = @"进入聊天室。。。";
  102. [hud showAnimated:YES];
  103. [[WFCCIMService sharedWFCIMService] joinChatroom:ws.conversation.target success:^{
  104. NSLog(@"join chatroom successs");
  105. [ws sendChatroomWelcomeMessage];
  106. [hud hideAnimated:YES];
  107. } error:^(int error_code) {
  108. NSLog(@"join chatroom error");
  109. hud.mode = MBProgressHUDModeText;
  110. hud.label.text = @"进入聊天室失败";
  111. // hud.offset = CGPointMake(0.f, MBProgressMaxOffset);
  112. [hud hideAnimated:YES afterDelay:1.f];
  113. hud.completionBlock = ^{
  114. [ws.navigationController popViewControllerAnimated:YES];
  115. };
  116. }];
  117. }
  118. WFCCConversationInfo *info = [[WFCCIMService sharedWFCIMService] getConversationInfo:self.conversation];
  119. self.chatInputBar.draft = info.draft;
  120. }
  121. - (void)setLoadingMore:(BOOL)loadingMore {
  122. _loadingMore = loadingMore;
  123. if (_loadingMore) {
  124. [self.headerActivityView startAnimating];
  125. } else {
  126. [self.headerActivityView stopAnimating];
  127. }
  128. }
  129. - (UIActivityIndicatorView *)headerActivityView {
  130. if (!_headerActivityView) {
  131. _headerActivityView = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
  132. }
  133. return _headerActivityView;
  134. }
  135. - (UIActivityIndicatorView *)footerActivityView {
  136. if (!_footerActivityView) {
  137. _footerActivityView = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
  138. }
  139. return _footerActivityView;
  140. }
  141. - (void)setLoadingNew:(BOOL)loadingNew {
  142. _loadingNew = loadingNew;
  143. if (loadingNew) {
  144. [self.footerActivityView startAnimating];
  145. } else {
  146. [self.footerActivityView stopAnimating];
  147. }
  148. }
  149. - (void)setHasNewMessage:(BOOL)hasNewMessage {
  150. _hasNewMessage = hasNewMessage;
  151. UICollectionViewFlowLayout *_customFlowLayout = (UICollectionViewFlowLayout*)self.collectionView.collectionViewLayout;
  152. if (hasNewMessage) {
  153. _customFlowLayout.footerReferenceSize = CGSizeMake(320.0f, 20.0f);
  154. } else {
  155. _customFlowLayout.footerReferenceSize = CGSizeZero;
  156. }
  157. }
  158. - (void)loadMoreMessage:(BOOL)isHistory {
  159. __weak typeof(self) weakSelf = self;
  160. if (isHistory) {
  161. if (self.loadingMore) {
  162. return;
  163. }
  164. self.loadingMore = YES;
  165. long lastIndex = 0;
  166. long long lastUid = 0;
  167. if (weakSelf.modelList.count) {
  168. lastIndex = [weakSelf.modelList firstObject].message.messageId;
  169. lastUid = [weakSelf.modelList firstObject].message.messageUid;
  170. }
  171. dispatch_async(dispatch_get_global_queue(0, DISPATCH_QUEUE_PRIORITY_DEFAULT), ^{
  172. NSArray *messageList = [[WFCCIMService sharedWFCIMService] getMessages:weakSelf.conversation contentTypes:nil from:lastIndex count:10 withUser:self.privateChatUser];
  173. if (!messageList.count) {
  174. messageList = [[WFCCIMService sharedWFCIMService] getRemoteMessages:weakSelf.conversation before:lastUid count:10];
  175. }
  176. if (messageList.count == 0) {
  177. self.hasMoreOld = NO;
  178. }
  179. [NSThread sleepForTimeInterval:0.5];
  180. dispatch_async(dispatch_get_main_queue(), ^{
  181. [weakSelf appendMessages:messageList newMessage:NO highlightId:0];
  182. weakSelf.loadingMore = NO;
  183. });
  184. });
  185. } else {
  186. if (weakSelf.loadingNew || !weakSelf.hasNewMessage) {
  187. return;
  188. }
  189. weakSelf.loadingNew = YES;
  190. long lastIndex = 0;
  191. if (self.modelList.count) {
  192. lastIndex = [self.modelList lastObject].message.messageId;
  193. }
  194. dispatch_async(dispatch_get_global_queue(0, DISPATCH_QUEUE_PRIORITY_DEFAULT), ^{
  195. NSArray *messageList = [[WFCCIMService sharedWFCIMService] getMessages:self.conversation contentTypes:nil from:lastIndex count:-10 withUser:self.privateChatUser];
  196. if (!messageList.count || messageList.count < 10) {
  197. self.hasNewMessage = NO;
  198. }
  199. NSMutableArray *mutableMessages = [messageList mutableCopy];
  200. for (int i = 0; i < mutableMessages.count/2; i++) {
  201. int j = (int)mutableMessages.count - 1 - i;
  202. WFCCMessage *msg = [mutableMessages objectAtIndex:i];
  203. [mutableMessages insertObject:[mutableMessages objectAtIndex:j] atIndex:i];
  204. [mutableMessages removeObjectAtIndex:i+1];
  205. [mutableMessages insertObject:msg atIndex:j];
  206. [mutableMessages removeObjectAtIndex:j+1];
  207. }
  208. [NSThread sleepForTimeInterval:3];
  209. dispatch_async(dispatch_get_main_queue(), ^{
  210. [weakSelf appendMessages:mutableMessages newMessage:YES highlightId:0];
  211. weakSelf.loadingNew = NO;
  212. });
  213. });
  214. }
  215. }
  216. - (void)sendChatroomWelcomeMessage {
  217. WFCCTipNotificationContent *tip = [[WFCCTipNotificationContent alloc] init];
  218. WFCCUserInfo *userInfo = [[WFCCIMService sharedWFCIMService] getUserInfo:[WFCCNetworkService sharedInstance].userId refresh:NO];
  219. tip.tip = [NSString stringWithFormat:@"欢迎 %@ 加入聊天室", userInfo.displayName];
  220. [self sendMessage:tip];
  221. }
  222. - (void)sendChatroomLeaveMessage {
  223. __block WFCCConversation *strongConv = self.conversation;
  224. dispatch_async(dispatch_get_main_queue(), ^{
  225. WFCCTipNotificationContent *tip = [[WFCCTipNotificationContent alloc] init];
  226. WFCCUserInfo *userInfo = [[WFCCIMService sharedWFCIMService] getUserInfo:[WFCCNetworkService sharedInstance].userId refresh:NO];
  227. tip.tip = [NSString stringWithFormat:@"%@ 离开了聊天室", userInfo.displayName];
  228. [[WFCCIMService sharedWFCIMService] send:strongConv content:tip success:^(long long messageUid, long long timestamp) {
  229. [[WFCCIMService sharedWFCIMService] quitChatroom:strongConv.target success:nil error:nil];
  230. } error:^(int error_code) {
  231. }];
  232. });
  233. }
  234. - (void)onLeftBtnPressed:(id)sender {
  235. [self.navigationController popViewControllerAnimated:YES];
  236. }
  237. - (void)didMoveToParentViewController:(UIViewController*)parent
  238. {
  239. [super didMoveToParentViewController:parent];
  240. if(!parent){
  241. [self leftMessageVC];
  242. }
  243. }
  244. - (void)leftMessageVC {
  245. if (self.conversation.type == Chatroom_Type) {
  246. [self sendChatroomLeaveMessage];
  247. }
  248. }
  249. - (void)onRightBarBtn:(UIBarButtonItem *)sender {
  250. WFCUConversationSettingViewController *gvc = [[WFCUConversationSettingViewController alloc] init];
  251. gvc.conversation = self.conversation;
  252. [self.navigationController pushViewController:gvc animated:YES];
  253. }
  254. - (void)setTargetUser:(WFCCUserInfo *)targetUser {
  255. _targetUser = targetUser;
  256. if(targetUser.displayName.length == 0) {
  257. self.title = [NSString stringWithFormat:@"用户<%@>", self.conversation.target];
  258. } else {
  259. self.title = targetUser.displayName;
  260. }
  261. self.navigationItem.backBarButtonItem = [[UIBarButtonItem alloc] init];
  262. self.navigationItem.backBarButtonItem.title = self.title;
  263. }
  264. - (void)setTargetGroup:(WFCCGroupInfo *)targetGroup {
  265. _targetGroup = targetGroup;
  266. if(targetGroup.name.length == 0) {
  267. self.title = [NSString stringWithFormat:@"群组<%@>", self.conversation.target];
  268. self.navigationItem.backBarButtonItem = [[UIBarButtonItem alloc] init];
  269. self.navigationItem.backBarButtonItem.title = @"消息";
  270. } else {
  271. self.title = [NSString stringWithFormat:@"%@(%d)", targetGroup.name, (int)targetGroup.memberCount];
  272. self.navigationItem.backBarButtonItem = [[UIBarButtonItem alloc] init];
  273. self.navigationItem.backBarButtonItem.title = targetGroup.name;
  274. }
  275. }
  276. - (void)setTargetChannel:(WFCCChannelInfo *)targetChannel {
  277. _targetChannel = targetChannel;
  278. if(targetChannel.name.length == 0) {
  279. self.title = [NSString stringWithFormat:@"频道<%@>", self.conversation.target];
  280. self.navigationItem.backBarButtonItem = [[UIBarButtonItem alloc] init];
  281. self.navigationItem.backBarButtonItem.title = @"消息";
  282. } else {
  283. self.title = targetChannel.name;
  284. self.navigationItem.backBarButtonItem = [[UIBarButtonItem alloc] init];
  285. self.navigationItem.backBarButtonItem.title = targetChannel.name;
  286. }
  287. }
  288. - (void)setTargetChatroom:(WFCCChatroomInfo *)targetChatroom {
  289. _targetChatroom = targetChatroom;
  290. if(targetChatroom.title.length == 0) {
  291. self.title = [NSString stringWithFormat:@"聊天室<%@>", self.conversation.target];
  292. self.navigationItem.backBarButtonItem = [[UIBarButtonItem alloc] init];
  293. self.navigationItem.backBarButtonItem.title = @"消息";
  294. } else {
  295. self.title = targetChatroom.title;
  296. self.navigationItem.backBarButtonItem = [[UIBarButtonItem alloc] init];
  297. self.navigationItem.backBarButtonItem.title = targetChatroom.title;
  298. }
  299. }
  300. - (void)setShowAlias:(BOOL)showAlias {
  301. _showAlias = showAlias;
  302. if (self.modelList) {
  303. for (WFCUMessageModel *model in self.modelList) {
  304. if (showAlias && model.message.direction == MessageDirection_Receive) {
  305. model.showNameLabel = YES;
  306. } else {
  307. model.showNameLabel = NO;
  308. }
  309. }
  310. }
  311. }
  312. - (void)onUserInfoUpdated:(NSNotification *)notification {
  313. self.targetUser = notification.userInfo[@"userInfo"];
  314. }
  315. - (void)onGroupInfoUpdated:(NSNotification *)notification {
  316. self.targetGroup = notification.userInfo[@"groupInfo"];
  317. }
  318. - (void)onChannelInfoUpdated:(NSNotification *)notification {
  319. // self.targetGroup = notification.userInfo[@"groupInfo"];
  320. self.targetChannel = notification.userInfo[@"channelInfo"];
  321. }
  322. - (void)scrollToBottom:(BOOL)animated {
  323. NSUInteger rowCount = [self.collectionView numberOfItemsInSection:0];
  324. if (rowCount == 0) {
  325. return;
  326. }
  327. NSUInteger finalRow = rowCount - 1;
  328. for (int i = 0; i < self.modelList.count; i++) {
  329. if ([self.modelList objectAtIndex:i].highlighted) {
  330. finalRow = i;
  331. break;
  332. }
  333. }
  334. NSIndexPath *finalIndexPath = [NSIndexPath indexPathForItem:finalRow inSection:0];
  335. [self.collectionView scrollToItemAtIndexPath:finalIndexPath
  336. atScrollPosition:UICollectionViewScrollPositionBottom
  337. animated:animated];
  338. }
  339. - (void)initializedSubViews {
  340. UICollectionViewFlowLayout *_customFlowLayout = [[UICollectionViewFlowLayout alloc] init];
  341. _customFlowLayout.minimumLineSpacing = 0.0f;
  342. _customFlowLayout.sectionInset = UIEdgeInsetsMake(0.0f, 0.0f, 0.0f, 0.0f);
  343. _customFlowLayout.scrollDirection = UICollectionViewScrollDirectionVertical;
  344. _customFlowLayout.headerReferenceSize = CGSizeMake(320.0f, 20.0f);
  345. UIEdgeInsets insets = UIEdgeInsetsZero;
  346. if (@available(iOS 11.0, *)) {
  347. insets = self.view.safeAreaInsets;
  348. }
  349. CGRect frame = self.view.bounds;
  350. frame.origin.y += kStatusBarAndNavigationBarHeight;
  351. frame.size.height -= (kTabbarSafeBottomMargin + kStatusBarAndNavigationBarHeight);
  352. self.backgroundView = [[UIView alloc] initWithFrame:frame];
  353. [self.view addSubview:self.backgroundView];
  354. self.collectionView = [[UICollectionView alloc] initWithFrame:CGRectMake(0, 0, self.backgroundView.bounds.size.width, self.backgroundView.bounds.size.height - CHAT_INPUT_BAR_HEIGHT) collectionViewLayout:_customFlowLayout];
  355. [self.backgroundView addSubview:self.collectionView];
  356. self.backgroundView.backgroundColor = [UIColor whiteColor];
  357. [self.collectionView
  358. setBackgroundColor:[UIColor colorWithRed:(235) / 255.0f green:(235) / 255.0f blue:(235) / 255.0f alpha:1]];
  359. self.collectionView.showsHorizontalScrollIndicator = NO;
  360. self.collectionView.showsVerticalScrollIndicator = NO;
  361. self.collectionView.alwaysBounceVertical = YES;
  362. self.view.backgroundColor = self.collectionView.backgroundColor;
  363. [self registerCell:[WFCUTextCell class] forContent:[WFCCTextMessageContent class]];
  364. [self registerCell:[WFCUImageCell class] forContent:[WFCCImageMessageContent class]];
  365. [self registerCell:[WFCUVoiceCell class] forContent:[WFCCSoundMessageContent class]];
  366. [self registerCell:[WFCUVideoCell class] forContent:[WFCCVideoMessageContent class]];
  367. [self registerCell:[WFCULocationCell class] forContent:[WFCCLocationMessageContent class]];
  368. [self registerCell:[WFCUFileCell class] forContent:[WFCCFileMessageContent class]];
  369. [self registerCell:[WFCUStickerCell class] forContent:[WFCCStickerMessageContent class]];
  370. [self registerCell:[WFCUInformationCell class] forContent:[WFCCCreateGroupNotificationContent class]];
  371. [self registerCell:[WFCUInformationCell class] forContent:[WFCCAddGroupeMemberNotificationContent class]];
  372. [self registerCell:[WFCUInformationCell class] forContent:[WFCCKickoffGroupMemberNotificaionContent class]];
  373. [self registerCell:[WFCUInformationCell class] forContent:[WFCCQuitGroupNotificationContent class]];
  374. [self registerCell:[WFCUInformationCell class] forContent:[WFCCDismissGroupNotificationContent class]];
  375. [self registerCell:[WFCUInformationCell class] forContent:[WFCCTransferGroupOwnerNotificationContent class]];
  376. [self registerCell:[WFCUInformationCell class] forContent:[WFCCModifyGroupAliasNotificationContent class]];
  377. [self registerCell:[WFCUInformationCell class] forContent:[WFCCChangeGroupNameNotificationContent class]];
  378. [self registerCell:[WFCUInformationCell class] forContent:[WFCCChangeGroupPortraitNotificationContent class]];
  379. [self registerCell:[WFCUCallSummaryCell class] forContent:[WFCCCallStartMessageContent class]];
  380. [self registerCell:[WFCUInformationCell class] forContent:[WFCCTipNotificationContent class]];
  381. [self registerCell:[WFCUInformationCell class] forContent:[WFCCUnknownMessageContent class]];
  382. [self registerCell:[WFCUInformationCell class] forContent:[WFCCRecallMessageContent class]];
  383. [self.collectionView registerClass:[UICollectionReusableView class] forSupplementaryViewOfKind:UICollectionElementKindSectionHeader withReuseIdentifier:@"HeaderView"];
  384. [self.collectionView registerClass:[UICollectionReusableView class] forSupplementaryViewOfKind:UICollectionElementKindSectionFooter withReuseIdentifier:@"FooterView"];
  385. self.collectionView.dataSource = self;
  386. self.collectionView.delegate = self;
  387. }
  388. - (void)registerCell:(Class)cellCls forContent:(Class)msgContentCls {
  389. [self.collectionView registerClass:cellCls
  390. forCellWithReuseIdentifier:[NSString stringWithFormat:@"%d", [msgContentCls getContentType]]];
  391. [self.cellContentDict setObject:cellCls forKey:@([msgContentCls getContentType])];
  392. }
  393. - (void)showTyping:(WFCCTypingType)typingType {
  394. if (self.showTypingTimer) {
  395. [self.showTypingTimer invalidate];
  396. }
  397. self.showTypingTimer = [NSTimer timerWithTimeInterval:TYPING_INTERVAL/2 target:self selector:@selector(stopShowTyping) userInfo:nil repeats:NO];
  398. [[NSRunLoop mainRunLoop] addTimer:self.showTypingTimer forMode:NSDefaultRunLoopMode];
  399. if (typingType == Typing_TEXT) {
  400. self.title = @"对方正在输入...";
  401. } else if(typingType == Typing_VOICE) {
  402. self.title = @"对方正在录音...";
  403. } else if(typingType == Typing_CAMERA) {
  404. self.title = @"对方正在拍照...";
  405. } else if(typingType == Typing_LOCATION) {
  406. self.title = @"对方正在选取位置...";
  407. } else if(typingType == Typing_FILE) {
  408. self.title = @"对方正在选取文件...";
  409. }
  410. }
  411. - (void)stopShowTyping {
  412. if(self.showTypingTimer != nil) {
  413. [self.showTypingTimer invalidate];
  414. self.showTypingTimer = nil;
  415. if (self.conversation.type == Single_Type) {
  416. self.targetUser = self.targetUser;
  417. } else if(self.conversation.type == Group_Type) {
  418. self.targetGroup = self.targetGroup;
  419. } else if(self.conversation.type == Channel_Type) {
  420. self.targetChannel = self.targetChannel;
  421. } else if(self.conversation.type == Group_Type) {
  422. self.targetGroup = self.targetGroup;
  423. }
  424. }
  425. }
  426. - (void)onResetKeyboard:(id)sender {
  427. [self.chatInputBar resetInputBarStatue];
  428. }
  429. - (void)didReceiveMemoryWarning {
  430. [super didReceiveMemoryWarning];
  431. }
  432. - (void)viewWillAppear:(BOOL)animated {
  433. [super viewWillAppear:animated];
  434. if(self.conversation.type == Single_Type) {
  435. WFCCUserInfo *userInfo = [[WFCCIMService sharedWFCIMService] getUserInfo:self.conversation.target refresh:YES];
  436. self.targetUser = userInfo;
  437. } else if(self.conversation.type == Group_Type) {
  438. WFCCGroupInfo *groupInfo = [[WFCCIMService sharedWFCIMService] getGroupInfo:self.conversation.target refresh:YES];
  439. self.targetGroup = groupInfo;
  440. } else if (self.conversation.type == Channel_Type) {
  441. WFCCChannelInfo *channelInfo = [[WFCCIMService sharedWFCIMService] getChannelInfo:self.conversation.target refresh:YES];
  442. self.targetChannel = channelInfo;
  443. } else if(self.conversation.type == Chatroom_Type) {
  444. __weak typeof(self)ws = self;
  445. [[WFCCIMService sharedWFCIMService] getChatroomInfo:self.conversation.target upateDt:ws.targetChatroom.updateDt success:^(WFCCChatroomInfo *chatroomInfo) {
  446. ws.targetChatroom = chatroomInfo;
  447. } error:^(int error_code) {
  448. }];
  449. }
  450. self.tabBarController.tabBar.hidden = YES;
  451. [self.collectionView reloadData];
  452. if (self.navigationController.viewControllers.count > 1) { // 记录系统返回手势的代理
  453. _scrollBackDelegate = self.navigationController.interactivePopGestureRecognizer.delegate; // 设置系统返回手势的代理为当前控制器
  454. self.navigationController.interactivePopGestureRecognizer.delegate = self;
  455. }
  456. if (self.conversation.type == Group_Type) {
  457. self.showAlias = ![[WFCCIMService sharedWFCIMService] isHiddenGroupMemberName:self.targetGroup.target];
  458. }
  459. if (self.firstAppear) {
  460. self.firstAppear = NO;
  461. [self scrollToBottom:NO];
  462. }
  463. }
  464. - (void)viewDidAppear:(BOOL)animated {
  465. [super viewDidAppear:animated];
  466. }
  467. - (void)viewWillDisappear:(BOOL)animated {
  468. [super viewWillDisappear:animated];
  469. NSString *newDraft = self.chatInputBar.draft;
  470. if (![self.orignalDraft isEqualToString:newDraft]) {
  471. self.orignalDraft = newDraft;
  472. [[WFCCIMService sharedWFCIMService] setConversation:self.conversation draft:newDraft];
  473. }
  474. // 设置系统返回手势的代理为我们刚进入控制器的时候记录的系统的返回手势代理
  475. self.navigationController.interactivePopGestureRecognizer.delegate = _scrollBackDelegate;
  476. [self.chatInputBar resetInputBarStatue];
  477. }
  478. - (void)sendMessage:(WFCCMessageContent *)content {
  479. //发送消息时,client会发出"kSendingMessageStatusUpdated“的通知,消息界面收到通知后加入到列表中。
  480. __weak typeof(self) ws = self;
  481. [[WFCCIMService sharedWFCIMService] send:self.conversation content:content toUser:self.privateChatUser expireDuration:0 success:^(long long messageUid, long long timestamp) {
  482. NSLog(@"send message success");
  483. if ([content isKindOfClass:[WFCCStickerMessageContent class]]) {
  484. [ws saveStickerRemoteUrl:(WFCCStickerMessageContent *)content];
  485. }
  486. } error:^(int error_code) {
  487. NSLog(@"send message fail(%d)", error_code);
  488. }];
  489. }
  490. - (void)onReceiveMessages:(NSNotification *)notification {
  491. NSArray<WFCCMessage *> *messages = notification.object;
  492. [self appendMessages:messages newMessage:YES highlightId:0];
  493. [[WFCCIMService sharedWFCIMService] clearUnreadStatus:self.conversation];
  494. }
  495. - (void)onRecallMessages:(NSNotification *)notification {
  496. long long messageUid = [notification.object longLongValue];
  497. WFCCMessage *msg = [[WFCCIMService sharedWFCIMService] getMessageByUid:messageUid];
  498. if (msg != nil) {
  499. for (int i = 0; i < self.modelList.count; i++) {
  500. WFCUMessageModel *model = [self.modelList objectAtIndex:i];
  501. if (model.message.messageUid == messageUid) {
  502. model.message = msg;
  503. [self.collectionView reloadItemsAtIndexPaths:@[[NSIndexPath indexPathForRow:i inSection:0]]];
  504. break;
  505. }
  506. }
  507. }
  508. }
  509. - (void)onSendingMessage:(NSNotification *)notification {
  510. WFCCMessage *message = [notification.userInfo objectForKey:@"message"];
  511. WFCCMessageStatus status = [[notification.userInfo objectForKey:@"status"] integerValue];
  512. if (status == Message_Status_Sending) {
  513. if ([message.conversation isEqual:self.conversation]) {
  514. [self appendMessages:@[message] newMessage:YES highlightId:0];
  515. }
  516. }
  517. }
  518. - (void)onMessageListChanged:(NSNotification *)notification {
  519. [self reloadMessageList];
  520. }
  521. - (void)reloadMessageList {
  522. NSArray *messageList;
  523. if (self.highlightMessageId > 0) {
  524. NSArray *messageListOld = [[WFCCIMService sharedWFCIMService] getMessages:self.conversation contentTypes:nil from:self.highlightMessageId+1 count:15 withUser:self.privateChatUser];
  525. NSArray *messageListNew = [[WFCCIMService sharedWFCIMService] getMessages:self.conversation contentTypes:nil from:self.highlightMessageId count:-15 withUser:self.privateChatUser];
  526. NSMutableArray *list = [[NSMutableArray alloc] init];
  527. [list addObjectsFromArray:messageListNew];
  528. [list addObjectsFromArray:messageListOld];
  529. messageList = [list copy];
  530. [[WFCCIMService sharedWFCIMService] clearUnreadStatus:self.conversation];
  531. if (messageListNew.count == 15) {
  532. self.hasNewMessage = YES;
  533. }
  534. } else {
  535. messageList = [[WFCCIMService sharedWFCIMService] getMessages:self.conversation contentTypes:nil from:0 count:15 withUser:self.privateChatUser];
  536. [[WFCCIMService sharedWFCIMService] clearUnreadStatus:self.conversation];
  537. }
  538. self.modelList = [[NSMutableArray alloc] init];
  539. [self appendMessages:messageList newMessage:NO highlightId:self.highlightMessageId];
  540. self.highlightMessageId = 0;
  541. }
  542. - (void)appendMessages:(NSArray<WFCCMessage *> *)messages newMessage:(BOOL)newMessage highlightId:(long)highlightId {
  543. if (messages.count == 0) {
  544. return;
  545. }
  546. int count = 0;
  547. for (int i = 0; i < messages.count; i++) {
  548. WFCCMessage *message = [messages objectAtIndex:i];
  549. if (![message.conversation isEqual:self.conversation]) {
  550. continue;
  551. }
  552. if ([message.content isKindOfClass:[WFCCTypingMessageContent class]] && message.direction == MessageDirection_Receive) {
  553. double now = [[NSDate date] timeIntervalSince1970];
  554. if (now - message.serverTime + [WFCCNetworkService sharedInstance].serverDeltaTime < TYPING_INTERVAL) {
  555. WFCCTypingMessageContent *content = (WFCCTypingMessageContent *)message.content;
  556. [self showTyping:content.type];
  557. }
  558. continue;
  559. }
  560. if (!([message.content.class getContentFlags] & 0x1)) {
  561. continue;
  562. }
  563. count++;
  564. if (newMessage) {
  565. BOOL showTime = YES;
  566. if (self.modelList.count > 0 && (message.serverTime - (self.modelList[self.modelList.count - 1]).message.serverTime < 60 * 1000)) {
  567. showTime = NO;
  568. }
  569. WFCUMessageModel *model = [WFCUMessageModel modelOf:message showName:message.direction == MessageDirection_Receive && self.showAlias showTime:showTime];
  570. if (highlightId > 0 && message.messageId == highlightId) {
  571. model.highlighted = YES;
  572. }
  573. [self.modelList addObject:model];
  574. } else {
  575. if (self.modelList.count > 0 && (self.modelList[0].message.serverTime - message.serverTime < 60 * 1000) && i != 0) {
  576. self.modelList[0].showTimeLabel = NO;
  577. }
  578. WFCUMessageModel *model = [WFCUMessageModel modelOf:message showName:message.direction == MessageDirection_Receive&&self.showAlias showTime:YES];
  579. if (highlightId > 0 && message.messageId == highlightId) {
  580. model.highlighted = YES;
  581. }
  582. [self.modelList insertObject:model atIndex:0];
  583. }
  584. }
  585. if (count > 0) {
  586. [self stopShowTyping];
  587. }
  588. BOOL isAtButtom = NO;
  589. if (newMessage) {
  590. CGPoint offset = self.collectionView.contentOffset;
  591. CGSize size = self.collectionView.contentSize;
  592. CGSize visiableSize = self.collectionView.visibleSize;
  593. isAtButtom = (offset.y + visiableSize.height - size.height) > -100;
  594. }
  595. [self.collectionView reloadData];
  596. if (newMessage || self.modelList.count == messages.count) {
  597. if(isAtButtom) {
  598. [self scrollToBottom:YES];
  599. }
  600. } else {
  601. CGFloat offset = 0;
  602. for (int i = 0; i < count; i++) {
  603. CGSize size = [self collectionView:self.collectionView layout:self.collectionView.collectionViewLayout sizeForItemAtIndexPath:[NSIndexPath indexPathForRow:i inSection:0]];
  604. offset += size.height;
  605. }
  606. self.collectionView.contentOffset = CGPointMake(0, offset);
  607. [UIView animateWithDuration:0.2 animations:^{
  608. self.collectionView.contentOffset = CGPointMake(0, offset - 20);
  609. }];
  610. }
  611. }
  612. - (WFCUMessageModel *)modelOfMessage:(long)messageId {
  613. if (messageId <= 0) {
  614. return nil;
  615. }
  616. for (WFCUMessageModel *model in self.modelList) {
  617. if (model.message.messageId == messageId) {
  618. return model;
  619. }
  620. }
  621. return nil;
  622. }
  623. - (void)stopPlayer {
  624. if (self.player && [self.player isPlaying]) {
  625. [self.player stop];
  626. if ([self.playTimer isValid]) {
  627. [self.playTimer invalidate];
  628. self.playTimer = nil;
  629. }
  630. }
  631. [self modelOfMessage:self.playingMessageId].voicePlaying = NO;
  632. self.playingMessageId = 0;
  633. [[NSNotificationCenter defaultCenter] postNotificationName:kVoiceMessagePlayStoped object:nil];
  634. }
  635. -(void)prepardToPlay:(WFCUMessageModel *)model {
  636. if (self.playingMessageId == model.message.messageId) {
  637. [self stopPlayer];
  638. } else {
  639. [self stopPlayer];
  640. self.playingMessageId = model.message.messageId;
  641. WFCCSoundMessageContent *soundContent = (WFCCSoundMessageContent *)model.message.content;
  642. if (soundContent.localPath.length == 0) {
  643. model.mediaDownloading = YES;
  644. __weak typeof(self) weakSelf = self;
  645. [[WFCUMediaMessageDownloader sharedDownloader] tryDownload:model.message success:^(long long messageUid, NSString *localPath) {
  646. model.mediaDownloading = NO;
  647. [weakSelf startPlay:model];
  648. } error:^(long long messageUid, int error_code) {
  649. model.mediaDownloading = NO;
  650. }];
  651. } else {
  652. [self startPlay:model];
  653. }
  654. }
  655. }
  656. -(void)startPlay:(WFCUMessageModel *)model {
  657. if ([model.message.content isKindOfClass:[WFCCSoundMessageContent class]]) {
  658. // Setup audio session
  659. AVAudioSession *session = [AVAudioSession sharedInstance];
  660. [session setCategory:AVAudioSessionCategoryPlayAndRecord error:nil];
  661. [session overrideOutputAudioPort:AVAudioSessionPortOverrideSpeaker
  662. error:nil];
  663. WFCCSoundMessageContent *snc = (WFCCSoundMessageContent *)model.message.content;
  664. NSError *error = nil;
  665. self.player = [[AVAudioPlayer alloc] initWithData:[snc getWavData] error:&error];
  666. [self.player setDelegate:self];
  667. [self.player prepareToPlay];
  668. [self.player play];
  669. model.voicePlaying = YES;
  670. [[NSNotificationCenter defaultCenter] postNotificationName:kVoiceMessageStartPlaying object:@(self.playingMessageId)];
  671. } else if([model.message.content isKindOfClass:[WFCCVideoMessageContent class]]) {
  672. WFCCVideoMessageContent *videoMsg = (WFCCVideoMessageContent *)model.message.content;
  673. NSURL *url = [NSURL fileURLWithPath:videoMsg.localPath];
  674. if (!self.videoPlayerViewController) {
  675. self.videoPlayerViewController = [VideoPlayerKit videoPlayerWithContainingView:self.view optionalTopView:nil hideTopViewWithControls:YES];
  676. // self.videoPlayerViewController.delegate = self;
  677. self.videoPlayerViewController.allowPortraitFullscreen = YES;
  678. } else {
  679. [self.videoPlayerViewController.view removeFromSuperview];
  680. }
  681. [self.view addSubview:self.videoPlayerViewController.view];
  682. [self.videoPlayerViewController playVideoWithTitle:@" " URL:url videoID:nil shareURL:nil isStreaming:NO playInFullScreen:YES];
  683. }
  684. }
  685. #pragma mark - UICollectionViewDataSource
  686. - (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
  687. return self.modelList.count;
  688. }
  689. // The cell that is returned must be retrieved from a call to -dequeueReusableCellWithReuseIdentifier:forIndexPath:
  690. - (__kindof UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
  691. WFCUMessageModel *model = self.modelList[indexPath.row];
  692. NSString *objName = [NSString stringWithFormat:@"%d", [model.message.content.class getContentType]];
  693. WFCUMessageCellBase *cell = nil;
  694. if(![self.cellContentDict objectForKey:@([model.message.content.class getContentType])]) {
  695. cell = [collectionView dequeueReusableCellWithReuseIdentifier:[NSString stringWithFormat:@"%d", [WFCCUnknownMessageContent getContentType]] forIndexPath:indexPath];
  696. } else {
  697. cell = [collectionView dequeueReusableCellWithReuseIdentifier:objName forIndexPath:indexPath];
  698. }
  699. cell.delegate = self;
  700. [[NSNotificationCenter defaultCenter] removeObserver:cell];
  701. cell.model = model;
  702. return cell;
  703. }
  704. - (UICollectionReusableView *) collectionView:(UICollectionView *)collectionView viewForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath
  705. {
  706. if ([kind isEqualToString:UICollectionElementKindSectionHeader]) {
  707. if(!self.headerView) {
  708. self.headerView = [collectionView dequeueReusableSupplementaryViewOfKind:kind withReuseIdentifier:@"HeaderView" forIndexPath:indexPath];
  709. self.headerActivityView.center = CGPointMake(self.headerView.bounds.size.width/2, self.headerView.bounds.size.height/2);
  710. [self.headerView addSubview:self.headerActivityView];
  711. }
  712. return self.headerView;
  713. } else {
  714. if(!self.footerView) {
  715. self.footerView = [collectionView dequeueReusableSupplementaryViewOfKind:kind withReuseIdentifier:@"FooterView" forIndexPath:indexPath];
  716. self.footerActivityView.center = CGPointMake(self.footerView.bounds.size.width/2, self.footerView.bounds.size.height/2);
  717. [self.footerView addSubview:self.footerActivityView];
  718. }
  719. return self.footerView;
  720. }
  721. return nil;
  722. }
  723. #pragma mark - UICollectionViewDelegateFlowLayout
  724. - (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath {
  725. WFCUMessageModel *model = self.modelList[indexPath.row];
  726. Class cellCls = self.cellContentDict[@([[model.message.content class] getContentType])];
  727. if (!cellCls) {
  728. cellCls = self.cellContentDict[@([[WFCCUnknownMessageContent class] getContentType])];
  729. }
  730. return [cellCls sizeForCell:model withViewWidth:self.collectionView.frame.size.width];
  731. }
  732. #pragma mark - MessageCellDelegate
  733. - (void)didTapMessageCell:(WFCUMessageCellBase *)cell withModel:(WFCUMessageModel *)model {
  734. if ([model.message.content isKindOfClass:[WFCCImageMessageContent class]]) {
  735. self.imageMsgs = [[WFCCIMService sharedWFCIMService] getMessages:self.conversation contentTypes:@[@(MESSAGE_CONTENT_TYPE_IMAGE)] from:0 count:100 withUser:self.privateChatUser];
  736. SDPhotoBrowser *browser = [[SDPhotoBrowser alloc] init];
  737. browser.sourceImagesContainerView = self.backgroundView;
  738. browser.imageCount = self.imageMsgs.count;
  739. int i;
  740. for (i = 0; i < self.imageMsgs.count; i++) {
  741. if ([self.imageMsgs objectAtIndex:i].messageId == model.message.messageId) {
  742. break;
  743. }
  744. }
  745. if (i == self.imageMsgs.count) {
  746. i = 0;
  747. }
  748. browser.currentImageIndex = i;
  749. browser.delegate = self;
  750. [browser show]; // 展示图片浏览器
  751. } else if([model.message.content isKindOfClass:[WFCCSoundMessageContent class]]) {
  752. if (model.message.direction == MessageDirection_Receive && model.message.status != Message_Status_Played) {
  753. [[WFCCIMService sharedWFCIMService] setMediaMessagePlayed:model.message.messageId];
  754. model.message.status = Message_Status_Played;
  755. [self.collectionView reloadItemsAtIndexPaths:@[[self.collectionView indexPathForCell:cell]]];
  756. }
  757. [self prepardToPlay:model];
  758. } else if([model.message.content isKindOfClass:[WFCCLocationMessageContent class]]) {
  759. WFCCLocationMessageContent *locContent = (WFCCLocationMessageContent *)model.message.content;
  760. WFCULocationViewController *vc = [[WFCULocationViewController alloc] initWithLocationPoint:[[WFCULocationPoint alloc] initWithCoordinate:locContent.coordinate andTitle:locContent.title]];
  761. [self.navigationController pushViewController:vc animated:YES];
  762. } else if ([model.message.content isKindOfClass:[WFCCFileMessageContent class]]) {
  763. WFCCFileMessageContent *fileContent = (WFCCFileMessageContent *)model.message.content;
  764. WFCUBrowserViewController *bvc = [[WFCUBrowserViewController alloc] init];
  765. bvc.url = fileContent.remoteUrl;
  766. [self.navigationController pushViewController:bvc animated:YES];
  767. } else if ([model.message.content isKindOfClass:[WFCCCallStartMessageContent class]]) {
  768. WFCCCallStartMessageContent *callStartMsg = (WFCCCallStartMessageContent *)model.message.content;
  769. #if WFCU_SUPPORT_VOIP
  770. [self didTouchVideoBtn:callStartMsg.isAudioOnly];
  771. #endif
  772. } else if([model.message.content isKindOfClass:[WFCCVideoMessageContent class]]) {
  773. WFCCVideoMessageContent *videoMsg = (WFCCVideoMessageContent *)model.message.content;
  774. if (model.message.direction == MessageDirection_Receive && model.message.status != Message_Status_Played) {
  775. [[WFCCIMService sharedWFCIMService] setMediaMessagePlayed:model.message.messageId];
  776. model.message.status = Message_Status_Played;
  777. [self.collectionView reloadItemsAtIndexPaths:@[[self.collectionView indexPathForCell:cell]]];
  778. }
  779. if (videoMsg.localPath.length == 0) {
  780. model.mediaDownloading = YES;
  781. __weak typeof(self) weakSelf = self;
  782. [[WFCUMediaMessageDownloader sharedDownloader] tryDownload:model.message success:^(long long messageUid, NSString *localPath) {
  783. model.mediaDownloading = NO;
  784. [weakSelf startPlay:model];
  785. } error:^(long long messageUid, int error_code) {
  786. model.mediaDownloading = NO;
  787. }];
  788. } else {
  789. [self startPlay:model];
  790. }
  791. }
  792. }
  793. - (void)didTapMessagePortrait:(WFCUMessageCellBase *)cell withModel:(WFCUMessageModel *)model {
  794. WFCUProfileTableViewController *vc = [[WFCUProfileTableViewController alloc] init];
  795. vc.userInfo = [[WFCCIMService sharedWFCIMService] getUserInfo:model.message.fromUser refresh:NO];
  796. vc.hidesBottomBarWhenPushed = YES;
  797. [self.navigationController pushViewController:vc animated:YES];
  798. }
  799. - (void)didLongPressMessageCell:(WFCUMessageCellBase *)cell withModel:(WFCUMessageModel *)model {
  800. if ([cell isKindOfClass:[WFCUMessageCellBase class]]) {
  801. [self becomeFirstResponder];
  802. [self displayMenu:(WFCUMessageCellBase *)cell];
  803. }
  804. }
  805. - (void)didLongPressMessagePortrait:(WFCUMessageCellBase *)cell withModel:(WFCUMessageModel *)model {
  806. if (self.conversation.type == Group_Type) {
  807. if (model.message.direction == MessageDirection_Receive) {
  808. WFCCUserInfo *sender = [[WFCCIMService sharedWFCIMService] getUserInfo:model.message.fromUser refresh:NO];
  809. [self.chatInputBar appendMention:model.message.fromUser name:sender.displayName];
  810. }
  811. } else if(self.conversation.type == Channel_Type) {
  812. UIAlertController *alertController = [UIAlertController alertControllerWithTitle:nil message:nil preferredStyle:UIAlertControllerStyleActionSheet];
  813. UIAlertAction *cancelAction = [UIAlertAction actionWithTitle:@"取消" style:UIAlertActionStyleCancel handler:nil];
  814. UIAlertAction *okAction = [UIAlertAction actionWithTitle:@"与订阅者私聊" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
  815. if (model.message.direction == MessageDirection_Receive) {
  816. WFCCChannelInfo *channelInfo = [[WFCCIMService sharedWFCIMService] getChannelInfo:self.conversation.target refresh:NO];
  817. if ([channelInfo.owner isEqualToString:[WFCCNetworkService sharedInstance].userId]) {
  818. WFCUMessageListViewController *mvc = [[WFCUMessageListViewController alloc] init];
  819. mvc.conversation = [WFCCConversation conversationWithType:self.conversation.type target:self.conversation.target line:self.conversation.line];
  820. mvc.privateChatUser = model.message.fromUser;
  821. [self.navigationController pushViewController:mvc animated:YES];
  822. }
  823. }
  824. }];
  825. [alertController addAction:cancelAction];
  826. [alertController addAction:okAction];
  827. [self presentViewController:alertController animated:YES completion:nil];
  828. }
  829. }
  830. - (void)didTapResendBtn:(WFCUMessageModel *)model {
  831. NSInteger index = [self.modelList indexOfObject:model];
  832. if (index >= 0) {
  833. NSIndexPath *path = [NSIndexPath indexPathForRow:index inSection:0];
  834. [self.modelList removeObjectAtIndex:index];
  835. [self.collectionView deleteItemsAtIndexPaths:@[path]];
  836. [[WFCCIMService sharedWFCIMService] deleteMessage:model.message.messageId];
  837. [self sendMessage:model.message.content];
  838. }
  839. }
  840. - (void)didSelectUrl:(WFCUMessageCellBase *)cell withModel:(WFCUMessageModel *)model withUrl:(NSString *)urlString {
  841. WFCUBrowserViewController *bvc = [[WFCUBrowserViewController alloc] init];
  842. bvc.url = urlString;
  843. [self.navigationController pushViewController:bvc animated:YES];
  844. }
  845. - (void)didSelectPhoneNumber:(WFCUMessageCellBase *)cell withModel:(WFCUMessageModel *)model withPhoneNumber:(NSString *)phoneNumber {
  846. UIAlertController *alertController = [UIAlertController alertControllerWithTitle:[NSString stringWithFormat:@"我猜%@是一个电话号码", phoneNumber] message:nil preferredStyle:UIAlertControllerStyleActionSheet];
  847. UIAlertAction *cancelAction = [UIAlertAction actionWithTitle:@"取消" style:UIAlertActionStyleCancel handler:nil];
  848. UIAlertAction *callAction = [UIAlertAction actionWithTitle:@"呼叫" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
  849. NSURL *url = [[NSURL alloc] initWithString:[NSString stringWithFormat:@"telprompt:%@", phoneNumber]];
  850. [[UIApplication sharedApplication] openURL:url];
  851. }];
  852. UIAlertAction *copyAction = [UIAlertAction actionWithTitle:@"复制号码" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
  853. UIPasteboard *pasteboard = [UIPasteboard generalPasteboard];
  854. pasteboard.string = phoneNumber;
  855. }];
  856. // UIAlertAction *addContactAction = [UIAlertAction actionWithTitle:@"添加到通讯录" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
  857. //
  858. //
  859. // }];
  860. [alertController addAction:cancelAction];
  861. [alertController addAction:callAction];
  862. [alertController addAction:copyAction];
  863. // [alertController addAction:addContactAction];
  864. [self presentViewController:alertController animated:YES completion:nil];
  865. }
  866. #pragma mark - AVAudioPlayerDelegate
  867. - (void)audioPlayerDidFinishPlaying:(AVAudioPlayer *)player successfully:(BOOL)flag {
  868. NSLog(@"player finished");
  869. [self stopPlayer];
  870. }
  871. - (void)audioPlayerDecodeErrorDidOccur:(AVAudioPlayer *)player error:(NSError *)error {
  872. NSLog(@"player decode error");
  873. [[[UIAlertView alloc] initWithTitle:@"警告" message:@"网络错误" delegate:nil cancelButtonTitle:@"确定" otherButtonTitles:nil, nil] show];
  874. [self stopPlayer];
  875. }
  876. #pragma mark - UIScrollViewDelegate
  877. - (void)scrollViewDidScroll:(UIScrollView *)scrollView {
  878. }
  879. - (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView {
  880. [self.chatInputBar resetInputBarStatue];
  881. }
  882. - (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset {
  883. if (self.hasNewMessage && targetContentOffset->y == (scrollView.contentSize.height - scrollView.bounds.size.height)) {
  884. [self loadMoreMessage:NO];
  885. }
  886. if (targetContentOffset->y == 0 && self.hasMoreOld) {
  887. [self loadMoreMessage:YES];
  888. }
  889. }
  890. - (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate {
  891. }
  892. #pragma mark - ChatInputBarDelegate
  893. - (void)imageDidCapture:(UIImage *)capturedImage {
  894. if (!capturedImage) {
  895. return;
  896. }
  897. WFCCImageMessageContent *imgContent = [WFCCImageMessageContent contentFrom:capturedImage];
  898. [self sendMessage:imgContent];
  899. }
  900. - (void)videoDidCapture:(NSString *)videoPath thumbnail:(UIImage *)image duration:(long)duration {
  901. WFCCVideoMessageContent *videoContent = [WFCCVideoMessageContent contentPath:videoPath thumbnail:image];
  902. [self sendMessage:videoContent];
  903. }
  904. - (void)didTouchSend:(NSString *)stringContent withMentionInfos:(NSMutableArray<WFCUMetionInfo *> *)mentionInfos {
  905. if (stringContent.length == 0) {
  906. return;
  907. }
  908. WFCCTextMessageContent *txtContent = [[WFCCTextMessageContent alloc] init];
  909. txtContent.text = stringContent;
  910. NSMutableArray *mentionTargets = [[NSMutableArray alloc] init];
  911. for (WFCUMetionInfo *mentionInfo in mentionInfos) {
  912. if (mentionInfo.mentionType == 2) {
  913. txtContent.mentionedType = 2;
  914. mentionTargets = nil;
  915. break;
  916. } else if(mentionInfo.mentionType == 1) {
  917. txtContent.mentionedType = 1;
  918. [mentionTargets addObject:mentionInfo.target];
  919. }
  920. }
  921. if (txtContent.mentionedType == 1) {
  922. txtContent.mentionedTargets = [mentionTargets copy];
  923. }
  924. [self sendMessage:txtContent];
  925. }
  926. - (void)recordDidEnd:(NSString *)dataUri duration:(long)duration error:(NSError *)error {
  927. [self sendMessage:[WFCCSoundMessageContent soundMessageContentForWav:dataUri duration:duration]];
  928. }
  929. - (void)willChangeFrame:(CGRect)newFrame withDuration:(CGFloat)duration keyboardShowing:(BOOL)keyboardShowing {
  930. [UIView animateWithDuration:duration animations:^{
  931. CGRect frame = self.collectionView.frame;
  932. CGFloat diff = MIN(frame.size.height, self.collectionView.contentSize.height) - newFrame.origin.y;
  933. if(diff > 0) {
  934. frame.origin.y = -diff;
  935. self.collectionView.frame = frame;
  936. } else {
  937. self.collectionView.frame = CGRectMake(0, 0, self.backgroundView.bounds.size.width, newFrame.origin.y);
  938. }
  939. } completion:^(BOOL finished) {
  940. self.collectionView.frame = CGRectMake(0, 0, self.backgroundView.bounds.size.width, newFrame.origin.y);
  941. if (keyboardShowing) {
  942. [self scrollToBottom:NO];
  943. }
  944. }];
  945. }
  946. - (UINavigationController *)requireNavi {
  947. return self.navigationController;
  948. }
  949. - (void)locationDidSelect:(CLLocationCoordinate2D)location locationName:(NSString *)locationName mapScreenShot:(UIImage *)mapScreenShot {
  950. WFCCLocationMessageContent *content = [WFCCLocationMessageContent contentWith:location title:locationName thumbnail:mapScreenShot];
  951. [self sendMessage:content];
  952. }
  953. - (void)didSelectFiles:(NSArray *)files {
  954. for (NSString *file in files) {
  955. WFCCFileMessageContent *content = [WFCCFileMessageContent fileMessageContentFromPath:file];
  956. [self sendMessage:content];
  957. [NSThread sleepForTimeInterval:0.05];
  958. }
  959. }
  960. - (void)saveStickerRemoteUrl:(WFCCStickerMessageContent *)stickerContent {
  961. if (stickerContent.localPath.length && stickerContent.remoteUrl.length) {
  962. [[NSUserDefaults standardUserDefaults] setObject:stickerContent.remoteUrl forKey:[NSString stringWithFormat:@"sticker_remote_for_%ld", stickerContent.localPath.hash]];
  963. [[NSUserDefaults standardUserDefaults] synchronize];
  964. }
  965. }
  966. - (void)didSelectSticker:(NSString *)stickerPath {
  967. WFCCStickerMessageContent * content = [WFCCStickerMessageContent contentFrom:stickerPath];
  968. NSString *remoteUrl = [[NSUserDefaults standardUserDefaults] objectForKey:[NSString stringWithFormat:@"sticker_remote_for_%ld", stickerPath.hash]];
  969. content.remoteUrl = remoteUrl;
  970. [self sendMessage:content];
  971. }
  972. #if WFCU_SUPPORT_VOIP
  973. - (void)didTouchVideoBtn:(BOOL)isAudioOnly {
  974. if(self.conversation.type == Single_Type) {
  975. WFCUVideoViewController *videoVC = [[WFCUVideoViewController alloc] initWithTarget:self.conversation.target conversation:self.conversation audioOnly:isAudioOnly];
  976. [[WFAVEngineKit sharedEngineKit] presentViewController:videoVC];
  977. } else {
  978. WFCUContactListViewController *pvc = [[WFCUContactListViewController alloc] init];
  979. pvc.selectContact = YES;
  980. pvc.multiSelect = YES;
  981. NSMutableArray *disabledUser = [[NSMutableArray alloc] init];
  982. [disabledUser addObject:[WFCCNetworkService sharedInstance].userId];
  983. pvc.disableUsers = disabledUser;
  984. NSMutableArray *candidateUser = [[NSMutableArray alloc] init];
  985. NSArray<WFCCGroupMember *> *members = [[WFCCIMService sharedWFCIMService] getGroupMembers:self.conversation.target forceUpdate:NO];
  986. for (WFCCGroupMember *member in members) {
  987. [candidateUser addObject:member.memberId];
  988. }
  989. pvc.candidateUsers = candidateUser;
  990. __weak typeof(self)ws = self;
  991. pvc.selectResult = ^(NSArray<NSString *> *contacts) {
  992. if (contacts.count == 1) {
  993. WFCUVideoViewController *videoVC = [[WFCUVideoViewController alloc] initWithTarget:[contacts objectAtIndex:0] conversation:ws.conversation audioOnly:isAudioOnly];
  994. [[WFAVEngineKit sharedEngineKit] presentViewController:videoVC];
  995. }
  996. };
  997. pvc.disableUsersSelected = YES;
  998. UINavigationController *navi = [[UINavigationController alloc] initWithRootViewController:pvc];
  999. [self.navigationController presentViewController:navi animated:YES completion:nil];
  1000. }
  1001. }
  1002. #endif
  1003. - (void)onTyping:(WFCCTypingType)type {
  1004. if (self.conversation.type == Single_Type) {
  1005. [self sendMessage:[WFCCTypingMessageContent contentType:type]];
  1006. }
  1007. }
  1008. #pragma mark - SDPhotoBrowserDelegate
  1009. - (UIImage *)photoBrowser:(SDPhotoBrowser *)browser placeholderImageForIndex:(NSInteger)index {
  1010. WFCCMessage *msg = [self.imageMsgs objectAtIndex:index];
  1011. if ([[msg.content class] getContentType] == MESSAGE_CONTENT_TYPE_IMAGE) {
  1012. WFCCImageMessageContent *imgContent = (WFCCImageMessageContent *)msg.content;
  1013. return imgContent.thumbnail;
  1014. }
  1015. return nil;
  1016. }
  1017. - (NSURL *)photoBrowser:(SDPhotoBrowser *)browser highQualityImageURLForIndex:(NSInteger)index {
  1018. WFCCMessage *msg = [self.imageMsgs objectAtIndex:index];
  1019. if ([[msg.content class] getContentType] == MESSAGE_CONTENT_TYPE_IMAGE) {
  1020. WFCCImageMessageContent *imgContent = (WFCCImageMessageContent *)msg.content;
  1021. return [NSURL URLWithString:imgContent.remoteUrl];
  1022. }
  1023. return nil;
  1024. }
  1025. - (void)photoBrowserDidDismiss:(SDPhotoBrowser *)browser {
  1026. self.imageMsgs = nil;
  1027. }
  1028. #pragma mark - UIGestureRecognizerDelegate
  1029. - (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer {
  1030. return self.navigationController.childViewControllers.count > 1;
  1031. }
  1032. - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer {
  1033. return self.navigationController.viewControllers.count > 1;
  1034. }
  1035. #pragma mark - menu
  1036. - (void)displayMenu:(WFCUMessageCellBase *)baseCell {
  1037. UIMenuController *menu = [UIMenuController sharedMenuController];
  1038. UIMenuItem *deleteItem = [[UIMenuItem alloc]initWithTitle:@"删除" action:@selector(performDelete:)];
  1039. UIMenuItem *copyItem = [[UIMenuItem alloc]initWithTitle:@"复制" action:@selector(performCopy:)];
  1040. UIMenuItem *forwardItem = [[UIMenuItem alloc]initWithTitle:@"转发" action:@selector(performForward:)];
  1041. UIMenuItem *recallItem = [[UIMenuItem alloc]initWithTitle:@"撤回" action:@selector(performRecall:)];
  1042. CGRect menuPos;
  1043. if ([baseCell isKindOfClass:[WFCUMessageCell class]]) {
  1044. WFCUMessageCell *msgCell = (WFCUMessageCell *)baseCell;
  1045. menuPos = msgCell.bubbleView.frame;
  1046. } else {
  1047. menuPos = baseCell.frame;
  1048. }
  1049. [menu setTargetRect:menuPos inView:baseCell];
  1050. WFCCMessage *msg = baseCell.model.message;
  1051. NSMutableArray *items = [[NSMutableArray alloc] init];
  1052. [items addObject:deleteItem];
  1053. if ([msg.content isKindOfClass:[WFCCTextMessageContent class]]) {
  1054. [items addObject:copyItem];
  1055. }
  1056. if ([msg.content isKindOfClass:[WFCCImageMessageContent class]] ||
  1057. [msg.content isKindOfClass:[WFCCTextMessageContent class]] ||
  1058. [msg.content isKindOfClass:[WFCCLocationMessageContent class]] ||
  1059. [msg.content isKindOfClass:[WFCCFileMessageContent class]] ||
  1060. [msg.content isKindOfClass:[WFCCVideoMessageContent class]] ||
  1061. // [msg.content isKindOfClass:[WFCCSoundMessageContent class]] || //语音消息禁止转发,出于安全原因考虑,微信就禁止转发。如果您能确保安全,可以把这行注释打开
  1062. [msg.content isKindOfClass:[WFCCStickerMessageContent class]]) {
  1063. [items addObject:forwardItem];
  1064. }
  1065. BOOL canRecall = NO;
  1066. if ([baseCell isKindOfClass:[WFCUMessageCell class]] &&
  1067. msg.direction == MessageDirection_Send
  1068. ) {
  1069. NSDate *cur = [NSDate date];
  1070. if ([cur timeIntervalSince1970]*1000 - msg.serverTime < 60 * 1000) {
  1071. canRecall = YES;
  1072. }
  1073. }
  1074. if (!canRecall && self.conversation.type == Group_Type) {
  1075. WFCCGroupInfo *groupInfo = [[WFCCIMService sharedWFCIMService] getGroupInfo:self.conversation.target refresh:NO];
  1076. if([groupInfo.owner isEqualToString:[WFCCNetworkService sharedInstance].userId]) {
  1077. canRecall = YES;
  1078. if ([groupInfo.owner isEqualToString:msg.fromUser]) {
  1079. canRecall = NO;
  1080. }
  1081. } else {
  1082. __block BOOL isManager = false;
  1083. NSArray *memberList = [[WFCCIMService sharedWFCIMService] getGroupMembers:self.conversation.target forceUpdate:NO];
  1084. [memberList enumerateObjectsUsingBlock:^(WFCCGroupMember * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
  1085. if ([obj.memberId isEqualToString:[WFCCNetworkService sharedInstance].userId]) {
  1086. if (obj.type != Member_Type_Normal && ![msg.fromUser isEqualToString:obj.memberId]) {
  1087. isManager = YES;
  1088. }
  1089. *stop = YES;
  1090. }
  1091. }];
  1092. if(isManager && ![msg.fromUser isEqualToString:groupInfo.owner]) {
  1093. canRecall = YES;
  1094. }
  1095. }
  1096. }
  1097. if (canRecall) {
  1098. [items addObject:recallItem];
  1099. }
  1100. [menu setMenuItems:items];
  1101. self.cell4Menu = baseCell;
  1102. [menu setMenuVisible:YES];
  1103. }
  1104. -(BOOL)canBecomeFirstResponder {
  1105. return YES;
  1106. }
  1107. -(BOOL)canPerformAction:(SEL)action withSender:(id)sender {
  1108. if(self.cell4Menu) {
  1109. if (action == @selector(performDelete:) || action == @selector(performCopy:) || action == @selector(performForward:) || action == @selector(performRecall:)) {
  1110. return YES; //显示自定义的菜单项
  1111. } else {
  1112. return NO;
  1113. }
  1114. }
  1115. if (action == @selector(paste:)) {
  1116. UIPasteboard *pasteboard = [UIPasteboard generalPasteboard];
  1117. return pasteboard.string != nil;
  1118. }
  1119. return NO;//[super canPerformAction:action withSender:sender];
  1120. }
  1121. - (void)paste:(id)sender {
  1122. [self.chatInputBar paste:sender];
  1123. }
  1124. -(void)performDelete:(UIMenuController *)sender {
  1125. [[WFCCIMService sharedWFCIMService] deleteMessage:self.cell4Menu.model.message.messageId];
  1126. [self.modelList removeObject:self.cell4Menu.model];
  1127. [self.collectionView deleteItemsAtIndexPaths:@[[self.collectionView indexPathForCell:self.cell4Menu]]];
  1128. }
  1129. -(void)performCopy:(UIMenuItem *)sender {
  1130. if (self.cell4Menu) {
  1131. if ([self.cell4Menu.model.message.content isKindOfClass:[WFCCTextMessageContent class]]) {
  1132. UIPasteboard *pasteboard = [UIPasteboard generalPasteboard];
  1133. pasteboard.string = ((WFCCTextMessageContent *)self.cell4Menu.model.message.content).text;
  1134. }
  1135. }
  1136. }
  1137. -(void)performForward:(UIMenuItem *)sender {
  1138. if (self.cell4Menu) {
  1139. WFCUForwardViewController *controller = [[WFCUForwardViewController alloc] init];
  1140. controller.message = self.cell4Menu.model.message;
  1141. UINavigationController *navi = [[UINavigationController alloc] initWithRootViewController:controller];
  1142. [self.navigationController presentViewController:navi animated:YES completion:nil];
  1143. }
  1144. }
  1145. -(void)performRecall:(UIMenuItem *)sender {
  1146. if (self.cell4Menu.model.message) {
  1147. __block MBProgressHUD *hud = [MBProgressHUD showHUDAddedTo:self.view animated:YES];
  1148. hud.label.text = @"撤回中。。。";
  1149. [hud showAnimated:YES];
  1150. __weak typeof(self) ws = self;
  1151. [[WFCCIMService sharedWFCIMService] recall:self.cell4Menu.model.message success:^{
  1152. dispatch_async(dispatch_get_main_queue(), ^{
  1153. [hud hideAnimated:YES];
  1154. ws.cell4Menu.model.message = [[WFCCIMService sharedWFCIMService] getMessage:ws.cell4Menu.model.message.messageId];
  1155. [ws.collectionView reloadItemsAtIndexPaths:@[[ws.collectionView indexPathForCell:ws.cell4Menu]]];
  1156. });
  1157. } error:^(int error_code) {
  1158. dispatch_async(dispatch_get_main_queue(), ^{
  1159. hud.mode = MBProgressHUDModeText;
  1160. hud.label.text = @"撤回失败";
  1161. [hud hideAnimated:YES afterDelay:1.f];
  1162. });
  1163. }];
  1164. }
  1165. }
  1166. - (void)onMenuHidden:(id)sender {
  1167. UIMenuController *menu = [UIMenuController sharedMenuController];
  1168. [menu setMenuItems:nil];
  1169. __weak typeof(self)ws = self;
  1170. dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
  1171. ws.cell4Menu = nil;
  1172. });
  1173. }
  1174. - (void)dealloc {
  1175. [[NSNotificationCenter defaultCenter] removeObserver:self];
  1176. }
  1177. @end