Files
featherVoice/TUIKit/TUIChat/UI_Minimalist/Chat/TUIGroupChatViewController_Minimalist.m
2025-08-08 10:49:36 +08:00

434 lines
20 KiB
Objective-C

//
// TUIGroupChatViewController.m
// TXIMSDK_TUIKit_iOS
//
// Created by kayev on 2021/6/17.
// Copyright © 2023 Tencent. All rights reserved.
//
#import "ReactiveObjC/ReactiveObjC.h"
#import <TIMCommon/NSString+TUIEmoji.h>
#import <TIMCommon/TIMCommonModel.h>
#import <TIMCommon/TIMDefine.h>
#import <TUICore/NSDictionary+TUISafe.h>
#import <TUICore/TUICore.h>
#import <TUICore/TUILogin.h>
#import "TUIBaseChatViewController_Minimalist+ProtectedAPI.h"
#import "TUIGroupChatViewController_Minimalist.h"
#import "TUIGroupPendencyController.h"
#import "TUIGroupPendencyDataProvider.h"
#import "TUILinkCellData.h"
#import "TUIMessageDataProvider.h"
#import "TUITextMessageCellData.h"
#import "TUIMessageController_Minimalist.h"
#import "TUIGroupPinCell.h"
#import "TUIGroupPinPageViewController.h"
@interface TUIGroupChatViewController_Minimalist () <V2TIMGroupListener>
//@property (nonatomic, strong) UIButton *atBtn;
@property(nonatomic, strong) UIView *tipsView;
@property(nonatomic, strong) UILabel *pendencyLabel;
@property(nonatomic, strong) UIButton *pendencyBtn;
@property(nonatomic, strong) TUIGroupPendencyDataProvider *pendencyViewModel;
@property(nonatomic, strong) NSMutableArray<TUIUserModel *> *atUserList;
@property(nonatomic, assign) BOOL responseKeyboard;
@property(nonatomic, strong) TUIGroupPinCellView *oneGroupPinView;
@property(nonatomic, strong) NSArray *groupPinList;
@property(nonatomic, strong) TUIGroupPinPageViewController *pinPageVC;
@end
@implementation TUIGroupChatViewController_Minimalist
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
[self setupTipsView];
[self setupGroupPinTips];
[[V2TIMManager sharedInstance] addGroupListener:self];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(refreshTipsView)
name:TUICore_TUIChatExtension_ChatViewTopArea_ChangedNotification
object:nil];
}
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
[self refreshTipsView];
}
- (void)dealloc {
[TUICore unRegisterEventByObject:self];
}
- (void)setupTipsView {
self.tipsView = [[UIView alloc] initWithFrame:CGRectZero];
self.tipsView.backgroundColor = RGB(246, 234, 190);
[self.view addSubview:self.tipsView];
self.tipsView.mm_height(24).mm_width(self.view.mm_w);
self.pendencyLabel = [[UILabel alloc] initWithFrame:CGRectZero];
[self.tipsView addSubview:self.pendencyLabel];
self.pendencyLabel.font = [UIFont systemFontOfSize:12];
self.pendencyBtn = [UIButton buttonWithType:UIButtonTypeSystem];
[self.tipsView addSubview:self.pendencyBtn];
[self.pendencyBtn setTitle:TIMCommonLocalizableString(TUIKitChatPendencyTitle) forState:UIControlStateNormal];
[self.pendencyBtn.titleLabel setFont:[UIFont systemFontOfSize:12]];
[self.pendencyBtn addTarget:self action:@selector(openPendency:) forControlEvents:UIControlEventTouchUpInside];
[self.pendencyBtn sizeToFit];
self.tipsView.alpha = 0;
@weakify(self);
[RACObserve(self.pendencyViewModel, unReadCnt) subscribeNext:^(NSNumber *unReadCnt) {
@strongify(self);
if ([unReadCnt intValue]) {
self.pendencyLabel.text = [NSString stringWithFormat:TIMCommonLocalizableString(TUIKitChatPendencyRequestToJoinGroupFormat), unReadCnt];
[self.pendencyLabel sizeToFit];
CGFloat gap = (self.tipsView.mm_w - self.pendencyLabel.mm_w - self.pendencyBtn.mm_w - 8) / 2;
self.pendencyLabel.mm_left(gap).mm__centerY(self.tipsView.mm_h / 2);
self.pendencyBtn.mm_hstack(8);
self.tipsView.alpha = 1;
[self refreshTipsView];
} else {
self.tipsView.alpha = 0;
}
}];
[self getPendencyList];
}
- (void)refreshTipsView {
UIView *topView = [TUIGroupChatViewController_Minimalist topAreaBottomView];
CGRect transRect = [topView convertRect:topView.bounds toView:self.view];
self.tipsView.frame = CGRectMake(0, transRect.origin.y + transRect.size.height, self.tipsView.frame.size.width, self.tipsView.frame.size.height);
}
- (void)setupGroupPinTips {
self.oneGroupPinView = [[TUIGroupPinCellView alloc] init];
CGFloat margin = 0 ;
self.oneGroupPinView.frame = CGRectMake(0, margin, self.view.frame.size.width, 62);
UIView *topView = [TUIGroupChatViewController_Minimalist groupPinTopView];
for (UIView *subview in topView.subviews) {
[subview removeFromSuperview];
}
topView.frame = CGRectMake(0, 0, self.view.mm_w, 0);
[topView addSubview:self.oneGroupPinView];
@weakify(self);
self.oneGroupPinView.isFirstPage = YES;
self.oneGroupPinView.onClickCellView = ^(V2TIMMessage *originMessage) {
@strongify(self);
if (self.groupPinList.count >= 2) {
[self gotoDetailPopPinPage];
}
else {
[self jump2GroupPinHighlightLine:originMessage];
}
};
self.oneGroupPinView.onClickRemove = ^(V2TIMMessage *originMessage) {
@strongify(self);
[self.messageController unPinGroupMessage:originMessage];
};
self.messageController.pinGroupMessageChanged = ^(NSArray * _Nonnull groupPinList) {
@strongify(self);
if (groupPinList.count > 0 ) {
if (!self.oneGroupPinView.superview) {
[topView addSubview:self.oneGroupPinView];
}
TUIMessageCellData * cellData = [TUIMessageDataProvider getCellData:[groupPinList lastObject]];
[self.oneGroupPinView fillWithData:cellData];
if (groupPinList.count >= 2) {
[self.oneGroupPinView showMultiAnimation];
topView.frame = CGRectMake(0, 0, self.view.mm_w, self.oneGroupPinView.frame.size.height +20 + margin);
self.oneGroupPinView.removeButton.hidden = YES;
}
else {
[self.oneGroupPinView hiddenMultiAnimation];
topView.frame = CGRectMake(0, 0, self.view.mm_w, self.oneGroupPinView.frame.size.height + margin);
if ([self.messageController isCurrentUserRoleSuperAdminInGroup]) {
self.oneGroupPinView.removeButton.hidden = NO;
}
else {
self.oneGroupPinView.removeButton.hidden = YES;
}
}
}
else {
[self.oneGroupPinView removeFromSuperview];
topView.frame = CGRectMake(0, 0, self.view.mm_w, 0);
}
self.groupPinList = groupPinList;
[[NSNotificationCenter defaultCenter] postNotificationName:TUICore_TUIChatExtension_ChatViewTopArea_ChangedNotification object:nil];
if (self.pinPageVC) {
NSMutableArray *formatGroupPinList = [NSMutableArray arrayWithArray:groupPinList.reverseObjectEnumerator.allObjects];
self.pinPageVC.groupPinList = formatGroupPinList;
self.pinPageVC.canRemove = [self.messageController isCurrentUserRoleSuperAdminInGroup];
if (groupPinList.count > 0) {
[self reloadPopPinPage];
}
else {
[self.pinPageVC dismissViewControllerAnimated:NO completion:nil];
}
}
};
self.messageController.groupRoleChanged = ^(V2TIMGroupMemberRole role) {
@strongify(self);
self.messageController.pinGroupMessageChanged(self.groupPinList);
if (self.pinPageVC) {
self.pinPageVC.canRemove = [self.messageController isCurrentUserRoleSuperAdminInGroup];
[self.pinPageVC.tableview reloadData];
}
};
}
- (void)gotoDetailPopPinPage {
TUIGroupPinPageViewController *vc = [[TUIGroupPinPageViewController alloc] init];
self.pinPageVC = vc;
NSMutableArray *formatGroupPinList = [NSMutableArray arrayWithArray:self.groupPinList.reverseObjectEnumerator.allObjects];
vc.groupPinList = formatGroupPinList;
vc.canRemove = [self.messageController isCurrentUserRoleSuperAdminInGroup];
vc.view.frame = self.view.frame;
CGFloat cellHight = (62);
CGFloat maxOnePage = 4;
float height = (self.groupPinList.count) * cellHight;
height = MIN(cellHight * maxOnePage , height);
UIView *topView = [TUIGroupChatViewController_Minimalist groupPinTopView];
CGRect transRect = [topView convertRect:topView.bounds toView:[UIApplication sharedApplication].delegate.window];
vc.modalPresentationStyle = UIModalPresentationOverFullScreen;
@weakify(self);
[self presentViewController:vc animated:NO completion:^{
vc.tableview.frame = CGRectMake(0, CGRectGetMinY(transRect), self.view.frame.size.width, 60);
vc.customArrowView.frame = CGRectMake(0, CGRectGetMaxY(vc.tableview.frame), vc.tableview.frame.size.width, 0);
vc.bottomShadow.frame = CGRectMake(0, CGRectGetMaxY(vc.customArrowView.frame), vc.tableview.frame.size.width, 0);
[UIView animateWithDuration:0.3 animations:^{
vc.tableview.frame = CGRectMake(0, CGRectGetMinY(transRect), self.view.frame.size.width, height);
vc.customArrowView.frame = CGRectMake(0, CGRectGetMaxY(vc.tableview.frame), vc.tableview.frame.size.width, 40);
vc.bottomShadow.frame = CGRectMake(0, CGRectGetMaxY(vc.customArrowView.frame),
vc.tableview.frame.size.width,
self.view.frame.size.height);
UIBezierPath *maskPath = [UIBezierPath bezierPathWithRoundedRect:vc.customArrowView.bounds
byRoundingCorners:(UIRectCornerBottomLeft | UIRectCornerBottomRight)
cornerRadii:CGSizeMake(10.0, 10.0)];
CAShapeLayer *maskLayer = [[CAShapeLayer alloc] init];
maskLayer.frame = vc.customArrowView.bounds;
maskLayer.path = maskPath.CGPath;
vc.customArrowView.layer.mask = maskLayer;
}];
}];
vc.onClickRemove = ^(V2TIMMessage *originMessage) {
@strongify(self);
[self.messageController unPinGroupMessage:originMessage];
};
vc.onClickCellView = ^(V2TIMMessage *originMessage) {
@strongify(self);
[self jump2GroupPinHighlightLine:originMessage];
};
}
- (void)jump2GroupPinHighlightLine:(V2TIMMessage *)originMessage {
TUIMessageController_Minimalist *msgVC = (TUIMessageController_Minimalist *)self.messageController;
NSString * originMsgID = originMessage.msgID;
[msgVC findMessages:@[originMsgID ?: @""] callback:^(BOOL success, NSString * _Nonnull desc, NSArray<V2TIMMessage *> * _Nonnull messages) {
if (success) {
V2TIMMessage *message = messages.firstObject;
if (message && message.status == V2TIM_MSG_STATUS_SEND_SUCC ) {
[msgVC locateAssignMessage:originMessage matchKeyWord:@""];
}
else {
[TUITool makeToast:TIMCommonLocalizableString(TUIKitReplyMessageNotFoundOriginMessage)];
}
}
}];
}
- (void)reloadPopPinPage {
CGFloat cellHight = (62);
CGFloat maxOnePage = 4;
float height = (self.groupPinList.count) * cellHight;
height = MIN(cellHight * maxOnePage , height);
self.pinPageVC.tableview.frame = CGRectMake(0, self.pinPageVC.tableview.frame.origin.y, self.view.frame.size.width, height);
self.pinPageVC.customArrowView.frame = CGRectMake(0, CGRectGetMaxY(self.pinPageVC.tableview.frame), self.pinPageVC.tableview.frame.size.width, 40);
self.pinPageVC.bottomShadow.frame = CGRectMake(0, CGRectGetMaxY(self.pinPageVC.customArrowView.frame),
self.pinPageVC.tableview.frame.size.width,
self.view.frame.size.height);
[self.pinPageVC.tableview reloadData];
}
- (void)getPendencyList {
if (self.conversationData.groupID.length > 0) [self.pendencyViewModel loadData];
}
- (void)openPendency:(id)sender {
TUIGroupPendencyController *vc = [[TUIGroupPendencyController alloc] init];
@weakify(self);
vc.cellClickBlock = ^(TUIGroupPendencyCell *_Nonnull cell) {
if (cell.pendencyData.isRejectd || cell.pendencyData.isAccepted) {
// No longer enter the details page after selection
return;
}
@strongify(self);
[[V2TIMManager sharedInstance] getUsersInfo:@[ cell.pendencyData.fromUser ]
succ:^(NSArray<V2TIMUserFullInfo *> *profiles) {
// Show user profile VC
NSDictionary *param = @{
TUICore_TUIContactObjectFactory_UserProfileController_UserProfile : profiles.firstObject,
TUICore_TUIContactObjectFactory_UserProfileController_PendencyData : cell.pendencyData,
TUICore_TUIContactObjectFactory_UserProfileController_ActionType : @(3)
};
[self.navigationController pushViewController:TUICore_TUIContactObjectFactory_UserProfileController_Minimalist
param:param
forResult:nil];
}
fail:nil];
};
vc.viewModel = self.pendencyViewModel;
[self.navigationController pushViewController:vc animated:YES];
}
- (void)setConversationData:(TUIChatConversationModel *)conversationData {
[super setConversationData:conversationData];
if (self.conversationData.groupID.length > 0) {
_pendencyViewModel = [TUIGroupPendencyDataProvider new];
_pendencyViewModel.groupId = conversationData.groupID;
}
self.atUserList = [NSMutableArray array];
}
#pragma mark - V2TIMGroupListener
- (void)onReceiveJoinApplication:(NSString *)groupID member:(V2TIMGroupMemberInfo *)member opReason:(NSString *)opReason {
[self getPendencyList];
}
- (void)onGroupInfoChanged:(NSString *)groupID changeInfoList:(NSArray<V2TIMGroupChangeInfo *> *)changeInfoList {
if (![groupID isEqualToString:self.conversationData.groupID]) {
return;
}
for (V2TIMGroupChangeInfo *changeInfo in changeInfoList) {
if (changeInfo.type == V2TIM_GROUP_INFO_CHANGE_TYPE_NAME) {
self.conversationData.title = changeInfo.value;
} else if (changeInfo.type == V2TIM_GROUP_INFO_CHANGE_TYPE_FACE) {
self.conversationData.faceUrl = changeInfo.value;
}
}
}
#pragma mark - TUIInputControllerDelegate
- (void)inputController:(TUIInputController_Minimalist *)inputController didSendMessage:(V2TIMMessage *)msg {
/**
* If the text message has @ user, createTextAtMessage is required
*/
if (msg.elemType == V2TIM_ELEM_TYPE_TEXT) {
NSMutableArray *atUserList = [NSMutableArray array];
for (TUIUserModel *model in self.atUserList) {
if (model.userId) {
[atUserList addObject:model.userId];
}
}
if (atUserList.count > 0) {
NSData *cloudCustomData = msg.cloudCustomData;
msg = [[V2TIMManager sharedInstance] createTextAtMessage:msg.textElem.text atUserList:atUserList];
msg.cloudCustomData = cloudCustomData;
}
/**
* After the message is sent, the atUserList need to be reset
*/
[self.atUserList removeAllObjects];
}
[super inputController:inputController didSendMessage:msg];
}
- (void)inputControllerDidInputAt:(TUIInputController_Minimalist *)inputController {
[super inputControllerDidInputAt:inputController];
/**
* Input of @ character detected
*/
if (self.conversationData.groupID.length > 0) {
if ([self.navigationController.topViewController isKindOfClass:NSClassFromString(@"TUISelectGroupMemberViewController_Minimalist")]) {
return;
}
//When pushing a new VC, the keyboard needs to be hidden.
[self.inputController reset];
__weak typeof(self) weakSelf = self;
NSMutableDictionary *param = [NSMutableDictionary dictionary];
param[TUICore_TUIContactObjectFactory_SelectGroupMemberVC_GroupID] = self.conversationData.groupID;
param[TUICore_TUIContactObjectFactory_SelectGroupMemberVC_Name] = TIMCommonLocalizableString(TUIKitAtSelectMemberTitle);
param[TUICore_TUIContactObjectFactory_SelectGroupMemberVC_OptionalStyle] = @(1);
[self.navigationController
pushViewController:TUICore_TUIContactObjectFactory_SelectGroupMemberVC_Minimalist
param:param
forResult:^(NSDictionary *_Nonnull param) {
NSArray<TUIUserModel *> *modelList = [param tui_objectForKey:TUICore_TUIContactObjectFactory_SelectGroupMemberVC_ResultUserList
asClass:NSArray.class];
NSMutableString *atText = [[NSMutableString alloc] init];
for (int i = 0; i < modelList.count; i++) {
TUIUserModel *model = modelList[i];
if (![model isKindOfClass:TUIUserModel.class]) {
NSAssert(NO, @"Error data-type in modelList");
continue;
}
[self.atUserList addObject:model];
[atText appendString:[NSString stringWithFormat:@"@%@ ", model.name]];
}
UIFont *textFont = kTUIInputNoramlFont;
NSAttributedString *spaceString = [[NSAttributedString alloc] initWithString:atText attributes:@{NSFontAttributeName : textFont}];
[weakSelf.inputController.inputBar addWordsToInputBar:spaceString ];
}];
}
}
- (void)inputController:(TUIInputController_Minimalist *)inputController didDeleteAt:(NSString *)atText {
[super inputController:inputController didDeleteAt:atText];
for (TUIUserModel *user in self.atUserList) {
if ([atText rangeOfString:user.name].location != NSNotFound) {
[self.atUserList removeObject:user];
break;
}
}
}
#pragma mark - TUIBaseMessageControllerDelegate
- (void)messageController:(TUIBaseMessageController_Minimalist *)controller onLongSelectMessageAvatar:(TUIMessageCell *)cell {
if (!cell || !cell.messageData || !cell.messageData.identifier) {
return;
}
if ([cell.messageData.identifier isEqualToString:[TUILogin getUserID]]) {
return;
}
BOOL atUserExist = NO;
for (TUIUserModel *model in self.atUserList) {
if ([model.userId isEqualToString:cell.messageData.identifier]) {
atUserExist = YES;
break;
}
}
if (!atUserExist) {
TUIUserModel *user = [[TUIUserModel alloc] init];
user.userId = cell.messageData.identifier;
user.name = cell.messageData.senderName;
[self.atUserList addObject:user];
NSString *nameString = [NSString stringWithFormat:@"@%@ ", user.name];
UIFont *textFont = kTUIInputNoramlFont;
NSAttributedString *spaceString = [[NSAttributedString alloc] initWithString:nameString attributes:@{NSFontAttributeName : textFont}];
[self.inputController.inputBar addWordsToInputBar:spaceString ];
}
}
#pragma mark - Override Methods
- (NSString *)forwardTitleWithMyName:(NSString *)nameStr {
return TIMCommonLocalizableString(TUIKitRelayGroupChatHistory);
}
@end