2026-02-03 16:54:38 +08:00
|
|
|
|
//
|
|
|
|
|
|
// KBAIPersonaSidebarView.m
|
|
|
|
|
|
// keyBoard
|
|
|
|
|
|
//
|
|
|
|
|
|
// Created by Codex on 2026/2/3.
|
|
|
|
|
|
//
|
|
|
|
|
|
|
|
|
|
|
|
#import "KBAIPersonaSidebarView.h"
|
|
|
|
|
|
#import "KBPersonaModel.h"
|
|
|
|
|
|
#import <Masonry/Masonry.h>
|
|
|
|
|
|
#import <SDWebImage/SDWebImage.h>
|
|
|
|
|
|
#import <MJRefresh/MJRefresh.h>
|
|
|
|
|
|
|
|
|
|
|
|
#pragma mark - Cell
|
|
|
|
|
|
|
|
|
|
|
|
@interface KBAIPersonaSidebarCell : UITableViewCell
|
|
|
|
|
|
|
|
|
|
|
|
@property (nonatomic, strong) UIImageView *avatarImageView;
|
|
|
|
|
|
@property (nonatomic, strong) UILabel *nameLabel;
|
|
|
|
|
|
@property (nonatomic, strong) UILabel *descLabel;
|
|
|
|
|
|
@property (nonatomic, strong) UIImageView *checkImageView;
|
|
|
|
|
|
@property (nonatomic, strong) UIImageView *arrowImageView;
|
|
|
|
|
|
@property (nonatomic, strong) UIView *lineView;
|
|
|
|
|
|
|
|
|
|
|
|
- (void)configureWithPersona:(KBPersonaModel *)persona selected:(BOOL)selected;
|
|
|
|
|
|
|
|
|
|
|
|
@end
|
|
|
|
|
|
|
|
|
|
|
|
@implementation KBAIPersonaSidebarCell
|
|
|
|
|
|
|
|
|
|
|
|
- (instancetype)initWithStyle:(UITableViewCellStyle)style
|
|
|
|
|
|
reuseIdentifier:(NSString *)reuseIdentifier {
|
|
|
|
|
|
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
|
|
|
|
|
|
if (self) {
|
|
|
|
|
|
self.backgroundColor = [UIColor clearColor];
|
|
|
|
|
|
self.selectionStyle = UITableViewCellSelectionStyleNone;
|
|
|
|
|
|
[self setupUI];
|
|
|
|
|
|
}
|
|
|
|
|
|
return self;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
- (void)setupUI {
|
|
|
|
|
|
[self.contentView addSubview:self.avatarImageView];
|
|
|
|
|
|
[self.contentView addSubview:self.nameLabel];
|
|
|
|
|
|
[self.contentView addSubview:self.descLabel];
|
|
|
|
|
|
[self.contentView addSubview:self.checkImageView];
|
|
|
|
|
|
[self.contentView addSubview:self.arrowImageView];
|
|
|
|
|
|
[self.contentView addSubview:self.lineView];
|
|
|
|
|
|
|
|
|
|
|
|
[self.avatarImageView mas_makeConstraints:^(MASConstraintMaker *make) {
|
|
|
|
|
|
make.left.equalTo(self.contentView).offset(16);
|
|
|
|
|
|
make.centerY.equalTo(self.contentView);
|
|
|
|
|
|
make.width.height.mas_equalTo(44);
|
|
|
|
|
|
}];
|
|
|
|
|
|
|
|
|
|
|
|
[self.nameLabel mas_makeConstraints:^(MASConstraintMaker *make) {
|
|
|
|
|
|
make.left.equalTo(self.avatarImageView.mas_right).offset(12);
|
|
|
|
|
|
make.top.equalTo(self.avatarImageView).offset(2);
|
|
|
|
|
|
make.right.lessThanOrEqualTo(self.contentView).offset(-60);
|
|
|
|
|
|
}];
|
|
|
|
|
|
|
|
|
|
|
|
[self.descLabel mas_makeConstraints:^(MASConstraintMaker *make) {
|
|
|
|
|
|
make.left.equalTo(self.nameLabel);
|
|
|
|
|
|
make.top.equalTo(self.nameLabel.mas_bottom).offset(4);
|
|
|
|
|
|
make.right.lessThanOrEqualTo(self.contentView).offset(-60);
|
|
|
|
|
|
}];
|
|
|
|
|
|
|
|
|
|
|
|
[self.checkImageView mas_makeConstraints:^(MASConstraintMaker *make) {
|
|
|
|
|
|
make.centerY.equalTo(self.contentView);
|
|
|
|
|
|
make.right.equalTo(self.contentView).offset(-16);
|
|
|
|
|
|
make.width.height.mas_equalTo(22);
|
|
|
|
|
|
}];
|
|
|
|
|
|
|
|
|
|
|
|
[self.arrowImageView mas_makeConstraints:^(MASConstraintMaker *make) {
|
|
|
|
|
|
make.centerY.equalTo(self.contentView);
|
|
|
|
|
|
make.right.equalTo(self.contentView).offset(-18);
|
2026-02-03 18:03:21 +08:00
|
|
|
|
make.width.mas_equalTo(6);
|
|
|
|
|
|
make.height.mas_equalTo(8);
|
|
|
|
|
|
|
2026-02-03 16:54:38 +08:00
|
|
|
|
}];
|
|
|
|
|
|
|
|
|
|
|
|
[self.lineView mas_makeConstraints:^(MASConstraintMaker *make) {
|
|
|
|
|
|
make.left.equalTo(self.nameLabel);
|
|
|
|
|
|
make.right.equalTo(self.contentView).offset(-16);
|
|
|
|
|
|
make.bottom.equalTo(self.contentView);
|
|
|
|
|
|
make.height.mas_equalTo(0.5);
|
|
|
|
|
|
}];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
- (void)configureWithPersona:(KBPersonaModel *)persona selected:(BOOL)selected {
|
|
|
|
|
|
if (!persona) {
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
[self.avatarImageView sd_setImageWithURL:[NSURL URLWithString:persona.avatarUrl]
|
|
|
|
|
|
placeholderImage:[UIImage imageNamed:@"placeholder_avatar"]];
|
|
|
|
|
|
self.nameLabel.text = persona.name ?: @"";
|
|
|
|
|
|
NSString *desc = persona.shortDesc.length > 0 ? persona.shortDesc : persona.introText;
|
|
|
|
|
|
self.descLabel.text = desc ?: @"";
|
|
|
|
|
|
self.checkImageView.hidden = !selected;
|
|
|
|
|
|
self.arrowImageView.hidden = selected;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#pragma mark - Lazy
|
|
|
|
|
|
|
|
|
|
|
|
- (UIImageView *)avatarImageView {
|
|
|
|
|
|
if (!_avatarImageView) {
|
|
|
|
|
|
_avatarImageView = [[UIImageView alloc] init];
|
|
|
|
|
|
_avatarImageView.contentMode = UIViewContentModeScaleAspectFill;
|
|
|
|
|
|
_avatarImageView.layer.cornerRadius = 22;
|
|
|
|
|
|
_avatarImageView.clipsToBounds = YES;
|
|
|
|
|
|
_avatarImageView.layer.borderWidth = 1;
|
|
|
|
|
|
_avatarImageView.layer.borderColor = [[UIColor whiteColor] colorWithAlphaComponent:0.6].CGColor;
|
|
|
|
|
|
}
|
|
|
|
|
|
return _avatarImageView;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
- (UILabel *)nameLabel {
|
|
|
|
|
|
if (!_nameLabel) {
|
|
|
|
|
|
_nameLabel = [[UILabel alloc] init];
|
|
|
|
|
|
_nameLabel.font = [UIFont systemFontOfSize:16 weight:UIFontWeightMedium];
|
|
|
|
|
|
_nameLabel.textColor = [UIColor whiteColor];
|
|
|
|
|
|
}
|
|
|
|
|
|
return _nameLabel;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
- (UILabel *)descLabel {
|
|
|
|
|
|
if (!_descLabel) {
|
|
|
|
|
|
_descLabel = [[UILabel alloc] init];
|
|
|
|
|
|
_descLabel.font = [UIFont systemFontOfSize:12];
|
|
|
|
|
|
_descLabel.textColor = [[UIColor whiteColor] colorWithAlphaComponent:0.7];
|
|
|
|
|
|
_descLabel.lineBreakMode = NSLineBreakByTruncatingTail;
|
|
|
|
|
|
}
|
|
|
|
|
|
return _descLabel;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
- (UIImageView *)checkImageView {
|
|
|
|
|
|
if (!_checkImageView) {
|
|
|
|
|
|
_checkImageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"ai_role_sel"]];
|
|
|
|
|
|
}
|
|
|
|
|
|
return _checkImageView;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
- (UIImageView *)arrowImageView {
|
|
|
|
|
|
if (!_arrowImageView) {
|
|
|
|
|
|
_arrowImageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"right_arrow_icon"]];
|
|
|
|
|
|
}
|
|
|
|
|
|
return _arrowImageView;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
- (UIView *)lineView {
|
|
|
|
|
|
if (!_lineView) {
|
|
|
|
|
|
_lineView = [[UIView alloc] init];
|
|
|
|
|
|
_lineView.backgroundColor = [[UIColor whiteColor] colorWithAlphaComponent:0.12];
|
|
|
|
|
|
}
|
|
|
|
|
|
return _lineView;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@end
|
|
|
|
|
|
|
|
|
|
|
|
#pragma mark - View
|
|
|
|
|
|
|
|
|
|
|
|
@interface KBAIPersonaSidebarView () <UITableViewDelegate, UITableViewDataSource, UITextFieldDelegate>
|
|
|
|
|
|
|
|
|
|
|
|
@property (nonatomic, strong) UIVisualEffectView *blurView;
|
|
|
|
|
|
@property (nonatomic, strong) UIView *contentView;
|
|
|
|
|
|
@property (nonatomic, strong) UIView *searchContainer;
|
|
|
|
|
|
@property (nonatomic, strong) UIImageView *searchIconView;
|
|
|
|
|
|
@property (nonatomic, strong) UITextField *searchField;
|
2026-02-03 17:00:42 +08:00
|
|
|
|
@property (nonatomic, strong) BaseTableView *tableView;
|
2026-02-03 18:03:21 +08:00
|
|
|
|
@property (nonatomic, strong) UIView *searchResultContainer;
|
|
|
|
|
|
@property (nonatomic, strong) BaseTableView *searchResultTableView;
|
2026-02-03 16:54:38 +08:00
|
|
|
|
|
|
|
|
|
|
@property (nonatomic, strong) NSArray<KBPersonaModel *> *personas;
|
|
|
|
|
|
@property (nonatomic, strong) NSArray<KBPersonaModel *> *displayPersonas;
|
2026-02-03 18:03:21 +08:00
|
|
|
|
@property (nonatomic, strong) NSArray<KBPersonaModel *> *searchResults;
|
|
|
|
|
|
@property (nonatomic, assign) BOOL isShowingSearchResults;
|
2026-02-03 16:54:38 +08:00
|
|
|
|
@property (nonatomic, assign) NSInteger currentPage;
|
|
|
|
|
|
@property (nonatomic, assign) BOOL hasMore;
|
|
|
|
|
|
|
|
|
|
|
|
@end
|
|
|
|
|
|
|
|
|
|
|
|
@implementation KBAIPersonaSidebarView
|
|
|
|
|
|
|
|
|
|
|
|
- (instancetype)initWithFrame:(CGRect)frame {
|
|
|
|
|
|
self = [super initWithFrame:frame];
|
|
|
|
|
|
if (self) {
|
|
|
|
|
|
[self setupUI];
|
|
|
|
|
|
}
|
|
|
|
|
|
return self;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
- (void)setupUI {
|
|
|
|
|
|
self.backgroundColor = [UIColor clearColor];
|
|
|
|
|
|
|
|
|
|
|
|
[self addSubview:self.blurView];
|
|
|
|
|
|
[self addSubview:self.contentView];
|
|
|
|
|
|
|
|
|
|
|
|
[self.contentView addSubview:self.searchContainer];
|
|
|
|
|
|
[self.searchContainer addSubview:self.searchIconView];
|
|
|
|
|
|
[self.searchContainer addSubview:self.searchField];
|
|
|
|
|
|
[self.contentView addSubview:self.tableView];
|
2026-02-03 18:03:21 +08:00
|
|
|
|
[self.contentView addSubview:self.searchResultContainer];
|
|
|
|
|
|
[self.searchResultContainer addSubview:self.searchResultTableView];
|
2026-02-03 16:54:38 +08:00
|
|
|
|
|
|
|
|
|
|
[self.blurView mas_makeConstraints:^(MASConstraintMaker *make) {
|
|
|
|
|
|
make.edges.equalTo(self);
|
|
|
|
|
|
}];
|
|
|
|
|
|
|
|
|
|
|
|
[self.contentView mas_makeConstraints:^(MASConstraintMaker *make) {
|
|
|
|
|
|
make.edges.equalTo(self);
|
|
|
|
|
|
}];
|
|
|
|
|
|
|
|
|
|
|
|
[self.searchContainer mas_makeConstraints:^(MASConstraintMaker *make) {
|
2026-02-03 17:00:42 +08:00
|
|
|
|
make.top.equalTo(self.contentView).offset(KB_STATUSBAR_HEIGHT + 20);
|
2026-02-03 16:54:38 +08:00
|
|
|
|
make.left.equalTo(self.contentView).offset(16);
|
|
|
|
|
|
make.right.equalTo(self.contentView).offset(-16);
|
|
|
|
|
|
make.height.mas_equalTo(36);
|
|
|
|
|
|
}];
|
|
|
|
|
|
|
|
|
|
|
|
[self.searchIconView mas_makeConstraints:^(MASConstraintMaker *make) {
|
|
|
|
|
|
make.left.equalTo(self.searchContainer).offset(12);
|
|
|
|
|
|
make.centerY.equalTo(self.searchContainer);
|
|
|
|
|
|
make.width.height.mas_equalTo(16);
|
|
|
|
|
|
}];
|
|
|
|
|
|
|
|
|
|
|
|
[self.searchField mas_makeConstraints:^(MASConstraintMaker *make) {
|
|
|
|
|
|
make.left.equalTo(self.searchIconView.mas_right).offset(8);
|
|
|
|
|
|
make.right.equalTo(self.searchContainer).offset(-12);
|
|
|
|
|
|
make.centerY.equalTo(self.searchContainer);
|
|
|
|
|
|
make.height.mas_equalTo(28);
|
|
|
|
|
|
}];
|
|
|
|
|
|
|
|
|
|
|
|
[self.tableView mas_makeConstraints:^(MASConstraintMaker *make) {
|
|
|
|
|
|
make.top.equalTo(self.searchContainer.mas_bottom).offset(10);
|
|
|
|
|
|
make.left.right.bottom.equalTo(self.contentView);
|
|
|
|
|
|
}];
|
|
|
|
|
|
|
2026-02-03 18:03:21 +08:00
|
|
|
|
[self.searchResultContainer mas_makeConstraints:^(MASConstraintMaker *make) {
|
|
|
|
|
|
make.top.equalTo(self.searchContainer.mas_bottom).offset(10);
|
|
|
|
|
|
make.left.right.bottom.equalTo(self.contentView);
|
|
|
|
|
|
}];
|
|
|
|
|
|
|
|
|
|
|
|
[self.searchResultTableView mas_makeConstraints:^(MASConstraintMaker *make) {
|
|
|
|
|
|
make.edges.equalTo(self.searchResultContainer);
|
|
|
|
|
|
}];
|
|
|
|
|
|
|
2026-02-03 16:54:38 +08:00
|
|
|
|
__weak typeof(self) weakSelf = self;
|
|
|
|
|
|
MJRefreshAutoNormalFooter *footer = [MJRefreshAutoNormalFooter footerWithRefreshingBlock:^{
|
|
|
|
|
|
__strong typeof(weakSelf) strongSelf = weakSelf;
|
|
|
|
|
|
if (!strongSelf) {
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (!strongSelf.hasMore) {
|
|
|
|
|
|
[strongSelf.tableView.mj_footer endRefreshingWithNoMoreData];
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
strongSelf.currentPage += 1;
|
|
|
|
|
|
if ([strongSelf.delegate respondsToSelector:@selector(personaSidebarView:requestPersonasAtPage:)]) {
|
|
|
|
|
|
[strongSelf.delegate personaSidebarView:strongSelf requestPersonasAtPage:strongSelf.currentPage];
|
|
|
|
|
|
}
|
|
|
|
|
|
}];
|
|
|
|
|
|
footer.stateLabel.hidden = YES;
|
|
|
|
|
|
footer.backgroundColor = [UIColor clearColor];
|
|
|
|
|
|
footer.automaticallyHidden = YES;
|
|
|
|
|
|
self.tableView.mj_footer = footer;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#pragma mark - Public
|
|
|
|
|
|
|
|
|
|
|
|
- (void)requestPersonasIfNeeded {
|
|
|
|
|
|
if (self.personas.count == 0) {
|
|
|
|
|
|
self.currentPage = 1;
|
|
|
|
|
|
self.hasMore = YES;
|
2026-02-03 18:03:21 +08:00
|
|
|
|
NSLog(@"[SidebarSearch] 请求人设数据: page=%ld", (long)self.currentPage);
|
2026-02-03 16:54:38 +08:00
|
|
|
|
if ([self.delegate respondsToSelector:@selector(personaSidebarView:requestPersonasAtPage:)]) {
|
|
|
|
|
|
[self.delegate personaSidebarView:self requestPersonasAtPage:self.currentPage];
|
|
|
|
|
|
}
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
[self applyFilterAndReload];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
- (void)updatePersonas:(NSArray<KBPersonaModel *> *)personas
|
|
|
|
|
|
reset:(BOOL)reset
|
|
|
|
|
|
hasMore:(BOOL)hasMore
|
|
|
|
|
|
currentPage:(NSInteger)currentPage {
|
|
|
|
|
|
self.hasMore = hasMore;
|
|
|
|
|
|
NSInteger safePage = MAX(1, currentPage);
|
|
|
|
|
|
// HomeVC 传入的是全量列表,这里直接替换,避免重复
|
|
|
|
|
|
self.personas = personas ?: @[];
|
|
|
|
|
|
self.currentPage = safePage;
|
2026-02-03 18:03:21 +08:00
|
|
|
|
NSLog(@"[SidebarSearch] 更新人设: count=%ld, page=%ld, hasMore=%@",
|
|
|
|
|
|
(long)self.personas.count, (long)self.currentPage, self.hasMore ? @"YES" : @"NO");
|
2026-02-03 16:54:38 +08:00
|
|
|
|
[self applyFilterAndReload];
|
2026-02-03 18:03:21 +08:00
|
|
|
|
if (self.isShowingSearchResults && self.searchField.text.length > 0) {
|
|
|
|
|
|
[self performSearch];
|
|
|
|
|
|
}
|
2026-02-03 16:54:38 +08:00
|
|
|
|
[self endLoadingMore];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
- (void)updateSelectedPersonaId:(NSInteger)personaId {
|
|
|
|
|
|
self.selectedPersonaId = personaId;
|
|
|
|
|
|
[self.tableView reloadData];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
- (void)endLoadingMore {
|
|
|
|
|
|
if ([self.tableView.mj_footer isRefreshing]) {
|
|
|
|
|
|
if (self.hasMore) {
|
|
|
|
|
|
[self.tableView.mj_footer endRefreshing];
|
|
|
|
|
|
} else {
|
|
|
|
|
|
[self.tableView.mj_footer endRefreshingWithNoMoreData];
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
- (void)resetLoadMore {
|
|
|
|
|
|
[self.tableView.mj_footer resetNoMoreData];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#pragma mark - Search
|
|
|
|
|
|
|
|
|
|
|
|
- (void)searchFieldChanged:(UITextField *)textField {
|
2026-02-03 18:03:21 +08:00
|
|
|
|
NSString *keyword = [textField.text stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
|
|
|
|
|
|
NSLog(@"[SidebarSearch] 输入变化: \"%@\"", keyword);
|
|
|
|
|
|
if (keyword.length == 0) {
|
|
|
|
|
|
[self hideSearchResults];
|
|
|
|
|
|
}
|
2026-02-03 16:54:38 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
- (void)applyFilterAndReload {
|
2026-02-03 18:03:21 +08:00
|
|
|
|
self.displayPersonas = self.personas;
|
|
|
|
|
|
[self.tableView reloadData];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
- (void)performSearch {
|
2026-02-03 16:54:38 +08:00
|
|
|
|
NSString *keyword = [self.searchField.text stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
|
2026-02-03 18:03:21 +08:00
|
|
|
|
NSLog(@"[SidebarSearch] 执行搜索: \"%@\" (total=%ld)", keyword, (long)self.personas.count);
|
2026-02-03 16:54:38 +08:00
|
|
|
|
if (keyword.length == 0) {
|
2026-02-03 18:03:21 +08:00
|
|
|
|
[self hideSearchResults];
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
NSMutableArray *result = [NSMutableArray array];
|
|
|
|
|
|
for (KBPersonaModel *persona in self.personas) {
|
|
|
|
|
|
NSString *name = persona.name ?: @"";
|
|
|
|
|
|
NSString *desc = persona.shortDesc ?: persona.introText ?: @"";
|
|
|
|
|
|
if ([name localizedCaseInsensitiveContainsString:keyword] ||
|
|
|
|
|
|
[desc localizedCaseInsensitiveContainsString:keyword]) {
|
|
|
|
|
|
[result addObject:persona];
|
2026-02-03 16:54:38 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-02-03 18:03:21 +08:00
|
|
|
|
self.searchResults = result;
|
|
|
|
|
|
NSLog(@"[SidebarSearch] 搜索结果: %ld 条", (long)self.searchResults.count);
|
|
|
|
|
|
if (self.searchResults.count > 0) {
|
|
|
|
|
|
[self showSearchResults];
|
|
|
|
|
|
[self.searchResultTableView reloadData];
|
|
|
|
|
|
} else {
|
|
|
|
|
|
[self hideSearchResults];
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
- (void)showSearchResults {
|
|
|
|
|
|
self.isShowingSearchResults = YES;
|
|
|
|
|
|
self.tableView.hidden = YES;
|
|
|
|
|
|
self.searchResultContainer.hidden = NO;
|
|
|
|
|
|
[self.contentView bringSubviewToFront:self.searchResultContainer];
|
|
|
|
|
|
NSLog(@"[SidebarSearch] 显示搜索结果视图");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
- (void)hideSearchResults {
|
|
|
|
|
|
self.isShowingSearchResults = NO;
|
|
|
|
|
|
self.searchResults = @[];
|
|
|
|
|
|
self.searchResultContainer.hidden = YES;
|
|
|
|
|
|
self.tableView.hidden = NO;
|
|
|
|
|
|
NSLog(@"[SidebarSearch] 隐藏搜索结果视图");
|
2026-02-03 16:54:38 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#pragma mark - UITableViewDataSource
|
|
|
|
|
|
|
|
|
|
|
|
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
|
2026-02-03 18:03:21 +08:00
|
|
|
|
if (tableView == self.searchResultTableView) {
|
|
|
|
|
|
return self.searchResults.count;
|
|
|
|
|
|
}
|
2026-02-03 16:54:38 +08:00
|
|
|
|
return self.displayPersonas.count;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
- (UITableViewCell *)tableView:(UITableView *)tableView
|
|
|
|
|
|
cellForRowAtIndexPath:(NSIndexPath *)indexPath {
|
|
|
|
|
|
KBAIPersonaSidebarCell *cell = [tableView dequeueReusableCellWithIdentifier:@"KBAIPersonaSidebarCell"
|
|
|
|
|
|
forIndexPath:indexPath];
|
2026-02-03 18:03:21 +08:00
|
|
|
|
KBPersonaModel *persona = (tableView == self.searchResultTableView)
|
|
|
|
|
|
? self.searchResults[indexPath.row]
|
|
|
|
|
|
: self.displayPersonas[indexPath.row];
|
2026-02-03 16:54:38 +08:00
|
|
|
|
BOOL selected = (persona.personaId == self.selectedPersonaId);
|
|
|
|
|
|
[cell configureWithPersona:persona selected:selected];
|
|
|
|
|
|
return cell;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#pragma mark - UITableViewDelegate
|
|
|
|
|
|
|
|
|
|
|
|
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
|
|
|
|
|
|
return 72.0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
|
2026-02-03 18:03:21 +08:00
|
|
|
|
NSArray *source = (tableView == self.searchResultTableView) ? self.searchResults : self.displayPersonas;
|
|
|
|
|
|
if (indexPath.row >= source.count) {
|
2026-02-03 16:54:38 +08:00
|
|
|
|
return;
|
|
|
|
|
|
}
|
2026-02-03 18:03:21 +08:00
|
|
|
|
KBPersonaModel *persona = source[indexPath.row];
|
2026-02-03 16:54:38 +08:00
|
|
|
|
self.selectedPersonaId = persona.personaId;
|
|
|
|
|
|
[self.tableView reloadData];
|
2026-02-03 18:03:21 +08:00
|
|
|
|
if (self.isShowingSearchResults) {
|
|
|
|
|
|
[self.searchResultTableView reloadData];
|
|
|
|
|
|
}
|
2026-02-03 16:54:38 +08:00
|
|
|
|
if ([self.delegate respondsToSelector:@selector(personaSidebarView:didSelectPersona:)]) {
|
|
|
|
|
|
[self.delegate personaSidebarView:self didSelectPersona:persona];
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#pragma mark - Lazy
|
|
|
|
|
|
|
|
|
|
|
|
- (UIVisualEffectView *)blurView {
|
|
|
|
|
|
if (!_blurView) {
|
|
|
|
|
|
UIBlurEffect *effect = [UIBlurEffect effectWithStyle:UIBlurEffectStyleDark];
|
|
|
|
|
|
_blurView = [[UIVisualEffectView alloc] initWithEffect:effect];
|
|
|
|
|
|
}
|
|
|
|
|
|
return _blurView;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
- (UIView *)contentView {
|
|
|
|
|
|
if (!_contentView) {
|
|
|
|
|
|
_contentView = [[UIView alloc] init];
|
|
|
|
|
|
_contentView.backgroundColor = [[UIColor blackColor] colorWithAlphaComponent:0.25];
|
|
|
|
|
|
}
|
|
|
|
|
|
return _contentView;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
- (UIView *)searchContainer {
|
|
|
|
|
|
if (!_searchContainer) {
|
|
|
|
|
|
_searchContainer = [[UIView alloc] init];
|
|
|
|
|
|
_searchContainer.backgroundColor = [[UIColor whiteColor] colorWithAlphaComponent:0.12];
|
|
|
|
|
|
_searchContainer.layer.cornerRadius = 18;
|
|
|
|
|
|
_searchContainer.clipsToBounds = YES;
|
|
|
|
|
|
}
|
|
|
|
|
|
return _searchContainer;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
- (UIImageView *)searchIconView {
|
|
|
|
|
|
if (!_searchIconView) {
|
|
|
|
|
|
_searchIconView = [[UIImageView alloc] initWithImage:[UIImage systemImageNamed:@"magnifyingglass"]];
|
|
|
|
|
|
_searchIconView.tintColor = [[UIColor whiteColor] colorWithAlphaComponent:0.8];
|
|
|
|
|
|
}
|
|
|
|
|
|
return _searchIconView;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
- (UITextField *)searchField {
|
|
|
|
|
|
if (!_searchField) {
|
|
|
|
|
|
_searchField = [[UITextField alloc] init];
|
|
|
|
|
|
_searchField.placeholder = KBLocalized(@"Search Role");
|
|
|
|
|
|
_searchField.textColor = [UIColor whiteColor];
|
|
|
|
|
|
_searchField.font = [UIFont systemFontOfSize:14];
|
|
|
|
|
|
_searchField.clearButtonMode = UITextFieldViewModeWhileEditing;
|
2026-02-03 17:00:42 +08:00
|
|
|
|
_searchField.returnKeyType = UIReturnKeySearch;
|
2026-02-03 18:03:21 +08:00
|
|
|
|
_searchField.delegate = self;
|
2026-02-03 16:54:38 +08:00
|
|
|
|
[_searchField addTarget:self action:@selector(searchFieldChanged:) forControlEvents:UIControlEventEditingChanged];
|
2026-02-03 18:03:21 +08:00
|
|
|
|
[_searchField addTarget:self action:@selector(searchFieldReturnTapped:) forControlEvents:UIControlEventEditingDidEndOnExit];
|
2026-02-03 16:54:38 +08:00
|
|
|
|
}
|
|
|
|
|
|
return _searchField;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-03 17:00:42 +08:00
|
|
|
|
- (BaseTableView *)tableView {
|
2026-02-03 16:54:38 +08:00
|
|
|
|
if (!_tableView) {
|
2026-02-03 17:00:42 +08:00
|
|
|
|
_tableView = [[BaseTableView alloc] initWithFrame:CGRectZero style:UITableViewStylePlain];
|
2026-02-03 16:54:38 +08:00
|
|
|
|
_tableView.backgroundColor = [UIColor clearColor];
|
|
|
|
|
|
_tableView.separatorStyle = UITableViewCellSeparatorStyleNone;
|
|
|
|
|
|
_tableView.showsVerticalScrollIndicator = NO;
|
|
|
|
|
|
_tableView.delegate = self;
|
|
|
|
|
|
_tableView.dataSource = self;
|
|
|
|
|
|
[_tableView registerClass:[KBAIPersonaSidebarCell class] forCellReuseIdentifier:@"KBAIPersonaSidebarCell"];
|
|
|
|
|
|
}
|
|
|
|
|
|
return _tableView;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-03 18:03:21 +08:00
|
|
|
|
- (UIView *)searchResultContainer {
|
|
|
|
|
|
if (!_searchResultContainer) {
|
|
|
|
|
|
_searchResultContainer = [[UIView alloc] init];
|
|
|
|
|
|
_searchResultContainer.backgroundColor = [UIColor clearColor];
|
|
|
|
|
|
_searchResultContainer.hidden = YES;
|
|
|
|
|
|
}
|
|
|
|
|
|
return _searchResultContainer;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
- (BaseTableView *)searchResultTableView {
|
|
|
|
|
|
if (!_searchResultTableView) {
|
|
|
|
|
|
_searchResultTableView = [[BaseTableView alloc] initWithFrame:CGRectZero style:UITableViewStylePlain];
|
|
|
|
|
|
_searchResultTableView.backgroundColor = [UIColor clearColor];
|
|
|
|
|
|
_searchResultTableView.separatorStyle = UITableViewCellSeparatorStyleNone;
|
|
|
|
|
|
_searchResultTableView.showsVerticalScrollIndicator = NO;
|
|
|
|
|
|
_searchResultTableView.delegate = self;
|
|
|
|
|
|
_searchResultTableView.dataSource = self;
|
|
|
|
|
|
[_searchResultTableView registerClass:[KBAIPersonaSidebarCell class] forCellReuseIdentifier:@"KBAIPersonaSidebarCell"];
|
|
|
|
|
|
}
|
|
|
|
|
|
return _searchResultTableView;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#pragma mark - UITextFieldDelegate
|
|
|
|
|
|
|
|
|
|
|
|
- (BOOL)textFieldShouldReturn:(UITextField *)textField {
|
|
|
|
|
|
if (textField == self.searchField) {
|
|
|
|
|
|
NSLog(@"[SidebarSearch] textFieldShouldReturn");
|
|
|
|
|
|
[textField resignFirstResponder];
|
|
|
|
|
|
[self performSearch];
|
|
|
|
|
|
return YES;
|
|
|
|
|
|
}
|
|
|
|
|
|
return NO;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
- (void)searchFieldReturnTapped:(UITextField *)textField {
|
|
|
|
|
|
if (textField == self.searchField) {
|
|
|
|
|
|
NSLog(@"[SidebarSearch] EditingDidEndOnExit");
|
|
|
|
|
|
[textField resignFirstResponder];
|
|
|
|
|
|
[self performSearch];
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-03 16:54:38 +08:00
|
|
|
|
@end
|