提交
This commit is contained in:
@@ -0,0 +1,19 @@
|
||||
//
|
||||
// TUIBaseChatViewController+ProtectedAPI.h
|
||||
// TXIMSDK_TUIKit_iOS
|
||||
//
|
||||
// Created by kayev on 2021/6/17.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
//
|
||||
|
||||
#import <TIMCommon/TIMDefine.h>
|
||||
#import <TUICore/TUICore.h>
|
||||
#import "TUIBaseChatViewController.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface TUIBaseChatViewController () <TUIInputControllerDelegate, TUINotificationProtocol>
|
||||
- (NSString *)forwardTitleWithMyName:(NSString *)nameStr;
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
121
TUIKit/TUIChat/UI_Classic/Chat/TUIBaseChatViewController.h
Normal file
121
TUIKit/TUIChat/UI_Classic/Chat/TUIBaseChatViewController.h
Normal file
@@ -0,0 +1,121 @@
|
||||
|
||||
// Created by Tencent on 2023/06/09.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
/**
|
||||
*
|
||||
* Tencent Cloud Communication Service Interface Component TUIKIT - Chat Interface Component
|
||||
*
|
||||
* This document mainly declares the components used to implement the chat interface, which supports two modes of 1v1 single chat and group chat, including:
|
||||
* - Message display area: that is, the bubble display area.
|
||||
* - Message input area: that is, the part that allows users to input message text, emoticons, pictures and videos.
|
||||
*
|
||||
* The TUIBaseChatViewController class is used to implement the general controller of the chat view, which is responsible for unified control of input, message
|
||||
* controller, and more views. The classes and protocols declared in this file can effectively help you implement custom message formats.
|
||||
*
|
||||
*/
|
||||
#import <TIMCommon/TIMCommonModel.h>
|
||||
#import <UIKit/UIKit.h>
|
||||
#import "TUIBaseMessageController.h"
|
||||
#import "TUIChatConversationModel.h"
|
||||
#import "TUIInputController.h"
|
||||
@class TUIBaseChatViewController;
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// TUIBaseChatViewController
|
||||
//
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/**
|
||||
*
|
||||
* 【Module name】TUIBaseChatViewController
|
||||
* 【Function description】Responsible for implementing the UI components of the chat interface, including the message display area and the message input area.
|
||||
*
|
||||
* The TUIBaseChatViewController class is used to implement the overall controller of the chat view, and is responsible for unified control of the chat message
|
||||
* controller (TUIBaseMessageController), the information input controller (TUIInputController) and more views.
|
||||
*
|
||||
* The chat message controller is responsible for responding in the UI when you receive a new message or sending a message, and respond to your interactions on
|
||||
* the message bubble, see: Section\Chat\TUIBaseMessageController.h The information input controller is responsible for receiving your input, providing you with
|
||||
* the editing function of the input content and sending messages. For details, please refer to: Section\Chat\Input\TUIInputController.h This class contains the
|
||||
* "more" view, that is, when you click the "+" button in the UI, more buttons can be displayed to satisfy your further operations. For details, please refer
|
||||
* to: Section\Chat\TUIMoreView.h
|
||||
*
|
||||
* Q: How to implement custom messages?
|
||||
* A: If you want to implement a message style that TUIKit does not support, such as adding a voting link to the message style, you can refer to the
|
||||
* documentation: https://cloud.tencent.com/document/product/269/37067
|
||||
*/
|
||||
@interface TUIBaseChatViewController : UIViewController
|
||||
|
||||
@property(nonatomic, strong) TUIChatConversationModel *conversationData;
|
||||
|
||||
/**
|
||||
* Highlight text
|
||||
* In the search scenario, when highlightKeyword is not empty and matches @locateMessage, opening the chat session page will highlight the current cell
|
||||
*/
|
||||
@property(nonatomic, copy) NSString *highlightKeyword;
|
||||
|
||||
/**
|
||||
* Locate message
|
||||
* In the search scenario, when locateMessage is not empty, opening the chat session page will automatically scroll to here
|
||||
*/
|
||||
@property(nonatomic, strong) V2TIMMessage *locateMessage;
|
||||
|
||||
/**
|
||||
* View for displaying unread count number
|
||||
*/
|
||||
@property TUIUnReadView *unRead;
|
||||
|
||||
/**
|
||||
* TUIKit message controller
|
||||
* It is responsible for the display of message bubbles, and at the same time, it is responsible for responding to the user's interaction with the message
|
||||
* bubbles, such as: clicking on the avatar of the message sender, tapping the message, and long-pressing the message. For more information about the chat
|
||||
* message controller, please refer to TUIChat\UI\Chat\TUIBaseMessageController.h
|
||||
*/
|
||||
@property TUIBaseMessageController *messageController;
|
||||
|
||||
/**
|
||||
*
|
||||
* The custom container view at the bottom, which is usually used to add plugin subviews.
|
||||
*/
|
||||
@property (nonatomic, strong) UIView *bottomContainerView;
|
||||
|
||||
/**
|
||||
* TUIKit input controller
|
||||
* Responsible for receiving user input, and displaying the "+" button, voice input button, emoticon button, etc.
|
||||
* At the same time, TUIInputController integrates the message sending function, and you can directly use TUIInputController to collect and send message input.
|
||||
* Please refer to TUIChat\UI\Input\TUIInputController.h for details of the message input controller
|
||||
*/
|
||||
@property TUIInputController *inputController;
|
||||
|
||||
/**
|
||||
* Data group for more menu view data
|
||||
* More menu views include: Capture, Picture, Video, File. See Section\Chat\TUIMoreView.h for details
|
||||
*/
|
||||
@property NSArray<TUIInputMoreCellData *> *moreMenus;
|
||||
|
||||
/**
|
||||
* Need to scroll to the bottom
|
||||
* It is usually used to push to another VC and need to scroll to the bottom after returning.
|
||||
*/
|
||||
@property(nonatomic, assign) BOOL needScrollToBottom;
|
||||
|
||||
- (void)sendMessage:(V2TIMMessage *)message;
|
||||
|
||||
- (void)sendMessage:(V2TIMMessage *)message placeHolderCellData:(TUIMessageCellData *)placeHolderCellData;
|
||||
|
||||
/**
|
||||
* Add a custom view at the top of the chat interface. The view will stay at the top of the message list and will not slide up as the message list slides up.
|
||||
* If not set, it will not be displayed by default.
|
||||
*/
|
||||
+ (void)setCustomTopView:(UIView *)view;
|
||||
|
||||
/**
|
||||
* Get a custom view at the top of the chat interface.
|
||||
*/
|
||||
+ (UIView *)customTopView;
|
||||
|
||||
+ (UIView *)groupPinTopView;
|
||||
|
||||
+ (UIView *)topAreaBottomView;
|
||||
|
||||
@end
|
||||
1548
TUIKit/TUIChat/UI_Classic/Chat/TUIBaseChatViewController.m
Normal file
1548
TUIKit/TUIChat/UI_Classic/Chat/TUIBaseChatViewController.m
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,29 @@
|
||||
//
|
||||
// TUIBaseMessageController+ProtectedAPI.h
|
||||
// TXIMSDK_TUIKit_iOS
|
||||
//
|
||||
// Created by kayev on 2021/7/8.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
//
|
||||
|
||||
#import "TUIBaseMessageController.h"
|
||||
#import "TUIChatConversationModel.h"
|
||||
#import "TUIMessageDataProvider.h"
|
||||
|
||||
@class TUIMessageCellData;
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface TUIBaseMessageController () <TUIMessageBaseDataProviderDataSource>
|
||||
|
||||
@property(nonatomic, strong) TUIMessageDataProvider *messageDataProvider;
|
||||
@property(nonatomic, strong) TUIChatConversationModel *conversationData;
|
||||
@property(nonatomic, strong) UIActivityIndicatorView *indicatorView;
|
||||
|
||||
- (void)onNewMessage:(NSNotification *)notification;
|
||||
|
||||
- (void)onJumpToRepliesDetailPage:(TUIMessageCellData *)data;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
109
TUIKit/TUIChat/UI_Classic/Chat/TUIBaseMessageController.h
Normal file
109
TUIKit/TUIChat/UI_Classic/Chat/TUIBaseMessageController.h
Normal file
@@ -0,0 +1,109 @@
|
||||
|
||||
// Created by Tencent on 2023/06/09.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
/**
|
||||
*
|
||||
* This file declares the controller class used to implement the messaging logic
|
||||
* The message controller is responsible for uniformly displaying the messages you send/receive, while providing response callbacks when you interact with
|
||||
* those messages (tap/long-press, etc.). The message controller is also responsible for unified data processing of the messages you send into a data format
|
||||
* that can be sent through the IM SDK and sent. That is to say, when you use this controller, you can save a lot of data processing work, so that you can
|
||||
* access the IM SDK more quickly and conveniently.
|
||||
*/
|
||||
|
||||
#import <TIMCommon/TUIMessageCell.h>
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
#import "TUIBaseMessageControllerDelegate.h"
|
||||
#import "TUIChatConversationModel.h"
|
||||
#import "TUIChatDefine.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@class TUIConversationCellData;
|
||||
@class TUIBaseMessageController;
|
||||
@class TUIReplyMessageCell;
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// TUIBaseMessageController
|
||||
//
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/**
|
||||
*
|
||||
* 【Module name】TUIBaseMessageController
|
||||
* 【Function description】The message controller is responsible for implementing a series of business logic such as receiving, sending, and displaying
|
||||
* messages.
|
||||
* - This class provides callback interfaces for interactive operations such as receiving/displaying new messages, showing/hiding menus, and clicking on
|
||||
* message avatars.
|
||||
* - At the same time, this class provides the sending function of image, video, and file information, and directly integrates and calls the IM SDK to realize
|
||||
* the sending function.
|
||||
*
|
||||
*/
|
||||
@interface TUIBaseMessageController : UITableViewController
|
||||
|
||||
+ (void)asyncGetDisplayString:(NSArray<V2TIMMessage *> *)messageList callback:(void(^)(NSDictionary<NSString *, NSString *> *))callback;
|
||||
+ (nullable NSString *)getDisplayString:(V2TIMMessage *)message;
|
||||
|
||||
@property(nonatomic, weak) id<TUIBaseMessageControllerDelegate> delegate;
|
||||
|
||||
@property(nonatomic, assign) BOOL isInVC;
|
||||
|
||||
/**
|
||||
* Whether a read receipt is required to send a message, the default is NO
|
||||
*/
|
||||
@property(nonatomic) BOOL isMsgNeedReadReceipt;
|
||||
|
||||
@property(nonatomic, copy) void (^groupRoleChanged)(V2TIMGroupMemberRole role);
|
||||
|
||||
@property(nonatomic, copy) void (^pinGroupMessageChanged)(NSArray *groupPinList);
|
||||
|
||||
|
||||
- (void)sendMessage:(V2TIMMessage *)msg;
|
||||
|
||||
- (void)sendMessage:(V2TIMMessage *)msg placeHolderCellData:(TUIMessageCellData *)placeHolderCellData;
|
||||
|
||||
- (void)clearUImsg;
|
||||
|
||||
- (void)scrollToBottom:(BOOL)animate;
|
||||
|
||||
- (void)setConversation:(TUIChatConversationModel *)conversationData;
|
||||
|
||||
- (void)sendPlaceHolderUIMessage:(TUIMessageCellData *)cellData;
|
||||
/**
|
||||
*
|
||||
* After enabling multi-selection mode, get the currently selected result
|
||||
* Returns an empty array if multiple selection mode is off
|
||||
*/
|
||||
- (NSArray<TUIMessageCellData *> *)multiSelectedResult:(TUIMultiResultOption)option;
|
||||
- (void)enableMultiSelectedMode:(BOOL)enable;
|
||||
|
||||
- (void)deleteMessages:(NSArray<TUIMessageCellData *> *)uiMsgs;
|
||||
|
||||
/**
|
||||
* Conversation read report
|
||||
*
|
||||
*/
|
||||
- (void)readReport;
|
||||
|
||||
/**
|
||||
* Subclass implements click-to-reply messages
|
||||
*/
|
||||
- (void)showReplyMessage:(TUIReplyMessageCell *)cell;
|
||||
- (void)willShowMediaMessage:(TUIMessageCell *)cell;
|
||||
- (void)didCloseMediaMessage:(TUIMessageCell *)cell;
|
||||
|
||||
// Reload the specific cell UI.
|
||||
- (void)reloadCellOfMessage:(NSString *)messageID;
|
||||
- (void)reloadAndScrollToBottomOfMessage:(NSString *)messageID;
|
||||
- (void)scrollCellToBottomOfMessage:(NSString *)messageID;
|
||||
|
||||
- (void)loadGroupInfo;
|
||||
- (BOOL)isCurrentUserRoleSuperAdminInGroup;
|
||||
- (BOOL)isCurrentMessagePin:(NSString *)msgID;
|
||||
- (void)unPinGroupMessage:(V2TIMMessage *)innerMessage;
|
||||
|
||||
- (CGFloat)getHeightFromMessageCellData:(TUIMessageCellData *)cellData;
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
1930
TUIKit/TUIChat/UI_Classic/Chat/TUIBaseMessageController.m
Normal file
1930
TUIKit/TUIChat/UI_Classic/Chat/TUIBaseMessageController.m
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,125 @@
|
||||
|
||||
// Created by Tencent on 2023/06/09.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
@import ImSDK_Plus;
|
||||
|
||||
@class TUIBaseMessageController;
|
||||
@class TUIMessageCellData;
|
||||
@class TUIMessageCell;
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// TUIBaseMessageControllerDelegate
|
||||
//
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@protocol TUIBaseMessageControllerDelegate <NSObject>
|
||||
|
||||
/**
|
||||
* Callback for clicking controller
|
||||
* You can use this callback to: reset the InputController, dismiss the keyboard.
|
||||
*
|
||||
* @param controller Delegator, Message Controller
|
||||
*/
|
||||
- (void)didTapInMessageController:(TUIBaseMessageController *)controller;
|
||||
|
||||
/**
|
||||
* Callback after hide long press menu button
|
||||
* You can customize the implementation of this delegate function according to your needs.
|
||||
*
|
||||
* @param controller Delegator, Message Controller
|
||||
*/
|
||||
- (void)didHideMenuInMessageController:(TUIBaseMessageController *)controller;
|
||||
|
||||
|
||||
/**
|
||||
* Callback before hide long press menu button
|
||||
* You can customize the implementation of this delegate function according to your needs.
|
||||
*
|
||||
* @param controller Delegator, Message Controller
|
||||
* @param view The view where the controller is located
|
||||
*/
|
||||
- (BOOL)messageController:(TUIBaseMessageController *)controller willShowMenuInCell:(UIView *)view;
|
||||
|
||||
/**
|
||||
* Callback for receiving new message
|
||||
* You can use this callback to initialize a new message based on the incoming data and perform a new message reminder.
|
||||
*
|
||||
* @param controller Delegator, Message Controller
|
||||
* @param message Incoming new message
|
||||
*
|
||||
* @return Returns the new message unit that needs to be displayed.
|
||||
*/
|
||||
- (TUIMessageCellData *)messageController:(TUIBaseMessageController *)controller onNewMessage:(V2TIMMessage *)message;
|
||||
|
||||
/**
|
||||
* Callback for displaying new message
|
||||
* You can use this callback to initialize the message bubble based on the incoming data and display it
|
||||
*
|
||||
* @param controller Delegator, Message Controller
|
||||
* @param data Data needed to display
|
||||
*
|
||||
* @return Returns the new message unit that needs to be displayed.。
|
||||
*/
|
||||
- (TUIMessageCell *)messageController:(TUIBaseMessageController *)controller onShowMessageData:(TUIMessageCellData *)data;
|
||||
|
||||
/**
|
||||
* The callback the cell will be displayed with
|
||||
*/
|
||||
- (void)messageController:(TUIBaseMessageController *)controller willDisplayCell:(TUIMessageCell *)cell withData:(TUIMessageCellData *)cellData;
|
||||
|
||||
/**
|
||||
* Callback for clicking avatar in the message cell
|
||||
* You can use this callback to achieve: jump to the detailed information interface of the corresponding user.
|
||||
* 1. First pull user information, if the user is a friend of the current user, initialize the corresponding friend information interface and jump.
|
||||
* 2. If the user is not a friend of the current user, the corresponding interface for adding friends is initialized and a jump is performed.
|
||||
*
|
||||
*/
|
||||
- (void)messageController:(TUIBaseMessageController *)controller onSelectMessageAvatar:(TUIMessageCell *)cell;
|
||||
|
||||
/**
|
||||
* Callback for long pressing avatar in the message cell
|
||||
*/
|
||||
- (void)messageController:(TUIBaseMessageController *)controller onLongSelectMessageAvatar:(TUIMessageCell *)cell;
|
||||
|
||||
/**
|
||||
* Callback for clicking message content in the message cell
|
||||
*/
|
||||
- (void)messageController:(TUIBaseMessageController *)controller onSelectMessageContent:(TUIMessageCell *)cell;
|
||||
|
||||
/**
|
||||
* After long-pressing the message, the menu bar will pop up, and the callback after clicking the menu option
|
||||
* menuType: The type of menu that was clicked. 0 - multiple choice, 1 - forwarding.
|
||||
*/
|
||||
- (void)messageController:(TUIBaseMessageController *)controller onSelectMessageMenu:(NSInteger)menuType withData:(TUIMessageCellData *)data;
|
||||
|
||||
/**
|
||||
* Callback for about to reply to the message (usually triggered by long-pressing the message content and then clicking the reply button)
|
||||
*/
|
||||
- (void)messageController:(TUIBaseMessageController *)controller onRelyMessage:(TUIMessageCellData *)data;
|
||||
|
||||
/**
|
||||
* Callback for quoting message (triggered by long-pressing the message content and then clicking the quote button)
|
||||
*/
|
||||
- (void)messageController:(TUIBaseMessageController *)controller onReferenceMessage:(TUIMessageCellData *)data;
|
||||
|
||||
/**
|
||||
* Callback for re-editing message (usually for re-calling a message)
|
||||
*/
|
||||
- (void)messageController:(TUIBaseMessageController *)controller onReEditMessage:(TUIMessageCellData *)data;
|
||||
|
||||
/// Forward text.
|
||||
- (void)messageController:(TUIBaseMessageController *)controller onForwardText:(NSString *)text;
|
||||
|
||||
/**
|
||||
* Get the height of custom Tips (such as safety tips in Demo)
|
||||
*/
|
||||
- (CGFloat)getTopMarginByCustomView;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
17
TUIKit/TUIChat/UI_Classic/Chat/TUIC2CChatViewController.h
Normal file
17
TUIKit/TUIChat/UI_Classic/Chat/TUIC2CChatViewController.h
Normal file
@@ -0,0 +1,17 @@
|
||||
//
|
||||
// TUIC2CChatViewController.h
|
||||
// TXIMSDK_TUIKit_iOS
|
||||
//
|
||||
// Created by kayev on 2021/6/17.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
//
|
||||
|
||||
#import "TUIBaseChatViewController.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface TUIC2CChatViewController : TUIBaseChatViewController
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
159
TUIKit/TUIChat/UI_Classic/Chat/TUIC2CChatViewController.m
Normal file
159
TUIKit/TUIChat/UI_Classic/Chat/TUIC2CChatViewController.m
Normal file
@@ -0,0 +1,159 @@
|
||||
//
|
||||
// TUIC2CChatViewController.m
|
||||
// TXIMSDK_TUIKit_iOS
|
||||
//
|
||||
// Created by kayev on 2021/6/17.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
//
|
||||
|
||||
#import "TUIC2CChatViewController.h"
|
||||
#import "TUIBaseChatViewController+ProtectedAPI.h"
|
||||
#import "TUIChatConfig.h"
|
||||
#import "TUICloudCustomDataTypeCenter.h"
|
||||
#import "TUILinkCellData.h"
|
||||
#import "TUIMessageController.h"
|
||||
#import "TUIMessageDataProvider.h"
|
||||
|
||||
#define kC2CTypingTime 3000.0
|
||||
|
||||
@interface TUIC2CChatViewController ()
|
||||
|
||||
// If one sendTypingBaseCondation is satisfied, sendTypingBaseCondationInVC is used until the current session exits
|
||||
|
||||
@property(nonatomic, assign) BOOL sendTypingBaseCondationInVC;
|
||||
|
||||
@end
|
||||
|
||||
@implementation TUIC2CChatViewController
|
||||
|
||||
- (void)dealloc {
|
||||
self.sendTypingBaseCondationInVC = NO;
|
||||
}
|
||||
|
||||
- (void)viewDidLoad {
|
||||
[super viewDidLoad];
|
||||
// Do any additional setup after loading the view.
|
||||
self.sendTypingBaseCondationInVC = NO;
|
||||
self.view.backgroundColor = [UIColor clearColor];
|
||||
|
||||
// notify
|
||||
NSDictionary *param = @{TUICore_TUIChatNotify_ChatVC_ViewDidLoadSubKey_UserID: self.conversationData.userID ? : @""};
|
||||
[TUICore notifyEvent:TUICore_TUIChatNotify
|
||||
subKey:TUICore_TUIChatNotify_ChatVC_ViewDidLoadSubKey
|
||||
object:nil
|
||||
param:param];
|
||||
}
|
||||
|
||||
#pragma mark - Override Methods
|
||||
|
||||
- (void)inputControllerDidInputAt:(TUIInputController *)inputController {
|
||||
[super inputControllerDidInputAt:inputController];
|
||||
NSAttributedString *spaceString = [[NSAttributedString alloc]
|
||||
initWithString:@"@"
|
||||
attributes:@{NSFontAttributeName : kTUIInputNoramlFont, NSForegroundColorAttributeName : kTUIInputNormalTextColor}];
|
||||
[self.inputController.inputBar addWordsToInputBar:spaceString];
|
||||
}
|
||||
|
||||
- (NSString *)forwardTitleWithMyName:(NSString *)nameStr {
|
||||
NSString *title = [NSString stringWithFormat:TIMCommonLocalizableString(TUIKitRelayChatHistoryForSomebodyFormat), self.conversationData.title, nameStr];
|
||||
return rtlString(title);
|
||||
}
|
||||
|
||||
- (void)inputController:(TUIInputController *)inputController didSelectMoreCell:(TUIInputMoreCell *)cell {
|
||||
[super inputController:inputController didSelectMoreCell:cell];
|
||||
}
|
||||
|
||||
- (void)inputControllerBeginTyping:(TUIInputController *)inputController {
|
||||
[super inputControllerBeginTyping:inputController];
|
||||
|
||||
[self sendTypingMsgByStatus:YES];
|
||||
}
|
||||
|
||||
- (void)inputControllerEndTyping:(TUIInputController *)inputController {
|
||||
[super inputControllerEndTyping:inputController];
|
||||
|
||||
[self sendTypingMsgByStatus:NO];
|
||||
}
|
||||
|
||||
- (BOOL)sendTypingBaseCondation {
|
||||
if (self.sendTypingBaseCondationInVC) {
|
||||
return YES;
|
||||
}
|
||||
|
||||
if ([self.messageController isKindOfClass:TUIMessageController.class]) {
|
||||
TUIMessageController *vc = (TUIMessageController *)self.messageController;
|
||||
NSDictionary *messageFeatureDic = (id)[vc.C2CIncomingLastMsg parseCloudCustomData:messageFeature];
|
||||
|
||||
if (messageFeatureDic && [messageFeatureDic isKindOfClass:[NSDictionary class]] && [messageFeatureDic.allKeys containsObject:@"needTyping"] &&
|
||||
[messageFeatureDic.allKeys containsObject:@"version"]) {
|
||||
BOOL needTyping = NO;
|
||||
|
||||
BOOL versionControl = NO;
|
||||
|
||||
BOOL timeControl = NO;
|
||||
|
||||
if ([messageFeatureDic[@"needTyping"] intValue] == 1) {
|
||||
needTyping = YES;
|
||||
}
|
||||
|
||||
if ([messageFeatureDic[@"version"] intValue] == 1) {
|
||||
versionControl = YES;
|
||||
}
|
||||
|
||||
CFTimeInterval current = [NSDate.new timeIntervalSince1970];
|
||||
long currentTimeFloor = floor(current);
|
||||
long otherSideTimeFloor = floor([vc.C2CIncomingLastMsg.timestamp timeIntervalSince1970]);
|
||||
long interval = currentTimeFloor - otherSideTimeFloor;
|
||||
if (interval <= kC2CTypingTime) {
|
||||
timeControl = YES;
|
||||
}
|
||||
|
||||
if (needTyping && versionControl && timeControl) {
|
||||
self.sendTypingBaseCondationInVC = YES;
|
||||
return YES;
|
||||
}
|
||||
}
|
||||
}
|
||||
return NO;
|
||||
}
|
||||
- (void)sendTypingMsgByStatus:(BOOL)editing {
|
||||
// switch control
|
||||
if (![TUIChatConfig defaultConfig].enableTypingStatus) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (![self sendTypingBaseCondation]) {
|
||||
return;
|
||||
}
|
||||
|
||||
NSError *error = nil;
|
||||
NSDictionary *param = @{
|
||||
BussinessID : BussinessID_Typing,
|
||||
@"typingStatus" : editing ? @1 : @0,
|
||||
@"version" : @1,
|
||||
@"userAction" : @14,
|
||||
@"actionParam" : editing ? @"EIMAMSG_InputStatus_Ing" : @"EIMAMSG_InputStatus_End",
|
||||
};
|
||||
NSData *data = [NSJSONSerialization dataWithJSONObject:param options:0 error:&error];
|
||||
|
||||
V2TIMMessage *msg = [TUIMessageDataProvider getCustomMessageWithJsonData:data];
|
||||
[msg setIsExcludedFromContentModeration:YES];
|
||||
TUISendMessageAppendParams *appendParams = [[TUISendMessageAppendParams alloc] init];
|
||||
appendParams.isSendPushInfo = NO;
|
||||
appendParams.isOnlineUserOnly = YES;
|
||||
appendParams.priority = V2TIM_PRIORITY_DEFAULT;
|
||||
|
||||
[TUIMessageDataProvider sendMessage:msg
|
||||
toConversation:self.conversationData
|
||||
appendParams:appendParams
|
||||
Progress:^(uint32_t progress) {
|
||||
|
||||
}
|
||||
SuccBlock:^{
|
||||
NSLog(@"success");
|
||||
}
|
||||
FailBlock:^(int code, NSString *desc) {
|
||||
NSLog(@"Fail");
|
||||
}];
|
||||
}
|
||||
@end
|
||||
50
TUIKit/TUIChat/UI_Classic/Chat/TUIChatSmallTongueView.h
Normal file
50
TUIKit/TUIChat/UI_Classic/Chat/TUIChatSmallTongueView.h
Normal file
@@ -0,0 +1,50 @@
|
||||
//
|
||||
// TUIChatSmallTongue.h
|
||||
// TUIChat
|
||||
//
|
||||
// Created by xiangzhang on 2022/1/6.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
#import "TUIChatDefine.h"
|
||||
|
||||
@class TUIChatSmallTongue;
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@protocol TUIChatSmallTongueViewDelegate <NSObject>
|
||||
|
||||
- (void)onChatSmallTongueClick:(TUIChatSmallTongue *)tongue;
|
||||
|
||||
@end
|
||||
|
||||
@interface TUIChatSmallTongueView : UIView
|
||||
|
||||
@property(nonatomic, weak) id<TUIChatSmallTongueViewDelegate> delegate;
|
||||
|
||||
- (void)setTongue:(TUIChatSmallTongue *)tongue;
|
||||
|
||||
@end
|
||||
|
||||
@interface TUIChatSmallTongue : NSObject
|
||||
|
||||
@property(nonatomic, assign) TUIChatSmallTongueType type;
|
||||
@property(nonatomic, strong) UIView *parentView;
|
||||
@property(nonatomic, assign) NSInteger unreadMsgCount;
|
||||
@property(nonatomic, strong) NSString *atTipsStr;
|
||||
@property(nonatomic, strong) NSArray *atMsgSeqs;
|
||||
|
||||
@end
|
||||
|
||||
@interface TUIChatSmallTongueManager : NSObject
|
||||
|
||||
+ (void)showTongue:(TUIChatSmallTongue *)tongue delegate:(id<TUIChatSmallTongueViewDelegate>)delegate;
|
||||
+ (void)removeTongue:(TUIChatSmallTongueType)type;
|
||||
+ (void)removeTongue;
|
||||
+ (void)hideTongue:(BOOL)isHidden;
|
||||
+ (void)adaptTongueBottomMargin:(CGFloat)margin;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
230
TUIKit/TUIChat/UI_Classic/Chat/TUIChatSmallTongueView.m
Normal file
230
TUIKit/TUIChat/UI_Classic/Chat/TUIChatSmallTongueView.m
Normal file
@@ -0,0 +1,230 @@
|
||||
//
|
||||
// TUIChatSmallTongue.m
|
||||
// TUIChat
|
||||
//
|
||||
// Created by xiangzhang on 2022/1/6.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
//
|
||||
|
||||
#import "TUIChatSmallTongueView.h"
|
||||
#import <TUICore/NSString+TUIUtil.h>
|
||||
#import <TUICore/TUIDarkModel.h>
|
||||
#import <TUICore/TUIThemeManager.h>
|
||||
#import "TUIChatConfig.h"
|
||||
#import <TIMCommon/TIMDefine.h>
|
||||
#define TongueHeight 35.f
|
||||
#define TongueImageWidth 12.f
|
||||
#define TongueImageHeight 12.f
|
||||
#define TongueLeftSpace 10.f
|
||||
#define TongueMiddleSpace 5.f
|
||||
#define TongueRightSpace 10.f
|
||||
#define TongueFontSize 13
|
||||
|
||||
@interface TUIChatSmallTongueView ()
|
||||
|
||||
@property(nonatomic, strong) UIImageView *imageView;
|
||||
@property(nonatomic, strong) UILabel *label;
|
||||
|
||||
@end
|
||||
|
||||
@implementation TUIChatSmallTongueView {
|
||||
TUIChatSmallTongue *_tongue;
|
||||
}
|
||||
|
||||
+ (void)load {
|
||||
[NSNotificationCenter.defaultCenter addObserver:self selector:@selector(onThemeChanged:) name:TUIDidApplyingThemeChangedNotfication object:nil];
|
||||
}
|
||||
|
||||
- (instancetype)initWithFrame:(CGRect)frame {
|
||||
self = [super initWithFrame:frame];
|
||||
if (self) {
|
||||
self.backgroundColor = TUIChatDynamicColor(@"chat_small_tongue_bg_color", @"#FFFFFF");
|
||||
// border
|
||||
self.layer.borderWidth = 0.2;
|
||||
self.layer.borderColor = TUIChatDynamicColor(@"chat_small_tongue_line_color", @"#E5E5E5").CGColor;
|
||||
self.layer.cornerRadius = 2;
|
||||
self.layer.masksToBounds = YES;
|
||||
// shadow
|
||||
self.layer.shadowColor = RGBA(0, 0, 0, 0.15).CGColor;
|
||||
self.layer.shadowOpacity = 1;
|
||||
self.layer.shadowOffset = CGSizeMake(0, 0);
|
||||
self.layer.shadowRadius = 2;
|
||||
self.clipsToBounds = NO;
|
||||
// tap
|
||||
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(onTap)];
|
||||
[self addGestureRecognizer:tap];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)onTap {
|
||||
if (self.delegate && [self.delegate respondsToSelector:@selector(onChatSmallTongueClick:)]) {
|
||||
[self.delegate onChatSmallTongueClick:_tongue];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setTongue:(TUIChatSmallTongue *)tongue {
|
||||
_tongue = tongue;
|
||||
if (!self.imageView) {
|
||||
self.imageView = [[UIImageView alloc] init];
|
||||
[self addSubview:self.imageView];
|
||||
}
|
||||
self.imageView.image = [TUIChatSmallTongueView getTongueImage:tongue];
|
||||
|
||||
if (!self.label) {
|
||||
self.label = [[UILabel alloc] init];
|
||||
self.label.font = [UIFont systemFontOfSize:TongueFontSize];
|
||||
[self addSubview:self.label];
|
||||
}
|
||||
self.label.text = [TUIChatSmallTongueView getTongueText:tongue];
|
||||
self.label.rtlAlignment = TUITextRTLAlignmentLeading;
|
||||
self.label.textColor = TUIChatDynamicColor(@"chat_drop_down_color", @"#147AFF");
|
||||
[self.imageView mas_remakeConstraints:^(MASConstraintMaker *make) {
|
||||
make.width.height.mas_equalTo(TongueImageWidth);
|
||||
make.leading.mas_equalTo(TongueLeftSpace);
|
||||
make.top.mas_equalTo(10);
|
||||
}];
|
||||
[self.label mas_remakeConstraints:^(MASConstraintMaker *make) {
|
||||
make.trailing.mas_lessThanOrEqualTo(self.mas_trailing).mas_offset(-TongueRightSpace);
|
||||
make.height.mas_equalTo(TongueImageHeight);
|
||||
make.leading.mas_equalTo(self.imageView.mas_trailing).mas_offset(TongueMiddleSpace);
|
||||
make.top.mas_equalTo(10);
|
||||
}];
|
||||
}
|
||||
|
||||
+ (CGFloat)getTongueWidth:(TUIChatSmallTongue *)tongue {
|
||||
NSString *tongueText = [self getTongueText:tongue];
|
||||
CGSize titleSize = [tongueText boundingRectWithSize:CGSizeMake(MAXFLOAT, TongueHeight)
|
||||
options:NSStringDrawingUsesLineFragmentOrigin
|
||||
attributes:@{NSFontAttributeName : [UIFont systemFontOfSize:TongueFontSize]}
|
||||
context:nil]
|
||||
.size;
|
||||
CGFloat tongueWidth = TongueLeftSpace + TongueImageWidth + TongueMiddleSpace + ceil(titleSize.width) + TongueRightSpace;
|
||||
return tongueWidth;
|
||||
}
|
||||
|
||||
+ (NSString *)getTongueText:(TUIChatSmallTongue *)tongue {
|
||||
static NSMutableDictionary *titleCacheFormat;
|
||||
if (titleCacheFormat == nil) {
|
||||
titleCacheFormat = [NSMutableDictionary dictionary];
|
||||
[titleCacheFormat setObject:TIMCommonLocalizableString(TUIKitChatBackToLatestLocation) forKey:@(TUIChatSmallTongueType_ScrollToBoom)];
|
||||
[titleCacheFormat setObject:TIMCommonLocalizableString(TUIKitChatNewMessages) forKey:@(TUIChatSmallTongueType_ReceiveNewMsg)];
|
||||
}
|
||||
|
||||
if (tongue.type == TUIChatSmallTongueType_SomeoneAt) {
|
||||
NSString *atMeStr = TIMCommonLocalizableString(TUIKitConversationTipsAtMe);
|
||||
NSString *atAllStr = TIMCommonLocalizableString(TUIKitConversationTipsAtAll);
|
||||
if ([tongue.atTipsStr tui_containsString:atMeStr]) {
|
||||
atMeStr = [atMeStr stringByReplacingOccurrencesOfString:@"[" withString:@""];
|
||||
atMeStr = [atMeStr stringByReplacingOccurrencesOfString:@"]" withString:@""];
|
||||
[titleCacheFormat setObject:atMeStr forKey:@(TUIChatSmallTongueType_SomeoneAt)];
|
||||
} else if ([tongue.atTipsStr tui_containsString:atAllStr]) {
|
||||
atAllStr = [atAllStr stringByReplacingOccurrencesOfString:@"[" withString:@""];
|
||||
atAllStr = [atAllStr stringByReplacingOccurrencesOfString:@"]" withString:@""];
|
||||
[titleCacheFormat setObject:atAllStr forKey:@(TUIChatSmallTongueType_SomeoneAt)];
|
||||
}
|
||||
}
|
||||
|
||||
if (tongue.type == TUIChatSmallTongueType_ReceiveNewMsg) {
|
||||
return [NSString stringWithFormat:[titleCacheFormat objectForKey:@(TUIChatSmallTongueType_ReceiveNewMsg)],
|
||||
tongue.unreadMsgCount > 99 ? @"99+" : @(tongue.unreadMsgCount)];
|
||||
} else {
|
||||
return [titleCacheFormat objectForKey:@(tongue.type)];
|
||||
}
|
||||
}
|
||||
|
||||
static NSMutableDictionary *gImageCache;
|
||||
+ (UIImage *)getTongueImage:(TUIChatSmallTongue *)tongue {
|
||||
if (gImageCache == nil) {
|
||||
gImageCache = [NSMutableDictionary dictionary];
|
||||
[gImageCache setObject:TUIChatBundleThemeImage(@"chat_drop_down_img", @"drop_down") ?: UIImage.new forKey:@(TUIChatSmallTongueType_ScrollToBoom)];
|
||||
[gImageCache setObject:TUIChatBundleThemeImage(@"chat_drop_down_img", @"drop_down") ?: UIImage.new forKey:@(TUIChatSmallTongueType_ReceiveNewMsg)];
|
||||
[gImageCache setObject:TUIChatBundleThemeImage(@"chat_pull_up_img", @"pull_up") ?: UIImage.new forKey:@(TUIChatSmallTongueType_SomeoneAt)];
|
||||
}
|
||||
return [gImageCache objectForKey:@(tongue.type)];
|
||||
}
|
||||
|
||||
+ (void)onThemeChanged:(NSNotification *)notice {
|
||||
gImageCache = nil;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation TUIChatSmallTongue
|
||||
|
||||
- (instancetype)init {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
self.type = TUIChatSmallTongueType_None;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
static TUIChatSmallTongueView *gTongueView = nil;
|
||||
static TUIChatSmallTongue *gTongue = nil;
|
||||
static CGFloat gBottomMargin = 0;
|
||||
|
||||
@implementation TUIChatSmallTongueManager
|
||||
|
||||
+ (void)showTongue:(TUIChatSmallTongue *)tongue delegate:(id<TUIChatSmallTongueViewDelegate>)delegate {
|
||||
if (tongue.type == gTongue.type
|
||||
&& tongue.parentView == gTongue.parentView
|
||||
&& tongue.unreadMsgCount == gTongue.unreadMsgCount
|
||||
&& tongue.atMsgSeqs == gTongue.atMsgSeqs
|
||||
&& !gTongueView.hidden) {
|
||||
return;
|
||||
}
|
||||
gTongue = tongue;
|
||||
|
||||
if (!gTongueView) {
|
||||
gTongueView = [[TUIChatSmallTongueView alloc] init];
|
||||
} else {
|
||||
[gTongueView removeFromSuperview];
|
||||
}
|
||||
CGFloat tongueWidth = [TUIChatSmallTongueView getTongueWidth:gTongue];
|
||||
if(isRTL()) {
|
||||
gTongueView.frame =
|
||||
CGRectMake(16,
|
||||
tongue.parentView.mm_h - Bottom_SafeHeight - TTextView_Height - 20 - TongueHeight - gBottomMargin,
|
||||
tongueWidth, TongueHeight);
|
||||
}
|
||||
else {
|
||||
gTongueView.frame =
|
||||
CGRectMake(tongue.parentView.mm_w - tongueWidth - 16,
|
||||
tongue.parentView.mm_h - Bottom_SafeHeight - TTextView_Height - 20 - TongueHeight - gBottomMargin,
|
||||
tongueWidth, TongueHeight);
|
||||
}
|
||||
|
||||
gTongueView.delegate = delegate;
|
||||
[gTongueView setTongue:gTongue];
|
||||
[tongue.parentView addSubview:gTongueView];
|
||||
}
|
||||
|
||||
+ (void)removeTongue:(TUIChatSmallTongueType)type {
|
||||
if (type != gTongue.type) {
|
||||
return;
|
||||
}
|
||||
[self removeTongue];
|
||||
}
|
||||
|
||||
+ (void)removeTongue {
|
||||
gTongue = nil;
|
||||
if (gTongueView) {
|
||||
[gTongueView removeFromSuperview];
|
||||
gTongueView = nil;
|
||||
}
|
||||
}
|
||||
|
||||
+ (void)hideTongue:(BOOL)isHidden {
|
||||
if (gTongueView) {
|
||||
gTongueView.hidden = isHidden;
|
||||
}
|
||||
}
|
||||
|
||||
+ (void)adaptTongueBottomMargin:(CGFloat)margin {
|
||||
gBottomMargin = margin;
|
||||
}
|
||||
|
||||
@end
|
||||
13
TUIKit/TUIChat/UI_Classic/Chat/TUIFileViewController.h
Normal file
13
TUIKit/TUIChat/UI_Classic/Chat/TUIFileViewController.h
Normal file
@@ -0,0 +1,13 @@
|
||||
//
|
||||
// FileViewController.h
|
||||
// TUIKitDemo
|
||||
//
|
||||
// Created by kennethmiao on 2018/11/12.
|
||||
// Copyright © 2018 Tencent. All rights reserved.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
#import "TUIFileMessageCell.h"
|
||||
@interface TUIFileViewController : UIViewController
|
||||
@property(nonatomic, strong) TUIFileMessageCellData *data;
|
||||
@end
|
||||
117
TUIKit/TUIChat/UI_Classic/Chat/TUIFileViewController.m
Normal file
117
TUIKit/TUIChat/UI_Classic/Chat/TUIFileViewController.m
Normal file
@@ -0,0 +1,117 @@
|
||||
//
|
||||
// FileViewController.m
|
||||
// TUIKitDemo
|
||||
//
|
||||
// Created by kennethmiao on 2018/11/12.
|
||||
// Copyright © 2018 Tencent. All rights reserved.
|
||||
//
|
||||
|
||||
#import "TUIFileViewController.h"
|
||||
#import <QuickLook/QuickLook.h>
|
||||
#import <TIMCommon/TIMDefine.h>
|
||||
#import <TUICore/TUIGlobalization.h>
|
||||
#import <TUICore/TUIThemeManager.h>
|
||||
#import <TUICore/UIView+TUILayout.h>
|
||||
#import <TUICore/UIView+TUIToast.h>
|
||||
#import "ReactiveObjC/ReactiveObjC.h"
|
||||
|
||||
@interface TUIFileViewController () <UIDocumentInteractionControllerDelegate>
|
||||
@property(nonatomic, strong) UIImageView *image;
|
||||
@property(nonatomic, strong) UILabel *name;
|
||||
@property(nonatomic, strong) UILabel *progress;
|
||||
@property(nonatomic, strong) UIButton *button;
|
||||
@property(nonatomic, strong) UIDocumentInteractionController *document;
|
||||
@end
|
||||
|
||||
@implementation TUIFileViewController
|
||||
|
||||
- (void)viewDidLoad {
|
||||
[super viewDidLoad];
|
||||
self.view.backgroundColor = [UIColor whiteColor];
|
||||
|
||||
UILabel *titleLabel = [[UILabel alloc] init];
|
||||
titleLabel.text = TIMCommonLocalizableString(File);
|
||||
titleLabel.font = [UIFont boldSystemFontOfSize:17.0];
|
||||
titleLabel.textColor = TIMCommonDynamicColor(@"nav_title_text_color", @"#000000");
|
||||
titleLabel.textAlignment = isRTL()?NSTextAlignmentRight:NSTextAlignmentLeft;
|
||||
[titleLabel sizeToFit];
|
||||
self.navigationItem.titleView = titleLabel;
|
||||
|
||||
// left
|
||||
UIImage *defaultImage = [UIImage imageNamed:TUIChatImagePath(@"back")];
|
||||
UIButton *leftButton = [[UIButton alloc] initWithFrame:CGRectMake(0, 0, 30, 30)];
|
||||
UIImage *formatImage = [TIMCommonDynamicImage(@"nav_back_img", defaultImage) rtl_imageFlippedForRightToLeftLayoutDirection];
|
||||
|
||||
[leftButton addTarget:self action:@selector(onBack:) forControlEvents:UIControlEventTouchUpInside];
|
||||
[leftButton setImage:formatImage forState:UIControlStateNormal];
|
||||
UIBarButtonItem *leftItem = [[UIBarButtonItem alloc] initWithCustomView:leftButton];
|
||||
self.navigationItem.leftBarButtonItem = leftItem;
|
||||
|
||||
_image = [[UIImageView alloc] initWithFrame:CGRectMake((self.view.frame.size.width - 80) * 0.5, NavBar_Height + StatusBar_Height + 50, 80, 80)];
|
||||
_image.contentMode = UIViewContentModeScaleAspectFit;
|
||||
_image.image = [UIImage imageNamed:TUIChatImagePath(@"msg_file")];
|
||||
[self.view addSubview:_image];
|
||||
|
||||
_name = [[UILabel alloc] initWithFrame:CGRectMake(0, _image.frame.origin.y + _image.frame.size.height + 20, self.view.frame.size.width, 40)];
|
||||
_name.textColor = [UIColor blackColor];
|
||||
_name.font = [UIFont systemFontOfSize:15];
|
||||
_name.textAlignment = NSTextAlignmentCenter;
|
||||
_name.text = _data.fileName;
|
||||
[self.view addSubview:_name];
|
||||
|
||||
_button = [[UIButton alloc] initWithFrame:CGRectMake(100, _name.frame.origin.y + _name.frame.size.height + 20, self.view.frame.size.width - 200, 40)];
|
||||
[_button setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
|
||||
_button.backgroundColor = [UIColor colorWithRed:44 / 255.0 green:145 / 255.0 blue:247 / 255.0 alpha:1.0];
|
||||
_button.layer.cornerRadius = 5;
|
||||
[_button.layer setMasksToBounds:YES];
|
||||
[_button addTarget:self action:@selector(onOpen:) forControlEvents:UIControlEventTouchUpInside];
|
||||
|
||||
[self.view addSubview:_button];
|
||||
|
||||
@weakify(self);
|
||||
[RACObserve(_data, downladProgress) subscribeNext:^(NSNumber *x) {
|
||||
@strongify(self);
|
||||
int progress = [x intValue];
|
||||
if (progress < 100 && progress > 0) {
|
||||
[self.button setTitle:[NSString stringWithFormat:TIMCommonLocalizableString(TUIKitDownloadProgressFormat), progress] forState:UIControlStateNormal];
|
||||
} else {
|
||||
[self.button setTitle:TIMCommonLocalizableString(TUIKitOpenWithOtherApp) forState:UIControlStateNormal];
|
||||
}
|
||||
}];
|
||||
if ([_data isLocalExist]) {
|
||||
[self.button setTitle:TIMCommonLocalizableString(TUIKitOpenWithOtherApp) forState:UIControlStateNormal];
|
||||
|
||||
} else {
|
||||
[self.button setTitle:TIMCommonLocalizableString(Download) forState:UIControlStateNormal];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)onOpen:(id)sender {
|
||||
BOOL isExist = NO;
|
||||
NSString *path = [_data getFilePath:&isExist];
|
||||
if (isExist) {
|
||||
NSURL *url = [NSURL fileURLWithPath:path];
|
||||
_document = [UIDocumentInteractionController interactionControllerWithURL:url];
|
||||
_document.delegate = self;
|
||||
[_document presentOptionsMenuFromRect:self.view.bounds inView:self.view animated:YES];
|
||||
} else {
|
||||
[_data downloadFile];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)onBack:(id)sender {
|
||||
[self.navigationController popViewControllerAnimated:YES];
|
||||
}
|
||||
|
||||
- (UIView *)documentInteractionControllerViewForPreview:(UIDocumentInteractionController *)controller {
|
||||
return self.view;
|
||||
}
|
||||
|
||||
- (CGRect)documentInteractionControllerRectForPreview:(UIDocumentInteractionController *)controller {
|
||||
return self.view.frame;
|
||||
}
|
||||
|
||||
- (UIViewController *)documentInteractionControllerViewControllerForPreview:(UIDocumentInteractionController *)controller {
|
||||
return self;
|
||||
}
|
||||
@end
|
||||
16
TUIKit/TUIChat/UI_Classic/Chat/TUIGroupChatViewController.h
Normal file
16
TUIKit/TUIChat/UI_Classic/Chat/TUIGroupChatViewController.h
Normal file
@@ -0,0 +1,16 @@
|
||||
//
|
||||
// TUIGroupChatViewController.h
|
||||
// TXIMSDK_TUIKit_iOS
|
||||
//
|
||||
// Created by kayev on 2021/6/17.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
//
|
||||
|
||||
#import "TUIBaseChatViewController.h"
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface TUIGroupChatViewController : TUIBaseChatViewController
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
437
TUIKit/TUIChat/UI_Classic/Chat/TUIGroupChatViewController.m
Normal file
437
TUIKit/TUIChat/UI_Classic/Chat/TUIGroupChatViewController.m
Normal file
@@ -0,0 +1,437 @@
|
||||
//
|
||||
// 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+ProtectedAPI.h"
|
||||
#import "TUIGroupChatViewController.h"
|
||||
#import "TUIGroupPendencyController.h"
|
||||
#import "TUIGroupPendencyDataProvider.h"
|
||||
#import "TUILinkCellData.h"
|
||||
#import "TUIMessageDataProvider.h"
|
||||
#import "TUITextMessageCellData.h"
|
||||
#import "TUIChatFlexViewController.h"
|
||||
#import "TUIMessageController.h"
|
||||
#import "TUIGroupPinCell.h"
|
||||
#import "TUIGroupPinPageViewController.h"
|
||||
|
||||
@interface TUIGroupChatViewController () <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
|
||||
|
||||
- (void)viewDidLoad {
|
||||
[super viewDidLoad];
|
||||
// Do any additional setup after loading the view.
|
||||
self.view.backgroundColor = [UIColor clearColor];
|
||||
[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 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 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 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 *msgVC = (TUIMessageController *)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) {
|
||||
// After selecting, you will no longer enter the details page.
|
||||
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_Classic
|
||||
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;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - TUIInputControllerDelegate
|
||||
- (void)inputController:(TUIInputController *)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 *)inputController {
|
||||
[super inputControllerDidInputAt:inputController];
|
||||
/**
|
||||
* Input of @ character detected
|
||||
*/
|
||||
if (self.conversationData.groupID.length > 0) {
|
||||
if ([self.navigationController.topViewController isKindOfClass:NSClassFromString(@"TUISelectGroupMemberViewController")]) {
|
||||
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_Classic
|
||||
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;
|
||||
}
|
||||
[weakSelf.atUserList addObject:model];
|
||||
[atText appendString:[NSString stringWithFormat:@"@%@ ", model.name]];
|
||||
}
|
||||
|
||||
NSAttributedString *spaceString = [[NSAttributedString alloc]
|
||||
initWithString:atText
|
||||
attributes:@{NSFontAttributeName : kTUIInputNoramlFont, NSForegroundColorAttributeName : kTUIInputNormalTextColor}];
|
||||
[weakSelf.inputController.inputBar addWordsToInputBar:spaceString ];
|
||||
}];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)inputController:(TUIInputController *)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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)inputController:(TUIInputController *)inputController didSelectMoreCell:(TUIInputMoreCell *)cell {
|
||||
[super inputController:inputController didSelectMoreCell:cell];
|
||||
}
|
||||
|
||||
#pragma mark - TUIBaseMessageControllerDelegate
|
||||
- (void)messageController:(TUIBaseMessageController *)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
|
||||
23
TUIKit/TUIChat/UI_Classic/Chat/TUIGroupInfoController.h
Normal file
23
TUIKit/TUIChat/UI_Classic/Chat/TUIGroupInfoController.h
Normal file
@@ -0,0 +1,23 @@
|
||||
|
||||
// Created by Tencent on 2023/06/09.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
@class TUIGroupInfoController;
|
||||
@class TUIGroupMemberCellData;
|
||||
@class V2TIMGroupInfo;
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// TUIGroupInfoController
|
||||
//
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@interface TUIGroupInfoController : UITableViewController
|
||||
|
||||
@property(nonatomic, strong) NSString *groupId;
|
||||
|
||||
- (void)updateData;
|
||||
- (void)updateGroupInfo;
|
||||
@end
|
||||
602
TUIKit/TUIChat/UI_Classic/Chat/TUIGroupInfoController.m
Normal file
602
TUIKit/TUIChat/UI_Classic/Chat/TUIGroupInfoController.m
Normal file
@@ -0,0 +1,602 @@
|
||||
|
||||
// Created by Tencent on 2023/06/09.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
#import "TUIGroupInfoController.h"
|
||||
#import <TIMCommon/TIMDefine.h>
|
||||
#import <TUICore/TUICore.h>
|
||||
#import <TUICore/TUILogin.h>
|
||||
#import <TUICore/TUIThemeManager.h>
|
||||
#import <TIMCommon/TIMGroupInfo+TUIDataProvider.h>
|
||||
#import "TUIGroupInfoDataProvider.h"
|
||||
#import <TIMCommon/TUICommonGroupInfoCellData.h>
|
||||
#import "TUIGroupNoticeCell.h"
|
||||
#import "TUIGroupNoticeController.h"
|
||||
#define ADD_TAG @"-1"
|
||||
#define DEL_TAG @"-2"
|
||||
|
||||
@interface TUIGroupInfoController () <TUIModifyViewDelegate,TUIProfileCardDelegate, TUIGroupInfoDataProviderDelegate>
|
||||
@property(nonatomic, strong) TUIGroupInfoDataProvider *dataProvider;
|
||||
@property(nonatomic, strong) TUINaviBarIndicatorView *titleView;
|
||||
@property(nonatomic, strong) UIViewController *showContactSelectVC;
|
||||
@end
|
||||
|
||||
@implementation TUIGroupInfoController
|
||||
|
||||
- (void)viewDidLoad {
|
||||
[super viewDidLoad];
|
||||
[self setupViews];
|
||||
self.dataProvider = [[TUIGroupInfoDataProvider alloc] initWithGroupID:self.groupId];
|
||||
self.dataProvider.delegate = self;
|
||||
[self.dataProvider loadData];
|
||||
|
||||
@weakify(self);
|
||||
[RACObserve(self.dataProvider, dataList) subscribeNext:^(id _Nullable x) {
|
||||
@strongify(self);
|
||||
[self.tableView reloadData];
|
||||
}];
|
||||
|
||||
_titleView = [[TUINaviBarIndicatorView alloc] init];
|
||||
self.navigationItem.titleView = _titleView;
|
||||
self.navigationItem.title = @"";
|
||||
[_titleView setTitle:TIMCommonLocalizableString(ProfileDetails)];
|
||||
}
|
||||
|
||||
- (void)setupViews {
|
||||
self.tableView.tableFooterView = [[UIView alloc] init];
|
||||
self.tableView.backgroundColor = TIMCommonDynamicColor(@"controller_bg_color", @"#F2F3F5");
|
||||
self.tableView.delaysContentTouches = NO;
|
||||
if (@available(iOS 15.0, *)) {
|
||||
self.tableView.sectionHeaderTopPadding = 0;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)updateData {
|
||||
[self.dataProvider loadData];
|
||||
}
|
||||
|
||||
- (void)updateGroupInfo {
|
||||
[self.dataProvider updateGroupInfo];
|
||||
}
|
||||
|
||||
#pragma mark - Table view data source
|
||||
|
||||
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
|
||||
return self.dataProvider.dataList.count;
|
||||
}
|
||||
|
||||
- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section {
|
||||
UIView *view = [[UIView alloc] init];
|
||||
view.backgroundColor = [UIColor clearColor];
|
||||
return view;
|
||||
}
|
||||
|
||||
- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section {
|
||||
return 10;
|
||||
}
|
||||
|
||||
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
|
||||
NSMutableArray *array = self.dataProvider.dataList[section];
|
||||
return array.count;
|
||||
}
|
||||
|
||||
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
|
||||
NSMutableArray *array = self.dataProvider.dataList[indexPath.section];
|
||||
NSObject *data = array[indexPath.row];
|
||||
if ([data isKindOfClass:[TUIProfileCardCellData class]]) {
|
||||
return [(TUIProfileCardCellData *)data heightOfWidth:Screen_Width];
|
||||
} else if ([data isKindOfClass:[TUIGroupMembersCellData class]]) {
|
||||
return [(TUIGroupMembersCellData *)data heightOfWidth:Screen_Width];
|
||||
} else if ([data isKindOfClass:[TUIButtonCellData class]]) {
|
||||
return [(TUIButtonCellData *)data heightOfWidth:Screen_Width];
|
||||
} else if ([data isKindOfClass:[TUICommonSwitchCellData class]]) {
|
||||
return [(TUICommonSwitchCellData *)data heightOfWidth:Screen_Width];
|
||||
} else if ([data isKindOfClass:TUIGroupNoticeCellData.class]) {
|
||||
return 72.0;
|
||||
}
|
||||
return 44;
|
||||
}
|
||||
|
||||
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
|
||||
NSMutableArray *array = self.dataProvider.dataList[indexPath.section];
|
||||
NSObject *data = array[indexPath.row];
|
||||
if ([data isKindOfClass:[TUIProfileCardCellData class]]) {
|
||||
TUIProfileCardCell *cell = [tableView dequeueReusableCellWithIdentifier:TGroupCommonCell_ReuseId];
|
||||
if (!cell) {
|
||||
cell = [[TUIProfileCardCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:TGroupCommonCell_ReuseId];
|
||||
}
|
||||
cell.delegate = self;
|
||||
[cell fillWithData:(TUIProfileCardCellData *)data];
|
||||
return cell;
|
||||
} else if ([data isKindOfClass:[TUICommonTextCellData class]]) {
|
||||
TUICommonTextCell *cell = [tableView dequeueReusableCellWithIdentifier:TKeyValueCell_ReuseId];
|
||||
if (!cell) {
|
||||
cell = [[TUICommonTextCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:TKeyValueCell_ReuseId];
|
||||
}
|
||||
[cell fillWithData:(TUICommonTextCellData *)data];
|
||||
return cell;
|
||||
} else if ([data isKindOfClass:[TUIGroupMembersCellData class]]) {
|
||||
TUICommonTableViewCell *cell = [[TUICommonTableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:TGroupMembersCell_ReuseId];
|
||||
NSDictionary *param = @{
|
||||
@"data": data ,
|
||||
@"pushVC": self.navigationController,
|
||||
@"groupID": self.groupId ? self.groupId : @"",
|
||||
@"membersData": self.dataProvider.membersData ,
|
||||
@"groupMembersCellData": self.dataProvider.groupMembersCellData,
|
||||
};
|
||||
[TUICore raiseExtension:TUICore_TUIChatExtension_GroupProfileMemberListExtension_ClassicExtensionID
|
||||
parentView:cell
|
||||
param:param];
|
||||
return cell;
|
||||
} else if ([data isKindOfClass:[TUICommonSwitchCellData class]]) {
|
||||
TUICommonSwitchCell *cell = [tableView dequeueReusableCellWithIdentifier:TSwitchCell_ReuseId];
|
||||
if (!cell) {
|
||||
cell = [[TUICommonSwitchCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:TSwitchCell_ReuseId];
|
||||
}
|
||||
[cell fillWithData:(TUICommonSwitchCellData *)data];
|
||||
return cell;
|
||||
} else if ([data isKindOfClass:[TUIButtonCellData class]]) {
|
||||
TUIButtonCell *cell = [tableView dequeueReusableCellWithIdentifier:TButtonCell_ReuseId];
|
||||
if (!cell) {
|
||||
cell = [[TUIButtonCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:TButtonCell_ReuseId];
|
||||
}
|
||||
[cell fillWithData:(TUIButtonCellData *)data];
|
||||
return cell;
|
||||
} else if ([data isKindOfClass:TUIGroupNoticeCellData.class]) {
|
||||
TUIGroupNoticeCell *cell = [tableView dequeueReusableCellWithIdentifier:@"TUIGroupNoticeCell"];
|
||||
if (cell == nil) {
|
||||
cell = [[TUIGroupNoticeCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"TUIGroupNoticeCell"];
|
||||
}
|
||||
cell.cellData = data;
|
||||
return cell;
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
|
||||
}
|
||||
|
||||
- (void)leftBarButtonClick:(UIButton *)sender {
|
||||
[self.navigationController popViewControllerAnimated:YES];
|
||||
}
|
||||
|
||||
#pragma mark TUIGroupInfoDataProviderDelegate
|
||||
|
||||
- (void)groupProfileExtensionButtonClick:(TUICommonTextCell *)cell {
|
||||
|
||||
TUIExtensionInfo *info = cell.data.tui_extValueObj;
|
||||
if (info == nil || ![info isKindOfClass:TUIExtensionInfo.class] || info.onClicked == nil) {
|
||||
return;
|
||||
}
|
||||
info.onClicked(@{});
|
||||
}
|
||||
- (void)didSelectMembers {
|
||||
NSMutableDictionary *param = [NSMutableDictionary dictionary];
|
||||
if (_groupId && _groupId.length >0) {
|
||||
param[@"groupID"] = _groupId;
|
||||
}
|
||||
|
||||
if (self.dataProvider.groupInfo) {
|
||||
param[@"groupInfo"] = self.dataProvider.groupInfo;
|
||||
}
|
||||
|
||||
UIViewController *vc = [TUICore createObject:TUICore_TUIContactObjectFactory
|
||||
key:TUICore_TUIContactObjectFactory_GetGroupMemberVCMethod
|
||||
param:param];
|
||||
if (vc && [vc isKindOfClass:UIViewController.class]) {
|
||||
[self.navigationController pushViewController:vc animated:YES];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)didSelectAddOption:(TUICommonTextCell *)cell {
|
||||
TUICommonTextCellData *data = cell.textData;
|
||||
BOOL isApprove = [data.key isEqualToString:TIMCommonLocalizableString(TUIKitGroupProfileInviteType)];
|
||||
__weak typeof(self) weakSelf = self;
|
||||
UIAlertController *ac = [UIAlertController
|
||||
alertControllerWithTitle:nil
|
||||
message:isApprove ? TIMCommonLocalizableString(TUIKitGroupProfileInviteType) : TIMCommonLocalizableString(TUIKitGroupProfileJoinType)
|
||||
preferredStyle:UIAlertControllerStyleActionSheet];
|
||||
|
||||
NSArray *actionList = @[
|
||||
@{
|
||||
@(V2TIM_GROUP_ADD_FORBID) : isApprove ? TIMCommonLocalizableString(TUIKitGroupProfileInviteDisable)
|
||||
: TIMCommonLocalizableString(TUIKitGroupProfileJoinDisable)
|
||||
},
|
||||
@{@(V2TIM_GROUP_ADD_AUTH) : TIMCommonLocalizableString(TUIKitGroupProfileAdminApprove)},
|
||||
@{@(V2TIM_GROUP_ADD_ANY) : TIMCommonLocalizableString(TUIKitGroupProfileAutoApproval)}
|
||||
];
|
||||
for (NSDictionary *map in actionList) {
|
||||
[ac tuitheme_addAction:[UIAlertAction actionWithTitle:map.allValues.firstObject
|
||||
style:UIAlertActionStyleDefault
|
||||
handler:^(UIAlertAction *_Nonnull action) {
|
||||
V2TIMGroupAddOpt opt = (V2TIMGroupAddOpt)[map.allKeys.firstObject intValue];
|
||||
if (isApprove) {
|
||||
[weakSelf.dataProvider setGroupApproveOpt:opt];
|
||||
} else {
|
||||
[weakSelf.dataProvider setGroupAddOpt:opt];
|
||||
}
|
||||
}]];
|
||||
}
|
||||
[ac tuitheme_addAction:[UIAlertAction actionWithTitle:TIMCommonLocalizableString(Cancel) style:UIAlertActionStyleCancel handler:nil]];
|
||||
|
||||
[self presentViewController:ac animated:YES completion:nil];
|
||||
}
|
||||
|
||||
- (void)didSelectGroupNick:(TUICommonTextCell *)cell {
|
||||
TUIModifyViewData *data = [[TUIModifyViewData alloc] init];
|
||||
data.title = TIMCommonLocalizableString(TUIKitGroupProfileEditAlias);
|
||||
data.content = self.dataProvider.selfInfo.nameCard;
|
||||
data.desc = TIMCommonLocalizableString(TUIKitGroupProfileEditAliasDesc);
|
||||
TUIModifyView *modify = [[TUIModifyView alloc] init];
|
||||
modify.tag = 2;
|
||||
modify.delegate = self;
|
||||
[modify setData:data];
|
||||
[modify showInWindow:self.view.window];
|
||||
}
|
||||
|
||||
- (void)didSelectCommon {
|
||||
UIAlertController *ac = [UIAlertController alertControllerWithTitle:nil message:nil preferredStyle:UIAlertControllerStyleActionSheet];
|
||||
|
||||
__weak typeof(self) weakSelf = self;
|
||||
if ([self.dataProvider.groupInfo isPrivate] || [TUIGroupInfoDataProvider isMeOwner:self.dataProvider.groupInfo]) {
|
||||
[ac tuitheme_addAction:[UIAlertAction actionWithTitle:TIMCommonLocalizableString(TUIKitGroupProfileEditGroupName)
|
||||
style:UIAlertActionStyleDefault
|
||||
handler:^(UIAlertAction *_Nonnull action) {
|
||||
TUIModifyViewData *data = [[TUIModifyViewData alloc] init];
|
||||
data.title = TIMCommonLocalizableString(TUIKitGroupProfileEditGroupName);
|
||||
data.content = weakSelf.dataProvider.groupInfo.groupName;
|
||||
data.desc = TIMCommonLocalizableString(TUIKitGroupProfileEditGroupName);
|
||||
TUIModifyView *modify = [[TUIModifyView alloc] init];
|
||||
modify.tag = 0;
|
||||
modify.delegate = weakSelf;
|
||||
[modify setData:data];
|
||||
[modify showInWindow:weakSelf.view.window];
|
||||
}]];
|
||||
}
|
||||
if ([TUIGroupInfoDataProvider isMeOwner:self.dataProvider.groupInfo]) {
|
||||
[ac tuitheme_addAction:[UIAlertAction actionWithTitle:TIMCommonLocalizableString(TUIKitGroupProfileEditAnnouncement)
|
||||
style:UIAlertActionStyleDefault
|
||||
handler:^(UIAlertAction *_Nonnull action) {
|
||||
TUIModifyViewData *data = [[TUIModifyViewData alloc] init];
|
||||
data.title = TIMCommonLocalizableString(TUIKitGroupProfileEditAnnouncement);
|
||||
TUIModifyView *modify = [[TUIModifyView alloc] init];
|
||||
modify.tag = 1;
|
||||
modify.delegate = weakSelf;
|
||||
[modify setData:data];
|
||||
[modify showInWindow:weakSelf.view.window];
|
||||
}]];
|
||||
}
|
||||
|
||||
if ([TUIGroupInfoDataProvider isMeOwner:self.dataProvider.groupInfo]) {
|
||||
@weakify(self);
|
||||
[ac tuitheme_addAction:[UIAlertAction actionWithTitle:TIMCommonLocalizableString(TUIKitGroupProfileEditAvatar)
|
||||
style:UIAlertActionStyleDefault
|
||||
handler:^(UIAlertAction *_Nonnull action) {
|
||||
@strongify(self);
|
||||
TUISelectAvatarController *vc = [[TUISelectAvatarController alloc] init];
|
||||
vc.selectAvatarType = TUISelectAvatarTypeGroupAvatar;
|
||||
vc.profilFaceURL = self.dataProvider.groupInfo.faceURL;
|
||||
[self.navigationController pushViewController:vc animated:YES];
|
||||
vc.selectCallBack = ^(NSString *_Nonnull urlStr) {
|
||||
if (urlStr.length > 0) {
|
||||
V2TIMGroupInfo *info = [[V2TIMGroupInfo alloc] init];
|
||||
info.groupID = self.groupId;
|
||||
info.faceURL = urlStr;
|
||||
[[V2TIMManager sharedInstance] setGroupInfo:info
|
||||
succ:^{
|
||||
[self updateGroupInfo];
|
||||
}
|
||||
fail:^(int code, NSString *msg) {
|
||||
[TUITool makeToastError:code msg:msg];
|
||||
}];
|
||||
}
|
||||
};
|
||||
}]];
|
||||
}
|
||||
|
||||
[ac tuitheme_addAction:[UIAlertAction actionWithTitle:TIMCommonLocalizableString(Cancel) style:UIAlertActionStyleCancel handler:nil]];
|
||||
|
||||
[self presentViewController:ac animated:YES completion:nil];
|
||||
}
|
||||
|
||||
- (void)didSelectOnNotDisturb:(TUICommonSwitchCell *)cell {
|
||||
V2TIMReceiveMessageOpt opt;
|
||||
if (cell.switcher.on) {
|
||||
opt = V2TIM_RECEIVE_NOT_NOTIFY_MESSAGE;
|
||||
} else {
|
||||
opt = V2TIM_RECEIVE_MESSAGE;
|
||||
}
|
||||
@weakify(self);
|
||||
[V2TIMManager.sharedInstance markConversation:@[ [NSString stringWithFormat:@"group_%@", self.groupId] ]
|
||||
markType:@(V2TIM_CONVERSATION_MARK_TYPE_FOLD)
|
||||
enableMark:NO
|
||||
succ:nil
|
||||
fail:nil];
|
||||
|
||||
[self.dataProvider setGroupReceiveMessageOpt:opt
|
||||
Succ:^{
|
||||
@strongify(self);
|
||||
[self updateGroupInfo];
|
||||
}
|
||||
fail:^(int code, NSString *desc){
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)didSelectOnTop:(TUICommonSwitchCell *)cell {
|
||||
if (cell.switcher.on) {
|
||||
[[TUIConversationPin sharedInstance] addTopConversation:[NSString stringWithFormat:@"group_%@", _groupId]
|
||||
callback:^(BOOL success, NSString *_Nonnull errorMessage) {
|
||||
if (success) {
|
||||
return;
|
||||
}
|
||||
cell.switcher.on = !cell.switcher.isOn;
|
||||
[TUITool makeToast:errorMessage];
|
||||
}];
|
||||
} else {
|
||||
[[TUIConversationPin sharedInstance] removeTopConversation:[NSString stringWithFormat:@"group_%@", _groupId]
|
||||
callback:^(BOOL success, NSString *_Nonnull errorMessage) {
|
||||
if (success) {
|
||||
return;
|
||||
}
|
||||
cell.switcher.on = !cell.switcher.isOn;
|
||||
[TUITool makeToast:errorMessage];
|
||||
}];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)didDeleteGroup:(TUIButtonCell *)cell {
|
||||
UIAlertController *ac = [UIAlertController alertControllerWithTitle:nil
|
||||
message:TIMCommonLocalizableString(TUIKitGroupProfileDeleteGroupTips)
|
||||
preferredStyle:UIAlertControllerStyleActionSheet];
|
||||
|
||||
@weakify(self);
|
||||
[ac tuitheme_addAction:[UIAlertAction
|
||||
actionWithTitle:TIMCommonLocalizableString(Confirm)
|
||||
style:UIAlertActionStyleDestructive
|
||||
handler:^(UIAlertAction *_Nonnull action) {
|
||||
@strongify(self);
|
||||
@weakify(self);
|
||||
if ([self.dataProvider.groupInfo canDismissGroup]) {
|
||||
[self.dataProvider
|
||||
dismissGroup:^{
|
||||
@strongify(self);
|
||||
@weakify(self);
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
@strongify(self);
|
||||
UIViewController *vc = [self findConversationListViewController];
|
||||
[[TUIConversationPin sharedInstance]
|
||||
removeTopConversation:[NSString stringWithFormat:@"group_%@", self.groupId]
|
||||
callback:nil];
|
||||
[V2TIMManager.sharedInstance markConversation:@[ [NSString stringWithFormat:@"group_%@", self.groupId] ]
|
||||
markType:@(V2TIM_CONVERSATION_MARK_TYPE_FOLD)
|
||||
enableMark:NO
|
||||
succ:^(NSArray<V2TIMConversationOperationResult *> *result) {
|
||||
[self.navigationController popToViewController:vc animated:YES];
|
||||
}
|
||||
fail:^(int code, NSString *desc) {
|
||||
[self.navigationController popToViewController:vc animated:YES];
|
||||
}];
|
||||
});
|
||||
}
|
||||
fail:^(int code, NSString *msg) {
|
||||
[TUITool makeToastError:code msg:msg];
|
||||
}];
|
||||
} else {
|
||||
[self.dataProvider
|
||||
quitGroup:^{
|
||||
@strongify(self);
|
||||
@weakify(self);
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
@strongify(self);
|
||||
UIViewController *vc = [self findConversationListViewController];
|
||||
[[TUIConversationPin sharedInstance]
|
||||
removeTopConversation:[NSString stringWithFormat:@"group_%@", self.groupId]
|
||||
callback:nil];
|
||||
[V2TIMManager.sharedInstance markConversation:@[ [NSString stringWithFormat:@"group_%@", self.groupId] ]
|
||||
markType:@(V2TIM_CONVERSATION_MARK_TYPE_FOLD)
|
||||
enableMark:NO
|
||||
succ:^(NSArray<V2TIMConversationOperationResult *> *result) {
|
||||
[self.navigationController popToViewController:vc animated:YES];
|
||||
}
|
||||
fail:^(int code, NSString *desc) {
|
||||
[self.navigationController popToViewController:vc animated:YES];
|
||||
}];
|
||||
});
|
||||
}
|
||||
fail:^(int code, NSString *msg) {
|
||||
[TUITool makeToastError:code msg:msg];
|
||||
}];
|
||||
}
|
||||
}]];
|
||||
|
||||
[ac tuitheme_addAction:[UIAlertAction actionWithTitle:TIMCommonLocalizableString(Cancel) style:UIAlertActionStyleCancel handler:nil]];
|
||||
[self presentViewController:ac animated:YES completion:nil];
|
||||
}
|
||||
|
||||
- (void)didReportGroup:(TUIButtonCell *)cell {
|
||||
NSURL *url = [NSURL URLWithString:@"https://cloud.tencent.com/act/event/report-platform"];
|
||||
[TUITool openLinkWithURL:url];
|
||||
}
|
||||
|
||||
- (UIViewController *)findConversationListViewController {
|
||||
UIViewController *vc = self.navigationController.viewControllers[0];
|
||||
for (UIViewController *vc in self.navigationController.viewControllers) {
|
||||
if ([vc isKindOfClass:NSClassFromString(@"TUIFoldListViewController")]) {
|
||||
return vc;
|
||||
}
|
||||
}
|
||||
return vc;
|
||||
}
|
||||
|
||||
- (void)didSelectOnFoldConversation:(TUICommonSwitchCell *)cell {
|
||||
BOOL enableMark = NO;
|
||||
if (cell.switcher.on) {
|
||||
enableMark = YES;
|
||||
}
|
||||
|
||||
@weakify(self);
|
||||
|
||||
[V2TIMManager.sharedInstance markConversation:@[ [NSString stringWithFormat:@"group_%@", self.groupId] ]
|
||||
markType:@(V2TIM_CONVERSATION_MARK_TYPE_FOLD)
|
||||
enableMark:enableMark
|
||||
succ : ^(NSArray<V2TIMConversationOperationResult *> *result) {
|
||||
cell.switchData.on = enableMark;
|
||||
[[TUIConversationPin sharedInstance] removeTopConversation:[NSString stringWithFormat:@"group_%@", self.groupId]
|
||||
callback:^(BOOL success, NSString *_Nonnull errorMessage) {
|
||||
@strongify(self);
|
||||
[self updateGroupInfo];
|
||||
}];
|
||||
} fail:nil];
|
||||
|
||||
}
|
||||
|
||||
- (void)didSelectOnChangeBackgroundImage:(TUICommonTextCell *)cell {
|
||||
@weakify(self);
|
||||
NSString *conversationID = [NSString stringWithFormat:@"group_%@", self.groupId];
|
||||
TUISelectAvatarController *vc = [[TUISelectAvatarController alloc] init];
|
||||
vc.selectAvatarType = TUISelectAvatarTypeConversationBackGroundCover;
|
||||
vc.profilFaceURL = [self getBackgroundImageUrlByConversationID:conversationID];
|
||||
[self.navigationController pushViewController:vc animated:YES];
|
||||
vc.selectCallBack = ^(NSString *_Nonnull urlStr) {
|
||||
@strongify(self);
|
||||
[self appendBackgroundImage:urlStr conversationID:conversationID];
|
||||
if (IS_NOT_EMPTY_NSSTRING(conversationID)) {
|
||||
[TUICore notifyEvent:TUICore_TUIContactNotify
|
||||
subKey:TUICore_TUIContactNotify_UpdateConversationBackgroundImageSubKey
|
||||
object:self
|
||||
param:@{TUICore_TUIContactNotify_UpdateConversationBackgroundImageSubKey_ConversationID : conversationID}];
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
- (NSString *)getBackgroundImageUrlByConversationID:(NSString *)targerConversationID {
|
||||
if (targerConversationID.length == 0) {
|
||||
return nil;
|
||||
}
|
||||
NSDictionary *dict = [NSUserDefaults.standardUserDefaults objectForKey:@"conversation_backgroundImage_map"];
|
||||
if (dict == nil) {
|
||||
dict = @{};
|
||||
}
|
||||
NSString *conversationID_UserID = [NSString stringWithFormat:@"%@_%@", targerConversationID, [TUILogin getUserID]];
|
||||
if (![dict isKindOfClass:NSDictionary.class] || ![dict.allKeys containsObject:conversationID_UserID]) {
|
||||
return nil;
|
||||
}
|
||||
return [dict objectForKey:conversationID_UserID];
|
||||
}
|
||||
|
||||
- (void)appendBackgroundImage:(NSString *)imgUrl conversationID:(NSString *)conversationID {
|
||||
if (conversationID.length == 0) {
|
||||
return;
|
||||
}
|
||||
NSDictionary *dict = [NSUserDefaults.standardUserDefaults objectForKey:@"conversation_backgroundImage_map"];
|
||||
if (dict == nil) {
|
||||
dict = @{};
|
||||
}
|
||||
if (![dict isKindOfClass:NSDictionary.class]) {
|
||||
return;
|
||||
}
|
||||
|
||||
NSString *conversationID_UserID = [NSString stringWithFormat:@"%@_%@", conversationID, [TUILogin getUserID]];
|
||||
NSMutableDictionary *originDataDict = [NSMutableDictionary dictionaryWithDictionary:dict];
|
||||
if (imgUrl.length == 0) {
|
||||
[originDataDict removeObjectForKey:conversationID_UserID];
|
||||
} else {
|
||||
[originDataDict setObject:imgUrl forKey:conversationID_UserID];
|
||||
}
|
||||
|
||||
[NSUserDefaults.standardUserDefaults setObject:originDataDict forKey:@"conversation_backgroundImage_map"];
|
||||
[NSUserDefaults.standardUserDefaults synchronize];
|
||||
}
|
||||
|
||||
|
||||
- (void)didClearAllHistory:(TUIButtonCell *)cell {
|
||||
@weakify(self);
|
||||
UIAlertController *ac = [UIAlertController alertControllerWithTitle:nil
|
||||
message:TIMCommonLocalizableString(TUIKitClearAllChatHistoryTips)
|
||||
preferredStyle:UIAlertControllerStyleAlert];
|
||||
[ac tuitheme_addAction:[UIAlertAction actionWithTitle:TIMCommonLocalizableString(Confirm)
|
||||
style:UIAlertActionStyleDestructive
|
||||
handler:^(UIAlertAction *_Nonnull action) {
|
||||
@strongify(self);
|
||||
[self.dataProvider
|
||||
clearAllHistory:^{
|
||||
[TUICore notifyEvent:TUICore_TUIConversationNotify
|
||||
subKey:TUICore_TUIConversationNotify_ClearConversationUIHistorySubKey
|
||||
object:self
|
||||
param:nil];
|
||||
[TUITool makeToast:@"success"];
|
||||
}
|
||||
fail:^(int code, NSString *desc) {
|
||||
[TUITool makeToastError:code msg:desc];
|
||||
}];
|
||||
}]];
|
||||
[ac tuitheme_addAction:[UIAlertAction actionWithTitle:TIMCommonLocalizableString(Cancel) style:UIAlertActionStyleCancel handler:nil]];
|
||||
[self presentViewController:ac animated:YES completion:nil];
|
||||
}
|
||||
|
||||
- (UINavigationController *)pushNavigationController {
|
||||
return self.navigationController;
|
||||
}
|
||||
- (void)didSelectGroupNotice {
|
||||
TUIGroupNoticeController *vc = [[TUIGroupNoticeController alloc] init];
|
||||
vc.groupID = self.groupId;
|
||||
__weak typeof(self) weakSelf = self;
|
||||
vc.onNoticeChanged = ^{
|
||||
[weakSelf updateGroupInfo];
|
||||
};
|
||||
[self.navigationController pushViewController:vc animated:YES];
|
||||
}
|
||||
|
||||
#pragma mark TUIProfileCardDelegate
|
||||
|
||||
- (void)didTapOnAvatar:(TUIProfileCardCell *)cell {
|
||||
TUISelectAvatarController *vc = [[TUISelectAvatarController alloc] init];
|
||||
vc.selectAvatarType = TUISelectAvatarTypeGroupAvatar;
|
||||
vc.profilFaceURL = self.dataProvider.groupInfo.faceURL;
|
||||
[self.navigationController pushViewController:vc animated:YES];
|
||||
@weakify(self);
|
||||
vc.selectCallBack = ^(NSString *_Nonnull urlStr) {
|
||||
@strongify(self);
|
||||
if (urlStr.length > 0) {
|
||||
V2TIMGroupInfo *info = [[V2TIMGroupInfo alloc] init];
|
||||
info.groupID = self.groupId;
|
||||
info.faceURL = urlStr;
|
||||
[[V2TIMManager sharedInstance] setGroupInfo:info
|
||||
succ:^{
|
||||
[self updateGroupInfo];
|
||||
}
|
||||
fail:^(int code, NSString *msg) {
|
||||
[TUITool makeToastError:code msg:msg];
|
||||
}];
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#pragma mark TUIModifyViewDelegate
|
||||
|
||||
- (void)modifyView:(TUIModifyView *)modifyView didModiyContent:(NSString *)content {
|
||||
if (modifyView.tag == 0) {
|
||||
[self.dataProvider setGroupName:content];
|
||||
} else if (modifyView.tag == 1) {
|
||||
[self.dataProvider setGroupNotification:content];
|
||||
} else if (modifyView.tag == 2) {
|
||||
[self.dataProvider setGroupMemberNameCard:content];
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@interface IUGroupView : UIView
|
||||
@property(nonatomic, strong) UIView *view;
|
||||
@end
|
||||
|
||||
@implementation IUGroupView
|
||||
|
||||
- (instancetype)init {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
self.view = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 10, 10)];
|
||||
[self addSubview:self.view];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
@end
|
||||
62
TUIKit/TUIChat/UI_Classic/Chat/TUIGroupInfoDataProvider.h
Normal file
62
TUIKit/TUIChat/UI_Classic/Chat/TUIGroupInfoDataProvider.h
Normal file
@@ -0,0 +1,62 @@
|
||||
|
||||
// Created by Tencent on 2023/06/09.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
|
||||
|
||||
@import Foundation;
|
||||
@import UIKit;
|
||||
@import ImSDK_Plus;
|
||||
|
||||
@class TUICommonCellData;
|
||||
@class TUICommonTextCell;
|
||||
@class TUICommonSwitchCell;
|
||||
@class TUIButtonCell;
|
||||
@class TUIProfileCardCellData;
|
||||
@class TUIProfileCardCell;
|
||||
@class TUIGroupMemberCellData;
|
||||
@class TUIGroupMembersCellData;
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@protocol TUIGroupInfoDataProviderDelegate <NSObject>
|
||||
- (UINavigationController *)pushNavigationController;
|
||||
- (void)didSelectMembers;
|
||||
- (void)didSelectGroupNick:(TUICommonTextCell *)cell;
|
||||
- (void)didSelectAddOption:(UITableViewCell *)cell;
|
||||
- (void)didSelectCommon;
|
||||
- (void)didSelectOnNotDisturb:(TUICommonSwitchCell *)cell;
|
||||
- (void)didSelectOnTop:(TUICommonSwitchCell *)cell;
|
||||
- (void)didSelectOnFoldConversation:(TUICommonSwitchCell *)cell;
|
||||
- (void)didSelectOnChangeBackgroundImage:(TUICommonTextCell *)cell;
|
||||
- (void)didDeleteGroup:(TUIButtonCell *)cell;
|
||||
- (void)didClearAllHistory:(TUIButtonCell *)cell;
|
||||
- (void)didSelectGroupNotice;
|
||||
@end
|
||||
|
||||
@interface TUIGroupInfoDataProvider : NSObject
|
||||
@property(nonatomic, weak) id<TUIGroupInfoDataProviderDelegate> delegate;
|
||||
@property(nonatomic, strong) V2TIMGroupInfo *groupInfo;
|
||||
@property(nonatomic, strong) NSMutableArray *dataList;
|
||||
@property(nonatomic, strong) NSMutableArray<TUIGroupMemberCellData *> *membersData;
|
||||
@property(nonatomic, strong) TUIGroupMembersCellData *groupMembersCellData;
|
||||
@property(nonatomic, strong, readonly) V2TIMGroupMemberFullInfo *selfInfo;
|
||||
@property(nonatomic, strong, readonly) TUIProfileCardCellData *profileCellData;
|
||||
|
||||
- (instancetype)initWithGroupID:(NSString *)groupID;
|
||||
- (void)loadData;
|
||||
- (void)updateGroupInfo;
|
||||
- (void)setGroupAddOpt:(V2TIMGroupAddOpt)opt;
|
||||
- (void)setGroupApproveOpt:(V2TIMGroupAddOpt)opt;
|
||||
- (void)setGroupReceiveMessageOpt:(V2TIMReceiveMessageOpt)opt Succ:(V2TIMSucc)succ fail:(V2TIMFail)fail;
|
||||
- (void)setGroupName:(NSString *)groupName;
|
||||
- (void)setGroupNotification:(NSString *)notification;
|
||||
- (void)setGroupMemberNameCard:(NSString *)nameCard;
|
||||
- (void)dismissGroup:(V2TIMSucc)succ fail:(V2TIMFail)fail;
|
||||
- (void)quitGroup:(V2TIMSucc)succ fail:(V2TIMFail)fail;
|
||||
- (void)clearAllHistory:(V2TIMSucc)succ fail:(V2TIMFail)fail;
|
||||
- (void)updateGroupAvatar:(NSString *)url succ:(V2TIMSucc)succ fail:(V2TIMFail)fail;
|
||||
- (void)transferGroupOwner:(NSString *)groupID member:(NSString *)userID succ:(V2TIMSucc)succ fail:(V2TIMFail)fail;
|
||||
+ (BOOL)isMeOwner:(V2TIMGroupInfo *)groupInfo;
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
748
TUIKit/TUIChat/UI_Classic/Chat/TUIGroupInfoDataProvider.m
Normal file
748
TUIKit/TUIChat/UI_Classic/Chat/TUIGroupInfoDataProvider.m
Normal file
@@ -0,0 +1,748 @@
|
||||
|
||||
// Created by Tencent on 2023/06/09.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
|
||||
#import <TIMCommon/TIMCommonModel.h>
|
||||
#import <TIMCommon/TIMDefine.h>
|
||||
#import <TIMCommon/TIMGroupInfo+TUIDataProvider.h>
|
||||
#import "TUIGroupInfoDataProvider.h"
|
||||
#import <TIMCommon/TUICommonGroupInfoCellData.h>
|
||||
#import "TUIGroupNoticeCell.h"
|
||||
#import "TUIGroupConfig.h"
|
||||
#import <TUICore/TUICore.h>
|
||||
|
||||
@interface TUIGroupInfoDataProvider () <V2TIMGroupListener>
|
||||
@property(nonatomic, strong) TUICommonTextCellData *addOptionData;
|
||||
@property(nonatomic, strong) TUICommonTextCellData *inviteOptionData;
|
||||
@property(nonatomic, strong) TUICommonTextCellData *groupNickNameCellData;
|
||||
@property(nonatomic, strong, readwrite) TUIProfileCardCellData *profileCellData;
|
||||
@property(nonatomic, strong) V2TIMGroupMemberFullInfo *selfInfo;
|
||||
@property(nonatomic, strong) NSString *groupID;
|
||||
@property(nonatomic, assign) BOOL firstLoadData;
|
||||
@end
|
||||
|
||||
@implementation TUIGroupInfoDataProvider
|
||||
|
||||
- (instancetype)initWithGroupID:(NSString *)groupID {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
self.groupID = groupID;
|
||||
[[V2TIMManager sharedInstance] addGroupListener:self];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
#pragma mark V2TIMGroupListener
|
||||
- (void)onMemberEnter:(NSString *)groupID memberList:(NSArray<V2TIMGroupMemberInfo *> *)memberList {
|
||||
[self loadData];
|
||||
}
|
||||
|
||||
- (void)onMemberLeave:(NSString *)groupID member:(V2TIMGroupMemberInfo *)member {
|
||||
[self loadData];
|
||||
}
|
||||
|
||||
- (void)onMemberInvited:(NSString *)groupID opUser:(V2TIMGroupMemberInfo *)opUser memberList:(NSArray<V2TIMGroupMemberInfo *> *)memberList {
|
||||
[self loadData];
|
||||
}
|
||||
|
||||
- (void)onMemberKicked:(NSString *)groupID opUser:(V2TIMGroupMemberInfo *)opUser memberList:(NSArray<V2TIMGroupMemberInfo *> *)memberList {
|
||||
[self loadData];
|
||||
}
|
||||
|
||||
- (void)onGroupInfoChanged:(NSString *)groupID changeInfoList:(NSArray<V2TIMGroupChangeInfo *> *)changeInfoList {
|
||||
if (![groupID isEqualToString:self.groupID]) {
|
||||
return;
|
||||
}
|
||||
|
||||
[self loadData];
|
||||
}
|
||||
|
||||
- (void)loadData {
|
||||
if (self.groupID.length == 0) {
|
||||
[TUITool makeToastError:ERR_INVALID_PARAMETERS msg:@"invalid groupID"];
|
||||
return;
|
||||
}
|
||||
self.firstLoadData = YES;
|
||||
dispatch_group_t group = dispatch_group_create();
|
||||
dispatch_group_enter(group);
|
||||
[self getGroupInfo:^{
|
||||
dispatch_group_leave(group);
|
||||
}];
|
||||
|
||||
dispatch_group_enter(group);
|
||||
[self getGroupMembers:^{
|
||||
dispatch_group_leave(group);
|
||||
}];
|
||||
|
||||
dispatch_group_enter(group);
|
||||
[self getSelfInfoInGroup:^{
|
||||
dispatch_group_leave(group);
|
||||
}];
|
||||
|
||||
__weak typeof(self) weakSelf = self;
|
||||
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
|
||||
[weakSelf setupData];
|
||||
});
|
||||
}
|
||||
|
||||
- (void)updateGroupInfo {
|
||||
@weakify(self);
|
||||
[[V2TIMManager sharedInstance] getGroupsInfo:@[ self.groupID ]
|
||||
succ:^(NSArray<V2TIMGroupInfoResult *> *groupResultList) {
|
||||
@strongify(self);
|
||||
if (groupResultList.count == 1) {
|
||||
self.groupInfo = groupResultList[0].info;
|
||||
[self setupData];
|
||||
}
|
||||
}
|
||||
fail:^(int code, NSString *msg) {
|
||||
[self makeToastError:code msg:msg];
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)makeToastError:(NSInteger)code msg:(NSString *)msg {
|
||||
if (!self.firstLoadData) {
|
||||
[TUITool makeToastError:code msg:msg];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)transferGroupOwner:(NSString *)groupID member:(NSString *)userID succ:(V2TIMSucc)succ fail:(V2TIMFail)fail {
|
||||
[V2TIMManager.sharedInstance transferGroupOwner:groupID
|
||||
member:userID
|
||||
succ:^{
|
||||
succ();
|
||||
}
|
||||
fail:^(int code, NSString *desc) {
|
||||
fail(code, desc);
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)updateGroupAvatar:(NSString *)url succ:(V2TIMSucc)succ fail:(V2TIMFail)fail {
|
||||
V2TIMGroupInfo *info = [[V2TIMGroupInfo alloc] init];
|
||||
info.groupID = self.groupID;
|
||||
info.faceURL = url;
|
||||
[V2TIMManager.sharedInstance setGroupInfo:info succ:succ fail:fail];
|
||||
}
|
||||
|
||||
- (void)getGroupInfo:(dispatch_block_t)callback {
|
||||
__weak typeof(self) weakSelf = self;
|
||||
[[V2TIMManager sharedInstance] getGroupsInfo:@[ self.groupID ]
|
||||
succ:^(NSArray<V2TIMGroupInfoResult *> *groupResultList) {
|
||||
weakSelf.groupInfo = groupResultList.firstObject.info;
|
||||
if (callback) {
|
||||
callback();
|
||||
}
|
||||
}
|
||||
fail:^(int code, NSString *msg) {
|
||||
if (callback) {
|
||||
callback();
|
||||
}
|
||||
[self makeToastError:code msg:msg];
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)getGroupMembers:(dispatch_block_t)callback {
|
||||
__weak typeof(self) weakSelf = self;
|
||||
[[V2TIMManager sharedInstance] getGroupMemberList:self.groupID
|
||||
filter:V2TIM_GROUP_MEMBER_FILTER_ALL
|
||||
nextSeq:0
|
||||
succ:^(uint64_t nextSeq, NSArray<V2TIMGroupMemberFullInfo *> *memberList) {
|
||||
NSMutableArray *membersData = [NSMutableArray array];
|
||||
for (V2TIMGroupMemberFullInfo *fullInfo in memberList) {
|
||||
TUIGroupMemberCellData *data = [[TUIGroupMemberCellData alloc] init];
|
||||
data.identifier = fullInfo.userID;
|
||||
data.name = fullInfo.userID;
|
||||
data.avatarUrl = fullInfo.faceURL;
|
||||
if (fullInfo.nameCard.length > 0) {
|
||||
data.name = fullInfo.nameCard;
|
||||
} else if (fullInfo.friendRemark.length > 0) {
|
||||
data.name = fullInfo.friendRemark;
|
||||
} else if (fullInfo.nickName.length > 0) {
|
||||
data.name = fullInfo.nickName;
|
||||
}
|
||||
[membersData addObject:data];
|
||||
}
|
||||
|
||||
|
||||
weakSelf.membersData = membersData;
|
||||
if (callback) {
|
||||
callback();
|
||||
}
|
||||
}
|
||||
fail:^(int code, NSString *msg) {
|
||||
weakSelf.membersData = [NSMutableArray arrayWithCapacity:1];
|
||||
[self makeToastError:code msg:msg];
|
||||
if (callback) {
|
||||
callback();
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)getSelfInfoInGroup:(dispatch_block_t)callback {
|
||||
NSString *loginUserID = [[V2TIMManager sharedInstance] getLoginUser];
|
||||
if (loginUserID.length == 0) {
|
||||
if (callback) {
|
||||
callback();
|
||||
}
|
||||
return;
|
||||
}
|
||||
__weak typeof(self) weakSelf = self;
|
||||
[[V2TIMManager sharedInstance] getGroupMembersInfo:self.groupID
|
||||
memberList:@[ loginUserID ]
|
||||
succ:^(NSArray<V2TIMGroupMemberFullInfo *> *memberList) {
|
||||
for (V2TIMGroupMemberFullInfo *item in memberList) {
|
||||
if ([item.userID isEqualToString:loginUserID]) {
|
||||
weakSelf.selfInfo = item;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (callback) {
|
||||
callback();
|
||||
}
|
||||
}
|
||||
fail:^(int code, NSString *desc) {
|
||||
[self makeToastError:code msg:desc];
|
||||
if (callback) {
|
||||
callback();
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)setupData {
|
||||
NSMutableArray *dataList = [NSMutableArray array];
|
||||
if (self.groupInfo) {
|
||||
NSMutableArray *commonArray = [NSMutableArray array];
|
||||
TUIProfileCardCellData *commonData = [[TUIProfileCardCellData alloc] init];
|
||||
commonData.avatarImage = DefaultGroupAvatarImageByGroupType(self.groupInfo.groupType);
|
||||
commonData.avatarUrl = [NSURL URLWithString:self.groupInfo.faceURL];
|
||||
commonData.name = self.groupInfo.groupName;
|
||||
commonData.identifier = self.groupInfo.groupID;
|
||||
commonData.signature = self.groupInfo.notification;
|
||||
|
||||
if ([TUIGroupInfoDataProvider isMeOwner:self.groupInfo] || [self.groupInfo isPrivate]) {
|
||||
commonData.cselector = @selector(didSelectCommon);
|
||||
commonData.showAccessory = YES;
|
||||
}
|
||||
self.profileCellData = commonData;
|
||||
|
||||
[commonArray addObject:commonData];
|
||||
[dataList addObject:commonArray];
|
||||
|
||||
NSMutableArray *memberArray = [NSMutableArray array];
|
||||
if (![TUIGroupConfig.sharedConfig isItemHiddenInGroupConfig:TUIGroupConfigItem_Members]) {
|
||||
TUICommonTextCellData *countData = [[TUICommonTextCellData alloc] init];
|
||||
countData.key = TIMCommonLocalizableString(TUIKitGroupProfileMember);
|
||||
countData.value = [NSString stringWithFormat:TIMCommonLocalizableString(TUIKitGroupProfileMemberCount), self.groupInfo.memberCount];
|
||||
countData.cselector = @selector(didSelectMembers);
|
||||
countData.showAccessory = YES;
|
||||
[memberArray addObject:countData];
|
||||
|
||||
NSMutableArray *tmpArray = [self getShowMembers:self.membersData];
|
||||
TUIGroupMembersCellData *membersData = [[TUIGroupMembersCellData alloc] init];
|
||||
membersData.members = tmpArray;
|
||||
[memberArray addObject:membersData];
|
||||
self.groupMembersCellData = membersData;
|
||||
[dataList addObject:memberArray];
|
||||
}
|
||||
|
||||
// group info
|
||||
NSMutableArray *groupInfoArray = [NSMutableArray array];
|
||||
|
||||
if (![TUIGroupConfig.sharedConfig isItemHiddenInGroupConfig:TUIGroupConfigItem_Notice]) {
|
||||
TUIGroupNoticeCellData *notice = [[TUIGroupNoticeCellData alloc] init];
|
||||
notice.name = TIMCommonLocalizableString(TUIKitGroupNotice);
|
||||
notice.desc = self.groupInfo.notification ?: TIMCommonLocalizableString(TUIKitGroupNoticeNull);
|
||||
notice.target = self;
|
||||
notice.selector = @selector(didSelectNotice);
|
||||
[groupInfoArray addObject:notice];
|
||||
}
|
||||
|
||||
if (![TUIGroupConfig.sharedConfig isItemHiddenInGroupConfig:TUIGroupConfigItem_Members]) {
|
||||
NSMutableDictionary *param = [NSMutableDictionary dictionary];
|
||||
param[@"pushVC"] = self.delegate.pushNavigationController;
|
||||
param[@"groupID"] = self.groupID.length > 0 ? self.groupID : @"";
|
||||
NSArray<TUIExtensionInfo *> *extensionList = [TUICore getExtensionList:
|
||||
TUICore_TUIChatExtension_GroupProfileSettingsItemExtension_ClassicExtensionID
|
||||
param:param];
|
||||
for (TUIExtensionInfo *info in extensionList) {
|
||||
if (info.text && info.onClicked) {
|
||||
TUICommonTextCellData *manageData = [[TUICommonTextCellData alloc] init];
|
||||
manageData.key = info.text; //TIMCommonLocalizableString(TUIKitGroupProfileManage);
|
||||
manageData.value = @"";
|
||||
manageData.tui_extValueObj = info;
|
||||
manageData.showAccessory = YES;
|
||||
manageData.cselector = @selector(groupProfileExtensionButtonClick:);
|
||||
if (info.weight == 100 ) {
|
||||
if ([TUIGroupInfoDataProvider isMeOwner:self.groupInfo]) {
|
||||
[groupInfoArray addObject:manageData];
|
||||
}
|
||||
}
|
||||
else {
|
||||
[groupInfoArray addObject:manageData];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (![TUIGroupConfig.sharedConfig isItemHiddenInGroupConfig:TUIGroupConfigItem_Manage]) {
|
||||
TUICommonTextCellData *typeData = [[TUICommonTextCellData alloc] init];
|
||||
typeData.key = TIMCommonLocalizableString(TUIKitGroupProfileType);
|
||||
typeData.value = [TUIGroupInfoDataProvider getGroupTypeName:self.groupInfo];
|
||||
[groupInfoArray addObject:typeData];
|
||||
|
||||
TUICommonTextCellData *addOptionData = [[TUICommonTextCellData alloc] init];
|
||||
addOptionData.key = TIMCommonLocalizableString(TUIKitGroupProfileJoinType);
|
||||
|
||||
if ([TUIGroupInfoDataProvider isMeOwner:self.groupInfo]) {
|
||||
addOptionData.cselector = @selector(didSelectAddOption:);
|
||||
addOptionData.showAccessory = YES;
|
||||
}
|
||||
addOptionData.value = [TUIGroupInfoDataProvider getAddOption:self.groupInfo];
|
||||
|
||||
[groupInfoArray addObject:addOptionData];
|
||||
self.addOptionData = addOptionData;
|
||||
|
||||
TUICommonTextCellData *inviteOptionData = [[TUICommonTextCellData alloc] init];
|
||||
inviteOptionData.key = TIMCommonLocalizableString(TUIKitGroupProfileInviteType);
|
||||
if ([TUIGroupInfoDataProvider isMeOwner:self.groupInfo]) {
|
||||
inviteOptionData.cselector = @selector(didSelectAddOption:);
|
||||
inviteOptionData.showAccessory = YES;
|
||||
}
|
||||
inviteOptionData.value = [TUIGroupInfoDataProvider getApproveOption:self.groupInfo];
|
||||
[groupInfoArray addObject:inviteOptionData];
|
||||
self.inviteOptionData = inviteOptionData;
|
||||
[dataList addObject:groupInfoArray];
|
||||
|
||||
}
|
||||
|
||||
// personal info
|
||||
if (![TUIGroupConfig.sharedConfig isItemHiddenInGroupConfig:TUIGroupConfigItem_Alias]) {
|
||||
TUICommonTextCellData *nickData = [[TUICommonTextCellData alloc] init];
|
||||
nickData.key = TIMCommonLocalizableString(TUIKitGroupProfileAlias);
|
||||
nickData.value = self.selfInfo.nameCard;
|
||||
nickData.cselector = @selector(didSelectGroupNick:);
|
||||
nickData.showAccessory = YES;
|
||||
self.groupNickNameCellData = nickData;
|
||||
[dataList addObject:@[ nickData ]];
|
||||
}
|
||||
|
||||
TUICommonSwitchCellData *markFold = [[TUICommonSwitchCellData alloc] init];
|
||||
TUICommonSwitchCellData *switchData = [[TUICommonSwitchCellData alloc] init];
|
||||
if (![TUIGroupConfig.sharedConfig isItemHiddenInGroupConfig:TUIGroupConfigItem_MuteAndPin]) {
|
||||
NSMutableArray *personalArray = [NSMutableArray array];
|
||||
|
||||
TUICommonSwitchCellData *messageSwitchData = [[TUICommonSwitchCellData alloc] init];
|
||||
|
||||
if (![self.groupInfo.groupType isEqualToString:GroupType_Meeting]) {
|
||||
messageSwitchData.on = (self.groupInfo.recvOpt == V2TIM_RECEIVE_NOT_NOTIFY_MESSAGE);
|
||||
messageSwitchData.title = TIMCommonLocalizableString(TUIKitGroupProfileMessageDoNotDisturb);
|
||||
messageSwitchData.cswitchSelector = @selector(didSelectOnNotDisturb:);
|
||||
[personalArray addObject:messageSwitchData];
|
||||
}
|
||||
|
||||
|
||||
|
||||
markFold.title = TIMCommonLocalizableString(TUIKitConversationMarkFold);
|
||||
|
||||
markFold.displaySeparatorLine = YES;
|
||||
|
||||
markFold.cswitchSelector = @selector(didSelectOnFoldConversation:);
|
||||
if (messageSwitchData.on) {
|
||||
[personalArray addObject:markFold];
|
||||
}
|
||||
|
||||
switchData.title = TIMCommonLocalizableString(TUIKitGroupProfileStickyOnTop);
|
||||
[personalArray addObject:switchData];
|
||||
|
||||
[dataList addObject:personalArray];
|
||||
}
|
||||
|
||||
if (![TUIGroupConfig.sharedConfig isItemHiddenInGroupConfig:TUIGroupConfigItem_Background]) {
|
||||
TUICommonTextCellData *changeBackgroundImageItem = [[TUICommonTextCellData alloc] init];
|
||||
changeBackgroundImageItem.key = TIMCommonLocalizableString(ProfileSetBackgroundImage);
|
||||
changeBackgroundImageItem.cselector = @selector(didSelectOnChangeBackgroundImage:);
|
||||
changeBackgroundImageItem.showAccessory = YES;
|
||||
[dataList addObject:@[ changeBackgroundImageItem ]];
|
||||
}
|
||||
|
||||
NSMutableArray *buttonArray = [NSMutableArray array];
|
||||
if (![TUIGroupConfig.sharedConfig isItemHiddenInGroupConfig:TUIGroupConfigItem_ClearChatHistory]) {
|
||||
TUIButtonCellData *clearHistory = [[TUIButtonCellData alloc] init];
|
||||
clearHistory.title = TIMCommonLocalizableString(TUIKitClearAllChatHistory);
|
||||
clearHistory.style = ButtonRedText;
|
||||
clearHistory.cbuttonSelector = @selector(didClearAllHistory:);
|
||||
[buttonArray addObject:clearHistory];
|
||||
}
|
||||
|
||||
if (![TUIGroupConfig.sharedConfig isItemHiddenInGroupConfig:TUIGroupConfigItem_DeleteAndLeave]) {
|
||||
TUIButtonCellData *quitButton = [[TUIButtonCellData alloc] init];
|
||||
quitButton.title = TIMCommonLocalizableString(TUIKitGroupProfileDeleteAndExit);
|
||||
quitButton.style = ButtonRedText;
|
||||
quitButton.cbuttonSelector = @selector(didDeleteGroup:);
|
||||
[buttonArray addObject:quitButton];
|
||||
}
|
||||
|
||||
if ([self.class isMeSuper:self.groupInfo] &&
|
||||
![TUIGroupConfig.sharedConfig isItemHiddenInGroupConfig:TUIGroupConfigItem_Transfer]) {
|
||||
NSMutableDictionary *param = [NSMutableDictionary dictionary];
|
||||
param[@"pushVC"] = self.delegate.pushNavigationController;
|
||||
param[@"groupID"] = self.groupID.length > 0 ? self.groupID : @"";
|
||||
void (^updateGroupInfoCallback)(void) = ^{
|
||||
[self updateGroupInfo];
|
||||
};
|
||||
param[@"updateGroupInfoCallback"] = updateGroupInfoCallback;
|
||||
NSArray<TUIExtensionInfo *> *extensionList = [TUICore getExtensionList:
|
||||
TUICore_TUIChatExtension_GroupProfileBottomItemExtension_ClassicExtensionID
|
||||
param:param];
|
||||
for (TUIExtensionInfo *info in extensionList) {
|
||||
if (info.text && info.onClicked) {
|
||||
TUIButtonCellData *transferButton = [[TUIButtonCellData alloc] init];
|
||||
transferButton.title = info.text;
|
||||
transferButton.style = ButtonRedText;
|
||||
transferButton.tui_extValueObj = info;
|
||||
transferButton.cbuttonSelector = @selector(groupProfileExtensionButtonClick:);
|
||||
[buttonArray addObject:transferButton];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ([self.groupInfo canDismissGroup] &&
|
||||
![TUIGroupConfig.sharedConfig isItemHiddenInGroupConfig:TUIGroupConfigItem_Dismiss]) {
|
||||
TUIButtonCellData *deletebutton = [[TUIButtonCellData alloc] init];
|
||||
deletebutton.title = TIMCommonLocalizableString(TUIKitGroupProfileDissolve);
|
||||
deletebutton.style = ButtonRedText;
|
||||
deletebutton.cbuttonSelector = @selector(didDeleteGroup:);
|
||||
[buttonArray addObject:deletebutton];
|
||||
}
|
||||
|
||||
if (![TUIGroupConfig.sharedConfig isItemHiddenInGroupConfig:TUIGroupConfigItem_Report]) {
|
||||
TUIButtonCellData *reportButton = [[TUIButtonCellData alloc] init];
|
||||
reportButton.title = TIMCommonLocalizableString(TUIKitGroupProfileReport);
|
||||
reportButton.style = ButtonRedText;
|
||||
reportButton.cbuttonSelector = @selector(didReportGroup:);
|
||||
[buttonArray addObject:reportButton];
|
||||
}
|
||||
|
||||
TUIButtonCellData *lastCellData = [buttonArray lastObject];
|
||||
lastCellData.hideSeparatorLine = YES;
|
||||
[dataList addObject:buttonArray];
|
||||
|
||||
#ifndef SDKPlaceTop
|
||||
#define SDKPlaceTop
|
||||
#endif
|
||||
#ifdef SDKPlaceTop
|
||||
@weakify(self);
|
||||
[V2TIMManager.sharedInstance getConversation:[NSString stringWithFormat:@"group_%@", self.groupID]
|
||||
succ:^(V2TIMConversation *conv) {
|
||||
@strongify(self);
|
||||
|
||||
markFold.on = [self.class isMarkedByFoldType:conv.markList];
|
||||
|
||||
switchData.cswitchSelector = @selector(didSelectOnTop:);
|
||||
switchData.on = conv.isPinned;
|
||||
|
||||
if (markFold.on) {
|
||||
switchData.on = NO;
|
||||
switchData.disableChecked = YES;
|
||||
}
|
||||
|
||||
self.dataList = dataList;
|
||||
}
|
||||
fail:^(int code, NSString *desc) {
|
||||
NSLog(@"");
|
||||
}];
|
||||
#else
|
||||
if ([[[TUIConversationPin sharedInstance] topConversationList] containsObject:[NSString stringWithFormat:@"group_%@", self.groupID]]) {
|
||||
switchData.on = YES;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
- (void)groupProfileExtensionButtonClick:(TUICommonTextCell *)cell {
|
||||
//Implemented through self.delegate, because of TUIButtonCellData cbuttonSelector mechanism
|
||||
}
|
||||
- (void)didSelectMembers {
|
||||
if (self.delegate && [self.delegate respondsToSelector:@selector(didSelectMembers)]) {
|
||||
[self.delegate didSelectMembers];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)didSelectGroupNick:(TUICommonTextCell *)cell {
|
||||
if (self.delegate && [self.delegate respondsToSelector:@selector(didSelectGroupNick:)]) {
|
||||
[self.delegate didSelectGroupNick:cell];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)didSelectAddOption:(UITableViewCell *)cell {
|
||||
if (self.delegate && [self.delegate respondsToSelector:@selector(didSelectAddOption:)]) {
|
||||
[self.delegate didSelectAddOption:cell];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)didSelectCommon {
|
||||
if (self.delegate && [self.delegate respondsToSelector:@selector(didSelectCommon)]) {
|
||||
[self.delegate didSelectCommon];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)didSelectOnNotDisturb:(TUICommonSwitchCell *)cell {
|
||||
if (self.delegate && [self.delegate respondsToSelector:@selector(didSelectOnNotDisturb:)]) {
|
||||
[self.delegate didSelectOnNotDisturb:cell];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)didSelectOnTop:(TUICommonSwitchCell *)cell {
|
||||
if (self.delegate && [self.delegate respondsToSelector:@selector(didSelectOnTop:)]) {
|
||||
[self.delegate didSelectOnTop:cell];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)didSelectOnFoldConversation:(TUICommonSwitchCell *)cell {
|
||||
if (self.delegate && [self.delegate respondsToSelector:@selector(didSelectOnFoldConversation:)]) {
|
||||
[self.delegate didSelectOnFoldConversation:cell];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)didSelectOnChangeBackgroundImage:(TUICommonTextCell *)cell {
|
||||
if (self.delegate && [self.delegate respondsToSelector:@selector(didSelectOnChangeBackgroundImage:)]) {
|
||||
[self.delegate didSelectOnChangeBackgroundImage:cell];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)didDeleteGroup:(TUIButtonCell *)cell {
|
||||
if (self.delegate && [self.delegate respondsToSelector:@selector(didDeleteGroup:)]) {
|
||||
[self.delegate didDeleteGroup:cell];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)didClearAllHistory:(TUIButtonCell *)cell {
|
||||
if (self.delegate && [self.delegate respondsToSelector:@selector(didClearAllHistory:)]) {
|
||||
[self.delegate didClearAllHistory:cell];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)didSelectNotice {
|
||||
if (self.delegate && [self.delegate respondsToSelector:@selector(didSelectGroupNotice)]) {
|
||||
[self.delegate didSelectGroupNotice];
|
||||
}
|
||||
}
|
||||
- (void)didReportGroup:(TUIButtonCell *)cell {
|
||||
NSURL *url = [NSURL URLWithString:@"https://cloud.tencent.com/act/event/report-platform"];
|
||||
[TUITool openLinkWithURL:url];
|
||||
}
|
||||
|
||||
- (NSMutableArray *)getShowMembers:(NSMutableArray *)members {
|
||||
int maxCount = TGroupMembersCell_Column_Count * TGroupMembersCell_Row_Count;
|
||||
if ([self.groupInfo canInviteMember]) maxCount--;
|
||||
if ([self.groupInfo canRemoveMember]) maxCount--;
|
||||
NSMutableArray *tmpArray = [NSMutableArray array];
|
||||
|
||||
for (NSInteger i = 0; i < members.count && i < maxCount; ++i) {
|
||||
[tmpArray addObject:members[i]];
|
||||
}
|
||||
if ([self.groupInfo canInviteMember]) {
|
||||
TUIGroupMemberCellData *add = [[TUIGroupMemberCellData alloc] init];
|
||||
add.avatarImage = TUIContactCommonBundleImage(@"add");
|
||||
add.tag = 1;
|
||||
[tmpArray addObject:add];
|
||||
}
|
||||
if ([self.groupInfo canRemoveMember]) {
|
||||
TUIGroupMemberCellData *delete = [[TUIGroupMemberCellData alloc] init];
|
||||
delete.avatarImage = TUIContactCommonBundleImage(@"delete");
|
||||
delete.tag = 2;
|
||||
[tmpArray addObject:delete];
|
||||
}
|
||||
return tmpArray;
|
||||
}
|
||||
|
||||
- (void)setGroupAddOpt:(V2TIMGroupAddOpt)opt {
|
||||
@weakify(self);
|
||||
V2TIMGroupInfo *info = [[V2TIMGroupInfo alloc] init];
|
||||
info.groupID = self.groupID;
|
||||
info.groupAddOpt = opt;
|
||||
|
||||
[[V2TIMManager sharedInstance] setGroupInfo:info
|
||||
succ:^{
|
||||
@strongify(self);
|
||||
self.groupInfo.groupAddOpt = opt;
|
||||
self.addOptionData.value = [TUIGroupInfoDataProvider getAddOptionWithV2AddOpt:opt];
|
||||
}
|
||||
fail:^(int code, NSString *desc) {
|
||||
[self makeToastError:code msg:desc];
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)setGroupApproveOpt:(V2TIMGroupAddOpt)opt {
|
||||
@weakify(self);
|
||||
V2TIMGroupInfo *info = [[V2TIMGroupInfo alloc] init];
|
||||
info.groupID = self.groupID;
|
||||
info.groupApproveOpt = opt;
|
||||
|
||||
[[V2TIMManager sharedInstance] setGroupInfo:info
|
||||
succ:^{
|
||||
@strongify(self);
|
||||
self.groupInfo.groupApproveOpt = opt;
|
||||
self.inviteOptionData.value = [TUIGroupInfoDataProvider getApproveOption:self.groupInfo];
|
||||
}
|
||||
fail:^(int code, NSString *desc) {
|
||||
[self makeToastError:code msg:desc];
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)setGroupReceiveMessageOpt:(V2TIMReceiveMessageOpt)opt Succ:(V2TIMSucc)succ fail:(V2TIMFail)fail {
|
||||
[[V2TIMManager sharedInstance] setGroupReceiveMessageOpt:self.groupID opt:opt succ:succ fail:fail];
|
||||
}
|
||||
|
||||
- (void)setGroupName:(NSString *)groupName {
|
||||
V2TIMGroupInfo *info = [[V2TIMGroupInfo alloc] init];
|
||||
info.groupID = self.groupID;
|
||||
info.groupName = groupName;
|
||||
@weakify(self);
|
||||
[[V2TIMManager sharedInstance] setGroupInfo:info
|
||||
succ:^{
|
||||
@strongify(self);
|
||||
self.profileCellData.name = groupName;
|
||||
}
|
||||
fail:^(int code, NSString *msg) {
|
||||
[self makeToastError:code msg:msg];
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)setGroupNotification:(NSString *)notification {
|
||||
V2TIMGroupInfo *info = [[V2TIMGroupInfo alloc] init];
|
||||
info.groupID = self.groupID;
|
||||
info.notification = notification;
|
||||
@weakify(self);
|
||||
[[V2TIMManager sharedInstance] setGroupInfo:info
|
||||
succ:^{
|
||||
@strongify(self);
|
||||
self.profileCellData.signature = notification;
|
||||
}
|
||||
fail:^(int code, NSString *msg) {
|
||||
[self makeToastError:code msg:msg];
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)setGroupMemberNameCard:(NSString *)nameCard {
|
||||
NSString *userID = [V2TIMManager sharedInstance].getLoginUser;
|
||||
V2TIMGroupMemberFullInfo *info = [[V2TIMGroupMemberFullInfo alloc] init];
|
||||
info.userID = userID;
|
||||
info.nameCard = nameCard;
|
||||
@weakify(self);
|
||||
[[V2TIMManager sharedInstance] setGroupMemberInfo:self.groupID
|
||||
info:info
|
||||
succ:^{
|
||||
@strongify(self);
|
||||
self.groupNickNameCellData.value = nameCard;
|
||||
self.selfInfo.nameCard = nameCard;
|
||||
}
|
||||
fail:^(int code, NSString *msg) {
|
||||
[self makeToastError:code msg:msg];
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)dismissGroup:(V2TIMSucc)succ fail:(V2TIMFail)fail {
|
||||
[[V2TIMManager sharedInstance] dismissGroup:self.groupID succ:succ fail:fail];
|
||||
}
|
||||
|
||||
- (void)quitGroup:(V2TIMSucc)succ fail:(V2TIMFail)fail {
|
||||
[[V2TIMManager sharedInstance] quitGroup:self.groupID succ:succ fail:fail];
|
||||
}
|
||||
|
||||
- (void)clearAllHistory:(V2TIMSucc)succ fail:(V2TIMFail)fail {
|
||||
[V2TIMManager.sharedInstance clearGroupHistoryMessage:self.groupID succ:succ fail:fail];
|
||||
}
|
||||
|
||||
+ (NSString *)getGroupTypeName:(V2TIMGroupInfo *)groupInfo {
|
||||
if (groupInfo.groupType) {
|
||||
if ([groupInfo.groupType isEqualToString:@"Work"]) {
|
||||
return TIMCommonLocalizableString(TUIKitWorkGroup);
|
||||
} else if ([groupInfo.groupType isEqualToString:@"Public"]) {
|
||||
return TIMCommonLocalizableString(TUIKitPublicGroup);
|
||||
} else if ([groupInfo.groupType isEqualToString:@"Meeting"]) {
|
||||
return TIMCommonLocalizableString(TUIKitChatRoom);
|
||||
} else if ([groupInfo.groupType isEqualToString:@"Community"]) {
|
||||
return TIMCommonLocalizableString(TUIKitCommunity);
|
||||
}
|
||||
}
|
||||
|
||||
return @"";
|
||||
}
|
||||
|
||||
+ (NSString *)getAddOption:(V2TIMGroupInfo *)groupInfo {
|
||||
switch (groupInfo.groupAddOpt) {
|
||||
case V2TIM_GROUP_ADD_FORBID:
|
||||
return TIMCommonLocalizableString(TUIKitGroupProfileJoinDisable);
|
||||
break;
|
||||
case V2TIM_GROUP_ADD_AUTH:
|
||||
return TIMCommonLocalizableString(TUIKitGroupProfileAdminApprove);
|
||||
break;
|
||||
case V2TIM_GROUP_ADD_ANY:
|
||||
return TIMCommonLocalizableString(TUIKitGroupProfileAutoApproval);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return @"";
|
||||
}
|
||||
|
||||
+ (NSString *)getAddOptionWithV2AddOpt:(V2TIMGroupAddOpt)opt {
|
||||
switch (opt) {
|
||||
case V2TIM_GROUP_ADD_FORBID:
|
||||
return TIMCommonLocalizableString(TUIKitGroupProfileJoinDisable);
|
||||
break;
|
||||
case V2TIM_GROUP_ADD_AUTH:
|
||||
return TIMCommonLocalizableString(TUIKitGroupProfileAdminApprove);
|
||||
break;
|
||||
case V2TIM_GROUP_ADD_ANY:
|
||||
return TIMCommonLocalizableString(TUIKitGroupProfileAutoApproval);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return @"";
|
||||
}
|
||||
|
||||
+ (NSString *)getApproveOption:(V2TIMGroupInfo *)groupInfo {
|
||||
switch (groupInfo.groupApproveOpt) {
|
||||
case V2TIM_GROUP_ADD_FORBID:
|
||||
return TIMCommonLocalizableString(TUIKitGroupProfileInviteDisable);
|
||||
break;
|
||||
case V2TIM_GROUP_ADD_AUTH:
|
||||
return TIMCommonLocalizableString(TUIKitGroupProfileAdminApprove);
|
||||
break;
|
||||
case V2TIM_GROUP_ADD_ANY:
|
||||
return TIMCommonLocalizableString(TUIKitGroupProfileAutoApproval);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return @"";
|
||||
}
|
||||
|
||||
+ (BOOL)isMarkedByFoldType:(NSArray *)markList {
|
||||
for (NSNumber *num in markList) {
|
||||
if (num.unsignedLongValue == V2TIM_CONVERSATION_MARK_TYPE_FOLD) {
|
||||
return YES;
|
||||
}
|
||||
}
|
||||
return NO;
|
||||
}
|
||||
|
||||
+ (BOOL)isMarkedByHideType:(NSArray *)markList {
|
||||
for (NSNumber *num in markList) {
|
||||
if (num.unsignedLongValue == V2TIM_CONVERSATION_MARK_TYPE_HIDE) {
|
||||
return YES;
|
||||
}
|
||||
}
|
||||
return NO;
|
||||
}
|
||||
|
||||
+ (BOOL)isMeOwner:(V2TIMGroupInfo *)groupInfo {
|
||||
return [groupInfo.owner isEqualToString:[[V2TIMManager sharedInstance] getLoginUser]] || (groupInfo.role == V2TIM_GROUP_MEMBER_ROLE_ADMIN);
|
||||
}
|
||||
|
||||
+ (BOOL)isMeSuper:(V2TIMGroupInfo *)groupInfo {
|
||||
return [groupInfo.owner isEqualToString:[[V2TIMManager sharedInstance] getLoginUser]] && (groupInfo.role == V2TIM_GROUP_MEMBER_ROLE_SUPER);
|
||||
}
|
||||
@end
|
||||
19
TUIKit/TUIChat/UI_Classic/Chat/TUIGroupNoticeController.h
Normal file
19
TUIKit/TUIChat/UI_Classic/Chat/TUIGroupNoticeController.h
Normal file
@@ -0,0 +1,19 @@
|
||||
//
|
||||
// TUIGroupNoticeController.h
|
||||
// TUIGroup
|
||||
//
|
||||
// Created by harvy on 2022/1/11.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface TUIGroupNoticeController : UIViewController
|
||||
|
||||
@property(nonatomic, copy) dispatch_block_t onNoticeChanged;
|
||||
@property(nonatomic, copy) NSString *groupID;
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
114
TUIKit/TUIChat/UI_Classic/Chat/TUIGroupNoticeController.m
Normal file
114
TUIKit/TUIChat/UI_Classic/Chat/TUIGroupNoticeController.m
Normal file
@@ -0,0 +1,114 @@
|
||||
//
|
||||
// TUIGroupNoticeController.m
|
||||
// TUIGroup
|
||||
//
|
||||
// Created by harvy on 2022/1/11.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
//
|
||||
|
||||
#import "TUIGroupNoticeController.h"
|
||||
#import <TUICore/TUIThemeManager.h>
|
||||
#import "TUIGroupNoticeDataProvider.h"
|
||||
|
||||
@interface TUIGroupNoticeController ()
|
||||
|
||||
@property(nonatomic, strong) UITextView *textView;
|
||||
@property(nonatomic, weak) UIButton *rightButton;
|
||||
@property(nonatomic, strong) TUIGroupNoticeDataProvider *dataProvider;
|
||||
|
||||
@end
|
||||
|
||||
@implementation TUIGroupNoticeController
|
||||
|
||||
- (void)viewDidLoad {
|
||||
[super viewDidLoad];
|
||||
|
||||
self.view.backgroundColor = TIMCommonDynamicColor(@"controller_bg_color", @"#F2F3F5");
|
||||
[self.view addSubview:self.textView];
|
||||
|
||||
UIButton *rightBtn = [UIButton buttonWithType:UIButtonTypeCustom];
|
||||
[rightBtn setTitleColor:TIMCommonDynamicColor(@"nav_title_text_color", @"#000000") forState:UIControlStateNormal];
|
||||
[rightBtn setTitleColor:TIMCommonDynamicColor(@"nav_title_text_color", @"#000000") forState:UIControlStateSelected];
|
||||
[rightBtn setTitle:TIMCommonLocalizableString(Edit) forState:UIControlStateNormal];
|
||||
[rightBtn setTitle:TIMCommonLocalizableString(Done) forState:UIControlStateSelected];
|
||||
[rightBtn sizeToFit];
|
||||
[rightBtn addTarget:self action:@selector(onClickRight:) forControlEvents:UIControlEventTouchUpInside];
|
||||
self.rightButton = rightBtn;
|
||||
self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithCustomView:rightBtn];
|
||||
self.rightButton.hidden = YES;
|
||||
|
||||
UILabel *titleLabel = [[UILabel alloc] init];
|
||||
titleLabel.text = TIMCommonLocalizableString(TUIKitGroupNotice);
|
||||
titleLabel.font = [UIFont boldSystemFontOfSize:17.0];
|
||||
titleLabel.textColor = TIMCommonDynamicColor(@"nav_title_text_color", @"#000000");
|
||||
[titleLabel sizeToFit];
|
||||
self.navigationItem.titleView = titleLabel;
|
||||
|
||||
__weak typeof(self) weakSelf = self;
|
||||
self.dataProvider.groupID = self.groupID;
|
||||
[self.dataProvider getGroupInfo:^{
|
||||
weakSelf.textView.text = weakSelf.dataProvider.groupInfo.notification;
|
||||
weakSelf.textView.editable = NO;
|
||||
weakSelf.rightButton.hidden = !weakSelf.dataProvider.canEditNotice;
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)viewWillLayoutSubviews {
|
||||
[super viewWillLayoutSubviews];
|
||||
|
||||
self.textView.frame = self.view.bounds;
|
||||
}
|
||||
|
||||
- (void)viewDidAppear:(BOOL)animated {
|
||||
[super viewDidAppear:animated];
|
||||
|
||||
if (self.dataProvider.canEditNotice && self.textView.text.length == 0) {
|
||||
[self onClickRight:self.rightButton];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)onClickRight:(UIButton *)button {
|
||||
if (button.isSelected) {
|
||||
self.textView.editable = NO;
|
||||
[self.textView resignFirstResponder];
|
||||
[self updateNotice];
|
||||
} else {
|
||||
self.textView.editable = YES;
|
||||
[self.textView becomeFirstResponder];
|
||||
}
|
||||
button.selected = !button.isSelected;
|
||||
}
|
||||
|
||||
- (void)updateNotice {
|
||||
__weak typeof(self) weakSelf = self;
|
||||
[self.dataProvider updateNotice:self.textView.text
|
||||
callback:^(int code, NSString *desc) {
|
||||
if (code != 0) {
|
||||
[TUITool makeToastError:code msg:desc];
|
||||
return;
|
||||
}
|
||||
if (weakSelf.onNoticeChanged) {
|
||||
weakSelf.onNoticeChanged();
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
- (UITextView *)textView {
|
||||
if (_textView == nil) {
|
||||
_textView = [[UITextView alloc] init];
|
||||
_textView.textAlignment = isRTL()?NSTextAlignmentRight:NSTextAlignmentLeft;
|
||||
_textView.backgroundColor = TIMCommonDynamicColor(@"controller_bg_color", @"#F2F3F5");
|
||||
_textView.textColor = TIMCommonDynamicColor(@"form_title_color", @"#000000");
|
||||
_textView.font = [UIFont systemFontOfSize:17];
|
||||
}
|
||||
return _textView;
|
||||
}
|
||||
|
||||
- (TUIGroupNoticeDataProvider *)dataProvider {
|
||||
if (_dataProvider == nil) {
|
||||
_dataProvider = [[TUIGroupNoticeDataProvider alloc] init];
|
||||
}
|
||||
return _dataProvider;
|
||||
}
|
||||
|
||||
@end
|
||||
29
TUIKit/TUIChat/UI_Classic/Chat/TUIMediaView.h
Normal file
29
TUIKit/TUIChat/UI_Classic/Chat/TUIMediaView.h
Normal file
@@ -0,0 +1,29 @@
|
||||
|
||||
// Created by Tencent on 2023/06/09.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
/**
|
||||
* This file declares the logic used to realize the sliding display of pictures and videos
|
||||
*/
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
@import ImSDK_Plus;
|
||||
|
||||
@interface TUIMediaView : UIView
|
||||
@property(nonatomic, copy) dispatch_block_t onClose;
|
||||
|
||||
/**
|
||||
* Setting thumb, for animating
|
||||
*/
|
||||
- (void)setThumb:(UIImageView *)thumb frame:(CGRect)frame;
|
||||
|
||||
/**
|
||||
* Setting the current message that needs to be displayed. MediaView will automatically load the before and after messages of the current message for display,
|
||||
* mainly used in the scene of the normal message list
|
||||
*/
|
||||
- (void)setCurMessage:(V2TIMMessage *)curMessage;
|
||||
|
||||
/**
|
||||
* Setting the current and all messages to be displayed, mainly used in the scenario of merge-forward message lists
|
||||
*/
|
||||
- (void)setCurMessage:(V2TIMMessage *)curMessage allMessages:(NSArray *)messages;
|
||||
@end
|
||||
279
TUIKit/TUIChat/UI_Classic/Chat/TUIMediaView.m
Normal file
279
TUIKit/TUIChat/UI_Classic/Chat/TUIMediaView.m
Normal file
@@ -0,0 +1,279 @@
|
||||
//
|
||||
// MenuView.m
|
||||
// UIKit
|
||||
//
|
||||
// Created by kennethmiao on 2018/9/18.
|
||||
// Copyright © 2018 Tencent. All rights reserved.
|
||||
//
|
||||
|
||||
#import "TUIMediaView.h"
|
||||
#import <TIMCommon/TIMDefine.h>
|
||||
#import <TIMCommon/TUIMessageCell.h>
|
||||
#import <TUICore/TUIDarkModel.h>
|
||||
#import <TUICore/TUIGlobalization.h>
|
||||
#import "TUIChatConversationModel.h"
|
||||
#import "TUIImageCollectionCell.h"
|
||||
#import "TUIMessageMediaDataProvider.h"
|
||||
#import "TUIVideoCollectionCell.h"
|
||||
|
||||
#define ANIMATION_TIME 0.2
|
||||
|
||||
@interface TUIMediaView () <UICollectionViewDelegate,
|
||||
UICollectionViewDataSource,
|
||||
UICollectionViewDelegateFlowLayout,
|
||||
UIScrollViewDelegate,
|
||||
TUIMediaCollectionCellDelegate>
|
||||
@property(strong, nonatomic) TUIMessageMediaDataProvider *dataProvider;
|
||||
@property(strong, nonatomic) UICollectionView *menuCollectionView;
|
||||
@property(strong, nonatomic) UIImage *saveBackgroundImage;
|
||||
@property(strong, nonatomic) UIImage *saveShadowImage;
|
||||
@property(strong, nonatomic) UIImageView *imageView;
|
||||
@property(strong, nonatomic) UIImage *thumbImage;
|
||||
@property(strong, nonatomic) UIView *coverView;
|
||||
@property(strong, nonatomic) UIView *mediaView;
|
||||
@property(assign, nonatomic) CGRect thumbFrame;
|
||||
@property(assign, nonatomic) NSIndexPath *currentVisibleIndexPath;
|
||||
@end
|
||||
|
||||
@implementation TUIMediaView {
|
||||
V2TIMMessage *_curMessage;
|
||||
}
|
||||
|
||||
- (instancetype)init {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_currentVisibleIndexPath = [NSIndexPath indexPathForRow:0 inSection:0];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
- (void)setupViews {
|
||||
self.backgroundColor = [UIColor clearColor];
|
||||
|
||||
self.coverView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, Screen_Width * 3, Screen_Height * 3)];
|
||||
self.coverView.backgroundColor = [UIColor blackColor];
|
||||
[self addSubview:self.coverView];
|
||||
|
||||
self.mediaView = [[UIView alloc] initWithFrame:self.thumbFrame];
|
||||
self.mediaView.backgroundColor = [UIColor clearColor];
|
||||
[self addSubview:self.mediaView];
|
||||
|
||||
TUICollectionRTLFitFlowLayout *menuFlowLayout = [[TUICollectionRTLFitFlowLayout alloc] init];
|
||||
menuFlowLayout.scrollDirection = UICollectionViewScrollDirectionHorizontal;
|
||||
menuFlowLayout.minimumLineSpacing = 0;
|
||||
menuFlowLayout.minimumInteritemSpacing = 0;
|
||||
menuFlowLayout.sectionInset = UIEdgeInsetsMake(0, 0, 0, 0);
|
||||
|
||||
self.menuCollectionView = [[UICollectionView alloc] initWithFrame:self.mediaView.bounds collectionViewLayout:menuFlowLayout];
|
||||
self.menuCollectionView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
|
||||
[self.menuCollectionView registerClass:[TUIImageCollectionCell class] forCellWithReuseIdentifier:TImageMessageCell_ReuseId];
|
||||
[self.menuCollectionView registerClass:[TUIVideoCollectionCell class] forCellWithReuseIdentifier:TVideoMessageCell_ReuseId];
|
||||
self.menuCollectionView.pagingEnabled = YES;
|
||||
self.menuCollectionView.delegate = self;
|
||||
self.menuCollectionView.dataSource = self;
|
||||
self.menuCollectionView.showsHorizontalScrollIndicator = NO;
|
||||
self.menuCollectionView.showsVerticalScrollIndicator = NO;
|
||||
self.menuCollectionView.alwaysBounceHorizontal = YES;
|
||||
self.menuCollectionView.decelerationRate = UIScrollViewDecelerationRateFast;
|
||||
self.menuCollectionView.backgroundColor = [UIColor clearColor];
|
||||
self.menuCollectionView.hidden = YES;
|
||||
[self.mediaView addSubview:self.menuCollectionView];
|
||||
|
||||
self.imageView = [[UIImageView alloc] initWithFrame:self.mediaView.bounds];
|
||||
self.imageView.layer.cornerRadius = 5.0;
|
||||
[self.imageView.layer setMasksToBounds:YES];
|
||||
self.imageView.contentMode = UIViewContentModeScaleAspectFit;
|
||||
self.imageView.backgroundColor = [UIColor clearColor];
|
||||
self.imageView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
|
||||
self.imageView.image = self.thumbImage;
|
||||
[self.mediaView addSubview:self.imageView];
|
||||
|
||||
[UIView animateWithDuration:ANIMATION_TIME
|
||||
animations:^{
|
||||
self.mediaView.frame = self.bounds;
|
||||
}];
|
||||
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(ANIMATION_TIME * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
|
||||
[self.imageView removeFromSuperview];
|
||||
self.menuCollectionView.hidden = NO;
|
||||
});
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:kEnableAllRotationOrientationNotification object:nil];
|
||||
[self setupRotaionNotifications];
|
||||
}
|
||||
|
||||
- (void)setupRotaionNotifications {
|
||||
if (@available(iOS 16.0, *)) {
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self
|
||||
selector:@selector(onDeviceOrientationChange:)
|
||||
name:TUIMessageMediaViewDeviceOrientationChangeNotification
|
||||
object:nil];
|
||||
} else {
|
||||
[[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications];
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self
|
||||
selector:@selector(onDeviceOrientationChange:)
|
||||
name:UIDeviceOrientationDidChangeNotification
|
||||
object:nil];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setThumb:(UIImageView *)thumb frame:(CGRect)frame {
|
||||
self.thumbImage = thumb.image;
|
||||
self.thumbFrame = frame;
|
||||
[self setupViews];
|
||||
}
|
||||
|
||||
- (void)setCurMessage:(V2TIMMessage *)curMessage {
|
||||
_curMessage = curMessage;
|
||||
TUIChatConversationModel *model = [[TUIChatConversationModel alloc] init];
|
||||
model.userID = _curMessage.userID;
|
||||
model.groupID = _curMessage.groupID;
|
||||
self.dataProvider = [[TUIMessageMediaDataProvider alloc] initWithConversationModel:model];
|
||||
[self.dataProvider loadMediaWithMessage:_curMessage];
|
||||
|
||||
@weakify(self);
|
||||
[RACObserve(self.dataProvider, medias) subscribeNext:^(NSArray *x) {
|
||||
@strongify(self);
|
||||
[self.menuCollectionView reloadData];
|
||||
for (int i = 0; i < self.dataProvider.medias.count; i++) {
|
||||
TUIMessageCellData *data = self.dataProvider.medias[i];
|
||||
if ([data.innerMessage.msgID isEqualToString:self->_curMessage.msgID]) {
|
||||
self.currentVisibleIndexPath = [NSIndexPath indexPathForRow:i inSection:0];
|
||||
[self.menuCollectionView scrollToItemAtIndexPath:[NSIndexPath indexPathForRow:i inSection:0]
|
||||
atScrollPosition:(UICollectionViewScrollPositionLeft)animated:NO];
|
||||
return;
|
||||
}
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)setCurMessage:(V2TIMMessage *)curMessage allMessages:(NSArray<V2TIMMessage *> *)allMessages {
|
||||
_curMessage = curMessage;
|
||||
|
||||
NSMutableArray *medias = [NSMutableArray array];
|
||||
for (V2TIMMessage *message in allMessages) {
|
||||
TUIMessageCellData *data = [TUIMessageMediaDataProvider getMediaCellData:message];
|
||||
if (data) {
|
||||
[medias addObject:data];
|
||||
}
|
||||
}
|
||||
|
||||
self.dataProvider = [[TUIMessageMediaDataProvider alloc] initWithConversationModel:nil];
|
||||
self.dataProvider.medias = medias;
|
||||
|
||||
[self.menuCollectionView reloadData];
|
||||
for (int i = 0; i < self.dataProvider.medias.count; i++) {
|
||||
TUIMessageCellData *data = self.dataProvider.medias[i];
|
||||
if ([data.innerMessage.msgID isEqualToString:_curMessage.msgID]) {
|
||||
[self.menuCollectionView scrollToItemAtIndexPath:[NSIndexPath indexPathForRow:i inSection:0]
|
||||
atScrollPosition:(UICollectionViewScrollPositionLeft)animated:NO];
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
|
||||
return self.dataProvider.medias.count;
|
||||
}
|
||||
|
||||
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
|
||||
TUIMessageCellData *data = self.dataProvider.medias[indexPath.row];
|
||||
TUIMediaCollectionCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:data.reuseId forIndexPath:indexPath];
|
||||
if (cell) {
|
||||
cell.delegate = self;
|
||||
[cell fillWithData:data];
|
||||
if ([cell isKindOfClass:[TUIVideoCollectionCell class]]) {
|
||||
TUIVideoCollectionCell *videoCell = (TUIVideoCollectionCell *)cell;
|
||||
[videoCell reloadAllView];
|
||||
}
|
||||
else if ([cell isKindOfClass:[TUIImageCollectionCell class]]) {
|
||||
TUIImageCollectionCell *imageCell = (TUIImageCollectionCell *)cell;
|
||||
[imageCell reloadAllView];
|
||||
}
|
||||
else {
|
||||
//empty
|
||||
}
|
||||
}
|
||||
return cell;
|
||||
}
|
||||
|
||||
- (CGSize)collectionView:(UICollectionView *)collectionView
|
||||
layout:(UICollectionViewLayout *)collectionViewLayout
|
||||
sizeForItemAtIndexPath:(NSIndexPath *)indexPath {
|
||||
return collectionView.frame.size;
|
||||
}
|
||||
|
||||
- (void)traitCollectionDidChange:(UITraitCollection *)previousTraitCollection {
|
||||
[super traitCollectionDidChange:previousTraitCollection];
|
||||
[self.menuCollectionView.collectionViewLayout invalidateLayout];
|
||||
}
|
||||
- (CGPoint)collectionView:(UICollectionView *)collectionView targetContentOffsetForProposedContentOffset:(CGPoint)proposedContentOffset {
|
||||
UICollectionViewLayoutAttributes *attrs = [collectionView layoutAttributesForItemAtIndexPath:self.currentVisibleIndexPath];
|
||||
CGPoint newOriginForOldIndex = attrs.frame.origin;
|
||||
return newOriginForOldIndex.x == 0 ? proposedContentOffset : newOriginForOldIndex;
|
||||
}
|
||||
#pragma mark TUIMediaCollectionCellDelegate
|
||||
|
||||
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
|
||||
CGPoint center = CGPointMake(scrollView.contentOffset.x + (scrollView.frame.size.width / 2), scrollView.frame.size.height / 2);
|
||||
NSIndexPath *ip = [self.menuCollectionView indexPathForItemAtPoint:center];
|
||||
if (ip) {
|
||||
self.currentVisibleIndexPath = ip;
|
||||
}
|
||||
|
||||
NSArray *indexPaths = [self.menuCollectionView indexPathsForVisibleItems];
|
||||
NSIndexPath *indexPath = indexPaths.firstObject;
|
||||
TUIMessageCellData *data = self.dataProvider.medias[indexPath.row];
|
||||
_curMessage = data.innerMessage;
|
||||
if (indexPath.row <= self.dataProvider.pageCount / 2) {
|
||||
[self.dataProvider loadOlderMedia];
|
||||
}
|
||||
if (indexPath.row >= self.dataProvider.medias.count - self.dataProvider.pageCount / 2) {
|
||||
[self.dataProvider loadNewerMedia];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView {
|
||||
NSArray *indexPaths = [self.menuCollectionView indexPathsForVisibleItems];
|
||||
NSIndexPath *indexPath = indexPaths.firstObject;
|
||||
UICollectionViewCell *cell = [self.menuCollectionView cellForItemAtIndexPath:indexPath];
|
||||
if ([cell isKindOfClass:[TUIVideoCollectionCell class]]) {
|
||||
TUIVideoCollectionCell *videoCell = (TUIVideoCollectionCell *)cell;
|
||||
[videoCell stopVideoPlayAndSave];
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark TUIMediaCollectionCellDelegate
|
||||
- (void)onCloseMedia:(TUIMediaCollectionCell *)cell {
|
||||
if (self.onClose) {
|
||||
self.onClose();
|
||||
}
|
||||
if (self.superview) {
|
||||
[self removeFromSuperview];
|
||||
}
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:kDisableAllRotationOrientationNotification object:nil];
|
||||
}
|
||||
- (void)applyRotaionFrame {
|
||||
self.frame = CGRectMake(0, 0, Screen_Width, Screen_Height);
|
||||
self.coverView.frame = CGRectMake(0, 0, Screen_Width * 3, Screen_Height * 3);
|
||||
self.mediaView.frame = self.frame;
|
||||
self.mediaView.center = CGPointMake(self.frame.size.width / 2.0, self.frame.size.height / 2.0);
|
||||
self.menuCollectionView.frame = self.mediaView.frame;
|
||||
self.menuCollectionView.center = CGPointMake(self.frame.size.width / 2.0, self.frame.size.height / 2.0);
|
||||
[self.menuCollectionView setNeedsLayout];
|
||||
self.imageView.frame = self.mediaView.frame;
|
||||
self.imageView.center = CGPointMake(self.frame.size.width / 2.0, self.frame.size.height / 2.0);
|
||||
[self.menuCollectionView
|
||||
performBatchUpdates:^{
|
||||
}
|
||||
completion:^(BOOL finished) {
|
||||
if (finished) {
|
||||
[self.menuCollectionView scrollToItemAtIndexPath:self.currentVisibleIndexPath atScrollPosition:(UICollectionViewScrollPositionLeft)animated:NO];
|
||||
}
|
||||
}];
|
||||
return;
|
||||
}
|
||||
|
||||
- (void)onDeviceOrientationChange:(NSNotification *)noti {
|
||||
[UIView performWithoutAnimation:^{
|
||||
[self applyRotaionFrame];
|
||||
}];
|
||||
}
|
||||
@end
|
||||
@@ -0,0 +1,27 @@
|
||||
//
|
||||
// TUIMergeMessageListController.h
|
||||
// Pods
|
||||
//
|
||||
// Created by harvy on 2020/12/9.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
//
|
||||
|
||||
#import <TIMCommon/TIMDefine.h>
|
||||
#import <UIKit/UIKit.h>
|
||||
#import "TUIBaseMessageControllerDelegate.h"
|
||||
#import "TUIChatConversationModel.h"
|
||||
#import "TUIMessageDataProvider.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface TUIMergeMessageListController : UITableViewController
|
||||
|
||||
@property(nonatomic, weak) id<TUIBaseMessageControllerDelegate> delegate;
|
||||
@property(nonatomic, strong) V2TIMMergerElem *mergerElem;
|
||||
@property(nonatomic, copy) dispatch_block_t willCloseCallback;
|
||||
@property(nonatomic, strong) TUIChatConversationModel *conversationData;
|
||||
@property(nonatomic, strong) TUIMessageDataProvider *parentPageDataProvider;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
599
TUIKit/TUIChat/UI_Classic/Chat/TUIMergeMessageListController.m
Normal file
599
TUIKit/TUIChat/UI_Classic/Chat/TUIMergeMessageListController.m
Normal file
@@ -0,0 +1,599 @@
|
||||
//
|
||||
// TUIMergeMessageListController.m
|
||||
// Pods
|
||||
//
|
||||
// Created by harvy on 2020/12/9.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
//
|
||||
|
||||
#import "TUIMergeMessageListController.h"
|
||||
#import <TIMCommon/TIMDefine.h>
|
||||
#import <TIMCommon/TUISystemMessageCell.h>
|
||||
#import <TUICore/TUICore.h>
|
||||
#import <TUICore/TUIDarkModel.h>
|
||||
#import <TUICore/TUIGlobalization.h>
|
||||
#import <TUICore/TUIThemeManager.h>
|
||||
#import "TUIFaceMessageCell.h"
|
||||
#import "TUIFileMessageCell.h"
|
||||
#import "TUIFileViewController.h"
|
||||
#import "TUIImageMessageCell.h"
|
||||
#import "TUIJoinGroupMessageCell.h"
|
||||
#import "TUILinkCell.h"
|
||||
#import "TUIMediaView.h"
|
||||
#import "TUIMergeMessageCell.h"
|
||||
#import "TUIMessageDataProvider.h"
|
||||
#import "TUIMessageSearchDataProvider.h"
|
||||
#import "TUIReferenceMessageCell.h"
|
||||
#import "TUIRepliesDetailViewController.h"
|
||||
#import "TUIReplyMessageCell.h"
|
||||
#import "TUIReplyMessageCellData.h"
|
||||
#import "TUITextMessageCell.h"
|
||||
#import "TUIVideoMessageCell.h"
|
||||
#import "TUIVoiceMessageCell.h"
|
||||
#import "TUIMessageCellConfig.h"
|
||||
#import "TUIChatConfig.h"
|
||||
|
||||
#define STR(x) @ #x
|
||||
|
||||
@interface TUIMergeMessageListController () <TUIMessageCellDelegate, TUIMessageBaseDataProviderDataSource,TUINotificationProtocol>
|
||||
@property(nonatomic, strong) NSArray<V2TIMMessage *> *imMsgs;
|
||||
@property(nonatomic, strong) NSMutableArray<TUIMessageCellData *> *uiMsgs;
|
||||
@property(nonatomic, strong) NSMutableDictionary *stylesCache;
|
||||
@property(nonatomic, strong) TUIMessageSearchDataProvider *msgDataProvider;
|
||||
|
||||
@property(nonatomic, strong) TUIMessageCellConfig *messageCellConfig;
|
||||
|
||||
@end
|
||||
|
||||
@implementation TUIMergeMessageListController
|
||||
|
||||
- (instancetype)init {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
[TUICore registerEvent:TUICore_TUIPluginNotify
|
||||
subKey:TUICore_TUIPluginNotify_DidChangePluginViewSubKey
|
||||
object:self];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)viewDidLoad {
|
||||
[super viewDidLoad];
|
||||
_uiMsgs = [[NSMutableArray alloc] init];
|
||||
[self loadMessages];
|
||||
[self setupViews];
|
||||
}
|
||||
|
||||
- (void)viewWillDisappear:(BOOL)animated {
|
||||
[self updateCellStyle:YES];
|
||||
if (self.willCloseCallback) {
|
||||
self.willCloseCallback();
|
||||
}
|
||||
}
|
||||
|
||||
- (void)updateCellStyle:(BOOL)recover {
|
||||
if (recover) {
|
||||
TUIMessageCellLayout.incommingMessageLayout.avatarInsets = UIEdgeInsetsFromString(self.stylesCache[STR(incomingAvatarInsets)]);
|
||||
TUIMessageCellLayout.incommingTextMessageLayout.avatarInsets = UIEdgeInsetsFromString(self.stylesCache[STR(incomingAvatarInsets)]);
|
||||
TUIMessageCellLayout.incommingVoiceMessageLayout.avatarInsets = UIEdgeInsetsFromString(self.stylesCache[STR(incomingAvatarInsets)]);
|
||||
TUIMessageCellLayout.incommingMessageLayout.messageInsets = UIEdgeInsetsFromString(self.stylesCache[STR(incommingMessageInsets)]);
|
||||
[TUITextMessageCell setOutgoingTextColor:self.stylesCache[STR(outgoingTextColor)]];
|
||||
[TUITextMessageCell setIncommingTextColor:self.stylesCache[STR(incomingTextColor)]];
|
||||
return;
|
||||
}
|
||||
|
||||
UIEdgeInsets incomingAvatarInsets = TUIMessageCellLayout.incommingTextMessageLayout.avatarInsets;
|
||||
TUIMessageCellLayout.incommingMessageLayout.avatarInsets = UIEdgeInsetsMake(0, 10, 0, 10);
|
||||
TUIMessageCellLayout.incommingTextMessageLayout.avatarInsets = UIEdgeInsetsMake(0, 10, 0, 10);
|
||||
TUIMessageCellLayout.incommingVoiceMessageLayout.avatarInsets = UIEdgeInsetsMake(0, 10, 0, 10);
|
||||
[self.stylesCache setObject:NSStringFromUIEdgeInsets(incomingAvatarInsets) forKey:STR(incomingAvatarInsets)];
|
||||
|
||||
UIEdgeInsets incommingMessageInsets = TUIMessageCellLayout.incommingMessageLayout.messageInsets;
|
||||
TUIMessageCellLayout.incommingMessageLayout.messageInsets = UIEdgeInsetsMake(5, 5, 0, 0);
|
||||
[self.stylesCache setObject:NSStringFromUIEdgeInsets(incommingMessageInsets) forKey:STR(incommingMessageInsets)];
|
||||
|
||||
UIColor *outgoingTextColor = [TUITextMessageCell outgoingTextColor];
|
||||
[TUITextMessageCell setOutgoingTextColor:TUIChatDynamicColor(@"chat_text_message_send_text_color", @"#000000")];
|
||||
[self.stylesCache setObject:outgoingTextColor forKey:STR(outgoingTextColor)];
|
||||
|
||||
UIColor *incomingTextColor = [TUITextMessageCell incommingTextColor];
|
||||
[TUITextMessageCell setIncommingTextColor:TUIChatDynamicColor(@"chat_text_message_receive_text_color", @"#000000")];
|
||||
[self.stylesCache setObject:incomingTextColor forKey:STR(incomingTextColor)];
|
||||
}
|
||||
|
||||
- (void)loadMessages {
|
||||
@weakify(self);
|
||||
[self.mergerElem
|
||||
downloadMergerMessage:^(NSArray<V2TIMMessage *> *msgs) {
|
||||
@strongify(self);
|
||||
self.imMsgs = msgs;
|
||||
[self updateCellStyle:NO];
|
||||
[self getMessages:self.imMsgs];
|
||||
}
|
||||
fail:^(int code, NSString *desc) {
|
||||
[self updateCellStyle:NO];
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)getMessages:(NSArray *)msgs {
|
||||
NSMutableArray *uiMsgs = [self transUIMsgFromIMMsg:msgs];
|
||||
@weakify(self);
|
||||
[self.msgDataProvider preProcessMessage:uiMsgs
|
||||
callback:^{
|
||||
@strongify(self);
|
||||
@weakify(self);
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
@strongify(self);
|
||||
if (uiMsgs.count != 0) {
|
||||
NSIndexSet *indexSet = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, uiMsgs.count)];
|
||||
[self.uiMsgs insertObjects:uiMsgs atIndexes:indexSet];
|
||||
[self.tableView reloadData];
|
||||
[self.tableView layoutIfNeeded];
|
||||
}
|
||||
});
|
||||
}];
|
||||
}
|
||||
|
||||
- (NSMutableArray *)transUIMsgFromIMMsg:(NSArray *)msgs {
|
||||
NSMutableArray *uiMsgs = [NSMutableArray array];
|
||||
for (NSInteger k = 0; k < msgs.count; k++) {
|
||||
V2TIMMessage *msg = msgs[k];
|
||||
if ([self.delegate respondsToSelector:@selector(messageController:onNewMessage:)]) {
|
||||
TUIMessageCellData *data = [self.delegate messageController:nil onNewMessage:msg];
|
||||
if (data) {
|
||||
TUIMessageCellLayout *layout = TUIMessageCellLayout.incommingMessageLayout;
|
||||
if ([data isKindOfClass:TUITextMessageCellData.class] || [data isKindOfClass:TUIReferenceMessageCellData.class]) {
|
||||
layout = TUIMessageCellLayout.incommingTextMessageLayout;
|
||||
} else if ([data isKindOfClass:TUIVoiceMessageCellData.class]) {
|
||||
layout = TUIMessageCellLayout.incommingVoiceMessageLayout;
|
||||
}
|
||||
data.cellLayout = layout;
|
||||
data.direction = MsgDirectionIncoming;
|
||||
data.innerMessage = msg;
|
||||
data.showName = YES;
|
||||
[uiMsgs addObject:data];
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
TUIMessageCellData *data = [TUIMessageDataProvider getCellData:msg];
|
||||
if (!data) {
|
||||
continue;
|
||||
}
|
||||
TUIMessageCellLayout *layout = TUIMessageCellLayout.incommingMessageLayout;
|
||||
if ([data isKindOfClass:TUITextMessageCellData.class]) {
|
||||
layout = TUIMessageCellLayout.incommingTextMessageLayout;
|
||||
} else if ([data isKindOfClass:TUIReplyMessageCellData.class] || [data isKindOfClass:TUIReferenceMessageCellData.class]) {
|
||||
layout = TUIMessageCellLayout.incommingTextMessageLayout;
|
||||
TUIReferenceMessageCellData *textData = (TUIReferenceMessageCellData *)data;
|
||||
textData.textColor = TUIChatDynamicColor(@"chat_text_message_receive_text_color", @"#000000");
|
||||
textData.showRevokedOriginMessage = YES;
|
||||
} else if ([data isKindOfClass:TUIVoiceMessageCellData.class]) {
|
||||
TUIVoiceMessageCellData *voiceData = (TUIVoiceMessageCellData *)data;
|
||||
voiceData.cellLayout = [TUIMessageCellLayout incommingVoiceMessageLayout];
|
||||
voiceData.voiceImage = [[TUIImageCache sharedInstance] getResourceFromCache:TUIChatImagePath(@"message_voice_receiver_normal")];
|
||||
voiceData.voiceAnimationImages =
|
||||
[NSArray arrayWithObjects:[[TUIImageCache sharedInstance] getResourceFromCache:TUIChatImagePath(@"message_voice_receiver_playing_1")],
|
||||
[[TUIImageCache sharedInstance] getResourceFromCache:TUIChatImagePath(@"message_voice_receiver_playing_2")],
|
||||
[[TUIImageCache sharedInstance] getResourceFromCache:TUIChatImagePath(@"message_voice_receiver_playing_3")], nil];
|
||||
voiceData.voiceTop = 10;
|
||||
msg.localCustomInt = 1;
|
||||
layout = TUIMessageCellLayout.incommingVoiceMessageLayout;
|
||||
}
|
||||
data.cellLayout = layout;
|
||||
data.direction = MsgDirectionIncoming;
|
||||
data.innerMessage = msg;
|
||||
data.showName = YES;
|
||||
[uiMsgs addObject:data];
|
||||
}
|
||||
return uiMsgs;
|
||||
}
|
||||
|
||||
- (void)setupViews {
|
||||
self.title = TIMCommonLocalizableString(TUIKitRelayChatHistory);
|
||||
self.tableView.scrollsToTop = NO;
|
||||
self.tableView.estimatedRowHeight = 0;
|
||||
[self.tableView setSeparatorStyle:UITableViewCellSeparatorStyleNone];
|
||||
self.tableView.backgroundColor = TUIChatDynamicColor(@"chat_controller_bg_color", @"#FFFFFF");
|
||||
self.tableView.contentInset = UIEdgeInsetsMake(5, 0, 0, 0);
|
||||
[self.messageCellConfig bindTableView:self.tableView];
|
||||
}
|
||||
|
||||
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
|
||||
return _uiMsgs.count;
|
||||
}
|
||||
|
||||
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
|
||||
static CGFloat screenWidth = 0;
|
||||
if (screenWidth == 0) {
|
||||
screenWidth = Screen_Width;
|
||||
}
|
||||
if (indexPath.row < _uiMsgs.count) {
|
||||
TUIMessageCellData *cellData = _uiMsgs[indexPath.row];
|
||||
CGFloat height = [self.messageCellConfig getHeightFromMessageCellData:cellData];
|
||||
return height;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
|
||||
TUIMessageCellData *data = _uiMsgs[indexPath.row];
|
||||
data.showMessageTime = YES;
|
||||
data.showCheckBox = NO;
|
||||
TUIMessageCell *cell = nil;
|
||||
if ([self.delegate respondsToSelector:@selector(messageController:onShowMessageData:)]) {
|
||||
cell = [self.delegate messageController:nil onShowMessageData:data];
|
||||
if (cell) {
|
||||
cell.delegate = self;
|
||||
return cell;
|
||||
}
|
||||
}
|
||||
cell = [tableView dequeueReusableCellWithIdentifier:data.reuseId forIndexPath:indexPath];
|
||||
cell.delegate = self;
|
||||
[cell fillWithData:_uiMsgs[indexPath.row]];
|
||||
return cell;
|
||||
}
|
||||
|
||||
#pragma mark - TUIMessageCellDelegate
|
||||
- (void)onSelectMessage:(TUIMessageCell *)cell {
|
||||
|
||||
if (TUIChatConfig.defaultConfig.eventConfig.chatEventListener &&
|
||||
[TUIChatConfig.defaultConfig.eventConfig.chatEventListener respondsToSelector:@selector(onMessageClicked:messageCellData:)]) {
|
||||
BOOL result = [TUIChatConfig.defaultConfig.eventConfig.chatEventListener onMessageClicked:cell messageCellData:cell.messageData];
|
||||
if (result) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if ([cell isKindOfClass:[TUIImageMessageCell class]]) {
|
||||
[self showImageMessage:(TUIImageMessageCell *)cell];
|
||||
}
|
||||
if ([cell isKindOfClass:[TUIVoiceMessageCell class]]) {
|
||||
[self playVoiceMessage:(TUIVoiceMessageCell *)cell];
|
||||
}
|
||||
if ([cell isKindOfClass:[TUIVideoMessageCell class]]) {
|
||||
[self showVideoMessage:(TUIVideoMessageCell *)cell];
|
||||
}
|
||||
if ([cell isKindOfClass:[TUIFileMessageCell class]]) {
|
||||
[self showFileMessage:(TUIFileMessageCell *)cell];
|
||||
}
|
||||
if ([cell isKindOfClass:[TUIMergeMessageCell class]]) {
|
||||
TUIMergeMessageListController *mergeVc = [[TUIMergeMessageListController alloc] init];
|
||||
mergeVc.mergerElem = [(TUIMergeMessageCell *)cell mergeData].mergerElem;
|
||||
mergeVc.delegate = self.delegate;
|
||||
[self.navigationController pushViewController:mergeVc animated:YES];
|
||||
}
|
||||
if ([cell isKindOfClass:[TUILinkCell class]]) {
|
||||
[self showLinkMessage:(TUILinkCell *)cell];
|
||||
}
|
||||
if ([cell isKindOfClass:[TUIReplyMessageCell class]]) {
|
||||
[self showReplyMessage:(TUIReplyMessageCell *)cell];
|
||||
}
|
||||
if ([cell isKindOfClass:[TUIReferenceMessageCell class]]) {
|
||||
[self showReplyMessage:(TUIReplyMessageCell *)cell];
|
||||
}
|
||||
|
||||
if ([self.delegate respondsToSelector:@selector(messageController:onSelectMessageContent:)]) {
|
||||
[self.delegate messageController:nil onSelectMessageContent:cell];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
- (BOOL)checkIfMessageExistsInLocal:(V2TIMMessage *)locateMessage {
|
||||
NSInteger index = 0;
|
||||
for (TUIMessageCellData *uiMsg in self.uiMsgs) {
|
||||
if ([uiMsg.innerMessage.msgID isEqualToString:locateMessage.msgID]) {
|
||||
return YES;
|
||||
}
|
||||
index++;
|
||||
}
|
||||
if (index == self.uiMsgs.count) {
|
||||
return NO;
|
||||
}
|
||||
return NO;
|
||||
}
|
||||
- (void)scrollToLocateMessage:(V2TIMMessage *)locateMessage matchKeyword:(NSString *)msgAbstract {
|
||||
CGFloat offsetY = 0;
|
||||
NSInteger index = 0;
|
||||
for (TUIMessageCellData *uiMsg in self.uiMsgs) {
|
||||
if ([uiMsg.innerMessage.msgID isEqualToString:locateMessage.msgID]) {
|
||||
break;
|
||||
}
|
||||
offsetY += [uiMsg heightOfWidth:Screen_Width];
|
||||
index++;
|
||||
}
|
||||
|
||||
if (index == self.uiMsgs.count) {
|
||||
return;
|
||||
}
|
||||
|
||||
offsetY -= self.tableView.frame.size.height / 2.0;
|
||||
if (offsetY <= TMessageController_Header_Height) {
|
||||
offsetY = TMessageController_Header_Height + 0.1;
|
||||
}
|
||||
|
||||
if (offsetY > TMessageController_Header_Height) {
|
||||
[self.tableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:index inSection:0]
|
||||
atScrollPosition:UITableViewScrollPositionMiddle
|
||||
animated:YES];
|
||||
}
|
||||
|
||||
[self highlightKeyword:msgAbstract locateMessage:locateMessage];
|
||||
}
|
||||
|
||||
- (void)highlightKeyword:(NSString *)keyword locateMessage:(V2TIMMessage *)locateMessage {
|
||||
TUIMessageCellData *cellData = nil;
|
||||
for (TUIMessageCellData *tmp in self.uiMsgs) {
|
||||
if ([tmp.msgID isEqualToString:locateMessage.msgID]) {
|
||||
cellData = tmp;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (cellData == nil) {
|
||||
return;
|
||||
}
|
||||
|
||||
CGFloat time = 0.5;
|
||||
UITableViewRowAnimation animation = UITableViewRowAnimationFade;
|
||||
if ([cellData isKindOfClass:TUITextMessageCellData.class]) {
|
||||
time = 2;
|
||||
animation = UITableViewRowAnimationNone;
|
||||
}
|
||||
|
||||
@weakify(self);
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
@strongify(self);
|
||||
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:[self.uiMsgs indexOfObject:cellData] inSection:0];
|
||||
cellData.highlightKeyword = keyword;
|
||||
TUIMessageCell *cell = [self.tableView cellForRowAtIndexPath:indexPath];
|
||||
[cell fillWithData:cellData];
|
||||
});
|
||||
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(time * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
|
||||
@strongify(self);
|
||||
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:[self.uiMsgs indexOfObject:cellData] inSection:0];
|
||||
cellData.highlightKeyword = nil;
|
||||
TUIMessageCell *cell = [self.tableView cellForRowAtIndexPath:indexPath];
|
||||
[cell fillWithData:cellData];
|
||||
});
|
||||
}
|
||||
- (void)showReplyMessage:(TUIReplyMessageCell *)cell {
|
||||
[UIApplication.sharedApplication.keyWindow endEditing:YES];
|
||||
NSString *originMsgID = @"";
|
||||
NSString *msgAbstract = @"";
|
||||
if ([cell isKindOfClass:TUIReplyMessageCell.class]) {
|
||||
TUIReplyMessageCell *acell = (TUIReplyMessageCell *)cell;
|
||||
TUIReplyMessageCellData *cellData = acell.replyData;
|
||||
originMsgID = cellData.messageRootID;
|
||||
msgAbstract = cellData.msgAbstract;
|
||||
} else if ([cell isKindOfClass:TUIReferenceMessageCell.class]) {
|
||||
TUIReferenceMessageCell *acell = (TUIReferenceMessageCell *)cell;
|
||||
TUIReferenceMessageCellData *cellData = acell.referenceData;
|
||||
originMsgID = cellData.originMsgID;
|
||||
msgAbstract = cellData.msgAbstract;
|
||||
}
|
||||
|
||||
|
||||
TUIMessageCellData *originMemoryMessageData = nil;
|
||||
for (TUIMessageCellData *uiMsg in self.uiMsgs) {
|
||||
if ([uiMsg.innerMessage.msgID isEqualToString:originMsgID]) {
|
||||
originMemoryMessageData = uiMsg;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (originMemoryMessageData && [cell isKindOfClass:TUIReplyMessageCell.class]) {
|
||||
[self onJumpToRepliesDetailPage:originMemoryMessageData];
|
||||
}
|
||||
else {
|
||||
[(TUIMessageSearchDataProvider *)self.msgDataProvider
|
||||
findMessages:@[ originMsgID ?: @"" ]
|
||||
callback:^(BOOL success, NSString *_Nonnull desc, NSArray<V2TIMMessage *> *_Nonnull msgs) {
|
||||
if (!success) {
|
||||
[TUITool makeToast:TIMCommonLocalizableString(TUIKitReplyMessageNotFoundOriginMessage)];
|
||||
return;
|
||||
}
|
||||
V2TIMMessage *message = msgs.firstObject;
|
||||
if (message == nil) {
|
||||
[TUITool makeToast:TIMCommonLocalizableString(TUIKitReplyMessageNotFoundOriginMessage)];
|
||||
return;
|
||||
}
|
||||
|
||||
if (message.status == V2TIM_MSG_STATUS_HAS_DELETED || message.status == V2TIM_MSG_STATUS_LOCAL_REVOKED) {
|
||||
[TUITool makeToast:TIMCommonLocalizableString(TUIKitReplyMessageNotFoundOriginMessage)];
|
||||
return;
|
||||
}
|
||||
|
||||
if ([cell isKindOfClass:TUIReplyMessageCell.class]) {
|
||||
[self jumpDetailPageByMessage:message];
|
||||
} else if ([cell isKindOfClass:TUIReferenceMessageCell.class]) {
|
||||
BOOL existInLocal = [self checkIfMessageExistsInLocal:message];
|
||||
if (!existInLocal) {
|
||||
[TUITool makeToast:TIMCommonLocalizableString(TUIKitReplyMessageNotFoundOriginMessage)];
|
||||
return;
|
||||
}
|
||||
[self scrollToLocateMessage:message matchKeyword:msgAbstract];
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
- (void)jumpDetailPageByMessage:(V2TIMMessage *)message {
|
||||
NSMutableArray *uiMsgs = [self transUIMsgFromIMMsg:@[ message ]];
|
||||
[self.msgDataProvider preProcessMessage:uiMsgs
|
||||
callback:^{
|
||||
for (TUIMessageCellData *cellData in uiMsgs) {
|
||||
if ([cellData.innerMessage.msgID isEqual:message.msgID]) {
|
||||
[self onJumpToRepliesDetailPage:cellData];
|
||||
return;
|
||||
}
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)onJumpToRepliesDetailPage:(TUIMessageCellData *)data {
|
||||
TUIRepliesDetailViewController *repliesDetailVC = [[TUIRepliesDetailViewController alloc] initWithCellData:data conversationData:self.conversationData];
|
||||
repliesDetailVC.delegate = self.delegate;
|
||||
[self.navigationController pushViewController:repliesDetailVC animated:YES];
|
||||
repliesDetailVC.parentPageDataProvider = self.parentPageDataProvider;
|
||||
__weak typeof(self) weakSelf = self;
|
||||
repliesDetailVC.willCloseCallback = ^() {
|
||||
[weakSelf.tableView reloadData];
|
||||
};
|
||||
}
|
||||
|
||||
- (void)showImageMessage:(TUIImageMessageCell *)cell {
|
||||
CGRect frame = [cell.thumb convertRect:cell.thumb.bounds toView:[UIApplication sharedApplication].delegate.window];
|
||||
TUIMediaView *mediaView = [[TUIMediaView alloc] initWithFrame:CGRectMake(0, 0, Screen_Width, Screen_Height)];
|
||||
[mediaView setThumb:cell.thumb frame:frame];
|
||||
[mediaView setCurMessage:cell.messageData.innerMessage allMessages:self.imMsgs];
|
||||
[[UIApplication sharedApplication].keyWindow addSubview:mediaView];
|
||||
}
|
||||
|
||||
- (void)playVoiceMessage:(TUIVoiceMessageCell *)cell {
|
||||
for (NSInteger index = 0; index < _uiMsgs.count; ++index) {
|
||||
if (![_uiMsgs[index] isKindOfClass:[TUIVoiceMessageCellData class]]) {
|
||||
continue;
|
||||
}
|
||||
TUIVoiceMessageCellData *uiMsg = (TUIVoiceMessageCellData *)_uiMsgs[index];
|
||||
if (uiMsg == cell.voiceData) {
|
||||
[uiMsg playVoiceMessage];
|
||||
cell.voiceReadPoint.hidden = YES;
|
||||
} else {
|
||||
[uiMsg stopVoiceMessage];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)showVideoMessage:(TUIVideoMessageCell *)cell {
|
||||
CGRect frame = [cell.thumb convertRect:cell.thumb.bounds toView:[UIApplication sharedApplication].delegate.window];
|
||||
TUIMediaView *mediaView = [[TUIMediaView alloc] initWithFrame:CGRectMake(0, 0, Screen_Width, Screen_Height)];
|
||||
[mediaView setThumb:cell.thumb frame:frame];
|
||||
[mediaView setCurMessage:cell.messageData.innerMessage allMessages:self.imMsgs];
|
||||
[[UIApplication sharedApplication].keyWindow addSubview:mediaView];
|
||||
}
|
||||
|
||||
- (void)showFileMessage:(TUIFileMessageCell *)cell {
|
||||
TUIFileViewController *file = [[TUIFileViewController alloc] init];
|
||||
file.data = [cell fileData];
|
||||
[self.navigationController pushViewController:file animated:YES];
|
||||
}
|
||||
|
||||
- (void)showLinkMessage:(TUILinkCell *)cell {
|
||||
TUILinkCellData *cellData = cell.customData;
|
||||
if (cellData.link) {
|
||||
[[UIApplication sharedApplication] openURL:[NSURL URLWithString:cellData.link]];
|
||||
}
|
||||
}
|
||||
|
||||
- (NSMutableDictionary *)stylesCache {
|
||||
if (_stylesCache == nil) {
|
||||
_stylesCache = [NSMutableDictionary dictionary];
|
||||
}
|
||||
return _stylesCache;
|
||||
}
|
||||
|
||||
- (UIImage *)bubbleImage {
|
||||
CGRect rect = CGRectMake(0, 0, 100, 40);
|
||||
UIGraphicsBeginImageContext(rect.size);
|
||||
CGContextRef context = UIGraphicsGetCurrentContext();
|
||||
CGContextSetFillColorWithColor(context, [[UIColor clearColor] CGColor]);
|
||||
CGContextFillRect(context, rect);
|
||||
UIImage *img = UIGraphicsGetImageFromCurrentImageContext();
|
||||
UIGraphicsEndImageContext();
|
||||
return img;
|
||||
}
|
||||
|
||||
- (TUIMessageSearchDataProvider *)msgDataProvider {
|
||||
if (_msgDataProvider == nil) {
|
||||
_msgDataProvider = [[TUIMessageSearchDataProvider alloc] init];
|
||||
_msgDataProvider.dataSource = self;
|
||||
}
|
||||
return _msgDataProvider;
|
||||
}
|
||||
|
||||
- (TUIMessageCellConfig *)messageCellConfig {
|
||||
if (_messageCellConfig == nil) {
|
||||
_messageCellConfig = [[TUIMessageCellConfig alloc] init];
|
||||
}
|
||||
return _messageCellConfig;
|
||||
}
|
||||
|
||||
#pragma mark - TUIMessageBaseDataProviderDataSource
|
||||
- (void)dataProviderDataSourceWillChange:(TUIMessageBaseDataProvider *)dataProvider {
|
||||
// do nothing
|
||||
}
|
||||
|
||||
- (void)dataProviderDataSourceChange:(TUIMessageBaseDataProvider *)dataProvider
|
||||
withType:(TUIMessageBaseDataProviderDataSourceChangeType)type
|
||||
atIndex:(NSUInteger)index
|
||||
animation:(BOOL)animation {
|
||||
// do nothing
|
||||
}
|
||||
|
||||
- (void)dataProviderDataSourceDidChange:(TUIMessageBaseDataProvider *)dataProvider {
|
||||
[self.tableView reloadData];
|
||||
}
|
||||
|
||||
- (void)dataProvider:(TUIMessageBaseDataProvider *)dataProvider onRemoveHeightCache:(TUIMessageCellData *)cellData {
|
||||
if (cellData) {
|
||||
[self.messageCellConfig removeHeightCacheOfMessageCellData:cellData];
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - TUINotificationProtocol
|
||||
- (void)onNotifyEvent:(NSString *)key subKey:(NSString *)subKey object:(id)anObject param:(NSDictionary *)param {
|
||||
if ([key isEqualToString:TUICore_TUIPluginNotify] && [subKey isEqualToString:TUICore_TUIPluginNotify_DidChangePluginViewSubKey]) {
|
||||
TUIMessageCellData *data = param[TUICore_TUIPluginNotify_DidChangePluginViewSubKey_Data];
|
||||
[self.messageCellConfig removeHeightCacheOfMessageCellData:data];
|
||||
[self reloadAndScrollToBottomOfMessage:data.innerMessage.msgID section:0];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)reloadAndScrollToBottomOfMessage:(NSString *)messageID section:(NSInteger)section {
|
||||
// Dispatch the task to RunLoop to ensure that they are executed after the UITableView refresh is complete.
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[self reloadCellOfMessage:messageID section:section];
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[self scrollCellToBottomOfMessage:messageID section:section];
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
- (void)reloadCellOfMessage:(NSString *)messageID section:(NSInteger)section {
|
||||
NSIndexPath *indexPath = [self indexPathOfMessage:messageID section:section];
|
||||
|
||||
// Disable animation when loading to avoid cell jumping.
|
||||
if (indexPath == nil) {
|
||||
return;
|
||||
}
|
||||
[UIView performWithoutAnimation:^{
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[self.tableView reloadRowsAtIndexPaths:@[ indexPath ] withRowAnimation:UITableViewRowAnimationNone];
|
||||
});
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)scrollCellToBottomOfMessage:(NSString *)messageID section:(NSInteger)section {
|
||||
NSIndexPath *indexPath = [self indexPathOfMessage:messageID section:section];
|
||||
|
||||
// Scroll the tableView only if the bottom of the cell is invisible.
|
||||
CGRect cellRect = [self.tableView rectForRowAtIndexPath:indexPath];
|
||||
CGRect tableViewRect = self.tableView.bounds;
|
||||
BOOL isBottomInvisible = cellRect.origin.y < CGRectGetMaxY(tableViewRect) && CGRectGetMaxY(cellRect) > CGRectGetMaxY(tableViewRect);
|
||||
if (isBottomInvisible) {
|
||||
[self.tableView scrollToRowAtIndexPath:indexPath atScrollPosition:UITableViewScrollPositionBottom animated:YES];
|
||||
}
|
||||
}
|
||||
|
||||
- (NSIndexPath *)indexPathOfMessage:(NSString *)messageID section:(NSInteger)section {
|
||||
for (int i = 0; i < self.uiMsgs.count; i++) {
|
||||
TUIMessageCellData *data = self.uiMsgs[i];
|
||||
if ([data.innerMessage.msgID isEqualToString:messageID]) {
|
||||
return [NSIndexPath indexPathForRow:i inSection:section];
|
||||
}
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
@end
|
||||
106
TUIKit/TUIChat/UI_Classic/Chat/TUIMessageCellConfig.h
Normal file
106
TUIKit/TUIChat/UI_Classic/Chat/TUIMessageCellConfig.h
Normal file
@@ -0,0 +1,106 @@
|
||||
// Created by Tencent on 2023/07/20.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
@class TUIMessageCellData;
|
||||
|
||||
typedef NSString * TUICellDataClassName;
|
||||
typedef NSString * TUICellClassName;
|
||||
typedef NSString * TUIBusinessID;
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface TUIMessageCellConfig : NSObject
|
||||
/**
|
||||
* 1.
|
||||
* Bind the message tableView, and TUIMessageCellConfig will perform built-in and customized cell bindings for the tableView internally.
|
||||
*/
|
||||
- (void)bindTableView:(UITableView *)tableView;
|
||||
|
||||
/**
|
||||
* 2.
|
||||
* Bind cell and cellData to the tableView, and the binding can be called externally.
|
||||
*/
|
||||
- (void)bindMessageCellClass:(Class)cellClass cellDataClass:(Class)cellDataClass reuseID:(NSString *)reuseID;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@interface TUIMessageCellConfig (MessageCellWidth)
|
||||
+ (void)setMaxTextSize:(CGSize)maxTextSz;
|
||||
@end
|
||||
|
||||
@interface TUIMessageCellConfig (MessageCellHeight)
|
||||
|
||||
/**
|
||||
* Get key of cache
|
||||
*/
|
||||
- (NSString *)getHeightCacheKey:(TUIMessageCellData *)msg;
|
||||
|
||||
/**
|
||||
* Get the height, and the cache will be queried first.
|
||||
*
|
||||
*/
|
||||
- (CGFloat)getHeightFromMessageCellData:(TUIMessageCellData *)cellData;
|
||||
|
||||
/**
|
||||
* Get the estimated height, and the cache will be queried first.
|
||||
*/
|
||||
- (CGFloat)getEstimatedHeightFromMessageCellData:(TUIMessageCellData *)cellData;
|
||||
|
||||
/**
|
||||
* Remove cache
|
||||
*/
|
||||
- (void)removeHeightCacheOfMessageCellData:(TUIMessageCellData *)cellData;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@interface TUIMessageCellConfig (CustomMessageRegister)
|
||||
|
||||
/**
|
||||
* Register custom message
|
||||
*
|
||||
* @param messageCellName cell
|
||||
* @param messageCellDataName cellData
|
||||
* @param businessID
|
||||
*/
|
||||
+ (void)registerCustomMessageCell:(TUICellClassName)messageCellName
|
||||
messageCellData:(TUICellDataClassName)messageCellDataName
|
||||
forBusinessID:(TUIBusinessID)businessID;
|
||||
|
||||
/**
|
||||
* Register custom message (This is for internal use of TUIKit plugin, please use registerCustomMessageCell:messageCellData:forBusinessID: instead.)
|
||||
*
|
||||
* @param messageCellName (custom message cell name)
|
||||
* @param messageCellDataName (custom message cell data)
|
||||
* @param businessID (custom message businessID)
|
||||
* @param isPlugin (From Plugin)
|
||||
*/
|
||||
+ (void)registerCustomMessageCell:(TUICellClassName)messageCellName
|
||||
messageCellData:(TUICellDataClassName)messageCellDataName
|
||||
forBusinessID:(TUIBusinessID)businessID
|
||||
isPlugin:(BOOL)isPlugin;
|
||||
|
||||
/**
|
||||
* Get all custom message UI with enumeration
|
||||
*/
|
||||
+ (void)enumerateCustomMessageInfo:(void(^)(NSString *messageCellName,
|
||||
NSString *messageCellDataName,
|
||||
NSString *businessID,
|
||||
BOOL isPlugin))callback;
|
||||
|
||||
/**
|
||||
* Get the class of custom message cellData based on businessID
|
||||
*/
|
||||
+ (nullable Class)getCustomMessageCellDataClass:(NSString *)businessID;
|
||||
|
||||
/**
|
||||
* Whether the custom message cell data is defined in plugin
|
||||
*/
|
||||
+ (BOOL)isPluginCustomMessageCellData:(TUIMessageCellData *)data;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
251
TUIKit/TUIChat/UI_Classic/Chat/TUIMessageCellConfig.m
Normal file
251
TUIKit/TUIChat/UI_Classic/Chat/TUIMessageCellConfig.m
Normal file
@@ -0,0 +1,251 @@
|
||||
// Created by Tencent on 2023/07/20.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
|
||||
#import "TUIMessageCellConfig.h"
|
||||
|
||||
#import <TUICore/TUIThemeManager.h>
|
||||
#import <TIMCommon/TUIMessageCell.h>
|
||||
#import <TIMCommon/TUIMessageCellData.h>
|
||||
#import <TIMCommon/TUISystemMessageCell.h>
|
||||
#import <TIMCommon/TUISystemMessageCellData.h>
|
||||
|
||||
#import "TUIMessageDataProvider.h"
|
||||
|
||||
#import "TUIFaceMessageCell.h"
|
||||
#import "TUIFaceMessageCellData.h"
|
||||
#import "TUIFileMessageCell.h"
|
||||
#import "TUIFileMessageCellData.h"
|
||||
#import "TUIImageMessageCell.h"
|
||||
#import "TUIImageMessageCellData.h"
|
||||
#import "TUIJoinGroupMessageCell.h"
|
||||
#import "TUIJoinGroupMessageCellData.h"
|
||||
#import "TUIMergeMessageCell.h"
|
||||
#import "TUIMergeMessageCellData.h"
|
||||
#import "TUIReferenceMessageCell.h"
|
||||
#import "TUIReplyMessageCell.h"
|
||||
#import "TUIReplyMessageCellData.h"
|
||||
#import "TUITextMessageCell.h"
|
||||
#import "TUITextMessageCellData.h"
|
||||
#import "TUIVideoMessageCell.h"
|
||||
#import "TUIVideoMessageCellData.h"
|
||||
#import "TUIVoiceMessageCell.h"
|
||||
#import "TUIVoiceMessageCellData.h"
|
||||
|
||||
#define kIsCustomMessageFromPlugin @"kIsCustomMessageFromPlugin"
|
||||
|
||||
typedef Class<TUIMessageCellProtocol> CellClass;
|
||||
typedef NSString * MessageID;
|
||||
typedef NSNumber * HeightNumber;
|
||||
|
||||
static NSMutableDictionary *gCustomMessageInfoMap = nil;
|
||||
|
||||
@interface TUIMessageCellConfig ()
|
||||
|
||||
@property(nonatomic, weak) UITableView *tableView;
|
||||
@property(nonatomic, strong) NSMutableDictionary<TUICellDataClassName, CellClass> *cellClassMaps;
|
||||
@property(nonatomic, strong) NSMutableDictionary<MessageID, HeightNumber> *heightCacheMaps;
|
||||
|
||||
@end
|
||||
|
||||
@implementation TUIMessageCellConfig (MessageCellWidth)
|
||||
+ (void)setMaxTextSize:(CGSize)maxTextSz {
|
||||
[TUITextMessageCell setMaxTextSize:maxTextSz];
|
||||
}
|
||||
@end
|
||||
#pragma mark - Custom Message Register
|
||||
@implementation TUIMessageCellConfig (CustomMessageRegister)
|
||||
|
||||
+ (NSMutableDictionary *)getCustomMessageInfoMap {
|
||||
if (gCustomMessageInfoMap == nil) {
|
||||
gCustomMessageInfoMap = [NSMutableDictionary dictionary];
|
||||
}
|
||||
return gCustomMessageInfoMap;
|
||||
}
|
||||
|
||||
+ (void)registerBuiltInCustomMessageInfo {
|
||||
[self registerCustomMessageCell:@"TUILinkCell" messageCellData:@"TUILinkCellData" forBusinessID:BussinessID_TextLink];
|
||||
[self registerCustomMessageCell:@"TUIGroupCreatedCell" messageCellData:@"TUIGroupCreatedCellData" forBusinessID:BussinessID_GroupCreate];
|
||||
[self registerCustomMessageCell:@"TUIEvaluationCell" messageCellData:@"TUIEvaluationCellData" forBusinessID:BussinessID_Evaluation];
|
||||
[self registerCustomMessageCell:@"TUIOrderCell" messageCellData:@"TUIOrderCellData" forBusinessID:BussinessID_Order];
|
||||
[self registerCustomMessageCell:@"TUIMessageCell" messageCellData:@"TUITypingStatusCellData" forBusinessID:BussinessID_Typing];
|
||||
[self registerCustomMessageCell:@"TUISystemMessageCell" messageCellData:@"TUILocalTipsCellData" forBusinessID:@"local_tips"];
|
||||
|
||||
}
|
||||
|
||||
+ (void)registerExternalCustomMessageInfo {
|
||||
// Insert your own custom message UI here, your businessID can not be same with built-in
|
||||
//
|
||||
// Example:
|
||||
// [self registerCustomMessageCell:#your message cell# messageCellData:#your message cell data# forBusinessID:#your id#];
|
||||
//
|
||||
// ...
|
||||
}
|
||||
|
||||
+ (void)registerCustomMessageCell:(TUICellClassName)messageCellName
|
||||
messageCellData:(TUICellDataClassName)messageCellDataName
|
||||
forBusinessID:(TUIBusinessID)businessID {
|
||||
[self registerCustomMessageCell:messageCellName messageCellData:messageCellDataName forBusinessID:businessID isPlugin:NO];
|
||||
}
|
||||
|
||||
+ (void)registerCustomMessageCell:(TUICellClassName)messageCellName
|
||||
messageCellData:(TUICellDataClassName)messageCellDataName
|
||||
forBusinessID:(TUIBusinessID)businessID
|
||||
isPlugin:(BOOL)isPlugin {
|
||||
NSAssert(messageCellName.length > 0, @"message cell name can not be nil");
|
||||
NSAssert(messageCellDataName.length > 0, @"message cell data name can not be nil");
|
||||
NSAssert(businessID.length > 0, @"businessID can not be nil");
|
||||
NSAssert([[self getCustomMessageInfoMap] objectForKey:businessID] == nil, @"businessID can not be same with the exists");
|
||||
|
||||
NSMutableDictionary *info = [NSMutableDictionary dictionary];
|
||||
info[BussinessID] = businessID;
|
||||
info[TMessageCell_Name] = messageCellName;
|
||||
info[TMessageCell_Data_Name] = messageCellDataName;
|
||||
info[kIsCustomMessageFromPlugin] = @(isPlugin);
|
||||
[[self getCustomMessageInfoMap] setObject:info forKey:businessID];
|
||||
}
|
||||
|
||||
+ (void)enumerateCustomMessageInfo:(void(^)(NSString *messageCellName,
|
||||
NSString *messageCellDataName,
|
||||
NSString *businessID,
|
||||
BOOL isPlugin))callback {
|
||||
if (callback == nil) {
|
||||
return;
|
||||
}
|
||||
[[self getCustomMessageInfoMap] enumerateKeysAndObjectsUsingBlock:^(TUIBusinessID key, NSDictionary *info, BOOL * _Nonnull stop) {
|
||||
NSString *businessID = info[BussinessID];
|
||||
NSString *messageCellName = info[TMessageCell_Name];
|
||||
NSString *messageCellDataName = info[TMessageCell_Data_Name];
|
||||
NSNumber *isPlugin = info[kIsCustomMessageFromPlugin];
|
||||
if (businessID && messageCellName && messageCellDataName && isPlugin) {
|
||||
callback(messageCellName, messageCellDataName, businessID, isPlugin);
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
+ (nullable Class)getCustomMessageCellDataClass:(NSString *)businessID {
|
||||
NSDictionary *info = [[self getCustomMessageInfoMap] objectForKey:businessID];
|
||||
if (info == nil) {
|
||||
return nil;
|
||||
}
|
||||
NSString *messageCellDataName = info[TMessageCell_Data_Name];
|
||||
Class messageCellDataClass = NSClassFromString(messageCellDataName);
|
||||
return messageCellDataClass;
|
||||
}
|
||||
|
||||
+ (BOOL)isPluginCustomMessageCellData:(TUIMessageCellData *)data {
|
||||
__block BOOL flag = NO;
|
||||
[[self getCustomMessageInfoMap] enumerateKeysAndObjectsUsingBlock:^(TUIBusinessID key, NSDictionary *info, BOOL * _Nonnull stop) {
|
||||
NSString *businessID = info[BussinessID];
|
||||
NSNumber *isPlugin = info[kIsCustomMessageFromPlugin];
|
||||
if (businessID && isPlugin && isPlugin.boolValue && [data.reuseId isEqualToString:businessID]) {
|
||||
flag = YES;
|
||||
*stop = YES;
|
||||
}
|
||||
}];
|
||||
return flag;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
||||
#pragma mark - Cell Message cell height
|
||||
@implementation TUIMessageCellConfig (MessageCellHeight)
|
||||
|
||||
- (NSString *)getHeightCacheKey:(TUIMessageCellData *)msg {
|
||||
return msg.msgID.length == 0 ? [NSString stringWithFormat:@"%p", msg] : msg.msgID;
|
||||
}
|
||||
|
||||
- (CGFloat)getHeightFromMessageCellData:(TUIMessageCellData *)cellData {
|
||||
static CGFloat screenWidth = 0;
|
||||
if (screenWidth == 0) {
|
||||
screenWidth = Screen_Width;
|
||||
}
|
||||
NSString *key = [self getHeightCacheKey:cellData];
|
||||
CGFloat height = [[self.heightCacheMaps objectForKey:key] floatValue];
|
||||
if (height == 0) {
|
||||
CellClass cellClass = [self.cellClassMaps objectForKey:NSStringFromClass(cellData.class)];
|
||||
if ([cellClass respondsToSelector:@selector(getHeight:withWidth:)]) {
|
||||
height = [cellClass getHeight:cellData withWidth:screenWidth];
|
||||
[self.heightCacheMaps setObject:@(height) forKey:key];
|
||||
}
|
||||
}
|
||||
return height;
|
||||
}
|
||||
|
||||
- (CGFloat)getEstimatedHeightFromMessageCellData:(TUIMessageCellData *)cellData {
|
||||
NSString *key = [self getHeightCacheKey:cellData];
|
||||
CGFloat height = [[self.heightCacheMaps objectForKey:key] floatValue];
|
||||
return height > 0 ? height : UITableViewAutomaticDimension;;
|
||||
}
|
||||
|
||||
- (void)removeHeightCacheOfMessageCellData:(TUIMessageCellData *)cellData {
|
||||
NSString *key = [self getHeightCacheKey:cellData];
|
||||
[self.heightCacheMaps removeObjectForKey:key];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
||||
#pragma mark - TUIMessageTable
|
||||
|
||||
@implementation TUIMessageCellConfig
|
||||
|
||||
#pragma mark - Life Cycle
|
||||
+ (void)load {
|
||||
[self registerBuiltInCustomMessageInfo];
|
||||
[self registerExternalCustomMessageInfo];
|
||||
}
|
||||
|
||||
#pragma mark - UI
|
||||
|
||||
- (void)bindTableView:(UITableView *)tableView {
|
||||
self.tableView = tableView;
|
||||
|
||||
[self bindMessageCellClass:TUITextMessageCell.class cellDataClass:TUITextMessageCellData.class reuseID:TTextMessageCell_ReuseId];
|
||||
[self bindMessageCellClass:TUIVoiceMessageCell.class cellDataClass:TUIVoiceMessageCellData.class reuseID:TVoiceMessageCell_ReuseId];
|
||||
[self bindMessageCellClass:TUIImageMessageCell.class cellDataClass:TUIImageMessageCellData.class reuseID:TImageMessageCell_ReuseId];
|
||||
[self bindMessageCellClass:TUISystemMessageCell.class cellDataClass:TUISystemMessageCellData.class reuseID:TSystemMessageCell_ReuseId];
|
||||
[self bindMessageCellClass:TUIFaceMessageCell.class cellDataClass:TUIFaceMessageCellData.class reuseID:TFaceMessageCell_ReuseId];
|
||||
[self bindMessageCellClass:TUIVideoMessageCell.class cellDataClass:TUIVideoMessageCellData.class reuseID:TVideoMessageCell_ReuseId];
|
||||
[self bindMessageCellClass:TUIFileMessageCell.class cellDataClass:TUIFileMessageCellData.class reuseID:TFileMessageCell_ReuseId];
|
||||
[self bindMessageCellClass:TUIJoinGroupMessageCell.class cellDataClass:TUIJoinGroupMessageCellData.class reuseID:TJoinGroupMessageCell_ReuseId];
|
||||
[self bindMessageCellClass:TUIMergeMessageCell.class cellDataClass:TUIMergeMessageCellData.class reuseID:TMergeMessageCell_ReuserId];
|
||||
[self bindMessageCellClass:TUIReplyMessageCell.class cellDataClass:TUIReplyMessageCellData.class reuseID:TReplyMessageCell_ReuseId];
|
||||
[self bindMessageCellClass:TUIReferenceMessageCell.class cellDataClass:TUIReferenceMessageCellData.class reuseID:TUIReferenceMessageCell_ReuseId];
|
||||
__weak typeof(self) weakSelf = self;
|
||||
[self.class enumerateCustomMessageInfo:^(NSString * _Nonnull messageCellName,
|
||||
NSString * _Nonnull messageCellDataName,
|
||||
NSString * _Nonnull businessID,
|
||||
BOOL isPlugin) {
|
||||
Class cellClass = NSClassFromString(messageCellName);
|
||||
Class cellDataClass = NSClassFromString(messageCellDataName);
|
||||
[weakSelf bindMessageCellClass:cellClass cellDataClass:cellDataClass reuseID:businessID];
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)bindMessageCellClass:(Class)cellClass cellDataClass:(Class)cellDataClass reuseID:(NSString *)reuseID {
|
||||
NSAssert(cellClass != nil, @"The UITableViewCell can not be nil");
|
||||
NSAssert(cellDataClass != nil, @"The cell data class can not be nil");
|
||||
NSAssert(reuseID.length > 0, @"The reuse identifier can not be nil");
|
||||
|
||||
[self.tableView registerClass:cellClass forCellReuseIdentifier:reuseID];
|
||||
[self.cellClassMaps setObject:cellClass forKey:NSStringFromClass(cellDataClass)];
|
||||
}
|
||||
|
||||
#pragma mark - Lazy && Read-write operate
|
||||
- (NSMutableDictionary<TUICellDataClassName,CellClass> *)cellClassMaps {
|
||||
if (_cellClassMaps == nil) {
|
||||
_cellClassMaps = [NSMutableDictionary dictionary];
|
||||
}
|
||||
return _cellClassMaps;
|
||||
}
|
||||
|
||||
- (NSMutableDictionary<MessageID,HeightNumber> *)heightCacheMaps {
|
||||
if (_heightCacheMaps == nil) {
|
||||
_heightCacheMaps = [NSMutableDictionary dictionary];
|
||||
}
|
||||
return _heightCacheMaps;
|
||||
}
|
||||
|
||||
|
||||
@end
|
||||
30
TUIKit/TUIChat/UI_Classic/Chat/TUIMessageController.h
Normal file
30
TUIKit/TUIChat/UI_Classic/Chat/TUIMessageController.h
Normal file
@@ -0,0 +1,30 @@
|
||||
|
||||
// Created by Tencent on 2023/06/09.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
|
||||
#import <TIMCommon/TIMDefine.h>
|
||||
#import "TUIBaseMessageController.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface TUIMessageController : TUIBaseMessageController
|
||||
|
||||
/**
|
||||
* Highlight text
|
||||
* In the search scenario, when highlightKeyword is not empty and matches @locateMessage, opening the chat session page will highlight the current cell
|
||||
*/
|
||||
@property(nonatomic, copy) NSString *hightlightKeyword;
|
||||
|
||||
/**
|
||||
* Locate message
|
||||
* In the search scenario, when locateMessage is not empty, opening the chat session page will automatically scroll to here
|
||||
*/
|
||||
@property(nonatomic, strong) V2TIMMessage *locateMessage;
|
||||
|
||||
@property(nonatomic, strong) V2TIMMessage *C2CIncomingLastMsg;
|
||||
|
||||
- (void)locateAssignMessage:(V2TIMMessage *)message matchKeyWord:(NSString *)keyword;
|
||||
- (void)findMessages:(NSArray<NSString *> *)msgIDs callback:(void (^)(BOOL success, NSString *desc, NSArray<V2TIMMessage *> *messages))callback;
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
587
TUIKit/TUIChat/UI_Classic/Chat/TUIMessageController.m
Normal file
587
TUIKit/TUIChat/UI_Classic/Chat/TUIMessageController.m
Normal file
@@ -0,0 +1,587 @@
|
||||
|
||||
// Created by Tencent on 2023/06/09.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
|
||||
#import "TUIMessageController.h"
|
||||
#import <TIMCommon/TIMDefine.h>
|
||||
#import <TUICore/TUIGlobalization.h>
|
||||
#import <TUICore/TUIThemeManager.h>
|
||||
#import <TUICore/UIView+TUILayout.h>
|
||||
#import "TUIBaseMessageController+ProtectedAPI.h"
|
||||
#import "TUIChatConfig.h"
|
||||
#import "TUIChatModifyMessageHelper.h"
|
||||
#import "TUIChatSmallTongueView.h"
|
||||
#import "TUIMessageSearchDataProvider.h"
|
||||
#import "TUIReferenceMessageCell.h"
|
||||
#import "TUIReplyMessageCell.h"
|
||||
#import "TUIReplyMessageCellData.h"
|
||||
#import "TUITextMessageCell.h"
|
||||
|
||||
#define MSG_GET_COUNT 20
|
||||
|
||||
@interface TUIMessageController () <TUIChatSmallTongueViewDelegate>
|
||||
@property(nonatomic, strong) UIActivityIndicatorView *bottomIndicatorView;
|
||||
@property(nonatomic, assign) uint64_t locateGroupMessageSeq;
|
||||
@property(nonatomic, strong) TUIChatSmallTongueView *tongueView;
|
||||
@property(nonatomic, strong) NSMutableArray *receiveMsgs;
|
||||
@property(nonatomic, weak) UIImageView *backgroudView;
|
||||
@end
|
||||
|
||||
@implementation TUIMessageController
|
||||
|
||||
#pragma mark - Life Cycle
|
||||
- (void)viewDidLoad {
|
||||
[super viewDidLoad];
|
||||
|
||||
self.bottomIndicatorView =
|
||||
[[UIActivityIndicatorView alloc] initWithFrame:CGRectMake(0, 0, self.tableView.frame.size.width, TMessageController_Header_Height)];
|
||||
self.bottomIndicatorView.activityIndicatorViewStyle = UIActivityIndicatorViewStyleGray;
|
||||
self.tableView.tableFooterView = self.bottomIndicatorView;
|
||||
|
||||
self.tableView.backgroundColor = UIColor.clearColor;
|
||||
|
||||
[NSNotificationCenter.defaultCenter addObserver:self selector:@selector(keyboardWillShow) name:UIKeyboardWillShowNotification object:nil];
|
||||
[NSNotificationCenter.defaultCenter addObserver:self selector:@selector(onBottomMarginChanged:)
|
||||
name:TUIKitNotification_onMessageVCBottomMarginChanged object:nil];
|
||||
|
||||
self.receiveMsgs = [NSMutableArray array];
|
||||
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
if (self.conversationData.atMsgSeqs.count > 0) {
|
||||
TUIChatSmallTongue *tongue = [[TUIChatSmallTongue alloc] init];
|
||||
tongue.type = TUIChatSmallTongueType_SomeoneAt;
|
||||
tongue.parentView = self.view.superview;
|
||||
tongue.atTipsStr = self.conversationData.atTipsStr;
|
||||
tongue.atMsgSeqs = [self.conversationData.atMsgSeqs copy];
|
||||
[TUIChatSmallTongueManager showTongue:tongue delegate:self];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
- (void)dealloc {
|
||||
[TUIChatSmallTongueManager removeTongue];
|
||||
[NSNotificationCenter.defaultCenter removeObserver:self];
|
||||
NSLog(@"%s dealloc", __FUNCTION__);
|
||||
}
|
||||
|
||||
- (void)viewWillAppear:(BOOL)animated {
|
||||
[super viewWillAppear:animated];
|
||||
[TUIChatSmallTongueManager hideTongue:NO];
|
||||
}
|
||||
|
||||
- (void)viewWillDisappear:(BOOL)animated {
|
||||
[super viewWillDisappear:animated];
|
||||
[TUIChatSmallTongueManager hideTongue:YES];
|
||||
}
|
||||
|
||||
#pragma mark - Notification
|
||||
|
||||
- (void)keyboardWillShow {
|
||||
if (![self messageSearchDataProvider].isNewerNoMoreMsg) {
|
||||
[[self messageSearchDataProvider] removeAllSearchData];
|
||||
[self.tableView reloadData];
|
||||
[self loadMessages:YES];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)onBottomMarginChanged:(NSNotification *)notification {
|
||||
NSDictionary *userInfo = notification.userInfo;
|
||||
if ([userInfo.allKeys containsObject:TUIKitNotification_onMessageVCBottomMarginChanged_Margin] &&
|
||||
[userInfo[TUIKitNotification_onMessageVCBottomMarginChanged_Margin] isKindOfClass:NSNumber.class]) {
|
||||
float margin = [userInfo[TUIKitNotification_onMessageVCBottomMarginChanged_Margin] floatValue];
|
||||
[TUIChatSmallTongueManager adaptTongueBottomMargin:margin];
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - Overrider
|
||||
- (void)willShowMediaMessage:(TUIMessageCell *)cell {
|
||||
[TUIChatSmallTongueManager hideTongue:YES];
|
||||
}
|
||||
|
||||
- (void)didCloseMediaMessage:(TUIMessageCell *)cell {
|
||||
[TUIChatSmallTongueManager hideTongue:NO];
|
||||
}
|
||||
|
||||
#pragma mark - UIScrollViewDelegate
|
||||
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
|
||||
[super scrollViewDidScroll:scrollView];
|
||||
if (scrollView.contentOffset.y <= TMessageController_Header_Height
|
||||
&& (scrollView.isTracking || scrollView.isDecelerating)
|
||||
&& ![self messageSearchDataProvider].isOlderNoMoreMsg
|
||||
&& !self.indicatorView.isAnimating) {
|
||||
// Display pull-to-refresh icon
|
||||
[self.indicatorView startAnimating];
|
||||
} else if ([self isScrollToBottomIndicatorViewY:scrollView]) {
|
||||
if ((scrollView.isTracking || scrollView.isDecelerating)
|
||||
&& ![self messageSearchDataProvider].isNewerNoMoreMsg
|
||||
&& !self.bottomIndicatorView.isAnimating) {
|
||||
// Display pull-to-refresh icon
|
||||
[self.bottomIndicatorView startAnimating];
|
||||
}
|
||||
/**
|
||||
* Remove the "back to the latest position", "xxx new message" bottom-banner-tips
|
||||
*/
|
||||
if (self.isInVC) {
|
||||
[TUIChatSmallTongueManager removeTongue:TUIChatSmallTongueType_ScrollToBoom];
|
||||
[TUIChatSmallTongueManager removeTongue:TUIChatSmallTongueType_ReceiveNewMsg];
|
||||
}
|
||||
} else if (self.isInVC && 0 == self.receiveMsgs.count
|
||||
&& self.tableView.contentSize.height - self.tableView.contentOffset.y >= Screen_Height * 2.0) {
|
||||
CGPoint point = [scrollView.panGestureRecognizer translationInView:scrollView];
|
||||
/**
|
||||
* When swiping, add a "back to last position" bottom-banner-tips
|
||||
*/
|
||||
if (point.y > 0) {
|
||||
TUIChatSmallTongue *tongue = [[TUIChatSmallTongue alloc] init];
|
||||
tongue.type = TUIChatSmallTongueType_ScrollToBoom;
|
||||
tongue.parentView = self.view.superview;
|
||||
[TUIChatSmallTongueManager showTongue:tongue delegate:self];
|
||||
}
|
||||
} else if (self.isInVC && self.tableView.contentSize.height - self.tableView.contentOffset.y >= 20) {
|
||||
/**
|
||||
* Remove the "someone @ me" bottom-banner-tips
|
||||
*/
|
||||
[TUIChatSmallTongueManager removeTongue:TUIChatSmallTongueType_SomeoneAt];
|
||||
} else {
|
||||
if (self.indicatorView.isAnimating) {
|
||||
[self.indicatorView stopAnimating];
|
||||
}
|
||||
if (self.bottomIndicatorView.isAnimating) {
|
||||
[self.bottomIndicatorView stopAnimating];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
|
||||
[super scrollViewDidEndDecelerating:scrollView];
|
||||
if (scrollView.contentOffset.y <= TMessageController_Header_Height && ![self messageSearchDataProvider].isOlderNoMoreMsg) {
|
||||
/**
|
||||
* Pull old news
|
||||
*/
|
||||
[self loadMessages:YES];
|
||||
} else if ([self isScrollToBottomIndicatorViewY:scrollView] && ![self messageSearchDataProvider].isNewerNoMoreMsg) {
|
||||
/**
|
||||
* Load latese message
|
||||
*/
|
||||
[self loadMessages:NO];
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL)isScrollToBottomIndicatorViewY:(UIScrollView *)scrollView {
|
||||
/**
|
||||
* +2 pixels when scrolling to critical point
|
||||
*/
|
||||
return (scrollView.contentOffset.y + self.tableView.mm_h + 2) > (scrollView.contentSize.height - self.indicatorView.mm_h);
|
||||
}
|
||||
|
||||
#pragma mark - Getters & Setters
|
||||
- (void)setConversation:(TUIChatConversationModel *)conversationData {
|
||||
self.conversationData = conversationData;
|
||||
self.messageDataProvider = [[TUIMessageSearchDataProvider alloc] initWithConversationModel:self.conversationData];
|
||||
self.messageDataProvider.dataSource = self;
|
||||
if (self.locateMessage) {
|
||||
[self loadAndScrollToLocateMessages:NO isHighlight:YES];
|
||||
} else {
|
||||
[[self messageSearchDataProvider] removeAllSearchData];
|
||||
[self loadMessages:YES];
|
||||
}
|
||||
[self loadGroupInfo];
|
||||
}
|
||||
|
||||
#pragma mark - Private Methods
|
||||
- (TUIMessageSearchDataProvider *)messageSearchDataProvider {
|
||||
return (TUIMessageSearchDataProvider *)self.messageDataProvider;
|
||||
}
|
||||
|
||||
- (void)loadAndScrollToLocateMessages:(BOOL)scrollToBoom isHighlight:(BOOL)isHighlight{
|
||||
if (!self.locateMessage && self.locateGroupMessageSeq == 0) {
|
||||
return;
|
||||
}
|
||||
@weakify(self);
|
||||
[[self messageSearchDataProvider]
|
||||
loadMessageWithSearchMsg:self.locateMessage
|
||||
SearchMsgSeq:self.locateGroupMessageSeq
|
||||
ConversationInfo:self.conversationData
|
||||
SucceedBlock:^(BOOL isOlderNoMoreMsg, BOOL isNewerNoMoreMsg, NSArray<TUIMessageCellData *> *_Nonnull newMsgs) {
|
||||
@strongify(self);
|
||||
[self.indicatorView stopAnimating];
|
||||
[self.bottomIndicatorView stopAnimating];
|
||||
self.indicatorView.mm_h = 0;
|
||||
self.bottomIndicatorView.mm_h = 0;
|
||||
|
||||
[self.tableView reloadData];
|
||||
[self.tableView layoutIfNeeded];
|
||||
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[self scrollToLocateMessage:scrollToBoom];
|
||||
if (isHighlight) {
|
||||
[self highlightKeyword];
|
||||
}
|
||||
});
|
||||
}
|
||||
FailBlock:^(int code, NSString *desc){}];
|
||||
}
|
||||
|
||||
- (void)scrollToLocateMessage:(BOOL)scrollToBoom {
|
||||
/**
|
||||
* First find the coordinate offset of locateMsg
|
||||
*/
|
||||
CGFloat offsetY = 0;
|
||||
NSInteger index = 0;
|
||||
for (TUIMessageCellData *uiMsg in [self messageSearchDataProvider].uiMsgs) {
|
||||
if ([self isLocateMessage:uiMsg]) {
|
||||
break;
|
||||
}
|
||||
offsetY += [self getHeightFromMessageCellData:uiMsg];
|
||||
index++;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* The locateMsg not found
|
||||
*/
|
||||
if (index == [self messageSearchDataProvider].uiMsgs.count) {
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* tableview
|
||||
* Offset half the height of the tableview
|
||||
*/
|
||||
offsetY -= self.tableView.frame.size.height / 2.0;
|
||||
if (offsetY <= TMessageController_Header_Height) {
|
||||
offsetY = TMessageController_Header_Height + 0.1;
|
||||
}
|
||||
|
||||
if (offsetY > TMessageController_Header_Height) {
|
||||
[self.tableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:index inSection:0]
|
||||
atScrollPosition:UITableViewScrollPositionMiddle
|
||||
animated:YES];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)highlightKeyword {
|
||||
TUIMessageCellData *cellData = nil;
|
||||
for (TUIMessageCellData *tmp in [self messageSearchDataProvider].uiMsgs) {
|
||||
if ([self isLocateMessage:tmp]) {
|
||||
cellData = tmp;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (cellData == nil || cellData.innerMessage.elemType == V2TIM_ELEM_TYPE_GROUP_TIPS) {
|
||||
return;
|
||||
}
|
||||
|
||||
@weakify(self);
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
@strongify(self);
|
||||
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:[[self messageDataProvider].uiMsgs indexOfObject:cellData] inSection:0];
|
||||
cellData.highlightKeyword = self.hightlightKeyword.length ? self.hightlightKeyword : @"hightlight";
|
||||
TUIMessageCell *cell = [self.tableView cellForRowAtIndexPath:indexPath];
|
||||
[cell fillWithData:cellData];
|
||||
@weakify(self);
|
||||
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
|
||||
@strongify(self);
|
||||
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:[[self messageDataProvider].uiMsgs indexOfObject:cellData] inSection:0];
|
||||
cellData.highlightKeyword = nil;
|
||||
TUIMessageCell *cell = [self.tableView cellForRowAtIndexPath:indexPath];
|
||||
[cell fillWithData:cellData];
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
- (BOOL)isLocateMessage:(TUIMessageCellData *)uiMsg {
|
||||
if (self.locateMessage) {
|
||||
if ([uiMsg.innerMessage.msgID isEqualToString:self.locateMessage.msgID]) {
|
||||
return YES;
|
||||
}
|
||||
} else {
|
||||
if (self.conversationData.groupID.length > 0 && uiMsg.innerMessage && uiMsg.innerMessage.seq == self.locateGroupMessageSeq) {
|
||||
return YES;
|
||||
}
|
||||
}
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (void)loadMessages:(BOOL)order {
|
||||
if ([self messageSearchDataProvider].isLoadingData) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (order && [self messageSearchDataProvider].isOlderNoMoreMsg) {
|
||||
[self.indicatorView stopAnimating];
|
||||
return;
|
||||
}
|
||||
if (!order && [self messageSearchDataProvider].isNewerNoMoreMsg) {
|
||||
[self.bottomIndicatorView stopAnimating];
|
||||
return;
|
||||
}
|
||||
|
||||
@weakify(self);
|
||||
[[self messageSearchDataProvider]
|
||||
loadMessageWithIsRequestOlderMsg:order
|
||||
ConversationInfo:self.conversationData
|
||||
SucceedBlock:^(BOOL isOlderNoMoreMsg, BOOL isNewerNoMoreMsg, BOOL isFirstLoad, NSArray<TUIMessageCellData *> *_Nonnull newUIMsgs) {
|
||||
@strongify(self);
|
||||
|
||||
[self.indicatorView stopAnimating];
|
||||
[self.bottomIndicatorView stopAnimating];
|
||||
if (isOlderNoMoreMsg) {
|
||||
self.indicatorView.mm_h = 0;
|
||||
} else {
|
||||
self.indicatorView.mm_h = TMessageController_Header_Height;
|
||||
}
|
||||
if (isNewerNoMoreMsg) {
|
||||
self.bottomIndicatorView.mm_h = 0;
|
||||
} else {
|
||||
self.bottomIndicatorView.mm_h = TMessageController_Header_Height;
|
||||
}
|
||||
|
||||
[self.tableView reloadData];
|
||||
[self.tableView layoutIfNeeded];
|
||||
[newUIMsgs enumerateObjectsWithOptions:NSEnumerationReverse
|
||||
usingBlock:^(TUIMessageCellData *_Nonnull obj, NSUInteger idx, BOOL *_Nonnull stop) {
|
||||
if (obj.direction == MsgDirectionIncoming) {
|
||||
self.C2CIncomingLastMsg = obj.innerMessage;
|
||||
*stop = YES;
|
||||
}
|
||||
}];
|
||||
|
||||
if (isFirstLoad) {
|
||||
[self scrollToBottom:NO];
|
||||
} else {
|
||||
if (order) {
|
||||
NSInteger index = 0;
|
||||
if (newUIMsgs.count > 0) {
|
||||
index = newUIMsgs.count - 1;
|
||||
}
|
||||
if (self.messageDataProvider.uiMsgs.count > 0) {
|
||||
[self.tableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:index inSection:0]
|
||||
atScrollPosition:UITableViewScrollPositionTop
|
||||
animated:NO];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
FailBlock:^(int code, NSString *desc){
|
||||
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)showReplyMessage:(TUIReplyMessageCell *)cell {
|
||||
NSString *originMsgID = @"";
|
||||
NSString *msgAbstract = @"";
|
||||
if ([cell isKindOfClass:TUIReplyMessageCell.class]) {
|
||||
TUIReplyMessageCell *acell = (TUIReplyMessageCell *)cell;
|
||||
TUIReplyMessageCellData *cellData = acell.replyData;
|
||||
originMsgID = cellData.messageRootID;
|
||||
msgAbstract = cellData.msgAbstract;
|
||||
} else if ([cell isKindOfClass:TUIReferenceMessageCell.class]) {
|
||||
TUIReferenceMessageCell *acell = (TUIReferenceMessageCell *)cell;
|
||||
TUIReferenceMessageCellData *cellData = acell.referenceData;
|
||||
originMsgID = cellData.originMsgID;
|
||||
msgAbstract = cellData.msgAbstract;
|
||||
}
|
||||
|
||||
@weakify(self);
|
||||
[(TUIMessageSearchDataProvider *)self.messageDataProvider
|
||||
findMessages:@[ originMsgID ?: @"" ]
|
||||
callback:^(BOOL success, NSString *_Nonnull desc, NSArray<V2TIMMessage *> *_Nonnull msgs) {
|
||||
@strongify(self);
|
||||
if (!success) {
|
||||
[TUITool makeToast:TIMCommonLocalizableString(TUIKitReplyMessageNotFoundOriginMessage)];
|
||||
return;
|
||||
}
|
||||
V2TIMMessage *message = msgs.firstObject;
|
||||
if (message == nil) {
|
||||
[TUITool makeToast:TIMCommonLocalizableString(TUIKitReplyMessageNotFoundOriginMessage)];
|
||||
return;
|
||||
}
|
||||
|
||||
if (message.status == V2TIM_MSG_STATUS_HAS_DELETED || message.status == V2TIM_MSG_STATUS_LOCAL_REVOKED) {
|
||||
[TUITool makeToast:TIMCommonLocalizableString(TUIKitReplyMessageNotFoundOriginMessage)];
|
||||
return;
|
||||
}
|
||||
|
||||
BOOL hasRiskContent = message.hasRiskContent;
|
||||
|
||||
if ([cell isKindOfClass:TUIReplyMessageCell.class]) {
|
||||
if (hasRiskContent) {
|
||||
return;
|
||||
}
|
||||
[self jumpDetailPageByMessage:message];
|
||||
} else if ([cell isKindOfClass:TUIReferenceMessageCell.class]) {
|
||||
[self locateAssignMessage:message matchKeyWord:msgAbstract];
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)jumpDetailPageByMessage:(V2TIMMessage *)message {
|
||||
NSMutableArray *uiMsgs = [self.messageDataProvider transUIMsgFromIMMsg:@[ message ]];
|
||||
if (uiMsgs.count == 0) {
|
||||
return;
|
||||
}
|
||||
[self.messageDataProvider preProcessMessage:uiMsgs
|
||||
callback:^{
|
||||
for (TUIMessageCellData *cellData in uiMsgs) {
|
||||
if ([cellData.innerMessage.msgID isEqual:message.msgID]) {
|
||||
[self onJumpToRepliesDetailPage:cellData];
|
||||
return;
|
||||
}
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)locateAssignMessage:(V2TIMMessage *)message matchKeyWord:(NSString *)keyword {
|
||||
if (message == nil) {
|
||||
return;
|
||||
}
|
||||
self.locateMessage = message;
|
||||
self.hightlightKeyword = keyword;
|
||||
|
||||
BOOL memoryExist = NO;
|
||||
for (TUIMessageCellData *cellData in self.messageDataProvider.uiMsgs) {
|
||||
if ([cellData.innerMessage.msgID isEqual:message.msgID]) {
|
||||
memoryExist = YES;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (memoryExist) {
|
||||
[self scrollToLocateMessage:NO];
|
||||
[self highlightKeyword];
|
||||
return;
|
||||
}
|
||||
|
||||
TUIMessageSearchDataProvider *provider = (TUIMessageSearchDataProvider *)self.messageDataProvider;
|
||||
provider.isNewerNoMoreMsg = NO;
|
||||
provider.isOlderNoMoreMsg = NO;
|
||||
[self loadAndScrollToLocateMessages:NO isHighlight:YES];
|
||||
}
|
||||
|
||||
- (void)findMessages:(NSArray<NSString *> *)msgIDs callback:(void (^)(BOOL success, NSString *desc, NSArray<V2TIMMessage *> *messages))callback {
|
||||
TUIMessageSearchDataProvider *provider = (TUIMessageSearchDataProvider *)self.messageDataProvider;
|
||||
if (provider) {
|
||||
[provider findMessages:msgIDs callback:callback];
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - TUIMessageBaseDataProviderDataSource
|
||||
- (void)dataProvider:(TUIMessageDataProvider *)dataProvider ReceiveNewUIMsg:(TUIMessageCellData *)uiMsg {
|
||||
[super dataProvider:dataProvider ReceiveNewUIMsg:uiMsg];
|
||||
/**
|
||||
* When viewing historical messages, if you scroll more than two screens, after receiving a new message, add a "xxx new message" bottom-banner-tips
|
||||
*/
|
||||
if (self.tableView.contentSize.height - self.tableView.contentOffset.y >= Screen_Height * 2.0) {
|
||||
[self.receiveMsgs addObject:uiMsg];
|
||||
TUIChatSmallTongue *tongue = [[TUIChatSmallTongue alloc] init];
|
||||
tongue.type = TUIChatSmallTongueType_ReceiveNewMsg;
|
||||
tongue.parentView = self.view.superview;
|
||||
tongue.unreadMsgCount = self.receiveMsgs.count;
|
||||
[TUIChatSmallTongueManager showTongue:tongue delegate:self];
|
||||
}
|
||||
|
||||
if (self.isInVC) {
|
||||
self.C2CIncomingLastMsg = uiMsg.innerMessage;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)dataProvider:(TUIMessageDataProvider *)dataProvider ReceiveRevokeUIMsg:(TUIMessageCellData *)uiMsg {
|
||||
/**
|
||||
* Recalled messages need to be removed from "xxx new messages" bottom-banner-tips
|
||||
*/
|
||||
[super dataProvider:dataProvider ReceiveRevokeUIMsg:uiMsg];
|
||||
if ([self.receiveMsgs containsObject:uiMsg]) {
|
||||
[self.receiveMsgs removeObject:uiMsg];
|
||||
TUIChatSmallTongue *tongue = [[TUIChatSmallTongue alloc] init];
|
||||
tongue.type = TUIChatSmallTongueType_ReceiveNewMsg;
|
||||
tongue.parentView = self.view.superview;
|
||||
tongue.unreadMsgCount = self.receiveMsgs.count;
|
||||
if (tongue.unreadMsgCount != 0) {
|
||||
[TUIChatSmallTongueManager showTongue:tongue delegate:self];
|
||||
} else {
|
||||
[TUIChatSmallTongueManager removeTongue:TUIChatSmallTongueType_ReceiveNewMsg];
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* When the retracted message is a "reply" type of message, go to the root message to delete the currently retracted message.
|
||||
*/
|
||||
|
||||
if ([uiMsg isKindOfClass:TUIReplyMessageCellData.class]) {
|
||||
TUIReplyMessageCellData *cellData = (TUIReplyMessageCellData *)uiMsg;
|
||||
NSString *messageRootID = @"";
|
||||
NSString *revokeMsgID = @"";
|
||||
messageRootID = cellData.messageRootID;
|
||||
revokeMsgID = cellData.msgID;
|
||||
|
||||
[(TUIMessageSearchDataProvider *)self.messageDataProvider
|
||||
findMessages:@[ messageRootID ?: @"" ]
|
||||
callback:^(BOOL success, NSString *_Nonnull desc, NSArray<V2TIMMessage *> *_Nonnull msgs) {
|
||||
if (success) {
|
||||
V2TIMMessage *message = msgs.firstObject;
|
||||
[[TUIChatModifyMessageHelper defaultHelper] modifyMessage:message revokeMsgID:revokeMsgID];
|
||||
}
|
||||
}];
|
||||
}
|
||||
/*
|
||||
The message whose reference is withdrawn should not expose the original message content, so it is necessary to traverse all reference messages and reply messages and replace the original message content with "TIMCommonLocalizableString(TUIKitRepliesOriginMessageRevoke)"
|
||||
*/
|
||||
for (TUIMessageCellData * cellData in self.messageDataProvider.uiMsgs) {
|
||||
if ([cellData isKindOfClass:TUIReplyMessageCellData.class]) {
|
||||
TUIReplyMessageCellData *replyMessageData = (TUIReplyMessageCellData *)cellData;
|
||||
if ([replyMessageData.originMessage.msgID isEqualToString:uiMsg.msgID]) {
|
||||
[self.messageDataProvider processQuoteMessage:@[replyMessageData]];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - TUIChatSmallTongueViewDelegate
|
||||
- (void)onChatSmallTongueClick:(TUIChatSmallTongue *)tongue {
|
||||
switch (tongue.type) {
|
||||
case TUIChatSmallTongueType_ScrollToBoom: {
|
||||
@weakify(self)
|
||||
[self.messageDataProvider getLastMessage:YES succ:^(V2TIMMessage * _Nonnull message) {
|
||||
@strongify(self)
|
||||
if (!message) return;
|
||||
self.locateMessage = message;
|
||||
for (TUIMessageCellData *cellData in self.messageDataProvider.uiMsgs) {
|
||||
if ([self isLocateMessage:cellData]) {
|
||||
[self scrollToLocateMessage:YES];
|
||||
return;
|
||||
}
|
||||
}
|
||||
[self loadAndScrollToLocateMessages:YES isHighlight:NO];
|
||||
} fail:^(int code, NSString *desc) {
|
||||
NSLog(@"getLastMessage failed");
|
||||
}];
|
||||
} break;
|
||||
case TUIChatSmallTongueType_ReceiveNewMsg: {
|
||||
[TUIChatSmallTongueManager removeTongue:TUIChatSmallTongueType_ReceiveNewMsg];
|
||||
TUIMessageCellData *cellData = self.receiveMsgs.firstObject;
|
||||
if (cellData) {
|
||||
self.locateMessage = cellData.innerMessage;
|
||||
[self scrollToLocateMessage:YES];
|
||||
[self highlightKeyword];
|
||||
}
|
||||
[self.receiveMsgs removeAllObjects];
|
||||
} break;
|
||||
case TUIChatSmallTongueType_SomeoneAt: {
|
||||
[TUIChatSmallTongueManager removeTongue:TUIChatSmallTongueType_SomeoneAt];
|
||||
[self.conversationData.atMsgSeqs removeAllObjects];
|
||||
self.locateGroupMessageSeq = [tongue.atMsgSeqs.firstObject integerValue];
|
||||
for (TUIMessageCellData *cellData in self.messageDataProvider.uiMsgs) {
|
||||
if ([self isLocateMessage:cellData]) {
|
||||
[self scrollToLocateMessage:YES];
|
||||
[self highlightKeyword];
|
||||
return;
|
||||
}
|
||||
}
|
||||
[self loadAndScrollToLocateMessages:YES isHighlight:YES];
|
||||
} break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
67
TUIKit/TUIChat/UI_Classic/Chat/TUIMessageMultiChooseView.h
Normal file
67
TUIKit/TUIChat/UI_Classic/Chat/TUIMessageMultiChooseView.h
Normal file
@@ -0,0 +1,67 @@
|
||||
|
||||
// Created by Tencent on 2023/06/09.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@class TUIMessageMultiChooseView;
|
||||
|
||||
@protocol TUIMessageMultiChooseViewDelegate <NSObject>
|
||||
|
||||
/**
|
||||
* Callback when the cancel button on the multi-select message panel is clicked
|
||||
*/
|
||||
- (void)messageMultiChooseViewOnCancelClicked:(TUIMessageMultiChooseView *)multiChooseView;
|
||||
|
||||
/**
|
||||
* Callback for when the forward button on the multi-select message panel is clicked
|
||||
*/
|
||||
- (void)messageMultiChooseViewOnRelayClicked:(TUIMessageMultiChooseView *)multiChooseView;
|
||||
|
||||
/**
|
||||
* Callback for when the delete button on the multi-select message panel is clicked
|
||||
*/
|
||||
- (void)messageMultiChooseViewOnDeleteClicked:(TUIMessageMultiChooseView *)multiChooseView;
|
||||
|
||||
@end
|
||||
|
||||
@interface TUIMessageMultiChooseView : UIView
|
||||
|
||||
@property(nonatomic, weak) id<TUIMessageMultiChooseViewDelegate> delegate;
|
||||
|
||||
#pragma mark - Top toolbar
|
||||
/**
|
||||
* The top toolbar, showing shortcut operations such as cancel
|
||||
*/
|
||||
@property(nonatomic, strong) UIView *toolView;
|
||||
|
||||
/**
|
||||
* Top toolbar element: Cancel button
|
||||
*/
|
||||
@property(nonatomic, strong) UIButton *cancelButton;
|
||||
|
||||
/**
|
||||
* Top toolbar element: title
|
||||
*/
|
||||
@property(nonatomic, strong) UILabel *titleLabel;
|
||||
|
||||
#pragma mark - Bottom menu bar
|
||||
/**
|
||||
* The bottom menu bar, shows the operation menu after multiple selection messages, such as forwarding, deleting, etc.
|
||||
*/
|
||||
@property(nonatomic, strong) UIView *menuView;
|
||||
|
||||
/**
|
||||
* Bottom menu bar element: Forward button
|
||||
*/
|
||||
@property(nonatomic, strong) UIButton *relayButton;
|
||||
|
||||
/**
|
||||
* Bottom menu bar element: Delete button
|
||||
*/
|
||||
@property(nonatomic, strong) UIButton *deleteButton;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
159
TUIKit/TUIChat/UI_Classic/Chat/TUIMessageMultiChooseView.m
Normal file
159
TUIKit/TUIChat/UI_Classic/Chat/TUIMessageMultiChooseView.m
Normal file
@@ -0,0 +1,159 @@
|
||||
//
|
||||
// TUIMessageMultiChooseView.m
|
||||
// Pods
|
||||
//
|
||||
// Created by harvy on 2020/11/27.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
//
|
||||
|
||||
#import "TUIMessageMultiChooseView.h"
|
||||
#import <TUICore/TUIDarkModel.h>
|
||||
#import <TUICore/TUIGlobalization.h>
|
||||
#import <TUICore/TUIThemeManager.h>
|
||||
#import <TUICore/UIView+TUILayout.h>
|
||||
#import <TIMCommon/TIMDefine.h>
|
||||
|
||||
@interface TUIMessageMultiChooseView ()
|
||||
|
||||
@property(nonatomic, strong) CALayer *separtorLayer;
|
||||
|
||||
@end
|
||||
|
||||
@implementation TUIMessageMultiChooseView
|
||||
|
||||
- (instancetype)initWithFrame:(CGRect)frame {
|
||||
if (self = [super initWithFrame:frame]) {
|
||||
[self setupViews];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
|
||||
id hitView = [super hitTest:point withEvent:event];
|
||||
if (hitView == self) {
|
||||
return nil;
|
||||
} else {
|
||||
return hitView;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)layoutSubviews {
|
||||
[super layoutSubviews];
|
||||
|
||||
CGFloat toolHeight = 44;
|
||||
CGFloat menuHeight = 54;
|
||||
CGFloat centerTopOffset = 0;
|
||||
CGFloat centerBottomOffset = 0;
|
||||
if (@available(iOS 11.0, *)) {
|
||||
toolHeight += self.safeAreaInsets.top;
|
||||
menuHeight += self.safeAreaInsets.bottom;
|
||||
centerTopOffset = self.safeAreaInsets.top;
|
||||
centerBottomOffset = self.safeAreaInsets.bottom;
|
||||
}
|
||||
|
||||
self.toolView.frame = CGRectMake(0, 0, self.bounds.size.width, toolHeight);
|
||||
self.menuView.frame = CGRectMake(0, self.bounds.size.height - menuHeight, self.bounds.size.width, menuHeight);
|
||||
self.separtorLayer.frame = CGRectMake(0, 0, self.menuView.mm_w, 1);
|
||||
|
||||
// toolView
|
||||
{
|
||||
CGFloat centerY = 0.5 * (self.toolView.bounds.size.height - self.cancelButton.bounds.size.height);
|
||||
self.cancelButton.frame = CGRectMake(10, centerY += 0.5 * centerTopOffset, self.cancelButton.bounds.size.width, self.cancelButton.bounds.size.height);
|
||||
|
||||
[self.titleLabel sizeToFit];
|
||||
self.titleLabel.center = self.toolView.center;
|
||||
CGRect titleRect = self.self.titleLabel.frame;
|
||||
titleRect.origin.y += 0.5 * centerTopOffset;
|
||||
self.titleLabel.frame = titleRect;
|
||||
}
|
||||
|
||||
// menuView
|
||||
{
|
||||
NSInteger count = self.menuView.subviews.count;
|
||||
CGFloat width = self.menuView.bounds.size.width / count;
|
||||
CGFloat height = (self.menuView.bounds.size.height - centerBottomOffset);
|
||||
for (int i = 0; i < self.menuView.subviews.count; i++) {
|
||||
UIView *sub = self.menuView.subviews[i];
|
||||
CGFloat centerY = (self.menuView.bounds.size.height - height) * 0.5;
|
||||
sub.frame = CGRectMake(i * width, centerY -= 0.5 * centerBottomOffset, width, height);
|
||||
}
|
||||
}
|
||||
if(isRTL()) {
|
||||
for (UIView *subview in self.toolView.subviews) {
|
||||
[subview resetFrameToFitRTL];
|
||||
}
|
||||
for (UIView *subview in self.menuView.subviews) {
|
||||
[subview resetFrameToFitRTL];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - Views
|
||||
- (void)setupViews {
|
||||
_toolView = [[UIView alloc] init];
|
||||
_toolView.backgroundColor = TIMCommonDynamicColor(@"head_bg_gradient_start_color", @"#EBF0F6");
|
||||
[self addSubview:_toolView];
|
||||
|
||||
_menuView = [[UIView alloc] init];
|
||||
_menuView.backgroundColor = TUIChatDynamicColor(@"chat_controller_bg_color", @"#FFFFFF");
|
||||
[_menuView.layer addSublayer:self.separtorLayer];
|
||||
[self addSubview:_menuView];
|
||||
|
||||
_cancelButton = [UIButton buttonWithType:UIButtonTypeCustom];
|
||||
[_cancelButton setTitle:TIMCommonLocalizableString(Cancel) forState:UIControlStateNormal];
|
||||
[_cancelButton setTitleColor:TIMCommonDynamicColor(@"form_title_color", @"#000000") forState:UIControlStateNormal];
|
||||
_cancelButton.titleLabel.font = [UIFont systemFontOfSize:15.0];
|
||||
[_cancelButton addTarget:self action:@selector(onCancel:) forControlEvents:UIControlEventTouchUpInside];
|
||||
[_cancelButton sizeToFit];
|
||||
[_toolView addSubview:_cancelButton];
|
||||
|
||||
_titleLabel = [[UILabel alloc] init];
|
||||
_titleLabel.text = @"";
|
||||
_titleLabel.font = [UIFont boldSystemFontOfSize:17.0];
|
||||
_titleLabel.textColor = TIMCommonDynamicColor(@"form_title_color", @"#000000");
|
||||
[_toolView addSubview:_titleLabel];
|
||||
|
||||
_relayButton = [UIButton buttonWithType:UIButtonTypeCustom];
|
||||
[_relayButton setTitle:TIMCommonLocalizableString(Forward) forState:UIControlStateNormal];
|
||||
_relayButton.titleLabel.font = [UIFont systemFontOfSize:14.0];
|
||||
[_relayButton setTitleColor:TIMCommonDynamicColor(@"form_title_color", @"#000000") forState:UIControlStateNormal];
|
||||
[_relayButton addTarget:self action:@selector(onRelay:) forControlEvents:UIControlEventTouchUpInside];
|
||||
[_menuView addSubview:_relayButton];
|
||||
|
||||
_deleteButton = [UIButton buttonWithType:UIButtonTypeCustom];
|
||||
[_deleteButton setTitle:TIMCommonLocalizableString(Delete) forState:UIControlStateNormal];
|
||||
_deleteButton.titleLabel.font = [UIFont systemFontOfSize:14.0];
|
||||
[_deleteButton setTitleColor:TIMCommonDynamicColor(@"form_title_color", @"#000000") forState:UIControlStateNormal];
|
||||
[_deleteButton addTarget:self action:@selector(onDelete:) forControlEvents:UIControlEventTouchUpInside];
|
||||
[_menuView addSubview:_deleteButton];
|
||||
}
|
||||
|
||||
#pragma mark - Action
|
||||
- (void)onCancel:(UIButton *)cancelButton {
|
||||
if (self.delegate && [self.delegate respondsToSelector:@selector(messageMultiChooseViewOnCancelClicked:)]) {
|
||||
[self.delegate messageMultiChooseViewOnCancelClicked:self];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)onRelay:(UIButton *)cancelButton {
|
||||
if (self.delegate && [self.delegate respondsToSelector:@selector(messageMultiChooseViewOnRelayClicked:)]) {
|
||||
[self.delegate messageMultiChooseViewOnRelayClicked:self];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)onDelete:(UIButton *)cancelButton {
|
||||
if (self.delegate && [self.delegate respondsToSelector:@selector(messageMultiChooseViewOnDeleteClicked:)]) {
|
||||
[self.delegate messageMultiChooseViewOnDeleteClicked:self];
|
||||
}
|
||||
}
|
||||
|
||||
- (CALayer *)separtorLayer {
|
||||
if (_separtorLayer == nil) {
|
||||
_separtorLayer = [CALayer layer];
|
||||
|
||||
_separtorLayer.backgroundColor = TIMCommonDynamicColor(@"separator_color", @"#DBDBDB").CGColor;
|
||||
}
|
||||
return _separtorLayer;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,47 @@
|
||||
|
||||
// Created by Tencent on 2023/06/09.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
/**
|
||||
*
|
||||
* This file mainly declares the controller class that jumps to the read member list after clicking the read label of the group chat message
|
||||
*
|
||||
* The TUIMessageReadSelectView class defines a tab-like view in the read list. Currently only used in TUIMessageReadViewController.
|
||||
* TUIMessageReadSelectViewDelegate Callback for clicked view event. Currently implemented by TUIMessageReadViewController to switch between read and unread
|
||||
* lists.
|
||||
*
|
||||
* The TUIMessageReadViewController class implements the UI and logic for the read member list.
|
||||
* TUIMessageReadViewControllerDelegate callback click member list cell event.
|
||||
*/
|
||||
|
||||
#import <TIMCommon/TUIMessageCellData.h>
|
||||
#import <UIKit/UIKit.h>
|
||||
#import "TUIChatDefine.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@class TUIMessageReadSelectView;
|
||||
@protocol TUIMessageReadSelectViewDelegate <NSObject>
|
||||
@optional
|
||||
- (void)messageReadSelectView:(TUIMessageReadSelectView *)view didSelectItemTag:(TUIMessageReadViewTag)tag;
|
||||
@end
|
||||
|
||||
@interface TUIMessageReadSelectView : UIView
|
||||
@property(nonatomic, weak) id<TUIMessageReadSelectViewDelegate> delegate;
|
||||
@property(nonatomic, assign) BOOL selected;
|
||||
|
||||
- (instancetype)initWithTitle:(NSString *)title viewTag:(TUIMessageReadViewTag)tag selected:(BOOL)selected;
|
||||
|
||||
@end
|
||||
|
||||
@class TUIMessageDataProvider;
|
||||
@interface TUIMessageReadViewController : UIViewController
|
||||
@property(copy, nonatomic) void (^viewWillDismissHandler)(void);
|
||||
|
||||
- (instancetype)initWithCellData:(TUIMessageCellData *)data
|
||||
dataProvider:(TUIMessageDataProvider *)dataProvider
|
||||
showReadStatusDisable:(BOOL)showReadStatusDisable
|
||||
c2cReceiverName:(NSString *)name
|
||||
c2cReceiverAvatar:(NSString *)avatarUrl;
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
656
TUIKit/TUIChat/UI_Classic/Chat/TUIMessageReadViewController.m
Normal file
656
TUIKit/TUIChat/UI_Classic/Chat/TUIMessageReadViewController.m
Normal file
@@ -0,0 +1,656 @@
|
||||
//
|
||||
// TUIMessageReadViewController.m
|
||||
// TUIChat
|
||||
//
|
||||
// Created by xia on 2022/3/10.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
//
|
||||
|
||||
#import "TUIMessageReadViewController.h"
|
||||
#import <TUICore/TUICore.h>
|
||||
#import <TUICore/TUIThemeManager.h>
|
||||
#import <TUICore/TUITool.h>
|
||||
#import <TUICore/UIColor+TUIHexColor.h>
|
||||
#import "TUIImageMessageCellData.h"
|
||||
#import "TUIMemberCell.h"
|
||||
#import "TUIMemberCellData.h"
|
||||
#import "TUIMessageDataProvider.h"
|
||||
#import "TUIVideoMessageCellData.h"
|
||||
|
||||
@interface TUIMessageReadSelectView ()
|
||||
|
||||
@property(nonatomic, copy) NSString *title;
|
||||
|
||||
@property(nonatomic, strong) UILabel *titleLabel;
|
||||
@property(nonatomic, strong) UIView *bottomLine;
|
||||
|
||||
@end
|
||||
|
||||
@implementation TUIMessageReadSelectView
|
||||
|
||||
#pragma mark - Life cycle
|
||||
- (void)layoutSubviews {
|
||||
[super layoutSubviews];
|
||||
[self layoutViews];
|
||||
}
|
||||
|
||||
#pragma mark - Public
|
||||
- (instancetype)initWithTitle:(NSString *)title viewTag:(TUIMessageReadViewTag)tag selected:(BOOL)selected {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
self.title = title;
|
||||
self.tag = tag;
|
||||
|
||||
[self setupViews];
|
||||
[self setupGesture];
|
||||
[self setupRAC];
|
||||
|
||||
self.selected = selected;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
#pragma mark - Private
|
||||
- (void)setupViews {
|
||||
self.backgroundColor = TUIChatDynamicColor(@"chat_controller_bg_color", @"#FFFFFF");
|
||||
|
||||
self.titleLabel = [[UILabel alloc] init];
|
||||
self.titleLabel.text = self.title;
|
||||
self.titleLabel.font = [UIFont systemFontOfSize:16];
|
||||
self.titleLabel.textAlignment = NSTextAlignmentCenter;
|
||||
[self addSubview:self.titleLabel];
|
||||
|
||||
self.bottomLine = [[UIView alloc] init];
|
||||
[self addSubview:self.bottomLine];
|
||||
}
|
||||
|
||||
- (void)layoutViews {
|
||||
self.titleLabel.mm_sizeToFit().mm_height(24).mm__centerX(self.mm_w / 2.0).mm__centerY(self.mm_h / 2.0);
|
||||
|
||||
self.bottomLine.mm_width(self.titleLabel.mm_w).mm_height(5 / 2.0).mm_top(self.titleLabel.mm_maxY + 8 / 2.0).mm__centerX(self.titleLabel.mm_centerX);
|
||||
}
|
||||
|
||||
- (void)setupRAC {
|
||||
@weakify(self);
|
||||
[RACObserve(self, selected) subscribeNext:^(id _Nullable x) {
|
||||
@strongify(self);
|
||||
[self updateColorBySelected:self.selected];
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)updateColorBySelected:(BOOL)selected {
|
||||
UIColor *color = selected ? TUIChatDynamicColor(@"chat_message_read_status_tab_color", @"#147AFF")
|
||||
: TIMCommonDynamicColor(@"chat_message_read_status_tab_unselect_color", @"#444444");
|
||||
self.titleLabel.textColor = color;
|
||||
self.bottomLine.hidden = !selected;
|
||||
self.bottomLine.backgroundColor = color;
|
||||
}
|
||||
|
||||
- (void)setupGesture {
|
||||
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(onTapped:)];
|
||||
[self addGestureRecognizer:tap];
|
||||
}
|
||||
|
||||
#pragma mark - Event
|
||||
- (void)onTapped:(UIGestureRecognizer *)gesture {
|
||||
if ([self.delegate respondsToSelector:@selector(messageReadSelectView:didSelectItemTag:)]) {
|
||||
[self.delegate messageReadSelectView:self didSelectItemTag:self.tag];
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@interface TUIMessageReadViewController () <UITableViewDelegate, UITableViewDataSource, TUIMessageReadSelectViewDelegate>
|
||||
|
||||
@property(nonatomic, strong) TUIMessageCellData *cellData;
|
||||
@property(nonatomic, assign) BOOL showReadStatusDisable;
|
||||
@property(nonatomic, assign) TUIMessageReadViewTag selectedViewTag;
|
||||
@property(nonatomic, strong) TUIMessageDataProvider *dataProvider;
|
||||
|
||||
@property(nonatomic, strong) UIView *messageBackView;
|
||||
@property(nonatomic, strong) UITableView *tableView;
|
||||
@property(nonatomic, strong) UIActivityIndicatorView *indicatorView;
|
||||
@property(nonatomic, strong) UILabel *contentLabel;
|
||||
|
||||
@property(nonatomic, strong) NSMutableDictionary *selectViewsDict;
|
||||
@property(nonatomic, strong) NSMutableArray *readMembers;
|
||||
@property(nonatomic, strong) NSMutableArray *unreadMembers;
|
||||
@property(nonatomic, assign) NSUInteger readSeq;
|
||||
@property(nonatomic, assign) NSUInteger unreadSeq;
|
||||
@property(nonatomic, copy) NSString *c2cReceiverName;
|
||||
@property(nonatomic, copy) NSString *c2cReceiverAvatarUrl;
|
||||
|
||||
@end
|
||||
|
||||
@implementation TUIMessageReadViewController
|
||||
|
||||
#pragma mark - Life cycle
|
||||
- (void)viewDidLoad {
|
||||
[super viewDidLoad];
|
||||
if ([self isGroupMessageRead]) {
|
||||
self.selectedViewTag = TUIMessageReadViewTagRead;
|
||||
[self loadMembers];
|
||||
} else {
|
||||
self.selectedViewTag = TUIMessageReadViewTagC2C;
|
||||
}
|
||||
[self setupViews];
|
||||
// Do any additional setup after loading the view.
|
||||
}
|
||||
|
||||
- (void)viewWillLayoutSubviews {
|
||||
[super viewWillLayoutSubviews];
|
||||
[self layoutViews];
|
||||
}
|
||||
|
||||
- (void)viewWillDisappear:(BOOL)animated {
|
||||
[super viewWillDisappear:animated];
|
||||
if (_viewWillDismissHandler) {
|
||||
_viewWillDismissHandler();
|
||||
}
|
||||
}
|
||||
- (void)dealloc {
|
||||
NSLog(@"%s dealloc", __FUNCTION__);
|
||||
}
|
||||
|
||||
#pragma mark - Public
|
||||
- (instancetype)initWithCellData:(TUIMessageCellData *)data
|
||||
dataProvider:(TUIMessageDataProvider *)dataProvider
|
||||
showReadStatusDisable:(BOOL)showReadStatusDisable
|
||||
c2cReceiverName:(NSString *)name
|
||||
c2cReceiverAvatar:(NSString *)avatarUrl {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
self.cellData = data;
|
||||
self.dataProvider = dataProvider;
|
||||
self.showReadStatusDisable = showReadStatusDisable;
|
||||
self.c2cReceiverName = name;
|
||||
self.c2cReceiverAvatarUrl = avatarUrl;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
#pragma mark - Private
|
||||
- (void)setupViews {
|
||||
self.view.backgroundColor = TIMCommonDynamicColor(@"controller_bg_color", @"#F2F3F5");
|
||||
[self setupTitleView];
|
||||
[self setupMessageView];
|
||||
if ([self isGroupMessageRead]) {
|
||||
[self setupSelectView];
|
||||
}
|
||||
[self setupTableView];
|
||||
}
|
||||
|
||||
- (void)layoutViews {
|
||||
id content = [self content];
|
||||
CGFloat messageBackViewHeight = 69 ;
|
||||
if ([content isKindOfClass:UIImage.class]) {
|
||||
messageBackViewHeight = 87;
|
||||
}
|
||||
[self.messageBackView mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.top.mas_equalTo(self.view.mas_safeAreaLayoutGuideTop);
|
||||
make.leading.mas_equalTo(0);
|
||||
make.height.mas_equalTo(messageBackViewHeight);
|
||||
make.width.mas_equalTo(self.view);
|
||||
}];
|
||||
// content label may not exist when content is not text
|
||||
if (self.contentLabel) {
|
||||
[self.contentLabel mas_remakeConstraints:^(MASConstraintMaker *make) {
|
||||
make.leading.mas_equalTo(16);
|
||||
make.top.mas_equalTo(33);
|
||||
make.height.mas_equalTo(24);
|
||||
make.trailing.mas_equalTo(-16);
|
||||
}];
|
||||
}
|
||||
|
||||
UIView *readView = [self layoutSelectView:self.selectViewsDict[@(TUIMessageReadViewTagRead)] leftView:nil];
|
||||
UIView *unreadView = [self layoutSelectView:self.selectViewsDict[@(TUIMessageReadViewTagUnread)] leftView:readView];
|
||||
if (self.showReadStatusDisable) {
|
||||
[self layoutSelectView:self.selectViewsDict[@(TUIMessageReadViewTagReadDisable)] leftView:unreadView];
|
||||
}
|
||||
|
||||
self.tableView.mm_top(self.messageBackView.mm_maxY + 10 + (self.selectViewsDict.count > 0 ? 48 : 0))
|
||||
.mm_left(0)
|
||||
.mm_width(self.view.mm_w)
|
||||
.mm_height(self.view.mm_h - _tableView.mm_y);
|
||||
}
|
||||
|
||||
- (UIView *)layoutSelectView:(UIView *)view leftView:(UIView *)leftView {
|
||||
NSInteger count = self.selectViewsDict.count;
|
||||
if (count == 0) {
|
||||
return nil;
|
||||
}
|
||||
[view mas_remakeConstraints:^(MASConstraintMaker *make) {
|
||||
make.width.mas_equalTo(self.view).multipliedBy(1.0/count);
|
||||
make.height.mas_equalTo(48);
|
||||
if (leftView) {
|
||||
make.leading.mas_equalTo(leftView.mas_trailing);
|
||||
}
|
||||
else {
|
||||
make.leading.mas_equalTo(0);
|
||||
}
|
||||
make.top.mas_equalTo(self.messageBackView.mas_bottom).mas_offset(10);
|
||||
}];
|
||||
return view;
|
||||
}
|
||||
|
||||
- (void)setupTitleView {
|
||||
UILabel *titleLabel = [[UILabel alloc] init];
|
||||
titleLabel.text = TIMCommonLocalizableString(TUIKitMessageReadDetail);
|
||||
titleLabel.font = [UIFont systemFontOfSize:18.0];
|
||||
titleLabel.textColor = TIMCommonDynamicColor(@"nav_title_text_color", @"#000000");
|
||||
[titleLabel sizeToFit];
|
||||
self.navigationItem.titleView = titleLabel;
|
||||
}
|
||||
|
||||
- (void)setupMessageView {
|
||||
UIView *messageBackView = [[UIView alloc] init];
|
||||
messageBackView.backgroundColor = TIMCommonDynamicColor(@"form_bg_color", @"#FFFFFF");
|
||||
[self.view addSubview:messageBackView];
|
||||
self.messageBackView = messageBackView;
|
||||
|
||||
UILabel *nameLabel = [[UILabel alloc] init];
|
||||
nameLabel.text = self.cellData.senderName;
|
||||
nameLabel.font = [UIFont systemFontOfSize:12.0];
|
||||
nameLabel.textColor = TUIChatDynamicColor(@"chat_message_read_name_date_text_color", @"#999999");
|
||||
nameLabel.textAlignment = isRTL()?NSTextAlignmentRight:NSTextAlignmentLeft;
|
||||
[messageBackView addSubview:nameLabel];
|
||||
[nameLabel sizeToFit];
|
||||
[nameLabel mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.top.mas_equalTo(12);
|
||||
make.leading.mas_equalTo(16);
|
||||
make.width.mas_equalTo(nameLabel.frame.size.width);
|
||||
make.height.mas_equalTo(nameLabel.frame.size.height);
|
||||
}];
|
||||
UILabel *dateLabel = [[UILabel alloc] init];
|
||||
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
|
||||
[formatter setDateFormat:@"MM-dd HH:mm"];
|
||||
NSString *dateString = [formatter stringFromDate:self.cellData.innerMessage.timestamp];
|
||||
dateLabel.text = dateString;
|
||||
dateLabel.font = [UIFont systemFontOfSize:12];
|
||||
dateLabel.textAlignment = isRTL()?NSTextAlignmentRight:NSTextAlignmentLeft;
|
||||
dateLabel.textColor = TUIChatDynamicColor(@"chat_message_read_name_date_text_color", @"#999999");
|
||||
[messageBackView addSubview:dateLabel];
|
||||
[dateLabel sizeToFit];
|
||||
[dateLabel mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.centerY.mas_equalTo(nameLabel);
|
||||
make.leading.mas_equalTo(nameLabel.mas_trailing).mas_offset(12);
|
||||
make.width.mas_equalTo(dateLabel.frame.size.width);
|
||||
make.height.mas_equalTo(dateLabel.frame.size.height);
|
||||
}];
|
||||
id content = [self content];
|
||||
if ([content isKindOfClass:NSString.class]) {
|
||||
UILabel *contentLabel = [[UILabel alloc] init];
|
||||
contentLabel.text = content;
|
||||
contentLabel.font = [UIFont systemFontOfSize:16];
|
||||
contentLabel.textColor = TUIChatDynamicColor(@"chat_input_text_color", @"#111111");
|
||||
contentLabel.lineBreakMode = NSLineBreakByTruncatingTail;
|
||||
contentLabel.textAlignment = isRTL()?NSTextAlignmentRight:NSTextAlignmentLeft;
|
||||
self.contentLabel = contentLabel;
|
||||
[messageBackView addSubview:contentLabel];
|
||||
} else if ([content isKindOfClass:UIImage.class]) {
|
||||
UIImageView *imageView = [[UIImageView alloc] init];
|
||||
imageView.image = content;
|
||||
[messageBackView addSubview:imageView];
|
||||
[imageView mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.top.mas_equalTo(33);
|
||||
make.leading.mas_equalTo(16);
|
||||
make.width.mas_equalTo([self scaledSizeOfImage:content].width);
|
||||
make.height.mas_equalTo([self scaledSizeOfImage:content].height);
|
||||
}];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setupSelectView {
|
||||
NSMutableDictionary *dataDict = [self selectViewsData];
|
||||
NSInteger count = dataDict.count;
|
||||
NSMutableArray * selectViewsArray = [NSMutableArray arrayWithCapacity:2];
|
||||
UIView *tmp = nil;
|
||||
for (NSNumber *tag in dataDict) {
|
||||
NSDictionary *data = dataDict[tag];
|
||||
TUIMessageReadSelectView *selectView = [[TUIMessageReadSelectView alloc] initWithTitle:data[@"title"]
|
||||
viewTag:[data[@"tag"] integerValue]
|
||||
selected:[data[@"selected"] boolValue]];
|
||||
selectView.backgroundColor = TIMCommonDynamicColor(@"form_bg_color", @"#FFFFFF");
|
||||
selectView.delegate = self;
|
||||
[self.view addSubview:selectView];
|
||||
[selectViewsArray addObject:selectView];
|
||||
[self.selectViewsDict setObject:selectView forKey:data[@"tag"]];
|
||||
|
||||
selectView.mm_width(self.view.mm_w / count)
|
||||
.mm_height(48)
|
||||
.mm_left(tmp == nil ? 0 : tmp.mm_x + tmp.mm_w)
|
||||
.mm_top(self.messageBackView.mm_y + self.messageBackView.mm_h + 10);
|
||||
|
||||
tmp = selectView;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setupTableView {
|
||||
_tableView = [[UITableView alloc] initWithFrame:CGRectZero style:UITableViewStylePlain];
|
||||
_tableView.delegate = self;
|
||||
_tableView.dataSource = self;
|
||||
_tableView.contentInset = UIEdgeInsetsMake(0, 0, 8, 0);
|
||||
_tableView.rowHeight = 56;
|
||||
[_tableView setBackgroundColor:TIMCommonDynamicColor(@"form_bg_color", @"#FFFFFF")];
|
||||
[self.view addSubview:_tableView];
|
||||
UIView *view = [[UIView alloc] initWithFrame:CGRectZero];
|
||||
[_tableView setTableFooterView:view];
|
||||
_tableView.separatorStyle = [self isGroupMessageRead] ? UITableViewCellSeparatorStyleSingleLine : UITableViewCellSeparatorStyleNone;
|
||||
_tableView.separatorInset = UIEdgeInsetsMake(0, 58, 0, 0);
|
||||
[_tableView registerClass:[TUIMemberCell class] forCellReuseIdentifier:kMemberCellReuseId];
|
||||
|
||||
self.indicatorView.frame = CGRectMake(0, 0, self.view.bounds.size.width, TMessageController_Header_Height);
|
||||
self.tableView.tableFooterView = self.indicatorView;
|
||||
}
|
||||
|
||||
- (void)loadMembers {
|
||||
[self getReadMembersWithCompletion:^(int code, NSString *desc, NSArray *members, BOOL isFinished) {
|
||||
if (code != 0) {
|
||||
[TUITool makeToast:TIMCommonLocalizableString(TUIKitMessageReadGetReadMembersFail)];
|
||||
NSLog(@"get read members failed, code: %d, desc: %@", code, desc);
|
||||
return;
|
||||
}
|
||||
[self.tableView reloadData];
|
||||
}];
|
||||
[self getUnreadMembersWithCompletion:^(int code, NSString *desc, NSArray *members, BOOL isFinished) {
|
||||
if (code != 0) {
|
||||
[TUITool makeToast:TIMCommonLocalizableString(TUIKitMessageReadGetUnreadMembersFail)];
|
||||
NSLog(@"get unread members failed, code: %d, desc: %@", code, desc);
|
||||
return;
|
||||
}
|
||||
[self.tableView reloadData];
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)getReadMembersWithCompletion:(void (^)(int code, NSString *desc, NSArray *members, BOOL isFinished))completion {
|
||||
@weakify(self);
|
||||
[TUIMessageDataProvider getReadMembersOfMessage:self.cellData.innerMessage
|
||||
filter:V2TIM_GROUP_MESSAGE_READ_MEMBERS_FILTER_READ
|
||||
nextSeq:self.readSeq
|
||||
completion:^(int code, NSString *_Nonnull desc, NSArray *_Nonnull members, NSUInteger nextSeq, BOOL isFinished) {
|
||||
@strongify(self);
|
||||
if (code != 0) {
|
||||
if (completion) {
|
||||
completion(code, desc, nil, NO);
|
||||
}
|
||||
return;
|
||||
}
|
||||
[self.readMembers addObjectsFromArray:members];
|
||||
self.readSeq = isFinished ? -1 : nextSeq;
|
||||
|
||||
if (completion) {
|
||||
completion(code, desc, members, isFinished);
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)getUnreadMembersWithCompletion:(void (^)(int code, NSString *desc, NSArray *members, BOOL isFinished))completion {
|
||||
@weakify(self);
|
||||
[TUIMessageDataProvider getReadMembersOfMessage:self.cellData.innerMessage
|
||||
filter:V2TIM_GROUP_MESSAGE_READ_MEMBERS_FILTER_UNREAD
|
||||
nextSeq:self.unreadSeq
|
||||
completion:^(int code, NSString *_Nonnull desc, NSArray *_Nonnull members, NSUInteger nextSeq, BOOL isFinished) {
|
||||
@strongify(self);
|
||||
if (code != 0) {
|
||||
if (completion) {
|
||||
completion(code, desc, nil, NO);
|
||||
}
|
||||
return;
|
||||
}
|
||||
[self.unreadMembers addObjectsFromArray:members];
|
||||
self.unreadSeq = isFinished ? -1 : nextSeq;
|
||||
|
||||
if (completion) {
|
||||
completion(code, desc, members, isFinished);
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)getUserOrFriendProfileVCWithUserID:(NSString *)userID SuccBlock:(void (^)(UIViewController *vc))succ failBlock:(nullable V2TIMFail)fail {
|
||||
NSDictionary *param = @{
|
||||
TUICore_TUIContactObjectFactory_GetUserOrFriendProfileVCMethod_UserIDKey: userID ? : @"",
|
||||
TUICore_TUIContactObjectFactory_GetUserOrFriendProfileVCMethod_SuccKey: succ ? : ^(UIViewController *vc){},
|
||||
TUICore_TUIContactObjectFactory_GetUserOrFriendProfileVCMethod_FailKey: fail ? : ^(int code, NSString * desc){}
|
||||
};
|
||||
[TUICore createObject:TUICore_TUIContactObjectFactory key:TUICore_TUIContactObjectFactory_GetUserOrFriendProfileVCMethod param:param];
|
||||
}
|
||||
|
||||
#pragma mark - UITableViewDataSource & UITableViewDelegate
|
||||
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
|
||||
if ([self isGroupMessageRead]) {
|
||||
if (indexPath.row >= [self members].count) {
|
||||
return;
|
||||
}
|
||||
V2TIMGroupMemberInfo *member = [self members][indexPath.row];
|
||||
[self getUserOrFriendProfileVCWithUserID:member.userID
|
||||
SuccBlock:^(UIViewController *vc) {
|
||||
[self.navigationController pushViewController:vc animated:YES];
|
||||
}
|
||||
failBlock:nil];
|
||||
} else {
|
||||
[self getUserOrFriendProfileVCWithUserID:self.cellData.innerMessage.userID
|
||||
SuccBlock:^(UIViewController *vc) {
|
||||
[self.navigationController pushViewController:vc animated:YES];
|
||||
}
|
||||
failBlock:nil];
|
||||
}
|
||||
}
|
||||
|
||||
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
|
||||
return 1;
|
||||
}
|
||||
|
||||
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
|
||||
return [self isGroupMessageRead] ? [self members].count : 1;
|
||||
}
|
||||
|
||||
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
|
||||
TUIMemberCell *cell = [tableView dequeueReusableCellWithIdentifier:kMemberCellReuseId forIndexPath:indexPath];
|
||||
cell.changeColorWhenTouched = YES;
|
||||
|
||||
TUIMemberCellData *data;
|
||||
if ([self isGroupMessageRead]) {
|
||||
if (indexPath.row >= [self members].count) {
|
||||
return nil;
|
||||
}
|
||||
V2TIMGroupMemberInfo *member = [self members][indexPath.row];
|
||||
data = [[TUIMemberCellData alloc] initWithUserID:member.userID
|
||||
nickName:member.nickName
|
||||
friendRemark:member.friendRemark
|
||||
nameCard:member.nameCard
|
||||
avatarUrl:member.faceURL
|
||||
detail:nil];
|
||||
} else {
|
||||
NSString *detail = nil;
|
||||
BOOL isPeerRead = self.cellData.messageReceipt.isPeerRead;
|
||||
detail = isPeerRead ? TIMCommonLocalizableString(TUIKitMessageReadC2CRead) : TIMCommonLocalizableString(TUIKitMessageReadC2CUnReadDetail);
|
||||
data = [[TUIMemberCellData alloc] initWithUserID:self.cellData.innerMessage.userID
|
||||
nickName:nil
|
||||
friendRemark:self.c2cReceiverName
|
||||
nameCard:nil
|
||||
avatarUrl:self.c2cReceiverAvatarUrl
|
||||
detail:detail];
|
||||
}
|
||||
[cell fillWithData:data];
|
||||
return cell;
|
||||
}
|
||||
|
||||
#pragma mark - UIScrollView
|
||||
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
|
||||
if (scrollView.contentOffset.y <= 0 || scrollView.contentOffset.y < scrollView.bounds.origin.y) {
|
||||
return;
|
||||
}
|
||||
if (self.indicatorView.isAnimating) {
|
||||
return;
|
||||
}
|
||||
[self.indicatorView startAnimating];
|
||||
@weakify(self);
|
||||
switch (self.selectedViewTag) {
|
||||
case TUIMessageReadViewTagRead: {
|
||||
[self getReadMembersWithCompletion:^(int code, NSString *desc, NSArray *members, BOOL isFinished) {
|
||||
@strongify(self);
|
||||
[self.indicatorView stopAnimating];
|
||||
[self refreshTableView];
|
||||
|
||||
if (members != nil && members.count == 0) {
|
||||
[TUITool makeToast:TIMCommonLocalizableString(TUIKitMessageReadNoMoreData)];
|
||||
[self.tableView setContentOffset:CGPointMake(0, scrollView.contentOffset.y - TMessageController_Header_Height) animated:YES];
|
||||
}
|
||||
}];
|
||||
break;
|
||||
}
|
||||
case TUIMessageReadViewTagUnread: {
|
||||
[self getUnreadMembersWithCompletion:^(int code, NSString *desc, NSArray *members, BOOL isFinished) {
|
||||
@strongify(self);
|
||||
[self.indicatorView stopAnimating];
|
||||
[self refreshTableView];
|
||||
|
||||
if (members != nil && members.count == 0) {
|
||||
[TUITool makeToast:TIMCommonLocalizableString(TUIKitMessageReadNoMoreData)];
|
||||
[self.tableView setContentOffset:CGPointMake(0, scrollView.contentOffset.y - TMessageController_Header_Height) animated:YES];
|
||||
}
|
||||
}];
|
||||
break;
|
||||
}
|
||||
case TUIMessageReadViewTagReadDisable: {
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)refreshTableView {
|
||||
[self.tableView reloadData];
|
||||
[self.tableView layoutIfNeeded];
|
||||
}
|
||||
|
||||
#pragma mark - TUIMessageReadSelectViewDelegate
|
||||
- (void)messageReadSelectView:(TUIMessageReadSelectView *)view didSelectItemTag:(TUIMessageReadViewTag)tag {
|
||||
for (TUIMessageReadSelectView *view in self.selectViewsDict.allValues) {
|
||||
view.selected = view.tag == tag;
|
||||
}
|
||||
self.selectedViewTag = tag;
|
||||
[self.tableView reloadData];
|
||||
}
|
||||
|
||||
#pragma mark - Getter
|
||||
- (id)content {
|
||||
V2TIMMessage *msg = self.cellData.innerMessage;
|
||||
NSMutableString *content = [NSMutableString stringWithString:[TUIMessageDataProvider getDisplayString:msg]];
|
||||
switch (msg.elemType) {
|
||||
case V2TIM_ELEM_TYPE_IMAGE: {
|
||||
TUIImageMessageCellData *data = (TUIImageMessageCellData *)self.cellData;
|
||||
return data.thumbImage;
|
||||
}
|
||||
case V2TIM_ELEM_TYPE_VIDEO: {
|
||||
TUIVideoMessageCellData *data = (TUIVideoMessageCellData *)self.cellData;
|
||||
return data.thumbImage;
|
||||
}
|
||||
case V2TIM_ELEM_TYPE_FILE: {
|
||||
[content appendString:msg.fileElem.filename];
|
||||
break;
|
||||
}
|
||||
case V2TIM_ELEM_TYPE_CUSTOM: {
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return content;
|
||||
}
|
||||
|
||||
- (CGSize)scaledSizeOfImage:(UIImage *)image {
|
||||
NSSet *portraitOrientations =
|
||||
[NSSet setWithArray:@[ @(UIImageOrientationLeft), @(UIImageOrientationRight), @(UIImageOrientationLeftMirrored), @(UIImageOrientationRightMirrored) ]];
|
||||
|
||||
UIImageOrientation orientation = image.imageOrientation;
|
||||
CGFloat width = CGImageGetWidth(image.CGImage);
|
||||
CGFloat height = CGImageGetHeight(image.CGImage);
|
||||
|
||||
// Height is fixed at 42.0, and width is proportionally scaled.
|
||||
if ([portraitOrientations containsObject:@(orientation)]) {
|
||||
// UIImage is stored in memory in a fixed size, like 1280 * 720.
|
||||
// So we should adapt its size manually according to the direction.
|
||||
return CGSizeMake(42.0 * height / width, 42.0);
|
||||
} else {
|
||||
return CGSizeMake(42.0 * width / height, 42.0);
|
||||
}
|
||||
}
|
||||
|
||||
- (NSMutableDictionary *)selectViewsData {
|
||||
NSMutableDictionary *readViews = [NSMutableDictionary dictionaryWithDictionary:@{
|
||||
@(TUIMessageReadViewTagRead) : @{
|
||||
@"tag" : @(TUIMessageReadViewTagRead),
|
||||
@"title" :
|
||||
[NSString stringWithFormat:@"%ld %@", (long)self.cellData.messageReceipt.readCount, TIMCommonLocalizableString(TUIKitMessageReadPartRead)],
|
||||
@"selected" : @(YES)
|
||||
},
|
||||
@(TUIMessageReadViewTagUnread) : @{
|
||||
@"tag" : @(TUIMessageReadViewTagUnread),
|
||||
@"title" :
|
||||
[NSString stringWithFormat:@"%ld %@", (long)self.cellData.messageReceipt.unreadCount, TIMCommonLocalizableString(TUIKitMessageReadPartUnread)],
|
||||
@"selected" : @(NO)
|
||||
},
|
||||
}];
|
||||
if (self.showReadStatusDisable) {
|
||||
[readViews
|
||||
setObject:@{@"tag" : @(TUIMessageReadViewTagReadDisable), @"title" : TIMCommonLocalizableString(TUIKitMessageReadPartDisable), @"selected" : @(NO)}
|
||||
forKey:@(TUIMessageReadViewTagReadDisable)];
|
||||
}
|
||||
return readViews;
|
||||
}
|
||||
|
||||
- (NSArray *)members {
|
||||
switch (self.selectedViewTag) {
|
||||
case TUIMessageReadViewTagRead: {
|
||||
return self.readMembers;
|
||||
}
|
||||
case TUIMessageReadViewTagUnread: {
|
||||
return self.unreadMembers;
|
||||
}
|
||||
case TUIMessageReadViewTagReadDisable: {
|
||||
return @[];
|
||||
}
|
||||
default: {
|
||||
return @[];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (NSMutableDictionary *)selectViewsDict {
|
||||
if (!_selectViewsDict) {
|
||||
_selectViewsDict = [[NSMutableDictionary alloc] init];
|
||||
}
|
||||
return _selectViewsDict;
|
||||
}
|
||||
|
||||
- (NSMutableArray *)readMembers {
|
||||
if (!_readMembers) {
|
||||
_readMembers = [[NSMutableArray alloc] init];
|
||||
}
|
||||
return _readMembers;
|
||||
}
|
||||
|
||||
- (NSMutableArray *)unreadMembers {
|
||||
if (!_unreadMembers) {
|
||||
_unreadMembers = [[NSMutableArray alloc] init];
|
||||
}
|
||||
return _unreadMembers;
|
||||
}
|
||||
|
||||
- (UIActivityIndicatorView *)indicatorView {
|
||||
if (_indicatorView == nil) {
|
||||
_indicatorView = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
|
||||
_indicatorView.hidesWhenStopped = YES;
|
||||
}
|
||||
return _indicatorView;
|
||||
}
|
||||
|
||||
- (BOOL)isGroupMessageRead {
|
||||
return self.cellData.innerMessage.groupID.length > 0;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,31 @@
|
||||
//
|
||||
// TUIRepliesDetailViewController.h
|
||||
// TUIChat
|
||||
//
|
||||
// Created by wyl on 2022/4/27.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
//
|
||||
|
||||
#import <TIMCommon/TIMDefine.h>
|
||||
#import <UIKit/UIKit.h>
|
||||
#import "TUIBaseMessageControllerDelegate.h"
|
||||
#import "TUIChatConversationModel.h"
|
||||
#import "TUIInputController.h"
|
||||
|
||||
@class TUIMessageDataProvider;
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface TUIRepliesDetailViewController : UIViewController
|
||||
|
||||
- (instancetype)initWithCellData:(TUIMessageCellData *)data conversationData:(TUIChatConversationModel *)conversationData;
|
||||
|
||||
@property(nonatomic, weak) id<TUIBaseMessageControllerDelegate> delegate;
|
||||
@property(nonatomic, strong) V2TIMMergerElem *mergerElem;
|
||||
@property(nonatomic, copy) dispatch_block_t willCloseCallback;
|
||||
@property(nonatomic, strong) TUIInputController *inputController;
|
||||
@property(nonatomic, strong) TUIMessageDataProvider *parentPageDataProvider;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
755
TUIKit/TUIChat/UI_Classic/Chat/TUIRepliesDetailViewController.m
Normal file
755
TUIKit/TUIChat/UI_Classic/Chat/TUIRepliesDetailViewController.m
Normal file
@@ -0,0 +1,755 @@
|
||||
//
|
||||
// TUIRepliesDetailViewController.m
|
||||
// TUIChat
|
||||
//
|
||||
// Created by wyl on 2022/4/27.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
//
|
||||
|
||||
#import "TUIRepliesDetailViewController.h"
|
||||
#import <TIMCommon/TUIMessageCell.h>
|
||||
#import "TUIChatDataProvider.h"
|
||||
#import "TUIMessageDataProvider.h"
|
||||
#import "TUITextMessageCell.h"
|
||||
#import "TUITextMessageCellData.h"
|
||||
#import "TUIChatConfig.h"
|
||||
|
||||
#import <TIMCommon/TIMDefine.h>
|
||||
#import <TIMCommon/TUISystemMessageCell.h>
|
||||
#import <TUICore/TUICore.h>
|
||||
#import <TUICore/TUIDarkModel.h>
|
||||
#import <TUICore/TUIGlobalization.h>
|
||||
#import <TUICore/TUIThemeManager.h>
|
||||
#import "TUIFaceMessageCell.h"
|
||||
#import "TUIFileMessageCell.h"
|
||||
#import "TUIFileViewController.h"
|
||||
#import "TUIImageMessageCell.h"
|
||||
#import "TUIJoinGroupMessageCell.h"
|
||||
#import "TUILinkCell.h"
|
||||
#import "TUIMediaView.h"
|
||||
#import "TUIMergeMessageCell.h"
|
||||
#import "TUIMergeMessageListController.h"
|
||||
#import "TUIMessageDataProvider.h"
|
||||
#import "TUIReferenceMessageCell.h"
|
||||
#import "TUIReplyMessageCell.h"
|
||||
#import "TUIReplyMessageCellData.h"
|
||||
#import "TUITextMessageCell.h"
|
||||
#import "TUIVideoMessageCell.h"
|
||||
#import "TUIVoiceMessageCell.h"
|
||||
#import "TUIMessageCellConfig.h"
|
||||
|
||||
@interface TUIRepliesDetailViewController () <TUIInputControllerDelegate,
|
||||
UITableViewDelegate,
|
||||
UITableViewDataSource,
|
||||
TUIMessageBaseDataProviderDataSource,
|
||||
TUIMessageCellDelegate,
|
||||
TUINotificationProtocol,
|
||||
V2TIMAdvancedMsgListener>
|
||||
|
||||
@property(nonatomic, strong) TUIMessageCellData *cellData;
|
||||
@property(nonatomic, strong) TUIMessageDataProvider *msgDataProvider;
|
||||
|
||||
@property(nonatomic, strong) UIView *headerView;
|
||||
@property(nonatomic, strong) UITableView *tableView;
|
||||
@property(nonatomic, strong) NSArray<V2TIMMessage *> *imMsgs;
|
||||
@property(nonatomic, strong) NSMutableArray<TUIMessageCellData *> *uiMsgs;
|
||||
@property(nonatomic, assign) BOOL responseKeyboard;
|
||||
@property(nonatomic, strong) TUIChatConversationModel *conversationData;
|
||||
@property(nonatomic, strong) TUIMessageCellConfig *messageCellConfig;
|
||||
|
||||
@property(nonatomic, strong) TUIMessageCellLayout *originCellLayout;
|
||||
@property TMsgDirection direction;
|
||||
@property(nonatomic, assign) BOOL showName;
|
||||
@property(nonatomic, assign) BOOL showMessageTime;
|
||||
@property(nonatomic) BOOL isMsgNeedReadReceipt;
|
||||
|
||||
@end
|
||||
|
||||
@implementation TUIRepliesDetailViewController
|
||||
- (instancetype)initWithCellData:(TUIMessageCellData *)data conversationData:(TUIChatConversationModel *)conversationData {
|
||||
self = [super init];
|
||||
self.cellData = data;
|
||||
[self setConversation:conversationData];
|
||||
return self;
|
||||
}
|
||||
- (void)viewDidLoad {
|
||||
[super viewDidLoad];
|
||||
|
||||
[self setupViews];
|
||||
|
||||
[self setupInputViewController];
|
||||
|
||||
[[V2TIMManager sharedInstance] addAdvancedMsgListener:self];
|
||||
|
||||
[TUICore registerEvent:TUICore_TUIPluginNotify
|
||||
subKey:TUICore_TUIPluginNotify_DidChangePluginViewSubKey
|
||||
object:self];
|
||||
}
|
||||
- (void)viewWillAppear:(BOOL)animated {
|
||||
[self updateRootMsg];
|
||||
|
||||
[self applyData];
|
||||
|
||||
[self updateTableViewConstraint];
|
||||
|
||||
}
|
||||
|
||||
- (void)viewDidAppear:(BOOL)animated {
|
||||
[super viewDidAppear:animated];
|
||||
self.responseKeyboard = YES;
|
||||
self.isMsgNeedReadReceipt = YES;
|
||||
}
|
||||
|
||||
- (void)viewWillDisappear:(BOOL)animated {
|
||||
[super viewWillDisappear:animated];
|
||||
self.responseKeyboard = NO;
|
||||
[self revertRootMsg];
|
||||
if (self.willCloseCallback) {
|
||||
self.willCloseCallback();
|
||||
}
|
||||
}
|
||||
- (void)viewDidDisappear:(BOOL)animated {
|
||||
[super viewDidDisappear:animated];
|
||||
|
||||
if (self.inputController.status == Input_Status_Input || self.inputController.status == Input_Status_Input_Keyboard) {
|
||||
CGPoint offset = self.tableView.contentOffset;
|
||||
__weak typeof(self) weakSelf = self;
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
__strong typeof(weakSelf) strongSelf = weakSelf;
|
||||
strongSelf.responseKeyboard = YES;
|
||||
[UIApplication.sharedApplication.keyWindow endEditing:YES];
|
||||
[strongSelf inputController:strongSelf.inputController didChangeHeight:CGRectGetMaxY(strongSelf.inputController.inputBar.frame) + Bottom_SafeHeight];
|
||||
[strongSelf.tableView setContentOffset:offset];
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
- (void)dealloc {
|
||||
NSLog(@"%s dealloc", __FUNCTION__);
|
||||
[TUICore unRegisterEventByObject:self];
|
||||
}
|
||||
|
||||
- (void)applyData {
|
||||
NSArray *messageModifyReplies = self.cellData.messageModifyReplies;
|
||||
NSMutableArray *msgIDArray = [NSMutableArray array];
|
||||
if (messageModifyReplies.count > 0) {
|
||||
for (NSDictionary *dic in messageModifyReplies) {
|
||||
if (dic) {
|
||||
NSString *messageID = dic[@"messageID"];
|
||||
if (IS_NOT_EMPTY_NSSTRING(messageID)) {
|
||||
[msgIDArray addObject:messageID];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// When the only reply is retracted, go back to the previous controller
|
||||
if (msgIDArray.count <= 0) {
|
||||
[self.navigationController popViewControllerAnimated:YES];
|
||||
}
|
||||
|
||||
__weak typeof(self) weakSelf = self;
|
||||
[TUIChatDataProvider findMessages:msgIDArray
|
||||
callback:^(BOOL succ, NSString *_Nonnull error_message, NSArray *_Nonnull msgs) {
|
||||
__strong typeof(weakSelf) strongSelf = weakSelf;
|
||||
if (succ) {
|
||||
if (msgs.count > 0) {
|
||||
strongSelf.imMsgs = msgs;
|
||||
strongSelf.uiMsgs = [self transUIMsgFromIMMsg:msgs];
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
if (strongSelf.uiMsgs.count != 0) {
|
||||
[strongSelf.tableView reloadData];
|
||||
[strongSelf.tableView layoutIfNeeded];
|
||||
dispatch_after(dispatch_time(DISPATCH_TIME_NOW,
|
||||
(int64_t)(0.1 * NSEC_PER_SEC)),
|
||||
dispatch_get_main_queue(), ^{
|
||||
[strongSelf scrollToBottom:NO];
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)updateTableViewConstraint {
|
||||
CGFloat textViewHeight = TUIChatConfig.defaultConfig.enableMainPageInputBar? TTextView_Height:0;
|
||||
CGFloat height = textViewHeight + Bottom_SafeHeight;
|
||||
CGRect msgFrame = self.tableView.frame;
|
||||
msgFrame.size.height = self.view.frame.size.height - height;
|
||||
self.tableView.frame = msgFrame;
|
||||
}
|
||||
|
||||
- (void)setupViews {
|
||||
self.title = TIMCommonLocalizableString(TUIKitRepliesDetailTitle);
|
||||
self.view.backgroundColor = TUIChatDynamicColor(@"chat_controller_bg_color", @"#FFFFFF");
|
||||
self.tableView.scrollsToTop = NO;
|
||||
self.tableView.delegate = self;
|
||||
self.tableView.dataSource = self;
|
||||
[self.tableView setSeparatorStyle:UITableViewCellSeparatorStyleNone];
|
||||
self.tableView.contentInset = UIEdgeInsetsMake(5, 0, 0, 0);
|
||||
[self.messageCellConfig bindTableView:self.tableView];
|
||||
}
|
||||
|
||||
- (void)setupInputViewController {
|
||||
_inputController = [[TUIInputController alloc] init];
|
||||
_inputController.delegate = self;
|
||||
_inputController.view.frame =
|
||||
CGRectMake(0, self.view.frame.size.height - TTextView_Height - Bottom_SafeHeight, self.view.frame.size.width, TTextView_Height + Bottom_SafeHeight);
|
||||
_inputController.view.autoresizingMask = UIViewAutoresizingFlexibleTopMargin;
|
||||
_inputController.inputBar.isFromReplyPage = YES;
|
||||
[self addChildViewController:_inputController];
|
||||
[self.view addSubview:_inputController.view];
|
||||
TUIFaceGroup *group = TIMConfig.defaultConfig.faceGroups[0];
|
||||
[_inputController.faceSegementScrollView setItems:(id) @[ group ] delegate:(id)_inputController];
|
||||
TUIMenuCellData *data = [[TUIMenuCellData alloc] init];
|
||||
data.path = group.menuPath;
|
||||
data.isSelected = YES;
|
||||
[_inputController.menuView setData:(id) @[ data ]];
|
||||
_inputController.view.hidden = !TUIChatConfig.defaultConfig.enableMainPageInputBar;
|
||||
CGFloat margin = 0;
|
||||
CGFloat padding = 10;
|
||||
_inputController.inputBar.inputTextView.frame =
|
||||
CGRectMake(margin, _inputController.inputBar.inputTextView.frame.origin.y,
|
||||
_inputController.inputBar.frame.size.width - _inputController.inputBar.faceButton.frame.size.width - margin * 2 - padding,
|
||||
_inputController.inputBar.inputTextView.frame.size.height);
|
||||
|
||||
_inputController.inputBar.faceButton.frame =
|
||||
CGRectMake(_inputController.inputBar.frame.size.width - _inputController.inputBar.faceButton.frame.size.width - margin,
|
||||
_inputController.inputBar.faceButton.frame.origin.y, _inputController.inputBar.faceButton.frame.size.width,
|
||||
_inputController.inputBar.faceButton.frame.size.height);
|
||||
|
||||
if (_inputController.inputBar.micButton) {
|
||||
_inputController.inputBar.micButton.alpha = 0;
|
||||
}
|
||||
if (_inputController.inputBar.moreButton) {
|
||||
_inputController.inputBar.moreButton.alpha = 0;
|
||||
}
|
||||
[_inputController.inputBar defaultLayout];
|
||||
}
|
||||
|
||||
- (void)updateRootMsg {
|
||||
self.originCellLayout = self.cellData.cellLayout;
|
||||
self.direction = self.cellData.direction;
|
||||
self.showName = self.cellData.showName;
|
||||
self.showMessageTime = self.cellData.showMessageTime;
|
||||
|
||||
TUIMessageCellData *data = self.cellData;
|
||||
TUIMessageCellLayout *layout = TUIMessageCellLayout.incommingMessageLayout;
|
||||
if ([data isKindOfClass:TUITextMessageCellData.class]) {
|
||||
layout = TUIMessageCellLayout.incommingTextMessageLayout;
|
||||
}
|
||||
if ([data isKindOfClass:TUIReferenceMessageCellData.class]) {
|
||||
layout = TUIMessageCellLayout.incommingTextMessageLayout;
|
||||
}
|
||||
if ([data isKindOfClass:TUIVoiceMessageCellData.class]) {
|
||||
layout = [TUIMessageCellLayout incommingVoiceMessageLayout];
|
||||
}
|
||||
self.cellData.cellLayout = layout;
|
||||
self.cellData.direction = MsgDirectionIncoming;
|
||||
self.cellData.showName = YES;
|
||||
self.cellData.showMessageModifyReplies = NO;
|
||||
self.cellData.showMessageTime = YES;
|
||||
}
|
||||
- (void)revertRootMsg {
|
||||
self.cellData.cellLayout = self.originCellLayout;
|
||||
self.cellData.direction = self.direction;
|
||||
self.cellData.showName = self.showName;
|
||||
self.cellData.showMessageModifyReplies = YES;
|
||||
self.cellData.showMessageTime = self.showMessageTime;
|
||||
}
|
||||
|
||||
- (NSMutableArray *)transUIMsgFromIMMsg:(NSArray *)msgs {
|
||||
NSMutableArray *uiMsgs = [NSMutableArray array];
|
||||
for (NSInteger k = 0; k < msgs.count; k++) {
|
||||
V2TIMMessage *msg = msgs[k];
|
||||
TUIMessageCellData *data = [TUITextMessageCellData getCellData:msg];
|
||||
TUIMessageCellLayout *layout = TUIMessageCellLayout.incommingMessageLayout;
|
||||
if ([data isKindOfClass:TUITextMessageCellData.class]) {
|
||||
layout = TUIMessageCellLayout.incommingTextMessageLayout;
|
||||
}
|
||||
data.cellLayout = layout;
|
||||
data.direction = MsgDirectionIncoming;
|
||||
data.showName = YES;
|
||||
if (data) {
|
||||
data.innerMessage = msg;
|
||||
[uiMsgs addObject:data];
|
||||
}
|
||||
}
|
||||
|
||||
NSArray *sortedArray = [uiMsgs sortedArrayUsingComparator:^NSComparisonResult(TUIMessageCellData *obj1, TUIMessageCellData *obj2) {
|
||||
if ([obj1.innerMessage.timestamp timeIntervalSince1970] == [obj2.innerMessage.timestamp timeIntervalSince1970]) {
|
||||
return obj1.innerMessage.seq > obj2.innerMessage.seq;
|
||||
} else {
|
||||
return [obj1.innerMessage.timestamp compare:obj2.innerMessage.timestamp];
|
||||
}
|
||||
}];
|
||||
|
||||
uiMsgs = [NSMutableArray arrayWithArray:sortedArray];
|
||||
|
||||
return uiMsgs;
|
||||
}
|
||||
|
||||
#pragma mark - tableView
|
||||
|
||||
- (UITableView *)tableView {
|
||||
if (!_tableView) {
|
||||
CGRect rect = self.view.bounds;
|
||||
_tableView = [[UITableView alloc] initWithFrame:rect style:UITableViewStylePlain];
|
||||
if (@available(iOS 15.0, *)) {
|
||||
_tableView.sectionHeaderTopPadding = 0;
|
||||
}
|
||||
[self.view addSubview:_tableView];
|
||||
}
|
||||
return _tableView;
|
||||
}
|
||||
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
|
||||
return 2;
|
||||
}
|
||||
- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section {
|
||||
if (section == 1) {
|
||||
return 20;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section {
|
||||
if (section == 1) {
|
||||
UIView *view = [[UIView alloc] init];
|
||||
view.backgroundColor = [UIColor clearColor];
|
||||
return view;
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section {
|
||||
if (section == 0) {
|
||||
return 0.5;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
- (UIView *)tableView:(UITableView *)tableView viewForFooterInSection:(NSInteger)section {
|
||||
if (section == 0) {
|
||||
UIView *line = [[UIView alloc] initWithFrame:CGRectMake(0, 0, Screen_Width, 0.5)];
|
||||
line.backgroundColor = TIMCommonDynamicColor(@"separator_color", @"#DBDBDB");
|
||||
return line;
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
|
||||
if (section == 0) {
|
||||
return 1;
|
||||
}
|
||||
return _uiMsgs.count;
|
||||
}
|
||||
|
||||
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
|
||||
if (indexPath.section == 0) {
|
||||
return [self.messageCellConfig getHeightFromMessageCellData:self.cellData];
|
||||
} else {
|
||||
if (indexPath.row < self.uiMsgs.count) {
|
||||
TUIMessageCellData *cellData = self.uiMsgs[indexPath.row];
|
||||
return [self.messageCellConfig getHeightFromMessageCellData:cellData];
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
|
||||
if (indexPath.section == 0) {
|
||||
TUIMessageCell *cell = nil;
|
||||
TUIMessageCellData *data = self.cellData;
|
||||
cell = [tableView dequeueReusableCellWithIdentifier:data.reuseId forIndexPath:indexPath];
|
||||
cell.delegate = self;
|
||||
[cell fillWithData:data];
|
||||
[cell notifyBottomContainerReadyOfData:nil];
|
||||
return cell;
|
||||
}
|
||||
TUIMessageCellData *data = _uiMsgs[indexPath.row];
|
||||
data.showMessageTime = YES;
|
||||
data.showCheckBox = NO;
|
||||
TUIMessageCell *cell = nil;
|
||||
|
||||
cell = [tableView dequeueReusableCellWithIdentifier:data.reuseId forIndexPath:indexPath];
|
||||
[cell fillWithData:_uiMsgs[indexPath.row]];
|
||||
cell.delegate = self;
|
||||
[cell notifyBottomContainerReadyOfData:nil];
|
||||
if ([cell isKindOfClass:TUIBubbleMessageCell.class]) {
|
||||
TUIBubbleMessageCell *bubbleCell = (TUIBubbleMessageCell *)cell;
|
||||
if (bubbleCell.bubbleView) {
|
||||
bubbleCell.bubbleView.image = nil;
|
||||
}
|
||||
}
|
||||
return cell;
|
||||
}
|
||||
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView {
|
||||
[self.inputController reset];
|
||||
}
|
||||
#pragma mark - TUIInputControllerDelegate
|
||||
|
||||
- (void)inputController:(TUIInputController *)inputController didChangeHeight:(CGFloat)height {
|
||||
if (!self.responseKeyboard) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (self.inputController.replyData == nil) {
|
||||
[self onRelyMessage:self.cellData];
|
||||
}
|
||||
[UIView animateWithDuration:0.3
|
||||
delay:0
|
||||
options:UIViewAnimationOptionCurveEaseOut
|
||||
animations:^{
|
||||
CGRect msgFrame = self.tableView.frame;
|
||||
msgFrame.size.height = self.view.frame.size.height - height;
|
||||
self.tableView.frame = msgFrame;
|
||||
|
||||
CGRect inputFrame = self.inputController.view.frame;
|
||||
inputFrame.origin.y = msgFrame.origin.y + msgFrame.size.height;
|
||||
inputFrame.size.height = height;
|
||||
self.inputController.view.frame = inputFrame;
|
||||
|
||||
[self scrollToBottom:NO];
|
||||
}
|
||||
completion:nil];
|
||||
}
|
||||
|
||||
- (void)inputController:(TUIInputController *)inputController didSendMessage:(V2TIMMessage *)msg {
|
||||
[self sendMessage:msg];
|
||||
}
|
||||
|
||||
- (void)inputController:(TUIInputController *)inputController didSelectMoreCell:(TUIInputMoreCell *)cell {
|
||||
cell.disableDefaultSelectAction = NO;
|
||||
|
||||
if (cell.disableDefaultSelectAction) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)sendMessage:(V2TIMMessage *)message {
|
||||
TUIMessageCellData *cellData = nil;
|
||||
if (!cellData) {
|
||||
cellData = [TUIMessageDataProvider getCellData:message];
|
||||
}
|
||||
if (cellData) {
|
||||
cellData.innerMessage.needReadReceipt = self.isMsgNeedReadReceipt;
|
||||
[self sendUIMessage:cellData];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)sendUIMessage:(TUIMessageCellData *)cellData {
|
||||
@weakify(self);
|
||||
[self.parentPageDataProvider sendUIMsg:cellData
|
||||
toConversation:self.conversationData
|
||||
willSendBlock:^(BOOL isReSend, TUIMessageCellData *_Nonnull dateUIMsg) {
|
||||
@strongify(self);
|
||||
|
||||
int delay = 1;
|
||||
if ([cellData isKindOfClass:[TUIImageMessageCellData class]]) {
|
||||
delay = 0;
|
||||
}
|
||||
|
||||
@weakify(self);
|
||||
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delay * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
|
||||
@strongify(self);
|
||||
if (cellData.status == Msg_Status_Sending) {
|
||||
[self changeMsg:cellData status:Msg_Status_Sending_2];
|
||||
}
|
||||
});
|
||||
}
|
||||
SuccBlock:^{
|
||||
@strongify(self);
|
||||
[self changeMsg:cellData status:Msg_Status_Succ];
|
||||
[self scrollToBottom:YES];
|
||||
}
|
||||
FailBlock:^(int code, NSString *desc) {
|
||||
@strongify(self);
|
||||
[TUITool makeToastError:code msg:desc];
|
||||
[self changeMsg:cellData status:Msg_Status_Fail];
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)scrollToBottom:(BOOL)animated {
|
||||
if (self.uiMsgs.count > 0) {
|
||||
[self.tableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:self.uiMsgs.count - 1 inSection:1]
|
||||
atScrollPosition:UITableViewScrollPositionBottom
|
||||
animated:animated];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)changeMsg:(TUIMessageCellData *)msg status:(TMsgStatus)status {
|
||||
msg.status = status;
|
||||
NSInteger index = [self.uiMsgs indexOfObject:msg];
|
||||
if ([self.tableView numberOfRowsInSection:0] > index) {
|
||||
TUIMessageCell *cell = [self.tableView cellForRowAtIndexPath:[NSIndexPath indexPathForRow:index inSection:0]];
|
||||
[cell fillWithData:msg];
|
||||
} else {
|
||||
NSLog(@"lack of cell");
|
||||
}
|
||||
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:@"kTUINotifyMessageStatusChanged"
|
||||
object:nil
|
||||
userInfo:@{
|
||||
@"msg" : msg,
|
||||
@"status" : [NSNumber numberWithUnsignedInteger:status],
|
||||
@"msgSender" : self,
|
||||
}];
|
||||
}
|
||||
|
||||
#pragma mark - Message reply
|
||||
|
||||
- (void)onRelyMessage:(nonnull TUIMessageCellData *)data {
|
||||
NSString *desc = @"";
|
||||
desc = [self replyReferenceMessageDesc:data];
|
||||
|
||||
TUIReplyPreviewData *replyData = [[TUIReplyPreviewData alloc] init];
|
||||
replyData.msgID = data.msgID;
|
||||
replyData.msgAbstract = desc;
|
||||
replyData.sender = data.senderName;
|
||||
replyData.type = (NSInteger)data.innerMessage.elemType;
|
||||
replyData.originMessage = data.innerMessage;
|
||||
self.inputController.replyData = replyData;
|
||||
}
|
||||
- (NSString *)replyReferenceMessageDesc:(TUIMessageCellData *)data {
|
||||
NSString *desc = @"";
|
||||
if (data.innerMessage.elemType == V2TIM_ELEM_TYPE_FILE) {
|
||||
desc = data.innerMessage.fileElem.filename;
|
||||
} else if (data.innerMessage.elemType == V2TIM_ELEM_TYPE_MERGER) {
|
||||
desc = data.innerMessage.mergerElem.title;
|
||||
} else if (data.innerMessage.elemType == V2TIM_ELEM_TYPE_CUSTOM) {
|
||||
desc = [TUIMessageDataProvider getDisplayString:data.innerMessage];
|
||||
} else if (data.innerMessage.elemType == V2TIM_ELEM_TYPE_TEXT) {
|
||||
desc = data.innerMessage.textElem.text;
|
||||
}
|
||||
return desc;
|
||||
}
|
||||
|
||||
#pragma mark - TUIMessageCellDelegate
|
||||
- (void)onSelectMessage:(TUIMessageCell *)cell {
|
||||
if (TUIChatConfig.defaultConfig.eventConfig.chatEventListener &&
|
||||
[TUIChatConfig.defaultConfig.eventConfig.chatEventListener respondsToSelector:@selector(onMessageClicked:messageCellData:)]) {
|
||||
BOOL result = [TUIChatConfig.defaultConfig.eventConfig.chatEventListener onMessageClicked:cell messageCellData:cell.messageData];
|
||||
if (result) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
if ([cell isKindOfClass:[TUIImageMessageCell class]]) {
|
||||
[self showImageMessage:(TUIImageMessageCell *)cell];
|
||||
}
|
||||
if ([cell isKindOfClass:[TUIVoiceMessageCell class]]) {
|
||||
[self playVoiceMessage:(TUIVoiceMessageCell *)cell];
|
||||
}
|
||||
if ([cell isKindOfClass:[TUIVideoMessageCell class]]) {
|
||||
[self showVideoMessage:(TUIVideoMessageCell *)cell];
|
||||
}
|
||||
if ([cell isKindOfClass:[TUIFileMessageCell class]]) {
|
||||
[self showFileMessage:(TUIFileMessageCell *)cell];
|
||||
}
|
||||
if ([cell isKindOfClass:[TUIMergeMessageCell class]]) {
|
||||
TUIMergeMessageListController *mergeVc = [[TUIMergeMessageListController alloc] init];
|
||||
mergeVc.mergerElem = [(TUIMergeMessageCell *)cell mergeData].mergerElem;
|
||||
mergeVc.delegate = self.delegate;
|
||||
[self.navigationController pushViewController:mergeVc animated:YES];
|
||||
}
|
||||
if ([cell isKindOfClass:[TUILinkCell class]]) {
|
||||
[self showLinkMessage:(TUILinkCell *)cell];
|
||||
}
|
||||
// if ([cell isKindOfClass:[TUIReplyMessageCell class]]) {
|
||||
// [self showReplyMessage:(TUIReplyMessageCell *)cell];
|
||||
// }
|
||||
// if ([cell isKindOfClass:[TUIReferenceMessageCell class]]) {
|
||||
// [self showReplyMessage:(TUIReplyMessageCell *)cell];
|
||||
// }
|
||||
|
||||
if ([self.delegate respondsToSelector:@selector(messageController:onSelectMessageContent:)]) {
|
||||
[self.delegate messageController:nil onSelectMessageContent:cell];
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - V2TIMAdvancedMsgListener
|
||||
|
||||
- (void)onRecvNewMessage:(V2TIMMessage *)msg {
|
||||
V2TIMMessage *imMsg = msg;
|
||||
if (imMsg == nil || ![imMsg isKindOfClass:V2TIMMessage.class]) {
|
||||
return;
|
||||
}
|
||||
if ([imMsg.msgID isEqualToString:self.cellData.msgID] ) {
|
||||
TUIMessageCellData *cellData = [TUIMessageDataProvider getCellData:imMsg];
|
||||
self.cellData.messageModifyReplies = cellData.messageModifyReplies;
|
||||
[self applyData];
|
||||
}
|
||||
|
||||
}
|
||||
- (void)onRecvMessageModified:(V2TIMMessage *)msg {
|
||||
V2TIMMessage *imMsg = msg;
|
||||
if (imMsg == nil || ![imMsg isKindOfClass:V2TIMMessage.class]) {
|
||||
return;
|
||||
}
|
||||
if ([imMsg.msgID isEqualToString:self.cellData.msgID] ) {
|
||||
TUIMessageCellData *cellData = [TUIMessageDataProvider getCellData:imMsg];
|
||||
self.cellData.messageModifyReplies = cellData.messageModifyReplies;
|
||||
[self applyData];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#pragma mark - dataProviderDataChange
|
||||
- (void)dataProviderDataSourceWillChange:(TUIMessageDataProvider *)dataProvider {
|
||||
}
|
||||
|
||||
- (void)dataProviderDataSourceChange:(TUIMessageDataProvider *)dataProvider
|
||||
withType:(TUIMessageBaseDataProviderDataSourceChangeType)type
|
||||
atIndex:(NSUInteger)index
|
||||
animation:(BOOL)animation {
|
||||
}
|
||||
|
||||
- (void)dataProviderDataSourceDidChange:(TUIMessageDataProvider *)dataProvider {
|
||||
|
||||
}
|
||||
|
||||
- (void)dataProvider:(TUIMessageBaseDataProvider *)dataProvider onRemoveHeightCache:(TUIMessageCellData *)cellData {
|
||||
if (cellData) {
|
||||
[self.messageCellConfig removeHeightCacheOfMessageCellData:cellData];
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - action
|
||||
|
||||
- (void)showImageMessage:(TUIImageMessageCell *)cell {
|
||||
CGRect frame = [cell.thumb convertRect:cell.thumb.bounds toView:[UIApplication sharedApplication].delegate.window];
|
||||
TUIMediaView *mediaView = [[TUIMediaView alloc] initWithFrame:CGRectMake(0, 0, Screen_Width, Screen_Height)];
|
||||
[mediaView setThumb:cell.thumb frame:frame];
|
||||
[mediaView setCurMessage:cell.messageData.innerMessage allMessages:@[ self.cellData.innerMessage ]];
|
||||
[[UIApplication sharedApplication].keyWindow addSubview:mediaView];
|
||||
}
|
||||
|
||||
- (void)playVoiceMessage:(TUIVoiceMessageCell *)cell {
|
||||
TUIVoiceMessageCellData *uiMsg = (TUIVoiceMessageCellData *)self.cellData;
|
||||
if (uiMsg == cell.voiceData) {
|
||||
[uiMsg playVoiceMessage];
|
||||
cell.voiceReadPoint.hidden = YES;
|
||||
} else {
|
||||
[uiMsg stopVoiceMessage];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)showVideoMessage:(TUIVideoMessageCell *)cell {
|
||||
CGRect frame = [cell.thumb convertRect:cell.thumb.bounds toView:[UIApplication sharedApplication].delegate.window];
|
||||
TUIMediaView *mediaView = [[TUIMediaView alloc] initWithFrame:CGRectMake(0, 0, Screen_Width, Screen_Height)];
|
||||
[mediaView setThumb:cell.thumb frame:frame];
|
||||
[mediaView setCurMessage:cell.messageData.innerMessage allMessages:@[ self.cellData.innerMessage ]];
|
||||
__weak typeof(self) weakSelf = self;
|
||||
mediaView.onClose = ^{
|
||||
[weakSelf.tableView reloadData];
|
||||
};
|
||||
[[UIApplication sharedApplication].keyWindow addSubview:mediaView];
|
||||
}
|
||||
|
||||
- (void)showFileMessage:(TUIFileMessageCell *)cell {
|
||||
TUIFileViewController *file = [[TUIFileViewController alloc] init];
|
||||
file.data = [cell fileData];
|
||||
[self.navigationController pushViewController:file animated:YES];
|
||||
}
|
||||
|
||||
- (void)showLinkMessage:(TUILinkCell *)cell {
|
||||
TUILinkCellData *cellData = cell.customData;
|
||||
if (cellData.link) {
|
||||
[[UIApplication sharedApplication] openURL:[NSURL URLWithString:cellData.link]];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setConversation:(TUIChatConversationModel *)conversationData {
|
||||
self.conversationData = conversationData;
|
||||
if (!self.msgDataProvider) {
|
||||
self.msgDataProvider = [[TUIMessageDataProvider alloc] initWithConversationModel:conversationData];
|
||||
self.msgDataProvider.dataSource = self;
|
||||
}
|
||||
[self loadMessage];
|
||||
}
|
||||
- (void)loadMessage {
|
||||
if (self.msgDataProvider.isLoadingData || self.msgDataProvider.isNoMoreMsg) {
|
||||
return;
|
||||
}
|
||||
|
||||
[self.msgDataProvider
|
||||
loadMessageSucceedBlock:^(BOOL isFirstLoad, BOOL isNoMoreMsg, NSArray<TUIMessageCellData *> *_Nonnull newMsgs) {
|
||||
|
||||
}
|
||||
FailBlock:^(int code, NSString *desc) {
|
||||
[TUITool makeToastError:code msg:desc];
|
||||
}];
|
||||
}
|
||||
|
||||
#pragma mark - TUINotificationProtocol
|
||||
- (void)onNotifyEvent:(NSString *)key subKey:(NSString *)subKey object:(id)anObject param:(NSDictionary *)param {
|
||||
if ([key isEqualToString:TUICore_TUIPluginNotify] &&
|
||||
[subKey isEqualToString:TUICore_TUIPluginNotify_DidChangePluginViewSubKey]) {
|
||||
TUIMessageCellData *data = param[TUICore_TUIPluginNotify_DidChangePluginViewSubKey_Data];
|
||||
NSInteger section = 1;
|
||||
if ([data.msgID isEqualToString:self.cellData.msgID] ) {
|
||||
//root section
|
||||
section = 0;
|
||||
}
|
||||
[self.messageCellConfig removeHeightCacheOfMessageCellData:data];
|
||||
[self reloadAndScrollToBottomOfMessage:data.innerMessage.msgID section:section];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)reloadAndScrollToBottomOfMessage:(NSString *)messageID section:(NSInteger)section {
|
||||
// Dispatch the task to RunLoop to ensure that they are executed after the UITableView refresh is complete.
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[self reloadCellOfMessage:messageID section:section];
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[self scrollCellToBottomOfMessage:messageID section:section];
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
- (void)reloadCellOfMessage:(NSString *)messageID section:(NSInteger)section {
|
||||
NSIndexPath *indexPath = [self indexPathOfMessage:messageID section:section];
|
||||
|
||||
// Disable animation when loading to avoid cell jumping.
|
||||
if (indexPath == nil) {
|
||||
return;
|
||||
}
|
||||
[UIView performWithoutAnimation:^{
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[self.tableView reloadRowsAtIndexPaths:@[ indexPath ] withRowAnimation:UITableViewRowAnimationNone];
|
||||
});
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)scrollCellToBottomOfMessage:(NSString *)messageID section:(NSInteger)section {
|
||||
NSIndexPath *indexPath = [self indexPathOfMessage:messageID section:section];
|
||||
|
||||
// Scroll the tableView only if the bottom of the cell is invisible.
|
||||
CGRect cellRect = [self.tableView rectForRowAtIndexPath:indexPath];
|
||||
CGRect tableViewRect = self.tableView.bounds;
|
||||
BOOL isBottomInvisible = cellRect.origin.y < CGRectGetMaxY(tableViewRect) && CGRectGetMaxY(cellRect) > CGRectGetMaxY(tableViewRect);
|
||||
if (isBottomInvisible) {
|
||||
[self.tableView scrollToRowAtIndexPath:indexPath atScrollPosition:UITableViewScrollPositionBottom animated:YES];
|
||||
}
|
||||
}
|
||||
|
||||
- (NSIndexPath *)indexPathOfMessage:(NSString *)messageID section:(NSInteger)section {
|
||||
if (section == 0) {
|
||||
return [NSIndexPath indexPathForRow:0 inSection:section];
|
||||
} else {
|
||||
for (int i = 0; i < self.uiMsgs.count; i++) {
|
||||
TUIMessageCellData *data = self.uiMsgs[i];
|
||||
if ([data.innerMessage.msgID isEqualToString:messageID]) {
|
||||
return [NSIndexPath indexPathForRow:i inSection:section];
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (TUIMessageCellConfig *)messageCellConfig {
|
||||
if (_messageCellConfig == nil) {
|
||||
_messageCellConfig = [[TUIMessageCellConfig alloc] init];
|
||||
}
|
||||
return _messageCellConfig;
|
||||
}
|
||||
|
||||
@end
|
||||
Reference in New Issue
Block a user