TUIChatSmallTongueView.m 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230
  1. //
  2. // TUIChatSmallTongue.m
  3. // TUIChat
  4. //
  5. // Created by xiangzhang on 2022/1/6.
  6. // Copyright © 2023 Tencent. All rights reserved.
  7. //
  8. #import "TUIChatSmallTongueView.h"
  9. #import <TUICore/NSString+TUIUtil.h>
  10. #import <TUICore/TUIDarkModel.h>
  11. #import <TUICore/TUIThemeManager.h>
  12. #import "TUIChatConfig.h"
  13. #import <TIMCommon/TIMDefine.h>
  14. #define TongueHeight 35.f
  15. #define TongueImageWidth 12.f
  16. #define TongueImageHeight 12.f
  17. #define TongueLeftSpace 10.f
  18. #define TongueMiddleSpace 5.f
  19. #define TongueRightSpace 10.f
  20. #define TongueFontSize 13
  21. @interface TUIChatSmallTongueView ()
  22. @property(nonatomic, strong) UIImageView *imageView;
  23. @property(nonatomic, strong) UILabel *label;
  24. @end
  25. @implementation TUIChatSmallTongueView {
  26. TUIChatSmallTongue *_tongue;
  27. }
  28. + (void)load {
  29. [NSNotificationCenter.defaultCenter addObserver:self selector:@selector(onThemeChanged:) name:TUIDidApplyingThemeChangedNotfication object:nil];
  30. }
  31. - (instancetype)initWithFrame:(CGRect)frame {
  32. self = [super initWithFrame:frame];
  33. if (self) {
  34. self.backgroundColor = TUIChatDynamicColor(@"chat_small_tongue_bg_color", @"#FFFFFF");
  35. // border
  36. self.layer.borderWidth = 0.2;
  37. self.layer.borderColor = TUIChatDynamicColor(@"chat_small_tongue_line_color", @"#E5E5E5").CGColor;
  38. self.layer.cornerRadius = 2;
  39. self.layer.masksToBounds = YES;
  40. // shadow
  41. self.layer.shadowColor = RGBA(0, 0, 0, 0.15).CGColor;
  42. self.layer.shadowOpacity = 1;
  43. self.layer.shadowOffset = CGSizeMake(0, 0);
  44. self.layer.shadowRadius = 2;
  45. self.clipsToBounds = NO;
  46. // tap
  47. UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(onTap)];
  48. [self addGestureRecognizer:tap];
  49. }
  50. return self;
  51. }
  52. - (void)onTap {
  53. if (self.delegate && [self.delegate respondsToSelector:@selector(onChatSmallTongueClick:)]) {
  54. [self.delegate onChatSmallTongueClick:_tongue];
  55. }
  56. }
  57. - (void)setTongue:(TUIChatSmallTongue *)tongue {
  58. _tongue = tongue;
  59. if (!self.imageView) {
  60. self.imageView = [[UIImageView alloc] init];
  61. [self addSubview:self.imageView];
  62. }
  63. self.imageView.image = [TUIChatSmallTongueView getTongueImage:tongue];
  64. if (!self.label) {
  65. self.label = [[UILabel alloc] init];
  66. self.label.font = [UIFont systemFontOfSize:TongueFontSize];
  67. [self addSubview:self.label];
  68. }
  69. self.label.text = [TUIChatSmallTongueView getTongueText:tongue];
  70. self.label.rtlAlignment = TUITextRTLAlignmentLeading;
  71. self.label.textColor = TUIChatDynamicColor(@"chat_drop_down_color", @"#147AFF");
  72. [self.imageView mas_remakeConstraints:^(MASConstraintMaker *make) {
  73. make.width.height.mas_equalTo(TongueImageWidth);
  74. make.leading.mas_equalTo(TongueLeftSpace);
  75. make.top.mas_equalTo(10);
  76. }];
  77. [self.label mas_remakeConstraints:^(MASConstraintMaker *make) {
  78. make.trailing.mas_lessThanOrEqualTo(self.mas_trailing).mas_offset(-TongueRightSpace);
  79. make.height.mas_equalTo(TongueImageHeight);
  80. make.leading.mas_equalTo(self.imageView.mas_trailing).mas_offset(TongueMiddleSpace);
  81. make.top.mas_equalTo(10);
  82. }];
  83. }
  84. + (CGFloat)getTongueWidth:(TUIChatSmallTongue *)tongue {
  85. NSString *tongueText = [self getTongueText:tongue];
  86. CGSize titleSize = [tongueText boundingRectWithSize:CGSizeMake(MAXFLOAT, TongueHeight)
  87. options:NSStringDrawingUsesLineFragmentOrigin
  88. attributes:@{NSFontAttributeName : [UIFont systemFontOfSize:TongueFontSize]}
  89. context:nil]
  90. .size;
  91. CGFloat tongueWidth = TongueLeftSpace + TongueImageWidth + TongueMiddleSpace + ceil(titleSize.width) + TongueRightSpace;
  92. return tongueWidth;
  93. }
  94. + (NSString *)getTongueText:(TUIChatSmallTongue *)tongue {
  95. static NSMutableDictionary *titleCacheFormat;
  96. if (titleCacheFormat == nil) {
  97. titleCacheFormat = [NSMutableDictionary dictionary];
  98. [titleCacheFormat setObject:TIMCommonLocalizableString(TUIKitChatBackToLatestLocation) forKey:@(TUIChatSmallTongueType_ScrollToBoom)];
  99. [titleCacheFormat setObject:TIMCommonLocalizableString(TUIKitChatNewMessages) forKey:@(TUIChatSmallTongueType_ReceiveNewMsg)];
  100. }
  101. if (tongue.type == TUIChatSmallTongueType_SomeoneAt) {
  102. NSString *atMeStr = TIMCommonLocalizableString(TUIKitConversationTipsAtMe);
  103. NSString *atAllStr = TIMCommonLocalizableString(TUIKitConversationTipsAtAll);
  104. if ([tongue.atTipsStr tui_containsString:atMeStr]) {
  105. atMeStr = [atMeStr stringByReplacingOccurrencesOfString:@"[" withString:@""];
  106. atMeStr = [atMeStr stringByReplacingOccurrencesOfString:@"]" withString:@""];
  107. [titleCacheFormat setObject:atMeStr forKey:@(TUIChatSmallTongueType_SomeoneAt)];
  108. } else if ([tongue.atTipsStr tui_containsString:atAllStr]) {
  109. atAllStr = [atAllStr stringByReplacingOccurrencesOfString:@"[" withString:@""];
  110. atAllStr = [atAllStr stringByReplacingOccurrencesOfString:@"]" withString:@""];
  111. [titleCacheFormat setObject:atAllStr forKey:@(TUIChatSmallTongueType_SomeoneAt)];
  112. }
  113. }
  114. if (tongue.type == TUIChatSmallTongueType_ReceiveNewMsg) {
  115. return [NSString stringWithFormat:[titleCacheFormat objectForKey:@(TUIChatSmallTongueType_ReceiveNewMsg)],
  116. tongue.unreadMsgCount > 99 ? @"99+" : @(tongue.unreadMsgCount)];
  117. } else {
  118. return [titleCacheFormat objectForKey:@(tongue.type)];
  119. }
  120. }
  121. static NSMutableDictionary *gImageCache;
  122. + (UIImage *)getTongueImage:(TUIChatSmallTongue *)tongue {
  123. if (gImageCache == nil) {
  124. gImageCache = [NSMutableDictionary dictionary];
  125. [gImageCache setObject:TUIChatBundleThemeImage(@"chat_drop_down_img", @"drop_down") ?: UIImage.new forKey:@(TUIChatSmallTongueType_ScrollToBoom)];
  126. [gImageCache setObject:TUIChatBundleThemeImage(@"chat_drop_down_img", @"drop_down") ?: UIImage.new forKey:@(TUIChatSmallTongueType_ReceiveNewMsg)];
  127. [gImageCache setObject:TUIChatBundleThemeImage(@"chat_pull_up_img", @"pull_up") ?: UIImage.new forKey:@(TUIChatSmallTongueType_SomeoneAt)];
  128. }
  129. return [gImageCache objectForKey:@(tongue.type)];
  130. }
  131. + (void)onThemeChanged:(NSNotification *)notice {
  132. gImageCache = nil;
  133. }
  134. @end
  135. @implementation TUIChatSmallTongue
  136. - (instancetype)init {
  137. self = [super init];
  138. if (self) {
  139. self.type = TUIChatSmallTongueType_None;
  140. }
  141. return self;
  142. }
  143. @end
  144. static TUIChatSmallTongueView *gTongueView = nil;
  145. static TUIChatSmallTongue *gTongue = nil;
  146. static CGFloat gBottomMargin = 0;
  147. @implementation TUIChatSmallTongueManager
  148. + (void)showTongue:(TUIChatSmallTongue *)tongue delegate:(id<TUIChatSmallTongueViewDelegate>)delegate {
  149. if (tongue.type == gTongue.type
  150. && tongue.parentView == gTongue.parentView
  151. && tongue.unreadMsgCount == gTongue.unreadMsgCount
  152. && tongue.atMsgSeqs == gTongue.atMsgSeqs
  153. && !gTongueView.hidden) {
  154. return;
  155. }
  156. gTongue = tongue;
  157. if (!gTongueView) {
  158. gTongueView = [[TUIChatSmallTongueView alloc] init];
  159. } else {
  160. [gTongueView removeFromSuperview];
  161. }
  162. CGFloat tongueWidth = [TUIChatSmallTongueView getTongueWidth:gTongue];
  163. if(isRTL()) {
  164. gTongueView.frame =
  165. CGRectMake(16,
  166. tongue.parentView.mm_h - Bottom_SafeHeight - TTextView_Height - 20 - TongueHeight - gBottomMargin,
  167. tongueWidth, TongueHeight);
  168. }
  169. else {
  170. gTongueView.frame =
  171. CGRectMake(tongue.parentView.mm_w - tongueWidth - 16,
  172. tongue.parentView.mm_h - Bottom_SafeHeight - TTextView_Height - 20 - TongueHeight - gBottomMargin,
  173. tongueWidth, TongueHeight);
  174. }
  175. gTongueView.delegate = delegate;
  176. [gTongueView setTongue:gTongue];
  177. [tongue.parentView addSubview:gTongueView];
  178. }
  179. + (void)removeTongue:(TUIChatSmallTongueType)type {
  180. if (type != gTongue.type) {
  181. return;
  182. }
  183. [self removeTongue];
  184. }
  185. + (void)removeTongue {
  186. gTongue = nil;
  187. if (gTongueView) {
  188. [gTongueView removeFromSuperview];
  189. gTongueView = nil;
  190. }
  191. }
  192. + (void)hideTongue:(BOOL)isHidden {
  193. if (gTongueView) {
  194. gTongueView.hidden = isHidden;
  195. }
  196. }
  197. + (void)adaptTongueBottomMargin:(CGFloat)margin {
  198. gBottomMargin = margin;
  199. }
  200. @end