增加换肤功能
This commit is contained in:
@@ -0,0 +1,16 @@
|
||||
|
||||
// Created by Tencent on 2023/06/09.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
/**
|
||||
* This document declares that after the group is successfully created, the "xxx create group chat" message cell displayed when jumping to the message
|
||||
* interface
|
||||
*/
|
||||
|
||||
#import <TIMCommon/TUISystemMessageCell.h>
|
||||
#import "TUIGroupCreatedCellData.h"
|
||||
|
||||
@interface TUIGroupCreatedCell_Minimalist : TUISystemMessageCell
|
||||
|
||||
- (void)fillWithData:(TUIGroupCreatedCellData *)data;
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,17 @@
|
||||
//
|
||||
// MyCustomCell.m
|
||||
// TUIKitDemo
|
||||
//
|
||||
// Created by annidyfeng on 2019/6/10.
|
||||
// Copyright © 2019 Tencent. All rights reserved.
|
||||
//
|
||||
|
||||
#import "TUIGroupCreatedCell_Minimalist.h"
|
||||
|
||||
@implementation TUIGroupCreatedCell_Minimalist
|
||||
|
||||
- (void)fillWithData:(TUIGroupCreatedCellData *)data {
|
||||
[super fillWithData:data];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,20 @@
|
||||
//
|
||||
// TUIGroupProfileCardCellData.h
|
||||
// TUIGroup
|
||||
//
|
||||
// Created by wyl on 2023/1/3.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
//
|
||||
|
||||
#import <TIMCommon/TIMCommonModel.h>
|
||||
@class UINavigationController;
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface TUIGroupProfileCardCellData_Minimalist : TUIProfileCardCellData
|
||||
|
||||
@property(nonatomic, weak) UINavigationController *navigationController;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,23 @@
|
||||
//
|
||||
// TUIGroupProfileCardCellData_Minimalist.m
|
||||
// TUIGroup
|
||||
//
|
||||
// Created by wyl on 2023/1/3.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
//
|
||||
|
||||
#import "TUIGroupProfileCardCellData_Minimalist.h"
|
||||
#import <TIMCommon/TIMDefine.h>
|
||||
#import <TUICore/TUIThemeManager.h>
|
||||
|
||||
@implementation TUIGroupProfileCardCellData_Minimalist
|
||||
- (instancetype)init {
|
||||
self = [super init];
|
||||
return self;
|
||||
}
|
||||
|
||||
- (CGFloat)heightOfWidth:(CGFloat)width {
|
||||
return kScale390(329);
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,44 @@
|
||||
//
|
||||
// TUIGroupProfileCardViewCell_Minimalist.h
|
||||
// TUIGroup
|
||||
//
|
||||
// Created by wyl on 2023/1/3.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
//
|
||||
|
||||
#import <TIMCommon/TIMCommonModel.h>
|
||||
#import "TUIGroupProfileCardCellData_Minimalist.h"
|
||||
|
||||
@class V2TIMGroupInfo;
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface TUIGroupProfileHeaderItemView_Minimalist : UIView
|
||||
@property(nonatomic, strong) UIImageView *iconView;
|
||||
@property(nonatomic, strong) UILabel *textLabel;
|
||||
@property(nonatomic, copy) void (^messageBtnClickBlock)(void);
|
||||
@end
|
||||
|
||||
@interface TUIGroupProfileHeaderView_Minimalist : UIView
|
||||
@property(nonatomic, strong) UIImageView *headImg;
|
||||
@property(nonatomic, copy) void (^headImgClickBlock)(void);
|
||||
@property(nonatomic, strong) UILabel *descriptionLabel;
|
||||
@property(nonatomic, strong) UIButton *editButton;
|
||||
@property(nonatomic, copy) void (^editBtnClickBlock)(void);
|
||||
@property(nonatomic, strong) UILabel *idLabel;
|
||||
@property(nonatomic, strong) UIView *functionListView;
|
||||
@property(nonatomic, strong) NSArray<TUIGroupProfileHeaderItemView_Minimalist *> *itemViewList;
|
||||
@property(nonatomic, strong) V2TIMGroupInfo *groupInfo;
|
||||
|
||||
@end
|
||||
|
||||
@interface TUIGroupProfileCardViewCell_Minimalist : TUICommonTableViewCell
|
||||
@property(nonatomic, strong) TUIGroupProfileHeaderView_Minimalist *headerView;
|
||||
@property(nonatomic, strong) TUIGroupProfileCardCellData_Minimalist *cardData;
|
||||
- (void)fillWithData:(TUIGroupProfileCardCellData_Minimalist *)data;
|
||||
|
||||
@property(nonatomic, strong) NSArray<TUIGroupProfileHeaderItemView_Minimalist *> *itemViewList;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,304 @@
|
||||
//
|
||||
// TUIGroupProfileCardViewCell.m
|
||||
// TUIGroup
|
||||
//
|
||||
// Created by wyl on 2023/1/3.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
//
|
||||
|
||||
#import "TUIGroupProfileCardViewCell_Minimalist.h"
|
||||
#import <TIMCommon/TIMCommonModel.h>
|
||||
#import <TIMCommon/TIMDefine.h>
|
||||
#import <TIMCommon/TUIGroupAvatar+Helper.h>
|
||||
#import <TUICore/TUICore.h>
|
||||
#import <TUICore/TUIThemeManager.h>
|
||||
|
||||
@implementation TUIGroupProfileHeaderItemView_Minimalist
|
||||
- (instancetype)initWithFrame:(CGRect)frame {
|
||||
self = [super initWithFrame:frame];
|
||||
if (self) {
|
||||
[self setupView];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
- (void)setupView {
|
||||
self.iconView = [[UIImageView alloc] initWithImage:DefaultAvatarImage];
|
||||
[self addSubview:self.iconView];
|
||||
self.iconView.userInteractionEnabled = YES;
|
||||
self.iconView.contentMode = UIViewContentModeScaleAspectFit;
|
||||
|
||||
self.textLabel = [[UILabel alloc] init];
|
||||
self.textLabel.font = [UIFont systemFontOfSize:kScale390(16)];
|
||||
self.textLabel.rtlAlignment = TUITextRTLAlignmentCenter;
|
||||
self.textLabel.textColor = [UIColor tui_colorWithHex:@"#000000"];
|
||||
self.textLabel.userInteractionEnabled = YES;
|
||||
[self addSubview:self.textLabel];
|
||||
self.textLabel.text = @"Message";
|
||||
|
||||
self.backgroundColor = [UIColor tui_colorWithHex:@"#f9f9f9"];
|
||||
self.layer.cornerRadius = kScale390(12);
|
||||
self.layer.masksToBounds = YES;
|
||||
|
||||
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(click)];
|
||||
[self addGestureRecognizer:tap];
|
||||
}
|
||||
+ (BOOL)requiresConstraintBasedLayout {
|
||||
return YES;
|
||||
}
|
||||
|
||||
// this is Apple's recommended place for adding/updating constraints
|
||||
- (void)updateConstraints {
|
||||
|
||||
[super updateConstraints];
|
||||
[self.iconView mas_remakeConstraints:^(MASConstraintMaker *make) {
|
||||
make.width.height.mas_equalTo(kScale390(30));
|
||||
make.top.mas_equalTo(kScale390(19));
|
||||
make.centerX.mas_equalTo(self);
|
||||
}];
|
||||
[self.textLabel sizeToFit];
|
||||
[self.textLabel mas_remakeConstraints:^(MASConstraintMaker *make) {
|
||||
make.leading.trailing.mas_equalTo(self);
|
||||
make.height.mas_equalTo(kScale390(19));
|
||||
make.top.mas_equalTo(self.iconView.mas_bottom).mas_offset(kScale390(11));
|
||||
make.centerX.mas_equalTo(self);
|
||||
}];
|
||||
}
|
||||
- (void)layoutSubviews {
|
||||
[super layoutSubviews];
|
||||
|
||||
}
|
||||
- (void)click {
|
||||
if (self.messageBtnClickBlock) {
|
||||
self.messageBtnClickBlock();
|
||||
}
|
||||
}
|
||||
@end
|
||||
|
||||
@interface TUIGroupProfileHeaderView_Minimalist ()
|
||||
@end
|
||||
|
||||
@implementation TUIGroupProfileHeaderView_Minimalist
|
||||
|
||||
- (instancetype)initWithFrame:(CGRect)frame {
|
||||
self = [super initWithFrame:frame];
|
||||
if (self) {
|
||||
[self setupView];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)setupView {
|
||||
self.headImg = [[UIImageView alloc] initWithImage:DefaultAvatarImage];
|
||||
[self addSubview:self.headImg];
|
||||
self.headImg.userInteractionEnabled = YES;
|
||||
self.headImg.contentMode = UIViewContentModeScaleAspectFit;
|
||||
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(headImageClick)];
|
||||
[self.headImg addGestureRecognizer:tap];
|
||||
|
||||
self.descriptionLabel = [[UILabel alloc] init];
|
||||
self.descriptionLabel.font = [UIFont boldSystemFontOfSize:kScale390(24)];
|
||||
self.descriptionLabel.textAlignment = NSTextAlignmentCenter;
|
||||
self.descriptionLabel.userInteractionEnabled = YES;
|
||||
[self addSubview:self.descriptionLabel];
|
||||
|
||||
self.editButton = [UIButton buttonWithType:UIButtonTypeSystem];
|
||||
[self addSubview:self.editButton];
|
||||
[self.editButton setImage:[UIImage imageNamed:TUIContactImagePath(@"icon_group_edit")] forState:UIControlStateNormal];
|
||||
[self.editButton addTarget:self action:@selector(editButtonClick) forControlEvents:UIControlEventTouchUpInside];
|
||||
self.editButton.hidden = YES;
|
||||
|
||||
self.idLabel = [[UILabel alloc] init];
|
||||
self.idLabel.font = [UIFont systemFontOfSize:kScale390(12)];
|
||||
self.idLabel.textColor = [UIColor tui_colorWithHex:@"666666"];
|
||||
self.idLabel.textAlignment = NSTextAlignmentCenter;
|
||||
self.idLabel.userInteractionEnabled = YES;
|
||||
[self addSubview:self.idLabel];
|
||||
|
||||
self.functionListView = [[UIView alloc] init];
|
||||
self.functionListView.userInteractionEnabled = YES;
|
||||
[self addSubview:self.functionListView];
|
||||
}
|
||||
|
||||
- (void)setGroupInfo:(V2TIMGroupInfo *)groupInfo {
|
||||
_groupInfo = groupInfo;
|
||||
[self.headImg sd_setImageWithURL:[NSURL URLWithString:self.groupInfo.faceURL]
|
||||
placeholderImage:DefaultGroupAvatarImageByGroupType(self.groupInfo.groupType)];
|
||||
[self configHeadImageView:groupInfo];
|
||||
|
||||
self.descriptionLabel.text = groupInfo.groupName;
|
||||
self.idLabel.text = self.groupInfo.groupID;
|
||||
if ([self.class isMeSuper:groupInfo]) {
|
||||
self.editButton.hidden = NO;
|
||||
}
|
||||
|
||||
// tell constraints they need updating
|
||||
[self setNeedsUpdateConstraints];
|
||||
|
||||
// update constraints now so we can animate the change
|
||||
[self updateConstraintsIfNeeded];
|
||||
|
||||
[self layoutIfNeeded];
|
||||
|
||||
}
|
||||
- (void)configHeadImageView:(V2TIMGroupInfo *)groupInfo {
|
||||
/**
|
||||
* Setup default avatar
|
||||
*/
|
||||
if (groupInfo.groupID.length > 0) {
|
||||
/**
|
||||
* If it is a group, change the group default avatar to the last used avatar
|
||||
*/
|
||||
[self.headImg sd_setImageWithURL:[NSURL URLWithString:self.groupInfo.faceURL]
|
||||
placeholderImage:DefaultGroupAvatarImageByGroupType(self.groupInfo.groupType)];
|
||||
}
|
||||
|
||||
@weakify(self);
|
||||
NSString *groupID = groupInfo.groupID ?: @"";
|
||||
NSString *pFaceUrl = groupInfo.faceURL ?: @"";
|
||||
NSString *groupType = groupInfo.groupType ?: @"";
|
||||
UIImage *originAvatarImage = DefaultGroupAvatarImageByGroupType(self.groupInfo.groupType) ?: [UIImage new];
|
||||
|
||||
NSDictionary *param = @{
|
||||
@"groupID" : groupID,
|
||||
@"faceUrl" : pFaceUrl,
|
||||
@"groupType" : groupType,
|
||||
@"originAvatarImage" : originAvatarImage,
|
||||
};
|
||||
[TUIGroupAvatar configAvatarByParam:param targetView:self.headImg];
|
||||
}
|
||||
|
||||
- (void)setItemViewList:(NSArray<TUIGroupProfileHeaderItemView_Minimalist *> *)itemList {
|
||||
for (UIView *subView in self.functionListView.subviews) {
|
||||
[subView removeFromSuperview];
|
||||
}
|
||||
if (itemList.count > 0) {
|
||||
for (TUIGroupProfileHeaderItemView_Minimalist *itemView in itemList) {
|
||||
[self.functionListView addSubview:itemView];
|
||||
}
|
||||
CGFloat width = kScale390(92);
|
||||
CGFloat height = kScale390(95);
|
||||
CGFloat space = kScale390(18);
|
||||
CGFloat contentWidth = itemList.count * width + (itemList.count - 1) * space;
|
||||
CGFloat x = 0.5 * (self.bounds.size.width - contentWidth);
|
||||
for (TUIGroupProfileHeaderItemView_Minimalist *itemView in itemList) {
|
||||
itemView.frame = CGRectMake(x, 0, width, height);
|
||||
x = CGRectGetMaxX(itemView.frame) + space;
|
||||
}
|
||||
}
|
||||
}
|
||||
+ (BOOL)requiresConstraintBasedLayout {
|
||||
return YES;
|
||||
}
|
||||
|
||||
// this is Apple's recommended place for adding/updating constraints
|
||||
- (void)updateConstraints {
|
||||
|
||||
[super updateConstraints];
|
||||
CGFloat imgWidth = kScale390(94);
|
||||
[self.headImg mas_remakeConstraints:^(MASConstraintMaker *make) {
|
||||
make.width.height.mas_equalTo(imgWidth);
|
||||
make.centerX.mas_equalTo(self.mas_centerX);
|
||||
make.top.mas_equalTo(kScale390(42));
|
||||
}];
|
||||
|
||||
MASAttachKeys(self.headImg);
|
||||
|
||||
if ([TUIConfig defaultConfig].avatarType == TAvatarTypeRounded) {
|
||||
self.headImg.layer.masksToBounds = YES;
|
||||
self.headImg.layer.cornerRadius = imgWidth / 2;
|
||||
} else if ([TUIConfig defaultConfig].avatarType == TAvatarTypeRadiusCorner) {
|
||||
self.headImg.layer.masksToBounds = YES;
|
||||
self.headImg.layer.cornerRadius = [TUIConfig defaultConfig].avatarCornerRadius;
|
||||
}
|
||||
|
||||
[self.descriptionLabel sizeToFit];
|
||||
[self.descriptionLabel mas_remakeConstraints:^(MASConstraintMaker *make) {
|
||||
make.centerX.mas_equalTo(self.mas_centerX);
|
||||
make.top.mas_equalTo(self.headImg.mas_bottom).mas_offset(kScale390(10));
|
||||
make.height.mas_equalTo(30);
|
||||
make.width.mas_equalTo(self.descriptionLabel.frame.size.width);
|
||||
make.width.mas_lessThanOrEqualTo(self).multipliedBy(0.5);
|
||||
}];
|
||||
MASAttachKeys(self.descriptionLabel);
|
||||
|
||||
[self.editButton mas_remakeConstraints:^(MASConstraintMaker *make) {
|
||||
make.leading.mas_equalTo(self.descriptionLabel.mas_trailing).mas_equalTo(kScale390(3));
|
||||
make.top.mas_equalTo(self.descriptionLabel.mas_top);
|
||||
make.height.mas_equalTo(30);
|
||||
make.width.mas_equalTo(30);
|
||||
}];
|
||||
MASAttachKeys(self.editButton);
|
||||
|
||||
[self.idLabel mas_remakeConstraints:^(MASConstraintMaker *make) {
|
||||
make.leading.mas_equalTo(self.mas_leading);
|
||||
make.top.mas_equalTo(self.descriptionLabel.mas_bottom).mas_offset(kScale390(8));
|
||||
make.height.mas_equalTo(30);
|
||||
make.width.mas_equalTo(self.frame.size.width);
|
||||
}];
|
||||
if (self.functionListView.subviews.count > 0) {
|
||||
[self.functionListView mas_remakeConstraints:^(MASConstraintMaker *make) {
|
||||
make.leading.mas_equalTo(0);
|
||||
make.width.mas_equalTo(self.bounds.size.width);
|
||||
make.height.mas_equalTo(kScale390(95));
|
||||
make.top.mas_equalTo(self.idLabel.mas_bottom).mas_offset(kScale390(18));
|
||||
}];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)layoutSubviews {
|
||||
[super layoutSubviews];
|
||||
}
|
||||
|
||||
- (void)headImageClick {
|
||||
if (self.headImgClickBlock && [self.class isMeSuper:self.groupInfo]) {
|
||||
self.headImgClickBlock();
|
||||
}
|
||||
}
|
||||
|
||||
- (void)editButtonClick {
|
||||
if (self.editBtnClickBlock && [self.class isMeSuper:self.groupInfo]) {
|
||||
self.editBtnClickBlock();
|
||||
}
|
||||
}
|
||||
|
||||
+ (BOOL)isMeSuper:(V2TIMGroupInfo *)groupInfo {
|
||||
return [groupInfo.owner isEqualToString:[[V2TIMManager sharedInstance] getLoginUser]] && (groupInfo.role == V2TIM_GROUP_MEMBER_ROLE_SUPER);
|
||||
}
|
||||
@end
|
||||
|
||||
@implementation TUIGroupProfileCardViewCell_Minimalist
|
||||
|
||||
- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier {
|
||||
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
|
||||
if (self) {
|
||||
[self setupViews];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)setupViews {
|
||||
self.headerView = [[TUIGroupProfileHeaderView_Minimalist alloc] initWithFrame:CGRectMake(0, 0, Screen_Width, kScale390(355))];
|
||||
|
||||
[self.contentView addSubview:self.headerView];
|
||||
}
|
||||
|
||||
- (void)fillWithData:(TUIGroupProfileCardCellData_Minimalist *)data {
|
||||
[super fillWithData:data];
|
||||
self.cardData = data;
|
||||
|
||||
[self.headerView.headImg sd_setImageWithURL:data.avatarUrl placeholderImage:data.avatarImage];
|
||||
self.headerView.descriptionLabel.text = data.name;
|
||||
self.headerView.idLabel.text = [NSString stringWithFormat:@"ID: %@", data.identifier];
|
||||
}
|
||||
- (void)layoutSubviews {
|
||||
[super layoutSubviews];
|
||||
if (self.headerView.functionListView.subviews.count > 0) {
|
||||
self.headerView.frame = CGRectMake(0, 0, self.contentView.bounds.size.width, kScale390(355));
|
||||
} else {
|
||||
self.headerView.frame = CGRectMake(0, 0, self.contentView.bounds.size.width, kScale390(257));
|
||||
}
|
||||
[self.headerView.descriptionLabel sizeToFit];
|
||||
self.headerView.itemViewList = self.itemViewList;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,23 @@
|
||||
|
||||
// Created by Tencent on 2023/06/09.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
|
||||
#import <TIMCommon/TIMCommonModel.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
@class TUIMemberDescribeCellData_Minimalist;
|
||||
@class TUIMemberCellData_Minimalist;
|
||||
|
||||
@interface TUIMemberDescribeCell_Minimalist : TUICommonTableViewCell
|
||||
|
||||
- (void)fillWithData:(TUIMemberDescribeCellData_Minimalist *)cellData;
|
||||
|
||||
@end
|
||||
|
||||
@interface TUIMemberCell_Minimalist : TUICommonTableViewCell
|
||||
|
||||
- (void)fillWithData:(TUIMemberCellData_Minimalist *)cellData;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,205 @@
|
||||
//
|
||||
// TUIMemberCell.m
|
||||
// TUIChat
|
||||
//
|
||||
// Created by xia on 2022/3/11.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
//
|
||||
|
||||
#import "TUIMemberCell_Minimalist.h"
|
||||
#import <TIMCommon/TIMCommonModel.h>
|
||||
#import <TIMCommon/TIMDefine.h>
|
||||
#import <TUICore/TUIThemeManager.h>
|
||||
#import "TUIMemberCellData.h"
|
||||
|
||||
@interface TUIMemberDescribeCell_Minimalist ()
|
||||
@property(nonatomic, strong) UIView *containerView;
|
||||
|
||||
@property(nonatomic, strong) UILabel *titleLabel;
|
||||
@property(nonatomic, strong) UIImageView *icon;
|
||||
@property(nonatomic, strong) TUIMemberDescribeCellData *cellData;
|
||||
|
||||
@end
|
||||
|
||||
@implementation TUIMemberDescribeCell_Minimalist
|
||||
- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier {
|
||||
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
|
||||
if (self) {
|
||||
[self setupViews];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)setupViews {
|
||||
self.containerView = [[UIView alloc] initWithFrame:CGRectZero];
|
||||
self.containerView.backgroundColor = [UIColor tui_colorWithHex:@"#F9F9F9"];
|
||||
self.containerView.layer.cornerRadius = kScale390(20);
|
||||
[self addSubview:self.containerView];
|
||||
|
||||
self.icon = [[UIImageView alloc] initWithFrame:CGRectZero];
|
||||
[self.containerView addSubview:self.icon];
|
||||
|
||||
self.titleLabel = [[UILabel alloc] initWithFrame:CGRectZero];
|
||||
self.titleLabel.textAlignment = NSTextAlignmentLeft;
|
||||
self.titleLabel.textColor = TIMCommonDynamicColor(@"form_title_color", @"#000000");
|
||||
[self.containerView addSubview:self.titleLabel];
|
||||
|
||||
[self setSelectionStyle:UITableViewCellSelectionStyleNone];
|
||||
}
|
||||
|
||||
- (void)layoutSubviews {
|
||||
[super layoutSubviews];
|
||||
}
|
||||
|
||||
#pragma mark - Public
|
||||
- (void)fillWithData:(TUIMemberDescribeCellData *)cellData {
|
||||
[super fillWithData:cellData];
|
||||
self.cellData = cellData;
|
||||
|
||||
self.titleLabel.text = cellData.title;
|
||||
[self.icon setImage:cellData.icon];
|
||||
|
||||
// tell constraints they need updating
|
||||
[self setNeedsUpdateConstraints];
|
||||
|
||||
// update constraints now so we can animate the change
|
||||
[self updateConstraintsIfNeeded];
|
||||
|
||||
[self layoutIfNeeded];
|
||||
}
|
||||
|
||||
+ (BOOL)requiresConstraintBasedLayout {
|
||||
return YES;
|
||||
}
|
||||
|
||||
// this is Apple's recommended place for adding/updating constraints
|
||||
- (void)updateConstraints {
|
||||
|
||||
[super updateConstraints];
|
||||
|
||||
CGFloat margin = kScale390(16);
|
||||
[self.containerView mas_remakeConstraints:^(MASConstraintMaker *make) {
|
||||
make.leading.mas_equalTo(margin);
|
||||
make.top.mas_equalTo(0);
|
||||
make.trailing.mas_equalTo(-margin);
|
||||
make.height.mas_equalTo(kScale390(57));
|
||||
}];
|
||||
[self.icon mas_remakeConstraints:^(MASConstraintMaker *make) {
|
||||
make.leading.mas_equalTo(kScale390(20));
|
||||
make.centerY.mas_equalTo(self.containerView);
|
||||
make.width.height.mas_equalTo(kScale390(16));
|
||||
}];
|
||||
|
||||
[self.titleLabel sizeToFit];
|
||||
[self.titleLabel mas_remakeConstraints:^(MASConstraintMaker *make) {
|
||||
make.centerY.mas_equalTo(self.containerView);
|
||||
make.leading.mas_equalTo(self.icon.mas_trailing).mas_offset(kScale390(11));
|
||||
make.height.mas_equalTo(self.titleLabel.frame.size.height);
|
||||
make.trailing.mas_equalTo(-kScale390(11));
|
||||
}];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@interface TUIMemberCell_Minimalist ()
|
||||
|
||||
@property(nonatomic, strong) UIImageView *avatarView;
|
||||
@property(nonatomic, strong) UILabel *titleLabel;
|
||||
@property(nonatomic, strong) UILabel *detailLabel;
|
||||
@property(nonatomic, strong) TUIMemberCellData *cellData;
|
||||
|
||||
@end
|
||||
|
||||
@implementation TUIMemberCell_Minimalist
|
||||
|
||||
#pragma mark - Life cycle
|
||||
- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier {
|
||||
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
|
||||
if (self) {
|
||||
self.contentView.backgroundColor = TIMCommonDynamicColor(@"form_bg_color", @"#FFFFFF");
|
||||
|
||||
self.avatarView = [[UIImageView alloc] initWithImage:DefaultAvatarImage];
|
||||
[self.contentView addSubview:self.avatarView];
|
||||
|
||||
self.titleLabel = [[UILabel alloc] initWithFrame:CGRectZero];
|
||||
[self.contentView addSubview:self.titleLabel];
|
||||
self.titleLabel.textColor = TIMCommonDynamicColor(@"form_title_color", @"#000000");
|
||||
self.titleLabel.font = [UIFont boldSystemFontOfSize:kScale390(14)];
|
||||
|
||||
self.detailLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, 100, 20)];
|
||||
[self.contentView addSubview:self.detailLabel];
|
||||
self.detailLabel.rtlAlignment = TUITextRTLAlignmentTrailing;
|
||||
self.detailLabel.textColor = TIMCommonDynamicColor(@"form_title_color", @"#000000");
|
||||
self.detailLabel.mm__centerY(self.avatarView.mm_centerY);
|
||||
|
||||
[self setSelectionStyle:UITableViewCellSelectionStyleNone];
|
||||
|
||||
self.changeColorWhenTouched = YES;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)layoutSubviews {
|
||||
[super layoutSubviews];
|
||||
}
|
||||
|
||||
#pragma mark - Public
|
||||
- (void)fillWithData:(TUIMemberCellData *)cellData {
|
||||
[super fillWithData:cellData];
|
||||
self.cellData = cellData;
|
||||
|
||||
self.titleLabel.text = cellData.title;
|
||||
[self.avatarView sd_setImageWithURL:cellData.avatarUrL placeholderImage:DefaultAvatarImage];
|
||||
self.detailLabel.hidden = cellData.detail.length == 0;
|
||||
self.detailLabel.text = cellData.detail;
|
||||
// tell constraints they need updating
|
||||
[self setNeedsUpdateConstraints];
|
||||
|
||||
// update constraints now so we can animate the change
|
||||
[self updateConstraintsIfNeeded];
|
||||
|
||||
[self layoutIfNeeded];
|
||||
}
|
||||
|
||||
+ (BOOL)requiresConstraintBasedLayout {
|
||||
return YES;
|
||||
}
|
||||
|
||||
// this is Apple's recommended place for adding/updating constraints
|
||||
- (void)updateConstraints {
|
||||
|
||||
[super updateConstraints];
|
||||
|
||||
CGFloat imgWidth = kScale390(32);
|
||||
|
||||
[self.avatarView mas_remakeConstraints:^(MASConstraintMaker *make) {
|
||||
make.width.height.mas_equalTo(imgWidth);
|
||||
make.centerY.mas_equalTo(self.contentView.mas_centerY);
|
||||
make.leading.mas_equalTo(kScale390(24));
|
||||
}];
|
||||
if ([TUIConfig defaultConfig].avatarType == TAvatarTypeRounded) {
|
||||
self.avatarView.layer.masksToBounds = YES;
|
||||
self.avatarView.layer.cornerRadius = imgWidth / 2;
|
||||
} else if ([TUIConfig defaultConfig].avatarType == TAvatarTypeRadiusCorner) {
|
||||
self.avatarView.layer.masksToBounds = YES;
|
||||
self.avatarView.layer.cornerRadius = [TUIConfig defaultConfig].avatarCornerRadius;
|
||||
}
|
||||
|
||||
[self.titleLabel mas_remakeConstraints:^(MASConstraintMaker *make) {
|
||||
make.centerY.mas_equalTo(self.avatarView.mas_centerY);
|
||||
make.leading.mas_equalTo(self.avatarView.mas_trailing).mas_offset(4);
|
||||
make.height.mas_equalTo(kScale390(17));
|
||||
make.trailing.lessThanOrEqualTo(self.detailLabel.mas_leading).mas_offset(-5);
|
||||
}];
|
||||
|
||||
[self.detailLabel sizeToFit];
|
||||
[self.detailLabel mas_remakeConstraints:^(MASConstraintMaker *make) {
|
||||
make.centerY.mas_equalTo(self.avatarView.mas_centerY);
|
||||
make.height.mas_equalTo(20);
|
||||
make.trailing.mas_equalTo(self.contentView.mas_trailing).mas_offset(-12);
|
||||
make.width.mas_equalTo(self.detailLabel.frame.size.width);
|
||||
}];
|
||||
|
||||
|
||||
}
|
||||
@end
|
||||
@@ -0,0 +1,65 @@
|
||||
|
||||
// Created by Tencent on 2023/06/09.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
#import <TIMCommon/TUIBubbleMessageCell_Minimalist.h>
|
||||
#import <TIMCommon/TUITextView.h>
|
||||
#import "TUIChatDefine.h"
|
||||
#import "TUITextMessageCellData.h"
|
||||
|
||||
@interface TUITextMessageCell_Minimalist : TUIBubbleMessageCell_Minimalist <UITextViewDelegate>
|
||||
|
||||
/**
|
||||
*
|
||||
* TextView for display text message content
|
||||
*/
|
||||
@property(nonatomic, strong) TUITextView *textView;
|
||||
|
||||
/**
|
||||
*
|
||||
* Selected text content
|
||||
*/
|
||||
@property(nonatomic, strong) NSString *selectContent;
|
||||
|
||||
/**
|
||||
*
|
||||
* Callback for selected all text
|
||||
*/
|
||||
@property(nonatomic, strong) TUIChatSelectAllContentCallback selectAllContentContent;
|
||||
|
||||
@property TUITextMessageCellData *textData;
|
||||
|
||||
@property(nonatomic, strong) UIImageView *voiceReadPoint;
|
||||
|
||||
- (void)fillWithData:(TUITextMessageCellData *)data;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@interface TUITextMessageCell_Minimalist (TUILayoutConfiguration)
|
||||
|
||||
/**
|
||||
* The color of label which displays the text message content.
|
||||
* Used when the message direction is send.
|
||||
*/
|
||||
@property(nonatomic, class) UIColor *outgoingTextColor;
|
||||
|
||||
/**
|
||||
* The font of label which displays the text message content.
|
||||
* Used when the message direction is send.
|
||||
*/
|
||||
@property(nonatomic, class) UIFont *outgoingTextFont;
|
||||
|
||||
/**
|
||||
* The color of label which displays the text message content.
|
||||
* Used when the message direction is received.
|
||||
*/
|
||||
@property(nonatomic, class) UIColor *incommingTextColor;
|
||||
|
||||
/**
|
||||
* The font of label which displays the text message content.
|
||||
* Used when the message direction is received.
|
||||
*/
|
||||
@property(nonatomic, class) UIFont *incommingTextFont;
|
||||
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,327 @@
|
||||
//
|
||||
// TUITextMessageCell_Minimalist.m
|
||||
// UIKit
|
||||
//
|
||||
// Created by annidyfeng on 2019/5/30.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
//
|
||||
|
||||
#import "TUITextMessageCell_Minimalist.h"
|
||||
#import <TIMCommon/TIMCommonModel.h>
|
||||
#import <TIMCommon/TIMDefine.h>
|
||||
#import <TUICore/TUICore.h>
|
||||
#import <TUICore/TUIGlobalization.h>
|
||||
|
||||
@interface TUITextMessageCell_Minimalist ()<TUITextViewDelegate>
|
||||
|
||||
@end
|
||||
@implementation TUITextMessageCell_Minimalist
|
||||
|
||||
- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier {
|
||||
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
|
||||
if (self) {
|
||||
self.textView = [[TUITextView alloc] init];
|
||||
self.textView.backgroundColor = [UIColor clearColor];
|
||||
self.textView.textContainerInset = UIEdgeInsetsMake(0, 0, 0, 0);
|
||||
self.textView.textContainer.lineFragmentPadding = 0;
|
||||
self.textView.scrollEnabled = NO;
|
||||
self.textView.editable = NO;
|
||||
self.textView.delegate = self;
|
||||
self.textView.tuiTextViewDelegate = self;
|
||||
self.bubbleView.userInteractionEnabled = YES;
|
||||
[self.bubbleView addSubview:self.textView];
|
||||
[self.container bringSubviewToFront:self.msgStatusView];
|
||||
self.bottomContainer = [[UIView alloc] init];
|
||||
[self.contentView addSubview:self.bottomContainer];
|
||||
|
||||
self.voiceReadPoint = [[UIImageView alloc] init];
|
||||
self.voiceReadPoint.backgroundColor = [UIColor redColor];
|
||||
self.voiceReadPoint.frame = CGRectMake(0, 0, 5, 5);
|
||||
self.voiceReadPoint.hidden = YES;
|
||||
[self.voiceReadPoint.layer setCornerRadius:self.voiceReadPoint.frame.size.width / 2];
|
||||
[self.voiceReadPoint.layer setMasksToBounds:YES];
|
||||
[self.bubbleView addSubview:self.voiceReadPoint];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)prepareForReuse {
|
||||
[super prepareForReuse];
|
||||
for (UIView *view in self.bottomContainer.subviews) {
|
||||
[view removeFromSuperview];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)onLongPressTextViewMessage:(UITextView *)textView {
|
||||
if (self.delegate && [self.delegate respondsToSelector:@selector(onLongPressMessage:)]) {
|
||||
[self.delegate onLongPressMessage:self];
|
||||
}
|
||||
}
|
||||
|
||||
// Override
|
||||
- (void)notifyBottomContainerReadyOfData:(TUIMessageCellData *)cellData {
|
||||
NSDictionary *param = @{TUICore_TUIChatExtension_BottomContainer_CellData : self.textData};
|
||||
[TUICore raiseExtension:TUICore_TUIChatExtension_BottomContainer_MinimalistExtensionID parentView:self.bottomContainer param:param];
|
||||
}
|
||||
|
||||
- (void)fillWithData:(TUITextMessageCellData *)data;
|
||||
{
|
||||
// set data
|
||||
[super fillWithData:data];
|
||||
self.textData = data;
|
||||
self.selectContent = data.content;
|
||||
self.voiceReadPoint.hidden = !data.showUnreadPoint;
|
||||
self.bottomContainer.hidden = CGSizeEqualToSize(data.bottomContainerSize, CGSizeZero);
|
||||
|
||||
UIColor *textColor = self.class.incommingTextColor;
|
||||
UIFont *textFont = self.class.incommingTextFont;
|
||||
if (data.direction == MsgDirectionIncoming) {
|
||||
textColor = self.class.incommingTextColor;
|
||||
textFont = self.class.incommingTextFont;
|
||||
} else {
|
||||
textColor = self.class.outgoingTextColor;
|
||||
textFont = self.class.outgoingTextFont;
|
||||
}
|
||||
self.textView.attributedText = [data getContentAttributedString:textFont];
|
||||
self.textView.textColor = textColor;
|
||||
self.textView.font = textFont;
|
||||
self.textView.textAlignment = isRTL()?NSTextAlignmentRight:NSTextAlignmentLeft;
|
||||
|
||||
// tell constraints they need updating
|
||||
[self setNeedsUpdateConstraints];
|
||||
|
||||
// update constraints now so we can animate the change
|
||||
[self updateConstraintsIfNeeded];
|
||||
|
||||
[self layoutIfNeeded];
|
||||
}
|
||||
|
||||
+ (BOOL)requiresConstraintBasedLayout {
|
||||
return YES;
|
||||
}
|
||||
|
||||
// this is Apple's recommended place for adding/updating constraints
|
||||
- (void)updateConstraints {
|
||||
|
||||
[super updateConstraints];
|
||||
// self.textView.frame = (CGRect){.origin = self.textData.textOrigin, .size = self.textData.textSize};
|
||||
[self.textView mas_remakeConstraints:^(MASConstraintMaker *make) {
|
||||
make.leading.mas_equalTo(self.bubbleView.mas_leading).mas_offset(ceil(self.textData.textOrigin.x));
|
||||
make.top.mas_equalTo(self.bubbleView.mas_top).mas_offset(ceil(self.textData.textOrigin.y));
|
||||
make.width.mas_equalTo(ceil(self.textData.textSize.width));
|
||||
make.height.mas_equalTo(ceil(self.textData.textSize.height));
|
||||
}];
|
||||
MASAttachKeys(self.textView);
|
||||
if (self.voiceReadPoint.hidden == NO) {
|
||||
[self.voiceReadPoint mas_remakeConstraints:^(MASConstraintMaker *make) {
|
||||
make.top.mas_equalTo(self.bubbleView);
|
||||
make.leading.mas_equalTo(self.bubbleView.mas_trailing).mas_offset(1);
|
||||
make.size.mas_equalTo(CGSizeMake(5, 5));
|
||||
}];
|
||||
}
|
||||
MASAttachKeys(self.voiceReadPoint);
|
||||
[self layoutBottomContainer];
|
||||
|
||||
}
|
||||
- (void)layoutSubviews {
|
||||
[super layoutSubviews];
|
||||
}
|
||||
|
||||
- (void)layoutBottomContainer {
|
||||
if (CGSizeEqualToSize(self.textData.bottomContainerSize, CGSizeZero)) {
|
||||
return;
|
||||
}
|
||||
|
||||
CGSize size = self.textData.bottomContainerSize;
|
||||
/// TransitionView should not cover the replyView.
|
||||
/// Add an extra tiny offset to the left or right of TransitionView if replyView is visible.
|
||||
CGFloat offset = self.replyLineView.hidden ? 0 : 1;
|
||||
[self.bottomContainer mas_remakeConstraints:^(MASConstraintMaker *make) {
|
||||
if (self.textData.direction == MsgDirectionIncoming) {
|
||||
make.leading.mas_equalTo(self.container.mas_leading).mas_offset(offset);
|
||||
} else {
|
||||
make.trailing.mas_equalTo(self.container.mas_trailing).mas_offset(-offset);
|
||||
}
|
||||
make.top.mas_equalTo(self.bubbleView.mas_bottom).mas_offset(self.messageData.messageContainerAppendSize.height + 6);
|
||||
make.size.mas_equalTo(size);
|
||||
}];
|
||||
|
||||
if (!self.messageModifyRepliesButton.hidden) {
|
||||
CGRect oldRect = self.messageModifyRepliesButton.frame;
|
||||
CGRect newRect = CGRectMake(oldRect.origin.x, CGRectGetMaxY(self.bottomContainer.frame) + 5, oldRect.size.width, oldRect.size.height);
|
||||
self.messageModifyRepliesButton.frame = newRect;
|
||||
}
|
||||
|
||||
for (UIView *view in self.replyAvatarImageViews) {
|
||||
CGRect oldRect = view.frame;
|
||||
CGRect newRect = CGRectMake(oldRect.origin.x, CGRectGetMaxY(self.bottomContainer.frame) + 5, oldRect.size.width, oldRect.size.height);
|
||||
view.frame = newRect;
|
||||
}
|
||||
if (!self.replyLineView.hidden) {
|
||||
CGRect oldRect = self.retryView.frame;
|
||||
CGRect newRect = CGRectMake(oldRect.origin.x, oldRect.origin.y, oldRect.size.width, oldRect.size.height + self.bottomContainer.mm_h);
|
||||
self.retryView.frame = newRect;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)textViewDidChangeSelection:(UITextView *)textView {
|
||||
NSAttributedString *selectedString = [textView.attributedText attributedSubstringFromRange:textView.selectedRange];
|
||||
if (self.selectAllContentContent && selectedString.length > 0) {
|
||||
if (selectedString.length == textView.attributedText.length) {
|
||||
self.selectAllContentContent(YES);
|
||||
} else {
|
||||
self.selectAllContentContent(NO);
|
||||
}
|
||||
}
|
||||
if (selectedString.length > 0) {
|
||||
NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc] init];
|
||||
[attributedString appendAttributedString:selectedString];
|
||||
NSUInteger offsetLocation = 0;
|
||||
for (NSDictionary *emojiLocation in self.textData.emojiLocations) {
|
||||
NSValue *key = emojiLocation.allKeys.firstObject;
|
||||
NSAttributedString *originStr = emojiLocation[key];
|
||||
NSRange currentRange = [key rangeValue];
|
||||
/**
|
||||
* After each emoji is replaced, the length of the string will change, and the actual location of the emoji will also change accordingly.
|
||||
*/
|
||||
currentRange.location += offsetLocation;
|
||||
if (currentRange.location >= textView.selectedRange.location) {
|
||||
currentRange.location -= textView.selectedRange.location;
|
||||
if (currentRange.location + currentRange.length <= attributedString.length) {
|
||||
[attributedString replaceCharactersInRange:currentRange withAttributedString:originStr];
|
||||
offsetLocation += originStr.length - currentRange.length;
|
||||
}
|
||||
}
|
||||
}
|
||||
self.selectContent = attributedString.string;
|
||||
} else {
|
||||
self.selectContent = nil;
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - TUIMessageCelllProtocol
|
||||
+ (CGFloat)getEstimatedHeight:(TUIMessageCellData *)data {
|
||||
return 44.f;
|
||||
}
|
||||
|
||||
+ (CGFloat)getHeight:(TUIMessageCellData *)data withWidth:(CGFloat)width {
|
||||
NSAssert([data isKindOfClass:TUITextMessageCellData.class], @"data must be kind of TUITextMessageCellData");
|
||||
TUITextMessageCellData *textCellData = (TUITextMessageCellData *)data;
|
||||
|
||||
CGFloat height = [super getHeight:textCellData withWidth:width];
|
||||
if (textCellData.bottomContainerSize.height > 0) {
|
||||
height += textCellData.bottomContainerSize.height + 6;
|
||||
}
|
||||
return height;
|
||||
}
|
||||
|
||||
+ (CGSize)getContentSize:(TUIMessageCellData *)data {
|
||||
NSAssert([data isKindOfClass:TUITextMessageCellData.class], @"data must be kind of TUITextMessageCellData");
|
||||
TUITextMessageCellData *textCellData = (TUITextMessageCellData *)data;
|
||||
|
||||
UIFont *textFont = textCellData.direction == MsgDirectionIncoming ? self.incommingTextFont : self.outgoingTextFont;
|
||||
NSAttributedString *attributeString = [textCellData getContentAttributedString:textFont];
|
||||
CGRect rect = [attributeString boundingRectWithSize:CGSizeMake(TTextMessageCell_Text_Width_Max, MAXFLOAT)
|
||||
options:NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading
|
||||
context:nil];
|
||||
CGSize size = rect.size;
|
||||
|
||||
CGRect rect2 = [attributeString boundingRectWithSize:CGSizeMake(MAXFLOAT, [textFont lineHeight])
|
||||
options:NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading
|
||||
context:nil];
|
||||
CGSize size2 = rect2.size;
|
||||
|
||||
// If there are multiple lines, determine whether the font width of the last line exceeds the position of the message status. If so, the message status will wrap.
|
||||
// If there is only one line, directly add the width of the message status
|
||||
int max_width = size.height > [textFont lineHeight] ? size.width : TTextMessageCell_Text_Width_Max;
|
||||
if ((int)size2.width / max_width > 1) {
|
||||
if ((int)size2.width % max_width == 0 || (int)size2.width % max_width + textCellData.msgStatusSize.width >= max_width) {
|
||||
size.height += textCellData.msgStatusSize.height;
|
||||
}
|
||||
} else {
|
||||
size.width += textCellData.msgStatusSize.width + kScale390(10);
|
||||
}
|
||||
|
||||
textCellData.textSize = size;
|
||||
CGFloat y = textCellData.cellLayout.bubbleInsets.top + [TUIBubbleMessageCell_Minimalist getBubbleTop:textCellData];
|
||||
textCellData.textOrigin = CGPointMake(textCellData.cellLayout.bubbleInsets.left, y);
|
||||
|
||||
size.height += textCellData.cellLayout.bubbleInsets.top + textCellData.cellLayout.bubbleInsets.bottom;
|
||||
size.width += textCellData.cellLayout.bubbleInsets.left + textCellData.cellLayout.bubbleInsets.right;
|
||||
|
||||
if (textCellData.direction == MsgDirectionIncoming) {
|
||||
size.height = MAX(size.height, TUIBubbleMessageCell_Minimalist.incommingBubble.size.height);
|
||||
} else {
|
||||
size.height = MAX(size.height, TUIBubbleMessageCell_Minimalist.outgoingBubble.size.height);
|
||||
}
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@implementation TUITextMessageCell_Minimalist (TUILayoutConfiguration)
|
||||
|
||||
+ (void)initialize {
|
||||
[NSNotificationCenter.defaultCenter addObserver:self selector:@selector(onThemeChanged) name:TUIDidApplyingThemeChangedNotfication object:nil];
|
||||
}
|
||||
|
||||
static UIColor *gOutgoingTextColor;
|
||||
|
||||
+ (UIColor *)outgoingTextColor {
|
||||
if (!gOutgoingTextColor) {
|
||||
gOutgoingTextColor = TUIChatDynamicColor(@"chat_text_message_send_text_color", @"#000000");
|
||||
}
|
||||
return gOutgoingTextColor;
|
||||
}
|
||||
|
||||
+ (void)setOutgoingTextColor:(UIColor *)outgoingTextColor {
|
||||
gOutgoingTextColor = outgoingTextColor;
|
||||
}
|
||||
|
||||
static UIFont *gOutgoingTextFont;
|
||||
|
||||
+ (UIFont *)outgoingTextFont {
|
||||
if (!gOutgoingTextFont) {
|
||||
gOutgoingTextFont = [UIFont systemFontOfSize:16];
|
||||
}
|
||||
return gOutgoingTextFont;
|
||||
}
|
||||
|
||||
+ (void)setOutgoingTextFont:(UIFont *)outgoingTextFont {
|
||||
gOutgoingTextFont = outgoingTextFont;
|
||||
}
|
||||
|
||||
static UIColor *gIncommingTextColor;
|
||||
|
||||
+ (UIColor *)incommingTextColor {
|
||||
if (!gIncommingTextColor) {
|
||||
gIncommingTextColor = TUIChatDynamicColor(@"chat_text_message_receive_text_color", @"#000000");
|
||||
}
|
||||
return gIncommingTextColor;
|
||||
}
|
||||
|
||||
+ (void)setIncommingTextColor:(UIColor *)incommingTextColor {
|
||||
gIncommingTextColor = incommingTextColor;
|
||||
}
|
||||
|
||||
static UIFont *gIncommingTextFont;
|
||||
|
||||
+ (UIFont *)incommingTextFont {
|
||||
if (!gIncommingTextFont) {
|
||||
gIncommingTextFont = [UIFont systemFontOfSize:16];
|
||||
}
|
||||
return gIncommingTextFont;
|
||||
}
|
||||
|
||||
+ (void)setIncommingTextFont:(UIFont *)incommingTextFont {
|
||||
gIncommingTextFont = incommingTextFont;
|
||||
}
|
||||
|
||||
+ (void)onThemeChanged {
|
||||
gOutgoingTextColor = nil;
|
||||
gIncommingTextColor = nil;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,17 @@
|
||||
|
||||
// Created by Tencent on 2023/06/09.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
|
||||
#import <TIMCommon/TUIBubbleMessageCell_Minimalist.h>
|
||||
#import "TUIFaceMessageCellData.h"
|
||||
|
||||
@interface TUIFaceMessageCell_Minimalist : TUIBubbleMessageCell_Minimalist
|
||||
/**
|
||||
* Image view for the resource of emticon
|
||||
*/
|
||||
@property(nonatomic, strong) UIImageView *face;
|
||||
|
||||
@property TUIFaceMessageCellData *faceData;
|
||||
|
||||
- (void)fillWithData:(TUIFaceMessageCellData *)data;
|
||||
@end
|
||||
@@ -0,0 +1,97 @@
|
||||
//
|
||||
// FaceMessageCell.m
|
||||
// UIKit
|
||||
//
|
||||
// Created by annidyfeng on 2019/5/30.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
//
|
||||
|
||||
#import "TUIFaceMessageCell_Minimalist.h"
|
||||
#import <TIMCommon/TIMDefine.h>
|
||||
|
||||
@interface TUIFaceMessageCell_Minimalist ()
|
||||
@end
|
||||
|
||||
@implementation TUIFaceMessageCell_Minimalist
|
||||
|
||||
- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier {
|
||||
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
|
||||
if (self) {
|
||||
_face = [[UIImageView alloc] init];
|
||||
_face.contentMode = UIViewContentModeScaleAspectFit;
|
||||
[self.bubbleView addSubview:_face];
|
||||
_face.mm_fill();
|
||||
_face.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
|
||||
_face.backgroundColor = RGBA(236, 240, 246, 1);
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
+ (BOOL)requiresConstraintBasedLayout {
|
||||
return YES;
|
||||
}
|
||||
|
||||
// this is Apple's recommended place for adding/updating constraints
|
||||
- (void)updateConstraints {
|
||||
|
||||
[super updateConstraints];
|
||||
|
||||
CGFloat topMargin = 5;
|
||||
CGFloat height = self.container.mm_h;
|
||||
[self.face mas_remakeConstraints:^(MASConstraintMaker *make) {
|
||||
make.height.mas_equalTo(kScale390(88));
|
||||
make.centerX.mas_equalTo(self.container.mas_centerX);
|
||||
make.top.mas_equalTo(topMargin);
|
||||
make.width.mas_equalTo(kScale390(90));
|
||||
}];
|
||||
|
||||
|
||||
}
|
||||
- (void)layoutSubviews {
|
||||
[super layoutSubviews];
|
||||
}
|
||||
|
||||
- (void)fillWithData:(TUIFaceMessageCellData *)data {
|
||||
// set data
|
||||
[super fillWithData:data];
|
||||
self.faceData = data;
|
||||
UIImage *image = [[TUIImageCache sharedInstance] getFaceFromCache:data.path];
|
||||
if (!image) {
|
||||
image = [UIImage imageWithContentsOfFile:TUIChatFaceImagePath(@"ic_unknown_image")];
|
||||
}
|
||||
_face.image = image;
|
||||
|
||||
// tell constraints they need updating
|
||||
[self setNeedsUpdateConstraints];
|
||||
|
||||
// update constraints now so we can animate the change
|
||||
[self updateConstraintsIfNeeded];
|
||||
|
||||
[self layoutIfNeeded];
|
||||
}
|
||||
|
||||
#pragma mark - TUIMessageCellProtocol
|
||||
+ (CGSize)getContentSize:(TUIMessageCellData *)data {
|
||||
NSAssert([data isKindOfClass:TUIFaceMessageCellData.class], @"data must be kind of TUIFaceMessageCellData");
|
||||
TUIFaceMessageCellData *faceCellData = (TUIFaceMessageCellData *)data;
|
||||
|
||||
UIImage *image = [[TUIImageCache sharedInstance] getFaceFromCache:faceCellData.path];
|
||||
if (!image) {
|
||||
image = [UIImage imageWithContentsOfFile:TUIChatFaceImagePath(@"ic_unknown_image")];
|
||||
}
|
||||
CGFloat imageHeight = image.size.height;
|
||||
CGFloat imageWidth = image.size.width;
|
||||
if (imageHeight > TFaceMessageCell_Image_Height_Max) {
|
||||
imageHeight = TFaceMessageCell_Image_Height_Max;
|
||||
imageWidth = image.size.width / image.size.height * imageHeight;
|
||||
}
|
||||
if (imageWidth > TFaceMessageCell_Image_Width_Max) {
|
||||
imageWidth = TFaceMessageCell_Image_Width_Max;
|
||||
imageHeight = image.size.height / image.size.width * imageWidth;
|
||||
}
|
||||
imageWidth += kScale390(30);
|
||||
imageHeight += kScale390(30);
|
||||
return CGSizeMake(imageWidth, imageHeight);
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,39 @@
|
||||
|
||||
// Created by Tencent on 2023/06/09.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
#import <TIMCommon/TUIBubbleMessageCell_Minimalist.h>
|
||||
#import "TUIFileMessageCellData.h"
|
||||
|
||||
@interface TUIFileMessageCell_Minimalist : TUIBubbleMessageCell_Minimalist
|
||||
|
||||
/**
|
||||
* File bubble view, used to wrap messages on the UI
|
||||
*/
|
||||
@property(nonatomic, strong) UIImageView *bubble;
|
||||
|
||||
/**
|
||||
* Label for displaying filename
|
||||
* As the main label of the file message, it displays the file information (including the suffix).
|
||||
*/
|
||||
@property(nonatomic, strong) UILabel *fileName;
|
||||
|
||||
/**
|
||||
* Label for displaying file size
|
||||
* As the secondary label of the file message, it further displays the secondary information of the file.
|
||||
*/
|
||||
@property(nonatomic, strong) UILabel *length;
|
||||
|
||||
/**
|
||||
* File icon
|
||||
*/
|
||||
@property(nonatomic, strong) UIImageView *fileImage;
|
||||
|
||||
/**
|
||||
* Download icon
|
||||
*/
|
||||
@property(nonatomic, strong) UIImageView *downloadImage;
|
||||
|
||||
@property TUIFileMessageCellData *fileData;
|
||||
|
||||
- (void)fillWithData:(TUIFileMessageCellData *)data;
|
||||
@end
|
||||
@@ -0,0 +1,332 @@
|
||||
//
|
||||
// TUIFileMessageCell_Minimalist.m
|
||||
// UIKit
|
||||
//
|
||||
// Created by annidyfeng on 2019/5/30.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
//
|
||||
|
||||
#import "TUIFileMessageCell_Minimalist.h"
|
||||
#import <TIMCommon/TIMDefine.h>
|
||||
#import <TUICore/TUIThemeManager.h>
|
||||
#import "ReactiveObjC/ReactiveObjC.h"
|
||||
#import "TUIMessageProgressManager.h"
|
||||
|
||||
@interface TUIFileMessageCell_Minimalist () <V2TIMSDKListener, TUIMessageProgressManagerDelegate>
|
||||
@property(nonatomic, strong) UIView *fileContainer;
|
||||
@property(nonatomic, strong) UIView *animateHighlightView;
|
||||
@property(nonatomic, strong) UIView *progressView;
|
||||
@end
|
||||
|
||||
@implementation TUIFileMessageCell_Minimalist
|
||||
|
||||
- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier {
|
||||
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
|
||||
if (self) {
|
||||
[self.bubbleView addSubview:self.fileContainer];
|
||||
[self.fileContainer addSubview:self.progressView];
|
||||
|
||||
_fileImage = [[UIImageView alloc] init];
|
||||
_fileImage.image = [[TUIImageCache sharedInstance] getResourceFromCache:TUIChatImagePath(@"msg_file")];
|
||||
_fileImage.contentMode = UIViewContentModeScaleAspectFit;
|
||||
[self.fileContainer addSubview:_fileImage];
|
||||
|
||||
_fileName = [[UILabel alloc] init];
|
||||
_fileName.font = [UIFont systemFontOfSize:14];
|
||||
_fileName.textColor = [UIColor blackColor];
|
||||
[self.fileContainer addSubview:_fileName];
|
||||
|
||||
_length = [[UILabel alloc] init];
|
||||
_length.font = [UIFont systemFontOfSize:12];
|
||||
_length.textColor = RGBA(122, 122, 122, 1);
|
||||
[self.bubbleView addSubview:_length];
|
||||
|
||||
_downloadImage = [[UIImageView alloc] init];
|
||||
_downloadImage.image = [[TUIImageCache sharedInstance] getResourceFromCache:TUIChatImagePath_Minimalist(@"file_download")];
|
||||
_downloadImage.contentMode = UIViewContentModeScaleAspectFit;
|
||||
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(downloadClick)];
|
||||
[_downloadImage addGestureRecognizer:tap];
|
||||
[_downloadImage setUserInteractionEnabled:YES];
|
||||
[self.contentView addSubview:_downloadImage];
|
||||
|
||||
[V2TIMManager.sharedInstance addIMSDKListener:self];
|
||||
[TUIMessageProgressManager.shareManager addDelegate:self];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)downloadClick {
|
||||
_downloadImage.frame = CGRectZero;
|
||||
if (self.delegate && [self.delegate respondsToSelector:@selector(onSelectMessage:)]) {
|
||||
[self.delegate onSelectMessage:self];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)fillWithData:(TUIFileMessageCellData *)data {
|
||||
// set data
|
||||
[super fillWithData:data];
|
||||
self.fileData = data;
|
||||
_fileName.text = data.fileName;
|
||||
_length.text = [self formatLength:data.length];
|
||||
|
||||
@weakify(self);
|
||||
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
@strongify(self);
|
||||
NSInteger uploadProgress = [TUIMessageProgressManager.shareManager uploadProgressForMessage:self.fileData.msgID];
|
||||
NSInteger downloadProgress = [TUIMessageProgressManager.shareManager downloadProgressForMessage:self.fileData.msgID];
|
||||
[self onUploadProgress:self.fileData.msgID progress:uploadProgress];
|
||||
[self onDownloadProgress:self.fileData.msgID progress:downloadProgress];
|
||||
|
||||
});
|
||||
// tell constraints they need updating
|
||||
[self setNeedsUpdateConstraints];
|
||||
|
||||
// update constraints now so we can animate the change
|
||||
[self updateConstraintsIfNeeded];
|
||||
|
||||
[self layoutIfNeeded];
|
||||
|
||||
}
|
||||
|
||||
+ (BOOL)requiresConstraintBasedLayout {
|
||||
return YES;
|
||||
}
|
||||
|
||||
// this is Apple's recommended place for adding/updating constraints
|
||||
- (void)updateConstraints {
|
||||
|
||||
[super updateConstraints];
|
||||
|
||||
CGSize containerSize = [self.class getContentSize:self.fileData];
|
||||
[self.fileContainer mas_remakeConstraints:^(MASConstraintMaker *make) {
|
||||
make.width.mas_equalTo(containerSize.width - kScale390(32));
|
||||
make.height.mas_equalTo(48);
|
||||
make.leading.mas_equalTo(kScale390(16));
|
||||
make.top.mas_equalTo(self.bubbleView).mas_offset(8);
|
||||
}];
|
||||
|
||||
|
||||
CGFloat fileImageSize = 24;
|
||||
|
||||
[self.fileImage mas_remakeConstraints:^(MASConstraintMaker *make) {
|
||||
make.leading.mas_equalTo(self.container.mas_leading).mas_offset(kScale390(17));
|
||||
make.top.mas_equalTo(self.container.mas_top).mas_offset(kScale390(12));
|
||||
make.size.mas_equalTo(fileImageSize);
|
||||
}];
|
||||
|
||||
[self.fileName mas_remakeConstraints:^(MASConstraintMaker *make) {
|
||||
make.leading.mas_equalTo(self.fileImage.mas_trailing).mas_offset(kScale390(8));
|
||||
make.top.mas_equalTo(15);
|
||||
make.trailing.mas_equalTo(self.container.mas_trailing).mas_offset(- kScale390(12));
|
||||
make.height.mas_equalTo(17);
|
||||
}];
|
||||
|
||||
[self.length mas_remakeConstraints:^(MASConstraintMaker *make) {
|
||||
make.leading.mas_equalTo(self.container).mas_offset(kScale390(22));
|
||||
make.bottom.mas_equalTo(self.container.mas_bottom).mas_offset(- 11);
|
||||
make.size.mas_equalTo(CGSizeMake(150, 14));
|
||||
}];
|
||||
|
||||
if (!self.fileData.isLocalExist && !self.fileData.isDownloading) {
|
||||
CGFloat downloadSize = 16;
|
||||
[self.downloadImage mas_remakeConstraints:^(MASConstraintMaker *make) {
|
||||
if (self.fileData.direction == MsgDirectionIncoming) {
|
||||
make.leading.mas_equalTo(self.bubbleView.mas_trailing).mas_offset(kScale390(8));
|
||||
} else {
|
||||
make.trailing.mas_equalTo(self.bubbleView.mas_leading).mas_offset(- kScale390(8));
|
||||
}
|
||||
make.centerY.mas_equalTo(self.length.mas_centerY);
|
||||
make.height.width.mas_equalTo(downloadSize);
|
||||
}];
|
||||
} else {
|
||||
_downloadImage.frame = CGRectZero;
|
||||
}
|
||||
[self.progressView mas_remakeConstraints:^(MASConstraintMaker *make) {
|
||||
make.leading.mas_equalTo(0);
|
||||
make.top.mas_equalTo(0);
|
||||
make.width.mas_equalTo(self.progressView.mm_w ?: 1);
|
||||
make.height.mas_equalTo(self.fileContainer.mm_h);
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)layoutSubviews {
|
||||
[super layoutSubviews];
|
||||
}
|
||||
|
||||
#pragma mark - TUIMessageProgressManagerDelegate
|
||||
- (void)onUploadProgress:(NSString *)msgID progress:(NSInteger)progress {
|
||||
if (![msgID isEqualToString:self.fileData.msgID]) {
|
||||
return;
|
||||
}
|
||||
|
||||
self.fileData.uploadProgress = progress;
|
||||
[self updateUploadProgress:(int)progress];
|
||||
}
|
||||
|
||||
- (void)onDownloadProgress:(NSString *)msgID progress:(NSInteger)progress {
|
||||
if (![msgID isEqualToString:self.fileData.msgID]) {
|
||||
return;
|
||||
}
|
||||
self.fileData.downladProgress = progress;
|
||||
[self updateDownloadProgress:(int)progress];
|
||||
}
|
||||
|
||||
- (void)updateUploadProgress:(int)progress {
|
||||
[self.indicator startAnimating];
|
||||
self.progressView.hidden = YES;
|
||||
self.length.text = [self formatLength:self.fileData.length];
|
||||
NSLog(@"updateProgress:%ld,isLocalExist:%@,isDownloading:%@", (long)progress, self.fileData.isLocalExist ? @"YES" : @"NO",
|
||||
self.fileData.isDownloading ? @"YES" : @"NO");
|
||||
if (progress >= 100 || progress == 0) {
|
||||
[self.indicator stopAnimating];
|
||||
return;
|
||||
}
|
||||
[self showProgressLoadingAnimation:progress];
|
||||
}
|
||||
- (void)updateDownloadProgress:(int)progress {
|
||||
[self.indicator startAnimating];
|
||||
self.progressView.hidden = YES;
|
||||
self.length.text = [self formatLength:self.fileData.length];
|
||||
if (!self.fileData.isLocalExist && !self.fileData.isDownloading) {
|
||||
_downloadImage.hidden = NO;
|
||||
} else {
|
||||
_downloadImage.hidden = YES;
|
||||
}
|
||||
|
||||
if (progress >= 100 || progress == 0) {
|
||||
[self.indicator stopAnimating];
|
||||
return;
|
||||
}
|
||||
|
||||
[self showProgressLoadingAnimation:progress];
|
||||
}
|
||||
|
||||
- (void)showProgressLoadingAnimation:(NSInteger)progress {
|
||||
self.progressView.hidden = NO;
|
||||
NSLog(@"showProgressLodingAnimation:%ld", (long)progress);
|
||||
[UIView animateWithDuration:0.25
|
||||
animations:^{
|
||||
[self.progressView mas_updateConstraints:^(MASConstraintMaker *make) {
|
||||
make.width.mas_equalTo(self.fileContainer.mm_w * progress / 100.0);
|
||||
}];
|
||||
}
|
||||
completion:^(BOOL finished) {
|
||||
if (progress == 0 || progress >= 100) {
|
||||
self.progressView.hidden = YES;
|
||||
[self.indicator stopAnimating];
|
||||
self.length.text = [self formatLength:self.fileData.length];
|
||||
self.downloadImage.hidden = YES;
|
||||
}
|
||||
}];
|
||||
|
||||
self.length.text = [self formatLength:self.fileData.length];
|
||||
}
|
||||
|
||||
- (NSString *)formatLength:(long)length {
|
||||
/**
|
||||
*
|
||||
* Display file size by default
|
||||
*/
|
||||
double len = length;
|
||||
NSArray *array = [NSArray arrayWithObjects:@"Bytes", @"K", @"M", @"G", @"T", nil];
|
||||
int factor = 0;
|
||||
while (len > 1024) {
|
||||
len /= 1024;
|
||||
factor++;
|
||||
if (factor >= 4) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
NSString *str = [NSString stringWithFormat:@"%4.2f%@", len, array[factor]];
|
||||
|
||||
/**
|
||||
*
|
||||
* Formatted display characters
|
||||
*/
|
||||
if (self.fileData.isDownloading || (length == 0 && (self.fileData.status == Msg_Status_Sending || self.fileData.status == Msg_Status_Sending_2))) {
|
||||
str = [NSString
|
||||
stringWithFormat:@"%zd%%", self.fileData.direction == MsgDirectionIncoming ? self.fileData.downladProgress : self.fileData.uploadProgress];
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
|
||||
- (UIView *)progressView {
|
||||
if (_progressView == nil) {
|
||||
_progressView = [[UIView alloc] init];
|
||||
_progressView.backgroundColor = [UIColor colorWithRed:208 / 255.0 green:228 / 255.0 blue:255 / 255.0 alpha:1 / 1.0];
|
||||
}
|
||||
return _progressView;
|
||||
}
|
||||
|
||||
- (UIView *)fileContainer {
|
||||
if (_fileContainer == nil) {
|
||||
_fileContainer = [[UIView alloc] init];
|
||||
_fileContainer.backgroundColor = [UIColor whiteColor];
|
||||
_fileContainer.layer.cornerRadius = 16;
|
||||
_fileContainer.layer.masksToBounds = YES;
|
||||
}
|
||||
return _fileContainer;
|
||||
}
|
||||
|
||||
- (void)onConnectSuccess {
|
||||
[self fillWithData:self.fileData];
|
||||
}
|
||||
|
||||
- (void)highlightWhenMatchKeyword:(NSString *)keyword {
|
||||
if (keyword) {
|
||||
if (self.highlightAnimating) {
|
||||
return;
|
||||
}
|
||||
[self animate:3];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)animate:(int)times {
|
||||
times--;
|
||||
if (times < 0) {
|
||||
[self.animateHighlightView removeFromSuperview];
|
||||
self.highlightAnimating = NO;
|
||||
return;
|
||||
}
|
||||
self.highlightAnimating = YES;
|
||||
self.animateHighlightView.frame = self.bubbleView.bounds;
|
||||
self.animateHighlightView.alpha = 0.1;
|
||||
[self.container addSubview:self.animateHighlightView];
|
||||
[UIView animateWithDuration:0.25
|
||||
animations:^{
|
||||
self.animateHighlightView.alpha = 0.5;
|
||||
}
|
||||
completion:^(BOOL finished) {
|
||||
[UIView animateWithDuration:0.25
|
||||
animations:^{
|
||||
self.animateHighlightView.alpha = 0.1;
|
||||
}
|
||||
completion:^(BOOL finished) {
|
||||
if (!self.messageData.highlightKeyword) {
|
||||
[self animate:0];
|
||||
return;
|
||||
}
|
||||
[self animate:times];
|
||||
}];
|
||||
}];
|
||||
}
|
||||
|
||||
- (UIView *)animateHighlightView {
|
||||
if (_animateHighlightView == nil) {
|
||||
_animateHighlightView = [[UIView alloc] init];
|
||||
_animateHighlightView.backgroundColor = [UIColor orangeColor];
|
||||
_animateHighlightView.layer.cornerRadius = 12;
|
||||
_animateHighlightView.layer.masksToBounds = YES;
|
||||
}
|
||||
return _animateHighlightView;
|
||||
}
|
||||
|
||||
#pragma mark - TUIMessageCellProtocol
|
||||
+ (CGSize)getContentSize:(TUIMessageCellData *)data {
|
||||
CGSize size = CGSizeMake(kScale390(250), 90);
|
||||
return size;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,17 @@
|
||||
|
||||
// Created by Tencent on 2023/06/09.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
#import <UIKit/UIKit.h>
|
||||
#import "TUIImageMessageCellData.h"
|
||||
#import "TUIMediaCollectionCell_Minimalist.h"
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// TUIMediaImageCell_Minimalist
|
||||
//
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@interface TUIImageCollectionCell_Minimalist : TUIMediaCollectionCell_Minimalist
|
||||
|
||||
- (void)fillWithData:(TUIImageMessageCellData *)data;
|
||||
@end
|
||||
@@ -0,0 +1,157 @@
|
||||
|
||||
// Created by Tencent on 2023/06/09.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
#import "TUIImageCollectionCell_Minimalist.h"
|
||||
#import <TIMCommon/TIMDefine.h>
|
||||
#import <TUICore/TUITool.h>
|
||||
|
||||
@implementation TUIImageCollectionCell_Minimalist
|
||||
- (id)initWithFrame:(CGRect)frame {
|
||||
self = [super initWithFrame:frame];
|
||||
if (self) {
|
||||
[self setupViews];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)setupViews {
|
||||
self.imageView = [[UIImageView alloc] init];
|
||||
self.imageView.layer.cornerRadius = 5.0;
|
||||
[self.imageView.layer setMasksToBounds:YES];
|
||||
self.imageView.contentMode = UIViewContentModeScaleAspectFit;
|
||||
self.imageView.backgroundColor = [UIColor clearColor];
|
||||
[self addSubview:self.imageView];
|
||||
self.imageView.mm_fill();
|
||||
self.imageView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
|
||||
|
||||
self.downloadBtn = [UIButton buttonWithType:UIButtonTypeCustom];
|
||||
self.downloadBtn.contentMode = UIViewContentModeScaleToFill;
|
||||
[self.downloadBtn setImage:TUIChatCommonBundleImage(@"download") forState:UIControlStateNormal];
|
||||
[self.downloadBtn addTarget:self action:@selector(onDownloadBtnClick) forControlEvents:UIControlEventTouchUpInside];
|
||||
[self addSubview:self.downloadBtn];
|
||||
|
||||
self.backgroundColor = [UIColor clearColor];
|
||||
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(onSelectMedia)];
|
||||
[self addGestureRecognizer:tap];
|
||||
}
|
||||
|
||||
- (void)onDownloadBtnClick {
|
||||
UIImage *image = self.imageView.image;
|
||||
[[PHPhotoLibrary sharedPhotoLibrary]
|
||||
performChanges:^{
|
||||
[PHAssetChangeRequest creationRequestForAssetFromImage:image];
|
||||
}
|
||||
completionHandler:^(BOOL success, NSError *_Nullable error) {
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
if (success) {
|
||||
[TUITool makeToast:TIMCommonLocalizableString(TUIKitPictureSavedSuccess)];
|
||||
} else {
|
||||
[TUITool makeToast:TIMCommonLocalizableString(TUIKitPictureSavedFailed)];
|
||||
}
|
||||
});
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)onSelectMedia {
|
||||
if (self.delegate && [self.delegate respondsToSelector:@selector(onCloseMedia:)]) {
|
||||
[self.delegate onCloseMedia:self];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)fillWithData:(TUIImageMessageCellData *)data;
|
||||
{
|
||||
[super fillWithData:data];
|
||||
self.imageView.image = nil;
|
||||
|
||||
//1.Read from cache
|
||||
if ([self originImageFirst:data]) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ([self largeImageSecond:data]) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (data.thumbImage == nil) {
|
||||
[data downloadImage:TImage_Type_Thumb];
|
||||
}
|
||||
if (data.thumbImage && data.largeImage == nil) {
|
||||
[data downloadImage:TImage_Type_Large];
|
||||
}
|
||||
|
||||
@weakify(self);
|
||||
[[RACObserve(data, thumbImage) takeUntil:self.rac_prepareForReuseSignal] subscribeNext:^(UIImage *thumbImage) {
|
||||
@strongify(self);
|
||||
if (thumbImage) {
|
||||
self.imageView.image = thumbImage;
|
||||
}
|
||||
}];
|
||||
|
||||
// largeImage
|
||||
[[RACObserve(data, largeImage) takeUntil:self.rac_prepareForReuseSignal] subscribeNext:^(UIImage *largeImage) {
|
||||
@strongify(self);
|
||||
if (largeImage) {
|
||||
self.imageView.image = largeImage;
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)layoutSubviews {
|
||||
[super layoutSubviews];
|
||||
self.downloadBtn.mm_width(31).mm_height(31).mm_right(16).mm_bottom(48);
|
||||
}
|
||||
|
||||
- (BOOL)largeImageSecond:(TUIImageMessageCellData *)data {
|
||||
@weakify(self);
|
||||
BOOL isExist = NO;
|
||||
NSString *path = [data getImagePath:TImage_Type_Large isExist:&isExist];
|
||||
if (isExist) {
|
||||
[data decodeImage:TImage_Type_Large];
|
||||
[self fillLargeImageWithData:data];
|
||||
}
|
||||
return isExist;
|
||||
}
|
||||
|
||||
|
||||
- (BOOL)originImageFirst:(TUIImageMessageCellData *)data {
|
||||
BOOL isExist = NO;
|
||||
NSString *path = [data getImagePath:TImage_Type_Origin isExist:&isExist];
|
||||
if (isExist) {
|
||||
[data decodeImage:TImage_Type_Origin];
|
||||
[self fillOriginImageImageWithData:data];
|
||||
}
|
||||
return isExist;
|
||||
}
|
||||
- (void)fillOriginImageImageWithData:(TUIImageMessageCellData *)data {
|
||||
@weakify(self);
|
||||
// originImage
|
||||
[[RACObserve(data, originImage) takeUntil:self.rac_prepareForReuseSignal] subscribeNext:^(UIImage *originImage) {
|
||||
@strongify(self);
|
||||
if (originImage) {
|
||||
self.imageView.image = originImage;
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
|
||||
- (void)fillLargeImageWithData:(TUIImageMessageCellData *)data {
|
||||
@weakify(self);
|
||||
// largeImage
|
||||
[[RACObserve(data, largeImage) takeUntil:self.rac_prepareForReuseSignal] subscribeNext:^(UIImage *largeImage) {
|
||||
@strongify(self);
|
||||
if (largeImage) {
|
||||
self.imageView.image = largeImage;
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)fillThumbImageWithData:(TUIImageMessageCellData *)data {
|
||||
@weakify(self);
|
||||
[[RACObserve(data, thumbImage) takeUntil:self.rac_prepareForReuseSignal] subscribeNext:^(UIImage *thumbImage) {
|
||||
@strongify(self);
|
||||
if (thumbImage) {
|
||||
self.imageView.image = thumbImage;
|
||||
}
|
||||
}];
|
||||
}
|
||||
@end
|
||||
@@ -0,0 +1,16 @@
|
||||
|
||||
// Created by Tencent on 2023/06/09.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
#import <TIMCommon/TUIMessageCell_Minimalist.h>
|
||||
#import "TUIImageMessageCellData.h"
|
||||
|
||||
@interface TUIImageMessageCell_Minimalist : TUIMessageCell_Minimalist
|
||||
|
||||
@property(nonatomic, strong) UIImageView *thumb;
|
||||
|
||||
@property(nonatomic, strong) UILabel *progress;
|
||||
|
||||
@property TUIImageMessageCellData *imageData;
|
||||
|
||||
- (void)fillWithData:(TUIImageMessageCellData *)data;
|
||||
@end
|
||||
@@ -0,0 +1,273 @@
|
||||
//
|
||||
// TUIImageMessageCell_Minimalist.m
|
||||
// UIKit
|
||||
//
|
||||
// Created by annidyfeng on 2019/5/30.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
//
|
||||
|
||||
#import "TUIImageMessageCell_Minimalist.h"
|
||||
#import <TIMCommon/TIMDefine.h>
|
||||
|
||||
@interface TUIImageMessageCell_Minimalist ()
|
||||
|
||||
@property(nonatomic, strong) UIView *animateHighlightView;
|
||||
|
||||
@end
|
||||
|
||||
@implementation TUIImageMessageCell_Minimalist
|
||||
|
||||
- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier {
|
||||
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
|
||||
if (self) {
|
||||
_thumb = [[UIImageView alloc] init];
|
||||
_thumb.layer.cornerRadius = 5.0;
|
||||
[_thumb.layer setMasksToBounds:YES];
|
||||
_thumb.contentMode = UIViewContentModeScaleAspectFit;
|
||||
_thumb.backgroundColor = [UIColor clearColor];
|
||||
[self.container addSubview:_thumb];
|
||||
|
||||
_progress = [[UILabel alloc] init];
|
||||
_progress.textColor = [UIColor whiteColor];
|
||||
_progress.font = [UIFont systemFontOfSize:15];
|
||||
_progress.textAlignment = NSTextAlignmentCenter;
|
||||
_progress.layer.cornerRadius = 5.0;
|
||||
_progress.hidden = YES;
|
||||
_progress.backgroundColor = TImageMessageCell_Progress_Color;
|
||||
[_progress.layer setMasksToBounds:YES];
|
||||
[self.container addSubview:_progress];
|
||||
|
||||
self.msgTimeLabel.textColor = RGB(255, 255, 255);
|
||||
[self makeConstraints];
|
||||
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)fillWithData:(TUIImageMessageCellData *)data;
|
||||
{
|
||||
// set data
|
||||
[super fillWithData:data];
|
||||
self.imageData = data;
|
||||
_thumb.image = nil;
|
||||
BOOL hasRiskContent = self.messageData.innerMessage.hasRiskContent;
|
||||
if (hasRiskContent) {
|
||||
self.thumb.image = TIMCommonBundleThemeImage(@"", @"icon_security_strike");
|
||||
self.progress.hidden = YES;
|
||||
return;
|
||||
}
|
||||
if (data.thumbImage == nil) {
|
||||
[data downloadImage:TImage_Type_Thumb];
|
||||
}
|
||||
|
||||
@weakify(self);
|
||||
[[RACObserve(data, thumbImage) takeUntil:self.rac_prepareForReuseSignal] subscribeNext:^(UIImage *thumbImage) {
|
||||
@strongify(self);
|
||||
if (thumbImage) {
|
||||
self.thumb.image = thumbImage;
|
||||
// tell constraints they need updating
|
||||
[self setNeedsUpdateConstraints];
|
||||
|
||||
// update constraints now so we can animate the change
|
||||
[self updateConstraintsIfNeeded];
|
||||
|
||||
[self layoutIfNeeded];
|
||||
}
|
||||
}];
|
||||
|
||||
if (data.direction == MsgDirectionIncoming) {
|
||||
[[[RACObserve(data, thumbProgress) takeUntil:self.rac_prepareForReuseSignal] distinctUntilChanged] subscribeNext:^(NSNumber *x) {
|
||||
@strongify(self);
|
||||
int progress = [x intValue];
|
||||
self.progress.text = [NSString stringWithFormat:@"%d%%", progress];
|
||||
self.progress.hidden = (progress >= 100 || progress == 0);
|
||||
|
||||
// tell constraints they need updating
|
||||
[self setNeedsUpdateConstraints];
|
||||
|
||||
// update constraints now so we can animate the change
|
||||
[self updateConstraintsIfNeeded];
|
||||
|
||||
[self layoutIfNeeded];
|
||||
}];
|
||||
}
|
||||
}
|
||||
- (void)makeConstraints {
|
||||
[self.thumb mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.height.mas_equalTo(self.container);
|
||||
make.width.mas_equalTo(self.container);
|
||||
make.top.mas_equalTo(self.container);
|
||||
make.leading.mas_equalTo(self.container);
|
||||
}];
|
||||
|
||||
[self.progress mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.edges.mas_equalTo(self.container);
|
||||
}];
|
||||
}
|
||||
|
||||
// this is Apple's recommended place for adding/updating constraints
|
||||
- (void)updateConstraints {
|
||||
|
||||
[super updateConstraints];
|
||||
|
||||
CGFloat topMargin = 0;
|
||||
CGFloat height = self.container.mm_h;
|
||||
BOOL hasRiskContent = self.messageData.innerMessage.hasRiskContent;
|
||||
if (hasRiskContent ) {
|
||||
[self.thumb mas_remakeConstraints:^(MASConstraintMaker *make) {
|
||||
make.top.mas_equalTo(self.container).mas_offset(12);
|
||||
make.size.mas_equalTo(CGSizeMake(150, 150));
|
||||
make.centerX.mas_equalTo(self.container);
|
||||
}];
|
||||
}
|
||||
else {
|
||||
[self.thumb mas_remakeConstraints:^(MASConstraintMaker *make) {
|
||||
make.height.mas_equalTo(height);
|
||||
make.width.mas_equalTo(self.container.mas_width);
|
||||
make.top.mas_equalTo(self.container).mas_offset(topMargin);
|
||||
make.leading.mas_equalTo(self.container);
|
||||
}];
|
||||
}
|
||||
|
||||
[self.progress mas_remakeConstraints:^(MASConstraintMaker *make) {
|
||||
make.edges.mas_equalTo(self.container);
|
||||
}];
|
||||
|
||||
[self.msgTimeLabel mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.width.mas_equalTo(38);
|
||||
make.height.mas_equalTo(self.messageData.msgStatusSize.height);
|
||||
make.bottom.mas_equalTo(self.container).mas_offset(-kScale390(9));
|
||||
make.trailing.mas_equalTo(self.container).mas_offset(-kScale390(8));
|
||||
}];
|
||||
|
||||
[self.msgStatusView mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.width.mas_equalTo(16);
|
||||
make.height.mas_equalTo(self.messageData.msgStatusSize.height);
|
||||
make.bottom.mas_equalTo(self.msgTimeLabel);
|
||||
make.trailing.mas_equalTo(self.msgTimeLabel.mas_leading);
|
||||
}];
|
||||
|
||||
[self.selectedView mas_remakeConstraints:^(MASConstraintMaker *make) {
|
||||
make.edges.mas_equalTo(self.contentView);
|
||||
}];
|
||||
[self.selectedIcon mas_remakeConstraints:^(MASConstraintMaker *make) {
|
||||
make.leading.mas_equalTo(self.contentView.mas_leading).mas_offset(3);
|
||||
make.top.mas_equalTo(self.avatarView.mas_centerY).mas_offset(-10);
|
||||
if (self.messageData.showCheckBox) {
|
||||
make.width.mas_equalTo(20);
|
||||
make.height.mas_equalTo(20);
|
||||
} else {
|
||||
make.size.mas_equalTo(CGSizeZero);
|
||||
}
|
||||
}];
|
||||
}
|
||||
- (void)layoutSubviews {
|
||||
[super layoutSubviews];
|
||||
}
|
||||
|
||||
- (void)highlightWhenMatchKeyword:(NSString *)keyword {
|
||||
if (keyword) {
|
||||
if (self.highlightAnimating) {
|
||||
return;
|
||||
}
|
||||
[self animate:3];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)animate:(int)times {
|
||||
times--;
|
||||
if (times < 0) {
|
||||
[self.animateHighlightView removeFromSuperview];
|
||||
self.highlightAnimating = NO;
|
||||
return;
|
||||
}
|
||||
self.highlightAnimating = YES;
|
||||
self.animateHighlightView.frame = self.container.bounds;
|
||||
self.animateHighlightView.alpha = 0.1;
|
||||
[self.container addSubview:self.animateHighlightView];
|
||||
[UIView animateWithDuration:0.25
|
||||
animations:^{
|
||||
self.animateHighlightView.alpha = 0.5;
|
||||
}
|
||||
completion:^(BOOL finished) {
|
||||
[UIView animateWithDuration:0.25
|
||||
animations:^{
|
||||
self.animateHighlightView.alpha = 0.1;
|
||||
}
|
||||
completion:^(BOOL finished) {
|
||||
if (!self.imageData.highlightKeyword) {
|
||||
[self animate:0];
|
||||
return;
|
||||
}
|
||||
[self animate:times];
|
||||
}];
|
||||
}];
|
||||
}
|
||||
|
||||
- (UIView *)animateHighlightView {
|
||||
if (_animateHighlightView == nil) {
|
||||
_animateHighlightView = [[UIView alloc] init];
|
||||
_animateHighlightView.backgroundColor = [UIColor orangeColor];
|
||||
}
|
||||
return _animateHighlightView;
|
||||
}
|
||||
|
||||
#pragma mark - TUIMessageCellProtocol
|
||||
+ (CGFloat)getEstimatedHeight:(TUIMessageCellData *)data {
|
||||
return 139.f;
|
||||
}
|
||||
|
||||
+ (CGSize)getContentSize:(TUIMessageCellData *)data {
|
||||
NSAssert([data isKindOfClass:TUIImageMessageCellData.class], @"data must be kind of TUIImageMessageCellData");
|
||||
TUIImageMessageCellData *imageCellData = (TUIImageMessageCellData *)data;
|
||||
BOOL hasRiskContent = imageCellData.innerMessage.hasRiskContent;
|
||||
if (hasRiskContent) {
|
||||
return CGSizeMake(150, 150);
|
||||
}
|
||||
|
||||
CGSize size = CGSizeZero;
|
||||
BOOL isDir = NO;
|
||||
if (![imageCellData.path isEqualToString:@""] && [[NSFileManager defaultManager] fileExistsAtPath:imageCellData.path isDirectory:&isDir]) {
|
||||
if (!isDir) {
|
||||
size = [UIImage imageWithContentsOfFile:imageCellData.path].size;
|
||||
}
|
||||
}
|
||||
|
||||
if (CGSizeEqualToSize(size, CGSizeZero)) {
|
||||
for (TUIImageItem *item in imageCellData.items) {
|
||||
if (item.type == TImage_Type_Origin) {
|
||||
size = item.size;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (CGSizeEqualToSize(size, CGSizeZero)) {
|
||||
for (TUIImageItem *item in imageCellData.items) {
|
||||
if (item.type == TImage_Type_Large) {
|
||||
size = item.size;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (CGSizeEqualToSize(size, CGSizeZero)) {
|
||||
for (TUIImageItem *item in imageCellData.items) {
|
||||
if (item.type == TImage_Type_Thumb) {
|
||||
size = item.size;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (CGSizeEqualToSize(size, CGSizeZero)) {
|
||||
return size;
|
||||
}
|
||||
CGFloat widthMax = kScale390(250);
|
||||
CGFloat heightMax = kScale390(250);
|
||||
if (size.height > size.width) {
|
||||
size.width = size.width / size.height * heightMax;
|
||||
size.height = heightMax;
|
||||
} else {
|
||||
size.height = size.height / size.width * widthMax;
|
||||
size.width = widthMax;
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,30 @@
|
||||
|
||||
// Created by Tencent on 2023/06/09.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
#import <TIMCommon/TUISystemMessageCell.h>
|
||||
#import "TUIJoinGroupMessageCellData.h"
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@class TUIJoinGroupMessageCell_Minimalist;
|
||||
|
||||
@protocol TUIJoinGroupMessageCellDelegate_Minimalist <NSObject>
|
||||
|
||||
@optional
|
||||
|
||||
- (void)didTapOnNameLabel:(TUIJoinGroupMessageCell_Minimalist *)cell;
|
||||
|
||||
- (void)didTapOnSecondNameLabel:(TUIJoinGroupMessageCell_Minimalist *)cell;
|
||||
|
||||
- (void)didTapOnRestNameLabel:(TUIJoinGroupMessageCell_Minimalist *)cell withIndex:(NSInteger)index;
|
||||
|
||||
@end
|
||||
|
||||
@interface TUIJoinGroupMessageCell_Minimalist : TUISystemMessageCell
|
||||
|
||||
@property TUIJoinGroupMessageCellData *joinData;
|
||||
|
||||
@property(nonatomic, weak) id<TUIJoinGroupMessageCellDelegate_Minimalist> joinGroupDelegate;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,135 @@
|
||||
|
||||
// Created by Tencent on 2023/06/09.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
#import "TUIJoinGroupMessageCell_Minimalist.h"
|
||||
#import <TIMCommon/TIMDefine.h>
|
||||
|
||||
@interface TUIJoinGroupMessageCell_Minimalist () <UITextViewDelegate>
|
||||
@property(nonatomic, strong) UITextView *textView;
|
||||
@end
|
||||
|
||||
@implementation TUIJoinGroupMessageCell_Minimalist
|
||||
|
||||
- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier {
|
||||
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
|
||||
if (self) {
|
||||
_textView = [[UITextView alloc] init];
|
||||
_textView.editable = NO;
|
||||
_textView.scrollEnabled = NO;
|
||||
_textView.backgroundColor = [UIColor clearColor];
|
||||
_textView.textColor = [UIColor d_systemGrayColor];
|
||||
_textView.textContainerInset = UIEdgeInsetsMake(5, 0, 5, 0);
|
||||
_textView.layer.cornerRadius = 3;
|
||||
_textView.delegate = self;
|
||||
|
||||
_textView.textAlignment = NSTextAlignmentLeft;
|
||||
[self.messageLabel removeFromSuperview];
|
||||
[self.container addSubview:_textView];
|
||||
_textView.delaysContentTouches = NO;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)fillWithData:(TUIJoinGroupMessageCellData *)data;
|
||||
{
|
||||
[super fillWithData:data];
|
||||
self.joinData = data;
|
||||
self.nameLabel.hidden = YES;
|
||||
self.avatarView.hidden = YES;
|
||||
self.retryView.hidden = YES;
|
||||
[self.indicator stopAnimating];
|
||||
|
||||
NSMutableAttributedString *attributeString = [[NSMutableAttributedString alloc] initWithString:[NSString stringWithFormat:@"%@", data.content]];
|
||||
NSMutableParagraphStyle *paragraphStyle = [[NSMutableParagraphStyle alloc] init];
|
||||
paragraphStyle.alignment = NSTextAlignmentCenter;
|
||||
NSDictionary *attributeDict = @{
|
||||
NSFontAttributeName : self.messageLabel.font,
|
||||
NSForegroundColorAttributeName : self.messageLabel.textColor,
|
||||
NSBackgroundColorAttributeName : self.messageLabel.backgroundColor,
|
||||
NSParagraphStyleAttributeName : paragraphStyle
|
||||
};
|
||||
[attributeString setAttributes:attributeDict range:NSMakeRange(0, attributeString.length)];
|
||||
|
||||
if (data.userNameList.count > 0) {
|
||||
NSArray *nameRangeList = [self findRightRangeOfAllString:data.userNameList inText:attributeString.string];
|
||||
int i = 0;
|
||||
for (i = 0; i < nameRangeList.count; i++) {
|
||||
NSString *nameRangeString = nameRangeList[i];
|
||||
NSRange nameRange = NSRangeFromString(nameRangeString);
|
||||
[attributeString addAttribute:NSLinkAttributeName value:[NSString stringWithFormat:@"%d", i] range:nameRange];
|
||||
}
|
||||
}
|
||||
self.textView.attributedText = attributeString;
|
||||
[self setNeedsLayout];
|
||||
}
|
||||
|
||||
+ (BOOL)requiresConstraintBasedLayout {
|
||||
return YES;
|
||||
}
|
||||
|
||||
// this is Apple's recommended place for adding/updating constraints
|
||||
- (void)updateConstraints {
|
||||
|
||||
[super updateConstraints];
|
||||
|
||||
[self.container mas_remakeConstraints:^(MASConstraintMaker *make) {
|
||||
make.center.mas_equalTo(self.contentView);
|
||||
make.size.mas_equalTo(self.contentView);
|
||||
}];
|
||||
|
||||
if(self.textView.superview) {
|
||||
[self.textView mas_remakeConstraints:^(MASConstraintMaker *make) {
|
||||
make.center.mas_equalTo(self.container);
|
||||
make.size.mas_equalTo(self.contentView);
|
||||
}];
|
||||
}
|
||||
}
|
||||
- (void)layoutSubviews {
|
||||
[super layoutSubviews];
|
||||
}
|
||||
|
||||
- (void)onSelectUserName:(NSInteger)index {
|
||||
if (self.joinGroupDelegate && [self.joinGroupDelegate respondsToSelector:@selector(didTapOnRestNameLabel:withIndex:)])
|
||||
[self.joinGroupDelegate didTapOnRestNameLabel:self withIndex:index];
|
||||
}
|
||||
|
||||
- (BOOL)textView:(UITextView *)textView shouldInteractWithURL:(NSURL *)URL inRange:(NSRange)characterRange {
|
||||
NSArray *userNames = _joinData.userNameList;
|
||||
NSURL *urlRecognizer = [[NSURL alloc] init];
|
||||
|
||||
for (int i = 0; i < userNames.count; i++) {
|
||||
urlRecognizer = [NSURL URLWithString:[NSString stringWithFormat:@"%d", i]];
|
||||
if ([URL isEqual:urlRecognizer]) {
|
||||
[self onSelectUserName:i];
|
||||
}
|
||||
}
|
||||
|
||||
return NO;
|
||||
}
|
||||
|
||||
/**
|
||||
* To obtain the exact position of the nickname in the text content, the following properties are used: the storage order of userName in the array must be the
|
||||
* same as the order in which the final text is displayed. For example: the text content is, "A invited B, C, D to join the group", then the storage order of
|
||||
* the elements in userName must be ABCD. Therefore, the method of "searching from the beginning and searching in succession" is used. For example, find the
|
||||
* first element A first, because of the characteristics of rangeOfString, it must find the A at the head position. After finding A at the head position, we
|
||||
* remove A from the search range, and the search range becomes "B, C, D are invited to join the group", and then continue to search for the next element, which
|
||||
* is B.
|
||||
*/
|
||||
- (NSMutableArray *)findRightRangeOfAllString:(NSMutableArray<NSString *> *)stringList inText:(NSString *)text {
|
||||
NSMutableArray *rangeList = [NSMutableArray array];
|
||||
NSUInteger beginLocation = 0;
|
||||
NSEnumerator *enumer = [stringList objectEnumerator];
|
||||
|
||||
NSString *string = [NSString string];
|
||||
while (string = [enumer nextObject]) {
|
||||
NSRange newRange = NSMakeRange(beginLocation, text.length - beginLocation);
|
||||
NSRange stringRange = [text rangeOfString:string options:NSLiteralSearch range:newRange];
|
||||
|
||||
if (stringRange.length > 0) {
|
||||
[rangeList addObject:NSStringFromRange(stringRange)];
|
||||
beginLocation = stringRange.location + stringRange.length;
|
||||
}
|
||||
}
|
||||
return rangeList;
|
||||
}
|
||||
@end
|
||||
@@ -0,0 +1,39 @@
|
||||
//
|
||||
// TUIMediaCollectionCell.h
|
||||
// TUIChat
|
||||
//
|
||||
// Created by xiangzhang on 2021/11/22.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Photos/Photos.h>
|
||||
#import <TIMCommon/TUIMessageCellData.h>
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
@class TUIMediaCollectionCell_Minimalist;
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// TUIMediaCollectionCellDelegate
|
||||
//
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@protocol TUIMediaCollectionCellDelegate_Minimalist <NSObject>
|
||||
/**
|
||||
* meida cell
|
||||
*/
|
||||
- (void)onCloseMedia:(TUIMediaCollectionCell_Minimalist *)cell;
|
||||
@end
|
||||
|
||||
@interface TUIMediaCollectionCell_Minimalist : UICollectionViewCell
|
||||
|
||||
@property(nonatomic, strong) UIImageView *imageView;
|
||||
@property(nonatomic, strong) UIButton *downloadBtn;
|
||||
|
||||
@property(nonatomic, weak) id<TUIMediaCollectionCellDelegate_Minimalist> delegate;
|
||||
- (void)fillWithData:(TUIMessageCellData *)data;
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,24 @@
|
||||
//
|
||||
// TUIMediaCollectionCell.m
|
||||
// TUIChat
|
||||
//
|
||||
// Created by xiangzhang on 2021/11/22.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
//
|
||||
|
||||
#import "TUIMediaCollectionCell_Minimalist.h"
|
||||
|
||||
@implementation TUIMediaCollectionCell_Minimalist
|
||||
- (id)initWithFrame:(CGRect)frame {
|
||||
self = [super initWithFrame:frame];
|
||||
if (self) {
|
||||
self.backgroundColor = [UIColor clearColor];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)fillWithData:(TUIMessageCellData *)data {
|
||||
return;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,19 @@
|
||||
|
||||
// Created by Tencent on 2023/06/09.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
#import <UIKit/UIKit.h>
|
||||
#import "TUIMenuCellData.h"
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// TUIMenuCell_Minimalist
|
||||
//
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@interface TUIMenuCell_Minimalist : UICollectionViewCell
|
||||
|
||||
@property(nonatomic, strong) UIImageView *menu;
|
||||
|
||||
- (void)setData:(TUIMenuCellData *)data;
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,46 @@
|
||||
//
|
||||
// InputMenuCell.m
|
||||
// UIKit
|
||||
//
|
||||
// Created by kennethmiao on 2018/9/20.
|
||||
// Copyright © 2018 Tencent. All rights reserved.
|
||||
//
|
||||
|
||||
#import "TUIMenuCell_Minimalist.h"
|
||||
#import <TIMCommon/TIMDefine.h>
|
||||
#import <TUICore/TUIThemeManager.h>
|
||||
|
||||
@implementation TUIMenuCell_Minimalist
|
||||
- (id)initWithFrame:(CGRect)frame {
|
||||
self = [super initWithFrame:frame];
|
||||
if (self) {
|
||||
[self setupViews];
|
||||
[self defaultLayout];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)setupViews {
|
||||
self.backgroundColor = TUIChatDynamicColor(@"chat_controller_bg_color", @"#EBF0F6");
|
||||
_menu = [[UIImageView alloc] init];
|
||||
_menu.backgroundColor = [UIColor clearColor];
|
||||
[self addSubview:_menu];
|
||||
}
|
||||
|
||||
- (void)defaultLayout {
|
||||
}
|
||||
|
||||
- (void)setData:(TUIMenuCellData *)data {
|
||||
// set data
|
||||
_menu.image = [[TUIImageCache sharedInstance] getFaceFromCache:data.path];
|
||||
if (data.isSelected) {
|
||||
self.backgroundColor = TUIChatDynamicColor(@"chat_face_menu_select_color", @"#FFFFFF");
|
||||
} else {
|
||||
self.backgroundColor = TUIChatDynamicColor(@"chat_input_controller_bg_color", @"#EBF0F6");
|
||||
}
|
||||
// update layout
|
||||
CGSize size = self.frame.size;
|
||||
_menu.frame = CGRectMake(TMenuCell_Margin, TMenuCell_Margin, size.width - 2 * TMenuCell_Margin, size.height - 2 * TMenuCell_Margin);
|
||||
_menu.contentMode = UIViewContentModeScaleAspectFit;
|
||||
}
|
||||
@end
|
||||
@@ -0,0 +1,39 @@
|
||||
|
||||
// Created by Tencent on 2023/06/09.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
/**
|
||||
* This document declares the TUIMergeMessageCell class.
|
||||
* When multiple messages are merged and forwarded, a merged-forward message will be displayed on the chat interface.
|
||||
*
|
||||
* When we receive a merged-forward message, it is usually displayed in the chat interface like this:
|
||||
* | History of vinson and lynx | -- title | vinson:When will the new version of the SDK be released? | -- abstract1 | lynx:Plan for next
|
||||
* Monday, the specific time depends on the system test situation in these two days.. | -- abstract2 | vinson:Okay.
|
||||
*/
|
||||
|
||||
#import <TIMCommon/TUIMessageCell_Minimalist.h>
|
||||
#import "TUIMergeMessageCellData.h"
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface TUIMergeMessageCell_Minimalist : TUIMessageCell_Minimalist
|
||||
/**
|
||||
* Title of merged-forward message
|
||||
*/
|
||||
@property(nonatomic, strong) UILabel *relayTitleLabel;
|
||||
|
||||
|
||||
/**
|
||||
* Horizontal dividing line
|
||||
*/
|
||||
@property(nonatomic, strong) UIView *separtorView;
|
||||
|
||||
/**
|
||||
* bottom prompt
|
||||
*/
|
||||
@property(nonatomic, strong) UILabel *bottomTipsLabel;
|
||||
|
||||
@property(nonatomic, strong) TUIMergeMessageCellData *mergeData;
|
||||
- (void)fillWithData:(TUIMergeMessageCellData *)data;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,326 @@
|
||||
//
|
||||
// TUIMergeMessageCell.m
|
||||
// Pods
|
||||
//
|
||||
// Created by harvy on 2020/12/9.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
//
|
||||
|
||||
#import "TUIMergeMessageCell_Minimalist.h"
|
||||
#import <TIMCommon/TIMDefine.h>
|
||||
#import <TUICore/TUIThemeManager.h>
|
||||
|
||||
#ifndef CGFLOAT_CEIL
|
||||
#ifdef CGFLOAT_IS_DOUBLE
|
||||
#define CGFLOAT_CEIL(value) ceil(value)
|
||||
#else
|
||||
#define CGFLOAT_CEIL(value) ceilf(value)
|
||||
#endif
|
||||
#endif
|
||||
|
||||
@interface TUIMergeMessageDetailRow_Minimalist : UIView
|
||||
@property(nonatomic, strong) UILabel *abstractName;
|
||||
@property(nonatomic, strong) UILabel *abstractBreak;
|
||||
@property(nonatomic, strong) UILabel *abstractDetail;
|
||||
@property(nonatomic, assign) CGFloat abstractNameLimitedWidth;
|
||||
- (void)fillWithData:(NSAttributedString *)name detailContent:(NSAttributedString *)detailContent;
|
||||
@end
|
||||
@implementation TUIMergeMessageDetailRow_Minimalist
|
||||
|
||||
- (instancetype)init {
|
||||
self = [super init];
|
||||
if(self){
|
||||
[self setupview];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
- (void)setupview {
|
||||
[self addSubview:self.abstractName];
|
||||
[self addSubview:self.abstractBreak];
|
||||
[self addSubview:self.abstractDetail];
|
||||
}
|
||||
|
||||
- (UILabel *)abstractName {
|
||||
if(!_abstractName) {
|
||||
_abstractName = [[UILabel alloc] init];
|
||||
_abstractName.numberOfLines = 1;
|
||||
_abstractName.font = [UIFont systemFontOfSize:12.0];
|
||||
_abstractName.textColor = [UIColor colorWithRed:187 / 255.0 green:187 / 255.0 blue:187 / 255.0 alpha:1 / 1.0];
|
||||
_abstractName.textAlignment = isRTL()? NSTextAlignmentRight:NSTextAlignmentLeft;
|
||||
}
|
||||
return _abstractName;
|
||||
}
|
||||
- (UILabel *)abstractBreak {
|
||||
if(!_abstractBreak) {
|
||||
_abstractBreak = [[UILabel alloc] init];
|
||||
_abstractBreak.text = @":";
|
||||
_abstractBreak.font = [UIFont systemFontOfSize:12.0];
|
||||
_abstractBreak.textColor = TUIChatDynamicColor(@"chat_merge_message_content_color", @"#d5d5d5");
|
||||
}
|
||||
return _abstractBreak;
|
||||
}
|
||||
- (UILabel *)abstractDetail {
|
||||
if(!_abstractDetail) {
|
||||
_abstractDetail = [[UILabel alloc] init];
|
||||
_abstractDetail.numberOfLines = 0;
|
||||
_abstractName.font = [UIFont systemFontOfSize:12.0];
|
||||
_abstractDetail.textColor = TUIChatDynamicColor(@"chat_merge_message_content_color", @"#d5d5d5");
|
||||
_abstractDetail.textAlignment = isRTL()? NSTextAlignmentRight:NSTextAlignmentLeft;
|
||||
}
|
||||
return _abstractDetail;
|
||||
}
|
||||
|
||||
+ (BOOL)requiresConstraintBasedLayout {
|
||||
return YES;
|
||||
}
|
||||
|
||||
// this is Apple's recommended place for adding/updating constraints
|
||||
- (void)updateConstraints {
|
||||
|
||||
[super updateConstraints];
|
||||
|
||||
[self.abstractName sizeToFit];
|
||||
[self.abstractName mas_remakeConstraints:^(MASConstraintMaker *make) {
|
||||
make.leading.mas_equalTo(0);
|
||||
make.top.mas_equalTo(0);
|
||||
make.trailing.mas_lessThanOrEqualTo(self.abstractBreak.mas_leading);
|
||||
make.width.mas_equalTo(self.abstractNameLimitedWidth);
|
||||
}];
|
||||
|
||||
[self.abstractBreak sizeToFit];
|
||||
[self.abstractBreak mas_remakeConstraints:^(MASConstraintMaker *make) {
|
||||
make.leading.mas_equalTo(self.abstractName.mas_trailing);
|
||||
make.top.mas_equalTo(self.abstractName);
|
||||
make.width.mas_offset(self.abstractBreak.frame.size.width);
|
||||
make.height.mas_offset(self.abstractBreak.frame.size.height);
|
||||
}];
|
||||
|
||||
[self.abstractDetail sizeToFit];
|
||||
[self.abstractDetail mas_remakeConstraints:^(MASConstraintMaker *make) {
|
||||
make.leading.mas_equalTo(self.abstractBreak.mas_trailing);
|
||||
make.top.mas_equalTo(0);
|
||||
make.trailing.mas_lessThanOrEqualTo(self.mas_trailing).mas_offset(-15);
|
||||
make.bottom.mas_equalTo(self);
|
||||
}];
|
||||
}
|
||||
- (void)fillWithData:(NSAttributedString *)name detailContent:(NSAttributedString *)detailContent {
|
||||
|
||||
self.abstractName.attributedText = name;
|
||||
self.abstractDetail.attributedText = detailContent;
|
||||
|
||||
NSAttributedString * senderStr = [[NSAttributedString alloc] initWithString:self.abstractName.text];
|
||||
CGRect senderRect = [senderStr boundingRectWithSize:CGSizeMake(70, MAXFLOAT) options:NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading
|
||||
context:nil];
|
||||
self.abstractNameLimitedWidth = MIN(ceil(senderRect.size.width) + 2, 70);
|
||||
// tell constraints they need updating
|
||||
[self setNeedsUpdateConstraints];
|
||||
|
||||
// update constraints now so we can animate the change
|
||||
[self updateConstraintsIfNeeded];
|
||||
|
||||
[self layoutIfNeeded];
|
||||
|
||||
|
||||
}
|
||||
- (void)layoutSubviews {
|
||||
[super layoutSubviews];
|
||||
}
|
||||
|
||||
|
||||
@end
|
||||
@interface TUIMergeMessageCell_Minimalist ()
|
||||
|
||||
@property(nonatomic, strong) CAShapeLayer *borderLayer;
|
||||
@property(nonatomic, strong) TUIMergeMessageDetailRow_Minimalist *contentRowView1;
|
||||
@property(nonatomic, strong) TUIMergeMessageDetailRow_Minimalist *contentRowView2;
|
||||
@property(nonatomic, strong) TUIMergeMessageDetailRow_Minimalist *contentRowView3;
|
||||
|
||||
@end
|
||||
@implementation TUIMergeMessageCell_Minimalist
|
||||
|
||||
- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier {
|
||||
if ([super initWithStyle:style reuseIdentifier:reuseIdentifier]) {
|
||||
[self setupViews];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)setupViews {
|
||||
self.container.backgroundColor = RGBA(249, 249, 249, 0.94);
|
||||
|
||||
_relayTitleLabel = [[UILabel alloc] init];
|
||||
_relayTitleLabel.text = @"Chat history";
|
||||
_relayTitleLabel.font = [UIFont systemFontOfSize:12];
|
||||
_relayTitleLabel.textColor = RGBA(0, 0, 0, 0.8);
|
||||
[self.container addSubview:_relayTitleLabel];
|
||||
|
||||
_contentRowView1 = [[TUIMergeMessageDetailRow_Minimalist alloc] init];
|
||||
[self.container addSubview:_contentRowView1];
|
||||
_contentRowView2 = [[TUIMergeMessageDetailRow_Minimalist alloc] init];
|
||||
[self.container addSubview:_contentRowView2];
|
||||
_contentRowView3 = [[TUIMergeMessageDetailRow_Minimalist alloc] init];
|
||||
[self.container addSubview:_contentRowView3];
|
||||
|
||||
_separtorView = [[UIView alloc] init];
|
||||
_separtorView.backgroundColor = TIMCommonDynamicColor(@"separator_color", @"#DBDBDB");
|
||||
[self.container addSubview:_separtorView];
|
||||
|
||||
_bottomTipsLabel = [[UILabel alloc] init];
|
||||
_bottomTipsLabel.text = TIMCommonLocalizableString(TUIKitRelayChatHistory);
|
||||
_bottomTipsLabel.textColor = RGBA(153, 153, 153, 1);
|
||||
_bottomTipsLabel.font = [UIFont systemFontOfSize:10];
|
||||
[self.container addSubview:_bottomTipsLabel];
|
||||
}
|
||||
|
||||
+ (BOOL)requiresConstraintBasedLayout {
|
||||
return YES;
|
||||
}
|
||||
|
||||
// this is Apple's recommended place for adding/updating constraints
|
||||
- (void)updateConstraints {
|
||||
|
||||
[super updateConstraints];
|
||||
|
||||
[self.relayTitleLabel sizeToFit];
|
||||
[self.relayTitleLabel mas_remakeConstraints:^(MASConstraintMaker *make) {
|
||||
make.leading.mas_equalTo(self.container).mas_offset(10);
|
||||
make.width.mas_lessThanOrEqualTo(self.container);
|
||||
make.height.mas_equalTo(self.relayTitleLabel.font.lineHeight);
|
||||
make.top.mas_equalTo(self.container).mas_offset(10);
|
||||
}];
|
||||
|
||||
[self.contentRowView1 mas_remakeConstraints:^(MASConstraintMaker *make) {
|
||||
make.leading.mas_equalTo(self.relayTitleLabel);
|
||||
make.top.mas_equalTo(self.relayTitleLabel.mas_bottom).mas_offset(3);
|
||||
make.trailing.mas_equalTo(self.container);
|
||||
make.height.mas_equalTo(self.mergeData.abstractRow1Size.height);
|
||||
}];
|
||||
|
||||
[self.contentRowView2 mas_remakeConstraints:^(MASConstraintMaker *make) {
|
||||
make.leading.mas_equalTo(self.relayTitleLabel);
|
||||
make.top.mas_equalTo(self.contentRowView1.mas_bottom).mas_offset(3);
|
||||
make.trailing.mas_equalTo(self.container);
|
||||
make.height.mas_equalTo(self.mergeData.abstractRow2Size.height);
|
||||
}];
|
||||
|
||||
[self.contentRowView3 mas_remakeConstraints:^(MASConstraintMaker *make) {
|
||||
make.leading.mas_equalTo(self.relayTitleLabel);
|
||||
make.top.mas_equalTo(self.contentRowView2.mas_bottom).mas_offset(3);
|
||||
make.trailing.mas_equalTo(self.container);
|
||||
make.height.mas_equalTo(self.mergeData.abstractRow3Size.height);
|
||||
}];
|
||||
|
||||
UIView *lastView = self.contentRowView1;
|
||||
int count = self.mergeData.abstractSendDetailList.count;
|
||||
if (count >= 3) {
|
||||
lastView = self.contentRowView3;
|
||||
}
|
||||
else if (count == 2){
|
||||
lastView = self.contentRowView2;
|
||||
}
|
||||
[self.separtorView mas_remakeConstraints:^(MASConstraintMaker *make) {
|
||||
make.leading.mas_equalTo(self.container).mas_offset(10);
|
||||
make.trailing.mas_equalTo(self.container).mas_offset(-10);
|
||||
make.top.mas_equalTo(lastView.mas_bottom).mas_offset(3);
|
||||
make.height.mas_equalTo(1);
|
||||
}];
|
||||
|
||||
[self.bottomTipsLabel mas_remakeConstraints:^(MASConstraintMaker *make) {
|
||||
make.leading.mas_equalTo(self.contentRowView1);
|
||||
make.top.mas_equalTo(self.separtorView.mas_bottom).mas_offset(5);
|
||||
make.width.mas_lessThanOrEqualTo(self.container);
|
||||
make.height.mas_equalTo(self.bottomTipsLabel.font.lineHeight);
|
||||
}];
|
||||
|
||||
}
|
||||
- (void)layoutSubviews {
|
||||
[super layoutSubviews];
|
||||
}
|
||||
|
||||
- (void)fillWithData:(TUIMergeMessageCellData *)data {
|
||||
[super fillWithData:data];
|
||||
self.mergeData = data;
|
||||
self.relayTitleLabel.text = data.title;
|
||||
int count = self.mergeData.abstractSendDetailList.count;
|
||||
switch (count) {
|
||||
case 1:
|
||||
[self.contentRowView1 fillWithData:self.mergeData.abstractSendDetailList[0][@"sender"] detailContent:self.mergeData.abstractSendDetailList[0][@"detail"]];
|
||||
self.contentRowView1.hidden = NO;
|
||||
self.contentRowView2.hidden = YES;
|
||||
self.contentRowView3.hidden = YES;
|
||||
break;
|
||||
case 2:
|
||||
[self.contentRowView1 fillWithData:self.mergeData.abstractSendDetailList[0][@"sender"] detailContent:self.mergeData.abstractSendDetailList[0][@"detail"]];
|
||||
[self.contentRowView2 fillWithData:self.mergeData.abstractSendDetailList[1][@"sender"] detailContent:self.mergeData.abstractSendDetailList[1][@"detail"]];
|
||||
|
||||
self.contentRowView1.hidden = NO;
|
||||
self.contentRowView2.hidden = NO;
|
||||
self.contentRowView3.hidden = YES;
|
||||
break;
|
||||
default:
|
||||
|
||||
[self.contentRowView1 fillWithData:self.mergeData.abstractSendDetailList[0][@"sender"] detailContent:self.mergeData.abstractSendDetailList[0][@"detail"]];
|
||||
[self.contentRowView2 fillWithData:self.mergeData.abstractSendDetailList[1][@"sender"] detailContent:self.mergeData.abstractSendDetailList[1][@"detail"]];
|
||||
[self.contentRowView3 fillWithData:self.mergeData.abstractSendDetailList[2][@"sender"] detailContent:self.mergeData.abstractSendDetailList[2][@"detail"]];
|
||||
self.contentRowView1.hidden = NO;
|
||||
self.contentRowView2.hidden = NO;
|
||||
self.contentRowView3.hidden = NO;
|
||||
break;
|
||||
}
|
||||
// tell constraints they need updating
|
||||
[self setNeedsUpdateConstraints];
|
||||
|
||||
// update constraints now so we can animate the change
|
||||
[self updateConstraintsIfNeeded];
|
||||
|
||||
[self layoutIfNeeded];
|
||||
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - TUIMessageCellProtocol
|
||||
+ (CGSize)getContentSize:(TUIMessageCellData *)data {
|
||||
NSAssert([data isKindOfClass:TUIMergeMessageCellData.class], @"data must be kind of TUIMergeMessageCellData");
|
||||
TUIMergeMessageCellData *mergeCellData = (TUIMergeMessageCellData *)data;
|
||||
|
||||
mergeCellData.abstractRow1Size = [self.class caculate:mergeCellData index:0];
|
||||
mergeCellData.abstractRow2Size = [self.class caculate:mergeCellData index:1];
|
||||
mergeCellData.abstractRow3Size = [self.class caculate:mergeCellData index:2];
|
||||
|
||||
NSAttributedString *abstractAttributedString = [mergeCellData abstractAttributedString];
|
||||
CGRect rect = [abstractAttributedString boundingRectWithSize:CGSizeMake(TMergeMessageCell_Width_Max - 20, MAXFLOAT)
|
||||
options:NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading
|
||||
context:nil];
|
||||
CGSize size = CGSizeMake(CGFLOAT_CEIL(rect.size.width), CGFLOAT_CEIL(rect.size.height) - 10);
|
||||
mergeCellData.abstractSize = size;
|
||||
CGFloat height = mergeCellData.abstractRow1Size.height + mergeCellData.abstractRow2Size.height + mergeCellData.abstractRow3Size.height;
|
||||
UIFont *titleFont = [UIFont systemFontOfSize:16];
|
||||
height = (10 + titleFont.lineHeight + 3) + height + 1 + 5 + 20 + 5 + 3;
|
||||
return CGSizeMake(TMergeMessageCell_Width_Max, height + mergeCellData.msgStatusSize.height);
|
||||
}
|
||||
|
||||
+ (CGSize)caculate:(TUIMergeMessageCellData *)data index:(NSInteger)index {
|
||||
|
||||
NSArray<NSDictionary *> *abstractSendDetailList = data.abstractSendDetailList;
|
||||
if (abstractSendDetailList.count <= index){
|
||||
return CGSizeZero;
|
||||
}
|
||||
NSAttributedString * senderStr = abstractSendDetailList[index][@"sender"];
|
||||
CGRect senderRect = [senderStr boundingRectWithSize:CGSizeMake(70, MAXFLOAT) options:NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading
|
||||
context:nil];
|
||||
|
||||
NSMutableAttributedString *abstr = [[NSMutableAttributedString alloc] initWithString:@""];
|
||||
[abstr appendAttributedString:[[NSAttributedString alloc] initWithString:@":"]];
|
||||
[abstr appendAttributedString:abstractSendDetailList[index][@"detail"]];
|
||||
|
||||
CGFloat senderWidth = senderRect.size.width;
|
||||
CGRect rect = [abstr boundingRectWithSize:CGSizeMake(200 - 20 - senderWidth, MAXFLOAT)
|
||||
options:NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading
|
||||
context:nil];
|
||||
CGSize size = CGSizeMake(TMergeMessageCell_Width_Max,
|
||||
MIN(TMergeMessageCell_Height_Max / 3.0, CGFLOAT_CEIL(rect.size.height)));
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,15 @@
|
||||
|
||||
// Created by Tencent on 2023/06/09.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
#import "TUIMediaCollectionCell_Minimalist.h"
|
||||
#import "TUIVideoMessageCellData.h"
|
||||
|
||||
@interface TUIVideoCollectionCell_Minimalist : TUIMediaCollectionCell_Minimalist
|
||||
|
||||
- (void)fillWithData:(TUIVideoMessageCellData *)data;
|
||||
|
||||
- (void)stopVideoPlayAndSave;
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,287 @@
|
||||
|
||||
// Created by Tencent on 2023/06/09.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
#import "TUIVideoCollectionCell_Minimalist.h"
|
||||
#import <TIMCommon/TIMDefine.h>
|
||||
#import "ReactiveObjC/ReactiveObjC.h"
|
||||
|
||||
@import MediaPlayer;
|
||||
@import AVFoundation;
|
||||
@import AVKit;
|
||||
|
||||
@interface TUIVideoCollectionCell_Minimalist ()
|
||||
@property(nonatomic, strong) UILabel *duration;
|
||||
@property(nonatomic, strong) UILabel *playTime;
|
||||
@property(nonatomic, strong) UISlider *playProcess;
|
||||
@property(nonatomic, strong) UIButton *mainPlayBtn;
|
||||
@property(nonatomic, strong) UIButton *playBtn;
|
||||
@property(nonatomic, strong) UIButton *closeBtn;
|
||||
@property(nonatomic, strong) AVPlayer *player;
|
||||
@property(nonatomic, strong) NSString *videoPath;
|
||||
@property(nonatomic, strong) NSURL *videoUrl;
|
||||
@property(nonatomic, assign) BOOL isPlay;
|
||||
@property(nonatomic, assign) BOOL isSaveVideo;
|
||||
@property(nonatomic, strong) TUIVideoMessageCellData *videoData;
|
||||
@end
|
||||
|
||||
@implementation TUIVideoCollectionCell_Minimalist
|
||||
- (id)initWithFrame:(CGRect)frame {
|
||||
self = [super initWithFrame:frame];
|
||||
if (self) {
|
||||
[self setupViews];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)setupViews {
|
||||
self.imageView = [[UIImageView alloc] init];
|
||||
self.imageView.layer.cornerRadius = 5.0;
|
||||
[self.imageView.layer setMasksToBounds:YES];
|
||||
self.imageView.contentMode = UIViewContentModeScaleAspectFit;
|
||||
self.imageView.backgroundColor = [UIColor clearColor];
|
||||
[self addSubview:self.imageView];
|
||||
self.imageView.mm_fill();
|
||||
self.imageView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
|
||||
|
||||
self.mainPlayBtn = [UIButton buttonWithType:UIButtonTypeCustom];
|
||||
self.mainPlayBtn.contentMode = UIViewContentModeScaleToFill;
|
||||
[self.mainPlayBtn setImage:TUIChatCommonBundleImage(@"video_play_big") forState:UIControlStateNormal];
|
||||
[self.mainPlayBtn addTarget:self action:@selector(onPlayBtnClick) forControlEvents:UIControlEventTouchUpInside];
|
||||
[self addSubview:self.mainPlayBtn];
|
||||
|
||||
self.playBtn = [UIButton buttonWithType:UIButtonTypeCustom];
|
||||
self.playBtn.contentMode = UIViewContentModeScaleToFill;
|
||||
[self.playBtn setImage:TUIChatCommonBundleImage(@"video_play") forState:UIControlStateNormal];
|
||||
[self.playBtn addTarget:self action:@selector(onPlayBtnClick) forControlEvents:UIControlEventTouchUpInside];
|
||||
[self addSubview:self.playBtn];
|
||||
|
||||
self.closeBtn = [UIButton buttonWithType:UIButtonTypeCustom];
|
||||
self.closeBtn.contentMode = UIViewContentModeScaleToFill;
|
||||
[self.closeBtn setImage:TUIChatCommonBundleImage(@"video_close") forState:UIControlStateNormal];
|
||||
[self.closeBtn addTarget:self action:@selector(onCloseBtnClick) forControlEvents:UIControlEventTouchUpInside];
|
||||
[self addSubview:self.closeBtn];
|
||||
|
||||
self.downloadBtn = [UIButton buttonWithType:UIButtonTypeCustom];
|
||||
self.downloadBtn.contentMode = UIViewContentModeScaleToFill;
|
||||
[self.downloadBtn setImage:TUIChatCommonBundleImage(@"download") forState:UIControlStateNormal];
|
||||
[self.downloadBtn addTarget:self action:@selector(onDownloadBtnClick) forControlEvents:UIControlEventTouchUpInside];
|
||||
[self addSubview:self.downloadBtn];
|
||||
|
||||
self.playTime = [[UILabel alloc] init];
|
||||
self.playTime.textColor = [UIColor whiteColor];
|
||||
self.playTime.font = [UIFont systemFontOfSize:12];
|
||||
self.playTime.text = @"00:00";
|
||||
[self addSubview:self.playTime];
|
||||
|
||||
self.duration = [[UILabel alloc] init];
|
||||
self.duration.textColor = [UIColor whiteColor];
|
||||
self.duration.font = [UIFont systemFontOfSize:12];
|
||||
self.duration.text = @"00:00";
|
||||
[self addSubview:self.duration];
|
||||
|
||||
self.playProcess = [[UISlider alloc] init];
|
||||
self.playProcess.minimumValue = 0;
|
||||
self.playProcess.maximumValue = 1;
|
||||
self.playProcess.minimumTrackTintColor = [UIColor whiteColor];
|
||||
[self.playProcess addTarget:self action:@selector(onSliderValueChangedBegin:) forControlEvents:UIControlEventTouchDown];
|
||||
[self.playProcess addTarget:self action:@selector(onSliderValueChanged:) forControlEvents:UIControlEventTouchUpInside];
|
||||
[self addSubview:self.playProcess];
|
||||
}
|
||||
|
||||
- (void)fillWithData:(TUIVideoMessageCellData *)data;
|
||||
{
|
||||
[super fillWithData:data];
|
||||
self.videoData = data;
|
||||
self.isSaveVideo = NO;
|
||||
|
||||
CGFloat duration = data.videoItem.duration;
|
||||
self.duration.text = [NSString stringWithFormat:@"%.2d:%.2d", (int)duration / 60, (int)duration % 60];
|
||||
|
||||
self.imageView.image = nil;
|
||||
if (data.thumbImage == nil) {
|
||||
[data downloadThumb];
|
||||
}
|
||||
@weakify(self);
|
||||
[[RACObserve(data, thumbImage) takeUntil:self.rac_prepareForReuseSignal] subscribeNext:^(UIImage *thumbImage) {
|
||||
@strongify(self);
|
||||
if (thumbImage) {
|
||||
self.imageView.image = thumbImage;
|
||||
}
|
||||
}];
|
||||
|
||||
if (![self.videoData isVideoExist]) {
|
||||
/**
|
||||
* Undownloaded videos play using online url
|
||||
*/
|
||||
[self.videoData getVideoUrl:^(NSString *_Nonnull url) {
|
||||
@strongify(self);
|
||||
if (url) {
|
||||
[self addPlayer:[NSURL URLWithString:url]];
|
||||
}
|
||||
}];
|
||||
/**
|
||||
* Download video asynchronously
|
||||
*/
|
||||
[self.videoData downloadVideo];
|
||||
} else {
|
||||
/**
|
||||
* Downloaded videos can be played directly using local video files
|
||||
*/
|
||||
self.videoPath = self.videoData.videoPath;
|
||||
if (self.videoPath) {
|
||||
[self addPlayer:[NSURL fileURLWithPath:self.videoPath]];
|
||||
}
|
||||
}
|
||||
|
||||
[[[RACObserve(self.videoData, videoPath) filter:^BOOL(NSString *path) {
|
||||
return [path length] > 0;
|
||||
}] take:1] subscribeNext:^(NSString *path) {
|
||||
@strongify(self);
|
||||
self.videoPath = path;
|
||||
if (self.isSaveVideo) {
|
||||
[self saveVideo];
|
||||
}
|
||||
|
||||
/**
|
||||
* If it has not been played, or the playback is wrong, switch from online playback to local playback
|
||||
*/
|
||||
if (self.player.status == AVPlayerStatusFailed || self.player.status == AVPlayerStatusReadyToPlay) {
|
||||
[self addPlayer:[NSURL fileURLWithPath:self.videoPath]];
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)layoutSubviews {
|
||||
[super layoutSubviews];
|
||||
self.mainPlayBtn.mm_width(65).mm_height(65).mm__centerX(self.mm_w / 2).mm__centerY(self.mm_h / 2);
|
||||
self.closeBtn.mm_width(31).mm_height(31).mm_left(16).mm_bottom(47);
|
||||
self.downloadBtn.mm_width(31).mm_height(31).mm_right(16).mm_bottom(48);
|
||||
self.playBtn.mm_width(30).mm_height(30).mm_left(32).mm_bottom(108);
|
||||
self.playTime.mm_width(40).mm_height(21).mm_left(self.playBtn.mm_maxX + 12).mm__centerY(self.playBtn.mm_centerY);
|
||||
self.duration.mm_width(40).mm_height(21).mm_right(15).mm__centerY(self.playBtn.mm_centerY);
|
||||
self.playProcess.mm_sizeToFit()
|
||||
.mm_left(self.playTime.mm_maxX + 10)
|
||||
.mm_flexToRight(self.duration.mm_r + self.duration.mm_w + 10)
|
||||
.mm__centerY(self.playBtn.mm_centerY);
|
||||
}
|
||||
|
||||
- (void)addPlayer:(NSURL *)url {
|
||||
self.videoUrl = url;
|
||||
if (!self.player) {
|
||||
self.player = [AVPlayer playerWithURL:self.videoUrl];
|
||||
AVPlayerLayer *playerLayer = [AVPlayerLayer playerLayerWithPlayer:self.player];
|
||||
playerLayer.frame = self.bounds;
|
||||
[self.layer insertSublayer:playerLayer atIndex:0];
|
||||
|
||||
@weakify(self);
|
||||
[self.player addPeriodicTimeObserverForInterval:CMTimeMakeWithSeconds(0.05, 30)
|
||||
queue:NULL
|
||||
usingBlock:^(CMTime time) {
|
||||
@strongify(self);
|
||||
CGFloat curTime = CMTimeGetSeconds(self.player.currentItem.currentTime);
|
||||
CGFloat duration = CMTimeGetSeconds(self.player.currentItem.duration);
|
||||
CGFloat progress = curTime / duration;
|
||||
[self.playProcess setValue:progress];
|
||||
self.playTime.text = [NSString stringWithFormat:@"%.2d:%.2d", (int)curTime / 60, (int)curTime % 60];
|
||||
}];
|
||||
[self addPlayerItemObserver];
|
||||
} else {
|
||||
[self removePlayerItemObserver];
|
||||
AVPlayerItem *item = [AVPlayerItem playerItemWithURL:self.videoUrl];
|
||||
[self.player replaceCurrentItemWithPlayerItem:item];
|
||||
[self addPlayerItemObserver];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)addPlayerItemObserver {
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onVideoPlayEnd) name:AVPlayerItemDidPlayToEndTimeNotification object:nil];
|
||||
}
|
||||
|
||||
- (void)removePlayerItemObserver {
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:self name:AVPlayerItemDidPlayToEndTimeNotification object:nil];
|
||||
}
|
||||
|
||||
- (void)stopVideoPlayAndSave {
|
||||
[self stopPlay];
|
||||
self.isSaveVideo = NO;
|
||||
[TUITool hideToast];
|
||||
}
|
||||
|
||||
#pragma player event
|
||||
|
||||
- (void)onPlayBtnClick {
|
||||
if (self.isPlay) {
|
||||
[self stopPlay];
|
||||
} else {
|
||||
[self play];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)onCloseBtnClick {
|
||||
[self stopPlay];
|
||||
if (self.delegate && [self.delegate respondsToSelector:@selector(onCloseMedia:)]) {
|
||||
[self.delegate onCloseMedia:self];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)onVideoPlayEnd {
|
||||
if (1 == self.playProcess.value) {
|
||||
[self.player seekToTime:CMTimeMakeWithSeconds(0, 30)];
|
||||
[self stopPlay];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)onSliderValueChangedBegin:(id)sender {
|
||||
[self.player pause];
|
||||
}
|
||||
|
||||
- (void)onSliderValueChanged:(id)sender {
|
||||
UISlider *slider = (UISlider *)sender;
|
||||
CGFloat curTime = CMTimeGetSeconds(self.player.currentItem.duration) * slider.value;
|
||||
[self.player seekToTime:CMTimeMakeWithSeconds(curTime, 30)];
|
||||
[self play];
|
||||
}
|
||||
|
||||
- (void)play {
|
||||
self.isPlay = YES;
|
||||
[self.player play];
|
||||
self.imageView.hidden = YES;
|
||||
self.mainPlayBtn.hidden = YES;
|
||||
[self.playBtn setImage:TUIChatCommonBundleImage(@"video_pause") forState:UIControlStateNormal];
|
||||
}
|
||||
|
||||
- (void)stopPlay {
|
||||
self.isPlay = NO;
|
||||
[self.player pause];
|
||||
self.mainPlayBtn.hidden = NO;
|
||||
[self.playBtn setImage:TUIChatCommonBundleImage(@"video_play") forState:UIControlStateNormal];
|
||||
}
|
||||
|
||||
#pragma video save
|
||||
- (void)onDownloadBtnClick {
|
||||
if (![self.videoData isVideoExist]) {
|
||||
self.isSaveVideo = YES;
|
||||
[TUITool makeToast:TIMCommonLocalizableString(TUIKitVideoDownloading) duration:CGFLOAT_MAX];
|
||||
} else {
|
||||
[self saveVideo];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)saveVideo {
|
||||
[TUITool hideToast];
|
||||
[[PHPhotoLibrary sharedPhotoLibrary]
|
||||
performChanges:^{
|
||||
PHAssetChangeRequest *request = [PHAssetChangeRequest creationRequestForAssetFromVideoAtFileURL:[NSURL fileURLWithPath:self.videoPath]];
|
||||
request.creationDate = [NSDate date];
|
||||
}
|
||||
completionHandler:^(BOOL success, NSError *_Nullable error) {
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
if (success) {
|
||||
[TUITool makeToast:TIMCommonLocalizableString(TUIKitVideoSavedSuccess) duration:1];
|
||||
} else {
|
||||
[TUITool makeToastError:-1 msg:TIMCommonLocalizableString(TUIKitVideoSavedFailed)];
|
||||
}
|
||||
});
|
||||
}];
|
||||
}
|
||||
@end
|
||||
@@ -0,0 +1,42 @@
|
||||
|
||||
// Created by Tencent on 2023/06/09.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
/**
|
||||
* This document declares the TUIVideoMessageCell unit, which is responsible for the display of video messages.
|
||||
* The video message unit, a unit displayed when sending and receiving video messages, can display the video cover, video duration, etc. to the user,
|
||||
* and at the same time, can respond to user operations and provide an operation entry for video playback.
|
||||
* When you tap the video message, you will enter the video playback interface.
|
||||
*/
|
||||
#import <TIMCommon/TUIMessageCell.h>
|
||||
#import <TIMCommon/TUIMessageCell_Minimalist.h>
|
||||
#import "TUIVideoMessageCellData.h"
|
||||
/**
|
||||
* 【Module name】TUIVideoMessageCell_Minimalist
|
||||
* 【Function description】 Video message unit
|
||||
* - The video message unit provides the function of extracting and displaying thumbnails of video messages, and can display the video length and video
|
||||
* download/upload progress.
|
||||
* - At the same time, the network acquisition and local acquisition of video messages (if it exists in the local cache) are integrated in the message unit.
|
||||
*/
|
||||
@interface TUIVideoMessageCell_Minimalist : TUIMessageCell_Minimalist
|
||||
|
||||
/**
|
||||
* Video thumbnail
|
||||
* Display the thumbnail of the video when it is not playing, so that users can get general information about the video without playing the video.
|
||||
*/
|
||||
@property(nonatomic, strong) UIImageView *thumb;
|
||||
|
||||
/**
|
||||
* Play icon, that is, the "arrow icon" displayed in the UI.
|
||||
*/
|
||||
@property(nonatomic, strong) UIImageView *play;
|
||||
|
||||
/**
|
||||
* Label for displaying video doadloading/uploading progress
|
||||
*
|
||||
*/
|
||||
@property(nonatomic, strong) UILabel *progress;
|
||||
|
||||
@property TUIVideoMessageCellData *videoData;
|
||||
|
||||
- (void)fillWithData:(TUIVideoMessageCellData *)data;
|
||||
@end
|
||||
@@ -0,0 +1,288 @@
|
||||
//
|
||||
// TUIVideoMessageCell.m
|
||||
// UIKit
|
||||
//
|
||||
// Created by annidyfeng on 2019/5/30.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
//
|
||||
|
||||
#import "TUIVideoMessageCell_Minimalist.h"
|
||||
#import <TIMCommon/TIMDefine.h>
|
||||
#import "TUIMessageProgressManager.h"
|
||||
#import "TUICircleLodingView.h"
|
||||
|
||||
@interface TUIVideoMessageCell_Minimalist () <TUIMessageProgressManagerDelegate>
|
||||
|
||||
@property(nonatomic, strong) UIView *animateHighlightView;
|
||||
|
||||
@property(nonatomic, strong) TUICircleLodingView *animateCircleView;
|
||||
|
||||
@end
|
||||
|
||||
@implementation TUIVideoMessageCell_Minimalist
|
||||
|
||||
- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier {
|
||||
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
|
||||
if (self) {
|
||||
_thumb = [[UIImageView alloc] init];
|
||||
_thumb.layer.cornerRadius = 5.0;
|
||||
[_thumb.layer setMasksToBounds:YES];
|
||||
_thumb.contentMode = UIViewContentModeScaleAspectFit;
|
||||
_thumb.backgroundColor = [UIColor clearColor];
|
||||
[self.container addSubview:_thumb];
|
||||
_thumb.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
|
||||
|
||||
CGSize playSize = TVideoMessageCell_Play_Size;
|
||||
_play = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, playSize.width, playSize.height)];
|
||||
_play.contentMode = UIViewContentModeScaleAspectFit;
|
||||
_play.image = [[TUIImageCache sharedInstance] getResourceFromCache:TUIChatImagePath(@"play_normal")];
|
||||
[_thumb addSubview:_play];
|
||||
|
||||
_animateCircleView = [[TUICircleLodingView alloc] initWithFrame:CGRectMake(0, 0, kScale390(40), kScale390(40))];
|
||||
_animateCircleView.progress = 0;
|
||||
[_thumb addSubview:_animateCircleView];
|
||||
|
||||
_progress = [[UILabel alloc] init];
|
||||
_progress.textColor = [UIColor whiteColor];
|
||||
_progress.font = [UIFont systemFontOfSize:15];
|
||||
_progress.textAlignment = NSTextAlignmentCenter;
|
||||
_progress.layer.cornerRadius = 5.0;
|
||||
_progress.hidden = YES;
|
||||
_progress.backgroundColor = TVideoMessageCell_Progress_Color;
|
||||
[_progress.layer setMasksToBounds:YES];
|
||||
[self.container addSubview:_progress];
|
||||
_progress.mm_fill();
|
||||
_progress.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
|
||||
|
||||
self.msgTimeLabel.textColor = RGB(255, 255, 255);
|
||||
[TUIMessageProgressManager.shareManager addDelegate:self];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)fillWithData:(TUIVideoMessageCellData *)data;
|
||||
{
|
||||
// set data
|
||||
[super fillWithData:data];
|
||||
self.videoData = data;
|
||||
_thumb.image = nil;
|
||||
BOOL hasRiskContent = self.messageData.innerMessage.hasRiskContent;
|
||||
if (hasRiskContent) {
|
||||
self.thumb.image = TIMCommonBundleThemeImage(@"", @"icon_security_strike");
|
||||
self.thumb.contentMode = UIViewContentModeScaleAspectFill;
|
||||
self.play.hidden = YES;
|
||||
self.indicator.hidden = YES;
|
||||
self.animateCircleView.hidden = YES;
|
||||
return;
|
||||
}
|
||||
if (data.thumbImage == nil) {
|
||||
[data downloadThumb];
|
||||
}
|
||||
|
||||
if (data.isPlaceHolderCellData) {
|
||||
//show placeHolder
|
||||
_thumb.backgroundColor = [UIColor grayColor];
|
||||
_animateCircleView.progress = (data.videoTranscodingProgress *100);
|
||||
self.play.hidden = YES;
|
||||
self.indicator.hidden = YES;
|
||||
self.animateCircleView.hidden = NO;
|
||||
@weakify(self);
|
||||
[[RACObserve(data, videoTranscodingProgress) takeUntil:self.rac_prepareForReuseSignal] subscribeNext:^(NSNumber *x) {
|
||||
@strongify(self);
|
||||
double progress = [x doubleValue];
|
||||
self.animateCircleView.progress = (progress *100);
|
||||
}];
|
||||
if (data.thumbImage) {
|
||||
self.thumb.image = data.thumbImage;
|
||||
}
|
||||
// tell constraints they need updating
|
||||
[self setNeedsUpdateConstraints];
|
||||
|
||||
// update constraints now so we can animate the change
|
||||
[self updateConstraintsIfNeeded];
|
||||
|
||||
[self layoutIfNeeded];
|
||||
|
||||
return;
|
||||
}
|
||||
else {
|
||||
self.animateCircleView.hidden = YES;
|
||||
}
|
||||
|
||||
@weakify(self);
|
||||
[[RACObserve(data, thumbImage) takeUntil:self.rac_prepareForReuseSignal] subscribeNext:^(UIImage *thumbImage) {
|
||||
@strongify(self);
|
||||
if (thumbImage) {
|
||||
self.thumb.image = thumbImage;
|
||||
}
|
||||
}];
|
||||
|
||||
if (data.direction == MsgDirectionIncoming) {
|
||||
[[[RACObserve(data, thumbProgress) takeUntil:self.rac_prepareForReuseSignal] distinctUntilChanged] subscribeNext:^(NSNumber *x) {
|
||||
@strongify(self);
|
||||
int progress = [x intValue];
|
||||
self.progress.text = [NSString stringWithFormat:@"%d%%", progress];
|
||||
self.progress.hidden = (progress >= 100 || progress == 0);
|
||||
self.play.hidden = !self.progress.hidden;
|
||||
}];
|
||||
} else {
|
||||
[[[RACObserve(data, uploadProgress) takeUntil:self.rac_prepareForReuseSignal] distinctUntilChanged] subscribeNext:^(NSNumber *x) {
|
||||
@strongify(self);
|
||||
self.play.hidden = !self.progress.hidden;
|
||||
}];
|
||||
}
|
||||
// tell constraints they need updating
|
||||
[self setNeedsUpdateConstraints];
|
||||
|
||||
// update constraints now so we can animate the change
|
||||
[self updateConstraintsIfNeeded];
|
||||
|
||||
[self layoutIfNeeded];
|
||||
|
||||
}
|
||||
+ (BOOL)requiresConstraintBasedLayout {
|
||||
return YES;
|
||||
}
|
||||
|
||||
// this is Apple's recommended place for adding/updating constraints
|
||||
- (void)updateConstraints {
|
||||
|
||||
[super updateConstraints];
|
||||
BOOL hasRiskContent = self.messageData.innerMessage.hasRiskContent;
|
||||
if (hasRiskContent ) {
|
||||
[self.thumb mas_remakeConstraints:^(MASConstraintMaker *make) {
|
||||
make.top.mas_equalTo(self.container).mas_offset(12);
|
||||
make.size.mas_equalTo(CGSizeMake(150, 150));
|
||||
make.centerX.mas_equalTo(self.container);
|
||||
}];
|
||||
}
|
||||
else {
|
||||
[self.thumb mas_remakeConstraints:^(MASConstraintMaker *make) {
|
||||
make.height.mas_equalTo(self.container.mas_height);
|
||||
make.width.mas_equalTo(self.container);
|
||||
make.leading.mas_equalTo(self.container.mas_leading);
|
||||
make.top.mas_equalTo(self.container);
|
||||
}];
|
||||
}
|
||||
|
||||
|
||||
[self.play mas_remakeConstraints:^(MASConstraintMaker *make) {
|
||||
make.size.mas_equalTo(TVideoMessageCell_Play_Size);
|
||||
make.center.mas_equalTo(self.thumb);
|
||||
}];
|
||||
|
||||
[self.msgTimeLabel mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.width.mas_equalTo(38);
|
||||
make.height.mas_equalTo(self.messageData.msgStatusSize.height);
|
||||
make.bottom.mas_equalTo(self.container).mas_offset(-kScale390(9));
|
||||
make.trailing.mas_equalTo(self.container).mas_offset(-kScale390(8));
|
||||
}];
|
||||
|
||||
[self.msgStatusView mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.width.mas_equalTo(16);
|
||||
make.height.mas_equalTo(self.messageData.msgStatusSize.height);
|
||||
make.bottom.mas_equalTo(self.msgTimeLabel);
|
||||
make.trailing.mas_equalTo(self.msgTimeLabel.mas_leading);
|
||||
}];
|
||||
|
||||
[self.animateCircleView mas_remakeConstraints:^(MASConstraintMaker *make) {
|
||||
make.center.mas_equalTo(self.thumb);
|
||||
make.size.mas_equalTo(CGSizeMake(kScale390(40), kScale390(40)));
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)layoutSubviews {
|
||||
[super layoutSubviews];
|
||||
}
|
||||
|
||||
- (void)highlightWhenMatchKeyword:(NSString *)keyword {
|
||||
if (keyword) {
|
||||
if (self.highlightAnimating) {
|
||||
return;
|
||||
}
|
||||
[self animate:3];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)animate:(int)times {
|
||||
times--;
|
||||
if (times < 0) {
|
||||
[self.animateHighlightView removeFromSuperview];
|
||||
self.highlightAnimating = NO;
|
||||
return;
|
||||
}
|
||||
self.highlightAnimating = YES;
|
||||
self.animateHighlightView.frame = self.container.bounds;
|
||||
self.animateHighlightView.alpha = 0.1;
|
||||
[self.container addSubview:self.animateHighlightView];
|
||||
[UIView animateWithDuration:0.25
|
||||
animations:^{
|
||||
self.animateHighlightView.alpha = 0.5;
|
||||
}
|
||||
completion:^(BOOL finished) {
|
||||
[UIView animateWithDuration:0.25
|
||||
animations:^{
|
||||
self.animateHighlightView.alpha = 0.1;
|
||||
}
|
||||
completion:^(BOOL finished) {
|
||||
if (!self.videoData.highlightKeyword) {
|
||||
[self animate:0];
|
||||
return;
|
||||
}
|
||||
[self animate:times];
|
||||
}];
|
||||
}];
|
||||
}
|
||||
|
||||
- (UIView *)animateHighlightView {
|
||||
if (_animateHighlightView == nil) {
|
||||
_animateHighlightView = [[UIView alloc] init];
|
||||
_animateHighlightView.backgroundColor = [UIColor orangeColor];
|
||||
}
|
||||
return _animateHighlightView;
|
||||
}
|
||||
#pragma mark - TUIMessageProgressManagerDelegate
|
||||
|
||||
- (void)onUploadProgress:(NSString *)msgID progress:(NSInteger)progress {
|
||||
if (![msgID isEqualToString:self.videoData.msgID]) {
|
||||
return;
|
||||
}
|
||||
if (self.videoData.direction == MsgDirectionOutgoing) {
|
||||
self.videoData.uploadProgress = progress;
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - TUIMessageCellProtocol
|
||||
+ (CGSize)getContentSize:(TUIMessageCellData *)data {
|
||||
NSAssert([data isKindOfClass:TUIVideoMessageCellData.class], @"data must be kind of TUIVideoMessageCellData");
|
||||
TUIVideoMessageCellData *videoCellData = (TUIVideoMessageCellData *)data;
|
||||
BOOL hasRiskContent = videoCellData.innerMessage.hasRiskContent;
|
||||
if (hasRiskContent) {
|
||||
return CGSizeMake(150, 150);
|
||||
}
|
||||
|
||||
CGSize size = CGSizeZero;
|
||||
BOOL isDir = NO;
|
||||
if (![videoCellData.snapshotPath isEqualToString:@""] && [[NSFileManager defaultManager] fileExistsAtPath:videoCellData.snapshotPath isDirectory:&isDir]) {
|
||||
if (!isDir) {
|
||||
size = [UIImage imageWithContentsOfFile:videoCellData.snapshotPath].size;
|
||||
}
|
||||
} else {
|
||||
size = videoCellData.snapshotItem.size;
|
||||
}
|
||||
if (CGSizeEqualToSize(size, CGSizeZero)) {
|
||||
return size;
|
||||
}
|
||||
CGFloat widthMax = kScale390(250);
|
||||
CGFloat heightMax = kScale390(250);
|
||||
if (size.height > size.width) {
|
||||
size.width = size.width / size.height * heightMax;
|
||||
size.height = heightMax;
|
||||
} else {
|
||||
size.height = size.height / size.width * widthMax;
|
||||
size.width = widthMax;
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,48 @@
|
||||
|
||||
// Created by Tencent on 2023/06/09.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
/**
|
||||
|
||||
* This file declares the TUIVoiceMessageCell class, which is responsible for implementing the display of voice messages.
|
||||
* Voice messages, i.e. message units displayed after voice is sent/received. TUIKit displays it as a message with a "sound wave" icon in a bubble by default.
|
||||
* The voice message unit is also responsible for responding to the user's operation and playing the corresponding audio information when the user clicks.
|
||||
*/
|
||||
#import <TIMCommon/TUIBubbleMessageCell_Minimalist.h>
|
||||
#import "TUIVoiceMessageCellData.h"
|
||||
@import AVFoundation;
|
||||
|
||||
/**
|
||||
*
|
||||
* 【Module name】 TUIVoiceMessageCell_Minimalist
|
||||
* 【Function description】 Voice message unit
|
||||
* - Voice messages, i.e. message units displayed after voice is sent/received. TUIKit displays it as a message with a "sound wave" icon in a bubble by
|
||||
* default.
|
||||
* - The voice message unit provides the display and playback functions of voice messages.
|
||||
* - The TUIVoiceMessageCellData in the voice message unit integrates and calls the voice download and acquisition of the IM SDK, and handles the related
|
||||
* business logic.
|
||||
* - This class inherits from TUIBubbleMessageCell to implement bubble messages. You can implement custom bubbles by referring to this inheritance
|
||||
* relationship.
|
||||
*/
|
||||
@interface TUIVoiceMessageCell_Minimalist : TUIBubbleMessageCell_Minimalist
|
||||
|
||||
@property(nonatomic, strong) UIImageView *voicePlay;
|
||||
|
||||
/**
|
||||
* Voice icon
|
||||
* It is used to display the voice "sound wave" icon, and at the same time realize the animation effect of the voice when it is playing.
|
||||
*/
|
||||
@property(nonatomic, strong) NSMutableArray *voiceAnimations;
|
||||
|
||||
/**
|
||||
* Label for displays video duration
|
||||
* Used to display the duration of the speech outside the bubble, the default value is an integer and the unit is seconds.
|
||||
*/
|
||||
@property(nonatomic, strong) UILabel *duration;
|
||||
|
||||
@property(nonatomic, strong) UIImageView *voiceReadPoint;
|
||||
|
||||
@property TUIVoiceMessageCellData *voiceData;
|
||||
|
||||
- (void)fillWithData:(TUIVoiceMessageCellData *)data;
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,226 @@
|
||||
//
|
||||
// TUIVoiceMessageCell.m
|
||||
// UIKit
|
||||
//
|
||||
// Created by annidyfeng on 2019/5/30.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
//
|
||||
|
||||
#import "TUIVoiceMessageCell_Minimalist.h"
|
||||
#import <TIMCommon/TIMDefine.h>
|
||||
#import <TUICore/TUIThemeManager.h>
|
||||
#import <TUICore/TUICore.h>
|
||||
|
||||
@interface TUIVoiceMessageCell_Minimalist ()
|
||||
|
||||
@end
|
||||
|
||||
@implementation TUIVoiceMessageCell_Minimalist
|
||||
|
||||
- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier {
|
||||
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
|
||||
if (self) {
|
||||
_voicePlay = [[UIImageView alloc] init];
|
||||
_voicePlay.image = [[TUIImageCache sharedInstance] getResourceFromCache:TUIChatImagePath_Minimalist(@"voice_play")];
|
||||
[self.bubbleView addSubview:_voicePlay];
|
||||
|
||||
self.voiceAnimations = [NSMutableArray array];
|
||||
for (int i = 0; i < 6; ++i) {
|
||||
UIImageView *animation = [[UIImageView alloc] init];
|
||||
animation.image = [[TUIImageCache sharedInstance] getResourceFromCache:TUIChatImagePath_Minimalist(@"voice_play_animation")];
|
||||
[self.bubbleView addSubview:animation];
|
||||
[self.voiceAnimations addObject:animation];
|
||||
}
|
||||
|
||||
_duration = [[UILabel alloc] init];
|
||||
_duration.font = [UIFont boldSystemFontOfSize:14];
|
||||
_duration.rtlAlignment = TUITextRTLAlignmentTrailing;
|
||||
[self.bubbleView addSubview:_duration];
|
||||
|
||||
_voiceReadPoint = [[UIImageView alloc] init];
|
||||
_voiceReadPoint.backgroundColor = [UIColor redColor];
|
||||
_voiceReadPoint.frame = CGRectMake(0, 0, 5, 5);
|
||||
_voiceReadPoint.hidden = YES;
|
||||
[_voiceReadPoint.layer setCornerRadius:_voiceReadPoint.frame.size.width / 2];
|
||||
[_voiceReadPoint.layer setMasksToBounds:YES];
|
||||
[self.bubbleView addSubview:_voiceReadPoint];
|
||||
|
||||
self.bottomContainer = [[UIView alloc] init];
|
||||
[self.contentView addSubview:self.bottomContainer];
|
||||
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)prepareForReuse {
|
||||
[super prepareForReuse];
|
||||
for (UIView *view in self.bottomContainer.subviews) {
|
||||
[view removeFromSuperview];
|
||||
}
|
||||
}
|
||||
|
||||
// Override
|
||||
- (void)notifyBottomContainerReadyOfData:(TUIMessageCellData *)cellData {
|
||||
NSDictionary *param = @{TUICore_TUIChatExtension_BottomContainer_CellData : self.voiceData};
|
||||
[TUICore raiseExtension:TUICore_TUIChatExtension_BottomContainer_MinimalistExtensionID parentView:self.bottomContainer param:param];
|
||||
}
|
||||
|
||||
- (void)fillWithData:(TUIVoiceMessageCellData *)data;
|
||||
{
|
||||
// set data
|
||||
[super fillWithData:data];
|
||||
self.voiceData = data;
|
||||
|
||||
if (data.duration > 0) {
|
||||
_duration.text = [NSString stringWithFormat:@"%d:%.2d", (int)data.duration / 60, (int)data.duration % 60];
|
||||
} else {
|
||||
_duration.text = @"0:01";
|
||||
}
|
||||
|
||||
self.bottomContainer.hidden = CGSizeEqualToSize(data.bottomContainerSize, CGSizeZero);
|
||||
|
||||
if (self.voiceData.innerMessage.localCustomInt == 0 && self.voiceData.direction == MsgDirectionIncoming) self.voiceReadPoint.hidden = NO;
|
||||
|
||||
@weakify(self);
|
||||
[[RACObserve(data, isPlaying) takeUntil:self.rac_prepareForReuseSignal] subscribeNext:^(NSNumber *x) {
|
||||
@strongify(self);
|
||||
if ([x boolValue]) {
|
||||
[self startAnimating];
|
||||
} else {
|
||||
[self stopAnimating];
|
||||
if (data.duration > 0) {
|
||||
self.duration.text = [NSString stringWithFormat:@"%d:%.2d", (int)data.duration / 60, (int)data.duration % 60];
|
||||
} else {
|
||||
self.duration.text = @"0:01";
|
||||
}
|
||||
}
|
||||
}];
|
||||
|
||||
[[RACObserve(data, currentTime) takeUntil:self.rac_prepareForReuseSignal] subscribeNext:^(NSNumber *time) {
|
||||
@strongify(self);
|
||||
if (!data.isPlaying) {
|
||||
return;
|
||||
}
|
||||
int min = (int)data.currentTime / 60;
|
||||
int sec = (int)data.currentTime % 60;
|
||||
NSString *forMatStr = [NSString stringWithFormat:@"%d:%.2d", min, sec];
|
||||
self.duration.text = [NSString stringWithFormat:@"%d:%.2d", (int)data.currentTime / 60, (int)data.currentTime % 60];
|
||||
}];
|
||||
|
||||
|
||||
// tell constraints they need updating
|
||||
[self setNeedsUpdateConstraints];
|
||||
|
||||
// update constraints now so we can animate the change
|
||||
[self updateConstraintsIfNeeded];
|
||||
|
||||
[self layoutIfNeeded];
|
||||
}
|
||||
|
||||
+ (BOOL)requiresConstraintBasedLayout {
|
||||
return YES;
|
||||
}
|
||||
|
||||
// this is Apple's recommended place for adding/updating constraints
|
||||
- (void)updateConstraints {
|
||||
|
||||
[super updateConstraints];
|
||||
|
||||
[self.voicePlay mas_remakeConstraints:^(MASConstraintMaker *make) {
|
||||
make.top.mas_equalTo(12);
|
||||
make.leading.mas_equalTo(kScale390(16));
|
||||
make.width.mas_equalTo(11);
|
||||
make.height.mas_equalTo(13);
|
||||
}];
|
||||
|
||||
CGFloat animationStartX = kScale390(35);
|
||||
for (int i = 0; i < self.voiceAnimations.count; ++i) {
|
||||
UIImageView *animation = self.voiceAnimations[i];
|
||||
[animation mas_remakeConstraints:^(MASConstraintMaker *make) {
|
||||
make.leading.mas_equalTo(self.bubbleView).mas_offset(animationStartX + kScale390(25) * i);
|
||||
make.top.mas_equalTo(self.bubbleView).mas_offset(self.voiceData.voiceTop);
|
||||
make.width.height.mas_equalTo(_voiceData.voiceHeight);
|
||||
}];
|
||||
}
|
||||
|
||||
[self.duration mas_remakeConstraints:^(MASConstraintMaker *make) {
|
||||
make.width.mas_greaterThanOrEqualTo(kScale390(34));
|
||||
make.height.mas_greaterThanOrEqualTo(17);
|
||||
make.top.mas_equalTo(self.voiceData.voiceTop + 2);
|
||||
make.trailing.mas_equalTo(self.container).mas_offset(- kScale390(14));
|
||||
}];
|
||||
|
||||
if (self.voiceData.direction == MsgDirectionOutgoing) {
|
||||
self.voiceReadPoint.hidden = YES;
|
||||
}
|
||||
else {
|
||||
[self.voiceReadPoint mas_remakeConstraints:^(MASConstraintMaker *make) {
|
||||
make.top.mas_equalTo(self.bubbleView);
|
||||
make.leading.mas_equalTo(self.bubbleView.mas_trailing).mas_offset(1);
|
||||
make.size.mas_equalTo(CGSizeMake(5, 5));
|
||||
}];
|
||||
}
|
||||
[self layoutBottomContainer];
|
||||
|
||||
}
|
||||
- (void)layoutBottomContainer {
|
||||
if (CGSizeEqualToSize(self.voiceData.bottomContainerSize, CGSizeZero)) {
|
||||
return;
|
||||
}
|
||||
|
||||
CGSize size = self.voiceData.bottomContainerSize;
|
||||
|
||||
[self.bottomContainer mas_remakeConstraints:^(MASConstraintMaker *make) {
|
||||
if (self.voiceData.direction == MsgDirectionIncoming) {
|
||||
make.leading.mas_equalTo(self.container.mas_leading);
|
||||
} else {
|
||||
make.trailing.mas_equalTo(self.container.mas_trailing);
|
||||
}
|
||||
make.top.mas_equalTo(self.container.mas_bottom).offset(self.messageData.messageContainerAppendSize.height + 6);
|
||||
make.size.mas_equalTo(size);
|
||||
}];
|
||||
|
||||
CGFloat repliesBtnTextWidth = self.messageModifyRepliesButton.frame.size.width;
|
||||
if (!self.messageModifyRepliesButton.hidden) {
|
||||
UIImageView *lastAvatarImageView = self.replyAvatarImageViews.lastObject;
|
||||
[self.messageModifyRepliesButton mas_remakeConstraints:^(MASConstraintMaker *make) {
|
||||
if (self.voiceData.direction == MsgDirectionIncoming) {
|
||||
make.leading.mas_equalTo(lastAvatarImageView.mas_trailing);
|
||||
} else {
|
||||
make.trailing.mas_equalTo(self.container.mas_trailing);
|
||||
}
|
||||
make.top.mas_equalTo(self.bottomContainer.mas_bottom);
|
||||
make.size.mas_equalTo(CGSizeMake(repliesBtnTextWidth + 10, 30));
|
||||
}];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)startAnimating {
|
||||
_voicePlay.image = [[TUIImageCache sharedInstance] getResourceFromCache:TUIChatImagePath_Minimalist(@"voice_pause")];
|
||||
}
|
||||
|
||||
- (void)stopAnimating {
|
||||
_voicePlay.image = [[TUIImageCache sharedInstance] getResourceFromCache:TUIChatImagePath_Minimalist(@"voice_play")];
|
||||
}
|
||||
|
||||
- (void)layoutSubviews {
|
||||
[super layoutSubviews];
|
||||
}
|
||||
|
||||
#pragma mark - TUIMessageCellProtocol
|
||||
+ (CGFloat)getHeight:(TUIMessageCellData *)data withWidth:(CGFloat)width {
|
||||
CGFloat height = [super getHeight:data withWidth:width];
|
||||
if (data.bottomContainerSize.height > 0) {
|
||||
height += data.bottomContainerSize.height + kScale375(6);
|
||||
}
|
||||
return height;
|
||||
}
|
||||
+ (CGSize)getContentSize:(TUIMessageCellData *)data {
|
||||
NSAssert([data isKindOfClass:TUIVoiceMessageCellData.class], @"data must be kind of TUIVoiceMessageCellData");
|
||||
TUIVoiceMessageCellData *voiceCellData = (TUIVoiceMessageCellData *)data;
|
||||
|
||||
return CGSizeMake((voiceCellData.voiceHeight + kScale390(5)) * 6 + kScale390(82),
|
||||
voiceCellData.voiceHeight + voiceCellData.voiceTop * 3 + voiceCellData.msgStatusSize.height);
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,24 @@
|
||||
//
|
||||
// TUIEvaluationCell.h
|
||||
// TUIChat
|
||||
//
|
||||
// Created by xia on 2022/6/10.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
//
|
||||
|
||||
#import <TIMCommon/TUIBubbleMessageCell_Minimalist.h>
|
||||
#import "TUIEvaluationCellData.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface TUIEvaluationCell_Minimalist : TUIBubbleMessageCell_Minimalist
|
||||
|
||||
@property(nonatomic, strong) UILabel *titleLabel;
|
||||
@property(nonatomic, strong) UILabel *commentLabel;
|
||||
@property(nonatomic, strong) NSMutableArray *starImageArray;
|
||||
|
||||
- (void)fillWithData:(TUIEvaluationCellData *)data;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,140 @@
|
||||
//
|
||||
// TUIEvaluationCell_Minimalist.m
|
||||
// TUIChat
|
||||
//
|
||||
// Created by xia on 2022/6/10.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
//
|
||||
|
||||
#import "TUIEvaluationCell_Minimalist.h"
|
||||
#import <TUICore/TUIGlobalization.h>
|
||||
#import <TUICore/TUIThemeManager.h>
|
||||
|
||||
@implementation TUIEvaluationCell_Minimalist
|
||||
|
||||
- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier {
|
||||
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
|
||||
if (self) {
|
||||
_titleLabel = [[UILabel alloc] init];
|
||||
_titleLabel.font = [UIFont systemFontOfSize:15];
|
||||
_titleLabel.numberOfLines = 1;
|
||||
_titleLabel.lineBreakMode = NSLineBreakByTruncatingTail;
|
||||
_titleLabel.textColor = TUIChatDynamicColor(@"chat_text_message_receive_text_color", @"#000000");
|
||||
[self.container addSubview:_titleLabel];
|
||||
|
||||
for (int i = 0; i < 5; i++) {
|
||||
UIImageView *imageView = [[UIImageView alloc] init];
|
||||
[imageView setImage:TUIChatBundleThemeImage(@"chat_custom_evaluation_message_img", @"message_custom_evaluation")];
|
||||
[self.container addSubview:imageView];
|
||||
[self.starImageArray addObject:imageView];
|
||||
}
|
||||
|
||||
_commentLabel = [[UILabel alloc] init];
|
||||
_commentLabel.font = [UIFont systemFontOfSize:15];
|
||||
_commentLabel.numberOfLines = 0;
|
||||
_commentLabel.textColor = TUIChatDynamicColor(@"chat_custom_evaluation_message_desc_color", @"#000000");
|
||||
[self.container addSubview:_commentLabel];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)fillWithData:(TUIEvaluationCellData *)data {
|
||||
[super fillWithData:data];
|
||||
|
||||
self.titleLabel.text = data.desc;
|
||||
self.commentLabel.text = data.comment;
|
||||
|
||||
// Configure all StarViews to avoid UI cache clutter
|
||||
for (int i = 0; i < self.starImageArray.count; i++) {
|
||||
UIImageView *starView = [self.starImageArray objectAtIndex:i];
|
||||
starView.hidden = (i >= data.score);
|
||||
}
|
||||
// tell constraints they need updating
|
||||
[self setNeedsUpdateConstraints];
|
||||
|
||||
// update constraints now so we can animate the change
|
||||
[self updateConstraintsIfNeeded];
|
||||
|
||||
[self layoutIfNeeded];
|
||||
}
|
||||
|
||||
+ (BOOL)requiresConstraintBasedLayout {
|
||||
return YES;
|
||||
}
|
||||
|
||||
// this is Apple's recommended place for adding/updating constraints
|
||||
- (void)updateConstraints {
|
||||
|
||||
[super updateConstraints];
|
||||
|
||||
[self.titleLabel mas_remakeConstraints:^(MASConstraintMaker *make) {
|
||||
make.top.mas_equalTo(10);
|
||||
make.leading.mas_equalTo(10);
|
||||
make.width.mas_equalTo(225);
|
||||
make.height.mas_equalTo(18);
|
||||
}];
|
||||
|
||||
UIImageView *leftView = nil;
|
||||
for (UIImageView *starView in self.starImageArray) {
|
||||
if (leftView == nil) {
|
||||
[starView mas_remakeConstraints:^(MASConstraintMaker *make) {
|
||||
make.leading.mas_equalTo(10);
|
||||
make.top.mas_equalTo(self.titleLabel.mas_bottom).mas_offset(6);
|
||||
make.width.mas_equalTo(30);
|
||||
make.height.mas_equalTo(30);
|
||||
}];
|
||||
} else {
|
||||
[starView mas_remakeConstraints:^(MASConstraintMaker *make) {
|
||||
make.leading.mas_equalTo(leftView.mas_trailing);
|
||||
make.top.mas_equalTo(self.titleLabel.mas_bottom).mas_offset(6);
|
||||
make.width.mas_equalTo(30);
|
||||
make.height.mas_equalTo(30);
|
||||
}];
|
||||
}
|
||||
leftView = starView;
|
||||
}
|
||||
|
||||
UIImageView *starView = self.starImageArray.firstObject;
|
||||
|
||||
self.commentLabel.hidden = self.commentLabel.text.length == 0;
|
||||
if (self.commentLabel.text.length > 0) {
|
||||
CGRect rect = [self.commentLabel.text boundingRectWithSize:CGSizeMake(225, MAXFLOAT)
|
||||
options:NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading
|
||||
attributes:@{NSFontAttributeName : [UIFont systemFontOfSize:15]}
|
||||
context:nil];
|
||||
CGSize size = CGSizeMake(225, ceilf(rect.size.height));
|
||||
[self.commentLabel mas_remakeConstraints:^(MASConstraintMaker *make) {
|
||||
make.top.mas_equalTo(starView.mas_bottom).mas_offset(6);
|
||||
make.leading.mas_equalTo(10);
|
||||
make.width.mas_equalTo(size.width);
|
||||
make.height.mas_equalTo(size.height);
|
||||
}];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)layoutSubviews {
|
||||
[super layoutSubviews];
|
||||
}
|
||||
|
||||
- (NSMutableArray *)starImageArray {
|
||||
if (!_starImageArray) {
|
||||
_starImageArray = [[NSMutableArray alloc] init];
|
||||
}
|
||||
return _starImageArray;
|
||||
}
|
||||
|
||||
#pragma mark - TUIMessageCellProtocol
|
||||
+ (CGSize)getContentSize:(TUIMessageCellData *)data {
|
||||
NSAssert([data isKindOfClass:TUIEvaluationCellData.class], @"data must be kind of TUIEvaluationCellData");
|
||||
TUIEvaluationCellData *evaluationCellData = (TUIEvaluationCellData *)data;
|
||||
|
||||
CGRect rect = [evaluationCellData.comment boundingRectWithSize:CGSizeMake(215, MAXFLOAT)
|
||||
options:NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading
|
||||
attributes:@{NSFontAttributeName : [UIFont systemFontOfSize:15]}
|
||||
context:nil];
|
||||
CGSize size = CGSizeMake(245, ceilf(rect.size.height));
|
||||
size.height += evaluationCellData.comment.length > 0 ? 88 : 50;
|
||||
return size;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,23 @@
|
||||
//
|
||||
// MyCustomCell.h
|
||||
// TUIKitDemo
|
||||
//
|
||||
// Created by annidyfeng on 2019/6/10.
|
||||
// Copyright © 2019 Tencent. All rights reserved.
|
||||
//
|
||||
|
||||
#import <TIMCommon/TUIBubbleMessageCell_Minimalist.h>
|
||||
#import <TIMCommon/TUIMessageCell.h>
|
||||
#import "TUILinkCellData.h"
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface TUILinkCell_Minimalist : TUIBubbleMessageCell_Minimalist
|
||||
@property UILabel *myTextLabel;
|
||||
@property UILabel *myLinkLabel;
|
||||
|
||||
@property TUILinkCellData *customData;
|
||||
- (void)fillWithData:(TUILinkCellData *)data;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,93 @@
|
||||
//
|
||||
// TUILinkCell_Minimalist.m
|
||||
// TUIKitDemo
|
||||
//
|
||||
// Created by annidyfeng on 2019/6/10.
|
||||
// Copyright © 2019 Tencent. All rights reserved.
|
||||
//
|
||||
|
||||
#import "TUILinkCell_Minimalist.h"
|
||||
#import <TUICore/TUIGlobalization.h>
|
||||
#import <TUICore/TUIThemeManager.h>
|
||||
|
||||
@implementation TUILinkCell_Minimalist
|
||||
|
||||
- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier {
|
||||
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
|
||||
if (self) {
|
||||
_myTextLabel = [[UILabel alloc] init];
|
||||
_myTextLabel.numberOfLines = 0;
|
||||
_myTextLabel.font = [UIFont systemFontOfSize:15];
|
||||
_myTextLabel.textColor = TUIChatDynamicColor(@"chat_link_message_title_color", @"#000000");
|
||||
[self.container addSubview:_myTextLabel];
|
||||
|
||||
_myLinkLabel = [[UILabel alloc] initWithFrame:CGRectZero];
|
||||
_myLinkLabel.text = TIMCommonLocalizableString(TUIKitMoreLinkDetails);
|
||||
_myLinkLabel.font = [UIFont systemFontOfSize:15];
|
||||
_myLinkLabel.textColor = TUIChatDynamicColor(@"chat_link_message_subtitle_color", @"#0000FF");
|
||||
[self.container addSubview:_myLinkLabel];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)fillWithData:(TUILinkCellData *)data;
|
||||
{
|
||||
[super fillWithData:data];
|
||||
self.customData = data;
|
||||
self.myTextLabel.text = data.text;
|
||||
|
||||
// tell constraints they need updating
|
||||
[self setNeedsUpdateConstraints];
|
||||
|
||||
// update constraints now so we can animate the change
|
||||
[self updateConstraintsIfNeeded];
|
||||
|
||||
[self layoutIfNeeded];
|
||||
|
||||
}
|
||||
|
||||
+ (BOOL)requiresConstraintBasedLayout {
|
||||
return YES;
|
||||
}
|
||||
|
||||
// this is Apple's recommended place for adding/updating constraints
|
||||
- (void)updateConstraints {
|
||||
|
||||
[super updateConstraints];
|
||||
CGRect rect = [self.myTextLabel.text boundingRectWithSize:CGSizeMake(245, MAXFLOAT)
|
||||
options:NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading
|
||||
attributes:@{NSFontAttributeName : [UIFont systemFontOfSize:15]}
|
||||
context:nil];
|
||||
[self.myTextLabel mas_remakeConstraints:^(MASConstraintMaker *make) {
|
||||
make.top.mas_equalTo(10);
|
||||
make.leading.mas_equalTo(10);
|
||||
make.width.mas_equalTo(245);
|
||||
make.height.mas_equalTo(rect.size.height);
|
||||
}];
|
||||
[self.myLinkLabel sizeToFit];
|
||||
[self.myLinkLabel mas_remakeConstraints:^(MASConstraintMaker *make) {
|
||||
make.top.mas_equalTo(self.myTextLabel.mas_bottom).mas_offset(15);
|
||||
make.leading.mas_equalTo(10);
|
||||
make.width.mas_equalTo(self.myLinkLabel.frame.size.width);
|
||||
make.height.mas_equalTo(self.myLinkLabel.frame.size.height);
|
||||
}];
|
||||
}
|
||||
- (void)layoutSubviews {
|
||||
[super layoutSubviews];
|
||||
}
|
||||
|
||||
#pragma mark - TUIMessageCellProtocol
|
||||
+ (CGSize)getContentSize:(TUIMessageCellData *)data {
|
||||
NSAssert([data isKindOfClass:TUILinkCellData.class], @"data must be kind of TUILinkCellData");
|
||||
TUILinkCellData *linkCellData = (TUILinkCellData *)data;
|
||||
|
||||
CGFloat textMaxWidth = 245.f;
|
||||
CGRect rect = [linkCellData.text boundingRectWithSize:CGSizeMake(textMaxWidth, MAXFLOAT)
|
||||
options:NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading
|
||||
attributes:@{NSFontAttributeName : [UIFont systemFontOfSize:15]}
|
||||
context:nil];
|
||||
CGSize size = CGSizeMake(textMaxWidth + 15, rect.size.height + 56);
|
||||
return size;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,26 @@
|
||||
//
|
||||
// TUIOrderCell.h
|
||||
// TUIChat
|
||||
//
|
||||
// Created by xia on 2022/6/13.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
//
|
||||
|
||||
#import <TIMCommon/TUIBubbleMessageCell_Minimalist.h>
|
||||
#import "TUIOrderCellData.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface TUIOrderCell_Minimalist : TUIBubbleMessageCell_Minimalist
|
||||
|
||||
@property(nonatomic, strong) UILabel *titleLabel;
|
||||
@property(nonatomic, strong) UILabel *descLabel;
|
||||
@property(nonatomic, strong) UIImageView *iconView;
|
||||
@property(nonatomic, strong) UILabel *priceLabel;
|
||||
@property(nonatomic, strong) TUIOrderCellData *customData;
|
||||
|
||||
- (void)fillWithData:(TUIOrderCellData *)data;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,112 @@
|
||||
//
|
||||
// TUIOrderCell_Minimalist.m
|
||||
// TUIChat
|
||||
//
|
||||
// Created by xia on 2022/6/13.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
//
|
||||
|
||||
#import "TUIOrderCell_Minimalist.h"
|
||||
#import <TUICore/TUIGlobalization.h>
|
||||
#import <TUICore/TUIThemeManager.h>
|
||||
|
||||
@implementation TUIOrderCell_Minimalist
|
||||
|
||||
- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier {
|
||||
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
|
||||
if (self) {
|
||||
_titleLabel = [[UILabel alloc] init];
|
||||
_titleLabel.font = [UIFont boldSystemFontOfSize:12];
|
||||
_titleLabel.textColor = TUIChatDynamicColor(@"chat_text_message_receive_text_color", @"#000000");
|
||||
_titleLabel.lineBreakMode = NSLineBreakByTruncatingTail;
|
||||
[self.container addSubview:_titleLabel];
|
||||
|
||||
_descLabel = [[UILabel alloc] init];
|
||||
_descLabel.font = [UIFont systemFontOfSize:12];
|
||||
_descLabel.numberOfLines = 1;
|
||||
_descLabel.lineBreakMode = NSLineBreakByTruncatingTail;
|
||||
_descLabel.textColor = TUIChatDynamicColor(@"chat_custom_order_message_desc_color", @"#999999");
|
||||
[self.container addSubview:_descLabel];
|
||||
|
||||
_priceLabel = [[UILabel alloc] init];
|
||||
_priceLabel.font = [UIFont boldSystemFontOfSize:18];
|
||||
_priceLabel.lineBreakMode = NSLineBreakByTruncatingTail;
|
||||
_priceLabel.textColor = TUIChatDynamicColor(@"chat_custom_order_message_price_color", @"#FF7201");
|
||||
[self.container addSubview:_priceLabel];
|
||||
|
||||
_iconView = [[UIImageView alloc] init];
|
||||
_iconView.layer.cornerRadius = 8.0;
|
||||
_iconView.layer.masksToBounds = YES;
|
||||
[self.container addSubview:_iconView];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)fillWithData:(TUIOrderCellData *)data {
|
||||
[super fillWithData:data];
|
||||
|
||||
self.customData = data;
|
||||
self.titleLabel.text = data.title;
|
||||
self.descLabel.text = data.desc;
|
||||
self.priceLabel.text = data.price;
|
||||
if (data.imageUrl == nil) {
|
||||
[self.iconView setImage:TUIChatBundleThemeImage(@"chat_custom_order_message_img", @"message_custom_order")];
|
||||
} else {
|
||||
[self.iconView setImage:[UIImage sd_imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:data.imageUrl]]]];
|
||||
}
|
||||
// tell constraints they need updating
|
||||
[self setNeedsUpdateConstraints];
|
||||
|
||||
// update constraints now so we can animate the change
|
||||
[self updateConstraintsIfNeeded];
|
||||
|
||||
[self layoutIfNeeded];
|
||||
}
|
||||
|
||||
+ (BOOL)requiresConstraintBasedLayout {
|
||||
return YES;
|
||||
}
|
||||
|
||||
// this is Apple's recommended place for adding/updating constraints
|
||||
- (void)updateConstraints {
|
||||
|
||||
[super updateConstraints];
|
||||
|
||||
[self.iconView mas_remakeConstraints:^(MASConstraintMaker *make) {
|
||||
make.top.mas_equalTo(10);
|
||||
make.leading.mas_equalTo(12);
|
||||
make.width.mas_equalTo(60);
|
||||
make.height.mas_equalTo(60);
|
||||
}];
|
||||
[self.titleLabel mas_remakeConstraints:^(MASConstraintMaker *make) {
|
||||
make.top.mas_equalTo(10);
|
||||
make.leading.mas_equalTo(80);
|
||||
make.width.mas_equalTo(150);
|
||||
make.height.mas_equalTo(17);
|
||||
}];
|
||||
|
||||
[self.descLabel mas_remakeConstraints:^(MASConstraintMaker *make) {
|
||||
make.top.mas_equalTo(30);
|
||||
make.leading.mas_equalTo(80);
|
||||
make.width.mas_equalTo(150);
|
||||
make.height.mas_equalTo(17);
|
||||
}];
|
||||
|
||||
[self.priceLabel mas_remakeConstraints:^(MASConstraintMaker *make) {
|
||||
make.top.mas_equalTo(49);
|
||||
make.leading.mas_equalTo(80);
|
||||
make.width.mas_equalTo(150);
|
||||
make.height.mas_equalTo(25);
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)layoutSubviews {
|
||||
[super layoutSubviews];
|
||||
}
|
||||
|
||||
#pragma mark - TUIMessageCellProtocol
|
||||
+ (CGSize)getContentSize:(TUIMessageCellData *)data {
|
||||
return CGSizeMake(245, 80);
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,17 @@
|
||||
//
|
||||
// TUIFileReplyQuoteView_Minimalist.h
|
||||
// TUIChat
|
||||
//
|
||||
// Created by harvy on 2021/11/25.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
//
|
||||
|
||||
#import "TUIVoiceReplyQuoteView_Minimalist.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface TUIFileReplyQuoteView_Minimalist : TUIVoiceReplyQuoteView_Minimalist
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,22 @@
|
||||
//
|
||||
// TUIFileReplyQuoteView_Minimalist.m
|
||||
// TUIChat
|
||||
//
|
||||
// Created by harvy on 2021/11/25.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
//
|
||||
|
||||
#import "TUIFileReplyQuoteView_Minimalist.h"
|
||||
#import "TUIFileReplyQuoteViewData.h"
|
||||
|
||||
@implementation TUIFileReplyQuoteView_Minimalist
|
||||
|
||||
- (void)fillWithData:(TUIReplyQuoteViewData *)data {
|
||||
[super fillWithData:data];
|
||||
if (![data isKindOfClass:TUIFileReplyQuoteViewData.class]) {
|
||||
return;
|
||||
}
|
||||
self.textLabel.numberOfLines = 1;
|
||||
self.textLabel.lineBreakMode = NSLineBreakByTruncatingMiddle;
|
||||
}
|
||||
@end
|
||||
@@ -0,0 +1,19 @@
|
||||
//
|
||||
// TUIImageReplyQuoteView_Minimalist.h
|
||||
// TUIChat
|
||||
//
|
||||
// Created by harvy on 2021/11/25.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
//
|
||||
|
||||
#import "TUIReplyQuoteView_Minimalist.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface TUIImageReplyQuoteView_Minimalist : TUIReplyQuoteView_Minimalist
|
||||
|
||||
@property(nonatomic, strong) UIImageView *imageView;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,66 @@
|
||||
//
|
||||
// TUIImageReplyQuoteView_Minimalist.m
|
||||
// TUIChat
|
||||
//
|
||||
// Created by harvy on 2021/11/25.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
//
|
||||
|
||||
#import "TUIImageReplyQuoteView_Minimalist.h"
|
||||
#import "TUIImageReplyQuoteViewData.h"
|
||||
|
||||
@implementation TUIImageReplyQuoteView_Minimalist
|
||||
|
||||
- (instancetype)initWithFrame:(CGRect)frame {
|
||||
if (self = [super initWithFrame:frame]) {
|
||||
_imageView = [[UIImageView alloc] init];
|
||||
_imageView.frame = CGRectMake(0, 0, 60, 60);
|
||||
[self addSubview:_imageView];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)fillWithData:(TUIReplyQuoteViewData *)data {
|
||||
[super fillWithData:data];
|
||||
|
||||
if (![data isKindOfClass:TUIImageReplyQuoteViewData.class]) {
|
||||
return;
|
||||
}
|
||||
TUIImageReplyQuoteViewData *myData = (TUIImageReplyQuoteViewData *)data;
|
||||
self.imageView.image = myData.image;
|
||||
if (myData.image == nil && myData.imageStatus != TUIImageReplyQuoteStatusDownloading) {
|
||||
[myData downloadImage];
|
||||
}
|
||||
// tell constraints they need updating
|
||||
[self setNeedsUpdateConstraints];
|
||||
|
||||
// update constraints now so we can animate the change
|
||||
[self updateConstraintsIfNeeded];
|
||||
|
||||
[self layoutIfNeeded];
|
||||
|
||||
}
|
||||
+ (BOOL)requiresConstraintBasedLayout {
|
||||
return YES;
|
||||
}
|
||||
// this is Apple's recommended place for adding/updating constraints
|
||||
- (void)updateConstraints {
|
||||
|
||||
[super updateConstraints];
|
||||
TUIImageReplyQuoteViewData *myData = (TUIImageReplyQuoteViewData *)self.data;
|
||||
|
||||
[self.imageView mas_remakeConstraints:^(MASConstraintMaker *make) {
|
||||
make.leading.mas_equalTo(self);
|
||||
make.top.mas_equalTo(self);
|
||||
make.size.mas_equalTo(myData.imageSize);
|
||||
}];
|
||||
|
||||
}
|
||||
|
||||
- (void)reset {
|
||||
[super reset];
|
||||
self.imageView.image = nil;
|
||||
self.imageView.frame = CGRectMake(self.imageView.frame.origin.x, self.imageView.frame.origin.y, 60, 60);
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,19 @@
|
||||
//
|
||||
// TUIMergeReplyQuoteView.h
|
||||
// TUIChat
|
||||
//
|
||||
// Created by harvy on 2021/11/25.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
//
|
||||
|
||||
#import "TUIReplyQuoteView_Minimalist.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface TUIMergeReplyQuoteView_Minimalist : TUIReplyQuoteView_Minimalist
|
||||
|
||||
@property(nonatomic, strong) UILabel *titleLabel;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,74 @@
|
||||
//
|
||||
// TUIMergeReplyQuoteView_Minimalist.m
|
||||
// TUIChat
|
||||
//
|
||||
// Created by harvy on 2021/11/25.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
//
|
||||
|
||||
#import "TUIMergeReplyQuoteView_Minimalist.h"
|
||||
#import <TUICore/TUIDarkModel.h>
|
||||
#import <TUICore/UIView+TUILayout.h>
|
||||
#import "TUIMergeReplyQuoteViewData.h"
|
||||
|
||||
@implementation TUIMergeReplyQuoteView_Minimalist
|
||||
|
||||
- (instancetype)initWithFrame:(CGRect)frame {
|
||||
if (self = [super initWithFrame:frame]) {
|
||||
_titleLabel = [[UILabel alloc] init];
|
||||
_titleLabel.text = @"title";
|
||||
_titleLabel.font = [UIFont systemFontOfSize:10.0];
|
||||
_titleLabel.textColor = [UIColor d_systemGrayColor];
|
||||
_titleLabel.numberOfLines = 1;
|
||||
|
||||
[self addSubview:_titleLabel];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)layoutSubviews {
|
||||
[super layoutSubviews];
|
||||
}
|
||||
|
||||
- (void)fillWithData:(TUIReplyQuoteViewData *)data {
|
||||
[super fillWithData:data];
|
||||
|
||||
if (![data isKindOfClass:TUIMergeReplyQuoteViewData.class]) {
|
||||
return;
|
||||
}
|
||||
|
||||
TUIMergeReplyQuoteViewData *myData = (TUIMergeReplyQuoteViewData *)data;
|
||||
self.titleLabel.text = myData.title;
|
||||
|
||||
// tell constraints they need updating
|
||||
[self setNeedsUpdateConstraints];
|
||||
|
||||
// update constraints now so we can animate the change
|
||||
[self updateConstraintsIfNeeded];
|
||||
|
||||
[self layoutIfNeeded];
|
||||
}
|
||||
|
||||
+ (BOOL)requiresConstraintBasedLayout {
|
||||
return YES;
|
||||
}
|
||||
// this is Apple's recommended place for adding/updating constraints
|
||||
- (void)updateConstraints {
|
||||
|
||||
[super updateConstraints];
|
||||
|
||||
[self.titleLabel sizeToFit];
|
||||
[self.titleLabel mas_remakeConstraints:^(MASConstraintMaker *make) {
|
||||
make.leading.mas_equalTo(self);
|
||||
make.top.mas_equalTo(self);
|
||||
make.trailing.mas_equalTo(self.mas_trailing);
|
||||
make.height.mas_equalTo(self.titleLabel.font.lineHeight);
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)reset {
|
||||
[super reset];
|
||||
self.titleLabel.text = @"";
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,45 @@
|
||||
//
|
||||
// TUIReferenceMessageCell_Minimalist.h
|
||||
// TUIChat
|
||||
//
|
||||
// Created by wyl on 2022/5/24.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
//
|
||||
|
||||
#import <TIMCommon/TUIBubbleMessageCell_Minimalist.h>
|
||||
#import "TUIChatDefine.h"
|
||||
|
||||
@class TUIReplyMessageCellData;
|
||||
@class TUIReplyQuoteViewData;
|
||||
@class TUIImageVideoReplyQuoteViewData;
|
||||
@class TUIVoiceFileReplyQuoteViewData;
|
||||
@class TUIMergeReplyQuoteViewData;
|
||||
@class TUIReferenceMessageCellData;
|
||||
@class TUITextView;
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface TUIReferenceMessageCell_Minimalist : TUIBubbleMessageCell_Minimalist
|
||||
/**
|
||||
*
|
||||
* Border of quote view
|
||||
*/
|
||||
@property(nonatomic, strong) CALayer *quoteBorderLayer;
|
||||
|
||||
@property(nonatomic, strong) UIImageView *quoteLineView;
|
||||
|
||||
@property(nonatomic, strong) UIView *quoteView;
|
||||
|
||||
@property(nonatomic, strong) UILabel *senderLabel;
|
||||
|
||||
@property(nonatomic, strong) TUIReferenceMessageCellData *referenceData;
|
||||
|
||||
@property(nonatomic, strong) TUITextView *textView;
|
||||
@property(nonatomic, strong) NSString *selectContent;
|
||||
@property(nonatomic, strong) TUIReferenceSelectAllContentCallback selectAllContentContent;
|
||||
|
||||
- (void)fillWithData:(TUIReferenceMessageCellData *)data;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,519 @@
|
||||
//
|
||||
// TUIReferenceMessageCell.m
|
||||
// TUIChat
|
||||
//
|
||||
// Created by wyl on 2022/5/24.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
//
|
||||
|
||||
#import "TUIReferenceMessageCell_Minimalist.h"
|
||||
#import <TIMCommon/NSString+TUIEmoji.h>
|
||||
#import <TUICore/TUICore.h>
|
||||
#import <TUICore/TUIDarkModel.h>
|
||||
#import <TUICore/TUIThemeManager.h>
|
||||
#import <TUICore/UIView+TUILayout.h>
|
||||
#import "TUIFileMessageCellData.h"
|
||||
#import "TUIImageMessageCellData.h"
|
||||
#import "TUILinkCellData.h"
|
||||
#import "TUIMergeMessageCellData.h"
|
||||
#import "TUIReplyMessageCellData.h"
|
||||
#import "TUIReplyMessageCell_Minimalist.h"
|
||||
#import "TUITextMessageCellData.h"
|
||||
#import "TUIVideoMessageCellData.h"
|
||||
#import "TUIVoiceMessageCellData.h"
|
||||
|
||||
#import "TUIFileReplyQuoteView_Minimalist.h"
|
||||
#import "TUIImageReplyQuoteView_Minimalist.h"
|
||||
#import "TUIMergeReplyQuoteView_Minimalist.h"
|
||||
#import "TUIReplyQuoteView_Minimalist.h"
|
||||
#import "TUITextMessageCell_Minimalist.h"
|
||||
#import "TUITextReplyQuoteView_Minimalist.h"
|
||||
#import "TUIVideoReplyQuoteView_Minimalist.h"
|
||||
#import "TUIVoiceReplyQuoteView_Minimalist.h"
|
||||
|
||||
#ifndef CGFLOAT_CEIL
|
||||
#ifdef CGFLOAT_IS_DOUBLE
|
||||
#define CGFLOAT_CEIL(value) ceil(value)
|
||||
#else
|
||||
#define CGFLOAT_CEIL(value) ceilf(value)
|
||||
#endif
|
||||
#endif
|
||||
|
||||
@interface TUIReferenceMessageCell_Minimalist () <UITextViewDelegate,TUITextViewDelegate>
|
||||
@property(nonatomic, strong) TUIReplyQuoteView_Minimalist *currentOriginView;
|
||||
|
||||
@property(nonatomic, strong) NSMutableDictionary<NSString *, TUIReplyQuoteView_Minimalist *> *customOriginViewsCache;
|
||||
|
||||
@property(nonatomic,strong) UILongPressGestureRecognizer *longPressGesture;
|
||||
|
||||
@end
|
||||
|
||||
@implementation TUIReferenceMessageCell_Minimalist
|
||||
- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier {
|
||||
if (self = [super initWithStyle:style reuseIdentifier:reuseIdentifier]) {
|
||||
[self setupViews];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)setupViews {
|
||||
[self setupContentTextView];
|
||||
[self.quoteView addSubview:self.senderLabel];
|
||||
[self.contentView addSubview:self.quoteLineView];
|
||||
[self.contentView addSubview:self.quoteView];
|
||||
|
||||
self.bottomContainer = [[UIView alloc] init];
|
||||
[self.contentView addSubview:self.bottomContainer];
|
||||
}
|
||||
- (void)setupContentTextView {
|
||||
self.textView = [[TUITextView alloc] init];
|
||||
self.textView.backgroundColor = [UIColor clearColor];
|
||||
self.textView.textContainerInset = UIEdgeInsetsMake(0, 0, 0, 0);
|
||||
self.textView.textContainer.lineFragmentPadding = 0;
|
||||
self.textView.scrollEnabled = NO;
|
||||
self.textView.editable = NO;
|
||||
self.textView.delegate = self;
|
||||
self.textView.tuiTextViewDelegate = self;
|
||||
self.textView.font = [UIFont systemFontOfSize:16.0];
|
||||
self.textView.textColor = TUIChatDynamicColor(@"chat_reference_message_content_text_color", @"#000000");
|
||||
|
||||
[self.bubbleView addSubview:self.textView];
|
||||
}
|
||||
|
||||
- (void)onLongPressTextViewMessage:(UITextView *)textView {
|
||||
if (self.delegate && [self.delegate respondsToSelector:@selector(onLongPressMessage:)]) {
|
||||
[self.delegate onLongPressMessage:self];
|
||||
}
|
||||
}
|
||||
- (void)fillWithData:(TUIReferenceMessageCellData *)data {
|
||||
[super fillWithData:data];
|
||||
self.referenceData = data;
|
||||
self.senderLabel.text = [NSString stringWithFormat:@"%@:", data.sender];
|
||||
self.senderLabel.rtlAlignment = TUITextRTLAlignmentLeading;
|
||||
self.selectContent = data.content;
|
||||
self.textView.attributedText = [data.content getFormatEmojiStringWithFont:self.textView.font emojiLocations:self.referenceData.emojiLocations];
|
||||
|
||||
if (isRTL()) {
|
||||
self.textView.textAlignment = NSTextAlignmentRight;
|
||||
}
|
||||
else {
|
||||
self.textView.textAlignment = NSTextAlignmentLeft;
|
||||
}
|
||||
|
||||
UIImage *lineImage = nil;
|
||||
if (self.bubbleData.direction == MsgDirectionIncoming) {
|
||||
lineImage = [[TUIImageCache sharedInstance] getResourceFromCache:TUIChatImagePath_Minimalist(@"msg_reply_line_income")];
|
||||
} else {
|
||||
lineImage = [[TUIImageCache sharedInstance] getResourceFromCache:TUIChatImagePath_Minimalist(@"msg_reply_line_outcome")];
|
||||
}
|
||||
lineImage = [lineImage rtl_imageFlippedForRightToLeftLayoutDirection];
|
||||
|
||||
UIEdgeInsets ei = UIEdgeInsetsFromString(@"{10,0,20,0}");
|
||||
ei = rtlEdgeInsetsWithInsets(ei);
|
||||
self.quoteLineView.image = [lineImage resizableImageWithCapInsets:ei resizingMode:UIImageResizingModeStretch];
|
||||
|
||||
self.bottomContainer.hidden = CGSizeEqualToSize(data.bottomContainerSize, CGSizeZero);
|
||||
|
||||
@weakify(self);
|
||||
[[RACObserve(data, originMessage) takeUntil:self.rac_prepareForReuseSignal] subscribeNext:^(V2TIMMessage *originMessage) {
|
||||
@strongify(self);
|
||||
// tell constraints they need updating
|
||||
[self setNeedsUpdateConstraints];
|
||||
|
||||
// update constraints now so we can animate the change
|
||||
[self updateConstraintsIfNeeded];
|
||||
|
||||
[self layoutIfNeeded];
|
||||
}];
|
||||
|
||||
// tell constraints they need updating
|
||||
[self setNeedsUpdateConstraints];
|
||||
|
||||
// update constraints now so we can animate the change
|
||||
[self updateConstraintsIfNeeded];
|
||||
|
||||
[self layoutIfNeeded];
|
||||
}
|
||||
|
||||
// Override
|
||||
- (void)notifyBottomContainerReadyOfData:(TUIMessageCellData *)cellData {
|
||||
NSDictionary *param = @{TUICore_TUIChatExtension_BottomContainer_CellData : self.referenceData};
|
||||
[TUICore raiseExtension:TUICore_TUIChatExtension_BottomContainer_MinimalistExtensionID parentView:self.bottomContainer param:param];
|
||||
}
|
||||
|
||||
- (void)updateUI:(TUIReferenceMessageCellData *)referenceData {
|
||||
self.currentOriginView = [self getCustomOriginView:referenceData.originCellData];
|
||||
[self hiddenAllCustomOriginViews:YES];
|
||||
self.currentOriginView.hidden = NO;
|
||||
|
||||
referenceData.quoteData.supportForReply = NO;
|
||||
referenceData.quoteData.showRevokedOriginMessage = referenceData.showRevokedOriginMessage;
|
||||
[self.currentOriginView fillWithData:referenceData.quoteData];
|
||||
|
||||
if (referenceData.direction == MsgDirectionIncoming) {
|
||||
self.textView.textColor = TUIChatDynamicColor(@"chat_reference_message_content_recv_text_color", @"#000000");
|
||||
self.senderLabel.textColor = TUIChatDynamicColor(@"chat_reference_message_quoteView_recv_text_color", @"#888888");
|
||||
self.quoteView.backgroundColor = TUIChatDynamicColor(@"chat_reference_message_quoteView_bg_color", @"#4444440c");
|
||||
} else {
|
||||
self.textView.textColor = TUIChatDynamicColor(@"chat_reference_message_content_text_color", @"#000000");
|
||||
self.senderLabel.textColor = TUIChatDynamicColor(@"chat_reference_message_quoteView_text_color", @"#888888");
|
||||
self.quoteView.backgroundColor = TUIChatDynamicColor(@"chat_reference_message_quoteView_bg_color", @"#4444440c");
|
||||
}
|
||||
if (referenceData.textColor) {
|
||||
self.textView.textColor = referenceData.textColor;
|
||||
}
|
||||
|
||||
[self.textView mas_remakeConstraints:^(MASConstraintMaker *make) {
|
||||
make.leading.mas_equalTo(self.bubbleView.mas_leading).mas_offset(self.referenceData.textOrigin.x);
|
||||
make.top.mas_equalTo(self.bubbleView.mas_top).mas_offset(self.referenceData.textOrigin.y);
|
||||
make.size.mas_equalTo(self.referenceData.textSize);
|
||||
}];
|
||||
|
||||
[self.quoteView mas_remakeConstraints:^(MASConstraintMaker *make) {
|
||||
if (self.referenceData.direction == MsgDirectionIncoming) {
|
||||
make.leading.mas_equalTo(self.bubbleView).mas_offset(15);
|
||||
}
|
||||
else {
|
||||
make.trailing.mas_equalTo(self.bubbleView).mas_offset(- 15);
|
||||
}
|
||||
make.top.mas_equalTo(self.bubbleView.mas_bottom).mas_offset(self.messageData.messageContainerAppendSize.height + 6);
|
||||
make.size.mas_equalTo(self.referenceData.quoteSize);
|
||||
}];
|
||||
|
||||
[self.senderLabel mas_remakeConstraints:^(MASConstraintMaker *make) {
|
||||
make.leading.mas_equalTo(self.quoteView).mas_offset(6);
|
||||
make.top.mas_equalTo(self.quoteView).mas_offset(8);
|
||||
make.width.mas_equalTo(referenceData.senderSize.width);
|
||||
make.height.mas_equalTo(referenceData.senderSize.height);
|
||||
}];
|
||||
|
||||
BOOL hideSenderLabel = (referenceData.originCellData.innerMessage.status == V2TIM_MSG_STATUS_LOCAL_REVOKED) &&
|
||||
!referenceData.showRevokedOriginMessage;
|
||||
if (hideSenderLabel) {
|
||||
self.senderLabel.hidden = YES;
|
||||
} else {
|
||||
self.senderLabel.hidden = NO;
|
||||
}
|
||||
|
||||
[self.currentOriginView mas_remakeConstraints:^(MASConstraintMaker *make) {
|
||||
if (hideSenderLabel) {
|
||||
make.leading.mas_equalTo(self.quoteView).mas_offset(6);
|
||||
make.top.mas_equalTo(self.quoteView).mas_offset(8);
|
||||
make.trailing.mas_lessThanOrEqualTo(self.quoteView.mas_trailing);
|
||||
make.height.mas_equalTo(self.referenceData.quotePlaceholderSize.height);
|
||||
}
|
||||
else {
|
||||
make.leading.mas_equalTo(self.senderLabel.mas_trailing).mas_offset(4);
|
||||
make.top.mas_equalTo(self.senderLabel.mas_top).mas_offset(1);
|
||||
make.trailing.mas_lessThanOrEqualTo(self.quoteView.mas_trailing);
|
||||
make.height.mas_equalTo(self.referenceData.quotePlaceholderSize.height);
|
||||
}
|
||||
}];
|
||||
|
||||
[self.quoteLineView mas_remakeConstraints:^(MASConstraintMaker *make) {
|
||||
make.top.mas_equalTo(self.bubbleView.mas_bottom);
|
||||
make.bottom.mas_equalTo(self.quoteView.mas_centerY);
|
||||
make.width.mas_equalTo(17);
|
||||
if (self.referenceData.direction == MsgDirectionIncoming) {
|
||||
make.leading.mas_equalTo(self.container.mas_leading).mas_offset(- 1);
|
||||
} else {
|
||||
make.trailing.mas_equalTo(self.container.mas_trailing);
|
||||
}
|
||||
}];
|
||||
|
||||
|
||||
}
|
||||
|
||||
- (TUIReplyQuoteView_Minimalist *)getCustomOriginView:(TUIMessageCellData *)originCellData {
|
||||
NSString *reuseId = originCellData ? NSStringFromClass(originCellData.class) : NSStringFromClass(TUITextMessageCellData.class);
|
||||
TUIReplyQuoteView_Minimalist *view = nil;
|
||||
BOOL reuse = NO;
|
||||
if ([self.customOriginViewsCache.allKeys containsObject:reuseId]) {
|
||||
view = [self.customOriginViewsCache objectForKey:reuseId];
|
||||
reuse = YES;
|
||||
}
|
||||
|
||||
if (view == nil) {
|
||||
Class class = [originCellData getReplyQuoteViewClass];
|
||||
NSString *clsStr = NSStringFromClass(class);
|
||||
if (![clsStr tui_containsString:@"_Minimalist"]) {
|
||||
clsStr = [clsStr stringByAppendingString:@"_Minimalist"];
|
||||
class = NSClassFromString(clsStr);
|
||||
}
|
||||
if (class) {
|
||||
view = [[class alloc] init];
|
||||
}
|
||||
}
|
||||
|
||||
if (view == nil) {
|
||||
TUITextReplyQuoteView_Minimalist *quoteView = [[TUITextReplyQuoteView_Minimalist alloc] init];
|
||||
view = quoteView;
|
||||
}
|
||||
|
||||
if ([view isKindOfClass:[TUITextReplyQuoteView_Minimalist class]]) {
|
||||
TUITextReplyQuoteView_Minimalist *quoteView = (TUITextReplyQuoteView_Minimalist *)view;
|
||||
if (self.referenceData.direction == MsgDirectionIncoming) {
|
||||
quoteView.textLabel.textColor = TUIChatDynamicColor(@"chat_reference_message_quoteView_recv_text_color", @"#888888");
|
||||
} else {
|
||||
quoteView.textLabel.textColor = TUIChatDynamicColor(@"chat_reference_message_quoteView_text_color", @"#888888");
|
||||
}
|
||||
} else if ([view isKindOfClass:[TUIMergeReplyQuoteView_Minimalist class]]) {
|
||||
TUIMergeReplyQuoteView_Minimalist *quoteView = (TUIMergeReplyQuoteView_Minimalist *)view;
|
||||
if (self.referenceData.direction == MsgDirectionIncoming) {
|
||||
quoteView.titleLabel.textColor = TUIChatDynamicColor(@"chat_reference_message_quoteView_recv_text_color", @"#888888");
|
||||
} else {
|
||||
quoteView.titleLabel.textColor = TUIChatDynamicColor(@"chat_reference_message_quoteView_text_color", @"#888888");
|
||||
}
|
||||
}
|
||||
|
||||
if (!reuse) {
|
||||
[self.customOriginViewsCache setObject:view forKey:reuseId];
|
||||
[self.quoteView addSubview:view];
|
||||
}
|
||||
|
||||
view.hidden = YES;
|
||||
return view;
|
||||
}
|
||||
|
||||
- (void)hiddenAllCustomOriginViews:(BOOL)hidden {
|
||||
[self.customOriginViewsCache enumerateKeysAndObjectsUsingBlock:^(NSString *_Nonnull key, TUIReplyQuoteView_Minimalist *_Nonnull obj, BOOL *_Nonnull stop) {
|
||||
obj.hidden = hidden;
|
||||
[obj reset];
|
||||
}];
|
||||
}
|
||||
|
||||
+ (BOOL)requiresConstraintBasedLayout {
|
||||
return YES;
|
||||
}
|
||||
|
||||
// this is Apple's recommended place for adding/updating constraints
|
||||
- (void)updateConstraints {
|
||||
|
||||
[super updateConstraints];
|
||||
|
||||
[self updateUI:self.referenceData];
|
||||
|
||||
[self layoutBottomContainer];
|
||||
|
||||
}
|
||||
|
||||
- (void)layoutBottomContainer {
|
||||
if (CGSizeEqualToSize(self.referenceData.bottomContainerSize, CGSizeZero)) {
|
||||
return;
|
||||
}
|
||||
|
||||
CGSize size = self.referenceData.bottomContainerSize;
|
||||
/// TransitionView should not cover the replyView.
|
||||
/// Add an extra tiny offset to the left or right of TransitionView if replyView is visible.
|
||||
CGFloat offset = self.quoteLineView.hidden ? 0 : 2;
|
||||
|
||||
[self.bottomContainer mas_remakeConstraints:^(MASConstraintMaker *make) {
|
||||
if (self.referenceData.direction == MsgDirectionIncoming) {
|
||||
make.leading.mas_equalTo(self.container.mas_leading).mas_offset(offset);
|
||||
} else {
|
||||
make.trailing.mas_equalTo(self.container.mas_trailing).mas_offset(-offset);
|
||||
}
|
||||
make.top.mas_equalTo(self.quoteView.mas_bottom).mas_offset(6);
|
||||
make.size.mas_equalTo(size);
|
||||
}];
|
||||
|
||||
if (!self.quoteView.hidden) {
|
||||
CGRect oldRect = self.quoteView.frame;
|
||||
CGRect newRect = CGRectMake(oldRect.origin.x, CGRectGetMaxY(self.bottomContainer.frame) + 5, oldRect.size.width, oldRect.size.height);
|
||||
self.quoteView.frame = newRect;
|
||||
}
|
||||
if (!self.messageModifyRepliesButton.hidden) {
|
||||
CGRect oldRect = self.messageModifyRepliesButton.frame;
|
||||
CGRect newRect = CGRectMake(oldRect.origin.x, CGRectGetMaxY(self.quoteView.frame) + 5, oldRect.size.width, oldRect.size.height);
|
||||
self.messageModifyRepliesButton.frame = newRect;
|
||||
}
|
||||
for (UIView *view in self.replyAvatarImageViews) {
|
||||
CGRect oldRect = view.frame;
|
||||
CGRect newRect = CGRectMake(oldRect.origin.x, CGRectGetMaxY(self.quoteView.frame) + 5, oldRect.size.width, oldRect.size.height);
|
||||
view.frame = newRect;
|
||||
}
|
||||
if (!self.quoteLineView.hidden) {
|
||||
CGRect oldRect = self.quoteLineView.frame;
|
||||
CGRect newRect = CGRectMake(oldRect.origin.x, oldRect.origin.y, oldRect.size.width, oldRect.size.height + self.bottomContainer.mm_h);
|
||||
self.quoteLineView.frame = newRect;
|
||||
}
|
||||
}
|
||||
|
||||
- (UILabel *)senderLabel {
|
||||
if (_senderLabel == nil) {
|
||||
_senderLabel = [[UILabel alloc] init];
|
||||
_senderLabel.text = @"harvy:";
|
||||
_senderLabel.font = [UIFont systemFontOfSize:12.0];
|
||||
_senderLabel.textColor = TUIChatDynamicColor(@"chat_reference_message_sender_text_color", @"#888888");
|
||||
}
|
||||
return _senderLabel;
|
||||
}
|
||||
|
||||
- (UIImageView *)quoteLineView {
|
||||
if (_quoteLineView == nil) {
|
||||
_quoteLineView = [[UIImageView alloc] init];
|
||||
}
|
||||
return _quoteLineView;
|
||||
}
|
||||
|
||||
- (UIView *)quoteView {
|
||||
if (_quoteView == nil) {
|
||||
_quoteView = [[UIView alloc] init];
|
||||
_quoteView.backgroundColor = TUIChatDynamicColor(@"chat_reference_message_quoteView_bg_color", @"#4444440c");
|
||||
_quoteView.layer.cornerRadius = 10.0;
|
||||
_quoteView.layer.masksToBounds = YES;
|
||||
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(quoteViewOnTap)];
|
||||
[_quoteView addGestureRecognizer:tap];
|
||||
}
|
||||
return _quoteView;
|
||||
}
|
||||
|
||||
- (void)quoteViewOnTap {
|
||||
if (self.delegate && [self.delegate respondsToSelector:@selector(onSelectMessage:)]) {
|
||||
[self.delegate onSelectMessage:self];
|
||||
}
|
||||
}
|
||||
|
||||
- (NSMutableDictionary *)customOriginViewsCache {
|
||||
if (_customOriginViewsCache == nil) {
|
||||
_customOriginViewsCache = [[NSMutableDictionary alloc] init];
|
||||
}
|
||||
return _customOriginViewsCache;
|
||||
}
|
||||
|
||||
- (void)textViewDidChangeSelection:(UITextView *)textView {
|
||||
NSAttributedString *selectedString = [textView.attributedText attributedSubstringFromRange:textView.selectedRange];
|
||||
if (self.selectAllContentContent && selectedString) {
|
||||
if (selectedString.length == textView.attributedText.length) {
|
||||
self.selectAllContentContent(YES);
|
||||
} else {
|
||||
self.selectAllContentContent(NO);
|
||||
}
|
||||
}
|
||||
if (selectedString.length > 0) {
|
||||
NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc] init];
|
||||
[attributedString appendAttributedString:selectedString];
|
||||
NSUInteger offsetLocation = 0;
|
||||
for (NSDictionary *emojiLocation in self.referenceData.emojiLocations) {
|
||||
NSValue *key = emojiLocation.allKeys.firstObject;
|
||||
NSAttributedString *originStr = emojiLocation[key];
|
||||
NSRange currentRange = [key rangeValue];
|
||||
currentRange.location += offsetLocation;
|
||||
if (currentRange.location >= textView.selectedRange.location) {
|
||||
currentRange.location -= textView.selectedRange.location;
|
||||
if (currentRange.location + currentRange.length <= attributedString.length) {
|
||||
[attributedString replaceCharactersInRange:currentRange withAttributedString:originStr];
|
||||
offsetLocation += originStr.length - currentRange.length;
|
||||
}
|
||||
}
|
||||
}
|
||||
self.selectContent = attributedString.string;
|
||||
} else {
|
||||
self.selectContent = nil;
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - TUIMessageCellProtocol
|
||||
+ (CGFloat)getHeight:(TUIMessageCellData *)data withWidth:(CGFloat)width {
|
||||
NSAssert([data isKindOfClass:TUIReferenceMessageCellData.class], @"data must be kind of TUIReferenceMessageCellData");
|
||||
TUIReferenceMessageCellData *referenceCellData = (TUIReferenceMessageCellData *)data;
|
||||
|
||||
CGFloat cellHeight = [super getHeight:referenceCellData withWidth:width];
|
||||
cellHeight += referenceCellData.quoteSize.height + referenceCellData.bottomContainerSize.height;
|
||||
if (referenceCellData.bottomContainerSize.height > 0) {
|
||||
cellHeight += kScale375(6);
|
||||
}
|
||||
cellHeight += kScale375(6);
|
||||
return cellHeight;
|
||||
}
|
||||
|
||||
+ (CGSize)getContentSize:(TUIMessageCellData *)data {
|
||||
NSAssert([data isKindOfClass:TUIReferenceMessageCellData.class], @"data must be kind of TUIReferenceMessageCellData");
|
||||
TUIReferenceMessageCellData *referenceCellData = (TUIReferenceMessageCellData *)data;
|
||||
|
||||
CGFloat quoteHeight = 0;
|
||||
CGFloat quoteWidth = 0;
|
||||
CGFloat quoteMaxWidth = TReplyQuoteView_Max_Width;
|
||||
CGFloat quotePlaceHolderMarginWidth = 12;
|
||||
|
||||
// Calculate the size of label which displays the sender's displayname
|
||||
CGSize senderSize = [@"0" sizeWithAttributes:@{NSFontAttributeName : [UIFont boldSystemFontOfSize:12.0]}];
|
||||
CGRect senderRect = [[NSString stringWithFormat:@"%@:",referenceCellData.sender] boundingRectWithSize:CGSizeMake(quoteMaxWidth, senderSize.height)
|
||||
options:NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading
|
||||
attributes:@{NSFontAttributeName : [UIFont boldSystemFontOfSize:12.0]}
|
||||
context:nil];
|
||||
|
||||
// Calculate the size of revoke string
|
||||
CGRect messageRevokeRect = CGRectZero;
|
||||
bool showRevokeStr = (referenceCellData.originCellData.innerMessage.status == V2TIM_MSG_STATUS_LOCAL_REVOKED) &&
|
||||
!referenceCellData.showRevokedOriginMessage;
|
||||
if (showRevokeStr) {
|
||||
NSString *msgRevokeStr = TIMCommonLocalizableString(TUIKitReferenceOriginMessageRevoke);
|
||||
messageRevokeRect = [msgRevokeStr boundingRectWithSize:CGSizeMake(quoteMaxWidth, senderSize.height)
|
||||
options:NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading
|
||||
attributes:@{NSFontAttributeName : [UIFont boldSystemFontOfSize:12.0]}
|
||||
context:nil];
|
||||
}
|
||||
|
||||
// Calculate the size of customize quote placeholder view
|
||||
CGSize placeholderSize = [referenceCellData quotePlaceholderSizeWithType:referenceCellData.originMsgType data:referenceCellData.quoteData];
|
||||
|
||||
// Calculate the size of label which displays the content of replying the original message
|
||||
UIFont *textFont = [UIFont systemFontOfSize:16.0];
|
||||
NSAttributedString *attributeString = [referenceCellData.content getFormatEmojiStringWithFont:textFont emojiLocations:nil];
|
||||
|
||||
CGRect replyContentRect = [attributeString boundingRectWithSize:CGSizeMake(TTextMessageCell_Text_Width_Max, MAXFLOAT)
|
||||
options:NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading
|
||||
context:nil];
|
||||
CGSize size = CGSizeMake(CGFLOAT_CEIL(replyContentRect.size.width), CGFLOAT_CEIL(replyContentRect.size.height));
|
||||
|
||||
CGRect replyContentRect2 = [attributeString boundingRectWithSize:CGSizeMake(MAXFLOAT, [textFont lineHeight])
|
||||
options:NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading
|
||||
context:nil];
|
||||
CGSize size2 = replyContentRect2.size;
|
||||
|
||||
// If there are multiple lines, determine whether the font width of the last line exceeds the position of the message status. If so, the message status will wrap.
|
||||
// If there is only one line, directly add the width of the message status
|
||||
if ((int)size2.width / (int)TTextMessageCell_Text_Width_Max > 0) {
|
||||
if ((int)size2.width % (int)TTextMessageCell_Text_Width_Max > TTextMessageCell_Text_Width_Max - referenceCellData.msgStatusSize.width) {
|
||||
size.height += referenceCellData.msgStatusSize.height;
|
||||
}
|
||||
} else {
|
||||
size.width += referenceCellData.msgStatusSize.width + kScale390(10);
|
||||
}
|
||||
|
||||
referenceCellData.textSize = size;
|
||||
CGFloat y = referenceCellData.cellLayout.bubbleInsets.top + [TUIBubbleMessageCell_Minimalist getBubbleTop:referenceCellData];
|
||||
referenceCellData.textOrigin = CGPointMake(referenceCellData.cellLayout.bubbleInsets.left, y);
|
||||
|
||||
size.height += referenceCellData.cellLayout.bubbleInsets.top + referenceCellData.cellLayout.bubbleInsets.bottom;
|
||||
size.width += referenceCellData.cellLayout.bubbleInsets.left + referenceCellData.cellLayout.bubbleInsets.right;
|
||||
|
||||
if (referenceCellData.direction == MsgDirectionIncoming) {
|
||||
size.height = MAX(size.height, TUIBubbleMessageCell_Minimalist.incommingBubble.size.height);
|
||||
} else {
|
||||
size.height = MAX(size.height, TUIBubbleMessageCell_Minimalist.outgoingBubble.size.height);
|
||||
}
|
||||
|
||||
quoteWidth = senderRect.size.width;
|
||||
quoteWidth += placeholderSize.width;
|
||||
quoteWidth += (quotePlaceHolderMarginWidth * 2);
|
||||
|
||||
quoteHeight = MAX(senderRect.size.height, placeholderSize.height);
|
||||
quoteHeight += (8 + 8);
|
||||
|
||||
if (showRevokeStr) {
|
||||
quoteWidth = messageRevokeRect.size.width;
|
||||
quoteHeight = MAX(senderRect.size.height, placeholderSize.height);
|
||||
quoteHeight += (8 + 8);
|
||||
}
|
||||
|
||||
referenceCellData.senderSize = CGSizeMake(CGFLOAT_CEIL(senderRect.size.width)+3, senderRect.size.height);
|
||||
referenceCellData.quotePlaceholderSize = CGSizeMake(CGFLOAT_CEIL(placeholderSize.width), placeholderSize.height);
|
||||
// self.replyContentSize = CGSizeMake(replyContentRect.size.width, replyContentRect.size.height);
|
||||
referenceCellData.quoteSize = CGSizeMake(CGFLOAT_CEIL(quoteWidth), quoteHeight);
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,35 @@
|
||||
//
|
||||
// TUIReplyMessageCell_Minimalist.h
|
||||
// TUIChat
|
||||
//
|
||||
// Created by harvy on 2021/11/11.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
//
|
||||
|
||||
#import <TIMCommon/TUIBubbleMessageCell_Minimalist.h>
|
||||
#import "TUIChatDefine.h"
|
||||
#import <TIMCommon/TUITextView.h>
|
||||
|
||||
@class TUIReplyMessageCellData;
|
||||
@class TUIReplyQuoteViewData;
|
||||
@class TUIImageVideoReplyQuoteViewData;
|
||||
@class TUIVoiceFileReplyQuoteViewData;
|
||||
@class TUIMergeReplyQuoteViewData;
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface TUIReplyMessageCell_Minimalist : TUIBubbleMessageCell_Minimalist
|
||||
|
||||
@property(nonatomic, strong) UIView *quoteBorderLine;
|
||||
@property(nonatomic, strong) UIView *quoteView;
|
||||
|
||||
@property(nonatomic, strong) TUITextView *textView;
|
||||
@property(nonatomic, strong) UILabel *senderLabel;
|
||||
|
||||
@property(nonatomic, strong) TUIReplyMessageCellData *replyData;
|
||||
|
||||
- (void)fillWithData:(TUIReplyMessageCellData *)data;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,428 @@
|
||||
//
|
||||
// TUIReplyMessageCell.m
|
||||
// TUIChat
|
||||
//
|
||||
// Created by harvy on 2021/11/11.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
//
|
||||
|
||||
#import "TUIReplyMessageCell_Minimalist.h"
|
||||
#import <TIMCommon/NSString+TUIEmoji.h>
|
||||
#import <TUICore/TUICore.h>
|
||||
#import <TUICore/TUIDarkModel.h>
|
||||
#import <TUICore/TUIThemeManager.h>
|
||||
#import <TUICore/UIView+TUILayout.h>
|
||||
#import "TUIFileMessageCellData.h"
|
||||
#import "TUIImageMessageCellData.h"
|
||||
#import "TUILinkCellData.h"
|
||||
#import "TUIMergeMessageCellData.h"
|
||||
#import "TUIReplyMessageCellData.h"
|
||||
#import "TUITextMessageCellData.h"
|
||||
#import "TUIVideoMessageCellData.h"
|
||||
#import "TUIVoiceMessageCellData.h"
|
||||
|
||||
#import "TUIFileReplyQuoteView_Minimalist.h"
|
||||
#import "TUIImageReplyQuoteView_Minimalist.h"
|
||||
#import "TUIMergeReplyQuoteView_Minimalist.h"
|
||||
#import "TUIReplyQuoteView_Minimalist.h"
|
||||
#import "TUITextReplyQuoteView_Minimalist.h"
|
||||
#import "TUIVideoReplyQuoteView_Minimalist.h"
|
||||
#import "TUIVoiceReplyQuoteView_Minimalist.h"
|
||||
|
||||
@interface TUIReplyMessageCell_Minimalist ()<UITextViewDelegate,TUITextViewDelegate>
|
||||
|
||||
@property(nonatomic, strong) TUIReplyQuoteView_Minimalist *currentOriginView;
|
||||
|
||||
@property(nonatomic, strong) NSMutableDictionary<NSString *, TUIReplyQuoteView_Minimalist *> *customOriginViewsCache;
|
||||
|
||||
@end
|
||||
|
||||
@implementation TUIReplyMessageCell_Minimalist
|
||||
|
||||
- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier {
|
||||
if (self = [super initWithStyle:style reuseIdentifier:reuseIdentifier]) {
|
||||
[self setupViews];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)setupViews {
|
||||
[self.quoteView addSubview:self.senderLabel];
|
||||
[self.quoteView addSubview:self.quoteBorderLine];
|
||||
|
||||
[self.bubbleView addSubview:self.quoteView];
|
||||
[self.bubbleView addSubview:self.textView];
|
||||
|
||||
self.bottomContainer = [[UIView alloc] init];
|
||||
[self.contentView addSubview:self.bottomContainer];
|
||||
}
|
||||
|
||||
// Override
|
||||
- (void)notifyBottomContainerReadyOfData:(TUIMessageCellData *)cellData {
|
||||
NSDictionary *param = @{TUICore_TUIChatExtension_BottomContainer_CellData : self.replyData};
|
||||
[TUICore raiseExtension:TUICore_TUIChatExtension_BottomContainer_MinimalistExtensionID parentView:self.bottomContainer param:param];
|
||||
}
|
||||
|
||||
- (void)fillWithData:(TUIReplyMessageCellData *)data {
|
||||
[super fillWithData:data];
|
||||
self.replyData = data;
|
||||
self.senderLabel.text = [NSString stringWithFormat:@"%@:", data.sender];
|
||||
if (data.direction == MsgDirectionIncoming) {
|
||||
self.textView.textColor = TUIChatDynamicColor(@"chat_reply_message_content_recv_text_color", @"#000000");
|
||||
self.senderLabel.textColor = TUIChatDynamicColor(@"chat_reply_message_quoteView_recv_text_color", @"#888888");
|
||||
self.quoteView.backgroundColor = TUIChatDynamicColor(@"chat_reply_message_quoteView_bg_color", @"#4444440c");
|
||||
} else {
|
||||
self.textView.textColor = TUIChatDynamicColor(@"chat_reply_message_content_text_color", @"#000000");
|
||||
self.senderLabel.textColor = TUIChatDynamicColor(@"chat_reply_message_quoteView_text_color", @"#888888");
|
||||
self.quoteView.backgroundColor = [UIColor colorWithRed:68 / 255.0 green:68 / 255.0 blue:68 / 255.0 alpha:0.05];
|
||||
}
|
||||
self.textView.attributedText = [data.content getFormatEmojiStringWithFont:self.textView.font emojiLocations:self.replyData.emojiLocations];
|
||||
|
||||
self.bottomContainer.hidden = CGSizeEqualToSize(data.bottomContainerSize, CGSizeZero);
|
||||
|
||||
@weakify(self);
|
||||
[[RACObserve(data, originMessage) takeUntil:self.rac_prepareForReuseSignal] subscribeNext:^(V2TIMMessage *originMessage) {
|
||||
@strongify(self);
|
||||
// tell constraints they need updating
|
||||
[self setNeedsUpdateConstraints];
|
||||
|
||||
// update constraints now so we can animate the change
|
||||
[self updateConstraintsIfNeeded];
|
||||
|
||||
[self layoutIfNeeded];
|
||||
}];
|
||||
|
||||
// tell constraints they need updating
|
||||
[self setNeedsUpdateConstraints];
|
||||
|
||||
// update constraints now so we can animate the change
|
||||
[self updateConstraintsIfNeeded];
|
||||
|
||||
[self layoutIfNeeded];
|
||||
}
|
||||
|
||||
+ (BOOL)requiresConstraintBasedLayout {
|
||||
return YES;
|
||||
}
|
||||
|
||||
// this is Apple's recommended place for adding/updating constraints
|
||||
- (void)updateConstraints {
|
||||
|
||||
[super updateConstraints];
|
||||
|
||||
[self updateUI:self.replyData];
|
||||
|
||||
[self layoutBottomContainer];
|
||||
|
||||
}
|
||||
|
||||
- (void)updateUI:(TUIReplyMessageCellData *)replyData {
|
||||
self.currentOriginView = [self getCustomOriginView:replyData.originCellData];
|
||||
[self hiddenAllCustomOriginViews:YES];
|
||||
self.currentOriginView.hidden = NO;
|
||||
|
||||
replyData.quoteData.supportForReply = YES;
|
||||
replyData.quoteData.showRevokedOriginMessage = replyData.showRevokedOriginMessage;
|
||||
[self.currentOriginView fillWithData:replyData.quoteData];
|
||||
|
||||
[self.quoteView mas_remakeConstraints:^(MASConstraintMaker *make) {
|
||||
make.leading.mas_equalTo(self.bubbleView).mas_offset(16);
|
||||
make.top.mas_equalTo(12);
|
||||
make.trailing.mas_lessThanOrEqualTo(self.bubbleView).mas_offset(-16);
|
||||
make.width.mas_greaterThanOrEqualTo(self.senderLabel);
|
||||
make.height.mas_equalTo(self.replyData.quoteSize.height);
|
||||
}];
|
||||
|
||||
[self.quoteBorderLine mas_remakeConstraints:^(MASConstraintMaker *make) {
|
||||
make.leading.mas_equalTo(self.quoteView);
|
||||
make.top.mas_equalTo(self.quoteView);
|
||||
make.width.mas_equalTo(3);
|
||||
make.bottom.mas_equalTo(self.quoteView);
|
||||
}];
|
||||
|
||||
[self.textView mas_remakeConstraints:^(MASConstraintMaker *make) {
|
||||
make.leading.mas_equalTo(self.quoteView).mas_offset(4);
|
||||
make.top.mas_equalTo(self.quoteView.mas_bottom).mas_offset(12);
|
||||
make.trailing.mas_lessThanOrEqualTo(self.quoteView).mas_offset(-4);;
|
||||
make.bottom.mas_equalTo(self.bubbleView).mas_offset(-4);
|
||||
}];
|
||||
|
||||
[self.senderLabel mas_remakeConstraints:^(MASConstraintMaker *make) {
|
||||
make.leading.mas_equalTo(self.textView);
|
||||
make.top.mas_equalTo(3);
|
||||
make.size.mas_equalTo(self.replyData.senderSize);
|
||||
}];
|
||||
|
||||
BOOL hideSenderLabel = (replyData.originCellData.innerMessage.status == V2TIM_MSG_STATUS_LOCAL_REVOKED) &&
|
||||
!replyData.showRevokedOriginMessage;
|
||||
if (hideSenderLabel) {
|
||||
self.senderLabel.hidden = YES;
|
||||
} else {
|
||||
self.senderLabel.hidden = NO;
|
||||
}
|
||||
|
||||
[self.currentOriginView mas_remakeConstraints:^(MASConstraintMaker *make) {
|
||||
make.leading.mas_equalTo(self.senderLabel);
|
||||
if (hideSenderLabel) {
|
||||
make.centerY.mas_equalTo(self.quoteView);
|
||||
} else {
|
||||
make.top.mas_equalTo(self.senderLabel.mas_bottom).mas_offset(4);
|
||||
}
|
||||
// make.width.mas_greaterThanOrEqualTo(self.replyData.quotePlaceholderSize);
|
||||
make.trailing.mas_lessThanOrEqualTo(self.quoteView.mas_trailing);
|
||||
make.height.mas_equalTo(self.replyData.quotePlaceholderSize);
|
||||
}];
|
||||
|
||||
}
|
||||
|
||||
- (TUIReplyQuoteView_Minimalist *)getCustomOriginView:(TUIMessageCellData *)originCellData {
|
||||
NSString *reuseId = originCellData ? NSStringFromClass(originCellData.class) : NSStringFromClass(TUITextMessageCellData.class);
|
||||
TUIReplyQuoteView_Minimalist *view = nil;
|
||||
BOOL reuse = NO;
|
||||
if ([self.customOriginViewsCache.allKeys containsObject:reuseId]) {
|
||||
view = [self.customOriginViewsCache objectForKey:reuseId];
|
||||
reuse = YES;
|
||||
}
|
||||
|
||||
if (view == nil) {
|
||||
Class class = [originCellData getReplyQuoteViewClass];
|
||||
NSString *clsStr = NSStringFromClass(class);
|
||||
if (![clsStr tui_containsString:@"_Minimalist"]) {
|
||||
clsStr = [clsStr stringByAppendingString:@"_Minimalist"];
|
||||
class = NSClassFromString(clsStr);
|
||||
}
|
||||
if (class) {
|
||||
view = [[class alloc] init];
|
||||
}
|
||||
}
|
||||
|
||||
if (view == nil) {
|
||||
TUITextReplyQuoteView_Minimalist *quoteView = [[TUITextReplyQuoteView_Minimalist alloc] init];
|
||||
view = quoteView;
|
||||
}
|
||||
|
||||
if ([view isKindOfClass:[TUITextReplyQuoteView_Minimalist class]]) {
|
||||
TUITextReplyQuoteView_Minimalist *quoteView = (TUITextReplyQuoteView_Minimalist *)view;
|
||||
if (self.replyData.direction == MsgDirectionIncoming) {
|
||||
quoteView.textLabel.textColor = TUIChatDynamicColor(@"chat_reply_message_quoteView_recv_text_color", @"#888888");
|
||||
} else {
|
||||
quoteView.textLabel.textColor = TUIChatDynamicColor(@"chat_reply_message_quoteView_text_color", @"#888888");
|
||||
}
|
||||
} else if ([view isKindOfClass:[TUIMergeReplyQuoteView_Minimalist class]]) {
|
||||
TUIMergeReplyQuoteView_Minimalist *quoteView = (TUIMergeReplyQuoteView_Minimalist *)view;
|
||||
if (self.replyData.direction == MsgDirectionIncoming) {
|
||||
quoteView.titleLabel.textColor = TUIChatDynamicColor(@"chat_reply_message_quoteView_recv_text_color", @"#888888");
|
||||
} else {
|
||||
quoteView.titleLabel.textColor = TUIChatDynamicColor(@"chat_reply_message_quoteView_text_color", @"#888888");
|
||||
}
|
||||
}
|
||||
|
||||
if (!reuse) {
|
||||
[self.customOriginViewsCache setObject:view forKey:reuseId];
|
||||
[self.quoteView addSubview:view];
|
||||
}
|
||||
|
||||
view.hidden = YES;
|
||||
return view;
|
||||
}
|
||||
|
||||
- (void)hiddenAllCustomOriginViews:(BOOL)hidden {
|
||||
[self.customOriginViewsCache enumerateKeysAndObjectsUsingBlock:^(NSString *_Nonnull key, TUIReplyQuoteView_Minimalist *_Nonnull obj, BOOL *_Nonnull stop) {
|
||||
obj.hidden = hidden;
|
||||
[obj reset];
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)layoutSubviews {
|
||||
[super layoutSubviews];
|
||||
|
||||
[self updateUI:self.replyData];
|
||||
|
||||
[self layoutBottomContainer];
|
||||
}
|
||||
|
||||
- (void)layoutBottomContainer {
|
||||
if (CGSizeEqualToSize(self.replyData.bottomContainerSize, CGSizeZero)) {
|
||||
return;
|
||||
}
|
||||
|
||||
CGSize size = self.replyData.bottomContainerSize;
|
||||
[self.bottomContainer mas_remakeConstraints:^(MASConstraintMaker *make) {
|
||||
if (self.replyData.direction == MsgDirectionIncoming) {
|
||||
make.leading.mas_equalTo(self.container.mas_leading);
|
||||
} else {
|
||||
make.trailing.mas_equalTo(self.container.mas_trailing);
|
||||
}
|
||||
make.top.mas_equalTo(self.bubbleView.mas_bottom).mas_offset(self.messageData.messageContainerAppendSize.height + 6);
|
||||
make.size.mas_equalTo(size);
|
||||
}];
|
||||
|
||||
if (!self.messageModifyRepliesButton.hidden) {
|
||||
CGRect oldRect = self.messageModifyRepliesButton.frame;
|
||||
CGRect newRect = CGRectMake(oldRect.origin.x, CGRectGetMaxY(self.bottomContainer.frame), oldRect.size.width, oldRect.size.height);
|
||||
self.messageModifyRepliesButton.frame = newRect;
|
||||
}
|
||||
|
||||
for (UIView *view in self.replyAvatarImageViews) {
|
||||
CGRect oldRect = view.frame;
|
||||
CGRect newRect = CGRectMake(oldRect.origin.x, CGRectGetMaxY(self.bottomContainer.frame) + 5, oldRect.size.width, oldRect.size.height);
|
||||
view.frame = newRect;
|
||||
}
|
||||
}
|
||||
|
||||
- (UILabel *)senderLabel {
|
||||
if (_senderLabel == nil) {
|
||||
_senderLabel = [[UILabel alloc] init];
|
||||
_senderLabel.text = @"harvy:";
|
||||
_senderLabel.font = [UIFont boldSystemFontOfSize:12.0];
|
||||
_senderLabel.textColor = TUIChatDynamicColor(@"chat_reply_message_sender_text_color", @"#888888");
|
||||
_senderLabel.textAlignment = isRTL()?NSTextAlignmentRight:NSTextAlignmentLeft;
|
||||
}
|
||||
return _senderLabel;
|
||||
}
|
||||
|
||||
- (UIView *)quoteView {
|
||||
if (_quoteView == nil) {
|
||||
_quoteView = [[UIView alloc] init];
|
||||
_quoteView.backgroundColor = TUIChatDynamicColor(@"chat_reply_message_quoteView_bg_color", @"#4444440c");
|
||||
}
|
||||
return _quoteView;
|
||||
}
|
||||
|
||||
- (UIView *)quoteBorderLine {
|
||||
if (_quoteBorderLine == nil) {
|
||||
_quoteBorderLine = [[UIView alloc] init];
|
||||
_quoteBorderLine.backgroundColor = [UIColor colorWithRed:68 / 255.0 green:68 / 255.0 blue:68 / 255.0 alpha:0.1];
|
||||
}
|
||||
return _quoteBorderLine;
|
||||
}
|
||||
|
||||
- (TUITextView *)textView {
|
||||
if (_textView == nil) {
|
||||
_textView = [[TUITextView alloc] init];
|
||||
_textView.font = [UIFont systemFontOfSize:16.0];
|
||||
_textView.textColor = TUIChatDynamicColor(@"chat_reply_message_content_text_color", @"#000000");
|
||||
_textView.backgroundColor = [UIColor clearColor];
|
||||
_textView.textContainerInset = UIEdgeInsetsMake(0, 0, 0, 0);
|
||||
_textView.textContainer.lineFragmentPadding = 0;
|
||||
_textView.scrollEnabled = NO;
|
||||
_textView.editable = NO;
|
||||
_textView.delegate = self;
|
||||
_textView.tuiTextViewDelegate = self;
|
||||
_textView.textAlignment = isRTL()?NSTextAlignmentRight:NSTextAlignmentLeft;
|
||||
}
|
||||
return _textView;
|
||||
}
|
||||
|
||||
- (void)onLongPressTextViewMessage:(UITextView *)textView {
|
||||
if (self.delegate && [self.delegate respondsToSelector:@selector(onLongPressMessage:)]) {
|
||||
[self.delegate onLongPressMessage:self];
|
||||
}
|
||||
}
|
||||
|
||||
- (NSMutableDictionary *)customOriginViewsCache {
|
||||
if (_customOriginViewsCache == nil) {
|
||||
_customOriginViewsCache = [[NSMutableDictionary alloc] init];
|
||||
}
|
||||
return _customOriginViewsCache;
|
||||
}
|
||||
|
||||
#pragma mark - TUIMessageCellProtocol
|
||||
+ (CGFloat)getHeight:(TUIMessageCellData *)data withWidth:(CGFloat)width {
|
||||
NSAssert([data isKindOfClass:TUIReplyMessageCellData.class], @"data must be kind of TUIReplyMessageCellData");
|
||||
TUIReplyMessageCellData *replyCellData = (TUIReplyMessageCellData *)data;
|
||||
|
||||
CGFloat height = [super getHeight:replyCellData withWidth:width];
|
||||
|
||||
if (replyCellData.bottomContainerSize.height > 0) {
|
||||
height += replyCellData.bottomContainerSize.height + 6;
|
||||
}
|
||||
|
||||
return height;
|
||||
}
|
||||
|
||||
+ (CGSize)getContentSize:(TUIMessageCellData *)data {
|
||||
NSAssert([data isKindOfClass:TUIReplyMessageCellData.class], @"data must be kind of TUIReplyMessageCellData");
|
||||
TUIReplyMessageCellData *replyCellData = (TUIReplyMessageCellData *)data;
|
||||
|
||||
CGFloat height = 0;
|
||||
CGFloat quoteHeight = 0;
|
||||
CGFloat quoteWidth = 0;
|
||||
|
||||
CGFloat quoteMinWidth = 100;
|
||||
CGFloat quoteMaxWidth = TReplyQuoteView_Max_Width;
|
||||
CGFloat quotePlaceHolderMarginWidth = 12;
|
||||
|
||||
// Calculate the size of label which displays the sender's displyname
|
||||
CGSize senderSize = [@"0" sizeWithAttributes:@{NSFontAttributeName : [UIFont boldSystemFontOfSize:12.0]}];
|
||||
CGRect senderRect = [replyCellData.sender boundingRectWithSize:CGSizeMake(quoteMaxWidth, senderSize.height)
|
||||
options:NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading
|
||||
attributes:@{NSFontAttributeName : [UIFont boldSystemFontOfSize:12.0]}
|
||||
context:nil];
|
||||
|
||||
CGRect messageRevokeRect = CGRectZero;
|
||||
BOOL showRevokeStr = (replyCellData.originCellData.innerMessage.status == V2TIM_MSG_STATUS_LOCAL_REVOKED) &&
|
||||
!replyCellData.showRevokedOriginMessage;
|
||||
if (showRevokeStr) {
|
||||
NSString *msgRevokeStr = TIMCommonLocalizableString(TUIKitRepliesOriginMessageRevoke);
|
||||
messageRevokeRect = [msgRevokeStr boundingRectWithSize:CGSizeMake(quoteMaxWidth, senderSize.height)
|
||||
options:NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading
|
||||
attributes:@{NSFontAttributeName : [UIFont boldSystemFontOfSize:12.0]}
|
||||
context:nil];
|
||||
}
|
||||
|
||||
// Calculate the size of customize quote placeholder view
|
||||
CGSize placeholderSize = [replyCellData quotePlaceholderSizeWithType:replyCellData.originMsgType data:replyCellData.quoteData];
|
||||
|
||||
// Calculate the size of label which displays the content of replying the original message
|
||||
UIFont *font = [UIFont systemFontOfSize:16.0];
|
||||
NSAttributedString *attributeString = [replyCellData.content getFormatEmojiStringWithFont:font emojiLocations:nil];
|
||||
CGRect replyContentRect = [attributeString boundingRectWithSize:CGSizeMake(quoteMaxWidth, CGFLOAT_MAX)
|
||||
options:NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading
|
||||
context:nil];
|
||||
|
||||
// Calculate the size of quote view base the content
|
||||
quoteWidth = senderRect.size.width;
|
||||
if (quoteWidth < placeholderSize.width) {
|
||||
quoteWidth = placeholderSize.width;
|
||||
}
|
||||
if (quoteWidth < replyContentRect.size.width) {
|
||||
quoteWidth = replyContentRect.size.width;
|
||||
}
|
||||
quoteWidth += quotePlaceHolderMarginWidth;
|
||||
if (quoteWidth > quoteMaxWidth) {
|
||||
quoteWidth = quoteMaxWidth;
|
||||
}
|
||||
if (quoteWidth < quoteMinWidth) {
|
||||
quoteWidth = quoteMinWidth;
|
||||
}
|
||||
|
||||
quoteHeight = 3 + senderRect.size.height + 4 + placeholderSize.height + 6;
|
||||
|
||||
if (showRevokeStr) {
|
||||
quoteWidth = MAX(quoteWidth, messageRevokeRect.size.width);
|
||||
quoteHeight = 3 + 4 + messageRevokeRect.size.height + 6;
|
||||
}
|
||||
|
||||
replyCellData.senderSize = CGSizeMake(quoteWidth, senderRect.size.height);
|
||||
replyCellData.quotePlaceholderSize = placeholderSize;
|
||||
replyCellData.replyContentSize = CGSizeMake(replyContentRect.size.width, replyContentRect.size.height);
|
||||
replyCellData.quoteSize = CGSizeMake(quoteWidth, quoteHeight);
|
||||
|
||||
// Calculate the height of cell
|
||||
height = 12 + quoteHeight + 12 + replyCellData.replyContentSize.height + 12;
|
||||
|
||||
CGRect replyContentRect2 = [attributeString boundingRectWithSize:CGSizeMake(MAXFLOAT, [font lineHeight])
|
||||
options:NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading
|
||||
context:nil];
|
||||
|
||||
// Determine whether the width of the last line exceeds the position of the message status. If it exceeds, the message status will be wrapped.
|
||||
if ((int)replyContentRect2.size.width % (int)quoteWidth == 0 ||
|
||||
(int)replyContentRect2.size.width % (int)quoteWidth + replyCellData.msgStatusSize.width > quoteWidth) {
|
||||
height += replyCellData.msgStatusSize.height;
|
||||
}
|
||||
|
||||
return CGSizeMake(quoteWidth + TReplyQuoteView_Margin_Width, height);
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,23 @@
|
||||
//
|
||||
// TUIReplyQuoteView_Minimalist.h
|
||||
// TUIChat
|
||||
//
|
||||
// Created by harvy on 2021/11/25.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
@class TUIReplyQuoteViewData;
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface TUIReplyQuoteView_Minimalist : UIView
|
||||
|
||||
@property(nonatomic, strong) TUIReplyQuoteViewData *data;
|
||||
|
||||
- (void)fillWithData:(TUIReplyQuoteViewData *)data;
|
||||
- (void)reset;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,22 @@
|
||||
//
|
||||
// TUIReplyQuoteView_Minimalist.m
|
||||
// TUIChat
|
||||
//
|
||||
// Created by harvy on 2021/11/25.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
//
|
||||
|
||||
#import "TUIReplyQuoteView_Minimalist.h"
|
||||
#import <TIMCommon/NSString+TUIEmoji.h>
|
||||
#import "TUIReplyQuoteViewData.h"
|
||||
|
||||
@implementation TUIReplyQuoteView_Minimalist
|
||||
|
||||
- (void)fillWithData:(TUIReplyQuoteViewData *)data {
|
||||
_data = data;
|
||||
}
|
||||
|
||||
- (void)reset {
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,19 @@
|
||||
//
|
||||
// TUITextReplyQuoteView.h
|
||||
// TUIChat
|
||||
//
|
||||
// Created by harvy on 2021/11/25.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
//
|
||||
|
||||
#import "TUIReplyQuoteView_Minimalist.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface TUITextReplyQuoteView_Minimalist : TUIReplyQuoteView_Minimalist
|
||||
|
||||
@property(nonatomic, strong) UILabel *textLabel;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,79 @@
|
||||
//
|
||||
// TUITextReplyQuoteView.m
|
||||
// TUIChat
|
||||
//
|
||||
// Created by harvy on 2021/11/25.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
//
|
||||
|
||||
#import "TUITextReplyQuoteView_Minimalist.h"
|
||||
#import <TIMCommon/NSString+TUIEmoji.h>
|
||||
#import <TUICore/TUIThemeManager.h>
|
||||
#import "TUITextReplyQuoteViewData.h"
|
||||
|
||||
@implementation TUITextReplyQuoteView_Minimalist
|
||||
|
||||
- (instancetype)initWithFrame:(CGRect)frame {
|
||||
if (self = [super initWithFrame:frame]) {
|
||||
_textLabel = [[UILabel alloc] init];
|
||||
_textLabel.font = [UIFont systemFontOfSize:10.0];
|
||||
_textLabel.textColor = TUIChatDynamicColor(@"chat_reply_message_sender_text_color", @"888888");
|
||||
_textLabel.numberOfLines = 2;
|
||||
[self addSubview:_textLabel];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
+ (BOOL)requiresConstraintBasedLayout {
|
||||
return YES;
|
||||
}
|
||||
|
||||
// this is Apple's recommended place for adding/updating constraints
|
||||
- (void)updateConstraints {
|
||||
|
||||
[super updateConstraints];
|
||||
[self.textLabel mas_remakeConstraints:^(MASConstraintMaker *make) {
|
||||
make.edges.mas_equalTo(self);
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)fillWithData:(TUIReplyQuoteViewData *)data {
|
||||
[super fillWithData:data];
|
||||
if (![data isKindOfClass:TUITextReplyQuoteViewData.class]) {
|
||||
return;
|
||||
}
|
||||
TUITextReplyQuoteViewData *myData = (TUITextReplyQuoteViewData *)data;
|
||||
BOOL showRevokeStr = data.originCellData.innerMessage.status == V2TIM_MSG_STATUS_LOCAL_REVOKED &&
|
||||
!data.showRevokedOriginMessage;
|
||||
if (showRevokeStr) {
|
||||
NSString * revokeStr = data.supportForReply?
|
||||
TIMCommonLocalizableString(TUIKitRepliesOriginMessageRevoke):
|
||||
TIMCommonLocalizableString(TUIKitReferenceOriginMessageRevoke);
|
||||
self.textLabel.attributedText = [revokeStr getFormatEmojiStringWithFont:self.textLabel.font emojiLocations:nil];
|
||||
}
|
||||
else {
|
||||
self.textLabel.attributedText = [myData.text getFormatEmojiStringWithFont:self.textLabel.font emojiLocations:nil];
|
||||
}
|
||||
|
||||
if (isRTL()) {
|
||||
self.textLabel.textAlignment = NSTextAlignmentRight;
|
||||
}
|
||||
else {
|
||||
self.textLabel.textAlignment = NSTextAlignmentLeft;
|
||||
}
|
||||
|
||||
// tell constraints they need updating
|
||||
[self setNeedsUpdateConstraints];
|
||||
|
||||
// update constraints now so we can animate the change
|
||||
[self updateConstraintsIfNeeded];
|
||||
|
||||
[self layoutIfNeeded];
|
||||
|
||||
}
|
||||
|
||||
- (void)reset {
|
||||
self.textLabel.text = @"";
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,19 @@
|
||||
//
|
||||
// TUIVideoReplyQuoteView_Minimalist.h
|
||||
// TUIChat
|
||||
//
|
||||
// Created by harvy on 2021/11/25.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
//
|
||||
|
||||
#import "TUIImageReplyQuoteView_Minimalist.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface TUIVideoReplyQuoteView_Minimalist : TUIImageReplyQuoteView_Minimalist
|
||||
|
||||
@property(nonatomic, strong) UIImageView *playView;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,61 @@
|
||||
//
|
||||
// TUIVideoReplyQuoteView_Minimalist.m
|
||||
// TUIChat
|
||||
//
|
||||
// Created by harvy on 2021/11/25.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
//
|
||||
|
||||
#import "TUIVideoReplyQuoteView_Minimalist.h"
|
||||
#import <TIMCommon/TIMDefine.h>
|
||||
#import <TUICore/TUIDarkModel.h>
|
||||
#import "TUIVideoReplyQuoteViewData.h"
|
||||
|
||||
@implementation TUIVideoReplyQuoteView_Minimalist
|
||||
|
||||
- (instancetype)initWithFrame:(CGRect)frame {
|
||||
if (self = [super initWithFrame:frame]) {
|
||||
_playView = [[UIImageView alloc] init];
|
||||
_playView.image = TUIChatCommonBundleImage(@"play_normal");
|
||||
_playView.frame = CGRectMake(0, 0, 30, 30);
|
||||
[self addSubview:_playView];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)layoutSubviews {
|
||||
[super layoutSubviews];
|
||||
}
|
||||
+ (BOOL)requiresConstraintBasedLayout {
|
||||
return YES;
|
||||
}
|
||||
|
||||
// this is Apple's recommended place for adding/updating constraints
|
||||
- (void)updateConstraints {
|
||||
|
||||
[super updateConstraints];
|
||||
|
||||
TUIVideoReplyQuoteViewData *myData = (TUIVideoReplyQuoteViewData *)self.data;
|
||||
|
||||
[self.imageView mas_remakeConstraints:^(MASConstraintMaker *make) {
|
||||
make.leading.top.mas_equalTo(self);
|
||||
if (CGSizeEqualToSize(CGSizeZero, myData.imageSize)) {
|
||||
make.size.mas_equalTo(CGSizeMake(60, 60));
|
||||
}
|
||||
else {
|
||||
make.size.mas_equalTo(myData.imageSize);
|
||||
}
|
||||
}];
|
||||
|
||||
[self.playView mas_remakeConstraints:^(MASConstraintMaker *make) {
|
||||
make.size.mas_equalTo(CGSizeMake(30, 30));
|
||||
make.center.mas_equalTo(self.imageView);
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)fillWithData:(TUIReplyQuoteViewData *)data {
|
||||
//TUIImageReplyQuoteView deal Image
|
||||
[super fillWithData:data];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,19 @@
|
||||
//
|
||||
// TUIVoiceReplyQuoteView.h
|
||||
// TUIChat
|
||||
//
|
||||
// Created by harvy on 2021/11/25.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
//
|
||||
|
||||
#import "TUITextReplyQuoteView_Minimalist.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface TUIVoiceReplyQuoteView_Minimalist : TUITextReplyQuoteView_Minimalist
|
||||
|
||||
@property(nonatomic, strong) UIImageView *iconView;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,76 @@
|
||||
//
|
||||
// TUIVoiceReplyQuoteView_Minimalist.m
|
||||
// TUIChat
|
||||
//
|
||||
// Created by harvy on 2021/11/25.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
//
|
||||
|
||||
#import "TUIVoiceReplyQuoteView_Minimalist.h"
|
||||
#import <TIMCommon/NSString+TUIEmoji.h>
|
||||
#import <TIMCommon/TIMDefine.h>
|
||||
#import <TUICore/TUIDarkModel.h>
|
||||
#import "TUIVoiceReplyQuoteViewData.h"
|
||||
|
||||
@implementation TUIVoiceReplyQuoteView_Minimalist
|
||||
|
||||
- (instancetype)initWithFrame:(CGRect)frame {
|
||||
if (self = [super initWithFrame:frame]) {
|
||||
_iconView = [[UIImageView alloc] init];
|
||||
_iconView.image = TUIChatCommonBundleImage(@"message_voice_receiver_normal");
|
||||
[self addSubview:_iconView];
|
||||
|
||||
self.textLabel.numberOfLines = 1;
|
||||
self.textLabel.font = [UIFont systemFontOfSize:10.0];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)layoutSubviews {
|
||||
[super layoutSubviews];
|
||||
}
|
||||
|
||||
- (void)fillWithData:(TUIReplyQuoteViewData *)data {
|
||||
[super fillWithData:data];
|
||||
if (![data isKindOfClass:TUIVoiceReplyQuoteViewData.class]) {
|
||||
return;
|
||||
}
|
||||
TUIVoiceReplyQuoteViewData *myData = (TUIVoiceReplyQuoteViewData *)data;
|
||||
self.iconView.image = myData.icon;
|
||||
self.textLabel.numberOfLines = 1;
|
||||
// tell constraints they need updating
|
||||
[self setNeedsUpdateConstraints];
|
||||
|
||||
// update constraints now so we can animate the change
|
||||
[self updateConstraintsIfNeeded];
|
||||
|
||||
[self layoutIfNeeded];
|
||||
}
|
||||
|
||||
+ (BOOL)requiresConstraintBasedLayout {
|
||||
return YES;
|
||||
}
|
||||
|
||||
// this is Apple's recommended place for adding/updating constraints
|
||||
- (void)updateConstraints {
|
||||
|
||||
[super updateConstraints];
|
||||
[self.iconView mas_remakeConstraints:^(MASConstraintMaker *make) {
|
||||
make.leading.mas_equalTo(self);
|
||||
make.top.mas_equalTo(self);
|
||||
make.width.mas_equalTo(15);
|
||||
make.height.mas_equalTo(15);
|
||||
}];
|
||||
|
||||
[self.textLabel sizeToFit];
|
||||
[self.textLabel mas_remakeConstraints:^(MASConstraintMaker *make) {
|
||||
make.leading.mas_equalTo(self.iconView.mas_trailing).mas_offset(3);
|
||||
make.centerY.mas_equalTo(self.mas_centerY);
|
||||
make.trailing.mas_equalTo(self.mas_trailing);
|
||||
make.height.mas_equalTo(self.textLabel.font.lineHeight);
|
||||
}];
|
||||
|
||||
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -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_Minimalist.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface TUIBaseChatViewController_Minimalist () <TUIInputControllerDelegate_Minimalist, TUINotificationProtocol>
|
||||
- (NSString *)forwardTitleWithMyName:(NSString *)nameStr;
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,96 @@
|
||||
|
||||
// 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_Minimalist.h"
|
||||
#import "TUIChatConversationModel.h"
|
||||
#import "TUIInputController_Minimalist.h"
|
||||
@class TUIBaseChatViewController;
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// TUIBaseChatViewController_Minimalist
|
||||
//
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/**
|
||||
* 【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_Minimalist : 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;
|
||||
|
||||
/**
|
||||
* 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_Minimalist *messageController;
|
||||
|
||||
/**
|
||||
* 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_Minimalist *inputController;
|
||||
|
||||
- (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
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,128 @@
|
||||
|
||||
// Created by Tencent on 2023/06/09.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
@import ImSDK_Plus;
|
||||
|
||||
@class TUIBaseMessageController_Minimalist;
|
||||
@class TUIMessageCellData;
|
||||
@class TUIMessageCell;
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// TUIBaseMessageControllerDelegate
|
||||
//
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@protocol TUIBaseMessageControllerDelegate_Minimalist <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_Minimalist *)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_Minimalist *)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_Minimalist *)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_Minimalist *)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_Minimalist *)controller onShowMessageData:(TUIMessageCellData *)data;
|
||||
|
||||
/**
|
||||
* The callback the cell will be displayed with
|
||||
*/
|
||||
- (void)messageController:(TUIBaseMessageController_Minimalist *)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_Minimalist *)controller onSelectMessageAvatar:(TUIMessageCell *)cell;
|
||||
|
||||
/**
|
||||
*
|
||||
* Callback for long pressing avatar in the message cell
|
||||
*/
|
||||
- (void)messageController:(TUIBaseMessageController_Minimalist *)controller onLongSelectMessageAvatar:(TUIMessageCell *)cell;
|
||||
|
||||
/**
|
||||
*
|
||||
* Callback for clicking message content in the message cell
|
||||
*/
|
||||
- (void)messageController:(TUIBaseMessageController_Minimalist *)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_Minimalist *)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_Minimalist *)controller onRelyMessage:(TUIMessageCellData *)data;
|
||||
|
||||
/**
|
||||
* Callback for quoting message (triggered by long-pressing the message content and then clicking the quote button)
|
||||
*/
|
||||
- (void)messageController:(TUIBaseMessageController_Minimalist *)controller onReferenceMessage:(TUIMessageCellData *)data;
|
||||
|
||||
|
||||
/**
|
||||
* Callback for re-editing message (usually for re-calling a message)
|
||||
*/
|
||||
- (void)messageController:(TUIBaseMessageController_Minimalist *)controller onReEditMessage:(TUIMessageCellData *)data;
|
||||
|
||||
/**
|
||||
* Get the height of custom Tips (such as safety tips in Demo)
|
||||
*/
|
||||
- (CGFloat)getTopMarginByCustomView;
|
||||
|
||||
- (void)messageController:(TUIBaseMessageController_Minimalist *)controller onSelectMessageWhenMultiCheckboxAppear:(TUIMessageCellData *)data;
|
||||
|
||||
/// Forward text.
|
||||
- (void)messageController:(TUIBaseMessageController_Minimalist *)controller onForwardText:(NSString *)text;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -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_Minimalist.h"
|
||||
#import "TUIChatConversationModel.h"
|
||||
#import "TUIMessageDataProvider.h"
|
||||
|
||||
@class TUIMessageCellData;
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface TUIBaseMessageController_Minimalist () <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
|
||||
@@ -0,0 +1,111 @@
|
||||
|
||||
// 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_Minimalist.h"
|
||||
#import "TUIChatConversationModel.h"
|
||||
#import "TUIChatDefine.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@class TUIConversationCellData_Minimalist;
|
||||
@class TUIBaseMessageController_Minimalist;
|
||||
@class TUIReplyMessageCell_Minimalist;
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// TUIBaseMessageController_Minimalist
|
||||
//
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/**
|
||||
* 【Module name】TUIBaseMessageController_Minimalist
|
||||
* 【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_Minimalist : UITableViewController
|
||||
|
||||
+ (void)asyncGetDisplayString:(NSArray<V2TIMMessage *> *)messageList callback:(void(^)(NSDictionary<NSString *, NSString *> *))callback;
|
||||
+ (nullable NSString *)getDisplayString:(V2TIMMessage *)message;
|
||||
|
||||
@property(nonatomic, copy) void (^groupRoleChanged)(V2TIMGroupMemberRole role);
|
||||
|
||||
@property(nonatomic, copy) void (^pinGroupMessageChanged)(NSArray *groupPinList);
|
||||
|
||||
@property(nonatomic, weak) id<TUIBaseMessageControllerDelegate_Minimalist> delegate;
|
||||
|
||||
@property(nonatomic, assign) BOOL isInVC;
|
||||
|
||||
/**
|
||||
* Whether a read receipt is required to send a message, the default is NO
|
||||
*/
|
||||
@property(nonatomic) BOOL isMsgNeedReadReceipt;
|
||||
|
||||
- (void)sendMessage:(V2TIMMessage *)msg;
|
||||
|
||||
- (void)sendMessage:(V2TIMMessage *)msg placeHolderCellData:(TUIMessageCellData *)placeHolderCellData;
|
||||
|
||||
- (void)clearUImsg;
|
||||
|
||||
- (void)sendPlaceHolderUIMessage:(TUIMessageCellData *)cellData;
|
||||
|
||||
- (void)scrollToBottom:(BOOL)animate;
|
||||
|
||||
- (void)setConversation:(TUIChatConversationModel *)conversationData;
|
||||
|
||||
/**
|
||||
* 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_Minimalist *)cell;
|
||||
- (void)willShowMediaMessage:(TUIMessageCell *)cell;
|
||||
- (void)didCloseMediaMessage:(TUIMessageCell *)cell;
|
||||
|
||||
/**
|
||||
* Subclass implements click-to-delete message
|
||||
*/
|
||||
- (void)onDelete:(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
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,17 @@
|
||||
//
|
||||
// TUIC2CChatViewController.h
|
||||
// TXIMSDK_TUIKit_iOS
|
||||
//
|
||||
// Created by kayev on 2021/6/17.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
//
|
||||
|
||||
#import "TUIBaseChatViewController_Minimalist.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface TUIC2CChatViewController_Minimalist : TUIBaseChatViewController_Minimalist
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,144 @@
|
||||
//
|
||||
// TUIC2CChatViewController_Minimalist.m
|
||||
// TXIMSDK_TUIKit_iOS
|
||||
//
|
||||
// Created by kayev on 2021/6/17.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
//
|
||||
|
||||
#import "TUIC2CChatViewController_Minimalist.h"
|
||||
#import "TUIBaseChatViewController_Minimalist+ProtectedAPI.h"
|
||||
#import "TUIChatConfig.h"
|
||||
#import "TUICloudCustomDataTypeCenter.h"
|
||||
#import "TUILinkCellData.h"
|
||||
#import "TUIMessageController_Minimalist.h"
|
||||
#import "TUIMessageDataProvider.h"
|
||||
|
||||
#define kC2CTypingTime 30.0
|
||||
|
||||
@interface TUIC2CChatViewController_Minimalist ()
|
||||
|
||||
// If one sendTypingBaseCondation is satisfied, sendTypingBaseCondationInVC is used until the current session exits
|
||||
|
||||
@property(nonatomic, assign) BOOL sendTypingBaseCondationInVC;
|
||||
|
||||
@end
|
||||
|
||||
@implementation TUIC2CChatViewController_Minimalist
|
||||
|
||||
- (void)dealloc {
|
||||
self.sendTypingBaseCondationInVC = NO;
|
||||
}
|
||||
- (void)viewDidLoad {
|
||||
[super viewDidLoad];
|
||||
// Do any additional setup after loading the view.
|
||||
self.sendTypingBaseCondationInVC = NO;
|
||||
}
|
||||
|
||||
#pragma mark - Override Methods
|
||||
- (NSString *)forwardTitleWithMyName:(NSString *)nameStr {
|
||||
NSString *title = [NSString stringWithFormat:TIMCommonLocalizableString(TUIKitRelayChatHistoryForSomebodyFormat), self.conversationData.title, nameStr];
|
||||
return rtlString(title);
|
||||
}
|
||||
|
||||
- (void)inputControllerDidInputAt:(TUIInputController_Minimalist *)inputController {
|
||||
[super inputControllerDidInputAt:inputController];
|
||||
NSAttributedString *spaceString = [[NSAttributedString alloc]
|
||||
initWithString:@"@"
|
||||
attributes:@{NSFontAttributeName : kTUIInputNoramlFont, NSForegroundColorAttributeName : kTUIInputNormalTextColor}];
|
||||
[self.inputController.inputBar addWordsToInputBar:spaceString];
|
||||
}
|
||||
|
||||
- (void)inputControllerBeginTyping:(TUIInputController_Minimalist *)inputController {
|
||||
[super inputControllerBeginTyping:inputController];
|
||||
|
||||
[self sendTypingMsgByStatus:YES];
|
||||
}
|
||||
|
||||
- (void)inputControllerEndTyping:(TUIInputController_Minimalist *)inputController {
|
||||
[super inputControllerEndTyping:inputController];
|
||||
|
||||
[self sendTypingMsgByStatus:NO];
|
||||
}
|
||||
|
||||
- (BOOL)sendTypingBaseCondation {
|
||||
if (self.sendTypingBaseCondationInVC) {
|
||||
return YES;
|
||||
}
|
||||
|
||||
if ([self.messageController isKindOfClass:TUIMessageController_Minimalist.class]) {
|
||||
TUIMessageController_Minimalist *vc = (TUIMessageController_Minimalist *)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
|
||||
@@ -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"
|
||||
|
||||
@import UIKit;
|
||||
|
||||
@class TUIChatSmallTongue_Minimalist;
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@protocol TUIChatSmallTongueViewDelegate_Minimalist <NSObject>
|
||||
|
||||
- (void)onChatSmallTongueClick:(TUIChatSmallTongue_Minimalist *)tongue;
|
||||
|
||||
@end
|
||||
|
||||
@interface TUIChatSmallTongueView_Minimalist : UIView
|
||||
|
||||
@property(nonatomic, weak) id<TUIChatSmallTongueViewDelegate_Minimalist> delegate;
|
||||
|
||||
- (void)setTongue:(TUIChatSmallTongueView_Minimalist *)tongue;
|
||||
|
||||
@end
|
||||
|
||||
@interface TUIChatSmallTongue_Minimalist : NSObject
|
||||
|
||||
@property(nonatomic, assign) TUIChatSmallTongueType type;
|
||||
@property(nonatomic, strong) UIView *parentView;
|
||||
@property(nonatomic, assign) uint64_t unreadMsgCount;
|
||||
@property(nonatomic, strong) NSArray *atMsgSeqs;
|
||||
|
||||
@end
|
||||
|
||||
@interface TUIChatSmallTongueManager_Minimalist : NSObject
|
||||
|
||||
+ (void)showTongue:(TUIChatSmallTongue_Minimalist *)tongue delegate:(id<TUIChatSmallTongueViewDelegate_Minimalist>)delegate;
|
||||
+ (void)removeTongue:(TUIChatSmallTongueType)type;
|
||||
+ (void)removeTongue;
|
||||
+ (void)hideTongue:(BOOL)isHidden;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,225 @@
|
||||
//
|
||||
// TUIChatSmallTongue.m
|
||||
// TUIChat
|
||||
//
|
||||
// Created by xiangzhang on 2022/1/6.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
//
|
||||
|
||||
#import "TUIChatSmallTongueView_Minimalist.h"
|
||||
#import <TIMCommon/TIMDefine.h>
|
||||
#import <TUICore/TUIDarkModel.h>
|
||||
#import <TUICore/TUIThemeManager.h>
|
||||
#import "TUIChatConfig.h"
|
||||
|
||||
#define TongueMiddleSpace 5.f
|
||||
#define TongueRightSpace 10.f
|
||||
#define TongueFontSize 14
|
||||
|
||||
@interface TUIChatSmallTongueView_Minimalist ()
|
||||
|
||||
@property(nonatomic, strong) UIImageView *imageView;
|
||||
@property(nonatomic, strong) UILabel *label;
|
||||
|
||||
@end
|
||||
|
||||
@implementation TUIChatSmallTongueView_Minimalist {
|
||||
TUIChatSmallTongue_Minimalist *_tongue;
|
||||
}
|
||||
|
||||
- (instancetype)initWithFrame:(CGRect)frame {
|
||||
self = [super initWithFrame:frame];
|
||||
if (self) {
|
||||
// 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;
|
||||
|
||||
// backgroudView
|
||||
UIImageView *backgroudView = [[UIImageView alloc] initWithFrame:frame];
|
||||
[self addSubview:backgroudView];
|
||||
backgroudView.mm_fill();
|
||||
backgroudView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
|
||||
UIImage *bkImage = [[TUIImageCache sharedInstance] getResourceFromCache:TUIChatImagePath_Minimalist(@"small_tongue_bk")];
|
||||
bkImage = [bkImage rtl_imageFlippedForRightToLeftLayoutDirection];
|
||||
UIEdgeInsets ei = UIEdgeInsetsFromString(@"{5,12,5,5}");
|
||||
ei = rtlEdgeInsetsWithInsets(ei);
|
||||
backgroudView.image = [bkImage resizableImageWithCapInsets:ei resizingMode:UIImageResizingModeStretch];
|
||||
|
||||
// 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_Minimalist *)tongue {
|
||||
_tongue = tongue;
|
||||
if (!self.imageView) {
|
||||
self.imageView = [[UIImageView alloc] init];
|
||||
[self addSubview:self.imageView];
|
||||
}
|
||||
self.imageView.image = [TUIChatSmallTongueView_Minimalist getTongueImage:tongue];
|
||||
self.imageView.mm_width(kScale390(18)).mm_height(kScale390(18)).mm_left(kScale390(18)).mm_top(kScale390(5));
|
||||
|
||||
if (!self.label) {
|
||||
self.label = [[UILabel alloc] init];
|
||||
self.label.font = [UIFont systemFontOfSize:TongueFontSize];
|
||||
[self addSubview:self.label];
|
||||
}
|
||||
NSString *text = [TUIChatSmallTongueView_Minimalist getTongueText:tongue];
|
||||
;
|
||||
if (text) {
|
||||
self.label.hidden = NO;
|
||||
self.label.text = text;
|
||||
self.label.textAlignment = NSTextAlignmentCenter;
|
||||
self.label.textColor = TUIChatDynamicColor(@"chat_drop_down_color", @"#147AFF");
|
||||
self.label.mm_width(kScale390(16)).mm_height(kScale390(20)).mm_top(self.imageView.mm_b + kScale390(2)).mm__centerX(self.imageView.mm_centerX);
|
||||
} else {
|
||||
self.label.hidden = YES;
|
||||
}
|
||||
}
|
||||
|
||||
+ (CGFloat)getTongueWidth:(TUIChatSmallTongue_Minimalist *)tongue {
|
||||
return kScale390(54);
|
||||
}
|
||||
|
||||
+ (CGFloat)getTongueHeight:(TUIChatSmallTongue_Minimalist *)tongue {
|
||||
CGFloat tongueHeight = 0;
|
||||
switch (tongue.type) {
|
||||
case TUIChatSmallTongueType_ScrollToBoom: {
|
||||
tongueHeight = kScale390(29);
|
||||
} break;
|
||||
case TUIChatSmallTongueType_ReceiveNewMsg: {
|
||||
tongueHeight = kScale390(47);
|
||||
} break;
|
||||
case TUIChatSmallTongueType_SomeoneAt: {
|
||||
tongueHeight = kScale390(47);
|
||||
} break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return tongueHeight;
|
||||
}
|
||||
|
||||
+ (NSString *)getTongueText:(TUIChatSmallTongue_Minimalist *)tongue {
|
||||
NSString *tongueText = nil;
|
||||
switch (tongue.type) {
|
||||
case TUIChatSmallTongueType_ScrollToBoom: {
|
||||
tongueText = nil;
|
||||
} break;
|
||||
case TUIChatSmallTongueType_ReceiveNewMsg: {
|
||||
tongueText = [NSString stringWithFormat:@"%@", tongue.unreadMsgCount > 99 ? @"99+" : @(tongue.unreadMsgCount)];
|
||||
} break;
|
||||
case TUIChatSmallTongueType_SomeoneAt: {
|
||||
tongueText = [NSString stringWithFormat:@"%@", tongue.atMsgSeqs.count > 99 ? @"99+" : @(tongue.atMsgSeqs.count)];
|
||||
} break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return tongueText;
|
||||
}
|
||||
|
||||
+ (UIImage *)getTongueImage:(TUIChatSmallTongue_Minimalist *)tongue {
|
||||
UIImage *tongueImage = nil;
|
||||
switch (tongue.type) {
|
||||
case TUIChatSmallTongueType_ScrollToBoom: {
|
||||
tongueImage = [[TUIImageCache sharedInstance] getResourceFromCache:TUIChatImagePath_Minimalist(@"small_tongue_scroll_to_boom")];
|
||||
} break;
|
||||
case TUIChatSmallTongueType_ReceiveNewMsg: {
|
||||
tongueImage = [[TUIImageCache sharedInstance] getResourceFromCache:TUIChatImagePath_Minimalist(@"small_tongue_scroll_to_boom")];
|
||||
break;
|
||||
}
|
||||
case TUIChatSmallTongueType_SomeoneAt: {
|
||||
tongueImage = [[TUIImageCache sharedInstance] getResourceFromCache:TUIChatImagePath_Minimalist(@"small_tongue_someone_at_me")];
|
||||
} break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return tongueImage;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation TUIChatSmallTongue_Minimalist
|
||||
|
||||
- (instancetype)init {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
self.type = TUIChatSmallTongueType_None;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
static TUIChatSmallTongueView_Minimalist *gTongueView = nil;
|
||||
static TUIChatSmallTongue_Minimalist *gTongue = nil;
|
||||
|
||||
@implementation TUIChatSmallTongueManager_Minimalist
|
||||
|
||||
+ (void)showTongue:(TUIChatSmallTongue_Minimalist *)tongue delegate:(id<TUIChatSmallTongueViewDelegate_Minimalist>)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_Minimalist alloc] init];
|
||||
} else {
|
||||
[gTongueView removeFromSuperview];
|
||||
}
|
||||
CGFloat tongueWidth = [TUIChatSmallTongueView_Minimalist getTongueWidth:gTongue];
|
||||
CGFloat tongueHeight = [TUIChatSmallTongueView_Minimalist getTongueHeight:gTongue];
|
||||
if(isRTL()) {
|
||||
gTongueView.frame =
|
||||
CGRectMake(kScale390(16),
|
||||
tongue.parentView.mm_h - Bottom_SafeHeight - TTextView_Height - 20 - tongueHeight,
|
||||
tongueWidth, tongueHeight);
|
||||
}
|
||||
else {
|
||||
gTongueView.frame =
|
||||
CGRectMake(tongue.parentView.mm_w - kScale390(54),
|
||||
tongue.parentView.mm_h - Bottom_SafeHeight - TTextView_Height - 20 - tongueHeight,
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,16 @@
|
||||
//
|
||||
// FileViewController.h
|
||||
// TUIKitDemo
|
||||
//
|
||||
// Created by kennethmiao on 2018/11/12.
|
||||
// Copyright © 2018 Tencent. All rights reserved.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
#import "TUIFileMessageCell_Minimalist.h"
|
||||
|
||||
@interface TUIFileViewController_Minimalist : UIViewController
|
||||
@property(nonatomic, strong) TUIFileMessageCellData *data;
|
||||
@property(nonatomic, copy) void (^dismissClickCallback)(void);
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,118 @@
|
||||
//
|
||||
// FileViewController.m
|
||||
// TUIKitDemo
|
||||
//
|
||||
// Created by kennethmiao on 2018/11/12.
|
||||
// Copyright © 2018 Tencent. All rights reserved.
|
||||
//
|
||||
|
||||
#import "TUIFileViewController_Minimalist.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_Minimalist () <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_Minimalist
|
||||
|
||||
- (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 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 {
|
||||
if (self.dismissClickCallback) {
|
||||
self.dismissClickCallback();
|
||||
}
|
||||
[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
|
||||
@@ -0,0 +1,16 @@
|
||||
//
|
||||
// TUIGroupChatViewController_Minimalist.h
|
||||
// TXIMSDK_TUIKit_iOS
|
||||
//
|
||||
// Created by kayev on 2021/6/17.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
//
|
||||
|
||||
#import "TUIBaseChatViewController_Minimalist.h"
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface TUIGroupChatViewController_Minimalist : TUIBaseChatViewController_Minimalist
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,433 @@
|
||||
//
|
||||
// TUIGroupChatViewController.m
|
||||
// TXIMSDK_TUIKit_iOS
|
||||
//
|
||||
// Created by kayev on 2021/6/17.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
//
|
||||
|
||||
#import "ReactiveObjC/ReactiveObjC.h"
|
||||
|
||||
#import <TIMCommon/NSString+TUIEmoji.h>
|
||||
#import <TIMCommon/TIMCommonModel.h>
|
||||
#import <TIMCommon/TIMDefine.h>
|
||||
#import <TUICore/NSDictionary+TUISafe.h>
|
||||
#import <TUICore/TUICore.h>
|
||||
#import <TUICore/TUILogin.h>
|
||||
#import "TUIBaseChatViewController_Minimalist+ProtectedAPI.h"
|
||||
#import "TUIGroupChatViewController_Minimalist.h"
|
||||
#import "TUIGroupPendencyController.h"
|
||||
#import "TUIGroupPendencyDataProvider.h"
|
||||
#import "TUILinkCellData.h"
|
||||
#import "TUIMessageDataProvider.h"
|
||||
#import "TUITextMessageCellData.h"
|
||||
#import "TUIMessageController_Minimalist.h"
|
||||
#import "TUIGroupPinCell.h"
|
||||
#import "TUIGroupPinPageViewController.h"
|
||||
|
||||
@interface TUIGroupChatViewController_Minimalist () <V2TIMGroupListener>
|
||||
|
||||
//@property (nonatomic, strong) UIButton *atBtn;
|
||||
@property(nonatomic, strong) UIView *tipsView;
|
||||
@property(nonatomic, strong) UILabel *pendencyLabel;
|
||||
@property(nonatomic, strong) UIButton *pendencyBtn;
|
||||
|
||||
@property(nonatomic, strong) TUIGroupPendencyDataProvider *pendencyViewModel;
|
||||
@property(nonatomic, strong) NSMutableArray<TUIUserModel *> *atUserList;
|
||||
@property(nonatomic, assign) BOOL responseKeyboard;
|
||||
@property(nonatomic, strong) TUIGroupPinCellView *oneGroupPinView;
|
||||
@property(nonatomic, strong) NSArray *groupPinList;
|
||||
@property(nonatomic, strong) TUIGroupPinPageViewController *pinPageVC;
|
||||
@end
|
||||
|
||||
@implementation TUIGroupChatViewController_Minimalist
|
||||
|
||||
- (void)viewDidLoad {
|
||||
[super viewDidLoad];
|
||||
// Do any additional setup after loading the view.
|
||||
[self setupTipsView];
|
||||
[self setupGroupPinTips];
|
||||
|
||||
[[V2TIMManager sharedInstance] addGroupListener:self];
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self
|
||||
selector:@selector(refreshTipsView)
|
||||
name:TUICore_TUIChatExtension_ChatViewTopArea_ChangedNotification
|
||||
object:nil];
|
||||
}
|
||||
|
||||
- (void)viewWillAppear:(BOOL)animated {
|
||||
[super viewWillAppear:animated];
|
||||
[self refreshTipsView];
|
||||
}
|
||||
|
||||
- (void)dealloc {
|
||||
[TUICore unRegisterEventByObject:self];
|
||||
}
|
||||
|
||||
- (void)setupTipsView {
|
||||
self.tipsView = [[UIView alloc] initWithFrame:CGRectZero];
|
||||
self.tipsView.backgroundColor = RGB(246, 234, 190);
|
||||
[self.view addSubview:self.tipsView];
|
||||
self.tipsView.mm_height(24).mm_width(self.view.mm_w);
|
||||
|
||||
self.pendencyLabel = [[UILabel alloc] initWithFrame:CGRectZero];
|
||||
[self.tipsView addSubview:self.pendencyLabel];
|
||||
self.pendencyLabel.font = [UIFont systemFontOfSize:12];
|
||||
|
||||
self.pendencyBtn = [UIButton buttonWithType:UIButtonTypeSystem];
|
||||
[self.tipsView addSubview:self.pendencyBtn];
|
||||
[self.pendencyBtn setTitle:TIMCommonLocalizableString(TUIKitChatPendencyTitle) forState:UIControlStateNormal];
|
||||
[self.pendencyBtn.titleLabel setFont:[UIFont systemFontOfSize:12]];
|
||||
[self.pendencyBtn addTarget:self action:@selector(openPendency:) forControlEvents:UIControlEventTouchUpInside];
|
||||
[self.pendencyBtn sizeToFit];
|
||||
self.tipsView.alpha = 0;
|
||||
|
||||
@weakify(self);
|
||||
[RACObserve(self.pendencyViewModel, unReadCnt) subscribeNext:^(NSNumber *unReadCnt) {
|
||||
@strongify(self);
|
||||
if ([unReadCnt intValue]) {
|
||||
self.pendencyLabel.text = [NSString stringWithFormat:TIMCommonLocalizableString(TUIKitChatPendencyRequestToJoinGroupFormat), unReadCnt];
|
||||
[self.pendencyLabel sizeToFit];
|
||||
CGFloat gap = (self.tipsView.mm_w - self.pendencyLabel.mm_w - self.pendencyBtn.mm_w - 8) / 2;
|
||||
self.pendencyLabel.mm_left(gap).mm__centerY(self.tipsView.mm_h / 2);
|
||||
self.pendencyBtn.mm_hstack(8);
|
||||
self.tipsView.alpha = 1;
|
||||
[self refreshTipsView];
|
||||
} else {
|
||||
self.tipsView.alpha = 0;
|
||||
}
|
||||
}];
|
||||
|
||||
[self getPendencyList];
|
||||
}
|
||||
|
||||
- (void)refreshTipsView {
|
||||
UIView *topView = [TUIGroupChatViewController_Minimalist topAreaBottomView];
|
||||
CGRect transRect = [topView convertRect:topView.bounds toView:self.view];
|
||||
self.tipsView.frame = CGRectMake(0, transRect.origin.y + transRect.size.height, self.tipsView.frame.size.width, self.tipsView.frame.size.height);
|
||||
}
|
||||
|
||||
- (void)setupGroupPinTips {
|
||||
self.oneGroupPinView = [[TUIGroupPinCellView alloc] init];
|
||||
CGFloat margin = 0 ;
|
||||
self.oneGroupPinView.frame = CGRectMake(0, margin, self.view.frame.size.width, 62);
|
||||
UIView *topView = [TUIGroupChatViewController_Minimalist groupPinTopView];
|
||||
for (UIView *subview in topView.subviews) {
|
||||
[subview removeFromSuperview];
|
||||
}
|
||||
topView.frame = CGRectMake(0, 0, self.view.mm_w, 0);
|
||||
[topView addSubview:self.oneGroupPinView];
|
||||
@weakify(self);
|
||||
self.oneGroupPinView.isFirstPage = YES;
|
||||
self.oneGroupPinView.onClickCellView = ^(V2TIMMessage *originMessage) {
|
||||
@strongify(self);
|
||||
if (self.groupPinList.count >= 2) {
|
||||
[self gotoDetailPopPinPage];
|
||||
}
|
||||
else {
|
||||
[self jump2GroupPinHighlightLine:originMessage];
|
||||
}
|
||||
|
||||
};
|
||||
self.oneGroupPinView.onClickRemove = ^(V2TIMMessage *originMessage) {
|
||||
@strongify(self);
|
||||
[self.messageController unPinGroupMessage:originMessage];
|
||||
};
|
||||
self.messageController.pinGroupMessageChanged = ^(NSArray * _Nonnull groupPinList) {
|
||||
@strongify(self);
|
||||
if (groupPinList.count > 0 ) {
|
||||
if (!self.oneGroupPinView.superview) {
|
||||
[topView addSubview:self.oneGroupPinView];
|
||||
}
|
||||
TUIMessageCellData * cellData = [TUIMessageDataProvider getCellData:[groupPinList lastObject]];
|
||||
[self.oneGroupPinView fillWithData:cellData];
|
||||
if (groupPinList.count >= 2) {
|
||||
[self.oneGroupPinView showMultiAnimation];
|
||||
topView.frame = CGRectMake(0, 0, self.view.mm_w, self.oneGroupPinView.frame.size.height +20 + margin);
|
||||
self.oneGroupPinView.removeButton.hidden = YES;
|
||||
}
|
||||
else {
|
||||
[self.oneGroupPinView hiddenMultiAnimation];
|
||||
topView.frame = CGRectMake(0, 0, self.view.mm_w, self.oneGroupPinView.frame.size.height + margin);
|
||||
if ([self.messageController isCurrentUserRoleSuperAdminInGroup]) {
|
||||
self.oneGroupPinView.removeButton.hidden = NO;
|
||||
}
|
||||
else {
|
||||
self.oneGroupPinView.removeButton.hidden = YES;
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
[self.oneGroupPinView removeFromSuperview];
|
||||
topView.frame = CGRectMake(0, 0, self.view.mm_w, 0);
|
||||
}
|
||||
self.groupPinList = groupPinList;
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:TUICore_TUIChatExtension_ChatViewTopArea_ChangedNotification object:nil];
|
||||
if (self.pinPageVC) {
|
||||
NSMutableArray *formatGroupPinList = [NSMutableArray arrayWithArray:groupPinList.reverseObjectEnumerator.allObjects];
|
||||
self.pinPageVC.groupPinList = formatGroupPinList;
|
||||
self.pinPageVC.canRemove = [self.messageController isCurrentUserRoleSuperAdminInGroup];
|
||||
if (groupPinList.count > 0) {
|
||||
[self reloadPopPinPage];
|
||||
}
|
||||
else {
|
||||
[self.pinPageVC dismissViewControllerAnimated:NO completion:nil];
|
||||
}
|
||||
}
|
||||
};
|
||||
self.messageController.groupRoleChanged = ^(V2TIMGroupMemberRole role) {
|
||||
@strongify(self);
|
||||
self.messageController.pinGroupMessageChanged(self.groupPinList);
|
||||
if (self.pinPageVC) {
|
||||
self.pinPageVC.canRemove = [self.messageController isCurrentUserRoleSuperAdminInGroup];
|
||||
[self.pinPageVC.tableview reloadData];
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
- (void)gotoDetailPopPinPage {
|
||||
TUIGroupPinPageViewController *vc = [[TUIGroupPinPageViewController alloc] init];
|
||||
self.pinPageVC = vc;
|
||||
NSMutableArray *formatGroupPinList = [NSMutableArray arrayWithArray:self.groupPinList.reverseObjectEnumerator.allObjects];
|
||||
vc.groupPinList = formatGroupPinList;
|
||||
vc.canRemove = [self.messageController isCurrentUserRoleSuperAdminInGroup];
|
||||
vc.view.frame = self.view.frame;
|
||||
CGFloat cellHight = (62);
|
||||
CGFloat maxOnePage = 4;
|
||||
float height = (self.groupPinList.count) * cellHight;
|
||||
height = MIN(cellHight * maxOnePage , height);
|
||||
UIView *topView = [TUIGroupChatViewController_Minimalist groupPinTopView];
|
||||
CGRect transRect = [topView convertRect:topView.bounds toView:[UIApplication sharedApplication].delegate.window];
|
||||
vc.modalPresentationStyle = UIModalPresentationOverFullScreen;
|
||||
@weakify(self);
|
||||
[self presentViewController:vc animated:NO completion:^{
|
||||
vc.tableview.frame = CGRectMake(0, CGRectGetMinY(transRect), self.view.frame.size.width, 60);
|
||||
vc.customArrowView.frame = CGRectMake(0, CGRectGetMaxY(vc.tableview.frame), vc.tableview.frame.size.width, 0);
|
||||
vc.bottomShadow.frame = CGRectMake(0, CGRectGetMaxY(vc.customArrowView.frame), vc.tableview.frame.size.width, 0);
|
||||
[UIView animateWithDuration:0.3 animations:^{
|
||||
vc.tableview.frame = CGRectMake(0, CGRectGetMinY(transRect), self.view.frame.size.width, height);
|
||||
vc.customArrowView.frame = CGRectMake(0, CGRectGetMaxY(vc.tableview.frame), vc.tableview.frame.size.width, 40);
|
||||
vc.bottomShadow.frame = CGRectMake(0, CGRectGetMaxY(vc.customArrowView.frame),
|
||||
vc.tableview.frame.size.width,
|
||||
self.view.frame.size.height);
|
||||
UIBezierPath *maskPath = [UIBezierPath bezierPathWithRoundedRect:vc.customArrowView.bounds
|
||||
byRoundingCorners:(UIRectCornerBottomLeft | UIRectCornerBottomRight)
|
||||
cornerRadii:CGSizeMake(10.0, 10.0)];
|
||||
CAShapeLayer *maskLayer = [[CAShapeLayer alloc] init];
|
||||
maskLayer.frame = vc.customArrowView.bounds;
|
||||
maskLayer.path = maskPath.CGPath;
|
||||
vc.customArrowView.layer.mask = maskLayer;
|
||||
}];
|
||||
}];
|
||||
|
||||
vc.onClickRemove = ^(V2TIMMessage *originMessage) {
|
||||
@strongify(self);
|
||||
[self.messageController unPinGroupMessage:originMessage];
|
||||
};
|
||||
|
||||
vc.onClickCellView = ^(V2TIMMessage *originMessage) {
|
||||
@strongify(self);
|
||||
[self jump2GroupPinHighlightLine:originMessage];
|
||||
};
|
||||
|
||||
}
|
||||
- (void)jump2GroupPinHighlightLine:(V2TIMMessage *)originMessage {
|
||||
TUIMessageController_Minimalist *msgVC = (TUIMessageController_Minimalist *)self.messageController;
|
||||
NSString * originMsgID = originMessage.msgID;
|
||||
[msgVC findMessages:@[originMsgID ?: @""] callback:^(BOOL success, NSString * _Nonnull desc, NSArray<V2TIMMessage *> * _Nonnull messages) {
|
||||
if (success) {
|
||||
V2TIMMessage *message = messages.firstObject;
|
||||
if (message && message.status == V2TIM_MSG_STATUS_SEND_SUCC ) {
|
||||
[msgVC locateAssignMessage:originMessage matchKeyWord:@""];
|
||||
}
|
||||
else {
|
||||
[TUITool makeToast:TIMCommonLocalizableString(TUIKitReplyMessageNotFoundOriginMessage)];
|
||||
}
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)reloadPopPinPage {
|
||||
CGFloat cellHight = (62);
|
||||
CGFloat maxOnePage = 4;
|
||||
float height = (self.groupPinList.count) * cellHight;
|
||||
height = MIN(cellHight * maxOnePage , height);
|
||||
self.pinPageVC.tableview.frame = CGRectMake(0, self.pinPageVC.tableview.frame.origin.y, self.view.frame.size.width, height);
|
||||
self.pinPageVC.customArrowView.frame = CGRectMake(0, CGRectGetMaxY(self.pinPageVC.tableview.frame), self.pinPageVC.tableview.frame.size.width, 40);
|
||||
self.pinPageVC.bottomShadow.frame = CGRectMake(0, CGRectGetMaxY(self.pinPageVC.customArrowView.frame),
|
||||
self.pinPageVC.tableview.frame.size.width,
|
||||
self.view.frame.size.height);
|
||||
[self.pinPageVC.tableview reloadData];
|
||||
}
|
||||
|
||||
- (void)getPendencyList {
|
||||
if (self.conversationData.groupID.length > 0) [self.pendencyViewModel loadData];
|
||||
}
|
||||
|
||||
- (void)openPendency:(id)sender {
|
||||
TUIGroupPendencyController *vc = [[TUIGroupPendencyController alloc] init];
|
||||
@weakify(self);
|
||||
vc.cellClickBlock = ^(TUIGroupPendencyCell *_Nonnull cell) {
|
||||
if (cell.pendencyData.isRejectd || cell.pendencyData.isAccepted) {
|
||||
// No longer enter the details page after selection
|
||||
return;
|
||||
}
|
||||
@strongify(self);
|
||||
[[V2TIMManager sharedInstance] getUsersInfo:@[ cell.pendencyData.fromUser ]
|
||||
succ:^(NSArray<V2TIMUserFullInfo *> *profiles) {
|
||||
// Show user profile VC
|
||||
NSDictionary *param = @{
|
||||
TUICore_TUIContactObjectFactory_UserProfileController_UserProfile : profiles.firstObject,
|
||||
TUICore_TUIContactObjectFactory_UserProfileController_PendencyData : cell.pendencyData,
|
||||
TUICore_TUIContactObjectFactory_UserProfileController_ActionType : @(3)
|
||||
};
|
||||
[self.navigationController pushViewController:TUICore_TUIContactObjectFactory_UserProfileController_Minimalist
|
||||
param:param
|
||||
forResult:nil];
|
||||
}
|
||||
fail:nil];
|
||||
};
|
||||
vc.viewModel = self.pendencyViewModel;
|
||||
[self.navigationController pushViewController:vc animated:YES];
|
||||
}
|
||||
|
||||
- (void)setConversationData:(TUIChatConversationModel *)conversationData {
|
||||
[super setConversationData:conversationData];
|
||||
|
||||
if (self.conversationData.groupID.length > 0) {
|
||||
_pendencyViewModel = [TUIGroupPendencyDataProvider new];
|
||||
_pendencyViewModel.groupId = conversationData.groupID;
|
||||
}
|
||||
|
||||
self.atUserList = [NSMutableArray array];
|
||||
}
|
||||
|
||||
#pragma mark - V2TIMGroupListener
|
||||
- (void)onReceiveJoinApplication:(NSString *)groupID member:(V2TIMGroupMemberInfo *)member opReason:(NSString *)opReason {
|
||||
[self getPendencyList];
|
||||
}
|
||||
|
||||
- (void)onGroupInfoChanged:(NSString *)groupID changeInfoList:(NSArray<V2TIMGroupChangeInfo *> *)changeInfoList {
|
||||
if (![groupID isEqualToString:self.conversationData.groupID]) {
|
||||
return;
|
||||
}
|
||||
for (V2TIMGroupChangeInfo *changeInfo in changeInfoList) {
|
||||
if (changeInfo.type == V2TIM_GROUP_INFO_CHANGE_TYPE_NAME) {
|
||||
self.conversationData.title = changeInfo.value;
|
||||
} else if (changeInfo.type == V2TIM_GROUP_INFO_CHANGE_TYPE_FACE) {
|
||||
self.conversationData.faceUrl = changeInfo.value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - TUIInputControllerDelegate
|
||||
- (void)inputController:(TUIInputController_Minimalist *)inputController didSendMessage:(V2TIMMessage *)msg {
|
||||
/**
|
||||
* If the text message has @ user, createTextAtMessage is required
|
||||
*/
|
||||
if (msg.elemType == V2TIM_ELEM_TYPE_TEXT) {
|
||||
NSMutableArray *atUserList = [NSMutableArray array];
|
||||
for (TUIUserModel *model in self.atUserList) {
|
||||
if (model.userId) {
|
||||
[atUserList addObject:model.userId];
|
||||
}
|
||||
}
|
||||
if (atUserList.count > 0) {
|
||||
NSData *cloudCustomData = msg.cloudCustomData;
|
||||
msg = [[V2TIMManager sharedInstance] createTextAtMessage:msg.textElem.text atUserList:atUserList];
|
||||
msg.cloudCustomData = cloudCustomData;
|
||||
}
|
||||
/**
|
||||
* After the message is sent, the atUserList need to be reset
|
||||
*/
|
||||
[self.atUserList removeAllObjects];
|
||||
}
|
||||
[super inputController:inputController didSendMessage:msg];
|
||||
}
|
||||
|
||||
- (void)inputControllerDidInputAt:(TUIInputController_Minimalist *)inputController {
|
||||
[super inputControllerDidInputAt:inputController];
|
||||
/**
|
||||
* Input of @ character detected
|
||||
*/
|
||||
if (self.conversationData.groupID.length > 0) {
|
||||
if ([self.navigationController.topViewController isKindOfClass:NSClassFromString(@"TUISelectGroupMemberViewController_Minimalist")]) {
|
||||
return;
|
||||
}
|
||||
//When pushing a new VC, the keyboard needs to be hidden.
|
||||
[self.inputController reset];
|
||||
__weak typeof(self) weakSelf = self;
|
||||
NSMutableDictionary *param = [NSMutableDictionary dictionary];
|
||||
param[TUICore_TUIContactObjectFactory_SelectGroupMemberVC_GroupID] = self.conversationData.groupID;
|
||||
param[TUICore_TUIContactObjectFactory_SelectGroupMemberVC_Name] = TIMCommonLocalizableString(TUIKitAtSelectMemberTitle);
|
||||
param[TUICore_TUIContactObjectFactory_SelectGroupMemberVC_OptionalStyle] = @(1);
|
||||
[self.navigationController
|
||||
pushViewController:TUICore_TUIContactObjectFactory_SelectGroupMemberVC_Minimalist
|
||||
param:param
|
||||
forResult:^(NSDictionary *_Nonnull param) {
|
||||
NSArray<TUIUserModel *> *modelList = [param tui_objectForKey:TUICore_TUIContactObjectFactory_SelectGroupMemberVC_ResultUserList
|
||||
asClass:NSArray.class];
|
||||
NSMutableString *atText = [[NSMutableString alloc] init];
|
||||
for (int i = 0; i < modelList.count; i++) {
|
||||
TUIUserModel *model = modelList[i];
|
||||
if (![model isKindOfClass:TUIUserModel.class]) {
|
||||
NSAssert(NO, @"Error data-type in modelList");
|
||||
continue;
|
||||
}
|
||||
[self.atUserList addObject:model];
|
||||
[atText appendString:[NSString stringWithFormat:@"@%@ ", model.name]];
|
||||
}
|
||||
|
||||
UIFont *textFont = kTUIInputNoramlFont;
|
||||
NSAttributedString *spaceString = [[NSAttributedString alloc] initWithString:atText attributes:@{NSFontAttributeName : textFont}];
|
||||
[weakSelf.inputController.inputBar addWordsToInputBar:spaceString ];
|
||||
}];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)inputController:(TUIInputController_Minimalist *)inputController didDeleteAt:(NSString *)atText {
|
||||
[super inputController:inputController didDeleteAt:atText];
|
||||
|
||||
for (TUIUserModel *user in self.atUserList) {
|
||||
if ([atText rangeOfString:user.name].location != NSNotFound) {
|
||||
[self.atUserList removeObject:user];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - TUIBaseMessageControllerDelegate
|
||||
- (void)messageController:(TUIBaseMessageController_Minimalist *)controller onLongSelectMessageAvatar:(TUIMessageCell *)cell {
|
||||
if (!cell || !cell.messageData || !cell.messageData.identifier) {
|
||||
return;
|
||||
}
|
||||
if ([cell.messageData.identifier isEqualToString:[TUILogin getUserID]]) {
|
||||
return;
|
||||
}
|
||||
BOOL atUserExist = NO;
|
||||
for (TUIUserModel *model in self.atUserList) {
|
||||
if ([model.userId isEqualToString:cell.messageData.identifier]) {
|
||||
atUserExist = YES;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!atUserExist) {
|
||||
TUIUserModel *user = [[TUIUserModel alloc] init];
|
||||
user.userId = cell.messageData.identifier;
|
||||
user.name = cell.messageData.senderName;
|
||||
[self.atUserList addObject:user];
|
||||
|
||||
NSString *nameString = [NSString stringWithFormat:@"@%@ ", user.name];
|
||||
UIFont *textFont = kTUIInputNoramlFont;
|
||||
NSAttributedString *spaceString = [[NSAttributedString alloc] initWithString:nameString attributes:@{NSFontAttributeName : textFont}];
|
||||
[self.inputController.inputBar addWordsToInputBar:spaceString ];
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - Override Methods
|
||||
- (NSString *)forwardTitleWithMyName:(NSString *)nameStr {
|
||||
return TIMCommonLocalizableString(TUIKitRelayGroupChatHistory);
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,24 @@
|
||||
|
||||
// Created by Tencent on 2023/06/09.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
@class TUIGroupInfoController_Minimalist;
|
||||
@class TUIGroupMemberCellData;
|
||||
@class V2TIMGroupInfo;
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// TUIGroupInfoController
|
||||
//
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@interface TUIGroupInfoController_Minimalist : UIViewController
|
||||
|
||||
@property(nonatomic, strong) UITableView *tableView;
|
||||
@property(nonatomic, strong) NSString *groupId;
|
||||
|
||||
- (void)updateData;
|
||||
- (void)updateGroupInfo;
|
||||
@end
|
||||
@@ -0,0 +1,710 @@
|
||||
|
||||
// Created by Tencent on 2023/06/09.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
#import "TUIGroupInfoController_Minimalist.h"
|
||||
#import <TIMCommon/TIMCommonModel.h>
|
||||
#import <TIMCommon/TIMDefine.h>
|
||||
#import <TUICore/TUICore.h>
|
||||
#import <TUICore/TUILogin.h>
|
||||
#import <TUICore/TUIThemeManager.h>
|
||||
#import <TIMCommon/TIMGroupInfo+TUIDataProvider.h>
|
||||
#import <TIMCommon/TUICommonGroupInfoCellData_Minimalist.h>
|
||||
#import <TIMCommon/TUIGroupButtonCell_Minimalist.h>
|
||||
#import "TUIGroupInfoDataProvider_Minimalist.h"
|
||||
#import "TUIGroupNoticeCell.h"
|
||||
#import "TUIGroupNoticeController_Minimalist.h"
|
||||
#import "TUIGroupProfileCardViewCell_Minimalist.h"
|
||||
|
||||
@interface TUIGroupInfoController_Minimalist () <TUIModifyViewDelegate,
|
||||
TUIProfileCardDelegate,
|
||||
TUIGroupInfoDataProviderDelegate_Minimalist,
|
||||
UITableViewDelegate,
|
||||
UITableViewDataSource>
|
||||
@property(nonatomic, strong) TUIGroupInfoDataProvider_Minimalist *dataProvider;
|
||||
@property(nonatomic, strong) TUINaviBarIndicatorView *titleView;
|
||||
@end
|
||||
|
||||
@implementation TUIGroupInfoController_Minimalist
|
||||
|
||||
- (void)viewDidLoad {
|
||||
[super viewDidLoad];
|
||||
|
||||
self.dataProvider = [[TUIGroupInfoDataProvider_Minimalist alloc] initWithGroupID:self.groupId];
|
||||
self.dataProvider.delegate = self;
|
||||
|
||||
[self setupViews];
|
||||
[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 = [[UITableView alloc] init];
|
||||
self.tableView.delegate = self;
|
||||
self.tableView.dataSource = self;
|
||||
self.tableView.backgroundColor = TIMCommonDynamicColor(@"", @"#FFFFFF");
|
||||
self.tableView.delaysContentTouches = NO;
|
||||
self.tableView.separatorStyle = UITableViewCellSeparatorStyleSingleLine;
|
||||
self.tableView.separatorColor = [UIColor whiteColor];
|
||||
self.tableView.separatorInset = UIEdgeInsetsMake(0, -58, 0, 0);
|
||||
if (@available(iOS 15.0, *)) {
|
||||
self.tableView.sectionHeaderTopPadding = 0;
|
||||
}
|
||||
self.tableView.frame = self.view.bounds;
|
||||
[self.view addSubview:self.tableView];
|
||||
|
||||
self.tableView.tableFooterView = [[UIView alloc] init];
|
||||
TUIGroupProfileHeaderView_Minimalist *headerView = [[TUIGroupProfileHeaderView_Minimalist alloc] init];
|
||||
self.tableView.tableHeaderView = headerView;
|
||||
[self updateGroupInfo];
|
||||
|
||||
@weakify(self);
|
||||
headerView.headImgClickBlock = ^{
|
||||
@strongify(self);
|
||||
[self didSelectAvatar];
|
||||
};
|
||||
headerView.editBtnClickBlock = ^{
|
||||
@strongify(self);
|
||||
[self didSelectEditGroupName];
|
||||
};
|
||||
|
||||
// Extension
|
||||
NSMutableArray<TUIGroupProfileHeaderItemView_Minimalist *> *itemViewList = [NSMutableArray array];
|
||||
NSMutableDictionary *param = [NSMutableDictionary dictionary];
|
||||
if (self.groupId.length > 0) {
|
||||
param[TUICore_TUIContactExtension_GroupInfoCardActionMenu_GroupID] = self.groupId;
|
||||
}
|
||||
param[TUICore_TUIContactExtension_GroupInfoCardActionMenu_FilterVideoCall] = @(NO);
|
||||
param[TUICore_TUIContactExtension_GroupInfoCardActionMenu_FilterAudioCall] = @(NO);
|
||||
if (self.navigationController) {
|
||||
param[TUICore_TUIContactExtension_GroupInfoCardActionMenu_PushVC] = self.navigationController;
|
||||
}
|
||||
NSArray<TUIExtensionInfo *> *extensionList = [TUICore getExtensionList:
|
||||
TUICore_TUIContactExtension_GroupInfoCardActionMenu_MinimalistExtensionID
|
||||
param:param];
|
||||
for (TUIExtensionInfo *info in extensionList) {
|
||||
if (info.icon && info.text && info.onClicked) {
|
||||
TUIGroupProfileHeaderItemView_Minimalist *itemView = [[TUIGroupProfileHeaderItemView_Minimalist alloc] init];
|
||||
itemView.iconView.image = info.icon;
|
||||
itemView.textLabel.text = info.text;
|
||||
itemView.messageBtnClickBlock = ^{
|
||||
info.onClicked(param);
|
||||
};
|
||||
[itemViewList addObject:itemView];
|
||||
}
|
||||
}
|
||||
headerView.itemViewList = itemViewList;
|
||||
|
||||
if (itemViewList.count > 0) {
|
||||
headerView.frame = CGRectMake(0, 0, self.tableView.bounds.size.width, 355);
|
||||
} else {
|
||||
headerView.frame = CGRectMake(0, 0, self.tableView.bounds.size.width, 257);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)updateData {
|
||||
[self.dataProvider loadData];
|
||||
}
|
||||
|
||||
- (void)updateGroupInfo {
|
||||
__weak typeof(self) weakSelf = self;
|
||||
[self.dataProvider updateGroupInfo:^{
|
||||
TUIGroupProfileHeaderView_Minimalist *headerView = (TUIGroupProfileHeaderView_Minimalist *)self.tableView.tableHeaderView;
|
||||
headerView.groupInfo = weakSelf.dataProvider.groupInfo;
|
||||
}];
|
||||
}
|
||||
|
||||
#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:[TUIGroupMemberCellData_Minimalist class]]) {
|
||||
return [(TUIGroupMemberCellData_Minimalist *)data heightOfWidth:Screen_Width];
|
||||
} else if ([data isKindOfClass:[TUIGroupButtonCellData_Minimalist class]]) {
|
||||
return [(TUIGroupButtonCellData_Minimalist *)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 kScale390(55);
|
||||
}
|
||||
|
||||
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
|
||||
NSMutableArray *array = self.dataProvider.dataList[indexPath.section];
|
||||
NSObject *data = array[indexPath.row];
|
||||
@weakify(self);
|
||||
|
||||
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];
|
||||
cell.backgroundColor = TIMCommonDynamicColor(@"", @"#f9f9f9");
|
||||
cell.contentView.backgroundColor = TIMCommonDynamicColor(@"", @"#f9f9f9");
|
||||
|
||||
return cell;
|
||||
} else if ([data isKindOfClass:[TUIGroupMemberCellData_Minimalist 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,
|
||||
};
|
||||
[TUICore raiseExtension:
|
||||
TUICore_TUIChatExtension_GroupProfileMemberListExtension_MinimalistExtensionID
|
||||
parentView:cell
|
||||
param:param];
|
||||
cell.backgroundColor = TIMCommonDynamicColor(@"", @"#f9f9f9");
|
||||
cell.contentView.backgroundColor = TIMCommonDynamicColor(@"", @"#f9f9f9");
|
||||
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];
|
||||
cell.backgroundColor = TIMCommonDynamicColor(@"", @"#f9f9f9");
|
||||
cell.contentView.backgroundColor = TIMCommonDynamicColor(@"", @"#f9f9f9");
|
||||
return cell;
|
||||
} else if ([data isKindOfClass:[TUIGroupButtonCellData_Minimalist class]]) {
|
||||
TUIGroupButtonCell_Minimalist *cell = [tableView dequeueReusableCellWithIdentifier:TButtonCell_ReuseId];
|
||||
if (!cell) {
|
||||
cell = [[TUIGroupButtonCell_Minimalist alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:TButtonCell_ReuseId];
|
||||
}
|
||||
[cell fillWithData:(TUIGroupButtonCellData_Minimalist *)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.backgroundColor = TIMCommonDynamicColor(@"", @"#f9f9f9");
|
||||
cell.contentView.backgroundColor = TIMCommonDynamicColor(@"", @"#f9f9f9");
|
||||
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)onSendMessage:(TUIGroupProfileCardViewCell_Minimalist *)cell {
|
||||
TUIGroupProfileCardCellData_Minimalist *cellData = cell.cardData;
|
||||
|
||||
UIImage *avataImage = [UIImage new];
|
||||
if (cell.headerView.headImg.image) {
|
||||
avataImage = cell.headerView.headImg.image;
|
||||
}
|
||||
|
||||
NSDictionary *param = @{
|
||||
TUICore_TUIChatObjectFactory_ChatViewController_Title : cellData.name ?: @"",
|
||||
TUICore_TUIChatObjectFactory_ChatViewController_GroupID : cellData.identifier ?: @"",
|
||||
TUICore_TUIChatObjectFactory_ChatViewController_AvatarImage : avataImage ?: [UIImage new]
|
||||
};
|
||||
[self.navigationController pushViewController:TUICore_TUIChatObjectFactory_ChatViewController_Minimalist param:param forResult:nil];
|
||||
}
|
||||
|
||||
- (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_Minimalist
|
||||
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];
|
||||
|
||||
if ([self.dataProvider.groupInfo isPrivate] || [TUIGroupInfoDataProvider_Minimalist isMeOwner:self.dataProvider.groupInfo]) {
|
||||
@weakify(self);
|
||||
[ac tuitheme_addAction:[UIAlertAction actionWithTitle:TIMCommonLocalizableString(TUIKitGroupProfileEditGroupName)
|
||||
style:UIAlertActionStyleDefault
|
||||
handler:^(UIAlertAction *_Nonnull action) {
|
||||
@strongify(self);
|
||||
[self didSelectEditGroupName];
|
||||
}]];
|
||||
}
|
||||
if ([TUIGroupInfoDataProvider_Minimalist isMeOwner:self.dataProvider.groupInfo]) {
|
||||
@weakify(self);
|
||||
[ac tuitheme_addAction:[UIAlertAction actionWithTitle:TIMCommonLocalizableString(TUIKitGroupProfileEditAnnouncement)
|
||||
style:UIAlertActionStyleDefault
|
||||
handler:^(UIAlertAction *_Nonnull action) {
|
||||
@strongify(self);
|
||||
[self didSelectEditAnnouncement];
|
||||
}]];
|
||||
}
|
||||
|
||||
if ([TUIGroupInfoDataProvider_Minimalist isMeOwner:self.dataProvider.groupInfo]) {
|
||||
@weakify(self);
|
||||
[ac tuitheme_addAction:[UIAlertAction actionWithTitle:TIMCommonLocalizableString(TUIKitGroupProfileEditAvatar)
|
||||
style:UIAlertActionStyleDefault
|
||||
handler:^(UIAlertAction *_Nonnull action) {
|
||||
@strongify(self);
|
||||
[self didSelectAvatar];
|
||||
}]];
|
||||
}
|
||||
|
||||
[ac tuitheme_addAction:[UIAlertAction actionWithTitle:TIMCommonLocalizableString(Cancel) style:UIAlertActionStyleCancel handler:nil]];
|
||||
|
||||
[self presentViewController:ac animated:YES completion:nil];
|
||||
}
|
||||
|
||||
- (void)didSelectEditGroupName {
|
||||
TUIModifyViewData *data = [[TUIModifyViewData alloc] init];
|
||||
data.title = TIMCommonLocalizableString(TUIKitGroupProfileEditGroupName);
|
||||
data.content = self.dataProvider.groupInfo.groupName;
|
||||
data.desc = TIMCommonLocalizableString(TUIKitGroupProfileEditGroupName);
|
||||
TUIModifyView *modify = [[TUIModifyView alloc] init];
|
||||
modify.tag = 0;
|
||||
modify.delegate = self;
|
||||
[modify setData:data];
|
||||
[modify showInWindow:self.view.window];
|
||||
}
|
||||
- (void)didSelectEditAnnouncement {
|
||||
TUIModifyViewData *data = [[TUIModifyViewData alloc] init];
|
||||
data.title = TIMCommonLocalizableString(TUIKitGroupProfileEditAnnouncement);
|
||||
TUIModifyView *modify = [[TUIModifyView alloc] init];
|
||||
modify.tag = 1;
|
||||
modify.delegate = self;
|
||||
[modify setData:data];
|
||||
[modify showInWindow:self.view.window];
|
||||
}
|
||||
- (void)didSelectAvatar {
|
||||
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;
|
||||
@weakify(self);
|
||||
[[V2TIMManager sharedInstance] setGroupInfo:info
|
||||
succ:^{
|
||||
@strongify(self);
|
||||
[self updateGroupInfo];
|
||||
}
|
||||
fail:^(int code, NSString *msg) {
|
||||
[TUITool makeToastError:code msg:msg];
|
||||
}];
|
||||
}
|
||||
};
|
||||
}
|
||||
- (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_Minimalist *vc = [[TUIGroupNoticeController_Minimalist 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];
|
||||
}];
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
- (void)didAddMemebers {
|
||||
[TUICore notifyEvent:TUICore_TUIContactNotify subKey:TUICore_TUIContactNotify_OnAddMemebersClickSubKey object:self param:nil];
|
||||
}
|
||||
|
||||
#pragma mark TUIModifyViewDelegate
|
||||
|
||||
- (void)modifyView:(TUIModifyView *)modifyView didModiyContent:(NSString *)content {
|
||||
@weakify(self);
|
||||
if (modifyView.tag == 0) {
|
||||
[self.dataProvider setGroupName:content
|
||||
succ:^{
|
||||
@strongify(self);
|
||||
[self updateGroupInfo];
|
||||
}
|
||||
fail:^(int code, NSString *desc){
|
||||
}];
|
||||
} else if (modifyView.tag == 1) {
|
||||
[self.dataProvider setGroupNotification:content];
|
||||
} else if (modifyView.tag == 2) {
|
||||
[self.dataProvider setGroupMemberNameCard:content];
|
||||
}
|
||||
}
|
||||
@end
|
||||
|
||||
@interface IUGroupView_Minimalist : UIView
|
||||
@property(nonatomic, strong) UIView *view;
|
||||
@end
|
||||
|
||||
@implementation IUGroupView_Minimalist
|
||||
|
||||
- (instancetype)init {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
self.view = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 10, 10)];
|
||||
[self addSubview:self.view];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
@end
|
||||
@@ -0,0 +1,68 @@
|
||||
//
|
||||
// TUIGroupInfoDataProvider_Minimalist.h
|
||||
// TUIGroup
|
||||
//
|
||||
// Created by wyl on 2023/1/3.
|
||||
// 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;
|
||||
@class TUIGroupMemberCellData_Minimalist;
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@protocol TUIGroupInfoDataProviderDelegate_Minimalist <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;
|
||||
- (void)didAddMemebers;
|
||||
|
||||
@end
|
||||
|
||||
@interface TUIGroupInfoDataProvider_Minimalist : NSObject
|
||||
@property(nonatomic, weak) id<TUIGroupInfoDataProviderDelegate_Minimalist> delegate;
|
||||
@property(nonatomic, strong) V2TIMGroupInfo *groupInfo;
|
||||
@property(nonatomic, strong) NSMutableArray *dataList;
|
||||
@property(nonatomic, strong) NSMutableArray<TUIGroupMemberCellData_Minimalist *> *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 (^)(void))callback;
|
||||
- (void)setGroupAddOpt:(V2TIMGroupAddOpt)opt;
|
||||
- (void)setGroupApproveOpt:(V2TIMGroupAddOpt)opt;
|
||||
- (void)setGroupReceiveMessageOpt:(V2TIMReceiveMessageOpt)opt Succ:(V2TIMSucc)succ fail:(V2TIMFail)fail;
|
||||
- (void)setGroupName:(NSString *)groupName succ:(V2TIMSucc)succ fail:(V2TIMFail)fail;
|
||||
- (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
|
||||
@@ -0,0 +1,794 @@
|
||||
//
|
||||
// TUIGroupInfoDataProvider_Minimalist.m
|
||||
// TUIGroup
|
||||
//
|
||||
// Created by wyl on 2023/1/3.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
//
|
||||
|
||||
#import "TUIGroupInfoDataProvider_Minimalist.h"
|
||||
#import <TIMCommon/TIMCommonModel.h>
|
||||
#import <TIMCommon/TIMDefine.h>
|
||||
#import <TUICore/TUILogin.h>
|
||||
#import <TIMCommon/TIMGroupInfo+TUIDataProvider.h>
|
||||
#import <TIMCommon/TUICommonGroupInfoCellData_Minimalist.h>
|
||||
#import "TUIGroupNoticeCell.h"
|
||||
#import "TUIGroupProfileCardViewCell_Minimalist.h"
|
||||
#import "TUIGroupConfig.h"
|
||||
#import <TUICore/TUICore.h>
|
||||
|
||||
@interface TUIGroupInfoDataProvider_Minimalist () <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_Minimalist
|
||||
|
||||
- (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 {
|
||||
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:(void (^)(void))callback {
|
||||
@weakify(self);
|
||||
[[V2TIMManager sharedInstance] getGroupsInfo:@[ self.groupID ]
|
||||
succ:^(NSArray<V2TIMGroupInfoResult *> *groupResultList) {
|
||||
@strongify(self);
|
||||
if (groupResultList.count == 1) {
|
||||
self.groupInfo = groupResultList[0].info;
|
||||
[self setupData];
|
||||
if (callback) {
|
||||
callback();
|
||||
}
|
||||
}
|
||||
}
|
||||
fail:^(int code, NSString *msg) {
|
||||
[self 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_Minimalist *data = [[TUIGroupMemberCellData_Minimalist alloc] init];
|
||||
data.identifier = fullInfo.userID;
|
||||
data.name = fullInfo.userID;
|
||||
data.avatarUrl = fullInfo.faceURL;
|
||||
data.showAccessory = YES;
|
||||
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;
|
||||
}
|
||||
if ([fullInfo.userID isEqualToString:[V2TIMManager sharedInstance].getLoginUser]) {
|
||||
weakSelf.selfInfo = fullInfo;
|
||||
continue;
|
||||
} else {
|
||||
[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();
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
- (TUIGroupMemberCellData_Minimalist *)creatSelfData {
|
||||
TUIGroupMemberCellData_Minimalist *data = [[TUIGroupMemberCellData_Minimalist alloc] init];
|
||||
data.identifier = [V2TIMManager sharedInstance].getLoginUser;
|
||||
data.avatarUrl = [TUILogin getFaceUrl];
|
||||
|
||||
data.showAccessory = YES;
|
||||
|
||||
data.name = TIMCommonLocalizableString(YOU);
|
||||
data.showAccessory = NO;
|
||||
if ([TUIGroupInfoDataProvider_Minimalist isMeSuper:self.groupInfo]) {
|
||||
data.detailName = TIMCommonLocalizableString(TUIKitMembersRoleSuper);
|
||||
|
||||
} else if ([TUIGroupInfoDataProvider_Minimalist isMeOwner:self.groupInfo]) {
|
||||
data.detailName = TIMCommonLocalizableString(TUIKitMembersRoleAdmin);
|
||||
} else {
|
||||
data.detailName = TIMCommonLocalizableString(TUIKitMembersRoleMember);
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
|
||||
- (void)setupData {
|
||||
NSMutableArray *dataList = [NSMutableArray array];
|
||||
if (self.groupInfo) {
|
||||
if (![[TUIGroupConfig sharedConfig] isItemHiddenInGroupConfig:TUIGroupConfigItem_MuteAndPin]) {
|
||||
NSMutableArray *avatarAndInfoArray = [NSMutableArray array];
|
||||
// Mute Notifications
|
||||
TUICommonSwitchCellData *muteData = [[TUICommonSwitchCellData alloc] init];
|
||||
if (![self.groupInfo.groupType isEqualToString:GroupType_Meeting]) {
|
||||
muteData.on = (self.groupInfo.recvOpt == V2TIM_RECEIVE_NOT_NOTIFY_MESSAGE);
|
||||
muteData.title = TIMCommonLocalizableString(TUIKitGroupProfileMessageDoNotDisturb);
|
||||
muteData.cswitchSelector = @selector(didSelectOnNotDisturb:);
|
||||
[avatarAndInfoArray addObject:muteData];
|
||||
}
|
||||
// Minimize
|
||||
TUICommonSwitchCellData *minimize = [[TUICommonSwitchCellData alloc] init];
|
||||
minimize.title = TIMCommonLocalizableString(TUIKitConversationMarkFold);
|
||||
minimize.displaySeparatorLine = YES;
|
||||
minimize.cswitchSelector = @selector(didSelectOnFoldConversation:);
|
||||
if (muteData.on) {
|
||||
[avatarAndInfoArray addObject:minimize];
|
||||
}
|
||||
// Pin
|
||||
TUICommonSwitchCellData *pinData = [[TUICommonSwitchCellData alloc] init];
|
||||
pinData.title = TIMCommonLocalizableString(TUIKitGroupProfileStickyOnTop);
|
||||
[avatarAndInfoArray addObject:pinData];
|
||||
[dataList addObject:avatarAndInfoArray];
|
||||
|
||||
#ifndef SDKPlaceTop
|
||||
#define SDKPlaceTop
|
||||
#endif
|
||||
#ifdef SDKPlaceTop
|
||||
@weakify(self);
|
||||
[V2TIMManager.sharedInstance getConversation:[NSString stringWithFormat:@"group_%@", self.groupID]
|
||||
succ:^(V2TIMConversation *conv) {
|
||||
@strongify(self);
|
||||
minimize.on = [self.class isMarkedByFoldType:conv.markList];
|
||||
pinData.cswitchSelector = @selector(didSelectOnTop:);
|
||||
pinData.on = conv.isPinned;
|
||||
if (minimize.on) {
|
||||
pinData.on = NO;
|
||||
pinData.disableChecked = YES;
|
||||
}
|
||||
self.dataList = dataList;
|
||||
}
|
||||
fail:^(int code, NSString *desc) {
|
||||
}];
|
||||
#else
|
||||
NSString *groupID = [NSString stringWithFormat:@"group_%@", self.groupID];
|
||||
if ([[[TUIConversationPin sharedInstance] topConversationList] containsObject:groupID]) {
|
||||
pinData.on = YES;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
if (![[TUIGroupConfig sharedConfig] isItemHiddenInGroupConfig:TUIGroupConfigItem_Manage]) {
|
||||
NSMutableArray *groupInfoArray = [NSMutableArray array];
|
||||
|
||||
// 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];
|
||||
|
||||
// Manage
|
||||
{
|
||||
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_MinimalistExtensionID
|
||||
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_Minimalist isMeOwner:self.groupInfo]) {
|
||||
[groupInfoArray addObject:manageData];
|
||||
}
|
||||
}
|
||||
else {
|
||||
[groupInfoArray addObject:manageData];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Group Type
|
||||
TUICommonTextCellData *typeData = [[TUICommonTextCellData alloc] init];
|
||||
typeData.key = TIMCommonLocalizableString(TUIKitGroupProfileType);
|
||||
typeData.value = [TUIGroupInfoDataProvider_Minimalist getGroupTypeName:self.groupInfo];
|
||||
[groupInfoArray addObject:typeData];
|
||||
|
||||
// Group Joining Method
|
||||
TUICommonTextCellData *joinData = [[TUICommonTextCellData alloc] init];
|
||||
joinData.key = TIMCommonLocalizableString(TUIKitGroupProfileJoinType);
|
||||
if ([TUIGroupInfoDataProvider_Minimalist isMeOwner:self.groupInfo]) {
|
||||
joinData.cselector = @selector(didSelectAddOption:);
|
||||
joinData.showAccessory = YES;
|
||||
}
|
||||
joinData.value = [TUIGroupInfoDataProvider_Minimalist getAddOption:self.groupInfo];
|
||||
[groupInfoArray addObject:joinData];
|
||||
self.addOptionData = joinData;
|
||||
|
||||
// Group Inviting Method
|
||||
TUICommonTextCellData *inviteOptionData = [[TUICommonTextCellData alloc] init];
|
||||
inviteOptionData.key = TIMCommonLocalizableString(TUIKitGroupProfileInviteType);
|
||||
if ([TUIGroupInfoDataProvider_Minimalist isMeOwner:self.groupInfo]) {
|
||||
inviteOptionData.cselector = @selector(didSelectAddOption:);
|
||||
inviteOptionData.showAccessory = YES;
|
||||
}
|
||||
inviteOptionData.value = [TUIGroupInfoDataProvider_Minimalist getApproveOption:self.groupInfo];
|
||||
[groupInfoArray addObject:inviteOptionData];
|
||||
self.inviteOptionData = inviteOptionData;
|
||||
|
||||
[dataList addObject:groupInfoArray];
|
||||
}
|
||||
|
||||
if (![[TUIGroupConfig sharedConfig] isItemHiddenInGroupConfig:TUIGroupConfigItem_Alias]) {
|
||||
// My Alias in Group
|
||||
TUICommonTextCellData *aliasData = [[TUICommonTextCellData alloc] init];
|
||||
aliasData.key = TIMCommonLocalizableString(TUIKitGroupProfileAlias);
|
||||
aliasData.value = self.selfInfo.nameCard;
|
||||
aliasData.cselector = @selector(didSelectGroupNick:);
|
||||
aliasData.showAccessory = YES;
|
||||
self.groupNickNameCellData = aliasData;
|
||||
[dataList addObject:@[ aliasData ]];
|
||||
}
|
||||
|
||||
if (![[TUIGroupConfig sharedConfig] isItemHiddenInGroupConfig:TUIGroupConfigItem_Background]) {
|
||||
// Background
|
||||
TUICommonTextCellData *changeBackgroundImageItem = [[TUICommonTextCellData alloc] init];
|
||||
changeBackgroundImageItem.key = TIMCommonLocalizableString(ProfileSetBackgroundImage);
|
||||
changeBackgroundImageItem.cselector = @selector(didSelectOnChangeBackgroundImage:);
|
||||
changeBackgroundImageItem.showAccessory = YES;
|
||||
[dataList addObject:@[ changeBackgroundImageItem ]];
|
||||
}
|
||||
|
||||
if (![[TUIGroupConfig sharedConfig] isItemHiddenInGroupConfig:TUIGroupConfigItem_Members]) {
|
||||
// Group Members
|
||||
NSMutableArray *memberArray = [NSMutableArray array];
|
||||
|
||||
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];
|
||||
|
||||
TUIGroupButtonCellData_Minimalist *addMembers = [[TUIGroupButtonCellData_Minimalist alloc] init];
|
||||
addMembers.title = TIMCommonLocalizableString(TUIKitAddMembers);
|
||||
addMembers.style = ButtonBule;
|
||||
addMembers.isInfoPageLeftButton = YES;
|
||||
addMembers.cbuttonSelector = @selector(didAddMemebers);
|
||||
if ([self.groupInfo canInviteMember]) {
|
||||
[memberArray addObject:addMembers];
|
||||
}
|
||||
|
||||
[memberArray addObject:[self creatSelfData]];
|
||||
|
||||
int otherMemberCount = 0;
|
||||
for (TUIGroupMemberCellData_Minimalist *memberObj in self.membersData) {
|
||||
// memberObj.cselector = @selector(didCurrentMemberAtCell:);
|
||||
[memberArray addObject:memberObj];
|
||||
otherMemberCount++;
|
||||
if (otherMemberCount > 1) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
[dataList addObject:memberArray];
|
||||
}
|
||||
|
||||
NSMutableArray *buttonArray = [NSMutableArray array];
|
||||
if (![[TUIGroupConfig sharedConfig] isItemHiddenInGroupConfig:TUIGroupConfigItem_ClearChatHistory]) {
|
||||
// Clear Chat History
|
||||
TUIGroupButtonCellData_Minimalist *clearHistory = [[TUIGroupButtonCellData_Minimalist alloc] init];
|
||||
clearHistory.title = TIMCommonLocalizableString(TUIKitClearAllChatHistory);
|
||||
clearHistory.style = ButtonRedText;
|
||||
clearHistory.cbuttonSelector = @selector(didClearAllHistory:);
|
||||
[buttonArray addObject:clearHistory];
|
||||
}
|
||||
|
||||
if (![[TUIGroupConfig sharedConfig] isItemHiddenInGroupConfig:TUIGroupConfigItem_DeleteAndLeave]) {
|
||||
// Delete and Leave
|
||||
TUIGroupButtonCellData_Minimalist *quitButton = [[TUIGroupButtonCellData_Minimalist 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]) {
|
||||
// Transfer Group
|
||||
|
||||
NSMutableDictionary *param = [NSMutableDictionary dictionary];
|
||||
param[@"pushVC"] = self.delegate.pushNavigationController;
|
||||
param[@"groupID"] = self.groupID.length > 0 ? self.groupID : @"";
|
||||
void (^updateGroupInfoCallback)(void) = ^{
|
||||
[self updateGroupInfo:nil];
|
||||
};
|
||||
param[@"updateGroupInfoCallback"] = updateGroupInfoCallback;
|
||||
NSArray<TUIExtensionInfo *> *extensionList = [TUICore getExtensionList:
|
||||
TUICore_TUIChatExtension_GroupProfileBottomItemExtension_MinimalistExtensionID
|
||||
param:param];
|
||||
for (TUIExtensionInfo *info in extensionList) {
|
||||
if (info.text && info.onClicked) {
|
||||
TUIGroupButtonCellData_Minimalist *transferButton = [[TUIGroupButtonCellData_Minimalist alloc] init];
|
||||
transferButton.title = info.text;//TIMCommonLocalizableString(TUIKitGroupTransferOwner);
|
||||
transferButton.style = ButtonRedText;
|
||||
transferButton.tui_extValueObj = info;
|
||||
transferButton.cbuttonSelector = @selector(groupProfileExtensionButtonClick:);
|
||||
[buttonArray addObject:transferButton];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ([self.groupInfo canDismissGroup] &&
|
||||
![[TUIGroupConfig sharedConfig] isItemHiddenInGroupConfig:TUIGroupConfigItem_Dismiss]) {
|
||||
// Disband Group
|
||||
TUIGroupButtonCellData_Minimalist *deletebutton = [[TUIGroupButtonCellData_Minimalist alloc] init];
|
||||
deletebutton.title = TIMCommonLocalizableString(TUIKitGroupProfileDissolve);
|
||||
deletebutton.style = ButtonRedText;
|
||||
deletebutton.cbuttonSelector = @selector(didDeleteGroup:);
|
||||
[buttonArray addObject:deletebutton];
|
||||
}
|
||||
|
||||
if (![[TUIGroupConfig sharedConfig] isItemHiddenInGroupConfig:TUIGroupConfigItem_Report]) {
|
||||
// Report
|
||||
TUIGroupButtonCellData_Minimalist *reportButton = [[TUIGroupButtonCellData_Minimalist alloc] init];
|
||||
reportButton.title = TIMCommonLocalizableString(TUIKitGroupProfileReport);
|
||||
reportButton.style = ButtonRedText;
|
||||
reportButton.cbuttonSelector = @selector(didReportGroup:);
|
||||
[buttonArray addObject:reportButton];
|
||||
}
|
||||
|
||||
if (buttonArray.count > 1) {
|
||||
TUIGroupButtonCellData_Minimalist *lastCellData = [buttonArray lastObject];
|
||||
lastCellData.hideSeparatorLine = YES;
|
||||
[dataList addObject:buttonArray];
|
||||
}
|
||||
|
||||
self.dataList = dataList;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - TUIGroupInfoDataProviderDelegate_Minimalist
|
||||
- (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)didAddMemebers {
|
||||
if (self.delegate && [self.delegate respondsToSelector:@selector(didAddMemebers)]) {
|
||||
[self.delegate didAddMemebers];
|
||||
}
|
||||
}
|
||||
|
||||
- (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_Minimalist *add = [[TUIGroupMemberCellData_Minimalist alloc] init];
|
||||
add.avatarImage = TUIContactCommonBundleImage(@"add");
|
||||
add.tag = 1;
|
||||
[tmpArray addObject:add];
|
||||
}
|
||||
if ([self.groupInfo canRemoveMember]) {
|
||||
TUIGroupMemberCellData_Minimalist *delete = [[TUIGroupMemberCellData_Minimalist 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_Minimalist 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_Minimalist 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 succ:(V2TIMSucc)succ fail:(V2TIMFail)fail {
|
||||
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;
|
||||
if (succ) {
|
||||
succ();
|
||||
}
|
||||
}
|
||||
fail:^(int code, NSString *msg) {
|
||||
[self makeToastError:code msg:msg];
|
||||
if (fail) {
|
||||
fail(code, 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];
|
||||
}
|
||||
|
||||
- (void)makeToastError:(NSInteger)code msg:(NSString *)msg {
|
||||
if (!self.firstLoadData) {
|
||||
[TUITool makeToastError:code msg:msg];
|
||||
}
|
||||
}
|
||||
+ (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
|
||||
@@ -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_Minimalist : UIViewController
|
||||
|
||||
@property(nonatomic, copy) dispatch_block_t onNoticeChanged;
|
||||
@property(nonatomic, copy) NSString *groupID;
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,114 @@
|
||||
//
|
||||
// TUIGroupNoticeController.m
|
||||
// TUIGroup
|
||||
//
|
||||
// Created by harvy on 2022/1/11.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
//
|
||||
|
||||
#import "TUIGroupNoticeController_Minimalist.h"
|
||||
#import <TUICore/TUIThemeManager.h>
|
||||
#import "TUIGroupNoticeDataProvider.h"
|
||||
|
||||
@interface TUIGroupNoticeController_Minimalist ()
|
||||
|
||||
@property(nonatomic, strong) UITextView *textView;
|
||||
@property(nonatomic, weak) UIButton *rightButton;
|
||||
@property(nonatomic, strong) TUIGroupNoticeDataProvider *dataProvider;
|
||||
|
||||
@end
|
||||
|
||||
@implementation TUIGroupNoticeController_Minimalist
|
||||
|
||||
- (void)viewDidLoad {
|
||||
[super viewDidLoad];
|
||||
|
||||
self.view.backgroundColor = [UIColor tui_colorWithHex:@"#f9f9f9"];
|
||||
[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_Minimalist/Chat/TUIMediaView_Minimalist.h
Normal file
29
TUIKit/TUIChat/UI_Minimalist/Chat/TUIMediaView_Minimalist.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_Minimalist : 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
|
||||
202
TUIKit/TUIChat/UI_Minimalist/Chat/TUIMediaView_Minimalist.m
Normal file
202
TUIKit/TUIChat/UI_Minimalist/Chat/TUIMediaView_Minimalist.m
Normal file
@@ -0,0 +1,202 @@
|
||||
//
|
||||
// MenuView.m
|
||||
// UIKit
|
||||
//
|
||||
// Created by kennethmiao on 2018/9/18.
|
||||
// Copyright © 2018 Tencent. All rights reserved.
|
||||
//
|
||||
|
||||
#import "TUIMediaView_Minimalist.h"
|
||||
#import <TIMCommon/TIMDefine.h>
|
||||
#import <TIMCommon/TUIMessageCell.h>
|
||||
#import <TUICore/TUIDarkModel.h>
|
||||
#import <TUICore/TUIGlobalization.h>
|
||||
#import "TUIChatConversationModel.h"
|
||||
#import "TUIImageCollectionCell_Minimalist.h"
|
||||
#import "TUIMessageMediaDataProvider.h"
|
||||
#import "TUIVideoCollectionCell_Minimalist.h"
|
||||
|
||||
#define ANIMATION_TIME 0.2
|
||||
|
||||
@interface TUIMediaView_Minimalist () <UICollectionViewDelegate,
|
||||
UICollectionViewDataSource,
|
||||
UICollectionViewDelegateFlowLayout,
|
||||
UIScrollViewDelegate,
|
||||
TUIMediaCollectionCellDelegate_Minimalist>
|
||||
@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;
|
||||
@end
|
||||
|
||||
@implementation TUIMediaView_Minimalist {
|
||||
V2TIMMessage *_curMessage;
|
||||
}
|
||||
|
||||
- (void)setupViews {
|
||||
self.backgroundColor = [UIColor clearColor];
|
||||
|
||||
self.coverView = [[UIView alloc] initWithFrame:self.bounds];
|
||||
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];
|
||||
|
||||
UICollectionViewFlowLayout *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_Minimalist class] forCellWithReuseIdentifier:TImageMessageCell_ReuseId];
|
||||
[self.menuCollectionView registerClass:[TUIVideoCollectionCell_Minimalist 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;
|
||||
});
|
||||
}
|
||||
|
||||
- (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.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_Minimalist *cell = [collectionView dequeueReusableCellWithReuseIdentifier:data.reuseId forIndexPath:indexPath];
|
||||
if (cell) {
|
||||
cell.delegate = self;
|
||||
[cell fillWithData:data];
|
||||
}
|
||||
return cell;
|
||||
}
|
||||
|
||||
- (CGSize)collectionView:(UICollectionView *)collectionView
|
||||
layout:(UICollectionViewLayout *)collectionViewLayout
|
||||
sizeForItemAtIndexPath:(NSIndexPath *)indexPath {
|
||||
return collectionView.frame.size;
|
||||
}
|
||||
|
||||
#pragma mark TUIMediaCollectionCellDelegate
|
||||
|
||||
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
|
||||
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_Minimalist class]]) {
|
||||
TUIVideoCollectionCell_Minimalist *videoCell = (TUIVideoCollectionCell_Minimalist *)cell;
|
||||
[videoCell stopVideoPlayAndSave];
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark TUIMediaCollectionCellDelegate
|
||||
- (void)onCloseMedia:(TUIMediaCollectionCell_Minimalist *)cell {
|
||||
if (self.onClose) {
|
||||
self.onClose();
|
||||
}
|
||||
if (self.superview) {
|
||||
[self removeFromSuperview];
|
||||
}
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:kDisableAllRotationOrientationNotification object:nil];
|
||||
}
|
||||
|
||||
@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_Minimalist.h"
|
||||
#import "TUIChatConversationModel.h"
|
||||
#import "TUIMessageDataProvider.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface TUIMergeMessageListController_Minimalist : UITableViewController
|
||||
|
||||
@property(nonatomic, weak) id<TUIBaseMessageControllerDelegate_Minimalist> 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
|
||||
@@ -0,0 +1,580 @@
|
||||
//
|
||||
// TUIMergeMessageListController.m
|
||||
// Pods
|
||||
//
|
||||
// Created by harvy on 2020/12/9.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
//
|
||||
|
||||
#import "TUIMergeMessageListController_Minimalist.h"
|
||||
#import <TIMCommon/TIMDefine.h>
|
||||
#import <TIMCommon/TUIMessageCellLayout.h>
|
||||
#import <TIMCommon/TUISystemMessageCell.h>
|
||||
#import <TUICore/TUICore.h>
|
||||
#import <TUICore/TUIDarkModel.h>
|
||||
#import <TUICore/TUIGlobalization.h>
|
||||
#import <TUICore/TUIThemeManager.h>
|
||||
#import "TUIFaceMessageCell_Minimalist.h"
|
||||
#import "TUIFileMessageCell_Minimalist.h"
|
||||
#import "TUIFileViewController_Minimalist.h"
|
||||
#import "TUIImageMessageCell_Minimalist.h"
|
||||
#import "TUIJoinGroupMessageCell_Minimalist.h"
|
||||
#import "TUILinkCell_Minimalist.h"
|
||||
#import "TUIMediaView_Minimalist.h"
|
||||
#import "TUIMergeMessageCell_Minimalist.h"
|
||||
#import "TUIMessageDataProvider.h"
|
||||
#import "TUIMessageSearchDataProvider.h"
|
||||
#import "TUIReferenceMessageCell_Minimalist.h"
|
||||
#import "TUIRepliesDetailViewController_Minimalist.h"
|
||||
#import "TUIReplyMessageCellData.h"
|
||||
#import "TUIReplyMessageCell_Minimalist.h"
|
||||
#import "TUITextMessageCell_Minimalist.h"
|
||||
#import "TUIVideoMessageCell_Minimalist.h"
|
||||
#import "TUIVoiceMessageCell_Minimalist.h"
|
||||
#import "TUIMessageCellConfig_Minimalist.h"
|
||||
#import "TUIChatConfig.h"
|
||||
|
||||
#define STR(x) @ #x
|
||||
|
||||
@interface TUIMergeMessageListController_Minimalist () <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_Minimalist *messageCellConfig;
|
||||
@end
|
||||
|
||||
@implementation TUIMergeMessageListController_Minimalist
|
||||
|
||||
- (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 {
|
||||
if (self.willCloseCallback) {
|
||||
self.willCloseCallback();
|
||||
}
|
||||
}
|
||||
|
||||
- (void)loadMessages {
|
||||
@weakify(self);
|
||||
[self.mergerElem
|
||||
downloadMergerMessage:^(NSArray<V2TIMMessage *> *msgs) {
|
||||
@strongify(self);
|
||||
self.imMsgs = msgs;
|
||||
[self getMessages:self.imMsgs];
|
||||
}
|
||||
fail:^(int code, NSString *desc){
|
||||
// to do
|
||||
}];
|
||||
}
|
||||
|
||||
- (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;
|
||||
[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.voiceTop = 10;
|
||||
msg.localCustomInt = 1;
|
||||
layout = TUIMessageCellLayout.incommingVoiceMessageLayout;
|
||||
}
|
||||
data.cellLayout = layout;
|
||||
data.direction = MsgDirectionIncoming;
|
||||
data.innerMessage = msg;
|
||||
data.showName = NO;
|
||||
[uiMsgs addObject:data];
|
||||
}
|
||||
for (TUIMessageCellData *cellData in uiMsgs) {
|
||||
[TUIMessageDataProvider updateUIMsgStatus:cellData uiMsgs:uiMsgs];
|
||||
}
|
||||
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];
|
||||
|
||||
UIImage *image = TIMCommonDynamicImage(@"nav_back_img", [UIImage imageNamed:TIMCommonImagePath(@"nav_back")]);
|
||||
image = [image imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal];
|
||||
image = [image rtl_imageFlippedForRightToLeftLayoutDirection];
|
||||
UIButton *backButton = [UIButton buttonWithType:UIButtonTypeCustom];
|
||||
[backButton setImage:image forState:UIControlStateNormal];
|
||||
[backButton addTarget:self action:@selector(back) forControlEvents:UIControlEventTouchUpInside];
|
||||
self.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc] initWithCustomView:backButton];
|
||||
}
|
||||
|
||||
- (void)back {
|
||||
[self.navigationController popViewControllerAnimated:YES];
|
||||
[self dismissViewControllerAnimated:YES completion:nil];
|
||||
}
|
||||
|
||||
- (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 < self.uiMsgs.count) {
|
||||
TUIMessageCellData *cellData = self.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.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_Minimalist class]]) {
|
||||
[self showImageMessage:(TUIImageMessageCell_Minimalist *)cell];
|
||||
}
|
||||
if ([cell isKindOfClass:[TUIVoiceMessageCell_Minimalist class]]) {
|
||||
[self playVoiceMessage:(TUIVoiceMessageCell_Minimalist *)cell];
|
||||
}
|
||||
if ([cell isKindOfClass:[TUIVideoMessageCell_Minimalist class]]) {
|
||||
[self showVideoMessage:(TUIVideoMessageCell_Minimalist *)cell];
|
||||
}
|
||||
if ([cell isKindOfClass:[TUIFileMessageCell_Minimalist class]]) {
|
||||
[self showFileMessage:(TUIFileMessageCell_Minimalist *)cell];
|
||||
}
|
||||
if ([cell isKindOfClass:[TUIMergeMessageCell_Minimalist class]]) {
|
||||
TUIMergeMessageListController_Minimalist *mergeVc = [[TUIMergeMessageListController_Minimalist alloc] init];
|
||||
mergeVc.mergerElem = [(TUIMergeMessageCell_Minimalist *)cell mergeData].mergerElem;
|
||||
mergeVc.delegate = self.delegate;
|
||||
[self.navigationController pushViewController:mergeVc animated:YES];
|
||||
}
|
||||
if ([cell isKindOfClass:[TUILinkCell_Minimalist class]]) {
|
||||
[self showLinkMessage:(TUILinkCell_Minimalist *)cell];
|
||||
}
|
||||
if ([cell isKindOfClass:[TUIReplyMessageCell_Minimalist class]]) {
|
||||
[self showReplyMessage:(TUIReplyMessageCell_Minimalist *)cell];
|
||||
}
|
||||
if ([cell isKindOfClass:[TUIReferenceMessageCell_Minimalist class]]) {
|
||||
[self showReplyMessage:(TUIReplyMessageCell_Minimalist *)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_Minimalist *)cell {
|
||||
[UIApplication.sharedApplication.keyWindow endEditing:YES];
|
||||
NSString *originMsgID = @"";
|
||||
NSString *msgAbstract = @"";
|
||||
if ([cell isKindOfClass:TUIReplyMessageCell_Minimalist.class]) {
|
||||
TUIReplyMessageCell_Minimalist *acell = (TUIReplyMessageCell_Minimalist *)cell;
|
||||
TUIReplyMessageCellData *cellData = acell.replyData;
|
||||
originMsgID = cellData.messageRootID;
|
||||
msgAbstract = cellData.msgAbstract;
|
||||
} else if ([cell isKindOfClass:TUIReferenceMessageCell_Minimalist.class]) {
|
||||
TUIReferenceMessageCell_Minimalist *acell = (TUIReferenceMessageCell_Minimalist *)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_Minimalist.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_Minimalist.class]) {
|
||||
[self jumpDetailPageByMessage:message];
|
||||
} else if ([cell isKindOfClass:TUIReferenceMessageCell_Minimalist.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_Minimalist *repliesDetailVC = [[TUIRepliesDetailViewController_Minimalist alloc] initWithCellData:data
|
||||
conversationData:self.conversationData];
|
||||
repliesDetailVC.delegate = self.delegate;
|
||||
repliesDetailVC.modalPresentationStyle = UIModalPresentationCustom;
|
||||
[self.navigationController presentViewController:repliesDetailVC animated:YES completion:nil];
|
||||
repliesDetailVC.parentPageDataProvider = self.parentPageDataProvider;
|
||||
__weak typeof(self) weakSelf = self;
|
||||
repliesDetailVC.willCloseCallback = ^() {
|
||||
[weakSelf.tableView reloadData];
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
- (void)showImageMessage:(TUIImageMessageCell_Minimalist *)cell {
|
||||
CGRect frame = [cell.thumb convertRect:cell.thumb.bounds toView:[UIApplication sharedApplication].delegate.window];
|
||||
TUIMediaView_Minimalist *mediaView = [[TUIMediaView_Minimalist 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_Minimalist *)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_Minimalist *)cell {
|
||||
CGRect frame = [cell.thumb convertRect:cell.thumb.bounds toView:[UIApplication sharedApplication].delegate.window];
|
||||
TUIMediaView_Minimalist *mediaView = [[TUIMediaView_Minimalist 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_Minimalist *)cell {
|
||||
TUIFileMessageCellData *fileData = cell.fileData;
|
||||
if (![fileData isLocalExist]) {
|
||||
[fileData downloadFile];
|
||||
return;
|
||||
}
|
||||
|
||||
TUIFileViewController_Minimalist *file = [[TUIFileViewController_Minimalist alloc] init];
|
||||
file.data = [cell fileData];
|
||||
[self.navigationController pushViewController:file animated:YES];
|
||||
}
|
||||
|
||||
- (void)showLinkMessage:(TUILinkCell_Minimalist *)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_Minimalist *)messageCellConfig {
|
||||
if (_messageCellConfig == nil) {
|
||||
_messageCellConfig = [[TUIMessageCellConfig_Minimalist alloc] init];
|
||||
}
|
||||
return _messageCellConfig;
|
||||
}
|
||||
|
||||
#pragma mark - TUIMessageBaseDataProviderDataSource
|
||||
- (void)dataProviderDataSourceWillChange:(TUIMessageDataProvider *)dataProvider {
|
||||
// do nothing
|
||||
}
|
||||
|
||||
- (void)dataProviderDataSourceChange:(TUIMessageDataProvider *)dataProvider
|
||||
withType:(TUIMessageBaseDataProviderDataSourceChangeType)type
|
||||
atIndex:(NSUInteger)index
|
||||
animation:(BOOL)animation {
|
||||
// do nothing
|
||||
}
|
||||
|
||||
- (void)dataProviderDataSourceDidChange:(TUIMessageDataProvider *)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
|
||||
@@ -0,0 +1,102 @@
|
||||
// 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_Minimalist : 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_Minimalist (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_Minimalist (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
|
||||
@@ -0,0 +1,248 @@
|
||||
// Created by Tencent on 2023/07/20.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
|
||||
#import "TUIMessageCellConfig_Minimalist.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_Minimalist.h"
|
||||
#import "TUIFaceMessageCellData.h"
|
||||
#import "TUIFileMessageCell_Minimalist.h"
|
||||
#import "TUIFileMessageCellData.h"
|
||||
#import "TUIImageMessageCell_Minimalist.h"
|
||||
#import "TUIImageMessageCellData.h"
|
||||
#import "TUIJoinGroupMessageCell_Minimalist.h"
|
||||
#import "TUIJoinGroupMessageCellData.h"
|
||||
#import "TUIMergeMessageCell_Minimalist.h"
|
||||
#import "TUIMergeMessageCellData.h"
|
||||
#import "TUIReferenceMessageCell_Minimalist.h"
|
||||
#import "TUIReplyMessageCell_Minimalist.h"
|
||||
#import "TUIReplyMessageCellData.h"
|
||||
#import "TUITextMessageCell_Minimalist.h"
|
||||
#import "TUITextMessageCellData.h"
|
||||
#import "TUIVideoMessageCell_Minimalist.h"
|
||||
#import "TUIVideoMessageCellData.h"
|
||||
#import "TUIVoiceMessageCell_Minimalist.h"
|
||||
#import "TUIVoiceMessageCellData.h"
|
||||
|
||||
#define kIsCustomMessageFromPlugin @"kIsCustomMessageFromPlugin"
|
||||
|
||||
typedef Class<TUIMessageCellProtocol> CellClass;
|
||||
typedef NSString * MessageID;
|
||||
typedef NSNumber * HeightNumber;
|
||||
|
||||
static NSMutableDictionary *gCustomMessageInfoMap = nil;
|
||||
|
||||
@interface TUIMessageCellConfig_Minimalist ()
|
||||
|
||||
@property(nonatomic, weak) UITableView *tableView;
|
||||
@property(nonatomic, strong) NSMutableDictionary<TUICellDataClassName, CellClass> *cellClassMaps;
|
||||
@property(nonatomic, strong) NSMutableDictionary<MessageID, HeightNumber> *heightCacheMaps;
|
||||
|
||||
@end
|
||||
|
||||
#pragma mark - Custom Message Register
|
||||
@implementation TUIMessageCellConfig_Minimalist (CustomMessageRegister)
|
||||
|
||||
+ (NSMutableDictionary *)getCustomMessageInfoMap {
|
||||
if (gCustomMessageInfoMap == nil) {
|
||||
gCustomMessageInfoMap = [NSMutableDictionary dictionary];
|
||||
}
|
||||
return gCustomMessageInfoMap;
|
||||
}
|
||||
|
||||
+ (void)registerBuiltInCustomMessageInfo {
|
||||
[self registerCustomMessageCell:@"TUILinkCell_Minimalist" messageCellData:@"TUILinkCellData" forBusinessID:BussinessID_TextLink];
|
||||
[self registerCustomMessageCell:@"TUIGroupCreatedCell_Minimalist" messageCellData:@"TUIGroupCreatedCellData" forBusinessID:BussinessID_GroupCreate];
|
||||
[self registerCustomMessageCell:@"TUIEvaluationCell_Minimalist" messageCellData:@"TUIEvaluationCellData" forBusinessID:BussinessID_Evaluation];
|
||||
[self registerCustomMessageCell:@"TUIOrderCell_Minimalist" messageCellData:@"TUIOrderCellData" forBusinessID:BussinessID_Order];
|
||||
[self registerCustomMessageCell:@"TUIMessageCell_Minimalist" 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_Minimalist (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_Minimalist
|
||||
|
||||
#pragma mark - Life Cycle
|
||||
+ (void)load {
|
||||
[self registerBuiltInCustomMessageInfo];
|
||||
[self registerExternalCustomMessageInfo];
|
||||
}
|
||||
|
||||
#pragma mark - UI
|
||||
|
||||
- (void)bindTableView:(UITableView *)tableView {
|
||||
self.tableView = tableView;
|
||||
|
||||
[self bindMessageCellClass:TUITextMessageCell_Minimalist.class cellDataClass:TUITextMessageCellData.class reuseID:TTextMessageCell_ReuseId];
|
||||
[self bindMessageCellClass:TUIVoiceMessageCell_Minimalist.class cellDataClass:TUIVoiceMessageCellData.class reuseID:TVoiceMessageCell_ReuseId];
|
||||
[self bindMessageCellClass:TUIImageMessageCell_Minimalist.class cellDataClass:TUIImageMessageCellData.class reuseID:TImageMessageCell_ReuseId];
|
||||
[self bindMessageCellClass:TUISystemMessageCell.class cellDataClass:TUISystemMessageCellData.class reuseID:TSystemMessageCell_ReuseId];
|
||||
[self bindMessageCellClass:TUIFaceMessageCell_Minimalist.class cellDataClass:TUIFaceMessageCellData.class reuseID:TFaceMessageCell_ReuseId];
|
||||
[self bindMessageCellClass:TUIVideoMessageCell_Minimalist.class cellDataClass:TUIVideoMessageCellData.class reuseID:TVideoMessageCell_ReuseId];
|
||||
[self bindMessageCellClass:TUIFileMessageCell_Minimalist.class cellDataClass:TUIFileMessageCellData.class reuseID:TFileMessageCell_ReuseId];
|
||||
[self bindMessageCellClass:TUIJoinGroupMessageCell_Minimalist.class cellDataClass:TUIJoinGroupMessageCellData.class reuseID:TJoinGroupMessageCell_ReuseId];
|
||||
[self bindMessageCellClass:TUIMergeMessageCell_Minimalist.class cellDataClass:TUIMergeMessageCellData.class reuseID:TMergeMessageCell_ReuserId];
|
||||
[self bindMessageCellClass:TUIReplyMessageCell_Minimalist.class cellDataClass:TUIReplyMessageCellData.class reuseID:TReplyMessageCell_ReuseId];
|
||||
[self bindMessageCellClass:TUIReferenceMessageCell_Minimalist.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
|
||||
@@ -0,0 +1,29 @@
|
||||
|
||||
// Created by Tencent on 2023/06/09.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
|
||||
#import <TIMCommon/TIMDefine.h>
|
||||
#import "TUIBaseMessageController_Minimalist.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface TUIMessageController_Minimalist : TUIBaseMessageController_Minimalist
|
||||
|
||||
/**
|
||||
* 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
|
||||
@@ -0,0 +1,613 @@
|
||||
|
||||
// Created by Tencent on 2023/06/09.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
|
||||
#import "TUIMessageController_Minimalist.h"
|
||||
#import <TIMCommon/TIMDefine.h>
|
||||
#import <TUICore/TUIGlobalization.h>
|
||||
#import <TUICore/TUIThemeManager.h>
|
||||
#import <TUICore/UIView+TUILayout.h>
|
||||
#import "TUIBaseMessageController_Minimalist+ProtectedAPI.h"
|
||||
#import "TUIChatConfig.h"
|
||||
#import "TUIChatModifyMessageHelper.h"
|
||||
#import "TUIChatSmallTongueView_Minimalist.h"
|
||||
#import "TUIMessageSearchDataProvider.h"
|
||||
#import "TUIReferenceMessageCell_Minimalist.h"
|
||||
#import "TUIReplyMessageCellData.h"
|
||||
#import "TUIReplyMessageCell_Minimalist.h"
|
||||
#import "TUITextMessageCell_Minimalist.h"
|
||||
|
||||
#define MSG_GET_COUNT 20
|
||||
|
||||
@interface TUIMessageController_Minimalist () <TUIChatSmallTongueViewDelegate_Minimalist>
|
||||
@property(nonatomic, strong) UIActivityIndicatorView *bottomIndicatorView;
|
||||
@property(nonatomic, assign) uint64_t locateGroupMessageSeq;
|
||||
@property(nonatomic, strong) TUIChatSmallTongueView_Minimalist *tongueView;
|
||||
@property(nonatomic, strong) NSMutableArray *receiveMsgs;
|
||||
@property(nonatomic, weak) UIImageView *backgroudView;
|
||||
@end
|
||||
|
||||
@implementation TUIMessageController_Minimalist
|
||||
|
||||
#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];
|
||||
|
||||
self.receiveMsgs = [NSMutableArray array];
|
||||
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
if (self.conversationData.atMsgSeqs.count > 0) {
|
||||
TUIChatSmallTongue_Minimalist *tongue = [[TUIChatSmallTongue_Minimalist alloc] init];
|
||||
tongue.type = TUIChatSmallTongueType_SomeoneAt;
|
||||
tongue.parentView = self.view.superview;
|
||||
tongue.atMsgSeqs = [self.conversationData.atMsgSeqs copy];
|
||||
[TUIChatSmallTongueManager_Minimalist showTongue:tongue delegate:self];
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
- (void)dealloc {
|
||||
[TUIChatSmallTongueManager_Minimalist removeTongue];
|
||||
[NSNotificationCenter.defaultCenter removeObserver:self];
|
||||
}
|
||||
|
||||
- (void)viewWillAppear:(BOOL)animated {
|
||||
[super viewWillAppear:animated];
|
||||
[TUIChatSmallTongueManager_Minimalist hideTongue:NO];
|
||||
}
|
||||
|
||||
- (void)viewWillDisappear:(BOOL)animated {
|
||||
[super viewWillDisappear:animated];
|
||||
[TUIChatSmallTongueManager_Minimalist hideTongue:YES];
|
||||
}
|
||||
|
||||
#pragma mark - Notification
|
||||
|
||||
- (void)keyboardWillShow {
|
||||
if (![self messageSearchDataProvider].isNewerNoMoreMsg) {
|
||||
[[self messageSearchDataProvider] removeAllSearchData];
|
||||
[self.tableView reloadData];
|
||||
[self loadMessages:YES];
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - Overrider
|
||||
- (void)willShowMediaMessage:(TUIMessageCell *)cell {
|
||||
[TUIChatSmallTongueManager_Minimalist hideTongue:YES];
|
||||
}
|
||||
|
||||
- (void)didCloseMediaMessage:(TUIMessageCell *)cell {
|
||||
[TUIChatSmallTongueManager_Minimalist hideTongue:NO];
|
||||
}
|
||||
|
||||
- (void)onDelete:(TUIMessageCell *)cell {
|
||||
TUIMessageCellData *cellData = cell.messageData;
|
||||
@weakify(self);
|
||||
UIAlertController *vc = [UIAlertController alertControllerWithTitle:nil
|
||||
message:TIMCommonLocalizableString(ConfirmDeleteMessage)
|
||||
preferredStyle:UIAlertControllerStyleActionSheet];
|
||||
[vc tuitheme_addAction:[UIAlertAction actionWithTitle:TIMCommonLocalizableString(Delete)
|
||||
style:UIAlertActionStyleDestructive
|
||||
handler:^(UIAlertAction *_Nonnull action) {
|
||||
@strongify(self);
|
||||
[self.messageDataProvider deleteUIMsgs:@[ cellData ]
|
||||
SuccBlock:^{
|
||||
[self updateAtMeTongue:cellData];
|
||||
}
|
||||
FailBlock:^(int code, NSString *desc) {
|
||||
NSLog(@"remove msg failed!");
|
||||
NSAssert(NO, desc);
|
||||
}];
|
||||
}]];
|
||||
[vc tuitheme_addAction:[UIAlertAction actionWithTitle:TIMCommonLocalizableString(Cancel) style:UIAlertActionStyleCancel handler:nil]];
|
||||
[self presentViewController:vc animated:YES completion:nil];
|
||||
}
|
||||
|
||||
- (void)updateAtMeTongue:(TUIMessageCellData *)deleteCellData {
|
||||
uint64_t deleteSeq = deleteCellData.innerMessage.seq;
|
||||
if ([self.conversationData.atMsgSeqs containsObject:@(deleteSeq)]) {
|
||||
[self.conversationData.atMsgSeqs removeObject:@(deleteSeq)];
|
||||
if (self.conversationData.atMsgSeqs.count > 0) {
|
||||
TUIChatSmallTongue_Minimalist *tongue = [[TUIChatSmallTongue_Minimalist alloc] init];
|
||||
tongue.type = TUIChatSmallTongueType_SomeoneAt;
|
||||
tongue.parentView = self.view.superview;
|
||||
tongue.atMsgSeqs = [self.conversationData.atMsgSeqs copy];
|
||||
[TUIChatSmallTongueManager_Minimalist showTongue:tongue delegate:self];
|
||||
} else {
|
||||
[TUIChatSmallTongueManager_Minimalist removeTongue:TUIChatSmallTongueType_SomeoneAt];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - UIScrollViewDelegate
|
||||
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
|
||||
[super scrollViewDidScroll:scrollView];
|
||||
if (scrollView.contentOffset.y <= TMessageController_Header_Height && ![self messageSearchDataProvider].isOlderNoMoreMsg) {
|
||||
/**
|
||||
* Display pull-to-refresh icon
|
||||
*/
|
||||
if (!self.indicatorView.isAnimating) {
|
||||
[self.indicatorView startAnimating];
|
||||
}
|
||||
} else if ([self isScrollToBottomIndicatorViewY:scrollView]) {
|
||||
if (![self messageSearchDataProvider].isNewerNoMoreMsg) {
|
||||
/**
|
||||
* Display pull-up-loading icon
|
||||
*/
|
||||
if (!self.bottomIndicatorView.isAnimating) {
|
||||
[self.bottomIndicatorView startAnimating];
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Remove the "back to the latest position", "xxx new message" bottom-banner-tips
|
||||
*/
|
||||
if (self.isInVC) {
|
||||
[TUIChatSmallTongueManager_Minimalist removeTongue:TUIChatSmallTongueType_ScrollToBoom];
|
||||
[TUIChatSmallTongueManager_Minimalist 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_Minimalist *tongue = [[TUIChatSmallTongue_Minimalist alloc] init];
|
||||
tongue.type = TUIChatSmallTongueType_ScrollToBoom;
|
||||
tongue.parentView = self.view.superview;
|
||||
[TUIChatSmallTongueManager_Minimalist 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_Minimalist 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;
|
||||
self.messageDataProvider.mergeAdjacentMsgsFromTheSameSender = YES;
|
||||
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(), ^{
|
||||
@strongify(self);
|
||||
[self scrollToLocateMessage:scrollToBoom];
|
||||
if (isHighlight) {
|
||||
[self highlightKeyword];
|
||||
}
|
||||
});
|
||||
}
|
||||
FailBlock:^(int code, NSString *desc){}];
|
||||
}
|
||||
|
||||
- (void)scrollToLocateMessage:(BOOL)scrollToBoom {
|
||||
/**
|
||||
* locateMsg
|
||||
* 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];
|
||||
[cell layoutSubviews];
|
||||
@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];
|
||||
[cell layoutSubviews];
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
- (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_Minimalist *)cell {
|
||||
NSString *originMsgID = @"";
|
||||
NSString *msgAbstract = @"";
|
||||
if ([cell isKindOfClass:TUIReplyMessageCell_Minimalist.class]) {
|
||||
TUIReplyMessageCell_Minimalist *acell = (TUIReplyMessageCell_Minimalist *)cell;
|
||||
TUIReplyMessageCellData *cellData = acell.replyData;
|
||||
originMsgID = cellData.messageRootID;
|
||||
msgAbstract = cellData.msgAbstract;
|
||||
} else if ([cell isKindOfClass:TUIReferenceMessageCell_Minimalist.class]) {
|
||||
TUIReferenceMessageCell_Minimalist *acell = (TUIReferenceMessageCell_Minimalist *)cell;
|
||||
TUIReferenceMessageCellData *cellData = acell.referenceData;
|
||||
originMsgID = cellData.originMsgID;
|
||||
msgAbstract = cellData.msgAbstract;
|
||||
}
|
||||
|
||||
[(TUIMessageSearchDataProvider *)self.messageDataProvider
|
||||
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_Minimalist.class]) {
|
||||
[self jumpDetailPageByMessage:message];
|
||||
} else if ([cell isKindOfClass:TUIReferenceMessageCell_Minimalist.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_Minimalist *tongue = [[TUIChatSmallTongue_Minimalist alloc] init];
|
||||
tongue.type = TUIChatSmallTongueType_ReceiveNewMsg;
|
||||
tongue.parentView = self.view.superview;
|
||||
tongue.unreadMsgCount = self.receiveMsgs.count;
|
||||
[TUIChatSmallTongueManager_Minimalist 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_Minimalist *tongue = [[TUIChatSmallTongue_Minimalist alloc] init];
|
||||
tongue.type = TUIChatSmallTongueType_ReceiveNewMsg;
|
||||
tongue.parentView = self.view.superview;
|
||||
tongue.unreadMsgCount = self.receiveMsgs.count;
|
||||
if (tongue.unreadMsgCount != 0) {
|
||||
[TUIChatSmallTongueManager_Minimalist showTongue:tongue delegate:self];
|
||||
} else {
|
||||
[TUIChatSmallTongueManager_Minimalist 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];
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
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_Minimalist *)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_Minimalist 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_Minimalist 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
|
||||
@@ -0,0 +1,68 @@
|
||||
|
||||
// Created by Tencent on 2023/06/09.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@class TUIMessageMultiChooseView_Minimalist;
|
||||
|
||||
@protocol TUIMessageMultiChooseViewDelegate_Minimalist <NSObject>
|
||||
|
||||
/**
|
||||
* Callback when the cancel button on the multi-select message panel is clicked
|
||||
*/
|
||||
- (void)messageMultiChooseViewOnCancelClicked:(TUIMessageMultiChooseView_Minimalist *)multiChooseView;
|
||||
|
||||
/**
|
||||
* Callback for when the forward button on the multi-select message panel is clicked
|
||||
*/
|
||||
- (void)messageMultiChooseViewOnRelayClicked:(TUIMessageMultiChooseView_Minimalist *)multiChooseView;
|
||||
|
||||
/**
|
||||
* Callback for when the delete button on the multi-select message panel is clicked
|
||||
*/
|
||||
- (void)messageMultiChooseViewOnDeleteClicked:(TUIMessageMultiChooseView_Minimalist *)multiChooseView;
|
||||
|
||||
@end
|
||||
|
||||
@interface TUIMessageMultiChooseView_Minimalist : UIView
|
||||
|
||||
@property(nonatomic, weak) id<TUIMessageMultiChooseViewDelegate_Minimalist> 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;
|
||||
|
||||
@property(nonatomic, strong) UIButton *deleteButton;
|
||||
|
||||
@property(nonatomic, strong) UILabel *selectedCountLabel;
|
||||
|
||||
@property(nonatomic, strong) UIButton *bottomCancelButton;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,168 @@
|
||||
//
|
||||
// TUIMessageMultiChooseView.m
|
||||
// Pods
|
||||
//
|
||||
// Created by harvy on 2020/11/27.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
//
|
||||
|
||||
#import "TUIMessageMultiChooseView_Minimalist.h"
|
||||
#import <TUICore/TUIDarkModel.h>
|
||||
#import <TUICore/TUIGlobalization.h>
|
||||
#import <TUICore/TUIThemeManager.h>
|
||||
#import <TUICore/UIView+TUILayout.h>
|
||||
#import <TIMCommon/TIMDefine.h>
|
||||
|
||||
@interface TUIMessageMultiChooseView_Minimalist ()
|
||||
|
||||
@property(nonatomic, strong) CALayer *separtorLayer;
|
||||
|
||||
@end
|
||||
|
||||
@implementation TUIMessageMultiChooseView_Minimalist
|
||||
|
||||
- (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
|
||||
{
|
||||
CGFloat width = self.menuView.bounds.size.width;
|
||||
self.relayButton.frame = CGRectMake(kScale390(23), kScale390(17), kScale390(16), kScale390(16));
|
||||
self.deleteButton.frame =
|
||||
CGRectMake(self.relayButton.frame.origin.x + self.relayButton.frame.size.width + kScale390(17), kScale390(17), kScale390(16), kScale390(16));
|
||||
[self.selectedCountLabel sizeToFit];
|
||||
CGFloat labelWidth = self.selectedCountLabel.frame.size.width;
|
||||
labelWidth = labelWidth + 10;
|
||||
self.selectedCountLabel.frame = CGRectMake((width - labelWidth) * 0.5, kScale390(14), labelWidth, kScale390(30));
|
||||
self.bottomCancelButton.frame = CGRectMake((width - kScale390(50) - kScale390(23)), kScale390(14), kScale390((50)), kScale390(30));
|
||||
}
|
||||
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 setImage:[UIImage imageNamed:TUIChatImagePath_Minimalist(@"icon_mutilselect_forward")] forState:UIControlStateNormal];
|
||||
[_relayButton addTarget:self action:@selector(onRelay:) forControlEvents:UIControlEventTouchUpInside];
|
||||
[_menuView addSubview:_relayButton];
|
||||
|
||||
_deleteButton = [UIButton buttonWithType:UIButtonTypeCustom];
|
||||
[_deleteButton setImage:[UIImage imageNamed:TUIChatImagePath_Minimalist(@"icon_mutilselect_delete")] forState:UIControlStateNormal];
|
||||
[_deleteButton addTarget:self action:@selector(onDelete:) forControlEvents:UIControlEventTouchUpInside];
|
||||
[_menuView addSubview:_deleteButton];
|
||||
|
||||
_selectedCountLabel = [[UILabel alloc] init];
|
||||
_selectedCountLabel.text = @"1 Selecte";
|
||||
_selectedCountLabel.font = [UIFont systemFontOfSize:14.0];
|
||||
[_menuView addSubview:_selectedCountLabel];
|
||||
|
||||
_bottomCancelButton = [UIButton buttonWithType:UIButtonTypeCustom];
|
||||
[_bottomCancelButton setTitle:TIMCommonLocalizableString(Cancel) forState:UIControlStateNormal];
|
||||
_bottomCancelButton.titleLabel.font = [UIFont systemFontOfSize:14.0];
|
||||
[_bottomCancelButton setTitleColor:[UIColor systemBlueColor] forState:UIControlStateNormal];
|
||||
[_bottomCancelButton addTarget:self action:@selector(onCancel:) forControlEvents:UIControlEventTouchUpInside];
|
||||
[_menuView addSubview:_bottomCancelButton];
|
||||
}
|
||||
|
||||
#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,34 @@
|
||||
//
|
||||
// TUIMessageReadViewController_Minimalist.h
|
||||
// TUIChat
|
||||
//
|
||||
// Created by xia on 2022/3/10.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
//
|
||||
|
||||
#import <TIMCommon/TUIMessageCellData.h>
|
||||
#import <TIMCommon/TUIMessageCell_Minimalist.h>
|
||||
#import <UIKit/UIKit.h>
|
||||
#import "TUIChatDefine.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@class TUIMessageDataProvider;
|
||||
|
||||
@interface TUIMessageReadViewController_Minimalist : UIViewController
|
||||
@property(nonatomic, strong) Class alertCellClass;
|
||||
@property(nonatomic, strong) TUIMessageCellData *alertViewCellData;
|
||||
@property(nonatomic, assign) CGRect originFrame;
|
||||
|
||||
@property(copy, nonatomic) void (^viewWillShowHandler)(TUIMessageCell *alertView);
|
||||
@property(copy, nonatomic) void (^viewDidShowHandler)(TUIMessageCell *alertView);
|
||||
@property(copy, nonatomic) void (^viewWillDismissHandler)(TUIMessageCell *alertView);
|
||||
|
||||
- (instancetype)initWithCellData:(TUIMessageCellData *)data
|
||||
dataProvider:(TUIMessageDataProvider *)dataProvider
|
||||
showReadStatusDisable:(BOOL)showReadStatusDisable
|
||||
c2cReceiverName:(NSString *)name
|
||||
c2cReceiverAvatar:(NSString *)avatarUrl;
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,434 @@
|
||||
//
|
||||
// TUIMessageReadViewController_Minimalist.m
|
||||
// TUIChat
|
||||
//
|
||||
// Created by xia on 2022/3/10.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
//
|
||||
|
||||
#import "TUIMessageReadViewController_Minimalist.h"
|
||||
#import <TUICore/TUICore.h>
|
||||
#import <TUICore/TUIThemeManager.h>
|
||||
#import <TUICore/TUITool.h>
|
||||
#import <TUICore/UIColor+TUIHexColor.h>
|
||||
#import "TUIImageMessageCellData.h"
|
||||
#import "TUIMemberCellData.h"
|
||||
#import "TUIMemberCell_Minimalist.h"
|
||||
#import "TUIMessageDataProvider.h"
|
||||
#import "TUIVideoMessageCellData.h"
|
||||
#import "TUITextMessageCellData.h"
|
||||
#import "TUIReplyMessageCellData.h"
|
||||
#import "TUIVoiceMessageCellData.h"
|
||||
#import "TUIMessageCellConfig_Minimalist.h"
|
||||
|
||||
@interface TUIMessageReadViewController_Minimalist () <UITableViewDelegate, UITableViewDataSource,TUINotificationProtocol>
|
||||
|
||||
@property(nonatomic, strong) TUIMessageCellData *cellData;
|
||||
@property(nonatomic, assign) BOOL showReadStatusDisable;
|
||||
@property(nonatomic, strong) TUIMessageDataProvider *dataProvider;
|
||||
|
||||
@property(nonatomic, strong) UIView *messageBackView;
|
||||
@property(nonatomic, strong) UITableView *tableView;
|
||||
|
||||
@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;
|
||||
@property(nonatomic, strong) TUIMessageCell_Minimalist *alertView;
|
||||
@property(nonatomic, strong) TUIMessageCellConfig_Minimalist *messageCellConfig;
|
||||
@end
|
||||
|
||||
@implementation TUIMessageReadViewController_Minimalist
|
||||
|
||||
#pragma mark - Life cycle
|
||||
- (void)viewDidLoad {
|
||||
[super viewDidLoad];
|
||||
if ([self isGroupMessageRead]) {
|
||||
[self loadMembers];
|
||||
}
|
||||
[self setupViews];
|
||||
// Do any additional setup after loading the view.
|
||||
|
||||
[TUICore registerEvent:TUICore_TUIPluginNotify subKey:TUICore_TUIPluginNotify_DidChangePluginViewSubKey object:self];
|
||||
}
|
||||
|
||||
- (void)viewWillLayoutSubviews {
|
||||
[super viewWillLayoutSubviews];
|
||||
[self layoutViews];
|
||||
}
|
||||
|
||||
- (void)viewWillAppear:(BOOL)animated {
|
||||
[super viewWillAppear:animated];
|
||||
|
||||
[self updateRootMsg];
|
||||
if (_viewWillShowHandler) {
|
||||
_viewWillShowHandler(_alertView);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)viewDidAppear:(BOOL)animated {
|
||||
[super viewDidAppear:animated];
|
||||
|
||||
if (_viewDidShowHandler) {
|
||||
_viewDidShowHandler(_alertView);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)viewWillDisappear:(BOOL)animated {
|
||||
[super viewWillDisappear:animated];
|
||||
if (_viewWillDismissHandler) {
|
||||
_viewWillDismissHandler(_alertView);
|
||||
}
|
||||
}
|
||||
- (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];
|
||||
}
|
||||
|
||||
- (void)layoutViews {
|
||||
float backViewTop = self.navigationController.navigationBar.mm_maxY;
|
||||
self.messageBackView.frame = CGRectMake(0, 0, self.view.bounds.size.width, kScale390(17)+ kScale390(24)+ kScale390(4));
|
||||
self.tableView.mm_top(backViewTop).mm_left(0).mm_width(self.view.mm_w).mm_height(self.view.mm_h - _tableView.mm_y);
|
||||
}
|
||||
|
||||
- (void)setupTitleView {
|
||||
UILabel *titleLabel = [[UILabel alloc] init];
|
||||
titleLabel.text = TIMCommonLocalizableString(MessageInfo);
|
||||
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");
|
||||
messageBackView.userInteractionEnabled = YES;
|
||||
self.messageBackView = messageBackView;
|
||||
self.tableView.tableHeaderView = self.messageBackView;
|
||||
self.tableView.tableHeaderView.userInteractionEnabled = YES;
|
||||
|
||||
UILabel *dateLabel = [[UILabel alloc] init];
|
||||
[messageBackView addSubview:dateLabel];
|
||||
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:kScale390(14)];
|
||||
dateLabel.textAlignment = isRTL()?NSTextAlignmentRight:NSTextAlignmentLeft;
|
||||
dateLabel.textColor = TUIChatDynamicColor(@"chat_message_read_name_date_text_color", @"#999999");
|
||||
[dateLabel sizeToFit];
|
||||
[dateLabel mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.centerX.mas_equalTo(self.view);
|
||||
make.top.mas_equalTo(kScale390(17));
|
||||
make.width.mas_equalTo(dateLabel.frame.size.width);
|
||||
make.height.mas_equalTo(kScale390(24));
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)updateRootMsg {
|
||||
TUIMessageCellData *data = self.alertViewCellData;
|
||||
self.alertViewCellData.showAvatar = NO;
|
||||
self.alertViewCellData.showMessageModifyReplies = NO;
|
||||
}
|
||||
|
||||
- (void)loadMembers {
|
||||
[self getReadMembersWithCompletion:^(int code, NSString *desc, NSArray *members, BOOL isFinished) {
|
||||
[self.tableView reloadData];
|
||||
}];
|
||||
[self getUnreadMembersWithCompletion:^(int code, NSString *desc, NSArray *members, BOOL isFinished) {
|
||||
[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) {
|
||||
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) {
|
||||
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_Minimalist key:TUICore_TUIContactObjectFactory_GetUserOrFriendProfileVCMethod param:param];
|
||||
}
|
||||
|
||||
#pragma mark - UITableViewDataSource & UITableViewDelegate
|
||||
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
|
||||
[tableView deselectRowAtIndexPath:indexPath animated:NO];
|
||||
TUICommonCellData *data = [self members][indexPath.row];
|
||||
@weakify(self);
|
||||
if ([data isKindOfClass:TUIMemberDescribeCellData.class]) {
|
||||
return;
|
||||
} else if ([data isKindOfClass:TUIMemberCellData.class]) {
|
||||
if ([self isGroupMessageRead]) {
|
||||
if (indexPath.row >= [self members].count) {
|
||||
return;
|
||||
}
|
||||
TUIMemberCellData *currentData = (TUIMemberCellData *)data;
|
||||
[self getUserOrFriendProfileVCWithUserID:currentData.userID
|
||||
SuccBlock:^(UIViewController *vc) {
|
||||
@strongify(self);
|
||||
[self.navigationController pushViewController:vc animated:YES];
|
||||
}
|
||||
failBlock:nil];
|
||||
} else {
|
||||
[self getUserOrFriendProfileVCWithUserID:self.cellData.innerMessage.userID
|
||||
SuccBlock:^(UIViewController *vc) {
|
||||
@strongify(self);
|
||||
[self.navigationController pushViewController:vc animated:YES];
|
||||
}
|
||||
failBlock:nil];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
|
||||
return 2;
|
||||
}
|
||||
|
||||
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
|
||||
|
||||
if(section == 0){
|
||||
return 1;
|
||||
}
|
||||
return [self members].count;
|
||||
}
|
||||
|
||||
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
|
||||
|
||||
if (indexPath.section == 0) {
|
||||
TUIMessageCell *cell = nil;
|
||||
TUIMessageCellData *data = self.alertViewCellData;
|
||||
cell = [tableView dequeueReusableCellWithIdentifier:data.reuseId forIndexPath:indexPath];
|
||||
cell.delegate = self;
|
||||
[cell fillWithData:data];
|
||||
[cell notifyBottomContainerReadyOfData:nil];
|
||||
return cell;
|
||||
}
|
||||
|
||||
TUICommonCellData *data = [self members][indexPath.row];
|
||||
TUICommonTableViewCell *cell = nil;
|
||||
|
||||
if ([data isKindOfClass:TUIMemberDescribeCellData.class]) {
|
||||
cell = [tableView dequeueReusableCellWithIdentifier:@"TUIMemberDescribeCell_Minimalist" forIndexPath:indexPath];
|
||||
} else {
|
||||
cell = [tableView dequeueReusableCellWithIdentifier:kMemberCellReuseId forIndexPath:indexPath];
|
||||
}
|
||||
|
||||
[cell fillWithData:data];
|
||||
return cell;
|
||||
}
|
||||
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
|
||||
if (indexPath.section == 0) {
|
||||
CGFloat margin = 0;
|
||||
if (self.cellData.sameToNextMsgSender) {
|
||||
margin = 10;
|
||||
}
|
||||
return [self.messageCellConfig getHeightFromMessageCellData:self.cellData] + margin;
|
||||
}
|
||||
|
||||
TUICommonCellData *data = [self members][indexPath.row];
|
||||
if ([data isKindOfClass:TUIMemberDescribeCellData.class]) {
|
||||
return kScale390(57);
|
||||
} else {
|
||||
return kScale390(57);
|
||||
}
|
||||
return kScale390(57);
|
||||
}
|
||||
#pragma mark - datasource
|
||||
- (NSArray *)members {
|
||||
NSMutableArray *dataArray = [NSMutableArray arrayWithCapacity:2];
|
||||
|
||||
if ([self isGroupMessageRead]) {
|
||||
if (self.readMembers.count > 0) {
|
||||
TUIMemberDescribeCellData *describeCellData = [[TUIMemberDescribeCellData alloc] init];
|
||||
describeCellData.title = TIMCommonLocalizableString(GroupReadBy);
|
||||
describeCellData.icon = [[TUIImageCache sharedInstance] getResourceFromCache:TUIChatImagePath_Minimalist(@"msg_status_all_people_read")];
|
||||
[dataArray addObject:describeCellData];
|
||||
|
||||
for (V2TIMGroupMemberInfo *member in self.readMembers) {
|
||||
TUIMemberCellData *data = [[TUIMemberCellData alloc] initWithUserID:member.userID
|
||||
nickName:member.nickName
|
||||
friendRemark:member.friendRemark
|
||||
nameCard:member.nameCard
|
||||
avatarUrl:member.faceURL
|
||||
detail:nil];
|
||||
[dataArray addObject:data];
|
||||
}
|
||||
}
|
||||
|
||||
if (self.unreadMembers.count > 0) {
|
||||
TUIMemberDescribeCellData *describeCellData = [[TUIMemberDescribeCellData alloc] init];
|
||||
describeCellData.title = TIMCommonLocalizableString(GroupDeliveredTo);
|
||||
describeCellData.icon = [[TUIImageCache sharedInstance] getResourceFromCache:TUIChatImagePath_Minimalist(@"msg_status_some_people_read")];
|
||||
|
||||
[dataArray addObject:describeCellData];
|
||||
|
||||
for (V2TIMGroupMemberInfo *member in self.unreadMembers) {
|
||||
TUIMemberCellData *data = [[TUIMemberCellData alloc] initWithUserID:member.userID
|
||||
nickName:member.nickName
|
||||
friendRemark:member.friendRemark
|
||||
nameCard:member.nameCard
|
||||
avatarUrl:member.faceURL
|
||||
detail:nil];
|
||||
[dataArray addObject:data];
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (self.cellData.direction == MsgDirectionIncoming) {
|
||||
return dataArray;
|
||||
}
|
||||
NSString *detail = nil;
|
||||
BOOL isPeerRead = self.cellData.messageReceipt.isPeerRead;
|
||||
|
||||
TUIMemberDescribeCellData *describeCellData = [[TUIMemberDescribeCellData alloc] init];
|
||||
if (isPeerRead) {
|
||||
describeCellData.title = TIMCommonLocalizableString(C2CReadBy);
|
||||
describeCellData.icon = [[TUIImageCache sharedInstance] getResourceFromCache:TUIChatImagePath_Minimalist(@"msg_status_all_people_read")];
|
||||
} else {
|
||||
describeCellData.title = TIMCommonLocalizableString(C2CDeliveredTo);
|
||||
describeCellData.icon = [[TUIImageCache sharedInstance] getResourceFromCache:TUIChatImagePath_Minimalist(@"msg_status_some_people_read")];
|
||||
}
|
||||
|
||||
TUIMemberCellData *data = [[TUIMemberCellData alloc] initWithUserID:self.cellData.innerMessage.userID
|
||||
nickName:nil
|
||||
friendRemark:self.c2cReceiverName
|
||||
nameCard:nil
|
||||
avatarUrl:self.c2cReceiverAvatarUrl
|
||||
detail:detail];
|
||||
|
||||
[dataArray addObject:describeCellData];
|
||||
[dataArray addObject:data];
|
||||
}
|
||||
|
||||
return dataArray;
|
||||
}
|
||||
|
||||
- (NSMutableArray *)readMembers {
|
||||
if (!_readMembers) {
|
||||
_readMembers = [[NSMutableArray alloc] init];
|
||||
}
|
||||
return _readMembers;
|
||||
}
|
||||
|
||||
- (NSMutableArray *)unreadMembers {
|
||||
if (!_unreadMembers) {
|
||||
_unreadMembers = [[NSMutableArray alloc] init];
|
||||
}
|
||||
return _unreadMembers;
|
||||
}
|
||||
|
||||
- (BOOL)isGroupMessageRead {
|
||||
return self.cellData.innerMessage.groupID.length > 0;
|
||||
}
|
||||
|
||||
- (UITableView *)tableView {
|
||||
if (!_tableView) {
|
||||
_tableView = [[UITableView alloc] initWithFrame:CGRectZero style:UITableViewStylePlain];
|
||||
_tableView.delegate = self;
|
||||
_tableView.dataSource = self;
|
||||
_tableView.contentInset = UIEdgeInsetsMake(0, 0, 8, 0);
|
||||
[_tableView setBackgroundColor:TIMCommonDynamicColor(@"form_bg_color", @"#FFFFFF")];
|
||||
[self.view addSubview:_tableView];
|
||||
|
||||
UIView *view = [[UIView alloc] initWithFrame:CGRectZero];
|
||||
[_tableView setTableFooterView:view];
|
||||
_tableView.separatorStyle = UITableViewCellSeparatorStyleNone;
|
||||
[_tableView registerClass:[TUIMemberCell_Minimalist class] forCellReuseIdentifier:kMemberCellReuseId];
|
||||
[_tableView registerClass:[TUIMemberDescribeCell_Minimalist class] forCellReuseIdentifier:@"TUIMemberDescribeCell_Minimalist"];
|
||||
[self.messageCellConfig bindTableView:self.tableView];
|
||||
}
|
||||
return _tableView;
|
||||
}
|
||||
|
||||
- (void)dataProvider:(TUIMessageBaseDataProvider *)dataProvider onRemoveHeightCache:(TUIMessageCellData *)cellData {
|
||||
if (cellData) {
|
||||
[self.messageCellConfig removeHeightCacheOfMessageCellData:cellData];
|
||||
}
|
||||
}
|
||||
- (TUIMessageCellConfig_Minimalist *)messageCellConfig {
|
||||
if (_messageCellConfig == nil) {
|
||||
_messageCellConfig = [[TUIMessageCellConfig_Minimalist alloc] init];
|
||||
}
|
||||
return _messageCellConfig;
|
||||
}
|
||||
|
||||
#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]) {
|
||||
// Translation View is Shown or content changed.
|
||||
TUIMessageCellData *data = param[TUICore_TUIPluginNotify_DidChangePluginViewSubKey_Data];
|
||||
[self clearAndReloadCellOfData:data];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)clearAndReloadCellOfData:(TUIMessageCellData *)data {
|
||||
[self.messageCellConfig removeHeightCacheOfMessageCellData:data];
|
||||
[self.tableView reloadData];
|
||||
}
|
||||
@end
|
||||
@@ -0,0 +1,33 @@
|
||||
//
|
||||
// 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_Minimalist.h"
|
||||
#import "TUIChatConversationModel.h"
|
||||
#import "TUIChatFlexViewController.h"
|
||||
#import "TUIInputController_Minimalist.h"
|
||||
#import "TUIMessageDataProvider.h"
|
||||
|
||||
@class TUIMessageDataProvider;
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface TUIRepliesDetailViewController_Minimalist : TUIChatFlexViewController
|
||||
|
||||
- (instancetype)initWithCellData:(TUIMessageCellData *)data conversationData:(TUIChatConversationModel *)conversationData;
|
||||
|
||||
@property(nonatomic, weak) id<TUIBaseMessageControllerDelegate_Minimalist> delegate;
|
||||
@property(nonatomic, strong) V2TIMMergerElem *mergerElem;
|
||||
@property(nonatomic, copy) dispatch_block_t willCloseCallback;
|
||||
@property(nonatomic, strong) TUIInputController_Minimalist *inputController;
|
||||
@property(nonatomic, strong) TUIMessageDataProvider *parentPageDataProvider;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,805 @@
|
||||
//
|
||||
// TUIRepliesDetailViewController.m
|
||||
// TUIChat
|
||||
//
|
||||
// Created by wyl on 2022/4/27.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
//
|
||||
|
||||
#import "TUIRepliesDetailViewController_Minimalist.h"
|
||||
#import <TIMCommon/TUIMessageCell.h>
|
||||
#import "TUIChatDataProvider.h"
|
||||
#import "TUIMenuCellData.h"
|
||||
#import "TUIMessageDataProvider.h"
|
||||
#import "TUITextMessageCellData.h"
|
||||
#import "TUITextMessageCell_Minimalist.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_Minimalist.h"
|
||||
#import "TUIFileMessageCell_Minimalist.h"
|
||||
#import "TUIFileViewController_Minimalist.h"
|
||||
#import "TUIImageMessageCell_Minimalist.h"
|
||||
#import "TUIJoinGroupMessageCell_Minimalist.h"
|
||||
#import "TUILinkCell_Minimalist.h"
|
||||
#import "TUIMediaView_Minimalist.h"
|
||||
#import "TUIMergeMessageCell_Minimalist.h"
|
||||
#import "TUIMergeMessageListController_Minimalist.h"
|
||||
#import "TUIReferenceMessageCell_Minimalist.h"
|
||||
#import "TUIReplyMessageCellData.h"
|
||||
#import "TUIReplyMessageCell_Minimalist.h"
|
||||
#import "TUITextMessageCell_Minimalist.h"
|
||||
#import "TUIVideoMessageCell_Minimalist.h"
|
||||
#import "TUIVoiceMessageCell_Minimalist.h"
|
||||
#import "TUIMessageCellConfig_Minimalist.h"
|
||||
|
||||
@interface TUIRepliesDetailViewController_Minimalist () <TUIInputControllerDelegate_Minimalist,
|
||||
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) TUIMessageCellLayout *originCellLayout;
|
||||
@property TMsgDirection direction;
|
||||
@property(nonatomic, assign) BOOL showAvatar;
|
||||
@property(nonatomic, assign) BOOL sameToNextMsgSender;
|
||||
@property(nonatomic) BOOL isMsgNeedReadReceipt;
|
||||
|
||||
// topGestureView subviews
|
||||
@property(nonatomic, strong) UIButton *cancelButton;
|
||||
@property(nonatomic, strong) UILabel *titleLabel;
|
||||
|
||||
@property(nonatomic, strong) TUIMessageCellConfig_Minimalist *messageCellConfig;
|
||||
|
||||
@end
|
||||
|
||||
@implementation TUIRepliesDetailViewController_Minimalist
|
||||
- (instancetype)initWithCellData:(TUIMessageCellData *)data conversationData:(TUIChatConversationModel *)conversationData {
|
||||
self = [super init];
|
||||
self.cellData = data;
|
||||
self.showAvatar = data.showAvatar;
|
||||
self.sameToNextMsgSender = data.sameToNextMsgSender;
|
||||
self.cellData.showAvatar = YES;
|
||||
self.cellData.sameToNextMsgSender = NO;
|
||||
[self setConversation:conversationData];
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc {
|
||||
self.cellData.showAvatar = self.showAvatar;
|
||||
self.cellData.sameToNextMsgSender = self.sameToNextMsgSender;
|
||||
[TUICore unRegisterEventByObject:self];
|
||||
}
|
||||
|
||||
- (void)viewDidLoad {
|
||||
[super viewDidLoad];
|
||||
|
||||
[self setupViews];
|
||||
|
||||
// [self setupInputViewController];
|
||||
|
||||
[self setnormalTop];
|
||||
|
||||
self.topImgView.hidden = YES;
|
||||
|
||||
_cancelButton = [UIButton buttonWithType:UIButtonTypeCustom];
|
||||
[_cancelButton setTitle:TIMCommonLocalizableString(Cancel) forState:UIControlStateNormal];
|
||||
[_cancelButton setTitleColor:[UIColor systemBlueColor] forState:UIControlStateNormal];
|
||||
_cancelButton.titleLabel.font = [UIFont systemFontOfSize:16.0];
|
||||
[_cancelButton addTarget:self action:@selector(onCancel:) forControlEvents:UIControlEventTouchUpInside];
|
||||
[_cancelButton sizeToFit];
|
||||
[self.topGestureView addSubview:_cancelButton];
|
||||
|
||||
_titleLabel = [[UILabel alloc] init];
|
||||
_titleLabel.text = [NSString stringWithFormat:@"0 %@", TIMCommonLocalizableString(TUIKitThreadQuote)];
|
||||
_titleLabel.font = [UIFont boldSystemFontOfSize:16.0];
|
||||
_titleLabel.textColor = TIMCommonDynamicColor(@"form_title_color", @"#000000");
|
||||
[_titleLabel sizeToFit];
|
||||
[self.topGestureView addSubview:_titleLabel];
|
||||
|
||||
[self updateSubContainerView];
|
||||
|
||||
[[V2TIMManager sharedInstance] addAdvancedMsgListener:self];
|
||||
|
||||
[TUICore registerEvent:TUICore_TUIPluginNotify subKey:TUICore_TUIPluginNotify_DidChangePluginViewSubKey object:self];
|
||||
}
|
||||
|
||||
- (void)updateSubContainerView {
|
||||
// [super updateSubContainerView];
|
||||
self.topGestureView.frame = CGRectMake(0, 0, self.containerView.frame.size.width, kScale390(60));
|
||||
self.cancelButton.frame =
|
||||
CGRectMake(kScale390(15), (self.topGestureView.bounds.size.height - kScale390(22)) * 0.5, self.cancelButton.frame.size.width, kScale390(22));
|
||||
self.titleLabel.frame = CGRectMake((self.topGestureView.bounds.size.width - self.titleLabel.frame.size.width) * 0.5, self.cancelButton.frame.origin.y,
|
||||
self.titleLabel.frame.size.width, kScale390(22));
|
||||
|
||||
self.tableView.frame = CGRectMake(0, self.topGestureView.frame.size.height, self.containerView.frame.size.width,
|
||||
self.containerView.frame.size.height - self.topGestureView.frame.size.height);
|
||||
if (isRTL()) {
|
||||
[self.cancelButton resetFrameToFitRTL];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)viewWillAppear:(BOOL)animated {
|
||||
[self updateRootMsg];
|
||||
|
||||
[self applyData];
|
||||
|
||||
[self updateTableViewConstraint];
|
||||
|
||||
[self updateSubContainerView];
|
||||
}
|
||||
|
||||
- (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)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];
|
||||
}
|
||||
self.titleLabel.text = [NSString stringWithFormat:@"%lu %@", (unsigned long)msgIDArray.count, TIMCommonLocalizableString(TUIKitThreadQuote)];
|
||||
[self.titleLabel sizeToFit];
|
||||
__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];
|
||||
for (TUIMessageCellData *data in strongSelf.uiMsgs) {
|
||||
[TUIMessageDataProvider updateUIMsgStatus:data uiMsgs:strongSelf.uiMsgs];
|
||||
}
|
||||
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 = [UIColor colorWithRed:0 green:0 blue:0 alpha:.5];
|
||||
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_Minimalist alloc] init];
|
||||
_inputController.delegate = self;
|
||||
_inputController.view.frame = CGRectMake(0, self.containerView.frame.size.height - TTextView_Height - Bottom_SafeHeight,
|
||||
self.containerView.frame.size.width, TTextView_Height + Bottom_SafeHeight);
|
||||
_inputController.view.autoresizingMask = UIViewAutoresizingFlexibleTopMargin;
|
||||
[self addChildViewController:_inputController];
|
||||
[self.containerView 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 ]];
|
||||
|
||||
CGFloat margin = 20;
|
||||
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 removeFromSuperview];
|
||||
}
|
||||
if (_inputController.inputBar.moreButton) {
|
||||
[_inputController.inputBar.moreButton removeFromSuperview];
|
||||
}
|
||||
if (_inputController.inputBar.cameraButton) {
|
||||
[_inputController.inputBar.cameraButton removeFromSuperview];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)updateRootMsg {
|
||||
self.originCellLayout = self.cellData.cellLayout;
|
||||
self.direction = self.cellData.direction;
|
||||
|
||||
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.showMessageModifyReplies = NO;
|
||||
}
|
||||
- (void)revertRootMsg {
|
||||
self.cellData.cellLayout = self.originCellLayout;
|
||||
self.cellData.direction = self.direction;
|
||||
self.cellData.showMessageModifyReplies = YES;
|
||||
}
|
||||
|
||||
- (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.direction = MsgDirectionIncoming;
|
||||
data.cellLayout = layout;
|
||||
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 = CGRectMake(0, self.topGestureView.frame.size.height, self.containerView.frame.size.width,
|
||||
self.containerView.frame.size.height - self.topGestureView.frame.size.height);
|
||||
|
||||
_tableView = [[UITableView alloc] initWithFrame:rect style:UITableViewStylePlain];
|
||||
if (@available(iOS 15.0, *)) {
|
||||
_tableView.sectionHeaderTopPadding = 0;
|
||||
}
|
||||
[self.containerView 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.showCheckBox = NO;
|
||||
TUIMessageCell *cell = nil;
|
||||
|
||||
cell = [tableView dequeueReusableCellWithIdentifier:data.reuseId forIndexPath:indexPath];
|
||||
[cell fillWithData:_uiMsgs[indexPath.row]];
|
||||
[cell notifyBottomContainerReadyOfData:nil];
|
||||
cell.delegate = self;
|
||||
return cell;
|
||||
}
|
||||
|
||||
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView {
|
||||
[self.inputController reset];
|
||||
}
|
||||
|
||||
#pragma mark - TUIInputControllerDelegate
|
||||
|
||||
- (void)inputController:(TUIInputController_Minimalist *)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_Minimalist *)inputController didSendMessage:(V2TIMMessage *)msg {
|
||||
[self sendMessage:msg];
|
||||
}
|
||||
|
||||
- (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)onCancel:(id)btn {
|
||||
[self dismissViewControllerAnimated:YES completion:nil];
|
||||
}
|
||||
|
||||
- (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_Minimalist class]]) {
|
||||
[self showImageMessage:(TUIImageMessageCell_Minimalist *)cell];
|
||||
}
|
||||
if ([cell isKindOfClass:[TUIVoiceMessageCell_Minimalist class]]) {
|
||||
[self playVoiceMessage:(TUIVoiceMessageCell_Minimalist *)cell];
|
||||
}
|
||||
if ([cell isKindOfClass:[TUIVideoMessageCell_Minimalist class]]) {
|
||||
[self showVideoMessage:(TUIVideoMessageCell_Minimalist *)cell];
|
||||
}
|
||||
if ([cell isKindOfClass:[TUIFileMessageCell_Minimalist class]]) {
|
||||
[self showFileMessage:(TUIFileMessageCell_Minimalist *)cell];
|
||||
}
|
||||
if ([cell isKindOfClass:[TUIMergeMessageCell_Minimalist class]]) {
|
||||
TUIMergeMessageListController_Minimalist *mergeVc = [[TUIMergeMessageListController_Minimalist alloc] init];
|
||||
mergeVc.mergerElem = [(TUIMergeMessageCell_Minimalist *)cell mergeData].mergerElem;
|
||||
mergeVc.delegate = self.delegate;
|
||||
UINavigationController *nav = [[UINavigationController alloc] initWithRootViewController:mergeVc];
|
||||
nav.modalPresentationStyle = UIModalPresentationFullScreen;
|
||||
[self presentViewController:nav animated:NO completion:nil];
|
||||
}
|
||||
if ([cell isKindOfClass:[TUILinkCell_Minimalist class]]) {
|
||||
[self showLinkMessage:(TUILinkCell_Minimalist *)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:(TUIMessageBaseDataProvider *)dataProvider {
|
||||
}
|
||||
- (void)dataProviderDataSourceChange:(TUIMessageBaseDataProvider *)dataProvider
|
||||
withType:(TUIMessageBaseDataProviderDataSourceChangeType)type
|
||||
atIndex:(NSUInteger)index
|
||||
animation:(BOOL)animation {
|
||||
}
|
||||
- (void)dataProviderDataSourceDidChange:(TUIMessageBaseDataProvider *)dataProvider {
|
||||
// update by applyData
|
||||
}
|
||||
|
||||
|
||||
- (void)dataProvider:(TUIMessageBaseDataProvider *)dataProvider onRemoveHeightCache:(TUIMessageCellData *)cellData {
|
||||
if (cellData) {
|
||||
[self.messageCellConfig removeHeightCacheOfMessageCellData:cellData];
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - action
|
||||
|
||||
- (void)showImageMessage:(TUIImageMessageCell_Minimalist *)cell {
|
||||
CGRect frame = [cell.thumb convertRect:cell.thumb.bounds toView:[UIApplication sharedApplication].delegate.window];
|
||||
TUIMediaView_Minimalist *mediaView = [[TUIMediaView_Minimalist 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_Minimalist *)cell {
|
||||
TUIVoiceMessageCellData *uiMsg = (TUIVoiceMessageCellData *)self.cellData;
|
||||
if (uiMsg == cell.voiceData) {
|
||||
[uiMsg playVoiceMessage];
|
||||
cell.voiceReadPoint.hidden = YES;
|
||||
} else {
|
||||
[uiMsg stopVoiceMessage];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)showVideoMessage:(TUIVideoMessageCell_Minimalist *)cell {
|
||||
CGRect frame = [cell.thumb convertRect:cell.thumb.bounds toView:[UIApplication sharedApplication].delegate.window];
|
||||
TUIMediaView_Minimalist *mediaView = [[TUIMediaView_Minimalist 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_Minimalist *)cell {
|
||||
TUIFileMessageCellData *fileData = cell.fileData;
|
||||
if (![fileData isLocalExist]) {
|
||||
[fileData downloadFile];
|
||||
return;
|
||||
}
|
||||
|
||||
TUIFileViewController_Minimalist *file = [[TUIFileViewController_Minimalist alloc] init];
|
||||
file.data = [cell fileData];
|
||||
file.dismissClickCallback = ^{
|
||||
[self dismissViewControllerAnimated:YES completion:nil];
|
||||
};
|
||||
|
||||
UINavigationController *nav = [[UINavigationController alloc] initWithRootViewController:file];
|
||||
|
||||
[self presentViewController:nav animated:NO completion:nil];
|
||||
}
|
||||
|
||||
- (void)showLinkMessage:(TUILinkCell_Minimalist *)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_Minimalist *)messageCellConfig {
|
||||
if (_messageCellConfig == nil) {
|
||||
_messageCellConfig = [[TUIMessageCellConfig_Minimalist alloc] init];
|
||||
}
|
||||
return _messageCellConfig;
|
||||
}
|
||||
|
||||
@end
|
||||
343
TUIKit/TUIChat/UI_Minimalist/Config/TUIChatConfig_Minimalist.h
Normal file
343
TUIKit/TUIChat/UI_Minimalist/Config/TUIChatConfig_Minimalist.h
Normal file
@@ -0,0 +1,343 @@
|
||||
//
|
||||
// TUIChatConfig_Minimalist.h
|
||||
// TUIChat
|
||||
//
|
||||
// Created by Tencent on 2024/7/16.
|
||||
// Copyright © 2024 Tencent. All rights reserved.
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "UIAlertController+TUICustomStyle.h"
|
||||
#import "TUIChatShortcutMenuView.h"
|
||||
#import <TIMCommon/TIMCommonModel.h>
|
||||
#import <TIMCommon/TUIMessageCellLayout.h>
|
||||
#import "TUIChatConfig.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@class TUIMessageCellData;
|
||||
|
||||
typedef NS_ENUM(NSInteger, TUIAvatarStyle_Minimalist) {
|
||||
TUIAvatarStyleRectangle_Minimalist,
|
||||
TUIAvatarStyleCircle_Minimalist,
|
||||
TUIAvatarStyleRoundedRectangle_Minimalist,
|
||||
};
|
||||
typedef NS_OPTIONS(NSInteger, TUIChatItemWhenLongPressMessage_Minimalist) {
|
||||
TUIChatItemWhenLongPressMessage_Minimalist_None = 0,
|
||||
TUIChatItemWhenLongPressMessage_Minimalist_Reply = 1 << 0,
|
||||
TUIChatItemWhenLongPressMessage_Minimalist_EmojiReaction = 1 << 1,
|
||||
TUIChatItemWhenLongPressMessage_Minimalist_Quote = 1 << 2,
|
||||
TUIChatItemWhenLongPressMessage_Minimalist_Pin = 1 << 3,
|
||||
TUIChatItemWhenLongPressMessage_Minimalist_Recall = 1 << 4,
|
||||
TUIChatItemWhenLongPressMessage_Minimalist_Translate = 1 << 5,
|
||||
TUIChatItemWhenLongPressMessage_Minimalist_Convert = 1 << 6,
|
||||
TUIChatItemWhenLongPressMessage_Minimalist_Forward = 1 << 7,
|
||||
TUIChatItemWhenLongPressMessage_Minimalist_Select = 1 << 8,
|
||||
TUIChatItemWhenLongPressMessage_Minimalist_Copy = 1 << 9,
|
||||
TUIChatItemWhenLongPressMessage_Minimalist_Delete = 1 << 10,
|
||||
TUIChatItemWhenLongPressMessage_Minimalist_Info = 1 << 11,
|
||||
};
|
||||
@protocol TUIChatConfigDelegate_Minimalist <NSObject>
|
||||
/**
|
||||
* Tells the delegate a user's avatar in the chat list is clicked.
|
||||
* Returning YES indicates this event has been intercepted, and Chat will not process it further.
|
||||
* Returning NO indicates this event is not intercepted, and Chat will continue to process it.
|
||||
*/
|
||||
- (BOOL)onUserAvatarClicked:(UIView *)view messageCellData:(TUIMessageCellData *)celldata;
|
||||
/**
|
||||
* Tells the delegate a user's avatar in the chat list is long pressed.
|
||||
* Returning YES indicates that this event has been intercepted, and Chat will not process it further.
|
||||
* Returning NO indicates that this event is not intercepted, and Chat will continue to process it.
|
||||
*/
|
||||
- (BOOL)onUserAvatarLongPressed:(UIView *)view messageCellData:(TUIMessageCellData *)celldata;
|
||||
/**
|
||||
* Tells the delegate a message in the chat list is clicked.
|
||||
* Returning YES indicates that this event has been intercepted, and Chat will not process it further.
|
||||
* Returning NO indicates that this event is not intercepted, and Chat will continue to process it.
|
||||
*/
|
||||
- (BOOL)onMessageClicked:(UIView *)view messageCellData:(TUIMessageCellData *)celldata;
|
||||
/**
|
||||
* Tells the delegate a message in the chat list is long pressed.
|
||||
* Returning YES indicates that this event has been intercepted, and Chat will not process it further.
|
||||
* Returning NO indicates that this event is not intercepted, and Chat will continue to process it.
|
||||
*/
|
||||
- (BOOL)onMessageLongPressed:(UIView *)view messageCellData:(TUIMessageCellData *)celldata;
|
||||
@end
|
||||
|
||||
|
||||
@interface TUIChatConfig_Minimalist : NSObject
|
||||
+ (TUIChatConfig_Minimalist *)sharedConfig;
|
||||
/**
|
||||
* The object that acts as the delegate of the TUIChatMessageConfig_Minimalist.
|
||||
*/
|
||||
@property (nonatomic, weak) id<TUIChatConfigDelegate_Minimalist> delegate;
|
||||
/**
|
||||
* Customize the backgroud color of message list interface.
|
||||
* This configuration takes effect in all message list interfaces.
|
||||
*/
|
||||
@property (nonatomic, strong) UIColor *backgroudColor;
|
||||
/**
|
||||
* Customize the backgroud image of message list interface.
|
||||
* This configuration takes effect in all message list interfaces.
|
||||
*/
|
||||
@property (nonatomic, strong) UIImage *backgroudImage;
|
||||
/**
|
||||
* Customize the style of avatar.
|
||||
* The default value is TUIAvatarStyleCircle.
|
||||
* This configuration takes effect in all avatars.
|
||||
*/
|
||||
@property (nonatomic, assign) TUIAvatarStyle_Minimalist avatarStyle;
|
||||
/**
|
||||
* Customize the corner radius of the avatar.
|
||||
* This configuration takes effect in all avatars.
|
||||
*/
|
||||
@property (nonatomic, assign) CGFloat avatarCornerRadius;
|
||||
/**
|
||||
* Display the group avatar in the nine-square grid style.
|
||||
* The default value is YES.
|
||||
* This configuration takes effect in all groups.
|
||||
*/
|
||||
@property (nonatomic, assign) BOOL enableGroupGridAvatar;
|
||||
/**
|
||||
* Default avatar image.
|
||||
* This configuration takes effect in all avatars.
|
||||
*/
|
||||
@property (nonatomic, strong) UIImage *defaultAvatarImage;
|
||||
/**
|
||||
* Enable the display "Alice is typing..." on one-to-one chat interface.
|
||||
* The default value is YES.
|
||||
* This configuration takes effect in all one-to-one chat message list interfaces.
|
||||
*/
|
||||
@property (nonatomic, assign) BOOL enableTypingIndicator;
|
||||
/**
|
||||
* When sending a message, set this flag to require message read receipt.
|
||||
* The default value is NO.
|
||||
* This configuration takes effect in all chat message list interfaces.
|
||||
*/
|
||||
@property (nonatomic, assign) BOOL isMessageReadReceiptNeeded;
|
||||
/**
|
||||
* Hide the "Video Call" button in the message list header.
|
||||
* The default value is NO.
|
||||
*/
|
||||
@property (nonatomic, assign) BOOL hideVideoCallButton;
|
||||
/**
|
||||
* Hide the "Audio Call" button in the message list header.
|
||||
* The default value is NO.
|
||||
*/
|
||||
@property (nonatomic, assign) BOOL hideAudioCallButton;
|
||||
/**
|
||||
* Turn on audio and video call floating windows,
|
||||
* The default value is YES.
|
||||
*/
|
||||
@property (nonatomic, assign) BOOL enableFloatWindowForCall;
|
||||
/**
|
||||
* Enable multi-terminal login function for audio and video calls
|
||||
* The default value is NO.
|
||||
*/
|
||||
@property (nonatomic, assign) BOOL enableMultiDeviceForCall;
|
||||
/**
|
||||
* Set this parameter when the sender sends a message, and the receiver will not update the unread count after receiving the message.
|
||||
* The default value is NO.
|
||||
*/
|
||||
@property (nonatomic, assign) BOOL isExcludedFromUnreadCount;
|
||||
/**
|
||||
* Set this parameter when the sender sends a message, and the receiver will not update the last message of the conversation after receiving the message.
|
||||
* The default value is NO.
|
||||
*/
|
||||
@property (nonatomic, assign) BOOL isExcludedFromLastMessage;
|
||||
/**
|
||||
* Time interval within which a message can be recalled after being sent.
|
||||
* The default value is 120 seconds.
|
||||
* If you want to adjust this configuration, please modify the setting on Chat Console synchronously: https://trtc.io/document/34419?platform=web&product=chat&menulabel=uikit#message-recall-settings
|
||||
*/
|
||||
@property (nonatomic, assign) NSUInteger timeIntervalForAllowedMessageRecall;
|
||||
/**
|
||||
* Maximum audio recording duration, no more than 60s.
|
||||
* The default value is 60 seconds.
|
||||
*/
|
||||
@property (nonatomic, assign) CGFloat maxAudioRecordDuration;
|
||||
/**
|
||||
* Maximum video recording duration, no more than 15s.
|
||||
* The default value is 15 seconds.
|
||||
*/
|
||||
@property (nonatomic, assign) CGFloat maxVideoRecordDuration;
|
||||
/**
|
||||
* Enable custom ringtone.
|
||||
* This config takes effect only for Android devices.
|
||||
*/
|
||||
@property (nonatomic, assign) BOOL enableAndroidCustomRing;
|
||||
/**
|
||||
* Hide the items in the pop-up menu when user presses the message.
|
||||
*/
|
||||
+ (void)hideItemsWhenLongPressMessage:(TUIChatItemWhenLongPressMessage_Minimalist)items;
|
||||
/**
|
||||
* Call this method to use speakers instead of handsets by default when playing voice messages.
|
||||
*/
|
||||
+ (void)setPlayingSoundMessageViaSpeakerByDefault;
|
||||
/**
|
||||
* Add a custom view at the top of the chat interface.
|
||||
* This view will be displayed at the top of the message list and will not slide up.
|
||||
*/
|
||||
+ (void)setCustomTopView:(UIView *)view;
|
||||
/**
|
||||
* Register custom message.
|
||||
* - Parameters:
|
||||
* - businessID: Customized message‘s businessID, which is unique.
|
||||
* - cellName: Customized message's MessagCell class name.
|
||||
* - cellDataName: Customized message's MessagCellData class name.
|
||||
*/
|
||||
- (void)registerCustomMessage:(NSString *)businessID
|
||||
messageCellClassName:(NSString *)cellName
|
||||
messageCellDataClassName:(NSString *)cellDataName;
|
||||
@end
|
||||
|
||||
|
||||
@interface TUIChatConfig_Minimalist (MessageStyle)
|
||||
/**
|
||||
* The color of send text message.
|
||||
*/
|
||||
@property(nonatomic, assign) UIColor *sendTextMessageColor;
|
||||
/**
|
||||
* The font of send text message.
|
||||
*/
|
||||
@property(nonatomic, assign) UIFont *sendTextMessageFont;
|
||||
/**
|
||||
* The color of receive text message.
|
||||
*/
|
||||
@property(nonatomic, assign) UIColor *receiveTextMessageColor;
|
||||
/**
|
||||
* The font of receive text message.
|
||||
*/
|
||||
@property(nonatomic, assign) UIFont *receiveTextMessageFont;
|
||||
/**
|
||||
* The text color of system message.
|
||||
*/
|
||||
@property (nonatomic, strong) UIColor *systemMessageTextColor;
|
||||
/**
|
||||
* The font of system message.
|
||||
*/
|
||||
@property (nonatomic, strong) UIFont *systemMessageTextFont;
|
||||
/**
|
||||
* The background color of system message.
|
||||
*/
|
||||
@property (nonatomic, strong) UIColor *systemMessageBackgroundColor;
|
||||
@end
|
||||
|
||||
|
||||
@interface TUIChatConfig_Minimalist (MessageLayout)
|
||||
/**
|
||||
* Text message cell layout of my sent message.
|
||||
*/
|
||||
@property(nonatomic, assign, readonly) TUIMessageCellLayout *sendTextMessageLayout;
|
||||
/**
|
||||
* Text message cell layout of my received message.
|
||||
*/
|
||||
@property(nonatomic, assign, readonly) TUIMessageCellLayout *receiveTextMessageLayout;
|
||||
/**
|
||||
* Image message cell layout of my sent message.
|
||||
*/
|
||||
@property(nonatomic, assign, readonly) TUIMessageCellLayout *sendImageMessageLayout;
|
||||
/**
|
||||
* Image message cell layout of my received message.
|
||||
*/
|
||||
@property(nonatomic, assign, readonly) TUIMessageCellLayout *receiveImageMessageLayout;
|
||||
/**
|
||||
* Voice message cell layout of my sent message.
|
||||
*/
|
||||
@property(nonatomic, assign, readonly) TUIMessageCellLayout *sendVoiceMessageLayout;
|
||||
/**
|
||||
* Voice message cell layout of my received message.
|
||||
*/
|
||||
@property(nonatomic, assign, readonly) TUIMessageCellLayout *receiveVoiceMessageLayout;
|
||||
/**
|
||||
* Video message cell layout of my sent message.
|
||||
*/
|
||||
@property(nonatomic, assign, readonly) TUIMessageCellLayout *sendVideoMessageLayout;
|
||||
/**
|
||||
* Video message cell layout of my received message.
|
||||
*/
|
||||
@property(nonatomic, assign, readonly) TUIMessageCellLayout *receiveVideoMessageLayout;
|
||||
/**
|
||||
* Other message cell layout of my sent message.
|
||||
*/
|
||||
@property(nonatomic, assign, readonly) TUIMessageCellLayout *sendMessageLayout;
|
||||
/**
|
||||
* Other message cell layout of my received message.
|
||||
*/
|
||||
@property(nonatomic, assign, readonly) TUIMessageCellLayout *receiveMessageLayout;
|
||||
/**
|
||||
* System message cell layout.
|
||||
*/
|
||||
@property(nonatomic, assign, readonly) TUIMessageCellLayout *systemMessageLayout;
|
||||
@end
|
||||
|
||||
|
||||
@interface TUIChatConfig_Minimalist (MessageBubble)
|
||||
/**
|
||||
* Enable the message display in the bubble style.
|
||||
* The default value is YES.
|
||||
*/
|
||||
@property(nonatomic, assign) BOOL enableMessageBubbleStyle;
|
||||
/**
|
||||
* Set the background image of the last sent message bubble in consecutive messages.
|
||||
*/
|
||||
@property (nonatomic, strong) UIImage *sendLastBubbleBackgroundImage;
|
||||
/**
|
||||
* Set the background image of the non-last sent message bubble in consecutive message.
|
||||
*/
|
||||
@property (nonatomic, strong) UIImage *sendBubbleBackgroundImage;
|
||||
/**
|
||||
* Set the background image of the sent message bubble in highlight status.
|
||||
*/
|
||||
@property (nonatomic, strong) UIImage *sendHighlightBubbleBackgroundImage;
|
||||
/**
|
||||
* Set the light background image when the sent message bubble needs to flicker.
|
||||
*/
|
||||
@property (nonatomic, strong) UIImage *sendAnimateLightBubbleBackgroundImage;
|
||||
/**
|
||||
* Set the dark background image when the sent message bubble needs to flicker.
|
||||
*/
|
||||
@property (nonatomic, strong) UIImage *sendAnimateDarkBubbleBackgroundImage;
|
||||
/**
|
||||
* Set the background image of the last received message bubble in consecutive message.
|
||||
*/
|
||||
@property (nonatomic, strong) UIImage *receiveLastBubbleBackgroundImage;
|
||||
/**
|
||||
* Set the background image of the non-last received message bubble in consecutive message.
|
||||
*/
|
||||
@property (nonatomic, strong) UIImage *receiveBubbleBackgroundImage;
|
||||
/**
|
||||
* Set the background image of the received message bubble in highlight status.
|
||||
*/
|
||||
@property (nonatomic, strong) UIImage *receiveHighlightBubbleBackgroundImage;
|
||||
/**
|
||||
* Set the light background image when the received message bubble needs to flicker.
|
||||
*/
|
||||
@property (nonatomic, strong) UIImage *receiveAnimateLightBubbleBackgroundImage;
|
||||
/**
|
||||
* Set the dark background image when the received message bubble needs to flicker.
|
||||
*/
|
||||
@property (nonatomic, strong) UIImage *receiveAnimateDarkBubbleBackgroundImage;
|
||||
@end
|
||||
|
||||
@interface TUIChatConfig_Minimalist (InputBar)
|
||||
/**
|
||||
* DataSource for inputBar.
|
||||
*/
|
||||
@property (nonatomic, weak) id<TUIChatInputBarConfigDataSource> inputBarDataSource;
|
||||
/**
|
||||
* Show the input bar in the message list interface.
|
||||
* The default value is YES.
|
||||
*/
|
||||
@property(nonatomic, assign) BOOL showInputBar;
|
||||
/**
|
||||
* Hide items in more menu.
|
||||
*/
|
||||
+ (void)hideItemsInMoreMenu:(TUIChatInputBarMoreMenuItem)items;
|
||||
/**
|
||||
* Add sticker group.
|
||||
*/
|
||||
- (void)addStickerGroup:(TUIFaceGroup *)group;
|
||||
@end
|
||||
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
564
TUIKit/TUIChat/UI_Minimalist/Config/TUIChatConfig_Minimalist.m
Normal file
564
TUIKit/TUIChat/UI_Minimalist/Config/TUIChatConfig_Minimalist.m
Normal file
@@ -0,0 +1,564 @@
|
||||
//
|
||||
// TUIChatConfig_Minimalist.m
|
||||
// TUIChat
|
||||
//
|
||||
// Created by Tencent on 2024/7/16.
|
||||
// Copyright © 2024 Tencent. All rights reserved.
|
||||
|
||||
#import "TUIChatConfig_Minimalist.h"
|
||||
|
||||
#import <TUICore/TUIConfig.h>
|
||||
#import <TIMCommon/TUIMessageCellLayout.h>
|
||||
#import <TIMCommon/TUIMessageCell.h>
|
||||
#import <TIMCommon/TIMConfig.h>
|
||||
#import <TIMCommon/TUIBubbleMessageCell_Minimalist.h>
|
||||
#import <TIMCommon/TUISystemMessageCellData.h>
|
||||
#import <TIMCommon/TIMCommonMediator.h>
|
||||
#import <TIMCommon/TUIEmojiMeditorProtocol.h>
|
||||
#import "TUIBaseChatViewController_Minimalist.h"
|
||||
#import "TUITextMessageCell_Minimalist.h"
|
||||
#import "TUIEmojiConfig.h"
|
||||
#import "TUIChatConversationModel.h"
|
||||
#import "TUIVoiceMessageCellData.h"
|
||||
|
||||
@interface TUIChatConfig_Minimalist()<TUIChatEventListener>
|
||||
|
||||
@end
|
||||
|
||||
@implementation TUIChatConfig_Minimalist
|
||||
|
||||
+ (TUIChatConfig_Minimalist *)sharedConfig {
|
||||
static dispatch_once_t onceToken;
|
||||
static TUIChatConfig_Minimalist *config;
|
||||
dispatch_once(&onceToken, ^{
|
||||
config = [[TUIChatConfig_Minimalist alloc] init];
|
||||
});
|
||||
return config;
|
||||
}
|
||||
|
||||
- (instancetype)init {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
TUIChatConfig.defaultConfig.eventConfig.chatEventListener = self;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)setEnableTypingIndicator:(BOOL)enable {
|
||||
[TUIChatConfig defaultConfig].enableTypingStatus = enable;
|
||||
}
|
||||
|
||||
- (BOOL)enableTypingIndicator {
|
||||
return [TUIChatConfig defaultConfig].enableTypingStatus;
|
||||
}
|
||||
|
||||
- (void)setBackgroudColor:(UIColor *)backgroudColor {
|
||||
[TUIChatConfig defaultConfig].backgroudColor = backgroudColor;
|
||||
}
|
||||
|
||||
- (UIColor *)backgroudColor {
|
||||
return [TUIChatConfig defaultConfig].backgroudColor;
|
||||
}
|
||||
|
||||
- (void)setBackgroudImage:(UIImage *)backgroudImage {
|
||||
[TUIChatConfig defaultConfig].backgroudImage = backgroudImage;
|
||||
}
|
||||
|
||||
- (UIImage *)backgroudImage {
|
||||
return [TUIChatConfig defaultConfig].backgroudImage;
|
||||
}
|
||||
|
||||
- (void)setAvatarStyle:(TUIAvatarStyle_Minimalist)avatarStyle {
|
||||
[TUIConfig defaultConfig].avatarType = (TUIKitAvatarType)avatarStyle;
|
||||
}
|
||||
|
||||
- (TUIAvatarStyle_Minimalist)avatarStyle {
|
||||
return (TUIAvatarStyle_Minimalist)[TUIConfig defaultConfig].avatarType;
|
||||
}
|
||||
|
||||
- (void)setAvatarCornerRadius:(CGFloat)avatarCornerRadius {
|
||||
[TUIConfig defaultConfig].avatarCornerRadius = avatarCornerRadius;
|
||||
}
|
||||
|
||||
- (CGFloat)avatarCornerRadius {
|
||||
return [TUIConfig defaultConfig].avatarCornerRadius;
|
||||
}
|
||||
|
||||
- (void)setDefaultAvatarImage:(UIImage *)defaultAvatarImage {
|
||||
[TUIConfig defaultConfig].defaultAvatarImage = defaultAvatarImage;
|
||||
}
|
||||
|
||||
- (UIImage *)defaultAvatarImage {
|
||||
return [TUIConfig defaultConfig].defaultAvatarImage;
|
||||
}
|
||||
|
||||
- (void)setEnableGroupGridAvatar:(BOOL)enableGroupGridAvatar {
|
||||
[TUIConfig defaultConfig].enableGroupGridAvatar = enableGroupGridAvatar;
|
||||
}
|
||||
|
||||
- (BOOL)enableGroupGridAvatar {
|
||||
return [TUIConfig defaultConfig].enableGroupGridAvatar;
|
||||
}
|
||||
|
||||
- (void)setIsMessageReadReceiptNeeded:(BOOL)isMessageReadReceiptNeeded {
|
||||
[TUIChatConfig defaultConfig].msgNeedReadReceipt = isMessageReadReceiptNeeded;
|
||||
}
|
||||
|
||||
- (BOOL)isMessageReadReceiptNeeded {
|
||||
return [TUIChatConfig defaultConfig].msgNeedReadReceipt;
|
||||
}
|
||||
|
||||
- (void)setTimeIntervalForAllowedMessageRecall:(NSUInteger)timeIntervalForAllowedMessageRecall {
|
||||
[TUIChatConfig defaultConfig].timeIntervalForMessageRecall = timeIntervalForAllowedMessageRecall;
|
||||
}
|
||||
|
||||
- (NSUInteger)timeIntervalForAllowedMessageRecall {
|
||||
return [TUIChatConfig defaultConfig].timeIntervalForMessageRecall;
|
||||
}
|
||||
|
||||
- (void)setEnableFloatWindowForCall:(BOOL)enableFloatWindowForCall {
|
||||
[TUIChatConfig defaultConfig].enableFloatWindowForCall = enableFloatWindowForCall;
|
||||
}
|
||||
|
||||
- (BOOL)enableFloatWindowForCall {
|
||||
return [TUIChatConfig defaultConfig].enableFloatWindowForCall;
|
||||
}
|
||||
|
||||
- (void)setEnableMultiDeviceForCall:(BOOL)enableMultiDeviceForCall {
|
||||
[TUIChatConfig defaultConfig].enableMultiDeviceForCall = enableMultiDeviceForCall;
|
||||
}
|
||||
|
||||
- (BOOL)enableMultiDeviceForCall {
|
||||
return [TUIChatConfig defaultConfig].enableMultiDeviceForCall;
|
||||
}
|
||||
|
||||
- (void)setHideVideoCallButton:(BOOL)hideVideoCallButton {
|
||||
[TUIChatConfig defaultConfig].enableVideoCall = !hideVideoCallButton;
|
||||
}
|
||||
|
||||
- (void)setEnableAndroidCustomRing:(BOOL)enableAndroidCustomRing {
|
||||
[TUIConfig defaultConfig].enableCustomRing = enableAndroidCustomRing;
|
||||
}
|
||||
|
||||
- (BOOL)enableAndroidCustomRing {
|
||||
return [TUIConfig defaultConfig].enableCustomRing;
|
||||
}
|
||||
|
||||
- (BOOL)hideVideoCallButton {
|
||||
return ![TUIChatConfig defaultConfig].enableVideoCall;
|
||||
}
|
||||
|
||||
- (void)setHideAudioCallButton:(BOOL)hideAudioCallButton {
|
||||
[TUIChatConfig defaultConfig].enableAudioCall = !hideAudioCallButton;
|
||||
}
|
||||
|
||||
- (BOOL)hideAudioCallButton {
|
||||
return ![TUIChatConfig defaultConfig].enableAudioCall;
|
||||
}
|
||||
|
||||
+ (void)hideItemsWhenLongPressMessage:(TUIChatItemWhenLongPressMessage_Minimalist)items {
|
||||
[TUIChatConfig defaultConfig].enablePopMenuReplyAction = !(items & TUIChatItemWhenLongPressMessage_Minimalist_Reply);
|
||||
[TUIChatConfig defaultConfig].enablePopMenuEmojiReactAction = !(items & TUIChatItemWhenLongPressMessage_Minimalist_EmojiReaction);
|
||||
[TUIChatConfig defaultConfig].enablePopMenuReferenceAction = !(items & TUIChatItemWhenLongPressMessage_Minimalist_Quote);
|
||||
[TUIChatConfig defaultConfig].enablePopMenuPinAction = !(items & TUIChatItemWhenLongPressMessage_Minimalist_Pin);
|
||||
[TUIChatConfig defaultConfig].enablePopMenuRecallAction = !(items & TUIChatItemWhenLongPressMessage_Minimalist_Recall);
|
||||
[TUIChatConfig defaultConfig].enablePopMenuTranslateAction = !(items & TUIChatItemWhenLongPressMessage_Minimalist_Translate);
|
||||
[TUIChatConfig defaultConfig].enablePopMenuConvertAction = !(items & TUIChatItemWhenLongPressMessage_Minimalist_Convert);
|
||||
[TUIChatConfig defaultConfig].enablePopMenuForwardAction = !(items & TUIChatItemWhenLongPressMessage_Minimalist_Forward);
|
||||
[TUIChatConfig defaultConfig].enablePopMenuSelectAction = !(items & TUIChatItemWhenLongPressMessage_Minimalist_Select);
|
||||
[TUIChatConfig defaultConfig].enablePopMenuCopyAction = !(items & TUIChatItemWhenLongPressMessage_Minimalist_Copy);
|
||||
[TUIChatConfig defaultConfig].enablePopMenuDeleteAction = !(items & TUIChatItemWhenLongPressMessage_Minimalist_Delete);
|
||||
[TUIChatConfig defaultConfig].enablePopMenuInfoAction = !(items & TUIChatItemWhenLongPressMessage_Minimalist_Info);
|
||||
}
|
||||
|
||||
- (void)setIsExcludedFromUnreadCount:(BOOL)isExcludedFromUnreadCount {
|
||||
[TUIConfig defaultConfig].isExcludedFromUnreadCount = isExcludedFromUnreadCount;
|
||||
}
|
||||
|
||||
- (BOOL)isExcludedFromUnreadCount {
|
||||
return [TUIConfig defaultConfig].isExcludedFromUnreadCount;
|
||||
}
|
||||
|
||||
- (void)setIsExcludedFromLastMessage:(BOOL)isExcludedFromLastMessage {
|
||||
[TUIConfig defaultConfig].isExcludedFromLastMessage = isExcludedFromLastMessage;
|
||||
}
|
||||
|
||||
- (BOOL)isExcludedFromLastMessage {
|
||||
return [TUIConfig defaultConfig].isExcludedFromLastMessage;
|
||||
}
|
||||
|
||||
- (void)setMaxAudioRecordDuration:(CGFloat)maxAudioRecordDuration {
|
||||
[TUIChatConfig defaultConfig].maxAudioRecordDuration = maxAudioRecordDuration;
|
||||
}
|
||||
|
||||
- (CGFloat)maxAudioRecordDuration {
|
||||
return [TUIChatConfig defaultConfig].maxAudioRecordDuration;
|
||||
}
|
||||
|
||||
- (void)setMaxVideoRecordDuration:(CGFloat)maxVideoRecordDuration {
|
||||
[TUIChatConfig defaultConfig].maxVideoRecordDuration = maxVideoRecordDuration;
|
||||
}
|
||||
|
||||
- (CGFloat)maxVideoRecordDuration {
|
||||
return [TUIChatConfig defaultConfig].maxVideoRecordDuration;
|
||||
}
|
||||
|
||||
+ (void)setPlayingSoundMessageViaSpeakerByDefault {
|
||||
if ([TUIVoiceMessageCellData getAudioplaybackStyle] == TUIVoiceAudioPlaybackStyleHandset) {
|
||||
[TUIVoiceMessageCellData changeAudioPlaybackStyle];
|
||||
}
|
||||
}
|
||||
|
||||
+ (void)setCustomTopView:(UIView *)view {
|
||||
[TUIBaseChatViewController_Minimalist setCustomTopView:view];
|
||||
}
|
||||
|
||||
- (void)registerCustomMessage:(NSString *)businessID
|
||||
messageCellClassName:(NSString *)cellName
|
||||
messageCellDataClassName:(NSString *)cellDataName {
|
||||
[[TUIChatConfig defaultConfig] registerCustomMessage:businessID
|
||||
messageCellClassName:cellName
|
||||
messageCellDataClassName:cellDataName
|
||||
styleType:TUIChatRegisterCustomMessageStyleTypeMinimalist];
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - TUIChatEventListener
|
||||
- (BOOL)onUserIconClicked:(UIView *)view messageCellData:(TUIMessageCellData *)celldata {
|
||||
if ([self.delegate respondsToSelector:@selector(onUserAvatarClicked:messageCellData:)]) {
|
||||
return [self.delegate onUserAvatarClicked:view messageCellData:celldata];
|
||||
}
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (BOOL)onUserIconLongClicked:(UIView *)view messageCellData:(TUIMessageCellData *)celldata {
|
||||
if ([self.delegate respondsToSelector:@selector(onUserAvatarLongPressed:messageCellData:)]) {
|
||||
return [self.delegate onUserAvatarLongPressed:view messageCellData:celldata];
|
||||
}
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (BOOL)onMessageClicked:(UIView *)view messageCellData:(TUIMessageCellData *)celldata {
|
||||
if ([self.delegate respondsToSelector:@selector(onMessageClicked:messageCellData:)]) {
|
||||
return [self.delegate onMessageClicked:view messageCellData:celldata];
|
||||
}
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (BOOL)onMessageLongClicked:(UIView *)view messageCellData:(TUIMessageCellData *)celldata {
|
||||
if ([self.delegate respondsToSelector:@selector(onMessageLongPressed:messageCellData:)]) {
|
||||
return [self.delegate onMessageLongPressed:view messageCellData:celldata];
|
||||
}
|
||||
return NO;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@implementation TUIChatConfig_Minimalist (MessageStyle)
|
||||
|
||||
- (void)setSendNicknameFont:(UIFont *)sendNicknameFont {
|
||||
TUIMessageCell_Minimalist.outgoingNameFont = sendNicknameFont;
|
||||
}
|
||||
|
||||
- (UIFont *)sendNicknameFont {
|
||||
return TUIMessageCell_Minimalist.outgoingNameFont;
|
||||
}
|
||||
|
||||
- (void)setReceiveNicknameFont:(UIFont *)receiveNicknameFont {
|
||||
TUIMessageCell_Minimalist.incommingNameFont = receiveNicknameFont;
|
||||
}
|
||||
|
||||
- (UIFont *)receiveNicknameFont {
|
||||
return TUIMessageCell_Minimalist.incommingNameFont;
|
||||
}
|
||||
|
||||
- (void)setSendNicknameColor:(UIColor *)sendNicknameColor {
|
||||
TUIMessageCell_Minimalist.outgoingNameColor = sendNicknameColor;
|
||||
}
|
||||
|
||||
- (UIColor *)sendNicknameColor {
|
||||
return TUIMessageCell_Minimalist.outgoingNameColor;
|
||||
}
|
||||
|
||||
- (void)setReceiveNicknameColor:(UIColor *)receiveNicknameColor {
|
||||
TUIMessageCell_Minimalist.incommingNameColor = receiveNicknameColor;
|
||||
}
|
||||
|
||||
- (UIColor *)receiveNicknameColor {
|
||||
return TUIMessageCell_Minimalist.incommingNameColor;
|
||||
}
|
||||
|
||||
- (void)setSendTextMessageFont:(UIFont *)sendTextMessageFont {
|
||||
TUITextMessageCell_Minimalist.outgoingTextFont = sendTextMessageFont;
|
||||
}
|
||||
|
||||
- (UIFont *)sendTextMessageFont {
|
||||
return TUITextMessageCell_Minimalist.outgoingTextFont;
|
||||
}
|
||||
|
||||
- (void)setReceiveTextMessageFont:(UIFont *)receiveTextMessageFont {
|
||||
TUITextMessageCell_Minimalist.incommingTextFont = receiveTextMessageFont;
|
||||
}
|
||||
|
||||
- (UIFont *)receiveTextMessageFont {
|
||||
return TUITextMessageCell_Minimalist.incommingTextFont;
|
||||
}
|
||||
|
||||
- (void)setSendTextMessageColor:(UIColor *)sendTextMessageColor {
|
||||
TUITextMessageCell_Minimalist.outgoingTextColor = sendTextMessageColor;
|
||||
}
|
||||
|
||||
- (UIColor *)sendTextMessageColor {
|
||||
return TUITextMessageCell_Minimalist.outgoingTextColor;
|
||||
}
|
||||
|
||||
- (void)setReceiveTextMessageColor:(UIColor *)receiveTextMessageColor {
|
||||
TUITextMessageCell_Minimalist.incommingTextColor = receiveTextMessageColor;
|
||||
}
|
||||
|
||||
- (UIColor *)receiveTextMessageColor {
|
||||
return TUITextMessageCell_Minimalist.incommingTextColor;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
typedef NS_ENUM(NSInteger, UIMessageCellLayoutType) {
|
||||
UIMessageCellLayoutTypeText,
|
||||
UIMessageCellLayoutTypeImage,
|
||||
UIMessageCellLayoutTypeVideo,
|
||||
UIMessageCellLayoutTypeVoice,
|
||||
UIMessageCellLayoutTypeOther,
|
||||
UIMessageCellLayoutTypeSystem
|
||||
};
|
||||
|
||||
@implementation TUIChatConfig_Minimalist (MessageLayout)
|
||||
|
||||
- (TUIMessageCellLayout *)sendTextMessageLayout {
|
||||
return [self getMessageLayoutOfType:UIMessageCellLayoutTypeText isSender:YES];
|
||||
}
|
||||
|
||||
- (TUIMessageCellLayout *)receiveTextMessageLayout {
|
||||
return [self getMessageLayoutOfType:UIMessageCellLayoutTypeText isSender:NO];
|
||||
}
|
||||
|
||||
- (TUIMessageCellLayout *)sendImageMessageLayout {
|
||||
return [self getMessageLayoutOfType:UIMessageCellLayoutTypeImage isSender:YES];
|
||||
}
|
||||
|
||||
- (TUIMessageCellLayout *)receiveImageMessageLayout {
|
||||
return [self getMessageLayoutOfType:UIMessageCellLayoutTypeImage isSender:NO];
|
||||
}
|
||||
|
||||
- (TUIMessageCellLayout *)sendVoiceMessageLayout {
|
||||
return [self getMessageLayoutOfType:UIMessageCellLayoutTypeVoice isSender:YES];
|
||||
}
|
||||
|
||||
- (TUIMessageCellLayout *)receiveVoiceMessageLayout {
|
||||
return [self getMessageLayoutOfType:UIMessageCellLayoutTypeVoice isSender:NO];
|
||||
}
|
||||
|
||||
- (TUIMessageCellLayout *)sendVideoMessageLayout {
|
||||
return [self getMessageLayoutOfType:UIMessageCellLayoutTypeVideo isSender:YES];
|
||||
}
|
||||
|
||||
- (TUIMessageCellLayout *)receiveVideoMessageLayout {
|
||||
return [self getMessageLayoutOfType:UIMessageCellLayoutTypeVideo isSender:NO];
|
||||
}
|
||||
|
||||
- (TUIMessageCellLayout *)sendMessageLayout {
|
||||
return [self getMessageLayoutOfType:UIMessageCellLayoutTypeOther isSender:YES];
|
||||
}
|
||||
|
||||
- (TUIMessageCellLayout *)receiveMessageLayout {
|
||||
return [self getMessageLayoutOfType:UIMessageCellLayoutTypeOther isSender:NO];
|
||||
}
|
||||
|
||||
- (TUIMessageCellLayout *)systemMessageLayout {
|
||||
return [self getMessageLayoutOfType:UIMessageCellLayoutTypeSystem isSender:NO];
|
||||
}
|
||||
|
||||
- (TUIMessageCellLayout *)getMessageLayoutOfType:(UIMessageCellLayoutType)type isSender:(BOOL)isSender {
|
||||
TUIMessageCellLayout *innerLayout = nil;
|
||||
switch (type) {
|
||||
case UIMessageCellLayoutTypeText: {
|
||||
innerLayout = isSender ? [TUIMessageCellLayout outgoingTextMessageLayout] : [TUIMessageCellLayout incommingTextMessageLayout];
|
||||
break;
|
||||
}
|
||||
case UIMessageCellLayoutTypeImage: {
|
||||
innerLayout = isSender ? [TUIMessageCellLayout outgoingImageMessageLayout] : [TUIMessageCellLayout incommingImageMessageLayout];
|
||||
break;
|
||||
}
|
||||
case UIMessageCellLayoutTypeVideo: {
|
||||
innerLayout = isSender ? [TUIMessageCellLayout outgoingVideoMessageLayout] : [TUIMessageCellLayout incommingVideoMessageLayout];
|
||||
break;
|
||||
}
|
||||
case UIMessageCellLayoutTypeVoice: {
|
||||
innerLayout = isSender ? [TUIMessageCellLayout outgoingVoiceMessageLayout] : [TUIMessageCellLayout incommingVoiceMessageLayout];
|
||||
break;
|
||||
}
|
||||
case UIMessageCellLayoutTypeOther: {
|
||||
innerLayout = isSender ? [TUIMessageCellLayout outgoingMessageLayout] : [TUIMessageCellLayout incommingMessageLayout];
|
||||
break;
|
||||
}
|
||||
case UIMessageCellLayoutTypeSystem: {
|
||||
innerLayout = [TUIMessageCellLayout systemMessageLayout];
|
||||
break;
|
||||
}
|
||||
}
|
||||
return innerLayout;
|
||||
}
|
||||
|
||||
- (void)setSystemMessageBackgroundColor:(UIColor *)systemMessageBackgroundColor {
|
||||
TUISystemMessageCellData.textBackgroundColor = systemMessageBackgroundColor;
|
||||
}
|
||||
|
||||
- (UIColor *)systemMessageBackgroundColor {
|
||||
return TUISystemMessageCellData.textBackgroundColor;
|
||||
}
|
||||
|
||||
- (void)setSystemMessageTextFont:(UIFont *)systemMessageTextFont {
|
||||
TUISystemMessageCellData.textFont = systemMessageTextFont;
|
||||
}
|
||||
|
||||
- (UIFont *)systemMessageTextFont {
|
||||
return TUISystemMessageCellData.textFont;
|
||||
}
|
||||
|
||||
- (void)setSystemMessageTextColor:(UIColor *)systemMessageTextColor {
|
||||
TUISystemMessageCellData.textColor = systemMessageTextColor;
|
||||
}
|
||||
|
||||
- (UIColor *)systemMessageTextColor {
|
||||
return TUISystemMessageCellData.textColor;
|
||||
}
|
||||
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@implementation TUIChatConfig_Minimalist (MessageBubble)
|
||||
|
||||
- (void)setEnableMessageBubbleStyle:(BOOL)enableMessageBubbleStyle {
|
||||
[TIMConfig defaultConfig].enableMessageBubble = enableMessageBubbleStyle;
|
||||
}
|
||||
|
||||
- (BOOL)enableMessageBubbleStyle {
|
||||
return [TIMConfig defaultConfig].enableMessageBubble;
|
||||
}
|
||||
|
||||
- (void)setSendLastBubbleBackgroundImage:(UIImage *)sendLastBubbleBackgroundImage {
|
||||
[TUIBubbleMessageCell_Minimalist setOutgoingBubble:sendLastBubbleBackgroundImage];
|
||||
}
|
||||
|
||||
- (UIImage *)sendLastBubbleBackgroundImage {
|
||||
return [TUIBubbleMessageCell_Minimalist outgoingBubble];
|
||||
}
|
||||
|
||||
- (void)setSendBubbleBackgroundImage:(UIImage *)sendBubbleBackgroundImage {
|
||||
[TUIBubbleMessageCell_Minimalist setOutgoingSameBubble:sendBubbleBackgroundImage];
|
||||
}
|
||||
|
||||
- (UIImage *)sendBubbleBackgroundImage {
|
||||
return [TUIBubbleMessageCell_Minimalist outgoingSameBubble];
|
||||
}
|
||||
|
||||
- (void)setReceiveLastBubbleBackgroundImage:(UIImage *)receiveLastBubbleBackgroundImage {
|
||||
[TUIBubbleMessageCell_Minimalist setIncommingBubble:receiveLastBubbleBackgroundImage];
|
||||
}
|
||||
|
||||
- (UIImage *)receiveLastBubbleBackgroundImage {
|
||||
return [TUIBubbleMessageCell_Minimalist incommingBubble];
|
||||
}
|
||||
|
||||
- (void)setReceiveBubbleBackgroundImage:(UIImage *)receiveBubbleBackgroundImage {
|
||||
[TUIBubbleMessageCell_Minimalist setIncommingSameBubble:receiveBubbleBackgroundImage];
|
||||
}
|
||||
|
||||
- (UIImage *)receiveBubbleBackgroundImage {
|
||||
return [TUIBubbleMessageCell_Minimalist incommingSameBubble];
|
||||
}
|
||||
|
||||
- (void)setSendHighlightBubbleBackgroundImage:(UIImage *)sendHighlightBackgroundImage {
|
||||
[TUIBubbleMessageCell_Minimalist setOutgoingHighlightedBubble:sendHighlightBackgroundImage];
|
||||
}
|
||||
|
||||
- (UIImage *)sendHighlightBubbleBackgroundImage {
|
||||
return [TUIBubbleMessageCell_Minimalist outgoingHighlightedBubble];
|
||||
}
|
||||
|
||||
- (void)setReceiveHighlightBubbleBackgroundImage:(UIImage *)receiveHighlightBubbleBackgroundImage {
|
||||
[TUIBubbleMessageCell_Minimalist setIncommingHighlightedBubble:receiveHighlightBubbleBackgroundImage];
|
||||
}
|
||||
|
||||
- (UIImage *)receiveHighlightBubbleBackgroundImage {
|
||||
return [TUIBubbleMessageCell_Minimalist incommingHighlightedBubble];
|
||||
}
|
||||
|
||||
- (void)setSendAnimateLightBubbleBackgroundImage:(UIImage *)sendAnimateLightBackgroundImage {
|
||||
[TUIBubbleMessageCell_Minimalist setOutgoingAnimatedHighlightedAlpha20:sendAnimateLightBackgroundImage];
|
||||
}
|
||||
|
||||
- (UIImage *)sendAnimateLightBubbleBackgroundImage {
|
||||
return [TUIBubbleMessageCell_Minimalist outgoingAnimatedHighlightedAlpha20];
|
||||
}
|
||||
|
||||
- (void)setReceiveAnimateLightBubbleBackgroundImage:(UIImage *)receiveAnimateLightBubbleBackgroundImage {
|
||||
[TUIBubbleMessageCell_Minimalist setIncommingAnimatedHighlightedAlpha20:receiveAnimateLightBubbleBackgroundImage];
|
||||
}
|
||||
|
||||
- (UIImage *)receiveAnimateLightBubbleBackgroundImage {
|
||||
return [TUIBubbleMessageCell_Minimalist incommingAnimatedHighlightedAlpha20];
|
||||
}
|
||||
|
||||
- (void)setSendAnimateDarkBubbleBackgroundImage:(UIImage *)sendAnimateDarkBackgroundImage {
|
||||
[TUIBubbleMessageCell_Minimalist setOutgoingAnimatedHighlightedAlpha50:sendAnimateDarkBackgroundImage];
|
||||
}
|
||||
|
||||
- (UIImage *)sendAnimateDarkBubbleBackgroundImage {
|
||||
return [TUIBubbleMessageCell_Minimalist outgoingAnimatedHighlightedAlpha50];
|
||||
}
|
||||
|
||||
- (void)setReceiveAnimateDarkBubbleBackgroundImage:(UIImage *)receiveAnimateDarkBubbleBackgroundImage {
|
||||
[TUIBubbleMessageCell_Minimalist setIncommingAnimatedHighlightedAlpha50:receiveAnimateDarkBubbleBackgroundImage];
|
||||
}
|
||||
|
||||
- (UIImage *)receiveAnimateDarkBubbleBackgroundImage {
|
||||
return [TUIBubbleMessageCell_Minimalist incommingAnimatedHighlightedAlpha50];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@implementation TUIChatConfig_Minimalist (InputBar)
|
||||
|
||||
- (id<TUIChatInputBarConfigDataSource>)inputBarDataSource {
|
||||
return [TUIChatConfig defaultConfig].inputBarDataSource;
|
||||
}
|
||||
|
||||
- (void)setInputBarDataSource:(id<TUIChatInputBarConfigDataSource>)inputBarDataSource {
|
||||
[TUIChatConfig defaultConfig].inputBarDataSource = inputBarDataSource;
|
||||
}
|
||||
|
||||
- (void)setShowInputBar:(BOOL)showInputBar {
|
||||
[TUIChatConfig defaultConfig].enableMainPageInputBar = showInputBar;
|
||||
}
|
||||
|
||||
- (BOOL)showInputBar {
|
||||
return ![TUIChatConfig defaultConfig].enableMainPageInputBar;
|
||||
}
|
||||
|
||||
+ (void)hideItemsInMoreMenu:(TUIChatInputBarMoreMenuItem)items {
|
||||
[TUIChatConfig defaultConfig].enableWelcomeCustomMessage = !(items & TUIChatInputBarMoreMenuItem_CustomMessage);
|
||||
[TUIChatConfig defaultConfig].showRecordVideoButton = !(items & TUIChatInputBarMoreMenuItem_RecordVideo);
|
||||
[TUIChatConfig defaultConfig].showTakePhotoButton = !(items & TUIChatInputBarMoreMenuItem_TakePhoto);
|
||||
[TUIChatConfig defaultConfig].showAlbumButton = !(items & TUIChatInputBarMoreMenuItem_Album);
|
||||
[TUIChatConfig defaultConfig].showFileButton = !(items & TUIChatInputBarMoreMenuItem_File);
|
||||
}
|
||||
|
||||
- (void)addStickerGroup:(TUIFaceGroup *)group {
|
||||
id<TUIEmojiMeditorProtocol> service = [[TIMCommonMediator share] getObject:@protocol(TUIEmojiMeditorProtocol)];
|
||||
[service appendFaceGroup:group];
|
||||
}
|
||||
|
||||
@end
|
||||
15
TUIKit/TUIChat/UI_Minimalist/Header/TUIChat_Minimalist.h
Normal file
15
TUIKit/TUIChat/UI_Minimalist/Header/TUIChat_Minimalist.h
Normal file
@@ -0,0 +1,15 @@
|
||||
//
|
||||
// TUIChat_Minimalist.h
|
||||
// Pods
|
||||
//
|
||||
// Created by harvy on 2022/6/9.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef TUIChat_Minimalist_h
|
||||
#define TUIChat_Minimalist_h
|
||||
|
||||
#import "TUIC2CChatViewController_Minimalist.h"
|
||||
#import "TUIChatConfig.h"
|
||||
#import "TUIGroupChatViewController_Minimalist.h"
|
||||
#endif /* TUIChat_Minimalist_h */
|
||||
193
TUIKit/TUIChat/UI_Minimalist/Input/TUIInputBar_Minimalist.h
Normal file
193
TUIKit/TUIChat/UI_Minimalist/Input/TUIInputBar_Minimalist.h
Normal file
@@ -0,0 +1,193 @@
|
||||
|
||||
// Created by Tencent on 2023/06/09.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
/**
|
||||
* This file declares the TUIInputBarDelegate protocol and the TUIInputBar class.
|
||||
* TUI input bar, a UI component used to detect and obtain user input.
|
||||
* TUIInputBar, the UI component at the bottom of the chat message. Includes text input box, emoji button, voice button, and "+" button ("More" button)
|
||||
* TUIInputBarDelegate provides callbacks for various situations of the input bar, including the callback for the emoticon of clicking the input bar, the
|
||||
* "more" view, and the voice button. And callbacks to send message, send voice, change input height.
|
||||
*/
|
||||
|
||||
#import <TIMCommon/TIMCommonModel.h>
|
||||
#import <TUICore/TUIThemeManager.h>
|
||||
#import <UIKit/UIKit.h>
|
||||
#import "TUIResponderTextView_Minimalist.h"
|
||||
#define kTUIInputNoramlFont [UIFont systemFontOfSize:16.0]
|
||||
#define kTUIInputNormalTextColor TUIChatDynamicColor(@"chat_input_text_color", @"#000000")
|
||||
|
||||
typedef NS_ENUM(NSInteger, TUIRecordStatus) {
|
||||
TUIRecordStatus_Init,
|
||||
TUIRecordStatus_Record,
|
||||
TUIRecordStatus_Delete,
|
||||
TUIRecordStatus_Cancel,
|
||||
};
|
||||
|
||||
@class TUIInputBar_Minimalist;
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// TUIInputBarDelegate
|
||||
//
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@protocol TUIInputBarDelegate_Minimalist <NSObject>
|
||||
|
||||
/**
|
||||
* Callback after clicking the emoji button - "smiley" button.
|
||||
* You can use this callback to achieve: After clicking the emoticon button, the corresponding emoticon view is displayed.
|
||||
*/
|
||||
- (void)inputBarDidTouchFace:(TUIInputBar_Minimalist *)textView;
|
||||
|
||||
/**
|
||||
* Callback after more button - "+" is clicked.
|
||||
* You can use this callback to achieve: corresponding user's click operation to display more corresponding views.
|
||||
*/
|
||||
- (void)inputBarDidTouchMore:(TUIInputBar_Minimalist *)textView;
|
||||
|
||||
/**
|
||||
* You can use this callback to achieve: jump to the camera interface, take a photo or select a local photo
|
||||
*/
|
||||
- (void)inputBarDidTouchCamera:(TUIInputBar_Minimalist *)textView;
|
||||
|
||||
/**
|
||||
* Callback when input bar height changes
|
||||
* This callback is fired when the InputBar height changes when you click the voice button, emoji button, "+" button, or call out/retract the keyboard
|
||||
* You can use this callback to achieve: UI layout adjustment when InputBar height changes through this callback function.
|
||||
* In the default implementation of TUIKit, this callback function further calls the didChangeHeight delegate in TUIInputController to adjust the height of the
|
||||
* UI layout after processing the appearance of the expression view and more views.
|
||||
*/
|
||||
- (void)inputBar:(TUIInputBar_Minimalist *)textView didChangeInputHeight:(CGFloat)offset;
|
||||
|
||||
/**
|
||||
* Callback when sending a text message.
|
||||
* This callback is fired when you send a text message through the InputBar (click the send button from the keyboard).
|
||||
* You can use this callback to get the content of the InputBar and send the message.
|
||||
* In the default implementation of TUIKit, this callback further calls the didSendMessage delegate in TUIInputController for further logical processing of
|
||||
* message sending after processing the appearance of the expression view and more views.
|
||||
*/
|
||||
- (void)inputBar:(TUIInputBar_Minimalist *)textView didSendText:(NSString *)text;
|
||||
|
||||
/**
|
||||
* Callback when sending voice
|
||||
* This callback is triggered when you long press and release the voice button.
|
||||
* You can use this callback to process the recorded voice information and send the voice message.
|
||||
* In the default implementation of TUIKit, this callback function further calls the didSendMessage delegate in TUIInputController for further logical
|
||||
* processing of message sending after processing the appearance of the expression view and more views.
|
||||
*/
|
||||
- (void)inputBar:(TUIInputBar_Minimalist *)textView didSendVoice:(NSString *)path;
|
||||
|
||||
/**
|
||||
* Callback after entering text containing the @ character
|
||||
*/
|
||||
- (void)inputBarDidInputAt:(TUIInputBar_Minimalist *)textView;
|
||||
|
||||
/**
|
||||
* Callback after removing text containing @ characters (e.g. removing @xxx)
|
||||
*/
|
||||
- (void)inputBar:(TUIInputBar_Minimalist *)textView didDeleteAt:(NSString *)text;
|
||||
|
||||
/**
|
||||
* Callback after keyboard button click
|
||||
* After clicking the emoticon button, the "smiley face" icon at the corresponding position will become the "keyboard" icon, which is the keyboard button at
|
||||
* this time. You can use this callback to: hide the currently displayed emoticon view or more views, and open the keyboard.
|
||||
*/
|
||||
- (void)inputBarDidTouchKeyboard:(TUIInputBar_Minimalist *)textView;
|
||||
|
||||
/**
|
||||
* Callback after clicking delete button on keyboard
|
||||
*/
|
||||
- (void)inputBarDidDeleteBackward:(TUIInputBar_Minimalist *)textView;
|
||||
|
||||
- (void)inputTextViewShouldBeginTyping:(UITextView *)textView;
|
||||
|
||||
- (void)inputTextViewShouldEndTyping:(UITextView *)textView;
|
||||
|
||||
@end
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// TUIInputBar
|
||||
//
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@interface TUIInputBar_Minimalist : UIView
|
||||
|
||||
/**
|
||||
* Separtor
|
||||
*/
|
||||
@property(nonatomic, strong) UIView *lineView;
|
||||
|
||||
/**
|
||||
* Voice button
|
||||
* Switch to voice input state after clicking
|
||||
*/
|
||||
@property(nonatomic, strong) UIButton *micButton;
|
||||
|
||||
/**
|
||||
* Camera button
|
||||
* Switch to camera after clicking
|
||||
*/
|
||||
@property(nonatomic, strong) UIButton *cameraButton;
|
||||
|
||||
/**
|
||||
* Keyboard button
|
||||
* Switch to keyboard input state after clicking
|
||||
*/
|
||||
@property(nonatomic, strong) UIButton *keyboardButton;
|
||||
|
||||
/**
|
||||
* Input view
|
||||
*/
|
||||
@property(nonatomic, strong) TUIResponderTextView_Minimalist *inputTextView;
|
||||
|
||||
/**
|
||||
* Emoticon button
|
||||
* Switch to emoji input state after clicking
|
||||
*/
|
||||
@property(nonatomic, strong) UIButton *faceButton;
|
||||
|
||||
/**
|
||||
* More button
|
||||
* A button that, when clicked, opens up more menu options
|
||||
*/
|
||||
@property(nonatomic, strong) UIButton *moreButton;
|
||||
|
||||
/**
|
||||
* Record View
|
||||
*/
|
||||
@property(nonatomic, strong) UIView *recordView;
|
||||
@property(nonatomic, strong) UIImageView *recordDeleteView;
|
||||
@property(nonatomic, strong) UIView *recordBackgroudView;
|
||||
@property(nonatomic, strong) UIView *recordTipsView;
|
||||
@property(nonatomic, strong) UILabel *recordTipsLabel;
|
||||
@property(nonatomic, strong) UILabel *recordTimeLabel;
|
||||
@property(nonatomic, strong) NSMutableArray *recordAnimateViews;
|
||||
@property(nonatomic, strong) UIImageView *recordAnimateCoverView;
|
||||
@property(nonatomic, assign) CGRect recordAnimateCoverViewFrame;
|
||||
|
||||
@property(nonatomic, weak) id<TUIInputBarDelegate_Minimalist> delegate;
|
||||
@property(nonatomic, copy) void (^inputBarTextChanged)(UITextView * textview);
|
||||
|
||||
/**
|
||||
*
|
||||
* Add emoticon text
|
||||
* Used to input emoticon text in the current text input box
|
||||
*
|
||||
* @param emoji The string representation of the emoticon to be entered.
|
||||
*/
|
||||
- (void)addEmoji:(TUIFaceCellData *)emoji;
|
||||
|
||||
- (void)backDelete;
|
||||
|
||||
- (void)clearInput;
|
||||
|
||||
- (NSString *)getInput;
|
||||
|
||||
- (void)updateTextViewFrame;
|
||||
|
||||
- (void)changeToKeyboard;
|
||||
- (void)addDraftToInputBar:(NSAttributedString *)draft;
|
||||
- (void)addWordsToInputBar:(NSAttributedString *)words;
|
||||
|
||||
@end
|
||||
667
TUIKit/TUIChat/UI_Minimalist/Input/TUIInputBar_Minimalist.m
Normal file
667
TUIKit/TUIChat/UI_Minimalist/Input/TUIInputBar_Minimalist.m
Normal file
@@ -0,0 +1,667 @@
|
||||
//
|
||||
// TUIInputBar_Minimalist.m
|
||||
// UIKit
|
||||
//
|
||||
// Created by kennethmiao on 2018/9/18.
|
||||
// Copyright © 2018 Tencent. All rights reserved.
|
||||
//
|
||||
|
||||
#import "TUIInputBar_Minimalist.h"
|
||||
#import <TIMCommon/NSString+TUIEmoji.h>
|
||||
#import <TIMCommon/NSTimer+TUISafe.h>
|
||||
#import <TIMCommon/TIMDefine.h>
|
||||
#import <TUICore/TUICore.h>
|
||||
#import <TUICore/TUIDarkModel.h>
|
||||
#import <TUICore/TUIGlobalization.h>
|
||||
#import <TUICore/TUITool.h>
|
||||
#import <TUICore/UIView+TUILayout.h>
|
||||
#import "ReactiveObjC/ReactiveObjC.h"
|
||||
#import "TUIAudioRecorder.h"
|
||||
#import "TUIChatConfig.h"
|
||||
|
||||
@interface TUIInputBar_Minimalist () <UITextViewDelegate, TUIAudioRecorderDelegate>
|
||||
|
||||
@property(nonatomic, strong) NSDate *recordStartTime;
|
||||
@property(nonatomic, strong) TUIAudioRecorder *recorder;
|
||||
@property(nonatomic, strong) NSTimer *recordTimer;
|
||||
|
||||
@property(nonatomic, assign) BOOL isFocusOn;
|
||||
@property(nonatomic, strong) NSTimer *sendTypingStatusTimer;
|
||||
@property(nonatomic, assign) BOOL allowSendTypingStatusByChangeWord;
|
||||
@end
|
||||
|
||||
@implementation TUIInputBar_Minimalist
|
||||
|
||||
- (void)dealloc {
|
||||
if (_sendTypingStatusTimer) {
|
||||
[_sendTypingStatusTimer invalidate];
|
||||
_sendTypingStatusTimer = nil;
|
||||
}
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:self];
|
||||
}
|
||||
|
||||
- (id)initWithFrame:(CGRect)frame {
|
||||
self = [super initWithFrame:frame];
|
||||
if (self) {
|
||||
[self setupViews];
|
||||
[self defaultLayout];
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onThemeChanged) name:TUIDidApplyingThemeChangedNotfication object:nil];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)setupViews {
|
||||
self.backgroundColor = RGBA(255, 255, 255, 1);
|
||||
|
||||
_lineView = [[UIView alloc] init];
|
||||
_lineView.backgroundColor = TIMCommonDynamicColor(@"separator_color", @"#FFFFFF");
|
||||
|
||||
_moreButton = [[UIButton alloc] init];
|
||||
[_moreButton addTarget:self action:@selector(clickMoreBtn:) forControlEvents:UIControlEventTouchUpInside];
|
||||
[_moreButton setImage:[[TUIImageCache sharedInstance] getResourceFromCache:TUIChatImagePath_Minimalist(@"TypeSelectorBtnHL_Black")]
|
||||
forState:UIControlStateNormal];
|
||||
[_moreButton setImage:[[TUIImageCache sharedInstance] getResourceFromCache:TUIChatImagePath_Minimalist(@"TypeSelectorBtnHL_Black")]
|
||||
forState:UIControlStateHighlighted];
|
||||
[self addSubview:_moreButton];
|
||||
|
||||
_inputTextView = [[TUIResponderTextView_Minimalist alloc] init];
|
||||
_inputTextView.delegate = self;
|
||||
[_inputTextView setFont:kTUIInputNoramlFont];
|
||||
_inputTextView.backgroundColor = TUIChatDynamicColor(@"chat_input_bg_color", @"#FFFFFF");
|
||||
_inputTextView.textColor = TUIChatDynamicColor(@"chat_input_text_color", @"#000000");
|
||||
UIEdgeInsets ei = UIEdgeInsetsMake(kScale390(9), kScale390(16), kScale390(9), kScale390(30));
|
||||
_inputTextView.textContainerInset = rtlEdgeInsetsWithInsets(ei);
|
||||
_inputTextView.textAlignment = isRTL()?NSTextAlignmentRight: NSTextAlignmentLeft;
|
||||
[_inputTextView setReturnKeyType:UIReturnKeySend];
|
||||
[self addSubview:_inputTextView];
|
||||
|
||||
_keyboardButton = [[UIButton alloc] init];
|
||||
[_keyboardButton addTarget:self action:@selector(clickKeyboardBtn:) forControlEvents:UIControlEventTouchUpInside];
|
||||
[_keyboardButton setImage:TUIChatBundleThemeImage(@"chat_ToolViewKeyboard_img", @"ToolViewKeyboard") forState:UIControlStateNormal];
|
||||
[_keyboardButton setImage:TUIChatBundleThemeImage(@"chat_ToolViewKeyboardHL_img", @"ToolViewKeyboardHL") forState:UIControlStateHighlighted];
|
||||
_keyboardButton.hidden = YES;
|
||||
[self addSubview:_keyboardButton];
|
||||
|
||||
_faceButton = [[UIButton alloc] init];
|
||||
[_faceButton addTarget:self action:@selector(clickFaceBtn:) forControlEvents:UIControlEventTouchUpInside];
|
||||
[_faceButton setImage:[[TUIImageCache sharedInstance] getResourceFromCache:TUIChatImagePath_Minimalist(@"ToolViewEmotion")] forState:UIControlStateNormal];
|
||||
[_faceButton setImage:[[TUIImageCache sharedInstance] getResourceFromCache:TUIChatImagePath_Minimalist(@"ToolViewEmotion")]
|
||||
forState:UIControlStateHighlighted];
|
||||
[self addSubview:_faceButton];
|
||||
|
||||
_micButton = [[UIButton alloc] init];
|
||||
[_micButton addTarget:self action:@selector(recordBtnDown:) forControlEvents:UIControlEventTouchDown];
|
||||
[_micButton addTarget:self action:@selector(recordBtnUp:) forControlEvents:UIControlEventTouchUpInside];
|
||||
[_micButton addTarget:self action:@selector(recordBtnCancel:) forControlEvents:UIControlEventTouchUpOutside | UIControlEventTouchCancel];
|
||||
[_micButton addTarget:self action:@selector(recordBtnDragExit:) forControlEvents:UIControlEventTouchDragExit];
|
||||
[_micButton addTarget:self action:@selector(recordBtnDragEnter:) forControlEvents:UIControlEventTouchDragEnter];
|
||||
[_micButton setImage:[[TUIImageCache sharedInstance] getResourceFromCache:TUIChatImagePath_Minimalist(@"ToolViewInputVoice")]
|
||||
forState:UIControlStateNormal];
|
||||
[self addSubview:_micButton];
|
||||
|
||||
_cameraButton = [[UIButton alloc] init];
|
||||
[_cameraButton addTarget:self action:@selector(clickCameraBtn:) forControlEvents:UIControlEventTouchUpInside];
|
||||
[_cameraButton setImage:[[TUIImageCache sharedInstance] getResourceFromCache:TUIChatImagePath_Minimalist(@"ToolViewInputCamera")]
|
||||
forState:UIControlStateNormal];
|
||||
[_cameraButton setImage:[[TUIImageCache sharedInstance] getResourceFromCache:TUIChatImagePath_Minimalist(@"ToolViewInputCamera")]
|
||||
forState:UIControlStateHighlighted];
|
||||
[self addSubview:_cameraButton];
|
||||
|
||||
[self initRecordView];
|
||||
}
|
||||
|
||||
- (void)initRecordView {
|
||||
_recordView = [[UIView alloc] init];
|
||||
_recordView.backgroundColor = RGBA(255, 255, 255, 1);
|
||||
_recordView.hidden = YES;
|
||||
[self addSubview:_recordView];
|
||||
|
||||
_recordDeleteView = [[UIImageView alloc] init];
|
||||
[_recordView addSubview:_recordDeleteView];
|
||||
|
||||
_recordBackgroudView = [[UIView alloc] init];
|
||||
[_recordView addSubview:_recordBackgroudView];
|
||||
|
||||
_recordTimeLabel = [[UILabel alloc] init];
|
||||
_recordTimeLabel.textColor = [UIColor whiteColor];
|
||||
_recordTimeLabel.font = [UIFont systemFontOfSize:14];
|
||||
[_recordView addSubview:_recordTimeLabel];
|
||||
|
||||
_recordAnimateViews = [NSMutableArray array];
|
||||
for (int i = 0; i < 6; i++) {
|
||||
UIImageView *recordAnimateView = [[UIImageView alloc] init];
|
||||
[recordAnimateView setImage:[[TUIImageCache sharedInstance] getResourceFromCache:TUIChatImagePath_Minimalist(@"voice_record_animation")]];
|
||||
[_recordView addSubview:recordAnimateView];
|
||||
[_recordAnimateViews addObject:recordAnimateView];
|
||||
}
|
||||
|
||||
_recordAnimateCoverView = [[UIImageView alloc] init];
|
||||
[_recordView addSubview:_recordAnimateCoverView];
|
||||
|
||||
_recordTipsView = [[UIView alloc] init];
|
||||
_recordTipsView.backgroundColor = [UIColor whiteColor];
|
||||
_recordTipsView.frame = CGRectMake(0, -56, Screen_Width, 56);
|
||||
[_recordView addSubview:_recordTipsView];
|
||||
|
||||
_recordTipsLabel = [[UILabel alloc] init];
|
||||
_recordTipsLabel.textColor = RGBA(102, 102, 102, 1);
|
||||
_recordTipsLabel.textColor = [UIColor blackColor];
|
||||
_recordTipsLabel.text = TIMCommonLocalizableString(TUIKitInputRecordTipsTitle);
|
||||
_recordTipsLabel.textAlignment = NSTextAlignmentCenter;
|
||||
_recordTipsLabel.font = [UIFont systemFontOfSize:14];
|
||||
_recordTipsLabel.frame = CGRectMake(0, 10, Screen_Width, 22);
|
||||
[_recordTipsView addSubview:_recordTipsLabel];
|
||||
|
||||
[self setRecordStatus:TUIRecordStatus_Init];
|
||||
}
|
||||
|
||||
- (void)setRecordStatus:(TUIRecordStatus)status {
|
||||
switch (status) {
|
||||
case TUIRecordStatus_Init:
|
||||
case TUIRecordStatus_Record:
|
||||
case TUIRecordStatus_Cancel: {
|
||||
_recordDeleteView.frame = CGRectMake(kScale390(16), _recordDeleteView.mm_y, 24, 24);
|
||||
if (isRTL()){
|
||||
[_recordDeleteView resetFrameToFitRTL];
|
||||
}
|
||||
_recordDeleteView.image = [[TUIImageCache sharedInstance] getResourceFromCache:TUIChatImagePath_Minimalist(@"voice_record_delete")];
|
||||
_recordBackgroudView.backgroundColor = RGBA(20, 122, 255, 1);
|
||||
_recordAnimateCoverView.backgroundColor = _recordBackgroudView.backgroundColor;
|
||||
_recordAnimateCoverView.frame = self.recordAnimateCoverViewFrame;
|
||||
_recordTimeLabel.text = @"0:00";
|
||||
_recordTipsLabel.text = TIMCommonLocalizableString(TUIKitInputRecordTipsTitle);
|
||||
|
||||
if (TUIRecordStatus_Record == status) {
|
||||
_recordView.hidden = NO;
|
||||
} else {
|
||||
_recordView.hidden = YES;
|
||||
}
|
||||
} break;
|
||||
case TUIRecordStatus_Delete: {
|
||||
_recordDeleteView.frame = CGRectMake(0, _recordDeleteView.mm_y, 20, 24);
|
||||
if (isRTL()){
|
||||
[_recordDeleteView resetFrameToFitRTL];
|
||||
}
|
||||
_recordDeleteView.image = [[TUIImageCache sharedInstance] getResourceFromCache:TUIChatImagePath_Minimalist(@"voice_record_delete_ready")];
|
||||
_recordBackgroudView.backgroundColor = RGBA(255, 88, 76, 1);
|
||||
_recordAnimateCoverView.backgroundColor = _recordBackgroudView.backgroundColor;
|
||||
_recordTipsLabel.text = TIMCommonLocalizableString(TUIKitInputRecordCancelTipsTitle);
|
||||
|
||||
_recordView.hidden = NO;
|
||||
} break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)applyBorderTheme {
|
||||
if (_recordBackgroudView) {
|
||||
[_recordBackgroudView.layer setMasksToBounds:YES];
|
||||
[_recordBackgroudView.layer setCornerRadius:_recordBackgroudView.mm_h / 2.0];
|
||||
}
|
||||
|
||||
if (_inputTextView) {
|
||||
[_inputTextView.layer setMasksToBounds:YES];
|
||||
[_inputTextView.layer setCornerRadius:_inputTextView.mm_h / 2.0];
|
||||
[_inputTextView.layer setBorderWidth:0.5f];
|
||||
[_inputTextView.layer setBorderColor:RGBA(221, 221, 221, 1).CGColor];
|
||||
}
|
||||
}
|
||||
- (void)defaultLayout {
|
||||
_lineView.frame = CGRectMake(0, 0, Screen_Width, TLine_Heigh);
|
||||
|
||||
CGFloat iconSize = 24;
|
||||
_moreButton.frame = CGRectMake(kScale390(16), kScale390(13), iconSize, iconSize);
|
||||
_cameraButton.frame = CGRectMake(Screen_Width - kScale390(16) - iconSize, 13, iconSize, iconSize);
|
||||
_micButton.frame = CGRectMake(Screen_Width - kScale390(56) - iconSize, 13, iconSize, iconSize);
|
||||
|
||||
CGFloat faceSize = 19;
|
||||
_faceButton.frame = CGRectMake(_micButton.mm_x - kScale390(50), 15, faceSize, faceSize);
|
||||
|
||||
_keyboardButton.frame = _faceButton.frame;
|
||||
_inputTextView.frame = CGRectMake(kScale390(56), 7, Screen_Width - kScale390(152), 36);
|
||||
|
||||
_recordView.frame = CGRectMake(0, _inputTextView.mm_y, self.mm_w, _inputTextView.mm_h);
|
||||
_recordDeleteView.frame = CGRectMake(kScale390(16), 4, iconSize, iconSize);
|
||||
_recordBackgroudView.frame = CGRectMake(kScale390(54), 0, self.mm_w - kScale390(70), _recordView.mm_h);
|
||||
_recordTimeLabel.frame = CGRectMake(kScale390(70), kScale390(7), 32, 22);
|
||||
|
||||
CGFloat animationStartX = kScale390(112);
|
||||
CGFloat animationY = 8;
|
||||
CGFloat animationSize = 20;
|
||||
CGFloat animationSpace = kScale390(8);
|
||||
CGFloat animationCoverWidth = 0;
|
||||
for (int i = 0; i < self.recordAnimateViews.count; ++i) {
|
||||
UIView *animationView = self.recordAnimateViews[i];
|
||||
animationView.frame = CGRectMake(animationStartX + (animationSize + animationSpace) * i, animationY, animationSize, animationSize);
|
||||
animationCoverWidth = (animationSize + animationSpace) * (i + 1);
|
||||
}
|
||||
_recordAnimateCoverViewFrame = CGRectMake(animationStartX, animationY, animationCoverWidth, animationSize);
|
||||
_recordAnimateCoverView.frame = self.recordAnimateCoverViewFrame;
|
||||
[self applyBorderTheme];
|
||||
|
||||
if(isRTL()) {
|
||||
for (UIView *subviews in self.subviews) {
|
||||
[subviews resetFrameToFitRTL];
|
||||
}
|
||||
for (UIView *subview in _recordView.subviews) {
|
||||
[subview resetFrameToFitRTL];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)layoutButton:(CGFloat)height {
|
||||
CGRect frame = self.frame;
|
||||
CGFloat offset = height - frame.size.height;
|
||||
frame.size.height = height;
|
||||
self.frame = frame;
|
||||
|
||||
if (_delegate && [_delegate respondsToSelector:@selector(inputBar:didChangeInputHeight:)]) {
|
||||
[_delegate inputBar:self didChangeInputHeight:offset];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)clickCameraBtn:(UIButton *)sender {
|
||||
_micButton.hidden = NO;
|
||||
_keyboardButton.hidden = YES;
|
||||
_inputTextView.hidden = NO;
|
||||
_faceButton.hidden = NO;
|
||||
[self setRecordStatus:TUIRecordStatus_Cancel];
|
||||
if (_delegate && [_delegate respondsToSelector:@selector(inputBarDidTouchCamera:)]) {
|
||||
[_delegate inputBarDidTouchCamera:self];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)clickKeyboardBtn:(UIButton *)sender {
|
||||
_micButton.hidden = NO;
|
||||
_keyboardButton.hidden = YES;
|
||||
_inputTextView.hidden = NO;
|
||||
_faceButton.hidden = NO;
|
||||
[self setRecordStatus:TUIRecordStatus_Cancel];
|
||||
[self layoutButton:_inputTextView.frame.size.height + 2 * TTextView_Margin];
|
||||
if (_delegate && [_delegate respondsToSelector:@selector(inputBarDidTouchKeyboard:)]) {
|
||||
[_delegate inputBarDidTouchKeyboard:self];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)clickFaceBtn:(UIButton *)sender {
|
||||
_micButton.hidden = NO;
|
||||
_faceButton.hidden = YES;
|
||||
_keyboardButton.hidden = NO;
|
||||
_inputTextView.hidden = NO;
|
||||
[self setRecordStatus:TUIRecordStatus_Cancel];
|
||||
if (_delegate && [_delegate respondsToSelector:@selector(inputBarDidTouchFace:)]) {
|
||||
[_delegate inputBarDidTouchFace:self];
|
||||
}
|
||||
_keyboardButton.frame = _faceButton.frame;
|
||||
}
|
||||
|
||||
- (void)clickMoreBtn:(UIButton *)sender {
|
||||
if (_delegate && [_delegate respondsToSelector:@selector(inputBarDidTouchMore:)]) {
|
||||
[_delegate inputBarDidTouchMore:self];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)recordBtnDown:(UIButton *)sender {
|
||||
[self.recorder record];
|
||||
}
|
||||
|
||||
- (void)recordBtnUp:(UIButton *)sender {
|
||||
NSTimeInterval interval = [[NSDate date] timeIntervalSinceDate:_recordStartTime];
|
||||
if (interval < 1) {
|
||||
[self.recorder cancel];
|
||||
} else if (interval > 60) {
|
||||
[self.recorder cancel];
|
||||
} else {
|
||||
[self.recorder stop];
|
||||
NSString *path = self.recorder.recordedFilePath;
|
||||
if (path) {
|
||||
if (_delegate && [_delegate respondsToSelector:@selector(inputBar:didSendVoice:)]) {
|
||||
[_delegate inputBar:self didSendVoice:path];
|
||||
}
|
||||
}
|
||||
}
|
||||
[self setRecordStatus:TUIRecordStatus_Cancel];
|
||||
}
|
||||
|
||||
- (void)recordBtnCancel:(UIGestureRecognizer *)gesture {
|
||||
[self setRecordStatus:TUIRecordStatus_Cancel];
|
||||
[self.recorder cancel];
|
||||
}
|
||||
|
||||
- (void)recordBtnDragExit:(UIButton *)sender {
|
||||
[self setRecordStatus:TUIRecordStatus_Delete];
|
||||
}
|
||||
|
||||
- (void)recordBtnDragEnter:(UIButton *)sender {
|
||||
[self setRecordStatus:TUIRecordStatus_Record];
|
||||
}
|
||||
|
||||
- (void)showHapticFeedback {
|
||||
if (@available(iOS 10.0, *)) {
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
UIImpactFeedbackGenerator *generator = [[UIImpactFeedbackGenerator alloc] initWithStyle:UIImpactFeedbackStyleMedium];
|
||||
[generator prepare];
|
||||
[generator impactOccurred];
|
||||
});
|
||||
|
||||
} else {
|
||||
// Fallback on earlier versions
|
||||
}
|
||||
}
|
||||
#pragma mark - talk
|
||||
|
||||
- (void)textViewDidBeginEditing:(UITextView *)textView {
|
||||
self.keyboardButton.hidden = YES;
|
||||
self.micButton.hidden = NO;
|
||||
self.faceButton.hidden = NO;
|
||||
|
||||
self.isFocusOn = YES;
|
||||
self.allowSendTypingStatusByChangeWord = YES;
|
||||
|
||||
__weak typeof(self) weakSelf = self;
|
||||
self.sendTypingStatusTimer = [NSTimer tui_scheduledTimerWithTimeInterval:4
|
||||
repeats:YES
|
||||
block:^(NSTimer *_Nonnull timer) {
|
||||
__strong typeof(weakSelf) strongSelf = weakSelf;
|
||||
strongSelf.allowSendTypingStatusByChangeWord = YES;
|
||||
}];
|
||||
|
||||
if (self.isFocusOn && [textView.textStorage tui_getPlainString].length > 0) {
|
||||
if (_delegate && [_delegate respondsToSelector:@selector(inputTextViewShouldBeginTyping:)]) {
|
||||
[_delegate inputTextViewShouldBeginTyping:textView];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)textViewDidEndEditing:(UITextView *)textView {
|
||||
self.isFocusOn = NO;
|
||||
if (_delegate && [_delegate respondsToSelector:@selector(inputTextViewShouldEndTyping:)]) {
|
||||
[_delegate inputTextViewShouldEndTyping:textView];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)textViewDidChange:(UITextView *)textView {
|
||||
if (self.allowSendTypingStatusByChangeWord && self.isFocusOn && [textView.textStorage tui_getPlainString].length > 0) {
|
||||
if (_delegate && [_delegate respondsToSelector:@selector(inputTextViewShouldBeginTyping:)]) {
|
||||
self.allowSendTypingStatusByChangeWord = NO;
|
||||
[_delegate inputTextViewShouldBeginTyping:textView];
|
||||
}
|
||||
}
|
||||
|
||||
if (self.isFocusOn && [textView.textStorage tui_getPlainString].length == 0) {
|
||||
if (_delegate && [_delegate respondsToSelector:@selector(inputTextViewShouldEndTyping:)]) {
|
||||
[_delegate inputTextViewShouldEndTyping:textView];
|
||||
}
|
||||
}
|
||||
if (self.inputBarTextChanged) {
|
||||
self.inputBarTextChanged(_inputTextView);
|
||||
}
|
||||
CGSize size = [_inputTextView sizeThatFits:CGSizeMake(_inputTextView.frame.size.width, TTextView_TextView_Height_Max)];
|
||||
CGFloat oldHeight = _inputTextView.frame.size.height;
|
||||
CGFloat newHeight = size.height;
|
||||
|
||||
if (newHeight > TTextView_TextView_Height_Max) {
|
||||
newHeight = TTextView_TextView_Height_Max;
|
||||
}
|
||||
if (newHeight < TTextView_TextView_Height_Min) {
|
||||
newHeight = TTextView_TextView_Height_Min;
|
||||
}
|
||||
if (oldHeight == newHeight) {
|
||||
return;
|
||||
}
|
||||
|
||||
__weak typeof(self) ws = self;
|
||||
[UIView animateWithDuration:0.3
|
||||
animations:^{
|
||||
CGRect textFrame = ws.inputTextView.frame;
|
||||
textFrame.size.height += newHeight - oldHeight;
|
||||
ws.inputTextView.frame = textFrame;
|
||||
[ws layoutButton:newHeight + 2 * TTextView_Margin];
|
||||
}];
|
||||
}
|
||||
|
||||
- (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text {
|
||||
if ([text tui_containsString:@"["] && [text tui_containsString:@"]"]) {
|
||||
NSRange selectedRange = textView.selectedRange;
|
||||
if (selectedRange.length > 0) {
|
||||
[textView.textStorage deleteCharactersInRange:selectedRange];
|
||||
}
|
||||
|
||||
NSMutableAttributedString *textChange = [text getAdvancedFormatEmojiStringWithFont:kTUIInputNoramlFont
|
||||
textColor:kTUIInputNormalTextColor
|
||||
emojiLocations:nil];
|
||||
[textView.textStorage insertAttributedString:textChange atIndex:textView.textStorage.length];
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
self.inputTextView.selectedRange = NSMakeRange(self.inputTextView.textStorage.length + 1, 0);
|
||||
});
|
||||
return NO;
|
||||
}
|
||||
|
||||
if ([text isEqualToString:@"\n"]) {
|
||||
if (_delegate && [_delegate respondsToSelector:@selector(inputBar:didSendText:)]) {
|
||||
NSString *sp = [[textView.textStorage tui_getPlainString] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
|
||||
if (sp.length == 0) {
|
||||
UIAlertController *ac = [UIAlertController alertControllerWithTitle:TIMCommonLocalizableString(TUIKitInputBlankMessageTitle)
|
||||
message:nil
|
||||
preferredStyle:UIAlertControllerStyleAlert];
|
||||
[ac tuitheme_addAction:[UIAlertAction actionWithTitle:TIMCommonLocalizableString(Confirm) style:UIAlertActionStyleDefault handler:nil]];
|
||||
[self.mm_viewController presentViewController:ac animated:YES completion:nil];
|
||||
} else {
|
||||
[_delegate inputBar:self didSendText:[textView.textStorage tui_getPlainString]];
|
||||
[self clearInput];
|
||||
}
|
||||
}
|
||||
return NO;
|
||||
} else if ([text isEqualToString:@""]) {
|
||||
if (textView.textStorage.length > range.location) {
|
||||
// Delete the @ message like @xxx at one time
|
||||
NSAttributedString *lastAttributedStr = [textView.textStorage attributedSubstringFromRange:NSMakeRange(range.location, 1)];
|
||||
NSString *lastStr = [lastAttributedStr tui_getPlainString];
|
||||
if (lastStr && lastStr.length > 0 && [lastStr characterAtIndex:0] == ' ') {
|
||||
NSUInteger location = range.location;
|
||||
NSUInteger length = range.length;
|
||||
|
||||
// corresponds to ascii code
|
||||
int at = 64;
|
||||
// (space) ascii
|
||||
// Space (space) corresponding ascii code
|
||||
int space = 32;
|
||||
|
||||
while (location != 0) {
|
||||
location--;
|
||||
length++;
|
||||
// Convert characters to ascii code, copy to int, avoid out of bounds
|
||||
int c = (int)[[[textView.textStorage attributedSubstringFromRange:NSMakeRange(location, 1)] tui_getPlainString] characterAtIndex:0];
|
||||
|
||||
if (c == at) {
|
||||
NSString *atText = [[textView.textStorage attributedSubstringFromRange:NSMakeRange(location, length)] tui_getPlainString];
|
||||
UIFont *textFont = kTUIInputNoramlFont;
|
||||
NSAttributedString *spaceString = [[NSAttributedString alloc] initWithString:@"" attributes:@{NSFontAttributeName : textFont}];
|
||||
[textView.textStorage replaceCharactersInRange:NSMakeRange(location, length) withAttributedString:spaceString];
|
||||
if (self.delegate && [self.delegate respondsToSelector:@selector(inputBar:didDeleteAt:)]) {
|
||||
[self.delegate inputBar:self didDeleteAt:atText];
|
||||
}
|
||||
return NO;
|
||||
} else if (c == space) {
|
||||
// Avoid "@nickname Hello, nice to meet you (space) "" Press del after a space to over-delete to @
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Monitor the input of @ character, including full-width/half-width
|
||||
else if ([text isEqualToString:@"@"] || [text isEqualToString:@"@"]) {
|
||||
if (self.delegate && [self.delegate respondsToSelector:@selector(inputBarDidInputAt:)]) {
|
||||
[self.delegate inputBarDidInputAt:self];
|
||||
}
|
||||
return NO;
|
||||
}
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (void)onDeleteBackward:(TUIResponderTextView_Minimalist *)textView {
|
||||
if (self.delegate && [self.delegate respondsToSelector:@selector(inputBarDidDeleteBackward:)]) {
|
||||
[self.delegate inputBarDidDeleteBackward:self];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)clearInput {
|
||||
[_inputTextView.textStorage deleteCharactersInRange:NSMakeRange(0, _inputTextView.textStorage.length)];
|
||||
[self textViewDidChange:_inputTextView];
|
||||
}
|
||||
|
||||
- (NSString *)getInput {
|
||||
return [_inputTextView.textStorage tui_getPlainString];
|
||||
}
|
||||
|
||||
- (void)addEmoji:(TUIFaceCellData *)emoji {
|
||||
// Create emoji attachment
|
||||
TUIEmojiTextAttachment *emojiTextAttachment = [[TUIEmojiTextAttachment alloc] init];
|
||||
emojiTextAttachment.faceCellData = emoji;
|
||||
|
||||
// Set tag and image
|
||||
emojiTextAttachment.emojiTag = emoji.name;
|
||||
emojiTextAttachment.image = [[TUIImageCache sharedInstance] getFaceFromCache:emoji.path];
|
||||
|
||||
// Set emoji size
|
||||
emojiTextAttachment.emojiSize = kTIMDefaultEmojiSize;
|
||||
NSAttributedString *str = [NSAttributedString attributedStringWithAttachment:emojiTextAttachment];
|
||||
|
||||
NSRange selectedRange = _inputTextView.selectedRange;
|
||||
if (selectedRange.length > 0) {
|
||||
[_inputTextView.textStorage deleteCharactersInRange:selectedRange];
|
||||
}
|
||||
// Insert emoji image
|
||||
[_inputTextView.textStorage insertAttributedString:str atIndex:_inputTextView.selectedRange.location];
|
||||
|
||||
_inputTextView.selectedRange = NSMakeRange(_inputTextView.selectedRange.location + 1, 0);
|
||||
[self resetTextStyle];
|
||||
|
||||
if (_inputTextView.contentSize.height > TTextView_TextView_Height_Max) {
|
||||
float offset = _inputTextView.contentSize.height - _inputTextView.frame.size.height;
|
||||
[_inputTextView scrollRectToVisible:CGRectMake(0, offset, _inputTextView.frame.size.width, _inputTextView.frame.size.height) animated:YES];
|
||||
}
|
||||
[self textViewDidChange:_inputTextView];
|
||||
}
|
||||
|
||||
- (void)resetTextStyle {
|
||||
// After changing text selection, should reset style.
|
||||
NSRange wholeRange = NSMakeRange(0, _inputTextView.textStorage.length);
|
||||
|
||||
[_inputTextView.textStorage removeAttribute:NSFontAttributeName range:wholeRange];
|
||||
|
||||
[_inputTextView.textStorage removeAttribute:NSForegroundColorAttributeName range:wholeRange];
|
||||
|
||||
[_inputTextView.textStorage addAttribute:NSForegroundColorAttributeName value:kTUIInputNormalTextColor range:wholeRange];
|
||||
|
||||
[_inputTextView.textStorage addAttribute:NSFontAttributeName value:kTUIInputNoramlFont range:wholeRange];
|
||||
[_inputTextView setFont:kTUIInputNoramlFont];
|
||||
_inputTextView.textAlignment = isRTL()?NSTextAlignmentRight: NSTextAlignmentLeft;
|
||||
|
||||
}
|
||||
|
||||
- (void)backDelete {
|
||||
if (_inputTextView.textStorage.length > 0) {
|
||||
[_inputTextView.textStorage deleteCharactersInRange:NSMakeRange(_inputTextView.textStorage.length - 1, 1)];
|
||||
[self textViewDidChange:_inputTextView];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)updateTextViewFrame {
|
||||
[self textViewDidChange:[UITextView new]];
|
||||
}
|
||||
|
||||
- (void)changeToKeyboard {
|
||||
[self clickKeyboardBtn:self.keyboardButton];
|
||||
}
|
||||
|
||||
- (void)onThemeChanged {
|
||||
[self applyBorderTheme];
|
||||
}
|
||||
|
||||
- (void)addDraftToInputBar:(NSAttributedString *)draft {
|
||||
[self addWordsToInputBar:draft];
|
||||
}
|
||||
|
||||
- (void)addWordsToInputBar:(NSAttributedString *)words {
|
||||
NSRange selectedRange = self.inputTextView.selectedRange;
|
||||
if (selectedRange.length > 0) {
|
||||
[self.inputTextView.textStorage deleteCharactersInRange:selectedRange];
|
||||
}
|
||||
// Insert words
|
||||
[self.inputTextView.textStorage insertAttributedString:words atIndex:self.inputTextView.selectedRange.location];
|
||||
|
||||
self.inputTextView.selectedRange = NSMakeRange(self.inputTextView.textStorage.length + 1, 0);
|
||||
|
||||
[self resetTextStyle];
|
||||
|
||||
[self updateTextViewFrame];
|
||||
}
|
||||
#pragma mark - TUIAudioRecorderDelegate
|
||||
- (void)audioRecorder:(TUIAudioRecorder *)recorder didCheckPermission:(BOOL)isGranted isFirstTime:(BOOL)isFirstTime {
|
||||
if (isFirstTime) {
|
||||
if (!isGranted) {
|
||||
[self showRequestMicAuthorizationAlert];
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
[self setRecordStatus:TUIRecordStatus_Record];
|
||||
_recordStartTime = [NSDate date];
|
||||
[self showHapticFeedback];
|
||||
}
|
||||
|
||||
- (void)showRequestMicAuthorizationAlert {
|
||||
UIAlertController *ac = [UIAlertController alertControllerWithTitle:TIMCommonLocalizableString(TUIKitInputNoMicTitle)
|
||||
message:TIMCommonLocalizableString(TUIKitInputNoMicTips)
|
||||
preferredStyle:UIAlertControllerStyleAlert];
|
||||
[ac tuitheme_addAction:[UIAlertAction actionWithTitle:TIMCommonLocalizableString(TUIKitInputNoMicOperateLater) style:UIAlertActionStyleCancel handler:nil]];
|
||||
[ac tuitheme_addAction:[UIAlertAction actionWithTitle:TIMCommonLocalizableString(TUIKitInputNoMicOperateEnable)
|
||||
style:UIAlertActionStyleDefault
|
||||
handler:^(UIAlertAction *_Nonnull action) {
|
||||
UIApplication *app = [UIApplication sharedApplication];
|
||||
NSURL *settingsURL = [NSURL URLWithString:UIApplicationOpenSettingsURLString];
|
||||
if ([app canOpenURL:settingsURL]) {
|
||||
[app openURL:settingsURL];
|
||||
}
|
||||
}]];
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[self.mm_viewController presentViewController:ac animated:YES completion:nil];
|
||||
});
|
||||
}
|
||||
|
||||
- (void)audioRecorder:(TUIAudioRecorder *)recorder didRecordTimeChanged:(NSTimeInterval)time {
|
||||
float maxDuration = MIN(59.7, [TUIChatConfig defaultConfig].maxAudioRecordDuration);
|
||||
NSInteger seconds = maxDuration - time;
|
||||
_recordTimeLabel.text = [NSString stringWithFormat:@"%d:%.2d", (int)time / 60, (int)time % 60 + 1];
|
||||
|
||||
CGFloat width = _recordAnimateCoverViewFrame.size.width;
|
||||
int interval_ms = (int)(time * 1000);
|
||||
int runloop_ms = 5 * 1000;
|
||||
CGFloat offset_x = width * (interval_ms % runloop_ms) / runloop_ms;
|
||||
_recordAnimateCoverView.frame = CGRectMake(_recordAnimateCoverViewFrame.origin.x + offset_x, _recordAnimateCoverViewFrame.origin.y, width - offset_x,
|
||||
_recordAnimateCoverViewFrame.size.height);
|
||||
if (time > maxDuration) {
|
||||
[self.recorder stop];
|
||||
[self setRecordStatus:TUIRecordStatus_Cancel];
|
||||
NSString *path = self.recorder.recordedFilePath;
|
||||
if (path) {
|
||||
if (_delegate && [_delegate respondsToSelector:@selector(inputBar:didSendVoice:)]) {
|
||||
[_delegate inputBar:self didSendVoice:path];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - Getter
|
||||
- (TUIAudioRecorder *)recorder {
|
||||
if (!_recorder) {
|
||||
_recorder = [[TUIAudioRecorder alloc] init];
|
||||
_recorder.delegate = self;
|
||||
}
|
||||
return _recorder;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,134 @@
|
||||
|
||||
// Created by Tencent on 2023/06/09.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
/**
|
||||
* This document declares the relevant components to implement the input area.
|
||||
* The input area includes the emoticon view input area (TUIFaceView+TUIMoreView), the "more" functional area (TUIMoreView) and the text input area
|
||||
* (TUIInputBar). This file contains the TUIInputControllerDelegate protocol and the TInputController class. In the input bar (TUIInputBar), button response
|
||||
* callbacks for expressions, voices, and more views are provided. In this class, the InputBar is actually combined with the above three views to realize the
|
||||
* display and switching logic of each view.
|
||||
*/
|
||||
#import <TIMCommon/TUIMessageCell.h>
|
||||
#import <UIKit/UIKit.h>
|
||||
#import "TUIChatDefine.h"
|
||||
#import "TUIFaceView.h"
|
||||
#import "TUIInputBar_Minimalist.h"
|
||||
#import "TUIMenuView_Minimalist.h"
|
||||
#import "TUIReplyPreviewBar_Minimalist.h"
|
||||
#import "TUIFaceSegementScrollView.h"
|
||||
|
||||
@class TUIInputController_Minimalist;
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// TUIInputControllerDelegate
|
||||
//
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@protocol TUIInputControllerDelegate_Minimalist <NSObject>
|
||||
|
||||
/**
|
||||
* Callback when the current InputController height changes.
|
||||
* You can use this callback to adjust the UI layout of each component in the controller according to the changed height.
|
||||
*/
|
||||
- (void)inputController:(TUIInputController_Minimalist *)inputController didChangeHeight:(CGFloat)height;
|
||||
|
||||
/**
|
||||
* Callback when the current InputController sends a message.
|
||||
*/
|
||||
- (void)inputController:(TUIInputController_Minimalist *)inputController didSendMessage:(V2TIMMessage *)msg;
|
||||
|
||||
/**
|
||||
* Callback when the more button in the bottom of input controller was clicked
|
||||
*/
|
||||
- (void)inputControllerDidSelectMoreButton:(TUIInputController_Minimalist *)inputController;
|
||||
|
||||
/**
|
||||
* Callback when the take-photo button in the bottom of input controller was clicked
|
||||
*/
|
||||
- (void)inputControllerDidSelectCamera:(TUIInputController_Minimalist *)inputController;
|
||||
|
||||
/**
|
||||
* Callback when @ character is entered
|
||||
*/
|
||||
- (void)inputControllerDidInputAt:(TUIInputController_Minimalist *)inputController;
|
||||
|
||||
/**
|
||||
* Callback when there are @xxx characters removed
|
||||
*/
|
||||
- (void)inputController:(TUIInputController_Minimalist *)inputController didDeleteAt:(NSString *)atText;
|
||||
|
||||
- (void)inputControllerBeginTyping:(TUIInputController_Minimalist *)inputController;
|
||||
- (void)inputControllerEndTyping:(TUIInputController_Minimalist *)inputController;
|
||||
|
||||
@end
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// TUIInputControllerDelegate
|
||||
//
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@interface TUIInputController_Minimalist : UIViewController
|
||||
|
||||
/**
|
||||
* A preview view above the input box for message reply scenarios
|
||||
*/
|
||||
@property(nonatomic, strong) TUIReplyPreviewBar_Minimalist *replyPreviewBar;
|
||||
|
||||
/**
|
||||
* The preview view below the input box, with the message reference scene
|
||||
*
|
||||
*/
|
||||
@property(nonatomic, strong) TUIReferencePreviewBar_Minimalist *referencePreviewBar;
|
||||
|
||||
/**
|
||||
* Message currently being replied to
|
||||
*/
|
||||
@property(nonatomic, strong) TUIReplyPreviewData *replyData;
|
||||
|
||||
@property(nonatomic, strong) TUIReferencePreviewData *referenceData;
|
||||
|
||||
/**
|
||||
* Input bar
|
||||
* The input bar contains a series of interactive components such as text input box, voice button, "more" button, emoticon button, etc., and provides
|
||||
* corresponding callbacks for these components.
|
||||
*/
|
||||
@property(nonatomic, strong) TUIInputBar_Minimalist *inputBar;
|
||||
|
||||
/**
|
||||
* Emoticon view
|
||||
* The emoticon view generally appears after clicking the "Smiley" button. Responsible for displaying each expression group and the expressions within the
|
||||
* group.
|
||||
*
|
||||
*/
|
||||
|
||||
@property(nonatomic, strong) TUIFaceSegementScrollView *faceSegementScrollView;
|
||||
|
||||
/**
|
||||
* Menu view
|
||||
* The menu view is located below the emoticon view and is responsible for providing the emoticon grouping unit and the send button.
|
||||
*/
|
||||
@property(nonatomic, strong) TUIMenuView_Minimalist *menuView;
|
||||
|
||||
@property(nonatomic, weak) id<TUIInputControllerDelegate_Minimalist> delegate;
|
||||
|
||||
/**
|
||||
* Reset the current input controller.
|
||||
* If there is currently an emoji view or a "more" view being displayed, collapse the corresponding view and set the current status to Input_Status_Input.
|
||||
* That is, no matter what state the current InputController is in, reset it to its initialized state.
|
||||
*/
|
||||
- (void)reset;
|
||||
|
||||
/**
|
||||
* Show/hide preview bar of message reply input box
|
||||
*/
|
||||
- (void)showReplyPreview:(TUIReplyPreviewData *)data;
|
||||
- (void)showReferencePreview:(TUIReferencePreviewData *)data;
|
||||
- (void)exitReplyAndReference:(void (^__nullable)(void))finishedCallback;
|
||||
|
||||
/**
|
||||
* Current input box state
|
||||
*/
|
||||
@property(nonatomic, assign, readonly) InputStatus status;
|
||||
@end
|
||||
@@ -0,0 +1,627 @@
|
||||
//
|
||||
// TInputController.m
|
||||
// UIKit
|
||||
//
|
||||
// Created by kennethmiao on 2018/9/18.
|
||||
// Copyright © 2018 Tencent. All rights reserved.
|
||||
//
|
||||
|
||||
#import "TUIInputController_Minimalist.h"
|
||||
#import <AVFoundation/AVFoundation.h>
|
||||
#import <TIMCommon/NSString+TUIEmoji.h>
|
||||
#import <TIMCommon/TIMCommonModel.h>
|
||||
#import <TIMCommon/TIMDefine.h>
|
||||
#import <TUICore/TUIDarkModel.h>
|
||||
#import <TUICore/TUIThemeManager.h>
|
||||
#import "TUIChatConfig.h"
|
||||
#import "TUIChatDataProvider.h"
|
||||
#import "TUIChatModifyMessageHelper.h"
|
||||
#import "TUICloudCustomDataTypeCenter.h"
|
||||
#import "TUIFaceMessageCell_Minimalist.h"
|
||||
#import "TUIMenuCellData.h"
|
||||
#import "TUIMenuCell_Minimalist.h"
|
||||
#import "TUIMessageDataProvider.h"
|
||||
#import "TUITextMessageCell_Minimalist.h"
|
||||
#import "TUIVoiceMessageCell_Minimalist.h"
|
||||
#import <TIMCommon/TIMCommonMediator.h>
|
||||
#import <TIMCommon/TUIEmojiMeditorProtocol.h>
|
||||
|
||||
@interface TUIInputController_Minimalist () <TUIInputBarDelegate_Minimalist, TUIMenuViewDelegate_Minimalist, TUIFaceViewDelegate>
|
||||
@property(nonatomic, assign) InputStatus status;
|
||||
@property(nonatomic, assign) CGRect keyboardFrame;
|
||||
|
||||
@property(nonatomic, copy) void (^modifyRootReplyMsgBlock)(TUIMessageCellData *);
|
||||
@end
|
||||
|
||||
@implementation TUIInputController_Minimalist
|
||||
- (void)viewDidLoad {
|
||||
[super viewDidLoad];
|
||||
[self setupViews];
|
||||
}
|
||||
- (void)viewWillAppear:(BOOL)animated {
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillHide:) name:UIKeyboardWillHideNotification object:nil];
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillShow:) name:UIKeyboardWillShowNotification object:nil];
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillChangeFrame:) name:UIKeyboardWillChangeFrameNotification object:nil];
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(inputMessageStatusChanged:) name:@"kTUINotifyMessageStatusChanged" object:nil];
|
||||
}
|
||||
|
||||
- (void)viewDidAppear:(BOOL)animated {
|
||||
[super viewDidAppear:animated];
|
||||
for (UIGestureRecognizer *gesture in self.view.window.gestureRecognizers) {
|
||||
NSLog(@"gesture = %@", gesture);
|
||||
gesture.delaysTouchesBegan = NO;
|
||||
NSLog(@"delaysTouchesBegan = %@", gesture.delaysTouchesBegan ? @"YES" : @"NO");
|
||||
NSLog(@"delaysTouchesEnded = %@", gesture.delaysTouchesEnded ? @"YES" : @"NO");
|
||||
}
|
||||
self.navigationController.interactivePopGestureRecognizer.delaysTouchesBegan = NO;
|
||||
}
|
||||
|
||||
- (void)viewDidDisappear:(BOOL)animated {
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:self];
|
||||
}
|
||||
|
||||
- (void)setupViews {
|
||||
self.view.backgroundColor = RGBA(255, 255, 255, 1);
|
||||
_status = Input_Status_Input;
|
||||
|
||||
_inputBar =
|
||||
[[TUIInputBar_Minimalist alloc] initWithFrame:CGRectMake(0, CGRectGetMaxY(self.replyPreviewBar.frame), self.view.frame.size.width, TTextView_Height)];
|
||||
_inputBar.delegate = self;
|
||||
[self.view addSubview:_inputBar];
|
||||
}
|
||||
|
||||
- (void)keyboardWillHide:(NSNotification *)notification {
|
||||
if (_delegate && [_delegate respondsToSelector:@selector(inputController:didChangeHeight:)]) {
|
||||
CGFloat inputContainerBottom = [self getInputContainerBottom];
|
||||
[_delegate inputController:self didChangeHeight:inputContainerBottom + Bottom_SafeHeight];
|
||||
}
|
||||
if (_status == Input_Status_Input_Keyboard) {
|
||||
_status = Input_Status_Input;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)keyboardWillShow:(NSNotification *)notification {
|
||||
if (_status == Input_Status_Input_Face) {
|
||||
[self hideFaceAnimation];
|
||||
} else {
|
||||
//[self hideFaceAnimation:NO];
|
||||
//[self hideMoreAnimation:NO];
|
||||
}
|
||||
_status = Input_Status_Input_Keyboard;
|
||||
}
|
||||
|
||||
- (void)keyboardWillChangeFrame:(NSNotification *)notification {
|
||||
CGRect keyboardFrame = [notification.userInfo[UIKeyboardFrameEndUserInfoKey] CGRectValue];
|
||||
if (_delegate && [_delegate respondsToSelector:@selector(inputController:didChangeHeight:)]) {
|
||||
CGFloat inputContainerBottom = [self getInputContainerBottom];
|
||||
[_delegate inputController:self didChangeHeight:keyboardFrame.size.height + inputContainerBottom];
|
||||
}
|
||||
self.keyboardFrame = keyboardFrame;
|
||||
}
|
||||
|
||||
- (void)hideFaceAnimation {
|
||||
self.faceSegementScrollView.hidden = NO;
|
||||
self.faceSegementScrollView.alpha = 1.0;
|
||||
self.menuView.hidden = NO;
|
||||
self.menuView.alpha = 1.0;
|
||||
__weak typeof(self) ws = self;
|
||||
[UIView animateWithDuration:0.3
|
||||
delay:0
|
||||
options:UIViewAnimationOptionCurveEaseOut
|
||||
animations:^{
|
||||
ws.faceSegementScrollView.alpha = 0.0;
|
||||
ws.menuView.alpha = 0.0;
|
||||
}
|
||||
completion:^(BOOL finished) {
|
||||
ws.faceSegementScrollView.hidden = YES;
|
||||
ws.faceSegementScrollView.alpha = 1.0;
|
||||
ws.menuView.hidden = YES;
|
||||
ws.menuView.alpha = 1.0;
|
||||
[ws.menuView removeFromSuperview];
|
||||
[ws.faceSegementScrollView removeFromSuperview];
|
||||
ws.view.backgroundColor = RGBA(255, 255, 255, 1);
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)showFaceAnimation {
|
||||
[self.view addSubview:self.faceSegementScrollView];
|
||||
[self.view addSubview:self.menuView];
|
||||
__weak typeof(self) ws = self;
|
||||
[self.faceSegementScrollView updateRecentView];
|
||||
[self.faceSegementScrollView setAllFloatCtrlViewAllowSendSwitch:(self.inputBar.inputTextView.text.length > 0)?YES:NO];
|
||||
self.faceSegementScrollView.onScrollCallback = ^(NSInteger indexPage) {
|
||||
[ws.menuView scrollToMenuIndex:indexPage];
|
||||
};
|
||||
self.inputBar.inputBarTextChanged = ^(UITextView *textview) {
|
||||
if(textview.text.length > 0) {
|
||||
[ws.faceSegementScrollView setAllFloatCtrlViewAllowSendSwitch:YES];
|
||||
}
|
||||
else {
|
||||
[ws.faceSegementScrollView setAllFloatCtrlViewAllowSendSwitch:NO];
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
self.faceSegementScrollView.hidden = NO;
|
||||
CGRect frame = self.menuView.frame;
|
||||
frame.origin.y = self.view.window.frame.size.height;
|
||||
self.menuView.frame = frame;
|
||||
self.menuView.hidden = NO;
|
||||
frame = self.faceSegementScrollView.frame;
|
||||
frame.origin.y = self.menuView.frame.origin.y + self.menuView.frame.size.height;
|
||||
self.faceSegementScrollView.frame = frame;
|
||||
|
||||
[UIView animateWithDuration:0.3
|
||||
delay:0
|
||||
options:UIViewAnimationOptionCurveEaseOut
|
||||
animations:^{
|
||||
CGRect newFrame = ws.menuView.frame;
|
||||
newFrame.origin.y = CGRectGetMaxY(ws.inputBar.frame); // ws.inputBar.frame.origin.y + ws.inputBar.frame.size.height;
|
||||
ws.menuView.frame = newFrame;
|
||||
|
||||
newFrame = ws.faceSegementScrollView.frame;
|
||||
newFrame.origin.y = ws.menuView.frame.origin.y + ws.menuView.frame.size.height;
|
||||
ws.faceSegementScrollView.frame = newFrame;
|
||||
ws.view.backgroundColor = TUIChatDynamicColor(@"chat_input_controller_bg_color", @"#EBF0F6");
|
||||
}
|
||||
completion:nil];
|
||||
}
|
||||
|
||||
- (void)inputBarDidTouchCamera:(TUIInputBar_Minimalist *)textView {
|
||||
[_inputBar.inputTextView resignFirstResponder];
|
||||
[self hideFaceAnimation];
|
||||
_status = Input_Status_Input_Camera;
|
||||
if (_delegate && [_delegate respondsToSelector:@selector(inputController:didChangeHeight:)]) {
|
||||
CGFloat inputContainerBottom = [self getInputContainerBottom];
|
||||
[_delegate inputController:self didChangeHeight:inputContainerBottom + Bottom_SafeHeight];
|
||||
}
|
||||
if (_delegate && [_delegate respondsToSelector:@selector(inputControllerDidSelectCamera:)]) {
|
||||
[_delegate inputControllerDidSelectCamera:self];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)inputBarDidTouchMore:(TUIInputBar_Minimalist *)textView {
|
||||
if (self.delegate && [self.delegate respondsToSelector:@selector(inputControllerDidSelectMoreButton:)]) {
|
||||
[self.delegate inputControllerDidSelectMoreButton:self];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)inputBarDidTouchFace:(TUIInputBar_Minimalist *)textView {
|
||||
if ([TIMConfig defaultConfig].faceGroups.count == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
[_inputBar.inputTextView resignFirstResponder];
|
||||
_status = Input_Status_Input_Face;
|
||||
if (_delegate && [_delegate respondsToSelector:@selector(inputController:didChangeHeight:)]) {
|
||||
[_delegate inputController:self
|
||||
didChangeHeight:CGRectGetMaxY(_inputBar.frame) + self.faceSegementScrollView.frame.size.height + self.menuView.frame.size.height ];
|
||||
}
|
||||
[self showFaceAnimation];
|
||||
}
|
||||
|
||||
- (void)inputBarDidTouchKeyboard:(TUIInputBar_Minimalist *)textView {
|
||||
if (_status == Input_Status_Input_Face) {
|
||||
[self hideFaceAnimation];
|
||||
}
|
||||
_status = Input_Status_Input_Keyboard;
|
||||
[_inputBar.inputTextView becomeFirstResponder];
|
||||
}
|
||||
|
||||
- (void)inputBar:(TUIInputBar_Minimalist *)textView didChangeInputHeight:(CGFloat)offset {
|
||||
if (_status == Input_Status_Input_Face) {
|
||||
[self showFaceAnimation];
|
||||
}
|
||||
if (_delegate && [_delegate respondsToSelector:@selector(inputController:didChangeHeight:)]) {
|
||||
[_delegate inputController:self didChangeHeight:self.view.frame.size.height + offset];
|
||||
if (_referencePreviewBar) {
|
||||
CGRect referencePreviewBarFrame = _referencePreviewBar.frame;
|
||||
_referencePreviewBar.frame = CGRectMake(referencePreviewBarFrame.origin.x, referencePreviewBarFrame.origin.y + offset,
|
||||
referencePreviewBarFrame.size.width, referencePreviewBarFrame.size.height);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)inputBar:(TUIInputBar_Minimalist *)textView didSendText:(NSString *)text {
|
||||
/**
|
||||
* Emoticon internationalization --> restore to actual Chinese key
|
||||
*/
|
||||
NSString *content = [text getInternationalStringWithfaceContent];
|
||||
V2TIMMessage *message = [[V2TIMManager sharedInstance] createTextMessage:content];
|
||||
[self appendReplyDataIfNeeded:message];
|
||||
[self appendReferenceDataIfNeeded:message];
|
||||
if (_delegate && [_delegate respondsToSelector:@selector(inputController:didSendMessage:)]) {
|
||||
[_delegate inputController:self didSendMessage:message];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)inputMessageStatusChanged:(NSNotification *)noti {
|
||||
NSDictionary *userInfo = noti.userInfo;
|
||||
TUIMessageCellData *msg = userInfo[@"msg"];
|
||||
long status = [userInfo[@"status"] intValue];
|
||||
if ([msg isKindOfClass:TUIMessageCellData.class] && status == Msg_Status_Succ) {
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
if (self.modifyRootReplyMsgBlock) {
|
||||
self.modifyRootReplyMsgBlock(msg);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
- (void)appendReplyDataIfNeeded:(V2TIMMessage *)message {
|
||||
if (self.replyData) {
|
||||
V2TIMMessage *parentMsg = self.replyData.originMessage;
|
||||
NSMutableDictionary *simpleReply = [NSMutableDictionary dictionary];
|
||||
[simpleReply addEntriesFromDictionary:@{
|
||||
@"messageID" : self.replyData.msgID ?: @"",
|
||||
@"messageAbstract" : [self.replyData.msgAbstract ?: @"" getInternationalStringWithfaceContent],
|
||||
@"messageSender" : self.replyData.sender ?: @"",
|
||||
@"messageType" : @(self.replyData.type),
|
||||
@"messageTime" : @(self.replyData.originMessage.timestamp ? [self.replyData.originMessage.timestamp timeIntervalSince1970] : 0),
|
||||
@"messageSequence" : @(self.replyData.originMessage.seq),
|
||||
@"version" : @(kMessageReplyVersion),
|
||||
}];
|
||||
|
||||
NSMutableDictionary *cloudResultDic = [[NSMutableDictionary alloc] initWithCapacity:5];
|
||||
if (parentMsg.cloudCustomData) {
|
||||
NSDictionary *originDic = [TUITool jsonData2Dictionary:parentMsg.cloudCustomData];
|
||||
if (originDic && [originDic isKindOfClass:[NSDictionary class]]) {
|
||||
[cloudResultDic addEntriesFromDictionary:originDic];
|
||||
}
|
||||
/**
|
||||
* Accept the data in the parent, but cannot save messageReplies\messageReact, because the root message topic creator has this field.
|
||||
* messageReplies\messageReact cannot be stored in the new message currently sent
|
||||
*/
|
||||
[cloudResultDic removeObjectForKey:@"messageReplies"];
|
||||
[cloudResultDic removeObjectForKey:@"messageReact"];
|
||||
}
|
||||
NSString *messageParentReply = cloudResultDic[@"messageReply"];
|
||||
NSString *messageRootID = [messageParentReply valueForKey:@"messageRootID"];
|
||||
if (self.replyData.messageRootID.length > 0) {
|
||||
messageRootID = self.replyData.messageRootID;
|
||||
}
|
||||
if (!IS_NOT_EMPTY_NSSTRING(messageRootID)) {
|
||||
/**
|
||||
* If the original message does not have a messageRootID, you need to use the msgID of the current original message as root
|
||||
*/
|
||||
if (IS_NOT_EMPTY_NSSTRING(parentMsg.msgID)) {
|
||||
messageRootID = parentMsg.msgID;
|
||||
}
|
||||
}
|
||||
[simpleReply setObject:messageRootID forKey:@"messageRootID"];
|
||||
[cloudResultDic setObject:simpleReply forKey:@"messageReply"];
|
||||
NSData *data = [TUITool dictionary2JsonData:cloudResultDic];
|
||||
if (data) {
|
||||
message.cloudCustomData = data;
|
||||
}
|
||||
|
||||
[self exitReplyAndReference:nil];
|
||||
|
||||
__weak typeof(self) weakSelf = self;
|
||||
self.modifyRootReplyMsgBlock = ^(TUIMessageCellData *cellData) {
|
||||
__strong typeof(self) strongSelf = weakSelf;
|
||||
[strongSelf modifyRootReplyMsgByID:messageRootID currentMsg:cellData];
|
||||
strongSelf.modifyRootReplyMsgBlock = nil;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
- (void)modifyRootReplyMsgByID:(NSString *)messageRootID currentMsg:(TUIMessageCellData *)messageCellData {
|
||||
NSDictionary *simpleCurrentContent = @{
|
||||
@"messageID" : messageCellData.innerMessage.msgID ?: @"",
|
||||
@"messageAbstract" : [messageCellData.innerMessage.textElem.text ?: @"" getInternationalStringWithfaceContent],
|
||||
@"messageSender" : messageCellData.innerMessage.sender ?: @"",
|
||||
@"messageType" : @(messageCellData.innerMessage.elemType),
|
||||
@"messageTime" : @(messageCellData.innerMessage.timestamp ? [messageCellData.innerMessage.timestamp timeIntervalSince1970] : 0),
|
||||
@"messageSequence" : @(messageCellData.innerMessage.seq),
|
||||
@"version" : @(kMessageReplyVersion)
|
||||
};
|
||||
if (messageRootID) {
|
||||
[TUIChatDataProvider findMessages:@[ messageRootID ]
|
||||
callback:^(BOOL succ, NSString *_Nonnull error_message, NSArray *_Nonnull msgs) {
|
||||
if (succ) {
|
||||
if (msgs.count > 0) {
|
||||
V2TIMMessage *rootMsg = msgs.firstObject;
|
||||
[[TUIChatModifyMessageHelper defaultHelper] modifyMessage:rootMsg
|
||||
simpleCurrentContent:simpleCurrentContent];
|
||||
}
|
||||
}
|
||||
}];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)appendReferenceDataIfNeeded:(V2TIMMessage *)message {
|
||||
if (self.referenceData) {
|
||||
NSDictionary *dict = @{
|
||||
@"messageReply" : @{
|
||||
@"messageID" : self.referenceData.msgID ?: @"",
|
||||
@"messageAbstract" : [self.referenceData.msgAbstract ?: @"" getInternationalStringWithfaceContent],
|
||||
@"messageSender" : self.referenceData.sender ?: @"",
|
||||
@"messageType" : @(self.referenceData.type),
|
||||
@"messageTime" : @(self.referenceData.originMessage.timestamp ? [self.referenceData.originMessage.timestamp timeIntervalSince1970] : 0),
|
||||
@"messageSequence" : @(self.referenceData.originMessage.seq),
|
||||
@"version" : @(kMessageReplyVersion)
|
||||
}
|
||||
};
|
||||
NSError *error = nil;
|
||||
NSData *data = [NSJSONSerialization dataWithJSONObject:dict options:0 error:&error];
|
||||
if (error == nil) {
|
||||
message.cloudCustomData = data;
|
||||
}
|
||||
[self exitReplyAndReference:nil];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)inputBar:(TUIInputBar_Minimalist *)textView didSendVoice:(NSString *)path {
|
||||
NSURL *url = [NSURL fileURLWithPath:path];
|
||||
AVURLAsset *audioAsset = [AVURLAsset URLAssetWithURL:url options:nil];
|
||||
float duration = (float)CMTimeGetSeconds(audioAsset.duration);
|
||||
int formatDuration = duration > 59 ? 60 : duration + 1 ;
|
||||
V2TIMMessage *message = [[V2TIMManager sharedInstance] createSoundMessage:path duration:formatDuration];
|
||||
if (message && _delegate && [_delegate respondsToSelector:@selector(inputController:didSendMessage:)]) {
|
||||
[_delegate inputController:self didSendMessage:message];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)inputBarDidInputAt:(TUIInputBar_Minimalist *)textView {
|
||||
if (_delegate && [_delegate respondsToSelector:@selector(inputControllerDidInputAt:)]) {
|
||||
[_delegate inputControllerDidInputAt:self];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)inputBar:(TUIInputBar_Minimalist *)textView didDeleteAt:(NSString *)atText {
|
||||
if (_delegate && [_delegate respondsToSelector:@selector(inputController:didDeleteAt:)]) {
|
||||
[_delegate inputController:self didDeleteAt:atText];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)inputBarDidDeleteBackward:(TUIInputBar_Minimalist *)textView {
|
||||
if (textView.inputTextView.text.length == 0) {
|
||||
[self exitReplyAndReference:nil];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)inputTextViewShouldBeginTyping:(UITextView *)textView {
|
||||
if (_delegate && [_delegate respondsToSelector:@selector(inputControllerBeginTyping:)]) {
|
||||
[_delegate inputControllerBeginTyping:self];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)inputTextViewShouldEndTyping:(UITextView *)textView {
|
||||
if (_delegate && [_delegate respondsToSelector:@selector(inputControllerEndTyping:)]) {
|
||||
[_delegate inputControllerEndTyping:self];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)reset {
|
||||
if (_status == Input_Status_Input) {
|
||||
return;
|
||||
} else if (_status == Input_Status_Input_Face) {
|
||||
[self hideFaceAnimation];
|
||||
}
|
||||
_status = Input_Status_Input;
|
||||
[_inputBar.inputTextView resignFirstResponder];
|
||||
if (_delegate && [_delegate respondsToSelector:@selector(inputController:didChangeHeight:)]) {
|
||||
CGFloat inputContainerBottom = [self getInputContainerBottom];
|
||||
[_delegate inputController:self didChangeHeight:inputContainerBottom + Bottom_SafeHeight];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)showReferencePreview:(TUIReferencePreviewData *)data {
|
||||
self.referenceData = data;
|
||||
[self.referencePreviewBar removeFromSuperview];
|
||||
[self.view addSubview:self.referencePreviewBar];
|
||||
self.inputBar.lineView.hidden = YES;
|
||||
|
||||
self.referencePreviewBar.previewReferenceData = data;
|
||||
|
||||
self.inputBar.mm_y = 0;
|
||||
|
||||
self.referencePreviewBar.frame = CGRectMake(0, 0, self.view.bounds.size.width, TMenuView_Menu_Height);
|
||||
self.referencePreviewBar.mm_y = CGRectGetMaxY(self.inputBar.frame);
|
||||
|
||||
// Set the default position to solve the UI confusion when the keyboard does not become the first responder
|
||||
if (self.delegate && [self.delegate respondsToSelector:@selector(inputController:didChangeHeight:)]) {
|
||||
[self.delegate inputController:self didChangeHeight:CGRectGetMaxY(self.inputBar.frame) + Bottom_SafeHeight + TMenuView_Menu_Height];
|
||||
}
|
||||
|
||||
if (self.status == Input_Status_Input_Keyboard) {
|
||||
CGFloat keyboradHeight = self.keyboardFrame.size.height;
|
||||
if (self.delegate && [self.delegate respondsToSelector:@selector(inputController:didChangeHeight:)]) {
|
||||
[self.delegate inputController:self didChangeHeight:CGRectGetMaxY(self.referencePreviewBar.frame) + keyboradHeight];
|
||||
}
|
||||
} else if (self.status == Input_Status_Input_Face) {
|
||||
[self.inputBar changeToKeyboard];
|
||||
} else {
|
||||
[self.inputBar.inputTextView becomeFirstResponder];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)showReplyPreview:(TUIReplyPreviewData *)data {
|
||||
self.replyData = data;
|
||||
[self.replyPreviewBar removeFromSuperview];
|
||||
[self.view addSubview:self.replyPreviewBar];
|
||||
self.inputBar.lineView.hidden = YES;
|
||||
|
||||
self.replyPreviewBar.previewData = data;
|
||||
|
||||
self.replyPreviewBar.frame = CGRectMake(0, 0, self.view.bounds.size.width, TMenuView_Menu_Height);
|
||||
self.inputBar.mm_y = CGRectGetMaxY(self.replyPreviewBar.frame);
|
||||
|
||||
// Set the default position to solve the UI confusion when the keyboard does not become the first responder
|
||||
if (self.delegate && [self.delegate respondsToSelector:@selector(inputController:didChangeHeight:)]) {
|
||||
[self.delegate inputController:self didChangeHeight:CGRectGetMaxY(self.inputBar.frame) + Bottom_SafeHeight];
|
||||
}
|
||||
|
||||
if (self.status == Input_Status_Input_Keyboard) {
|
||||
CGFloat keyboradHeight = self.keyboardFrame.size.height;
|
||||
if (self.delegate && [self.delegate respondsToSelector:@selector(inputController:didChangeHeight:)]) {
|
||||
[self.delegate inputController:self didChangeHeight:CGRectGetMaxY(self.inputBar.frame) + keyboradHeight];
|
||||
}
|
||||
} else if (self.status == Input_Status_Input_Face) {
|
||||
[self.inputBar changeToKeyboard];
|
||||
} else {
|
||||
[self.inputBar.inputTextView becomeFirstResponder];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)exitReplyAndReference:(void (^__nullable)(void))finishedCallback {
|
||||
if (self.replyData == nil && self.referenceData == nil) {
|
||||
if (finishedCallback) {
|
||||
finishedCallback();
|
||||
}
|
||||
return;
|
||||
}
|
||||
self.replyData = nil;
|
||||
self.referenceData = nil;
|
||||
__weak typeof(self) weakSelf = self;
|
||||
[UIView animateWithDuration:0.25
|
||||
animations:^{
|
||||
weakSelf.replyPreviewBar.hidden = YES;
|
||||
weakSelf.referencePreviewBar.hidden = YES;
|
||||
weakSelf.inputBar.mm_y = 0;
|
||||
|
||||
if (weakSelf.status == Input_Status_Input_Keyboard) {
|
||||
CGFloat keyboradHeight = weakSelf.keyboardFrame.size.height;
|
||||
if (weakSelf.delegate && [weakSelf.delegate respondsToSelector:@selector(inputController:didChangeHeight:)]) {
|
||||
[weakSelf.delegate inputController:weakSelf didChangeHeight:CGRectGetMaxY(weakSelf.inputBar.frame) + keyboradHeight];
|
||||
}
|
||||
} else {
|
||||
if (weakSelf.delegate && [weakSelf.delegate respondsToSelector:@selector(inputController:didChangeHeight:)]) {
|
||||
[weakSelf.delegate inputController:weakSelf didChangeHeight:CGRectGetMaxY(weakSelf.inputBar.frame) + Bottom_SafeHeight];
|
||||
}
|
||||
}
|
||||
}
|
||||
completion:^(BOOL finished) {
|
||||
[weakSelf.replyPreviewBar removeFromSuperview];
|
||||
[weakSelf.referencePreviewBar removeFromSuperview];
|
||||
weakSelf.replyPreviewBar = nil;
|
||||
weakSelf.referencePreviewBar = nil;
|
||||
[weakSelf hideFaceAnimation];
|
||||
weakSelf.inputBar.lineView.hidden = NO;
|
||||
if (finishedCallback) {
|
||||
finishedCallback();
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)menuView:(TUIMenuView_Minimalist *)menuView didSelectItemAtIndex:(NSInteger)index {
|
||||
[self.faceSegementScrollView setPageIndex:index];
|
||||
}
|
||||
|
||||
- (void)menuViewDidSendMessage:(TUIMenuView_Minimalist *)menuView {
|
||||
NSString *text = [_inputBar getInput];
|
||||
if ([text isEqualToString:@""]) {
|
||||
return;
|
||||
}
|
||||
/**
|
||||
* Emoticon internationalization --> restore to actual Chinese key
|
||||
*/
|
||||
NSString *content = [text getInternationalStringWithfaceContent];
|
||||
[_inputBar clearInput];
|
||||
V2TIMMessage *message = [[V2TIMManager sharedInstance] createTextMessage:content];
|
||||
[self appendReplyDataIfNeeded:message];
|
||||
[self appendReferenceDataIfNeeded:message];
|
||||
if (_delegate && [_delegate respondsToSelector:@selector(inputController:didSendMessage:)]) {
|
||||
[_delegate inputController:self didSendMessage:message];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)faceView:(TUIFaceView *)faceView scrollToFaceGroupIndex:(NSInteger)index {
|
||||
[self.menuView scrollToMenuIndex:index];
|
||||
}
|
||||
|
||||
- (void)faceViewDidBackDelete:(TUIFaceView *)faceView {
|
||||
[_inputBar backDelete];
|
||||
}
|
||||
- (void)faceViewClickSendMessageBtn {
|
||||
[self menuViewDidSendMessage:self.menuView];
|
||||
}
|
||||
|
||||
- (void)faceView:(TUIFaceView *)faceView didSelectItemAtIndexPath:(NSIndexPath *)indexPath {
|
||||
TUIFaceGroup *group = faceView.faceGroups[indexPath.section];
|
||||
TUIFaceCellData *face = group.faces[indexPath.row];
|
||||
if (group.isNeedAddInInputBar) {
|
||||
[_inputBar addEmoji:face];
|
||||
[self updateRecentMenuQueue:face.name];
|
||||
} else {
|
||||
if (face.name) {
|
||||
V2TIMMessage *message = [[V2TIMManager sharedInstance] createFaceMessage:group.groupIndex data:[face.name dataUsingEncoding:NSUTF8StringEncoding]];
|
||||
if (_delegate && [_delegate respondsToSelector:@selector(inputController:didSendMessage:)]) {
|
||||
[_delegate inputController:self didSendMessage:message];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
- (void)updateRecentMenuQueue:(NSString *)faceName {
|
||||
id<TUIEmojiMeditorProtocol> service = [[TIMCommonMediator share] getObject:@protocol(TUIEmojiMeditorProtocol)];
|
||||
return [service updateRecentMenuQueue:faceName];
|
||||
}
|
||||
|
||||
#pragma mark - lazy load
|
||||
- (TUIFaceSegementScrollView *)faceSegementScrollView {
|
||||
if(!_faceSegementScrollView) {
|
||||
_faceSegementScrollView = [[TUIFaceSegementScrollView alloc]
|
||||
initWithFrame:CGRectMake(0,
|
||||
_inputBar.frame.origin.y + _inputBar.frame.size.height, self.view.frame.size.width,
|
||||
TFaceView_Height)];
|
||||
[_faceSegementScrollView setItems:[TIMConfig defaultConfig].faceGroups delegate:self];
|
||||
}
|
||||
return _faceSegementScrollView;
|
||||
}
|
||||
|
||||
- (TUIMenuView_Minimalist *)menuView {
|
||||
if (!_menuView) {
|
||||
_menuView = [[TUIMenuView_Minimalist alloc] initWithFrame:CGRectMake(16, _inputBar.mm_maxY, self.view.frame.size.width - 32, TMenuView_Menu_Height)];
|
||||
_menuView.delegate = self;
|
||||
|
||||
TIMConfig *config = [TIMConfig defaultConfig];
|
||||
NSMutableArray *menus = [NSMutableArray array];
|
||||
for (NSInteger i = 0; i < config.faceGroups.count; ++i) {
|
||||
TUIFaceGroup *group = config.faceGroups[i];
|
||||
TUIMenuCellData *data = [[TUIMenuCellData alloc] init];
|
||||
data.path = group.menuPath;
|
||||
data.isSelected = NO;
|
||||
if (i == 0) {
|
||||
data.isSelected = YES;
|
||||
}
|
||||
[menus addObject:data];
|
||||
}
|
||||
[_menuView setData:menus];
|
||||
}
|
||||
return _menuView;
|
||||
}
|
||||
|
||||
- (TUIReplyPreviewBar_Minimalist *)replyPreviewBar {
|
||||
if (_replyPreviewBar == nil) {
|
||||
_replyPreviewBar = [[TUIReplyPreviewBar_Minimalist alloc] init];
|
||||
__weak typeof(self) weakSelf = self;
|
||||
_replyPreviewBar.onClose = ^{
|
||||
__strong typeof(weakSelf) strongSelf = weakSelf;
|
||||
[strongSelf exitReplyAndReference:nil];
|
||||
};
|
||||
}
|
||||
return _replyPreviewBar;
|
||||
}
|
||||
|
||||
- (TUIReferencePreviewBar_Minimalist *)referencePreviewBar {
|
||||
if (_referencePreviewBar == nil) {
|
||||
_referencePreviewBar = [[TUIReferencePreviewBar_Minimalist alloc] init];
|
||||
__weak typeof(self) weakSelf = self;
|
||||
_referencePreviewBar.onClose = ^{
|
||||
__strong typeof(weakSelf) strongSelf = weakSelf;
|
||||
[strongSelf exitReplyAndReference:nil];
|
||||
};
|
||||
}
|
||||
return _referencePreviewBar;
|
||||
}
|
||||
|
||||
- (CGFloat)getInputContainerBottom {
|
||||
CGFloat inputHeight = CGRectGetMaxY(_inputBar.frame);
|
||||
if (_referencePreviewBar) {
|
||||
inputHeight = CGRectGetMaxY(_referencePreviewBar.frame);
|
||||
}
|
||||
return inputHeight;
|
||||
}
|
||||
|
||||
@end
|
||||
62
TUIKit/TUIChat/UI_Minimalist/Input/TUIMenuView_Minimalist.h
Normal file
62
TUIKit/TUIChat/UI_Minimalist/Input/TUIMenuView_Minimalist.h
Normal file
@@ -0,0 +1,62 @@
|
||||
|
||||
// Created by Tencent on 2023/06/09.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
/**
|
||||
*
|
||||
* This file declares the components used to implement the emoji menu view.
|
||||
* The emoji menu view, the bright white view at the bottom of the emoji view, is responsible for displaying individual emoji groups and their thumbnails, and
|
||||
* providing a "Send" button.
|
||||
*
|
||||
* The TUIMenuViewDelegate protocol provides the emoticon menu view with event callbacks for sending messages and cell selection.
|
||||
* The TUIMenuView class, the "ontology" of the emoticon menu view, is responsible for displaying it in the form of a view in the UI, and at the same time
|
||||
* serving as a "container" for each component. You can switch between different groups of emoticons or send emoticons through the emoticon menu view.
|
||||
*/
|
||||
#import <UIKit/UIKit.h>
|
||||
#import "TUIMenuCellData.h"
|
||||
|
||||
@class TUIMenuView_Minimalist;
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// TUIMenuViewDelegate
|
||||
//
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@protocol TUIMenuViewDelegate_Minimalist <NSObject>
|
||||
|
||||
/**
|
||||
* Callback after clicking on a specific menuCell
|
||||
* You can use this callback to achieve: in response to the user's click, switch to the corresponding emoticon group view according to the menuCell selected by
|
||||
* the user.
|
||||
*/
|
||||
- (void)menuView:(TUIMenuView_Minimalist *)menuView didSelectItemAtIndex:(NSInteger)index;
|
||||
|
||||
/**
|
||||
* Callback after click of send button on menuView
|
||||
* You can send the content of the current input box (TUIInputBar) through this callback
|
||||
* In the default implementation of TUIKit, the delegate call chain is menuView -> inputController -> messageController.
|
||||
* Call the sendMessage function in the above classes respectively, so that the functions are reasonably layered and the code reuse rate is improved.
|
||||
*/
|
||||
- (void)menuViewDidSendMessage:(TUIMenuView_Minimalist *)menuView;
|
||||
|
||||
@end
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// TUIMenuView
|
||||
//
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
///
|
||||
@interface TUIMenuView_Minimalist : UIView
|
||||
|
||||
@property(nonatomic, strong) UICollectionView *menuCollectionView;
|
||||
|
||||
@property(nonatomic, strong) UICollectionViewFlowLayout *menuFlowLayout;
|
||||
|
||||
@property(nonatomic, weak) id<TUIMenuViewDelegate_Minimalist> delegate;
|
||||
|
||||
- (void)scrollToMenuIndex:(NSInteger)index;
|
||||
|
||||
- (void)setData:(NSMutableArray<TUIMenuCellData *> *)data;
|
||||
|
||||
@end
|
||||
122
TUIKit/TUIChat/UI_Minimalist/Input/TUIMenuView_Minimalist.m
Normal file
122
TUIKit/TUIChat/UI_Minimalist/Input/TUIMenuView_Minimalist.m
Normal file
@@ -0,0 +1,122 @@
|
||||
//
|
||||
// MenuView.m
|
||||
// UIKit
|
||||
//
|
||||
// Created by kennethmiao on 2018/9/18.
|
||||
// Copyright © 2018 Tencent. All rights reserved.
|
||||
//
|
||||
|
||||
#import "TUIMenuView_Minimalist.h"
|
||||
#import <TIMCommon/TIMDefine.h>
|
||||
#import <TUICore/TUIDarkModel.h>
|
||||
#import <TUICore/TUIGlobalization.h>
|
||||
#import <TUICore/TUIThemeManager.h>
|
||||
#import "TUIMenuCell_Minimalist.h"
|
||||
|
||||
@interface TUIMenuView_Minimalist () <UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout>
|
||||
@property(nonatomic, strong) NSMutableArray<TUIMenuCellData *> *data;
|
||||
@end
|
||||
|
||||
@implementation TUIMenuView_Minimalist
|
||||
- (id)initWithFrame:(CGRect)frame {
|
||||
self = [super initWithFrame:frame];
|
||||
if (self) {
|
||||
[self setupViews];
|
||||
[self defaultLayout];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)setData:(NSMutableArray<TUIMenuCellData *> *)data {
|
||||
_data = data;
|
||||
[_menuCollectionView reloadData];
|
||||
[self defaultLayout];
|
||||
[_menuCollectionView layoutIfNeeded];
|
||||
[_menuCollectionView selectItemAtIndexPath:[NSIndexPath indexPathForItem:0 inSection:0] animated:NO scrollPosition:UICollectionViewScrollPositionNone];
|
||||
}
|
||||
|
||||
- (void)setupViews {
|
||||
self.backgroundColor = [UIColor clearColor];
|
||||
_menuFlowLayout = [[TUICollectionRTLFitFlowLayout alloc] init];
|
||||
_menuFlowLayout.scrollDirection = UICollectionViewScrollDirectionHorizontal;
|
||||
_menuFlowLayout.minimumLineSpacing = 0;
|
||||
_menuFlowLayout.minimumInteritemSpacing = 0;
|
||||
//_menuFlowLayout.headerReferenceSize = CGSizeMake(TMenuView_Margin, 1);
|
||||
|
||||
_menuCollectionView = [[UICollectionView alloc] initWithFrame:CGRectZero collectionViewLayout:_menuFlowLayout];
|
||||
[_menuCollectionView registerClass:[TUIMenuCell_Minimalist class] forCellWithReuseIdentifier:TMenuCell_ReuseId];
|
||||
[_menuCollectionView registerClass:[UICollectionViewCell class] forCellWithReuseIdentifier:TMenuCell_Line_ReuseId];
|
||||
_menuCollectionView.collectionViewLayout = _menuFlowLayout;
|
||||
_menuCollectionView.delegate = self;
|
||||
_menuCollectionView.dataSource = self;
|
||||
_menuCollectionView.showsHorizontalScrollIndicator = NO;
|
||||
_menuCollectionView.showsVerticalScrollIndicator = NO;
|
||||
_menuCollectionView.backgroundColor = self.backgroundColor;
|
||||
_menuCollectionView.alwaysBounceHorizontal = YES;
|
||||
[self addSubview:_menuCollectionView];
|
||||
}
|
||||
|
||||
- (void)defaultLayout {
|
||||
[_menuCollectionView mas_remakeConstraints:^(MASConstraintMaker *make) {
|
||||
make.leading.mas_equalTo(0);
|
||||
make.trailing.mas_equalTo(self.mas_trailing).mas_offset(0);
|
||||
make.height.mas_equalTo(40);
|
||||
make.centerY.mas_equalTo(self);
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)sendUpInside:(UIButton *)sender {
|
||||
if (_delegate && [_delegate respondsToSelector:@selector(menuViewDidSendMessage:)]) {
|
||||
[_delegate menuViewDidSendMessage:self];
|
||||
}
|
||||
}
|
||||
|
||||
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
|
||||
return _data.count * 2;
|
||||
}
|
||||
|
||||
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
|
||||
if (indexPath.row % 2 == 0) {
|
||||
TUIMenuCell_Minimalist *cell = [collectionView dequeueReusableCellWithReuseIdentifier:TMenuCell_ReuseId forIndexPath:indexPath];
|
||||
[cell setData:_data[indexPath.row / 2]];
|
||||
return cell;
|
||||
} else {
|
||||
UICollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:TMenuCell_Line_ReuseId forIndexPath:indexPath];
|
||||
cell.backgroundColor = [UIColor clearColor];
|
||||
return cell;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath {
|
||||
if (indexPath.row % 2 != 0) {
|
||||
return;
|
||||
}
|
||||
for (NSInteger i = 0; i < _data.count; ++i) {
|
||||
TUIMenuCellData *data = _data[i];
|
||||
data.isSelected = (i == indexPath.row / 2);
|
||||
}
|
||||
[_menuCollectionView reloadData];
|
||||
if (_delegate && [_delegate respondsToSelector:@selector(menuView:didSelectItemAtIndex:)]) {
|
||||
[_delegate menuView:self didSelectItemAtIndex:indexPath.row / 2];
|
||||
}
|
||||
}
|
||||
|
||||
- (CGSize)collectionView:(UICollectionView *)collectionView
|
||||
layout:(UICollectionViewLayout *)collectionViewLayout
|
||||
sizeForItemAtIndexPath:(NSIndexPath *)indexPath {
|
||||
if (indexPath.row % 2 == 0) {
|
||||
CGFloat wh = collectionView.frame.size.height;
|
||||
return CGSizeMake(wh, wh);
|
||||
} else {
|
||||
return CGSizeMake(TLine_Heigh, collectionView.frame.size.height);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)scrollToMenuIndex:(NSInteger)index {
|
||||
for (NSInteger i = 0; i < _data.count; ++i) {
|
||||
TUIMenuCellData *data = _data[i];
|
||||
data.isSelected = (i == index);
|
||||
}
|
||||
[_menuCollectionView reloadData];
|
||||
}
|
||||
@end
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user