TUIGroupChatViewController.m 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436
  1. //
  2. // TUIGroupChatViewController.m
  3. // TXIMSDK_TUIKit_iOS
  4. //
  5. // Created by kayev on 2021/6/17.
  6. // Copyright © 2023 Tencent. All rights reserved.
  7. //
  8. #import "ReactiveObjC/ReactiveObjC.h"
  9. #import <TIMCommon/NSString+TUIEmoji.h>
  10. #import <TIMCommon/TIMCommonModel.h>
  11. #import <TIMCommon/TIMDefine.h>
  12. #import <TUICore/NSDictionary+TUISafe.h>
  13. #import <TUICore/TUICore.h>
  14. #import <TUICore/TUILogin.h>
  15. #import "TUIBaseChatViewController+ProtectedAPI.h"
  16. #import "TUIGroupChatViewController.h"
  17. #import "TUIGroupPendencyController.h"
  18. #import "TUIGroupPendencyDataProvider.h"
  19. #import "TUILinkCellData.h"
  20. #import "TUIMessageDataProvider.h"
  21. #import "TUITextMessageCellData.h"
  22. #import "TUIChatFlexViewController.h"
  23. #import "TUIMessageController.h"
  24. #import "TUIGroupPinCell.h"
  25. #import "TUIGroupPinPageViewController.h"
  26. @interface TUIGroupChatViewController () <V2TIMGroupListener>
  27. //@property (nonatomic, strong) UIButton *atBtn;
  28. @property(nonatomic, strong) UIView *tipsView;
  29. @property(nonatomic, strong) UILabel *pendencyLabel;
  30. @property(nonatomic, strong) UIButton *pendencyBtn;
  31. @property(nonatomic, strong) TUIGroupPendencyDataProvider *pendencyViewModel;
  32. @property(nonatomic, strong) NSMutableArray<TUIUserModel *> *atUserList;
  33. @property(nonatomic, assign) BOOL responseKeyboard;
  34. @property(nonatomic, strong) TUIGroupPinCellView *oneGroupPinView;
  35. @property(nonatomic, strong) NSArray *groupPinList;
  36. @property(nonatomic, strong) TUIGroupPinPageViewController *pinPageVC;
  37. @end
  38. @implementation TUIGroupChatViewController
  39. - (void)viewDidLoad {
  40. [super viewDidLoad];
  41. // Do any additional setup after loading the view.
  42. [self setupTipsView];
  43. [self setupGroupPinTips];
  44. [[V2TIMManager sharedInstance] addGroupListener:self];
  45. [[NSNotificationCenter defaultCenter] addObserver:self
  46. selector:@selector(refreshTipsView)
  47. name:TUICore_TUIChatExtension_ChatViewTopArea_ChangedNotification
  48. object:nil];
  49. }
  50. - (void)viewWillAppear:(BOOL)animated {
  51. [super viewWillAppear:animated];
  52. [self refreshTipsView];
  53. }
  54. - (void)dealloc {
  55. [TUICore unRegisterEventByObject:self];
  56. }
  57. - (void)setupTipsView {
  58. self.tipsView = [[UIView alloc] initWithFrame:CGRectZero];
  59. self.tipsView.backgroundColor = RGB(246, 234, 190);
  60. [self.view addSubview:self.tipsView];
  61. self.tipsView.mm_height(24).mm_width(self.view.mm_w);
  62. self.pendencyLabel = [[UILabel alloc] initWithFrame:CGRectZero];
  63. [self.tipsView addSubview:self.pendencyLabel];
  64. self.pendencyLabel.font = [UIFont systemFontOfSize:12];
  65. self.pendencyBtn = [UIButton buttonWithType:UIButtonTypeSystem];
  66. [self.tipsView addSubview:self.pendencyBtn];
  67. [self.pendencyBtn setTitle:TIMCommonLocalizableString(TUIKitChatPendencyTitle) forState:UIControlStateNormal];
  68. [self.pendencyBtn.titleLabel setFont:[UIFont systemFontOfSize:12]];
  69. [self.pendencyBtn addTarget:self action:@selector(openPendency:) forControlEvents:UIControlEventTouchUpInside];
  70. [self.pendencyBtn sizeToFit];
  71. self.tipsView.alpha = 0;
  72. @weakify(self);
  73. [RACObserve(self.pendencyViewModel, unReadCnt) subscribeNext:^(NSNumber *unReadCnt) {
  74. @strongify(self);
  75. if ([unReadCnt intValue]) {
  76. self.pendencyLabel.text = [NSString stringWithFormat:TIMCommonLocalizableString(TUIKitChatPendencyRequestToJoinGroupFormat), unReadCnt];
  77. [self.pendencyLabel sizeToFit];
  78. CGFloat gap = (self.tipsView.mm_w - self.pendencyLabel.mm_w - self.pendencyBtn.mm_w - 8) / 2;
  79. self.pendencyLabel.mm_left(gap).mm__centerY(self.tipsView.mm_h / 2);
  80. self.pendencyBtn.mm_hstack(8);
  81. self.tipsView.alpha = 1;
  82. [self refreshTipsView];
  83. } else {
  84. self.tipsView.alpha = 0;
  85. }
  86. }];
  87. [self getPendencyList];
  88. }
  89. - (void)refreshTipsView {
  90. UIView *topView = [TUIGroupChatViewController topAreaBottomView];
  91. CGRect transRect = [topView convertRect:topView.bounds toView:self.view];
  92. self.tipsView.frame = CGRectMake(0, transRect.origin.y + transRect.size.height, self.tipsView.frame.size.width, self.tipsView.frame.size.height);
  93. }
  94. - (void)setupGroupPinTips {
  95. self.oneGroupPinView = [[TUIGroupPinCellView alloc] init];
  96. CGFloat margin = 0 ;
  97. self.oneGroupPinView.frame = CGRectMake(0, margin, self.view.frame.size.width, 62);
  98. UIView *topView = [TUIGroupChatViewController groupPinTopView];
  99. for (UIView *subview in topView.subviews) {
  100. [subview removeFromSuperview];
  101. }
  102. topView.frame = CGRectMake(0, 0, self.view.mm_w, 0);
  103. [topView addSubview:self.oneGroupPinView];
  104. @weakify(self);
  105. self.oneGroupPinView.isFirstPage = YES;
  106. self.oneGroupPinView.onClickCellView = ^(V2TIMMessage *originMessage) {
  107. @strongify(self);
  108. if (self.groupPinList.count >= 2) {
  109. [self gotoDetailPopPinPage];
  110. }
  111. else {
  112. [self jump2GroupPinHighlightLine:originMessage];
  113. }
  114. };
  115. self.oneGroupPinView.onClickRemove = ^(V2TIMMessage *originMessage) {
  116. @strongify(self);
  117. [self.messageController unPinGroupMessage:originMessage];
  118. };
  119. self.messageController.pinGroupMessageChanged = ^(NSArray * _Nonnull groupPinList) {
  120. @strongify(self);
  121. if (groupPinList.count > 0 ) {
  122. if (!self.oneGroupPinView.superview) {
  123. [topView addSubview:self.oneGroupPinView];
  124. }
  125. TUIMessageCellData * cellData = [TUIMessageDataProvider getCellData:[groupPinList lastObject]];
  126. [self.oneGroupPinView fillWithData:cellData];
  127. if (groupPinList.count >= 2) {
  128. [self.oneGroupPinView showMultiAnimation];
  129. topView.frame = CGRectMake(0, 0, self.view.mm_w, self.oneGroupPinView.frame.size.height +20 + margin);
  130. self.oneGroupPinView.removeButton.hidden = YES;
  131. }
  132. else {
  133. [self.oneGroupPinView hiddenMultiAnimation];
  134. topView.frame = CGRectMake(0, 0, self.view.mm_w, self.oneGroupPinView.frame.size.height + margin);
  135. if ([self.messageController isCurrentUserRoleSuperAdminInGroup]) {
  136. self.oneGroupPinView.removeButton.hidden = NO;
  137. }
  138. else {
  139. self.oneGroupPinView.removeButton.hidden = YES;
  140. }
  141. }
  142. }
  143. else {
  144. [self.oneGroupPinView removeFromSuperview];
  145. topView.frame = CGRectMake(0, 0, self.view.mm_w, 0);
  146. }
  147. self.groupPinList = groupPinList;
  148. [[NSNotificationCenter defaultCenter] postNotificationName:TUICore_TUIChatExtension_ChatViewTopArea_ChangedNotification object:nil];
  149. if (self.pinPageVC) {
  150. NSMutableArray *formatGroupPinList = [NSMutableArray arrayWithArray:groupPinList.reverseObjectEnumerator.allObjects];
  151. self.pinPageVC.groupPinList = formatGroupPinList;
  152. self.pinPageVC.canRemove = [self.messageController isCurrentUserRoleSuperAdminInGroup];
  153. if (groupPinList.count > 0) {
  154. [self reloadPopPinPage];
  155. }
  156. else {
  157. [self.pinPageVC dismissViewControllerAnimated:NO completion:nil];
  158. }
  159. }
  160. };
  161. self.messageController.groupRoleChanged = ^(V2TIMGroupMemberRole role) {
  162. @strongify(self);
  163. self.messageController.pinGroupMessageChanged(self.groupPinList);
  164. if (self.pinPageVC) {
  165. self.pinPageVC.canRemove = [self.messageController isCurrentUserRoleSuperAdminInGroup];
  166. [self.pinPageVC.tableview reloadData];
  167. }
  168. };
  169. }
  170. - (void)gotoDetailPopPinPage {
  171. TUIGroupPinPageViewController *vc = [[TUIGroupPinPageViewController alloc] init];
  172. self.pinPageVC = vc;
  173. NSMutableArray *formatGroupPinList = [NSMutableArray arrayWithArray:self.groupPinList.reverseObjectEnumerator.allObjects];
  174. vc.groupPinList = formatGroupPinList;
  175. vc.canRemove = [self.messageController isCurrentUserRoleSuperAdminInGroup];
  176. vc.view.frame = self.view.frame;
  177. CGFloat cellHight = (62);
  178. CGFloat maxOnePage = 4;
  179. float height = (self.groupPinList.count) * cellHight;
  180. height = MIN(cellHight * maxOnePage , height);
  181. UIView *topView = [TUIGroupChatViewController groupPinTopView];
  182. CGRect transRect = [topView convertRect:topView.bounds toView:[UIApplication sharedApplication].delegate.window];
  183. vc.modalPresentationStyle = UIModalPresentationOverFullScreen;
  184. @weakify(self);
  185. [self presentViewController:vc animated:NO completion:^{
  186. vc.tableview.frame = CGRectMake(0, CGRectGetMinY(transRect), self.view.frame.size.width, 60);
  187. vc.customArrowView.frame = CGRectMake(0, CGRectGetMaxY(vc.tableview.frame), vc.tableview.frame.size.width, 0);
  188. vc.bottomShadow.frame = CGRectMake(0, CGRectGetMaxY(vc.customArrowView.frame), vc.tableview.frame.size.width, 0);
  189. [UIView animateWithDuration:0.3 animations:^{
  190. vc.tableview.frame = CGRectMake(0, CGRectGetMinY(transRect), self.view.frame.size.width, height);
  191. vc.customArrowView.frame = CGRectMake(0, CGRectGetMaxY(vc.tableview.frame), vc.tableview.frame.size.width, 40);
  192. vc.bottomShadow.frame = CGRectMake(0, CGRectGetMaxY(vc.customArrowView.frame),
  193. vc.tableview.frame.size.width,
  194. self.view.frame.size.height);
  195. UIBezierPath *maskPath = [UIBezierPath bezierPathWithRoundedRect:vc.customArrowView.bounds
  196. byRoundingCorners:(UIRectCornerBottomLeft | UIRectCornerBottomRight)
  197. cornerRadii:CGSizeMake(10.0, 10.0)];
  198. CAShapeLayer *maskLayer = [[CAShapeLayer alloc] init];
  199. maskLayer.frame = vc.customArrowView.bounds;
  200. maskLayer.path = maskPath.CGPath;
  201. vc.customArrowView.layer.mask = maskLayer;
  202. }];
  203. }];
  204. vc.onClickRemove = ^(V2TIMMessage *originMessage) {
  205. @strongify(self);
  206. [self.messageController unPinGroupMessage:originMessage];
  207. };
  208. vc.onClickCellView = ^(V2TIMMessage *originMessage) {
  209. @strongify(self);
  210. [self jump2GroupPinHighlightLine:originMessage];
  211. };
  212. }
  213. - (void)jump2GroupPinHighlightLine:(V2TIMMessage *)originMessage {
  214. TUIMessageController *msgVC = (TUIMessageController *)self.messageController;
  215. NSString * originMsgID = originMessage.msgID;
  216. [msgVC findMessages:@[originMsgID ?: @""] callback:^(BOOL success, NSString * _Nonnull desc, NSArray<V2TIMMessage *> * _Nonnull messages) {
  217. if (success) {
  218. V2TIMMessage *message = messages.firstObject;
  219. if (message && message.status == V2TIM_MSG_STATUS_SEND_SUCC ) {
  220. [msgVC locateAssignMessage:originMessage matchKeyWord:@""];
  221. }
  222. else {
  223. [TUITool makeToast:TIMCommonLocalizableString(TUIKitReplyMessageNotFoundOriginMessage)];
  224. }
  225. }
  226. }];
  227. }
  228. - (void)reloadPopPinPage {
  229. CGFloat cellHight = (62);
  230. CGFloat maxOnePage = 4;
  231. float height = (self.groupPinList.count) * cellHight;
  232. height = MIN(cellHight * maxOnePage , height);
  233. self.pinPageVC.tableview.frame = CGRectMake(0, self.pinPageVC.tableview.frame.origin.y, self.view.frame.size.width, height);
  234. self.pinPageVC.customArrowView.frame = CGRectMake(0, CGRectGetMaxY(self.pinPageVC.tableview.frame), self.pinPageVC.tableview.frame.size.width, 40);
  235. self.pinPageVC.bottomShadow.frame = CGRectMake(0, CGRectGetMaxY(self.pinPageVC.customArrowView.frame),
  236. self.pinPageVC.tableview.frame.size.width,
  237. self.view.frame.size.height);
  238. [self.pinPageVC.tableview reloadData];
  239. }
  240. - (void)getPendencyList {
  241. if (self.conversationData.groupID.length > 0) [self.pendencyViewModel loadData];
  242. }
  243. - (void)openPendency:(id)sender {
  244. TUIGroupPendencyController *vc = [[TUIGroupPendencyController alloc] init];
  245. @weakify(self);
  246. vc.cellClickBlock = ^(TUIGroupPendencyCell *_Nonnull cell) {
  247. if (cell.pendencyData.isRejectd || cell.pendencyData.isAccepted) {
  248. // After selecting, you will no longer enter the details page.
  249. return;
  250. }
  251. @strongify(self);
  252. [[V2TIMManager sharedInstance] getUsersInfo:@[ cell.pendencyData.fromUser ]
  253. succ:^(NSArray<V2TIMUserFullInfo *> *profiles) {
  254. // Show user profile VC
  255. NSDictionary *param = @{
  256. TUICore_TUIContactObjectFactory_UserProfileController_UserProfile : profiles.firstObject,
  257. TUICore_TUIContactObjectFactory_UserProfileController_PendencyData : cell.pendencyData,
  258. TUICore_TUIContactObjectFactory_UserProfileController_ActionType : @(3)
  259. };
  260. [self.navigationController pushViewController:TUICore_TUIContactObjectFactory_UserProfileController_Classic
  261. param:param
  262. forResult:nil];
  263. }
  264. fail:nil];
  265. };
  266. vc.viewModel = self.pendencyViewModel;
  267. [self.navigationController pushViewController:vc animated:YES];
  268. }
  269. - (void)setConversationData:(TUIChatConversationModel *)conversationData {
  270. [super setConversationData:conversationData];
  271. if (self.conversationData.groupID.length > 0) {
  272. _pendencyViewModel = [TUIGroupPendencyDataProvider new];
  273. _pendencyViewModel.groupId = conversationData.groupID;
  274. }
  275. self.atUserList = [NSMutableArray array];
  276. }
  277. #pragma mark - V2TIMGroupListener
  278. - (void)onReceiveJoinApplication:(NSString *)groupID member:(V2TIMGroupMemberInfo *)member opReason:(NSString *)opReason {
  279. [self getPendencyList];
  280. }
  281. - (void)onGroupInfoChanged:(NSString *)groupID changeInfoList:(NSArray<V2TIMGroupChangeInfo *> *)changeInfoList {
  282. if (![groupID isEqualToString:self.conversationData.groupID]) {
  283. return;
  284. }
  285. for (V2TIMGroupChangeInfo *changeInfo in changeInfoList) {
  286. if (changeInfo.type == V2TIM_GROUP_INFO_CHANGE_TYPE_NAME) {
  287. self.conversationData.title = changeInfo.value;
  288. return;
  289. }
  290. }
  291. }
  292. #pragma mark - TUIInputControllerDelegate
  293. - (void)inputController:(TUIInputController *)inputController didSendMessage:(V2TIMMessage *)msg {
  294. /**
  295. * If the text message has @ user, createTextAtMessage is required
  296. */
  297. if (msg.elemType == V2TIM_ELEM_TYPE_TEXT) {
  298. NSMutableArray *atUserList = [NSMutableArray array];
  299. for (TUIUserModel *model in self.atUserList) {
  300. if (model.userId) {
  301. [atUserList addObject:model.userId];
  302. }
  303. }
  304. if (atUserList.count > 0) {
  305. NSData *cloudCustomData = msg.cloudCustomData;
  306. msg = [[V2TIMManager sharedInstance] createTextAtMessage:msg.textElem.text atUserList:atUserList];
  307. msg.cloudCustomData = cloudCustomData;
  308. }
  309. /**
  310. * After the message is sent, the atUserList need to be reset
  311. */
  312. [self.atUserList removeAllObjects];
  313. }
  314. [super inputController:inputController didSendMessage:msg];
  315. }
  316. - (void)inputControllerDidInputAt:(TUIInputController *)inputController {
  317. [super inputControllerDidInputAt:inputController];
  318. /**
  319. * Input of @ character detected
  320. */
  321. if (self.conversationData.groupID.length > 0) {
  322. if ([self.navigationController.topViewController isKindOfClass:NSClassFromString(@"TUISelectGroupMemberViewController")]) {
  323. return;
  324. }
  325. //When pushing a new VC, the keyboard needs to be hidden.
  326. [self.inputController reset];
  327. __weak typeof(self) weakSelf = self;
  328. NSMutableDictionary *param = [NSMutableDictionary dictionary];
  329. param[TUICore_TUIContactObjectFactory_SelectGroupMemberVC_GroupID] = self.conversationData.groupID;
  330. param[TUICore_TUIContactObjectFactory_SelectGroupMemberVC_Name] = TIMCommonLocalizableString(TUIKitAtSelectMemberTitle);
  331. param[TUICore_TUIContactObjectFactory_SelectGroupMemberVC_OptionalStyle] = @(1);
  332. [self.navigationController
  333. pushViewController:TUICore_TUIContactObjectFactory_SelectGroupMemberVC_Classic
  334. param:param
  335. forResult:^(NSDictionary *_Nonnull param) {
  336. NSArray<TUIUserModel *> *modelList = [param tui_objectForKey:TUICore_TUIContactObjectFactory_SelectGroupMemberVC_ResultUserList
  337. asClass:NSArray.class];
  338. NSMutableString *atText = [[NSMutableString alloc] init];
  339. for (int i = 0; i < modelList.count; i++) {
  340. TUIUserModel *model = modelList[i];
  341. if (![model isKindOfClass:TUIUserModel.class]) {
  342. NSAssert(NO, @"Error data-type in modelList");
  343. continue;
  344. }
  345. [weakSelf.atUserList addObject:model];
  346. [atText appendString:[NSString stringWithFormat:@"@%@ ", model.name]];
  347. }
  348. NSAttributedString *spaceString = [[NSAttributedString alloc]
  349. initWithString:atText
  350. attributes:@{NSFontAttributeName : kTUIInputNoramlFont, NSForegroundColorAttributeName : kTUIInputNormalTextColor}];
  351. [weakSelf.inputController.inputBar addWordsToInputBar:spaceString ];
  352. }];
  353. }
  354. }
  355. - (void)inputController:(TUIInputController *)inputController didDeleteAt:(NSString *)atText {
  356. [super inputController:inputController didDeleteAt:atText];
  357. for (TUIUserModel *user in self.atUserList) {
  358. if ([atText rangeOfString:user.name].location != NSNotFound) {
  359. [self.atUserList removeObject:user];
  360. break;
  361. }
  362. }
  363. }
  364. - (void)inputController:(TUIInputController *)inputController didSelectMoreCell:(TUIInputMoreCell *)cell {
  365. [super inputController:inputController didSelectMoreCell:cell];
  366. }
  367. #pragma mark - TUIBaseMessageControllerDelegate
  368. - (void)messageController:(TUIBaseMessageController *)controller onLongSelectMessageAvatar:(TUIMessageCell *)cell {
  369. if (!cell || !cell.messageData || !cell.messageData.identifier) {
  370. return;
  371. }
  372. if ([cell.messageData.identifier isEqualToString:[TUILogin getUserID]]) {
  373. return;
  374. }
  375. BOOL atUserExist = NO;
  376. for (TUIUserModel *model in self.atUserList) {
  377. if ([model.userId isEqualToString:cell.messageData.identifier]) {
  378. atUserExist = YES;
  379. break;
  380. }
  381. }
  382. if (!atUserExist) {
  383. TUIUserModel *user = [[TUIUserModel alloc] init];
  384. user.userId = cell.messageData.identifier;
  385. user.name = cell.messageData.senderName;
  386. [self.atUserList addObject:user];
  387. NSString *nameString = [NSString stringWithFormat:@"@%@ ", user.name];
  388. UIFont *textFont = kTUIInputNoramlFont;
  389. NSAttributedString *spaceString = [[NSAttributedString alloc] initWithString:nameString attributes:@{NSFontAttributeName : textFont}];
  390. [self.inputController.inputBar addWordsToInputBar:spaceString ];
  391. }
  392. }
  393. #pragma mark - Override Methods
  394. - (NSString *)forwardTitleWithMyName:(NSString *)nameStr {
  395. return TIMCommonLocalizableString(TUIKitRelayGroupChatHistory);
  396. }
  397. @end