TUICommonModel.m 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807
  1. //
  2. // TCommonCell.m
  3. // TXIMSDK_TUIKit_iOS
  4. //
  5. // Created by annidyfeng on 2019/5/6.
  6. // Copyright © 2023 Tencent. All rights reserved.
  7. //
  8. #import "TUICommonModel.h"
  9. #import <objc/runtime.h>
  10. #import "NSString+TUIUtil.h"
  11. #import "TUIDarkModel.h"
  12. #import "TUIGlobalization.h"
  13. #import "TUIThemeManager.h"
  14. #import "TUITool.h"
  15. #import "UIView+TUILayout.h"
  16. /////////////////////////////////////////////////////////////////////////////////
  17. //
  18. // TUIUserFullInfo
  19. //
  20. /////////////////////////////////////////////////////////////////////////////////
  21. @implementation V2TIMUserFullInfo (TUIUserFullInfo)
  22. - (NSString *)showName {
  23. if ([NSString isEmpty:self.nickName]) return self.userID;
  24. return self.nickName;
  25. }
  26. - (NSString *)showGender {
  27. if (self.gender == V2TIM_GENDER_MALE) return TUIKitLocalizableString(Male);
  28. if (self.gender == V2TIM_GENDER_FEMALE) return TUIKitLocalizableString(Female);
  29. return TUIKitLocalizableString(Unsetted);
  30. }
  31. - (NSString *)showSignature {
  32. if (self.selfSignature == nil) return TUIKitLocalizableString(TUIKitNoSelfSignature);
  33. return [NSString stringWithFormat:TUIKitLocalizableString(TUIKitSelfSignatureFormat), self.selfSignature];
  34. }
  35. - (NSString *)showAllowType {
  36. if (self.allowType == V2TIM_FRIEND_ALLOW_ANY) {
  37. return TUIKitLocalizableString(TUIKitAllowTypeAcceptOne);
  38. }
  39. if (self.allowType == V2TIM_FRIEND_NEED_CONFIRM) {
  40. return TUIKitLocalizableString(TUIKitAllowTypeNeedConfirm);
  41. }
  42. if (self.allowType == V2TIM_FRIEND_DENY_ANY) {
  43. return TUIKitLocalizableString(TUIKitAllowTypeDeclineAll);
  44. }
  45. return nil;
  46. }
  47. @end
  48. /////////////////////////////////////////////////////////////////////////////////
  49. //
  50. // TUIUserModel
  51. //
  52. /////////////////////////////////////////////////////////////////////////////////
  53. @implementation TUIUserModel
  54. - (id)copyWithZone:(NSZone *)zone {
  55. TUIUserModel *model = [[TUIUserModel alloc] init];
  56. model.userId = self.userId;
  57. model.name = self.name;
  58. model.avatar = self.avatar;
  59. return model;
  60. }
  61. @end
  62. /////////////////////////////////////////////////////////////////////////////////
  63. //
  64. // TUIScrollView
  65. //
  66. /////////////////////////////////////////////////////////////////////////////////
  67. static void *gScrollViewBoundsChangeNotificationContext = &gScrollViewBoundsChangeNotificationContext;
  68. @interface TUIScrollView ()
  69. @property(nonatomic, readonly) CGFloat imageAspectRatio;
  70. @property(nonatomic) CGRect initialImageFrame;
  71. @property(strong, nonatomic, readonly) UITapGestureRecognizer *tap;
  72. @end
  73. @implementation TUIScrollView
  74. @synthesize tap = _tap;
  75. #pragma mark - Tap to Zoom
  76. - (UITapGestureRecognizer *)tap {
  77. if (_tap == nil) {
  78. _tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tapToZoom:)];
  79. _tap.numberOfTapsRequired = 2;
  80. }
  81. return _tap;
  82. }
  83. - (void)tapToZoom:(UIGestureRecognizer *)gestureRecognizer {
  84. if (self.zoomScale > self.minimumZoomScale) {
  85. [self setZoomScale:self.minimumZoomScale animated:YES];
  86. } else {
  87. CGPoint tapLocation = [gestureRecognizer locationInView:self.imageView];
  88. CGFloat zoomRectWidth = self.imageView.frame.size.width / self.maximumZoomScale;
  89. CGFloat zoomRectHeight = self.imageView.frame.size.height / self.maximumZoomScale;
  90. CGFloat zoomRectX = tapLocation.x - zoomRectWidth * 0.5;
  91. CGFloat zoomRectY = tapLocation.y - zoomRectHeight * 0.5;
  92. CGRect zoomRect = CGRectMake(zoomRectX, zoomRectY, zoomRectWidth, zoomRectHeight);
  93. [self zoomToRect:zoomRect animated:YES];
  94. }
  95. }
  96. #pragma mark - Private Methods
  97. - (void)configure {
  98. self.showsVerticalScrollIndicator = NO;
  99. self.showsHorizontalScrollIndicator = NO;
  100. [self startObservingBoundsChange];
  101. }
  102. - (void)setImageView:(UIImageView *)imageView {
  103. if (_imageView.superview == self) {
  104. [_imageView removeGestureRecognizer:self.tap];
  105. [_imageView removeFromSuperview];
  106. }
  107. if (imageView) {
  108. _imageView = imageView;
  109. _initialImageFrame = CGRectNull;
  110. _imageView.userInteractionEnabled = YES;
  111. [_imageView addGestureRecognizer:self.tap];
  112. [self addSubview:imageView];
  113. }
  114. }
  115. - (CGFloat)imageAspectRatio {
  116. if (self.imageView.image) {
  117. return self.imageView.image.size.width / self.imageView.image.size.height;
  118. }
  119. return 1;
  120. }
  121. - (CGSize)rectSizeForAspectRatio:(CGFloat)ratio thatFitsSize:(CGSize)size {
  122. CGFloat containerWidth = size.width;
  123. CGFloat containerHeight = size.height;
  124. CGFloat resultWidth = 0;
  125. CGFloat resultHeight = 0;
  126. if ((ratio <= 0) || (containerHeight <= 0)) {
  127. return size;
  128. }
  129. if (containerWidth / containerHeight >= ratio) {
  130. resultHeight = containerHeight;
  131. resultWidth = containerHeight * ratio;
  132. } else {
  133. resultWidth = containerWidth;
  134. resultHeight = containerWidth / ratio;
  135. }
  136. return CGSizeMake(resultWidth, resultHeight);
  137. }
  138. - (void)scaleImageForScrollViewTransitionFromBounds:(CGRect)oldBounds toBounds:(CGRect)newBounds {
  139. CGPoint oldContentOffset = CGPointMake(oldBounds.origin.x, oldBounds.origin.y);
  140. CGSize oldSize = oldBounds.size;
  141. CGSize newSize = newBounds.size;
  142. CGSize containedImageSizeOld = [self rectSizeForAspectRatio:self.imageAspectRatio thatFitsSize:oldSize];
  143. CGSize containedImageSizeNew = [self rectSizeForAspectRatio:self.imageAspectRatio thatFitsSize:newSize];
  144. if (containedImageSizeOld.height <= 0) {
  145. containedImageSizeOld = containedImageSizeNew;
  146. }
  147. CGFloat orientationRatio = (containedImageSizeNew.height / containedImageSizeOld.height);
  148. CGAffineTransform t = CGAffineTransformMakeScale(orientationRatio, orientationRatio);
  149. self.imageView.frame = CGRectApplyAffineTransform(self.imageView.frame, t);
  150. self.contentSize = self.imageView.frame.size;
  151. CGFloat xOffset = (oldContentOffset.x + oldSize.width * 0.5) * orientationRatio - newSize.width * 0.5;
  152. CGFloat yOffset = (oldContentOffset.y + oldSize.height * 0.5) * orientationRatio - newSize.height * 0.5;
  153. xOffset -= MAX(xOffset + newSize.width - self.contentSize.width, 0);
  154. yOffset -= MAX(yOffset + newSize.height - self.contentSize.height, 0);
  155. xOffset += MAX(-xOffset, 0);
  156. yOffset += MAX(-yOffset, 0);
  157. self.contentOffset = CGPointMake(xOffset, yOffset);
  158. }
  159. - (void)setupInitialImageFrame {
  160. if (self.imageView.image && CGRectEqualToRect(self.initialImageFrame, CGRectNull)) {
  161. CGSize imageViewSize = [self rectSizeForAspectRatio:self.imageAspectRatio thatFitsSize:self.bounds.size];
  162. self.initialImageFrame = CGRectMake(0, 0, imageViewSize.width, imageViewSize.height);
  163. self.imageView.frame = self.initialImageFrame;
  164. self.contentSize = self.initialImageFrame.size;
  165. }
  166. }
  167. #pragma mark - KVO
  168. - (void)startObservingBoundsChange {
  169. [self addObserver:self
  170. forKeyPath:@"bounds"
  171. options:NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew
  172. context:gScrollViewBoundsChangeNotificationContext];
  173. }
  174. - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey, id> *)change context:(void *)context {
  175. if (context == gScrollViewBoundsChangeNotificationContext) {
  176. CGRect oldRect = ((NSValue *)change[NSKeyValueChangeOldKey]).CGRectValue;
  177. CGRect newRect = ((NSValue *)change[NSKeyValueChangeNewKey]).CGRectValue;
  178. if (!CGSizeEqualToSize(oldRect.size, newRect.size)) {
  179. [self scaleImageForScrollViewTransitionFromBounds:oldRect toBounds:newRect];
  180. }
  181. }
  182. }
  183. - (void)dealloc {
  184. [self removeObserver:self forKeyPath:@"bounds"];
  185. }
  186. #pragma mark - UIScrollView
  187. - (void)setContentOffset:(CGPoint)contentOffset {
  188. const CGSize contentSize = self.contentSize;
  189. const CGSize scrollViewSize = self.bounds.size;
  190. if (contentSize.width < scrollViewSize.width) {
  191. contentOffset.x = -(scrollViewSize.width - contentSize.width) * 0.5;
  192. }
  193. if (contentSize.height < scrollViewSize.height) {
  194. contentOffset.y = -(scrollViewSize.height - contentSize.height) * 0.5;
  195. }
  196. [super setContentOffset:contentOffset];
  197. }
  198. #pragma mark - UIView
  199. - (instancetype)initWithFrame:(CGRect)frame {
  200. self = [super initWithFrame:frame];
  201. if (self) {
  202. [self configure];
  203. }
  204. return self;
  205. }
  206. - (instancetype)initWithCoder:(NSCoder *)aDecoder {
  207. self = [super initWithCoder:aDecoder];
  208. if (self) {
  209. [self configure];
  210. }
  211. return self;
  212. }
  213. - (void)layoutSubviews {
  214. [super layoutSubviews];
  215. [self setupInitialImageFrame];
  216. }
  217. @end
  218. /////////////////////////////////////////////////////////////////////////////////
  219. //
  220. // TUIGroupAvatar
  221. //
  222. /////////////////////////////////////////////////////////////////////////////////
  223. #define groupAvatarWidth (48 * [[UIScreen mainScreen] scale])
  224. @implementation TUIGroupAvatar
  225. + (void)createGroupAvatar:(NSArray *)group finished:(void (^)(UIImage *groupAvatar))finished {
  226. dispatch_async(dispatch_get_global_queue(0, 0), ^{
  227. NSInteger avatarCount = group.count > 9 ? 9 : group.count;
  228. CGFloat width = groupAvatarWidth / 3 * 0.90;
  229. CGFloat space3 = (groupAvatarWidth - width * 3) / 4;
  230. CGFloat space2 = (groupAvatarWidth - width * 2 + space3) / 2;
  231. CGFloat space1 = (groupAvatarWidth - width) / 2;
  232. __block CGFloat y = avatarCount > 6 ? space3 : (avatarCount > 3 ? space2 : space1);
  233. __block CGFloat x = avatarCount % 3 == 0 ? space3 : (avatarCount % 3 == 2 ? space2 : space1);
  234. width = avatarCount > 4 ? width : (avatarCount > 1 ? (groupAvatarWidth - 3 * space3) / 2 : groupAvatarWidth);
  235. if (avatarCount == 1) {
  236. x = 0;
  237. y = 0;
  238. }
  239. if (avatarCount == 2) {
  240. x = space3;
  241. } else if (avatarCount == 3) {
  242. x = (groupAvatarWidth - width) / 2;
  243. y = space3;
  244. } else if (avatarCount == 4) {
  245. x = space3;
  246. y = space3;
  247. }
  248. dispatch_async(dispatch_get_main_queue(), ^{
  249. UIView *view = [[UIView alloc] initWithFrame:CGRectMake(0, 0, groupAvatarWidth, groupAvatarWidth)];
  250. [view setBackgroundColor:[UIColor colorWithWhite:0.8 alpha:0.6]];
  251. view.layer.cornerRadius = 6;
  252. __block NSInteger count = 0;
  253. for (NSInteger i = avatarCount - 1; i >= 0; i--) {
  254. NSString *avatarUrl = [group objectAtIndex:i];
  255. UIImageView *imageView = [[UIImageView alloc] initWithFrame:CGRectMake(x, y, width, width)];
  256. [view addSubview:imageView];
  257. [imageView sd_setImageWithURL:[NSURL URLWithString:avatarUrl]
  258. placeholderImage:DefaultAvatarImage
  259. completed:^(UIImage *_Nullable image, NSError *_Nullable error, SDImageCacheType cacheType, NSURL *_Nullable imageURL) {
  260. count++;
  261. if (count == avatarCount) {
  262. UIGraphicsBeginImageContextWithOptions(view.frame.size, NO, 2.0);
  263. [view.layer renderInContext:UIGraphicsGetCurrentContext()];
  264. UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
  265. UIGraphicsEndPDFContext();
  266. CGImageRef imageRef = image.CGImage;
  267. CGImageRef imageRefRect =
  268. CGImageCreateWithImageInRect(imageRef, CGRectMake(0, 0, view.frame.size.width * 2, view.frame.size.height * 2));
  269. UIImage *ansImage = [[UIImage alloc] initWithCGImage:imageRefRect];
  270. CGImageRelease(imageRefRect);
  271. dispatch_async(dispatch_get_main_queue(), ^{
  272. if (finished) {
  273. finished(ansImage);
  274. }
  275. });
  276. }
  277. }];
  278. if (avatarCount == 3) {
  279. if (i == 2) {
  280. y = width + space3 * 2;
  281. x = space3;
  282. } else {
  283. x += width + space3;
  284. }
  285. } else if (avatarCount == 4) {
  286. if (i % 2 == 0) {
  287. y += width + space3;
  288. x = space3;
  289. } else {
  290. x += width + space3;
  291. }
  292. } else {
  293. if (i % 3 == 0) {
  294. y += (width + space3);
  295. x = space3;
  296. } else {
  297. x += (width + space3);
  298. }
  299. }
  300. }
  301. });
  302. });
  303. }
  304. + (void)fetchGroupAvatars:(NSString *)groupID placeholder:(UIImage *)placeholder callback:(void (^)(BOOL success, UIImage *image, NSString *groupID))callback {
  305. @tui_weakify(self);
  306. [[V2TIMManager sharedInstance] getGroupMemberList:groupID
  307. filter:V2TIM_GROUP_MEMBER_FILTER_ALL
  308. nextSeq:0
  309. succ:^(uint64_t nextSeq, NSArray<V2TIMGroupMemberFullInfo *> *memberList) {
  310. @tui_strongify(self);
  311. int i = 0;
  312. NSMutableArray *groupMemberAvatars = [NSMutableArray arrayWithCapacity:1];
  313. for (V2TIMGroupMemberFullInfo *member in memberList) {
  314. if (member.faceURL.length > 0) {
  315. [groupMemberAvatars addObject:member.faceURL];
  316. } else {
  317. [groupMemberAvatars addObject:@"http://placeholder"];
  318. }
  319. if (++i == 9) {
  320. break;
  321. }
  322. }
  323. if (i <= 1) {
  324. [self asyncClearCacheAvatarForGroup:groupID];
  325. if (callback) {
  326. callback(NO, placeholder, groupID);
  327. }
  328. return;
  329. }
  330. NSString *key = [NSString stringWithFormat:@"TUIConversationLastGroupMember_%@", groupID];
  331. [NSUserDefaults.standardUserDefaults setInteger:groupMemberAvatars.count forKey:key];
  332. [NSUserDefaults.standardUserDefaults synchronize];
  333. [TUIGroupAvatar createGroupAvatar:groupMemberAvatars
  334. finished:^(UIImage *groupAvatar) {
  335. @tui_strongify(self);
  336. UIImage *avatar = groupAvatar;
  337. [self cacheGroupAvatar:avatar number:(UInt32)groupMemberAvatars.count groupID:groupID];
  338. if (callback) {
  339. callback(YES, avatar, groupID);
  340. }
  341. }];
  342. }
  343. fail:^(int code, NSString *msg) {
  344. if (callback) {
  345. callback(NO, placeholder, groupID);
  346. }
  347. }];
  348. }
  349. + (void)cacheGroupAvatar:(UIImage *)avatar number:(UInt32)memberNum groupID:(NSString *)groupID {
  350. dispatch_async(dispatch_get_global_queue(0, 0), ^{
  351. if (groupID == nil || groupID.length == 0) {
  352. return;
  353. }
  354. NSString *tempPath = NSTemporaryDirectory();
  355. NSString *filePath = [NSString stringWithFormat:@"%@groupAvatar_%@_%d.png", tempPath, groupID, memberNum];
  356. NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
  357. // check to delete old file
  358. NSNumber *oldValue = [defaults objectForKey:groupID];
  359. if (oldValue != nil) {
  360. UInt32 oldMemberNum = [oldValue unsignedIntValue];
  361. NSString *oldFilePath = [NSString stringWithFormat:@"%@groupAvatar_%@_%d.png", tempPath, groupID, oldMemberNum];
  362. NSFileManager *fileManager = [NSFileManager defaultManager];
  363. [fileManager removeItemAtPath:oldFilePath error:nil];
  364. }
  365. // Save image.
  366. BOOL success = [UIImagePNGRepresentation(avatar) writeToFile:filePath atomically:YES];
  367. if (success) {
  368. [defaults setObject:@(memberNum) forKey:groupID];
  369. }
  370. });
  371. }
  372. + (void)getCacheGroupAvatar:(NSString *)groupID callback:(void (^)(UIImage *, NSString *groupID))imageCallBack {
  373. if (groupID == nil || groupID.length == 0) {
  374. if (imageCallBack) {
  375. imageCallBack(nil, groupID);
  376. }
  377. return;
  378. }
  379. [[V2TIMManager sharedInstance] getGroupsInfo:@[ groupID ]
  380. succ:^(NSArray<V2TIMGroupInfoResult *> *groupResultList) {
  381. V2TIMGroupInfoResult *groupInfo = groupResultList.firstObject;
  382. if (!groupInfo) {
  383. imageCallBack(nil, groupID);
  384. return;
  385. }
  386. UInt32 memberNum = groupInfo.info.memberCount;
  387. memberNum = MAX(1, memberNum);
  388. memberNum = MIN(memberNum, 9);
  389. ;
  390. NSString *tempPath = NSTemporaryDirectory();
  391. NSString *filePath = [NSString stringWithFormat:@"%@groupAvatar_%@_%u.png", tempPath, groupID, (unsigned int)memberNum];
  392. NSFileManager *fileManager = [NSFileManager defaultManager];
  393. UIImage *avatar = nil;
  394. BOOL success = [fileManager fileExistsAtPath:filePath];
  395. if (success) {
  396. avatar = [[UIImage alloc] initWithContentsOfFile:filePath];
  397. NSString *key = [NSString stringWithFormat:@"TUIConversationLastGroupMember_%@", groupID];
  398. [NSUserDefaults.standardUserDefaults setInteger:memberNum forKey:key];
  399. [NSUserDefaults.standardUserDefaults synchronize];
  400. }
  401. imageCallBack(avatar, groupInfo.info.groupID);
  402. }
  403. fail:^(int code, NSString *msg) {
  404. imageCallBack(nil, groupID);
  405. }];
  406. }
  407. + (UIImage *)getCacheAvatarForGroup:(NSString *)groupId number:(UInt32)memberNum {
  408. memberNum = MAX(1, memberNum);
  409. memberNum = MIN(memberNum, 9);
  410. ;
  411. NSString *tempPath = NSTemporaryDirectory();
  412. NSString *filePath = [NSString stringWithFormat:@"%@groupAvatar_%@_%u.png", tempPath, groupId, (unsigned int)memberNum];
  413. NSFileManager *fileManager = [NSFileManager defaultManager];
  414. UIImage *avatar = nil;
  415. BOOL success = [fileManager fileExistsAtPath:filePath];
  416. if (success) {
  417. avatar = [[UIImage alloc] initWithContentsOfFile:filePath];
  418. }
  419. return avatar;
  420. }
  421. + (void)asyncClearCacheAvatarForGroup:(NSString *)groupID {
  422. dispatch_async(dispatch_get_global_queue(0, 0), ^{
  423. NSString *tempPath = NSTemporaryDirectory();
  424. for (int i = 0; i < 9; i++) {
  425. NSString *filePath = [NSString stringWithFormat:@"%@groupAvatar_%@_%d.png", tempPath, groupID, (i + 1)];
  426. if ([NSFileManager.defaultManager fileExistsAtPath:filePath]) {
  427. [NSFileManager.defaultManager removeItemAtPath:filePath error:nil];
  428. }
  429. }
  430. });
  431. }
  432. @end
  433. /////////////////////////////////////////////////////////////////////////////////
  434. //
  435. // TUIImageCache
  436. //
  437. /////////////////////////////////////////////////////////////////////////////////
  438. @interface TUIImageCache ()
  439. @property(nonatomic, strong) NSMutableDictionary *resourceCache;
  440. @property(nonatomic, strong) NSMutableDictionary *faceCache;
  441. @end
  442. @implementation TUIImageCache
  443. + (instancetype)sharedInstance {
  444. static dispatch_once_t onceToken;
  445. static TUIImageCache *instance;
  446. dispatch_once(&onceToken, ^{
  447. instance = [[TUIImageCache alloc] init];
  448. [UIImage d_fixResizableImage];
  449. });
  450. return instance;
  451. }
  452. - (instancetype)init {
  453. self = [super init];
  454. if (self) {
  455. _resourceCache = [NSMutableDictionary dictionary];
  456. _faceCache = [NSMutableDictionary dictionary];
  457. }
  458. return self;
  459. }
  460. - (void)addResourceToCache:(NSString *)path {
  461. __weak typeof(self) ws = self;
  462. [TUITool asyncDecodeImage:path
  463. complete:^(NSString *key, UIImage *image) {
  464. __strong __typeof(ws) strongSelf = ws;
  465. if (key && image) {
  466. [strongSelf.resourceCache setValue:image forKey:key];
  467. }
  468. }];
  469. }
  470. - (UIImage *)getResourceFromCache:(NSString *)path {
  471. if (path.length == 0) {
  472. return nil;
  473. }
  474. UIImage *image = [_resourceCache objectForKey:path];
  475. if (!image) {
  476. image = [UIImage d_imagePath:path];
  477. }
  478. return image;
  479. }
  480. - (void)addFaceToCache:(NSString *)path {
  481. __weak typeof(self) ws = self;
  482. [TUITool asyncDecodeImage:path
  483. complete:^(NSString *key, UIImage *image) {
  484. __strong __typeof(ws) strongSelf = ws;
  485. if (key && image) {
  486. [strongSelf.faceCache setValue:image forKey:key];
  487. }
  488. }];
  489. }
  490. - (UIImage *)getFaceFromCache:(NSString *)path {
  491. if (path.length == 0) {
  492. return nil;
  493. }
  494. UIImage *image = [_faceCache objectForKey:path];
  495. if (!image) {
  496. // gif extion
  497. if ([path tui_containsString:@".gif"]) {
  498. image = [UIImage sd_imageWithGIFData:[NSData dataWithContentsOfFile:path]];
  499. }
  500. else {
  501. image = [UIImage imageWithContentsOfFile:path];
  502. if (!image) {
  503. // gif
  504. NSString *formatPath = [path stringByAppendingString:@".gif"];
  505. image = [UIImage sd_imageWithGIFData:[NSData dataWithContentsOfFile:formatPath]];
  506. }
  507. }
  508. if (!image) {
  509. image = [_faceCache objectForKey:TUIChatFaceImagePath(@"ic_unknown_image")];
  510. }
  511. }
  512. return image;
  513. }
  514. @end
  515. @interface IUCoreView : UIView
  516. @property(nonatomic, strong) UIView *view;
  517. @end
  518. @implementation IUCoreView
  519. - (instancetype)init {
  520. self = [super init];
  521. if (self) {
  522. self.view = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 10, 10)];
  523. [self addSubview:self.view];
  524. }
  525. return self;
  526. }
  527. @end
  528. @implementation TUINavigationController
  529. - (instancetype)initWithRootViewController:(UIViewController *)rootViewController {
  530. if (self = [super initWithRootViewController:rootViewController]) {
  531. self.interactivePopGestureRecognizer.delegate = self;
  532. self.delegate = self;
  533. }
  534. return self;
  535. }
  536. - (void)viewWillAppear:(BOOL)animated {
  537. [super viewWillAppear:animated];
  538. if (@available(iOS 13.0, *)) {
  539. UINavigationBarAppearance *appearance = [UINavigationBarAppearance new];
  540. [appearance configureWithDefaultBackground];
  541. appearance.shadowColor = nil;
  542. appearance.backgroundEffect = nil;
  543. appearance.backgroundColor = self.navigationBackColor;
  544. self.navigationBar.backgroundColor = self.navigationBackColor;
  545. self.navigationBar.barTintColor = self.navigationBackColor;
  546. self.navigationBar.shadowImage = [UIImage new];
  547. self.navigationBar.standardAppearance = appearance;
  548. self.navigationBar.scrollEdgeAppearance = appearance;
  549. } else {
  550. self.navigationBar.backgroundColor = self.navigationBackColor;
  551. self.navigationBar.barTintColor = self.navigationBackColor;
  552. self.navigationBar.shadowImage = [UIImage new];
  553. }
  554. }
  555. - (void)viewDidLoad {
  556. [super viewDidLoad];
  557. self.delegate = self;
  558. }
  559. - (void)back {
  560. if ([self.uiNaviDelegate respondsToSelector:@selector(navigationControllerDidClickLeftButton:)]) {
  561. [self.uiNaviDelegate navigationControllerDidClickLeftButton:self];
  562. }
  563. [self popViewControllerAnimated:YES];
  564. }
  565. - (UIColor *)navigationBackColor {
  566. if (!_navigationBackColor) {
  567. _navigationBackColor = [self tintColor];
  568. }
  569. return _navigationBackColor;
  570. }
  571. - (UIColor *)tintColor {
  572. return TUICoreDynamicColor(@"head_bg_gradient_start_color", @"#EBF0F6");
  573. }
  574. - (void)pushViewController:(UIViewController *)viewController animated:(BOOL)animated {
  575. if (self.viewControllers.count != 0) {
  576. viewController.hidesBottomBarWhenPushed = YES;
  577. self.tabBarController.tabBar.hidden = YES;
  578. UIImage *image = self.navigationItemBackArrowImage;
  579. image = [image imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal];
  580. UIBarButtonItem *back = [[UIBarButtonItem alloc] initWithImage:image style:UIBarButtonItemStylePlain target:self action:@selector(back)];
  581. viewController.navigationItem.leftBarButtonItems = @[ back ];
  582. viewController.navigationItem.leftItemsSupplementBackButton = NO;
  583. }
  584. [super pushViewController:viewController animated:animated];
  585. }
  586. - (UIImage *)navigationItemBackArrowImage {
  587. if (!_navigationItemBackArrowImage) {
  588. _navigationItemBackArrowImage = TUICoreDynamicImage(@"nav_back_img", [UIImage imageNamed:TUICoreImagePath(@"nav_back")]);
  589. }
  590. return _navigationItemBackArrowImage;
  591. }
  592. // fix: https://developer.apple.com/forums/thread/660750
  593. - (NSArray<__kindof UIViewController *> *)popToRootViewControllerAnimated:(BOOL)animated {
  594. if (@available(iOS 14.0, *)) {
  595. for (UIViewController *vc in self.viewControllers) {
  596. vc.hidesBottomBarWhenPushed = NO;
  597. self.tabBarController.tabBar.hidden = NO;
  598. }
  599. }
  600. return [super popToRootViewControllerAnimated:animated];
  601. }
  602. - (void)navigationController:(UINavigationController *)navigationController didShowViewController:(UIViewController *)viewController animated:(BOOL)animated {
  603. if (navigationController.viewControllers.count == 1) {
  604. /**
  605. * If the number of view controllers in the stack is 1, it means only the root controller, clear the currentShowVC, and disable the swipe gesture for
  606. * the following method
  607. */
  608. self.currentShowVC = Nil;
  609. } else {
  610. /**
  611. * Assign the pushed view controller to currentShowVC
  612. */
  613. self.currentShowVC = viewController;
  614. }
  615. if ([navigationController respondsToSelector:@selector(interactivePopGestureRecognizer)]) {
  616. if (self.viewControllers.count == 1) {
  617. /**
  618. * Forbid the sliding back of the home page
  619. */
  620. navigationController.interactivePopGestureRecognizer.enabled = NO;
  621. } else {
  622. navigationController.interactivePopGestureRecognizer.enabled = YES;
  623. }
  624. }
  625. }
  626. - (void)navigationController:(UINavigationController *)navigationController willShowViewController:(UIViewController *)viewController animated:(BOOL)animated {
  627. /**
  628. * Listen to the event returned by the side slide
  629. */
  630. __weak typeof(self) weakSelf = self;
  631. if (@available(iOS 10.0, *)) {
  632. [viewController.transitionCoordinator notifyWhenInteractionChangesUsingBlock:^(id<UIViewControllerTransitionCoordinatorContext> _Nonnull context) {
  633. __strong typeof(weakSelf) strongSelf = weakSelf;
  634. [strongSelf handleSideSlideReturnIfNeeded:context];
  635. }];
  636. } else {
  637. [viewController.transitionCoordinator notifyWhenInteractionEndsUsingBlock:^(id<UIViewControllerTransitionCoordinatorContext> _Nonnull context) {
  638. __strong typeof(weakSelf) strongSelf = weakSelf;
  639. [strongSelf handleSideSlideReturnIfNeeded:context];
  640. }];
  641. }
  642. }
  643. - (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer {
  644. if (gestureRecognizer == self.interactivePopGestureRecognizer) {
  645. if (self.currentShowVC == self.topViewController) {
  646. /**
  647. * If currentShowVC exists, it means that the number of controllers in the stack is greater than 1, allowing the side slide gesture to be activated
  648. */
  649. return YES;
  650. }
  651. return NO;
  652. }
  653. return YES;
  654. }
  655. - (void)handleSideSlideReturnIfNeeded:(id<UIViewControllerTransitionCoordinatorContext>)context {
  656. if (context.isCancelled) {
  657. return;
  658. }
  659. UIViewController *fromVc = [context viewControllerForKey:UITransitionContextFromViewControllerKey];
  660. if ([self.uiNaviDelegate respondsToSelector:@selector(navigationControllerDidSideSlideReturn:fromViewController:)]) {
  661. [self.uiNaviDelegate navigationControllerDidSideSlideReturn:self fromViewController:fromVc];
  662. }
  663. }
  664. @end
  665. @implementation UIAlertController (TUITheme)
  666. - (void)tuitheme_addAction:(UIAlertAction *)action {
  667. if (action.style == UIAlertActionStyleDefault || action.style == UIAlertActionStyleCancel) {
  668. UIColor *tempColor = TUICoreDynamicColor(@"primary_theme_color", @"#147AFF");
  669. [action setValue:tempColor forKey:@"_titleTextColor"];
  670. }
  671. [self addAction:action];
  672. }
  673. @end
  674. static const void *tui_valueCallbackKey = @"tui_valueCallbackKey";
  675. static const void *tui_nonValueCallbackKey = @"tui_nonValueCallbackKey";
  676. static const void *tui_extValueObjKey = @"tui_extValueObjKey";
  677. @implementation NSObject (TUIExtValue)
  678. - (void)setTui_valueCallback:(TUIValueCallbck)tui_valueCallback {
  679. objc_setAssociatedObject(self, tui_valueCallbackKey, tui_valueCallback, OBJC_ASSOCIATION_COPY_NONATOMIC);
  680. }
  681. - (TUIValueCallbck)tui_valueCallback {
  682. return objc_getAssociatedObject(self, tui_valueCallbackKey);
  683. }
  684. - (void)setTui_nonValueCallback:(TUINonValueCallbck)tui_nonValueCallback {
  685. objc_setAssociatedObject(self, tui_nonValueCallbackKey, tui_nonValueCallback, OBJC_ASSOCIATION_COPY_NONATOMIC);
  686. }
  687. - (TUINonValueCallbck)tui_nonValueCallback {
  688. return objc_getAssociatedObject(self, tui_nonValueCallbackKey);
  689. }
  690. - (void)setTui_extValueObj:(id)tui_extValueObj {
  691. objc_setAssociatedObject(self, tui_extValueObjKey, tui_extValueObj, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
  692. }
  693. - (id)tui_extValueObj {
  694. return objc_getAssociatedObject(self, tui_extValueObjKey);
  695. }
  696. @end