提交
This commit is contained in:
16
TUIKit/TUIChat/UI_Classic/Cell/Base/TUIGroupCreatedCell.h
Normal file
16
TUIKit/TUIChat/UI_Classic/Cell/Base/TUIGroupCreatedCell.h
Normal file
@@ -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 : TUISystemMessageCell
|
||||
|
||||
- (void)fillWithData:(TUIGroupCreatedCellData *)data;
|
||||
|
||||
@end
|
||||
17
TUIKit/TUIChat/UI_Classic/Cell/Base/TUIGroupCreatedCell.m
Normal file
17
TUIKit/TUIChat/UI_Classic/Cell/Base/TUIGroupCreatedCell.m
Normal file
@@ -0,0 +1,17 @@
|
||||
//
|
||||
// MyCustomCell.m
|
||||
// TUIKitDemo
|
||||
//
|
||||
// Created by annidyfeng on 2019/6/10.
|
||||
// Copyright © 2019 Tencent. All rights reserved.
|
||||
//
|
||||
|
||||
#import "TUIGroupCreatedCell.h"
|
||||
|
||||
@implementation TUIGroupCreatedCell
|
||||
|
||||
- (void)fillWithData:(TUIGroupCreatedCellData *)data {
|
||||
[super fillWithData:data];
|
||||
}
|
||||
|
||||
@end
|
||||
48
TUIKit/TUIChat/UI_Classic/Cell/Base/TUIInputMoreCell.h
Normal file
48
TUIKit/TUIChat/UI_Classic/Cell/Base/TUIInputMoreCell.h
Normal file
@@ -0,0 +1,48 @@
|
||||
|
||||
// Created by Tencent on 2023/06/09.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
/**
|
||||
*
|
||||
*
|
||||
* This document declares modules for implementing "more" units.
|
||||
* More units, that is, the UI interface that appears after clicking the "+" in the lower right corner of the chat interface.
|
||||
* At present, more units provide four multimedia sending functions of camera, video, picture, and file, and you can also customize it according to your needs.
|
||||
*/
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
#import "TUIInputMoreCellData.h"
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// TUIInputMoreCell
|
||||
//
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@interface TUIInputMoreCell : UICollectionViewCell
|
||||
|
||||
/**
|
||||
* The icons corresponding to more cells are obtained from the image of TUIInputMoreCellData.
|
||||
* The icons of each unit are different, which are used to visually represent the corresponding functions of the unit.
|
||||
*/
|
||||
@property(nonatomic, strong) UIImageView *image;
|
||||
|
||||
/**
|
||||
* The label corresponding to more cells, the text content of which is obtained from the title of TUIInputMoreCellData.
|
||||
* The names of each unit are different (such as camera, video, file, album, etc.), which are used to display the corresponding functions of the unit in text
|
||||
* form below the icon.
|
||||
*/
|
||||
@property(nonatomic, strong) UILabel *title;
|
||||
|
||||
@property(nonatomic, strong) TUIInputMoreCellData *data;
|
||||
|
||||
/**
|
||||
* Whether to disable the default selection behavior encapsulated in TUIKit, such as group live broadcast by default to create live room and other behaviors,
|
||||
* default: NO
|
||||
*/
|
||||
@property(nonatomic, assign) BOOL disableDefaultSelectAction;
|
||||
|
||||
- (void)fillWithData:(TUIInputMoreCellData *)data;
|
||||
|
||||
+ (CGSize)getSize;
|
||||
|
||||
@end
|
||||
67
TUIKit/TUIChat/UI_Classic/Cell/Base/TUIInputMoreCell.m
Normal file
67
TUIKit/TUIChat/UI_Classic/Cell/Base/TUIInputMoreCell.m
Normal file
@@ -0,0 +1,67 @@
|
||||
//
|
||||
// TMoreCell.m
|
||||
// UIKit
|
||||
//
|
||||
// Created by annidyfeng on 2019/5/22.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
//
|
||||
|
||||
#import "TUIInputMoreCell.h"
|
||||
#import <TIMCommon/TIMDefine.h>
|
||||
|
||||
@implementation TUIInputMoreCell
|
||||
|
||||
- (id)initWithFrame:(CGRect)frame {
|
||||
self = [super initWithFrame:frame];
|
||||
if (self) {
|
||||
[self setupViews];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)setupViews {
|
||||
_image = [[UIImageView alloc] init];
|
||||
_image.contentMode = UIViewContentModeScaleAspectFit;
|
||||
[self addSubview:_image];
|
||||
|
||||
_title = [[UILabel alloc] init];
|
||||
[_title setFont:[UIFont systemFontOfSize:10]];
|
||||
[_title setTextColor:[UIColor grayColor]];
|
||||
_title.textAlignment = NSTextAlignmentCenter;
|
||||
[self addSubview:_title];
|
||||
}
|
||||
|
||||
- (void)fillWithData:(TUIInputMoreCellData *)data {
|
||||
// set data
|
||||
_data = data;
|
||||
self.hidden = (data == nil) ? YES : NO;
|
||||
_image.image = data.image;
|
||||
[_title setText:data.title];
|
||||
// update layout
|
||||
CGSize menuSize = TMoreCell_Image_Size;
|
||||
_image.frame = CGRectMake(0, 0, menuSize.width, menuSize.height);
|
||||
_title.frame = CGRectMake(0, _image.frame.origin.y + _image.frame.size.height, _image.frame.size.width + 10, TMoreCell_Title_Height);
|
||||
_title.center = CGPointMake(_image.center.x, _title.center.y);
|
||||
}
|
||||
|
||||
+ (CGSize)getSize {
|
||||
CGSize menuSize = TMoreCell_Image_Size;
|
||||
return CGSizeMake(menuSize.width, menuSize.height + TMoreCell_Title_Height);
|
||||
}
|
||||
@end
|
||||
|
||||
@interface IUChatView : UIView
|
||||
@property(nonatomic, strong) UIView *view;
|
||||
@end
|
||||
|
||||
@implementation IUChatView
|
||||
|
||||
- (instancetype)init {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
self.view = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 10, 10)];
|
||||
[self addSubview:self.view];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
@end
|
||||
16
TUIKit/TUIChat/UI_Classic/Cell/Base/TUIMemberCell.h
Normal file
16
TUIKit/TUIChat/UI_Classic/Cell/Base/TUIMemberCell.h
Normal file
@@ -0,0 +1,16 @@
|
||||
|
||||
// Created by Tencent on 2023/06/09.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
|
||||
#import <TIMCommon/TIMCommonModel.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@class TUIMemberCellData;
|
||||
@interface TUIMemberCell : TUICommonTableViewCell
|
||||
|
||||
- (void)fillWithData:(TUIMemberCellData *)cellData;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
115
TUIKit/TUIChat/UI_Classic/Cell/Base/TUIMemberCell.m
Normal file
115
TUIKit/TUIChat/UI_Classic/Cell/Base/TUIMemberCell.m
Normal file
@@ -0,0 +1,115 @@
|
||||
//
|
||||
// TUIMemberCell.m
|
||||
// TUIChat
|
||||
//
|
||||
// Created by xia on 2022/3/11.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
//
|
||||
|
||||
#import "TUIMemberCell.h"
|
||||
#import <TIMCommon/TIMCommonModel.h>
|
||||
#import <TIMCommon/TIMDefine.h>
|
||||
#import <TUICore/TUIThemeManager.h>
|
||||
#import "TUIMemberCellData.h"
|
||||
|
||||
@interface TUIMemberCell ()
|
||||
|
||||
@property(nonatomic, strong) UIImageView *avatarView;
|
||||
@property(nonatomic, strong) UILabel *titleLabel;
|
||||
@property(nonatomic, strong) UILabel *detailLabel;
|
||||
@property(nonatomic, strong) TUIMemberCellData *cellData;
|
||||
|
||||
@end
|
||||
|
||||
@implementation TUIMemberCell
|
||||
|
||||
#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.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 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(34);
|
||||
|
||||
[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(12));
|
||||
}];
|
||||
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(12);
|
||||
make.height.mas_equalTo(20);
|
||||
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(-3);
|
||||
make.width.mas_equalTo(self.detailLabel.frame.size.width);
|
||||
}];
|
||||
|
||||
|
||||
}
|
||||
@end
|
||||
75
TUIKit/TUIChat/UI_Classic/Cell/Base/TUITextMessageCell.h
Normal file
75
TUIKit/TUIChat/UI_Classic/Cell/Base/TUITextMessageCell.h
Normal file
@@ -0,0 +1,75 @@
|
||||
|
||||
// Created by Tencent on 2023/06/09.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
#import <TIMCommon/TUIBubbleMessageCell.h>
|
||||
#import <TIMCommon/TUITextView.h>
|
||||
#import "TUIChatDefine.h"
|
||||
#import "TUITextMessageCellData.h"
|
||||
|
||||
@class TUITextView;
|
||||
|
||||
typedef void (^TUIChatSelectAllContentCallback)(BOOL);
|
||||
|
||||
@interface TUITextMessageCell : TUIBubbleMessageCell <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;
|
||||
|
||||
/// Data for text message cell.
|
||||
@property(nonatomic, strong) TUITextMessageCellData *textData;
|
||||
|
||||
@property(nonatomic, strong) UIImageView *voiceReadPoint;
|
||||
|
||||
- (void)fillWithData:(TUITextMessageCellData *)data;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@interface TUITextMessageCell (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;
|
||||
|
||||
+ (void)setMaxTextSize:(CGSize)maxTextSz;
|
||||
|
||||
@end
|
||||
339
TUIKit/TUIChat/UI_Classic/Cell/Base/TUITextMessageCell.m
Normal file
339
TUIKit/TUIChat/UI_Classic/Cell/Base/TUITextMessageCell.m
Normal file
@@ -0,0 +1,339 @@
|
||||
//
|
||||
// TUITextMessageCell.m
|
||||
// UIKit
|
||||
//
|
||||
// Created by annidyfeng on 2019/5/30.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
//
|
||||
|
||||
#import "TUITextMessageCell.h"
|
||||
#import <TIMCommon/TIMCommonModel.h>
|
||||
#import <TIMCommon/TIMDefine.h>
|
||||
#import <TUICore/TUICore.h>
|
||||
#import <TUICore/TUIGlobalization.h>
|
||||
#import "TUIFaceView.h"
|
||||
#import <TUICore/UIColor+TUIHexColor.h>
|
||||
|
||||
#ifndef CGFLOAT_CEIL
|
||||
#ifdef CGFLOAT_IS_DOUBLE
|
||||
#define CGFLOAT_CEIL(value) ceil(value)
|
||||
#else
|
||||
#define CGFLOAT_CEIL(value) ceilf(value)
|
||||
#endif
|
||||
#endif
|
||||
|
||||
@interface TUITextMessageCell ()<TUITextViewDelegate>
|
||||
@end
|
||||
|
||||
@implementation TUITextMessageCell
|
||||
|
||||
- (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.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_ClassicExtensionID 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;
|
||||
|
||||
// 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 mas_remakeConstraints:^(MASConstraintMaker *make) {
|
||||
make.leading.mas_equalTo(self.bubbleView.mas_leading).mas_offset(self.textData.textOrigin.x);
|
||||
make.top.mas_equalTo(self.bubbleView.mas_top).mas_offset(self.textData.textOrigin.y);
|
||||
make.size.mas_equalTo(self.textData.textSize);
|
||||
}];
|
||||
|
||||
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));
|
||||
}];
|
||||
}
|
||||
|
||||
BOOL hasRiskContent = self.messageData.innerMessage.hasRiskContent;
|
||||
if (hasRiskContent ) {
|
||||
[self.securityStrikeView mas_remakeConstraints:^(MASConstraintMaker *make) {
|
||||
make.top.mas_equalTo(self.textView.mas_bottom);
|
||||
make.width.mas_equalTo(self.bubbleView);
|
||||
make.bottom.mas_equalTo(self.container).mas_offset(- self.messageData.messageContainerAppendSize.height);
|
||||
}];
|
||||
}
|
||||
[self layoutBottomContainer];
|
||||
|
||||
}
|
||||
|
||||
- (void)layoutBottomContainer {
|
||||
if (CGSizeEqualToSize(self.textData.bottomContainerSize, CGSizeZero)) {
|
||||
return;
|
||||
}
|
||||
|
||||
CGSize size = self.textData.bottomContainerSize;
|
||||
|
||||
[self.bottomContainer mas_remakeConstraints:^(MASConstraintMaker *make) {
|
||||
if (self.textData.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(6);
|
||||
make.size.mas_equalTo(size);
|
||||
}];
|
||||
|
||||
CGFloat repliesBtnTextWidth = self.messageModifyRepliesButton.frame.size.width;
|
||||
if (!self.messageModifyRepliesButton.hidden) {
|
||||
[self.messageModifyRepliesButton mas_remakeConstraints:^(MASConstraintMaker *make) {
|
||||
if (self.textData.direction == MsgDirectionIncoming) {
|
||||
make.leading.mas_equalTo(self.container.mas_leading);
|
||||
} 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)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;
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:@"kTUIChatPopMenuWillHideNotification" object:nil];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - TUIMessageCellProtocol
|
||||
|
||||
+ (CGFloat)getEstimatedHeight:(TUIMessageCellData *)data {
|
||||
return 60.f;
|
||||
}
|
||||
|
||||
+ (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;
|
||||
}
|
||||
|
||||
static CGSize gMaxTextSize;
|
||||
+ (void)setMaxTextSize:(CGSize)maxTextSz {
|
||||
gMaxTextSize = maxTextSz;
|
||||
}
|
||||
+ (CGSize)getContentSize:(TUIMessageCellData *)data {
|
||||
NSAssert([data isKindOfClass:TUITextMessageCellData.class], @"data must be kind of TUITextMessageCellData");
|
||||
TUITextMessageCellData *textCellData = (TUITextMessageCellData *)data;
|
||||
|
||||
NSAttributedString *attributeString = [textCellData getContentAttributedString:
|
||||
data.direction == MsgDirectionIncoming ? self.incommingTextFont : self.outgoingTextFont];
|
||||
|
||||
if (CGSizeEqualToSize(gMaxTextSize, CGSizeZero)) {
|
||||
gMaxTextSize = CGSizeMake(TTextMessageCell_Text_Width_Max, MAXFLOAT);
|
||||
}
|
||||
CGSize contentSize = [textCellData getContentAttributedStringSize:attributeString maxTextSize:gMaxTextSize];
|
||||
textCellData.textSize = contentSize;
|
||||
|
||||
CGPoint textOrigin = CGPointMake(textCellData.cellLayout.bubbleInsets.left,
|
||||
textCellData.cellLayout.bubbleInsets.top);
|
||||
textCellData.textOrigin = textOrigin;
|
||||
|
||||
CGFloat height = contentSize.height;
|
||||
CGFloat width = contentSize.width;
|
||||
|
||||
height += textCellData.cellLayout.bubbleInsets.top;
|
||||
height += textCellData.cellLayout.bubbleInsets.bottom;
|
||||
|
||||
width += textCellData.cellLayout.bubbleInsets.left;
|
||||
width += textCellData.cellLayout.bubbleInsets.right;
|
||||
|
||||
if (textCellData.direction == MsgDirectionIncoming) {
|
||||
height = MAX(height, TUIBubbleMessageCell.incommingBubble.size.height);
|
||||
} else {
|
||||
height = MAX(height, TUIBubbleMessageCell.outgoingBubble.size.height);
|
||||
}
|
||||
|
||||
BOOL hasRiskContent = textCellData.innerMessage.hasRiskContent;
|
||||
if (hasRiskContent) {
|
||||
width = MAX(width, 200);// width must more than TIMCommonLocalizableString(TUIKitMessageTypeSecurityStrike)
|
||||
height += kTUISecurityStrikeViewTopLineMargin;
|
||||
height += kTUISecurityStrikeViewTopLineToBottom;
|
||||
}
|
||||
|
||||
CGSize size = CGSizeMake(width, height);
|
||||
return size;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@implementation TUITextMessageCell (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
|
||||
20
TUIKit/TUIChat/UI_Classic/Cell/Chat/TUIFaceMessageCell.h
Normal file
20
TUIKit/TUIChat/UI_Classic/Cell/Chat/TUIFaceMessageCell.h
Normal file
@@ -0,0 +1,20 @@
|
||||
|
||||
// Created by Tencent on 2023/06/09.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
|
||||
#import <TIMCommon/TUIBubbleMessageCell.h>
|
||||
#import <TIMCommon/TUIMessageCell.h>
|
||||
#import "TUIFaceMessageCellData.h"
|
||||
|
||||
@interface TUIFaceMessageCell : TUIBubbleMessageCell
|
||||
|
||||
/**
|
||||
*
|
||||
* Image view for the resource of emticon
|
||||
*/
|
||||
@property(nonatomic, strong) UIImageView *face;
|
||||
|
||||
@property TUIFaceMessageCellData *faceData;
|
||||
|
||||
- (void)fillWithData:(TUIFaceMessageCellData *)data;
|
||||
@end
|
||||
100
TUIKit/TUIChat/UI_Classic/Cell/Chat/TUIFaceMessageCell.m
Normal file
100
TUIKit/TUIChat/UI_Classic/Cell/Chat/TUIFaceMessageCell.m
Normal file
@@ -0,0 +1,100 @@
|
||||
//
|
||||
// FaceMessageCell.m
|
||||
// UIKit
|
||||
//
|
||||
// Created by annidyfeng on 2019/5/30.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
//
|
||||
|
||||
#import "TUIFaceMessageCell.h"
|
||||
#import <TIMCommon/TIMDefine.h>
|
||||
|
||||
@interface TUIFaceMessageCell ()
|
||||
@end
|
||||
|
||||
@implementation TUIFaceMessageCell
|
||||
|
||||
- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier {
|
||||
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
|
||||
if (self) {
|
||||
_face = [[UIImageView alloc] init];
|
||||
_face.contentMode = UIViewContentModeScaleAspectFit;
|
||||
[self.container addSubview:_face];
|
||||
_face.mm_fill();
|
||||
_face.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)layoutSubviews {
|
||||
[super layoutSubviews];
|
||||
}
|
||||
|
||||
+ (BOOL)requiresConstraintBasedLayout {
|
||||
return YES;
|
||||
}
|
||||
|
||||
// this is Apple's recommended place for adding/updating constraints
|
||||
- (void)updateConstraints {
|
||||
[super updateConstraints];
|
||||
|
||||
CGFloat topMargin = 0;
|
||||
CGFloat height = self.container.mm_h;
|
||||
if (self.messageData.messageContainerAppendSize.height > 0) {
|
||||
topMargin = 10;
|
||||
CGFloat tagViewTopPadding = 6;
|
||||
height = self.container.mm_h - topMargin - self.messageData.messageContainerAppendSize.height - tagViewTopPadding;
|
||||
self.bubbleView.hidden = NO;
|
||||
} else {
|
||||
self.bubbleView.hidden = YES;
|
||||
}
|
||||
[self.face mas_remakeConstraints:^(MASConstraintMaker *make) {
|
||||
make.height.mas_equalTo(height);
|
||||
make.centerX.mas_equalTo(self.container.mas_centerX);
|
||||
make.top.mas_equalTo(topMargin);
|
||||
make.width.mas_equalTo(self.container);
|
||||
}];
|
||||
|
||||
|
||||
}
|
||||
- (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;
|
||||
}
|
||||
return CGSizeMake(imageWidth, imageHeight);
|
||||
}
|
||||
|
||||
@end
|
||||
36
TUIKit/TUIChat/UI_Classic/Cell/Chat/TUIFileMessageCell.h
Normal file
36
TUIKit/TUIChat/UI_Classic/Cell/Chat/TUIFileMessageCell.h
Normal file
@@ -0,0 +1,36 @@
|
||||
|
||||
// Created by Tencent on 2023/06/09.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
#import <TIMCommon/TUIBubbleMessageCell.h>
|
||||
#import <TIMCommon/TUIMessageCell.h>
|
||||
#import "TUIFileMessageCellData.h"
|
||||
|
||||
@interface TUIFileMessageCell : TUIMessageCell
|
||||
|
||||
/**
|
||||
|
||||
* 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 *image;
|
||||
|
||||
@property TUIFileMessageCellData *fileData;
|
||||
|
||||
- (void)fillWithData:(TUIFileMessageCellData *)data;
|
||||
@end
|
||||
425
TUIKit/TUIChat/UI_Classic/Cell/Chat/TUIFileMessageCell.m
Normal file
425
TUIKit/TUIChat/UI_Classic/Cell/Chat/TUIFileMessageCell.m
Normal file
@@ -0,0 +1,425 @@
|
||||
//
|
||||
// TFileMessageCell.m
|
||||
// UIKit
|
||||
//
|
||||
// Created by annidyfeng on 2019/5/30.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
//
|
||||
|
||||
#import "TUIFileMessageCell.h"
|
||||
#import <TIMCommon/TIMDefine.h>
|
||||
#import <TUICore/TUIThemeManager.h>
|
||||
#import "ReactiveObjC/ReactiveObjC.h"
|
||||
#import "TUIMessageProgressManager.h"
|
||||
#import <TUICore/TUICore.h>
|
||||
|
||||
@interface TUIFileMessageCell () <V2TIMSDKListener, TUIMessageProgressManagerDelegate>
|
||||
|
||||
@property(nonatomic, strong) CAShapeLayer *maskLayer;
|
||||
@property(nonatomic, strong) CAShapeLayer *borderLayer;
|
||||
@property(nonatomic, strong) UIView *progressView;
|
||||
@property(nonatomic, strong) UIView *fileContainer;
|
||||
|
||||
@property(nonatomic, strong) UIView *animateHighlightView;
|
||||
|
||||
@end
|
||||
|
||||
@implementation TUIFileMessageCell
|
||||
|
||||
- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier {
|
||||
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
|
||||
if (self) {
|
||||
_bubble = [[UIImageView alloc] initWithFrame:self.container.bounds];
|
||||
[self.container addSubview:_bubble];
|
||||
_bubble.hidden = YES;
|
||||
_bubble.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
|
||||
|
||||
self.securityStrikeView = [[TUISecurityStrikeView alloc] init];
|
||||
[self.container addSubview:self.securityStrikeView];
|
||||
|
||||
[self.container addSubview:self.fileContainer];
|
||||
self.fileContainer.backgroundColor = TUIChatDynamicColor(@"chat_file_message_bg_color", @"#FFFFFF");
|
||||
[self.fileContainer addSubview:self.progressView];
|
||||
|
||||
_fileName = [[UILabel alloc] init];
|
||||
_fileName.font = [UIFont boldSystemFontOfSize:15];
|
||||
_fileName.textColor = TUIChatDynamicColor(@"chat_file_message_title_color", @"#000000");
|
||||
[self.fileContainer addSubview:_fileName];
|
||||
|
||||
_length = [[UILabel alloc] init];
|
||||
_length.font = [UIFont systemFontOfSize:12];
|
||||
_length.textColor = TUIChatDynamicColor(@"chat_file_message_subtitle_color", @"#888888");
|
||||
[self.fileContainer addSubview:_length];
|
||||
|
||||
_image = [[UIImageView alloc] init];
|
||||
_image.image = [[TUIImageCache sharedInstance] getResourceFromCache:TUIChatImagePath(@"msg_file_p")];
|
||||
_image.contentMode = UIViewContentModeScaleAspectFit;
|
||||
[self.fileContainer addSubview:_image];
|
||||
|
||||
[self.fileContainer.layer insertSublayer:self.borderLayer atIndex:0];
|
||||
[self.fileContainer.layer setMask:self.maskLayer];
|
||||
|
||||
[V2TIMManager.sharedInstance addIMSDKListener:self];
|
||||
[TUIMessageProgressManager.shareManager addDelegate:self];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)fillWithData:(TUIFileMessageCellData *)data {
|
||||
// set data
|
||||
[super fillWithData:data];
|
||||
self.fileData = data;
|
||||
_fileName.text = data.fileName;
|
||||
_length.text = [self formatLength:data.length];
|
||||
_image.image = [[TUIImageCache sharedInstance] getResourceFromCache:[self getImagePathByCurrentFileType:data.fileName.pathExtension]];
|
||||
@weakify(self);
|
||||
[self prepareReactTagUI:self.container];
|
||||
|
||||
self.securityStrikeView.hidden = YES;
|
||||
BOOL hasRiskContent = self.messageData.innerMessage.hasRiskContent;
|
||||
if (hasRiskContent) {
|
||||
self.bubble.image = [self getErrorBubble];
|
||||
self.securityStrikeView.hidden = NO;
|
||||
self.readReceiptLabel.hidden = YES;
|
||||
self.retryView.hidden = NO;
|
||||
}
|
||||
|
||||
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];
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
- (UIImage *)getErrorBubble {
|
||||
if (self.messageData.direction == MsgDirectionIncoming) {
|
||||
return TUIBubbleMessageCell.incommingErrorBubble;
|
||||
} else {
|
||||
return TUIBubbleMessageCell.outgoingErrorBubble;
|
||||
}
|
||||
}
|
||||
|
||||
#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 showProgressLodingAnimation:progress];
|
||||
}
|
||||
- (void)updateDownloadProgress:(int)progress {
|
||||
[self.indicator startAnimating];
|
||||
self.progressView.hidden = YES;
|
||||
self.length.text = [self formatLength:self.fileData.length];
|
||||
|
||||
if (progress >= 100 || progress == 0) {
|
||||
[self.indicator stopAnimating];
|
||||
return;
|
||||
}
|
||||
|
||||
[self showProgressLodingAnimation:progress];
|
||||
}
|
||||
- (void)showProgressLodingAnimation:(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.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.direction == MsgDirectionOutgoing) {
|
||||
if (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];
|
||||
}
|
||||
} else {
|
||||
if (!self.fileData.isLocalExist && !self.fileData.isDownloading) {
|
||||
str = [NSString stringWithFormat:@"%@ %@", str, TIMCommonLocalizableString(TUIKitNotDownload)];
|
||||
}
|
||||
}
|
||||
|
||||
return str;
|
||||
}
|
||||
- (NSString *)getImagePathByCurrentFileType:(NSString *)pathExtension {
|
||||
if (pathExtension.length > 0) {
|
||||
if ([pathExtension hasSuffix:@"ppt"] || [pathExtension hasSuffix:@"key"] || [pathExtension hasSuffix:@"pdf"]) {
|
||||
return TUIChatImagePath(@"msg_file_p");
|
||||
}
|
||||
}
|
||||
return TUIChatImagePath(@"msg_file");
|
||||
}
|
||||
|
||||
+ (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];
|
||||
CGSize fileContainerSize = [self.class getFileContentSize:self.fileData];
|
||||
[self.fileContainer mas_remakeConstraints:^(MASConstraintMaker *make) {
|
||||
make.center.mas_equalTo(self.container);
|
||||
make.size.mas_equalTo(fileContainerSize);
|
||||
}];
|
||||
|
||||
CGFloat imageHeight = fileContainerSize.height - 2 * TFileMessageCell_Margin;
|
||||
CGFloat imageWidth = imageHeight;
|
||||
[self.image mas_remakeConstraints:^(MASConstraintMaker *make) {
|
||||
make.leading.mas_equalTo(self.fileContainer.mas_leading).mas_offset(TFileMessageCell_Margin);
|
||||
make.top.mas_equalTo(self.fileContainer.mas_top).mas_offset(TFileMessageCell_Margin);
|
||||
make.size.mas_equalTo(CGSizeMake(imageWidth, imageHeight));
|
||||
}];
|
||||
|
||||
CGFloat textWidth = fileContainerSize.width - 2 * TFileMessageCell_Margin - imageWidth;
|
||||
CGSize nameSize = [_fileName sizeThatFits:fileContainerSize];
|
||||
|
||||
[self.fileName mas_remakeConstraints:^(MASConstraintMaker *make) {
|
||||
make.leading.mas_equalTo(self.image.mas_trailing).mas_offset(TFileMessageCell_Margin);
|
||||
make.top.mas_equalTo(self.image);
|
||||
make.size.mas_equalTo(CGSizeMake(textWidth, nameSize.height));
|
||||
}];
|
||||
|
||||
CGSize lengthSize = [_length sizeThatFits:fileContainerSize];
|
||||
[self.length mas_remakeConstraints:^(MASConstraintMaker *make) {
|
||||
make.leading.mas_equalTo(self.fileName);
|
||||
make.top.mas_equalTo(self.fileName.mas_bottom).mas_offset(TFileMessageCell_Margin * 0.5);
|
||||
make.size.mas_equalTo(CGSizeMake(textWidth, nameSize.height));
|
||||
}];
|
||||
|
||||
|
||||
if (self.messageData.messageContainerAppendSize.height > 0) {
|
||||
[self.fileContainer mas_remakeConstraints:^(MASConstraintMaker *make) {
|
||||
make.center.mas_equalTo(self.container);
|
||||
make.size.mas_equalTo(self.container);
|
||||
}];
|
||||
self.bubble.hidden = NO;
|
||||
}
|
||||
|
||||
self.maskLayer.frame = self.fileContainer.bounds;
|
||||
self.borderLayer.frame = self.fileContainer.bounds;
|
||||
|
||||
UIRectCorner corner = UIRectCornerBottomLeft | UIRectCornerBottomRight | UIRectCornerTopLeft;
|
||||
if (self.fileData.direction == MsgDirectionIncoming) {
|
||||
corner = UIRectCornerBottomLeft | UIRectCornerBottomRight | UIRectCornerTopRight;
|
||||
}
|
||||
UIBezierPath *bezierPath = [UIBezierPath bezierPathWithRoundedRect:self.fileContainer.bounds byRoundingCorners:corner cornerRadii:CGSizeMake(10, 10)];
|
||||
self.maskLayer.path = bezierPath.CGPath;
|
||||
self.borderLayer.path = bezierPath.CGPath;
|
||||
|
||||
BOOL hasRiskContent = self.messageData.innerMessage.hasRiskContent;
|
||||
if (hasRiskContent ) {
|
||||
|
||||
[self.fileContainer mas_remakeConstraints:^(MASConstraintMaker *make) {
|
||||
make.top.mas_equalTo(self.container).mas_offset(13);
|
||||
make.leading.mas_equalTo(12);
|
||||
make.size.mas_equalTo(fileContainerSize);
|
||||
}];
|
||||
[self.bubble mas_remakeConstraints:^(MASConstraintMaker *make) {
|
||||
make.leading.mas_equalTo(0);
|
||||
make.size.mas_equalTo(self.container);
|
||||
make.top.mas_equalTo(self.container);
|
||||
}];
|
||||
[self.securityStrikeView mas_remakeConstraints:^(MASConstraintMaker *make) {
|
||||
make.top.mas_equalTo(self.fileContainer.mas_bottom);
|
||||
make.width.mas_equalTo(self.container);
|
||||
make.bottom.mas_equalTo(self.container).mas_offset(-self.messageData.messageContainerAppendSize.height);
|
||||
}];
|
||||
self.bubble.hidden = NO;
|
||||
}
|
||||
else {
|
||||
self.bubble.hidden = YES;
|
||||
}
|
||||
|
||||
[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];
|
||||
}
|
||||
|
||||
- (CAShapeLayer *)maskLayer {
|
||||
if (_maskLayer == nil) {
|
||||
_maskLayer = [CAShapeLayer layer];
|
||||
}
|
||||
return _maskLayer;
|
||||
}
|
||||
|
||||
- (CAShapeLayer *)borderLayer {
|
||||
if (_borderLayer == nil) {
|
||||
_borderLayer = [CAShapeLayer layer];
|
||||
_borderLayer.lineWidth = 0.5f;
|
||||
_borderLayer.strokeColor = [UIColor colorWithRed:221 / 255.0 green:221 / 255.0 blue:221 / 255.0 alpha:1.0].CGColor;
|
||||
_borderLayer.fillColor = [UIColor clearColor].CGColor;
|
||||
}
|
||||
return _borderLayer;
|
||||
}
|
||||
|
||||
- (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 = TUIChatDynamicColor(@"chat_file_message_bg_color", @"#FFFFFF");
|
||||
}
|
||||
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.container.bounds;
|
||||
self.animateHighlightView.alpha = 0.1;
|
||||
[self.fileContainer 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];
|
||||
}
|
||||
return _animateHighlightView;
|
||||
}
|
||||
|
||||
- (void)prepareReactTagUI:(UIView *)containerView {
|
||||
NSDictionary *param = @{TUICore_TUIChatExtension_ChatMessageReactPreview_Delegate: self};
|
||||
[TUICore raiseExtension:TUICore_TUIChatExtension_ChatMessageReactPreview_ClassicExtensionID parentView:containerView param:param];
|
||||
}
|
||||
|
||||
#pragma mark - TUIMessageCellProtocol
|
||||
+ (CGSize)getFileContentSize:(TUIMessageCellData *)data {
|
||||
BOOL hasRiskContent = data.innerMessage.hasRiskContent;
|
||||
if (hasRiskContent) {
|
||||
return CGSizeMake(237, 62);
|
||||
}
|
||||
return TFileMessageCell_Container_Size;
|
||||
}
|
||||
+ (CGSize)getContentSize:(TUIMessageCellData *)data {
|
||||
CGSize size = [self.class getFileContentSize:data];
|
||||
BOOL hasRiskContent = data.innerMessage.hasRiskContent;
|
||||
if (hasRiskContent) {
|
||||
CGFloat bubbleTopMargin = 12;
|
||||
CGFloat bubbleBottomMargin = 12;
|
||||
size.width = MAX(size.width, 261);// width must more than TIMCommonLocalizableString(TUIKitMessageTypeSecurityStrike)
|
||||
size.height += bubbleTopMargin;
|
||||
size.height += kTUISecurityStrikeViewTopLineMargin;
|
||||
size.height += kTUISecurityStrikeViewTopLineToBottom;
|
||||
size.height += bubbleBottomMargin;
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
||||
@end
|
||||
18
TUIKit/TUIChat/UI_Classic/Cell/Chat/TUIImageCollectionCell.h
Normal file
18
TUIKit/TUIChat/UI_Classic/Cell/Chat/TUIImageCollectionCell.h
Normal file
@@ -0,0 +1,18 @@
|
||||
|
||||
// Created by Tencent on 2023/06/09.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
#import <UIKit/UIKit.h>
|
||||
#import "TUIImageMessageCellData.h"
|
||||
#import "TUIMediaCollectionCell.h"
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// TUIMediaImageCell
|
||||
//
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@interface TUIImageCollectionCell : TUIMediaCollectionCell
|
||||
|
||||
- (void)fillWithData:(TUIImageMessageCellData *)data;
|
||||
- (void)reloadAllView;
|
||||
@end
|
||||
423
TUIKit/TUIChat/UI_Classic/Cell/Chat/TUIImageCollectionCell.m
Normal file
423
TUIKit/TUIChat/UI_Classic/Cell/Chat/TUIImageCollectionCell.m
Normal file
@@ -0,0 +1,423 @@
|
||||
|
||||
// Created by Tencent on 2023/06/09.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
#import "TUIImageCollectionCell.h"
|
||||
#import <TIMCommon/TIMDefine.h>
|
||||
#import <TUICore/TUITool.h>
|
||||
#import "TUICircleLodingView.h"
|
||||
|
||||
@interface TUIImageCollectionCellScrollView : UIScrollView <UIScrollViewDelegate>
|
||||
@property(nonatomic, strong) UIView *containerView;
|
||||
@property(assign, nonatomic) CGFloat imageNormalWidth;
|
||||
@property(assign, nonatomic) CGFloat imageNormalHeight;
|
||||
- (void)pictureZoomWithScale:(CGFloat)zoomScale;
|
||||
|
||||
@end
|
||||
|
||||
@implementation TUIImageCollectionCellScrollView
|
||||
- (id)initWithFrame:(CGRect)frame {
|
||||
self = [super initWithFrame:frame];
|
||||
if (self) {
|
||||
self.delegate = self;
|
||||
self.minimumZoomScale = 0.1f;
|
||||
self.maximumZoomScale = 2.0f;
|
||||
_imageNormalHeight = frame.size.height;
|
||||
_imageNormalWidth = frame.size.width;
|
||||
self.containerView = [[UIView alloc] initWithFrame:frame];
|
||||
[self addSubview:self.containerView];
|
||||
if (@available(iOS 11.0, *)) {
|
||||
self.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever;
|
||||
} else {
|
||||
// Fallback on earlier versions
|
||||
}
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)drawRect:(CGRect)rect {
|
||||
[super drawRect:rect];
|
||||
}
|
||||
|
||||
#pragma mark-- Help Methods
|
||||
|
||||
- (void)pictureZoomWithScale:(CGFloat)zoomScale {
|
||||
CGFloat imageScaleWidth = zoomScale * self.imageNormalWidth;
|
||||
CGFloat imageScaleHeight = zoomScale * self.imageNormalHeight;
|
||||
CGFloat imageX = 0;
|
||||
CGFloat imageY = 0;
|
||||
if (imageScaleWidth < self.frame.size.width) {
|
||||
imageX = floorf((self.frame.size.width - imageScaleWidth) / 2.0);
|
||||
}
|
||||
if (imageScaleHeight < self.frame.size.height) {
|
||||
imageY = floorf((self.frame.size.height - imageScaleHeight) / 2.0);
|
||||
}
|
||||
self.containerView.frame = CGRectMake(imageX, imageY, imageScaleWidth, imageScaleHeight);
|
||||
self.contentSize = CGSizeMake(imageScaleWidth, imageScaleHeight);
|
||||
}
|
||||
|
||||
#pragma mark-- Setter
|
||||
|
||||
- (void)setImageNormalWidth:(CGFloat)imageNormalWidth {
|
||||
_imageNormalWidth = imageNormalWidth;
|
||||
self.containerView.frame = CGRectMake(0, 0, _imageNormalWidth, _imageNormalHeight);
|
||||
self.containerView.center = CGPointMake(self.frame.size.width / 2, self.frame.size.height / 2);
|
||||
}
|
||||
|
||||
- (void)setImageNormalHeight:(CGFloat)imageNormalHeight {
|
||||
_imageNormalHeight = imageNormalHeight;
|
||||
self.containerView.frame = CGRectMake(0, 0, _imageNormalWidth, _imageNormalHeight);
|
||||
self.containerView.center = CGPointMake(self.frame.size.width / 2, self.frame.size.height / 2);
|
||||
}
|
||||
|
||||
#pragma mark-- UIScrollViewDelegate
|
||||
|
||||
// Returns the view control that needs to be zoomed. During zooming
|
||||
- (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView {
|
||||
return self.containerView;
|
||||
}
|
||||
|
||||
// BeginZooming
|
||||
- (void)scrollViewWillBeginZooming:(UIScrollView *)scrollView withView:(UIView *)view {
|
||||
NSLog(@"BeginZooming");
|
||||
}
|
||||
// EndZooming
|
||||
- (void)scrollViewDidEndZooming:(UIScrollView *)scrollView withView:(UIView *)view atScale:(CGFloat)scale {
|
||||
NSLog(@"EndZooming");
|
||||
}
|
||||
|
||||
// zoom
|
||||
- (void)scrollViewDidZoom:(UIScrollView *)scrollView {
|
||||
CGFloat imageScaleWidth = scrollView.zoomScale * self.imageNormalWidth;
|
||||
CGFloat imageScaleHeight = scrollView.zoomScale * self.imageNormalHeight;
|
||||
CGFloat imageX = 0;
|
||||
CGFloat imageY = 0;
|
||||
if (imageScaleWidth < self.frame.size.width) {
|
||||
imageX = floorf((self.frame.size.width - imageScaleWidth) / 2.0);
|
||||
}
|
||||
if (imageScaleHeight < self.frame.size.height) {
|
||||
imageY = floorf((self.frame.size.height - imageScaleHeight) / 2.0);
|
||||
}
|
||||
self.containerView.frame = CGRectMake(imageX, imageY, imageScaleWidth, imageScaleHeight);
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@interface TUIImageCollectionCell ()
|
||||
@property(nonatomic, strong) TUIImageCollectionCellScrollView *scrollView;
|
||||
@property(nonatomic, strong) TUIImageMessageCellData *imgCellData;
|
||||
@property(nonatomic, strong) UIButton *mainDownloadBtn;
|
||||
@property(nonatomic, strong) TUICircleLodingView *animateCircleView;
|
||||
|
||||
@end
|
||||
|
||||
@implementation TUIImageCollectionCell
|
||||
- (id)initWithFrame:(CGRect)frame {
|
||||
self = [super initWithFrame:frame];
|
||||
if (self) {
|
||||
[self setupViews];
|
||||
[self setupRotaionNotifications];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)setupViews {
|
||||
self.scrollView = [[TUIImageCollectionCellScrollView alloc] initWithFrame:CGRectMake(0, 0, self.bounds.size.width, self.bounds.size.height)];
|
||||
[self addSubview:self.scrollView];
|
||||
|
||||
self.imageView = [[UIImageView alloc] init];
|
||||
self.imageView.layer.cornerRadius = 5.0;
|
||||
[self.imageView.layer setMasksToBounds:YES];
|
||||
self.imageView.contentMode = UIViewContentModeScaleAspectFill;
|
||||
self.imageView.backgroundColor = [UIColor clearColor];
|
||||
[self.scrollView.containerView addSubview:self.imageView];
|
||||
self.imageView.mm_fill();
|
||||
self.imageView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
|
||||
|
||||
self.mainDownloadBtn = [UIButton buttonWithType:UIButtonTypeCustom];
|
||||
self.mainDownloadBtn.contentMode = UIViewContentModeScaleToFill;
|
||||
[self.mainDownloadBtn setTitle:TIMCommonLocalizableString(TUIKitImageViewOrigin) forState:UIControlStateNormal];
|
||||
self.mainDownloadBtn.backgroundColor = [UIColor grayColor];
|
||||
[self.mainDownloadBtn.titleLabel setFont:[UIFont systemFontOfSize:14]];
|
||||
self.mainDownloadBtn.layer.borderColor = [UIColor whiteColor].CGColor;
|
||||
self.mainDownloadBtn.layer.cornerRadius = .16;
|
||||
self.mainDownloadBtn.hidden = YES;
|
||||
[self.mainDownloadBtn addTarget:self action:@selector(mainDownloadBtnClick) forControlEvents:UIControlEventTouchUpInside];
|
||||
[self addSubview:self.mainDownloadBtn];
|
||||
|
||||
self.downloadBtn = [UIButton buttonWithType:UIButtonTypeCustom];
|
||||
self.downloadBtn.contentMode = UIViewContentModeScaleToFill;
|
||||
[self.downloadBtn setImage:TUIChatCommonBundleImage(@"download") forState:UIControlStateNormal];
|
||||
[self.downloadBtn addTarget:self action:@selector(onSaveBtnClick) forControlEvents:UIControlEventTouchUpInside];
|
||||
[self addSubview:self.downloadBtn];
|
||||
|
||||
self.animateCircleView = [[TUICircleLodingView alloc] initWithFrame:CGRectMake(0, 0, kScale390(40), kScale390(40))];
|
||||
self.animateCircleView.hidden = YES;
|
||||
self.animateCircleView.progress = 0;
|
||||
[self addSubview:_animateCircleView];
|
||||
|
||||
self.backgroundColor = [UIColor clearColor];
|
||||
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(onSelectMedia)];
|
||||
[self addGestureRecognizer:tap];
|
||||
}
|
||||
|
||||
- (void)setupRotaionNotifications {
|
||||
if (@available(iOS 16.0, *)) {
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self
|
||||
selector:@selector(onDeviceOrientationChange:)
|
||||
name:TUIMessageMediaViewDeviceOrientationChangeNotification
|
||||
object:nil];
|
||||
} else {
|
||||
[[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications];
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self
|
||||
selector:@selector(onDeviceOrientationChange:)
|
||||
name:UIDeviceOrientationDidChangeNotification
|
||||
object:nil];
|
||||
}
|
||||
}
|
||||
- (void)mainDownloadBtnClick {
|
||||
if (self.imgCellData.originImage == nil) {
|
||||
[self.imgCellData downloadImage:TImage_Type_Origin];
|
||||
}
|
||||
}
|
||||
- (void)onSaveBtnClick {
|
||||
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.imgCellData = data;
|
||||
self.imageView.image = nil;
|
||||
|
||||
BOOL hasRiskContent = data.innerMessage.hasRiskContent;
|
||||
if (hasRiskContent ) {
|
||||
self.imageView.image = TIMCommonBundleThemeImage(@"", @"icon_security_strike");
|
||||
for (UIView *subview in self.subviews) {
|
||||
if (subview != self.scrollView ){
|
||||
subview.hidden = YES;
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
//1.Read from cache
|
||||
if ([self originImageFirst:data]) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ([self largeImageSecond:data]) {
|
||||
return;
|
||||
}
|
||||
|
||||
//2. download image
|
||||
if (data.thumbImage == nil) {
|
||||
[data downloadImage:TImage_Type_Thumb];
|
||||
}
|
||||
if (data.thumbImage && data.largeImage == nil) {
|
||||
self.animateCircleView.hidden = NO;
|
||||
[data downloadImage:TImage_Type_Large];
|
||||
}
|
||||
|
||||
[self fillThumbImageWithData:data];
|
||||
[self fillLargeImageWithData:data];
|
||||
[self fillOriginImageWithData:data];
|
||||
}
|
||||
|
||||
- (BOOL)largeImageSecond:(TUIImageMessageCellData *)data {
|
||||
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 fillOriginImageWithData:data];
|
||||
}
|
||||
return isExist;
|
||||
}
|
||||
|
||||
- (void)fillOriginImageWithData:(TUIImageMessageCellData *)data{
|
||||
@weakify(self);
|
||||
// originImage
|
||||
[[RACObserve(data, originImage) takeUntil:self.rac_prepareForReuseSignal] subscribeNext:^(UIImage *originImage) {
|
||||
@strongify(self);
|
||||
if (originImage) {
|
||||
self.imageView.image = originImage;
|
||||
[self setNeedsLayout];
|
||||
}
|
||||
}];
|
||||
[[[RACObserve(data, originProgress) takeUntil:self.rac_prepareForReuseSignal] distinctUntilChanged] subscribeNext:^(NSNumber *x) {
|
||||
@strongify(self);
|
||||
int progress = [x intValue];
|
||||
if (progress == 100) {
|
||||
self.animateCircleView.progress = 99;
|
||||
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
|
||||
self.animateCircleView.progress = 100;
|
||||
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
|
||||
self.animateCircleView.progress = 0;
|
||||
self.mainDownloadBtn.hidden = YES;
|
||||
self.animateCircleView.hidden = YES;
|
||||
[self.mainDownloadBtn setTitle:TIMCommonLocalizableString(TUIKitImageViewOrigin) forState:UIControlStateNormal];
|
||||
});
|
||||
});
|
||||
} else if (progress > 1 && progress < 100) {
|
||||
self.animateCircleView.progress = progress;
|
||||
[self.mainDownloadBtn setTitle:[NSString stringWithFormat:@"%d%%", progress] forState:UIControlStateNormal];
|
||||
self.animateCircleView.hidden = YES;
|
||||
} else {
|
||||
self.animateCircleView.progress = progress;
|
||||
}
|
||||
}];
|
||||
}
|
||||
- (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;
|
||||
[self setNeedsLayout];
|
||||
}
|
||||
}];
|
||||
[[[RACObserve(data, largeProgress) takeUntil:self.rac_prepareForReuseSignal] distinctUntilChanged] subscribeNext:^(NSNumber *x) {
|
||||
@strongify(self);
|
||||
int progress = [x intValue];
|
||||
if (progress == 100) {
|
||||
self.animateCircleView.progress = 99;
|
||||
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
|
||||
self.animateCircleView.progress = 100;
|
||||
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
|
||||
self.animateCircleView.progress = 0;
|
||||
self.mainDownloadBtn.hidden = NO;
|
||||
self.animateCircleView.hidden = YES;
|
||||
});
|
||||
});
|
||||
} else if (progress > 1 && progress < 100) {
|
||||
self.animateCircleView.progress = progress;
|
||||
self.mainDownloadBtn.hidden = YES;
|
||||
self.animateCircleView.hidden = NO;
|
||||
} else {
|
||||
self.animateCircleView.progress = progress;
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)fillThumbImageWithData:(TUIImageMessageCellData *)data {
|
||||
@weakify(self);
|
||||
[[RACObserve(data, thumbImage) takeUntil:self.rac_prepareForReuseSignal] subscribeNext:^(UIImage *thumbImage) {
|
||||
@strongify(self);
|
||||
if (thumbImage) {
|
||||
self.imageView.image = thumbImage;
|
||||
[self setNeedsLayout];
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)layoutSubviews {
|
||||
[super layoutSubviews];
|
||||
[self.mainDownloadBtn sizeToFit];
|
||||
self.mainDownloadBtn.mm_width(self.mainDownloadBtn.mm_w + 10).mm_height(self.mainDownloadBtn.mm_h).mm__centerX(self.mm_w / 2).mm_bottom(48);
|
||||
self.mainDownloadBtn.layer.cornerRadius = (self.mainDownloadBtn.mm_h * 0.5);
|
||||
self.animateCircleView.tui_mm_center();
|
||||
self.downloadBtn.mm_width(31).mm_height(31).mm_right(16).mm_bottom(48);
|
||||
self.scrollView.mm_width(self.mm_w).mm_height(self.mm_h).mm__centerX(self.mm_w / 2).mm__centerY(self.mm_h / 2);
|
||||
self.scrollView.imageNormalWidth = self.imageView.image.size.width;
|
||||
self.scrollView.imageNormalHeight = self.imageView.image.size.height;
|
||||
self.imageView.frame = CGRectMake(self.scrollView.bounds.origin.x,
|
||||
self.scrollView.bounds.origin.y,
|
||||
self.imageView.image.size.width,
|
||||
self.imageView.image.size.height);
|
||||
|
||||
[self.imageView layoutIfNeeded];
|
||||
|
||||
[self adjustScale];
|
||||
}
|
||||
|
||||
- (void)onDeviceOrientationChange:(NSNotification *)noti {
|
||||
[self reloadAllView];
|
||||
}
|
||||
- (void)reloadAllView {
|
||||
for (UIView *subview in self.subviews) {
|
||||
if (subview) {
|
||||
[UIView animateWithDuration:0.1 animations:^{
|
||||
[subview removeFromSuperview];
|
||||
}];
|
||||
}
|
||||
}
|
||||
[self setupViews];
|
||||
[self fillWithData:self.imgCellData];
|
||||
|
||||
}
|
||||
|
||||
#pragma mark - V2TIMAdvancedMsgListener
|
||||
- (void)onRecvMessageModified:(V2TIMMessage *)msg {
|
||||
V2TIMMessage *imMsg = msg;
|
||||
if (imMsg == nil || ![imMsg isKindOfClass:V2TIMMessage.class]) {
|
||||
return;
|
||||
}
|
||||
if ([self.imgCellData.innerMessage.msgID isEqualToString:imMsg.msgID]) {
|
||||
BOOL hasRiskContent = imMsg.hasRiskContent;
|
||||
if (hasRiskContent) {
|
||||
self.imgCellData.innerMessage = imMsg;
|
||||
[self showRiskAlert];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)showRiskAlert {
|
||||
UIAlertController *ac = [UIAlertController alertControllerWithTitle:nil
|
||||
message:TIMCommonLocalizableString(TUIKitPictureCheckRisk)
|
||||
preferredStyle:UIAlertControllerStyleAlert];
|
||||
__weak typeof(self) weakSelf = self;
|
||||
[ac tuitheme_addAction:[UIAlertAction actionWithTitle:TIMCommonLocalizableString(TUIKitVideoCheckRiskCancel)
|
||||
style:UIAlertActionStyleCancel
|
||||
handler:^(UIAlertAction *_Nonnull action) {
|
||||
__strong typeof(weakSelf) strongSelf = weakSelf;
|
||||
[strongSelf reloadAllView];
|
||||
}]];
|
||||
|
||||
[[UIApplication sharedApplication].keyWindow.rootViewController presentViewController:ac animated:YES completion:nil];
|
||||
}
|
||||
|
||||
- (void)adjustScale {
|
||||
CGFloat scale = 1;
|
||||
if (Screen_Width > self.imageView.image.size.width) {
|
||||
scale = 1;
|
||||
CGFloat scaleHeight = Screen_Height/ self.imageView.image.size.height;
|
||||
scale = MIN(scale, scaleHeight);
|
||||
}
|
||||
else {
|
||||
scale = Screen_Width/ self.imageView.image.size.width;
|
||||
CGFloat scaleHeight = Screen_Height/ self.imageView.image.size.height;
|
||||
scale = MIN(scale, scaleHeight);
|
||||
}
|
||||
self.scrollView.containerView.frame = CGRectMake(0, 0,MIN(Screen_Width,self.imageView.image.size.width), self.imageView.image.size.height);
|
||||
[self.scrollView pictureZoomWithScale:scale];
|
||||
}
|
||||
@end
|
||||
17
TUIKit/TUIChat/UI_Classic/Cell/Chat/TUIImageMessageCell.h
Normal file
17
TUIKit/TUIChat/UI_Classic/Cell/Chat/TUIImageMessageCell.h
Normal file
@@ -0,0 +1,17 @@
|
||||
|
||||
// Created by Tencent on 2023/06/09.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
#import <TIMCommon/TUIBubbleMessageCell.h>
|
||||
#import <TIMCommon/TUIMessageCell.h>
|
||||
#import "TUIImageMessageCellData.h"
|
||||
|
||||
@interface TUIImageMessageCell : TUIBubbleMessageCell
|
||||
|
||||
@property(nonatomic, strong) UIImageView *thumb;
|
||||
|
||||
@property(nonatomic, strong) UILabel *progress;
|
||||
|
||||
@property TUIImageMessageCellData *imageData;
|
||||
|
||||
- (void)fillWithData:(TUIImageMessageCellData *)data;
|
||||
@end
|
||||
300
TUIKit/TUIChat/UI_Classic/Cell/Chat/TUIImageMessageCell.m
Normal file
300
TUIKit/TUIChat/UI_Classic/Cell/Chat/TUIImageMessageCell.m
Normal file
@@ -0,0 +1,300 @@
|
||||
//
|
||||
// TUIImageMessageCell.m
|
||||
// UIKit
|
||||
//
|
||||
// Created by annidyfeng on 2019/5/30.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
//
|
||||
|
||||
#import "TUIImageMessageCell.h"
|
||||
#import <TIMCommon/TIMDefine.h>
|
||||
|
||||
@interface TUIImageMessageCell ()
|
||||
|
||||
@property(nonatomic, strong) UIView *animateHighlightView;
|
||||
|
||||
@end
|
||||
|
||||
@implementation TUIImageMessageCell
|
||||
|
||||
- (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 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.securityStrikeView.textLabel.text = TIMCommonLocalizableString(TUIKitMessageTypeSecurityStrikeImage);
|
||||
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;
|
||||
}
|
||||
}];
|
||||
|
||||
[[[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];
|
||||
|
||||
}
|
||||
|
||||
+ (BOOL)requiresConstraintBasedLayout {
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (void)makeConstraints {
|
||||
[self.thumb mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.height.mas_equalTo(self.bubbleView);
|
||||
make.width.mas_equalTo(self.bubbleView);
|
||||
make.top.mas_equalTo(self.bubbleView);
|
||||
make.leading.mas_equalTo(self.bubbleView);
|
||||
}];
|
||||
|
||||
[self.progress mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.edges.mas_equalTo(self.bubbleView);
|
||||
}];
|
||||
}
|
||||
// this is Apple's recommended place for adding/updating constraints
|
||||
- (void)updateConstraints {
|
||||
|
||||
[super updateConstraints];
|
||||
|
||||
if (self.imageData.isSuperLongImage) {
|
||||
self.thumb.contentMode = UIViewContentModeScaleToFill;
|
||||
}
|
||||
else {
|
||||
self.thumb.contentMode = UIViewContentModeScaleAspectFit;
|
||||
}
|
||||
|
||||
CGFloat topMargin = 0;
|
||||
CGFloat height = self.bubbleView.mm_h;
|
||||
if (self.messageData.messageContainerAppendSize.height > 0) {
|
||||
topMargin = 10;
|
||||
CGFloat tagViewTopMargin = 6;
|
||||
height = self.bubbleView.mm_h - topMargin - self.messageData.messageContainerAppendSize.height - tagViewTopMargin;
|
||||
[self.thumb mas_remakeConstraints:^(MASConstraintMaker *make) {
|
||||
make.height.mas_equalTo(height);
|
||||
make.width.mas_equalTo(self.bubbleView.mas_width);
|
||||
make.top.mas_equalTo(self.bubbleView).mas_offset(topMargin);
|
||||
make.leading.mas_equalTo(self.bubbleView);
|
||||
}];
|
||||
} else {
|
||||
[self.thumb mas_remakeConstraints:^(MASConstraintMaker *make) {
|
||||
make.top.mas_equalTo(self.bubbleView).mas_offset(self.messageData.cellLayout.bubbleInsets.top);
|
||||
make.bottom.mas_equalTo(self.bubbleView).mas_offset(-self.messageData.cellLayout.bubbleInsets.bottom);
|
||||
make.leading.mas_equalTo(self.bubbleView).mas_offset(self.messageData.cellLayout.bubbleInsets.left);
|
||||
make.trailing.mas_equalTo(self.bubbleView).mas_offset(-self.messageData.cellLayout.bubbleInsets.right);
|
||||
}];
|
||||
}
|
||||
BOOL hasRiskContent = self.messageData.innerMessage.hasRiskContent;
|
||||
if (hasRiskContent ) {
|
||||
[self.thumb mas_remakeConstraints:^(MASConstraintMaker *make) {
|
||||
make.top.mas_equalTo(self.bubbleView).mas_offset(12);
|
||||
make.size.mas_equalTo(CGSizeMake(150, 150));
|
||||
make.centerX.mas_equalTo(self.bubbleView);
|
||||
}];
|
||||
|
||||
[self.securityStrikeView mas_remakeConstraints:^(MASConstraintMaker *make) {
|
||||
make.top.mas_equalTo(self.thumb.mas_bottom);
|
||||
make.width.mas_equalTo(self.bubbleView);
|
||||
make.bottom.mas_equalTo(self.container).mas_offset(- self.messageData.messageContainerAppendSize.height);
|
||||
}];
|
||||
}
|
||||
|
||||
[self.progress mas_remakeConstraints:^(MASConstraintMaker *make) {
|
||||
make.edges.mas_equalTo(self.bubbleView);
|
||||
}];
|
||||
[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);
|
||||
}
|
||||
}];
|
||||
|
||||
[self.timeLabel sizeToFit];
|
||||
[self.timeLabel mas_remakeConstraints:^(MASConstraintMaker *make) {
|
||||
make.trailing.mas_equalTo(self.contentView.mas_trailing).mas_offset(-10);
|
||||
make.top.mas_equalTo(self.avatarView);
|
||||
if (self.messageData.showMessageTime) {
|
||||
make.width.mas_equalTo(self.timeLabel.frame.size.width);
|
||||
make.height.mas_equalTo(self.timeLabel.frame.size.height);
|
||||
} else {
|
||||
make.width.mas_equalTo(0);
|
||||
make.height.mas_equalTo(0);
|
||||
}
|
||||
}];
|
||||
|
||||
}
|
||||
|
||||
- (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 186.f;
|
||||
}
|
||||
|
||||
+ (CGSize)getContentSize:(TUIMessageCellData *)data {
|
||||
NSAssert([data isKindOfClass:TUIImageMessageCellData.class], @"data must be kind of TUIImageMessageCellData");
|
||||
TUIImageMessageCellData *imageCellData = (TUIImageMessageCellData *)data;
|
||||
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_Thumb) {
|
||||
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_Origin) {
|
||||
size = item.size;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (CGSizeEqualToSize(size, CGSizeZero)) {
|
||||
return size;
|
||||
}
|
||||
if (size.height > size.width) {
|
||||
if (size.height > 5* size.width) {
|
||||
size.width = TImageMessageCell_Image_Width_Max;
|
||||
size.height = TImageMessageCell_Image_Height_Max;
|
||||
imageCellData.isSuperLongImage = YES;
|
||||
}
|
||||
else {
|
||||
size.width = size.width / size.height * TImageMessageCell_Image_Height_Max;
|
||||
size.height = TImageMessageCell_Image_Height_Max;
|
||||
}
|
||||
} else {
|
||||
size.height = size.height / size.width * TImageMessageCell_Image_Width_Max;
|
||||
size.width = TImageMessageCell_Image_Width_Max;
|
||||
}
|
||||
|
||||
BOOL hasRiskContent = imageCellData.innerMessage.hasRiskContent;
|
||||
if (hasRiskContent) {
|
||||
CGFloat bubbleTopMargin = 12;
|
||||
CGFloat bubbleBottomMargin = 12;
|
||||
size.height = MAX(size.height, 150);// width must more than TIMCommonBundleThemeImage(@"", @"icon_security_strike");
|
||||
size.width = MAX(size.width, 200);// width must more than TIMCommonLocalizableString(TUIKitMessageTypeSecurityStrike)
|
||||
size.height += bubbleTopMargin;
|
||||
size.height += kTUISecurityStrikeViewTopLineMargin;
|
||||
size.height += kTUISecurityStrikeViewTopLineToBottom;
|
||||
size.height += bubbleBottomMargin;
|
||||
}
|
||||
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;
|
||||
|
||||
@protocol TUIJoinGroupMessageCellDelegate <NSObject>
|
||||
|
||||
@optional
|
||||
|
||||
- (void)didTapOnNameLabel:(TUIJoinGroupMessageCell *)cell;
|
||||
|
||||
- (void)didTapOnSecondNameLabel:(TUIJoinGroupMessageCell *)cell;
|
||||
|
||||
- (void)didTapOnRestNameLabel:(TUIJoinGroupMessageCell *)cell withIndex:(NSInteger)index;
|
||||
|
||||
@end
|
||||
|
||||
@interface TUIJoinGroupMessageCell : TUISystemMessageCell
|
||||
|
||||
@property TUIJoinGroupMessageCellData *joinData;
|
||||
|
||||
@property(nonatomic, weak) id<TUIJoinGroupMessageCellDelegate> joinGroupDelegate;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
145
TUIKit/TUIChat/UI_Classic/Cell/Chat/TUIJoinGroupMessageCell.m
Normal file
145
TUIKit/TUIChat/UI_Classic/Cell/Chat/TUIJoinGroupMessageCell.m
Normal file
@@ -0,0 +1,145 @@
|
||||
|
||||
// Created by Tencent on 2023/06/09.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
#import "TUIJoinGroupMessageCell.h"
|
||||
#import <TIMCommon/TIMDefine.h>
|
||||
|
||||
@interface TUIJoinGroupMessageCell () <UITextViewDelegate>
|
||||
|
||||
@property(nonatomic, strong) UITextView *textView;
|
||||
|
||||
@end
|
||||
|
||||
@implementation TUIJoinGroupMessageCell
|
||||
|
||||
- (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 : [UIColor d_systemGrayColor],
|
||||
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;
|
||||
|
||||
// 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.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];
|
||||
self.container.tui_mm_center();
|
||||
self.textView.mm_fill();
|
||||
}
|
||||
|
||||
- (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
|
||||
39
TUIKit/TUIChat/UI_Classic/Cell/Chat/TUIMediaCollectionCell.h
Normal file
39
TUIKit/TUIChat/UI_Classic/Cell/Chat/TUIMediaCollectionCell.h
Normal file
@@ -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;
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// TUIMediaCollectionCellDelegate
|
||||
//
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@protocol TUIMediaCollectionCellDelegate <NSObject>
|
||||
/**
|
||||
* meida cell
|
||||
*/
|
||||
- (void)onCloseMedia:(TUIMediaCollectionCell *)cell;
|
||||
@end
|
||||
|
||||
@interface TUIMediaCollectionCell : UICollectionViewCell
|
||||
|
||||
@property(nonatomic, strong) UIImageView *imageView;
|
||||
@property(nonatomic, strong) UIButton *downloadBtn;
|
||||
|
||||
@property(nonatomic, weak) id<TUIMediaCollectionCellDelegate> delegate;
|
||||
- (void)fillWithData:(TUIMessageCellData *)data;
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
31
TUIKit/TUIChat/UI_Classic/Cell/Chat/TUIMediaCollectionCell.m
Normal file
31
TUIKit/TUIChat/UI_Classic/Cell/Chat/TUIMediaCollectionCell.m
Normal file
@@ -0,0 +1,31 @@
|
||||
//
|
||||
// TUIMediaCollectionCell.m
|
||||
// TUIChat
|
||||
//
|
||||
// Created by xiangzhang on 2021/11/22.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
//
|
||||
|
||||
#import "TUIMediaCollectionCell.h"
|
||||
|
||||
@interface TUIMediaCollectionCell()<V2TIMAdvancedMsgListener>
|
||||
|
||||
@end
|
||||
@implementation TUIMediaCollectionCell
|
||||
- (id)initWithFrame:(CGRect)frame {
|
||||
self = [super initWithFrame:frame];
|
||||
if (self) {
|
||||
self.backgroundColor = [UIColor clearColor];
|
||||
[self registerTUIKitNotification];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)fillWithData:(TUIMessageCellData *)data {
|
||||
return;
|
||||
}
|
||||
|
||||
- (void)registerTUIKitNotification {
|
||||
[[V2TIMManager sharedInstance] addAdvancedMsgListener:self];
|
||||
}
|
||||
@end
|
||||
19
TUIKit/TUIChat/UI_Classic/Cell/Chat/TUIMenuCell.h
Normal file
19
TUIKit/TUIChat/UI_Classic/Cell/Chat/TUIMenuCell.h
Normal file
@@ -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
|
||||
//
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@interface TUIMenuCell : UICollectionViewCell
|
||||
|
||||
@property(nonatomic, strong) UIImageView *menu;
|
||||
|
||||
- (void)setData:(TUIMenuCellData *)data;
|
||||
|
||||
@end
|
||||
46
TUIKit/TUIChat/UI_Classic/Cell/Chat/TUIMenuCell.m
Normal file
46
TUIKit/TUIChat/UI_Classic/Cell/Chat/TUIMenuCell.m
Normal file
@@ -0,0 +1,46 @@
|
||||
//
|
||||
// InputMenuCell.m
|
||||
// UIKit
|
||||
//
|
||||
// Created by kennethmiao on 2018/9/20.
|
||||
// Copyright © 2018 Tencent. All rights reserved.
|
||||
//
|
||||
|
||||
#import "TUIMenuCell.h"
|
||||
#import <TIMCommon/TIMDefine.h>
|
||||
#import <TUICore/TUIThemeManager.h>
|
||||
|
||||
@implementation TUIMenuCell
|
||||
- (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
|
||||
41
TUIKit/TUIChat/UI_Classic/Cell/Chat/TUIMergeMessageCell.h
Normal file
41
TUIKit/TUIChat/UI_Classic/Cell/Chat/TUIMergeMessageCell.h
Normal file
@@ -0,0 +1,41 @@
|
||||
|
||||
// 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.h>
|
||||
#import "TUIMergeMessageCellData.h"
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface TUIMergeMessageCell : TUIMessageCell
|
||||
/**
|
||||
* 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
|
||||
381
TUIKit/TUIChat/UI_Classic/Cell/Chat/TUIMergeMessageCell.m
Normal file
381
TUIKit/TUIChat/UI_Classic/Cell/Chat/TUIMergeMessageCell.m
Normal file
@@ -0,0 +1,381 @@
|
||||
//
|
||||
// TUIMergeMessageCell.m
|
||||
// Pods
|
||||
//
|
||||
// Created by harvy on 2020/12/9.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
//
|
||||
|
||||
#import "TUIMergeMessageCell.h"
|
||||
#import <TIMCommon/TIMDefine.h>
|
||||
#import <TUICore/TUIThemeManager.h>
|
||||
#import <TUICore/TUICore.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 : 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
|
||||
|
||||
- (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;
|
||||
_abstractDetail.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 ()
|
||||
|
||||
@property(nonatomic, strong) CAShapeLayer *maskLayer;
|
||||
@property(nonatomic, strong) CAShapeLayer *borderLayer;
|
||||
@property(nonatomic, strong) TUIMergeMessageDetailRow *contentRowView1;
|
||||
@property(nonatomic, strong) TUIMergeMessageDetailRow *contentRowView2;
|
||||
@property(nonatomic, strong) TUIMergeMessageDetailRow *contentRowView3;
|
||||
@end
|
||||
|
||||
@implementation TUIMergeMessageCell
|
||||
|
||||
- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier {
|
||||
if ([super initWithStyle:style reuseIdentifier:reuseIdentifier]) {
|
||||
[self setupViews];
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onThemeChanged) name:TUIDidApplyingThemeChangedNotfication object:nil];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)setupViews {
|
||||
self.container.backgroundColor = TUIChatDynamicColor(@"chat_merge_message_bg_color", @"#FFFFFF");
|
||||
|
||||
_relayTitleLabel = [[UILabel alloc] init];
|
||||
_relayTitleLabel.text = @"Chat history";
|
||||
_relayTitleLabel.font = [UIFont systemFontOfSize:16];
|
||||
_relayTitleLabel.textColor = TUIChatDynamicColor(@"chat_merge_message_title_color", @"#000000");
|
||||
[self.container addSubview:_relayTitleLabel];
|
||||
|
||||
_contentRowView1 = [[TUIMergeMessageDetailRow alloc] init];
|
||||
[self.container addSubview:_contentRowView1];
|
||||
_contentRowView2 = [[TUIMergeMessageDetailRow alloc] init];
|
||||
[self.container addSubview:_contentRowView2];
|
||||
_contentRowView3 = [[TUIMergeMessageDetailRow 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 = TUIChatDynamicColor(@"chat_merge_message_content_color", @"#d5d5d5");
|
||||
_bottomTipsLabel.font = [UIFont systemFontOfSize:9];
|
||||
[self.container addSubview:_bottomTipsLabel];
|
||||
|
||||
[self.container.layer insertSublayer:self.borderLayer atIndex:0];
|
||||
[self.container.layer setMask:self.maskLayer];
|
||||
|
||||
}
|
||||
|
||||
+ (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.top.mas_equalTo(self.container).mas_offset(10);
|
||||
make.trailing.mas_equalTo(self.container).mas_offset(-10);
|
||||
make.height.mas_equalTo(self.relayTitleLabel.font.lineHeight);
|
||||
}];
|
||||
|
||||
[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);
|
||||
}];
|
||||
|
||||
self.maskLayer.frame = self.container.bounds;
|
||||
self.borderLayer.frame = self.container.bounds;
|
||||
|
||||
UIRectCorner corner = UIRectCornerBottomLeft | UIRectCornerBottomRight | UIRectCornerTopLeft;
|
||||
if (self.mergeData.direction == MsgDirectionIncoming) {
|
||||
corner = UIRectCornerBottomLeft | UIRectCornerBottomRight | UIRectCornerTopRight;
|
||||
}
|
||||
UIBezierPath *bezierPath = [UIBezierPath bezierPathWithRoundedRect:self.container.bounds byRoundingCorners:corner cornerRadii:CGSizeMake(10, 10)];
|
||||
self.maskLayer.path = bezierPath.CGPath;
|
||||
self.borderLayer.path = bezierPath.CGPath;
|
||||
|
||||
}
|
||||
|
||||
- (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;
|
||||
}
|
||||
|
||||
[self prepareReactTagUI:self.container];
|
||||
|
||||
// tell constraints they need updating
|
||||
[self setNeedsUpdateConstraints];
|
||||
|
||||
// update constraints now so we can animate the change
|
||||
[self updateConstraintsIfNeeded];
|
||||
|
||||
[self layoutIfNeeded];
|
||||
|
||||
}
|
||||
|
||||
- (CAShapeLayer *)maskLayer {
|
||||
if (_maskLayer == nil) {
|
||||
_maskLayer = [CAShapeLayer layer];
|
||||
}
|
||||
return _maskLayer;
|
||||
}
|
||||
|
||||
- (CAShapeLayer *)borderLayer {
|
||||
if (_borderLayer == nil) {
|
||||
_borderLayer = [CAShapeLayer layer];
|
||||
_borderLayer.lineWidth = 1.0;
|
||||
_borderLayer.strokeColor = TIMCommonDynamicColor(@"separator_color", @"#DBDBDB").CGColor;
|
||||
_borderLayer.fillColor = [UIColor clearColor].CGColor;
|
||||
}
|
||||
return _borderLayer;
|
||||
}
|
||||
|
||||
// MARK: ThemeChanged
|
||||
- (void)applyBorderTheme {
|
||||
if (_borderLayer) {
|
||||
_borderLayer.strokeColor = TIMCommonDynamicColor(@"separator_color", @"#DBDBDB").CGColor;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)onThemeChanged {
|
||||
[self applyBorderTheme];
|
||||
}
|
||||
|
||||
- (void)prepareReactTagUI:(UIView *)containerView {
|
||||
NSDictionary *param = @{TUICore_TUIChatExtension_ChatMessageReactPreview_Delegate: self};
|
||||
[TUICore raiseExtension:TUICore_TUIChatExtension_ChatMessageReactPreview_ClassicExtensionID parentView:containerView param:param];
|
||||
}
|
||||
|
||||
#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);
|
||||
}
|
||||
|
||||
+ (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 = MIN(CGFLOAT_CEIL(senderRect.size.width), 70);
|
||||
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
|
||||
16
TUIKit/TUIChat/UI_Classic/Cell/Chat/TUIVideoCollectionCell.h
Normal file
16
TUIKit/TUIChat/UI_Classic/Cell/Chat/TUIVideoCollectionCell.h
Normal file
@@ -0,0 +1,16 @@
|
||||
|
||||
// Created by Tencent on 2023/06/09.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
#import "TUIMediaCollectionCell.h"
|
||||
#import "TUIVideoMessageCellData.h"
|
||||
|
||||
@interface TUIVideoCollectionCell : TUIMediaCollectionCell
|
||||
|
||||
- (void)fillWithData:(TUIVideoMessageCellData *)data;
|
||||
|
||||
- (void)stopVideoPlayAndSave;
|
||||
|
||||
- (void)reloadAllView;
|
||||
@end
|
||||
532
TUIKit/TUIChat/UI_Classic/Cell/Chat/TUIVideoCollectionCell.m
Normal file
532
TUIKit/TUIChat/UI_Classic/Cell/Chat/TUIVideoCollectionCell.m
Normal file
@@ -0,0 +1,532 @@
|
||||
|
||||
// Created by Tencent on 2023/06/09.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
#import "TUIVideoCollectionCell.h"
|
||||
#import <TIMCommon/TIMDefine.h>
|
||||
#import "ReactiveObjC/ReactiveObjC.h"
|
||||
#import "TUICircleLodingView.h"
|
||||
|
||||
@import MediaPlayer;
|
||||
@import AVFoundation;
|
||||
@import AVKit;
|
||||
|
||||
@interface TUIVideoCollectionCellScrollView : UIScrollView <UIScrollViewDelegate>
|
||||
@property(nonatomic, strong) UIView *videoView;
|
||||
@property(assign, nonatomic) CGFloat videoViewNormalWidth;
|
||||
@property(assign, nonatomic) CGFloat videoViewNormalHeight;
|
||||
|
||||
- (void)pictureZoomWithScale:(CGFloat)zoomScale;
|
||||
|
||||
@end
|
||||
|
||||
@implementation TUIVideoCollectionCellScrollView
|
||||
- (id)initWithFrame:(CGRect)frame {
|
||||
self = [super initWithFrame:frame];
|
||||
if (self) {
|
||||
self.delegate = self;
|
||||
self.minimumZoomScale = 1.0f;
|
||||
self.maximumZoomScale = 2.0f;
|
||||
_videoViewNormalHeight = frame.size.height;
|
||||
_videoViewNormalWidth = frame.size.width;
|
||||
self.videoView = [[UIView alloc] initWithFrame:frame];
|
||||
[self addSubview:self.videoView];
|
||||
if (@available(iOS 11.0, *)) {
|
||||
self.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever;
|
||||
} else {
|
||||
// Fallback on earlier versions
|
||||
}
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)drawRect:(CGRect)rect {
|
||||
[super drawRect:rect];
|
||||
}
|
||||
|
||||
#pragma mark-- Help Methods
|
||||
|
||||
- (void)pictureZoomWithScale:(CGFloat)zoomScale {
|
||||
CGFloat imageScaleWidth = zoomScale * self.videoViewNormalWidth;
|
||||
CGFloat imageScaleHeight = zoomScale * self.videoViewNormalHeight;
|
||||
CGFloat imageX = 0;
|
||||
CGFloat imageY = 0;
|
||||
if (imageScaleWidth < self.frame.size.width) {
|
||||
imageX = floorf((self.frame.size.width - imageScaleWidth) / 2.0);
|
||||
}
|
||||
if (imageScaleHeight < self.frame.size.height) {
|
||||
imageY = floorf((self.frame.size.height - imageScaleHeight) / 2.0);
|
||||
}
|
||||
self.videoView.frame = CGRectMake(imageX, imageY, imageScaleWidth, imageScaleHeight);
|
||||
self.contentSize = CGSizeMake(imageScaleWidth, imageScaleHeight);
|
||||
}
|
||||
|
||||
#pragma mark-- Setter
|
||||
|
||||
- (void)setVideoViewNormalWidth:(CGFloat)videoViewNormalWidth {
|
||||
_videoViewNormalWidth = videoViewNormalWidth;
|
||||
self.videoView.frame = CGRectMake(0, 0, _videoViewNormalWidth, _videoViewNormalHeight);
|
||||
self.videoView.center = CGPointMake(self.frame.size.width / 2, self.frame.size.height / 2);
|
||||
}
|
||||
|
||||
- (void)setVideoViewNormalHeight:(CGFloat)videoViewNormalHeight {
|
||||
_videoViewNormalHeight = videoViewNormalHeight;
|
||||
self.videoView.frame = CGRectMake(0, 0, _videoViewNormalWidth, _videoViewNormalHeight);
|
||||
self.videoView.center = CGPointMake(self.frame.size.width / 2, self.frame.size.height / 2);
|
||||
}
|
||||
|
||||
#pragma mark-- UIScrollViewDelegate
|
||||
|
||||
// Returns the view control that needs to be zoomed. During zooming
|
||||
- (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView {
|
||||
return self.videoView;
|
||||
}
|
||||
|
||||
// BeginZooming
|
||||
- (void)scrollViewWillBeginZooming:(UIScrollView *)scrollView withView:(UIView *)view {
|
||||
NSLog(@"BeginZooming");
|
||||
}
|
||||
// EndZooming
|
||||
- (void)scrollViewDidEndZooming:(UIScrollView *)scrollView withView:(UIView *)view atScale:(CGFloat)scale {
|
||||
NSLog(@"EndZooming");
|
||||
}
|
||||
|
||||
// zoom
|
||||
- (void)scrollViewDidZoom:(UIScrollView *)scrollView {
|
||||
CGFloat imageScaleWidth = scrollView.zoomScale * self.videoViewNormalWidth;
|
||||
CGFloat imageScaleHeight = scrollView.zoomScale * self.videoViewNormalHeight;
|
||||
CGFloat imageX = 0;
|
||||
CGFloat imageY = 0;
|
||||
if (imageScaleWidth < self.frame.size.width) {
|
||||
imageX = floorf((self.frame.size.width - imageScaleWidth) / 2.0);
|
||||
}
|
||||
if (imageScaleHeight < self.frame.size.height) {
|
||||
imageY = floorf((self.frame.size.height - imageScaleHeight) / 2.0);
|
||||
}
|
||||
self.videoView.frame = CGRectMake(imageX, imageY, imageScaleWidth, imageScaleHeight);
|
||||
}
|
||||
|
||||
@end
|
||||
@interface TUIVideoCollectionCell ()
|
||||
@property(nonatomic, strong) TUIVideoCollectionCellScrollView *scrollView;
|
||||
@property(nonatomic, strong) UILabel *duration;
|
||||
@property(nonatomic, strong) UILabel *playTime;
|
||||
@property(nonatomic, strong) UISlider *playProcess;
|
||||
@property(nonatomic, strong) UIButton *mainPlayBtn;
|
||||
@property(nonatomic, strong) UIButton *mainDownloadBtn;
|
||||
@property(nonatomic, strong) UIButton *playBtn;
|
||||
@property(nonatomic, strong) UIButton *closeBtn;
|
||||
@property(nonatomic, strong) TUICircleLodingView *animateCircleView;
|
||||
@property(nonatomic, strong) AVPlayer *player;
|
||||
@property(nonatomic, strong) AVPlayerLayer *playerLayer;
|
||||
@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
|
||||
- (id)initWithFrame:(CGRect)frame {
|
||||
self = [super initWithFrame:frame];
|
||||
if (self) {
|
||||
[self setupViews];
|
||||
[self setupRotaionNotifications];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)setupRotaionNotifications {
|
||||
if (@available(iOS 16.0, *)) {
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self
|
||||
selector:@selector(onDeviceOrientationChange:)
|
||||
name:TUIMessageMediaViewDeviceOrientationChangeNotification
|
||||
object:nil];
|
||||
} else {
|
||||
[[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications];
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self
|
||||
selector:@selector(onDeviceOrientationChange:)
|
||||
name:UIDeviceOrientationDidChangeNotification
|
||||
object:nil];
|
||||
}
|
||||
}
|
||||
- (void)setupViews {
|
||||
self.scrollView = [[TUIVideoCollectionCellScrollView alloc] initWithFrame:CGRectMake(0, 0, self.bounds.size.width, self.bounds.size.height)];
|
||||
[self addSubview:self.scrollView];
|
||||
|
||||
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.scrollView.videoView 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.mainDownloadBtn = [UIButton buttonWithType:UIButtonTypeCustom];
|
||||
self.mainDownloadBtn.contentMode = UIViewContentModeScaleToFill;
|
||||
[self.mainDownloadBtn setImage:[[TUIImageCache sharedInstance] getResourceFromCache:TUIChatImagePath(@"download")] forState:UIControlStateNormal];
|
||||
self.mainDownloadBtn.hidden = YES;
|
||||
[self.mainDownloadBtn addTarget:self action:@selector(mainDownloadBtnClick) forControlEvents:UIControlEventTouchUpInside];
|
||||
[self addSubview:self.mainDownloadBtn];
|
||||
|
||||
self.animateCircleView = [[TUICircleLodingView alloc] initWithFrame:CGRectMake(0, 0, kScale390(40), kScale390(40))];
|
||||
self.animateCircleView.progress = 0;
|
||||
self.animateCircleView.hidden = YES;
|
||||
[self addSubview:_animateCircleView];
|
||||
|
||||
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;
|
||||
|
||||
BOOL hasRiskContent = data.innerMessage.hasRiskContent;
|
||||
if (hasRiskContent) {
|
||||
self.imageView.image = TIMCommonBundleThemeImage(@"", @"icon_security_strike");
|
||||
for (UIView *subview in self.subviews) {
|
||||
if (subview != self.scrollView && subview != self.closeBtn){
|
||||
subview.hidden = YES;
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
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]) {
|
||||
self.mainDownloadBtn.hidden = NO;
|
||||
self.mainPlayBtn.hidden = YES;
|
||||
self.animateCircleView.hidden = YES;
|
||||
} 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, videoProgress) takeUntil:self.rac_prepareForReuseSignal] distinctUntilChanged] subscribeNext:^(id _Nullable x) {
|
||||
@strongify(self);
|
||||
int progress = [x intValue];
|
||||
if (progress == 100) {
|
||||
self.animateCircleView.progress = 99;
|
||||
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
|
||||
self.animateCircleView.progress = 100;
|
||||
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
|
||||
self.animateCircleView.progress = 0;
|
||||
self.mainDownloadBtn.hidden = YES;
|
||||
self.animateCircleView.hidden = YES;
|
||||
self.mainPlayBtn.hidden = NO;
|
||||
});
|
||||
});
|
||||
} else if (progress > 1 && progress < 100) {
|
||||
self.animateCircleView.progress = progress;
|
||||
self.mainDownloadBtn.hidden = YES;
|
||||
self.animateCircleView.hidden = NO;
|
||||
} else {
|
||||
self.animateCircleView.progress = progress;
|
||||
}
|
||||
}];
|
||||
|
||||
[[[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];
|
||||
}
|
||||
self.animateCircleView.hidden = YES;
|
||||
[self addPlayer:[NSURL fileURLWithPath:self.videoPath]];
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)layoutSubviews {
|
||||
[super layoutSubviews];
|
||||
[self.mainDownloadBtn sizeToFit];
|
||||
self.mainDownloadBtn.mm_width(65).mm_height(65).mm__centerX(self.mm_w / 2).mm__centerY(self.mm_h / 2);
|
||||
self.mainDownloadBtn.layer.cornerRadius = (self.mainDownloadBtn.mm_h * 0.5);
|
||||
self.animateCircleView.tui_mm_center();
|
||||
|
||||
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);
|
||||
self.scrollView.mm_width(self.mm_w).mm_height(self.mm_h).mm__centerX(self.mm_w / 2).mm__centerY(self.mm_h / 2);
|
||||
self.scrollView.videoViewNormalWidth = self.mm_w;
|
||||
self.scrollView.videoViewNormalHeight = self.mm_h;
|
||||
self.playerLayer.frame = self.scrollView.bounds;
|
||||
[self.scrollView.videoView.layer layoutIfNeeded];
|
||||
}
|
||||
|
||||
- (void)addPlayer:(NSURL *)url {
|
||||
self.videoUrl = url;
|
||||
if (!self.player) {
|
||||
self.player = [AVPlayer playerWithURL:self.videoUrl];
|
||||
self.playerLayer = [AVPlayerLayer playerLayerWithPlayer:self.player];
|
||||
self.playerLayer.frame = self.scrollView.videoView.bounds;
|
||||
[self.scrollView.videoView.layer insertSublayer:self.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.videoData isVideoExist]) {
|
||||
[self.videoData downloadVideo];
|
||||
} else {
|
||||
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 {
|
||||
BOOL hasRiskContent = self.videoData.innerMessage.hasRiskContent;
|
||||
self.isPlay = NO;
|
||||
[self.player pause];
|
||||
self.imageView.hidden = NO;
|
||||
if (!hasRiskContent) {
|
||||
self.mainPlayBtn.hidden = NO;
|
||||
}
|
||||
[self.playBtn setImage:TUIChatCommonBundleImage(@"video_play") forState:UIControlStateNormal];
|
||||
}
|
||||
|
||||
- (void)mainDownloadBtnClick {
|
||||
if (![self.videoData isVideoExist]) {
|
||||
[self.videoData downloadVideo];
|
||||
}
|
||||
}
|
||||
#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)];
|
||||
}
|
||||
});
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)onDeviceOrientationChange:(NSNotification *)noti {
|
||||
UIDeviceOrientation orientation = [UIDevice currentDevice].orientation;
|
||||
[self reloadAllView];
|
||||
}
|
||||
|
||||
- (void)reloadAllView {
|
||||
if (self.player) {
|
||||
[self stopPlay];
|
||||
self.player = nil;
|
||||
}
|
||||
if (self.playerLayer) {
|
||||
[self.playerLayer removeFromSuperlayer];
|
||||
self.playerLayer = nil;
|
||||
}
|
||||
for (UIView *subview in self.scrollView.subviews) {
|
||||
if (subview) {
|
||||
[subview removeFromSuperview];
|
||||
}
|
||||
}
|
||||
for (UIView *subview in self.subviews) {
|
||||
if (subview) {
|
||||
[subview removeFromSuperview];
|
||||
}
|
||||
}
|
||||
[self setupViews];
|
||||
if (self.videoData) {
|
||||
[self fillWithData:self.videoData];
|
||||
}
|
||||
}
|
||||
#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 - V2TIMAdvancedMsgListener
|
||||
- (void)onRecvMessageModified:(V2TIMMessage *)msg {
|
||||
V2TIMMessage *imMsg = msg;
|
||||
if (imMsg == nil || ![imMsg isKindOfClass:V2TIMMessage.class]) {
|
||||
return;
|
||||
}
|
||||
if ([self.videoData.innerMessage.msgID isEqualToString:imMsg.msgID]) {
|
||||
BOOL hasRiskContent = msg.hasRiskContent;
|
||||
if (hasRiskContent) {
|
||||
self.videoData.innerMessage = imMsg;
|
||||
[self showRiskAlert];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)showRiskAlert {
|
||||
if (self.player) {
|
||||
[self stopPlay];
|
||||
}
|
||||
UIAlertController *ac = [UIAlertController alertControllerWithTitle:nil
|
||||
message:TIMCommonLocalizableString(TUIKitVideoCheckRisk)
|
||||
preferredStyle:UIAlertControllerStyleAlert];
|
||||
__weak typeof(self) weakSelf = self;
|
||||
[ac tuitheme_addAction:[UIAlertAction actionWithTitle:TIMCommonLocalizableString(TUIKitVideoCheckRiskCancel)
|
||||
style:UIAlertActionStyleCancel
|
||||
handler:^(UIAlertAction *_Nonnull action) {
|
||||
__strong typeof(weakSelf) strongSelf = weakSelf;
|
||||
[strongSelf reloadAllView];
|
||||
}]];
|
||||
|
||||
[[UIApplication sharedApplication].keyWindow.rootViewController presentViewController:ac animated:YES completion:nil];
|
||||
}
|
||||
@end
|
||||
49
TUIKit/TUIChat/UI_Classic/Cell/Chat/TUIVideoMessageCell.h
Normal file
49
TUIKit/TUIChat/UI_Classic/Cell/Chat/TUIVideoMessageCell.h
Normal file
@@ -0,0 +1,49 @@
|
||||
|
||||
// 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/TUIBubbleMessageCell.h>
|
||||
#import <TIMCommon/TUIMessageCell.h>
|
||||
#import "TUIVideoMessageCellData.h"
|
||||
/**
|
||||
*
|
||||
* 【Module name】TUIVideoMessageCell
|
||||
* 【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 : TUIBubbleMessageCell
|
||||
|
||||
/**
|
||||
*
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* Label for displaying video duration
|
||||
*/
|
||||
@property(nonatomic, strong) UILabel *duration;
|
||||
|
||||
/**
|
||||
* 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
|
||||
419
TUIKit/TUIChat/UI_Classic/Cell/Chat/TUIVideoMessageCell.m
Normal file
419
TUIKit/TUIChat/UI_Classic/Cell/Chat/TUIVideoMessageCell.m
Normal file
@@ -0,0 +1,419 @@
|
||||
//
|
||||
// TUIVideoMessageCell.m
|
||||
// UIKit
|
||||
//
|
||||
// Created by annidyfeng on 2019/5/30.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
//
|
||||
|
||||
#import "TUIVideoMessageCell.h"
|
||||
#import <TIMCommon/TIMDefine.h>
|
||||
#import "TUICircleLodingView.h"
|
||||
#import "TUIMessageProgressManager.h"
|
||||
|
||||
@interface TUIVideoMessageCell () <TUIMessageProgressManagerDelegate>
|
||||
|
||||
@property(nonatomic, strong) UIView *animateHighlightView;
|
||||
|
||||
@property(nonatomic, strong) TUICircleLodingView *animateCircleView;
|
||||
|
||||
@property(nonatomic, strong) UIImageView *downloadImage;
|
||||
|
||||
@end
|
||||
|
||||
@implementation TUIVideoMessageCell
|
||||
|
||||
- (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 = UIViewContentModeScaleAspectFill;
|
||||
_thumb.backgroundColor = [UIColor clearColor];
|
||||
[self.bubbleView 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")];
|
||||
_play.hidden = YES;
|
||||
[_thumb addSubview:_play];
|
||||
|
||||
_downloadImage = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, playSize.width, playSize.height)];
|
||||
_downloadImage.contentMode = UIViewContentModeScaleAspectFit;
|
||||
_downloadImage.image = [[TUIImageCache sharedInstance] getResourceFromCache:TUIChatImagePath(@"download")];
|
||||
_downloadImage.hidden = YES;
|
||||
[_thumb addSubview:_downloadImage];
|
||||
|
||||
_duration = [[UILabel alloc] init];
|
||||
_duration.textColor = [UIColor whiteColor];
|
||||
_duration.font = [UIFont systemFontOfSize:12];
|
||||
[_thumb addSubview:_duration];
|
||||
|
||||
_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;
|
||||
[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.securityStrikeView.textLabel.text = TIMCommonLocalizableString(TUIKitMessageTypeSecurityStrikeImage);
|
||||
self.duration.text = @"";
|
||||
self.play.hidden = YES;
|
||||
self.downloadImage.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.duration.text = @"";
|
||||
self.play.hidden = YES;
|
||||
self.downloadImage.hidden = YES;
|
||||
self.indicator.hidden = YES;
|
||||
self.animateCircleView.hidden = NO;
|
||||
@weakify(self);
|
||||
[[RACObserve(data, videoTranscodingProgress) takeUntil:self.rac_prepareForReuseSignal] subscribeNext:^(NSNumber *x) {
|
||||
// The transcoded animation can display up to 30% at maximum,
|
||||
// and the upload progress increases from 30% to 100%.
|
||||
@strongify(self);
|
||||
double progress = [x doubleValue];
|
||||
double factor = 0.3;
|
||||
double resultProgress = (progress *100) * factor;
|
||||
self.animateCircleView.progress = resultProgress;
|
||||
}];
|
||||
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;
|
||||
}
|
||||
|
||||
@weakify(self);
|
||||
[[RACObserve(data, thumbImage) takeUntil:self.rac_prepareForReuseSignal] subscribeNext:^(UIImage *thumbImage) {
|
||||
@strongify(self);
|
||||
if (thumbImage) {
|
||||
self.thumb.image = thumbImage;
|
||||
}
|
||||
}];
|
||||
|
||||
_duration.text = [NSString stringWithFormat:@"%02ld:%02ld", (long)data.videoItem.duration / 60, (long)data.videoItem.duration % 60];
|
||||
|
||||
self.play.hidden = YES;
|
||||
self.downloadImage.hidden = YES;
|
||||
self.indicator.hidden = YES;
|
||||
if (data.direction == MsgDirectionIncoming) {
|
||||
[[[RACObserve(data, thumbProgress) takeUntil:self.rac_prepareForReuseSignal] distinctUntilChanged] subscribeNext:^(NSNumber *x) {
|
||||
@strongify(self);
|
||||
// Cover download progress callback
|
||||
int progress = [x intValue];
|
||||
self.progress.text = [NSString stringWithFormat:@"%d%%", progress];
|
||||
self.progress.hidden = (progress >= 100 || progress == 0);
|
||||
self.animateCircleView.progress = progress;
|
||||
if (progress >= 100 || progress == 0) {
|
||||
// The progress of cover download is called back and the download video icon is displayed when the cover progress is 100.
|
||||
if ([data isVideoExist]) {
|
||||
self.play.hidden = NO;
|
||||
} else {
|
||||
self.downloadImage.hidden = NO;
|
||||
}
|
||||
} else {
|
||||
self.play.hidden = YES;
|
||||
self.downloadImage.hidden = YES;
|
||||
}
|
||||
}];
|
||||
|
||||
// Video resource download progress callback
|
||||
[[[RACObserve(data, videoProgress) takeUntil:self.rac_prepareForReuseSignal] distinctUntilChanged] subscribeNext:^(NSNumber *x) {
|
||||
@strongify(self);
|
||||
int progress = [x intValue];
|
||||
self.animateCircleView.progress = progress;
|
||||
if (progress >= 100 || progress == 0) {
|
||||
self.play.hidden = NO;
|
||||
self.animateCircleView.hidden = YES;
|
||||
} else {
|
||||
self.play.hidden = YES;
|
||||
self.downloadImage.hidden = YES;
|
||||
self.animateCircleView.hidden = NO;
|
||||
}
|
||||
}];
|
||||
|
||||
} else {
|
||||
if ([data isVideoExist]) {
|
||||
[[[RACObserve(data, uploadProgress) takeUntil:self.rac_prepareForReuseSignal] distinctUntilChanged] subscribeNext:^(NSNumber *x) {
|
||||
@strongify(self);
|
||||
int progress = [x intValue];
|
||||
if (data.placeHolderCellData.videoTranscodingProgress > 0) {
|
||||
progress = MAX(progress, 30);//the upload progress increases from 30% to 100%.
|
||||
}
|
||||
self.animateCircleView.progress = progress;
|
||||
if (progress >= 100 || progress == 0) {
|
||||
[self.indicator stopAnimating];
|
||||
self.play.hidden = NO;
|
||||
self.animateCircleView.hidden = YES;
|
||||
} else {
|
||||
[self.indicator startAnimating];
|
||||
self.play.hidden = YES;
|
||||
self.animateCircleView.hidden = NO;
|
||||
}
|
||||
}];
|
||||
|
||||
} else {
|
||||
[[[RACObserve(data, thumbProgress) takeUntil:self.rac_prepareForReuseSignal] distinctUntilChanged] subscribeNext:^(NSNumber *x) {
|
||||
@strongify(self);
|
||||
// Cover download progress callback
|
||||
int progress = [x intValue];
|
||||
self.progress.text = [NSString stringWithFormat:@"%d%%", progress];
|
||||
self.progress.hidden = (progress >= 100 || progress == 0);
|
||||
self.animateCircleView.progress = progress;
|
||||
if (progress >= 100 || progress == 0) {
|
||||
// The download video icon is displayed when the cover progress reaches 100
|
||||
if ([data isVideoExist]) {
|
||||
self.play.hidden = NO;
|
||||
} else {
|
||||
self.downloadImage.hidden = NO;
|
||||
}
|
||||
} else {
|
||||
self.play.hidden = YES;
|
||||
self.downloadImage.hidden = YES;
|
||||
}
|
||||
}];
|
||||
|
||||
// Video resource download progress callback
|
||||
[[[RACObserve(data, videoProgress) takeUntil:self.rac_prepareForReuseSignal] distinctUntilChanged] subscribeNext:^(NSNumber *x) {
|
||||
@strongify(self);
|
||||
int progress = [x intValue];
|
||||
self.animateCircleView.progress = progress;
|
||||
if (progress >= 100 || progress == 0) {
|
||||
self.play.hidden = NO;
|
||||
self.animateCircleView.hidden = YES;
|
||||
} else {
|
||||
self.play.hidden = YES;
|
||||
self.downloadImage.hidden = YES;
|
||||
self.animateCircleView.hidden = NO;
|
||||
}
|
||||
}];
|
||||
}
|
||||
}
|
||||
// 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];
|
||||
if (self.messageData.messageContainerAppendSize.height > 0) {
|
||||
CGFloat topMargin = 10;
|
||||
CGFloat tagViewTopMargin = 6;
|
||||
CGFloat thumbHeight = self.bubbleView.mm_h - topMargin - self.messageData.messageContainerAppendSize.height - tagViewTopMargin;
|
||||
CGSize size = [self.class getContentSize:self.messageData];
|
||||
[self.thumb mas_remakeConstraints:^(MASConstraintMaker *make) {
|
||||
make.height.mas_equalTo(thumbHeight);
|
||||
make.width.mas_equalTo(size.width);
|
||||
make.centerX.mas_equalTo(self.bubbleView);
|
||||
make.top.mas_equalTo(self.container).mas_offset(topMargin);
|
||||
}];
|
||||
[self.duration mas_remakeConstraints:^(MASConstraintMaker *make) {
|
||||
make.trailing.mas_equalTo(self.thumb.mas_trailing).mas_offset(-2);
|
||||
make.width.mas_greaterThanOrEqualTo(20);
|
||||
make.height.mas_equalTo(20);
|
||||
make.bottom.mas_equalTo(self.thumb.mas_bottom);
|
||||
}];
|
||||
} else {
|
||||
[self.thumb mas_remakeConstraints:^(MASConstraintMaker *make) {
|
||||
make.top.mas_equalTo(self.bubbleView).mas_offset(self.messageData.cellLayout.bubbleInsets.top);
|
||||
make.bottom.mas_equalTo(self.bubbleView).mas_offset(- self.messageData.cellLayout.bubbleInsets.bottom);
|
||||
make.leading.mas_equalTo(self.bubbleView).mas_offset(self.messageData.cellLayout.bubbleInsets.left);
|
||||
make.trailing.mas_equalTo(self.bubbleView).mas_offset(- self.messageData.cellLayout.bubbleInsets.right);
|
||||
}];
|
||||
[self.duration mas_remakeConstraints:^(MASConstraintMaker *make) {
|
||||
make.trailing.mas_equalTo(self.thumb.mas_trailing).mas_offset(-2);
|
||||
make.width.mas_greaterThanOrEqualTo(20);
|
||||
make.height.mas_equalTo(20);
|
||||
make.bottom.mas_equalTo(self.thumb.mas_bottom);
|
||||
}];
|
||||
}
|
||||
|
||||
|
||||
BOOL hasRiskContent = self.messageData.innerMessage.hasRiskContent;
|
||||
if (hasRiskContent ) {
|
||||
[self.thumb mas_remakeConstraints:^(MASConstraintMaker *make) {
|
||||
make.top.mas_equalTo(self.bubbleView).mas_offset(12);
|
||||
make.size.mas_equalTo(CGSizeMake(150, 150));
|
||||
make.centerX.mas_equalTo(self.bubbleView);
|
||||
}];
|
||||
|
||||
[self.securityStrikeView mas_remakeConstraints:^(MASConstraintMaker *make) {
|
||||
make.top.mas_equalTo(self.thumb.mas_bottom);
|
||||
make.width.mas_equalTo(self.bubbleView);
|
||||
if(self.messageData.messageContainerAppendSize.height>0) {
|
||||
make.bottom.mas_equalTo(self.container).mas_offset(-self.messageData.messageContainerAppendSize.height);
|
||||
}
|
||||
else {
|
||||
make.bottom.mas_equalTo(self.container).mas_offset(-12);
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
[self.play mas_remakeConstraints:^(MASConstraintMaker *make) {
|
||||
make.size.mas_equalTo(TVideoMessageCell_Play_Size);
|
||||
make.center.mas_equalTo(self.thumb);
|
||||
}];
|
||||
[self.downloadImage mas_remakeConstraints:^(MASConstraintMaker *make) {
|
||||
make.size.mas_equalTo(TVideoMessageCell_Play_Size);
|
||||
make.center.mas_equalTo(self.thumb);
|
||||
}];
|
||||
|
||||
[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;
|
||||
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;
|
||||
}
|
||||
if (size.height > size.width) {
|
||||
size.width = size.width / size.height * TVideoMessageCell_Image_Height_Max;
|
||||
size.height = TVideoMessageCell_Image_Height_Max;
|
||||
} else {
|
||||
size.height = size.height / size.width * TVideoMessageCell_Image_Width_Max;
|
||||
size.width = TVideoMessageCell_Image_Width_Max;
|
||||
}
|
||||
BOOL hasRiskContent = videoCellData.innerMessage.hasRiskContent;
|
||||
if (hasRiskContent) {
|
||||
CGFloat bubbleTopMargin = 12;
|
||||
CGFloat bubbleBottomMargin = 12;
|
||||
size.height = MAX(size.height, 150);// width must more than TIMCommonBundleThemeImage(@"", @"icon_security_strike");
|
||||
size.width = MAX(size.width, 200);// width must more than TIMCommonLocalizableString(TUIKitMessageTypeSecurityStrike)
|
||||
size.height += bubbleTopMargin;
|
||||
size.height += kTUISecurityStrikeViewTopLineMargin;
|
||||
size.height += kTUISecurityStrikeViewTopLineToBottom;
|
||||
size.height += bubbleBottomMargin;
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
||||
@end
|
||||
48
TUIKit/TUIChat/UI_Classic/Cell/Chat/TUIVoiceMessageCell.h
Normal file
48
TUIKit/TUIChat/UI_Classic/Cell/Chat/TUIVoiceMessageCell.h
Normal file
@@ -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.h>
|
||||
#import "TUIVoiceMessageCellData.h"
|
||||
|
||||
@import AVFoundation;
|
||||
|
||||
/**
|
||||
*
|
||||
* 【Module name】 TUIVoiceMessageCell
|
||||
* 【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 : TUIBubbleMessageCell
|
||||
|
||||
/**
|
||||
* 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) UIImageView *voice;
|
||||
|
||||
/**
|
||||
* 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
|
||||
230
TUIKit/TUIChat/UI_Classic/Cell/Chat/TUIVoiceMessageCell.m
Normal file
230
TUIKit/TUIChat/UI_Classic/Cell/Chat/TUIVoiceMessageCell.m
Normal file
@@ -0,0 +1,230 @@
|
||||
//
|
||||
// TUIVoiceMessageCell.m
|
||||
// UIKit
|
||||
//
|
||||
// Created by annidyfeng on 2019/5/30.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
//
|
||||
|
||||
#import "TUIVoiceMessageCell.h"
|
||||
#import <TIMCommon/TIMDefine.h>
|
||||
#import <TUICore/TUIThemeManager.h>
|
||||
#import <TUICore/TUICore.h>
|
||||
|
||||
@implementation TUIVoiceMessageCell
|
||||
|
||||
- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier {
|
||||
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
|
||||
if (self) {
|
||||
_voice = [[UIImageView alloc] init];
|
||||
_voice.animationDuration = 1;
|
||||
[self.bubbleView addSubview:_voice];
|
||||
|
||||
_duration = [[UILabel alloc] init];
|
||||
_duration.font = [UIFont boldSystemFontOfSize:12];
|
||||
[self.bubbleView addSubview:_duration];
|
||||
|
||||
self.bottomContainer = [[UIView alloc] init];
|
||||
[self.contentView addSubview:self.bottomContainer];
|
||||
|
||||
_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];
|
||||
}
|
||||
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_ClassicExtensionID parentView:self.bottomContainer param:param];
|
||||
}
|
||||
|
||||
- (void)fillWithData:(TUIVoiceMessageCellData *)data {
|
||||
// set data
|
||||
[super fillWithData:data];
|
||||
self.voiceData = data;
|
||||
self.bottomContainer.hidden = CGSizeEqualToSize(data.bottomContainerSize, CGSizeZero);
|
||||
|
||||
if (data.duration > 0) {
|
||||
_duration.text = [NSString stringWithFormat:@"%ld\"", (long)data.duration];
|
||||
} else {
|
||||
_duration.text = @"1\"";
|
||||
}
|
||||
_voice.image = data.voiceImage;
|
||||
_voice.animationImages = data.voiceAnimationImages;
|
||||
BOOL hasRiskContent = self.messageData.innerMessage.hasRiskContent;
|
||||
|
||||
if (hasRiskContent) {
|
||||
self.securityStrikeView.textLabel.text = TIMCommonLocalizableString(TUIKitMessageTypeSecurityStrikeVoice);
|
||||
}
|
||||
|
||||
if (self.voiceData.innerMessage.localCustomInt == 0 && self.voiceData.direction == MsgDirectionIncoming) self.voiceReadPoint.hidden = NO;
|
||||
|
||||
// animate
|
||||
@weakify(self);
|
||||
[[RACObserve(data, isPlaying) takeUntil:self.rac_prepareForReuseSignal] subscribeNext:^(NSNumber *x) {
|
||||
@strongify(self);
|
||||
if ([x boolValue]) {
|
||||
[self.voice startAnimating];
|
||||
} else {
|
||||
[self.voice stopAnimating];
|
||||
}
|
||||
}];
|
||||
|
||||
[self applyStyleFromDirection:data.direction];
|
||||
|
||||
// tell constraints they need updating
|
||||
[self setNeedsUpdateConstraints];
|
||||
|
||||
// update constraints now so we can animate the change
|
||||
[self updateConstraintsIfNeeded];
|
||||
|
||||
[self layoutIfNeeded];
|
||||
|
||||
}
|
||||
|
||||
- (void)applyStyleFromDirection:(TMsgDirection)direction {
|
||||
if (direction == MsgDirectionIncoming) {
|
||||
_duration.rtlAlignment = TUITextRTLAlignmentLeading;
|
||||
_duration.textColor = TUIChatDynamicColor(@"chat_voice_message_recv_duration_time_color", @"#000000");
|
||||
} else {
|
||||
_duration.rtlAlignment = TUITextRTLAlignmentTrailing;
|
||||
_duration.textColor = TUIChatDynamicColor(@"chat_voice_message_send_duration_time_color", @"#000000");
|
||||
}
|
||||
}
|
||||
|
||||
+ (BOOL)requiresConstraintBasedLayout {
|
||||
return YES;
|
||||
}
|
||||
|
||||
// This is Apple's recommended place for adding/updating constraints
|
||||
- (void)updateConstraints {
|
||||
[super updateConstraints];
|
||||
|
||||
[self.voice sizeToFit];
|
||||
[self.voice mas_remakeConstraints:^(MASConstraintMaker *make) {
|
||||
make.top.mas_equalTo(self.voiceData.voiceTop);
|
||||
make.width.height.mas_equalTo(_voiceData.voiceHeight);
|
||||
if (self.voiceData.direction == MsgDirectionOutgoing) {
|
||||
make.trailing.mas_equalTo(-self.voiceData.cellLayout.bubbleInsets.right);
|
||||
} else {
|
||||
make.leading.mas_equalTo(self.voiceData.cellLayout.bubbleInsets.left);
|
||||
}
|
||||
}];
|
||||
|
||||
[self.duration mas_remakeConstraints:^(MASConstraintMaker *make) {
|
||||
make.width.mas_greaterThanOrEqualTo(10);
|
||||
make.height.mas_greaterThanOrEqualTo(TVoiceMessageCell_Duration_Size.height);
|
||||
make.centerY.mas_equalTo(self.voice);
|
||||
if (self.voiceData.direction == MsgDirectionOutgoing) {
|
||||
make.trailing.mas_equalTo(self.voice.mas_leading).mas_offset(-5);
|
||||
} else {
|
||||
make.leading.mas_equalTo(self.voice.mas_trailing).mas_offset(5);
|
||||
}
|
||||
}];
|
||||
|
||||
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));
|
||||
}];
|
||||
}
|
||||
BOOL hasRiskContent = self.messageData.innerMessage.hasRiskContent;
|
||||
if (hasRiskContent) {
|
||||
[self.securityStrikeView mas_remakeConstraints:^(MASConstraintMaker *make) {
|
||||
make.top.mas_equalTo(self.voice.mas_bottom);
|
||||
make.width.mas_equalTo(self.bubbleView);
|
||||
make.bottom.mas_equalTo(self.container).mas_offset(- self.messageData.messageContainerAppendSize.height);
|
||||
}];
|
||||
}
|
||||
|
||||
[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(6);
|
||||
make.size.mas_equalTo(size);
|
||||
}];
|
||||
|
||||
CGFloat repliesBtnTextWidth = self.messageModifyRepliesButton.frame.size.width;
|
||||
if (!self.messageModifyRepliesButton.hidden) {
|
||||
[self.messageModifyRepliesButton 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.bottomContainer.mas_bottom);
|
||||
make.size.mas_equalTo(CGSizeMake(repliesBtnTextWidth + 10, 30));
|
||||
}];
|
||||
}
|
||||
}
|
||||
|
||||
#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 {
|
||||
TUIVoiceMessageCellData *voiceCellData = (TUIVoiceMessageCellData *)data;
|
||||
|
||||
CGFloat bubbleWidth = TVoiceMessageCell_Back_Width_Min + voiceCellData.duration / TVoiceMessageCell_Max_Duration * Screen_Width;
|
||||
if (bubbleWidth > TVoiceMessageCell_Back_Width_Max) {
|
||||
bubbleWidth = TVoiceMessageCell_Back_Width_Max;
|
||||
}
|
||||
|
||||
CGFloat bubbleHeight = TVoiceMessageCell_Duration_Size.height;
|
||||
if (voiceCellData.direction == MsgDirectionIncoming) {
|
||||
bubbleWidth = MAX(bubbleWidth, [TUIBubbleMessageCell incommingBubble].size.width);
|
||||
bubbleHeight = voiceCellData.voiceImage.size.height + 2 * voiceCellData.voiceTop; //[TUIBubbleMessageCellData incommingBubble].size.height;
|
||||
} else {
|
||||
bubbleWidth = MAX(bubbleWidth, [TUIBubbleMessageCell outgoingBubble].size.width);
|
||||
bubbleHeight = voiceCellData.voiceImage.size.height + 2 * voiceCellData.voiceTop; // [TUIBubbleMessageCellData outgoingBubble].size.height;
|
||||
}
|
||||
|
||||
CGFloat width = bubbleWidth + TVoiceMessageCell_Duration_Size.width;
|
||||
CGFloat height = bubbleHeight;
|
||||
|
||||
BOOL hasRiskContent = voiceCellData.innerMessage.hasRiskContent;
|
||||
if (hasRiskContent) {
|
||||
width = MAX(width, 200);// width must more than TIMCommonLocalizableString(TUIKitMessageTypeSecurityStrikeVoice)
|
||||
height += kTUISecurityStrikeViewTopLineMargin;
|
||||
height += kTUISecurityStrikeViewTopLineToBottom;
|
||||
}
|
||||
|
||||
return CGSizeMake(width, height);
|
||||
}
|
||||
|
||||
|
||||
@end
|
||||
24
TUIKit/TUIChat/UI_Classic/Cell/Custom/TUIEvaluationCell.h
Normal file
24
TUIKit/TUIChat/UI_Classic/Cell/Custom/TUIEvaluationCell.h
Normal file
@@ -0,0 +1,24 @@
|
||||
//
|
||||
// TUIEvaluationCell.h
|
||||
// TUIChat
|
||||
//
|
||||
// Created by xia on 2022/6/10.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
//
|
||||
|
||||
#import <TIMCommon/TUIBubbleMessageCell.h>
|
||||
#import "TUIEvaluationCellData.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface TUIEvaluationCell : TUIBubbleMessageCell
|
||||
|
||||
@property(nonatomic, strong) UILabel *titleLabel;
|
||||
@property(nonatomic, strong) UILabel *commentLabel;
|
||||
@property(nonatomic, strong) NSMutableArray *starImageArray;
|
||||
|
||||
- (void)fillWithData:(TUIEvaluationCellData *)data;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
141
TUIKit/TUIChat/UI_Classic/Cell/Custom/TUIEvaluationCell.m
Normal file
141
TUIKit/TUIChat/UI_Classic/Cell/Custom/TUIEvaluationCell.m
Normal file
@@ -0,0 +1,141 @@
|
||||
//
|
||||
// TUIEvaluationCell.m
|
||||
// TUIChat
|
||||
//
|
||||
// Created by xia on 2022/6/10.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
//
|
||||
|
||||
#import "TUIEvaluationCell.h"
|
||||
#import <TUICore/TUIGlobalization.h>
|
||||
#import <TUICore/TUIThemeManager.h>
|
||||
|
||||
@implementation TUIEvaluationCell
|
||||
|
||||
- (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
|
||||
23
TUIKit/TUIChat/UI_Classic/Cell/Custom/TUILinkCell.h
Normal file
23
TUIKit/TUIChat/UI_Classic/Cell/Custom/TUILinkCell.h
Normal file
@@ -0,0 +1,23 @@
|
||||
//
|
||||
// MyCustomCell.h
|
||||
// TUIKitDemo
|
||||
//
|
||||
// Created by annidyfeng on 2019/6/10.
|
||||
// Copyright © 2019 Tencent. All rights reserved.
|
||||
//
|
||||
|
||||
#import <TIMCommon/TUIBubbleMessageCell.h>
|
||||
#import <TIMCommon/TUIMessageCell.h>
|
||||
#import "TUILinkCellData.h"
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface TUILinkCell : TUIBubbleMessageCell
|
||||
@property UILabel *myTextLabel;
|
||||
@property UILabel *myLinkLabel;
|
||||
|
||||
@property TUILinkCellData *customData;
|
||||
- (void)fillWithData:(TUILinkCellData *)data;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
95
TUIKit/TUIChat/UI_Classic/Cell/Custom/TUILinkCell.m
Normal file
95
TUIKit/TUIChat/UI_Classic/Cell/Custom/TUILinkCell.m
Normal file
@@ -0,0 +1,95 @@
|
||||
//
|
||||
// MyCustomCell.m
|
||||
// TUIKitDemo
|
||||
//
|
||||
// Created by annidyfeng on 2019/6/10.
|
||||
// Copyright © 2019 Tencent. All rights reserved.
|
||||
//
|
||||
|
||||
#import "TUILinkCell.h"
|
||||
#import <TUICore/TUIGlobalization.h>
|
||||
#import <TUICore/TUIThemeManager.h>
|
||||
|
||||
@implementation TUILinkCell
|
||||
|
||||
- (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.textAlignment = isRTL()?NSTextAlignmentRight:NSTextAlignmentLeft;
|
||||
_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.textAlignment = isRTL()?NSTextAlignmentRight:NSTextAlignmentLeft;
|
||||
_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
|
||||
26
TUIKit/TUIChat/UI_Classic/Cell/Custom/TUIOrderCell.h
Normal file
26
TUIKit/TUIChat/UI_Classic/Cell/Custom/TUIOrderCell.h
Normal file
@@ -0,0 +1,26 @@
|
||||
//
|
||||
// TUIOrderCell.h
|
||||
// TUIChat
|
||||
//
|
||||
// Created by xia on 2022/6/13.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
//
|
||||
|
||||
#import <TIMCommon/TUIBubbleMessageCell.h>
|
||||
#import "TUIOrderCellData.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface TUIOrderCell : TUIBubbleMessageCell
|
||||
|
||||
@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
|
||||
112
TUIKit/TUIChat/UI_Classic/Cell/Custom/TUIOrderCell.m
Normal file
112
TUIKit/TUIChat/UI_Classic/Cell/Custom/TUIOrderCell.m
Normal file
@@ -0,0 +1,112 @@
|
||||
//
|
||||
// TUIOrderCell.m
|
||||
// TUIChat
|
||||
//
|
||||
// Created by xia on 2022/6/13.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
//
|
||||
|
||||
#import "TUIOrderCell.h"
|
||||
#import <TUICore/TUIGlobalization.h>
|
||||
#import <TUICore/TUIThemeManager.h>
|
||||
|
||||
@implementation TUIOrderCell
|
||||
|
||||
- (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 {
|
||||
CGSize size = CGSizeMake(245, 80);
|
||||
return size;
|
||||
}
|
||||
|
||||
@end
|
||||
17
TUIKit/TUIChat/UI_Classic/Cell/Reply/TUIFileReplyQuoteView.h
Normal file
17
TUIKit/TUIChat/UI_Classic/Cell/Reply/TUIFileReplyQuoteView.h
Normal file
@@ -0,0 +1,17 @@
|
||||
//
|
||||
// TUIFileReplyQuoteView.h
|
||||
// TUIChat
|
||||
//
|
||||
// Created by harvy on 2021/11/25.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
//
|
||||
|
||||
#import "TUIVoiceReplyQuoteView.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface TUIFileReplyQuoteView : TUIVoiceReplyQuoteView
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
22
TUIKit/TUIChat/UI_Classic/Cell/Reply/TUIFileReplyQuoteView.m
Normal file
22
TUIKit/TUIChat/UI_Classic/Cell/Reply/TUIFileReplyQuoteView.m
Normal file
@@ -0,0 +1,22 @@
|
||||
//
|
||||
// TUIFileReplyQuoteView.m
|
||||
// TUIChat
|
||||
//
|
||||
// Created by harvy on 2021/11/25.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
//
|
||||
|
||||
#import "TUIFileReplyQuoteView.h"
|
||||
#import "TUIFileReplyQuoteViewData.h"
|
||||
|
||||
@implementation TUIFileReplyQuoteView
|
||||
|
||||
- (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.h
|
||||
// TUIChat
|
||||
//
|
||||
// Created by harvy on 2021/11/25.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
//
|
||||
|
||||
#import "TUIReplyQuoteView.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface TUIImageReplyQuoteView : TUIReplyQuoteView
|
||||
|
||||
@property(nonatomic, strong) UIImageView *imageView;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,66 @@
|
||||
//
|
||||
// TUIImageReplyQuoteView.m
|
||||
// TUIChat
|
||||
//
|
||||
// Created by harvy on 2021/11/25.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
//
|
||||
|
||||
#import "TUIImageReplyQuoteView.h"
|
||||
#import "TUIImageReplyQuoteViewData.h"
|
||||
|
||||
@implementation TUIImageReplyQuoteView
|
||||
|
||||
- (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.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface TUIMergeReplyQuoteView : TUIReplyQuoteView
|
||||
|
||||
@property(nonatomic, strong) UILabel *titleLabel;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,75 @@
|
||||
//
|
||||
// TUIMergeReplyQuoteView.m
|
||||
// TUIChat
|
||||
//
|
||||
// Created by harvy on 2021/11/25.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
//
|
||||
|
||||
#import "TUIMergeReplyQuoteView.h"
|
||||
#import <TUICore/TUIDarkModel.h>
|
||||
#import <TUICore/UIView+TUILayout.h>
|
||||
#import "TUIMergeReplyQuoteViewData.h"
|
||||
|
||||
@implementation TUIMergeReplyQuoteView
|
||||
|
||||
- (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,43 @@
|
||||
//
|
||||
// TUIReferenceMessageCell.h
|
||||
// TUIChat
|
||||
//
|
||||
// Created by wyl on 2022/5/24.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
//
|
||||
|
||||
#import <TIMCommon/TUIBubbleMessageCell.h>
|
||||
#import "TUIChatDefine.h"
|
||||
|
||||
@class TUIReplyMessageCellData;
|
||||
@class TUIReplyQuoteViewData;
|
||||
@class TUIImageVideoReplyQuoteViewData;
|
||||
@class TUIVoiceFileReplyQuoteViewData;
|
||||
@class TUIMergeReplyQuoteViewData;
|
||||
@class TUIReferenceMessageCellData;
|
||||
@class TUITextView;
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface TUIReferenceMessageCell : TUIBubbleMessageCell
|
||||
/**
|
||||
*
|
||||
* Border of quote view
|
||||
*/
|
||||
@property(nonatomic, strong) CALayer *quoteBorderLayer;
|
||||
|
||||
@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
|
||||
488
TUIKit/TUIChat/UI_Classic/Cell/Reply/TUIReferenceMessageCell.m
Normal file
488
TUIKit/TUIChat/UI_Classic/Cell/Reply/TUIReferenceMessageCell.m
Normal file
@@ -0,0 +1,488 @@
|
||||
//
|
||||
// TUIReferenceMessageCell.m
|
||||
// TUIChat
|
||||
//
|
||||
// Created by wyl on 2022/5/24.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
//
|
||||
|
||||
#import "TUIReferenceMessageCell.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 "TUIReplyMessageCell.h"
|
||||
#import "TUIReplyMessageCellData.h"
|
||||
#import "TUITextMessageCellData.h"
|
||||
#import "TUIVideoMessageCellData.h"
|
||||
#import "TUIVoiceMessageCellData.h"
|
||||
|
||||
#import "TUIFileReplyQuoteView.h"
|
||||
#import "TUIImageReplyQuoteView.h"
|
||||
#import "TUIMergeReplyQuoteView.h"
|
||||
#import "TUIReplyQuoteView.h"
|
||||
#import "TUITextMessageCell.h"
|
||||
#import "TUITextReplyQuoteView.h"
|
||||
#import "TUIVideoReplyQuoteView.h"
|
||||
#import "TUIVoiceReplyQuoteView.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 () <UITextViewDelegate,TUITextViewDelegate>
|
||||
|
||||
@property(nonatomic, strong) TUIReplyQuoteView *currentOriginView;
|
||||
@property(nonatomic, strong) NSMutableDictionary<NSString *, TUIReplyQuoteView *> *customOriginViewsCache;
|
||||
|
||||
@end
|
||||
|
||||
@implementation TUIReferenceMessageCell
|
||||
- (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.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.selectContent = data.content;
|
||||
self.textView.attributedText = [data.content getFormatEmojiStringWithFont:self.textView.font emojiLocations:self.referenceData.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.referenceData];
|
||||
|
||||
[self layoutBottomContainer];
|
||||
|
||||
}
|
||||
|
||||
// Override
|
||||
- (void)notifyBottomContainerReadyOfData:(TUIMessageCellData *)cellData {
|
||||
NSDictionary *param = @{TUICore_TUIChatExtension_BottomContainer_CellData : self.referenceData};
|
||||
[TUICore raiseExtension:TUICore_TUIChatExtension_BottomContainer_ClassicExtensionID 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];
|
||||
|
||||
[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);
|
||||
}];
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
BOOL hasRiskContent = self.messageData.innerMessage.hasRiskContent;
|
||||
if (hasRiskContent ) {
|
||||
[self.securityStrikeView mas_remakeConstraints:^(MASConstraintMaker *make) {
|
||||
make.top.mas_equalTo(self.textView.mas_bottom);
|
||||
make.width.mas_equalTo(self.bubbleView);
|
||||
make.bottom.mas_equalTo(self.container);
|
||||
}];
|
||||
}
|
||||
|
||||
[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.quoteView mas_remakeConstraints:^(MASConstraintMaker *make) {
|
||||
if (self.referenceData.direction == MsgDirectionIncoming) {
|
||||
make.leading.mas_equalTo(self.bubbleView);
|
||||
}
|
||||
else {
|
||||
make.trailing.mas_equalTo(self.bubbleView);
|
||||
}
|
||||
make.top.mas_equalTo(self.container.mas_bottom).mas_offset(6);
|
||||
make.size.mas_equalTo(self.referenceData.quoteSize);
|
||||
}];
|
||||
|
||||
if (self.referenceData.showMessageModifyReplies) {
|
||||
[self.messageModifyRepliesButton mas_remakeConstraints:^(MASConstraintMaker *make) {
|
||||
if (self.referenceData.direction == MsgDirectionIncoming) {
|
||||
make.leading.mas_equalTo(self.quoteView.mas_leading);
|
||||
}
|
||||
else {
|
||||
make.trailing.mas_equalTo(self.quoteView.mas_trailing);
|
||||
}
|
||||
make.top.mas_equalTo(self.quoteView.mas_bottom).mas_offset(3);
|
||||
make.size.mas_equalTo(self.messageModifyRepliesButton.frame.size);
|
||||
}];
|
||||
}
|
||||
[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);
|
||||
}
|
||||
else {
|
||||
make.leading.mas_equalTo(self.senderLabel.mas_trailing).mas_offset(3);
|
||||
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);
|
||||
}
|
||||
}];
|
||||
|
||||
}
|
||||
|
||||
- (TUIReplyQuoteView *)getCustomOriginView:(TUIMessageCellData *)originCellData {
|
||||
NSString *reuseId = originCellData ? NSStringFromClass(originCellData.class) : NSStringFromClass(TUITextMessageCellData.class);
|
||||
TUIReplyQuoteView *view = nil;
|
||||
BOOL reuse = NO;
|
||||
BOOL hasRiskContent = originCellData.innerMessage.hasRiskContent;
|
||||
if (hasRiskContent) {
|
||||
reuseId = @"hasRiskContent";
|
||||
}
|
||||
if ([self.customOriginViewsCache.allKeys containsObject:reuseId]) {
|
||||
view = [self.customOriginViewsCache objectForKey:reuseId];
|
||||
reuse = YES;
|
||||
}
|
||||
|
||||
if (hasRiskContent && view == nil){
|
||||
TUITextReplyQuoteView *quoteView = [[TUITextReplyQuoteView alloc] init];
|
||||
view = quoteView;
|
||||
}
|
||||
|
||||
if (view == nil) {
|
||||
Class class = [originCellData getReplyQuoteViewClass];
|
||||
if (class) {
|
||||
view = [[class alloc] init];
|
||||
}
|
||||
}
|
||||
|
||||
if (view == nil) {
|
||||
TUITextReplyQuoteView *quoteView = [[TUITextReplyQuoteView alloc] init];
|
||||
view = quoteView;
|
||||
}
|
||||
|
||||
if ([view isKindOfClass:[TUITextReplyQuoteView class]]) {
|
||||
TUITextReplyQuoteView *quoteView = (TUITextReplyQuoteView *)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 class]]) {
|
||||
TUIMergeReplyQuoteView *quoteView = (TUIMergeReplyQuoteView *)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 *_Nonnull obj, BOOL *_Nonnull stop) {
|
||||
obj.hidden = hidden;
|
||||
[obj reset];
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)layoutSubviews {
|
||||
[super layoutSubviews];
|
||||
}
|
||||
|
||||
- (void)layoutBottomContainer {
|
||||
if (CGSizeEqualToSize(self.referenceData.bottomContainerSize, CGSizeZero)) {
|
||||
return;
|
||||
}
|
||||
|
||||
CGSize size = self.referenceData.bottomContainerSize;
|
||||
CGFloat topMargin = self.bubbleView.mm_maxY + self.nameLabel.mm_h + 6;
|
||||
|
||||
[self.bottomContainer mas_remakeConstraints:^(MASConstraintMaker *make) {
|
||||
if (!self.messageModifyRepliesButton.isHidden){
|
||||
make.top.mas_equalTo(self.messageModifyRepliesButton.mas_bottom).mas_offset(8);
|
||||
}
|
||||
else {
|
||||
make.top.mas_equalTo(self.quoteView.mas_bottom).mas_offset(8);
|
||||
}
|
||||
make.size.mas_equalTo(size);
|
||||
if (self.referenceData.direction == MsgDirectionOutgoing) {
|
||||
make.trailing.mas_equalTo(self.container);
|
||||
}
|
||||
else {
|
||||
make.leading.mas_equalTo(self.container);
|
||||
}
|
||||
}];
|
||||
|
||||
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), oldRect.size.width, oldRect.size.height);
|
||||
self.messageModifyRepliesButton.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;
|
||||
}
|
||||
|
||||
- (UIView *)quoteView {
|
||||
if (_quoteView == nil) {
|
||||
_quoteView = [[UIView alloc] init];
|
||||
_quoteView.backgroundColor = TUIChatDynamicColor(@"chat_reference_message_quoteView_bg_color", @"#4444440c");
|
||||
_quoteView.layer.cornerRadius = 4.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:data withWidth:width];
|
||||
cellHeight += referenceCellData.quoteSize.height + referenceCellData.bottomContainerSize.height;
|
||||
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 customize quote placeholder view
|
||||
CGSize placeholderSize = [referenceCellData quotePlaceholderSizeWithType:referenceCellData.originMsgType data:referenceCellData.quoteData];
|
||||
|
||||
// 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 label which displays the content of replying the original message
|
||||
NSAttributedString *attributeString = [referenceCellData.content getFormatEmojiStringWithFont:[UIFont systemFontOfSize:16.0] 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));
|
||||
referenceCellData.textSize = size;
|
||||
referenceCellData.textOrigin = CGPointMake(referenceCellData.cellLayout.bubbleInsets.left,
|
||||
referenceCellData.cellLayout.bubbleInsets.top + [TUIBubbleMessageCell getBubbleTop:referenceCellData]);
|
||||
|
||||
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.incommingBubble.size.height);
|
||||
} else {
|
||||
size.height = MAX(size.height, TUIBubbleMessageCell.outgoingBubble.size.height);
|
||||
}
|
||||
|
||||
BOOL hasRiskContent = referenceCellData.innerMessage.hasRiskContent;
|
||||
if (hasRiskContent) {
|
||||
size.width = MAX(size.width, 200);// width must more than TIMCommonLocalizableString(TUIKitMessageTypeSecurityStrike)
|
||||
size.height += kTUISecurityStrikeViewTopLineMargin;
|
||||
size.height += kTUISecurityStrikeViewTopLineToBottom;
|
||||
}
|
||||
|
||||
quoteWidth = senderRect.size.width;
|
||||
quoteWidth += placeholderSize.width;
|
||||
quoteWidth += (quotePlaceHolderMarginWidth * 2);
|
||||
|
||||
if (showRevokeStr) {
|
||||
quoteWidth = messageRevokeRect.size.width;
|
||||
}
|
||||
|
||||
quoteHeight = MAX(senderRect.size.height, placeholderSize.height);
|
||||
quoteHeight += (8 + 8);
|
||||
|
||||
referenceCellData.senderSize = CGSizeMake(senderRect.size.width, senderRect.size.height);
|
||||
referenceCellData.quotePlaceholderSize = placeholderSize;
|
||||
// self.replyContentSize = CGSizeMake(replyContentRect.size.width, replyContentRect.size.height);
|
||||
referenceCellData.quoteSize = CGSizeMake(quoteWidth, quoteHeight);
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
@end
|
||||
38
TUIKit/TUIChat/UI_Classic/Cell/Reply/TUIReplyMessageCell.h
Normal file
38
TUIKit/TUIChat/UI_Classic/Cell/Reply/TUIReplyMessageCell.h
Normal file
@@ -0,0 +1,38 @@
|
||||
//
|
||||
// TUIReplyMessageCell.h
|
||||
// TUIChat
|
||||
//
|
||||
// Created by harvy on 2021/11/11.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
//
|
||||
|
||||
#import <TIMCommon/TUIBubbleMessageCell.h>
|
||||
#import <TIMCommon/TUITextView.h>
|
||||
#import "TUIChatDefine.h"
|
||||
|
||||
@class TUIReplyMessageCellData;
|
||||
@class TUIReplyQuoteViewData;
|
||||
@class TUIImageVideoReplyQuoteViewData;
|
||||
@class TUIVoiceFileReplyQuoteViewData;
|
||||
@class TUIMergeReplyQuoteViewData;
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface TUIReplyMessageCell : TUIBubbleMessageCell
|
||||
|
||||
@property(nonatomic, strong) UIView *quoteBorderLine;
|
||||
@property(nonatomic, strong) UIView *quoteView;
|
||||
|
||||
@property(nonatomic, strong) TUITextView *textView;
|
||||
@property(nonatomic, strong) NSString *selectContent;
|
||||
@property(nonatomic, strong) TUIReplySelectAllContentCallback selectAllContentContent;
|
||||
|
||||
@property(nonatomic, strong) UILabel *senderLabel;
|
||||
|
||||
@property(nonatomic, strong) TUIReplyMessageCellData *replyData;
|
||||
|
||||
- (void)fillWithData:(TUIReplyMessageCellData *)data;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
484
TUIKit/TUIChat/UI_Classic/Cell/Reply/TUIReplyMessageCell.m
Normal file
484
TUIKit/TUIChat/UI_Classic/Cell/Reply/TUIReplyMessageCell.m
Normal file
@@ -0,0 +1,484 @@
|
||||
//
|
||||
// TUIReplyMessageCell.m
|
||||
// TUIChat
|
||||
//
|
||||
// Created by harvy on 2021/11/11.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
//
|
||||
|
||||
#import "TUIReplyMessageCell.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.h"
|
||||
#import "TUIImageReplyQuoteView.h"
|
||||
#import "TUIMergeReplyQuoteView.h"
|
||||
#import "TUIReplyQuoteView.h"
|
||||
#import "TUITextReplyQuoteView.h"
|
||||
#import "TUIVideoReplyQuoteView.h"
|
||||
#import "TUIVoiceReplyQuoteView.h"
|
||||
|
||||
@interface TUIReplyMessageCell () <UITextViewDelegate,TUITextViewDelegate>
|
||||
|
||||
@property(nonatomic, strong) TUIReplyQuoteView *currentOriginView;
|
||||
|
||||
@property(nonatomic, strong) NSMutableDictionary<NSString *, TUIReplyQuoteView *> *customOriginViewsCache;
|
||||
|
||||
@end
|
||||
|
||||
@implementation TUIReplyMessageCell
|
||||
|
||||
- (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_ClassicExtensionID parentView:self.bottomContainer param:param];
|
||||
}
|
||||
|
||||
- (void)fillWithData:(TUIReplyMessageCellData *)data {
|
||||
[super fillWithData:data];
|
||||
self.replyData = data;
|
||||
self.senderLabel.text = [NSString stringWithFormat:@"%@:", data.sender];
|
||||
self.textView.attributedText = [data.content getFormatEmojiStringWithFont:self.textView.font emojiLocations:self.replyData.emojiLocations];
|
||||
self.bottomContainer.hidden = CGSizeEqualToSize(data.bottomContainerSize, CGSizeZero);
|
||||
|
||||
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];
|
||||
}
|
||||
|
||||
@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);
|
||||
}];
|
||||
|
||||
BOOL hasRiskContent = self.messageData.innerMessage.hasRiskContent;
|
||||
if (hasRiskContent ) {
|
||||
[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.size.mas_equalTo(self.replyData.replyContentSize);
|
||||
}];
|
||||
|
||||
[self.securityStrikeView mas_remakeConstraints:^(MASConstraintMaker *make) {
|
||||
make.top.mas_equalTo(self.textView.mas_bottom);
|
||||
make.width.mas_equalTo(self.bubbleView);
|
||||
make.bottom.mas_equalTo(self.container);
|
||||
}];
|
||||
}
|
||||
|
||||
[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 *)getCustomOriginView:(TUIMessageCellData *)originCellData {
|
||||
NSString *reuseId = originCellData ? NSStringFromClass(originCellData.class) : NSStringFromClass(TUITextMessageCellData.class);
|
||||
TUIReplyQuoteView *view = nil;
|
||||
BOOL reuse = NO;
|
||||
BOOL hasRiskContent = originCellData.innerMessage.hasRiskContent;
|
||||
if (hasRiskContent) {
|
||||
reuseId = @"hasRiskContent";
|
||||
}
|
||||
if ([self.customOriginViewsCache.allKeys containsObject:reuseId]) {
|
||||
view = [self.customOriginViewsCache objectForKey:reuseId];
|
||||
reuse = YES;
|
||||
}
|
||||
|
||||
if (hasRiskContent && view == nil){
|
||||
TUITextReplyQuoteView *quoteView = [[TUITextReplyQuoteView alloc] init];
|
||||
view = quoteView;
|
||||
}
|
||||
|
||||
if (view == nil) {
|
||||
Class class = [originCellData getReplyQuoteViewClass];
|
||||
if (class) {
|
||||
view = [[class alloc] init];
|
||||
}
|
||||
}
|
||||
|
||||
if (view == nil) {
|
||||
TUITextReplyQuoteView *quoteView = [[TUITextReplyQuoteView alloc] init];
|
||||
view = quoteView;
|
||||
}
|
||||
|
||||
if ([view isKindOfClass:[TUITextReplyQuoteView class]]) {
|
||||
TUITextReplyQuoteView *quoteView = (TUITextReplyQuoteView *)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 class]]) {
|
||||
TUIMergeReplyQuoteView *quoteView = (TUIMergeReplyQuoteView *)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 *_Nonnull obj, BOOL *_Nonnull stop) {
|
||||
obj.hidden = hidden;
|
||||
[obj reset];
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)layoutSubviews {
|
||||
[super layoutSubviews];
|
||||
|
||||
}
|
||||
|
||||
- (void)layoutBottomContainer {
|
||||
if (CGSizeEqualToSize(self.replyData.bottomContainerSize, CGSizeZero)) {
|
||||
return;
|
||||
}
|
||||
|
||||
CGSize size = self.replyData.bottomContainerSize;
|
||||
CGFloat topMargin = self.bubbleView.mm_maxY + self.nameLabel.mm_h + 8;
|
||||
[self.bottomContainer mas_remakeConstraints:^(MASConstraintMaker *make) {
|
||||
make.top.mas_equalTo(self.bubbleView.mas_bottom).mas_offset(8);
|
||||
make.size.mas_equalTo(size);
|
||||
if (self.replyData.direction == MsgDirectionOutgoing) {
|
||||
make.trailing.mas_equalTo(self.container);
|
||||
}
|
||||
else {
|
||||
make.leading.mas_equalTo(self.container);
|
||||
}
|
||||
}];
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
- (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;
|
||||
}
|
||||
|
||||
- (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.replyData.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: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 + kScale375(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;
|
||||
UIFont *font = [UIFont systemFontOfSize:16.0];
|
||||
|
||||
// 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];
|
||||
|
||||
// Calculate the size of revoke string
|
||||
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
|
||||
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;
|
||||
|
||||
BOOL lineSpacingChecked = NO ;
|
||||
if (quoteWidth > quoteMaxWidth) {
|
||||
quoteWidth = quoteMaxWidth;
|
||||
//line spacing
|
||||
lineSpacingChecked = YES;
|
||||
}
|
||||
if (quoteWidth < quoteMinWidth) {
|
||||
quoteWidth = quoteMinWidth;
|
||||
}
|
||||
if (showRevokeStr) {
|
||||
quoteWidth = MAX(quoteWidth, messageRevokeRect.size.width);
|
||||
}
|
||||
|
||||
quoteHeight = 3 + senderRect.size.height + 4 + placeholderSize.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);
|
||||
|
||||
// cell
|
||||
// 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 (lineSpacingChecked) {
|
||||
if ((int)replyContentRect2.size.width % (int)quoteWidth == 0 ||
|
||||
(int)replyContentRect2.size.width % (int)quoteWidth + font.lineHeight > quoteWidth) {
|
||||
height += font.lineHeight;
|
||||
}
|
||||
}
|
||||
CGSize size = CGSizeMake(quoteWidth + TReplyQuoteView_Margin_Width, height);
|
||||
|
||||
BOOL hasRiskContent = replyCellData.innerMessage.hasRiskContent;
|
||||
if (hasRiskContent) {
|
||||
size.width = MAX(size.width, 200);// width must more than TIMCommonLocalizableString(TUIKitMessageTypeSecurityStrike)
|
||||
size.height += kTUISecurityStrikeViewTopLineMargin;
|
||||
size.height += kTUISecurityStrikeViewTopLineToBottom;
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
||||
@end
|
||||
23
TUIKit/TUIChat/UI_Classic/Cell/Reply/TUIReplyQuoteView.h
Normal file
23
TUIKit/TUIChat/UI_Classic/Cell/Reply/TUIReplyQuoteView.h
Normal file
@@ -0,0 +1,23 @@
|
||||
//
|
||||
// TUIReplyQuoteView.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 : UIView
|
||||
|
||||
@property(nonatomic, strong) TUIReplyQuoteViewData *data;
|
||||
|
||||
- (void)fillWithData:(TUIReplyQuoteViewData *)data;
|
||||
- (void)reset;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
22
TUIKit/TUIChat/UI_Classic/Cell/Reply/TUIReplyQuoteView.m
Normal file
22
TUIKit/TUIChat/UI_Classic/Cell/Reply/TUIReplyQuoteView.m
Normal file
@@ -0,0 +1,22 @@
|
||||
//
|
||||
// TUIReplyQuoteView.m
|
||||
// TUIChat
|
||||
//
|
||||
// Created by harvy on 2021/11/25.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
//
|
||||
|
||||
#import "TUIReplyQuoteView.h"
|
||||
#import <TIMCommon/NSString+TUIEmoji.h>
|
||||
#import "TUIReplyQuoteViewData.h"
|
||||
|
||||
@implementation TUIReplyQuoteView
|
||||
|
||||
- (void)fillWithData:(TUIReplyQuoteViewData *)data {
|
||||
_data = data;
|
||||
}
|
||||
|
||||
- (void)reset {
|
||||
}
|
||||
|
||||
@end
|
||||
19
TUIKit/TUIChat/UI_Classic/Cell/Reply/TUITextReplyQuoteView.h
Normal file
19
TUIKit/TUIChat/UI_Classic/Cell/Reply/TUITextReplyQuoteView.h
Normal file
@@ -0,0 +1,19 @@
|
||||
//
|
||||
// TUITextReplyQuoteView.h
|
||||
// TUIChat
|
||||
//
|
||||
// Created by harvy on 2021/11/25.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
//
|
||||
|
||||
#import "TUIReplyQuoteView.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface TUITextReplyQuoteView : TUIReplyQuoteView
|
||||
|
||||
@property(nonatomic, strong) UILabel *textLabel;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
72
TUIKit/TUIChat/UI_Classic/Cell/Reply/TUITextReplyQuoteView.m
Normal file
72
TUIKit/TUIChat/UI_Classic/Cell/Reply/TUITextReplyQuoteView.m
Normal file
@@ -0,0 +1,72 @@
|
||||
//
|
||||
// TUITextReplyQuoteView.m
|
||||
// TUIChat
|
||||
//
|
||||
// Created by harvy on 2021/11/25.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
//
|
||||
|
||||
#import "TUITextReplyQuoteView.h"
|
||||
#import <TIMCommon/NSString+TUIEmoji.h>
|
||||
#import <TUICore/TUIThemeManager.h>
|
||||
#import "TUITextReplyQuoteViewData.h"
|
||||
|
||||
@implementation TUITextReplyQuoteView
|
||||
|
||||
- (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];
|
||||
}
|
||||
|
||||
// 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.h
|
||||
// TUIChat
|
||||
//
|
||||
// Created by harvy on 2021/11/25.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
//
|
||||
|
||||
#import "TUIImageReplyQuoteView.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface TUIVideoReplyQuoteView : TUIImageReplyQuoteView
|
||||
|
||||
@property(nonatomic, strong) UIImageView *playView;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,61 @@
|
||||
//
|
||||
// TUIVideoReplyQuoteView.m
|
||||
// TUIChat
|
||||
//
|
||||
// Created by harvy on 2021/11/25.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
//
|
||||
|
||||
#import <TIMCommon/TIMDefine.h>
|
||||
#import <TUICore/TUIDarkModel.h>
|
||||
#import "TUIVideoReplyQuoteView.h"
|
||||
#import "TUIVideoReplyQuoteViewData.h"
|
||||
|
||||
@implementation TUIVideoReplyQuoteView
|
||||
|
||||
- (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.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface TUIVoiceReplyQuoteView : TUITextReplyQuoteView
|
||||
|
||||
@property(nonatomic, strong) UIImageView *iconView;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,79 @@
|
||||
//
|
||||
// TUIVoiceReplyQuoteView.m
|
||||
// TUIChat
|
||||
//
|
||||
// Created by harvy on 2021/11/25.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
//
|
||||
|
||||
#import "TUIVoiceReplyQuoteView.h"
|
||||
#import <TIMCommon/NSString+TUIEmoji.h>
|
||||
#import <TIMCommon/TIMDefine.h>
|
||||
#import <TUICore/TUIDarkModel.h>
|
||||
#import "TUIVoiceReplyQuoteViewData.h"
|
||||
|
||||
@implementation TUIVoiceReplyQuoteView
|
||||
|
||||
- (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
|
||||
Reference in New Issue
Block a user