增加换肤功能
@@ -0,0 +1,33 @@
|
||||
//
|
||||
// TUISearchResultCellModel.h
|
||||
// Pods
|
||||
//
|
||||
// Created by harvy on 2020/12/24.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <TIMCommon/TIMDefine.h>
|
||||
|
||||
@import UIKit;
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface TUISearchResultCellModel : NSObject
|
||||
|
||||
@property(nonatomic, copy) NSString* __nullable title;
|
||||
@property(nonatomic, copy) NSString* __nullable details;
|
||||
@property(nonatomic, strong) NSAttributedString* __nullable titleAttributeString;
|
||||
@property(nonatomic, strong) NSAttributedString* __nullable detailsAttributeString;
|
||||
|
||||
@property(nonatomic, copy) NSString* __nullable avatarUrl;
|
||||
@property(nonatomic, assign) TUIKitAvatarType avatarType;
|
||||
@property(nonatomic, strong) UIImage* __nullable avatarImage;
|
||||
@property(nonatomic, copy) NSString* __nullable groupID;
|
||||
@property(nonatomic, copy) NSString* __nullable groupType;
|
||||
|
||||
@property(nonatomic, assign) BOOL hideSeparatorLine;
|
||||
@property(nonatomic, strong) id context;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,19 @@
|
||||
//
|
||||
// TUISearchResultCellModel.m
|
||||
// Pods
|
||||
//
|
||||
// Created by harvy on 2020/12/24.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
//
|
||||
|
||||
#import "TUISearchResultCellModel.h"
|
||||
|
||||
@implementation TUISearchResultCellModel
|
||||
- (instancetype)init {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_avatarType = TAvatarTypeRounded;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
@end
|
||||
21
TUIKit/TUISearch/BaseCell/CellUI/TUISearchResultCell.h
Normal file
@@ -0,0 +1,21 @@
|
||||
//
|
||||
// TUISearchResultCell.h
|
||||
// Pods
|
||||
//
|
||||
// Created by harvy on 2020/12/24.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
@class TUISearchResultCellModel;
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface TUISearchResultCell : UITableViewCell
|
||||
@property(nonatomic, strong) UIImageView *avatarView;
|
||||
@property(nonatomic, strong) UILabel *title_label;
|
||||
|
||||
- (void)fillWithData:(TUISearchResultCellModel *)cellModel;
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
263
TUIKit/TUISearch/BaseCell/CellUI/TUISearchResultCell.m
Normal file
@@ -0,0 +1,263 @@
|
||||
//
|
||||
// TUISearchResultCell.m
|
||||
// Pods
|
||||
//
|
||||
// Created by harvy on 2020/12/24.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
//
|
||||
|
||||
#import "TUISearchResultCell.h"
|
||||
#import <TIMCommon/TIMCommonModel.h>
|
||||
#import <TIMCommon/TIMDefine.h>
|
||||
#import <TUICore/TUIThemeManager.h>
|
||||
#import "TUISearchResultCellModel.h"
|
||||
@interface TUISearchResultCell ()
|
||||
@property(nonatomic, strong) UILabel *detail_title;
|
||||
@property(nonatomic, strong) UIView *separtorView;
|
||||
@property(nonatomic, strong) TUISearchResultCellModel *cellModel;
|
||||
|
||||
@end
|
||||
|
||||
@implementation TUISearchResultCell
|
||||
|
||||
- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier {
|
||||
if (self = [super initWithStyle:style reuseIdentifier:reuseIdentifier]) {
|
||||
[self setupViews];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)setupViews {
|
||||
self.contentView.backgroundColor = TIMCommonDynamicColor(@"form_bg_color", @"#FFFFFF");
|
||||
|
||||
_avatarView = [[UIImageView alloc] init];
|
||||
[self.contentView addSubview:_avatarView];
|
||||
|
||||
_title_label = [[UILabel alloc] init];
|
||||
_title_label.text = @"";
|
||||
_title_label.textColor = TIMCommonDynamicColor(@"form_title_color", @"#000000");
|
||||
_title_label.font = [UIFont systemFontOfSize:14.0];
|
||||
_title_label.rtlAlignment = TUITextRTLAlignmentLeading;
|
||||
[self.contentView addSubview:_title_label];
|
||||
|
||||
_detail_title = [[UILabel alloc] init];
|
||||
_detail_title.text = @"";
|
||||
_detail_title.textColor = TIMCommonDynamicColor(@"form_subtitle_color", @"#888888");
|
||||
_detail_title.font = [UIFont systemFontOfSize:12.0];
|
||||
_detail_title.rtlAlignment = TUITextRTLAlignmentLeading;
|
||||
[self.contentView addSubview:_detail_title];
|
||||
|
||||
_separtorView = [[UIView alloc] init];
|
||||
_separtorView.backgroundColor = TIMCommonDynamicColor(@"separator_color", @"#DBDBDB");
|
||||
[self.contentView addSubview:_separtorView];
|
||||
|
||||
self.selectionStyle = UITableViewCellSelectionStyleNone;
|
||||
}
|
||||
|
||||
- (void)layoutSubviews {
|
||||
[super layoutSubviews];
|
||||
}
|
||||
|
||||
- (void)fillWithData:(TUISearchResultCellModel *)cellModel {
|
||||
self.cellModel = cellModel;
|
||||
|
||||
self.title_label.text = nil;
|
||||
self.title_label.attributedText = nil;
|
||||
self.detail_title.text = nil;
|
||||
self.detail_title.attributedText = nil;
|
||||
|
||||
self.title_label.text = cellModel.title;
|
||||
if (cellModel.titleAttributeString) {
|
||||
self.title_label.attributedText = cellModel.titleAttributeString;
|
||||
}
|
||||
self.detail_title.text = cellModel.details;
|
||||
if (cellModel.detailsAttributeString) {
|
||||
self.detail_title.attributedText = cellModel.detailsAttributeString;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup default avatar
|
||||
*/
|
||||
if (cellModel.groupID.length > 0) {
|
||||
/**
|
||||
* If it is a group, change the group default avatar to the last used avatar
|
||||
*/
|
||||
UIImage *avatar = nil;
|
||||
if (TUIConfig.defaultConfig.enableGroupGridAvatar) {
|
||||
NSString *key = [NSString stringWithFormat:@"TUIConversationLastGroupMember_%@", cellModel.groupID];
|
||||
NSInteger member = [NSUserDefaults.standardUserDefaults integerForKey:key];
|
||||
avatar = [TUIGroupAvatar getCacheAvatarForGroup:cellModel.groupID number:(UInt32)member];
|
||||
}
|
||||
cellModel.avatarImage = avatar ? avatar : DefaultGroupAvatarImageByGroupType(cellModel.groupType);
|
||||
}
|
||||
|
||||
@weakify(self);
|
||||
[[RACObserve(cellModel, avatarUrl) takeUntil:self.rac_prepareForReuseSignal] subscribeNext:^(NSString *faceUrl) {
|
||||
@strongify(self);
|
||||
if (cellModel.groupID.length > 0) {
|
||||
/**
|
||||
* Group avatar
|
||||
*/
|
||||
if (IS_NOT_EMPTY_NSSTRING(faceUrl)) {
|
||||
/**
|
||||
* The group avatar has been manually set externally
|
||||
*/
|
||||
[self.avatarView sd_setImageWithURL:[NSURL URLWithString:faceUrl] placeholderImage:self.cellModel.avatarImage];
|
||||
} else {
|
||||
/**
|
||||
* The group avatar has not been set externally. If the synthetic avatar is allowed, the synthetic avatar will be used; otherwise, the default
|
||||
* avatar will be used.
|
||||
*/
|
||||
if (TUIConfig.defaultConfig.enableGroupGridAvatar) {
|
||||
/**
|
||||
* If the synthetic avatar is allowed, the synthetic avatar will be used
|
||||
* 1. Asynchronously obtain the cached synthetic avatar according to the number of group members
|
||||
* 2. If the cache is hit, use the cached synthetic avatar directly
|
||||
* 3. If the cache is not hit, recompose a new avatar
|
||||
*
|
||||
* Note:
|
||||
* 1. Since "asynchronously obtaining cached avatars" and "synthesizing avatars" take a long time, it is easy to cause cell reuse problems, so
|
||||
* it is necessary to confirm whether to assign values directly according to groupID.
|
||||
* 2. Use SDWebImage to implement placeholder, because SDWebImage has already dealt with the problem of cell reuse
|
||||
*/
|
||||
|
||||
// 1. Obtain group avatar from cache
|
||||
|
||||
// fix: Th getCacheGroupAvatar needs to request the
|
||||
// network. When the network is disconnected, since the headImageView is not set, the current conversation sends a message, the conversation
|
||||
// is moved up, and the avatar of the first conversation is reused, resulting in confusion of the avatar.
|
||||
[self.avatarView sd_setImageWithURL:nil placeholderImage:cellModel.avatarImage];
|
||||
[TUIGroupAvatar
|
||||
getCacheGroupAvatar:cellModel.groupID
|
||||
callback:^(UIImage *avatar, NSString *groupID) {
|
||||
@strongify(self);
|
||||
if ([groupID isEqualToString:self.cellModel.groupID]) {
|
||||
// 1.1 When the callback is invoked, the cell is not reused
|
||||
|
||||
if (avatar != nil) {
|
||||
// 2. Hit the cache and assign directly
|
||||
[self.avatarView sd_setImageWithURL:nil placeholderImage:avatar];
|
||||
} else {
|
||||
// 3. Synthesize new avatars asynchronously without hitting cache
|
||||
|
||||
[self.avatarView sd_setImageWithURL:nil placeholderImage:cellModel.avatarImage];
|
||||
[TUIGroupAvatar
|
||||
fetchGroupAvatars:cellModel.groupID
|
||||
placeholder:cellModel.avatarImage
|
||||
callback:^(BOOL success, UIImage *image, NSString *groupID) {
|
||||
@strongify(self);
|
||||
if ([groupID isEqualToString:self.cellModel.groupID]) {
|
||||
// When the callback is invoked, the cell is not reused
|
||||
[self.avatarView
|
||||
sd_setImageWithURL:nil
|
||||
placeholderImage:success ? image
|
||||
: DefaultGroupAvatarImageByGroupType(self.cellModel.groupType)];
|
||||
} else {
|
||||
// callback, When the callback is invoked, the cell has been reused to other groupIDs.
|
||||
// Since a new callback will be triggered when the new groupID synthesizes new avatar, it is
|
||||
// ignored here
|
||||
}
|
||||
}];
|
||||
}
|
||||
} else {
|
||||
// 1.2 When the callback is invoked, the cell has been reused to other groupIDs. Since a new callback will be triggered
|
||||
// when the new groupID gets the cache, it is ignored here
|
||||
}
|
||||
}];
|
||||
} else {
|
||||
/**
|
||||
* Synthetic avatars are not allowed, use the default avatar directly
|
||||
*/
|
||||
[self.avatarView sd_setImageWithURL:nil placeholderImage:cellModel.avatarImage];
|
||||
}
|
||||
}
|
||||
} else {
|
||||
/**
|
||||
* Personal avatar
|
||||
*/
|
||||
[self.avatarView sd_setImageWithURL:[NSURL URLWithString:faceUrl] placeholderImage:self.cellModel.avatarImage];
|
||||
}
|
||||
}];
|
||||
|
||||
// tell constraints they need updating
|
||||
[self setNeedsUpdateConstraints];
|
||||
|
||||
// update constraints now so we can animate the change
|
||||
[self updateConstraintsIfNeeded];
|
||||
|
||||
[self layoutIfNeeded];
|
||||
}
|
||||
+ (BOOL)requiresConstraintBasedLayout {
|
||||
return YES;
|
||||
}
|
||||
|
||||
// this is Apple's recommended place for adding/updating constraints
|
||||
- (void)updateConstraints {
|
||||
|
||||
[super updateConstraints];
|
||||
|
||||
CGSize headSize = CGSizeMake(kScale390(40), kScale390(40));
|
||||
|
||||
[self.avatarView mas_remakeConstraints:^(MASConstraintMaker *make) {
|
||||
make.size.mas_equalTo(headSize);
|
||||
make.centerY.mas_equalTo(self.contentView.mas_centerY);
|
||||
make.leading.mas_equalTo(kScale390(10));
|
||||
}];
|
||||
|
||||
if ([TUIConfig defaultConfig].avatarType == TAvatarTypeRounded) {
|
||||
self.avatarView.layer.masksToBounds = YES;
|
||||
self.avatarView.layer.cornerRadius = headSize.height / 2;
|
||||
} else if ([TUIConfig defaultConfig].avatarType == TAvatarTypeRadiusCorner) {
|
||||
self.avatarView.layer.masksToBounds = YES;
|
||||
self.avatarView.layer.cornerRadius = [TUIConfig defaultConfig].avatarCornerRadius;
|
||||
}
|
||||
|
||||
NSString *title = self.title_label.text;
|
||||
if (title.length == 0) {
|
||||
title = self.title_label.attributedText.string;
|
||||
}
|
||||
NSString *detail = self.detail_title.text;
|
||||
if (detail.length == 0) {
|
||||
detail = self.detail_title.attributedText.string;
|
||||
}
|
||||
[self.title_label sizeToFit];
|
||||
[self.detail_title sizeToFit];
|
||||
if (title.length && self.detail_title.text.length) {
|
||||
[self.title_label mas_remakeConstraints:^(MASConstraintMaker *make) {
|
||||
make.top.mas_equalTo(self.avatarView.mas_top);
|
||||
make.leading.mas_equalTo(self.avatarView.mas_trailing).mas_offset(10);
|
||||
make.trailing.mas_equalTo(self.contentView.mas_trailing).mas_offset(-10);
|
||||
make.height.mas_greaterThanOrEqualTo(self.title_label.frame.size.height);
|
||||
}];
|
||||
|
||||
[self.detail_title mas_remakeConstraints:^(MASConstraintMaker *make) {
|
||||
make.bottom.mas_equalTo(self.avatarView.mas_bottom);
|
||||
make.leading.mas_equalTo(self.avatarView.mas_trailing).mas_offset(10);
|
||||
make.trailing.mas_equalTo(self.contentView.mas_trailing).mas_offset(-10);
|
||||
make.height.mas_greaterThanOrEqualTo(self.detail_title.frame.size.height);
|
||||
}];
|
||||
} else {
|
||||
[self.title_label mas_remakeConstraints:^(MASConstraintMaker *make) {
|
||||
make.centerY.mas_equalTo(self.avatarView.mas_centerY);
|
||||
make.leading.mas_equalTo(self.avatarView.mas_trailing).mas_offset(10);
|
||||
make.trailing.mas_equalTo(self.contentView.mas_trailing).mas_offset(-10);
|
||||
make.height.mas_greaterThanOrEqualTo(self.title_label.frame.size.height);
|
||||
}];
|
||||
|
||||
[self.detail_title mas_remakeConstraints:^(MASConstraintMaker *make) {
|
||||
make.centerY.mas_equalTo(self.avatarView.mas_centerY);
|
||||
make.leading.mas_equalTo(self.avatarView.mas_trailing).mas_offset(10);
|
||||
make.trailing.mas_equalTo(self.contentView.mas_trailing).mas_offset(-10);
|
||||
make.height.mas_greaterThanOrEqualTo(self.detail_title.frame.size.height);
|
||||
}];
|
||||
}
|
||||
[self.separtorView mas_remakeConstraints:^(MASConstraintMaker *make) {
|
||||
make.leading.mas_equalTo(self.avatarView.mas_trailing);
|
||||
make.bottom.mas_equalTo(self.contentView.mas_bottom).mas_offset(-1);
|
||||
make.width.mas_equalTo(self.contentView);
|
||||
make.height.mas_equalTo(1);
|
||||
}];
|
||||
|
||||
}
|
||||
|
||||
@end
|
||||
91
TUIKit/TUISearch/BaseDataProvider/TUISearchDataProvider.h
Normal file
@@ -0,0 +1,91 @@
|
||||
//
|
||||
// TUISearchDataProvider.h
|
||||
// Pods
|
||||
//
|
||||
// Created by harvy on 2020/12/28.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <TIMCommon/TIMDefine.h>
|
||||
@class TUISearchResultCellModel;
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
#ifndef __TUISearchDataProvider_H__
|
||||
#define __TUISearchDataProvider_H__
|
||||
|
||||
#define kSearchChatHistoryConversationId @"Id"
|
||||
#define kSearchChatHistoryConverationInfo @"conversation"
|
||||
#define kSearchChatHistoryConversationMsgs @"msgs"
|
||||
|
||||
///////////////////////////////////////////////////////////////////// Configuration /////////////////////////////////////////////////////////////////
|
||||
|
||||
/**
|
||||
* The default maximum number of each module, if it is equal to or exceeds, it will display "View More***"
|
||||
*/
|
||||
#define kMaxNumOfPerModule 3
|
||||
|
||||
/**
|
||||
* The enumeration name represents the searched module, and the enumeration value represents the order between modules
|
||||
*/
|
||||
typedef NS_ENUM(NSInteger, TUISearchResultModule) {
|
||||
TUISearchResultModuleAll = 1 << 0,
|
||||
TUISearchResultModuleContact = 1 << 1,
|
||||
TUISearchResultModuleGroup = 1 << 2,
|
||||
TUISearchResultModuleChatHistory = 1 << 3,
|
||||
};
|
||||
|
||||
typedef NSString *TUISearchParamKey;
|
||||
FOUNDATION_EXTERN TUISearchParamKey TUISearchChatHistoryParamKeyConversationId;
|
||||
FOUNDATION_EXTERN TUISearchParamKey TUISearchChatHistoryParamKeyCount;
|
||||
FOUNDATION_EXPORT TUISearchParamKey TUISearchChatHistoryParamKeyPage;
|
||||
FOUNDATION_EXTERN NSUInteger TUISearchDefaultPageSize;
|
||||
|
||||
static inline NSString *titleForModule(TUISearchResultModule module, BOOL isHeader) {
|
||||
NSString *headerTitle = @"";
|
||||
NSString *footerTitle = @"";
|
||||
switch (module) {
|
||||
case TUISearchResultModuleContact:
|
||||
headerTitle = TIMCommonLocalizableString(TUIKitSearchItemHeaderTitleContact);
|
||||
footerTitle = TIMCommonLocalizableString(TUIKitSearchItemFooterTitleContact);
|
||||
break;
|
||||
case TUISearchResultModuleGroup:
|
||||
headerTitle = TIMCommonLocalizableString(TUIKitSearchItemHeaderTitleGroup);
|
||||
footerTitle = TIMCommonLocalizableString(TUIKitSearchItemFooterTitleGroup);
|
||||
break;
|
||||
case TUISearchResultModuleChatHistory:
|
||||
headerTitle = TIMCommonLocalizableString(TUIkitSearchItemHeaderTitleChatHistory);
|
||||
footerTitle = TIMCommonLocalizableString(TUIKitSearchItemFooterTitleChatHistory);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return isHeader ? headerTitle : footerTitle;
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#endif
|
||||
|
||||
@protocol TUISearchResultDelegate <NSObject>
|
||||
|
||||
- (void)onSearchResults:(NSDictionary<NSNumber *, NSArray<TUISearchResultCellModel *> *> *)results forModules:(TUISearchResultModule)modules;
|
||||
- (void)onSearchError:(NSString *)errMsg;
|
||||
|
||||
@end
|
||||
|
||||
@interface TUISearchDataProvider : NSObject
|
||||
|
||||
@property(nonatomic, weak) id<TUISearchResultDelegate> delegate;
|
||||
|
||||
@property(nonatomic, strong, readonly) NSMutableDictionary<NSNumber *, NSArray<TUISearchResultCellModel *> *> *resultSet;
|
||||
|
||||
- (void)searchForKeyword:(NSString *)keyword forModules:(TUISearchResultModule)modules param:(NSDictionary<TUISearchParamKey, id> *__nullable)param;
|
||||
|
||||
+ (NSAttributedString *)attributeStringWithText:(NSString *__nullable)text key:(NSString *__nullable)key;
|
||||
+ (NSString *)matchedTextForMessage:(V2TIMMessage *)msg withKey:(NSString *)key;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
458
TUIKit/TUISearch/BaseDataProvider/TUISearchDataProvider.m
Normal file
@@ -0,0 +1,458 @@
|
||||
//
|
||||
// TUISearchDataProvider.m
|
||||
// Pods
|
||||
//
|
||||
// Created by harvy on 2020/12/28.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
//
|
||||
|
||||
#import "TUISearchDataProvider.h"
|
||||
#import <TIMCommon/TIMDefine.h>
|
||||
#import <TUICore/TUIThemeManager.h>
|
||||
#import "TUISearchGroupDataProvider.h"
|
||||
#import "TUISearchResultCellModel.h"
|
||||
#import <TUICore/NSString+TUIUtil.h>
|
||||
|
||||
TUISearchParamKey TUISearchChatHistoryParamKeyConversationId = @"TUISearchChatHistoryParamKeyConversationId";
|
||||
TUISearchParamKey TUISearchChatHistoryParamKeyCount = @"TUISearchChatHistoryParamKeyCount";
|
||||
TUISearchParamKey TUISearchChatHistoryParamKeyPage = @"TUISearchChatHistoryParamKeyPage";
|
||||
NSUInteger TUISearchDefaultPageSize = 20;
|
||||
|
||||
typedef void (^TUISearchResultCallback)(BOOL succ, NSString *__nullable errMsg, NSArray<TUISearchResultCellModel *> *__nullable results);
|
||||
|
||||
@interface TUISearchDataProvider ()
|
||||
|
||||
@property(nonatomic, strong) NSMutableDictionary<NSNumber *, NSArray<TUISearchResultCellModel *> *> *resultSet;
|
||||
|
||||
@end
|
||||
|
||||
@implementation TUISearchDataProvider
|
||||
|
||||
- (void)searchForKeyword:(NSString *)keyword forModules:(TUISearchResultModule)modules param:(NSDictionary<TUISearchParamKey, id> *__nullable)param {
|
||||
__weak typeof(self) weakSelf = self;
|
||||
if (![NSThread isMainThread]) {
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[weakSelf searchForKeyword:keyword forModules:modules param:param];
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (keyword.length == 0) {
|
||||
[self.resultSet removeAllObjects];
|
||||
if ([self.delegate respondsToSelector:@selector(onSearchResults:forModules:)]) {
|
||||
[self.delegate onSearchResults:self.resultSet forModules:modules];
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
dispatch_group_t group = dispatch_group_create();
|
||||
BOOL request = NO;
|
||||
|
||||
// Contact
|
||||
if ((modules == TUISearchResultModuleAll) || (modules & TUISearchResultModuleContact)) {
|
||||
request = YES;
|
||||
dispatch_group_enter(group);
|
||||
[self searchContacts:keyword
|
||||
callback:^(BOOL succ, NSString *errMsg, NSArray *results) {
|
||||
if (succ && results) {
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
if (results.count) {
|
||||
[weakSelf.resultSet setObject:results forKey:@(TUISearchResultModuleContact)];
|
||||
} else {
|
||||
[weakSelf.resultSet removeObjectForKey:@(TUISearchResultModuleContact)];
|
||||
}
|
||||
dispatch_group_leave(group);
|
||||
});
|
||||
return;
|
||||
}
|
||||
dispatch_group_leave(group);
|
||||
}];
|
||||
}
|
||||
|
||||
// Group
|
||||
if ((modules == TUISearchResultModuleAll) || (modules & TUISearchResultModuleGroup)) {
|
||||
request = YES;
|
||||
dispatch_group_enter(group);
|
||||
[self searchGroups:keyword
|
||||
callback:^(BOOL succ, NSString *errMsg, NSArray *results) {
|
||||
if (succ && results) {
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
if (results.count) {
|
||||
[weakSelf.resultSet setObject:results forKey:@(TUISearchResultModuleGroup)];
|
||||
} else {
|
||||
[weakSelf.resultSet removeObjectForKey:@(TUISearchResultModuleGroup)];
|
||||
}
|
||||
dispatch_group_leave(group);
|
||||
});
|
||||
return;
|
||||
}
|
||||
dispatch_group_leave(group);
|
||||
}];
|
||||
}
|
||||
|
||||
// Chat history
|
||||
if ((modules == TUISearchResultModuleAll) || (modules & TUISearchResultModuleChatHistory)) {
|
||||
request = YES;
|
||||
dispatch_group_enter(group);
|
||||
[self searchChatHistory:keyword
|
||||
param:param
|
||||
callback:^(BOOL succ, NSString *_Nullable errMsg, NSArray<TUISearchResultCellModel *> *_Nullable results) {
|
||||
if (succ && results) {
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
if (results.count) {
|
||||
[weakSelf.resultSet setObject:results forKey:@(TUISearchResultModuleChatHistory)];
|
||||
} else {
|
||||
[weakSelf.resultSet removeObjectForKey:@(TUISearchResultModuleChatHistory)];
|
||||
}
|
||||
});
|
||||
}
|
||||
dispatch_group_leave(group);
|
||||
}];
|
||||
}
|
||||
|
||||
if (!request) {
|
||||
if ([self.delegate respondsToSelector:@selector(onSearchError:)]) {
|
||||
[self.delegate onSearchError:@"search module not exists"];
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
|
||||
if ([self.delegate respondsToSelector:@selector(onSearchResults:forModules:)]) {
|
||||
[self.delegate onSearchResults:self.resultSet forModules:modules];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
- (void)searchContacts:(NSString *)keyword callback:(TUISearchResultCallback)callback {
|
||||
if (keyword == nil || callback == nil) {
|
||||
if (callback) {
|
||||
callback(NO, @"invalid parameters, keyword is null", nil);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
V2TIMFriendSearchParam *param = [[V2TIMFriendSearchParam alloc] init];
|
||||
param.keywordList = @[ keyword ];
|
||||
param.isSearchUserID = YES;
|
||||
param.isSearchNickName = YES;
|
||||
param.isSearchRemark = YES;
|
||||
[V2TIMManager.sharedInstance searchFriends:param
|
||||
succ:^(NSArray<V2TIMFriendInfoResult *> *infoList) {
|
||||
NSMutableArray *arrayM = [NSMutableArray array];
|
||||
for (V2TIMFriendInfoResult *result in infoList) {
|
||||
TUISearchResultCellModel *cellModel = [[TUISearchResultCellModel alloc] init];
|
||||
NSString *title = result.friendInfo.friendRemark;
|
||||
if (title.length == 0) {
|
||||
title = result.friendInfo.userFullInfo.nickName;
|
||||
}
|
||||
if (title.length == 0) {
|
||||
title = result.friendInfo.userID;
|
||||
}
|
||||
|
||||
NSString *why = @"";
|
||||
if ([result.friendInfo.friendRemark.lowercaseString containsString:keyword.lowercaseString]) {
|
||||
why = result.friendInfo.friendRemark;
|
||||
} else if ([result.friendInfo.userFullInfo.nickName.lowercaseString containsString:keyword.lowercaseString]) {
|
||||
why = result.friendInfo.userFullInfo.nickName;
|
||||
} else if ([result.friendInfo.userID.lowercaseString containsString:keyword.lowercaseString]) {
|
||||
why = result.friendInfo.userID;
|
||||
}
|
||||
if (why.length) {
|
||||
if ([why isEqualToString:title]) {
|
||||
why = nil;
|
||||
} else {
|
||||
why = [NSString stringWithFormat:TIMCommonLocalizableString(TUIKitSearchResultMatchFormat), why];
|
||||
}
|
||||
}
|
||||
|
||||
cellModel.titleAttributeString = [TUISearchDataProvider attributeStringWithText:title key:keyword];
|
||||
cellModel.detailsAttributeString = [TUISearchDataProvider attributeStringWithText:why key:keyword];
|
||||
cellModel.groupID = nil;
|
||||
cellModel.avatarUrl = result.friendInfo.userFullInfo.faceURL;
|
||||
cellModel.avatarImage = DefaultAvatarImage;
|
||||
cellModel.context = result.friendInfo;
|
||||
[arrayM addObject:cellModel];
|
||||
}
|
||||
callback(YES, nil, arrayM);
|
||||
}
|
||||
fail:^(int code, NSString *desc) {
|
||||
callback(NO, desc, nil);
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)searchGroups:(NSString *)keyword callback:(TUISearchResultCallback)callback {
|
||||
if (keyword == nil || callback == nil) {
|
||||
if (callback) {
|
||||
callback(NO, @"invalid parameters, keyword is null", nil);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
TUISearchGroupParam *param = [[TUISearchGroupParam alloc] init];
|
||||
param.keywordList = @[ keyword ];
|
||||
param.isSearchGroupID = YES;
|
||||
param.isSearchGroupName = YES;
|
||||
param.isSearchGroupMember = YES;
|
||||
param.isSearchMemberRemark = YES;
|
||||
param.isSearchMemberUserID = YES;
|
||||
param.isSearchMemberNickName = YES;
|
||||
param.isSearchMemberNameCard = YES;
|
||||
[TUISearchGroupDataProvider searchGroups:param
|
||||
succ:^(NSArray<TUISearchGroupResult *> *_Nonnull resultSet) {
|
||||
NSMutableArray *arrayM = [NSMutableArray array];
|
||||
for (TUISearchGroupResult *result in resultSet) {
|
||||
TUISearchResultCellModel *cellModel = [[TUISearchResultCellModel alloc] init];
|
||||
NSString *title = result.groupInfo.groupName;
|
||||
if (title.length == 0) {
|
||||
title = result.groupInfo.groupID;
|
||||
}
|
||||
cellModel.titleAttributeString = [TUISearchDataProvider attributeStringWithText:title key:keyword];
|
||||
cellModel.detailsAttributeString = nil;
|
||||
cellModel.groupID = result.groupInfo.groupID;
|
||||
cellModel.groupType = result.groupInfo.groupType;
|
||||
cellModel.avatarImage = DefaultGroupAvatarImageByGroupType(result.groupInfo.groupType);
|
||||
cellModel.avatarUrl = result.groupInfo.faceURL;
|
||||
cellModel.context = result.groupInfo;
|
||||
[arrayM addObject:cellModel];
|
||||
|
||||
if (result.matchField == TUISearchGroupMatchFieldGroupID) {
|
||||
NSString *text = [NSString stringWithFormat:TIMCommonLocalizableString(TUIKitSearchResultMatchGroupIDFormat), result.matchValue];
|
||||
cellModel.detailsAttributeString = [TUISearchDataProvider attributeStringWithText:text key:keyword];
|
||||
} else if (result.matchField == TUISearchGroupMatchFieldMember && result.matchMembers.count) {
|
||||
NSString *text = TIMCommonLocalizableString(TUIKitSearchResultMatchGroupMember);
|
||||
for (int i = 0; i < result.matchMembers.count; i++) {
|
||||
TUISearchGroupMemberMatchResult *memberResult = result.matchMembers[i];
|
||||
text = [text stringByAppendingString:memberResult.memberMatchValue];
|
||||
if (i < result.matchMembers.count - 1) {
|
||||
text = [text stringByAppendingString:@"、"];
|
||||
}
|
||||
}
|
||||
cellModel.detailsAttributeString = [TUISearchDataProvider attributeStringWithText:text key:keyword];
|
||||
}
|
||||
}
|
||||
callback(YES, nil, arrayM);
|
||||
}
|
||||
fail:^(NSInteger code, NSString *_Nonnull desc) {
|
||||
callback(NO, desc, nil);
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)searchChatHistory:(NSString *)keyword param:(NSDictionary<TUISearchParamKey, id> *__nullable)paramters callback:(TUISearchResultCallback)callback {
|
||||
if (keyword == nil || callback == nil) {
|
||||
if (callback) {
|
||||
callback(NO, @"invalid parameters, keyword is null", nil);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
NSUInteger pageSize = TUISearchDefaultPageSize;
|
||||
NSUInteger pageIndex = 0;
|
||||
NSString *conversationID = nil;
|
||||
NSArray *allKeys = paramters.allKeys;
|
||||
BOOL displayWithConveration = YES;
|
||||
|
||||
if ([allKeys containsObject:TUISearchChatHistoryParamKeyCount]) {
|
||||
pageSize = [paramters[TUISearchChatHistoryParamKeyCount] integerValue];
|
||||
}
|
||||
if ([allKeys containsObject:TUISearchChatHistoryParamKeyPage]) {
|
||||
pageIndex = [paramters[TUISearchChatHistoryParamKeyPage] integerValue];
|
||||
}
|
||||
if ([allKeys containsObject:TUISearchChatHistoryParamKeyConversationId]) {
|
||||
conversationID = paramters[TUISearchChatHistoryParamKeyConversationId];
|
||||
displayWithConveration = NO;
|
||||
}
|
||||
|
||||
V2TIMMessageSearchParam *param = [[V2TIMMessageSearchParam alloc] init];
|
||||
param.keywordList = @[ keyword ];
|
||||
param.messageTypeList = nil;
|
||||
param.conversationID = conversationID;
|
||||
param.searchTimePosition = 0;
|
||||
param.searchTimePeriod = 0;
|
||||
param.pageIndex = pageIndex;
|
||||
param.pageSize = pageSize;
|
||||
[V2TIMManager.sharedInstance searchLocalMessages:param
|
||||
succ:^(V2TIMMessageSearchResult *searchResult) {
|
||||
if (searchResult.totalCount == 0) {
|
||||
if (callback) {
|
||||
callback(YES, nil, @[]);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
NSMutableArray *conversationIds = [NSMutableArray array];
|
||||
NSMutableDictionary *conversationInfoMap = [NSMutableDictionary dictionary];
|
||||
NSMutableDictionary *conversationMessageMap = [NSMutableDictionary dictionary];
|
||||
NSMutableDictionary *conversationCountMap = [NSMutableDictionary dictionary];
|
||||
NSArray<V2TIMMessageSearchResultItem *> *messageSearchResultItems = searchResult.messageSearchResultItems;
|
||||
for (V2TIMMessageSearchResultItem *searchItem in messageSearchResultItems) {
|
||||
NSString *conversationID = searchItem.conversationID;
|
||||
NSUInteger messageCount = searchItem.messageCount;
|
||||
NSArray<V2TIMMessage *> *messageList = searchItem.messageList ?: @[];
|
||||
if (conversationID.length == 0) {
|
||||
continue;
|
||||
}
|
||||
[conversationIds addObject:conversationID];
|
||||
conversationMessageMap[conversationID] = messageList;
|
||||
conversationCountMap[conversationID] = @(messageCount);
|
||||
}
|
||||
|
||||
if (conversationIds.count == 0) {
|
||||
if (callback) {
|
||||
callback(YES, nil, @[]);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
[V2TIMManager.sharedInstance getConversationList:conversationIds
|
||||
succ:^(NSArray<V2TIMConversation *> *list) {
|
||||
for (V2TIMConversation *conversation in list) {
|
||||
if (conversation.conversationID) {
|
||||
conversationInfoMap[conversation.conversationID] = conversation;
|
||||
}
|
||||
}
|
||||
NSMutableArray *arrayM = [NSMutableArray array];
|
||||
for (NSString *conversationId in conversationIds) {
|
||||
if (![conversationInfoMap.allKeys containsObject:conversationId]) {
|
||||
continue;
|
||||
}
|
||||
|
||||
V2TIMConversation *conv = conversationInfoMap[conversationId];
|
||||
NSArray *messageList = conversationMessageMap[conversationId];
|
||||
NSUInteger count = [conversationCountMap[conversationId] integerValue];
|
||||
if (displayWithConveration) {
|
||||
TUISearchResultCellModel *cellModel = [[TUISearchResultCellModel alloc] init];
|
||||
NSString *desc = [NSString stringWithFormat:TIMCommonLocalizableString(TUIKitSearchResultDisplayChatHistoryCountFormat), count];
|
||||
if (messageList.count == 1) {
|
||||
V2TIMMessage *firstMessage = messageList.firstObject;
|
||||
desc = [TUISearchDataProvider matchedTextForMessage:(V2TIMMessage *)firstMessage withKey:keyword];
|
||||
}
|
||||
cellModel.title = conv.showName;
|
||||
cellModel.detailsAttributeString = [TUISearchDataProvider attributeStringWithText:desc key:messageList.count == 1 ? keyword : nil];
|
||||
cellModel.groupID = conv.groupID;
|
||||
cellModel.avatarImage = conv.type == V2TIM_GROUP ? DefaultGroupAvatarImageByGroupType(conv.groupType) : DefaultAvatarImage;
|
||||
cellModel.groupType = conv.groupType;
|
||||
cellModel.avatarUrl = conv.faceUrl;
|
||||
cellModel.context = @{
|
||||
kSearchChatHistoryConversationId : conversationId,
|
||||
kSearchChatHistoryConverationInfo : conv,
|
||||
kSearchChatHistoryConversationMsgs : messageList
|
||||
};
|
||||
[arrayM addObject:cellModel];
|
||||
} else {
|
||||
for (V2TIMMessage *message in messageList) {
|
||||
TUISearchResultCellModel *cellModel = [[TUISearchResultCellModel alloc] init];
|
||||
cellModel.title = message.nickName ?: message.sender;
|
||||
NSString *desc = [TUISearchDataProvider matchedTextForMessage:message withKey:keyword];
|
||||
cellModel.detailsAttributeString = [TUISearchDataProvider attributeStringWithText:desc key:keyword];
|
||||
cellModel.groupID = conv.groupID;
|
||||
cellModel.groupType = conv.groupType;
|
||||
cellModel.avatarUrl = message.faceURL;
|
||||
cellModel.avatarImage = conv.type == V2TIM_GROUP ? DefaultGroupAvatarImageByGroupType(conv.groupType) : DefaultAvatarImage;
|
||||
cellModel.context = message;
|
||||
[arrayM addObject:cellModel];
|
||||
}
|
||||
}
|
||||
}
|
||||
TUISearchResultCellModel *lastCellModel = [arrayM lastObject];
|
||||
lastCellModel.hideSeparatorLine = YES;
|
||||
|
||||
if (callback) {
|
||||
callback(YES, nil, arrayM);
|
||||
}
|
||||
}
|
||||
fail:^(int code, NSString *desc) {
|
||||
if (callback) {
|
||||
callback(NO, desc, nil);
|
||||
}
|
||||
}];
|
||||
}
|
||||
fail:^(int code, NSString *desc) {
|
||||
if (callback) {
|
||||
callback(NO, desc, nil);
|
||||
}
|
||||
if (code == ERR_SDK_INTERFACE_NOT_SUPPORT) {
|
||||
[TUITool postUnsupportNotificationOfService:TIMCommonLocalizableString(TUIKitErrorUnsupportIntefaceSearch)];
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
+ (NSAttributedString *)attributeStringWithText:(NSString *)text key:(NSString *)key {
|
||||
if (text.length == 0) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
if (key == nil || key.length == 0 || ![text.lowercaseString tui_containsString:key.lowercaseString]) {
|
||||
NSAttributedString *attributeString = [[NSAttributedString alloc] initWithString:text
|
||||
attributes:@{NSForegroundColorAttributeName : [UIColor darkGrayColor]}];
|
||||
return attributeString;
|
||||
}
|
||||
|
||||
NSMutableAttributedString *attr = [[NSMutableAttributedString alloc] initWithString:text
|
||||
attributes:@{NSForegroundColorAttributeName : [UIColor darkGrayColor]}];
|
||||
|
||||
NSUInteger loc = 0;
|
||||
NSUInteger len = text.length;
|
||||
while (len > 0) {
|
||||
NSRange range = [text.lowercaseString rangeOfString:key.lowercaseString options:NSCaseInsensitiveSearch range:NSMakeRange(loc, len)];
|
||||
if (range.length) {
|
||||
[attr addAttribute:NSForegroundColorAttributeName value:TIMCommonDynamicColor(@"primary_theme_color", @"#147AFF") range:range];
|
||||
loc = range.location + 1;
|
||||
len = text.length - loc;
|
||||
} else {
|
||||
len = 0;
|
||||
loc = 0;
|
||||
}
|
||||
}
|
||||
return [[NSAttributedString alloc] initWithAttributedString:attr];
|
||||
}
|
||||
|
||||
+ (NSString *)matchedTextForMessage:(V2TIMMessage *)msg withKey:(NSString *)key {
|
||||
if (key.length == 0) {
|
||||
return @"";
|
||||
}
|
||||
|
||||
if ((msg.elemType == V2TIM_ELEM_TYPE_TEXT) && [msg.textElem.text tui_containsString:key]) {
|
||||
return msg.textElem.text;
|
||||
} else if ((msg.elemType == V2TIM_ELEM_TYPE_IMAGE) && [msg.imageElem.path tui_containsString:key]) {
|
||||
return msg.imageElem.path;
|
||||
} else if ((msg.elemType == V2TIM_ELEM_TYPE_SOUND) && [msg.soundElem.path tui_containsString:key]) {
|
||||
return msg.soundElem.path;
|
||||
} else if (msg.elemType == V2TIM_ELEM_TYPE_VIDEO) {
|
||||
if ([msg.videoElem.videoPath tui_containsString:key]) {
|
||||
return msg.videoElem.videoPath;
|
||||
} else if ([msg.videoElem.snapshotPath tui_containsString:key]) {
|
||||
return msg.videoElem.snapshotPath;
|
||||
} else {
|
||||
return @"";
|
||||
}
|
||||
} else if ((msg.elemType == V2TIM_ELEM_TYPE_FILE) && [msg.fileElem.path tui_containsString:key]) {
|
||||
return msg.fileElem.path;
|
||||
} else if (msg.elemType == V2TIM_ELEM_TYPE_MERGER) {
|
||||
NSArray *abM = msg.mergerElem.abstractList;
|
||||
NSString *abs = @"";
|
||||
for (NSString *ab in abM) {
|
||||
abs = [abs stringByAppendingString:ab];
|
||||
abs = [abs stringByAppendingString:@","];
|
||||
}
|
||||
if ([msg.mergerElem.title tui_containsString:key]) {
|
||||
return msg.mergerElem.title;
|
||||
} else if ([abs tui_containsString:key]) {
|
||||
return abs;
|
||||
} else {
|
||||
return @"";
|
||||
}
|
||||
}
|
||||
|
||||
return @"";
|
||||
}
|
||||
|
||||
#pragma mark - Lazy
|
||||
- (NSMutableDictionary *)resultSet {
|
||||
if (_resultSet == nil) {
|
||||
_resultSet = [NSMutableDictionary dictionary];
|
||||
}
|
||||
return _resultSet;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,74 @@
|
||||
//
|
||||
// TUISearchGroupDataProvider.h
|
||||
// Pods
|
||||
//
|
||||
// Created by harvy on 2021/3/30.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <TIMCommon/TIMDefine.h>
|
||||
|
||||
@class TUISearchGroupResult;
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
typedef NS_ENUM(NSInteger, TUISearchGroupMatchField) {
|
||||
TUISearchGroupMatchFieldGroupID = 0x1 << 1,
|
||||
TUISearchGroupMatchFieldGroupName = 0x1 << 2,
|
||||
TUISearchGroupMatchFieldMember = 0x1 << 3,
|
||||
};
|
||||
|
||||
typedef NS_ENUM(NSInteger, TUISearchGroupMemberMatchField) {
|
||||
TUISearchGroupMemberMatchFieldUserID = 0x1 << 1,
|
||||
TUISearchGroupMemberMatchFieldNickName = 0x1 << 2,
|
||||
TUISearchGroupMemberMatchFieldRemark = 0x1 << 3,
|
||||
TUISearchGroupMemberMatchFieldNameCard = 0x1 << 4,
|
||||
};
|
||||
|
||||
typedef void (^TUISearchGroupResultListSucc)(NSArray<TUISearchGroupResult *> *resultSet);
|
||||
typedef void (^TUISearchGroupResultListFail)(NSInteger code, NSString *desc);
|
||||
|
||||
#pragma mark - Paramter
|
||||
@interface TUISearchGroupParam : NSObject
|
||||
|
||||
@property(nonatomic, copy) NSArray<NSString *> *keywordList;
|
||||
|
||||
@property(nonatomic, assign) BOOL isSearchGroupID;
|
||||
@property(nonatomic, assign) BOOL isSearchGroupName;
|
||||
@property(nonatomic, assign) BOOL isSearchGroupMember;
|
||||
|
||||
@property(nonatomic, assign) BOOL isSearchMemberUserID;
|
||||
@property(nonatomic, assign) BOOL isSearchMemberNickName;
|
||||
@property(nonatomic, assign) BOOL isSearchMemberRemark;
|
||||
@property(nonatomic, assign) BOOL isSearchMemberNameCard;
|
||||
|
||||
@end
|
||||
|
||||
#pragma mark - Group member match result
|
||||
@interface TUISearchGroupMemberMatchResult : NSObject
|
||||
|
||||
@property(nonatomic, strong, readonly) V2TIMGroupMemberFullInfo *memberInfo;
|
||||
@property(nonatomic, assign, readonly) TUISearchGroupMemberMatchField memberMatchField;
|
||||
@property(nonatomic, copy, readonly) NSString *memberMatchValue;
|
||||
|
||||
@end
|
||||
|
||||
#pragma mark - Group match result
|
||||
@interface TUISearchGroupResult : NSObject
|
||||
|
||||
@property(nonatomic, strong, readonly) V2TIMGroupInfo *groupInfo;
|
||||
@property(nonatomic, assign, readonly) TUISearchGroupMatchField matchField;
|
||||
@property(nonatomic, copy, readonly) NSString *__nullable matchValue;
|
||||
@property(nonatomic, strong, readonly) NSArray<TUISearchGroupMemberMatchResult *> *__nullable matchMembers;
|
||||
|
||||
@end
|
||||
|
||||
#pragma mark - Group Search
|
||||
@interface TUISearchGroupDataProvider : NSObject
|
||||
|
||||
+ (void)searchGroups:(TUISearchGroupParam *)searchParam succ:(TUISearchGroupResultListSucc __nullable)succ fail:(TUISearchGroupResultListFail __nullable)fail;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
280
TUIKit/TUISearch/BaseDataProvider/TUISearchGroupDataProvider.m
Normal file
@@ -0,0 +1,280 @@
|
||||
//
|
||||
// TUISearchGroupDataProvider.m
|
||||
// Pods
|
||||
//
|
||||
// Created by harvy on 2021/3/30.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
//
|
||||
|
||||
#import "TUISearchGroupDataProvider.h"
|
||||
#import "NSString+TUIUtil.h"
|
||||
|
||||
static BOOL match(NSArray *keywords, NSString *text) {
|
||||
BOOL isMatch = NO;
|
||||
for (NSString *keyword in keywords) {
|
||||
if ([text.lowercaseString tui_containsString:keyword]) {
|
||||
isMatch |= YES;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return isMatch;
|
||||
}
|
||||
|
||||
@interface TUISearchGroupResult ()
|
||||
|
||||
@property(nonatomic, copy) NSString *groupId;
|
||||
@property(nonatomic, strong) NSArray<V2TIMGroupMemberFullInfo *> *memberInfos;
|
||||
|
||||
@property(nonatomic, strong) V2TIMGroupInfo *groupInfo;
|
||||
@property(nonatomic, assign) TUISearchGroupMatchField matchField;
|
||||
@property(nonatomic, copy) NSString *matchValue;
|
||||
@property(nonatomic, strong) NSArray<TUISearchGroupMemberMatchResult *> *matchMembers;
|
||||
|
||||
@end
|
||||
@implementation TUISearchGroupResult
|
||||
@end
|
||||
|
||||
@interface TUISearchGroupMemberMatchResult ()
|
||||
@property(nonatomic, strong) V2TIMGroupMemberFullInfo *memberInfo;
|
||||
@property(nonatomic, assign) TUISearchGroupMemberMatchField memberMatchField;
|
||||
@property(nonatomic, copy) NSString *memberMatchValue;
|
||||
@end
|
||||
@implementation TUISearchGroupMemberMatchResult
|
||||
@end
|
||||
|
||||
@implementation TUISearchGroupParam
|
||||
@end
|
||||
|
||||
@implementation TUISearchGroupDataProvider
|
||||
|
||||
+ (void)searchGroups:(TUISearchGroupParam *)searchParam succ:(TUISearchGroupResultListSucc __nullable)succ fail:(TUISearchGroupResultListFail __nullable)fail {
|
||||
if (searchParam == nil) {
|
||||
if (fail) {
|
||||
fail(-1, @"Invalid paramters, searchParam is null");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (searchParam.keywordList == nil || searchParam.keywordList.count == 0 || searchParam.keywordList.count > 5) {
|
||||
if (fail) {
|
||||
fail(-1, @"Invalid paramters, keyword count is zero or beyond the limit of five");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
NSMutableArray *keywords = [NSMutableArray array];
|
||||
for (NSString *keyword in searchParam.keywordList) {
|
||||
[keywords addObject:keyword.lowercaseString];
|
||||
}
|
||||
|
||||
__block NSArray *groupsOne = nil;
|
||||
__block NSArray *groupsTwo = nil;
|
||||
|
||||
dispatch_group_t group = dispatch_group_create();
|
||||
|
||||
dispatch_group_enter(group);
|
||||
[self doSearchGroups:searchParam
|
||||
keywords:keywords
|
||||
succ:^(NSArray<TUISearchGroupResult *> *_Nonnull resultSet) {
|
||||
groupsOne = resultSet;
|
||||
dispatch_group_leave(group);
|
||||
}
|
||||
fail:^(NSInteger code, NSString *_Nonnull desc) {
|
||||
dispatch_group_leave(group);
|
||||
}];
|
||||
|
||||
BOOL isSearchMember = searchParam.isSearchGroupMember;
|
||||
if (isSearchMember) {
|
||||
dispatch_group_enter(group);
|
||||
[self doSearchMembers:searchParam
|
||||
keywords:keywords
|
||||
succ:^(NSArray<TUISearchGroupResult *> *_Nonnull resultSet) {
|
||||
groupsTwo = resultSet;
|
||||
dispatch_group_leave(group);
|
||||
}
|
||||
fail:^(NSInteger code, NSString *_Nonnull desc) {
|
||||
dispatch_group_leave(group);
|
||||
}];
|
||||
}
|
||||
|
||||
__weak typeof(self) weakSelf = self;
|
||||
dispatch_group_notify(group, dispatch_get_global_queue(0, 0), ^{
|
||||
NSArray *resultSet = [weakSelf mergeGroupSets:groupsOne withOthers:groupsTwo];
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
if (succ) {
|
||||
succ(resultSet);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
+ (void)doSearchGroups:(TUISearchGroupParam *)searchParam
|
||||
keywords:(NSArray<NSString *> *)keywords
|
||||
succ:(TUISearchGroupResultListSucc)succ
|
||||
fail:(TUISearchGroupResultListFail)fail {
|
||||
V2TIMGroupSearchParam *groupParam = [[V2TIMGroupSearchParam alloc] init];
|
||||
groupParam.keywordList = keywords;
|
||||
groupParam.isSearchGroupID = searchParam.isSearchGroupID;
|
||||
groupParam.isSearchGroupName = searchParam.isSearchGroupName;
|
||||
|
||||
[V2TIMManager.sharedInstance searchGroups:groupParam
|
||||
succ:^(NSArray<V2TIMGroupInfo *> *groupList) {
|
||||
NSMutableArray *arrayM = [NSMutableArray array];
|
||||
for (V2TIMGroupInfo *groupInfo in groupList) {
|
||||
TUISearchGroupResult *result = [[TUISearchGroupResult alloc] init];
|
||||
result.groupId = groupInfo.groupID;
|
||||
result.groupInfo = groupInfo;
|
||||
result.matchMembers = nil;
|
||||
if (match(keywords, groupInfo.groupName)) {
|
||||
result.matchField = TUISearchGroupMatchFieldGroupName;
|
||||
result.matchValue = groupInfo.groupName;
|
||||
[arrayM addObject:result];
|
||||
continue;
|
||||
}
|
||||
if (match(keywords, groupInfo.groupID)) {
|
||||
result.matchField = TUISearchGroupMatchFieldGroupID;
|
||||
result.matchValue = groupInfo.groupID;
|
||||
[arrayM addObject:result];
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (succ) {
|
||||
succ(arrayM);
|
||||
}
|
||||
}
|
||||
fail:^(int code, NSString *desc) {
|
||||
if (fail) {
|
||||
fail(code, desc);
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
+ (void)doSearchMembers:(TUISearchGroupParam *)searchParam
|
||||
keywords:(NSArray<NSString *> *)keywords
|
||||
succ:(TUISearchGroupResultListSucc)succ
|
||||
fail:(TUISearchGroupResultListFail)fail {
|
||||
V2TIMGroupMemberSearchParam *memberParam = [[V2TIMGroupMemberSearchParam alloc] init];
|
||||
memberParam.keywordList = keywords;
|
||||
memberParam.groupIDList = nil;
|
||||
memberParam.isSearchMemberUserID = searchParam.isSearchMemberUserID;
|
||||
memberParam.isSearchMemberNickName = searchParam.isSearchMemberNickName;
|
||||
memberParam.isSearchMemberNameCard = searchParam.isSearchMemberNameCard;
|
||||
memberParam.isSearchMemberRemark = searchParam.isSearchMemberRemark;
|
||||
|
||||
[V2TIMManager.sharedInstance searchGroupMembers:memberParam
|
||||
succ:^(NSDictionary<NSString *, NSArray<V2TIMGroupMemberFullInfo *> *> *memberList) {
|
||||
NSMutableArray<TUISearchGroupResult *> *resultSet = [NSMutableArray array];
|
||||
NSMutableArray *groupIds = [NSMutableArray array];
|
||||
[memberList enumerateKeysAndObjectsUsingBlock:^(NSString *_Nonnull groupId, NSArray<V2TIMGroupMemberFullInfo *> *_Nonnull obj, BOOL *_Nonnull stop) {
|
||||
[groupIds addObject:groupId];
|
||||
TUISearchGroupResult *result = [[TUISearchGroupResult alloc] init];
|
||||
result.groupId = groupId;
|
||||
result.matchField = TUISearchGroupMatchFieldMember;
|
||||
result.matchValue = nil;
|
||||
result.memberInfos = obj;
|
||||
[resultSet addObject:result];
|
||||
}];
|
||||
|
||||
NSMutableDictionary *groupInfoMap = [NSMutableDictionary dictionary];
|
||||
dispatch_group_t group = dispatch_group_create();
|
||||
{
|
||||
dispatch_group_enter(group);
|
||||
[V2TIMManager.sharedInstance getGroupsInfo:groupIds
|
||||
succ:^(NSArray<V2TIMGroupInfoResult *> *groupResultList) {
|
||||
for (V2TIMGroupInfoResult *groupInfoResult in groupResultList) {
|
||||
if (groupInfoResult.resultCode == 0) {
|
||||
groupInfoMap[groupInfoResult.info.groupID] = groupInfoResult.info;
|
||||
}
|
||||
}
|
||||
dispatch_group_leave(group);
|
||||
}
|
||||
fail:^(int code, NSString *desc) {
|
||||
dispatch_group_leave(group);
|
||||
}];
|
||||
}
|
||||
|
||||
{
|
||||
dispatch_group_enter(group);
|
||||
for (TUISearchGroupResult *result in resultSet) {
|
||||
NSArray *members = result.memberInfos;
|
||||
NSMutableArray *arrayM = [NSMutableArray array];
|
||||
for (V2TIMGroupMemberFullInfo *memberInfo in members) {
|
||||
TUISearchGroupMemberMatchResult *memberMatchResult = [[TUISearchGroupMemberMatchResult alloc] init];
|
||||
if (match(keywords, memberInfo.nameCard)) {
|
||||
memberMatchResult.memberMatchField = TUISearchGroupMemberMatchFieldNameCard;
|
||||
memberMatchResult.memberMatchValue = memberInfo.nameCard;
|
||||
[arrayM addObject:memberMatchResult];
|
||||
continue;
|
||||
}
|
||||
|
||||
if (match(keywords, memberInfo.friendRemark)) {
|
||||
memberMatchResult.memberMatchField = TUISearchGroupMemberMatchFieldRemark;
|
||||
memberMatchResult.memberMatchValue = memberInfo.friendRemark;
|
||||
[arrayM addObject:memberMatchResult];
|
||||
continue;
|
||||
}
|
||||
|
||||
if (match(keywords, memberInfo.nickName)) {
|
||||
memberMatchResult.memberMatchField = TUISearchGroupMemberMatchFieldNickName;
|
||||
memberMatchResult.memberMatchValue = memberInfo.nickName;
|
||||
[arrayM addObject:memberMatchResult];
|
||||
continue;
|
||||
}
|
||||
|
||||
if (match(keywords, memberInfo.userID)) {
|
||||
memberMatchResult.memberMatchField = TUISearchGroupMemberMatchFieldUserID;
|
||||
memberMatchResult.memberMatchValue = memberInfo.userID;
|
||||
[arrayM addObject:memberMatchResult];
|
||||
continue;
|
||||
}
|
||||
}
|
||||
result.matchMembers = arrayM;
|
||||
}
|
||||
dispatch_group_leave(group);
|
||||
}
|
||||
|
||||
dispatch_group_notify(group, dispatch_get_global_queue(0, 0), ^{
|
||||
NSMutableArray *arrayM = [NSMutableArray array];
|
||||
NSArray *validGroupIds = groupInfoMap.allKeys;
|
||||
for (TUISearchGroupResult *result in resultSet) {
|
||||
if ([validGroupIds containsObject:result.groupId]) {
|
||||
result.groupInfo = groupInfoMap[result.groupId];
|
||||
[arrayM addObject:result];
|
||||
}
|
||||
}
|
||||
if (succ) {
|
||||
succ(arrayM);
|
||||
}
|
||||
});
|
||||
}
|
||||
fail:^(int code, NSString *desc) {
|
||||
if (fail) {
|
||||
fail(code, desc);
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
+ (NSArray *)mergeGroupSets:(NSArray *)groupsOne withOthers:(NSArray *)groupsTwo {
|
||||
NSMutableArray *arrayM = [NSMutableArray array];
|
||||
|
||||
NSMutableDictionary *map = [NSMutableDictionary dictionary];
|
||||
for (TUISearchGroupResult *result in groupsOne) {
|
||||
[arrayM addObject:result];
|
||||
map[result.groupId] = @(1);
|
||||
}
|
||||
|
||||
for (TUISearchGroupResult *result in groupsTwo) {
|
||||
if ([map objectForKey:result.groupId]) {
|
||||
continue;
|
||||
}
|
||||
[arrayM addObject:result];
|
||||
}
|
||||
|
||||
[arrayM sortUsingComparator:^NSComparisonResult(TUISearchGroupResult *obj1, TUISearchGroupResult *obj2) {
|
||||
return obj1.groupInfo.lastMessageTime > obj2.groupInfo.lastMessageTime ? NSOrderedDescending : NSOrderedAscending;
|
||||
}];
|
||||
|
||||
return arrayM;
|
||||
}
|
||||
|
||||
@end
|
||||
36
TUIKit/TUISearch/Resources/PrivacyInfo.xcprivacy
Normal file
@@ -0,0 +1,36 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>NSPrivacyTracking</key>
|
||||
<false/>
|
||||
<key>NSPrivacyTrackingDomains</key>
|
||||
<array/>
|
||||
<key>NSPrivacyCollectedDataTypes</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>NSPrivacyCollectedDataType</key>
|
||||
<string>NSPrivacyCollectedDataTypeUserID</string>
|
||||
<key>NSPrivacyCollectedDataTypeLinked</key>
|
||||
<false/>
|
||||
<key>NSPrivacyCollectedDataTypeTracking</key>
|
||||
<false/>
|
||||
<key>NSPrivacyCollectedDataTypePurposes</key>
|
||||
<array>
|
||||
<string>NSPrivacyCollectedDataTypePurposeProductPersonalization</string>
|
||||
</array>
|
||||
</dict>
|
||||
</array>
|
||||
<key>NSPrivacyAccessedAPITypes</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>NSPrivacyAccessedAPIType</key>
|
||||
<string>NSPrivacyAccessedAPICategoryUserDefaults</string>
|
||||
<key>NSPrivacyAccessedAPITypeReasons</key>
|
||||
<array>
|
||||
<string>CA92.1</string>
|
||||
</array>
|
||||
</dict>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
BIN
TUIKit/TUISearch/Resources/TUISearch.bundle/right@2x.png
Normal file
|
After Width: | Height: | Size: 1.2 KiB |
BIN
TUIKit/TUISearch/Resources/TUISearch.bundle/search.png
Normal file
|
After Width: | Height: | Size: 833 B |
|
After Width: | Height: | Size: 1.1 KiB |
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>id</key>
|
||||
<string>dark</string>
|
||||
<key>name</key>
|
||||
<string>黑夜</string>
|
||||
<key>name_en</key>
|
||||
<string>Dark</string>
|
||||
</dict>
|
||||
</plist>
|
||||
|
After Width: | Height: | Size: 19 KiB |
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>id</key>
|
||||
<string>light</string>
|
||||
<key>name</key>
|
||||
<string>轻量</string>
|
||||
<key>name_en</key>
|
||||
<string>Light</string>
|
||||
</dict>
|
||||
</plist>
|
||||
|
After Width: | Height: | Size: 11 KiB |
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>id</key>
|
||||
<string>lively</string>
|
||||
<key>name</key>
|
||||
<string>活泼</string>
|
||||
<key>name_en</key>
|
||||
<string>Lively</string>
|
||||
</dict>
|
||||
</plist>
|
||||
|
After Width: | Height: | Size: 9.7 KiB |
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>id</key>
|
||||
<string>serious</string>
|
||||
<key>name</key>
|
||||
<string>严肃</string>
|
||||
<key>name_en</key>
|
||||
<string>Business</string>
|
||||
</dict>
|
||||
</plist>
|
||||
|
After Width: | Height: | Size: 9.3 KiB |
29
TUIKit/TUISearch/TUISearch.podspec
Normal file
@@ -0,0 +1,29 @@
|
||||
Pod::Spec.new do |spec|
|
||||
spec.name = 'TUISearch'
|
||||
spec.version = '8.5.6864'
|
||||
spec.platform = :ios
|
||||
spec.ios.deployment_target = '9.0'
|
||||
spec.license = { :type => 'Proprietary',
|
||||
:text => <<-LICENSE
|
||||
copyright 2017 tencent Ltd. All rights reserved.
|
||||
LICENSE
|
||||
}
|
||||
spec.homepage = 'https://cloud.tencent.com/document/product/269/3794'
|
||||
spec.documentation_url = 'https://cloud.tencent.com/document/product/269/9147'
|
||||
spec.authors = 'tencent video cloud'
|
||||
spec.summary = 'TUISearch'
|
||||
spec.dependency 'TUICore'
|
||||
spec.dependency 'TIMCommon'
|
||||
spec.dependency 'ReactiveObjC'
|
||||
|
||||
spec.requires_arc = true
|
||||
|
||||
spec.source = { :path => './' }
|
||||
spec.source_files = '**/*.{h,m,mm,c}'
|
||||
|
||||
spec.resource = ['Resources/*.bundle']
|
||||
|
||||
spec.resource_bundle = {
|
||||
"#{spec.module_name}_Privacy" => 'Resources/PrivacyInfo.xcprivacy'
|
||||
}
|
||||
end
|
||||
16
TUIKit/TUISearch/UI_Classic/Header/TUISearch.h
Normal file
@@ -0,0 +1,16 @@
|
||||
//
|
||||
// TUISearch.h
|
||||
// Pods
|
||||
//
|
||||
// Created by harvy on 2022/6/9.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef TUISearch_h
|
||||
#define TUISearch_h
|
||||
|
||||
#import "TUISearchBar.h"
|
||||
#import "TUISearchResultListController.h"
|
||||
#import "TUISearchViewController.h"
|
||||
|
||||
#endif /* TUISearch_h */
|
||||
@@ -0,0 +1,17 @@
|
||||
//
|
||||
// TUISearchExtensionObserver.h
|
||||
// TUISearch
|
||||
//
|
||||
// Created by harvy on 2023/4/3.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface TUISearchExtensionObserver : NSObject
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,61 @@
|
||||
//
|
||||
// TUISearchExtensionObserver.m
|
||||
// TUISearch
|
||||
//
|
||||
// Created by harvy on 2023/4/3.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
//
|
||||
|
||||
#import "TUISearchExtensionObserver.h"
|
||||
|
||||
#import <TUICore/TUICore.h>
|
||||
#import <TUICore/TUIDefine.h>
|
||||
|
||||
#import "TUISearchBar.h"
|
||||
|
||||
@interface TUISearchExtensionObserver () <TUIExtensionProtocol>
|
||||
|
||||
@end
|
||||
|
||||
@implementation TUISearchExtensionObserver
|
||||
|
||||
+ (void)load {
|
||||
[TUICore registerExtension:TUICore_TUIConversationExtension_ConversationListBanner_ClassicExtensionID object:TUISearchExtensionObserver.shareInstance];
|
||||
}
|
||||
|
||||
static id gShareInstance = nil;
|
||||
+ (instancetype)shareInstance {
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
gShareInstance = [[self alloc] init];
|
||||
});
|
||||
return gShareInstance;
|
||||
}
|
||||
|
||||
#pragma mark - TUIExtensionProtocol
|
||||
- (BOOL)onRaiseExtension:(NSString *)extensionID parentView:(UIView *)parentView param:(nullable NSDictionary *)param {
|
||||
if (![extensionID isKindOfClass:NSString.class]) {
|
||||
return NO;
|
||||
}
|
||||
|
||||
if ([extensionID isEqualToString:TUICore_TUIConversationExtension_ConversationListBanner_ClassicExtensionID]) {
|
||||
if (![param isKindOfClass:NSDictionary.class] || parentView == nil || ![parentView isKindOfClass:UIView.class]) {
|
||||
return NO;
|
||||
}
|
||||
UIViewController *modalVC = [param tui_objectForKey:TUICore_TUIConversationExtension_ConversationListBanner_ModalVC asClass:UIViewController.class];
|
||||
NSString *sizeStr = [param tui_objectForKey:TUICore_TUIConversationExtension_ConversationListBanner_BannerSize asClass:NSString.class];
|
||||
CGSize size = CGSizeFromString(sizeStr);
|
||||
|
||||
TUISearchBar *searchBar = [[TUISearchBar alloc] init];
|
||||
searchBar.frame = CGRectMake(0, 0, size.width, size.height);
|
||||
[searchBar setParentVC:modalVC];
|
||||
[searchBar setEntrance:YES];
|
||||
[parentView addSubview:searchBar];
|
||||
return YES;
|
||||
} else {
|
||||
// do nothing
|
||||
return NO;
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
13
TUIKit/TUISearch/UI_Classic/Service/TUISearchService.h
Normal file
@@ -0,0 +1,13 @@
|
||||
|
||||
// Created by Tencent on 2023/06/09.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface TUISearchService : NSObject
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
27
TUIKit/TUISearch/UI_Classic/Service/TUISearchService.m
Normal file
@@ -0,0 +1,27 @@
|
||||
|
||||
// Created by Tencent on 2023/06/09.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
#import "TUISearchService.h"
|
||||
|
||||
#import <TIMCommon/TIMDefine.h>
|
||||
#import <TUICore/TUICore.h>
|
||||
#import <TUICore/TUIThemeManager.h>
|
||||
|
||||
#import "TUISearchBar.h"
|
||||
|
||||
@implementation TUISearchService
|
||||
|
||||
+ (void)load {
|
||||
TUIRegisterThemeResourcePath(TUISearchThemePath, TUIThemeModuleSearch);
|
||||
}
|
||||
|
||||
+ (TUISearchService *)shareInstance {
|
||||
static dispatch_once_t onceToken;
|
||||
static TUISearchService *g_sharedInstance = nil;
|
||||
dispatch_once(&onceToken, ^{
|
||||
g_sharedInstance = [[TUISearchService alloc] init];
|
||||
});
|
||||
return g_sharedInstance;
|
||||
}
|
||||
|
||||
@end
|
||||
34
TUIKit/TUISearch/UI_Classic/UI/TUISearchBar.h
Normal file
@@ -0,0 +1,34 @@
|
||||
//
|
||||
// TUISearchBar.h
|
||||
// Pods
|
||||
//
|
||||
// Created by harvy on 2020/12/23.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@class TUISearchBar;
|
||||
@protocol TUISearchBarDelegate <NSObject>
|
||||
|
||||
@optional
|
||||
- (void)searchBarDidEnterSearch:(TUISearchBar *)searchBar;
|
||||
- (void)searchBarDidCancelClicked:(TUISearchBar *)searchBar;
|
||||
- (void)searchBar:(TUISearchBar *)searchBar searchText:(NSString *)key;
|
||||
@end
|
||||
|
||||
@interface TUISearchBar : UIView
|
||||
|
||||
@property(nonatomic, strong, readonly) UISearchBar *searchBar;
|
||||
|
||||
@property(nonatomic, weak) id<TUISearchBarDelegate> delegate;
|
||||
// use weak, prevent circular references
|
||||
@property(nonatomic, weak) UIViewController *parentVC;
|
||||
|
||||
- (void)setEntrance:(BOOL)isEntrance;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
135
TUIKit/TUISearch/UI_Classic/UI/TUISearchBar.m
Normal file
@@ -0,0 +1,135 @@
|
||||
//
|
||||
// TUISearchBar.m
|
||||
// Pods
|
||||
//
|
||||
// Created by harvy on 2020/12/23.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
//
|
||||
|
||||
#import "TUISearchBar.h"
|
||||
#import <TIMCommon/TIMDefine.h>
|
||||
#import <TUICore/TUICore.h>
|
||||
#import <TUICore/TUIDarkModel.h>
|
||||
#import <TUICore/TUIGlobalization.h>
|
||||
#import <TUICore/TUIThemeManager.h>
|
||||
#import <TUICore/UIView+TUILayout.h>
|
||||
#import "TUISearchViewController.h"
|
||||
|
||||
@interface TUISearchBar () <UISearchBarDelegate>
|
||||
@property(nonatomic, strong) UISearchBar *searchBar;
|
||||
@property(nonatomic, assign) BOOL isEntrance;
|
||||
@end
|
||||
|
||||
@implementation TUISearchBar
|
||||
@synthesize delegate;
|
||||
|
||||
- (void)setEntrance:(BOOL)isEntrance {
|
||||
self.isEntrance = isEntrance;
|
||||
[self setupViews];
|
||||
}
|
||||
|
||||
- (UIColor *)bgColorOfSearchBar {
|
||||
return TIMCommonDynamicColor(@"head_bg_gradient_start_color", @"#EBF0F6");
|
||||
}
|
||||
|
||||
- (void)setupViews {
|
||||
self.backgroundColor = self.bgColorOfSearchBar;
|
||||
_searchBar = [[UISearchBar alloc] init];
|
||||
_searchBar.placeholder = TIMCommonLocalizableString(Search);
|
||||
_searchBar.backgroundImage = [UIImage new];
|
||||
_searchBar.barTintColor = UIColor.redColor;
|
||||
_searchBar.showsCancelButton = NO;
|
||||
_searchBar.delegate = self;
|
||||
_searchBar.showsCancelButton = !self.isEntrance;
|
||||
_searchBar.searchTextField.textAlignment = isRTL()?NSTextAlignmentRight:NSTextAlignmentLeft;
|
||||
if (@available(iOS 13.0, *)) {
|
||||
_searchBar.searchTextField.backgroundColor = TIMCommonDynamicColor(@"search_textfield_bg_color", @"#FEFEFE");
|
||||
}
|
||||
[self addSubview:_searchBar];
|
||||
[self enableCancelButton];
|
||||
}
|
||||
|
||||
- (void)layoutSubviews {
|
||||
[super layoutSubviews];
|
||||
self.searchBar.frame = CGRectMake(10, 5, self.mm_w - 10 - 10, self.mm_h - 5 - 5);
|
||||
|
||||
[self updateSearchIcon];
|
||||
}
|
||||
|
||||
- (void)updateSearchIcon {
|
||||
if ([self.searchBar isFirstResponder] || self.searchBar.text.length || !self.isEntrance) {
|
||||
[self.searchBar setPositionAdjustment:UIOffsetZero forSearchBarIcon:UISearchBarIconSearch];
|
||||
self.backgroundColor = self.superview.backgroundColor;
|
||||
} else {
|
||||
[self.searchBar setPositionAdjustment:UIOffsetMake(0.5 * (self.mm_w - 10 - 10) - 40, 0) forSearchBarIcon:UISearchBarIconSearch];
|
||||
self.backgroundColor = self.bgColorOfSearchBar;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)showSearchVC {
|
||||
TUISearchViewController *vc = [[TUISearchViewController alloc] init];
|
||||
TUINavigationController *nav = [[TUINavigationController alloc] initWithRootViewController:(UIViewController *)vc];
|
||||
nav.modalPresentationStyle = UIModalPresentationFullScreen;
|
||||
|
||||
UIViewController *parentVC = self.parentVC;
|
||||
if (parentVC) {
|
||||
[parentVC presentViewController:nav animated:NO completion:nil];
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - UISearchBarDelegate
|
||||
- (BOOL)searchBarShouldBeginEditing:(UISearchBar *)searchBar {
|
||||
[self showSearchVC];
|
||||
|
||||
if (self.isEntrance && [self.delegate respondsToSelector:@selector(searchBarDidEnterSearch:)]) {
|
||||
[self.delegate searchBarDidEnterSearch:self];
|
||||
}
|
||||
|
||||
__weak typeof(self) weakSelf = self;
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[weakSelf updateSearchIcon];
|
||||
});
|
||||
return !self.isEntrance;
|
||||
}
|
||||
|
||||
- (void)searchBarCancelButtonClicked:(UISearchBar *)searchBar {
|
||||
if ([self.delegate respondsToSelector:@selector(searchBarDidCancelClicked:)]) {
|
||||
[self.delegate searchBarDidCancelClicked:self];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)searchBarSearchButtonClicked:(UISearchBar *)searchBar {
|
||||
if ([self.delegate respondsToSelector:@selector(searchBar:searchText:)]) {
|
||||
[self.delegate searchBar:self searchText:searchBar.text];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText {
|
||||
if ([self.delegate respondsToSelector:@selector(searchBar:searchText:)]) {
|
||||
[self.delegate searchBar:self searchText:searchBar.text];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)searchBarTextDidEndEditing:(UISearchBar *)searchBar {
|
||||
[self enableCancelButton];
|
||||
}
|
||||
|
||||
- (void)enableCancelButton {
|
||||
__weak typeof(self) weakSelf = self;
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
UIButton *cancelBtn = [weakSelf.searchBar valueForKeyPath:@"cancelButton"];
|
||||
for (UIButton *view in cancelBtn.subviews) {
|
||||
if ([view isKindOfClass:UIButton.class]) {
|
||||
view.userInteractionEnabled = YES;
|
||||
view.enabled = YES;
|
||||
}
|
||||
}
|
||||
cancelBtn.enabled = YES;
|
||||
cancelBtn.userInteractionEnabled = YES;
|
||||
[cancelBtn setTitleColor:[UIColor systemBlueColor] forState:UIControlStateNormal];
|
||||
[UIBarButtonItem appearanceWhenContainedInInstancesOfClasses:@[ [UISearchBar class] ]].title = TIMCommonLocalizableString(TUIKitSearchItemCancel);
|
||||
;
|
||||
});
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,21 @@
|
||||
//
|
||||
// TUISearchResultHeaderFooterView.h
|
||||
// Pods
|
||||
//
|
||||
// Created by harvy on 2020/12/24.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface TUISearchResultHeaderFooterView : UITableViewHeaderFooterView
|
||||
|
||||
@property(nonatomic, assign) BOOL isFooter;
|
||||
@property(nonatomic, copy) NSString* __nullable title;
|
||||
@property(nonatomic, copy) dispatch_block_t __nullable onTap;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
171
TUIKit/TUISearch/UI_Classic/UI/TUISearchResultHeaderFooterView.m
Normal file
@@ -0,0 +1,171 @@
|
||||
//
|
||||
// TUISearchResultHeaderFooterView.m
|
||||
// Pods
|
||||
//
|
||||
// Created by harvy on 2020/12/24.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
//
|
||||
|
||||
#import "TUISearchResultHeaderFooterView.h"
|
||||
#import <TIMCommon/TIMDefine.h>
|
||||
#import <TUICore/TUIThemeManager.h>
|
||||
|
||||
@interface TUISearchResultHeaderFooterView ()
|
||||
|
||||
@property(nonatomic, strong) UIImageView *iconView;
|
||||
@property(nonatomic, strong) UILabel *titleLabel;
|
||||
@property(nonatomic, strong) UIImageView *accessoryView;
|
||||
@property(nonatomic, strong) UIView *separtorView;
|
||||
|
||||
@end
|
||||
|
||||
@implementation TUISearchResultHeaderFooterView
|
||||
|
||||
- (instancetype)initWithReuseIdentifier:(NSString *)reuseIdentifier {
|
||||
if (self = [super initWithReuseIdentifier:reuseIdentifier]) {
|
||||
[self setupViews];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)setupViews {
|
||||
[self.contentView addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tap:)]];
|
||||
self.contentView.backgroundColor = TIMCommonDynamicColor(@"form_bg_color", @"#FFFFFF");
|
||||
_iconView = [[UIImageView alloc] init];
|
||||
_iconView.image = [UIImage imageNamed:TUISearchImagePath(@"search")];
|
||||
[self.contentView addSubview:_iconView];
|
||||
|
||||
_titleLabel = [[UILabel alloc] init];
|
||||
_titleLabel.text = @"";
|
||||
_titleLabel.font = [UIFont systemFontOfSize:12.0];
|
||||
_titleLabel.rtlAlignment = TUITextRTLAlignmentLeading;
|
||||
[self.contentView addSubview:_titleLabel];
|
||||
|
||||
_accessoryView = [[UIImageView alloc] init];
|
||||
_accessoryView.image = [[UIImage imageNamed:TUISearchImagePath(@"right")] rtl_imageFlippedForRightToLeftLayoutDirection];
|
||||
[self.contentView addSubview:_accessoryView];
|
||||
|
||||
_separtorView = [[UIView alloc] init];
|
||||
_separtorView.backgroundColor = TIMCommonDynamicColor(@"separator_color", @"#DBDBDB");
|
||||
[self.contentView addSubview:_separtorView];
|
||||
}
|
||||
|
||||
- (void)tap:(UITapGestureRecognizer *)tap {
|
||||
if (self.onTap) {
|
||||
self.onTap();
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setIsFooter:(BOOL)isFooter {
|
||||
_isFooter = isFooter;
|
||||
|
||||
self.iconView.hidden = !self.isFooter;
|
||||
self.iconView.hidden = !self.isFooter;
|
||||
self.accessoryView.hidden = !self.isFooter;
|
||||
UIColor *footerColor = TIMCommonDynamicColor(@"primary_theme_color", @"#147AFF");
|
||||
self.titleLabel.textColor = self.isFooter ? footerColor : [UIColor darkGrayColor];
|
||||
|
||||
// tell constraints they need updating
|
||||
[self setNeedsUpdateConstraints];
|
||||
|
||||
// update constraints now so we can animate the change
|
||||
[self updateConstraintsIfNeeded];
|
||||
|
||||
[self layoutIfNeeded];
|
||||
|
||||
}
|
||||
|
||||
- (void)setTitle:(NSString *)title {
|
||||
_title = title;
|
||||
self.titleLabel.text = 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];
|
||||
if (self.isFooter) {
|
||||
[self.iconView mas_remakeConstraints:^(MASConstraintMaker *make) {
|
||||
make.centerY.mas_equalTo(self.contentView);
|
||||
make.height.width.mas_equalTo(20);
|
||||
if(isRTL()){
|
||||
make.right.mas_equalTo(self.contentView.mas_right).mas_offset(-10);
|
||||
}
|
||||
else {
|
||||
make.left.mas_equalTo(self.contentView.mas_left).mas_offset(10);
|
||||
}
|
||||
}];
|
||||
[self.titleLabel sizeToFit];
|
||||
[self.titleLabel mas_remakeConstraints:^(MASConstraintMaker *make) {
|
||||
if(isRTL()) {
|
||||
make.right.mas_equalTo(self.iconView.mas_left).mas_offset(-10);
|
||||
}
|
||||
else{
|
||||
make.left.mas_equalTo(self.iconView.mas_right).mas_offset(10);
|
||||
}
|
||||
make.leading.mas_equalTo(self.iconView.mas_trailing).mas_offset(10);
|
||||
make.centerY.mas_equalTo(self.contentView);
|
||||
make.width.mas_equalTo(self.titleLabel.frame.size.width);
|
||||
make.height.mas_equalTo(self.titleLabel.font.lineHeight);
|
||||
}];
|
||||
|
||||
[self.accessoryView mas_remakeConstraints:^(MASConstraintMaker *make) {
|
||||
make.height.width.mas_equalTo(10);
|
||||
make.centerY.mas_equalTo(self.contentView);
|
||||
if(isRTL()) {
|
||||
make.left.mas_equalTo(self.contentView.mas_left).mas_offset(10);
|
||||
}else {
|
||||
make.right.mas_equalTo(self.contentView.mas_right).mas_offset(-10);
|
||||
}
|
||||
}];
|
||||
[self.separtorView mas_remakeConstraints:^(MASConstraintMaker *make) {
|
||||
make.leading.mas_equalTo(10);
|
||||
make.bottom.mas_equalTo(self.contentView);
|
||||
make.width.mas_equalTo(self.contentView);
|
||||
make.height.mas_equalTo(1);
|
||||
}];
|
||||
|
||||
MASAttachKeys(self.iconView,self.titleLabel,self.accessoryView,self.separtorView);
|
||||
} else {
|
||||
[self.titleLabel mas_remakeConstraints:^(MASConstraintMaker *make) {
|
||||
make.leading.mas_equalTo(self.contentView.mas_leading).mas_offset(10);
|
||||
make.centerY.mas_equalTo(self.contentView);
|
||||
make.trailing.mas_equalTo(self.contentView.mas_trailing).mas_offset(-10);
|
||||
make.height.mas_equalTo(self.titleLabel.font.lineHeight);
|
||||
}];
|
||||
[self.separtorView mas_remakeConstraints:^(MASConstraintMaker *make) {
|
||||
make.leading.mas_equalTo(10);
|
||||
make.bottom.mas_equalTo(self.contentView).mas_offset(-1);
|
||||
make.width.mas_equalTo(self.contentView);
|
||||
make.height.mas_equalTo(1);
|
||||
}];
|
||||
MASAttachKeys(self.titleLabel,self.separtorView);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)layoutSubviews {
|
||||
[super layoutSubviews];
|
||||
}
|
||||
|
||||
- (void)setFrame:(CGRect)frame {
|
||||
if (self.isFooter) {
|
||||
CGSize size = frame.size;
|
||||
size.height -= 10;
|
||||
frame.size = size;
|
||||
}
|
||||
[super setFrame:frame];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,24 @@
|
||||
//
|
||||
// TUISearchResultListController.h
|
||||
// Pods
|
||||
//
|
||||
// Created by harvy on 2020/12/25.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
#import "TUISearchDataProvider.h"
|
||||
@class TUISearchResultCellModel;
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface TUISearchResultListController : UIViewController
|
||||
|
||||
- (instancetype)initWithResults:(NSArray<TUISearchResultCellModel *> *__nullable)results
|
||||
keyword:(NSString *__nullable)keyword
|
||||
module:(TUISearchResultModule)module
|
||||
param:(NSDictionary<TUISearchParamKey, id> *__nullable)param;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
310
TUIKit/TUISearch/UI_Classic/UI/TUISearchResultListController.m
Normal file
@@ -0,0 +1,310 @@
|
||||
//
|
||||
// TUISearchResultListController.m
|
||||
// Pods
|
||||
//
|
||||
// Created by harvy on 2020/12/25.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
//
|
||||
|
||||
#import "TUISearchResultListController.h"
|
||||
#import <TIMCommon/TIMDefine.h>
|
||||
#import <TUICore/TUICore.h>
|
||||
#import "TUISearchBar.h"
|
||||
#import "TUISearchResultCell.h"
|
||||
#import "TUISearchResultCellModel.h"
|
||||
#import "TUISearchResultHeaderFooterView.h"
|
||||
|
||||
@interface TUISearchResultListController () <UITableViewDelegate, UITableViewDataSource, TUISearchBarDelegate, TUISearchResultDelegate>
|
||||
|
||||
@property(nonatomic, strong) NSMutableArray<TUISearchResultCellModel *> *results;
|
||||
@property(nonatomic, copy) NSString *keyword;
|
||||
@property(nonatomic, assign) TUISearchResultModule module;
|
||||
@property(nonatomic, strong) NSDictionary<TUISearchParamKey, id> *param;
|
||||
@property(nonatomic, strong) TUISearchDataProvider *dataProvider;
|
||||
|
||||
@property(nonatomic, strong) TUISearchBar *searchBar;
|
||||
@property(nonatomic, strong) UITableView *tableView;
|
||||
|
||||
@property(nonatomic, assign) BOOL allowPageRequest;
|
||||
@property(nonatomic, assign) NSUInteger pageIndex;
|
||||
|
||||
@end
|
||||
|
||||
@implementation TUISearchResultListController
|
||||
static NSString *const Id = @"cell";
|
||||
static NSString *const HFId = @"HFId";
|
||||
|
||||
- (instancetype)initWithResults:(NSArray<TUISearchResultCellModel *> *__nullable)results
|
||||
keyword:(NSString *__nullable)keyword
|
||||
module:(TUISearchResultModule)module
|
||||
param:(NSDictionary<TUISearchParamKey, id> *__nullable)param;
|
||||
{
|
||||
if (self = [super init]) {
|
||||
_results = (module == TUISearchResultModuleChatHistory) ? [NSMutableArray arrayWithArray:@[]] : [NSMutableArray arrayWithArray:results];
|
||||
_keyword = keyword;
|
||||
_module = module;
|
||||
_dataProvider = [[TUISearchDataProvider alloc] init];
|
||||
_dataProvider.delegate = self;
|
||||
_param = param;
|
||||
_allowPageRequest = (module == TUISearchResultModuleChatHistory);
|
||||
_pageIndex = 0;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)viewDidLoad {
|
||||
[super viewDidLoad];
|
||||
|
||||
self.navigationController.navigationBar.backgroundColor = [UIColor groupTableViewBackgroundColor];
|
||||
[self.navigationController.navigationBar setBackgroundImage:[self imageWithColor:[UIColor groupTableViewBackgroundColor]]
|
||||
forBarMetrics:UIBarMetricsDefault];
|
||||
self.navigationController.navigationBar.shadowImage = [UIImage new];
|
||||
self.view.backgroundColor = [UIColor groupTableViewBackgroundColor];
|
||||
|
||||
UIImage *image = [UIImage imageNamed:TIMCommonImagePath(@"nav_back")];
|
||||
image = [image imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal];
|
||||
image = [image rtl_imageFlippedForRightToLeftLayoutDirection];
|
||||
UIBarButtonItem *back = [[UIBarButtonItem alloc] initWithImage:image style:UIBarButtonItemStylePlain target:self action:@selector(back)];
|
||||
self.navigationItem.leftBarButtonItems = @[ back ];
|
||||
self.navigationItem.leftItemsSupplementBackButton = NO;
|
||||
|
||||
_searchBar = [[TUISearchBar alloc] init];
|
||||
[_searchBar setEntrance:NO];
|
||||
_searchBar.delegate = self;
|
||||
_searchBar.searchBar.text = self.keyword;
|
||||
self.navigationItem.titleView = _searchBar;
|
||||
|
||||
_tableView = [[UITableView alloc] initWithFrame:self.view.bounds style:UITableViewStyleGrouped];
|
||||
_tableView.delegate = self;
|
||||
_tableView.dataSource = self;
|
||||
_tableView.backgroundColor = [UIColor groupTableViewBackgroundColor];
|
||||
_tableView.separatorStyle = UITableViewCellSeparatorStyleNone;
|
||||
_tableView.rowHeight = 60.f;
|
||||
[_tableView registerClass:TUISearchResultCell.class forCellReuseIdentifier:Id];
|
||||
[_tableView registerClass:TUISearchResultHeaderFooterView.class forHeaderFooterViewReuseIdentifier:HFId];
|
||||
[self.view addSubview:_tableView];
|
||||
|
||||
if (self.module == TUISearchResultModuleChatHistory) {
|
||||
[self.dataProvider searchForKeyword:self.searchBar.searchBar.text forModules:self.module param:self.param];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)back {
|
||||
[self.navigationController popViewControllerAnimated:YES];
|
||||
}
|
||||
|
||||
- (void)viewWillLayoutSubviews {
|
||||
[super viewWillLayoutSubviews];
|
||||
self.tableView.frame = self.view.bounds;
|
||||
self.searchBar.frame = CGRectMake(0, 0, self.view.mm_w, 44);
|
||||
}
|
||||
|
||||
- (void)viewWillAppear:(BOOL)animated {
|
||||
[super viewWillAppear:animated];
|
||||
self.navigationController.navigationBar.backgroundColor = [UIColor groupTableViewBackgroundColor];
|
||||
[self.navigationController.navigationBar setBackgroundImage:[self imageWithColor:[UIColor groupTableViewBackgroundColor]]
|
||||
forBarMetrics:UIBarMetricsDefault];
|
||||
self.navigationController.navigationBar.shadowImage = [UIImage new];
|
||||
}
|
||||
|
||||
- (void)viewWillDisappear:(BOOL)animated {
|
||||
[super viewWillDisappear:animated];
|
||||
self.navigationController.navigationBar.backgroundColor = nil;
|
||||
[self.navigationController.navigationBar setBackgroundImage:nil forBarMetrics:UIBarMetricsDefault];
|
||||
self.navigationController.navigationBar.shadowImage = nil;
|
||||
}
|
||||
|
||||
#pragma mark - Table view data source
|
||||
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
|
||||
return self.results.count;
|
||||
}
|
||||
|
||||
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
|
||||
TUISearchResultCell *cell = [tableView dequeueReusableCellWithIdentifier:Id forIndexPath:indexPath];
|
||||
if (indexPath.row >= self.results.count) {
|
||||
return cell;
|
||||
}
|
||||
[cell fillWithData:self.results[indexPath.row]];
|
||||
return cell;
|
||||
}
|
||||
|
||||
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
|
||||
[tableView deselectRowAtIndexPath:indexPath animated:NO];
|
||||
if (indexPath.row >= self.results.count) {
|
||||
return;
|
||||
}
|
||||
TUISearchResultCellModel *cellModel = self.results[indexPath.row];
|
||||
[self onSelectModel:cellModel module:self.module];
|
||||
}
|
||||
|
||||
- (CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section {
|
||||
return 0;
|
||||
}
|
||||
|
||||
- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section {
|
||||
return self.results.count == 0 ? 0 : 30;
|
||||
}
|
||||
|
||||
- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section {
|
||||
TUISearchResultHeaderFooterView *headerView = [tableView dequeueReusableHeaderFooterViewWithIdentifier:HFId];
|
||||
headerView.isFooter = NO;
|
||||
headerView.title = titleForModule(self.module, YES);
|
||||
return headerView;
|
||||
}
|
||||
|
||||
- (UIView *)tableView:(UITableView *)tableView viewForFooterInSection:(NSInteger)section {
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
|
||||
[self.view endEditing:YES];
|
||||
[self.searchBar endEditing:YES];
|
||||
|
||||
if (self.allowPageRequest && scrollView.contentOffset.y + scrollView.bounds.size.height > scrollView.contentSize.height) {
|
||||
self.allowPageRequest = NO;
|
||||
NSMutableDictionary *param = [NSMutableDictionary dictionaryWithDictionary:self.param];
|
||||
param[TUISearchChatHistoryParamKeyPage] = @(self.pageIndex);
|
||||
param[TUISearchChatHistoryParamKeyCount] = @(TUISearchDefaultPageSize);
|
||||
self.param = [NSDictionary dictionaryWithDictionary:param];
|
||||
[self.dataProvider searchForKeyword:self.searchBar.searchBar.text forModules:self.module param:self.param];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)onBack {
|
||||
[self.navigationController popViewControllerAnimated:YES];
|
||||
}
|
||||
|
||||
- (void)onSelectModel:(TUISearchResultCellModel *)cellModel module:(TUISearchResultModule)module {
|
||||
[self.searchBar endEditing:YES];
|
||||
if (module == TUISearchResultModuleChatHistory) {
|
||||
if (![cellModel.context isKindOfClass:NSDictionary.class]) {
|
||||
if ([cellModel.context isKindOfClass:V2TIMMessage.class]) {
|
||||
V2TIMMessage *message = cellModel.context;
|
||||
NSString *title = message.userID;
|
||||
if (cellModel.title.length > 0) {
|
||||
title = cellModel.title;
|
||||
}
|
||||
NSDictionary *param = @{
|
||||
TUICore_TUIChatObjectFactory_ChatViewController_Title : title ?: @"",
|
||||
TUICore_TUIChatObjectFactory_ChatViewController_UserID : message.userID ?: @"",
|
||||
TUICore_TUIChatObjectFactory_ChatViewController_GroupID : message.groupID ?: @"",
|
||||
TUICore_TUIChatObjectFactory_ChatViewController_HighlightKeyword : self.searchBar.searchBar.text ?: @"",
|
||||
TUICore_TUIChatObjectFactory_ChatViewController_LocateMessage : message,
|
||||
};
|
||||
[self.navigationController pushViewController:TUICore_TUIChatObjectFactory_ChatViewController_Classic param:param forResult:nil];
|
||||
return;
|
||||
}
|
||||
return;
|
||||
}
|
||||
NSDictionary *convInfo = cellModel.context;
|
||||
NSString *conversationId = convInfo[kSearchChatHistoryConversationId];
|
||||
V2TIMConversation *conversation = convInfo[kSearchChatHistoryConverationInfo];
|
||||
NSArray *msgs = convInfo[kSearchChatHistoryConversationMsgs];
|
||||
if (msgs.count == 1) {
|
||||
NSString *title = cellModel.title ?: cellModel.titleAttributeString.string;
|
||||
NSDictionary *param = @{
|
||||
TUICore_TUIChatObjectFactory_ChatViewController_Title : title ?: @"",
|
||||
TUICore_TUIChatObjectFactory_ChatViewController_UserID : conversation.userID ?: @"",
|
||||
TUICore_TUIChatObjectFactory_ChatViewController_GroupID : conversation.groupID ?: @"",
|
||||
TUICore_TUIChatObjectFactory_ChatViewController_HighlightKeyword : self.searchBar.searchBar.text ?: @"",
|
||||
TUICore_TUIChatObjectFactory_ChatViewController_LocateMessage : msgs.firstObject,
|
||||
};
|
||||
[self.navigationController pushViewController:TUICore_TUIChatObjectFactory_ChatViewController_Classic param:param forResult:nil];
|
||||
return;
|
||||
}
|
||||
|
||||
NSMutableArray *results = [NSMutableArray array];
|
||||
for (V2TIMMessage *message in msgs) {
|
||||
TUISearchResultCellModel *model = [[TUISearchResultCellModel alloc] init];
|
||||
model.title = message.nickName ?: message.sender;
|
||||
NSString *desc = [TUISearchDataProvider matchedTextForMessage:message withKey:self.searchBar.searchBar.text];
|
||||
model.detailsAttributeString = [TUISearchDataProvider attributeStringWithText:desc key:self.searchBar.searchBar.text];
|
||||
model.avatarUrl = message.faceURL;
|
||||
model.groupType = conversation.groupID;
|
||||
model.avatarImage = conversation.type == V2TIM_C2C ? DefaultAvatarImage : DefaultGroupAvatarImageByGroupType(conversation.groupType);
|
||||
model.context = message;
|
||||
[results addObject:model];
|
||||
}
|
||||
TUISearchResultListController *vc =
|
||||
[[TUISearchResultListController alloc] initWithResults:results
|
||||
keyword:self.searchBar.searchBar.text
|
||||
module:module
|
||||
param:@{TUISearchChatHistoryParamKeyConversationId : conversationId}];
|
||||
[self.navigationController pushViewController:vc animated:YES];
|
||||
return;
|
||||
}
|
||||
|
||||
NSDictionary *param = nil;
|
||||
NSString *title = cellModel.title ?: cellModel.titleAttributeString.string;
|
||||
if (module == TUISearchResultModuleContact && [cellModel.context isKindOfClass:V2TIMFriendInfo.class]) {
|
||||
V2TIMFriendInfo *friend = cellModel.context;
|
||||
param = @{
|
||||
TUICore_TUIChatObjectFactory_ChatViewController_Title : title ?: @"",
|
||||
TUICore_TUIChatObjectFactory_ChatViewController_UserID : friend.userID ?: @"",
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
if (module == TUISearchResultModuleGroup && [cellModel.context isKindOfClass:V2TIMGroupInfo.class]) {
|
||||
V2TIMGroupInfo *group = cellModel.context;
|
||||
param = @{
|
||||
TUICore_TUIChatObjectFactory_ChatViewController_Title : title ?: @"",
|
||||
TUICore_TUIChatObjectFactory_ChatViewController_GroupID : group.groupID ?: @"",
|
||||
|
||||
};
|
||||
}
|
||||
[self.navigationController pushViewController:TUICore_TUIChatObjectFactory_ChatViewController_Classic param:param forResult:nil];
|
||||
}
|
||||
|
||||
#pragma mark - TUISearchBarDelegate
|
||||
- (void)searchBarDidCancelClicked:(TUISearchBar *)searchBar {
|
||||
[self dismissViewControllerAnimated:NO completion:nil];
|
||||
}
|
||||
|
||||
- (void)searchBar:(TUISearchBar *)searchBar searchText:(NSString *)key {
|
||||
[self.results removeAllObjects];
|
||||
self.allowPageRequest = YES;
|
||||
self.pageIndex = 0;
|
||||
NSMutableDictionary *param = [NSMutableDictionary dictionaryWithDictionary:self.param];
|
||||
param[TUISearchChatHistoryParamKeyPage] = @(self.pageIndex);
|
||||
self.param = [NSDictionary dictionaryWithDictionary:param];
|
||||
|
||||
[self.dataProvider searchForKeyword:key forModules:self.module param:self.param];
|
||||
}
|
||||
|
||||
#pragma mark - TUISearchResultDelegate
|
||||
- (void)onSearchResults:(NSDictionary<NSNumber *, NSArray<TUISearchResultCellModel *> *> *)results forModules:(TUISearchResultModule)modules {
|
||||
NSArray *arrayM = [results objectForKey:@(modules)];
|
||||
if (arrayM == nil) {
|
||||
arrayM = @[];
|
||||
}
|
||||
|
||||
[self.results addObjectsFromArray:arrayM];
|
||||
[self.tableView reloadData];
|
||||
|
||||
self.allowPageRequest = (arrayM.count >= TUISearchDefaultPageSize);
|
||||
self.pageIndex = (arrayM.count < TUISearchDefaultPageSize) ? self.pageIndex : self.pageIndex + 1;
|
||||
|
||||
if (!self.allowPageRequest) {
|
||||
}
|
||||
}
|
||||
|
||||
- (void)onSearchError:(NSString *)errMsg {
|
||||
NSLog(@"search error: %@", errMsg);
|
||||
}
|
||||
|
||||
- (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
|
||||
17
TUIKit/TUISearch/UI_Classic/UI/TUISearchViewController.h
Normal file
@@ -0,0 +1,17 @@
|
||||
//
|
||||
// TUISearchViewController.h
|
||||
// Pods
|
||||
//
|
||||
// Created by harvy on 2020/12/24.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface TUISearchViewController : UIViewController
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
299
TUIKit/TUISearch/UI_Classic/UI/TUISearchViewController.m
Normal file
@@ -0,0 +1,299 @@
|
||||
//
|
||||
// TUISearchViewController.m
|
||||
// Pods
|
||||
//
|
||||
// Created by harvy on 2020/12/24.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
//
|
||||
|
||||
#import "TUISearchViewController.h"
|
||||
#import <TIMCommon/TIMDefine.h>
|
||||
#import <TUICore/TUICore.h>
|
||||
#import "TUISearchBar.h"
|
||||
#import "TUISearchDataProvider.h"
|
||||
#import "TUISearchResultCell.h"
|
||||
#import "TUISearchResultCellModel.h"
|
||||
#import "TUISearchResultHeaderFooterView.h"
|
||||
#import "TUISearchResultListController.h"
|
||||
|
||||
@interface TUISearchViewController () <UITableViewDelegate, UITableViewDataSource, TUISearchBarDelegate, TUISearchResultDelegate>
|
||||
|
||||
@property(nonatomic, strong) TUISearchBar *searchBar;
|
||||
@property(nonatomic, strong) UITableView *tableView;
|
||||
@property(nonatomic, strong) TUISearchDataProvider *dataProvider;
|
||||
|
||||
@end
|
||||
|
||||
@implementation TUISearchViewController
|
||||
|
||||
static NSString *const Id = @"cell";
|
||||
static NSString *const HFId = @"HFId";
|
||||
|
||||
- (void)viewDidLoad {
|
||||
[super viewDidLoad];
|
||||
_dataProvider = [[TUISearchDataProvider alloc] init];
|
||||
_dataProvider.delegate = self;
|
||||
[self setupViews];
|
||||
[TUITool addUnsupportNotificationInVC:self];
|
||||
}
|
||||
|
||||
- (void)dealloc {
|
||||
NSLog(@"%s dealloc", __FUNCTION__);
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:self];
|
||||
}
|
||||
|
||||
- (void)setupViews {
|
||||
self.view.backgroundColor = [UIColor groupTableViewBackgroundColor];
|
||||
_searchBar = [[TUISearchBar alloc] init];
|
||||
[_searchBar setEntrance:NO];
|
||||
_searchBar.delegate = self;
|
||||
self.navigationItem.titleView = _searchBar;
|
||||
|
||||
_tableView = [[UITableView alloc] initWithFrame:self.view.bounds style:UITableViewStyleGrouped];
|
||||
_tableView.delegate = self;
|
||||
_tableView.dataSource = self;
|
||||
_tableView.backgroundColor = [UIColor groupTableViewBackgroundColor];
|
||||
_tableView.separatorStyle = UITableViewCellSeparatorStyleNone;
|
||||
_tableView.rowHeight = 60.f;
|
||||
[_tableView registerClass:TUISearchResultCell.class forCellReuseIdentifier:Id];
|
||||
[_tableView registerClass:TUISearchResultHeaderFooterView.class forHeaderFooterViewReuseIdentifier:HFId];
|
||||
[self.view addSubview:_tableView];
|
||||
|
||||
[_searchBar.searchBar becomeFirstResponder];
|
||||
}
|
||||
|
||||
- (void)viewWillLayoutSubviews {
|
||||
[super viewWillLayoutSubviews];
|
||||
self.tableView.frame = self.view.bounds;
|
||||
self.searchBar.frame = CGRectMake(0, 0, self.view.mm_w, 44);
|
||||
}
|
||||
|
||||
- (void)viewWillAppear:(BOOL)animated {
|
||||
[super viewWillAppear:animated];
|
||||
}
|
||||
|
||||
- (void)viewWillDisappear:(BOOL)animated {
|
||||
[super viewWillDisappear:animated];
|
||||
}
|
||||
|
||||
#pragma mark - TUISearchResultDelegate
|
||||
- (void)onSearchError:(NSString *)errMsg {
|
||||
}
|
||||
|
||||
- (void)onSearchResults:(NSDictionary<NSNumber *, NSArray<TUISearchResultCellModel *> *> *)results forModules:(TUISearchResultModule)modules {
|
||||
[self.tableView reloadData];
|
||||
}
|
||||
|
||||
#pragma mark - UITableViewDataSource、TUITableViewDelegate
|
||||
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
|
||||
return self.dataProvider.resultSet.allKeys.count;
|
||||
}
|
||||
|
||||
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
|
||||
NSArray *results = [self resultForSection:section];
|
||||
return results.count > kMaxNumOfPerModule ? kMaxNumOfPerModule : results.count;
|
||||
}
|
||||
|
||||
- (TUISearchResultCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
|
||||
TUISearchResultCell *cell = [tableView dequeueReusableCellWithIdentifier:Id forIndexPath:indexPath];
|
||||
TUISearchResultModule module = TUISearchResultModuleContact;
|
||||
NSArray *results = [self resultForSection:indexPath.section module:&module];
|
||||
if (results.count <= indexPath.row) {
|
||||
return cell;
|
||||
}
|
||||
[cell fillWithData:results[indexPath.row]];
|
||||
return cell;
|
||||
}
|
||||
|
||||
- (UIView *)tableView:(UITableView *)tableView viewForFooterInSection:(NSInteger)section {
|
||||
TUISearchResultModule module = TUISearchResultModuleContact;
|
||||
NSArray *results = [self resultForSection:section module:&module];
|
||||
if (results.count < kMaxNumOfPerModule) {
|
||||
return [UIView new];
|
||||
}
|
||||
__weak typeof(self) weakSelf = self;
|
||||
TUISearchResultHeaderFooterView *footerView = [tableView dequeueReusableHeaderFooterViewWithIdentifier:HFId];
|
||||
footerView.isFooter = YES;
|
||||
footerView.title = titleForModule(module, NO);
|
||||
footerView.onTap = ^{
|
||||
[weakSelf onSelectMoreModule:module results:results];
|
||||
};
|
||||
return footerView;
|
||||
}
|
||||
|
||||
- (CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section {
|
||||
NSArray *results = [self resultForSection:section];
|
||||
if (results.count < kMaxNumOfPerModule) {
|
||||
return 10;
|
||||
}
|
||||
return 44;
|
||||
}
|
||||
|
||||
- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section {
|
||||
TUISearchResultModule module = TUISearchResultModuleContact;
|
||||
[self resultForSection:section module:&module];
|
||||
TUISearchResultHeaderFooterView *headerView = [tableView dequeueReusableHeaderFooterViewWithIdentifier:HFId];
|
||||
headerView.isFooter = NO;
|
||||
headerView.title = titleForModule(module, YES);
|
||||
headerView.onTap = nil;
|
||||
return headerView;
|
||||
}
|
||||
|
||||
- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section {
|
||||
return 30;
|
||||
}
|
||||
|
||||
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
|
||||
[self.view endEditing:YES];
|
||||
[self.searchBar endEditing:YES];
|
||||
}
|
||||
|
||||
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
|
||||
[tableView deselectRowAtIndexPath:indexPath animated:NO];
|
||||
TUISearchResultModule module = TUISearchResultModuleContact;
|
||||
NSArray *results = [self resultForSection:indexPath.section module:&module];
|
||||
if (results.count <= indexPath.row) {
|
||||
return;
|
||||
}
|
||||
TUISearchResultCellModel *cellModel = results[indexPath.row];
|
||||
[self onSelectModel:cellModel module:module];
|
||||
}
|
||||
|
||||
#pragma mark - action
|
||||
- (void)onSelectModel:(TUISearchResultCellModel *)cellModel module:(TUISearchResultModule)module {
|
||||
[self.searchBar endEditing:YES];
|
||||
|
||||
if (module == TUISearchResultModuleChatHistory) {
|
||||
if (![cellModel.context isKindOfClass:NSDictionary.class]) {
|
||||
return;
|
||||
}
|
||||
NSDictionary *convInfo = cellModel.context;
|
||||
NSString *conversationId = convInfo[kSearchChatHistoryConversationId];
|
||||
V2TIMConversation *conversation = convInfo[kSearchChatHistoryConverationInfo];
|
||||
NSArray *msgs = convInfo[kSearchChatHistoryConversationMsgs];
|
||||
if (msgs.count == 1) {
|
||||
NSString *title = cellModel.title ?: cellModel.titleAttributeString.string;
|
||||
NSDictionary *param = @{
|
||||
TUICore_TUIChatObjectFactory_ChatViewController_Title : title ?: @"",
|
||||
TUICore_TUIChatObjectFactory_ChatViewController_UserID : conversation.userID ?: @"",
|
||||
TUICore_TUIChatObjectFactory_ChatViewController_GroupID : conversation.groupID ?: @"",
|
||||
TUICore_TUIChatObjectFactory_ChatViewController_HighlightKeyword : self.searchBar.searchBar.text ?: @"",
|
||||
TUICore_TUIChatObjectFactory_ChatViewController_LocateMessage : msgs.firstObject,
|
||||
};
|
||||
[self.navigationController pushViewController:TUICore_TUIChatObjectFactory_ChatViewController_Classic param:param forResult:nil];
|
||||
return;
|
||||
}
|
||||
|
||||
NSMutableArray *results = [NSMutableArray array];
|
||||
for (V2TIMMessage *message in msgs) {
|
||||
TUISearchResultCellModel *model = [[TUISearchResultCellModel alloc] init];
|
||||
model.title = message.nickName ?: message.sender;
|
||||
NSString *desc = [TUISearchDataProvider matchedTextForMessage:message withKey:self.searchBar.searchBar.text];
|
||||
model.detailsAttributeString = [TUISearchDataProvider attributeStringWithText:desc key:self.searchBar.searchBar.text];
|
||||
model.avatarUrl = message.faceURL;
|
||||
model.groupType = conversation.groupType;
|
||||
model.avatarImage = conversation.type == V2TIM_C2C ? DefaultAvatarImage : DefaultGroupAvatarImageByGroupType(conversation.groupType);
|
||||
;
|
||||
model.context = message;
|
||||
[results addObject:model];
|
||||
}
|
||||
TUISearchResultListController *vc =
|
||||
[[TUISearchResultListController alloc] initWithResults:results
|
||||
keyword:self.searchBar.searchBar.text
|
||||
module:module
|
||||
param:@{TUISearchChatHistoryParamKeyConversationId : conversationId}];
|
||||
[self.navigationController pushViewController:vc animated:YES];
|
||||
return;
|
||||
}
|
||||
|
||||
NSDictionary *param = nil;
|
||||
|
||||
if (module == TUISearchResultModuleContact && [cellModel.context isKindOfClass:V2TIMFriendInfo.class]) {
|
||||
V2TIMFriendInfo *friend = cellModel.context;
|
||||
NSString *title = cellModel.title ?: cellModel.titleAttributeString.string;
|
||||
param = @{
|
||||
TUICore_TUIChatObjectFactory_ChatViewController_Title : title ?: @"",
|
||||
TUICore_TUIChatObjectFactory_ChatViewController_UserID : friend.userID ?: @"",
|
||||
};
|
||||
}
|
||||
|
||||
if (module == TUISearchResultModuleGroup && [cellModel.context isKindOfClass:V2TIMGroupInfo.class]) {
|
||||
V2TIMGroupInfo *group = cellModel.context;
|
||||
NSString *title = cellModel.title ?: cellModel.titleAttributeString.string;
|
||||
param = @{
|
||||
TUICore_TUIChatObjectFactory_ChatViewController_Title : title ?: @"",
|
||||
TUICore_TUIChatObjectFactory_ChatViewController_GroupID : group.groupID ?: @"",
|
||||
};
|
||||
}
|
||||
[self.navigationController pushViewController:TUICore_TUIChatObjectFactory_ChatViewController_Classic param:param forResult:nil];
|
||||
}
|
||||
|
||||
- (void)onSelectMoreModule:(TUISearchResultModule)module results:(NSArray<TUISearchResultCellModel *> *)results {
|
||||
TUISearchResultListController *vc = [[TUISearchResultListController alloc] initWithResults:results
|
||||
keyword:self.searchBar.searchBar.text
|
||||
module:module
|
||||
param:nil];
|
||||
[self.navigationController pushViewController:vc animated:YES];
|
||||
}
|
||||
|
||||
#pragma mark - viewmodel
|
||||
- (NSArray *)resultForSection:(NSInteger)section {
|
||||
return [self resultForSection:section module:nil];
|
||||
}
|
||||
|
||||
- (NSArray *)resultForSection:(NSInteger)section module:(TUISearchResultModule *)module {
|
||||
NSArray *keys = self.dataProvider.resultSet.allKeys;
|
||||
if (section >= keys.count) {
|
||||
return 0;
|
||||
}
|
||||
keys = [keys sortedArrayUsingComparator:^NSComparisonResult(NSNumber *obj1, NSNumber *obj2) {
|
||||
return [obj1 intValue] < [obj2 intValue] ? NSOrderedAscending : NSOrderedDescending;
|
||||
}];
|
||||
|
||||
NSNumber *key = keys[section];
|
||||
if (module) {
|
||||
*module = (TUISearchResultModule)[key integerValue];
|
||||
}
|
||||
return [self.dataProvider.resultSet objectForKey:key];
|
||||
}
|
||||
|
||||
#pragma mark - TUISearchBarDelegate
|
||||
- (void)searchBarDidCancelClicked:(TUISearchBar *)searchBar {
|
||||
[self dismissViewControllerAnimated:NO completion:nil];
|
||||
}
|
||||
|
||||
- (void)searchBar:(TUISearchBar *)searchBar searchText:(NSString *)key {
|
||||
[self.dataProvider searchForKeyword:key forModules:TUISearchResultModuleAll param:nil];
|
||||
}
|
||||
|
||||
- (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
|
||||
|
||||
@interface IUSearchView : UIView
|
||||
@property(nonatomic, strong) UIView *view;
|
||||
@end
|
||||
|
||||
@implementation IUSearchView
|
||||
|
||||
- (instancetype)init {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
self.view = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 10, 10)];
|
||||
[self addSubview:self.view];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
@end
|
||||
@@ -0,0 +1,22 @@
|
||||
//
|
||||
// TUISearchResultCell_Minimalist.h
|
||||
// TUISearch
|
||||
//
|
||||
// Created by wyl on 2022/12/16.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
//
|
||||
|
||||
#import "TUISearchResultCell.h"
|
||||
@class TUISearchResultCellModel;
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface TUISearchResultCell_Minimalist : UITableViewCell
|
||||
@property(nonatomic, strong) UIImageView *avatarView;
|
||||
@property(nonatomic, strong) UILabel *title_label;
|
||||
|
||||
- (void)fillWithData:(TUISearchResultCellModel *)cellModel;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,282 @@
|
||||
//
|
||||
// TUISearchResultCell_Minimalist.m
|
||||
// TUISearch
|
||||
//
|
||||
// Created by wyl on 2022/12/16.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
//
|
||||
|
||||
#import "TUISearchResultCell_Minimalist.h"
|
||||
#import <TIMCommon/TIMCommonModel.h>
|
||||
#import <TIMCommon/TIMDefine.h>
|
||||
#import <TUICore/TUIThemeManager.h>
|
||||
#import "TUISearchResultCellModel.h"
|
||||
|
||||
@interface TUISearchResultCell_Minimalist ()
|
||||
@property(nonatomic, strong) UILabel *detail_title;
|
||||
@property(nonatomic, strong) UIImageView *rowAccessoryView;
|
||||
@property(nonatomic, strong) UIView *separtorView;
|
||||
@property(nonatomic, strong) TUISearchResultCellModel *cellModel;
|
||||
|
||||
@end
|
||||
@implementation TUISearchResultCell_Minimalist
|
||||
|
||||
- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier {
|
||||
if (self = [super initWithStyle:style reuseIdentifier:reuseIdentifier]) {
|
||||
[self setupViews];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)setupViews {
|
||||
self.contentView.backgroundColor = TIMCommonDynamicColor(@"form_bg_color", @"#FFFFFF");
|
||||
|
||||
_avatarView = [[UIImageView alloc] init];
|
||||
[self.contentView addSubview:_avatarView];
|
||||
|
||||
_title_label = [[UILabel alloc] init];
|
||||
_title_label.text = @"";
|
||||
_title_label.textColor = TIMCommonDynamicColor(@"form_title_color", @"#000000");
|
||||
_title_label.font = [UIFont boldSystemFontOfSize:14.0];
|
||||
_title_label.textAlignment = isRTL()?NSTextAlignmentRight:NSTextAlignmentLeft;
|
||||
[self.contentView addSubview:_title_label];
|
||||
|
||||
_detail_title = [[UILabel alloc] init];
|
||||
_detail_title.text = @"";
|
||||
_detail_title.textColor = TIMCommonDynamicColor(@"form_subtitle_color", @"#888888");
|
||||
_detail_title.font = [UIFont systemFontOfSize:12.0];
|
||||
_detail_title.textAlignment = isRTL()?NSTextAlignmentRight:NSTextAlignmentLeft;
|
||||
|
||||
[self.contentView addSubview:_detail_title];
|
||||
|
||||
_rowAccessoryView = [[UIImageView alloc] init];
|
||||
_rowAccessoryView.image = [UIImage imageNamed:TUISearchImagePath(@"right")];
|
||||
[self.contentView addSubview:_rowAccessoryView];
|
||||
_rowAccessoryView.hidden = YES;
|
||||
|
||||
_separtorView = [[UIView alloc] init];
|
||||
_separtorView.backgroundColor = TIMCommonDynamicColor(@"separator_color", @"#DBDBDB");
|
||||
[self.contentView addSubview:_separtorView];
|
||||
_separtorView.hidden = YES;
|
||||
|
||||
self.selectionStyle = UITableViewCellSelectionStyleNone;
|
||||
}
|
||||
|
||||
- (void)layoutSubviews {
|
||||
[super layoutSubviews];
|
||||
}
|
||||
|
||||
- (void)fillWithData:(TUISearchResultCellModel *)cellModel {
|
||||
self.cellModel = cellModel;
|
||||
|
||||
self.title_label.text = nil;
|
||||
self.title_label.attributedText = nil;
|
||||
self.detail_title.text = nil;
|
||||
self.detail_title.attributedText = nil;
|
||||
|
||||
self.title_label.text = cellModel.title;
|
||||
if (cellModel.titleAttributeString) {
|
||||
self.title_label.attributedText = cellModel.titleAttributeString;
|
||||
}
|
||||
self.detail_title.text = cellModel.details;
|
||||
if (cellModel.detailsAttributeString) {
|
||||
self.detail_title.attributedText = cellModel.detailsAttributeString;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup default avatar
|
||||
*/
|
||||
if (cellModel.groupID.length > 0) {
|
||||
/**
|
||||
* If it is a group, change the group default avatar to the last used avatar
|
||||
*/
|
||||
UIImage *avatar = nil;
|
||||
if (TUIConfig.defaultConfig.enableGroupGridAvatar) {
|
||||
NSString *key = [NSString stringWithFormat:@"TUIConversationLastGroupMember_%@", cellModel.groupID];
|
||||
NSInteger member = [NSUserDefaults.standardUserDefaults integerForKey:key];
|
||||
avatar = [TUIGroupAvatar getCacheAvatarForGroup:cellModel.groupID number:(UInt32)member];
|
||||
}
|
||||
cellModel.avatarImage = avatar ? avatar : DefaultGroupAvatarImageByGroupType(cellModel.groupType);
|
||||
}
|
||||
|
||||
@weakify(self);
|
||||
[[RACObserve(cellModel, avatarUrl) takeUntil:self.rac_prepareForReuseSignal] subscribeNext:^(NSString *faceUrl) {
|
||||
@strongify(self);
|
||||
if (cellModel.groupID.length > 0) {
|
||||
/**
|
||||
* Group avatar
|
||||
*/
|
||||
if (IS_NOT_EMPTY_NSSTRING(faceUrl)) {
|
||||
/**
|
||||
* The group avatar has been manually set externally
|
||||
*/
|
||||
[self.avatarView sd_setImageWithURL:[NSURL URLWithString:faceUrl] placeholderImage:self.cellModel.avatarImage];
|
||||
} else {
|
||||
/**
|
||||
* The group avatar has not been set externally. If the synthetic avatar is allowed, the synthetic avatar will be used; otherwise, the default
|
||||
* avatar will be used.
|
||||
*/
|
||||
if (TUIConfig.defaultConfig.enableGroupGridAvatar) {
|
||||
/**
|
||||
*
|
||||
* If the synthetic avatar is allowed, the synthetic avatar will be used
|
||||
* 1. Asynchronously obtain the cached synthetic avatar according to the number of group members
|
||||
* 2. If the cache is hit, use the cached synthetic avatar directly
|
||||
* 3. If the cache is not hit, recompose a new avatar
|
||||
*
|
||||
* Note:
|
||||
* 1. Since "asynchronously obtaining cached avatars" and "synthesizing avatars" take a long time, it is easy to cause cell reuse problems, so
|
||||
* it is necessary to confirm whether to assign values directly according to groupID.
|
||||
* 2. Use SDWebImage to implement placeholder, because SDWebImage has already dealt with the problem of cell reuse
|
||||
*/
|
||||
|
||||
// 1. Obtain group avatar from cache
|
||||
|
||||
// fix: Th getCacheGroupAvatar needs to request the
|
||||
// network. When the network is disconnected, since the headImageView is not set, the current conversation sends a message, the conversation
|
||||
// is moved up, and the avatar of the first conversation is reused, resulting in confusion of the avatar.
|
||||
[self.avatarView sd_setImageWithURL:nil placeholderImage:cellModel.avatarImage];
|
||||
[TUIGroupAvatar
|
||||
getCacheGroupAvatar:cellModel.groupID
|
||||
callback:^(UIImage *avatar, NSString *groupID) {
|
||||
@strongify(self);
|
||||
if ([groupID isEqualToString:self.cellModel.groupID]) {
|
||||
// 1.1 When the callback is invoked, the cell is not reused
|
||||
|
||||
if (avatar != nil) {
|
||||
// 2. Hit the cache and assign directly
|
||||
[self.avatarView sd_setImageWithURL:nil placeholderImage:avatar];
|
||||
} else {
|
||||
// 3. Synthesize new avatars asynchronously without hitting cache
|
||||
|
||||
[self.avatarView sd_setImageWithURL:nil placeholderImage:cellModel.avatarImage];
|
||||
[TUIGroupAvatar
|
||||
fetchGroupAvatars:cellModel.groupID
|
||||
placeholder:cellModel.avatarImage
|
||||
callback:^(BOOL success, UIImage *image, NSString *groupID) {
|
||||
@strongify(self);
|
||||
if ([groupID isEqualToString:self.cellModel.groupID]) {
|
||||
// callback ,cell
|
||||
// When the callback is invoked, the cell is not reused
|
||||
[self.avatarView
|
||||
sd_setImageWithURL:nil
|
||||
placeholderImage:success ? image
|
||||
: DefaultGroupAvatarImageByGroupType(self.cellModel.groupType)];
|
||||
} else {
|
||||
// callback, When the callback is invoked, the cell has been reused to other groupIDs.
|
||||
// Since a new callback will be triggered when the new groupID synthesizes new avatar, it is
|
||||
// ignored here
|
||||
}
|
||||
}];
|
||||
}
|
||||
} else {
|
||||
// 1.2 When the callback is invoked, the cell has been reused to other groupIDs. Since a new callback will be triggered
|
||||
// when the new groupID gets the cache, it is ignored here
|
||||
}
|
||||
}];
|
||||
} else {
|
||||
/**
|
||||
* Synthetic avatars are not allowed, use the default avatar directly
|
||||
*/
|
||||
[self.avatarView sd_setImageWithURL:nil placeholderImage:cellModel.avatarImage];
|
||||
}
|
||||
}
|
||||
} else {
|
||||
/**
|
||||
* Personal avatar
|
||||
*/
|
||||
[self.avatarView sd_setImageWithURL:[NSURL URLWithString:faceUrl] placeholderImage:self.cellModel.avatarImage];
|
||||
}
|
||||
}];
|
||||
|
||||
// tell constraints they need updating
|
||||
[self setNeedsUpdateConstraints];
|
||||
|
||||
// update constraints now so we can animate the change
|
||||
[self updateConstraintsIfNeeded];
|
||||
|
||||
[self layoutIfNeeded];
|
||||
}
|
||||
|
||||
+ (BOOL)requiresConstraintBasedLayout {
|
||||
return YES;
|
||||
}
|
||||
|
||||
// this is Apple's recommended place for adding/updating constraints
|
||||
- (void)updateConstraints {
|
||||
|
||||
[super updateConstraints];
|
||||
|
||||
CGSize headSize = CGSizeMake(kScale390(40), kScale390(40));
|
||||
|
||||
[self.avatarView mas_remakeConstraints:^(MASConstraintMaker *make) {
|
||||
make.size.mas_equalTo(headSize);
|
||||
make.centerY.mas_equalTo(self.contentView.mas_centerY);
|
||||
make.leading.mas_equalTo(kScale390(16));
|
||||
}];
|
||||
|
||||
if ([TUIConfig defaultConfig].avatarType == TAvatarTypeRounded) {
|
||||
self.avatarView.layer.masksToBounds = YES;
|
||||
self.avatarView.layer.cornerRadius = headSize.height / 2;
|
||||
} else if ([TUIConfig defaultConfig].avatarType == TAvatarTypeRadiusCorner) {
|
||||
self.avatarView.layer.masksToBounds = YES;
|
||||
self.avatarView.layer.cornerRadius = [TUIConfig defaultConfig].avatarCornerRadius;
|
||||
}
|
||||
|
||||
NSString *title = self.title_label.text;
|
||||
if (title.length == 0) {
|
||||
title = self.title_label.attributedText.string;
|
||||
}
|
||||
NSString *detail = self.detail_title.text;
|
||||
if (detail.length == 0) {
|
||||
detail = self.detail_title.attributedText.string;
|
||||
}
|
||||
|
||||
[self.title_label sizeToFit];
|
||||
|
||||
[self.detail_title sizeToFit];
|
||||
|
||||
if (title.length && self.detail_title.text.length) {
|
||||
[self.title_label mas_remakeConstraints:^(MASConstraintMaker *make) {
|
||||
make.top.mas_equalTo(self.avatarView.mas_top);
|
||||
make.leading.mas_equalTo(self.avatarView.mas_trailing).mas_offset(10);
|
||||
make.trailing.mas_equalTo(self.contentView.mas_trailing).mas_offset(-10);
|
||||
make.height.mas_greaterThanOrEqualTo(self.title_label.frame.size.height);
|
||||
}];
|
||||
|
||||
[self.detail_title mas_remakeConstraints:^(MASConstraintMaker *make) {
|
||||
make.bottom.mas_equalTo(self.avatarView.mas_bottom);
|
||||
make.leading.mas_equalTo(self.avatarView.mas_trailing).mas_offset(10);
|
||||
make.trailing.mas_equalTo(self.contentView.mas_trailing).mas_offset(-10);
|
||||
make.height.mas_greaterThanOrEqualTo(self.detail_title.frame.size.height);
|
||||
}];
|
||||
} else {
|
||||
|
||||
[self.title_label mas_remakeConstraints:^(MASConstraintMaker *make) {
|
||||
make.centerY.mas_equalTo(self.avatarView.mas_centerY);
|
||||
make.leading.mas_equalTo(self.avatarView.mas_trailing).mas_offset(10);
|
||||
make.trailing.mas_equalTo(self.contentView.mas_trailing).mas_offset(-10);
|
||||
make.height.mas_greaterThanOrEqualTo(self.title_label.frame.size.height);
|
||||
}];
|
||||
[self.detail_title mas_remakeConstraints:^(MASConstraintMaker *make) {
|
||||
make.centerY.mas_equalTo(self.avatarView.mas_centerY);
|
||||
make.leading.mas_equalTo(self.avatarView.mas_trailing).mas_offset(10);
|
||||
make.trailing.mas_equalTo(self.contentView.mas_trailing).mas_offset(-10);
|
||||
make.height.mas_greaterThanOrEqualTo(self.title_label.frame.size.height);
|
||||
}];
|
||||
}
|
||||
|
||||
[self.rowAccessoryView mas_remakeConstraints:^(MASConstraintMaker *make) {
|
||||
make.height.width.mas_equalTo(10);
|
||||
make.centerY.mas_equalTo(self.contentView);
|
||||
make.trailing.mas_equalTo(self.contentView.mas_trailing).mas_offset(- 10);
|
||||
}];
|
||||
[self.separtorView mas_remakeConstraints:^(MASConstraintMaker *make) {
|
||||
make.leading.mas_equalTo(self.detail_title.mas_trailing);
|
||||
make.bottom.mas_equalTo(self.contentView.mas_bottom).mas_offset(-1);
|
||||
make.width.mas_equalTo(self.contentView);
|
||||
make.height.mas_equalTo(1);
|
||||
}];
|
||||
|
||||
}
|
||||
@end
|
||||
16
TUIKit/TUISearch/UI_Minimalist/Header/TUISearch_Minimalist.h
Normal file
@@ -0,0 +1,16 @@
|
||||
//
|
||||
// TUISearch_Minimalist.h
|
||||
// Pods
|
||||
//
|
||||
// Created by harvy on 2022/6/9.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef TUISearch_Minimalist_h
|
||||
#define TUISearch_Minimalist_h
|
||||
|
||||
#import "TUISearchBar_Minimalist.h"
|
||||
#import "TUISearchResultListController_Minimalist.h"
|
||||
#import "TUISearchViewController_Minimalist.h"
|
||||
|
||||
#endif /* TUISearch_Minimalist_h */
|
||||
@@ -0,0 +1,17 @@
|
||||
//
|
||||
// TUISearchExtensionObserver_Minimalist.h
|
||||
// TUISearch
|
||||
//
|
||||
// Created by harvy on 2023/4/3.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface TUISearchExtensionObserver_Minimalist : NSObject
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,62 @@
|
||||
//
|
||||
// TUISearchExtensionObserver_Minimalist.m
|
||||
// TUISearch
|
||||
//
|
||||
// Created by harvy on 2023/4/3.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
//
|
||||
|
||||
#import "TUISearchExtensionObserver_Minimalist.h"
|
||||
|
||||
#import <TUICore/TUICore.h>
|
||||
#import <TUICore/TUIDefine.h>
|
||||
|
||||
#import "TUISearchBar_Minimalist.h"
|
||||
|
||||
@interface TUISearchExtensionObserver_Minimalist () <TUIExtensionProtocol>
|
||||
|
||||
@end
|
||||
|
||||
@implementation TUISearchExtensionObserver_Minimalist
|
||||
|
||||
+ (void)load {
|
||||
[TUICore registerExtension:TUICore_TUIConversationExtension_ConversationListBanner_MinimalistExtensionID
|
||||
object:TUISearchExtensionObserver_Minimalist.shareInstance];
|
||||
}
|
||||
|
||||
static id gShareInstance = nil;
|
||||
+ (instancetype)shareInstance {
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
gShareInstance = [[self alloc] init];
|
||||
});
|
||||
return gShareInstance;
|
||||
}
|
||||
|
||||
#pragma mark - TUIExtensionProtocol
|
||||
- (BOOL)onRaiseExtension:(NSString *)extensionID parentView:(UIView *)parentView param:(nullable NSDictionary *)param {
|
||||
if (![extensionID isKindOfClass:NSString.class]) {
|
||||
return NO;
|
||||
}
|
||||
|
||||
if ([extensionID isEqualToString:TUICore_TUIConversationExtension_ConversationListBanner_MinimalistExtensionID]) {
|
||||
if (![param isKindOfClass:NSDictionary.class] || parentView == nil || ![parentView isKindOfClass:UIView.class]) {
|
||||
return NO;
|
||||
}
|
||||
UIViewController *modalVC = [param tui_objectForKey:TUICore_TUIConversationExtension_ConversationListBanner_ModalVC asClass:UIViewController.class];
|
||||
NSString *sizeStr = [param tui_objectForKey:TUICore_TUIConversationExtension_ConversationListBanner_BannerSize asClass:NSString.class];
|
||||
CGSize size = CGSizeFromString(sizeStr);
|
||||
|
||||
TUISearchBar_Minimalist *searchBar = [[TUISearchBar_Minimalist alloc] init];
|
||||
searchBar.frame = CGRectMake(0, 0, size.width, size.height);
|
||||
[searchBar setParentVC:modalVC];
|
||||
[searchBar setEntrance:YES];
|
||||
[parentView addSubview:searchBar];
|
||||
return YES;
|
||||
} else {
|
||||
// do nothing
|
||||
return NO;
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,13 @@
|
||||
|
||||
// Created by Tencent on 2023/06/09.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface TUISearchService_Minimalist : NSObject
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,26 @@
|
||||
|
||||
// Created by Tencent on 2023/06/09.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
#import "TUISearchService_Minimalist.h"
|
||||
#import "TUISearchBar_Minimalist.h"
|
||||
|
||||
#import <TIMCommon/TIMDefine.h>
|
||||
#import <TUICore/TUICore.h>
|
||||
#import <TUICore/TUIThemeManager.h>
|
||||
|
||||
@implementation TUISearchService_Minimalist
|
||||
|
||||
+ (void)load {
|
||||
TUIRegisterThemeResourcePath(TUIBundlePath(@"TUISearchTheme_Minimalist", TUISearchBundle_Key_Class), TUIThemeModuleSearch_Minimalist);
|
||||
}
|
||||
|
||||
static id gShareInstance = nil;
|
||||
+ (instancetype)shareInstance {
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
gShareInstance = [[self alloc] init];
|
||||
});
|
||||
return gShareInstance;
|
||||
}
|
||||
|
||||
@end
|
||||
34
TUIKit/TUISearch/UI_Minimalist/UI/TUISearchBar_Minimalist.h
Normal file
@@ -0,0 +1,34 @@
|
||||
//
|
||||
// TUISearchBar_Minimalist.h
|
||||
// Pods
|
||||
//
|
||||
// Created by harvy on 2020/12/23.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@class TUISearchBar_Minimalist;
|
||||
@protocol TUISearchBarDelegate_Minimalist <NSObject>
|
||||
|
||||
@optional
|
||||
- (void)searchBarDidEnterSearch:(TUISearchBar_Minimalist *)searchBar;
|
||||
- (void)searchBarDidCancelClicked:(TUISearchBar_Minimalist *)searchBar;
|
||||
- (void)searchBar:(TUISearchBar_Minimalist *)searchBar searchText:(NSString *)key;
|
||||
@end
|
||||
|
||||
@interface TUISearchBar_Minimalist : UIView
|
||||
|
||||
@property(nonatomic, strong, readonly) UISearchBar *searchBar;
|
||||
|
||||
@property(nonatomic, weak) id<TUISearchBarDelegate_Minimalist> delegate;
|
||||
// use weak, prevent circular references
|
||||
@property(nonatomic, weak) UIViewController *parentVC;
|
||||
|
||||
- (void)setEntrance:(BOOL)isEntrance;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
135
TUIKit/TUISearch/UI_Minimalist/UI/TUISearchBar_Minimalist.m
Normal file
@@ -0,0 +1,135 @@
|
||||
//
|
||||
// TUISearchBar_Minimalist.m
|
||||
// Pods
|
||||
//
|
||||
// Created by harvy on 2020/12/23.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
//
|
||||
|
||||
#import "TUISearchBar_Minimalist.h"
|
||||
#import <TIMCommon/TIMDefine.h>
|
||||
#import <TUICore/TUICore.h>
|
||||
#import <TUICore/TUIDarkModel.h>
|
||||
#import <TUICore/TUIGlobalization.h>
|
||||
#import <TUICore/TUIThemeManager.h>
|
||||
#import <TUICore/UIView+TUILayout.h>
|
||||
#import "TUISearchViewController_Minimalist.h"
|
||||
|
||||
@interface TUISearchBar_Minimalist () <UISearchBarDelegate>
|
||||
@property(nonatomic, strong) UISearchBar *searchBar;
|
||||
@property(nonatomic, assign) BOOL isEntrance;
|
||||
@end
|
||||
|
||||
@implementation TUISearchBar_Minimalist
|
||||
@synthesize delegate;
|
||||
|
||||
- (void)setEntrance:(BOOL)isEntrance {
|
||||
self.isEntrance = isEntrance;
|
||||
[self setupViews];
|
||||
}
|
||||
|
||||
- (UIColor *)bgColorOfSearchBar {
|
||||
return RGBA(255, 255, 255, 1);
|
||||
}
|
||||
|
||||
- (void)setupViews {
|
||||
self.backgroundColor = self.bgColorOfSearchBar;
|
||||
_searchBar = [[UISearchBar alloc] init];
|
||||
_searchBar.placeholder = TIMCommonLocalizableString(Search);
|
||||
_searchBar.backgroundImage = [UIImage new];
|
||||
_searchBar.barTintColor = UIColor.redColor;
|
||||
_searchBar.showsCancelButton = NO;
|
||||
_searchBar.searchTextField.textAlignment = isRTL()?NSTextAlignmentRight:NSTextAlignmentLeft;
|
||||
_searchBar.delegate = self;
|
||||
_searchBar.showsCancelButton = !self.isEntrance;
|
||||
if (@available(iOS 13.0, *)) {
|
||||
_searchBar.searchTextField.backgroundColor = RGBA(246, 246, 246, 1);
|
||||
}
|
||||
[self addSubview:_searchBar];
|
||||
[self enableCancelButton];
|
||||
}
|
||||
|
||||
- (void)layoutSubviews {
|
||||
[super layoutSubviews];
|
||||
self.searchBar.frame = CGRectMake(10, 5, self.mm_w - 10 - 10, self.mm_h - 5 - 5);
|
||||
|
||||
[self updateSearchIcon];
|
||||
}
|
||||
|
||||
- (void)updateSearchIcon {
|
||||
if ([self.searchBar isFirstResponder] || self.searchBar.text.length || !self.isEntrance) {
|
||||
self.backgroundColor = self.superview.backgroundColor;
|
||||
} else {
|
||||
self.backgroundColor = self.bgColorOfSearchBar;
|
||||
}
|
||||
[self.searchBar setPositionAdjustment:UIOffsetZero forSearchBarIcon:UISearchBarIconSearch];
|
||||
}
|
||||
|
||||
- (void)showSearchVC {
|
||||
TUISearchViewController_Minimalist *vc = [[TUISearchViewController_Minimalist alloc] init];
|
||||
TUINavigationController *nav = [[TUINavigationController alloc] initWithRootViewController:(UIViewController *)vc];
|
||||
nav.modalPresentationStyle = UIModalPresentationFullScreen;
|
||||
|
||||
UIViewController *parentVC = self.parentVC;
|
||||
if (parentVC) {
|
||||
[parentVC presentViewController:nav animated:NO completion:nil];
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - UISearchBarDelegate
|
||||
- (BOOL)searchBarShouldBeginEditing:(UISearchBar *)searchBar {
|
||||
[self showSearchVC];
|
||||
|
||||
if (self.isEntrance && [self.delegate respondsToSelector:@selector(searchBarDidEnterSearch:)]) {
|
||||
[self.delegate searchBarDidEnterSearch:self];
|
||||
}
|
||||
|
||||
__weak typeof(self) weakSelf = self;
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[weakSelf updateSearchIcon];
|
||||
});
|
||||
return !self.isEntrance;
|
||||
}
|
||||
|
||||
- (void)searchBarCancelButtonClicked:(UISearchBar *)searchBar {
|
||||
if ([self.delegate respondsToSelector:@selector(searchBarDidCancelClicked:)]) {
|
||||
[self.delegate searchBarDidCancelClicked:self];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)searchBarSearchButtonClicked:(UISearchBar *)searchBar {
|
||||
if ([self.delegate respondsToSelector:@selector(searchBar:searchText:)]) {
|
||||
[self.delegate searchBar:self searchText:searchBar.text];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText {
|
||||
if ([self.delegate respondsToSelector:@selector(searchBar:searchText:)]) {
|
||||
[self.delegate searchBar:self searchText:searchBar.text];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)searchBarTextDidEndEditing:(UISearchBar *)searchBar {
|
||||
[self enableCancelButton];
|
||||
}
|
||||
|
||||
- (void)enableCancelButton {
|
||||
__weak typeof(self) weakSelf = self;
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
UIButton *cancelBtn = [weakSelf.searchBar valueForKeyPath:@"cancelButton"];
|
||||
for (UIButton *view in cancelBtn.subviews) {
|
||||
if ([view isKindOfClass:UIButton.class]) {
|
||||
view.userInteractionEnabled = YES;
|
||||
view.enabled = YES;
|
||||
}
|
||||
}
|
||||
cancelBtn.enabled = YES;
|
||||
cancelBtn.userInteractionEnabled = YES;
|
||||
[cancelBtn setTitleColor:[UIColor systemBlueColor] forState:UIControlStateNormal];
|
||||
|
||||
[UIBarButtonItem appearanceWhenContainedInInstancesOfClasses:@[ [UISearchBar class] ]].title = TIMCommonLocalizableString(TUIKitSearchItemCancel);
|
||||
;
|
||||
});
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,22 @@
|
||||
//
|
||||
// TUISearchEmptyView_Minimalist.h
|
||||
// TUISearch
|
||||
//
|
||||
// Created by wyl on 2022/12/19.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface TUISearchEmptyView_Minimalist : UIView
|
||||
|
||||
@property(nonatomic, strong) UIImageView *midImage;
|
||||
@property(nonatomic, strong) UILabel *tipsLabel;
|
||||
|
||||
- (instancetype)initWithImage:(UIImage *)img Text:(NSString *)text;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,44 @@
|
||||
//
|
||||
// TUISearchEmptyView_Minimalist.m
|
||||
// TUISearch
|
||||
//
|
||||
// Created by wyl on 2022/12/19.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
//
|
||||
|
||||
#import "TUISearchEmptyView_Minimalist.h"
|
||||
#import <TIMCommon/TIMDefine.h>
|
||||
|
||||
@implementation TUISearchEmptyView_Minimalist
|
||||
|
||||
- (instancetype)initWithImage:(UIImage *)img Text:(NSString *)text {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
self.tipsLabel.text = text;
|
||||
self.midImage = [[UIImageView alloc] initWithImage:img];
|
||||
[self addSubview:self.tipsLabel];
|
||||
[self addSubview:self.midImage];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)layoutSubviews {
|
||||
[super layoutSubviews];
|
||||
self.midImage.frame = CGRectMake((self.bounds.size.width - kScale390(105)) * 0.5, 0, kScale390(105), kScale390(105));
|
||||
[self.tipsLabel sizeToFit];
|
||||
self.tipsLabel.frame = CGRectMake((self.bounds.size.width - self.tipsLabel.frame.size.width) * 0.5,
|
||||
self.midImage.frame.origin.y + self.midImage.frame.size.height + kScale390(10), self.tipsLabel.frame.size.width,
|
||||
self.tipsLabel.frame.size.height);
|
||||
}
|
||||
|
||||
- (UILabel *)tipsLabel {
|
||||
if (_tipsLabel == nil) {
|
||||
_tipsLabel = [[UILabel alloc] init];
|
||||
_tipsLabel.textColor = [UIColor tui_colorWithHex:@"#999999"];
|
||||
_tipsLabel.font = [UIFont systemFontOfSize:14.0];
|
||||
_tipsLabel.textAlignment = NSTextAlignmentCenter;
|
||||
}
|
||||
return _tipsLabel;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,29 @@
|
||||
//
|
||||
// TUISearchResultHeaderFooterView.h
|
||||
// Pods
|
||||
//
|
||||
// Created by harvy on 2020/12/24.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface TUISearchResultHeaderFooterView_Minimalist : UITableViewHeaderFooterView
|
||||
|
||||
@property(nonatomic, assign) BOOL isFooter;
|
||||
@property(nonatomic, assign) BOOL showMoreBtn;
|
||||
@property(nonatomic, copy) NSString *__nullable title;
|
||||
@property(nonatomic, copy) dispatch_block_t __nullable onTap;
|
||||
|
||||
@end
|
||||
|
||||
@interface TUISearchChatHistoryResultHeaderView_Minimalist : UITableViewHeaderFooterView
|
||||
@property(nonatomic, copy) NSString *__nullable title;
|
||||
@property(nonatomic, copy) dispatch_block_t __nullable onTap;
|
||||
|
||||
- (void)configPlaceHolderImage:(UIImage *)img imgUrl:(NSString *)imgurl Text:(NSString *)text;
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,273 @@
|
||||
//
|
||||
// TUISearchResultHeaderFooterView_Minimalist.m
|
||||
// Pods
|
||||
//
|
||||
// Created by harvy on 2020/12/24.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
//
|
||||
|
||||
#import "TUISearchResultHeaderFooterView_Minimalist.h"
|
||||
#import <TIMCommon/TIMDefine.h>
|
||||
#import <TUICore/TUIThemeManager.h>
|
||||
|
||||
@interface TUISearchResultHeaderFooterView_Minimalist ()
|
||||
|
||||
@property(nonatomic, strong) UIImageView *iconView;
|
||||
@property(nonatomic, strong) UILabel *titleLabel;
|
||||
@property(nonatomic, strong) UIButton *moreBtn;
|
||||
|
||||
@end
|
||||
|
||||
@implementation TUISearchResultHeaderFooterView_Minimalist
|
||||
|
||||
- (instancetype)initWithReuseIdentifier:(NSString *)reuseIdentifier {
|
||||
if (self = [super initWithReuseIdentifier:reuseIdentifier]) {
|
||||
[self setupViews];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)setupViews {
|
||||
[self.contentView addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tap:)]];
|
||||
self.contentView.backgroundColor = TIMCommonDynamicColor(@"form_bg_color", @"#FFFFFF");
|
||||
_iconView = [[UIImageView alloc] init];
|
||||
_iconView.image = [UIImage imageNamed:TUISearchImagePath(@"search")];
|
||||
[self.contentView addSubview:_iconView];
|
||||
|
||||
_titleLabel = [[UILabel alloc] init];
|
||||
_titleLabel.text = @"";
|
||||
_titleLabel.font = [UIFont boldSystemFontOfSize:24.0];
|
||||
[self.contentView addSubview:_titleLabel];
|
||||
|
||||
_moreBtn = [UIButton buttonWithType:UIButtonTypeCustom];
|
||||
[_moreBtn setTitle:TIMCommonLocalizableString(More) forState:UIControlStateNormal];
|
||||
[_moreBtn setTitleColor:[UIColor systemBlueColor] forState:UIControlStateNormal];
|
||||
_moreBtn.titleLabel.font = [UIFont boldSystemFontOfSize:kScale390(12)];
|
||||
_moreBtn.userInteractionEnabled = NO;
|
||||
[self.contentView addSubview:_moreBtn];
|
||||
}
|
||||
|
||||
- (void)tap:(UITapGestureRecognizer *)tap {
|
||||
if (self.onTap) {
|
||||
self.onTap();
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setIsFooter:(BOOL)isFooter {
|
||||
_isFooter = isFooter;
|
||||
|
||||
self.iconView.hidden = !self.isFooter;
|
||||
self.iconView.hidden = !self.isFooter;
|
||||
UIColor *footerColor = TIMCommonDynamicColor(@"primary_theme_color", @"#147AFF");
|
||||
self.titleLabel.textColor = self.isFooter ? footerColor : [UIColor darkGrayColor];
|
||||
}
|
||||
|
||||
- (void)setShowMoreBtn:(BOOL)showMoreBtn {
|
||||
_showMoreBtn = showMoreBtn;
|
||||
self.moreBtn.hidden = !showMoreBtn;
|
||||
}
|
||||
|
||||
- (void)setTitle:(NSString *)title {
|
||||
_title = title;
|
||||
self.titleLabel.text = title;
|
||||
}
|
||||
|
||||
+ (BOOL)requiresConstraintBasedLayout {
|
||||
return YES;
|
||||
}
|
||||
|
||||
// this is Apple's recommended place for adding/updating constraints
|
||||
- (void)updateConstraints {
|
||||
|
||||
[super updateConstraints];
|
||||
if (self.isFooter) {
|
||||
[self.iconView mas_remakeConstraints:^(MASConstraintMaker *make) {
|
||||
make.centerY.mas_equalTo(self.contentView);
|
||||
make.height.width.mas_equalTo(20);
|
||||
if(isRTL()){
|
||||
make.right.mas_equalTo(self.contentView.mas_right).mas_offset(-10);
|
||||
}
|
||||
else {
|
||||
make.left.mas_equalTo(self.contentView.mas_left).mas_offset(10);
|
||||
}
|
||||
}];
|
||||
[self.titleLabel sizeToFit];
|
||||
[self.titleLabel mas_remakeConstraints:^(MASConstraintMaker *make) {
|
||||
if(isRTL()) {
|
||||
make.right.mas_equalTo(self.iconView.mas_left).mas_offset(-10);
|
||||
}
|
||||
else{
|
||||
make.left.mas_equalTo(self.iconView.mas_right).mas_offset(10);
|
||||
}
|
||||
make.centerY.mas_equalTo(self.contentView);
|
||||
make.width.mas_equalTo(self.titleLabel.frame.size.width);
|
||||
make.height.mas_equalTo(self.titleLabel.font.lineHeight);
|
||||
}];
|
||||
|
||||
MASAttachKeys(self.iconView,self.titleLabel);
|
||||
} else {
|
||||
[self.titleLabel mas_remakeConstraints:^(MASConstraintMaker *make) {
|
||||
if(isRTL()) {
|
||||
make.left.mas_equalTo(self.moreBtn.mas_right).mas_offset(kScale390(16));
|
||||
make.right.mas_equalTo(self.contentView.mas_right).mas_offset(-kScale390(16));
|
||||
}
|
||||
else{
|
||||
make.left.mas_equalTo(self.contentView.mas_left).mas_offset(kScale390(16));
|
||||
make.right.mas_equalTo(self.moreBtn.mas_left).mas_offset(-kScale390(16));
|
||||
|
||||
}
|
||||
// make.leading.mas_equalTo(self.contentView.mas_leading).mas_offset(kScale390(16));
|
||||
make.centerY.mas_equalTo(self.contentView);
|
||||
// make.trailing.mas_equalTo(self.contentView.mas_trailing).mas_offset(-10);
|
||||
make.height.mas_equalTo(self.titleLabel.font.lineHeight);
|
||||
}];
|
||||
[self.moreBtn sizeToFit];
|
||||
[self.moreBtn mas_remakeConstraints:^(MASConstraintMaker *make) {
|
||||
if(isRTL()) {
|
||||
make.left.mas_equalTo(self.contentView).mas_offset(kScale390(10));
|
||||
}
|
||||
else{
|
||||
make.right.mas_equalTo(self.contentView).mas_offset(-kScale390(10));
|
||||
}
|
||||
make.centerY.mas_equalTo(self.contentView.mas_centerY);
|
||||
make.width.mas_equalTo(self.moreBtn.frame.size.width);
|
||||
make.height.mas_equalTo(self.moreBtn.frame.size.height);
|
||||
}];
|
||||
|
||||
MASAttachKeys(self.titleLabel,self.moreBtn);
|
||||
}
|
||||
}
|
||||
- (void)layoutSubviews {
|
||||
[super layoutSubviews];
|
||||
}
|
||||
|
||||
- (void)setFrame:(CGRect)frame {
|
||||
if (self.isFooter) {
|
||||
CGSize size = frame.size;
|
||||
size.height -= 10;
|
||||
frame.size = size;
|
||||
}
|
||||
[super setFrame:frame];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@interface TUISearchChatHistoryResultHeaderView_Minimalist ()
|
||||
@property(nonatomic, strong) UIImageView *iconView;
|
||||
@property(nonatomic, strong) UILabel *titleLabel;
|
||||
@property(nonatomic, strong) UIImageView *rowAccessoryView;
|
||||
@property(nonatomic, strong) UIView *separtorView;
|
||||
@end
|
||||
@implementation TUISearchChatHistoryResultHeaderView_Minimalist
|
||||
- (instancetype)initWithReuseIdentifier:(NSString *)reuseIdentifier {
|
||||
if (self = [super initWithReuseIdentifier:reuseIdentifier]) {
|
||||
[self setupViews];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)setupViews {
|
||||
[self.contentView addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tap:)]];
|
||||
self.contentView.backgroundColor = TIMCommonDynamicColor(@"form_bg_color", @"#FFFFFF");
|
||||
_iconView = [[UIImageView alloc] init];
|
||||
_iconView.image = [UIImage imageNamed:TUISearchImagePath(@"search")];
|
||||
[self.contentView addSubview:_iconView];
|
||||
|
||||
_titleLabel = [[UILabel alloc] init];
|
||||
_titleLabel.text = @"";
|
||||
_titleLabel.font = [UIFont boldSystemFontOfSize:14.0];
|
||||
[self.contentView addSubview:_titleLabel];
|
||||
|
||||
_rowAccessoryView = [[UIImageView alloc] init];
|
||||
_rowAccessoryView.image = [[UIImage imageNamed:TUISearchImagePath(@"right")] rtl_imageFlippedForRightToLeftLayoutDirection];
|
||||
[self.contentView addSubview:_rowAccessoryView];
|
||||
|
||||
_separtorView = [[UIView alloc] init];
|
||||
_separtorView.backgroundColor = TIMCommonDynamicColor(@"separator_color", @"#DBDBDB");
|
||||
[self.contentView addSubview:_separtorView];
|
||||
}
|
||||
|
||||
- (void)configPlaceHolderImage:(UIImage *)img imgUrl:(NSString *)imgurl Text:(NSString *)text {
|
||||
[_iconView sd_setImageWithURL:[NSURL URLWithString:imgurl] placeholderImage:img];
|
||||
_titleLabel.text = text;
|
||||
// tell constraints they need updating
|
||||
[self setNeedsUpdateConstraints];
|
||||
|
||||
// update constraints now so we can animate the change
|
||||
[self updateConstraintsIfNeeded];
|
||||
|
||||
[self layoutIfNeeded];
|
||||
}
|
||||
- (void)tap:(UITapGestureRecognizer *)tap {
|
||||
if (self.onTap) {
|
||||
self.onTap();
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setTitle:(NSString *)title {
|
||||
_title = title;
|
||||
self.titleLabel.text = title;
|
||||
}
|
||||
|
||||
// this is Apple's recommended place for adding/updating constraints
|
||||
- (void)updateConstraints {
|
||||
|
||||
[super updateConstraints];
|
||||
|
||||
CGFloat imgWitdh = kScale390(40);
|
||||
[self.iconView mas_remakeConstraints:^(MASConstraintMaker *make) {
|
||||
make.centerY.mas_equalTo(self.contentView);
|
||||
make.height.width.mas_equalTo(kScale390(40));
|
||||
if(isRTL()){
|
||||
make.right.mas_equalTo(self.contentView.mas_right).mas_offset(-kScale390(16));
|
||||
}
|
||||
else {
|
||||
make.left.mas_equalTo(self.contentView.mas_left).mas_offset(kScale390(16));
|
||||
}
|
||||
}];
|
||||
if ([TUIConfig defaultConfig].avatarType == TAvatarTypeRounded) {
|
||||
self.iconView.layer.masksToBounds = YES;
|
||||
self.iconView.layer.cornerRadius = imgWitdh / 2.0;
|
||||
} else if ([TUIConfig defaultConfig].avatarType == TAvatarTypeRadiusCorner) {
|
||||
self.iconView.layer.masksToBounds = YES;
|
||||
self.iconView.layer.cornerRadius = [TUIConfig defaultConfig].avatarCornerRadius;
|
||||
}
|
||||
|
||||
[self.titleLabel sizeToFit];
|
||||
[self.titleLabel mas_remakeConstraints:^(MASConstraintMaker *make) {
|
||||
if(isRTL()){
|
||||
make.right.mas_equalTo(self.iconView.mas_left).mas_offset(-kScale390(8));
|
||||
}
|
||||
else {
|
||||
make.left.mas_equalTo(self.iconView.mas_right).mas_offset(kScale390(8));
|
||||
}
|
||||
make.centerY.mas_equalTo(self.contentView);
|
||||
make.width.mas_equalTo(self.titleLabel.frame.size.width);
|
||||
make.height.mas_equalTo(self.titleLabel.frame.size.height);
|
||||
}];
|
||||
|
||||
[self.rowAccessoryView mas_remakeConstraints:^(MASConstraintMaker *make) {
|
||||
make.height.width.mas_equalTo(10);
|
||||
make.centerY.mas_equalTo(self.contentView);
|
||||
|
||||
if(isRTL()){
|
||||
make.left.mas_equalTo(self.contentView.mas_left).mas_offset(10);
|
||||
}
|
||||
else {
|
||||
make.right.mas_equalTo(self.contentView.mas_right).mas_offset(-10);
|
||||
}
|
||||
// make.trailing.mas_equalTo(self.contentView).mas_offset(-10);
|
||||
}];
|
||||
|
||||
[self.separtorView mas_remakeConstraints:^(MASConstraintMaker *make) {
|
||||
make.leading.mas_equalTo(10);
|
||||
make.bottom.mas_equalTo(self.contentView).mas_offset(-1);
|
||||
make.width.mas_equalTo(self.contentView);
|
||||
make.height.mas_equalTo(1);
|
||||
}];
|
||||
}
|
||||
- (void)layoutSubviews {
|
||||
[super layoutSubviews];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,27 @@
|
||||
//
|
||||
// TUISearchResultListController.h
|
||||
// Pods
|
||||
//
|
||||
// Created by harvy on 2020/12/25.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
#import "TUISearchDataProvider.h"
|
||||
@class TUISearchResultCellModel;
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface TUISearchResultListController_Minimalist : UIViewController
|
||||
|
||||
- (instancetype)initWithResults:(NSArray<TUISearchResultCellModel *> *__nullable)results
|
||||
keyword:(NSString *__nullable)keyword
|
||||
module:(TUISearchResultModule)module
|
||||
param:(NSDictionary<TUISearchParamKey, id> *__nullable)param;
|
||||
|
||||
@property(nonatomic, copy) NSString *headerConversationShowName;
|
||||
@property(nonatomic, copy) NSString *headerConversationURL;
|
||||
@property(nonatomic, strong) UIImage *headerConversationAvatar;
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,390 @@
|
||||
//
|
||||
// TUISearchResultListController_Minimalist.m
|
||||
// Pods
|
||||
//
|
||||
// Created by harvy on 2020/12/25.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
//
|
||||
|
||||
#import "TUISearchResultListController_Minimalist.h"
|
||||
#import <TIMCommon/TIMDefine.h>
|
||||
#import <TUICore/TUICore.h>
|
||||
#import "TUISearchBar_Minimalist.h"
|
||||
#import "TUISearchEmptyView_Minimalist.h"
|
||||
#import "TUISearchResultCellModel.h"
|
||||
#import "TUISearchResultCell_Minimalist.h"
|
||||
#import "TUISearchResultHeaderFooterView_Minimalist.h"
|
||||
|
||||
@interface TUISearchResultListController_Minimalist () <UITableViewDelegate, UITableViewDataSource, TUISearchBarDelegate_Minimalist, TUISearchResultDelegate>
|
||||
|
||||
@property(nonatomic, strong) NSMutableArray<TUISearchResultCellModel *> *results;
|
||||
@property(nonatomic, copy) NSString *keyword;
|
||||
@property(nonatomic, assign) TUISearchResultModule module;
|
||||
@property(nonatomic, strong) NSDictionary<TUISearchParamKey, id> *param;
|
||||
@property(nonatomic, strong) TUISearchDataProvider *dataProvider;
|
||||
|
||||
@property(nonatomic, strong) TUISearchBar_Minimalist *searchBar;
|
||||
@property(nonatomic, strong) UITableView *tableView;
|
||||
@property(nonatomic, strong) TUISearchEmptyView_Minimalist *noDataEmptyView;
|
||||
|
||||
@property(nonatomic, assign) BOOL allowPageRequest;
|
||||
@property(nonatomic, assign) NSUInteger pageIndex;
|
||||
|
||||
@end
|
||||
|
||||
@implementation TUISearchResultListController_Minimalist
|
||||
static NSString *const Id = @"cell";
|
||||
static NSString *const HFId = @"HFId";
|
||||
static NSString *const HistoryHFId = @"HistoryHFId";
|
||||
|
||||
- (instancetype)initWithResults:(NSArray<TUISearchResultCellModel *> *__nullable)results
|
||||
keyword:(NSString *__nullable)keyword
|
||||
module:(TUISearchResultModule)module
|
||||
param:(NSDictionary<TUISearchParamKey, id> *__nullable)param;
|
||||
{
|
||||
if (self = [super init]) {
|
||||
_results = (module == TUISearchResultModuleChatHistory) ? [NSMutableArray arrayWithArray:@[]] : [NSMutableArray arrayWithArray:results];
|
||||
_keyword = keyword;
|
||||
_module = module;
|
||||
_dataProvider = [[TUISearchDataProvider alloc] init];
|
||||
_dataProvider.delegate = self;
|
||||
_param = param;
|
||||
_allowPageRequest = (module == TUISearchResultModuleChatHistory);
|
||||
_pageIndex = 0;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)viewDidLoad {
|
||||
[super viewDidLoad];
|
||||
|
||||
self.navigationController.navigationBar.backgroundColor = [UIColor whiteColor];
|
||||
[self.navigationController.navigationBar setBackgroundImage:[self imageWithColor:[UIColor whiteColor]] forBarMetrics:UIBarMetricsDefault];
|
||||
self.navigationController.navigationBar.shadowImage = [UIImage new];
|
||||
self.view.backgroundColor = [UIColor whiteColor];
|
||||
|
||||
UIImage *image = [UIImage imageNamed:TIMCommonImagePath(@"nav_back")];
|
||||
image = [image imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal];
|
||||
image = [image rtl_imageFlippedForRightToLeftLayoutDirection];
|
||||
UIBarButtonItem *back = [[UIBarButtonItem alloc] initWithImage:image style:UIBarButtonItemStylePlain target:self action:@selector(back)];
|
||||
self.navigationItem.leftBarButtonItems = @[ back ];
|
||||
self.navigationItem.leftItemsSupplementBackButton = NO;
|
||||
|
||||
_searchBar = [[TUISearchBar_Minimalist alloc] init];
|
||||
[_searchBar setEntrance:NO];
|
||||
_searchBar.delegate = self;
|
||||
_searchBar.searchBar.text = self.keyword;
|
||||
self.navigationItem.titleView = _searchBar;
|
||||
|
||||
_tableView = [[UITableView alloc] initWithFrame:self.view.bounds style:UITableViewStyleGrouped];
|
||||
_tableView.delegate = self;
|
||||
_tableView.dataSource = self;
|
||||
_tableView.backgroundColor = [UIColor whiteColor];
|
||||
_tableView.separatorStyle = UITableViewCellSeparatorStyleNone;
|
||||
_tableView.rowHeight = 60.f;
|
||||
[_tableView registerClass:TUISearchResultCell_Minimalist.class forCellReuseIdentifier:Id];
|
||||
[_tableView registerClass:TUISearchResultHeaderFooterView_Minimalist.class forHeaderFooterViewReuseIdentifier:HFId];
|
||||
[_tableView registerClass:TUISearchChatHistoryResultHeaderView_Minimalist.class forHeaderFooterViewReuseIdentifier:HistoryHFId];
|
||||
[self.view addSubview:_tableView];
|
||||
self.noDataEmptyView.frame = CGRectMake(0, kScale390(42), self.view.bounds.size.width - 20, 200);
|
||||
[self.tableView addSubview:self.noDataEmptyView];
|
||||
|
||||
if (self.module == TUISearchResultModuleChatHistory) {
|
||||
[self.dataProvider searchForKeyword:self.searchBar.searchBar.text forModules:self.module param:self.param];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)back {
|
||||
[self.navigationController popViewControllerAnimated:YES];
|
||||
}
|
||||
|
||||
- (void)viewWillLayoutSubviews {
|
||||
[super viewWillLayoutSubviews];
|
||||
self.tableView.frame = self.view.bounds;
|
||||
self.searchBar.frame = CGRectMake(0, 0, self.view.mm_w, 44);
|
||||
}
|
||||
|
||||
- (void)viewWillAppear:(BOOL)animated {
|
||||
[super viewWillAppear:animated];
|
||||
self.navigationController.navigationBar.backgroundColor = [UIColor whiteColor];
|
||||
[self.navigationController.navigationBar setBackgroundImage:[self imageWithColor:[UIColor whiteColor]] forBarMetrics:UIBarMetricsDefault];
|
||||
self.navigationController.navigationBar.shadowImage = [UIImage new];
|
||||
}
|
||||
|
||||
- (void)viewWillDisappear:(BOOL)animated {
|
||||
[super viewWillDisappear:animated];
|
||||
self.navigationController.navigationBar.backgroundColor = nil;
|
||||
[self.navigationController.navigationBar setBackgroundImage:nil forBarMetrics:UIBarMetricsDefault];
|
||||
self.navigationController.navigationBar.shadowImage = nil;
|
||||
}
|
||||
|
||||
- (TUISearchEmptyView_Minimalist *)noDataEmptyView {
|
||||
if (_noDataEmptyView == nil) {
|
||||
_noDataEmptyView = [[TUISearchEmptyView_Minimalist alloc] initWithImage:TUISearchBundleThemeImage(@"", @"search_not_found_icon")
|
||||
Text:TIMCommonLocalizableString(TUIKitSearchNoResultLists)];
|
||||
_noDataEmptyView.hidden = YES;
|
||||
}
|
||||
return _noDataEmptyView;
|
||||
}
|
||||
|
||||
#pragma mark - Table view data source
|
||||
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
|
||||
return self.results.count;
|
||||
}
|
||||
|
||||
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
|
||||
TUISearchResultCell_Minimalist *cell = [tableView dequeueReusableCellWithIdentifier:Id forIndexPath:indexPath];
|
||||
if (indexPath.row >= self.results.count) {
|
||||
return cell;
|
||||
}
|
||||
TUISearchResultCellModel *model = self.results[indexPath.row];
|
||||
model.avatarType = TAvatarTypeRadiusCorner;
|
||||
[cell fillWithData:model];
|
||||
return cell;
|
||||
}
|
||||
|
||||
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
|
||||
[tableView deselectRowAtIndexPath:indexPath animated:NO];
|
||||
if (indexPath.row >= self.results.count) {
|
||||
return;
|
||||
}
|
||||
TUISearchResultCellModel *cellModel = self.results[indexPath.row];
|
||||
TUISearchResultCell_Minimalist *cell = [tableView cellForRowAtIndexPath:indexPath];
|
||||
|
||||
if (self.module == TUISearchResultModuleChatHistory) {
|
||||
cellModel.avatarImage = self.headerConversationAvatar ?: cell.avatarView.image;
|
||||
cellModel.title = self.headerConversationShowName ?: cell.title_label.text;
|
||||
} else {
|
||||
cellModel.avatarImage = cell.avatarView.image;
|
||||
cellModel.title = cell.title_label.text;
|
||||
}
|
||||
[self onSelectModel:cellModel module:self.module];
|
||||
}
|
||||
|
||||
- (CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section {
|
||||
return 0;
|
||||
}
|
||||
|
||||
- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section {
|
||||
if (self.module == TUISearchResultModuleChatHistory) {
|
||||
if (self.results.count == 0) {
|
||||
return 0;
|
||||
} else {
|
||||
TUISearchResultCellModel *cellModel = self.results[0];
|
||||
if ([cellModel.context isKindOfClass:NSDictionary.class]) {
|
||||
return 0;
|
||||
} else {
|
||||
return kScale390(64);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return self.results.count == 0 ? 0 : 30;
|
||||
}
|
||||
}
|
||||
|
||||
- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section {
|
||||
if (self.module == TUISearchResultModuleChatHistory) {
|
||||
TUISearchChatHistoryResultHeaderView_Minimalist *headerView = [tableView dequeueReusableHeaderFooterViewWithIdentifier:HistoryHFId];
|
||||
TUISearchResultCellModel *cellModel = self.results[0];
|
||||
[headerView configPlaceHolderImage:self.headerConversationAvatar imgUrl:self.headerConversationURL Text:self.headerConversationShowName];
|
||||
headerView.onTap = ^{
|
||||
[self headerViewJump2ChatViewController:cellModel];
|
||||
};
|
||||
return headerView;
|
||||
} else {
|
||||
TUISearchResultHeaderFooterView_Minimalist *headerView = [tableView dequeueReusableHeaderFooterViewWithIdentifier:HFId];
|
||||
headerView.showMoreBtn = NO;
|
||||
headerView.title = titleForModule(self.module, YES);
|
||||
headerView.isFooter = NO;
|
||||
return headerView;
|
||||
}
|
||||
}
|
||||
|
||||
- (UIView *)tableView:(UITableView *)tableView viewForFooterInSection:(NSInteger)section {
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
|
||||
[self.view endEditing:YES];
|
||||
[self.searchBar endEditing:YES];
|
||||
|
||||
if (self.allowPageRequest && scrollView.contentOffset.y + scrollView.bounds.size.height > scrollView.contentSize.height) {
|
||||
self.allowPageRequest = NO;
|
||||
NSMutableDictionary *param = [NSMutableDictionary dictionaryWithDictionary:self.param];
|
||||
param[TUISearchChatHistoryParamKeyPage] = @(self.pageIndex);
|
||||
param[TUISearchChatHistoryParamKeyCount] = @(TUISearchDefaultPageSize);
|
||||
self.param = [NSDictionary dictionaryWithDictionary:param];
|
||||
[self.dataProvider searchForKeyword:self.searchBar.searchBar.text forModules:self.module param:self.param];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)onBack {
|
||||
[self.navigationController popViewControllerAnimated:YES];
|
||||
}
|
||||
|
||||
- (void)headerViewJump2ChatViewController:(TUISearchResultCellModel *)cellModel {
|
||||
if ([cellModel.context isKindOfClass:V2TIMMessage.class]) {
|
||||
V2TIMMessage *message = cellModel.context;
|
||||
NSString *title = message.userID;
|
||||
if (self.headerConversationShowName.length > 0) {
|
||||
title = self.headerConversationShowName;
|
||||
}
|
||||
NSDictionary *param = @{
|
||||
TUICore_TUIChatObjectFactory_ChatViewController_Title : title ?: @"",
|
||||
TUICore_TUIChatObjectFactory_ChatViewController_UserID : message.userID ?: @"",
|
||||
TUICore_TUIChatObjectFactory_ChatViewController_GroupID : message.groupID ?: @"",
|
||||
TUICore_TUIChatObjectFactory_ChatViewController_AvatarImage : self.headerConversationAvatar ?: [[UIImage alloc] init],
|
||||
TUICore_TUIChatObjectFactory_ChatViewController_AvatarUrl : self.headerConversationURL ?: @"",
|
||||
};
|
||||
[self.navigationController pushViewController:TUICore_TUIChatObjectFactory_ChatViewController_Minimalist param:param forResult:nil];
|
||||
} else if (([cellModel.context isKindOfClass:NSDictionary.class])) {
|
||||
NSDictionary *convInfo = cellModel.context;
|
||||
V2TIMConversation *conversation = convInfo[kSearchChatHistoryConverationInfo];
|
||||
NSString *title = cellModel.title ?: cellModel.titleAttributeString.string;
|
||||
if (self.headerConversationShowName.length > 0) {
|
||||
title = self.headerConversationShowName;
|
||||
}
|
||||
NSDictionary *param = @{
|
||||
TUICore_TUIChatObjectFactory_ChatViewController_Title : title ?: @"",
|
||||
TUICore_TUIChatObjectFactory_ChatViewController_UserID : conversation.userID ?: @"",
|
||||
TUICore_TUIChatObjectFactory_ChatViewController_GroupID : conversation.groupID ?: @"",
|
||||
TUICore_TUIChatObjectFactory_ChatViewController_AvatarImage : self.headerConversationAvatar ?: [[UIImage alloc] init],
|
||||
TUICore_TUIChatObjectFactory_ChatViewController_AvatarUrl : self.headerConversationURL ?: @"",
|
||||
};
|
||||
[self.navigationController pushViewController:TUICore_TUIChatObjectFactory_ChatViewController_Minimalist param:param forResult:nil];
|
||||
}
|
||||
}
|
||||
- (void)onSelectModel:(TUISearchResultCellModel *)cellModel module:(TUISearchResultModule)module {
|
||||
[self.searchBar endEditing:YES];
|
||||
if (module == TUISearchResultModuleChatHistory) {
|
||||
if (![cellModel.context isKindOfClass:NSDictionary.class]) {
|
||||
if ([cellModel.context isKindOfClass:V2TIMMessage.class]) {
|
||||
V2TIMMessage *message = cellModel.context;
|
||||
NSString *title = message.userID;
|
||||
if (cellModel.title.length > 0) {
|
||||
title = cellModel.title;
|
||||
}
|
||||
NSDictionary *param = @{
|
||||
TUICore_TUIChatObjectFactory_ChatViewController_Title : title ?: @"",
|
||||
TUICore_TUIChatObjectFactory_ChatViewController_UserID : message.userID ?: @"",
|
||||
TUICore_TUIChatObjectFactory_ChatViewController_GroupID : message.groupID ?: @"",
|
||||
TUICore_TUIChatObjectFactory_ChatViewController_AvatarImage : cellModel.avatarImage ?: [[UIImage alloc] init],
|
||||
TUICore_TUIChatObjectFactory_ChatViewController_HighlightKeyword : self.searchBar.searchBar.text ?: @"",
|
||||
TUICore_TUIChatObjectFactory_ChatViewController_LocateMessage : message,
|
||||
};
|
||||
[self.navigationController pushViewController:TUICore_TUIChatObjectFactory_ChatViewController_Minimalist param:param forResult:nil];
|
||||
return;
|
||||
}
|
||||
return;
|
||||
}
|
||||
NSDictionary *convInfo = cellModel.context;
|
||||
NSString *conversationId = convInfo[kSearchChatHistoryConversationId];
|
||||
V2TIMConversation *conversation = convInfo[kSearchChatHistoryConverationInfo];
|
||||
NSArray *msgs = convInfo[kSearchChatHistoryConversationMsgs];
|
||||
|
||||
NSMutableArray *results = [NSMutableArray array];
|
||||
for (V2TIMMessage *message in msgs) {
|
||||
TUISearchResultCellModel *model = [[TUISearchResultCellModel alloc] init];
|
||||
model.title = message.nickName ?: message.sender;
|
||||
NSString *desc = [TUISearchDataProvider matchedTextForMessage:message withKey:self.searchBar.searchBar.text];
|
||||
model.detailsAttributeString = [TUISearchDataProvider attributeStringWithText:desc key:self.searchBar.searchBar.text];
|
||||
model.avatarUrl = message.faceURL;
|
||||
model.groupType = conversation.groupID;
|
||||
model.avatarImage = conversation.type == V2TIM_C2C ? DefaultAvatarImage : DefaultGroupAvatarImageByGroupType(conversation.groupType);
|
||||
model.context = message;
|
||||
[results addObject:model];
|
||||
}
|
||||
TUISearchResultListController_Minimalist *vc =
|
||||
[[TUISearchResultListController_Minimalist alloc] initWithResults:results
|
||||
keyword:self.searchBar.searchBar.text
|
||||
module:module
|
||||
param:@{TUISearchChatHistoryParamKeyConversationId : conversationId}];
|
||||
|
||||
vc.headerConversationAvatar = cellModel.avatarImage;
|
||||
vc.headerConversationShowName = cellModel.title;
|
||||
[self.navigationController pushViewController:vc animated:YES];
|
||||
return;
|
||||
}
|
||||
|
||||
NSDictionary *param = nil;
|
||||
NSString *title = cellModel.title ?: cellModel.titleAttributeString.string;
|
||||
if (module == TUISearchResultModuleContact && [cellModel.context isKindOfClass:V2TIMFriendInfo.class]) {
|
||||
V2TIMFriendInfo *friend = cellModel.context;
|
||||
param = @{
|
||||
TUICore_TUIChatObjectFactory_ChatViewController_Title : title ?: @"",
|
||||
TUICore_TUIChatObjectFactory_ChatViewController_UserID : friend.userID ?: @"",
|
||||
TUICore_TUIChatObjectFactory_ChatViewController_AvatarImage : cellModel.avatarImage ?: [UIImage new],
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
if (module == TUISearchResultModuleGroup && [cellModel.context isKindOfClass:V2TIMGroupInfo.class]) {
|
||||
V2TIMGroupInfo *group = cellModel.context;
|
||||
param = @{
|
||||
TUICore_TUIChatObjectFactory_ChatViewController_Title : title ?: @"",
|
||||
TUICore_TUIChatObjectFactory_ChatViewController_GroupID : group.groupID ?: @"",
|
||||
TUICore_TUIChatObjectFactory_ChatViewController_AvatarImage : cellModel.avatarImage ?: [UIImage new],
|
||||
};
|
||||
}
|
||||
[self.navigationController pushViewController:TUICore_TUIChatObjectFactory_ChatViewController_Minimalist param:param forResult:nil];
|
||||
}
|
||||
|
||||
#pragma mark - TUISearchBarDelegate
|
||||
- (void)searchBarDidCancelClicked:(TUISearchBar_Minimalist *)searchBar {
|
||||
[self dismissViewControllerAnimated:NO completion:nil];
|
||||
}
|
||||
|
||||
- (void)searchBar:(TUISearchBar_Minimalist *)searchBar searchText:(NSString *)key {
|
||||
[self.results removeAllObjects];
|
||||
self.allowPageRequest = YES;
|
||||
self.pageIndex = 0;
|
||||
NSMutableDictionary *param = [NSMutableDictionary dictionaryWithDictionary:self.param];
|
||||
param[TUISearchChatHistoryParamKeyPage] = @(self.pageIndex);
|
||||
self.param = [NSDictionary dictionaryWithDictionary:param];
|
||||
|
||||
[self.dataProvider searchForKeyword:key forModules:self.module param:self.param];
|
||||
}
|
||||
|
||||
#pragma mark - TUISearchResultDelegate
|
||||
- (void)onSearchResults:(NSDictionary<NSNumber *, NSArray<TUISearchResultCellModel *> *> *)results forModules:(TUISearchResultModule)modules {
|
||||
NSArray *arrayM = [results objectForKey:@(modules)];
|
||||
if (arrayM == nil) {
|
||||
arrayM = @[];
|
||||
}
|
||||
self.noDataEmptyView.hidden = YES;
|
||||
if ((arrayM.count == 0) && (self.results.count == 0)) {
|
||||
self.noDataEmptyView.hidden = NO;
|
||||
if (self.searchBar.searchBar.text.length == 0) {
|
||||
self.noDataEmptyView.hidden = YES;
|
||||
}
|
||||
}
|
||||
|
||||
[self.results addObjectsFromArray:arrayM];
|
||||
[self.tableView reloadData];
|
||||
|
||||
self.allowPageRequest = (arrayM.count >= TUISearchDefaultPageSize);
|
||||
self.pageIndex = (arrayM.count < TUISearchDefaultPageSize) ? self.pageIndex : self.pageIndex + 1;
|
||||
|
||||
if (!self.allowPageRequest) {
|
||||
}
|
||||
}
|
||||
|
||||
- (void)onSearchError:(NSString *)errMsg {
|
||||
NSLog(@"search error: %@", errMsg);
|
||||
}
|
||||
|
||||
- (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
|
||||
@@ -0,0 +1,17 @@
|
||||
//
|
||||
// TUISearchViewController.h
|
||||
// Pods
|
||||
//
|
||||
// Created by harvy on 2020/12/24.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface TUISearchViewController_Minimalist : UIViewController
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,327 @@
|
||||
//
|
||||
// TUISearchViewController_Minimalist.m
|
||||
// Pods
|
||||
//
|
||||
// Created by harvy on 2020/12/24.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
//
|
||||
|
||||
#import "TUISearchViewController_Minimalist.h"
|
||||
#import <TIMCommon/TIMDefine.h>
|
||||
#import <TUICore/TUICore.h>
|
||||
#import "TUISearchBar_Minimalist.h"
|
||||
#import "TUISearchDataProvider.h"
|
||||
#import "TUISearchEmptyView_Minimalist.h"
|
||||
#import "TUISearchResultCellModel.h"
|
||||
#import "TUISearchResultCell_Minimalist.h"
|
||||
#import "TUISearchResultHeaderFooterView_Minimalist.h"
|
||||
#import "TUISearchResultListController_Minimalist.h"
|
||||
|
||||
@interface TUISearchViewController_Minimalist () <UITableViewDelegate, UITableViewDataSource, TUISearchBarDelegate_Minimalist, TUISearchResultDelegate>
|
||||
|
||||
@property(nonatomic, strong) TUISearchBar_Minimalist *searchBar;
|
||||
@property(nonatomic, strong) UITableView *tableView;
|
||||
@property(nonatomic, strong) TUISearchDataProvider *dataProvider;
|
||||
@property(nonatomic, strong) TUISearchEmptyView_Minimalist *noDataEmptyView;
|
||||
@end
|
||||
|
||||
@implementation TUISearchViewController_Minimalist
|
||||
|
||||
static NSString *const Id = @"cell";
|
||||
static NSString *const HFId = @"HFId";
|
||||
|
||||
- (void)viewDidLoad {
|
||||
[super viewDidLoad];
|
||||
_dataProvider = [[TUISearchDataProvider alloc] init];
|
||||
_dataProvider.delegate = self;
|
||||
[self setupViews];
|
||||
[TUITool addUnsupportNotificationInVC:self];
|
||||
}
|
||||
|
||||
- (void)dealloc {
|
||||
NSLog(@"%s dealloc", __FUNCTION__);
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:self];
|
||||
}
|
||||
|
||||
- (void)setupViews {
|
||||
self.view.backgroundColor = [UIColor whiteColor];
|
||||
_searchBar = [[TUISearchBar_Minimalist alloc] init];
|
||||
[_searchBar setEntrance:NO];
|
||||
_searchBar.delegate = self;
|
||||
self.navigationItem.titleView = _searchBar;
|
||||
|
||||
_tableView = [[UITableView alloc] initWithFrame:self.view.bounds style:UITableViewStyleGrouped];
|
||||
_tableView.delegate = self;
|
||||
_tableView.dataSource = self;
|
||||
_tableView.backgroundColor = [UIColor whiteColor];
|
||||
_tableView.separatorStyle = UITableViewCellSeparatorStyleNone;
|
||||
_tableView.rowHeight = kScale390(72);
|
||||
[_tableView registerClass:TUISearchResultCell_Minimalist.class forCellReuseIdentifier:Id];
|
||||
[_tableView registerClass:TUISearchResultHeaderFooterView_Minimalist.class forHeaderFooterViewReuseIdentifier:HFId];
|
||||
[self.view addSubview:_tableView];
|
||||
|
||||
self.noDataEmptyView.frame = CGRectMake(0, kScale390(42), self.view.bounds.size.width - 20, 200);
|
||||
[self.tableView addSubview:self.noDataEmptyView];
|
||||
|
||||
[_searchBar.searchBar becomeFirstResponder];
|
||||
}
|
||||
|
||||
- (void)viewWillLayoutSubviews {
|
||||
[super viewWillLayoutSubviews];
|
||||
self.tableView.frame = self.view.bounds;
|
||||
self.searchBar.frame = CGRectMake(0, 0, self.view.mm_w, 44);
|
||||
}
|
||||
|
||||
- (UIColor *)navBackColor {
|
||||
return [UIColor whiteColor];
|
||||
}
|
||||
|
||||
- (void)viewWillAppear:(BOOL)animated {
|
||||
[super viewWillAppear:animated];
|
||||
if (@available(iOS 15.0, *)) {
|
||||
UINavigationBarAppearance *appearance = [UINavigationBarAppearance new];
|
||||
[appearance configureWithDefaultBackground];
|
||||
appearance.shadowColor = nil;
|
||||
appearance.backgroundEffect = nil;
|
||||
appearance.backgroundColor = [self navBackColor];
|
||||
UINavigationBar *navigationBar = self.navigationController.navigationBar;
|
||||
navigationBar.backgroundColor = [self navBackColor];
|
||||
navigationBar.barTintColor = [self navBackColor];
|
||||
navigationBar.shadowImage = [UIImage new];
|
||||
navigationBar.standardAppearance = appearance;
|
||||
navigationBar.scrollEdgeAppearance = appearance;
|
||||
} else {
|
||||
UINavigationBar *navigationBar = self.navigationController.navigationBar;
|
||||
navigationBar.backgroundColor = [self navBackColor];
|
||||
navigationBar.barTintColor = [self navBackColor];
|
||||
navigationBar.shadowImage = [UIImage new];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)viewWillDisappear:(BOOL)animated {
|
||||
[super viewWillDisappear:animated];
|
||||
}
|
||||
|
||||
- (TUISearchEmptyView_Minimalist *)noDataEmptyView {
|
||||
if (_noDataEmptyView == nil) {
|
||||
_noDataEmptyView = [[TUISearchEmptyView_Minimalist alloc] initWithImage:TUISearchBundleThemeImage(@"", @"search_not_found_icon")
|
||||
Text:TIMCommonLocalizableString(TUIKitSearchNoResultLists)];
|
||||
_noDataEmptyView.hidden = YES;
|
||||
}
|
||||
return _noDataEmptyView;
|
||||
}
|
||||
|
||||
#pragma mark - TUISearchResultDelegate
|
||||
- (void)onSearchError:(NSString *)errMsg {
|
||||
}
|
||||
|
||||
- (void)onSearchResults:(NSDictionary<NSNumber *, NSArray<TUISearchResultCellModel *> *> *)results forModules:(TUISearchResultModule)modules {
|
||||
self.noDataEmptyView.hidden = YES;
|
||||
if (!results || results.allKeys.count == 0) {
|
||||
self.noDataEmptyView.hidden = NO;
|
||||
if (self.searchBar.searchBar.text.length == 0) {
|
||||
self.noDataEmptyView.hidden = YES;
|
||||
}
|
||||
}
|
||||
[self.tableView reloadData];
|
||||
}
|
||||
|
||||
#pragma mark - UITableViewDataSource、TUITableViewDelegate
|
||||
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
|
||||
return self.dataProvider.resultSet.allKeys.count;
|
||||
}
|
||||
|
||||
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
|
||||
NSArray *results = [self resultForSection:section];
|
||||
return results.count > kMaxNumOfPerModule ? kMaxNumOfPerModule : results.count;
|
||||
}
|
||||
|
||||
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
|
||||
TUISearchResultCell_Minimalist *cell = [tableView dequeueReusableCellWithIdentifier:Id forIndexPath:indexPath];
|
||||
TUISearchResultModule module = TUISearchResultModuleContact;
|
||||
NSArray *results = [self resultForSection:indexPath.section module:&module];
|
||||
if (results.count <= indexPath.row) {
|
||||
return cell;
|
||||
}
|
||||
TUISearchResultCellModel *model = results[indexPath.row];
|
||||
model.avatarType = TAvatarTypeRadiusCorner;
|
||||
[cell fillWithData:model];
|
||||
return cell;
|
||||
}
|
||||
|
||||
- (UIView *)tableView:(UITableView *)tableView viewForFooterInSection:(NSInteger)section {
|
||||
return [UIView new];
|
||||
}
|
||||
|
||||
- (CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section {
|
||||
return 20;
|
||||
}
|
||||
|
||||
- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section {
|
||||
TUISearchResultModule module = TUISearchResultModuleContact;
|
||||
[self resultForSection:section module:&module];
|
||||
TUISearchResultHeaderFooterView_Minimalist *headerView = [tableView dequeueReusableHeaderFooterViewWithIdentifier:HFId];
|
||||
headerView.isFooter = NO;
|
||||
headerView.showMoreBtn = YES;
|
||||
headerView.title = titleForModule(module, YES);
|
||||
__weak typeof(self) weakSelf = self;
|
||||
NSArray *results = [self resultForSection:section module:&module];
|
||||
headerView.onTap = ^{
|
||||
[weakSelf onSelectMoreModule:module results:results];
|
||||
};
|
||||
return headerView;
|
||||
}
|
||||
|
||||
- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section {
|
||||
return 30;
|
||||
}
|
||||
|
||||
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
|
||||
[self.view endEditing:YES];
|
||||
[self.searchBar endEditing:YES];
|
||||
}
|
||||
|
||||
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
|
||||
[tableView deselectRowAtIndexPath:indexPath animated:NO];
|
||||
TUISearchResultModule module = TUISearchResultModuleContact;
|
||||
NSArray *results = [self resultForSection:indexPath.section module:&module];
|
||||
if (results.count <= indexPath.row) {
|
||||
return;
|
||||
}
|
||||
TUISearchResultCellModel *cellModel = results[indexPath.row];
|
||||
TUISearchResultCell_Minimalist *cell = [tableView cellForRowAtIndexPath:indexPath];
|
||||
cellModel.avatarImage = cell.avatarView.image;
|
||||
cellModel.title = cell.title_label.text;
|
||||
[self onSelectModel:cellModel module:module];
|
||||
}
|
||||
|
||||
#pragma mark - action
|
||||
- (void)onSelectModel:(TUISearchResultCellModel *)cellModel module:(TUISearchResultModule)module {
|
||||
[self.searchBar endEditing:YES];
|
||||
|
||||
if (module == TUISearchResultModuleChatHistory) {
|
||||
if (![cellModel.context isKindOfClass:NSDictionary.class]) {
|
||||
return;
|
||||
}
|
||||
NSDictionary *convInfo = cellModel.context;
|
||||
NSString *conversationId = convInfo[kSearchChatHistoryConversationId];
|
||||
V2TIMConversation *conversation = convInfo[kSearchChatHistoryConverationInfo];
|
||||
NSArray *msgs = convInfo[kSearchChatHistoryConversationMsgs];
|
||||
|
||||
NSMutableArray *results = [NSMutableArray array];
|
||||
for (V2TIMMessage *message in msgs) {
|
||||
TUISearchResultCellModel *model = [[TUISearchResultCellModel alloc] init];
|
||||
model.title = message.nickName ?: message.sender;
|
||||
NSString *desc = [TUISearchDataProvider matchedTextForMessage:message withKey:self.searchBar.searchBar.text];
|
||||
model.detailsAttributeString = [TUISearchDataProvider attributeStringWithText:desc key:self.searchBar.searchBar.text];
|
||||
model.avatarUrl = message.faceURL;
|
||||
model.groupType = conversation.groupType;
|
||||
model.avatarImage = conversation.type == V2TIM_C2C ? DefaultAvatarImage : DefaultGroupAvatarImageByGroupType(conversation.groupType);
|
||||
;
|
||||
model.context = message;
|
||||
[results addObject:model];
|
||||
}
|
||||
TUISearchResultListController_Minimalist *vc =
|
||||
[[TUISearchResultListController_Minimalist alloc] initWithResults:results
|
||||
keyword:self.searchBar.searchBar.text
|
||||
module:module
|
||||
param:@{TUISearchChatHistoryParamKeyConversationId : conversationId}];
|
||||
vc.headerConversationAvatar = cellModel.avatarImage;
|
||||
vc.headerConversationShowName = cellModel.title;
|
||||
|
||||
[self.navigationController pushViewController:vc animated:YES];
|
||||
return;
|
||||
}
|
||||
|
||||
NSDictionary *param = nil;
|
||||
|
||||
NSString *title = cellModel.title ?: cellModel.titleAttributeString.string;
|
||||
if (module == TUISearchResultModuleContact && [cellModel.context isKindOfClass:V2TIMFriendInfo.class]) {
|
||||
V2TIMFriendInfo *friend = cellModel.context;
|
||||
param = @{
|
||||
TUICore_TUIChatObjectFactory_ChatViewController_Title : title ?: @"",
|
||||
TUICore_TUIChatObjectFactory_ChatViewController_UserID : friend.userID ?: @"",
|
||||
TUICore_TUIChatObjectFactory_ChatViewController_AvatarImage : cellModel.avatarImage ?: [UIImage new],
|
||||
};
|
||||
}
|
||||
|
||||
if (module == TUISearchResultModuleGroup && [cellModel.context isKindOfClass:V2TIMGroupInfo.class]) {
|
||||
V2TIMGroupInfo *group = cellModel.context;
|
||||
param = @{
|
||||
TUICore_TUIChatObjectFactory_ChatViewController_Title : title ?: @"",
|
||||
TUICore_TUIChatObjectFactory_ChatViewController_GroupID : group.groupID ?: @"",
|
||||
TUICore_TUIChatObjectFactory_ChatViewController_AvatarImage : cellModel.avatarImage ?: [UIImage new],
|
||||
};
|
||||
}
|
||||
[self.navigationController pushViewController:TUICore_TUIChatObjectFactory_ChatViewController_Minimalist param:param forResult:nil];
|
||||
}
|
||||
|
||||
- (void)onSelectMoreModule:(TUISearchResultModule)module results:(NSArray<TUISearchResultCellModel *> *)results {
|
||||
TUISearchResultListController_Minimalist *vc = [[TUISearchResultListController_Minimalist alloc] initWithResults:results
|
||||
keyword:self.searchBar.searchBar.text
|
||||
module:module
|
||||
param:nil];
|
||||
[self.navigationController pushViewController:vc animated:YES];
|
||||
}
|
||||
|
||||
#pragma mark - viewmodel
|
||||
- (NSArray *)resultForSection:(NSInteger)section {
|
||||
return [self resultForSection:section module:nil];
|
||||
}
|
||||
|
||||
- (NSArray *)resultForSection:(NSInteger)section module:(TUISearchResultModule *)module {
|
||||
NSArray *keys = self.dataProvider.resultSet.allKeys;
|
||||
if (section >= keys.count) {
|
||||
return 0;
|
||||
}
|
||||
keys = [keys sortedArrayUsingComparator:^NSComparisonResult(NSNumber *obj1, NSNumber *obj2) {
|
||||
return [obj1 intValue] < [obj2 intValue] ? NSOrderedAscending : NSOrderedDescending;
|
||||
}];
|
||||
|
||||
NSNumber *key = keys[section];
|
||||
if (module) {
|
||||
*module = (TUISearchResultModule)[key integerValue];
|
||||
}
|
||||
return [self.dataProvider.resultSet objectForKey:key];
|
||||
}
|
||||
|
||||
#pragma mark - TUISearchBarDelegate
|
||||
- (void)searchBarDidCancelClicked:(TUISearchBar_Minimalist *)searchBar {
|
||||
[self dismissViewControllerAnimated:NO completion:nil];
|
||||
}
|
||||
|
||||
- (void)searchBar:(TUISearchBar_Minimalist *)searchBar searchText:(NSString *)key {
|
||||
[self.dataProvider searchForKeyword:key forModules:TUISearchResultModuleAll param:nil];
|
||||
}
|
||||
|
||||
- (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
|
||||
|
||||
@interface IUSearchView_Minimalist : UIView
|
||||
@property(nonatomic, strong) UIView *view;
|
||||
@end
|
||||
|
||||
@implementation IUSearchView_Minimalist
|
||||
|
||||
- (instancetype)init {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
self.view = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 10, 10)];
|
||||
[self addSubview:self.view];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
@end
|
||||