增加换肤功能

This commit is contained in:
启星
2025-08-14 10:07:49 +08:00
parent f6964c1e89
commit 4f9318d98e
8789 changed files with 978530 additions and 2 deletions

View File

@@ -0,0 +1,102 @@
// Created by Tencent on 2023/06/09.
// Copyright © 2023 Tencent. All rights reserved.
/**
*
*
* This document declares the TUIBubbleMessageCell_Minimalist class.
* Bubble messages, the most common type of messages that contain strings and emoticons.
* Both TUIFileMessageCell_Minimalist and TUIVoiceMessageCell_Minimalist inherit from this class and implement the userinterface of bubble messages.
* If developers want to customize the bubble message, they can also refer to the implementation methods of the above two message units to implement their own
* bubble message unit.
*/
#import "TUIBubbleMessageCellData.h"
#import "TUIMessageCell_Minimalist.h"
NS_ASSUME_NONNULL_BEGIN
@interface TUIBubbleMessageCell_Minimalist : TUIMessageCell_Minimalist
/**
* The bubble image view, the message's bubble icon, wraps the message's content on the UI as a background panel for the bubble.
*/
@property(nonatomic, strong) UIImageView *bubbleView;
@property(nonatomic, strong) TUIBubbleMessageCellData *bubbleData;
- (void)fillWithData:(TUIBubbleMessageCellData *)data;
+ (CGFloat)getBubbleTop:(TUIBubbleMessageCellData *)data;
@end
#pragma mark - TUILayoutConfiguration
@interface TUIBubbleMessageCell_Minimalist (TUILayoutConfiguration)
/**
* Send bubble icon (normal state)
* - The send icon of the bubble, assigned to the @bubble when the bubble message was sent.
*/
@property(nonatomic, class) UIImage *outgoingBubble;
/**
* Send bubble icon (normal state)
* - The send icon of the bubble, assigned to the @bubble when the bubble message was sent.
*/
@property(nonatomic, class) UIImage *outgoingSameBubble;
/**
* Send bubble icon (highlighted state)
* - The send icon (highlighted state) of the bubble, assigned to @highlightedBubble when the bubble message was sent.
*/
@property(nonatomic, class) UIImage *outgoingHighlightedBubble;
/**
* Send bubble icon (highlighted state)
*/
@property(nonatomic, class) UIImage *outgoingAnimatedHighlightedAlpha20;
@property(nonatomic, class) UIImage *outgoingAnimatedHighlightedAlpha50;
/**
*
* Receive bubble icon (normal state)
* - The receive icon of the bubble, assigned to the @bubble when the bubble message was received.
*/
@property(nonatomic, class) UIImage *incommingBubble;
/**
*
* Receive bubble icon (normal state)
* - The receive icon of the bubble, assigned to the @bubble when the bubble message was received.
*/
@property(nonatomic, class) UIImage *incommingSameBubble;
/**
*
* Receive bubble icon (highlighted state)
* - The receive icon of the bubble, assigned to @highlightedBubble when the bubble message was received.
*/
@property(nonatomic, class) UIImage *incommingHighlightedBubble;
/**
*
* Receive bubble icon (highlighted state)
*/
@property(nonatomic, class) UIImage *incommingAnimatedHighlightedAlpha20;
@property(nonatomic, class) UIImage *incommingAnimatedHighlightedAlpha50;
/**
* Spacing at the top of the send bubble
* - It is used to locate the top of the sent bubble, and is assigned to @bubbleTop when the bubble message was sent.
*/
@property(nonatomic, class) CGFloat outgoingBubbleTop;
/**
* Spacing at the top of the receiving bubble
* - It is used to locate the top of the receive bubble, and is assigned to @bubbleTop when the bubble message was received.
*/
@property(nonatomic, class) CGFloat incommingBubbleTop;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,356 @@
//
// TUIBubbleMessageCell_Minimalist.m
// TXIMSDK_TUIKit_iOS
//
// Created by annidyfeng on 2019/5/22.
// Copyright © 2023 Tencent. All rights reserved.
//
#import "TUIBubbleMessageCell_Minimalist.h"
#import <TIMCommon/TIMCommonModel.h>
#import <TIMCommon/TIMDefine.h>
@implementation TUIBubbleMessageCell_Minimalist
- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier {
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
if (self) {
_bubbleView = [[UIImageView alloc] initWithFrame:self.container.bounds];
_bubbleView.userInteractionEnabled = YES;
[self.container addSubview:_bubbleView];
_bubbleView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
}
return self;
}
- (void)fillWithData:(TUIBubbleMessageCellData *)data {
[super fillWithData:data];
_bubbleData = data;
if (self.bubbleData.sameToNextMsgSender) {
self.bubbleView.image = self.getSameMessageBubble;
self.bubbleView.highlightedImage = self.getHighlightSameMessageBubble;
} else {
self.bubbleView.image = self.getBubble;
self.bubbleView.highlightedImage = self.getHighlightBubble;
}
// 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.bubbleView mas_remakeConstraints:^(MASConstraintMaker *make) {
make.leading.mas_equalTo(0);
make.size.mas_equalTo(self.container);
make.top.mas_equalTo(self.container);
}];
CGPoint center = self.retryView.center;
center.y = self.bubbleView.center.y;
self.retryView.center = center;
}
- (void)layoutSubviews {
[super layoutSubviews];
}
- (void)highlightWhenMatchKeyword:(NSString *)keyword {
/**
* The parent class implements the default highlighting effect - flickering
*/
if (keyword) {
if (self.highlightAnimating) {
return;
}
[self animate:3];
}
}
- (void)animate:(int)times {
times--;
if (times < 0) {
if (self.bubbleData.sameToNextMsgSender) {
self.bubbleView.image = self.getSameMessageBubble;
} else {
self.bubbleView.image = self.getBubble;
}
self.bubbleView.layer.cornerRadius = 0;
self.bubbleView.layer.masksToBounds = YES;
self.highlightAnimating = NO;
return;
}
self.bubbleView.image = self.getAnimateHighlightBubble_alpha50;
self.bubbleView.layer.cornerRadius = 12;
self.bubbleView.layer.masksToBounds = YES;
self.highlightAnimating = YES;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.25 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
self.bubbleView.image = self.getAnimateHighlightBubble_alpha20;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.25 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
if (!self.bubbleData.highlightKeyword) {
[self animate:0];
return;
}
[self animate:times];
});
});
}
- (CGFloat)getBubbleTop {
return [self.class getBubbleTop:self.bubbleData];
}
- (UIImage *)getBubble {
if (!TIMConfig.defaultConfig.enableMessageBubble) {
return nil;
}
if (self.bubbleData.direction == MsgDirectionIncoming) {
return self.class.incommingBubble;
} else {
return self.class.outgoingBubble;
}
}
- (UIImage *)getHighlightBubble {
if (!TIMConfig.defaultConfig.enableMessageBubble) {
return nil;
}
if (self.bubbleData.direction == MsgDirectionIncoming) {
return self.class.incommingHighlightedBubble;
} else {
return self.class.outgoingHighlightedBubble;
}
}
- (UIImage *)getAnimateHighlightBubble_alpha50 {
if (!TIMConfig.defaultConfig.enableMessageBubble) {
return nil;
}
if (self.bubbleData.direction == MsgDirectionIncoming) {
return self.class.incommingAnimatedHighlightedAlpha50;
} else {
return self.class.outgoingAnimatedHighlightedAlpha50;
}
}
- (UIImage *)getAnimateHighlightBubble_alpha20 {
if (!TIMConfig.defaultConfig.enableMessageBubble) {
return nil;
}
if (self.bubbleData.direction == MsgDirectionIncoming) {
return self.class.incommingAnimatedHighlightedAlpha20;
} else {
return self.class.outgoingAnimatedHighlightedAlpha20;
}
}
- (UIImage *)getSameMessageBubble {
if (!TIMConfig.defaultConfig.enableMessageBubble) {
return nil;
}
return self.bubbleData.direction == MsgDirectionIncoming ? self.class.incommingSameBubble : self.class.outgoingSameBubble;
}
- (UIImage *)getHighlightSameMessageBubble {
if (!TIMConfig.defaultConfig.enableMessageBubble) {
return nil;
}
return self.getSameMessageBubble;
}
+ (CGFloat)getBubbleTop:(TUIBubbleMessageCellData *)data {
if (data.direction == MsgDirectionIncoming) {
return self.class.incommingBubbleTop;
} else {
return self.class.outgoingBubbleTop;
}
}
@end
@implementation TUIBubbleMessageCell_Minimalist (TUILayoutConfiguration)
+ (void)initialize {
[NSNotificationCenter.defaultCenter addObserver:self selector:@selector(onThemeChanged:) name:TUIDidApplyingThemeChangedNotfication object:nil];
}
#pragma mark - gOutgoingBubble
static UIImage *gOutgoingBubble;
+ (UIImage *)outgoingBubble {
if (!gOutgoingBubble) {
UIImage *defaultImage = [[TUIImageCache sharedInstance] getResourceFromCache:TUIChatImagePath_Minimalist(@"SenderTextNodeBkg")];
[self setOutgoingBubble:defaultImage];
}
return gOutgoingBubble;
}
+ (void)setOutgoingBubble:(UIImage *)outgoingBubble {
gOutgoingBubble = [self stretchImage:outgoingBubble];
}
#pragma mark - gOutgoingSameBubble
static UIImage *gOutgoingSameBubble;
+ (UIImage *)outgoingSameBubble {
if (!gOutgoingSameBubble) {
UIImage *defaultImage = [[TUIImageCache sharedInstance] getResourceFromCache:TUIChatImagePath_Minimalist(@"SenderTextNodeBkg_Same")];
[self setOutgoingSameBubble:defaultImage];
}
return gOutgoingSameBubble;
}
+ (void)setOutgoingSameBubble:(UIImage *)outgoingSameBubble {
gOutgoingSameBubble = [self stretchImage:outgoingSameBubble];
}
#pragma mark - gOutgoingHighlightedBubble
static UIImage *gOutgoingHighlightedBubble;
+ (UIImage *)outgoingHighlightedBubble {
if (!gOutgoingHighlightedBubble) {
UIImage *defaultImage = [[TUIImageCache sharedInstance] getResourceFromCache:TUIChatImagePath_Minimalist(@"SenderTextNodeBkg")];
[self setOutgoingHighlightedBubble:defaultImage];
}
return gOutgoingHighlightedBubble;
}
+ (void)setOutgoingHighlightedBubble:(UIImage *)outgoingHighlightedBubble {
gOutgoingHighlightedBubble = [self stretchImage:outgoingHighlightedBubble];
}
#pragma mark - gOutgoingAnimatedHighlightedAlpha20
static UIImage *gOutgoingAnimatedHighlightedAlpha20;
+ (UIImage *)outgoingAnimatedHighlightedAlpha20 {
if (!gOutgoingAnimatedHighlightedAlpha20) {
UIImage *alpha20 = [[TUIImageCache sharedInstance] getResourceFromCache:TUIChatImagePath(@"SenderTextNodeBkg_alpha20")];
[self setOutgoingAnimatedHighlightedAlpha20:TUIChatDynamicImage(@"chat_bubble_send_alpha20_img", alpha20)];
}
return gOutgoingAnimatedHighlightedAlpha20;
}
+ (void)setOutgoingAnimatedHighlightedAlpha20:(UIImage *)outgoingAnimatedHighlightedAlpha20 {
gOutgoingAnimatedHighlightedAlpha20 = [self stretchImage:outgoingAnimatedHighlightedAlpha20];
}
#pragma mark - gOutgoingAnimatedHighlightedAlpha50
static UIImage *gOutgoingAnimatedHighlightedAlpha50;
+ (UIImage *)outgoingAnimatedHighlightedAlpha50 {
if (!gOutgoingAnimatedHighlightedAlpha50) {
UIImage *alpha50 = [[TUIImageCache sharedInstance] getResourceFromCache:TUIChatImagePath(@"SenderTextNodeBkg_alpha50")];
[self setOutgoingAnimatedHighlightedAlpha50:TUIChatDynamicImage(@"chat_bubble_send_alpha50_img", alpha50)];
}
return gOutgoingAnimatedHighlightedAlpha50;
}
+ (void)setOutgoingAnimatedHighlightedAlpha50:(UIImage *)outgoingAnimatedHighlightedAlpha50 {
gOutgoingAnimatedHighlightedAlpha50 = [self stretchImage:outgoingAnimatedHighlightedAlpha50];
}
#pragma mark - gIncommingBubble
static UIImage *gIncommingBubble;
+ (UIImage *)incommingBubble {
if (!gIncommingBubble) {
UIImage *defaultImage = [[TUIImageCache sharedInstance] getResourceFromCache:TUIChatImagePath_Minimalist(@"ReceiverTextNodeBkg")];
[self setIncommingBubble:defaultImage];
}
return gIncommingBubble;
}
+ (void)setIncommingBubble:(UIImage *)incommingBubble {
gIncommingBubble = [self stretchImage:incommingBubble];
}
#pragma mark - gIncommingSameBubble
static UIImage *gIncommingSameBubble;
+ (UIImage *)incommingSameBubble {
if (!gIncommingSameBubble) {
UIImage *defaultImage = [[TUIImageCache sharedInstance] getResourceFromCache:TUIChatImagePath_Minimalist(@"ReceiverTextNodeBkg_Same")];
[self setIncommingSameBubble:defaultImage];
}
return gIncommingSameBubble;
}
+ (void)setIncommingSameBubble:(UIImage *)incommingSameBubble {
gIncommingSameBubble = [self stretchImage:incommingSameBubble];
}
#pragma mark - gIncommingHighlightedBubble
static UIImage *gIncommingHighlightedBubble;
+ (UIImage *)incommingHighlightedBubble {
if (!gIncommingHighlightedBubble) {
UIImage *defaultImage = [[TUIImageCache sharedInstance] getResourceFromCache:TUIChatImagePath_Minimalist(@"ReceiverTextNodeBkg")];
[self setIncommingHighlightedBubble:defaultImage];
}
return gIncommingHighlightedBubble;
}
+ (void)setIncommingHighlightedBubble:(UIImage *)incommingHighlightedBubble {
gIncommingHighlightedBubble = [self stretchImage:incommingHighlightedBubble];
}
#pragma mark - gIncommingAnimatedHighlightedAlpha20
static UIImage *gIncommingAnimatedHighlightedAlpha20;
+ (UIImage *)incommingAnimatedHighlightedAlpha20 {
if (!gIncommingAnimatedHighlightedAlpha20) {
UIImage *alpha20 = [[TUIImageCache sharedInstance] getResourceFromCache:TUIChatImagePath(@"ReceiverTextNodeBkg_alpha20")];
[self setIncommingAnimatedHighlightedAlpha20:TUIChatDynamicImage(@"chat_bubble_receive_alpha20_img", alpha20)];
}
return gIncommingAnimatedHighlightedAlpha20;
}
+ (void)setIncommingAnimatedHighlightedAlpha20:(UIImage *)incommingAnimatedHighlightedAlpha20 {
gIncommingAnimatedHighlightedAlpha20 = [self stretchImage:incommingAnimatedHighlightedAlpha20];
}
#pragma mark - gIncommingAnimatedHighlightedAlpha50
static UIImage *gIncommingAnimatedHighlightedAlpha50;
+ (UIImage *)incommingAnimatedHighlightedAlpha50 {
if (!gIncommingAnimatedHighlightedAlpha50) {
UIImage *alpha50 = [[TUIImageCache sharedInstance] getResourceFromCache:TUIChatImagePath(@"ReceiverTextNodeBkg_alpha50")];
[self setIncommingAnimatedHighlightedAlpha50:TUIChatDynamicImage(@"chat_bubble_receive_alpha50_img", alpha50)];
}
return gIncommingAnimatedHighlightedAlpha50;
}
+ (void)setIncommingAnimatedHighlightedAlpha50:(UIImage *)incommingAnimatedHighlightedAlpha50 {
gIncommingAnimatedHighlightedAlpha50 = [self stretchImage:incommingAnimatedHighlightedAlpha50];
}
+ (UIImage *)stretchImage:(UIImage *)oldImage {
UIImage *image = [oldImage rtl_imageFlippedForRightToLeftLayoutDirection];
UIEdgeInsets insets = rtlEdgeInsetsWithInsets(UIEdgeInsetsFromString(@"{12,12,12,12}"));
return [image resizableImageWithCapInsets:insets resizingMode:UIImageResizingModeStretch];
}
static CGFloat gOutgoingBubbleTop = 0;
+ (CGFloat)outgoingBubbleTop {
return gOutgoingBubbleTop;
}
+ (void)setOutgoingBubbleTop:(CGFloat)outgoingBubble {
gOutgoingBubbleTop = outgoingBubble;
}
static CGFloat gIncommingBubbleTop = 0;
+ (CGFloat)incommingBubbleTop {
return gIncommingBubbleTop;
}
+ (void)setIncommingBubbleTop:(CGFloat)incommingBubbleTop {
gIncommingBubbleTop = incommingBubbleTop;
}
+ (void)onThemeChanged:(NSNotification *)notice {
gOutgoingBubble = nil;
gOutgoingHighlightedBubble = nil;
gOutgoingAnimatedHighlightedAlpha50 = nil;
gOutgoingAnimatedHighlightedAlpha20 = nil;
gIncommingBubble = nil;
gIncommingHighlightedBubble = nil;
gIncommingAnimatedHighlightedAlpha50 = nil;
gIncommingAnimatedHighlightedAlpha20 = nil;
}
@end

View File

@@ -0,0 +1,41 @@
//
// TUICommonGroupInfoCellData_Minimalist.h
// TIMCommon
//
// Created by yiliangwang on 2024/12/26.
// Copyright © 2024 Tencent. All rights reserved.
#import <Foundation/Foundation.h>
#import <TIMCommon/TIMCommonModel.h>
NS_ASSUME_NONNULL_BEGIN
@interface TUIGroupButtonCellData_Minimalist : TUICommonCellData
@property(nonatomic, strong) NSString *title;
@property SEL cbuttonSelector;
@property TUIButtonStyle style;
@property(nonatomic, strong) UIColor *textColor;
@property(nonatomic, assign) BOOL hideSeparatorLine;
@property(nonatomic, assign) BOOL isInfoPageLeftButton;
@end
@interface TUIGroupMemberCellData_Minimalist : TUICommonCellData
@property(nonatomic, strong) NSString *identifier;
@property(nonatomic, strong) NSString *name;
@property(nonatomic, strong) UIImage *avatarImage;
@property(nonatomic, strong) NSString *avatarUrl;
@property(nonatomic, assign) BOOL showAccessory;
@property(nonatomic, copy) NSString *detailName;
@property NSInteger tag;
@end
@interface TUICommonGroupInfoCellData_Minimalist : NSObject
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,24 @@
//
// TUICommonGroupInfoCellData_Minimalist.m
// TIMCommon
//
// Created by yiliangwang on 2024/12/26.
// Copyright © 2024 Tencent. All rights reserved.
#import "TUICommonGroupInfoCellData_Minimalist.h"
#import <TIMCommon/TIMDefine.h>
#import <TUICore/TUIThemeManager.h>
@implementation TUIGroupButtonCellData_Minimalist
- (CGFloat)heightOfWidth:(CGFloat)width {
return 56;
}
@end
@implementation TUIGroupMemberCellData_Minimalist
- (CGFloat)heightOfWidth:(CGFloat)width {
return kScale390(48);
}
@end
@implementation TUICommonGroupInfoCellData_Minimalist
@end

View File

@@ -0,0 +1,28 @@
//
// TUIConfig_Minimalist.h
// TIMCommon
//
// Created by Tencent on 2024/7/16.
// Copyright © 2024 Tencent. All rights reserved.
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface TUIConfig_Minimalist : NSObject
/**
* Show the toast prompt built in TUIKit.
* The default value is YES.
*/
+ (void)enableToast:(BOOL)enable;
/**
* Switch the language of TUIKit.
* The currently supported languages are "en", "zh-Hans", and "ar".
*/
+ (void)switchLanguageToTarget:(NSString *)targetLanguage;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,22 @@
//
// TUIConfig_Minimalist.m
// TIMCommon
//
// Created by Tencent on 2024/7/16.
// Copyright © 2024 Tencent. All rights reserved.
#import "TUIConfig_Minimalist.h"
#import <TUICore/TUIConfig.h>
#import <TUICore/TUIGlobalization.h>
@implementation TUIConfig_Minimalist
+ (void)enableToast:(BOOL)enable {
[TUIConfig defaultConfig].enableToast = enable;
}
+ (void)switchLanguageToTarget:(NSString *)targetLanguage {
[TUIGlobalization setPreferredLanguage:targetLanguage];
}
@end

View File

@@ -0,0 +1,21 @@
//
// TUIGroupButtonCell_Minimalist.h
// TUIGroup
//
// Created by wyl on 2023/1/4.
// Copyright © 2023 Tencent. All rights reserved.
//
#import <TIMCommon/TIMCommonModel.h>
#import <TIMCommon/TUICommonGroupInfoCellData_Minimalist.h>
NS_ASSUME_NONNULL_BEGIN
@interface TUIGroupButtonCell_Minimalist : TUICommonTableViewCell
@property(nonatomic, strong) UIButton *button;
@property(nonatomic, strong) TUIGroupButtonCellData_Minimalist *buttonData;
- (void)fillWithData:(TUIGroupButtonCellData_Minimalist *)data;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,168 @@
//
// TUIGroupButtonCell_Minimalist.m
// TUIGroup
//
// Created by wyl on 2023/1/4.
// Copyright © 2023 Tencent. All rights reserved.
//
#import "TUIGroupButtonCell_Minimalist.h"
#import <TUICore/TUIThemeManager.h>
@implementation TUIGroupButtonCell_Minimalist {
UIView *_line;
}
- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier {
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
if (self) {
[self setupViews];
self.changeColorWhenTouched = YES;
}
return self;
}
- (void)setupViews {
self.backgroundColor = [UIColor tui_colorWithHex:@"#f9f9f9"];
self.contentView.backgroundColor = [UIColor tui_colorWithHex:@"#f9f9f9"];
_button = [UIButton buttonWithType:UIButtonTypeCustom];
[_button.titleLabel setFont:[UIFont systemFontOfSize:16]];
[_button addTarget:self action:@selector(onClick:) forControlEvents:UIControlEventTouchUpInside];
[self.contentView addSubview:_button];
[self setSeparatorInset:UIEdgeInsetsMake(0, Screen_Width, 0, 0)];
[self setSelectionStyle:UITableViewCellSelectionStyleNone];
self.changeColorWhenTouched = YES;
_line = [[UIView alloc] initWithFrame:CGRectZero];
[self.contentView addSubview:_line];
_line.backgroundColor = [UIColor whiteColor];
}
- (void)fillWithData:(TUIGroupButtonCellData_Minimalist *)data {
[super fillWithData:data];
self.buttonData = data;
[_button setTitle:data.title forState:UIControlStateNormal];
if(isRTL()) {
_button.contentHorizontalAlignment = UIControlContentHorizontalAlignmentRight;
}
else {
_button.contentHorizontalAlignment = UIControlContentHorizontalAlignmentLeft;
}
_button.titleEdgeInsets = UIEdgeInsetsMake(0, 0, 0, 0);
switch (data.style) {
case ButtonGreen: {
[_button setTitleColor:TIMCommonDynamicColor(@"form_green_button_text_color", @"#FFFFFF") forState:UIControlStateNormal];
_button.backgroundColor = TIMCommonDynamicColor(@"", @"#f9f9f9");
[_button setBackgroundImage:[self imageWithColor:TIMCommonDynamicColor(@"", @"#f9f9f9")] forState:UIControlStateHighlighted];
} break;
case ButtonWhite: {
[_button setTitleColor:TIMCommonDynamicColor(@"form_white_button_text_color", @"#000000") forState:UIControlStateNormal];
_button.backgroundColor = TIMCommonDynamicColor(@"", @"#f9f9f9");
} break;
case ButtonRedText: {
[_button setTitleColor:TIMCommonDynamicColor(@"form_redtext_button_text_color", @"#FF0000") forState:UIControlStateNormal];
_button.backgroundColor = TIMCommonDynamicColor(@"", @"#f9f9f9");
break;
}
case ButtonBule: {
if (data.isInfoPageLeftButton) {
[_button setTitleColor:TIMCommonDynamicColor(@"", @"#0365F9") forState:UIControlStateNormal];
_button.backgroundColor = TIMCommonDynamicColor(@"", @"#f9f9f9");
} else {
[_button.titleLabel setTextColor:[UIColor tui_colorWithHex:@"147AFF"]];
_button.backgroundColor = [UIColor tui_colorWithHex:@"#f9f9f9"];
_button.contentHorizontalAlignment = UIControlContentHorizontalAlignmentCenter;
_button.layer.cornerRadius = kScale390(10);
_button.layer.masksToBounds = YES;
self.backgroundColor = [UIColor clearColor];
self.contentView.backgroundColor = [UIColor clearColor];
}
} break;
default:
break;
}
if (data.textColor) {
[_button setTitleColor:data.textColor forState:UIControlStateNormal];
}
_line.hidden = data.hideSeparatorLine;
// 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.button mas_remakeConstraints:^(MASConstraintMaker *make) {
make.leading.mas_equalTo(self.contentView.mas_leading).mas_offset(kScale390(20));
make.trailing.mas_equalTo(self.contentView.mas_trailing).mas_offset(- kScale390(20));
make.top.mas_equalTo(self.contentView);
make.bottom.mas_equalTo(self.contentView);
}];
[_line mas_remakeConstraints:^(MASConstraintMaker *make) {
make.leading.mas_equalTo(self.contentView.mas_leading).mas_offset(20);
make.trailing.mas_equalTo(self.contentView.mas_trailing);
make.height.mas_equalTo(0.2);
make.bottom.mas_equalTo(self.contentView);
}];
}
- (void)layoutSubviews {
[super layoutSubviews];
}
- (void)onClick:(UIButton *)sender {
if (self.buttonData.cbuttonSelector) {
UIViewController *vc = self.mm_viewController;
if ([vc respondsToSelector:self.buttonData.cbuttonSelector]) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
[vc performSelector:self.buttonData.cbuttonSelector withObject:self];
#pragma clang diagnostic pop
}
}
}
- (void)didAddSubview:(UIView *)subview {
[super didAddSubview:subview];
if (subview != self.contentView) {
[subview removeFromSuperview];
}
}
- (UIImage *)imageWithColor:(UIColor *)color {
CGRect rect = CGRectMake(0.0f, 0.0f, 1.0f, 1.0f);
UIGraphicsBeginImageContext(rect.size);
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetFillColorWithColor(context, [color CGColor]);
CGContextFillRect(context, rect);
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return image;
}
@end

View File

@@ -0,0 +1,33 @@
// Created by Tencent on 2023/06/09.
// Copyright © 2023 Tencent. All rights reserved.
/**
* This document declares the TUIBubbleMessageCell_Minimalist class.
* Bubble messages, the most common type of messages that contain strings and emoticons.
* Both TUIFileMessageCell_Minimalist and TUIVoiceMessageCell_Minimalist inherit from this class and implement the userinterface of bubble messages.
* If developers want to customize the bubble message, they can also refer to the implementation methods of the above two message units to implement their own
* bubble message unit.
*/
#import "TUIMessageCell.h"
#import "TUIMessageCellData.h"
NS_ASSUME_NONNULL_BEGIN
typedef NS_ENUM(NSInteger, TUIMessageStatus) {
TUIMessageStatus_Unkown,
TUIMessageStatus_Sending,
TUIMessageStatus_Send_Succ,
TUIMessageStatus_Some_People_Read,
TUIMessageStatus_All_People_Read,
};
@interface TUIMessageCell_Minimalist : TUIMessageCell
@property(nonatomic, strong) UIImageView *replyLineView;
@property(nonatomic, strong) NSMutableArray *replyAvatarImageViews;
@property(nonatomic, strong) UIImageView *msgStatusView;
@property(nonatomic, strong) UILabel *msgTimeLabel;
- (void)fillWithData:(TUIMessageCellData *)data;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,461 @@
//
// TUIMessageCell_Minimalist.m
// TXIMSDK_TUIKit_iOS
//
// Created by annidyfeng on 2019/5/22.
// Copyright © 2023 Tencent. All rights reserved.
//
#import "TUIMessageCell_Minimalist.h"
#import <TIMCommon/TIMCommonModel.h>
#import <TIMCommon/TIMDefine.h>
#import "NSString+TUIEmoji.h"
#import <TUICore/TUICore.h>
#import <TIMCommon/TUIRelationUserModel.h>
@interface TUIMessageCell_Minimalist ()
@property(nonatomic, assign) TUIMessageStatus status;
@property(nonatomic, strong) NSMutableArray *animationImages;
@end
@implementation TUIMessageCell_Minimalist
- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier {
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
if (self) {
_replyLineView = [[UIImageView alloc] initWithFrame:CGRectZero];
_replyLineView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
[self.contentView addSubview:_replyLineView];
[self.messageModifyRepliesButton.titleLabel setFont:[UIFont systemFontOfSize:12]];
if(isRTL()) {
self.messageModifyRepliesButton.contentHorizontalAlignment = UIControlContentHorizontalAlignmentRight;
}
else {
self.messageModifyRepliesButton.contentHorizontalAlignment = UIControlContentHorizontalAlignmentLeft;
}
[self.messageModifyRepliesButton setTitleColor:RGBA(0, 95, 255, 1) forState:UIControlStateNormal];
_msgStatusView = [[UIImageView alloc] initWithFrame:CGRectZero];
_msgStatusView.contentMode = UIViewContentModeScaleAspectFit;
_msgStatusView.layer.zPosition = FLT_MAX;
UITapGestureRecognizer *tap2 = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(onJumpToMessageInfoPage)];
[_msgStatusView addGestureRecognizer:tap2];
_msgStatusView.userInteractionEnabled = YES;
[self.container addSubview:_msgStatusView];
_msgTimeLabel = [[UILabel alloc] initWithFrame:CGRectZero];
_msgTimeLabel.textColor = RGB(102, 102, 102);
_msgTimeLabel.font = [UIFont systemFontOfSize:12];
_msgTimeLabel.rtlAlignment = TUITextRTLAlignmentTrailing;
_msgTimeLabel.layer.zPosition = FLT_MAX;
[self.container addSubview:_msgTimeLabel];
self.animationImages = [NSMutableArray array];
for (int i = 1; i <= 45; ++i) {
NSString *imageName = [NSString stringWithFormat:@"msg_status_sending_%d", i];
NSString *imagePath = TUIChatImagePath_Minimalist(imageName);
UIImage *image = [[TUIImageCache sharedInstance] getResourceFromCache:imagePath];
[self.animationImages addObject:image];
}
_replyAvatarImageViews = [NSMutableArray array];
}
return self;
}
- (void)prepareReactTagUI:(UIView *)containerView {
NSDictionary *param = @{TUICore_TUIChatExtension_ChatMessageReactPreview_Delegate: self};
[TUICore raiseExtension:TUICore_TUIChatExtension_ChatMessageReactPreview_MinimalistExtensionID parentView:containerView param:param];
}
- (void)onJumpToMessageInfoPage {
if (self.delegate && [self.delegate respondsToSelector:@selector(onJumpToMessageInfoPage:selectCell:)]) {
[self.delegate onJumpToMessageInfoPage:self.messageData selectCell:self];
}
}
+ (BOOL)requiresConstraintBasedLayout {
return YES;
}
// this is Apple's recommended place for adding/updating constraints
- (void)updateConstraints {
[super updateConstraints];
TUIMessageCellLayout *cellLayout = self.messageData.cellLayout;
BOOL isInComing = (self.messageData.direction == MsgDirectionIncoming);
[self.nameLabel mas_remakeConstraints:^(MASConstraintMaker *make) {
if (isInComing) {
make.leading.mas_equalTo(self.container.mas_leading).mas_offset(7);
} else {
make.trailing.mas_equalTo(self.container.mas_trailing);
}
if (self.messageData.showName) {
make.width.mas_greaterThanOrEqualTo(20);
make.height.mas_greaterThanOrEqualTo(20);
} else {
make.height.mas_equalTo(0);
}
make.top.mas_equalTo(self.avatarView.mas_top);
}];
[self.selectedIcon mas_remakeConstraints:^(MASConstraintMaker *make) {
make.leading.mas_equalTo(self.contentView.mas_leading).mas_offset(3);
make.centerY.mas_equalTo(self.container.mas_centerY);
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_updateConstraints:^(MASConstraintMaker *make) {
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);
}
}];
CGSize csize = [self.class getContentSize:self.messageData];
CGFloat contentWidth = csize.width;
CGFloat contentHeight = csize.height;
if (self.messageData.direction == MsgDirectionIncoming) {
self.avatarView.hidden = !self.messageData.showAvatar;
[self.avatarView mas_remakeConstraints:^(MASConstraintMaker *make) {
if (self.messageData.showCheckBox) {
make.leading.mas_equalTo(self.selectedIcon.mas_trailing).mas_offset(cellLayout.avatarInsets.left);
} else {
make.leading.mas_equalTo(self.contentView.mas_leading).mas_offset(cellLayout.avatarInsets.left);
}
make.top.mas_equalTo(cellLayout.avatarInsets.top);
make.size.mas_equalTo(cellLayout.avatarSize);
}];
[self.container mas_remakeConstraints:^(MASConstraintMaker *make) {
make.leading.mas_equalTo(self.avatarView.mas_trailing).mas_offset(cellLayout.messageInsets.left);
make.top.mas_equalTo(self.nameLabel.mas_bottom).mas_offset(cellLayout.messageInsets.top);
make.width.mas_equalTo(contentWidth);
make.height.mas_equalTo(contentHeight);
}];
CGRect indicatorFrame = self.indicator.frame;
[self.indicator mas_remakeConstraints:^(MASConstraintMaker *make) {
make.leading.mas_equalTo(self.container.mas_trailing).mas_offset(8);
make.centerY.mas_equalTo(self.container.mas_centerY);
make.size.mas_equalTo(indicatorFrame.size);
}];
self.retryView.frame = self.indicator.frame;
self.readReceiptLabel.hidden = YES;
} else {
if (!self.messageData.showAvatar) {
cellLayout.avatarSize = CGSizeZero;
}
[self.avatarView mas_remakeConstraints:^(MASConstraintMaker *make) {
make.trailing.mas_equalTo(self.contentView.mas_trailing).mas_offset(-cellLayout.avatarInsets.right);
make.top.mas_equalTo(cellLayout.avatarInsets.top);
make.size.mas_equalTo(cellLayout.avatarSize);
}];
[self.container mas_remakeConstraints:^(MASConstraintMaker *make) {
make.trailing.mas_equalTo(self.avatarView.mas_leading).mas_offset(-cellLayout.messageInsets.right);
make.top.mas_equalTo(self.nameLabel.mas_bottom).mas_offset(cellLayout.messageInsets.top);
make.width.mas_equalTo(contentWidth);
make.height.mas_equalTo(contentHeight);
}];
CGRect indicatorFrame = self.indicator.frame;
[self.indicator mas_remakeConstraints:^(MASConstraintMaker *make) {
make.trailing.mas_equalTo(self.container.mas_leading).mas_offset(-8);
make.centerY.mas_equalTo(self.container.mas_centerY);
make.size.mas_equalTo(indicatorFrame.size);
}];
self.retryView.frame = self.indicator.frame;
[self.readReceiptLabel sizeToFit];
[self.readReceiptLabel mas_remakeConstraints:^(MASConstraintMaker *make) {
make.bottom.mas_equalTo(self.container.mas_bottom);
make.trailing.mas_equalTo(self.container.mas_leading).mas_offset(-8);
make.size.mas_equalTo(self.readReceiptLabel.frame.size);
}];
}
if (!self.messageModifyRepliesButton.isHidden) {
self.messageModifyRepliesButton.mm_sizeToFit();
CGFloat repliesBtnTextWidth = self.messageModifyRepliesButton.frame.size.width;
[self.messageModifyRepliesButton mas_remakeConstraints:^(MASConstraintMaker *make) {
if (isInComing) {
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);
make.size.mas_equalTo(CGSizeMake(repliesBtnTextWidth + 10, 30));
}];
}
if (self.messageData.showMessageModifyReplies && _replyAvatarImageViews.count > 0) {
CGFloat lineViewW = 17;
CGFloat avatarSize = 16;
CGFloat repliesBtnW = kScale390(50);
CGFloat avatarY = self.contentView.mm_h - (self.messageData.sameToNextMsgSender ? avatarSize : avatarSize * 2);
if (self.messageData.direction == MsgDirectionIncoming) {
UIImageView *preAvatarImageView = nil;
for (int i = 0; i < _replyAvatarImageViews.count; ++i) {
UIImageView *avatarView = _replyAvatarImageViews[i];
if (i == 0) {
preAvatarImageView = nil;
}
else {
preAvatarImageView = _replyAvatarImageViews[i-1];
}
[avatarView mas_remakeConstraints:^(MASConstraintMaker *make) {
if (i == 0) {
make.leading.mas_equalTo(_replyLineView.mas_trailing);
}
else {
make.leading.mas_equalTo(preAvatarImageView.mas_centerX);
}
make.top.mas_equalTo(avatarY);
make.width.height.mas_equalTo(avatarSize);
}];
avatarView.layer.masksToBounds = YES;
avatarView.layer.cornerRadius = avatarSize / 2.0;
}
}
else {
__block UIImageView *preAvatarImageView = nil;
NSInteger count = _replyAvatarImageViews.count;
for (NSInteger i = (count - 1); i >=0; i--) {
UIImageView *avatarView = _replyAvatarImageViews[i];
[avatarView mas_remakeConstraints:^(MASConstraintMaker *make) {
if (!preAvatarImageView) {
make.trailing.mas_equalTo(self.messageModifyRepliesButton.mas_leading);
}
else {
make.trailing.mas_equalTo(preAvatarImageView.mas_centerX);
}
make.top.mas_equalTo(avatarY);
make.width.height.mas_equalTo(avatarSize);
}];
avatarView.layer.masksToBounds = YES;
avatarView.layer.cornerRadius = avatarSize / 2.0;
preAvatarImageView = avatarView;
}
}
UIImageView *lastAvatarImageView = _replyAvatarImageViews.lastObject;
[self.messageModifyRepliesButton mas_remakeConstraints:^(MASConstraintMaker *make) {
if (self.messageData.direction == MsgDirectionIncoming) {
make.leading.mas_equalTo(lastAvatarImageView.mas_trailing);
}
else {
make.trailing.mas_equalTo(_replyLineView.mas_leading);
}
make.top.mas_equalTo(avatarY);
make.width.mas_equalTo(repliesBtnW);
make.height.mas_equalTo(avatarSize);
}];
[_replyLineView mas_remakeConstraints:^(MASConstraintMaker *make) {
if (self.messageData.direction == MsgDirectionIncoming) {
make.leading.mas_equalTo(self.container.mas_leading).mas_offset(- 1);
}
else {
make.trailing.mas_equalTo(self.container.mas_trailing);
}
make.top.mas_equalTo(CGRectGetMaxY(self.container.frame) - 14);
make.width.mas_equalTo(lineViewW);
make.bottom.mas_equalTo(self.messageModifyRepliesButton.mas_centerY);
}];
} else {
_replyLineView.frame = CGRectZero;
self.messageModifyRepliesButton.frame = CGRectZero;
}
[_msgTimeLabel mas_makeConstraints:^(MASConstraintMaker *make) {
make.width.mas_equalTo(38);
make.height.mas_equalTo(self.messageData.msgStatusSize.height);
make.bottom.mas_equalTo(self.container).mas_offset(-kScale390(9));
make.trailing.mas_equalTo(self.container).mas_offset(-kScale390(16));
}];
[_msgStatusView mas_makeConstraints:^(MASConstraintMaker *make) {
make.width.mas_equalTo(16);
make.height.mas_equalTo(self.messageData.msgStatusSize.height);
make.bottom.mas_equalTo(self.msgTimeLabel);
make.trailing.mas_equalTo(_msgTimeLabel.mas_leading);
}];
}
- (void)fillWithData:(TUIMessageCellData *)data {
[super fillWithData:data];
self.readReceiptLabel.hidden = YES;
self.messageModifyRepliesButton.hidden = YES;
[self.messageModifyRepliesButton setImage:nil forState:UIControlStateNormal];
//react
[self prepareReactTagUI:self.contentView];
if (_replyAvatarImageViews.count > 0) {
for (UIImageView *imageView in _replyAvatarImageViews) {
[imageView removeFromSuperview];
}
[_replyAvatarImageViews removeAllObjects];
}
_replyLineView.hidden = YES;
if (data.showMessageModifyReplies) {
_replyLineView.hidden = NO;
self.messageModifyRepliesButton.hidden = NO;
// line
UIImage *lineImage = nil;
if (self.messageData.direction == MsgDirectionIncoming) {
lineImage = [[TUIImageCache sharedInstance] getResourceFromCache:TUIChatImagePath_Minimalist(@"msg_reply_line_income")];
} else {
lineImage = [[TUIImageCache sharedInstance] getResourceFromCache:TUIChatImagePath_Minimalist(@"msg_reply_line_outcome")];
}
lineImage = [lineImage rtl_imageFlippedForRightToLeftLayoutDirection];
UIEdgeInsets ei = UIEdgeInsetsFromString(@"{10,0,20,0}");
ei = rtlEdgeInsetsWithInsets(ei);
_replyLineView.image = [lineImage resizableImageWithCapInsets:ei resizingMode:UIImageResizingModeStretch];
// avtar
NSInteger avatarCount = 0;
NSInteger avatarMaxCount = 4;
NSMutableDictionary *existSenderMap = [NSMutableDictionary dictionary];
for (NSDictionary *senderMap in self.messageData.messageModifyReplies) {
NSString *sender = senderMap[@"messageSender"];
TUIRelationUserModel *userModel = self.messageData.additionalUserInfoResult[sender];
NSURL *headUrl = [NSURL URLWithString:userModel.faceURL];
NSString *existSender = existSenderMap[@"messageSender"];
if (!sender || [sender isEqualToString:existSender]) {
//exist sender head not add again
continue;
}
UIImageView *avatarView = [[UIImageView alloc] init];
if (avatarCount < avatarMaxCount - 1) {
existSenderMap[@"messageSender"] = sender;
[avatarView sd_setImageWithURL:headUrl placeholderImage:DefaultAvatarImage];
} else {
[avatarView setImage:[[TUIImageCache sharedInstance] getResourceFromCache:TUIChatImagePath_Minimalist(@"msg_reply_more_icon")]];
}
[_replyAvatarImageViews addObject:avatarView];
[self.contentView addSubview:avatarView];
if (++avatarCount >= avatarMaxCount) {
break;
}
}
}
_msgTimeLabel.text = [TUITool convertDateToHMStr:self.messageData.innerMessage.timestamp];
self.indicator.hidden = YES;
_msgStatusView.hidden = YES;
self.readReceiptLabel.hidden = YES;
if (self.messageData.direction == MsgDirectionOutgoing) {
self.status = TUIMessageStatus_Unkown;
if (self.messageData.status == Msg_Status_Sending || self.messageData.status == Msg_Status_Sending_2) {
[self updateMessageStatus:TUIMessageStatus_Sending];
} else if (self.messageData.status == Msg_Status_Succ) {
[self updateMessageStatus:TUIMessageStatus_Send_Succ];
}
[self updateReadLabelText];
}
// tell constraints they need updating
[self setNeedsUpdateConstraints];
// update constraints now so we can animate the change
[self updateConstraintsIfNeeded];
[self layoutIfNeeded];
}
- (void)updateReadLabelText {
if (self.messageData.innerMessage.groupID.length > 0) {
// group message
if (self.messageData.messageReceipt == nil) {
// haven't received the message receipt yet
return;
}
NSInteger readCount = self.messageData.messageReceipt.readCount;
NSInteger unreadCount = self.messageData.messageReceipt.unreadCount;
if (unreadCount == 0) {
// All read
[self updateMessageStatus:TUIMessageStatus_All_People_Read];
} else if (readCount > 0) {
// Some read
[self updateMessageStatus:TUIMessageStatus_Some_People_Read];
}
} else {
// c2c message
BOOL isPeerRead = self.messageData.messageReceipt.isPeerRead;
if (isPeerRead) {
[self updateMessageStatus:TUIMessageStatus_All_People_Read];
}
}
}
- (void)updateMessageStatus:(TUIMessageStatus)status {
if (status <= self.status) {
return;
}
if (self.messageData.showReadReceipt && self.messageData.direction == MsgDirectionOutgoing && self.messageData.innerMessage.needReadReceipt &&
(self.messageData.innerMessage.userID || self.messageData.innerMessage.groupID)) {
_msgStatusView.hidden = NO;
_msgStatusView.image = nil;
}
if (_msgStatusView.isAnimating) {
[_msgStatusView stopAnimating];
_msgStatusView.animationImages = nil;
}
switch (status) {
case TUIMessageStatus_Sending: {
_msgStatusView.animationImages = self.animationImages;
[_msgStatusView startAnimating];
} break;
case TUIMessageStatus_Send_Succ: {
_msgStatusView.image = [[TUIImageCache sharedInstance] getResourceFromCache:TUIChatImagePath_Minimalist(@"msg_status_send_succ")];
} break;
case TUIMessageStatus_Some_People_Read: {
_msgStatusView.image = [[TUIImageCache sharedInstance] getResourceFromCache:TUIChatImagePath_Minimalist(@"msg_status_some_people_read")];
} break;
case TUIMessageStatus_All_People_Read: {
_msgStatusView.image = [[TUIImageCache sharedInstance] getResourceFromCache:TUIChatImagePath_Minimalist(@"msg_status_all_people_read")];
} break;
default:
break;
}
self.status = status;
}
- (void)layoutSubviews {
[super layoutSubviews];
}
#pragma mark - TUIMessageCellProtocol
+ (CGFloat)getHeight:(TUIMessageCellData *)data withWidth:(CGFloat)width {
NSAssert([data isKindOfClass:TUIMessageCellData.class], @"data must be kind of TUIMessageCellData");
CGFloat height = [super getHeight:data withWidth:width];
if (data.sameToNextMsgSender) {
height -= kScale375(16);
}
return height;
}
@end